context-mode 1.0.162 → 1.0.164

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.
Files changed (149) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  5. package/.openclaw-plugin/package.json +1 -1
  6. package/README.md +149 -30
  7. package/bin/statusline.mjs +24 -4
  8. package/build/adapters/antigravity/index.d.ts +1 -1
  9. package/build/adapters/antigravity-cli/index.d.ts +51 -0
  10. package/build/adapters/antigravity-cli/index.js +342 -0
  11. package/build/adapters/claude-code/hooks.d.ts +1 -0
  12. package/build/adapters/claude-code/hooks.js +3 -0
  13. package/build/adapters/claude-code/index.js +24 -5
  14. package/build/adapters/client-map.js +5 -0
  15. package/build/adapters/codex/hooks.d.ts +5 -1
  16. package/build/adapters/codex/hooks.js +5 -1
  17. package/build/adapters/codex/index.d.ts +9 -1
  18. package/build/adapters/codex/index.js +87 -5
  19. package/build/adapters/copilot-cli/hooks.d.ts +33 -0
  20. package/build/adapters/copilot-cli/hooks.js +64 -0
  21. package/build/adapters/copilot-cli/index.d.ts +48 -0
  22. package/build/adapters/copilot-cli/index.js +341 -0
  23. package/build/adapters/detect.d.ts +1 -1
  24. package/build/adapters/detect.js +71 -3
  25. package/build/adapters/openclaw/mcp-tools.js +1 -1
  26. package/build/adapters/opencode/index.js +31 -17
  27. package/build/adapters/opencode/zod3tov4.js +27 -6
  28. package/build/adapters/pi/extension.d.ts +2 -12
  29. package/build/adapters/pi/extension.js +128 -109
  30. package/build/adapters/types.d.ts +5 -4
  31. package/build/adapters/types.js +4 -3
  32. package/build/cache-heal.d.ts +48 -0
  33. package/build/cache-heal.js +150 -0
  34. package/build/cli.js +37 -97
  35. package/build/executor.d.ts +25 -0
  36. package/build/executor.js +143 -22
  37. package/build/lifecycle.d.ts +48 -0
  38. package/build/lifecycle.js +111 -0
  39. package/build/opencode-plugin.js +5 -2
  40. package/build/routing-block.d.ts +8 -0
  41. package/build/routing-block.js +86 -0
  42. package/build/runtime.d.ts +0 -36
  43. package/build/runtime.js +107 -27
  44. package/build/search/flood-guard.d.ts +57 -0
  45. package/build/search/flood-guard.js +80 -0
  46. package/build/security.d.ts +73 -3
  47. package/build/security.js +293 -33
  48. package/build/server.d.ts +14 -0
  49. package/build/server.js +441 -354
  50. package/build/session/analytics.d.ts +1 -1
  51. package/build/session/analytics.js +5 -1
  52. package/build/session/db.js +23 -3
  53. package/build/session/extract.js +78 -0
  54. package/build/store.d.ts +1 -1
  55. package/build/store.js +139 -25
  56. package/build/tool-naming.d.ts +4 -0
  57. package/build/tool-naming.js +24 -0
  58. package/build/util/jsonc.d.ts +14 -0
  59. package/build/util/jsonc.js +104 -0
  60. package/cli.bundle.mjs +253 -250
  61. package/configs/antigravity/GEMINI.md +2 -2
  62. package/configs/antigravity-cli/hooks/hooks.json +37 -0
  63. package/configs/antigravity-cli/hooks.json +37 -0
  64. package/configs/antigravity-cli/mcp_config.json +10 -0
  65. package/configs/antigravity-cli/plugin.json +14 -0
  66. package/configs/antigravity-cli/rules/context-mode.md +77 -0
  67. package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
  68. package/configs/claude-code/CLAUDE.md +2 -2
  69. package/configs/codex/AGENTS.md +2 -2
  70. package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
  71. package/configs/copilot-cli/.mcp.json +12 -0
  72. package/configs/copilot-cli/README.md +47 -0
  73. package/configs/copilot-cli/hooks.json +41 -0
  74. package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
  75. package/configs/gemini-cli/GEMINI.md +2 -2
  76. package/configs/jetbrains-copilot/copilot-instructions.md +2 -2
  77. package/configs/kilo/AGENTS.md +2 -2
  78. package/configs/kiro/KIRO.md +2 -2
  79. package/configs/omp/SYSTEM.md +2 -2
  80. package/configs/openclaw/AGENTS.md +2 -2
  81. package/configs/opencode/AGENTS.md +2 -2
  82. package/configs/qwen-code/QWEN.md +2 -2
  83. package/configs/vscode-copilot/copilot-instructions.md +2 -2
  84. package/configs/zed/AGENTS.md +2 -2
  85. package/hooks/antigravity-cli/payload.mjs +98 -0
  86. package/hooks/antigravity-cli/posttooluse.mjs +138 -0
  87. package/hooks/antigravity-cli/pretooluse.mjs +78 -0
  88. package/hooks/antigravity-cli/stop.mjs +58 -0
  89. package/hooks/codex/pretooluse.mjs +14 -4
  90. package/hooks/codex/stop.mjs +12 -4
  91. package/hooks/copilot-cli/posttooluse.mjs +79 -0
  92. package/hooks/copilot-cli/precompact.mjs +66 -0
  93. package/hooks/copilot-cli/pretooluse.mjs +41 -0
  94. package/hooks/copilot-cli/sessionstart.mjs +121 -0
  95. package/hooks/copilot-cli/stop.mjs +59 -0
  96. package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
  97. package/hooks/core/codex-caps.mjs +112 -0
  98. package/hooks/core/formatters.mjs +158 -7
  99. package/hooks/core/mcp-ready.mjs +37 -8
  100. package/hooks/core/routing.mjs +94 -8
  101. package/hooks/core/tool-naming.mjs +3 -0
  102. package/hooks/hooks.json +12 -1
  103. package/hooks/pretooluse.mjs +6 -2
  104. package/hooks/routing-block.mjs +3 -4
  105. package/hooks/security.bundle.mjs +2 -1
  106. package/hooks/session-db.bundle.mjs +5 -5
  107. package/hooks/session-directive.mjs +88 -20
  108. package/hooks/session-extract.bundle.mjs +2 -2
  109. package/hooks/session-helpers.mjs +21 -0
  110. package/hooks/sessionstart.mjs +37 -5
  111. package/hooks/stop.mjs +49 -0
  112. package/openclaw.plugin.json +1 -1
  113. package/package.json +2 -10
  114. package/server.bundle.mjs +206 -200
  115. package/skills/ctx-insight/SKILL.md +12 -17
  116. package/build/util/db-lock.d.ts +0 -65
  117. package/build/util/db-lock.js +0 -166
  118. package/insight/index.html +0 -13
  119. package/insight/package.json +0 -55
  120. package/insight/server.mjs +0 -1265
  121. package/insight/src/components/analytics.tsx +0 -112
  122. package/insight/src/components/ui/badge.tsx +0 -52
  123. package/insight/src/components/ui/button.tsx +0 -58
  124. package/insight/src/components/ui/card.tsx +0 -103
  125. package/insight/src/components/ui/chart.tsx +0 -371
  126. package/insight/src/components/ui/collapsible.tsx +0 -19
  127. package/insight/src/components/ui/input.tsx +0 -20
  128. package/insight/src/components/ui/progress.tsx +0 -83
  129. package/insight/src/components/ui/scroll-area.tsx +0 -55
  130. package/insight/src/components/ui/separator.tsx +0 -23
  131. package/insight/src/components/ui/table.tsx +0 -114
  132. package/insight/src/components/ui/tabs.tsx +0 -82
  133. package/insight/src/components/ui/tooltip.tsx +0 -64
  134. package/insight/src/lib/api.ts +0 -144
  135. package/insight/src/lib/utils.ts +0 -6
  136. package/insight/src/main.tsx +0 -22
  137. package/insight/src/routeTree.gen.ts +0 -189
  138. package/insight/src/router.tsx +0 -19
  139. package/insight/src/routes/__root.tsx +0 -55
  140. package/insight/src/routes/enterprise.tsx +0 -316
  141. package/insight/src/routes/index.tsx +0 -1482
  142. package/insight/src/routes/knowledge.tsx +0 -221
  143. package/insight/src/routes/knowledge_.$dbHash.$sourceId.tsx +0 -137
  144. package/insight/src/routes/search.tsx +0 -97
  145. package/insight/src/routes/sessions.tsx +0 -179
  146. package/insight/src/routes/sessions_.$dbHash.$sessionId.tsx +0 -181
  147. package/insight/src/styles.css +0 -104
  148. package/insight/tsconfig.json +0 -29
  149. package/insight/vite.config.ts +0 -19
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Shared Antigravity CLI (`agy`) hook payload normalization.
3
+ *
4
+ * The only refs-backed field is the working directory: the upstream hook example
5
+ * (refs/platforms/antigravity-cli/examples/title/title.sh:10, README.md:11)
6
+ * reads it from `workspace.current_dir` (an OBJECT field). We read that FIRST.
7
+ *
8
+ * The remaining fields below are empirically-derived/UNVERIFIED — no upstream
9
+ * agy doc or example confirms this shape; they are best-effort assumptions:
10
+ * { conversationId, stepIdx, toolCall: { name, args }, error,
11
+ * workspacePaths: [..], transcriptPath, artifactDirectoryPath }
12
+ *
13
+ * The shared context-mode capture/routing code expects Claude-shaped fields, so
14
+ * keep this mapping in one place for PreToolUse/PostToolUse/Stop.
15
+ */
16
+
17
+ export function parseAgyPayload(raw) {
18
+ try {
19
+ const cleaned = String(raw ?? "").replace(/^\uFEFF/, "").trim();
20
+ return cleaned ? JSON.parse(cleaned) : {};
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+
26
+ export function getAgyProjectDir(payload) {
27
+ // Refs-backed FIRST: workspace.current_dir is the only upstream-documented
28
+ // field (examples/title/title.sh:10). `workspacePaths[0]` is an unverified
29
+ // fallback kept defensively.
30
+ const workspace = payload?.workspace;
31
+ if (workspace && typeof workspace === "object" && typeof workspace.current_dir === "string" && workspace.current_dir) {
32
+ return workspace.current_dir;
33
+ }
34
+ return Array.isArray(payload?.workspacePaths) && payload.workspacePaths.length > 0
35
+ ? String(payload.workspacePaths[0])
36
+ : undefined;
37
+ }
38
+
39
+ // agy native tool-name -> canonical map. Keep in sync with the two other copies:
40
+ // hooks/core/routing.mjs (TOOL_ALIASES) and src/session/extract.ts
41
+ // (TOOL_NAME_NORMALIZE). Three layers normalize independently; adding a new agy
42
+ // tool means updating all three (a single shared table is a follow-up cleanup).
43
+ function normalizeAgyToolName(name) {
44
+ switch (name) {
45
+ case "run_command":
46
+ return "Bash";
47
+ case "view_file":
48
+ return "Read";
49
+ case "grep_search":
50
+ return "Grep";
51
+ case "list_dir":
52
+ return "LS";
53
+ case "web_fetch":
54
+ case "read_url_content":
55
+ return "WebFetch";
56
+ case "search_web":
57
+ return "WebSearch";
58
+ default:
59
+ return name;
60
+ }
61
+ }
62
+
63
+ function normalizeAgyToolInput(toolName, args) {
64
+ const input = args && typeof args === "object" ? { ...args } : {};
65
+ const canonical = normalizeAgyToolName(toolName);
66
+ if (canonical === "Bash" && typeof input.CommandLine === "string" && typeof input.command !== "string") {
67
+ input.command = input.CommandLine;
68
+ }
69
+ if (canonical === "WebFetch") {
70
+ const url = input.url ?? input.URL ?? input.Url;
71
+ if (typeof url === "string" && typeof input.url !== "string") input.url = url;
72
+ }
73
+ if (canonical === "Read") {
74
+ const filePath = input.file_path ?? input.path ?? input.AbsolutePath ?? input.FilePath;
75
+ if (typeof filePath === "string" && typeof input.file_path !== "string") input.file_path = filePath;
76
+ }
77
+ if (canonical === "Grep") {
78
+ const pattern = input.pattern ?? input.Pattern ?? input.query ?? input.Query;
79
+ if (typeof pattern === "string" && typeof input.pattern !== "string") input.pattern = pattern;
80
+ }
81
+ return input;
82
+ }
83
+
84
+ export function fromAgy(payload) {
85
+ const toolCall = payload?.toolCall ?? {};
86
+ const rawToolName = toolCall?.name ?? "";
87
+ return {
88
+ session_id: payload?.conversationId,
89
+ transcript_path: payload?.transcriptPath,
90
+ cwd: getAgyProjectDir(payload),
91
+ tool_name: normalizeAgyToolName(rawToolName),
92
+ tool_input: normalizeAgyToolInput(rawToolName, toolCall?.args),
93
+ tool_response: typeof payload?.error === "string" ? payload.error : "",
94
+ tool_output: {
95
+ isError: typeof payload?.error === "string" && payload.error.length > 0,
96
+ },
97
+ };
98
+ }
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ import "../ensure-deps.mjs";
4
+ /**
5
+ * Antigravity CLI (`agy`) PostToolUse hook — session event capture.
6
+ *
7
+ * agy fires hooks from a config at ~/.gemini/config/hooks.json (or via an
8
+ * installed agy plugin's hooks/hooks.json) and pipes a payload whose shape
9
+ * differs from the Claude-Code/Codex wire format this pipeline expects:
10
+ *
11
+ * { conversationId, stepIdx, toolCall: { name, args }, error,
12
+ * workspacePaths: [..], transcriptPath, artifactDirectoryPath }
13
+ *
14
+ * The event name arrives as argv (set in hooks.json), NOT in the payload, and
15
+ * the hook CWD is ~/.gemini/config — so the project dir MUST come from
16
+ * workspacePaths[0], never process.cwd(). We translate agy's payload into the
17
+ * Claude-shaped `input` the shared extractor/attribution pipeline consumes,
18
+ * then reuse it unchanged. This hook is capture-only and emits no stdout.
19
+ */
20
+
21
+ import {
22
+ readStdin,
23
+ getSessionId,
24
+ getSessionDBPath,
25
+ getInputProjectDir,
26
+ ANTIGRAVITY_CLI_OPTS,
27
+ } from "../session-helpers.mjs";
28
+ import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
29
+ import { readFileSync, unlinkSync } from "node:fs";
30
+ import { dirname, resolve } from "node:path";
31
+ import { tmpdir } from "node:os";
32
+ import { fileURLToPath } from "node:url";
33
+ import { fromAgy, parseAgyPayload } from "./payload.mjs";
34
+
35
+ const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
36
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
37
+ const OPTS = ANTIGRAVITY_CLI_OPTS;
38
+
39
+ try {
40
+ const input = fromAgy(parseAgyPayload(await readStdin()));
41
+
42
+ if (input.tool_name) {
43
+ const projectDir = getInputProjectDir(input, OPTS);
44
+
45
+ const { extractEvents } = await loadExtract();
46
+ const { resolveProjectAttributions } = await loadProjectAttribution();
47
+ const { SessionDB } = await loadSessionDB();
48
+
49
+ const dbPath = getSessionDBPath(OPTS, projectDir);
50
+ const db = new SessionDB({ dbPath });
51
+ const sessionId = getSessionId(input, OPTS);
52
+
53
+ db.ensureSession(sessionId, projectDir);
54
+
55
+ const normalizedInput = {
56
+ tool_name: input.tool_name,
57
+ tool_input: input.tool_input ?? {},
58
+ tool_response: input.tool_response ?? "",
59
+ tool_output: input.tool_output,
60
+ };
61
+
62
+ const events = extractEvents(normalizedInput);
63
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
64
+
65
+ try {
66
+ const rejectedPath = resolve(tmpdir(), `context-mode-rejected-${sessionId}.txt`);
67
+ let rejectedData;
68
+ try {
69
+ rejectedData = readFileSync(rejectedPath, "utf-8").trim();
70
+ unlinkSync(rejectedPath);
71
+ } catch { /* no marker */ }
72
+ if (rejectedData) {
73
+ const colonIdx = rejectedData.indexOf(":");
74
+ const rejTool = colonIdx > 0 ? rejectedData.slice(0, colonIdx) : rejectedData;
75
+ const rejReason = colonIdx > 0 ? rejectedData.slice(colonIdx + 1) : "denied";
76
+ attributeAndInsertEvents(
77
+ db,
78
+ sessionId,
79
+ [{
80
+ type: "rejected",
81
+ category: "rejected-approach",
82
+ data: `${rejTool}: ${rejReason}`,
83
+ priority: 2,
84
+ }],
85
+ input,
86
+ projectDir,
87
+ "PreToolUse",
88
+ resolveProjectAttributions,
89
+ );
90
+ }
91
+ } catch { /* best-effort */ }
92
+
93
+ try {
94
+ const redirectPath = resolve(tmpdir(), `context-mode-redirect-${sessionId}.txt`);
95
+ let redirectData;
96
+ try {
97
+ redirectData = readFileSync(redirectPath, "utf-8").trim();
98
+ unlinkSync(redirectPath);
99
+ } catch { /* no marker */ }
100
+
101
+ if (redirectData) {
102
+ const i1 = redirectData.indexOf(":");
103
+ const i2 = i1 >= 0 ? redirectData.indexOf(":", i1 + 1) : -1;
104
+ const i3 = i2 >= 0 ? redirectData.indexOf(":", i2 + 1) : -1;
105
+ if (i1 > 0 && i2 > i1 && i3 > i2) {
106
+ const tool = redirectData.slice(0, i1);
107
+ const type = redirectData.slice(i1 + 1, i2);
108
+ const bytesRaw = redirectData.slice(i2 + 1, i3);
109
+ const summary = redirectData.slice(i3 + 1);
110
+ const bytesAvoided = Number.parseInt(bytesRaw, 10);
111
+ if (Number.isFinite(bytesAvoided) && bytesAvoided > 0) {
112
+ attributeAndInsertEvents(
113
+ db,
114
+ sessionId,
115
+ [{
116
+ type,
117
+ category: "redirect",
118
+ data: `${tool}: ${summary}`,
119
+ priority: 2,
120
+ bytes_avoided: bytesAvoided,
121
+ }],
122
+ input,
123
+ projectDir,
124
+ "PreToolUse",
125
+ resolveProjectAttributions,
126
+ );
127
+ }
128
+ }
129
+ }
130
+ } catch { /* best-effort */ }
131
+
132
+ db.close();
133
+ }
134
+ } catch {
135
+ // Swallow errors — a hook must never fail the host agent.
136
+ }
137
+
138
+ // Capture-only hook: emit nothing.
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ /**
4
+ * Antigravity CLI (`agy`) PreToolUse hook — bounded routing enforcement.
5
+ *
6
+ * agy honors top-level `{ decision: "deny" | "ask", reason }` responses for
7
+ * PreToolUse. It does not honor additionalContext, so mapped context guidance is
8
+ * emitted as a deny-and-retry instruction. We register only tools with existing
9
+ * core routing branches (Bash/Read/Grep/WebFetch), not LS/search_web.
10
+ */
11
+
12
+ import { dirname, resolve } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { writeFileSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { readStdin } from "../core/stdin.mjs";
17
+ import { routePreToolUse, initSecurity } from "../core/routing.mjs";
18
+ import { formatDecision } from "../core/formatters.mjs";
19
+ import { fromAgy, getAgyProjectDir, parseAgyPayload } from "./payload.mjs";
20
+ import { getSessionId, ANTIGRAVITY_CLI_OPTS } from "../session-helpers.mjs";
21
+
22
+ const __hookDir = dirname(fileURLToPath(import.meta.url));
23
+
24
+ try {
25
+ await initSecurity(resolve(__hookDir, "..", "..", "build"));
26
+
27
+ const payload = parseAgyPayload(await readStdin());
28
+ const input = fromAgy(payload);
29
+
30
+ const decision = routePreToolUse(
31
+ String(input.tool_name ?? ""),
32
+ input.tool_input ?? {},
33
+ getAgyProjectDir(payload),
34
+ "antigravity-cli",
35
+ input.session_id,
36
+ );
37
+ const response = formatDecision("antigravity-cli", decision);
38
+
39
+ if (decision && input.tool_name) {
40
+ // Key markers on the SAME id posttooluse.mjs reads. getSessionId prefers the
41
+ // transcript UUID over conversationId, so deriving it any other way here
42
+ // (e.g. input.session_id) would miss the handoff whenever agy's transcript
43
+ // is <uuid>.jsonl and silently drop the rejected/redirect analytics.
44
+ const sessionId = getSessionId(input, ANTIGRAVITY_CLI_OPTS);
45
+ const formattedDeny = response && typeof response === "object" && response.decision === "deny";
46
+ if (formattedDeny || decision.action === "deny" || decision.action === "modify") {
47
+ try {
48
+ const reason = formattedDeny
49
+ ? (response.reason || "denied")
50
+ : decision.action === "deny"
51
+ ? (decision.reason || "denied")
52
+ : "Redirected to context-mode sandbox";
53
+ writeFileSync(
54
+ resolve(tmpdir(), `context-mode-rejected-${sessionId}.txt`),
55
+ `${input.tool_name}:${reason}`,
56
+ "utf-8",
57
+ );
58
+ } catch { /* best-effort */ }
59
+ }
60
+ if (decision.redirectMeta) {
61
+ try {
62
+ const meta = decision.redirectMeta;
63
+ const summary = String(meta.commandSummary ?? "").slice(0, 200);
64
+ writeFileSync(
65
+ resolve(tmpdir(), `context-mode-redirect-${sessionId}.txt`),
66
+ `${meta.tool}:${meta.type}:${meta.bytesAvoided}:${summary}`,
67
+ "utf-8",
68
+ );
69
+ } catch { /* best-effort */ }
70
+ }
71
+ }
72
+
73
+ if (response !== null) {
74
+ process.stdout.write(JSON.stringify(response) + "\n");
75
+ }
76
+ } catch {
77
+ // Fail OPEN. Empty stdout + exit 0 lets agy continue the tool call.
78
+ }
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ import "../ensure-deps.mjs";
4
+ /**
5
+ * Antigravity CLI (`agy`) Stop hook — session-end capture.
6
+ *
7
+ * agy's verified hook list exposes `Stop` ("when agent tries to exit") and no
8
+ * separate SessionEnd hook, so this records a single session_end marker when
9
+ * agy emits it. `agy -p` probes have not emitted Stop, so registration is
10
+ * best-effort. The hook is capture-only and emits no stdout.
11
+ */
12
+
13
+ import {
14
+ readStdin,
15
+ getSessionId,
16
+ getSessionDBPath,
17
+ getInputProjectDir,
18
+ ANTIGRAVITY_CLI_OPTS,
19
+ } from "../session-helpers.mjs";
20
+ import { createSessionLoaders } from "../session-loaders.mjs";
21
+ import { dirname } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { fromAgy, parseAgyPayload } from "./payload.mjs";
24
+
25
+ const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
26
+ const { loadSessionDB } = createSessionLoaders(HOOK_DIR);
27
+ const OPTS = ANTIGRAVITY_CLI_OPTS;
28
+
29
+ try {
30
+ const payload = parseAgyPayload(await readStdin());
31
+ const input = fromAgy(payload);
32
+ const projectDir = getInputProjectDir(input, OPTS);
33
+
34
+ const { SessionDB } = await loadSessionDB();
35
+ const dbPath = getSessionDBPath(OPTS, projectDir);
36
+ const db = new SessionDB({ dbPath });
37
+ const sessionId = getSessionId(input, OPTS);
38
+
39
+ db.ensureSession(sessionId, projectDir);
40
+ db.insertEvent(
41
+ sessionId,
42
+ {
43
+ type: "session_end",
44
+ category: "session",
45
+ priority: 1,
46
+ data: JSON.stringify({
47
+ status: payload?.status ?? "stopped",
48
+ stepIdx: payload?.stepIdx ?? null,
49
+ transcriptPath: payload?.transcriptPath ?? null,
50
+ }),
51
+ },
52
+ "Stop",
53
+ );
54
+
55
+ db.close();
56
+ } catch {
57
+ // A hook must never fail the host agent.
58
+ }
@@ -4,9 +4,11 @@ import "../suppress-stderr.mjs";
4
4
  /**
5
5
  * Codex CLI preToolUse hook for context-mode.
6
6
  *
7
- * Codex PreToolUse supports deny only additionalContext, updatedInput,
8
- * ask, and allow are rejected by codex-rs output_parser.rs.
9
- * Source: codex-rs/hooks/src/engine/output_parser.rs
7
+ * Codex PreToolUse honors `permissionDecision:"deny"` on all builds, and
8
+ * `permissionDecision:"allow" + updatedInput` / `additionalContext` on
9
+ * codex-cli >= 0.141.0 (#845). Capability is detected at runtime by
10
+ * codex-caps.mjs; older builds fail closed (redirect → deny). `ask` is still
11
+ * unsupported. Source: codex-rs/hooks/src/engine/output_parser.rs
10
12
  */
11
13
 
12
14
  import { dirname, resolve } from "node:path";
@@ -14,6 +16,7 @@ import { fileURLToPath } from "node:url";
14
16
  import { readStdin, parseStdin, getInputProjectDir, getSessionId, CODEX_OPTS } from "../session-helpers.mjs";
15
17
  import { routePreToolUse, initSecurity } from "../core/routing.mjs";
16
18
  import { formatDecision } from "../core/formatters.mjs";
19
+ import { codexSupportsUpdatedInput } from "../core/codex-caps.mjs";
17
20
 
18
21
  const __hookDir = dirname(fileURLToPath(import.meta.url));
19
22
  await initSecurity(resolve(__hookDir, "..", "..", "build"));
@@ -25,7 +28,14 @@ const toolInput = input.tool_input ?? {};
25
28
  const projectDir = getInputProjectDir(input, CODEX_OPTS);
26
29
 
27
30
  const decision = routePreToolUse(tool, toolInput, projectDir, "codex", getSessionId(input, CODEX_OPTS));
28
- const response = formatDecision("codex", decision);
31
+ // #845: only modify/context depend on Codex's rewrite capability. Detection is
32
+ // cached, but skip the probe entirely for deny / ask / passthrough decisions.
33
+ const needsCaps = decision && (decision.action === "modify" || decision.action === "context");
34
+ const response = formatDecision(
35
+ "codex",
36
+ decision,
37
+ needsCaps ? { codexSupportsRewrite: codexSupportsUpdatedInput() } : {},
38
+ );
29
39
  const output = response ?? {
30
40
  hookSpecificOutput: { hookEventName: "PreToolUse" },
31
41
  };
@@ -3,7 +3,11 @@ import "./platform.mjs";
3
3
  import "../suppress-stderr.mjs";
4
4
  import "../ensure-deps.mjs";
5
5
  /**
6
- * Codex CLI Stop hook — record turn/session end state for continuity.
6
+ * Codex CLI Stop hook — record turn-end state for continuity.
7
+ *
8
+ * Stop fires at the end of an assistant turn, not at true session shutdown.
9
+ * Store a turn_end marker so session_end remains reserved for actual terminal
10
+ * lifecycle events on platforms that expose one.
7
11
  */
8
12
 
9
13
  import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, CODEX_OPTS } from "../session-helpers.mjs";
@@ -26,13 +30,17 @@ try {
26
30
  const sessionId = getSessionId(input, OPTS);
27
31
 
28
32
  db.ensureSession(sessionId, projectDir);
29
- db.insertEvent(sessionId, {
30
- type: "session_end",
31
- status: "completed",
33
+ const payload = {
32
34
  stop_hook_active: input.stop_hook_active ?? false,
33
35
  last_assistant_message: typeof input.last_assistant_message === "string"
34
36
  ? input.last_assistant_message.slice(0, 2000)
35
37
  : null,
38
+ };
39
+ db.insertEvent(sessionId, {
40
+ type: "turn_end",
41
+ category: "session",
42
+ data: JSON.stringify(payload),
43
+ priority: 1,
36
44
  }, "Stop");
37
45
 
38
46
  db.close();
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ import "../ensure-deps.mjs";
4
+
5
+ import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
6
+ import {
7
+ readStdin,
8
+ parseStdin,
9
+ getSessionId,
10
+ getSessionDBPath,
11
+ getInputProjectDir,
12
+ COPILOT_OPTS,
13
+ resolveConfigDir,
14
+ } from "../session-helpers.mjs";
15
+ import { appendFileSync, mkdirSync } from "node:fs";
16
+ import { join, dirname } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
20
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
21
+ const OPTS = COPILOT_OPTS;
22
+ // Diagnostic log is opt-in via CONTEXT_MODE_DEBUG. PostToolUse fires on every
23
+ // tool call, so an unconditional append-only log grows without bound under the
24
+ // user's config dir. Gate it behind the env flag — same pattern as the kimi
25
+ // hooks — so contributors can still capture it on demand. See #787 review.
26
+ const DEBUG_LOG = process.env.CONTEXT_MODE_DEBUG
27
+ ? join(resolveConfigDir(OPTS), "context-mode", "posttooluse-debug.log")
28
+ : null;
29
+
30
+ function logDebug(line) {
31
+ if (!DEBUG_LOG) return;
32
+ try {
33
+ mkdirSync(dirname(DEBUG_LOG), { recursive: true });
34
+ appendFileSync(DEBUG_LOG, line);
35
+ } catch {
36
+ /* silent */
37
+ }
38
+ }
39
+
40
+ try {
41
+ const raw = await readStdin();
42
+ const input = parseStdin(raw);
43
+ const projectDir = getInputProjectDir(input, OPTS);
44
+ const toolName = input.tool_name ?? input.toolName ?? "";
45
+ const toolInput = input.tool_input ?? input.toolArgs ?? {};
46
+ const toolResponse =
47
+ input.tool_result?.text_result_for_llm ??
48
+ input.toolResult?.textResultForLlm ??
49
+ input.tool_response ??
50
+ input.toolResult;
51
+
52
+ logDebug(`[${new Date().toISOString()}] CALL: ${toolName}\n`);
53
+
54
+ const { extractEvents } = await loadExtract();
55
+ const { resolveProjectAttributions } = await loadProjectAttribution();
56
+ const { SessionDB } = await loadSessionDB();
57
+
58
+ const dbPath = getSessionDBPath(OPTS, projectDir);
59
+ const db = new SessionDB({ dbPath });
60
+ const sessionId = getSessionId(input, OPTS);
61
+
62
+ db.ensureSession(sessionId, projectDir);
63
+
64
+ const events = extractEvents({
65
+ tool_name: toolName,
66
+ tool_input: toolInput,
67
+ tool_response: typeof toolResponse === "string"
68
+ ? toolResponse
69
+ : JSON.stringify(toolResponse ?? ""),
70
+ tool_output: input.tool_output,
71
+ });
72
+
73
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
74
+
75
+ logDebug(`[${new Date().toISOString()}] OK: ${toolName} -> ${events.length} events\n`);
76
+ db.close();
77
+ } catch (err) {
78
+ logDebug(`[${new Date().toISOString()}] ERR: ${err?.message || err}\n`);
79
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ import "../ensure-deps.mjs";
4
+
5
+ import { createSessionLoaders } from "../session-loaders.mjs";
6
+ import {
7
+ readStdin,
8
+ parseStdin,
9
+ getSessionId,
10
+ getSessionDBPath,
11
+ getInputProjectDir,
12
+ COPILOT_OPTS,
13
+ resolveConfigDir,
14
+ } from "../session-helpers.mjs";
15
+ import { appendFileSync, mkdirSync } from "node:fs";
16
+ import { join, dirname } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
20
+ const { loadSessionDB, loadSnapshot } = createSessionLoaders(HOOK_DIR);
21
+ const OPTS = COPILOT_OPTS;
22
+ // Diagnostic log is opt-in via CONTEXT_MODE_DEBUG (same pattern as the kimi
23
+ // hooks): keep the error telemetry available to contributors without writing an
24
+ // append-only file to every user's config dir on compaction. See #787 review.
25
+ const DEBUG_LOG = process.env.CONTEXT_MODE_DEBUG
26
+ ? join(resolveConfigDir(OPTS), "context-mode", "precompact-debug.log")
27
+ : null;
28
+
29
+ function logDebug(line) {
30
+ if (!DEBUG_LOG) return;
31
+ try {
32
+ mkdirSync(dirname(DEBUG_LOG), { recursive: true });
33
+ appendFileSync(DEBUG_LOG, line);
34
+ } catch {
35
+ /* silent */
36
+ }
37
+ }
38
+
39
+ try {
40
+ const raw = await readStdin();
41
+ const input = parseStdin(raw);
42
+ const projectDir = getInputProjectDir(input, OPTS);
43
+
44
+ const { buildResumeSnapshot } = await loadSnapshot();
45
+ const { SessionDB } = await loadSessionDB();
46
+
47
+ const dbPath = getSessionDBPath(OPTS, projectDir);
48
+ const db = new SessionDB({ dbPath });
49
+ const sessionId = getSessionId(input, OPTS);
50
+
51
+ const events = db.getEvents(sessionId);
52
+
53
+ if (events.length > 0) {
54
+ const stats = db.getSessionStats(sessionId);
55
+ const snapshot = buildResumeSnapshot(events, {
56
+ compactCount: (stats?.compact_count ?? 0) + 1,
57
+ });
58
+
59
+ db.upsertResume(sessionId, snapshot, events.length);
60
+ db.incrementCompactCount(sessionId);
61
+ }
62
+
63
+ db.close();
64
+ } catch (err) {
65
+ logDebug(`[${new Date().toISOString()}] ${err?.message || err}\n`);
66
+ }
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { readStdin } from "../core/stdin.mjs";
7
+ import { routePreToolUse, initSecurity } from "../core/routing.mjs";
8
+ import { formatDecision } from "../core/formatters.mjs";
9
+ import { parseStdin, getSessionId, getInputProjectDir, COPILOT_OPTS } from "../session-helpers.mjs";
10
+
11
+ const __hookDir = dirname(fileURLToPath(import.meta.url));
12
+
13
+ try {
14
+ await initSecurity(resolve(__hookDir, "..", "..", "build"));
15
+
16
+ const raw = await readStdin();
17
+ const input = parseStdin(raw);
18
+ const tool = input.tool_name ?? input.toolName ?? "";
19
+ const toolInput = input.tool_input ?? input.toolArgs ?? {};
20
+ const projectDir = getInputProjectDir(input, COPILOT_OPTS);
21
+
22
+ const decision = routePreToolUse(
23
+ tool,
24
+ toolInput,
25
+ projectDir,
26
+ "copilot-cli",
27
+ getSessionId(input, COPILOT_OPTS),
28
+ );
29
+ const response = formatDecision("copilot-cli", decision);
30
+ if (response !== null) {
31
+ process.stdout.write(JSON.stringify(response) + "\n");
32
+ }
33
+ } catch {
34
+ // Fail OPEN. A throw here (better-sqlite3 ABI skew, a routing exception, a
35
+ // malformed-stdin parse error) must NOT exit non-zero with empty stdout:
36
+ // GitHub Copilot CLI 1.0.59 treats a failed PreToolUse hook as "Denied by
37
+ // preToolUse hook (hook errored)" and blocks EVERY tool, bricking the agent.
38
+ // A legitimate veto is a normal stdout write + normal return (never a throw),
39
+ // so it is unaffected by this catch. Empty stdout + exit 0 => host ALLOWS the
40
+ // tool; context-mode routing is skipped for this one call, agent keeps working.
41
+ }