alchemylab 1.2.4 → 1.3.1

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
@@ -126,8 +126,8 @@ 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
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
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),I=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(),[,k]=await Promise.all([v,w,I])
130
+ return{sessionId:y,model:k,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
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
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
@@ -137,7 +137,22 @@ if(s){const e=s.preferredProvider||"inherit",n=s.preferredModelTier||"same",a=(0
137
137
  a.resolved&&(o=a.fullModel)}else{const s=(0,a.getBuiltInAgentConfig)(e)
138
138
  if(s){const e=(0,i.resolveAgentModel)(t,s.preferredProvider||"inherit",s.preferredModelTier||"same",r)
139
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`)
140
- return(t?.data||t||[]).find((t=>t.name===e))||null}catch{return null}}fetchProjectDefaultModel(){return this.deps.getDefaultModel()||null}}},1943(module){"use strict"
140
+ return(t?.data||t||[]).find((t=>t.name===e))||null}catch{return null}}fetchProjectDefaultModel(){return this.deps.getDefaultModel()||null}}},1405(e,exports,t){"use strict"
141
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleStartBuildingAction=async function(e,t){const{plannerSessionId:o,parentSessionId:n,requestIds:i,summary:a}=e,{logger:c,apiClient:l,projectId:d,projectPath:u}=t
142
+ if(!o)return{success:!1,message:"plannerSessionId is required",error:"missing_params"}
143
+ c.info("Starting start-building action",{plannerSessionId:o,requestIds:i,payloadParent:n})
144
+ let h=null
145
+ try{const e=await l.get(`/api/projects/${d}/cli-sessions/resolve/${o}`)
146
+ e.success&&(h=e.data)}catch(e){c.warn("Failed to resolve planner session",{error:(0,s.getErrorMessage)(e)})}const g=n||h?.parent?.externalSessionId||null
147
+ if(!g)return{success:!1,message:"Could not find parent session for the planner. The planner session may not have been properly linked at creation time.",error:"parent_not_found"}
148
+ c.info("Resolved parent session",{plannerSessionId:o,parentSessionId:g})
149
+ try{await l.post(`/api/projects/${d}/session-messages/reports`,{fromSessionId:o,toSessionId:g,status:"success",summary:"Plan complete. Created requests: "+(i||[]).join(", "),details:a||"Plan complete"})}catch(e){c.warn("Failed to create report",{error:(0,s.getErrorMessage)(e)})}const p=h?.id
150
+ if(p)try{await l.patch(`/api/projects/${d}/cli-sessions/${p}`,{status:"archived"}),c.info("Archived planner session",{plannerSessionId:o,sessionLocalId:p})}catch(e){c.warn("Failed to archive planner session (non-fatal)",{error:(0,s.getErrorMessage)(e)})}const f=(0,r.getOpenCodeServerUrl)(u,c)
151
+ if(f){const e=`\x3c!-- inserted-prompt --\x3eThe planner has finished creating the plan.\n\nCreated requests: ${(i||[]).join(", ")}\n\nPlease begin working on these requests.`
152
+ try{const s=await fetch(`${f}/session/${g}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e}],...t.cachedDefaultModel?(()=>{const[e,...s]=t.cachedDefaultModel.split("/")
153
+ return{model:{providerID:e,modelID:s.join("/")}}})():{}})})
154
+ s.ok?c.info("Notified parent session to start building",{parentSessionId:g}):c.warn("Failed to notify parent session",{status:s.status,parentSessionId:g})}catch(e){c.warn("Could not notify parent session (may be offline)",{error:(0,s.getErrorMessage)(e)})}}return{success:!0,message:"Build report sent to parent session",data:{parentSessionId:g}}}
155
+ const s=t(8824),r=t(8148)},1943(module){"use strict"
141
156
  module.exports=require("fs/promises")},2096(e,exports,t){"use strict"
142
157
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
143
158
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProjectConfigService=void 0
@@ -159,13 +174,15 @@ c=JSON.parse(e)}catch(e){}c[e]={preset:t,disabledTools:s||[],allowedAlchemyTools
159
174
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
160
175
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
161
176
  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
177
+ class I{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.activeMcpCalls=new Map,this.pendingAgentResync=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
163
178
  const s=(0,i.createMetricsCollector)(!1)
164
179
  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)()
165
180
  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
166
181
  if(!r)return void t.status(400).json({success:!1,error:"No project"})
167
182
  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})))
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
183
+ t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.get("/api/registry/projects",(async(e,s)=>{try{const{listLive:e}=await t.e(640).then(t.bind(t,2640))
184
+ s.json({success:!0,data:e()})}catch(e){s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.post("/api/mcp/tool-call-event",((e,t)=>{const{action:s,callId:r}=e.body||{}
185
+ !r||"start"!==s&&"end"!==s?t.status(400).json({success:!1,error:"action must be start|end, callId required"}):(this.handleMcpToolCallEvent(s,r+""),t.json({success:!0}))})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:s,sessionId:r,variables:o,timing:n}=e.body
169
186
  if(!s)return void t.status(400).json({success:!1,error:"eventType is required"})
170
187
  const i=await this.hookExecutor.processEvent({eventType:s,sessionId:r||"",variables:o||{},timing:n})
171
188
  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:[]}
@@ -180,7 +197,11 @@ try{c.default.accessSync(r,c.default.constants.W_OK)}catch{return void s.json({s
180
197
  s.json({success:!0,message:"Update started. The CLI will restart when complete."}),o("npm install -g alchemylab@latest",{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")
181
198
  try{if(!c.default.existsSync(s))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
182
199
  const e=c.default.readFileSync(s,"utf-8"),r=JSON.parse(e)
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
200
+ 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)}))))}handleMcpToolCallEvent(e,t){"start"!==e?(this.activeMcpCalls.delete(t),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.logger.info("Firing deferred agent resync now that MCP is idle"),this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync failed",{error:(0,g.getErrorMessage)(e)}))))):this.activeMcpCalls.set(t,Date.now())}startMcpStaleCleanup(){this.mcpStaleCleanupInterval||(this.mcpStaleCleanupInterval=setInterval((()=>{const e=Date.now()-12e4
201
+ let t=0
202
+ for(const[s,r]of this.activeMcpCalls)e>r&&(this.activeMcpCalls.delete(s),t++)
203
+ t>0&&(this.logger.warn("Cleaned up stale MCP tool-call tracking entries",{removed:t}),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync after cleanup failed",{error:(0,g.getErrorMessage)(e)})))))}),6e4))}async resyncAgentMarkdowns(){if(!this.projectId)return
204
+ if(this.activeMcpCalls.size>0)return this.pendingAgentResync=!0,void this.logger.info("Deferring agent resync until MCP tool calls complete",{inFlight:this.activeMcpCalls.size})
184
205
  const e=await this.configurer.fetchAgentsFromRemote(this.projectId)
185
206
  if(!e?.length)return
186
207
  const t=await this.configurer.fetchProjectFromRemote(this.projectId),s=t?.metadata?.globalSystemPrompt,r=a.default.join(this.options.projectPath,".opencode","agent")
@@ -201,15 +222,15 @@ const o=r?.metadata?.globalSystemPrompt
201
222
  o?this.logger.info("Using global system prompt from project metadata",{projectId:e.id,promptLength:o.length}):this.logger.debug("No global system prompt found in project metadata")
202
223
  const n=a.default.join(this.options.projectPath,".opencode","agent")
203
224
  await c.default.promises.mkdir(n,{recursive:!0})
204
- let i=0
205
- for(const e of s){if(!e.enabled)continue
206
- const t="alchemy",s=this.cachedDefaultModel||"anthropic/claude-opus-4-5",r=this.configurer.generateAgentMarkdown(e,t,s,o),l=a.default.join(n,e.name+".md")
207
- await c.default.promises.writeFile(l,r,"utf8"),i++,this.logger.info("Synced agent file",{name:e.name,path:l,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
225
+ const i="alchemy",l=this.cachedDefaultModel||"anthropic/claude-opus-4-5",d=s.filter((e=>e.enabled)),u=await Promise.allSettled(d.map((e=>{const t=this.configurer.generateAgentMarkdown(e,i,l,o),s=a.default.join(n,e.name+".md")
226
+ return c.default.promises.writeFile(s,t,"utf8").then((()=>{this.logger.info("Synced agent file",{name:e.name,path:s,mode:e.mode})}))}))),h=u.filter((e=>"fulfilled"===e.status)).length,p=u.filter((e=>"rejected"===e.status)).length
227
+ p>0&&this.logger.warn("Some agent file writes failed",{writeFails:p})
228
+ try{const e=a.default.join(this.options.projectPath,"opencode.json")
208
229
  if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),o=JSON.parse(t),n=r?.metadata?.bashToolAgents||{}
209
230
  if(o.agent){for(const e of s)e.enabled&&o.agent[e.name]&&e.name in n&&(o.agent[e.name].tools.bash=!!n[e.name])
210
- c.default.writeFileSync(e,JSON.stringify(o,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let l=!1
231
+ c.default.writeFileSync(e,JSON.stringify(o,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let f=!1
211
232
  try{const e=new v.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,getDefaultModel:()=>this.cachedDefaultModel||void 0}).getOpenCodeServerUrl(this.options.projectPath)
212
- e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&(this.logger.info("Triggered OpenCode config reload after agent sync"),l=!0)}catch(e){this.logger.warn("Could not reload OpenCode config",{error:(0,g.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${i} agent(s) to local files${l?" - config reloaded":""}`,count:i,reloaded:l})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
233
+ e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&(this.logger.info("Triggered OpenCode config reload after agent sync"),f=!0)}catch(e){this.logger.warn("Could not reload OpenCode config",{error:(0,g.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${h} agent(s) to local files${f?" - config reloaded":""}`,count:h,reloaded:f})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
213
234
  if(!r)return void s.status(400).json({success:!1,error:"requestId is required"})
214
235
  if(!/^req\d+$/.test(r))return void s.status(400).json({success:!1,error:"Invalid requestId format"})
215
236
  this.logger.info("Generating journal for request",{requestId:r})
@@ -284,7 +305,7 @@ t.json({success:!0,data:h})}catch(e){this.logger.error("Spawn session failed",{e
284
305
  if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
285
306
  const{agentName:r}=e.params,o=await this.apiClient.get(`/api/projects/${s.id}/agents`),n=(o?.data||o||[]).find((e=>e.name===r))
286
307
  if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
287
- 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
308
+ 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<I.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
288
309
  const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
289
310
  try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
290
311
  const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
@@ -346,7 +367,8 @@ if(r.success&&r.data?.serverId){this.serverId=r.data.serverId,this.logger.info("
346
367
  const e=r.data.versionWarnings
347
368
  if(e&&e.length>0){const t=e.some((e=>"required"===e.severity))
348
369
  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`))
349
- 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
370
+ 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()=>{if(this.projectId)try{const{touchHeartbeat:e}=await t.e(640).then(t.bind(t,2640))
371
+ e(this.projectId)}catch{}try{const e=await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString(),clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}),t=e?.data?.versionWarnings
350
372
  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`))
351
373
  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")
352
374
  if(!c.default.existsSync(e))return this.logger.debug("SSE forwarder: opencode.json not found, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
@@ -361,8 +383,8 @@ if(e)break
361
383
  o+=r.decode(s,{stream:!0})
362
384
  const n=o.split("\n")
363
385
  o=n.pop()||""
364
- for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6))
365
- 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
386
+ for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6)),s=t.type||"unknown"
387
+ this.sseEventBuffer.push({type:s,data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,error:(0,g.getErrorMessage)(e)}))),"session.idle"===s&&this.flushSSEEvents().catch((e=>this.logger.debug("Immediate SSE flush failed",{error:(0,g.getErrorMessage)(e)})))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,g.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,g.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
366
388
  if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
367
389
  if(!t)return
368
390
  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||""
@@ -414,14 +436,17 @@ const e=encodeURIComponent(t.requestId),[r,o]=await Promise.all([this.apiClient.
414
436
  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})
415
437
  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)}}))
416
438
  break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
417
- 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)
439
+ continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{const e=a.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
440
+ c.default.mkdirSync(a.default.dirname(e),{recursive:!0}),c.default.writeFileSync(e,JSON.stringify({port:this.port},null,2),"utf-8"),this.logger.info("Wrote action-server port file",{port:this.port,path:e})}catch(e){this.logger.warn("Failed to write action-server port file",{error:(0,g.getErrorMessage)(e)})}try{const e=this.readProjectConfig()
441
+ if(e?.id){const{upsertEntry:s}=await t.e(640).then(t.bind(t,2640))
442
+ s({projectId:e.id,projectPath:this.options.projectPath,port:this.port,pid:process.pid,startedAt:(new Date).toISOString(),lastHeartbeat:(new Date).toISOString()}),this.logger.info("Wrote running-projects registry entry",{projectId:e.id,port:this.port})}}catch(e){this.logger.warn("Failed to write project registry entry",{error:(0,g.getErrorMessage)(e)})}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,g.getErrorMessage)(e)
418
443
  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"}
419
444
  this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
420
445
  const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
421
446
  t["X-Service-Secret"]=s
422
447
  const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
423
448
  if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
424
- 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()
449
+ void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,g.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler(),this.startMcpStaleCleanup()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,g.getErrorMessage)(t)})}}this.trackedChildPids.clear()
425
450
  try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
