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 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={45(module){"use strict"
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=S.shift()
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>b&&(r.busy=!1,r.busySince=void 0,t=!0)
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=I){return await o(e,t)||new Promise(((i,a)=>{let c=!1
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=S.indexOf(u)
46
- 0>e||S.splice(e,1),a(Error(`All ${t} pool slot(s) busy after waiting ${Math.round((Date.now()-l)/1e3)}s`))}),r)
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)):(S.unshift(u),s())}
51
- S.push(u)}))},exports.releaseSlot=n,exports.scheduleSlotRelease=i,exports.watchPidAndRelease=function(e,t,r,s=5e3){if(i(t,r),!e||0>=e)return
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(),S.length=0}
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"]),b=12e5,I=6e5,E=new Map,S=[]},833(e,exports,t){"use strict"
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}),b=this.resolveModel(t,n),I=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,b,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},b={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}}}
245
- try{await(0,s.executeSameSession)(v,m,w,b),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()
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),b=t(5419),I=t(3962),E=t(2928),S=t(6563),C=t(1401),k=t(1292),T=t(8148),_=t(8463),j=t(489),A=t(3807),P=t(7478),M=t(5317),O=t(355),R=t(833)
302
- class D{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
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,O.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
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,P.generateRequestBranchName)(r,""):void 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,A.simpleGit)(this.options.projectPath).revparse([s])).trim()
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,j.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:[]}
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,M.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath,env:{...process.env,...(0,b.getOpencodeSpawnEnv)(this.options.projectPath,(0,I.isDevMode)())}})
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 handleSpawnSession(e,t){try{const{agent:r,prompt:s,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d}=e.body
429
- if(!r||!s)return void t.status(400).json({success:!1,error:"agent and prompt are required"})
430
- if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
431
- const 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})
432
- t.json({success:!0,data:g})}catch(e){this.logger.error("Spawn session failed",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleRunTestsNow(e,r){try{const{requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i}=e.body||{}
433
- if(!this.projectId)return void r.status(400).json({success:!1,error:"No project configured"})
434
- if(!s)return void r.status(400).json({success:!1,error:"requestId is required"})
435
- const{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)
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.error("Run tests now failed",{error:(0,f.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleSetupTests(e,t){try{const{requestId:r,parentSessionId:s,branchName:o}=e.body||{}
438
- if(!this.projectId)return void t.status(400).json({success:!1,error:"No project configured"})
439
- const n=this.projectId,i=this.options.projectPath
440
- await(0,_.sweepStaleSetupWorktrees)(i,this.logger)
441
- const a=await(0,_.resolveSetupBranch)(this.apiClient,n,o,r,this.logger,i),c=(0,_.generateSetupId)(),l=d.default.join((0,_.getSetupWorktreesBase)(i),c)
442
- try{await(0,_.createSetupWorktree)(i,l,a,this.logger)}catch(e){const r=(0,f.getErrorMessage)(e)
443
- return this.logger.error("Failed to create setup worktree",{branch:a,error:r}),void t.status(500).json({success:!1,error:"Failed to create setup worktree: "+r})}const u=(0,_.buildSetupPrompt)(i,r),h=await(0,T.spawnActionAgent)({agent:"test-architect",prompt:u,cwd:l,projectPath:i,projectId:n,title:r?`[${r}] Test setup`:"Test setup",requestId:r,parentSessionId:s,allowParallel:!0,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
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<D.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
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,R.getOpenCodeUrlFromConfig)(this.options.projectPath)
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=t(45)
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{}}const r={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:h.default.hostname(),platform:process.platform,clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}
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 s=await this.apiClient.post("/api/servers/register",r)
511
- if(s.success&&s.data?.serverId){this.serverId=s.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})
512
- const e=s.data.versionWarnings
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:s.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`))
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
- try{const r={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e),cachedDefaultModel:this.cachedDefaultModel},s=await(0,g.executeAction)(e.type,e.payload||{},r)
568
- await this.apiClient.post(`/api/actions/${e.id}/result`,{success:s.success,result:s.data,error:s.message||s.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:s.success,duration:Date.now()-t})}catch(r){const s=(0,f.getErrorMessage)(r)
569
- await this.apiClient.post(`/api/actions/${e.id}/result`,{success:!1,error:s,duration:Date.now()-t}),this.logger.error("Action failed",{actionId:e.id,type:e.type,error:s})}}async handleCLIBridgeCommand(e,t){try{const{action:r,params:s,timeout:o}=e.body
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 S.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 C.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()??[]
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=D,D.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
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}}},3444(e,exports,t){"use strict"
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},4627(e,exports){"use strict"
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 b=await async function(e,t,o){const n=Date.now()+1e3*o
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),I=Date.now()-v,E=await async function(e,t){try{const r=await fetch(`${e}/session/${t}/message?limit=200`,{signal:AbortSignal.timeout(1e4)})
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),S=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(E),C=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
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:b.status,messages:E,toolCallCount:S,elapsedMs:I,finalResponse:C}}}
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 n=process.config||{},i=(n.setup,n.commands||[]),a=o&&o.length>0?o.map((e=>i.find((t=>t.name===e)))).filter((e=>e)):i,c=o&&o.length>0&&a.length<i.length
1015
- let l=`You are a test runner agent. Your job is to verify that request ${e.localId||e.id} ("${e.title}") passes all required tests.\n\n## Test Process: ${process.name}\n${process.description?`\n${process.description}\n`:""}\n${process.scope?`**Scope:** Files under \`${process.scope}\`\n`:""}\n## Project ID: ${r}\n\n## Test Commands${c?" (partial run \u2014 only the commands listed below)":""}\nExecute these in order:\n`
1016
- for(let e=0;e<a.length;e++){const t=a[e]
1017
- l+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}if(l+=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.`,n.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0){l+="\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"
1018
- for(const t of e.acceptanceCriteria)l+=`- ${t}\n`
1019
- l+='\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 l}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
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,b=g.branchName||"requests/"+d
1025
- let I
1026
- if(u?.length)I=(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
+ 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
- I=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===I.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
1029
- const E=Math.max(1,Math.min(8,I[0]?.config?.poolSize??2)),S=I[0]?.config?.fingerprint||[]
1030
- let C
1031
- try{C=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=C.path,T=(0,n.simpleGit)(v)
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",b]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:k,branchName:b,slotIndex:C.index})}catch(e){m.warn("Could not reset slot, recreating",{error:(0,i.getErrorMessage)(e)})
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(b))return(0,c.releaseSlot)(k),{success:!1,message:`Branch ${b} not found`,error:"Branch not found"}
1036
- await T.raw(["worktree","add","--detach",k,b]),m.info("Created pool slot worktree (detached)",{worktreePath:k,branchName:b,slotIndex:C.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)}
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,S),j=0===S.length||_!==C.storedFingerprint,A=0===S.length?null:_===C.storedFingerprint
1038
- j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:k,slotIndex:C.index,fingerprintFiles:S})
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 I){const t=e.config?.setup||[]
1041
- if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:C.index})
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)})}}}S.length>0&&await(0,c.writeSlotFingerprint)(k,_),await(0,c.writeWarmSnapshot)(k)}else{const{removed:e}=await(0,c.restoreToWarmSnapshot)(k)
1043
- e>0&&m.info("Pruned drift from warm slot",{worktreePath:k,slotIndex:C.index,removed:e})}let M
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 I){let t
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:I.length,worktreePath:k,spawnedSessionIds:D}}}catch(e){const t=(0,i.getErrorMessage)(e)
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=`PLAN-${p.replace(/\.md$/i,"")}.md`
1151
- let f
1152
- try{const e=await d.post(`/api/projects/${u}/documents`,{filename:m,content:" ",type:"Plan"})
1153
- f=e?.data?.id,l.info("Pre-created plan document for auto-planner",{planId:f,filename:m})}catch(e){l.warn("Failed to pre-create plan document; auto-planner will fall back to plan_write create",{error:(0,r.getErrorMessage)(e)})}const y=f?`[Session Context]\n- planId: ${f} \u2014 this is YOUR associated plan document. It already exists with a placeholder filename.\n- Use plan_write (update / append / replace) with planId "${f}" to fill it in.\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 plan is given here, not via the active-plan slot.\n\n${e}`:e,v=await(0,s.spawnActionAgent)({agent:"auto-planner",prompt:y,cwd:h,projectPath:h,projectId:u,title:t,requestId:o,parentSessionId:a,apiClient:d,logger:l,trackChildPid:g})
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(8148)},7163(e,exports){"use strict"
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"),b=o.includes("document_operations"),I=o.includes("query_history")
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}),e.requestId&&l.push({eventType:"request_completed",timing:"after",variables:{...u,requestId:e.requestId}})}else if(y){const e=t(i?.paths)||[],r=(Array.isArray(e)?e:[]).map((e=>e.path||e.documentId)).filter(Boolean)
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 b?l.push({eventType:"document_operations",timing:"after",variables:u}):I&&l.push({eventType:"query_history",timing:"after",variables:u})
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 b
1389
- try{b=(0,n.openSync)(w,"a")}catch{}const I=(0,a.resolveRunCommand)(),E=(0,s.spawn)(I.command,[...I.prependArgs,...y],{detached:!0,stdio:["ignore","ignore",b??"ignore"],cwd:u||process.cwd(),env:o})
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 b=(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)}})
1396
- return b.success?(p.info("Spawned action agent",{agent:t,sessionId:w,pid:b.pid,requestId:d}),{success:!0,sessionId:w,pid:b.pid}):{success:!1,sessionId:w,error:b.error||"Spawn failed"}}
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&&r.push("Triggered from request: "+t),r.push(""),r.push("1. Inspect manifests (package.json, pyproject.toml, Cargo.toml, go.mod, Makefile, CI configs) to identify build/test/lint commands."),r.push("2. Use `testing_reference` for per-language patterns."),r.push("3. Propose a draft to the user (name, setup commands, test commands with timeouts) as a single message."),r.push("4. Iterate until the user confirms."),r.push("5. Call `testing_process_create` with the agreed configuration."),r.push("6. Send a brief report back to your parent via `session_message_operations` (type: report) summarizing what you registered, then end your session."),r.push(""),r.push("You are read-only with respect to project files. Do not modify source code or run tests."),r.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]
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},8740(e,exports){"use strict"
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 b=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},I={tools:b},E=i?.openCodeModel||p||f||"anthropic/claude-opus-4-5"
1529
- i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:f&&(I.model=f)
1530
- const S=c.join(s,".opencode","agent")
1531
- await r.mkdir(S,{recursive:!0})
1532
- const C=E||"anthropic/claude-opus-4-5-20251101"
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,C,k),s=c.join(S,e.name+".md")
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(S,"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))):[]
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=b,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)
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,b.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!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),b=e(8824),I=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))
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 S
1673
- const C=i.indexOf("--port");-1!==C&&i[C+1]&&(S=parseInt(i[C+1],10),isNaN(S)&&(console.error("\u274c Invalid port number"),process.exit(1)))
1674
- const k=i.filter(((e,t)=>"--port"!==e&&(-1===C||t!==C+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 S=(0,h.join)(l,"server.log"),{createLogger:C}=await Promise.resolve().then(e.t.bind(e,2782,23)),k=C({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:S,baseDir:i}),T=await a(k),_=new m.TokenManager(k,i,{remoteServerUrl:T,allowInteractiveAuth:!1})
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,b.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)
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,I.findAvailablePort)(3456),D=new y.AlchemyActionServer(k,{projectPath:i,remoteServerUrl:T,port:R,authToken:A,tokenManager:_})
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,b.getErrorMessage)(e)}),{success:!1,error:(0,b.getErrorMessage)(e)})))])
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,b.getErrorMessage)(e)})}finally{y=!1}}),3e5),await m.start(g)}catch(e){k.error("Failed to start server",{error:(0,b.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,b.getErrorMessage)(e)),process.exit(1)}}({port:S})
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,I.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:r,remoteServerUrl:i,port:h,authToken:u,tokenManager:c})
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,b.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,b.getErrorMessage)(e)),process.exit(1)}}({port:S})
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.")}()