alchemylab 1.0.3 → 1.0.5

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
@@ -1,295 +1,300 @@
1
1
  #!/usr/bin/env node
2
2
  (()=>{"use strict"
3
- function e(t){var r=n[t]
4
- if(void 0!==r)return r.exports
3
+ function e(t){var s=n[t]
4
+ if(void 0!==s)return s.exports
5
5
  var module=n[t]={exports:{}}
6
- return s[t].call(module.exports,module,module.exports,e),module.exports}var t,r,o,s={848(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleFinalizeRequestAction=async function(e,t){const{requestId:s}=e,{logger:n,apiClient:i,projectPath:a,projectId:c}=t
7
- if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
8
- n.info("Starting finalize-request action",{requestId:s})
9
- try{const e=await i.post(`/api/projects/${c}/requests/${s}/validate-completion`,{source:"ui"})
6
+ return o[t].call(module.exports,module,module.exports,e),module.exports}var t,s,r,o={848(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleFinalizeRequestAction=async function(e,t){const{requestId:o}=e,{logger:n,apiClient:i,projectPath:a,projectId:c}=t
7
+ if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
8
+ n.info("Starting finalize-request action",{requestId:o})
9
+ try{const e=await i.post(`/api/projects/${c}/requests/${o}/validate-completion`,{source:"ui"})
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,r)=>{e+=`${r+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:t.reason}}const l=await i.get(`/api/projects/${c}/requests/${s}`),d=l?.data,u="requests/"+s,g=(0,r.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"}
13
+ return t.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",t.blockedByTasks.forEach(((t,s)=>{e+=`${s+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:t.reason}}const l=await i.get(`/api/projects/${c}/requests/${o}`),d=l?.data,u="requests/"+o,h=(0,s.simpleGit)(a),g=await h.status(),p=await h.branch(),f=p.all.includes("main")?"main":"master"
14
+ if(g.current!==f&&await h.checkout(f),!p.all.includes(u))return{success:!1,message:`Branch '${u}' not found. The request may not have any file changes to merge.`,error:"Branch not found"}
15
15
  let m,y=!1
16
16
  try{const e=d?.title?`Merge ${u}-${d.title}`:"Merge "+u
17
- await g.merge([u,"--no-ff","-m",e])
18
- const t=await g.log({maxCount:1})
19
- m=t.latest?.hash,n.info("Git merge completed",{requestId:s,mergeCommit:m?.substring(0,8)})}catch(e){const t=(0,o.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:s,error:e instanceof Error?e.message:e+""})}return{success:!1,message:`Merge conflicts detected for request ${s}. 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/${s}/finalize-completion`,{summary:"Finalized request "+s,acceptanceEvidence:[{evidence:"Finalized via GUI after review"}],mergeResult:{mergeCommitHash:m||"unknown",cleanedUp:y,method:"action-server-merge"}})}catch(e){const t=(0,o.getErrorMessage)(e)
21
- return n.error("CRITICAL: Merge succeeded but server finalization failed",{requestId:s,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 ${s}: ${d?.title||s}`,data:{mergeCommitHash:m,branchDeleted:y}}}catch(e){const t=(0,o.getErrorMessage)(e)
22
- return n.error("finalize-request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to finalize request: "+t,error:t}}}
23
- const r=t(3807),o=t(8824)},857(module){module.exports=require("os")},1152(e,exports){function t(e=!0){return new r(e)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.createMetricsCollector=t,exports.createNullMetricsCollector=function(){return t(!1)}
24
- class r{constructor(e=!0){this.enabled=e,this.counters=new Map,this.gauges=new Map,this.histograms=new Map}getKey(e,t){const r=Object.entries(t).map((([e,t])=>`${e}="${t+""}"`)).join(",")
25
- return r?`${e}{${r}}`:e}incrementCounter(e,t=1,r={}){if(!this.enabled)return
26
- const o=this.getKey(e,r),s=this.counters.get(o)||0
27
- this.counters.set(o,s+t)}setGauge(e,t,r={}){if(!this.enabled)return
28
- const o=this.getKey(e,r)
29
- this.gauges.set(o,t)}observeHistogram(e,t,r={}){if(!this.enabled)return
30
- const o=this.getKey(e,r),s=this.histograms.get(o)||[]
31
- s.push(t),this.histograms.set(o,s)}recordValue(e,t,r={}){this.observeHistogram(e,t,r)}startTimer(e){if(!this.enabled)return()=>{}
17
+ await h.merge([u,"--no-ff","-m",e])
18
+ const t=await h.log({maxCount:1})
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)
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
+ return n.error("finalize-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to finalize request: "+t,error:t}}}
23
+ const s=t(3807),r=t(8824)},857(module){module.exports=require("os")},1152(e,exports){function t(e=!0){return new s(e)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.createMetricsCollector=t,exports.createNullMetricsCollector=function(){return t(!1)}
24
+ class s{constructor(e=!0){this.enabled=e,this.counters=new Map,this.gauges=new Map,this.histograms=new Map}getKey(e,t){const s=Object.entries(t).map((([e,t])=>`${e}="${t+""}"`)).join(",")
25
+ return s?`${e}{${s}}`:e}incrementCounter(e,t=1,s={}){if(!this.enabled)return
26
+ const r=this.getKey(e,s),o=this.counters.get(r)||0
27
+ this.counters.set(r,o+t)}setGauge(e,t,s={}){if(!this.enabled)return
28
+ const r=this.getKey(e,s)
29
+ this.gauges.set(r,t)}observeHistogram(e,t,s={}){if(!this.enabled)return
30
+ const r=this.getKey(e,s),o=this.histograms.get(r)||[]
31
+ o.push(t),this.histograms.set(r,o)}recordValue(e,t,s={}){this.observeHistogram(e,t,s)}startTimer(e){if(!this.enabled)return()=>{}
32
32
  const t=Date.now()
33
- return(r={})=>{const o=Date.now()-t
34
- this.observeHistogram(e,o,r)}}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){Object.defineProperty(exports,"__esModule",{value:!0}),exports.UnifiedLauncher=void 0
35
- const r=t(5317),o=t(8824)
36
- exports.UnifiedLauncher=class{constructor(e,t,r){this.logger=e,this.installContext=t,this.configurer=r}async launch(e){const{projectPath:t,settings:r,multiAgent:s}=e,{installPath:n,nodePath:i}=this.installContext
33
+ return(s={})=>{const r=Date.now()-t
34
+ 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){Object.defineProperty(exports,"__esModule",{value:!0}),exports.UnifiedLauncher=void 0
35
+ const s=t(5317),r=t(8824)
36
+ 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
37
37
  this.logger.info("UnifiedLauncher: Starting launch",{projectPath:t,installPath:n})
38
- try{const o=await this.configurer.configureOpenCode(n,t,i,r,s)
39
- return o.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:o.configPath}),this.launchBackground(e,o)):(this.logger.error("UnifiedLauncher: Configuration failed",{error:o.error}),{success:!1,error:o.error})}catch(e){return this.logger.error("UnifiedLauncher: Launch failed",{error:(0,o.getErrorMessage)(e)}),{success:!1,error:(0,o.getErrorMessage)(e)}}}async launchBackground(e,t){const{projectPath:o,envVars:s}=e
40
- try{(0,r.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"OpenCode not found in PATH."}}return this.logger.info("UnifiedLauncher: Launching opencode serve in background",{command:"opencode serve",cwd:o}),new Promise((e=>{const n=(0,r.spawn)("opencode",["serve"],{stdio:["ignore","pipe","pipe"],cwd:o,detached:!0,env:{...process.env,...s}})
38
+ try{const r=await this.configurer.configureOpenCode(n,t,i,s,o)
39
+ 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,t){const{projectPath:r,envVars:o}=e
40
+ try{(0,s.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"OpenCode not found in PATH."}}return this.logger.info("UnifiedLauncher: Launching opencode serve in background",{command:"opencode serve",cwd:r}),new Promise((e=>{const n=(0,s.spawn)("opencode",["serve"],{stdio:["ignore","pipe","pipe"],cwd:r,detached:!0,env:{...process.env,...o}})
41
41
  let i="",a=!1
42
- const c=r=>{i+=r
43
- const o=i.match(/listening on http:\/\/[^:]+:(\d+)/)
44
- if(o&&!a){const r=parseInt(o[1],10)
45
- a=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:n.pid,port:r}),n.unref(),e({success:!0,pid:n.pid,port:r,configPath:t.configPath})}}
46
- n.stdout?.on("data",(e=>c(e.toString()))),n.stderr?.on("data",(e=>c(e.toString()))),setTimeout((()=>{a||(a=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:i.slice(0,500)}),n.unref(),e({success:!0,pid:n.pid,configPath:t.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),n.on("error",(t=>{a||(a=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),n.on("exit",(t=>{a||(a=!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){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
42
+ const c=s=>{i+=s
43
+ const r=i.match(/listening on http:\/\/[^:]+:(\d+)/)
44
+ if(r&&!a){const s=parseInt(r[1],10)
45
+ a=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:n.pid,port:s}),n.unref(),e({success:!0,pid:n.pid,port:s,configPath:t.configPath})}}
46
+ n.stdout?.on("data",(e=>c(e.toString()))),n.stderr?.on("data",(e=>c(e.toString()))),setTimeout((()=>{a||(a=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:i.slice(0,500)}),n.unref(),e({success:!0,pid:n.pid,configPath:t.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),n.on("error",(t=>{a||(a=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),n.on("exit",(t=>{a||(a=!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){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
47
47
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
48
- const o=r(t(6928)),s=r(t(1943)),n=r(t(6982)),i=r(t(857)),a=t(9067),c=t(8824),l="alchemy-lab"
49
- exports.TokenManager=class{constructor(e,t,r={}){this.projectId=null,this.encryptionKey=null,this.pendingAuthCheck=!1,this.tempAuthCode=null,this.CACHE_TTL_MS=6e4,this.TOKEN_CACHE={manualToken:null,encryptedToken:null,hasValidToken:null},this.initPromise=null,this.initialized=!1,this.logger=e.child({component:"token-manager"}),this.remoteServerUrl=r.remoteServerUrl||"https://api.alchemylabcode.com",this.allowInteractiveAuth=!1!==r.allowInteractiveAuth,void 0!==r.cacheTtlMs&&r.cacheTtlMs>0&&(this.CACHE_TTL_MS=r.cacheTtlMs),this.serverInstallDir=this.getModuleDir(),this.tokenDirPath=this.serverInstallDir,this.authFlow=new a.ExternalAuthFlow(e,this.remoteServerUrl),this.logger.info("TokenManager initialized",{workingDir:t,serverInstallDir:this.serverInstallDir,tokenDirPath:this.tokenDirPath,allowInteractiveAuth:this.allowInteractiveAuth,cacheTtlMs:this.CACHE_TTL_MS}),this.generateEncryptionKey()}getModuleDir(){const e=__dirname||process.cwd(),t=i.default.homedir()
50
- return e.includes("node_modules")?o.default.join(t,".alchemy_lab","tokens"):o.default.join(t,".alchemy_lab","tokens-dev")}async initialize(){return this.initPromise||(this.initPromise=this.ensureTokenDirectoryExists().then((()=>{this.initialized=!0,this.logger.info("TokenManager initialization completed")})).catch((e=>{throw this.logger.error("TokenManager initialization failed",{error:e instanceof Error?e.stack:e+""}),Error("TokenManager initialization failed")}))),this.initPromise}getStableMachineId(){try{const e=t(857),r=o.default.join(this.serverInstallDir,".machine-id")
51
- try{const e=t(9896).readFileSync(r,"utf8").trim()
52
- if(e&&e.length>0)return e}catch{}const s=e.platform(),i=e.arch(),a=e.homedir(),c=e.cpus(),l=`${s}-${i}-${c&&c.length>0?c[0].model:"unknown"}-${a}`,d=n.default.createHash("sha256").update(l).digest("hex")
53
- try{t(9896).writeFileSync(r,d,"utf8"),this.logger.debug("Created new machine ID file",{path:r})}catch(e){this.logger.warn("Could not persist machine ID, will regenerate on restart",{error:e instanceof Error?e.message:e+""})}return d}catch(e){return this.logger.error("Error getting stable machine ID",{error:(0,c.getErrorMessage)(e)}),n.default.randomBytes(32).toString("hex")}}generateEncryptionKey(){try{const e=t(857).platform(),r=this.getStableMachineId(),o=`${l}-${e}-${r}`
54
- this.encryptionKey=n.default.createHash("sha256").update(o).digest(),this.logger.debug("Generated encryption key using stable machine ID")}catch(e){this.logger.error("Error generating encryption key",{error:(0,c.getErrorMessage)(e)}),this.encryptionKey=Buffer.from(l.repeat(4),"utf8")}}generateLegacyEncryptionKey(){try{const e=t(857).platform(),r=t(857).hostname(),o=`${l}-${e}-${r}`
55
- return n.default.createHash("sha256").update(o).digest()}catch(e){return this.logger.error("Error generating legacy encryption key",{error:(0,c.getErrorMessage)(e)}),Buffer.from(l.repeat(4),"utf8")}}async ensureTokenDirectoryExists(){try{await s.default.mkdir(this.tokenDirPath,{recursive:!0}),this.logger.debug("Token directory ensured exists",{path:this.tokenDirPath})}catch(e){throw this.logger.error("Error creating token directory",{path:this.tokenDirPath,error:(0,c.getErrorMessage)(e)}),Error("Error creating token directory: "+this.tokenDirPath)}}setProjectId(e){this.projectId=e,this.logger.info("Set project ID for token manager",{projectId:e})}getAccountKey(){return"default-user-"+n.default.createHash("sha256").update(this.remoteServerUrl).digest("hex").substring(0,8)}getTokenFilePath(e){const t=e.replace(/[^a-zA-Z0-9]/g,"_")
56
- return o.default.join(this.tokenDirPath,t+".token")}invalidateCache(){this.logger.debug("Invalidating all token caches"),this.TOKEN_CACHE.manualToken=null,this.TOKEN_CACHE.encryptedToken=null,this.TOKEN_CACHE.hasValidToken=null}async checkForManualToken(){const e=Date.now(),t=o.default.join(this.tokenDirPath,"manual-token.json")
48
+ 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"
49
+ exports.TokenManager=class{constructor(e,t,s={}){this.projectId=null,this.encryptionKey=null,this.pendingAuthCheck=!1,this.tempAuthCode=null,this.CACHE_TTL_MS=6e4,this.TOKEN_CACHE={manualToken:null,encryptedToken:null,hasValidToken:null},this.initPromise=null,this.initialized=!1,this.logger=e.child({component:"token-manager"}),this.remoteServerUrl=s.remoteServerUrl||"https://api.alchemylabcode.com",this.allowInteractiveAuth=!1!==s.allowInteractiveAuth,void 0!==s.cacheTtlMs&&s.cacheTtlMs>0&&(this.CACHE_TTL_MS=s.cacheTtlMs),this.serverInstallDir=this.getModuleDir(),this.tokenDirPath=this.serverInstallDir,this.authFlow=new a.ExternalAuthFlow(e,this.remoteServerUrl),this.logger.info("TokenManager initialized",{workingDir:t,serverInstallDir:this.serverInstallDir,tokenDirPath:this.tokenDirPath,allowInteractiveAuth:this.allowInteractiveAuth,cacheTtlMs:this.CACHE_TTL_MS}),this.generateEncryptionKey()}getModuleDir(){const e=__dirname||process.cwd(),t=i.default.homedir()
50
+ return e.includes("node_modules")?r.default.join(t,".alchemy_lab","tokens"):r.default.join(t,".alchemy_lab","tokens-dev")}async initialize(){return this.initPromise||(this.initPromise=this.ensureTokenDirectoryExists().then((()=>{this.initialized=!0,this.logger.info("TokenManager initialization completed")})).catch((e=>{throw this.logger.error("TokenManager initialization failed",{error:e instanceof Error?e.stack:e+""}),Error("TokenManager initialization failed")}))),this.initPromise}getStableMachineId(){try{const e=t(857),s=r.default.join(this.serverInstallDir,".machine-id")
51
+ try{const e=t(9896).readFileSync(s,"utf8").trim()
52
+ if(e&&e.length>0)return e}catch{}const o=e.platform(),i=e.arch(),a=e.homedir(),c=e.cpus(),l=`${o}-${i}-${c&&c.length>0?c[0].model:"unknown"}-${a}`,d=n.default.createHash("sha256").update(l).digest("hex")
53
+ try{t(9896).writeFileSync(s,d,"utf8"),this.logger.debug("Created new machine ID file",{path:s})}catch(e){this.logger.warn("Could not persist machine ID, will regenerate on restart",{error:e instanceof Error?e.message:e+""})}return d}catch(e){return this.logger.error("Error getting stable machine ID",{error:(0,c.getErrorMessage)(e)}),n.default.randomBytes(32).toString("hex")}}generateEncryptionKey(){try{const e=t(857).platform(),s=this.getStableMachineId(),r=`${l}-${e}-${s}`
54
+ this.encryptionKey=n.default.createHash("sha256").update(r).digest(),this.logger.debug("Generated encryption key using stable machine ID")}catch(e){this.logger.error("Error generating encryption key",{error:(0,c.getErrorMessage)(e)}),this.encryptionKey=Buffer.from(l.repeat(4),"utf8")}}generateLegacyEncryptionKey(){try{const e=t(857).platform(),s=t(857).hostname(),r=`${l}-${e}-${s}`
55
+ return n.default.createHash("sha256").update(r).digest()}catch(e){return this.logger.error("Error generating legacy encryption key",{error:(0,c.getErrorMessage)(e)}),Buffer.from(l.repeat(4),"utf8")}}async ensureTokenDirectoryExists(){try{await o.default.mkdir(this.tokenDirPath,{recursive:!0}),this.logger.debug("Token directory ensured exists",{path:this.tokenDirPath})}catch(e){throw this.logger.error("Error creating token directory",{path:this.tokenDirPath,error:(0,c.getErrorMessage)(e)}),Error("Error creating token directory: "+this.tokenDirPath)}}setProjectId(e){this.projectId=e,this.logger.info("Set project ID for token manager",{projectId:e})}getAccountKey(){return"default-user-"+n.default.createHash("sha256").update(this.remoteServerUrl).digest("hex").substring(0,8)}getTokenFilePath(e){const t=e.replace(/[^a-zA-Z0-9]/g,"_")
56
+ return r.default.join(this.tokenDirPath,t+".token")}invalidateCache(){this.logger.debug("Invalidating all token caches"),this.TOKEN_CACHE.manualToken=null,this.TOKEN_CACHE.encryptedToken=null,this.TOKEN_CACHE.hasValidToken=null}async checkForManualToken(){const e=Date.now(),t=r.default.join(this.tokenDirPath,"manual-token.json")
57
57
  if(this.TOKEN_CACHE.manualToken&&e<this.TOKEN_CACHE.manualToken.expiresAt){const t=this.TOKEN_CACHE.manualToken
58
58
  return this.logger.debug("Using cached manual token check result",{ageSecs:Math.round((e-t.timestamp)/1e3),exists:t.exists,hasToken:null!==t.value}),t.exists&&t.value?t.value:null}this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Checking for manual token file",{tokenFilePath:t})
59
- try{let r=!1,o=null
60
- try{await s.default.stat(t),r=!0,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file exists")}catch{this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file does not exist")}if(r)try{const r=await s.default.readFile(t,"utf8"),n=JSON.parse(r)
61
- new Date(n.expiresAt)>new Date?(o=n,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Valid manual token found",{username:o.username,expiresAt:o.expiresAt})):this.logger.debug("Manual token has expired",{expiresAt:n.expiresAt})}catch(e){this.logger.error("Error parsing manual token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.manualToken={value:o,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:r},o}catch(e){return this.logger.error("Error checking for manual token",{error:(0,c.getErrorMessage)(e)}),null}}encrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
62
- const t=n.default.randomBytes(16),r=n.default.createCipheriv("aes-256-cbc",this.encryptionKey,t)
63
- let o=r.update(e,"utf8","hex")
64
- return o+=r.final("hex"),`${t.toString("hex")}:${o}`}decrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
59
+ try{let s=!1,r=null
60
+ try{await o.default.stat(t),s=!0,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file exists")}catch{this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file does not exist")}if(s)try{const s=await o.default.readFile(t,"utf8"),n=JSON.parse(s)
61
+ new Date(n.expiresAt)>new Date?(r=n,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Valid manual token found",{username:r.username,expiresAt:r.expiresAt})):this.logger.debug("Manual token has expired",{expiresAt:n.expiresAt})}catch(e){this.logger.error("Error parsing manual token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.manualToken={value:r,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:s},r}catch(e){return this.logger.error("Error checking for manual token",{error:(0,c.getErrorMessage)(e)}),null}}encrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
62
+ const t=n.default.randomBytes(16),s=n.default.createCipheriv("aes-256-cbc",this.encryptionKey,t)
63
+ let r=s.update(e,"utf8","hex")
64
+ return r+=s.final("hex"),`${t.toString("hex")}:${r}`}decrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
65
65
  try{return this.decryptWithKey(e,this.encryptionKey)}catch(t){this.logger.warn("Failed to decrypt with current key, trying legacy hostname-based key",{error:t instanceof Error?t.message:t+""})
66
- try{const t=this.generateLegacyEncryptionKey(),r=this.decryptWithKey(e,t)
67
- return this.logger.info("Successfully decrypted with legacy key, will re-encrypt with new key on next store"),r}catch(e){this.logger.error("Failed to decrypt with both current and legacy keys",{primaryError:t instanceof Error?t.message:t+"",legacyError:e instanceof Error?e.message:e+""})
68
- const r=this.getAccountKey(),o=this.getTokenFilePath(r)
69
- try{s.default.rename(o,o+".corrupt").catch((()=>this.logger.warn("Could not backup corrupted token file")))}catch(e){this.logger.error("Error backing up corrupted file: "+e)}throw Error("Could not decrypt token file with current or legacy key - it may be corrupted")}}}decryptWithKey(e,t){try{const[r,o]=e.split(":")
70
- if(!r||!o)throw Error("Invalid encrypted data format")
71
- const s=Buffer.from(r,"hex"),i=n.default.createDecipheriv("aes-256-cbc",t,s)
72
- let a=i.update(o,"hex","utf8")
66
+ try{const t=this.generateLegacyEncryptionKey(),s=this.decryptWithKey(e,t)
67
+ return this.logger.info("Successfully decrypted with legacy key, will re-encrypt with new key on next store"),s}catch(e){this.logger.error("Failed to decrypt with both current and legacy keys",{primaryError:t instanceof Error?t.message:t+"",legacyError:e instanceof Error?e.message:e+""})
68
+ const s=this.getAccountKey(),r=this.getTokenFilePath(s)
69
+ try{o.default.rename(r,r+".corrupt").catch((()=>this.logger.warn("Could not backup corrupted token file")))}catch(e){this.logger.error("Error backing up corrupted file: "+e)}throw Error("Could not decrypt token file with current or legacy key - it may be corrupted")}}}decryptWithKey(e,t){try{const[s,r]=e.split(":")
70
+ if(!s||!r)throw Error("Invalid encrypted data format")
71
+ const o=Buffer.from(s,"hex"),i=n.default.createDecipheriv("aes-256-cbc",t,o)
72
+ let a=i.update(r,"hex","utf8")
73
73
  return a+=i.final("utf8"),a}catch(e){throw e}}async storeToken(e){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
74
- try{const t=this.getAccountKey(),r=this.getTokenFilePath(t),o=JSON.stringify(e),n=this.encrypt(o)
75
- await s.default.writeFile(r,n,"utf8"),this.logger.info("Token stored securely",{username:e.username,expiresAt:e.expiresAt}),this.invalidateCache()}catch(e){throw this.logger.error("Error storing token",{error:(0,c.getErrorMessage)(e)}),Error("Failed to store token securely")}}async getToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
74
+ try{const t=this.getAccountKey(),s=this.getTokenFilePath(t),r=JSON.stringify(e),n=this.encrypt(r)
75
+ await o.default.writeFile(s,n,"utf8"),this.logger.info("Token stored securely",{username:e.username,expiresAt:e.expiresAt}),this.invalidateCache()}catch(e){throw this.logger.error("Error storing token",{error:(0,c.getErrorMessage)(e)}),Error("Failed to store token securely")}}async getToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
76
76
  try{if(this.hasPendingAuth()&&this.tempAuthCode){const e=await this.checkAuthStatus(this.tempAuthCode)
77
77
  if(e)return e}const e=await this.checkForManualToken()
78
78
  if(e)return e
79
79
  const t=Date.now()
80
80
  if(this.TOKEN_CACHE.encryptedToken&&t<this.TOKEN_CACHE.encryptedToken.expiresAt){const e=this.TOKEN_CACHE.encryptedToken
81
- return this.logger.debug("Using cached encrypted token",{ageSecs:Math.round((t-e.timestamp)/1e3),hasToken:null!==e.value}),e.value}const r=this.getAccountKey(),o=this.getTokenFilePath(r)
82
- try{await s.default.stat(o)}catch{return this.logger.debug("No encrypted token file found"),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:t,expiresAt:t+this.CACHE_TTL_MS,lastCheck:t,exists:!1},this.allowInteractiveAuth&&!this.hasPendingAuth()?(this.logger.debug("No stored token found, starting authentication flow"),this.startAuthenticationFlow().catch((e=>{this.logger.error("Failed to start authentication flow",{error:e instanceof Error?e.message:e+""})}))):this.logger.debug("Interactive authentication disabled or already pending"),null}try{const e=await s.default.readFile(o,"utf8"),t=this.decrypt(e),r=JSON.parse(t),n=new Date(r.expiresAt),i=new Date,a=n.getTime()-i.getTime(),c=a/36e5
83
- return this.logger.info("Token expiration check details",{tokenExpiresAt:r.expiresAt,currentTime:i.toISOString(),isExpired:i>=n,timeUntilExpiryMs:a,timeUntilExpiryHours:Math.round(100*c)/100,username:r.username,systemTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone}),n>i?(this.logger.info("Retrieved valid token from storage",{username:r.username,expiresAt:r.expiresAt}),this.TOKEN_CACHE.encryptedToken={value:r,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!0},r):(this.logger.info("Stored token has expired",{expiresAt:r.expiresAt,expiredByMs:i.getTime()-n.getTime(),expiredByHours:Math.round((i.getTime()-n.getTime())/36e5*100)/100}),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null)}catch(e){this.logger.error("Error reading/decrypting token file",{error:(0,c.getErrorMessage)(e),path:o})
84
- try{const e=o+".backup"
85
- await s.default.rename(o,e),this.logger.info("Moved corrupted token file to backup",{from:o,to:e})}catch(e){this.logger.error("Error creating backup of corrupted token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null}}catch(e){return this.logger.error("Error retrieving token",{error:(0,c.getErrorMessage)(e)}),null}}async removeToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
81
+ return this.logger.debug("Using cached encrypted token",{ageSecs:Math.round((t-e.timestamp)/1e3),hasToken:null!==e.value}),e.value}const s=this.getAccountKey(),r=this.getTokenFilePath(s)
82
+ try{await o.default.stat(r)}catch{return this.logger.debug("No encrypted token file found"),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:t,expiresAt:t+this.CACHE_TTL_MS,lastCheck:t,exists:!1},this.allowInteractiveAuth&&!this.hasPendingAuth()?(this.logger.debug("No stored token found, starting authentication flow"),this.startAuthenticationFlow().catch((e=>{this.logger.error("Failed to start authentication flow",{error:e instanceof Error?e.message:e+""})}))):this.logger.debug("Interactive authentication disabled or already pending"),null}try{const e=await o.default.readFile(r,"utf8"),t=this.decrypt(e),s=JSON.parse(t),n=new Date(s.expiresAt),i=new Date,a=n.getTime()-i.getTime(),c=a/36e5
83
+ return this.logger.info("Token expiration check details",{tokenExpiresAt:s.expiresAt,currentTime:i.toISOString(),isExpired:i>=n,timeUntilExpiryMs:a,timeUntilExpiryHours:Math.round(100*c)/100,username:s.username,systemTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone}),n>i?(this.logger.info("Retrieved valid token from storage",{username:s.username,expiresAt:s.expiresAt}),this.TOKEN_CACHE.encryptedToken={value:s,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!0},s):(this.logger.info("Stored token has expired",{expiresAt:s.expiresAt,expiredByMs:i.getTime()-n.getTime(),expiredByHours:Math.round((i.getTime()-n.getTime())/36e5*100)/100}),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null)}catch(e){this.logger.error("Error reading/decrypting token file",{error:(0,c.getErrorMessage)(e),path:r})
84
+ try{const e=r+".backup"
85
+ await o.default.rename(r,e),this.logger.info("Moved corrupted token file to backup",{from:r,to:e})}catch(e){this.logger.error("Error creating backup of corrupted token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null}}catch(e){return this.logger.error("Error retrieving token",{error:(0,c.getErrorMessage)(e)}),null}}async removeToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
86
86
  try{const e=this.getAccountKey(),t=this.getTokenFilePath(e)
87
- try{await s.default.stat(t)}catch{return this.logger.info("No token file to remove"),!1}return await s.default.unlink(t),this.logger.info("Token removed from secure storage"),this.invalidateCache(),!0}catch(e){return this.logger.error("Error removing token",{error:(0,c.getErrorMessage)(e)}),!1}}async hasValidToken(){const e=Date.now()
87
+ try{await o.default.stat(t)}catch{return this.logger.info("No token file to remove"),!1}return await o.default.unlink(t),this.logger.info("Token removed from secure storage"),this.invalidateCache(),!0}catch(e){return this.logger.error("Error removing token",{error:(0,c.getErrorMessage)(e)}),!1}}async hasValidToken(){const e=Date.now()
88
88
  if(this.TOKEN_CACHE.hasValidToken&&e<this.TOKEN_CACHE.hasValidToken.expiresAt){const t=this.TOKEN_CACHE.hasValidToken
89
89
  return this.logger.debug("Using cached hasValidToken result",{ageSecs:Math.round((e-t.timestamp)/1e3),hasToken:t.value}),t.value}this.logger.debug("Checking if valid token exists (cache miss or expired)")
90
90
  const t=null!==await this.getToken()
91
- return this.TOKEN_CACHE.hasValidToken={value:t,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:t},t}getTokenDirectoryPath(){return this.tokenDirPath}getManualTokenFilePath(){return o.default.join(this.tokenDirPath,"manual-token.json")}logTokenLocationInstructions(){const e=this.getManualTokenFilePath()
91
+ return this.TOKEN_CACHE.hasValidToken={value:t,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:t},t}getTokenDirectoryPath(){return this.tokenDirPath}getManualTokenFilePath(){return r.default.join(this.tokenDirPath,"manual-token.json")}logTokenLocationInstructions(){const e=this.getManualTokenFilePath()
92
92
  console.log("========================================"),console.log("To use MCP server, you need to authenticate."),console.log("Token file location: "+e),console.log("You can either:"),console.log('\n1. Use the authenticate tool (recommended)\n2. Manually place a token file at the above location\n3. Set up automatic authentication flow\n\nFor manual token placement, create a JSON file with:\n{\n "token": "your_token_here",\n "userId": "your_user_id",\n "username": "your_username", \n "role": "user",\n "expiresAt": "2025-12-31T23:59:59.000Z"\n}\n'),console.log("For more information, see the authentication documentation."),console.log("========================================")}async startAuthenticationFlow(){this.pendingAuthCheck=!0
93
93
  try{const{tempCode:e,authUrl:t}=await this.authFlow.startAuthFlow()
94
94
  return this.tempAuthCode=e,this.logger.info("Started external authentication flow",{tempCode:e,authUrl:t}),this.authFlow.startPolling(e,(async t=>{this.logger.info("Authentication flow completed",{tempCode:e.substring(0,4)+"***",username:t.username}),await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),console.log("Authentication successful!"),console.log(`Welcome, ${t.username}!`),console.log("You can now use all MCP tools and features."),console.log("========================================")}),(()=>{this.logger.info("Authentication flow timed out",{tempCode:e.substring(0,4)+"***"}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow timed out."),console.log("Please try again or manually place a token file."),console.log("Token file location:"),console.log(""+this.getManualTokenFilePath()),console.log("========================================")}),(t=>{this.logger.error("Authentication flow error",{tempCode:e.substring(0,4)+"***",error:t}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow error."),console.log("Error: "+t),console.log("Please try again or manually place a token file."),console.log(""+this.getManualTokenFilePath()),console.log("========================================")})),{tempCode:e,authUrl:t}}catch(e){throw this.logger.error("Error starting authentication flow",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,e}}hasPendingAuth(){return this.pendingAuthCheck||!!this.tempAuthCode}getAuthStatus(){return this.hasPendingAuth()?{hasToken:!1,isPendingAuth:!0,tempCode:this.tempAuthCode,authUrl:this.authFlow.getAuthUrl(this.tempAuthCode)}:{hasToken:!1,isPendingAuth:!1}}async checkAuthStatus(e){if(!e)return null
95
95
  try{const t=this.authFlow.getPollingStatus()
96
96
  if(t.status===a.PollingStatus.AUTHENTICATED&&t.tokenInfo)return this.logger.info("Authentication polling has token available",{tempCode:e.substring(0,4)+"***",username:t.tokenInfo.username}),t.tokenInfo
97
97
  if(t.status===a.PollingStatus.ERROR||t.status===a.PollingStatus.TIMEOUT)return this.pendingAuthCheck=!1,this.tempAuthCode=null,null
98
- const r=this.authFlow.getPollingUrl(e),o=await fetch(r)
99
- if(o.ok){const e=await o.json()
98
+ const s=this.authFlow.getPollingUrl(e),r=await fetch(s)
99
+ if(r.ok){const e=await r.json()
100
100
  if(e.authenticated&&e.token){const t=this.authFlow.parseTokenResponse(e)
101
101
  if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
102
102
  try{const e=await this.getToken()
103
103
  if(!e)return this.logger.debug("No token available for validation"),!1
104
104
  const t=this.remoteServerUrl+"/auth/validate"
105
105
  this.logger.debug("Making token validation request",{validationUrl:t})
106
- const r=await fetch(t,{method:"GET",headers:{Authorization:"Bearer "+e.token,"Content-Type":"application/json"},signal:AbortSignal.timeout(1e4)})
107
- if(r.ok){const e=await r.json()
108
- 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:r.status,statusText:r.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,r,o,s){return await this.authFlow.startGoogleOAuthPolling(e,t,r,o,s)}}},1943(module){module.exports=require("fs/promises")},2096(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
106
+ const s=await fetch(t,{method:"GET",headers:{Authorization:"Bearer "+e.token,"Content-Type":"application/json"},signal:AbortSignal.timeout(1e4)})
107
+ if(s.ok){const e=await s.json()
108
+ 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){module.exports=require("fs/promises")},2096(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
109
109
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProjectConfigService=void 0
110
- const o=r(t(1943)),s=r(t(6928)),n=r(t(857)),i=t(4139),a=t(8824)
111
- exports.ProjectConfigService=class{constructor(e){this.logger=e.child({component:"project-config"})}async readProjectId(e){try{const t=s.default.join(e,i.ALCHEMY_DIR_NAME,"project.json"),r=await o.default.readFile(t,"utf-8"),n=JSON.parse(r)
112
- 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=s.default.join(e,i.ALCHEMY_DIR_NAME,"config.json"),r=await o.default.readFile(t,"utf-8")
113
- return JSON.parse(r)}catch(e){return this.logger.debug("Could not read project config",{error:(0,a.getErrorMessage)(e)}),{}}}async saveMultiAgentConfig(e,t,r){try{const n=s.default.join(e,i.ALCHEMY_DIR_NAME),a=s.default.join(n,"config.json")
114
- await o.default.mkdir(n,{recursive:!0})
110
+ const r=s(t(1943)),o=s(t(6928)),n=s(t(857)),i=t(4139),a=t(8824)
111
+ 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)
112
+ 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")
113
+ 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")
114
+ await r.default.mkdir(n,{recursive:!0})
115
115
  let c={}
116
- try{const e=await o.default.readFile(a,"utf-8")
117
- c=JSON.parse(e)}catch(e){this.logger.debug("No existing config.json, creating new one")}c.multiAgent=!1!==t,void 0!==r&&(c.autoJournal=r),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 o.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()
118
- return s.default.join(e,".alchemy_lab","tool-preferences.json")}async loadToolPreferences(e){const t=this.getToolPreferencesPath()
119
- try{const r=await o.default.readFile(t,"utf-8"),s=JSON.parse(r)
120
- return e&&s[e]?s[e]:e?null:s}catch(t){return this.logger.debug("No user preferences found",{error:(0,a.getErrorMessage)(t)}),e?null:{}}}async saveToolPreferences(e,t,r,n){const i=this.getToolPreferencesPath(),a=s.default.dirname(i)
121
- this.logger.info("Saving user tool preferences",{clientType:e,preset:t,disabledToolsCount:r?.length||0,allowedToolsCount:n?.length||0}),await o.default.mkdir(a,{recursive:!0})
116
+ try{const e=await r.default.readFile(a,"utf-8")
117
+ 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()
118
+ return o.default.join(e,".alchemy_lab","tool-preferences.json")}async loadToolPreferences(e){const t=this.getToolPreferencesPath()
119
+ try{const s=await r.default.readFile(t,"utf-8"),o=JSON.parse(s)
120
+ 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)
121
+ this.logger.info("Saving user tool preferences",{clientType:e,preset:t,disabledToolsCount:s?.length||0,allowedToolsCount:n?.length||0}),await r.default.mkdir(a,{recursive:!0})
122
122
  let c={}
123
- try{const e=await o.default.readFile(i,"utf-8")
124
- c=JSON.parse(e)}catch(e){}c[e]={preset:t,disabledTools:r||[],allowedAlchemyTools:n||[],updatedAt:(new Date).toISOString()},await o.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){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
123
+ try{const e=await r.default.readFile(i,"utf-8")
124
+ 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){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
125
125
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
126
- const o=r(t(7252)),s=r(t(8577)),n=t(4192),i=t(1152),a=r(t(6928)),c=r(t(9896)),l=r(t(857)),d=t(5607),u=t(8984),g=t(8524),h=t(8824),p=t(2096),f=t(7210),m=t(5317)
127
- class y{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
128
- const r=(0,i.createMetricsCollector)(!1)
129
- this.apiClient=new n.BaseApiClient(t.remoteServerUrl,e,r,t.authToken),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,o.default)()
130
- return e.use((0,s.default)()),e.use(o.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:r}=e.params,o=this.projectId
131
- if(!o)return void t.status(400).json({success:!1,error:"No project"})
132
- const s=await this.apiClient.get(`/api/projects/${o}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),n=(s?.data||s||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title})))
133
- 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:r,sessionId:o,variables:s,timing:n}=e.body
134
- if(!r)return void t.status(400).json({success:!1,error:"eventType is required"})
135
- const i=await this.hookExecutor.processEvent({eventType:r,sessionId:o||"",variables:s||{},timing:n})
136
- 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.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 r=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
137
- try{if(!c.default.existsSync(r))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
138
- const e=c.default.readFileSync(r,"utf-8"),o=JSON.parse(e)
139
- t.json({port:o.port,pid:o.pid,startedAt:o.startedAt,baseUrl:"http://localhost:"+o.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,t){try{const e=this.readProjectConfig()
126
+ 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)
127
+ 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
128
+ const s=(0,i.createMetricsCollector)(!1)
129
+ 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)()
130
+ 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
131
+ if(!r)return void t.status(400).json({success:!1,error:"No project"})
132
+ 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})))
133
+ 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
134
+ if(!s)return void t.status(400).json({success:!1,error:"eventType is required"})
135
+ const i=await this.hookExecutor.processEvent({eventType:s,sessionId:r||"",variables:o||{},timing:n})
136
+ 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")
137
+ try{if(!c.default.existsSync(s))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
138
+ const e=c.default.readFileSync(s,"utf-8"),r=JSON.parse(e)
139
+ 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,t){try{const e=this.readProjectConfig()
140
140
  if(!e?.id)return void t.status(400).json({success:!1,error:"No project configuration found"})
141
141
  this.logger.info("Syncing agents from remote",{projectId:e.id})
142
- const r=await this.configurer.fetchAgentsFromRemote(e.id)
143
- if(!r||0===r.length)return void t.json({success:!0,message:"No agents to sync",count:0})
144
- const o=await this.configurer.fetchProjectFromRemote(e.id)
145
- this.logger.debug("Fetched project for global prompt",{projectId:e.id,hasProject:!!o,hasGlobalSystemPrompt:!!o?.metadata?.globalSystemPrompt})
146
- const s=o?.metadata?.globalSystemPrompt
147
- s?this.logger.info("Using global system prompt from project metadata",{projectId:e.id,promptLength:s.length}):this.logger.debug("No global system prompt found in project metadata")
142
+ const s=await this.configurer.fetchAgentsFromRemote(e.id)
143
+ if(!s||0===s.length)return void t.json({success:!0,message:"No agents to sync",count:0})
144
+ const r=await this.configurer.fetchProjectFromRemote(e.id)
145
+ this.logger.debug("Fetched project for global prompt",{projectId:e.id,hasProject:!!r,hasGlobalSystemPrompt:!!r?.metadata?.globalSystemPrompt})
146
+ const o=r?.metadata?.globalSystemPrompt
147
+ o?this.logger.info("Using global system prompt from project metadata",{projectId:e.id,promptLength:o.length}):this.logger.debug("No global system prompt found in project metadata")
148
148
  const n=a.default.join(this.options.projectPath,".opencode","agent")
149
149
  await c.default.promises.mkdir(n,{recursive:!0})
150
150
  let i=0
151
- for(const e of r){if(!e.enabled)continue
152
- const t="alchemy",r=this.configurer.generateAgentMarkdown(e,t,"anthropic/claude-sonnet-4-20250514",s),o=a.default.join(n,e.name+".md")
153
- await c.default.promises.writeFile(o,r,"utf8"),i++,this.logger.info("Synced agent file",{name:e.name,path:o,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
154
- if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t),n=o?.metadata?.bashToolAgents||{}
155
- 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])
156
- 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 l=!1
151
+ for(const e of s){if(!e.enabled)continue
152
+ const t="alchemy",s=this.configurer.generateAgentMarkdown(e,t,"anthropic/claude-sonnet-4-20250514",o),r=a.default.join(n,e.name+".md")
153
+ await c.default.promises.writeFile(r,s,"utf8"),i++,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")
154
+ if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),o=JSON.parse(t),n=r?.metadata?.bashToolAgents||{}
155
+ if(o.agent){for(const e of s)e.enabled&&o.agent[e.name]&&e.name in n&&(o.agent[e.name].tools.bash=!!n[e.name])
156
+ c.default.writeFileSync(e,JSON.stringify(o,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let l=!1
157
157
  try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
158
- if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
159
- 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 o=(0,m.spawn)("opencode",["serve"],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),s=new Promise((e=>{let t=""
160
- const r=setTimeout((()=>{this.logger.warn("Timeout waiting for OpenCode port, killing process")
161
- try{o.kill("SIGTERM")}catch{}e(null)}),1e4)
162
- o.stdout?.on("data",(s=>{t+=s.toString()
158
+ if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
159
+ if(s.pid)try{process.kill(s.pid,"SIGTERM"),this.logger.info("Killed existing OpenCode process",{pid:s.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 r=(0,y.spawn)("opencode",["serve"],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),o=new Promise((e=>{let t=""
160
+ const s=setTimeout((()=>{this.logger.warn("Timeout waiting for OpenCode port, killing process")
161
+ try{r.kill("SIGTERM")}catch{}e(null)}),1e4)
162
+ r.stdout?.on("data",(o=>{t+=o.toString()
163
163
  const n=t.match(/listening on http:\/\/[^:]+:(\d+)/)
164
- n&&(clearTimeout(r),o.unref(),e({port:parseInt(n[1],10),pid:o.pid}))})),o.on("error",(()=>{clearTimeout(r)
165
- try{o.kill("SIGTERM")}catch{}e(null)}))})),n=await s
166
- n&&(this.trackChildPid(n.pid),c.default.writeFileSync(e,JSON.stringify({port:n.port,pid:n.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:n.port,pid:n.pid}),this.setOpenCodePort(n.port),l=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,h.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${i} agent(s) to local files${l?" - OpenCode reloading":""}`,count:i,restarted:l})}catch(e){this.logger.error("Failed to sync agents",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGenerateJournal(e,t){try{const{requestId:r}=e.body
167
- if(!r)return void t.status(400).json({success:!1,error:"requestId is required"})
168
- if(!/^req\d+$/.test(r))return void t.status(400).json({success:!1,error:"Invalid requestId format"})
169
- this.logger.info("Generating journal for request",{requestId:r})
170
- 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.`,s=(0,m.spawn)("opencode",["run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
171
- s.pid&&this.trackChildPid(s.pid),s.unref(),t.json({success:!0,message:"Journal generation started for "+r})}catch(e){this.logger.error("Failed to generate journal",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleEventHooksProxy(e,t){try{const{eventType:r}=e.params,{sessionId:o}=e.query,s=this.projectId
164
+ n&&(clearTimeout(s),r.unref(),e({port:parseInt(n[1],10),pid:r.pid}))})),r.on("error",(()=>{clearTimeout(s)
165
+ try{r.kill("SIGTERM")}catch{}e(null)}))})),n=await o
166
+ n&&(this.trackChildPid(n.pid),c.default.writeFileSync(e,JSON.stringify({port:n.port,pid:n.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:n.port,pid:n.pid}),this.setOpenCodePort(n.port),l=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,g.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${i} agent(s) to local files${l?" - OpenCode reloading":""}`,count:i,restarted:l})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,t){try{const{requestId:s}=e.body
167
+ if(!s)return void t.status(400).json({success:!1,error:"requestId is required"})
168
+ if(!/^req\d+$/.test(s))return void t.status(400).json({success:!1,error:"Invalid requestId format"})
169
+ this.logger.info("Generating journal for request",{requestId:s})
170
+ const r=`Generate a journal entry for request ${s}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${s}\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-${s}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,o=(0,y.spawn)("opencode",["run","--agent","journal",r],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
171
+ o.pid&&this.trackChildPid(o.pid),o.unref(),t.json({success:!0,message:"Journal generation started for "+s})}catch(e){this.logger.error("Failed to generate journal",{error:(0,g.getErrorMessage)(e)}),t.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
172
+ if(!o)return void t.status(400).json({success:!1,error:"Project not registered"})
173
+ this.logger.info("Proxying event hooks request",{projectId:o,eventType:s,sessionId:r})
174
+ const n=r?"?sessionId="+encodeURIComponent(r):"",i=await this.apiClient.get(`/api/projects/${o}/event-hooks/by-event/${s}${n}`)
175
+ 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
176
+ if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
177
+ const{hookId:r,hookName:o,eventType:n,executionMode:i,agent:a}=e.body
178
+ this.logger.debug("Proxying hook trigger notification",{projectId:s,hookName:o,eventType:n})
179
+ const c=await this.apiClient.post(`/api/projects/${s}/hook-triggers`,{hookId:r,hookName:o,eventType:n,executionMode:i,agent:a})
180
+ 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()
181
+ if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
182
+ const{sessionId:r,hookId:o}=e.params,n=await this.apiClient.get(`/api/projects/${s.id}/hook-iterations/${r}/${o}`)
183
+ 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()
184
+ if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
185
+ const{sessionId:r,hookId:o}=e.params,n=await this.apiClient.post(`/api/projects/${s.id}/hook-iterations/${r}/${o}/increment`,{})
186
+ 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
172
187
  if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
173
- this.logger.info("Proxying event hooks request",{projectId:s,eventType:r,sessionId:o})
174
- const n=o?"?sessionId="+encodeURIComponent(o):"",i=await this.apiClient.get(`/api/projects/${s}/event-hooks/by-event/${r}${n}`)
175
- 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 r=this.projectId
176
- if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
177
- const{hookId:o,hookName:s,eventType:n,executionMode:i,agent:a}=e.body
178
- this.logger.debug("Proxying hook trigger notification",{projectId:r,hookName:s,eventType:n})
179
- const c=await this.apiClient.post(`/api/projects/${r}/hook-triggers`,{hookId:o,hookName:s,eventType:n,executionMode:i,agent:a})
180
- 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 r=this.readProjectConfig()
181
- if(!r?.id)return void t.status(400).json({success:!1,error:"No project configured"})
182
- const{sessionId:o,hookId:s}=e.params,n=await this.apiClient.get(`/api/projects/${r.id}/hook-iterations/${o}/${s}`)
183
- 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 r=this.readProjectConfig()
184
- if(!r?.id)return void t.status(400).json({success:!1,error:"No project configured"})
185
- const{sessionId:o,hookId:s}=e.params,n=await this.apiClient.post(`/api/projects/${r.id}/hook-iterations/${o}/${s}/increment`,{})
186
- 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 r=this.projectId
187
- if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
188
- const{sessionId:o,requestId:s,summary:n,status:i}=e.body
189
- if(!o)return void t.status(400).json({success:!1,error:"sessionId is required"})
190
- this.logger.info("Direct report creation",{sessionId:o,requestId:s,status:i})
188
+ const{sessionId:r,requestId:o,summary:n,status:i}=e.body
189
+ if(!r)return void t.status(400).json({success:!1,error:"sessionId is required"})
190
+ this.logger.info("Direct report creation",{sessionId:r,requestId:o,status:i})
191
191
  const a=[]
192
- try{const e=await this.apiClient.get(`/api/projects/${r}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`),t=e?.data||e||[]
193
- let s=o
194
- for(const e of t)a.push({ancestorId:e.externalSessionId,childId:s}),s=e.externalSessionId}catch(e){this.logger.warn("Failed to fetch ancestor chain",{sessionId:o,error:(0,h.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
192
+ try{const e=await this.apiClient.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),t=e?.data||e||[]
193
+ let o=r
194
+ 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"})
195
195
  let c,l=n
196
- if(!l&&s)try{const e=await this.apiClient.get(`/api/projects/${r}/requests/${s}`),t=e?.data||e
196
+ if(!l&&o)try{const e=await this.apiClient.get(`/api/projects/${s}/requests/${o}`),t=e?.data||e
197
197
  c=t?.title
198
- const o=t?.completionSummary
199
- o?l=o:c&&(l="Done: "+c)}catch(e){this.logger.debug("Could not fetch request details for summary",{requestId:s,error:(0,h.getErrorMessage)(e)})}l||(l="Completed request "+(s||"unknown"))
200
- const d=s||o
198
+ const r=t?.completionSummary
199
+ 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"))
200
+ const d=o||r
201
201
  if(this.directReportDedup.size>=1e3){const e=this.directReportDedup.keys().next().value
202
202
  void 0!==e&&this.directReportDedup.delete(e)}this.directReportDedup.has(d)||this.directReportDedup.set(d,new Set)
203
- 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 o=await this.apiClient.post(`/api/projects/${r}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),s=o?.data||o
204
- return u.add(e),this.logger.info("Direct report created for ancestor",{messageCode:s?.messageCode,ancestorId:e,fromChild:t}),{externalSessionId:e,messageCode:s?.messageCode,fromSessionId:t}}))),f=[]
203
+ 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
204
+ 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=[]
205
205
  for(const e of p)"fulfilled"===e.status&&f.push(e.value)
206
- for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,h.getErrorMessage)(e.reason)})
207
- if(setTimeout((()=>{this.directReportDedup.delete(d)}),3e5),s)try{const e=await this.apiClient.get(`/api/projects/${r}/session-request-associations/by-session/${encodeURIComponent(o)}?activeOnly=true`),t=e?.data||e||[],s=(await Promise.allSettled(t.map((e=>this.apiClient.post(`/api/projects/${r}/session-request-associations/end`,{sessionId:o,requestLocalId:e.requestLocalId}))))).filter((e=>"rejected"===e.status)).length
208
- this.logger.info("Released request associations",{sessionId:o,count:t.length,failCount:s})}catch(e){this.logger.warn("Failed to release associations",{sessionId:o,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:r}=e.params,o=this.readProjectConfig()
209
- if(o?.id)try{const e=await this.apiClient.get(`/api/projects/${o.id}/cli-sessions/external/opencode/${encodeURIComponent(r)}`),s=e?.data||e
210
- if(this.logger.info("Agent type DB lookup",{sessionId:r,dbAgentType:s?.agentType||null,dbId:s?.id||s?.localId||null}),s?.agentType)return void t.json({success:!0,agentType:s.agentType,source:"db"})}catch(e){this.logger.info("Agent type DB lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:r},5e3)
211
- if(this.logger.info("Agent type bridge lookup",{sessionId:r,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:r,error:(0,h.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:r}),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:r}=e.params,o=this.readProjectConfig()
212
- if(o?.id)try{const e=await this.apiClient.get(`/api/projects/${o.id}/cli-sessions/resolve/${encodeURIComponent(r)}`),s=e?.data?.parent?.externalSessionId||""
213
- return void t.json({success:!0,parentSessionId:s})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:r,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 r=this.getOpenCodeUrl()
214
- if(!r)return void t.status(502).json({error:"OpenCode not running"})
215
- const o=`${r}/${e.path.replace(/^\//,"")}${e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):""}`,s=e.headers.accept?.includes("text/event-stream")
216
- if(s){const r=new AbortController,s=()=>{this.proxyAbortControllers.delete(r)},n=(async()=>{try{const e=await fetch(o,{headers:{Accept:"text/event-stream"},signal:r.signal})
217
- if(!e.ok||!e.body)return s(),void t.status(e.status).json({error:"SSE upstream error"})
206
+ for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,g.getErrorMessage)(e.reason)})
207
+ 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
208
+ 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()
209
+ if(r?.id)try{const e=await this.apiClient.get(`/api/projects/${r.id}/cli-sessions/external/opencode/${encodeURIComponent(s)}`),o=e?.data||e
210
+ 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)
211
+ 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()
212
+ if(r?.id)try{const e=await this.apiClient.get(`/api/projects/${r.id}/cli-sessions/resolve/${encodeURIComponent(s)}`),o=e?.data?.parent?.externalSessionId||""
213
+ 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()
214
+ if(!s)return void t.status(502).json({error:"OpenCode not running"})
215
+ const r=`${s}/${e.path.replace(/^\//,"")}${e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):""}`,o=e.headers.accept?.includes("text/event-stream")
216
+ if(o){const s=new AbortController,o=()=>{this.proxyAbortControllers.delete(s)},n=(async()=>{try{const e=await fetch(r,{headers:{Accept:"text/event-stream"},signal:s.signal})
217
+ if(!e.ok||!e.body)return o(),void t.status(e.status).json({error:"SSE upstream error"})
218
218
  t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"})
219
219
  const n=e.body.getReader()
220
- try{for(;;){const{done:e,value:r}=await n.read()
220
+ try{for(;;){const{done:e,value:s}=await n.read()
221
221
  if(e)break
222
- t.write(r)}}catch{}t.end()}catch{t.headersSent||t.status(502).json({error:"SSE upstream error"})}finally{s()}})()
223
- return this.proxyAbortControllers.set(r,n),void e.on("close",(()=>{try{r.abort()}catch{}}))}const n={"Content-Type":e.headers["content-type"]||"application/json"},i={method:e.method,headers:n}
222
+ t.write(s)}}catch{}t.end()}catch{t.headersSent||t.status(502).json({error:"SSE upstream error"})}finally{o()}})()
223
+ return this.proxyAbortControllers.set(s,n),void e.on("close",(()=>{try{s.abort()}catch{}}))}const n={"Content-Type":e.headers["content-type"]||"application/json"},i={method:e.method,headers:n}
224
224
  "GET"!==e.method&&"HEAD"!==e.method&&e.body&&(i.body=JSON.stringify(e.body))
225
- const a=await fetch(o,i),c=a.headers.get("content-type")||"",l=c.includes("application/json")?await a.json():await a.text()
226
- t.status(a.status),c&&t.setHeader("Content-Type",c),"string"==typeof l?t.send(l):t.json(l)}catch(r){this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,h.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,h.getErrorMessage)(r)})}}async handleGetAgentByName(e,t){try{const r=this.readProjectConfig()
227
- if(!r?.id)return void t.status(400).json({success:!1,error:"No project configured"})
228
- const{agentName:o}=e.params,s=await this.apiClient.get(`/api/projects/${r.id}/agents`),n=(s?.data||s||[]).find((e=>e.name===o))
229
- if(!n)return void t.status(404).json({success:!1,error:`Agent '${o}' not found`})
230
- 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<y.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
225
+ const a=await fetch(r,i),c=a.headers.get("content-type")||"",l=c.includes("application/json")?await a.json():await a.text()
226
+ t.status(a.status),c&&t.setHeader("Content-Type",c),"string"==typeof l?t.send(l):t.json(l)}catch(s){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()
227
+ if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
228
+ const{agentName:r}=e.params,o=await this.apiClient.get(`/api/projects/${s.id}/agents`),n=(o?.data||o||[]).find((e=>e.name===r))
229
+ if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
230
+ 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
231
231
  const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
232
232
  try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
233
- const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
234
- return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}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
233
+ const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
234
+ 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
235
235
  const t="http://127.0.0.1:"+e
236
- this.openCodeUrlCache={url:t,timestamp:Date.now()},this.logger.info("OpenCode port set directly",{port:e})}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
236
+ 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)})
237
+ if(!t.ok)return
238
+ const s=await t.json(),r=s?.default||{},[o,n]=Object.entries(r)[0]||[]
239
+ if(!o||!n)return
240
+ const i=`${o}/${n}`
241
+ 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
237
242
  if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
238
243
  const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
239
244
  try{if(!c.default.existsSync(e))return null
240
- const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
241
- if(r.port){const e="http://127.0.0.1:"+r.port
242
- return this.openCodeUrlCache={url:e,timestamp:Date.now()},e}}catch{}return null}async handleSessionCommand(e,t){const r=this.getOpenCodeUrl()
243
- if(!r)return{success:!1,error:"OpenCode server not available. Is 'opencode serve' running?"}
244
- try{switch(e){case"list-sessions":{const[e,o]=await Promise.all([fetch(r+"/session"),fetch(r+"/session/status")])
245
+ const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
246
+ if(s.port){const e="http://127.0.0.1:"+s.port
247
+ return this.openCodeUrlCache={url:e,timestamp:Date.now()},e}}catch{}return null}async handleSessionCommand(e,t){const s=this.getOpenCodeUrl()
248
+ if(!s)return{success:!1,error:"OpenCode server not available. Is 'opencode serve' running?"}
249
+ try{switch(e){case"list-sessions":{const[e,r]=await Promise.all([fetch(s+"/session"),fetch(s+"/session/status")])
245
250
  if(!e.ok)return{success:!1,error:"Failed to list sessions: "+e.status}
246
- const s=await e.json(),n=o.ok?await o.json():{}
251
+ const o=await e.json(),n=r.ok?await r.json():{}
247
252
  let i={}
248
253
  if(this.projectId)try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions`,{limit:200}),t=e?.data||e||[]
249
- for(const e of t)e.externalSessionId&&e.agentType&&(i[e.externalSessionId]=e.agentType)}catch{}let a=s.map((e=>({id:e.id,title:e.title||"(untitled)",parentId:e.parentID||void 0,status:n[e.id]?.type||"idle",agentType:i[e.id]||void 0,time:e.time})))
254
+ for(const e of t)e.externalSessionId&&e.agentType&&(i[e.externalSessionId]=e.agentType)}catch{}let a=o.map((e=>({id:e.id,title:e.title||"(untitled)",parentId:e.parentID||void 0,status:n[e.id]?.type||"idle",agentType:i[e.id]||void 0,time:e.time})))
250
255
  if(t.status&&(a=a.filter((e=>e.status===t.status))),t.agentType&&(a=a.filter((e=>e.agentType===t.agentType))),t.search){const e=t.search.toLowerCase()
251
256
  a=a.filter((t=>t.title?.toLowerCase().includes(e)))}return t.limit&&(a=a.slice(0,t.limit)),{success:!0,data:a}}case"get-messages":{const e=t.sessionId
252
257
  if(!e)return{success:!1,error:"sessionId is required"}
253
- const o=t.limit||50,s=await fetch(`${r}/session/${e}/message?limit=${o}`)
254
- return s.ok?{success:!0,data:(await s.json()).map((e=>({role:e.info.role,time:e.info.time?.created,parts:e.parts||[]})))}:404===s.status?{success:!1,error:"Session not found"}:{success:!1,error:"Failed to get messages: "+s.status}}case"send-message":{const e=t.sessionId
258
+ const r=t.limit||50,o=await fetch(`${s}/session/${e}/message?limit=${r}`)
259
+ return o.ok?{success:!0,data:(await o.json()).map((e=>({role:e.info.role,time:e.info.time?.created,parts:e.parts||[]})))}:404===o.status?{success:!1,error:"Session not found"}:{success:!1,error:"Failed to get messages: "+o.status}}case"send-message":{const e=t.sessionId
255
260
  if(!e)return{success:!1,error:"sessionId is required"}
256
261
  if(!t.message)return{success:!1,error:"message is required"}
257
- const o={parts:[{type:"text",text:t.message}]}
258
- if(t.agent&&(o.agent=t.agent),t.variant&&(o.variant=t.variant),t.system&&(o.system=t.system),this.cachedDefaultModel){const[e,...t]=this.cachedDefaultModel.split("/")
259
- o.model={providerID:e,modelID:t.join("/")}}const s=await fetch(`${r}/session/${e}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})
260
- if(!s.ok){const e=await s.text().catch((()=>"Unknown error"))
261
- return{success:!1,error:`Failed to send message: ${s.status} ${e}`}}return{success:!0,data:{messageId:null}}}case"create-session":{const e={}
262
+ const r={parts:[{type:"text",text:t.message}]}
263
+ if(t.agent&&(r.agent=t.agent),t.variant&&(r.variant=t.variant),t.system&&(r.system=t.system),this.cachedDefaultModel){const[e,...t]=this.cachedDefaultModel.split("/")
264
+ r.model={providerID:e,modelID:t.join("/")}}const o=await fetch(`${s}/session/${e}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)})
265
+ if(!o.ok){const e=await o.text().catch((()=>"Unknown error"))
266
+ return{success:!1,error:`Failed to send message: ${o.status} ${e}`}}return{success:!0,data:{messageId:null}}}case"create-session":{const e={}
262
267
  t.parentId&&(e.parentID=t.parentId),t.title&&(e.title=t.title)
263
- const o=await fetch(r+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})
264
- if(!o.ok)return{success:!1,error:"Failed to create session: "+o.status}
265
- const s=await o.json()
266
- return{success:!0,data:{id:s.id,title:s.title||t.title||"(untitled)",status:"idle",agentType:t.agent||void 0}}}case"get-session-status":{const e=t.sessionId
268
+ const r=await fetch(s+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})
269
+ if(!r.ok)return{success:!1,error:"Failed to create session: "+r.status}
270
+ const o=await r.json()
271
+ return{success:!0,data:{id:o.id,title:o.title||t.title||"(untitled)",status:"idle",agentType:t.agent||void 0}}}case"get-session-status":{const e=t.sessionId
267
272
  if(!e)return{success:!1,error:"sessionId is required"}
268
- const[o,s]=await Promise.all([fetch(r+"/session/status"),fetch(r+"/session")]),n=o.ok?await o.json():{}
273
+ const[r,o]=await Promise.all([fetch(s+"/session/status"),fetch(s+"/session")]),n=r.ok?await r.json():{}
269
274
  let i,a
270
- if(s.ok){const t=(await s.json()).find((t=>t.id===e))
271
- i=t?.title}if(this.projectId)try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),r=t?.data||t
272
- r?.agentType&&(a=r.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()
275
+ if(o.ok){const t=(await o.json()).find((t=>t.id===e))
276
+ 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
277
+ 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()
273
278
  if(!e?.id)throw Error("No project configuration found")
274
279
  this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId)
275
280
  const t={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:l.default.hostname(),platform:process.platform}
276
281
  this.logger.info("Registering with remote",{projectId:this.projectId})
277
- const r=await this.apiClient.post("/api/servers/register",t)
278
- r.success&&r.data?.serverId?(this.serverId=r.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})):this.logger.warn("Registration failed, continuing without remote connection",{error:r.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((()=>{}))}async connectToOpenCodeSSE(){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
282
+ const s=await this.apiClient.post("/api/servers/register",t)
283
+ 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")
279
284
  if(!c.default.existsSync(e))return this.logger.debug("SSE forwarder: opencode.json not found, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
280
285
  let t
281
- try{t=JSON.parse(c.default.readFileSync(e,"utf-8")).port}catch{return void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)}const r=`http://localhost:${t}/event`
282
- this.logger.info("SSE forwarder connecting",{url:r}),this.sseAbortController=new AbortController
283
- try{const e=await fetch(r,{headers:{Accept:"text/event-stream"},signal:this.sseAbortController.signal})
286
+ try{t=JSON.parse(c.default.readFileSync(e,"utf-8")).port}catch{return void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)}const s=`http://localhost:${t}/event`
287
+ this.logger.info("SSE forwarder connecting",{url:s}),this.sseAbortController=new AbortController
288
+ try{const e=await fetch(s,{headers:{Accept:"text/event-stream"},signal:this.sseAbortController.signal})
284
289
  if(!e.ok||!e.body)return this.logger.warn("SSE forwarder: upstream error",{status:e.status}),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
285
- const t=e.body.getReader(),o=new TextDecoder
286
- let s="";(async()=>{try{for(;;){const{done:e,value:r}=await t.read()
290
+ const t=e.body.getReader(),r=new TextDecoder
291
+ let o="";(async()=>{try{for(;;){const{done:e,value:s}=await t.read()
287
292
  if(e)break
288
- s+=o.decode(r,{stream:!0})
289
- const n=s.split("\n")
290
- s=n.pop()||""
293
+ o+=r.decode(s,{stream:!0})
294
+ const n=o.split("\n")
295
+ o=n.pop()||""
291
296
  for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6))
292
- 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,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
297
+ 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
293
298
  if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
294
299
  if(!t)return
295
300
  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||""
@@ -297,614 +302,669 @@ if(!t)return
297
302
  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
298
303
  if(!this.hasRemoteEventConsumers)return void(this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500))
299
304
  const e=this.sseEventBuffer.splice(0)
300
- 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
301
- 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=await this.apiClient.get(`/api/projects/${this.projectId}/actions/pending?timeout=30000`)
302
- if(e.success&&e.data&&void 0!==e.data.hasRemoteEventConsumers&&(this.hasRemoteEventConsumers=e.data.hasRemoteEventConsumers),e.success&&e.data?.settings){const{defaultModel:t,autoJournal:r}=e.data.settings
303
- if(void 0!==t&&(this.cachedDefaultModel=t,this.hookExecutor.setDefaultModel(t)),void 0!==r&&r!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:r})
305
+ 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
306
+ 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"}
307
+ this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
308
+ const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
309
+ t["X-Service-Secret"]=s
310
+ const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(35e3)})
311
+ if(!r.ok)throw Error("Poll failed with status "+r.status)
312
+ const o=await r.json(),n={success:!0,data:o.data||o}
313
+ 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
314
+ 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})
304
315
  const e=new p.ProjectConfigService(this.logger)
305
- await e.saveMultiAgentConfig(this.options.projectPath,void 0,r),this.cachedAutoJournal=r}}if(e.success&&e.data?.actions?.length>0){const t=[],r=[]
306
- for(const o of e.data.actions)"opencode-proxy"===o.type||"run-tests"===o.type?t.push(o):r.push(o)
307
- for(const e of t)this.processAction(e).catch((t=>this.logger.debug("Proxy action failed",{actionId:e.id,error:(0,h.getErrorMessage)(t)})))
308
- if(r.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
309
- try{for(const e of r)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)
310
- if(e.success&&e.data?.metadata){const{autoJournal:t,defaultModel:r}=e.data.metadata
311
- if(void 0!==r&&(this.cachedDefaultModel=r,this.hookExecutor.setDefaultModel(r)),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed, syncing to local config",{oldValue:this.cachedAutoJournal,newValue:t})
316
+ await e.saveMultiAgentConfig(this.options.projectPath,void 0,t),this.cachedAutoJournal=t}}if(n.success&&n.data?.actions?.length>0){const e=[],t=[]
317
+ for(const s of n.data.actions)"opencode-proxy"===s.type||"run-tests"===s.type?e.push(s):t.push(s)
318
+ for(const t of e)this.processAction(t).catch((e=>this.logger.debug("Proxy action failed",{actionId:t.id,error:(0,g.getErrorMessage)(e)})))
319
+ if(t.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
320
+ 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)
321
+ if(e.success&&e.data?.metadata){const{autoJournal:t,defaultModel:s}=e.data.metadata
322
+ 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})
312
323
  const e=new p.ProjectConfigService(this.logger)
313
- await e.saveMultiAgentConfig(this.options.projectPath,void 0,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})
324
+ 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})
314
325
  const t=Date.now()
315
- try{const r={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e)},o=await(0,d.executeAction)(e.type,e.payload||{},r)
316
- await this.apiClient.post(`/api/actions/${e.id}/result`,{success:o.success,result:o.data,error:o.message||o.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:o.success,duration:Date.now()-t})}catch(r){const o=(0,h.getErrorMessage)(r)
317
- await this.apiClient.post(`/api/actions/${e.id}/result`,{success:!1,error:o,duration:Date.now()-t}),this.logger.error("Action failed",{actionId:e.id,type:e.type,error:o})}}async handleCLIBridgeCommand(e,t){try{const{action:r,params:o,timeout:s}=e.body
318
- if(!r)return void t.status(400).json({success:!1,error:"action is required"})
319
- if(["list-notifications","get-notification","respond-to-notification"].includes(r)){const e=await this.handleNotificationCommand(r,o||{})
320
- return void t.json(e)}if(["list-sessions","get-messages","send-message","create-session","get-session-status"].includes(r)){const e=await this.handleSessionCommand(r,o||{})
321
- return void t.json(e)}if(["list-requests","get-request"].includes(r)){const e=await this.handleRequestCommand(r,o||{})
322
- return void t.json(e)}const n=await this.cliBridge.sendCommand(r,o||{},s)
323
- 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"}
324
- const r=this.projectId
326
+ 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)
327
+ 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)
328
+ 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
329
+ if(!s)return void t.status(400).json({success:!1,error:"action is required"})
330
+ if(["list-notifications","get-notification","respond-to-notification"].includes(s)){const e=await this.handleNotificationCommand(s,r||{})
331
+ 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||{})
332
+ return void t.json(e)}if(["list-requests","get-request"].includes(s)){const e=await this.handleRequestCommand(s,r||{})
333
+ return void t.json(e)}const n=await this.cliBridge.sendCommand(s,r||{},o)
334
+ 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"}
335
+ const s=this.projectId
325
336
  try{switch(e){case"list-notifications":{const e={limit:t.limit||30}
326
- 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/${r}/session-messages`,e)).data}}case"get-notification":return t.messageCode?{success:!0,data:(await this.apiClient.get(`/api/projects/${r}/session-messages/by-code/${encodeURIComponent(t.messageCode)}`)).data}:{success:!1,error:"messageCode is required"}
337
+ 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"}
327
338
  case"respond-to-notification":{if(!t.messageCode)return{success:!1,error:"messageCode is required"}
328
339
  const e=t.messageCode
329
- return{success:!0,data:(await this.apiClient.post(`/api/projects/${r}/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"}
330
- const r=this.projectId
340
+ 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"}
341
+ const s=this.projectId
331
342
  try{switch(e){case"list-requests":{const e={}
332
- 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/${r}/requests`,e)).data}}case"get-request":{if(!t.requestId)return{success:!1,error:"requestId is required"}
333
- const e=encodeURIComponent(t.requestId),[o,s]=await Promise.all([this.apiClient.get(`/api/projects/${r}/requests/${e}`),this.apiClient.get(`/api/projects/${r}/tasks`,{requestId:t.requestId})])
334
- return{success:!0,data:{...o.data,tasks:s.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})
343
+ 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"}
344
+ 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})])
345
+ 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})
335
346
  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)}}))
336
347
  break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
337
- 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)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder()}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()
348
+ 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"}
349
+ this.apiClient.authToken&&(t.Authorization="Bearer "+this.apiClient.authToken)
350
+ const s=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
351
+ t["X-Service-Secret"]=s
352
+ const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
353
+ if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
354
+ 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()
338
355
  try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
339
- if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
340
- if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,h.getErrorMessage)(e)})}}}}catch{}}async stop(){this.logger.info("Stopping AlchemyActionServer...")
341
- 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.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)
342
- this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=y,y.PROJECT_CONFIG_TTL=3e4},2782(e,exports,t){function r(){return"true"===process.env.ENABLE_DEBUG_TOOLS?.toLowerCase()}function o(e={}){const t={...function(e){const t={}
343
- if(!r())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.logToFile=!1,t
356
+ if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t)
357
+ 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()??[]
358
+ t.json({success:!0,data:s})}handleTestInjectStuckSession(e,t){if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
359
+ const{sessionId:s,title:r}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(s,r)
360
+ 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
361
+ if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
362
+ const r=await this.stuckSessionWatchdog.retrySession(s)
363
+ r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}async stop(){this.logger.info("Stopping AlchemyActionServer...")
364
+ 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)
365
+ 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},2782(e,exports,t){function s(){return"true"===process.env.ENABLE_DEBUG_TOOLS?.toLowerCase()}function r(e={}){const t={...function(e){const t={}
366
+ if(!s())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.logToFile=!1,t
344
367
  if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
345
- const r=e||process.cwd()
346
- t.logFilePath=i.default.join(r,"logs","debug.log")}const o=process.env.DEBUG_LEVEL?.toLowerCase()
347
- return o&&Object.values(c.LogLevel).includes(o)&&(t.level=o),t}(e.baseDir),...e},{level:s=c.LogLevel.INFO,serviceName:h="mcp-service",logToFile:p=!1,logFilePath:f="./logs/app.log",console=!0}=t,m=[]
368
+ const s=e||process.cwd()
369
+ t.logFilePath=i.default.join(s,"logs","debug.log")}const r=process.env.DEBUG_LEVEL?.toLowerCase()
370
+ 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=[]
348
371
  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)
349
372
  a.default.existsSync(t)||a.default.mkdirSync(t,{recursive:!0})}(f)
350
- const e=s===c.LogLevel.DEBUG&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()
351
- m.push(new n.default.transports.File({filename:f,format:e?g:u}))}const y=n.default.createLogger({level:s,defaultMeta:{service:h},transports:m})
352
- return s===c.LogLevel.DEBUG&&p&&r()&&y.info("Enhanced logging initialized",{level:s,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 r=y.child(e)
353
- return{error(e,t){r.error(e,t)},warn(e,t){r.warn(e,t)},info(e,t){r.info(e,t)},debug(e,t){r.debug(e,t)},child(e){return o({...t,serviceName:`${h}:${e.component||"nested"}`})}}}}}var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
354
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.createLogger=o,exports.createEnvLogger=function(e={}){return o(e)},exports.isDebugToolsEnabled=r,exports.isDebugLoggingEnabled=function(){return!!r()&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()},exports.getDebugLevelFromEnv=function(){if(!r())return c.LogLevel.INFO
373
+ const e=o===c.LogLevel.DEBUG&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()
374
+ 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})
375
+ 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)
376
+ 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}}
377
+ 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
355
378
  const e=process.env.DEBUG_LEVEL?.toLowerCase()
356
379
  return e&&Object.values(c.LogLevel).includes(e)?e:c.LogLevel.INFO}
357
- const n=s(t(5124)),i=s(t(6928)),a=s(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:r,...o})=>`${r} [${e}]: ${t}${Object.keys(o).length?" "+JSON.stringify(o,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:r,service:o,component:s,...n})=>{const i=o?`[${o}]`:"",a=s?`[${s}]`:"",c=Object.keys(n).length?" "+JSON.stringify(n,null,2):""
358
- return`${r} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),h=o({logToFile:!1,console:!0,level:c.LogLevel.INFO})
359
- exports.default=h},3077(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookExecutor=void 0
360
- const r=t(8740),o=t(4627),s=t(4769)
361
- exports.HookExecutor=class{constructor(e,t,o,s,n){this.logger=e,this.cache=new r.HookCache(e,t,s),this.ctx={logger:e,projectId:t,projectPath:o,apiClient:s,cachedDefaultModel:n?.cachedDefaultModel,trackChildPid:n?.trackChildPid}}setDefaultModel(e){this.ctx.cachedDefaultModel=e}setProjectId(e){this.ctx.projectId=e,this.cache.setProjectId(e)}async processEvent(e){const{eventType:t,sessionId:r,variables:s,timing:n}=e,i={triggered:0,hooks:[]}
380
+ 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):""
381
+ return`${s} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),g=r({logToFile:!1,console:!0,level:c.LogLevel.INFO})
382
+ exports.default=g},2928(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.StuckSessionWatchdog=void 0
383
+ const s=t(8824)
384
+ 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"}
385
+ return this.stuckSessions.set(s,r),this.testInjectedIds.add(s),this.notifiedSessions.add(s),this.onStuckDetected?.(r),r}clearTestStuck(e){return!!this.testInjectedIds.has(e)&&(this.testInjectedIds.delete(e),this.stuckSessions.delete(e),this.notifiedSessions.delete(e),this.onStuckResolved?.(e),!0)}async check(){const e=this.getOpenCodeUrl()
386
+ if(e)try{const[t,r]=await Promise.all([fetch(e+"/session",{signal:AbortSignal.timeout(5e3)}),fetch(e+"/session/status",{signal:AbortSignal.timeout(5e3)})])
387
+ if(!t.ok||!r.ok)return
388
+ const o=await t.json(),n=await r.json(),i=o.filter((e=>"running"===n[e.id]?.type)),a=new Set
389
+ await Promise.all(i.map((async t=>{try{const s=await fetch(`${e}/session/${t.id}/message?limit=5`,{signal:AbortSignal.timeout(5e3)})
390
+ if(!s.ok)return
391
+ const r=[...await s.json()].reverse().find((e=>"assistant"===e.info.role))
392
+ if(!r)return
393
+ const o=r.info
394
+ if(o.time.completed)return
395
+ const n=o.tokens
396
+ if(n&&(n.output>0||n.reasoning>0))return
397
+ const i=r.parts||[]
398
+ if(i.some((e=>"tool"===e.type&&e.state&&("pending"===e.state.status||"running"===e.state.status))))return
399
+ if(i.some((e=>"text"===e.type&&e.text&&e.text.length>0)))return
400
+ const c=Date.now()-o.time.created
401
+ if(c<this.stuckThresholdMs)return
402
+ if(a.add(t.id),!this.stuckSessions.has(t.id)){const e={sessionId:t.id,messageId:o.id,detectedAt:Date.now(),retryCount:0,lastMessageCreated:o.time.created,sessionTitle:t.title}
403
+ this.stuckSessions.set(t.id,e),this.logger.warn("Session detected as stuck",{sessionId:t.id,title:t.title,messageAge:Math.round(c/1e3)}),e.retryCount<this.maxRetries?(this.logger.info("Auto-retrying stuck session",{sessionId:t.id,retryCount:e.retryCount}),this.retrySession(t.id).then((s=>{s.success||(this.logger.warn("Auto-retry failed",{sessionId:t.id,error:s.error}),this.notifiedSessions.has(t.id)||(this.notifiedSessions.add(t.id),this.onStuckDetected?.(e)))})).catch((()=>{}))):this.notifiedSessions.has(t.id)||(this.notifiedSessions.add(t.id),this.onStuckDetected?.(e))}}catch(e){this.logger.debug("Failed to check session for stuck state",{sessionId:t.id,error:(0,s.getErrorMessage)(e)})}})))
404
+ for(const[e]of this.stuckSessions)a.has(e)||this.testInjectedIds.has(e)||(this.stuckSessions.delete(e),this.notifiedSessions.delete(e),this.resolvedRecently.add(e),this.logger.info("Session no longer stuck",{sessionId:e}),this.onStuckResolved?.(e),setTimeout((()=>this.resolvedRecently.delete(e)),6e4))}catch(e){this.logger.debug("Stuck session check failed",{error:(0,s.getErrorMessage)(e)})}}async retrySession(e){const t=this.getOpenCodeUrl()
405
+ if(!t)return{success:!1,error:"OpenCode server not available"}
406
+ const r=this.stuckSessions.get(e)
407
+ if(!r)return{success:!1,error:"Session is not currently flagged as stuck"}
408
+ if(r.retryCount>=this.maxRetries)return{success:!1,error:`Max retries (${this.maxRetries}) exceeded for this session`}
409
+ try{this.logger.info("Retrying stuck session: aborting",{sessionId:e})
410
+ const s=await fetch(`${t}/session/${e}/abort`,{method:"POST",signal:AbortSignal.timeout(5e3)})
411
+ s.ok||this.logger.warn("Abort returned non-OK (may already be idle)",{sessionId:e,status:s.status}),await new Promise((e=>setTimeout(e,1e3))),this.logger.info("Retrying stuck session: sending continue",{sessionId:e})
412
+ const o=await fetch(`${t}/session/${e}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:"Continue"}]}),signal:AbortSignal.timeout(1e4)})
413
+ if(!o.ok){const e=await o.text().catch((()=>""))
414
+ return{success:!1,error:`Failed to re-send prompt: ${o.status} ${e}`}}return r.retryCount++,this.stuckSessions.delete(e),this.notifiedSessions.delete(e),this.logger.info("Stuck session retry sent",{sessionId:e,retryCount:r.retryCount}),{success:!0}}catch(e){return{success:!1,error:(0,s.getErrorMessage)(e)}}}}},3077(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookExecutor=void 0
415
+ const s=t(8740),r=t(4627),o=t(4769)
416
+ exports.HookExecutor=class{constructor(e,t,r,o,n){this.logger=e,this.cache=new s.HookCache(e,t,o),this.ctx={logger:e,projectId:t,projectPath:r,apiClient:o,cachedDefaultModel:n?.cachedDefaultModel,trackChildPid:n?.trackChildPid}}setDefaultModel(e){this.ctx.cachedDefaultModel=e}setProjectId(e){this.ctx.projectId=e,this.cache.setProjectId(e)}async processEvent(e){const{eventType:t,sessionId:s,variables:o,timing:n}=e,i={triggered:0,hooks:[]}
362
417
  if(!this.ctx.projectId)return this.logger.debug("No project ID, skipping hook event",{eventType:t}),i
363
- try{const e=await this.cache.getHooksForEvent(t,r)
418
+ try{const e=await this.cache.getHooksForEvent(t,s)
364
419
  if(0===e.length)return i
365
420
  const a=new Set,c=e.filter((e=>!a.has(e.id)&&(a.add(e.id),!0))),l=n||"after",d=c.filter((e=>(e.timing||"after")===l))
366
421
  if(0===d.length)return i
367
- const u={...s}
368
- if(r&&(!u.parentSessionId||!u.agentType))try{const e=await this.ctx.apiClient.get(`/api/projects/${this.ctx.projectId}/cli-sessions/external/opencode/${encodeURIComponent(r)}`),t=e?.data||e
369
- 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}`),r=e?.data||e
370
- r?.externalSessionId&&(u.parentSessionId=r.externalSessionId)}}catch(e){this.logger.warn("Failed to enrich hook variables with session context",{sessionId:r,error:e+""})}const g=d.filter((e=>(0,o.evaluateCondition)(e.condition,u)))
371
- if(0===g.length)return i
372
- for(const e of g){await this.executeHook(e,u,r),i.triggered++,i.hooks.push(e.name)
373
- const t=(0,o.determineEventToEmit)(e,u)
374
- t&&await this.processCustomEvent(t,{...u,sessionId:r||""},0)}"session_idle"!==t&&"session_created"!==t&&r&&this.cache.preWarm("session_idle",r)}catch(e){this.logger.error("Error processing hook event",{eventType:t,error:e+""})}return i}async executeHook(e,t,r){const n=(0,o.interpolatePrompt)(e.promptTemplate,t),i=t.requestId
375
- if(this.logger.info("Executing hook",{name:e.name,action:e.action,target:e.target,eventType:e.eventType,sessionId:r}),this.notifyHookTrigger(e).catch((()=>{})),"inject_prompt"===e.action)switch(e.target){case"same_session":default:await(0,s.executeSameSession)(n,r,this.ctx)
422
+ const u={...o}
423
+ 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
424
+ 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
425
+ 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)))
426
+ if(0===h.length)return i
427
+ for(const e of h){await this.executeHook(e,u,s),i.triggered++,i.hooks.push(e.name)
428
+ const t=(0,r.determineEventToEmit)(e,u)
429
+ 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
430
+ 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)
376
431
  break
377
- case"parent_session":case"ancestor_chain":await(0,s.executeInjectPrompt)(e,e.promptTemplate,r,t,this.ctx)}else await(0,s.executeBackground)(e,n,r,i,this.ctx)}async processCustomEvent(e,t,r){if(r>=10)return void this.logger.warn("Custom event chain depth exceeded",{eventName:e,chainDepth:r})
432
+ case"parent_session":case"ancestor_chain":await(0,o.executeInjectPrompt)(e,e.promptTemplate,s,t,this.ctx)}else await(0,o.executeBackground)(e,n,s,i,this.ctx)}async processCustomEvent(e,t,s){if(s>=10)return void this.logger.warn("Custom event chain depth exceeded",{eventName:e,chainDepth:s})
378
433
  const n=e.startsWith("custom:")?e:"custom:"+e,i=await this.cache.getHooksForEvent(n,t.sessionId)
379
434
  if(0===i.length)return
380
- const a={...t,chainDepth:r+1+"",triggerEvent:n},c=i.filter((e=>(0,o.evaluateCondition)(e.condition,a)))
381
- for(const e of c){const t=(0,o.interpolatePrompt)(e.promptTemplate,a),i=a.sessionId||""
382
- if(this.logger.info("Executing custom event hook",{name:e.name,event:n,chainDepth:r}),"inject_prompt"===e.action)switch(e.target){case"same_session":default:await(0,s.executeSameSession)(t,i,this.ctx)
435
+ const a={...t,chainDepth:s+1+"",triggerEvent:n},c=i.filter((e=>(0,r.evaluateCondition)(e.condition,a)))
436
+ for(const e of c){const t=(0,r.interpolatePrompt)(e.promptTemplate,a),i=a.sessionId||""
437
+ if(this.logger.info("Executing custom event hook",{name:e.name,event:n,chainDepth:s}),"inject_prompt"===e.action)switch(e.target){case"same_session":default:await(0,o.executeSameSession)(t,i,this.ctx)
383
438
  break
384
- case"parent_session":case"ancestor_chain":await(0,s.executeInjectPrompt)(e,e.promptTemplate,i,a,this.ctx)}else await(0,s.executeBackground)(e,t,i,a.requestId,this.ctx)
385
- const c=(0,o.determineEventToEmit)(e,a)
386
- c&&await this.processCustomEvent(c,a,r+1)}}async notifyHookTrigger(e){try{await this.ctx.apiClient.post(`/api/projects/${this.ctx.projectId}/hook-triggers`,{hookId:e.id,hookName:e.name,eventType:e.eventType,action:e.action,target:e.target,agent:e.agent})}catch{}}}},3444(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
439
+ case"parent_session":case"ancestor_chain":await(0,o.executeInjectPrompt)(e,e.promptTemplate,i,a,this.ctx)}else await(0,o.executeBackground)(e,t,i,a.requestId,this.ctx)
440
+ const c=(0,r.determineEventToEmit)(e,a)
441
+ c&&await this.processCustomEvent(c,a,s+1)}}async notifyHookTrigger(e){try{await this.ctx.apiClient.post(`/api/projects/${this.ctx.projectId}/hook-triggers`,{hookId:e.id,hookName:e.name,eventType:e.eventType,action:e.action,target:e.target,agent:e.agent})}catch{}}}},3444(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
387
442
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=exports.defaultLogger=exports.isDebugToolsEnabled=exports.getDebugLevelFromEnv=exports.isDebugLoggingEnabled=exports.createEnvLogger=exports.createLogger=void 0
388
- const o=t(2782)
389
- Object.defineProperty(exports,"createLogger",{enumerable:!0,get(){return o.createLogger}}),Object.defineProperty(exports,"createEnvLogger",{enumerable:!0,get(){return o.createEnvLogger}}),Object.defineProperty(exports,"isDebugLoggingEnabled",{enumerable:!0,get(){return o.isDebugLoggingEnabled}}),Object.defineProperty(exports,"getDebugLevelFromEnv",{enumerable:!0,get(){return o.getDebugLevelFromEnv}}),Object.defineProperty(exports,"isDebugToolsEnabled",{enumerable:!0,get(){return o.isDebugToolsEnabled}})
390
- const s=r(t(2782))
391
- exports.defaultLogger=s.default
443
+ const r=t(2782)
444
+ Object.defineProperty(exports,"createLogger",{enumerable:!0,get(){return r.createLogger}}),Object.defineProperty(exports,"createEnvLogger",{enumerable:!0,get(){return r.createEnvLogger}}),Object.defineProperty(exports,"isDebugLoggingEnabled",{enumerable:!0,get(){return r.isDebugLoggingEnabled}}),Object.defineProperty(exports,"getDebugLevelFromEnv",{enumerable:!0,get(){return r.getDebugLevelFromEnv}}),Object.defineProperty(exports,"isDebugToolsEnabled",{enumerable:!0,get(){return r.isDebugToolsEnabled}})
445
+ const o=s(t(2782))
446
+ exports.defaultLogger=o.default
392
447
  const n=t(6540)
393
- Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=s.default},3785(module){module.exports=require("readline")},3807(module){module.exports=require("simple-git")},3962(e,exports,t){function r(){if(n)return n
448
+ Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=o.default},3785(module){module.exports=require("readline")},3807(module){module.exports=require("simple-git")},3962(e,exports,t){function s(){if(n)return n
394
449
  const e=process.argv[1]||""
395
- let r=e
396
- try{r=t(9896).realpathSync(e)}catch{}let o=""
397
- if(r.includes("node_modules")){const e=r.lastIndexOf("/dist/")
398
- e>0&&(o=r.substring(0,e+5))}else{const e=r.lastIndexOf("/dist-dev/")
399
- if(e>0)o=r.substring(0,e+9)
400
- else{const e=r.lastIndexOf("/dist/")
401
- e>0&&(o=r.substring(0,e+5))}}if(!o){const e=process.cwd()
402
- o=e.includes("alchemy-local")||e.includes("alchemy")?s.default.join(e,"dist-dev"):s.default.join(e,"dist")}const i=o.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1,a=s.default.join(o,"mcp-direct.js"),c=process.env.REMOTE_SERVER_URL||"https://api.alchemylabcode.com"
403
- return n={installPath:o,scriptPath:a,nodePath:process.execPath,isDevMode:i,serverName:"alchemy",remoteServerUrl:c},n}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
404
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.getInstallContext=r,exports.clearInstallContextCache=function(){n=null},exports.isDevMode=function(e){return e?e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1:r().isDevMode},exports.getServerName=function(e){return e??r().isDevMode,"alchemy"}
405
- const s=o(t(6928))
450
+ let s=e
451
+ try{s=t(9896).realpathSync(e)}catch{}let r=""
452
+ if(s.includes("node_modules")){const e=s.lastIndexOf("/dist/")
453
+ e>0&&(r=s.substring(0,e+5))}else{const e=s.lastIndexOf("/dist-dev/")
454
+ if(e>0)r=s.substring(0,e+9)
455
+ else{const e=s.lastIndexOf("/dist/")
456
+ e>0&&(r=s.substring(0,e+5))}}if(!r){const e=process.cwd()
457
+ r=e.includes("alchemy-local")||e.includes("alchemy")?o.default.join(e,"dist-dev"):o.default.join(e,"dist")}const i=r.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1,a=o.default.join(r,"mcp-direct.js"),c=process.env.REMOTE_SERVER_URL||"https://api.alchemylabcode.com"
458
+ return n={installPath:r,scriptPath:a,nodePath:process.execPath,isDevMode:i,serverName:"alchemy",remoteServerUrl:c},n}var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
459
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.getInstallContext=s,exports.clearInstallContextCache=function(){n=null},exports.isDevMode=function(e){return e?e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1:s().isDevMode},exports.getServerName=function(e){return e??s().isDevMode,"alchemy"}
460
+ const o=r(t(6928))
406
461
  let n=null},4139(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.ALCHEMY_DIR_NAME=exports.TOKEN_DIR_NAME=void 0,exports.TOKEN_DIR_NAME="alchemy-key",exports.ALCHEMY_DIR_NAME=".alchemy_lab"},4192(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.BaseApiClient=void 0
407
- const r=t(7163)
408
- exports.BaseApiClient=class{constructor(e,t,o,s,n){this.failureCount=0,this.circuitState="closed",this.circuitResetTimeout=null,this.baseUrl=e,this.logger=t,this.metrics=o,this.authToken=s,this.options={retryAttempts:3,retryDelay:1e3,timeout:1e4,circuitBreakerThreshold:5,circuitBreakerResetTime:3e4,...n},this.logger.debug("BaseApiClient constructor options:",{providedOptions:n?JSON.stringify(n):"undefined",mergedOptions:JSON.stringify(this.options)})
409
- try{(0,r.debugTokenAccess)(this,"BaseApiClient constructor")}catch(e){}}setAuthToken(e){this.authToken=e||void 0,this.logger.debug("Auth token updated",{hasToken:!!e,tokenLength:e?e.length:0})}getAuthToken(){return this.authToken}async requestWithRetry(e,t,r,o,s){if("open"===this.circuitState)throw this.logger.warn(`Circuit breaker open - rejecting request to ${e} ${t}`),Error("Service unavailable - circuit breaker open")
462
+ const s=t(7163)
463
+ exports.BaseApiClient=class{constructor(e,t,r,o,n){this.failureCount=0,this.circuitState="closed",this.circuitResetTimeout=null,this.baseUrl=e,this.logger=t,this.metrics=r,this.authToken=o,this.options={retryAttempts:3,retryDelay:1e3,timeout:1e4,circuitBreakerThreshold:5,circuitBreakerResetTime:3e4,...n},this.logger.debug("BaseApiClient constructor options:",{providedOptions:n?JSON.stringify(n):"undefined",mergedOptions:JSON.stringify(this.options)})
464
+ try{(0,s.debugTokenAccess)(this,"BaseApiClient constructor")}catch(e){}}setAuthToken(e){this.authToken=e||void 0,this.logger.debug("Auth token updated",{hasToken:!!e,tokenLength:e?e.length:0})}getAuthToken(){return this.authToken}async requestWithRetry(e,t,s,r,o){if("open"===this.circuitState)throw this.logger.warn(`Circuit breaker open - rejecting request to ${e} ${t}`),Error("Service unavailable - circuit breaker open")
410
465
  const n=t.startsWith("/")?t:"/"+t
411
466
  let i=`${this.baseUrl}${n}`
412
- if(s&&Object.keys(s).length>0){const e=Object.entries(s).filter((([e,t])=>null!=t)).map((([e,t])=>Array.isArray(t)?t.map((t=>`${encodeURIComponent(e)}=${encodeURIComponent(t+"")}`)).join("&"):`${encodeURIComponent(e)}=${encodeURIComponent(t+"")}`)).join("&")
413
- i=`${i}${i.includes("?")?"&":"?"}${e}`}const a={"Content-Type":"application/json",Accept:"application/json",...o}
467
+ if(o&&Object.keys(o).length>0){const e=Object.entries(o).filter((([e,t])=>null!=t)).map((([e,t])=>Array.isArray(t)?t.map((t=>`${encodeURIComponent(e)}=${encodeURIComponent(t+"")}`)).join("&"):`${encodeURIComponent(e)}=${encodeURIComponent(t+"")}`)).join("&")
468
+ i=`${i}${i.includes("?")?"&":"?"}${e}`}const a={"Content-Type":"application/json",Accept:"application/json",...r}
414
469
  this.authToken?(a.Authorization="Bearer "+this.authToken,this.logger.debug("Using auth token for request",{method:e,path:n,tokenLength:this.authToken.length})):this.logger.warn(`No auth token available for ${e} ${t}`,{operation:"api-request",method:e,path:t,authStatus:"missing"})
415
470
  const c=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
416
471
  a["X-Service-Secret"]=c
417
472
  let l=0
418
- const d=this.options.retryAttempts,u={method:e,path:n},g=this.metrics.startTimer("api_request_duration"),h=e=>g({...u,...e})
473
+ const d=this.options.retryAttempts,u={method:e,path:n},h=this.metrics.startTimer("api_request_duration"),g=e=>h({...u,...e})
419
474
  for(;d>l;){l++
420
475
  try{this.logger.debug(`Making ${e} request to ${i}`,{attempt:l,maxAttempts:d,hasAuth:!!a.Authorization,hasServiceIdentifier:!!a["X-Client-Type"]})
421
476
  const t={method:e,headers:{...a},signal:AbortSignal.timeout(this.options.timeout||1e4)}
422
- if("GET"!==e&&void 0!==r&&(t.body=JSON.stringify(r)),this.logger.debug("Request details",{method:e,url:i}),t.headers){const e={}
423
- Object.entries(t.headers).forEach((([t,r])=>{"authorization"===t.toLowerCase()?e[t]="Bearer [redacted]":e[t]=r})),this.logger.debug("Request headers",{headers:e})}if("GET"!==e&&t.body){const e="string"==typeof t.body?t.body:JSON.stringify(t.body)
424
- this.logger.debug("Request body",{bodyPreview:e.substring(0,200)+(e.length>200?"...":""),bodyLength:e.length})}const o=await fetch(i,t)
425
- if(!o.ok){if(this.incrementFailureCount(),this.logger.error("API request failed with status "+o.status,{method:e,url:i,status:o.status,statusText:o.statusText,attempt:l}),401===o.status)throw h({success:!1,status:o.status,retries:l-1}),Error("Authentication failed - invalid or expired token")
426
- if(429===o.status){const e=o.headers.get("Retry-After"),t=e?1e3*parseInt(e):2*(this.options.retryDelay||1e3)
477
+ if("GET"!==e&&void 0!==s&&(t.body=JSON.stringify(s)),this.logger.debug("Request details",{method:e,url:i}),t.headers){const e={}
478
+ 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)
479
+ this.logger.debug("Request body",{bodyPreview:e.substring(0,200)+(e.length>200?"...":""),bodyLength:e.length})}const r=await fetch(i,t)
480
+ 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")
481
+ if(429===r.status){const e=r.headers.get("Retry-After"),t=e?1e3*parseInt(e):2*(this.options.retryDelay||1e3)
427
482
  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)))
428
- continue}}if(o.status>=500&&d>l){const e=(this.options.retryDelay||1e3)*Math.pow(2,l-1)
429
- this.logger.warn(`Server error ${o.status}, retry in ${e}ms`,{attempt:l,maxAttempts:d}),await new Promise((t=>setTimeout(t,e)))
430
- continue}let t=`API request failed with status ${o.status}: ${o.statusText}`,r={}
431
- try{const e=await o.json()
432
- if(e&&"object"==typeof e&&!1===e.success&&e.error){const o=e.error
433
- o.message&&(t=o.message),r={code:o.code,statusCode:o.statusCode,retryable:o.retryable,details:o.details}}else e&&"object"==typeof e&&e.message&&(t=e.message)}catch(e){try{const e=await o.text()
434
- 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:o.status,retries:l-1})
435
- const s=Error(t)
436
- throw s.status=o.status,s.statusText=o.statusText,s.method=e,s.url=i,s.errorDetails=r,s}this.resetFailureCount()
437
- const s=await o.json()
438
- if(null===s||"object"!=typeof s)throw Error("Invalid API response: expected object, got "+typeof s)
439
- if(s&&"object"==typeof s&&!("success"in s)){this.logger.warn("Non-standard API response detected - wrapping in standard format",{responseKeys:Object.keys(s)})
440
- const e={success:!0,data:s}
441
- return h({success:!0,status:o.status,retries:l-1,standardFormat:!1}),e}if(s&&"object"==typeof s&&"success"in s&&!1===s.success){const e=s
442
- throw this.logger.error("API returned error response",{errorCode:e.error?.code,errorMessage:e.error?.message,statusCode:e.error?.statusCode}),h({success:!1,status:o.status,retries:l-1,errorCode:e.error?.code}),Error(`API error: ${e.error?.code}: ${e.error?.message}`)}if(s&&"object"==typeof s&&"success"in s&&!0===s.success&&"data"in s){const t=s
443
- return h({success:!0,status:o.status,retries:l-1,standardFormat:!0}),this.logger.debug("API request succeeded with status "+o.status,{method:e,url:i,status:o.status,retries:l-1,hasMetadata:"metadata"in t}),t}return this.logger.warn("Invalid API response format: Unexpected structure",{responseType:typeof s,responseKeys:"object"==typeof s?Object.keys(s):[]}),{success:!0,data:s}}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
444
- const r=Math.min((this.options.retryDelay||1e3)*Math.pow(2,l-1),5e3)
445
- this.logger.warn(`API request failed, retrying in ${r}ms`,{method:e,url:i,error:t.message||t+"",attempt:l,maxAttempts:d}),await new Promise((e=>setTimeout(e,r)))}}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
483
+ continue}}if(r.status>=500&&d>l){const e=(this.options.retryDelay||1e3)*Math.pow(2,l-1)
484
+ this.logger.warn(`Server error ${r.status}, retry in ${e}ms`,{attempt:l,maxAttempts:d}),await new Promise((t=>setTimeout(t,e)))
485
+ continue}let t=`API request failed with status ${r.status}: ${r.statusText}`,s={}
486
+ try{const e=await r.json()
487
+ if(e&&"object"==typeof e&&!1===e.success&&e.error){const r=e.error
488
+ 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()
489
+ 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})
490
+ const o=Error(t)
491
+ throw o.status=r.status,o.statusText=r.statusText,o.method=e,o.url=i,o.errorDetails=s,o}this.resetFailureCount()
492
+ const o=await r.json()
493
+ if(null===o||"object"!=typeof o)throw Error("Invalid API response: expected object, got "+typeof o)
494
+ if(o&&"object"==typeof o&&!("success"in o)){this.logger.warn("Non-standard API response detected - wrapping in standard format",{responseKeys:Object.keys(o)})
495
+ const e={success:!0,data:o}
496
+ 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
497
+ 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
498
+ 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
499
+ const s=Math.min((this.options.retryDelay||1e3)*Math.pow(2,l-1),5e3)
500
+ 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
446
501
  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)
447
502
  const e=this.options.circuitBreakerResetTime||5e3
448
- this.circuitResetTimeout=setTimeout((()=>{this.circuitState="half-open",this.logger.info("Circuit breaker half-open, next request will probe")}),e)}async get(e,t,r){return this.requestWithRetry("GET",e,void 0,r,t)}async post(e,t,r){return this.requestWithRetry("POST",e,t,r)}async put(e,t,r){return this.requestWithRetry("PUT",e,t,r)}async delete(e,t){return this.requestWithRetry("DELETE",e,void 0,t)}async patch(e,t,r){return this.requestWithRetry("PATCH",e,t,r)}}},4627(e,exports){function t(e,t){try{return function(e){const t=r.get(e)
503
+ this.circuitResetTimeout=setTimeout((()=>{this.circuitState="half-open",this.logger.info("Circuit breaker half-open, next request will probe")}),e)}async get(e,t,s){return this.requestWithRetry("GET",e,void 0,s,t)}async post(e,t,s){return this.requestWithRetry("POST",e,t,s)}async put(e,t,s){return this.requestWithRetry("PUT",e,t,s)}async delete(e,t){return this.requestWithRetry("DELETE",e,void 0,t)}async patch(e,t,s){return this.requestWithRetry("PATCH",e,t,s)}}},4627(e,exports){function t(e,t){try{return function(e){const t=s.get(e)
449
504
  if(t)return t
450
- let o=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"{{GLOBSTAR}}").replace(/\*/g,"[^/]*").replace(/\?/g,".").replace(/\{\{GLOBSTAR\}\}/g,".*")
451
- const s=RegExp(`^${o}$`)
452
- return r.size>500&&r.clear(),r.set(e,s),s}(t).test(e)}catch{return!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.evaluateCondition=function(e,r){if(!e||!e.conditions||0===e.conditions.length)return!0
453
- const o=e.conditions.map((e=>{const o=r[e.field]||""
454
- return"paths"===e.field&&o.includes(", ")?function(e,r,o){switch(r){case"matches":return e.some((e=>t(e,o)))
455
- case"not_matches":return e.every((e=>!t(e,o)))
456
- case"contains":return e.some((e=>e.includes(o)))
457
- case"not_contains":return e.every((e=>!e.includes(o)))
458
- case"equals":return e.some((e=>e===o))
459
- case"not_equals":return e.every((e=>e!==o))
460
- default:return!0}}(o.split(", "),e.operator,e.value):function(e,r,o){switch(r){case"matches":return t(e,o)
461
- case"not_matches":return!t(e,o)
462
- case"contains":return e.includes(o)
463
- case"not_contains":return!e.includes(o)
464
- case"equals":return e===o
465
- case"not_equals":return e!==o
466
- default:return!0}}(o,e.operator,e.value)}))
467
- return"OR"===e.logic?o.some((e=>e)):o.every((e=>e))},exports.interpolatePrompt=function(e,t){let r=e
468
- for(const[e,o]of Object.entries(t))r=r.replace(RegExp(`\\{\\{${e}\\}\\}`,"g"),o)
469
- return r},exports.determineEventToEmit=function(e,t){if(e.emitEventOnCondition){const{condition:r,event:s}=e.emitEventOnCondition,n=t[r.field]||""
505
+ let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"{{GLOBSTAR}}").replace(/\*/g,"[^/]*").replace(/\?/g,".").replace(/\{\{GLOBSTAR\}\}/g,".*")
506
+ const o=RegExp(`^${r}$`)
507
+ return s.size>500&&s.clear(),s.set(e,o),o}(t).test(e)}catch{return!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.evaluateCondition=function(e,s){if(!e||!e.conditions||0===e.conditions.length)return!0
508
+ const r=e.conditions.map((e=>{const r=s[e.field]||""
509
+ return"paths"===e.field&&r.includes(", ")?function(e,s,r){switch(s){case"matches":return e.some((e=>t(e,r)))
510
+ case"not_matches":return e.every((e=>!t(e,r)))
511
+ case"contains":return e.some((e=>e.includes(r)))
512
+ case"not_contains":return e.every((e=>!e.includes(r)))
513
+ case"equals":return e.some((e=>e===r))
514
+ case"not_equals":return e.every((e=>e!==r))
515
+ default:return!0}}(r.split(", "),e.operator,e.value):function(e,s,r){switch(s){case"matches":return t(e,r)
516
+ case"not_matches":return!t(e,r)
517
+ case"contains":return e.includes(r)
518
+ case"not_contains":return!e.includes(r)
519
+ case"equals":return e===r
520
+ case"not_equals":return e!==r
521
+ default:return!0}}(r,e.operator,e.value)}))
522
+ return"OR"===e.logic?r.some((e=>e)):r.every((e=>e))},exports.interpolatePrompt=function(e,t){let s=e
523
+ for(const[e,r]of Object.entries(t))s=s.replace(RegExp(`\\{\\{${e}\\}\\}`,"g"),r)
524
+ return s},exports.determineEventToEmit=function(e,t){if(e.emitEventOnCondition){const{condition:s,event:o}=e.emitEventOnCondition,n=t[s.field]||""
470
525
  let i=!1
471
- switch(r.operator){case"contains":i=n.includes(r.value)
526
+ switch(s.operator){case"contains":i=n.includes(s.value)
472
527
  break
473
- case"not_contains":i=!n.includes(r.value)
528
+ case"not_contains":i=!n.includes(s.value)
474
529
  break
475
- case"equals":i=n===r.value
530
+ case"equals":i=n===s.value
476
531
  break
477
- case"not_equals":i=n!==r.value
532
+ case"not_equals":i=n!==s.value
478
533
  break
479
- case"matches":try{o.has(r.value)||(o.set(r.value,RegExp(r.value)),o.size>500&&o.clear()),i=o.get(r.value).test(n)}catch{i=!1}break
480
- case"not_matches":try{o.has(r.value)||(o.set(r.value,RegExp(r.value)),o.size>500&&o.clear()),i=!o.get(r.value).test(n)}catch{i=!0}}if(i)return s}return e.emitEvent||null},exports.createToolCallHash=function(e,t){return`${e}:${t?.substring(0,100)||""}`},exports.isDuplicateToolCall=function(e,t=Date.now()){const r=s.get(e)
481
- if(r&&n>t-r)return!0
482
- for(const[e,r]of s.entries())t-r>2*n&&s.delete(e)
483
- return s.set(e,t),!1},exports.tryMarkMessageProcessed=function(e,t){if(!t)return!0
534
+ case"matches":try{r.has(s.value)||(r.set(s.value,RegExp(s.value)),r.size>500&&r.clear()),i=r.get(s.value).test(n)}catch{i=!1}break
535
+ case"not_matches":try{r.has(s.value)||(r.set(s.value,RegExp(s.value)),r.size>500&&r.clear()),i=!r.get(s.value).test(n)}catch{i=!0}}if(i)return o}return e.emitEvent||null},exports.createToolCallHash=function(e,t){return`${e}:${t?.substring(0,100)||""}`},exports.isDuplicateToolCall=function(e,t=Date.now()){const s=o.get(e)
536
+ if(s&&n>t-s)return!0
537
+ for(const[e,s]of o.entries())t-s>2*n&&o.delete(e)
538
+ return o.set(e,t),!1},exports.tryMarkMessageProcessed=function(e,t){if(!t)return!0
484
539
  if(i.get(e)===t)return!1
485
540
  if(i.set(e,t),i.size>100){const e=i.keys().next().value
486
541
  e&&i.delete(e)}return!0}
487
- const r=new Map,o=new Map,s=new Map,n=2e3,i=new Map},4769(e,exports,t){function r(e){return(0,s.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function o(e,t,o){const s=r(o)
488
- if(!s||!t)return!1
489
- try{const r=await async function(e,t){try{const r=await t.apiClient.get(`/api/projects/${t.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),o=r?.data||r
490
- return o?.agentType||""}catch{return""}}(t,o)||void 0
542
+ const s=new Map,r=new Map,o=new Map,n=2e3,i=new Map},4769(e,exports,t){function s(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function r(e,t,r){const o=s(r)
543
+ if(!o||!t)return!1
544
+ try{const s=await async function(e,t){try{const s=await t.apiClient.get(`/api/projects/${t.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),r=s?.data||s
545
+ return r?.agentType||""}catch{return""}}(t,r)||void 0
491
546
  let n
492
- if(o.cachedDefaultModel){const[e,...t]=o.cachedDefaultModel.split("/")
493
- n={providerID:e,modelID:t.join("/")}}const i=`${s}/session/${t}/prompt_async`,a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e}],...r?{agent:r}:{},...n?{model:n}:{}})})
494
- return a.ok?(o.logger.debug("Message sent to session",{sessionId:t}),!0):(o.logger.warn("Failed to send message to session",{sessionId:t,status:a.status}),!1)}catch(e){return o.logger.warn("Error sending message to session",{sessionId:t,error:e+""}),!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeBackground=async function(e,t,r,o,n){const i=await(0,s.spawnActionAgent)({agent:e.agent,prompt:t,cwd:n.projectPath,projectPath:n.projectPath,projectId:n.projectId,title:"Hook: "+e.name,requestId:o,parentSessionId:"independent_session"===e.target?void 0:r||void 0,apiClient:n.apiClient,logger:n.logger,trackChildPid:n.trackChildPid})
495
- i.success||n.logger.warn("Background hook spawn failed",{hook:e.name,error:i.error})},exports.executeSameSession=async function(e,t,r){await o(e,t,r)},exports.executeInjectPrompt=async function(e,r,s,n,i){let a=[]
496
- if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(s)}/ancestors`)
497
- a=(e?.data||e||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))),i.logger.debug("inject_prompt: ancestors fetched",{sessionId:s,count:a.length,ancestors:a.map((e=>e.externalSessionId))})}catch(e){i.logger.warn("inject_prompt: ancestors fetch failed, falling back to parent",{sessionId:s,error:e+""})}if(0===a.length)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(s)}/parent`),t=e?.data?.parentExternalSessionId||e?.parentExternalSessionId
498
- t&&(a=[{externalSessionId:t}])}catch{}if(0===a.length)return void i.logger.debug("inject_prompt: no ancestors found",{sessionId:s})
547
+ if(r.cachedDefaultModel){const[e,...t]=r.cachedDefaultModel.split("/")
548
+ n={providerID:e,modelID:t.join("/")}}const i=`${o}/session/${t}/prompt_async`,a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e}],...s?{agent:s}:{},...n?{model:n}:{}})})
549
+ return a.ok?(r.logger.debug("Message sent to session",{sessionId:t}),!0):(r.logger.warn("Failed to send message to session",{sessionId:t,status:a.status}),!1)}catch(e){return r.logger.warn("Error sending message to session",{sessionId:t,error:e+""}),!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeBackground=async function(e,t,s,r,n){const i=await(0,o.spawnActionAgent)({agent:e.agent,prompt:t,cwd:n.projectPath,projectPath:n.projectPath,projectId:n.projectId,title:"Hook: "+e.name,requestId:r,parentSessionId:"independent_session"===e.target?void 0:s||void 0,apiClient:n.apiClient,logger:n.logger,trackChildPid:n.trackChildPid})
550
+ i.success||n.logger.warn("Background hook spawn failed",{hook:e.name,error:i.error})},exports.executeSameSession=async function(e,t,s){await r(e,t,s)},exports.executeInjectPrompt=async function(e,s,o,n,i){let a=[]
551
+ if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`)
552
+ a=(e?.data||e||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))),i.logger.debug("inject_prompt: ancestors fetched",{sessionId:o,count:a.length,ancestors:a.map((e=>e.externalSessionId))})}catch(e){i.logger.warn("inject_prompt: ancestors fetch failed, falling back to parent",{sessionId:o,error:e+""})}if(0===a.length)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(o)}/parent`),t=e?.data?.parentExternalSessionId||e?.parentExternalSessionId
553
+ t&&(a=[{externalSessionId:t}])}catch{}if(0===a.length)return void i.logger.debug("inject_prompt: no ancestors found",{sessionId:o})
499
554
  const c=a.map((e=>`${e.agentType||"agent"}${e.title?" "+e.title:""} (${e.externalSessionId})`)).join(" \u2192 "),{interpolatePrompt:l}=await Promise.resolve().then(t.bind(t,4627)),d={...n,chainInfo:c}