426
451
  if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
427
452
  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()??[]
@@ -434,8 +459,11 @@ r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}as
434
459
  for(const e of this.versionWarnings)console.error(`\x1b[31m ${e.message}\x1b[0m`)
435
460
  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)
436
461
  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...")
437
- 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)
438
- 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"
462
+ for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.timerHookScheduler?.stop(),this.stopSSEEventForwarder(),this.mcpStaleCleanupInterval&&(clearInterval(this.mcpStaleCleanupInterval),this.mcpStaleCleanupInterval=void 0)
463
+ try{const e=a.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
464
+ c.default.existsSync(e)&&c.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await t.e(640).then(t.bind(t,2640))
465
+ e(this.projectId)}catch{}if(this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
466
+ this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=I,I.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
439
467
  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)
440
468
  if((0,s.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
441
469
  const d=(0,o.simpleGit)(e);(0,s.mkdirSync)(c,{recursive:!0})
@@ -534,7 +562,26 @@ Object.defineProperty(exports,"createLogger",{enumerable:!0,get(){return r.creat
534
562
  const o=s(t(2782))
535
563
  exports.defaultLogger=o.default
536
564
  const n=t(6540)
537
- Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=o.default},3785(module){"use strict"
565
+ Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=o.default},3614(e,exports,t){"use strict"
566
+ async function s(e,t,s){const r=a.default.join(e,".alchemy_lab","project.json")
567
+ try{const o=await i.default.readFile(r,"utf-8"),n=JSON.parse(o)
568
+ n.id=t,n.registeredRemotely=!0,n.metadata&&(n.metadata.basePath=e,n.metadata.alchemyPath=a.default.join(e,".alchemy_lab")),await i.default.writeFile(r,JSON.stringify(n,null,2),"utf-8"),s.info("Updated project.json",{projectJsonPath:r,newProjectId:t})}catch(e){s.warn("Could not update project.json (will be created on launch)",{error:e instanceof Error?e.message:e+""})}}async function r(e,t,s){const r=a.default.join(t,".alchemy_lab","opencode","opencode-local.db")
569
+ try{await i.default.access(r)}catch{return void s.info("No OpenCode database found, skipping session directory update")}try{await l("sqlite3",[r,`UPDATE session SET directory = replace(directory, '${e.replace(/'/g,"''")}', '${t.replace(/'/g,"''")}') WHERE directory LIKE '${e.replace(/'/g,"''")}%';`],{timeout:1e4}),s.info("Updated OpenCode session directories",{sourcePath:e,targetPath:t})}catch(e){s.warn("Could not update OpenCode session directories",{error:e instanceof Error?e.message:e+""})}}async function o(e){const t=[]
570
+ for(const s of d){const r=a.default.join(e,s)
571
+ t.push(i.default.rm(r,{recursive:!0,force:!0}).catch((()=>{})))}t.push((async()=>{try{const t=a.default.join(e,".alchemy_lab"),s=await i.default.readdir(t)
572
+ await Promise.all(s.filter((e=>e.endsWith(".lock"))).map((e=>i.default.unlink(a.default.join(t,e)).catch((()=>{})))))}catch{}})()),await Promise.all(t)}var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
573
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleForkFilesystemAction=async function(e,t){const{logger:n}=t,{sourcePath:a,targetPath:c,newProjectId:h}=e
574
+ if(!a||!c||!h)return{success:!1,message:"sourcePath, targetPath, and newProjectId are required",error:"missing_params"}
575
+ n.info("Starting filesystem fork",{sourcePath:a,targetPath:c,newProjectId:h})
576
+ try{const e=await async function(e,t,s){const r=e.endsWith("/")?e:e+"/"
577
+ try{const o=[...d,...u].flatMap((e=>["--exclude",e]))
578
+ return await l("rsync",["-a",...o,r,t],{timeout:3e5}),s.info("Directory copied via rsync",{source:e,target:t}),!0}catch(e){s.warn("rsync failed, falling back to cp -R",{error:e instanceof Error?e.message:e+""})}try{await i.default.mkdir(t,{recursive:!0})
579
+ const r=e.endsWith("/")?e+".":e+"/."
580
+ return await l("cp",["-R",r,t+"/"],{timeout:3e5}),s.info("Directory copied via cp -R",{source:e,target:t}),!0}catch(e){return s.error("cp -R failed",{error:e instanceof Error?e.message:e+""}),!1}}(a,c,n)
581
+ return e?(await Promise.all([s(c,h,n),r(a,c,n),o(c)]),n.info("Filesystem fork complete",{targetPath:c,newProjectId:h}),{success:!0,message:"Project forked to "+c,data:{targetPath:c,newProjectId:h}}):{success:!1,message:"Failed to copy project directory",error:"copy_failed"}}catch(e){const t=e instanceof Error?e.message:e+""
582
+ n.error("Filesystem fork failed",{error:t})
583
+ try{await i.default.rm(c,{recursive:!0,force:!0}),n.info("Cleaned up partial fork directory",{targetPath:c})}catch(e){n.warn("Failed to clean up partial fork",{error:e instanceof Error?e.message:e+""})}return{success:!1,message:"Filesystem fork failed: "+t,error:t}}}
584
+ const i=n(t(1943)),a=n(t(6928)),c=t(5317),l=(0,t(9023).promisify)(c.execFile),d=[".alchemy_lab/opencode.json",".alchemy_lab/server.log",".alchemy_lab/action-server-server.log",".alchemy_lab/mcp-identity-debug.log",".alchemy_lab/worktrees",".opencode/node_modules"],u=[".alchemy_lab/*.lock"]},3785(module){"use strict"
538
585
  module.exports=require("readline")},3807(module){"use strict"
539
586
  module.exports=require("simple-git")},3962(e,exports,t){"use strict"
540
587
  function s(){if(n)return n
@@ -635,7 +682,33 @@ return o.set(e,t),!1},exports.tryMarkMessageProcessed=function(e,t){if(!t)return
635
682
  if(i.get(e)===t)return!1
636
683
  if(i.set(e,t),i.size>100){const e=i.keys().next().value
637
684
  e&&i.delete(e)}return!0}
638
- const s=new Map,r=new Map,o=new Map,n=2e3,i=new Map},4769(e,exports,t){"use strict"
685
+ const s=new Map,r=new Map,o=new Map,n=2e3,i=new Map},4654(e,exports,t){"use strict"
686
+ async function s(e,t){try{const s=await fetch(e+"/session/status",{signal:AbortSignal.timeout(5e3)})
687
+ if(!s.ok)return"error"
688
+ const r=await s.json()
689
+ return r[t]?.type||"unknown"}catch{return"error"}}function r(e){return new Promise((t=>setTimeout(t,e)))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handlePromptAndWaitAction=async function(e,t){const{logger:n,projectPath:i,projectId:a,apiClient:c,trackChildPid:l}=t,{sessionId:d,message:u,prompt:h,timeoutSeconds:g=300,agent:p="coder",title:f}=e,m=u||h
690
+ if(!m)return{success:!1,message:"message is required",error:"missing_params"}
691
+ const y=(0,o.getOpenCodeServerUrl)(i,n)
692
+ if(!y)return{success:!1,message:"OpenCode not running",error:"no_opencode"}
693
+ const v=Date.now()
694
+ let w=d
695
+ if(w){try{const e=await fetch(`${y}/session/${w}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:m}],...t.cachedDefaultModel?(()=>{const[e,...s]=t.cachedDefaultModel.split("/")
696
+ return{model:{providerID:e,modelID:s.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
697
+ if(!e.ok)return{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:"Failed to inject prompt: "+(e instanceof Error?e.message:e+""),error:"prompt_failed"}}n.info("Injected prompt into existing session",{sessionId:w})}else{const e=await(0,o.spawnActionAgent)({agent:p,prompt:m,cwd:i,projectPath:i,projectId:a,title:f||"prompt-and-wait",apiClient:c,logger:n,trackChildPid:l,getDefaultModel(){return t.cachedDefaultModel||void 0}})
698
+ if(!e.success||!e.sessionId)return{success:!1,message:"Failed to spawn session: "+e.error,error:"spawn_failed"}
699
+ w=e.sessionId,n.info("Spawned session for prompt-and-wait",{sessionId:w})}const I=await async function(e,t,o){const n=Date.now()+1e3*o
700
+ let i=!1
701
+ const a=Date.now()+15e3
702
+ for(;Date.now()<a&&Date.now()<n;){const o=await s(e,t)
703
+ if("busy"===o||"running"===o){i=!0
704
+ break}await r(500)}for(;Date.now()<n;){const o=await s(e,t)
705
+ if("error"===o)return{status:"error"}
706
+ if(("idle"===o||"completed"===o)&&i)return{status:"completed"}
707
+ "busy"!==o&&"running"!==o||(i=!0),await r(2e3)}return{status:"timeout"}}(y,w,g),k=Date.now()-v,S=await async function(e,t){try{const s=await fetch(`${e}/session/${t}/message?limit=200`,{signal:AbortSignal.timeout(1e4)})
708
+ return s.ok?(await s.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),C=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(S),j=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
709
+ return""}(S)
710
+ return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:S,toolCallCount:C,elapsedMs:k,finalResponse:j}}}
711
+ const o=t(8148)},4769(e,exports,t){"use strict"
639
712
  function s(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function r(e,t,r){const o=s(r)
640
713
  if(!o||!t)return!1
641
714
  try{const s=await async function(e,t){try{const s=await t.apiClient.get(`/api/projects/${t.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),r=s?.data||s
@@ -645,8 +718,8 @@ if(r.cachedDefaultModel){const[e,...t]=r.cachedDefaultModel.split("/")
645
718
  n={providerID:e,modelID:t.join("/")}}const i=`${o}/session/${t}/prompt_async`,a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e}],...s?{agent:s}:{},...n?{model:n}:{}})})
646
719
  return a.ok?(r.logger.debug("Message sent to session",{sessionId:t}),!0):(r.logger.warn("Failed to send message to session",{sessionId:t,status:a.status}),!1)}catch(e){return r.logger.warn("Error sending message to session",{sessionId:t,error:e+""}),!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeBackground=async function(e,t,s,r,n){const i=await(0,o.spawnActionAgent)({agent:e.agent,prompt:t,cwd:n.projectPath,projectPath:n.projectPath,projectId:n.projectId,title:"Hook: "+e.name,requestId:r,parentSessionId:"independent_session"===e.target?void 0:s||void 0,apiClient:n.apiClient,logger:n.logger,trackChildPid:n.trackChildPid})
647
720
  i.success||n.logger.warn("Background hook spawn failed",{hook:e.name,error:i.error})},exports.executeSameSession=async function(e,t,s){await r(e,t,s)},exports.executeInjectPrompt=async function(e,s,o,n,i){let a=[]
648
- if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`)
649
- a=(e?.data||e||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))),i.logger.debug("inject_prompt: ancestors fetched",{sessionId:o,count:a.length,ancestors:a.map((e=>e.externalSessionId))})}catch(e){i.logger.warn("inject_prompt: ancestors fetch failed, falling back to parent",{sessionId:o,error:e+""})}if(0===a.length)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/parent`),t=e?.data?.parentExternalSessionId||e?.parentExternalSessionId
721
+ if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`),t=/^ses_[0-9A-Z]{26}$/
722
+ a=(e?.data||e||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))).filter((e=>!(!e.externalSessionId||t.test(e.externalSessionId))||(i.logger.warn("inject_prompt: skipping corrupt ancestor (internal-ULID-format externalSessionId)",{sessionId:o,corruptExternalSessionId:e.externalSessionId}),!1))),i.logger.debug("inject_prompt: ancestors fetched",{sessionId:o,count:a.length,ancestors:a.map((e=>e.externalSessionId))})}catch(e){i.logger.warn("inject_prompt: ancestors fetch failed, falling back to parent",{sessionId:o,error:e+""})}if(0===a.length)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/parent`),t=e?.data?.parentExternalSessionId||e?.parentExternalSessionId
650
723
  t&&(a=[{externalSessionId:t}])}catch{}if(0===a.length)return void i.logger.debug("inject_prompt: no ancestors found",{sessionId:o})
651
724
  const c=a.map((e=>`${e.agentType||"agent"}${e.title?" "+e.title:""} (${e.externalSessionId})`)).join(" \u2192 "),{interpolatePrompt:l}=await Promise.resolve().then(t.bind(t,4627)),d={...n,chainInfo:c}
652
725
  i.logger.debug("inject_prompt: interpolating",{variables:Object.keys(d),chainInfo:c,sessionId:n.sessionId,agentType:n.agentType})
@@ -664,17 +737,17 @@ const o=t(8148)},5086(module){"use strict"
664
737
  module.exports=require("ws")},5124(module){"use strict"
665
738
  module.exports=require("winston")},5317(module){"use strict"
666
739
  module.exports=require("child_process")},5607(e,exports,t){"use strict"
667
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,s){const r=u[e]
740
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,s){const r=f[e]
668
741
  if(!r)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
669
- try{return await r(t,s)}catch(t){const r=(0,d.getErrorMessage)(t)
742
+ try{return await r(t,s)}catch(t){const r=(0,p.getErrorMessage)(t)
670
743
  return s.logger.error("Action handler failed",{actionType:e,error:r}),{success:!1,message:"Action failed: "+r,error:r}}}
671
- const s=t(6636),r=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(8824),u={revert_request:s.handleRevertRequestAction,"revert-request":s.handleRevertRequestAction,complete_request:r.handleCompleteRequestAction,"complete-request":r.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":o.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction,async"inject-prompt"(e,s){const{sessionId:r,message:o}=e
744
+ const s=t(6636),r=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(3614),u=t(4654),h=t(7078),g=t(1405),p=t(8824),f={revert_request:s.handleRevertRequestAction,"revert-request":s.handleRevertRequestAction,complete_request:r.handleCompleteRequestAction,"complete-request":r.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":o.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction,async"inject-prompt"(e,s){const{sessionId:r,message:o}=e
672
745
  if(!r||!o)return{success:!1,message:"sessionId and message are required",error:"missing_params"}
673
746
  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")
674
747
  let a
675
748
  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("/")
676
749
  return{model:{providerID:e,modelID:t.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
677
- 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"
750
+ 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,p.getErrorMessage)(e),error:"prompt_error"}}},"fork-filesystem":d.handleForkFilesystemAction,"prompt-and-wait":u.handlePromptAndWaitAction,"run-ab-prompt":u.handlePromptAndWaitAction,"spawn-auto-planner":h.handleSpawnAutoPlannerAction,"start-building":g.handleStartBuildingAction}},5671(e,exports,t){"use strict"
678
751
  function s(e){const[t,...s]=e.split("/")
679
752
  return{provider:t||"anthropic",modelId:s.join("/")||e}}function r(e){const t={anthropic:!1,openai:!1}
680
753
  try{const r=(0,a.join)(e,"opencode.json")
@@ -750,34 +823,34 @@ let i=`You are a test runner agent. Your job is to verify that request ${e.local
750
823
  for(let e=0;e<n.length;e++){const t=n[e]
751
824
  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"
752
825
  for(const t of e.acceptanceCriteria)i+=`- ${t}\n`
753
- 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
826
+ 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:I}=d
754
827
  if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
755
828
  m.info("Starting run-tests action",{requestId:u,testProcessIds:h})
756
829
  try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
757
830
  if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
758
- const p=d.data,I=p.branchName||"requests/"+u
831
+ const p=d.data,k=p.branchName||"requests/"+u
759
832
  let S
760
833
  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))
761
834
  else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
762
835
  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"}
763
- const C=(0,o.join)(w,c,l),b=(0,o.join)(C,u),E=(0,n.simpleGit)(w)
764
- if((0,r.existsSync)(b))try{const e=(0,n.simpleGit)(b)
765
- 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)})
766
- try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(C,{recursive:!0})
767
- try{if(!(await E.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
768
- 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)}
769
- m.info("Test worktree already exists, using as-is",{worktreePath:b})}}const{execSync:j}=await Promise.resolve().then(t.t.bind(t,5317,23))
836
+ const C=(0,o.join)(w,c,l),j=(0,o.join)(C,u),b=(0,n.simpleGit)(w)
837
+ if((0,r.existsSync)(j))try{const e=(0,n.simpleGit)(j)
838
+ await e.raw(["reset","--hard",k]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:j,branchName:k})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
839
+ try{await b.raw(["worktree","remove","--force",j])}catch{}}if(!(0,r.existsSync)(j)){(0,r.mkdirSync)(C,{recursive:!0})
840
+ try{if(!(await b.branch()).all.includes(k))return{success:!1,message:`Branch ${k} not found`,error:"Branch not found"}
841
+ await b.raw(["worktree","add","--detach",j,k]),m.info("Created test worktree (detached)",{worktreePath:j,branchName:k})}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)}
842
+ m.info("Test worktree already exists, using as-is",{worktreePath:j})}}const{execSync:E}=await Promise.resolve().then(t.t.bind(t,5317,23))
770
843
  for(const e of S){const t=e.config?.setup||[]
771
844
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
772
- 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=[]
845
+ for(const s of t)try{m.info("Setup command",{command:s,worktreePath:j}),E(s,{cwd:j,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=[]
773
846
  for(const process of S){let t
774
847
  const r=e.testRunIds?.[T.length]
775
- 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)})
848
+ try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:j}),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:j,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)})
776
849
  continue}const o=s(p,process,0,v,f),n=f?"tester":"auto-tester"
777
- 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})
850
+ try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:j,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:g,allowParallel:!0,apiClient:y,logger:m,trackChildPid:I})
778
851
  if(!e.success)throw Error(e.error||"Spawn failed")
779
- 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)})
780
- 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)
852
+ 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:j,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
853
+ 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:j}}}catch(e){const t=(0,i.getErrorMessage)(e)
781
854
  return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
782
855
  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"
783
856
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
@@ -861,7 +934,16 @@ if(await s(t))return t}throw Error(`No available port found after ${t} attempts
861
934
  const o=r(t(9278))},6895(module){"use strict"
862
935
  module.exports=require("chalk")},6928(module){"use strict"
863
936
  module.exports=require("path")},6982(module){"use strict"
864
- module.exports=require("crypto")},7163(e,exports){"use strict"
937
+ module.exports=require("crypto")},7078(e,exports,t){"use strict"
938
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleSpawnAutoPlannerAction=async function(e,t){const{requestId:o,parentSessionId:n}=e,{logger:i,apiClient:a,projectId:c,projectPath:l,trackChildPid:d}=t
939
+ if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
940
+ i.info("Starting spawn-auto-planner action",{requestId:o})
941
+ try{const e=await a.get(`/api/projects/${c}/requests/${o}`)
942
+ if(!e.success||!e.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
943
+ const t=e.data,s=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",u=`Create a plan for request ${o}: ${t.title}\n\nDescription: ${t.description||"No description provided."}\n\n${s?"Acceptance Criteria:\n- "+s:""}\n\nResearch the codebase and create a comprehensive implementation plan.`,h=await(0,r.spawnActionAgent)({agent:"auto-planner",prompt:u,cwd:l,projectPath:l,projectId:c,title:`[${o}] Plan: ${t.title}`,requestId:o,parentSessionId:n,apiClient:a,logger:i,trackChildPid:d})
944
+ return h.success?(i.info("Spawned auto-planner for request",{requestId:o,sessionId:h.sessionId,pid:h.pid}),{success:!0,message:"Auto-planner spawned for request "+o,data:{sessionId:h.sessionId,pid:h.pid}}):{success:!1,message:"Failed to spawn auto-planner: "+h.error,error:h.error||"Spawn failed"}}catch(e){const t=(0,s.getErrorMessage)(e)
945
+ return i.error("spawn-auto-planner action failed",{requestId:o,error:t}),{success:!1,message:"Failed to spawn auto-planner: "+t,error:t}}}
946
+ const s=t(8824),r=t(8148)},7163(e,exports){"use strict"
865
947
  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"
866
948
  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
867
949
  var s=t(3077)
@@ -908,13 +990,17 @@ const d=e.data
908
990
  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}
909
991
  if(!(await i.patch(`/api/projects/${a}/requests/${o}`,{status:"open"})).success)return{success:!1,message:"Failed to update request status",error:"Update failed"}
910
992
  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.`
911
- let g=!1,p=!1
912
- 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})
993
+ let g,p=!1,f=!1,m=d.cliOrigin?.sessionId||null
994
+ if(m)try{const e=await i.get(`/api/projects/${a}/cli-sessions/resolve/${m}`)
995
+ e.success&&e.data?.parent?.externalSessionId&&(g=e.data.parent.externalSessionId,m=g,n.info("Resolved parent session for notification",{cliOriginSession:d.cliOrigin.sessionId,parentSession:m}))}catch(e){n.warn("Failed to resolve session parent, using cliOrigin directly",{error:(0,s.getErrorMessage)(e)})}if(!m)try{const e=await i.get(`/api/projects/${a}/session-request-associations/by-request/${o}?activeOnly=true`)
996
+ if(e.success&&Array.isArray(e.data)&&e.data.length>0){const t=e.data[0].opencodeSessionId
997
+ try{const e=await i.get(`/api/projects/${a}/cli-sessions/resolve/${t}`)
998
+ e.data?.parent?.externalSessionId?(m=e.data.parent.externalSessionId,g=m):m=t}catch{m=t}}}catch{}if(m)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:m,status:"success",summary:`Request ${o} "${d.title}" has been approved. Please begin implementation.`,details:h}),p=!0,n.info("Stored session report for target session",{requestId:o,sessionId:m})
913
999
  try{const e=(0,r.getOpenCodeServerUrl)(c,n)
914
- 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("/")
1000
+ 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/${m}/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("/")
915
1001
  return{model:{providerID:e,modelID:s.join("/")}}})():{}})})
916
- 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})
917
- 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)
1002
+ i.ok?n.info("Pushed notification to CLI session",{sessionId:m}):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 target session, spawning work agent instead",{requestId:o,error:(0,s.getErrorMessage)(e)})}if(!p)try{const e=await(0,r.spawnActionAgent)({agent:"work",prompt:h,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${d.title}`,requestId:o,parentSessionId:g,apiClient:i,logger:n,trackChildPid:l})
1003
+ f=e.success,f?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:p,agentSpawned:f}),{success:!0,message:`Successfully approved request ${o}: ${d.title}${p?" \u2014 creating session notified":f?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:p,agentSpawned:f}}}catch(e){const t=(0,s.getErrorMessage)(e)
918
1004
  return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
919
1005
  const s=t(8824),r=t(8148)},7960(e,exports){"use strict"
920
1006
  function t(){const e=process.argv[1]||""
@@ -929,17 +1015,17 @@ const m=new Date,y=`${(m.getHours()+"").padStart(2,"0")}${(m.getMinutes()+"").pa
929
1015
  let w
930
1016
  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))
