context-mode 1.0.21 → 1.0.23

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 (59) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/.openclaw-plugin/index.ts +11 -0
  4. package/.openclaw-plugin/openclaw.plugin.json +23 -0
  5. package/.openclaw-plugin/package.json +28 -0
  6. package/README.md +165 -26
  7. package/build/adapters/antigravity/index.d.ts +49 -0
  8. package/build/adapters/antigravity/index.js +217 -0
  9. package/build/adapters/client-map.d.ts +10 -0
  10. package/build/adapters/client-map.js +18 -0
  11. package/build/adapters/detect.d.ts +8 -1
  12. package/build/adapters/detect.js +58 -1
  13. package/build/adapters/kiro/hooks.d.ts +32 -0
  14. package/build/adapters/kiro/hooks.js +47 -0
  15. package/build/adapters/kiro/index.d.ts +50 -0
  16. package/build/adapters/kiro/index.js +325 -0
  17. package/build/adapters/openclaw/config.d.ts +8 -0
  18. package/build/adapters/openclaw/config.js +8 -0
  19. package/build/adapters/openclaw/hooks.d.ts +50 -0
  20. package/build/adapters/openclaw/hooks.js +61 -0
  21. package/build/adapters/openclaw/index.d.ts +51 -0
  22. package/build/adapters/openclaw/index.js +459 -0
  23. package/build/adapters/openclaw/session-db.d.ts +55 -0
  24. package/build/adapters/openclaw/session-db.js +88 -0
  25. package/build/adapters/types.d.ts +1 -1
  26. package/build/cli.js +5 -3
  27. package/build/executor.js +99 -112
  28. package/build/openclaw/workspace-router.d.ts +29 -0
  29. package/build/openclaw/workspace-router.js +64 -0
  30. package/build/openclaw-plugin.d.ts +121 -0
  31. package/build/openclaw-plugin.js +525 -0
  32. package/build/server.js +45 -10
  33. package/build/session/db.d.ts +9 -0
  34. package/build/session/db.js +38 -0
  35. package/cli.bundle.mjs +136 -124
  36. package/configs/antigravity/GEMINI.md +58 -0
  37. package/configs/antigravity/mcp_config.json +7 -0
  38. package/configs/kiro/mcp_config.json +7 -0
  39. package/configs/openclaw/AGENTS.md +58 -0
  40. package/configs/openclaw/openclaw.json +13 -0
  41. package/hooks/core/routing.mjs +16 -8
  42. package/hooks/kiro/posttooluse.mjs +58 -0
  43. package/hooks/kiro/pretooluse.mjs +63 -0
  44. package/hooks/posttooluse.mjs +6 -5
  45. package/hooks/precompact.mjs +5 -4
  46. package/hooks/session-db.bundle.mjs +57 -0
  47. package/hooks/session-extract.bundle.mjs +1 -0
  48. package/hooks/session-helpers.mjs +41 -3
  49. package/hooks/session-loaders.mjs +28 -0
  50. package/hooks/session-snapshot.bundle.mjs +14 -0
  51. package/hooks/sessionstart.mjs +6 -5
  52. package/hooks/userpromptsubmit.mjs +6 -5
  53. package/hooks/vscode-copilot/posttooluse.mjs +5 -4
  54. package/hooks/vscode-copilot/precompact.mjs +5 -4
  55. package/hooks/vscode-copilot/sessionstart.mjs +5 -4
  56. package/openclaw.plugin.json +23 -0
  57. package/package.json +13 -2
  58. package/server.bundle.mjs +94 -82
  59. package/start.mjs +1 -0
