agentflowctl 0.3.0 → 0.5.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 +166 -42
- package/dist/agentConfig.js +8 -25
- package/dist/agents/index.js +0 -4
- package/dist/arbitration.js +10 -0
- package/dist/cleanup.js +54 -0
- package/dist/cli.js +130 -60
- package/dist/config.js +2 -2
- package/dist/detect.js +82 -0
- package/dist/engine.js +62 -28
- package/dist/logs.js +173 -0
- package/dist/paths.js +4 -2
- package/dist/proc.js +1 -4
- package/dist/roles.js +70 -39
- package/dist/runner.js +25 -29
- package/dist/schemas.js +10 -6
- package/dist/setup.js +70 -0
- package/examples/flow.config.json +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import {
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { stdin, stdout } from "node:process";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { config } from "./config.js";
|
|
6
8
|
import { advance, loadRepoConfig } from "./engine.js";
|
|
7
|
-
import {
|
|
8
|
-
import { addWorktree, git
|
|
9
|
-
import {
|
|
9
|
+
import { probeAgent, resolveAgent, runCommand } from "./runner.js";
|
|
10
|
+
import { addWorktree, git } from "./git.js";
|
|
11
|
+
import { cleanableRuns, cleanRun } from "./cleanup.js";
|
|
12
|
+
import { describeDetected, detectProjectDefaults } from "./detect.js";
|
|
13
|
+
import { CMD_AGENT, listLogs, localTime, logMark, nextLogFile, renderLog } from "./logs.js";
|
|
14
|
+
import { flowDir, logDir, projectRoot, worktreeDir } from "./paths.js";
|
|
10
15
|
import { TaskList } from "./schemas.js";
|
|
11
16
|
import { agentRuns, getRun, listRuns, listSubstitutions, saveRun, usageByAgent } from "./store.js";
|
|
12
17
|
import { readJsonFile } from "./util.js";
|
|
18
|
+
import { runSetup, SETUP_ADAPTERS } from "./setup.js";
|
|
13
19
|
import { addAgent, readRawConfig, removeAgent, setAgent, setCycle, writeRawConfig } from "./agentConfig.js";
|
|
14
20
|
function mustGetRun(id) {
|
|
15
21
|
const run = getRun(id);
|
|
@@ -30,7 +36,7 @@ function printSummary(run) {
|
|
|
30
36
|
console.log(`run ${run.id}`);
|
|
31
37
|
console.log(`階段 ${run.stage}`);
|
|
32
38
|
console.log(`用量 agent 執行 ${agentRuns(run.id)} / ${run.maxAgentRuns} 次`);
|
|
33
|
-
console.log(`agent ${run.cycle.join("
|
|
39
|
+
console.log(`agent ${run.cycle.join("、")}${run.lastWriter ? `(最後作者:${run.lastWriter})` : ""}`);
|
|
34
40
|
console.log(`分支 ${run.branch}`);
|
|
35
41
|
console.log(`worktree ${worktreeDir(run.id)}`);
|
|
36
42
|
if (run.prUrl)
|
|
@@ -40,7 +46,14 @@ function printSummary(run) {
|
|
|
40
46
|
if (run.stage === "failed") {
|
|
41
47
|
console.log(`失敗於 ${run.failedStage ?? "?"}`);
|
|
42
48
|
console.log(`原因 ${run.failureReason ?? "?"}`);
|
|
43
|
-
|
|
49
|
+
const logs = listLogs(logDir(run.id));
|
|
50
|
+
const last = logs.at(-1);
|
|
51
|
+
const lastFailed = logs.filter((e) => e.footer && !e.footer.ok).at(-1);
|
|
52
|
+
if (last)
|
|
53
|
+
console.log(`最後的 log #${last.seq} ${logMark(last)}(agentflowctl logs ${run.id} ${last.seq})`);
|
|
54
|
+
if (lastFailed && lastFailed !== last)
|
|
55
|
+
console.log(`最近失敗的 log #${lastFailed.seq}(agentflowctl logs ${run.id} ${lastFailed.seq})`);
|
|
56
|
+
console.log(`\n必要時直接在 worktree 裡修正,再執行 agentflowctl resume ${run.id}`);
|
|
44
57
|
}
|
|
45
58
|
if (run.stage === "paused") {
|
|
46
59
|
console.log(`暫停於 ${run.pausedStage ?? "?"}`);
|
|
@@ -50,7 +63,7 @@ function printSummary(run) {
|
|
|
50
63
|
if (run.stage === "awaiting_approval")
|
|
51
64
|
console.log(`\n確認計畫後執行 agentflowctl approve ${run.id}`);
|
|
52
65
|
}
|
|
53
|
-
/**
|
|
66
|
+
/** 決定參與的 agent:指令參數 > flow.config.json > 自動偵測已安裝的 CLI */
|
|
54
67
|
async function resolveCycle(flag) {
|
|
55
68
|
const cfg = loadRepoConfig();
|
|
56
69
|
const wanted = flag ? flag.split(",").map((s) => s.trim()).filter(Boolean) : cfg.cycle;
|
|
@@ -61,17 +74,26 @@ async function resolveCycle(flag) {
|
|
|
61
74
|
}
|
|
62
75
|
return wanted;
|
|
63
76
|
}
|
|
77
|
+
// 沒有內建 agent:只從 agents 裡定義的挑出已安裝的
|
|
78
|
+
const defined = Object.keys(cfg.agents);
|
|
79
|
+
if (!defined.length)
|
|
80
|
+
throw new Error("還沒有設定任何 agent,請先用 agentflowctl agent setup 互動設定,或用 agent add <name> --adapter <adapter> 新增");
|
|
64
81
|
const found = [];
|
|
65
|
-
for (const name of
|
|
82
|
+
for (const name of defined)
|
|
66
83
|
if (await probeAgent(resolveAgent(cfg, name)))
|
|
67
84
|
found.push(name);
|
|
68
85
|
if (!found.length)
|
|
69
|
-
throw new Error(
|
|
86
|
+
throw new Error(`設定的 agent(${defined.join("、")})都沒有偵測到已安裝的 CLI,可用 agentflowctl doctor 檢查`);
|
|
70
87
|
return found;
|
|
71
88
|
}
|
|
72
89
|
const program = new Command()
|
|
73
90
|
.name("agentflowctl")
|
|
74
|
-
.description("在專案資料夾內執行的 Agent 開發流程:規格 → 計畫 → TDD 實作 → 驗證 → 審查 → PR")
|
|
91
|
+
.description("在專案資料夾內執行的 Agent 開發流程:規格 → 計畫 → TDD 實作 → 驗證 → 審查 → PR")
|
|
92
|
+
.option("-v, --verbose", "執行時印出 agent 的文字、工具呼叫與專案指令(預設只印階段進度)")
|
|
93
|
+
.hook("preAction", (cmd) => {
|
|
94
|
+
if (cmd.opts().verbose)
|
|
95
|
+
config.verbose = true;
|
|
96
|
+
});
|
|
75
97
|
program
|
|
76
98
|
.command("run")
|
|
77
99
|
.description("在目前的專案建立並執行新的 flow")
|
|
@@ -80,7 +102,7 @@ program
|
|
|
80
102
|
.option("--base <branch>", "基底分支(預設為目前的分支)")
|
|
81
103
|
.option("--max-agent-runs <n>", "單一 run 最多執行幾次 agent(預設取 flow.config.json 的 maxAgentRuns)")
|
|
82
104
|
.option("--manual-plan", "計畫通過 AI 審查後,仍停下來等你確認", false)
|
|
83
|
-
.option("--cycle <agents>", "agent
|
|
105
|
+
.option("--cycle <agents>", "參與的 agent,例如 claude,codex,gemini(順序不影響分工)")
|
|
84
106
|
.action(async (opts) => {
|
|
85
107
|
const requirement = opts.reqFile ? readFileSync(opts.reqFile, "utf8") : opts.req;
|
|
86
108
|
if (!requirement?.trim())
|
|
@@ -94,13 +116,10 @@ program
|
|
|
94
116
|
const branch = `flow/${id}`;
|
|
95
117
|
console.log(`[${id}] 🌿 從 ${base} 建立 worktree(分支 ${branch})`);
|
|
96
118
|
await addWorktree(root, worktreeDir(id), base, branch);
|
|
97
|
-
console.log(`[${id}] 🤝 agent
|
|
98
|
-
// 先裝好相依套件:有些 agent 的沙箱不能連網,無法自己安裝
|
|
119
|
+
console.log(`[${id}] 🤝 參與的 agent:${cycle.join("、")}(角色隨機分配)`);
|
|
99
120
|
const cfg = loadRepoConfig();
|
|
100
|
-
const install = await runCommand({ runId: id, cwd: worktreeDir(id), logFile: join(logDir(id), "install.log") }, cfg.install);
|
|
101
|
-
if (!install.ok)
|
|
102
|
-
console.log(`[${id}] ⚠️ 安裝相依套件失敗,稍後 verify 階段會再試一次(見 ${join(logDir(id), "install.log")})`);
|
|
103
121
|
const now = new Date().toISOString();
|
|
122
|
+
// worktree 一建好就寫入紀錄:之後在任何地方中斷,都能用 resume 接續或用 clean 清掉
|
|
104
123
|
const run = saveRun({
|
|
105
124
|
id,
|
|
106
125
|
baseBranch: base,
|
|
@@ -116,6 +135,12 @@ program
|
|
|
116
135
|
createdAt: now,
|
|
117
136
|
updatedAt: now,
|
|
118
137
|
});
|
|
138
|
+
for (const line of describeDetected(readRawConfig(configPath()), detectProjectDefaults(root)))
|
|
139
|
+
console.log(`[${id}] ${line}`);
|
|
140
|
+
// 先裝好相依套件:有些 agent 的沙箱不能連網,無法自己安裝
|
|
141
|
+
const install = await runCommand({ runId: id, cwd: worktreeDir(id), logFile: nextLogFile(logDir(id), "setup", "install", CMD_AGENT), stage: "setup", step: "install" }, cfg.install);
|
|
142
|
+
if (!install.ok)
|
|
143
|
+
console.log(`[${id}] ⚠️ 安裝相依套件失敗,稍後 verify 階段會再試一次(agentflowctl logs ${id} ${install.seq})`);
|
|
119
144
|
await drive(run);
|
|
120
145
|
});
|
|
121
146
|
program
|
|
@@ -152,14 +177,29 @@ program
|
|
|
152
177
|
console.log(`已取消 ${id}`);
|
|
153
178
|
});
|
|
154
179
|
program
|
|
155
|
-
.command("clean
|
|
156
|
-
.description("移除 run 的 worktree
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
.command("clean [id]")
|
|
181
|
+
.description("移除 run 的 worktree 與紀錄(分支會保留);--all 清掉所有已結束的 run 與中斷留下的孤兒 worktree")
|
|
182
|
+
.option("--all", "清除所有 done、failed 的 run,以及沒有紀錄的 worktree;進行中、暫停、等待核准的不動", false)
|
|
183
|
+
.action(async (id, opts) => {
|
|
184
|
+
if (opts.all === Boolean(id))
|
|
185
|
+
throw new Error("請指定 run id,或使用 --all(兩者擇一)");
|
|
186
|
+
if (id) {
|
|
187
|
+
if (!(await cleanRun(id)))
|
|
188
|
+
throw new Error(`找不到 run:${id}`);
|
|
189
|
+
console.log(`已清除 ${id},分支仍保留,不需要時可用 git branch -D 刪除`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const targets = cleanableRuns();
|
|
193
|
+
if (!targets.length) {
|
|
194
|
+
await git(projectRoot(), "worktree", "prune");
|
|
195
|
+
console.log("沒有可清除的 run");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
for (const t of targets) {
|
|
199
|
+
await cleanRun(t.id);
|
|
200
|
+
console.log(`已清除 ${t.id}(${t.stage ?? "沒有紀錄,可能是建立時中斷"})`);
|
|
201
|
+
}
|
|
202
|
+
console.log(`\n共清除 ${targets.length} 個,分支仍保留,不需要時可用 git branch -D 刪除`);
|
|
163
203
|
});
|
|
164
204
|
program
|
|
165
205
|
.command("status <id>")
|
|
@@ -201,34 +241,33 @@ function applyEdit(edit, done) {
|
|
|
201
241
|
for (const c of changes)
|
|
202
242
|
console.log(` ↳ ${c}`);
|
|
203
243
|
if (JSON.stringify(before.cycle) !== JSON.stringify(cfg.cycle)) {
|
|
204
|
-
console.log(" 已建立的 run
|
|
244
|
+
console.log(" 已建立的 run 會沿用建立時參與的 agent,不受影響");
|
|
205
245
|
}
|
|
206
246
|
}
|
|
207
247
|
const agent = program.command("agent").description("管理 agent 與 adapter 設定(寫入 flow.config.json)");
|
|
208
248
|
agent
|
|
209
249
|
.command("list")
|
|
210
|
-
.description("
|
|
250
|
+
.description("列出設定的 agent、是否已安裝、是否參與")
|
|
211
251
|
.action(async () => {
|
|
212
252
|
const cfg = loadRepoConfig();
|
|
213
253
|
const cycle = await resolveCycle().catch(() => cfg.cycle ?? []);
|
|
214
|
-
const names =
|
|
254
|
+
const names = Object.keys(cfg.agents);
|
|
255
|
+
if (!names.length)
|
|
256
|
+
console.log("還沒有設定任何 agent,請用 agent setup 互動設定,或用 agent add <name> --adapter <adapter> 新增");
|
|
215
257
|
for (const name of names) {
|
|
216
258
|
const def = resolveAgent(cfg, name);
|
|
217
259
|
const ok = await probeAgent(def);
|
|
218
260
|
const pos = cycle.indexOf(name);
|
|
219
|
-
const kind = builtinAgents(cfg.removedAgents).includes(name) ? (name in cfg.agents ? "內建(已覆寫)" : "內建") : "自訂";
|
|
220
261
|
const detail = [
|
|
221
262
|
`adapter=${def.adapter}`,
|
|
222
263
|
def.model && `model=${def.model}`,
|
|
223
264
|
def.extraArgs.length && `extraArgs=${def.extraArgs.join(" ")}`,
|
|
224
265
|
def.command && `command=${def.command.join(" ")}`,
|
|
225
266
|
].filter(Boolean);
|
|
226
|
-
console.log(`${ok ? "✅" : "❌"} ${name.padEnd(14)} ${
|
|
267
|
+
console.log(`${ok ? "✅" : "❌"} ${name.padEnd(14)} ${pos >= 0 ? "參與" : "不參與"} ${detail.join(" ")}`);
|
|
227
268
|
}
|
|
228
|
-
if (cfg.removedAgents.length)
|
|
229
|
-
console.log(`\n已移除的內建 agent:${cfg.removedAgents.join("、")}(可用 agent add <name> --adapter <name> 加回)`);
|
|
230
269
|
console.log(`
|
|
231
|
-
|
|
270
|
+
參與的 agent:${cycle.length ? cycle.join("、") : "(沒有可用的 agent)"}${cfg.cycle ? "" : "(自動偵測)"}`);
|
|
232
271
|
});
|
|
233
272
|
agent
|
|
234
273
|
.command("add <name> [command...]")
|
|
@@ -237,7 +276,7 @@ agent
|
|
|
237
276
|
.option("--model <model>", "模型名稱")
|
|
238
277
|
.option("--extra-arg <arg>", "額外參數,可重複;以 - 開頭時寫成 --extra-arg=--sandbox", collect)
|
|
239
278
|
.action((name, command, opts) => {
|
|
240
|
-
applyEdit((cfg) => addAgent(cfg, name, { adapter: opts.adapter, model: opts.model, extraArgs: opts.extraArg, command: command.length ? command : undefined }), `已新增 ${name}
|
|
279
|
+
applyEdit((cfg) => addAgent(cfg, name, { adapter: opts.adapter, model: opts.model, extraArgs: opts.extraArg, command: command.length ? command : undefined }), `已新增 ${name};要讓它參與請用 agent cycle`);
|
|
241
280
|
});
|
|
242
281
|
agent
|
|
243
282
|
.command("set <name> [command...]")
|
|
@@ -250,51 +289,71 @@ agent
|
|
|
250
289
|
});
|
|
251
290
|
agent
|
|
252
291
|
.command("remove <name>")
|
|
253
|
-
.description("刪除 agent
|
|
292
|
+
.description("刪除 agent,一併從參與的 agent 移除")
|
|
254
293
|
.action((name) => applyEdit((cfg) => removeAgent(cfg, name), `已移除 ${name}`));
|
|
255
294
|
agent
|
|
256
295
|
.command("cycle [names]")
|
|
257
|
-
.description("
|
|
296
|
+
.description("顯示參與的 agent,或用逗號分隔設定新的名單")
|
|
258
297
|
.action(async (names) => {
|
|
259
298
|
if (!names) {
|
|
260
299
|
const cfg = loadRepoConfig();
|
|
261
|
-
console.log(`${(await resolveCycle()).join("
|
|
300
|
+
console.log(`${(await resolveCycle()).join("、")}${cfg.cycle ? "" : "(自動偵測)"}`);
|
|
262
301
|
return;
|
|
263
302
|
}
|
|
264
303
|
const list = names.split(",").map((s) => s.trim()).filter(Boolean);
|
|
265
|
-
applyEdit((cfg) => setCycle(cfg, list),
|
|
304
|
+
applyEdit((cfg) => setCycle(cfg, list), `參與的 agent 設為 ${list.join("、")}`);
|
|
266
305
|
const cfg = loadRepoConfig();
|
|
267
306
|
for (const n of list) {
|
|
268
307
|
if (!(await probeAgent(resolveAgent(cfg, n))))
|
|
269
308
|
console.log(`⚠️ ${n} 目前找不到可執行的 CLI,run 會失敗,請先安裝或用 agent set 修正`);
|
|
270
309
|
}
|
|
271
310
|
});
|
|
272
|
-
|
|
273
|
-
.command("
|
|
274
|
-
.description("
|
|
311
|
+
agent
|
|
312
|
+
.command("setup")
|
|
313
|
+
.description("互動式設定:偵測已安裝的 claude、codex、gemini,逐一選擇要不要加入並設定參與的 agent")
|
|
275
314
|
.action(async () => {
|
|
315
|
+
if (!stdin.isTTY)
|
|
316
|
+
throw new Error("agent setup 需要互動式終端機,請改用 agent add");
|
|
317
|
+
const detected = {};
|
|
318
|
+
for (const a of SETUP_ADAPTERS)
|
|
319
|
+
detected[a] = await probeAgent({ adapter: a, extraArgs: [] });
|
|
320
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
321
|
+
let edit;
|
|
322
|
+
try {
|
|
323
|
+
edit = await runSetup(readRawConfig(configPath()), { ask: (q) => rl.question(q), detected, log: (l) => console.log(l) });
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
rl.close();
|
|
327
|
+
}
|
|
328
|
+
if (!edit)
|
|
329
|
+
return;
|
|
330
|
+
const result = edit;
|
|
331
|
+
applyEdit(() => result, "設定完成");
|
|
332
|
+
console.log("");
|
|
333
|
+
await doctor();
|
|
334
|
+
});
|
|
335
|
+
/** 檢查設定的 agent 是否已安裝,印出參與的 agent 與主要設定 */
|
|
336
|
+
async function doctor() {
|
|
276
337
|
const cfg = loadRepoConfig();
|
|
277
|
-
const names =
|
|
338
|
+
const names = Object.keys(cfg.agents);
|
|
339
|
+
if (!names.length)
|
|
340
|
+
console.log("還沒有設定任何 agent,請用 agent setup 互動設定,或用 agent add <name> --adapter <adapter> 新增");
|
|
278
341
|
for (const name of names) {
|
|
279
342
|
const def = resolveAgent(cfg, name);
|
|
280
343
|
const ok = await probeAgent(def);
|
|
281
344
|
console.log(`${ok ? "✅" : "❌"} ${name.padEnd(10)} adapter=${def.adapter}${def.model ? ` model=${def.model}` : ""}`);
|
|
282
345
|
}
|
|
283
346
|
try {
|
|
284
|
-
console.log(`\n
|
|
347
|
+
console.log(`\n參與的 agent:${(await resolveCycle()).join("、")}`);
|
|
285
348
|
}
|
|
286
349
|
catch (e) {
|
|
287
350
|
console.log(`\n${e.message}`);
|
|
288
351
|
}
|
|
289
|
-
|
|
290
|
-
console.log("\n登入方式:訂閱登入(執行 agent 時會移除 API key)");
|
|
291
|
-
if (leaked.length) {
|
|
292
|
-
console.log(`⚠️ 環境中有 ${leaked.join("、")},agentflowctl 執行 agent 時會移除,但你自己直接執行 CLI 時仍可能改走 API 計費`);
|
|
293
|
-
}
|
|
294
|
-
console.log(`單一 run 的 agent 執行上限:${cfg.maxAgentRuns} 次`);
|
|
352
|
+
console.log(`\n單一 run 的 agent 執行上限:${cfg.maxAgentRuns} 次`);
|
|
295
353
|
console.log(`修正策略:${cfg.fixStrategy} 測試與實作分開:${cfg.tddSplit ? "是" : "否"}`);
|
|
296
354
|
console.log(`程式碼審查人數:${cfg.reviewQuorum} 計畫審查人數:${cfg.planReviewQuorum} 計畫仲裁:${cfg.planArbiter ? "開啟" : "關閉"}`);
|
|
297
|
-
}
|
|
355
|
+
}
|
|
356
|
+
program.command("doctor").description("檢查可用的 agent CLI 與目前參與的 agent").action(doctor);
|
|
298
357
|
program
|
|
299
358
|
.command("list")
|
|
300
359
|
.description("列出這個專案的所有 run")
|
|
@@ -305,18 +364,29 @@ program
|
|
|
305
364
|
}
|
|
306
365
|
});
|
|
307
366
|
program
|
|
308
|
-
.command("logs <id>")
|
|
309
|
-
.description("列出 log
|
|
310
|
-
.option("--latest", "顯示最新一份 log
|
|
311
|
-
.
|
|
367
|
+
.command("logs <id> [seq]")
|
|
368
|
+
.description("列出 log;指定編號(或 --latest)時顯示解析後的內容,最後附上錯誤整理")
|
|
369
|
+
.option("--latest", "顯示最新一份 log", false)
|
|
370
|
+
.option("--raw", "顯示原始內容(agent 的 JSON 行)", false)
|
|
371
|
+
.action((id, seq, opts) => {
|
|
312
372
|
mustGetRun(id);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
if (!files.length)
|
|
373
|
+
const logs = listLogs(logDir(id));
|
|
374
|
+
if (!logs.length)
|
|
316
375
|
return console.log("還沒有 log");
|
|
317
|
-
if (!opts.latest)
|
|
318
|
-
|
|
319
|
-
|
|
376
|
+
if (!seq && !opts.latest) {
|
|
377
|
+
console.log(" # 結果 階段 步驟 agent 開始時間");
|
|
378
|
+
for (const e of logs) {
|
|
379
|
+
const h = e.header;
|
|
380
|
+
console.log(`${String(e.seq).padStart(3)} ${logMark(e).padEnd(4)} ${(h?.stage ?? "?").padEnd(12)} ${(h?.step ?? "?").padEnd(19)} ${(h?.agent ?? "?").padEnd(10)} ${localTime(h?.startedAt)}`);
|
|
381
|
+
}
|
|
382
|
+
console.log(`\n查看內容:agentflowctl logs ${id} <編號>(加 --raw 看原始 JSON)`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const entry = seq ? logs.find((e) => e.seq === Number(seq)) : logs.at(-1);
|
|
386
|
+
if (!entry)
|
|
387
|
+
throw new Error(`找不到 log #${seq}(共 ${logs.length} 份,可用 agentflowctl logs ${id} 列出)`);
|
|
388
|
+
const text = readFileSync(entry.file, "utf8");
|
|
389
|
+
console.log(opts.raw ? text : renderLog(text, entry.file));
|
|
320
390
|
});
|
|
321
391
|
program.parseAsync().catch((err) => {
|
|
322
392
|
console.error(`錯誤:${err.message}`);
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const config = {
|
|
2
|
-
/** 指定 Claude Code 使用的模型;未設定則用 Claude Code 預設值 */
|
|
3
|
-
model: process.env.AGENTFLOWCTL_MODEL,
|
|
4
2
|
/** 單一 Agent 執行最多幾輪工具迴圈,避免卡在迴圈裡 */
|
|
5
3
|
maxTurns: Number(process.env.AGENTFLOWCTL_MAX_TURNS ?? 80),
|
|
6
4
|
/** 同一個關卡連續失敗幾次後停止 */
|
|
7
5
|
maxAttempts: Number(process.env.AGENTFLOWCTL_MAX_ATTEMPTS ?? 3),
|
|
6
|
+
/** 終端機是否印出 agent 的文字、工具呼叫與專案指令;預設安靜,-v 或 AGENTFLOWCTL_VERBOSE=1 開啟 */
|
|
7
|
+
verbose: process.env.AGENTFLOWCTL_VERBOSE === "1",
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=config.js.map
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { RepoConfig } from "./schemas.js";
|
|
4
|
+
const LOCKFILES = [
|
|
5
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
6
|
+
["yarn.lock", "yarn"],
|
|
7
|
+
["bun.lock", "bun"],
|
|
8
|
+
["bun.lockb", "bun"],
|
|
9
|
+
["package-lock.json", "npm"],
|
|
10
|
+
];
|
|
11
|
+
/** 不鎖 lockfile:implement 階段 agent 可能新增依賴 */
|
|
12
|
+
const INSTALL = {
|
|
13
|
+
npm: RepoConfig.parse({}).install,
|
|
14
|
+
pnpm: "pnpm install",
|
|
15
|
+
yarn: "yarn install",
|
|
16
|
+
bun: "bun install",
|
|
17
|
+
};
|
|
18
|
+
/** 執行專案內 bin 的前綴,取代預設指令裡的 npx */
|
|
19
|
+
const EXEC = { npm: "npx", pnpm: "pnpm exec", yarn: "yarn", bun: "bunx" };
|
|
20
|
+
/** 每項檢查會找的 script 名稱,依序取第一個存在的 */
|
|
21
|
+
const CHECK_SCRIPTS = {
|
|
22
|
+
typecheck: ["typecheck", "type-check"],
|
|
23
|
+
lint: ["lint"],
|
|
24
|
+
test: ["test"],
|
|
25
|
+
build: ["build"],
|
|
26
|
+
};
|
|
27
|
+
function readPackageJson(root) {
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
30
|
+
return pkg && typeof pkg === "object" ? pkg : {};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function detectManager(root, pkg) {
|
|
37
|
+
if (typeof pkg.packageManager === "string") {
|
|
38
|
+
const name = pkg.packageManager.split("@")[0];
|
|
39
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") {
|
|
40
|
+
return { manager: name, source: "package.json 的 packageManager" };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const [file, manager] of LOCKFILES) {
|
|
44
|
+
if (existsSync(join(root, file)))
|
|
45
|
+
return { manager, source: file };
|
|
46
|
+
}
|
|
47
|
+
return { manager: "npm", source: "預設" };
|
|
48
|
+
}
|
|
49
|
+
const withExec = (cmd, manager) => cmd.replace(/^npx /, `${EXEC[manager]} `);
|
|
50
|
+
export function detectProjectDefaults(root) {
|
|
51
|
+
const pkg = readPackageJson(root);
|
|
52
|
+
const { manager, source } = detectManager(root, pkg);
|
|
53
|
+
const defaults = RepoConfig.parse({});
|
|
54
|
+
const scripts = pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {};
|
|
55
|
+
const checks = defaults.checks.map(({ name, cmd }) => {
|
|
56
|
+
const script = CHECK_SCRIPTS[name]?.find((s) => typeof scripts[s] === "string");
|
|
57
|
+
return { name, cmd: script ? `${manager} run ${script}` : withExec(cmd, manager) };
|
|
58
|
+
});
|
|
59
|
+
return { manager, source, install: INSTALL[manager], test: withExec(defaults.test, manager), checks };
|
|
60
|
+
}
|
|
61
|
+
/** 補上原始設定裡沒寫的 install、test、checks;不是物件就原樣回傳,交給 schema 報錯 */
|
|
62
|
+
export function withProjectDefaults(raw, detected) {
|
|
63
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
64
|
+
return raw;
|
|
65
|
+
const { install, test, checks } = detected;
|
|
66
|
+
return { install, test, checks, ...raw };
|
|
67
|
+
}
|
|
68
|
+
/** run 開始時印出的說明:只列出這次用了偵測結果的欄位 */
|
|
69
|
+
export function describeDetected(raw, detected) {
|
|
70
|
+
const lines = [];
|
|
71
|
+
if (!("install" in raw))
|
|
72
|
+
lines.push(` install:${detected.install}`);
|
|
73
|
+
if (!("test" in raw))
|
|
74
|
+
lines.push(` test:${detected.test}`);
|
|
75
|
+
if (!("checks" in raw))
|
|
76
|
+
lines.push(...detected.checks.map((c) => ` checks.${c.name}:${c.cmd}`));
|
|
77
|
+
if (!lines.length)
|
|
78
|
+
return [];
|
|
79
|
+
const why = detected.source === "預設" ? "預設" : `依 ${detected.source}`;
|
|
80
|
+
return [`🔧 依專案偵測指令:${detected.manager}(${why})`, ...lines];
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=detect.js.map
|