931
1017
  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})
932
- const k=(0,i.join)(l||process.cwd(),".alchemy_lab"),I=(0,i.join)(k,"spawn-stderr.log")
1018
+ const I=(0,i.join)(l||process.cwd(),".alchemy_lab"),k=(0,i.join)(I,"spawn-stderr.log")
933
1019
  let S
934
- 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})
935
- return b.unref(),p&&p(b.pid),{success:!0,pid:b.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
1020
+ try{S=(0,n.openSync)(k,"a")}catch{}const C=(0,a.resolveRunCommand)(),j=(0,r.spawn)(C.command,[...C.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:o})
1021
+ return j.unref(),p&&p(j.pid),{success:!0,pid:j.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
936
1022
  const r=t(5317),o=t(8824),n=t(9896),i=t(6928),a=t(9068)},8148(e,exports,t){"use strict"
937
1023
  function s(e,t){try{const t=(0,o.join)(e,c,"opencode.json"),s=(0,r.readFileSync)(t,"utf8"),n=JSON.parse(s)
938
1024
  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
939
1025
  let m,y,v,w=null
940
1026
  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})
941
- 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)}})
942
- 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"}}
1027
+ 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 I=(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)}})
1028
+ return I.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:I.pid,requestId:d}),{success:!0,sessionId:w,pid:I.pid}):{success:!1,sessionId:w,error:I.error||"Spawn failed"}}
943
1029
  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"