@@ -0,0 +1,58 @@
1
+ # context-mode — MANDATORY routing rules
2
+
3
+ You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session. Antigravity does NOT have hooks, so these instructions are your ONLY enforcement mechanism. Follow them strictly.
4
+
5
+ ## BLOCKED commands — do NOT use these
6
+
7
+ ### curl / wget — FORBIDDEN
8
+ Do NOT use `curl` or `wget` via `run_command`. They dump raw HTTP responses directly into your context window.
9
+ Instead use:
10
+ - `mcp__context-mode__ctx_fetch_and_index(url, source)` to fetch and index web pages
11
+ - `mcp__context-mode__ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
12
+
13
+ ### Inline HTTP — FORBIDDEN
14
+ Do NOT run inline HTTP calls via `run_command` with `node -e "fetch(..."`, `python -c "requests.get(..."`, or similar patterns. They bypass the sandbox and flood context.
15
+ Instead use:
16
+ - `mcp__context-mode__ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
17
+
18
+ ### Direct web fetching — FORBIDDEN
19
+ Do NOT use `read_url_content` for large pages. Raw HTML can exceed 100 KB.
20
+ Instead use:
21
+ - `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` to query the indexed content
22
+
23
+ ## REDIRECTED tools — use sandbox equivalents
24
+
25
+ ### Shell (>20 lines output)
26
+ `run_command` is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
27
+ For everything else, use:
28
+ - `mcp__context-mode__ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
29
+ - `mcp__context-mode__ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
30
+
31
+ ### File reading (for analysis)
32
+ If you are reading a file to **edit** it → `view_file` / `replace_file_content` is correct (edit needs content in context).
33
+ If you are reading to **analyze, explore, or summarize** → use `mcp__context-mode__ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file stays in the sandbox.
34
+
35
+ ### Search (large results)
36
+ Search results can flood context. Use `mcp__context-mode__ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
37
+
38
+ ## Tool selection hierarchy
39
+
40
+ 1. **GATHER**: `mcp__context-mode__ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
41
+ 2. **FOLLOW-UP**: `mcp__context-mode__ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
42
+ 3. **PROCESSING**: `mcp__context-mode__ctx_execute(language, code)` | `mcp__context-mode__ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
43
+ 4. **WEB**: `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
44
+ 5. **INDEX**: `mcp__context-mode__ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
45
+
46
+ ## Output constraints
47
+
48
+ - Keep responses under 500 words.
49
+ - Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
50
+ - When indexing content, use descriptive source labels so others can `search(source: "label")` later.
51
+
52
+ ## ctx commands
53
+
54
+ | Command | Action |
55
+ |---------|--------|
56
+ | `ctx stats` | Call the `stats` MCP tool and display the full output verbatim |
57
+ | `ctx doctor` | Call the `doctor` MCP tool, run the returned shell command, display as checklist |
58
+ | `ctx upgrade` | Call the `upgrade` MCP tool, run the returned shell command, display as checklist |
@@ -0,0 +1,7 @@
1
+ {
2
+ "mcpServers": {
3
+ "context-mode": {
4
+ "command": "context-mode"
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "mcpServers": {
3
+ "context-mode": {
4
+ "command": "context-mode"
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,58 @@
1
+ # context-mode — MANDATORY routing rules
2
+
3
+ You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session.
4
+
5
+ ## BLOCKED commands — do NOT attempt these
6
+
7
+ ### curl / wget — BLOCKED
8
+ Any shell command containing `curl` or `wget` will be intercepted and blocked by the context-mode plugin. Do NOT retry.
9
+ Instead use:
10
+ - `mcp__context-mode__ctx_fetch_and_index(url, source)` to fetch and index web pages
11
+ - `mcp__context-mode__ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
12
+
13
+ ### Inline HTTP — BLOCKED
14
+ Any shell command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` will be intercepted and blocked. Do NOT retry with shell.
15
+ Instead use:
16
+ - `mcp__context-mode__ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
17
+
18
+ ### Direct web fetching — BLOCKED
19
+ Do NOT use any direct URL fetching tool. Use the sandbox equivalent.
20
+ Instead use:
21
+ - `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` to query the indexed content
22
+
23
+ ## REDIRECTED tools — use sandbox equivalents
24
+
25
+ ### Shell (>20 lines output)
26
+ Shell is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
27
+ For everything else, use:
28
+ - `mcp__context-mode__ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
29
+ - `mcp__context-mode__ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
30
+
31
+ ### File reading (for analysis)
32
+ If you are reading a file to **edit** it → reading is correct (edit needs content in context).
33
+ If you are reading to **analyze, explore, or summarize** → use `mcp__context-mode__ctx_execute_file(path, language, code)` instead. Only your printed summary enters context.
34
+
35
+ ### grep / search (large results)
36
+ Search results can flood context. Use `mcp__context-mode__ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
37
+
38
+ ## Tool selection hierarchy
39
+
40
+ 1. **GATHER**: `mcp__context-mode__ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
41
+ 2. **FOLLOW-UP**: `mcp__context-mode__ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
42
+ 3. **PROCESSING**: `mcp__context-mode__ctx_execute(language, code)` | `mcp__context-mode__ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
43
+ 4. **WEB**: `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
44
+ 5. **INDEX**: `mcp__context-mode__ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
45
+
46
+ ## Output constraints
47
+
48
+ - Keep responses under 500 words.
49
+ - Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
50
+ - When indexing content, use descriptive source labels so others can `search(source: "label")` later.
51
+
52
+ ## ctx commands
53
+
54
+ | Command | Action |
55
+ |---------|--------|
56
+ | `ctx stats` | Call the `stats` MCP tool and display the full output verbatim |
57
+ | `ctx doctor` | Call the `doctor` MCP tool, run the returned shell command, display as checklist |
58
+ | `ctx upgrade` | Call the `upgrade` MCP tool, run the returned shell command, display as checklist |
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://openclaw.ai/config.json",
3
+ "plugins": {
4
+ "entries": {
5
+ "context-mode": {
6
+ "enabled": true
7
+ }
8
+ },
9
+ "slots": {
10
+ "contextEngine": "context-mode"
11
+ }
12
+ }
13
+ }
@@ -22,7 +22,10 @@ import { resolve } from "node:path";
22
22
  // (Claude Code, Gemini, Cursor, VS Code Copilot)
23
23
  // Session scoped via process.ppid (= host PID, constant for session lifetime).
24
24
  const _guidanceShown = new Set();
25
- const _guidanceDir = resolve(tmpdir(), `context-mode-guidance-${process.ppid}`);
25
+ const _guidanceId = process.env.VITEST_WORKER_ID
26
+ ? `${process.ppid}-w${process.env.VITEST_WORKER_ID}`
27
+ : String(process.ppid);
28
+ const _guidanceDir = resolve(tmpdir(), `context-mode-guidance-${_guidanceId}`);
26
29
 
