@jiantw83/cliproxy (0.1.8)
Installation
@jiantw83:registry=https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/npm install @jiantw83/cliproxy@0.1.8"@jiantw83/cliproxy": "0.1.8"About this package
CLIProxy
CLIProxy 是分散式派工系統的管理端:提供 OpenAI 相容的派工端點
(/api/v1/chat/completions、/v1/completions、/v1/responses),
監控多台 CLIProxyWorker,並提供一個繁體中文的
管理介面(金鑰管理、派工紀錄、系統日誌、CLI 工具狀態、模型路由、
測試呼叫)。管理介面與管理 API 一律要求以 Gitea OAuth2 登入且僅限
系統管理員(is_admin)使用。
系統需求
- Node.js ≥ 20.11.0
- 一個資料庫:SQLite(預設,免安裝)/PostgreSQL/SQL Server
- 一個 Gitea 站台(供 OAuth2 登入),以及一個對該站台有效的 Personal Access Token(僅首次設定時使用一次)
安裝
CLIProxy 發布在本組織 Gitea 的 npm package registry(非公開的 npmjs.com),安裝前要先讓 npm 知道去哪裡拉套件、並帶上有讀取權限的 Gitea token。
方式一:npm 安裝(推薦)
-
設定 registry 與認證(擇一即可):
- 全域設定(寫進
~/.npmrc,之後每次安裝/更新都不用再帶):npm config set @jiantw83:registry https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/ npm config set //gitea.jsc.idv.tw/api/packages/jiantw83/npm/:_authToken <你的 Gitea token> - 或單次安裝時用參數帶入(不寫進任何設定檔):
npm install -g @jiantw83/cliproxy \ --@jiantw83:registry=https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/ \ --//gitea.jsc.idv.tw/api/packages/jiantw83/npm/:_authToken=<你的 Gitea token>
- 全域設定(寫進
-
全域安裝:
npm install -g @jiantw83/cliproxy安裝完成後可直接用
cliproxy指令(scope 只影響套件名稱,不影響 指令名稱);前端靜態檔已預先建置並隨套件發布,不需要另外cd web && npm install && npm run build。 -
依
DATABASE_PROVIDER(未設定則預設sqlite)產生 schema 並套用 migration:cliproxy migrate非
sqlite的 provider 必須先設定DATABASE_URL才能執行本指令 (見下方「環境變數」)。 -
啟動服務:
cliproxy start # 監聽 PORT(預設 3000),前端由同一個服務靜態提供
Gitea token 需具備該 repo 的讀取權限,向管理員索取或在 Gitea
「設定 → 應用程式」自行建立(read:package 權限即可)。
首次啟動後,用瀏覽器開啟 http://<host>:<port>/,會看到「首次設定」
畫面:貼上 Gitea 站台網址與 Personal Access Token 後送出,系統會
自動在該 Gitea 站台建立一個 OAuth 應用程式並算出回呼網址,接著導向
Gitea 完成登入(僅 is_admin 帳號能登入成功)。
完成設定後的登入頁會多一個「重設 Gitea OAuth 設定」入口,需要一把
對該站台有效且擁有者為系統管理員的 PAT,送出後會刪除 Gitea 端的
CLIProxy OAuth 應用程式與本機設定、所有登入 session 一併失效,畫面
回到「首次設定」,用於 OAuth 應用程式被刪、client_secret 不符或
回呼網址變更導致登入卡死時自救。
方式二:從原始碼建置
git clone https://gitea.jsc.idv.tw/jiantw83/CLIProxy.git
cd CLIProxy
npm install
npm run prisma:generate # 產生 Prisma Client
npm run prisma:migrate # 依 DATABASE_PROVIDER 產生對應 schema 並套用 migration
cd web && npm install && npm run build && cd ..
npm run build
npm start # 監聽 PORT(預設 3000),前端由同一個服務靜態提供
首次啟動後的設定步驟同「方式一」。
首次啟動後,用瀏覽器開啟 http://<host>:<port>/,會看到「首次設定」
畫面:貼上 Gitea 站台網址與 Personal Access Token 後送出,系統會
自動在該 Gitea 站台建立一個 OAuth 應用程式並算出回呼網址,接著導向
Gitea 完成登入(僅 is_admin 帳號能登入成功)。
完成設定後的登入頁會多一個「重設 Gitea OAuth 設定」入口,需要一把
對該站台有效且擁有者為系統管理員的 PAT,送出後會刪除 Gitea 端的
CLIProxy OAuth 應用程式與本機設定、所有登入 session 一併失效,畫面
回到「首次設定」,用於 OAuth 應用程式被刪、client_secret 不符或
回呼網址變更導致登入卡死時自救。
更新
方式一:npm 更新
npm update -g @jiantw83/cliproxy
cliproxy migrate # 有新 migration 時套用
若要指定更新到某個版本,改用
npm install -g @jiantw83/cliproxy@<版本號>。
方式二:從原始碼更新
git pull
npm install
npm run prisma:generate # 重新產生 Prisma Client
npm run prisma:migrate # 有新 migration 時套用
cd web && npm install && npm run build && cd ..
npm run build
不論哪種方式,重新啟動服務即可;Gitea OAuth 設定與 Session 皆存在 資料庫/記憶體中不受影響,Worker 端不需重新註冊。
移除
方式一:npm 解除安裝
npm uninstall -g @jiantw83/cliproxy
方式二:從原始碼安裝時的移除
停掉服務後,刪除整個資料夾即可。
兩種方式都一樣:資料庫檔案(SQLite 預設在 prisma/sqlite/dev.db,
或依你設定的 DATABASE_URL)需另外手動刪除;若要順手撤銷 Gitea 端
建立的 OAuth 應用程式,有兩條路:在登入頁以具系統管理員身分的 PAT
執行「重設 Gitea OAuth 設定」,CLIProxy 會一併刪除 Gitea 端名稱為
CLIProxy 的 OAuth 應用程式;或直接到 Gitea 後台手動刪除(直接刪掉
資料庫檔的情境只能走這條,CLIProxy 沒有機會反向刪除)。
環境變數
| 變數 | 預設值 | 說明 |
|---|---|---|
PORT |
3000 |
HTTP 監聽埠號 |
DATABASE_PROVIDER |
sqlite |
sqlite/postgresql/sqlserver 三選一,決定套用哪個 prisma/providers/<provider>.prisma 與 migration 目錄 |
DATABASE_URL |
provider 為 sqlite 時預設 file:prisma/sqlite/dev.db,其餘 provider 必填 |
Prisma 連線字串 |
WORKER_OFFLINE_MS |
300000(5 分鐘) |
超過多久沒收到心跳視為 Worker 離線 |
WORKER_CONCURRENCY_LIMIT |
4 |
健康分數 C 因子計算壓力時,假設每台 Worker 的可並行任務數上限;與 Worker 端 TASK_CONCURRENCY 預設值一致 |
HEARTBEAT_INTERVAL_MS |
30000 |
期望的心跳間隔,用於健康分數 Q 因子的新鮮度判斷 |
CLI_LIFECYCLE_TASK_TIMEOUT_MS |
300000 |
安裝/解除安裝/啟用/停用任務的逾時秒數 |
RETENTION_METRIC_SAMPLE_MAX_COUNT / _MAX_DAYS |
— | 時序樣本環狀保留策略(近 N 筆/近 N 天) |
RETENTION_SYSTEM_LOG_MAX_COUNT / _MAX_DAYS |
— | 系統日誌環狀保留策略 |
TASK_CLAIM_POLL_INTERVAL_MS |
150(下限 50) |
Worker 長輪詢向佇列嘗試認領任務的間隔;低於下限會被夾到下限,避免打成緊迴圈 |
TASK_CLAIM_MAX_BATCH |
4 |
一次長輪詢最多能認領的任務筆數上限,預設與 Worker 端 TASK_CONCURRENCY 一致;Worker 端可用 max query 要更少,但不能超過這個上限 |
TASK_PREFETCH_GRACE_MS |
60000 |
已認領但尚未開始執行(被預取)的任務,等多久才視為逾時;任務真的開始執行後改以 timeoutMs 起算 |
CREDENTIAL_REFRESH_CHECK_INTERVAL_MS |
300000(5 分鐘) |
自動憑證刷新排程的掃描間隔 |
CREDENTIAL_REFRESH_LEAD_MS |
1800000(30 分鐘) |
登入到期前多久開始自動送出刷新任務 |
CREDENTIAL_REFRESH_MIN_INTERVAL_MS |
600000(10 分鐘) |
同一工具兩次自動刷新之間的最短間隔,避免刷不動時打成迴圈 |
SQLITE_BUSY_TIMEOUT_MS |
15000 |
SQLite PRAGMA busy_timeout:併發寫入互搶檔案鎖時的重試等待上限,只在 DATABASE_PROVIDER=sqlite 生效 |
GITEA_CA_CERT |
無 | 指向 PEM 檔(可為含多張憑證的 bundle)路徑,把該 CA 加入信任清單後仍完整驗證對 Gitea 的 9 個出站呼叫(OAuth 應用程式管理、登入回呼、清除設定);檔案無法讀取時 fail-closed,記一則 ERR 並維持預設驗證,不會自動退化成忽略驗證 |
GITEA_TLS_INSECURE |
0 |
設為 1/true 時,對 Gitea 的出站呼叫不驗證 TLS 憑證;只作用於這 9 個呼叫,不影響 CLIProxy 其他任何 TLS 連線;與 GITEA_CA_CERT 同時設定時以後者為準;啟動時記一則 WRN、管理介面全站顯示警示標誌,僅適合在可信任網路使用 |
| WORKER_RELEASES_TOKEN | 無 | 抓 CLIProxyWorker 版本發布紀錄正本用的 Gitea 存取 token;設定檔的 workerReleasesToken 優先,這個變數只是備援 |
| WORKER_RELEASES_URL | CLIProxyWorker repo 的 contracts/worker-releases.ts raw 網址 | 版本發布紀錄正本位置,組織的 Gitea 網址結構不同時可覆蓋 |
| WORKER_RELEASES_REFRESH_MS | 600000(10 分鐘) | 重抓版本發布紀錄正本的間隔 |
| CLIPROXY_CONFIG_FILE | ~/.config/cliproxy/config.json | 設定檔位置,非標準部署或測試時可覆蓋 |
設定檔
不適合放資料庫、也不方便放環境變數的機密(例如以 npx/全域安裝執行時
沒有專案目錄可放 .env)放在本機設定檔,位置與格式比照 CLIProxyWorker 的
~/.config/cliproxyworker/config.json:
// ~/.config/cliproxy/config.json(建議權限 600)
{
// 抓 CLIProxyWorker 版本發布紀錄正本用的 Gitea 存取 token(該 repo 為私有)
"workerReleasesToken": "<gitea token>"
}
- 每次要用到時才重讀,改完不必重啟,下一個排程週期就生效。
- 檔案不存在是正常情況(沒設定過);內容不是合法 JSON 物件時只記一則 WRN 並當作沒設定,不會讓伺服器啟動失敗。
- 位置可用
CLIPROXY_CONFIG_FILE覆蓋。
Gitea OAuth2 設定不透過環境變數,而是透過管理介面「首次設定」
畫面呼叫 POST /api/auth/register(giteaBaseUrl/
personalAccessToken/選填的 clientSecret)在啟動後設定一次,
設定結果存在資料庫的 GiteaAuthConfig 表,可隨時用新的 PAT 再次呼叫
該端點覆蓋設定。
Worker 註冊流程
- 系統在啟動或首次讀取時會自動建立一組一次性註冊 token;管理介面 的「註冊 token 管理」區塊可檢視、複製與重新產生。
- 在要跑 CLIProxyWorker 的機器上安裝並執行:
成功後會在
cliproxyworker register --server https://<CLIProxy 位址> --token <剛才產生的註冊 token>~/.config/cliproxyworker/config.json(權限 600)寫入workerId/workerToken;同一台機器只需註冊一次。 - 執行
cliproxyworker daemon啟動常駐程序,開始每隔HEARTBEAT_INTERVAL_MS上報系統資訊與 CLI 工具狀態、以 socket 優先領取任務; socket 不可用時會自動回退長輪詢。 - 回到管理介面「總覽」頁應該立刻看到該 Worker 上線;「CLI 工具」頁 可對它下發安裝/啟用等操作。
已知限制
- 三 provider 驗證(I-2):
scripts/prisma-cli.mjs已針對postgresql/sqlserver產生對應 schema 並以prisma validate確認語法正確(含@LongText依 provider 置換為@db.Text/@db.NVarChar(Max)),但目前的開發環境沒有 Docker 也沒有可連線的 PostgreSQL/SQL Server 執行個體,因此尚未實際執行過這兩個 provider 的migrate deploy與一次完整派工。SQLite 這條路徑已由完整測試 套件(真的 SQLite,涵蓋 migration/心跳/派工/健康分數/模型 路由等)反覆驗證。若要補齊,需要一個可連線的 PostgreSQL 或 SQL Server 執行個體,設定DATABASE_PROVIDER/DATABASE_URL後跑npm run prisma:migrate即可沿用同一套流程驗證。 - F-8/F-9 的模型選擇已支援
model: "auto"或省略model時由AutoModelSelectorService依提示內容自動挑選(需求 6),挑選理由會 回傳於回應的autoReason欄位;測試呼叫頁的執行者/工具/模型皆可 選「自動」。 - E-1 的註冊 token 目前僅保留列出、複製、重新產生與撤銷;首次建立 由系統自動處理,管理介面「註冊 token 管理」頁可直接操作。
- PROTOCOL_VERSION 已 bump 至
1.3.0(模型 Tag 語意變更:CliToolModelInfo.tag由「CLI 別名」改為formatModelTag()產生的正規化模型 Tag,派工與 CLI 呼叫一律以此為準):舊版 CLIProxyWorker 回報1.2.0以下心跳時會被ProtocolVersionGuard以 409 拒絕,必須同時升級 CLIProxyWorker 到含本次變更的版本才能 繼續運作,不支援新舊版本混跑。 - Worker 軟體版本檢查(與上面的 PROTOCOL_VERSION 是兩件事):
contracts/worker-releases.ts記錄 CLIProxyWorker 每個版本新增了 哪些功能(WORKER_RELEASES),CLIProxyWorker 切版本時需同步更新 這份複本;src/worker/worker-version-policy.ts的REQUIRED_WORKER_FEATURES只列伺服器目前依賴的功能名稱,版本號 一律查前者,取交集後的最大值即為MIN_WORKER_VERSION。Worker 回報 的workerVersion低於此門檻(或從未回報過)時,TaskQueueService仍讓它正常註冊/心跳,只是claimBatch不會再派任何任務給它,管理 介面「已註冊執行者」列表會顯示「版本過舊已停用派工」。 - 模型 Tag 正規化規則:
formatModelTag()(contracts/cli-tool.ts) 把 CLI 工具回報的原始模型 id 正規化成對外唯一的模型 Tag—— trim+小寫 → 去除供應商前綴(openai//anthropic//google//github//models/) → 版本段以點號合併(-4-5→-4.5)直到 字串不再變化;不同 CLI 工具對同一模型的不同寫法(例如claude-haiku-4-5與claude-haiku-4.5)會合併成同一個 Tag, 之後派工、健康分數、管理介面一律以 Tag 稱呼模型。 - 模型實測結論僅供顯示、不影響派工:
ModelProbe的實測結論 (available/temporary-failure/permanent-failure)不再壓低 或排除任何{模型}-{執行者}-{工具}組合的派工資格,只出現在ModelSummary.status/routes[].probeConclusion供前端顯示;GateState已移除probe-temporary-failure/probe-permanent-failure兩個狀態。 - 模型路由頁的啟用/停用以顏色反映狀態:「模型操作」欄的按鈕改為 狀態色——啟用中為綠底白字「● 已啟用」,停用中為灰底深灰字 「○ 已停用」,點擊即切換;顏色之外仍保留文字與符號,避免只靠顏色 辨識造成色覺障礙使用者無法分辨。
- PROTOCOL_VERSION 已 bump 至
1.4.0(新增 CLI 工具版本檢查相關 欄位:CliToolStatus.latestVersion/updateCheckSupported): Worker 端會在心跳裡順便回報每個 CLI 工具目前是否有新版可用,管理 介面「CLI 工具」頁只會顯示提示(工具名稱後標記•、卡片內顯示 「有新版 x.y.z 可更新」、詳細清單新增「最新版本」欄),不會 自動送出更新任務,是否更新仍由使用者手動按下既有的「更新」按鈕 決定。與先前的 PROTOCOL_VERSION bump 相同,isProtocolVersionCompatible()是嚴格相等比對,Worker 與 CLIProxy 必須同時升級到含本次變更的版本 才能繼續運作,不支援新舊版本混跑。 - PROTOCOL_VERSION 已 bump 至
1.5.0(模型清單新增價格與上下文窗口:CliToolModelInfo.pricing/context/infoSource):Worker 端在心跳的 每個模型上多回報「價格」(顯示用原字串如free/$0.25/1.5/$20/月, 外加折算成 USD/每百萬 token 的可比較數值)與「上下文窗口」(token 數 近似值與250K這類顯示字串,由formatContextWindowLabel()產生)。 兩者的 undefined/null 語意與CliToolStatus.quota一致:欄位省略代表 Worker 版本還不支援,伺服器維持既有值不動;明確給 null 才視為查不到而 清空。資料寫入CliToolModel,同一個模型 Tag 由多個工具回報時以src/routing/model-economics.ts收斂(cli自陳優先於catalog內建 對照表,同級取較新的一筆)。與先前的 bump 相同,Worker 與 CLIProxy 必須 同時升級,不支援新舊版本混跑;對應的 Worker 功能名稱為cli-tool-model-pricing-context(由 CLIProxyWorker 那邊決定掛在哪一版),但未列入REQUIRED_WORKER_FEATURES,舊 Worker 只是沒有這兩項資訊,不會被停止派工。 - 價格與上下文窗口會進入自動判斷:
modelTags()依回報數值多產生free/cost-efficient/premium-cost/long-context/short-context標籤(沒回報就不加,不用模型名稱去猜價格);AutoModelSelectorService在model: "auto"挑選時,會先粗估提示的 token 數(約 2.5 字元/token, 刻意高估)加上預留輸出 token,塞不進上下文窗口的模型直接排除、 佔用超過窗口 70% 者扣分,單價為 0/低於 1 USD 每百萬 token 者加分、 高於 5 者扣分,理由字串會一併帶出「免費」「單價較低」「長上下文」。 管理介面「模型路由」頁新增「價格」「上下文窗口」兩欄(滑鼠移上去可看 折算後的單價與資料來源)。 - Worker 版本清單存取 token 放本機設定檔:CLIProxyWorker 是私有 repo,
WorkerReleasesFetchService去抓contracts/worker-releases.ts正本時需要一組 具讀取權限的 Gitea token,沒有就只會拿到 HTTP 404、一直沿用內建的舊複本。 token 放在設定檔~/.config/cliproxy/config.json的workerReleasesToken(見下方「設定檔」),不經管理介面設定、也不存資料庫;每次抓取都重讀設定檔, 改完不必重啟。WORKER_RELEASES_TOKEN環境變數仍可用,但只在設定檔沒填時採用。 token 明文只在抓取當下進到Authorization標頭,不寫進任何日誌。 contracts/worker-releases.ts已重新對齊 CLIProxyWorker 正本:先前這份手動複本 與正本的版本號對不起來(複本把worker-version-self-report記在0.2.0,正本是0.0.10),已整份改成正本現況(最新一筆0.1.2)。因此MIN_WORKER_VERSION會是0.0.10而非0.2.0;這也是抓取正本這個機制存在的原因——token 設定好之後,這份複本 只是開機時的墊底值,每 10 分鐘就會被正本覆蓋。- 「CLI 工具」頁的「開新視窗」與「登入」是兩種不同用途的互動終端:
「開新視窗」開出一般互動終端(
purpose=shell),對 kiro 而言會直接 進入聊天 TUI;「登入」開出登入專用終端(purpose=login),對有設定auth.loginArgs的工具(目前僅 kiro)會執行該工具自身的登入子指令 (例如kiro-cli login),其餘工具的「登入」目前仍是裸指令,與「開 新視窗」行為一致。兩者各自以獨立瀏覽器視窗開啟,視窗名稱依worker、工具、purpose區分,不會互相覆蓋彼此的視窗。 - 企業防火牆重簽 TLS 時 Gitea 登入失敗(
NODE_EXTRA_CA_CERTS/GITEA_CA_CERT/GITEA_TLS_INSECURE): 若站台憑證被企業防火牆重簽(例如 issuer 為O=Fortinet, CN=FG6H1ETB21900327這類設備憑證),對 Gitea 的出站呼叫(OAuth 應用程式管理、登入回呼)會以TypeError: fetch failed(causeSELF_SIGNED_CERT_IN_CHAIN)失敗, 伺服器日誌顯示「登入回呼發生非預期例外」,因為 Node 的fetch不使用 系統 CA store。判斷方式:curl打得通同一個 URL、但node -e "fetch(...)"打不通,即為此問題。三者的關係與適用時機:NODE_EXTRA_CA_CERTS:作用於整個 Node 行程的所有 TLS 連線,適合 「除了 Gitea 之外,行程本身還有其他站台也被同一個防火牆攔截」的情況; 例如:NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt npm run devGITEA_CA_CERT:只作用於gitea-auth.service.ts對 Gitea 的 9 個出站 呼叫,仍完整驗證憑證,範圍比NODE_EXTRA_CA_CERTS更收斂,一般 情況下優先使用這個;指到系統 CA bundle(例如/etc/ssl/certs/ca-certificates.crt)或匯出的防火牆設備憑證皆可。GITEA_TLS_INSECURE:最後手段,明示對 Gitea 呼叫不驗證憑證, 等同關掉這 9 個呼叫的中間人攻擊防護(client_secret、authorization code、access token 皆可能被攔截竄改),只在真的無法取得可用 CA 檔時 才使用,且僅限於可信任網路;開啟後管理介面全站會顯示「目前未驗證 Gitea TLS 憑證」警示,日誌也會記一則 WRN。 三者可以並存:GITEA_CA_CERT與GITEA_TLS_INSECURE同時設定時以GITEA_CA_CERT為準,並多記一則 WRN 說明GITEA_TLS_INSECURE本次 被忽略;NODE_EXTRA_CA_CERTS不受這兩個變數影響,仍可單獨沿用。 (2026/08/19)
- 派工紀錄頁「嘗試次數」欄語意(原「嘗試順序」):一次派工的
attemptSequence是總嘗試次數,不是第幾次成功的順序——分類 階段(判斷該提示屬於哪種問題類別)與處理階段(實際呼叫 CLI)各算 一次,因此一次零失敗的正常派工也會顯示 2。展開列不再只限失敗的 紀錄,成功的紀錄也能展開查看每一次嘗試明細,並在每一列標示該次是 「分類階段」或「處理階段」(依problemCategory是否為null判斷)。(2026/08/19) - 派工紀錄頁「嘗試次數」欄只計處理階段:上一項讓「嘗試次數」欄
正名,但顯示值仍是分類+處理兩階段加總的
attemptSequence,把 「跑完 2 個階段」誤講成「重試過 1 次」。現在該欄改顯示DispatchRecord.processAttemptCount——只計處理階段(problemCategory非null)的嘗試次數,分類階段不計入但展開明細仍完整看得到、並 維持原本的「分類階段/處理階段」標示;attemptSequence(總呼叫 次數)本身不變,仍是判斷是否可展開(> 0)的依據。既有 438 筆 舊資料已一次性回填processAttemptCount;回填不到的歷史紀錄(例如 完全沒有嘗試明細、或當時仍是pending)該欄顯示—,不會顯示成0。(2026/08/20) - 總覽頁面板的資料來源與空狀態:金鑰統計只納入未撤銷(
revokedAt為null)的金鑰,已撤銷金鑰的歷史統計仍留在金鑰管理頁查看; 「資源走勢」與「CLI 資源占用」兩個面板改為區分「載入失敗」與 「目前真的沒有樣本」兩種空狀態——端點回應非 2xx 時顯示紅字載入 失敗訊息(含 HTTP 狀態碼),不再與正常的空樣本狀態混淆。 (2026/08/19) - 派工端點的
executor必須填 workerId,不是執行者的人類可讀名稱:POST /api/v1/chat/completions(及/v1/completions、/v1/responses) 選填的executor欄位,在DispatchService.buildFilteredChain()是拿去跟RankedComboDetail.executor(即ChainBuilderService.buildChain()產出的row.workerId)做字串全等 比對;因此executor必須傳「Worker 註冊流程」取得的workerId(例如cmswwqhf10004blp0eax80vu0),而不是 CLI 工具或執行者的名稱 (例如claude)。三欄位(model+executor+tool)齊帶時,若executor填成非 workerId 的字串,篩選鏈必為空、固定回應{ "message": "沒有任何可用的 {模型}-{執行者}-{工具} 組合" }(HTTP 503);這是預期行為,不是 bug。要固定使用某台 Worker 派工時, 請到管理介面「總覽」或「CLI 工具」頁複製該 Worker 的workerId,tool才是 CLI 工具名稱(例如claude/copilot/kiro/antigravity)。(2026/08/20) - 併發 16 崩潰的真正根因是單台 Worker 容量不足,不是已知失效候選混入
推薦鏈:壓測報告(P1/P2-重測)原判斷併發 16 效能崩潰是因為 auto
推薦鏈仍會嘗試
copilot(未登入)/antigravity(憑證刷新失效)這類 已知失效候選,但該判斷已過期——not-logged-in/credential-refresh-ineffective兩個閘門狀態已於 2026/08/19(commit7b9ba1e)納入SKIP_DISPATCH_GATE_STATES(src/routing/chain-builder.service.ts), 早於 P2-重測執行時間。實際核對 P2-重測期間(2026/08/20 09:14~11:01) 資料庫裡全部 24 筆「分類階段全部失敗」紀錄的DispatchAttempt明細, 48 筆嘗試全部是timeout,執行者/工具皆為claude/kiro(同一 台 Worker),沒有任何 copilot/antigravity 嘗試紀錄。真正瓶頸是:某些 模型 Tag(如claude-haiku-4.5)在本機只有 2 個可用組合(claude/kiro),且兩者都指向同一台 Worker(TASK_CONCURRENCY=4),併發 16 時這些組合的等待時間超過CLASSIFY_TIMEOUT_MS(30,000ms)而逾時失敗, 與候選是否「已知失效」無關——這些組合本身是健康、已登入的正常候選。 依此結論,不在buildFilteredChain()/ChainBuilderService加入 候選健康度快取或跳過機制(該類機制對應到已在 commit76635c4(2026/08/14)刻意移除的 K 失敗冷卻因子,重新加入 需要重新評估是否違反 I.7「健康分數只影響排序、不剔除任何組合」的既有 設計原則,且對本次觀察到的逾時模式沒有直接幫助)。後續若要真正緩解, 建議方向為調高CLASSIFY_TIMEOUT_MS、調高TASK_CONCURRENCY、或增加 可承載該模型 Tag 的 Worker/CLI 工具數量,三者皆屬容量規劃決策,需 另行裁示與驗證,不在本次代辦處理範圍內。(2026/08/20) - 併發 8 表現不一致,重跑後確認並非 SQLite 版本差異造成:壓測報告
記載併發 8 的成功率在 P0-重測(60.0%)與 P1-重測(100.0%)間差異極大,
當時懷疑是
connection_limit=1修復(commit909dd15)前後版本差異。 以修復後版本(a9adbcb加上群組 A 的文件/註解修正,未改動派工邏輯) 重跑一次併發 8(20 次請求,scripts/stress-results/followup-c8.jsonl), 結果成功率仍為 60.0%(12/20),e2e p95 為 121,497ms(基準線 33,938ms 的約 3.58 倍,超過「≤ 3 倍」門檻),皆未達 §1.3 門檻;失敗 的 8 筆全部一樣是claude-haiku-4.5/gpt-5.4-mini這類只有 1~2 個 可用組合的模型在同一台 Worker 上逾時(與上一條「併發 16 崩潰」同根因)。 由於 SQLite 修復已經在本次測試版本中、仍重現與 P0-重測相同的 60.0%,可排除「SQLite 版本差異」這個假設;原本 P0/P1 兩次併發 8 結果不一致,更可能是每次執行時AutoModelSelectorService挑到的 模型 Tag 恰好有多少可用組合(進而是否會撞上單一 Worker 容量瓶頸) 批次間隨機波動所致,而不是版本或程式碼差異。(2026/08/20) WORKER_OFFLINE_MS/TASK_PREFETCH_GRACE_MS不是聊天派工端點實際生效 的失敗判定門檻:這兩個環境變數分別用於「判定 Worker 整體離線」與 「判定已認領但尚未開始執行的任務逾時」,但POST /api/v1/chat/completions等聊天派工端點在分類階段與處理階段各自以程式碼常數CLASSIFY_TIMEOUT_MS(30,000ms)/PROCESS_TIMEOUT_MS(120,000ms) 為單一 combo 的等待上限(DispatchService.awaitTaskResult再加 10 秒 寬限才回報逾時),兩者皆早於WORKER_OFFLINE_MS預設的 300,000ms 觸發, 目前也不可用環境變數調整。實測對照:Worker daemon 整台停止時, 推薦鏈會對鏈上每個 combo 依序等滿「分類階段 40 秒」才換下一組,總計 140~160 秒即對應多個 combo 依序逾時的加總;模擬單一 CLI 呼叫卡住 (SIGSTOP)時,只有當下那個 combo 受影響,40 秒(分類階段)左右即 回報逾時,與實測的約 42.5 秒相符。(2026/08/20)
開發
npm run dev # NestJS 熱重載
npm run test # 後端測試(真的 SQLite,不用 mock)
npm run lint
cd web && npm run dev # 前端獨立開發伺服器(另開一個終端)
更新時間:2026/08/20 17:45:47(Asia/Taipei)
Dependencies
Dependencies
| ID | Version |
|---|---|
| @nestjs/common | ^11.0.0 |
| @nestjs/core | ^11.0.0 |
| @nestjs/platform-express | ^11.0.0 |
| @nestjs/platform-socket.io | ^11.2.1 |
| @nestjs/serve-static | ^5.0.0 |
| @nestjs/websockets | ^11.2.1 |
| @prisma/client | ^6.0.0 |
| commander | ^12.1.0 |
| cookie-parser | ^1.4.7 |
| prisma | ^6.0.0 |
| reflect-metadata | ^0.2.2 |
| rxjs | ^7.8.1 |
| socket.io | ^4.8.3 |
Development Dependencies
| ID | Version |
|---|---|
| @eslint/js | ^9.9.0 |
| @nestjs/cli | ^11.0.0 |
| @nestjs/testing | ^11.0.0 |
| @types/cookie-parser | ^1.4.10 |
| @types/express | ^5.0.6 |
| @types/jest | ^29.5.0 |
| @types/node | ^20.11.0 |
| eslint | ^9.0.0 |
| eslint-config-prettier | ^9.1.0 |
| jest | ^29.7.0 |
| prettier | ^3.3.0 |
| source-map-support | ^0.5.21 |
| ts-jest | ^29.1.0 |
| ts-node | ^10.9.2 |
| typescript | ^5.5.0 |
| typescript-eslint | ^8.0.0 |