alchemylab 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -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")
@@ -190,34 +211,26 @@ for(const t of e){if(!t.enabled)continue
190
211
  const e=this.configurer.generateAgentMarkdown(t,"alchemy",o,s)
191
212
  await c.default.promises.writeFile(a.default.join(r,t.name+".md"),e,"utf8")}this.logger.info("Re-synced agent markdowns after model change",{model:o,count:e.length})
192
213
  try{const e=new v.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,getDefaultModel:()=>this.cachedDefaultModel||void 0}).getOpenCodeServerUrl(this.options.projectPath)
193
- e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&this.logger.info("Triggered OpenCode config reload")}catch(e){this.logger.debug("Failed to trigger OpenCode config reload",{error:(0,g.getErrorMessage)(e)})}}async handleSyncAgents(e,s){try{const e=this.readProjectConfig()
194
- if(!e?.id)return void s.status(400).json({success:!1,error:"No project configuration found"})
214
+ e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&this.logger.info("Triggered OpenCode config reload")}catch(e){this.logger.debug("Failed to trigger OpenCode config reload",{error:(0,g.getErrorMessage)(e)})}}async handleSyncAgents(e,t){try{const e=this.readProjectConfig()
215
+ if(!e?.id)return void t.status(400).json({success:!1,error:"No project configuration found"})
195
216
  this.logger.info("Syncing agents from remote",{projectId:e.id})
196
- const r=await this.configurer.fetchAgentsFromRemote(e.id)
197
- if(!r||0===r.length)return void s.json({success:!0,message:"No agents to sync",count:0})
198
- const o=await this.configurer.fetchProjectFromRemote(e.id)
199
- this.logger.debug("Fetched project for global prompt",{projectId:e.id,hasProject:!!o,hasGlobalSystemPrompt:!!o?.metadata?.globalSystemPrompt})
200
- const n=o?.metadata?.globalSystemPrompt
201
- n?this.logger.info("Using global system prompt from project metadata",{projectId:e.id,promptLength:n.length}):this.logger.debug("No global system prompt found in project metadata")
202
- const i=a.default.join(this.options.projectPath,".opencode","agent")
203
- await c.default.promises.mkdir(i,{recursive:!0})
204
- let l=0
205
- for(const e of r){if(!e.enabled)continue
206
- const t="alchemy",s=this.cachedDefaultModel||"anthropic/claude-opus-4-5",r=this.configurer.generateAgentMarkdown(e,t,s,n),o=a.default.join(i,e.name+".md")
207
- await c.default.promises.writeFile(o,r,"utf8"),l++,this.logger.info("Synced agent file",{name:e.name,path:o,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
208
- if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t),n=o?.metadata?.bashToolAgents||{}
209
- if(s.agent){for(const e of r)e.enabled&&s.agent[e.name]&&e.name in n&&(s.agent[e.name].tools.bash=!!n[e.name])
210
- c.default.writeFileSync(e,JSON.stringify(s,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let d=!1
211
- try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
212
- if(c.default.existsSync(e)){const s=c.default.readFileSync(e,"utf-8"),r=JSON.parse(s)
213
- if(r.pid)try{process.kill(r.pid,"SIGTERM"),this.logger.info("Killed existing OpenCode process",{pid:r.pid}),await new Promise((e=>setTimeout(e,500)))}catch(e){"ESRCH"!==e?.code&&this.logger.warn("Could not kill OpenCode",{error:(0,g.getErrorMessage)(e)})}const o=r.port||0,{resolveServeCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,w.spawn)(i.command,[...i.prependArgs,"--port",o+""],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),l=new Promise((e=>{let t=""
214
- const s=setTimeout((()=>{this.logger.warn("Timeout waiting for OpenCode port, killing process")
215
- try{a.kill("SIGTERM")}catch{}e(null)}),1e4)
216
- a.stdout?.on("data",(r=>{t+=r.toString()
217
- const o=t.match(/listening on http:\/\/[^:]+:(\d+)/)
218
- o&&(clearTimeout(s),a.unref(),e({port:parseInt(o[1],10),pid:a.pid}))})),a.on("error",(()=>{clearTimeout(s)
219
- try{a.kill("SIGTERM")}catch{}e(null)}))})),u=await l
220
- u&&(this.trackChildPid(u.pid),c.default.writeFileSync(e,JSON.stringify({port:u.port,pid:u.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:u.port,pid:u.pid}),this.setOpenCodePort(u.port),d=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,g.getErrorMessage)(e)})}s.json({success:!0,message:`Synced ${l} agent(s) to local files${d?" - OpenCode reloading":""}`,count:l,restarted:d})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
217
+ const s=await this.configurer.fetchAgentsFromRemote(e.id)
218
+ if(!s||0===s.length)return void t.json({success:!0,message:"No agents to sync",count:0})
219
+ const r=await this.configurer.fetchProjectFromRemote(e.id)
220
+ this.logger.debug("Fetched project for global prompt",{projectId:e.id,hasProject:!!r,hasGlobalSystemPrompt:!!r?.metadata?.globalSystemPrompt})
221
+ const o=r?.metadata?.globalSystemPrompt
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")
223
+ const n=a.default.join(this.options.projectPath,".opencode","agent")
224
+ await c.default.promises.mkdir(n,{recursive:!0})
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")
229
+ if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),o=JSON.parse(t),n=r?.metadata?.bashToolAgents||{}
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])
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
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)
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
221
234
  if(!r)return void s.status(400).json({success:!1,error:"requestId is required"})
222
235
  if(!/^req\d+$/.test(r))return void s.status(400).json({success:!1,error:"Invalid requestId format"})
223
236
  this.logger.info("Generating journal for request",{requestId:r})
@@ -292,7 +305,7 @@ t.json({success:!0,data:h})}catch(e){this.logger.error("Spawn session failed",{e
292
305
  if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
293
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))
294
307
  if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
295
- t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<k.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
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
296
309
  const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
297
310
  try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
298
311
  const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
@@ -354,7 +367,8 @@ if(r.success&&r.data?.serverId){this.serverId=r.data.serverId,this.logger.info("
354
367
  const e=r.data.versionWarnings
355
368
  if(e&&e.length>0){const t=e.some((e=>"required"===e.severity))
356
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`))
357
- this.versionWarnings=e,t&&(this._pendingUpdateBlock=!0)}}else this.logger.warn("Registration failed, continuing without remote connection",{error:r.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{try{const e=await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString(),clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}),t=e?.data?.versionWarnings
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
358
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`))
359
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")
360
374
  if(!c.default.existsSync(e))return this.logger.debug("SSE forwarder: opencode.json not found, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
@@ -369,8 +383,8 @@ if(e)break
369
383
  o+=r.decode(s,{stream:!0})
370
384
  const n=o.split("\n")
371
385
  o=n.pop()||""
372
- for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6))
373
- this.sseEventBuffer.push({type:t.type||"unknown",data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,error:(0,g.getErrorMessage)(e)})))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,g.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,g.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
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
374
388
  if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
