context-mode 1.0.156 → 1.0.158
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/build/session/db.d.ts +35 -0
- package/build/session/db.js +71 -0
- package/build/session/error-classifier.d.ts +87 -0
- package/build/session/error-classifier.js +303 -0
- package/cli.bundle.mjs +128 -111
- package/hooks/platform-bridge.mjs +133 -2
- package/hooks/session-db.bundle.mjs +23 -6
- package/hooks/session-loaders.mjs +93 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +130 -113
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import os from "node:os";
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
10
11
|
|
|
11
12
|
const CACHE_TTL_MS = 60_000;
|
|
12
13
|
const FETCH_TIMEOUT_MS = 2_000;
|
|
@@ -110,6 +111,117 @@ export function buildUrl(cfg, _eventType) {
|
|
|
110
111
|
return `${cfg.platform_url}/events`;
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
// === Project identity resolution — worktree-invariant canonicalization ===
|
|
115
|
+
// Filesystem path is the wrong identifier for "project": git worktrees fork
|
|
116
|
+
// the path while keeping the same repo, monorepos collapse N packages into
|
|
117
|
+
// one umbrella, and forks of the same repo look like different projects.
|
|
118
|
+
// Resolve to a stable identity using:
|
|
119
|
+
// 1. Closest package.json `name` if it lives DEEPER than the .git root
|
|
120
|
+
// (monorepo sub-package — preserve granularity)
|
|
121
|
+
// 2. git config remote.origin.url, normalized
|
|
122
|
+
// (worktrees of one repo collapse to one identity)
|
|
123
|
+
// 3. Closest package.json `name` at any depth
|
|
124
|
+
// (local-only Node project)
|
|
125
|
+
// 4. basename(projectDir) (last resort)
|
|
126
|
+
const _projectIdentityCache = new Map();
|
|
127
|
+
|
|
128
|
+
function resolveProjectIdentity(projectDir) {
|
|
129
|
+
if (typeof projectDir !== "string" || !projectDir) return null;
|
|
130
|
+
if (_projectIdentityCache.has(projectDir)) return _projectIdentityCache.get(projectDir);
|
|
131
|
+
const id = computeProjectIdentity(projectDir);
|
|
132
|
+
_projectIdentityCache.set(projectDir, id);
|
|
133
|
+
return id;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function computeProjectIdentity(projectDir) {
|
|
137
|
+
let absoluteDir;
|
|
138
|
+
try {
|
|
139
|
+
absoluteDir = path.resolve(projectDir);
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const walked = walkUpFromDir(absoluteDir);
|
|
144
|
+
const pkg = walked.packageJson;
|
|
145
|
+
const gitTop = walked.gitToplevel;
|
|
146
|
+
|
|
147
|
+
// (1) Monorepo sub-package: package.json STRICTLY deeper than .git root.
|
|
148
|
+
if (pkg && gitTop && pkg.dir !== gitTop && pkg.dir.length > gitTop.length && pkg.name) {
|
|
149
|
+
return pkg.name;
|
|
150
|
+
}
|
|
151
|
+
// (2) Git remote URL.
|
|
152
|
+
const remote = gitTop ? readGitRemote(gitTop) : null;
|
|
153
|
+
if (remote) return normalizeRemoteUrl(remote);
|
|
154
|
+
// (3) Closest package.json (any depth).
|
|
155
|
+
if (pkg?.name) return pkg.name;
|
|
156
|
+
// (4) Basename.
|
|
157
|
+
return path.basename(absoluteDir);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function walkUpFromDir(start) {
|
|
161
|
+
let dir = start;
|
|
162
|
+
let pkg = null;
|
|
163
|
+
let gitTop = null;
|
|
164
|
+
// Safety: cap walk to 64 levels; real filesystems never hit this.
|
|
165
|
+
for (let i = 0; i < 64; i++) {
|
|
166
|
+
if (!pkg) {
|
|
167
|
+
const pkgPath = path.join(dir, "package.json");
|
|
168
|
+
if (fs.existsSync(pkgPath)) {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
171
|
+
if (typeof parsed?.name === "string" && parsed.name.trim()) {
|
|
172
|
+
pkg = { dir, name: parsed.name.trim() };
|
|
173
|
+
}
|
|
174
|
+
} catch { /* malformed — skip silently */ }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!gitTop && fs.existsSync(path.join(dir, ".git"))) {
|
|
178
|
+
gitTop = dir;
|
|
179
|
+
}
|
|
180
|
+
if (pkg && gitTop) break;
|
|
181
|
+
const parent = path.dirname(dir);
|
|
182
|
+
if (parent === dir) break;
|
|
183
|
+
dir = parent;
|
|
184
|
+
}
|
|
185
|
+
return { packageJson: pkg, gitToplevel: gitTop };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readGitRemote(gitTop) {
|
|
189
|
+
try {
|
|
190
|
+
const url = execSync("git config --get remote.origin.url", {
|
|
191
|
+
cwd: gitTop,
|
|
192
|
+
encoding: "utf8",
|
|
193
|
+
timeout: 500,
|
|
194
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
195
|
+
}).trim();
|
|
196
|
+
return url || null;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Canonical wire shape: host/path, lowercased host, no scheme, no .git suffix,
|
|
203
|
+
// no embedded credentials. All clone-equivalents collapse to one identity.
|
|
204
|
+
function normalizeRemoteUrl(url) {
|
|
205
|
+
let u = String(url).trim();
|
|
206
|
+
// SSH form (git@host:org/repo) → host/org/repo
|
|
207
|
+
const sshMatch = u.match(/^[a-z0-9_-]+@([^:]+):(.+)$/i);
|
|
208
|
+
if (sshMatch) {
|
|
209
|
+
u = `${sshMatch[1]}/${sshMatch[2]}`;
|
|
210
|
+
} else {
|
|
211
|
+
// scheme://[user[:pass]@]host/path → host/path
|
|
212
|
+
u = u.replace(/^[a-z]+:\/\/(?:[^@/]+@)?/i, "");
|
|
213
|
+
}
|
|
214
|
+
u = u.replace(/\.git\/?$/i, "").replace(/\/+$/, "");
|
|
215
|
+
// Lowercase host segment only (paths can be case-sensitive)
|
|
216
|
+
const slash = u.indexOf("/");
|
|
217
|
+
if (slash > 0) {
|
|
218
|
+
u = u.slice(0, slash).toLowerCase() + u.slice(slash);
|
|
219
|
+
} else {
|
|
220
|
+
u = u.toLowerCase();
|
|
221
|
+
}
|
|
222
|
+
return u;
|
|
223
|
+
}
|
|
224
|
+
|
|
113
225
|
// === Privacy: secret + PII redaction ===
|
|
114
226
|
const SECRETS = [
|
|
115
227
|
/\b(?:ghp|gho|ghs|ghu|github_pat)_[A-Za-z0-9_]{20,}\b/g, // GitHub
|
|
@@ -157,7 +269,16 @@ export async function maybeForward(event, platform, opts = {}) {
|
|
|
157
269
|
const cfg = readConfig();
|
|
158
270
|
if (!cfg) return;
|
|
159
271
|
|
|
160
|
-
|
|
272
|
+
// Project identity must be resolved from the RAW projectDir — the resolver
|
|
273
|
+
// reads `git config` against the actual filesystem path. After sanitize,
|
|
274
|
+
// $HOME-normalization would break the lookup. We overlay the resolved id
|
|
275
|
+
// back onto the event so the sanitize/walk path sees the canonical value
|
|
276
|
+
// (URL or package name, which need no further normalization).
|
|
277
|
+
const resolvedProject = resolveProjectIdentity(event?.projectDir);
|
|
278
|
+
const eventWithProject = resolvedProject !== null
|
|
279
|
+
? { ...event, project: resolvedProject }
|
|
280
|
+
: event;
|
|
281
|
+
const ev = sanitizeEvent(eventWithProject);
|
|
161
282
|
const ctrl = new AbortController();
|
|
162
283
|
const t = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
163
284
|
|
|
@@ -204,6 +325,16 @@ export const _internal = {
|
|
|
204
325
|
sanitizeEvent,
|
|
205
326
|
privacyTransform,
|
|
206
327
|
configPath,
|
|
207
|
-
|
|
328
|
+
resolveProjectIdentity,
|
|
329
|
+
normalizeRemoteUrl,
|
|
330
|
+
walkUpFromDir,
|
|
331
|
+
resetState: () => {
|
|
332
|
+
_cache = null;
|
|
333
|
+
_cacheLoadedAt = 0;
|
|
334
|
+
_warned = false;
|
|
335
|
+
_fsLoads = 0;
|
|
336
|
+
_projectIdentityCache.clear();
|
|
337
|
+
},
|
|
208
338
|
get fsLoads() { return _fsLoads; },
|
|
339
|
+
get projectIdentityCacheSize() { return _projectIdentityCache.size; },
|
|
209
340
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{createRequire as oe}from"node:module";import{existsSync as ie,unlinkSync as P,renameSync as ae}from"node:fs";import{tmpdir as ce}from"node:os";import{join as ue}from"node:path";var A=class{#e;constructor(e){this.#e=e}pragma(e){let r=this.#e.prepare(`PRAGMA ${e}`).all();if(!r||r.length===0)return;if(r.length>1)return r;let s=Object.values(r[0]);return s.length===1?s[0]:r[0]}exec(e){let t="",r=null;for(let a=0;a<e.length;a++){let i=e[a];if(r)t+=i,i===r&&(r=null);else if(i==="'"||i==='"')t+=i,r=i;else if(i===";"){let c=t.trim();c&&this.#e.prepare(c).run(),t=""}else t+=i}let s=t.trim();return s&&this.#e.prepare(s).run(),this}prepare(e){let t=this.#e.prepare(e);return{run:(...r)=>t.run(...r),get:(...r)=>{let s=t.get(...r);return s===null?void 0:s},all:(...r)=>t.all(...r),iterate:(...r)=>t.iterate(...r)}}transaction(e){return this.#e.transaction(e)}close(){this.#e.close()}},w=class{#e;constructor(e){this.#e=e}pragma(e){let r=this.#e.prepare(`PRAGMA ${e}`).all();if(!r||r.length===0)return;if(r.length>1)return r;let s=Object.values(r[0]);return s.length===1?s[0]:r[0]}exec(e){return this.#e.exec(e),this}prepare(e){let t=this.#e.prepare(e);return{run:(...r)=>t.run(...r),get:(...r)=>t.get(...r),all:(...r)=>t.all(...r),iterate:(...r)=>typeof t.iterate=="function"?t.iterate(...r):t.all(...r)[Symbol.iterator]()}}transaction(e){return(...t)=>{this.#e.exec("BEGIN");try{let r=e(...t);return this.#e.exec("COMMIT"),r}catch(r){throw this.#e.exec("ROLLBACK"),r}}}close(){this.#e.close()}},
|
|
2
|
-
`))}function K(n){let e=process.env[l];if(e===void 0)return{kind:"unset"};let t=e.trim();if(!t)return{kind:"ignored-empty",ignoredEnvVar:l,ignoredReason:"empty"};if(!
|
|
3
|
-
`)}function Oe(n){return n.ignoredEnvVar&&n.ignoredReason==="empty"?`Ignored empty ${n.ignoredEnvVar}; using adapter default.`:null}function J(){return`Set ${l} to a writable absolute path.`}function Ce(n){if(!n||typeof n!="object")return null;let e=n.path;return typeof e=="string"&&e.length>0?e:null}var m;function
|
|
1
|
+
import{createRequire as oe}from"node:module";import{existsSync as ie,unlinkSync as P,renameSync as ae}from"node:fs";import{tmpdir as ce}from"node:os";import{join as ue}from"node:path";var A=class{#e;constructor(e){this.#e=e}pragma(e){let r=this.#e.prepare(`PRAGMA ${e}`).all();if(!r||r.length===0)return;if(r.length>1)return r;let s=Object.values(r[0]);return s.length===1?s[0]:r[0]}exec(e){let t="",r=null;for(let a=0;a<e.length;a++){let i=e[a];if(r)t+=i,i===r&&(r=null);else if(i==="'"||i==='"')t+=i,r=i;else if(i===";"){let c=t.trim();c&&this.#e.prepare(c).run(),t=""}else t+=i}let s=t.trim();return s&&this.#e.prepare(s).run(),this}prepare(e){let t=this.#e.prepare(e);return{run:(...r)=>t.run(...r),get:(...r)=>{let s=t.get(...r);return s===null?void 0:s},all:(...r)=>t.all(...r),iterate:(...r)=>t.iterate(...r)}}transaction(e){return this.#e.transaction(e)}close(){this.#e.close()}},w=class{#e;constructor(e){this.#e=e}pragma(e){let r=this.#e.prepare(`PRAGMA ${e}`).all();if(!r||r.length===0)return;if(r.length>1)return r;let s=Object.values(r[0]);return s.length===1?s[0]:r[0]}exec(e){return this.#e.exec(e),this}prepare(e){let t=this.#e.prepare(e);return{run:(...r)=>t.run(...r),get:(...r)=>t.get(...r),all:(...r)=>t.all(...r),iterate:(...r)=>typeof t.iterate=="function"?t.iterate(...r):t.all(...r)[Symbol.iterator]()}}transaction(e){return(...t)=>{this.#e.exec("BEGIN");try{let r=e(...t);return this.#e.exec("COMMIT"),r}catch(r){throw this.#e.exec("ROLLBACK"),r}}}close(){this.#e.close()}},g=null;function de(n){let e=null;try{return e=new n(":memory:"),e.exec("CREATE VIRTUAL TABLE __fts5_probe USING fts5(x)"),!0}catch{return!1}finally{try{e?.close()}catch{}}}function le(n,e){let t=e!==void 0?e:globalThis.Bun;if(typeof t<"u"&&t!==null)return!0;let r=n??process.versions,[s,a]=(r.node??"0.0.0").split("."),i=Number(s),c=Number(a);return!Number.isFinite(i)||!Number.isFinite(c)?!1:i>22||i===22&&c>=5}function Ee(){if(!g){let n=oe(import.meta.url);if(globalThis.Bun){let e=n(["bun","sqlite"].join(":")).Database;g=function(r,s){let a=new e(r,{readonly:s?.readonly,create:!0}),i=new A(a);return s?.timeout&&i.pragma(`busy_timeout = ${s.timeout}`),i}}else if(le()){let e=null;try{({DatabaseSync:e}=n(["node","sqlite"].join(":")))}catch{e=null}e&&de(e)?g=function(r,s){let a=new e(r,{readOnly:s?.readonly??!1}),i=new w(a);return s?.timeout&&i.pragma(`busy_timeout = ${s.timeout}`),i}:g=n("better-sqlite3")}else g=n("better-sqlite3")}return g}function F(n){n.pragma("journal_mode = WAL"),n.pragma("synchronous = NORMAL");try{n.pragma("mmap_size = 268435456")}catch{}}function k(n){if(!ie(n))for(let e of["-wal","-shm"])try{P(n+e)}catch{}}function ge(n){for(let e of["","-wal","-shm"])try{P(n+e)}catch{}}function x(n){try{n.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{n.close()}catch{}}function B(n="context-mode"){return ue(ce(),`${n}-${process.pid}.db`)}function me(n,e=[100,500,2e3]){let t;for(let r=0;r<=e.length;r++)try{return n()}catch(s){let a=s instanceof Error?s.message:String(s);if(!a.includes("SQLITE_BUSY")&&!a.includes("database is locked"))throw s;if(t=s instanceof Error?s:new Error(a),r<e.length){let i=e[r],c=Date.now();for(;Date.now()-c<i;);}}throw new Error(`SQLITE_BUSY: database is locked after ${e.length} retries. Original error: ${t?.message}`)}function _e(n){return n.includes("SQLITE_CORRUPT")||n.includes("SQLITE_NOTADB")||n.includes("database disk image is malformed")||n.includes("file is not a database")}function pe(n){let e=Date.now();for(let t of["","-wal","-shm"])try{ae(n+t,`${n}${t}.corrupt-${e}`)}catch{}}var y=Symbol.for("__context_mode_live_dbs_v3__"),C=(()=>{let n=globalThis;return n[y]||(n[y]=new Set,process.on("exit",()=>{for(let e of n[y])x(e);n[y].clear()})),n[y]})(),v=class{#e;#t;constructor(e){let t=Ee();this.#e=e,k(e);let r;try{r=new t(e,{timeout:3e4}),F(r)}catch(s){let a=s instanceof Error?s.message:String(s);if(_e(a)){pe(e),k(e);try{r=new t(e,{timeout:3e4}),F(r)}catch(i){throw new Error(`Failed to create fresh DB after renaming corrupt file: ${i instanceof Error?i.message:String(i)}`)}}else throw s}this.#t=r,C.add(this.#t),this.initSchema(),this.prepareStatements()}get db(){return this.#t}get dbPath(){return this.#e}close(){C.delete(this.#t),x(this.#t)}withRetry(e){return me(e)}cleanup(){C.delete(this.#t),x(this.#t),ge(this.#e)}};import{createHash as S}from"node:crypto";import{execFileSync as ye}from"node:child_process";import{accessSync as Se,constants as fe,existsSync as D,mkdirSync as he,realpathSync as Te,renameSync as I}from"node:fs";import{homedir as q}from"node:os";import{dirname as ve,isAbsolute as G,join as E,resolve as _}from"node:path";var l="CONTEXT_MODE_DIR",Y="sessions",j="content",f=class extends Error{kind;path;overrideEnvVar;ignoredEnvVar;ignoredReason;constructor(e,t,r=l,s,a,i={}){super(a??Le(e,t,i),{cause:s}),this.name="StorageDirectoryError",this.kind=e,this.path=t,this.overrideEnvVar=r,this.ignoredEnvVar=i.ignoredEnvVar,this.ignoredReason=i.ignoredReason}},b=new Map;function qe(n){let e=n.env??process.env,t=n.legacySessionDirEnv,r=t?e[t]?.trim():void 0;return r&&t?(n.onLegacySessionDir?.(t,r),r):E(Re(n.configDir,n.configDirEnv,e),"context-mode","sessions")}function Re(n,e,t){let r=e?t[e]:void 0;return r&&r.trim()!==""?V(r.trim()):V(n,q())}function V(n,e){return n.startsWith("~")?_(q(),n.replace(/^~[/\\]?/,"")):G(n)?_(n):e?_(e,n):_(n)}function be(n,e,t){return new f(n,e,l,void 0,[`Invalid ${l} for context-mode ${n} directory: ${t}`,J()].join(`
|
|
2
|
+
`))}function K(n){let e=process.env[l];if(e===void 0)return{kind:"unset"};let t=e.trim();if(!t)return{kind:"ignored-empty",ignoredEnvVar:l,ignoredReason:"empty"};if(!G(t))throw be(n,t,`${l} must be an absolute path.`);return{kind:"override",root:_(t)}}function De(n){return n.kind==="ignored-empty"?{ignoredEnvVar:n.ignoredEnvVar,ignoredReason:n.ignoredReason}:{}}function z(n,e){let t=K(n);return t.kind!=="override"?null:{kind:n,path:E(t.root,e),envVar:l,source:"override"}}function Ne(n,e,t){return{kind:n,path:_(e()),envVar:null,source:"default",...t}}function Q(n){let e=K("session");return e.kind==="override"?{kind:"session",path:E(e.root,Y),envVar:l,source:"override"}:Ne("session",n,De(e))}function Ge(n){let e=z("content",j);if(e)return e;let t=Q(n);return{kind:"content",path:E(ve(t.path),j),envVar:t.envVar,source:t.source,ignoredEnvVar:t.ignoredEnvVar,ignoredReason:t.ignoredReason}}function Ye(n){let e=z("stats",Y);if(e)return e;let t=Q(n);return{kind:"stats",path:t.path,envVar:t.envVar,source:t.source,ignoredEnvVar:t.ignoredEnvVar,ignoredReason:t.ignoredReason}}function Ke(n){return n.message}function ze(n){return n.source==="override"&&n.envVar?`via ${n.envVar}`:n.ignoredEnvVar&&n.ignoredReason==="empty"?`default; ignored empty ${n.ignoredEnvVar}`:"default"}function Qe(){b.clear()}function Je(n){let e=[n.kind,n.path,n.source,n.envVar??"",n.ignoredEnvVar??"",n.ignoredReason??""].join("\0"),t=b.get(e);if(t instanceof f)throw t;if(t===n.path)return t;try{return he(n.path,{recursive:!0}),Se(n.path,fe.W_OK),b.set(e,n.path),n.path}catch(r){let s=new f(n.kind,Ce(r)??n.path,l,r,void 0,{ignoredEnvVar:n.ignoredEnvVar,ignoredReason:n.ignoredReason});throw b.set(e,s),s}}function Le(n,e,t={}){return[`context-mode ${n} directory is not writable: ${e}`,Oe(t),J()].filter(Boolean).join(`
|
|
3
|
+
`)}function Oe(n){return n.ignoredEnvVar&&n.ignoredReason==="empty"?`Ignored empty ${n.ignoredEnvVar}; using adapter default.`:null}function J(){return`Set ${l} to a writable absolute path.`}function Ce(n){if(!n||typeof n!="object")return null;let e=n.path;return typeof e=="string"&&e.length>0?e:null}var m;function h(n){let e=n.replace(/\\/g,"/");return/^\/+$/.test(e)?"/":/^[A-Za-z]:\/+$/.test(e)?`${e.slice(0,2)}/`:e.replace(/\/+$/,"")}function H(n){let e=n;try{e=Te.native(n)}catch{}let t=h(e);return process.platform==="win32"||process.platform==="darwin"?t.toLowerCase():t}function Z(n,e){return ye("git",["-C",n,...e],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim()}function Ae(n){let e=Z(n,["rev-parse","--show-toplevel"]);return e.length>0?h(e):null}function we(n){let e=Z(n,["worktree","list","--porcelain"]).split(/\r?\n/).find(t=>t.startsWith("worktree "))?.replace("worktree ","")?.trim();return e?h(e):null}function xe(n=process.cwd()){let e=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(m&&m.projectDir===n&&m.envSuffix===e)return m.suffix;let t="";if(e!==void 0)t=e?`__${e}`:"";else try{let r=Ae(n),s=we(n);if(r&&s){let a=H(r),i=H(s);a!==i&&(t=`__${S("sha256").update(a).digest("hex").slice(0,8)}`)}}catch{}return m={projectDir:n,envSuffix:e,suffix:t},t}function Ze(){m=void 0}function ee(n){return S("sha256").update(h(n)).digest("hex").slice(0,16)}function te(n){let e=h(n),t=process.platform==="darwin"||process.platform==="win32"?e.toLowerCase():e;return S("sha256").update(t).digest("hex").slice(0,16)}function et(n){let{projectDir:e,contentDir:t}=n,r=te(e),s=E(t,`${r}.db`);if(D(s))return s;let a=ee(e);if(a===r)return s;let i=E(t,`${a}.db`);if(D(i))try{I(i,s);for(let c of["-wal","-shm"])try{I(i+c,s+c)}catch{}}catch{}return s}function tt(n){return Ie({...n,ext:".db"})}function Ie(n){let{projectDir:e,sessionsDir:t,ext:r}=n,s=n.suffix??xe(e),a=te(e),i=E(t,`${a}${s}${r}`);if(D(i))return i;let c=ee(e);if(c===a)return i;let d=E(t,`${c}${s}${r}`);if(D(d))try{I(d,i)}catch{}return i}var W=1e3,X=5;function R(n){let e=Number(n);return!Number.isFinite(e)||e<=0?0:Math.floor(e)}var o={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",getLatestAttributedProject:"getLatestAttributedProject",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",getSessionRollup:"getSessionRollup",getMaxFileEdits:"getMaxFileEdits",incrementCompactCount:"incrementCompactCount",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",claimLatestUnconsumedResume:"claimLatestUnconsumedResume",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions",searchEvents:"searchEvents",incrementToolCall:"incrementToolCall",getToolCallTotals:"getToolCallTotals",getToolCallByTool:"getToolCallByTool",getEventBytesSummary:"getEventBytesSummary"},Ue=[["project_dir","TEXT NOT NULL DEFAULT ''"],["attribution_source","TEXT NOT NULL DEFAULT 'unknown'"],["attribution_confidence","REAL NOT NULL DEFAULT 0"],["bytes_avoided","INTEGER NOT NULL DEFAULT 0"],["bytes_returned","INTEGER NOT NULL DEFAULT 0"]];function ne(n){let e=n.pragma("table_xinfo(session_events)"),t=new Set(e.map(s=>s.name)),r=!1;for(let[s,a]of Ue)t.has(s)||(n.exec(`ALTER TABLE session_events ADD COLUMN ${s} ${a}`),r=!0);return r&&n.exec("CREATE INDEX IF NOT EXISTS idx_session_events_project ON session_events(session_id, project_dir)"),r}function nt(n,e){let t=null;try{t=new e(n),ne(t)}catch{}finally{try{t?.close()}catch{}}}var $=class extends v{constructor(e){super(e?.dbPath??B("session"))}stmt(e){return this.stmts.get(e)}initSchema(){try{let t=this.db.pragma("table_xinfo(session_events)").find(r=>r.name==="data_hash");t&&t.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
|
|
4
4
|
CREATE TABLE IF NOT EXISTS session_events (
|
|
5
5
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6
6
|
session_id TEXT NOT NULL,
|
|
@@ -87,7 +87,24 @@ import{createRequire as oe}from"node:module";import{existsSync as ie,unlinkSync
|
|
|
87
87
|
)`),e(o.updateMetaLastEvent,`UPDATE session_meta
|
|
88
88
|
SET last_event_at = datetime('now'), event_count = event_count + 1
|
|
89
89
|
WHERE session_id = ?`),e(o.ensureSession,"INSERT OR IGNORE INTO session_meta (session_id, project_dir) VALUES (?, ?)"),e(o.getSessionStats,`SELECT session_id, project_dir, started_at, last_event_at, event_count, compact_count
|
|
90
|
-
FROM session_meta WHERE session_id = ?`),e(o.
|
|
90
|
+
FROM session_meta WHERE session_id = ?`),e(o.getSessionRollup,`SELECT
|
|
91
|
+
COUNT(*) AS tool_calls,
|
|
92
|
+
COALESCE(SUM(CASE WHEN category = 'error' THEN 1 ELSE 0 END), 0) AS errors,
|
|
93
|
+
COUNT(DISTINCT type) AS unique_tools,
|
|
94
|
+
COUNT(DISTINCT CASE WHEN category = 'file' THEN data END) AS unique_files,
|
|
95
|
+
CASE WHEN SUM(CASE WHEN category = 'git' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END AS has_commit,
|
|
96
|
+
CAST(COALESCE((MAX(strftime('%s', created_at)) - MIN(strftime('%s', created_at))) / 60.0, 0) AS INTEGER) AS duration_min,
|
|
97
|
+
COALESCE(SUM(CASE WHEN type = 'external_ref' THEN 1 ELSE 0 END), 0) AS sources_indexed,
|
|
98
|
+
CAST(COALESCE(SUM(bytes_avoided) / 1024.0, 0) AS INTEGER) AS total_chunks,
|
|
99
|
+
COALESCE(SUM(CASE WHEN type IN ('file_search', 'file_glob') THEN 1 ELSE 0 END), 0) AS search_queries
|
|
100
|
+
FROM session_events
|
|
101
|
+
WHERE session_id = ?`),e(o.getMaxFileEdits,`SELECT COALESCE(MAX(c), 0) AS max_file_edits
|
|
102
|
+
FROM (
|
|
103
|
+
SELECT COUNT(*) AS c
|
|
104
|
+
FROM session_events
|
|
105
|
+
WHERE session_id = ? AND category = 'file' AND type IN ('file_edit', 'file_write')
|
|
106
|
+
GROUP BY data
|
|
107
|
+
)`),e(o.incrementCompactCount,"UPDATE session_meta SET compact_count = compact_count + 1 WHERE session_id = ?"),e(o.upsertResume,`INSERT INTO session_resume (session_id, snapshot, event_count)
|
|
91
108
|
VALUES (?, ?, ?)
|
|
92
109
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
93
110
|
snapshot = excluded.snapshot,
|
|
@@ -118,6 +135,6 @@ import{createRequire as oe}from"node:module";import{existsSync as ie,unlinkSync
|
|
|
118
135
|
FROM tool_calls WHERE session_id = ?`),e(o.getToolCallByTool,`SELECT tool, calls, bytes_returned
|
|
119
136
|
FROM tool_calls WHERE session_id = ? ORDER BY calls DESC`),e(o.getEventBytesSummary,`SELECT COALESCE(SUM(bytes_avoided), 0) AS bytes_avoided,
|
|
120
137
|
COALESCE(SUM(bytes_returned), 0) AS bytes_returned
|
|
121
|
-
FROM session_events WHERE session_id = ?`)}insertEvent(e,t,r="PostToolUse",s,a){let i=
|
|
138
|
+
FROM session_events WHERE session_id = ?`)}insertEvent(e,t,r="PostToolUse",s,a){let i=S("sha256").update(t.data).digest("hex").slice(0,16).toUpperCase(),c=String(s?.projectDir??t.project_dir??this._getSessionProjectDir(e)).trim(),d=String(s?.source??t.attribution_source??"unknown"),u=Number(s?.confidence??t.attribution_confidence??0),T=Number.isFinite(u)?Math.max(0,Math.min(1,u)):0,p=R(a?.bytesAvoided),N=R(a?.bytesReturned),L=this.db.transaction(()=>{if(this.stmt(o.checkDuplicate).get(e,X,t.type,i))return;this.stmt(o.getEventCount).get(e).cnt>=W&&this.stmt(o.evictLowestPriority).run(e),this.stmt(o.insertEvent).run(e,t.type,t.category,t.priority,t.data,c,d,T,p,N,r,i),this.stmt(o.updateMetaLastEvent).run(e)});this.withRetry(()=>L())}bulkInsertEvents(e,t,r="PostToolUse",s,a){if(!t||t.length===0)return;if(t.length===1){this.insertEvent(e,t[0],r,s?.[0],a?.[0]);return}let i=t.map((d,u)=>{let T=S("sha256").update(d.data).digest("hex").slice(0,16).toUpperCase(),p=s?.[u],N=String(p?.projectDir??d.project_dir??this._getSessionProjectDir(e)??"").trim(),L=String(p?.source??d.attribution_source??"unknown"),O=Number(p?.confidence??d.attribution_confidence??0),U=Number.isFinite(O)?Math.max(0,Math.min(1,O)):0,M=a?.[u],re=R(M?.bytesAvoided),se=R(M?.bytesReturned);return{event:d,dataHash:T,projectDir:N,attributionSource:L,attributionConfidence:U,bytesAvoided:re,bytesReturned:se}}),c=this.db.transaction(()=>{let d=this.stmt(o.getEventCount).get(e).cnt;for(let u of i)this.stmt(o.checkDuplicate).get(e,X,u.event.type,u.dataHash)||(d>=W?this.stmt(o.evictLowestPriority).run(e):d++,this.stmt(o.insertEvent).run(e,u.event.type,u.event.category,u.event.priority,u.event.data,u.projectDir,u.attributionSource,u.attributionConfidence,u.bytesAvoided,u.bytesReturned,r,u.dataHash));this.stmt(o.updateMetaLastEvent).run(e)});this.withRetry(()=>c())}getEvents(e,t){let r=t?.limit??1e3,s=t?.type,a=t?.minPriority;return s&&a!==void 0?this.stmt(o.getEventsByTypeAndPriority).all(e,s,a,r):s?this.stmt(o.getEventsByType).all(e,s,r):a!==void 0?this.stmt(o.getEventsByPriority).all(e,a,r):this.stmt(o.getEvents).all(e,r)}getEventCount(e){return this.stmt(o.getEventCount).get(e).cnt}getEventBytesSummary(e){let t=this.stmt(o.getEventBytesSummary).get(e);return{bytesAvoided:Number(t?.bytes_avoided??0),bytesReturned:Number(t?.bytes_returned??0)}}getLatestAttributedProjectDir(e){return this.stmt(o.getLatestAttributedProject).get(e)?.project_dir||null}_getSessionProjectDir(e){try{return this.db.prepare("SELECT project_dir FROM session_meta WHERE session_id = ?").get(e)?.project_dir||""}catch{return""}}searchEvents(e,t,r,s){try{let a=e.replace(/[%_]/g,c=>"\\"+c),i=s??null;return this.stmt(o.searchEvents).all(r,a,a,i,i,t)}catch{return[]}}getSessionIdsForProject(e){try{return this.db.prepare(`SELECT DISTINCT session_id
|
|
122
139
|
FROM session_events
|
|
123
|
-
WHERE project_dir = ?`).all(e).map(r=>r.session_id)}catch{return[]}}ensureSession(e,t){this.stmt(o.ensureSession).run(e,t)}getSessionStats(e){return this.stmt(o.getSessionStats).get(e)??null}incrementCompactCount(e){this.stmt(o.incrementCompactCount).run(e)}upsertResume(e,t,r){this.stmt(o.upsertResume).run(e,t,r??0)}getResume(e){return this.stmt(o.getResume).get(e)??null}markResumeConsumed(e){this.stmt(o.markResumeConsumed).run(e)}claimLatestUnconsumedResume(e){let t=this.stmt(o.claimLatestUnconsumedResume).get(e);return t?{sessionId:t.session_id,snapshot:t.snapshot}:null}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(e,t,r=0){let s=Number.isFinite(r)&&r>0?Math.round(r):0;try{this.stmt(o.incrementToolCall).run(e,t,s)}catch{}}getToolCallStats(e){try{let t=this.stmt(o.getToolCallTotals).get(e),r=this.stmt(o.getToolCallByTool).all(e),s={};for(let a of r)s[a.tool]={calls:a.calls,bytesReturned:a.bytes_returned};return{totalCalls:t?.calls??0,totalBytesReturned:t?.bytes_returned??0,byTool:s}}catch{return{totalCalls:0,totalBytesReturned:0,byTool:{}}}}deleteSession(e){this.db.transaction(()=>{this.stmt(o.deleteEvents).run(e),this.stmt(o.deleteResume).run(e),this.stmt(o.deleteMeta).run(e)})()}cleanupOldSessions(e=7){let t=`-${e}`,r=this.stmt(o.getOldSessions).all(t);for(let{session_id:s}of r)this.deleteSession(s);return r.length}pruneOrphanedEvents(){let e=this.db.prepare("DELETE FROM session_events WHERE session_id NOT IN (SELECT session_id FROM session_meta)").run();return Number(e.changes??0)}};export{$ as SessionDB,
|
|
140
|
+
WHERE project_dir = ?`).all(e).map(r=>r.session_id)}catch{return[]}}ensureSession(e,t){this.stmt(o.ensureSession).run(e,t)}getSessionStats(e){return this.stmt(o.getSessionStats).get(e)??null}getSessionRollup(e){let t=this.stmt(o.getSessionRollup).get(e),r=this.stmt(o.getMaxFileEdits).get(e),s=this.getSessionStats(e),a=(t?.tool_calls??0)>0?t?.unique_files??0:0,i=t?.errors??0,c=Math.min(a,i);return{tool_calls:t?.tool_calls??0,errors:t?.errors??0,unique_tools:t?.unique_tools??0,unique_files:t?.unique_files??0,max_file_edits:r?.max_file_edits??0,has_commit:t?.has_commit??0,edit_test_cycles:c,duration_min:t?.duration_min??0,compact_count:s?.compact_count??0,sources_indexed:t?.sources_indexed??0,total_chunks:t?.total_chunks??0,search_queries:t?.search_queries??0}}incrementCompactCount(e){this.stmt(o.incrementCompactCount).run(e)}upsertResume(e,t,r){this.stmt(o.upsertResume).run(e,t,r??0)}getResume(e){return this.stmt(o.getResume).get(e)??null}markResumeConsumed(e){this.stmt(o.markResumeConsumed).run(e)}claimLatestUnconsumedResume(e){let t=this.stmt(o.claimLatestUnconsumedResume).get(e);return t?{sessionId:t.session_id,snapshot:t.snapshot}:null}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(e,t,r=0){let s=Number.isFinite(r)&&r>0?Math.round(r):0;try{this.stmt(o.incrementToolCall).run(e,t,s)}catch{}}getToolCallStats(e){try{let t=this.stmt(o.getToolCallTotals).get(e),r=this.stmt(o.getToolCallByTool).all(e),s={};for(let a of r)s[a.tool]={calls:a.calls,bytesReturned:a.bytes_returned};return{totalCalls:t?.calls??0,totalBytesReturned:t?.bytes_returned??0,byTool:s}}catch{return{totalCalls:0,totalBytesReturned:0,byTool:{}}}}deleteSession(e){this.db.transaction(()=>{this.stmt(o.deleteEvents).run(e),this.stmt(o.deleteResume).run(e),this.stmt(o.deleteMeta).run(e)})()}cleanupOldSessions(e=7){let t=`-${e}`,r=this.stmt(o.getOldSessions).all(t);for(let{session_id:s}of r)this.deleteSession(s);return r.length}pruneOrphanedEvents(){let e=this.db.prepare("DELETE FROM session_events WHERE session_id NOT IN (SELECT session_id FROM session_meta)").run();return Number(e.changes??0)}};export{$ as SessionDB,f as StorageDirectoryError,Ze as _resetWorktreeSuffixCacheForTests,ne as applyMissingSessionEventsColumns,Qe as clearStorageDirectoryCheckCacheForTests,ze as describeStorageDirectorySource,nt as ensureSessionEventsSchema,Je as ensureWritableStorageDir,Ke as formatStorageDirectoryError,xe as getWorktreeSuffix,te as hashProjectDirCanonical,ee as hashProjectDirLegacy,h as normalizeWorktreePath,Ge as resolveContentStorageDir,et as resolveContentStorePath,qe as resolveDefaultSessionDir,tt as resolveSessionDbPath,Ie as resolveSessionPath,Q as resolveSessionStorageDir,Ye as resolveStatsStorageDir};
|
|
@@ -106,23 +106,102 @@ export function attributeAndInsertEvents(db, sessionId, events, input, projectDi
|
|
|
106
106
|
// the unconfigured-user path costs at most one syscall per minute.
|
|
107
107
|
if (hasPlatformConfig()) {
|
|
108
108
|
const platform = detectPlatformFromEnv();
|
|
109
|
+
// Session-wide rollup snapshot — stamped onto every outgoing event so
|
|
110
|
+
// the analytics engine sees the seed.ts shape (tool_calls, errors,
|
|
111
|
+
// unique_tools, ...). Defensive call: older SessionDB bundles that
|
|
112
|
+
// predate v1.0.158 won't have getSessionRollup; fall back to null
|
|
113
|
+
// and the bridge will still pass the per-event facts through.
|
|
114
|
+
const rollup = typeof db.getSessionRollup === "function"
|
|
115
|
+
? db.getSessionRollup(sessionId)
|
|
116
|
+
: null;
|
|
117
|
+
|
|
109
118
|
for (let i = 0; i < events.length; i++) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
...events[i],
|
|
114
|
-
...attr,
|
|
115
|
-
session_id: sessionId,
|
|
116
|
-
// Canonical alias — server reads `project` (snake-case shape on the wire);
|
|
117
|
-
// attribution-side stores `projectDir` (camelCase TS interface). Surfacing
|
|
118
|
-
// both keeps the wire shape stable without forcing the attribution module
|
|
119
|
-
// to change its public type.
|
|
120
|
-
project: attr?.projectDir,
|
|
121
|
-
},
|
|
122
|
-
platform,
|
|
123
|
-
);
|
|
119
|
+
const enriched = enrichEventForPlatform(events[i], attributions[i]);
|
|
120
|
+
const payload = rollup ? { ...enriched, ...rollup } : enriched;
|
|
121
|
+
maybeForward({ ...payload, session_id: sessionId }, platform);
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
return attributions;
|
|
128
126
|
}
|
|
127
|
+
|
|
128
|
+
// ── Per-event enrichment (seed.ts shape parity) ──────────────────────────
|
|
129
|
+
//
|
|
130
|
+
// Each canonical event from session-extract carries only {type, category, data}.
|
|
131
|
+
// The platform's events table has 35 columns; the engine's aggregate SQL reads
|
|
132
|
+
// most of them. This helper derives the per-event-derivable subset directly
|
|
133
|
+
// from the event's own facts — no I/O, no classifier dependency, no allocation
|
|
134
|
+
// beyond the spread. Aggregates (tool_calls, errors, ...) come from the
|
|
135
|
+
// session rollup stamp in the caller.
|
|
136
|
+
//
|
|
137
|
+
// PRD-context-as-a-service §5.4 ABI: bridge stays a dumb pipe. This enrichment
|
|
138
|
+
// runs BEFORE maybeForward so the body envelope spreads the enriched event
|
|
139
|
+
// unchanged.
|
|
140
|
+
function enrichEventForPlatform(event, attribution) {
|
|
141
|
+
const error = event?.category === "error" ? 1 : 0;
|
|
142
|
+
const dataStr = typeof event?.data === "string" ? event.data : "";
|
|
143
|
+
|
|
144
|
+
const enriched = {
|
|
145
|
+
...event,
|
|
146
|
+
...attribution,
|
|
147
|
+
error,
|
|
148
|
+
// session_* are open-string passthroughs (ADR-0001) — let the platform
|
|
149
|
+
// do forensic queries on the raw shape without forcing the wide→narrow
|
|
150
|
+
// category derivation to ever round-trip.
|
|
151
|
+
session_category: event?.category,
|
|
152
|
+
session_type: event?.type,
|
|
153
|
+
session_data: dataStr.length > 0 ? dataStr.slice(0, 500) : undefined,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Error events: surface the message + classify
|
|
157
|
+
if (error === 1) {
|
|
158
|
+
enriched.error_message = dataStr.slice(0, 1000);
|
|
159
|
+
const cls = classifyError(dataStr);
|
|
160
|
+
enriched.error_category = cls.error_category;
|
|
161
|
+
enriched.error_tool = cls.error_tool;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Git events: surface commit message + mark has_commit at the event level
|
|
165
|
+
// (rollup-level has_commit comes from the session-wide stamp; both win
|
|
166
|
+
// when set — `{...enriched, ...rollup}` order keeps rollup authoritative
|
|
167
|
+
// for non-git events while git events stay marked).
|
|
168
|
+
if (event?.category === "git" && dataStr.length > 0) {
|
|
169
|
+
enriched.commit_message = dataStr.slice(0, 500);
|
|
170
|
+
enriched.has_commit = 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// File events: ship the file path as the single-item array shape the
|
|
174
|
+
// platform schema expects (Zod: z.array(z.string()).max(20))
|
|
175
|
+
if (event?.category === "file" && dataStr.length > 0) {
|
|
176
|
+
enriched.file_paths = [dataStr.slice(0, 500)];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return enriched;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Inline error classifier — seed.ts ERROR_CATEGORIES parity ────────────
|
|
183
|
+
//
|
|
184
|
+
// Mirrors src/session/error-classifier.ts's 10-category table for runtime
|
|
185
|
+
// callers (this is a .mjs hook file; the TS classifier ships bundled but
|
|
186
|
+
// the bundle import path costs an extra ~20ms on first hook fire and an
|
|
187
|
+
// extra disk read per hook subprocess. Inline keeps the hot path fast.)
|
|
188
|
+
// If the table ever drifts from error-classifier.ts, the classifier test
|
|
189
|
+
// suite (tests/session/classifier.test.ts) is the canonical source — sync
|
|
190
|
+
// the patterns there first, then mirror here.
|
|
191
|
+
function classifyError(message) {
|
|
192
|
+
const m = String(message ?? "").toLowerCase();
|
|
193
|
+
if (!m) return { error_category: "unknown", error_tool: "Bash" };
|
|
194
|
+
|
|
195
|
+
// Order matters: timeout + git_conflict checked BEFORE test_failed so
|
|
196
|
+
// "test timed out" and "CONFLICT … fail" land in the right bucket.
|
|
197
|
+
if (/etimedout|timed out|timeout|deadline exceeded/.test(m)) return { error_category: "timeout", error_tool: "Bash" };
|
|
198
|
+
if (/conflict.*(merge|rebase|git)|merge conflict|^conflict/.test(m)) return { error_category: "git_conflict", error_tool: "Bash" };
|
|
199
|
+
if (/enoent|no such file|cannot find module|filenotfounderror/.test(m)) return { error_category: "file_not_found", error_tool: "Read" };
|
|
200
|
+
if (/command not found|: not found|exit code 127/.test(m)) return { error_category: "command_not_found", error_tool: "Bash" };
|
|
201
|
+
if (/old_string|could not find string|matches multiple/.test(m)) return { error_category: "edit_match_failed", error_tool: "Edit" };
|
|
202
|
+
if (/eacces|permission denied|operation not permitted|eperm/.test(m)) return { error_category: "permission_denied", error_tool: "Bash" };
|
|
203
|
+
if (/syntaxerror|error ts\d+|unexpected token|parse error/.test(m)) return { error_category: "syntax_error", error_tool: "Bash" };
|
|
204
|
+
if (/typeerror|referenceerror|rangeerror|traceback|nullpointer/.test(m)) return { error_category: "runtime_error", error_tool: "Bash" };
|
|
205
|
+
if (/test failed|fail |tests failed|assertion/.test(m)) return { error_category: "test_failed", error_tool: "Bash" };
|
|
206
|
+
return { error_category: "unknown", error_tool: "Bash" };
|
|
207
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
6
|
+
"version": "1.0.158",
|
|
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.
|
|
3
|
+
"version": "1.0.158",
|
|
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",
|