alchemylab 1.2.4 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/640.js +14 -0
- package/dist/cli.js +177 -90
- package/dist/http-server.js +619 -582
- package/dist/index.js +411 -374
- package/dist/mcp-direct.js +152 -115
- package/package.json +1 -1
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),
|
|
130
|
-
return{sessionId:y,model:
|
|
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}}},
|
|
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
|
|
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.
|
|
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)}))))}
|
|
200
|
+
t.json({port:r.port,pid:r.pid,startedAt:r.startedAt,baseUrl:"http://localhost:"+r.port,directory:this.options.projectPath,proxyEnabled:!0})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,g.getErrorMessage)(e)})}}updateDefaultModel(e){void 0!==e&&e!==this.cachedDefaultModel&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e||""),e&&this.projectId&&this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Background agent markdown re-sync failed",{error:(0,g.getErrorMessage)(e)}))))}handleMcpToolCallEvent(e,t){"start"!==e?(this.activeMcpCalls.delete(t),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.logger.info("Firing deferred agent resync now that MCP is idle"),this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync failed",{error:(0,g.getErrorMessage)(e)}))))):this.activeMcpCalls.set(t,Date.now())}startMcpStaleCleanup(){this.mcpStaleCleanupInterval||(this.mcpStaleCleanupInterval=setInterval((()=>{const e=Date.now()-12e4
|
|
201
|
+
let t=0
|
|
202
|
+
for(const[s,r]of this.activeMcpCalls)e>r&&(this.activeMcpCalls.delete(s),t++)
|
|
203
|
+
t>0&&(this.logger.warn("Cleaned up stale MCP tool-call tracking entries",{removed:t}),0===this.activeMcpCalls.size&&this.pendingAgentResync&&(this.pendingAgentResync=!1,this.resyncAgentMarkdowns().catch((e=>this.logger.debug("Deferred resync after cleanup failed",{error:(0,g.getErrorMessage)(e)})))))}),6e4))}async resyncAgentMarkdowns(){if(!this.projectId)return
|
|
204
|
+
if(this.activeMcpCalls.size>0)return this.pendingAgentResync=!0,void this.logger.info("Deferring agent resync until MCP tool calls complete",{inFlight:this.activeMcpCalls.size})
|
|
184
205
|
const e=await this.configurer.fetchAgentsFromRemote(this.projectId)
|
|
185
206
|
if(!e?.length)return
|
|
186
207
|
const t=await this.configurer.fetchProjectFromRemote(this.projectId),s=t?.metadata?.globalSystemPrompt,r=a.default.join(this.options.projectPath,".opencode","agent")
|
|
@@ -201,15 +222,15 @@ const o=r?.metadata?.globalSystemPrompt
|
|
|
201
222
|
o?this.logger.info("Using global system prompt from project metadata",{projectId:e.id,promptLength:o.length}):this.logger.debug("No global system prompt found in project metadata")
|
|
202
223
|
const n=a.default.join(this.options.projectPath,".opencode","agent")
|
|
203
224
|
await c.default.promises.mkdir(n,{recursive:!0})
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
225
|
+
const i="alchemy",l=this.cachedDefaultModel||"anthropic/claude-opus-4-5",d=s.filter((e=>e.enabled)),u=await Promise.allSettled(d.map((e=>{const t=this.configurer.generateAgentMarkdown(e,i,l,o),s=a.default.join(n,e.name+".md")
|
|
226
|
+
return c.default.promises.writeFile(s,t,"utf8").then((()=>{this.logger.info("Synced agent file",{name:e.name,path:s,mode:e.mode})}))}))),h=u.filter((e=>"fulfilled"===e.status)).length,p=u.filter((e=>"rejected"===e.status)).length
|
|
227
|
+
p>0&&this.logger.warn("Some agent file writes failed",{writeFails:p})
|
|
228
|
+
try{const e=a.default.join(this.options.projectPath,"opencode.json")
|
|
208
229
|
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),o=JSON.parse(t),n=r?.metadata?.bashToolAgents||{}
|
|
209
230
|
if(o.agent){for(const e of s)e.enabled&&o.agent[e.name]&&e.name in n&&(o.agent[e.name].tools.bash=!!n[e.name])
|
|
210
|
-
c.default.writeFileSync(e,JSON.stringify(o,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let
|
|
231
|
+
c.default.writeFileSync(e,JSON.stringify(o,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let f=!1
|
|
211
232
|
try{const e=new v.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,getDefaultModel:()=>this.cachedDefaultModel||void 0}).getOpenCodeServerUrl(this.options.projectPath)
|
|
212
|
-
e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&(this.logger.info("Triggered OpenCode config reload after agent sync"),
|
|
233
|
+
e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3)})).ok&&(this.logger.info("Triggered OpenCode config reload after agent sync"),f=!0)}catch(e){this.logger.warn("Could not reload OpenCode config",{error:(0,g.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${h} agent(s) to local files${f?" - config reloaded":""}`,count:h,reloaded:f})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
|
|
213
234
|
if(!r)return void s.status(400).json({success:!1,error:"requestId is required"})
|
|
214
235
|
if(!/^req\d+$/.test(r))return void s.status(400).json({success:!1,error:"Invalid requestId format"})
|
|
215
236
|
this.logger.info("Generating journal for request",{requestId:r})
|
|
@@ -284,7 +305,7 @@ t.json({success:!0,data:h})}catch(e){this.logger.error("Spawn session failed",{e
|
|
|
284
305
|
if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
285
306
|
const{agentName:r}=e.params,o=await this.apiClient.get(`/api/projects/${s.id}/agents`),n=(o?.data||o||[]).find((e=>e.name===r))
|
|
286
307
|
if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
|
|
287
|
-
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<
|
|
308
|
+
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<I.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
|
|
288
309
|
const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
|
|
289
310
|
try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
|
|
290
311
|
const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
|
|
@@ -346,7 +367,8 @@ if(r.success&&r.data?.serverId){this.serverId=r.data.serverId,this.logger.info("
|
|
|
346
367
|
const e=r.data.versionWarnings
|
|
347
368
|
if(e&&e.length>0){const t=e.some((e=>"required"===e.severity))
|
|
348
369
|
for(const t of e)"required"===t.severity?(this.logger.error("[UPDATE REQUIRED] "+t.message),console.error("\n\x1b[31m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),console.error("\u2551 UPDATE REQUIRED \u2551"),console.error("\x1b[31m\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\x1b[0m"),console.error(`\x1b[31m${t.message}\x1b[0m\n`)):(this.logger.warn("[UPDATE RECOMMENDED] "+t.message),console.warn(`\n\x1b[33m[UPDATE RECOMMENDED] ${t.message}\x1b[0m\n`))
|
|
349
|
-
this.versionWarnings=e,t&&(this._pendingUpdateBlock=!0)}}else this.logger.warn("Registration failed, continuing without remote connection",{error:r.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{try{const
|
|
370
|
+
this.versionWarnings=e,t&&(this._pendingUpdateBlock=!0)}}else this.logger.warn("Registration failed, continuing without remote connection",{error:r.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{if(this.projectId)try{const{touchHeartbeat:e}=await t.e(640).then(t.bind(t,2640))
|
|
371
|
+
e(this.projectId)}catch{}try{const e=await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString(),clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}),t=e?.data?.versionWarnings
|
|
350
372
|
if(t&&t.length>0){if(t.map((e=>`${e.component}:${e.severity}`)).join(",")!==this.versionWarnings.map((e=>`${e.component}:${e.severity}`)).join(",")){for(const e of t)"required"===e.severity?(this.logger.error("[UPDATE REQUIRED] "+e.message),console.error(`\n\x1b[31m[UPDATE REQUIRED] ${e.message}\x1b[0m\n`)):(this.logger.warn("[UPDATE RECOMMENDED] "+e.message),console.warn(`\n\x1b[33m[UPDATE RECOMMENDED] ${e.message}\x1b[0m\n`))
|
|
351
373
|
this.versionWarnings=t}!t.some((e=>"required"===e.severity))&&!this._pendingUpdateBlock||this.updateBlocked||await this.enterUpdateMode()}else this.versionWarnings.length>0&&(this.versionWarnings=[])}catch(e){this.logger.warn("Heartbeat failed",{error:(0,g.getErrorMessage)(e)})}}),3e4))}startSSEEventForwarder(){this.logger.info("Starting SSE event forwarder"),this.connectToOpenCodeSSE(),this.sseForwarderInterval=setInterval((async()=>{await this.flushSSEEvents()}),2e3)}stopSSEEventForwarder(){this.sseForwarderInterval&&(clearInterval(this.sseForwarderInterval),this.sseForwarderInterval=void 0),this.sseAbortController&&(this.sseAbortController.abort(),this.sseAbortController=void 0),this.flushSSEEvents().catch((e=>this.logger.warn("Failed to flush remaining SSE events",{error:(0,g.getErrorMessage)(e)})))}async connectToOpenCodeSSE(){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
352
374
|
if(!c.default.existsSync(e))return this.logger.debug("SSE forwarder: opencode.json not found, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
|
|
@@ -361,8 +383,8 @@ if(e)break
|
|
|
361
383
|
o+=r.decode(s,{stream:!0})
|
|
362
384
|
const n=o.split("\n")
|
|
363
385
|
o=n.pop()||""
|
|
364
|
-
for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6))
|
|
365
|
-
this.sseEventBuffer.push({type:
|
|
386
|
+
for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6)),s=t.type||"unknown"
|
|
387
|
+
this.sseEventBuffer.push({type:s,data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,error:(0,g.getErrorMessage)(e)}))),"session.idle"===s&&this.flushSSEEvents().catch((e=>this.logger.debug("Immediate SSE flush failed",{error:(0,g.getErrorMessage)(e)})))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,g.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,g.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
|
|
366
388
|
if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
|
|
367
389
|
if(!t)return
|
|
368
390
|
this.logger.debug("SSE hook: session_idle",{sessionId:t}),await this.hookExecutor.processEvent({eventType:"session_idle",sessionId:t,variables:{sessionId:t}})}else if("session.created"===t){const t=e?.properties?.info||e?.info,s=t?.id||""
|
|
@@ -414,14 +436,17 @@ const e=encodeURIComponent(t.requestId),[r,o]=await Promise.all([this.apiClient.
|
|
|
414
436
|
return{success:!0,data:{...r.data,tasks:o.data||[]}}}default:return{success:!1,error:"Unknown request action: "+e}}}catch(e){return{success:!1,error:(0,g.getErrorMessage)(e)}}}handleCLIBridgeStatus(e,t){t.json({uiConnected:this.cliBridge.isUIConnected()})}async start(){this.logger.info("Starting AlchemyActionServer",{projectPath:this.options.projectPath,port:this.port})
|
|
415
437
|
for(let e=0;10>e;e++)try{await new Promise(((e,t)=>{try{this.server=this.app.listen(this.port,"127.0.0.1",(()=>{this.logger.info("HTTP server started",{port:this.port}),this.cliBridge.attach(this.server),e()})),this.server.on("error",(e=>{e.code,t(e)}))}catch(e){t(e)}}))
|
|
416
438
|
break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
|
|
417
|
-
continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{
|
|
439
|
+
continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{const e=a.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
|
|
440
|
+
c.default.mkdirSync(a.default.dirname(e),{recursive:!0}),c.default.writeFileSync(e,JSON.stringify({port:this.port},null,2),"utf-8"),this.logger.info("Wrote action-server port file",{port:this.port,path:e})}catch(e){this.logger.warn("Failed to write action-server port file",{error:(0,g.getErrorMessage)(e)})}try{const e=this.readProjectConfig()
|
|
441
|
+
if(e?.id){const{upsertEntry:s}=await t.e(640).then(t.bind(t,2640))
|
|
442
|
+
s({projectId:e.id,projectPath:this.options.projectPath,port:this.port,pid:process.pid,startedAt:(new Date).toISOString(),lastHeartbeat:(new Date).toISOString()}),this.logger.info("Wrote running-projects registry entry",{projectId:e.id,port:this.port})}}catch(e){this.logger.warn("Failed to write project registry entry",{error:(0,g.getErrorMessage)(e)})}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,g.getErrorMessage)(e)
|
|
418
443
|
t.includes("Required update"),this.logger.warn("Remote registration failed, continuing in local-only mode",{error:t})}try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=5000`,t={"Content-Type":"application/json",Accept:"application/json"}
|
|
419
444
|
this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
|
|
420
445
|
const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
|
|
421
446
|
t["X-Service-Secret"]=s
|
|
422
447
|
const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
|
|
423
448
|
if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
|
|
424
|
-
void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,g.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,g.getErrorMessage)(t)})}}this.trackedChildPids.clear()
|
|
449
|
+
void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,g.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler(),this.startMcpStaleCleanup()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,g.getErrorMessage)(t)})}}this.trackedChildPids.clear()
|
|
425
450
|
try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
426
451
|
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
|
|
427
452
|
if(s.pid)try{process.kill(-s.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:s.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(s.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:s.pid,error:(0,g.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new y.TimerHookScheduler({hookExecutor:this.hookExecutor,apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.timerHookScheduler.start()}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new m.StuckSessionWatchdog({getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,logger:this.logger,onStuckDetected:e=>this.handleStuckSessionDetected(e),onStuckResolved:e=>this.handleStuckSessionResolved(e)}),this.stuckSessionWatchdog.start()}handleStuckSessionDetected(e){this.sseEventBuffer.push({type:"session.stuck",data:{type:"session.stuck",properties:{sessionId:e.sessionId,messageId:e.messageId,detectedAt:e.detectedAt,sessionTitle:e.sessionTitle,retryCount:e.retryCount}}}),this.logger.warn("Stuck session detected",{sessionId:e.sessionId,title:e.sessionTitle})}handleStuckSessionResolved(e){this.sseEventBuffer.push({type:"session.unstuck",data:{type:"session.unstuck",properties:{sessionId:e}}})}handleGetStuckSessions(e,t){const s=this.stuckSessionWatchdog?.getStuckSessions()??[]
|
|
@@ -434,8 +459,11 @@ r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}as
|
|
|
434
459
|
for(const e of this.versionWarnings)console.error(`\x1b[31m ${e.message}\x1b[0m`)
|
|
435
460
|
console.error("\x1b[31m\n Functionality disabled. Update via UI or run:\n npm update -g alchemylab\x1b[0m\n"),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.timerHookScheduler?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval)
|
|
436
461
|
for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.clear(),this.cliBridge.close(),this.logger.info("Update-required mode active. HTTP server still running on port "+this.port)}}async stop(){this.logger.info("Stopping AlchemyActionServer...")
|
|
437
|
-
for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}
|
|
438
|
-
|
|
462
|
+
for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.timerHookScheduler?.stop(),this.stopSSEEventForwarder(),this.mcpStaleCleanupInterval&&(clearInterval(this.mcpStaleCleanupInterval),this.mcpStaleCleanupInterval=void 0)
|
|
463
|
+
try{const e=a.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
|
|
464
|
+
c.default.existsSync(e)&&c.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await t.e(640).then(t.bind(t,2640))
|
|
465
|
+
e(this.projectId)}catch{}if(this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
|
|
466
|
+
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=I,I.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
|
|
439
467
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ensureRequestWorktree=async function(e,t,i){const a="requests/"+t,c=(0,r.join)(e,n.ALCHEMY_DIR_NAME,"worktrees"),l=(0,r.join)(c,t)
|
|
440
468
|
if((0,s.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
|
|
441
469
|
const d=(0,o.simpleGit)(e);(0,s.mkdirSync)(c,{recursive:!0})
|
|
@@ -534,7 +562,26 @@ Object.defineProperty(exports,"createLogger",{enumerable:!0,get(){return r.creat
|
|
|
534
562
|
const o=s(t(2782))
|
|
535
563
|
exports.defaultLogger=o.default
|
|
536
564
|
const n=t(6540)
|
|
537
|
-
Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=o.default},
|
|
565
|
+
Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=o.default},3614(e,exports,t){"use strict"
|
|
566
|
+
async function s(e,t,s){const r=a.default.join(e,".alchemy_lab","project.json")
|
|
567
|
+
try{const o=await i.default.readFile(r,"utf-8"),n=JSON.parse(o)
|
|
568
|
+
n.id=t,n.registeredRemotely=!0,n.metadata&&(n.metadata.basePath=e,n.metadata.alchemyPath=a.default.join(e,".alchemy_lab")),await i.default.writeFile(r,JSON.stringify(n,null,2),"utf-8"),s.info("Updated project.json",{projectJsonPath:r,newProjectId:t})}catch(e){s.warn("Could not update project.json (will be created on launch)",{error:e instanceof Error?e.message:e+""})}}async function r(e,t,s){const r=a.default.join(t,".alchemy_lab","opencode","opencode-local.db")
|
|
569
|
+
try{await i.default.access(r)}catch{return void s.info("No OpenCode database found, skipping session directory update")}try{await l("sqlite3",[r,`UPDATE session SET directory = replace(directory, '${e.replace(/'/g,"''")}', '${t.replace(/'/g,"''")}') WHERE directory LIKE '${e.replace(/'/g,"''")}%';`],{timeout:1e4}),s.info("Updated OpenCode session directories",{sourcePath:e,targetPath:t})}catch(e){s.warn("Could not update OpenCode session directories",{error:e instanceof Error?e.message:e+""})}}async function o(e){const t=[]
|
|
570
|
+
for(const s of d){const r=a.default.join(e,s)
|
|
571
|
+
t.push(i.default.rm(r,{recursive:!0,force:!0}).catch((()=>{})))}t.push((async()=>{try{const t=a.default.join(e,".alchemy_lab"),s=await i.default.readdir(t)
|
|
572
|
+
await Promise.all(s.filter((e=>e.endsWith(".lock"))).map((e=>i.default.unlink(a.default.join(t,e)).catch((()=>{})))))}catch{}})()),await Promise.all(t)}var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
573
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleForkFilesystemAction=async function(e,t){const{logger:n}=t,{sourcePath:a,targetPath:c,newProjectId:h}=e
|
|
574
|
+
if(!a||!c||!h)return{success:!1,message:"sourcePath, targetPath, and newProjectId are required",error:"missing_params"}
|
|
575
|
+
n.info("Starting filesystem fork",{sourcePath:a,targetPath:c,newProjectId:h})
|
|
576
|
+
try{const e=await async function(e,t,s){const r=e.endsWith("/")?e:e+"/"
|
|
577
|
+
try{const o=[...d,...u].flatMap((e=>["--exclude",e]))
|
|
578
|
+
return await l("rsync",["-a",...o,r,t],{timeout:3e5}),s.info("Directory copied via rsync",{source:e,target:t}),!0}catch(e){s.warn("rsync failed, falling back to cp -R",{error:e instanceof Error?e.message:e+""})}try{await i.default.mkdir(t,{recursive:!0})
|
|
579
|
+
const r=e.endsWith("/")?e+".":e+"/."
|
|
580
|
+
return await l("cp",["-R",r,t+"/"],{timeout:3e5}),s.info("Directory copied via cp -R",{source:e,target:t}),!0}catch(e){return s.error("cp -R failed",{error:e instanceof Error?e.message:e+""}),!1}}(a,c,n)
|
|
581
|
+
return e?(await Promise.all([s(c,h,n),r(a,c,n),o(c)]),n.info("Filesystem fork complete",{targetPath:c,newProjectId:h}),{success:!0,message:"Project forked to "+c,data:{targetPath:c,newProjectId:h}}):{success:!1,message:"Failed to copy project directory",error:"copy_failed"}}catch(e){const t=e instanceof Error?e.message:e+""
|
|
582
|
+
n.error("Filesystem fork failed",{error:t})
|
|
583
|
+
try{await i.default.rm(c,{recursive:!0,force:!0}),n.info("Cleaned up partial fork directory",{targetPath:c})}catch(e){n.warn("Failed to clean up partial fork",{error:e instanceof Error?e.message:e+""})}return{success:!1,message:"Filesystem fork failed: "+t,error:t}}}
|
|
584
|
+
const i=n(t(1943)),a=n(t(6928)),c=t(5317),l=(0,t(9023).promisify)(c.execFile),d=[".alchemy_lab/opencode.json",".alchemy_lab/server.log",".alchemy_lab/action-server-server.log",".alchemy_lab/mcp-identity-debug.log",".alchemy_lab/worktrees",".opencode/node_modules"],u=[".alchemy_lab/*.lock"]},3785(module){"use strict"
|
|
538
585
|
module.exports=require("readline")},3807(module){"use strict"
|
|
539
586
|
module.exports=require("simple-git")},3962(e,exports,t){"use strict"
|
|
540
587
|
function s(){if(n)return n
|
|
@@ -635,7 +682,33 @@ return o.set(e,t),!1},exports.tryMarkMessageProcessed=function(e,t){if(!t)return
|
|
|
635
682
|
if(i.get(e)===t)return!1
|
|
636
683
|
if(i.set(e,t),i.size>100){const e=i.keys().next().value
|
|
637
684
|
e&&i.delete(e)}return!0}
|
|
638
|
-
const s=new Map,r=new Map,o=new Map,n=2e3,i=new Map},
|
|
685
|
+
const s=new Map,r=new Map,o=new Map,n=2e3,i=new Map},4654(e,exports,t){"use strict"
|
|
686
|
+
async function s(e,t){try{const s=await fetch(e+"/session/status",{signal:AbortSignal.timeout(5e3)})
|
|
687
|
+
if(!s.ok)return"error"
|
|
688
|
+
const r=await s.json()
|
|
689
|
+
return r[t]?.type||"unknown"}catch{return"error"}}function r(e){return new Promise((t=>setTimeout(t,e)))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handlePromptAndWaitAction=async function(e,t){const{logger:n,projectPath:i,projectId:a,apiClient:c,trackChildPid:l}=t,{sessionId:d,message:u,prompt:h,timeoutSeconds:g=300,agent:p="coder",title:f}=e,m=u||h
|
|
690
|
+
if(!m)return{success:!1,message:"message is required",error:"missing_params"}
|
|
691
|
+
const y=(0,o.getOpenCodeServerUrl)(i,n)
|
|
692
|
+
if(!y)return{success:!1,message:"OpenCode not running",error:"no_opencode"}
|
|
693
|
+
const v=Date.now()
|
|
694
|
+
let w=d
|
|
695
|
+
if(w){try{const e=await fetch(`${y}/session/${w}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:m}],...t.cachedDefaultModel?(()=>{const[e,...s]=t.cachedDefaultModel.split("/")
|
|
696
|
+
return{model:{providerID:e,modelID:s.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
|
|
697
|
+
if(!e.ok)return{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:"Failed to inject prompt: "+(e instanceof Error?e.message:e+""),error:"prompt_failed"}}n.info("Injected prompt into existing session",{sessionId:w})}else{const e=await(0,o.spawnActionAgent)({agent:p,prompt:m,cwd:i,projectPath:i,projectId:a,title:f||"prompt-and-wait",apiClient:c,logger:n,trackChildPid:l,getDefaultModel(){return t.cachedDefaultModel||void 0}})
|
|
698
|
+
if(!e.success||!e.sessionId)return{success:!1,message:"Failed to spawn session: "+e.error,error:"spawn_failed"}
|
|
699
|
+
w=e.sessionId,n.info("Spawned session for prompt-and-wait",{sessionId:w})}const I=await async function(e,t,o){const n=Date.now()+1e3*o
|
|
700
|
+
let i=!1
|
|
701
|
+
const a=Date.now()+15e3
|
|
702
|
+
for(;Date.now()<a&&Date.now()<n;){const o=await s(e,t)
|
|
703
|
+
if("busy"===o||"running"===o){i=!0
|
|
704
|
+
break}await r(500)}for(;Date.now()<n;){const o=await s(e,t)
|
|
705
|
+
if("error"===o)return{status:"error"}
|
|
706
|
+
if(("idle"===o||"completed"===o)&&i)return{status:"completed"}
|
|
707
|
+
"busy"!==o&&"running"!==o||(i=!0),await r(2e3)}return{status:"timeout"}}(y,w,g),k=Date.now()-v,S=await async function(e,t){try{const s=await fetch(`${e}/session/${t}/message?limit=200`,{signal:AbortSignal.timeout(1e4)})
|
|
708
|
+
return s.ok?(await s.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),C=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(S),j=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
|
|
709
|
+
return""}(S)
|
|
710
|
+
return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:S,toolCallCount:C,elapsedMs:k,finalResponse:j}}}
|
|
711
|
+
const o=t(8148)},4769(e,exports,t){"use strict"
|
|
639
712
|
function s(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function r(e,t,r){const o=s(r)
|
|
640
713
|
if(!o||!t)return!1
|
|
641
714
|
try{const s=await async function(e,t){try{const s=await t.apiClient.get(`/api/projects/${t.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),r=s?.data||s
|
|
@@ -645,8 +718,8 @@ if(r.cachedDefaultModel){const[e,...t]=r.cachedDefaultModel.split("/")
|
|
|
645
718
|
n={providerID:e,modelID:t.join("/")}}const i=`${o}/session/${t}/prompt_async`,a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e}],...s?{agent:s}:{},...n?{model:n}:{}})})
|
|
646
719
|
return a.ok?(r.logger.debug("Message sent to session",{sessionId:t}),!0):(r.logger.warn("Failed to send message to session",{sessionId:t,status:a.status}),!1)}catch(e){return r.logger.warn("Error sending message to session",{sessionId:t,error:e+""}),!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeBackground=async function(e,t,s,r,n){const i=await(0,o.spawnActionAgent)({agent:e.agent,prompt:t,cwd:n.projectPath,projectPath:n.projectPath,projectId:n.projectId,title:"Hook: "+e.name,requestId:r,parentSessionId:"independent_session"===e.target?void 0:s||void 0,apiClient:n.apiClient,logger:n.logger,trackChildPid:n.trackChildPid})
|
|
647
720
|
i.success||n.logger.warn("Background hook spawn failed",{hook:e.name,error:i.error})},exports.executeSameSession=async function(e,t,s){await r(e,t,s)},exports.executeInjectPrompt=async function(e,s,o,n,i){let a=[]
|
|
648
|
-
if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`)
|
|
649
|
-
a=(e?.data||e||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))),i.logger.debug("inject_prompt: ancestors fetched",{sessionId:o,count:a.length,ancestors:a.map((e=>e.externalSessionId))})}catch(e){i.logger.warn("inject_prompt: ancestors fetch failed, falling back to parent",{sessionId:o,error:e+""})}if(0===a.length)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/parent`),t=e?.data?.parentExternalSessionId||e?.parentExternalSessionId
|
|
721
|
+
if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`),t=/^ses_[0-9A-Z]{26}$/
|
|
722
|
+
a=(e?.data||e||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))).filter((e=>!(!e.externalSessionId||t.test(e.externalSessionId))||(i.logger.warn("inject_prompt: skipping corrupt ancestor (internal-ULID-format externalSessionId)",{sessionId:o,corruptExternalSessionId:e.externalSessionId}),!1))),i.logger.debug("inject_prompt: ancestors fetched",{sessionId:o,count:a.length,ancestors:a.map((e=>e.externalSessionId))})}catch(e){i.logger.warn("inject_prompt: ancestors fetch failed, falling back to parent",{sessionId:o,error:e+""})}if(0===a.length)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/parent`),t=e?.data?.parentExternalSessionId||e?.parentExternalSessionId
|
|
650
723
|
t&&(a=[{externalSessionId:t}])}catch{}if(0===a.length)return void i.logger.debug("inject_prompt: no ancestors found",{sessionId:o})
|
|
651
724
|
const c=a.map((e=>`${e.agentType||"agent"}${e.title?" "+e.title:""} (${e.externalSessionId})`)).join(" \u2192 "),{interpolatePrompt:l}=await Promise.resolve().then(t.bind(t,4627)),d={...n,chainInfo:c}
|
|
652
725
|
i.logger.debug("inject_prompt: interpolating",{variables:Object.keys(d),chainInfo:c,sessionId:n.sessionId,agentType:n.agentType})
|
|
@@ -664,17 +737,17 @@ const o=t(8148)},5086(module){"use strict"
|
|
|
664
737
|
module.exports=require("ws")},5124(module){"use strict"
|
|
665
738
|
module.exports=require("winston")},5317(module){"use strict"
|
|
666
739
|
module.exports=require("child_process")},5607(e,exports,t){"use strict"
|
|
667
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,s){const r=
|
|
740
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,s){const r=f[e]
|
|
668
741
|
if(!r)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
|
|
669
|
-
try{return await r(t,s)}catch(t){const r=(0,
|
|
742
|
+
try{return await r(t,s)}catch(t){const r=(0,p.getErrorMessage)(t)
|
|
670
743
|
return s.logger.error("Action handler failed",{actionType:e,error:r}),{success:!1,message:"Action failed: "+r,error:r}}}
|
|
671
|
-
const s=t(6636),r=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(
|
|
744
|
+
const s=t(6636),r=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(3614),u=t(4654),h=t(7078),g=t(1405),p=t(8824),f={revert_request:s.handleRevertRequestAction,"revert-request":s.handleRevertRequestAction,complete_request:r.handleCompleteRequestAction,"complete-request":r.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":o.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction,async"inject-prompt"(e,s){const{sessionId:r,message:o}=e
|
|
672
745
|
if(!r||!o)return{success:!1,message:"sessionId and message are required",error:"missing_params"}
|
|
673
746
|
const n=await Promise.resolve().then(t.t.bind(t,9896,23)),i=(await Promise.resolve().then(t.t.bind(t,6928,23))).join(s.projectPath,".alchemy_lab","opencode.json")
|
|
674
747
|
let a
|
|
675
748
|
try{a="http://127.0.0.1:"+JSON.parse(n.readFileSync(i,"utf-8")).port}catch{return{success:!1,message:"Cannot read OpenCode config",error:"no_opencode"}}try{const e=await fetch(`${a}/session/${r}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o}],...s.cachedDefaultModel?(()=>{const[e,...t]=s.cachedDefaultModel.split("/")
|
|
676
749
|
return{model:{providerID:e,modelID:t.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
|
|
677
|
-
return e.ok?{success:!0,message:"Prompt injected into session "+r}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,
|
|
750
|
+
return e.ok?{success:!0,message:"Prompt injected into session "+r}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,p.getErrorMessage)(e),error:"prompt_error"}}},"fork-filesystem":d.handleForkFilesystemAction,"prompt-and-wait":u.handlePromptAndWaitAction,"run-ab-prompt":u.handlePromptAndWaitAction,"spawn-auto-planner":h.handleSpawnAutoPlannerAction,"start-building":g.handleStartBuildingAction}},5671(e,exports,t){"use strict"
|
|
678
751
|
function s(e){const[t,...s]=e.split("/")
|
|
679
752
|
return{provider:t||"anthropic",modelId:s.join("/")||e}}function r(e){const t={anthropic:!1,openai:!1}
|
|
680
753
|
try{const r=(0,a.join)(e,"opencode.json")
|
|
@@ -750,34 +823,34 @@ let i=`You are a test runner agent. Your job is to verify that request ${e.local
|
|
|
750
823
|
for(let e=0;e<n.length;e++){const t=n[e]
|
|
751
824
|
i+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}if(i+=r?`\n## Instructions\n\nYou are in interactive mode. Present the test plan to the user and **wait for their direction** before running anything.\n\n1. Greet the user and list the test commands available for this process.\n2. Wait for the user to tell you to proceed (they may want to run specific commands, skip some, or run all).\n3. Use \`execute_test_command\` with processId="${process.id}" and the command name to run each command.\n4. After each command, explain the result and wait for the user before continuing.\n5. When the user is satisfied, call \`report_test_results\` with the overall status.\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`:`\n## Instructions\n\nSetup commands have already been executed. Use the \`execute_test_command\` tool to run each test command:\n\n1. For each command above, call \`execute_test_command\` with processId="${process.id}" and the command name.\n2. Check if it passed or failed.\n3. If it fails: try to determine why \u2014 is it a real bug, a flaky test, or an environment issue? Retry once if it looks flaky.\n4. When all commands are done, call \`report_test_results\` with the overall status and per-command results.\n - If all REQUIRED commands passed \u2192 status "passed"\n - If any REQUIRED command failed \u2192 status "failed" with diagnosis of each failure\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`,o.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0){i+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by reading code changes and running ad-hoc commands as needed. This is **advisory only** \u2014 it does not affect the overall pass/fail status.\n\nCriteria to verify:\n"
|
|
752
825
|
for(const t of e.acceptanceCriteria)i+=`- ${t}\n`
|
|
753
|
-
i+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return i}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,d){const{requestId:u,testProcessIds:h,parentSessionId:g,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:
|
|
826
|
+
i+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return i}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,d){const{requestId:u,testProcessIds:h,parentSessionId:g,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:I}=d
|
|
754
827
|
if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
755
828
|
m.info("Starting run-tests action",{requestId:u,testProcessIds:h})
|
|
756
829
|
try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
|
|
757
830
|
if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
|
|
758
|
-
const p=d.data,
|
|
831
|
+
const p=d.data,k=p.branchName||"requests/"+u
|
|
759
832
|
let S
|
|
760
833
|
if(h?.length)S=(await Promise.all(h.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
|
|
761
834
|
else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
|
|
762
835
|
S=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===S.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
|
|
763
|
-
const C=(0,o.join)(w,c,l),
|
|
764
|
-
if((0,r.existsSync)(
|
|
765
|
-
await e.raw(["reset","--hard",
|
|
766
|
-
try{await
|
|
767
|
-
try{if(!(await
|
|
768
|
-
await
|
|
769
|
-
m.info("Test worktree already exists, using as-is",{worktreePath:
|
|
836
|
+
const C=(0,o.join)(w,c,l),j=(0,o.join)(C,u),b=(0,n.simpleGit)(w)
|
|
837
|
+
if((0,r.existsSync)(j))try{const e=(0,n.simpleGit)(j)
|
|
838
|
+
await e.raw(["reset","--hard",k]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:j,branchName:k})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
|
|
839
|
+
try{await b.raw(["worktree","remove","--force",j])}catch{}}if(!(0,r.existsSync)(j)){(0,r.mkdirSync)(C,{recursive:!0})
|
|
840
|
+
try{if(!(await b.branch()).all.includes(k))return{success:!1,message:`Branch ${k} not found`,error:"Branch not found"}
|
|
841
|
+
await b.raw(["worktree","add","--detach",j,k]),m.info("Created test worktree (detached)",{worktreePath:j,branchName:k})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
|
|
842
|
+
m.info("Test worktree already exists, using as-is",{worktreePath:j})}}const{execSync:E}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
770
843
|
for(const e of S){const t=e.config?.setup||[]
|
|
771
844
|
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
|
|
772
|
-
for(const s of t)try{m.info("Setup command",{command:s,worktreePath:
|
|
845
|
+
for(const s of t)try{m.info("Setup command",{command:s,worktreePath:j}),E(s,{cwd:j,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:s,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const T=[],A=[]
|
|
773
846
|
for(const process of S){let t
|
|
774
847
|
const r=e.testRunIds?.[T.length]
|
|
775
|
-
try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:
|
|
848
|
+
try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:j}),t=(await y.get(`/api/projects/${v}/test-runs/${r}`)).data,T.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:j,parentSessionId:g})).data,T.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:r,error:(0,i.getErrorMessage)(e)})
|
|
776
849
|
continue}const o=s(p,process,0,v,f),n=f?"tester":"auto-tester"
|
|
777
|
-
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:
|
|
850
|
+
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:j,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:g,allowParallel:!0,apiClient:y,logger:m,trackChildPid:I})
|
|
778
851
|
if(!e.success)throw Error(e.error||"Spawn failed")
|
|
779
|
-
A.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:
|
|
780
|
-
try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===A.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${A.length} test run(s) for request ${u}`,data:{testRunIds:T,agentsSpawned:A.length,processCount:S.length,worktreePath:
|
|
852
|
+
A.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:j,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
|
|
853
|
+
try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===A.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${A.length} test run(s) for request ${u}`,data:{testRunIds:T,agentsSpawned:A.length,processCount:S.length,worktreePath:j}}}catch(e){const t=(0,i.getErrorMessage)(e)
|
|
781
854
|
return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
|
|
782
855
|
const r=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(8148),c=".alchemy_lab",l="test-worktrees"},6563(e,exports,t){"use strict"
|
|
783
856
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
|
|
@@ -861,7 +934,16 @@ if(await s(t))return t}throw Error(`No available port found after ${t} attempts
|
|
|
861
934
|
const o=r(t(9278))},6895(module){"use strict"
|
|
862
935
|
module.exports=require("chalk")},6928(module){"use strict"
|
|
863
936
|
module.exports=require("path")},6982(module){"use strict"
|
|
864
|
-
module.exports=require("crypto")},
|
|
937
|
+
module.exports=require("crypto")},7078(e,exports,t){"use strict"
|
|
938
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleSpawnAutoPlannerAction=async function(e,t){const{requestId:o,parentSessionId:n}=e,{logger:i,apiClient:a,projectId:c,projectPath:l,trackChildPid:d}=t
|
|
939
|
+
if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
940
|
+
i.info("Starting spawn-auto-planner action",{requestId:o})
|
|
941
|
+
try{const e=await a.get(`/api/projects/${c}/requests/${o}`)
|
|
942
|
+
if(!e.success||!e.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
|
|
943
|
+
const t=e.data,s=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",u=`Create a plan for request ${o}: ${t.title}\n\nDescription: ${t.description||"No description provided."}\n\n${s?"Acceptance Criteria:\n- "+s:""}\n\nResearch the codebase and create a comprehensive implementation plan.`,h=await(0,r.spawnActionAgent)({agent:"auto-planner",prompt:u,cwd:l,projectPath:l,projectId:c,title:`[${o}] Plan: ${t.title}`,requestId:o,parentSessionId:n,apiClient:a,logger:i,trackChildPid:d})
|
|
944
|
+
return h.success?(i.info("Spawned auto-planner for request",{requestId:o,sessionId:h.sessionId,pid:h.pid}),{success:!0,message:"Auto-planner spawned for request "+o,data:{sessionId:h.sessionId,pid:h.pid}}):{success:!1,message:"Failed to spawn auto-planner: "+h.error,error:h.error||"Spawn failed"}}catch(e){const t=(0,s.getErrorMessage)(e)
|
|
945
|
+
return i.error("spawn-auto-planner action failed",{requestId:o,error:t}),{success:!1,message:"Failed to spawn auto-planner: "+t,error:t}}}
|
|
946
|
+
const s=t(8824),r=t(8148)},7163(e,exports){"use strict"
|
|
865
947
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.debugTokenAccess=function(){},exports.compareTokens=function(){},exports.debugTokenStorage=function(){},exports.disableTokenDebugging=function(){},exports.enableTokenDebugging=function(){},new WeakMap},7210(e,exports,t){"use strict"
|
|
866
948
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.tryMarkMessageProcessed=exports.createToolCallHash=exports.isDuplicateToolCall=exports.determineEventToEmit=exports.interpolatePrompt=exports.evaluateCondition=exports.TimerConditionEvaluator=exports.TimerHookScheduler=exports.HookCache=exports.HookExecutor=void 0
|
|
867
949
|
var s=t(3077)
|
|
@@ -908,13 +990,17 @@ const d=e.data
|
|
|
908
990
|
if("pending_approval"!==d.status)return{success:!1,message:`Cannot approve request with status '${d.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+d.status}
|
|
909
991
|
if(!(await i.patch(`/api/projects/${a}/requests/${o}`,{status:"open"})).success)return{success:!1,message:"Failed to update request status",error:"Update failed"}
|
|
910
992
|
const u=Array.isArray(d.acceptanceCriteria)?d.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",h=`Work on request ${o}: ${d.title}\n\nDescription: ${d.description}\n\n${u?"Acceptance Criteria:\n- "+u:""}\n\nThis request has just been approved. Please begin implementation.`
|
|
911
|
-
let g=!1,
|
|
912
|
-
if(
|
|
993
|
+
let g,p=!1,f=!1,m=d.cliOrigin?.sessionId||null
|
|
994
|
+
if(m)try{const e=await i.get(`/api/projects/${a}/cli-sessions/resolve/${m}`)
|
|
995
|
+
e.success&&e.data?.parent?.externalSessionId&&(g=e.data.parent.externalSessionId,m=g,n.info("Resolved parent session for notification",{cliOriginSession:d.cliOrigin.sessionId,parentSession:m}))}catch(e){n.warn("Failed to resolve session parent, using cliOrigin directly",{error:(0,s.getErrorMessage)(e)})}if(!m)try{const e=await i.get(`/api/projects/${a}/session-request-associations/by-request/${o}?activeOnly=true`)
|
|
996
|
+
if(e.success&&Array.isArray(e.data)&&e.data.length>0){const t=e.data[0].opencodeSessionId
|
|
997
|
+
try{const e=await i.get(`/api/projects/${a}/cli-sessions/resolve/${t}`)
|
|
998
|
+
e.data?.parent?.externalSessionId?(m=e.data.parent.externalSessionId,g=m):m=t}catch{m=t}}}catch{}if(m)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:m,status:"success",summary:`Request ${o} "${d.title}" has been approved. Please begin implementation.`,details:h}),p=!0,n.info("Stored session report for target session",{requestId:o,sessionId:m})
|
|
913
999
|
try{const e=(0,r.getOpenCodeServerUrl)(c,n)
|
|
914
|
-
if(e){const s=`\u2705 [Request Approved]\n\nRequest ${o} "${d.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${h}`,r=`${e}/session/${
|
|
1000
|
+
if(e){const s=`\u2705 [Request Approved]\n\nRequest ${o} "${d.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${h}`,r=`${e}/session/${m}/prompt_async`,i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:s}],...t.cachedDefaultModel?(()=>{const[e,...s]=t.cachedDefaultModel.split("/")
|
|
915
1001
|
return{model:{providerID:e,modelID:s.join("/")}}})():{}})})
|
|
916
|
-
i.ok?n.info("Pushed notification to CLI session",{sessionId:
|
|
917
|
-
|
|
1002
|
+
i.ok?n.info("Pushed notification to CLI session",{sessionId:m}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,s.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify target session, spawning work agent instead",{requestId:o,error:(0,s.getErrorMessage)(e)})}if(!p)try{const e=await(0,r.spawnActionAgent)({agent:"work",prompt:h,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${d.title}`,requestId:o,parentSessionId:g,apiClient:i,logger:n,trackChildPid:l})
|
|
1003
|
+
f=e.success,f?n.info("Spawned work agent for request",{requestId:o,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:o,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:o,error:(0,s.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:o,notifiedSession:p,agentSpawned:f}),{success:!0,message:`Successfully approved request ${o}: ${d.title}${p?" \u2014 creating session notified":f?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:p,agentSpawned:f}}}catch(e){const t=(0,s.getErrorMessage)(e)
|
|
918
1004
|
return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
|
|
919
1005
|
const s=t(8824),r=t(8148)},7960(e,exports){"use strict"
|
|
920
1006
|
function t(){const e=process.argv[1]||""
|
|
@@ -929,17 +1015,17 @@ const m=new Date,y=`${(m.getHours()+"").padStart(2,"0")}${(m.getMinutes()+"").pa
|
|
|
929
1015
|
let w
|
|
930
1016
|
if(o.ALCHEMY_SESSION_TAG=`mcp-${y}-${v}`,s(l,"Spawn env setup",{agent:t,initialBranch:h||"none",sessionId:d||"new",attachMode:!!g}),w=g&&d&&c?["run","--attach",g,"--session",d,"--agent",t,c]:d&&c?["run","--session",d,"--agent",t,c]:c?["run","--agent",t,c]:["run","--agent",t],u){const e=w.findIndex((e=>e===c))
|
|
931
1017
|
e>0?w.splice(e,0,"--model",u):w.push("--model",u)}s(l,"Spawning opencode",{args:w.map((e=>e===c?`[prompt:${e.length}chars]`:e)),model:u||"not specified",promptLength:c?.length,promptPreview:c?.slice(0,100),cwd:l,hasSessionId:!!d})
|
|
932
|
-
const
|
|
1018
|
+
const I=(0,i.join)(l||process.cwd(),".alchemy_lab"),k=(0,i.join)(I,"spawn-stderr.log")
|
|
933
1019
|
let S
|
|
934
|
-
try{S=(0,n.openSync)(
|
|
935
|
-
return
|
|
1020
|
+
try{S=(0,n.openSync)(k,"a")}catch{}const C=(0,a.resolveRunCommand)(),j=(0,r.spawn)(C.command,[...C.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:o})
|
|
1021
|
+
return j.unref(),p&&p(j.pid),{success:!0,pid:j.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
|
|
936
1022
|
const r=t(5317),o=t(8824),n=t(9896),i=t(6928),a=t(9068)},8148(e,exports,t){"use strict"
|
|
937
1023
|
function s(e,t){try{const t=(0,o.join)(e,c,"opencode.json"),s=(0,r.readFileSync)(t,"utf8"),n=JSON.parse(s)
|
|
938
1024
|
if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,i.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=s,exports.spawnActionAgent=async function(e){const{agent:t,prompt:r,cwd:o,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:h,apiClient:g,logger:p,trackChildPid:f}=e
|
|
939
1025
|
let m,y,v,w=null
|
|
940
1026
|
try{const s=new a.SessionSpawnService({apiClient:g,logger:p,projectId:e.projectId,getDefaultModel:e.getDefaultModel||(()=>{})}),o=await s.prepareSession({agent:t,prompt:r,projectPath:c,requestId:d,parentSessionId:u,title:l,allowParallel:h})
|
|
941
|
-
w=o.sessionId,m=o.model,y=o.serverUrl,v=o.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,i.getErrorMessage)(e)}),y=s(c,p)||void 0}const
|
|
942
|
-
return
|
|
1027
|
+
w=o.sessionId,m=o.model,y=o.serverUrl,v=o.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,i.getErrorMessage)(e)}),y=s(c,p)||void 0}const I=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:w||void 0,serverUrl:y,model:m,initialBranch:v,allowParallel:h??!1,onSpawn(e){e&&f&&f(e)}})
|
|
1028
|
+
return I.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:I.pid,requestId:d}),{success:!0,sessionId:w,pid:I.pid}):{success:!1,sessionId:w,error:I.error||"Spawn failed"}}
|
|
943
1029
|
const r=t(9896),o=t(6928),n=t(8035),i=t(8824),a=t(1292),c=".alchemy_lab"},8524(e,exports,t){"use strict"
|
|
944
1030
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
945
1031
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
|
|
@@ -1004,34 +1090,34 @@ return` ${o.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${r}`}: ${s}`})).join("\n")
|
|
|
1004
1090
|
let c=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${i}"\n`
|
|
1005
1091
|
if(e.color&&(c+=`color: "${e.color}"\n`),c+=`tools:\n${a}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){c+="permission:\n"
|
|
1006
1092
|
for(const[t,s]of Object.entries(e.opencodePermission))if("object"==typeof s&&null!==s){c+=` ${t}:\n`
|
|
1007
|
-
for(const[e,t]of Object.entries(s))c+=` ${e}: ${t}\n`}else c+=` ${t}: ${s}\n`}return void 0!==e.temperature&&(c+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(c+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(c+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),c+="---\n\n",n&&(c+=n.trim()+"\n\n"),e.prompt&&(c+=e.prompt),e.promptAddition&&(c+="\n\n"+e.promptAddition),c}async configureOpenCode(e,r,n="node",i,a){try{const s=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=c.join(e,"mcp-direct.js")
|
|
1093
|
+
for(const[e,t]of Object.entries(s))c+=` ${e}: ${t}\n`}else c+=` ${t}: ${s}\n`}return void 0!==e.temperature&&(c+=`temperature: ${e.temperature}\n`),(e.name.endsWith("-thinking")||e.thinkingModeDefault)&&(c+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(c+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),c+="---\n\n",n&&(c+=n.trim()+"\n\n"),e.prompt&&(c+=e.prompt),e.promptAddition&&(c+="\n\n"+e.promptAddition),c}async configureOpenCode(e,r,n="node",i,a){try{const s=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=c.join(e,"mcp-direct.js")
|
|
1008
1094
|
this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:r})
|
|
1009
1095
|
const h=c.join(r,"opencode.json")
|
|
1010
1096
|
let g=null,p=null,f=null,m=null,y=!1,v=!1
|
|
1011
1097
|
const w=c.join(r,".alchemy_lab","config.json")
|
|
1012
1098
|
if(!i){try{const e=await s.readFile(w,"utf-8"),t=JSON.parse(e)
|
|
1013
1099
|
t?.openCodeTools?(g=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:g})):v=!0,t?.openCodeModel&&(p=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),v=!0}try{const e=await s.readFile(h,"utf-8"),t=JSON.parse(e)
|
|
1014
|
-
t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const
|
|
1015
|
-
i?.openCodeModel?
|
|
1100
|
+
t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const I=i?.openCodeTools||g||f||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},k={tools:I},S=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
|
|
1101
|
+
i?.openCodeModel?k.model=i.openCodeModel:p?k.model=p:m&&(k.model=m)
|
|
1016
1102
|
const C=c.join(r,".opencode","agent")
|
|
1017
1103
|
await s.mkdir(C,{recursive:!0})
|
|
1018
|
-
const
|
|
1019
|
-
let E
|
|
1020
|
-
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),
|
|
1021
|
-
for(const e of
|
|
1022
|
-
const t=this.generateAgentMarkdown(e,d,b
|
|
1104
|
+
const j=S||"anthropic/claude-opus-4-5-20251101"
|
|
1105
|
+
let b,E=null,T=null
|
|
1106
|
+
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),b=T?.metadata?.globalSystemPrompt,b&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:b.length}),E=await this.fetchAgentsFromRemote(i.projectId),E&&E.length>0){this.logger.info("Using agents from remote database",{count:E.length})
|
|
1107
|
+
for(const e of E){if(!e.enabled)continue
|
|
1108
|
+
const t=this.generateAgentMarkdown(e,d,j,b),r=c.join(C,e.name+".md")
|
|
1023
1109
|
await s.writeFile(r,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:r,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
|
|
1024
1110
|
else this.logger.warn("No projectId provided - agents must be configured via database")
|
|
1025
1111
|
const A={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,r],enabled:!0}}}
|
|
1026
|
-
if(l&&process.env.REMOTE_SERVER_URL&&(A.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),
|
|
1027
|
-
for(const e of
|
|
1112
|
+
if(l&&process.env.REMOTE_SERVER_URL&&(A.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),E&&E.length>0){A.agent={}
|
|
1113
|
+
for(const e of E){if(!e.enabled)continue
|
|
1028
1114
|
const t={}
|
|
1029
1115
|
for(const[s,r]of Object.entries(e.tools||{})){const e=r,n=s.replace(/-/g,"_")
|
|
1030
1116
|
t[o.OPENCODE_BUILTIN_TOOLS.has(s)?s:`${d}_${n}`]=e}const s=T?.metadata?.bashToolAgents||{}
|
|
1031
1117
|
e.name in s&&(t.bash=!!s[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name)&&void 0===t.task&&(t.task=!1)
|
|
1032
1118
|
let r=""
|
|
1033
|
-
|
|
1034
|
-
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${
|
|
1119
|
+
b&&(r+=b.trim()+"\n\n"),e.prompt&&(r+=e.prompt),e.promptAddition&&(r+="\n\n"+e.promptAddition),A.agent[e.name]={tools:t,...r?{prompt:r}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}A.agent.general={...A.agent.general,disable:!0},A.agent.explore={...A.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
|
|
1120
|
+
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${b?b.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`
|
|
1035
1121
|
await s.writeFile(c.join(C,"explore.md"),e,"utf8"),this.logger.info("Created explore agent override with research prompt"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(A.agent).length})}const P=[]
|
|
1036
1122
|
P.push("file://"+c.join(r,".opencode","plugin","inject-session.js")),P.push("file://"+c.join(r,".opencode","plugin","event-hooks.js")),P.push("file://"+c.join(r,".opencode","plugin","system-logger.js")),P.length>0&&(A.plugin=P),await s.writeFile(h,JSON.stringify(A,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:h,plugins:P})
|
|
1037
1123
|
try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),r=c.join(e.homedir(),".alchemy_lab","opencode","config")
|
|
@@ -1041,8 +1127,9 @@ let n={}
|
|
|
1041
1127
|
try{n=JSON.parse(await s.readFile(o,"utf-8"))}catch{}const i=(n.plugin||[]).filter((e=>!e.includes(".opencode/plugin/")))
|
|
1042
1128
|
n.plugin=[...i,...P],n.$schema=n.$schema||"https://opencode.ai/config.json",await s.writeFile(o,JSON.stringify(n,null,2),"utf8"),this.logger.info("Updated global alchemy opencode config",{globalConfigPath:o,plugins:n.plugin})}catch(e){this.logger.warn("Failed to write global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(v&&!i)try{let e={}
|
|
1043
1129
|
try{const t=await s.readFile(w,"utf-8")
|
|
1044
|
-
e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=
|
|
1045
|
-
o.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:o.synced}),o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:h}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,s.getErrorMessage)(e)}),{success:!1,error:(0,s.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=o,o.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},
|
|
1130
|
+
e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=I,await s.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPlugins:s}=await t.e(939).then(t.t.bind(t,9939,23)),o=await s(r,l??!1,this.logger,e)
|
|
1131
|
+
o.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:o.synced}),o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:h}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,s.getErrorMessage)(e)}),{success:!1,error:(0,s.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=o,o.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9023(module){"use strict"
|
|
1132
|
+
module.exports=require("util")},9067(e,exports,t){"use strict"
|
|
1046
1133
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
1047
1134
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
|
|
1048
1135
|
const r=s(t(6982)),o=t(8824)
|
|
@@ -1150,15 +1237,15 @@ setTimeout((()=>{clearInterval(r),s(!1)}),t)}))}async function a(t){if(process.e
|
|
|
1150
1237
|
try{const{createConfigManager:s}=await e.e(60).then(e.t.bind(e,9060,23)),r=s(t)
|
|
1151
1238
|
return(await r.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,s,r,o="cli-init"){try{const n=(0,h.basename)(s)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:s,createdVia:o}})})
|
|
1152
1239
|
if(i.ok){const e=await i.json()
|
|
1153
|
-
return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)}catch(e){r.info("Project registration failed",{error:(0,
|
|
1154
|
-
const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),
|
|
1240
|
+
return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)}catch(e){r.info("Project registration failed",{error:(0,I.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
|
|
1241
|
+
const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),I=e(8824),k=e(6800),S=e(4139);(async function(){23===parseInt(process.version.replace("v","").split(".")[0],10)&&(console.error(`\n\u274c Node.js ${process.version} is not supported.`),console.error(" AlchemyLab requires Node.js 22 or 24+ (Node 23 has a broken sqlite API)."),console.error("\n Switch with: nvm use 22 or nvm use 24\n"),process.exit(1))
|
|
1155
1242
|
const i=process.argv.slice(2)
|
|
1156
1243
|
let C
|
|
1157
|
-
const
|
|
1158
|
-
const
|
|
1159
|
-
if(0===
|
|
1244
|
+
const j=i.indexOf("--port");-1!==j&&i[j+1]&&(C=parseInt(i[j+1],10),isNaN(C)&&(console.error("\u274c Invalid port number"),process.exit(1)))
|
|
1245
|
+
const b=i.filter(((e,t)=>"--port"!==e&&(-1===j||t!==j+1)))
|
|
1246
|
+
if(0===b.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
|
|
1160
1247
|
await(0,u.mkdir)(l,{recursive:!0})
|
|
1161
|
-
const C=(0,h.join)(l,"server.log"),{createLogger:
|
|
1248
|
+
const C=(0,h.join)(l,"server.log"),{createLogger:j}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=j({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:C,baseDir:i}),E=await a(b),T=new f.TokenManager(b,i,{remoteServerUrl:E,allowInteractiveAuth:!1})
|
|
1162
1249
|
await T.initialize()
|
|
1163
1250
|
let A=await T.getToken()
|
|
1164
1251
|
if(!A?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
|
|
@@ -1169,12 +1256,12 @@ console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
|
|
|
1169
1256
|
const n=(0,h.join)(e,S.ALCHEMY_DIR_NAME)
|
|
1170
1257
|
let i
|
|
1171
1258
|
await(0,u.mkdir)(n,{recursive:!0}),s?(console.log(`\u{1f4cb} Registering project "${(0,h.basename)(e)||"Local Project"}"...`),i=await c(t,s,e,o),console.log(i?"\u2705 Project registered: "+i:"\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token \u2014 skipping project registration")
|
|
1172
|
-
const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,
|
|
1259
|
+
const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,E,P,b)
|
|
1173
1260
|
const M=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
|
|
1174
1261
|
let _
|
|
1175
1262
|
try{const e=JSON.parse((0,d.readFileSync)(M,"utf-8"))
|
|
1176
|
-
if(_=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(
|
|
1177
|
-
t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(M,JSON.stringify(e,null,2),"utf-8"),_=t,console.log("\u2705 Project registered: "+t))}}catch{
|
|
1263
|
+
if(_=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(E,P,i,b,"cli-init-retry")
|
|
1264
|
+
t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(M,JSON.stringify(e,null,2),"utf-8"),_=t,console.log("\u2705 Project registered: "+t))}}catch{b.warn("Could not read project.json")}await async function(t){try{return void(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
|
|
1178
1265
|
try{const e=(0,h.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
|
|
1179
1266
|
if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
|
|
1180
1267
|
t.opencodeVersion&&(s=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
|
|
@@ -1186,29 +1273,29 @@ console.log(" Downloading from: "+t)
|
|
|
1186
1273
|
const r=(0,h.join)((0,p.homedir)(),".local","bin")
|
|
1187
1274
|
await(0,u.mkdir)(r,{recursive:!0})
|
|
1188
1275
|
const o=(0,h.join)(r,"opencode")
|
|
1189
|
-
return(0,g.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,g.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(r)||(console.log(` \u26a0\ufe0f Add ${r} to your PATH:`),console.log(` export PATH="${r}:$PATH"`),console.log(""),process.env.PATH=`${r}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,
|
|
1276
|
+
return(0,g.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,g.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(r)||(console.log(` \u26a0\ufe0f Add ${r} to your PATH:`),console.log(` export PATH="${r}:$PATH"`),console.log(""),process.env.PATH=`${r}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,I.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
|
|
1190
1277
|
const O=t()
|
|
1191
1278
|
console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+O,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
|
|
1192
|
-
const x=s.port||await(0,
|
|
1193
|
-
try{const t=(0,v.getInstallContext)(),s=new m.MCPClientConfigurer(E,
|
|
1194
|
-
if(
|
|
1195
|
-
try{const e=await fetch(
|
|
1279
|
+
const x=s.port||await(0,k.findAvailablePort)(3456),$=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:E,port:x,authToken:P})
|
|
1280
|
+
try{const t=(0,v.getInstallContext)(),s=new m.MCPClientConfigurer(b,E,P),r=new w.UnifiedLauncher(b,t,s),[,a,c]=await Promise.all([$.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
|
|
1281
|
+
if(E.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
|
|
1282
|
+
try{const e=await fetch(E+"/health")
|
|
1196
1283
|
if(e.ok){const t=await e.json()
|
|
1197
|
-
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return
|
|
1284
|
+
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return E.replace(/:\d+/,":3000")})(),r.launch({projectPath:i,settings:_?{projectId:_}:void 0}).catch((e=>(b.warn("Failed to launch OpenCode serve",{error:(0,I.getErrorMessage)(e)}),{success:!1,error:(0,I.getErrorMessage)(e)})))])
|
|
1198
1285
|
if(console.log(""),console.log(" Action server .... port "+$.getPort()),c.success&&"port"in c&&c.port){"pid"in c&&c.pid&&$.trackChildPid(c.pid),$.setOpenCodePort(c.port),console.log(" OpenCode serve ... port "+c.port)
|
|
1199
|
-
const e=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),
|
|
1286
|
+
const e=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),b.info("Wrote OpenCode config",{path:e,port:c.port})}else c.success?console.log(" OpenCode serve ... started"):console.log(" OpenCode serve ... failed (run manually: opencode serve)")
|
|
1200
1287
|
console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
|
|
1201
1288
|
let l=!1,u=null
|
|
1202
1289
|
const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await $.stop(),process.exit(0))}
|
|
1203
1290
|
process.on("SIGTERM",g),process.on("SIGINT",g)
|
|
1204
|
-
const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),f=new p({projectPath:i,logger:
|
|
1291
|
+
const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),f=new p({projectPath:i,logger:b,remoteServerUrl:E,authToken:P,projectId:_})
|
|
1205
1292
|
u=setInterval((async()=>{try{if(await T.hasValidToken())return
|
|
1206
1293
|
console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
|
|
1207
1294
|
const e=await T.startAuthenticationFlow()
|
|
1208
1295
|
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(T)){const e=await T.getToken()
|
|
1209
|
-
e?.token&&(P=e.token,$.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){
|
|
1210
|
-
const
|
|
1211
|
-
switch("--help"!==
|
|
1296
|
+
e?.token&&(P=e.token,$.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){b.warn("Token watchdog check failed",{error:(0,I.getErrorMessage)(e)})}}),3e5),await f.start(g)}catch(e){b.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:C})
|
|
1297
|
+
const E=b[0].toLowerCase()
|
|
1298
|
+
switch("--help"!==E&&"-h"!==E||(s(),process.exit(0)),"--version"!==E&&"-v"!==E||(console.log("alchemy v"+t()),process.exit(0)),E){case"serve":await async function(e={}){const s=process.cwd()
|
|
1212
1299
|
r(s)||(console.log("\u{1f4c1} Current directory is not an AlchemyLab project."),console.log(""),console.log("\u{1f4a1} Run `alchemy` to initialize this project."),console.log(""),process.exit(1))
|
|
1213
1300
|
const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:s}),n=t()
|
|
1214
1301
|
console.log(`\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 AlchemyLab Server v${n.padEnd(10)} \u2551\n\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n`),console.log("\u{1f4c1} Project: "+s)
|
|
@@ -1216,13 +1303,13 @@ const i=await a(o),c=new f.TokenManager(o,s,{remoteServerUrl:i,allowInteractiveA
|
|
|
1216
1303
|
await c.initialize()
|
|
1217
1304
|
const d=await c.getToken(),u=d?.token
|
|
1218
1305
|
u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
|
|
1219
|
-
const h=e.port||await(0,
|
|
1306
|
+
const h=e.port||await(0,k.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:h,authToken:u})
|
|
1220
1307
|
try{await g.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${g.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
|
|
1221
1308
|
let e=!1
|
|
1222
1309
|
const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
|
|
1223
|
-
process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,
|
|
1310
|
+
process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:C})
|
|
1224
1311
|
break
|
|
1225
1312
|
case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),s=await a(t),r=new f.TokenManager(t,e,{remoteServerUrl:s,allowInteractiveAuth:!1})
|
|
1226
1313
|
await r.initialize(),await r.hasValidToken()?await r.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
|
|
1227
1314
|
break
|
|
1228
|
-
default:console.error("\u274c Unknown command: "+
|
|
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})()
|