375
389
  if(!t)return
376
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||""
@@ -422,14 +436,17 @@ const e=encodeURIComponent(t.requestId),[r,o]=await Promise.all([this.apiClient.
422
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})
423
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)}}))
424
438
  break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
425
- continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,g.getErrorMessage)(e)
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)
426
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"}
427
444
  this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
428
445
  const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
429
446
  t["X-Service-Secret"]=s
430
447
  const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
431
448
  if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
432
- void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,g.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,g.getErrorMessage)(t)})}}this.trackedChildPids.clear()
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()
433
450
  try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
434
451
  if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
435
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()??[]
@@ -442,8 +459,11 @@ r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}as
442
459
  for(const e of this.versionWarnings)console.error(`\x1b[31m ${e.message}\x1b[0m`)
443
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)
444
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...")
445
- for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}if(this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.timerHookScheduler?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
446
- this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=k,k.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
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"
447
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)
448
468
  if((0,s.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
449
469
  const d=(0,o.simpleGit)(e);(0,s.mkdirSync)(c,{recursive:!0})
@@ -542,7 +562,26 @@ Object.defineProperty(exports,"createLogger",{enumerable:!0,get(){return r.creat
542
562
  const o=s(t(2782))
543
563
  exports.defaultLogger=o.default
544
564
  const n=t(6540)
545
- 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"
546
585
  module.exports=require("readline")},3807(module){"use strict"
547
586
  module.exports=require("simple-git")},3962(e,exports,t){"use strict"
548
587
  function s(){if(n)return n
@@ -643,7 +682,33 @@ return o.set(e,t),!1},exports.tryMarkMessageProcessed=function(e,t){if(!t)return
643
682
  if(i.get(e)===t)return!1
644
683
  if(i.set(e,t),i.size>100){const e=i.keys().next().value
645
684
  e&&i.delete(e)}return!0}
646
- 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"
647
712
  function s(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function r(e,t,r){const o=s(r)
648
713
  if(!o||!t)return!1
649
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
@@ -653,8 +718,8 @@ if(r.cachedDefaultModel){const[e,...t]=r.cachedDefaultModel.split("/")
653
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}:{}})})
654
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})
655
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=[]
656
- if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`)
657
- 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
658
723
  t&&(a=[{externalSessionId:t}])}catch{}if(0===a.length)return void i.logger.debug("inject_prompt: no ancestors found",{sessionId:o})
659
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}
660
725
  i.logger.debug("inject_prompt: interpolating",{variables:Object.keys(d),chainInfo:c,sessionId:n.sessionId,agentType:n.agentType})
@@ -672,17 +737,17 @@ const o=t(8148)},5086(module){"use strict"
672
737
  module.exports=require("ws")},5124(module){"use strict"
673
738
  module.exports=require("winston")},5317(module){"use strict"
674
739
  module.exports=require("child_process")},5607(e,exports,t){"use strict"
675
- 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]
676
741
  if(!r)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
677
- 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)
678
743
  return s.logger.error("Action handler failed",{actionType:e,error:r}),{success:!1,message:"Action failed: "+r,error:r}}}
679
- 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
680
745
  if(!r||!o)return{success:!1,message:"sessionId and message are required",error:"missing_params"}
681
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")
682
747
  let a
683
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("/")
684
749
  return{model:{providerID:e,modelID:t.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
685
- return e.ok?{success:!0,message:"Prompt injected into session "+r}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,d.getErrorMessage)(e),error:"prompt_error"}}}}},5671(e,exports,t){"use strict"
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"
686
751
  function s(e){const[t,...s]=e.split("/")
687
752
  return{provider:t||"anthropic",modelId:s.join("/")||e}}function r(e){const t={anthropic:!1,openai:!1}
688
753
  try{const r=(0,a.join)(e,"opencode.json")
@@ -758,34 +823,34 @@ let i=`You are a test runner agent. Your job is to verify that request ${e.local
758
823
  for(let e=0;e<n.length;e++){const t=n[e]
759
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"
760
825
  for(const t of e.acceptanceCriteria)i+=`- ${t}\n`
761
- i+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return i}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,d){const{requestId:u,testProcessIds:h,parentSessionId:g,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:k}=d
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
762
827
  if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
763
828
  m.info("Starting run-tests action",{requestId:u,testProcessIds:h})
764
829
  try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
765
830
  if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
766
- const p=d.data,I=p.branchName||"requests/"+u
831
+ const p=d.data,k=p.branchName||"requests/"+u
767
832
  let S
768
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))
769
834
  else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
