agentflowctl 0.2.0 → 0.3.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 +19 -1
- package/dist/agentConfig.js +21 -9
- 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 +2 -0
- package/dist/agents/types.js +14 -0
- package/dist/cli.js +10 -8
- package/dist/runner.js +12 -3
- package/dist/schemas.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,6 +97,21 @@ agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保
|
|
|
97
97
|
|
|
98
98
|
`status` 會列出任務。進行中的任務會標出正在寫測試還是正在寫實作。
|
|
99
99
|
|
|
100
|
+
### 執行中的終端機輸出
|
|
101
|
+
|
|
102
|
+
agent 每次使用工具,都會印出完整的指令或主要參數,不截斷。多行指令的後續行會縮排對齊。install、測試、verify 這些專案指令也會以 `$ ` 開頭印出來:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
💬 [claude] 先讀現有的表單元件
|
|
106
|
+
🔧 [claude] Read: /repo/.agentflowctl/worktrees/f-xxxx/src/LoginForm.tsx
|
|
107
|
+
🔧 [claude] Bash: pnpm vitest run src/LoginForm.test.tsx
|
|
108
|
+
🔧 [codex] shell: bash -lc 'pnpm test'
|
|
109
|
+
🔧 [gemini] run_shell_command: npm run lint
|
|
110
|
+
$ pnpm install
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
工具參數依序取 command、檔案路徑、path、pattern、url、query,都沒有時印出整包 JSON。每次執行的完整輸出都在 `agentflowctl logs <id>`。
|
|
114
|
+
|
|
100
115
|
## 設定
|
|
101
116
|
|
|
102
117
|
專案根目錄的 `flow.config.json`。完整範例見 `examples/flow.config.json`。未提供的欄位使用內建預設(安裝指令、測試指令、檢查清單預設對應 Vite + TypeScript + Vitest)。
|
|
@@ -126,6 +141,7 @@ agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保
|
|
|
126
141
|
| `maxAgentRuns` | `60` | 單一 run 最多執行幾次 agent |
|
|
127
142
|
| `install` / `test` / `checks` | 見 `src/schemas.ts` | verify 階段實際執行的指令 |
|
|
128
143
|
| `agents` | 內建 claude、codex、gemini | 覆寫內建 agent,或用 `command` adapter 接上其他 CLI |
|
|
144
|
+
| `removedAgents` | `[]` | 移除的內建 agent,不會被自動偵測、不能放進輪替。通常用 `agent remove` 寫入 |
|
|
129
145
|
|
|
130
146
|
verify 失敗(型別、lint、建置)一律交回最後作者。審查意見才依 `fixStrategy` 決定修正者。
|
|
131
147
|
|
|
@@ -140,13 +156,15 @@ agentflowctl agent add aider --adapter command -- aider --yes-always --message {
|
|
|
140
156
|
agentflowctl agent set codex --model 你要用的模型 --extra-arg=--search
|
|
141
157
|
agentflowctl agent set aider --adapter gemini # 換 adapter
|
|
142
158
|
agentflowctl agent remove aider
|
|
159
|
+
agentflowctl agent remove gemini # 內建的也能移除
|
|
143
160
|
agentflowctl agent cycle claude-strong,codex,gemini # 不帶參數時顯示目前的順序
|
|
144
161
|
```
|
|
145
162
|
|
|
146
163
|
修改會連帶更新相關設定,並在終端機列出:
|
|
147
164
|
|
|
148
165
|
- `set --adapter` 換 adapter 時,會清掉舊 adapter 的 `model`、`extraArgs`、`command`,這次有重新指定的除外。
|
|
149
|
-
- `remove`
|
|
166
|
+
- `remove` 會一併從 `cycle` 移除。`cycle` 變空就刪除這個欄位,改回自動偵測。
|
|
167
|
+
- 內建的 claude、codex、gemini 也能 `remove`:覆寫設定會一起刪掉,名稱記在 `removedAgents`,之後自動偵測會跳過它,也不能放進 `cycle`。要加回來用 `agent add gemini --adapter gemini`。
|
|
150
168
|
- `--extra-arg` 可以重複指定,會整個取代原本的 `extraArgs`。參數以 `-` 開頭時,寫成 `--extra-arg=--sandbox`。
|
|
151
169
|
|
|
152
170
|
已建立的 run 會沿用建立時的輪替順序,不受這些修改影響。
|
package/dist/agentConfig.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { DEFAULT_CYCLE } from "./agents/index.js";
|
|
3
|
+
import { builtinAgents, DEFAULT_CYCLE } from "./agents/index.js";
|
|
4
4
|
import { AgentDef, RepoConfig } from "./schemas.js";
|
|
5
5
|
/** 這些欄位的意義取決於 adapter,換 adapter 時要清掉 */
|
|
6
6
|
const ADAPTER_FIELDS = ["model", "extraArgs", "command"];
|
|
7
7
|
const isBuiltin = (name) => DEFAULT_CYCLE.includes(name);
|
|
8
8
|
const agentsOf = (cfg) => ({ ...(cfg.agents ?? {}) });
|
|
9
|
-
const
|
|
9
|
+
const removedOf = (cfg) => (cfg.removedAgents ?? []);
|
|
10
|
+
const isDefined = (cfg, name) => builtinAgents(removedOf(cfg)).includes(name) || name in agentsOf(cfg);
|
|
11
|
+
/** 寫回 removedAgents;清單變空就刪掉欄位 */
|
|
12
|
+
function withRemoved(cfg, removed) {
|
|
13
|
+
const next = { ...cfg, removedAgents: removed };
|
|
14
|
+
if (!removed.length)
|
|
15
|
+
delete next.removedAgents;
|
|
16
|
+
return next;
|
|
17
|
+
}
|
|
10
18
|
/** 只留下有值的欄位,驗證後回傳 */
|
|
11
19
|
function buildAgent(base, patch) {
|
|
12
20
|
const next = { ...base };
|
|
@@ -29,7 +37,10 @@ export function addAgent(cfg, name, def) {
|
|
|
29
37
|
if (isDefined(cfg, name))
|
|
30
38
|
throw new Error(`agent ${name} 已存在,要修改請用 agent set`);
|
|
31
39
|
const { adapter, ...patch } = def;
|
|
32
|
-
|
|
40
|
+
const next = { ...cfg, agents: { ...agentsOf(cfg), [name]: buildAgent({ adapter }, patch) } };
|
|
41
|
+
// 加回先前移除的內建 agent
|
|
42
|
+
const removed = removedOf(cfg);
|
|
43
|
+
return { cfg: removed.includes(name) ? withRemoved(next, removed.filter((n) => n !== name)) : next, changes: [] };
|
|
33
44
|
}
|
|
34
45
|
export function setAgent(cfg, name, patch) {
|
|
35
46
|
if (!isDefined(cfg, name))
|
|
@@ -51,16 +62,17 @@ export function setAgent(cfg, name, patch) {
|
|
|
51
62
|
return { cfg: { ...cfg, agents: { ...agents, [name]: buildAgent(base, patch) } }, changes };
|
|
52
63
|
}
|
|
53
64
|
export function removeAgent(cfg, name) {
|
|
65
|
+
if (!isDefined(cfg, name))
|
|
66
|
+
throw new Error(`未定義的 agent:${name}`);
|
|
54
67
|
const agents = agentsOf(cfg);
|
|
55
|
-
if (!(name in agents)) {
|
|
56
|
-
throw new Error(isBuiltin(name) ? `${name} 是內建 agent,沒有覆寫設定可以刪除` : `未定義的 agent:${name}`);
|
|
57
|
-
}
|
|
58
68
|
delete agents[name];
|
|
59
|
-
|
|
69
|
+
// 內建 agent 沒有設定也存在,要記在 removedAgents 才算移除;覆寫設定一併刪掉
|
|
70
|
+
let next = { ...cfg, agents };
|
|
71
|
+
if (isBuiltin(name))
|
|
72
|
+
next = withRemoved(next, [...removedOf(cfg), name]);
|
|
60
73
|
const changes = [];
|
|
61
74
|
const cycle = cfg.cycle;
|
|
62
|
-
|
|
63
|
-
if (cycle?.includes(name) && !isBuiltin(name)) {
|
|
75
|
+
if (cycle?.includes(name)) {
|
|
64
76
|
const rest = cycle.filter((n) => n !== name);
|
|
65
77
|
if (rest.length) {
|
|
66
78
|
next.cycle = rest;
|
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
|
@@ -5,4 +5,6 @@ import { gemini } from "./gemini.js";
|
|
|
5
5
|
export const ADAPTERS = { claude, codex, gemini, command };
|
|
6
6
|
/** 沒有設定時,依序偵測這些已安裝的 CLI 組成輪替順序 */
|
|
7
7
|
export const DEFAULT_CYCLE = ["claude", "codex", "gemini"];
|
|
8
|
+
/** 扣掉設定裡 removedAgents 之後,仍可使用的內建 agent */
|
|
9
|
+
export const builtinAgents = (removed = []) => DEFAULT_CYCLE.filter((n) => !removed.includes(n));
|
|
8
10
|
//# 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/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { builtinAgents } from "./agents/index.js";
|
|
6
6
|
import { advance, loadRepoConfig } from "./engine.js";
|
|
7
7
|
import { API_KEY_VARS, probeAgent, resolveAgent, runCommand } from "./runner.js";
|
|
8
8
|
import { addWorktree, git, removeWorktree } from "./git.js";
|
|
@@ -62,11 +62,11 @@ async function resolveCycle(flag) {
|
|
|
62
62
|
return wanted;
|
|
63
63
|
}
|
|
64
64
|
const found = [];
|
|
65
|
-
for (const name of
|
|
65
|
+
for (const name of builtinAgents(cfg.removedAgents))
|
|
66
66
|
if (await probeAgent(resolveAgent(cfg, name)))
|
|
67
67
|
found.push(name);
|
|
68
68
|
if (!found.length)
|
|
69
|
-
throw new Error(
|
|
69
|
+
throw new Error(`沒有偵測到任何 agent CLI(${builtinAgents(cfg.removedAgents).join("、") || "內建 agent 都已移除"}),可用 agentflowctl doctor 檢查`);
|
|
70
70
|
return found;
|
|
71
71
|
}
|
|
72
72
|
const program = new Command()
|
|
@@ -211,12 +211,12 @@ agent
|
|
|
211
211
|
.action(async () => {
|
|
212
212
|
const cfg = loadRepoConfig();
|
|
213
213
|
const cycle = await resolveCycle().catch(() => cfg.cycle ?? []);
|
|
214
|
-
const names = [...new Set([...
|
|
214
|
+
const names = [...new Set([...builtinAgents(cfg.removedAgents), ...Object.keys(cfg.agents)])];
|
|
215
215
|
for (const name of names) {
|
|
216
216
|
const def = resolveAgent(cfg, name);
|
|
217
217
|
const ok = await probeAgent(def);
|
|
218
218
|
const pos = cycle.indexOf(name);
|
|
219
|
-
const kind =
|
|
219
|
+
const kind = builtinAgents(cfg.removedAgents).includes(name) ? (name in cfg.agents ? "內建(已覆寫)" : "內建") : "自訂";
|
|
220
220
|
const detail = [
|
|
221
221
|
`adapter=${def.adapter}`,
|
|
222
222
|
def.model && `model=${def.model}`,
|
|
@@ -225,6 +225,8 @@ agent
|
|
|
225
225
|
].filter(Boolean);
|
|
226
226
|
console.log(`${ok ? "✅" : "❌"} ${name.padEnd(14)} ${kind.padEnd(8)} ${pos >= 0 ? `輪替 #${pos + 1}` : "不在輪替"} ${detail.join(" ")}`);
|
|
227
227
|
}
|
|
228
|
+
if (cfg.removedAgents.length)
|
|
229
|
+
console.log(`\n已移除的內建 agent:${cfg.removedAgents.join("、")}(可用 agent add <name> --adapter <name> 加回)`);
|
|
228
230
|
console.log(`
|
|
229
231
|
輪替順序:${cycle.length ? cycle.join(" → ") : "(沒有可用的 agent)"}${cfg.cycle ? "" : "(自動偵測)"}`);
|
|
230
232
|
});
|
|
@@ -248,8 +250,8 @@ agent
|
|
|
248
250
|
});
|
|
249
251
|
agent
|
|
250
252
|
.command("remove <name>")
|
|
251
|
-
.description("
|
|
252
|
-
.action((name) => applyEdit((cfg) => removeAgent(cfg, name),
|
|
253
|
+
.description("刪除 agent(含內建的 claude、codex、gemini),一併從輪替移除;內建的可用 agent add 加回")
|
|
254
|
+
.action((name) => applyEdit((cfg) => removeAgent(cfg, name), `已移除 ${name}`));
|
|
253
255
|
agent
|
|
254
256
|
.command("cycle [names]")
|
|
255
257
|
.description("顯示輪替順序,或用逗號分隔設定新的順序")
|
|
@@ -272,7 +274,7 @@ program
|
|
|
272
274
|
.description("檢查可用的 agent CLI 與目前的輪替設定")
|
|
273
275
|
.action(async () => {
|
|
274
276
|
const cfg = loadRepoConfig();
|
|
275
|
-
const names = [...new Set([...
|
|
277
|
+
const names = [...new Set([...builtinAgents(cfg.removedAgents), ...Object.keys(cfg.agents)])];
|
|
276
278
|
for (const name of names) {
|
|
277
279
|
const def = resolveAgent(cfg, name);
|
|
278
280
|
const ok = await probeAgent(def);
|
package/dist/runner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { ADAPTERS,
|
|
4
|
+
import { ADAPTERS, builtinAgents } from "./agents/index.js";
|
|
5
5
|
import { AgentDef } from "./schemas.js";
|
|
6
6
|
import { projectRoot, runDir } from "./paths.js";
|
|
7
7
|
import { exec, execShell } from "./proc.js";
|
|
@@ -56,14 +56,22 @@ function appendLog(file, text) {
|
|
|
56
56
|
mkdirSync(dirname(file), { recursive: true });
|
|
57
57
|
appendFileSync(file, text.endsWith("\n") ? text : `${text}\n`);
|
|
58
58
|
}
|
|
59
|
+
/** 一行工具事件的畫面輸出:完整顯示指令或參數,多行時後續行縮排對齊 */
|
|
60
|
+
export function formatToolLine(agent, tool) {
|
|
61
|
+
const head = ` 🔧 [${agent}] ${tool.name}`;
|
|
62
|
+
const detail = tool.detail?.trim();
|
|
63
|
+
return detail ? `${head}: ${detail.split("\n").join("\n ")}` : head;
|
|
64
|
+
}
|
|
59
65
|
/** 內建的 claude、codex、gemini 定義,可在 flow.config.json 覆寫或新增其他 agent */
|
|
60
66
|
export function resolveAgent(cfg, name) {
|
|
61
67
|
const custom = cfg.agents[name];
|
|
62
68
|
if (custom)
|
|
63
69
|
return custom;
|
|
64
|
-
if (
|
|
70
|
+
if (builtinAgents(cfg.removedAgents).includes(name)) {
|
|
65
71
|
return AgentDef.parse({ adapter: name });
|
|
66
72
|
}
|
|
73
|
+
if (cfg.removedAgents.includes(name))
|
|
74
|
+
throw new Error(`${name} 已從設定移除(removedAgents),要使用請先 agent add ${name} --adapter ${name}`);
|
|
67
75
|
throw new Error(`未定義的 agent:${name}(請在 flow.config.json 的 agents 裡設定)`);
|
|
68
76
|
}
|
|
69
77
|
/** 確認某個 agent 的 CLI 是否可以執行 */
|
|
@@ -108,7 +116,7 @@ export async function runAgent(name, def, t, prompt) {
|
|
|
108
116
|
console.log(` 💬 [${name}] ${ev.text.trim().split("\n")[0]?.slice(0, 110)}`);
|
|
109
117
|
}
|
|
110
118
|
else if (ev.kind === "tool") {
|
|
111
|
-
console.log(
|
|
119
|
+
console.log(formatToolLine(name, ev));
|
|
112
120
|
}
|
|
113
121
|
else if (ev.kind === "usage") {
|
|
114
122
|
inputTokens += ev.inputTokens ?? 0;
|
|
@@ -134,6 +142,7 @@ export async function runAgent(name, def, t, prompt) {
|
|
|
134
142
|
*/
|
|
135
143
|
export async function runCommand(t, cmd) {
|
|
136
144
|
appendLog(t.logFile, `$ ${cmd}`);
|
|
145
|
+
console.log(` $ ${cmd.trim().split("\n").join("\n ")}`);
|
|
137
146
|
const r = await execShell(cmd, { cwd: t.cwd });
|
|
138
147
|
const output = `${r.stdout}\n${r.stderr}`.trim();
|
|
139
148
|
appendLog(t.logFile, output);
|
package/dist/schemas.js
CHANGED
|
@@ -51,6 +51,8 @@ export const AgentDef = z.object({
|
|
|
51
51
|
export const RepoConfig = z.object({
|
|
52
52
|
/** 自訂或覆寫 agent;claude、codex、gemini 三個名稱內建 */
|
|
53
53
|
agents: z.record(z.string(), AgentDef).default({}),
|
|
54
|
+
/** 移除的內建 agent:不再自動偵測,也不能放進輪替;用 agent add 加回 */
|
|
55
|
+
removedAgents: z.array(z.string()).default([]),
|
|
54
56
|
/** 輪替順序;未設定時自動偵測已安裝的 CLI */
|
|
55
57
|
cycle: z.array(z.string()).min(1).optional(),
|
|
56
58
|
/** review 後的修正由誰做:ring=輪到下一位;author=最後寫程式的 agent */
|