27
30
  function guidanceOnce(type, content) {
28
31
  // Fast path: in-memory (same process)
@@ -99,7 +102,6 @@ const TOOL_ALIASES = {
99
102
  "grep_search": "Grep",
100
103
  "search_file_content": "Grep",
101
104
  "web_fetch": "WebFetch",
102
- "activate_skill": "Agent",
103
105
  // OpenCode
104
106
  "bash": "Bash",
105
107
  "view": "Read",
@@ -119,6 +121,10 @@ const TOOL_ALIASES = {
119
121
  "Shell": "Bash",
120
122
  // VS Code Copilot
121
123
  "run_in_terminal": "Bash",
124
+ // Kiro CLI (https://kiro.dev/docs/cli/hooks/)
125
+ "fs_read": "Read",
126
+ "fs_write": "Write",
127
+ "execute_bash": "Bash",
122
128
  };
123
129
 
124
130
  /**
@@ -223,12 +229,14 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
223
229
  // ─── Agent/Task: inject context-mode routing into subagent prompts ───
224
230
  if (canonical === "Agent" || canonical === "Task") {
225
231
  const subagentType = toolInput.subagent_type ?? "";
226
- const prompt = toolInput.prompt ?? "";
232
+ // Detect the correct field name for the prompt/request/objective/question/query
233
+ const fieldName = ["prompt", "request", "objective", "question", "query", "task"].find(f => f in toolInput) ?? "prompt";
234
+ const prompt = toolInput[fieldName] ?? "";
227
235
 
228
236
  const updatedInput =
229
237
  subagentType === "Bash"
230
- ? { ...toolInput, prompt: prompt + ROUTING_BLOCK, subagent_type: "general-purpose" }
231
- : { ...toolInput, prompt: prompt + ROUTING_BLOCK };
238
+ ? { ...toolInput, [fieldName]: prompt + ROUTING_BLOCK, subagent_type: "general-purpose" }
239
+ : { ...toolInput, [fieldName]: prompt + ROUTING_BLOCK };
232
240
 
233
241
  return { action: "modify", updatedInput };
234
242
  }
@@ -237,7 +245,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
237
245
  // Match both __execute and __ctx_execute (prefixed tool names)
238
246
  // Cursor can also surface the tool as MCP:ctx_execute_file.
239
247
  if (
240
- (toolName.includes("context-mode") && /__(ctx_)?execute$/.test(toolName)) ||
248
+ (toolName.includes("context-mode") && /(?:__|\/)(ctx_)?execute$/.test(toolName)) ||
241
249
  /^MCP:(ctx_)?execute$/.test(toolName)
242
250
  ) {
243
251
  if (security && toolInput.language === "shell") {
@@ -259,7 +267,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
259
267
  // ─── MCP execute_file: check file path + code against deny patterns ───
260
268
  // Cursor can also surface the tool as MCP:ctx_execute_file.
261
269
  if (
262
- (toolName.includes("context-mode") && /__(ctx_)?execute_file$/.test(toolName)) ||
270
+ (toolName.includes("context-mode") && /(?:__|\/)(ctx_)?execute_file$/.test(toolName)) ||
263
271
  /^MCP:(ctx_)?execute_file$/.test(toolName)
264
272
  ) {
265
273
  if (security) {
@@ -291,7 +299,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
291
299
  }
292
300
 
293
301
  // ─── MCP batch_execute: check each command individually ───
294
- if (toolName.includes("context-mode") && /__(ctx_)?batch_execute$/.test(toolName)) {
302
+ if (toolName.includes("context-mode") && /(?:__|\/)(ctx_)?batch_execute$/.test(toolName)) {
295
303
  if (security) {
296
304
  const commands = toolInput.commands ?? [];
297
305
  const policies = security.readBashPolicies(projectDir);
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ /**
4
+ * Kiro CLI PostToolUse hook — session event capture.
5
+ * Must be fast (<20ms). No network, no LLM, just SQLite writes.
6
+ *
7
+ * Source: https://kiro.dev/docs/cli/hooks/
8
+ */
9
+
10
+ import { readStdin, getSessionId, getSessionDBPath, getInputProjectDir, KIRO_OPTS } from "../session-helpers.mjs";
11
+ import { appendFileSync } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
+
16
+ const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
17
+ const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
18
+ const OPTS = KIRO_OPTS;
19
+ const DEBUG_LOG = join(homedir(), ".kiro", "context-mode", "posttooluse-debug.log");
20
+
21
+ try {
22
+ const raw = await readStdin();
23
+ const input = JSON.parse(raw);
24
+
25
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
26
+
27
+ const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
28
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
29
+
30
+ const dbPath = getSessionDBPath(OPTS);
31
+ const db = new SessionDB({ dbPath });
32
+ const sessionId = getSessionId(input, OPTS);
33
+ const projectDir = getInputProjectDir(input, OPTS);
34
+
35
+ db.ensureSession(sessionId, projectDir);
36
+
37
+ const events = extractEvents({
38
+ tool_name: input.tool_name,
39
+ tool_input: input.tool_input ?? {},
40
+ tool_response: typeof input.tool_response === "string"
41
+ ? input.tool_response
42
+ : JSON.stringify(input.tool_response ?? ""),
43
+ tool_output: input.tool_output,
44
+ });
45
+
46
+ for (const event of events) {
47
+ db.insertEvent(sessionId, event, "PostToolUse");
48
+ }
49
+
50
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] OK: ${input.tool_name} → ${events.length} events\n`);
51
+ db.close();
52
+ } catch (err) {
53
+ try {
54
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ERR: ${err?.message || err}\n`);
55
+ } catch { /* silent */ }
56
+ }
57
+
58
+ // PostToolUse is non-blocking — no stdout output
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ /**
4
+ * Kiro CLI PreToolUse hook for context-mode.
5
+ * Uses exit codes instead of JSON stdout:
6
+ * - Exit 0: allow (stdout → agent context)
7
+ * - Exit 2: block (stderr → agent error)
8
+ *
9
+ * Source: https://kiro.dev/docs/cli/hooks/
10
+ */
11
+
12
+ import { dirname, resolve } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { readStdin } from "../core/stdin.mjs";
15
+ import { routePreToolUse, initSecurity } from "../core/routing.mjs";
16
+
17
+ const __hookDir = dirname(fileURLToPath(import.meta.url));
18
+ await initSecurity(resolve(__hookDir, "..", "..", "build"));
19
+
20
+ const raw = await readStdin();
21
+ const input = JSON.parse(raw);
22
+ // Kiro stdin: { hook_event_name, cwd, tool_name, tool_input }
23
+ const tool = input.tool_name ?? "";
24
+ const toolInput = input.tool_input ?? {};
25
+ const projectDir = input.cwd ?? process.cwd();
26
+
27
+ const decision = routePreToolUse(tool, toolInput, projectDir);
28
+
29
+ if (!decision) process.exit(0);
30
+
31
+ switch (decision.action) {
32
+ case "deny":
33
+ process.stderr.write(decision.reason ?? "Blocked by context-mode");
34
+ process.exit(2);
35
+ break;
36
+
37
+ case "modify":
38
+ // Kiro CLI cannot modify tool input — deny with redirect message
39
+ // The updatedInput.command contains an echo "..." wrapper — extract inner message
40
+ if (typeof decision.updatedInput?.command === "string") {
41
+ const msg = decision.updatedInput.command
42
+ .replace(/^echo\s+"?/, "")
43
+ .replace(/"?\s*$/, "");
44
+ process.stderr.write(msg);
45
+ } else {
46
+ process.stderr.write(decision.reason ?? "Blocked by context-mode routing");
47
+ }
48
+ process.exit(2);
49
+ break;
50
+
51
+ case "context":
52
+ process.stdout.write(decision.additionalContext ?? "");
53
+ process.exit(0);
54
+ break;
55
+
56
+ case "ask":
57
+ // Kiro CLI has no "ask" concept — passthrough
58
+ process.exit(0);
59
+ break;
60
+
61
+ default:
62
+ process.exit(0);
63
+ }
@@ -10,20 +10,21 @@ import "./suppress-stderr.mjs";
10
10
  */
11
11
 
12
12
  import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
13
- import { join, dirname } from "node:path";
14
- import { fileURLToPath, pathToFileURL } from "node:url";
13
+ import { createSessionLoaders } from "./session-loaders.mjs";
14
+ import { dirname } from "node:path";
15
+ import { fileURLToPath } from "node:url";
15
16
 
16
17
  // Resolve absolute path for imports — relative dynamic imports can fail
17
18
  // when Claude Code invokes hooks from a different working directory.
18
19
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
19
- const PKG_SESSION = join(HOOK_DIR, "..", "build", "session");
20
+ const { loadSessionDB, loadExtract } = createSessionLoaders(HOOK_DIR);
20
21
 
21
22
  try {
22
23
  const raw = await readStdin();
23
24
  const input = JSON.parse(raw);
24
25
 
25
- const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
26
- const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
26
+ const { extractEvents } = await loadExtract();
27
+ const { SessionDB } = await loadSessionDB();
27
28
 
28
29
  const dbPath = getSessionDBPath();
29
30
  const db = new SessionDB({ dbPath });
@@ -9,22 +9,23 @@ import "./suppress-stderr.mjs";
9
9
  */
10
10
 
11
11
  import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
12
+ import { createSessionLoaders } from "./session-loaders.mjs";
12
13
  import { appendFileSync } from "node:fs";
13
14
  import { join, dirname } from "node:path";
14
15
  import { homedir } from "node:os";
15
- import { fileURLToPath, pathToFileURL } from "node:url";
16
+ import { fileURLToPath } from "node:url";
16
17
 
17
18
  // Resolve absolute path for imports
18
19
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
19
- const PKG_SESSION = join(HOOK_DIR, "..", "build", "session");
20
+ const { loadSessionDB, loadSnapshot } = createSessionLoaders(HOOK_DIR);
20
21
  const DEBUG_LOG = join(homedir(), ".claude", "context-mode", "precompact-debug.log");
21
22
 
22
23
  try {
23
24
  const raw = await readStdin();
24
25
  const input = JSON.parse(raw);
25
26
 
26
- const { buildResumeSnapshot } = await import(pathToFileURL(join(PKG_SESSION, "snapshot.js")).href);
27
- const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
27
+ const { buildResumeSnapshot } = await loadSnapshot();
28
+ const { SessionDB } = await loadSessionDB();
28
29
 
29
30
  const dbPath = getSessionDBPath();
30
31
  const db = new SessionDB({ dbPath });
@@ -0,0 +1,57 @@
1
+ import{createRequire as p}from"node:module";import{unlinkSync as _}from"node:fs";import{tmpdir as T}from"node:os";import{join as g}from"node:path";var E=null;function h(){return E||(E=p(import.meta.url)("better-sqlite3")),E}function R(i){i.pragma("journal_mode = WAL"),i.pragma("synchronous = NORMAL")}function S(i){for(let t of["","-wal","-shm"])try{_(i+t)}catch{}}function d(i){try{i.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{i.close()}catch{}}function c(i="context-mode"){return g(T(),`${i}-${process.pid}.db`)}var a=class{#e;#t;constructor(t){let s=h();this.#e=t,this.#t=new s(t,{timeout:5e3}),R(this.#t),this.initSchema(),this.prepareStatements()}get db(){return this.#t}get dbPath(){return this.#e}close(){d(this.#t)}cleanup(){d(this.#t),S(this.#e)}};import{createHash as m}from"node:crypto";import{execFileSync as l}from"node:child_process";function U(){let i=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(i!==void 0)return i?`__${i}`:"";try{let t=process.cwd(),s=l("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(n=>n.startsWith("worktree "))?.replace("worktree ","")?.trim();if(s&&t!==s)return`__${m("sha256").update(t).digest("hex").slice(0,8)}`}catch{}return""}var y=1e3,v=5,e={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",incrementCompactCount:"incrementCompactCount",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions"},u=class extends a{constructor(t){super(t?.dbPath??c("session"))}stmt(t){return this.stmts.get(t)}initSchema(){try{let s=this.db.pragma("table_xinfo(session_events)").find(n=>n.name==="data_hash");s&&s.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
2
+ CREATE TABLE IF NOT EXISTS session_events (
3
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
4
+ session_id TEXT NOT NULL,
5
+ type TEXT NOT NULL,
6
+ category TEXT NOT NULL,
7
+ priority INTEGER NOT NULL DEFAULT 2,
8
+ data TEXT NOT NULL,
9
+ source_hook TEXT NOT NULL,
10
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
11
+ data_hash TEXT NOT NULL DEFAULT ''
12
+ );
13
+
14
+ CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id);
15
+ CREATE INDEX IF NOT EXISTS idx_session_events_type ON session_events(session_id, type);
16
+ CREATE INDEX IF NOT EXISTS idx_session_events_priority ON session_events(session_id, priority);
17
+
18
+ CREATE TABLE IF NOT EXISTS session_meta (
19
+ session_id TEXT PRIMARY KEY,
20
+ project_dir TEXT NOT NULL,
21
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
22
+ last_event_at TEXT,
23
+ event_count INTEGER NOT NULL DEFAULT 0,
24
+ compact_count INTEGER NOT NULL DEFAULT 0
25
+ );
26
+
27
+ CREATE TABLE IF NOT EXISTS session_resume (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ session_id TEXT NOT NULL UNIQUE,
30
+ snapshot TEXT NOT NULL,
31
+ event_count INTEGER NOT NULL,
32
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33
+ consumed INTEGER NOT NULL DEFAULT 0
34
+ );
35
+ `)}prepareStatements(){this.stmts=new Map;let t=(s,n)=>{this.stmts.set(s,this.db.prepare(n))};t(e.insertEvent,`INSERT INTO session_events (session_id, type, category, priority, data, source_hook, data_hash)
36
+ VALUES (?, ?, ?, ?, ?, ?, ?)`),t(e.getEvents,`SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
37
+ FROM session_events WHERE session_id = ? ORDER BY id ASC LIMIT ?`),t(e.getEventsByType,`SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
38
+ FROM session_events WHERE session_id = ? AND type = ? ORDER BY id ASC LIMIT ?`),t(e.getEventsByPriority,`SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
39
+ FROM session_events WHERE session_id = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(e.getEventsByTypeAndPriority,`SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
40
+ FROM session_events WHERE session_id = ? AND type = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(e.getEventCount,"SELECT COUNT(*) AS cnt FROM session_events WHERE session_id = ?"),t(e.checkDuplicate,`SELECT 1 FROM (
41
+ SELECT type, data_hash FROM session_events
42
+ WHERE session_id = ? ORDER BY id DESC LIMIT ?
43
+ ) AS recent
44
+ WHERE recent.type = ? AND recent.data_hash = ?
45
+ LIMIT 1`),t(e.evictLowestPriority,`DELETE FROM session_events WHERE id = (
46
+ SELECT id FROM session_events WHERE session_id = ?
47
+ ORDER BY priority ASC, id ASC LIMIT 1
48
+ )`),t(e.updateMetaLastEvent,`UPDATE session_meta
49
+ SET last_event_at = datetime('now'), event_count = event_count + 1
50
+ WHERE session_id = ?`),t(e.ensureSession,"INSERT OR IGNORE INTO session_meta (session_id, project_dir) VALUES (?, ?)"),t(e.getSessionStats,`SELECT session_id, project_dir, started_at, last_event_at, event_count, compact_count
51
+ FROM session_meta WHERE session_id = ?`),t(e.incrementCompactCount,"UPDATE session_meta SET compact_count = compact_count + 1 WHERE session_id = ?"),t(e.upsertResume,`INSERT INTO session_resume (session_id, snapshot, event_count)
52
+ VALUES (?, ?, ?)
53
+ ON CONFLICT(session_id) DO UPDATE SET
54
+ snapshot = excluded.snapshot,
55
+ event_count = excluded.event_count,
56
+ created_at = datetime('now'),
57
+ consumed = 0`),t(e.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(e.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(e.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(e.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(e.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(e.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,s,n="PostToolUse"){let r=m("sha256").update(s.data).digest("hex").slice(0,16).toUpperCase();this.db.transaction(()=>{if(this.stmt(e.checkDuplicate).get(t,v,s.type,r))return;this.stmt(e.getEventCount).get(t).cnt>=y&&this.stmt(e.evictLowestPriority).run(t),this.stmt(e.insertEvent).run(t,s.type,s.category,s.priority,s.data,n,r),this.stmt(e.updateMetaLastEvent).run(t)})()}getEvents(t,s){let n=s?.limit??1e3,r=s?.type,o=s?.minPriority;return r&&o!==void 0?this.stmt(e.getEventsByTypeAndPriority).all(t,r,o,n):r?this.stmt(e.getEventsByType).all(t,r,n):o!==void 0?this.stmt(e.getEventsByPriority).all(t,o,n):this.stmt(e.getEvents).all(t,n)}getEventCount(t){return this.stmt(e.getEventCount).get(t).cnt}ensureSession(t,s){this.stmt(e.ensureSession).run(t,s)}getSessionStats(t){return this.stmt(e.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(e.incrementCompactCount).run(t)}upsertResume(t,s,n){this.stmt(e.upsertResume).run(t,s,n??0)}getResume(t){return this.stmt(e.getResume).get(t)??null}markResumeConsumed(t){this.stmt(e.markResumeConsumed).run(t)}deleteSession(t){this.db.transaction(()=>{this.stmt(e.deleteEvents).run(t),this.stmt(e.deleteResume).run(t),this.stmt(e.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let s=`-${t}`,n=this.stmt(e.getOldSessions).all(s);for(let{session_id:r}of n)this.deleteSession(r);return n.length}};export{u as SessionDB,U as getWorktreeSuffix};
@@ -0,0 +1 @@
1
+ function o(t,e=300){return t==null?"":t.length<=e?t:t.slice(0,e)}function c(t,e=300){if(t==null)return"";let n=typeof t=="string"?t:JSON.stringify(t);return o(n,e)}function l(t){let{tool_name:e,tool_input:n,tool_response:s}=t,r=[];if(e==="Read"){let i=String(n.file_path??"");return/CLAUDE\.md$|\.claude[\\/]/i.test(i)&&(r.push({type:"rule",category:"rule",data:o(i),priority:1}),s&&s.length>0&&r.push({type:"rule_content",category:"rule",data:o(s,5e3),priority:1})),r.push({type:"file_read",category:"file",data:o(i),priority:1}),r}if(e==="Edit"){let i=String(n.file_path??"");return r.push({type:"file_edit",category:"file",data:o(i),priority:1}),r}if(e==="NotebookEdit"){let i=String(n.notebook_path??"");return r.push({type:"file_edit",category:"file",data:o(i),priority:1}),r}if(e==="Write"){let i=String(n.file_path??"");return r.push({type:"file_write",category:"file",data:o(i),priority:1}),r}if(e==="Glob"){let i=String(n.pattern??"");return r.push({type:"file_glob",category:"file",data:o(i),priority:3}),r}if(e==="Grep"){let i=String(n.pattern??""),a=String(n.path??"");return r.push({type:"file_search",category:"file",data:o(`${i} in ${a}`),priority:3}),r}return r}function u(t){if(t.tool_name!=="Bash")return[];let n=String(t.tool_input.command??"").match(/\bcd\s+("([^"]+)"|'([^']+)'|(\S+))/);if(!n)return[];let s=n[2]??n[3]??n[4]??"";return[{type:"cwd",category:"cwd",data:o(s),priority:2}]}function d(t){let{tool_name:e,tool_input:n,tool_response:s,tool_output:r}=t,i=String(s??""),a=r?.isError===!0;return!(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(i))&&!a?[]:[{type:"error_tool",category:"error",data:o(i,300),priority:2}]}var g=[{pattern:/\bgit\s+checkout\b/,operation:"branch"},{pattern:/\bgit\s+commit\b/,operation:"commit"},{pattern:/\bgit\s+merge\s+\S+/,operation:"merge"},{pattern:/\bgit\s+rebase\b/,operation:"rebase"},{pattern:/\bgit\s+stash\b/,operation:"stash"},{pattern:/\bgit\s+push\b/,operation:"push"},{pattern:/\bgit\s+pull\b/,operation:"pull"},{pattern:/\bgit\s+log\b/,operation:"log"},{pattern:/\bgit\s+diff\b/,operation:"diff"},{pattern:/\bgit\s+status\b/,operation:"status"},{pattern:/\bgit\s+branch\b/,operation:"branch"},{pattern:/\bgit\s+reset\b/,operation:"reset"},{pattern:/\bgit\s+add\b/,operation:"add"},{pattern:/\bgit\s+cherry-pick\b/,operation:"cherry-pick"},{pattern:/\bgit\s+tag\b/,operation:"tag"},{pattern:/\bgit\s+fetch\b/,operation:"fetch"},{pattern:/\bgit\s+clone\b/,operation:"clone"},{pattern:/\bgit\s+worktree\b/,operation:"worktree"}];function b(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??""),n=g.find(s=>s.pattern.test(e));return n?[{type:"git",category:"git",data:o(n.operation),priority:2}]:[]}function y(t){return new Set(["TodoWrite","TaskCreate","TaskUpdate"]).has(t.tool_name)?[{type:t.tool_name==="TaskUpdate"?"task_update":t.tool_name==="TaskCreate"?"task_create":"task",category:"task",data:o(JSON.stringify(t.tool_input),300),priority:1}]:[]}function f(t){if(t.tool_name==="EnterPlanMode")return[{type:"plan_enter",category:"plan",data:"entered plan mode",priority:2}];if(t.tool_name==="ExitPlanMode"){let e=[],n=t.tool_input.allowedPrompts,s=Array.isArray(n)&&n.length>0?`exited plan mode (allowed: ${c(n.map(i=>typeof i=="object"&&i!==null&&"prompt"in i?String(i.prompt):String(i)).join(", "),200)})`:"exited plan mode";e.push({type:"plan_exit",category:"plan",data:o(s),priority:2});let r=String(t.tool_response??"").toLowerCase();return r.includes("approved")||r.includes("approve")?e.push({type:"plan_approved",category:"plan",data:"plan approved by user",priority:1}):(r.includes("rejected")||r.includes("decline")||r.includes("denied"))&&e.push({type:"plan_rejected",category:"plan",data:o(`plan rejected: ${t.tool_response??""}`,300),priority:2}),e}if(t.tool_name==="Write"||t.tool_name==="Edit"){let e=String(t.tool_input.file_path??"");if(/[/\\]\.claude[/\\]plans[/\\]/.test(e))return[{type:"plan_file_write",category:"plan",data:o(`plan file: ${e.split(/[/\\]/).pop()??e}`),priority:2}]}return[]}var h=[/\bsource\s+\S*activate\b/,/\bexport\s+\w+=/,/\bnvm\s+use\b/,/\bpyenv\s+(shell|local|global)\b/,/\bconda\s+activate\b/,/\brbenv\s+(shell|local|global)\b/,/\bnpm\s+install\b/,/\bnpm\s+ci\b/,/\bpip\s+install\b/,/\bbun\s+install\b/,/\byarn\s+(add|install)\b/,/\bpnpm\s+(add|install)\b/,/\bcargo\s+(install|add)\b/,/\bgo\s+(install|get)\b/,/\brustup\b/,/\basdf\b/,/\bvolta\b/,/\bdeno\s+install\b/];function _(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??"");if(!h.some(r=>r.test(e)))return[];let s=e.replace(/\bexport\s+(\w+)=\S*/g,"export $1=***");return[{type:"env",category:"env",data:o(s),priority:2}]}function m(t){if(t.tool_name!=="Skill")return[];let e=String(t.tool_input.skill??"");return[{type:"skill",category:"skill",data:o(e),priority:3}]}function S(t){if(t.tool_name!=="Agent")return[];let e=o(String(t.tool_input.prompt??t.tool_input.description??""),200),n=t.tool_response?o(String(t.tool_response),300):"",s=n.length>0;return[{type:s?"subagent_completed":"subagent_launched",category:"subagent",data:o(s?`[completed] ${e} \u2192 ${n}`:`[launched] ${e}`,300),priority:s?2:3}]}function k(t){let{tool_name:e,tool_input:n}=t;if(!e.startsWith("mcp__"))return[];let s=e.split("__"),r=s[s.length-1]||e,i=Object.values(n).find(p=>typeof p=="string"),a=i?`: ${o(String(i),100)}`:"";return[{type:"mcp",category:"mcp",data:o(`${r}${a}`),priority:3}]}function E(t){if(t.tool_name!=="AskUserQuestion")return[];let e=t.tool_input.questions,n=Array.isArray(e)&&e.length>0?String(e[0].question??""):"",s=o(String(t.tool_response??""),150),r=n?`Q: ${o(n,120)} \u2192 A: ${s}`:`answer: ${s}`;return[{type:"decision_question",category:"decision",data:o(r),priority:2}]}function v(t){if(t.tool_name!=="EnterWorktree")return[];let e=String(t.tool_input.name??"unnamed");return[{type:"worktree",category:"env",data:o(`entered worktree: ${e}`),priority:2}]}var x=[/\b(don'?t|do not|never|always|instead|rather|prefer)\b/i,/\b(use|switch to|go with|pick|choose)\s+\w+\s+(instead|over|not)\b/i,/\b(no,?\s+(use|do|try|make))\b/i,/\b(hayır|hayir|evet|böyle|boyle|degil|değil|yerine|kullan)\b/i];function w(t){return x.some(n=>n.test(t))?[{type:"decision",category:"decision",data:o(t,300),priority:2}]:[]}var T=[/\b(act as|you are|behave like|pretend|role of|persona)\b/i,/\b(senior|staff|principal|lead)\s+(engineer|developer|architect)\b/i,/\b(gibi davran|rolünde|olarak çalış)\b/i];function R(t){return T.some(n=>n.test(t))?[{type:"role",category:"role",data:o(t,300),priority:3}]:[]}var A=[{mode:"investigate",pattern:/\b(why|how does|explain|understand|what is|analyze|debug|look into)\b/i},{mode:"implement",pattern:/\b(create|add|build|implement|write|make|develop|fix)\b/i},{mode:"discuss",pattern:/\b(think about|consider|should we|what if|pros and cons|opinion)\b/i},{mode:"review",pattern:/\b(review|check|audit|verify|test|validate)\b/i}];function I(t){let e=A.find(({pattern:n})=>n.test(t));return e?[{type:"intent",category:"intent",data:o(e.mode),priority:4}]:[]}function $(t){return t.length<=1024?[]:[{type:"data",category:"data",data:o(t,200),priority:4}]}function P(t){try{let e=[];return e.push(...l(t)),e.push(...u(t)),e.push(...d(t)),e.push(...b(t)),e.push(..._(t)),e.push(...y(t)),e.push(...f(t)),e.push(...m(t)),e.push(...S(t)),e.push(...k(t)),e.push(...E(t)),e.push(...v(t)),e}catch{return[]}}function H(t){try{let e=[];return e.push(...w(t)),e.push(...R(t)),e.push(...I(t)),e.push(...$(t)),e}catch{return[]}}export{P as extractEvents,H as extractUserEvents};
@@ -7,11 +7,42 @@
7
7
  * configuration. Defaults to Claude Code settings for backward compatibility.
8
8
  */
9
9
 
10
+ import { execFileSync } from "node:child_process";
10
11
  import { createHash } from "node:crypto";
11
12
  import { join } from "node:path";
12
13
  import { mkdirSync } from "node:fs";
13
14
  import { homedir } from "node:os";
14
15
 
16
+ /**
17
+ * Returns the worktree suffix for session path isolation.
18
+ * Mirrors the logic in src/server.ts — kept in sync manually since
19
+ * hooks run as plain .mjs (no TypeScript build step).
20
+ */
21
+ function getWorktreeSuffix() {
22
+ const envSuffix = process.env.CONTEXT_MODE_SESSION_SUFFIX;
23
+ if (envSuffix !== undefined) {
24
+ return envSuffix ? `__${envSuffix}` : "";
25
+ }
26
+ try {
27
+ const cwd = process.cwd();
28
+ const mainWorktree = execFileSync(
29
+ "git",
30
+ ["worktree", "list", "--porcelain"],
31
+ { encoding: "utf-8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] },
32
+ )
33
+ .split(/\r?\n/)
34
+ .find((l) => l.startsWith("worktree "))
35
+ ?.replace("worktree ", "")
36
+ ?.trim();
37
+ if (mainWorktree && cwd !== mainWorktree) {
38
+ return `__${createHash("sha256").update(cwd).digest("hex").slice(0, 8)}`;
39
+ }
40
+ } catch {
41
+ // git not available or not a git repo — no suffix
42
+ }
43
+ return "";
44
+ }
45
+
15
46
  /** Claude Code platform options (default). */
16
47
  const CLAUDE_OPTS = {
17
48
  configDir: ".claude",
@@ -40,6 +71,13 @@ export const CURSOR_OPTS = {
40
71
  sessionIdEnv: "CURSOR_SESSION_ID",
41
72
  };
42
73
 
74
+ /** Kiro CLI platform options. */
75
+ export const KIRO_OPTS = {
76
+ configDir: ".kiro",
77
+ projectDirEnv: undefined, // Kiro CLI provides cwd in hook stdin, no env var
78
+ sessionIdEnv: undefined, // No session ID env var — uses ppid fallback
79
+ };
80
+
43
81
  /**
44
82
  * Read all of stdin as a string (event-based, cross-platform safe).
45
83
  */
@@ -104,7 +142,7 @@ export function getSessionDBPath(opts = CLAUDE_OPTS) {
104
142
  const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
105
143
  const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
106
144
  mkdirSync(dir, { recursive: true });
107
- return join(dir, `${hash}.db`);
145
+ return join(dir, `${hash}${getWorktreeSuffix()}.db`);
108
146
  }
109
147
 
110
148
  /**
@@ -117,7 +155,7 @@ export function getSessionEventsPath(opts = CLAUDE_OPTS) {
117
155
  const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
118
156
  const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
119
157
  mkdirSync(dir, { recursive: true });
120
- return join(dir, `${hash}-events.md`);
158
+ return join(dir, `${hash}${getWorktreeSuffix()}-events.md`);
121
159
  }
122
160
 
123
161
  /**
@@ -130,5 +168,5 @@ export function getCleanupFlagPath(opts = CLAUDE_OPTS) {
130
168
  const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
131
169
  const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
132
170
  mkdirSync(dir, { recursive: true });
133
- return join(dir, `${hash}.cleanup`);
171
+ return join(dir, `${hash}${getWorktreeSuffix()}.cleanup`);
134
172
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Session module loaders — bundle-only.
3
+ *
4
+ * All session modules are loaded from esbuild bundles (hooks/session-*.bundle.mjs).
5
+ * Bundles are built by CI (bundle.yml) and shipped with every release.
6
+ * No fallback to build/ — if the bundle is missing, the error surfaces immediately.
7
+ */
8
+
9
+ import { join } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+
12
+ export function createSessionLoaders(hookDir) {
13
+ const bundleDir = hookDir.endsWith("vscode-copilot")
14
+ ? join(hookDir, "..")
15
+ : hookDir;
16
+
17
+ return {
18
+ async loadSessionDB() {
19
+ return await import(pathToFileURL(join(bundleDir, "session-db.bundle.mjs")).href);
20
+ },
21
+ async loadExtract() {
22
+ return await import(pathToFileURL(join(bundleDir, "session-extract.bundle.mjs")).href);
23
+ },
24
+ async loadSnapshot() {
25
+ return await import(pathToFileURL(join(bundleDir, "session-snapshot.bundle.mjs")).href);
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,14 @@
1
+ function p(t,e){return t.length<=e?t:t.slice(0,Math.max(0,e-3))+"..."}function a(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}var G=2048,J=10;function q(t){if(t.length===0)return"";let e=new Map;for(let u of t){let l=u.data,i=e.get(l);i||(i={ops:new Map,last:""},e.set(l,i));let r;u.type==="file_write"?r="write":u.type==="file_read"?r="read":u.type==="file_edit"?r="edit":r=u.type,i.ops.set(r,(i.ops.get(r)??0)+1),i.last=r}let s=Array.from(e.entries()).slice(-J),c=[" <active_files>"];for(let[u,{ops:l,last:i}]of s){let r=Array.from(l.entries()).map(([f,d])=>`${f}:${d}`).join(",");c.push(` <file path="${a(u)}" ops="${a(r)}" last="${a(i)}" />`)}return c.push(" </active_files>"),c.join(`
2
+ `)}function z(t){if(t.length===0)return"";let e=[],n={};for(let i of t)try{let r=JSON.parse(i.data);typeof r.subject=="string"?e.push(r.subject):typeof r.taskId=="string"&&typeof r.status=="string"&&(n[r.taskId]=r.status)}catch{}if(e.length===0)return"";let s=new Set(["completed","deleted","failed"]),c=Object.keys(n).sort((i,r)=>Number(i)-Number(r)),u=[];for(let i=0;i<e.length;i++){let r=c[i],f=r?n[r]??"pending":"pending";s.has(f)||u.push(e[i])}if(u.length===0)return"";let l=[" <task_state>"];for(let i of u)l.push(` - ${a(p(i,100))}`);return l.push(" </task_state>"),l.join(`
3
+ `)}function K(t){if(t.length===0)return"";let e=new Set,n=[" <rules>"];for(let s of t){let c=s.data;e.has(c)||(e.add(c),s.type==="rule_content"?n.push(` <rule_content>${a(p(s.data,400))}</rule_content>`):n.push(` - ${a(p(s.data,200))}`))}return n.push(" </rules>"),n.join(`
4
+ `)}function U(t){if(t.length===0)return"";let e=new Set,n=[" <decisions>"];for(let s of t){let c=s.data;e.has(c)||(e.add(c),n.push(` - ${a(p(s.data,200))}`))}return n.push(" </decisions>"),n.join(`
5
+ `)}function V(t,e,n){let s=[];if(!t&&e.length===0&&!n)return"";s.push(" <environment>"),t&&s.push(` <cwd>${a(t.data)}</cwd>`),n&&s.push(` <git op="${a(n.data)}" />`);for(let c of e)s.push(` <env>${a(p(c.data,150))}</env>`);return s.push(" </environment>"),s.join(`
6
+ `)}function Y(t){if(t.length===0)return"";let e=[" <errors_encountered>"];for(let n of t)e.push(` - ${a(p(n.data,150))}`);return e.push(" </errors_encountered>"),e.join(`
7
+ `)}function H(t){return` <intent mode="${a(t.data)}">${a(p(t.data,100))}</intent>`}function N(t){if(t.length===0)return"";let e=[" <subagents>"];for(let n of t){let s=n.type==="subagent_completed"?"completed":n.type==="subagent_launched"?"launched":"unknown";e.push(` <agent status="${s}">${a(p(n.data,200))}</agent>`)}return e.push(" </subagents>"),e.join(`
8
+ `)}function Q(t){if(t.length===0)return"";let e=new Map;for(let s of t){let c=s.data.split(":")[0].trim();e.set(c,(e.get(c)??0)+1)}let n=[" <mcp_tools>"];for(let[s,c]of e)n.push(` <tool name="${a(s)}" calls="${c}" />`);return n.push(" </mcp_tools>"),n.join(`
9
+ `)}function et(t,e){let n=e?.maxBytes??G,s=e?.compactCount??1,c=new Date().toISOString(),u=[],l=[],i=[],r=[],f=[],d=[],_=[],h=[],k=[],b=[],B=[],S=[];for(let o of t)switch(o.category){case"file":u.push(o);break;case"task":l.push(o);break;case"rule":i.push(o);break;case"decision":r.push(o);break;case"cwd":f.push(o);break;case"error":d.push(o);break;case"env":_.push(o);break;case"git":h.push(o);break;case"subagent":k.push(o);break;case"intent":b.push(o);break;case"mcp":B.push(o);break;case"plan":S.push(o);break}let m=[],w=q(u);w&&m.push(w);let x=z(l);x&&m.push(x);let L=K(i);L&&m.push(L);let g=[],j=U(r);j&&g.push(j);let P=f.length>0?f[f.length-1]:void 0,R=h.length>0?h[h.length-1]:void 0,M=V(P,_,R);M&&g.push(M);let I=Y(d);I&&g.push(I);let D=k.filter(o=>o.type==="subagent_completed"),T=N(D);T&&g.push(T),S.length>0&&S[S.length-1].type==="plan_enter"&&g.push(' <plan_mode status="active" />');let v=[];if(b.length>0){let o=b[b.length-1];v.push(H(o))}let O=Q(B);O&&v.push(O);let X=k.filter(o=>o.type==="subagent_launched"),A=N(X);A&&v.push(A);let E=`<session_resume compact_count="${s}" events_captured="${t.length}" generated_at="${c}">`,$="</session_resume>",C=[m,g,v];for(let o=C.length;o>=0;o--){let F=C.slice(0,o).flat().join(`
10
+ `),y;if(F?y=`${E}
11
+ ${F}
12
+ ${$}`:y=`${E}
13
+ ${$}`,Buffer.byteLength(y)<=n)return y}return`${E}
14
+ ${$}`}export{et as buildResumeSnapshot,q as renderActiveFiles,U as renderDecisions,V as renderEnvironment,Y as renderErrors,H as renderIntent,Q as renderMcpTools,K as renderRules,N as renderSubagents,z as renderTaskState};