alchemylab 1.0.14 → 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/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,h=(0,s.simpleGit)(a),g=await h.status(),p=await h.branch(),f=p.all.includes("main")?"main":"master"
14
- if(g.current!==f&&await h.checkout(f),!p.all.includes(u))return{success:!1,message:`Branch '${u}' not found. The request may not have any file changes to merge.`,error:"Branch not found"}
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 h.merge([u,"--no-ff","-m",e])
18
- const t=await h.log({maxCount:1})
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 h.merge(["--abort"])}catch(e){n.error("CRITICAL: git merge --abort failed \u2014 repo may be in inconsistent state",{requestId:o,error:e instanceof Error?e.message:e+""})}return{success:!1,message:`Merge conflicts detected for request ${o}. Please resolve conflicts before finalizing.`,error:"Merge conflicts",data:{hasConflicts:!0}}}return{success:!1,message:"Merge failed: "+t,error:t}}try{await h.deleteLocalBranch(u,!0),y=!0}catch{}try{await i.post(`/api/projects/${c}/requests/${o}/finalize-completion`,{summary:"Finalized request "+o,acceptanceEvidence:[{evidence:"Finalized via GUI after review"}],mergeResult:{mergeCommitHash:m||"unknown",cleanedUp:y,method:"action-server-merge"}})}catch(e){const t=(0,r.getErrorMessage)(e)
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),r=t(8824)
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:o}=e,{installPath:n,nodePath:i}=this.installContext
40
- this.logger.info("UnifiedLauncher: Starting launch",{projectPath:t,installPath:n})
41
- try{const r=await this.configurer.configureOpenCode(n,t,i,s,o)
42
- return r.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:r.configPath}),this.launchBackground(e,r)):(this.logger.error("UnifiedLauncher: Configuration failed",{error:r.error}),{success:!1,error:r.error})}catch(e){return this.logger.error("UnifiedLauncher: Launch failed",{error:(0,r.getErrorMessage)(e)}),{success:!1,error:(0,r.getErrorMessage)(e)}}}async launchBackground(e,r){const{projectPath:o,envVars:n}=e
43
- try{(0,s.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:o})
44
- const{resolveServeCommand:i}=await Promise.resolve().then(t.bind(t,9068)),a=i()
45
- return new Promise((e=>{const t=(0,s.spawn)(a.command,[...a.prependArgs,"--port","0"],{stdio:["ignore","pipe","pipe"],cwd:o,detached:!0,env:{...process.env,...n}})
46
- let i="",c=!1
47
- const l=s=>{i+=s
48
- const o=i.match(/listening on http:\/\/[^:]+:(\d+)/)
49
- if(o&&!c){const s=parseInt(o[1],10)
50
- c=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:t.pid,port:s}),t.unref(),e({success:!0,pid:t.pid,port:s,configPath:r.configPath})}}
51
- t.stdout?.on("data",(e=>l(e.toString()))),t.stderr?.on("data",(e=>l(e.toString()))),setTimeout((()=>{c||(c=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:i.slice(0,500)}),t.unref(),e({success:!0,pid:t.pid,configPath:r.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),t.on("error",(t=>{c||(c=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),t.on("exit",(t=>{c||(c=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:t,output:i.slice(0,500)}),e({success:!1,error:"opencode serve exited with code "+t}))}))}))}}},1253(e,exports,t){"use strict"
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)}}},1943(module){"use strict"
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 saveMultiAgentConfig(e,t,s){try{const n=o.default.join(e,i.ALCHEMY_DIR_NAME),a=o.default.join(n,"config.json")
122
- await r.default.mkdir(n,{recursive:!0})
123
- let c={}
124
- try{const e=await r.default.readFile(a,"utf-8")
125
- c=JSON.parse(e)}catch(e){this.logger.debug("No existing config.json, creating new one")}c.multiAgent=!1!==t,void 0!==s&&(c.autoJournal=s),c.lockTimeout||(c.lockTimeout=3600),c.worktreeCleanupDelay||(c.worktreeCleanupDelay=900),c.maxWorktreeAge||(c.maxWorktreeAge=24),c.cleanupInterval||(c.cleanupInterval=15),void 0===c.preserveBranchesOnCleanup&&(c.preserveBranchesOnCleanup=!0),c.maxConcurrentSessions||(c.maxConcurrentSessions=10),c.sessionTimeoutWarning||(c.sessionTimeoutWarning=45),await r.default.writeFile(a,JSON.stringify(c,null,2),"utf-8"),this.logger.info("Saved multiAgent config",{configPath:a,multiAgent:c.multiAgent})}catch(e){this.logger.error("Failed to save multiAgent config",{error:(0,a.getErrorMessage)(e)})}}getToolPreferencesPath(){const e=n.default.homedir()
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),h=t(8524),g=t(8824),p=t(2096),f=t(7210),m=t(2928),y=t(5317)
136
- class v{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
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 h.CLIBridgeWebSocket(e),(0,h.setCLIBridge)(this.cliBridge),this.hookExecutor=new f.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,r.default)()
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,g.getErrorMessage)(e)})}})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:s,sessionId:r,variables:o,timing:n}=e.body
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,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.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")
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,g.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,g.getErrorMessage)(e)})}}async handleSyncAgents(e,s){try{const e=this.readProjectConfig()
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,g.getErrorMessage)(e)})}let d=!1
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,g.getErrorMessage)(e)})}const{resolveServeCommand:o}=await Promise.resolve().then(t.bind(t,9068)),n=o(),i=(0,y.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=""
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,g.getErrorMessage)(e)})}s.json({success:!0,message:`Synced ${l} agent(s) to local files${d?" - OpenCode reloading":""}`,count:l,restarted:d})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
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,y.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
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,g.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleEventHooksProxy(e,t){try{const{eventType:s}=e.params,{sessionId:r}=e.query,o=this.projectId
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,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleHookTriggersProxy(e,t){try{const s=this.projectId
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,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGetHookIteration(e,t){try{const s=this.readProjectConfig()
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,g.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleIncrementHookIteration(e,t){try{const s=this.readProjectConfig()
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,g.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleDirectReport(e,t){try{const s=this.projectId
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,g.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
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,g.getErrorMessage)(e)})}l||(l="Completed request "+(o||"unknown"))
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),h=a.filter((({ancestorId:e})=>!u.has(e)||(this.logger.info("Direct report dedup: already notified",{ancestorId:e,dedupKey:d}),!1))),p=await Promise.allSettled(h.map((async({ancestorId:e,childId:t})=>{const r=await this.apiClient.post(`/api/projects/${s}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),o=r?.data||r
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,g.getErrorMessage)(e.reason)})
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,g.getErrorMessage)(e)})}t.json({success:!0,data:{notifiedSessions:f,report:f[0]?{messageCode:f[0].messageCode}:void 0,parentExternalSessionId:a[0]?.ancestorId||null}})}catch(e){this.logger.error("Direct report creation failed",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGetSessionAgentType(e,t){try{const{sessionId:s}=e.params,r=this.readProjectConfig()
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,g.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:s},5e3)
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,g.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:s}),t.json({success:!0,agentType:null,source:"unknown"})}catch(e){this.logger.debug("Failed to get session agent type",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGetSessionParent(e,t){try{const{sessionId:s}=e.params,r=this.readProjectConfig()
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,g.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{const s=this.getOpenCodeUrl()
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 h
242
- try{h=u.includes("application/json")?await l.json():await l.text()}catch{return void t.status(l.status).end()}t.status(l.status),u&&t.setHeader("Content-Type",u),"string"==typeof h?t.send(h):t.json(h)}catch(s){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,g.getErrorMessage)(s),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,g.getErrorMessage)(s)})}}async handleGetAgentByName(e,t){try{const s=this.readProjectConfig()
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,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<v.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
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,g.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e
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,g.getErrorMessage)(e)})))}async seedDefaultModelFromZen(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
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,g.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
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,g.getErrorMessage)(e)}}}async register(){const e=this.readProjectConfig()
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,g.getErrorMessage)(e)})}}),3e4))}startSSEEventForwarder(){this.logger.info("Starting SSE event forwarder"),this.connectToOpenCodeSSE(),this.sseForwarderInterval=setInterval((async()=>{await this.flushSSEEvents()}),2e3)}stopSSEEventForwarder(){this.sseForwarderInterval&&(clearInterval(this.sseForwarderInterval),this.sseForwarderInterval=void 0),this.sseAbortController&&(this.sseAbortController.abort(),this.sseAbortController=void 0),this.flushSSEEvents().catch((()=>{}))}async connectToOpenCodeSSE(){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
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((()=>{}))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,g.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,g.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
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?.id||e?.info?.id||""
317
- if(!t)return
318
- setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:t}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:t,variables:{sessionId:t}})}),500)}}async flushSSEEvents(){if(0===this.sseEventBuffer.length||!this.projectId)return
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,g.getErrorMessage)(t),count:e.length}),this.sseEventBuffer.unshift(...e),this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500)}}startPolling(){this.projectId&&(this.isPolling=!0,this.pollAbortController=new AbortController,this.logger.info("Starting action long-polling",{projectId:this.projectId}),this.longPollLoop())}async longPollLoop(){for(;this.isPolling;)try{await this.pollOnce()}catch(e){if(!this.isPolling)return
322
- this.logger.debug("Long-poll error, retrying in 5s",{error:(0,g.getErrorMessage)(e)}),await new Promise((e=>setTimeout(e,5e3)))}}async pollOnce(){if(this.isPolling&&this.projectId)try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=30000`,t={"Content-Type":"application/json",Accept:"application/json"}
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.saveMultiAgentConfig(this.options.projectPath,void 0,t),this.cachedAutoJournal=t}}if(n.success&&n.data?.actions?.length>0){const e=[],t=[]
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.debug("Proxy action failed",{actionId:t.id,error:(0,g.getErrorMessage)(e)})))
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.saveMultiAgentConfig(this.options.projectPath,void 0,t),this.cachedAutoJournal=t}}}catch(e){this.logger.debug("Settings sync failed",{error:(0,g.getErrorMessage)(e)})}}async processAction(e){this.logger.info("Processing action",{actionId:e.id,type:e.type})
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,g.getErrorMessage)(s)
377
+ await this.apiClient.post(`/api/actions/${e.id}/result`,{success:r.success,result:r.data,error:r.message||r.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:r.success,duration:Date.now()-t})}catch(s){const r=(0,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,g.getErrorMessage)(e)})}}async handleNotificationCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
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,g.getErrorMessage)(e)}}}async handleRequestCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
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,g.getErrorMessage)(e)}}}handleCLIBridgeStatus(e,t){t.json({uiConnected:this.cliBridge.isUIConnected()})}async start(){this.logger.info("Starting AlchemyActionServer",{projectPath:this.options.projectPath,port:this.port})
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,g.getErrorMessage)(e)})}try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=5000`,t={"Content-Type":"application/json",Accept:"application/json"}
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,g.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,g.getErrorMessage)(t)})}}this.trackedChildPids.clear()
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,g.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()??[]
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=v,v.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
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(),h=u.all.includes(a)
386
- try{if(h)await d.raw(["worktree","add",l,a]),i.info("Pre-spawn: created worktree for existing branch",{requestId:t,branchName:a,worktreePath:l})
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:g="mcp-service",logToFile:p=!1,logFilePath:f="./logs/app.log",console=!0}=t,m=[]
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?h:u}))}const y=n.default.createLogger({level:o,defaultMeta:{service:g},transports:m})
401
- return o===c.LogLevel.DEBUG&&p&&s()&&y.info("Enhanced logging initialized",{level:o,serviceName:g,logToFile:p,logFilePath:f,debugLogging:process.env.DEBUG_LOGGING}),{error(e,t){y.error(e,t)},warn(e,t){y.warn(e,t)},info(e,t){y.info(e,t)},debug(e,t){y.debug(e,t)},child(e){const s=y.child(e)
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:`${g}:${e.component||"nested"}`})}}}}}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
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()),h=n.default.format.combine(n.default.format.timestamp(),n.default.format.printf((({level:e,message:t,timestamp:s,service:r,component:o,...n})=>{const i=r?`[${r}]`:"",a=o?`[${o}]`:"",c=Object.keys(n).length?" "+JSON.stringify(n,null,2):""
407
- return`${s} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),g=r({logToFile:!1,console:!0,level:c.LogLevel.INFO})
408
- exports.default=g},2928(e,exports,t){"use strict"
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 h=d.filter((e=>(0,r.evaluateCondition)(e.condition,u)))
454
- if(0===h.length)return i
455
- for(const e of h){await this.executeHook(e,u,s),i.triggered++,i.hooks.push(e.name)
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},h=this.metrics.startTimer("api_request_duration"),g=e=>h({...u,...e})
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 g({success:!1,status:r.status,retries:l-1}),Error("Authentication failed - invalid or expired token")
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+""})}}g({success:!1,status:r.status,retries:l-1})
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 g({success:!0,status:r.status,retries:l-1,standardFormat:!1}),e}if(o&&"object"==typeof o&&"success"in o&&!1===o.success){const e=o
531
- throw this.logger.error("API returned error response",{errorCode:e.error?.code,errorMessage:e.error?.message,statusCode:e.error?.statusCode}),g({success:!1,status:r.status,retries:l-1,errorCode:e.error?.code}),Error(`API error: ${e.error?.code}: ${e.error?.message}`)}if(o&&"object"==typeof o&&"success"in o&&!0===o.success&&"data"in o){const t=o
532
- return g({success:!0,status:r.status,retries:l-1,standardFormat:!0}),this.logger.debug("API request succeeded with status "+r.status,{method:e,url:i,status:r.status,retries:l-1,hasMetadata:"metadata"in t}),t}return this.logger.warn("Invalid API response format: Unexpected structure",{responseType:typeof o,responseKeys:"object"==typeof o?Object.keys(o):[]}),{success:!0,data:o}}catch(t){if(l>=d)throw this.incrementFailureCount(),this.logger.error(`API request failed after ${l} attempts`,{method:e,url:i,error:t.message||t+"",status:t.status}),g({success:!1,error:!0,retries:l-1}),t
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"}}}}},5700(e,exports,t){"use strict"
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:h,parentSessionId:g,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:C}=d
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:h})
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,k=p.branchName||"requests/"+u
700
+ const p=d.data,I=p.branchName||"requests/"+u
639
701
  let S