500
555
  i.logger.debug("inject_prompt: interpolating",{variables:Object.keys(d),chainInfo:c,sessionId:n.sessionId,agentType:n.agentType})
501
- const u=l(r,d)
502
- for(const e of a)i.logger.debug("inject_prompt: sending to ancestor",{targetSessionId:e.externalSessionId,agentType:e.agentType}),await o("\x3c!-- inserted-prompt --\x3e"+u,e.externalSessionId,i)},exports.executeNotification=async function(e,t){const o=r(t)
503
- if(o)try{const t=o+"/tui/toast"
504
- await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:"Background Work Complete",message:e,variant:"success",duration:8e3})})}catch(e){t.logger.warn("Failed to send toast notification",{error:e+""})}},exports.executeDirectReport=async function(e,t,r){try{const s="http://localhost:3456/api/direct-report",n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:e,requestId:t||void 0,status:"success"})})
505
- if(!n.ok)return void r.logger.warn("Direct report failed",{sessionId:e,status:n.status})
556
+ const u=l(s,d)
557
+ for(const e of a)i.logger.debug("inject_prompt: sending to ancestor",{targetSessionId:e.externalSessionId,agentType:e.agentType}),await r("\x3c!-- inserted-prompt --\x3e"+u,e.externalSessionId,i)},exports.executeNotification=async function(e,t){const r=s(t)
558
+ if(r)try{const t=r+"/tui/toast"
559
+ await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:"Background Work Complete",message:e,variant:"success",duration:8e3})})}catch(e){t.logger.warn("Failed to send toast notification",{error:e+""})}},exports.executeDirectReport=async function(e,t,s){try{const o="http://localhost:3456/api/direct-report",n=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:e,requestId:t||void 0,status:"success"})})
560
+ if(!n.ok)return void s.logger.warn("Direct report failed",{sessionId:e,status:n.status})
506
561
  const i=await n.json()
