@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 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
- ## Codex Support
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.0",
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.0"
38
+ "@brawnen/agent-harness-protocol": "^0.1.1"
39
39
  },
40
40
  "scripts": {
41
41
  "start": "node ./bin/agent-harness.js --help"
@@ -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
+ }
@@ -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({
@@ -110,7 +110,9 @@ function runStateUpdate(argv) {
110
110
  }
111
111
 
112
112
  const result = updateTaskState(process.cwd(), taskId, changes);
113
- printJson(result);
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
 
@@ -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 checks = [
311
- hasCodexHookCommand(parsedHooks, "UserPromptSubmit", "user_prompt_submit_intake.js"),
312
- hasCodexHookCommand(parsedHooks, "SessionStart", "session_start_restore.js"),
313
- hasCodexHookCommand(parsedHooks, "PostToolUse", "post_tool_use_record_evidence.js")
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 (checks.some((item) => item === false)) {
317
- return warn(".codex/hooks", "hooks.json 存在,但 agent-harness Codex hooks 不完整");
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) => command.includes("agent-harness gate before-tool"));
352
- const hasPostTool = commands.some((command) => command.includes("agent-harness state update"));
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 ok(".claude/settings.json", "Claude Code hooks 已配置");
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
- return warn(".claude/settings.json", "hooks 存在,但 agent-harness 命令不完整");
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
+ }