context-mode 1.0.53 → 1.0.56

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 (66) 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 +103 -32
  6. package/build/adapters/antigravity/index.d.ts +1 -3
  7. package/build/adapters/antigravity/index.js +0 -30
  8. package/build/adapters/claude-code/hooks.d.ts +18 -0
  9. package/build/adapters/claude-code/hooks.js +23 -0
  10. package/build/adapters/claude-code/index.d.ts +1 -3
  11. package/build/adapters/claude-code/index.js +48 -35
  12. package/build/adapters/client-map.js +1 -0
  13. package/build/adapters/codex/index.d.ts +1 -3
  14. package/build/adapters/codex/index.js +1 -31
  15. package/build/adapters/cursor/index.d.ts +1 -3
  16. package/build/adapters/cursor/index.js +0 -11
  17. package/build/adapters/detect.d.ts +1 -0
  18. package/build/adapters/detect.js +18 -2
  19. package/build/adapters/gemini-cli/index.d.ts +1 -3
  20. package/build/adapters/gemini-cli/index.js +0 -30
  21. package/build/adapters/kiro/index.d.ts +1 -3
  22. package/build/adapters/kiro/index.js +0 -30
  23. package/build/adapters/openclaw/index.d.ts +1 -3
  24. package/build/adapters/openclaw/index.js +0 -38
  25. package/build/adapters/opencode/index.d.ts +5 -4
  26. package/build/adapters/opencode/index.js +37 -41
  27. package/build/adapters/types.d.ts +1 -14
  28. package/build/adapters/vscode-copilot/index.d.ts +1 -3
  29. package/build/adapters/vscode-copilot/index.js +0 -32
  30. package/build/adapters/zed/index.d.ts +1 -3
  31. package/build/adapters/zed/index.js +0 -30
  32. package/build/cli.js +12 -28
  33. package/build/executor.d.ts +0 -1
  34. package/build/executor.js +28 -16
  35. package/build/openclaw-plugin.js +12 -34
  36. package/build/opencode-plugin.d.ts +1 -0
  37. package/build/opencode-plugin.js +5 -9
  38. package/build/runtime.js +29 -11
  39. package/build/server.d.ts +2 -0
  40. package/build/server.js +69 -61
  41. package/build/store.d.ts +4 -3
  42. package/build/store.js +101 -34
  43. package/build/truncate.d.ts +4 -17
  44. package/build/truncate.js +4 -52
  45. package/cli.bundle.mjs +184 -157
  46. package/configs/codex/AGENTS.md +19 -0
  47. package/configs/kilo/AGENTS.md +58 -0
  48. package/configs/kilo/kilo.json +10 -0
  49. package/hooks/core/tool-naming.mjs +1 -0
  50. package/hooks/ensure-deps.mjs +80 -2
  51. package/hooks/pretooluse.mjs +25 -20
  52. package/hooks/routing-block.mjs +10 -1
  53. package/hooks/session-snapshot.bundle.mjs +13 -13
  54. package/hooks/sessionstart.mjs +25 -1
  55. package/openclaw.plugin.json +1 -1
  56. package/package.json +1 -1
  57. package/server.bundle.mjs +159 -129
  58. package/skills/context-mode-ops/SKILL.md +111 -0
  59. package/skills/context-mode-ops/agent-teams.md +198 -0
  60. package/skills/context-mode-ops/communication.md +224 -0
  61. package/skills/context-mode-ops/release.md +199 -0
  62. package/skills/context-mode-ops/review-pr.md +269 -0
  63. package/skills/context-mode-ops/tdd.md +329 -0
  64. package/skills/context-mode-ops/triage-issue.md +218 -0
  65. package/skills/context-mode-ops/validation.md +238 -0
  66. package/start.mjs +5 -52
