mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-25 17:15:04 +02:00
* Refactor worker version checks and increase timeout settings - Updated the default hook timeout from 5000ms to 120000ms for improved stability. - Modified the worker version check to log a warning instead of restarting the worker on version mismatch. - Removed legacy PM2 cleanup and worker start logic, simplifying the ensureWorkerRunning function. - Enhanced polling mechanism for worker readiness with increased retries and reduced interval. * feat: implement worker queue polling to ensure processing completion before proceeding * refactor: change worker command from start to restart in hooks configuration * refactor: remove session management complexity - Simplify createSDKSession to pure INSERT OR IGNORE - Remove auto-create logic from storeObservation/storeSummary - Delete 11 unused session management methods - Derive prompt_number from user_prompts count - Keep sdk_sessions table schema unchanged for compatibility * refactor: simplify session management by removing unused methods and auto-creation logic * Refactor session prompt number retrieval in SessionRoutes - Updated the method of obtaining the prompt number from the session. - Replaced `store.getPromptCounter(sessionDbId)` with `store.getPromptNumberFromUserPrompts(claudeSessionId)` for better clarity and accuracy. - Adjusted the logic for incrementing the prompt number to derive it from the user prompts count instead of directly incrementing a counter. * refactor: replace getPromptCounter with getPromptNumberFromUserPrompts in SessionManager Phase 7 of session management simplification. Updates SessionManager to derive prompt numbers from user_prompts table count instead of using the deprecated prompt_counter column. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: simplify SessionCompletionHandler to use direct SQL query Phase 8: Remove call to findActiveSDKSession() and replace with direct database query in SessionCompletionHandler.completeByClaudeId(). This removes dependency on the deleted findActiveSDKSession() method and simplifies the code by using a straightforward SELECT query. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: remove markSessionCompleted call from SDKAgent - Delete call to markSessionCompleted() in SDKAgent.ts - Session status is no longer tracked or updated - Part of phase 9: simplifying session management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: remove markSessionComplete method (Phase 10) - Deleted markSessionComplete() method from DatabaseManager - Removed markSessionComplete call from SessionCompletionHandler - Session completion status no longer tracked in database - Part of session management simplification effort 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: replace deleted updateSDKSessionId calls in import script (Phase 11) - Replace updateSDKSessionId() calls with direct SQL UPDATE statements - Method was deleted in Phase 3 as part of session management simplification - Import script now uses direct database access consistently 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * test: add validation for SQL updates in sdk_sessions table * refactor: enhance worker-cli to support manual and automated runs * Remove cleanup hook and associated session completion logic - Deleted the cleanup-hook implementation from the hooks directory. - Removed the session completion endpoint that was used by the cleanup hook. - Updated the SessionCompletionHandler to eliminate the completeByClaudeId method and its dependencies. - Adjusted the SessionRoutes to reflect the removal of the session completion route. * fix: update worker-cli command to use bun for consistency * feat: Implement timestamp fix for observations and enhance processing logic - Added `earliestPendingTimestamp` to `ActiveSession` to track the original timestamp of the earliest pending message. - Updated `SDKAgent` to capture and utilize the earliest pending timestamp during response processing. - Modified `SessionManager` to track the earliest timestamp when yielding messages. - Created scripts for fixing corrupted timestamps, validating fixes, and investigating timestamp issues. - Verified that all corrupted observations have been repaired and logic for future processing is sound. - Ensured orphan processing can be safely re-enabled after validation. * feat: Enhance SessionStore to support custom database paths and add timestamp fields for observations and summaries * Refactor pending queue processing and add management endpoints - Disabled automatic recovery of orphaned queues on startup; users must now use the new /api/pending-queue/process endpoint. - Updated processOrphanedQueues method to processPendingQueues with improved session handling and return detailed results. - Added new API endpoints for managing pending queues: GET /api/pending-queue and POST /api/pending-queue/process. - Introduced a new script (check-pending-queue.ts) for checking and processing pending observation queues interactively or automatically. - Enhanced logging and error handling for better monitoring of session processing. * updated agent sdk * feat: Add manual recovery guide and queue management endpoints to documentation --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
15 lines
8.2 KiB
JavaScript
Executable File
15 lines
8.2 KiB
JavaScript
Executable File
#!/usr/bin/env bun
|
|
import{stdin as y}from"process";var f=JSON.stringify({continue:!0,suppressOutput:!0});import C from"path";import{homedir as H}from"os";import{readFileSync as x}from"fs";import{readFileSync as P,writeFileSync as v,existsSync as w}from"fs";import{join as b}from"path";import{homedir as W}from"os";var d="bugfix,feature,refactor,discovery,decision,change",D="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:b(W(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:d,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:D,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!w(t))return this.getAllDefaults();let r=P(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{v(t,JSON.stringify(n,null,2),"utf-8"),c.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(a){c.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},a)}}let s={...this.DEFAULTS};for(let a of Object.keys(this.DEFAULTS))n[a]!==void 0&&(s[a]=n[a]);return s}catch(r){return c.warn("SETTINGS","Failed to load settings, using defaults",{settingsPath:t},r),this.getAllDefaults()}}};var T=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(T||{}),O=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=g.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=T[t]??1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
|
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=typeof r=="string"?JSON.parse(r):r;if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),s=String(t.getHours()).padStart(2,"0"),a=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0"),_=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${s}:${a}:${i}.${_}`}log(t,r,e,n,s){if(t<this.getLevel())return;let a=this.formatTimestamp(new Date),i=T[t].padEnd(5),_=r.padEnd(6),E="";n?.correlationId?E=`[${n.correlationId}] `:n?.sessionId&&(E=`[session-${n.sessionId}] `);let l="";s!=null&&(this.getLevel()===0&&typeof s=="object"?l=`
|
|
`+JSON.stringify(s,null,2):l=" "+this.formatData(s));let u="";if(n){let{sessionId:J,sdkSessionId:Y,correlationId:q,...L}=n;Object.keys(L).length>0&&(u=` {${Object.entries(L).map(([I,$])=>`${I}=${$}`).join(", ")}}`)}let M=`[${a}] [${i}] [${_}] ${E}${e}${u}${l}`;t===3?console.error(M):console.log(M)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,s=""){let E=((new Error().stack||"").split(`
|
|
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),l=E?`${E[1].split("/").pop()}:${E[2]}`:"unknown",u={...e,location:l};return this.warn(t,`[HAPPY-PATH] ${r}`,u,n),s}},c=new O;var m={DEFAULT:12e4,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function h(o){return process.platform==="win32"?Math.round(o*m.WINDOWS_MULTIPLIER):o}function R(o={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=o,s=e||"Worker service connection failed.",a=t?` (port ${t})`:"",i=`${s}${a}
|
|
|
|
`;return i+=`To restart the worker:
|
|
`,i+=`1. Exit Claude Code completely
|
|
`,i+=`2. Run: claude-mem restart
|
|
`,i+="3. Restart Claude Code",r&&(i+=`
|
|
|
|
If that doesn't work, try: /troubleshoot`),n&&(i=`Worker Error: ${n}
|
|
|
|
${i}`),i}var F=C.join(H(),".claude","plugins","marketplaces","thedotmack"),U=h(m.HEALTH_CHECK),S=null;function p(){if(S!==null)return S;let o=C.join(g.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=g.loadFromFile(o);return S=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),S}async function K(){let o=p();return(await fetch(`http://127.0.0.1:${o}/api/readiness`,{signal:AbortSignal.timeout(U)})).ok}function j(){let o=C.join(F,"package.json");return JSON.parse(x(o,"utf-8")).version}async function X(){let o=p(),t=await fetch(`http://127.0.0.1:${o}/api/version`,{signal:AbortSignal.timeout(U)});if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function V(){let o=j(),t=await X();o!==t&&c.warn("SYSTEM","Worker version mismatch",{pluginVersion:o,workerVersion:t,hint:"Restart worker with: claude-mem worker restart"})}async function N(){for(let r=0;r<25;r++){try{if(await K()){await V();return}}catch{}await new Promise(e=>setTimeout(e,200))}throw new Error(R({port:p(),customPrefix:"Worker did not become ready within 5 seconds."}))}import G from"path";function k(o){if(!o||o.trim()==="")return c.warn("PROJECT_NAME","Empty cwd provided, using fallback",{cwd:o}),"unknown-project";let t=G.basename(o);if(t===""){if(process.platform==="win32"){let e=o.match(/^([A-Z]):\\/i);if(e){let s=`drive-${e[1].toUpperCase()}`;return c.info("PROJECT_NAME","Drive root detected",{cwd:o,projectName:s}),s}}return c.warn("PROJECT_NAME","Root directory detected, using fallback",{cwd:o}),"unknown-project"}return t}async function B(o){if(await N(),!o)throw new Error("newHook requires input");let{session_id:t,cwd:r,prompt:e}=o,n=k(r),s=p(),a=await fetch(`http://127.0.0.1:${s}/api/sessions/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,project:n,prompt:e}),signal:AbortSignal.timeout(5e3)});if(!a.ok)throw new Error(`Session initialization failed: ${a.status}`);let i=await a.json(),_=i.sessionDbId,E=i.promptNumber;if(i.skipped&&i.reason==="private"){console.error(`[new-hook] Session ${_}, prompt #${E} (fully private - skipped)`),console.log(f);return}console.error(`[new-hook] Session ${_}, prompt #${E}`);let l=e.startsWith("/")?e.substring(1):e,u=await fetch(`http://127.0.0.1:${s}/sessions/${_}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userPrompt:l,promptNumber:E}),signal:AbortSignal.timeout(5e3)});if(!u.ok)throw new Error(`SDK agent start failed: ${u.status}`);console.log(f)}var A="";y.on("data",o=>A+=o);y.on("end",async()=>{let o;try{o=A?JSON.parse(A):void 0}catch(t){throw new Error(`Failed to parse hook input: ${t instanceof Error?t.message:String(t)}`)}await B(o)});
|