alchemylab 1.1.0 → 1.2.0

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
@@ -10,14 +10,14 @@ try{const e=await i.post(`/api/projects/${c}/requests/${o}/validate-completion`,
10
10
  if(!e.success||!e.data)return{success:!1,message:"Server validation failed: "+(e.data?.reason||"Unknown error"),error:"Validation failed"}
11
11
  const t=e.data
12
12
  if(!t.canComplete){let e="Cannot finalize request: "+t.reason
13
- return t.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",t.blockedByTasks.forEach(((t,s)=>{e+=`${s+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:t.reason}}const l=await i.get(`/api/projects/${c}/requests/${o}`),d=l?.data,u="requests/"+o,g=(0,s.simpleGit)(a),h=await g.status(),p=await g.branch(),f=p.all.includes("main")?"main":"master"
14
- if(h.current!==f&&await g.checkout(f),!p.all.includes(u))return{success:!1,message:`Branch '${u}' not found. The request may not have any file changes to merge.`,error:"Branch not found"}
13
+ return t.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",t.blockedByTasks.forEach(((t,s)=>{e+=`${s+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:t.reason}}const l=await i.get(`/api/projects/${c}/requests/${o}`),d=l?.data,u="requests/"+o,h=(0,s.simpleGit)(a),g=await h.status(),p=await h.branch(),f=p.all.includes("main")?"main":"master"
14
+ if(g.current!==f&&await h.checkout(f),!p.all.includes(u))return{success:!1,message:`Branch '${u}' not found. The request may not have any file changes to merge.`,error:"Branch not found"}
15
15
  let m,y=!1
16
16
  try{const e=d?.title?`Merge ${u}-${d.title}`:"Merge "+u
17
- await g.merge([u,"--no-ff","-m",e])
18
- const t=await g.log({maxCount:1})
17
+ await h.merge([u,"--no-ff","-m",e])
18
+ const t=await h.log({maxCount:1})
19
19
  m=t.latest?.hash,n.info("Git merge completed",{requestId:o,mergeCommit:m?.substring(0,8)})}catch(e){const t=(0,r.getErrorMessage)(e)
20
- if(t.toLowerCase().includes("conflict")){try{await g.merge(["--abort"])}catch(e){n.error("CRITICAL: git merge --abort failed \u2014 repo may be in inconsistent state",{requestId:o,error:e instanceof Error?e.message:e+""})}return{success:!1,message:`Merge conflicts detected for request ${o}. Please resolve conflicts before finalizing.`,error:"Merge conflicts",data:{hasConflicts:!0}}}return{success:!1,message:"Merge failed: "+t,error:t}}try{await g.deleteLocalBranch(u,!0),y=!0}catch{}try{await i.post(`/api/projects/${c}/requests/${o}/finalize-completion`,{summary:"Finalized request "+o,acceptanceEvidence:[{evidence:"Finalized via GUI after review"}],mergeResult:{mergeCommitHash:m||"unknown",cleanedUp:y,method:"action-server-merge"}})}catch(e){const t=(0,r.getErrorMessage)(e)
20
+ if(t.toLowerCase().includes("conflict")){try{await h.merge(["--abort"])}catch(e){n.error("CRITICAL: git merge --abort failed \u2014 repo may be in inconsistent state",{requestId:o,error:e instanceof Error?e.message:e+""})}return{success:!1,message:`Merge conflicts detected for request ${o}. Please resolve conflicts before finalizing.`,error:"Merge conflicts",data:{hasConflicts:!0}}}return{success:!1,message:"Merge failed: "+t,error:t}}try{await h.deleteLocalBranch(u,!0),y=!0}catch{}try{await i.post(`/api/projects/${c}/requests/${o}/finalize-completion`,{summary:"Finalized request "+o,acceptanceEvidence:[{evidence:"Finalized via GUI after review"}],mergeResult:{mergeCommitHash:m||"unknown",cleanedUp:y,method:"action-server-merge"}})}catch(e){const t=(0,r.getErrorMessage)(e)
21
21
  return n.error("CRITICAL: Merge succeeded but server finalization failed",{requestId:o,mergeCommitHash:m,error:t}),{success:!1,message:`Code was merged (commit: ${m?.substring(0,8)}) but server finalization failed: ${t}`,error:t,data:{mergeCommitHash:m,partialSuccess:!0}}}return{success:!0,message:`Successfully finalized request ${o}: ${d?.title||o}`,data:{mergeCommitHash:m,branchDeleted:y}}}catch(e){const t=(0,r.getErrorMessage)(e)
22
22
  return n.error("finalize-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to finalize request: "+t,error:t}}}
23
23
  const s=t(3807),r=t(8824)},857(module){"use strict"
@@ -116,30 +116,27 @@ return this.logger.info("Token validation successful",{status:e.status||"unknown
116
116
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.SessionSpawnService=void 0
117
117
  const s=t(9896),r=t(6928),o=t(2424),n=t(8824),i=t(5671),a=t(8955)
118
118
  exports.SessionSpawnService=class{constructor(e){this.deps=e}getOpenCodeServerUrl(e){try{const t=(0,r.join)(e,".alchemy_lab","opencode.json"),o=(0,s.readFileSync)(t,"utf8"),n=JSON.parse(o)
119
- if(n.port)return"http://127.0.0.1:"+n.port}catch(e){this.deps.logger.debug("Could not read opencode.json for server URL",{error:(0,n.getErrorMessage)(e)})}return null}async prepareSession(e){const{agent:t,prompt:s,projectPath:r,requestId:i,parentSessionId:a,specialization:c,callerAgentType:l}=e,{logger:d}=this.deps,u=this.deps.projectId,g=this.getOpenCodeServerUrl(r)
120
- if(!g)throw Error("Cannot prepare session: OpenCode server not running (no .alchemy_lab/opencode.json)")
121
- const h=e.title?e.title:i?`[${i}] ${s.slice(0,40)}${s.length>40?"...":""}`:`${s.slice(0,50)}${s.length>50?"...":""}`,p={agentType:t}
119
+ if(n.port)return"http://127.0.0.1:"+n.port}catch(e){this.deps.logger.debug("Could not read opencode.json for server URL",{error:(0,n.getErrorMessage)(e)})}return null}async prepareSession(e){const{agent:t,prompt:s,projectPath:r,requestId:i,parentSessionId:a,specialization:c,callerAgentType:l}=e,{logger:d}=this.deps,u=this.deps.projectId,h=this.getOpenCodeServerUrl(r)
120
+ if(!h)throw Error("Cannot prepare session: OpenCode server not running (no .alchemy_lab/opencode.json)")
121
+ const g=e.title?e.title:i?`[${i}] ${s.slice(0,40)}${s.length>40?"...":""}`:`${s.slice(0,50)}${s.length>50?"...":""}`,p={agentType:t}
122
122
  i&&(p.requestId=i),c&&(p.specialization=c),l&&(p.callerAgentType=l)
123
- const f=await fetch(g+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:h,...a?{parentID:a}:{},metadata:p})})
123
+ const f=await fetch(h+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:g,...a?{parentID:a}:{},metadata:p})})
124
124
  if(!f.ok)throw Error("Failed to create session in OpenCode: "+f.status)
125
125
  const m=await f.json()
126
126
  if(!m.id)throw Error("Session created in OpenCode but no ID returned")
127
127
  const y=m.id
128
- d.info("Pre-created OpenCode session",{sessionId:y,title:h,agent:t})
129
- const v=this.registerSession({sessionId:y,parentSessionId:a,projectId:u,agent:t,title:h,specialization:c,callerAgentType:l,requestId:i}),w=this.resolveModel(t,r),C=i?(0,o.ensureRequestWorktree)(r,i,d).catch((e=>{d.warn("Pre-spawn worktree creation failed (non-fatal)",{requestId:i,error:(0,n.getErrorMessage)(e)})})):Promise.resolve(),[,I]=await Promise.all([v,w,C])
130
- return{sessionId:y,model:I,serverUrl:g,initialBranch:i?"requests/"+i:void 0}}async registerSession(e){const{sessionId:t,parentSessionId:s,projectId:r,agent:o,title:i,specialization:a,callerAgentType:c,requestId:l}=e,{apiClient:d,logger:u}=this.deps
131
- try{let e,g
128
+ d.info("Pre-created OpenCode session",{sessionId:y,title:g,agent:t})
129
+ const v=this.registerSession({sessionId:y,parentSessionId:a,projectId:u,agent:t,title:g,specialization:c,callerAgentType:l,requestId:i}),w=this.resolveModel(t,r),k=i?(0,o.ensureRequestWorktree)(r,i,d).catch((e=>{d.warn("Pre-spawn worktree creation failed (non-fatal)",{requestId:i,error:(0,n.getErrorMessage)(e)})})):Promise.resolve(),[,I]=await Promise.all([v,w,k])
130
+ return{sessionId:y,model:I,serverUrl:h,initialBranch:i?"requests/"+i:void 0}}async registerSession(e){const{sessionId:t,parentSessionId:s,projectId:r,agent:o,title:i,specialization:a,callerAgentType:c,requestId:l}=e,{apiClient:d,logger:u}=this.deps
131
+ try{let e,h
132
132
  if(s)try{const t=await d.post(`/api/projects/${r}/cli-sessions`,{externalSessionId:s,providerId:"opencode",status:"active",agentType:c||void 0}),o=t?.data||t
133
- e=o?.id,g=o?.workflowId,u.debug("Registered parent session",{parentSessionId:s,parentDbId:e,parentWorkflowId:g})}catch(e){u.warn("Failed to register parent session",{error:(0,n.getErrorMessage)(e)})}if(await d.post(`/api/projects/${r}/cli-sessions`,{externalSessionId:t,providerId:"opencode",parentSessionId:e||null,title:i,status:"active",agentType:o,specialization:a,workflowId:g||void 0}),u.info("Registered session in alchemy DB",{sessionId:t,agent:o,parentSessionId:s}),l){try{await d.post(`/api/projects/${r}/session-request-associations`,{opencodeSessionId:t,requestLocalId:l}),u.info("Created session-request association",{sessionId:t,requestId:l})}catch(e){u.warn("Failed to create session-request association",{error:(0,n.getErrorMessage)(e)})}if("coder"===o)try{await d.patch(`/api/projects/${r}/requests/${l}`,{cliOrigin:{providerId:"opencode",sessionId:t,createdAt:(new Date).toISOString()}}),u.debug("Updated request cliOrigin",{sessionId:t,requestId:l})}catch(e){u.warn("Failed to update request cliOrigin",{error:(0,n.getErrorMessage)(e)})}}}catch(e){u.warn("Failed to register session in alchemy DB",{error:(0,n.getErrorMessage)(e)})}}async resolveModel(e,t){const{logger:s}=this.deps
134
- let r
133
+ e=o?.id,h=o?.workflowId,u.debug("Registered parent session",{parentSessionId:s,parentDbId:e,parentWorkflowId:h})}catch(e){u.warn("Failed to register parent session",{error:(0,n.getErrorMessage)(e)})}if(await d.post(`/api/projects/${r}/cli-sessions`,{externalSessionId:t,providerId:"opencode",parentSessionId:e||null,title:i,status:"active",agentType:o,specialization:a,workflowId:h||void 0}),u.info("Registered session in alchemy DB",{sessionId:t,agent:o,parentSessionId:s}),l){try{await d.post(`/api/projects/${r}/session-request-associations`,{opencodeSessionId:t,requestLocalId:l}),u.info("Created session-request association",{sessionId:t,requestId:l})}catch(e){u.warn("Failed to create session-request association",{error:(0,n.getErrorMessage)(e)})}if("coder"===o)try{await d.patch(`/api/projects/${r}/requests/${l}`,{cliOrigin:{providerId:"opencode",sessionId:t,createdAt:(new Date).toISOString()}}),u.debug("Updated request cliOrigin",{sessionId:t,requestId:l})}catch(e){u.warn("Failed to update request cliOrigin",{error:(0,n.getErrorMessage)(e)})}}}catch(e){u.warn("Failed to register session in alchemy DB",{error:(0,n.getErrorMessage)(e)})}}async resolveModel(e,t){const{logger:s}=this.deps,r=this.fetchProjectDefaultModel()||void 0
134
+ let o
135
135
  try{const s=await this.fetchAgentConfig(e)
136
- if(s){const e=s.preferredProvider||"inherit",o=s.preferredModelTier||"same",n=(0,i.resolveAgentModel)(t,e,o)
137
- n.resolved&&(r=n.fullModel)}else{const s=(0,a.getBuiltInAgentConfig)(e)
138
- if(s){const e=(0,i.resolveAgentModel)(t,s.preferredProvider||"inherit",s.preferredModelTier||"same")
139
- e.resolved&&(r=e.fullModel)}}}catch(t){s.debug("Error resolving agent model",{agent:e,error:(0,n.getErrorMessage)(t)})}const o=this.fetchProjectDefaultModel()
140
- if(o)if(r){const t=await this.fetchAgentConfig(e)
141
- "same"===(t?.preferredModelTier||"same")&&(r=o)}else r=o
142
- return r}async fetchAgentConfig(e){try{const t=await this.deps.apiClient.get(`/api/projects/${this.deps.projectId}/agents`)
136
+ if(s){const e=s.preferredProvider||"inherit",n=s.preferredModelTier||"same",a=(0,i.resolveAgentModel)(t,e,n,r)
137
+ a.resolved&&(o=a.fullModel)}else{const s=(0,a.getBuiltInAgentConfig)(e)
138
+ if(s){const e=(0,i.resolveAgentModel)(t,s.preferredProvider||"inherit",s.preferredModelTier||"same",r)
139
+ e.resolved&&(o=e.fullModel)}}}catch(t){s.debug("Error resolving agent model",{agent:e,error:(0,n.getErrorMessage)(t)})}return!o&&r&&(o=r),o}async fetchAgentConfig(e){try{const t=await this.deps.apiClient.get(`/api/projects/${this.deps.projectId}/agents`)
143
140
  return(t?.data||t||[]).find((t=>t.name===e))||null}catch{return null}}fetchProjectDefaultModel(){return this.deps.getDefaultModel()||null}}},1943(module){"use strict"
144
141
  module.exports=require("fs/promises")},2096(e,exports,t){"use strict"
145
142
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
@@ -161,20 +158,39 @@ try{const e=await r.default.readFile(i,"utf-8")
161
158
  c=JSON.parse(e)}catch(e){}c[e]={preset:t,disabledTools:s||[],allowedAlchemyTools:n||[],updatedAt:(new Date).toISOString()},await r.default.writeFile(i,JSON.stringify(c,null,2),"utf-8"),this.logger.info("User tool preferences saved",{clientType:e,prefsFile:i})}}},2366(e,exports,t){"use strict"
162
159
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
163
160
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
164
- const r=s(t(7252)),o=s(t(8577)),n=t(4192),i=t(1152),a=s(t(6928)),c=s(t(9896)),l=s(t(857)),d=t(5607),u=t(8984),g=t(8524),h=t(8824),p=t(2096),f=t(7210),m=t(2928),y=t(1292),v=t(5317)
165
- class w{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.settingsSyncCounter=0,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
161
+ const r=s(t(7252)),o=s(t(8577)),n=t(4192),i=t(1152),a=s(t(6928)),c=s(t(9896)),l=s(t(857)),d=t(5607),u=t(8984),h=t(8524),g=t(8824),p=t(2096),f=t(7210),m=t(2928),y=t(6563),v=t(1292),w=t(5317)
162
+ class k{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,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.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
166
163
  const s=(0,i.createMetricsCollector)(!1)
167
- this.apiClient=new n.BaseApiClient(t.remoteServerUrl,e,s,t.authToken,{timeout:35e3}),this.configurer=new u.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new g.CLIBridgeWebSocket(e),(0,g.setCLIBridge)(this.cliBridge),this.hookExecutor=new f.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,r.default)()
164
+ this.apiClient=new n.BaseApiClient(t.remoteServerUrl,e,s,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 f.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,r.default)()
168
165
  return e.use((0,o.default)()),e.use(r.default.json({limit:"10mb"})),e.get("/health",this.handleHealth.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)),e.get("/api/event-hooks/by-event/:eventType",this.handleEventHooksProxy.bind(this)),e.post("/api/hook-triggers",this.handleHookTriggersProxy.bind(this)),e.get("/api/hook-iterations/:sessionId/:hookId",this.handleGetHookIteration.bind(this)),e.post("/api/hook-iterations/:sessionId/:hookId/increment",this.handleIncrementHookIteration.bind(this)),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:s}=e.params,r=this.projectId
169
166
  if(!r)return void t.status(400).json({success:!1,error:"No project"})
170
167
  const o=await this.apiClient.get(`/api/projects/${r}/cli-sessions/external/opencode/${encodeURIComponent(s)}/ancestors`),n=(o?.data||o||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title})))
171
- t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:s,sessionId:r,variables:o,timing:n}=e.body
168
+ t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:s,sessionId:r,variables:o,timing:n}=e.body
172
169
  if(!s)return void t.status(400).json({success:!1,error:"eventType is required"})
173
170
  const i=await this.hookExecutor.processEvent({eventType:s,sessionId:r||"",variables:o||{},timing:n})