770
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"}
771
- const C=(0,o.join)(w,c,l),b=(0,o.join)(C,u),E=(0,n.simpleGit)(w)
772
- if((0,r.existsSync)(b))try{const e=(0,n.simpleGit)(b)
773
- await e.raw(["reset","--hard",I]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:b,branchName:I})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
774
- try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(C,{recursive:!0})
775
- try{if(!(await E.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
776
- await E.raw(["worktree","add","--detach",b,I]),m.info("Created test worktree (detached)",{worktreePath:b,branchName:I})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
777
- m.info("Test worktree already exists, using as-is",{worktreePath:b})}}const{execSync:j}=await Promise.resolve().then(t.t.bind(t,5317,23))
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))
778
843
  for(const e of S){const t=e.config?.setup||[]
779
844
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
780
- for(const s of t)try{m.info("Setup command",{command:s,worktreePath:b}),j(s,{cwd:b,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:s,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const T=[],A=[]
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=[]
781
846
  for(const process of S){let t
782
847
  const r=e.testRunIds?.[T.length]
783
- try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:b}),t=(await y.get(`/api/projects/${v}/test-runs/${r}`)).data,T.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:b,parentSessionId:g})).data,T.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:r,error:(0,i.getErrorMessage)(e)})
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)})
784
849
  continue}const o=s(p,process,0,v,f),n=f?"tester":"auto-tester"
