@brawnen/agent-harness-cli 0.1.0 → 0.1.1
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 +33 -3
- package/package.json +2 -2
- package/src/commands/gate.js +1 -1
- package/src/commands/hook.js +43 -0
- package/src/commands/init.js +22 -0
- package/src/commands/state.js +10 -2
- package/src/commands/status.js +128 -11
- package/src/index.js +6 -0
- package/src/lib/claude-hooks.js +49 -0
- package/src/lib/codex-hooks.js +48 -0
- package/src/lib/gemini-hooks.js +76 -0
- package/src/lib/hook-core.js +616 -0
- package/src/lib/hook-io/claude.js +23 -0
- package/src/lib/hook-io/codex.js +23 -0
- package/src/lib/hook-io/gemini.js +130 -0
- package/src/lib/hook-io/shared.js +52 -0
- package/src/lib/runtime-paths.js +39 -0
- package/src/lib/task-core.js +23 -3
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ The current implementation also covers:
|
|
|
37
37
|
- host rule injection
|
|
38
38
|
- base project config generation
|
|
39
39
|
- minimal `.codex/hooks.json` integration
|
|
40
|
+
- `.claude/settings.json` hook integration
|
|
41
|
+
- `.gemini/settings.json` hook integration
|
|
40
42
|
|
|
41
43
|
## Current Boundaries
|
|
42
44
|
|
|
@@ -144,7 +146,7 @@ Current responsibilities:
|
|
|
144
146
|
- `docs scaffold --type design-note|adr`
|
|
145
147
|
- generate minimal Markdown skeletons from task context
|
|
146
148
|
|
|
147
|
-
##
|
|
149
|
+
## Host Support
|
|
148
150
|
|
|
149
151
|
The current repository has the most complete host integration for `Codex`.
|
|
150
152
|
|
|
@@ -152,16 +154,44 @@ Current Codex coverage includes:
|
|
|
152
154
|
|
|
153
155
|
- `SessionStart`
|
|
154
156
|
- `UserPromptSubmit`
|
|
157
|
+
|
|
158
|
+
Currently disabled by default:
|
|
159
|
+
|
|
155
160
|
- `PreToolUse`
|
|
156
161
|
- `PostToolUse`
|
|
157
162
|
|
|
158
163
|
Highlights:
|
|
159
164
|
|
|
160
165
|
- automatic intake / continue / clarify
|
|
161
|
-
- pre-tool gating
|
|
162
|
-
- automatic evidence capture
|
|
163
166
|
- active task restore
|
|
164
167
|
|
|
168
|
+
Current Codex boundary:
|
|
169
|
+
|
|
170
|
+
- tool-level hooks remain implemented but are not enabled by default because of host visibility noise
|
|
171
|
+
|
|
172
|
+
Current Gemini CLI coverage includes:
|
|
173
|
+
|
|
174
|
+
- `SessionStart`
|
|
175
|
+
- `BeforeAgent`
|
|
176
|
+
- `BeforeTool`
|
|
177
|
+
- `AfterTool`
|
|
178
|
+
- `AfterAgent`
|
|
179
|
+
|
|
180
|
+
Highlights:
|
|
181
|
+
|
|
182
|
+
- automatic intake / continue / clarify
|
|
183
|
+
- before-tool gating for supported Gemini tools
|
|
184
|
+
- shell evidence capture through `AfterTool`
|
|
185
|
+
- completion gating through `AfterAgent`
|
|
186
|
+
|
|
187
|
+
Current Claude Code coverage includes:
|
|
188
|
+
|
|
189
|
+
- `SessionStart`
|
|
190
|
+
- `UserPromptSubmit`
|
|
191
|
+
- `PreToolUse`
|
|
192
|
+
- `PostToolUse`
|
|
193
|
+
- `Stop`
|
|
194
|
+
|
|
165
195
|
### Current `PreToolUse` Coverage For `Bash`
|
|
166
196
|
|
|
167
197
|
`Bash` currently supports high-confidence path inference for common write commands such as:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brawnen/agent-harness-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI for task convergence, verification, reporting, and delivery in agent-harness projects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@brawnen/agent-harness-protocol": "^0.1.
|
|
38
|
+
"@brawnen/agent-harness-protocol": "^0.1.1"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"start": "node ./bin/agent-harness.js --help"
|
package/src/commands/gate.js
CHANGED
|
@@ -67,7 +67,7 @@ function parseBeforeToolArgs(argv) {
|
|
|
67
67
|
return { ok: true, options };
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function beforeTool(cwd, options) {
|
|
70
|
+
export function beforeTool(cwd, options) {
|
|
71
71
|
const taskId = options.taskId ?? resolveActiveTaskId(cwd);
|
|
72
72
|
const taskState = taskId ? getTaskState(cwd, taskId) : null;
|
|
73
73
|
const config = loadProjectConfig(cwd);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { runClaudeHook, readHookPayload } from "../lib/claude-hooks.js";
|
|
2
|
+
import { runCodexHook } from "../lib/codex-hooks.js";
|
|
3
|
+
import { runGeminiHook } from "../lib/gemini-hooks.js";
|
|
4
|
+
|
|
5
|
+
export function runHook(argv) {
|
|
6
|
+
const [host, event] = argv;
|
|
7
|
+
|
|
8
|
+
if (!host || !event) {
|
|
9
|
+
console.error("用法: hook <claude|codex|gemini> <event>");
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!["claude", "claude-code", "codex", "gemini", "gemini-cli"].includes(host)) {
|
|
14
|
+
console.error(`未知 hook 宿主: ${host}`);
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const payload = readHookPayload();
|
|
20
|
+
const result = runHostHook(host, event, payload);
|
|
21
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
22
|
+
return 0;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(error.message);
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runHostHook(host, event, payload) {
|
|
30
|
+
if (host === "claude" || host === "claude-code") {
|
|
31
|
+
return runClaudeHook(event, payload);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (host === "codex") {
|
|
35
|
+
return runCodexHook(event, payload);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (host === "gemini" || host === "gemini-cli") {
|
|
39
|
+
return runGeminiHook(event, payload);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error(`未知 hook 宿主: ${host}`);
|
|
43
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -295,6 +295,9 @@ function queueInitActions(context) {
|
|
|
295
295
|
if (hosts.includes("claude-code")) {
|
|
296
296
|
queueClaudeSettingsMerge(actions, cwd);
|
|
297
297
|
}
|
|
298
|
+
if (hosts.includes("gemini-cli")) {
|
|
299
|
+
queueGeminiSettingsMerge(actions, cwd);
|
|
300
|
+
}
|
|
298
301
|
}
|
|
299
302
|
}
|
|
300
303
|
|
|
@@ -361,6 +364,25 @@ function queueClaudeSettingsMerge(actions, cwd) {
|
|
|
361
364
|
});
|
|
362
365
|
}
|
|
363
366
|
|
|
367
|
+
function queueGeminiSettingsMerge(actions, cwd) {
|
|
368
|
+
const targetPath = path.join(cwd, ".gemini", "settings.json");
|
|
369
|
+
const templatePath = path.join(PROTOCOL_ROOT, "adapters", "gemini-cli", "hooks.json");
|
|
370
|
+
const template = JSON.parse(readText(templatePath));
|
|
371
|
+
const existing = fs.existsSync(targetPath) ? readJson(targetPath) : {};
|
|
372
|
+
const merged = mergeClaudeSettings(existing, template);
|
|
373
|
+
const content = `${JSON.stringify(merged, null, 2)}\n`;
|
|
374
|
+
|
|
375
|
+
actions.push({
|
|
376
|
+
description: fs.existsSync(targetPath) ? "合并 Gemini CLI hooks" : "创建 Gemini CLI hooks 配置",
|
|
377
|
+
relativePath: path.relative(cwd, targetPath),
|
|
378
|
+
run: () => {
|
|
379
|
+
ensureDirectory(path.dirname(targetPath));
|
|
380
|
+
fs.writeFileSync(targetPath, content, "utf8");
|
|
381
|
+
},
|
|
382
|
+
skip: false
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
364
386
|
function queueRuntimeFiles(actions, cwd) {
|
|
365
387
|
const runtimeReadme = path.join(cwd, DEFAULT_RUNTIME_DIR, "README.md");
|
|
366
388
|
queueWriteAction({
|
package/src/commands/state.js
CHANGED
|
@@ -110,7 +110,9 @@ function runStateUpdate(argv) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
const result = updateTaskState(process.cwd(), taskId, changes);
|
|
113
|
-
|
|
113
|
+
if (parsed.options.verbose) {
|
|
114
|
+
printJson(result);
|
|
115
|
+
}
|
|
114
116
|
return 0;
|
|
115
117
|
} catch (error) {
|
|
116
118
|
console.error(error.message);
|
|
@@ -208,7 +210,8 @@ function parseStateUpdateArgs(argv) {
|
|
|
208
210
|
phase: null,
|
|
209
211
|
state: null,
|
|
210
212
|
taskId: null,
|
|
211
|
-
tool: null
|
|
213
|
+
tool: null,
|
|
214
|
+
verbose: false
|
|
212
215
|
};
|
|
213
216
|
|
|
214
217
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -251,6 +254,11 @@ function parseStateUpdateArgs(argv) {
|
|
|
251
254
|
continue;
|
|
252
255
|
}
|
|
253
256
|
|
|
257
|
+
if (arg === "--verbose") {
|
|
258
|
+
options.verbose = true;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
254
262
|
return { ok: false, error: `未知参数: ${arg}` };
|
|
255
263
|
}
|
|
256
264
|
|
package/src/commands/status.js
CHANGED
|
@@ -72,6 +72,10 @@ export function runStatus(argv) {
|
|
|
72
72
|
pushCheck(checks, claudeHooksCheck);
|
|
73
73
|
exitCode = maxExitCode(exitCode, claudeHooksCheck.severity);
|
|
74
74
|
|
|
75
|
+
const geminiAdapterCheck = inspectGeminiAdapter(cwd, runtimeMode, hosts.includes("gemini-cli"));
|
|
76
|
+
pushCheck(checks, geminiAdapterCheck);
|
|
77
|
+
exitCode = maxExitCode(exitCode, geminiAdapterCheck.severity);
|
|
78
|
+
|
|
75
79
|
const runtimeDirsCheck = inspectRuntimeDirectories(cwd, runtimeMode);
|
|
76
80
|
pushCheck(checks, runtimeDirsCheck);
|
|
77
81
|
exitCode = maxExitCode(exitCode, runtimeDirsCheck.severity);
|
|
@@ -307,14 +311,21 @@ function inspectCodexHooks(cwd, hasCodexHost) {
|
|
|
307
311
|
return warn(".codex/hooks", "hooks.json 存在,但 JSON 解析失败");
|
|
308
312
|
}
|
|
309
313
|
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
const hasUserPromptSubmit = hasCodexHookCommand(parsedHooks, "UserPromptSubmit", "user_prompt_submit_intake.js");
|
|
315
|
+
const hasSessionStart = hasCodexHookCommand(parsedHooks, "SessionStart", "session_start_restore.js");
|
|
316
|
+
const hasPreToolUse = hasCodexHookCommand(parsedHooks, "PreToolUse", "pre_tool_use_gate.js");
|
|
317
|
+
const hasPostToolUse = hasCodexHookCommand(parsedHooks, "PostToolUse", "post_tool_use_record_evidence.js");
|
|
318
|
+
|
|
319
|
+
if (!hasUserPromptSubmit || !hasSessionStart) {
|
|
320
|
+
return warn(".codex/hooks", "hooks.json 存在,但缺少最小 Codex hooks:SessionStart / UserPromptSubmit");
|
|
321
|
+
}
|
|
315
322
|
|
|
316
|
-
if (
|
|
317
|
-
return
|
|
323
|
+
if (!hasPreToolUse && !hasPostToolUse) {
|
|
324
|
+
return ok(".codex/hooks", "Codex hooks 已配置;已启用 SessionStart / UserPromptSubmit,工具级 hooks 当前关闭");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (hasPreToolUse !== hasPostToolUse) {
|
|
328
|
+
return warn(".codex/hooks", "hooks.json 存在,但工具级 hooks 仅部分启用");
|
|
318
329
|
}
|
|
319
330
|
|
|
320
331
|
return ok(".codex/hooks", "Codex hooks 已配置;trusted project 默认启用,untrusted 请显式使用 codex --enable codex_hooks");
|
|
@@ -348,14 +359,120 @@ function inspectClaudeHooks(cwd, runtimeMode, hasClaudeHost) {
|
|
|
348
359
|
.map((hook) => hook.command)
|
|
349
360
|
.filter(Boolean);
|
|
350
361
|
|
|
351
|
-
const hasPreTool = commands.some((command) =>
|
|
352
|
-
|
|
362
|
+
const hasPreTool = commands.some((command) =>
|
|
363
|
+
command.includes("agent-harness gate before-tool") ||
|
|
364
|
+
command.includes("@brawnen/agent-harness-cli gate before-tool") ||
|
|
365
|
+
command.includes("packages/cli/bin/agent-harness.js\" gate before-tool") ||
|
|
366
|
+
command.includes("packages/cli/bin/agent-harness.js gate before-tool")
|
|
367
|
+
);
|
|
368
|
+
const hasPostTool = commands.some((command) =>
|
|
369
|
+
command.includes("agent-harness state update") ||
|
|
370
|
+
command.includes("@brawnen/agent-harness-cli state update") ||
|
|
371
|
+
command.includes("packages/cli/bin/agent-harness.js\" state update") ||
|
|
372
|
+
command.includes("packages/cli/bin/agent-harness.js state update")
|
|
373
|
+
);
|
|
374
|
+
const hasSessionStart = commands.some((command) =>
|
|
375
|
+
command.includes("agent-harness hook claude session-start") ||
|
|
376
|
+
command.includes("@brawnen/agent-harness-cli hook claude session-start") ||
|
|
377
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook claude session-start") ||
|
|
378
|
+
command.includes("packages/cli/bin/agent-harness.js hook claude session-start")
|
|
379
|
+
);
|
|
380
|
+
const hasUserPromptSubmit = commands.some((command) =>
|
|
381
|
+
command.includes("agent-harness hook claude user-prompt-submit") ||
|
|
382
|
+
command.includes("@brawnen/agent-harness-cli hook claude user-prompt-submit") ||
|
|
383
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook claude user-prompt-submit") ||
|
|
384
|
+
command.includes("packages/cli/bin/agent-harness.js hook claude user-prompt-submit")
|
|
385
|
+
);
|
|
386
|
+
const hasStop = commands.some((command) =>
|
|
387
|
+
command.includes("agent-harness hook claude stop") ||
|
|
388
|
+
command.includes("@brawnen/agent-harness-cli hook claude stop") ||
|
|
389
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook claude stop") ||
|
|
390
|
+
command.includes("packages/cli/bin/agent-harness.js hook claude stop")
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (hasSessionStart && hasUserPromptSubmit && hasPreTool && hasPostTool && hasStop) {
|
|
394
|
+
return ok(".claude/settings.json", "Claude Code hooks 已配置(SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop)");
|
|
395
|
+
}
|
|
353
396
|
|
|
354
397
|
if (hasPreTool && hasPostTool) {
|
|
355
|
-
return
|
|
398
|
+
return warn(".claude/settings.json", "检测到旧版 Claude Code 最小 hooks,缺少 SessionStart / UserPromptSubmit / Stop");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return warn(".claude/settings.json", "hooks 存在,但 Claude Code adapter 命令不完整");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function inspectGeminiAdapter(cwd, runtimeMode, hasGeminiHost) {
|
|
405
|
+
if (!hasGeminiHost) {
|
|
406
|
+
return skip("Gemini adapter", "当前项目未检测到 Gemini CLI 宿主");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const hostPath = path.join(cwd, "GEMINI.md");
|
|
410
|
+
const settingsPath = path.join(cwd, ".gemini", "settings.json");
|
|
411
|
+
if (!fs.existsSync(hostPath)) {
|
|
412
|
+
return warn("Gemini adapter", "缺少 GEMINI.md,无法注入 Gemini CLI 宿主规则");
|
|
356
413
|
}
|
|
357
414
|
|
|
358
|
-
|
|
415
|
+
if (!fs.existsSync(settingsPath)) {
|
|
416
|
+
if (runtimeMode === "protocol-only") {
|
|
417
|
+
return skip("Gemini adapter", "protocol-only 模式,仅依赖 GEMINI.md 规则注入");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return warn("Gemini adapter", "未发现 .gemini/settings.json,Gemini CLI hooks 尚未配置");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
let parsed;
|
|
424
|
+
try {
|
|
425
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
426
|
+
} catch {
|
|
427
|
+
return warn("Gemini adapter", ".gemini/settings.json 存在,但 JSON 解析失败");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const commands = Object.values(parsed.hooks ?? {})
|
|
431
|
+
.flatMap((entries) => entries ?? [])
|
|
432
|
+
.flatMap((entry) => entry.hooks ?? [])
|
|
433
|
+
.map((hook) => hook.command)
|
|
434
|
+
.filter(Boolean);
|
|
435
|
+
|
|
436
|
+
const hasSessionStart = commands.some((command) =>
|
|
437
|
+
command.includes("agent-harness hook gemini session-start") ||
|
|
438
|
+
command.includes("@brawnen/agent-harness-cli hook gemini session-start") ||
|
|
439
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook gemini session-start") ||
|
|
440
|
+
command.includes("packages/cli/bin/agent-harness.js hook gemini session-start")
|
|
441
|
+
);
|
|
442
|
+
const hasBeforeAgent = commands.some((command) =>
|
|
443
|
+
command.includes("agent-harness hook gemini before-agent") ||
|
|
444
|
+
command.includes("@brawnen/agent-harness-cli hook gemini before-agent") ||
|
|
445
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook gemini before-agent") ||
|
|
446
|
+
command.includes("packages/cli/bin/agent-harness.js hook gemini before-agent")
|
|
447
|
+
);
|
|
448
|
+
const hasBeforeTool = commands.some((command) =>
|
|
449
|
+
command.includes("agent-harness hook gemini before-tool") ||
|
|
450
|
+
command.includes("@brawnen/agent-harness-cli hook gemini before-tool") ||
|
|
451
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook gemini before-tool") ||
|
|
452
|
+
command.includes("packages/cli/bin/agent-harness.js hook gemini before-tool")
|
|
453
|
+
);
|
|
454
|
+
const hasAfterTool = commands.some((command) =>
|
|
455
|
+
command.includes("agent-harness hook gemini after-tool") ||
|
|
456
|
+
command.includes("@brawnen/agent-harness-cli hook gemini after-tool") ||
|
|
457
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook gemini after-tool") ||
|
|
458
|
+
command.includes("packages/cli/bin/agent-harness.js hook gemini after-tool")
|
|
459
|
+
);
|
|
460
|
+
const hasAfterAgent = commands.some((command) =>
|
|
461
|
+
command.includes("agent-harness hook gemini after-agent") ||
|
|
462
|
+
command.includes("@brawnen/agent-harness-cli hook gemini after-agent") ||
|
|
463
|
+
command.includes("packages/cli/bin/agent-harness.js\" hook gemini after-agent") ||
|
|
464
|
+
command.includes("packages/cli/bin/agent-harness.js hook gemini after-agent")
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
const modeSummary = runtimeMode === "protocol-only"
|
|
468
|
+
? "当前为 protocol-only,但 hooks 已可用"
|
|
469
|
+
: "已检测到 .harness 运行时目录,可配合 hooks 与 CLI 做 state / verify / report";
|
|
470
|
+
|
|
471
|
+
if (hasSessionStart && hasBeforeAgent && hasBeforeTool && hasAfterTool && hasAfterAgent) {
|
|
472
|
+
return ok("Gemini adapter", `Gemini CLI hooks 已配置(SessionStart / BeforeAgent / BeforeTool / AfterTool / AfterAgent);${modeSummary}`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return warn("Gemini adapter", "Gemini CLI hooks 已部分配置,但命令集合不完整");
|
|
359
476
|
}
|
|
360
477
|
|
|
361
478
|
function hasCodexHookCommand(parsedHooks, eventName, commandFragment) {
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { runAudit } from "./commands/audit.js";
|
|
|
2
2
|
import { runDelivery } from "./commands/delivery.js";
|
|
3
3
|
import { runDocs } from "./commands/docs.js";
|
|
4
4
|
import { runGate } from "./commands/gate.js";
|
|
5
|
+
import { runHook } from "./commands/hook.js";
|
|
5
6
|
import { runInit } from "./commands/init.js";
|
|
6
7
|
import { runReport } from "./commands/report.js";
|
|
7
8
|
import { runState } from "./commands/state.js";
|
|
@@ -21,6 +22,7 @@ Usage:
|
|
|
21
22
|
agent-harness delivery <ready|request|commit>
|
|
22
23
|
agent-harness docs scaffold --type <design-note|adr>
|
|
23
24
|
agent-harness gate before-tool --tool <tool>
|
|
25
|
+
agent-harness hook <claude|codex|gemini> <event>
|
|
24
26
|
agent-harness status
|
|
25
27
|
agent-harness task intake "<任务描述>"
|
|
26
28
|
agent-harness task confirm [--task-id <task-id>]
|
|
@@ -75,6 +77,10 @@ export function run(argv) {
|
|
|
75
77
|
return runGate(argv.slice(1));
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
if (command === "hook") {
|
|
81
|
+
return runHook(argv.slice(1));
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
if (command === "status") {
|
|
79
85
|
return runStatus(argv.slice(1));
|
|
80
86
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleCompletionGate,
|
|
3
|
+
handlePromptSubmit,
|
|
4
|
+
handleSessionStart
|
|
5
|
+
} from "./hook-core.js";
|
|
6
|
+
import { buildClaudeHookOutput, resolveClaudeCompletionMessage } from "./hook-io/claude.js";
|
|
7
|
+
import { readHookPayload, resolvePayloadCwd, resolvePayloadPrompt } from "./hook-io/shared.js";
|
|
8
|
+
|
|
9
|
+
const MANUAL_FALLBACK_COMMANDS = [
|
|
10
|
+
"npx @brawnen/agent-harness-cli state active",
|
|
11
|
+
"npx @brawnen/agent-harness-cli task intake \"任务描述\"",
|
|
12
|
+
"npx @brawnen/agent-harness-cli task suspend-active --reason \"切换任务\""
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export { readHookPayload };
|
|
16
|
+
|
|
17
|
+
export function runClaudeHook(event, payload) {
|
|
18
|
+
const cwd = resolvePayloadCwd(payload);
|
|
19
|
+
|
|
20
|
+
if (event === "session-start") {
|
|
21
|
+
return buildClaudeHookOutput("SessionStart", handleSessionStart({
|
|
22
|
+
cwd,
|
|
23
|
+
fallbackCommands: [
|
|
24
|
+
"npx @brawnen/agent-harness-cli state active",
|
|
25
|
+
"npx @brawnen/agent-harness-cli task intake \"任务描述\""
|
|
26
|
+
],
|
|
27
|
+
hostDisplayName: "Claude Code",
|
|
28
|
+
source: payload?.source ?? ""
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (event === "user-prompt-submit") {
|
|
33
|
+
return buildClaudeHookOutput("UserPromptSubmit", handlePromptSubmit({
|
|
34
|
+
cwd,
|
|
35
|
+
fallbackCommands: MANUAL_FALLBACK_COMMANDS,
|
|
36
|
+
hostDisplayName: "Claude Code",
|
|
37
|
+
prompt: resolvePayloadPrompt(payload)
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (event === "stop") {
|
|
42
|
+
return buildClaudeHookOutput("Stop", handleCompletionGate({
|
|
43
|
+
cwd,
|
|
44
|
+
lastAssistantMessage: resolveClaudeCompletionMessage(payload)
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error(`未知 Claude hook 事件: ${event}`);
|
|
49
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleCompletionGate,
|
|
3
|
+
handlePromptSubmit,
|
|
4
|
+
handleSessionStart
|
|
5
|
+
} from "./hook-core.js";
|
|
6
|
+
import { buildCodexHookOutput, resolveCodexCompletionMessage } from "./hook-io/codex.js";
|
|
7
|
+
import { resolvePayloadCwd, resolvePayloadPrompt } from "./hook-io/shared.js";
|
|
8
|
+
|
|
9
|
+
const MANUAL_FALLBACK_COMMANDS = [
|
|
10
|
+
"node packages/cli/bin/agent-harness.js state active",
|
|
11
|
+
"node packages/cli/bin/agent-harness.js task intake \"任务描述\"",
|
|
12
|
+
"node packages/cli/bin/agent-harness.js task suspend-active --reason \"切换任务\""
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export function runCodexHook(event, payload) {
|
|
16
|
+
if (event === "session-start") {
|
|
17
|
+
const decision = handleSessionStart({
|
|
18
|
+
cwd: resolvePayloadCwd(payload),
|
|
19
|
+
fallbackCommands: [
|
|
20
|
+
"node packages/cli/bin/agent-harness.js state active",
|
|
21
|
+
"node packages/cli/bin/agent-harness.js task intake \"任务描述\""
|
|
22
|
+
],
|
|
23
|
+
hostDisplayName: "Codex",
|
|
24
|
+
source: payload?.source ?? ""
|
|
25
|
+
});
|
|
26
|
+
return buildCodexHookOutput("SessionStart", decision);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (event === "user-prompt-submit") {
|
|
30
|
+
const decision = handlePromptSubmit({
|
|
31
|
+
cwd: resolvePayloadCwd(payload),
|
|
32
|
+
fallbackCommands: MANUAL_FALLBACK_COMMANDS,
|
|
33
|
+
hostDisplayName: "Codex",
|
|
34
|
+
prompt: resolvePayloadPrompt(payload)
|
|
35
|
+
});
|
|
36
|
+
return buildCodexHookOutput("UserPromptSubmit", decision);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (event === "stop") {
|
|
40
|
+
const decision = handleCompletionGate({
|
|
41
|
+
cwd: resolvePayloadCwd(payload),
|
|
42
|
+
lastAssistantMessage: resolveCodexCompletionMessage(payload)
|
|
43
|
+
});
|
|
44
|
+
return buildCodexHookOutput("Stop", decision);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(`未知 Codex hook 事件: ${event}`);
|
|
48
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleAfterTool,
|
|
3
|
+
handleBeforeTool,
|
|
4
|
+
handleCompletionGate,
|
|
5
|
+
handlePromptSubmit,
|
|
6
|
+
handleSessionStart
|
|
7
|
+
} from "./hook-core.js";
|
|
8
|
+
import {
|
|
9
|
+
buildGeminiHookOutput,
|
|
10
|
+
resolveGeminiCompletionMessage,
|
|
11
|
+
resolveGeminiToolCommand,
|
|
12
|
+
resolveGeminiToolExitCode,
|
|
13
|
+
resolveGeminiToolName,
|
|
14
|
+
resolveGeminiToolOutput,
|
|
15
|
+
resolveGeminiToolPath
|
|
16
|
+
} from "./hook-io/gemini.js";
|
|
17
|
+
import { resolvePayloadCwd, resolvePayloadPrompt } from "./hook-io/shared.js";
|
|
18
|
+
|
|
19
|
+
const MANUAL_FALLBACK_COMMANDS = [
|
|
20
|
+
"node packages/cli/bin/agent-harness.js state active",
|
|
21
|
+
"node packages/cli/bin/agent-harness.js task intake \"任务描述\"",
|
|
22
|
+
"node packages/cli/bin/agent-harness.js task suspend-active --reason \"切换任务\""
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function runGeminiHook(event, payload) {
|
|
26
|
+
const cwd = resolvePayloadCwd(payload);
|
|
27
|
+
|
|
28
|
+
if (event === "session-start") {
|
|
29
|
+
return buildGeminiHookOutput(handleSessionStart({
|
|
30
|
+
cwd,
|
|
31
|
+
fallbackCommands: [
|
|
32
|
+
"node packages/cli/bin/agent-harness.js state active",
|
|
33
|
+
"node packages/cli/bin/agent-harness.js task intake \"任务描述\""
|
|
34
|
+
],
|
|
35
|
+
hostDisplayName: "Gemini CLI",
|
|
36
|
+
source: payload?.source ?? ""
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (event === "before-agent") {
|
|
41
|
+
return buildGeminiHookOutput(handlePromptSubmit({
|
|
42
|
+
cwd,
|
|
43
|
+
fallbackCommands: MANUAL_FALLBACK_COMMANDS,
|
|
44
|
+
hostDisplayName: "Gemini CLI",
|
|
45
|
+
prompt: resolvePayloadPrompt(payload)
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (event === "before-tool") {
|
|
50
|
+
return buildGeminiHookOutput(handleBeforeTool({
|
|
51
|
+
command: resolveGeminiToolCommand(payload),
|
|
52
|
+
cwd,
|
|
53
|
+
filePath: resolveGeminiToolPath(payload),
|
|
54
|
+
toolName: resolveGeminiToolName(payload)
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (event === "after-tool") {
|
|
59
|
+
return buildGeminiHookOutput(handleAfterTool({
|
|
60
|
+
command: resolveGeminiToolCommand(payload),
|
|
61
|
+
cwd,
|
|
62
|
+
exitCode: resolveGeminiToolExitCode(payload),
|
|
63
|
+
output: resolveGeminiToolOutput(payload),
|
|
64
|
+
toolName: resolveGeminiToolName(payload)
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (event === "after-agent") {
|
|
69
|
+
return buildGeminiHookOutput(handleCompletionGate({
|
|
70
|
+
cwd,
|
|
71
|
+
lastAssistantMessage: resolveGeminiCompletionMessage(payload)
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error(`未知 Gemini hook 事件: ${event}`);
|
|
76
|
+
}
|