alchemylab 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +24 -13
- package/dist/http-server.js +104 -95
- package/dist/index.js +104 -95
- package/dist/mcp-direct.js +372 -363
- package/dist/plugins/hooks/plugin.js +61 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -215,15 +215,18 @@ if(!s)return void t.status(502).json({error:"OpenCode not running"})
|
|
|
215
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
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
217
|
if(!e.ok||!e.body)return o(),void t.status(e.status).json({error:"SSE upstream error"})
|
|
218
|
-
t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"})
|
|
218
|
+
t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders()
|
|
219
219
|
const n=e.body.getReader()
|
|
220
220
|
try{for(;;){const{done:e,value:s}=await n.read()
|
|
221
221
|
if(e)break
|
|
222
|
-
t.write(s)}}catch{}t.end()}catch{t.headersSent||t.status(502).json({error:"SSE upstream error"})}finally{o()}})()
|
|
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
223
|
return this.proxyAbortControllers.set(s,n),void e.on("close",(()=>{try{s.abort()}catch{}}))}const n={"Content-Type":e.headers["content-type"]||"application/json"},i={method:e.method,headers:n}
|
|
224
224
|
"GET"!==e.method&&"HEAD"!==e.method&&e.body&&(i.body=JSON.stringify(e.body))
|
|
225
|
-
const a=await fetch(r,i)
|
|
226
|
-
|
|
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")||""
|
|
228
|
+
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
230
|
if(!s?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
228
231
|
const{agentName:r}=e.params,o=await this.apiClient.get(`/api/projects/${s.id}/agents`),n=(o?.data||o||[]).find((e=>e.name===r))
|
|
229
232
|
if(!n)return void t.status(404).json({success:!1,error:`Agent '${r}' not found`})
|
|
@@ -362,7 +365,15 @@ if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"
|
|
|
362
365
|
const r=await this.stuckSessionWatchdog.retrySession(s)
|
|
363
366
|
r.success?t.json({success:!0}):t.status(400).json({success:!1,error:r.error})}async stop(){this.logger.info("Stopping AlchemyActionServer...")
|
|
364
367
|
for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}if(this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
|
|
365
|
-
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=v,v.PROJECT_CONFIG_TTL=3e4},
|
|
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},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)
|
|
369
|
+
if((0,s.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
|
|
370
|
+
const d=(0,o.simpleGit)(e);(0,s.mkdirSync)(c,{recursive:!0})
|
|
371
|
+
try{await d.raw(["worktree","prune"])}catch{}const u=await d.branch(),h=u.all.includes(a)
|
|
372
|
+
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})
|
|
373
|
+
else{const e=u.all.includes("main")?"main":"master"
|
|
374
|
+
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+""
|
|
375
|
+
i.warn("Pre-spawn: failed to create worktree (MCP process will retry)",{requestId:t,error:s})}}
|
|
376
|
+
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={}
|
|
366
377
|
if(!s())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.logToFile=!1,t
|
|
367
378
|
if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
|
|
368
379
|
const s=e||process.cwd()
|
|
@@ -705,21 +716,21 @@ let k
|
|
|
705
716
|
try{k=(0,i.openSync)(C,"a")}catch{}const S=(0,o.spawn)("opencode",v,{detached:!0,stdio:["ignore","ignore",k??"ignore"],cwd:c||process.cwd(),env:n})
|
|
706
717
|
return S.unref(),f&&f(S.pid),{success:!0,pid:S.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
|
|
707
718
|
return m&&e instanceof Error&&m(e),{success:!1,error:t}}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=r,exports.spawnJournalSubagent=function(e,t,s,o){return r({agent:"journal",prompt:`Generate a journal entry for request ${e}.\n\nSteps:\n1. Use ${t}__generate_request_journal tool with requestId: ${e}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${e}.md using ${t}__document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,cwd:s,parentServerUrl:o})},exports.spawnWorkSubagent=function(e,t,s){return r({agent:"work",prompt:e,cwd:t,parentServerUrl:s})}
|
|
708
|
-
const o=t(5317),n=t(8824),i=t(9896),a=t(6928)},8148(e,exports,t){function s(e,t){try{const t=(0,o.join)(e,
|
|
709
|
-
if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,
|
|
710
|
-
let
|
|
711
|
-
|
|
719
|
+
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,c,"opencode.json"),s=(0,r.readFileSync)(t,"utf8"),n=JSON.parse(s)
|
|
720
|
+
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)
|
|
721
|
+
let y,v=null
|
|
722
|
+
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}:{}})})
|
|
712
723
|
if(!l.ok)return c.warn("Failed to pre-create session via HTTP",{status:l.status}),null
|
|
713
724
|
const d=await l.json()
|
|
714
725
|
if(!d.id)return c.warn("Session created but no ID returned"),null
|
|
715
726
|
const u=d.id
|
|
716
727
|
c.info("Pre-created agent session",{sessionId:u,title:t,agent:s})
|
|
717
|
-
try{await
|
|
728
|
+
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")
|
|
718
729
|
try{const e=await fetch("http://localhost:3456/api/project-settings")
|
|
719
730
|
if(e.ok){const s=await e.json()
|
|
720
|
-
s.data?.defaultModel&&(
|
|
721
|
-
return
|
|
722
|
-
const r=t(9896),o=t(6928),n=t(8035),i=t(
|
|
731
|
+
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)}})
|
|
732
|
+
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"}}
|
|
733
|
+
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}}
|
|
723
734
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
|
|
724
735
|
const r=t(5086),o=s(t(6982))
|
|
725
736
|
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())
|