507
- if(!i.success||!i.data)return void r.logger.warn("Direct report returned failure",{sessionId:e})
562
+ if(!i.success||!i.data)return void s.logger.warn("Direct report returned failure",{sessionId:e})
508
563
  const{notifiedSessions:a,report:c,parentExternalSessionId:l}=i.data,d=a||(l?[{externalSessionId:l}]:[])
509
- for(const e of d){const t=c?.content?.reportStatus||"success",s=[`\x3c!-- inserted-prompt --\x3e${{success:"\u2705",partial:"\u26a0\ufe0f",failure:"\u274c",blocked:"\u{1f6ab}"}[t]||"\u{1f4cb}"} **Worker Report \u2014 ${t}**`,"",c?.content?.summary||"Task completed",...c?.content?.details?["",c.content.details]:[]].join("\n")
510
- await o(s,e.externalSessionId,r)}}catch(t){r.logger.warn("Error in direct report",{sessionId:e,error:t+""})}}
511
- const s=t(8148)},5086(module){module.exports=require("ws")},5124(module){module.exports=require("winston")},5317(module){module.exports=require("child_process")},5607(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,r){const o=u[e]
512
- if(!o)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
513
- try{return await o(t,r)}catch(t){const o=(0,d.getErrorMessage)(t)
514
- return r.logger.error("Action handler failed",{actionType:e,error:o}),{success:!1,message:"Action failed: "+o,error:o}}}
515
- const r=t(6636),o=t(7453),s=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(8824),u={revert_request:r.handleRevertRequestAction,"revert-request":r.handleRevertRequestAction,complete_request:o.handleCompleteRequestAction,"complete-request":o.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":s.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction}},5700(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
516
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRestartMcpAction=async function(e,t){const{logger:r,projectPath:n}=t,i=e.reason||"Manual restart requested"
517
- r.info("Executing restart-mcp action",{reason:i})
518
- try{const e=function(e){return s.default.join(e,".alchemy_lab","mcp-restart-trigger")}(n),t=s.default.dirname(e)
519
- await o.default.mkdir(t,{recursive:!0})
564
+ for(const e of d){const t=c?.content?.reportStatus||"success",o=[`\x3c!-- inserted-prompt --\x3e${{success:"\u2705",partial:"\u26a0\ufe0f",failure:"\u274c",blocked:"\u{1f6ab}"}[t]||"\u{1f4cb}"} **Worker Report \u2014 ${t}**`,"",c?.content?.summary||"Task completed",...c?.content?.details?["",c.content.details]:[]].join("\n")
565
+ await r(o,e.externalSessionId,s)}}catch(t){s.logger.warn("Error in direct report",{sessionId:e,error:t+""})}}
566
+ const o=t(8148)},5086(module){module.exports=require("ws")},5124(module){module.exports=require("winston")},5317(module){module.exports=require("child_process")},5607(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,s){const r=u[e]
567
+ if(!r)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
568
+ try{return await r(t,s)}catch(t){const r=(0,d.getErrorMessage)(t)
569
+ return s.logger.error("Action handler failed",{actionType:e,error:r}),{success:!1,message:"Action failed: "+r,error:r}}}
570
+ const s=t(6636),r=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(8824),u={revert_request:s.handleRevertRequestAction,"revert-request":s.handleRevertRequestAction,complete_request:r.handleCompleteRequestAction,"complete-request":r.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":o.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction,async"inject-prompt"(e,s){const{sessionId:r,message:o}=e
571
+ if(!r||!o)return{success:!1,message:"sessionId and message are required",error:"missing_params"}
572
+ 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")
573
+ let a
574
+ 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)})
575
+ 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){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
576
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRestartMcpAction=async function(e,t){const{logger:s,projectPath:n}=t,i=e.reason||"Manual restart requested"
577
+ s.info("Executing restart-mcp action",{reason:i})
578
+ try{const e=function(e){return o.default.join(e,".alchemy_lab","mcp-restart-trigger")}(n),t=o.default.dirname(e)
579
+ await r.default.mkdir(t,{recursive:!0})
520
580
  const a=JSON.stringify({timestamp:(new Date).toISOString(),reason:i},null,2)
521
- return await o.default.writeFile(e,a,"utf-8"),r.info("MCP restart triggered",{triggerPath:e,reason:i}),{success:!0,message:"MCP restart triggered: "+i,data:{triggerPath:e}}}catch(e){const t=e instanceof Error?e.message:e+""
522
- return r.error("Failed to trigger MCP restart",{error:t}),{success:!1,message:"Failed to trigger MCP restart: "+t,error:t}}}
523
- const o=r(t(1943)),s=r(t(6928))},6540(e,exports){var t
524
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=void 0,function(e){e.ERROR="error",e.WARN="warn",e.INFO="info",e.DEBUG="debug"}(t||(exports.LogLevel=t={}))},6555(e,exports,t){function r(e,process,t,r,o=!1){const s=process.config||{},n=(s.setup,s.commands||[])
525
- let i=`You are a test runner agent. Your job is to verify that request ${e.localId||e.id} ("${e.title}") passes all required tests.\n\n## Test Process: ${process.name}\n${process.description?`\n${process.description}\n`:""}\n${process.scope?`**Scope:** Files under \`${process.scope}\`\n`:""}\n## Project ID: ${r}\n\n## Test Commands\nExecute these in order:\n`
581
+ return await r.default.writeFile(e,a,"utf-8"),s.info("MCP restart triggered",{triggerPath:e,reason:i}),{success:!0,message:"MCP restart triggered: "+i,data:{triggerPath:e}}}catch(e){const t=e instanceof Error?e.message:e+""
582
+ return s.error("Failed to trigger MCP restart",{error:t}),{success:!1,message:"Failed to trigger MCP restart: "+t,error:t}}}
583
+ const r=s(t(1943)),o=s(t(6928))},6540(e,exports){var t
584
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=void 0,function(e){e.ERROR="error",e.WARN="warn",e.INFO="info",e.DEBUG="debug"}(t||(exports.LogLevel=t={}))},6555(e,exports,t){function s(e,process,t,s,r=!1){const o=process.config||{},n=(o.setup,o.commands||[])
585
+ let i=`You are a test runner agent. Your job is to verify that request ${e.localId||e.id} ("${e.title}") passes all required tests.\n\n## Test Process: ${process.name}\n${process.description?`\n${process.description}\n`:""}\n${process.scope?`**Scope:** Files under \`${process.scope}\`\n`:""}\n## Project ID: ${s}\n\n## Test Commands\nExecute these in order:\n`
526
586
  for(let e=0;e<n.length;e++){const t=n[e]
527
- i+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}if(i+=o?`\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.`,s.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"
587
+ 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"
528
588
  for(const t of e.acceptanceCriteria)i+=`- ${t}\n`
529
- 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
589
+ 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
530
590
  if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
531
- m.info("Starting run-tests action",{requestId:u,testProcessIds:g})
591
+ m.info("Starting run-tests action",{requestId:u,testProcessIds:h})
532
592
  try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
533
593
  if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
534
- const p=d.data,I=p.branchName||"requests/"+u
535
- let E
536
- if(g?.length)E=(await Promise.all(g.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
594
+ const p=d.data,k=p.branchName||"requests/"+u
595
+ let S
596
+ 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))
537
597
  else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
538
- E=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===E.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
539
- const b=(0,s.join)(w,c,l),k=(0,s.join)(b,u),j=(0,n.simpleGit)(w)
540
- if((0,o.existsSync)(k))try{const e=(0,n.simpleGit)(k)
541
- await e.raw(["reset","--hard",I]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:k,branchName:I})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
542
- try{await j.raw(["worktree","remove","--force",k])}catch{}}if(!(0,o.existsSync)(k)){(0,o.mkdirSync)(b,{recursive:!0})
543
- try{if(!(await j.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
544
- await j.raw(["worktree","add","--detach",k,I]),m.info("Created test worktree (detached)",{worktreePath:k,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)}
545
- m.info("Test worktree already exists, using as-is",{worktreePath:k})}}const{execSync:T}=await Promise.resolve().then(t.t.bind(t,5317,23))
546
- for(const e of E){const t=e.config?.setup||[]
598
+ 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"}
599
+ const I=(0,o.join)(w,c,l),E=(0,o.join)(I,u),b=(0,n.simpleGit)(w)
600
+ if((0,r.existsSync)(E))try{const e=(0,n.simpleGit)(E)
601
+ await e.raw(["reset","--hard",k]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:E,branchName:k})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
602
+ try{await b.raw(["worktree","remove","--force",E])}catch{}}if(!(0,r.existsSync)(E)){(0,r.mkdirSync)(I,{recursive:!0})
603
+ try{if(!(await b.branch()).all.includes(k))return{success:!1,message:`Branch ${k} not found`,error:"Branch not found"}
604
+ await b.raw(["worktree","add","--detach",E,k]),m.info("Created test worktree (detached)",{worktreePath:E,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)}
605
+ m.info("Test worktree already exists, using as-is",{worktreePath:E})}}const{execSync:j}=await Promise.resolve().then(t.t.bind(t,5317,23))
606
+ for(const e of S){const t=e.config?.setup||[]
547
607
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
548
- for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),T(r,{cwd:k,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:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const S=[],P=[]
549
- for(const process of E){let t
550
- const o=e.testRunIds?.[S.length]
551
- try{o?(await y.patch(`/api/projects/${v}/test-runs/${o}`,{worktreePath:k}),t=(await y.get(`/api/projects/${v}/test-runs/${o}`)).data,S.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:k,parentSessionId:h})).data,S.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:o,error:(0,i.getErrorMessage)(e)})
552
- continue}const s=r(p,process,0,v,f),n=f?"tester":"auto-tester"
553
- try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:s,cwd:k,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:h,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
608
+ for(const s of t)try{m.info("Setup command",{command:s,worktreePath:E}),j(s,{cwd:E,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=[]
609
+ for(const process of S){let t
610
+ const r=e.testRunIds?.[T.length]
611
+ try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:E}),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:E,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)})
612
+ continue}const o=s(p,process,0,v,f),n=f?"tester":"auto-tester"
613
+ try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:E,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:g,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
554
614
  if(!e.success)throw Error(e.error||"Spawn failed")
