claude-mem 13.3.0 → 13.4.0
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/.codex-plugin/plugin.json +1 -1
- package/dist/npx-cli/index.js +401 -384
- package/dist/opencode-plugin/index.js +34 -33
- package/openclaw/dist/index.js +13 -13
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/src/index.ts +30 -24
- package/package.json +5 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/plugin/scripts/bun-runner.js +12 -1
- package/plugin/scripts/context-generator.cjs +27 -25
- package/plugin/scripts/mcp-server.cjs +29 -29
- package/plugin/scripts/server-beta-service.cjs +162 -138
- package/plugin/scripts/worker-service.cjs +334 -9712
- package/plugin/ui/viewer-bundle.js +1 -1
package/openclaw/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
var
|
|
2
|
-
**${o}**`;return e.subtitle&&(
|
|
3
|
-
${e.subtitle}`),
|
|
1
|
+
var Y="127.0.0.1",X=["\u{1F527}","\u{1F4D0}","\u{1F50D}","\u{1F4BB}","\u{1F9EA}","\u{1F41B}","\u{1F6E1}\uFE0F","\u2601\uFE0F","\u{1F4E6}","\u{1F3AF}","\u{1F52E}","\u26A1","\u{1F30A}","\u{1F3A8}","\u{1F4CA}","\u{1F680}","\u{1F52C}","\u{1F3D7}\uFE0F","\u{1F4DD}","\u{1F3AD}"];function V(e){let s=0;for(let o=0;o<e.length;o++)s=(s<<5)-s+e.charCodeAt(o)|0;return X[Math.abs(s)%X.length]}var ee="\u{1F99E}",ne="\u2328\uFE0F",te="Claude Code Session",re="\u{1F980}";function se(e){let s=e?.primary??ee,o=e?.claudeCode??ne,a=e?.claudeCodeLabel??te,l=e?.default??re,g=e?.agents??{};return function(m){if(!m)return l;if(m.startsWith("openclaw-")){let p=m.slice(9);return p?`${g[p]||V(p)} ${p}`:`${s} openclaw`}if(m==="openclaw")return`${s} openclaw`;let b=a.trim();return b?`${o} ${b} (${m})`:`${o} ${m}`}}var q=Y;function T(e){return`http://${q}:${e}`}var oe=3,Q=3e4,y="CLOSED",j=0,J=0,$=!1;function G(e){return y==="CLOSED"?!0:y==="OPEN"?Date.now()-J>=Q?(y="HALF_OPEN",e.info("[claude-mem] Circuit breaker: probing worker connection"),$?!1:($=!0,!0)):!1:$?!1:($=!0,!0)}function z(e){y!=="CLOSED"&&e.info("[claude-mem] Worker connection restored \u2014 circuit closed"),y="CLOSED",j=0,$=!1}function M(e){$=!1,j++,(y==="HALF_OPEN"||y==="CLOSED"&&j>=oe)&&(y="OPEN",J=Date.now(),e.warn(`[claude-mem] Worker unreachable \u2014 disabling requests for ${Q/1e3}s`))}function ie(){y="CLOSED",j=0,J=0,$=!1}async function Z(e,s,o,a){if(!G(a))return null;try{let l=await fetch(`${T(e)}${s}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});return l.ok?(z(a),await l.json()):(M(a),a.warn(`[claude-mem] Worker POST ${s} returned ${l.status}`),null)}catch(l){let g=l instanceof Error?l.message:String(l);return M(a),y!=="OPEN"&&a.warn(`[claude-mem] Worker POST ${s} failed: ${g}`),null}}function ae(e,s,o,a){G(a)&&fetch(`${T(e)}${s}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}).then(l=>{if(!l.ok){M(a),a.warn(`[claude-mem] Worker POST ${s} returned ${l.status}`);return}z(a)}).catch(l=>{let g=l instanceof Error?l.message:String(l);M(a),y!=="OPEN"&&a.warn(`[claude-mem] Worker POST ${s} failed: ${g}`)})}async function H(e,s,o){if(!G(o))return null;try{let a=await fetch(`${T(e)}${s}`);return a.ok?(z(o),await a.text()):(M(o),o.warn(`[claude-mem] Worker GET ${s} returned ${a.status}`),null)}catch(a){let l=a instanceof Error?a.message:String(a);return M(o),y!=="OPEN"&&o.warn(`[claude-mem] Worker GET ${s} failed: ${l}`),null}}async function W(e,s,o){let a=await H(e,s,o);if(!a)return null;try{return JSON.parse(a)}catch{return o.warn(`[claude-mem] Worker GET ${s} returned non-JSON response`),null}}function ce(e,s){let o=e.title||"Untitled",l=`${s(e.project)}
|
|
2
|
+
**${o}**`;return e.subtitle&&(l+=`
|
|
3
|
+
${e.subtitle}`),l}var le={telegram:{namespace:"telegram",functionName:"sendMessageTelegram"},whatsapp:{namespace:"whatsapp",functionName:"sendMessageWhatsApp"},discord:{namespace:"discord",functionName:"sendMessageDiscord"},slack:{namespace:"slack",functionName:"sendMessageSlack"},signal:{namespace:"signal",functionName:"sendMessageSignal"},imessage:{namespace:"imessage",functionName:"sendMessageIMessage"},line:{namespace:"line",functionName:"sendMessageLine"}};async function ue(e,s,o,a){try{let l=await fetch(`https://api.telegram.org/bot${e}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:s,text:o,parse_mode:"Markdown"})});if(!l.ok){let g=await l.text();a.warn(`[claude-mem] Direct Telegram send failed (${l.status}): ${g}`)}}catch(l){let g=l instanceof Error?l.message:String(l);a.warn(`[claude-mem] Direct Telegram send error: ${g}`)}}function ge(e,s,o,a,l){if(l&&s==="telegram")return ue(l,o,a,e.logger);let g=le[s];if(!g)return e.logger.warn(`[claude-mem] Unsupported channel type: ${s}`),Promise.resolve();let v=e.runtime.channel[g.namespace];if(!v)return e.logger.warn(`[claude-mem] Channel "${s}" not available in runtime`),Promise.resolve();let m=v[g.functionName];return m?m(...s==="whatsapp"?[o,a,{verbose:!1}]:[o,a]).catch(p=>{let w=p instanceof Error?p.message:String(p);e.logger.error(`[claude-mem] Failed to send to ${s}: ${w}`)}):(e.logger.warn(`[claude-mem] Channel "${s}" has no ${g.functionName} function`),Promise.resolve())}async function de(e,s,o,a,l,g,v,m){let b=1e3,p=3e4;for(;!l.signal.aborted;){try{g("reconnecting"),e.logger.info(`[claude-mem] Connecting to SSE stream at ${T(s)}/stream`);let w=await fetch(`${T(s)}/stream`,{signal:l.signal,headers:{Accept:"text/event-stream"}});if(!w.ok)throw new Error(`SSE stream returned HTTP ${w.status}`);if(!w.body)throw new Error("SSE stream response has no body");g("connected"),b=1e3,e.logger.info("[claude-mem] Connected to SSE stream");let x=w.body.getReader(),D=new TextDecoder,_="";for(;;){let{done:R,value:P}=await x.read();if(R)break;_+=D.decode(P,{stream:!0}),_.length>1048576&&(e.logger.warn("[claude-mem] SSE buffer overflow, clearing buffer"),_="");let L=_.split(`
|
|
4
4
|
|
|
5
|
-
`);_=L.pop()||"";for(let
|
|
5
|
+
`);_=L.pop()||"";for(let B of L){let F=B.split(`
|
|
6
6
|
`).filter(S=>S.startsWith("data:")).map(S=>S.slice(5).trim());if(F.length===0)continue;let A=F.join(`
|
|
7
|
-
`);if(A)try{let S=JSON.parse(A);if(S.type==="new_observation"&&S.observation){let
|
|
8
|
-
`));let f=1e3;u.length>f&&(u=u.slice(0,f));let
|
|
9
|
-
`));break}}await
|
|
10
|
-
`)}function
|
|
11
|
-
`)}}}),e.registerCommand({name:"claude-mem-search",description:"Search Claude-Mem observations by query",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-search <query> [limit]";let t=n.split(/\s+/),i=t[t.length-1],
|
|
12
|
-
`)}}),e.registerCommand({name:"claude-mem-recent",description:"Show recent Claude-Mem context for a project",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"",t=n?n.split(/\s+/):[],i=t.length>0?t[t.length-1]:"",
|
|
13
|
-
`)}}),e.registerCommand({name:"claude-mem-timeline",description:"Find best memory match and show nearby timeline events",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-timeline <query> [depthBefore] [depthAfter]";let t=n.split(/\s+/),i=5,
|
|
14
|
-
`)}}),e.registerCommand({name:"claude_mem_status",description:"Check Claude-Mem worker health and session status",handler:async()=>{let r=await
|
|
15
|
-
`)}}catch{return{text:"Claude-Mem worker responded but returned unexpected data"}}}}),e.logger.info(`[claude-mem] OpenClaw plugin loaded \u2014 v1.0.0 (worker: ${
|
|
7
|
+
`);if(A)try{let S=JSON.parse(A);if(S.type==="new_observation"&&S.observation){let C=ce(S.observation,v);await ge(e,o,a,C,m)}}catch(S){let O=S instanceof Error?S.message:String(S);e.logger.warn(`[claude-mem] Failed to parse SSE frame: ${O}`)}}}}catch(w){if(l.signal.aborted)break;g("reconnecting");let x=w instanceof Error?w.message:String(w);e.logger.warn(`[claude-mem] SSE stream error: ${x}. Reconnecting in ${b/1e3}s`)}if(l.signal.aborted)break;await new Promise(w=>setTimeout(w,b)),b=Math.min(b*2,p)}g("disconnected")}function me(e){let s=e.pluginConfig||{},o=s.workerPort||37777;q=s.workerHost||Y;let a=s.project||"openclaw",l=se(s.observationFeed?.emojis);function g(r){return r.agentId?`openclaw-${r.agentId}`:a}let v=new Map,m=new Map,b=new Map,p=new Map,w=s.syncMemoryFile!==!1,x=new Set(s.syncMemoryFileExclude||[]);function D(r){let n=r||"default";return v.has(n)||v.set(n,`openclaw-${n}-${Date.now()}`),v.get(n)}function _(r){if(!w)return!1;let n=r?.agentId;return!(n&&x.has(n))}function R(r){let n=new Set;for(let t of[r.sessionKey,r.conversationId,r.channelId]){let i=typeof t=="string"?t.trim():"";i&&n.add(i)}return n.size===0&&n.add("default"),Array.from(n)}function P(r){let n=R(r),t=n.find(u=>m.has(u));t=t?m.get(t):n[0];let i=b.get(t);i||(i=new Set([t]),b.set(t,i));for(let u of n)i.add(u),m.set(u,t);let c=D(t);for(let u of i)v.set(u,c);return{canonicalKey:t,contentSessionId:c}}function L(r,n,t){let i=Date.now();for(let[d,f]of p)i-f>2e3&&p.delete(d);let c=`${r}::${n}::${t}`,u=p.get(c);return p.set(c,i),typeof u=="number"&&i-u<=2e3}function B(r){let n=R(r),t=n.map(c=>m.get(c)).find(Boolean)||n[0],i=b.get(t)||new Set([t,...n]);for(let c of i)m.delete(c),v.delete(c);b.delete(t),v.delete(t)}let F=6e4,A=new Map;async function S(r){let n=[a],t=r?g(r):null;t&&t!==a&&n.push(t);let i=n.join(","),c=A.get(i);if(c&&Date.now()-c.fetchedAt<F)return c.text;let u=await H(o,`/api/context/inject?projects=${encodeURIComponent(i)}`,e.logger);if(u&&u.trim().length>0){let d=u.trim();return A.set(i,{text:d,fetchedAt:Date.now()}),d}return null}async function O(r,n,t){let{contentSessionId:i}=P(r),c=g(r);if(L(i,c,n)){e.logger.info(`[claude-mem] Skipping duplicate prompt init: contentSessionId=${i} project=${c} via=${t}`);return}await Z(o,"/api/sessions/init",{contentSessionId:i,project:c,prompt:n},e.logger),e.logger.info(`[claude-mem] Session initialized via ${t}: contentSessionId=${i} project=${c}`)}e.on("session_start",async(r,n)=>{await O(n,"session start","session_start")}),e.on("message_received",async(r,n)=>{let{canonicalKey:t,contentSessionId:i}=P(n);e.logger.info(`[claude-mem] Message received \u2014 prompt capture deferred to before_agent_start: session=${t} contentSessionId=${i} hasContent=${!!r.content}`)}),e.on("after_compaction",async(r,n)=>{await O(n,"after compaction","after_compaction")}),e.on("before_agent_start",async(r,n)=>{await O(n,r.prompt||"agent run","before_agent_start")}),e.on("before_prompt_build",async(r,n)=>{if(!_(n))return;let t=await S(n);if(t)return e.logger.info(`[claude-mem] Context injected via system prompt for agent=${n.agentId??"unknown"}`),{appendSystemContext:t}}),e.on("tool_result_persist",(r,n)=>{e.logger.info(`[claude-mem] tool_result_persist fired: tool=${r.toolName??"unknown"} agent=${n.agentId??"none"} session=${n.sessionKey??"none"}`);let t=r.toolName;if(!t||t.startsWith("memory_"))return;let{canonicalKey:i,contentSessionId:c}=P(n),u="",d=r.message?.content;Array.isArray(d)&&(u=d.filter(E=>(E.type==="tool_result"||E.type==="text")&&"text"in E).map(E=>String(E.text)).join(`
|
|
8
|
+
`));let f=1e3;u.length>f&&(u=u.slice(0,f));let h=n.workspaceDir||process.cwd();n.workspaceDir||e.logger.info(`[claude-mem] tool_result_persist missing workspaceDir; using process.cwd(): session=${i} tool=${t}`),ae(o,"/api/sessions/observations",{contentSessionId:c,tool_name:t,tool_input:r.params||{},tool_response:u,cwd:h},e.logger)}),e.on("agent_end",async(r,n)=>{let{contentSessionId:t}=P(n),i="";if(Array.isArray(r.messages))for(let c=r.messages.length-1;c>=0;c--){let u=r.messages[c];if(u?.role==="assistant"){typeof u.content=="string"?i=u.content:Array.isArray(u.content)&&(i=u.content.filter(d=>d.type==="text").map(d=>d.text||"").join(`
|
|
9
|
+
`));break}}await Z(o,"/api/sessions/summarize",{contentSessionId:t,last_assistant_message:i},e.logger)}),e.on("session_end",async(r,n)=>{B(n),e.logger.info("[claude-mem] Session tracking cleaned up")}),e.on("gateway_start",async()=>{ie(),v.clear(),A.clear(),p.clear(),m.clear(),b.clear(),e.logger.info("[claude-mem] Gateway started \u2014 session tracking reset")});let C=null,I="disconnected",k=null;e.registerService({id:"claude-mem-observation-feed",start:async r=>{C&&(C.abort(),k&&(await k,k=null));let n=s.observationFeed;if(!n?.enabled){e.logger.info("[claude-mem] Observation feed disabled");return}if(!n.channel||!n.to){e.logger.warn("[claude-mem] Observation feed misconfigured \u2014 channel or target missing");return}e.logger.info(`[claude-mem] Observation feed starting \u2014 channel: ${n.channel}, target: ${n.to}`),C=new AbortController,k=de(e,o,n.channel,n.to,C,t=>{I=t},l,n.botToken)},stop:async r=>{C&&(C.abort(),C=null),k&&(await k,k=null),I="disconnected",e.logger.info("[claude-mem] Observation feed stopped \u2014 SSE connection closed")}});function U(r,n=5){return!Array.isArray(r)||r.length===0?"No results found.":r.slice(0,n).map((t,i)=>{let c=t,u=String(c.title||c.subtitle||c.text||"Untitled"),d=c.project?` [${String(c.project)}]`:"";return`${i+1}. ${u}${d}`}).join(`
|
|
10
|
+
`)}function N(r,n=10){let t=Number(r);return Number.isFinite(t)?Math.max(1,Math.min(50,Math.trunc(t))):n}e.registerCommand({name:"claude_mem_feed",description:"Show or toggle Claude-Mem observation feed status",acceptsArgs:!0,handler:async r=>{let n=s.observationFeed;if(!n)return{text:"Observation feed not configured. Add observationFeed to your plugin config."};let t=r.args?.trim();return t==="on"?(e.logger.info("[claude-mem] Feed enable requested via command"),{text:"Feed enable requested. Update observationFeed.enabled in your plugin config to persist."}):t==="off"?(e.logger.info("[claude-mem] Feed disable requested via command"),{text:"Feed disable requested. Update observationFeed.enabled in your plugin config to persist."}):{text:["Claude-Mem Observation Feed",`Enabled: ${n.enabled?"yes":"no"}`,`Channel: ${n.channel||"not set"}`,`Target: ${n.to||"not set"}`,`Connection: ${I}`].join(`
|
|
11
|
+
`)}}}),e.registerCommand({name:"claude-mem-search",description:"Search Claude-Mem observations by query",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-search <query> [limit]";let t=n.split(/\s+/),i=t[t.length-1],c=/^\d+$/.test(i),u=c?N(i,10):10,d=c?t.slice(0,-1).join(" "):n,f=await W(o,`/api/search/observations?query=${encodeURIComponent(d)}&limit=${u}`,e.logger);if(!f)return"Claude-Mem search failed (worker unavailable or invalid response).";let h=Array.isArray(f.items)?f.items:[];return[`Claude-Mem Search: "${d}"`,U(h,u)].join(`
|
|
12
|
+
`)}}),e.registerCommand({name:"claude-mem-recent",description:"Show recent Claude-Mem context for a project",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"",t=n?n.split(/\s+/):[],i=t.length>0?t[t.length-1]:"",c=/^\d+$/.test(i),u=c?N(i,3):3,d=c?t.slice(0,-1).join(" "):n,f=new URLSearchParams;f.set("limit",String(u)),d&&f.set("project",d);let h=await W(o,`/api/context/recent?${f.toString()}`,e.logger);if(!h)return"Claude-Mem recent context failed (worker unavailable or invalid response).";let E=Array.isArray(h.session_summaries)?h.session_summaries:[],K=Array.isArray(h.recent_observations)?h.recent_observations:[];return["Claude-Mem Recent Context",`Project: ${d||"(auto)"}`,`Session summaries: ${E.length}`,`Recent observations: ${K.length}`,U(K,Math.min(5,K.length||5))].join(`
|
|
13
|
+
`)}}),e.registerCommand({name:"claude-mem-timeline",description:"Find best memory match and show nearby timeline events",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-timeline <query> [depthBefore] [depthAfter]";let t=n.split(/\s+/),i=5,c=5;t.length>=2&&/^\d+$/.test(t[t.length-1])&&(i=N(t.pop(),5)),t.length>=2&&/^\d+$/.test(t[t.length-1])&&(c=N(t.pop(),5));let u=t.join(" "),d=new URLSearchParams({query:u,mode:"auto",depth_before:String(c),depth_after:String(i)}),f=await W(o,`/api/timeline/by-query?${d.toString()}`,e.logger);if(!f)return"Claude-Mem timeline lookup failed (worker unavailable or invalid response).";let h=Array.isArray(f.timeline)?f.timeline:[],E=f.anchor?String(f.anchor):"(none)";return[`Claude-Mem Timeline: "${u}"`,`Anchor: ${E}`,U(h,8)].join(`
|
|
14
|
+
`)}}),e.registerCommand({name:"claude_mem_status",description:"Check Claude-Mem worker health and session status",handler:async()=>{let r=await H(o,"/api/health",e.logger);if(!r)return{text:`Claude-Mem worker unreachable at port ${o}`};try{return{text:["Claude-Mem Worker Status",`Status: ${JSON.parse(r).status||"unknown"}`,`Port: ${o}`,`Active sessions: ${v.size}`,`Observation feed: ${I}`].join(`
|
|
15
|
+
`)}}catch{return{text:"Claude-Mem worker responded but returned unexpected data"}}}}),e.logger.info(`[claude-mem] OpenClaw plugin loaded \u2014 v1.0.0 (worker: ${q}:${o})`)}export{me as default};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Claude-Mem (Persistent Memory)",
|
|
4
4
|
"description": "OpenClaw plugin for Claude-Mem. Records observations from embedded runner sessions and streams them to messaging channels.",
|
|
5
5
|
"kind": "memory",
|
|
6
|
-
"version": "13.
|
|
6
|
+
"version": "13.4.0",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"author": "thedotmack",
|
|
9
9
|
"homepage": "https://claude-mem.ai",
|
package/openclaw/src/index.ts
CHANGED
|
@@ -700,28 +700,17 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
700
700
|
return null;
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const { canonicalKey, contentSessionId } = rememberSessionContext(ctx);
|
|
710
|
-
api.logger.info(`[claude-mem] Message received — prompt capture deferred to before_agent_start: session=${canonicalKey} contentSessionId=${contentSessionId} hasContent=${Boolean(event.content)}`);
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
api.on("after_compaction", async (_event, ctx) => {
|
|
714
|
-
const { contentSessionId } = rememberSessionContext(ctx);
|
|
715
|
-
api.logger.info(`[claude-mem] Session preserved after compaction: ${contentSessionId}`);
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
api.on("before_agent_start", async (event, ctx) => {
|
|
703
|
+
// Centralized session-init POST. session_start, after_compaction, and
|
|
704
|
+
// before_agent_start each call this; the 2s dedup guard
|
|
705
|
+
// (shouldSkipDuplicatePromptInit) collapses the redundant inits a single
|
|
706
|
+
// user-message flow produces into one prompt record, while still ensuring a
|
|
707
|
+
// session is initialized even on flows that never reach before_agent_start.
|
|
708
|
+
async function initSessionOnce(ctx: EventContext, promptText: string, via: string): Promise<void> {
|
|
719
709
|
const { contentSessionId } = rememberSessionContext(ctx);
|
|
720
710
|
const projectName = getProjectName(ctx);
|
|
721
|
-
const promptText = event.prompt || "agent run";
|
|
722
711
|
|
|
723
712
|
if (shouldSkipDuplicatePromptInit(contentSessionId, projectName, promptText)) {
|
|
724
|
-
api.logger.info(`[claude-mem] Skipping duplicate prompt init: contentSessionId=${contentSessionId} project=${projectName}`);
|
|
713
|
+
api.logger.info(`[claude-mem] Skipping duplicate prompt init: contentSessionId=${contentSessionId} project=${projectName} via=${via}`);
|
|
725
714
|
return;
|
|
726
715
|
}
|
|
727
716
|
|
|
@@ -731,7 +720,24 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
731
720
|
prompt: promptText,
|
|
732
721
|
}, api.logger);
|
|
733
722
|
|
|
734
|
-
api.logger.info(`[claude-mem] Session initialized via
|
|
723
|
+
api.logger.info(`[claude-mem] Session initialized via ${via}: contentSessionId=${contentSessionId} project=${projectName}`);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
api.on("session_start", async (_event, ctx) => {
|
|
727
|
+
await initSessionOnce(ctx, "session start", "session_start");
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
api.on("message_received", async (event, ctx) => {
|
|
731
|
+
const { canonicalKey, contentSessionId } = rememberSessionContext(ctx);
|
|
732
|
+
api.logger.info(`[claude-mem] Message received — prompt capture deferred to before_agent_start: session=${canonicalKey} contentSessionId=${contentSessionId} hasContent=${Boolean(event.content)}`);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
api.on("after_compaction", async (_event, ctx) => {
|
|
736
|
+
await initSessionOnce(ctx, "after compaction", "after_compaction");
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
api.on("before_agent_start", async (event, ctx) => {
|
|
740
|
+
await initSessionOnce(ctx, event.prompt || "agent run", "before_agent_start");
|
|
735
741
|
});
|
|
736
742
|
|
|
737
743
|
api.on("before_prompt_build", async (_event, ctx) => {
|
|
@@ -767,11 +773,11 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
767
773
|
toolResponseText = toolResponseText.slice(0, MAX_TOOL_RESPONSE_LENGTH);
|
|
768
774
|
}
|
|
769
775
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
776
|
+
// Fall back to the process cwd when the event carries no workspaceDir, so a
|
|
777
|
+
// missing ctx field never silently drops a captured observation.
|
|
778
|
+
const workspaceDir = ctx.workspaceDir || process.cwd();
|
|
779
|
+
if (!ctx.workspaceDir) {
|
|
780
|
+
api.logger.info(`[claude-mem] tool_result_persist missing workspaceDir; using process.cwd(): session=${canonicalKey} tool=${toolName}`);
|
|
775
781
|
}
|
|
776
782
|
|
|
777
783
|
workerPostFireAndForget(workerPort, "/api/sessions/observations", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.4.0",
|
|
4
4
|
"description": "Memory compression system for Claude Code - persist context across sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -47,13 +47,11 @@
|
|
|
47
47
|
"plugin/.claude-plugin",
|
|
48
48
|
"plugin/.codex-plugin",
|
|
49
49
|
"plugin/.mcp.json",
|
|
50
|
-
"plugin/CLAUDE.md",
|
|
51
50
|
"plugin/package.json",
|
|
52
51
|
"plugin/hooks",
|
|
53
52
|
"plugin/modes",
|
|
54
53
|
"plugin/scripts/*.js",
|
|
55
54
|
"plugin/scripts/*.cjs",
|
|
56
|
-
"plugin/scripts/CLAUDE.md",
|
|
57
55
|
"plugin/skills",
|
|
58
56
|
"plugin/ui",
|
|
59
57
|
"openclaw"
|
|
@@ -98,6 +96,8 @@
|
|
|
98
96
|
"cursor:uninstall": "bun plugin/scripts/worker-service.cjs cursor uninstall",
|
|
99
97
|
"cursor:status": "bun plugin/scripts/worker-service.cjs cursor status",
|
|
100
98
|
"cursor:setup": "bun plugin/scripts/worker-service.cjs cursor setup",
|
|
99
|
+
"lint:hook-io": "node scripts/check-hook-io-discipline.cjs",
|
|
100
|
+
"lint:spawn-env": "node scripts/check-spawn-env-discipline.cjs",
|
|
101
101
|
"typecheck": "tsc --noEmit && tsc --noEmit -p src/ui/viewer/tsconfig.json",
|
|
102
102
|
"typecheck:root": "tsc --noEmit",
|
|
103
103
|
"typecheck:viewer": "tsc --noEmit -p src/ui/viewer/tsconfig.json",
|
|
@@ -109,7 +109,8 @@
|
|
|
109
109
|
"test:infra": "bun test tests/infrastructure/",
|
|
110
110
|
"test:server": "bun test tests/server/",
|
|
111
111
|
"e2e:server-beta:docker": "bash scripts/e2e-server-beta-docker.sh",
|
|
112
|
-
"
|
|
112
|
+
"check:postinstall-allowlist": "node scripts/check-postinstall-allowlist.js",
|
|
113
|
+
"prepublishOnly": "npm run build && node scripts/check-postinstall-allowlist.js",
|
|
113
114
|
"release": "np",
|
|
114
115
|
"release:patch": "np patch --no-cleanup",
|
|
115
116
|
"release:minor": "np minor --no-cleanup",
|
package/plugin/package.json
CHANGED
|
@@ -168,7 +168,14 @@ if (child.stdin) {
|
|
|
168
168
|
` CLAUDE_PLUGIN_ROOT: ${RESOLVED_PLUGIN_ROOT}`,
|
|
169
169
|
].join('\n');
|
|
170
170
|
|
|
171
|
-
//
|
|
171
|
+
// IO discipline (see src/shared/hook-io.ts intent vocabulary):
|
|
172
|
+
// - this stderr write is a USER_HINT (Claude Code surfaces it inline).
|
|
173
|
+
// - the CAPTURE_BROKEN marker file below is a DIAGNOSTIC durable signal for
|
|
174
|
+
// the next session-start hint.
|
|
175
|
+
// - exit 0 below is the EXIT_SIGNAL per CLAUDE.md (Windows Terminal tab
|
|
176
|
+
// management); the marker file, not the exit code, is the durable failure
|
|
177
|
+
// signal. bun-runner runs in its own node process BEFORE hookCommand's
|
|
178
|
+
// stderr buffer is installed, so this write is never swallowed.
|
|
172
179
|
console.error(diagnostic);
|
|
173
180
|
|
|
174
181
|
// Persist diagnostic to the runner-errors log and drop a CAPTURE_BROKEN marker
|
|
@@ -193,6 +200,10 @@ if (child.stdin) {
|
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
child.on('error', (err) => {
|
|
203
|
+
// EXCEPTION to CLAUDE.md exit-0-on-error: Bun-not-found is a user environment
|
|
204
|
+
// problem, not a hook execution failure. Surfacing exit 1 here forces Claude
|
|
205
|
+
// Code to display the stderr message rather than silently retrying. This runs
|
|
206
|
+
// before any hook handler, so the exit-0 tab-management rationale doesn't apply.
|
|
196
207
|
console.error(`Failed to start Bun: ${err.message}`);
|
|
197
208
|
process.exit(1);
|
|
198
209
|
});
|