@@ -56,3 +56,22 @@ Search results can flood context. Use `ctx_execute(language: "shell", code: "gre
56
56
  | `ctx stats` | Call the `stats` MCP tool and display the full output verbatim |
57
57
  | `ctx doctor` | Call the `doctor` MCP tool, run the returned shell command, display as checklist |
58
58
  | `ctx upgrade` | Call the `upgrade` MCP tool, run the returned shell command, display as checklist |
59
+
60
+ ## Windows notes
61
+
62
+ **PowerShell cmdlets in shell scripts** — The sandbox executes scripts via bash. PowerShell
63
+ cmdlets (`Format-List`, `Format-Table`, `Get-Culture`, etc.) do not exist in bash and will fail
64
+ with `command not found`. Wrap them with `pwsh -NoProfile -Command "..."` instead.
65
+
66
+ **Relative paths** — The sandbox CWD is a temp directory, not your project root. Always convert
67
+ any user-supplied path to an absolute path before passing it to `rg`, `grep`, or `find`.
68
+ Ask the user to confirm the absolute path if it is not already known.
69
+
70
+ **Windows drive letter paths** — The sandbox runs Git Bash / MSYS2, not WSL. Drive letters must
71
+ use the MSYS2 convention, NOT the WSL convention:
72
+ `X:\path` → `/x/path` (lowercase letter, no `/mnt/` prefix).
73
+ Never emit `/mnt/<letter>/` prefixes regardless of which drive the project is on.
74
+
75
+ **Always quote paths** — If a path contains spaces, bash splits it into separate arguments.
76
+ Always wrap every path in double quotes: `rg "symbol" "$REPO_ROOT/some dir/Source"`.
77
+ This applies to all tools: `rg`, `grep`, `find`, `ls`, etc.
@@ -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
+ - `context-mode_ctx_fetch_and_index(url, source)` to fetch and index web pages
11
+ - `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
+ - `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
+ - `context-mode_ctx_fetch_and_index(url, source)` then `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
+ - `context-mode_ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
29
+ - `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 `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 `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**: `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**: `context-mode_ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
42
+ 3. **PROCESSING**: `context-mode_ctx_execute(language, code)` | `context-mode_ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
43
+ 4. **WEB**: `context-mode_ctx_fetch_and_index(url, source)` then `context-mode_ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
44
+ 5. **INDEX**: `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,10 @@
1
+ {
2
+ "$schema": "https://app.kilo.ai/config.json",
3
+ "mcp": {
4
+ "context-mode": {
5
+ "type": "local",
6
+ "command": ["context-mode"]
7
+ }
8
+ },
9
+ "plugin": ["context-mode"]
10
+ }
@@ -20,6 +20,7 @@ const TOOL_PREFIXES = {
20
20
  "gemini-cli": (tool) => `mcp__context-mode__${tool}`,
21
21
  "antigravity": (tool) => `mcp__context-mode__${tool}`,
22
22
  "opencode": (tool) => `context-mode_${tool}`,
23
+ "kilo": (tool) => `context-mode_${tool}`,
23
24
  "vscode-copilot": (tool) => `context-mode_${tool}`,
24
25
  "kiro": (tool) => `@context-mode/${tool}`,
25
26
  "zed": (tool) => `mcp:context-mode:${tool}`,
@@ -8,13 +8,22 @@
8
8
  * hook that needs native modules. Fast path: existsSync check (~0.1ms).
9
9
  * Slow path: npm install (first run only, ~5-30s).
10
10
  *
11
+ * Also handles ABI compatibility (#148, #203): when the current Node.js
12
+ * version differs from the one better-sqlite3 was compiled against,
13
+ * automatically swaps in a cached binary or rebuilds. This protects
14
+ * both the MCP server AND hooks from ABI mismatch crashes when users
15
+ * have multiple Node versions via mise/volta/fnm/nvm.
16
+ *
17
+ * @see https://github.com/mksglu/context-mode/issues/148
11
18
  * @see https://github.com/mksglu/context-mode/issues/172
19
+ * @see https://github.com/mksglu/context-mode/issues/203
12
20
  */
13
21
 
14
- import { existsSync } from "node:fs";
22
+ import { existsSync, copyFileSync } from "node:fs";
15
23
  import { execSync } from "node:child_process";
16
24
  import { resolve, dirname } from "node:path";
17
25
  import { fileURLToPath } from "node:url";
26
+ import { createRequire } from "node:module";
18
27
 
19
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
20
29
  const root = resolve(__dirname, "..");
@@ -23,7 +32,9 @@ const NATIVE_DEPS = ["better-sqlite3"];
23
32
 
24
33
  export function ensureDeps() {
25
34
  for (const pkg of NATIVE_DEPS) {
26
- if (!existsSync(resolve(root, "node_modules", pkg))) {
35
+ const pkgDir = resolve(root, "node_modules", pkg);
36
+ if (!existsSync(pkgDir)) {
37
+ // Package not installed at all
27
38
  try {
28
39
  execSync(`npm install ${pkg} --no-package-lock --no-save --silent`, {
29
40
  cwd: root,
@@ -31,9 +42,76 @@ export function ensureDeps() {
31
42
  timeout: 120000,
32
43
  });
33
44
  } catch { /* best effort — hook degrades gracefully without DB */ }
45
+ } else if (
46
+ !existsSync(resolve(pkgDir, "build", "Release")) &&
47
+ !existsSync(resolve(pkgDir, "prebuilds"))
48
+ ) {
49
+ // Package installed but native binary missing (e.g., npm ignore-scripts=true)
50
+ try {
51
+ execSync(`npm rebuild ${pkg} --ignore-scripts=false`, {
52
+ cwd: root,
53
+ stdio: "pipe",
54
+ timeout: 120000,
55
+ });
56
+ } catch { /* best effort — hook degrades gracefully without DB */ }
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * ABI-aware native binary caching for better-sqlite3 (#148, #203).
63
+ *
64
+ * Users with mise/asdf/volta/fnm may run sessions with different Node
65
+ * versions. Each ABI needs its own compiled binary — cache them
66
+ * side-by-side so switching Node versions doesn't require a rebuild
67
+ * every time.
68
+ *
69
+ * Flow:
70
+ * 1. Check if ABI-specific cache exists → swap in
71
+ * 2. Probe-load better-sqlite3 → if OK, cache current binary
72
+ * 3. If ABI mismatch → npm rebuild, then cache the new binary
73
+ */
74
+ export function ensureNativeCompat(pluginRoot) {
75
+ try {
76
+ const abi = process.versions.modules;
77
+ const nativeDir = resolve(pluginRoot, "node_modules", "better-sqlite3", "build", "Release");
78
+ const binaryPath = resolve(nativeDir, "better_sqlite3.node");
79
+ const abiCachePath = resolve(nativeDir, `better_sqlite3.abi${abi}.node`);
80
+
81
+ if (!existsSync(nativeDir)) return;
82
+
83
+ // Fast path: cached binary for this ABI already exists
84
+ if (existsSync(abiCachePath)) {
85
+ copyFileSync(abiCachePath, binaryPath);
86
+ return;
87
+ }
88
+
89
+ if (!existsSync(binaryPath)) return;
90
+
91
+ // Probe: try loading better-sqlite3 with current Node
92
+ try {
93
+ const req = createRequire(resolve(pluginRoot, "package.json"));
94
+ req("better-sqlite3");
95
+ // Load succeeded — cache the working binary for this ABI
96
+ copyFileSync(binaryPath, abiCachePath);
97
+ } catch (probeErr) {
98
+ if (probeErr?.message?.includes("NODE_MODULE_VERSION")) {
99
+ // ABI mismatch — rebuild for current Node version
100
+ execSync("npm rebuild better-sqlite3", {
101
+ cwd: pluginRoot,
102
+ stdio: "pipe",
103
+ timeout: 60000,
104
+ });
105
+ if (existsSync(binaryPath)) {
106
+ copyFileSync(binaryPath, abiCachePath);
107
+ }
108
+ }
34
109
  }
110
+ } catch {
111
+ /* best effort — caller will report the error on first DB access */
35
112
  }
36
113
  }
37
114
 
38
115
  // Auto-run on import (like suppress-stderr.mjs)
39
116
  ensureDeps();
117
+ ensureNativeCompat(root);
@@ -87,39 +87,44 @@ try {
87
87
  writeFileSync(ipPath, JSON.stringify(ip, null, 2) + "\n", "utf-8");
88
88
  }
89
89
 
90
- // 3. Update hook path + matcher in settings.json
90
+ // 3. Update hook paths + matcher in settings.json for ALL hook types (#187)
91
+ // Previously only fixed PreToolUse — SessionStart, PostToolUse, PreCompact,
92
+ // UserPromptSubmit paths remained stale after marketplace auto-update.
91
93
  const settingsPath = resolve(homedir(), ".claude", "settings.json");
92
94
  try {
93
95
  const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
94
- const hooks = settings.hooks?.PreToolUse;
95
- if (Array.isArray(hooks)) {
96
- let changed = false;
97
- for (const entry of hooks) {
98
- // Fix deprecated Task-only matcher → Agent|Task
99
- if (entry.matcher && entry.matcher.includes("Task") && !entry.matcher.includes("Agent")) {
96
+ const allHooks = settings.hooks || {};
97
+ let changed = false;
98
+
99
+ for (const hookType of Object.keys(allHooks)) {
100
+ const entries = allHooks[hookType];
101
+ if (!Array.isArray(entries)) continue;
102
+
103
+ for (const entry of entries) {
104
+ // Fix deprecated Task-only matcher (PreToolUse only)
105
+ if (hookType === "PreToolUse" && entry.matcher?.includes("Task") && !entry.matcher.includes("Agent")) {
100
106
  entry.matcher = entry.matcher.replace("Task", "Agent|Task");
101
107
  changed = true;
102
108
  }
109
+ // Rewrite stale context-mode hook paths to point to current version
103
110
  for (const h of (entry.hooks || [])) {
104
- if (h.command?.includes("pretooluse.mjs") && !h.command.includes(targetDir)) {
105
- h.command = "node " + resolve(targetDir, "hooks", "pretooluse.mjs");
106
- changed = true;
111
+ if (h.command && h.command.includes(".mjs") && h.command.includes("context-mode") && !h.command.includes(targetDir)) {
112
+ // Extract the script filename (e.g., sessionstart.mjs, pretooluse.mjs)
113
+ const scriptMatch = h.command.match(/([a-z]+\.mjs)\s*"?\s*$/);
114
+ if (scriptMatch) {
115
+ h.command = "node " + resolve(targetDir, "hooks", scriptMatch[1]);
116
+ changed = true;
117
+ }
107
118
  }
108
119
  }
109
120
  }
110
- if (changed) writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
111
121
  }
122
+
123
+ if (changed) writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
112
124
  } catch { /* skip settings update */ }
113
125
 
114
- // 4. Nuke stale version dirs (keep only targetDir and current running dir)
115
- try {
116
- const keepDirs = new Set([basename(targetDir), myDirName]);
117
- for (const d of readdirSync(cacheParent)) {
118
- if (!keepDirs.has(d)) {
119
- try { rmSync(resolve(cacheParent, d), { recursive: true, force: true }); } catch { /* skip */ }
120
- }
121
- }
122
- } catch { /* skip */ }
126
+ // Old version dirs are cleaned lazily by sessionstart.mjs (age-gated >1h)
127
+ // to avoid breaking active sessions that still reference them (#181).
123
128
 
124
129
  writeFileSync(marker, Date.now().toString(), "utf-8");
125
130
  }
@@ -35,12 +35,21 @@ export function createRoutingBlock(t) {
35
35
  - DO NOT use Read for analysis (use execute_file). Read IS correct for files you intend to Edit.
36
36
  - DO NOT use WebFetch (use ${t("ctx_fetch_and_index")} instead).
37
37
  - Bash is ONLY for git/mkdir/rm/mv/navigation.
38
+ - DO NOT use ${t("ctx_execute")} or ${t("ctx_execute_file")} to create, modify, or overwrite files.
39
+ ctx_execute is for data analysis, log processing, and computation only.
38
40
  </forbidden_actions>
39
41
 
42
+ <file_writing_policy>
43
+ ALWAYS use the native Write tool to create files and Edit tool to modify files.
44
+ NEVER use ${t("ctx_execute")}, ${t("ctx_execute_file")}, or Bash to write file content.
45
+ This applies to all file types: code, configs, plans, specs, YAML, JSON, markdown.
46
+ </file_writing_policy>
47
+
40
48
  <output_constraints>
41
49
  <word_limit>Keep your final response under 500 words.</word_limit>
42
50
  <artifact_policy>
43
- Write artifacts (code, configs, PRDs) to FILES. NEVER return them as inline text.
51
+ Write artifacts (code, configs, PRDs) to FILES using the native Write tool. NEVER return them as inline text.
52
+ Use Edit tool for modifications to existing files.
44
53
  Return only: file path + 1-line description.
45
54
  </artifact_policy>
46
55
  <response_format>
@@ -1,14 +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(`
1
+ function l(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 p=u.data,i=e.get(p);i||(i={ops:new Map,last:""},e.set(p,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:p,last:i}]of s){let r=Array.from(p.entries()).map(([f,g])=>`${f}:${g}`).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 p=[" <task_state>"];for(let i of u)p.push(` - ${a(l(i,100))}`);return p.push(" </task_state>"),p.join(`
3
+ `)}function U(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(l(s.data,400))}</rule_content>`):n.push(` - ${a(l(s.data,200))}`))}return n.push(" </rules>"),n.join(`
4
+ `)}function V(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(l(s.data,200))}`))}return n.push(" </decisions>"),n.join(`
5
+ `)}function Y(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(l(c.data,150))}</env>`);return s.push(" </environment>"),s.join(`
6
+ `)}function H(t){if(t.length===0)return"";let e=[" <errors_encountered>"];for(let n of t)e.push(` - ${a(l(n.data,150))}`);return e.push(" </errors_encountered>"),e.join(`
7
+ `)}function K(t){return` <intent mode="${a(t.data)}">${a(l(t.data,100))}</intent>`}function P(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(l(n.data,200))}</agent>`)}return e.push(" </subagents>"),e.join(`
8
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};
9
+ `)}function et(t,e){let n=e?.maxBytes??G,s=e?.compactCount??1,c=new Date().toISOString(),u=[],p=[],i=[],r=[],f=[],g=[],$=[],h=[],E=[],S=[],w=[],m=[];for(let o of t)switch(o.category){case"file":u.push(o);break;case"task":p.push(o);break;case"rule":i.push(o);break;case"decision":r.push(o);break;case"cwd":f.push(o);break;case"error":g.push(o);break;case"env":$.push(o);break;case"git":h.push(o);break;case"subagent":E.push(o);break;case"intent":S.push(o);break;case"mcp":w.push(o);break;case"plan":m.push(o);break}let b=[],x=q(u);x&&b.push(x);let B=z(p);B&&b.push(B);let j=U(i);j&&b.push(j);let d=[],I=V(r);I&&d.push(I);let R=f.length>0?f[f.length-1]:void 0,F=h.length>0?h[h.length-1]:void 0,L=Y(R,$,F);L&&d.push(L);let M=H(g);M&&d.push(M);let D=E.filter(o=>o.type==="subagent_completed"),O=P(D);O&&d.push(O),m.length>0&&m[m.length-1].type==="plan_enter"&&d.push(' <plan_mode status="active" />');let v=[];if(S.length>0){let o=S[S.length-1];v.push(K(o))}let T=Q(w);T&&v.push(T);let X=E.filter(o=>o.type==="subagent_launched"),A=P(X);A&&v.push(A);let _=`<session_resume compact_count="${s}" events_captured="${t.length}" generated_at="${c}">`,k="</session_resume>",C=[b,d,v];for(let o=C.length;o>=0;o--){let N=C.slice(0,o).flat().join(`
10
+ `),y;if(N?y=`${_}
11
+ ${N}
12
+ ${k}`:y=`${_}
13
+ ${k}`,Buffer.byteLength(y)<=n)return y}return`${_}
14
+ ${k}`}export{et as buildResumeSnapshot,q as renderActiveFiles,V as renderDecisions,Y as renderEnvironment,H as renderErrors,K as renderIntent,Q as renderMcpTools,U as renderRules,P as renderSubagents,z as renderTaskState};
@@ -24,7 +24,7 @@ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLat
24
24
  import { createSessionLoaders } from "./session-loaders.mjs";