785
- try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:b,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:g,allowParallel:!0,apiClient:y,logger:m,trackChildPid:k})
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})
786
851
  if(!e.success)throw Error(e.error||"Spawn failed")
787
- A.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:b,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
788
- try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===A.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${A.length} test run(s) for request ${u}`,data:{testRunIds:T,agentsSpawned:A.length,processCount:S.length,worktreePath:b}}}catch(e){const t=(0,i.getErrorMessage)(e)
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)
789
854
  return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
790
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"
791
856
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
@@ -869,7 +934,16 @@ if(await s(t))return t}throw Error(`No available port found after ${t} attempts
869
934
  const o=r(t(9278))},6895(module){"use strict"
870
935
  module.exports=require("chalk")},6928(module){"use strict"
871
936
  module.exports=require("path")},6982(module){"use strict"
872
- 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"
873
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"
874
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
875
949
  var s=t(3077)
@@ -916,13 +990,17 @@ const d=e.data
916
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}
917
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"}
918
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.`
919
- let g=!1,p=!1
920
- if(d.cliOrigin?.sessionId)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:d.cliOrigin.sessionId,status:"success",summary:`Request ${o} "${d.title}" has been approved. Please begin implementation.`,details:h}),g=!0,n.info("Stored session report for creating session",{requestId:o,sessionId:d.cliOrigin.sessionId})
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})
921
999
  try{const e=(0,r.getOpenCodeServerUrl)(c,n)
922
- if(e){const s=`\u2705 [Request Approved]\n\nRequest ${o} "${d.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${h}`,r=`${e}/session/${d.cliOrigin.sessionId}/prompt_async`,i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:s}],...t.cachedDefaultModel?(()=>{const[e,...s]=t.cachedDefaultModel.split("/")
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("/")
923
1001
  return{model:{providerID:e,modelID:s.join("/")}}})():{}})})
924
- i.ok?n.info("Pushed notification to CLI session",{sessionId:d.cliOrigin.sessionId}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,s.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify creating session, spawning work agent instead",{requestId:o,error:(0,s.getErrorMessage)(e)})}if(!g)try{const e=await(0,r.spawnActionAgent)({agent:"work",prompt:h,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${d.title}`,requestId:o,apiClient:i,logger:n,trackChildPid:l})
925
- p=e.success,p?n.info("Spawned work agent for request",{requestId:o,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:o,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:o,error:(0,s.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:o,notifiedSession:g,agentSpawned:p}),{success:!0,message:`Successfully approved request ${o}: ${d.title}${g?" \u2014 creating session notified":p?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:g,agentSpawned:p}}}catch(e){const t=(0,s.getErrorMessage)(e)
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)
926
1004
  return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
927
1005
  const s=t(8824),r=t(8148)},7960(e,exports){"use strict"
928
1006
  function t(){const e=process.argv[1]||""
@@ -937,17 +1015,17 @@ const m=new Date,y=`${(m.getHours()+"").padStart(2,"0")}${(m.getMinutes()+"").pa
937
1015
  let w
938
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))
939
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})
940
- 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")
941
1019
  let S
