diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index f0062e15..07415a0f 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -786,7 +786,7 @@ FRAMING: This is a mid-session progress checkpoint. The session is ongoing - you WHERE up.claude_session_id = ? ORDER BY up.created_at_epoch DESC LIMIT 1 - `).get(s.claudeSessionId);n&&(this.sseBroadcaster.broadcast({type:"new_prompt",prompt:{id:n.id,claude_session_id:n.claude_session_id,project:n.project,prompt_number:n.prompt_number,prompt_text:n.prompt_text,created_at_epoch:n.created_at_epoch}}),this.dbManager.getChromaSync().syncUserPrompt(n.id,n.sdk_session_id,n.project,n.prompt_text,n.prompt_number,n.created_at_epoch).catch(o=>{Q.error("WORKER","Failed to sync user_prompt to Chroma",{promptId:n.id},o)})),this.broadcastProcessingStatus(!0),this.sdkAgent.startSession(s,this).catch(o=>{Q.failure("WORKER","SDK agent error",{sessionId:t},o)}),this.sseBroadcaster.broadcast({type:"session_started",sessionDbId:t,project:s.project}),a.json({status:"initialized",sessionDbId:t,port:Xo()})}catch(t){Q.failure("WORKER","Session init failed",{},t),a.status(500).json({error:t.message})}}handleObservations(e,a){try{let t=parseInt(e.params.sessionDbId,10),{tool_name:s,tool_input:i,tool_response:n,prompt_number:o,cwd:l}=e.body;this.sessionManager.queueObservation(t,{tool_name:s,tool_input:i,tool_response:n,prompt_number:o,cwd:l});let c=this.sessionManager.getSession(t);c&&!c.generatorPromise&&(c.generatorPromise=this.sdkAgent.startSession(c,this).catch(u=>{Q.failure("WORKER","SDK agent error",{sessionId:t},u)})),this.sseBroadcaster.broadcast({type:"observation_queued",sessionDbId:t}),a.json({status:"queued"})}catch(t){Q.failure("WORKER","Observation queuing failed",{},t),a.status(500).json({error:t.message})}}handleSummarize(e,a){try{let t=parseInt(e.params.sessionDbId,10);this.sessionManager.queueSummarize(t);let s=this.sessionManager.getSession(t);s&&!s.generatorPromise&&(s.generatorPromise=this.sdkAgent.startSession(s,this).catch(i=>{Q.failure("WORKER","SDK agent error",{sessionId:t},i)})),a.json({status:"queued"})}catch(t){Q.failure("WORKER","Summarize queuing failed",{},t),a.status(500).json({error:t.message})}}handleSessionStatus(e,a){try{let t=parseInt(e.params.sessionDbId,10),s=this.sessionManager.getSession(t);if(!s){a.json({status:"not_found"});return}a.json({status:"active",sessionDbId:t,project:s.project,queueLength:s.pendingMessages.length,uptime:Date.now()-s.startTime})}catch(t){Q.failure("WORKER","Session status failed",{},t),a.status(500).json({error:t.message})}}async handleSessionDelete(e,a){try{let t=parseInt(e.params.sessionDbId,10);await this.sessionManager.deleteSession(t),this.dbManager.markSessionComplete(t),this.sseBroadcaster.broadcast({type:"session_completed",sessionDbId:t}),a.json({status:"deleted"})}catch(t){Q.failure("WORKER","Session delete failed",{},t),a.status(500).json({error:t.message})}}async handleSessionComplete(e,a){try{let t=parseInt(e.params.sessionDbId,10);if(isNaN(t)){a.status(400).json({success:!1,error:"Invalid session ID"});return}await this.sessionManager.deleteSession(t),this.dbManager.markSessionComplete(t),this.broadcastProcessingStatus(!1),this.sseBroadcaster.broadcast({type:"session_completed",timestamp:Date.now(),sessionDbId:t}),a.json({success:!0})}catch(t){Q.failure("WORKER","Session complete failed",{},t),a.status(500).json({success:!1,error:String(t)})}}handleGetObservations(e,a){try{let{offset:t,limit:s,project:i}=Md(e),n=this.paginationHelper.getObservations(t,s,i);a.json(n)}catch(t){Q.failure("WORKER","Get observations failed",{},t),a.status(500).json({error:t.message})}}handleGetSummaries(e,a){try{let{offset:t,limit:s,project:i}=Md(e),n=this.paginationHelper.getSummaries(t,s,i);a.json(n)}catch(t){Q.failure("WORKER","Get summaries failed",{},t),a.status(500).json({error:t.message})}}handleGetPrompts(e,a){try{let{offset:t,limit:s,project:i}=Md(e),n=this.paginationHelper.getPrompts(t,s,i);a.json(n)}catch(t){Q.failure("WORKER","Get prompts failed",{},t),a.status(500).json({error:t.message})}}handleGetStats(e,a){try{let t=this.dbManager.getSessionStore().db,s=on(),i=Pr.default.join(s,"package.json"),o=JSON.parse((0,vt.readFileSync)(i,"utf-8")).version,l=t.prepare("SELECT COUNT(*) as count FROM observations").get(),c=t.prepare("SELECT COUNT(*) as count FROM sdk_sessions").get(),u=t.prepare("SELECT COUNT(*) as count FROM session_summaries").get(),p=Pr.default.join((0,nl.homedir)(),".claude-mem","claude-mem.db"),m=0;(0,vt.existsSync)(p)&&(m=(0,vt.statSync)(p).size);let d=Math.floor((Date.now()-this.startTime)/1e3),h=this.sessionManager.getActiveSessionCount(),f=this.sseBroadcaster.getClientCount();a.json({worker:{version:o,uptime:d,activeSessions:h,sseClients:f,port:Xo()},database:{path:p,size:m,observations:l.count,sessions:c.count,summaries:u.count}})}catch(t){Q.failure("WORKER","Get stats failed",{},t),a.status(500).json({error:t.message})}}handleGetSettings(e,a){try{let t=Pr.default.join((0,nl.homedir)(),".claude","settings.json");if(!(0,vt.existsSync)(t)){a.json({CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777"});return}let s=(0,vt.readFileSync)(t,"utf-8"),n=JSON.parse(s).env||{};a.json({CLAUDE_MEM_MODEL:n.CLAUDE_MEM_MODEL||"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:n.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",CLAUDE_MEM_WORKER_PORT:n.CLAUDE_MEM_WORKER_PORT||"37777"})}catch(t){Q.failure("WORKER","Get settings failed",{},t),a.status(500).json({error:t.message})}}handleUpdateSettings(e,a){try{let{CLAUDE_MEM_MODEL:t,CLAUDE_MEM_CONTEXT_OBSERVATIONS:s,CLAUDE_MEM_WORKER_PORT:i}=e.body;if(s){let l=parseInt(s,10);if(isNaN(l)||l<1||l>200){a.status(400).json({success:!1,error:"CLAUDE_MEM_CONTEXT_OBSERVATIONS must be between 1 and 200"});return}}if(i){let l=parseInt(i,10);if(isNaN(l)||l<1024||l>65535){a.status(400).json({success:!1,error:"CLAUDE_MEM_WORKER_PORT must be between 1024 and 65535"});return}}let n=Pr.default.join((0,nl.homedir)(),".claude","settings.json"),o={env:{}};if((0,vt.existsSync)(n)){let l=(0,vt.readFileSync)(n,"utf-8");o=JSON.parse(l),o.env||(o.env={})}t&&(o.env.CLAUDE_MEM_MODEL=t),s&&(o.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS=s),i&&(o.env.CLAUDE_MEM_WORKER_PORT=i),(0,vt.writeFileSync)(n,JSON.stringify(o,null,2),"utf-8"),Q.info("WORKER","Settings updated"),a.json({success:!0,message:"Settings updated successfully"})}catch(t){Q.failure("WORKER","Update settings failed",{},t),a.status(500).json({success:!1,error:String(t)})}}handleGetProcessingStatus(e,a){a.json({isProcessing:this.isProcessing})}broadcastProcessingStatus(e){this.isProcessing=e,this.sseBroadcaster.broadcast({type:"processing_status",isProcessing:e})}handleSetProcessing(e,a){try{let{isProcessing:t}=e.body;if(typeof t!="boolean"){a.status(400).json({error:"isProcessing must be a boolean"});return}this.broadcastProcessingStatus(t),Q.debug("WORKER","Processing status updated",{isProcessing:t}),a.json({status:"ok",isProcessing:t})}catch(t){Q.failure("WORKER","Failed to set processing status",{},t),a.status(500).json({error:t.message})}}handleGetMcpStatus(e,a){try{let t=this.isMcpEnabled();a.json({enabled:t})}catch(t){Q.failure("WORKER","Get MCP status failed",{},t),a.status(500).json({error:t.message})}}handleToggleMcp(e,a){try{let{enabled:t}=e.body;if(typeof t!="boolean"){a.status(400).json({error:"enabled must be a boolean"});return}this.toggleMcp(t),a.json({success:!0,enabled:this.isMcpEnabled()})}catch(t){Q.failure("WORKER","Toggle MCP failed",{},t),a.status(500).json({success:!1,error:t.message})}}isMcpEnabled(){let e=on(),a=Pr.default.join(e,"plugin",".mcp.json");return(0,vt.existsSync)(a)}toggleMcp(e){try{let a=on(),t=Pr.default.join(a,"plugin",".mcp.json"),s=Pr.default.join(a,"plugin",".mcp.json.disabled");e&&(0,vt.existsSync)(s)?((0,vt.renameSync)(s,t),Q.info("WORKER","MCP search server enabled")):!e&&(0,vt.existsSync)(t)?((0,vt.renameSync)(t,s),Q.info("WORKER","MCP search server disabled")):Q.debug("WORKER","MCP toggle no-op (already in desired state)",{enabled:e})}catch(a){throw Q.failure("WORKER","Failed to toggle MCP",{enabled:e},a),a}}handleSearchObservations(e,a){try{let t=e.query.query,s=e.query.format||"full",i=parseInt(e.query.limit,10)||20,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let l=this.dbManager.getSessionSearch().searchObservations(t,{limit:i,project:n});a.json({query:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project,score:c.score})):l})}catch(t){Q.failure("WORKER","Search observations failed",{},t),a.status(500).json({error:t.message})}}handleSearchSessions(e,a){try{let t=e.query.query,s=e.query.format||"full",i=parseInt(e.query.limit,10)||20;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let o=this.dbManager.getSessionSearch().searchSessions(t,{limit:i});a.json({query:t,count:o.length,format:s,results:s==="index"?o.map(l=>({id:l.id,request:l.request,completed:l.completed,created_at_epoch:l.created_at_epoch,project:l.project,score:l.score})):o})}catch(t){Q.failure("WORKER","Search sessions failed",{},t),a.status(500).json({error:t.message})}}handleSearchPrompts(e,a){try{let t=e.query.query,s=e.query.format||"full",i=parseInt(e.query.limit,10)||20,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let l=this.dbManager.getSessionSearch().searchUserPrompts(t,{limit:i,project:n});a.json({query:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,claude_session_id:c.claude_session_id,prompt_number:c.prompt_number,prompt_text:c.prompt_text,created_at_epoch:c.created_at_epoch,score:c.score})):l})}catch(t){Q.failure("WORKER","Search prompts failed",{},t),a.status(500).json({error:t.message})}}handleSearchByConcept(e,a){try{let t=e.query.concept,s=e.query.format||"full",i=parseInt(e.query.limit,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: concept"});return}let l=this.dbManager.getSessionSearch().findByConcept(t,{limit:i,project:n});a.json({concept:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project,concepts:c.concepts})):l})}catch(t){Q.failure("WORKER","Search by concept failed",{},t),a.status(500).json({error:t.message})}}handleSearchByFile(e,a){try{let t=e.query.filePath,s=e.query.format||"full",i=parseInt(e.query.limit,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: filePath"});return}let l=this.dbManager.getSessionSearch().findByFile(t,{limit:i,project:n});a.json({filePath:t,count:l.observations.length+l.sessions.length,format:s,results:{observations:s==="index"?l.observations.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project})):l.observations,sessions:s==="index"?l.sessions.map(c=>({id:c.id,request:c.request,completed:c.completed,created_at_epoch:c.created_at_epoch,project:c.project})):l.sessions}})}catch(t){Q.failure("WORKER","Search by file failed",{},t),a.status(500).json({error:t.message})}}handleSearchByType(e,a){try{let t=e.query.type,s=e.query.format||"full",i=parseInt(e.query.limit,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: type"});return}let l=this.dbManager.getSessionSearch().findByType(t,{limit:i,project:n});a.json({type:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project})):l})}catch(t){Q.failure("WORKER","Search by type failed",{},t),a.status(500).json({error:t.message})}}handleGetRecentContext(e,a){try{let t=e.query.project||Pr.default.basename(process.cwd()),s=parseInt(e.query.limit,10)||3,i=this.dbManager.getSessionStore(),o=i.getRecentSessionsWithStatus(t,s).map(l=>{let c=l.has_summary&&l.sdk_session_id?i.getSummaryForSession(l.sdk_session_id):null,u=l.sdk_session_id?i.getObservationsForSession(l.sdk_session_id):[];return{session_id:l.id,sdk_session_id:l.sdk_session_id,project:l.project,status:l.status,has_summary:l.has_summary,summary:c,observations:u.map(p=>({id:p.id,type:p.type,title:p.title,subtitle:p.subtitle,created_at_epoch:p.created_at_epoch})),created_at_epoch:l.started_at_epoch}});a.json({project:t,limit:s,count:o.length,sessions:o})}catch(t){Q.failure("WORKER","Get recent context failed",{},t),a.status(500).json({error:t.message})}}handleGetContextTimeline(e,a){try{let t=e.query.anchor,s=parseInt(e.query.depth_before,10)||10,i=parseInt(e.query.depth_after,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: anchor"});return}let o=this.dbManager.getSessionStore(),l;if(/^\d+$/.test(t)){let c=parseInt(t,10),u=o.getObservationById(c);if(!u){a.status(404).json({error:`Observation #${c} not found`});return}l=o.getTimelineAroundObservation(c,u.created_at_epoch,s,i,n)}else if(t.startsWith("S")||t.startsWith("#S")){let c=t.replace(/^#?S/,""),u=parseInt(c,10),p=o.getSessionSummariesByIds([u]);if(p.length===0){a.status(404).json({error:`Session #${u} not found`});return}l=o.getTimelineAroundTimestamp(p[0].created_at_epoch,s,i,n)}else{let c=new Date(t);if(isNaN(c.getTime())){a.status(400).json({error:`Invalid timestamp: ${t}`});return}l=o.getTimelineAroundTimestamp(c.getTime(),s,i,n)}a.json({anchor:t,depth_before:s,depth_after:i,project:n,timeline:l})}catch(t){Q.failure("WORKER","Get context timeline failed",{},t),a.status(500).json({error:t.message})}}handleGetTimelineByQuery(e,a){try{let t=e.query.query,s=e.query.mode||"auto",i=parseInt(e.query.depth_before,10)||10,n=parseInt(e.query.depth_after,10)||10,o=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let l=this.dbManager.getSessionSearch(),c=this.dbManager.getSessionStore(),u=null,p=null;if(s==="observations"||s==="auto"){let d=l.searchObservations(t,{limit:1,project:o});d.length>0&&(u=d[0],p={type:"observation",results:d})}if(!u&&(s==="sessions"||s==="auto")){let d=l.searchSessions(t,{limit:1});d.length>0&&(u=d[0],p={type:"session",results:d})}if(!u){a.json({query:t,mode:s,match:null,timeline:null,message:"No matches found for query"});return}let m=p.type==="observation"?c.getTimelineAroundObservation(u.id,u.created_at_epoch,i,n,o):c.getTimelineAroundTimestamp(u.created_at_epoch,i,n,o);a.json({query:t,mode:s,match:{type:p.type,id:u.id,title:u.title||u.request,score:u.score,created_at_epoch:u.created_at_epoch},depth_before:i,depth_after:n,timeline:m})}catch(t){Q.failure("WORKER","Get timeline by query failed",{},t),a.status(500).json({error:t.message})}}handleSearchHelp(e,a){a.json({title:"Claude-Mem Search API",description:"HTTP API for searching persistent memory",endpoints:[{path:"/api/search/observations",method:"GET",description:"Search observations using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/sessions",method:"GET",description:"Search session summaries using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)"}},{path:"/api/search/prompts",method:"GET",description:"Search user prompts using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/by-concept",method:"GET",description:"Find observations by concept tag",parameters:{concept:"Concept tag (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-file",method:"GET",description:"Find observations and sessions by file path",parameters:{filePath:"File path or partial path (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results per type (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-type",method:"GET",description:"Find observations by type",parameters:{type:"Observation type (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/context/recent",method:"GET",description:"Get recent session context including summaries and observations",parameters:{project:"Project name (default: current directory)",limit:"Number of recent sessions (default: 3)"}},{path:"/api/context/timeline",method:"GET",description:"Get unified timeline around a specific point in time",parameters:{anchor:'Anchor point: observation ID, session ID (e.g., "S123"), or ISO timestamp (required)',depth_before:"Number of records before anchor (default: 10)",depth_after:"Number of records after anchor (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/timeline/by-query",method:"GET",description:"Search for best match, then get timeline around it",parameters:{query:"Search query (required)",mode:'Search mode: "auto", "observations", or "sessions" (default: "auto")',depth_before:"Number of records before match (default: 10)",depth_after:"Number of records after match (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/help",method:"GET",description:"Get this help documentation"}],examples:['curl "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"','curl "http://localhost:37777/api/search/by-type?type=bugfix&limit=10"','curl "http://localhost:37777/api/context/recent?project=claude-mem&limit=3"','curl "http://localhost:37777/api/context/timeline?anchor=123&depth_before=5&depth_after=5"']})}};function Md(r){let e=parseInt(r.query.offset,10)||0,a=Math.min(parseInt(r.query.limit,10)||20,100),t=r.query.project;return{offset:e,limit:a,project:t}}if(require.main===module||!module.parent){let r=new ji;process.on("SIGTERM",async()=>{Q.info("SYSTEM","Received SIGTERM, shutting down gracefully"),await r.shutdown(),process.exit(0)}),process.on("SIGINT",async()=>{Q.info("SYSTEM","Received SIGINT, shutting down gracefully"),await r.shutdown(),process.exit(0)}),r.start().catch(e=>{Q.failure("SYSTEM","Worker startup failed",{},e),process.exit(1)})}var _$=ji;0&&(module.exports={WorkerService}); + `).get(s.claudeSessionId);n&&(this.sseBroadcaster.broadcast({type:"new_prompt",prompt:{id:n.id,claude_session_id:n.claude_session_id,project:n.project,prompt_number:n.prompt_number,prompt_text:n.prompt_text,created_at_epoch:n.created_at_epoch}}),this.dbManager.getChromaSync().syncUserPrompt(n.id,n.sdk_session_id,n.project,n.prompt_text,n.prompt_number,n.created_at_epoch).catch(o=>{Q.error("WORKER","Failed to sync user_prompt to Chroma",{promptId:n.id},o)})),this.broadcastProcessingStatus(!0),s.generatorPromise=this.sdkAgent.startSession(s,this).catch(o=>{Q.failure("WORKER","SDK agent error",{sessionId:t},o)}),this.sseBroadcaster.broadcast({type:"session_started",sessionDbId:t,project:s.project}),a.json({status:"initialized",sessionDbId:t,port:Xo()})}catch(t){Q.failure("WORKER","Session init failed",{},t),a.status(500).json({error:t.message})}}handleObservations(e,a){try{let t=parseInt(e.params.sessionDbId,10),{tool_name:s,tool_input:i,tool_response:n,prompt_number:o,cwd:l}=e.body;this.sessionManager.queueObservation(t,{tool_name:s,tool_input:i,tool_response:n,prompt_number:o,cwd:l});let c=this.sessionManager.getSession(t);c&&!c.generatorPromise&&(c.generatorPromise=this.sdkAgent.startSession(c,this).catch(u=>{Q.failure("WORKER","SDK agent error",{sessionId:t},u)})),this.sseBroadcaster.broadcast({type:"observation_queued",sessionDbId:t}),a.json({status:"queued"})}catch(t){Q.failure("WORKER","Observation queuing failed",{},t),a.status(500).json({error:t.message})}}handleSummarize(e,a){try{let t=parseInt(e.params.sessionDbId,10);this.sessionManager.queueSummarize(t);let s=this.sessionManager.getSession(t);s&&!s.generatorPromise&&(s.generatorPromise=this.sdkAgent.startSession(s,this).catch(i=>{Q.failure("WORKER","SDK agent error",{sessionId:t},i)})),a.json({status:"queued"})}catch(t){Q.failure("WORKER","Summarize queuing failed",{},t),a.status(500).json({error:t.message})}}handleSessionStatus(e,a){try{let t=parseInt(e.params.sessionDbId,10),s=this.sessionManager.getSession(t);if(!s){a.json({status:"not_found"});return}a.json({status:"active",sessionDbId:t,project:s.project,queueLength:s.pendingMessages.length,uptime:Date.now()-s.startTime})}catch(t){Q.failure("WORKER","Session status failed",{},t),a.status(500).json({error:t.message})}}async handleSessionDelete(e,a){try{let t=parseInt(e.params.sessionDbId,10);await this.sessionManager.deleteSession(t),this.dbManager.markSessionComplete(t),this.sseBroadcaster.broadcast({type:"session_completed",sessionDbId:t}),a.json({status:"deleted"})}catch(t){Q.failure("WORKER","Session delete failed",{},t),a.status(500).json({error:t.message})}}async handleSessionComplete(e,a){try{let t=parseInt(e.params.sessionDbId,10);if(isNaN(t)){a.status(400).json({success:!1,error:"Invalid session ID"});return}await this.sessionManager.deleteSession(t),this.dbManager.markSessionComplete(t),this.broadcastProcessingStatus(!1),this.sseBroadcaster.broadcast({type:"session_completed",timestamp:Date.now(),sessionDbId:t}),a.json({success:!0})}catch(t){Q.failure("WORKER","Session complete failed",{},t),a.status(500).json({success:!1,error:String(t)})}}handleGetObservations(e,a){try{let{offset:t,limit:s,project:i}=Md(e),n=this.paginationHelper.getObservations(t,s,i);a.json(n)}catch(t){Q.failure("WORKER","Get observations failed",{},t),a.status(500).json({error:t.message})}}handleGetSummaries(e,a){try{let{offset:t,limit:s,project:i}=Md(e),n=this.paginationHelper.getSummaries(t,s,i);a.json(n)}catch(t){Q.failure("WORKER","Get summaries failed",{},t),a.status(500).json({error:t.message})}}handleGetPrompts(e,a){try{let{offset:t,limit:s,project:i}=Md(e),n=this.paginationHelper.getPrompts(t,s,i);a.json(n)}catch(t){Q.failure("WORKER","Get prompts failed",{},t),a.status(500).json({error:t.message})}}handleGetStats(e,a){try{let t=this.dbManager.getSessionStore().db,s=on(),i=Pr.default.join(s,"package.json"),o=JSON.parse((0,vt.readFileSync)(i,"utf-8")).version,l=t.prepare("SELECT COUNT(*) as count FROM observations").get(),c=t.prepare("SELECT COUNT(*) as count FROM sdk_sessions").get(),u=t.prepare("SELECT COUNT(*) as count FROM session_summaries").get(),p=Pr.default.join((0,nl.homedir)(),".claude-mem","claude-mem.db"),m=0;(0,vt.existsSync)(p)&&(m=(0,vt.statSync)(p).size);let d=Math.floor((Date.now()-this.startTime)/1e3),h=this.sessionManager.getActiveSessionCount(),f=this.sseBroadcaster.getClientCount();a.json({worker:{version:o,uptime:d,activeSessions:h,sseClients:f,port:Xo()},database:{path:p,size:m,observations:l.count,sessions:c.count,summaries:u.count}})}catch(t){Q.failure("WORKER","Get stats failed",{},t),a.status(500).json({error:t.message})}}handleGetSettings(e,a){try{let t=Pr.default.join((0,nl.homedir)(),".claude","settings.json");if(!(0,vt.existsSync)(t)){a.json({CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777"});return}let s=(0,vt.readFileSync)(t,"utf-8"),n=JSON.parse(s).env||{};a.json({CLAUDE_MEM_MODEL:n.CLAUDE_MEM_MODEL||"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:n.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",CLAUDE_MEM_WORKER_PORT:n.CLAUDE_MEM_WORKER_PORT||"37777"})}catch(t){Q.failure("WORKER","Get settings failed",{},t),a.status(500).json({error:t.message})}}handleUpdateSettings(e,a){try{let{CLAUDE_MEM_MODEL:t,CLAUDE_MEM_CONTEXT_OBSERVATIONS:s,CLAUDE_MEM_WORKER_PORT:i}=e.body;if(s){let l=parseInt(s,10);if(isNaN(l)||l<1||l>200){a.status(400).json({success:!1,error:"CLAUDE_MEM_CONTEXT_OBSERVATIONS must be between 1 and 200"});return}}if(i){let l=parseInt(i,10);if(isNaN(l)||l<1024||l>65535){a.status(400).json({success:!1,error:"CLAUDE_MEM_WORKER_PORT must be between 1024 and 65535"});return}}let n=Pr.default.join((0,nl.homedir)(),".claude","settings.json"),o={env:{}};if((0,vt.existsSync)(n)){let l=(0,vt.readFileSync)(n,"utf-8");o=JSON.parse(l),o.env||(o.env={})}t&&(o.env.CLAUDE_MEM_MODEL=t),s&&(o.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS=s),i&&(o.env.CLAUDE_MEM_WORKER_PORT=i),(0,vt.writeFileSync)(n,JSON.stringify(o,null,2),"utf-8"),Q.info("WORKER","Settings updated"),a.json({success:!0,message:"Settings updated successfully"})}catch(t){Q.failure("WORKER","Update settings failed",{},t),a.status(500).json({success:!1,error:String(t)})}}handleGetProcessingStatus(e,a){a.json({isProcessing:this.isProcessing})}broadcastProcessingStatus(e){this.isProcessing=e,this.sseBroadcaster.broadcast({type:"processing_status",isProcessing:e})}handleSetProcessing(e,a){try{let{isProcessing:t}=e.body;if(typeof t!="boolean"){a.status(400).json({error:"isProcessing must be a boolean"});return}this.broadcastProcessingStatus(t),Q.debug("WORKER","Processing status updated",{isProcessing:t}),a.json({status:"ok",isProcessing:t})}catch(t){Q.failure("WORKER","Failed to set processing status",{},t),a.status(500).json({error:t.message})}}handleGetMcpStatus(e,a){try{let t=this.isMcpEnabled();a.json({enabled:t})}catch(t){Q.failure("WORKER","Get MCP status failed",{},t),a.status(500).json({error:t.message})}}handleToggleMcp(e,a){try{let{enabled:t}=e.body;if(typeof t!="boolean"){a.status(400).json({error:"enabled must be a boolean"});return}this.toggleMcp(t),a.json({success:!0,enabled:this.isMcpEnabled()})}catch(t){Q.failure("WORKER","Toggle MCP failed",{},t),a.status(500).json({success:!1,error:t.message})}}isMcpEnabled(){let e=on(),a=Pr.default.join(e,"plugin",".mcp.json");return(0,vt.existsSync)(a)}toggleMcp(e){try{let a=on(),t=Pr.default.join(a,"plugin",".mcp.json"),s=Pr.default.join(a,"plugin",".mcp.json.disabled");e&&(0,vt.existsSync)(s)?((0,vt.renameSync)(s,t),Q.info("WORKER","MCP search server enabled")):!e&&(0,vt.existsSync)(t)?((0,vt.renameSync)(t,s),Q.info("WORKER","MCP search server disabled")):Q.debug("WORKER","MCP toggle no-op (already in desired state)",{enabled:e})}catch(a){throw Q.failure("WORKER","Failed to toggle MCP",{enabled:e},a),a}}handleSearchObservations(e,a){try{let t=e.query.query,s=e.query.format||"full",i=parseInt(e.query.limit,10)||20,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let l=this.dbManager.getSessionSearch().searchObservations(t,{limit:i,project:n});a.json({query:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project,score:c.score})):l})}catch(t){Q.failure("WORKER","Search observations failed",{},t),a.status(500).json({error:t.message})}}handleSearchSessions(e,a){try{let t=e.query.query,s=e.query.format||"full",i=parseInt(e.query.limit,10)||20;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let o=this.dbManager.getSessionSearch().searchSessions(t,{limit:i});a.json({query:t,count:o.length,format:s,results:s==="index"?o.map(l=>({id:l.id,request:l.request,completed:l.completed,created_at_epoch:l.created_at_epoch,project:l.project,score:l.score})):o})}catch(t){Q.failure("WORKER","Search sessions failed",{},t),a.status(500).json({error:t.message})}}handleSearchPrompts(e,a){try{let t=e.query.query,s=e.query.format||"full",i=parseInt(e.query.limit,10)||20,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let l=this.dbManager.getSessionSearch().searchUserPrompts(t,{limit:i,project:n});a.json({query:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,claude_session_id:c.claude_session_id,prompt_number:c.prompt_number,prompt_text:c.prompt_text,created_at_epoch:c.created_at_epoch,score:c.score})):l})}catch(t){Q.failure("WORKER","Search prompts failed",{},t),a.status(500).json({error:t.message})}}handleSearchByConcept(e,a){try{let t=e.query.concept,s=e.query.format||"full",i=parseInt(e.query.limit,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: concept"});return}let l=this.dbManager.getSessionSearch().findByConcept(t,{limit:i,project:n});a.json({concept:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project,concepts:c.concepts})):l})}catch(t){Q.failure("WORKER","Search by concept failed",{},t),a.status(500).json({error:t.message})}}handleSearchByFile(e,a){try{let t=e.query.filePath,s=e.query.format||"full",i=parseInt(e.query.limit,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: filePath"});return}let l=this.dbManager.getSessionSearch().findByFile(t,{limit:i,project:n});a.json({filePath:t,count:l.observations.length+l.sessions.length,format:s,results:{observations:s==="index"?l.observations.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project})):l.observations,sessions:s==="index"?l.sessions.map(c=>({id:c.id,request:c.request,completed:c.completed,created_at_epoch:c.created_at_epoch,project:c.project})):l.sessions}})}catch(t){Q.failure("WORKER","Search by file failed",{},t),a.status(500).json({error:t.message})}}handleSearchByType(e,a){try{let t=e.query.type,s=e.query.format||"full",i=parseInt(e.query.limit,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: type"});return}let l=this.dbManager.getSessionSearch().findByType(t,{limit:i,project:n});a.json({type:t,count:l.length,format:s,results:s==="index"?l.map(c=>({id:c.id,type:c.type,title:c.title,subtitle:c.subtitle,created_at_epoch:c.created_at_epoch,project:c.project})):l})}catch(t){Q.failure("WORKER","Search by type failed",{},t),a.status(500).json({error:t.message})}}handleGetRecentContext(e,a){try{let t=e.query.project||Pr.default.basename(process.cwd()),s=parseInt(e.query.limit,10)||3,i=this.dbManager.getSessionStore(),o=i.getRecentSessionsWithStatus(t,s).map(l=>{let c=l.has_summary&&l.sdk_session_id?i.getSummaryForSession(l.sdk_session_id):null,u=l.sdk_session_id?i.getObservationsForSession(l.sdk_session_id):[];return{session_id:l.id,sdk_session_id:l.sdk_session_id,project:l.project,status:l.status,has_summary:l.has_summary,summary:c,observations:u.map(p=>({id:p.id,type:p.type,title:p.title,subtitle:p.subtitle,created_at_epoch:p.created_at_epoch})),created_at_epoch:l.started_at_epoch}});a.json({project:t,limit:s,count:o.length,sessions:o})}catch(t){Q.failure("WORKER","Get recent context failed",{},t),a.status(500).json({error:t.message})}}handleGetContextTimeline(e,a){try{let t=e.query.anchor,s=parseInt(e.query.depth_before,10)||10,i=parseInt(e.query.depth_after,10)||10,n=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: anchor"});return}let o=this.dbManager.getSessionStore(),l;if(/^\d+$/.test(t)){let c=parseInt(t,10),u=o.getObservationById(c);if(!u){a.status(404).json({error:`Observation #${c} not found`});return}l=o.getTimelineAroundObservation(c,u.created_at_epoch,s,i,n)}else if(t.startsWith("S")||t.startsWith("#S")){let c=t.replace(/^#?S/,""),u=parseInt(c,10),p=o.getSessionSummariesByIds([u]);if(p.length===0){a.status(404).json({error:`Session #${u} not found`});return}l=o.getTimelineAroundTimestamp(p[0].created_at_epoch,s,i,n)}else{let c=new Date(t);if(isNaN(c.getTime())){a.status(400).json({error:`Invalid timestamp: ${t}`});return}l=o.getTimelineAroundTimestamp(c.getTime(),s,i,n)}a.json({anchor:t,depth_before:s,depth_after:i,project:n,timeline:l})}catch(t){Q.failure("WORKER","Get context timeline failed",{},t),a.status(500).json({error:t.message})}}handleGetTimelineByQuery(e,a){try{let t=e.query.query,s=e.query.mode||"auto",i=parseInt(e.query.depth_before,10)||10,n=parseInt(e.query.depth_after,10)||10,o=e.query.project;if(!t){a.status(400).json({error:"Missing required parameter: query"});return}let l=this.dbManager.getSessionSearch(),c=this.dbManager.getSessionStore(),u=null,p=null;if(s==="observations"||s==="auto"){let d=l.searchObservations(t,{limit:1,project:o});d.length>0&&(u=d[0],p={type:"observation",results:d})}if(!u&&(s==="sessions"||s==="auto")){let d=l.searchSessions(t,{limit:1});d.length>0&&(u=d[0],p={type:"session",results:d})}if(!u){a.json({query:t,mode:s,match:null,timeline:null,message:"No matches found for query"});return}let m=p.type==="observation"?c.getTimelineAroundObservation(u.id,u.created_at_epoch,i,n,o):c.getTimelineAroundTimestamp(u.created_at_epoch,i,n,o);a.json({query:t,mode:s,match:{type:p.type,id:u.id,title:u.title||u.request,score:u.score,created_at_epoch:u.created_at_epoch},depth_before:i,depth_after:n,timeline:m})}catch(t){Q.failure("WORKER","Get timeline by query failed",{},t),a.status(500).json({error:t.message})}}handleSearchHelp(e,a){a.json({title:"Claude-Mem Search API",description:"HTTP API for searching persistent memory",endpoints:[{path:"/api/search/observations",method:"GET",description:"Search observations using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/sessions",method:"GET",description:"Search session summaries using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)"}},{path:"/api/search/prompts",method:"GET",description:"Search user prompts using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/by-concept",method:"GET",description:"Find observations by concept tag",parameters:{concept:"Concept tag (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-file",method:"GET",description:"Find observations and sessions by file path",parameters:{filePath:"File path or partial path (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results per type (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-type",method:"GET",description:"Find observations by type",parameters:{type:"Observation type (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/context/recent",method:"GET",description:"Get recent session context including summaries and observations",parameters:{project:"Project name (default: current directory)",limit:"Number of recent sessions (default: 3)"}},{path:"/api/context/timeline",method:"GET",description:"Get unified timeline around a specific point in time",parameters:{anchor:'Anchor point: observation ID, session ID (e.g., "S123"), or ISO timestamp (required)',depth_before:"Number of records before anchor (default: 10)",depth_after:"Number of records after anchor (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/timeline/by-query",method:"GET",description:"Search for best match, then get timeline around it",parameters:{query:"Search query (required)",mode:'Search mode: "auto", "observations", or "sessions" (default: "auto")',depth_before:"Number of records before match (default: 10)",depth_after:"Number of records after match (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/help",method:"GET",description:"Get this help documentation"}],examples:['curl "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"','curl "http://localhost:37777/api/search/by-type?type=bugfix&limit=10"','curl "http://localhost:37777/api/context/recent?project=claude-mem&limit=3"','curl "http://localhost:37777/api/context/timeline?anchor=123&depth_before=5&depth_after=5"']})}};function Md(r){let e=parseInt(r.query.offset,10)||0,a=Math.min(parseInt(r.query.limit,10)||20,100),t=r.query.project;return{offset:e,limit:a,project:t}}if(require.main===module||!module.parent){let r=new ji;process.on("SIGTERM",async()=>{Q.info("SYSTEM","Received SIGTERM, shutting down gracefully"),await r.shutdown(),process.exit(0)}),process.on("SIGINT",async()=>{Q.info("SYSTEM","Received SIGINT, shutting down gracefully"),await r.shutdown(),process.exit(0)}),r.start().catch(e=>{Q.failure("SYSTEM","Worker startup failed",{},e),process.exit(1)})}var _$=ji;0&&(module.exports={WorkerService}); /*! Bundled license information: depd/index.js: diff --git a/src/services/worker-service.ts b/src/services/worker-service.ts index 4483c839..a18f04be 100644 --- a/src/services/worker-service.ts +++ b/src/services/worker-service.ts @@ -262,7 +262,7 @@ export class WorkerService { this.broadcastProcessingStatus(true); // Start SDK agent in background (pass worker ref for spinner control) - this.sdkAgent.startSession(session, this).catch(err => { + session.generatorPromise = this.sdkAgent.startSession(session, this).catch(err => { logger.failure('WORKER', 'SDK agent error', { sessionId: sessionDbId }, err); });