25
25
  import { join, dirname } from "node:path";
26
26
  import { fileURLToPath } from "node:url";
27
- import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
27
+ import { readFileSync, writeFileSync, unlinkSync, readdirSync, rmSync, statSync } from "node:fs";
28
28
  import { homedir } from "node:os";
29
29
 
30
30
  // Resolve absolute path for imports (fileURLToPath for Windows compat)
@@ -120,6 +120,30 @@ try {
120
120
  }
121
121
 
122
122
  db.close();
123
+
124
+ // Age-gated lazy cleanup of old plugin cache version dirs (#181).
125
+ // Only delete dirs older than 1 hour to avoid breaking active sessions.
126
+ try {
127
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
128
+ if (pluginRoot) {
129
+ const cacheParentMatch = pluginRoot.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+[\\/])/);
130
+ if (cacheParentMatch) {
131
+ const cacheParent = cacheParentMatch[1];
132
+ const myDir = pluginRoot.replace(cacheParent, "").replace(/[\\/]/g, "");
133
+ const ONE_HOUR = 3600000;
134
+ const now = Date.now();
135
+ for (const d of readdirSync(cacheParent)) {
136
+ if (d === myDir) continue;
137
+ try {
138
+ const st = statSync(join(cacheParent, d));
139
+ if (now - st.mtimeMs > ONE_HOUR) {
140
+ rmSync(join(cacheParent, d), { recursive: true, force: true });
141
+ }
142
+ } catch { /* skip */ }
143
+ }
144
+ }
145
+ }
146
+ } catch { /* best effort — never block session start */ }
123
147
  }
124
148
  // "clear" — no action needed
125
149
  } catch (err) {
@@ -3,7 +3,7 @@
3
3
  "name": "Context Mode",
4
4
  "kind": "tool",
5
5
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
6
- "version": "1.0.53",
6
+ "version": "1.0.56",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.53",
3
+ "version": "1.0.56",
4
4
  "type": "module",
5
5
  "description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
6
6
  "author": "Mert Koseoğlu",