942
- try{S=(0,n.openSync)(I,"a")}catch{}const C=(0,a.resolveRunCommand)(),b=(0,r.spawn)(C.command,[...C.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:o})
943
- 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)}}}
944
1022
  const r=t(5317),o=t(8824),n=t(9896),i=t(6928),a=t(9068)},8148(e,exports,t){"use strict"
945
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)
946
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
947
1025
  let m,y,v,w=null
948
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})
949
- w=o.sessionId,m=o.model,y=o.serverUrl,v=o.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,i.getErrorMessage)(e)}),y=s(c,p)||void 0}const k=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:w||void 0,serverUrl:y,model:m,initialBranch:v,allowParallel:h??!1,onSpawn(e){e&&f&&f(e)}})
950
- return k.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:k.pid,requestId:d}),{success:!0,sessionId:w,pid:k.pid}):{success:!1,sessionId:w,error:k.error||"Spawn failed"}}
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"}}
951
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"
952
1030
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
953
1031
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
@@ -1012,34 +1090,34 @@ return` ${o.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${r}`}: ${s}`})).join("\n")
1012
1090
  let c=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${i}"\n`
1013
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"
1014
1092
  for(const[t,s]of Object.entries(e.opencodePermission))if("object"==typeof s&&null!==s){c+=` ${t}:\n`
1015
- for(const[e,t]of Object.entries(s))c+=` ${e}: ${t}\n`}else c+=` ${t}: ${s}\n`}return void 0!==e.temperature&&(c+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(c+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(c+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),c+="---\n\n",n&&(c+=n.trim()+"\n\n"),e.prompt&&(c+=e.prompt),e.promptAddition&&(c+="\n\n"+e.promptAddition),c}async configureOpenCode(e,r,n="node",i,a){try{const s=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=c.join(e,"mcp-direct.js")
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")
1016
1094
  this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:r})
1017
1095
  const h=c.join(r,"opencode.json")
1018
1096
  let g=null,p=null,f=null,m=null,y=!1,v=!1
1019
1097
  const w=c.join(r,".alchemy_lab","config.json")
1020
1098
  if(!i){try{const e=await s.readFile(w,"utf-8"),t=JSON.parse(e)
1021
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)
1022
- t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const k=i?.openCodeTools||g||f||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},I={tools:k},S=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
1023
- 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)
1024
1102
  const C=c.join(r,".opencode","agent")
1025
1103
  await s.mkdir(C,{recursive:!0})
1026
- const b=S||"anthropic/claude-opus-4-5-20251101"
1027
- let E,j=null,T=null
1028
- if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),E=T?.metadata?.globalSystemPrompt,E&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:E.length}),j=await this.fetchAgentsFromRemote(i.projectId),j&&j.length>0){this.logger.info("Using agents from remote database",{count:j.length})
1029
- for(const e of j){if(!e.enabled)continue
1030
- 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")
1031
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")
1032
1110
  else this.logger.warn("No projectId provided - agents must be configured via database")
1033
1111
  const A={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,r],enabled:!0}}}
1034
- if(l&&process.env.REMOTE_SERVER_URL&&(A.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),j&&j.length>0){A.agent={}
1035
- 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
1036
1114
  const t={}
1037
1115
  for(const[s,r]of Object.entries(e.tools||{})){const e=r,n=s.replace(/-/g,"_")
1038
1116
  t[o.OPENCODE_BUILTIN_TOOLS.has(s)?s:`${d}_${n}`]=e}const s=T?.metadata?.bashToolAgents||{}
1039
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)
1040
1118
  let r=""