944
1030
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
945
1031
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
@@ -1004,34 +1090,34 @@ return` ${o.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${r}`}: ${s}`})).join("\n")
1004
1090
  let c=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${i}"\n`
1005
1091
  if(e.color&&(c+=`color: "${e.color}"\n`),c+=`tools:\n${a}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){c+="permission:\n"
1006
1092
  for(const[t,s]of Object.entries(e.opencodePermission))if("object"==typeof s&&null!==s){c+=` ${t}:\n`
1007
- 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")
1093
+ 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")||e.thinkingModeDefault)&&(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")
1008
1094
  this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:r})
1009
1095
  const h=c.join(r,"opencode.json")
1010
1096
  let g=null,p=null,f=null,m=null,y=!1,v=!1
1011
1097
  const w=c.join(r,".alchemy_lab","config.json")
1012
1098
  if(!i){try{const e=await s.readFile(w,"utf-8"),t=JSON.parse(e)
1013
1099
  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)
1014
- 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"
1015
- i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:m&&(I.model=m)
1100
+ 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 I=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},k={tools:I},S=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
1101
+ i?.openCodeModel?k.model=i.openCodeModel:p?k.model=p:m&&(k.model=m)
1016
1102
  const C=c.join(r,".opencode","agent")
1017
1103
  await s.mkdir(C,{recursive:!0})
1018
- const b=S||"anthropic/claude-opus-4-5-20251101"
1019
- let E,j=null,T=null
1020
- 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})
1021
- for(const e of j){if(!e.enabled)continue
1022
- const t=this.generateAgentMarkdown(e,d,b,E),r=c.join(C,e.name+".md")
1104
+ const j=S||"anthropic/claude-opus-4-5-20251101"
1105
+ let b,E=null,T=null
1106
+ if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),b=T?.metadata?.globalSystemPrompt,b&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:b.length}),E=await this.fetchAgentsFromRemote(i.projectId),E&&E.length>0){this.logger.info("Using agents from remote database",{count:E.length})
1107
+ for(const e of E){if(!e.enabled)continue
1108
+ const t=this.generateAgentMarkdown(e,d,j,b),r=c.join(C,e.name+".md")
1023
1109
  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")
1024
1110
  else this.logger.warn("No projectId provided - agents must be configured via database")
1025
1111
  const A={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,r],enabled:!0}}}
1026
- 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={}
1027
- for(const e of j){if(!e.enabled)continue
1112
+ if(l&&process.env.REMOTE_SERVER_URL&&(A.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),E&&E.length>0){A.agent={}
1113
+ for(const e of E){if(!e.enabled)continue
1028
1114
  const t={}
1029
1115
  for(const[s,r]of Object.entries(e.tools||{})){const e=r,n=s.replace(/-/g,"_")
1030
1116
  t[o.OPENCODE_BUILTIN_TOOLS.has(s)?s:`${d}_${n}`]=e}const s=T?.metadata?.bashToolAgents||{}
1031
1117
  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)
1032
1118
  let r=""
1033
- 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}}
1034
- 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`
1119
+ b&&(r+=b.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}}
1120
+ const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${b?b.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`
1035
1121
  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=[]
1036
1122
  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})
1037
1123
  try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),r=c.join(e.homedir(),".alchemy_lab","opencode","config")
@@ -1041,8 +1127,9 @@ let n={}
1041
1127
  try{n=JSON.parse(await s.readFile(o,"utf-8"))}catch{}const i=(n.plugin||[]).filter((e=>!e.includes(".opencode/plugin/")))
1042
1128
  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={}
1043
1129
  try{const t=await s.readFile(w,"utf-8")
1044
- 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)
1045
- 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"
1130
+ e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=I,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)
1131
+ 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"])},9023(module){"use strict"
1132
+ module.exports=require("util")},9067(e,exports,t){"use strict"
1046
1133
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
1047
1134
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
1048
1135
  const r=s(t(6982)),o=t(8824)
@@ -1150,15 +1237,15 @@ setTimeout((()=>{clearInterval(r),s(!1)}),t)}))}async function a(t){if(process.e
1150
1237
  try{const{createConfigManager:s}=await e.e(60).then(e.t.bind(e,9060,23)),r=s(t)
1151
1238
  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}})})
1152
1239
  if(i.ok){const e=await i.json()
1153
- 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})
1154
- 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))
1240
+ return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)}catch(e){r.info("Project registration failed",{error:(0,I.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
1241
+ 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),I=e(8824),k=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))
1155
1242
  const i=process.argv.slice(2)
1156
1243
  let C
1157
- 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)))
1158
- const E=i.filter(((e,t)=>"--port"!==e&&(-1===b||t!==b+1)))
1159
- if(0===E.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
1244
+ const j=i.indexOf("--port");-1!==j&&i[j+1]&&(C=parseInt(i[j+1],10),isNaN(C)&&(console.error("\u274c Invalid port number"),process.exit(1)))
1245
+ const b=i.filter(((e,t)=>"--port"!==e&&(-1===j||t!==j+1)))
1246
+ if(0===b.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
1160
1247
  await(0,u.mkdir)(l,{recursive:!0})
1161
- 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})
1248
+ const C=(0,h.join)(l,"server.log"),{createLogger:j}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=j({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:C,baseDir:i}),E=await a(b),T=new f.TokenManager(b,i,{remoteServerUrl:E,allowInteractiveAuth:!1})
1162
1249
  await T.initialize()
1163
1250
  let A=await T.getToken()
1164
1251
  if(!A?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
@@ -1169,12 +1256,12 @@ console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
1169
1256
  const n=(0,h.join)(e,S.ALCHEMY_DIR_NAME)
1170
1257
  let i
1171
1258
  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")
1172
- 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)
1259
+ 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,E,P,b)
1173
1260
  const M=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
1174
1261
  let _
1175
1262
  try{const e=JSON.parse((0,d.readFileSync)(M,"utf-8"))
1176
- if(_=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,E,"cli-init-retry")
1177
- 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"
1263
+ if(_=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(E,P,i,b,"cli-init-retry")
1264
+ 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{b.warn("Could not read project.json")}await async function(t){try{return void(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
1178
1265
  try{const e=(0,h.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
1179
1266
  if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
1180
1267
  t.opencodeVersion&&(s=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
@@ -1186,29 +1273,29 @@ console.log(" Downloading from: "+t)
1186
1273
  const r=(0,h.join)((0,p.homedir)(),".local","bin")
1187
1274
  await(0,u.mkdir)(r,{recursive:!0})
1188
1275
  const o=(0,h.join)(r,"opencode")
1189
- 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)
1276
+ 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,I.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)
1190
1277
  const O=t()
1191
1278
  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)
1192
- const x=s.port||await(0,I.findAvailablePort)(3456),$=new y.AlchemyActionServer(E,{projectPath:i,remoteServerUrl:j,port:x,authToken:P})
1193
- 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
1194
- if(j.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
1195
- try{const e=await fetch(j+"/health")
1279
+ const x=s.port||await(0,k.findAvailablePort)(3456),$=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:E,port:x,authToken:P})
1280
+ 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([$.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
1281
+ if(E.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
1282
+ try{const e=await fetch(E+"/health")
1196
1283
  if(e.ok){const t=await e.json()
1197
- 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)})))])
1284
+ if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return E.replace(/:\d+/,":3000")})(),r.launch({projectPath:i,settings:_?{projectId:_}:void 0}).catch((e=>(b.warn("Failed to launch OpenCode serve",{error:(0,I.getErrorMessage)(e)}),{success:!1,error:(0,I.getErrorMessage)(e)})))])
1198
1285
  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)
1199
- 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)")
1286
+ 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)),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)")
1200
1287
  console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
1201
1288
  let l=!1,u=null
1202
1289
  const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await $.stop(),process.exit(0))}
1203
1290
  process.on("SIGTERM",g),process.on("SIGINT",g)
1204
- 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:_})
1291
+ 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:_})
1205
1292
  u=setInterval((async()=>{try{if(await T.hasValidToken())return
1206
1293
  console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
1207
1294
  const e=await T.startAuthenticationFlow()
1208
1295
  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()
1209
- 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})
1210
- const j=E[0].toLowerCase()
1211
- 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()
1296
+ 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){b.warn("Token watchdog check failed",{error:(0,I.getErrorMessage)(e)})}}),3e5),await f.start(g)}catch(e){b.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:C})
1297
+ const E=b[0].toLowerCase()
1298
+ 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()
1212
1299
  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))
1213
1300
  const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:s}),n=t()
1214
1301
  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)
@@ -1216,13 +1303,13 @@ const i=await a(o),c=new f.TokenManager(o,s,{remoteServerUrl:i,allowInteractiveA
1216
1303
  await c.initialize()
1217
1304
  const d=await c.getToken(),u=d?.token
1218
1305
  u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
1219
- const h=e.port||await(0,I.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:h,authToken:u})
1306
+ const h=e.port||await(0,k.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:h,authToken:u})
1220
1307
  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("")
1221
1308
  let e=!1
1222
1309
  const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
1223
- 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})
1310
+ process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:C})
1224
1311
  break
1225
1312
  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})
1226
1313
  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.")}()
1227
1314
  break
1228
- 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})()
1315
+ 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})()