640
- if(h?.length)S=(await Promise.all(h.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
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 I=(0,o.join)(w,c,l),b=(0,o.join)(I,u),E=(0,n.simpleGit)(w)
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",k]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:b,branchName:k})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
646
- try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(I,{recursive:!0})
647
- try{if(!(await E.branch()).all.includes(k))return{success:!1,message:`Branch ${k} not found`,error:"Branch not found"}
648
- await E.raw(["worktree","add","--detach",b,k]),m.info("Created test worktree (detached)",{worktreePath:b,branchName:k})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
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:g})).data,T.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:r,error:(0,i.getErrorMessage)(e)})
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:g,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
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),h=await u.status(),g=(await u.branch()).all.includes("main")?"main":"master"
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
- h.current!==g&&(i.info("Switching to default branch",{from:h.current,to:g}),await u.checkout(g))
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 h=!1,g=!1
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}),h=!0,n.info("Stored session report for creating session",{requestId:o,sessionId:t.cliOrigin.sessionId})
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(!h)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})
744
- g=e.success,g?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:h,agentSpawned:g}),{success:!0,message:`Successfully approved request ${o}: ${t.title}${h?" \u2014 creating session notified":g?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:h,agentSpawned:g}}}catch(e){const t=(0,s.getErrorMessage)(e)
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,a.join)(e||process.cwd(),".alchemy_lab","spawn-debug.log");(0,i.appendFileSync)(o,r)}catch{}}function r(e){const{agent:t,prompt:r,cwd:l,parentServerUrl:d,parentSessionId:u,sessionId:h,model:g,initialBranch:p,serverUrl:f,onSpawn:m,onError:y}=e
753
- try{const n={...process.env}
754
- d&&(n.OPENCODE_PARENT_SERVER=d),p&&(n.ALCHEMY_INITIAL_BRANCH=p),e.allowParallel&&(n.ALCHEMY_ALLOW_PARALLEL="1")
755
- const u=new Date,y=`${(u.getHours()+"").padStart(2,"0")}${(u.getMinutes()+"").padStart(2,"0")}${(u.getSeconds()+"").padStart(2,"0")}`,v=Math.random().toString(36).substring(2,7)
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(n.ALCHEMY_SESSION_TAG=`mcp-${y}-${v}`,s(l,"Spawn env setup",{agent:t,initialBranch:p||"none",hasParentServer:!!d,sessionId:h||"new",attachMode:!!f}),w=f&&h&&r?["run","--attach",f,"--session",h,"--agent",t,r]:h&&r?["run","--session",h,"--agent",t,r]:r?["run","--agent",t,r]:["run","--agent",t],g){const e=w.findIndex((e=>e===r))
758
- e>0?w.splice(e,0,"--model",g):w.push("--model",g)}s(l,"Spawning opencode",{args:w.map((e=>e===r?`[prompt:${e.length}chars]`:e)),model:g||"not specified",promptLength:r?.length,promptPreview:r?.slice(0,100),cwd:l,hasSessionId:!!h,hasParentServerUrl:!!d})
759
- const C=(0,a.join)(l||process.cwd(),".alchemy_lab"),k=(0,a.join)(C,"spawn-stderr.log")
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,i.openSync)(k,"a")}catch{}const I=(0,c.resolveRunCommand)(),b=(0,o.spawn)(I.command,[...I.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:n})
762
- return b.unref(),m&&m(b.pid),{success:!0,pid:b.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
763
- return y&&e instanceof Error&&y(e),{success:!1,error:t}}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=r,exports.spawnJournalSubagent=function(e,t,s,o){return r({agent:"journal",prompt:`Generate a journal entry for request ${e}.\n\nSteps:\n1. Use ${t}__generate_request_journal tool with requestId: ${e}\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-${e}.md using ${t}__document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,cwd:s,parentServerUrl:o})},exports.spawnWorkSubagent=function(e,t,s){return r({agent:"work",prompt:e,cwd:t,parentServerUrl:s})}
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,a.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=s,exports.spawnActionAgent=async function(e){const{agent:t,prompt:r,cwd:o,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:h,apiClient:g,logger:p,trackChildPid:f}=e,m=s(c,p)
767
- let y,v=null
768
- m?v=await async function(e,t,s,r,o,n,i,c){try{const l=await fetch(e+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t,...o?{parentID:o}:{}})})
769
- if(!l.ok)return c.warn("Failed to pre-create session via HTTP",{status:l.status}),null
770
- const d=await l.json()
771
- if(!d.id)return c.warn("Session created but no ID returned"),null
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}},8984(e,exports,t){"use strict"
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 h=c.join(o,"opencode.json")
837
- let g=null,p=null,f=null,m=null,y=!1,v=!1
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?(g=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:g})):v=!0,t?.openCodeModel&&(p=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),v=!0}try{const e=await s.readFile(h,"utf-8"),t=JSON.parse(e)
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||g||f||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},k={tools:C},S=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
842
- i?.openCodeModel?k.model=i.openCodeModel:p?k.model=p:m&&(k.model=m)
843
- const I=c.join(o,".opencode","agent")
844
- await s.mkdir(I,{recursive:!0})
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(I,e.name+".md")
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(I,"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=[]
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(h,JSON.stringify(P,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:h,plugins:A})
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:h}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,s.getErrorMessage)(e)}),{success:!1,error:(0,s.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=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"
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)
@@ -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,h.join)(__dirname,"..","package.json"),(0,h.join)(__dirname,"..","..","package.json")]
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,h.join)(e,S.ALCHEMY_DIR_NAME,"project.json")
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,g.spawn)(s,r,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((s=>{const r=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(r),s(!0))}),2e3)
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,h.basename)(s)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:s,createdVia:o}})})
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),h=e(6928),g=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),C=e(8824),k=e(6800),S=e(4139);(async function(){const i=process.argv.slice(2)
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 I=i.indexOf("--port");-1!==I&&i[I+1]&&(p=parseInt(i[I+1],10),isNaN(p)&&(console.error("\u274c Invalid port number"),process.exit(1)))
984
- const b=i.filter(((e,t)=>"--port"!==e&&(-1===I||t!==I+1)))
985
- if(0===b.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
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,h.join)(l,"server.log"),{createLogger:I}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=I({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})
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,h.join)(e,S.ALCHEMY_DIR_NAME)
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,h.basename)(e)||"Local Project"}"...`),i=await c(t,s,e,o),console.log(i?"\u2705 Project registered: "+i:"\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token \u2014 skipping project registration")
998
- const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,E,P,b)
999
- const A=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
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,g.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
1004
- try{const e=(0,h.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
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,g.execFileSync)("which",["go"],{stdio:"pipe"}),console.log(" Running: go install github.com/sst/opencode@"+r),(0,g.execFileSync)("go",["install","github.com/sst/opencode@"+r],{stdio:"inherit",timeout:12e4})
1009
- try{return(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"}),void console.log("\u2705 OpenCode installed successfully via go install")}catch{console.log("\u26a0\ufe0f go install completed but opencode not found in PATH"),console.log(" Ensure $(go env GOPATH)/bin is in your PATH")}}catch{}try{const{platform:t,arch:r}=await Promise.resolve().then(e.t.bind(e,857,23)),o=t(),n=r(),i="darwin"===o?"darwin":"linux"===o?"linux":null,a="arm64"===n?"arm64":"x64"===n?"amd64":null
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,h.join)(process.env.HOME||"/usr/local/bin",".local","bin")
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,h.join)(r,"opencode")
1015
- return(0,g.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,g.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(r)||(console.log(` \u26a0\ufe0f Add ${r} to your PATH:`),console.log(` export PATH="${r}:$PATH"`),console.log(""),process.env.PATH=`${r}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,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)
1016
- const O=t()
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"+O,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
1018
- const _=s.port||await(0,k.findAvailablePort)(3456),x=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:E,port:_,authToken:P})
1019
- try{await x.start()
1020
- let t=process.env.FRONTEND_URL
1021
- if(!t)if(E.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
1022
- else{try{const e=await fetch(E+"/health")
1023
- if(e.ok){const s=await e.json()
1024
- t=s?.data?.frontendUrl}}catch{}t||(t=E.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
1025
- try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(b,E,P),s=new w.UnifiedLauncher(b,e,t),r=await s.launch({projectPath:i,settings:M?{projectId:M}:void 0})
1026
- if(r.success&&r.port){r.pid&&x.trackChildPid(r.pid),x.setOpenCodePort(r.port),console.log(" OpenCode serve ... port "+r.port)
1027
- const e=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:r.port,pid:r.pid,startedAt:(new Date).toISOString()},null,2)),b.info("Wrote OpenCode config",{path:e,port:r.port})}else{if(!r.success)throw Error(r.error||"Launch failed")
1028
- console.log(" OpenCode serve ... started")}}catch(e){b.warn("Failed to launch OpenCode serve",{error:(0,C.getErrorMessage)(e)}),console.log(" OpenCode serve ... failed (run manually: opencode serve)")}console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${t}\x07${t}\x1b]8;;\x07`),console.log("")
1029
- let s=!1,r=null
1030
- const a=async()=>{s||(s=!0,r&&clearInterval(r),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
1031
- process.on("SIGTERM",a),process.on("SIGINT",a)
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 l.start(a)}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})
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 h=e.port||await(0,k.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:h,authToken:u})
1048
- try{await g.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${g.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
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 g.stop(),process.exit(0))}
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})