alchemylab 1.0.6 → 1.0.8

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/939.js CHANGED
@@ -8,7 +8,7 @@ try{await o.default.promises.mkdir(c,{recursive:!0})}catch(e){return r.error("Fa
8
8
  try{if(!o.default.existsSync(s)){r.warn("Plugin source not found: "+e.sourceName,{sourcePath:s}),l.errors.push(e.destName)
9
9
  continue}const i=await o.default.promises.readFile(s,"utf8").catch((()=>null))
10
10
  if(null===i){r.warn("Could not read plugin source: "+e.sourceName),l.errors.push(e.destName)
11
- continue}const d=e.destName.startsWith("lib/")?i:["const __plugin = (function() {\n const module = { exports: {} };\n const exports = module.exports;\n (function() {",i," })();\n return module.exports.default || module.exports;\n})();\nexport default __plugin;"].join("\n"),p=u.default.createHash("md5").update(d).digest("hex"),m=t(a)
12
- if(null===m||p!==m){const s=n.default.dirname(a)
13
- s!==c&&await o.default.promises.mkdir(s,{recursive:!0}),await o.default.promises.writeFile(a,d,"utf8"),r.info("Synced plugin: "+e.destName,{reason:null===m?"not installed":"updated"}),l.synced.push(e.destName)}else l.skipped.push(e.destName)}catch(s){r.error("Failed to sync plugin: "+e.destName,{error:s instanceof Error?s.message:"Unknown error"}),l.errors.push(e.destName)}}return l}
11
+ continue}const d=e.destName.startsWith("lib/")?i:['import { createRequire as __createRequire } from "node:module";\nconst require = __createRequire(import.meta.url);\nconst __plugin = (function() {\n const module = { exports: {} };\n const exports = module.exports;\n (function() {',i," })();\n return module.exports.default || module.exports;\n})();\nexport default __plugin;"].join("\n"),m=u.default.createHash("md5").update(d).digest("hex"),p=t(a)
12
+ if(null===p||m!==p){const s=n.default.dirname(a)
13
+ s!==c&&await o.default.promises.mkdir(s,{recursive:!0}),await o.default.promises.writeFile(a,d,"utf8"),r.info("Synced plugin: "+e.destName,{reason:null===p?"not installed":"updated"}),l.synced.push(e.destName)}else l.skipped.push(e.destName)}catch(s){r.error("Failed to sync plugin: "+e.destName,{error:s instanceof Error?s.message:"Unknown error"}),l.errors.push(e.destName)}}return l}
14
14
  const o=r(s(9896)),n=r(s(6928)),u=r(s(6982)),a=[{sourceName:"plugins/hooks/plugin.js",destName:"event-hooks.js"},{sourceName:"plugins/hooks/lib/condition-evaluator.js",destName:"lib/condition-evaluator.js"},{sourceName:"plugins/hooks/lib/hook-utils.js",destName:"lib/hook-utils.js"},{sourceName:"plugins/hooks/lib/dedup-utils.js",destName:"lib/dedup-utils.js"},{sourceName:"plugins/inject-session.js",destName:"inject-session.js"},{sourceName:"plugins/system-logger.js",destName:"system-logger.js"}]}}
