@brawnen/agent-harness-cli 0.1.0 → 0.1.2

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.
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { evaluateTaskDeliveryReadiness, summarizeDeliveryReadiness } from "../lib/delivery-policy.js";
5
+ import { hasConvergedHostLayout, HOST_LAYOUT_VERSION } from "../lib/host-layout.js";
5
6
  import { evaluateTaskArtifactPolicy, inspectOutputPolicyWorkspace, normalizeOutputPolicy } from "../lib/output-policy.js";
6
7
  import { loadProjectConfig } from "../lib/project-config.js";
7
8
  import {
@@ -36,6 +37,10 @@ export function runStatus(argv) {
36
37
  pushCheck(checks, harnessConfig);
37
38
  exitCode = maxExitCode(exitCode, harnessConfig.severity);
38
39
 
40
+ const hostLayoutCheck = inspectHostLayout(cwd);
41
+ pushCheck(checks, hostLayoutCheck);
42
+ exitCode = maxExitCode(exitCode, hostLayoutCheck.severity);
43
+
39
44
  const deliveryPolicyCheck = inspectDeliveryPolicy(cwd);
40
45
  pushCheck(checks, deliveryPolicyCheck);
41
46
  exitCode = maxExitCode(exitCode, deliveryPolicyCheck.severity);
@@ -72,6 +77,10 @@ export function runStatus(argv) {
72
77
  pushCheck(checks, claudeHooksCheck);
73
78
  exitCode = maxExitCode(exitCode, claudeHooksCheck.severity);
74
79
 
80
+ const geminiAdapterCheck = inspectGeminiAdapter(cwd, runtimeMode, hosts.includes("gemini-cli"));
81
+ pushCheck(checks, geminiAdapterCheck);
82
+ exitCode = maxExitCode(exitCode, geminiAdapterCheck.severity);
83
+
75
84
  const runtimeDirsCheck = inspectRuntimeDirectories(cwd, runtimeMode);
76
85
  pushCheck(checks, runtimeDirsCheck);
77
86
  exitCode = maxExitCode(exitCode, runtimeDirsCheck.severity);
@@ -140,6 +149,28 @@ function inspectDeliveryPolicy(cwd) {
140
149
  return ok("delivery_policy", `active_task=${activeTask.task_id};${summarizeDeliveryReadiness(readiness)}`);
141
150
  }
142
151
 
152
+ function inspectHostLayout(cwd) {
153
+ if (!hasConvergedHostLayout(cwd)) {
154
+ return warn("host_layout", "未检测到 .harness/hosts 与 .harness/rules,当前仍可能处于旧布局");
155
+ }
156
+
157
+ const manifestPath = path.join(cwd, ".harness", "generated", "manifest.json");
158
+ if (!fs.existsSync(manifestPath)) {
159
+ return warn("host_layout", "已检测到收敛布局源目录,但缺少 .harness/generated/manifest.json");
160
+ }
161
+
162
+ try {
163
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
164
+ const version = manifest.version ?? HOST_LAYOUT_VERSION;
165
+ const hosts = Array.isArray(manifest.hosts) && manifest.hosts.length > 0
166
+ ? manifest.hosts.join(", ")
167
+ : "unknown";
168
+ return ok("host_layout", `已启用收敛布局(version=${version}, hosts=${hosts})`);
169
+ } catch {
170
+ return warn("host_layout", ".harness/generated/manifest.json 存在,但 JSON 解析失败");
171
+ }
172
+ }
173
+
143
174
  function inspectOutputPolicy(cwd) {
144
175
  const config = loadProjectConfig(cwd);
145
176
  if (!config) {
@@ -307,14 +338,21 @@ function inspectCodexHooks(cwd, hasCodexHost) {
307
338
  return warn(".codex/hooks", "hooks.json 存在,但 JSON 解析失败");
308
339
  }
309
340
 
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
- ];
341
+ const hasUserPromptSubmit = hasCodexHookCommand(parsedHooks, "UserPromptSubmit", "user_prompt_submit_intake.js");
342
+ const hasSessionStart = hasCodexHookCommand(parsedHooks, "SessionStart", "session_start_restore.js");
343
+ const hasPreToolUse = hasCodexHookCommand(parsedHooks, "PreToolUse", "pre_tool_use_gate.js");
344
+ const hasPostToolUse = hasCodexHookCommand(parsedHooks, "PostToolUse", "post_tool_use_record_evidence.js");
345
+
346
+ if (!hasUserPromptSubmit || !hasSessionStart) {
347
+ return warn(".codex/hooks", "hooks.json 存在,但缺少最小 Codex hooks:SessionStart / UserPromptSubmit");
348
+ }
315
349
 
316
- if (checks.some((item) => item === false)) {
317
- return warn(".codex/hooks", "hooks.json 存在,但 agent-harness Codex hooks 不完整");
350
+ if (!hasPreToolUse && !hasPostToolUse) {
351
+ return ok(".codex/hooks", "Codex hooks 已配置;已启用 SessionStart / UserPromptSubmit,工具级 hooks 当前关闭");
352
+ }
353
+
354
+ if (hasPreToolUse !== hasPostToolUse) {
355
+ return warn(".codex/hooks", "hooks.json 存在,但工具级 hooks 仅部分启用");
318
356
  }
319
357
 
320
358
  return ok(".codex/hooks", "Codex hooks 已配置;trusted project 默认启用,untrusted 请显式使用 codex --enable codex_hooks");
@@ -348,14 +386,130 @@ function inspectClaudeHooks(cwd, runtimeMode, hasClaudeHost) {
348
386
  .map((hook) => hook.command)
349
387
  .filter(Boolean);
350
388
 
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"));
389
+ const hasPreTool = hasCommandFragment(commands, [
390
+ "agent-harness gate before-tool",
391
+ "@brawnen/agent-harness-cli gate before-tool",
392
+ "packages/cli/bin/agent-harness.js\" gate before-tool",
393
+ "packages/cli/bin/agent-harness.js gate before-tool",
394
+ ".harness/hosts/claude/hooks/pre_tool_use.js"
395
+ ]);
396
+ const hasPostTool = hasCommandFragment(commands, [
397
+ "agent-harness state update",
398
+ "@brawnen/agent-harness-cli state update",
399
+ "packages/cli/bin/agent-harness.js\" state update",
400
+ "packages/cli/bin/agent-harness.js state update",
401
+ ".harness/hosts/claude/hooks/post_tool_use.js"
402
+ ]);
403
+ const hasSessionStart = hasCommandFragment(commands, [
404
+ "agent-harness hook claude session-start",
405
+ "@brawnen/agent-harness-cli hook claude session-start",
406
+ "packages/cli/bin/agent-harness.js\" hook claude session-start",
407
+ "packages/cli/bin/agent-harness.js hook claude session-start",
408
+ ".harness/hosts/claude/hooks/session_start.js"
409
+ ]);
410
+ const hasUserPromptSubmit = hasCommandFragment(commands, [
411
+ "agent-harness hook claude user-prompt-submit",
412
+ "@brawnen/agent-harness-cli hook claude user-prompt-submit",
413
+ "packages/cli/bin/agent-harness.js\" hook claude user-prompt-submit",
414
+ "packages/cli/bin/agent-harness.js hook claude user-prompt-submit",
415
+ ".harness/hosts/claude/hooks/user_prompt_submit.js"
416
+ ]);
417
+ const hasStop = hasCommandFragment(commands, [
418
+ "agent-harness hook claude stop",
419
+ "@brawnen/agent-harness-cli hook claude stop",
420
+ "packages/cli/bin/agent-harness.js\" hook claude stop",
421
+ "packages/cli/bin/agent-harness.js hook claude stop",
422
+ ".harness/hosts/claude/hooks/stop.js"
423
+ ]);
424
+
425
+ if (hasSessionStart && hasUserPromptSubmit && hasPreTool && hasPostTool && hasStop) {
426
+ return ok(".claude/settings.json", "Claude Code hooks 已配置(SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop)");
427
+ }
353
428
 
354
429
  if (hasPreTool && hasPostTool) {
355
- return ok(".claude/settings.json", "Claude Code hooks 已配置");
430
+ return warn(".claude/settings.json", "检测到旧版 Claude Code 最小 hooks,缺少 SessionStart / UserPromptSubmit / Stop");
356
431
  }
357
432
 
358
- return warn(".claude/settings.json", "hooks 存在,但 agent-harness 命令不完整");
433
+ return warn(".claude/settings.json", "hooks 存在,但 Claude Code adapter 命令不完整");
434
+ }
435
+
436
+ function inspectGeminiAdapter(cwd, runtimeMode, hasGeminiHost) {
437
+ if (!hasGeminiHost) {
438
+ return skip("Gemini adapter", "当前项目未检测到 Gemini CLI 宿主");
439
+ }
440
+
441
+ const hostPath = path.join(cwd, "GEMINI.md");
442
+ const settingsPath = path.join(cwd, ".gemini", "settings.json");
443
+ if (!fs.existsSync(hostPath)) {
444
+ return warn("Gemini adapter", "缺少 GEMINI.md,无法注入 Gemini CLI 宿主规则");
445
+ }
446
+
447
+ if (!fs.existsSync(settingsPath)) {
448
+ if (runtimeMode === "protocol-only") {
449
+ return skip("Gemini adapter", "protocol-only 模式,仅依赖 GEMINI.md 规则注入");
450
+ }
451
+
452
+ return warn("Gemini adapter", "未发现 .gemini/settings.json,Gemini CLI hooks 尚未配置");
453
+ }
454
+
455
+ let parsed;
456
+ try {
457
+ parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
458
+ } catch {
459
+ return warn("Gemini adapter", ".gemini/settings.json 存在,但 JSON 解析失败");
460
+ }
461
+
462
+ const commands = Object.values(parsed.hooks ?? {})
463
+ .flatMap((entries) => entries ?? [])
464
+ .flatMap((entry) => entry.hooks ?? [])
465
+ .map((hook) => hook.command)
466
+ .filter(Boolean);
467
+
468
+ const hasSessionStart = hasCommandFragment(commands, [
469
+ "agent-harness hook gemini session-start",
470
+ "@brawnen/agent-harness-cli hook gemini session-start",
471
+ "packages/cli/bin/agent-harness.js\" hook gemini session-start",
472
+ "packages/cli/bin/agent-harness.js hook gemini session-start",
473
+ ".harness/hosts/gemini/hooks/session_start.js"
474
+ ]);
475
+ const hasBeforeAgent = hasCommandFragment(commands, [
476
+ "agent-harness hook gemini before-agent",
477
+ "@brawnen/agent-harness-cli hook gemini before-agent",
478
+ "packages/cli/bin/agent-harness.js\" hook gemini before-agent",
479
+ "packages/cli/bin/agent-harness.js hook gemini before-agent",
480
+ ".harness/hosts/gemini/hooks/before_agent.js"
481
+ ]);
482
+ const hasBeforeTool = hasCommandFragment(commands, [
483
+ "agent-harness hook gemini before-tool",
484
+ "@brawnen/agent-harness-cli hook gemini before-tool",
485
+ "packages/cli/bin/agent-harness.js\" hook gemini before-tool",
486
+ "packages/cli/bin/agent-harness.js hook gemini before-tool",
487
+ ".harness/hosts/gemini/hooks/before_tool.js"
488
+ ]);
489
+ const hasAfterTool = hasCommandFragment(commands, [
490
+ "agent-harness hook gemini after-tool",
491
+ "@brawnen/agent-harness-cli hook gemini after-tool",
492
+ "packages/cli/bin/agent-harness.js\" hook gemini after-tool",
493
+ "packages/cli/bin/agent-harness.js hook gemini after-tool",
494
+ ".harness/hosts/gemini/hooks/after_tool.js"
495
+ ]);
496
+ const hasAfterAgent = hasCommandFragment(commands, [
497
+ "agent-harness hook gemini after-agent",
498
+ "@brawnen/agent-harness-cli hook gemini after-agent",
499
+ "packages/cli/bin/agent-harness.js\" hook gemini after-agent",
500
+ "packages/cli/bin/agent-harness.js hook gemini after-agent",
501
+ ".harness/hosts/gemini/hooks/after_agent.js"
502
+ ]);
503
+
504
+ const modeSummary = runtimeMode === "protocol-only"
505
+ ? "当前为 protocol-only,但 hooks 已可用"
506
+ : "已检测到 .harness 运行时目录,可配合 hooks 与兼容 CLI 做 state / verify / report";
507
+
508
+ if (hasSessionStart && hasBeforeAgent && hasBeforeTool && hasAfterTool && hasAfterAgent) {
509
+ return ok("Gemini adapter", `Gemini CLI hooks 已配置(SessionStart / BeforeAgent / BeforeTool / AfterTool / AfterAgent);${modeSummary}`);
510
+ }
511
+
512
+ return warn("Gemini adapter", "Gemini CLI hooks 已部分配置,但命令集合不完整");
359
513
  }
360
514
 
361
515
  function hasCodexHookCommand(parsedHooks, eventName, commandFragment) {
@@ -369,6 +523,10 @@ function hasCodexHookCommand(parsedHooks, eventName, commandFragment) {
369
523
  .some((hook) => typeof hook?.command === "string" && hook.command.includes(commandFragment));
370
524
  }
371
525
 
526
+ function hasCommandFragment(commands, fragments) {
527
+ return commands.some((command) => fragments.some((fragment) => command.includes(fragment)));
528
+ }
529
+
372
530
  function inspectRuntimeDirectories(cwd, runtimeMode) {
373
531
  if (runtimeMode === "protocol-only") {
374
532
  return skip("runtime", "protocol-only 模式,无需运行时目录");
@@ -0,0 +1,88 @@
1
+ import { applyHostLayoutWrites, collectHostLayoutWrites, HOST_LAYOUT_HOSTS } from "../lib/host-layout.js";
2
+
3
+ export function runSync(argv) {
4
+ const parsed = parseSyncArgs(argv);
5
+ if (!parsed.ok) {
6
+ console.error(parsed.error);
7
+ return 1;
8
+ }
9
+
10
+ const cwd = process.cwd();
11
+ const result = collectHostLayoutWrites(cwd, {
12
+ check: parsed.options.check,
13
+ hosts: parsed.options.hosts,
14
+ rewrite: parsed.options.rewrite,
15
+ seedMissing: true
16
+ });
17
+
18
+ for (const warning of result.warnings) {
19
+ console.error(`warn: ${warning}`);
20
+ }
21
+
22
+ if (parsed.options.check) {
23
+ if (result.warnings.length === 0 && result.writes.length === 0) {
24
+ console.log("host layout 已同步");
25
+ return 0;
26
+ }
27
+
28
+ for (const write of result.writes) {
29
+ console.log(`drift: ${write.relativePath}`);
30
+ }
31
+ return 1;
32
+ }
33
+
34
+ applyHostLayoutWrites(result.writes);
35
+
36
+ for (const write of result.writes) {
37
+ console.log(`write: ${write.relativePath}`);
38
+ }
39
+
40
+ if (result.warnings.length > 0) {
41
+ return 1;
42
+ }
43
+
44
+ console.log("sync 完成。");
45
+ return 0;
46
+ }
47
+
48
+ function parseSyncArgs(argv) {
49
+ const options = {
50
+ check: false,
51
+ hosts: null,
52
+ rewrite: false
53
+ };
54
+
55
+ const hosts = [];
56
+
57
+ for (let index = 0; index < argv.length; index += 1) {
58
+ const arg = argv[index];
59
+
60
+ if (arg === "--check") {
61
+ options.check = true;
62
+ continue;
63
+ }
64
+
65
+ if (arg === "--rewrite") {
66
+ options.rewrite = true;
67
+ continue;
68
+ }
69
+
70
+ if (arg === "--host") {
71
+ const value = argv[index + 1];
72
+ if (!HOST_LAYOUT_HOSTS.includes(value)) {
73
+ return { ok: false, error: "无效的 --host 参数。可选值: codex, claude-code, gemini-cli" };
74
+ }
75
+ hosts.push(value);
76
+ index += 1;
77
+ continue;
78
+ }
79
+
80
+ return { ok: false, error: `未知参数: ${arg}` };
81
+ }
82
+
83
+ if (hosts.length > 0) {
84
+ options.hosts = hosts;
85
+ }
86
+
87
+ return { ok: true, options };
88
+ }
package/src/index.js CHANGED
@@ -2,14 +2,16 @@ 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";
8
+ import { runSync } from "./commands/sync.js";
7
9
  import { runState } from "./commands/state.js";
8
10
  import { runStatus } from "./commands/status.js";
9
11
  import { runTask } from "./commands/task.js";
10
12
  import { runVerify } from "./commands/verify.js";
11
13
 
12
- const HELP_TEXT = `agent-harness CLI
14
+ const HELP_TEXT = `agent-harness compatibility CLI
13
15
 
14
16
  Usage:
15
17
  agent-harness --help
@@ -21,6 +23,8 @@ Usage:
21
23
  agent-harness delivery <ready|request|commit>
22
24
  agent-harness docs scaffold --type <design-note|adr>
23
25
  agent-harness gate before-tool --tool <tool>
26
+ agent-harness hook <claude|codex|gemini> <event>
27
+ agent-harness sync [--check] [--rewrite]
24
28
  agent-harness status
25
29
  agent-harness task intake "<任务描述>"
26
30
  agent-harness task confirm [--task-id <task-id>]
@@ -39,7 +43,7 @@ Options:
39
43
  --force
40
44
 
41
45
  Status:
42
- task/init/status/state/verify/report/gate/audit/delivery/docs MVP are implemented.
46
+ CLI 主要承担 init/status/manual fallback,宿主运行时正向 repo-local hooks 收敛。
43
47
  `;
44
48
 
45
49
  export function run(argv) {
@@ -51,7 +55,7 @@ export function run(argv) {
51
55
  }
52
56
 
53
57
  if (command === "--version" || command === "-v") {
54
- console.log("0.1.0");
58
+ console.log("0.1.2");
55
59
  return 0;
56
60
  }
57
61
 
@@ -75,10 +79,18 @@ export function run(argv) {
75
79
  return runGate(argv.slice(1));
76
80
  }
77
81
 
82
+ if (command === "hook") {
83
+ return runHook(argv.slice(1));
84
+ }
85
+
78
86
  if (command === "status") {
79
87
  return runStatus(argv.slice(1));
80
88
  }
81
89
 
90
+ if (command === "sync") {
91
+ return runSync(argv.slice(1));
92
+ }
93
+
82
94
  if (command === "task") {
83
95
  return runTask(argv.slice(1));
84
96
  }
@@ -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
+ }