context-mode 1.0.88 → 1.0.89

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.
@@ -96,24 +96,46 @@ try {
96
96
  const allHooks = settings.hooks || {};
97
97
  let changed = false;
98
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")) {
106
- entry.matcher = entry.matcher.replace("Task", "Agent|Task");
99
+ // If hooks.json is present, the plugin system owns hook registration.
100
+ // Remove any settings.json context-mode entries to prevent duplicate concurrent
101
+ // hook processes that cause "non-blocking hook error" on every tool call.
102
+ const hooksJsonPath = resolve(myRoot, "hooks", "hooks.json");
103
+ if (existsSync(hooksJsonPath)) {
104
+ for (const hookType of Object.keys(allHooks)) {
105
+ const entries = allHooks[hookType];
106
+ if (!Array.isArray(entries)) continue;
107
+ const filtered = entries.filter(
108
+ (entry) =>
109
+ !entry.hooks?.some(
110
+ (h) => h.command?.includes(".mjs") && h.command?.includes("context-mode"),
111
+ ),
112
+ );
113
+ if (filtered.length !== entries.length) {
114
+ allHooks[hookType] = filtered;
107
115
  changed = true;
108
116
  }
109
- // Rewrite stale context-mode hook paths to point to current version
110
- for (const h of (entry.hooks || [])) {
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
+ }
118
+ } else {
119
+ // Legacy: hooks.json absent rewrite stale paths to current version dir.
120
+ for (const hookType of Object.keys(allHooks)) {
121
+ const entries = allHooks[hookType];
122
+ if (!Array.isArray(entries)) continue;
123
+
124
+ for (const entry of entries) {
125
+ // Fix deprecated Task-only matcher (PreToolUse only)
126
+ if (hookType === "PreToolUse" && entry.matcher?.includes("Task") && !entry.matcher.includes("Agent")) {
127
+ entry.matcher = entry.matcher.replace("Task", "Agent|Task");
128
+ changed = true;
129
+ }
130
+ // Rewrite stale context-mode hook paths to point to current version
131
+ for (const h of (entry.hooks || [])) {
132
+ if (h.command && h.command.includes(".mjs") && h.command.includes("context-mode") && !h.command.includes(targetDir)) {
133
+ // Extract the script filename (e.g., sessionstart.mjs, pretooluse.mjs)
134
+ const scriptMatch = h.command.match(/([a-z]+\.mjs)\s*"?\s*$/);
135
+ if (scriptMatch) {
136
+ h.command = "node " + resolve(targetDir, "hooks", scriptMatch[1]);
137
+ changed = true;
138
+ }
117
139
  }
118
140
  }
119
141
  }
@@ -1,29 +1,29 @@
1
- function a(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}var N=10;function h(t,r=4){return[...new Set(t.filter(o=>o.length>0))].slice(0,r).map(o=>o.length>80?o.slice(0,80):o)}function m(t,r){if(r.length===0)return"";let s=r.map(n=>`"${a(n)}"`).join(", ");return`
1
+ function a(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}var M=10;function h(t,r=4){return[...new Set(t.filter(o=>o.length>0))].slice(0,r).map(o=>o.length>80?o.slice(0,80):o)}function m(t,r){if(r.length===0)return"";let s=r.map(n=>`"${a(n)}"`).join(", ");return`
2
2
  For full details:
3
3
  ${a(t)}(
4
4
  queries: [${s}],
5
5
  source: "session-events"
6
- )`}function x(t,r){if(t.length===0)return"";let s=new Map;for(let l of t){let b=l.data,p=s.get(b);p||(p={ops:new Map},s.set(b,p));let g;l.type==="file_write"?g="write":l.type==="file_read"?g="read":l.type==="file_edit"?g="edit":g=l.type,p.ops.set(g,(p.ops.get(g)??0)+1)}let o=Array.from(s.entries()).slice(-N),u=[],i=[];for(let[l,{ops:b}]of o){let p=Array.from(b.entries()).map(([S,y])=>`${S}\xD7${y}`).join(", "),g=l.split("/").pop()??l;u.push(` ${a(g)} (${a(p)})`),i.push(`${g} ${Array.from(b.keys()).join(" ")}`)}let e=h(i);return[` <files count="${s.size}">`,...u,m(r,e)," </files>"].join(`
7
- `)}function M(t,r){if(t.length===0)return"";let s=[],n=[];for(let i of t)s.push(` ${a(i.data)}`),n.push(i.data);let o=h(n);return[` <errors count="${t.length}">`,...s,m(r,o)," </errors>"].join(`
8
- `)}function A(t,r){if(t.length===0)return"";let s=new Set,n=[],o=[];for(let e of t)s.has(e.data)||(s.add(e.data),n.push(` ${a(e.data)}`),o.push(e.data));if(n.length===0)return"";let u=h(o);return[` <decisions count="${n.length}">`,...n,m(r,u)," </decisions>"].join(`
9
- `)}function D(t,r){if(t.length===0)return"";let s=new Set,n=[],o=[];for(let e of t)s.has(e.data)||(s.add(e.data),e.type==="rule_content"?n.push(` ${a(e.data)}`):n.push(` ${a(e.data)}`),o.push(e.data));if(n.length===0)return"";let u=h(o);return[` <rules count="${n.length}">`,...n,m(r,u)," </rules>"].join(`
10
- `)}function F(t,r){if(t.length===0)return"";let s=[],n=[];for(let i of t)s.push(` ${a(i.data)}`),n.push(i.data);let o=h(n);return[` <git count="${t.length}">`,...s,m(r,o)," </git>"].join(`
11
- `)}function R(t){if(t.length===0)return"";let r=[],s={};for(let e of t)try{let c=JSON.parse(e.data);typeof c.subject=="string"?r.push(c.subject):typeof c.taskId=="string"&&typeof c.status=="string"&&(s[c.taskId]=c.status)}catch{}if(r.length===0)return"";let n=new Set(["completed","deleted","failed"]),o=Object.keys(s).sort((e,c)=>Number(e)-Number(c)),u=[];for(let e=0;e<r.length;e++){let c=o[e],l=c?s[c]??"pending":"pending";n.has(l)||u.push(r[e])}if(u.length===0)return"";let i=[];for(let e of u)i.push(` [pending] ${a(e)}`);return i.join(`
12
- `)}function J(t,r){let s=R(t);if(!s)return"";let n=[];for(let e of t)try{let c=JSON.parse(e.data);typeof c.subject=="string"&&n.push(c.subject)}catch{}let o=h(n);return[` <task_state count="${s.split(`
6
+ )`}function A(t,r){if(t.length===0)return"";let s=new Map;for(let l of t){let b=l.data,p=s.get(b);p||(p={ops:new Map},s.set(b,p));let g;l.type==="file_write"?g="write":l.type==="file_read"?g="read":l.type==="file_edit"?g="edit":g=l.type,p.ops.set(g,(p.ops.get(g)??0)+1)}let o=Array.from(s.entries()).slice(-M),u=[],i=[];for(let[l,{ops:b}]of o){let p=Array.from(b.entries()).map(([S,y])=>`${S}\xD7${y}`).join(", "),g=l.split("/").pop()??l;u.push(` ${a(g)} (${a(p)})`),i.push(`${g} ${Array.from(b.keys()).join(" ")}`)}let e=h(i);return[` <files count="${s.size}">`,...u,m(r,e)," </files>"].join(`
7
+ `)}function x(t,r){if(t.length===0)return"";let s=[],n=[];for(let i of t)s.push(` ${a(i.data)}`),n.push(i.data);let o=h(n);return[` <errors count="${t.length}">`,...s,m(r,o)," </errors>"].join(`
8
+ `)}function D(t,r){if(t.length===0)return"";let s=new Set,n=[],o=[];for(let e of t)s.has(e.data)||(s.add(e.data),n.push(` ${a(e.data)}`),o.push(e.data));if(n.length===0)return"";let u=h(o);return[` <decisions count="${n.length}">`,...n,m(r,u)," </decisions>"].join(`
9
+ `)}function F(t,r){if(t.length===0)return"";let s=new Set,n=[],o=[];for(let e of t)s.has(e.data)||(s.add(e.data),e.type==="rule_content"?n.push(` ${a(e.data)}`):n.push(` ${a(e.data)}`),o.push(e.data));if(n.length===0)return"";let u=h(o);return[` <rules count="${n.length}">`,...n,m(r,u)," </rules>"].join(`
10
+ `)}function R(t,r){if(t.length===0)return"";let s=[],n=[];for(let i of t)s.push(` ${a(i.data)}`),n.push(i.data);let o=h(n);return[` <git count="${t.length}">`,...s,m(r,o)," </git>"].join(`
11
+ `)}function B(t){if(t.length===0)return"";let r=[],s={};for(let e of t)try{let c=JSON.parse(e.data);typeof c.subject=="string"?r.push(c.subject):typeof c.taskId=="string"&&typeof c.status=="string"&&(s[c.taskId]=c.status)}catch{}if(r.length===0)return"";let n=new Set(["completed","deleted","failed"]),o=Object.keys(s).sort((e,c)=>Number(e)-Number(c)),u=[];for(let e=0;e<r.length;e++){let c=o[e],l=c?s[c]??"pending":"pending";n.has(l)||u.push(r[e])}if(u.length===0)return"";let i=[];for(let e of u)i.push(` [pending] ${a(e)}`);return i.join(`
12
+ `)}function J(t,r){let s=B(t);if(!s)return"";let n=[];for(let e of t)try{let c=JSON.parse(e.data);typeof c.subject=="string"&&n.push(c.subject)}catch{}let o=h(n);return[` <task_state count="${s.split(`
13
13
  `).length}">`,s,m(r,o)," </task_state>"].join(`
14
14
  `)}function X(t,r,s){if(t.length===0&&r.length===0)return"";let n=[],o=[];if(t.length>0){let e=t[t.length-1];n.push(` cwd: ${a(e.data)}`),o.push("working directory")}for(let e of r)n.push(` ${a(e.data)}`),o.push(e.data);let u=h(o);return[" <environment>",...n,m(s,u)," </environment>"].join(`
15
15
  `)}function z(t,r){if(t.length===0)return"";let s=[],n=[];for(let i of t){let e=i.type==="subagent_completed"?"completed":i.type==="subagent_launched"?"launched":"unknown";s.push(` [${e}] ${a(i.data)}`),n.push(`subagent ${i.data}`)}let o=h(n);return[` <subagents count="${t.length}">`,...s,m(r,o)," </subagents>"].join(`
16
16
  `)}function G(t,r){if(t.length===0)return"";let s=new Map;for(let e of t){let c=e.data.split(":")[0].trim();s.set(c,(s.get(c)??0)+1)}let n=[],o=[];for(let[e,c]of s)n.push(` ${a(e)} (${c}\xD7)`),o.push(`skill ${e} invocation`);let u=h(o);return[` <skills count="${t.length}">`,...n,m(r,u)," </skills>"].join(`
17
- `)}function Q(t){if(t.length===0)return"";let r=t[t.length-1];return` <intent mode="${a(r.data)}"/>`}function H(t,r){let s=r?.compactCount??1,n=r?.searchTool??"ctx_search",o=new Date().toISOString(),u=[],i=[],e=[],c=[],l=[],b=[],p=[],g=[],S=[],y=[],k=[];for(let d of t)switch(d.category){case"file":u.push(d);break;case"task":i.push(d);break;case"rule":e.push(d);break;case"decision":c.push(d);break;case"cwd":l.push(d);break;case"error":b.push(d);break;case"env":p.push(d);break;case"git":g.push(d);break;case"subagent":S.push(d);break;case"intent":y.push(d);break;case"skill":k.push(d);break}let f=[];f.push(` <how_to_search>
17
+ `)}function P(t){if(t.length===0)return"";let r=t[t.length-1];return` <intent mode="${a(r.data)}"/>`}function V(t,r){let s=r?.compactCount??1,n=r?.searchTool??"ctx_search",o=new Date().toISOString(),u=[],i=[],e=[],c=[],l=[],b=[],p=[],g=[],S=[],y=[],k=[];for(let d of t)switch(d.category){case"file":u.push(d);break;case"task":i.push(d);break;case"rule":e.push(d);break;case"decision":c.push(d);break;case"cwd":l.push(d);break;case"error":b.push(d);break;case"env":p.push(d);break;case"git":g.push(d);break;case"subagent":S.push(d);break;case"intent":y.push(d);break;case"skill":k.push(d);break}let f=[];f.push(` <how_to_search>
18
18
  Each section below contains a summary of prior work.
19
19
  For FULL DETAILS, run the exact tool call shown under each section.
20
20
  Do NOT ask the user to re-explain prior work. Search first.
21
21
  Do NOT invent your own queries \u2014 use the ones provided.
22
- </how_to_search>`);let $=x(u,n);$&&f.push($);let v=M(b,n);v&&f.push(v);let w=A(c,n);w&&f.push(w);let E=D(e,n);E&&f.push(E);let q=F(g,n);q&&f.push(q);let L=J(i,n);L&&f.push(L);let _=X(l,p,n);_&&f.push(_);let j=z(S,n);j&&f.push(j);let T=G(k,n);T&&f.push(T);let O=Q(y);O&&f.push(O);let B=`<session_resume events="${t.length}" compact_count="${s}" generated_at="${o}">`,C="</session_resume>",I=f.join(`
22
+ </how_to_search>`);let $=A(u,n);$&&f.push($);let v=x(b,n);v&&f.push(v);let w=D(c,n);w&&f.push(w);let E=F(e,n);E&&f.push(E);let q=R(g,n);q&&f.push(q);let L=J(i,n);L&&f.push(L);let _=X(l,p,n);_&&f.push(_);let j=z(S,n);j&&f.push(j);let T=G(k,n);T&&f.push(T);let C=P(y);C&&f.push(C);let O=`<session_resume events="${t.length}" compact_count="${s}" generated_at="${o}">`,I="</session_resume>",N=f.join(`
23
23
 
24
- `);return I?`${B}
24
+ `);return N?`${O}
25
25
 
26
- ${I}
26
+ ${N}
27
27
 
28
- ${C}`:`${B}
29
- ${C}`}export{H as buildResumeSnapshot,R as renderTaskState};
28
+ ${I}`:`${O}
29
+ ${I}`}export{V as buildResumeSnapshot,B as renderTaskState};
@@ -28,18 +28,28 @@ if (isBun) {
28
28
  } else {
29
29
  try {
30
30
  Database = (await import("better-sqlite3")).default;
31
- } catch {
32
- console.error("\n Error: better-sqlite3 not found.");
33
- console.error(" Install it: npm install better-sqlite3");
34
- console.error(" Or use Bun: bun insight/server.mjs\n");
31
+ // Verify native addon loads correctly (catches arch mismatch: x86_64 vs arm64)
32
+ const testDb = new Database(":memory:");
33
+ testDb.close();
34
+ } catch (err) {
35
+ const msg = err instanceof Error ? err.message : String(err);
36
+ console.error("\n Error: better-sqlite3 failed to load.");
37
+ console.error(` ${msg}`);
38
+ if (msg.includes("incompatible architecture") || msg.includes("dlopen")) {
39
+ const cacheHint = process.env.INSIGHT_SESSION_DIR
40
+ ? join(dirname(process.env.INSIGHT_SESSION_DIR), "insight-cache", "node_modules")
41
+ : join("~", ".claude", "context-mode", "insight-cache", "node_modules");
42
+ console.error(`\n Fix: rm -rf ${cacheHint} && context-mode insight`);
43
+ } else {
44
+ console.error(" Install it: npm install better-sqlite3");
45
+ }
35
46
  process.exit(1);
36
47
  }
37
48
  }
38
49
 
39
50
  // ── Paths ────────────────────────────────────────────────
40
- const BASE = join(homedir(), ".claude", "context-mode");
41
- const CONTENT_DIR = join(BASE, "content");
42
- const SESSION_DIR = join(BASE, "sessions");
51
+ const SESSION_DIR = process.env.INSIGHT_SESSION_DIR || join(homedir(), ".claude", "context-mode", "sessions");
52
+ const CONTENT_DIR = process.env.INSIGHT_CONTENT_DIR || join(homedir(), ".claude", "context-mode", "content");
43
53
  const DIST_DIR = join(__dirname, "dist");
44
54
 
45
55
  // ── SQLite helpers ───────────────────────────────────────
@@ -103,6 +113,11 @@ function mergeByKey(arr, key, mergeFn) {
103
113
  return [...map.values()];
104
114
  }
105
115
 
116
+ // ── Input validation ────────────────────────────────────
117
+ function isValidHash(hash) {
118
+ return /^[a-f0-9_]+$/.test(hash);
119
+ }
120
+
106
121
  // ── API Handlers ─────────────────────────────────────────
107
122
 
108
123
  function apiOverview() {
@@ -530,6 +545,7 @@ function route(method, pathname, params) {
530
545
 
531
546
  if (pathname.startsWith("/api/content/") && pathname.includes("/chunks/")) {
532
547
  const parts = pathname.split("/");
548
+ if (!isValidHash(parts[3])) return { error: "invalid hash" };
533
549
  return apiSourceChunks(parts[3], Number(parts[5]));
534
550
  }
535
551
  if (pathname === "/api/search") {
@@ -539,10 +555,12 @@ function route(method, pathname, params) {
539
555
  }
540
556
  if (pathname.startsWith("/api/sessions/") && pathname.includes("/events/")) {
541
557
  const parts = pathname.split("/");
558
+ if (!isValidHash(parts[3])) return { error: "invalid hash" };
542
559
  return apiSessionEvents(parts[3], decodeURIComponent(parts[5]));
543
560
  }
544
561
  if (method === "DELETE" && pathname.startsWith("/api/content/")) {
545
562
  const parts = pathname.split("/");
563
+ if (!isValidHash(parts[3])) return { error: "invalid hash" };
546
564
  return apiDeleteSource(parts[3], Number(parts[5]));
547
565
  }
548
566
  return null;
@@ -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.88",
6
+ "version": "1.0.89",
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.88",
3
+ "version": "1.0.89",
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",