555
- 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:k,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
556
- 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:S,agentsSpawned:P.length,processCount:E.length,worktreePath:k}}}catch(e){const t=(0,i.getErrorMessage)(e)
615
+ 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:E,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
616
+ 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:E}}}catch(e){const t=(0,i.getErrorMessage)(e)
557
617
  return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
558
- const o=t(9896),s=t(6928),n=t(3807),i=t(8824),a=t(8148),c=".alchemy_lab",l="test-worktrees"},6636(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRevertRequestAction=async function(e,t){const{requestId:s,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
559
- if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
618
+ const r=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(8148),c=".alchemy_lab",l="test-worktrees"},6636(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRevertRequestAction=async function(e,t){const{requestId:o,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
619
+ if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
560
620
  if(!n)return{success:!1,message:"reason is required",error:"Missing reason in payload"}
561
- i.info("Starting revert_request action",{requestId:s,reason:n})
562
- try{const e=await a.post(`/api/projects/${l}/requests/${s}/validate-revert`,{})
621
+ i.info("Starting revert_request action",{requestId:o,reason:n})
622
+ try{const e=await a.post(`/api/projects/${l}/requests/${o}/validate-revert`,{})
563
623
  if(!e.success||!e.data)return{success:!1,message:"Server validation failed: "+(e.data?.reason||"Unknown error"),error:"Validation failed"}
564
624
  const t=e.data
565
625
  if(!t.canRevert){let e="Cannot revert request: "+t.reason
566
- return t.lifoViolation?.blockingRequests?.length>0&&(e+="\n\nRequests that must be reverted first:\n",t.lifoViolation.blockingRequests.forEach(((t,r)=>{e+=`${r+1}. ${t.requestId}: ${t.title}\n`}))),{success:!1,message:e,error:t.reason}}if("cancel"===t.operationType){const e=await a.post(`/api/projects/${l}/requests/${s}/finalize-revert`,{reason:n,operationType:"cancel"})
567
- return e.success?{success:!0,message:"Successfully canceled request "+s,data:{operationType:"cancel",reason:n}}:{success:!1,message:"Failed to finalize cancel operation",error:e.error?.message||"Finalize failed"}}let d=t.mergeCommitHash
568
- if(!d){i.info("No merge commit hash from server, searching git log",{requestId:s})
569
- const e=await async function(e,t){try{const o=(0,r.simpleGit)(t),s=(await o.raw(["log","--merges","--format=%H %s","HEAD"])).trim().split("\n").filter((e=>e.trim())),n=[`Merge requests/${e}-`,`Merge requests/${e} `,"Merge branch 'requests/"+e,"requests/"+e]
570
- for(const e of s){const[t,...r]=e.split(" "),o=r.join(" ")
571
- for(const e of n)if(o.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,o.getErrorMessage)(e)}}}(s,c)
572
- if(!e.found||!e.commitHash)return{success:!1,message:"Could not find merge commit for request "+s,error:"Merge commit not found"}
573
- d=e.commitHash}const u=(0,r.simpleGit)(c),g=await u.status(),h=(await u.branch()).all.includes("main")?"main":"master"
626
+ return t.lifoViolation?.blockingRequests?.length>0&&(e+="\n\nRequests that must be reverted first:\n",t.lifoViolation.blockingRequests.forEach(((t,s)=>{e+=`${s+1}. ${t.requestId}: ${t.title}\n`}))),{success:!1,message:e,error:t.reason}}if("cancel"===t.operationType){const e=await a.post(`/api/projects/${l}/requests/${o}/finalize-revert`,{reason:n,operationType:"cancel"})
627
+ return e.success?{success:!0,message:"Successfully canceled request "+o,data:{operationType:"cancel",reason:n}}:{success:!1,message:"Failed to finalize cancel operation",error:e.error?.message||"Finalize failed"}}let d=t.mergeCommitHash
628
+ if(!d){i.info("No merge commit hash from server, searching git log",{requestId:o})
629
+ const e=await async function(e,t){try{const r=(0,s.simpleGit)(t),o=(await r.raw(["log","--merges","--format=%H %s","HEAD"])).trim().split("\n").filter((e=>e.trim())),n=[`Merge requests/${e}-`,`Merge requests/${e} `,"Merge branch 'requests/"+e,"requests/"+e]
630
+ for(const e of o){const[t,...s]=e.split(" "),r=s.join(" ")
631
+ 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)
632
+ if(!e.found||!e.commitHash)return{success:!1,message:"Could not find merge commit for request "+o,error:"Merge commit not found"}
633
+ d=e.commitHash}const u=(0,s.simpleGit)(c),h=await u.status(),g=(await u.branch()).all.includes("main")?"main":"master"
574
634
  let p
575
- g.current!==h&&(i.info("Switching to default branch",{from:g.current,to:h}),await u.checkout(h))
635
+ h.current!==g&&(i.info("Switching to default branch",{from:h.current,to:g}),await u.checkout(g))
576
636
  try{i.info("Executing git revert",{mergeCommitHash:d.substring(0,8)}),await u.raw(["revert","-m","1","--no-edit",d])
577
637
  const e=await u.log({maxCount:1})
578
- p=e.latest?.hash||"",i.info("Git revert completed",{originalCommit:d.substring(0,8),revertCommit:p.substring(0,8)})}catch(e){const t=(0,o.getErrorMessage)(e)
579
- i.error("Git revert failed",{requestId:s,error:t})
580
- try{await u.raw(["revert","--abort"])}catch(e){i.error("CRITICAL: git revert --abort failed \u2014 repo may be in inconsistent state",{requestId:s,error:e instanceof Error?e.message:e+""})}return{success:!1,message:"Git revert failed: "+t,error:t}}const f=await a.post(`/api/projects/${l}/requests/${s}/finalize-revert`,{reason:n,operationType:"revert",revertCommitHash:p,originalMergeCommitHash:d})
581
- return f.success?{success:!0,message:"Successfully reverted request "+s,data:{operationType:"revert",originalMergeCommit:d,revertCommitHash:p,reason:n}}:(i.error("CRITICAL: Git revert succeeded but finalization failed",{requestId:s,revertCommitHash:p,error:f.error}),{success:!1,message:`CRITICAL: Git revert succeeded (commit: ${p.substring(0,8)}) but server finalization failed`,error:"Finalization failed"})}catch(e){const t=(0,o.getErrorMessage)(e)
582
- return i.error("Revert_request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to revert request: "+t,error:t}}}
583
- const r=t(3807),o=t(8824)},6800(e,exports,t){function r(e){return new Promise((t=>{const r=s.default.createServer()
584
- r.once("error",(e=>{e.code,t(!1)})),r.once("listening",(()=>{r.close((()=>{t(!0)}))})),r.listen(e)}))}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
585
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.isPortAvailable=r,exports.findAvailablePort=async function(e,t=10){for(let o=0;t>o;o++){const t=e+o
586
- if(await r(t))return t}throw Error(`No available port found after ${t} attempts starting from port ${e}`)}
587
- const s=o(t(9278))},6895(module){module.exports=require("chalk")},6928(module){module.exports=require("path")},6982(module){module.exports=require("crypto")},7163(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.debugTokenAccess=function(){},exports.compareTokens=function(){},exports.debugTokenStorage=function(){},exports.disableTokenDebugging=function(){},exports.enableTokenDebugging=function(){},new WeakMap},7210(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.tryMarkMessageProcessed=exports.createToolCallHash=exports.isDuplicateToolCall=exports.determineEventToEmit=exports.interpolatePrompt=exports.evaluateCondition=exports.HookCache=exports.HookExecutor=void 0
588
- var r=t(3077)
589
- Object.defineProperty(exports,"HookExecutor",{enumerable:!0,get(){return r.HookExecutor}})
590
- var o=t(8740)
591
- Object.defineProperty(exports,"HookCache",{enumerable:!0,get(){return o.HookCache}})
592
- var s=t(4627)
593
- Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return s.evaluateCondition}}),Object.defineProperty(exports,"interpolatePrompt",{enumerable:!0,get(){return s.interpolatePrompt}}),Object.defineProperty(exports,"determineEventToEmit",{enumerable:!0,get(){return s.determineEventToEmit}}),Object.defineProperty(exports,"isDuplicateToolCall",{enumerable:!0,get(){return s.isDuplicateToolCall}}),Object.defineProperty(exports,"createToolCallHash",{enumerable:!0,get(){return s.createToolCallHash}}),Object.defineProperty(exports,"tryMarkMessageProcessed",{enumerable:!0,get(){return s.tryMarkMessageProcessed}})},7252(module){module.exports=require("express")},7453(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCompleteRequestAction=async function(e,t){const{requestId:s,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
594
- if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
595
- i.info("Starting complete_request action",{requestId:s,reason:n})
596
- try{const e=await a.post(`/api/projects/${l}/requests/${s}/validate-completion`,{})
638
+ 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)
639
+ i.error("Git revert failed",{requestId:o,error:t})
640
+ try{await u.raw(["revert","--abort"])}catch(e){i.error("CRITICAL: git revert --abort failed \u2014 repo may be in inconsistent state",{requestId:o,error:e instanceof Error?e.message:e+""})}return{success:!1,message:"Git revert failed: "+t,error:t}}const f=await a.post(`/api/projects/${l}/requests/${o}/finalize-revert`,{reason:n,operationType:"revert",revertCommitHash:p,originalMergeCommitHash:d})
641
+ return f.success?{success:!0,message:"Successfully reverted request "+o,data:{operationType:"revert",originalMergeCommit:d,revertCommitHash:p,reason:n}}:(i.error("CRITICAL: Git revert succeeded but finalization failed",{requestId:o,revertCommitHash:p,error:f.error}),{success:!1,message:`CRITICAL: Git revert succeeded (commit: ${p.substring(0,8)}) but server finalization failed`,error:"Finalization failed"})}catch(e){const t=(0,r.getErrorMessage)(e)
642
+ return i.error("Revert_request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to revert request: "+t,error:t}}}
643
+ const s=t(3807),r=t(8824)},6800(e,exports,t){function s(e){return new Promise((t=>{const s=o.default.createServer()
644
+ s.once("error",(e=>{e.code,t(!1)})),s.once("listening",(()=>{s.close((()=>{t(!0)}))})),s.listen(e)}))}var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
645
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.isPortAvailable=s,exports.findAvailablePort=async function(e,t=10){for(let r=0;t>r;r++){const t=e+r
646
+ if(await s(t))return t}throw Error(`No available port found after ${t} attempts starting from port ${e}`)}
647
+ const o=r(t(9278))},6895(module){module.exports=require("chalk")},6928(module){module.exports=require("path")},6982(module){module.exports=require("crypto")},7163(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.debugTokenAccess=function(){},exports.compareTokens=function(){},exports.debugTokenStorage=function(){},exports.disableTokenDebugging=function(){},exports.enableTokenDebugging=function(){},new WeakMap},7210(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.tryMarkMessageProcessed=exports.createToolCallHash=exports.isDuplicateToolCall=exports.determineEventToEmit=exports.interpolatePrompt=exports.evaluateCondition=exports.HookCache=exports.HookExecutor=void 0
648
+ var s=t(3077)
649
+ Object.defineProperty(exports,"HookExecutor",{enumerable:!0,get(){return s.HookExecutor}})
650
+ var r=t(8740)
651
+ Object.defineProperty(exports,"HookCache",{enumerable:!0,get(){return r.HookCache}})
652
+ var o=t(4627)
653
+ Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return o.evaluateCondition}}),Object.defineProperty(exports,"interpolatePrompt",{enumerable:!0,get(){return o.interpolatePrompt}}),Object.defineProperty(exports,"determineEventToEmit",{enumerable:!0,get(){return o.determineEventToEmit}}),Object.defineProperty(exports,"isDuplicateToolCall",{enumerable:!0,get(){return o.isDuplicateToolCall}}),Object.defineProperty(exports,"createToolCallHash",{enumerable:!0,get(){return o.createToolCallHash}}),Object.defineProperty(exports,"tryMarkMessageProcessed",{enumerable:!0,get(){return o.tryMarkMessageProcessed}})},7252(module){module.exports=require("express")},7453(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCompleteRequestAction=async function(e,t){const{requestId:o,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
654
+ if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
655
+ i.info("Starting complete_request action",{requestId:o,reason:n})
656
+ try{const e=await a.post(`/api/projects/${l}/requests/${o}/validate-completion`,{})
597
657
  if(!e.success||!e.data)return{success:!1,message:"Server validation failed: "+(e.data?.reason||"Unknown error"),error:"Validation failed"}
598
658
  const c=e.data
599
659
  if(!c.canComplete){let e="Cannot complete request: "+c.reason
600
- return c.lifoViolation?.blockingRequests?.length>0&&(e+="\n\nRequests that must be re-completed first:\n",c.lifoViolation.blockingRequests.forEach(((t,r)=>{e+=`${r+1}. ${t.requestId}: ${t.title}\n`}))),c.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",c.blockedByTasks.forEach(((t,r)=>{e+=`${r+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:c.reason}}if("revert-removal"!==c.mergeStrategy)return{success:!1,message:"Action server only handles reverted request re-completion. Got merge strategy: "+c.mergeStrategy,error:"Unsupported merge strategy: "+c.mergeStrategy}
660
+ return c.lifoViolation?.blockingRequests?.length>0&&(e+="\n\nRequests that must be re-completed first:\n",c.lifoViolation.blockingRequests.forEach(((t,s)=>{e+=`${s+1}. ${t.requestId}: ${t.title}\n`}))),c.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",c.blockedByTasks.forEach(((t,s)=>{e+=`${s+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:c.reason}}if("revert-removal"!==c.mergeStrategy)return{success:!1,message:"Action server only handles reverted request re-completion. Got merge strategy: "+c.mergeStrategy,error:"Unsupported merge strategy: "+c.mergeStrategy}
601
661
  if(!c.revertInfo?.revertCommitHash)return{success:!1,message:"Server validated but did not provide revert commit hash",error:"Missing revert commit hash"}
602
- const d=await async function(e,t,s){const{logger:n,projectPath:i}=s
662
+ const d=await async function(e,t,o){const{logger:n,projectPath:i}=o
603
663
  n.info("Performing revert removal",{requestId:e,revertCommitHash:t.substring(0,8)})
604
- const a=(0,r.simpleGit)(i),c=(await a.branch()).all.includes("main")?"main":"master",l=await a.status()
664
+ const a=(0,s.simpleGit)(i),c=(await a.branch()).all.includes("main")?"main":"master",l=await a.status()
605
665
  if(l.current!==c&&(n.info("Switching to default branch",{from:l.current,to:c}),await a.checkout(c)),(await a.status()).files.length>0)return{success:!1,message:"Cannot re-complete request: working directory has uncommitted changes",error:"Uncommitted changes present"}
606
- if((await a.revparse(["HEAD"])).trim()!==t){const e=await a.raw(["rev-list","--count",t+"..HEAD"]),r=parseInt(e.trim(),10)
607
- if(r>0)return{success:!1,message:`Cannot re-complete: main branch has ${r} commit(s) since the revert. Create a new request instead.`,error:"Main branch has changed since revert"}}try{const r=(await a.raw(["rev-parse",t+"^"])).trim()
608
- await a.reset(["--hard",r])
609
- const o=await a.revparse(["HEAD"])
610
- if(o.trim()!==r)throw Error(`Git reset verification failed: expected ${r.substring(0,8)}, got ${o.substring(0,8)}`)
611
- return n.info("Revert removal succeeded",{requestId:e,removedCommit:t.substring(0,8),restoredTo:r.substring(0,8)}),{success:!0,message:"Successfully re-completed request "+e,data:{operationType:"revert-removal",removedRevertCommit:t,restoredToCommit:r}}}catch(t){const r=(0,o.getErrorMessage)(t)
612
- return n.error("Revert removal failed",{requestId:e,error:r}),{success:!1,message:"Failed to remove revert commit: "+r,error:r}}}(s,c.revertInfo.revertCommitHash,t)
666
+ if((await a.revparse(["HEAD"])).trim()!==t){const e=await a.raw(["rev-list","--count",t+"..HEAD"]),s=parseInt(e.trim(),10)
667
+ if(s>0)return{success:!1,message:`Cannot re-complete: main branch has ${s} commit(s) since the revert. Create a new request instead.`,error:"Main branch has changed since revert"}}try{const s=(await a.raw(["rev-parse",t+"^"])).trim()
668
+ await a.reset(["--hard",s])
669
+ const r=await a.revparse(["HEAD"])
670
+ if(r.trim()!==s)throw Error(`Git reset verification failed: expected ${s.substring(0,8)}, got ${r.substring(0,8)}`)
671
+ return n.info("Revert removal succeeded",{requestId:e,removedCommit:t.substring(0,8),restoredTo:s.substring(0,8)}),{success:!0,message:"Successfully re-completed request "+e,data:{operationType:"revert-removal",removedRevertCommit:t,restoredToCommit:s}}}catch(t){const s=(0,r.getErrorMessage)(t)
672
+ return n.error("Revert removal failed",{requestId:e,error:s}),{success:!1,message:"Failed to remove revert commit: "+s,error:s}}}(o,c.revertInfo.revertCommitHash,t)
613
673
  if(!d.success)return d
614
- try{await a.post(`/api/projects/${l}/requests/${s}/finalize-completion`,{summary:n||"Re-completed from UI",acceptanceEvidence:[{evidence:"Re-completed reverted request via GUI action"}],mergeResult:{mergeCommitHash:d.data.restoredToCommit,method:"revert-removal"}})}catch(e){return i.error("Merge succeeded but finalization failed",{requestId:s,error:(0,o.getErrorMessage)(e)}),{success:!1,message:"Git revert removal succeeded but server finalization failed: "+(0,o.getErrorMessage)(e),error:"Finalization failed"}}return d}catch(e){const t=(0,o.getErrorMessage)(e)
615
- return i.error("Complete_request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to complete request: "+t,error:t}}}
616
- const r=t(3807),o=t(8824)},7767(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleApproveAndWorkRequestAction=async function(e,t){const{requestId:s}=e,{logger:n,apiClient:i,projectId:a,projectPath:c,trackChildPid:l}=t
617
- if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
618
- n.info("Starting approve-and-work-request action",{requestId:s})
619
- try{const e=await i.get(`/api/projects/${a}/requests/${s}`)
620
- if(!e.success||!e.data)return{success:!1,message:`Request ${s} not found`,error:"Request not found"}
674
+ try{await a.post(`/api/projects/${l}/requests/${o}/finalize-completion`,{summary:n||"Re-completed from UI",acceptanceEvidence:[{evidence:"Re-completed reverted request via GUI action"}],mergeResult:{mergeCommitHash:d.data.restoredToCommit,method:"revert-removal"}})}catch(e){return i.error("Merge succeeded but finalization failed",{requestId:o,error:(0,r.getErrorMessage)(e)}),{success:!1,message:"Git revert removal succeeded but server finalization failed: "+(0,r.getErrorMessage)(e),error:"Finalization failed"}}return d}catch(e){const t=(0,r.getErrorMessage)(e)
675
+ return i.error("Complete_request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to complete request: "+t,error:t}}}
676
+ const s=t(3807),r=t(8824)},7767(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleApproveAndWorkRequestAction=async function(e,t){const{requestId:o}=e,{logger:n,apiClient:i,projectId:a,projectPath:c,trackChildPid:l}=t
677
+ if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
678
+ n.info("Starting approve-and-work-request action",{requestId:o})
679
+ try{const e=await i.get(`/api/projects/${a}/requests/${o}`)
680
+ if(!e.success||!e.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
621
681
  const t=e.data
622
682
  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}
623
- if(!(await i.patch(`/api/projects/${a}/requests/${s}`,{status:"open"})).success)return{success:!1,message:"Failed to update request status",error:"Update failed"}
624
- const d=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",u=`Work on request ${s}: ${t.title}\n\nDescription: ${t.description}\n\n${d?"Acceptance Criteria:\n- "+d:""}\n\nThis request has just been approved. Please begin implementation.`
625
- let g=!1,h=!1
626
- 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 ${s} "${t.title}" has been approved. Please begin implementation.`,details:u}),g=!0,n.info("Stored session report for creating session",{requestId:s,sessionId:t.cliOrigin.sessionId})
627
- try{const e=(0,o.getOpenCodeServerUrl)(c,n)
628
- if(e){const r=`\u2705 [Request Approved]\n\nRequest ${s} "${t.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${u}`,o=`${e}/session/${t.cliOrigin.sessionId}/prompt_async`,i=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:r}]})})
629
- 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,r.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify creating session, spawning work agent instead",{requestId:s,error:(0,r.getErrorMessage)(e)})}if(!g)try{const e=await(0,o.spawnActionAgent)({agent:"work",prompt:u,cwd:c,projectPath:c,projectId:a,title:`[${s}] ${t.title}`,requestId:s,apiClient:i,logger:n,trackChildPid:l})
630
- h=e.success,h?n.info("Spawned work agent for request",{requestId:s,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:s,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:s,error:(0,r.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:s,notifiedSession:g,agentSpawned:h}),{success:!0,message:`Successfully approved request ${s}: ${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,r.getErrorMessage)(e)
631
- return n.error("approve-and-work-request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
632
- const r=t(8824),o=t(8148)},7960(e,exports){function t(){const e=process.argv[1]||""
683
+ if(!(await i.patch(`/api/projects/${a}/requests/${o}`,{status:"open"})).success)return{success:!1,message:"Failed to update request status",error:"Update failed"}
684
+ 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.`
685
+ let h=!1,g=!1
686
+ 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})
687
+ try{const e=(0,r.getOpenCodeServerUrl)(c,n)
688
+ 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}]})})
689
+ 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})
690
+ 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)
691
+ return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
692
+ const s=t(8824),r=t(8148)},7960(e,exports){function t(){const e=process.argv[1]||""
633
693
  if(e.includes("/dist-dev/")||e.includes("\\dist-dev\\"))return!0
634
694
  if(e.includes("/dist/")||e.includes("\\dist\\"))return!1
635
695
  const t=process.cwd()
636
- 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){function r(e,t,r){try{const o=`[${(new Date).toISOString()}] [detached-subagent] ${t}${r?" "+JSON.stringify(r):""}\n`,s=(0,a.join)(e||process.cwd(),".alchemy_lab","spawn-debug.log");(0,i.appendFileSync)(s,o)}catch{}}function o(e){const{agent:t,prompt:o,cwd:c,parentServerUrl:l,parentSessionId:d,sessionId:u,model:g,initialBranch:h,serverUrl:p,onSpawn:f,onError:m}=e
696
+ 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){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:c,parentServerUrl:l,parentSessionId:d,sessionId:u,model:h,initialBranch:g,serverUrl:p,onSpawn:f,onError:m}=e
637
697
  try{const n={...process.env}
638
- l&&(n.OPENCODE_PARENT_SERVER=l),h&&(n.ALCHEMY_INITIAL_BRANCH=h),e.allowParallel&&(n.ALCHEMY_ALLOW_PARALLEL="1")
698
+ l&&(n.OPENCODE_PARENT_SERVER=l),g&&(n.ALCHEMY_INITIAL_BRANCH=g),e.allowParallel&&(n.ALCHEMY_ALLOW_PARALLEL="1")
639
699
  const d=new Date,m=`${(d.getHours()+"").padStart(2,"0")}${(d.getMinutes()+"").padStart(2,"0")}${(d.getSeconds()+"").padStart(2,"0")}`,y=Math.random().toString(36).substring(2,7)
640
700
  let v
641
- if(n.ALCHEMY_SESSION_TAG=`mcp-${m}-${y}`,r(c,"Spawn env setup",{agent:t,initialBranch:h||"none",hasParentServer:!!l,sessionId:u||"new",attachMode:!!p}),v=p&&u&&o?["run","--attach",p,"--session",u,"--agent",t,o]:u&&o?["run","--session",u,"--agent",t,o]:o?["run","--agent",t,o]:["run","--agent",t],g){const e=v.findIndex((e=>e===o))
642
- e>0?v.splice(e,0,"--model",g):v.push("--model",g)}r(c,"Spawning opencode",{args:v.map((e=>e===o?`[prompt:${e.length}chars]`:e)),model:g||"not specified",promptLength:o?.length,promptPreview:o?.slice(0,100),cwd:c,hasSessionId:!!u,hasParentServerUrl:!!l})
701
+ if(n.ALCHEMY_SESSION_TAG=`mcp-${m}-${y}`,s(c,"Spawn env setup",{agent:t,initialBranch:g||"none",hasParentServer:!!l,sessionId:u||"new",attachMode:!!p}),v=p&&u&&r?["run","--attach",p,"--session",u,"--agent",t,r]:u&&r?["run","--session",u,"--agent",t,r]:r?["run","--agent",t,r]:["run","--agent",t],h){const e=v.findIndex((e=>e===r))
702
+ e>0?v.splice(e,0,"--model",h):v.push("--model",h)}s(c,"Spawning opencode",{args:v.map((e=>e===r?`[prompt:${e.length}chars]`:e)),model:h||"not specified",promptLength:r?.length,promptPreview:r?.slice(0,100),cwd:c,hasSessionId:!!u,hasParentServerUrl:!!l})
643
703
  const w=(0,a.join)(c||process.cwd(),".alchemy_lab"),C=(0,a.join)(w,"spawn-stderr.log")
644
- let I
645
- try{I=(0,i.openSync)(C,"a")}catch{}const E=(0,s.spawn)("opencode",v,{detached:!0,stdio:["ignore","ignore",I??"ignore"],cwd:c||process.cwd(),env:n})
646
- return E.unref(),f&&f(E.pid),{success:!0,pid:E.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
647
- return m&&e instanceof Error&&m(e),{success:!1,error:t}}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=o,exports.spawnJournalSubagent=function(e,t,r,s){return o({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:r,parentServerUrl:s})},exports.spawnWorkSubagent=function(e,t,r){return o({agent:"work",prompt:e,cwd:t,parentServerUrl:r})}
648
- const s=t(5317),n=t(8824),i=t(9896),a=t(6928)},8148(e,exports,t){function r(e,t){try{const t=(0,s.join)(e,a,"opencode.json"),r=(0,o.readFileSync)(t,"utf8"),n=JSON.parse(r)
649
- 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=r,exports.spawnActionAgent=async function(e){const{agent:t,prompt:o,cwd:s,projectPath:a,title:c,requestId:l,parentSessionId:d,allowParallel:u,apiClient:g,logger:h,trackChildPid:p}=e,f=r(a,h)
704
+ let k
705
+ try{k=(0,i.openSync)(C,"a")}catch{}const S=(0,o.spawn)("opencode",v,{detached:!0,stdio:["ignore","ignore",k??"ignore"],cwd:c||process.cwd(),env:n})
706
+ return S.unref(),f&&f(S.pid),{success:!0,pid:S.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
707
+ return m&&e instanceof Error&&m(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})}
708
+ const o=t(5317),n=t(8824),i=t(9896),a=t(6928)},8148(e,exports,t){function s(e,t){try{const t=(0,o.join)(e,a,"opencode.json"),s=(0,r.readFileSync)(t,"utf8"),n=JSON.parse(s)
709
+ 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:a,title:c,requestId:l,parentSessionId:d,allowParallel:u,apiClient:h,logger:g,trackChildPid:p}=e,f=s(a,g)
650
710
  let m,y=null
651
- f?y=await async function(e,t,r,o,s,n,a,c){try{const l=await fetch(e+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t,...s?{parentID:s}:{}})})
711
+ f?y=await async function(e,t,s,r,o,n,a,c){try{const l=await fetch(e+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t,...o?{parentID:o}:{}})})
652
712
  if(!l.ok)return c.warn("Failed to pre-create session via HTTP",{status:l.status}),null
653
713
  const d=await l.json()
654
714
  if(!d.id)return c.warn("Session created but no ID returned"),null
655
715
  const u=d.id
656
- c.info("Pre-created agent session",{sessionId:u,title:t,agent:r})
657
- try{await a.post(`/api/projects/${n}/cli-sessions`,{externalSessionId:u,providerId:"opencode",...s?{parentExternalSessionId:s}:{},agentType:r,status:"active"}),c.info("Registered session in alchemy DB",{sessionId:u,agent:r,parentSessionId:s})}catch(e){c.warn("Failed to register session in DB",{error:(0,i.getErrorMessage)(e)})}if(o&&n){try{await a.post(`/api/projects/${n}/session-request-associations`,{opencodeSessionId:u,requestLocalId:o}),c.info("Created session-request association",{sessionId:u,requestId:o})}catch(e){c.warn("Failed to create session-request association",{error:(0,i.getErrorMessage)(e)})}if("coder"===r)try{await a.patch(`/api/projects/${n}/requests/${o}`,{cliOrigin:{providerId:"opencode",sessionId:u,createdAt:(new Date).toISOString()}}),c.info("Updated request cliOrigin to coder session",{sessionId:u,requestId:o})}catch(e){c.warn("Failed to update request cliOrigin",{error:(0,i.getErrorMessage)(e)})}}return u}catch(e){return c.warn("Failed to pre-create session",{error:(0,i.getErrorMessage)(e)}),null}}(f,c,t,l,d,e.projectId,g,h):h.warn("No OpenCode server URL \u2014 agent session will not be visible in session list")
716
+ c.info("Pre-created agent session",{sessionId:u,title:t,agent:s})
717
+ try{await a.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,i.getErrorMessage)(e)})}if(r&&n){try{await a.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,i.getErrorMessage)(e)})}if("coder"===s)try{await a.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,i.getErrorMessage)(e)})}}return u}catch(e){return c.warn("Failed to pre-create session",{error:(0,i.getErrorMessage)(e)}),null}}(f,c,t,l,d,e.projectId,h,g):g.warn("No OpenCode server URL \u2014 agent session will not be visible in session list")
658
718
  try{const e=await fetch("http://localhost:3456/api/project-settings")
659
- if(e.ok){const r=await e.json()
660
- r.data?.defaultModel&&(m=r.data.defaultModel,h.info("Using user default model for agent spawn",{agent:t,model:m}))}}catch{}const v=(0,n.spawnDetachedSubagent)({agent:t,prompt:o,cwd:s,sessionId:y||void 0,serverUrl:f||void 0,model:m,allowParallel:u??!1,onSpawn(e){e&&p&&p(e)}})
661
- return v.success?(h.info("Spawned action agent",{agent:t,sessionId:y,pid:v.pid,requestId:l}),{success:!0,sessionId:y,pid:v.pid}):{success:!1,sessionId:y,error:v.error||"Spawn failed"}}
662
- const o=t(9896),s=t(6928),n=t(8035),i=t(8824),a=".alchemy_lab"},8524(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
719
+ if(e.ok){const s=await e.json()
720
+ s.data?.defaultModel&&(m=s.data.defaultModel,g.info("Using user default model for agent spawn",{agent:t,model:m}))}}catch{}const v=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:y||void 0,serverUrl:f||void 0,model:m,allowParallel:u??!1,onSpawn(e){e&&p&&p(e)}})
721
+ return v.success?(g.info("Spawned action agent",{agent:t,sessionId:y,pid:v.pid,requestId:l}),{success:!0,sessionId:y,pid:v.pid}):{success:!1,sessionId:y,error:v.error||"Spawn failed"}}
722
+ const r=t(9896),o=t(6928),n=t(8035),i=t(8824),a=".alchemy_lab"},8524(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
663
723
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
664
- const o=t(5086),s=r(t(6982))
665
- exports.CLIBridgeWebSocket=class{constructor(e,t=3e4){this.wss=null,this.uiClient=null,this.pendingCommands=new Map,this.heartbeatInterval=null,this.logger=e,this.defaultTimeout=t}attach(e){this.wss=new o.WebSocketServer({server:e,path:"/ws/cli-bridge"}),this.wss.on("connection",(e=>{this.logger.info("[CLIBridge] UI client connected"),this.uiClient&&(this.logger.warn("[CLIBridge] Replacing existing UI client connection"),this.uiClient.close(1e3,"Replaced by new connection")),this.uiClient=e,this.startHeartbeat(),e.on("message",(e=>{try{const t=JSON.parse(e.toString())
724
+ const r=t(5086),o=s(t(6982))
725
+ exports.CLIBridgeWebSocket=class{constructor(e,t=3e4){this.wss=null,this.uiClient=null,this.pendingCommands=new Map,this.heartbeatInterval=null,this.logger=e,this.defaultTimeout=t}attach(e){this.wss=new r.WebSocketServer({server:e,path:"/ws/cli-bridge"}),this.wss.on("connection",(e=>{this.logger.info("[CLIBridge] UI client connected"),this.uiClient&&(this.logger.warn("[CLIBridge] Replacing existing UI client connection"),this.uiClient.close(1e3,"Replaced by new connection")),this.uiClient=e,this.startHeartbeat(),e.on("message",(e=>{try{const t=JSON.parse(e.toString())
666
726
  this.handleResponse(t)}catch(e){this.logger.error("[CLIBridge] Failed to parse message from UI",{error:e instanceof Error?e.message:e+""})}})),e.on("close",(()=>{this.logger.info("[CLIBridge] UI client disconnected"),this.uiClient===e&&(this.uiClient=null),this.stopHeartbeat()
667
- for(const[e,t]of this.pendingCommands)clearTimeout(t.timer),t.resolve({id:e,success:!1,error:"UI client disconnected during command. The command may have been partially executed."}),this.pendingCommands.delete(e)})),e.on("error",(e=>{this.logger.error("[CLIBridge] WebSocket error",{error:e.message})}))})),this.logger.info("[CLIBridge] WebSocket server attached at /ws/cli-bridge")}isUIConnected(){return null!==this.uiClient&&this.uiClient.readyState===o.WebSocket.OPEN}async sendCommand(e,t={},r){if(!this.isUIConnected())return{id:"",success:!1,error:"alchemy-ui is not connected. Please open alchemy-ui in your browser first."}
668
- const o=s.default.randomUUID(),n={id:o,action:e,params:t}
669
- return new Promise(((t,s)=>{const i=setTimeout((()=>{this.pendingCommands.delete(o),t({id:o,success:!1,error:`Command '${e}' timed out after ${(r||this.defaultTimeout)/1e3}s`})}),r||this.defaultTimeout)
670
- this.pendingCommands.set(o,{resolve:t,reject:s,timer:i})
671
- try{this.uiClient.send(JSON.stringify(n))}catch(e){clearTimeout(i),this.pendingCommands.delete(o),t({id:o,success:!1,error:"Failed to send command: "+(e instanceof Error?e.message:e+"")})}}))}handleResponse(e){const t=this.pendingCommands.get(e.id)
672
- t?(clearTimeout(t.timer),this.pendingCommands.delete(e.id),t.resolve(e)):this.logger.warn("[CLIBridge] Received response for unknown command",{id:e.id})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatInterval=setInterval((()=>{this.uiClient&&this.uiClient.readyState===o.WebSocket.OPEN&&this.uiClient.ping()}),3e4)}stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}close(){this.stopHeartbeat()
727
+ for(const[e,t]of this.pendingCommands)clearTimeout(t.timer),t.resolve({id:e,success:!1,error:"UI client disconnected during command. The command may have been partially executed."}),this.pendingCommands.delete(e)})),e.on("error",(e=>{this.logger.error("[CLIBridge] WebSocket error",{error:e.message})}))})),this.logger.info("[CLIBridge] WebSocket server attached at /ws/cli-bridge")}isUIConnected(){return null!==this.uiClient&&this.uiClient.readyState===r.WebSocket.OPEN}async sendCommand(e,t={},s){if(!this.isUIConnected())return{id:"",success:!1,error:"alchemy-ui is not connected. Please open alchemy-ui in your browser first."}
728
+ const r=o.default.randomUUID(),n={id:r,action:e,params:t}
729
+ return new Promise(((t,o)=>{const i=setTimeout((()=>{this.pendingCommands.delete(r),t({id:r,success:!1,error:`Command '${e}' timed out after ${(s||this.defaultTimeout)/1e3}s`})}),s||this.defaultTimeout)
730
+ this.pendingCommands.set(r,{resolve:t,reject:o,timer:i})
731
+ try{this.uiClient.send(JSON.stringify(n))}catch(e){clearTimeout(i),this.pendingCommands.delete(r),t({id:r,success:!1,error:"Failed to send command: "+(e instanceof Error?e.message:e+"")})}}))}handleResponse(e){const t=this.pendingCommands.get(e.id)
732
+ t?(clearTimeout(t.timer),this.pendingCommands.delete(e.id),t.resolve(e)):this.logger.warn("[CLIBridge] Received response for unknown command",{id:e.id})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatInterval=setInterval((()=>{this.uiClient&&this.uiClient.readyState===r.WebSocket.OPEN&&this.uiClient.ping()}),3e4)}stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}close(){this.stopHeartbeat()
673
733
  for(const[e,t]of this.pendingCommands)clearTimeout(t.timer),t.reject(Error("Bridge shutting down")),this.pendingCommands.delete(e)
674
734
  this.uiClient&&(this.uiClient.close(1e3,"Server shutting down"),this.uiClient=null),this.wss&&(this.wss.close(),this.wss=null)}}
675
- let n=null},8577(module){module.exports=require("cors")},8740(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookCache=void 0,exports.HookCache=class{constructor(e,t,r){this.cache=new Map,this.logger=e,this.projectId=t,this.apiClient=r}setProjectId(e){this.projectId=e}getCacheKey(e,t){return t?`${e}:session:${t}`:e+":all"}async getHooksForEvent(e,t,r){const o=this.getCacheKey(e,t)
676
- if(!r){const e=this.cache.get(o)
677
- if(e&&6e4>Date.now()-e.time)return e.hooks.filter((e=>e.enabled))}try{const r=t?"?sessionId="+encodeURIComponent(t):"",s=await this.apiClient.get(`/api/projects/${this.projectId}/event-hooks/by-event/${e}${r}`),n=(s?.data||s||[]).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})))
678
- return this.cache.set(o,{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){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){Object.defineProperty(exports,"__esModule",{value:!0}),exports.MCPClientConfigurer=void 0
679
- const r=t(8824)
680
- class o{constructor(e,t,r){this.logger=e,this.remoteServerUrl=t||process.env.REMOTE_SERVER_URL,this.authToken=r}async seedAgentsFromRemote(e){const t=this.remoteServerUrl
735
+ let n=null},8577(module){module.exports=require("cors")},8740(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookCache=void 0,exports.HookCache=class{constructor(e,t,s){this.cache=new Map,this.logger=e,this.projectId=t,this.apiClient=s}setProjectId(e){this.projectId=e}getCacheKey(e,t){return t?`${e}:session:${t}`:e+":all"}async getHooksForEvent(e,t,s){const r=this.getCacheKey(e,t)
736
+ if(!s){const e=this.cache.get(r)
737
+ 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})))
738
+ 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){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){Object.defineProperty(exports,"__esModule",{value:!0}),exports.MCPClientConfigurer=void 0
739
+ const s=t(8824)
740
+ 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
681
741
  if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot initialize agents"),!1
682
- try{const r=t+"/api/agents/seed"
683
- this.logger.info("Ensuring built-in agents exist",{url:r})
684
- const o={"Content-Type":"application/json","X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"}
685
- this.authToken&&(o.Authorization="Bearer "+this.authToken)
686
- const s=await fetch(r,{method:"POST",headers:o})
687
- s.ok||this.logger.warn("Failed to seed global agents",{status:s.status,statusText:s.statusText})
742
+ try{const s=t+"/api/agents/seed"
743
+ this.logger.info("Ensuring built-in agents exist",{url:s})
744
+ const r={"Content-Type":"application/json","X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"}
745
+ this.authToken&&(r.Authorization="Bearer "+this.authToken)
746
+ const o=await fetch(s,{method:"POST",headers:r})
747
+ o.ok||this.logger.warn("Failed to seed global agents",{status:o.status,statusText:o.statusText})
688
748
  const n=`${t}/api/projects/${e}/agents/initialize`
689
749
  this.logger.info("Initializing project agent activations",{url:n})
690
- const i=await fetch(n,{method:"POST",headers:o})
750
+ const i=await fetch(n,{method:"POST",headers:r})
691
751
  if(!i.ok)return this.logger.warn("Failed to initialize project agents",{status:i.status,statusText:i.statusText}),!1
692
752
  const a=await i.json()
693
- return this.logger.info("Initialized project agent activations",{initialized:a.data?.initialized||0}),!0}catch(e){return this.logger.warn("Error initializing agents from remote",{error:(0,r.getErrorMessage)(e)}),!1}}async fetchAgentsFromRemote(e){const t=this.remoteServerUrl
753
+ return this.logger.info("Initialized project agent activations",{initialized:a.data?.initialized||0}),!0}catch(e){return this.logger.warn("Error initializing agents from remote",{error:(0,s.getErrorMessage)(e)}),!1}}async fetchAgentsFromRemote(e){const t=this.remoteServerUrl
694
754
  if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot fetch agents from remote"),null
695
- try{const r=`${t}/api/projects/${e}/agents/enabled`
696
- this.logger.info("Fetching agents from remote",{url:r})
697
- const o={"Content-Type":"application/json"}
698
- this.authToken&&(o.Authorization="Bearer "+this.authToken)
699
- const s=await fetch(r,{method:"GET",headers:o})
700
- if(!s.ok)return this.logger.warn("Failed to fetch agents from remote",{status:s.status,statusText:s.statusText}),null
701
- const n=await s.json(),i=n.data||n
702
- return this.logger.info("Fetched agents from remote",{count:i.length}),i}catch(e){return this.logger.warn("Error fetching agents from remote",{error:(0,r.getErrorMessage)(e)}),null}}async fetchProjectFromRemote(e){const t=this.remoteServerUrl
755
+ try{const s=`${t}/api/projects/${e}/agents/enabled`
756
+ this.logger.info("Fetching agents from remote",{url:s})
757
+ const r={"Content-Type":"application/json"}
758
+ this.authToken&&(r.Authorization="Bearer "+this.authToken)
759
+ const o=await fetch(s,{method:"GET",headers:r})
760
+ if(!o.ok)return this.logger.warn("Failed to fetch agents from remote",{status:o.status,statusText:o.statusText}),null
761
+ const n=await o.json(),i=n.data||n
762
+ return this.logger.info("Fetched agents from remote",{count:i.length}),i}catch(e){return this.logger.warn("Error fetching agents from remote",{error:(0,s.getErrorMessage)(e)}),null}}async fetchProjectFromRemote(e){const t=this.remoteServerUrl
703
763
  if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot fetch project from remote"),null
704
- try{const r=await fetch(`${t}/api/projects/${e}`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:"Bearer "+this.authToken}:{}}})
705
- if(!r.ok)return this.logger.warn("Failed to fetch project from remote",{projectId:e,status:r.status,statusText:r.statusText}),null
706
- const o=await r.json()
707
- return o.data||o}catch(t){return this.logger.error("Error fetching project from remote",{projectId:e,error:(0,r.getErrorMessage)(t)}),null}}generateAgentMarkdown(e,t,r,s){const n=Object.entries(e.tools||{}).map((([e,r])=>{const s=e.replace(/-/g,"_")
708
- return` ${o.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${s}`}: ${r}`})).join("\n")
709
- let i=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${r}"\n`
764
+ try{const s=await fetch(`${t}/api/projects/${e}`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:"Bearer "+this.authToken}:{}}})
765
+ if(!s.ok)return this.logger.warn("Failed to fetch project from remote",{projectId:e,status:s.status,statusText:s.statusText}),null
766
+ const r=await s.json()
767
+ return r.data||r}catch(t){return this.logger.error("Error fetching project from remote",{projectId:e,error:(0,s.getErrorMessage)(t)}),null}}generateAgentMarkdown(e,t,s,o){const n=Object.entries(e.tools||{}).map((([e,s])=>{const o=e.replace(/-/g,"_")
768
+ return` ${r.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${o}`}: ${s}`})).join("\n")
769
+ let i=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${s}"\n`
710
770
  if(e.color&&(i+=`color: "${e.color}"\n`),i+=`tools:\n${n}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){i+="permission:\n"
711
- for(const[t,r]of Object.entries(e.opencodePermission))if("object"==typeof r&&null!==r){i+=` ${t}:\n`
712
- for(const[e,t]of Object.entries(r))i+=` ${e}: ${t}\n`}else i+=` ${t}: ${r}\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",s&&(i+=s.trim()+"\n\n"),e.prompt&&(i+=e.prompt),e.promptAddition&&(i+="\n\n"+e.promptAddition),i}async configureOpenCode(e,s,n="node",i,a){try{const r=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")
713
- this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:s})
714
- const g=c.join(s,"opencode.json")
715
- let h=null,p=null,f=null,m=null,y=!1,v=!1
716
- const w=c.join(s,".alchemy_lab","config.json")
717
- if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
718
- 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 r.readFile(g,"utf-8"),t=JSON.parse(e)
719
- 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},E=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
720
- i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:m&&(I.model=m)
721
- const b=c.join(s,".opencode","agent")
722
- await r.mkdir(b,{recursive:!0})
723
- const k=E||"anthropic/claude-opus-4-5-20251101"
724
- let j,T=null,S=null
725
- if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),S=await this.fetchProjectFromRemote(i.projectId),j=S?.metadata?.globalSystemPrompt,j&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:j.length}),T=await this.fetchAgentsFromRemote(i.projectId),T&&T.length>0){this.logger.info("Using agents from remote database",{count:T.length})
726
- for(const e of T){if(!e.enabled)continue
727
- const t=this.generateAgentMarkdown(e,d,k,j),o=c.join(b,e.name+".md")
728
- await r.writeFile(o,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:o,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
771
+ for(const[t,s]of Object.entries(e.opencodePermission))if("object"==typeof s&&null!==s){i+=` ${t}:\n`
772
+ 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")
773
+ this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:o})
774
+ const h=c.join(o,"opencode.json")
775
+ let g=null,p=null,f=null,m=null,y=!1,v=!1
776
+ const w=c.join(o,".alchemy_lab","config.json")
777
+ if(!i){try{const e=await s.readFile(w,"utf-8"),t=JSON.parse(e)
778
+ 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)
779
+ 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"
780
+ i?.openCodeModel?k.model=i.openCodeModel:p?k.model=p:m&&(k.model=m)
781
+ const I=c.join(o,".opencode","agent")
782
+ await s.mkdir(I,{recursive:!0})
783
+ const E=S||"anthropic/claude-opus-4-5-20251101"
784
+ let b,j=null,T=null
785
+ if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),b=T?.metadata?.globalSystemPrompt,b&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:b.length}),j=await this.fetchAgentsFromRemote(i.projectId),j&&j.length>0){this.logger.info("Using agents from remote database",{count:j.length})
786
+ for(const e of j){if(!e.enabled)continue
787
+ const t=this.generateAgentMarkdown(e,d,E,b),r=c.join(I,e.name+".md")
788
+ 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")
729
789
  else this.logger.warn("No projectId provided - agents must be configured via database")
730
- const P={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0}}}
731
- if(l&&process.env.REMOTE_SERVER_URL&&(P.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),T&&T.length>0){P.agent={}
732
- for(const e of T){if(!e.enabled)continue
790
+ const P={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,o],enabled:!0}}}
791
+ if(l&&process.env.REMOTE_SERVER_URL&&(P.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),j&&j.length>0){P.agent={}
792
+ for(const e of j){if(!e.enabled)continue
733
793
  const t={}
734
- for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
735
- t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=S?.metadata?.bashToolAgents||{}
736
- e.name in r&&(t.bash=!!r[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name)&&void 0===t.task&&(t.task=!1)
737
- let s=""
738
- j&&(s+=j.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),P.agent[e.name]={tools:t,...s?{prompt:s}:{},...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}}
739
- const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${j?j.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`
740
- await r.writeFile(c.join(b,"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=[]
741
- if(A.push("file://"+c.join(s,".opencode","plugin","inject-session.js")),A.push("file://"+c.join(s,".opencode","plugin","event-hooks.js")),A.push("file://"+c.join(s,".opencode","plugin","anthropic-auth.js")),A.push("file://"+c.join(s,".opencode","plugin","system-logger.js")),A.length>0&&(P.plugin=A),await r.writeFile(g,JSON.stringify(P,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:g,plugins:A}),v&&!i)try{let e={}
742
- try{const t=await r.readFile(w,"utf-8")
743
- e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=C,await r.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:r}=await t.e(939).then(t.t.bind(t,9939,23)),o=await r(s,l??!1,this.logger,e)
744
- o.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:o.synced}),o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:g}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,r.getErrorMessage)(e)}),{success:!1,error:(0,r.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=o,o.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9067(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
794
+ for(const[s,o]of Object.entries(e.tools||{})){const e=o,n=s.replace(/-/g,"_")
795
+ t[r.OPENCODE_BUILTIN_TOOLS.has(s)?s:`${d}_${n}`]=e}const s=T?.metadata?.bashToolAgents||{}
796
+ e.name in s&&(t.bash=!!s[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name)&&void 0===t.task&&(t.task=!1)
797
+ let o=""
798
+ b&&(o+=b.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}}
799
+ const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${b?b.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`
800
+ 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=[]
801
+ if(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}),v&&!i)try{let e={}
802
+ try{const t=await s.readFile(w,"utf-8")
803
+ 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)
804
+ 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){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
745
805
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
746
- const o=r(t(6982)),s=t(8824)
806
+ const r=s(t(6982)),o=t(8824)
747
807
  var n
748
- !function(e){e.NOT_STARTED="not_started",e.POLLING="polling",e.AUTHENTICATED="authenticated",e.TIMEOUT="timeout",e.ERROR="error"}(n||(exports.PollingStatus=n={})),exports.ExternalAuthFlow=class{constructor(e,t,r=5e3,o=9e5){this.pollingStatus=n.NOT_STARTED,this.pollingStartTime=0,this.pollingIntervalId=null,this.tokenResult=null,this.pollingError=null,this.logger=e.child({component:"auth-flow"}),this.remoteServerUrl=t,this.pollingIntervalMs=r,this.maxPollingTimeMs=o}getAuthUrl(e){let r=this.remoteServerUrl
749
- r=r.includes("api.alchemylabcode.com")?r.replace("api.alchemylabcode.com","alchemylabcode.com"):r.match(/localhost:\d+/)?r.replace(/localhost:\d+/,"localhost:3000"):r.replace(/\/\/api\./g,"//")
750
- const o=t(6928).basename(process.cwd()),s=`${r}/login?mcp_code=${e}&project=${encodeURIComponent(o)}`
751
- return this.logger.info("Generated auth URL",{url:s,frontendUrl:r,backendUrl:this.remoteServerUrl}),s}getPollingUrl(e){const t=`${this.remoteServerUrl}/auth/check-auth?code=${e}`
808
+ !function(e){e.NOT_STARTED="not_started",e.POLLING="polling",e.AUTHENTICATED="authenticated",e.TIMEOUT="timeout",e.ERROR="error"}(n||(exports.PollingStatus=n={})),exports.ExternalAuthFlow=class{constructor(e,t,s=5e3,r=9e5){this.pollingStatus=n.NOT_STARTED,this.pollingStartTime=0,this.pollingIntervalId=null,this.tokenResult=null,this.pollingError=null,this.logger=e.child({component:"auth-flow"}),this.remoteServerUrl=t,this.pollingIntervalMs=s,this.maxPollingTimeMs=r}getAuthUrl(e){let s=this.remoteServerUrl
809
+ s=s.includes("api.alchemylabcode.com")?s.replace("api.alchemylabcode.com","alchemylabcode.com"):s.match(/localhost:\d+/)?s.replace(/localhost:\d+/,"localhost:3000"):s.replace(/\/\/api\./g,"//")
810
+ const r=t(6928).basename(process.cwd()),o=`${s}/login?mcp_code=${e}&project=${encodeURIComponent(r)}`
811
+ return this.logger.info("Generated auth URL",{url:o,frontendUrl:s,backendUrl:this.remoteServerUrl}),o}getPollingUrl(e){const t=`${this.remoteServerUrl}/auth/check-auth?code=${e}`
752
812
  return this.logger.info("Generated polling URL",{url:t}),t}async requestTemporaryCode(){const e=this.remoteServerUrl+"/auth/generate-code"
753
813
  this.logger.info("Requesting temporary code from remote server",{url:e})
754
814
  try{const t=await fetch(e)
755
815
  if(!t.ok){const e=await t.text().catch((()=>"Failed to read error response body"))
756
- throw Error(`Error requesting temporary code: ${t.status} ${t.statusText}\nResponse: ${e}`)}const r=await t.json()
757
- if(!r.success||!r.code)throw Error("Invalid response from server: "+JSON.stringify(r))
758
- const o=r.code
759
- return this.logger.info("Received temporary code from server",{code:o}),o}catch(e){throw this.logger.error("Failed to request temporary code",{error:(0,s.getErrorMessage)(e)}),e}}async startAuthFlow(){const e=await this.requestTemporaryCode(),t=this.getAuthUrl(e),r=this.getPollingUrl(e)
760
- return this.logger.info("Starting external authentication flow",{tempCode:e,authUrl:t,pollingUrl:r,remoteServerUrl:this.remoteServerUrl}),{tempCode:e,authUrl:t,pollingUrl:r}}parseTokenResponse(e){try{return e&&e.token&&e.expiresAt?{token:e.token,userId:e.userId||"unknown",username:e.username||"unknown",role:e.role||"user",expiresAt:e.expiresAt}:null}catch(e){return this.logger.error("Error parsing token response",{error:(0,s.getErrorMessage)(e)}),null}}startPolling(e,t,r,o){this.pollingStatus=n.POLLING,this.pollingStartTime=Date.now(),this.tokenResult=null,this.pollingError=null,this.stopPolling(),this.logger.info("Starting polling for authentication",{tempCode:e.substring(0,4)+"***",interval:this.pollingIntervalMs,maxTime:this.maxPollingTimeMs})
761
- const i=async()=>{try{const o=Date.now()-this.pollingStartTime
762
- if(o>this.maxPollingTimeMs)return this.logger.warn("Authentication polling timed out",{tempCode:e.substring(0,4)+"***",elapsedTime:o,maxTime:this.maxPollingTimeMs}),this.pollingStatus=n.TIMEOUT,this.stopPolling(),void(r&&r())
816
+ throw Error(`Error requesting temporary code: ${t.status} ${t.statusText}\nResponse: ${e}`)}const s=await t.json()
817
+ if(!s.success||!s.code)throw Error("Invalid response from server: "+JSON.stringify(s))
818
+ const r=s.code
819
+ return this.logger.info("Received temporary code from server",{code:r}),r}catch(e){throw this.logger.error("Failed to request temporary code",{error:(0,o.getErrorMessage)(e)}),e}}async startAuthFlow(){const e=await this.requestTemporaryCode(),t=this.getAuthUrl(e),s=this.getPollingUrl(e)
820
+ return this.logger.info("Starting external authentication flow",{tempCode:e,authUrl:t,pollingUrl:s,remoteServerUrl:this.remoteServerUrl}),{tempCode:e,authUrl:t,pollingUrl:s}}parseTokenResponse(e){try{return e&&e.token&&e.expiresAt?{token:e.token,userId:e.userId||"unknown",username:e.username||"unknown",role:e.role||"user",expiresAt:e.expiresAt}:null}catch(e){return this.logger.error("Error parsing token response",{error:(0,o.getErrorMessage)(e)}),null}}startPolling(e,t,s,r){this.pollingStatus=n.POLLING,this.pollingStartTime=Date.now(),this.tokenResult=null,this.pollingError=null,this.stopPolling(),this.logger.info("Starting polling for authentication",{tempCode:e.substring(0,4)+"***",interval:this.pollingIntervalMs,maxTime:this.maxPollingTimeMs})
821
+ const i=async()=>{try{const r=Date.now()-this.pollingStartTime
822
+ if(r>this.maxPollingTimeMs)return this.logger.warn("Authentication polling timed out",{tempCode:e.substring(0,4)+"***",elapsedTime:r,maxTime:this.maxPollingTimeMs}),this.pollingStatus=n.TIMEOUT,this.stopPolling(),void(s&&s())
763
823
  const i=this.getPollingUrl(e)
764
824
  this.logger.debug("Polling for authentication status",{tempCode:e,url:i})
765
- try{const r=await fetch(i)
766
- if(!r.ok){const e=await r.text().catch((()=>"Failed to read error response body"))
767
- throw Error(`Error polling for authentication: ${r.status} ${r.statusText}\nResponse: ${e}`)}const o=await r.json()
768
- if(this.logger.debug("Polling response",{data:o}),o.authenticated&&o.token){this.logger.info("Authentication successful",{tempCode:e.substring(0,4)+"***",userId:o.userId})
769
- const r=this.parseTokenResponse(o)
770
- if(!r)throw Error("Invalid token response from server")
771
- this.pollingStatus=n.AUTHENTICATED,this.tokenResult=r,this.stopPolling(),t&&t(r)}else this.logger.debug("Not authenticated yet, continuing to poll",{tempCode:e.substring(0,4)+"***"})}catch(e){const t=(0,s.getErrorMessage)(e)
772
- throw Error("Error during polling: "+t)}}catch(t){const r=(0,s.getErrorMessage)(t)
773
- this.logger.error("Error polling for authentication",{tempCode:e.substring(0,4)+"***",error:r}),this.pollingStatus=n.ERROR,this.pollingError=r,this.stopPolling(),o&&o(r)}}
825
+ try{const s=await fetch(i)
826
+ if(!s.ok){const e=await s.text().catch((()=>"Failed to read error response body"))
827
+ throw Error(`Error polling for authentication: ${s.status} ${s.statusText}\nResponse: ${e}`)}const r=await s.json()
828
+ if(this.logger.debug("Polling response",{data:r}),r.authenticated&&r.token){this.logger.info("Authentication successful",{tempCode:e.substring(0,4)+"***",userId:r.userId})
829
+ const s=this.parseTokenResponse(r)
830
+ if(!s)throw Error("Invalid token response from server")
831
+ this.pollingStatus=n.AUTHENTICATED,this.tokenResult=s,this.stopPolling(),t&&t(s)}else this.logger.debug("Not authenticated yet, continuing to poll",{tempCode:e.substring(0,4)+"***"})}catch(e){const t=(0,o.getErrorMessage)(e)
832
+ throw Error("Error during polling: "+t)}}catch(t){const s=(0,o.getErrorMessage)(t)
833
+ this.logger.error("Error polling for authentication",{tempCode:e.substring(0,4)+"***",error:s}),this.pollingStatus=n.ERROR,this.pollingError=s,this.stopPolling(),r&&r(s)}}
774
834
  return i(),this.pollingIntervalId=setInterval(i,this.pollingIntervalMs),()=>this.stopPolling()}stopPolling(){this.pollingIntervalId&&(clearInterval(this.pollingIntervalId),this.pollingIntervalId=null,this.logger.debug("Stopped polling for authentication",{status:this.pollingStatus}))}getPollingStatus(){const e={status:this.pollingStatus}
775
835
  return this.pollingStatus===n.AUTHENTICATED&&this.tokenResult?e.tokenInfo=this.tokenResult:this.pollingStatus===n.ERROR&&this.pollingError&&(e.error=this.pollingError),this.pollingStartTime>0&&(e.elapsedTimeMs=Date.now()-this.pollingStartTime),e}resetPollingState(){this.stopPolling(),this.pollingStatus=n.NOT_STARTED,this.pollingStartTime=0,this.tokenResult=null,this.pollingError=null}async startGoogleOAuthFlow(){this.logger.info("Starting Google OAuth authentication flow")
776
- try{const e=o.default.randomBytes(16).toString("hex"),t=`${this.remoteServerUrl}/api/auth/google/auth-url?state=${e}`,r=await fetch(t)
777
- if(!r.ok){const e=await r.text().catch((()=>"Failed to read error response body"))
778
- throw Error(`Error getting Google OAuth URL: ${r.status} ${r.statusText}\nResponse: ${e}`)}const s=await r.json()
779
- if(!s.success||!s.data||!s.data.authUrl)throw Error("Invalid response from server: "+JSON.stringify(s))
780
- const n=s.data.authUrl
781
- return this.logger.info("Received Google OAuth URL from server",{authUrl:n,state:e}),{authUrl:n,state:e}}catch(e){throw this.logger.error("Failed to start Google OAuth flow",{error:(0,s.getErrorMessage)(e)}),e}}async isGoogleOAuthAvailable(){try{const e=this.remoteServerUrl+"/api/auth/google/status",t=await fetch(e)
836
+ try{const e=r.default.randomBytes(16).toString("hex"),t=`${this.remoteServerUrl}/api/auth/google/auth-url?state=${e}`,s=await fetch(t)
837
+ if(!s.ok){const e=await s.text().catch((()=>"Failed to read error response body"))
838
+ throw Error(`Error getting Google OAuth URL: ${s.status} ${s.statusText}\nResponse: ${e}`)}const o=await s.json()
839
+ if(!o.success||!o.data||!o.data.authUrl)throw Error("Invalid response from server: "+JSON.stringify(o))
840
+ const n=o.data.authUrl
841
+ return this.logger.info("Received Google OAuth URL from server",{authUrl:n,state:e}),{authUrl:n,state:e}}catch(e){throw this.logger.error("Failed to start Google OAuth flow",{error:(0,o.getErrorMessage)(e)}),e}}async isGoogleOAuthAvailable(){try{const e=this.remoteServerUrl+"/api/auth/google/status",t=await fetch(e)
782
842
  if(!t.ok)return!1
783
- const r=await t.json()
784
- return r.success&&r.data&&!0===r.data.enabled}catch(e){return this.logger.debug("Error checking Google OAuth availability",{error:(0,s.getErrorMessage)(e)}),!1}}async startGoogleOAuthPolling(e,t,r,o,i){return this.logger.info("Starting Google OAuth polling",{state:t}),this.pollingStatus=n.POLLING,this.pollingStartTime=Date.now(),this.tokenResult=null,this.pollingError=null,this.logger.info("Google OAuth authentication URLs ready for user interaction"),this.pollingIntervalId=setInterval((async()=>{try{if(Date.now()-this.pollingStartTime>this.maxPollingTimeMs)return this.logger.info("Google OAuth polling timed out"),this.pollingStatus=n.TIMEOUT,this.stopPolling(),void(o&&o())
785
- const e=`${this.remoteServerUrl}/api/auth/google/check-status?state=${t}`,s=await fetch(e)
786
- if(!s.ok)return void this.logger.debug("Polling response not ok",{status:s.status})
787
- const i=await s.json()
843
+ const s=await t.json()
844
+ return s.success&&s.data&&!0===s.data.enabled}catch(e){return this.logger.debug("Error checking Google OAuth availability",{error:(0,o.getErrorMessage)(e)}),!1}}async startGoogleOAuthPolling(e,t,s,r,i){return this.logger.info("Starting Google OAuth polling",{state:t}),this.pollingStatus=n.POLLING,this.pollingStartTime=Date.now(),this.tokenResult=null,this.pollingError=null,this.logger.info("Google OAuth authentication URLs ready for user interaction"),this.pollingIntervalId=setInterval((async()=>{try{if(Date.now()-this.pollingStartTime>this.maxPollingTimeMs)return this.logger.info("Google OAuth polling timed out"),this.pollingStatus=n.TIMEOUT,this.stopPolling(),void(r&&r())
845
+ const e=`${this.remoteServerUrl}/api/auth/google/check-status?state=${t}`,o=await fetch(e)
846
+ if(!o.ok)return void this.logger.debug("Polling response not ok",{status:o.status})
847
+ const i=await o.json()
788
848
  if(this.logger.debug("Google OAuth polling response",{data:i}),i.success&&i.data&&i.data.authenticated&&i.data.token){this.logger.info("Google OAuth authentication completed")
789
849
  const e=this.parseTokenResponse(i.data)
790
- e&&(this.pollingStatus=n.AUTHENTICATED,this.tokenResult=e,this.stopPolling(),r&&r(e))}else this.logger.debug("Not authenticated yet, continuing to poll",{hasSuccess:!!i.success,hasData:!!i.data,authenticated:i.data?.authenticated})}catch(e){this.logger.error("Error during Google OAuth polling",{error:(0,s.getErrorMessage)(e)}),this.pollingStatus=n.ERROR,this.pollingError=(0,s.getErrorMessage)(e),this.stopPolling(),i&&i(this.pollingError)}}),this.pollingIntervalMs),{authUrl:e,state:t}}}},9266(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
791
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleOpenCodeProxyAction=async function(e,t){const{method:r,path:i,body:a,headers:c}=e
792
- if(!r||!i)return{success:!1,message:"method and path are required in proxy payload"}
793
- const l=function(e){const t=o.default.join(e,".alchemy_lab","opencode.json")
794
- if(!s.default.existsSync(t))return null
795
- try{return"http://localhost:"+JSON.parse(s.default.readFileSync(t,"utf-8")).port}catch{return null}}(t.projectPath)
850
+ e&&(this.pollingStatus=n.AUTHENTICATED,this.tokenResult=e,this.stopPolling(),s&&s(e))}else this.logger.debug("Not authenticated yet, continuing to poll",{hasSuccess:!!i.success,hasData:!!i.data,authenticated:i.data?.authenticated})}catch(e){this.logger.error("Error during Google OAuth polling",{error:(0,o.getErrorMessage)(e)}),this.pollingStatus=n.ERROR,this.pollingError=(0,o.getErrorMessage)(e),this.stopPolling(),i&&i(this.pollingError)}}),this.pollingIntervalMs),{authUrl:e,state:t}}}},9266(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
851
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleOpenCodeProxyAction=async function(e,t){const{method:s,path:i,body:a,headers:c}=e
852
+ if(!s||!i)return{success:!1,message:"method and path are required in proxy payload"}
853
+ const l=function(e){const t=r.default.join(e,".alchemy_lab","opencode.json")
854
+ if(!o.default.existsSync(t))return null
855
+ try{return"http://localhost:"+JSON.parse(o.default.readFileSync(t,"utf-8")).port}catch{return null}}(t.projectPath)
796
856
  if(!l)return{success:!1,message:"OpenCode not running (no opencode.json found)"}
797
857
  const d=`${l}${i.startsWith("/")?i:"/"+i}`
798
- try{const e={"Content-Type":"application/json",...c},o={method:r.toUpperCase(),headers:e}
799
- if("GET"!==r.toUpperCase()&&"HEAD"!==r.toUpperCase()&&void 0!==a){let e=a
800
- if("object"==typeof a&&null!==a&&"_alchemyAgentType"in a){const{_alchemyAgentType:t,...r}=a
801
- e=r}o.body="string"==typeof e?e:JSON.stringify(e)}const s=await fetch(d,o),l=s.headers.get("content-type")||""
858
+ try{const e={"Content-Type":"application/json",...c},r={method:s.toUpperCase(),headers:e}
859
+ if("GET"!==s.toUpperCase()&&"HEAD"!==s.toUpperCase()&&void 0!==a){let e=a
860
+ if("object"==typeof a&&null!==a&&"_alchemyAgentType"in a){const{_alchemyAgentType:t,...s}=a
861
+ e=s}r.body="string"==typeof e?e:JSON.stringify(e)}const o=await fetch(d,r),l=o.headers.get("content-type")||""
802
862
  let u
803
- if(u=l.includes("application/json")?await s.json():await s.text(),"POST"===r.toUpperCase()&&"/session"===i&&s.status>=200&&300>s.status&&u?.id)try{const e=u.id,r=a?.parentID
804
- await t.apiClient.post(`/api/projects/${t.projectId}/cli-sessions`,{externalSessionId:e,providerId:"opencode",...r?{parentExternalSessionId:r}:{},agentType:a?._alchemyAgentType||"unknown",status:"active"}),t.logger.info("Registered proxy-created session in DB",{sessionId:e,parentSessionId:r})}catch(e){t.logger.warn("Failed to register proxy-created session",{error:(0,n.getErrorMessage)(e)})}return{success:!0,message:"Proxy request completed",data:{status:s.status,contentType:l,body:u}}}catch(e){return{success:!1,message:"Proxy request failed: "+(0,n.getErrorMessage)(e),error:(0,n.getErrorMessage)(e)}}}
805
- const o=r(t(6928)),s=r(t(9896)),n=t(8824)},9278(module){module.exports=require("net")},9735(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleApproveRequestAction=async function(e,t){const{requestId:o}=e,{logger:s,apiClient:n,projectId:i}=t
806
- if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
807
- s.info("Starting approve-request action",{requestId:o})
808
- try{const e=await n.get(`/api/projects/${i}/requests/${o}`)
809
- if(!e.success||!e.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
863
+ if(u=l.includes("application/json")?await o.json():await o.text(),"POST"===s.toUpperCase()&&"/session"===i&&o.status>=200&&300>o.status&&u?.id)try{const e=u.id,s=a?.parentID
864
+ await t.apiClient.post(`/api/projects/${t.projectId}/cli-sessions`,{externalSessionId:e,providerId:"opencode",...s?{parentExternalSessionId:s}:{},agentType:a?._alchemyAgentType||"unknown",status:"active"}),t.logger.info("Registered proxy-created session in DB",{sessionId:e,parentSessionId:s})}catch(e){t.logger.warn("Failed to register proxy-created session",{error:(0,n.getErrorMessage)(e)})}return{success:!0,message:"Proxy request completed",data:{status:o.status,contentType:l,body:u}}}catch(e){return{success:!1,message:"Proxy request failed: "+(0,n.getErrorMessage)(e),error:(0,n.getErrorMessage)(e)}}}
865
+ const r=s(t(6928)),o=s(t(9896)),n=t(8824)},9278(module){module.exports=require("net")},9735(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleApproveRequestAction=async function(e,t){const{requestId:r}=e,{logger:o,apiClient:n,projectId:i}=t
866
+ if(!r)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
867
+ o.info("Starting approve-request action",{requestId:r})
868
+ try{const e=await n.get(`/api/projects/${i}/requests/${r}`)
869
+ if(!e.success||!e.data)return{success:!1,message:`Request ${r} not found`,error:"Request not found"}
810
870
  const t=e.data
811
- return"pending_approval"!==t.status?{success:!1,message:`Cannot approve request with status '${t.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+t.status}:(await n.patch(`/api/projects/${i}/requests/${o}`,{status:"open"})).success?(s.info("Approve-request action completed",{requestId:o}),{success:!0,message:`Successfully approved request ${o}: ${t.title}`,data:{operationType:"approve",previousStatus:"pending_approval",newStatus:"open"}}):{success:!1,message:"Failed to update request status",error:"Update failed"}}catch(e){const t=(0,r.getErrorMessage)(e)
812
- return s.error("approve-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
813
- const r=t(8824)},9896(module){module.exports=require("fs")}},n={}
814
- e.m=s,r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,e.t=function(o,s){if(1&s&&(o=this(o)),8&s)return o
815
- if("object"==typeof o&&o){if(4&s&&o.__esModule)return o
816
- if(16&s&&"function"==typeof o.then)return o}var n=Object.create(null)
871
+ return"pending_approval"!==t.status?{success:!1,message:`Cannot approve request with status '${t.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+t.status}:(await n.patch(`/api/projects/${i}/requests/${r}`,{status:"open"})).success?(o.info("Approve-request action completed",{requestId:r}),{success:!0,message:`Successfully approved request ${r}: ${t.title}`,data:{operationType:"approve",previousStatus:"pending_approval",newStatus:"open"}}):{success:!1,message:"Failed to update request status",error:"Update failed"}}catch(e){const t=(0,s.getErrorMessage)(e)
872
+ return o.error("approve-request action failed",{requestId:r,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
873
+ const s=t(8824)},9896(module){module.exports=require("fs")}},n={}
874
+ e.m=o,s=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,e.t=function(r,o){if(1&o&&(r=this(r)),8&o)return r
875
+ if("object"==typeof r&&r){if(4&o&&r.__esModule)return r
876
+ if(16&o&&"function"==typeof r.then)return r}var n=Object.create(null)
817
877
  e.r(n)
818
878
  var i={}
819
- t=t||[null,r({}),r([]),r(r)]
820
- for(var a=2&s&&o;"object"==typeof a&&!~t.indexOf(a);a=r(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>o[e]))
821
- return i.default=()=>o,e.d(n,i),n},e.d=(exports,t)=>{for(var r in t)e.o(t,r)&&!e.o(exports,r)&&Object.defineProperty(exports,r,{enumerable:!0,get:t[r]})},e.f={},e.e=t=>Promise.all(Object.keys(e.f).reduce(((r,o)=>(e.f[o](t,r),r)),[])),e.u=e=>e+".js",e.o=(e,t)=>({}.hasOwnProperty.call(e,t)),e.r=exports=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})},o={27:1},e.f.require=t=>{o[t]||(t=>{var r=t.modules,s=t.ids,n=t.runtime
822
- for(var i in r)e.o(r,i)&&(e.m[i]=r[i])
879
+ t=t||[null,s({}),s([]),s(s)]
880
+ for(var a=2&o&&r;"object"==typeof a&&!~t.indexOf(a);a=s(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>r[e]))
881
+ return i.default=()=>r,e.d(n,i),n},e.d=(exports,t)=>{for(var s in t)e.o(t,s)&&!e.o(exports,s)&&Object.defineProperty(exports,s,{enumerable:!0,get:t[s]})},e.f={},e.e=t=>Promise.all(Object.keys(e.f).reduce(((s,r)=>(e.f[r](t,s),s)),[])),e.u=e=>e+".js",e.o=(e,t)=>({}.hasOwnProperty.call(e,t)),e.r=exports=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})},r={27:1},e.f.require=t=>{r[t]||(t=>{var s=t.modules,o=t.ids,n=t.runtime
882
+ for(var i in s)e.o(s,i)&&(e.m[i]=s[i])
823
883
  n&&n(e)
824
- for(var a=0;a<s.length;a++)o[s[a]]=1})(require("./"+e.u(t)))}
825
- var i={};(()=>{function t(){const e=[(0,g.join)(__dirname,"..","package.json"),(0,g.join)(__dirname,"..","..","package.json")]
884
+ for(var a=0;a<o.length;a++)r[o[a]]=1})(require("./"+e.u(t)))}
885
+ var i={};(()=>{function t(){const e=[(0,h.join)(__dirname,"..","package.json"),(0,h.join)(__dirname,"..","..","package.json")]
826
886
  for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
827
- if(e.version)return e.version}catch{}return"unknown"}function r(){const e=t()
828
- 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 o(e){const t=(0,g.join)(e,E.ALCHEMY_DIR_NAME,"project.json")
829
- return(0,d.existsSync)(t)}function s(e){const t=(0,p.platform)(),r="darwin"===t?"open":"win32"===t?"cmd":"xdg-open",o="win32"===t?["/c","start","",e]:[e];(0,h.spawn)(r,o,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((r=>{const o=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(o),r(!0))}),2e3)
830
- setTimeout((()=>{clearInterval(o),r(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
831
- try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),o=r(t)
832
- return(await o.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,r,o,s="cli-init"){try{const n=(0,g.basename)(r)||"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:r,createdVia:s}})})
887
+ if(e.version)return e.version}catch{}return"unknown"}function s(){const e=t()
888
+ 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")
889
+ 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)
890
+ setTimeout((()=>{clearInterval(r),s(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
891
+ try{const{createConfigManager:s}=await e.e(60).then(e.t.bind(e,9060,23)),r=s(t)
892
+ 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}})})
833
893
  if(i.ok){const e=await i.json()
834
- return e.data?.id||e.id}o.info(`Project registration failed (HTTP ${i.status})`)}catch(e){o.info("Project registration failed",{error:(0,C.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
835
- 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),E=e(4139);(async function(){const i=process.argv.slice(2)
894
+ 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})
895
+ 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)
836
896
  let p
837
- const b=i.indexOf("--port");-1!==b&&i[b+1]&&(p=parseInt(i[b+1],10),isNaN(p)&&(console.error("\u274c Invalid port number"),process.exit(1)))
838
- const k=i.filter(((e,t)=>"--port"!==e&&(-1===b||t!==b+1)))
839
- if(0===k.length)return void await async function(r={}){const i=process.cwd(),l=(0,g.join)(i,E.ALCHEMY_DIR_NAME)
897
+ 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)))
898
+ const E=i.filter(((e,t)=>"--port"!==e&&(-1===I||t!==I+1)))
899
+ if(0===E.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
840
900
  await(0,u.mkdir)(l,{recursive:!0})
841
- const p=(0,g.join)(l,"server.log"),{createLogger:b}=await Promise.resolve().then(e.t.bind(e,2782,23)),k=b({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),j=await a(k),T=new f.TokenManager(k,i,{remoteServerUrl:j,allowInteractiveAuth:!1})
842
- await T.initialize()
843
- let S=await T.getToken()
844
- if(!S?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
845
- const e=await T.startAuthenticationFlow()
846
- console.log("\u{1f310} "+e.authUrl),s(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(T)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),S=await T.getToken(),console.log(`\u2705 Welcome, ${S?.username||"user"}!`),console.log("")}let P=S?.token
847
- await async function(e,t,r,s){if(o(e))return
901
+ const p=(0,h.join)(l,"server.log"),{createLogger:I}=await Promise.resolve().then(e.t.bind(e,2782,23)),E=I({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),b=await a(E),j=new f.TokenManager(E,i,{remoteServerUrl:b,allowInteractiveAuth:!1})
902
+ await j.initialize()
903
+ let T=await j.getToken()
904
+ if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
905
+ const e=await j.startAuthenticationFlow()
906
+ 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
907
+ await async function(e,t,s,o){if(r(e))return
848
908
  console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
849
- const n=(0,g.join)(e,E.ALCHEMY_DIR_NAME)
909
+ const n=(0,h.join)(e,S.ALCHEMY_DIR_NAME)
850
910
  let i
851
- await(0,u.mkdir)(n,{recursive:!0}),r?(console.log(`\u{1f4cb} Registering project "${(0,g.basename)(e)||"Local Project"}"...`),i=await c(t,r,e,s),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")
852
- 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,j,P,k)
853
- const A=(0,g.join)(i,E.ALCHEMY_DIR_NAME,"project.json")
911
+ 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")
912
+ 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,b,P,E)
913
+ const A=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
854
914
  let M
855
915
  try{const e=JSON.parse((0,d.readFileSync)(A,"utf-8"))
856
- if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,k,"cli-init-retry")
857
- 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{k.warn("Could not read project.json")}await async function(t){try{return void(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let r="latest"
858
- try{const e=(0,g.join)(t,E.ALCHEMY_DIR_NAME,"config.json")
916
+ if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(b,P,i,E,"cli-init-retry")
917
+ 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{E.warn("Could not read project.json")}await async function(t){try{return void(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
918
+ try{const e=(0,h.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
859
919
  if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
860
- t.opencodeVersion&&(r=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
861
- const o="latest"===r?"latest":"v"+r
862
- try{(0,h.execFileSync)("which",["go"],{stdio:"pipe"}),console.log(" Running: go install github.com/sst/opencode@"+o),(0,h.execFileSync)("go",["install","github.com/sst/opencode@"+o],{stdio:"inherit",timeout:12e4})
863
- 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:o}=await Promise.resolve().then(e.t.bind(e,857,23)),s=t(),n=o(),i="darwin"===s?"darwin":"linux"===s?"linux":null,a="arm64"===n?"arm64":"x64"===n?"amd64":null
864
- if(i&&a){const e=`opencode_${i}_${a}`,t="latest"===r?"https://github.com/sst/opencode/releases/latest/download/"+e:`https://github.com/sst/opencode/releases/download/v${r}/${e}`
920
+ t.opencodeVersion&&(s=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
921
+ const r="latest"===s?"latest":"v"+s
922
+ 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})
923
+ 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
924
+ 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}`
865
925
  console.log(" Downloading from: "+t)
866
- const o=(0,g.join)(process.env.HOME||"/usr/local/bin",".local","bin")
867
- await(0,u.mkdir)(o,{recursive:!0})
868
- const s=(0,g.join)(o,"opencode")
869
- return(0,h.execFileSync)("curl",["-fsSL","-o",s,t],{stdio:"inherit",timeout:6e4}),(0,h.execFileSync)("chmod",["+x",s],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(o)||(console.log(` \u26a0\ufe0f Add ${o} to your PATH:`),console.log(` export PATH="${o}:$PATH"`),console.log(""),process.env.PATH=`${o}:${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)
926
+ const r=(0,h.join)(process.env.HOME||"/usr/local/bin",".local","bin")
927
+ await(0,u.mkdir)(r,{recursive:!0})
928
+ const o=(0,h.join)(r,"opencode")
929
+ 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)
870
930
  const _=t()
871
931
  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)
872
- const O=r.port||await(0,I.findAvailablePort)(3456),x=new y.AlchemyActionServer(k,{projectPath:i,remoteServerUrl:j,port:O,authToken:P})
932
+ const O=s.port||await(0,k.findAvailablePort)(3456),x=new y.AlchemyActionServer(E,{projectPath:i,remoteServerUrl:b,port:O,authToken:P})
873
933
  try{await x.start()
874
934
  let t=process.env.FRONTEND_URL
875
- if(!t)if(j.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
876
- else{try{const e=await fetch(j+"/health")
877
- if(e.ok){const r=await e.json()
878
- t=r?.data?.frontendUrl}}catch{}t||(t=j.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
879
- try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(k,j,P),r=new w.UnifiedLauncher(k,e,t),o=await r.launch({projectPath:i,settings:M?{projectId:M}:void 0})
880
- if(o.success&&o.port){o.pid&&x.trackChildPid(o.pid),x.setOpenCodePort(o.port),console.log(" OpenCode serve ... port "+o.port)
881
- const e=(0,g.join)(i,E.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:o.port,pid:o.pid,startedAt:(new Date).toISOString()},null,2)),k.info("Wrote OpenCode config",{path:e,port:o.port})}else{if(!o.success)throw Error(o.error||"Launch failed")
882
- console.log(" OpenCode serve ... started")}}catch(e){k.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("")
883
- let r=!1,o=null
884
- const a=async()=>{r||(r=!0,o&&clearInterval(o),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
935
+ if(!t)if(b.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
936
+ else{try{const e=await fetch(b+"/health")
937
+ if(e.ok){const s=await e.json()
938
+ t=s?.data?.frontendUrl}}catch{}t||(t=b.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
939
+ try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(E,b,P),s=new w.UnifiedLauncher(E,e,t),r=await s.launch({projectPath:i,settings:M?{projectId:M}:void 0})
940
+ if(r.success&&r.port){r.pid&&x.trackChildPid(r.pid),x.setOpenCodePort(r.port),console.log(" OpenCode serve ... port "+r.port)
941
+ 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)),E.info("Wrote OpenCode config",{path:e,port:r.port})}else{if(!r.success)throw Error(r.error||"Launch failed")
942
+ console.log(" OpenCode serve ... started")}}catch(e){E.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("")
943
+ let s=!1,r=null
944
+ const a=async()=>{s||(s=!0,r&&clearInterval(r),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
885
945
  process.on("SIGTERM",a),process.on("SIGINT",a)
886
- const{InteractiveShell:c}=await e.e(994).then(e.t.bind(e,5994,23)),l=new c({projectPath:i,logger:k,remoteServerUrl:j,authToken:P,projectId:M})
887
- o=setInterval((async()=>{try{if(await T.hasValidToken())return
946
+ const{InteractiveShell:c}=await e.e(994).then(e.t.bind(e,5994,23)),l=new c({projectPath:i,logger:E,remoteServerUrl:b,authToken:P,projectId:M})
947
+ r=setInterval((async()=>{try{if(await j.hasValidToken())return
888
948
  console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
889
- const e=await T.startAuthenticationFlow()
890
- if(console.log("\u{1f310} "+e.authUrl),s(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(T)){const e=await T.getToken()
891
- 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){k.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await l.start(a)}catch(e){k.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})
892
- const j=k[0].toLowerCase()
893
- switch("--help"!==j&&"-h"!==j||(r(),process.exit(0)),"--version"!==j&&"-v"!==j||(console.log("alchemy v"+t()),process.exit(0)),j){case"serve":await async function(e={}){const r=process.cwd()
894
- o(r)||(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))
895
- const s=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),n=t()
896
- console.log(`\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 AlchemyLab Server v${n.padEnd(10)} \u2551\n\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n`),console.log("\u{1f4c1} Project: "+r)
897
- const i=await a(s),c=new f.TokenManager(s,r,{remoteServerUrl:i,allowInteractiveAuth:!1})
949
+ const e=await j.startAuthenticationFlow()
950
+ 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()
951
+ 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){E.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await l.start(a)}catch(e){E.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})
952
+ const b=E[0].toLowerCase()
953
+ switch("--help"!==b&&"-h"!==b||(s(),process.exit(0)),"--version"!==b&&"-v"!==b||(console.log("alchemy v"+t()),process.exit(0)),b){case"serve":await async function(e={}){const s=process.cwd()
954
+ 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))
955
+ const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:s}),n=t()
956
+ console.log(`\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 AlchemyLab Server v${n.padEnd(10)} \u2551\n\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n`),console.log("\u{1f4c1} Project: "+s)
957
+ const i=await a(o),c=new f.TokenManager(o,s,{remoteServerUrl:i,allowInteractiveAuth:!1})
898
958
  await c.initialize()
899
959
  const d=await c.getToken(),u=d?.token
900
960
  u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
901
- const g=e.port||await(0,I.findAvailablePort)(3456),h=new y.AlchemyActionServer(s,{projectPath:r,remoteServerUrl:i,port:g,authToken:u})
902
- 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("")
961
+ const h=e.port||await(0,k.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:s,remoteServerUrl:i,port:h,authToken:u})
962
+ 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("")
903
963
  let e=!1
904
- const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await h.stop(),process.exit(0))}
905
- process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){s.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})
964
+ const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
965
+ 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})
906
966
  break
907
- case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),r=await a(t),o=new f.TokenManager(t,e,{remoteServerUrl:r,allowInteractiveAuth:!1})
908
- await o.initialize(),await o.hasValidToken()?await o.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
967
+ 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})
968
+ await r.initialize(),await r.hasValidToken()?await r.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
909
969
  break
910
- default:console.error("\u274c Unknown command: "+j),console.log(""),r(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()
970
+ default:console.error("\u274c Unknown command: "+b),console.log(""),s(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()