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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +149 -30
- package/bin/statusline.mjs +24 -4
- package/build/adapters/antigravity/index.d.ts +1 -1
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +342 -0
- package/build/adapters/claude-code/hooks.d.ts +1 -0
- package/build/adapters/claude-code/hooks.js +3 -0
- package/build/adapters/claude-code/index.js +24 -5
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +5 -1
- package/build/adapters/codex/hooks.js +5 -1
- package/build/adapters/codex/index.d.ts +9 -1
- package/build/adapters/codex/index.js +87 -5
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/detect.d.ts +1 -1
- package/build/adapters/detect.js +71 -3
- package/build/adapters/openclaw/mcp-tools.js +1 -1
- package/build/adapters/opencode/index.js +31 -17
- package/build/adapters/opencode/zod3tov4.js +27 -6
- package/build/adapters/pi/extension.d.ts +2 -12
- package/build/adapters/pi/extension.js +128 -109
- package/build/adapters/types.d.ts +5 -4
- package/build/adapters/types.js +4 -3
- package/build/cache-heal.d.ts +48 -0
- package/build/cache-heal.js +150 -0
- package/build/cli.js +37 -97
- package/build/executor.d.ts +25 -0
- package/build/executor.js +143 -22
- package/build/lifecycle.d.ts +48 -0
- package/build/lifecycle.js +111 -0
- package/build/opencode-plugin.js +5 -2
- package/build/routing-block.d.ts +8 -0
- package/build/routing-block.js +86 -0
- package/build/runtime.d.ts +0 -36
- package/build/runtime.js +107 -27
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/security.d.ts +73 -3
- package/build/security.js +293 -33
- package/build/server.d.ts +14 -0
- package/build/server.js +441 -354
- package/build/session/analytics.d.ts +1 -1
- package/build/session/analytics.js +5 -1
- package/build/session/db.js +23 -3
- package/build/session/extract.js +78 -0
- package/build/store.d.ts +1 -1
- package/build/store.js +139 -25
- package/build/tool-naming.d.ts +4 -0
- package/build/tool-naming.js +24 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/cli.bundle.mjs +253 -250
- package/configs/antigravity/GEMINI.md +2 -2
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +2 -2
- package/configs/codex/AGENTS.md +2 -2
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/gemini-cli/GEMINI.md +2 -2
- package/configs/jetbrains-copilot/copilot-instructions.md +2 -2
- package/configs/kilo/AGENTS.md +2 -2
- package/configs/kiro/KIRO.md +2 -2
- package/configs/omp/SYSTEM.md +2 -2
- package/configs/openclaw/AGENTS.md +2 -2
- package/configs/opencode/AGENTS.md +2 -2
- package/configs/qwen-code/QWEN.md +2 -2
- package/configs/vscode-copilot/copilot-instructions.md +2 -2
- package/configs/zed/AGENTS.md +2 -2
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/codex/pretooluse.mjs +14 -4
- package/hooks/codex/stop.mjs +12 -4
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +158 -7
- package/hooks/core/mcp-ready.mjs +37 -8
- package/hooks/core/routing.mjs +94 -8
- package/hooks/core/tool-naming.mjs +3 -0
- package/hooks/hooks.json +12 -1
- package/hooks/pretooluse.mjs +6 -2
- package/hooks/routing-block.mjs +3 -4
- package/hooks/security.bundle.mjs +2 -1
- package/hooks/session-db.bundle.mjs +5 -5
- package/hooks/session-directive.mjs +88 -20
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-helpers.mjs +21 -0
- package/hooks/sessionstart.mjs +37 -5
- package/hooks/stop.mjs +49 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -10
- package/server.bundle.mjs +206 -200
- package/skills/ctx-insight/SKILL.md +12 -17
- package/build/util/db-lock.d.ts +0 -65
- package/build/util/db-lock.js +0 -166
- package/insight/index.html +0 -13
- package/insight/package.json +0 -55
- package/insight/server.mjs +0 -1265
- package/insight/src/components/analytics.tsx +0 -112
- package/insight/src/components/ui/badge.tsx +0 -52
- package/insight/src/components/ui/button.tsx +0 -58
- package/insight/src/components/ui/card.tsx +0 -103
- package/insight/src/components/ui/chart.tsx +0 -371
- package/insight/src/components/ui/collapsible.tsx +0 -19
- package/insight/src/components/ui/input.tsx +0 -20
- package/insight/src/components/ui/progress.tsx +0 -83
- package/insight/src/components/ui/scroll-area.tsx +0 -55
- package/insight/src/components/ui/separator.tsx +0 -23
- package/insight/src/components/ui/table.tsx +0 -114
- package/insight/src/components/ui/tabs.tsx +0 -82
- package/insight/src/components/ui/tooltip.tsx +0 -64
- package/insight/src/lib/api.ts +0 -144
- package/insight/src/lib/utils.ts +0 -6
- package/insight/src/main.tsx +0 -22
- package/insight/src/routeTree.gen.ts +0 -189
- package/insight/src/router.tsx +0 -19
- package/insight/src/routes/__root.tsx +0 -55
- package/insight/src/routes/enterprise.tsx +0 -316
- package/insight/src/routes/index.tsx +0 -1482
- package/insight/src/routes/knowledge.tsx +0 -221
- package/insight/src/routes/knowledge_.$dbHash.$sourceId.tsx +0 -137
- package/insight/src/routes/search.tsx +0 -97
- package/insight/src/routes/sessions.tsx +0 -179
- package/insight/src/routes/sessions_.$dbHash.$sessionId.tsx +0 -181
- package/insight/src/styles.css +0 -104
- package/insight/tsconfig.json +0 -29
- 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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
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
|
};
|
package/hooks/codex/stop.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
+
}
|