package/dist/cli.js CHANGED
@@ -36,14 +36,16 @@ const s=t(5317),r=t(8824)
36
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
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
- let i="",a=!1
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}}
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,r){const{projectPath:o,envVars:n}=e
40
+ try{(0,s.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"OpenCode not found in PATH."}}this.logger.info("UnifiedLauncher: Launching opencode serve in background",{command:"opencode serve",cwd:o})
41
+ const{resolveServeCommand:i}=await Promise.resolve().then(t.bind(t,9068)),a=i()
42
+ return new Promise((e=>{const t=(0,s.spawn)(a.command,[...a.prependArgs,"--port","0"],{stdio:["ignore","pipe","pipe"],cwd:o,detached:!0,env:{...process.env,...n}})
43
+ let i="",c=!1
44
+ const l=s=>{i+=s
45
+ const o=i.match(/listening on http:\/\/[^:]+:(\d+)/)
46
+ if(o&&!c){const s=parseInt(o[1],10)
47
+ c=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:t.pid,port:s}),t.unref(),e({success:!0,pid:t.pid,port:s,configPath:r.configPath})}}
48
+ t.stdout?.on("data",(e=>l(e.toString()))),t.stderr?.on("data",(e=>l(e.toString()))),setTimeout((()=>{c||(c=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:i.slice(0,500)}),t.unref(),e({success:!0,pid:t.pid,configPath:r.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),t.on("error",(t=>{c||(c=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),t.on("exit",(t=>{c||(c=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:t,output:i.slice(0,500)}),e({success:!1,error:"opencode serve exited with code "+t}))}))}))}}},1253(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
47
49
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
48
50
  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
51
  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()
@@ -136,39 +138,39 @@ const i=await this.hookExecutor.processEvent({eventType:s,sessionId:r||"",variab
136
138
  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
139
  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
140
  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
- if(!e?.id)return void t.status(400).json({success:!1,error:"No project configuration found"})
141
+ t.json({port:r.port,pid:r.pid,startedAt:r.startedAt,baseUrl:"http://localhost:"+r.port,directory:this.options.projectPath,proxyEnabled:!0})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,g.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,g.getErrorMessage)(e)})}}async handleSyncAgents(e,s){try{const e=this.readProjectConfig()
142
+ if(!e?.id)return void s.status(400).json({success:!1,error:"No project configuration found"})
141
143
  this.logger.info("Syncing agents from remote",{projectId:e.id})
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
- const n=a.default.join(this.options.projectPath,".opencode","agent")
149
- await c.default.promises.mkdir(n,{recursive:!0})
150
- let i=0
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
144
+ const r=await this.configurer.fetchAgentsFromRemote(e.id)
145
+ if(!r||0===r.length)return void s.json({success:!0,message:"No agents to sync",count:0})
146
+ const o=await this.configurer.fetchProjectFromRemote(e.id)
147
+ this.logger.debug("Fetched project for global prompt",{projectId:e.id,hasProject:!!o,hasGlobalSystemPrompt:!!o?.metadata?.globalSystemPrompt})
148
+ const n=o?.metadata?.globalSystemPrompt
149
+ n?this.logger.info("Using global system prompt from project metadata",{projectId:e.id,promptLength:n.length}):this.logger.debug("No global system prompt found in project metadata")
150
+ const i=a.default.join(this.options.projectPath,".opencode","agent")
151
+ await c.default.promises.mkdir(i,{recursive:!0})
152
+ let l=0
153
+ for(const e of r){if(!e.enabled)continue
154
+ const t="alchemy",s=this.configurer.generateAgentMarkdown(e,t,"anthropic/claude-sonnet-4-20250514",n),r=a.default.join(i,e.name+".md")
155
+ await c.default.promises.writeFile(r,s,"utf8"),l++,this.logger.info("Synced agent file",{name:e.name,path:r,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
156
+ if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t),n=o?.metadata?.bashToolAgents||{}
157
+ 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])
158
+ c.default.writeFileSync(e,JSON.stringify(s,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,g.getErrorMessage)(e)})}let d=!1
157
159
  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"),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
