context-mode 1.0.157 → 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/session-db.bundle.mjs +23 -6
- package/hooks/session-loaders.mjs +93 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +130 -113
|
@@ -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,13 +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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
);
|
|
119
|
+
const enriched = enrichEventForPlatform(events[i], attributions[i]);
|
|
120
|
+
const payload = rollup ? { ...enriched, ...rollup } : enriched;
|
|
121
|
+
maybeForward({ ...payload, session_id: sessionId }, platform);
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
return attributions;
|
|
118
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",
|