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