174
- t.json({success:!0,data:i})}catch(e){this.logger.error("Hook event processing failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.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.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.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()})}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 s=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
171
+ 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/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.get("/api/timer-hooks/status",((e,t)=>{const s=this.timerHookScheduler?.getStatus()??{running:!1,trackedHooks:0,inFlight:[],hooks:[]}
172
+ t.json({success:!0,data:s})})),e.post("/api/timer-hooks/:hookId/pause",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
173
+ const s=this.timerHookScheduler.pauseHook(e.params.hookId)
174
+ t.json({success:s})})),e.post("/api/timer-hooks/:hookId/resume",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
175
+ const s=this.timerHookScheduler.resumeHook(e.params.hookId)
176
+ t.json({success:s})})),e.post("/api/timer-hooks/:hookId/stop",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
177
+ const s=this.timerHookScheduler.stopHook(e.params.hookId)
178
+ t.json({success:s})})),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,s)=>{try{const{execSync:e}=await Promise.resolve().then(t.t.bind(t,5317,23)),r=e("npm root -g",{encoding:"utf8"}).trim()
179
+ try{c.default.accessSync(r,c.default.constants.W_OK)}catch{return void s.json({success:!1,error:"Permission denied. Run manually: sudo npm update -g alchemylab"})}const{exec:o}=await Promise.resolve().then(t.t.bind(t,5317,23))
180
+ s.json({success:!0,message:"Update started. The CLI will restart when complete."}),o("npm update -g alchemylab",{timeout:12e4},((e,t,s)=>{e?this.logger.error("Self-update failed",{error:e.message,stderr:s}):(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){s.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()})}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 s=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
175
181
  try{if(!c.default.existsSync(s))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
176
182
  const e=c.default.readFileSync(s,"utf-8"),r=JSON.parse(e)
177
- t.json({port:r.port,pid:r.pid,startedAt:r.startedAt,baseUrl:"http://localhost:"+r.port,directory:this.options.projectPath,proxyEnabled:!0})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,h.getErrorMessage)(e)})}}async handleSyncAgents(e,s){try{const e=this.readProjectConfig()
183
+ t.json({port:r.port,pid:r.pid,startedAt:r.startedAt,baseUrl:"http://localhost:"+r.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)}))))}async resyncAgentMarkdowns(){if(!this.projectId)return
184
+ const e=await this.configurer.fetchAgentsFromRemote(this.projectId)
185
+ if(!e?.length)return
186
+ const t=await this.configurer.fetchProjectFromRemote(this.projectId),s=t?.metadata?.globalSystemPrompt,r=a.default.join(this.options.projectPath,".opencode","agent")
187
+ await c.default.promises.mkdir(r,{recursive:!0})
188
+ const o=this.cachedDefaultModel||"anthropic/claude-opus-4-5"
189
+ for(const t of e){if(!t.enabled)continue
190
+ const e=this.configurer.generateAgentMarkdown(t,"alchemy",o,s)
191
+ await c.default.promises.writeFile(a.default.join(r,t.name+".md"),e,"utf8")}this.logger.info("Re-synced agent markdowns after model change",{model:o,count:e.length})
192
+ try{const e=new v.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,getDefaultModel:()=>this.cachedDefaultModel||void 0}).getOpenCodeServerUrl(this.options.projectPath)
193
+ 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,s){try{const e=this.readProjectConfig()
178
194
  if(!e?.id)return void s.status(400).json({success:!1,error:"No project configuration found"})
179
195
  this.logger.info("Syncing agents from remote",{projectId:e.id})
180
196
  const r=await this.configurer.fetchAgentsFromRemote(e.id)
@@ -187,41 +203,41 @@ const i=a.default.join(this.options.projectPath,".opencode","agent")
187
203
  await c.default.promises.mkdir(i,{recursive:!0})
188
204
  let l=0
189
205
  for(const e of r){if(!e.enabled)continue
190
- const t="alchemy",s=this.configurer.generateAgentMarkdown(e,t,"anthropic/claude-sonnet-4-20250514",n),r=a.default.join(i,e.name+".md")
191
- await c.default.promises.writeFile(r,s,"utf8"),l++,this.logger.info("Synced agent file",{name:e.name,path:r,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
206
+ const t="alchemy",s=this.cachedDefaultModel||"anthropic/claude-opus-4-5",r=this.configurer.generateAgentMarkdown(e,t,s,n),o=a.default.join(i,e.name+".md")
207
+ await c.default.promises.writeFile(o,r,"utf8"),l++,this.logger.info("Synced agent file",{name:e.name,path:o,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
192
208
  if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t),n=o?.metadata?.bashToolAgents||{}
193
209
  if(s.agent){for(const e of r)e.enabled&&s.agent[e.name]&&e.name in n&&(s.agent[e.name].tools.bash=!!n[e.name])
194
- c.default.writeFileSync(e,JSON.stringify(s,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,h.getErrorMessage)(e)})}let d=!1
210
+ c.default.writeFileSync(e,JSON.stringify(s,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 d=!1
195
211
  try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
196
212
  if(c.default.existsSync(e)){const s=c.default.readFileSync(e,"utf-8"),r=JSON.parse(s)
197
- if(r.pid)try{process.kill(r.pid,"SIGTERM"),this.logger.info("Killed existing OpenCode process",{pid:r.pid}),await new Promise((e=>setTimeout(e,500)))}catch(e){"ESRCH"!==e?.code&&this.logger.warn("Could not kill OpenCode",{error:(0,h.getErrorMessage)(e)})}const{resolveServeCommand:o}=await Promise.resolve().then(t.bind(t,9068)),n=o(),i=(0,v.spawn)(n.command,[...n.prependArgs,"--port","0"],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),a=new Promise((e=>{let t=""
213
+ if(r.pid)try{process.kill(r.pid,"SIGTERM"),this.logger.info("Killed existing OpenCode process",{pid:r.pid}),await new Promise((e=>setTimeout(e,500)))}catch(e){"ESRCH"!==e?.code&&this.logger.warn("Could not kill OpenCode",{error:(0,g.getErrorMessage)(e)})}const o=r.port||0,{resolveServeCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,w.spawn)(i.command,[...i.prependArgs,"--port",o+""],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),l=new Promise((e=>{let t=""
198
214
  const s=setTimeout((()=>{this.logger.warn("Timeout waiting for OpenCode port, killing process")
199
- try{i.kill("SIGTERM")}catch{}e(null)}),1e4)
200
- i.stdout?.on("data",(r=>{t+=r.toString()
215
+ try{a.kill("SIGTERM")}catch{}e(null)}),1e4)
216
+ a.stdout?.on("data",(r=>{t+=r.toString()
201
217
  const o=t.match(/listening on http:\/\/[^:]+:(\d+)/)
202
- o&&(clearTimeout(s),i.unref(),e({port:parseInt(o[1],10),pid:i.pid}))})),i.on("error",(()=>{clearTimeout(s)
203
- try{i.kill("SIGTERM")}catch{}e(null)}))})),l=await a
204
- l&&(this.trackChildPid(l.pid),c.default.writeFileSync(e,JSON.stringify({port:l.port,pid:l.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:l.port,pid:l.pid}),this.setOpenCodePort(l.port),d=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,h.getErrorMessage)(e)})}s.json({success:!0,message:`Synced ${l} agent(s) to local files${d?" - OpenCode reloading":""}`,count:l,restarted:d})}catch(e){this.logger.error("Failed to sync agents",{error:(0,h.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
218
+ o&&(clearTimeout(s),a.unref(),e({port:parseInt(o[1],10),pid:a.pid}))})),a.on("error",(()=>{clearTimeout(s)
219
+ try{a.kill("SIGTERM")}catch{}e(null)}))})),u=await l
220
+ u&&(this.trackChildPid(u.pid),c.default.writeFileSync(e,JSON.stringify({port:u.port,pid:u.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:u.port,pid:u.pid}),this.setOpenCodePort(u.port),d=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,g.getErrorMessage)(e)})}s.json({success:!0,message:`Synced ${l} agent(s) to local files${d?" - OpenCode reloading":""}`,count:l,restarted:d})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
205
221
  if(!r)return void s.status(400).json({success:!1,error:"requestId is required"})
206
222
  if(!/^req\d+$/.test(r))return void s.status(400).json({success:!1,error:"Invalid requestId format"})
207
223
  this.logger.info("Generating journal for request",{requestId:r})
208
- const o=`Generate a journal entry for request ${r}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${r}\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-${r}.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,v.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
209
- a.pid&&this.trackChildPid(a.pid),a.unref(),s.json({success:!0,message:"Journal generation started for "+r})}catch(e){this.logger.error("Failed to generate journal",{error:(0,h.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleEventHooksProxy(e,t){try{const{eventType:s}=e.params,{sessionId:r}=e.query,o=this.projectId
224
+ const o=`Generate a journal entry for request ${r}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${r}\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-${r}.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,w.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
225
+ a.pid&&this.trackChildPid(a.pid),a.unref(),s.json({success:!0,message:"Journal generation started for "+r})}catch(e){this.logger.error("Failed to generate journal",{error:(0,g.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleEventHooksProxy(e,t){try{const{eventType:s}=e.params,{sessionId:r}=e.query,o=this.projectId
210
226
  if(!o)return void t.status(400).json({success:!1,error:"Project not registered"})
211
227
  this.logger.info("Proxying event hooks request",{projectId:o,eventType:s,sessionId:r})
212
228
  const n=r?"?sessionId="+encodeURIComponent(r):"",i=await this.apiClient.get(`/api/projects/${o}/event-hooks/by-event/${s}${n}`)
213
- t.json(i)}catch(e){this.logger.error("Failed to fetch event hooks",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleHookTriggersProxy(e,t){try{const s=this.projectId
229
+ t.json(i)}catch(e){this.logger.error("Failed to fetch event hooks",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleHookTriggersProxy(e,t){try{const s=this.projectId
214
230
  if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
215
231
  const{hookId:r,hookName:o,eventType:n,executionMode:i,agent:a}=e.body
216
232
  this.logger.debug("Proxying hook trigger notification",{projectId:s,hookName:o,eventType:n})
217
233
  const c=await this.apiClient.post(`/api/projects/${s}/hook-triggers`,{hookId:r,hookName:o,eventType:n,executionMode:i,agent:a})
218
- t.json(c)}catch(e){this.logger.debug("Failed to proxy hook trigger",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetHookIteration(e,t){try{const s=this.readProjectConfig()
234
+ t.json(c)}catch(e){this.logger.debug("Failed to proxy hook trigger",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGetHookIteration(e,t){try{const s=this.readProjectConfig()
219
235
  if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
220
236
  const{sessionId:r,hookId:o}=e.params,n=await this.apiClient.get(`/api/projects/${s.id}/hook-iterations/${r}/${o}`)
221
- t.json(n)}catch(e){this.logger.debug("Failed to get hook iteration",{error:(0,h.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleIncrementHookIteration(e,t){try{const s=this.readProjectConfig()
237
+ t.json(n)}catch(e){this.logger.debug("Failed to get hook iteration",{error:(0,g.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleIncrementHookIteration(e,t){try{const s=this.readProjectConfig()
222
238
  if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
223
239
  const{sessionId:r,hookId:o}=e.params,n=await this.apiClient.post(`/api/projects/${s.id}/hook-iterations/${r}/${o}/increment`,{})
224
- t.json(n)}catch(e){this.logger.debug("Failed to increment hook iteration",{error:(0,h.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleDirectReport(e,t){try{const s=this.projectId
240
+ t.json(n)}catch(e){this.logger.debug("Failed to increment hook iteration",{error:(0,g.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleDirectReport(e,t){try{const s=this.projectId
225
241
  if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
226
242
  const{sessionId:r,requestId:o,summary:n,status:i}=e.body
227
243
  if(!r)return void t.status(400).json({success:!1,error:"sessionId is required"})
@@ -229,26 +245,26 @@ this.logger.info("Direct report creation",{sessionId:r,requestId:o,status:i})
229
245
  const a=[]
230
246
  try{const e=await this.apiClient.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),t=e?.data||e||[]
231
247
  let o=r
232
- 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:r,error:(0,h.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
248
+ 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:r,error:(0,g.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
233
249
  let c,l=n
234
250
  if(!l&&o)try{const e=await this.apiClient.get(`/api/projects/${s}/requests/${o}`),t=e?.data||e
235
251
  c=t?.title
236
252
  const r=t?.completionSummary
237
- r?l=r:c&&(l="Done: "+c)}catch(e){this.logger.debug("Could not fetch request details for summary",{requestId:o,error:(0,h.getErrorMessage)(e)})}l||(l="Completed request "+(o||"unknown"))
253
+ r?l=r: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"))
238
254
  const d=o||r
239
255
  if(this.directReportDedup.size>=1e3){const e=this.directReportDedup.keys().next().value
240
256
  void 0!==e&&this.directReportDedup.delete(e)}this.directReportDedup.has(d)||this.directReportDedup.set(d,new Set)
241
- const u=this.directReportDedup.get(d),g=a.filter((({ancestorId:e})=>!u.has(e)||(this.logger.info("Direct report dedup: already notified",{ancestorId:e,dedupKey:d}),!1))),p=await Promise.allSettled(g.map((async({ancestorId:e,childId:t})=>{const r=await this.apiClient.post(`/api/projects/${s}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),o=r?.data||r
257
+ 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 r=await this.apiClient.post(`/api/projects/${s}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),o=r?.data||r
242
258
  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}}))),f=[]
243
259
  for(const e of p)"fulfilled"===e.status&&f.push(e.value)
244
- for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,h.getErrorMessage)(e.reason)})
260
+ for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,g.getErrorMessage)(e.reason)})
245
261
  if(setTimeout((()=>{this.directReportDedup.delete(d)}),3e5),o)try{const e=await this.apiClient.get(`/api/projects/${s}/session-request-associations/by-session/${encodeURIComponent(r)}?activeOnly=true`),t=e?.data||e||[],o=(await Promise.allSettled(t.map((e=>this.apiClient.post(`/api/projects/${s}/session-request-associations/end`,{sessionId:r,requestLocalId:e.requestLocalId}))))).filter((e=>"rejected"===e.status)).length
246
- this.logger.info("Released request associations",{sessionId:r,count:t.length,failCount:o})}catch(e){this.logger.warn("Failed to release associations",{sessionId:r,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,data:{notifiedSessions:f,report:f[0]?{messageCode:f[0].messageCode}:void 0,parentExternalSessionId:a[0]?.ancestorId||null}})}catch(e){this.logger.error("Direct report creation failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetSessionAgentType(e,t){try{const{sessionId:s}=e.params,r=this.readProjectConfig()
262
+ this.logger.info("Released request associations",{sessionId:r,count:t.length,failCount:o})}catch(e){this.logger.warn("Failed to release associations",{sessionId:r,error:(0,g.getErrorMessage)(e)})}t.json({success:!0,data:{notifiedSessions:f,report:f[0]?{messageCode:f[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:s}=e.params,r=this.readProjectConfig()
247
263
  if(r?.id)try{const e=await this.apiClient.get(`/api/projects/${r.id}/cli-sessions/external/opencode/${encodeURIComponent(s)}`),o=e?.data||e
248
- if(this.logger.info("Agent type DB lookup",{sessionId:s,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:s,error:(0,h.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:s},5e3)
249
- if(this.logger.info("Agent type bridge lookup",{sessionId:s,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:s,error:(0,h.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:s}),t.json({success:!0,agentType:null,source:"unknown"})}catch(e){this.logger.debug("Failed to get session agent type",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetSessionParent(e,t){try{const{sessionId:s}=e.params,r=this.readProjectConfig()
264
+ if(this.logger.info("Agent type DB lookup",{sessionId:s,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:s,error:(0,g.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:s},5e3)
265
+ if(this.logger.info("Agent type bridge lookup",{sessionId:s,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:s,error:(0,g.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:s}),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:s}=e.params,r=this.readProjectConfig()
250
266
  if(r?.id)try{const e=await this.apiClient.get(`/api/projects/${r.id}/cli-sessions/resolve/${encodeURIComponent(s)}`),o=e?.data?.parent?.externalSessionId||""
251
- return void t.json({success:!0,parentSessionId:o})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:s,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{const s=this.getOpenCodeUrl()
267
+ return void t.json({success:!0,parentSessionId:o})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:s,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 s=this.getOpenCodeUrl()
252
268
  if(!s)return void t.status(502).json({error:"OpenCode not running"})
253
269
  const r=e.path.replace(/^\//,""),o=e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):"",n=`${s}/${r}${o}`,i=e.headers.accept?.includes("text/event-stream")
254
270
  if(i){const s=new AbortController,r=()=>{this.proxyAbortControllers.delete(s)},o=(async()=>{try{const e=await fetch(n,{headers:{Accept:"text/event-stream"},signal:s.signal})
@@ -267,27 +283,27 @@ const t=this.getOpenCodeUrl()
267
283
  if(!t||t===s)throw e
268
284
  d=`${t}/${r}${o}`,this.logger.info("OpenCode proxy: retrying with fresh port",{oldUrl:s,newUrl:t}),l=await fetch(d,c)}if(204===l.status||"0"===l.headers.get("content-length"))return void t.status(l.status).end()
269
285
  const u=l.headers.get("content-type")||""
270
- let g
271
- try{g=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 g?t.send(g):t.json(g)}catch(s){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,h.getErrorMessage)(s),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,h.getErrorMessage)(s)})}}async handleSpawnSession(e,t){try{const{agent:s,prompt:r,requestId:o,parentSessionId:n,title:i,specialization:a,allowParallel:c,callerAgentType:l}=e.body
286
+ let h
287
+ 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(s){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,g.getErrorMessage)(s),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,g.getErrorMessage)(s)})}}async handleSpawnSession(e,t){try{const{agent:s,prompt:r,requestId:o,parentSessionId:n,title:i,specialization:a,allowParallel:c,callerAgentType:l}=e.body
272
288
  if(!s||!r)return void t.status(400).json({success:!1,error:"agent and prompt are required"})
273
289
  if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
274
- const d=this.projectId,u=new y.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:d,getDefaultModel:()=>this.cachedDefaultModel||void 0}),g=await u.prepareSession({agent:s,prompt:r,projectPath:this.options.projectPath,requestId:o,parentSessionId:n,title:i,specialization:a,allowParallel:c,callerAgentType:l})
275
- t.json({success:!0,data:g})}catch(e){this.logger.error("Spawn session failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetAgentByName(e,t){try{const s=this.readProjectConfig()
290
+ const d=this.projectId,u=new v.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:d,getDefaultModel:()=>this.cachedDefaultModel||void 0}),h=await u.prepareSession({agent:s,prompt:r,projectPath:this.options.projectPath,requestId:o,parentSessionId:n,title:i,specialization:a,allowParallel:c,callerAgentType:l})
291
+ t.json({success:!0,data:h})}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 handleGetAgentByName(e,t){try{const s=this.readProjectConfig()
276
292
  if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
277
293
  const{agentName:r}=e.params,o=await this.apiClient.get(`/api/projects/${s.id}/agents`),n=(o?.data||o||[]).find((e=>e.name===r))
278
294
  if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
279
- t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<w.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
295
+ 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<k.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
280
296
  const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
281
297
  try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
282
298
  const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
283
- return this.cachedProjectConfig={config:s,timestamp:Date.now()},s}catch(e){return this.logger.error("Failed to read project config",{error:(0,h.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e
299
+ return this.cachedProjectConfig={config:s,timestamp:Date.now()},s}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
284
300
  const t="http://127.0.0.1:"+e
285
- 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,h.getErrorMessage)(e)})))}async seedDefaultModelFromZen(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
301
+ 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)})
286
302
  if(!t.ok)return
287
303
  const s=await t.json(),r=s?.default||{},[o,n]=Object.entries(r)[0]||[]
288
304
  if(!o||!n)return
289
305
  const i=`${o}/${n}`
290
- this.cachedDefaultModel=i,this.hookExecutor.setDefaultModel(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,h.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
306
+ 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
291
307
  if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
292
308
  const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
293
309
  try{if(!c.default.existsSync(e))return null
@@ -323,13 +339,24 @@ const[r,o]=await Promise.all([fetch(s+"/session/status"),fetch(s+"/session")]),n
323
339
  let i,a
324
340
  if(o.ok){const t=(await o.json()).find((t=>t.id===e))
325
341
  i=t?.title}if(this.projectId)try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),s=t?.data||t
326
- s?.agentType&&(a=s.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,h.getErrorMessage)(e)}}}async register(){const e=this.readProjectConfig()
342
+ s?.agentType&&(a=s.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()
327
343
  if(!e?.id)throw Error("No project configuration found")
328
- this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId)
329
- const t={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:l.default.hostname(),platform:process.platform}
344
+ this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId),this.timerHookScheduler?.setProjectId(this.projectId)
345
+ try{const e=a.default.resolve(__dirname,"../../package.json"),t=JSON.parse(c.default.readFileSync(e,"utf8"))
346
+ this.clientVersion=t.version}catch{}try{const e=t(Object(function(){var e=Error("Cannot find module 'alchemylab-opencode/package.json'")
347
+ throw e.code="MODULE_NOT_FOUND",e}()))
348
+ this.opencodeVersion=e.version}catch{try{const e=process.env.ALCHEMY_OPENCODE_DIR
349
+ if(e){const t=JSON.parse(c.default.readFileSync(a.default.join(e,"package.json"),"utf8"))
350
+ this.opencodeVersion=t.version}}catch{}}const s={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:l.default.hostname(),platform:process.platform,clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}
330
351
  this.logger.info("Registering with remote",{projectId:this.projectId})
331
- const s=await this.apiClient.post("/api/servers/register",t)
332
- s.success&&s.data?.serverId?(this.serverId=s.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})):this.logger.warn("Registration failed, continuing without remote connection",{error:s.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{try{await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString()})}catch(e){this.logger.warn("Heartbeat failed",{error:(0,h.getErrorMessage)(e)})}}),3e4))}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,h.getErrorMessage)(e)})))}async connectToOpenCodeSSE(){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
352
+ const r=await this.apiClient.post("/api/servers/register",s)
353
+ if(r.success&&r.data?.serverId){this.serverId=r.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})
354
+ const e=r.data.versionWarnings
355
+ if(e&&e.length>0){const t=e.some((e=>"required"===e.severity))
356
+ 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`))
357
+ this.versionWarnings=e,t&&(this._pendingUpdateBlock=!0)}}else this.logger.warn("Registration failed, continuing without remote connection",{error:r.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(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
358
+ if(t&&t.length>0){if(t.map((e=>`${e.component}:${e.severity}`)).join(",")!==this.versionWarnings.map((e=>`${e.component}:${e.severity}`)).join(",")){for(const e of t)"required"===e.severity?(this.logger.error("[UPDATE REQUIRED] "+e.message),console.error(`\n\x1b[31m[UPDATE REQUIRED] ${e.message}\x1b[0m\n`)):(this.logger.warn("[UPDATE RECOMMENDED] "+e.message),console.warn(`\n\x1b[33m[UPDATE RECOMMENDED] ${e.message}\x1b[0m\n`))
359
+ this.versionWarnings=t}!t.some((e=>"required"===e.severity))&&!this._pendingUpdateBlock||this.updateBlocked||await this.enterUpdateMode()}else this.versionWarnings.length>0&&(this.versionWarnings=[])}catch(e){this.logger.warn("Heartbeat failed",{error:(0,g.getErrorMessage)(e)})}}),3e4))}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=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
333
360
  if(!c.default.existsSync(e))return this.logger.debug("SSE forwarder: opencode.json not found, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
334
361
  let t
335
362
  try{t=JSON.parse(c.default.readFileSync(e,"utf-8")).port}catch{return void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)}const s=`http://localhost:${t}/event`
@@ -343,17 +370,17 @@ o+=r.decode(s,{stream:!0})
343
370
  const n=o.split("\n")
344
371
  o=n.pop()||""
345
372
  for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6))
346
- this.sseEventBuffer.push({type:t.type||"unknown",data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,error:(0,h.getErrorMessage)(e)})))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,h.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,h.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
373
+ this.sseEventBuffer.push({type:t.type||"unknown",data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,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
347
374
  if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
348
375
  if(!t)return
349
376
  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,s=t?.id||""
350
377
  if(!s)return
351
378
  const r=t?.metadata
352
- r?.agentType&&this.projectId&&(this.logger.debug("SSE: session.created with alchemy metadata, ensuring registration",{sessionId:s,agentType:r.agentType}),this.apiClient.post(`/api/projects/${this.projectId}/cli-sessions`,{externalSessionId:s,providerId:"opencode",agentType:r.agentType,status:"active",specialization:r.specialization||void 0}).catch((e=>{this.logger.debug("SSE session registration (idempotent)",{sessionId:s,error:(0,h.getErrorMessage)(e)})}))),setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:s}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:s,variables:{sessionId:s}})}),500)}}async flushSSEEvents(){if(0===this.sseEventBuffer.length||!this.projectId)return
379
+ r?.agentType&&this.projectId&&(this.logger.debug("SSE: session.created with alchemy metadata, ensuring registration",{sessionId:s,agentType:r.agentType}),this.apiClient.post(`/api/projects/${this.projectId}/cli-sessions`,{externalSessionId:s,providerId:"opencode",agentType:r.agentType,status:"active",specialization:r.specialization||void 0}).catch((e=>{this.logger.debug("SSE session registration (idempotent)",{sessionId:s,error:(0,g.getErrorMessage)(e)})}))),setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:s}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:s,variables:{sessionId:s}})}),500)}}async flushSSEEvents(){if(0===this.sseEventBuffer.length||!this.projectId)return
353
380
  if(!this.hasRemoteEventConsumers)return void(this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500))
354
381
  const e=this.sseEventBuffer.splice(0)
355
- try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e})}catch(t){this.logger.debug("SSE event flush failed",{error:(0,h.getErrorMessage)(t),count:e.length}),this.sseEventBuffer.unshift(...e),this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500)}}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
356
- this.logger.debug("Long-poll error, retrying in 5s",{error:(0,h.getErrorMessage)(e)}),await new Promise((e=>setTimeout(e,5e3)))}}async pollOnce(){if(this.isPolling&&this.projectId)try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=30000`,t={"Content-Type":"application/json",Accept:"application/json"}
382
+ try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e})}catch(t){this.logger.debug("SSE event flush failed",{error:(0,g.getErrorMessage)(t),count:e.length}),this.sseEventBuffer.unshift(...e),this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500)}}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
383
+ 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"}
357
384
  this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
358
385
  const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
359
386
  t["X-Service-Secret"]=s
@@ -361,63 +388,67 @@ const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(35e3)})
361
388
  if(!r.ok)throw Error("Poll failed with status "+r.status)
362
389
  const o=await r.json(),n={success:!0,data:o.data||o}
363
390
  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
364
- if(void 0!==e&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e)),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:t})
391
+ if(void 0!==e&&this.updateDefaultModel(e),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:t})
365
392
  const e=new p.ProjectConfigService(this.logger)
366
393
  await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}if(n.success&&n.data?.actions?.length>0){const e=[],t=[]
367
394
  for(const s of n.data.actions)"opencode-proxy"===s.type||"run-tests"===s.type?e.push(s):t.push(s)
368
- for(const t of e)this.processAction(t).catch((e=>this.logger.warn("Proxy action failed",{actionId:t.id,error:(0,h.getErrorMessage)(e)})))
395
+ for(const t of e)this.processAction(t).catch((e=>this.logger.warn("Proxy action failed",{actionId:t.id,error:(0,g.getErrorMessage)(e)})))
369
396
  if(t.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
370
397
  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)
371
398
  if(e.success&&e.data?.metadata){const{autoJournal:t,defaultModel:s}=e.data.metadata
372
- if(void 0!==s&&(this.cachedDefaultModel=s,this.hookExecutor.setDefaultModel(s)),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed, syncing to local config",{oldValue:this.cachedAutoJournal,newValue:t})
399
+ if(void 0!==s&&this.updateDefaultModel(s),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed, syncing to local config",{oldValue:this.cachedAutoJournal,newValue:t})
373
400
  const e=new p.ProjectConfigService(this.logger)
374
- await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}}catch(e){this.logger.debug("Settings sync failed",{error:(0,h.getErrorMessage)(e)})}}async processAction(e){this.logger.info("Processing action",{actionId:e.id,type:e.type})
401
+ 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})
375
402
  const t=Date.now()
376
- try{const s={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e)},r=await(0,d.executeAction)(e.type,e.payload||{},s)
377
- await this.apiClient.post(`/api/actions/${e.id}/result`,{success:r.success,result:r.data,error:r.message||r.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:r.success,duration:Date.now()-t})}catch(s){const r=(0,h.getErrorMessage)(s)
403
+ try{const s={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e),cachedDefaultModel:this.cachedDefaultModel},r=await(0,d.executeAction)(e.type,e.payload||{},s)
404
+ await this.apiClient.post(`/api/actions/${e.id}/result`,{success:r.success,result:r.data,error:r.message||r.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:r.success,duration:Date.now()-t})}catch(s){const r=(0,g.getErrorMessage)(s)
378
405
  await this.apiClient.post(`/api/actions/${e.id}/result`,{success:!1,error:r,duration:Date.now()-t}),this.logger.error("Action failed",{actionId:e.id,type:e.type,error:r})}}async handleCLIBridgeCommand(e,t){try{const{action:s,params:r,timeout:o}=e.body
379
406
  if(!s)return void t.status(400).json({success:!1,error:"action is required"})
380
407
  if(["list-notifications","get-notification","respond-to-notification"].includes(s)){const e=await this.handleNotificationCommand(s,r||{})
381
408
  return void t.json(e)}if(["list-sessions","get-messages","send-message","create-session","get-session-status"].includes(s)){const e=await this.handleSessionCommand(s,r||{})
382
409
  return void t.json(e)}if(["list-requests","get-request"].includes(s)){const e=await this.handleRequestCommand(s,r||{})
383
410
  return void t.json(e)}const n=await this.cliBridge.sendCommand(s,r||{},o)
384
- t.json(n)}catch(e){t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleNotificationCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
411
+ 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"}
385
412
  const s=this.projectId
386
413
  try{switch(e){case"list-notifications":{const e={limit:t.limit||30}
387
414
  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/${s}/session-messages`,e)).data}}case"get-notification":return t.messageCode?{success:!0,data:(await this.apiClient.get(`/api/projects/${s}/session-messages/by-code/${encodeURIComponent(t.messageCode)}`)).data}:{success:!1,error:"messageCode is required"}
388
415
  case"respond-to-notification":{if(!t.messageCode)return{success:!1,error:"messageCode is required"}
389
416
  const e=t.messageCode
390
- return{success:!0,data:(await this.apiClient.post(`/api/projects/${s}/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,h.getErrorMessage)(e)}}}async handleRequestCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
417
+ return{success:!0,data:(await this.apiClient.post(`/api/projects/${s}/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"}
391
418
  const s=this.projectId
392
419
  try{switch(e){case"list-requests":{const e={}
393
420
  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/${s}/requests`,e)).data}}case"get-request":{if(!t.requestId)return{success:!1,error:"requestId is required"}
394
421
  const e=encodeURIComponent(t.requestId),[r,o]=await Promise.all([this.apiClient.get(`/api/projects/${s}/requests/${e}`),this.apiClient.get(`/api/projects/${s}/tasks`,{requestId:t.requestId})])
395
- return{success:!0,data:{...r.data,tasks:o.data||[]}}}default:return{success:!1,error:"Unknown request action: "+e}}}catch(e){return{success:!1,error:(0,h.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})
422
+ return{success:!0,data:{...r.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})
396
423
  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)}}))
397
424
  break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
398
- continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){this.logger.warn("Remote registration failed, continuing in local-only mode",{error:(0,h.getErrorMessage)(e)})}try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=5000`,t={"Content-Type":"application/json",Accept:"application/json"}
425
+ continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,g.getErrorMessage)(e)
426
+ 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"}
399
427
  this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
400
428
  const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
401
429
  t["X-Service-Secret"]=s
402
430
  const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
403
431
  if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
404
- void 0!==t?.defaultModel&&(this.cachedDefaultModel=t.defaultModel,this.hookExecutor.setDefaultModel(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,h.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog()}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,h.getErrorMessage)(t)})}}this.trackedChildPids.clear()
432
+ 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()}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()
405
433
  try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
406
434
  if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
407
- if(s.pid)try{process.kill(-s.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:s.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(s.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:s.pid,error:(0,h.getErrorMessage)(e)})}}}}catch{}}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new m.StuckSessionWatchdog({getOpenCodeUrl:()=>this.getOpenCodeUrl(),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 s=this.stuckSessionWatchdog?.getStuckSessions()??[]
435
+ if(s.pid)try{process.kill(-s.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:s.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(s.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:s.pid,error:(0,g.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new y.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()}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new m.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 s=this.stuckSessionWatchdog?.getStuckSessions()??[]
408
436
  t.json({success:!0,data:s})}handleTestInjectStuckSession(e,t){if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
409
437
  const{sessionId:s,title:r}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(s,r)
410
438
  this.logger.info("Test-injected stuck session",{sessionId:o.sessionId}),t.json({success:!0,data:o})}async handleRetryStuckSession(e,t){const{sessionId:s}=e.params
411
439
  if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
412
440
  const r=await this.stuckSessionWatchdog.retrySession(s)
413
- r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}async stop(){this.logger.info("Stopping AlchemyActionServer...")
414
- for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}if(this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.stopSSEEventForwarder(),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)
415
- this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=w,w.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
441
+ r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}async enterUpdateMode(){if(!this.updateBlocked){this.updateBlocked=!0,this.logger.error("Entering update-required mode \u2014 shutting down all services"),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\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("\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\x1b[0m")
442
+ for(const e of this.versionWarnings)console.error(`\x1b[31m ${e.message}\x1b[0m`)
443
+ 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.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval)
444
+ 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...")
445
+ for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}if(this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.timerHookScheduler?.stop(),this.stopSSEEventForwarder(),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)
446
+ this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=k,k.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
416
447
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ensureRequestWorktree=async function(e,t,i){const a="requests/"+t,c=(0,r.join)(e,n.ALCHEMY_DIR_NAME,"worktrees"),l=(0,r.join)(c,t)
417
448
  if((0,s.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
418
449
  const d=(0,o.simpleGit)(e);(0,s.mkdirSync)(c,{recursive:!0})
419
- try{await d.raw(["worktree","prune"])}catch{}const u=await d.branch(),g=u.all.includes(a)
420
- try{if(g)await d.raw(["worktree","add",l,a]),i.info("Pre-spawn: created worktree for existing branch",{requestId:t,branchName:a,worktreePath:l})
450
+ try{await d.raw(["worktree","prune"])}catch{}const u=await d.branch(),h=u.all.includes(a)
451
+ try{if(h)await d.raw(["worktree","add",l,a]),i.info("Pre-spawn: created worktree for existing branch",{requestId:t,branchName:a,worktreePath:l})
421
452
  else{const e=u.all.includes("main")?"main":"master"
422
453
  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 s=e instanceof Error?e.message:e+""
423
454
  i.warn("Pre-spawn: failed to create worktree (MCP process will retry)",{requestId:t,error:s})}}
@@ -427,22 +458,22 @@ if(!s())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.log
427
458
  if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
428
459
  const s=e||process.cwd()
429
460
  t.logFilePath=i.default.join(s,"logs","debug.log")}const r=process.env.DEBUG_LEVEL?.toLowerCase()
430
- return r&&Object.values(c.LogLevel).includes(r)&&(t.level=r),t}(e.baseDir),...e},{level:o=c.LogLevel.INFO,serviceName:h="mcp-service",logToFile:p=!1,logFilePath:f="./logs/app.log",console=!0}=t,m=[]
461
+ return r&&Object.values(c.LogLevel).includes(r)&&(t.level=r),t}(e.baseDir),...e},{level:o=c.LogLevel.INFO,serviceName:g="mcp-service",logToFile:p=!1,logFilePath:f="./logs/app.log",console=!0}=t,m=[]
431
462
  if(console&&m.push(new n.default.transports.Console({format:d,stderrLevels:["error","warn","info","debug"]})),p){!function(e){const t=i.default.dirname(e)
432
463
  a.default.existsSync(t)||a.default.mkdirSync(t,{recursive:!0})}(f)
433
464
  const e=o===c.LogLevel.DEBUG&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()
434
- m.push(new n.default.transports.File({filename:f,format:e?g:u}))}const y=n.default.createLogger({level:o,defaultMeta:{service:h},transports:m})
435
- return o===c.LogLevel.DEBUG&&p&&s()&&y.info("Enhanced logging initialized",{level:o,serviceName:h,logToFile:p,logFilePath:f,debugLogging:process.env.DEBUG_LOGGING}),{error(e,t){y.error(e,t)},warn(e,t){y.warn(e,t)},info(e,t){y.info(e,t)},debug(e,t){y.debug(e,t)},child(e){const s=y.child(e)
436
- return{error(e,t){s.error(e,t)},warn(e,t){s.warn(e,t)},info(e,t){s.info(e,t)},debug(e,t){s.debug(e,t)},child(e){return r({...t,serviceName:`${h}:${e.component||"nested"}`})}}}}}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
465
+ m.push(new n.default.transports.File({filename:f,format:e?h:u}))}const y=n.default.createLogger({level:o,defaultMeta:{service:g},transports:m})
466
+ return o===c.LogLevel.DEBUG&&p&&s()&&y.info("Enhanced logging initialized",{level:o,serviceName:g,logToFile:p,logFilePath:f,debugLogging:process.env.DEBUG_LOGGING}),{error(e,t){y.error(e,t)},warn(e,t){y.warn(e,t)},info(e,t){y.info(e,t)},debug(e,t){y.debug(e,t)},child(e){const s=y.child(e)
467
+ return{error(e,t){s.error(e,t)},warn(e,t){s.warn(e,t)},info(e,t){s.info(e,t)},debug(e,t){s.debug(e,t)},child(e){return r({...t,serviceName:`${g}:${e.component||"nested"}`})}}}}}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
437
468
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.createLogger=r,exports.createEnvLogger=function(e={}){return r(e)},exports.isDebugToolsEnabled=s,exports.isDebugLoggingEnabled=function(){return!!s()&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()},exports.getDebugLevelFromEnv=function(){if(!s())return c.LogLevel.INFO
438
469
  const e=process.env.DEBUG_LEVEL?.toLowerCase()
439
470
  return e&&Object.values(c.LogLevel).includes(e)?e:c.LogLevel.INFO}
440
- const n=o(t(5124)),i=o(t(6928)),a=o(t(9896)),c=t(6540),l=t(7960),d=n.default.format.combine(n.default.format.timestamp(),n.default.format.colorize(),n.default.format.printf((({level:e,message:t,timestamp:s,...r})=>`${s} [${e}]: ${t}${Object.keys(r).length?" "+JSON.stringify(r,null,0):""}`))),u=n.default.format.combine(n.default.format.timestamp(),n.default.format.json()),g=n.default.format.combine(n.default.format.timestamp(),n.default.format.printf((({level:e,message:t,timestamp:s,service:r,component:o,...n})=>{const i=r?`[${r}]`:"",a=o?`[${o}]`:"",c=Object.keys(n).length?" "+JSON.stringify(n,null,2):""
441
- return`${s} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),h=r({logToFile:!1,console:!0,level:c.LogLevel.INFO})
442
- exports.default=h},2928(e,exports,t){"use strict"
471
+ const n=o(t(5124)),i=o(t(6928)),a=o(t(9896)),c=t(6540),l=t(7960),d=n.default.format.combine(n.default.format.timestamp(),n.default.format.colorize(),n.default.format.printf((({level:e,message:t,timestamp:s,...r})=>`${s} [${e}]: ${t}${Object.keys(r).length?" "+JSON.stringify(r,null,0):""}`))),u=n.default.format.combine(n.default.format.timestamp(),n.default.format.json()),h=n.default.format.combine(n.default.format.timestamp(),n.default.format.printf((({level:e,message:t,timestamp:s,service:r,component:o,...n})=>{const i=r?`[${r}]`:"",a=o?`[${o}]`:"",c=Object.keys(n).length?" "+JSON.stringify(n,null,2):""
472
+ return`${s} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),g=r({logToFile:!1,console:!0,level:c.LogLevel.INFO})
473
+ exports.default=g},2928(e,exports,t){"use strict"
443
474
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.StuckSessionWatchdog=void 0
444
475
  const s=t(8824)
445
- exports.StuckSessionWatchdog=class{constructor(e){this.stuckSessions=new Map,this.resolvedRecently=new Set,this.notifiedSessions=new Set,this.testInjectedIds=new Set,this.logger=e.logger,this.getOpenCodeUrl=e.getOpenCodeUrl,this.onStuckDetected=e.onStuckDetected,this.onStuckResolved=e.onStuckResolved,this.checkIntervalMs=e.checkIntervalMs??3e4,this.stuckThresholdMs=e.stuckThresholdMs??9e4,this.maxRetries=e.maxRetries??3}start(){this.intervalTimer||(this.logger.info("Stuck session watchdog starting",{checkInterval:this.checkIntervalMs,stuckThreshold:this.stuckThresholdMs,maxRetries:this.maxRetries}),setTimeout((()=>this.check().catch((()=>{}))),5e3),this.intervalTimer=setInterval((()=>{this.check().catch((()=>{}))}),this.checkIntervalMs))}stop(){this.intervalTimer&&(clearInterval(this.intervalTimer),this.intervalTimer=void 0),this.stuckSessions.clear(),this.notifiedSessions.clear(),this.resolvedRecently.clear(),this.logger.info("Stuck session watchdog stopped")}getStuckSessions(){return Array.from(this.stuckSessions.values())}isStuck(e){return this.stuckSessions.has(e)}injectTestStuck(e,t){const s=e||"test-stuck-"+Date.now(),r={sessionId:s,messageId:"test-msg-"+Date.now(),detectedAt:Date.now(),retryCount:0,lastMessageCreated:Date.now()-this.stuckThresholdMs-1e3,sessionTitle:t||"Test Stuck Session"}
476
+ exports.StuckSessionWatchdog=class{constructor(e){this.stuckSessions=new Map,this.resolvedRecently=new Set,this.notifiedSessions=new Set,this.testInjectedIds=new Set,this.logger=e.logger,this.getOpenCodeUrl=e.getOpenCodeUrl,this.onStuckDetected=e.onStuckDetected,this.onStuckResolved=e.onStuckResolved,this.checkIntervalMs=e.checkIntervalMs??3e4,this.stuckThresholdMs=e.stuckThresholdMs??9e4,this.maxRetries=e.maxRetries??3,this.getDefaultModel=e.getDefaultModel}start(){this.intervalTimer||(this.logger.info("Stuck session watchdog starting",{checkInterval:this.checkIntervalMs,stuckThreshold:this.stuckThresholdMs,maxRetries:this.maxRetries}),setTimeout((()=>this.check().catch((()=>{}))),5e3),this.intervalTimer=setInterval((()=>{this.check().catch((()=>{}))}),this.checkIntervalMs))}stop(){this.intervalTimer&&(clearInterval(this.intervalTimer),this.intervalTimer=void 0),this.stuckSessions.clear(),this.notifiedSessions.clear(),this.resolvedRecently.clear(),this.logger.info("Stuck session watchdog stopped")}getStuckSessions(){return Array.from(this.stuckSessions.values())}isStuck(e){return this.stuckSessions.has(e)}injectTestStuck(e,t){const s=e||"test-stuck-"+Date.now(),r={sessionId:s,messageId:"test-msg-"+Date.now(),detectedAt:Date.now(),retryCount:0,lastMessageCreated:Date.now()-this.stuckThresholdMs-1e3,sessionTitle:t||"Test Stuck Session"}
446
477
  return this.stuckSessions.set(s,r),this.testInjectedIds.add(s),this.notifiedSessions.add(s),this.onStuckDetected?.(r),r}clearTestStuck(e){return!!this.testInjectedIds.has(e)&&(this.testInjectedIds.delete(e),this.stuckSessions.delete(e),this.notifiedSessions.delete(e),this.onStuckResolved?.(e),!0)}async check(){const e=this.getOpenCodeUrl()
447
478
  if(e)try{const[t,r]=await Promise.all([fetch(e+"/session",{signal:AbortSignal.timeout(5e3)}),fetch(e+"/session/status",{signal:AbortSignal.timeout(5e3)})])
448
479
  if(!t.ok||!r.ok)return
@@ -470,7 +501,10 @@ if(r.retryCount>=this.maxRetries)return{success:!1,error:`Max retries (${this.ma
470
501
  try{this.logger.info("Retrying stuck session: aborting",{sessionId:e})
471
502
  const s=await fetch(`${t}/session/${e}/abort`,{method:"POST",signal:AbortSignal.timeout(5e3)})
472
503
  s.ok||this.logger.warn("Abort returned non-OK (may already be idle)",{sessionId:e,status:s.status}),await new Promise((e=>setTimeout(e,1e3))),this.logger.info("Retrying stuck session: sending continue",{sessionId:e})
473
- const o=await fetch(`${t}/session/${e}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:"Continue"}]}),signal:AbortSignal.timeout(1e4)})
504
+ const o=await fetch(`${t}/session/${e}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:"Continue"}],...(()=>{const e=this.getDefaultModel?.()
505
+ if(!e)return{}
506
+ const[t,...s]=e.split("/")
507
+ return{model:{providerID:t,modelID:s.join("/")}}})()}),signal:AbortSignal.timeout(1e4)})
474
508
  if(!o.ok){const e=await o.text().catch((()=>""))
475
509
  return{success:!1,error:`Failed to re-send prompt: ${o.status} ${e}`}}return r.retryCount++,this.stuckSessions.delete(e),this.notifiedSessions.delete(e),this.logger.info("Stuck session retry sent",{sessionId:e,retryCount:r.retryCount}),{success:!0}}catch(e){return{success:!1,error:(0,s.getErrorMessage)(e)}}}}},3077(e,exports,t){"use strict"
476
510
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookExecutor=void 0
@@ -484,9 +518,9 @@ if(0===d.length)return i
484
518
  const u={...o}
485
519
  if(s&&(!u.parentSessionId||!u.agentType))try{const e=await this.ctx.apiClient.get(`/api/projects/${this.ctx.projectId}/cli-sessions/external/opencode/${encodeURIComponent(s)}`),t=e?.data||e
486
520
  if(t?.agentType&&!u.agentType&&(u.agentType=t.agentType),!u.parentSessionId&&t?.parentSessionId){const e=await this.ctx.apiClient.get(`/api/projects/${this.ctx.projectId}/cli-sessions/${t.parentSessionId}`),s=e?.data||e
487
- s?.externalSessionId&&(u.parentSessionId=s.externalSessionId)}}catch(e){this.logger.warn("Failed to enrich hook variables with session context",{sessionId:s,error:e+""})}const g=d.filter((e=>(0,r.evaluateCondition)(e.condition,u)))
488
- if(0===g.length)return i
489
- for(const e of g){await this.executeHook(e,u,s),i.triggered++,i.hooks.push(e.name)
521
+ s?.externalSessionId&&(u.parentSessionId=s.externalSessionId)}}catch(e){this.logger.warn("Failed to enrich hook variables with session context",{sessionId:s,error:e+""})}const h=d.filter((e=>(0,r.evaluateCondition)(e.condition,u)))
522
+ if(0===h.length)return i
523
+ for(const e of h){await this.executeHook(e,u,s),i.triggered++,i.hooks.push(e.name)
490
524
  const t=(0,r.determineEventToEmit)(e,u)
491
525
  t&&await this.processCustomEvent(t,{...u,sessionId:s||""},0)}"session_idle"!==t&&"session_created"!==t&&s&&this.cache.preWarm("session_idle",s)}catch(e){this.logger.error("Error processing hook event",{eventType:t,error:e+""})}return i}async executeHook(e,t,s){const n=(0,r.interpolatePrompt)(e.promptTemplate,t),i=t.requestId
492
526
  if(this.logger.info("Executing hook",{name:e.name,action:e.action,target:e.target,eventType:e.eventType,sessionId:s}),this.notifyHookTrigger(e).catch((()=>{})),"inject_prompt"===e.action)switch(e.target){case"same_session":default:await(0,o.executeSameSession)(n,s,this.ctx)
@@ -528,7 +562,7 @@ let n=null},4139(e,exports){"use strict"
528
562
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ALCHEMY_DIR_NAME=exports.TOKEN_DIR_NAME=void 0,exports.TOKEN_DIR_NAME="alchemy-key",exports.ALCHEMY_DIR_NAME=".alchemy_lab"},4192(e,exports,t){"use strict"
529
563
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.BaseApiClient=void 0
530
564
  const s=t(7163)
531
- exports.BaseApiClient=class{constructor(e,t,r,o,n){this.failureCount=0,this.circuitState="closed",this.circuitResetTimeout=null,this.baseUrl=e,this.logger=t,this.metrics=r,this.authToken=o,this.options={retryAttempts:3,retryDelay:1e3,timeout:1e4,circuitBreakerThreshold:5,circuitBreakerResetTime:3e4,...n},this.logger.debug("BaseApiClient constructor options:",{providedOptions:n?JSON.stringify(n):"undefined",mergedOptions:JSON.stringify(this.options)})
565
+ exports.BaseApiClient=class{constructor(e,t,r,o,n){this.failureCount=0,this.circuitState="closed",this.circuitResetTimeout=null,this.baseUrl=e,this.logger=t,this.metrics=r,this.authToken=o,this.options={retryAttempts:3,retryDelay:1e3,timeout:1e4,circuitBreakerThreshold:10,circuitBreakerResetTime:1e4,...n},this.logger.debug("BaseApiClient constructor options:",{providedOptions:n?JSON.stringify(n):"undefined",mergedOptions:JSON.stringify(this.options)})
532
566
  try{(0,s.debugTokenAccess)(this,"BaseApiClient constructor")}catch(e){}}setAuthToken(e){this.authToken=e||void 0,this.logger.debug("Auth token updated",{hasToken:!!e,tokenLength:e?e.length:0})}getAuthToken(){return this.authToken}async requestWithRetry(e,t,s,r,o){if("open"===this.circuitState)throw this.logger.warn(`Circuit breaker open - rejecting request to ${e} ${t}`),Error("Service unavailable - circuit breaker open")
533
567
  const n=t.startsWith("/")?t:"/"+t
534
568
  let i=`${this.baseUrl}${n}`
@@ -538,14 +572,14 @@ this.authToken?(a.Authorization="Bearer "+this.authToken,this.logger.debug("Usin
538
572
  const c=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
539
573
  a["X-Service-Secret"]=c
540
574
  let l=0
541
- const d=this.options.retryAttempts,u={method:e,path:n},g=this.metrics.startTimer("api_request_duration"),h=e=>g({...u,...e})
575
+ const d=this.options.retryAttempts,u={method:e,path:n},h=this.metrics.startTimer("api_request_duration"),g=e=>h({...u,...e})
542
576
  for(;d>l;){l++
543
577
  try{this.logger.debug(`Making ${e} request to ${i}`,{attempt:l,maxAttempts:d,hasAuth:!!a.Authorization,hasServiceIdentifier:!!a["X-Client-Type"]})
544
578
  const t={method:e,headers:{...a},signal:AbortSignal.timeout(this.options.timeout||1e4)}
545
579
  if("GET"!==e&&void 0!==s&&(t.body=JSON.stringify(s)),this.logger.debug("Request details",{method:e,url:i}),t.headers){const e={}
546
580
  Object.entries(t.headers).forEach((([t,s])=>{"authorization"===t.toLowerCase()?e[t]="Bearer [redacted]":e[t]=s})),this.logger.debug("Request headers",{headers:e})}if("GET"!==e&&t.body){const e="string"==typeof t.body?t.body:JSON.stringify(t.body)
547
581
  this.logger.debug("Request body",{bodyPreview:e.substring(0,200)+(e.length>200?"...":""),bodyLength:e.length})}const r=await fetch(i,t)
548
- if(!r.ok){if(this.incrementFailureCount(),this.logger.error("API request failed with status "+r.status,{method:e,url:i,status:r.status,statusText:r.statusText,attempt:l}),401===r.status)throw h({success:!1,status:r.status,retries:l-1}),Error("Authentication failed - invalid or expired token")
582
+ if(!r.ok){if(500>r.status||this.incrementFailureCount(),this.logger.error("API request failed with status "+r.status,{method:e,url:i,status:r.status,statusText:r.statusText,attempt:l}),401===r.status)throw g({success:!1,status:r.status,retries:l-1}),Error("Authentication failed - invalid or expired token")
549
583
  if(429===r.status){const e=r.headers.get("Retry-After"),t=e?1e3*parseInt(e):2*(this.options.retryDelay||1e3)
550
584
  if(this.logger.warn(`Rate limited (429), retrying after ${t}ms`,{attempt:l,maxAttempts:d,retryAfter:e}),d>l){await new Promise((e=>setTimeout(e,t)))
551
585
  continue}}if(r.status>=500&&d>l){const e=(this.options.retryDelay||1e3)*Math.pow(2,l-1)
@@ -554,18 +588,18 @@ continue}let t=`API request failed with status ${r.status}: ${r.statusText}`,s={
554
588
  try{const e=await r.json()
555
589
  if(e&&"object"==typeof e&&!1===e.success&&e.error){const r=e.error
556
590
  r.message&&(t=r.message),s={code:r.code,statusCode:r.statusCode,retryable:r.retryable,details:r.details}}else e&&"object"==typeof e&&e.message&&(t=e.message)}catch(e){try{const e=await r.text()
557
- e&&e.trim()&&(t=`${t}. Response: ${e.substring(0,200)}`)}catch(t){this.logger.debug("Failed to parse error response",{jsonError:e instanceof Error?e.message:e+"",textError:t instanceof Error?t.message:t+""})}}h({success:!1,status:r.status,retries:l-1})
591
+ e&&e.trim()&&(t=`${t}. Response: ${e.substring(0,200)}`)}catch(t){this.logger.debug("Failed to parse error response",{jsonError:e instanceof Error?e.message:e+"",textError:t instanceof Error?t.message:t+""})}}g({success:!1,status:r.status,retries:l-1})
558
592
  const o=Error(t)
559
593
  throw o.status=r.status,o.statusText=r.statusText,o.method=e,o.url=i,o.errorDetails=s,o}this.resetFailureCount()
560
594
  const o=await r.json()
561
595
  if(null===o||"object"!=typeof o)throw Error("Invalid API response: expected object, got "+typeof o)
562
596
  if(o&&"object"==typeof o&&!("success"in o)){this.logger.warn("Non-standard API response detected - wrapping in standard format",{responseKeys:Object.keys(o)})
563
597
  const e={success:!0,data:o}
564
- return h({success:!0,status:r.status,retries:l-1,standardFormat:!1}),e}if(o&&"object"==typeof o&&"success"in o&&!1===o.success){const e=o
565
- throw this.logger.error("API returned error response",{errorCode:e.error?.code,errorMessage:e.error?.message,statusCode:e.error?.statusCode}),h({success:!1,status:r.status,retries:l-1,errorCode:e.error?.code}),Error(`API error: ${e.error?.code}: ${e.error?.message}`)}if(o&&"object"==typeof o&&"success"in o&&!0===o.success&&"data"in o){const t=o
566
- return h({success:!0,status:r.status,retries:l-1,standardFormat:!0}),this.logger.debug("API request succeeded with status "+r.status,{method:e,url:i,status:r.status,retries:l-1,hasMetadata:"metadata"in t}),t}return this.logger.warn("Invalid API response format: Unexpected structure",{responseType:typeof o,responseKeys:"object"==typeof o?Object.keys(o):[]}),{success:!0,data:o}}catch(t){if(l>=d)throw this.incrementFailureCount(),this.logger.error(`API request failed after ${l} attempts`,{method:e,url:i,error:t.message||t+"",status:t.status}),h({success:!1,error:!0,retries:l-1}),t
598
+ return g({success:!0,status:r.status,retries:l-1,standardFormat:!1}),e}if(o&&"object"==typeof o&&"success"in o&&!1===o.success){const e=o
599
+ throw this.logger.error("API returned error response",{errorCode:e.error?.code,errorMessage:e.error?.message,statusCode:e.error?.statusCode}),g({success:!1,status:r.status,retries:l-1,errorCode:e.error?.code}),Error(`API error: ${e.error?.code}: ${e.error?.message}`)}if(o&&"object"==typeof o&&"success"in o&&!0===o.success&&"data"in o){const t=o
600
+ return g({success:!0,status:r.status,retries:l-1,standardFormat:!0}),this.logger.debug("API request succeeded with status "+r.status,{method:e,url:i,status:r.status,retries:l-1,hasMetadata:"metadata"in t}),t}return this.logger.warn("Invalid API response format: Unexpected structure",{responseType:typeof o,responseKeys:"object"==typeof o?Object.keys(o):[]}),{success:!0,data:o}}catch(t){if(l>=d)throw this.incrementFailureCount(),this.logger.error(`API request failed after ${l} attempts`,{method:e,url:i,error:t.message||t+"",status:t.status}),g({success:!1,error:!0,retries:l-1}),t
567
601
  const s=Math.min((this.options.retryDelay||1e3)*Math.pow(2,l-1),5e3)
568
- this.logger.warn(`API request failed, retrying in ${s}ms`,{method:e,url:i,error:t.message||t+"",attempt:l,maxAttempts:d}),await new Promise((e=>setTimeout(e,s)))}}throw Error("Unexpected end of requestWithRetry")}incrementFailureCount(){this.failureCount++,this.metrics.setGauge("api_failure_count",this.failureCount),"half-open"!==this.circuitState&&(this.failureCount<this.options.circuitBreakerThreshold||"closed"!==this.circuitState)||this.openCircuit()}resetFailureCount(){const e="closed"!==this.circuitState
602
+ this.logger.warn(`API request failed, retrying in ${s}ms`,{method:e,url:i,error:t.message||t+"",attempt:l,maxAttempts:d}),await new Promise((e=>setTimeout(e,s)))}}throw Error("Unexpected end of requestWithRetry")}incrementFailureCount(){this.failureCount++,this.metrics.setGauge("api_failure_count",this.failureCount),"half-open"!==this.circuitState?this.failureCount<this.options.circuitBreakerThreshold||"closed"!==this.circuitState||this.openCircuit():this.logger.warn("Half-open probe failed, will retry on next request")}resetFailureCount(){const e="closed"!==this.circuitState
569
603
  this.failureCount=0,this.circuitState="closed",this.metrics.setGauge("api_failure_count",0),this.metrics.setGauge("circuit_breaker_open",0),this.circuitResetTimeout&&(clearTimeout(this.circuitResetTimeout),this.circuitResetTimeout=null),e&&this.logger.info("Circuit breaker closed after successful request")}openCircuit(){this.circuitState="open",this.metrics.setGauge("circuit_breaker_open",1),this.logger.warn(`Circuit breaker opened after ${this.failureCount} failures`),this.circuitResetTimeout&&clearTimeout(this.circuitResetTimeout)
570
604
  const e=this.options.circuitBreakerResetTime||5e3
571
605
  this.circuitResetTimeout=setTimeout((()=>{this.circuitState="half-open",this.logger.info("Circuit breaker half-open, next request will probe")}),e)}async get(e,t,s){return this.requestWithRetry("GET",e,void 0,s,t)}async post(e,t,s){return this.requestWithRetry("POST",e,t,s)}async put(e,t,s){return this.requestWithRetry("PUT",e,t,s)}async delete(e,t){return this.requestWithRetry("DELETE",e,void 0,t)}async patch(e,t,s){return this.requestWithRetry("PATCH",e,t,s)}}},4245(module){function e(e){var t=Error("Cannot find module '"+e+"'")
@@ -646,7 +680,8 @@ const s=t(6636),r=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6
646
680
  if(!r||!o)return{success:!1,message:"sessionId and message are required",error:"missing_params"}
647
681
  const n=await Promise.resolve().then(t.t.bind(t,9896,23)),i=(await Promise.resolve().then(t.t.bind(t,6928,23))).join(s.projectPath,".alchemy_lab","opencode.json")
648
682
  let a
649
- try{a="http://127.0.0.1:"+JSON.parse(n.readFileSync(i,"utf-8")).port}catch{return{success:!1,message:"Cannot read OpenCode config",error:"no_opencode"}}try{const e=await fetch(`${a}/session/${r}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o}]}),signal:AbortSignal.timeout(1e4)})
683
+ try{a="http://127.0.0.1:"+JSON.parse(n.readFileSync(i,"utf-8")).port}catch{return{success:!1,message:"Cannot read OpenCode config",error:"no_opencode"}}try{const e=await fetch(`${a}/session/${r}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o}],...s.cachedDefaultModel?(()=>{const[e,...t]=s.cachedDefaultModel.split("/")
684
+ return{model:{providerID:e,modelID:t.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
650
685
  return e.ok?{success:!0,message:"Prompt injected into session "+r}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,d.getErrorMessage)(e),error:"prompt_error"}}}}},5671(e,exports,t){"use strict"
651
686
  function s(e){const[t,...s]=e.split("/")
652
687
  return{provider:t||"anthropic",modelId:s.join("/")||e}}function r(e){const t={anthropic:!1,openai:!1}
@@ -661,21 +696,19 @@ i=t}const a=function(e,t){const s=c[e]
661
696
  if(!s)return null
662
697
  const r=[...s].sort(((e,t)=>t.length-e.length))
663
698
  for(const e of r)if(t.includes(e))return e
664
- return null}(n.provider,n.modelId),l=c[n.provider],d=c[i]
665
- if(!a||!l||!d)return{provider:n.provider,modelId:n.modelId,fullModel:e,resolved:!1,reason:`Could not determine model family for '${n.modelId}', using main session model`}
666
- const u=l.indexOf(a)
667
- let g,h=0
668
- if("one_down"===r&&(h=1),"two_down"===r&&(h=2),i===n.provider)g=u+h
669
- else{const e=l.length>1?u/(l.length-1):0
670
- g=Math.round(e*(d.length-1))+h}g=Math.min(g,d.length-1),g=Math.max(g,0)
671
- const p=d[g]
672
- let f
673
- var m,y
674
- return i===n.provider?(m=a,y=p,f=n.modelId.replace(m,y)):f=p,{provider:i,modelId:f,fullModel:`${i}/${f}`,resolved:!0}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseModelString=s,exports.getAvailableProviders=r,exports.getMainSessionModel=o,exports.resolveModelTier=n,exports.resolveAgentModel=function(e,t="inherit",i="same"){const a=o(e)
675
- if(!a)return{provider:"anthropic",modelId:"claude-sonnet-4-5",fullModel:"anthropic/claude-sonnet-4-5",resolved:!1,reason:"Could not determine main session model, using default"}
676
- if("inherit"===t&&"same"===i){const e=s(a)
677
- return{provider:e.provider,modelId:e.modelId,fullModel:a,resolved:!0}}return n(a,t,i,r(e))}
678
- const i=t(9896),a=t(6928),c={anthropic:["opus","sonnet","haiku"]}},5700(e,exports,t){"use strict"
699
+ return null}(n.provider,n.modelId),d=c[n.provider],u=c[i]
700
+ if(!a||!d||!u)return{provider:n.provider,modelId:n.modelId,fullModel:e,resolved:!1,reason:`Could not determine model family for '${n.modelId}', using main session model`}
701
+ const h=d.indexOf(a)
702
+ let g,p=0
703
+ if("one_down"===r&&(p=1),"two_down"===r&&(p=2),i===n.provider)g=h+p
704
+ else{const e=d.length>1?h/(d.length-1):0
705
+ g=Math.round(e*(u.length-1))+p}g=Math.min(g,u.length-1),g=Math.max(g,0)
706
+ const f=u[g],m=l[i]?.[f]
707
+ return m?{provider:i,modelId:m,fullModel:`${i}/${m}`,resolved:!0}:{provider:n.provider,modelId:n.modelId,fullModel:e,resolved:!0,reason:`No known model for ${i}/${f}, using main session model`}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseModelString=s,exports.getAvailableProviders=r,exports.getMainSessionModel=o,exports.resolveModelTier=n,exports.resolveAgentModel=function(e,t="inherit",i="same",a){const c=a||o(e)
708
+ if(!c)return{provider:"anthropic",modelId:"claude-sonnet-4-5",fullModel:"anthropic/claude-sonnet-4-5",resolved:!1,reason:"Could not determine main session model, using default"}
709
+ if("inherit"===t&&"same"===i){const e=s(c)
710
+ return{provider:e.provider,modelId:e.modelId,fullModel:c,resolved:!0}}return n(c,t,i,r(e))}
711
+ const i=t(9896),a=t(6928),c={anthropic:["opus","sonnet","haiku"]},l={anthropic:{opus:"claude-opus-4-6",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5"}}},5700(e,exports,t){"use strict"
679
712
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
680
713
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRestartMcpAction=async function(e,t){const{logger:s,projectPath:n}=t,i=e.reason||"Manual restart requested"
681
714
  s.info("Executing restart-mcp action",{reason:i})
@@ -684,7 +717,40 @@ await r.default.mkdir(t,{recursive:!0})
684
717
  const a=JSON.stringify({timestamp:(new Date).toISOString(),reason:i},null,2)
685
718
  return await r.default.writeFile(e,a,"utf-8"),s.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+""
686
719
  return s.error("Failed to trigger MCP restart",{error:t}),{success:!1,message:"Failed to trigger MCP restart: "+t,error:t}}}
687
- const r=s(t(1943)),o=s(t(6928))},6540(e,exports){"use strict"
720
+ const r=s(t(1943)),o=s(t(6928))},6175(e,exports){"use strict"
721
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerConditionEvaluator=void 0
722
+ const t=new Set(["coder","reviewer","tester","journal","planner","researcher"])
723
+ exports.TimerConditionEvaluator=class{constructor(e){this.logger=e}async evaluate(e,t,s){if(!e||0===e.length)return{passed:!0,variables:{}}
724
+ const r=await this.fetchSessionState(t)
725
+ if(!r)return{passed:!1,variables:{},reason:"Failed to fetch session state"}
726
+ const o={totalSessions:r.sessions.length+"",runningSessions:r.running.length+"",idleSessions:r.idle.length+""}
727
+ let n
728
+ for(const t of e){const e=await this.evaluateSingle(t,r,s)
729
+ if(!e.passed)return{passed:!1,variables:o,reason:e.reason}
730
+ Object.assign(o,e.variables),e.targetSessionId&&!n&&(n=e.targetSessionId)}return{passed:!0,targetSessionId:n,variables:o}}async evaluateSingle(e,t,s){switch(e.type){case"no_busy_specialists":return this.evalNoBusySpecialists(t,s)
731
+ case"no_busy_agents":return this.evalNoBusyAgents(t,e.params?.agentType,s)
732
+ case"session_idle_for":return this.evalSessionIdleFor(t,e.params?.seconds??60,e.params?.agentType,s)
733
+ case"any_session_running":return this.evalAnySessionRunning(t)
734
+ case"all_sessions_idle":return this.evalAllSessionsIdle(t)
735
+ default:return this.logger.warn("Unknown timer condition type",{type:e.type}),{passed:!0,variables:{}}}}async evalNoBusySpecialists(e,s){if(0===e.running.length)return{passed:!0,variables:{busySpecialists:"0"}}
736
+ if(!s)return{passed:0===e.running.length,variables:{busySpecialists:e.running.length+""},reason:"No agent type resolver"}
737
+ let r=0
738
+ for(const o of e.running){const e=await s(o.id)
739
+ t.has(e)&&r++}return{passed:0===r,variables:{busySpecialists:r+""},reason:r>0?r+" specialist(s) still running":void 0}}async evalNoBusyAgents(e,t,s){if(!t)return{passed:0===e.running.length,variables:{busyAgents:e.running.length+""},reason:e.running.length>0?e.running.length+" session(s) still running":void 0}
740
+ if(!s)return{passed:!1,variables:{},reason:"No agent type resolver for type filtering"}
741
+ let r=0
742
+ for(const o of e.running)await s(o.id)===t&&r++
743
+ return{passed:0===r,variables:{busyAgents:r+"",checkedAgentType:t},reason:r>0?`${r} ${t} session(s) still running`:void 0}}async evalSessionIdleFor(e,t,s,r){const o=1e3*t,n=Date.now()
744
+ for(const t of e.idle){const e=n-t.time.updated
745
+ if(e>=o){if(s&&r&&await r(t.id)!==s)continue
746
+ return{passed:!0,targetSessionId:t.id,variables:{idleSessionId:t.id,idleDurationSeconds:Math.round(e/1e3)+"",idleSessionTitle:t.title||""}}}}return{passed:!1,variables:{},reason:s?`No ${s} session idle for ${t}s`:`No session idle for ${t}s`}}evalAnySessionRunning(e){const t=e.running.length>0
747
+ return{passed:t,variables:{runningSessions:e.running.length+""},reason:t?void 0:"No sessions running"}}evalAllSessionsIdle(e){if(0===e.sessions.length)return{passed:!1,variables:{},reason:"No sessions exist"}
748
+ const t=0===e.running.length
749
+ return{passed:t,variables:{idleSessions:e.idle.length+"",runningSessions:e.running.length+""},reason:t?void 0:e.running.length+" session(s) still running"}}async fetchSessionState(e){try{const[t,s]=await Promise.all([fetch(e+"/session",{signal:AbortSignal.timeout(5e3)}),fetch(e+"/session/status",{signal:AbortSignal.timeout(5e3)})])
750
+ if(!t.ok||!s.ok)return null
751
+ const r=await t.json(),o=await s.json(),n=r.filter((e=>"running"===o[e.id]?.type)),i=r.filter((e=>{const t=o[e.id]?.type
752
+ return"idle"===t||"completed"===t}))
753
+ return{sessions:r,statusMap:o,running:n,idle:i}}catch(e){return this.logger.debug("Failed to fetch OpenCode session state",{error:e+""}),null}}}},6540(e,exports){"use strict"
688
754
  var t
689
755
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=void 0,function(e){e.ERROR="error",e.WARN="warn",e.INFO="info",e.DEBUG="debug"}(t||(exports.LogLevel=t={}))},6555(e,exports,t){"use strict"
690
756
  function s(e,process,t,s,r=!1){const o=process.config||{},n=(o.setup,o.commands||[])
@@ -692,36 +758,84 @@ let i=`You are a test runner agent. Your job is to verify that request ${e.local
692
758
  for(let e=0;e<n.length;e++){const t=n[e]
693
759
  i+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}if(i+=r?`\n## Instructions\n\nYou are in interactive mode. Present the test plan to the user and **wait for their direction** before running anything.\n\n1. Greet the user and list the test commands available for this process.\n2. Wait for the user to tell you to proceed (they may want to run specific commands, skip some, or run all).\n3. Use \`execute_test_command\` with processId="${process.id}" and the command name to run each command.\n4. After each command, explain the result and wait for the user before continuing.\n5. When the user is satisfied, call \`report_test_results\` with the overall status.\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`:`\n## Instructions\n\nSetup commands have already been executed. Use the \`execute_test_command\` tool to run each test command:\n\n1. For each command above, call \`execute_test_command\` with processId="${process.id}" and the command name.\n2. Check if it passed or failed.\n3. If it fails: try to determine why \u2014 is it a real bug, a flaky test, or an environment issue? Retry once if it looks flaky.\n4. When all commands are done, call \`report_test_results\` with the overall status and per-command results.\n - If all REQUIRED commands passed \u2192 status "passed"\n - If any REQUIRED command failed \u2192 status "failed" with diagnosis of each failure\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`,o.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0){i+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by reading code changes and running ad-hoc commands as needed. This is **advisory only** \u2014 it does not affect the overall pass/fail status.\n\nCriteria to verify:\n"
694
760
  for(const t of e.acceptanceCriteria)i+=`- ${t}\n`
695
- i+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return i}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,d){const{requestId:u,testProcessIds:g,parentSessionId:h,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:C}=d
761
+ i+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return i}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,d){const{requestId:u,testProcessIds:h,parentSessionId:g,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:k}=d
696
762
  if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
697
- m.info("Starting run-tests action",{requestId:u,testProcessIds:g})
763
+ m.info("Starting run-tests action",{requestId:u,testProcessIds:h})
698
764
  try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
699
765
  if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
700
766
  const p=d.data,I=p.branchName||"requests/"+u
701
767
  let S
702
- if(g?.length)S=(await Promise.all(g.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
768
+ if(h?.length)S=(await Promise.all(h.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
703
769
  else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
704
770
  S=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===S.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
705
- const k=(0,o.join)(w,c,l),b=(0,o.join)(k,u),E=(0,n.simpleGit)(w)
771
+ const C=(0,o.join)(w,c,l),b=(0,o.join)(C,u),E=(0,n.simpleGit)(w)
706
772
  if((0,r.existsSync)(b))try{const e=(0,n.simpleGit)(b)
707
773
  await e.raw(["reset","--hard",I]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:b,branchName:I})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
708
- try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(k,{recursive:!0})
774
+ try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(C,{recursive:!0})
709
775
  try{if(!(await E.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
710
776
  await E.raw(["worktree","add","--detach",b,I]),m.info("Created test worktree (detached)",{worktreePath:b,branchName:I})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
711
777
  m.info("Test worktree already exists, using as-is",{worktreePath:b})}}const{execSync:j}=await Promise.resolve().then(t.t.bind(t,5317,23))
712
778
  for(const e of S){const t=e.config?.setup||[]
713
779
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
714
- for(const s of t)try{m.info("Setup command",{command:s,worktreePath:b}),j(s,{cwd:b,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:s,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const T=[],P=[]
780
+ for(const s of t)try{m.info("Setup command",{command:s,worktreePath:b}),j(s,{cwd:b,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:s,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const T=[],A=[]
715
781
  for(const process of S){let t
716
782
  const r=e.testRunIds?.[T.length]
717
- try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:b}),t=(await y.get(`/api/projects/${v}/test-runs/${r}`)).data,T.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:b,parentSessionId:h})).data,T.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:r,error:(0,i.getErrorMessage)(e)})
783
+ try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:b}),t=(await y.get(`/api/projects/${v}/test-runs/${r}`)).data,T.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:b,parentSessionId:g})).data,T.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:r,error:(0,i.getErrorMessage)(e)})
718
784
  continue}const o=s(p,process,0,v,f),n=f?"tester":"auto-tester"
719
- try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:b,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:h,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
785
+ try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:b,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:g,allowParallel:!0,apiClient:y,logger:m,trackChildPid:k})
720
786
  if(!e.success)throw Error(e.error||"Spawn failed")
