diff --git a/Auto Run Docs/PR-Triage/PR-Triage-09.md b/Auto Run Docs/PR-Triage/PR-Triage-09.md index fd27335c..0ca22686 100644 --- a/Auto Run Docs/PR-Triage/PR-Triage-09.md +++ b/Auto Run Docs/PR-Triage/PR-Triage-09.md @@ -7,7 +7,8 @@ These PRs address Chroma vector database stability, zombie processes, and enterp - [x] Review PR #887 (`fix: align IDs with metadatas in ChromaSearchStrategy` by @abkrim). Files: `src/services/worker/search/strategies/ChromaSearchStrategy.ts`, tests. IDs and metadata arrays are misaligned causing incorrect search results. Steps: (1) `gh pr checkout 887` (2) Review — array alignment between document IDs and their metadata is critical for correct results (3) Check test coverage (4) Run `npm run build` (5) If clean: `gh pr merge 887 --rebase --delete-branch` - **Merged** (2026-02-06): Confirmed the bug — `queryChroma()` deduplicates IDs but returns raw metadatas array, causing index misalignment in `filterByRecency()`. Fix builds a Map from sqlite_id→metadata then iterates deduplicated IDs. 20 tests pass (including 2 new). Build clean. -- [ ] Review PR #769 (`fix: close transport on connection error to prevent chroma-mcp zombie processes` by @jenyapoyarkov). Files: `src/services/sync/ChromaSync.ts`, tests. Transport left open on connection failure creates zombies. Steps: (1) `gh pr checkout 769` (2) Review — should close/dispose transport in the error path (3) Run `npm run build` (4) If clean: `gh pr merge 769 --rebase --delete-branch` +- [x] Review PR #769 (`fix: close transport on connection error to prevent chroma-mcp zombie processes` by @jenyapoyarkov). Files: `src/services/sync/ChromaSync.ts`, tests. Transport left open on connection failure creates zombies. Steps: (1) `gh pr checkout 769` (2) Review — should close/dispose transport in the error path (3) Run `npm run build` (4) If clean: `gh pr merge 769 --rebase --delete-branch` + - **Merged** (2026-02-06): Confirmed the bug — both `ensureCollection()` (~line 202) and `queryChroma()` (~line 862) error handlers reset `connected` and `client` on connection errors but never called `transport.close()`, leaving chroma-mcp subprocesses alive as zombies. Fix adds `transport.close()` (wrapped in try/catch for already-dead transports) and `transport = null` before resetting state, mirroring the `close()` method pattern. 3 new regression tests added. All 19 integration tests + 20 ChromaSearchStrategy tests pass. Build clean. - [ ] Review PR #884 (`fix: add Zscaler SSL certificate support for ChromaDB vector search` by @RClark4958). Files: `src/services/sync/ChromaSync.ts` + build artifacts. Enterprise environments use Zscaler SSL inspection which breaks Chroma HTTPS connections. Steps: (1) `gh pr checkout 884` (2) Review — should respect NODE_EXTRA_CA_CERTS or custom CA cert configuration (3) Verify this doesn't weaken SSL for non-Zscaler users (4) Run `npm run build` (5) If appropriate for enterprise support: `gh pr merge 884 --rebase --delete-branch` diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index f0c0cfa8..a969d860 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -941,7 +941,7 @@ For more info: https://docs.claude-mem.ai/cursor FROM user_prompts WHERE content_session_id = ? ORDER BY prompt_number ASC - `).all(e)}close(){this.db.close()}};Ah();Se();Mr();en();var r2=ut(require("path"),1),n2=ut(require("os"),1),_ie="9.0.17",Mh=class{client=null;transport=null;connected=!1;project;collectionName;VECTOR_DB_DIR;BATCH_SIZE=100;disabled;constructor(e){this.project=e,this.collectionName=`cm__${e}`,this.VECTOR_DB_DIR=r2.default.join(n2.default.homedir(),".claude-mem","vector-db"),this.disabled=process.platform==="win32",this.disabled&&E.warn("CHROMA_SYNC","Vector search disabled on Windows (prevents console popups)",{project:this.project,reason:"MCP SDK subprocess spawning causes visible console windows"})}isDisabled(){return this.disabled}async ensureConnection(){if(!(this.connected&&this.client)){E.info("CHROMA_SYNC","Connecting to Chroma MCP server...",{project:this.project});try{let r=Fe.loadFromFile(Yr).CLAUDE_MEM_PYTHON_VERSION,n=process.platform==="win32",i={command:"uvx",args:["--python",r,"chroma-mcp","--client-type","persistent","--data-dir",this.VECTOR_DB_DIR],stderr:"ignore"};n&&(i.windowsHide=!0,E.debug("CHROMA_SYNC","Windows detected, attempting to hide console window",{project:this.project})),this.transport=new Os(i),this.client=new ks({name:"claude-mem-chroma-sync",version:_ie},{capabilities:{}}),await this.client.connect(this.transport),this.connected=!0,E.info("CHROMA_SYNC","Connected to Chroma MCP server",{project:this.project})}catch(e){throw E.error("CHROMA_SYNC","Failed to connect to Chroma MCP server",{project:this.project},e),new Error(`Chroma connection failed: ${e instanceof Error?e.message:String(e)}`)}}}async ensureCollection(){if(await this.ensureConnection(),!this.client)throw new Error(`Chroma client not initialized. Call ensureConnection() before using client methods. Project: ${this.project}`);try{await this.client.callTool({name:"chroma_get_collection_info",arguments:{collection_name:this.collectionName}}),E.debug("CHROMA_SYNC","Collection exists",{collection:this.collectionName})}catch(e){let r=e instanceof Error?e.message:String(e);if(r.includes("Not connected")||r.includes("Connection closed")||r.includes("MCP error -32000"))throw this.connected=!1,this.client=null,E.error("CHROMA_SYNC","Connection lost during collection check",{collection:this.collectionName},e),new Error(`Chroma connection lost: ${r}`);E.error("CHROMA_SYNC","Collection check failed, attempting to create",{collection:this.collectionName},e),E.info("CHROMA_SYNC","Creating collection",{collection:this.collectionName});try{await this.client.callTool({name:"chroma_create_collection",arguments:{collection_name:this.collectionName,embedding_function_name:"default"}}),E.info("CHROMA_SYNC","Collection created",{collection:this.collectionName})}catch(i){throw E.error("CHROMA_SYNC","Failed to create collection",{collection:this.collectionName},i),new Error(`Collection creation failed: ${i instanceof Error?i.message:String(i)}`)}}}formatObservationDocs(e){let r=[],n=e.facts?JSON.parse(e.facts):[],i=e.concepts?JSON.parse(e.concepts):[],a=e.files_read?JSON.parse(e.files_read):[],o=e.files_modified?JSON.parse(e.files_modified):[],s={sqlite_id:e.id,doc_type:"observation",memory_session_id:e.memory_session_id,project:e.project,created_at_epoch:e.created_at_epoch,type:e.type||"discovery",title:e.title||"Untitled"};return e.subtitle&&(s.subtitle=e.subtitle),i.length>0&&(s.concepts=i.join(",")),a.length>0&&(s.files_read=a.join(",")),o.length>0&&(s.files_modified=o.join(",")),e.narrative&&r.push({id:`obs_${e.id}_narrative`,document:e.narrative,metadata:{...s,field_type:"narrative"}}),e.text&&r.push({id:`obs_${e.id}_text`,document:e.text,metadata:{...s,field_type:"text"}}),n.forEach((c,u)=>{r.push({id:`obs_${e.id}_fact_${u}`,document:c,metadata:{...s,field_type:"fact",fact_index:u}})}),r}formatSummaryDocs(e){let r=[],n={sqlite_id:e.id,doc_type:"session_summary",memory_session_id:e.memory_session_id,project:e.project,created_at_epoch:e.created_at_epoch,prompt_number:e.prompt_number||0};return e.request&&r.push({id:`summary_${e.id}_request`,document:e.request,metadata:{...n,field_type:"request"}}),e.investigated&&r.push({id:`summary_${e.id}_investigated`,document:e.investigated,metadata:{...n,field_type:"investigated"}}),e.learned&&r.push({id:`summary_${e.id}_learned`,document:e.learned,metadata:{...n,field_type:"learned"}}),e.completed&&r.push({id:`summary_${e.id}_completed`,document:e.completed,metadata:{...n,field_type:"completed"}}),e.next_steps&&r.push({id:`summary_${e.id}_next_steps`,document:e.next_steps,metadata:{...n,field_type:"next_steps"}}),e.notes&&r.push({id:`summary_${e.id}_notes`,document:e.notes,metadata:{...n,field_type:"notes"}}),r}async addDocuments(e){if(e.length!==0){if(await this.ensureCollection(),!this.client)throw new Error(`Chroma client not initialized. Call ensureConnection() before using client methods. Project: ${this.project}`);try{await this.client.callTool({name:"chroma_add_documents",arguments:{collection_name:this.collectionName,documents:e.map(r=>r.document),ids:e.map(r=>r.id),metadatas:e.map(r=>r.metadata)}}),E.debug("CHROMA_SYNC","Documents added",{collection:this.collectionName,count:e.length})}catch(r){throw E.error("CHROMA_SYNC","Failed to add documents",{collection:this.collectionName,count:e.length},r),new Error(`Document add failed: ${r instanceof Error?r.message:String(r)}`)}}}async syncObservation(e,r,n,i,a,o,s=0){if(this.disabled)return;let c={id:e,memory_session_id:r,project:n,text:null,type:i.type,title:i.title,subtitle:i.subtitle,facts:JSON.stringify(i.facts),narrative:i.narrative,concepts:JSON.stringify(i.concepts),files_read:JSON.stringify(i.files_read),files_modified:JSON.stringify(i.files_modified),prompt_number:a,discovery_tokens:s,created_at:new Date(o*1e3).toISOString(),created_at_epoch:o},u=this.formatObservationDocs(c);E.info("CHROMA_SYNC","Syncing observation",{observationId:e,documentCount:u.length,project:n}),await this.addDocuments(u)}async syncSummary(e,r,n,i,a,o,s=0){if(this.disabled)return;let c={id:e,memory_session_id:r,project:n,request:i.request,investigated:i.investigated,learned:i.learned,completed:i.completed,next_steps:i.next_steps,notes:i.notes,prompt_number:a,discovery_tokens:s,created_at:new Date(o*1e3).toISOString(),created_at_epoch:o},u=this.formatSummaryDocs(c);E.info("CHROMA_SYNC","Syncing summary",{summaryId:e,documentCount:u.length,project:n}),await this.addDocuments(u)}formatUserPromptDoc(e){return{id:`prompt_${e.id}`,document:e.prompt_text,metadata:{sqlite_id:e.id,doc_type:"user_prompt",memory_session_id:e.memory_session_id,project:e.project,created_at_epoch:e.created_at_epoch,prompt_number:e.prompt_number}}}async syncUserPrompt(e,r,n,i,a,o){if(this.disabled)return;let s={id:e,content_session_id:"",prompt_number:a,prompt_text:i,created_at:new Date(o*1e3).toISOString(),created_at_epoch:o,memory_session_id:r,project:n},c=this.formatUserPromptDoc(s);E.info("CHROMA_SYNC","Syncing user prompt",{promptId:e,project:n}),await this.addDocuments([c])}async getExistingChromaIds(){if(await this.ensureConnection(),!this.client)throw new Error(`Chroma client not initialized. Call ensureConnection() before using client methods. Project: ${this.project}`);let e=new Set,r=new Set,n=new Set,i=0,a=1e3;for(E.info("CHROMA_SYNC","Fetching existing Chroma document IDs...",{project:this.project});;)try{let s=(await this.client.callTool({name:"chroma_get_documents",arguments:{collection_name:this.collectionName,limit:a,offset:i,where:{project:this.project},include:["metadatas"]}})).content[0];if(s.type!=="text")throw new Error("Unexpected response type from chroma_get_documents");let u=JSON.parse(s.text).metadatas||[];if(u.length===0)break;for(let l of u)l.sqlite_id&&(l.doc_type==="observation"?e.add(l.sqlite_id):l.doc_type==="session_summary"?r.add(l.sqlite_id):l.doc_type==="user_prompt"&&n.add(l.sqlite_id));i+=a,E.debug("CHROMA_SYNC","Fetched batch of existing IDs",{project:this.project,offset:i,batchSize:u.length})}catch(o){throw E.error("CHROMA_SYNC","Failed to fetch existing IDs",{project:this.project},o),o}return E.info("CHROMA_SYNC","Existing IDs fetched",{project:this.project,observations:e.size,summaries:r.size,prompts:n.size}),{observations:e,summaries:r,prompts:n}}async ensureBackfilled(){if(this.disabled)return;E.info("CHROMA_SYNC","Starting smart backfill",{project:this.project}),await this.ensureCollection();let e=await this.getExistingChromaIds(),r=new Ta;try{let n=Array.from(e.observations),i=n.length>0?`AND id NOT IN (${n.join(",")})`:"",a=r.db.prepare(` + `).all(e)}close(){this.db.close()}};Ah();Se();Mr();en();var r2=ut(require("path"),1),n2=ut(require("os"),1),_ie="9.0.17",Mh=class{client=null;transport=null;connected=!1;project;collectionName;VECTOR_DB_DIR;BATCH_SIZE=100;disabled;constructor(e){this.project=e,this.collectionName=`cm__${e}`,this.VECTOR_DB_DIR=r2.default.join(n2.default.homedir(),".claude-mem","vector-db"),this.disabled=process.platform==="win32",this.disabled&&E.warn("CHROMA_SYNC","Vector search disabled on Windows (prevents console popups)",{project:this.project,reason:"MCP SDK subprocess spawning causes visible console windows"})}isDisabled(){return this.disabled}async ensureConnection(){if(!(this.connected&&this.client)){E.info("CHROMA_SYNC","Connecting to Chroma MCP server...",{project:this.project});try{let r=Fe.loadFromFile(Yr).CLAUDE_MEM_PYTHON_VERSION,n=process.platform==="win32",i={command:"uvx",args:["--python",r,"chroma-mcp","--client-type","persistent","--data-dir",this.VECTOR_DB_DIR],stderr:"ignore"};n&&(i.windowsHide=!0,E.debug("CHROMA_SYNC","Windows detected, attempting to hide console window",{project:this.project})),this.transport=new Os(i),this.client=new ks({name:"claude-mem-chroma-sync",version:_ie},{capabilities:{}}),await this.client.connect(this.transport),this.connected=!0,E.info("CHROMA_SYNC","Connected to Chroma MCP server",{project:this.project})}catch(e){throw E.error("CHROMA_SYNC","Failed to connect to Chroma MCP server",{project:this.project},e),new Error(`Chroma connection failed: ${e instanceof Error?e.message:String(e)}`)}}}async ensureCollection(){if(await this.ensureConnection(),!this.client)throw new Error(`Chroma client not initialized. Call ensureConnection() before using client methods. Project: ${this.project}`);try{await this.client.callTool({name:"chroma_get_collection_info",arguments:{collection_name:this.collectionName}}),E.debug("CHROMA_SYNC","Collection exists",{collection:this.collectionName})}catch(e){let r=e instanceof Error?e.message:String(e);if(r.includes("Not connected")||r.includes("Connection closed")||r.includes("MCP error -32000")){if(this.transport)try{await this.transport.close()}catch(i){E.debug("CHROMA_SYNC","Transport close error (expected if already dead)",{},i)}throw this.connected=!1,this.client=null,this.transport=null,E.error("CHROMA_SYNC","Connection lost during collection check",{collection:this.collectionName},e),new Error(`Chroma connection lost: ${r}`)}E.error("CHROMA_SYNC","Collection check failed, attempting to create",{collection:this.collectionName},e),E.info("CHROMA_SYNC","Creating collection",{collection:this.collectionName});try{await this.client.callTool({name:"chroma_create_collection",arguments:{collection_name:this.collectionName,embedding_function_name:"default"}}),E.info("CHROMA_SYNC","Collection created",{collection:this.collectionName})}catch(i){throw E.error("CHROMA_SYNC","Failed to create collection",{collection:this.collectionName},i),new Error(`Collection creation failed: ${i instanceof Error?i.message:String(i)}`)}}}formatObservationDocs(e){let r=[],n=e.facts?JSON.parse(e.facts):[],i=e.concepts?JSON.parse(e.concepts):[],a=e.files_read?JSON.parse(e.files_read):[],o=e.files_modified?JSON.parse(e.files_modified):[],s={sqlite_id:e.id,doc_type:"observation",memory_session_id:e.memory_session_id,project:e.project,created_at_epoch:e.created_at_epoch,type:e.type||"discovery",title:e.title||"Untitled"};return e.subtitle&&(s.subtitle=e.subtitle),i.length>0&&(s.concepts=i.join(",")),a.length>0&&(s.files_read=a.join(",")),o.length>0&&(s.files_modified=o.join(",")),e.narrative&&r.push({id:`obs_${e.id}_narrative`,document:e.narrative,metadata:{...s,field_type:"narrative"}}),e.text&&r.push({id:`obs_${e.id}_text`,document:e.text,metadata:{...s,field_type:"text"}}),n.forEach((c,u)=>{r.push({id:`obs_${e.id}_fact_${u}`,document:c,metadata:{...s,field_type:"fact",fact_index:u}})}),r}formatSummaryDocs(e){let r=[],n={sqlite_id:e.id,doc_type:"session_summary",memory_session_id:e.memory_session_id,project:e.project,created_at_epoch:e.created_at_epoch,prompt_number:e.prompt_number||0};return e.request&&r.push({id:`summary_${e.id}_request`,document:e.request,metadata:{...n,field_type:"request"}}),e.investigated&&r.push({id:`summary_${e.id}_investigated`,document:e.investigated,metadata:{...n,field_type:"investigated"}}),e.learned&&r.push({id:`summary_${e.id}_learned`,document:e.learned,metadata:{...n,field_type:"learned"}}),e.completed&&r.push({id:`summary_${e.id}_completed`,document:e.completed,metadata:{...n,field_type:"completed"}}),e.next_steps&&r.push({id:`summary_${e.id}_next_steps`,document:e.next_steps,metadata:{...n,field_type:"next_steps"}}),e.notes&&r.push({id:`summary_${e.id}_notes`,document:e.notes,metadata:{...n,field_type:"notes"}}),r}async addDocuments(e){if(e.length!==0){if(await this.ensureCollection(),!this.client)throw new Error(`Chroma client not initialized. Call ensureConnection() before using client methods. Project: ${this.project}`);try{await this.client.callTool({name:"chroma_add_documents",arguments:{collection_name:this.collectionName,documents:e.map(r=>r.document),ids:e.map(r=>r.id),metadatas:e.map(r=>r.metadata)}}),E.debug("CHROMA_SYNC","Documents added",{collection:this.collectionName,count:e.length})}catch(r){throw E.error("CHROMA_SYNC","Failed to add documents",{collection:this.collectionName,count:e.length},r),new Error(`Document add failed: ${r instanceof Error?r.message:String(r)}`)}}}async syncObservation(e,r,n,i,a,o,s=0){if(this.disabled)return;let c={id:e,memory_session_id:r,project:n,text:null,type:i.type,title:i.title,subtitle:i.subtitle,facts:JSON.stringify(i.facts),narrative:i.narrative,concepts:JSON.stringify(i.concepts),files_read:JSON.stringify(i.files_read),files_modified:JSON.stringify(i.files_modified),prompt_number:a,discovery_tokens:s,created_at:new Date(o*1e3).toISOString(),created_at_epoch:o},u=this.formatObservationDocs(c);E.info("CHROMA_SYNC","Syncing observation",{observationId:e,documentCount:u.length,project:n}),await this.addDocuments(u)}async syncSummary(e,r,n,i,a,o,s=0){if(this.disabled)return;let c={id:e,memory_session_id:r,project:n,request:i.request,investigated:i.investigated,learned:i.learned,completed:i.completed,next_steps:i.next_steps,notes:i.notes,prompt_number:a,discovery_tokens:s,created_at:new Date(o*1e3).toISOString(),created_at_epoch:o},u=this.formatSummaryDocs(c);E.info("CHROMA_SYNC","Syncing summary",{summaryId:e,documentCount:u.length,project:n}),await this.addDocuments(u)}formatUserPromptDoc(e){return{id:`prompt_${e.id}`,document:e.prompt_text,metadata:{sqlite_id:e.id,doc_type:"user_prompt",memory_session_id:e.memory_session_id,project:e.project,created_at_epoch:e.created_at_epoch,prompt_number:e.prompt_number}}}async syncUserPrompt(e,r,n,i,a,o){if(this.disabled)return;let s={id:e,content_session_id:"",prompt_number:a,prompt_text:i,created_at:new Date(o*1e3).toISOString(),created_at_epoch:o,memory_session_id:r,project:n},c=this.formatUserPromptDoc(s);E.info("CHROMA_SYNC","Syncing user prompt",{promptId:e,project:n}),await this.addDocuments([c])}async getExistingChromaIds(){if(await this.ensureConnection(),!this.client)throw new Error(`Chroma client not initialized. Call ensureConnection() before using client methods. Project: ${this.project}`);let e=new Set,r=new Set,n=new Set,i=0,a=1e3;for(E.info("CHROMA_SYNC","Fetching existing Chroma document IDs...",{project:this.project});;)try{let s=(await this.client.callTool({name:"chroma_get_documents",arguments:{collection_name:this.collectionName,limit:a,offset:i,where:{project:this.project},include:["metadatas"]}})).content[0];if(s.type!=="text")throw new Error("Unexpected response type from chroma_get_documents");let u=JSON.parse(s.text).metadatas||[];if(u.length===0)break;for(let l of u)l.sqlite_id&&(l.doc_type==="observation"?e.add(l.sqlite_id):l.doc_type==="session_summary"?r.add(l.sqlite_id):l.doc_type==="user_prompt"&&n.add(l.sqlite_id));i+=a,E.debug("CHROMA_SYNC","Fetched batch of existing IDs",{project:this.project,offset:i,batchSize:u.length})}catch(o){throw E.error("CHROMA_SYNC","Failed to fetch existing IDs",{project:this.project},o),o}return E.info("CHROMA_SYNC","Existing IDs fetched",{project:this.project,observations:e.size,summaries:r.size,prompts:n.size}),{observations:e,summaries:r,prompts:n}}async ensureBackfilled(){if(this.disabled)return;E.info("CHROMA_SYNC","Starting smart backfill",{project:this.project}),await this.ensureCollection();let e=await this.getExistingChromaIds(),r=new Ta;try{let n=Array.from(e.observations),i=n.length>0?`AND id NOT IN (${n.join(",")})`:"",a=r.db.prepare(` SELECT * FROM observations WHERE project = ? ${i} ORDER BY id ASC @@ -967,7 +967,7 @@ For more info: https://docs.claude-mem.ai/cursor FROM user_prompts up JOIN sdk_sessions s ON up.content_session_id = s.content_session_id WHERE s.project = ? - `).get(this.project);E.info("CHROMA_SYNC","Backfilling user prompts",{project:this.project,missing:_.length,existing:e.prompts.size,total:g.count});let m=[];for(let y of _)m.push(this.formatUserPromptDoc(y));for(let y=0;y=zh){E.info("SESSION","Idle timeout reached, triggering abort to kill subprocess",{sessionDbId:r,idleDurationMs:c,thresholdMs:zh}),i?.();return}a=Date.now()}}catch(o){if(n.aborted)return;E.error("SESSION","Error in queue processor loop",{sessionDbId:r},o),await new Promise(s=>setTimeout(s,1e3))}}toPendingMessageWithId(e){return{...this.store.toPendingMessage(e),_persistentId:e.id,_originalTimestamp:e.created_at_epoch}}waitForMessage(e,r=zh){return new Promise(n=>{let i,a=()=>{c(),n(!0)},o=()=>{c(),n(!1)},s=()=>{c(),n(!1)},c=()=>{i!==void 0&&clearTimeout(i),this.events.off("message",a),e.removeEventListener("abort",o)};this.events.once("message",a),e.addEventListener("abort",o,{once:!0}),i=setTimeout(s,r)})}};var Lh=require("child_process"),i2=require("util");Se();var a2=(0,i2.promisify)(Lh.exec),qh=new Map;function bie(t,e,r){qh.set(t,{pid:t,sessionDbId:e,spawnedAt:Date.now(),process:r}),E.info("PROCESS",`Registered PID ${t} for session ${e}`,{pid:t,sessionDbId:e})}function vd(t){qh.delete(t),E.debug("PROCESS",`Unregistered PID ${t}`,{pid:t})}function o2(t){let e=[];for(let[,r]of qh)r.sessionDbId===t&&e.push(r);return e.length>1&&E.warn("PROCESS",`Multiple processes found for session ${t}`,{count:e.length,pids:e.map(r=>r.pid)}),e[0]}async function s2(t,e=5e3){let{pid:r,process:n}=t;if(n.killed||n.exitCode!==null){vd(r);return}let i=new Promise(o=>{n.once("exit",()=>o())}),a=new Promise(o=>{setTimeout(o,e)});if(await Promise.race([i,a]),n.killed||n.exitCode!==null){vd(r);return}E.warn("PROCESS",`PID ${r} did not exit after ${e}ms, sending SIGKILL`,{pid:r,timeoutMs:e});try{n.kill("SIGKILL")}catch{}await new Promise(o=>setTimeout(o,200)),vd(r)}async function xie(){if(process.platform==="win32")return 0;let t=process.pid,e=0;try{let{stdout:r}=await a2('ps -eo pid,ppid,%cpu,etime,comm 2>/dev/null | grep "claude$" || true');for(let n of r.trim().split(` + `).get(this.project);E.info("CHROMA_SYNC","Backfilling user prompts",{project:this.project,missing:_.length,existing:e.prompts.size,total:g.count});let m=[];for(let y of _)m.push(this.formatUserPromptDoc(y));for(let y=0;y=zh){E.info("SESSION","Idle timeout reached, triggering abort to kill subprocess",{sessionDbId:r,idleDurationMs:c,thresholdMs:zh}),i?.();return}a=Date.now()}}catch(o){if(n.aborted)return;E.error("SESSION","Error in queue processor loop",{sessionDbId:r},o),await new Promise(s=>setTimeout(s,1e3))}}toPendingMessageWithId(e){return{...this.store.toPendingMessage(e),_persistentId:e.id,_originalTimestamp:e.created_at_epoch}}waitForMessage(e,r=zh){return new Promise(n=>{let i,a=()=>{c(),n(!0)},o=()=>{c(),n(!1)},s=()=>{c(),n(!1)},c=()=>{i!==void 0&&clearTimeout(i),this.events.off("message",a),e.removeEventListener("abort",o)};this.events.once("message",a),e.addEventListener("abort",o,{once:!0}),i=setTimeout(s,r)})}};var Lh=require("child_process"),i2=require("util");Se();var a2=(0,i2.promisify)(Lh.exec),qh=new Map;function bie(t,e,r){qh.set(t,{pid:t,sessionDbId:e,spawnedAt:Date.now(),process:r}),E.info("PROCESS",`Registered PID ${t} for session ${e}`,{pid:t,sessionDbId:e})}function vd(t){qh.delete(t),E.debug("PROCESS",`Unregistered PID ${t}`,{pid:t})}function o2(t){let e=[];for(let[,r]of qh)r.sessionDbId===t&&e.push(r);return e.length>1&&E.warn("PROCESS",`Multiple processes found for session ${t}`,{count:e.length,pids:e.map(r=>r.pid)}),e[0]}async function s2(t,e=5e3){let{pid:r,process:n}=t;if(n.killed||n.exitCode!==null){vd(r);return}let i=new Promise(o=>{n.once("exit",()=>o())}),a=new Promise(o=>{setTimeout(o,e)});if(await Promise.race([i,a]),n.killed||n.exitCode!==null){vd(r);return}E.warn("PROCESS",`PID ${r} did not exit after ${e}ms, sending SIGKILL`,{pid:r,timeoutMs:e});try{n.kill("SIGKILL")}catch{}await new Promise(o=>setTimeout(o,200)),vd(r)}async function xie(){if(process.platform==="win32")return 0;let t=process.pid,e=0;try{let{stdout:r}=await a2('ps -eo pid,ppid,%cpu,etime,comm 2>/dev/null | grep "claude$" || true');for(let n of r.trim().split(` `)){if(!n)continue;let i=n.trim().split(/\s+/);if(i.length<5)continue;let[a,o,s,c]=i,u=parseInt(a,10),l=parseInt(o,10),d=parseFloat(s);if(l!==t||d>0)continue;let p=0,f=c.match(/^(\d+)-(\d+):(\d+):(\d+)$/),h=c.match(/^(\d+):(\d+):(\d+)$/),_=c.match(/^(\d+):(\d+)$/);if(f?p=parseInt(f[1],10)*24*60+parseInt(f[2],10)*60+parseInt(f[3],10):h?p=parseInt(h[1],10)*60+parseInt(h[2],10):_&&(p=parseInt(_[1],10)),p>=2){E.info("PROCESS",`Killing idle daemon child PID ${u} (idle ${p}m)`,{pid:u,minutes:p});try{process.kill(u,"SIGKILL"),e++}catch{}}}}catch{}return e}async function Sie(){if(process.platform==="win32")return 0;try{let{stdout:t}=await a2('ps -eo pid,ppid,args 2>/dev/null | grep -E "claude.*haiku|claude.*output-format" | grep -v grep'),e=0;for(let r of t.trim().split(` `)){if(!r)continue;let n=r.trim().match(/^(\d+)\s+(\d+)/);if(n&&parseInt(n[2])===1){let i=parseInt(n[1]);E.warn("PROCESS",`Killing system orphan PID ${i}`,{pid:i});try{process.kill(i,"SIGKILL"),e++}catch{}}}return e}catch{return 0}}async function wie(t){let e=0;for(let[r,n]of qh)if(!t.has(n.sessionDbId)){E.warn("PROCESS",`Killing orphan PID ${r} (session ${n.sessionDbId} gone)`,{pid:r,sessionDbId:n.sessionDbId});try{n.process.kill("SIGKILL"),e++}catch{}vd(r)}return e+=await Sie(),e+=await xie(),e}function c2(t){return e=>{let r=(0,Lh.spawn)(e.command,e.args,{cwd:e.cwd,env:e.env,stdio:["pipe","pipe","pipe"],signal:e.signal,windowsHide:!0});return r.pid&&(bie(r.pid,t,r),r.on("exit",()=>{r.pid&&vd(r.pid)})),{stdin:r.stdin,stdout:r.stdout,get killed(){return r.killed},get exitCode(){return r.exitCode},kill:r.kill.bind(r),on:r.on.bind(r),once:r.once.bind(r),off:r.off.bind(r)}}}function u2(t,e=300*1e3){let r=setInterval(async()=>{try{let n=t(),i=await wie(n);i>0&&E.info("PROCESS",`Reaper cleaned up ${i} orphaned processes`,{killed:i})}catch(n){E.error("PROCESS","Reaper error",{},n)}},e);return()=>clearInterval(r)}var Fh=class{dbManager;sessions=new Map;sessionQueues=new Map;onSessionDeletedCallback;pendingStore=null;constructor(e){this.dbManager=e}getPendingStore(){if(!this.pendingStore){let e=this.dbManager.getSessionStore();this.pendingStore=new gd(e.db,3)}return this.pendingStore}setOnSessionDeleted(e){this.onSessionDeletedCallback=e}initializeSession(e,r,n){E.debug("SESSION","initializeSession called",{sessionDbId:e,promptNumber:n,has_currentUserPrompt:!!r});let i=this.sessions.get(e);if(i){E.debug("SESSION","Returning cached session",{sessionDbId:e,contentSessionId:i.contentSessionId,lastPromptNumber:i.lastPromptNumber});let c=this.dbManager.getSessionById(e);return c.project&&c.project!==i.project&&(E.debug("SESSION","Updating project from database",{sessionDbId:e,oldProject:i.project,newProject:c.project}),i.project=c.project),r?(E.debug("SESSION","Updating userPrompt for continuation",{sessionDbId:e,promptNumber:n,oldPrompt:i.userPrompt.substring(0,80),newPrompt:r.substring(0,80)}),i.userPrompt=r,i.lastPromptNumber=n||i.lastPromptNumber):E.debug("SESSION","No currentUserPrompt provided for existing session",{sessionDbId:e,promptNumber:n,usingCachedPrompt:i.userPrompt.substring(0,80)}),i}let a=this.dbManager.getSessionById(e);E.debug("SESSION","Fetched session from database",{sessionDbId:e,content_session_id:a.content_session_id,memory_session_id:a.memory_session_id}),a.memory_session_id&&E.warn("SESSION","Discarding stale memory_session_id from previous worker instance (Issue #817)",{sessionDbId:e,staleMemorySessionId:a.memory_session_id,reason:"SDK context lost on worker restart - will capture new ID"});let o=r||a.user_prompt;r?E.debug("SESSION","Initializing session with fresh userPrompt",{sessionDbId:e,promptNumber:n,userPrompt:r.substring(0,80)}):E.debug("SESSION","No currentUserPrompt provided for new session, using database",{sessionDbId:e,promptNumber:n,dbPrompt:a.user_prompt.substring(0,80)}),i={sessionDbId:e,contentSessionId:a.content_session_id,memorySessionId:null,project:a.project,userPrompt:o,pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:n||this.dbManager.getSessionStore().getPromptNumberFromUserPrompts(a.content_session_id),startTime:Date.now(),cumulativeInputTokens:0,cumulativeOutputTokens:0,earliestPendingTimestamp:null,conversationHistory:[],currentProvider:null,consecutiveRestarts:0},E.debug("SESSION","Creating new session object (memorySessionId cleared to prevent stale resume)",{sessionDbId:e,contentSessionId:a.content_session_id,dbMemorySessionId:a.memory_session_id||"(none in DB)",memorySessionId:"(cleared - will capture fresh from SDK)",lastPromptNumber:n||this.dbManager.getSessionStore().getPromptNumberFromUserPrompts(a.content_session_id)}),this.sessions.set(e,i);let s=new l2.EventEmitter;return this.sessionQueues.set(e,s),E.info("SESSION","Session initialized",{sessionId:e,project:i.project,contentSessionId:i.contentSessionId,queueDepth:0,hasGenerator:!1}),i}getSession(e){return this.sessions.get(e)}queueObservation(e,r){let n=this.sessions.get(e);n||(n=this.initializeSession(e));let i={type:"observation",tool_name:r.tool_name,tool_input:r.tool_input,tool_response:r.tool_response,prompt_number:r.prompt_number,cwd:r.cwd};try{let o=this.getPendingStore().enqueue(e,n.contentSessionId,i),s=this.getPendingStore().getPendingCount(e),c=E.formatTool(r.tool_name,r.tool_input);E.info("QUEUE",`ENQUEUED | sessionDbId=${e} | messageId=${o} | type=observation | tool=${c} | depth=${s}`,{sessionId:e})}catch(o){throw E.error("SESSION","Failed to persist observation to DB",{sessionId:e,tool:r.tool_name},o),o}this.sessionQueues.get(e)?.emit("message")}queueSummarize(e,r){let n=this.sessions.get(e);n||(n=this.initializeSession(e));let i={type:"summarize",last_assistant_message:r};try{let o=this.getPendingStore().enqueue(e,n.contentSessionId,i),s=this.getPendingStore().getPendingCount(e);E.info("QUEUE",`ENQUEUED | sessionDbId=${e} | messageId=${o} | type=summarize | depth=${s}`,{sessionId:e})}catch(o){throw E.error("SESSION","Failed to persist summarize to DB",{sessionId:e},o),o}this.sessionQueues.get(e)?.emit("message")}async deleteSession(e){let r=this.sessions.get(e);if(!r)return;let n=Date.now()-r.startTime;r.abortController.abort(),r.generatorPromise&&await r.generatorPromise.catch(()=>{E.debug("SYSTEM","Generator already failed, cleaning up",{sessionId:r.sessionDbId})});let i=o2(e);i&&!i.process.killed&&i.process.exitCode===null&&(E.debug("SESSION",`Waiting for subprocess PID ${i.pid} to exit`,{sessionId:e,pid:i.pid}),await s2(i,5e3)),this.sessions.delete(e),this.sessionQueues.delete(e),E.info("SESSION","Session deleted",{sessionId:e,duration:`${(n/1e3).toFixed(1)}s`,project:r.project}),this.onSessionDeletedCallback&&this.onSessionDeletedCallback()}removeSessionImmediate(e){let r=this.sessions.get(e);r&&(this.sessions.delete(e),this.sessionQueues.delete(e),E.info("SESSION","Session removed (orphaned after SDK termination)",{sessionId:e,project:r.project}),this.onSessionDeletedCallback&&this.onSessionDeletedCallback())}async shutdownAll(){let e=Array.from(this.sessions.keys());await Promise.all(e.map(r=>this.deleteSession(r)))}hasPendingMessages(){return this.getPendingStore().hasAnyPendingWork()}getActiveSessionCount(){return this.sessions.size}getTotalQueueDepth(){let e=0;for(let r of this.sessions.values())e+=this.getPendingStore().getPendingCount(r.sessionDbId);return e}getTotalActiveWork(){return this.getTotalQueueDepth()}isAnySessionProcessing(){return this.getPendingStore().hasAnyPendingWork()}async*getMessageIterator(e){let r=this.sessions.get(e);r||(r=this.initializeSession(e));let n=this.sessionQueues.get(e);if(!n)throw new Error(`No emitter for session ${e}`);let i=new Uh(this.getPendingStore(),n);for await(let a of i.createIterator({sessionDbId:e,signal:r.abortController.signal,onIdleTimeout:()=>{E.info("SESSION","Triggering abort due to idle timeout to kill subprocess",{sessionDbId:e}),r.abortController.abort()}}))r.earliestPendingTimestamp===null?r.earliestPendingTimestamp=a._originalTimestamp:r.earliestPendingTimestamp=Math.min(r.earliestPendingTimestamp,a._originalTimestamp),yield a}getPendingMessageStore(){return this.getPendingStore()}};Se();var Zh=class{sseClients=new Set;addClient(e){this.sseClients.add(e),E.debug("WORKER","Client connected",{total:this.sseClients.size}),e.on("close",()=>{this.removeClient(e)}),this.sendToClient(e,{type:"connected",timestamp:Date.now()})}removeClient(e){this.sseClients.delete(e),E.debug("WORKER","Client disconnected",{total:this.sseClients.size})}broadcast(e){if(this.sseClients.size===0){E.debug("WORKER","SSE broadcast skipped (no clients)",{eventType:e.type});return}let r={...e,timestamp:Date.now()},n=`data: ${JSON.stringify(r)}