alchemylab 1.0.13 → 1.1.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/478.js +6 -6
- package/dist/60.js +10 -10
- package/dist/800.js +7 -8
- package/dist/cli.js +254 -196
- package/dist/http-server.js +1449 -1760
- package/dist/index.js +1439 -1732
- package/dist/mcp-direct.js +604 -897
- package/dist/plugins/hooks/lib/dedup-utils.js +0 -28
- package/package.json +2 -1
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,g=(0,s.simpleGit)(a),h=await g.status(),p=await g.branch(),f=p.all.includes("main")?"main":"master"
|
|
14
|
+
if(h.current!==f&&await g.checkout(f),!p.all.includes(u))return{success:!1,message:`Branch '${u}' not found. The request may not have any file changes to merge.`,error:"Branch not found"}
|
|
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 g.merge([u,"--no-ff","-m",e])
|
|
18
|
+
const t=await g.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 g.merge(["--abort"])}catch(e){n.error("CRITICAL: git merge --abort failed \u2014 repo may be in inconsistent state",{requestId:o,error:e instanceof Error?e.message:e+""})}return{success:!1,message:`Merge conflicts detected for request ${o}. Please resolve conflicts before finalizing.`,error:"Merge conflicts",data:{hasConflicts:!0}}}return{success:!1,message:"Merge failed: "+t,error:t}}try{await g.deleteLocalBranch(u,!0),y=!0}catch{}try{await i.post(`/api/projects/${c}/requests/${o}/finalize-completion`,{summary:"Finalized request "+o,acceptanceEvidence:[{evidence:"Finalized via GUI after review"}],mergeResult:{mergeCommitHash:m||"unknown",cleanedUp:y,method:"action-server-merge"}})}catch(e){const t=(0,r.getErrorMessage)(e)
|
|
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"
|
|
@@ -34,21 +34,22 @@ o.push(t),this.histograms.set(r,o)}recordValue(e,t,s={}){this.observeHistogram(e
|
|
|
34
34
|
const t=Date.now()
|
|
35
35
|
return(s={})=>{const r=Date.now()-t
|
|
36
36
|
this.observeHistogram(e,r,s)}}getMetrics(){return{counters:Object.fromEntries(this.counters),gauges:Object.fromEntries(this.gauges),histograms:Object.fromEntries(this.histograms)}}reset(){this.counters.clear(),this.gauges.clear(),this.histograms.clear()}}},1187(e,exports,t){"use strict"
|
|
37
|
+
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
37
38
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.UnifiedLauncher=void 0
|
|
38
|
-
const s=t(5317),
|
|
39
|
-
exports.UnifiedLauncher=class{constructor(e,t,s){this.logger=e,this.installContext=t,this.configurer=s}async launch(e){const{projectPath:t,settings:s,multiAgent:
|
|
40
|
-
this.logger.info("UnifiedLauncher: Starting launch",{projectPath:t,installPath:
|
|
41
|
-
try{const
|
|
42
|
-
return
|
|
43
|
-
try{(0,
|
|
44
|
-
const{resolveServeCommand:
|
|
45
|
-
return new Promise((e=>{const t=(0,
|
|
46
|
-
let
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
if(
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
const r=s(t(6928)),o=t(5317),n=t(8824)
|
|
40
|
+
exports.UnifiedLauncher=class{constructor(e,t,s){this.logger=e,this.installContext=t,this.configurer=s}async launch(e){const{projectPath:t,settings:s,multiAgent:r}=e,{installPath:o,nodePath:i}=this.installContext
|
|
41
|
+
this.logger.info("UnifiedLauncher: Starting launch",{projectPath:t,installPath:o})
|
|
42
|
+
try{const n=await this.configurer.configureOpenCode(o,t,i,s,r)
|
|
43
|
+
return n.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:n.configPath}),this.launchBackground(e,n)):(this.logger.error("UnifiedLauncher: Configuration failed",{error:n.error}),{success:!1,error:n.error})}catch(e){return this.logger.error("UnifiedLauncher: Launch failed",{error:(0,n.getErrorMessage)(e)}),{success:!1,error:(0,n.getErrorMessage)(e)}}}async launchBackground(e,s){const{projectPath:n,envVars:i}=e
|
|
44
|
+
try{(0,o.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"OpenCode not found in PATH."}}this.logger.info("UnifiedLauncher: Launching opencode serve in background",{command:"opencode serve",cwd:n})
|
|
45
|
+
const{resolveServeCommand:a}=await Promise.resolve().then(t.bind(t,9068)),c=a()
|
|
46
|
+
return new Promise((e=>{const t=r.default.join(n,".alchemy_lab","opencode"),a=(0,o.spawn)(c.command,[...c.prependArgs,"--port","0","--data-dir",t],{stdio:["ignore","pipe","pipe"],cwd:n,detached:!0,env:{...process.env,...i}})
|
|
47
|
+
let l="",d=!1
|
|
48
|
+
const u=t=>{l+=t
|
|
49
|
+
const r=l.match(/listening on http:\/\/[^:]+:(\d+)/)
|
|
50
|
+
if(r&&!d){const t=parseInt(r[1],10)
|
|
51
|
+
d=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:a.pid,port:t}),a.unref(),e({success:!0,pid:a.pid,port:t,configPath:s.configPath})}}
|
|
52
|
+
a.stdout?.on("data",(e=>u(e.toString()))),a.stderr?.on("data",(e=>u(e.toString()))),setTimeout((()=>{d||(d=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:l.slice(0,500)}),a.unref(),e({success:!0,pid:a.pid,configPath:s.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),a.on("error",(t=>{d||(d=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),a.on("exit",(t=>{d||(d=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:t,output:l.slice(0,500)}),e({success:!1,error:"opencode serve exited with code "+t}))}))}))}}},1253(e,exports,t){"use strict"
|
|
52
53
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
53
54
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
|
|
54
55
|
const r=s(t(6928)),o=s(t(1943)),n=s(t(6982)),i=s(t(857)),a=t(9067),c=t(8824),l="alchemy-lab"
|
|
@@ -111,18 +112,46 @@ const t=this.remoteServerUrl+"/auth/validate"
|
|
|
111
112
|
this.logger.debug("Making token validation request",{validationUrl:t})
|
|
112
113
|
const s=await fetch(t,{method:"GET",headers:{Authorization:"Bearer "+e.token,"Content-Type":"application/json"},signal:AbortSignal.timeout(1e4)})
|
|
113
114
|
if(s.ok){const e=await s.json()
|
|
114
|
-
return this.logger.info("Token validation successful",{status:e.status||"unknown",username:e.username||"unknown"}),!0}return this.logger.info("Token validation failed - token may be invalid or expired",{status:s.status,statusText:s.statusText}),this.invalidateCache(),!1}catch(e){return e instanceof Error&&"AbortError"===e.name?(this.logger.info("Token validation request timed out - assuming offline or server unavailable"),!0):(this.logger.info("Token validation request failed",{error:(0,c.getErrorMessage)(e)}),!0)}}async hasValidAndWorkingToken(){return!!await this.hasValidToken()&&await this.validateTokenWithRemoteServer()}async isGoogleOAuthAvailable(){return await this.authFlow.isGoogleOAuthAvailable()}async startGoogleOAuthFlow(){return await this.authFlow.startGoogleOAuthFlow()}async startGoogleOAuthPolling(e,t,s,r,o){return await this.authFlow.startGoogleOAuthPolling(e,t,s,r,o)}}},
|
|
115
|
+
return this.logger.info("Token validation successful",{status:e.status||"unknown",username:e.username||"unknown"}),!0}return this.logger.info("Token validation failed - token may be invalid or expired",{status:s.status,statusText:s.statusText}),this.invalidateCache(),!1}catch(e){return e instanceof Error&&"AbortError"===e.name?(this.logger.info("Token validation request timed out - assuming offline or server unavailable"),!0):(this.logger.info("Token validation request failed",{error:(0,c.getErrorMessage)(e)}),!0)}}async hasValidAndWorkingToken(){return!!await this.hasValidToken()&&await this.validateTokenWithRemoteServer()}async isGoogleOAuthAvailable(){return await this.authFlow.isGoogleOAuthAvailable()}async startGoogleOAuthFlow(){return await this.authFlow.startGoogleOAuthFlow()}async startGoogleOAuthPolling(e,t,s,r,o){return await this.authFlow.startGoogleOAuthPolling(e,t,s,r,o)}}},1292(e,exports,t){"use strict"
|
|
116
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.SessionSpawnService=void 0
|
|
117
|
+
const s=t(9896),r=t(6928),o=t(2424),n=t(8824),i=t(5671),a=t(8955)
|
|
118
|
+
exports.SessionSpawnService=class{constructor(e){this.deps=e}getOpenCodeServerUrl(e){try{const t=(0,r.join)(e,".alchemy_lab","opencode.json"),o=(0,s.readFileSync)(t,"utf8"),n=JSON.parse(o)
|
|
119
|
+
if(n.port)return"http://127.0.0.1:"+n.port}catch(e){this.deps.logger.debug("Could not read opencode.json for server URL",{error:(0,n.getErrorMessage)(e)})}return null}async prepareSession(e){const{agent:t,prompt:s,projectPath:r,requestId:i,parentSessionId:a,specialization:c,callerAgentType:l}=e,{logger:d}=this.deps,u=this.deps.projectId,g=this.getOpenCodeServerUrl(r)
|
|
120
|
+
if(!g)throw Error("Cannot prepare session: OpenCode server not running (no .alchemy_lab/opencode.json)")
|
|
121
|
+
const h=e.title?e.title:i?`[${i}] ${s.slice(0,40)}${s.length>40?"...":""}`:`${s.slice(0,50)}${s.length>50?"...":""}`,p={agentType:t}
|
|
122
|
+
i&&(p.requestId=i),c&&(p.specialization=c),l&&(p.callerAgentType=l)
|
|
123
|
+
const f=await fetch(g+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:h,...a?{parentID:a}:{},metadata:p})})
|
|
124
|
+
if(!f.ok)throw Error("Failed to create session in OpenCode: "+f.status)
|
|
125
|
+
const m=await f.json()
|
|
126
|
+
if(!m.id)throw Error("Session created in OpenCode but no ID returned")
|
|
127
|
+
const y=m.id
|
|
128
|
+
d.info("Pre-created OpenCode session",{sessionId:y,title:h,agent:t})
|
|
129
|
+
const v=this.registerSession({sessionId:y,parentSessionId:a,projectId:u,agent:t,title:h,specialization:c,callerAgentType:l,requestId:i}),w=this.resolveModel(t,r),C=i?(0,o.ensureRequestWorktree)(r,i,d).catch((e=>{d.warn("Pre-spawn worktree creation failed (non-fatal)",{requestId:i,error:(0,n.getErrorMessage)(e)})})):Promise.resolve(),[,I]=await Promise.all([v,w,C])
|
|
130
|
+
return{sessionId:y,model:I,serverUrl:g,initialBranch:i?"requests/"+i:void 0}}async registerSession(e){const{sessionId:t,parentSessionId:s,projectId:r,agent:o,title:i,specialization:a,callerAgentType:c,requestId:l}=e,{apiClient:d,logger:u}=this.deps
|
|
131
|
+
try{let e,g
|
|
132
|
+
if(s)try{const t=await d.post(`/api/projects/${r}/cli-sessions`,{externalSessionId:s,providerId:"opencode",status:"active",agentType:c||void 0}),o=t?.data||t
|
|
133
|
+
e=o?.id,g=o?.workflowId,u.debug("Registered parent session",{parentSessionId:s,parentDbId:e,parentWorkflowId:g})}catch(e){u.warn("Failed to register parent session",{error:(0,n.getErrorMessage)(e)})}if(await d.post(`/api/projects/${r}/cli-sessions`,{externalSessionId:t,providerId:"opencode",parentSessionId:e||null,title:i,status:"active",agentType:o,specialization:a,workflowId:g||void 0}),u.info("Registered session in alchemy DB",{sessionId:t,agent:o,parentSessionId:s}),l){try{await d.post(`/api/projects/${r}/session-request-associations`,{opencodeSessionId:t,requestLocalId:l}),u.info("Created session-request association",{sessionId:t,requestId:l})}catch(e){u.warn("Failed to create session-request association",{error:(0,n.getErrorMessage)(e)})}if("coder"===o)try{await d.patch(`/api/projects/${r}/requests/${l}`,{cliOrigin:{providerId:"opencode",sessionId:t,createdAt:(new Date).toISOString()}}),u.debug("Updated request cliOrigin",{sessionId:t,requestId:l})}catch(e){u.warn("Failed to update request cliOrigin",{error:(0,n.getErrorMessage)(e)})}}}catch(e){u.warn("Failed to register session in alchemy DB",{error:(0,n.getErrorMessage)(e)})}}async resolveModel(e,t){const{logger:s}=this.deps
|
|
134
|
+
let r
|
|
135
|
+
try{const s=await this.fetchAgentConfig(e)
|
|
136
|
+
if(s){const e=s.preferredProvider||"inherit",o=s.preferredModelTier||"same",n=(0,i.resolveAgentModel)(t,e,o)
|
|
137
|
+
n.resolved&&(r=n.fullModel)}else{const s=(0,a.getBuiltInAgentConfig)(e)
|
|
138
|
+
if(s){const e=(0,i.resolveAgentModel)(t,s.preferredProvider||"inherit",s.preferredModelTier||"same")
|
|
139
|
+
e.resolved&&(r=e.fullModel)}}}catch(t){s.debug("Error resolving agent model",{agent:e,error:(0,n.getErrorMessage)(t)})}const o=this.fetchProjectDefaultModel()
|
|
140
|
+
if(o)if(r){const t=await this.fetchAgentConfig(e)
|
|
141
|
+
"same"===(t?.preferredModelTier||"same")&&(r=o)}else r=o
|
|
142
|
+
return r}async fetchAgentConfig(e){try{const t=await this.deps.apiClient.get(`/api/projects/${this.deps.projectId}/agents`)
|
|
143
|
+
return(t?.data||t||[]).find((t=>t.name===e))||null}catch{return null}}fetchProjectDefaultModel(){return this.deps.getDefaultModel()||null}}},1943(module){"use strict"
|
|
115
144
|
module.exports=require("fs/promises")},2096(e,exports,t){"use strict"
|
|
116
145
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
117
146
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProjectConfigService=void 0
|
|
118
147
|
const r=s(t(1943)),o=s(t(6928)),n=s(t(857)),i=t(4139),a=t(8824)
|
|
119
148
|
exports.ProjectConfigService=class{constructor(e){this.logger=e.child({component:"project-config"})}async readProjectId(e){try{const t=o.default.join(e,i.ALCHEMY_DIR_NAME,"project.json"),s=await r.default.readFile(t,"utf-8"),n=JSON.parse(s)
|
|
120
149
|
return n.id?n.id+"":null}catch(e){return this.logger.debug("Could not read project ID",{error:(0,a.getErrorMessage)(e)}),null}}async readProjectConfig(e){try{const t=o.default.join(e,i.ALCHEMY_DIR_NAME,"config.json"),s=await r.default.readFile(t,"utf-8")
|
|
121
|
-
return JSON.parse(s)}catch(e){return this.logger.debug("Could not read project config",{error:(0,a.getErrorMessage)(e)}),{}}}async
|
|
122
|
-
await r.default.mkdir(
|
|
123
|
-
let
|
|
124
|
-
try{const e=await r.default.readFile(
|
|
125
|
-
|
|
150
|
+
return JSON.parse(s)}catch(e){return this.logger.debug("Could not read project config",{error:(0,a.getErrorMessage)(e)}),{}}}async saveProjectConfig(e,t){try{const s=o.default.join(e,i.ALCHEMY_DIR_NAME),n=o.default.join(s,"config.json")
|
|
151
|
+
await r.default.mkdir(s,{recursive:!0})
|
|
152
|
+
let a={}
|
|
153
|
+
try{const e=await r.default.readFile(n,"utf-8")
|
|
154
|
+
a=JSON.parse(e)}catch(e){this.logger.debug("No existing config.json, creating new one")}void 0!==t&&(a.autoJournal=t),a.lockTimeout||(a.lockTimeout=3600),a.worktreeCleanupDelay||(a.worktreeCleanupDelay=900),a.maxWorktreeAge||(a.maxWorktreeAge=24),a.cleanupInterval||(a.cleanupInterval=15),void 0===a.preserveBranchesOnCleanup&&(a.preserveBranchesOnCleanup=!0),a.maxConcurrentSessions||(a.maxConcurrentSessions=10),a.sessionTimeoutWarning||(a.sessionTimeoutWarning=45),await r.default.writeFile(n,JSON.stringify(a,null,2),"utf-8"),this.logger.info("Saved project config",{configPath:n})}catch(e){this.logger.error("Failed to save project config",{error:(0,a.getErrorMessage)(e)})}}getToolPreferencesPath(){const e=n.default.homedir()
|
|
126
155
|
return o.default.join(e,".alchemy_lab","tool-preferences.json")}async loadToolPreferences(e){const t=this.getToolPreferencesPath()
|
|
127
156
|
try{const s=await r.default.readFile(t,"utf-8"),o=JSON.parse(s)
|
|
128
157
|
return e&&o[e]?o[e]:e?null:o}catch(t){return this.logger.debug("No user preferences found",{error:(0,a.getErrorMessage)(t)}),e?null:{}}}async saveToolPreferences(e,t,s,n){const i=this.getToolPreferencesPath(),a=o.default.dirname(i)
|
|
@@ -132,20 +161,20 @@ try{const e=await r.default.readFile(i,"utf-8")
|
|
|
132
161
|
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"
|
|
133
162
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
134
163
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
|
|
135
|
-
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),
|
|
136
|
-
class
|
|
164
|
+
const r=s(t(7252)),o=s(t(8577)),n=t(4192),i=t(1152),a=s(t(6928)),c=s(t(9896)),l=s(t(857)),d=t(5607),u=t(8984),g=t(8524),h=t(8824),p=t(2096),f=t(7210),m=t(2928),y=t(1292),v=t(5317)
|
|
165
|
+
class w{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.settingsSyncCounter=0,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
|
|
137
166
|
const s=(0,i.createMetricsCollector)(!1)
|
|
138
|
-
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
|
|
167
|
+
this.apiClient=new n.BaseApiClient(t.remoteServerUrl,e,s,t.authToken,{timeout:35e3}),this.configurer=new u.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new g.CLIBridgeWebSocket(e),(0,g.setCLIBridge)(this.cliBridge),this.hookExecutor=new f.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,r.default)()
|
|
139
168
|
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
|
|
140
169
|
if(!r)return void t.status(400).json({success:!1,error:"No project"})
|
|
141
170
|
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})))
|
|
142
|
-
t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,
|
|
171
|
+
t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:s,sessionId:r,variables:o,timing:n}=e.body
|
|
143
172
|
if(!s)return void t.status(400).json({success:!1,error:"eventType is required"})
|
|
144
173
|
const i=await this.hookExecutor.processEvent({eventType:s,sessionId:r||"",variables:o||{},timing:n})
|
|
145
|
-
t.json({success:!0,data:i})}catch(e){this.logger.error("Hook event processing failed",{error:(0,
|
|
174
|
+
t.json({success:!0,data:i})}catch(e){this.logger.error("Hook event processing failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.post("/api/spawn-session",this.handleSpawnSession.bind(this)),e.get("/api/stuck-sessions",this.handleGetStuckSessions.bind(this)),e.post("/api/stuck-sessions/test-inject",this.handleTestInjectStuckSession.bind(this)),e.post("/api/stuck-sessions/:sessionId/retry",this.handleRetryStuckSession.bind(this)),e.use("/api/opencode",((e,t)=>this.handleOpenCodeProxy(e,t))),e}handleHealth(e,t){t.json({status:"healthy",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),timestamp:(new Date).toISOString()})}handleStatus(e,t){t.json({status:"running",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),startTime:this.startTime.toISOString(),projectId:this.projectId,serverId:this.serverId,isPolling:this.isPolling,port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const s=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
146
175
|
try{if(!c.default.existsSync(s))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
|
|
147
176
|
const e=c.default.readFileSync(s,"utf-8"),r=JSON.parse(e)
|
|
148
|
-
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,
|
|
177
|
+
t.json({port:r.port,pid:r.pid,startedAt:r.startedAt,baseUrl:"http://localhost:"+r.port,directory:this.options.projectPath,proxyEnabled:!0})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,h.getErrorMessage)(e)})}}async handleSyncAgents(e,s){try{const e=this.readProjectConfig()
|
|
149
178
|
if(!e?.id)return void s.status(400).json({success:!1,error:"No project configuration found"})
|
|
150
179
|
this.logger.info("Syncing agents from remote",{projectId:e.id})
|
|
151
180
|
const r=await this.configurer.fetchAgentsFromRemote(e.id)
|
|
@@ -162,37 +191,37 @@ const t="alchemy",s=this.configurer.generateAgentMarkdown(e,t,"anthropic/claude-
|
|
|
162
191
|
await c.default.promises.writeFile(r,s,"utf8"),l++,this.logger.info("Synced agent file",{name:e.name,path:r,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
|
|
163
192
|
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t),n=o?.metadata?.bashToolAgents||{}
|
|
164
193
|
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])
|
|
165
|
-
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,
|
|
194
|
+
c.default.writeFileSync(e,JSON.stringify(s,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,h.getErrorMessage)(e)})}let d=!1
|
|
166
195
|
try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
167
196
|
if(c.default.existsSync(e)){const s=c.default.readFileSync(e,"utf-8"),r=JSON.parse(s)
|
|
168
|
-
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,
|
|
197
|
+
if(r.pid)try{process.kill(r.pid,"SIGTERM"),this.logger.info("Killed existing OpenCode process",{pid:r.pid}),await new Promise((e=>setTimeout(e,500)))}catch(e){"ESRCH"!==e?.code&&this.logger.warn("Could not kill OpenCode",{error:(0,h.getErrorMessage)(e)})}const{resolveServeCommand:o}=await Promise.resolve().then(t.bind(t,9068)),n=o(),i=(0,v.spawn)(n.command,[...n.prependArgs,"--port","0"],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),a=new Promise((e=>{let t=""
|
|
169
198
|
const s=setTimeout((()=>{this.logger.warn("Timeout waiting for OpenCode port, killing process")
|
|
170
199
|
try{i.kill("SIGTERM")}catch{}e(null)}),1e4)
|
|
171
200
|
i.stdout?.on("data",(r=>{t+=r.toString()
|
|
172
201
|
const o=t.match(/listening on http:\/\/[^:]+:(\d+)/)
|
|
173
202
|
o&&(clearTimeout(s),i.unref(),e({port:parseInt(o[1],10),pid:i.pid}))})),i.on("error",(()=>{clearTimeout(s)
|
|
174
203
|
try{i.kill("SIGTERM")}catch{}e(null)}))})),l=await a
|
|
175
|
-
l&&(this.trackChildPid(l.pid),c.default.writeFileSync(e,JSON.stringify({port:l.port,pid:l.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:l.port,pid:l.pid}),this.setOpenCodePort(l.port),d=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,
|
|
204
|
+
l&&(this.trackChildPid(l.pid),c.default.writeFileSync(e,JSON.stringify({port:l.port,pid:l.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:l.port,pid:l.pid}),this.setOpenCodePort(l.port),d=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,h.getErrorMessage)(e)})}s.json({success:!0,message:`Synced ${l} agent(s) to local files${d?" - OpenCode reloading":""}`,count:l,restarted:d})}catch(e){this.logger.error("Failed to sync agents",{error:(0,h.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
|
|
176
205
|
if(!r)return void s.status(400).json({success:!1,error:"requestId is required"})
|
|
177
206
|
if(!/^req\d+$/.test(r))return void s.status(400).json({success:!1,error:"Invalid requestId format"})
|
|
178
207
|
this.logger.info("Generating journal for request",{requestId:r})
|
|
179
|
-
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,
|
|
180
|
-
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,
|
|
208
|
+
const o=`Generate a journal entry for request ${r}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${r}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${r}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,v.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
|
|
209
|
+
a.pid&&this.trackChildPid(a.pid),a.unref(),s.json({success:!0,message:"Journal generation started for "+r})}catch(e){this.logger.error("Failed to generate journal",{error:(0,h.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleEventHooksProxy(e,t){try{const{eventType:s}=e.params,{sessionId:r}=e.query,o=this.projectId
|
|
181
210
|
if(!o)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
182
211
|
this.logger.info("Proxying event hooks request",{projectId:o,eventType:s,sessionId:r})
|
|
183
212
|
const n=r?"?sessionId="+encodeURIComponent(r):"",i=await this.apiClient.get(`/api/projects/${o}/event-hooks/by-event/${s}${n}`)
|
|
184
|
-
t.json(i)}catch(e){this.logger.error("Failed to fetch event hooks",{error:(0,
|
|
213
|
+
t.json(i)}catch(e){this.logger.error("Failed to fetch event hooks",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleHookTriggersProxy(e,t){try{const s=this.projectId
|
|
185
214
|
if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
186
215
|
const{hookId:r,hookName:o,eventType:n,executionMode:i,agent:a}=e.body
|
|
187
216
|
this.logger.debug("Proxying hook trigger notification",{projectId:s,hookName:o,eventType:n})
|
|
188
217
|
const c=await this.apiClient.post(`/api/projects/${s}/hook-triggers`,{hookId:r,hookName:o,eventType:n,executionMode:i,agent:a})
|
|
189
|
-
t.json(c)}catch(e){this.logger.debug("Failed to proxy hook trigger",{error:(0,
|
|
218
|
+
t.json(c)}catch(e){this.logger.debug("Failed to proxy hook trigger",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetHookIteration(e,t){try{const s=this.readProjectConfig()
|
|
190
219
|
if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
191
220
|
const{sessionId:r,hookId:o}=e.params,n=await this.apiClient.get(`/api/projects/${s.id}/hook-iterations/${r}/${o}`)
|
|
192
|
-
t.json(n)}catch(e){this.logger.debug("Failed to get hook iteration",{error:(0,
|
|
221
|
+
t.json(n)}catch(e){this.logger.debug("Failed to get hook iteration",{error:(0,h.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleIncrementHookIteration(e,t){try{const s=this.readProjectConfig()
|
|
193
222
|
if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
194
223
|
const{sessionId:r,hookId:o}=e.params,n=await this.apiClient.post(`/api/projects/${s.id}/hook-iterations/${r}/${o}/increment`,{})
|
|
195
|
-
t.json(n)}catch(e){this.logger.debug("Failed to increment hook iteration",{error:(0,
|
|
224
|
+
t.json(n)}catch(e){this.logger.debug("Failed to increment hook iteration",{error:(0,h.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleDirectReport(e,t){try{const s=this.projectId
|
|
196
225
|
if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
197
226
|
const{sessionId:r,requestId:o,summary:n,status:i}=e.body
|
|
198
227
|
if(!r)return void t.status(400).json({success:!1,error:"sessionId is required"})
|
|
@@ -200,26 +229,26 @@ this.logger.info("Direct report creation",{sessionId:r,requestId:o,status:i})
|
|
|
200
229
|
const a=[]
|
|
201
230
|
try{const e=await this.apiClient.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),t=e?.data||e||[]
|
|
202
231
|
let o=r
|
|
203
|
-
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,
|
|
232
|
+
for(const e of t)a.push({ancestorId:e.externalSessionId,childId:o}),o=e.externalSessionId}catch(e){this.logger.warn("Failed to fetch ancestor chain",{sessionId:r,error:(0,h.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
|
|
204
233
|
let c,l=n
|
|
205
234
|
if(!l&&o)try{const e=await this.apiClient.get(`/api/projects/${s}/requests/${o}`),t=e?.data||e
|
|
206
235
|
c=t?.title
|
|
207
236
|
const r=t?.completionSummary
|
|
208
|
-
r?l=r:c&&(l="Done: "+c)}catch(e){this.logger.debug("Could not fetch request details for summary",{requestId:o,error:(0,
|
|
237
|
+
r?l=r:c&&(l="Done: "+c)}catch(e){this.logger.debug("Could not fetch request details for summary",{requestId:o,error:(0,h.getErrorMessage)(e)})}l||(l="Completed request "+(o||"unknown"))
|
|
209
238
|
const d=o||r
|
|
210
239
|
if(this.directReportDedup.size>=1e3){const e=this.directReportDedup.keys().next().value
|
|
211
240
|
void 0!==e&&this.directReportDedup.delete(e)}this.directReportDedup.has(d)||this.directReportDedup.set(d,new Set)
|
|
212
|
-
const u=this.directReportDedup.get(d),
|
|
241
|
+
const u=this.directReportDedup.get(d),g=a.filter((({ancestorId:e})=>!u.has(e)||(this.logger.info("Direct report dedup: already notified",{ancestorId:e,dedupKey:d}),!1))),p=await Promise.allSettled(g.map((async({ancestorId:e,childId:t})=>{const r=await this.apiClient.post(`/api/projects/${s}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),o=r?.data||r
|
|
213
242
|
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=[]
|
|
214
243
|
for(const e of p)"fulfilled"===e.status&&f.push(e.value)
|
|
215
|
-
for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,
|
|
244
|
+
for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,h.getErrorMessage)(e.reason)})
|
|
216
245
|
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
|
|
217
|
-
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,
|
|
246
|
+
this.logger.info("Released request associations",{sessionId:r,count:t.length,failCount:o})}catch(e){this.logger.warn("Failed to release associations",{sessionId:r,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,data:{notifiedSessions:f,report:f[0]?{messageCode:f[0].messageCode}:void 0,parentExternalSessionId:a[0]?.ancestorId||null}})}catch(e){this.logger.error("Direct report creation failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetSessionAgentType(e,t){try{const{sessionId:s}=e.params,r=this.readProjectConfig()
|
|
218
247
|
if(r?.id)try{const e=await this.apiClient.get(`/api/projects/${r.id}/cli-sessions/external/opencode/${encodeURIComponent(s)}`),o=e?.data||e
|
|
219
|
-
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,
|
|
220
|
-
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,
|
|
248
|
+
if(this.logger.info("Agent type DB lookup",{sessionId:s,dbAgentType:o?.agentType||null,dbId:o?.id||o?.localId||null}),o?.agentType)return void t.json({success:!0,agentType:o.agentType,source:"db"})}catch(e){this.logger.info("Agent type DB lookup failed",{sessionId:s,error:(0,h.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:s},5e3)
|
|
249
|
+
if(this.logger.info("Agent type bridge lookup",{sessionId:s,bridgeAgentType:e?.data?.agentType||null,bridgeSuccess:e?.success}),e.success&&e.data?.agentType)return void t.json({success:!0,agentType:e.data.agentType,source:"bridge"})}catch(e){this.logger.info("Agent type bridge lookup failed",{sessionId:s,error:(0,h.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:s}),t.json({success:!0,agentType:null,source:"unknown"})}catch(e){this.logger.debug("Failed to get session agent type",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetSessionParent(e,t){try{const{sessionId:s}=e.params,r=this.readProjectConfig()
|
|
221
250
|
if(r?.id)try{const e=await this.apiClient.get(`/api/projects/${r.id}/cli-sessions/resolve/${encodeURIComponent(s)}`),o=e?.data?.parent?.externalSessionId||""
|
|
222
|
-
return void t.json({success:!0,parentSessionId:o})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:s,error:(0,
|
|
251
|
+
return void t.json({success:!0,parentSessionId:o})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:s,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{const s=this.getOpenCodeUrl()
|
|
223
252
|
if(!s)return void t.status(502).json({error:"OpenCode not running"})
|
|
224
253
|
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")
|
|
225
254
|
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})
|
|
@@ -238,23 +267,27 @@ const t=this.getOpenCodeUrl()
|
|
|
238
267
|
if(!t||t===s)throw e
|
|
239
268
|
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()
|
|
240
269
|
const u=l.headers.get("content-type")||""
|
|
241
|
-
let
|
|
242
|
-
try{
|
|
270
|
+
let g
|
|
271
|
+
try{g=u.includes("application/json")?await l.json():await l.text()}catch{return void t.status(l.status).end()}t.status(l.status),u&&t.setHeader("Content-Type",u),"string"==typeof g?t.send(g):t.json(g)}catch(s){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,h.getErrorMessage)(s),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,h.getErrorMessage)(s)})}}async handleSpawnSession(e,t){try{const{agent:s,prompt:r,requestId:o,parentSessionId:n,title:i,specialization:a,allowParallel:c,callerAgentType:l}=e.body
|
|
272
|
+
if(!s||!r)return void t.status(400).json({success:!1,error:"agent and prompt are required"})
|
|
273
|
+
if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
274
|
+
const d=this.projectId,u=new y.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:d,getDefaultModel:()=>this.cachedDefaultModel||void 0}),g=await u.prepareSession({agent:s,prompt:r,projectPath:this.options.projectPath,requestId:o,parentSessionId:n,title:i,specialization:a,allowParallel:c,callerAgentType:l})
|
|
275
|
+
t.json({success:!0,data:g})}catch(e){this.logger.error("Spawn session failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetAgentByName(e,t){try{const s=this.readProjectConfig()
|
|
243
276
|
if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
244
277
|
const{agentName:r}=e.params,o=await this.apiClient.get(`/api/projects/${s.id}/agents`),n=(o?.data||o||[]).find((e=>e.name===r))
|
|
245
278
|
if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
|
|
246
|
-
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,
|
|
279
|
+
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<w.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
|
|
247
280
|
const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
|
|
248
281
|
try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
|
|
249
282
|
const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
|
|
250
|
-
return this.cachedProjectConfig={config:s,timestamp:Date.now()},s}catch(e){return this.logger.error("Failed to read project config",{error:(0,
|
|
283
|
+
return this.cachedProjectConfig={config:s,timestamp:Date.now()},s}catch(e){return this.logger.error("Failed to read project config",{error:(0,h.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e
|
|
251
284
|
const t="http://127.0.0.1:"+e
|
|
252
|
-
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,
|
|
285
|
+
this.openCodeUrlCache={url:t,timestamp:Date.now()},this.logger.info("OpenCode port set directly",{port:e}),this.cachedDefaultModel||this.seedDefaultModelFromZen(e).catch((e=>this.logger.debug("Failed to seed zen default model",{error:(0,h.getErrorMessage)(e)})))}async seedDefaultModelFromZen(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
|
|
253
286
|
if(!t.ok)return
|
|
254
287
|
const s=await t.json(),r=s?.default||{},[o,n]=Object.entries(r)[0]||[]
|
|
255
288
|
if(!o||!n)return
|
|
256
289
|
const i=`${o}/${n}`
|
|
257
|
-
this.cachedDefaultModel=i,this.hookExecutor.setDefaultModel(i),this.logger.info("Seeded defaultModel from OpenCode zen default",{defaultModel:i}),this.apiClient.post("/api/user/preferences",{defaultModel:i}).catch((e=>this.logger.debug("Failed to seed user preference",{error:(0,
|
|
290
|
+
this.cachedDefaultModel=i,this.hookExecutor.setDefaultModel(i),this.logger.info("Seeded defaultModel from OpenCode zen default",{defaultModel:i}),this.apiClient.post("/api/user/preferences",{defaultModel:i}).catch((e=>this.logger.debug("Failed to seed user preference",{error:(0,h.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
|
|
258
291
|
if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
|
|
259
292
|
const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
260
293
|
try{if(!c.default.existsSync(e))return null
|
|
@@ -290,13 +323,13 @@ const[r,o]=await Promise.all([fetch(s+"/session/status"),fetch(s+"/session")]),n
|
|
|
290
323
|
let i,a
|
|
291
324
|
if(o.ok){const t=(await o.json()).find((t=>t.id===e))
|
|
292
325
|
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
|
|
293
|
-
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,
|
|
326
|
+
s?.agentType&&(a=s.agentType)}catch{}return{success:!0,data:{id:e,title:i||"(untitled)",status:n[e]?.type||"idle",agentType:a}}}default:return{success:!1,error:"Unknown session action: "+e}}}catch(e){return{success:!1,error:(0,h.getErrorMessage)(e)}}}async register(){const e=this.readProjectConfig()
|
|
294
327
|
if(!e?.id)throw Error("No project configuration found")
|
|
295
328
|
this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId)
|
|
296
329
|
const t={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:l.default.hostname(),platform:process.platform}
|
|
297
330
|
this.logger.info("Registering with remote",{projectId:this.projectId})
|
|
298
331
|
const s=await this.apiClient.post("/api/servers/register",t)
|
|
299
|
-
s.success&&s.data?.serverId?(this.serverId=s.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})):this.logger.warn("Registration failed, continuing without remote connection",{error:s.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{try{await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString()})}catch(e){this.logger.warn("Heartbeat failed",{error:(0,
|
|
332
|
+
s.success&&s.data?.serverId?(this.serverId=s.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})):this.logger.warn("Registration failed, continuing without remote connection",{error:s.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{try{await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString()})}catch(e){this.logger.warn("Heartbeat failed",{error:(0,h.getErrorMessage)(e)})}}),3e4))}startSSEEventForwarder(){this.logger.info("Starting SSE event forwarder"),this.connectToOpenCodeSSE(),this.sseForwarderInterval=setInterval((async()=>{await this.flushSSEEvents()}),2e3)}stopSSEEventForwarder(){this.sseForwarderInterval&&(clearInterval(this.sseForwarderInterval),this.sseForwarderInterval=void 0),this.sseAbortController&&(this.sseAbortController.abort(),this.sseAbortController=void 0),this.flushSSEEvents().catch((e=>this.logger.warn("Failed to flush remaining SSE events",{error:(0,h.getErrorMessage)(e)})))}async connectToOpenCodeSSE(){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
300
333
|
if(!c.default.existsSync(e))return this.logger.debug("SSE forwarder: opencode.json not found, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
|
|
301
334
|
let t
|
|
302
335
|
try{t=JSON.parse(c.default.readFileSync(e,"utf-8")).port}catch{return void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)}const s=`http://localhost:${t}/event`
|
|
@@ -310,16 +343,17 @@ o+=r.decode(s,{stream:!0})
|
|
|
310
343
|
const n=o.split("\n")
|
|
311
344
|
o=n.pop()||""
|
|
312
345
|
for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6))
|
|
313
|
-
this.sseEventBuffer.push({type:t.type||"unknown",data:t}),this.handleSSEHookEvent(t).catch((()
|
|
346
|
+
this.sseEventBuffer.push({type:t.type||"unknown",data:t}),this.handleSSEHookEvent(t).catch((e=>this.logger.warn("SSE hook event processing failed",{type:t?.type,error:(0,h.getErrorMessage)(e)})))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,h.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,h.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
|
|
314
347
|
if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
|
|
315
348
|
if(!t)return
|
|
316
|
-
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
|
|
317
|
-
if(!
|
|
318
|
-
|
|
349
|
+
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
|
+
if(!s)return
|
|
351
|
+
const r=t?.metadata
|
|
352
|
+
r?.agentType&&this.projectId&&(this.logger.debug("SSE: session.created with alchemy metadata, ensuring registration",{sessionId:s,agentType:r.agentType}),this.apiClient.post(`/api/projects/${this.projectId}/cli-sessions`,{externalSessionId:s,providerId:"opencode",agentType:r.agentType,status:"active",specialization:r.specialization||void 0}).catch((e=>{this.logger.debug("SSE session registration (idempotent)",{sessionId:s,error:(0,h.getErrorMessage)(e)})}))),setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:s}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:s,variables:{sessionId:s}})}),500)}}async flushSSEEvents(){if(0===this.sseEventBuffer.length||!this.projectId)return
|
|
319
353
|
if(!this.hasRemoteEventConsumers)return void(this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500))
|
|
320
354
|
const e=this.sseEventBuffer.splice(0)
|
|
321
|
-
try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e})}catch(t){this.logger.debug("SSE event flush failed",{error:(0,
|
|
322
|
-
this.logger.debug("Long-poll error, retrying in 5s",{error:(0,
|
|
355
|
+
try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e})}catch(t){this.logger.debug("SSE event flush failed",{error:(0,h.getErrorMessage)(t),count:e.length}),this.sseEventBuffer.unshift(...e),this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500)}}startPolling(){this.projectId&&(this.isPolling=!0,this.pollAbortController=new AbortController,this.logger.info("Starting action long-polling",{projectId:this.projectId}),this.longPollLoop())}async longPollLoop(){for(;this.isPolling;)try{await this.pollOnce()}catch(e){if(!this.isPolling)return
|
|
356
|
+
this.logger.debug("Long-poll error, retrying in 5s",{error:(0,h.getErrorMessage)(e)}),await new Promise((e=>setTimeout(e,5e3)))}}async pollOnce(){if(this.isPolling&&this.projectId)try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=30000`,t={"Content-Type":"application/json",Accept:"application/json"}
|
|
323
357
|
this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
|
|
324
358
|
const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
|
|
325
359
|
t["X-Service-Secret"]=s
|
|
@@ -329,48 +363,48 @@ const o=await r.json(),n={success:!0,data:o.data||o}
|
|
|
329
363
|
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
|
|
330
364
|
if(void 0!==e&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e)),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:t})
|
|
331
365
|
const e=new p.ProjectConfigService(this.logger)
|
|
332
|
-
await e.
|
|
366
|
+
await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}if(n.success&&n.data?.actions?.length>0){const e=[],t=[]
|
|
333
367
|
for(const s of n.data.actions)"opencode-proxy"===s.type||"run-tests"===s.type?e.push(s):t.push(s)
|
|
334
|
-
for(const t of e)this.processAction(t).catch((e=>this.logger.
|
|
368
|
+
for(const t of e)this.processAction(t).catch((e=>this.logger.warn("Proxy action failed",{actionId:t.id,error:(0,h.getErrorMessage)(e)})))
|
|
335
369
|
if(t.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
|
|
336
370
|
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)
|
|
337
371
|
if(e.success&&e.data?.metadata){const{autoJournal:t,defaultModel:s}=e.data.metadata
|
|
338
372
|
if(void 0!==s&&(this.cachedDefaultModel=s,this.hookExecutor.setDefaultModel(s)),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed, syncing to local config",{oldValue:this.cachedAutoJournal,newValue:t})
|
|
339
373
|
const e=new p.ProjectConfigService(this.logger)
|
|
340
|
-
await e.
|
|
374
|
+
await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}}catch(e){this.logger.debug("Settings sync failed",{error:(0,h.getErrorMessage)(e)})}}async processAction(e){this.logger.info("Processing action",{actionId:e.id,type:e.type})
|
|
341
375
|
const t=Date.now()
|
|
342
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)
|
|
343
|
-
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,
|
|
377
|
+
await this.apiClient.post(`/api/actions/${e.id}/result`,{success:r.success,result:r.data,error:r.message||r.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:r.success,duration:Date.now()-t})}catch(s){const r=(0,h.getErrorMessage)(s)
|
|
344
378
|
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
|
|
345
379
|
if(!s)return void t.status(400).json({success:!1,error:"action is required"})
|
|
346
380
|
if(["list-notifications","get-notification","respond-to-notification"].includes(s)){const e=await this.handleNotificationCommand(s,r||{})
|
|
347
381
|
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||{})
|
|
348
382
|
return void t.json(e)}if(["list-requests","get-request"].includes(s)){const e=await this.handleRequestCommand(s,r||{})
|
|
349
383
|
return void t.json(e)}const n=await this.cliBridge.sendCommand(s,r||{},o)
|
|
350
|
-
t.json(n)}catch(e){t.status(500).json({success:!1,error:(0,
|
|
384
|
+
t.json(n)}catch(e){t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleNotificationCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
|
|
351
385
|
const s=this.projectId
|
|
352
386
|
try{switch(e){case"list-notifications":{const e={limit:t.limit||30}
|
|
353
387
|
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"}
|
|
354
388
|
case"respond-to-notification":{if(!t.messageCode)return{success:!1,error:"messageCode is required"}
|
|
355
389
|
const e=t.messageCode
|
|
356
|
-
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,
|
|
390
|
+
return{success:!0,data:(await this.apiClient.post(`/api/projects/${s}/session-messages/${encodeURIComponent(e)}/answer`,{respondedBySessionId:t.respondedBySessionId||"cli-bridge",text:t.responseText,selectedOptions:t.selectedOptions,notes:t.notes})).data}}default:return{success:!1,error:"Unknown notification action: "+e}}}catch(e){return{success:!1,error:(0,h.getErrorMessage)(e)}}}async handleRequestCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
|
|
357
391
|
const s=this.projectId
|
|
358
392
|
try{switch(e){case"list-requests":{const e={}
|
|
359
393
|
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"}
|
|
360
394
|
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})])
|
|
361
|
-
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,
|
|
395
|
+
return{success:!0,data:{...r.data,tasks:o.data||[]}}}default:return{success:!1,error:"Unknown request action: "+e}}}catch(e){return{success:!1,error:(0,h.getErrorMessage)(e)}}}handleCLIBridgeStatus(e,t){t.json({uiConnected:this.cliBridge.isUIConnected()})}async start(){this.logger.info("Starting AlchemyActionServer",{projectPath:this.options.projectPath,port:this.port})
|
|
362
396
|
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)}}))
|
|
363
397
|
break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
|
|
364
|
-
continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){this.logger.warn("Remote registration failed, continuing in local-only mode",{error:(0,
|
|
398
|
+
continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){this.logger.warn("Remote registration failed, continuing in local-only mode",{error:(0,h.getErrorMessage)(e)})}try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=5000`,t={"Content-Type":"application/json",Accept:"application/json"}
|
|
365
399
|
this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
|
|
366
400
|
const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
|
|
367
401
|
t["X-Service-Secret"]=s
|
|
368
402
|
const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
|
|
369
403
|
if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
|
|
370
|
-
void 0!==t?.defaultModel&&(this.cachedDefaultModel=t.defaultModel,this.hookExecutor.setDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,
|
|
404
|
+
void 0!==t?.defaultModel&&(this.cachedDefaultModel=t.defaultModel,this.hookExecutor.setDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,h.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,h.getErrorMessage)(t)})}}this.trackedChildPids.clear()
|
|
371
405
|
try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
372
406
|
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
|
|
373
|
-
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,
|
|
407
|
+
if(s.pid)try{process.kill(-s.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:s.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(s.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:s.pid,error:(0,h.getErrorMessage)(e)})}}}}catch{}}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new m.StuckSessionWatchdog({getOpenCodeUrl:()=>this.getOpenCodeUrl(),logger:this.logger,onStuckDetected:e=>this.handleStuckSessionDetected(e),onStuckResolved:e=>this.handleStuckSessionResolved(e)}),this.stuckSessionWatchdog.start()}handleStuckSessionDetected(e){this.sseEventBuffer.push({type:"session.stuck",data:{type:"session.stuck",properties:{sessionId:e.sessionId,messageId:e.messageId,detectedAt:e.detectedAt,sessionTitle:e.sessionTitle,retryCount:e.retryCount}}}),this.logger.warn("Stuck session detected",{sessionId:e.sessionId,title:e.sessionTitle})}handleStuckSessionResolved(e){this.sseEventBuffer.push({type:"session.unstuck",data:{type:"session.unstuck",properties:{sessionId:e}}})}handleGetStuckSessions(e,t){const s=this.stuckSessionWatchdog?.getStuckSessions()??[]
|
|
374
408
|
t.json({success:!0,data:s})}handleTestInjectStuckSession(e,t){if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
|
|
375
409
|
const{sessionId:s,title:r}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(s,r)
|
|
376
410
|
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
|
|
@@ -378,12 +412,12 @@ if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"
|
|
|
378
412
|
const r=await this.stuckSessionWatchdog.retrySession(s)
|
|
379
413
|
r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}async stop(){this.logger.info("Stopping AlchemyActionServer...")
|
|
380
414
|
for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}if(this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
|
|
381
|
-
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=
|
|
415
|
+
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=w,w.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
|
|
382
416
|
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)
|
|
383
417
|
if((0,s.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
|
|
384
418
|
const d=(0,o.simpleGit)(e);(0,s.mkdirSync)(c,{recursive:!0})
|
|
385
|
-
try{await d.raw(["worktree","prune"])}catch{}const u=await d.branch(),
|
|
386
|
-
try{if(
|
|
419
|
+
try{await d.raw(["worktree","prune"])}catch{}const u=await d.branch(),g=u.all.includes(a)
|
|
420
|
+
try{if(g)await d.raw(["worktree","add",l,a]),i.info("Pre-spawn: created worktree for existing branch",{requestId:t,branchName:a,worktreePath:l})
|
|
387
421
|
else{const e=u.all.includes("main")?"main":"master"
|
|
388
422
|
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+""
|
|
389
423
|
i.warn("Pre-spawn: failed to create worktree (MCP process will retry)",{requestId:t,error:s})}}
|
|
@@ -393,19 +427,19 @@ if(!s())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.log
|
|
|
393
427
|
if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
|
|
394
428
|
const s=e||process.cwd()
|
|
395
429
|
t.logFilePath=i.default.join(s,"logs","debug.log")}const r=process.env.DEBUG_LEVEL?.toLowerCase()
|
|
396
|
-
return r&&Object.values(c.LogLevel).includes(r)&&(t.level=r),t}(e.baseDir),...e},{level:o=c.LogLevel.INFO,serviceName:
|
|
430
|
+
return r&&Object.values(c.LogLevel).includes(r)&&(t.level=r),t}(e.baseDir),...e},{level:o=c.LogLevel.INFO,serviceName:h="mcp-service",logToFile:p=!1,logFilePath:f="./logs/app.log",console=!0}=t,m=[]
|
|
397
431
|
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)
|
|
398
432
|
a.default.existsSync(t)||a.default.mkdirSync(t,{recursive:!0})}(f)
|
|
399
433
|
const e=o===c.LogLevel.DEBUG&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()
|
|
400
|
-
m.push(new n.default.transports.File({filename:f,format:e?
|
|
401
|
-
return o===c.LogLevel.DEBUG&&p&&s()&&y.info("Enhanced logging initialized",{level:o,serviceName:
|
|
402
|
-
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:`${
|
|
434
|
+
m.push(new n.default.transports.File({filename:f,format:e?g:u}))}const y=n.default.createLogger({level:o,defaultMeta:{service:h},transports:m})
|
|
435
|
+
return o===c.LogLevel.DEBUG&&p&&s()&&y.info("Enhanced logging initialized",{level:o,serviceName:h,logToFile:p,logFilePath:f,debugLogging:process.env.DEBUG_LOGGING}),{error(e,t){y.error(e,t)},warn(e,t){y.warn(e,t)},info(e,t){y.info(e,t)},debug(e,t){y.debug(e,t)},child(e){const s=y.child(e)
|
|
436
|
+
return{error(e,t){s.error(e,t)},warn(e,t){s.warn(e,t)},info(e,t){s.info(e,t)},debug(e,t){s.debug(e,t)},child(e){return r({...t,serviceName:`${h}:${e.component||"nested"}`})}}}}}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
403
437
|
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
|
|
404
438
|
const e=process.env.DEBUG_LEVEL?.toLowerCase()
|
|
405
439
|
return e&&Object.values(c.LogLevel).includes(e)?e:c.LogLevel.INFO}
|
|
406
|
-
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()),
|
|
407
|
-
return`${s} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),
|
|
408
|
-
exports.default=
|
|
440
|
+
const n=o(t(5124)),i=o(t(6928)),a=o(t(9896)),c=t(6540),l=t(7960),d=n.default.format.combine(n.default.format.timestamp(),n.default.format.colorize(),n.default.format.printf((({level:e,message:t,timestamp:s,...r})=>`${s} [${e}]: ${t}${Object.keys(r).length?" "+JSON.stringify(r,null,0):""}`))),u=n.default.format.combine(n.default.format.timestamp(),n.default.format.json()),g=n.default.format.combine(n.default.format.timestamp(),n.default.format.printf((({level:e,message:t,timestamp:s,service:r,component:o,...n})=>{const i=r?`[${r}]`:"",a=o?`[${o}]`:"",c=Object.keys(n).length?" "+JSON.stringify(n,null,2):""
|
|
441
|
+
return`${s} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),h=r({logToFile:!1,console:!0,level:c.LogLevel.INFO})
|
|
442
|
+
exports.default=h},2928(e,exports,t){"use strict"
|
|
409
443
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.StuckSessionWatchdog=void 0
|
|
410
444
|
const s=t(8824)
|
|
411
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"}
|
|
@@ -450,9 +484,9 @@ if(0===d.length)return i
|
|
|
450
484
|
const u={...o}
|
|
451
485
|
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
|
|
452
486
|
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
|
|
453
|
-
s?.externalSessionId&&(u.parentSessionId=s.externalSessionId)}}catch(e){this.logger.warn("Failed to enrich hook variables with session context",{sessionId:s,error:e+""})}const
|
|
454
|
-
if(0===
|
|
455
|
-
for(const e of
|
|
487
|
+
s?.externalSessionId&&(u.parentSessionId=s.externalSessionId)}}catch(e){this.logger.warn("Failed to enrich hook variables with session context",{sessionId:s,error:e+""})}const g=d.filter((e=>(0,r.evaluateCondition)(e.condition,u)))
|
|
488
|
+
if(0===g.length)return i
|
|
489
|
+
for(const e of g){await this.executeHook(e,u,s),i.triggered++,i.hooks.push(e.name)
|
|
456
490
|
const t=(0,r.determineEventToEmit)(e,u)
|
|
457
491
|
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
|
|
458
492
|
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)
|
|
@@ -504,14 +538,14 @@ this.authToken?(a.Authorization="Bearer "+this.authToken,this.logger.debug("Usin
|
|
|
504
538
|
const c=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
|
|
505
539
|
a["X-Service-Secret"]=c
|
|
506
540
|
let l=0
|
|
507
|
-
const d=this.options.retryAttempts,u={method:e,path:n},
|
|
541
|
+
const d=this.options.retryAttempts,u={method:e,path:n},g=this.metrics.startTimer("api_request_duration"),h=e=>g({...u,...e})
|
|
508
542
|
for(;d>l;){l++
|
|
509
543
|
try{this.logger.debug(`Making ${e} request to ${i}`,{attempt:l,maxAttempts:d,hasAuth:!!a.Authorization,hasServiceIdentifier:!!a["X-Client-Type"]})
|
|
510
544
|
const t={method:e,headers:{...a},signal:AbortSignal.timeout(this.options.timeout||1e4)}
|
|
511
545
|
if("GET"!==e&&void 0!==s&&(t.body=JSON.stringify(s)),this.logger.debug("Request details",{method:e,url:i}),t.headers){const e={}
|
|
512
546
|
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)
|
|
513
547
|
this.logger.debug("Request body",{bodyPreview:e.substring(0,200)+(e.length>200?"...":""),bodyLength:e.length})}const r=await fetch(i,t)
|
|
514
|
-
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
|
|
548
|
+
if(!r.ok){if(this.incrementFailureCount(),this.logger.error("API request failed with status "+r.status,{method:e,url:i,status:r.status,statusText:r.statusText,attempt:l}),401===r.status)throw h({success:!1,status:r.status,retries:l-1}),Error("Authentication failed - invalid or expired token")
|
|
515
549
|
if(429===r.status){const e=r.headers.get("Retry-After"),t=e?1e3*parseInt(e):2*(this.options.retryDelay||1e3)
|
|
516
550
|
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)))
|
|
517
551
|
continue}}if(r.status>=500&&d>l){const e=(this.options.retryDelay||1e3)*Math.pow(2,l-1)
|
|
@@ -520,16 +554,16 @@ continue}let t=`API request failed with status ${r.status}: ${r.statusText}`,s={
|
|
|
520
554
|
try{const e=await r.json()
|
|
521
555
|
if(e&&"object"==typeof e&&!1===e.success&&e.error){const r=e.error
|
|
522
556
|
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()
|
|
523
|
-
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+""})}}
|
|
557
|
+
e&&e.trim()&&(t=`${t}. Response: ${e.substring(0,200)}`)}catch(t){this.logger.debug("Failed to parse error response",{jsonError:e instanceof Error?e.message:e+"",textError:t instanceof Error?t.message:t+""})}}h({success:!1,status:r.status,retries:l-1})
|
|
524
558
|
const o=Error(t)
|
|
525
559
|
throw o.status=r.status,o.statusText=r.statusText,o.method=e,o.url=i,o.errorDetails=s,o}this.resetFailureCount()
|
|
526
560
|
const o=await r.json()
|
|
527
561
|
if(null===o||"object"!=typeof o)throw Error("Invalid API response: expected object, got "+typeof o)
|
|
528
562
|
if(o&&"object"==typeof o&&!("success"in o)){this.logger.warn("Non-standard API response detected - wrapping in standard format",{responseKeys:Object.keys(o)})
|
|
529
563
|
const e={success:!0,data:o}
|
|
530
|
-
return
|
|
531
|
-
throw this.logger.error("API returned error response",{errorCode:e.error?.code,errorMessage:e.error?.message,statusCode:e.error?.statusCode}),
|
|
532
|
-
return
|
|
564
|
+
return h({success:!0,status:r.status,retries:l-1,standardFormat:!1}),e}if(o&&"object"==typeof o&&"success"in o&&!1===o.success){const e=o
|
|
565
|
+
throw this.logger.error("API returned error response",{errorCode:e.error?.code,errorMessage:e.error?.message,statusCode:e.error?.statusCode}),h({success:!1,status:r.status,retries:l-1,errorCode:e.error?.code}),Error(`API error: ${e.error?.code}: ${e.error?.message}`)}if(o&&"object"==typeof o&&"success"in o&&!0===o.success&&"data"in o){const t=o
|
|
566
|
+
return h({success:!0,status:r.status,retries:l-1,standardFormat:!0}),this.logger.debug("API request succeeded with status "+r.status,{method:e,url:i,status:r.status,retries:l-1,hasMetadata:"metadata"in t}),t}return this.logger.warn("Invalid API response format: Unexpected structure",{responseType:typeof o,responseKeys:"object"==typeof o?Object.keys(o):[]}),{success:!0,data:o}}catch(t){if(l>=d)throw this.incrementFailureCount(),this.logger.error(`API request failed after ${l} attempts`,{method:e,url:i,error:t.message||t+"",status:t.status}),h({success:!1,error:!0,retries:l-1}),t
|
|
533
567
|
const s=Math.min((this.options.retryDelay||1e3)*Math.pow(2,l-1),5e3)
|
|
534
568
|
this.logger.warn(`API request failed, retrying in ${s}ms`,{method:e,url:i,error:t.message||t+"",attempt:l,maxAttempts:d}),await new Promise((e=>setTimeout(e,s)))}}throw Error("Unexpected end of requestWithRetry")}incrementFailureCount(){this.failureCount++,this.metrics.setGauge("api_failure_count",this.failureCount),"half-open"!==this.circuitState&&(this.failureCount<this.options.circuitBreakerThreshold||"closed"!==this.circuitState)||this.openCircuit()}resetFailureCount(){const e="closed"!==this.circuitState
|
|
535
569
|
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)
|
|
@@ -613,7 +647,35 @@ if(!r||!o)return{success:!1,message:"sessionId and message are required",error:"
|
|
|
613
647
|
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")
|
|
614
648
|
let a
|
|
615
649
|
try{a="http://127.0.0.1:"+JSON.parse(n.readFileSync(i,"utf-8")).port}catch{return{success:!1,message:"Cannot read OpenCode config",error:"no_opencode"}}try{const e=await fetch(`${a}/session/${r}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o}]}),signal:AbortSignal.timeout(1e4)})
|
|
616
|
-
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"}}}}},
|
|
650
|
+
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
|
+
function s(e){const[t,...s]=e.split("/")
|
|
652
|
+
return{provider:t||"anthropic",modelId:s.join("/")||e}}function r(e){const t={anthropic:!1,openai:!1}
|
|
653
|
+
try{const r=(0,a.join)(e,"opencode.json")
|
|
654
|
+
if((0,i.existsSync)(r)){const e=JSON.parse((0,i.readFileSync)(r,"utf8"))
|
|
655
|
+
if(e.provider&&((e.provider.anthropic?.options?.apiKey||process.env.ANTHROPIC_API_KEY)&&(t.anthropic=!0),(e.provider.openai?.options?.apiKey||process.env.OPENAI_API_KEY)&&(t.openai=!0)),e.model){const{provider:r}=s(e.model)
|
|
656
|
+
t[r]=!0}}process.env.ANTHROPIC_API_KEY&&(t.anthropic=!0),process.env.OPENAI_API_KEY&&(t.openai=!0)}catch(e){t.anthropic=!0}return t}function o(e){try{const t=(0,a.join)(e,"opencode.json")
|
|
657
|
+
if((0,i.existsSync)(t))return JSON.parse((0,i.readFileSync)(t,"utf8")).model||null}catch(e){}return null}function n(e,t,r,o){const n=s(e)
|
|
658
|
+
let i=n.provider
|
|
659
|
+
if("inherit"!==t){if(!o[t])return{provider:n.provider,modelId:n.modelId,fullModel:e,resolved:!1,reason:`Preferred provider '${t}' not available, using main session model`}
|
|
660
|
+
i=t}const a=function(e,t){const s=c[e]
|
|
661
|
+
if(!s)return null
|
|
662
|
+
const r=[...s].sort(((e,t)=>t.length-e.length))
|
|
663
|
+
for(const e of r)if(t.includes(e))return e
|
|
664
|
+
return null}(n.provider,n.modelId),l=c[n.provider],d=c[i]
|
|
665
|
+
if(!a||!l||!d)return{provider:n.provider,modelId:n.modelId,fullModel:e,resolved:!1,reason:`Could not determine model family for '${n.modelId}', using main session model`}
|
|
666
|
+
const u=l.indexOf(a)
|
|
667
|
+
let g,h=0
|
|
668
|
+
if("one_down"===r&&(h=1),"two_down"===r&&(h=2),i===n.provider)g=u+h
|
|
669
|
+
else{const e=l.length>1?u/(l.length-1):0
|
|
670
|
+
g=Math.round(e*(d.length-1))+h}g=Math.min(g,d.length-1),g=Math.max(g,0)
|
|
671
|
+
const p=d[g]
|
|
672
|
+
let f
|
|
673
|
+
var m,y
|
|
674
|
+
return i===n.provider?(m=a,y=p,f=n.modelId.replace(m,y)):f=p,{provider:i,modelId:f,fullModel:`${i}/${f}`,resolved:!0}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseModelString=s,exports.getAvailableProviders=r,exports.getMainSessionModel=o,exports.resolveModelTier=n,exports.resolveAgentModel=function(e,t="inherit",i="same"){const a=o(e)
|
|
675
|
+
if(!a)return{provider:"anthropic",modelId:"claude-sonnet-4-5",fullModel:"anthropic/claude-sonnet-4-5",resolved:!1,reason:"Could not determine main session model, using default"}
|
|
676
|
+
if("inherit"===t&&"same"===i){const e=s(a)
|
|
677
|
+
return{provider:e.provider,modelId:e.modelId,fullModel:a,resolved:!0}}return n(a,t,i,r(e))}
|
|
678
|
+
const i=t(9896),a=t(6928),c={anthropic:["opus","sonnet","haiku"]}},5700(e,exports,t){"use strict"
|
|
617
679
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
618
680
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRestartMcpAction=async function(e,t){const{logger:s,projectPath:n}=t,i=e.reason||"Manual restart requested"
|
|
619
681
|
s.info("Executing restart-mcp action",{reason:i})
|
|
@@ -630,31 +692,31 @@ let i=`You are a test runner agent. Your job is to verify that request ${e.local
|
|
|
630
692
|
for(let e=0;e<n.length;e++){const t=n[e]
|
|
631
693
|
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"
|
|
632
694
|
for(const t of e.acceptanceCriteria)i+=`- ${t}\n`
|
|
633
|
-
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:
|
|
695
|
+
i+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return i}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,d){const{requestId:u,testProcessIds:g,parentSessionId:h,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:C}=d
|
|
634
696
|
if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
635
|
-
m.info("Starting run-tests action",{requestId:u,testProcessIds:
|
|
697
|
+
m.info("Starting run-tests action",{requestId:u,testProcessIds:g})
|
|
636
698
|
try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
|
|
637
699
|
if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
|
|
638
|
-
const p=d.data,
|
|
700
|
+
const p=d.data,I=p.branchName||"requests/"+u
|
|
639
701
|
let S
|
|
640
|
-
if(
|
|
702
|
+
if(g?.length)S=(await Promise.all(g.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
|
|
641
703
|
else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
|
|
642
704
|
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"}
|
|
643
|
-
const
|
|
705
|
+
const k=(0,o.join)(w,c,l),b=(0,o.join)(k,u),E=(0,n.simpleGit)(w)
|
|
644
706
|
if((0,r.existsSync)(b))try{const e=(0,n.simpleGit)(b)
|
|
645
|
-
await e.raw(["reset","--hard",
|
|
646
|
-
try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(
|
|
647
|
-
try{if(!(await E.branch()).all.includes(
|
|
648
|
-
await E.raw(["worktree","add","--detach",b,
|
|
707
|
+
await e.raw(["reset","--hard",I]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:b,branchName:I})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
|
|
708
|
+
try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(k,{recursive:!0})
|
|
709
|
+
try{if(!(await E.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
|
|
710
|
+
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)}
|
|
649
711
|
m.info("Test worktree already exists, using as-is",{worktreePath:b})}}const{execSync:j}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
650
712
|
for(const e of S){const t=e.config?.setup||[]
|
|
651
713
|
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
|
|
652
714
|
for(const s of t)try{m.info("Setup command",{command:s,worktreePath:b}),j(s,{cwd:b,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:s,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const T=[],P=[]
|
|
653
715
|
for(const process of S){let t
|
|
654
716
|
const r=e.testRunIds?.[T.length]
|
|
655
|
-
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:
|
|
717
|
+
try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:b}),t=(await y.get(`/api/projects/${v}/test-runs/${r}`)).data,T.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:b,parentSessionId:h})).data,T.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:r,error:(0,i.getErrorMessage)(e)})
|
|
656
718
|
continue}const o=s(p,process,0,v,f),n=f?"tester":"auto-tester"
|
|
657
|
-
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:
|
|
719
|
+
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:b,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:h,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
|
|
658
720
|
if(!e.success)throw Error(e.error||"Spawn failed")
|
|
659
721
|
P.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:b,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
|
|
660
722
|
try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===P.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${P.length} test run(s) for request ${u}`,data:{testRunIds:T,agentsSpawned:P.length,processCount:S.length,worktreePath:b}}}catch(e){const t=(0,i.getErrorMessage)(e)
|
|
@@ -675,9 +737,9 @@ const e=await async function(e,t){try{const r=(0,s.simpleGit)(t),o=(await r.raw(
|
|
|
675
737
|
for(const e of o){const[t,...s]=e.split(" "),r=s.join(" ")
|
|
676
738
|
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)
|
|
677
739
|
if(!e.found||!e.commitHash)return{success:!1,message:"Could not find merge commit for request "+o,error:"Merge commit not found"}
|
|
678
|
-
d=e.commitHash}const u=(0,s.simpleGit)(c),
|
|
740
|
+
d=e.commitHash}const u=(0,s.simpleGit)(c),g=await u.status(),h=(await u.branch()).all.includes("main")?"main":"master"
|
|
679
741
|
let p
|
|
680
|
-
|
|
742
|
+
g.current!==h&&(i.info("Switching to default branch",{from:g.current,to:h}),await u.checkout(h))
|
|
681
743
|
try{i.info("Executing git revert",{mergeCommitHash:d.substring(0,8)}),await u.raw(["revert","-m","1","--no-edit",d])
|
|
682
744
|
const e=await u.log({maxCount:1})
|
|
683
745
|
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)
|
|
@@ -736,12 +798,12 @@ const t=e.data
|
|
|
736
798
|
if("pending_approval"!==t.status)return{success:!1,message:`Cannot approve request with status '${t.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+t.status}
|
|
737
799
|
if(!(await i.patch(`/api/projects/${a}/requests/${o}`,{status:"open"})).success)return{success:!1,message:"Failed to update request status",error:"Update failed"}
|
|
738
800
|
const d=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",u=`Work on request ${o}: ${t.title}\n\nDescription: ${t.description}\n\n${d?"Acceptance Criteria:\n- "+d:""}\n\nThis request has just been approved. Please begin implementation.`
|
|
739
|
-
let
|
|
740
|
-
if(t.cliOrigin?.sessionId)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:t.cliOrigin.sessionId,status:"success",summary:`Request ${o} "${t.title}" has been approved. Please begin implementation.`,details:u}),
|
|
801
|
+
let g=!1,h=!1
|
|
802
|
+
if(t.cliOrigin?.sessionId)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:t.cliOrigin.sessionId,status:"success",summary:`Request ${o} "${t.title}" has been approved. Please begin implementation.`,details:u}),g=!0,n.info("Stored session report for creating session",{requestId:o,sessionId:t.cliOrigin.sessionId})
|
|
741
803
|
try{const e=(0,r.getOpenCodeServerUrl)(c,n)
|
|
742
804
|
if(e){const s=`\u2705 [Request Approved]\n\nRequest ${o} "${t.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${u}`,r=`${e}/session/${t.cliOrigin.sessionId}/prompt_async`,i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:s}]})})
|
|
743
|
-
i.ok?n.info("Pushed notification to CLI session",{sessionId:t.cliOrigin.sessionId}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,s.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify creating session, spawning work agent instead",{requestId:o,error:(0,s.getErrorMessage)(e)})}if(!
|
|
744
|
-
|
|
805
|
+
i.ok?n.info("Pushed notification to CLI session",{sessionId:t.cliOrigin.sessionId}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,s.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify creating session, spawning work agent instead",{requestId:o,error:(0,s.getErrorMessage)(e)})}if(!g)try{const e=await(0,r.spawnActionAgent)({agent:"work",prompt:u,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${t.title}`,requestId:o,apiClient:i,logger:n,trackChildPid:l})
|
|
806
|
+
h=e.success,h?n.info("Spawned work agent for request",{requestId:o,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:o,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:o,error:(0,s.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:o,notifiedSession:g,agentSpawned:h}),{success:!0,message:`Successfully approved request ${o}: ${t.title}${g?" \u2014 creating session notified":h?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:g,agentSpawned:h}}}catch(e){const t=(0,s.getErrorMessage)(e)
|
|
745
807
|
return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
|
|
746
808
|
const s=t(8824),r=t(8148)},7960(e,exports){"use strict"
|
|
747
809
|
function t(){const e=process.argv[1]||""
|
|
@@ -749,34 +811,25 @@ if(e.includes("/dist-dev/")||e.includes("\\dist-dev\\"))return!0
|
|
|
749
811
|
if(e.includes("/dist/")||e.includes("\\dist\\"))return!1
|
|
750
812
|
const t=process.cwd()
|
|
751
813
|
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"
|
|
752
|
-
function s(e,t,s){try{const r=`[${(new Date).toISOString()}] [detached-subagent] ${t}${s?" "+JSON.stringify(s):""}\n`,o=(0,
|
|
753
|
-
try{const
|
|
754
|
-
|
|
755
|
-
const
|
|
814
|
+
function s(e,t,s){try{const r=`[${(new Date).toISOString()}] [detached-subagent] ${t}${s?" "+JSON.stringify(s):""}\n`,o=(0,i.join)(e||process.cwd(),".alchemy_lab","spawn-debug.log");(0,n.appendFileSync)(o,r)}catch{}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=function(e){const{agent:t,prompt:c,cwd:l,sessionId:d,model:u,initialBranch:g,serverUrl:h,onSpawn:p}=e
|
|
815
|
+
try{const o={...process.env},f=l||process.cwd()
|
|
816
|
+
o.OPENCODE_DATA_DIR=(0,i.join)(f,".alchemy_lab","opencode"),g&&(o.ALCHEMY_INITIAL_BRANCH=g),e.allowParallel&&(o.ALCHEMY_ALLOW_PARALLEL="1")
|
|
817
|
+
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)
|
|
756
818
|
let w
|
|
757
|
-
if(
|
|
758
|
-
e>0?w.splice(e,0,"--model",
|
|
759
|
-
const C=(0,
|
|
819
|
+
if(o.ALCHEMY_SESSION_TAG=`mcp-${y}-${v}`,s(l,"Spawn env setup",{agent:t,initialBranch:g||"none",sessionId:d||"new",attachMode:!!h}),w=h&&d&&c?["run","--attach",h,"--session",d,"--agent",t,c]:d&&c?["run","--session",d,"--agent",t,c]:c?["run","--agent",t,c]:["run","--agent",t],u){const e=w.findIndex((e=>e===c))
|
|
820
|
+
e>0?w.splice(e,0,"--model",u):w.push("--model",u)}s(l,"Spawning opencode",{args:w.map((e=>e===c?`[prompt:${e.length}chars]`:e)),model:u||"not specified",promptLength:c?.length,promptPreview:c?.slice(0,100),cwd:l,hasSessionId:!!d})
|
|
821
|
+
const C=(0,i.join)(l||process.cwd(),".alchemy_lab"),I=(0,i.join)(C,"spawn-stderr.log")
|
|
760
822
|
let S
|
|
761
|
-
try{S=(0,
|
|
762
|
-
return b.unref(),
|
|
763
|
-
|
|
764
|
-
const o=t(5317),n=t(8824),i=t(9896),a=t(6928),c=t(9068)},8148(e,exports,t){"use strict"
|
|
823
|
+
try{S=(0,n.openSync)(I,"a")}catch{}const k=(0,a.resolveRunCommand)(),b=(0,r.spawn)(k.command,[...k.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:o})
|
|
824
|
+
return b.unref(),p&&p(b.pid),{success:!0,pid:b.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
|
|
825
|
+
const r=t(5317),o=t(8824),n=t(9896),i=t(6928),a=t(9068)},8148(e,exports,t){"use strict"
|
|
765
826
|
function s(e,t){try{const t=(0,o.join)(e,c,"opencode.json"),s=(0,r.readFileSync)(t,"utf8"),n=JSON.parse(s)
|
|
766
|
-
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,
|
|
767
|
-
let y,v=null
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const u=d.id
|
|
773
|
-
c.info("Pre-created agent session",{sessionId:u,title:t,agent:s})
|
|
774
|
-
try{await i.post(`/api/projects/${n}/cli-sessions`,{externalSessionId:u,providerId:"opencode",...o?{parentExternalSessionId:o}:{},agentType:s,status:"active"}),c.info("Registered session in alchemy DB",{sessionId:u,agent:s,parentSessionId:o})}catch(e){c.warn("Failed to register session in DB",{error:(0,a.getErrorMessage)(e)})}if(r&&n){try{await i.post(`/api/projects/${n}/session-request-associations`,{opencodeSessionId:u,requestLocalId:r}),c.info("Created session-request association",{sessionId:u,requestId:r})}catch(e){c.warn("Failed to create session-request association",{error:(0,a.getErrorMessage)(e)})}if("coder"===s)try{await i.patch(`/api/projects/${n}/requests/${r}`,{cliOrigin:{providerId:"opencode",sessionId:u,createdAt:(new Date).toISOString()}}),c.info("Updated request cliOrigin to coder session",{sessionId:u,requestId:r})}catch(e){c.warn("Failed to update request cliOrigin",{error:(0,a.getErrorMessage)(e)})}}return u}catch(e){return c.warn("Failed to pre-create session",{error:(0,a.getErrorMessage)(e)}),null}}(m,l,t,d,u,e.projectId,g,p):p.warn("No OpenCode server URL \u2014 agent session will not be visible in session list")
|
|
775
|
-
try{const e=await fetch("http://localhost:3456/api/project-settings")
|
|
776
|
-
if(e.ok){const s=await e.json()
|
|
777
|
-
s.data?.defaultModel&&(y=s.data.defaultModel,p.info("Using user default model for agent spawn",{agent:t,model:y}))}}catch{}if(d)try{await(0,i.ensureRequestWorktree)(c,d,p)}catch(e){p.warn("Pre-spawn worktree creation failed (non-fatal)",{requestId:d,error:(0,a.getErrorMessage)(e)})}const w=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:v||void 0,serverUrl:m||void 0,model:y,allowParallel:h??!1,onSpawn(e){e&&f&&f(e)}})
|
|
778
|
-
return w.success?(p.info("Spawned action agent",{agent:t,sessionId:v,pid:w.pid,requestId:d}),{success:!0,sessionId:v,pid:w.pid}):{success:!1,sessionId:v,error:w.error||"Spawn failed"}}
|
|
779
|
-
const r=t(9896),o=t(6928),n=t(8035),i=t(2424),a=t(8824),c=".alchemy_lab"},8524(e,exports,t){"use strict"
|
|
827
|
+
if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,i.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=s,exports.spawnActionAgent=async function(e){const{agent:t,prompt:r,cwd:o,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:g,apiClient:h,logger:p,trackChildPid:f}=e
|
|
828
|
+
let m,y,v,w=null
|
|
829
|
+
try{const s=new a.SessionSpawnService({apiClient:h,logger:p,projectId:e.projectId,getDefaultModel:e.getDefaultModel||(()=>{})}),o=await s.prepareSession({agent:t,prompt:r,projectPath:c,requestId:d,parentSessionId:u,title:l,allowParallel:g})
|
|
830
|
+
w=o.sessionId,m=o.model,y=o.serverUrl,v=o.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,i.getErrorMessage)(e)}),y=s(c,p)||void 0}const C=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:w||void 0,serverUrl:y,model:m,initialBranch:v,allowParallel:g??!1,onSpawn(e){e&&f&&f(e)}})
|
|
831
|
+
return C.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:C.pid,requestId:d}),{success:!0,sessionId:w,pid:C.pid}):{success:!1,sessionId:w,error:C.error||"Spawn failed"}}
|
|
832
|
+
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"
|
|
780
833
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
781
834
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
|
|
782
835
|
const r=t(5086),o=s(t(6982))
|
|
@@ -796,7 +849,14 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookCache=void 0,
|
|
|
796
849
|
if(!s){const e=this.cache.get(r)
|
|
797
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})))
|
|
798
851
|
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"
|
|
799
|
-
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}},
|
|
852
|
+
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
|
+
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)
|
|
854
|
+
t.info("Fetching available agents from API")
|
|
855
|
+
const s=await e.proxyGet("/api/agents/templates")
|
|
856
|
+
s&&Array.isArray(s)&&s.length>0?(n=s.map((e=>({id:e.id||e.name,name:e.name,description:e.description,mode:e.mode,builtIn:e.builtIn}))),t.info("Agent registry initialized",{agentCount:n.length})):(t.warn("No agents returned from API, using defaults"),n=r),i=!0}catch(e){t.warn("Failed to fetch agents from API, using defaults",{error:e instanceof Error?e.message:e+""}),n=r,i=!0}})(),a)},exports.getAvailableAgents=t,exports.getAgentNames=s,exports.isValidAgent=function(e){return s().includes(e)},exports.getDefaultAgent=function(){return"work"},exports.formatAgentsForDescription=function(){return t().map((e=>`'${e.name}'${e.description?" - "+e.description:""}`)).join(", ")},exports.isAgentRegistryInitialized=function(){return i},exports.getBuiltInAgentConfig=function(e){const t=r.find((t=>t.name===e))
|
|
857
|
+
return t?{preferredProvider:t.preferredProvider||"inherit",preferredModelTier:t.preferredModelTier||"same",requestContextMode:t.requestContextMode}:null},exports.getAllowedSubagents=function(e){return o[e]||null},exports.resetAgentRegistry=function(){n=null,i=!1,a=null}
|
|
858
|
+
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
|
+
let n=null,i=!1,a=null},8984(e,exports,t){"use strict"
|
|
800
860
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.MCPClientConfigurer=void 0
|
|
801
861
|
const s=t(8824)
|
|
802
862
|
class r{constructor(e,t,s){this.logger=e,this.remoteServerUrl=t||process.env.REMOTE_SERVER_URL,this.authToken=s}async seedAgentsFromRemote(e){const t=this.remoteServerUrl
|
|
@@ -833,20 +893,20 @@ if(e.color&&(i+=`color: "${e.color}"\n`),i+=`tools:\n${n}\n`,e.opencodePermissio
|
|
|
833
893
|
for(const[t,s]of Object.entries(e.opencodePermission))if("object"==typeof s&&null!==s){i+=` ${t}:\n`
|
|
834
894
|
for(const[e,t]of Object.entries(s))i+=` ${e}: ${t}\n`}else i+=` ${t}: ${s}\n`}return void 0!==e.temperature&&(i+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(i+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(i+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),i+="---\n\n",o&&(i+=o.trim()+"\n\n"),e.prompt&&(i+=e.prompt),e.promptAddition&&(i+="\n\n"+e.promptAddition),i}async configureOpenCode(e,o,n="node",i,a){try{const s=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=c.join(e,"mcp-direct.js")
|
|
835
895
|
this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:o})
|
|
836
|
-
const
|
|
837
|
-
let
|
|
896
|
+
const g=c.join(o,"opencode.json")
|
|
897
|
+
let h=null,p=null,f=null,m=null,y=!1,v=!1
|
|
838
898
|
const w=c.join(o,".alchemy_lab","config.json")
|
|
839
899
|
if(!i){try{const e=await s.readFile(w,"utf-8"),t=JSON.parse(e)
|
|
840
|
-
t?.openCodeTools?(
|
|
841
|
-
t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const C=i?.openCodeTools||
|
|
842
|
-
i?.openCodeModel?
|
|
843
|
-
const
|
|
844
|
-
await s.mkdir(
|
|
900
|
+
t?.openCodeTools?(h=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:h})):v=!0,t?.openCodeModel&&(p=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),v=!0}try{const e=await s.readFile(g,"utf-8"),t=JSON.parse(e)
|
|
901
|
+
t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const C=i?.openCodeTools||h||f||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},I={tools:C},S=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
|
|
902
|
+
i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:m&&(I.model=m)
|
|
903
|
+
const k=c.join(o,".opencode","agent")
|
|
904
|
+
await s.mkdir(k,{recursive:!0})
|
|
845
905
|
const b=S||"anthropic/claude-opus-4-5-20251101"
|
|
846
906
|
let E,j=null,T=null
|
|
847
907
|
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})
|
|
848
908
|
for(const e of j){if(!e.enabled)continue
|
|
849
|
-
const t=this.generateAgentMarkdown(e,d,b,E),r=c.join(
|
|
909
|
+
const t=this.generateAgentMarkdown(e,d,b,E),r=c.join(k,e.name+".md")
|
|
850
910
|
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")
|
|
851
911
|
else this.logger.warn("No projectId provided - agents must be configured via database")
|
|
852
912
|
const P={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,o],enabled:!0}}}
|
|
@@ -859,8 +919,8 @@ e.name in s&&(t.bash=!!s[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.
|
|
|
859
919
|
let o=""
|
|
860
920
|
E&&(o+=E.trim()+"\n\n"),e.prompt&&(o+=e.prompt),e.promptAddition&&(o+="\n\n"+e.promptAddition),P.agent[e.name]={tools:t,...o?{prompt:o}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}P.agent.general={...P.agent.general,disable:!0},P.agent.explore={...P.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
|
|
861
921
|
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`
|
|
862
|
-
await s.writeFile(c.join(
|
|
863
|
-
A.push("file://"+c.join(o,".opencode","plugin","inject-session.js")),A.push("file://"+c.join(o,".opencode","plugin","event-hooks.js")),A.push("file://"+c.join(o,".opencode","plugin","system-logger.js")),A.length>0&&(P.plugin=A),await s.writeFile(
|
|
922
|
+
await s.writeFile(c.join(k,"explore.md"),e,"utf8"),this.logger.info("Created explore agent override with research prompt"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(P.agent).length})}const A=[]
|
|
923
|
+
A.push("file://"+c.join(o,".opencode","plugin","inject-session.js")),A.push("file://"+c.join(o,".opencode","plugin","event-hooks.js")),A.push("file://"+c.join(o,".opencode","plugin","system-logger.js")),A.length>0&&(P.plugin=A),await s.writeFile(g,JSON.stringify(P,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:g,plugins:A})
|
|
864
924
|
try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),r=c.join(e.homedir(),".alchemy_lab","opencode","config")
|
|
865
925
|
await s.mkdir(r,{recursive:!0})
|
|
866
926
|
const o=c.join(r,"opencode.json")
|
|
@@ -869,7 +929,7 @@ try{n=JSON.parse(await s.readFile(o,"utf-8"))}catch{}const i=(n.plugin||[]).filt
|
|
|
869
929
|
n.plugin=[...i,...A],n.$schema=n.$schema||"https://opencode.ai/config.json",await s.writeFile(o,JSON.stringify(n,null,2),"utf8"),this.logger.info("Updated global alchemy opencode config",{globalConfigPath:o,plugins:n.plugin})}catch(e){this.logger.warn("Failed to write global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(v&&!i)try{let e={}
|
|
870
930
|
try{const t=await s.readFile(w,"utf-8")
|
|
871
931
|
e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=C,await s.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPlugins:s}=await t.e(939).then(t.t.bind(t,9939,23)),r=await s(o,l??!1,this.logger,e)
|
|
872
|
-
r.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:r.synced}),r.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:r.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:
|
|
932
|
+
r.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:r.synced}),r.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:r.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:g}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,s.getErrorMessage)(e)}),{success:!1,error:(0,s.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=r,r.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9067(e,exports,t){"use strict"
|
|
873
933
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
874
934
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
|
|
875
935
|
const r=s(t(6982)),o=t(8824)
|
|
@@ -920,15 +980,15 @@ e&&(this.pollingStatus=n.AUTHENTICATED,this.tokenResult=e,this.stopPolling(),s&&
|
|
|
920
980
|
function s(){const e=process.env.ALCHEMY_OPENCODE_DIR
|
|
921
981
|
if(e)return e
|
|
922
982
|
try{const e=("undefined"!=typeof require?require:t(4245)).resolve("alchemylab-opencode/package.json")
|
|
923
|
-
return(0,
|
|
924
|
-
if(parseInt(e.replace("v","").split(".")[0],10)
|
|
925
|
-
if((0,
|
|
983
|
+
return(0,n.dirname)(e)}catch{}}function r(e){return 22===e||e>=24}function o(e,t){return{command:function(){try{const e=(0,a.execFileSync)("node",["--version"],{encoding:"utf8"}).trim()
|
|
984
|
+
if(r(parseInt(e.replace("v","").split(".")[0],10)))return"node"}catch{}const e=process.env.NVM_DIR||(0,n.join)(process.env.HOME||"",".nvm"),t=(0,n.join)(e,"versions","node")
|
|
985
|
+
if((0,i.existsSync)(t))try{const e=(0,i.readdirSync)(t).filter((e=>r(parseInt(e.replace("v","").split(".")[0],10)))).sort(((e,t)=>{const s=parseInt(e.replace("v","").split(".")[0],10)
|
|
926
986
|
return parseInt(t.replace("v","").split(".")[0],10)-s}))
|
|
927
|
-
if(e.length>0){const s=(0,
|
|
928
|
-
if((0,
|
|
929
|
-
return e?
|
|
930
|
-
return e?
|
|
931
|
-
const
|
|
987
|
+
if(e.length>0){const s=(0,n.join)(t,e[0],"bin","node")
|
|
988
|
+
if((0,i.existsSync)(s))return s}}catch{}throw Error(`AlchemyLab requires Node.js 22 or 24+ to run the OpenCode server (Node 23 is not supported).\nYour current version: ${process.version}\nInstall a compatible version: nvm install 22 (or download from https://nodejs.org)`)}(),prependArgs:["--no-warnings",(0,n.join)(e,t)]}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.resolveServeCommand=function(){const e=s()
|
|
989
|
+
return e?o((0,n.join)(e,"dist"),"serve.js"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=s()
|
|
990
|
+
return e?o((0,n.join)(e,"dist"),"run.js"):{command:"opencode",prependArgs:[]}}
|
|
991
|
+
const n=t(6928),i=t(9896),a=t(5317)},9266(e,exports,t){"use strict"
|
|
932
992
|
var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
933
993
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleOpenCodeProxyAction=async function(e,t){const{method:s,path:i,body:a,headers:c}=e
|
|
934
994
|
if(!s||!i)return{success:!1,message:"method and path are required in proxy payload"}
|
|
@@ -968,23 +1028,23 @@ for(var i in s)e.o(s,i)&&(e.m[i]=s[i])
|
|
|
968
1028
|
n&&n(e)
|
|
969
1029
|
for(var a=0;a<o.length;a++)r[o[a]]=1})(require("./"+e.u(t)))}
|
|
970
1030
|
var i={};(()=>{"use strict"
|
|
971
|
-
function t(){const e=[(0,
|
|
1031
|
+
function t(){const e=[(0,g.join)(__dirname,"..","package.json"),(0,g.join)(__dirname,"..","..","package.json")]
|
|
972
1032
|
for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
|
|
973
1033
|
if(e.version)return e.version}catch{}return"unknown"}function s(){const e=t()
|
|
974
|
-
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,
|
|
975
|
-
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,
|
|
1034
|
+
console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start action server + launch OpenCode (default)\n serve Start the action server only (for GUI-triggered actions)\n logout Log out and remove stored auth token\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy serve # Start action server only\n alchemy logout # Log out\nFor more information, visit: https://alchemylabcode.com/\n`)}function r(e){const t=(0,g.join)(e,S.ALCHEMY_DIR_NAME,"project.json")
|
|
1035
|
+
return(0,d.existsSync)(t)}function o(e){const t=(0,p.platform)(),s="darwin"===t?"open":"win32"===t?"cmd":"xdg-open",r="win32"===t?["/c","start","",e]:[e];(0,h.spawn)(s,r,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((s=>{const r=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(r),s(!0))}),2e3)
|
|
976
1036
|
setTimeout((()=>{clearInterval(r),s(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
|
|
977
1037
|
try{const{createConfigManager:s}=await e.e(60).then(e.t.bind(e,9060,23)),r=s(t)
|
|
978
|
-
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,
|
|
1038
|
+
return(await r.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,s,r,o="cli-init"){try{const n=(0,g.basename)(s)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:s,createdVia:o}})})
|
|
979
1039
|
if(i.ok){const e=await i.json()
|
|
980
1040
|
return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)}catch(e){r.info("Project registration failed",{error:(0,C.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
|
|
981
|
-
const l=e(3444),d=e(9896),u=e(1943),
|
|
1041
|
+
const l=e(3444),d=e(9896),u=e(1943),g=e(6928),h=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),C=e(8824),I=e(6800),S=e(4139);(async function(){const i=process.argv.slice(2)
|
|
982
1042
|
let p
|
|
983
|
-
const
|
|
984
|
-
const b=i.filter(((e,t)=>"--port"!==e&&(-1===
|
|
985
|
-
if(0===b.length)return void await async function(s={}){const i=process.cwd(),l=(0,
|
|
1043
|
+
const k=i.indexOf("--port");-1!==k&&i[k+1]&&(p=parseInt(i[k+1],10),isNaN(p)&&(console.error("\u274c Invalid port number"),process.exit(1)))
|
|
1044
|
+
const b=i.filter(((e,t)=>"--port"!==e&&(-1===k||t!==k+1)))
|
|
1045
|
+
if(0===b.length)return void await async function(s={}){const i=process.cwd(),l=(0,g.join)(i,S.ALCHEMY_DIR_NAME)
|
|
986
1046
|
await(0,u.mkdir)(l,{recursive:!0})
|
|
987
|
-
const p=(0,
|
|
1047
|
+
const p=(0,g.join)(l,"server.log"),{createLogger:k}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=k({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),E=await a(b),j=new f.TokenManager(b,i,{remoteServerUrl:E,allowInteractiveAuth:!1})
|
|
988
1048
|
await j.initialize()
|
|
989
1049
|
let T=await j.getToken()
|
|
990
1050
|
if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
|
|
@@ -992,49 +1052,47 @@ const e=await j.startAuthenticationFlow()
|
|
|
992
1052
|
console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(j)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),T=await j.getToken(),console.log(`\u2705 Welcome, ${T?.username||"user"}!`),console.log("")}let P=T?.token
|
|
993
1053
|
await async function(e,t,s,o){if(r(e))return
|
|
994
1054
|
console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
|
|
995
|
-
const n=(0,
|
|
1055
|
+
const n=(0,g.join)(e,S.ALCHEMY_DIR_NAME)
|
|
996
1056
|
let i
|
|
997
|
-
await(0,u.mkdir)(n,{recursive:!0}),s?(console.log(`\u{1f4cb} Registering project "${(0,
|
|
998
|
-
const a=(0,
|
|
999
|
-
const A=(0,
|
|
1057
|
+
await(0,u.mkdir)(n,{recursive:!0}),s?(console.log(`\u{1f4cb} Registering project "${(0,g.basename)(e)||"Local Project"}"...`),i=await c(t,s,e,o),console.log(i?"\u2705 Project registered: "+i:"\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token \u2014 skipping project registration")
|
|
1058
|
+
const a=(0,g.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,g.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,E,P,b)
|
|
1059
|
+
const A=(0,g.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
|
|
1000
1060
|
let M
|
|
1001
1061
|
try{const e=JSON.parse((0,d.readFileSync)(A,"utf-8"))
|
|
1002
1062
|
if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(E,P,i,b,"cli-init-retry")
|
|
1003
|
-
t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(A,JSON.stringify(e,null,2),"utf-8"),M=t,console.log("\u2705 Project registered: "+t))}}catch{b.warn("Could not read project.json")}await async function(t){try{return void(0,
|
|
1004
|
-
try{const e=(0,
|
|
1063
|
+
t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(A,JSON.stringify(e,null,2),"utf-8"),M=t,console.log("\u2705 Project registered: "+t))}}catch{b.warn("Could not read project.json")}await async function(t){try{return void(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
|
|
1064
|
+
try{const e=(0,g.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
|
|
1005
1065
|
if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
|
|
1006
1066
|
t.opencodeVersion&&(s=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
|
|
1007
1067
|
const r="latest"===s?"latest":"v"+s
|
|
1008
|
-
try{(0,
|
|
1009
|
-
try{return(0,
|
|
1068
|
+
try{(0,h.execFileSync)("which",["go"],{stdio:"pipe"}),console.log(" Running: go install github.com/sst/opencode@"+r),(0,h.execFileSync)("go",["install","github.com/sst/opencode@"+r],{stdio:"inherit",timeout:12e4})
|
|
1069
|
+
try{return(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"}),void console.log("\u2705 OpenCode installed successfully via go install")}catch{console.log("\u26a0\ufe0f go install completed but opencode not found in PATH"),console.log(" Ensure $(go env GOPATH)/bin is in your PATH")}}catch{}try{const{platform:t,arch:r}=await Promise.resolve().then(e.t.bind(e,857,23)),o=t(),n=r(),i="darwin"===o?"darwin":"linux"===o?"linux":null,a="arm64"===n?"arm64":"x64"===n?"amd64":null
|
|
1010
1070
|
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}`
|
|
1011
1071
|
console.log(" Downloading from: "+t)
|
|
1012
|
-
const r=(0,
|
|
1072
|
+
const r=(0,g.join)(process.env.HOME||"/usr/local/bin",".local","bin")
|
|
1013
1073
|
await(0,u.mkdir)(r,{recursive:!0})
|
|
1014
|
-
const o=(0,
|
|
1015
|
-
return(0,
|
|
1016
|
-
const
|
|
1017
|
-
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"+
|
|
1018
|
-
const
|
|
1019
|
-
try{await x.start()
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
if(
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
const{InteractiveShell:c}=await e.e(994).then(e.t.bind(e,5994,23)),l=new c({projectPath:i,logger:b,remoteServerUrl:E,authToken:P,projectId:M})
|
|
1033
|
-
r=setInterval((async()=>{try{if(await j.hasValidToken())return
|
|
1074
|
+
const o=(0,g.join)(r,"opencode")
|
|
1075
|
+
return(0,h.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,h.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(r)||(console.log(` \u26a0\ufe0f Add ${r} to your PATH:`),console.log(` export PATH="${r}:$PATH"`),console.log(""),process.env.PATH=`${r}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,C.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
|
|
1076
|
+
const _=t()
|
|
1077
|
+
console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+_,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
|
|
1078
|
+
const O=s.port||await(0,I.findAvailablePort)(3456),x=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:E,port:O,authToken:P})
|
|
1079
|
+
try{const t=(0,v.getInstallContext)(),s=new m.MCPClientConfigurer(b,E,P),r=new w.UnifiedLauncher(b,t,s),[,a,c]=await Promise.all([x.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
|
|
1080
|
+
if(E.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
|
|
1081
|
+
try{const e=await fetch(E+"/health")
|
|
1082
|
+
if(e.ok){const t=await e.json()
|
|
1083
|
+
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return E.replace(/:\d+/,":3000")})(),r.launch({projectPath:i,settings:M?{projectId:M}:void 0}).catch((e=>(b.warn("Failed to launch OpenCode serve",{error:(0,C.getErrorMessage)(e)}),{success:!1,error:(0,C.getErrorMessage)(e)})))])
|
|
1084
|
+
if(console.log(""),console.log(" Action server .... port "+x.getPort()),c.success&&"port"in c&&c.port){"pid"in c&&c.pid&&x.trackChildPid(c.pid),x.setOpenCodePort(c.port),console.log(" OpenCode serve ... port "+c.port)
|
|
1085
|
+
const e=(0,g.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),b.info("Wrote OpenCode config",{path:e,port:c.port})}else c.success?console.log(" OpenCode serve ... started"):console.log(" OpenCode serve ... failed (run manually: opencode serve)")
|
|
1086
|
+
console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
|
|
1087
|
+
let l=!1,u=null
|
|
1088
|
+
const h=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
|
|
1089
|
+
process.on("SIGTERM",h),process.on("SIGINT",h)
|
|
1090
|
+
const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),f=new p({projectPath:i,logger:b,remoteServerUrl:E,authToken:P,projectId:M})
|
|
1091
|
+
u=setInterval((async()=>{try{if(await j.hasValidToken())return
|
|
1034
1092
|
console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
|
|
1035
1093
|
const e=await j.startAuthenticationFlow()
|
|
1036
1094
|
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(j)){const e=await j.getToken()
|
|
1037
|
-
e?.token&&(P=e.token,x.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){b.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await
|
|
1095
|
+
e?.token&&(P=e.token,x.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){b.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await f.start(h)}catch(e){b.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({port:p})
|
|
1038
1096
|
const E=b[0].toLowerCase()
|
|
1039
1097
|
switch("--help"!==E&&"-h"!==E||(s(),process.exit(0)),"--version"!==E&&"-v"!==E||(console.log("alchemy v"+t()),process.exit(0)),E){case"serve":await async function(e={}){const s=process.cwd()
|
|
1040
1098
|
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))
|
|
@@ -1044,10 +1102,10 @@ const i=await a(o),c=new f.TokenManager(o,s,{remoteServerUrl:i,allowInteractiveA
|
|
|
1044
1102
|
await c.initialize()
|
|
1045
1103
|
const d=await c.getToken(),u=d?.token
|
|
1046
1104
|
u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
|
|
1047
|
-
const
|
|
1048
|
-
try{await
|
|
1105
|
+
const g=e.port||await(0,I.findAvailablePort)(3456),h=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:g,authToken:u})
|
|
1106
|
+
try{await h.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${h.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
|
|
1049
1107
|
let e=!1
|
|
1050
|
-
const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await
|
|
1108
|
+
const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await h.stop(),process.exit(0))}
|
|
1051
1109
|
process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({port:p})
|
|
1052
1110
|
break
|
|
1053
1111
|
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})
|