diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index 69b15796..99e7513c 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -474,7 +474,7 @@ Output this XML: **Required fields**: request, investigated, learned, completed, next_steps -**Optional fields**: notes`}function xg(a,e){let r=[],t=/([\s\S]*?)<\/observation>/g,i;for(;(i=t.exec(a))!==null;){let s=i[1],n=Vt(s,"type"),o=Vt(s,"title"),p=Vt(s,"subtitle"),c=Vt(s,"narrative"),l=ps(s,"facts","fact"),u=ps(s,"concepts","concept"),d=ps(s,"files_read","file"),m=ps(s,"files_modified","file");if(!n||!o||!p||!c){se.warn("PARSER","Observation missing required fields, skipping",{correlationId:e,hasType:!!n,hasTitle:!!o,hasSubtitle:!!p,hasNarrative:!!c});continue}if(!["change","discovery","decision"].includes(n.trim())){se.warn("PARSER",`Invalid observation type: ${n}, skipping`,{correlationId:e});continue}let f=u.filter(g=>g!==n.trim());f.length!==u.length&&se.warn("PARSER","Removed observation type from concepts array",{correlationId:e,type:n.trim(),originalConcepts:u,cleanedConcepts:f}),r.push({type:n.trim(),title:o,subtitle:p,facts:l,narrative:c,concepts:f,files_read:d,files_modified:m})}return r}function yg(a,e){let t=/([\s\S]*?)<\/summary>/.exec(a);if(!t)return null;let i=t[1],s=Vt(i,"request"),n=Vt(i,"investigated"),o=Vt(i,"learned"),p=Vt(i,"completed"),c=Vt(i,"next_steps"),l=Vt(i,"notes");return!s||!n||!o||!p||!c?(se.warn("PARSER","Summary missing required fields",{sessionId:e,hasRequest:!!s,hasInvestigated:!!n,hasLearned:!!o,hasCompleted:!!p,hasNextSteps:!!c}),null):{request:s,investigated:n,learned:o,completed:p,next_steps:c,notes:l}}function Vt(a,e){let t=new RegExp(`<${e}>([^<]*)`).exec(a);return t?t[1].trim():null}function ps(a,e,r){let t=[],s=new RegExp(`<${e}>(.*?)`,"s").exec(a);if(!s)return t;let n=s[1],o=new RegExp(`<${r}>([^<]+)`,"g"),p;for(;(p=o.exec(n))!==null;)t.push(p[1].trim());return t}var Q2=process.env.CLAUDE_MEM_MODEL||"claude-sonnet-4-5",J2=["Glob","Grep","ListMcpResourcesTool","WebSearch"],ls=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),us=class{app;port=null;sessions=new Map;constructor(){this.app=(0,Vc.default)(),this.app.use(Vc.default.json({limit:"50mb"})),this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/sessions/:sessionDbId/init",this.handleInit.bind(this)),this.app.post("/sessions/:sessionDbId/observations",this.handleObservation.bind(this)),this.app.post("/sessions/:sessionDbId/summarize",this.handleSummarize.bind(this)),this.app.get("/sessions/:sessionDbId/status",this.handleStatus.bind(this)),this.app.delete("/sessions/:sessionDbId",this.handleDelete.bind(this))}async start(){this.port=ls;let e=new nt,r=e.cleanupOrphanedSessions();return e.close(),r>0&&se.info("SYSTEM",`Cleaned up ${r} orphaned sessions`),new Promise((t,i)=>{this.app.listen(ls,"127.0.0.1",()=>{se.info("SYSTEM","Worker started",{port:ls,pid:process.pid,activeSessions:this.sessions.size}),t()}).on("error",s=>{s.code==="EADDRINUSE"&&se.error("SYSTEM",`Port ${ls} already in use - worker may already be running`),i(s)})})}handleHealth(e,r){r.json({status:"ok",port:this.port,pid:process.pid,activeSessions:this.sessions.size,uptime:process.uptime(),memory:process.memoryUsage()})}async handleInit(e,r){let t=parseInt(e.params.sessionDbId,10),{project:i,userPrompt:s}=e.body,n=se.sessionId(t);se.info("WORKER","Session init",{correlationId:n,project:i});let o=new nt,p=o.getSessionById(t);if(!p){o.close(),r.status(404).json({error:"Session not found in database"});return}let c=p.sdk_session_id||`session-${t}`,l={sessionDbId:t,claudeSessionId:c,sdkSessionId:null,project:i,userPrompt:s,pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,observationCounter:0,startTime:Date.now()};this.sessions.set(t,l),o.setWorkerPort(t,this.port),o.close(),l.generatorPromise=this.runSDKAgent(l).catch(u=>{se.failure("WORKER","SDK agent error",{sessionId:t},u);let d=new nt;d.markSessionFailed(t),d.close(),this.sessions.delete(t)}),se.success("WORKER","Session initialized",{sessionId:t,port:this.port}),r.json({status:"initialized",sessionDbId:t,port:this.port})}handleObservation(e,r){let t=parseInt(e.params.sessionDbId,10),{tool_name:i,tool_input:s,tool_output:n,prompt_number:o}=e.body,p=this.sessions.get(t);if(!p){let u=new nt,d=u.getSessionById(t);u.close();let m=d?.sdk_session_id||`session-${t}`;p={sessionDbId:t,claudeSessionId:m,sdkSessionId:null,project:d?.project||"",userPrompt:d?.user_prompt||"",pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,observationCounter:0,startTime:Date.now()},this.sessions.set(t,p),p.generatorPromise=this.runSDKAgent(p).catch(v=>{se.failure("WORKER","SDK agent error",{sessionId:t},v);let f=new nt;f.markSessionFailed(t),f.close(),this.sessions.delete(t)})}p.observationCounter++;let c=se.correlationId(t,p.observationCounter),l=se.formatTool(i,s);se.dataIn("WORKER",`Observation queued: ${l}`,{correlationId:c,queue:p.pendingMessages.length+1}),p.pendingMessages.push({type:"observation",tool_name:i,tool_input:s,tool_output:n,prompt_number:o}),r.json({status:"queued",queueLength:p.pendingMessages.length})}handleSummarize(e,r){let t=parseInt(e.params.sessionDbId,10),{prompt_number:i}=e.body,s=this.sessions.get(t);if(!s){let n=new nt,o=n.getSessionById(t);n.close();let p=o?.sdk_session_id||`session-${t}`;s={sessionDbId:t,claudeSessionId:p,sdkSessionId:null,project:o?.project||"",userPrompt:o?.user_prompt||"",pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,observationCounter:0,startTime:Date.now()},this.sessions.set(t,s),s.generatorPromise=this.runSDKAgent(s).catch(c=>{se.failure("WORKER","SDK agent error",{sessionId:t},c);let l=new nt;l.markSessionFailed(t),l.close(),this.sessions.delete(t)})}se.dataIn("WORKER","Summary requested",{sessionId:t,promptNumber:i,queue:s.pendingMessages.length+1}),s.pendingMessages.push({type:"summarize",prompt_number:i}),r.json({status:"queued",queueLength:s.pendingMessages.length})}handleStatus(e,r){let t=parseInt(e.params.sessionDbId,10),i=this.sessions.get(t);if(!i){r.status(404).json({error:"Session not found"});return}r.json({sessionDbId:t,sdkSessionId:i.sdkSessionId,project:i.project,pendingMessages:i.pendingMessages.length})}async handleDelete(e,r){let t=parseInt(e.params.sessionDbId,10),i=this.sessions.get(t);if(!i){r.status(404).json({error:"Session not found"});return}se.warn("WORKER","Session delete requested",{sessionId:t}),i.abortController.abort(),i.generatorPromise&&await Promise.race([i.generatorPromise,new Promise(n=>setTimeout(n,5e3))]);let s=new nt;s.markSessionFailed(t),s.close(),this.sessions.delete(t),se.info("WORKER","Session deleted",{sessionId:t}),r.json({status:"deleted"})}async runSDKAgent(e){se.info("SDK","Agent starting",{sessionId:e.sessionDbId});let r=process.env.CLAUDE_CODE_PATH||"/Users/alexnewman/.nvm/versions/node/v24.5.0/bin/claude";try{let t=lg({prompt:this.createMessageGenerator(e),options:{model:Q2,disallowedTools:J2,abortController:e.abortController,pathToClaudeCodeExecutable:r}});for await(let n of t)if(n.type==="system"&&n.subtype==="init"){let o=n;if(o.session_id){let p=new nt,c=p.updateSDKSessionId(e.sessionDbId,o.session_id);p.close(),c&&(se.success("SDK","Session initialized",{sessionId:e.sessionDbId,sdkSessionId:o.session_id}),e.sdkSessionId=o.session_id)}}else if(n.type==="assistant"){let o=n.message.content,p=Array.isArray(o)?o.filter(l=>l.type==="text").map(l=>l.text).join(` +**Optional fields**: notes`}function xg(a,e){let r=[],t=/([\s\S]*?)<\/observation>/g,i;for(;(i=t.exec(a))!==null;){let s=i[1],n=Vt(s,"type"),o=Vt(s,"title"),p=Vt(s,"subtitle"),c=Vt(s,"narrative"),l=ps(s,"facts","fact"),u=ps(s,"concepts","concept"),d=ps(s,"files_read","file"),m=ps(s,"files_modified","file");if(!n||!o||!p||!c){se.warn("PARSER","Observation missing required fields, skipping",{correlationId:e,hasType:!!n,hasTitle:!!o,hasSubtitle:!!p,hasNarrative:!!c});continue}if(!["change","discovery","decision"].includes(n.trim())){se.warn("PARSER",`Invalid observation type: ${n}, skipping`,{correlationId:e});continue}let f=u.filter(g=>g!==n.trim());f.length!==u.length&&se.warn("PARSER","Removed observation type from concepts array",{correlationId:e,type:n.trim(),originalConcepts:u,cleanedConcepts:f}),r.push({type:n.trim(),title:o,subtitle:p,facts:l,narrative:c,concepts:f,files_read:d,files_modified:m})}return r}function yg(a,e){let t=/([\s\S]*?)<\/summary>/.exec(a);if(!t)return null;let i=t[1],s=Vt(i,"request"),n=Vt(i,"investigated"),o=Vt(i,"learned"),p=Vt(i,"completed"),c=Vt(i,"next_steps"),l=Vt(i,"notes");return!s||!n||!o||!p||!c?(se.warn("PARSER","Summary missing required fields",{sessionId:e,hasRequest:!!s,hasInvestigated:!!n,hasLearned:!!o,hasCompleted:!!p,hasNextSteps:!!c}),null):{request:s,investigated:n,learned:o,completed:p,next_steps:c,notes:l}}function Vt(a,e){let t=new RegExp(`<${e}>([^<]*)`).exec(a);return t?t[1].trim():null}function ps(a,e,r){let t=[],s=new RegExp(`<${e}>(.*?)`,"s").exec(a);if(!s)return t;let n=s[1],o=new RegExp(`<${r}>([^<]+)`,"g"),p;for(;(p=o.exec(n))!==null;)t.push(p[1].trim());return t}var Q2=process.env.CLAUDE_MEM_MODEL||"claude-sonnet-4-5",J2=["Glob","Grep","ListMcpResourcesTool","WebSearch"],ls=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),us=class{app;port=null;sessions=new Map;constructor(){this.app=(0,Vc.default)(),this.app.use(Vc.default.json({limit:"50mb"})),this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/sessions/:sessionDbId/init",this.handleInit.bind(this)),this.app.post("/sessions/:sessionDbId/observations",this.handleObservation.bind(this)),this.app.post("/sessions/:sessionDbId/summarize",this.handleSummarize.bind(this)),this.app.get("/sessions/:sessionDbId/status",this.handleStatus.bind(this)),this.app.delete("/sessions/:sessionDbId",this.handleDelete.bind(this))}async start(){this.port=ls;let e=new nt,r=e.cleanupOrphanedSessions();return e.close(),r>0&&se.info("SYSTEM",`Cleaned up ${r} orphaned sessions`),new Promise((t,i)=>{this.app.listen(ls,"127.0.0.1",()=>{se.info("SYSTEM","Worker started",{port:ls,pid:process.pid,activeSessions:this.sessions.size}),t()}).on("error",s=>{s.code==="EADDRINUSE"&&se.error("SYSTEM",`Port ${ls} already in use - worker may already be running`),i(s)})})}handleHealth(e,r){r.json({status:"ok",port:this.port,pid:process.pid,activeSessions:this.sessions.size,uptime:process.uptime(),memory:process.memoryUsage()})}async handleInit(e,r){let t=parseInt(e.params.sessionDbId,10),{project:i,userPrompt:s}=e.body,n=se.sessionId(t);se.info("WORKER","Session init",{correlationId:n,project:i});let o=new nt,p=o.getSessionById(t);if(!p){o.close(),r.status(404).json({error:"Session not found in database"});return}let c=p.sdk_session_id||`session-${t}`,l={sessionDbId:t,claudeSessionId:c,sdkSessionId:p.sdk_session_id||null,project:i,userPrompt:s,pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,observationCounter:0,startTime:Date.now()};this.sessions.set(t,l),o.setWorkerPort(t,this.port),o.close(),l.generatorPromise=this.runSDKAgent(l).catch(u=>{se.failure("WORKER","SDK agent error",{sessionId:t},u);let d=new nt;d.markSessionFailed(t),d.close(),this.sessions.delete(t)}),se.success("WORKER","Session initialized",{sessionId:t,port:this.port}),r.json({status:"initialized",sessionDbId:t,port:this.port})}handleObservation(e,r){let t=parseInt(e.params.sessionDbId,10),{tool_name:i,tool_input:s,tool_output:n,prompt_number:o}=e.body,p=this.sessions.get(t);if(!p){let u=new nt,d=u.getSessionById(t);u.close();let m=d?.sdk_session_id||`session-${t}`;p={sessionDbId:t,claudeSessionId:m,sdkSessionId:null,project:d?.project||"",userPrompt:d?.user_prompt||"",pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,observationCounter:0,startTime:Date.now()},this.sessions.set(t,p),p.generatorPromise=this.runSDKAgent(p).catch(v=>{se.failure("WORKER","SDK agent error",{sessionId:t},v);let f=new nt;f.markSessionFailed(t),f.close(),this.sessions.delete(t)})}p.observationCounter++;let c=se.correlationId(t,p.observationCounter),l=se.formatTool(i,s);se.dataIn("WORKER",`Observation queued: ${l}`,{correlationId:c,queue:p.pendingMessages.length+1}),p.pendingMessages.push({type:"observation",tool_name:i,tool_input:s,tool_output:n,prompt_number:o}),r.json({status:"queued",queueLength:p.pendingMessages.length})}handleSummarize(e,r){let t=parseInt(e.params.sessionDbId,10),{prompt_number:i}=e.body,s=this.sessions.get(t);if(!s){let n=new nt,o=n.getSessionById(t);n.close();let p=o?.sdk_session_id||`session-${t}`;s={sessionDbId:t,claudeSessionId:p,sdkSessionId:null,project:o?.project||"",userPrompt:o?.user_prompt||"",pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,observationCounter:0,startTime:Date.now()},this.sessions.set(t,s),s.generatorPromise=this.runSDKAgent(s).catch(c=>{se.failure("WORKER","SDK agent error",{sessionId:t},c);let l=new nt;l.markSessionFailed(t),l.close(),this.sessions.delete(t)})}se.dataIn("WORKER","Summary requested",{sessionId:t,promptNumber:i,queue:s.pendingMessages.length+1}),s.pendingMessages.push({type:"summarize",prompt_number:i}),r.json({status:"queued",queueLength:s.pendingMessages.length})}handleStatus(e,r){let t=parseInt(e.params.sessionDbId,10),i=this.sessions.get(t);if(!i){r.status(404).json({error:"Session not found"});return}r.json({sessionDbId:t,sdkSessionId:i.sdkSessionId,project:i.project,pendingMessages:i.pendingMessages.length})}async handleDelete(e,r){let t=parseInt(e.params.sessionDbId,10),i=this.sessions.get(t);if(!i){r.status(404).json({error:"Session not found"});return}se.warn("WORKER","Session delete requested",{sessionId:t}),i.abortController.abort(),i.generatorPromise&&await Promise.race([i.generatorPromise,new Promise(n=>setTimeout(n,5e3))]);let s=new nt;s.markSessionFailed(t),s.close(),this.sessions.delete(t),se.info("WORKER","Session deleted",{sessionId:t}),r.json({status:"deleted"})}async runSDKAgent(e){se.info("SDK","Agent starting",{sessionId:e.sessionDbId});let r=process.env.CLAUDE_CODE_PATH||"/Users/alexnewman/.nvm/versions/node/v24.5.0/bin/claude";try{let t=lg({prompt:this.createMessageGenerator(e),options:{model:Q2,disallowedTools:J2,abortController:e.abortController,pathToClaudeCodeExecutable:r}});for await(let n of t)if(n.type==="system"&&n.subtype==="init"){let o=n;if(o.session_id){let p=new nt,c=p.updateSDKSessionId(e.sessionDbId,o.session_id);p.close(),c&&(se.success("SDK","Session initialized",{sessionId:e.sessionDbId,sdkSessionId:o.session_id}),e.sdkSessionId=o.session_id)}}else if(n.type==="assistant"){let o=n.message.content,p=Array.isArray(o)?o.filter(l=>l.type==="text").map(l=>l.text).join(` `):typeof o=="string"?o:"",c=p.length;se.dataOut("SDK",`Response received (${c} chars)`,{sessionId:e.sessionDbId,promptNumber:e.lastPromptNumber}),se.debug("SDK","Full response",{sessionId:e.sessionDbId},p),this.handleAgentMessage(e,p,e.lastPromptNumber)}let i=Date.now()-e.startTime;se.success("SDK","Agent completed",{sessionId:e.sessionDbId,duration:`${(i/1e3).toFixed(1)}s`});let s=new nt;s.markSessionCompleted(e.sessionDbId),s.close(),this.sessions.delete(e.sessionDbId)}catch(t){throw t.name==="AbortError"?se.warn("SDK","Agent aborted",{sessionId:e.sessionDbId}):se.failure("SDK","Agent error",{sessionId:e.sessionDbId},t),t}}async*createMessageGenerator(e){let r=hg(e.project,e.claudeSessionId,e.userPrompt);for(se.dataIn("SDK",`Init prompt sent (${r.length} chars)`,{sessionId:e.sessionDbId,claudeSessionId:e.claudeSessionId,project:e.project}),se.debug("SDK","Full init prompt",{sessionId:e.sessionDbId},r),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:r}};!e.abortController.signal.aborted;){if(e.pendingMessages.length===0){await new Promise(t=>setTimeout(t,100));continue}for(;e.pendingMessages.length>0;){let t=e.pendingMessages.shift();if(t.type==="summarize"){e.lastPromptNumber=t.prompt_number;let i=new nt,s=i.getSessionById(e.sessionDbId);if(i.close(),s){let n=gg(s);se.dataIn("SDK",`Summary prompt sent (${n.length} chars)`,{sessionId:e.sessionDbId,promptNumber:t.prompt_number}),se.debug("SDK","Full summary prompt",{sessionId:e.sessionDbId},n),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:n}}}}else if(t.type==="observation"){e.lastPromptNumber=t.prompt_number;let i=vg({id:0,tool_name:t.tool_name,tool_input:t.tool_input,tool_output:t.tool_output,created_at_epoch:Date.now()}),s=se.formatTool(t.tool_name,t.tool_input),n=se.correlationId(e.sessionDbId,e.observationCounter);se.dataIn("SDK",`Observation prompt: ${s}`,{correlationId:n,promptNumber:t.prompt_number,size:`${i.length} chars`}),se.debug("SDK","Full observation prompt",{correlationId:n},i),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:i}}}}}}handleAgentMessage(e,r,t){let i=se.correlationId(e.sessionDbId,e.observationCounter),s=xg(r,i);s.length>0&&se.info("PARSER",`Parsed ${s.length} observation(s)`,{correlationId:i,promptNumber:t,types:s.map(p=>p.type).join(", ")});let n=new nt;for(let p of s)e.sdkSessionId&&(n.storeObservation(e.sdkSessionId,e.project,p,t),se.success("DB","Observation stored",{correlationId:i,type:p.type,title:p.title}));let o=yg(r,e.sessionDbId);o&&e.sdkSessionId&&(se.info("PARSER","Summary parsed",{sessionId:e.sessionDbId,promptNumber:t}),n.storeSummary(e.sdkSessionId,e.project,o,t),se.success("DB","Summary stored",{sessionId:e.sessionDbId})),n.close()}};async function Y2(){await new us().start(),process.on("SIGINT",()=>{se.warn("SYSTEM","Shutting down (SIGINT)"),process.exit(0)}),process.on("SIGTERM",()=>{se.warn("SYSTEM","Shutting down (SIGTERM)"),process.exit(0)})}Y2().catch(a=>{se.failure("SYSTEM","Fatal startup error",{},a),process.exit(1)});0&&(module.exports={WorkerService}); /*! Bundled license information: diff --git a/src/services/worker-service.ts b/src/services/worker-service.ts index 0cbfcc31..1c843bfc 100644 --- a/src/services/worker-service.ts +++ b/src/services/worker-service.ts @@ -135,7 +135,7 @@ class WorkerService { const session: ActiveSession = { sessionDbId, claudeSessionId, - sdkSessionId: null, + sdkSessionId: dbSession.sdk_session_id || null, // Set from database since we set both fields now project, userPrompt, pendingMessages: [],