721
- P.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:b,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
722
- try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===P.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${P.length} test run(s) for request ${u}`,data:{testRunIds:T,agentsSpawned:P.length,processCount:S.length,worktreePath:b}}}catch(e){const t=(0,i.getErrorMessage)(e)
787
+ A.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:b,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
788
+ try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===A.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${A.length} test run(s) for request ${u}`,data:{testRunIds:T,agentsSpawned:A.length,processCount:S.length,worktreePath:b}}}catch(e){const t=(0,i.getErrorMessage)(e)
723
789
  return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
724
- const r=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(8148),c=".alchemy_lab",l="test-worktrees"},6636(e,exports,t){"use strict"
790
+ const r=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(8148),c=".alchemy_lab",l="test-worktrees"},6563(e,exports,t){"use strict"
791
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
792
+ const s=t(6175),r=t(4627),o=t(4769)
793
+ class n{constructor(e){this.tracking=new Map,this.inFlight=new Set,this.paused=new Set,this.stopped=new Set,this.cachedHooks=[],this.lastHookFetch=0,this.lastActivityTime=0,this.agentTypeCache=new Map,this.logger=e.logger,this.hookExecutor=e.hookExecutor,this.apiClient=e.apiClient,this.getOpenCodeUrl=e.getOpenCodeUrl,this.getDefaultModel=e.getDefaultModel,this.projectId=e.projectId,this.projectPath=e.projectPath,this.conditionEvaluator=new s.TimerConditionEvaluator(e.logger)}start(){this.tickTimer||(this.logger.info("Timer hook scheduler starting",{tickInterval:1e4}),setTimeout((()=>this.tick().catch((()=>{}))),3e3),this.tickTimer=setInterval((()=>{this.tick().catch((()=>{}))}),1e4))}stop(){this.tickTimer&&(clearInterval(this.tickTimer),this.tickTimer=void 0),this.tracking.clear(),this.inFlight.clear(),this.paused.clear(),this.stopped.clear(),this.cachedHooks=[],this.logger.info("Timer hook scheduler stopped")}pauseHook(e){return!this.stopped.has(e)&&(this.paused.add(e),this.logger.info("Timer hook paused",{hookId:e}),!0)}resumeHook(e){const t=this.paused.delete(e)
794
+ return t&&this.logger.info("Timer hook resumed",{hookId:e}),t}stopHook(e){return this.stopped.add(e),this.paused.delete(e),this.inFlight.delete(e),this.tracking.delete(e),this.logger.info("Timer hook stopped",{hookId:e}),!0}setProjectId(e){this.projectId=e}getStatus(){const e=Date.now(),t=this.cachedHooks.map((t=>{const s=this.tracking.get(t.id),r=t.stopAfterInactiveMinutes??10,o=this.paused.has(t.id),n=this.stopped.has(t.id),i=!o&&!n&&this.lastActivityTime>0&&r>0&&e-this.lastActivityTime>6e4*r,a=!(!t.maxIterations||!s||s.fireCount<t.maxIterations),c=!(!(t.timerExpiresAfter&&s&&s.firstFired>0)||(e-s.firstFired)/1e3<t.timerExpiresAfter)
795
+ return{id:t.id,name:t.name,agent:t.agent,action:t.action||"spawn_agent",intervalSeconds:t.intervalSeconds,maxIterations:t.maxIterations,timerExpiresAfter:t.timerExpiresAfter,stopAfterInactiveMinutes:t.stopAfterInactiveMinutes,fireCount:s?.fireCount||0,lastFired:s?.lastFired||0,firstFired:s?.firstFired||0,status:n?"stopped":o?"paused_manual":a?"limit_reached":c?"expired":i?"paused":this.inFlight.has(t.id)?"running":"waiting"}}))
796
+ return{running:!!this.tickTimer,trackedHooks:this.cachedHooks.length,inFlight:Array.from(this.inFlight),lastActivityTime:this.lastActivityTime,hooks:t}}async tick(){if(!this.projectId)return
797
+ const e=this.getOpenCodeUrl()
798
+ if(e)try{const t=await this.fetchTimerHooks()
799
+ if(0===t.length)return
800
+ const s=Date.now()
801
+ await this.updateLastActivityTime(e)
802
+ for(const r of t)try{await this.evaluateAndDispatch(r,s,e)}catch(e){this.logger.warn("Error evaluating timer hook",{hookId:r.id,name:r.name,error:e+""})}const r=new Set(t.map((e=>e.id)))
803
+ for(const e of this.tracking.keys())r.has(e)||this.tracking.delete(e)}catch(e){this.logger.debug("Timer hook tick failed",{error:e+""})}}async evaluateAndDispatch(e,t,s){const r=1e3*(e.intervalSeconds||60),o=this.tracking.get(e.id)
804
+ if(o&&t-o.lastFired<r)return
805
+ if(e.maxIterations&&o&&o.fireCount>=e.maxIterations)return
806
+ if(e.timerExpiresAfter&&o&&o.firstFired>0&&(t-o.firstFired)/1e3>=e.timerExpiresAfter)return
807
+ if(this.paused.has(e.id)||this.stopped.has(e.id))return
808
+ const n=e.stopAfterInactiveMinutes??10
809
+ if(this.lastActivityTime>0&&n>0&&t-this.lastActivityTime>6e4*n)return
810
+ if(this.inFlight.has(e.id))return
811
+ const i=this.parseConditions(e.timerCondition),a=await this.conditionEvaluator.evaluate(i,s,(e=>this.resolveAgentType(e)))
812
+ if(!a.passed)return void this.logger.debug("Timer condition not met",{hook:e.name,reason:a.reason})
813
+ let c=a.targetSessionId||""
814
+ if("inject_prompt"===e.action&&!c&&(c=await this.findSessionByAgentType(e.agent,s)||"",!c))return void this.logger.debug("Timer hook skipped: no target session found",{hook:e.name,agent:e.agent})
815
+ this.logger.info("Firing timer hook",{hook:e.name,hookId:e.id,interval:e.intervalSeconds,fireCount:(o?.fireCount||0)+1,targetSessionId:c||"(spawn)"})
816
+ const l={lastFired:t,firstFired:o?.firstFired||t,fireCount:(o?.fireCount||0)+1}
817
+ this.tracking.set(e.id,l),this.inFlight.add(e.id),this.dispatchHook(e,c,a.variables).finally((()=>{this.inFlight.delete(e.id)}))}async dispatchHook(e,t,s){try{const n=this.tracking.get(e.id),i={...s,hookName:e.name,hookId:e.id,intervalSeconds:(e.intervalSeconds||0)+"",fireCount:(n?.fireCount||0)+"",sessionId:t||""},a=(0,r.interpolatePrompt)(e.promptTemplate,i),c={logger:this.logger,projectId:this.projectId,projectPath:this.projectPath,apiClient:this.apiClient,cachedDefaultModel:this.getDefaultModel()}
818
+ "inject_prompt"===e.action&&t?await(0,o.executeSameSession)(a,t,c):await(0,o.executeBackground)(e,a,t,void 0,c),this.apiClient.post(`/api/projects/${this.projectId}/hook-triggers`,{hookId:e.id,hookName:e.name,eventType:"timer",action:e.action,target:e.target,agent:e.agent}).catch((()=>{}))}catch(t){this.logger.warn("Timer hook dispatch failed",{hook:e.name,error:t+""})}}async updateLastActivityTime(e){try{const t=await fetch(e+"/session",{signal:AbortSignal.timeout(5e3)})
819
+ if(!t.ok)return
820
+ const s=await t.json()
821
+ let r=0
822
+ for(const e of s)e.time.updated>r&&(r=e.time.updated)
823
+ r>0&&(this.lastActivityTime=r)}catch{}}async fetchTimerHooks(){const e=Date.now()
824
+ if(3e4>e-this.lastHookFetch&&this.cachedHooks.length>0)return this.cachedHooks
825
+ try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/event-hooks/timer`),s=t?.data||t||[]
826
+ this.cachedHooks=s.map((e=>({id:e.id||e.hookId,name:e.name||e.hookName,eventType:"timer",agent:e.agent||e.hookAgent,promptTemplate:e.promptTemplate||e.hookPromptTemplate,condition:e.condition||e.hookCondition||{},enabled:!1!==e.enabled,action:e.action||e.hookAction||"spawn_agent",target:e.target||e.hookTarget||"independent_session",timing:"after",maxIterations:e.maxIterations||e.hookMaxIterations||void 0,intervalSeconds:e.intervalSeconds||e.hookIntervalSeconds||void 0,timerCondition:e.timerCondition||e.hookTimerCondition||void 0,timerExpiresAfter:e.timerExpiresAfter||e.hookTimerExpiresAfter||void 0,stopAfterInactiveMinutes:e.stopAfterInactiveMinutes??e.hookStopAfterInactiveMinutes??10}))),this.lastHookFetch=e,this.cachedHooks.length>0&&this.logger.debug("Fetched timer hooks",{count:this.cachedHooks.length,hooks:this.cachedHooks.map((e=>e.name))})}catch(e){this.logger.debug("Failed to fetch timer hooks",{error:e+""})}return this.cachedHooks}parseConditions(e){return e&&Array.isArray(e)?e.filter((e=>e&&e.type)):[]}async resolveAgentType(e){const t=this.agentTypeCache.get(e)
827
+ if(t&&Date.now()-t.time<n.AGENT_TYPE_CACHE_TTL)return t.type
828
+ try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),s=t?.data||t,r=s?.agentType||""
829
+ return this.agentTypeCache.set(e,{type:r,time:Date.now()}),r}catch{return""}}async findSessionByAgentType(e,t){try{const[s,r]=await Promise.all([fetch(t+"/session",{signal:AbortSignal.timeout(5e3)}),fetch(t+"/session/status",{signal:AbortSignal.timeout(5e3)})])
830
+ if(!s.ok)return this.logger.warn("findSessionByAgentType: failed to fetch sessions",{status:s.status}),null
831
+ const o=await s.json(),n=r.ok?await r.json():{}
832
+ if(0===o.length)return this.logger.debug("findSessionByAgentType: no sessions found"),null
833
+ const i=o.sort(((e,t)=>{const s="running"===n[e.id]?.type||"idle"===n[e.id]?.type?1:0,r="running"===n[t.id]?.type||"idle"===n[t.id]?.type?1:0
834
+ return s!==r?r-s:t.time.updated-e.time.updated})),a=e=>!e||"default"===e||"alchemist"===e||e.includes("alchemist"),c="default"===e||"alchemist"===e,l=Math.min(i.length,15)
835
+ for(let t=0;l>t;t++){const s=i[t],r=await this.resolveAgentType(s.id)
836
+ if(r===e)return this.logger.debug("findSessionByAgentType: exact match",{sessionId:s.id,agentType:r}),s.id
837
+ if(c&&a(r))return this.logger.debug("findSessionByAgentType: orchestrator match",{sessionId:s.id,agentType:r,wanted:e}),s.id}const d=e=>!(!e.parentID&&!e.parentId),u=i.filter((e=>!d(e)))
838
+ return u.length>0?(this.logger.debug("findSessionByAgentType: fallback to root session",{sessionId:u[0].id,wanted:e}),u[0].id):(this.logger.debug("findSessionByAgentType: fallback to most recent session",{sessionId:i[0].id,wanted:e}),i[0].id)}catch(t){return this.logger.warn("findSessionByAgentType failed",{agentType:e,error:t+""}),null}}}exports.TimerHookScheduler=n,n.AGENT_TYPE_CACHE_TTL=6e4},6636(e,exports,t){"use strict"
725
839
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRevertRequestAction=async function(e,t){const{requestId:o,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
726
840
  if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
727
841
  if(!n)return{success:!1,message:"reason is required",error:"Missing reason in payload"}
@@ -737,9 +851,9 @@ const e=await async function(e,t){try{const r=(0,s.simpleGit)(t),o=(await r.raw(
737
851
  for(const e of o){const[t,...s]=e.split(" "),r=s.join(" ")
738
852
  for(const e of n)if(r.includes(e))return{found:!0,commitHash:t}}return{found:!1,message:"No merge commit found for request "+e}}catch(e){return{found:!1,message:"Error searching for merge commit: "+(0,r.getErrorMessage)(e)}}}(o,c)
739
853
  if(!e.found||!e.commitHash)return{success:!1,message:"Could not find merge commit for request "+o,error:"Merge commit not found"}
740
- d=e.commitHash}const u=(0,s.simpleGit)(c),g=await u.status(),h=(await u.branch()).all.includes("main")?"main":"master"
854
+ d=e.commitHash}const u=(0,s.simpleGit)(c),h=await u.status(),g=(await u.branch()).all.includes("main")?"main":"master"
741
855
  let p
742
- g.current!==h&&(i.info("Switching to default branch",{from:g.current,to:h}),await u.checkout(h))
856
+ h.current!==g&&(i.info("Switching to default branch",{from:h.current,to:g}),await u.checkout(g))
743
857
  try{i.info("Executing git revert",{mergeCommitHash:d.substring(0,8)}),await u.raw(["revert","-m","1","--no-edit",d])
744
858
  const e=await u.log({maxCount:1})
745
859
  p=e.latest?.hash||"",i.info("Git revert completed",{originalCommit:d.substring(0,8),revertCommit:p.substring(0,8)})}catch(e){const t=(0,r.getErrorMessage)(e)
@@ -757,13 +871,17 @@ module.exports=require("chalk")},6928(module){"use strict"
757
871
  module.exports=require("path")},6982(module){"use strict"
758
872
  module.exports=require("crypto")},7163(e,exports){"use strict"
759
873
  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"
760
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.tryMarkMessageProcessed=exports.createToolCallHash=exports.isDuplicateToolCall=exports.determineEventToEmit=exports.interpolatePrompt=exports.evaluateCondition=exports.HookCache=exports.HookExecutor=void 0
874
+ 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
761
875
  var s=t(3077)
762
876
  Object.defineProperty(exports,"HookExecutor",{enumerable:!0,get(){return s.HookExecutor}})
763
877
  var r=t(8740)
764
878
  Object.defineProperty(exports,"HookCache",{enumerable:!0,get(){return r.HookCache}})
765
- var o=t(4627)
766
- Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return o.evaluateCondition}}),Object.defineProperty(exports,"interpolatePrompt",{enumerable:!0,get(){return o.interpolatePrompt}}),Object.defineProperty(exports,"determineEventToEmit",{enumerable:!0,get(){return o.determineEventToEmit}}),Object.defineProperty(exports,"isDuplicateToolCall",{enumerable:!0,get(){return o.isDuplicateToolCall}}),Object.defineProperty(exports,"createToolCallHash",{enumerable:!0,get(){return o.createToolCallHash}}),Object.defineProperty(exports,"tryMarkMessageProcessed",{enumerable:!0,get(){return o.tryMarkMessageProcessed}})},7252(module){"use strict"
879
+ var o=t(6563)
880
+ Object.defineProperty(exports,"TimerHookScheduler",{enumerable:!0,get(){return o.TimerHookScheduler}})
881
+ var n=t(6175)
882
+ Object.defineProperty(exports,"TimerConditionEvaluator",{enumerable:!0,get(){return n.TimerConditionEvaluator}})
883
+ var i=t(4627)
884
+ Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return i.evaluateCondition}}),Object.defineProperty(exports,"interpolatePrompt",{enumerable:!0,get(){return i.interpolatePrompt}}),Object.defineProperty(exports,"determineEventToEmit",{enumerable:!0,get(){return i.determineEventToEmit}}),Object.defineProperty(exports,"isDuplicateToolCall",{enumerable:!0,get(){return i.isDuplicateToolCall}}),Object.defineProperty(exports,"createToolCallHash",{enumerable:!0,get(){return i.createToolCallHash}}),Object.defineProperty(exports,"tryMarkMessageProcessed",{enumerable:!0,get(){return i.tryMarkMessageProcessed}})},7252(module){"use strict"
767
885
  module.exports=require("express")},7453(e,exports,t){"use strict"
768
886
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCompleteRequestAction=async function(e,t){const{requestId:o,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
769
887
  if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
@@ -794,16 +912,17 @@ if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId
794
912
  n.info("Starting approve-and-work-request action",{requestId:o})
795
913
  try{const e=await i.get(`/api/projects/${a}/requests/${o}`)
796
914
  if(!e.success||!e.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
797
- const t=e.data
798
- if("pending_approval"!==t.status)return{success:!1,message:`Cannot approve request with status '${t.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+t.status}
915
+ const d=e.data
916
+ if("pending_approval"!==d.status)return{success:!1,message:`Cannot approve request with status '${d.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+d.status}
799
917
  if(!(await i.patch(`/api/projects/${a}/requests/${o}`,{status:"open"})).success)return{success:!1,message:"Failed to update request status",error:"Update failed"}
800
- const d=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",u=`Work on request ${o}: ${t.title}\n\nDescription: ${t.description}\n\n${d?"Acceptance Criteria:\n- "+d:""}\n\nThis request has just been approved. Please begin implementation.`
801
- let g=!1,h=!1
802
- if(t.cliOrigin?.sessionId)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:t.cliOrigin.sessionId,status:"success",summary:`Request ${o} "${t.title}" has been approved. Please begin implementation.`,details:u}),g=!0,n.info("Stored session report for creating session",{requestId:o,sessionId:t.cliOrigin.sessionId})
918
+ const u=Array.isArray(d.acceptanceCriteria)?d.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",h=`Work on request ${o}: ${d.title}\n\nDescription: ${d.description}\n\n${u?"Acceptance Criteria:\n- "+u:""}\n\nThis request has just been approved. Please begin implementation.`
919
+ let g=!1,p=!1
920
+ if(d.cliOrigin?.sessionId)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:d.cliOrigin.sessionId,status:"success",summary:`Request ${o} "${d.title}" has been approved. Please begin implementation.`,details:h}),g=!0,n.info("Stored session report for creating session",{requestId:o,sessionId:d.cliOrigin.sessionId})
803
921
  try{const e=(0,r.getOpenCodeServerUrl)(c,n)
804
- if(e){const s=`\u2705 [Request Approved]\n\nRequest ${o} "${t.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${u}`,r=`${e}/session/${t.cliOrigin.sessionId}/prompt_async`,i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:s}]})})
805
- i.ok?n.info("Pushed notification to CLI session",{sessionId:t.cliOrigin.sessionId}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,s.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify creating session, spawning work agent instead",{requestId:o,error:(0,s.getErrorMessage)(e)})}if(!g)try{const e=await(0,r.spawnActionAgent)({agent:"work",prompt:u,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${t.title}`,requestId:o,apiClient:i,logger:n,trackChildPid:l})
806
- h=e.success,h?n.info("Spawned work agent for request",{requestId:o,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:o,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:o,error:(0,s.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:o,notifiedSession:g,agentSpawned:h}),{success:!0,message:`Successfully approved request ${o}: ${t.title}${g?" \u2014 creating session notified":h?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:g,agentSpawned:h}}}catch(e){const t=(0,s.getErrorMessage)(e)
922
+ if(e){const s=`\u2705 [Request Approved]\n\nRequest ${o} "${d.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${h}`,r=`${e}/session/${d.cliOrigin.sessionId}/prompt_async`,i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:s}],...t.cachedDefaultModel?(()=>{const[e,...s]=t.cachedDefaultModel.split("/")
923
+ return{model:{providerID:e,modelID:s.join("/")}}})():{}})})
924
+ i.ok?n.info("Pushed notification to CLI session",{sessionId:d.cliOrigin.sessionId}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,s.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify creating session, spawning work agent instead",{requestId:o,error:(0,s.getErrorMessage)(e)})}if(!g)try{const e=await(0,r.spawnActionAgent)({agent:"work",prompt:h,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${d.title}`,requestId:o,apiClient:i,logger:n,trackChildPid:l})
925
+ p=e.success,p?n.info("Spawned work agent for request",{requestId:o,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:o,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:o,error:(0,s.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:o,notifiedSession:g,agentSpawned:p}),{success:!0,message:`Successfully approved request ${o}: ${d.title}${g?" \u2014 creating session notified":p?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:g,agentSpawned:p}}}catch(e){const t=(0,s.getErrorMessage)(e)
807
926
  return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
808
927
  const s=t(8824),r=t(8148)},7960(e,exports){"use strict"
809
928
  function t(){const e=process.argv[1]||""
@@ -811,24 +930,24 @@ if(e.includes("/dist-dev/")||e.includes("\\dist-dev\\"))return!0
811
930
  if(e.includes("/dist/")||e.includes("\\dist\\"))return!1
812
931
  const t=process.cwd()
813
932
  return!(!t.includes("/dist-dev")&&!t.includes("\\dist-dev"))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.isDevBuild=t,exports.getBuildMode=function(){return t()?"development":"production"},exports.enableDebugMode=function(){return"true"===process.env.ENABLE_DEBUG_TOOLS&&(process.env.DEBUG_LOGGING="true",!0)}},8035(e,exports,t){"use strict"
814
- function s(e,t,s){try{const r=`[${(new Date).toISOString()}] [detached-subagent] ${t}${s?" "+JSON.stringify(s):""}\n`,o=(0,i.join)(e||process.cwd(),".alchemy_lab","spawn-debug.log");(0,n.appendFileSync)(o,r)}catch{}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=function(e){const{agent:t,prompt:c,cwd:l,sessionId:d,model:u,initialBranch:g,serverUrl:h,onSpawn:p}=e
933
+ function s(e,t,s){try{const r=`[${(new Date).toISOString()}] [detached-subagent] ${t}${s?" "+JSON.stringify(s):""}\n`,o=(0,i.join)(e||process.cwd(),".alchemy_lab","spawn-debug.log");(0,n.appendFileSync)(o,r)}catch{}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=function(e){const{agent:t,prompt:c,cwd:l,sessionId:d,model:u,initialBranch:h,serverUrl:g,onSpawn:p}=e
815
934
  try{const o={...process.env},f=l||process.cwd()
816
- o.OPENCODE_DATA_DIR=(0,i.join)(f,".alchemy_lab","opencode"),g&&(o.ALCHEMY_INITIAL_BRANCH=g),e.allowParallel&&(o.ALCHEMY_ALLOW_PARALLEL="1")
935
+ o.OPENCODE_DATA_DIR=(0,i.join)(f,".alchemy_lab","opencode"),h&&(o.ALCHEMY_INITIAL_BRANCH=h),e.allowParallel&&(o.ALCHEMY_ALLOW_PARALLEL="1")
817
936
  const m=new Date,y=`${(m.getHours()+"").padStart(2,"0")}${(m.getMinutes()+"").padStart(2,"0")}${(m.getSeconds()+"").padStart(2,"0")}`,v=Math.random().toString(36).substring(2,7)
818
937
  let w
819
- if(o.ALCHEMY_SESSION_TAG=`mcp-${y}-${v}`,s(l,"Spawn env setup",{agent:t,initialBranch:g||"none",sessionId:d||"new",attachMode:!!h}),w=h&&d&&c?["run","--attach",h,"--session",d,"--agent",t,c]:d&&c?["run","--session",d,"--agent",t,c]:c?["run","--agent",t,c]:["run","--agent",t],u){const e=w.findIndex((e=>e===c))
938
+ if(o.ALCHEMY_SESSION_TAG=`mcp-${y}-${v}`,s(l,"Spawn env setup",{agent:t,initialBranch:h||"none",sessionId:d||"new",attachMode:!!g}),w=g&&d&&c?["run","--attach",g,"--session",d,"--agent",t,c]:d&&c?["run","--session",d,"--agent",t,c]:c?["run","--agent",t,c]:["run","--agent",t],u){const e=w.findIndex((e=>e===c))
820
939
  e>0?w.splice(e,0,"--model",u):w.push("--model",u)}s(l,"Spawning opencode",{args:w.map((e=>e===c?`[prompt:${e.length}chars]`:e)),model:u||"not specified",promptLength:c?.length,promptPreview:c?.slice(0,100),cwd:l,hasSessionId:!!d})
821
- const C=(0,i.join)(l||process.cwd(),".alchemy_lab"),I=(0,i.join)(C,"spawn-stderr.log")
940
+ const k=(0,i.join)(l||process.cwd(),".alchemy_lab"),I=(0,i.join)(k,"spawn-stderr.log")
822
941
  let S
823
- try{S=(0,n.openSync)(I,"a")}catch{}const k=(0,a.resolveRunCommand)(),b=(0,r.spawn)(k.command,[...k.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:o})
942
+ try{S=(0,n.openSync)(I,"a")}catch{}const C=(0,a.resolveRunCommand)(),b=(0,r.spawn)(C.command,[...C.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:o})
824
943
  return b.unref(),p&&p(b.pid),{success:!0,pid:b.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
825
944
  const r=t(5317),o=t(8824),n=t(9896),i=t(6928),a=t(9068)},8148(e,exports,t){"use strict"
826
945
  function s(e,t){try{const t=(0,o.join)(e,c,"opencode.json"),s=(0,r.readFileSync)(t,"utf8"),n=JSON.parse(s)
827
- if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,i.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=s,exports.spawnActionAgent=async function(e){const{agent:t,prompt:r,cwd:o,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:g,apiClient:h,logger:p,trackChildPid:f}=e
946
+ if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,i.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=s,exports.spawnActionAgent=async function(e){const{agent:t,prompt:r,cwd:o,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:h,apiClient:g,logger:p,trackChildPid:f}=e
828
947
  let m,y,v,w=null
829
- try{const s=new a.SessionSpawnService({apiClient:h,logger:p,projectId:e.projectId,getDefaultModel:e.getDefaultModel||(()=>{})}),o=await s.prepareSession({agent:t,prompt:r,projectPath:c,requestId:d,parentSessionId:u,title:l,allowParallel:g})
830
- w=o.sessionId,m=o.model,y=o.serverUrl,v=o.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,i.getErrorMessage)(e)}),y=s(c,p)||void 0}const C=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:w||void 0,serverUrl:y,model:m,initialBranch:v,allowParallel:g??!1,onSpawn(e){e&&f&&f(e)}})
831
- return C.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:C.pid,requestId:d}),{success:!0,sessionId:w,pid:C.pid}):{success:!1,sessionId:w,error:C.error||"Spawn failed"}}
948
+ try{const s=new a.SessionSpawnService({apiClient:g,logger:p,projectId:e.projectId,getDefaultModel:e.getDefaultModel||(()=>{})}),o=await s.prepareSession({agent:t,prompt:r,projectPath:c,requestId:d,parentSessionId:u,title:l,allowParallel:h})
949
+ w=o.sessionId,m=o.model,y=o.serverUrl,v=o.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,i.getErrorMessage)(e)}),y=s(c,p)||void 0}const k=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:w||void 0,serverUrl:y,model:m,initialBranch:v,allowParallel:h??!1,onSpawn(e){e&&f&&f(e)}})
950
+ return k.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:k.pid,requestId:d}),{success:!0,sessionId:w,pid:k.pid}):{success:!1,sessionId:w,error:k.error||"Spawn failed"}}
832
951
  const r=t(9896),o=t(6928),n=t(8035),i=t(8824),a=t(1292),c=".alchemy_lab"},8524(e,exports,t){"use strict"
833
952
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
834
953
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
@@ -847,7 +966,7 @@ let n=null},8577(module){"use strict"
847
966
  module.exports=require("cors")},8740(e,exports){"use strict"
848
967
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookCache=void 0,exports.HookCache=class{constructor(e,t,s){this.cache=new Map,this.logger=e,this.projectId=t,this.apiClient=s}setProjectId(e){this.projectId=e}getCacheKey(e,t){return t?`${e}:session:${t}`:e+":all"}async getHooksForEvent(e,t,s){const r=this.getCacheKey(e,t)
849
968
  if(!s){const e=this.cache.get(r)
850
- if(e&&6e4>Date.now()-e.time)return e.hooks.filter((e=>e.enabled))}try{const s=t?"?sessionId="+encodeURIComponent(t):"",o=await this.apiClient.get(`/api/projects/${this.projectId}/event-hooks/by-event/${e}${s}`),n=(o?.data||o||[]).map((e=>({id:e.id||e.hookId,name:e.name||e.hookName,eventType:e.eventType||e.hookEventType,agent:e.agent||e.hookAgent,promptTemplate:e.promptTemplate||e.hookPromptTemplate,condition:e.condition||e.hookCondition||{},enabled:!1!==e.enabled,action:e.action||e.hookAction||void 0,target:e.target||e.hookTarget||void 0,timing:e.timing||e.hookTiming||"after",maxIterations:e.maxIterations||e.hookMaxIterations||void 0,emitEvent:e.emitEvent||e.hookEmitEvent||void 0,emitEventOnCondition:e.emitEventOnCondition||e.hookEmitEventOnCondition||void 0})))
969
+ if(e&&6e4>Date.now()-e.time)return e.hooks.filter((e=>e.enabled))}try{const s=t?"?sessionId="+encodeURIComponent(t):"",o=await this.apiClient.get(`/api/projects/${this.projectId}/event-hooks/by-event/${e}${s}`),n=(o?.data||o||[]).map((e=>({id:e.id||e.hookId,name:e.name||e.hookName,eventType:e.eventType||e.hookEventType,agent:e.agent||e.hookAgent,promptTemplate:e.promptTemplate||e.hookPromptTemplate,condition:e.condition||e.hookCondition||{},enabled:!1!==e.enabled,action:e.action||e.hookAction||void 0,target:e.target||e.hookTarget||void 0,timing:e.timing||e.hookTiming||"after",maxIterations:e.maxIterations||e.hookMaxIterations||void 0,emitEvent:e.emitEvent||e.hookEmitEvent||void 0,emitEventOnCondition:e.emitEventOnCondition||e.hookEmitEventOnCondition||void 0,intervalSeconds:e.intervalSeconds||e.hookIntervalSeconds||void 0,timerCondition:e.timerCondition||e.hookTimerCondition||void 0,timerExpiresAfter:e.timerExpiresAfter||e.hookTimerExpiresAfter||void 0,stopAfterInactiveMinutes:e.stopAfterInactiveMinutes??e.hookStopAfterInactiveMinutes??10})))
851
970
  return this.cache.set(r,{hooks:n,time:Date.now()}),n.filter((e=>e.enabled))}catch(t){return this.logger.warn("Failed to fetch hooks",{eventType:e,error:t+""}),[]}}preWarm(e,t){this.getHooksForEvent(e,t).catch((()=>{}))}clear(){this.cache.clear()}}},8824(e,exports){"use strict"
852
971
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.getErrorMessage=function(e){return e instanceof Error?e.message:"string"==typeof e?e:null===e?"null error":void 0===e?"undefined error":e+""},exports.getErrorStack=function(e){return e instanceof Error?e.stack||e.message:e+""},exports.formatErrorForLog=function(e){return e instanceof Error?{message:e.message,stack:e.stack}:{message:e+""}},exports.isError=function(e){return e instanceof Error}},8955(e,exports){"use strict"
853
972
  function t(){return n||r}function s(){return t().map((e=>e.name))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.initializeAgentRegistry=async function(e,t){return a||(a=(async()=>{try{if(!e)return t.debug("No API client available, using default agents"),n=r,void(i=!0)
@@ -858,8 +977,8 @@ return t?{preferredProvider:t.preferredProvider||"inherit",preferredModelTier:t.
858
977
  const r=[{id:"coder",name:"coder",description:"Development and implementation agent",builtIn:!0,preferredModelTier:"same",requestContextMode:"required"},{id:"research",name:"research",description:"Codebase research and exploration agent",builtIn:!0,preferredModelTier:"two_down",requestContextMode:"none"},{id:"journal",name:"journal",description:"Journal generation agent",builtIn:!0,preferredModelTier:"one_down"},{id:"planner",name:"planner",description:"Planning and architecture agent",builtIn:!0,preferredModelTier:"same"},{id:"specialist",name:"specialist",description:"Domain specialist for development tasks",builtIn:!0,preferredModelTier:"same"},{id:"auto-planner",name:"auto-planner",description:"Autonomous background planner",builtIn:!0,preferredModelTier:"same"},{id:"alchemist",name:"alchemist",description:"Persistent orchestrator",builtIn:!0,preferredModelTier:"same"}],o={alchemist:["specialist","research","planner"],specialist:["coder","research","auto-planner","planner"],tester:["research"],"auto-tester":["research"]}
859
978
  let n=null,i=!1,a=null},8984(e,exports,t){"use strict"
860
979
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.MCPClientConfigurer=void 0
861
- const s=t(8824)
862
- class r{constructor(e,t,s){this.logger=e,this.remoteServerUrl=t||process.env.REMOTE_SERVER_URL,this.authToken=s}async seedAgentsFromRemote(e){const t=this.remoteServerUrl
980
+ const s=t(8824),r=t(5671)
981
+ class o{constructor(e,t,s){this.logger=e,this.remoteServerUrl=t||process.env.REMOTE_SERVER_URL,this.authToken=s}async seedAgentsFromRemote(e){const t=this.remoteServerUrl
863
982
  if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot initialize agents"),!1
864
983
  try{const s=t+"/api/agents/seed"
865
984
  this.logger.info("Ensuring built-in agents exist",{url:s})
@@ -886,50 +1005,52 @@ if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot fetch project f
886
1005
  try{const s=await fetch(`${t}/api/projects/${e}`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:"Bearer "+this.authToken}:{}}})
887
1006
  if(!s.ok)return this.logger.warn("Failed to fetch project from remote",{projectId:e,status:s.status,statusText:s.statusText}),null
888
1007
  const r=await s.json()
889
- return r.data||r}catch(t){return this.logger.error("Error fetching project from remote",{projectId:e,error:(0,s.getErrorMessage)(t)}),null}}generateAgentMarkdown(e,t,s,o){const n=Object.entries(e.tools||{}).map((([e,s])=>{const o=e.replace(/-/g,"_")
890
- return` ${r.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${o}`}: ${s}`})).join("\n")
891
- let i=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${s}"\n`
892
- if(e.color&&(i+=`color: "${e.color}"\n`),i+=`tools:\n${n}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){i+="permission:\n"
893
- for(const[t,s]of Object.entries(e.opencodePermission))if("object"==typeof s&&null!==s){i+=` ${t}:\n`
894
- for(const[e,t]of Object.entries(s))i+=` ${e}: ${t}\n`}else i+=` ${t}: ${s}\n`}return void 0!==e.temperature&&(i+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(i+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(i+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),i+="---\n\n",o&&(i+=o.trim()+"\n\n"),e.prompt&&(i+=e.prompt),e.promptAddition&&(i+="\n\n"+e.promptAddition),i}async configureOpenCode(e,o,n="node",i,a){try{const s=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=c.join(e,"mcp-direct.js")
895
- this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:o})
896
- const g=c.join(o,"opencode.json")
897
- let h=null,p=null,f=null,m=null,y=!1,v=!1
898
- const w=c.join(o,".alchemy_lab","config.json")
1008
+ return r.data||r}catch(t){return this.logger.error("Error fetching project from remote",{projectId:e,error:(0,s.getErrorMessage)(t)}),null}}generateAgentMarkdown(e,t,s,n){let i=s
1009
+ if(e.preferredModelTier&&"same"!==e.preferredModelTier){const t=(0,r.resolveModelTier)(s,e.preferredProvider||"inherit",e.preferredModelTier,{anthropic:!0,openai:!0})
1010
+ t.resolved&&(i=t.fullModel)}const a=Object.entries(e.tools||{}).map((([e,s])=>{const r=e.replace(/-/g,"_")
1011
+ return` ${o.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${r}`}: ${s}`})).join("\n")
1012
+ let c=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${i}"\n`
1013
+ if(e.color&&(c+=`color: "${e.color}"\n`),c+=`tools:\n${a}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){c+="permission:\n"
1014
+ for(const[t,s]of Object.entries(e.opencodePermission))if("object"==typeof s&&null!==s){c+=` ${t}:\n`
1015
+ for(const[e,t]of Object.entries(s))c+=` ${e}: ${t}\n`}else c+=` ${t}: ${s}\n`}return void 0!==e.temperature&&(c+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(c+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(c+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),c+="---\n\n",n&&(c+=n.trim()+"\n\n"),e.prompt&&(c+=e.prompt),e.promptAddition&&(c+="\n\n"+e.promptAddition),c}async configureOpenCode(e,r,n="node",i,a){try{const s=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=c.join(e,"mcp-direct.js")
1016
+ this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:r})
1017
+ const h=c.join(r,"opencode.json")
1018
+ let g=null,p=null,f=null,m=null,y=!1,v=!1
1019
+ const w=c.join(r,".alchemy_lab","config.json")
899
1020
  if(!i){try{const e=await s.readFile(w,"utf-8"),t=JSON.parse(e)
900
- t?.openCodeTools?(h=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:h})):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 s.readFile(g,"utf-8"),t=JSON.parse(e)
901
- t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const C=i?.openCodeTools||h||f||{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:C},S=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
1021
+ 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 s.readFile(h,"utf-8"),t=JSON.parse(e)
1022
+ t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const k=i?.openCodeTools||g||f||{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:k},S=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
902
1023
  i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:m&&(I.model=m)
903
- const k=c.join(o,".opencode","agent")
904
- await s.mkdir(k,{recursive:!0})
1024
+ const C=c.join(r,".opencode","agent")
1025
+ await s.mkdir(C,{recursive:!0})
905
1026
  const b=S||"anthropic/claude-opus-4-5-20251101"
906
1027
  let E,j=null,T=null
907
1028
  if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),E=T?.metadata?.globalSystemPrompt,E&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:E.length}),j=await this.fetchAgentsFromRemote(i.projectId),j&&j.length>0){this.logger.info("Using agents from remote database",{count:j.length})
908
1029
  for(const e of j){if(!e.enabled)continue
909
- const t=this.generateAgentMarkdown(e,d,b,E),r=c.join(k,e.name+".md")
1030
+ const t=this.generateAgentMarkdown(e,d,b,E),r=c.join(C,e.name+".md")
910
1031
  await s.writeFile(r,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:r,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
911
1032
  else this.logger.warn("No projectId provided - agents must be configured via database")
912
- const P={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,o],enabled:!0}}}
913
- if(l&&process.env.REMOTE_SERVER_URL&&(P.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),j&&j.length>0){P.agent={}
1033
+ const A={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,r],enabled:!0}}}
1034
+ if(l&&process.env.REMOTE_SERVER_URL&&(A.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),j&&j.length>0){A.agent={}
914
1035
  for(const e of j){if(!e.enabled)continue
915
1036
  const t={}
916
- for(const[s,o]of Object.entries(e.tools||{})){const e=o,n=s.replace(/-/g,"_")
917
- t[r.OPENCODE_BUILTIN_TOOLS.has(s)?s:`${d}_${n}`]=e}const s=T?.metadata?.bashToolAgents||{}
1037
+ for(const[s,r]of Object.entries(e.tools||{})){const e=r,n=s.replace(/-/g,"_")
1038
+ t[o.OPENCODE_BUILTIN_TOOLS.has(s)?s:`${d}_${n}`]=e}const s=T?.metadata?.bashToolAgents||{}
918
1039
  e.name in s&&(t.bash=!!s[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name)&&void 0===t.task&&(t.task=!1)
919
- let o=""
920
- E&&(o+=E.trim()+"\n\n"),e.prompt&&(o+=e.prompt),e.promptAddition&&(o+="\n\n"+e.promptAddition),P.agent[e.name]={tools:t,...o?{prompt:o}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}P.agent.general={...P.agent.general,disable:!0},P.agent.explore={...P.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}}
1040
+ let r=""
1041
+ E&&(r+=E.trim()+"\n\n"),e.prompt&&(r+=e.prompt),e.promptAddition&&(r+="\n\n"+e.promptAddition),A.agent[e.name]={tools:t,...r?{prompt:r}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}A.agent.general={...A.agent.general,disable:!0},A.agent.explore={...A.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}}
921
1042
  const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${E?E.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`
922
- await s.writeFile(c.join(k,"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(P.agent).length})}const A=[]
923
- A.push("file://"+c.join(o,".opencode","plugin","inject-session.js")),A.push("file://"+c.join(o,".opencode","plugin","event-hooks.js")),A.push("file://"+c.join(o,".opencode","plugin","system-logger.js")),A.length>0&&(P.plugin=A),await s.writeFile(g,JSON.stringify(P,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:g,plugins:A})
1043
+ await s.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(A.agent).length})}const P=[]
1044
+ P.push("file://"+c.join(r,".opencode","plugin","inject-session.js")),P.push("file://"+c.join(r,".opencode","plugin","event-hooks.js")),P.push("file://"+c.join(r,".opencode","plugin","system-logger.js")),P.length>0&&(A.plugin=P),await s.writeFile(h,JSON.stringify(A,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:h,plugins:P})
924
1045
  try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),r=c.join(e.homedir(),".alchemy_lab","opencode","config")
