context-mode 1.0.103 → 1.0.105

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 (98) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +39 -7
  6. package/bin/statusline.mjs +321 -0
  7. package/build/adapters/antigravity/index.d.ts +6 -0
  8. package/build/adapters/antigravity/index.js +10 -0
  9. package/build/adapters/base.d.ts +23 -0
  10. package/build/adapters/base.js +29 -0
  11. package/build/adapters/codex/index.d.ts +10 -0
  12. package/build/adapters/codex/index.js +22 -4
  13. package/build/adapters/cursor/index.d.ts +7 -0
  14. package/build/adapters/cursor/index.js +11 -0
  15. package/build/adapters/detect.d.ts +12 -1
  16. package/build/adapters/detect.js +69 -7
  17. package/build/adapters/gemini-cli/index.d.ts +8 -1
  18. package/build/adapters/gemini-cli/index.js +19 -7
  19. package/build/adapters/jetbrains-copilot/index.d.ts +7 -0
  20. package/build/adapters/jetbrains-copilot/index.js +12 -0
  21. package/build/adapters/kiro/index.d.ts +8 -0
  22. package/build/adapters/kiro/index.js +12 -0
  23. package/build/adapters/openclaw/index.d.ts +17 -0
  24. package/build/adapters/openclaw/index.js +29 -4
  25. package/build/adapters/opencode/index.d.ts +8 -0
  26. package/build/adapters/opencode/index.js +18 -6
  27. package/build/adapters/qwen-code/index.d.ts +1 -0
  28. package/build/adapters/qwen-code/index.js +3 -0
  29. package/build/adapters/types.d.ts +33 -0
  30. package/build/adapters/vscode-copilot/index.d.ts +6 -0
  31. package/build/adapters/vscode-copilot/index.js +10 -0
  32. package/build/adapters/zed/index.d.ts +1 -0
  33. package/build/adapters/zed/index.js +3 -0
  34. package/build/cli.d.ts +15 -0
  35. package/build/cli.js +62 -16
  36. package/build/concurrency/runPool.d.ts +36 -0
  37. package/build/concurrency/runPool.js +51 -0
  38. package/build/executor.d.ts +11 -1
  39. package/build/executor.js +77 -21
  40. package/build/fetch-cache.d.ts +13 -0
  41. package/build/fetch-cache.js +15 -0
  42. package/build/lifecycle.d.ts +6 -2
  43. package/build/lifecycle.js +29 -2
  44. package/build/opencode-plugin.d.ts +23 -0
  45. package/build/opencode-plugin.js +80 -6
  46. package/build/routing-block.d.ts +8 -0
  47. package/build/routing-block.js +86 -0
  48. package/build/runtime.d.ts +1 -0
  49. package/build/runtime.js +54 -3
  50. package/build/search/auto-memory.d.ts +23 -10
  51. package/build/search/auto-memory.js +64 -26
  52. package/build/search/unified.d.ts +3 -0
  53. package/build/search/unified.js +2 -2
  54. package/build/server.d.ts +47 -0
  55. package/build/server.js +736 -188
  56. package/build/session/analytics.d.ts +49 -1
  57. package/build/session/analytics.js +278 -16
  58. package/build/session/db.d.ts +53 -8
  59. package/build/session/db.js +200 -19
  60. package/build/session/extract.js +124 -2
  61. package/build/tool-naming.d.ts +4 -0
  62. package/build/tool-naming.js +24 -0
  63. package/cli.bundle.mjs +208 -158
  64. package/configs/antigravity/GEMINI.md +11 -0
  65. package/configs/claude-code/CLAUDE.md +11 -0
  66. package/configs/codex/AGENTS.md +11 -0
  67. package/configs/cursor/context-mode.mdc +11 -0
  68. package/configs/gemini-cli/GEMINI.md +11 -0
  69. package/configs/jetbrains-copilot/copilot-instructions.md +3 -0
  70. package/configs/kilo/AGENTS.md +11 -0
  71. package/configs/kiro/KIRO.md +11 -0
  72. package/configs/openclaw/AGENTS.md +11 -0
  73. package/configs/opencode/AGENTS.md +11 -0
  74. package/configs/pi/AGENTS.md +11 -0
  75. package/configs/qwen-code/QWEN.md +11 -0
  76. package/configs/vscode-copilot/copilot-instructions.md +3 -0
  77. package/configs/zed/AGENTS.md +11 -0
  78. package/hooks/auto-injection.mjs +36 -10
  79. package/hooks/cache-heal-utils.mjs +231 -0
  80. package/hooks/codex/sessionstart.mjs +7 -4
  81. package/hooks/core/routing.mjs +8 -2
  82. package/hooks/cursor/sessionstart.mjs +7 -4
  83. package/hooks/formatters/claude-code.mjs +20 -0
  84. package/hooks/gemini-cli/sessionstart.mjs +7 -2
  85. package/hooks/jetbrains-copilot/sessionstart.mjs +7 -2
  86. package/hooks/normalize-hooks.mjs +184 -0
  87. package/hooks/session-db.bundle.mjs +41 -14
  88. package/hooks/session-extract.bundle.mjs +2 -2
  89. package/hooks/session-helpers.mjs +68 -20
  90. package/hooks/session-loaders.mjs +8 -2
  91. package/hooks/sessionstart.mjs +8 -2
  92. package/hooks/vscode-copilot/sessionstart.mjs +7 -2
  93. package/openclaw.plugin.json +1 -1
  94. package/package.json +2 -1
  95. package/server.bundle.mjs +181 -134
  96. package/skills/ctx-doctor/SKILL.md +3 -3
  97. package/skills/ctx-insight/SKILL.md +1 -1
  98. package/start.mjs +63 -3
