pi-roundtable
v0.9.12
Published
A plugin-driven Pi agent server for one owner, reached through Discord or the web
Readme
pi-roundtable
English | 繁體中文
文件:https://pi-roundtable.wayneh.tw 原始碼與 issue:https://github.com/wayne930242/pi-roundtable
一個建立在 Pi 上的智慧體(agent)伺服器,可以透過 Discord 或網頁聊天跟它對話。 在 Discord 裡,每個 AI 智慧體在你的伺服器有自己的頻道和對話,共用工具與記憶。 在網頁上,經你的 OpenID Connect 供應商登入的人,透過 pi-roundtable-webchat 在各自的私人對話裡跟助理聊天。 想加功能,就用 TypeScript 寫外掛(plugin)。
- 為一位擁有者設計。Discord 和網頁聊天都能用,也可以讓其他人跟助理對話,但開放給多人使用的風險由營運者自行承擔:請先讀威脅模型。
- 只支援 Bun。套件直接發佈 TypeScript 原始碼,不需要建置步驟。
- MIT 授權。
你需要準備
- Bun 1.3 以上。
- PostgreSQL。
init建立的專案附有docker-compose.yml,可以直接啟動一個。 - 模型登入:你選的模型的供應商 API key(
anthropic/...用ANTHROPIC_API_KEY),或之前用 Pi 登入過的帳號。 - 用 Discord 的話:一個 Discord bot(有 bot 使用者、開了 Message Content intent,也已經邀請進你的伺服器),以及一個從網際網路連得到這個行程的位址,例如通道(tunnel)。Discord 要從這個位址抓智慧體的頭像。
- 用網頁聊天的話:一個會為聊天 API 簽發 access token 的 OpenID Connect 供應商,以及一個用 HTTPS 對外提供服務的反向代理。
五分鐘上手
npx pi-roundtable init my-bot # 或:bunx pi-roundtable init my-bot
cd my-bot
bun install
docker compose up -d # PostgreSQL,與 .env.example 一致
cp .env.example .env # 然後填入內容
bunx roundtable doctor
bunx roundtable startinit 會寫出一個能跑的專案,過程不會問你任何祕密資訊。
Bun 沒裝、版本太舊,或要建立的檔案已經存在時,它什麼都不寫。
init --adapter web 則寫出一個沒有 Discord、以網頁聊天為中心的專案,見網頁聊天。
兩種專案都會載入 Pi package pi-self-compact,每個 session 都需要它提供的 compact_session 工具。
.env
.env.example 說明了每個值的來源。
Bun 會自己讀 .env,.gitignore 也已經擋掉它。
| 變數 | 內容 |
| --- | --- |
| DISCORD_TOKEN | bot 的 token,在應用程式的 Bot 頁面取得 |
| DISCORD_GUILD_ID、DISCORD_ENTRY_CHANNEL_ID | 伺服器,以及負責統籌的智慧體所在的頻道(開啟開發者模式後,按右鍵複製 id) |
| OWNER_ID、OWNER_NAME | 你自己:唯一能改動一切的人 |
| DATABASE_URL | PostgreSQL;預設值與 docker-compose.yml 一致 |
| MODEL | 智慧體使用的模型,格式為 <provider>/<id> |
| PUBLIC_URL | 能從網際網路連到這個行程的位址 |
網頁聊天專案要填的是 OWNER_NAME、DATABASE_URL、MODEL,以及 OpenID Connect 的 issuer、audience、簽章金鑰位址、可以聊天的角色,和會開啟聊天的網頁來源(origin)。
doctor
bunx roundtable doctor 依序檢查下列項目,逐項印出通過或失敗,並說明如何修正:
- Bun 的版本。
.env對.env.example列出的每個變數都有值。roundtable.config.ts符合其 schema,失敗時指出是哪個鍵。- 每個外掛都能載入,且沒有兩個外掛同名。
access服務哪些人:列出 owner;主機有不只一個 surface、又寫了everyone: true時提出警告,因為它會讓每個 surface 上的所有人都能對話。- 有沒有外掛填了
images槽位。沒有也不算失敗,智慧體會用顯示名稱產生頭像。 - PostgreSQL 連得上,且能執行 migration。
- principal:預演下次啟動會從資料庫已知的人建立哪些 principal,印出的文字和啟動時的 log 相同;設定裡 owner 的身分若連到別的 principal 就判定失敗,因為主機會因此無法啟動;owner 被停用時提出警告。
- 有設定 Discord 時:Discord token 有效、bot 已在你的伺服器裡、Message Content intent 已開啟,而且 bot 在入口頻道有它需要的權限(包含 Pin Messages)。 bot 還不在伺服器裡的話,它會給你一個邀請連結,連結已經帶好這些權限。
- 模型登入存在。
- 有設定 Discord 時:
PUBLIC_URL的格式正確;加上--reachable還要求它有回應,所以 bot 得先跑起來。
只要有一項失敗就以非零狀態結束,警告不算失敗;檢查過程不會改動任何東西。 全新的專案只會卡在還沒填的憑證,它會列出是哪幾個。
start
bunx roundtable start 先執行不需要網路的檢查,其中任何一項失敗就停下來,並印出與 doctor 相同的訊息;全部通過則啟動 bot。
啟動後,agents.ts 裡的智慧體都有了自己的頻道,/roundtable schedule list 可以列出它們的排程。
收到 SIGTERM 或 SIGINT 時,它會先讓進行中的工作完成再停止。
外掛
roundtable add plugin <name> 會建立 plugins/<name>.ts 和它的測試,並把它列進 roundtable.config.ts。
roundtable add package <spec> 對 npm 上的 Pi package 做同樣的事:安裝套件,並寫好一個載入它的擴充、選用它的工具的外掛。
外掛是一個有名稱和 setup 函式的物件,setup 回傳它要新增的東西;下面這個外掛給每個智慧體一個工具:
import { definePlugin, defineTool } from "pi-roundtable";
import { Type } from "typebox";
export const hello = definePlugin({
name: "hello",
setup: () => ({
tools: [
defineTool({
name: "hello_greet",
description: "Greet someone by name. Call it when asked to say hello.",
parameters: Type.Object({ who: Type.String() }),
minTier: "member",
run: ({ who }) => `Hello, ${who}!`,
}),
],
}),
});測試時不需要 Discord 或 PostgreSQL:
import { expect, test } from "bun:test";
import { testPlugin } from "pi-roundtable/testing";
import { hello } from "./hello.ts";
test("hello greets", async () => {
const harness = await testPlugin(hello);
expect(await harness.runTool("hello_greet", { who: "Ada" })).toBe("Hello, Ada!");
await harness.stop();
});外掛指南(英文)說明外掛能新增的每個部分(工具、提示詞區段、智慧體、事件、服務、migration、provider、斜線指令、HTTP 路由等等)、啟動與停止的順序,以及每一種啟動錯誤和它的修正方式。
指南裡的範例放在 examples/,測試套件會執行每一個範例。
pi-roundtable/kit 提供頻道認領(claim)、工具與呈現用的輔助函式,以及 context 現有服務的純型別名稱。
pi-roundtable/discord 提供斜線指令註冊器、擁有者指令與面板的輔助函式,以及智慧體面板;它是會用到 discord.js 型別的入口(pi-roundtable/testing 也透過 testHost 組合出的指令用到少數幾個)。
這兩個入口的版本規則與主入口相同:1.0 之前,不相容的變更會放在次版本(minor)發佈,並列在變更記錄中。
網頁聊天
套件 pi-roundtable-webchat 是以外掛形式提供的聊天管道:在主機的 listener 上的 /chat 底下提供 WebSocket 和 REST API。
經你的 OpenID Connect 供應商登入的人,可以用你列出的人格(persona)開私人對話,在每一輪執行時看到它寫的文字和用的工具,也能回答暫緩呼叫的核准卡。
設定裡沒有 discord、plugins 列了 webChat({ ... }) 的主機,就是只有網頁的助理;roundtable init --adapter web 會建立這種專案。
npx pi-roundtable init my-desk --adapter web它的 README(英文)說明協定、存取對照、各項上限,以及讓同一個人只有一個身分的供應商設定,例如使用穩定的 subject claim、固定租戶。
MCP connector
獨立套件 pi-roundtable-mcp 用兩個外掛,把 bot 雙向接上 MCP:
mcpConnectors:你在 Discord 用私人表單加入一台 MCP server,例如 Notion、行事曆,或任何用 HTTP 提供 MCP 的服務。 接著由你的程式碼把它的工具交給你選的智慧體。 每台 server 和它的 token 由你自己架的 ContextForge gateway 保管。remoteMcp:Discord 以外的智慧體透過 MCP 傳訊息給你的智慧體,並讀取回答。 它也能使用你授權的 Discord 頻道,而且只限你選的操作。
bun add pi-roundtable-mcp它與 pi-roundtable 同步發版,每個選項都列在它的 README。
設定
roundtable.config.ts 放設定和外掛清單。
未知的鍵會報錯,並指出最接近的已知鍵。
bot 在 Discord 裡顯示的文字語言由 locale 設定決定:預設是 en,也可以是 zh-TW。
export default {
// ...
locale: "zh-TW",
timeZone: "Europe/Berlin", // 排程與時間戳記使用的時區;預設為 UTC
plugins: [hello],
} satisfies RoundtableConfig;有人在 agent 的頻道對助理說話時,那一輪也會讀進助理上次在該頻道發言之後的頻道訊息(頻道脈絡;見外掛指南)。
用 discord.channelContext 調整,或設 channelContext: false 關閉:
| discord.channelContext | 預設 | 說明 |
| --- | --- | --- |
| fetch | 50 | 在被呼叫的訊息之前抓取的訊息數,最多 100 |
| keep | 15 | 保留的訊息數,取最新的 |
| similarity | 0.8 | 同一作者相鄰兩則訊息相似到什麼程度算同一則 |
| messageChars | 500 | 訊息截斷的長度 |
| botMessageChars | 80 | 其他 bot 訊息截斷的長度 |
核准卡和 ask_user 問題會讓這一輪等 discord.cardGraceSeconds 秒(預設 120)。
在這段時間內回答,這一輪就帶著答案繼續;沒回答的話,這一輪會說它在等你,然後結束,卡片繼續開著、沒有期限,之後再回答就會在同一個對話開新的一輪,帶上你的答案,或核准後那個動作的結果。
coding worker 的卡片則會讓 worker 一直等下去。
威脅模型
pi-roundtable 為一位擁有者執行一個助理。 0.9 加入以 principal 為範圍的記憶和私人對話隔離,但模型憑證與主機執行權限仍然共用,還不是完整的多使用者服務;開放給別人使用的風險仍由營運者承擔。
- 信任誰。 營運者掌控主機、設定和憑證。principal 代表主機服務的人,identity 是連到這個人的帳號或憑證。擁有者只能由
access.owners或roundtable principal授予,不能來自 token claim。其他人的member或admintier 由核心access依驗證過的身分與帶 surface 前綴的角色判斷;外掛不能自行指定發言者的權限。 - 一輪能做什麼。 一輪只拿得到發言者的 tier 允許的工具;沒有外掛給 tier 的工具只有擁有者能用。工具在主機的行程裡執行,用的是主機的檔案和網路:
web_search和fetch_content連得到內部位址,所以網頁人格要在selection裡寫明它的工具。外掛也在同一個行程裡執行,權限一樣。 - 核准。 暫緩的呼叫由當事 principal 的任一已連結身分核准,而且當下 tier 仍要足夠。共用對話可以升級給擁有者核准,私人對話則只有本人能答;高於本人 tier 的呼叫會直接過期,不顯示卡片。核准卡和確認訊息都適用這條規則。
- 對話。 Discord 裡智慧體的頻道和群組房間是共用的:在那裡發言的每個人,都會寫進智慧體讀取的同一段對話。網頁聊天的對話屬於開啟它的人:在網頁聊天裡只有開啟者本人能讀、能發言、能回答它的提問,但擁有者可以透過擁有者主控台(pi-roundtable-web)讀取,營運者也能從資料庫和資料目錄讀到。
- 記憶隔離。 記憶依 principal 分開,不依擁有者 tier 判斷。私人對話讀本人的,共用對話讀當輪發言者的;連結 Discord 與網頁身分後,同一個人共用一份記憶。共用對話送給模型的請求和摘要會成對隱藏其他人的記憶工具與載入記憶的 worker 呼叫參數、結果,並依 provider 清除先前回合的 reasoning。其他工具參數和公開回覆仍是公開內容,即使模型在裡面重述私人記憶,也不在這項隔離保證內。第三方摘要器必須先使用
privateCompaction。記憶開啟時,若多位 principal 可在共用對話發言,claude-bridge會在開機與每輪送出前被拒絕,因為它重播未過濾的歷史;請改用其他 provider、設memory: false,或限制只有一位 principal。 - 模型憑證。 每一輪不論誰發言,都用主機的模型登入執行、由它計費。每個人自己的憑證預計在之後的版本加入。
- 資料。 對話、記憶和排程以未加密的形式存在 PostgreSQL 和資料目錄裡。
升級
升級到 0.9(英文)說明 principal 回填、明確的發言者、套件遷移、記憶隔離範圍、prompt 差異和回退方式。 一般單一擁有者的 Discord 主機可以保留舊設定;直接呼叫 runtime 的外掛和網頁整合則有必要的遷移步驟。 升級到 0.8(英文)說明怎麼把 0.7 的 Discord 專案升級(不需要改設定),以及怎麼啟動沒有 Discord 的主機。
指令
| 指令 | 作用 |
| --- | --- |
| roundtable init [dir] [--adapter discord\|web] | 在 dir(預設為目前目錄)建立專案,透過 Discord(預設)或 pi-roundtable-webchat 對話 |
| roundtable doctor [--reachable] | 檢查設定,並說明如何修正有問題的地方 |
| roundtable start | 先執行不需要網路的檢查,再啟動 bot |
| roundtable add plugin <name> | 新增 plugins/<name>.ts 和它的測試,並列進設定 |
| roundtable add package <spec> | 用 bun add 安裝 Pi package,並新增一個載入它、把它的工具交給每一輪 agent 的外掛 |
| roundtable upgrade [--write] | 以 diff 顯示改寫成 0.9 寫法的 roundtable.config.ts(owner 與 speakers 改成 access,discord 改成 adapter);加上 --write 時,先確認改寫後服務的人不變,再寫入 |
| roundtable principal list\|show\|create\|link\|unlink\|grant\|revoke\|disable\|enable | 列出並修改資料庫裡的 principal、它們的身分與角色;執行中的主機在 30 秒內看到變更 |
變更與授權
CHANGELOG.md(英文)列出套件匯出名稱的每一項變更。 MIT。
