alchemylab 1.4.2 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ const{sessionId:s,hookId:n}=e.params,a=await o.post(`/api/projects/${r}/hook-ite
27
27
  t.json(a)}catch(e){n.debug("Failed to increment hook iteration",{error:(0,r.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}(e,s,t)))}
28
28
  const r=t(8824)},441(e,exports){"use strict"
29
29
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.BAD_REQUEST=exports.INTERNAL_ERROR=exports.NOT_FOUND=exports.FORBIDDEN=exports.UNAUTHORIZED=exports.VALIDATION_ERROR=exports.MCP_PROTOCOL_ERROR=exports.REQUEST_VALIDATION_FAILED=exports.REQUEST_ID_INVALID_FORMAT=exports.REQUEST_NOT_FOUND=exports.CONNECTION_ERROR=exports.SERVICE_UNAVAILABLE=exports.CONNECTION_TIMEOUT=exports.SERVER_ERROR=exports.NETWORK_ERROR=exports.TASK_VALIDATION_FAILED=exports.TASK_ID_INVALID_FORMAT=exports.TASK_NOT_FOUND=void 0,exports.TASK_NOT_FOUND="TASK_NOT_FOUND",exports.TASK_ID_INVALID_FORMAT="TASK_ID_INVALID_FORMAT",exports.TASK_VALIDATION_FAILED="TASK_VALIDATION_FAILED",exports.NETWORK_ERROR="NETWORK_ERROR",exports.SERVER_ERROR="SERVER_ERROR",exports.CONNECTION_TIMEOUT="CONNECTION_TIMEOUT",exports.SERVICE_UNAVAILABLE="SERVICE_UNAVAILABLE",exports.CONNECTION_ERROR="CONNECTION_ERROR",exports.REQUEST_NOT_FOUND="REQUEST_NOT_FOUND",exports.REQUEST_ID_INVALID_FORMAT="REQUEST_ID_INVALID_FORMAT",exports.REQUEST_VALIDATION_FAILED="REQUEST_VALIDATION_FAILED",exports.MCP_PROTOCOL_ERROR="MCP_PROTOCOL_ERROR",exports.VALIDATION_ERROR="VALIDATION_ERROR",exports.UNAUTHORIZED="UNAUTHORIZED",exports.FORBIDDEN="FORBIDDEN",exports.NOT_FOUND="NOT_FOUND",exports.INTERNAL_ERROR="INTERNAL_ERROR",exports.BAD_REQUEST="BAD_REQUEST"},489(e,exports,t){"use strict"
30
- function r(e,t){return(0,h.join)(e,p,m,"pool-"+t)}function s(){const e=C.shift()
30
+ function r(e,t){return(0,h.join)(e,p,m,"pool-"+t)}function s(){const e=S.shift()
31
31
  e&&setImmediate(e)}async function o(e,t){const o=Date.now()
32
32
  !function(e){let t=!1
33
33
  for(const r of E.values())r.busy&&r.busySince&&e-r.busySince>b&&(r.busy=!1,r.busySince=void 0,t=!0)
@@ -42,13 +42,13 @@ try{n=await d.promises.readdir(o,{withFileTypes:!0})}catch{return}for(const e of
42
42
  return new Set(t)}catch{return null}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getSlotPath=r,exports.acquireSlot=async function(e,t,r=I){return await o(e,t)||new Promise(((i,a)=>{let c=!1
43
43
  const l=Date.now(),d=setTimeout((()=>{if(c)return
44
44
  c=!0
45
- const e=C.indexOf(u)
46
- 0>e||C.splice(e,1),a(Error(`All ${t} pool slot(s) busy after waiting ${Math.round((Date.now()-l)/1e3)}s`))}),r)
45
+ const e=S.indexOf(u)
46
+ 0>e||S.splice(e,1),a(Error(`All ${t} pool slot(s) busy after waiting ${Math.round((Date.now()-l)/1e3)}s`))}),r)
47
47
  d.unref()
48
48
  const u=async()=>{if(c)return
49
49
  const r=await o(e,t)
50
- c?r&&n(r.path):r?(c=!0,clearTimeout(d),i(r)):(C.unshift(u),s())}
51
- C.push(u)}))},exports.releaseSlot=n,exports.scheduleSlotRelease=i,exports.watchPidAndRelease=function(e,t,r,s=5e3){if(i(t,r),!e||0>=e)return
50
+ c?r&&n(r.path):r?(c=!0,clearTimeout(d),i(r)):(S.unshift(u),s())}
51
+ S.push(u)}))},exports.releaseSlot=n,exports.scheduleSlotRelease=i,exports.watchPidAndRelease=function(e,t,r,s=5e3){if(i(t,r),!e||0>=e)return
52
52
  const o=setInterval((()=>{try{process.kill(e,0)}catch{n(t),clearInterval(o)}}),s)
53
53
  o.unref()},exports.readSlotFingerprint=a,exports.writeSlotFingerprint=async function(e,t){try{await d.promises.writeFile((0,h.join)(e,f),t,"utf-8")}catch(e){console.warn("worktree-pool: writeSlotFingerprint failed (non-fatal):",(0,g.getErrorMessage)(e))}},exports.computeFingerprint=async function(e,t){if(!t||0===t.length)return""
54
54
  const r=(0,u.createHash)("sha256")
@@ -58,8 +58,8 @@ await d.promises.writeFile((0,h.join)(e,y),t.join("\n")+"\n","utf-8")}catch(e){c
58
58
  if(!t)return{removed:0}
59
59
  const r=await c(e)
60
60
  let s=0
61
- for(const o of r)if(!t.has(o))try{await d.promises.rm((0,h.join)(e,o),{force:!0}),s++}catch{}return{removed:s}},exports._resetForTests=function(){E.clear(),C.length=0}
62
- const d=t(9896),u=t(6982),h=t(6928),g=t(8824),p=".alchemy_lab",m="test-worktrees",f=".fingerprint.sha256",y=".alchemy-warm-snapshot.txt",v=new Set([f,y]),w=new Set([".git"]),b=12e5,I=6e5,E=new Map,C=[]},833(e,exports,t){"use strict"
61
+ for(const o of r)if(!t.has(o))try{await d.promises.rm((0,h.join)(e,o),{force:!0}),s++}catch{}return{removed:s}},exports._resetForTests=function(){E.clear(),S.length=0}
62
+ const d=t(9896),u=t(6982),h=t(6928),g=t(8824),p=".alchemy_lab",m="test-worktrees",f=".fingerprint.sha256",y=".alchemy-warm-snapshot.txt",v=new Set([f,y]),w=new Set([".git"]),b=12e5,I=6e5,E=new Map,S=[]},833(e,exports,t){"use strict"
63
63
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
64
64
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeUrlFromConfig=function(e){const t=o.default.join(e,".alchemy_lab","opencode.json")
65
65
  try{if(!s.default.existsSync(t))return null
@@ -270,7 +270,7 @@ let f=!1
270
270
  try{await m("create report",(()=>d.post(`/api/projects/${u}/session-messages/reports`,{fromSessionId:n,toSessionId:p,status:"success",summary:"Plan complete. Created requests: "+(a||[]).join(", "),details:c||"Plan complete"}))),f=!0}catch(e){l.error("Failed to create report after retry",{error:(0,r.getErrorMessage)(e)})}let y=!1
271
271
  const v=(0,o.getOpenCodeServerUrl)(h,l)
272
272
  if(v){const e=`The planner has finished creating the plan.\n\nCreated requests: ${(a||[]).join(", ")}\n\nPlease begin working on these requests.`
273
- try{const r=await m("notify parent",(()=>fetch(`${v}/session/${p}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e,metadata:(0,s.partMeta)("inserted-prompt")}],...t.cachedDefaultModel?(()=>{const[e,...r]=t.cachedDefaultModel.split("/")
273
+ try{const r=await m("notify parent",(()=>fetch(`${v}/session/${p}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e,metadata:(0,s.partMeta)("plan-complete",{plannerSessionId:n,summary:c,requestIds:a})}],...t.cachedDefaultModel?(()=>{const[e,...r]=t.cachedDefaultModel.split("/")
274
274
  return{model:{providerID:e,modelID:r.join("/")}}})():{}})})))
275
275
  r.ok?(y=!0,l.info("Notified parent session to start building",{parentSessionId:p})):l.error("Parent session prompt returned non-OK",{status:r.status,parentSessionId:p})}catch(e){l.error("Could not notify parent session after retry",{error:(0,r.getErrorMessage)(e)})}}else l.error("Could not resolve OpenCode server URL \u2014 parent prompt skipped")
276
276
  if(f&&y){const e=g?.id
@@ -298,28 +298,35 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.partMeta=function
298
298
  return{...e,_meta:{...s,alchemy:{...o,...r,kind:t}}}},exports.readAlchemyMeta=function(e){return e?._meta?.alchemy}},2366(e,exports,t){"use strict"
299
299
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
300
300
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
301
- const s=r(t(7252)),o=r(t(8577)),n=t(4192),i=t(1152),a=r(t(6928)),c=r(t(9896)),l=r(t(857)),d=t(5607),u=t(8984),h=t(8524),g=t(8824),p=t(2096),m=t(7210),f=t(7227),y=t(5419),v=t(3962),w=t(2928),b=t(6563),I=t(1401),E=t(1292),C=t(8148),S=t(8463),k=t(489),T=t(3807),_=t(7478),j=t(5317),A=t(355),P=t(833)
302
- class M{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.activeMcpCalls=new Map,this.pendingAgentResync=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.droppedEventCount=0,this.firstDropAt=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
303
- const r=(0,i.createMetricsCollector)(!1)
304
- this.apiClient=new n.BaseApiClient(t.remoteServerUrl,e,r,t.authToken,{timeout:35e3}),this.configurer=new u.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new h.CLIBridgeWebSocket(e),(0,h.setCLIBridge)(this.cliBridge),this.hookExecutor=new m.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,s.default)()
305
- return e.use(((e,t,r)=>{t.setHeader("Access-Control-Allow-Private-Network","true"),r()})),e.use((0,o.default)()),e.use(s.default.json({limit:"10mb"})),e.get("/health",this.handleHealth.bind(this)),e.get("/api/auth-status",this.handleAuthStatus.bind(this)),e.get("/status",this.handleStatus.bind(this)),e.get("/opencode",this.handleOpenCodeConfig.bind(this)),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.post("/api/generate-journal",this.handleGenerateJournal.bind(this)),(0,A.registerHooksRoutes)(e,{apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId}),e.get("/api/project-settings",((e,t)=>{t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.post("/api/sync-settings",(async(e,t)=>{await this.syncProjectSettings(),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.get("/api/agents/by-name/:agentName",this.handleGetAgentByName.bind(this)),e.get("/api/sessions/:sessionId/agent-type",this.handleGetSessionAgentType.bind(this)),e.get("/api/sessions/:sessionId/parent",this.handleGetSessionParent.bind(this)),e.get("/api/sessions/:sessionId/ancestors",(async(e,t)=>{try{const{sessionId:r}=e.params,s=this.projectId
301
+ const s=r(t(7252)),o=r(t(8577)),n=t(9896),i=t(5959),a=t(2640),c=t(4192),l=t(1152),d=r(t(6928)),u=r(t(9896)),h=r(t(857)),g=t(5607),p=t(8984),m=t(8524),f=t(8824),y=t(2096),v=t(7210),w=t(7227),b=t(5419),I=t(3962),E=t(2928),S=t(6563),C=t(1401),k=t(1292),T=t(8148),_=t(8463),j=t(489),A=t(3807),P=t(7478),M=t(5317),O=t(355),R=t(833)
302
+ class D{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.activeMcpCalls=new Map,this.pendingAgentResync=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.droppedEventCount=0,this.firstDropAt=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
303
+ const r=(0,l.createMetricsCollector)(!1)
304
+ this.apiClient=new c.BaseApiClient(t.remoteServerUrl,e,r,t.authToken,{timeout:35e3}),this.configurer=new p.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new m.CLIBridgeWebSocket(e),(0,m.setCLIBridge)(this.cliBridge),this.hookExecutor=new v.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,s.default)()
305
+ return e.use(((e,t,r)=>{t.setHeader("Access-Control-Allow-Private-Network","true"),r()})),e.use((0,o.default)()),e.use(s.default.json({limit:"10mb"})),e.get("/health",this.handleHealth.bind(this)),e.get("/api/commands/running",(async(e,t)=>{try{const e=16384,r=await Promise.all((0,i.listEntries)().map((async t=>{let r,s="",o=0,i=t.finishedAt??t.startedAt
306
+ try{const a=await n.promises.stat(t.logPath)
307
+ o=a.size,i=a.mtime.toISOString(),r=await n.promises.open(t.logPath,"r")
308
+ const c=Math.max(0,o-e),l=Buffer.alloc(o-c)
309
+ await r.read(l,0,l.length,c),s=l.toString("utf-8").split("\n").slice(-50).join("\n")}catch{}finally{await(r?.close().catch((()=>{})))}return{...t,tail:s,sizeBytes:o,modifiedAt:i}})))
310
+ t.json({success:!0,data:r})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/commands/start",(async(e,t)=>{try{const{id:r,name:s,command:o,cwd:n,sessionId:a,startedAt:c,logPath:l}=e.body??{}
311
+ if(!(r&&s&&o&&n&&c&&l))return void t.status(400).json({success:!1,error:"missing required fields"});(0,i.addStart)({id:r,name:s,command:o,cwd:n,sessionId:a,startedAt:c,logPath:l}),t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/commands/end",(async(e,t)=>{try{const{id:r,exitCode:s,timedOut:o,finishedAt:n}=e.body??{}
312
+ if(!r||"number"!=typeof s||!n)return void t.status(400).json({success:!1,error:"missing required fields"});(0,i.markEnd)({id:r,exitCode:s,timedOut:!!o,finishedAt:n}),t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.get("/api/auth-status",this.handleAuthStatus.bind(this)),e.get("/status",this.handleStatus.bind(this)),e.get("/opencode",this.handleOpenCodeConfig.bind(this)),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.post("/api/generate-journal",this.handleGenerateJournal.bind(this)),(0,O.registerHooksRoutes)(e,{apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId}),e.get("/api/project-settings",((e,t)=>{t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.post("/api/sync-settings",(async(e,t)=>{await this.syncProjectSettings(),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.get("/api/agents/by-name/:agentName",this.handleGetAgentByName.bind(this)),e.get("/api/sessions/:sessionId/agent-type",this.handleGetSessionAgentType.bind(this)),e.get("/api/sessions/:sessionId/parent",this.handleGetSessionParent.bind(this)),e.get("/api/sessions/:sessionId/ancestors",(async(e,t)=>{try{const{sessionId:r}=e.params,s=this.projectId
306
313
  if(!s)return void t.status(400).json({success:!1,error:"No project"})
307
314
  const o=await this.apiClient.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),n=(o?.data||o||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title})))
308
- t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.get("/api/registry/projects",(async(e,r)=>{try{const{listLive:e}=await t.e(640).then(t.bind(t,2640))
309
- r.json({success:!0,data:e()})}catch(e){r.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.post("/api/mcp/tool-call-event",((e,t)=>{const{action:r,callId:s}=e.body||{}
315
+ t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.get("/api/registry/projects",(async(e,r)=>{try{const{listLive:e}=await Promise.resolve().then(t.bind(t,2640))
316
+ r.json({success:!0,data:e()})}catch(e){r.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/mcp/tool-call-event",((e,t)=>{const{action:r,callId:s}=e.body||{}
310
317
  !s||"start"!==r&&"end"!==r?t.status(400).json({success:!1,error:"action must be start|end, callId required"}):(this.handleMcpToolCallEvent(r,s+""),t.json({success:!0}))})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:r,sessionId:s,variables:o,timing:n}=e.body
311
318
  if(!r)return void t.status(400).json({success:!1,error:"eventType is required"})
312
319
  const i=await this.hookExecutor.processEvent({eventType:r,sessionId:s||"",variables:o||{},timing:n})
313
- t.json({success:!0,data:i})}catch(e){this.logger.error("Hook event processing failed",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.post("/api/tool-event",(async(e,t)=>{try{const{toolName:r,sessionId:s,input:o,output:n,timing:i}=e.body||{}
320
+ t.json({success:!0,data:i})}catch(e){this.logger.error("Hook event processing failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/tool-event",(async(e,t)=>{try{const{toolName:r,sessionId:s,input:o,output:n,timing:i}=e.body||{}
314
321
  if(!r||"before"!==i&&"after"!==i)return void t.status(400).json({success:!1,error:"toolName and timing ('before'|'after') are required"})
315
- const a=(0,f.parseToolEvents)({toolName:r,sessionId:s||"",input:o,output:n,timing:i})
322
+ const a=(0,w.parseToolEvents)({toolName:r,sessionId:s||"",input:o,output:n,timing:i})
316
323
  let c=0
317
324
  for(const e of a)c+=(await this.hookExecutor.processEvent({eventType:e.eventType,sessionId:s||"",variables:e.variables,timing:e.timing})).triggered
318
- this.logger.info("tool-event received",{toolName:r,timing:i,sessionId:s||"",events:a.length,triggered:c}),t.json({success:!0,data:{events:a.length,triggered:c}})}catch(e){this.logger.error("tool-event processing failed",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.post("/api/spawn-session",this.handleSpawnSession.bind(this)),e.post("/api/setup-tests",this.handleSetupTests.bind(this)),e.post("/api/setup-commands",this.handleSetupCommands.bind(this)),e.post("/api/run-tests-now",this.handleRunTestsNow.bind(this)),e.get("/api/branch-head",(async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0,s=("string"==typeof e.query.branch?e.query.branch:void 0)||(r?(0,_.generateRequestBranchName)(r,""):void 0)
325
+ this.logger.info("tool-event received",{toolName:r,timing:i,sessionId:s||"",events:a.length,triggered:c}),t.json({success:!0,data:{events:a.length,triggered:c}})}catch(e){this.logger.error("tool-event processing failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.post("/api/spawn-session",this.handleSpawnSession.bind(this)),e.post("/api/setup-tests",this.handleSetupTests.bind(this)),e.post("/api/setup-commands",this.handleSetupCommands.bind(this)),e.post("/api/run-tests-now",this.handleRunTestsNow.bind(this)),e.get("/api/branch-head",(async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0,s=("string"==typeof e.query.branch?e.query.branch:void 0)||(r?(0,P.generateRequestBranchName)(r,""):void 0)
319
326
  if(!s)return void t.status(400).json({success:!1,error:"requestId or branch required"})
320
- const o=(await(0,T.simpleGit)(this.options.projectPath).revparse([s])).trim()
321
- t.json({success:!0,data:{branch:s,sha:o}})}catch(e){this.logger.debug("branch-head lookup failed",{error:(0,g.getErrorMessage)(e)}),t.json({success:!0,data:{branch:null,sha:null}})}})),e.post("/api/release-pool-slot",(async(e,t)=>{try{const{worktreePath:r}=e.body||{}
322
- if(!r||"string"!=typeof r)return void t.status(400).json({success:!1,error:"worktreePath required"});(0,k.releaseSlot)(r),this.logger.info("Released pool slot via report-back",{worktreePath:r}),t.json({success:!0})}catch(e){this.logger.warn("release-pool-slot failed (non-fatal)",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.get("/api/timer-hooks/status",((e,t)=>{const r=this.timerHookScheduler?.getStatus()??{running:!1,trackedHooks:0,inFlight:[],hooks:[]}
327
+ const o=(await(0,A.simpleGit)(this.options.projectPath).revparse([s])).trim()
328
+ t.json({success:!0,data:{branch:s,sha:o}})}catch(e){this.logger.debug("branch-head lookup failed",{error:(0,f.getErrorMessage)(e)}),t.json({success:!0,data:{branch:null,sha:null}})}})),e.post("/api/release-pool-slot",(async(e,t)=>{try{const{worktreePath:r}=e.body||{}
329
+ if(!r||"string"!=typeof r)return void t.status(400).json({success:!1,error:"worktreePath required"});(0,j.releaseSlot)(r),this.logger.info("Released pool slot via report-back",{worktreePath:r}),t.json({success:!0})}catch(e){this.logger.warn("release-pool-slot failed (non-fatal)",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.get("/api/timer-hooks/status",((e,t)=>{const r=this.timerHookScheduler?.getStatus()??{running:!1,trackedHooks:0,inFlight:[],hooks:[]}
323
330
  t.json({success:!0,data:r})})),e.post("/api/timer-hooks/:hookId/pause",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
324
331
  const r=this.timerHookScheduler.pauseHook(e.params.hookId)
325
332
  t.json({success:r})})),e.post("/api/timer-hooks/:hookId/resume",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
@@ -327,27 +334,27 @@ const r=this.timerHookScheduler.resumeHook(e.params.hookId)
327
334
  t.json({success:r})})),e.post("/api/timer-hooks/:hookId/stop",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
328
335
  const r=this.timerHookScheduler.stopHook(e.params.hookId)
329
336
  t.json({success:r})})),e.get("/api/stuck-sessions",this.handleGetStuckSessions.bind(this)),e.post("/api/stuck-sessions/test-inject",this.handleTestInjectStuckSession.bind(this)),e.post("/api/stuck-sessions/:sessionId/retry",this.handleRetryStuckSession.bind(this)),e.get("/api/version-warnings",((e,t)=>{t.json({success:!0,data:this.versionWarnings})})),e.post("/api/self-update",(async(e,r)=>{try{const{execSync:e}=await Promise.resolve().then(t.t.bind(t,5317,23)),s=e("npm root -g",{encoding:"utf8"}).trim()
330
- try{c.default.accessSync(s,c.default.constants.W_OK)}catch{return void r.json({success:!1,error:"Permission denied. Run manually: sudo npm install -g alchemylab@latest"})}const{exec:o}=await Promise.resolve().then(t.t.bind(t,5317,23))
331
- r.json({success:!0,message:"Update started. The CLI will restart when complete."}),o("npm install -g alchemylab@latest",{timeout:12e4},((e,t,r)=>{e?this.logger.error("Self-update failed",{error:e.message,stderr:r}):(this.logger.info("Self-update complete, shutting down for restart",{stdout:t.trim()}),setTimeout((()=>{console.log("\n\x1b[32mUpdate complete. Please restart alchemylab.\x1b[0m\n"),process.exit(0)}),1e3))}))}catch(e){r.json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.use("/api/opencode",((e,t)=>this.handleOpenCodeProxy(e,t))),e}handleHealth(e,t){t.json({status:"healthy",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),timestamp:(new Date).toISOString()})}async handleAuthStatus(e,t){const r=this.options.tokenManager
337
+ try{u.default.accessSync(s,u.default.constants.W_OK)}catch{return void r.json({success:!1,error:"Permission denied. Run manually: sudo npm install -g alchemylab@latest"})}const{exec:o}=await Promise.resolve().then(t.t.bind(t,5317,23))
338
+ r.json({success:!0,message:"Update started. The CLI will restart when complete."}),o("npm install -g alchemylab@latest",{timeout:12e4},((e,t,r)=>{e?this.logger.error("Self-update failed",{error:e.message,stderr:r}):(this.logger.info("Self-update complete, shutting down for restart",{stdout:t.trim()}),setTimeout((()=>{console.log("\n\x1b[32mUpdate complete. Please restart alchemylab.\x1b[0m\n"),process.exit(0)}),1e3))}))}catch(e){r.json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.use("/api/opencode",((e,t)=>this.handleOpenCodeProxy(e,t))),e}handleHealth(e,t){t.json({status:"healthy",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),timestamp:(new Date).toISOString()})}async handleAuthStatus(e,t){const r=this.options.tokenManager
332
339
  if(r)try{const e=await r.hasValidToken(),s=r.getAuthStatus(),o=e?await r.getToken():null
333
- t.json({authenticated:e,isPendingAuth:s.isPendingAuth,authUrl:e?void 0:s.authUrl,expiresAt:o?.expiresAt??null})}catch(e){this.logger.warn("auth-status check failed",{error:(0,g.getErrorMessage)(e)}),t.json({authenticated:!1,isPendingAuth:!1})}else t.json({authenticated:!0,isPendingAuth:!1})}handleStatus(e,t){t.json({status:"running",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),startTime:this.startTime.toISOString(),projectId:this.projectId,serverId:this.serverId,isPolling:this.isPolling,port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const r=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
334
- try{if(!c.default.existsSync(r))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
335
- const e=c.default.readFileSync(r,"utf-8"),s=JSON.parse(e)
336
- t.json({port:s.port,pid:s.pid,startedAt:s.startedAt,baseUrl:"http://localhost:"+s.port,directory:this.options.projectPath,proxyEnabled:!0})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,g.getErrorMessage)(e)})}}updateDefaultModel(e){void 0!==e&&e!==this.cachedDefaultModel&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e||""),e&&this.projectId&&this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Background agent markdown re-sync failed",{error:(0,g.getErrorMessage)(e)}))))}handleMcpToolCallEvent(e,t){"start"!==e?(this.activeMcpCalls.delete(t),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.logger.info("Firing deferred agent resync now that MCP is idle"),this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync failed",{error:(0,g.getErrorMessage)(e)}))))):this.activeMcpCalls.set(t,Date.now())}startMcpStaleCleanup(){this.mcpStaleCleanupInterval||(this.mcpStaleCleanupInterval=setInterval((()=>{const e=Date.now()-12e4
340
+ t.json({authenticated:e,isPendingAuth:s.isPendingAuth,authUrl:e?void 0:s.authUrl,expiresAt:o?.expiresAt??null})}catch(e){this.logger.warn("auth-status check failed",{error:(0,f.getErrorMessage)(e)}),t.json({authenticated:!1,isPendingAuth:!1})}else t.json({authenticated:!0,isPendingAuth:!1})}handleStatus(e,t){t.json({status:"running",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),startTime:this.startTime.toISOString(),projectId:this.projectId,serverId:this.serverId,isPolling:this.isPolling,port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const r=d.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
341
+ try{if(!u.default.existsSync(r))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
342
+ const e=u.default.readFileSync(r,"utf-8"),s=JSON.parse(e)
343
+ t.json({port:s.port,pid:s.pid,startedAt:s.startedAt,baseUrl:"http://localhost:"+s.port,directory:this.options.projectPath,proxyEnabled:!0})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,f.getErrorMessage)(e)})}}updateDefaultModel(e){void 0!==e&&e!==this.cachedDefaultModel&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e||""),e&&this.projectId&&this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Background agent markdown re-sync failed",{error:(0,f.getErrorMessage)(e)}))))}handleMcpToolCallEvent(e,t){"start"!==e?(this.activeMcpCalls.delete(t),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.logger.info("Firing deferred agent resync now that MCP is idle"),this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync failed",{error:(0,f.getErrorMessage)(e)}))))):this.activeMcpCalls.set(t,Date.now())}startMcpStaleCleanup(){this.mcpStaleCleanupInterval||(this.mcpStaleCleanupInterval=setInterval((()=>{const e=Date.now()-12e4
337
344
  let t=0
338
345
  for(const[r,s]of this.activeMcpCalls)e>s&&(this.activeMcpCalls.delete(r),t++)
339
- t>0&&(this.logger.warn("Cleaned up stale MCP tool-call tracking entries",{removed:t}),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync after cleanup failed",{error:(0,g.getErrorMessage)(e)})))))}),6e4))}async resyncAgentMarkdowns(){if(!this.projectId)return
346
+ t>0&&(this.logger.warn("Cleaned up stale MCP tool-call tracking entries",{removed:t}),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync after cleanup failed",{error:(0,f.getErrorMessage)(e)})))))}),6e4))}async resyncAgentMarkdowns(){if(!this.projectId)return
340
347
  if(this.activeMcpCalls.size>0)return this.pendingAgentResync=!0,void this.logger.info("Deferring agent resync until MCP tool calls complete",{inFlight:this.activeMcpCalls.size})
341
348
  const e=await this.configurer.fetchAgentsFromRemote(this.projectId)
342
349
  if(!e?.length)return
343
- const t=await this.configurer.fetchProjectFromRemote(this.projectId),r=t?.metadata?.globalSystemPrompt,s=a.default.join(this.options.projectPath,".opencode","agent")
344
- await c.default.promises.mkdir(s,{recursive:!0})
350
+ const t=await this.configurer.fetchProjectFromRemote(this.projectId),r=t?.metadata?.globalSystemPrompt,s=d.default.join(this.options.projectPath,".opencode","agent")
351
+ await u.default.promises.mkdir(s,{recursive:!0})
345
352
  const o=this.cachedDefaultModel||"anthropic/claude-opus-4-5"
346
353
  for(const t of e){if(!t.enabled)continue
347
354
  const e=this.configurer.generateAgentMarkdown(t,"alchemy",o,r)
348
- await c.default.promises.writeFile(a.default.join(s,t.name+".md"),e,"utf8")}this.logger.info("Re-synced agent markdowns after model change",{model:o,count:e.length})
355
+ await u.default.promises.writeFile(d.default.join(s,t.name+".md"),e,"utf8")}this.logger.info("Re-synced agent markdowns after model change",{model:o,count:e.length})
349
356
  try{const e=this.getOpenCodeUrl()
350
- e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&this.logger.info("Triggered OpenCode config reload")}catch(e){this.logger.debug("Failed to trigger OpenCode config reload",{error:(0,g.getErrorMessage)(e)})}}async handleSyncAgents(e,t){try{const e=this.projectId
357
+ e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&this.logger.info("Triggered OpenCode config reload")}catch(e){this.logger.debug("Failed to trigger OpenCode config reload",{error:(0,f.getErrorMessage)(e)})}}async handleSyncAgents(e,t){try{const e=this.projectId
351
358
  if(!e)return void t.status(400).json({success:!1,error:"Project not registered"})
352
359
  this.logger.info("Syncing agents from remote",{projectId:e})
353
360
  const r=await this.configurer.fetchAgentsFromRemote(e)
@@ -356,22 +363,22 @@ const s=await this.configurer.fetchProjectFromRemote(e)
356
363
  this.logger.debug("Fetched project for global prompt",{projectId:e,hasProject:!!s,hasGlobalSystemPrompt:!!s?.metadata?.globalSystemPrompt})
357
364
  const o=s?.metadata?.globalSystemPrompt
358
365
  o?this.logger.info("Using global system prompt from project metadata",{projectId:e,promptLength:o.length}):this.logger.debug("No global system prompt found in project metadata")
359
- const n=a.default.join(this.options.projectPath,".opencode","agent")
360
- await c.default.promises.mkdir(n,{recursive:!0})
361
- const i="alchemy",l=this.cachedDefaultModel||"anthropic/claude-opus-4-5",d=r.filter((e=>e.enabled)),u=await Promise.allSettled(d.map((e=>{const t=this.configurer.generateAgentMarkdown(e,i,l,o),r=a.default.join(n,e.name+".md")
362
- return c.default.promises.writeFile(r,t,"utf8").then((()=>{this.logger.info("Synced agent file",{name:e.name,path:r,mode:e.mode})}))}))),h=u.filter((e=>"fulfilled"===e.status)).length,p=u.filter((e=>"rejected"===e.status)).length
363
- p>0&&this.logger.warn("Some agent file writes failed",{writeFails:p})
364
- try{const e=a.default.join(this.options.projectPath,"opencode.json")
365
- if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),o=JSON.parse(t),n=s?.metadata?.bashToolAgents||{}
366
+ const n=d.default.join(this.options.projectPath,".opencode","agent")
367
+ await u.default.promises.mkdir(n,{recursive:!0})
368
+ const i="alchemy",a=this.cachedDefaultModel||"anthropic/claude-opus-4-5",c=r.filter((e=>e.enabled)),l=await Promise.allSettled(c.map((e=>{const t=this.configurer.generateAgentMarkdown(e,i,a,o),r=d.default.join(n,e.name+".md")
369
+ return u.default.promises.writeFile(r,t,"utf8").then((()=>{this.logger.info("Synced agent file",{name:e.name,path:r,mode:e.mode})}))}))),h=l.filter((e=>"fulfilled"===e.status)).length,g=l.filter((e=>"rejected"===e.status)).length
370
+ g>0&&this.logger.warn("Some agent file writes failed",{writeFails:g})
371
+ try{const e=d.default.join(this.options.projectPath,"opencode.json")
372
+ if(u.default.existsSync(e)){const t=u.default.readFileSync(e,"utf-8"),o=JSON.parse(t),n=s?.metadata?.bashToolAgents||{}
366
373
  if(o.agent){for(const e of r)e.enabled&&o.agent[e.name]&&e.name in n&&(o.agent[e.name].tools.bash=!!n[e.name])
367
- c.default.writeFileSync(e,JSON.stringify(o,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let m=!1
374
+ u.default.writeFileSync(e,JSON.stringify(o,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,f.getErrorMessage)(e)})}let p=!1
368
375
  try{const e=this.getOpenCodeUrl()
369
- e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&(this.logger.info("Triggered OpenCode config reload after agent sync"),m=!0)}catch(e){this.logger.warn("Could not reload OpenCode config",{error:(0,g.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${h} agent(s) to local files${m?" - config reloaded":""}`,count:h,reloaded:m})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,r){try{const{requestId:s}=e.body
376
+ e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&(this.logger.info("Triggered OpenCode config reload after agent sync"),p=!0)}catch(e){this.logger.warn("Could not reload OpenCode config",{error:(0,f.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${h} agent(s) to local files${p?" - config reloaded":""}`,count:h,reloaded:p})}catch(e){this.logger.error("Failed to sync agents",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleGenerateJournal(e,r){try{const{requestId:s}=e.body
370
377
  if(!s)return void r.status(400).json({success:!1,error:"requestId is required"})
371
378
  if(!/^req\d+$/.test(s))return void r.status(400).json({success:!1,error:"Invalid requestId format"})
372
379
  this.logger.info("Generating journal for request",{requestId:s})
373
- const o=`Generate a journal entry for request ${s}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${s}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${s}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,j.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath,env:{...process.env,...(0,y.getOpencodeSpawnEnv)(this.options.projectPath,(0,v.isDevMode)())}})
374
- a.pid&&this.trackChildPid(a.pid),a.unref(),r.json({success:!0,message:"Journal generation started for "+s})}catch(e){this.logger.error("Failed to generate journal",{error:(0,g.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleDirectReport(e,t){try{const r=this.projectId
380
+ const o=`Generate a journal entry for request ${s}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${s}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${s}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,M.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath,env:{...process.env,...(0,b.getOpencodeSpawnEnv)(this.options.projectPath,(0,I.isDevMode)())}})
381
+ a.pid&&this.trackChildPid(a.pid),a.unref(),r.json({success:!0,message:"Journal generation started for "+s})}catch(e){this.logger.error("Failed to generate journal",{error:(0,f.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleDirectReport(e,t){try{const r=this.projectId
375
382
  if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
376
383
  const{sessionId:s,requestId:o,summary:n,status:i}=e.body
377
384
  if(!s)return void t.status(400).json({success:!1,error:"sessionId is required"})
@@ -379,26 +386,26 @@ this.logger.info("Direct report creation",{sessionId:s,requestId:o,status:i})
379
386
  const a=[]
380
387
  try{const e=await this.apiClient.get(`/api/projects/${r}/cli-sessions/external/opencode/${encodeURIComponent(s)}/ancestors`),t=e?.data||e||[]
381
388
  let o=s
382
- for(const e of t)a.push({ancestorId:e.externalSessionId,childId:o}),o=e.externalSessionId}catch(e){this.logger.warn("Failed to fetch ancestor chain",{sessionId:s,error:(0,g.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
389
+ for(const e of t)a.push({ancestorId:e.externalSessionId,childId:o}),o=e.externalSessionId}catch(e){this.logger.warn("Failed to fetch ancestor chain",{sessionId:s,error:(0,f.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
383
390
  let c,l=n
384
391
  if(!l&&o)try{const e=await this.apiClient.get(`/api/projects/${r}/requests/${o}`),t=e?.data||e
385
392
  c=t?.title
386
393
  const s=t?.completionSummary
387
- s?l=s:c&&(l="Done: "+c)}catch(e){this.logger.debug("Could not fetch request details for summary",{requestId:o,error:(0,g.getErrorMessage)(e)})}l||(l="Completed request "+(o||"unknown"))
394
+ s?l=s:c&&(l="Done: "+c)}catch(e){this.logger.debug("Could not fetch request details for summary",{requestId:o,error:(0,f.getErrorMessage)(e)})}l||(l="Completed request "+(o||"unknown"))
388
395
  const d=o||s
389
396
  if(this.directReportDedup.size>=1e3){const e=this.directReportDedup.keys().next().value
390
397
  void 0!==e&&this.directReportDedup.delete(e)}this.directReportDedup.has(d)||this.directReportDedup.set(d,new Set)
391
- const u=this.directReportDedup.get(d),h=a.filter((({ancestorId:e})=>!u.has(e)||(this.logger.info("Direct report dedup: already notified",{ancestorId:e,dedupKey:d}),!1))),p=await Promise.allSettled(h.map((async({ancestorId:e,childId:t})=>{const s=await this.apiClient.post(`/api/projects/${r}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),o=s?.data||s
392
- return u.add(e),this.logger.info("Direct report created for ancestor",{messageCode:o?.messageCode,ancestorId:e,fromChild:t}),{externalSessionId:e,messageCode:o?.messageCode,fromSessionId:t,content:o?.content}}))),m=[]
393
- for(const e of p)"fulfilled"===e.status&&m.push(e.value)
394
- for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,g.getErrorMessage)(e.reason)})
398
+ const u=this.directReportDedup.get(d),h=a.filter((({ancestorId:e})=>!u.has(e)||(this.logger.info("Direct report dedup: already notified",{ancestorId:e,dedupKey:d}),!1))),g=await Promise.allSettled(h.map((async({ancestorId:e,childId:t})=>{const s=await this.apiClient.post(`/api/projects/${r}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),o=s?.data||s
399
+ return u.add(e),this.logger.info("Direct report created for ancestor",{messageCode:o?.messageCode,ancestorId:e,fromChild:t}),{externalSessionId:e,messageCode:o?.messageCode,fromSessionId:t,content:o?.content}}))),p=[]
400
+ for(const e of g)"fulfilled"===e.status&&p.push(e.value)
401
+ for(const e of g)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,f.getErrorMessage)(e.reason)})
395
402
  if(setTimeout((()=>{this.directReportDedup.delete(d)}),3e5),o)try{const e=await this.apiClient.get(`/api/projects/${r}/session-request-associations/by-session/${encodeURIComponent(s)}?activeOnly=true`),t=e?.data||e||[],o=(await Promise.allSettled(t.map((e=>this.apiClient.post(`/api/projects/${r}/session-request-associations/end`,{sessionId:s,requestLocalId:e.requestLocalId}))))).filter((e=>"rejected"===e.status)).length
396
- this.logger.info("Released request associations",{sessionId:s,count:t.length,failCount:o})}catch(e){this.logger.warn("Failed to release associations",{sessionId:s,error:(0,g.getErrorMessage)(e)})}t.json({success:!0,data:{notifiedSessions:m,report:m[0]?{messageCode:m[0].messageCode}:void 0,parentExternalSessionId:a[0]?.ancestorId||null}})}catch(e){this.logger.error("Direct report creation failed",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGetSessionAgentType(e,t){try{const{sessionId:r}=e.params,s=this.projectId
403
+ this.logger.info("Released request associations",{sessionId:s,count:t.length,failCount:o})}catch(e){this.logger.warn("Failed to release associations",{sessionId:s,error:(0,f.getErrorMessage)(e)})}t.json({success:!0,data:{notifiedSessions:p,report:p[0]?{messageCode:p[0].messageCode}:void 0,parentExternalSessionId:a[0]?.ancestorId||null}})}catch(e){this.logger.error("Direct report creation failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleGetSessionAgentType(e,t){try{const{sessionId:r}=e.params,s=this.projectId
397
404
  if(s)try{const e=await this.apiClient.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(r)}`),o=e?.data||e
398
- if(this.logger.info("Agent type DB lookup",{sessionId:r,dbAgentType:o?.agentType||null,dbId:o?.id||o?.localId||null}),o?.agentType)return void t.json({success:!0,agentType:o.agentType,source:"db"})}catch(e){this.logger.info("Agent type DB lookup failed",{sessionId:r,error:(0,g.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:r},5e3)
399
- if(this.logger.info("Agent type bridge lookup",{sessionId:r,bridgeAgentType:e?.data?.agentType||null,bridgeSuccess:e?.success}),e.success&&e.data?.agentType)return void t.json({success:!0,agentType:e.data.agentType,source:"bridge"})}catch(e){this.logger.info("Agent type bridge lookup failed",{sessionId:r,error:(0,g.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:r}),t.json({success:!0,agentType:null,source:"unknown"})}catch(e){this.logger.debug("Failed to get session agent type",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGetSessionParent(e,t){try{const{sessionId:r}=e.params,s=this.projectId
405
+ if(this.logger.info("Agent type DB lookup",{sessionId:r,dbAgentType:o?.agentType||null,dbId:o?.id||o?.localId||null}),o?.agentType)return void t.json({success:!0,agentType:o.agentType,source:"db"})}catch(e){this.logger.info("Agent type DB lookup failed",{sessionId:r,error:(0,f.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:r},5e3)
406
+ if(this.logger.info("Agent type bridge lookup",{sessionId:r,bridgeAgentType:e?.data?.agentType||null,bridgeSuccess:e?.success}),e.success&&e.data?.agentType)return void t.json({success:!0,agentType:e.data.agentType,source:"bridge"})}catch(e){this.logger.info("Agent type bridge lookup failed",{sessionId:r,error:(0,f.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:r}),t.json({success:!0,agentType:null,source:"unknown"})}catch(e){this.logger.debug("Failed to get session agent type",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleGetSessionParent(e,t){try{const{sessionId:r}=e.params,s=this.projectId
400
407
  if(s)try{const e=await this.apiClient.get(`/api/projects/${s}/cli-sessions/resolve/${encodeURIComponent(r)}`),o=e?.data?.parent?.externalSessionId||""
401
- return void t.json({success:!0,parentSessionId:o})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:r,error:(0,g.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{const r=this.getOpenCodeUrl()
408
+ return void t.json({success:!0,parentSessionId:o})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:r,error:(0,f.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{const r=this.getOpenCodeUrl()
402
409
  if(!r)return void t.status(502).json({error:"OpenCode not running"})
403
410
  const s=e.path.replace(/^\//,""),o=e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):"",n=`${r}/${s}${o}`,i=e.headers.accept?.includes("text/event-stream")
404
411
  if(i){const r=new AbortController,s=()=>{this.proxyAbortControllers.delete(r)},o=(async()=>{try{const e=await fetch(n,{headers:{Accept:"text/event-stream"},signal:r.signal})
@@ -418,49 +425,49 @@ if(!t||t===r)throw e
418
425
  d=`${t}/${s}${o}`,this.logger.info("OpenCode proxy: retrying with fresh port",{oldUrl:r,newUrl:t}),l=await fetch(d,c)}if(204===l.status||"0"===l.headers.get("content-length"))return void t.status(l.status).end()
419
426
  const u=l.headers.get("content-type")||""
420
427
  let h
421
- try{h=u.includes("application/json")?await l.json():await l.text()}catch{return void t.status(l.status).end()}t.status(l.status),u&&t.setHeader("Content-Type",u),"string"==typeof h?t.send(h):t.json(h)}catch(r){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,g.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,g.getErrorMessage)(r)})}}async handleSpawnSession(e,t){try{const{agent:r,prompt:s,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d}=e.body
428
+ try{h=u.includes("application/json")?await l.json():await l.text()}catch{return void t.status(l.status).end()}t.status(l.status),u&&t.setHeader("Content-Type",u),"string"==typeof h?t.send(h):t.json(h)}catch(r){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,f.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,f.getErrorMessage)(r)})}}async handleSpawnSession(e,t){try{const{agent:r,prompt:s,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d}=e.body
422
429
  if(!r||!s)return void t.status(400).json({success:!1,error:"agent and prompt are required"})
423
430
  if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
424
- const u=this.projectId,h=new E.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:u,getDefaultModel:()=>this.cachedDefaultModel||void 0}),g=await h.prepareSession({agent:r,prompt:s,projectPath:this.options.projectPath,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d})
425
- t.json({success:!0,data:g})}catch(e){this.logger.error("Spawn session failed",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleRunTestsNow(e,r){try{const{requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i}=e.body||{}
431
+ const u=this.projectId,h=new k.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:u,getDefaultModel:()=>this.cachedDefaultModel||void 0}),g=await h.prepareSession({agent:r,prompt:s,projectPath:this.options.projectPath,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d})
432
+ t.json({success:!0,data:g})}catch(e){this.logger.error("Spawn session failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleRunTestsNow(e,r){try{const{requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i}=e.body||{}
426
433
  if(!this.projectId)return void r.status(400).json({success:!1,error:"No project configured"})
427
434
  if(!s)return void r.status(400).json({success:!1,error:"requestId is required"})
428
435
  const{handleRunTestsAction:a}=await Promise.resolve().then(t.bind(t,6555)),c={projectId:this.projectId,projectPath:this.options.projectPath,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackedChildPids.add(e),cachedDefaultModel:this.cachedDefaultModel??null},l=await a({requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i,mode:"auto"},c)
429
436
  if(!l.success)return void r.status(500).json({success:!1,error:l.error||l.message})
430
- r.json({success:!0,data:l.data})}catch(e){this.logger.error("Run tests now failed",{error:(0,g.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleSetupTests(e,t){try{const{requestId:r,parentSessionId:s,branchName:o}=e.body||{}
437
+ r.json({success:!0,data:l.data})}catch(e){this.logger.error("Run tests now failed",{error:(0,f.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleSetupTests(e,t){try{const{requestId:r,parentSessionId:s,branchName:o}=e.body||{}
431
438
  if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
432
439
  const n=this.projectId,i=this.options.projectPath
433
- await(0,S.sweepStaleSetupWorktrees)(i,this.logger)
434
- const c=await(0,S.resolveSetupBranch)(this.apiClient,n,o,r,this.logger,i),l=(0,S.generateSetupId)(),d=a.default.join((0,S.getSetupWorktreesBase)(i),l)
435
- try{await(0,S.createSetupWorktree)(i,d,c,this.logger)}catch(e){const r=(0,g.getErrorMessage)(e)
436
- return this.logger.error("Failed to create setup worktree",{branch:c,error:r}),void t.status(500).json({success:!1,error:"Failed to create setup worktree: "+r})}const u=(0,S.buildSetupPrompt)(i,r),h=await(0,C.spawnActionAgent)({agent:"test-architect",prompt:u,cwd:d,projectPath:i,projectId:n,title:r?`[${r}] Test setup`:"Test setup",requestId:r,parentSessionId:s,allowParallel:!0,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
440
+ await(0,_.sweepStaleSetupWorktrees)(i,this.logger)
441
+ const a=await(0,_.resolveSetupBranch)(this.apiClient,n,o,r,this.logger,i),c=(0,_.generateSetupId)(),l=d.default.join((0,_.getSetupWorktreesBase)(i),c)
442
+ try{await(0,_.createSetupWorktree)(i,l,a,this.logger)}catch(e){const r=(0,f.getErrorMessage)(e)
443
+ return this.logger.error("Failed to create setup worktree",{branch:a,error:r}),void t.status(500).json({success:!1,error:"Failed to create setup worktree: "+r})}const u=(0,_.buildSetupPrompt)(i,r),h=await(0,T.spawnActionAgent)({agent:"test-architect",prompt:u,cwd:l,projectPath:i,projectId:n,title:r?`[${r}] Test setup`:"Test setup",requestId:r,parentSessionId:s,allowParallel:!0,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
437
444
  if(!h.success)return void t.status(500).json({success:!1,error:h.error||"Spawn failed"})
438
- t.json({success:!0,data:{sessionId:h.sessionId,worktreePath:d,branch:c}})}catch(e){this.logger.error("Setup tests failed",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleSetupCommands(e,t){try{const{requestId:r,parentSessionId:s,branchName:o,context:n}=e.body||{}
445
+ t.json({success:!0,data:{sessionId:h.sessionId,worktreePath:l,branch:a}})}catch(e){this.logger.error("Setup tests failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleSetupCommands(e,t){try{const{requestId:r,parentSessionId:s,branchName:o,context:n}=e.body||{}
439
446
  if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
440
- const i=this.projectId,c=this.options.projectPath
441
- await(0,S.sweepStaleSetupWorktrees)(c,this.logger)
442
- const l=await(0,S.resolveSetupBranch)(this.apiClient,i,o,r,this.logger,c),d=(0,S.generateSetupId)(),u=a.default.join((0,S.getSetupWorktreesBase)(c),d)
443
- try{await(0,S.createSetupWorktree)(c,u,l,this.logger)}catch(e){const r=(0,g.getErrorMessage)(e)
444
- return this.logger.error("Failed to create setup worktree (commands)",{branch:l,error:r}),void t.status(500).json({success:!1,error:"Failed to create setup worktree: "+r})}const h=(0,S.buildCommandSetupPrompt)(c,r,n),p=await(0,C.spawnActionAgent)({agent:"command-architect",prompt:h,cwd:u,projectPath:c,projectId:i,title:r?`[${r}] Commands setup`:"Commands setup",requestId:r,parentSessionId:s,allowParallel:!0,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
445
- if(!p.success)return void t.status(500).json({success:!1,error:p.error||"Spawn failed"})
446
- t.json({success:!0,data:{sessionId:p.sessionId,worktreePath:u,branch:l}})}catch(e){this.logger.error("Setup commands failed",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGetAgentByName(e,t){try{const r=this.projectId
447
+ const i=this.projectId,a=this.options.projectPath
448
+ await(0,_.sweepStaleSetupWorktrees)(a,this.logger)
449
+ const c=await(0,_.resolveSetupBranch)(this.apiClient,i,o,r,this.logger,a),l=(0,_.generateSetupId)(),u=d.default.join((0,_.getSetupWorktreesBase)(a),l)
450
+ try{await(0,_.createSetupWorktree)(a,u,c,this.logger)}catch(e){const r=(0,f.getErrorMessage)(e)
451
+ return this.logger.error("Failed to create setup worktree (commands)",{branch:c,error:r}),void t.status(500).json({success:!1,error:"Failed to create setup worktree: "+r})}const h=(0,_.buildCommandSetupPrompt)(a,r,n),g=await(0,T.spawnActionAgent)({agent:"command-architect",prompt:h,cwd:u,projectPath:a,projectId:i,title:r?`[${r}] Commands setup`:"Commands setup",requestId:r,parentSessionId:s,allowParallel:!0,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
452
+ if(!g.success)return void t.status(500).json({success:!1,error:g.error||"Spawn failed"})
453
+ t.json({success:!0,data:{sessionId:g.sessionId,worktreePath:u,branch:c}})}catch(e){this.logger.error("Setup commands failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleGetAgentByName(e,t){try{const r=this.projectId
447
454
  if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
448
455
  const{agentName:s}=e.params,o=await this.apiClient.get(`/api/projects/${r}/agents`),n=(o?.data||o||[]).find((e=>e.name===s))
449
456
  if(!n)return void t.status(404).json({success:!1,error:`Agent '${s}' not found`})
450
- t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<M.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
451
- const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
452
- try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
453
- const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
454
- return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,g.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e
457
+ t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<D.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
458
+ const e=d.default.join(this.options.projectPath,".alchemy_lab","project.json")
459
+ try{if(!u.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
460
+ const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
461
+ return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,f.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e
455
462
  const t="http://127.0.0.1:"+e
456
- this.openCodeUrlCache={url:t,timestamp:Date.now()},this.logger.info("OpenCode port set directly",{port:e}),this.cachedDefaultModel||this.seedDefaultModelFromZen(e).catch((e=>this.logger.debug("Failed to seed zen default model",{error:(0,g.getErrorMessage)(e)})))}async seedDefaultModelFromZen(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
463
+ this.openCodeUrlCache={url:t,timestamp:Date.now()},this.logger.info("OpenCode port set directly",{port:e}),this.cachedDefaultModel||this.seedDefaultModelFromZen(e).catch((e=>this.logger.debug("Failed to seed zen default model",{error:(0,f.getErrorMessage)(e)})))}async seedDefaultModelFromZen(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
457
464
  if(!t.ok)return
458
465
  const r=await t.json(),s=r?.default||{},[o,n]=Object.entries(s)[0]||[]
459
466
  if(!o||!n)return
460
467
  const i=`${o}/${n}`
461
- this.updateDefaultModel(i),this.logger.info("Seeded defaultModel from OpenCode zen default",{defaultModel:i}),this.apiClient.post("/api/user/preferences",{defaultModel:i}).catch((e=>this.logger.debug("Failed to seed user preference",{error:(0,g.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
468
+ this.updateDefaultModel(i),this.logger.info("Seeded defaultModel from OpenCode zen default",{defaultModel:i}),this.apiClient.post("/api/user/preferences",{defaultModel:i}).catch((e=>this.logger.debug("Failed to seed user preference",{error:(0,f.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
462
469
  if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
463
- const e=(0,P.getOpenCodeUrlFromConfig)(this.options.projectPath)
470
+ const e=(0,R.getOpenCodeUrlFromConfig)(this.options.projectPath)
464
471
  return e&&(this.openCodeUrlCache={url:e,timestamp:Date.now()}),e}async handleSessionCommand(e,t){const r=this.getOpenCodeUrl()
465
472
  if(!r)return{success:!1,error:"OpenCode server not available. Is 'opencode serve' running?"}
466
473
  try{switch(e){case"list-sessions":{const[e,s]=await Promise.all([fetch(r+"/session"),fetch(r+"/session/status")])
@@ -491,14 +498,14 @@ const[s,o]=await Promise.all([fetch(r+"/session/status"),fetch(r+"/session")]),n
491
498
  let i,a
492
499
  if(o.ok){const t=(await o.json()).find((t=>t.id===e))
493
500
  i=t?.title}if(this.projectId)try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),r=t?.data||t
494
- r?.agentType&&(a=r.agentType)}catch{}return{success:!0,data:{id:e,title:i||"(untitled)",status:n[e]?.type||"idle",agentType:a}}}default:return{success:!1,error:"Unknown session action: "+e}}}catch(e){return{success:!1,error:(0,g.getErrorMessage)(e)}}}async register(){const e=this.readProjectConfig()
501
+ r?.agentType&&(a=r.agentType)}catch{}return{success:!0,data:{id:e,title:i||"(untitled)",status:n[e]?.type||"idle",agentType:a}}}default:return{success:!1,error:"Unknown session action: "+e}}}catch(e){return{success:!1,error:(0,f.getErrorMessage)(e)}}}async register(){const e=this.readProjectConfig()
495
502
  if(!e?.id)throw Error("No project configuration found")
496
503
  this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId),this.timerHookScheduler?.setProjectId(this.projectId)
497
- try{const e=a.default.resolve(__dirname,"../../package.json"),t=JSON.parse(c.default.readFileSync(e,"utf8"))
504
+ try{const e=d.default.resolve(__dirname,"../../package.json"),t=JSON.parse(u.default.readFileSync(e,"utf8"))
498
505
  this.clientVersion=t.version}catch{}try{const e=t(45)
499
506
  this.opencodeVersion=e.version}catch{try{const e=process.env.ALCHEMY_OPENCODE_DIR
500
- if(e){const t=JSON.parse(c.default.readFileSync(a.default.join(e,"package.json"),"utf8"))
501
- this.opencodeVersion=t.version}}catch{}}const r={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:l.default.hostname(),platform:process.platform,clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}
507
+ if(e){const t=JSON.parse(u.default.readFileSync(d.default.join(e,"package.json"),"utf8"))
508
+ this.opencodeVersion=t.version}}catch{}}const r={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:h.default.hostname(),platform:process.platform,clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}
502
509
  this.logger.info("Registering with remote",{projectId:this.projectId})
503
510
  const s=await this.apiClient.post("/api/servers/register",r)
504
511
  if(s.success&&s.data?.serverId){this.serverId=s.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})
@@ -506,9 +513,8 @@ const e=s.data.versionWarnings
506
513
  if(e&&e.length>0){const t=e.some((e=>"required"===e.severity))
507
514
  for(const t of e)"required"===t.severity?(this.logger.error("[UPDATE REQUIRED] "+t.message),console.error("\n\x1b[31m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),console.error("\u2551 UPDATE REQUIRED \u2551"),console.error("\x1b[31m\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\x1b[0m"),console.error(`\x1b[31m${t.message}\x1b[0m\n`)):(this.logger.warn("[UPDATE RECOMMENDED] "+t.message),console.warn(`\n\x1b[33m[UPDATE RECOMMENDED] ${t.message}\x1b[0m\n`))
508
515
  this.versionWarnings=e,t&&(this._pendingUpdateBlock=!0)}}else this.logger.warn("Registration failed, continuing without remote connection",{error:s.error?.message||"Unknown error"})}async applyVersionWarnings(e){if(e.map((e=>`${e.component}:${e.severity}`)).join(",")!==this.versionWarnings.map((e=>`${e.component}:${e.severity}`)).join(",")){for(const t of e)"required"===t.severity?(this.logger.error("[UPDATE REQUIRED] "+t.message),console.error(`\n\x1b[31m[UPDATE REQUIRED] ${t.message}\x1b[0m\n`)):(this.logger.warn("[UPDATE RECOMMENDED] "+t.message),console.warn(`\n\x1b[33m[UPDATE RECOMMENDED] ${t.message}\x1b[0m\n`))
509
- this.versionWarnings=e,!e.some((e=>"required"===e.severity))&&!this._pendingUpdateBlock||this.updateBlocked||await this.enterUpdateMode()}}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{if(this.projectId)try{const{touchHeartbeat:e}=await t.e(640).then(t.bind(t,2640))
510
- e(this.projectId)}catch{}try{const e=await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString(),clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}),t=e?.data?.versionWarnings??[]
511
- await this.applyVersionWarnings(t)}catch(e){this.logger.warn("Heartbeat failed",{error:(0,g.getErrorMessage)(e)})}}),3e5))}startSSEEventForwarder(){this.logger.info("Starting SSE event forwarder"),this.connectToOpenCodeSSE(),this.sseForwarderInterval=setInterval((async()=>{await this.flushSSEEvents()}),2e3)}stopSSEEventForwarder(){this.sseForwarderInterval&&(clearInterval(this.sseForwarderInterval),this.sseForwarderInterval=void 0),this.sseAbortController&&(this.sseAbortController.abort(),this.sseAbortController=void 0),this.flushSSEEvents().catch((e=>this.logger.warn("Failed to flush remaining SSE events",{error:(0,g.getErrorMessage)(e)})))}async connectToOpenCodeSSE(){const e=this.getOpenCodeUrl()
516
+ this.versionWarnings=e,!e.some((e=>"required"===e.severity))&&!this._pendingUpdateBlock||this.updateBlocked||await this.enterUpdateMode()}}startHeartbeat(){this.serverId&&(setInterval((()=>{if(this.projectId)try{(0,a.touchHeartbeat)(this.projectId)}catch{}}),3e4),this.heartbeatInterval=setInterval((async()=>{try{const e=await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString(),clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}),t=e?.data?.versionWarnings??[]
517
+ await this.applyVersionWarnings(t)}catch(e){this.logger.warn("Heartbeat failed",{error:(0,f.getErrorMessage)(e)})}}),3e5))}startSSEEventForwarder(){this.logger.info("Starting SSE event forwarder"),this.connectToOpenCodeSSE(),this.sseForwarderInterval=setInterval((async()=>{await this.flushSSEEvents()}),2e3)}stopSSEEventForwarder(){this.sseForwarderInterval&&(clearInterval(this.sseForwarderInterval),this.sseForwarderInterval=void 0),this.sseAbortController&&(this.sseAbortController.abort(),this.sseAbortController=void 0),this.flushSSEEvents().catch((e=>this.logger.warn("Failed to flush remaining SSE events",{error:(0,f.getErrorMessage)(e)})))}async connectToOpenCodeSSE(){const e=this.getOpenCodeUrl()
512
518
  if(!e)return this.logger.debug("SSE forwarder: OpenCode URL unavailable, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
513
519
  const t=e+"/event"
514
520
  this.logger.info("SSE forwarder connecting",{url:t}),this.sseAbortController=new AbortController
@@ -521,23 +527,23 @@ o+=s.decode(t,{stream:!0})
521
527
  const n=o.split("\n")
522
528
  o=n.pop()||""
523
529
  for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6)),r=t.type||"unknown"
524
- this.sseEventBuffer.push({type:r,data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,error:(0,g.getErrorMessage)(e)}))),"session.idle"===r&&this.flushSSEEvents().catch((e=>this.logger.debug("Immediate SSE flush failed",{error:(0,g.getErrorMessage)(e)})))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,g.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,g.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
530
+ this.sseEventBuffer.push({type:r,data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,error:(0,f.getErrorMessage)(e)}))),"session.idle"===r&&this.flushSSEEvents().catch((e=>this.logger.debug("Immediate SSE flush failed",{error:(0,f.getErrorMessage)(e)})))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,f.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,f.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
525
531
  if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
526
532
  if(!t)return
527
533
  this.logger.debug("SSE hook: session_idle",{sessionId:t}),await this.hookExecutor.processEvent({eventType:"session_idle",sessionId:t,variables:{sessionId:t}})}else if("session.created"===t){const t=e?.properties?.info||e?.info,r=t?.id||""
528
534
  if(!r)return
529
535
  const s=t?.metadata
530
536
  if(s?.agentType&&this.projectId){const e=t?.parentID||null
531
- this.logger.info("SSE-driven cli-sessions upsert",{sessionId:r,agentType:s.agentType,parentExternalIdFromOpenCode:e,note:"no parent fields included in body; preserves existing parent if record exists"}),this.apiClient.post(`/api/projects/${this.projectId}/cli-sessions`,{externalSessionId:r,providerId:"opencode",agentType:s.agentType,status:"active",specialization:s.specialization||void 0}).catch((e=>{this.logger.debug("SSE session registration (idempotent)",{sessionId:r,error:(0,g.getErrorMessage)(e)})}))}setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:r}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:r,variables:{sessionId:r}})}),500)}}async flushSSEEvents(){if(!this.projectId)return
537
+ this.logger.info("SSE-driven cli-sessions upsert",{sessionId:r,agentType:s.agentType,parentExternalIdFromOpenCode:e,note:"no parent fields included in body; preserves existing parent if record exists"}),this.apiClient.post(`/api/projects/${this.projectId}/cli-sessions`,{externalSessionId:r,providerId:"opencode",agentType:s.agentType,status:"active",specialization:s.specialization||void 0}).catch((e=>{this.logger.debug("SSE session registration (idempotent)",{sessionId:r,error:(0,f.getErrorMessage)(e)})}))}setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:r}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:r,variables:{sessionId:r}})}),500)}}async flushSSEEvents(){if(!this.projectId)return
532
538
  if(0===this.sseEventBuffer.length&&0===this.droppedEventCount)return
533
539
  if(!this.hasRemoteEventConsumers){if(this.sseEventBuffer.length>500){const e=this.sseEventBuffer.length-500
534
540
  this.sseEventBuffer.splice(0,e),0===this.droppedEventCount&&(this.firstDropAt=Date.now()),this.droppedEventCount+=e}return}const e=this.sseEventBuffer.splice(0),t=this.droppedEventCount>0?{type:"alchemy.events.dropped",data:{count:this.droppedEventCount,firstDropAt:this.firstDropAt,reportedAt:Date.now()}}:null
535
541
  t&&e.unshift(t)
536
- try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e}),t&&(this.logger.warn("Reported dropped SSE events to remote",{count:t.data.count,firstDropAt:t.data.firstDropAt}),this.droppedEventCount=0,this.firstDropAt=0)}catch(r){this.logger.debug("SSE event flush failed",{error:(0,g.getErrorMessage)(r),count:e.length})
542
+ try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e}),t&&(this.logger.warn("Reported dropped SSE events to remote",{count:t.data.count,firstDropAt:t.data.firstDropAt}),this.droppedEventCount=0,this.firstDropAt=0)}catch(r){this.logger.debug("SSE event flush failed",{error:(0,f.getErrorMessage)(r),count:e.length})
537
543
  const s=t?e.slice(1):e
538
544
  if(this.sseEventBuffer.unshift(...s),this.sseEventBuffer.length>500){const e=this.sseEventBuffer.length-500
539
545
  this.sseEventBuffer.splice(0,e),0===this.droppedEventCount&&(this.firstDropAt=Date.now()),this.droppedEventCount+=e}}}startPolling(){this.projectId&&(this.isPolling=!0,this.pollAbortController=new AbortController,this.logger.info("Starting action long-polling",{projectId:this.projectId}),this.longPollLoop())}async longPollLoop(){for(;this.isPolling;)try{await this.pollOnce()}catch(e){if(!this.isPolling)return
540
- this.logger.debug("Long-poll error, retrying in 5s",{error:(0,g.getErrorMessage)(e)}),await new Promise((e=>setTimeout(e,5e3)))}}async pollOnce(){if(this.isPolling&&this.projectId&&!this.updateBlocked)try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=30000`,t={"Content-Type":"application/json",Accept:"application/json"}
546
+ this.logger.debug("Long-poll error, retrying in 5s",{error:(0,f.getErrorMessage)(e)}),await new Promise((e=>setTimeout(e,5e3)))}}async pollOnce(){if(this.isPolling&&this.projectId&&!this.updateBlocked)try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=30000`,t={"Content-Type":"application/json",Accept:"application/json"}
541
547
  this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
542
548
  const r=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
543
549
  t["X-Service-Secret"]=r,this.clientVersion&&(t["X-Client-Version"]=this.clientVersion),this.opencodeVersion&&(t["X-OpenCode-Version"]=this.opencodeVersion)
@@ -546,54 +552,54 @@ if(!s.ok)throw Error("Poll failed with status "+s.status)
546
552
  const o=await s.json(),n={success:!0,data:o.data||o}
547
553
  if(n.success&&n.data&&void 0!==n.data.hasRemoteEventConsumers&&(this.hasRemoteEventConsumers=n.data.hasRemoteEventConsumers),n.success&&n.data?.settings){const{defaultModel:e,autoJournal:t}=n.data.settings
548
554
  if(void 0!==e&&this.updateDefaultModel(e),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:t})
549
- const e=new p.ProjectConfigService(this.logger)
555
+ const e=new y.ProjectConfigService(this.logger)
550
556
  await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}if(n.success&&Array.isArray(n.data?.invalidations)&&n.data.invalidations.length>0){const e=n.data.invalidations
551
557
  this.logger.info("Received cache invalidations from remote",{invalidations:e}),e.includes("hooks")&&this.timerHookScheduler?.invalidateCache(),e.includes("quests")&&this.questTickScheduler?.invalidateCache(),e.includes("alchemist")&&this.questTickScheduler?.invalidateAlchemistCache()}if(n.success&&Array.isArray(n.data?.versionWarnings)&&this.applyVersionWarnings(n.data.versionWarnings),n.success&&n.data?.actions?.length>0){const e=[],t=[]
552
558
  for(const r of n.data.actions)"opencode-proxy"===r.type||"run-tests"===r.type?e.push(r):t.push(r)
553
- for(const t of e)this.processAction(t).catch((e=>this.logger.warn("Proxy action failed",{actionId:t.id,error:(0,g.getErrorMessage)(e)})))
559
+ for(const t of e)this.processAction(t).catch((e=>this.logger.warn("Proxy action failed",{actionId:t.id,error:(0,f.getErrorMessage)(e)})))
554
560
  if(t.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
555
561
  try{for(const e of t)await this.processAction(e)}finally{this.isProcessingAction=!1}}}}catch(e){throw e}}async syncProjectSettings(){try{const e=await this.apiClient.get("/api/projects/"+this.projectId)
556
562
  if(e.success&&e.data?.metadata){const{autoJournal:t,defaultModel:r}=e.data.metadata
557
563
  if(void 0!==r&&this.updateDefaultModel(r),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed, syncing to local config",{oldValue:this.cachedAutoJournal,newValue:t})
558
- const e=new p.ProjectConfigService(this.logger)
559
- await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}}catch(e){this.logger.debug("Settings sync failed",{error:(0,g.getErrorMessage)(e)})}}async processAction(e){this.logger.info("Processing action",{actionId:e.id,type:e.type})
564
+ const e=new y.ProjectConfigService(this.logger)
565
+ await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}}catch(e){this.logger.debug("Settings sync failed",{error:(0,f.getErrorMessage)(e)})}}async processAction(e){this.logger.info("Processing action",{actionId:e.id,type:e.type})
560
566
  const t=Date.now()
561
- try{const r={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e),cachedDefaultModel:this.cachedDefaultModel},s=await(0,d.executeAction)(e.type,e.payload||{},r)
562
- await this.apiClient.post(`/api/actions/${e.id}/result`,{success:s.success,result:s.data,error:s.message||s.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:s.success,duration:Date.now()-t})}catch(r){const s=(0,g.getErrorMessage)(r)
567
+ try{const r={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e),cachedDefaultModel:this.cachedDefaultModel},s=await(0,g.executeAction)(e.type,e.payload||{},r)
568
+ await this.apiClient.post(`/api/actions/${e.id}/result`,{success:s.success,result:s.data,error:s.message||s.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:s.success,duration:Date.now()-t})}catch(r){const s=(0,f.getErrorMessage)(r)
563
569
  await this.apiClient.post(`/api/actions/${e.id}/result`,{success:!1,error:s,duration:Date.now()-t}),this.logger.error("Action failed",{actionId:e.id,type:e.type,error:s})}}async handleCLIBridgeCommand(e,t){try{const{action:r,params:s,timeout:o}=e.body
564
570
  if(!r)return void t.status(400).json({success:!1,error:"action is required"})
565
571
  if(["list-notifications","get-notification","respond-to-notification"].includes(r)){const e=await this.handleNotificationCommand(r,s||{})
566
572
  return void t.json(e)}if(["list-sessions","get-messages","send-message","create-session","get-session-status"].includes(r)){const e=await this.handleSessionCommand(r,s||{})
567
573
  return void t.json(e)}if(["list-requests","get-request"].includes(r)){const e=await this.handleRequestCommand(r,s||{})
568
574
  return void t.json(e)}const n=await this.cliBridge.sendCommand(r,s||{},o)
569
- t.json(n)}catch(e){t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleNotificationCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
575
+ t.json(n)}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleNotificationCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
570
576
  const r=this.projectId
571
577
  try{switch(e){case"list-notifications":{const e={limit:t.limit||30}
572
578
  return t.sessionId&&(e.sessionId=t.sessionId),t.messageType&&(e.messageType=t.messageType),t.status&&(e.status=t.status),t.readStatus&&(e.readStatus=t.readStatus),{success:!0,data:(await this.apiClient.get(`/api/projects/${r}/session-messages`,e)).data}}case"get-notification":return t.messageCode?{success:!0,data:(await this.apiClient.get(`/api/projects/${r}/session-messages/by-code/${encodeURIComponent(t.messageCode)}`)).data}:{success:!1,error:"messageCode is required"}
573
579
  case"respond-to-notification":{if(!t.messageCode)return{success:!1,error:"messageCode is required"}
574
580
  const e=t.messageCode
575
- return{success:!0,data:(await this.apiClient.post(`/api/projects/${r}/session-messages/${encodeURIComponent(e)}/answer`,{respondedBySessionId:t.respondedBySessionId||"cli-bridge",text:t.responseText,selectedOptions:t.selectedOptions,notes:t.notes})).data}}default:return{success:!1,error:"Unknown notification action: "+e}}}catch(e){return{success:!1,error:(0,g.getErrorMessage)(e)}}}async handleRequestCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
581
+ return{success:!0,data:(await this.apiClient.post(`/api/projects/${r}/session-messages/${encodeURIComponent(e)}/answer`,{respondedBySessionId:t.respondedBySessionId||"cli-bridge",text:t.responseText,selectedOptions:t.selectedOptions,notes:t.notes})).data}}default:return{success:!1,error:"Unknown notification action: "+e}}}catch(e){return{success:!1,error:(0,f.getErrorMessage)(e)}}}async handleRequestCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
576
582
  const r=this.projectId
577
583
  try{switch(e){case"list-requests":{const e={}
578
584
  return t.status&&(e.status=t.status),t.type&&(e.type=t.type),t.limit&&(e.limit=t.limit),t.offset&&(e.offset=t.offset),t.sortBy&&(e.sortBy=t.sortBy),t.sortDirection&&(e.sortDirection=t.sortDirection),{success:!0,data:(await this.apiClient.get(`/api/projects/${r}/requests`,e)).data}}case"get-request":{if(!t.requestId)return{success:!1,error:"requestId is required"}
579
585
  const e=encodeURIComponent(t.requestId),[s,o]=await Promise.all([this.apiClient.get(`/api/projects/${r}/requests/${e}`),this.apiClient.get(`/api/projects/${r}/tasks`,{requestId:t.requestId})])
580
- return{success:!0,data:{...s.data,tasks:o.data||[]}}}default:return{success:!1,error:"Unknown request action: "+e}}}catch(e){return{success:!1,error:(0,g.getErrorMessage)(e)}}}handleCLIBridgeStatus(e,t){t.json({uiConnected:this.cliBridge.isUIConnected()})}async start(){this.logger.info("Starting AlchemyActionServer",{projectPath:this.options.projectPath,port:this.port})
586
+ return{success:!0,data:{...s.data,tasks:o.data||[]}}}default:return{success:!1,error:"Unknown request action: "+e}}}catch(e){return{success:!1,error:(0,f.getErrorMessage)(e)}}}handleCLIBridgeStatus(e,t){t.json({uiConnected:this.cliBridge.isUIConnected()})}async start(){this.logger.info("Starting AlchemyActionServer",{projectPath:this.options.projectPath,port:this.port})
581
587
  for(let e=0;10>e;e++)try{await new Promise(((e,t)=>{try{this.server=this.app.listen(this.port,"127.0.0.1",(()=>{this.logger.info("HTTP server started",{port:this.port}),this.cliBridge.attach(this.server),e()})),this.server.on("error",(e=>{e.code,t(e)}))}catch(e){t(e)}}))
582
588
  break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
583
- continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{const e=a.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
584
- c.default.mkdirSync(a.default.dirname(e),{recursive:!0}),c.default.writeFileSync(e,JSON.stringify({port:this.port},null,2),"utf-8"),this.logger.info("Wrote action-server port file",{port:this.port,path:e})}catch(e){this.logger.warn("Failed to write action-server port file",{error:(0,g.getErrorMessage)(e)})}try{const e=this.readProjectConfig()
585
- if(e?.id){const{upsertEntry:r}=await t.e(640).then(t.bind(t,2640))
586
- r({projectId:e.id,projectPath:this.options.projectPath,port:this.port,pid:process.pid,startedAt:(new Date).toISOString(),lastHeartbeat:(new Date).toISOString()}),this.logger.info("Wrote running-projects registry entry",{projectId:e.id,port:this.port})}}catch(e){this.logger.warn("Failed to write project registry entry",{error:(0,g.getErrorMessage)(e)})}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,g.getErrorMessage)(e)
589
+ continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{const e=d.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
590
+ u.default.mkdirSync(d.default.dirname(e),{recursive:!0}),u.default.writeFileSync(e,JSON.stringify({port:this.port},null,2),"utf-8"),this.logger.info("Wrote action-server port file",{port:this.port,path:e})}catch(e){this.logger.warn("Failed to write action-server port file",{error:(0,f.getErrorMessage)(e)})}try{const e=this.readProjectConfig()
591
+ if(e?.id){const{upsertEntry:r}=await Promise.resolve().then(t.bind(t,2640))
592
+ r({projectId:e.id,projectPath:this.options.projectPath,port:this.port,pid:process.pid,startedAt:(new Date).toISOString(),lastHeartbeat:(new Date).toISOString()}),this.logger.info("Wrote running-projects registry entry",{projectId:e.id,port:this.port})}}catch(e){this.logger.warn("Failed to write project registry entry",{error:(0,f.getErrorMessage)(e)})}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,f.getErrorMessage)(e)
587
593
  t.includes("Required update"),this.logger.warn("Remote registration failed, continuing in local-only mode",{error:t})}try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=5000`,t={"Content-Type":"application/json",Accept:"application/json"}
588
594
  this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
589
595
  const r=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
590
596
  t["X-Service-Secret"]=r
591
597
  const s=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
592
598
  if(s.ok){const e=await s.json(),t=e?.data?.settings||e?.settings
593
- void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,g.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler(),this.projectId&&this.startQuestTickScheduler(),this.startMcpStaleCleanup()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,g.getErrorMessage)(t)})}}this.trackedChildPids.clear()
594
- try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
595
- if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
596
- if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,g.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new b.TimerHookScheduler({hookExecutor:this.hookExecutor,apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.timerHookScheduler.start()}startQuestTickScheduler(){this.questTickScheduler=new I.QuestTickScheduler({apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.questTickScheduler.start()}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new w.StuckSessionWatchdog({getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,logger:this.logger,onStuckDetected:e=>this.handleStuckSessionDetected(e),onStuckResolved:e=>this.handleStuckSessionResolved(e)}),this.stuckSessionWatchdog.start()}handleStuckSessionDetected(e){this.sseEventBuffer.push({type:"session.stuck",data:{type:"session.stuck",properties:{sessionId:e.sessionId,messageId:e.messageId,detectedAt:e.detectedAt,sessionTitle:e.sessionTitle,retryCount:e.retryCount}}}),this.logger.warn("Stuck session detected",{sessionId:e.sessionId,title:e.sessionTitle})}handleStuckSessionResolved(e){this.sseEventBuffer.push({type:"session.unstuck",data:{type:"session.unstuck",properties:{sessionId:e}}})}handleGetStuckSessions(e,t){const r=this.stuckSessionWatchdog?.getStuckSessions()??[]
599
+ void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,f.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler(),this.projectId&&this.startQuestTickScheduler(),this.startMcpStaleCleanup()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,f.getErrorMessage)(t)})}}this.trackedChildPids.clear()
600
+ try{const e=d.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
601
+ if(u.default.existsSync(e)){const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
602
+ if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,f.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new S.TimerHookScheduler({hookExecutor:this.hookExecutor,apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.timerHookScheduler.start()}startQuestTickScheduler(){this.questTickScheduler=new C.QuestTickScheduler({apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.questTickScheduler.start()}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new E.StuckSessionWatchdog({getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,logger:this.logger,onStuckDetected:e=>this.handleStuckSessionDetected(e),onStuckResolved:e=>this.handleStuckSessionResolved(e)}),this.stuckSessionWatchdog.start()}handleStuckSessionDetected(e){this.sseEventBuffer.push({type:"session.stuck",data:{type:"session.stuck",properties:{sessionId:e.sessionId,messageId:e.messageId,detectedAt:e.detectedAt,sessionTitle:e.sessionTitle,retryCount:e.retryCount}}}),this.logger.warn("Stuck session detected",{sessionId:e.sessionId,title:e.sessionTitle})}handleStuckSessionResolved(e){this.sseEventBuffer.push({type:"session.unstuck",data:{type:"session.unstuck",properties:{sessionId:e}}})}handleGetStuckSessions(e,t){const r=this.stuckSessionWatchdog?.getStuckSessions()??[]
597
603
  t.json({success:!0,data:r})}handleTestInjectStuckSession(e,t){if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
598
604
  const{sessionId:r,title:s}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(r,s)
599
605
  this.logger.info("Test-injected stuck session",{sessionId:o.sessionId}),t.json({success:!0,data:o})}async handleRetryStuckSession(e,t){const{sessionId:r}=e.params
@@ -604,10 +610,10 @@ for(const e of this.versionWarnings)console.error(`\x1b[31m ${e.message}\x1b[0m
604
610
  console.error("\x1b[31m\n Functionality disabled. Update via UI or run:\n npm update -g alchemylab\x1b[0m\n"),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.timerHookScheduler?.stop(),this.questTickScheduler?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval)
605
611
  for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.clear(),this.cliBridge.close(),this.logger.info("Update-required mode active. HTTP server still running on port "+this.port)}}async stop(){this.logger.info("Stopping AlchemyActionServer...")
606
612
  for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.timerHookScheduler?.stop(),this.questTickScheduler?.stop(),this.stopSSEEventForwarder(),this.mcpStaleCleanupInterval&&(clearInterval(this.mcpStaleCleanupInterval),this.mcpStaleCleanupInterval=void 0)
607
- try{const e=a.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
608
- c.default.existsSync(e)&&c.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await t.e(640).then(t.bind(t,2640))
613
+ try{const e=d.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
614
+ u.default.existsSync(e)&&u.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await Promise.resolve().then(t.bind(t,2640))
609
615
  e(this.projectId)}catch{}if(this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
610
- this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=M,M.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
616
+ this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=D,D.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
611
617
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ensureRequestWorktree=async function(e,t,i){const a="requests/"+t,c=(0,s.join)(e,n.ALCHEMY_DIR_NAME,"worktrees"),l=(0,s.join)(c,t)
612
618
  if((0,r.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
613
619
  const d=(0,o.simpleGit)(e);(0,r.mkdirSync)(c,{recursive:!0})
@@ -616,7 +622,20 @@ try{if(h)await d.raw(["worktree","add",l,a]),i.info("Pre-spawn: created worktree
616
622
  else{const e=u.all.includes("main")?"main":"master"
617
623
  await d.raw(["worktree","add","-b",a,l,e]),i.info("Pre-spawn: created worktree with new branch from latest main",{requestId:t,branchName:a,mainBranch:e,worktreePath:l})}}catch(e){const r=e instanceof Error?e.message:e+""
618
624
  i.warn("Pre-spawn: failed to create worktree (MCP process will retry)",{requestId:t,error:r})}}
619
- const r=t(9896),s=t(6928),o=t(3807),n=t(4139)},2782(e,exports,t){"use strict"
625
+ const r=t(9896),s=t(6928),o=t(3807),n=t(4139)},2640(e,exports,t){"use strict"
626
+ function r(){if(!(0,o.existsSync)(c))return[]
627
+ try{const e=(0,o.readFileSync)(c,"utf-8"),t=JSON.parse(e)
628
+ return Array.isArray(t)?t:[]}catch{return[]}}function s(e){(0,o.existsSync)(a)||(0,o.mkdirSync)(a,{recursive:!0})
629
+ const t=`${c}.${process.pid}.tmp`;(0,o.writeFileSync)(t,JSON.stringify(e,null,2),"utf-8"),(0,o.renameSync)(t,c)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.upsertEntry=function(e){try{const t=r().filter((t=>t.projectId!==e.projectId))
630
+ t.push(e),s(t)}catch{}},exports.removeEntry=function(e){try{const t=r().filter((t=>t.projectId!==e))
631
+ if(0===t.length&&(0,o.existsSync)(c))return void(0,o.unlinkSync)(c)
632
+ s(t)}catch{}},exports.touchHeartbeat=function(e){try{const t=r(),o=t.findIndex((t=>t.projectId===e))
633
+ if(-1===o)return
634
+ t[o]={...t[o],lastHeartbeat:(new Date).toISOString()},s(t)}catch{}},exports.listLive=function(){const e=r(),t=Date.now()-l
635
+ return e.filter((e=>{const r=Date.parse(e.lastHeartbeat)
636
+ if(!isFinite(r)||t>r)return!1
637
+ try{process.kill(e.pid,0)}catch(e){if("ESRCH"===e?.code)return!1}return!0}))}
638
+ const o=t(9896),n=t(6928),i=t(857),a=(0,n.join)((0,i.homedir)(),".alchemy_lab"),c=(0,n.join)(a,"running-projects.json"),l=6e4},2782(e,exports,t){"use strict"
620
639
  function r(){return"true"===process.env.ENABLE_DEBUG_TOOLS?.toLowerCase()}function s(e={}){const t={...function(e){const t={}
621
640
  if(!r())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.logToFile=!1,t
622
641
  if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
@@ -867,9 +886,9 @@ break}await s(500)}for(;Date.now()<n;){const o=await r(e,t)
867
886
  if("error"===o)return{status:"error"}
868
887
  if(("idle"===o||"completed"===o)&&i)return{status:"completed"}
869
888
  "busy"!==o&&"running"!==o||(i=!0),await s(2e3)}return{status:"timeout"}}(y,w,g),I=Date.now()-v,E=await async function(e,t){try{const r=await fetch(`${e}/session/${t}/message?limit=200`,{signal:AbortSignal.timeout(1e4)})
870
- return r.ok?(await r.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),C=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(E),S=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
889
+ return r.ok?(await r.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),S=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(E),C=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
871
890
  return""}(E)
872
- return{success:!0,message:"Prompt completed",data:{sessionId:w,status:b.status,messages:E,toolCallCount:C,elapsedMs:I,finalResponse:S}}}
891
+ return{success:!0,message:"Prompt completed",data:{sessionId:w,status:b.status,messages:E,toolCallCount:S,elapsedMs:I,finalResponse:C}}}
873
892
  const o=t(8148)},4769(e,exports,t){"use strict"
874
893
  function r(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function s(e,t,s,o){const n=r(s)
875
894
  if(!n||!t)return!1
@@ -948,7 +967,15 @@ await s.default.mkdir(t,{recursive:!0})
948
967
  const a=JSON.stringify({timestamp:(new Date).toISOString(),reason:i},null,2)
949
968
  return await s.default.writeFile(e,a,"utf-8"),r.info("MCP restart triggered",{triggerPath:e,reason:i}),{success:!0,message:"MCP restart triggered: "+i,data:{triggerPath:e}}}catch(e){const t=e instanceof Error?e.message:e+""
950
969
  return r.error("Failed to trigger MCP restart",{error:t}),{success:!1,message:"Failed to trigger MCP restart: "+t,error:t}}}
951
- const s=r(t(1943)),o=r(t(6928))},6175(e,exports){"use strict"
970
+ const s=r(t(1943)),o=r(t(6928))},5959(e,exports){"use strict"
971
+ function t(){const e=Date.now()
972
+ for(const[t,s]of o)"running"!==s.status&&s.finishedAt&&e-Date.parse(s.finishedAt)>r&&o.delete(t)
973
+ if(o.size>s){const e=[...o.values()].filter((e=>"running"!==e.status)).sort(((e,t)=>(e.finishedAt??"").localeCompare(t.finishedAt??"")))
974
+ for(const t of e){if(o.size<=s)break
975
+ o.delete(t.id)}}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.addStart=function(e){const r={...e,status:"running"}
976
+ o.set(e.id,r),t()},exports.markEnd=function(e){const r=o.get(e.id)
977
+ r&&(r.exitCode=e.exitCode,r.timedOut=e.timedOut,r.finishedAt=e.finishedAt,r.status=e.timedOut?"timed-out":0===e.exitCode?"completed":"failed",t())},exports.listEntries=function(){return t(),[...o.values()].sort(((e,t)=>t.startedAt.localeCompare(e.startedAt)))},exports._resetForTest=function(){o.clear()}
978
+ const r=6e5,s=100,o=new Map},6175(e,exports){"use strict"
952
979
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerConditionEvaluator=void 0
953
980
  const t=new Set(["coder","reviewer","tester","journal","planner","researcher"])
954
981
  exports.TimerConditionEvaluator=class{constructor(e){this.logger=e}async evaluate(e,t,r){if(!e||0===e.length)return{passed:!0,variables:{}}
@@ -999,21 +1026,21 @@ let I
999
1026
  if(u?.length)I=(await Promise.all(u.map((e=>f.get(`/api/projects/${y}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
1000
1027
  else{const e=await f.get(`/api/projects/${y}/test-processes/active`)
1001
1028
  I=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===I.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
1002
- const E=Math.max(1,Math.min(8,I[0]?.config?.poolSize??2)),C=I[0]?.config?.fingerprint||[]
1003
- let S
1004
- try{S=await(0,c.acquireSlot)(v,E)}catch(e){return{success:!1,message:"Could not acquire test worktree slot: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}}const k=S.path,T=(0,n.simpleGit)(v)
1029
+ const E=Math.max(1,Math.min(8,I[0]?.config?.poolSize??2)),S=I[0]?.config?.fingerprint||[]
1030
+ let C
1031
+ try{C=await(0,c.acquireSlot)(v,E)}catch(e){return{success:!1,message:"Could not acquire test worktree slot: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}}const k=C.path,T=(0,n.simpleGit)(v)
1005
1032
  try{if((0,s.existsSync)(k))try{const e=(0,n.simpleGit)(k)
1006
- await e.raw(["reset","--hard",b]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:k,branchName:b,slotIndex:S.index})}catch(e){m.warn("Could not reset slot, recreating",{error:(0,i.getErrorMessage)(e)})
1033
+ await e.raw(["reset","--hard",b]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:k,branchName:b,slotIndex:C.index})}catch(e){m.warn("Could not reset slot, recreating",{error:(0,i.getErrorMessage)(e)})
1007
1034
  try{await T.raw(["worktree","remove","--force",k])}catch{}}if(!(0,s.existsSync)(k)){(0,s.mkdirSync)((0,o.join)(v,".alchemy_lab","test-worktrees"),{recursive:!0})
1008
1035
  try{if(!(await T.branch()).all.includes(b))return(0,c.releaseSlot)(k),{success:!1,message:`Branch ${b} not found`,error:"Branch not found"}
1009
- await T.raw(["worktree","add","--detach",k,b]),m.info("Created pool slot worktree (detached)",{worktreePath:k,branchName:b,slotIndex:S.index})}catch(e){if(!(e+"").includes("already exists"))return(0,c.releaseSlot)(k),{success:!1,message:"Failed to create pool slot worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
1010
- m.info("Pool slot worktree already exists, using as-is",{worktreePath:k})}}}catch(e){throw(0,c.releaseSlot)(k),e}const _=await(0,c.computeFingerprint)(k,C),j=0===C.length||_!==S.storedFingerprint,A=0===C.length?null:_===S.storedFingerprint
1011
- j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:k,slotIndex:S.index,fingerprintFiles:C})
1036
+ await T.raw(["worktree","add","--detach",k,b]),m.info("Created pool slot worktree (detached)",{worktreePath:k,branchName:b,slotIndex:C.index})}catch(e){if(!(e+"").includes("already exists"))return(0,c.releaseSlot)(k),{success:!1,message:"Failed to create pool slot worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
1037
+ m.info("Pool slot worktree already exists, using as-is",{worktreePath:k})}}}catch(e){throw(0,c.releaseSlot)(k),e}const _=await(0,c.computeFingerprint)(k,S),j=0===S.length||_!==C.storedFingerprint,A=0===S.length?null:_===C.storedFingerprint
1038
+ j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:k,slotIndex:C.index,fingerprintFiles:S})
1012
1039
  const{execSync:P}=await Promise.resolve().then(t.t.bind(t,5317,23))
1013
1040
  if(j){for(const e of I){const t=e.config?.setup||[]
1014
- if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:S.index})
1015
- for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),P(r,{cwd:k,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}C.length>0&&await(0,c.writeSlotFingerprint)(k,_),await(0,c.writeWarmSnapshot)(k)}else{const{removed:e}=await(0,c.restoreToWarmSnapshot)(k)
1016
- e>0&&m.info("Pruned drift from warm slot",{worktreePath:k,slotIndex:S.index,removed:e})}let M
1041
+ if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:C.index})
1042
+ for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),P(r,{cwd:k,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}S.length>0&&await(0,c.writeSlotFingerprint)(k,_),await(0,c.writeWarmSnapshot)(k)}else{const{removed:e}=await(0,c.restoreToWarmSnapshot)(k)
1043
+ e>0&&m.info("Pruned drift from warm slot",{worktreePath:k,slotIndex:C.index,removed:e})}let M
1017
1044
  try{const e=(0,n.simpleGit)(k)
1018
1045
  M=(await e.revparse(["HEAD"])).trim()}catch(e){m.warn("Failed to capture worktree HEAD; commit_sha will be NULL on this run",{worktreePath:k,error:(0,i.getErrorMessage)(e)})}const O=[],R=[],D=[]
1019
1046
  let x
@@ -1112,14 +1139,21 @@ const o=s(t(9278))},6895(module){"use strict"
1112
1139
  module.exports=require("chalk")},6928(module){"use strict"
1113
1140
  module.exports=require("path")},6982(module){"use strict"
1114
1141
  module.exports=require("crypto")},7078(e,exports,t){"use strict"
1115
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleSpawnAutoPlannerAction=async function(e,t){const{requestId:o,parentSessionId:n}=e,{logger:i,apiClient:a,projectId:c,projectPath:l,trackChildPid:d}=t
1116
- if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
1117
- i.info("Starting spawn-auto-planner action",{requestId:o})
1118
- try{const e=await a.get(`/api/projects/${c}/requests/${o}`)
1119
- if(!e.success||!e.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
1120
- const t=e.data,r=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",u=`Create a plan for request ${o}: ${t.title}\n\nDescription: ${t.description||"No description provided."}\n\n${r?"Acceptance Criteria:\n- "+r:""}\n\nResearch the codebase and create a comprehensive implementation plan.`,h=await(0,s.spawnActionAgent)({agent:"auto-planner",prompt:u,cwd:l,projectPath:l,projectId:c,title:`[${o}] Plan: ${t.title}`,requestId:o,parentSessionId:n,apiClient:a,logger:i,trackChildPid:d})
1121
- return h.success?(i.info("Spawned auto-planner for request",{requestId:o,sessionId:h.sessionId,pid:h.pid}),{success:!0,message:"Auto-planner spawned for request "+o,data:{sessionId:h.sessionId,pid:h.pid}}):{success:!1,message:"Failed to spawn auto-planner: "+h.error,error:h.error||"Spawn failed"}}catch(e){const t=(0,r.getErrorMessage)(e)
1122
- return i.error("spawn-auto-planner action failed",{requestId:o,error:t}),{success:!1,message:"Failed to spawn auto-planner: "+t,error:t}}}
1142
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleSpawnAutoPlannerAction=async function(e,t){const{requestId:o,prompt:n,title:i,parentSessionId:a,originMessageCode:c}=e,{logger:l,apiClient:d,projectId:u,projectPath:h,trackChildPid:g}=t
1143
+ if(!o&&!n)return{success:!1,message:"Either requestId or prompt is required",error:"Missing payload"}
1144
+ l.info("Starting spawn-auto-planner action",{requestId:o,hasPrompt:!!n})
1145
+ try{let e,t,p
1146
+ if(o){const r=await d.get(`/api/projects/${u}/requests/${o}`)
1147
+ if(!r.success||!r.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
1148
+ const s=r.data,n=Array.isArray(s.acceptanceCriteria)?s.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):""
1149
+ e=`Create a plan for request ${o}: ${s.title}\n\nDescription: ${s.description||"No description provided."}\n\n${n?"Acceptance Criteria:\n- "+n:""}\n\nResearch the codebase and create a comprehensive implementation plan.`,t=`[${o}] Plan: ${s.title}`,p=s.title||"New Plan for "+o}else e=n,t=i||"Auto-planner",p=i||"Auto-planner Draft"
1150
+ const m=`PLAN-${p.replace(/\.md$/i,"")}.md`
1151
+ let f
1152
+ try{const e=await d.post(`/api/projects/${u}/documents`,{filename:m,content:" ",type:"Plan"})
1153
+ f=e?.data?.id,l.info("Pre-created plan document for auto-planner",{planId:f,filename:m})}catch(e){l.warn("Failed to pre-create plan document; auto-planner will fall back to plan_write create",{error:(0,r.getErrorMessage)(e)})}const y=f?`[Session Context]\n- planId: ${f} \u2014 this is YOUR associated plan document. It already exists with a placeholder filename.\n- Use plan_write (update / append / replace) with planId "${f}" to fill it in.\n- On your FIRST plan_write call, include a "name" field with a short descriptive title so the document gets a meaningful filename.\n- Do NOT call plan_write "create" \u2014 always modify the document above.\n- Do NOT call plan_read "get-active" \u2014 your associated plan is given here, not via the active-plan slot.\n\n${e}`:e,v=await(0,s.spawnActionAgent)({agent:"auto-planner",prompt:y,cwd:h,projectPath:h,projectId:u,title:t,requestId:o,parentSessionId:a,apiClient:d,logger:l,trackChildPid:g})
1154
+ if(!v.success)return{success:!1,message:"Failed to spawn auto-planner: "+v.error,error:v.error||"Spawn failed"}
1155
+ if(l.info("Spawned auto-planner",{requestId:o,sessionId:v.sessionId,pid:v.pid,planId:f}),f&&v.sessionId)try{await d.post(`/api/projects/${u}/session-plan-associations`,{opencodeSessionId:v.sessionId,documentId:f})}catch(e){l.warn("Failed to create session-plan association at spawn time",{error:(0,r.getErrorMessage)(e)})}return c&&d.patch(`/api/projects/${u}/session-messages/${c}/response`,{partial:{plannerSessionId:v.sessionId,planId:f}}).catch((e=>l.warn("Failed to record plannerSessionId on origin message",{originMessageCode:c,error:(0,r.getErrorMessage)(e)}))),{success:!0,message:o?"Auto-planner spawned for request "+o:"Auto-planner spawned: "+(i||v.sessionId),data:{sessionId:v.sessionId,pid:v.pid}}}catch(e){const t=(0,r.getErrorMessage)(e)
1156
+ return l.error("spawn-auto-planner action failed",{requestId:o,error:t}),{success:!1,message:"Failed to spawn auto-planner: "+t,error:t}}}
1123
1157
  const r=t(8824),s=t(8148)},7163(e,exports){"use strict"
1124
1158
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.debugTokenAccess=function(){},exports.compareTokens=function(){},exports.debugTokenStorage=function(){},exports.disableTokenDebugging=function(){},exports.enableTokenDebugging=function(){},new WeakMap},7210(e,exports,t){"use strict"
1125
1159
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.tryMarkMessageProcessed=exports.createToolCallHash=exports.isDuplicateToolCall=exports.determineEventToEmit=exports.interpolatePrompt=exports.evaluateCondition=exports.TimerConditionEvaluator=exports.TimerHookScheduler=exports.HookCache=exports.HookExecutor=void 0
@@ -1493,13 +1527,13 @@ if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
1493
1527
  t?.openCodeTools?(g=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:g})):v=!0,t?.openCodeModel&&(p=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),v=!0}try{const e=await r.readFile(h,"utf-8"),t=JSON.parse(e)
1494
1528
  t?.mode?.alchemy?.tools&&(m=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:m})),t?.model&&(f=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const b=i?.openCodeTools||g||m||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},I={tools:b},E=i?.openCodeModel||p||f||"anthropic/claude-opus-4-5"
1495
1529
  i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:f&&(I.model=f)
1496
- const C=c.join(s,".opencode","agent")
1497
- await r.mkdir(C,{recursive:!0})
1498
- const S=E||"anthropic/claude-opus-4-5-20251101"
1530
+ const S=c.join(s,".opencode","agent")
1531
+ await r.mkdir(S,{recursive:!0})
1532
+ const C=E||"anthropic/claude-opus-4-5-20251101"
1499
1533
  let k,T=null,_=null
1500
1534
  if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),_=await this.fetchProjectFromRemote(i.projectId),k=_?.metadata?.globalSystemPrompt,k&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:k.length}),T=await this.fetchAgentsFromRemote(i.projectId),T&&T.length>0){this.logger.info("Using agents from remote database",{count:T.length})
1501
1535
  for(const e of T){if(!e.enabled)continue
1502
- const t=this.generateAgentMarkdown(e,d,S,k),s=c.join(C,e.name+".md")
1536
+ const t=this.generateAgentMarkdown(e,d,C,k),s=c.join(S,e.name+".md")
1503
1537
  await r.writeFile(s,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:s,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
1504
1538
  else this.logger.warn("No projectId provided - agents must be configured via database")
1505
1539
  const j={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0,timeout:6e5}}}
@@ -1512,7 +1546,7 @@ e.name in r&&(t.bash=!!r[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.
1512
1546
  let s=""
1513
1547
  k&&(s+=k.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),j.agent[e.name]={tools:t,...s?{prompt:s}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}j.agent.general={...j.agent.general,disable:!0},j.agent.explore={...j.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
1514
1548
  const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${k?k.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`
1515
- await r.writeFile(c.join(C,"explore.md"),e,"utf8"),this.logger.info("Created explore agent override with research prompt"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(j.agent).length})}const A=e=>"string"==typeof e&&e.includes(".opencode/plugin/"),P=(await this.fetchPluginManifest())["opencode-claude-auth"],M=j.plugin,O=Array.isArray(M)?M.filter((e=>!A(e))):[]
1549
+ await r.writeFile(c.join(S,"explore.md"),e,"utf8"),this.logger.info("Created explore agent override with research prompt"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(j.agent).length})}const A=e=>"string"==typeof e&&e.includes(".opencode/plugin/"),P=(await this.fetchPluginManifest())["opencode-claude-auth"],M=j.plugin,O=Array.isArray(M)?M.filter((e=>!A(e))):[]
1516
1550
  P&&this.applyAuthPluginPin(O,P),O.length>0?j.plugin=O:delete j.plugin,await r.writeFile(h,JSON.stringify(j,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:h})
1517
1551
  try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),s=c.join(e.homedir(),".alchemy_lab","opencode","config"),o=c.join(s,"opencode.json")
1518
1552
  let n=null
@@ -1635,12 +1669,12 @@ if(i.ok){const e=await i.json()
1635
1669
  return e.data?.id||e.id}s.info(`Project registration failed (HTTP ${i.status})`)}catch(e){s.info("Project registration failed",{error:(0,b.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
1636
1670
  const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),m=e(1253),f=e(8984),y=e(2366),v=e(3962),w=e(1187),b=e(8824),I=e(6800),E=e(4139);(async function(){23===parseInt(process.version.replace("v","").split(".")[0],10)&&(console.error(`\n\u274c Node.js ${process.version} is not supported.`),console.error(" AlchemyLab requires Node.js 22 or 24+ (Node 23 has a broken sqlite API)."),console.error("\n Switch with: nvm use 22 or nvm use 24\n"),process.exit(1))
1637
1671
  const i=process.argv.slice(2)
1638
- let C
1639
- const S=i.indexOf("--port");-1!==S&&i[S+1]&&(C=parseInt(i[S+1],10),isNaN(C)&&(console.error("\u274c Invalid port number"),process.exit(1)))
1640
- const k=i.filter(((e,t)=>"--port"!==e&&(-1===S||t!==S+1)))
1672
+ let S
1673
+ const C=i.indexOf("--port");-1!==C&&i[C+1]&&(S=parseInt(i[C+1],10),isNaN(S)&&(console.error("\u274c Invalid port number"),process.exit(1)))
1674
+ const k=i.filter(((e,t)=>"--port"!==e&&(-1===C||t!==C+1)))
1641
1675
  if(0===k.length)return void await async function(r={}){const i=process.cwd(),l=(0,h.join)(i,E.ALCHEMY_DIR_NAME)
1642
1676
  await(0,u.mkdir)(l,{recursive:!0})
1643
- const C=(0,h.join)(l,"server.log"),{createLogger:S}=await Promise.resolve().then(e.t.bind(e,2782,23)),k=S({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:C,baseDir:i}),T=await a(k),_=new m.TokenManager(k,i,{remoteServerUrl:T,allowInteractiveAuth:!1})
1677
+ const S=(0,h.join)(l,"server.log"),{createLogger:C}=await Promise.resolve().then(e.t.bind(e,2782,23)),k=C({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:S,baseDir:i}),T=await a(k),_=new m.TokenManager(k,i,{remoteServerUrl:T,allowInteractiveAuth:!1})
1644
1678
  await _.initialize()
1645
1679
  let j=await _.getToken()
1646
1680
  if(!j?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
@@ -1689,7 +1723,7 @@ u=setInterval((async()=>{if(!y)try{if(await _.hasValidToken())return
1689
1723
  y=!0,console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
1690
1724
  const e=await _.startAuthenticationFlow()
1691
1725
  if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(_)){const e=await _.getToken()
1692
- e?.token&&(A=e.token,D.updateAuthToken(A)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){k.warn("Token watchdog check failed",{error:(0,b.getErrorMessage)(e)})}finally{y=!1}}),3e5),await m.start(g)}catch(e){k.error("Failed to start server",{error:(0,b.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,b.getErrorMessage)(e)),process.exit(1)}}({port:C})
1726
+ e?.token&&(A=e.token,D.updateAuthToken(A)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){k.warn("Token watchdog check failed",{error:(0,b.getErrorMessage)(e)})}finally{y=!1}}),3e5),await m.start(g)}catch(e){k.error("Failed to start server",{error:(0,b.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,b.getErrorMessage)(e)),process.exit(1)}}({port:S})
1693
1727
  const T=k[0].toLowerCase()
1694
1728
  switch("--help"!==T&&"-h"!==T||(r(),process.exit(0)),"--version"!==T&&"-v"!==T||(console.log("alchemy v"+t()),process.exit(0)),T){case"serve":await async function(e={}){const r=process.cwd()
1695
1729
  s(r)||(console.log("\u{1f4c1} Current directory is not an AlchemyLab project."),console.log(""),console.log("\u{1f4a1} Run `alchemy` to initialize this project."),console.log(""),process.exit(1))
@@ -1703,7 +1737,7 @@ const h=e.port||await(0,I.findAvailablePort)(3456),g=new y.AlchemyActionServer(o
1703
1737
  try{await g.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${g.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
1704
1738
  let e=!1
1705
1739
  const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
1706
- process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,b.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,b.getErrorMessage)(e)),process.exit(1)}}({port:C})
1740
+ process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,b.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,b.getErrorMessage)(e)),process.exit(1)}}({port:S})
1707
1741
  break
1708
1742
  case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),r=await a(t),s=new m.TokenManager(t,e,{remoteServerUrl:r,allowInteractiveAuth:!1})
1709
1743
  await s.initialize(),await s.hasValidToken()?await s.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()