@@ -40,6 +40,17 @@ Use `mcp__context-mode__ctx_execute(language: "shell", code: "grep ...")` in san
40
40
  4. **WEB**: `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `mcp__context-mode__ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `mcp__context-mode__ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `mcp__context-mode__ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Output
44
55
 
45
56
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,17 @@ Use `ctx_execute(language: "shell", code: "grep ...")` in sandbox.
40
40
  4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Subagent routing
44
55
 
45
56
  Routing block auto-injected into subagent prompts. Bash-type subagents upgraded to general-purpose. No manual instruction needed.
@@ -41,6 +41,17 @@ Use `ctx_execute(language: "shell", code: "grep ...")` in sandbox.
41
41
  4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — raw HTML never enters context.
42
42
  5. **INDEX**: `ctx_index(content, source)` — store in FTS5 for later search.
43
43
 
44
+ ## Parallel I/O batches
45
+
46
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
47
+
48
+ - `ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
49
+ - `ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
50
+
51
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
52
+
53
+ GitHub API rate-limit: cap at 4 for `gh` calls.
54
+
44
55
  ## Output
45
56
 
46
57
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -20,6 +20,17 @@ Analyze/count/filter/compare/search/parse/transform data: **write code** via `ct
20
20
  4. **WEB**: `ctx_fetch_and_index(url)` then `ctx_search(queries)` — never dump raw HTML.
21
21
  5. **INDEX**: `ctx_index(content, source)` — store in FTS5 for later search.
22
22
 
23
+ ## Parallel I/O batches
24
+
25
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
26
+
27
+ - `ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
28
+ - `ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
29
+
30
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
31
+
32
+ GitHub API rate-limit: cap at 4 for `gh` calls.
33
+
23
34
  ## Forbidden Actions
24
35
 
25
36
  - DO NOT use Bash for >20 lines output — use `ctx_execute` or `ctx_batch_execute`.
@@ -40,6 +40,17 @@ Use `mcp__context-mode__ctx_execute(language: "shell", code: "grep ...")` in san
40
40
  4. **WEB**: `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `mcp__context-mode__ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `mcp__context-mode__ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `mcp__context-mode__ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Output
44
55
 
45
56
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,9 @@ Use `ctx_execute(language: "shell", code: "grep ...")` in sandbox.
40
40
  4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ### Parallel I/O batches
44
+ Pass `concurrency: 4-8` to `ctx_batch_execute` and `ctx_fetch_and_index` for network/API batches. Keep `concurrency: 1` for CPU-bound work (test, build, lint). GitHub gh: cap at 4.
45
+
43
46
  ## Output
44
47
 
45
48
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,17 @@ Use `context-mode_ctx_execute(language: "shell", code: "grep ...")` in sandbox.
40
40
  4. **WEB**: `context-mode_ctx_fetch_and_index(url, source)` then `context-mode_ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `context-mode_ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `context-mode_ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `context-mode_ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Output
44
55
 
45
56
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,17 @@ Use `@context-mode/ctx_execute(language: "shell", code: "grep ...")` in sandbox.
40
40
  4. **WEB**: `@context-mode/ctx_fetch_and_index(url, source)` then `@context-mode/ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `@context-mode/ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `@context-mode/ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `@context-mode/ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Output
44
55
 
45
56
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,17 @@ Use `context-mode__ctx_execute(language: "shell", code: "grep ...")` in sandbox.
40
40
  4. **WEB**: `context-mode__ctx_fetch_and_index(url, source)` then `context-mode__ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `context-mode__ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `context-mode__ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `context-mode__ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Output
44
55
 
45
56
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,17 @@ Use `context-mode_ctx_execute(language: "shell", code: "grep ...")` in sandbox.
40
40
  4. **WEB**: `context-mode_ctx_fetch_and_index(url, source)` then `context-mode_ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `context-mode_ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `context-mode_ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `context-mode_ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Output
44
55
 
45
56
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -41,6 +41,17 @@ Use `ctx_execute(language: "shell", code: "grep ...")` in sandbox.
41
41
  4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — raw HTML never enters context.
42
42
  5. **INDEX**: `ctx_index(content, source)` — store in FTS5 for later search.
43
43
 
44
+ ## Parallel I/O batches
45
+
46
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
47
+
48
+ - `ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
49
+ - `ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
50
+
51
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
52
+
53
+ GitHub API rate-limit: cap at 4 for `gh` calls.
54
+
44
55
  ## Output
45
56
 
46
57
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,17 @@ Use `mcp__context-mode__ctx_execute(language: "shell", code: "grep ...")` in san
40
40
  4. **WEB**: `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `mcp__context-mode__ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `mcp__context-mode__ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `mcp__context-mode__ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Subagent routing
44
55
 
45
56
  Routing block auto-injected into subagent prompts. Bash-type subagents upgraded to general-purpose. No manual instruction needed.
@@ -40,6 +40,9 @@ Use `ctx_execute(language: "shell", code: "grep ...")` in sandbox.
40
40
  4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ### Parallel I/O batches
44
+ Pass `concurrency: 4-8` to `ctx_batch_execute` and `ctx_fetch_and_index` for network/API batches. Keep `concurrency: 1` for CPU-bound work (test, build, lint). GitHub gh: cap at 4.
45
+
43
46
  ## Output
44
47
 
45
48
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -40,6 +40,17 @@ Use `mcp:context-mode:ctx_execute(language: "shell", code: "grep ...")` in sandb
40
40
  4. **WEB**: `mcp:context-mode:ctx_fetch_and_index(url, source)` then `mcp:context-mode:ctx_search(queries)` — raw HTML never enters context.
41
41
  5. **INDEX**: `mcp:context-mode:ctx_index(content, source)` — store in FTS5 for later search.
42
42
 
43
+ ## Parallel I/O batches
44
+
45
+ For multi-URL fetches or multi-API calls, **always** include `concurrency: N` (1-8):
46
+
47
+ - `mcp:context-mode:ctx_batch_execute(commands: [3+ network commands], concurrency: 5)` — gh, curl, dig, docker inspect, multi-region cloud queries
48
+ - `mcp:context-mode:ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)` — multi-URL batch fetch
49
+
50
+ **Use concurrency 4-8** for I/O-bound work (network calls, API queries). **Keep concurrency 1** for CPU-bound (npm test, build, lint) or commands sharing state (ports, lock files, same-repo writes).
51
+
52
+ GitHub API rate-limit: cap at 4 for `gh` calls.
53
+
43
54
  ## Output
44
55
 
45
56
  Terse like caveman. Technical substance exact. Only fluff die.
@@ -28,19 +28,47 @@ export function estimateTokens(text) {
28
28
  * @returns {string} XML block or empty string
29
29
  */
30
30
  export function buildAutoInjection(events) {
31
+ // Single O(N) pass instead of 4× O(N) Array.filter() loops. UserPromptSubmit
32
+ // fires this on every prompt; with N up to 100 events the prior implementation
33
+ // walked the array 4 times per prompt — wasteful on macOS, painful on Windows
34
+ // where V8 cold paths cost more.
35
+ let role;
36
+ const decisionsAll = [];
37
+ const skillsSeen = new Set();
38
+ const skillsOrdered = [];
39
+ let intent;
40
+ for (const e of events) {
41
+ switch (e.category) {
42
+ case "role":
43
+ role = e;
44
+ break;
45
+ case "decision":
46
+ decisionsAll.push(e);
47
+ break;
48
+ case "skill":
49
+ if (!skillsSeen.has(e.data)) {
50
+ skillsSeen.add(e.data);
51
+ skillsOrdered.push(e.data);
52
+ }
53
+ break;
54
+ case "intent":
55
+ intent = e;
56
+ break;
57
+ }
58
+ }
59
+
31
60
  const parts = [];
32
61
  let budget = 500; // hard cap in tokens
33
62
 
34
63
  // P1: Role (always first, never truncated from output)
35
- const roleEvent = events.filter(e => e.category === "role").pop(); // latest
36
- if (roleEvent) {
37
- const text = `<behavioral_directive>\n${roleEvent.data.slice(0, 400)}\n</behavioral_directive>`;
64
+ if (role) {
65
+ const text = `<behavioral_directive>\n${role.data.slice(0, 400)}\n</behavioral_directive>`;
38
66
  parts.push(text);
39
67
  budget -= estimateTokens(text);
40
68
  }
41
69
 
42
70
  // P2: Decisions (latest 5)
43
- const decisions = events.filter(e => e.category === "decision").slice(-5);
71
+ const decisions = decisionsAll.slice(-5);
44
72
  if (decisions.length > 0) {
45
73
  const lines = decisions.map(d => `- ${d.data.slice(0, 100)}`).join("\n");
46
74
  const text = `<rules>\nFollow these decisions:\n${lines}\n</rules>`;
@@ -58,17 +86,15 @@ export function buildAutoInjection(events) {
58
86
  }
59
87
 
60
88
  // P3: Skills (unique names, latest 10)
61
- const skills = [...new Set(events.filter(e => e.category === "skill").map(e => e.data))];
62
- if (skills.length > 0 && budget > 50) {
63
- const text = `<active_skills>\nRe-invoke if relevant: ${skills.slice(-10).join(", ")}\nTo reload: call the Skill tool with the skill name.\n</active_skills>`;
89
+ if (skillsOrdered.length > 0 && budget > 50) {
90
+ const text = `<active_skills>\nRe-invoke if relevant: ${skillsOrdered.slice(-10).join(", ")}\nTo reload: call the Skill tool with the skill name.\n</active_skills>`;
64
91
  parts.push(text);
65
92
  budget -= estimateTokens(text);
66
93
  }
67
94
 
68
95
  // P4: Intent (latest)
69
- const intentEvent = events.filter(e => e.category === "intent").pop();
70
- if (intentEvent && budget > 20) {
71
- parts.push(`<session_mode>${intentEvent.data}</session_mode>`);
96
+ if (intent && budget > 20) {
97
+ parts.push(`<session_mode>${intent.data}</session_mode>`);
72
98
  }
73
99
 
74
100
  if (parts.length === 0) return "";
@@ -0,0 +1,231 @@
1
+ // cache-heal-utils.mjs — fixes Brew-node-upgrade stale path bug
2
+ //
3
+ // Problem: start.mjs writes process.execPath into ~/.claude/settings.json
4
+ // when registering the cache-heal hook. On Brew, process.execPath returns
5
+ // the *versioned* Cellar snapshot:
6
+ //
7
+ // /opt/homebrew/Cellar/node/25.9.0_2/bin/node
8
+ //
9
+ // When Brew upgrades Node, that path disappears and Claude fails to spawn
10
+ // the hook ("session start" error). The stable symlink is:
11
+ //
12
+ // /opt/homebrew/bin/node
13
+ //
14
+ // Fix is two layered:
15
+ // A) New installs on Unix: write hook script with `#!/usr/bin/env node`
16
+ // shebang + chmod +x, register hook command as the bare script path.
17
+ // `env` resolves node from PATH at runtime — survives any Node upgrade.
18
+ // Windows keeps the explicit-execPath form (no shebang support).
19
+ // B) Self-heal: every MCP boot, scan ~/.claude/settings.json for an
20
+ // existing cache-heal hook command whose leading node path no longer
21
+ // exists. If stale, rewrite using pattern (A).
22
+ //
23
+ // This module is pure (no global state) and side-effect free except for
24
+ // the explicit selfHealCacheHealHook() entry point that touches disk.
25
+
26
+ import {
27
+ existsSync,
28
+ readFileSync,
29
+ writeFileSync,
30
+ chmodSync,
31
+ statSync,
32
+ } from "node:fs";
33
+
34
+ /**
35
+ * Convert any path string to forward slashes (matches normalize-hooks style,
36
+ * keeps round-trips on Windows safe).
37
+ */
38
+ function fwd(p) {
39
+ return String(p).replace(/\\/g, "/");
40
+ }
41
+
42
+ /**
43
+ * Extract the leading executable path from a hook command string IF it
44
+ * looks like a node binary. Returns null when the command is shebang-style
45
+ * (bare script path) or when the leading executable isn't node.
46
+ *
47
+ * Accepted shapes:
48
+ * '"/abs/path/to/node" "/abs/path/script.mjs"'
49
+ * '/abs/path/to/node "/abs/path/script.mjs"' (unquoted node)
50
+ *
51
+ * Returns null for:
52
+ * '"/abs/path/script.mjs"' (shebang form)
53
+ * '"/usr/bin/python3" "/abs/path/script.py"' (not node)
54
+ */
55
+ export function extractNodePath(cmd) {
56
+ if (!cmd || typeof cmd !== "string") return null;
57
+ const trimmed = cmd.trim();
58
+ if (!trimmed) return null;
59
+
60
+ // Match: optional quote, capture path until matching quote or whitespace.
61
+ let leading;
62
+ if (trimmed.startsWith('"')) {
63
+ const end = trimmed.indexOf('"', 1);
64
+ if (end === -1) return null;
65
+ leading = trimmed.slice(1, end);
66
+ } else {
67
+ const end = trimmed.search(/\s/);
68
+ leading = end === -1 ? trimmed : trimmed.slice(0, end);
69
+ }
70
+
71
+ if (!leading) return null;
72
+
73
+ // Only treat as a node path if the basename is a node binary.
74
+ // Match: "node", "node.exe" (case-insensitive on Windows-style names).
75
+ const base = leading.split(/[\\/]/).pop() ?? "";
76
+ if (!/^node(\.exe)?$/i.test(base)) return null;
77
+
78
+ return leading;
79
+ }
80
+
81
+ /**
82
+ * True when the hook command's leading node path no longer exists on disk.
83
+ * Returns false for shebang-style commands (no node prefix to validate).
84
+ */
85
+ export function isStaleNodePath(cmd) {
86
+ const nodePath = extractNodePath(cmd);
87
+ if (!nodePath) return false;
88
+ try {
89
+ return !existsSync(nodePath);
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Build a cross-platform hook command for the cache-heal script.
97
+ *
98
+ * On Unix (anything except win32):
99
+ * - Returns just the script path (double-quoted), e.g. '"/path/to/script.mjs"'
100
+ * - Caller MUST ensure the script has `#!/usr/bin/env node` shebang and
101
+ * chmod 0o755.
102
+ * - `env` resolves node from PATH at runtime → survives Brew/asdf/nvm
103
+ * upgrades.
104
+ *
105
+ * On Windows:
106
+ * - Returns '"<nodePath>" "<scriptPath>"' (forward slashes, both quoted).
107
+ * - Windows has no shebang support; we must invoke node explicitly.
108
+ */
109
+ export function buildHookCommand({ scriptPath, platform, nodePath }) {
110
+ if (!scriptPath || typeof scriptPath !== "string") {
111
+ throw new TypeError("buildHookCommand: scriptPath is required");
112
+ }
113
+ const safeScript = fwd(scriptPath);
114
+ if (platform === "win32") {
115
+ if (!nodePath || typeof nodePath !== "string") {
116
+ throw new TypeError(
117
+ "buildHookCommand: nodePath is required on win32",
118
+ );
119
+ }
120
+ const safeNode = fwd(nodePath);
121
+ return `"${safeNode}" "${safeScript}"`;
122
+ }
123
+ return `"${safeScript}"`;
124
+ }
125
+
126
+ /**
127
+ * Self-heal step for ~/.claude/settings.json.
128
+ *
129
+ * - Looks at SessionStart hooks for any registered cache-heal hook.
130
+ * - If its command has a stale node path (Brew upgrade scenario),
131
+ * rewrites the command using buildHookCommand() — Unix gets shebang
132
+ * form, Windows gets explicit nodePath form.
133
+ * - No-op when:
134
+ * * settings.json doesn't exist
135
+ * * no cache-heal hook is registered
136
+ * * the hook command is already valid (path exists or shebang form)
137
+ * - On Unix, also re-asserts the script's shebang + chmod +x so a healed
138
+ * command actually works.
139
+ *
140
+ * Returns: one of "noop" | "healed" | "missing-settings" — useful for
141
+ * tests and telemetry.
142
+ *
143
+ * Best-effort — all I/O is wrapped; never throws.
144
+ */
145
+ export function selfHealCacheHealHook({
146
+ settingsPath,
147
+ scriptPath,
148
+ platform,
149
+ nodePath,
150
+ }) {
151
+ if (!settingsPath || !existsSync(settingsPath)) return "missing-settings";
152
+
153
+ let raw;
154
+ try {
155
+ raw = readFileSync(settingsPath, "utf-8");
156
+ } catch {
157
+ return "noop";
158
+ }
159
+
160
+ let parsed;
161
+ try {
162
+ parsed = JSON.parse(raw);
163
+ } catch {
164
+ return "noop";
165
+ }
166
+
167
+ const hooks = parsed?.hooks;
168
+ if (!hooks || typeof hooks !== "object") return "noop";
169
+ const sessionStart = Array.isArray(hooks.SessionStart)
170
+ ? hooks.SessionStart
171
+ : null;
172
+ if (!sessionStart) return "noop";
173
+
174
+ let healed = false;
175
+ for (const matcher of sessionStart) {
176
+ const inner = matcher?.hooks;
177
+ if (!Array.isArray(inner)) continue;
178
+ for (const h of inner) {
179
+ if (typeof h?.command !== "string") continue;
180
+ if (!h.command.includes("context-mode-cache-heal")) continue;
181
+ if (!isStaleNodePath(h.command)) continue;
182
+
183
+ // Stale → rewrite.
184
+ h.command = buildHookCommand({ scriptPath, platform, nodePath });
185
+ healed = true;
186
+ }
187
+ }
188
+
189
+ if (!healed) return "noop";
190
+
191
+ // Unix: re-assert shebang + chmod so the bare-script command works.
192
+ if (platform !== "win32" && scriptPath && existsSync(scriptPath)) {
193
+ try {
194
+ ensureShebangAndExecBit(scriptPath);
195
+ } catch {
196
+ /* best effort */
197
+ }
198
+ }
199
+
200
+ try {
201
+ writeFileSync(
202
+ settingsPath,
203
+ JSON.stringify(parsed, null, 2) + "\n",
204
+ "utf-8",
205
+ );
206
+ } catch {
207
+ return "noop";
208
+ }
209
+ return "healed";
210
+ }
211
+
212
+ /**
213
+ * Ensure a script starts with `#!/usr/bin/env node` and has 0o755 mode.
214
+ * Idempotent — leaves correctly-shebanged scripts unchanged.
215
+ */
216
+ export function ensureShebangAndExecBit(scriptPath) {
217
+ if (!scriptPath || !existsSync(scriptPath)) return;
218
+ try {
219
+ const content = readFileSync(scriptPath, "utf-8");
220
+ if (!content.startsWith("#!")) {
221
+ writeFileSync(scriptPath, `#!/usr/bin/env node\n${content}`, "utf-8");
222
+ }
223
+ // statSync().mode lower 9 bits = perms.
224
+ const mode = statSync(scriptPath).mode & 0o777;
225
+ if (mode !== 0o755) {
226
+ chmodSync(scriptPath, 0o755);
227
+ }
228
+ } catch {
229
+ /* best effort */
230
+ }
231
+ }
@@ -14,7 +14,6 @@ import {
14
14
  writeSessionEventsFile,
15
15
  buildSessionDirective,
16
16
  getSessionEvents,
17
- getLatestSessionEvents,
18
17
  } from "../session-directive.mjs";
19
18
  import {
20
19
  readStdin,
@@ -57,9 +56,13 @@ try {
57
56
  try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
58
57
  }
59
58
 
60
- const events = source === "compact"
61
- ? getSessionEvents(db, getSessionId(input, OPTS))
62
- : getLatestSessionEvents(db);
59
+ // Filter events to the session being resumed/compacted. Falling back to
60
+ // getLatestSessionEvents(db) for resume leaks events from any other
61
+ // session whose session_meta.started_at is more recent — observed
62
+ // cross-session bleed when a different session started after this one
63
+ // and before the resume.
64
+ const sessionId = getSessionId(input, OPTS);
65
+ const events = sessionId ? getSessionEvents(db, sessionId) : [];
63
66
  if (events.length > 0) {
64
67
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
65
68
  additionalContext += buildSessionDirective(source, eventMeta, toolNamer);
@@ -153,6 +153,11 @@ const TOOL_ALIASES = {
153
153
  "container.exec": "Bash",
154
154
  "local_shell": "Bash",
155
155
  "grep_files": "Grep",
156
+ // OpenClaw native tools
157
+ "exec": "Bash",
158
+ "read": "Read",
159
+ "grep": "Grep",
160
+ "search": "Grep",
156
161
  // Cursor
157
162
  "mcp_web_fetch": "WebFetch",
158
163
  "mcp_fetch_tool": "WebFetch",
@@ -284,9 +289,10 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
284
289
  });
285
290
  }
286
291
 
287
- // Build tools (gradle, maven) → redirect to execute sandbox (Issue #38).
292
+ // Build tools (gradle, maven, sbt) → redirect to execute sandbox (Issue #38, #406).
288
293
  // These produce extremely verbose output that should stay in sandbox.
289
- if (/(^|\s|&&|\||\;)(\.\/gradlew|gradlew|gradle|\.\/mvnw|mvnw|mvn)\s/i.test(stripped)) {
294
+ // Word-boundary guard prevents matching `gradle-wrapper-config`, `mvnDocker`, etc.
295
+ if (/(^|\s|&&|\||\;)(\.\/gradlew|gradlew|gradle|\.\/mvnw|mvnw|mvn|\.\/sbt|sbt)(\s|$)/i.test(stripped)) {
290
296
  const safeCmd = command.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
291
297
  return mcpRedirect({
292
298
  action: "modify",
@@ -14,7 +14,6 @@ import {
14
14
  writeSessionEventsFile,
15
15
  buildSessionDirective,
16
16
  getSessionEvents,
17
- getLatestSessionEvents,
18
17
  } from "../session-directive.mjs";
19
18
  import {
20
19
  readStdin,
@@ -61,9 +60,13 @@ try {
61
60
  try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
62
61
  }
63
62
 
64
- const events = source === "compact"
65
- ? getSessionEvents(db, getSessionId(input, OPTS))
66
- : getLatestSessionEvents(db);
63
+ // Filter events to the session being resumed/compacted. Falling back to
64
+ // getLatestSessionEvents(db) for resume leaks events from any other
65
+ // session whose session_meta.started_at is more recent — observed
66
+ // cross-session bleed when a different session started after this one
67
+ // and before the resume.
68
+ const sessionId = getSessionId(input, OPTS);
69
+ const events = sessionId ? getSessionEvents(db, sessionId) : [];
67
70
  if (events.length > 0) {
68
71
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
69
72
  additionalContext += buildSessionDirective(source, eventMeta, toolNamer);