+ if(c.default.existsSync(e)){const s=c.default.readFileSync(e,"utf-8"),r=JSON.parse(s)
161
+ if(r.pid)try{process.kill(r.pid,"SIGTERM"),this.logger.info("Killed existing OpenCode process",{pid:r.pid}),await new Promise((e=>setTimeout(e,500)))}catch(e){"ESRCH"!==e?.code&&this.logger.warn("Could not kill OpenCode",{error:(0,g.getErrorMessage)(e)})}const{resolveServeCommand:o}=await Promise.resolve().then(t.bind(t,9068)),n=o(),i=(0,y.spawn)(n.command,[...n.prependArgs,"--port","0"],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),a=new Promise((e=>{let t=""
160
162
  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
- const n=t.match(/listening on http:\/\/[^:]+:(\d+)/)
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
163
+ try{i.kill("SIGTERM")}catch{}e(null)}),1e4)
164
+ i.stdout?.on("data",(r=>{t+=r.toString()
165
+ const o=t.match(/listening on http:\/\/[^:]+:(\d+)/)
166
+ o&&(clearTimeout(s),i.unref(),e({port:parseInt(o[1],10),pid:i.pid}))})),i.on("error",(()=>{clearTimeout(s)
167
+ try{i.kill("SIGTERM")}catch{}e(null)}))})),l=await a
168
+ l&&(this.trackChildPid(l.pid),c.default.writeFileSync(e,JSON.stringify({port:l.port,pid:l.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:l.port,pid:l.pid}),this.setOpenCodePort(l.port),d=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,g.getErrorMessage)(e)})}s.json({success:!0,message:`Synced ${l} agent(s) to local files${d?" - OpenCode reloading":""}`,count:l,restarted:d})}catch(e){this.logger.error("Failed to sync agents",{error:(0,g.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleGenerateJournal(e,s){try{const{requestId:r}=e.body
169
+ if(!r)return void s.status(400).json({success:!1,error:"requestId is required"})
170
+ if(!/^req\d+$/.test(r))return void s.status(400).json({success:!1,error:"Invalid requestId format"})
171
+ this.logger.info("Generating journal for request",{requestId:r})
172
+ const o=`Generate a journal entry for request ${r}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${r}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${r}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,y.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
173
+ a.pid&&this.trackChildPid(a.pid),a.unref(),s.json({success:!0,message:"Journal generation started for "+r})}catch(e){this.logger.error("Failed to generate journal",{error:(0,g.getErrorMessage)(e)}),s.status(500).json({success:!1,error:(0,g.getErrorMessage)(e)})}}async handleEventHooksProxy(e,t){try{const{eventType:s}=e.params,{sessionId:r}=e.query,o=this.projectId
172
174
  if(!o)return void t.status(400).json({success:!1,error:"Project not registered"})
173
175
  this.logger.info("Proxying event hooks request",{projectId:o,eventType:s,sessionId:r})
174
176
  const n=r?"?sessionId="+encodeURIComponent(r):"",i=await this.apiClient.get(`/api/projects/${o}/event-hooks/by-event/${s}${n}`)
@@ -212,21 +214,25 @@ if(this.logger.info("Agent type bridge lookup",{sessionId:s,bridgeAgentType:e?.d
212
214
  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
215
  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
216
  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"})
217
+ const r=e.path.replace(/^\//,""),o=e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):"",n=`${s}/${r}${o}`,i=e.headers.accept?.includes("text/event-stream")
218
+ if(i){const s=new AbortController,r=()=>{this.proxyAbortControllers.delete(s)},o=(async()=>{try{const e=await fetch(n,{headers:{Accept:"text/event-stream"},signal:s.signal})
219
+ if(!e.ok||!e.body)return r(),void t.status(e.status).json({error:"SSE upstream error"})
218
220
  t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders()
219
- const n=e.body.getReader()
220
- try{for(;;){const{done:e,value:s}=await n.read()
221
+ const o=e.body.getReader()
222
+ try{for(;;){const{done:e,value:s}=await o.read()
221
223
  if(e)break
222
- t.write(s),"function"==typeof t.flush&&t.flush()}}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
- "GET"!==e.method&&"HEAD"!==e.method&&e.body&&(i.body=JSON.stringify(e.body))
225
- const a=await fetch(r,i)
226
- if(204===a.status||"0"===a.headers.get("content-length"))return void t.status(a.status).end()
227
- const c=a.headers.get("content-type")||""
224
+ t.write(s),"function"==typeof t.flush&&t.flush()}}catch{}t.end()}catch{t.headersSent||t.status(502).json({error:"SSE upstream error"})}finally{r()}})()
225
+ return this.proxyAbortControllers.set(s,o),void e.on("close",(()=>{try{s.abort()}catch{}}))}const a={"Content-Type":e.headers["content-type"]||"application/json"},c={method:e.method,headers:a}
228
226
  let l
229
- try{l=c.includes("application/json")?await a.json():await a.text()}catch{return void t.status(a.status).end()}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
+ "GET"!==e.method&&"HEAD"!==e.method&&e.body&&(c.body=JSON.stringify(e.body))
228
+ let d=n
229
+ try{l=await fetch(n,c)}catch(e){this.openCodePort=null,this.openCodeUrlCache=null
230
+ const t=this.getOpenCodeUrl()
231
+ if(!t||t===s)throw e
232
+ d=`${t}/${r}${o}`,this.logger.info("OpenCode proxy: retrying with fresh port",{oldUrl:s,newUrl:t}),l=await fetch(d,c)}if(204===l.status||"0"===l.headers.get("content-length"))return void t.status(l.status).end()
233
+ const u=l.headers.get("content-type")||""
234
+ let h
235
+ try{h=u.includes("application/json")?await l.json():await l.text()}catch{return void t.status(l.status).end()}t.status(l.status),u&&t.setHeader("Content-Type",u),"string"==typeof h?t.send(h):t.json(h)}catch(s){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,g.getErrorMessage)(s),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,g.getErrorMessage)(s)})}}async handleGetAgentByName(e,t){try{const s=this.readProjectConfig()
230
236
  if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
231
237
  const{agentName:r}=e.params,o=await this.apiClient.get(`/api/projects/${s.id}/agents`),n=(o?.data||o||[]).find((e=>e.name===r))
232
238
  if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
@@ -365,7 +371,15 @@ if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"
365
371
  const r=await this.stuckSessionWatchdog.retrySession(s)
366
372
  r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}async stop(){this.logger.info("Stopping AlchemyActionServer...")
367
373
  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)
368
- 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={}
374
+ this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=v,v.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.ensureRequestWorktree=async function(e,t,i){const a="requests/"+t,c=(0,r.join)(e,n.ALCHEMY_DIR_NAME,"worktrees"),l=(0,r.join)(c,t)
375
+ if((0,s.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
376
+ const d=(0,o.simpleGit)(e);(0,s.mkdirSync)(c,{recursive:!0})
377
+ try{await d.raw(["worktree","prune"])}catch{}const u=await d.branch(),h=u.all.includes(a)
378
+ try{if(h)await d.raw(["worktree","add",l,a]),i.info("Pre-spawn: created worktree for existing branch",{requestId:t,branchName:a,worktreePath:l})
379
+ else{const e=u.all.includes("main")?"main":"master"
380
+ await d.raw(["worktree","add","-b",a,l,e]),i.info("Pre-spawn: created worktree with new branch from latest main",{requestId:t,branchName:a,mainBranch:e,worktreePath:l})}}catch(e){const s=e instanceof Error?e.message:e+""
381
+ i.warn("Pre-spawn: failed to create worktree (MCP process will retry)",{requestId:t,error:s})}}
382
+ const s=t(9896),r=t(6928),o=t(3807),n=t(4139)},2782(e,exports,t){function s(){return"true"===process.env.ENABLE_DEBUG_TOOLS?.toLowerCase()}function r(e={}){const t={...function(e){const t={}
369
383
  if(!s())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.logToFile=!1,t
370
384
  if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
371
385
  const s=e||process.cwd()
@@ -599,24 +613,24 @@ let S
599
613
  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))
600
614
  else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
601
615
  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"}
602
- const I=(0,o.join)(w,c,l),E=(0,o.join)(I,u),b=(0,n.simpleGit)(w)
603
- if((0,r.existsSync)(E))try{const e=(0,n.simpleGit)(E)
604
- 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)})
605
- try{await b.raw(["worktree","remove","--force",E])}catch{}}if(!(0,r.existsSync)(E)){(0,r.mkdirSync)(I,{recursive:!0})
606
- try{if(!(await b.branch()).all.includes(k))return{success:!1,message:`Branch ${k} not found`,error:"Branch not found"}
607
- 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)}
608
- m.info("Test worktree already exists, using as-is",{worktreePath:E})}}const{execSync:j}=await Promise.resolve().then(t.t.bind(t,5317,23))
616
+ const I=(0,o.join)(w,c,l),b=(0,o.join)(I,u),E=(0,n.simpleGit)(w)
617
+ if((0,r.existsSync)(b))try{const e=(0,n.simpleGit)(b)
618
+ await e.raw(["reset","--hard",k]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:b,branchName:k})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
619
+ try{await E.raw(["worktree","remove","--force",b])}catch{}}if(!(0,r.existsSync)(b)){(0,r.mkdirSync)(I,{recursive:!0})
620
+ try{if(!(await E.branch()).all.includes(k))return{success:!1,message:`Branch ${k} not found`,error:"Branch not found"}
621
+ await E.raw(["worktree","add","--detach",b,k]),m.info("Created test worktree (detached)",{worktreePath:b,branchName:k})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
622
+ m.info("Test worktree already exists, using as-is",{worktreePath:b})}}const{execSync:j}=await Promise.resolve().then(t.t.bind(t,5317,23))
609
623
  for(const e of S){const t=e.config?.setup||[]
610
624
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
611
- 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=[]
625
+ for(const s of t)try{m.info("Setup command",{command:s,worktreePath:b}),j(s,{cwd:b,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:s,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const T=[],P=[]
612
626
  for(const process of S){let t
613
627
  const r=e.testRunIds?.[T.length]
614
- 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)})
628
+ try{r?(await y.patch(`/api/projects/${v}/test-runs/${r}`,{worktreePath:b}),t=(await y.get(`/api/projects/${v}/test-runs/${r}`)).data,T.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:b,parentSessionId:g})).data,T.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:r,error:(0,i.getErrorMessage)(e)})
615
629
  continue}const o=s(p,process,0,v,f),n=f?"tester":"auto-tester"
616
- 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})
630
+ try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:b,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:g,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
617
631
  if(!e.success)throw Error(e.error||"Spawn failed")
618
- 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)})
619
- 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)
632
+ P.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:b,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
633
+ try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===P.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${P.length} test run(s) for request ${u}`,data:{testRunIds:T,agentsSpawned:P.length,processCount:S.length,worktreePath:b}}}catch(e){const t=(0,i.getErrorMessage)(e)
620
634
  return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
621
635
  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
622
636
  if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
@@ -696,33 +710,33 @@ const s=t(8824),r=t(8148)},7960(e,exports){function t(){const e=process.argv[1]|
696
710
  if(e.includes("/dist-dev/")||e.includes("\\dist-dev\\"))return!0
697
711
  if(e.includes("/dist/")||e.includes("\\dist\\"))return!1
698
712
  const t=process.cwd()
699
- 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
713
+ 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:l,parentServerUrl:d,parentSessionId:u,sessionId:h,model:g,initialBranch:p,serverUrl:f,onSpawn:m,onError:y}=e
700
714
  try{const n={...process.env}
701
- l&&(n.OPENCODE_PARENT_SERVER=l),g&&(n.ALCHEMY_INITIAL_BRANCH=g),e.allowParallel&&(n.ALCHEMY_ALLOW_PARALLEL="1")
702
- 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)
703
- let v
704
- 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))
705
- 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})
706
- const w=(0,a.join)(c||process.cwd(),".alchemy_lab"),C=(0,a.join)(w,"spawn-stderr.log")
707
- let k
708
- 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})
709
- return S.unref(),f&&f(S.pid),{success:!0,pid:S.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
710
- 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})}
711
- 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)
712
- 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)
713
- let m,y=null
714
- 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}:{}})})
715
+ d&&(n.OPENCODE_PARENT_SERVER=d),p&&(n.ALCHEMY_INITIAL_BRANCH=p),e.allowParallel&&(n.ALCHEMY_ALLOW_PARALLEL="1")
716
+ const u=new Date,y=`${(u.getHours()+"").padStart(2,"0")}${(u.getMinutes()+"").padStart(2,"0")}${(u.getSeconds()+"").padStart(2,"0")}`,v=Math.random().toString(36).substring(2,7)
717
+ let w
718
+ if(n.ALCHEMY_SESSION_TAG=`mcp-${y}-${v}`,s(l,"Spawn env setup",{agent:t,initialBranch:p||"none",hasParentServer:!!d,sessionId:h||"new",attachMode:!!f}),w=f&&h&&r?["run","--attach",f,"--session",h,"--agent",t,r]:h&&r?["run","--session",h,"--agent",t,r]:r?["run","--agent",t,r]:["run","--agent",t],g){const e=w.findIndex((e=>e===r))
719
+ e>0?w.splice(e,0,"--model",g):w.push("--model",g)}s(l,"Spawning opencode",{args:w.map((e=>e===r?`[prompt:${e.length}chars]`:e)),model:g||"not specified",promptLength:r?.length,promptPreview:r?.slice(0,100),cwd:l,hasSessionId:!!h,hasParentServerUrl:!!d})
720
+ const C=(0,a.join)(l||process.cwd(),".alchemy_lab"),k=(0,a.join)(C,"spawn-stderr.log")
721
+ let S
722
+ try{S=(0,i.openSync)(k,"a")}catch{}const I=(0,c.resolveRunCommand)(),b=(0,o.spawn)(I.command,[...I.prependArgs,...w],{detached:!0,stdio:["ignore","ignore",S??"ignore"],cwd:l||process.cwd(),env:n})
723
+ return b.unref(),m&&m(b.pid),{success:!0,pid:b.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
724
+ return y&&e instanceof Error&&y(e),{success:!1,error:t}}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=r,exports.spawnJournalSubagent=function(e,t,s,o){return r({agent:"journal",prompt:`Generate a journal entry for request ${e}.\n\nSteps:\n1. Use ${t}__generate_request_journal tool with requestId: ${e}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${e}.md using ${t}__document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,cwd:s,parentServerUrl:o})},exports.spawnWorkSubagent=function(e,t,s){return r({agent:"work",prompt:e,cwd:t,parentServerUrl:s})}
725
+ const o=t(5317),n=t(8824),i=t(9896),a=t(6928),c=t(9068)},8148(e,exports,t){function s(e,t){try{const t=(0,o.join)(e,c,"opencode.json"),s=(0,r.readFileSync)(t,"utf8"),n=JSON.parse(s)
726
+ if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,a.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=s,exports.spawnActionAgent=async function(e){const{agent:t,prompt:r,cwd:o,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:h,apiClient:g,logger:p,trackChildPid:f}=e,m=s(c,p)
727
+ let y,v=null
728
+ m?v=await async function(e,t,s,r,o,n,i,c){try{const l=await fetch(e+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t,...o?{parentID:o}:{}})})
715
729
  if(!l.ok)return c.warn("Failed to pre-create session via HTTP",{status:l.status}),null
716
730
  const d=await l.json()
717
731
  if(!d.id)return c.warn("Session created but no ID returned"),null
718
732
  const u=d.id
719
733
  c.info("Pre-created agent session",{sessionId:u,title:t,agent:s})
720
- 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")
734
+ try{await i.post(`/api/projects/${n}/cli-sessions`,{externalSessionId:u,providerId:"opencode",...o?{parentExternalSessionId:o}:{},agentType:s,status:"active"}),c.info("Registered session in alchemy DB",{sessionId:u,agent:s,parentSessionId:o})}catch(e){c.warn("Failed to register session in DB",{error:(0,a.getErrorMessage)(e)})}if(r&&n){try{await i.post(`/api/projects/${n}/session-request-associations`,{opencodeSessionId:u,requestLocalId:r}),c.info("Created session-request association",{sessionId:u,requestId:r})}catch(e){c.warn("Failed to create session-request association",{error:(0,a.getErrorMessage)(e)})}if("coder"===s)try{await i.patch(`/api/projects/${n}/requests/${r}`,{cliOrigin:{providerId:"opencode",sessionId:u,createdAt:(new Date).toISOString()}}),c.info("Updated request cliOrigin to coder session",{sessionId:u,requestId:r})}catch(e){c.warn("Failed to update request cliOrigin",{error:(0,a.getErrorMessage)(e)})}}return u}catch(e){return c.warn("Failed to pre-create session",{error:(0,a.getErrorMessage)(e)}),null}}(m,l,t,d,u,e.projectId,g,p):p.warn("No OpenCode server URL \u2014 agent session will not be visible in session list")
721
735
  try{const e=await fetch("http://localhost:3456/api/project-settings")
722
736
  if(e.ok){const s=await e.json()
723
- 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)}})
724
- 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"}}
725
- 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}}
737
+ s.data?.defaultModel&&(y=s.data.defaultModel,p.info("Using user default model for agent spawn",{agent:t,model:y}))}}catch{}if(d)try{await(0,i.ensureRequestWorktree)(c,d,p)}catch(e){p.warn("Pre-spawn worktree creation failed (non-fatal)",{requestId:d,error:(0,a.getErrorMessage)(e)})}const w=(0,n.spawnDetachedSubagent)({agent:t,prompt:r,cwd:o,sessionId:v||void 0,serverUrl:m||void 0,model:y,allowParallel:h??!1,onSpawn(e){e&&f&&f(e)}})
738
+ return w.success?(p.info("Spawned action agent",{agent:t,sessionId:v,pid:w.pid,requestId:d}),{success:!0,sessionId:v,pid:w.pid}):{success:!1,sessionId:v,error:w.error||"Spawn failed"}}
739
+ const r=t(9896),o=t(6928),n=t(8035),i=t(2424),a=t(8824),c=".alchemy_lab"},8524(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
726
740
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
727
741
  const r=t(5086),o=s(t(6982))
728
742
  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())
@@ -783,11 +797,11 @@ t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existin
783
797
  i?.openCodeModel?k.model=i.openCodeModel:p?k.model=p:m&&(k.model=m)
784
798
  const I=c.join(o,".opencode","agent")
785
799
  await s.mkdir(I,{recursive:!0})
786
- const E=S||"anthropic/claude-opus-4-5-20251101"
787
- let b,j=null,T=null
788
- 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})
800
+ const b=S||"anthropic/claude-opus-4-5-20251101"
801
+ let E,j=null,T=null
802
+ if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),E=T?.metadata?.globalSystemPrompt,E&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:E.length}),j=await this.fetchAgentsFromRemote(i.projectId),j&&j.length>0){this.logger.info("Using agents from remote database",{count:j.length})
789
803
  for(const e of j){if(!e.enabled)continue
790
- const t=this.generateAgentMarkdown(e,d,E,b),r=c.join(I,e.name+".md")
804
+ const t=this.generateAgentMarkdown(e,d,b,E),r=c.join(I,e.name+".md")
791
805
  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")
792
806
  else this.logger.warn("No projectId provided - agents must be configured via database")
793
807
  const P={$schema:"https://opencode.ai/config.json",model:S,mcp:{[d]:{type:"local",command:[n,u,o],enabled:!0}}}
@@ -798,10 +812,16 @@ for(const[s,o]of Object.entries(e.tools||{})){const e=o,n=s.replace(/-/g,"_")
798
812
  t[r.OPENCODE_BUILTIN_TOOLS.has(s)?s:`${d}_${n}`]=e}const s=T?.metadata?.bashToolAgents||{}
799
813
  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)
800
814
  let o=""
801
- 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}}
802
- 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`
815
+ E&&(o+=E.trim()+"\n\n"),e.prompt&&(o+=e.prompt),e.promptAddition&&(o+="\n\n"+e.promptAddition),P.agent[e.name]={tools:t,...o?{prompt:o}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}P.agent.general={...P.agent.general,disable:!0},P.agent.explore={...P.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
816
+ const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${E?E.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`
803
817
  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=[]
804
- 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={}
818
+ 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})
819
+ try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),r=c.join(e.homedir(),".alchemy_lab","opencode","config")
820
+ await s.mkdir(r,{recursive:!0})
821
+ const o=c.join(r,"opencode.json")
822
+ let n={}
823
+ try{n=JSON.parse(await s.readFile(o,"utf-8"))}catch{}const i=(n.plugin||[]).filter((e=>!e.includes(".opencode/plugin/")))
824
+ n.plugin=[...i,...A],n.$schema=n.$schema||"https://opencode.ai/config.json",await s.writeFile(o,JSON.stringify(n,null,2),"utf8"),this.logger.info("Updated global alchemy opencode config",{globalConfigPath:o,plugins:n.plugin})}catch(e){this.logger.warn("Failed to write global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(v&&!i)try{let e={}
805
825
  try{const t=await s.readFile(w,"utf-8")
806
826
  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)
807
827
  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}}
@@ -850,7 +870,14 @@ if(!o.ok)return void this.logger.debug("Polling response not ok",{status:o.statu
850
870
  const i=await o.json()
851
871
  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")
852
872
  const e=this.parseTokenResponse(i.data)
853
- 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}}
873
+ 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}}}},9068(e,exports,t){function s(){const e=process.env.ALCHEMY_OPENCODE_DIR
874
+ if(e)return e
875
+ try{const e=Object(function(){var e=Error("Cannot find module 'alchemylab-opencode/package.json'")
876
+ throw e.code="MODULE_NOT_FOUND",e}())
877
+ return(0,o.dirname)(e)}catch{}}function r(e,t){return{command:"node",prependArgs:["--experimental-strip-types","--experimental-transform-types","--no-warnings","--import",(0,o.join)(e,"node-preload.mjs"),(0,o.join)(e,t)]}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.resolveServeCommand=function(){const e=s()
878
+ return e?r((0,o.join)(e,"packages","opencode","src"),"serve.ts"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=s()
879
+ return e?r((0,o.join)(e,"packages","opencode","src"),"run.ts"):{command:"opencode",prependArgs:[]}}
880
+ const o=t(6928)},9266(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
854
881
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleOpenCodeProxyAction=async function(e,t){const{method:s,path:i,body:a,headers:c}=e
855
882
  if(!s||!i)return{success:!1,message:"method and path are required in proxy payload"}
856
883
  const l=function(e){const t=r.default.join(e,".alchemy_lab","opencode.json")
@@ -898,10 +925,10 @@ return e.data?.id||e.id}r.info(`Project registration failed (HTTP ${i.status})`)
898
925
  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)
899
926
  let p
900
927
  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)))
901
- const E=i.filter(((e,t)=>"--port"!==e&&(-1===I||t!==I+1)))
902
- if(0===E.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
928
+ const b=i.filter(((e,t)=>"--port"!==e&&(-1===I||t!==I+1)))
929
+ if(0===b.length)return void await async function(s={}){const i=process.cwd(),l=(0,h.join)(i,S.ALCHEMY_DIR_NAME)
903
930
  await(0,u.mkdir)(l,{recursive:!0})
904
- 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})
931
+ const p=(0,h.join)(l,"server.log"),{createLogger:I}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=I({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),E=await a(b),j=new f.TokenManager(b,i,{remoteServerUrl:E,allowInteractiveAuth:!1})
905
932
  await j.initialize()
906
933
  let T=await j.getToken()
907
934
  if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
@@ -912,12 +939,12 @@ console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
912
939
  const n=(0,h.join)(e,S.ALCHEMY_DIR_NAME)
913
940
  let i
914
941
  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")
915
- 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)
942
+ const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,E,P,b)
916
943
  const A=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"project.json")
917
944
  let M
918
945
  try{const e=JSON.parse((0,d.readFileSync)(A,"utf-8"))
919
- if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(b,P,i,E,"cli-init-retry")
920
- 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"
946
+ if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(E,P,i,b,"cli-init-retry")
947
+ t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(A,JSON.stringify(e,null,2),"utf-8"),M=t,console.log("\u2705 Project registered: "+t))}}catch{b.warn("Could not read project.json")}await async function(t){try{return void(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let s="latest"
921
948
  try{const e=(0,h.join)(t,S.ALCHEMY_DIR_NAME,"config.json")
922
949
  if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
923
950
  t.opencodeVersion&&(s=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
@@ -930,30 +957,30 @@ const r=(0,h.join)(process.env.HOME||"/usr/local/bin",".local","bin")
930
957
  await(0,u.mkdir)(r,{recursive:!0})
931
958
  const o=(0,h.join)(r,"opencode")
932
959
  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)
933
- const _=t()
934
- 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)
935
- const O=s.port||await(0,k.findAvailablePort)(3456),x=new y.AlchemyActionServer(E,{projectPath:i,remoteServerUrl:b,port:O,authToken:P})
960
+ const O=t()
961
+ console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+O,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
962
+ const _=s.port||await(0,k.findAvailablePort)(3456),x=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:E,port:_,authToken:P})
936
963
  try{await x.start()
937
964
  let t=process.env.FRONTEND_URL
938
- if(!t)if(b.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
939
- else{try{const e=await fetch(b+"/health")
965
+ if(!t)if(E.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
966
+ else{try{const e=await fetch(E+"/health")
940
967
  if(e.ok){const s=await e.json()
941
- t=s?.data?.frontendUrl}}catch{}t||(t=b.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
942
- 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})
968
+ t=s?.data?.frontendUrl}}catch{}t||(t=E.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
969
+ try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(b,E,P),s=new w.UnifiedLauncher(b,e,t),r=await s.launch({projectPath:i,settings:M?{projectId:M}:void 0})
943
970
  if(r.success&&r.port){r.pid&&x.trackChildPid(r.pid),x.setOpenCodePort(r.port),console.log(" OpenCode serve ... port "+r.port)
944
- 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")
945
- 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("")
971
+ const e=(0,h.join)(i,S.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:r.port,pid:r.pid,startedAt:(new Date).toISOString()},null,2)),b.info("Wrote OpenCode config",{path:e,port:r.port})}else{if(!r.success)throw Error(r.error||"Launch failed")
972
+ console.log(" OpenCode serve ... started")}}catch(e){b.warn("Failed to launch OpenCode serve",{error:(0,C.getErrorMessage)(e)}),console.log(" OpenCode serve ... failed (run manually: opencode serve)")}console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${t}\x07${t}\x1b]8;;\x07`),console.log("")
946
973
  let s=!1,r=null
947
974
  const a=async()=>{s||(s=!0,r&&clearInterval(r),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
948
975
  process.on("SIGTERM",a),process.on("SIGINT",a)
949
- 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})
976
+ const{InteractiveShell:c}=await e.e(994).then(e.t.bind(e,5994,23)),l=new c({projectPath:i,logger:b,remoteServerUrl:E,authToken:P,projectId:M})
950
977
  r=setInterval((async()=>{try{if(await j.hasValidToken())return
951
978
  console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
952
979
  const e=await j.startAuthenticationFlow()
953
980
  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()
954
- 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})
955
- const b=E[0].toLowerCase()
956
- 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()
981
+ e?.token&&(P=e.token,x.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){b.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await l.start(a)}catch(e){b.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({port:p})
982
+ const E=b[0].toLowerCase()
983
+ switch("--help"!==E&&"-h"!==E||(s(),process.exit(0)),"--version"!==E&&"-v"!==E||(console.log("alchemy v"+t()),process.exit(0)),E){case"serve":await async function(e={}){const s=process.cwd()
957
984
  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))
958
985
  const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:s}),n=t()
959
986
  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)
@@ -970,4 +997,4 @@ break
970
997
  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})
971
998
  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.")}()
972
999
  break
973
- 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})()
1000
+ default:console.error("\u274c Unknown command: "+E),console.log(""),s(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()