agentflowctl 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -16
- package/dist/agentConfig.js +6 -11
- package/dist/agents/claude.js +2 -2
- package/dist/agents/codex.js +6 -3
- package/dist/agents/gemini.js +2 -2
- package/dist/agents/index.js +0 -2
- package/dist/agents/types.js +14 -0
- package/dist/cleanup.js +54 -0
- package/dist/cli.js +120 -48
- package/dist/config.js +2 -0
- package/dist/detect.js +82 -0
- package/dist/engine.js +23 -13
- package/dist/logs.js +173 -0
- package/dist/paths.js +4 -2
- package/dist/proc.js +1 -4
- package/dist/runner.js +30 -26
- package/dist/schemas.js +2 -2
- package/dist/setup.js +70 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,15 +10,33 @@
|
|
|
10
10
|
|
|
11
11
|
## 快速開始
|
|
12
12
|
|
|
13
|
-
需要 Node.js 22 以上與 git。不需要全域安裝,直接用 `npx` 執行。先讓各家 CLI
|
|
13
|
+
需要 Node.js 22 以上與 git。不需要全域安裝,直接用 `npx` 執行。先讓各家 CLI 完成登入:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
claude # 完成登入
|
|
17
17
|
codex # 完成登入
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
沒有內建的 agent,只會使用 `flow.config.json` 的 `agents` 裡設定的。用 `agent setup` 互動設定:它會偵測本機的 `claude`、`codex`、`gemini`,逐一詢問要不要加入、名稱與 model,再設定輪替順序;確認後才一次寫入,最後自動跑一次 `doctor`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx agentflowctl agent setup
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
也可以不經互動,直接用指令新增:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx agentflowctl agent add claude --adapter claude
|
|
30
|
+
npx agentflowctl agent add codex --adapter codex
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
之後改了設定或換了環境,用 `doctor` 檢查。它會列出設定的 agent 的 CLI 是否已安裝,並印出即將使用的輪替順序。沒有設定 `cycle` 時,依 `agents` 的順序取已安裝的;一個都沒偵測到就無法執行。
|
|
34
|
+
|
|
35
|
+
```bash
|
|
18
36
|
npx agentflowctl doctor
|
|
19
37
|
```
|
|
20
38
|
|
|
21
|
-
`
|
|
39
|
+
從舊版升級:以前沒寫 `agents` 時會自動使用內建的 claude、codex、gemini,現在不會了,要先用 `agent setup` 或 `agent add` 補上。舊設定裡的 `removedAgents` 已不再使用,可以刪掉。
|
|
22
40
|
|
|
23
41
|
在專案資料夾內開始一次 run:
|
|
24
42
|
|
|
@@ -81,10 +99,13 @@ agentflowctl run --req "..." --manual-plan # 計畫通過 AI 審查後,仍
|
|
|
81
99
|
agentflowctl approve f-xxxx # 搭配 --manual-plan
|
|
82
100
|
agentflowctl status f-xxxx # 階段、任務進度、各 agent 用量、代打紀錄
|
|
83
101
|
agentflowctl list
|
|
84
|
-
agentflowctl logs f-xxxx
|
|
102
|
+
agentflowctl logs f-xxxx # 列出每一份 log 的編號、結果、階段、步驟、agent
|
|
103
|
+
agentflowctl logs f-xxxx 7 # 解析第 7 份 log,最後附上錯誤整理(--latest 看最新一份)
|
|
104
|
+
agentflowctl logs f-xxxx 7 --raw # 原始內容(agent 的 JSON 行)
|
|
85
105
|
agentflowctl resume f-xxxx # 從暫停、Ctrl-C 或失敗處接續
|
|
86
106
|
agentflowctl cancel f-xxxx
|
|
87
107
|
agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保留
|
|
108
|
+
agentflowctl clean --all # 清掉所有已結束的 run 與中斷留下的 worktree
|
|
88
109
|
```
|
|
89
110
|
|
|
90
111
|
| 選項 | 作用 |
|
|
@@ -94,12 +115,110 @@ agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保
|
|
|
94
115
|
| `--cycle` | 這次 run 的輪替順序,例如 `claude,codex,gemini`;建立後就固定,`resume` 沿用 |
|
|
95
116
|
| `--max-agent-runs` | 這次 run 的 agent 執行次數上限 |
|
|
96
117
|
| `--manual-plan` | 計畫通過審查後進入 `awaiting_approval`,等 `approve` 才開始實作 |
|
|
118
|
+
| `-v` / `--verbose` | 執行時印出 agent 的文字、工具呼叫與專案指令;`run`、`resume`、`approve` 都適用,也可設 `AGENTFLOWCTL_VERBOSE=1` |
|
|
97
119
|
|
|
98
120
|
`status` 會列出任務。進行中的任務會標出正在寫測試還是正在寫實作。
|
|
99
121
|
|
|
122
|
+
### 清除 worktree
|
|
123
|
+
|
|
124
|
+
`run` 建好 worktree 就會寫入 run 紀錄,所以不論在哪一步中斷(包括安裝相依套件時),都能用 `resume` 接續,或用 `clean` 清掉。
|
|
125
|
+
|
|
126
|
+
- `clean <id>`:移除該 run 的 worktree 與 `.agentflowctl/runs/<id>/`,並清掉 git 裡已失效的 worktree 登記。沒有 run 紀錄的 worktree 也能清,worktree 資料夾被手動刪掉時也一樣。
|
|
127
|
+
- `clean --all`:清掉所有 `done`、`failed` 的 run,以及沒有 run 紀錄的 worktree。進行中、`paused`、`awaiting_approval` 的不動;Ctrl-C 中斷、之後不打算接續的 run,先 `cancel` 再 `clean --all`,或直接 `clean <id>`。
|
|
128
|
+
|
|
129
|
+
兩者都保留 `flow/<id>` 分支,不需要時用 `git branch -D` 刪除。
|
|
130
|
+
|
|
131
|
+
### 執行中的終端機輸出
|
|
132
|
+
|
|
133
|
+
預設是安靜模式,只印出 `[run-id]` 開頭的階段進度(📝 🧐 ✓ ✗ ⚠️ 等)。agent 執行失敗、測試或檢查沒過時,會附上對應 log 的查看指令:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
[f-xxxx] 🔍 執行驗證
|
|
137
|
+
[f-xxxx] ✓ typecheck
|
|
138
|
+
[f-xxxx] ✗ lint(agentflowctl logs f-xxxx 15)
|
|
139
|
+
✗ codex 執行失敗(結束碼 1),可用 agentflowctl logs f-xxxx 16 查看
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
加上 `-v` 會另外印出 agent 每一段文字的第一行、每次工具呼叫的完整指令或主要參數(不截斷,多行指令的後續行縮排對齊),以及 install、測試、verify 這些以 `$ ` 開頭的專案指令:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
💬 [claude] 先讀現有的表單元件
|
|
146
|
+
🔧 [claude] Read: /repo/.agentflowctl/worktrees/f-xxxx/src/LoginForm.tsx
|
|
147
|
+
🔧 [claude] Bash: pnpm vitest run src/LoginForm.test.tsx
|
|
148
|
+
🔧 [codex] shell: bash -lc 'pnpm test'
|
|
149
|
+
🔧 [gemini] run_shell_command: npm run lint
|
|
150
|
+
$ pnpm install
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
工具參數依序取 command、檔案路徑、path、pattern、url、query,都沒有時印出整包 JSON。
|
|
154
|
+
|
|
155
|
+
### Log
|
|
156
|
+
|
|
157
|
+
每次執行 agent 或專案指令都會留一份 log,放在 `.agentflowctl/runs/<id>/logs/`,檔名是「序號-階段-步驟-agent」,專案指令的 agent 欄位是 `cmd`:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
001-setup-install-cmd.log
|
|
161
|
+
002-spec-spec-claude.log
|
|
162
|
+
007-implement-T1-tests-codex.log
|
|
163
|
+
008-implement-T1-red-cmd.log
|
|
164
|
+
015-verify-lint-cmd.log
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
檔案保留 agent 的原始輸出,也就是各家 CLI 的 JSON 行。第一行 `# agentflowctl {...}` 記錄階段、步驟、agent 與開始時間;stderr 接在 `[stderr]` 之後;最後一行 `# exit {...}` 記錄結束碼與是否成功,沒有這行就代表還在執行或被中斷。
|
|
168
|
+
|
|
169
|
+
`agentflowctl logs <id>` 列出所有 log。結果欄的 ✓ 是成功,✗ 是失敗,… 代表沒有結束紀錄:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
# 結果 階段 步驟 agent 開始時間
|
|
173
|
+
1 ✓ setup install cmd 2026-09-26 11:29:04
|
|
174
|
+
2 ✓ spec spec claude 2026-09-26 11:29:05
|
|
175
|
+
3 ✗ plan plan codex 2026-09-26 11:31:40
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`agentflowctl logs <id> <編號>` 會把原始 JSON 解析成易讀的格式:
|
|
179
|
+
|
|
180
|
+
| 標記 | 內容 |
|
|
181
|
+
|---|---|
|
|
182
|
+
| 💬 | agent 的完整文字,不截斷 |
|
|
183
|
+
| 🔧 | 工具呼叫與完整參數 |
|
|
184
|
+
| 📊 | token 用量 |
|
|
185
|
+
| 🏁 | 最後結果 |
|
|
186
|
+
| ⚠️ | 工具回報的錯誤。agent 通常會自己換方法繼續,所以不列進錯誤整理 |
|
|
187
|
+
| ❌ | adapter 不認得的錯誤事件 |
|
|
188
|
+
| 📄 | 不是 JSON 的輸出行 |
|
|
189
|
+
|
|
190
|
+
adapter 不認得、也看不出錯誤跡象的 JSON 行不會顯示,只列出行數,要看全部請加 `--raw`。專案指令的 log 本來就是純文字,會原樣顯示。
|
|
191
|
+
|
|
192
|
+
最後一段「錯誤」整理出結束碼、agent 回報的失敗、錯誤事件與 stderr:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
#3 plan / plan / codex
|
|
196
|
+
開始 2026-09-26 11:31:40 結束 2026-09-26 11:31:52 結束碼 1 ✗ 失敗
|
|
197
|
+
檔案 /repo/.agentflowctl/runs/f-xxxx/logs/003-plan-plan-codex.log
|
|
198
|
+
|
|
199
|
+
💬 先讀 spec.md 與 acceptance.json
|
|
200
|
+
🔧 shell: bash -lc 'cat .flow/spec.md'
|
|
201
|
+
🏁 失敗:stream disconnected before completion
|
|
202
|
+
|
|
203
|
+
── 錯誤 ──
|
|
204
|
+
結束碼 1
|
|
205
|
+
agent 回報失敗:stream disconnected before completion
|
|
206
|
+
stderr:
|
|
207
|
+
Error: stream disconnected before completion
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
執行成功時,stderr 會放在「其他輸出」段落,不算錯誤。
|
|
211
|
+
|
|
212
|
+
### 出錯時怎麼查
|
|
213
|
+
|
|
214
|
+
1. run 停下時印出的摘要,或 `agentflowctl status <id>`,會列出失敗的階段、原因、最後一份 log,以及最近失敗的那一份。
|
|
215
|
+
2. `agentflowctl logs <id> <編號>` 看那份 log 的錯誤段落。
|
|
216
|
+
3. 解析結果看不出原因時,加 `--raw` 看原始輸出。
|
|
217
|
+
4. 必要時直接在 worktree(`.agentflowctl/worktrees/<id>`)裡修正,再執行 `agentflowctl resume <id>`。
|
|
218
|
+
|
|
100
219
|
## 設定
|
|
101
220
|
|
|
102
|
-
專案根目錄的 `flow.config.json`。完整範例見 `examples/flow.config.json
|
|
221
|
+
專案根目錄的 `flow.config.json`。完整範例見 `examples/flow.config.json`。未提供的欄位使用內建預設;`install`、`test`、`checks` 沒寫時,會依專案現況偵測(見下方「專案指令的偵測」)。
|
|
103
222
|
|
|
104
223
|
```json
|
|
105
224
|
{
|
|
@@ -108,6 +227,7 @@ agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保
|
|
|
108
227
|
"tddSplit": true,
|
|
109
228
|
"tieBreak": "proceed",
|
|
110
229
|
"agents": {
|
|
230
|
+
"claude": { "adapter": "claude" },
|
|
111
231
|
"codex": { "adapter": "codex", "model": "你要用的模型" },
|
|
112
232
|
"aider": { "adapter": "command", "command": ["aider", "--yes-always", "--no-auto-commits", "--message", "{prompt}"] }
|
|
113
233
|
}
|
|
@@ -116,7 +236,7 @@ agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保
|
|
|
116
236
|
|
|
117
237
|
| 設定 | 預設 | 說明 |
|
|
118
238
|
|---|---|---|
|
|
119
|
-
| `cycle` | 自動偵測 |
|
|
239
|
+
| `cycle` | 自動偵測 | 輪替順序。未設定時依 `agents` 的順序取已安裝的 CLI。同一家 CLI 可以登記成不同 agent,例如 `claude-fast` 與 `claude-strong` |
|
|
120
240
|
| `fixStrategy` | `ring` | `ring`:審查意見交給審查者的下一位;`author`:交回最後作者 |
|
|
121
241
|
| `tddSplit` | `true` | 測試與實作是否分開 |
|
|
122
242
|
| `reviewQuorum` | `1` | 程式碼需要幾位不同審查者都 `approve` |
|
|
@@ -124,17 +244,35 @@ agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保
|
|
|
124
244
|
| `planArbiter` | `true` | 計畫審查僵持時交付仲裁。關掉之後,僵持會直接讓 run 失敗 |
|
|
125
245
|
| `tieBreak` | `proceed` | 兩家仲裁意見分歧時:`proceed` 繼續並記錄爭議;`stop` 停下 |
|
|
126
246
|
| `maxAgentRuns` | `60` | 單一 run 最多執行幾次 agent |
|
|
127
|
-
| `install` / `test` / `checks` |
|
|
128
|
-
| `agents` |
|
|
247
|
+
| `install` / `test` / `checks` | 依專案偵測 | 安裝、測試與 verify 階段實際執行的指令 |
|
|
248
|
+
| `agents` | `{}` | 可用的 agent,沒有內建的。每個都要指定 adapter(`claude`、`codex`、`gemini`,或用 `command` 接上其他 CLI) |
|
|
129
249
|
|
|
130
250
|
verify 失敗(型別、lint、建置)一律交回最後作者。審查意見才依 `fixStrategy` 決定修正者。
|
|
131
251
|
|
|
252
|
+
### 專案指令的偵測
|
|
253
|
+
|
|
254
|
+
`install`、`test`、`checks` 沒寫在 `flow.config.json` 時,每次讀設定都會依專案現況推出指令,不寫檔。有寫的欄位一律照你的設定。
|
|
255
|
+
|
|
256
|
+
- 套件管理器:先看 `package.json` 的 `packageManager`,再看 lockfile(`pnpm-lock.yaml`、`yarn.lock`、`bun.lock`/`bun.lockb`、`package-lock.json`),都沒有就用 npm。
|
|
257
|
+
- `install`:`pnpm install`、`yarn install`、`bun install` 或 `npm install --no-audit --no-fund`。不鎖 lockfile,因為實作時 agent 可能新增依賴。
|
|
258
|
+
- `checks`:typecheck、lint、test、build 四項。`package.json` 有對應的 script(`typecheck`/`type-check`、`lint`、`test`、`build`)就用 `<pm> run <script>`,否則用 `tsc --noEmit`、`eslint .`、`vitest run`、`vite build`,前面加上 `npx`、`pnpm exec`、`yarn` 或 `bunx`。
|
|
259
|
+
- `test`:`vitest run`,前綴同上。
|
|
260
|
+
|
|
261
|
+
`run` 建立 worktree 後會印出這次偵測到的指令:
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
[f-xxxx] 🔧 依專案偵測指令:pnpm(依 package.json 的 packageManager)
|
|
265
|
+
[f-xxxx] install:pnpm install
|
|
266
|
+
[f-xxxx] checks.typecheck:pnpm run type-check
|
|
267
|
+
```
|
|
268
|
+
|
|
132
269
|
### 用指令管理 agent
|
|
133
270
|
|
|
134
271
|
`agents` 與 `cycle` 也可以用 `agent` 指令修改,不必手動編輯 JSON。每次寫入前都會先驗證整份設定:
|
|
135
272
|
|
|
136
273
|
```bash
|
|
137
|
-
agentflowctl agent
|
|
274
|
+
agentflowctl agent setup # 互動設定 claude、codex、gemini 與輪替順序
|
|
275
|
+
agentflowctl agent list # 設定的 agent、是否已安裝、輪替位置
|
|
138
276
|
agentflowctl agent add claude-strong --adapter claude --model opus
|
|
139
277
|
agentflowctl agent add aider --adapter command -- aider --yes-always --message {prompt}
|
|
140
278
|
agentflowctl agent set codex --model 你要用的模型 --extra-arg=--search
|
|
@@ -146,8 +284,9 @@ agentflowctl agent cycle claude-strong,codex,gemini # 不帶參數時顯
|
|
|
146
284
|
修改會連帶更新相關設定,並在終端機列出:
|
|
147
285
|
|
|
148
286
|
- `set --adapter` 換 adapter 時,會清掉舊 adapter 的 `model`、`extraArgs`、`command`,這次有重新指定的除外。
|
|
149
|
-
- `remove`
|
|
287
|
+
- `remove` 會一併從 `cycle` 移除。`cycle` 變空就刪除這個欄位,改回從 `agents` 自動偵測。
|
|
150
288
|
- `--extra-arg` 可以重複指定,會整個取代原本的 `extraArgs`。參數以 `-` 開頭時,寫成 `--extra-arg=--sandbox`。
|
|
289
|
+
- `setup` 遇到已存在的名稱會先問要不要覆寫;不覆寫時保留原設定,但仍放進這次的輪替順序。在非互動式環境(CI、管線)裡請改用 `agent add`。`command` adapter 要自己寫指令,不在 `setup` 裡。
|
|
151
290
|
|
|
152
291
|
已建立的 run 會沿用建立時的輪替順序,不受這些修改影響。
|
|
153
292
|
|
|
@@ -204,7 +343,7 @@ Agent 的最後回覆要附上 XML 中繼資料:
|
|
|
204
343
|
</result>
|
|
205
344
|
```
|
|
206
345
|
|
|
207
|
-
`blocked` 與 `concerns` 會印在終端機上,完整回覆留在 log
|
|
346
|
+
`blocked` 與 `concerns` 會印在終端機上,完整回覆留在 log,可用 `agentflowctl logs` 查看。這份中繼資料只給人看;缺少或格式錯誤都不影響流程,是否通過仍由上表的程式檢查決定。
|
|
208
347
|
|
|
209
348
|
## Adapter
|
|
210
349
|
|
|
@@ -219,11 +358,7 @@ Codex 沙箱預設不能連網,所以建立 worktree 時會先跑 `install`。
|
|
|
219
358
|
|
|
220
359
|
各家讀的專案說明檔不同:Claude Code 讀 `CLAUDE.md`,Codex 讀 `AGENTS.md`,Gemini 讀 `GEMINI.md`。把專案慣例寫在 `AGENTS.md`,另外兩個檔案各用一行引用它。agentflowctl 的 prompt 在 `prompts/`,不依賴任何一家的 skills 或 plugins。
|
|
221
360
|
|
|
222
|
-
##
|
|
223
|
-
|
|
224
|
-
只支援各家 CLI 的訂閱登入。
|
|
225
|
-
|
|
226
|
-
執行 agent 時,一律從子程序環境移除 `ANTHROPIC_API_KEY`、`ANTHROPIC_AUTH_TOKEN`、`CODEX_API_KEY`、`OPENAI_API_KEY`、`GEMINI_API_KEY`、`GOOGLE_API_KEY`,避免環境裡的 key 蓋過訂閱登入。`doctor` 發現這些變數時會提醒。專案指令(install、test、build)不受影響。
|
|
361
|
+
## 額度與代打
|
|
227
362
|
|
|
228
363
|
上限是執行次數(`maxAgentRuns`,預設 60),不是金額。`status` 會列出各 agent 的執行次數與 token 數。
|
|
229
364
|
|
|
@@ -248,7 +383,7 @@ agentflowctl 本身只依賴 Node.js 與 git。專案指令透過系統 shell
|
|
|
248
383
|
| 環境 | 適合的用法 |
|
|
249
384
|
|---|---|
|
|
250
385
|
| 自己的電腦 | 自己的專案、自己寫的需求。剛開始可以加 `--manual-plan`,確認審查品質後再拿掉 |
|
|
251
|
-
| 容器、遠端開發機 | 無人值守。先在該環境內完成各家 CLI
|
|
386
|
+
| 容器、遠端開發機 | 無人值守。先在該環境內完成各家 CLI 的登入 |
|
|
252
387
|
| Claude Code、Codex 裡面 | 讓它們用 shell 執行 `npx agentflowctl` |
|
|
253
388
|
|
|
254
389
|
沒有容器隔離時,verify 會在你的電腦上執行 agent 寫出來的程式碼。Gemini 在無人值守時是 yolo 模式。處理外部 issue,或需求文字不是你自己寫的,放到可丟棄的環境。AI 審查計畫擋不住夾在需求裡的指示。
|
|
@@ -261,8 +396,11 @@ src/
|
|
|
261
396
|
engine.ts 狀態機與各階段
|
|
262
397
|
roles.ts 輪替規則(含計畫修正者與仲裁者)
|
|
263
398
|
runner.ts 執行 agent、正規化結果、執行專案指令
|
|
399
|
+
logs.ts log 檔名、檔頭檔尾、列表與解析
|
|
264
400
|
agents/ claude、codex、gemini、command
|
|
401
|
+
setup.ts agent setup 互動精靈
|
|
265
402
|
git.ts worktree 與 git 操作
|
|
403
|
+
cleanup.ts clean:移除 worktree 與 run 紀錄
|
|
266
404
|
store.ts 狀態、用量、代打紀錄
|
|
267
405
|
tasks.ts 任務 DAG
|
|
268
406
|
schemas.ts zod schema
|
package/dist/agentConfig.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { DEFAULT_CYCLE } from "./agents/index.js";
|
|
4
3
|
import { AgentDef, RepoConfig } from "./schemas.js";
|
|
5
4
|
/** 這些欄位的意義取決於 adapter,換 adapter 時要清掉 */
|
|
6
5
|
const ADAPTER_FIELDS = ["model", "extraArgs", "command"];
|
|
7
|
-
const isBuiltin = (name) => DEFAULT_CYCLE.includes(name);
|
|
8
6
|
const agentsOf = (cfg) => ({ ...(cfg.agents ?? {}) });
|
|
9
|
-
const isDefined = (cfg, name) =>
|
|
7
|
+
const isDefined = (cfg, name) => name in agentsOf(cfg);
|
|
10
8
|
/** 只留下有值的欄位,驗證後回傳 */
|
|
11
9
|
function buildAgent(base, patch) {
|
|
12
10
|
const next = { ...base };
|
|
@@ -38,8 +36,7 @@ export function setAgent(cfg, name, patch) {
|
|
|
38
36
|
throw new Error("沒有要修改的欄位(--adapter、--model、--extra-arg 或 -- <command>)");
|
|
39
37
|
}
|
|
40
38
|
const agents = agentsOf(cfg);
|
|
41
|
-
|
|
42
|
-
const base = { ...(agents[name] ?? { adapter: name }) };
|
|
39
|
+
const base = { ...agents[name] };
|
|
43
40
|
const changes = [];
|
|
44
41
|
if (patch.adapter !== undefined && patch.adapter !== base.adapter) {
|
|
45
42
|
const cleared = ADAPTER_FIELDS.filter((f) => base[f] !== undefined && patch[f] === undefined);
|
|
@@ -51,16 +48,14 @@ export function setAgent(cfg, name, patch) {
|
|
|
51
48
|
return { cfg: { ...cfg, agents: { ...agents, [name]: buildAgent(base, patch) } }, changes };
|
|
52
49
|
}
|
|
53
50
|
export function removeAgent(cfg, name) {
|
|
51
|
+
if (!isDefined(cfg, name))
|
|
52
|
+
throw new Error(`未定義的 agent:${name}`);
|
|
54
53
|
const agents = agentsOf(cfg);
|
|
55
|
-
if (!(name in agents)) {
|
|
56
|
-
throw new Error(isBuiltin(name) ? `${name} 是內建 agent,沒有覆寫設定可以刪除` : `未定義的 agent:${name}`);
|
|
57
|
-
}
|
|
58
54
|
delete agents[name];
|
|
59
55
|
const next = { ...cfg, agents };
|
|
60
56
|
const changes = [];
|
|
61
57
|
const cycle = cfg.cycle;
|
|
62
|
-
|
|
63
|
-
if (cycle?.includes(name) && !isBuiltin(name)) {
|
|
58
|
+
if (cycle?.includes(name)) {
|
|
64
59
|
const rest = cycle.filter((n) => n !== name);
|
|
65
60
|
if (rest.length) {
|
|
66
61
|
next.cycle = rest;
|
|
@@ -68,7 +63,7 @@ export function removeAgent(cfg, name) {
|
|
|
68
63
|
}
|
|
69
64
|
else {
|
|
70
65
|
delete next.cycle;
|
|
71
|
-
changes.push("輪替順序因此變空,已刪除 cycle
|
|
66
|
+
changes.push("輪替順序因此變空,已刪除 cycle,改回從 agents 自動偵測已安裝的 CLI");
|
|
72
67
|
}
|
|
73
68
|
}
|
|
74
69
|
return { cfg: next, changes };
|
package/dist/agents/claude.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { num, str, tryJson } from "./types.js";
|
|
3
|
+
import { num, str, toolDetail, tryJson } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Claude Code 的權限設定:acceptEdits 只自動允許工作目錄內的修改,
|
|
6
6
|
* 版本控制交給 agentflowctl,並啟用內建沙箱限制 shell 指令。
|
|
@@ -46,7 +46,7 @@ export const claude = {
|
|
|
46
46
|
if (b.type === "text" && str(b.text)?.trim())
|
|
47
47
|
out.push({ kind: "text", text: str(b.text) });
|
|
48
48
|
if (b.type === "tool_use")
|
|
49
|
-
out.push({ kind: "tool", name: str(b.name) ?? "tool" });
|
|
49
|
+
out.push({ kind: "tool", name: str(b.name) ?? "tool", detail: toolDetail(b.input) });
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
else if (ev.type === "result") {
|
package/dist/agents/codex.js
CHANGED
|
@@ -21,9 +21,12 @@ export const codex = {
|
|
|
21
21
|
if (item.type === "agent_message" && str(item.text)?.trim())
|
|
22
22
|
out.push({ kind: "text", text: str(item.text) });
|
|
23
23
|
else if (item.type === "command_execution")
|
|
24
|
-
out.push({ kind: "tool", name:
|
|
25
|
-
else if (item.type === "file_change")
|
|
26
|
-
|
|
24
|
+
out.push({ kind: "tool", name: "shell", detail: str(item.command) });
|
|
25
|
+
else if (item.type === "file_change") {
|
|
26
|
+
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
27
|
+
const paths = changes.map((c) => str(c.path)).filter(Boolean);
|
|
28
|
+
out.push({ kind: "tool", name: "edit", detail: paths.length ? paths.join(", ") : undefined });
|
|
29
|
+
}
|
|
27
30
|
}
|
|
28
31
|
else if (ev.type === "turn.completed") {
|
|
29
32
|
const usage = (ev.usage ?? {});
|
package/dist/agents/gemini.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num, str, tryJson } from "./types.js";
|
|
1
|
+
import { num, str, toolDetail, tryJson } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Google Gemini CLI:`gemini -p ... --output-format stream-json`。
|
|
4
4
|
* Gemini CLI 沒有「只允許工作目錄內的 shell 指令」這種細緻權限,
|
|
@@ -21,7 +21,7 @@ export const gemini = {
|
|
|
21
21
|
out.push({ kind: "text", text: str(ev.content) });
|
|
22
22
|
}
|
|
23
23
|
else if (ev.type === "tool_use") {
|
|
24
|
-
out.push({ kind: "tool", name: str(ev.tool_name) ?? "tool" });
|
|
24
|
+
out.push({ kind: "tool", name: str(ev.tool_name) ?? "tool", detail: toolDetail(ev.parameters) });
|
|
25
25
|
}
|
|
26
26
|
else if (ev.type === "result") {
|
|
27
27
|
const stats = (ev.stats ?? {});
|
package/dist/agents/index.js
CHANGED
|
@@ -3,6 +3,4 @@ import { codex } from "./codex.js";
|
|
|
3
3
|
import { command } from "./command.js";
|
|
4
4
|
import { gemini } from "./gemini.js";
|
|
5
5
|
export const ADAPTERS = { claude, codex, gemini, command };
|
|
6
|
-
/** 沒有設定時,依序偵測這些已安裝的 CLI 組成輪替順序 */
|
|
7
|
-
export const DEFAULT_CYCLE = ["claude", "codex", "gemini"];
|
|
8
6
|
//# sourceMappingURL=index.js.map
|
package/dist/agents/types.js
CHANGED
|
@@ -9,4 +9,18 @@ export function tryJson(line) {
|
|
|
9
9
|
}
|
|
10
10
|
export const num = (v) => (typeof v === "number" ? v : undefined);
|
|
11
11
|
export const str = (v) => (typeof v === "string" ? v : undefined);
|
|
12
|
+
/** 從工具參數挑出最能代表「正在做什麼」的欄位;都沒有時以 JSON 顯示全部參數 */
|
|
13
|
+
export function toolDetail(input) {
|
|
14
|
+
if (!input || typeof input !== "object")
|
|
15
|
+
return undefined;
|
|
16
|
+
const o = input;
|
|
17
|
+
for (const key of ["command", "cmd", "file_path", "absolute_path", "path", "pattern", "url", "query"]) {
|
|
18
|
+
const v = o[key];
|
|
19
|
+
if (typeof v === "string" && v.trim())
|
|
20
|
+
return v;
|
|
21
|
+
if (Array.isArray(v) && v.length)
|
|
22
|
+
return v.join(" ");
|
|
23
|
+
}
|
|
24
|
+
return Object.keys(o).length ? JSON.stringify(o) : undefined;
|
|
25
|
+
}
|
|
12
26
|
//# sourceMappingURL=types.js.map
|
package/dist/cleanup.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { git, removeWorktree } from "./git.js";
|
|
3
|
+
import { projectRoot, runDir, runsDir, worktreeDir, worktreesDir } from "./paths.js";
|
|
4
|
+
import { getRun } from "./store.js";
|
|
5
|
+
/**
|
|
6
|
+
* 移除一個 run 的 worktree 與紀錄,分支保留。
|
|
7
|
+
* 不需要 state.json:中斷在建立 worktree 之後、寫入紀錄之前留下的孤兒也能清。
|
|
8
|
+
* 回傳是否真的找到並移除了東西。
|
|
9
|
+
*/
|
|
10
|
+
export async function cleanRun(id) {
|
|
11
|
+
// id 會拼進要遞迴刪除的路徑,擋掉空字串、..、斜線
|
|
12
|
+
if (!/^[\w-]+$/.test(id))
|
|
13
|
+
throw new Error(`不合法的 run id:${id}`);
|
|
14
|
+
const root = projectRoot();
|
|
15
|
+
const wt = worktreeDir(id);
|
|
16
|
+
const found = existsSync(wt) || existsSync(runDir(id));
|
|
17
|
+
if (existsSync(wt)) {
|
|
18
|
+
// git 不認得這個資料夾時(登記已被 prune、或 worktree add 做到一半)改成直接刪
|
|
19
|
+
await removeWorktree(root, wt).catch(() => rmSync(wt, { recursive: true, force: true }));
|
|
20
|
+
}
|
|
21
|
+
rmSync(runDir(id), { recursive: true, force: true });
|
|
22
|
+
// 清掉資料夾已不存在的 worktree 登記,否則同名分支之後無法再 checkout
|
|
23
|
+
const before = await git(root, "worktree", "list", "--porcelain");
|
|
24
|
+
await git(root, "worktree", "prune");
|
|
25
|
+
const pruned = before !== (await git(root, "worktree", "list", "--porcelain"));
|
|
26
|
+
return found || pruned;
|
|
27
|
+
}
|
|
28
|
+
/** 已結束、可以安全清掉的階段;其他階段可能還在跑,或要 resume/approve */
|
|
29
|
+
const FINISHED = ["done", "failed"];
|
|
30
|
+
/**
|
|
31
|
+
* `clean --all` 要清的 run:已結束的,加上沒有 state.json 的孤兒(stage 為 undefined)。
|
|
32
|
+
* 讀不懂的 state.json 不算孤兒,保留給使用者自己判斷。
|
|
33
|
+
*/
|
|
34
|
+
export function cleanableRuns() {
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
for (const dir of [runsDir(), worktreesDir()])
|
|
37
|
+
if (existsSync(dir))
|
|
38
|
+
for (const id of readdirSync(dir))
|
|
39
|
+
ids.add(id);
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const id of [...ids].sort()) {
|
|
42
|
+
let stage;
|
|
43
|
+
try {
|
|
44
|
+
stage = getRun(id)?.stage;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (stage === undefined || FINISHED.includes(stage))
|
|
50
|
+
out.push({ id, stage });
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=cleanup.js.map
|