1041
- E&&(r+=E.trim()+"\n\n"),e.prompt&&(r+=e.prompt),e.promptAddition&&(r+="\n\n"+e.promptAddition),A.agent[e.name]={tools:t,...r?{prompt:r}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}A.agent.general={...A.agent.general,disable:!0},A.agent.explore={...A.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
1042
- const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${E?E.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`
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`
1043
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=[]
1044
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})
1045
1123
  try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),r=c.join(e.homedir(),".alchemy_lab","opencode","config")
@@ -1049,8 +1127,9 @@ let n={}
1049
1127
  try{n=JSON.parse(await s.readFile(o,"utf-8"))}catch{}const i=(n.plugin||[]).filter((e=>!e.includes(".opencode/plugin/")))
1050
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={}
1051
1129
  try{const t=await s.readFile(w,"utf-8")
1052
- e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=k,await s.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPlugins:s}=await t.e(939).then(t.t.bind(t,9939,23)),o=await s(r,l??!1,this.logger,e)
1053
- o.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:o.synced}),o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:h}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,s.getErrorMessage)(e)}),{success:!1,error:(0,s.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=o,o.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9067(e,exports,t){"use strict"
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"
1054
1133
  var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
1055
1134
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
1056
1135
  const r=s(t(6982)),o=t(8824)
@@ -1158,15 +1237,15 @@ setTimeout((()=>{clearInterval(r),s(!1)}),t)}))}async function a(t){if(process.e
1158
1237
  try{const{createConfigManager:s}=await e.e(60).then(e.t.bind(e,9060,23)),r=s(t)
1159
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}})})
1160
1239
  if(i.ok){const e=await i.json()
1161
- return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)}catch(e){r.info("Project registration failed",{error:(0,k.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
1162
- const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),k=e(8824),I=e(6800),S=e(4139);(async function(){23===parseInt(process.version.replace("v","").split(".")[0],10)&&(console.error(`\n\u274c Node.js ${process.version} is not supported.`),console.error(" AlchemyLab requires Node.js 22 or 24+ (Node 23 has a broken sqlite API)."),console.error("\n Switch with: nvm use 22 or nvm use 24\n"),process.exit(1))
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))
1163
1242
  const i=process.argv.slice(2)
1164
1243
  let C
1165
- const b=i.indexOf("--port");-1!==b&&i[b+1]&&(C=parseInt(i[b+1],10),isNaN(C)&&(console.error("\u274c Invalid port number"),process.exit(1)))
1166
- const E=i.filter(((e,t)=>"--port"!==e&&(-1===b||t!==b+1)))
1167
- if(0===E.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
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)
1168
1247
  await(0,u.mkdir)(l,{recursive:!0})
1169
- const C=(0,h.join)(l,"server.log"),{createLogger:b}=await Promise.resolve().then(e.t.bind(e,2782,23)),E=b({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:C,baseDir:i}),j=await a(E),T=new f.TokenManager(E,i,{remoteServerUrl:j,allowInteractiveAuth:!1})
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})
1170
1249
  await T.initialize()
1171
1250
  let A=await T.getToken()
1172
1251
  if(!A?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
@@ -1177,12 +1256,12 @@ console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
1177
1256
  const n=(0,h.join)(e,S.ALCHEMY_DIR_NAME)
1178
1257
  let i
1179
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")
1180
- const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,j,P,E)
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)
1181
1260
  const M=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
1182
1261
  let _
