alchemylab 1.4.4 → 1.4.6
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 +148 -130
- package/dist/http-server.js +207 -180
- package/dist/index.js +207 -180
- package/dist/mcp-direct.js +207 -180
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
(()=>{function e(t){var r=n[t]
|
|
3
3
|
if(void 0!==r)return r.exports
|
|
4
4
|
var module=n[t]={exports:{}}
|
|
5
|
-
return o[t].call(module.exports,module,module.exports,e),module.exports}var t,r,s,o={
|
|
6
|
-
module.exports=require("alchemylab-opencode/package.json")},270(e,exports){"use strict"
|
|
5
|
+
return o[t].call(module.exports,module,module.exports,e),module.exports}var t,r,s,o={270(e,exports){"use strict"
|
|
7
6
|
Object.defineProperty(exports,"__esModule",{value:!0})},355(e,exports,t){"use strict"
|
|
8
7
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.registerHooksRoutes=function(e,t){e.get("/api/event-hooks/by-event/:eventType",((e,s)=>async function(e,t,s){const{apiClient:o,logger:n,getProjectId:i}=s
|
|
9
8
|
try{const{eventType:r}=e.params,{sessionId:s}=e.query,a=i()
|
|
@@ -27,10 +26,10 @@ const{sessionId:s,hookId:n}=e.params,a=await o.post(`/api/projects/${r}/hook-ite
|
|
|
27
26
|
t.json(a)}catch(e){n.debug("Failed to increment hook iteration",{error:(0,r.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}(e,s,t)))}
|
|
28
27
|
const r=t(8824)},441(e,exports){"use strict"
|
|
29
28
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.BAD_REQUEST=exports.INTERNAL_ERROR=exports.NOT_FOUND=exports.FORBIDDEN=exports.UNAUTHORIZED=exports.VALIDATION_ERROR=exports.MCP_PROTOCOL_ERROR=exports.REQUEST_VALIDATION_FAILED=exports.REQUEST_ID_INVALID_FORMAT=exports.REQUEST_NOT_FOUND=exports.CONNECTION_ERROR=exports.SERVICE_UNAVAILABLE=exports.CONNECTION_TIMEOUT=exports.SERVER_ERROR=exports.NETWORK_ERROR=exports.TASK_VALIDATION_FAILED=exports.TASK_ID_INVALID_FORMAT=exports.TASK_NOT_FOUND=void 0,exports.TASK_NOT_FOUND="TASK_NOT_FOUND",exports.TASK_ID_INVALID_FORMAT="TASK_ID_INVALID_FORMAT",exports.TASK_VALIDATION_FAILED="TASK_VALIDATION_FAILED",exports.NETWORK_ERROR="NETWORK_ERROR",exports.SERVER_ERROR="SERVER_ERROR",exports.CONNECTION_TIMEOUT="CONNECTION_TIMEOUT",exports.SERVICE_UNAVAILABLE="SERVICE_UNAVAILABLE",exports.CONNECTION_ERROR="CONNECTION_ERROR",exports.REQUEST_NOT_FOUND="REQUEST_NOT_FOUND",exports.REQUEST_ID_INVALID_FORMAT="REQUEST_ID_INVALID_FORMAT",exports.REQUEST_VALIDATION_FAILED="REQUEST_VALIDATION_FAILED",exports.MCP_PROTOCOL_ERROR="MCP_PROTOCOL_ERROR",exports.VALIDATION_ERROR="VALIDATION_ERROR",exports.UNAUTHORIZED="UNAUTHORIZED",exports.FORBIDDEN="FORBIDDEN",exports.NOT_FOUND="NOT_FOUND",exports.INTERNAL_ERROR="INTERNAL_ERROR",exports.BAD_REQUEST="BAD_REQUEST"},489(e,exports,t){"use strict"
|
|
30
|
-
function r(e,t){return(0,h.join)(e,p,m,"pool-"+t)}function s(){const e=
|
|
29
|
+
function r(e,t){return(0,h.join)(e,p,m,"pool-"+t)}function s(){const e=C.shift()
|
|
31
30
|
e&&setImmediate(e)}async function o(e,t){const o=Date.now()
|
|
32
31
|
!function(e){let t=!1
|
|
33
|
-
for(const r of E.values())r.busy&&r.busySince&&e-r.busySince>
|
|
32
|
+
for(const r of E.values())r.busy&&r.busySince&&e-r.busySince>I&&(r.busy=!1,r.busySince=void 0,t=!0)
|
|
34
33
|
t&&s()}(o)
|
|
35
34
|
for(let s=0;t>s;s++){const t=r(e,s)
|
|
36
35
|
if(!(E.get(t)||{busy:!1}).busy)return E.set(t,{busy:!0,busySince:o}),{path:t,index:s,storedFingerprint:await a(t)}}return null}function n(e){const t=E.get(e)
|
|
@@ -39,16 +38,16 @@ return await async function r(s){const o=s?(0,h.join)(e,s):e
|
|
|
39
38
|
let n
|
|
40
39
|
try{n=await d.promises.readdir(o,{withFileTypes:!0})}catch{return}for(const e of n){const o=s?`${s}/${e.name}`:e.name
|
|
41
40
|
!s&&w.has(e.name)||!s&&v.has(e.name)||(e.isDirectory()?await r(o):(e.isFile()||e.isSymbolicLink())&&t.push(o))}}(""),t.sort(),t}async function l(e){try{const t=(await d.promises.readFile((0,h.join)(e,y),"utf-8")).split("\n").map((e=>e.trim())).filter(Boolean)
|
|
42
|
-
return new Set(t)}catch{return null}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getSlotPath=r,exports.acquireSlot=async function(e,t,r=
|
|
41
|
+
return new Set(t)}catch{return null}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getSlotPath=r,exports.acquireSlot=async function(e,t,r=b){return await o(e,t)||new Promise(((i,a)=>{let c=!1
|
|
43
42
|
const l=Date.now(),d=setTimeout((()=>{if(c)return
|
|
44
43
|
c=!0
|
|
45
|
-
const e=
|
|
46
|
-
0>e||
|
|
44
|
+
const e=C.indexOf(u)
|
|
45
|
+
0>e||C.splice(e,1),a(Error(`All ${t} pool slot(s) busy after waiting ${Math.round((Date.now()-l)/1e3)}s`))}),r)
|
|
47
46
|
d.unref()
|
|
48
47
|
const u=async()=>{if(c)return
|
|
49
48
|
const r=await o(e,t)
|
|
50
|
-
c?r&&n(r.path):r?(c=!0,clearTimeout(d),i(r)):(
|
|
51
|
-
|
|
49
|
+
c?r&&n(r.path):r?(c=!0,clearTimeout(d),i(r)):(C.unshift(u),s())}
|
|
50
|
+
C.push(u)}))},exports.releaseSlot=n,exports.scheduleSlotRelease=i,exports.watchPidAndRelease=function(e,t,r,s=5e3){if(i(t,r),!e||0>=e)return
|
|
52
51
|
const o=setInterval((()=>{try{process.kill(e,0)}catch{n(t),clearInterval(o)}}),s)
|
|
53
52
|
o.unref()},exports.readSlotFingerprint=a,exports.writeSlotFingerprint=async function(e,t){try{await d.promises.writeFile((0,h.join)(e,f),t,"utf-8")}catch(e){console.warn("worktree-pool: writeSlotFingerprint failed (non-fatal):",(0,g.getErrorMessage)(e))}},exports.computeFingerprint=async function(e,t){if(!t||0===t.length)return""
|
|
54
53
|
const r=(0,u.createHash)("sha256")
|
|
@@ -58,8 +57,8 @@ await d.promises.writeFile((0,h.join)(e,y),t.join("\n")+"\n","utf-8")}catch(e){c
|
|
|
58
57
|
if(!t)return{removed:0}
|
|
59
58
|
const r=await c(e)
|
|
60
59
|
let s=0
|
|
61
|
-
for(const o of r)if(!t.has(o))try{await d.promises.rm((0,h.join)(e,o),{force:!0}),s++}catch{}return{removed:s}},exports._resetForTests=function(){E.clear(),
|
|
62
|
-
const d=t(9896),u=t(6982),h=t(6928),g=t(8824),p=".alchemy_lab",m="test-worktrees",f=".fingerprint.sha256",y=".alchemy-warm-snapshot.txt",v=new Set([f,y]),w=new Set([".git"]),
|
|
60
|
+
for(const o of r)if(!t.has(o))try{await d.promises.rm((0,h.join)(e,o),{force:!0}),s++}catch{}return{removed:s}},exports._resetForTests=function(){E.clear(),C.length=0}
|
|
61
|
+
const d=t(9896),u=t(6982),h=t(6928),g=t(8824),p=".alchemy_lab",m="test-worktrees",f=".fingerprint.sha256",y=".alchemy-warm-snapshot.txt",v=new Set([f,y]),w=new Set([".git"]),I=12e5,b=6e5,E=new Map,C=[]},833(e,exports,t){"use strict"
|
|
63
62
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
64
63
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeUrlFromConfig=function(e){const t=o.default.join(e,".alchemy_lab","opencode.json")
|
|
65
64
|
try{if(!s.default.existsSync(t))return null
|
|
@@ -204,7 +203,7 @@ const y=await f.json()
|
|
|
204
203
|
if(!y.id)throw Error("Session created in OpenCode but no ID returned")
|
|
205
204
|
const v=y.id
|
|
206
205
|
u.info("Pre-created OpenCode session",{sessionId:v,title:p,agent:t})
|
|
207
|
-
const w=this.registerSession({sessionId:v,parentSessionId:a,projectId:h,agent:t,title:p,specialization:c,specializationHeadline:l,callerAgentType:d,requestId:i}),
|
|
206
|
+
const w=this.registerSession({sessionId:v,parentSessionId:a,projectId:h,agent:t,title:p,specialization:c,specializationHeadline:l,callerAgentType:d,requestId:i}),I=this.resolveModel(t,n),b=i?(0,r.ensureRequestWorktree)(n,i,u).catch((e=>{u.warn("Pre-spawn worktree creation failed (non-fatal)",{requestId:i,error:(0,o.getErrorMessage)(e)})})):Promise.resolve(),[,E]=await Promise.all([w,I,b])
|
|
208
207
|
return{sessionId:v,model:E,serverUrl:g,initialBranch:i?"requests/"+i:void 0}}async registerSession(e){const{sessionId:t,parentSessionId:r,projectId:s,agent:n,title:i,specialization:a,specializationHeadline:c,callerAgentType:l,requestId:d}=e,{apiClient:u,logger:h}=this.deps
|
|
209
208
|
try{let e,g
|
|
210
209
|
if(r)try{const t=await u.post(`/api/projects/${s}/cli-sessions`,{externalSessionId:r,providerId:"opencode",status:"active",agentType:l||void 0}),o=t?.data||t
|
|
@@ -241,8 +240,8 @@ if(!m)return void this.logger.info("Quest tick: no persistent alchemist session
|
|
|
241
240
|
const f=(t.currentIteration||0)+1
|
|
242
241
|
let y=!1
|
|
243
242
|
try{y=await this.bumpQuestMetadata(e,f)}catch(e){return void this.logger.warn("Quest tick: metadata PATCH failed; skipping prompt",{questId:a,error:e+""})}if(!y)return void this.logger.info("Quest tick: status changed during tick; skipping prompt",{questId:a})
|
|
244
|
-
const v=this.buildTickPrompt(e,f,d,h,p+1),w={logger:this.logger,projectId:this.projectId,projectPath:this.projectPath,apiClient:this.apiClient,cachedDefaultModel:this.getDefaultModel()??null},
|
|
245
|
-
try{await(0,s.executeSameSession)(v,m,w,
|
|
243
|
+
const v=this.buildTickPrompt(e,f,d,h,p+1),w={logger:this.logger,projectId:this.projectId,projectPath:this.projectPath,apiClient:this.apiClient,cachedDefaultModel:this.getDefaultModel()??null},I={alchemy:{kind:"quest-tick",payload:{questId:a,iteration:f,criteria:t.criteria||"",openCount:d,taskOnlyCount:h,maxInFlight:n,ticksToday:p+1,maxPerDay:i,minIterations:t.minIterations??o.QUEST_DEFAULTS.minIterations}}}
|
|
244
|
+
try{await(0,s.executeSameSession)(v,m,w,I),this.logger.info("Quest tick fired",{questId:a,iteration:f,alchemistSessionId:m})}catch(e){this.logger.warn("Quest tick: prompt send failed",{questId:a,error:e+""})}}invalidateCache(){this.lastQuestsFetch=0}invalidateAlchemistCache(){this.cachedAlchemistSessionId=null,this.cachedAlchemistAt=0}async fetchOpenQuests(){const e=Date.now()
|
|
246
245
|
if(18e5>e-this.lastQuestsFetch)return this.cachedQuests
|
|
247
246
|
try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/plans?type=Quest&limit=100`),r=t?.data||t||[]
|
|
248
247
|
return this.cachedQuests=r.filter((e=>e&&(e.metadata?.status||o.QUEST_STATUS_OPEN)===o.QUEST_STATUS_OPEN)),this.lastQuestsFetch=e,this.cachedQuests}catch(t){throw this.lastQuestsFetch=e,t}}async fetchQuestRequests(e){const t=await this.apiClient.get(`/api/projects/${this.projectId}/requests?questDocumentId=${encodeURIComponent(e)}&limit=1000`)
|
|
@@ -298,8 +297,8 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.partMeta=function
|
|
|
298
297
|
return{...e,_meta:{...s,alchemy:{...o,...r,kind:t}}}},exports.readAlchemyMeta=function(e){return e?._meta?.alchemy}},2366(e,exports,t){"use strict"
|
|
299
298
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
300
299
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
|
|
301
|
-
const s=r(t(7252)),o=r(t(8577)),n=t(9896),i=t(5959),a=t(2640),c=t(4192),l=t(1152),d=r(t(6928)),u=r(t(9896)),h=r(t(857)),g=t(5607),p=t(8984),m=t(8524),f=t(8824),y=t(2096),v=t(7210),w=t(7227),
|
|
302
|
-
class
|
|
300
|
+
const s=r(t(7252)),o=r(t(8577)),n=t(9896),i=t(5959),a=t(2640),c=t(4192),l=t(1152),d=r(t(6928)),u=r(t(9896)),h=r(t(857)),g=t(5607),p=t(8984),m=t(8524),f=t(8824),y=t(2096),v=t(7210),w=t(7227),I=t(5419),b=t(3962),E=t(2928),C=t(6563),S=t(1401),k=t(1292),T=t(8649),_=t(4306),j=t(9068),A=t(3435),P=t(8463),M=t(489),O=t(3807),R=t(7478),D=t(5317),x=t(355),$=t(833)
|
|
301
|
+
class F{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.activeMcpCalls=new Map,this.pendingAgentResync=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.droppedEventCount=0,this.firstDropAt=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
|
|
303
302
|
const r=(0,l.createMetricsCollector)(!1)
|
|
304
303
|
this.apiClient=new c.BaseApiClient(t.remoteServerUrl,e,r,t.authToken,{timeout:35e3}),this.configurer=new p.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new m.CLIBridgeWebSocket(e),(0,m.setCLIBridge)(this.cliBridge),this.hookExecutor=new v.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,s.default)()
|
|
305
304
|
return e.use(((e,t,r)=>{t.setHeader("Access-Control-Allow-Private-Network","true"),r()})),e.use((0,o.default)()),e.use(s.default.json({limit:"10mb"})),e.get("/health",this.handleHealth.bind(this)),e.get("/api/commands/running",(async(e,t)=>{try{const e=16384,r=await Promise.all((0,i.listEntries)().map((async t=>{let r,s="",o=0,i=t.finishedAt??t.startedAt
|
|
@@ -309,7 +308,7 @@ const c=Math.max(0,o-e),l=Buffer.alloc(o-c)
|
|
|
309
308
|
await r.read(l,0,l.length,c),s=l.toString("utf-8").split("\n").slice(-50).join("\n")}catch{}finally{await(r?.close().catch((()=>{})))}return{...t,tail:s,sizeBytes:o,modifiedAt:i}})))
|
|
310
309
|
t.json({success:!0,data:r})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/commands/start",(async(e,t)=>{try{const{id:r,name:s,command:o,cwd:n,sessionId:a,startedAt:c,logPath:l}=e.body??{}
|
|
311
310
|
if(!(r&&s&&o&&n&&c&&l))return void t.status(400).json({success:!1,error:"missing required fields"});(0,i.addStart)({id:r,name:s,command:o,cwd:n,sessionId:a,startedAt:c,logPath:l}),t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/commands/end",(async(e,t)=>{try{const{id:r,exitCode:s,timedOut:o,finishedAt:n}=e.body??{}
|
|
312
|
-
if(!r||"number"!=typeof s||!n)return void t.status(400).json({success:!1,error:"missing required fields"});(0,i.markEnd)({id:r,exitCode:s,timedOut:!!o,finishedAt:n}),t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.get("/api/auth-status",this.handleAuthStatus.bind(this)),e.get("/status",this.handleStatus.bind(this)),e.get("/opencode",this.handleOpenCodeConfig.bind(this)),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.post("/api/generate-journal",this.handleGenerateJournal.bind(this)),(0,
|
|
311
|
+
if(!r||"number"!=typeof s||!n)return void t.status(400).json({success:!1,error:"missing required fields"});(0,i.markEnd)({id:r,exitCode:s,timedOut:!!o,finishedAt:n}),t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.get("/api/auth-status",this.handleAuthStatus.bind(this)),e.get("/status",this.handleStatus.bind(this)),e.get("/opencode",this.handleOpenCodeConfig.bind(this)),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.post("/api/generate-journal",this.handleGenerateJournal.bind(this)),(0,x.registerHooksRoutes)(e,{apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId}),e.get("/api/project-settings",((e,t)=>{t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.post("/api/sync-settings",(async(e,t)=>{await this.syncProjectSettings(),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.get("/api/agents/by-name/:agentName",this.handleGetAgentByName.bind(this)),e.get("/api/sessions/:sessionId/agent-type",this.handleGetSessionAgentType.bind(this)),e.get("/api/sessions/:sessionId/parent",this.handleGetSessionParent.bind(this)),e.get("/api/sessions/:sessionId/ancestors",(async(e,t)=>{try{const{sessionId:r}=e.params,s=this.projectId
|
|
313
312
|
if(!s)return void t.status(400).json({success:!1,error:"No project"})
|
|
314
313
|
const o=await this.apiClient.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),n=(o?.data||o||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title})))
|
|
315
314
|
t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.get("/api/registry/projects",(async(e,r)=>{try{const{listLive:e}=await Promise.resolve().then(t.bind(t,2640))
|
|
@@ -322,11 +321,11 @@ if(!r||"before"!==i&&"after"!==i)return void t.status(400).json({success:!1,erro
|
|
|
322
321
|
const a=(0,w.parseToolEvents)({toolName:r,sessionId:s||"",input:o,output:n,timing:i})
|
|
323
322
|
let c=0
|
|
324
323
|
for(const e of a)c+=(await this.hookExecutor.processEvent({eventType:e.eventType,sessionId:s||"",variables:e.variables,timing:e.timing})).triggered
|
|
325
|
-
this.logger.info("tool-event received",{toolName:r,timing:i,sessionId:s||"",events:a.length,triggered:c}),t.json({success:!0,data:{events:a.length,triggered:c}})}catch(e){this.logger.error("tool-event processing failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.post("/api/spawn-session",this.handleSpawnSession.bind(this)),e.post("/api/setup-tests",this.handleSetupTests.bind(this)),e.post("/api/setup-commands",this.handleSetupCommands.bind(this)),e.post("/api/run-tests-now",this.handleRunTestsNow.bind(this)),e.get("/api/branch-head",(async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0,s=("string"==typeof e.query.branch?e.query.branch:void 0)||(r?(0,
|
|
324
|
+
this.logger.info("tool-event received",{toolName:r,timing:i,sessionId:s||"",events:a.length,triggered:c}),t.json({success:!0,data:{events:a.length,triggered:c}})}catch(e){this.logger.error("tool-event processing failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.post("/api/spawn-session",this.handleSpawnSession.bind(this)),e.post("/api/spawn-planner",this.handleSpawnPlanner.bind(this)),e.post("/api/setup-tests",this.handleSetupTests.bind(this)),e.post("/api/setup-commands",this.handleSetupCommands.bind(this)),e.post("/api/run-tests-now",this.handleRunTestsNow.bind(this)),e.get("/api/branch-head",(async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0,s=("string"==typeof e.query.branch?e.query.branch:void 0)||(r?(0,R.generateRequestBranchName)(r,""):void 0)
|
|
326
325
|
if(!s)return void t.status(400).json({success:!1,error:"requestId or branch required"})
|
|
327
|
-
const o=(await(0,
|
|
326
|
+
const o=(await(0,O.simpleGit)(this.options.projectPath).revparse([s])).trim()
|
|
328
327
|
t.json({success:!0,data:{branch:s,sha:o}})}catch(e){this.logger.debug("branch-head lookup failed",{error:(0,f.getErrorMessage)(e)}),t.json({success:!0,data:{branch:null,sha:null}})}})),e.post("/api/release-pool-slot",(async(e,t)=>{try{const{worktreePath:r}=e.body||{}
|
|
329
|
-
if(!r||"string"!=typeof r)return void t.status(400).json({success:!1,error:"worktreePath required"});(0,
|
|
328
|
+
if(!r||"string"!=typeof r)return void t.status(400).json({success:!1,error:"worktreePath required"});(0,M.releaseSlot)(r),this.logger.info("Released pool slot via report-back",{worktreePath:r}),t.json({success:!0})}catch(e){this.logger.warn("release-pool-slot failed (non-fatal)",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}})),e.get("/api/timer-hooks/status",((e,t)=>{const r=this.timerHookScheduler?.getStatus()??{running:!1,trackedHooks:0,inFlight:[],hooks:[]}
|
|
330
329
|
t.json({success:!0,data:r})})),e.post("/api/timer-hooks/:hookId/pause",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
|
|
331
330
|
const r=this.timerHookScheduler.pauseHook(e.params.hookId)
|
|
332
331
|
t.json({success:r})})),e.post("/api/timer-hooks/:hookId/resume",((e,t)=>{if(!this.timerHookScheduler)return void t.status(503).json({success:!1,error:"Scheduler not running"})
|
|
@@ -335,7 +334,7 @@ t.json({success:r})})),e.post("/api/timer-hooks/:hookId/stop",((e,t)=>{if(!this.
|
|
|
335
334
|
const r=this.timerHookScheduler.stopHook(e.params.hookId)
|
|
336
335
|
t.json({success:r})})),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.get("/api/version-warnings",((e,t)=>{t.json({success:!0,data:this.versionWarnings})})),e.post("/api/self-update",(async(e,r)=>{try{const{execSync:e}=await Promise.resolve().then(t.t.bind(t,5317,23)),s=e("npm root -g",{encoding:"utf8"}).trim()
|
|
337
336
|
try{u.default.accessSync(s,u.default.constants.W_OK)}catch{return void r.json({success:!1,error:"Permission denied. Run manually: sudo npm install -g alchemylab@latest"})}const{exec:o}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
338
|
-
r.json({success:!0,message:"Update started. The CLI will restart when complete."}),o("npm install -g alchemylab@latest",{timeout:12e4},((e,t,r)=>{e?this.logger.error("Self-update failed",{error:e.message,stderr:r}):(this.logger.info("Self-update complete, shutting down for restart",{stdout:t.trim()}),setTimeout((()=>{console.log("\n\x1b[32mUpdate complete. Please restart alchemylab.\x1b[0m\n"),process.exit(0)}),1e3))}))}catch(e){r.json({success:!1,error:(0,f.getErrorMessage)(e)})}})),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()})}async handleAuthStatus(e,t){const r=this.options.tokenManager
|
|
337
|
+
r.json({success:!0,message:"Update started. The CLI will restart when complete."}),o("npm install -g alchemylab@latest",{timeout:12e4},((e,t,r)=>{e?this.logger.error("Self-update failed",{error:e.message,stderr:r}):(this.logger.info("Self-update complete, shutting down for restart",{stdout:t.trim()}),setTimeout((()=>{console.log("\n\x1b[32mUpdate complete. Please restart alchemylab.\x1b[0m\n"),process.exit(0)}),1e3))}))}catch(e){r.json({success:!1,error:(0,f.getErrorMessage)(e)})}})),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),projectId:this.projectId,projectPath:this.options.projectPath,timestamp:(new Date).toISOString()})}async handleAuthStatus(e,t){const r=this.options.tokenManager
|
|
339
338
|
if(r)try{const e=await r.hasValidToken(),s=r.getAuthStatus(),o=e?await r.getToken():null
|
|
340
339
|
t.json({authenticated:e,isPendingAuth:s.isPendingAuth,authUrl:e?void 0:s.authUrl,expiresAt:o?.expiresAt??null})}catch(e){this.logger.warn("auth-status check failed",{error:(0,f.getErrorMessage)(e)}),t.json({authenticated:!1,isPendingAuth:!1})}else t.json({authenticated:!0,isPendingAuth:!1})}handleStatus(e,t){t.json({status:"running",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),startTime:this.startTime.toISOString(),projectId:this.projectId,serverId:this.serverId,isPolling:this.isPolling,port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const r=d.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
341
340
|
try{if(!u.default.existsSync(r))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
|
|
@@ -377,7 +376,7 @@ e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3
|
|
|
377
376
|
if(!s)return void r.status(400).json({success:!1,error:"requestId is required"})
|
|
378
377
|
if(!/^req\d+$/.test(s))return void r.status(400).json({success:!1,error:"Invalid requestId format"})
|
|
379
378
|
this.logger.info("Generating journal for request",{requestId:s})
|
|
380
|
-
const o=`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.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,
|
|
379
|
+
const o=`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.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,D.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath,env:{...process.env,...(0,I.getOpencodeSpawnEnv)(this.options.projectPath,(0,b.isDevMode)())}})
|
|
381
380
|
a.pid&&this.trackChildPid(a.pid),a.unref(),r.json({success:!0,message:"Journal generation started for "+s})}catch(e){this.logger.error("Failed to generate journal",{error:(0,f.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleDirectReport(e,t){try{const r=this.projectId
|
|
382
381
|
if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
383
382
|
const{sessionId:s,requestId:o,summary:n,status:i}=e.body
|
|
@@ -425,36 +424,27 @@ if(!t||t===r)throw e
|
|
|
425
424
|
d=`${t}/${s}${o}`,this.logger.info("OpenCode proxy: retrying with fresh port",{oldUrl:r,newUrl:t}),l=await fetch(d,c)}if(204===l.status||"0"===l.headers.get("content-length"))return void t.status(l.status).end()
|
|
426
425
|
const u=l.headers.get("content-type")||""
|
|
427
426
|
let h
|
|
428
|
-
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(r){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,f.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,f.getErrorMessage)(r)})}}async
|
|
429
|
-
|
|
430
|
-
if(
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
427
|
+
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(r){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,f.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,f.getErrorMessage)(r)})}}async handleSpawnPlanner(e,t){try{if(!(0,A.validateRequest)(e,t,this.projectId,["planId","prompt"]))return
|
|
428
|
+
const{agent:r,planId:s,prompt:o,title:n,parentSessionId:i}=e.body||{},a=new Set(["planner","auto-planner","quest-planner"])
|
|
429
|
+
if(r&&!a.has(r))return void t.status(400).json({success:!1,error:"agent must be one of: "+[...a].join(", ")})
|
|
430
|
+
const c=await(0,T.spawnPlannerWithDoc)({agent:r||"planner",planId:s,prompt:o,title:n||"Planner",parentSessionId:i,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e)})
|
|
431
|
+
if(!c.success||!c.sessionId)return void t.status(500).json({success:!1,error:c.error||"spawn failed"})
|
|
432
|
+
t.json({success:!0,data:{sessionId:c.sessionId,planId:c.planId,pid:c.pid}})}catch(e){(0,A.respondWithError)(t,this.logger,"Spawn planner",e)}}async handleSpawnSession(e,t){try{if(!(0,A.validateRequest)(e,t,this.projectId,["agent","prompt"]))return
|
|
433
|
+
const{agent:r,prompt:s,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d}=e.body,u=this.projectId,h=new k.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:u,getDefaultModel:()=>this.cachedDefaultModel||void 0}),g=await h.prepareSession({agent:r,prompt:s,projectPath:this.options.projectPath,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d})
|
|
434
|
+
t.json({success:!0,data:g})}catch(e){(0,A.respondWithError)(t,this.logger,"Spawn session",e)}}async handleRunTestsNow(e,r){try{if(!(0,A.validateRequest)(e,r,this.projectId,["requestId"]))return
|
|
435
|
+
const{requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i}=e.body||{},{handleRunTestsAction:a}=await Promise.resolve().then(t.bind(t,6555)),c={projectId:this.projectId,projectPath:this.options.projectPath,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackedChildPids.add(e),cachedDefaultModel:this.cachedDefaultModel??null},l=await a({requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i,mode:"auto"},c)
|
|
436
436
|
if(!l.success)return void r.status(500).json({success:!1,error:l.error||l.message})
|
|
437
|
-
r.json({success:!0,data:l.data})}catch(e){this.logger
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if(!h.success)return void t.status(500).json({success:!1,error:h.error||"Spawn failed"})
|
|
445
|
-
t.json({success:!0,data:{sessionId:h.sessionId,worktreePath:l,branch:a}})}catch(e){this.logger.error("Setup tests failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleSetupCommands(e,t){try{const{requestId:r,parentSessionId:s,branchName:o,context:n}=e.body||{}
|
|
446
|
-
if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
447
|
-
const i=this.projectId,a=this.options.projectPath
|
|
448
|
-
await(0,_.sweepStaleSetupWorktrees)(a,this.logger)
|
|
449
|
-
const c=await(0,_.resolveSetupBranch)(this.apiClient,i,o,r,this.logger,a),l=(0,_.generateSetupId)(),u=d.default.join((0,_.getSetupWorktreesBase)(a),l)
|
|
450
|
-
try{await(0,_.createSetupWorktree)(a,u,c,this.logger)}catch(e){const r=(0,f.getErrorMessage)(e)
|
|
451
|
-
return this.logger.error("Failed to create setup worktree (commands)",{branch:c,error:r}),void t.status(500).json({success:!1,error:"Failed to create setup worktree: "+r})}const h=(0,_.buildCommandSetupPrompt)(a,r,n),g=await(0,T.spawnActionAgent)({agent:"command-architect",prompt:h,cwd:u,projectPath:a,projectId:i,title:r?`[${r}] Commands setup`:"Commands setup",requestId:r,parentSessionId:s,allowParallel:!0,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
|
|
452
|
-
if(!g.success)return void t.status(500).json({success:!1,error:g.error||"Spawn failed"})
|
|
453
|
-
t.json({success:!0,data:{sessionId:g.sessionId,worktreePath:u,branch:c}})}catch(e){this.logger.error("Setup commands failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleGetAgentByName(e,t){try{const r=this.projectId
|
|
437
|
+
r.json({success:!0,data:l.data})}catch(e){(0,A.respondWithError)(r,this.logger,"Run tests now",e)}}async handleSetupTests(e,t){try{if(!(0,A.validateRequest)(e,t,this.projectId))return
|
|
438
|
+
const{requestId:r,parentSessionId:s,branchName:o,context:n}=e.body||{},i=await(0,_.setupAgentInWorktree)({agent:"test-architect",promptBuilder(e,t,r){return(0,P.buildSetupPrompt)(e,t,r)},titleBuilder(e){return e?`[${e}] Test setup`:"Test setup"},worktreeErrorLabel:"Failed to create setup worktree",callerContext:n,requestId:r,parentSessionId:s,branchName:o,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
|
|
439
|
+
if(!i.success)return void t.status(i.statusCode||500).json({success:!1,error:i.error})
|
|
440
|
+
t.json({success:!0,data:{sessionId:i.sessionId,worktreePath:i.worktreePath,branch:i.branch}})}catch(e){(0,A.respondWithError)(t,this.logger,"Setup tests",e)}}async handleSetupCommands(e,t){try{if(!(0,A.validateRequest)(e,t,this.projectId))return
|
|
441
|
+
const{requestId:r,parentSessionId:s,branchName:o,context:n}=e.body||{},i=await(0,_.setupAgentInWorktree)({agent:"command-architect",promptBuilder(e,t,r){return(0,P.buildCommandSetupPrompt)(e,t,r)},titleBuilder(e){return e?`[${e}] Commands setup`:"Commands setup"},worktreeErrorLabel:"Failed to create setup worktree (commands)",callerContext:n,requestId:r,parentSessionId:s,branchName:o,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
|
|
442
|
+
if(!i.success)return void t.status(i.statusCode||500).json({success:!1,error:i.error})
|
|
443
|
+
t.json({success:!0,data:{sessionId:i.sessionId,worktreePath:i.worktreePath,branch:i.branch}})}catch(e){(0,A.respondWithError)(t,this.logger,"Setup commands",e)}}async handleGetAgentByName(e,t){try{const r=this.projectId
|
|
454
444
|
if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
455
445
|
const{agentName:s}=e.params,o=await this.apiClient.get(`/api/projects/${r}/agents`),n=(o?.data||o||[]).find((e=>e.name===s))
|
|
456
446
|
if(!n)return void t.status(404).json({success:!1,error:`Agent '${s}' not found`})
|
|
457
|
-
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<
|
|
447
|
+
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<F.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
|
|
458
448
|
const e=d.default.join(this.options.projectPath,".alchemy_lab","project.json")
|
|
459
449
|
try{if(!u.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
|
|
460
450
|
const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
@@ -467,7 +457,7 @@ if(!o||!n)return
|
|
|
467
457
|
const i=`${o}/${n}`
|
|
468
458
|
this.updateDefaultModel(i),this.logger.info("Seeded defaultModel from OpenCode zen default",{defaultModel:i}),this.apiClient.post("/api/user/preferences",{defaultModel:i}).catch((e=>this.logger.debug("Failed to seed user preference",{error:(0,f.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
|
|
469
459
|
if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
|
|
470
|
-
const e=(0
|
|
460
|
+
const e=(0,$.getOpenCodeUrlFromConfig)(this.options.projectPath)
|
|
471
461
|
return e&&(this.openCodeUrlCache={url:e,timestamp:Date.now()}),e}async handleSessionCommand(e,t){const r=this.getOpenCodeUrl()
|
|
472
462
|
if(!r)return{success:!1,error:"OpenCode server not available. Is 'opencode serve' running?"}
|
|
473
463
|
try{switch(e){case"list-sessions":{const[e,s]=await Promise.all([fetch(r+"/session"),fetch(r+"/session/status")])
|
|
@@ -502,17 +492,16 @@ r?.agentType&&(a=r.agentType)}catch{}return{success:!0,data:{id:e,title:i||"(unt
|
|
|
502
492
|
if(!e?.id)throw Error("No project configuration found")
|
|
503
493
|
this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId),this.timerHookScheduler?.setProjectId(this.projectId)
|
|
504
494
|
try{const e=d.default.resolve(__dirname,"../../package.json"),t=JSON.parse(u.default.readFileSync(e,"utf8"))
|
|
505
|
-
this.clientVersion=t.version}catch{}try{const e=
|
|
506
|
-
this.opencodeVersion=e.version}catch{try{const e=process.env.ALCHEMY_OPENCODE_DIR
|
|
495
|
+
this.clientVersion=t.version}catch{}try{const e=(0,j.findOpencodeDir)()
|
|
507
496
|
if(e){const t=JSON.parse(u.default.readFileSync(d.default.join(e,"package.json"),"utf8"))
|
|
508
|
-
this.opencodeVersion=t.version}}catch{}
|
|
497
|
+
this.opencodeVersion=t.version}}catch{}const t={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:h.default.hostname(),platform:process.platform,clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}
|
|
509
498
|
this.logger.info("Registering with remote",{projectId:this.projectId})
|
|
510
|
-
const
|
|
511
|
-
if(
|
|
512
|
-
const e=
|
|
499
|
+
const r=await this.apiClient.post("/api/servers/register",t)
|
|
500
|
+
if(r.success&&r.data?.serverId){this.serverId=r.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})
|
|
501
|
+
const e=r.data.versionWarnings
|
|
513
502
|
if(e&&e.length>0){const t=e.some((e=>"required"===e.severity))
|
|
514
503
|
for(const t of e)"required"===t.severity?(this.logger.error("[UPDATE REQUIRED] "+t.message),console.error("\n\x1b[31m\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\u2557"),console.error("\u2551 UPDATE REQUIRED \u2551"),console.error("\x1b[31m\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\u255d\x1b[0m"),console.error(`\x1b[31m${t.message}\x1b[0m\n`)):(this.logger.warn("[UPDATE RECOMMENDED] "+t.message),console.warn(`\n\x1b[33m[UPDATE RECOMMENDED] ${t.message}\x1b[0m\n`))
|
|
515
|
-
this.versionWarnings=e,t&&(this._pendingUpdateBlock=!0)}}else this.logger.warn("Registration failed, continuing without remote connection",{error:
|
|
504
|
+
this.versionWarnings=e,t&&(this._pendingUpdateBlock=!0)}}else this.logger.warn("Registration failed, continuing without remote connection",{error:r.error?.message||"Unknown error"})}async applyVersionWarnings(e){if(e.map((e=>`${e.component}:${e.severity}`)).join(",")!==this.versionWarnings.map((e=>`${e.component}:${e.severity}`)).join(",")){for(const t of e)"required"===t.severity?(this.logger.error("[UPDATE REQUIRED] "+t.message),console.error(`\n\x1b[31m[UPDATE REQUIRED] ${t.message}\x1b[0m\n`)):(this.logger.warn("[UPDATE RECOMMENDED] "+t.message),console.warn(`\n\x1b[33m[UPDATE RECOMMENDED] ${t.message}\x1b[0m\n`))
|
|
516
505
|
this.versionWarnings=e,!e.some((e=>"required"===e.severity))&&!this._pendingUpdateBlock||this.updateBlocked||await this.enterUpdateMode()}}startHeartbeat(){this.serverId&&(setInterval((()=>{if(this.projectId)try{(0,a.touchHeartbeat)(this.projectId)}catch{}}),3e4),this.heartbeatInterval=setInterval((async()=>{try{const e=await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString(),clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}),t=e?.data?.versionWarnings??[]
|
|
517
506
|
await this.applyVersionWarnings(t)}catch(e){this.logger.warn("Heartbeat failed",{error:(0,f.getErrorMessage)(e)})}}),3e5))}startSSEEventForwarder(){this.logger.info("Starting SSE event forwarder"),this.connectToOpenCodeSSE(),this.sseForwarderInterval=setInterval((async()=>{await this.flushSSEEvents()}),2e3)}stopSSEEventForwarder(){this.sseForwarderInterval&&(clearInterval(this.sseForwarderInterval),this.sseForwarderInterval=void 0),this.sseAbortController&&(this.sseAbortController.abort(),this.sseAbortController=void 0),this.flushSSEEvents().catch((e=>this.logger.warn("Failed to flush remaining SSE events",{error:(0,f.getErrorMessage)(e)})))}async connectToOpenCodeSSE(){const e=this.getOpenCodeUrl()
|
|
518
507
|
if(!e)return this.logger.debug("SSE forwarder: OpenCode URL unavailable, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
|
|
@@ -564,9 +553,9 @@ if(void 0!==r&&this.updateDefaultModel(r),void 0!==t&&t!==this.cachedAutoJournal
|
|
|
564
553
|
const e=new y.ProjectConfigService(this.logger)
|
|
565
554
|
await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}}catch(e){this.logger.debug("Settings sync failed",{error:(0,f.getErrorMessage)(e)})}}async processAction(e){this.logger.info("Processing action",{actionId:e.id,type:e.type})
|
|
566
555
|
const t=Date.now()
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
await this.apiClient.post(`/api/actions/${e.id}/result`,{success:!
|
|
556
|
+
let r
|
|
557
|
+
try{const s={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e),cachedDefaultModel:this.cachedDefaultModel},o=await(0,g.executeAction)(e.type,e.payload||{},s)
|
|
558
|
+
r={success:o.success,result:o.data,error:o.message||o.error,duration:Date.now()-t}}catch(e){r={success:!1,error:(0,f.getErrorMessage)(e),duration:Date.now()-t}}try{await this.apiClient.post(`/api/actions/${e.id}/result`,r),r.success?this.logger.info("Action processed",{actionId:e.id,type:e.type,success:!0,duration:r.duration}):this.logger.error("Action failed",{actionId:e.id,type:e.type,error:r.error})}catch(t){this.logger.error("Failed to POST action result after retries; relying on stale-action sweep",{actionId:e.id,type:e.type,workSucceeded:r.success,postError:(0,f.getErrorMessage)(t)})}}async handleCLIBridgeCommand(e,t){try{const{action:r,params:s,timeout:o}=e.body
|
|
570
559
|
if(!r)return void t.status(400).json({success:!1,error:"action is required"})
|
|
571
560
|
if(["list-notifications","get-notification","respond-to-notification"].includes(r)){const e=await this.handleNotificationCommand(r,s||{})
|
|
572
561
|
return void t.json(e)}if(["list-sessions","get-messages","send-message","create-session","get-session-status"].includes(r)){const e=await this.handleSessionCommand(r,s||{})
|
|
@@ -599,7 +588,7 @@ if(s.ok){const e=await s.json(),t=e?.data?.settings||e?.settings
|
|
|
599
588
|
void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,f.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler(),this.projectId&&this.startQuestTickScheduler(),this.startMcpStaleCleanup()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,f.getErrorMessage)(t)})}}this.trackedChildPids.clear()
|
|
600
589
|
try{const e=d.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
601
590
|
if(u.default.existsSync(e)){const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
602
|
-
if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,f.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new
|
|
591
|
+
if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,f.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new C.TimerHookScheduler({hookExecutor:this.hookExecutor,apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.timerHookScheduler.start()}startQuestTickScheduler(){this.questTickScheduler=new S.QuestTickScheduler({apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.questTickScheduler.start()}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new E.StuckSessionWatchdog({getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,logger:this.logger,onStuckDetected:e=>this.handleStuckSessionDetected(e),onStuckResolved:e=>this.handleStuckSessionResolved(e)}),this.stuckSessionWatchdog.start()}handleStuckSessionDetected(e){this.sseEventBuffer.push({type:"session.stuck",data:{type:"session.stuck",properties:{sessionId:e.sessionId,messageId:e.messageId,detectedAt:e.detectedAt,sessionTitle:e.sessionTitle,retryCount:e.retryCount}}}),this.logger.warn("Stuck session detected",{sessionId:e.sessionId,title:e.sessionTitle})}handleStuckSessionResolved(e){this.sseEventBuffer.push({type:"session.unstuck",data:{type:"session.unstuck",properties:{sessionId:e}}})}handleGetStuckSessions(e,t){const r=this.stuckSessionWatchdog?.getStuckSessions()??[]
|
|
603
592
|
t.json({success:!0,data:r})}handleTestInjectStuckSession(e,t){if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
|
|
604
593
|
const{sessionId:r,title:s}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(r,s)
|
|
605
594
|
this.logger.info("Test-injected stuck session",{sessionId:o.sessionId}),t.json({success:!0,data:o})}async handleRetryStuckSession(e,t){const{sessionId:r}=e.params
|
|
@@ -613,7 +602,7 @@ for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortCo
|
|
|
613
602
|
try{const e=d.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
|
|
614
603
|
u.default.existsSync(e)&&u.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await Promise.resolve().then(t.bind(t,2640))
|
|
615
604
|
e(this.projectId)}catch{}if(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)
|
|
616
|
-
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=
|
|
605
|
+
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=F,F.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
|
|
617
606
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ensureRequestWorktree=async function(e,t,i){const a="requests/"+t,c=(0,s.join)(e,n.ALCHEMY_DIR_NAME,"worktrees"),l=(0,s.join)(c,t)
|
|
618
607
|
if((0,r.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
|
|
619
608
|
const d=(0,o.simpleGit)(e);(0,r.mkdirSync)(c,{recursive:!0})
|
|
@@ -718,7 +707,13 @@ break
|
|
|
718
707
|
case"parent_session":case"ancestor_chain":await(0,o.executeInjectPrompt)(e,e.promptTemplate,i,a,this.ctx)}else await(0,o.executeBackground)(e,t,i,a.requestId,this.ctx)
|
|
719
708
|
const c=(0,s.determineEventToEmit)(e,a)
|
|
720
709
|
c&&await this.processCustomEvent(c,a,r+1)}}async notifyHookTrigger(e){try{await this.ctx.apiClient.post(`/api/projects/${this.ctx.projectId}/hook-triggers`,{hookId:e.id,hookName:e.name,eventType:e.eventType,action:e.action,target:e.target,agent:e.agent})}catch{}}}},3416(e,exports){"use strict"
|
|
721
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.KIND_META=exports.QUEST_STATUS_CLOSED=exports.QUEST_STATUS_OPEN=exports.QUEST_DEFAULTS=exports.PLAN_KINDS=void 0,exports.PLAN_KINDS=["plan","quest"],exports.QUEST_DEFAULTS={tickIntervalSeconds:1800,maxInFlight:1,maxPerDay:24,minIterations:3},exports.QUEST_STATUS_OPEN="open",exports.QUEST_STATUS_CLOSED="closed",exports.KIND_META={plan:{docType:"Plan",filenamePrefix:"PLAN-",label:"plan",idLabel:"Plan ID",headingCreated:"Plan Created",errorVerb:"create plan",alchemyKind:"plan-create",canBeActivePlan:!0},quest:{docType:"Quest",filenamePrefix:"QUEST-",label:"quest",idLabel:"Quest ID",headingCreated:"Quest Created",errorVerb:"create quest",alchemyKind:"quest-create",canBeActivePlan:!1}}},
|
|
710
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.KIND_META=exports.QUEST_STATUS_DRAFT=exports.QUEST_STATUS_CLOSED=exports.QUEST_STATUS_OPEN=exports.QUEST_DEFAULTS=exports.PLAN_KINDS=void 0,exports.PLAN_KINDS=["plan","quest"],exports.QUEST_DEFAULTS={tickIntervalSeconds:1800,maxInFlight:1,maxPerDay:24,minIterations:3},exports.QUEST_STATUS_OPEN="open",exports.QUEST_STATUS_CLOSED="closed",exports.QUEST_STATUS_DRAFT="draft",exports.KIND_META={plan:{docType:"Plan",filenamePrefix:"PLAN-",label:"plan",idLabel:"Plan ID",headingCreated:"Plan Created",errorVerb:"create plan",alchemyKind:"plan-create",canBeActivePlan:!0},quest:{docType:"Quest",filenamePrefix:"QUEST-",label:"quest",idLabel:"Quest ID",headingCreated:"Quest Created",errorVerb:"create quest",alchemyKind:"quest-create",canBeActivePlan:!1}}},3435(e,exports,t){"use strict"
|
|
711
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateRequest=function(e,t,r,s=[]){if(!r)return t.status(400).json({success:!1,error:"No project configured"}),!1
|
|
712
|
+
const o=e.body??{},n=s.filter((e=>void 0===o[e]||null===o[e]||""===o[e]))
|
|
713
|
+
if(n.length>0){const e=1===n.length?"is":"are"
|
|
714
|
+
return t.status(400).json({success:!1,error:`${n.join(", ")} ${e} required`}),!1}return!0},exports.respondWithError=function(e,t,s,o){const n=(0,r.getErrorMessage)(o)
|
|
715
|
+
t.error(s+" failed",{error:n}),e.status(500).json({success:!1,error:n})}
|
|
716
|
+
const r=t(8824)},3444(e,exports,t){"use strict"
|
|
722
717
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
723
718
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=exports.defaultLogger=exports.isDebugToolsEnabled=exports.getDebugLevelFromEnv=exports.isDebugLoggingEnabled=exports.createEnvLogger=exports.createLogger=void 0
|
|
724
719
|
const s=t(2782)
|
|
@@ -823,7 +818,15 @@ this.logger.warn(`API request failed, retrying in ${r}ms`,{method:e,url:i,error:
|
|
|
823
818
|
this.failureCount=0,this.circuitState="closed",this.metrics.setGauge("api_failure_count",0),this.metrics.setGauge("circuit_breaker_open",0),this.circuitResetTimeout&&(clearTimeout(this.circuitResetTimeout),this.circuitResetTimeout=null),e&&this.logger.info("Circuit breaker closed after successful request")}openCircuit(){this.circuitState="open",this.metrics.setGauge("circuit_breaker_open",1),this.logger.warn(`Circuit breaker opened after ${this.failureCount} failures`),this.circuitResetTimeout&&clearTimeout(this.circuitResetTimeout)
|
|
824
819
|
const e=this.options.circuitBreakerResetTime||5e3
|
|
825
820
|
this.circuitResetTimeout=setTimeout((()=>{this.circuitState="half-open",this.logger.info("Circuit breaker half-open, next request will probe")}),e)}async get(e,t,r){return this.requestWithRetry("GET",e,void 0,r,t)}async post(e,t,r){return this.requestWithRetry("POST",e,t,r)}async put(e,t,r){return this.requestWithRetry("PUT",e,t,r)}async delete(e,t){return this.requestWithRetry("DELETE",e,void 0,t)}async patch(e,t,r){return this.requestWithRetry("PATCH",e,t,r)}}},4245(module){function e(e){var t=Error("Cannot find module '"+e+"'")
|
|
826
|
-
throw t.code="MODULE_NOT_FOUND",t}e.keys=()=>[],e.resolve=e,e.id=4245,module.exports=e},
|
|
821
|
+
throw t.code="MODULE_NOT_FOUND",t}e.keys=()=>[],e.resolve=e,e.id=4245,module.exports=e},4306(e,exports,t){"use strict"
|
|
822
|
+
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
823
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.setupAgentInWorktree=async function(e){const{projectPath:t,projectId:r,apiClient:a,logger:c,trackChildPid:l}=e
|
|
824
|
+
await(0,o.sweepStaleSetupWorktrees)(t,c)
|
|
825
|
+
const d=await(0,o.resolveSetupBranch)(a,r,e.branchName,e.requestId,c,t),u=(0,o.generateSetupId)(),h=s.default.join((0,o.getSetupWorktreesBase)(t),u)
|
|
826
|
+
try{await(0,o.createSetupWorktree)(t,h,d,c)}catch(t){const r=(0,i.getErrorMessage)(t)
|
|
827
|
+
return c.error(e.worktreeErrorLabel,{branch:d,error:r}),{success:!1,error:"Failed to create setup worktree: "+r,statusCode:500}}const g=e.promptBuilder(t,e.requestId,e.callerContext),p=e.titleBuilder(e.requestId),m=await(0,n.spawnActionAgent)({agent:e.agent,prompt:g,cwd:h,projectPath:t,projectId:r,title:p,requestId:e.requestId,parentSessionId:e.parentSessionId,allowParallel:!0,apiClient:a,logger:c,trackChildPid:l,getDefaultModel:e.getDefaultModel})
|
|
828
|
+
return m.success&&m.sessionId?{success:!0,sessionId:m.sessionId,worktreePath:h,branch:d}:{success:!1,error:m.error||"Spawn failed",statusCode:500}}
|
|
829
|
+
const s=r(t(6928)),o=t(8463),n=t(8148),i=t(8824)},4627(e,exports){"use strict"
|
|
827
830
|
function t(e,t){try{return function(e){const t=r.get(e)
|
|
828
831
|
if(t)return t
|
|
829
832
|
let s=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"{{GLOBSTAR}}").replace(/\*/g,"[^/]*").replace(/\?/g,".").replace(/\{\{GLOBSTAR\}\}/g,".*")
|
|
@@ -877,7 +880,7 @@ if(w){try{const e=await fetch(`${y}/session/${w}/prompt_async`,{method:"POST",he
|
|
|
877
880
|
return{model:{providerID:e,modelID:r.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
|
|
878
881
|
if(!e.ok)return{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:"Failed to inject prompt: "+(e instanceof Error?e.message:e+""),error:"prompt_failed"}}n.info("Injected prompt into existing session",{sessionId:w})}else{const e=await(0,o.spawnActionAgent)({agent:p,prompt:f,cwd:i,projectPath:i,projectId:a,title:m||"prompt-and-wait",apiClient:c,logger:n,trackChildPid:l,getDefaultModel(){return t.cachedDefaultModel||void 0}})
|
|
879
882
|
if(!e.success||!e.sessionId)return{success:!1,message:"Failed to spawn session: "+e.error,error:"spawn_failed"}
|
|
880
|
-
w=e.sessionId,n.info("Spawned session for prompt-and-wait",{sessionId:w})}const
|
|
883
|
+
w=e.sessionId,n.info("Spawned session for prompt-and-wait",{sessionId:w})}const I=await async function(e,t,o){const n=Date.now()+1e3*o
|
|
881
884
|
let i=!1
|
|
882
885
|
const a=Date.now()+15e3
|
|
883
886
|
for(;Date.now()<a&&Date.now()<n;){const o=await r(e,t)
|
|
@@ -885,10 +888,10 @@ if("busy"===o||"running"===o){i=!0
|
|
|
885
888
|
break}await s(500)}for(;Date.now()<n;){const o=await r(e,t)
|
|
886
889
|
if("error"===o)return{status:"error"}
|
|
887
890
|
if(("idle"===o||"completed"===o)&&i)return{status:"completed"}
|
|
888
|
-
"busy"!==o&&"running"!==o||(i=!0),await s(2e3)}return{status:"timeout"}}(y,w,g),
|
|
889
|
-
return r.ok?(await r.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),
|
|
891
|
+
"busy"!==o&&"running"!==o||(i=!0),await s(2e3)}return{status:"timeout"}}(y,w,g),b=Date.now()-v,E=await async function(e,t){try{const r=await fetch(`${e}/session/${t}/message?limit=200`,{signal:AbortSignal.timeout(1e4)})
|
|
892
|
+
return r.ok?(await r.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),C=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(E),S=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
|
|
890
893
|
return""}(E)
|
|
891
|
-
return{success:!0,message:"Prompt completed",data:{sessionId:w,status:
|
|
894
|
+
return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:E,toolCallCount:C,elapsedMs:b,finalResponse:S}}}
|
|
892
895
|
const o=t(8148)},4769(e,exports,t){"use strict"
|
|
893
896
|
function r(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function s(e,t,s,o){const n=r(s)
|
|
894
897
|
if(!n||!t)return!1
|
|
@@ -1011,49 +1014,49 @@ return"idle"===t||"completed"===t}))
|
|
|
1011
1014
|
return{sessions:s,statusMap:o,running:n,idle:i}}catch(e){return this.logger.debug("Failed to fetch OpenCode session state",{error:e+""}),null}}}},6540(e,exports){"use strict"
|
|
1012
1015
|
var t
|
|
1013
1016
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=void 0,function(e){e.ERROR="error",e.WARN="warn",e.INFO="info",e.DEBUG="debug"}(t||(exports.LogLevel=t={}))},6555(e,exports,t){"use strict"
|
|
1014
|
-
function r(e,process,t,r,s=!1,o){const
|
|
1015
|
-
let
|
|
1016
|
-
for(let e=0;e<
|
|
1017
|
-
|
|
1018
|
-
for(const t of e.acceptanceCriteria)
|
|
1019
|
-
|
|
1017
|
+
function r(e,process,t,r,s=!1,o,n){const i=process.config||{},a=(i.setup,i.commands||[]),c=o&&o.length>0?o.map((e=>a.find((t=>t.name===e)))).filter((e=>e)):a,l=new Set(n||[]),d=l.size>0?c.filter((e=>!l.has(e.name))):c,u=o&&o.length>0&&d.length<a.length||l.size>0,h=l.size>0?a.filter((e=>l.has(e.name))).map((e=>e.name)):[]
|
|
1018
|
+
let g=`You are a test runner agent. Your job is to verify that request ${e.localId||e.id} ("${e.title}") passes all required tests.\n\n## Test Process: ${process.name}\n${process.description?`\n${process.description}\n`:""}\n${process.scope?`**Scope:** Files under \`${process.scope}\`\n`:""}\n## Project ID: ${r}\n${h.length>0?`\n_The coder opted out of these command(s) for this completion: **${h.join(", ")}**. Do NOT run them. They've been excluded from the list below._\n`:""}\n## Test Commands${u?" (partial run \u2014 only the commands listed below)":""}\nExecute these in order:\n`
|
|
1019
|
+
for(let e=0;e<d.length;e++){const t=d[e]
|
|
1020
|
+
g+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}if(g+=s?`\n## Instructions\n\nYou are in interactive mode. Present the test plan to the user and **wait for their direction** before running anything.\n\n1. Greet the user and list the test commands available for this process.\n2. Wait for the user to tell you to proceed (they may want to run specific commands, skip some, or run all).\n3. Use \`execute_test_command\` with processId="${process.id}" and the command name to run each command.\n4. After each command, explain the result and wait for the user before continuing.\n5. When the user is satisfied, call \`report_test_results\` with the overall status.\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`:`\n## Instructions\n\nSetup commands have already been executed. Use the \`execute_test_command\` tool to run each test command:\n\n1. For each command above, call \`execute_test_command\` with processId="${process.id}" and the command name.\n2. Check if it passed or failed.\n3. If it fails: try to determine why \u2014 is it a real bug, a flaky test, or an environment issue? Retry once if it looks flaky.\n4. When all commands are done, call \`report_test_results\` with the overall status and per-command results.\n - If all REQUIRED commands passed \u2192 status "passed"\n - If any REQUIRED command failed \u2192 status "failed" with diagnosis of each failure\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`,i.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0){g+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by reading code changes and running ad-hoc commands as needed. This is **advisory only** \u2014 it does not affect the overall pass/fail status.\n\nCriteria to verify:\n"
|
|
1021
|
+
for(const t of e.acceptanceCriteria)g+=`- ${t}\n`
|
|
1022
|
+
g+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return g}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,l){const{requestId:d,testProcessIds:u,parentSessionId:h,mode:g}=e,p="interactive"===g,{logger:m,apiClient:f,projectId:y,projectPath:v,trackChildPid:w}=l
|
|
1020
1023
|
if(!d)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
1021
1024
|
m.info("Starting run-tests action",{requestId:d,testProcessIds:u})
|
|
1022
1025
|
try{const l=await f.get(`/api/projects/${y}/requests/${d}`)
|
|
1023
1026
|
if(!l.success||!l.data)return{success:!1,message:`Request ${d} not found`,error:"Request not found"}
|
|
1024
|
-
const g=l.data,
|
|
1025
|
-
let
|
|
1026
|
-
if(u?.length)
|
|
1027
|
+
const g=l.data,I=g.branchName||"requests/"+d
|
|
1028
|
+
let b
|
|
1029
|
+
if(u?.length)b=(await Promise.all(u.map((e=>f.get(`/api/projects/${y}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
|
|
1027
1030
|
else{const e=await f.get(`/api/projects/${y}/test-processes/active`)
|
|
1028
|
-
|
|
1029
|
-
const E=Math.max(1,Math.min(8,
|
|
1030
|
-
let
|
|
1031
|
-
try{
|
|
1031
|
+
b=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===b.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
|
|
1032
|
+
const E=Math.max(1,Math.min(8,b[0]?.config?.poolSize??2)),C=b[0]?.config?.fingerprint||[]
|
|
1033
|
+
let S
|
|
1034
|
+
try{S=await(0,c.acquireSlot)(v,E)}catch(e){return{success:!1,message:"Could not acquire test worktree slot: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}}const k=S.path,T=(0,n.simpleGit)(v)
|
|
1032
1035
|
try{if((0,s.existsSync)(k))try{const e=(0,n.simpleGit)(k)
|
|
1033
|
-
await e.raw(["reset","--hard",
|
|
1036
|
+
await e.raw(["reset","--hard",I]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:k,branchName:I,slotIndex:S.index})}catch(e){m.warn("Could not reset slot, recreating",{error:(0,i.getErrorMessage)(e)})
|
|
1034
1037
|
try{await T.raw(["worktree","remove","--force",k])}catch{}}if(!(0,s.existsSync)(k)){(0,s.mkdirSync)((0,o.join)(v,".alchemy_lab","test-worktrees"),{recursive:!0})
|
|
1035
|
-
try{if(!(await T.branch()).all.includes(
|
|
1036
|
-
await T.raw(["worktree","add","--detach",k,
|
|
1037
|
-
m.info("Pool slot worktree already exists, using as-is",{worktreePath:k})}}}catch(e){throw(0,c.releaseSlot)(k),e}const _=await(0,c.computeFingerprint)(k,
|
|
1038
|
-
j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:k,slotIndex:
|
|
1038
|
+
try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(k),{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
|
|
1039
|
+
await T.raw(["worktree","add","--detach",k,I]),m.info("Created pool slot worktree (detached)",{worktreePath:k,branchName:I,slotIndex:S.index})}catch(e){if(!(e+"").includes("already exists"))return(0,c.releaseSlot)(k),{success:!1,message:"Failed to create pool slot worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
|
|
1040
|
+
m.info("Pool slot worktree already exists, using as-is",{worktreePath:k})}}}catch(e){throw(0,c.releaseSlot)(k),e}const _=await(0,c.computeFingerprint)(k,C),j=0===C.length||_!==S.storedFingerprint,A=0===C.length?null:_===S.storedFingerprint
|
|
1041
|
+
j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:k,slotIndex:S.index,fingerprintFiles:C})
|
|
1039
1042
|
const{execSync:P}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
1040
|
-
if(j){for(const e of
|
|
1041
|
-
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:
|
|
1042
|
-
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),P(r,{cwd:k,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}
|
|
1043
|
-
e>0&&m.info("Pruned drift from warm slot",{worktreePath:k,slotIndex:
|
|
1043
|
+
if(j){for(const e of b){const t=e.config?.setup||[]
|
|
1044
|
+
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:S.index})
|
|
1045
|
+
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),P(r,{cwd:k,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}C.length>0&&await(0,c.writeSlotFingerprint)(k,_),await(0,c.writeWarmSnapshot)(k)}else{const{removed:e}=await(0,c.restoreToWarmSnapshot)(k)
|
|
1046
|
+
e>0&&m.info("Pruned drift from warm slot",{worktreePath:k,slotIndex:S.index,removed:e})}let M
|
|
1044
1047
|
try{const e=(0,n.simpleGit)(k)
|
|
1045
1048
|
M=(await e.revparse(["HEAD"])).trim()}catch(e){m.warn("Failed to capture worktree HEAD; commit_sha will be NULL on this run",{worktreePath:k,error:(0,i.getErrorMessage)(e)})}const O=[],R=[],D=[]
|
|
1046
1049
|
let x
|
|
1047
1050
|
const $={worktreePath:k,commitSha:M,fingerprintHit:A}
|
|
1048
|
-
for(const process of
|
|
1051
|
+
for(const process of b){let t
|
|
1049
1052
|
const s=e.testRunIds?.[O.length]
|
|
1050
1053
|
try{s?(await f.patch(`/api/projects/${y}/test-runs/${s}`,$),t=(await f.get(`/api/projects/${y}/test-runs/${s}`)).data,O.push(t.id)):(t=(await f.post(`/api/projects/${y}/test-runs`,{requestId:d,testProcessId:process.id,parentSessionId:h,...$})).data,O.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:d,processId:process.id,preCreatedId:s,error:(0,i.getErrorMessage)(e)})
|
|
1051
|
-
continue}const o=r(g,process,0,y,p,e.commandNames),n=p?"tester":"auto-tester"
|
|
1054
|
+
continue}const o=r(g,process,0,y,p,e.commandNames,e.skipCommandNames),n=p?"tester":"auto-tester"
|
|
1052
1055
|
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:k,projectPath:v,projectId:y,title:`[${d}] Test: ${process.name}`,requestId:d,parentSessionId:h,allowParallel:!0,apiClient:f,logger:m,trackChildPid:w})
|
|
1053
1056
|
if(!e.success)throw Error(e.error||"Spawn failed")
|
|
1054
1057
|
R.push(t.id),e.sessionId&&D.push(e.sessionId),e.pid&&(x=e.pid),e.sessionId&&(f.patch(`/api/projects/${y}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),f.post(`/api/projects/${y}/session-test-process-associations`,{opencodeSessionId:e.sessionId,testProcessId:process.id}).catch((t=>m.warn("Failed to create session\u2194test-process association",{sessionId:e.sessionId,testProcessId:process.id,error:(0,i.getErrorMessage)(t)})))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:k,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
|
|
1055
1058
|
try{await f.patch(`/api/projects/${y}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}if(0===R.length)return(0,c.releaseSlot)(k),{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}
|
|
1056
|
-
for(const e of D);return(0,c.watchPidAndRelease)(x,k,12e5),{success:!0,message:`Started ${R.length} test run(s) for request ${d}`,data:{testRunIds:O,agentsSpawned:R.length,processCount:
|
|
1059
|
+
for(const e of D);return(0,c.watchPidAndRelease)(x,k,12e5),{success:!0,message:`Started ${R.length} test run(s) for request ${d}`,data:{testRunIds:O,agentsSpawned:R.length,processCount:b.length,worktreePath:k,spawnedSessionIds:D}}}catch(e){const t=(0,i.getErrorMessage)(e)
|
|
1057
1060
|
return m.error("run-tests action failed",{requestId:d,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
|
|
1058
1061
|
const s=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(8148),c=t(489)},6563(e,exports,t){"use strict"
|
|
1059
1062
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
|
|
@@ -1147,14 +1150,12 @@ if(o){const r=await d.get(`/api/projects/${u}/requests/${o}`)
|
|
|
1147
1150
|
if(!r.success||!r.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
|
|
1148
1151
|
const s=r.data,n=Array.isArray(s.acceptanceCriteria)?s.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):""
|
|
1149
1152
|
e=`Create a plan for request ${o}: ${s.title}\n\nDescription: ${s.description||"No description provided."}\n\n${n?"Acceptance Criteria:\n- "+n:""}\n\nResearch the codebase and create a comprehensive implementation plan.`,t=`[${o}] Plan: ${s.title}`,p=s.title||"New Plan for "+o}else e=n,t=i||"Auto-planner",p=i||"Auto-planner Draft"
|
|
1150
|
-
const m
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if(!v.success)return{success:!1,message:"Failed to spawn auto-planner: "+v.error,error:v.error||"Spawn failed"}
|
|
1155
|
-
if(l.info("Spawned auto-planner",{requestId:o,sessionId:v.sessionId,pid:v.pid,planId:f}),f&&v.sessionId)try{await d.post(`/api/projects/${u}/session-plan-associations`,{opencodeSessionId:v.sessionId,documentId:f})}catch(e){l.warn("Failed to create session-plan association at spawn time",{error:(0,r.getErrorMessage)(e)})}return c&&d.patch(`/api/projects/${u}/session-messages/${c}/response`,{partial:{plannerSessionId:v.sessionId,planId:f}}).catch((e=>l.warn("Failed to record plannerSessionId on origin message",{originMessageCode:c,error:(0,r.getErrorMessage)(e)}))),{success:!0,message:o?"Auto-planner spawned for request "+o:"Auto-planner spawned: "+(i||v.sessionId),data:{sessionId:v.sessionId,pid:v.pid}}}catch(e){const t=(0,r.getErrorMessage)(e)
|
|
1153
|
+
const m=await(0,s.spawnPlannerWithDoc)({agent:"auto-planner",planName:p,prompt:e,title:t,requestId:o,parentSessionId:a,apiClient:d,logger:l,projectId:u,projectPath:h,trackChildPid:g})
|
|
1154
|
+
if(!m.success||!m.sessionId)return{success:!1,message:"Failed to spawn auto-planner: "+m.error,error:m.error||"Spawn failed"}
|
|
1155
|
+
const f=m.planId
|
|
1156
|
+
return c&&d.patch(`/api/projects/${u}/session-messages/${c}/response`,{partial:{plannerSessionId:m.sessionId,planId:f}}).catch((e=>l.warn("Failed to record plannerSessionId on origin message",{originMessageCode:c,error:(0,r.getErrorMessage)(e)}))),{success:!0,message:o?"Auto-planner spawned for request "+o:"Auto-planner spawned: "+(i||m.sessionId),data:{sessionId:m.sessionId,pid:m.pid}}}catch(e){const t=(0,r.getErrorMessage)(e)
|
|
1156
1157
|
return l.error("spawn-auto-planner action failed",{requestId:o,error:t}),{success:!1,message:"Failed to spawn auto-planner: "+t,error:t}}}
|
|
1157
|
-
const r=t(8824),s=t(
|
|
1158
|
+
const r=t(8824),s=t(8649)},7163(e,exports){"use strict"
|
|
1158
1159
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.debugTokenAccess=function(){},exports.compareTokens=function(){},exports.debugTokenStorage=function(){},exports.disableTokenDebugging=function(){},exports.enableTokenDebugging=function(){},new WeakMap},7210(e,exports,t){"use strict"
|
|
1159
1160
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.tryMarkMessageProcessed=exports.createToolCallHash=exports.isDuplicateToolCall=exports.determineEventToEmit=exports.interpolatePrompt=exports.evaluateCondition=exports.TimerConditionEvaluator=exports.TimerHookScheduler=exports.HookCache=exports.HookExecutor=void 0
|
|
1160
1161
|
var r=t(3077)
|
|
@@ -1170,7 +1171,7 @@ Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return i.
|
|
|
1170
1171
|
function t(e){if("string"!=typeof e)return e
|
|
1171
1172
|
try{return JSON.parse(e)}catch{return e}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseToolEvents=function(e){const{toolName:o,sessionId:n,input:i,output:a,timing:c}=e
|
|
1172
1173
|
if("before"===c)return[{eventType:"tool_executed",timing:"before",variables:{tool:o,toolName:o,input:JSON.stringify(i??{}).substring(0,1e3),sessionId:n||""}}]
|
|
1173
|
-
const l=[],d=a||"",u={output:d,toolName:o,sessionId:n||""},h=o.includes("request_operations")||o.includes("complete_request"),g=o.includes("task_operations")||o.includes("complete_task"),p=o.includes("write_file"),m=o.includes("replace_in_files"),f=o.includes("quick_write")||o.includes("quick_replace")||o.includes("quick_manage"),y=o.includes("read_files"),v=o.includes("search"),w=o.includes("manage_files"),
|
|
1174
|
+
const l=[],d=a||"",u={output:d,toolName:o,sessionId:n||""},h=o.includes("request_operations")||o.includes("complete_request"),g=o.includes("task_operations")||o.includes("complete_task"),p=o.includes("write_file"),m=o.includes("replace_in_files"),f=o.includes("quick_write")||o.includes("quick_replace")||o.includes("quick_manage"),y=o.includes("read_files"),v=o.includes("search"),w=o.includes("manage_files"),I=o.includes("document_operations"),b=o.includes("query_history")
|
|
1174
1175
|
if(h&&d){const e=i?.operation,t=function(e){for(const t of r){const r=e.match(t)
|
|
1175
1176
|
if(r)return r[1]}return null}(d)
|
|
1176
1177
|
t&&("merge"===e||"finalize"===e?l.push({eventType:"request_completed",timing:"after",variables:{...u,requestId:t}}):"complete"===e&&l.push({eventType:"request_ready_for_merge",timing:"after",variables:{...u,requestId:t}}))}else if(g&&d){const e=function(e){const t=e.match(s)
|
|
@@ -1178,10 +1179,10 @@ return t?t[1]:null}(d)
|
|
|
1178
1179
|
e&&l.push({eventType:"task_completed",timing:"after",variables:{...u,taskId:e}})}else if(p){const e=d.match(/(?:Created|Updated|Wrote)\s+(?:file\s+)?([^\s\n]+)/i)
|
|
1179
1180
|
l.push({eventType:"write_file",timing:"after",variables:{...u,path:e?.[1]||""}})}else if(m){const e=t(i?.operations)||[],r=(Array.isArray(e)?e:[]).map((e=>e.path||e.documentId)).filter(Boolean)
|
|
1180
1181
|
l.push({eventType:"replace_in_files",timing:"after",variables:{...u,paths:r.join(", "),fileCount:r.length+""}})}else if(f){const e={...u,path:i?.operationArgs?.path||"",title:i?.title||""},t=d.match(/\*\*Request:\*\*\s*(req\d+)/i)
|
|
1181
|
-
t&&(e.requestId=t[1]),l.push({eventType:"quick_edit",timing:"after",variables:e})
|
|
1182
|
+
t&&(e.requestId=t[1]),l.push({eventType:"quick_edit",timing:"after",variables:e})}else if(y){const e=t(i?.paths)||[],r=(Array.isArray(e)?e:[]).map((e=>e.path||e.documentId)).filter(Boolean)
|
|
1182
1183
|
l.push({eventType:"read_files",timing:"after",variables:{...u,paths:r.join(", "),fileCount:r.length+""}})}else if(v)l.push({eventType:"search",timing:"after",variables:{...u,query:i?.needle||"",directory:i?.directory||"",fileExtension:i?.options?.fileExtension||"*"}})
|
|
1183
1184
|
else if(w){const e=t(i?.operations)||[],r=(Array.isArray(e)?e:[]).map((e=>e.operationType)).filter(Boolean)
|
|
1184
|
-
l.push({eventType:"manage_files",timing:"after",variables:{...u,operationTypes:[...new Set(r)].join(", ")}})}else
|
|
1185
|
+
l.push({eventType:"manage_files",timing:"after",variables:{...u,operationTypes:[...new Set(r)].join(", ")}})}else I?l.push({eventType:"document_operations",timing:"after",variables:u}):b&&l.push({eventType:"query_history",timing:"after",variables:u})
|
|
1185
1186
|
return l.push({eventType:"tool_executed",timing:"after",variables:{tool:o,toolName:o,output:d,sessionId:n||""}}),l}
|
|
1186
1187
|
const r=[/Request completed:\s*(req\d+)/i,/Request (req\d+) completed/i,/completed.*?(req\d{6})/i,/Request finalized:\s*(req\d+)/i,/Request (req\d+) finalized/i,/finalized.*?(req\d{6})/i,/Request (req\d+) merged/i,/merged.*?(req\d{6})/i],s=/Task\s+(tas\d+).*(?:completed|marked as completed)/i},7252(module){"use strict"
|
|
1187
1188
|
module.exports=require("express")},7388(e,exports,t){"use strict"
|
|
@@ -1385,15 +1386,15 @@ let y
|
|
|
1385
1386
|
if(p&&(o.ALCHEMY_INITIAL_BRANCH=p),r(u,"Spawn env setup",{agent:t,initialBranch:p||"none",sessionId:h||"new",attachMode:!!m}),y=m&&h&&d?["run","--attach",m,"--session",h,"--agent",t,d]:h&&d?["run","--session",h,"--agent",t,d]:d?["run","--agent",t,d]:["run","--agent",t],g){const e=y.findIndex((e=>e===d))
|
|
1386
1387
|
e>0?y.splice(e,0,"--model",g):y.push("--model",g)}r(u,"Spawning opencode",{args:y.map((e=>e===d?`[prompt:${e.length}chars]`:e)),model:g||"not specified",promptLength:d?.length,promptPreview:d?.slice(0,100),cwd:u,hasSessionId:!!h})
|
|
1387
1388
|
const v=(0,i.join)(u||process.cwd(),".alchemy_lab"),w=(0,i.join)(v,"spawn-stderr.log")
|
|
1388
|
-
let
|
|
1389
|
-
try{
|
|
1389
|
+
let I
|
|
1390
|
+
try{I=(0,n.openSync)(w,"a")}catch{}const b=(0,a.resolveRunCommand)(),E=(0,s.spawn)(b.command,[...b.prependArgs,...y],{detached:!0,stdio:["ignore","ignore",I??"ignore"],cwd:u||process.cwd(),env:o})
|
|
1390
1391
|
return E.unref(),f&&f(E.pid),{success:!0,pid:E.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
|
|
1391
1392
|
const s=t(5317),o=t(8824),n=t(9896),i=t(6928),a=t(9068),c=t(5419),l=t(3962)},8148(e,exports,t){"use strict"
|
|
1392
1393
|
function r(e){return(0,n.getOpenCodeUrlFromConfig)(e)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=r,exports.spawnActionAgent=async function(e){const{agent:t,prompt:n,cwd:a,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:h,apiClient:g,logger:p,trackChildPid:m}=e
|
|
1393
1394
|
let f,y,v,w=null
|
|
1394
1395
|
try{const r=new i.SessionSpawnService({apiClient:g,logger:p,projectId:e.projectId,getDefaultModel:e.getDefaultModel||(()=>{})}),s=await r.prepareSession({agent:t,prompt:n,projectPath:c,requestId:d,parentSessionId:u,title:l,allowParallel:h})
|
|
1395
|
-
w=s.sessionId,f=s.model,y=s.serverUrl,v=s.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,o.getErrorMessage)(e)}),y=r(c)||void 0}const
|
|
1396
|
-
return
|
|
1396
|
+
w=s.sessionId,f=s.model,y=s.serverUrl,v=s.initialBranch}catch(e){p.warn("Session preparation failed \u2014 spawning without pre-created session",{error:(0,o.getErrorMessage)(e)}),y=r(c)||void 0}const I=(0,s.spawnDetachedSubagent)({agent:t,prompt:n,cwd:a,sessionId:w||void 0,serverUrl:y,model:f,initialBranch:v,allowParallel:h??!1,onSpawn(e){e&&m&&m(e)}})
|
|
1397
|
+
return I.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:I.pid,requestId:d}),{success:!0,sessionId:w,pid:I.pid}):{success:!1,sessionId:w,error:I.error||"Spawn failed"}}
|
|
1397
1398
|
const s=t(8035),o=t(8824),n=t(833),i=t(1292)},8463(e,exports,t){"use strict"
|
|
1398
1399
|
function r(e){return(0,o.join)(e,c,l)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getSetupWorktreesBase=r,exports.sweepStaleSetupWorktrees=async function(e,t){const i=r(e)
|
|
1399
1400
|
if(!(0,s.existsSync)(i))return
|
|
@@ -1406,8 +1407,8 @@ if(r?.success&&r?.data?.branchName)return r.data.branchName
|
|
|
1406
1407
|
if(r?.success&&r?.data)return"requests/"+s}catch(e){o.debug("resolveSetupBranch: request lookup failed, falling back to repo HEAD",{requestId:s,error:(0,a.getErrorMessage)(e)})}if(i)try{const e=(0,n.simpleGit)(i),t=(await e.revparse(["--abbrev-ref","HEAD"])).trim()
|
|
1407
1408
|
if(t&&"HEAD"!==t)return t}catch(e){o.debug("resolveSetupBranch: revparse HEAD failed, falling back to main",{error:(0,a.getErrorMessage)(e)})}return"main"},exports.createSetupWorktree=async function(e,t,o,i){(0,s.mkdirSync)(r(e),{recursive:!0})
|
|
1408
1409
|
const a=(0,n.simpleGit)(e)
|
|
1409
|
-
await a.raw(["worktree","add","--detach",t,o]),i.info("Created setup worktree",{worktreePath:t,branch:o})},exports.buildSetupPrompt=function(e,t){const r=["You are configuring the first test process for this project.","","Project path: "+e]
|
|
1410
|
-
return t&&
|
|
1410
|
+
await a.raw(["worktree","add","--detach",t,o]),i.info("Created setup worktree",{worktreePath:t,branch:o})},exports.buildSetupPrompt=function(e,t,r){const s=!(!r||!r.trim()),o=[s?"You are configuring an ADDITIONAL test process for this project (others already exist).":"You are configuring the first test process for this project.","","Project path: "+e]
|
|
1411
|
+
return t&&o.push("Triggered from request: "+t),s&&(o.push(""),o.push(r.trim())),o.push(""),o.push("1. Inspect manifests (package.json, pyproject.toml, Cargo.toml, go.mod, Makefile, CI configs) to identify build/test/lint commands."),o.push("2. Use `testing_reference` for per-language patterns."),o.push("3. Propose a draft to the user (name, setup commands, test commands with timeouts) as a single message."),o.push("4. Iterate until the user confirms."),o.push("5. Call `testing_process_create` with the agreed configuration."),o.push("6. Send a brief report back to your parent via `session_message_operations` (type: report) summarizing what you registered, then end your session."),o.push(""),o.push("You are read-only with respect to project files. Do not modify source code or run tests."),o.join("\n")},exports.buildCommandSetupPrompt=function(e,t,r){const s=["You are configuring fast project commands (typecheck, lint, build, format, dev server) for this project.","","Project path: "+e]
|
|
1411
1412
|
return t&&s.push("Triggered from request: "+t),r&&r.trim()&&(s.push(""),s.push("Caller hint: "+r.trim()),s.push("Treat the caller hint as guidance about what the user likely wants \u2014 still do your full discovery and confirm with the user before registering. Do not blindly register only what was hinted.")),s.push(""),s.push('1. Call `commands({ operation: "list" })` to see what is already registered.'),s.push("2. Inspect manifests at any depth (package.json, pyproject.toml, Cargo.toml, go.mod, Makefile, deno.json, CI configs)."),s.push("3. For monorepos: ask the user which contexts they want commands for. Propose one command per (context x kind) with the right cwd."),s.push("4. Propose a draft to the user (name, command, description, cwd, timeout) in a single message."),s.push("5. Iterate until the user confirms."),s.push('6. Call `commands({ operation: "register", commands: [...] })` to submit the agreed batch \u2014 each item creates a per-command approval notification the user must click through.'),s.push("7. Send a brief report back to your parent via `session_message_operations` (type: report) summarizing what you registered, then end your session."),s.push(""),s.push("You are read-only with respect to project files. Do not modify source code or run the commands."),s.join("\n")}
|
|
1412
1413
|
const s=t(9896),o=t(6928),n=t(3807),i=t(6982),a=t(8824),c=".alchemy_lab",l="setup-worktrees"},8524(e,exports,t){"use strict"
|
|
1413
1414
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
@@ -1435,7 +1436,24 @@ exports.ERROR_TYPE_PROPERTIES={[i.TASK_NOT_FOUND]:{statusCode:404,retryable:!1,d
|
|
|
1435
1436
|
class a extends Error{constructor(e,t="INTERNAL_ERROR",r=500,s){super(e),this.name=this.constructor.name,this.code=t,this.statusCode=r,this.details=s||{},s&&"code"in s&&"string"==typeof s.code&&(this.code=s.code)
|
|
1436
1437
|
const o=exports.ERROR_TYPE_PROPERTIES[this.code]
|
|
1437
1438
|
this.retryable=o?o.retryable:!s||!("retryable"in s)||!!s.retryable,Error.captureStackTrace(this,this.constructor)}toJSON(){return{name:this.name,message:this.message,code:this.code,statusCode:this.statusCode,details:this.details,retryable:this.retryable,stack:this.stack}}get metadata(){return this.details}isRetryable(){return this.retryable}}class c extends a{constructor(e="Resource not found",t){super(e,i.NOT_FOUND,404,t),this.resourceType="unknown",t&&("taskId"in t?(this.code=i.TASK_NOT_FOUND,this.resourceType="task"):"requestId"in t&&(this.code=i.REQUEST_NOT_FOUND,this.resourceType="request"))}}class l extends a{constructor(e="Invalid input",t){super(e,i.VALIDATION_ERROR,400,t)}}class d extends a{constructor(e="Authentication required",t){super(e,i.UNAUTHORIZED,401,t)}}class u extends a{constructor(e="Not authorized",t){super(e,i.FORBIDDEN,403,t)}}class h extends a{constructor(e="Database error",t){super(e,"DATABASE_ERROR",500,t)}}class g extends a{constructor(e="Service unavailable",t){if(super(e,i.SERVICE_UNAVAILABLE,503,t),t&&"structuredError"in t){const e=t.structuredError
|
|
1438
|
-
e&&e.error&&"retryable"in e.error&&(this.details.retryable=!!e.error.retryable)}}}class p extends a{constructor(e="File operation error",t){super(e,"FILE_OPERATION_ERROR",500,t)}}class m extends a{constructor(e="MCP protocol error",t){super(e,i.MCP_PROTOCOL_ERROR,500,t)}}exports.AppError=a,exports.NotFoundError=c,exports.ValidationError=l,exports.AuthenticationError=d,exports.AuthorizationError=u,exports.DatabaseError=h,exports.ServiceError=g,exports.FileOperationError=p,exports.McpProtocolError=m,exports.UnauthorizedError=d},
|
|
1439
|
+
e&&e.error&&"retryable"in e.error&&(this.details.retryable=!!e.error.retryable)}}}class p extends a{constructor(e="File operation error",t){super(e,"FILE_OPERATION_ERROR",500,t)}}class m extends a{constructor(e="MCP protocol error",t){super(e,i.MCP_PROTOCOL_ERROR,500,t)}}exports.AppError=a,exports.NotFoundError=c,exports.ValidationError=l,exports.AuthenticationError=d,exports.AuthorizationError=u,exports.DatabaseError=h,exports.ServiceError=g,exports.FileOperationError=p,exports.McpProtocolError=m,exports.UnauthorizedError=d},8649(e,exports,t){"use strict"
|
|
1440
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnPlannerWithDoc=async function(e){const{agent:t,prompt:o,title:n,parentSessionId:i,requestId:a,apiClient:c,logger:l,projectId:d,projectPath:u,trackChildPid:h}=e
|
|
1441
|
+
let g=e.planId
|
|
1442
|
+
if(!g){const r=`PLAN-${(e.planName||n).replace(/\.md$/i,"")}.md`
|
|
1443
|
+
try{const e=await c.post(`/api/projects/${d}/documents`,{filename:r,content:" ",type:"Plan"})
|
|
1444
|
+
g=e?.data?.id,l.info("spawnPlannerWithDoc: pre-created plan document",{planId:g,filename:r,agent:t})}catch(e){l.warn("spawnPlannerWithDoc: failed to pre-create plan document; planner will fall back to plan_write create",{error:(0,s.getErrorMessage)(e),agent:t})}}const p="quest-planner"===t?"quest":"plan",m=g?`[Session Context]\n- ${p}Id: ${g} \u2014 this is YOUR associated ${p} document. It already exists with a placeholder filename.\n- Use plan_write (update / append / replace) with planId "${g}" to fill it in.${"quest-planner"===t?" For quests, plan_write update also accepts criteria / tickIntervalSeconds / maxInFlight / maxPerDay / minIterations / status.":""}${"quest-planner"===t?"":'\n- On your FIRST plan_write call, include a "name" field with a short descriptive title so the document gets a meaningful filename.'}\n- Do NOT call plan_write "create" \u2014 always modify the document above.\n- Do NOT call plan_read "get-active" \u2014 your associated ${p} is given here, not via the active-${p} slot.\n\n${o}`:o,f=await(0,r.spawnActionAgent)({agent:t,prompt:m,cwd:u,projectPath:u,projectId:d,title:n,requestId:a,parentSessionId:i,apiClient:c,logger:l,trackChildPid:h})
|
|
1445
|
+
if(!f.success||!f.sessionId)return{success:!1,error:f.error||"spawnActionAgent returned no sessionId"}
|
|
1446
|
+
l.info("spawnPlannerWithDoc: spawn succeeded",{agent:t,sessionId:f.sessionId,pid:f.pid,planId:g})
|
|
1447
|
+
const y=(0,r.getOpenCodeServerUrl)(u)
|
|
1448
|
+
if(y&&async function(e){const{serverUrl:t,sessionId:r,logger:s}=e,o=t.replace(/\/$/,""),n=Date.now()+3e4
|
|
1449
|
+
let i=0
|
|
1450
|
+
for(;Date.now()<n;){i++
|
|
1451
|
+
try{const e=await fetch(`${o}/session/${r}/message`,{signal:AbortSignal.timeout(5e3)})
|
|
1452
|
+
if(e.ok){const t=(await e.json()).find((e=>"user"===e.info?.role)),n=t?.parts.find((e=>"text"===e.type))
|
|
1453
|
+
if(t&&n){const e={...n,metadata:{...n.metadata||{},alchemy:{kind:"agent-context"}}},a=`${o}/session/${r}/message/${t.info.id}/part/${n.id}`,c=await fetch(a,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(5e3)})
|
|
1454
|
+
if(c.ok)return void s.info("Tagged planner agent-context message",{sessionId:r,attempt:i})
|
|
1455
|
+
s.warn("PATCH planner agent-context failed",{sessionId:r,status:c.status})}}}catch{}await new Promise((e=>setTimeout(e,500)))}s.warn("Timed out tagging planner agent-context; spawn prompt will appear as user message",{sessionId:r,attempts:i})}({serverUrl:y,sessionId:f.sessionId,logger:l}),g)try{await c.post(`/api/projects/${d}/session-plan-associations`,{opencodeSessionId:f.sessionId,documentId:g})}catch(e){l.warn("spawnPlannerWithDoc: failed to create session-plan association",{error:(0,s.getErrorMessage)(e),sessionId:f.sessionId,planId:g})}return{success:!0,sessionId:f.sessionId,planId:g,pid:f.pid}}
|
|
1456
|
+
const r=t(8148),s=t(8824)},8740(e,exports){"use strict"
|
|
1439
1457
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookCache=void 0,exports.HookCache=class{constructor(e,t,r){this.cache=new Map,this.logger=e,this.projectId=t,this.apiClient=r}setProjectId(e){this.projectId=e}getCacheKey(e,t){return t?`${e}:session:${t}`:e+":all"}async getHooksForEvent(e,t,r){const s=this.getCacheKey(e,t)
|
|
1440
1458
|
if(!r){const e=this.cache.get(s)
|
|
1441
1459
|
if(e&&6e4>Date.now()-e.time)return e.hooks.filter((e=>e.enabled))}try{const r=t?"?sessionId="+encodeURIComponent(t):"",o=await this.apiClient.get(`/api/projects/${this.projectId}/event-hooks/by-event/${e}${r}`),n=(o?.data||o||[]).map((e=>({id:e.id||e.hookId,name:e.name||e.hookName,eventType:e.eventType||e.hookEventType,agent:e.agent||e.hookAgent,promptTemplate:e.promptTemplate||e.hookPromptTemplate,condition:e.condition||e.hookCondition||{},enabled:!1!==e.enabled,action:e.action||e.hookAction||void 0,target:e.target||e.hookTarget||void 0,timing:e.timing||e.hookTiming||"after",maxIterations:e.maxIterations||e.hookMaxIterations||void 0,emitEvent:e.emitEvent||e.hookEmitEvent||void 0,emitEventOnCondition:e.emitEventOnCondition||e.hookEmitEventOnCondition||void 0,intervalSeconds:e.intervalSeconds||e.hookIntervalSeconds||void 0,timerCondition:e.timerCondition||e.hookTimerCondition||void 0,timerExpiresAfter:e.timerExpiresAfter||e.hookTimerExpiresAfter||void 0,stopAfterInactiveMinutes:e.stopAfterInactiveMinutes??e.hookStopAfterInactiveMinutes??10})))
|
|
@@ -1525,15 +1543,15 @@ let g=null,p=null,m=null,f=null,y=!1,v=!1
|
|
|
1525
1543
|
const w=c.join(s,".alchemy_lab","config.json")
|
|
1526
1544
|
if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
|
|
1527
1545
|
t?.openCodeTools?(g=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:g})):v=!0,t?.openCodeModel&&(p=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),v=!0}try{const e=await r.readFile(h,"utf-8"),t=JSON.parse(e)
|
|
1528
|
-
t?.mode?.alchemy?.tools&&(m=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:m})),t?.model&&(f=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const
|
|
1529
|
-
i?.openCodeModel?
|
|
1530
|
-
const
|
|
1531
|
-
await r.mkdir(
|
|
1532
|
-
const
|
|
1546
|
+
t?.mode?.alchemy?.tools&&(m=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:m})),t?.model&&(f=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const I=i?.openCodeTools||g||m||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},b={tools:I},E=i?.openCodeModel||p||f||"anthropic/claude-opus-4-5"
|
|
1547
|
+
i?.openCodeModel?b.model=i.openCodeModel:p?b.model=p:f&&(b.model=f)
|
|
1548
|
+
const C=c.join(s,".opencode","agent")
|
|
1549
|
+
await r.mkdir(C,{recursive:!0})
|
|
1550
|
+
const S=E||"anthropic/claude-opus-4-5-20251101"
|
|
1533
1551
|
let k,T=null,_=null
|
|
1534
1552
|
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),_=await this.fetchProjectFromRemote(i.projectId),k=_?.metadata?.globalSystemPrompt,k&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:k.length}),T=await this.fetchAgentsFromRemote(i.projectId),T&&T.length>0){this.logger.info("Using agents from remote database",{count:T.length})
|
|
1535
1553
|
for(const e of T){if(!e.enabled)continue
|
|
1536
|
-
const t=this.generateAgentMarkdown(e,d,
|
|
1554
|
+
const t=this.generateAgentMarkdown(e,d,S,k),s=c.join(C,e.name+".md")
|
|
1537
1555
|
await r.writeFile(s,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:s,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
|
|
1538
1556
|
else this.logger.warn("No projectId provided - agents must be configured via database")
|
|
1539
1557
|
const j={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0,timeout:6e5}}}
|
|
@@ -1546,14 +1564,14 @@ e.name in r&&(t.bash=!!r[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.
|
|
|
1546
1564
|
let s=""
|
|
1547
1565
|
k&&(s+=k.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),j.agent[e.name]={tools:t,...s?{prompt:s}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}j.agent.general={...j.agent.general,disable:!0},j.agent.explore={...j.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}}
|
|
1548
1566
|
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${k?k.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`
|
|
1549
|
-
await r.writeFile(c.join(
|
|
1567
|
+
await r.writeFile(c.join(C,"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(j.agent).length})}const A=e=>"string"==typeof e&&e.includes(".opencode/plugin/"),P=(await this.fetchPluginManifest())["opencode-claude-auth"],M=j.plugin,O=Array.isArray(M)?M.filter((e=>!A(e))):[]
|
|
1550
1568
|
P&&this.applyAuthPluginPin(O,P),O.length>0?j.plugin=O:delete j.plugin,await r.writeFile(h,JSON.stringify(j,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:h})
|
|
1551
1569
|
try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),s=c.join(e.homedir(),".alchemy_lab","opencode","config"),o=c.join(s,"opencode.json")
|
|
1552
1570
|
let n=null
|
|
1553
1571
|
try{n=JSON.parse(await r.readFile(o,"utf-8"))}catch{}const i=n??{},a=Array.isArray(i.plugin)?i.plugin.slice():[],l=a.filter((e=>!A(e)))
|
|
1554
1572
|
P&&this.applyAuthPluginPin(l,P),a.length===l.length&&!a.some(((e,t)=>e!==l[t]))&&n||(i.plugin=l,await r.mkdir(s,{recursive:!0}),await r.writeFile(o,JSON.stringify(i,null,2),"utf8"),this.logger.info("Updated global opencode config plugin list",{globalConfigPath:o,plugins:l}))}catch(e){this.logger.warn("Failed to update global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(v&&!i)try{let e={}
|
|
1555
1573
|
try{const t=await r.readFile(w,"utf-8")
|
|
1556
|
-
e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=
|
|
1574
|
+
e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=I,await r.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPluginsGlobal:r,cleanupLegacyProjectPlugins:o}=await t.e(939).then(t.t.bind(t,9939,23)),n=await r(l??!1,this.logger,e)
|
|
1557
1575
|
n.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:n.errors}),await o(s,this.logger)}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,r.getErrorMessage)(e)}),{success:!1,error:(0,r.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=o,o.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9023(module){"use strict"
|
|
1558
1576
|
module.exports=require("util")},9067(e,exports,t){"use strict"
|
|
1559
1577
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
@@ -1611,7 +1629,7 @@ if(s(parseInt(e.replace("v","").split(".")[0],10)))return"node"}catch{}const e=p
|
|
|
1611
1629
|
if((0,i.existsSync)(t))try{const e=(0,i.readdirSync)(t).filter((e=>s(parseInt(e.replace("v","").split(".")[0],10)))).sort(((e,t)=>{const r=parseInt(e.replace("v","").split(".")[0],10)
|
|
1612
1630
|
return parseInt(t.replace("v","").split(".")[0],10)-r}))
|
|
1613
1631
|
if(e.length>0){const r=(0,n.join)(t,e[0],"bin","node")
|
|
1614
|
-
if((0,i.existsSync)(r))return r}}catch{}throw Error(`AlchemyLab requires Node.js 22 or 24+ to run the OpenCode server (Node 23 is not supported).\nYour current version: ${process.version}\nInstall a compatible version: nvm install 22 (or download from https://nodejs.org)`)}(),prependArgs:["--no-warnings",(0,n.join)(e,t)]}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.resolveServeCommand=function(){const e=r()
|
|
1632
|
+
if((0,i.existsSync)(r))return r}}catch{}throw Error(`AlchemyLab requires Node.js 22 or 24+ to run the OpenCode server (Node 23 is not supported).\nYour current version: ${process.version}\nInstall a compatible version: nvm install 22 (or download from https://nodejs.org)`)}(),prependArgs:["--no-warnings",(0,n.join)(e,t)]}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.findOpencodeDir=r,exports.resolveServeCommand=function(){const e=r()
|
|
1615
1633
|
return e?o((0,n.join)(e,"dist"),"serve.js"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=r()
|
|
1616
1634
|
return e?o((0,n.join)(e,"dist"),"run.js"):{command:"opencode",prependArgs:[]}}
|
|
1617
1635
|
const n=t(6928),i=t(9896),a=t(5317)},9266(e,exports,t){"use strict"
|
|
@@ -1666,15 +1684,15 @@ setTimeout((()=>{clearInterval(s),r(!1)}),t)}))}async function a(t){if(process.e
|
|
|
1666
1684
|
try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),s=r(t)
|
|
1667
1685
|
return(await s.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,r,s,o="cli-init"){try{const n=(0,h.basename)(r)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:r,createdVia:o}})})
|
|
1668
1686
|
if(i.ok){const e=await i.json()
|
|
1669
|
-
return e.data?.id||e.id}s.info(`Project registration failed (HTTP ${i.status})`)}catch(e){s.info("Project registration failed",{error:(0,
|
|
1670
|
-
const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),m=e(1253),f=e(8984),y=e(2366),v=e(3962),w=e(1187),
|
|
1687
|
+
return e.data?.id||e.id}s.info(`Project registration failed (HTTP ${i.status})`)}catch(e){s.info("Project registration failed",{error:(0,I.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
|
|
1688
|
+
const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),m=e(1253),f=e(8984),y=e(2366),v=e(3962),w=e(1187),I=e(8824),b=e(6800),E=e(4139);(async function(){23===parseInt(process.version.replace("v","").split(".")[0],10)&&(console.error(`\n\u274c Node.js ${process.version} is not supported.`),console.error(" AlchemyLab requires Node.js 22 or 24+ (Node 23 has a broken sqlite API)."),console.error("\n Switch with: nvm use 22 or nvm use 24\n"),process.exit(1))
|
|
1671
1689
|
const i=process.argv.slice(2)
|
|
1672
|
-
let
|
|
1673
|
-
const
|
|
1674
|
-
const k=i.filter(((e,t)=>"--port"!==e&&(-1===
|
|
1690
|
+
let C
|
|
1691
|
+
const S=i.indexOf("--port");-1!==S&&i[S+1]&&(C=parseInt(i[S+1],10),isNaN(C)&&(console.error("\u274c Invalid port number"),process.exit(1)))
|
|
1692
|
+
const k=i.filter(((e,t)=>"--port"!==e&&(-1===S||t!==S+1)))
|
|
1675
1693
|
if(0===k.length)return void await async function(r={}){const i=process.cwd(),l=(0,h.join)(i,E.ALCHEMY_DIR_NAME)
|
|
1676
1694
|
await(0,u.mkdir)(l,{recursive:!0})
|
|
1677
|
-
const
|
|
1695
|
+
const C=(0,h.join)(l,"server.log"),{createLogger:S}=await Promise.resolve().then(e.t.bind(e,2782,23)),k=S({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:C,baseDir:i}),T=await a(k),_=new m.TokenManager(k,i,{remoteServerUrl:T,allowInteractiveAuth:!1})
|
|
1678
1696
|
await _.initialize()
|
|
1679
1697
|
let j=await _.getToken()
|
|
1680
1698
|
if(!j?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
|
|
@@ -1702,15 +1720,15 @@ console.log(" Downloading from: "+t)
|
|
|
1702
1720
|
const s=(0,h.join)((0,p.homedir)(),".local","bin")
|
|
1703
1721
|
await(0,u.mkdir)(s,{recursive:!0})
|
|
1704
1722
|
const o=(0,h.join)(s,"opencode")
|
|
1705
|
-
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(s)||(console.log(` \u26a0\ufe0f Add ${s} to your PATH:`),console.log(` export PATH="${s}:$PATH"`),console.log(""),process.env.PATH=`${s}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,
|
|
1723
|
+
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(s)||(console.log(` \u26a0\ufe0f Add ${s} to your PATH:`),console.log(` export PATH="${s}:$PATH"`),console.log(""),process.env.PATH=`${s}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,I.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)
|
|
1706
1724
|
const O=t()
|
|
1707
1725
|
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)
|
|
1708
|
-
const R=r.port||await(0,
|
|
1726
|
+
const R=r.port||await(0,b.findAvailablePort)(3456),D=new y.AlchemyActionServer(k,{projectPath:i,remoteServerUrl:T,port:R,authToken:A,tokenManager:_})
|
|
1709
1727
|
try{const t=(0,v.getInstallContext)(),r=new f.MCPClientConfigurer(k,T,A),s=new w.UnifiedLauncher(k,t,r),[,a,c]=await Promise.all([D.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
|
|
1710
1728
|
if(T.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
|
|
1711
1729
|
try{const e=await fetch(T+"/health")
|
|
1712
1730
|
if(e.ok){const t=await e.json()
|
|
1713
|
-
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return T.replace(/:\d+/,":3000")})(),s.launch({projectPath:i,settings:M?{projectId:M}:void 0}).catch((e=>(k.warn("Failed to launch OpenCode serve",{error:(0,
|
|
1731
|
+
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return T.replace(/:\d+/,":3000")})(),s.launch({projectPath:i,settings:M?{projectId:M}:void 0}).catch((e=>(k.warn("Failed to launch OpenCode serve",{error:(0,I.getErrorMessage)(e)}),{success:!1,error:(0,I.getErrorMessage)(e)})))])
|
|
1714
1732
|
if(console.log(""),console.log(" Action server .... port "+D.getPort()),c.success&&"port"in c&&c.port){"pid"in c&&c.pid&&D.trackChildPid(c.pid),D.setOpenCodePort(c.port),console.log(" OpenCode serve ... port "+c.port)
|
|
1715
1733
|
const e=(0,h.join)(i,E.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),k.info("Wrote OpenCode config",{path:e,port:c.port})}else c.success?console.log(" OpenCode serve ... started"):console.log(" OpenCode serve ... failed (run manually: opencode serve)")
|
|
1716
1734
|
console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
|
|
@@ -1723,7 +1741,7 @@ u=setInterval((async()=>{if(!y)try{if(await _.hasValidToken())return
|
|
|
1723
1741
|
y=!0,console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
|
|
1724
1742
|
const e=await _.startAuthenticationFlow()
|
|
1725
1743
|
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(_)){const e=await _.getToken()
|
|
1726
|
-
e?.token&&(A=e.token,D.updateAuthToken(A)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){k.warn("Token watchdog check failed",{error:(0,
|
|
1744
|
+
e?.token&&(A=e.token,D.updateAuthToken(A)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){k.warn("Token watchdog check failed",{error:(0,I.getErrorMessage)(e)})}finally{y=!1}}),3e5),await m.start(g)}catch(e){k.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:C})
|
|
1727
1745
|
const T=k[0].toLowerCase()
|
|
1728
1746
|
switch("--help"!==T&&"-h"!==T||(r(),process.exit(0)),"--version"!==T&&"-v"!==T||(console.log("alchemy v"+t()),process.exit(0)),T){case"serve":await async function(e={}){const r=process.cwd()
|
|
1729
1747
|
s(r)||(console.log("\u{1f4c1} Current directory is not an AlchemyLab project."),console.log(""),console.log("\u{1f4a1} Run `alchemy` to initialize this project."),console.log(""),process.exit(1))
|
|
@@ -1733,11 +1751,11 @@ const i=await a(o),c=new m.TokenManager(o,r,{remoteServerUrl:i,allowInteractiveA
|
|
|
1733
1751
|
await c.initialize()
|
|
1734
1752
|
const d=await c.getToken(),u=d?.token
|
|
1735
1753
|
u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
|
|
1736
|
-
const h=e.port||await(0,
|
|
1754
|
+
const h=e.port||await(0,b.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:r,remoteServerUrl:i,port:h,authToken:u,tokenManager:c})
|
|
1737
1755
|
try{await g.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${g.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
|
|
1738
1756
|
let e=!1
|
|
1739
1757
|
const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
|
|
1740
|
-
process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,
|
|
1758
|
+
process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:C})
|
|
1741
1759
|
break
|
|
1742
1760
|
case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),r=await a(t),s=new m.TokenManager(t,e,{remoteServerUrl:r,allowInteractiveAuth:!1})
|
|
1743
1761
|
await s.initialize(),await s.hasValidToken()?await s.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
|