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 +3 -3
- package/dist/cli.js +137 -110
- package/dist/http-server.js +680 -717
- package/dist/index.js +582 -619
- package/dist/mcp-direct.js +813 -850
- package/package.json +3 -2
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:["
|
|
12
|
-
if(null===m
|
|
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===
|
|
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,
|
|
40
|
-
try{(0,s.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"OpenCode not found in PATH."}}
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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,
|
|
140
|
-
if(!e?.id)return void
|
|
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
|
|
143
|
-
if(!
|
|
144
|
-
const
|
|
145
|
-
this.logger.debug("Fetched project for global prompt",{projectId:e.id,hasProject:!!
|
|
146
|
-
const o
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
await c.default.promises.mkdir(
|
|
150
|
-
let
|
|
151
|
-
for(const e of
|
|
152
|
-
const t="alchemy",s=this.configurer.generateAgentMarkdown(e,t,"anthropic/claude-sonnet-4-20250514",
|
|
153
|
-
await c.default.promises.writeFile(r,s,"utf8"),
|
|
154
|
-
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),
|
|
155
|
-
if(
|
|
156
|
-
c.default.writeFileSync(e,JSON.stringify(
|
|
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
|
|
159
|
-
if(
|
|
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{
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
try{
|
|
166
|
-
|
|
167
|
-
if(!
|
|
168
|
-
if(!/^req\d+$/.test(
|
|
169
|
-
this.logger.info("Generating journal for request",{requestId:
|
|
170
|
-
const
|
|
171
|
-
|
|
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
|
|
216
|
-
if(
|
|
217
|
-
if(!e.ok||!e.body)return
|
|
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
|
|
220
|
-
try{for(;;){const{done:e,value:s}=await
|
|
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{
|
|
223
|
-
return this.proxyAbortControllers.set(s,
|
|
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
|
-
|
|
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},
|
|
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),
|
|
603
|
-
if((0,r.existsSync)(
|
|
604
|
-
await e.raw(["reset","--hard",k]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:
|
|
605
|
-
try{await
|
|
606
|
-
try{if(!(await
|
|
607
|
-
await
|
|
608
|
-
m.info("Test worktree already exists, using as-is",{worktreePath:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
702
|
-
const
|
|
703
|
-
let
|
|
704
|
-
if(n.ALCHEMY_SESSION_TAG=`mcp-${
|
|
705
|
-
e>0?
|
|
706
|
-
const
|
|
707
|
-
let
|
|
708
|
-
try{
|
|
709
|
-
return
|
|
710
|
-
return
|
|
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,
|
|
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,
|
|
713
|
-
let
|
|
714
|
-
|
|
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
|
|
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&&(
|
|
724
|
-
return
|
|
725
|
-
const r=t(9896),o=t(6928),n=t(8035),i=t(
|
|
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
|
|
787
|
-
let
|
|
788
|
-
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),T=await this.fetchProjectFromRemote(i.projectId),
|
|
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
|
|
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
|
-
|
|
802
|
-
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\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
|
-
|
|
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}}}},
|
|
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
|
|
902
|
-
if(0===
|
|
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)),
|
|
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,
|
|
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(
|
|
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{
|
|
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
|
|
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"+
|
|
935
|
-
const
|
|
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(
|
|
939
|
-
else{try{const e=await fetch(
|
|
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=
|
|
942
|
-
try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(E,
|
|
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)),
|
|
945
|
-
console.log(" OpenCode serve ... started")}}catch(e){
|
|
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:
|
|
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){
|
|
955
|
-
const b
|
|
956
|
-
switch("--help"!==
|
|
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: "+
|
|
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})()
|