context-mode 1.0.103 → 1.0.104

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 (97) 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 +66 -5
  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 +59 -16
  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 +6 -0
  45. package/build/opencode-plugin.js +60 -1
  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 +42 -0
  55. package/build/server.js +693 -164
  56. package/build/session/analytics.d.ts +49 -1
  57. package/build/session/analytics.js +278 -16
  58. package/build/session/db.d.ts +39 -8
  59. package/build/session/db.js +170 -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 +201 -159
  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 +5 -0
  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 +33 -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 +164 -125
  96. package/skills/ctx-insight/SKILL.md +1 -1
  97. package/start.mjs +63 -3
@@ -16,7 +16,7 @@ import { createToolNamer } from "../core/tool-naming.mjs";
16
16
 
17
17
  const toolNamer = createToolNamer("gemini-cli");
18
18
  const ROUTING_BLOCK = createRoutingBlock(toolNamer);
19
- import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
19
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents } from "../session-directive.mjs";
20
20
  import {
21
21
  readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
22
22
  getProjectDir, GEMINI_OPTS,
@@ -63,7 +63,12 @@ try {
63
63
  const dbPath = getSessionDBPath(OPTS);
64
64
  const db = new SessionDB({ dbPath });
65
65
 
66
- const events = getLatestSessionEvents(db);
66
+ // Filter events to the session being resumed. Falling back to
67
+ // getLatestSessionEvents(db) leaks events from any other session whose
68
+ // session_meta.started_at is more recent — observed cross-session bleed
69
+ // when a different session started after this one and before the resume.
70
+ const sessionId = getSessionId(input, OPTS);
71
+ const events = sessionId ? getSessionEvents(db, sessionId) : [];
67
72
  if (events.length > 0) {
68
73
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
69
74
  additionalContext += buildSessionDirective("resume", eventMeta, toolNamer);
@@ -17,7 +17,7 @@ import { createToolNamer } from "../core/tool-naming.mjs";
17
17
 
18
18
  const toolNamer = createToolNamer("jetbrains-copilot");
19
19
  const ROUTING_BLOCK = createRoutingBlock(toolNamer);
20
- import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
20
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents } from "../session-directive.mjs";
21
21
  import {
22
22
  readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
23
23
  getProjectDir, JETBRAINS_OPTS,
@@ -63,7 +63,12 @@ try {
63
63
  const dbPath = getSessionDBPath(OPTS);
64
64
  const db = new SessionDB({ dbPath });
65
65
 
66
- const events = getLatestSessionEvents(db);
66
+ // Filter events to the session being resumed. Falling back to
67
+ // getLatestSessionEvents(db) leaks events from any other session whose
68
+ // session_meta.started_at is more recent — observed cross-session bleed
69
+ // when a different session started after this one and before the resume.
70
+ const sessionId = getSessionId(input, OPTS);
71
+ const events = sessionId ? getSessionEvents(db, sessionId) : [];
67
72
  if (events.length > 0) {
68
73
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
69
74
  additionalContext += buildSessionDirective("resume", eventMeta, toolNamer);
@@ -0,0 +1,184 @@
1
+ // normalize-hooks.mjs — fixes #378
2
+ //
3
+ // Static committed files (hooks/hooks.json, .claude-plugin/plugin.json) ship
4
+ // with `${CLAUDE_PLUGIN_ROOT}` placeholder + bare `node` command. On Windows
5
+ // + Claude Code this triggers cjs/loader:1479 errors because:
6
+ // 1. bare `node` may not resolve via PATH (Git Bash, see #369)
7
+ // 2. `${CLAUDE_PLUGIN_ROOT}` resolution can hit MSYS path mangling (#372)
8
+ // 3. backslash paths get corrupted in shell quoting
9
+ //
10
+ // Our buildNodeCommand() fix handles dynamically-generated settings.json but
11
+ // not the static committed files. Solution: start.mjs detects the placeholder
12
+ // pattern on every MCP boot and rewrites with absolute paths using
13
+ // process.execPath + forward slashes. Idempotent — only rewrites when needed.
14
+ // Survives upgrades because it runs at every start.
15
+
16
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { resolve } from "node:path";
18
+
19
+ const PLACEHOLDER = "${CLAUDE_PLUGIN_ROOT}";
20
+
21
+ /** Convert any path string to forward slashes (MSYS-safe). */
22
+ function fwd(p) {
23
+ return String(p).replace(/\\/g, "/");
24
+ }
25
+
26
+ /**
27
+ * Pure detection: does this content contain an unresolved CLAUDE_PLUGIN_ROOT
28
+ * placeholder that should be normalized?
29
+ */
30
+ export function needsHookNormalization(content) {
31
+ if (!content || typeof content !== "string") return false;
32
+ return content.includes(PLACEHOLDER);
33
+ }
34
+
35
+ /**
36
+ * Rewrite hooks.json content. Replaces:
37
+ * - `node "${CLAUDE_PLUGIN_ROOT}/x.mjs"` →
38
+ * `"<execPath>" "<pluginRoot>/x.mjs"` (forward slashes, double-quoted)
39
+ *
40
+ * Pure function — takes content + paths, returns new content.
41
+ * Idempotent — leaves already-normalized content unchanged.
42
+ */
43
+ export function normalizeHooksJson(content, nodePath, pluginRoot) {
44
+ if (!needsHookNormalization(content)) return content;
45
+
46
+ const safeNode = fwd(nodePath);
47
+ const safeRoot = fwd(pluginRoot);
48
+
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(content);
52
+ } catch {
53
+ return content;
54
+ }
55
+
56
+ const hooks = parsed?.hooks;
57
+ if (!hooks || typeof hooks !== "object") return content;
58
+
59
+ let mutated = false;
60
+ for (const eventName of Object.keys(hooks)) {
61
+ const matchers = hooks[eventName];
62
+ if (!Array.isArray(matchers)) continue;
63
+ for (const matcher of matchers) {
64
+ const inner = matcher?.hooks;
65
+ if (!Array.isArray(inner)) continue;
66
+ for (const h of inner) {
67
+ if (typeof h?.command !== "string") continue;
68
+ if (!h.command.includes(PLACEHOLDER)) continue;
69
+ // Replace placeholder with absolute root (forward-slash).
70
+ let next = h.command.replaceAll(PLACEHOLDER, safeRoot);
71
+ // Replace bare `node ` prefix with quoted execPath. Match both
72
+ // `node ` and `node\t` at start, with optional surrounding whitespace.
73
+ next = next.replace(/^\s*node\s+/, `"${safeNode}" `);
74
+ h.command = next;
75
+ mutated = true;
76
+ }
77
+ }
78
+ }
79
+
80
+ if (!mutated) return content;
81
+
82
+ // Preserve 2-space indent (matches committed format).
83
+ return JSON.stringify(parsed, null, 2);
84
+ }
85
+
86
+ /**
87
+ * Rewrite plugin.json mcpServers. Replaces:
88
+ * - `command: "node"` → `command: "<execPath-fwd>"`
89
+ * - `args: ["${CLAUDE_PLUGIN_ROOT}/start.mjs"]` →
90
+ * `args: ["<pluginRoot-fwd>/start.mjs"]`
91
+ *
92
+ * Idempotent.
93
+ */
94
+ export function normalizePluginJson(content, nodePath, pluginRoot) {
95
+ if (!needsHookNormalization(content)) return content;
96
+
97
+ const safeNode = fwd(nodePath);
98
+ const safeRoot = fwd(pluginRoot);
99
+
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(content);
103
+ } catch {
104
+ return content;
105
+ }
106
+
107
+ const servers = parsed?.mcpServers;
108
+ if (!servers || typeof servers !== "object") return content;
109
+
110
+ let mutated = false;
111
+ for (const name of Object.keys(servers)) {
112
+ const srv = servers[name];
113
+ if (!srv || typeof srv !== "object") continue;
114
+
115
+ if (Array.isArray(srv.args)) {
116
+ const before = srv.args;
117
+ const after = before.map((a) =>
118
+ typeof a === "string" && a.includes(PLACEHOLDER)
119
+ ? a.replaceAll(PLACEHOLDER, safeRoot)
120
+ : a,
121
+ );
122
+ if (after.some((v, i) => v !== before[i])) {
123
+ srv.args = after;
124
+ mutated = true;
125
+ }
126
+ }
127
+
128
+ if (srv.command === "node" && mutated) {
129
+ // Only swap bare `node` when we also rewrote args — otherwise we'd
130
+ // touch user-customized server entries unrelated to placeholders.
131
+ srv.command = safeNode;
132
+ }
133
+ }
134
+
135
+ if (!mutated) return content;
136
+ return JSON.stringify(parsed, null, 2);
137
+ }
138
+
139
+ /**
140
+ * Apply normalization to hooks.json and plugin.json on startup.
141
+ *
142
+ * Options:
143
+ * - pluginRoot: absolute path to plugin install dir (e.g. __dirname of start.mjs)
144
+ * - nodePath: process.execPath
145
+ * - platform: process.platform (only "win32" triggers a write)
146
+ *
147
+ * Best-effort — never throws.
148
+ */
149
+ export function normalizeHooksOnStartup({ pluginRoot, nodePath, platform }) {
150
+ if (platform !== "win32") return;
151
+ if (!pluginRoot || !nodePath) return;
152
+
153
+ // hooks/hooks.json
154
+ try {
155
+ const hooksPath = resolve(pluginRoot, "hooks", "hooks.json");
156
+ if (existsSync(hooksPath)) {
157
+ const original = readFileSync(hooksPath, "utf-8");
158
+ if (needsHookNormalization(original)) {
159
+ const next = normalizeHooksJson(original, nodePath, pluginRoot);
160
+ if (next !== original) {
161
+ writeFileSync(hooksPath, next, "utf-8");
162
+ }
163
+ }
164
+ }
165
+ } catch {
166
+ /* best effort */
167
+ }
168
+
169
+ // .claude-plugin/plugin.json
170
+ try {
171
+ const pluginPath = resolve(pluginRoot, ".claude-plugin", "plugin.json");
172
+ if (existsSync(pluginPath)) {
173
+ const original = readFileSync(pluginPath, "utf-8");
174
+ if (needsHookNormalization(original)) {
175
+ const next = normalizePluginJson(original, nodePath, pluginRoot);
176
+ if (next !== original) {
177
+ writeFileSync(pluginPath, next, "utf-8");
178
+ }
179
+ }
180
+ }
181
+ } catch {
182
+ /* best effort */
183
+ }
184
+ }
@@ -1,4 +1,4 @@
1
- import{createRequire as b}from"node:module";import{existsSync as N,unlinkSync as y,renameSync as D}from"node:fs";import{tmpdir as O}from"node:os";import{join as A}from"node:path";var p=class{#t;constructor(t){this.#t=t}pragma(t){let e=this.#t.prepare(`PRAGMA ${t}`).all();if(!e||e.length===0)return;if(e.length>1)return e;let n=Object.values(e[0]);return n.length===1?n[0]:e[0]}exec(t){let s="",e=null;for(let o=0;o<t.length;o++){let a=t[o];if(e)s+=a,a===e&&(e=null);else if(a==="'"||a==='"')s+=a,e=a;else if(a===";"){let c=s.trim();c&&this.#t.prepare(c).run(),s=""}else s+=a}let n=s.trim();return n&&this.#t.prepare(n).run(),this}prepare(t){let s=this.#t.prepare(t);return{run:(...e)=>s.run(...e),get:(...e)=>{let n=s.get(...e);return n===null?void 0:n},all:(...e)=>s.all(...e),iterate:(...e)=>s.iterate(...e)}}transaction(t){return this.#t.transaction(t)}close(){this.#t.close()}},_=class{#t;constructor(t){this.#t=t}pragma(t){let e=this.#t.prepare(`PRAGMA ${t}`).all();if(!e||e.length===0)return;if(e.length>1)return e;let n=Object.values(e[0]);return n.length===1?n[0]:e[0]}exec(t){return this.#t.exec(t),this}prepare(t){let s=this.#t.prepare(t);return{run:(...e)=>s.run(...e),get:(...e)=>s.get(...e),all:(...e)=>s.all(...e),iterate:(...e)=>typeof s.iterate=="function"?s.iterate(...e):s.all(...e)[Symbol.iterator]()}}transaction(t){return(...s)=>{this.#t.exec("BEGIN");try{let e=t(...s);return this.#t.exec("COMMIT"),e}catch(e){throw this.#t.exec("ROLLBACK"),e}}}close(){this.#t.close()}},u=null;function w(){if(!u){let i=b(import.meta.url);if(globalThis.Bun){let t=i(["bun","sqlite"].join(":")).Database;u=function(e,n){let o=new t(e,{readonly:n?.readonly,create:!0}),a=new p(o);return n?.timeout&&a.pragma(`busy_timeout = ${n.timeout}`),a}}else if(process.platform==="linux")try{let{DatabaseSync:t}=i(["node","sqlite"].join(":"));u=function(e,n){let o=new t(e,{readOnly:n?.readonly??!1});return new _(o)}}catch{u=i("better-sqlite3")}else u=i("better-sqlite3")}return u}function T(i){i.pragma("journal_mode = WAL"),i.pragma("synchronous = NORMAL");try{i.pragma("mmap_size = 268435456")}catch{}}function h(i){if(!N(i))for(let t of["-wal","-shm"])try{y(i+t)}catch{}}function C(i){for(let t of["","-wal","-shm"])try{y(i+t)}catch{}}function l(i){try{i.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{i.close()}catch{}}function f(i="context-mode"){return A(O(),`${i}-${process.pid}.db`)}function I(i,t=[100,500,2e3]){let s;for(let e=0;e<=t.length;e++)try{return i()}catch(n){let o=n instanceof Error?n.message:String(n);if(!o.includes("SQLITE_BUSY")&&!o.includes("database is locked"))throw n;if(s=n instanceof Error?n:new Error(o),e<t.length){let a=t[e],c=Date.now();for(;Date.now()-c<a;);}}throw new Error(`SQLITE_BUSY: database is locked after ${t.length} retries. Original error: ${s?.message}`)}function U(i){return i.includes("SQLITE_CORRUPT")||i.includes("SQLITE_NOTADB")||i.includes("database disk image is malformed")||i.includes("file is not a database")}function M(i){let t=Date.now();for(let s of["","-wal","-shm"])try{D(i+s,`${i}${s}.corrupt-${t}`)}catch{}}var d=Symbol.for("__context_mode_live_dbs__"),m=(()=>{let i=globalThis;return i[d]||(i[d]=new Set,process.on("exit",()=>{for(let t of i[d])l(t);i[d].clear()})),i[d]})(),E=class{#t;#e;constructor(t){let s=w();this.#t=t,h(t);let e;try{e=new s(t,{timeout:3e4}),T(e)}catch(n){let o=n instanceof Error?n.message:String(n);if(U(o)){M(t),h(t);try{e=new s(t,{timeout:3e4}),T(e)}catch(a){throw new Error(`Failed to create fresh DB after renaming corrupt file: ${a instanceof Error?a.message:String(a)}`)}}else throw n}this.#e=e,m.add(this.#e),this.initSchema(),this.prepareStatements()}get db(){return this.#e}get dbPath(){return this.#t}close(){m.delete(this.#e),l(this.#e)}withRetry(t){return I(t)}cleanup(){m.delete(this.#e),l(this.#e),C(this.#t)}};import{createHash as R}from"node:crypto";import{execFileSync as x}from"node:child_process";function K(){let i=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(i!==void 0)return i?`__${i}`:"";try{let t=process.cwd(),s=x("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(e=>e.startsWith("worktree "))?.replace("worktree ","")?.trim();if(s&&t!==s)return`__${R("sha256").update(t).digest("hex").slice(0,8)}`}catch{}return""}var k=1e3,F=5,r={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",getLatestAttributedProject:"getLatestAttributedProject",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",searchEvents:"searchEvents"},L=class extends E{constructor(t){super(t?.dbPath??f("session"))}stmt(t){return this.stmts.get(t)}initSchema(){try{let s=this.db.pragma("table_xinfo(session_events)").find(e=>e.name==="data_hash");s&&s.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
1
+ import{createRequire as I}from"node:module";import{existsSync as U,unlinkSync as v,renameSync as M}from"node:fs";import{tmpdir as x}from"node:os";import{join as F}from"node:path";var g=class{#t;constructor(t){this.#t=t}pragma(t){let e=this.#t.prepare(`PRAGMA ${t}`).all();if(!e||e.length===0)return;if(e.length>1)return e;let r=Object.values(e[0]);return r.length===1?r[0]:e[0]}exec(t){let s="",e=null;for(let o=0;o<t.length;o++){let a=t[o];if(e)s+=a,a===e&&(e=null);else if(a==="'"||a==='"')s+=a,e=a;else if(a===";"){let c=s.trim();c&&this.#t.prepare(c).run(),s=""}else s+=a}let r=s.trim();return r&&this.#t.prepare(r).run(),this}prepare(t){let s=this.#t.prepare(t);return{run:(...e)=>s.run(...e),get:(...e)=>{let r=s.get(...e);return r===null?void 0:r},all:(...e)=>s.all(...e),iterate:(...e)=>s.iterate(...e)}}transaction(t){return this.#t.transaction(t)}close(){this.#t.close()}},h=class{#t;constructor(t){this.#t=t}pragma(t){let e=this.#t.prepare(`PRAGMA ${t}`).all();if(!e||e.length===0)return;if(e.length>1)return e;let r=Object.values(e[0]);return r.length===1?r[0]:e[0]}exec(t){return this.#t.exec(t),this}prepare(t){let s=this.#t.prepare(t);return{run:(...e)=>s.run(...e),get:(...e)=>s.get(...e),all:(...e)=>s.all(...e),iterate:(...e)=>typeof s.iterate=="function"?s.iterate(...e):s.all(...e)[Symbol.iterator]()}}transaction(t){return(...s)=>{this.#t.exec("BEGIN");try{let e=t(...s);return this.#t.exec("COMMIT"),e}catch(e){throw this.#t.exec("ROLLBACK"),e}}}close(){this.#t.close()}},d=null;function B(){if(!d){let i=I(import.meta.url);if(globalThis.Bun){let t=i(["bun","sqlite"].join(":")).Database;d=function(e,r){let o=new t(e,{readonly:r?.readonly,create:!0}),a=new g(o);return r?.timeout&&a.pragma(`busy_timeout = ${r.timeout}`),a}}else if(process.platform==="linux")try{let{DatabaseSync:t}=i(["node","sqlite"].join(":"));d=function(e,r){let o=new t(e,{readOnly:r?.readonly??!1});return new h(o)}}catch{d=i("better-sqlite3")}else d=i("better-sqlite3")}return d}function b(i){i.pragma("journal_mode = WAL"),i.pragma("synchronous = NORMAL");try{i.pragma("mmap_size = 268435456")}catch{}}function N(i){if(!U(i))for(let t of["-wal","-shm"])try{v(i+t)}catch{}}function P(i){for(let t of["","-wal","-shm"])try{v(i+t)}catch{}}function y(i){try{i.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{i.close()}catch{}}function C(i="context-mode"){return F(x(),`${i}-${process.pid}.db`)}function k(i,t=[100,500,2e3]){let s;for(let e=0;e<=t.length;e++)try{return i()}catch(r){let o=r instanceof Error?r.message:String(r);if(!o.includes("SQLITE_BUSY")&&!o.includes("database is locked"))throw r;if(s=r instanceof Error?r:new Error(o),e<t.length){let a=t[e],c=Date.now();for(;Date.now()-c<a;);}}throw new Error(`SQLITE_BUSY: database is locked after ${t.length} retries. Original error: ${s?.message}`)}function j(i){return i.includes("SQLITE_CORRUPT")||i.includes("SQLITE_NOTADB")||i.includes("database disk image is malformed")||i.includes("file is not a database")}function X(i){let t=Date.now();for(let s of["","-wal","-shm"])try{M(i+s,`${i}${s}.corrupt-${t}`)}catch{}}var m=Symbol.for("__context_mode_live_dbs__"),p=(()=>{let i=globalThis;return i[m]||(i[m]=new Set,process.on("exit",()=>{for(let t of i[m])y(t);i[m].clear()})),i[m]})(),T=class{#t;#e;constructor(t){let s=B();this.#t=t,N(t);let e;try{e=new s(t,{timeout:3e4}),b(e)}catch(r){let o=r instanceof Error?r.message:String(r);if(j(o)){X(t),N(t);try{e=new s(t,{timeout:3e4}),b(e)}catch(a){throw new Error(`Failed to create fresh DB after renaming corrupt file: ${a instanceof Error?a.message:String(a)}`)}}else throw r}this.#e=e,p.add(this.#e),this.initSchema(),this.prepareStatements()}get db(){return this.#e}get dbPath(){return this.#t}close(){p.delete(this.#e),y(this.#e)}withRetry(t){return k(t)}cleanup(){p.delete(this.#e),y(this.#e),P(this.#t)}};import{createHash as f}from"node:crypto";import{execFileSync as W}from"node:child_process";var l;function z(){let i=process.env.CONTEXT_MODE_SESSION_SUFFIX,t=process.cwd();if(l&&l.cwd===t&&l.envSuffix===i)return l.suffix;let s="";if(i!==void 0)s=i?`__${i}`:"";else try{let e=W("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(r=>r.startsWith("worktree "))?.replace("worktree ","")?.trim();e&&t!==e&&(s=`__${f("sha256").update(t).digest("hex").slice(0,8)}`)}catch{}return l={cwd:t,envSuffix:i,suffix:s},s}function J(){l=void 0}var O=1e3,D=5,n={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",getLatestAttributedProject:"getLatestAttributedProject",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",searchEvents:"searchEvents",incrementToolCall:"incrementToolCall",getToolCallTotals:"getToolCallTotals",getToolCallByTool:"getToolCallByTool"},A=class extends T{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(e=>e.name==="data_hash");s&&s.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
2
2
  CREATE TABLE IF NOT EXISTS session_events (
3
3
  id INTEGER PRIMARY KEY AUTOINCREMENT,
4
4
  session_id TEXT NOT NULL,
@@ -35,48 +35,67 @@ import{createRequire as b}from"node:module";import{existsSync as N,unlinkSync as
35
35
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
36
36
  consumed INTEGER NOT NULL DEFAULT 0
37
37
  );
38
- `);try{let t=this.db.pragma("table_xinfo(session_events)"),s=new Set(t.map(e=>e.name));s.has("project_dir")||this.db.exec("ALTER TABLE session_events ADD COLUMN project_dir TEXT NOT NULL DEFAULT ''"),s.has("attribution_source")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_source TEXT NOT NULL DEFAULT 'unknown'"),s.has("attribution_confidence")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_confidence REAL NOT NULL DEFAULT 0"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_session_events_project ON session_events(session_id, project_dir)")}catch{}}prepareStatements(){this.stmts=new Map;let t=(s,e)=>{this.stmts.set(s,this.db.prepare(e))};t(r.insertEvent,`INSERT INTO session_events (
38
+
39
+ CREATE TABLE IF NOT EXISTS tool_calls (
40
+ session_id TEXT NOT NULL,
41
+ tool TEXT NOT NULL,
42
+ calls INTEGER NOT NULL DEFAULT 0,
43
+ bytes_returned INTEGER NOT NULL DEFAULT 0,
44
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
45
+ PRIMARY KEY (session_id, tool)
46
+ );
47
+
48
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);
49
+ `);try{let t=this.db.pragma("table_xinfo(session_events)"),s=new Set(t.map(e=>e.name));s.has("project_dir")||this.db.exec("ALTER TABLE session_events ADD COLUMN project_dir TEXT NOT NULL DEFAULT ''"),s.has("attribution_source")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_source TEXT NOT NULL DEFAULT 'unknown'"),s.has("attribution_confidence")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_confidence REAL NOT NULL DEFAULT 0"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_session_events_project ON session_events(session_id, project_dir)")}catch{}}prepareStatements(){this.stmts=new Map;let t=(s,e)=>{this.stmts.set(s,this.db.prepare(e))};t(n.insertEvent,`INSERT INTO session_events (
39
50
  session_id, type, category, priority, data,
40
51
  project_dir, attribution_source, attribution_confidence,
41
52
  source_hook, data_hash
42
53
  )
43
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),t(r.getEvents,`SELECT id, session_id, type, category, priority, data,
54
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),t(n.getEvents,`SELECT id, session_id, type, category, priority, data,
44
55
  project_dir, attribution_source, attribution_confidence,
45
56
  source_hook, created_at, data_hash
46
- FROM session_events WHERE session_id = ? ORDER BY id ASC LIMIT ?`),t(r.getEventsByType,`SELECT id, session_id, type, category, priority, data,
57
+ FROM session_events WHERE session_id = ? ORDER BY id ASC LIMIT ?`),t(n.getEventsByType,`SELECT id, session_id, type, category, priority, data,
47
58
  project_dir, attribution_source, attribution_confidence,
48
59
  source_hook, created_at, data_hash
49
- FROM session_events WHERE session_id = ? AND type = ? ORDER BY id ASC LIMIT ?`),t(r.getEventsByPriority,`SELECT id, session_id, type, category, priority, data,
60
+ FROM session_events WHERE session_id = ? AND type = ? ORDER BY id ASC LIMIT ?`),t(n.getEventsByPriority,`SELECT id, session_id, type, category, priority, data,
50
61
  project_dir, attribution_source, attribution_confidence,
51
62
  source_hook, created_at, data_hash
52
- FROM session_events WHERE session_id = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(r.getEventsByTypeAndPriority,`SELECT id, session_id, type, category, priority, data,
63
+ FROM session_events WHERE session_id = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(n.getEventsByTypeAndPriority,`SELECT id, session_id, type, category, priority, data,
53
64
  project_dir, attribution_source, attribution_confidence,
54
65
  source_hook, created_at, data_hash
55
- FROM session_events WHERE session_id = ? AND type = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(r.getEventCount,"SELECT COUNT(*) AS cnt FROM session_events WHERE session_id = ?"),t(r.getLatestAttributedProject,`SELECT project_dir
66
+ FROM session_events WHERE session_id = ? AND type = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(n.getEventCount,"SELECT COUNT(*) AS cnt FROM session_events WHERE session_id = ?"),t(n.getLatestAttributedProject,`SELECT project_dir
56
67
  FROM session_events
57
68
  WHERE session_id = ? AND project_dir != ''
58
69
  ORDER BY id DESC
59
- LIMIT 1`),t(r.checkDuplicate,`SELECT 1 FROM (
70
+ LIMIT 1`),t(n.checkDuplicate,`SELECT 1 FROM (
60
71
  SELECT type, data_hash FROM session_events
61
72
  WHERE session_id = ? ORDER BY id DESC LIMIT ?
62
73
  ) AS recent
63
74
  WHERE recent.type = ? AND recent.data_hash = ?
64
- LIMIT 1`),t(r.evictLowestPriority,`DELETE FROM session_events WHERE id = (
75
+ LIMIT 1`),t(n.evictLowestPriority,`DELETE FROM session_events WHERE id = (
65
76
  SELECT id FROM session_events WHERE session_id = ?
66
77
  ORDER BY priority ASC, id ASC LIMIT 1
67
- )`),t(r.updateMetaLastEvent,`UPDATE session_meta
78
+ )`),t(n.updateMetaLastEvent,`UPDATE session_meta
68
79
  SET last_event_at = datetime('now'), event_count = event_count + 1
69
- WHERE session_id = ?`),t(r.ensureSession,"INSERT OR IGNORE INTO session_meta (session_id, project_dir) VALUES (?, ?)"),t(r.getSessionStats,`SELECT session_id, project_dir, started_at, last_event_at, event_count, compact_count
70
- FROM session_meta WHERE session_id = ?`),t(r.incrementCompactCount,"UPDATE session_meta SET compact_count = compact_count + 1 WHERE session_id = ?"),t(r.upsertResume,`INSERT INTO session_resume (session_id, snapshot, event_count)
80
+ WHERE session_id = ?`),t(n.ensureSession,"INSERT OR IGNORE INTO session_meta (session_id, project_dir) VALUES (?, ?)"),t(n.getSessionStats,`SELECT session_id, project_dir, started_at, last_event_at, event_count, compact_count
81
+ FROM session_meta WHERE session_id = ?`),t(n.incrementCompactCount,"UPDATE session_meta SET compact_count = compact_count + 1 WHERE session_id = ?"),t(n.upsertResume,`INSERT INTO session_resume (session_id, snapshot, event_count)
71
82
  VALUES (?, ?, ?)
72
83
  ON CONFLICT(session_id) DO UPDATE SET
73
84
  snapshot = excluded.snapshot,
74
85
  event_count = excluded.event_count,
75
86
  created_at = datetime('now'),
76
- consumed = 0`),t(r.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(r.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(r.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(r.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(r.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(r.searchEvents,`SELECT id, session_id, category, type, data, created_at
87
+ consumed = 0`),t(n.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(n.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(n.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(n.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(n.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(n.searchEvents,`SELECT id, session_id, category, type, data, created_at
77
88
  FROM session_events
78
89
  WHERE project_dir = ?
79
90
  AND (data LIKE '%' || ? || '%' ESCAPE '\\' OR category LIKE '%' || ? || '%' ESCAPE '\\')
80
91
  AND (? IS NULL OR category = ?)
81
92
  ORDER BY id ASC
82
- LIMIT ?`),t(r.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,s,e="PostToolUse",n){let o=R("sha256").update(s.data).digest("hex").slice(0,16).toUpperCase(),a=String(n?.projectDir??s.project_dir??"").trim(),c=String(n?.source??s.attribution_source??"unknown"),g=Number(n?.confidence??s.attribution_confidence??0),S=Number.isFinite(g)?Math.max(0,Math.min(1,g)):0,v=this.db.transaction(()=>{if(this.stmt(r.checkDuplicate).get(t,F,s.type,o))return;this.stmt(r.getEventCount).get(t).cnt>=k&&this.stmt(r.evictLowestPriority).run(t),this.stmt(r.insertEvent).run(t,s.type,s.category,s.priority,s.data,a,c,S,e,o),this.stmt(r.updateMetaLastEvent).run(t)});this.withRetry(()=>v())}getEvents(t,s){let e=s?.limit??1e3,n=s?.type,o=s?.minPriority;return n&&o!==void 0?this.stmt(r.getEventsByTypeAndPriority).all(t,n,o,e):n?this.stmt(r.getEventsByType).all(t,n,e):o!==void 0?this.stmt(r.getEventsByPriority).all(t,o,e):this.stmt(r.getEvents).all(t,e)}getEventCount(t){return this.stmt(r.getEventCount).get(t).cnt}getLatestAttributedProjectDir(t){return this.stmt(r.getLatestAttributedProject).get(t)?.project_dir||null}searchEvents(t,s,e,n){try{let o=t.replace(/[%_]/g,c=>"\\"+c),a=n??null;return this.stmt(r.searchEvents).all(e,o,o,a,a,s)}catch{return[]}}ensureSession(t,s){this.stmt(r.ensureSession).run(t,s)}getSessionStats(t){return this.stmt(r.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(r.incrementCompactCount).run(t)}upsertResume(t,s,e){this.stmt(r.upsertResume).run(t,s,e??0)}getResume(t){return this.stmt(r.getResume).get(t)??null}markResumeConsumed(t){this.stmt(r.markResumeConsumed).run(t)}deleteSession(t){this.db.transaction(()=>{this.stmt(r.deleteEvents).run(t),this.stmt(r.deleteResume).run(t),this.stmt(r.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let s=`-${t}`,e=this.stmt(r.getOldSessions).all(s);for(let{session_id:n}of e)this.deleteSession(n);return e.length}};export{L as SessionDB,K as getWorktreeSuffix};
93
+ LIMIT ?`),t(n.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')"),t(n.incrementToolCall,`INSERT INTO tool_calls (session_id, tool, calls, bytes_returned)
94
+ VALUES (?, ?, 1, ?)
95
+ ON CONFLICT(session_id, tool) DO UPDATE SET
96
+ calls = calls + 1,
97
+ bytes_returned = bytes_returned + excluded.bytes_returned,
98
+ updated_at = datetime('now')`),t(n.getToolCallTotals,`SELECT COALESCE(SUM(calls), 0) AS calls,
99
+ COALESCE(SUM(bytes_returned), 0) AS bytes_returned
100
+ FROM tool_calls WHERE session_id = ?`),t(n.getToolCallByTool,`SELECT tool, calls, bytes_returned
101
+ FROM tool_calls WHERE session_id = ? ORDER BY calls DESC`)}insertEvent(t,s,e="PostToolUse",r){let o=f("sha256").update(s.data).digest("hex").slice(0,16).toUpperCase(),a=String(r?.projectDir??s.project_dir??"").trim(),c=String(r?.source??s.attribution_source??"unknown"),u=Number(r?.confidence??s.attribution_confidence??0),_=Number.isFinite(u)?Math.max(0,Math.min(1,u)):0,E=this.db.transaction(()=>{if(this.stmt(n.checkDuplicate).get(t,D,s.type,o))return;this.stmt(n.getEventCount).get(t).cnt>=O&&this.stmt(n.evictLowestPriority).run(t),this.stmt(n.insertEvent).run(t,s.type,s.category,s.priority,s.data,a,c,_,e,o),this.stmt(n.updateMetaLastEvent).run(t)});this.withRetry(()=>E())}bulkInsertEvents(t,s,e="PostToolUse",r){if(!s||s.length===0)return;if(s.length===1){this.insertEvent(t,s[0],e,r?.[0]);return}let o=s.map((c,u)=>{let _=f("sha256").update(c.data).digest("hex").slice(0,16).toUpperCase(),E=r?.[u],S=String(E?.projectDir??c.project_dir??"").trim(),L=String(E?.source??c.attribution_source??"unknown"),R=Number(E?.confidence??c.attribution_confidence??0),w=Number.isFinite(R)?Math.max(0,Math.min(1,R)):0;return{event:c,dataHash:_,projectDir:S,attributionSource:L,attributionConfidence:w}}),a=this.db.transaction(()=>{let c=this.stmt(n.getEventCount).get(t).cnt;for(let u of o)this.stmt(n.checkDuplicate).get(t,D,u.event.type,u.dataHash)||(c>=O?this.stmt(n.evictLowestPriority).run(t):c++,this.stmt(n.insertEvent).run(t,u.event.type,u.event.category,u.event.priority,u.event.data,u.projectDir,u.attributionSource,u.attributionConfidence,e,u.dataHash));this.stmt(n.updateMetaLastEvent).run(t)});this.withRetry(()=>a())}getEvents(t,s){let e=s?.limit??1e3,r=s?.type,o=s?.minPriority;return r&&o!==void 0?this.stmt(n.getEventsByTypeAndPriority).all(t,r,o,e):r?this.stmt(n.getEventsByType).all(t,r,e):o!==void 0?this.stmt(n.getEventsByPriority).all(t,o,e):this.stmt(n.getEvents).all(t,e)}getEventCount(t){return this.stmt(n.getEventCount).get(t).cnt}getLatestAttributedProjectDir(t){return this.stmt(n.getLatestAttributedProject).get(t)?.project_dir||null}searchEvents(t,s,e,r){try{let o=t.replace(/[%_]/g,c=>"\\"+c),a=r??null;return this.stmt(n.searchEvents).all(e,o,o,a,a,s)}catch{return[]}}ensureSession(t,s){this.stmt(n.ensureSession).run(t,s)}getSessionStats(t){return this.stmt(n.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(n.incrementCompactCount).run(t)}upsertResume(t,s,e){this.stmt(n.upsertResume).run(t,s,e??0)}getResume(t){return this.stmt(n.getResume).get(t)??null}markResumeConsumed(t){this.stmt(n.markResumeConsumed).run(t)}getLatestSessionId(){try{return this.db.prepare("SELECT session_id FROM session_meta ORDER BY started_at DESC LIMIT 1").get()?.session_id??null}catch{return null}}incrementToolCall(t,s,e=0){let r=Number.isFinite(e)&&e>0?Math.round(e):0;try{this.stmt(n.incrementToolCall).run(t,s,r)}catch{}}getToolCallStats(t){try{let s=this.stmt(n.getToolCallTotals).get(t),e=this.stmt(n.getToolCallByTool).all(t),r={};for(let o of e)r[o.tool]={calls:o.calls,bytesReturned:o.bytes_returned};return{totalCalls:s?.calls??0,totalBytesReturned:s?.bytes_returned??0,byTool:r}}catch{return{totalCalls:0,totalBytesReturned:0,byTool:{}}}}deleteSession(t){this.db.transaction(()=>{this.stmt(n.deleteEvents).run(t),this.stmt(n.deleteResume).run(t),this.stmt(n.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let s=`-${t}`,e=this.stmt(n.getOldSessions).all(s);for(let{session_id:r}of e)this.deleteSession(r);return e.length}};export{A as SessionDB,J as _resetWorktreeSuffixCacheForTests,z as getWorktreeSuffix};
@@ -1,2 +1,2 @@
1
- function i(t){return t==null?"":String(t)}function g(t){return t==null?"":typeof t=="string"?t:JSON.stringify(t)}function b(t){let{tool_name:e,tool_input:o,tool_response:s}=t,r=[];if(e==="Read"){let n=String(o.file_path??"");return/CLAUDE\.md$|\.claude[\\/]/i.test(n)&&(r.push({type:"rule",category:"rule",data:i(n),priority:1}),s&&s.length>0&&r.push({type:"rule_content",category:"rule",data:i(s),priority:1})),r.push({type:"file_read",category:"file",data:i(n),priority:1}),r}if(e==="Edit"){let n=String(o.file_path??"");return r.push({type:"file_edit",category:"file",data:i(n),priority:1}),r}if(e==="NotebookEdit"){let n=String(o.notebook_path??"");return r.push({type:"file_edit",category:"file",data:i(n),priority:1}),r}if(e==="Write"){let n=String(o.file_path??"");return r.push({type:"file_write",category:"file",data:i(n),priority:1}),r}if(e==="Glob"){let n=String(o.pattern??"");return r.push({type:"file_glob",category:"file",data:i(n),priority:3}),r}if(e==="Grep"){let n=String(o.pattern??""),c=String(o.path??"");return r.push({type:"file_search",category:"file",data:i(`${n} in ${c}`),priority:3}),r}return r}function f(t){if(t.tool_name!=="Bash")return[];let o=String(t.tool_input.command??"").match(/\bcd\s+("([^"]+)"|'([^']+)'|(\S+))/);if(!o)return[];let s=o[2]??o[3]??o[4]??"";return[{type:"cwd",category:"cwd",data:i(s),priority:2}]}function y(t){let{tool_name:e,tool_input:o,tool_response:s,tool_output:r}=t,n=String(s??""),c=r?.isError===!0;return!(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(n))&&!c?[]:[{type:"error_tool",category:"error",data:i(n),priority:2}]}var h=[{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 _(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??""),o=h.find(s=>s.pattern.test(e));return o?[{type:"git",category:"git",data:i(o.operation),priority:2}]:[]}function m(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:i(JSON.stringify(t.tool_input)),priority:1}]:[]}function S(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=[],o=t.tool_input.allowedPrompts,s=Array.isArray(o)&&o.length>0?`exited plan mode (allowed: ${g(o.map(n=>typeof n=="object"&&n!==null&&"prompt"in n?String(n.prompt):String(n)).join(", "))})`:"exited plan mode";e.push({type:"plan_exit",category:"plan",data:i(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:i(`plan rejected: ${t.tool_response??""}`),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:i(`plan file: ${e.split(/[/\\]/).pop()??e}`),priority:2}]}return[]}var E=[/\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 k(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??"");if(!E.some(r=>r.test(e)))return[];let s=e.replace(/\bexport\s+(\w+)=\S*/g,"export $1=***");return[{type:"env",category:"env",data:i(s),priority:2}]}function v(t){if(t.tool_name!=="Skill")return[];let e=String(t.tool_input.skill??"");return[{type:"skill",category:"skill",data:i(e),priority:2}]}function x(t){if(!t.tool_response?.includes("Error")&&!t.tool_output?.isError)return[];let e=String(t.tool_response||""),o=[/not supported/i,/cannot/i,/does not support/i,/FAIL/i,/refused/i,/permission denied/i,/incompatible/i];for(let s of o){let r=e.match(s);if(r){let n=e.toLowerCase().indexOf(r[0].toLowerCase()),c=e.slice(Math.max(0,n-50),Math.min(e.length,n+200)).trim();return[{type:"constraint_discovered",category:"constraint",data:i(c),priority:2}]}}return[]}function w(t){if(t.tool_name!=="Agent")return[];let e=i(String(t.tool_input.prompt??t.tool_input.description??"")),o=t.tool_response?i(String(t.tool_response)):"",s=o.length>0;return[{type:s?"subagent_completed":"subagent_launched",category:"subagent",data:i(s?`[completed] ${e} \u2192 ${o}`:`[launched] ${e}`),priority:s?2:3}]}function R(t){let{tool_name:e,tool_input:o,tool_response:s}=t;if(!e.startsWith("mcp__"))return[];let r=e.split("__"),n=r[r.length-1]||e,c=Object.values(o).find(d=>typeof d=="string"),p=c?`: ${i(String(c))}`:"",u=s&&s.length>0?`
2
- response: ${i(s)}`:"";return[{type:"mcp",category:"mcp",data:i(`${n}${p}${u}`),priority:3}]}function A(t){if(t.tool_name!=="AskUserQuestion")return[];let e=t.tool_input.questions,o=Array.isArray(e)&&e.length>0?String(e[0].question??""):"",s=i(String(t.tool_response??"")),r=o?`Q: ${i(o)} \u2192 A: ${s}`:`answer: ${s}`;return[{type:"decision_question",category:"decision",data:i(r),priority:2}]}function I(t){if(t.tool_name!=="Agent")return[];if(!t.tool_response||t.tool_response.length===0)return[];let e=t.tool_response.length>500?t.tool_response.slice(0,500):t.tool_response;return[{type:"agent_finding",category:"agent-finding",data:i(e),priority:2}]}function T(t){let e=[g(t.tool_input),i(t.tool_response)].join(" ");if(e.length===0)return[];let o=new Set,s=e.match(/https?:\/\/[^\s)]+/g);if(s)for(let n of s)n=n.replace(/["'})\],;.]+$/,""),/localhost|127\.0\.0\.1/i.test(n)||o.add(n);let r=e.match(/(?<!\w)#(\d+)/g);if(r)for(let n of r)o.add(n);return o.size===0?[]:[{type:"external_ref",category:"external-ref",data:i(Array.from(o).join(", ")),priority:3}]}function $(t){if(t.tool_name!=="EnterWorktree")return[];let e=String(t.tool_input.name??"unnamed");return[{type:"worktree",category:"env",data:i(`entered worktree: ${e}`),priority:2}]}var H=[/\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 P(t){return H.some(o=>o.test(t))?[{type:"decision",category:"decision",data:i(t),priority:2}]:[]}var N=[/\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 C(t){return N.some(o=>o.test(t))?[{type:"role",category:"role",data:i(t),priority:3}]:[]}var L=[{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 B(t){let e=L.find(({pattern:o})=>o.test(t));return e?[{type:"intent",category:"intent",data:i(e.mode),priority:4}]:[]}var O=[/\bblocked on\b/i,/\bwaiting for\b/i,/\bneed\s+\S+\s+before\b/i,/\bcan'?t proceed until\b/i,/\bdepends on\b/i,/\bblocked\b/i,/\bbekliyor\b/i,/\bbekliyorum\b/i],F=[/\bunblocked\b/i,/\bresolved\b/i,/\bgot the\s+\S+/i,/\bis ready now\b/i,/\bcan proceed\b/i];function j(t){let e=[];return F.some(r=>r.test(t))?(e.push({type:"blocker_resolved",category:"blocked-on",data:i(t),priority:2}),e):(O.some(r=>r.test(t))&&e.push({type:"blocker",category:"blocked-on",data:i(t),priority:2}),e)}function D(t){return t.length<=1024?[]:[{type:"data",category:"data",data:i(t),priority:4}]}var a=null;function M(t){let{tool_name:e,tool_response:o,tool_output:s}=t,r=String(o??""),n=s?.isError===!0;if(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(r)||n)return a={tool:e,error:r.slice(0,200),callsSince:0},[];if(!a)return[];if(a.callsSince++,a.callsSince>10)return a=null,[];let p=e===a.tool,u=a.tool==="Read"&&(e==="Edit"||e==="Write");if(p||u){let d={type:"error_resolved",category:"error-resolution",data:i(`Error in ${a.tool}: ${a.error} \u2192 Fixed`),priority:2};return a=null,[d]}return[]}function q(){a=null}var l=[];function W(t){return`${t.length}:${t.slice(0,20)}`}function U(t){let{tool_name:e,tool_input:o}=t,s=W(JSON.stringify(o).slice(0,200));if(l.push({tool:e,inputHash:s}),l.length>50&&l.splice(0,l.length-50),l.length<3)return[];let r=0;for(let n=l.length-1;n>=0&&(l[n].tool===e&&l[n].inputHash===s);n--)r++;return r>=3?(l.splice(l.length-r),[{type:"retry_detected",category:"iteration-loop",data:i(`${e} called ${r} times with similar input`),priority:2}]):[]}function G(){l.length=0}function K(t){try{let e=[];return e.push(...b(t)),e.push(...f(t)),e.push(...y(t)),e.push(..._(t)),e.push(...k(t)),e.push(...m(t)),e.push(...S(t)),e.push(...v(t)),e.push(...w(t)),e.push(...R(t)),e.push(...A(t)),e.push(...x(t)),e.push(...$(t)),e.push(...I(t)),e.push(...T(t)),e.push(...M(t)),e.push(...U(t)),e}catch{return[]}}function z(t){try{let e=[];return e.push(...P(t)),e.push(...C(t)),e.push(...B(t)),e.push(...j(t)),e.push(...D(t)),e}catch{return[]}}export{K as extractEvents,z as extractUserEvents,q as resetErrorResolutionState,G as resetIterationLoopState};
1
+ function s(t){return t==null?"":String(t)}function f(t){return t==null?"":typeof t=="string"?t:JSON.stringify(t)}function b(t){let{tool_name:e,tool_input:o,tool_response:i}=t,r=[];if(e==="Read"){let n=String(o.file_path??"");return(/(?:CLAUDE|AGENTS(?:\.override)?|GEMINI|QWEN|KIRO)\.md$/i.test(n)||/\/copilot-instructions\.md$/i.test(n)||/\/context-mode\.mdc$/i.test(n)||/\.claude[\\/]/i.test(n)||/[\\/]memor(?:y|ies)[\\/][^\\/]+\.md$/i.test(n))&&(r.push({type:"rule",category:"rule",data:s(n),priority:1}),i&&i.length>0&&r.push({type:"rule_content",category:"rule",data:s(i),priority:1})),r.push({type:"file_read",category:"file",data:s(n),priority:1}),r}if(e==="Edit"){let n=String(o.file_path??"");return r.push({type:"file_edit",category:"file",data:s(n),priority:1}),r}if(e==="NotebookEdit"){let n=String(o.notebook_path??"");return r.push({type:"file_edit",category:"file",data:s(n),priority:1}),r}if(e==="Write"){let n=String(o.file_path??"");return r.push({type:"file_write",category:"file",data:s(n),priority:1}),r}if(e==="Glob"){let n=String(o.pattern??"");return r.push({type:"file_glob",category:"file",data:s(n),priority:3}),r}if(e==="Grep"){let n=String(o.pattern??""),a=String(o.path??"");return r.push({type:"file_search",category:"file",data:s(`${n} in ${a}`),priority:3}),r}return r}function y(t){if(t.tool_name!=="Bash")return[];let o=String(t.tool_input.command??"").match(/\bcd\s+("([^"]+)"|'([^']+)'|(\S+))/);if(!o)return[];let i=o[2]??o[3]??o[4]??"";return[{type:"cwd",category:"cwd",data:s(i),priority:2}]}function h(t){let{tool_name:e,tool_input:o,tool_response:i,tool_output:r}=t,n=String(i??""),a=r?.isError===!0;return!(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(n))&&!a?[]:[{type:"error_tool",category:"error",data:s(n),priority:2}]}var _=[{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 m(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??""),o=_.find(i=>i.pattern.test(e));return o?[{type:"git",category:"git",data:s(o.operation),priority:2}]:[]}function S(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:s(JSON.stringify(t.tool_input)),priority:1}]:[]}function E(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=[],o=t.tool_input.allowedPrompts,i=Array.isArray(o)&&o.length>0?`exited plan mode (allowed: ${f(o.map(n=>typeof n=="object"&&n!==null&&"prompt"in n?String(n.prompt):String(n)).join(", "))})`:"exited plan mode";e.push({type:"plan_exit",category:"plan",data:s(i),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:s(`plan rejected: ${t.tool_response??""}`),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:s(`plan file: ${e.split(/[/\\]/).pop()??e}`),priority:2}]}return[]}var k=[/\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 v(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??"");if(!k.some(r=>r.test(e)))return[];let i=e.replace(/\bexport\s+(\w+)=\S*/g,"export $1=***");return[{type:"env",category:"env",data:s(i),priority:2}]}function x(t){if(t.tool_name!=="Skill")return[];let e=String(t.tool_input.skill??"");return[{type:"skill",category:"skill",data:s(e),priority:2}]}function w(t){if(!t.tool_response?.includes("Error")&&!t.tool_output?.isError)return[];let e=String(t.tool_response||""),o=[/not supported/i,/cannot/i,/does not support/i,/FAIL/i,/refused/i,/permission denied/i,/incompatible/i];for(let i of o){let r=e.match(i);if(r){let n=e.toLowerCase().indexOf(r[0].toLowerCase()),a=e.slice(Math.max(0,n-50),Math.min(e.length,n+200)).trim();return[{type:"constraint_discovered",category:"constraint",data:s(a),priority:2}]}}return[]}function R(t){if(t.tool_name!=="Agent")return[];let e=s(String(t.tool_input.prompt??t.tool_input.description??"")),o=t.tool_response?s(String(t.tool_response)):"",i=o.length>0;return[{type:i?"subagent_completed":"subagent_launched",category:"subagent",data:s(i?`[completed] ${e} \u2192 ${o}`:`[launched] ${e}`),priority:i?2:3}]}function T(t){let{tool_name:e,tool_input:o,tool_response:i}=t;if(!e.startsWith("mcp__"))return[];let r=e.split("__"),n=r[r.length-1]||e,a=Object.values(o).find(d=>typeof d=="string"),p=a?`: ${s(String(a))}`:"",u=i&&i.length>0?`
2
+ response: ${s(i)}`:"";return[{type:"mcp",category:"mcp",data:s(`${n}${p}${u}`),priority:3}]}var A=2048;function I(t,e){if(Buffer.byteLength(t,"utf8")<=e)return{value:t,truncated:!1};let o=Buffer.from(t,"utf8"),i=e;for(;i>0&&(o[i]&192)===128;)i--;return{value:o.subarray(0,i).toString("utf8"),truncated:!0}}var $=/(authorization|auth_token|access_token|refresh_token|bearer|token|secret|password|passwd|pwd|api[-_]?key|apikey|cookie|set-cookie|signature|private[-_]?key|client[-_]?secret|x[-_]?api[-_]?key)/i,H="[REDACTED]";function g(t,e=new WeakSet){if(t==null||typeof t!="object")return t;if(e.has(t))return"[CIRCULAR]";e.add(t);let o;if(Array.isArray(t))o=t.map(i=>g(i,e));else{let i={};for(let[r,n]of Object.entries(t))$.test(r)?i[r]=H:i[r]=g(n,e);o=i}return e.delete(t),o}function N(t){let{tool_name:e,tool_input:o}=t;if(!e.startsWith("mcp__"))return[];let i=g(o??{}),r;try{r=JSON.stringify(i)}catch{r="{}"}let{value:n,truncated:a}=I(r,A),p=a?`{"tool_name":${JSON.stringify(e)},"params_raw":${JSON.stringify(n)},"truncated":true}`:`{"tool_name":${JSON.stringify(e)},"params":${n}}`;return[{type:"mcp_tool_call",category:"mcp_tool_call",data:s(p),priority:4}]}function C(t){if(t.tool_name!=="AskUserQuestion")return[];let e=t.tool_input.questions,o=Array.isArray(e)&&e.length>0?String(e[0].question??""):"",i=s(String(t.tool_response??"")),r=o?`Q: ${s(o)} \u2192 A: ${i}`:`answer: ${i}`;return[{type:"decision_question",category:"decision",data:s(r),priority:2}]}function P(t){if(t.tool_name!=="Agent")return[];if(!t.tool_response||t.tool_response.length===0)return[];let e=t.tool_response.length>500?t.tool_response.slice(0,500):t.tool_response;return[{type:"agent_finding",category:"agent-finding",data:s(e),priority:2}]}function O(t){let e=[f(t.tool_input),s(t.tool_response)].join(" ");if(e.length===0)return[];let o=new Set,i=e.match(/https?:\/\/[^\s)]+/g);if(i)for(let n of i)n=n.replace(/["'})\],;.]+$/,""),/localhost|127\.0\.0\.1/i.test(n)||o.add(n);let r=e.match(/(?<!\w)#(\d+)/g);if(r)for(let n of r)o.add(n);return o.size===0?[]:[{type:"external_ref",category:"external-ref",data:s(Array.from(o).join(", ")),priority:3}]}function B(t){if(t.tool_name!=="EnterWorktree")return[];let e=String(t.tool_input.name??"unnamed");return[{type:"worktree",category:"env",data:s(`entered worktree: ${e}`),priority:2}]}var L=[/\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 j(t){return L.some(o=>o.test(t))?[{type:"decision",category:"decision",data:s(t),priority:2}]:[]}var D=[/\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 M(t){return D.some(o=>o.test(t))?[{type:"role",category:"role",data:s(t),priority:3}]:[]}var W=[{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 F(t){let e=W.find(({pattern:o})=>o.test(t));return e?[{type:"intent",category:"intent",data:s(e.mode),priority:4}]:[]}var U=[/\bblocked on\b/i,/\bwaiting for\b/i,/\bneed\s+\S+\s+before\b/i,/\bcan'?t proceed until\b/i,/\bdepends on\b/i,/\bblocked\b/i,/\bbekliyor\b/i,/\bbekliyorum\b/i],G=[/\bunblocked\b/i,/\bresolved\b/i,/\bgot the\s+\S+/i,/\bis ready now\b/i,/\bcan proceed\b/i];function J(t){let e=[];return G.some(r=>r.test(t))?(e.push({type:"blocker_resolved",category:"blocked-on",data:s(t),priority:2}),e):(U.some(r=>r.test(t))&&e.push({type:"blocker",category:"blocked-on",data:s(t),priority:2}),e)}function K(t){return t.length<=1024?[]:[{type:"data",category:"data",data:s(t),priority:4}]}var c=null;function q(t){let{tool_name:e,tool_response:o,tool_output:i}=t,r=String(o??""),n=i?.isError===!0;if(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(r)||n)return c={tool:e,error:r.slice(0,200),callsSince:0},[];if(!c)return[];if(c.callsSince++,c.callsSince>10)return c=null,[];let p=e===c.tool,u=c.tool==="Read"&&(e==="Edit"||e==="Write");if(p||u){let d={type:"error_resolved",category:"error-resolution",data:s(`Error in ${c.tool}: ${c.error} \u2192 Fixed`),priority:2};return c=null,[d]}return[]}function V(){c=null}var l=[];function z(t){return`${t.length}:${t.slice(0,20)}`}function Q(t){let{tool_name:e,tool_input:o}=t,i=z(JSON.stringify(o).slice(0,200));if(l.push({tool:e,inputHash:i}),l.length>50&&l.splice(0,l.length-50),l.length<3)return[];let r=0;for(let n=l.length-1;n>=0&&(l[n].tool===e&&l[n].inputHash===i);n--)r++;return r>=3?(l.splice(l.length-r),[{type:"retry_detected",category:"iteration-loop",data:s(`${e} called ${r} times with similar input`),priority:2}]):[]}function Y(){l.length=0}function X(t){try{let e=[];return e.push(...b(t)),e.push(...y(t)),e.push(...h(t)),e.push(...m(t)),e.push(...v(t)),e.push(...S(t)),e.push(...E(t)),e.push(...x(t)),e.push(...R(t)),e.push(...T(t)),e.push(...N(t)),e.push(...C(t)),e.push(...w(t)),e.push(...B(t)),e.push(...P(t)),e.push(...O(t)),e.push(...q(t)),e.push(...Q(t)),e}catch{return[]}}function Z(t){try{let e=[];return e.push(...j(t)),e.push(...M(t)),e.push(...F(t)),e.push(...J(t)),e.push(...K(t)),e}catch{return[]}}export{X as extractEvents,Z as extractUserEvents,V as resetErrorResolutionState,Y as resetIterationLoopState};
@@ -10,37 +10,85 @@
10
10
  import { execFileSync } from "node:child_process";
11
11
  import { createHash } from "node:crypto";
12
12
  import { join } from "node:path";
13
- import { mkdirSync } from "node:fs";
14
- import { homedir } from "node:os";
13
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { homedir, tmpdir } from "node:os";
15
15
 
16
16
  /**
17
17
  * Returns the worktree suffix for session path isolation.
18
18
  * Mirrors the logic in src/server.ts — kept in sync manually since
19
19
  * hooks run as plain .mjs (no TypeScript build step).
20
+ *
21
+ * Two-level cache:
22
+ * 1. In-process module cache — same hook fire calls this 3× (db,
23
+ * events, cleanup paths) so cache hits 2 of 3 cold within process.
24
+ * 2. Cross-process marker file in tmpdir keyed by sha256(cwd) — every
25
+ * Pre/PostToolUse hook is a fresh node fork; without this each fire
26
+ * pays 12-50ms for `git worktree list` on Linux/macOS, 50-150ms on
27
+ * Windows where fork+exec is heavier.
28
+ *
29
+ * Marker filename uses sha256(cwd) so it is alphanumeric — safe across
30
+ * Windows path/filename rules. tmpdir() resolves correctly on all 3 OS.
20
31
  */
32
+ let _wtCacheInProcess;
33
+ function workTreeMarkerPath(cwd) {
34
+ const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
35
+ return join(tmpdir(), `cm-wt-${hash}.txt`);
36
+ }
37
+
21
38
  function getWorktreeSuffix() {
22
39
  const envSuffix = process.env.CONTEXT_MODE_SESSION_SUFFIX;
23
- if (envSuffix !== undefined) {
24
- return envSuffix ? `__${envSuffix}` : "";
40
+ const cwd = process.cwd();
41
+
42
+ if (
43
+ _wtCacheInProcess &&
44
+ _wtCacheInProcess.cwd === cwd &&
45
+ _wtCacheInProcess.envSuffix === envSuffix
46
+ ) {
47
+ return _wtCacheInProcess.suffix;
25
48
  }
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)}`;
49
+
50
+ let suffix;
51
+ if (envSuffix !== undefined) {
52
+ suffix = envSuffix ? `__${envSuffix}` : "";
53
+ } else {
54
+ // Try cross-process marker first.
55
+ const markerPath = workTreeMarkerPath(cwd);
56
+ try {
57
+ suffix = readFileSync(markerPath, "utf-8");
58
+ _wtCacheInProcess = { cwd, envSuffix, suffix };
59
+ return suffix;
60
+ } catch {
61
+ // marker missing → compute below
62
+ }
63
+
64
+ suffix = "";
65
+ try {
66
+ const mainWorktree = execFileSync(
67
+ "git",
68
+ ["worktree", "list", "--porcelain"],
69
+ { encoding: "utf-8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] },
70
+ )
71
+ .split(/\r?\n/)
72
+ .find((l) => l.startsWith("worktree "))
73
+ ?.replace("worktree ", "")
74
+ ?.trim();
75
+ if (mainWorktree && cwd !== mainWorktree) {
76
+ suffix = `__${createHash("sha256").update(cwd).digest("hex").slice(0, 8)}`;
77
+ }
78
+ } catch {
79
+ // git not available or not a git repo — no suffix
80
+ }
81
+
82
+ // Best-effort write so subsequent hook forks short-circuit.
83
+ try {
84
+ writeFileSync(markerPath, suffix, "utf-8");
85
+ } catch {
86
+ // tmpdir not writable — degrade gracefully
39
87
  }
40
- } catch {
41
- // git not available or not a git repo — no suffix
42
88
  }
43
- return "";
89
+
90
+ _wtCacheInProcess = { cwd, envSuffix, suffix };
91
+ return suffix;
44
92
  }
45
93
 
46
94
  /** Claude Code platform options (default). */
@@ -74,8 +74,14 @@ export function attributeAndInsertEvents(db, sessionId, events, input, projectDi
74
74
  workspaceRoots: Array.isArray(input.workspace_roots) ? input.workspace_roots : [],
75
75
  lastKnownProjectDir,
76
76
  });
77
- for (let i = 0; i < events.length; i++) {
78
- db.insertEvent(sessionId, events[i], hookName, attributions[i]);
77
+ // Prefer bulk path (single transaction = single WAL commit). Falls back
78
+ // to per-event insert for older SessionDB instances that lack bulkInsertEvents.
79
+ if (typeof db.bulkInsertEvents === "function") {
80
+ db.bulkInsertEvents(sessionId, events, hookName, attributions);
81
+ } else {
82
+ for (let i = 0; i < events.length; i++) {
83
+ db.insertEvent(sessionId, events[i], hookName, attributions[i]);
84
+ }
79
85
  }
80
86
  return attributions;
81
87
  }
@@ -22,7 +22,7 @@ import { buildAutoInjection } from "./auto-injection.mjs";
22
22
  const toolNamer = createToolNamer("claude-code");
23
23
  const ROUTING_BLOCK = createRoutingBlock(toolNamer);
24
24
  import { readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath, resolveConfigDir } from "./session-helpers.mjs";
25
- import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "./session-directive.mjs";
25
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents } from "./session-directive.mjs";
26
26
  import { createSessionLoaders } from "./session-loaders.mjs";
27
27
  import { join, dirname } from "node:path";
28
28
  import { fileURLToPath } from "node:url";
@@ -82,7 +82,13 @@ try {
82
82
  const dbPath = getSessionDBPath();
83
83
  const db = new SessionDB({ dbPath });
84
84
 
85
- const events = getLatestSessionEvents(db);
85
+ // Filter events to the session being resumed. Falling back to
86
+ // getLatestSessionEvents(db) leaks events from any other session whose
87
+ // session_meta.started_at is more recent — observed cross-session bleed
88
+ // when a different worktree session started after this one and before
89
+ // the resume.
90
+ const sessionId = getSessionId(input);
91
+ const events = sessionId ? getSessionEvents(db, sessionId) : [];
86
92
  if (events.length > 0) {
87
93
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath());
88
94
  additionalContext += buildSessionDirective("resume", eventMeta, toolNamer);
@@ -17,7 +17,7 @@ import { createToolNamer } from "../core/tool-naming.mjs";
17
17
 
18
18
  const toolNamer = createToolNamer("vscode-copilot");
19
19
  const ROUTING_BLOCK = createRoutingBlock(toolNamer);
20
- import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
20
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents } from "../session-directive.mjs";
21
21
  import {
22
22
  readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
23
23
  getProjectDir, VSCODE_OPTS,
@@ -63,7 +63,12 @@ try {
63
63
  const dbPath = getSessionDBPath(OPTS);
64
64
  const db = new SessionDB({ dbPath });
65
65
 
66
- const events = getLatestSessionEvents(db);
66
+ // Filter events to the session being resumed. Falling back to
67
+ // getLatestSessionEvents(db) leaks events from any other session whose
68
+ // session_meta.started_at is more recent — observed cross-session bleed
69
+ // when a different session started after this one and before the resume.
70
+ const sessionId = getSessionId(input, OPTS);
71
+ const events = sessionId ? getSessionEvents(db, sessionId) : [];
67
72
  if (events.length > 0) {
68
73
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
69
74
  additionalContext += buildSessionDirective("resume", eventMeta, toolNamer);
@@ -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.103",
6
+ "version": "1.0.104",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",