1183
1262
  try{const e=JSON.parse((0,d.readFileSync)(M,"utf-8"))
1184
- if(_=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,E,"cli-init-retry")
1185
- t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(M,JSON.stringify(e,null,2),"utf-8"),_=t,console.log("\u2705 Project registered: "+t))}}catch{E.warn("Could not read project.json")}await async function(t){try{return void(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
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"
1186
1265
  try{const e=(0,h.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
1187
1266
  if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
1188
1267
  t.opencodeVersion&&(s=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
@@ -1194,29 +1273,29 @@ console.log(" Downloading from: "+t)
1194
1273
  const r=(0,h.join)((0,p.homedir)(),".local","bin")
1195
1274
  await(0,u.mkdir)(r,{recursive:!0})
1196
1275
  const o=(0,h.join)(r,"opencode")
1197
- return(0,g.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,g.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(r)||(console.log(` \u26a0\ufe0f Add ${r} to your PATH:`),console.log(` export PATH="${r}:$PATH"`),console.log(""),process.env.PATH=`${r}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,k.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
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)
1198
1277
  const O=t()
1199
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)
1200
- const x=s.port||await(0,I.findAvailablePort)(3456),$=new y.AlchemyActionServer(E,{projectPath:i,remoteServerUrl:j,port:x,authToken:P})
1201
- try{const t=(0,v.getInstallContext)(),s=new m.MCPClientConfigurer(E,j,P),r=new w.UnifiedLauncher(E,t,s),[,a,c]=await Promise.all([$.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
1202
- if(j.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
1203
- try{const e=await fetch(j+"/health")
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")
1204
1283
  if(e.ok){const t=await e.json()
1205
- if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return j.replace(/:\d+/,":3000")})(),r.launch({projectPath:i,settings:_?{projectId:_}:void 0}).catch((e=>(E.warn("Failed to launch OpenCode serve",{error:(0,k.getErrorMessage)(e)}),{success:!1,error:(0,k.getErrorMessage)(e)})))])
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)})))])
1206
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)
1207
- const e=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),E.info("Wrote OpenCode config",{path:e,port:c.port})}else c.success?console.log(" OpenCode serve ... started"):console.log(" OpenCode serve ... failed (run manually: opencode serve)")
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)")
1208
1287
  console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
1209
1288
  let l=!1,u=null
1210
1289
  const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await $.stop(),process.exit(0))}
1211
1290
  process.on("SIGTERM",g),process.on("SIGINT",g)
1212
- const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),f=new p({projectPath:i,logger:E,remoteServerUrl:j,authToken:P,projectId:_})
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:_})
1213
1292
  u=setInterval((async()=>{try{if(await T.hasValidToken())return
1214
1293
  console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
1215
1294
  const e=await T.startAuthenticationFlow()
1216
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()
1217
- e?.token&&(P=e.token,$.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){E.warn("Token watchdog check failed",{error:(0,k.getErrorMessage)(e)})}}),3e5),await f.start(g)}catch(e){E.error("Failed to start server",{error:(0,k.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,k.getErrorMessage)(e)),process.exit(1)}}({port:C})
1218
- const j=E[0].toLowerCase()
1219
- switch("--help"!==j&&"-h"!==j||(s(),process.exit(0)),"--version"!==j&&"-v"!==j||(console.log("alchemy v"+t()),process.exit(0)),j){case"serve":await async function(e={}){const s=process.cwd()
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()
1220
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))
1221
1300
  const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:s}),n=t()
1222
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)
@@ -1224,13 +1303,13 @@ const i=await a(o),c=new f.TokenManager(o,s,{remoteServerUrl:i,allowInteractiveA
1224
1303
  await c.initialize()
1225
1304
  const d=await c.getToken(),u=d?.token
1226
1305
  u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
1227
- 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})
1228
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("")
1229
1308
  let e=!1
1230
1309
  const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
1231
- process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,k.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,k.getErrorMessage)(e)),process.exit(1)}}({port:C})
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})
1232
1311
  break
1233
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})
1234
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.")}()
1235
1314
  break
1236
- default:console.error("\u274c Unknown command: "+j),console.log(""),s(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()
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})()