925
1046
  await s.mkdir(r,{recursive:!0})
926
1047
  const o=c.join(r,"opencode.json")
927
1048
  let n={}
928
1049
  try{n=JSON.parse(await s.readFile(o,"utf-8"))}catch{}const i=(n.plugin||[]).filter((e=>!e.includes(".opencode/plugin/")))
929
- n.plugin=[...i,...A],n.$schema=n.$schema||"https://opencode.ai/config.json",await s.writeFile(o,JSON.stringify(n,null,2),"utf8"),this.logger.info("Updated global alchemy opencode config",{globalConfigPath:o,plugins:n.plugin})}catch(e){this.logger.warn("Failed to write global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(v&&!i)try{let e={}
1050
+ n.plugin=[...i,...P],n.$schema=n.$schema||"https://opencode.ai/config.json",await s.writeFile(o,JSON.stringify(n,null,2),"utf8"),this.logger.info("Updated global alchemy opencode config",{globalConfigPath:o,plugins:n.plugin})}catch(e){this.logger.warn("Failed to write global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(v&&!i)try{let e={}
930
1051
  try{const t=await s.readFile(w,"utf-8")
931
- e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=C,await s.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPlugins:s}=await t.e(939).then(t.t.bind(t,9939,23)),r=await s(o,l??!1,this.logger,e)
932
- r.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:r.synced}),r.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:r.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:g}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,s.getErrorMessage)(e)}),{success:!1,error:(0,s.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=r,r.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9067(e,exports,t){"use strict"
1052
+ e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=k,await s.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPlugins:s}=await t.e(939).then(t.t.bind(t,9939,23)),o=await s(r,l??!1,this.logger,e)
1053
+ o.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:o.synced}),o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:h}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,s.getErrorMessage)(e)}),{success:!1,error:(0,s.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=o,o.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9067(e,exports,t){"use strict"
933
1054
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
934
1055
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
935
1056
  const r=s(t(6982)),o=t(8824)
@@ -1028,73 +1149,74 @@ for(var i in s)e.o(s,i)&&(e.m[i]=s[i])
1028
1149
  n&&n(e)
1029
1150
  for(var a=0;a<o.length;a++)r[o[a]]=1})(require("./"+e.u(t)))}
