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.
@@ -1,15 +1,15 @@
1
- var Z="127.0.0.1",z=["\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 Q(e){let s=0;for(let o=0;o<e.length;o++)s=(s<<5)-s+e.charCodeAt(o)|0;return z[Math.abs(s)%z.length]}var V="\u{1F99E}",ee="\u2328\uFE0F",ne="Claude Code Session",te="\u{1F980}";function re(e){let s=e?.primary??V,o=e?.claudeCode??ee,a=e?.claudeCodeLabel??ne,c=e?.default??te,d=e?.agents??{};return function(m){if(!m)return c;if(m.startsWith("openclaw-")){let p=m.slice(9);return p?`${d[p]||Q(p)} ${p}`:`${s} openclaw`}if(m==="openclaw")return`${s} openclaw`;let b=a.trim();return b?`${o} ${b} (${m})`:`${o} ${m}`}}var W=Z;function T(e){return`http://${W}:${e}`}var se=3,Y=3e4,h="CLOSED",N=0,H=0,P=!1;function J(e){return h==="CLOSED"?!0:h==="OPEN"?Date.now()-H>=Y?(h="HALF_OPEN",e.info("[claude-mem] Circuit breaker: probing worker connection"),P?!1:(P=!0,!0)):!1:P?!1:(P=!0,!0)}function G(e){h!=="CLOSED"&&e.info("[claude-mem] Worker connection restored \u2014 circuit closed"),h="CLOSED",N=0,P=!1}function x(e){P=!1,N++,(h==="HALF_OPEN"||h==="CLOSED"&&N>=se)&&(h="OPEN",H=Date.now(),e.warn(`[claude-mem] Worker unreachable \u2014 disabling requests for ${Y/1e3}s`))}function oe(){h="CLOSED",N=0,H=0,P=!1}async function X(e,s,o,a){if(!J(a))return null;try{let c=await fetch(`${T(e)}${s}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});return c.ok?(G(a),await c.json()):(x(a),a.warn(`[claude-mem] Worker POST ${s} returned ${c.status}`),null)}catch(c){let d=c instanceof Error?c.message:String(c);return x(a),h!=="OPEN"&&a.warn(`[claude-mem] Worker POST ${s} failed: ${d}`),null}}function ie(e,s,o,a){J(a)&&fetch(`${T(e)}${s}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}).then(c=>{if(!c.ok){x(a),a.warn(`[claude-mem] Worker POST ${s} returned ${c.status}`);return}G(a)}).catch(c=>{let d=c instanceof Error?c.message:String(c);x(a),h!=="OPEN"&&a.warn(`[claude-mem] Worker POST ${s} failed: ${d}`)})}async function q(e,s,o){if(!J(o))return null;try{let a=await fetch(`${T(e)}${s}`);return a.ok?(G(o),await a.text()):(x(o),o.warn(`[claude-mem] Worker GET ${s} returned ${a.status}`),null)}catch(a){let c=a instanceof Error?a.message:String(a);return x(o),h!=="OPEN"&&o.warn(`[claude-mem] Worker GET ${s} failed: ${c}`),null}}async function K(e,s,o){let a=await q(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 ae(e,s){let o=e.title||"Untitled",c=`${s(e.project)}
2
- **${o}**`;return e.subtitle&&(c+=`
3
- ${e.subtitle}`),c}var ce={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 le(e,s,o,a){try{let c=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(!c.ok){let d=await c.text();a.warn(`[claude-mem] Direct Telegram send failed (${c.status}): ${d}`)}}catch(c){let d=c instanceof Error?c.message:String(c);a.warn(`[claude-mem] Direct Telegram send error: ${d}`)}}function ue(e,s,o,a,c){if(c&&s==="telegram")return le(c,o,a,e.logger);let d=ce[s];if(!d)return e.logger.warn(`[claude-mem] Unsupported channel type: ${s}`),Promise.resolve();let w=e.runtime.channel[d.namespace];if(!w)return e.logger.warn(`[claude-mem] Channel "${s}" not available in runtime`),Promise.resolve();let m=w[d.functionName];return m?m(...s==="whatsapp"?[o,a,{verbose:!1}]:[o,a]).catch(p=>{let v=p instanceof Error?p.message:String(p);e.logger.error(`[claude-mem] Failed to send to ${s}: ${v}`)}):(e.logger.warn(`[claude-mem] Channel "${s}" has no ${d.functionName} function`),Promise.resolve())}async function de(e,s,o,a,c,d,w,m){let b=1e3,p=3e4;for(;!c.signal.aborted;){try{d("reconnecting"),e.logger.info(`[claude-mem] Connecting to SSE stream at ${T(s)}/stream`);let v=await fetch(`${T(s)}/stream`,{signal:c.signal,headers:{Accept:"text/event-stream"}});if(!v.ok)throw new Error(`SSE stream returned HTTP ${v.status}`);if(!v.body)throw new Error("SSE stream response has no body");d("connected"),b=1e3,e.logger.info("[claude-mem] Connected to SSE stream");let M=v.body.getReader(),j=new TextDecoder,_="";for(;;){let{done:R,value:k}=await M.read();if(R)break;_+=j.decode(k,{stream:!0}),_.length>1048576&&(e.logger.warn("[claude-mem] SSE buffer overflow, clearing buffer"),_="");let L=_.split(`
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 D of L){let F=D.split(`
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 O=ae(S.observation,w);await ue(e,o,a,O,m)}}catch(S){let C=S instanceof Error?S.message:String(S);e.logger.warn(`[claude-mem] Failed to parse SSE frame: ${C}`)}}}}catch(v){if(c.signal.aborted)break;d("reconnecting");let M=v instanceof Error?v.message:String(v);e.logger.warn(`[claude-mem] SSE stream error: ${M}. Reconnecting in ${b/1e3}s`)}if(c.signal.aborted)break;await new Promise(v=>setTimeout(v,b)),b=Math.min(b*2,p)}d("disconnected")}function ge(e){let s=e.pluginConfig||{},o=s.workerPort||37777;W=s.workerHost||Z;let a=s.project||"openclaw",c=re(s.observationFeed?.emojis);function d(r){return r.agentId?`openclaw-${r.agentId}`:a}let w=new Map,m=new Map,b=new Map,p=new Map,v=s.syncMemoryFile!==!1,M=new Set(s.syncMemoryFileExclude||[]);function j(r){let n=r||"default";return w.has(n)||w.set(n,`openclaw-${n}-${Date.now()}`),w.get(n)}function _(r){if(!v)return!1;let n=r?.agentId;return!(n&&M.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 k(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 l=j(t);for(let u of i)w.set(u,l);return{canonicalKey:t,contentSessionId:l}}function L(r,n,t){let i=Date.now();for(let[g,f]of p)i-f>2e3&&p.delete(g);let l=`${r}::${n}::${t}`,u=p.get(l);return p.set(l,i),typeof u=="number"&&i-u<=2e3}function D(r){let n=R(r),t=n.map(l=>m.get(l)).find(Boolean)||n[0],i=b.get(t)||new Set([t,...n]);for(let l of i)m.delete(l),w.delete(l);b.delete(t),w.delete(t)}let F=6e4,A=new Map;async function S(r){let n=[a],t=r?d(r):null;t&&t!==a&&n.push(t);let i=n.join(","),l=A.get(i);if(l&&Date.now()-l.fetchedAt<F)return l.text;let u=await q(o,`/api/context/inject?projects=${encodeURIComponent(i)}`,e.logger);if(u&&u.trim().length>0){let g=u.trim();return A.set(i,{text:g,fetchedAt:Date.now()}),g}return null}e.on("session_start",async(r,n)=>{let{contentSessionId:t}=k(n);e.logger.info(`[claude-mem] Session tracking initialized: ${t}`)}),e.on("message_received",async(r,n)=>{let{canonicalKey:t,contentSessionId:i}=k(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)=>{let{contentSessionId:t}=k(n);e.logger.info(`[claude-mem] Session preserved after compaction: ${t}`)}),e.on("before_agent_start",async(r,n)=>{let{contentSessionId:t}=k(n),i=d(n),l=r.prompt||"agent run";if(L(t,i,l)){e.logger.info(`[claude-mem] Skipping duplicate prompt init: contentSessionId=${t} project=${i}`);return}await X(o,"/api/sessions/init",{contentSessionId:t,project:i,prompt:l},e.logger),e.logger.info(`[claude-mem] Session initialized via before_agent_start: contentSessionId=${t} project=${i}`)}),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:l}=k(n),u="",g=r.message?.content;Array.isArray(g)&&(u=g.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 y=n.workspaceDir;if(!y){e.logger.warn(`[claude-mem] Skipping observation persist because workspaceDir is unavailable: session=${i} tool=${t}`);return}ie(o,"/api/sessions/observations",{contentSessionId:l,tool_name:t,tool_input:r.params||{},tool_response:u,cwd:y},e.logger)}),e.on("agent_end",async(r,n)=>{let{contentSessionId:t}=k(n),i="";if(Array.isArray(r.messages))for(let l=r.messages.length-1;l>=0;l--){let u=r.messages[l];if(u?.role==="assistant"){typeof u.content=="string"?i=u.content:Array.isArray(u.content)&&(i=u.content.filter(g=>g.type==="text").map(g=>g.text||"").join(`
9
- `));break}}await X(o,"/api/sessions/summarize",{contentSessionId:t,last_assistant_message:i},e.logger)}),e.on("session_end",async(r,n)=>{D(n),e.logger.info("[claude-mem] Session tracking cleaned up")}),e.on("gateway_start",async()=>{oe(),w.clear(),A.clear(),p.clear(),m.clear(),b.clear(),e.logger.info("[claude-mem] Gateway started \u2014 session tracking reset")});let C=null,O="disconnected",$=null;e.registerService({id:"claude-mem-observation-feed",start:async r=>{C&&(C.abort(),$&&(await $,$=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,$=de(e,o,n.channel,n.to,C,t=>{O=t},c,n.botToken)},stop:async r=>{C&&(C.abort(),C=null),$&&(await $,$=null),O="disconnected",e.logger.info("[claude-mem] Observation feed stopped \u2014 SSE connection closed")}});function B(r,n=5){return!Array.isArray(r)||r.length===0?"No results found.":r.slice(0,n).map((t,i)=>{let l=t,u=String(l.title||l.subtitle||l.text||"Untitled"),g=l.project?` [${String(l.project)}]`:"";return`${i+1}. ${u}${g}`}).join(`
10
- `)}function I(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: ${O}`].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],l=/^\d+$/.test(i),u=l?I(i,10):10,g=l?t.slice(0,-1).join(" "):n,f=await K(o,`/api/search/observations?query=${encodeURIComponent(g)}&limit=${u}`,e.logger);if(!f)return"Claude-Mem search failed (worker unavailable or invalid response).";let y=Array.isArray(f.items)?f.items:[];return[`Claude-Mem Search: "${g}"`,B(y,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]:"",l=/^\d+$/.test(i),u=l?I(i,3):3,g=l?t.slice(0,-1).join(" "):n,f=new URLSearchParams;f.set("limit",String(u)),g&&f.set("project",g);let y=await K(o,`/api/context/recent?${f.toString()}`,e.logger);if(!y)return"Claude-Mem recent context failed (worker unavailable or invalid response).";let E=Array.isArray(y.session_summaries)?y.session_summaries:[],U=Array.isArray(y.recent_observations)?y.recent_observations:[];return["Claude-Mem Recent Context",`Project: ${g||"(auto)"}`,`Session summaries: ${E.length}`,`Recent observations: ${U.length}`,B(U,Math.min(5,U.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,l=5;t.length>=2&&/^\d+$/.test(t[t.length-1])&&(i=I(t.pop(),5)),t.length>=2&&/^\d+$/.test(t[t.length-1])&&(l=I(t.pop(),5));let u=t.join(" "),g=new URLSearchParams({query:u,mode:"auto",depth_before:String(l),depth_after:String(i)}),f=await K(o,`/api/timeline/by-query?${g.toString()}`,e.logger);if(!f)return"Claude-Mem timeline lookup failed (worker unavailable or invalid response).";let y=Array.isArray(f.timeline)?f.timeline:[],E=f.anchor?String(f.anchor):"(none)";return[`Claude-Mem Timeline: "${u}"`,`Anchor: ${E}`,B(y,8)].join(`
14
- `)}}),e.registerCommand({name:"claude_mem_status",description:"Check Claude-Mem worker health and session status",handler:async()=>{let r=await q(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: ${w.size}`,`Observation feed: ${O}`].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: ${W}:${o})`)}export{ge as default};
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.3.0",
6
+ "version": "13.4.0",
7
7
  "license": "Apache-2.0",
8
8
  "author": "thedotmack",
9
9
  "homepage": "https://claude-mem.ai",
@@ -700,28 +700,17 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
700
700
  return null;
701
701
  }
702
702
 
703
- api.on("session_start", async (_event, ctx) => {
704
- const { contentSessionId } = rememberSessionContext(ctx);
705
- api.logger.info(`[claude-mem] Session tracking initialized: ${contentSessionId}`);
706
- });
707
-
708
- api.on("message_received", async (event, ctx) => {
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 before_agent_start: contentSessionId=${contentSessionId} project=${projectName}`);
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
- const workspaceDir = ctx.workspaceDir;
771
-
772
- if (!workspaceDir) {
773
- api.logger.warn(`[claude-mem] Skipping observation persist because workspaceDir is unavailable: session=${canonicalKey} tool=${toolName}`);
774
- return;
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.0",
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
- "prepublishOnly": "npm run build",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem",
3
- "version": "13.3.0",
3
+ "version": "13.4.0",
4
4
  "description": "Memory compression system for Claude Code - persist context across sessions",
5
5
  "author": {
6
6
  "name": "Alex Newman"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem",
3
- "version": "13.3.0",
3
+ "version": "13.4.0",
4
4
  "description": "Memory compression system for Claude Code - persist context across sessions",
5
5
  "author": {
6
6
  "name": "Alex Newman",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-plugin",
3
- "version": "13.3.0",
3
+ "version": "13.4.0",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for claude-mem bundled hooks",
6
6
  "type": "module",
@@ -168,7 +168,14 @@ if (child.stdin) {
168
168
  ` CLAUDE_PLUGIN_ROOT: ${RESOLVED_PLUGIN_ROOT}`,
169
169
  ].join('\n');
170
170
 
171
- // Write to stderr so Claude Code surfaces the diagnostic.
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
  });