1030
1151
  var i={};(()=>{"use strict"
1031
- function t(){const e=[(0,g.join)(__dirname,"..","package.json"),(0,g.join)(__dirname,"..","..","package.json")]
1152
+ function t(){const e=[(0,h.join)(__dirname,"..","package.json"),(0,h.join)(__dirname,"..","..","package.json")]
1032
1153
  for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
1033
1154
  if(e.version)return e.version}catch{}return"unknown"}function s(){const e=t()
1034
- console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start action server + launch OpenCode (default)\n serve Start the action server only (for GUI-triggered actions)\n logout Log out and remove stored auth token\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy serve # Start action server only\n alchemy logout # Log out\nFor more information, visit: https://alchemylabcode.com/\n`)}function r(e){const t=(0,g.join)(e,S.ALCHEMY_DIR_NAME,"project.json")
1035
- return(0,d.existsSync)(t)}function o(e){const t=(0,p.platform)(),s="darwin"===t?"open":"win32"===t?"cmd":"xdg-open",r="win32"===t?["/c","start","",e]:[e];(0,h.spawn)(s,r,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((s=>{const r=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(r),s(!0))}),2e3)
1155
+ console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start action server + launch OpenCode (default)\n serve Start the action server only (for GUI-triggered actions)\n logout Log out and remove stored auth token\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy serve # Start action server only\n alchemy logout # Log out\nFor more information, visit: https://alchemylabcode.com/\n`)}function r(e){const t=(0,h.join)(e,S.ALCHEMY_DIR_NAME,"project.json")
1156
+ return(0,d.existsSync)(t)}function o(e){const t=(0,p.platform)(),s="darwin"===t?"open":"win32"===t?"cmd":"xdg-open",r="win32"===t?["/c","start","",e]:[e];(0,g.spawn)(s,r,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((s=>{const r=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(r),s(!0))}),2e3)
1036
1157
  setTimeout((()=>{clearInterval(r),s(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
1037
1158
  try{const{createConfigManager:s}=await e.e(60).then(e.t.bind(e,9060,23)),r=s(t)
1038
- return(await r.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,s,r,o="cli-init"){try{const n=(0,g.basename)(s)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:s,createdVia:o}})})
1159
+ return(await r.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,s,r,o="cli-init"){try{const n=(0,h.basename)(s)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:s,createdVia:o}})})
1039
1160
  if(i.ok){const e=await i.json()
1040
- return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)}catch(e){r.info("Project registration failed",{error:(0,C.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
1041
- const l=e(3444),d=e(9896),u=e(1943),g=e(6928),h=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),C=e(8824),I=e(6800),S=e(4139);(async function(){const i=process.argv.slice(2)
1042
- let p
1043
- const k=i.indexOf("--port");-1!==k&&i[k+1]&&(p=parseInt(i[k+1],10),isNaN(p)&&(console.error("\u274c Invalid port number"),process.exit(1)))
1044
- const b=i.filter(((e,t)=>"--port"!==e&&(-1===k||t!==k+1)))
1045
- if(0===b.length)return void await async function(s={}){const i=process.cwd(),l=(0,g.join)(i,S.ALCHEMY_DIR_NAME)
1161
+ return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)}catch(e){r.info("Project registration failed",{error:(0,k.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
1162
+ const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),k=e(8824),I=e(6800),S=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))
1163
+ const i=process.argv.slice(2)
1164
+ let C
1165
+ const b=i.indexOf("--port");-1!==b&&i[b+1]&&(C=parseInt(i[b+1],10),isNaN(C)&&(console.error("\u274c Invalid port number"),process.exit(1)))
1166
+ const E=i.filter(((e,t)=>"--port"!==e&&(-1===b||t!==b+1)))
1167
+ if(0===E.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
1046
1168
  await(0,u.mkdir)(l,{recursive:!0})
1047
- const p=(0,g.join)(l,"server.log"),{createLogger:k}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=k({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),E=await a(b),j=new f.TokenManager(b,i,{remoteServerUrl:E,allowInteractiveAuth:!1})
1048
- await j.initialize()
1049
- let T=await j.getToken()
1050
- if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
1051
- const e=await j.startAuthenticationFlow()
1052
- console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(j)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),T=await j.getToken(),console.log(`\u2705 Welcome, ${T?.username||"user"}!`),console.log("")}let P=T?.token
1169
+ const C=(0,h.join)(l,"server.log"),{createLogger:b}=await Promise.resolve().then(e.t.bind(e,2782,23)),E=b({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:C,baseDir:i}),j=await a(E),T=new f.TokenManager(E,i,{remoteServerUrl:j,allowInteractiveAuth:!1})
1170
+ await T.initialize()
1171
+ let A=await T.getToken()
1172
+ if(!A?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
1173
+ const e=await T.startAuthenticationFlow()
1174
+ console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(T)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),A=await T.getToken(),console.log(`\u2705 Welcome, ${A?.username||"user"}!`),console.log("")}let P=A?.token
1053
1175
  await async function(e,t,s,o){if(r(e))return
1054
1176
  console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
1055
- const n=(0,g.join)(e,S.ALCHEMY_DIR_NAME)
1177
+ const n=(0,h.join)(e,S.ALCHEMY_DIR_NAME)
1056
1178
  let i
1057
- await(0,u.mkdir)(n,{recursive:!0}),s?(console.log(`\u{1f4cb} Registering project "${(0,g.basename)(e)||"Local Project"}"...`),i=await c(t,s,e,o),console.log(i?"\u2705 Project registered: "+i:"\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token \u2014 skipping project registration")
1058
- const a=(0,g.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,g.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,E,P,b)
1059
- const A=(0,g.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
1060
- let M
1061
- try{const e=JSON.parse((0,d.readFileSync)(A,"utf-8"))
1062
- if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(E,P,i,b,"cli-init-retry")
1063
- t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(A,JSON.stringify(e,null,2),"utf-8"),M=t,console.log("\u2705 Project registered: "+t))}}catch{b.warn("Could not read project.json")}await async function(t){try{return void(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
1064
- try{const e=(0,g.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
1179
+ await(0,u.mkdir)(n,{recursive:!0}),s?(console.log(`\u{1f4cb} Registering project "${(0,h.basename)(e)||"Local Project"}"...`),i=await c(t,s,e,o),console.log(i?"\u2705 Project registered: "+i:"\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token \u2014 skipping project registration")
1180
+ const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,j,P,E)
1181
+ const M=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
1182
+ let _
1183
+ try{const e=JSON.parse((0,d.readFileSync)(M,"utf-8"))
1184
+ if(_=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,E,"cli-init-retry")
1185
+ t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(M,JSON.stringify(e,null,2),"utf-8"),_=t,console.log("\u2705 Project registered: "+t))}}catch{E.warn("Could not read project.json")}await async function(t){try{return void(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
1186
+ try{const e=(0,h.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
1065
1187
  if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
1066
1188
  t.opencodeVersion&&(s=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
1067
1189
  const r="latest"===s?"latest":"v"+s
1068
- try{(0,h.execFileSync)("which",["go"],{stdio:"pipe"}),console.log(" Running: go install github.com/sst/opencode@"+r),(0,h.execFileSync)("go",["install","github.com/sst/opencode@"+r],{stdio:"inherit",timeout:12e4})
1069
- try{return(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"}),void console.log("\u2705 OpenCode installed successfully via go install")}catch{console.log("\u26a0\ufe0f go install completed but opencode not found in PATH"),console.log(" Ensure $(go env GOPATH)/bin is in your PATH")}}catch{}try{const{platform:t,arch:r}=await Promise.resolve().then(e.t.bind(e,857,23)),o=t(),n=r(),i="darwin"===o?"darwin":"linux"===o?"linux":null,a="arm64"===n?"arm64":"x64"===n?"amd64":null
1190
+ try{(0,g.execFileSync)("which",["go"],{stdio:"pipe"}),console.log(" Running: go install github.com/sst/opencode@"+r),(0,g.execFileSync)("go",["install","github.com/sst/opencode@"+r],{stdio:"inherit",timeout:12e4})
1191
+ try{return(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"}),void console.log("\u2705 OpenCode installed successfully via go install")}catch{console.log("\u26a0\ufe0f go install completed but opencode not found in PATH"),console.log(" Ensure $(go env GOPATH)/bin is in your PATH")}}catch{}try{const{platform:t,arch:r}=await Promise.resolve().then(e.t.bind(e,857,23)),o=t(),n=r(),i="darwin"===o?"darwin":"linux"===o?"linux":null,a="arm64"===n?"arm64":"x64"===n?"amd64":null
1070
1192
  if(i&&a){const e=`opencode_${i}_${a}`,t="latest"===s?"https://github.com/sst/opencode/releases/latest/download/"+e:`https://github.com/sst/opencode/releases/download/v${s}/${e}`
1071
1193
  console.log(" Downloading from: "+t)
1072
- const r=(0,g.join)(process.env.HOME||"/usr/local/bin",".local","bin")
1194
+ const r=(0,h.join)((0,p.homedir)(),".local","bin")
1073
1195
  await(0,u.mkdir)(r,{recursive:!0})
1074
- const o=(0,g.join)(r,"opencode")
1075
- return(0,h.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,h.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(r)||(console.log(` \u26a0\ufe0f Add ${r} to your PATH:`),console.log(` export PATH="${r}:$PATH"`),console.log(""),process.env.PATH=`${r}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,C.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
1076
- const _=t()
1077
- console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+_,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
1078
- const O=s.port||await(0,I.findAvailablePort)(3456),x=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:E,port:O,authToken:P})
1079
- try{const t=(0,v.getInstallContext)(),s=new m.MCPClientConfigurer(b,E,P),r=new w.UnifiedLauncher(b,t,s),[,a,c]=await Promise.all([x.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
1080
- if(E.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
1081
- try{const e=await fetch(E+"/health")
1196
+ const o=(0,h.join)(r,"opencode")
1197
+ return(0,g.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,g.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(r)||(console.log(` \u26a0\ufe0f Add ${r} to your PATH:`),console.log(` export PATH="${r}:$PATH"`),console.log(""),process.env.PATH=`${r}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,k.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
1198
+ const O=t()
1199
+ console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+O,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
1200
+ const x=s.port||await(0,I.findAvailablePort)(3456),$=new y.AlchemyActionServer(E,{projectPath:i,remoteServerUrl:j,port:x,authToken:P})
1201
+ try{const t=(0,v.getInstallContext)(),s=new m.MCPClientConfigurer(E,j,P),r=new w.UnifiedLauncher(E,t,s),[,a,c]=await Promise.all([$.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
1202
+ if(j.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
1203
+ try{const e=await fetch(j+"/health")
1082
1204
  if(e.ok){const t=await e.json()
1083
- if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return E.replace(/:\d+/,":3000")})(),r.launch({projectPath:i,settings:M?{projectId:M}:void 0}).catch((e=>(b.warn("Failed to launch OpenCode serve",{error:(0,C.getErrorMessage)(e)}),{success:!1,error:(0,C.getErrorMessage)(e)})))])
1084
- if(console.log(""),console.log(" Action server .... port "+x.getPort()),c.success&&"port"in c&&c.port){"pid"in c&&c.pid&&x.trackChildPid(c.pid),x.setOpenCodePort(c.port),console.log(" OpenCode serve ... port "+c.port)
1085
- const e=(0,g.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),b.info("Wrote OpenCode config",{path:e,port:c.port})}else c.success?console.log(" OpenCode serve ... started"):console.log(" OpenCode serve ... failed (run manually: opencode serve)")
1205
+ if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return j.replace(/:\d+/,":3000")})(),r.launch({projectPath:i,settings:_?{projectId:_}:void 0}).catch((e=>(E.warn("Failed to launch OpenCode serve",{error:(0,k.getErrorMessage)(e)}),{success:!1,error:(0,k.getErrorMessage)(e)})))])
1206
+ if(console.log(""),console.log(" Action server .... port "+$.getPort()),c.success&&"port"in c&&c.port){"pid"in c&&c.pid&&$.trackChildPid(c.pid),$.setOpenCodePort(c.port),console.log(" OpenCode serve ... port "+c.port)
1207
+ const e=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),E.info("Wrote OpenCode config",{path:e,port:c.port})}else c.success?console.log(" OpenCode serve ... started"):console.log(" OpenCode serve ... failed (run manually: opencode serve)")
1086
1208
  console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
1087
1209
  let l=!1,u=null
1088
- const h=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
1089
- process.on("SIGTERM",h),process.on("SIGINT",h)
1090
- const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),f=new p({projectPath:i,logger:b,remoteServerUrl:E,authToken:P,projectId:M})
1091
- u=setInterval((async()=>{try{if(await j.hasValidToken())return
1210
+ const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await $.stop(),process.exit(0))}
1211
+ process.on("SIGTERM",g),process.on("SIGINT",g)
1212
+ const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),f=new p({projectPath:i,logger:E,remoteServerUrl:j,authToken:P,projectId:_})
1213
+ u=setInterval((async()=>{try{if(await T.hasValidToken())return
1092
1214
  console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
1093
- const e=await j.startAuthenticationFlow()
1094
- if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(j)){const e=await j.getToken()
1095
- e?.token&&(P=e.token,x.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){b.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await f.start(h)}catch(e){b.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({port:p})
1096
- const E=b[0].toLowerCase()
1097
- switch("--help"!==E&&"-h"!==E||(s(),process.exit(0)),"--version"!==E&&"-v"!==E||(console.log("alchemy v"+t()),process.exit(0)),E){case"serve":await async function(e={}){const s=process.cwd()
1215
+ const e=await T.startAuthenticationFlow()
1216
+ if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(T)){const e=await T.getToken()
1217
+ e?.token&&(P=e.token,$.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){E.warn("Token watchdog check failed",{error:(0,k.getErrorMessage)(e)})}}),3e5),await f.start(g)}catch(e){E.error("Failed to start server",{error:(0,k.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,k.getErrorMessage)(e)),process.exit(1)}}({port:C})
1218
+ const j=E[0].toLowerCase()
1219
+ switch("--help"!==j&&"-h"!==j||(s(),process.exit(0)),"--version"!==j&&"-v"!==j||(console.log("alchemy v"+t()),process.exit(0)),j){case"serve":await async function(e={}){const s=process.cwd()
1098
1220
  r(s)||(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))
1099
1221
  const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:s}),n=t()
1100
1222
  console.log(`\n\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 AlchemyLab Server v${n.padEnd(10)} \u2551\n\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n`),console.log("\u{1f4c1} Project: "+s)
@@ -1102,13 +1224,13 @@ const i=await a(o),c=new f.TokenManager(o,s,{remoteServerUrl:i,allowInteractiveA
1102
1224
  await c.initialize()
1103
1225
  const d=await c.getToken(),u=d?.token
1104
1226
  u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
1105
- const g=e.port||await(0,I.findAvailablePort)(3456),h=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:g,authToken:u})
1106
- try{await h.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${h.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
1227
+ const h=e.port||await(0,I.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:h,authToken:u})
1228
+ 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("")
1107
1229
  let e=!1
1108
- const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await h.stop(),process.exit(0))}
1109
- process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({port:p})
1230
+ const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
1231
+ process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,k.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,k.getErrorMessage)(e)),process.exit(1)}}({port:C})
1110
1232
  break
1111
1233
  case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),s=await a(t),r=new f.TokenManager(t,e,{remoteServerUrl:s,allowInteractiveAuth:!1})
1112
1234
  await r.initialize(),await r.hasValidToken()?await r.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
1113
1235
  break
1114
- default:console.error("\u274c Unknown command: "+E),console.log(""),s(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()
1236
+ default:console.error("\u274c Unknown command: "+j),console.log(""),s(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()