alchemylab 2.1.1 → 2.2.0

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
@@ -428,7 +428,7 @@ return M.set(r,i),i}catch{return!1}}function c(e){const t=String(e??"").trim()
428
428
  return/^exp_/.test(t)?null:/^env_/.test(t)?`${t} is an environment, not a held box: box takes the handle experiment_start returns (exp_...). Start one with experiment_start({ environmentId: "${t}" }) and pass its handle.`:`${t||"(empty)"} is not a box handle: box takes the handle experiment_start returns (exp_...). Start a box with experiment_start and pass its handle.`}var l=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
429
429
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.startExperiment=async function(e,t){let o=t.environmentId
430
430
  if(!o)try{const t=await e.apiClient.get(`/api/projects/${e.projectId}/environments/primary`)
431
- o=(t?.data||t)?.id}catch{}const i=[e.projectId,t.sessionId,o??"",n(t.liveWorktreePath)??"",(0,u.boxSize)(t.size)??""].join("\0"),a=T.get(i)
431
+ o=(t?.data||t)?.id}catch{}const i=[e.projectId,t.sessionId,o??"",n(t.liveWorktreePath)??"",(0,u.boxSize)(t.size)??""].join("\0"),a=P.get(i)
432
432
  if(a)return{...await a,reused:!0}
433
433
  const c=async function(e,t){const o=(0,k.experimentRegistry)(),i=void 0,a=t.environmentId
434
434
  let c
@@ -443,32 +443,32 @@ const C=e=>(o.setOpened(e.handle,e,n(t.liveWorktreePath),(0,u.boxSize)(t.size)),
443
443
  if(A&&e.logger?.info?.("experiment: evicted an idle box to make room at the cap",{projectId:e.projectId,evicted:A}),"live"===m.worktreeTarget||"pure-host"===m.confinement){const s=["policy","agent","human"]
444
444
  let n,a=!1
445
445
  if(t.liveWorktreePath&&f.default.existsSync(t.liveWorktreePath)&&!(0,h.isProjectFolder)(e.projectPath,t.liveWorktreePath))n=t.liveWorktreePath
446
- else{await(0,P.sweepStaleSetupWorktrees)(e.projectPath,e.logger)
447
- const r=await(0,P.resolveSetupBranch)(e.apiClient,e.projectId,t.branchName,t.requestId,e.logger,e.projectPath)
448
- n=p.default.join((0,P.getSetupWorktreesBase)(e.projectPath),`exp-${(0,P.generateSetupId)()}`),await(0,P.createSetupWorktree)(e.projectPath,n,r,e.logger),a=!0}const l=Array.isArray(m.commandGate)?m.commandGate.filter(e=>s.includes(e)):["policy","human"]
446
+ else{await(0,T.sweepStaleSetupWorktrees)(e.projectPath,e.logger)
447
+ const r=await(0,T.resolveSetupBranch)(e.apiClient,e.projectId,t.branchName,t.requestId,e.logger,e.projectPath)
448
+ n=p.default.join((0,T.getSetupWorktreesBase)(e.projectPath),`exp-${(0,T.generateSetupId)()}`),await(0,T.createSetupWorktree)(e.projectPath,n,r,e.logger),a=!0}const l=Array.isArray(m.commandGate)?m.commandGate.filter(e=>s.includes(e)):["policy","human"]
449
449
  return C({handle:o.register({projectId:e.projectId,sessionId:t.sessionId,environmentId:c?.id,environmentName:c?.name||"Workspace",commandGate:l,confinement:"pure-host",exp:new y.WorkspaceSession(n,m.connEnv&&"object"==typeof m.connEnv?m.connEnv:void 0),onDispose:()=>a?r(e.projectPath,n):Promise.resolve()}).handle,environmentId:c?.id,environmentName:c?.name||"Workspace",experimentName:i?.name,image:a?"host (snapshot of the default branch)":"host (live workspace)",hasServices:!1,workdir:n,hostTier:!0})}const _=[]
450
450
  Array.isArray(m.seed)?_.push(...m.seed.filter(Boolean)):"string"==typeof m.seed&&m.seed.trim()&&_.push(m.seed.trim())
451
- const x={gateImage:m.image,install:m.install,artifacts:Array.isArray(m.artifacts)?m.artifacts:void 0,connEnv:m.connEnv,shellInit:m.shellInit,seed:_.length?`set -e\n${_.join("\n")}`:void 0},T=(0,$.confinementPolicy)(m.confinement),M=(0,P.generateSetupId)()
451
+ const x={gateImage:m.image,install:m.install,artifacts:Array.isArray(m.artifacts)?m.artifacts:void 0,connEnv:m.connEnv,shellInit:m.shellInit,seed:_.length?`set -e\n${_.join("\n")}`:void 0},P=(0,$.confinementPolicy)(m.confinement),M=(0,T.generateSetupId)()
452
452
  let R,O
453
453
  const q=t.liveWorktreePath
454
- if(q&&!T.hostExec&&f.default.existsSync(q)&&!(0,h.isProjectFolder)(e.projectPath,q))R=q,O=!1
455
- else{await(0,P.sweepStaleSetupWorktrees)(e.projectPath,e.logger)
456
- const r=await(0,P.resolveSetupBranch)(e.apiClient,e.projectId,t.branchName,t.requestId,e.logger,e.projectPath)
457
- R=p.default.join((0,P.getSetupWorktreesBase)(e.projectPath),`exp-${M}`),await(0,P.createSetupWorktree)(e.projectPath,R,r,e.logger),O=!0}m.composeFile&&(x.composeFile=p.default.join(R,m.composeFile))
454
+ if(q&&!P.hostExec&&f.default.existsSync(q)&&!(0,h.isProjectFolder)(e.projectPath,q))R=q,O=!1
455
+ else{await(0,T.sweepStaleSetupWorktrees)(e.projectPath,e.logger)
456
+ const r=await(0,T.resolveSetupBranch)(e.apiClient,e.projectId,t.branchName,t.requestId,e.logger,e.projectPath)
457
+ R=p.default.join((0,T.getSetupWorktreesBase)(e.projectPath),`exp-${M}`),await(0,T.createSetupWorktree)(e.projectPath,R,r,e.logger),O=!0}m.composeFile&&(x.composeFile=p.default.join(R,m.composeFile))
458
458
  const D=m.resources&&"object"==typeof m.resources?m.resources:{},N=[],F=[]
459
459
  if(c?.id&&Array.isArray(m.persistentVolumes))for(const e of m.persistentVolumes){const t=e?.name&&String(e.name).trim(),r=e?.target&&String(e.target).trim()
460
460
  if(!t||!r)continue
461
461
  const n=`alchemy-pv-${c.id}-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,o=e.service&&String(e.service).trim()
462
- await s(n,!o),o?F.push({volume:n,target:r,service:o}):N.push({volume:n,target:r})}T.hostExec||(0,b.sweepBoxGit)(p.default.join(e.projectPath,".alchemy_lab","box-git"))
463
- const L=()=>T.hostExec?new j.HostExperiment({worktreePath:R,sandbox:T.hostExec,seed:x.seed,env:m.connEnv&&"object"==typeof m.connEnv?m.connEnv:void 0}):new v.ConfinedExperiment(R,x,{projectName:`alchemy-exp-${M}`,projectId:e.projectId,readOnlySource:!O,writableSource:!O&&!!t.requestId,boxGitBaseDir:p.default.join(e.projectPath,".alchemy_lab","box-git"),egress:T.egress,egressAllowlist:Array.isArray(m.egressAllowlist)?m.egressAllowlist:void 0,boxMounts:N.length?N:void 0,serviceMounts:F.length?F:void 0,session:{memory:D.memory,cpus:D.cpus,size:(0,u.boxSize)(t.size)??(0,u.boxSize)(D.size),pidsLimit:D.pidsLimit,scratchSizeMb:D.scratchMb,shmSizeMb:D.shmMb,addCaps:T.allowCaps&&Array.isArray(m.addCaps)?m.addCaps:void 0,sysctls:T.allowCaps&&m.sysctls&&"object"==typeof m.sysctls?m.sysctls:void 0,runAsRoot:T.allowRoot&&!0===m.runAsRoot||T.allowCaps&&Array.isArray(m.addCaps)&&m.addCaps.length>0||T.allowDockerSocket&&!0===m.dockerSocket,dockerSocket:T.allowDockerSocket&&!0===m.dockerSocket}}),U={"alchemy.purpose":"experiment","alchemy.session":t.sessionId,"alchemy.request":t.requestId,"alchemy.environment":c?.name}
462
+ await s(n,!o),o?F.push({volume:n,target:r,service:o}):N.push({volume:n,target:r})}P.hostExec||(0,b.sweepBoxGit)(p.default.join(e.projectPath,".alchemy_lab","box-git"))
463
+ const L=()=>P.hostExec?new j.HostExperiment({worktreePath:R,sandbox:P.hostExec,seed:x.seed,env:m.connEnv&&"object"==typeof m.connEnv?m.connEnv:void 0}):new v.ConfinedExperiment(R,x,{projectName:`alchemy-exp-${M}`,projectId:e.projectId,readOnlySource:!O,writableSource:!O&&!!t.requestId,boxGitBaseDir:p.default.join(e.projectPath,".alchemy_lab","box-git"),egress:P.egress,egressAllowlist:Array.isArray(m.egressAllowlist)?m.egressAllowlist:void 0,boxMounts:N.length?N:void 0,serviceMounts:F.length?F:void 0,session:{memory:D.memory,cpus:D.cpus,size:(0,u.boxSize)(t.size)??(0,u.boxSize)(D.size),pidsLimit:D.pidsLimit,scratchSizeMb:D.scratchMb,shmSizeMb:D.shmMb,addCaps:P.allowCaps&&Array.isArray(m.addCaps)?m.addCaps:void 0,sysctls:P.allowCaps&&m.sysctls&&"object"==typeof m.sysctls?m.sysctls:void 0,runAsRoot:P.allowRoot&&!0===m.runAsRoot||P.allowCaps&&Array.isArray(m.addCaps)&&m.addCaps.length>0||P.allowDockerSocket&&!0===m.dockerSocket,dockerSocket:P.allowDockerSocket&&!0===m.dockerSocket}}),U={"alchemy.purpose":"experiment","alchemy.session":t.sessionId,"alchemy.request":t.requestId,"alchemy.environment":c?.name}
464
464
  let B=L(),H=await(0,S.withContainerLabels)(U,()=>B.start())
465
465
  if(!H.ok&&g&&(x.install||x.artifacts?.length)&&(e.logger?.warn?.("experiment: the default install failed; opening the box without it",{error:H.error}),x.install=void 0,x.artifacts=void 0,B=L(),H=await(0,S.withContainerLabels)(U,()=>B.start())),!H.ok)throw O&&await r(e.projectPath,R),new Error(H.error||"experiment failed to start")
466
- const G=["policy","agent","human"],W=Array.isArray(m.commandGate)?m.commandGate.filter(e=>G.includes(e)):T.commandGate,K=o.register({projectId:e.projectId,sessionId:t.sessionId,environmentId:c?.id,environmentName:c?.name,commandGate:W,confinement:m.confinement||"confined",...!O&&t.requestId?{liveWorktree:{path:R,requestId:String(t.requestId)}}:{},exp:B,onDispose:async()=>{try{const r=B.getBlockedEgress,s="function"==typeof r?r.call(B):[],n=B.getPrepareEgress,o="function"==typeof n?n.call(B):[],i=[...s.map(e=>({host:e.host,method:e.method,ts:e.t,phase:"run",verdict:"blocked"})),...o.map(e=>({host:e.host,method:e.method,ts:e.t,phase:"prepare",verdict:e.allowed?"observed":"observed-would-block"}))]
466
+ const G=["policy","agent","human"],W=Array.isArray(m.commandGate)?m.commandGate.filter(e=>G.includes(e)):P.commandGate,K=o.register({projectId:e.projectId,sessionId:t.sessionId,environmentId:c?.id,environmentName:c?.name,commandGate:W,confinement:m.confinement||"confined",...!O&&t.requestId?{liveWorktree:{path:R,requestId:String(t.requestId)}}:{},exp:B,onDispose:async()=>{try{const r=B.getBlockedEgress,s="function"==typeof r?r.call(B):[],n=B.getPrepareEgress,o="function"==typeof n?n.call(B):[],i=[...s.map(e=>({host:e.host,method:e.method,ts:e.t,phase:"run",verdict:"blocked"})),...o.map(e=>({host:e.host,method:e.method,ts:e.t,phase:"prepare",verdict:e.allowed?"observed":"observed-would-block"}))]
467
467
  i.length&&(await e.apiClient.post(`/api/projects/${e.projectId}/egress-attempts`,{sessionId:t.sessionId,environmentName:c?.name,confinement:m.confinement||"confined",attempts:i}).catch(t=>e.logger?.warn?.("egress-attempts persist failed",{error:t?.message})),o.length&&e.logger?.info?.("prepare-phase egress observed",{environmentName:c?.name,hosts:o.map(e=>e.host)}))}catch{}O&&await r(e.projectPath,R)}})
468
- if(!T.hostExec){const e=(0,I.previewSelfBaseUrl)()
469
- e&&(0,I.registerPreviewHandle)(K.handle,`${e}/api/experiments/${K.handle}/live-frame`,c?.name||void 0,{awaitingFrame:!0})}return C({handle:K.handle,environmentId:c?.id,environmentName:c?.name,experimentName:i?.name,image:m.image||"node:20-alpine",hasServices:!!m.composeFile,workdir:T.hostExec?R:"/work",hostTier:!!T.hostExec,hasGit:!T.hostExec&&!0===B.hasGit,...T.hostExec||O||!t.requestId?{}:{sourceWritable:!0},...T.hostExec||"boolean"!=typeof B.hasGitBinary?{}:{gitBinary:B.hasGitBinary}})}(e,t)
470
- T.set(i,c)
471
- try{return await c}finally{T.get(i)===c&&T.delete(i)}},exports.runGatedExec=o,exports.boxDestination=i,exports.insertResultIntoExperiment=async function(e){const r=(0,k.experimentRegistry)().use(e.handle)
468
+ if(!P.hostExec){const e=(0,I.previewSelfBaseUrl)()
469
+ e&&(0,I.registerPreviewHandle)(K.handle,`${e}/api/experiments/${K.handle}/live-frame`,c?.name||void 0,{awaitingFrame:!0})}return C({handle:K.handle,environmentId:c?.id,environmentName:c?.name,experimentName:i?.name,image:m.image||"node:20-alpine",hasServices:!!m.composeFile,workdir:P.hostExec?R:"/work",hostTier:!!P.hostExec,hasGit:!P.hostExec&&!0===B.hasGit,...P.hostExec||O||!t.requestId?{}:{sourceWritable:!0},...P.hostExec||"boolean"!=typeof B.hasGitBinary?{}:{gitBinary:B.hasGitBinary}})}(e,t)
470
+ P.set(i,c)
471
+ try{return await c}finally{P.get(i)===c&&P.delete(i)}},exports.runGatedExec=o,exports.boxDestination=i,exports.insertResultIntoExperiment=async function(e){const r=(0,k.experimentRegistry)().use(e.handle)
472
472
  if(!r){const t=(0,k.experimentRegistry)().whyGone(e.handle)
473
473
  return{ok:!1,error:`experiment ${e.handle} is gone${t?` (${t.reason})`:""}. Reopen it with experiment_start.`}}if(!r.insertFrom)return{ok:!1,error:"this experiment runtime cannot take a stream; it is not a sealed container. Fetch to the host instead (omit `box`)."}
474
474
  const s=await Promise.resolve().then(()=>t(13080)),n=i(e.at)
@@ -498,13 +498,13 @@ if(e.apiClient&&r){const t=await(0,_.findBlastRow)(e.apiClient,r.projectId,e.com
498
498
  if(t){const r=(0,_.assessBlastRun)(t,e.selfProjectPath||"")
499
499
  if("refuse-self"===r.verdict)return{found:!0,exit:126,stdout:"",stderr:`command blocked by staging guard: ${r.reason}`,ms:0,timedOut:!1,gate:"blocked"}
500
500
  n=!0}}return(0,k.experimentRegistry)().markWork(e.handle,1),{found:!0,...await o({exp:t,command:e.command,projectPath:e.selfProjectPath,timeoutMs:e.timeoutMs,saved:e.saved??!1,requireApproval:n,approvalKey:e.handle,gates:r?.commandGate??[],sessionId:r?.sessionId,environmentName:r?.environmentName,source:"experiment",registeredAllows:s,why:e.why,sandboxed:"pure-host"!==r?.confinement&&"host"!==r?.confinement,onHumanApproved:e.onHumanApproved?t=>e.onHumanApproved(t,r?.environmentId):void 0,onJudgeApproved:e.onJudgeApproved?t=>e.onJudgeApproved(t,r?.environmentId):void 0,agentJudge:e.judgeDeps?t=>(0,x.judgeCommand)(e.judgeDeps,{command:t,environmentName:r?.environmentName,confinement:r?.confinement}):void 0,onApprovalNeeded:e.onApprovalNeeded,neverHoldForHuman:await a(e.apiClient,r?.projectId,r?.sessionId)}).finally(()=>(0,k.experimentRegistry)().markWork(e.handle,-1))}},exports.stopExperiment=async function(e){return(0,k.experimentRegistry)().remove(e)},exports.listExperiments=function(e){return(0,k.experimentRegistry)().list(e)},exports.resetPersistentVolume=async function(e,t){const r=`alchemy-pv-${e}-${String(t).replace(/[^a-zA-Z0-9_.-]/g,"-")}`,s=await(0,S.docker)(["volume","rm","-f",r],3e4)
501
- return 0!==s.exit?{ok:!1,error:(s.stderr||`exit ${s.exit}`).trim()}:{ok:!0}},exports.sweepOrphanExperiments=async function(e,t,r){if(r)try{const e=(0,P.getSetupWorktreesBase)(r)
501
+ return 0!==s.exit?{ok:!1,error:(s.stderr||`exit ${s.exit}`).trim()}:{ok:!0}},exports.sweepOrphanExperiments=async function(e,t,r){if(r)try{const e=(0,T.getSetupWorktreesBase)(r)
502
502
  if(f.default.existsSync(e)){const t=(0,w.default)(r)
503
503
  for(const r of f.default.readdirSync(e).filter(e=>e.startsWith("exp-"))){const s=p.default.join(e,r)
504
504
  try{await t.raw(["worktree","remove","--force",s])}catch{}try{f.default.rmSync(s,{recursive:!0,force:!0})}catch{}}try{await t.raw(["worktree","prune"])}catch{}}}catch(e){t?.warn?.("experiment: orphan worktree sweep failed",{error:String(e)})}try{const r=(await(0,S.docker)(["ps","-aq","--filter",`label=alchemy.experiment.project=${e}`],3e4)).stdout.split("\n").map(e=>e.trim()).filter(Boolean)
505
505
  if(!r.length)return
506
506
  await(0,S.docker)(["rm","-f","-v",...r],6e4),t?.info?.("experiment: swept orphan containers",{count:r.length})}catch(e){t?.warn?.("experiment: orphan sweep failed",{error:String(e)})}}
507
- const d=t(44876),u=t(22604),h=t(51857),p=l(t(76760)),f=l(t(73024)),g=t(77598),m=t(29869),w=l(t(83807)),y=t(77873),v=t(70662),b=t(17233),k=t(5452),I=t(28295),S=t(81618),$=t(77580),j=t(1047),E=t(84352),C=t(33521),A=t(75361),_=t(49816),x=t(73070),P=t(48463),T=new Map,M=new Map},20848(e,exports,t){"use strict"
507
+ const d=t(44876),u=t(22604),h=t(51857),p=l(t(76760)),f=l(t(73024)),g=t(77598),m=t(29869),w=l(t(83807)),y=t(77873),v=t(70662),b=t(17233),k=t(5452),I=t(28295),S=t(81618),$=t(77580),j=t(1047),E=t(84352),C=t(33521),A=t(75361),_=t(49816),x=t(73070),T=t(48463),P=new Map,M=new Map},20848(e,exports,t){"use strict"
508
508
  function r(e){return"completed"===e?.status&&("approved"===e?.gate||"session-allowed"===e?.gate)}async function s(e,t,s,n){let o=0,i=0,a=0,c=0
509
509
  try{const n=await(0,f.readAllPages)(e,(e,r)=>`/api/projects/${t}/tool-records?requestId=${encodeURIComponent(s)}&sortBy=createdAt&sortDirection=asc&limit=${r}&offset=${e}`)
510
510
  for(const e of n||[]){const t=String(e?.type??""),s=e?.metadata||{}
@@ -527,7 +527,7 @@ return a(t,e),t})
527
527
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.BOOKKEEPING_RECORD_TYPES=void 0,exports.handleFinalizeRequestAction=async function e(r,o){const{requestId:i}=r,a=!0===r.acceptEnvironmentGap,c=!0===r.acceptFailingTests,{logger:d,apiClient:f,projectPath:O,projectId:q}=o
528
528
  if(!i)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
529
529
  const D="string"==typeof r.signoffAskCode?r.signoffAskCode:null
530
- if("user"===r.signoff&&D&&"merge-queue-catchup"!==r.source){const e=await(0,M.signoffCardStale)({projectPath:O,projectId:q,requestId:i,askCode:D,defaultBranch:await(0,T.resolveDefaultBranch)(O),apiClient:{post:(e,t)=>f.post(e,t)}}).catch(e=>(d.warn("sign-off: could not tell whether its card is still current (the landing checks still run)",{requestId:i,error:(0,w.getErrorMessage)(e)}),null))
530
+ if("user"===r.signoff&&D&&"merge-queue-catchup"!==r.source){const e=await(0,M.signoffCardStale)({projectPath:O,projectId:q,requestId:i,askCode:D,defaultBranch:await(0,P.resolveDefaultBranch)(O),apiClient:{post:(e,t)=>f.post(e,t)}}).catch(e=>(d.warn("sign-off: could not tell whether its card is still current (the landing checks still run)",{requestId:i,error:(0,w.getErrorMessage)(e)}),null))
531
531
  if(e)return d.info("sign-off did not land: its card is out of date",{requestId:i,askCode:D,why:e}),{success:!1,refused:!0,message:`Request ${i} was not merged: ${e}. A new card shows what would merge now.`,error:e,data:{staleCard:e}}}d.info("Starting finalize-request action",{requestId:i})
532
532
  const N=await(0,k.awaitPendingProof)(i)
533
533
  if(N&&N.blocking.length>0){const e=(0,k.proofFailureReason)(N)
@@ -536,7 +536,7 @@ try{const k=await f.post(`/api/projects/${q}/requests/${i}/validate-completion`,
536
536
  if(!k.success||!k.data)return{success:!1,message:`Server validation failed: ${k.data?.reason||"Unknown error"}`,error:"Validation failed"}
537
537
  const j=k.data
538
538
  if(!j.canComplete){let e=`Cannot finalize request: ${j.reason}`
539
- return j.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",j.blockedByTasks.forEach((t,r)=>{e+=`${r+1}. ${t.taskId}: ${t.title} (${t.status})\n`})),{success:!1,refused:!0,message:e,error:j.reason}}const M=await f.get(`/api/projects/${q}/requests/${i}`),D=M?.data,N=`requests/${i}`,U=(0,m.simpleGit)(O),B=(await U.status(),await U.branch()),H=await(0,T.resolveDefaultBranch)(O),G=async()=>{try{if(B.all.includes(N)){if("0"!==(await U.raw(["rev-list","--count",`${H}..${N}`])).trim())return null
539
+ return j.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",j.blockedByTasks.forEach((t,r)=>{e+=`${r+1}. ${t.taskId}: ${t.title} (${t.status})\n`})),{success:!1,refused:!0,message:e,error:j.reason}}const M=await f.get(`/api/projects/${q}/requests/${i}`),D=M?.data,N=`requests/${i}`,U=(0,m.simpleGit)(O),B=(await U.status(),await U.branch()),H=await(0,P.resolveDefaultBranch)(O),G=async()=>{try{if(B.all.includes(N)){if("0"!==(await U.raw(["rev-list","--count",`${H}..${N}`])).trim())return null
540
540
  const e=(await U.raw(["rev-parse",N])).trim()
541
541
  return e===(await U.raw(["rev-parse",H])).trim()?"unknown":e}return(await U.raw(["log",H,"--merges","--format=%H","--grep",`Merge ${N}`,"-1"])).trim()||null}catch{return null}},W=await G()
542
542
  if(W){const e="unknown"===W
@@ -561,7 +561,7 @@ try{await f.post(`/api/projects/${q}/requests/${i}/finalize-completion`,{summary
561
561
  try{await f.post(`/api/projects/${q}/requests/${i}/finalize-completion`,{summary:r?`${r}\n\n${t}`:t,acceptanceEvidence:[{evidence:`The branch requests/${i} has the same content as ${H}: its change is already there.`}],mergeResult:{mergeCommitHash:"unknown",cleanedUp:!1,method:"already-in-main",diffStats:{additions:0,deletions:0,filesChanged:0}}})}catch(e){return{success:!1,message:`Finishing ${i} as already in ${H} failed: ${(0,w.getErrorMessage)(e)}`,error:(0,w.getErrorMessage)(e)}}return d.info("Branch content-identical to the default branch -- finished as already in main",{requestId:i,aheadCount:e}),{success:!0,message:`${i} is finished: ${t}`,data:{alreadyInMain:!0}}}return{success:!1,message:t,error:"Empty branch (nothing to merge)"}}}catch(e){d.warn("Could not compute ahead count; proceeding with merge",{requestId:i,error:(0,w.getErrorMessage)(e)})}let V,Y,J
562
562
  {const e=await(0,E.runMergeQueueGate)({projectPath:O,requestId:i,branchName:N,defaultBranch:H,requestTitle:D?.title,criteria:D?.acceptanceCriteria??D?.acceptance_criteria,acceptEnvironmentGap:a,acceptFailingTests:c,testGateOff:!1===D?.gates?.test?.enabled,source:"finalize-request",logger:d,fetchRequestRuns:async()=>{const e=await f.get(`/api/projects/${q}/requests/${i}/test-runs`),t=e?.data??e??[]
563
563
  return Array.isArray(t)?t:[]},fetchActiveProcesses:async()=>{const e=await f.get(`/api/projects/${q}/test-processes/active`),t=e?.data??e??[]
564
- return(Array.isArray(t)?t:[t]).filter(Boolean)},evaluatePromises:({baseSha:e,headSha:t})=>(0,x.checkPromisesAtMerge)({repoDir:O,baseSha:e,headSha:t,mergedRequestId:i,projectId:q,http:{get:e=>f.get(e),post:(e,t)=>f.post(e,t)},runChecks:(0,P.promiseCheckRunner)({projectPath:O,apiClient:f,logger:d,getProjectId:()=>q}),logger:d,askFromSessionId:D?.cliOrigin?.sessionId}),evaluateMainChange:async({landingSha:e})=>{const{mainChangeAtLanding:r}=await Promise.resolve().then(()=>t(69689))
564
+ return(Array.isArray(t)?t:[t]).filter(Boolean)},evaluatePromises:({baseSha:e,headSha:t})=>(0,x.checkPromisesAtMerge)({repoDir:O,baseSha:e,headSha:t,mergedRequestId:i,projectId:q,http:{get:e=>f.get(e),post:(e,t)=>f.post(e,t)},runChecks:(0,T.promiseCheckRunner)({projectPath:O,apiClient:f,logger:d,getProjectId:()=>q}),logger:d,askFromSessionId:D?.cliOrigin?.sessionId}),evaluateMainChange:async({landingSha:e})=>{const{mainChangeAtLanding:r}=await Promise.resolve().then(()=>t(69689))
565
565
  return r(o,i,e)},dispatchCatchUp:async e=>{const{handleRunTestsAction:s}=await Promise.resolve().then(()=>t(76555)),n=await s({requestId:i,parentSessionId:D?.cliOrigin?.sessionId,catchUp:{...e,resume:{finalize:!0,..."user"===r.signoff?{signoff:"user"}:{}}},mode:"auto"},o)
566
566
  return{success:n.success,error:n.error||(n.success?void 0:n.message),testRunIds:n.data?.testRunIds??[],settled:n.data?.catchUp?.settled??null}}})
567
567
  if("wait-slot"===e.action){const t=e.holder
@@ -604,7 +604,7 @@ const ee=(J?await(0,v.firstParentSha)(U,J):void 0)??Y,te=await(0,v.computeDiffSt
604
604
  try{await f.post(`/api/projects/${q}/requests/${i}/finalize-completion`,{summary:`Finalized request ${i}`,acceptanceEvidence:[{evidence:"Finalized via GUI after review"},...c?[{evidence:'The user answered the test-cap card with "merge anyway": this merged with the tests failing, waived for this merge.'}]:[],...a?[{evidence:"The user accepted an environment gap: the box could not run the checks, and this merged without them."}]:[],...re?[{evidence:`Parts of this merge were combined automatically and not reviewed together: ${re}`}]:[]],mergeResult:{mergeCommitHash:J||"unknown",mergeBaseSha:Y,cleanedUp:X,method:"action-server-merge",diffStats:te}})}catch(e){const t=(0,w.getErrorMessage)(e)
605
605
  return d.error("CRITICAL: Merge succeeded but server finalization failed",{requestId:i,mergeCommitHash:J,error:t}),{success:!1,message:`Code was merged (commit: ${J?.substring(0,8)}) but server finalization failed: ${t}`,error:t,data:{mergeCommitHash:J,partialSuccess:!0}}}return(0,g.maybePublishReceiptOnMerge)(i),(0,A.clearMergeCandidate)(O,i).catch(()=>{}),{success:!0,message:`Successfully finalized request ${i}: ${D?.title||i}`,data:{mergeCommitHash:J,branchDeleted:X,...re?{autoMergeNote:re}:{}}}}catch(e){const t=(0,w.getErrorMessage)(e)
606
606
  return d.error("finalize-request action failed",{requestId:i,error:t}),{success:!1,message:`Failed to finalize request: ${t}`,error:t}}finally{if(F&&!L)try{(0,j.releaseMergeLease)(O,i)}catch{}}},exports.isChangingMcpCall=r,exports.requestWorkShape=s,exports.catchUpFailedMessage=n
607
- const l=t(63580),d=t(95040),u=t(87780),h=c(t(79896)),p=c(t(16928)),f=t(5641),g=t(19817),m=t(83807),w=t(58824),y=t(27478),v=t(92456),b=t(17039),k=t(65856),I=t(35510),S=t(82860),$=t(46898),j=t(56509),E=t(14128),C=t(13922),A=t(87192),_=t(75453),x=t(75736),P=t(63484),T=t(52232),M=t(88816),R=new Set,O=new Set(["write-file","delete-file","delete-files","move-file","move-files","replace-in-files","replace-string","replace-strings","insert-lines","replace-lines","file-recover","create-directory"])
607
+ const l=t(63580),d=t(95040),u=t(87780),h=c(t(79896)),p=c(t(16928)),f=t(5641),g=t(19817),m=t(83807),w=t(58824),y=t(27478),v=t(92456),b=t(17039),k=t(65856),I=t(35510),S=t(82860),$=t(46898),j=t(56509),E=t(14128),C=t(13922),A=t(87192),_=t(75453),x=t(75736),T=t(63484),P=t(52232),M=t(88816),R=new Set,O=new Set(["write-file","delete-file","delete-files","move-file","move-files","replace-in-files","replace-string","replace-strings","insert-lines","replace-lines","file-recover","create-directory"])
608
608
  exports.BOOKKEEPING_RECORD_TYPES=new Set(["create-request","complete-request","revert-request","request-areas","create-task","complete-task","cancel-task","update-task-status","rule-waivers","criterion-waivers","read-site","drive-site","criterion-anchor-break","criterion-anchor-run","criterion-anchors-minted","promise-audit","mcp-call","run-command","request-started","user-choices","user-words","history-fold"])},33614(e,exports,t){"use strict"
609
609
  async function r(e,t,r){const s=a.default.join(e,".alchemy_lab","project.json")
610
610
  try{const n=await i.default.readFile(s,"utf-8"),o=JSON.parse(n)
@@ -1035,24 +1035,24 @@ return s.split("\n").map(e=>e.trim()).filter(Boolean)}catch{return[]}}(t.project
1035
1035
  if(o.length>0&&y){for(const t of C)t.rulesInstructions=s(o,e,l.requestId)
1036
1036
  p.info("review-fanout: the delivery review checks the rules",{requestId:l.requestId,rules:o.map(e=>e.id),files:e.length})}else o.length>0?(A={requestId:l.requestId,parentSessionId:l.coderSessionId,customReviewer:{name:"Rules",instructions:s(o,e,l.requestId)},...l.rules?.agent?{agent:l.rules.agent}:{}},p.info("review-fanout: Rules reviewer runs",{requestId:l.requestId,rules:o.map(e=>e.id),files:e.length})):p.info("review-fanout: no rule applies to this diff; Rules reviewer not spawned",{requestId:l.requestId,rules:w.length,files:e.length})}const _=[...C,...m.map(e=>({requestId:l.requestId,parentSessionId:l.coderSessionId,customReviewer:e,...E(e.name)?{agent:E(e.name)}:{}})),...A?[A]:[]]
1037
1037
  if(0===_.length)return a(t,l,"no-rule",j,`${l.requestId}: no rule applies to this diff and no other reviewer is assigned`,`No reviewer applies to ${l.requestId}: the only reviewer assigned is Rules and none of the workflow's rules touch this change. Re-complete the request to advance the review gate.`)
1038
- const x=await Promise.all(_.map(e=>(0,u.spawnReviewer)(e,t).catch(e=>({ok:!1,error:(0,d.getErrorMessage)(e)})))),P=x.filter(e=>!0===e.ok),T=x.length-P.length
1038
+ const x=await Promise.all(_.map(e=>(0,u.spawnReviewer)(e,t).catch(e=>({ok:!1,error:(0,d.getErrorMessage)(e)})))),T=x.filter(e=>!0===e.ok),P=x.length-T.length
1039
1039
  for(const e of x)e.ok||p.error("review-fanout: reviewer spawn FAILED -- that dimension will not run",{requestId:l.requestId,error:e.error})
1040
- if(0===P.length)return{success:!1,message:"review-fanout: no reviewers spawned",error:"spawn_failed"}
1041
- const M=P.map(e=>e.label),R=y?(0,k.reportQuietly)(P.map(e=>e.sessionId)):()=>{}
1040
+ if(0===T.length)return{success:!1,message:"review-fanout: no reviewers spawned",error:"spawn_failed"}
1041
+ const M=T.map(e=>e.label),R=y?(0,k.reportQuietly)(T.map(e=>e.sessionId)):()=>{}
1042
1042
  y||await(0,b.deliverPrompt)(t,l.coderSessionId,function(e){const t=e.length
1043
1043
  return`${t} reviewer${t>1?"s":""} (${e.join(", ")}) ${t>1?"are":"is"} reviewing your work and will report findings (must_fix / nice_to_have / out_of_scope) to you. Read each report as it arrives, but do NOT re-complete the request yet. When ALL reviewers have reported you will get one "review complete" message telling you to apply fixes and re-complete. Until then, end your turn between reports -- do not poll or sleep.`}(M),{noVerify:!0,noReply:!0})
1044
- const O=P.map(e=>e.sessionId),q=n(l.barrierMinutes),D=Date.now()+q,{decision:N,reports:F}=await(0,v.awaitFanoutBarrier)({apiClient:t.apiClient,projectId:t.projectId,coordinatorSessionId:l.coderSessionId,requestId:l.requestId,expectedSessionIds:O,deadlineMs:D,windowMs:q,pollIntervalMs:1500,holdPastDeadline:async e=>(0,S.sessionsHeldByRefusal)(e)||c(t,e),onHold:(e,t)=>p.info(e?"review-fanout: deadline passed while a usage limit or a refused turn holds reviewers; waiting for them":"review-fanout: the reviewers can move again; a fresh window to report",{requestId:l.requestId,missing:t.length})})
1044
+ const O=T.map(e=>e.sessionId),q=n(l.barrierMinutes),D=Date.now()+q,{decision:N,reports:F}=await(0,v.awaitFanoutBarrier)({apiClient:t.apiClient,projectId:t.projectId,coordinatorSessionId:l.coderSessionId,requestId:l.requestId,expectedSessionIds:O,deadlineMs:D,windowMs:q,pollIntervalMs:1500,holdPastDeadline:async e=>(0,S.sessionsHeldByRefusal)(e)||c(t,e),onHold:(e,t)=>p.info(e?"review-fanout: deadline passed while a usage limit or a refused turn holds reviewers; waiting for them":"review-fanout: the reviewers can move again; a fresh window to report",{requestId:l.requestId,missing:t.length})})
1045
1045
  R()
1046
- const L=P.filter(e=>N.missingSessionIds.includes(e.sessionId)).map(e=>e.label)
1046
+ const L=T.filter(e=>N.missingSessionIds.includes(e.sessionId)).map(e=>e.label)
1047
1047
  p.info("review-fanout: barrier fired",{requestId:l.requestId,reason:N.reason,reported:M.length-L.length,missing:L.length})
1048
- const U=y?(F??[]).filter(e=>P.some(t=>t.sessionId===e.fromSessionId)):[]
1049
- if(y){try{const{escalated:e,rework:r,reason:s}=i(U),n=P[0]?.sessionId
1048
+ const U=y?(F??[]).filter(e=>T.some(t=>t.sessionId===e.fromSessionId)):[]
1049
+ if(y){try{const{escalated:e,rework:r,reason:s}=i(U),n=T[0]?.sessionId
1050
1050
  0===U.length?(await t.apiClient.post(`/api/projects/${t.projectId}/requests/${l.requestId}/delivery-verdict`,{escalated:!0,reviewerSessionId:n,reason:"The delivery review did not report before its deadline, so nothing looked at this request before merge."}),p.warn("review-fanout: DELIVERY did not report; escalated rather than merged",{requestId:l.requestId})):(await t.apiClient.post(`/api/projects/${t.projectId}/requests/${l.requestId}/delivery-verdict`,{escalated:e,rework:r,reviewerSessionId:n,reason:s.slice(0,800)}),p.info("review-fanout: DELIVERY verdict applied",{requestId:l.requestId,escalated:e,rework:r}))}catch(e){p.warn("review-fanout: DELIVERY verdict failed to apply",{requestId:l.requestId,error:(0,d.getErrorMessage)(e)})}return{success:!0,message:`review-fanout: delivery verdict applied for ${l.requestId}`}}let B=!1
1051
1051
  try{const e=await t.apiClient.get(`/api/projects/${t.projectId}/requests/${l.requestId}`)
1052
1052
  B=e.success&&"ready_for_review"===e.data?.status}catch{}if(B){try{await t.apiClient.patch(`/api/projects/${t.projectId}/requests/${l.requestId}`,{reviewStatus:"completed",cliOrigin:{sessionId:l.coderSessionId},reviewRound:j?{reviewed:j}:{}}),t.logger.info("review-fanout: parked request -- barrier signaled the review finish",{requestId:l.requestId})}catch(e){t.logger.error("review-fanout: parked finish signal failed (gate stays pending -- reconciler surfaces it)",{requestId:l.requestId,error:(0,d.getErrorMessage)(e)})}let e=!0
1053
1053
  try{const r=await t.apiClient.get(`/api/projects/${t.projectId}/requests/${l.requestId}`)
1054
1054
  e=!r.success||"ready_for_review"===r.data?.status}catch{}e&&await(0,b.injectPromptToSession)(t,l.coderSessionId,(L.length>0?`The review round on ${l.requestId} closed; ${L.join(", ")} did not report before the deadline`:`All reviewers have reported on ${l.requestId}`)+". The request is held at ready_for_review, so do NOT re-complete it -- the server evaluates the review gate and the remaining gates advance on their own. Read the findings for context; a human decides at the next gate.")}else await(0,b.injectPromptToSession)(t,l.coderSessionId,o(M,L))
1055
- return{success:!0,message:`Review fan-out ${N.reason}: ${M.length-L.length}/${M.length} reviewers reported${T>0?` -- WARNING: ${T} requested reviewer${1===T?"":"s"} failed to spawn and that coverage never ran`:""}${B?" (held finish signaled)":""}`,data:{reason:N.reason,reviewers:M,missing:L,parked:B,unspawned:T,...j?{reviewed:j}:{}}}}
1055
+ return{success:!0,message:`Review fan-out ${N.reason}: ${M.length-L.length}/${M.length} reviewers reported${P>0?` -- WARNING: ${P} requested reviewer${1===P?"":"s"} failed to spawn and that coverage never ran`:""}${B?" (held finish signaled)":""}`,data:{reason:N.reason,reviewers:M,missing:L,parked:B,unspawned:P,...j?{reviewed:j}:{}}}}
1056
1056
  const d=t(58824),u=t(87775),h=t(20979),p=t(35317),f=(0,t(39023).promisify)(p.execFile),g=l(t(83807)),m=t(27478),w=t(95040),y=t(76982),v=t(27838),b=t(17039),k=t(53691),I=t(52232),S=t(82928),$=e=>Math.min(3e3,200*2**e)},76555(e,exports,t){"use strict"
1057
1057
  async function r(e,t,r){const s=(0,m.join)(e,".visual-check")
1058
1058
  let n=[]
@@ -1091,7 +1091,7 @@ try{const e=await i.get(`/api/projects/${r}/test-runs/${t}`)
1091
1091
  s=e?.data??e}catch{e=!0
1092
1092
  continue}const n=s?.status
1093
1093
  if("passed"!==n&&("skipped"!==n||!0!==s?.results?.scopeSkipped)){if("failed"===n||"error"===n||"skipped"===n)return"failed"
1094
- e=!0}}return e?"pending":"green"},p=async e=>{if("failed"===e){try{(0,x.releaseMergeLease)(t,s)}catch{}try{await(0,P.clearMergeCandidate)(t,s)}catch{}try{const e=await(0,D.resolveDefaultBranch)(t),r=await(0,T.reconcileBranchWithMain)({projectPath:t,requestId:s,branchName:n,defaultBranch:e})
1094
+ e=!0}}return e?"pending":"green"},p=async e=>{if("failed"===e){try{(0,x.releaseMergeLease)(t,s)}catch{}try{await(0,T.clearMergeCandidate)(t,s)}catch{}try{const e=await(0,D.resolveDefaultBranch)(t),r=await(0,P.reconcileBranchWithMain)({projectPath:t,requestId:s,branchName:n,defaultBranch:e})
1095
1095
  a.info("merge-queue: catch-up failed -- ticket released, branch reconciled for rework",{requestId:s,reconcile:r.outcome})}catch(e){a.warn("merge-queue: post-failure branch reconcile failed (rework proceeds without it)",{requestId:s,error:(0,v.getErrorMessage)(e)})}}else if((0,x.renewMergeLease)(t,s,{ttlMs:x.MERGE_LEASE_LAND_GRACE_TTL_MS,phase:"catchup"}),a.info("merge-queue: catch-up green -- lease held for landing",{requestId:s}),c&&l?.finalize)try{await i.post(`/api/projects/${r}/actions`,{type:"finalize-request",payload:{requestId:s,source:"merge-queue-catchup",...l.signoff?{signoff:l.signoff}:{}}}),a.info("merge-queue: catch-up green -- follow-up finalize queued automatically",{requestId:s})}catch(e){a.warn("merge-queue: failed to queue follow-up finalize; lease held with landing grace, a manual finalize will land the certified commit",{requestId:s,error:(0,v.getErrorMessage)(e)})}}
1096
1096
  if(!c){const e=await h()
1097
1097
  return"pending"!==e&&await p(e),e}for(;Date.now()<u;){await new Promise(e=>setTimeout(e,d))
@@ -1119,7 +1119,7 @@ return{key:e.key,status:"unverified",couldNotRun:!0,evidence:`verify[${e.key}] c
1119
1119
  return[`You are the ACCEPTANCE JUDGE for the experiment test "${process.name}". A drive was run against the project at the request HEAD in a confined box. Judge whether the RUN shows the acceptance criteria are satisfied; your verdict GATES this request like any other test.`,`Request id: ${e?.localId||e?.id}. Test run id: ${t.id}.`,"","Acceptance criteria:",a,"",...i?[i,""]:[],...o?["USER DECISIONS for this request (recorded through the question system -- treat these as authoritative facts when a criterion depends on information only the user has, e.g. approved content or credentials; content matching a USER ANSWER below SATISFIES such a criterion):",o,""]:[],"RUN EVIDENCE (the drive commands + output):",r,c,u,"",'On a FAILED verdict, set results.faultClass so the gate routes to the right owner: "fail_code" if the change is wrong; "fail_evidence" if a criterion could have been proven but the run did not (the coder should attach/adjust verify commands); "fail_environment" ONLY if the evidence shows a required tool/runtime is MISSING from the box (e.g. "not found") \u2014 set results.missingCapabilities to those tool names. Never call a real defect or a fixable evidence gap "fail_environment".',`Record your verdict as a TEST RUN: report_test_results { testRunId: "${t.id}", status: "passed"|"failed", ${h} }. status is "passed" only if every required criterion is satisfied${s.length?" by BOTH the evidence and the screenshots":" by the evidence"}. Do NOT send session messages: the server relays your recorded verdict to the coder (one channel, no duplicates).`].join("\n")}function h(e,process,t,r,s,n){const o=process.config||{},i=(o.setup,o.commands||[]),a=s&&s.length>0?s.map(e=>i.find(t=>t.name===e)).filter(e=>e):i,c=new Set(n||[]),l=c.size>0?a.filter(e=>!c.has(e.name)):a,d=s&&s.length>0&&l.length<i.length||c.size>0,u=c.size>0?i.filter(e=>c.has(e.name)).map(e=>e.name):[]
1120
1120
  let h=`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${u.length>0?`\n_The coder opted out of these command(s) for this completion: **${u.join(", ")}**. Do NOT run them. They've been excluded from the list below._\n`:""}\n## Test Commands${d?" (partial run \u2014 only the commands listed below)":""}\nExecute these in order:\n`
1121
1121
  for(let e=0;e<l.length;e++){const t=l[e]
1122
- h+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}return h+=`\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, per-command results, and testRunId: "${t.id}" (so the verdict lands on this exact run).\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.`,o.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0&&(h+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by running the relevant tests/commands (and reading code as needed). Criteria you confirm by execution are recorded as the test-dimension verdict.\n\nCriteria to verify (each is [key] [STATE] text \u2014 cite the key):\n",h+=(0,k.formatCriteriaWithKeys)(e.acceptanceCriteria)+"\n",h+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **key**: the criterion\'s [key] shown above\n- **status**: "verified" (proven by running it), "unverified" (not satisfied), or "unclear" (couldn\'t determine)\n- **evidence**: the command/output that proves it, or why you couldn\'t\n'),h}exports.rP=void 0,exports.runProofsAlone=c,exports.handleRunTestsAction=async function(e,s){const{requestId:f,testProcessIds:x,parentSessionId:P,catchUp:R,purpose:O}=e,{logger:q,apiClient:U,projectId:B,projectPath:H,trackChildPid:G}=s
1122
+ h+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}return h+=`\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, per-command results, and testRunId: "${t.id}" (so the verdict lands on this exact run).\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.`,o.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0&&(h+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by running the relevant tests/commands (and reading code as needed). Criteria you confirm by execution are recorded as the test-dimension verdict.\n\nCriteria to verify (each is [key] [STATE] text \u2014 cite the key):\n",h+=(0,k.formatCriteriaWithKeys)(e.acceptanceCriteria)+"\n",h+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **key**: the criterion\'s [key] shown above\n- **status**: "verified" (proven by running it), "unverified" (not satisfied), or "unclear" (couldn\'t determine)\n- **evidence**: the command/output that proves it, or why you couldn\'t\n'),h}exports.rP=void 0,exports.runProofsAlone=c,exports.handleRunTestsAction=async function(e,s){const{requestId:f,testProcessIds:x,parentSessionId:T,catchUp:R,purpose:O}=e,{logger:q,apiClient:U,projectId:B,projectPath:H,trackChildPid:G}=s
1123
1123
  if(!f)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
1124
1124
  const W=Date.now()
1125
1125
  q.info("Starting run-tests action",{requestId:f,testProcessIds:x})
@@ -1127,7 +1127,7 @@ try{const K=await U.get(`/api/projects/${B}/requests/${f}`)
1127
1127
  if(!K.success||!K.data)return{success:!1,message:`Request ${f} not found`,error:"Request not found"}
1128
1128
  const z=K.data
1129
1129
  if("main-came-in"===O){if("ready_for_review"!==String(z.status))return{success:!0,message:`${f} is ${z.status}, not waiting for sign-off; the checks after main came in are not run`}
1130
- const e=`requests/${f}`,t=await(0,T.conflictPending)(H,z,e)
1130
+ const e=`requests/${f}`,t=await(0,P.conflictPending)(H,z,e)
1131
1131
  if(t)return{success:!0,message:`not run on ${f}: ${t}`}
1132
1132
  const r=await(0,D.resolveDefaultBranch)(H),n=await(0,N.mainBroughtInSinceWork)(H,e,r).catch(()=>null)
1133
1133
  if(!n)return{success:!0,message:`nothing came into ${f} from main after its work; the checks are not run again`};(0,L.checkPromisesOnMainChange)({repoDir:H,headRef:r,projectId:B,http:{get:e=>U.get(e),post:(e,t)=>U.post(e,t)},logger:q,runChecks:(0,F.promiseCheckRunner)({projectPath:H,apiClient:U,logger:q,getProjectId:()=>B})}).catch(e=>{q.warn("promise checks after main came in failed (the run goes on)",{requestId:f,error:e instanceof Error?e.message:String(e)})})
@@ -1136,7 +1136,7 @@ if((o||i.some(e=>"changed"===e.since))&&await U.post(`/api/projects/${B}/request
1136
1136
  const a=(await(0,w.simpleGit)(H).revparse([`${n.postTip}^{tree}`]).catch(()=>"")).trim(),l=await U.get(`/api/projects/${B}/requests/${f}/test-runs`).catch(()=>null)
1137
1137
  if((Array.isArray(l?.data)?l.data:Array.isArray(l)?l:[]).some(e=>e?.commitSha===n.postTip||!!a&&e?.results?.testedTree===a))return{success:!0,message:`${f}'s checks already ran on this result; not run again`}
1138
1138
  if(!x?.length){const e=await U.get(`/api/projects/${B}/test-processes/active`).catch(()=>null),t=e?.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:null
1139
- if(t&&0===t.filter(e=>"experiment"!==e?.kind).length)return c(s,z,f,n.postTip,P)}}const{kindProfile:V}=await Promise.resolve().then(()=>t(87780)),Y=z.branchName||("request"===V(z.type).branch?`requests/${f}`:await(0,D.resolveDefaultBranch)(H))
1139
+ if(t&&0===t.filter(e=>"experiment"!==e?.kind).length)return c(s,z,f,n.postTip,T)}}const{kindProfile:V}=await Promise.resolve().then(()=>t(87780)),Y=z.branchName||("request"===V(z.type).branch?`requests/${f}`:await(0,D.resolveDefaultBranch)(H))
1140
1140
  let J,X=R?.mergeCommitSha??Y,Q=null
1141
1141
  if(!R)try{const e=(0,w.simpleGit)(H),t=await(0,D.resolveDefaultBranch)(H),r=await(0,y.prospectiveMergeCommit)(H,Y,t)
1142
1142
  if(r){const s=(await e.revparse([t]).catch(()=>"")).trim()
@@ -1157,7 +1157,7 @@ for(const o of e.filter(e=>n.has(e.id)))ee.add(o.id),r.push(o),s.splice(s.indexO
1157
1157
  0===r.length&&0===t.length&&l(z,q,f).commands.length>0&&q.warn("scope: criteria carry verify commands but no deterministic-capable process can run them; criteria stay unproven",{requestId:f})}if(s.length>0){let r
1158
1158
  try{r=R?R.mergeCommitSha:(await(0,w.simpleGit)(H).revparse([Y])).trim()}catch(e){q.warn("scope: could not resolve branch tip; skip records will carry no commitSha",{branchName:Y,error:(0,v.getErrorMessage)(e)})}for(const o of s){const s={summary:n.get(o.id)??(0,$.describeOutOfScope)(o,e,t),scopeSkipped:!0,scope:{pathGlobs:o.pathGlobs??null,areas:o.areas??null},changedFiles:(e??[]).slice(0,100),requestAreas:t,steps:[],..."main-came-in"===O?{purpose:O}:{}}
1159
1159
  try{let e=Z[o.id]?.id
1160
- if(!e){const t=await U.post(`/api/projects/${B}/test-runs`,{requestId:f,testProcessId:o.id,parentSessionId:P,...r?{commitSha:r}:{},..."main-came-in"===O?{purpose:O}:{}})
1160
+ if(!e){const t=await U.post(`/api/projects/${B}/test-runs`,{requestId:f,testProcessId:o.id,parentSessionId:T,...r?{commitSha:r}:{},..."main-came-in"===O?{purpose:O}:{}})
1161
1161
  e=(t?.data??t)?.id}e&&(await U.patch(`/api/projects/${B}/test-runs/${e}`,{status:"skipped",...r?{commitSha:r}:{},results:s}),delete Z[o.id],q.info("scope: process out of scope for this change; run skipped with recorded why",{testProcessId:o.id,processName:o.name,runId:e}))}catch(e){q.warn("scope: failed to record out-of-scope skip",{testProcessId:o.id,error:(0,v.getErrorMessage)(e)})}}}if(0===r.length)return q.info("scope: no test process in scope for this change; test gate resolves via scope-skip records",{requestId:f,processCount:J.length}),{success:!0,message:`No test process in scope for this change (${J.length} registered, 0 applicable). Each skipped run records why; the test gate resolves green off those records.`,data:{requestId:f,applicableProcessCount:0,skippedProcessIds:J.map(e=>e.id),...R?{catchUp:{mergeCommitSha:R.mergeCommitSha,settled:"green"}}:{}}}
1162
1162
  r.length<J.length&&q.info("scope: running applicable subset",{requestId:f,applicable:r.map(e=>e.id),outOfScope:s.map(e=>e.id)}),J=r}const te=Math.max(1,Math.min(8,J[0]?.config?.poolSize??2)),re=J[0]?.config?.fingerprint||[]
1163
1163
  let se
@@ -1186,7 +1186,7 @@ for(const process of J){let a
1186
1186
  const c=Z[process.id]
1187
1187
  try{if(c){await U.patch(`/api/projects/${B}/test-runs/${c.id}`,be)
1188
1188
  const e=await U.get(`/api/projects/${B}/test-runs/${c.id}`)
1189
- a=e.data,fe.push(a.id)}else{const e=await U.post(`/api/projects/${B}/test-runs`,{requestId:f,testProcessId:process.id,parentSessionId:P,...be})
1189
+ a=e.data,fe.push(a.id)}else{const e=await U.post(`/api/projects/${B}/test-runs`,{requestId:f,testProcessId:process.id,parentSessionId:T,...be})
1190
1190
  a=e.data,fe.push(a.id)}}catch(e){q.error("Failed to get/create test run record",{requestId:f,processId:process.id,preCreatedId:c?.id,error:(0,v.getErrorMessage)(e)})
1191
1191
  continue}let p,g=process.config?.environment
1192
1192
  if(process.environmentId)try{const e=await U.get(`/api/projects/${B}/environments/${process.environmentId}`),t=e?.data||e
@@ -1207,7 +1207,7 @@ r.push(`- Q: ${e.question_text}\n USER ANSWER: ${e.decision_text}${s}${t}`)}con
1207
1207
  for(const e of(s?.data||[]).filter(e=>e?.content?.requestId===f||"request"===e?.content?.anchor?.type&&e?.content?.anchor?.id===f))e?.content?.question&&r.push(`- Q: ${e.content.question}\n (still awaiting the user's answer)`)
1208
1208
  r.length&&(h=r.join("\n"))}catch(e){q.warn("failed to fetch human decisions for judge context",{requestId:f,error:(0,v.getErrorMessage)(e)})}const m=(0,I.judgeContractText)(c,(z?.acceptanceCriteria||[]).length),w=u(z,process,a,e,n,0,h,m)
1209
1209
  let y
1210
- const k=await(0,S.spawnActionAgent)({getDefaultModel:()=>s.cachedDefaultModel||void 0,agent:d,prompt:w,cwd:ne,projectPath:H,projectId:B,title:`[${f}] Experiment: ${process.name}`,requestId:f,parentSessionId:P,allowParallel:!0,apiClient:U,logger:q,trackChildPid:G,onSessionPrepared:e=>{y=e,ve.addSession(e)}})
1210
+ const k=await(0,S.spawnActionAgent)({getDefaultModel:()=>s.cachedDefaultModel||void 0,agent:d,prompt:w,cwd:ne,projectPath:H,projectId:B,title:`[${f}] Experiment: ${process.name}`,requestId:f,parentSessionId:T,allowParallel:!0,apiClient:U,logger:q,trackChildPid:G,onSessionPrepared:e=>{y=e,ve.addSession(e)}})
1211
1211
  if(!k.success)throw y&&ve.markSessionDone(y),new Error(k.error||"judge spawn failed")
1212
1212
  ge.push(a.id),k.sessionId&&we.push(k.sessionId),k.pid&&(ye=k.pid),k.sessionId&&(U.patch(`/api/projects/${B}/test-runs/${a.id}`,{testerSessionId:k.sessionId}).catch(e=>q.warn("Failed to set testerSessionId (experiment)",{error:(0,v.getErrorMessage)(e)})),U.post(`/api/projects/${B}/session-test-process-associations`,{opencodeSessionId:k.sessionId,testProcessId:process.id}).catch(e=>q.warn("tester association write failed",{testRunId:a.id,error:(0,v.getErrorMessage)(e)}))),q.info("Spawned experiment judge",{testRunId:a.id,processName:process.name,judge:d})}catch(e){q.error("Experiment run failed",{testRunId:a.id,error:(0,v.getErrorMessage)(e)}),await U.patch(`/api/projects/${B}/test-runs/${a.id}`,{status:"error",results:{commands:[],summary:`Experiment run error: ${(0,v.getErrorMessage)(e)}`,faultClass:"fail_environment"}}).catch(e=>q.warn("failed to mark run as error; the stuck-run watchdog will resolve it in <=15min",{testRunId:a.id,error:(0,v.getErrorMessage)(e)}))}continue}if(g){try{n(H,ne,q)
1213
1213
  const t=ee.has(process.id)
@@ -1223,26 +1223,26 @@ let r=null
1223
1223
  try{r=(await y()).find(e=>e?.id!==a.id&&"passed"===e?.status&&e?.testProcessId===process.id&&e?.commitSha===he&&e?.executedFingerprint===t&&(pe&&e?.results?.testedTree?e.results.testedTree===pe:!Q))??null}catch(e){q.debug("prior-run lookup failed; running the gate",{error:(0,v.getErrorMessage)(e)})}if(r){const e={...r.results||{},summary:`Reused: these commands already passed at commit ${he.slice(0,7)} (run ${r.id}); not re-run.`,reusedFromRunId:r.id,reusedScope:"all"}
1224
1224
  await U.patch(`/api/projects/${B}/test-runs/${a.id}`,{status:"passed",results:e,executedFingerprint:t}),me.push(a.id),q.info("Deterministic Tests gate reused a prior green run at the same commit",{testRunId:a.id,priorRunId:r.id,processName:process.name,commitSha:he})
1225
1225
  continue}}const I=Date.now(),S={"alchemy.purpose":"check","alchemy.request":f,"alchemy.environment":p,"alchemy.check":process.name},$=(0,A.splitGateCommands)(s),E=J.some(e=>Array.isArray(e?.config?.setup)&&e.config.setup.length>0),x=h&&$.verify.length>0&&!!he&&!E&&!e.commandNames?.length&&!e.skipCommandNames?.length
1226
- let P,T=!0,M=null
1226
+ let T,P=!0,M=null
1227
1227
  if(x){let e=null
1228
1228
  if($.process.length>0){const t=(0,A.groupFingerprint)($.process)
1229
1229
  let r=null
1230
- try{r=(0,A.findProcessEvidence)(await y(),{excludeRunId:a.id,processId:process.id,testedTree:pe,processFingerprint:t})}catch(e){q.debug("process-evidence lookup failed; running the test commands",{error:(0,v.getErrorMessage)(e)})}r?(e=(0,A.reusedProcessResult)(r,he),T=!1,q.info("Tests gate: the test commands already passed at this commit; only the criteria checks run",{testRunId:a.id,priorRunId:r.id,processName:process.name,commitSha:he})):e=await(0,_.withContainerLabels)(S,()=>(0,C.runTestsGate)(ne,{commands:$.process,environment:g}))}if(e?.error)P=e
1230
+ try{r=(0,A.findProcessEvidence)(await y(),{excludeRunId:a.id,processId:process.id,testedTree:pe,processFingerprint:t})}catch(e){q.debug("process-evidence lookup failed; running the test commands",{error:(0,v.getErrorMessage)(e)})}r?(e=(0,A.reusedProcessResult)(r,he),P=!1,q.info("Tests gate: the test commands already passed at this commit; only the criteria checks run",{testRunId:a.id,priorRunId:r.id,processName:process.name,commitSha:he})):e=await(0,_.withContainerLabels)(S,()=>(0,C.runTestsGate)(ne,{commands:$.process,environment:g}))}if(e?.error)T=e
1231
1231
  else{let t=ne
1232
1232
  try{t=await(0,A.prepareVerifyTree)(H,ne,(await(0,w.simpleGit)(ne).revparse(["HEAD"])).trim())}catch(e){q.warn("Tests gate: no clean checkout for the criteria checks; running them in the pool slot",{testRunId:a.id,error:(0,v.getErrorMessage)(e)})}M=t
1233
1233
  const r=await(0,_.withContainerLabels)({...S,"alchemy.check":`${process.name} (criteria)`},()=>(0,C.runTestsGate)(t,{commands:$.verify,environment:g}))
1234
- P=(0,A.mergeGroupResults)(e,r)}}else P=await(0,_.withContainerLabels)(S,()=>(0,C.runTestsGate)(ne,{commands:s,environment:g},{commandNames:e.commandNames,skipCommandNames:e.skipCommandNames}))
1235
- const R=T?await r(ne,H,q):{screenshots:[],found:0}
1234
+ T=(0,A.mergeGroupResults)(e,r)}}else T=await(0,_.withContainerLabels)(S,()=>(0,C.runTestsGate)(ne,{commands:s,environment:g},{commandNames:e.commandNames,skipCommandNames:e.skipCommandNames}))
1235
+ const R=P?await r(ne,H,q):{screenshots:[],found:0}
1236
1236
  if(M&&M!==ne){const e=await r(M,H,q)
1237
- R.screenshots=[...R.screenshots,...e.screenshots],R.found+=e.found}T&&o(H,ne,q)
1238
- let O={...P.results||{},processFingerprint:(0,A.groupFingerprint)($.process),...pe?{testedTree:pe}:{}}
1239
- if(t&&(O={...O||{},scopeVerifyOnly:!0,summary:["Process commands out of scope for this change; ran criterion verify commands only.",O?.summary].filter(Boolean).join(" \xb7 ")}),h){const e=d(P.results?.commands||[],{checkName:String(process.name??""),checkToolchains:(0,j.toolchainsOf)((process.config?.commands??[]).map(e=>e?.command)),uncovered:new Map(u.filter(e=>!e.covered).map(e=>[(0,k.criterionShortKey)(String(c[e.index]?.id??""),e.index),e.needs]))})
1237
+ R.screenshots=[...R.screenshots,...e.screenshots],R.found+=e.found}P&&o(H,ne,q)
1238
+ let O={...T.results||{},processFingerprint:(0,A.groupFingerprint)($.process),...pe?{testedTree:pe}:{}}
1239
+ if(t&&(O={...O||{},scopeVerifyOnly:!0,summary:["Process commands out of scope for this change; ran criterion verify commands only.",O?.summary].filter(Boolean).join(" \xb7 ")}),h){const e=d(T.results?.commands||[],{checkName:String(process.name??""),checkToolchains:(0,j.toolchainsOf)((process.config?.commands??[]).map(e=>e?.command)),uncovered:new Map(u.filter(e=>!e.covered).map(e=>[(0,k.criterionShortKey)(String(c[e.index]?.id??""),e.index),e.needs]))})
1240
1240
  e.length&&(O={...O||{},acceptanceCriteria:e})}if(R.screenshots.length)O={...O||{},screenshots:R.screenshots}
1241
1241
  else if(R.found>0){const e=`${R.found} screenshot(s) found but failed to upload`
1242
1242
  O={...O||{},summary:[O?.summary,e].filter(Boolean).join(" \xb7 ")}}const D=new Set((O?.commands||[]).map(e=>String(e?.name??""))),N=(s||[]).filter(e=>D.has(String(e?.name??""))).filter(e=>!0===e?.required||/^verify\[/.test(String(e?.name??""))).map(e=>String(e?.command??"").trim()).filter(e=>e.length>0).sort(),F=(0,b.createHash)("sha256").update(N.join("\n")).digest("hex")
1243
- await U.patch(`/api/projects/${B}/test-runs/${a.id}`,{status:P.status,results:O,executedFingerprint:F}),me.push(a.id),q.info("Deterministic Tests gate completed",{testRunId:a.id,processName:process.name,status:P.status,phaseMs:P.phaseMs,beforeGateMs:I-W,totalMs:Date.now()-W})}catch(e){q.error("Deterministic Tests gate errored",{testRunId:a.id,error:(0,v.getErrorMessage)(e)}),await U.patch(`/api/projects/${B}/test-runs/${a.id}`,{status:"error",results:{commands:[],summary:`Tests gate error: ${(0,v.getErrorMessage)(e)}`,faultClass:"fail_environment"}}).catch(()=>{}),me.push(a.id)}continue}const m=h(z,process,a,B,e.commandNames,e.skipCommandNames),y="auto-tester"
1243
+ await U.patch(`/api/projects/${B}/test-runs/${a.id}`,{status:T.status,results:O,executedFingerprint:F}),me.push(a.id),q.info("Deterministic Tests gate completed",{testRunId:a.id,processName:process.name,status:T.status,phaseMs:T.phaseMs,beforeGateMs:I-W,totalMs:Date.now()-W})}catch(e){q.error("Deterministic Tests gate errored",{testRunId:a.id,error:(0,v.getErrorMessage)(e)}),await U.patch(`/api/projects/${B}/test-runs/${a.id}`,{status:"error",results:{commands:[],summary:`Tests gate error: ${(0,v.getErrorMessage)(e)}`,faultClass:"fail_environment"}}).catch(()=>{}),me.push(a.id)}continue}const m=h(z,process,a,B,e.commandNames,e.skipCommandNames),y="auto-tester"
1244
1244
  try{let e
1245
- const t=await(0,S.spawnActionAgent)({getDefaultModel:()=>s.cachedDefaultModel||void 0,agent:y,prompt:m,cwd:ne,projectPath:H,projectId:B,title:`[${f}] Test: ${process.name}`,requestId:f,parentSessionId:P,allowParallel:!0,apiClient:U,logger:q,trackChildPid:G,onSessionPrepared:t=>{e=t,ve.addSession(t)}})
1245
+ const t=await(0,S.spawnActionAgent)({getDefaultModel:()=>s.cachedDefaultModel||void 0,agent:y,prompt:m,cwd:ne,projectPath:H,projectId:B,title:`[${f}] Test: ${process.name}`,requestId:f,parentSessionId:T,allowParallel:!0,apiClient:U,logger:q,trackChildPid:G,onSessionPrepared:t=>{e=t,ve.addSession(t)}})
1246
1246
  if(!t.success)throw e&&ve.markSessionDone(e),new Error(t.error||"Spawn failed")
1247
1247
  ge.push(a.id),t.sessionId&&we.push(t.sessionId),t.pid&&(ye=t.pid),t.sessionId&&(U.patch(`/api/projects/${B}/test-runs/${a.id}`,{testerSessionId:t.sessionId}).catch(e=>q.warn("Failed to set testerSessionId on test run",{error:(0,v.getErrorMessage)(e)})),U.post(`/api/projects/${B}/session-test-process-associations`,{opencodeSessionId:t.sessionId,testProcessId:process.id}).catch(e=>q.warn("Failed to create session\u2194test-process association",{sessionId:t.sessionId,testProcessId:process.id,error:(0,v.getErrorMessage)(e)}))),q.info("Spawned test-runner agent",{testRunId:a.id,processName:process.name,worktreePath:ne,pid:t.pid,sessionId:t.sessionId})}catch(e){q.error("Failed to spawn test-runner agent",{testRunId:a.id,error:(0,v.getErrorMessage)(e)})
1248
1248
  try{await U.patch(`/api/projects/${B}/test-runs/${a.id}`,{status:"error",results:{steps:[],summary:`Failed to spawn test agent: ${(0,v.getErrorMessage)(e)}`,faultClass:"fail_environment"}})}catch{}}}if(0===ge.length&&0===me.length)return ve.arm(),{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}
@@ -1251,7 +1251,7 @@ let ke=null
1251
1251
  if(R){const e={projectPath:H,projectId:B,requestId:f,branchName:Y,testRunIds:fe,apiClient:U,logger:q,resume:R.resume}
1252
1252
  0===ge.length?ke=await a({...e,watch:!1}):(ke="pending",a({...e,watch:!0}).catch(e=>q.warn("merge-queue: catch-up watcher crashed; lease TTL will unblock the queue",{requestId:f,error:(0,v.getErrorMessage)(e)})))}return{success:!0,message:`Started ${ge.length+me.length} test run(s) for request ${f}`,data:{testRunIds:fe,agentsSpawned:ge.length,processCount:J.length,worktreePath:ne,spawnedSessionIds:we,...R?{catchUp:{mergeCommitSha:R.mergeCommitSha,settled:ke}}:{}}}}catch(e){const t=(0,v.getErrorMessage)(e)
1253
1253
  return q.error("run-tests action failed",{requestId:f,error:t}),{success:!1,message:`Failed to run tests: ${t}`,error:t}}}
1254
- const p=t(75453),f=t(44600),g=t(79896),m=t(16928),w=t(83807),y=t(63905),v=t(58824),b=t(76982),k=t(65454),I=t(36160),S=t(28148),$=t(67842),j=t(29418),E=t(60489),C=t(41666),A=t(79075),_=t(81618),x=t(56509),P=t(87192),T=t(35510),M=t(96302),R=t(14139),O=t(40673),q=t(27584),D=t(52232),N=t(82865),F=t(63484),L=t(75736),U="alchemy:visual-check"
1254
+ const p=t(75453),f=t(44600),g=t(79896),m=t(16928),w=t(83807),y=t(63905),v=t(58824),b=t(76982),k=t(65454),I=t(36160),S=t(28148),$=t(67842),j=t(29418),E=t(60489),C=t(41666),A=t(79075),_=t(81618),x=t(56509),T=t(87192),P=t(35510),M=t(96302),R=t(14139),O=t(40673),q=t(27584),D=t(52232),N=t(82865),F=t(63484),L=t(75736),U="alchemy:visual-check"
1255
1255
  exports.rP='results: { summary, commands: [ { name: "each drive and verify[<key>] step from the evidence", status: "passed"|"failed"|"skipped"|"error", output? } ], acceptanceCriteria: [ { key: "the [key] shown next to the criterion", criterion, status: "verified"|"unverified"|"unclear", evidence } ], faultClass?: "fail_code"|"fail_evidence"|"fail_environment", missingCapabilities?: string[] }'},64306(e,exports,t){"use strict"
1256
1256
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
1257
1257
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.setupAgentInWorktree=async function(e){const{projectPath:t,projectId:r,apiClient:l,logger:d,trackChildPid:u}=e,h=`${r}:${e.agent}:${e.titleBuilder(void 0)??""}`,p=c.get(h),f=!!p&&await async function(e,t){try{const r=(0,s.getOpenCodeUrlFromConfig)(e)
@@ -1300,9 +1300,9 @@ o.push([`${e.localId} "${e.title}"`,...r.map(e=>` GATE: ${e.description}`),...s
1300
1300
  if("none"!==(0,d.kindProfile)(n).branch){const s=await e.get(`/api/projects/${t}/requests/${encodeURIComponent(r)}/workflow`)
1301
1301
  return!1===(s?.data??s)?.stamp?.test?.enabled}}const s=await e.get(`/api/projects/${t}/default-workflow`)
1302
1302
  return!1===(s?.data??s)?.gates?.test?.enabled},exports.setupRefusal=function(e){return e?e.openSetupRequest?`${e.openSetupRequest} is already setting up this project's test check, from the user's answer to the test-check card. Do not start another setup. If your request needs the check, report blocked with blockedOn "${e.openSetupRequest}": you are told when it is done.`:e.pendingCard?`The user has the project's test-check card open (${e.pendingCard}). Their answer there decides whether a check is set up, so do not start setup. Carry on with the work.`:null:null},exports.buildSetupPrompt=function(e,t,r,s,n,o,i=[],a={}){const c=r&&r.trim()?r.trim():"",l=a.existingProcesses??!!c,d=!(!s||!s.trim()),u=!(!n||!n.trim()),h=[l?"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}`]
1303
- return t&&h.push(`Triggered from request: ${t}`),c&&(h.push(""),h.push(c)),h.push(""),u?(h.push("THE CHECK TO REGISTER IS WHAT THE USER ASKED FOR, in their words:"),h.push(`"${n.trim()}"`),h.push("Register a process that runs exactly that. Do not go looking for other things to check and do not propose the project's general suite instead; if something they named does not exist, say so in your proposal."),h.push(""),h.push("1. Read what they named (the scripts, commands or files) to learn how each is invoked and what runtime it needs.")):d?(h.push("THE CHECK TO REGISTER IS WHAT THESE REQUESTS ASK FOR. Each GATE line is a promise that something runs on every merge; register a process that runs exactly that (the script the request builds lives on the request branch, so it may not be on main yet -- that is expected). Do not go looking for other things to check, and do not propose the project's general suite instead of the ask:"),h.push(s.trim()),h.push(""),h.push("1. Read the script the request names (on the request branch) to learn how it is invoked and what runtime it needs. When the ask is for the project's tests, the check runs the test suite with the project's runner (the suite the request writes may exist only on its branch), not a script of your own.")):h.push("1. Inspect manifests (package.json, pyproject.toml, Cargo.toml, go.mod, Makefile, CI configs) to identify build/test/lint commands."),o&&(h.push("THIS PROJECT'S TEST GATE IS OFF (its workflow strictness): a registered process does NOT run on merges until the user turns tests on in the workflow. Say so plainly in your question, and never promise the check will run on every merge."),h.push("")),i.length>0&&(h.push(`THE LOWEST RUNTIME THIS PROJECT DECLARES: ${i.map(e=>`${"python"===e.runtime?"Python":"Node"} ${e.version}, from ${e.source}`).join("; ")}. Run the check on an image at that version (for example ${i.map(e=>"python"===e.runtime?`python:${e.version}-slim`:`node:${e.version}-alpine`).join(", ")}). One version, not a matrix, unless the user asks for more. A check on another version is registered only when the user chose it (testing_process_create runtimeKept).`),h.push("")),h.push(a.ownSetupRequest?'IF WHAT YOU MUST REGISTER IS NOT WRITTEN YET (an open request is still writing it on its branch): report status "blocked" with blockedOn naming that request, then end. Your setup waits for it to merge and starts again then. Do not ask the user whether to wait.':"IF WHAT YOU MUST REGISTER IS NOT WRITTEN YET on the branch you see: report that to your parent in one sentence and end. It calls setup again once it has written it. Do not ask the user whether to wait."),h.push(""),h.push("2. Your agent brief carries the design guide and per-language patterns."),h.push("3. Prove the draft before the user sees it: `environment_verify` the environment config with the commands as its smoke (joined with &&), or, when the environment already exists, run them once in a box (`experiment_start`, then `run`). Fix what fails and run again. Propose only a draft that passed, and say in the question that it ran and passed."),h.push('4. Propose the draft to the user as ONE broadcast QUESTION via `session_message_operations` (send type "question"): the question text is the ask plus the draft as a short list (name; the runtime it runs on, next to the version the project says it supports; each exact command in backticks, exactly as it will run, with its timeout); `context` says ONCE, in at most 120 words, what the user must know to decide (a surprise you found, what a choice costs, your recommendation). Never repeat in one what the other says; plain words, no em dashes. Options [{id:"register",label:"Register it"},{id:"adjust",label:"I want changes"},{id:"decline",label:"No, skip it"}], NO defaultOption, priority "normal". A check runs on every future change, so the user must actually choose to add one: silence is not consent, and "no" has to be one click rather than a paragraph the user has to type. Text typed into THIS session counts as the user too -- if they are already talking to you here, confirm here instead. Never leave the proposal as plain chat text: the user is usually not watching this session, and a question reaches them in their inbox.'),h.push('5. End your turn after asking -- the answer resumes you automatically (do not poll or wait in a loop). On "adjust" or a correction, refine and re-ask the same way. On "decline", register nothing, report that the user declined, and end your session. On "register" (including the question expiring to its default), proceed.'),h.push("6. Call `testing_process_create` with the agreed configuration."),h.push("7. Send a brief report back to your parent via `session_message_operations` (type: report) summarizing what you registered, then end your session."),h.push(""),h.push("You are read-only with respect to project files: do not modify source code, and run the project's commands only in a box to prove the draft (step 3), never in the project folder."),h.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}`]
1303
+ return t&&h.push(`Triggered from request: ${t}`),c&&(h.push(""),h.push(c)),h.push(""),u?(h.push("THE CHECK TO REGISTER IS WHAT THE USER ASKED FOR, in their words:"),h.push(`"${n.trim()}"`),h.push("Register a process that runs exactly that. Do not go looking for other things to check and do not propose the project's general suite instead; if something they named does not exist, say so in your proposal."),h.push(""),h.push("1. Read what they named (the scripts, commands or files) to learn how each is invoked and what runtime it needs.")):d?(h.push("THE CHECK TO REGISTER IS WHAT THESE REQUESTS ASK FOR. Each GATE line is a promise that something runs on every merge; register a process that runs exactly that (the script the request builds lives on the request branch, so it may not be on main yet -- that is expected). Do not go looking for other things to check, and do not propose the project's general suite instead of the ask:"),h.push(s.trim()),h.push(""),h.push("1. Read the script the request names (on the request branch) to learn how it is invoked and what runtime it needs. When the ask is for the project's tests, the check runs the test suite with the project's runner (the suite the request writes may exist only on its branch), not a script of your own.")):h.push("1. Inspect manifests (package.json, pyproject.toml, Cargo.toml, go.mod, Makefile, CI configs) to identify build/test/lint commands."),o&&(h.push("THIS PROJECT'S TEST GATE IS OFF (its workflow strictness): a registered process does NOT run on merges until the user turns tests on in the workflow. Say so plainly in your question, and never promise the check will run on every merge."),h.push("")),i.length>0&&(h.push(`THE LOWEST RUNTIME THIS PROJECT DECLARES: ${i.map(e=>`${"python"===e.runtime?"Python":"Node"} ${e.version}, from ${e.source}`).join("; ")}. Run the check on an image at that version (for example ${i.map(e=>"python"===e.runtime?`python:${e.version}-slim`:`node:${e.version}-alpine`).join(", ")}). One version, not a matrix, unless the user asks for more. A check on another version is registered only when the user chose it (testing_process_create runtimeKept).`),h.push("")),h.push(a.ownSetupRequest?'IF WHAT YOU MUST REGISTER IS NOT WRITTEN YET (an open request is still writing it on its branch): report status "blocked" with blockedOn naming that request, then end. Your setup waits for it to merge and starts again then. Do not ask the user whether to wait.':"IF WHAT YOU MUST REGISTER IS NOT WRITTEN YET on the branch you see: report that to your parent in one sentence and end. It calls setup again once it has written it. Do not ask the user whether to wait."),h.push(""),h.push("2. Your agent brief carries the design guide and per-language patterns."),h.push("3. Prove the draft before the user sees it: `environment_verify` the environment config with the commands as its smoke (joined with &&), or, when the environment already exists, run them once in a box (`experiment_start`, then `run`). Fix what fails and run again. Propose only a draft that passed, and say in the question that it ran and passed."),h.push('4. Propose the draft to the user as ONE broadcast QUESTION via `session_message_operations` (send type "question"): the question text is the ask plus the draft as a short list (name; the runtime it runs on, next to the version the project says it supports; each exact command in backticks, exactly as it will run, with its timeout); `context` says ONCE, in at most 120 words, what the user must know to decide (a surprise you found, what a choice costs, your recommendation). Never repeat in one what the other says; plain words, no em dashes. Options [{id:"register",label:"Register it"},{id:"adjust",label:"I want changes"},{id:"decline",label:"No, skip it"}], NO defaultOption, priority "normal". A check runs on every future change, so the user must actually choose to add one: silence is not consent, and "no" has to be one click rather than a paragraph the user has to type. Text typed into THIS session counts as the user too -- if they are already talking to you here, confirm here instead. Never leave the proposal as plain chat text: the user is usually not watching this session, and a question reaches them as a card in Steer.'),h.push('5. End your turn after asking -- the answer resumes you automatically (do not poll or wait in a loop). On "adjust" or a correction, refine and re-ask the same way. On "decline", register nothing, report that the user declined, and end your session. On "register" (including the question expiring to its default), proceed.'),h.push("6. Call `testing_process_create` with the agreed configuration."),h.push("7. Send a brief report back to your parent via `session_message_operations` (type: report) summarizing what you registered, then end your session."),h.push(""),h.push("You are read-only with respect to project files: do not modify source code, and run the project's commands only in a box to prove the draft (step 3), never in the project folder."),h.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}`]
1304
1304
  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 the draft to the user as ONE broadcast QUESTION via `session_message_operations` (send type "question"): summarize the proposal (name, command, cwd, timeout per item) in the question text, options [{id:"register",label:"Register these"},{id:"adjust",label:"I want changes"},{id:"decline",label:"No, skip it"}], NO defaultOption, priority "normal". Silence is not consent: an unanswered proposal registers nothing. Never leave the proposal as plain chat text -- the user is usually not watching this session. If the user is already talking to you here, confirm here instead.'),s.push('5. End your turn after asking -- the answer resumes you automatically. On "adjust", refine and re-ask; on "register" (including expiry to the default), proceed.'),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")},exports.buildEnvironmentSetupPrompt=function(e,t,r,s="interactive"){const n=["You are configuring an ENVIRONMENT for this project -- its runnable, confined runtime (image, deps install, services, connection) that tests and the coder loop run against.","",`Project path: ${e}`]
1305
- return t&&n.push(`Triggered from request: ${t}`),r&&r.trim()&&(n.push(""),n.push(`Caller hint: ${r.trim()}`)),n.push(""),n.push("1. Call environment_list to see existing environments (reuse rather than duplicate)."),n.push("2. Inspect manifests + lockfiles and any docker-compose.yml to infer the runtime + services."),"auto"===s?n.push("3. AUTONOMOUS MODE: decide the environment yourself (image, install, services, connection) from what you inferred -- do NOT ask the user or wait for confirmation. This overrides the propose-and-confirm and ask-the-user steps in your standing instructions. If the project is genuinely ambiguous (e.g. a monorepo with several competing runtimes and no caller hint), do not guess: report back what is ambiguous and end instead of saving a wrong environment."):"ask"===s?n.push("3. Ask the user ONE question first: set this up autonomously (you decide, verify, save) or walk it through with them? Follow their choice; if autonomous, do not ask anything further."):n.push("3. CONFINED environments (no host access) are cheap, reversible and safe: when your inferred setup is fully confined, CREATE it without asking and report what you built -- creating a sealed box is not a decision the user needs to make, and asking is the top interrupt-budget complaint from live drives. Pick the runtime yourself from repo evidence (manifests, lockfiles, file types); never ask the user to choose between runtimes you could infer. ASK FIRST only when the environment needs host access (pure-host/workspace), credentials, network egress beyond defaults, or paid services -- those are real decisions."),n.push("4. Verify with environment_verify (saves are verify-gated: environment_create rejects a config that has not passed environment_verify in this session)."),n.push("5. Call environment_create once it verifies."),n.push("6. Report back via session_message_operations (type: report) with the environment name + id, then end your session."),n.push(""),n.push("You are read-only with respect to project files. Do not modify source code."),n.join("\n")},exports.handleSetupTestsAction=async function(e,r){const{getActionServerBaseUrl:s}=await Promise.resolve().then(()=>t(79414))
1305
+ return t&&n.push(`Triggered from request: ${t}`),r&&r.trim()&&(n.push(""),n.push(`Caller hint: ${r.trim()}`)),n.push(""),n.push("1. Call environment_list to see existing environments (reuse rather than duplicate)."),n.push("2. Inspect manifests + lockfiles and any docker-compose.yml to infer the runtime + services."),"auto"===s?n.push("3. AUTONOMOUS MODE: decide the environment yourself (image, install, services, connection) from what you inferred -- do NOT ask the user or wait for confirmation. This overrides the propose-and-confirm and ask-the-user steps in your standing instructions. If the project is genuinely ambiguous (e.g. a monorepo with several competing runtimes and no caller hint), do not guess: report back what is ambiguous and end instead of saving a wrong environment."):"ask"===s?n.push("3. Ask the user ONE question first: set this up autonomously (you decide, verify, save) or walk it through with them? Follow their choice; if autonomous, do not ask anything further."):n.push("3. CONFINED environments (no host access) are cheap, reversible and safe: when your inferred setup is fully confined, CREATE it without asking and report what you built (this overrides the propose-and-confirm step in your standing instructions) -- creating a sealed box is not a decision the user needs to make, and asking is the top interrupt-budget complaint from live drives. Pick the runtime yourself from repo evidence (manifests, lockfiles, file types); never ask the user to choose between runtimes you could infer. ASK FIRST only when the environment needs host access (pure-host/workspace), credentials, network egress beyond defaults, or paid services -- those are real decisions."),n.push("4. Verify with environment_verify (saves are verify-gated: environment_create rejects a config that has not passed environment_verify in this session)."),n.push("5. Call environment_create once it verifies."),n.push("6. Report back via session_message_operations (type: report) with the environment name + id, then end your session."),n.push(""),n.push("You are read-only with respect to project files. Do not modify source code."),n.join("\n")},exports.handleSetupTestsAction=async function(e,r){const{getActionServerBaseUrl:s}=await Promise.resolve().then(()=>t(79414))
1306
1306
  try{const t=await fetch(`${s()}/api/setup-tests`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parentSessionId:e.parentSessionId,context:e.context??'The user answered the test-setup question with "Set up tests".',...e.requestId?{requestId:e.requestId}:{},...e.settleRequestId?{settleRequestId:e.settleRequestId}:{}}),signal:AbortSignal.timeout(2e4)}),n=await t.json().catch(()=>null)
1307
1307
  return t.ok&&n?.success?(r.logger.info("setup-tests action: setup-architect spawned",{sessionId:n.data?.sessionId}),{success:!0,message:"setup-architect session spawned (tests flavor)",data:n.data}):{success:!1,message:`setup-tests spawn failed: ${n?.error??t.status}`}}catch(e){return{success:!1,message:`setup-tests spawn failed: ${e instanceof Error?e.message:String(e)}`}}}
1308
1308
  const s=t(59783),n=t(51857),o=t(79896),i=t(16928),a=t(83807),c=t(76982),l=t(58824),d=t(87780),u=new Map},28148(e,exports,t){"use strict"
@@ -1426,8 +1426,8 @@ const n="string"==typeof s?.text?s.text.trim().replace(/\s+/g," "):""
1426
1426
  if(!n)continue
1427
1427
  const o=/\*\*must_fix\*\*([\s\S]*?)(?:\*\*nice_to_have\*\*|\*\*out_of_scope\*\*|$)/i.exec(String(e?.summary??"")),i=(o?.[1]??"").split("\n").map(e=>e.trim()).filter(e=>e.startsWith("-")&&!/^-\s*\(none\)/i.test(e)).map(e=>e.replace(/^-\s*/,"").slice(0,300)).slice(0,3)
1428
1428
  0!==i.length&&t.push(` - asked: ${i.join("; ")}\n the worker answered: ${n.slice(0,500)}`)}return 0===t.length?"":`\n- Earlier review rounds on this request, and the worker's answers:\n${t.slice(-6).join("\n")}\n Do not raise one of these again unless the answer is wrong; if you do, say why the answer does not hold.`}((Array.isArray(e?.data)?e.data:Array.isArray(e)?e:[]).slice().reverse())}catch{}try{const e=(f?function(e,t,r,s){return`[Session Context]\n- Request id: ${e}\n- Your focus: ${t}\n- Worker session to report to: ${s}\n\nYou are a custom reviewer named **${t}**. Review this request's diff STRICTLY against the instructions below \u2014 other dimensions are covered by peer reviewers in parallel, ignore them silently.\n\nYour review instructions:\n${r}\n\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to gather context for any finding you intend to flag.\n3. Categorize each finding into must_fix / nice_to_have / out_of_scope, judged ONLY against your instructions above.\n4. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and a structured markdown summary.\n5. Exit. Do not send follow-ups.`}(s,f.name,f.instructions,h):k.length>1?function(e,t,r,s){const n=t.join(" + "),o=t.filter(e=>"CRITERIA"!==e),i=`[Session Context]\n- Request id: ${e}\n- Your focus: ${n} (combined -- this is a small diff, so one reviewer covers all of it)\n- Worker session to report to: ${r}`
1429
- return t.includes("CRITERIA")?`${i}\n\nYou are the single reviewer for this small diff, covering ${n} in one pass. You verify the diff actually delivers each acceptance criterion, RECORD your per-criterion verdict, AND review the code along the other focus lens(es).\n${s}\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to confirm whether each criterion is satisfied by the change (cite file:line).\n3. For every criterion, record your verdict with \`record_criteria_verification\` { requestId: "${e}", verifications: [{ key, status, evidence }] } \u2014 record a verdict for EVERY criterion in a SINGLE call: status is "met" (evidence = file:line justification), "unmet" (evidence = what is missing and how to fix it), or "reword" when the criterion's own words no longer fit what was asked (a later answer or decision changed it): never mark such a criterion met, say in evidence what no longer fits. Use the [key] shown next to each criterion.\n4. In the SAME pass, review the diff for ${o.join(", ")} findings and categorize each into must_fix / nice_to_have / out_of_scope. Any criterion you mark "unmet" is also a must_fix.\n5. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and a structured markdown summary (a per-criterion table + a must_fix section, findings labeled by their focus).\n6. Exit. Do not send follow-ups.\n\nRules:\n- Record your verdict for EVERY criterion (met and unmet) via the tool BEFORE you send your report.\n- A criterion that just restates a registered test/check command (e.g. "npm run check passes", "the build succeeds") is the TEST gate's job, not yours: mark it "met" with evidence "covered by the test gate" \u2014 the pipeline only reaches you alongside a green run.\n- This is a SMALL diff: keep the review proportionate. Flag real issues; do not manufacture findings to fill each focus.`:`${i}\n\nYou are the single reviewer for this small diff, reviewing it along COMBINED focus lenses: **${n}**. One pass, one report.\n\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to gather context for any finding you intend to flag.\n3. Categorize each finding into must_fix / nice_to_have / out_of_scope per your agent instructions, labeling each finding with the focus it belongs to.\n4. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and the structured markdown summary.\n5. Exit. Do not send follow-ups.\n\nThis is a SMALL diff: keep the review proportionate. Flag real issues; do not manufacture findings to fill each focus.`}(s,k,h,$):function(e,t,r,s){const n=`[Session Context]\n- Request id: ${e}\n- Your focus: ${t}\n- Worker session to report to: your PARENT session (${r}). Address reports with toSessionId "parent" -- never retype the id: one wrong character sends the report nowhere and the worker waits twelve minutes (req000327, 2026-09-02)`
1430
- return"CRITERIA"===t?`${n}\n\nYou are the CRITERIA reviewer. You verify the diff actually delivers each acceptance criterion, RECORD your per-criterion verdict, then report. The other reviewers cover code quality; you cover whether the work is complete and correct against the spec.\n${s}\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to confirm whether each criterion is satisfied by the change (cite file:line).\n3. For every criterion you confirm is satisfied, record it with \`record_criteria_verification\` { requestId: "${e}", verifications: [{ key, status, evidence }] } \u2014 record a verdict for EVERY criterion in a SINGLE call: status is "met" (evidence = file:line justification), "unmet" (evidence = what is missing and how to fix it), or "reword" when the criterion's own words no longer fit what was asked (a later answer or decision changed it): never mark such a criterion met, say in evidence what no longer fits. Use the [key] shown next to each criterion.\n4. Any criterion you mark "unmet" is also a must_fix in your report. The server opens a rework task from the unmet criteria for the worker to address. For each unmet criterion with a concrete fix location, ALSO pin it there before reporting: \`document_operations\` with operation "create-artifact-annotation", ref "file:<path>", file, line, and what is missing as body.\n5. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and a structured markdown summary (a per-criterion table + a must_fix section).\n6. Exit. Do not send follow-ups.\n\nRules:\n- Record your verdict for EVERY criterion (met and unmet) via the tool BEFORE you send your report.\n- A criterion that just restates a registered test/check command (e.g. "npm run check passes", "the build succeeds") is the TEST gate's job, not yours: mark it "met" with evidence "covered by the test gate" \u2014 the pipeline only reaches you alongside a green run, and failing it for missing diff evidence sends the coder on a pointless rework loop (observed live 2026-07-09).`:"DELIVERY"===t?`${n}\n\nYou are the DELIVERY reviewer. Your peers check the code against the request; you check the REQUEST. This is autonomous work -- nothing here waits for a human and it merges when the gates pass -- so you are the only reader who asks whether what landed is the thing that was asked for.\n\nYou run LAST, after every other reviewer has reported. That is deliberate: their findings and the criteria verdicts they recorded are inputs to your judgment, and you are the only one who sees them together with what was originally asked.\n${s}\n1. The diff is above, when the brief carries it. Otherwise call \`request_diff\` (format unified) to read it. Documents and anything cut off still come from \`request_diff\`.\n2. The request is above as the record says it now. For the criteria verdicts the CRITERIA reviewer recorded, read the request with \`request_operations\` get when a verdict matters to your judgment.\n3. The messages on the worker's session for this request are above when the brief carries them: the panel's reports, the opening brief and its answers, the decisions. You are WEIGHING the panel's findings, not repeating them: a criterion marked met can still leave the ask undelivered, and naming that gap is your job. When they are not above, list them yourself: \`session_message_operations\` list with { sessionId: "${r}", messageType: "all" } (pass that sessionId explicitly -- it is the WORKER's; listing without it returns your own empty inbox).\n3b. THE WORKER'S RUNS are above when it ran any: what a command printed is where a delivered number or claim came from.\n4. For anything older than this round, \`query_history\`.\n4b. LATE STEERS ARE PART OF THE ASK. In those messages, any message from the user delivered after the work finished (they render as user messages / [direct message] injects in the worker transcript, newest last) states what "done" means NOW, even if no criterion was updated -- the request may be write-locked in gates, so the record could not follow. Verify the result satisfies every one of them: a late steer the diff does not satisfy is a must_fix (quote the steer), and one you cannot judge is a DELIVERY: ESCALATE.\n4c. CHECKS THIS REQUEST CHANGED are above when it changed any. Each judges this request's own work, so judge the change: does the check as it runs now still fail on what it failed on before? A change that lets through what the old check rejected is a must_fix naming the input that now passes, unless the old check was itself wrong and the worker showed the repair both ways (passes on correct input, still fails on the broken input it exists to catch).\n5. Judge the four ways the delivery can diverge from the ask -- see your agent instructions. Cite file:line for anything you claim about the code.\n6. Send ONE report via \`session_message_operations\` send with type "report", toSessionId "parent", finish true.\n - Fixable by the worker -> a must_fix line with the specific change. That includes a small fix that keeps to what the user said and is easy to undo (a dictated sentence that is false about the code gets corrected, and the worker says so); what it suggests beyond that is a follow-up. A must_fix is about the delivered work and the worker's account on the record; the worker's final report to the user is written after this review, so what it should say goes in nice_to_have, never a must_fix.\n - Nothing wrong -> say exactly that, in one line. Common, legitimate, costs nothing.\n - A judgment that is not yours (it changes scope or is hard to undo) -> end the report with a line reading exactly \`DELIVERY: ESCALATE\` followed by one sentence naming what the user must decide. That line is what takes the request to them; without it your concern stays in a report the coder may close. Two defensible answers where the worker delivered one and stated the other beside it are easy to undo: that merges, and the choice goes in nice_to_have for the user to flip.\n7. Exit. Do not send follow-ups.\n\nRules:\n- Do not restate another reviewer's findings as your own; they are already reported.\n- Do not manufacture a concern to look useful, and do not soften a real divergence because the tests are green. Green tests are evidence about the code, not about the request.\n- You are NOT signing this off. Sign-off is the user's and stays theirs.`:`${n}\n\nYou are reviewing this request's diff with a single focus: **${t}**. Other dimensions are being reviewed in parallel by your peers \u2014 ignore them silently.\n\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to gather context for any finding you intend to flag.\n3. Categorize each finding into must_fix / nice_to_have / out_of_scope per your agent instructions.\n4. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and the structured markdown summary.\n5. Exit. Do not send follow-ups.`}(s,k[0],h,$))+E+C+A+_,r=`Reviewer (${S}) for ${s}`,n=await(0,o.spawnActionAgent)({getDefaultModel:()=>t.cachedDefaultModel||void 0,agent:j,prompt:e,cwd:y,projectPath:y,projectId:w,title:r,requestId:s,parentSessionId:h,allowParallel:!0,apiClient:m,logger:g,trackChildPid:v})
1429
+ return t.includes("CRITERIA")?`${i}\n\nYou are the single reviewer for this small diff, covering ${n} in one pass. You verify the diff actually delivers each acceptance criterion, RECORD your per-criterion verdict, AND review the code along the other focus lens(es).\n${s}\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to confirm whether each criterion is satisfied by the change (cite file:line).\n3. For every criterion, record your verdict with \`record_criteria_verification\` { requestId: "${e}", verifications: [{ key, status, evidence }] } \u2014 record a verdict for EVERY criterion in a SINGLE call: status is "met" (evidence = file:line justification), "unmet" (evidence = what is missing and how to fix it), or "reword" when the criterion's own words no longer fit what was asked (a later answer or decision changed it): never mark such a criterion met, say in evidence what no longer fits. Use the [key] shown next to each criterion.\n4. In the SAME pass, review the diff for ${o.join(", ")} findings and categorize each into must_fix / nice_to_have / out_of_scope. Any criterion you mark "unmet" is also a must_fix.\n5. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and a structured markdown summary (a per-criterion table + a must_fix section, findings labeled by their focus).\n6. Exit. Do not send follow-ups.\n\nRules:\n- A criterion that just restates a registered test/check command (e.g. "npm run check passes", "the build succeeds") is the TEST gate's job, not yours: mark it "met" with evidence "covered by the test gate" \u2014 the pipeline only reaches you alongside a green run.\n- This is a SMALL diff: keep the review proportionate. Flag real issues; do not manufacture findings to fill each focus.`:`${i}\n\nYou are the single reviewer for this small diff, reviewing it along COMBINED focus lenses: **${n}**. One pass, one report.\n\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to gather context for any finding you intend to flag.\n3. Categorize each finding into must_fix / nice_to_have / out_of_scope per your agent instructions, labeling each finding with the focus it belongs to.\n4. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and the structured markdown summary.\n5. Exit. Do not send follow-ups.\n\nThis is a SMALL diff: keep the review proportionate. Flag real issues; do not manufacture findings to fill each focus.`}(s,k,h,$):function(e,t,r,s){const n=`[Session Context]\n- Request id: ${e}\n- Your focus: ${t}\n- Worker session to report to: your PARENT session (${r}). Address reports with toSessionId "parent" -- never retype the id: one wrong character sends the report nowhere`
1430
+ return"CRITERIA"===t?`${n}\n\nYou are the CRITERIA reviewer. You verify the diff actually delivers each acceptance criterion, RECORD your per-criterion verdict, then report. The other reviewers cover code quality; you cover whether the work is complete and correct against the spec.\n${s}\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to confirm whether each criterion is satisfied by the change (cite file:line).\n3. Record a verdict for EVERY criterion in a SINGLE \`record_criteria_verification\` call { requestId: "${e}", verifications: [{ key, status, evidence }] }: status is "met" (evidence = file:line justification), "unmet" (evidence = what is missing and how to fix it), or "reword" when the criterion's own words no longer fit what was asked (a later answer or decision changed it): never mark such a criterion met, say in evidence what no longer fits. Use the [key] shown next to each criterion.\n4. Any criterion you mark "unmet" is also a must_fix in your report. The server opens a rework task from the unmet criteria for the worker to address. For each unmet criterion with a concrete fix location, ALSO pin it there before reporting: \`document_operations\` with operation "create-artifact-annotation", ref "file:<path>", file, line, and what is missing as body.\n5. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and a structured markdown summary (a per-criterion table + a must_fix section).\n6. Exit. Do not send follow-ups.\n\nRules:\n- A criterion that just restates a registered test/check command (e.g. "npm run check passes", "the build succeeds") is the TEST gate's job, not yours: mark it "met" with evidence "covered by the test gate" \u2014 the pipeline only reaches you alongside a green run, and failing it for missing diff evidence sends the worker on a pointless rework loop.`:"DELIVERY"===t?`${n}\n\nYou are the DELIVERY reviewer. Your peers check the code against the request; you check the REQUEST. This is autonomous work -- nothing here waits for a human and it merges when the gates pass -- so you are the only reader who asks whether what landed is the thing that was asked for.\n\nYou run LAST, after every other reviewer has reported. That is deliberate: their findings and the criteria verdicts they recorded are inputs to your judgment, and you are the only one who sees them together with what was originally asked.\n${s}\n1. The diff is above, when the brief carries it. Otherwise call \`request_diff\` (format unified) to read it. Documents and anything cut off still come from \`request_diff\`.\n2. The request is above as the record says it now. For the criteria verdicts the CRITERIA reviewer recorded, read the request with \`request_operations\` get when a verdict matters to your judgment.\n3. The messages on the worker's session for this request are above when the brief carries them: the panel's reports, the opening brief and its answers, the decisions. You are WEIGHING the panel's findings, not repeating them: a criterion marked met can still leave the ask undelivered, and naming that gap is your job. When they are not above, list them yourself: \`session_message_operations\` list with { sessionId: "${r}", messageType: "all" } (pass that sessionId explicitly -- it is the WORKER's; listing without it returns your own empty inbox).\n3b. THE WORKER'S RUNS are above when it ran any: what a command printed is where a delivered number or claim came from.\n4. For anything older than this round, \`query_history\`.\n4b. LATE STEERS ARE PART OF THE ASK. In those messages, any message from the user delivered after the work finished (they render as user messages / [direct message] injects in the worker transcript, newest last) states what "done" means NOW, even if no criterion was updated -- the request may be write-locked in gates, so the record could not follow. Verify the result satisfies every one of them: a late steer the diff does not satisfy is a must_fix (quote the steer), and one you cannot judge is a DELIVERY: ESCALATE.\n4c. CHECKS THIS REQUEST CHANGED are above when it changed any. Each judges this request's own work, so judge the change: does the check as it runs now still fail on what it failed on before? A change that lets through what the old check rejected is a must_fix naming the input that now passes, unless the old check was itself wrong and the worker showed the repair both ways (passes on correct input, still fails on the broken input it exists to catch).\n5. Judge the four ways the delivery can diverge from the ask -- see your agent instructions. Cite file:line for anything you claim about the code.\n6. Send ONE report via \`session_message_operations\` send with type "report", toSessionId "parent", finish true.\n - Fixable by the worker -> a must_fix line with the specific change. That includes a small fix that keeps to what the user said and is easy to undo (a dictated sentence that is false about the code gets corrected, and the worker says so); what it suggests beyond that is a follow-up. A must_fix is about the delivered work and the worker's account on the record; the worker's final report to the user is written after this review, so what it should say goes in nice_to_have, never a must_fix.\n - Nothing wrong -> say exactly that, in one line.\n - A judgment that is not yours (it changes scope or is hard to undo) -> end the report with a line reading exactly \`DELIVERY: ESCALATE\` followed by one sentence naming what the user must decide. That line is what takes the request to them; without it your concern stays in a report the coder may close. Two defensible answers where the worker delivered one and stated the other beside it are easy to undo: that merges, and the choice goes in nice_to_have for the user to flip.\n7. Exit. Do not send follow-ups.`:`${n}\n\nYou are reviewing this request's diff with a single focus: **${t}**. Other dimensions are being reviewed in parallel by your peers \u2014 ignore them silently.\n\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to gather context for any finding you intend to flag.\n3. Categorize each finding into must_fix / nice_to_have / out_of_scope per your agent instructions.\n4. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "parent", finish true, and the structured markdown summary.\n5. Exit. Do not send follow-ups.`}(s,k[0],h,$))+E+C+A+_,r=`Reviewer (${S}) for ${s}`,n=await(0,o.spawnActionAgent)({getDefaultModel:()=>t.cachedDefaultModel||void 0,agent:j,prompt:e,cwd:y,projectPath:y,projectId:w,title:r,requestId:s,parentSessionId:h,allowParallel:!0,apiClient:m,logger:g,trackChildPid:v})
1431
1431
  return n.success&&n.sessionId?{ok:!0,sessionId:n.sessionId,label:S}:{ok:!1,error:n.error||"Spawn failed"}}catch(e){const t=(0,i.getErrorMessage)(e)
1432
1432
  return g.error("spawn-reviewer failed",{requestId:s,label:S,error:t}),{ok:!1,error:t}}}exports.Lt=exports.ud=void 0,exports.spawnReviewer=s,exports.handleSpawnReviewerAction=async function(e,t){const r=await s(e,t)
1433
1433
  return r.ok?{success:!0,message:`Reviewer spawned: ${r.label}`,data:{sessionId:r.sessionId,focus:r.label}}:{success:!1,message:`Failed to spawn reviewer: ${r.error}`,error:r.error}}
@@ -1497,16 +1497,16 @@ i||(i=!0,e.logger.info("start-worker: waiting for the agent to load in the runti
1497
1497
  try{const r=await e.apiClient.get(`/api/projects/${e.projectId}/requests/${t}/record-for-agents`)
1498
1498
  s=String((r?.data??r)?.text??"")}catch{}try{return function(e,t){const r=Array.isArray(e?.acceptanceCriteria)?e.acceptanceCriteria:[]
1499
1499
  return[`## Description\n${String(e?.description??"").trim()||"_No description provided._"}`,`## Acceptance Criteria\n${(0,S.formatCriteriaWithHashes)(r)}`,...t&&t.trim()?[t.trim()]:[]].join("\n\n")}(r,s)}catch{return""}}async function o(e,t){if(!t)return null
1500
- try{const r=(0,E.simpleGit)(e),s=await r.diffSummary([`${t}..HEAD`])
1500
+ try{const r=(0,C.simpleGit)(e),s=await r.diffSummary([`${t}..HEAD`])
1501
1501
  return{files:s.files.map(e=>({file:e.file,insertions:e.insertions??0,deletions:e.deletions??0})),insertions:s.insertions,deletions:s.deletions}}catch{return null}}async function i(e,t){if(!t)return[]
1502
- try{const r=(0,E.simpleGit)(e)
1502
+ try{const r=(0,C.simpleGit)(e)
1503
1503
  return(await r.raw(["diff","--name-only",`${t}^1`,t]).catch(async()=>r.raw(["diff","--name-only",`${t}^`,t]))).split("\n").map(e=>e.trim()).filter(Boolean)}catch{return[]}}async function a(e,t,r){const{apiClient:s,projectId:n,projectPath:a,logger:c}=e,{p:l,req:d,worker:u,startPrompt:h,toolMask:f,deps:m}=t,y=r.sessionId
1504
1504
  if(r.reopenLocalId)try{await s.patch(`/api/projects/${n}/cli-sessions/${encodeURIComponent(r.reopenLocalId)}`,{status:"active"})}catch(e){c.warn("session routing: could not reopen the cold worker",{requestId:l.requestId,sessionId:y,error:(0,I.getErrorMessage)(e)})}let S=null
1505
1505
  try{const e=await s.post(`/api/projects/${n}/requests/${l.requestId}/pickup-compose`,{sessionId:y,agentType:u,warm:!0})
1506
1506
  S=(e?.data??e)?.text??null}catch(e){c.warn("session routing: warm pickup guidance failed; sending the request alone",{requestId:l.requestId,error:(0,I.getErrorMessage)(e)})}let $=null
1507
1507
  try{const e=r.previousRequestId?await s.get(`/api/projects/${n}/requests/${r.previousRequestId}`):null,t=(e?.data??e)?.mergeCommitSha??(e?.data??e)?.merge_commit_sha??null,[c,l]=await Promise.all([o(a,t),i(a,t)])
1508
- $=(0,j.sinceYouLastLookedBlock)(c,r.previousRequestId,l)}catch{}const E=[(A=l.requestId,_=r.previousRequestId,[`Next request in your thread: ${A}. This is a NEW record: ${_?`${_} is closed and must not be reopened, re-verified or amended`:"the request you finished before is closed"}. Work on ${A}'s branch, not the old one.`,"You already have this project in context, so do not re-read the map or the files you know; open only what this request changes."].join("\n")),h,$,S].filter(Boolean).join("\n\n"),C=await(m.deliver??((t,r)=>(0,w.deliverPrompt)(e,t,r,{kind:"inserted-prompt-generic",partPayload:{requestId:l.requestId,source:l.source},...(0,g.hasMask)(f)?{toolDenies:f}:{}})))(y,E)
1509
- var A,_
1508
+ $=(0,E.sinceYouLastLookedBlock)(c,r.previousRequestId,l)}catch{}const j=[(A=l.requestId,_=r.previousRequestId,x=r.area,[`${x?"Your next request":"Next request in your thread"}: ${A}. This is a NEW record: ${_?`${_} is closed and must not be reopened, re-verified or amended`:"the request you finished before is closed"}. Work on ${A}'s branch, not the old one.`,...x?["It is separate work in the same area, not a follow-up: take nothing over from what you did before except what you know of the code."]:[],"You already have this project in context, so do not re-read the map or the files you know; open only what this request changes."].join("\n")),h,$,S].filter(Boolean).join("\n\n"),C=await(m.deliver??((t,r)=>(0,w.deliverPrompt)(e,t,r,{kind:"inserted-prompt-generic",partPayload:{requestId:l.requestId,source:l.source},...(0,g.hasMask)(f)?{toolDenies:f}:{}})))(y,j)
1509
+ var A,_,x
1510
1510
  if(!1===C?.ok)return c.warn("session routing: delivery to the warm worker failed; spawning a fresh one",{requestId:l.requestId,sessionId:y,error:C?.error}),!1
1511
1511
  try{await async function(e,t,r){const{apiClient:s,projectId:n,logger:o}=e
1512
1512
  try{const e=await s.get(`/api/projects/${n}/session-request-associations?activeOnly=true&limit=500`),i=(e?.data??e??[]).filter(e=>(e?.requestLocalId??e?.request_local_id)===t).map(e=>String(e?.opencodeSessionId??e?.opencode_session_id??"")).filter(e=>e&&e!==r)
@@ -1514,7 +1514,7 @@ if(!i.length)return
1514
1514
  await s.post(`/api/projects/${n}/session-request-associations/end-all`,{requestLocalId:t}),o.info("session routing: released prior workers before hand-off",{requestId:t,released:i.length})}catch(e){o.warn("session routing: could not release prior workers",{requestId:t,error:(0,I.getErrorMessage)(e)})}}(e,l.requestId,y),await s.post(`/api/projects/${n}/session-request-associations`,{opencodeSessionId:y,requestLocalId:l.requestId,agentType:u})
1515
1515
  const t=`[${l.requestId}] ${l.verb??"Implement"} ${String(d?.title??"").trim()}`.trim().slice(0,160)
1516
1516
  try{const e=(0,v.getOpenCodeUrlFromConfig)(a)
1517
- e&&await fetch(`${e.replace(/\/$/,"")}/session/${y}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t}),signal:AbortSignal.timeout(5e3)}),await s.post(`/api/projects/${n}/cli-sessions`,{externalSessionId:y,providerId:"opencode",title:t})}catch(e){c.warn("session routing: warm worker not retitled",{requestId:l.requestId,sessionId:y,error:(0,I.getErrorMessage)(e)})}}catch(e){c.warn("session routing: could not associate the warm worker with the request",{requestId:l.requestId,sessionId:y,error:(0,I.getErrorMessage)(e)})}try{await(0,b.ensureRequestWorktree)(a,l.requestId,c)}catch(e){c.warn("start-worker: worktree for the warm worker failed",{requestId:l.requestId,error:(0,I.getErrorMessage)(e)})}return c.info("session routing: request handed to the warm worker already on its thread",{requestId:l.requestId,sessionId:y,previous:r.previousRequestId}),"request"===(0,p.kindProfile)(d?.type).branch&&(m.prewarm??k.prewarmWorkerBox)(e,y,l.requestId),!0}async function c(e,t){const{apiClient:r,projectId:n,projectPath:o,logger:i}=e,{p:a,req:c,worker:l,startPrompt:d,toolMask:u,deps:h}=t,f="string"==typeof a.verb&&a.verb.trim()||(0,p.kindProfile)(c?.type).verb,w=(e,t)=>{const r=String(t??"").slice(0,50)
1517
+ e&&await fetch(`${e.replace(/\/$/,"")}/session/${y}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t}),signal:AbortSignal.timeout(5e3)}),await s.post(`/api/projects/${n}/cli-sessions`,{externalSessionId:y,providerId:"opencode",title:t})}catch(e){c.warn("session routing: warm worker not retitled",{requestId:l.requestId,sessionId:y,error:(0,I.getErrorMessage)(e)})}}catch(e){c.warn("session routing: could not associate the warm worker with the request",{requestId:l.requestId,sessionId:y,error:(0,I.getErrorMessage)(e)})}try{await(0,b.ensureRequestWorktree)(a,l.requestId,c)}catch(e){c.warn("start-worker: worktree for the warm worker failed",{requestId:l.requestId,error:(0,I.getErrorMessage)(e)})}return c.info(r.area?"session routing: request handed to a warm worker of its area (no thread link)":"session routing: request handed to the warm worker already on its thread",{requestId:l.requestId,sessionId:y,previous:r.previousRequestId}),"request"===(0,p.kindProfile)(d?.type).branch&&(m.prewarm??k.prewarmWorkerBox)(e,y,l.requestId),!0}async function c(e,t){const{apiClient:r,projectId:n,projectPath:o,logger:i}=e,{p:a,req:c,worker:l,startPrompt:d,toolMask:u,deps:h}=t,f="string"==typeof a.verb&&a.verb.trim()||(0,p.kindProfile)(c?.type).verb,w=(e,t)=>{const r=String(t??"").slice(0,50)
1518
1518
  return r.toLowerCase().startsWith(`${e.toLowerCase()} `)?r:`${e} ${r}`},y=function(e){if(e&&(e.tier||e.provider))return{...e.tier?{modelTier:e.tier}:{},...e.provider?{provider:e.provider}:{}}}(a.model)
1519
1519
  if(!await(h.agentLoaded??s)(e,l,39e4))return{success:!1,message:`start-worker: the agent "${l}" has not loaded in this project's runtime, so ${a.requestId} has nobody to start on. A new or changed agent loads when running work pauses; start the request again then, or restart AlchemyLab in the project folder.`,error:"agent_not_loaded"}
1520
1520
  const v=h.spawnCoder??(async()=>(0,m.spawnActionAgent)({agent:l,prompt:d,cwd:o,projectPath:o,projectId:n,title:`[${a.requestId}] ${w(f,c?.title)}`,requestId:a.requestId,parentSessionId:a.hubSessionId,apiClient:r,logger:i,trackChildPid:e.trackChildPid,...(0,g.hasMask)(u)?{toolMask:u}:{},...y?{modelOverride:y}:{}})),b=await v(d,{modelOverride:y})
@@ -1524,68 +1524,69 @@ const a=await(0,w.sessionBusy)(i.replace(/\/$/,""),t)
1524
1524
  return a?{busy:a,liveElsewhere:await d(e,t,r)}:{busy:!1,liveElsewhere:null}}async function d(e,t,r){const{apiClient:s,projectId:n}=e
1525
1525
  try{const e=await s.get(`/api/projects/${n}/session-request-associations/by-session/${encodeURIComponent(t)}`),o=Array.isArray(e?.data)?e.data:Array.isArray(e)?e:[],i=Array.from(new Set(o.map(e=>String(e?.requestLocalId??e?.request_local_id??"")).filter(e=>/^req\d+$/.test(e)&&e!==r)))
1526
1526
  for(const e of i){const t=await s.get(`/api/projects/${n}/requests/${e}`).catch(()=>null),r=String((t?.data??t)?.status??"")
1527
- if(!A.has(r))return e}return null}catch{return null}}async function u(e,t,r,s){const n={}
1528
- if(t)try{const e=await fetch(`${t.replace(/\/$/,"")}/session/${r}/message?limit=1`,{signal:AbortSignal.timeout(6e3)}),s=e.ok?await e.json():[],o=(Array.isArray(s)?s[s.length-1]:void 0)?.info?.tokens
1529
- o&&(n.contextTokens=(o.input??0)+(o.cache?.read??0)+(o.cache?.write??0))}catch{}if(s)try{const t=await e.apiClient.get(`/api/projects/${e.projectId}/requests/${s}`),r=(t?.data??t)?.mergeCommitSha??(t?.data??t)?.merge_commit_sha??null
1530
- if(r){const[t,s]=await Promise.all([o(e.projectPath,r),i(e.projectPath,r)])
1531
- if(t){const e=new Set(s)
1532
- n.changedSinceLines=t.files.filter(t=>e.has(t.file)).reduce((e,t)=>e+t.insertions+t.deletions,0)}}}catch{}return n}async function h(e,t){const{apiClient:r,projectId:s,projectPath:n,logger:o}=e
1533
- try{const i=await r.get(`/api/projects/${s}`),a=(0,$.readWarmCoderPolicy)((i?.data??i)?.metadata)
1534
- if(!a.enabled)return{why:"off"}
1535
- const c=(0,v.getOpenCodeUrlFromConfig)(n),l=new Set,h=new Map
1536
- if(c){const[e,t]=await Promise.all([fetch(`${c}/session`,{signal:AbortSignal.timeout(6e3)}).catch(()=>null),fetch(`${c}/session/status`,{signal:AbortSignal.timeout(6e3)}).catch(()=>null)])
1527
+ if(!_.has(r))return e}return null}catch{return null}}async function u(e,t){if(t)try{const r=await e.apiClient.get(`/api/projects/${e.projectId}/requests/${t}`),s=(r?.data??r)?.mergeCommitSha??(r?.data??r)?.merge_commit_sha??null
1528
+ if(!s)return
1529
+ const[n,a]=await Promise.all([o(e.projectPath,s),i(e.projectPath,s)])
1530
+ if(!n)return
1531
+ const c=new Set(a)
1532
+ return n.files.filter(e=>c.has(e.file)).reduce((e,t)=>e+t.insertions+t.deletions,0)}catch{return}}async function h(e,t){const{apiClient:r,projectId:s,projectPath:n,logger:o}=e,i=e.startWorkerDeps??{}
1533
+ try{const a=await r.get(`/api/projects/${s}`),c=(0,$.readWarmCoderPolicy)((a?.data??a)?.metadata)
1534
+ if(!c.enabled)return{why:"off"}
1535
+ const l=(0,v.getOpenCodeUrlFromConfig)(n),h=i.reloadedAt??(0,j.configReloadedAt)(n),p=i.lastCall??(e=>l?(0,j.readLastCall)(l,e):Promise.resolve({})),g=i.modelLimits??(l?await(0,j.readModelLimits)(l):new Map),m=new Set,w=new Map
1536
+ if(l){const[e,t]=await Promise.all([fetch(`${l}/session`,{signal:AbortSignal.timeout(6e3)}).catch(()=>null),fetch(`${l}/session/status`,{signal:AbortSignal.timeout(6e3)}).catch(()=>null)])
1537
1537
  if(e?.ok){const t=await e.json().catch(()=>[])
1538
- for(const e of t)h.set(e.id,Number(e.time?.updated??e.time?.created??0))}if(t?.ok){const e=await t.json().catch(()=>({}))
1539
- for(const[t,r]of Object.entries(e??{}))"busy"===r?.type&&l.add(t)}}const p=await(0,f.readAllPages)(r,e=>`/api/projects/${s}/cli-sessions?status=active&limit=100&offset=${e}`),g=new Set
1538
+ for(const e of t)w.set(e.id,Number(e.time?.updated??e.time?.created??0))}if(t?.ok){const e=await t.json().catch(()=>({}))
1539
+ for(const[t,r]of Object.entries(e??{}))"busy"===r?.type&&m.add(t)}}const y=await(0,f.readAllPages)(r,e=>`/api/projects/${s}/cli-sessions?status=active&limit=100&offset=${e}`),b=new Set
1540
1540
  try{const e=await r.get(`/api/projects/${s}/requests/${t.requestId}/arc`)
1541
- for(const t of(e?.data??e)?.requests??[])t?.localId&&g.add(String(t.localId))}catch{}if(t.planDocumentId)try{const e=await(0,f.readAllPages)(r,e=>`/api/projects/${s}/requests?planDocumentId=${encodeURIComponent(t.planDocumentId)}&limit=100&offset=${e}`)
1542
- for(const t of e)(t?.localId??t?.id)&&g.add(String(t.localId??t.id))}catch{}g.delete(t.requestId)
1543
- const m=new Map,w=new Map,y=new Map
1541
+ for(const t of(e?.data??e)?.requests??[])t?.localId&&b.add(String(t.localId))}catch{}if(t.planDocumentId)try{const e=await(0,f.readAllPages)(r,e=>`/api/projects/${s}/requests?planDocumentId=${encodeURIComponent(t.planDocumentId)}&limit=100&offset=${e}`)
1542
+ for(const t of e)(t?.localId??t?.id)&&b.add(String(t.localId??t.id))}catch{}b.delete(t.requestId)
1543
+ const k=new Map,I=new Map,S=new Map
1544
1544
  try{const e=await r.get(`/api/projects/${s}/session-request-associations?limit=500`)
1545
1545
  for(const r of e?.data??e??[]){const e=String(r?.opencodeSessionId??r?.opencode_session_id??""),s=String(r?.requestLocalId??r?.request_local_id??"")
1546
1546
  if(!e||!s||s===t.requestId)continue
1547
- g.has(s)&&m.set(e,(m.get(e)??0)+1)
1547
+ b.has(s)&&k.set(e,(k.get(e)??0)+1)
1548
1548
  const n=Date.parse(r?.startedAt??r?.started_at??"")||0
1549
- n>=(y.get(e)??-1)&&(y.set(e,n),w.set(e,s))}}catch{}const b=new Set(p.map(e=>e?.externalSessionId).filter(Boolean))
1550
- if(a.coldResume)for(const e of Array.from(m.keys()))if(!b.has(e))try{const t=await r.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),n=t?.data??t
1551
- n?.externalSessionId&&"archived"===n.status&&p.push(n)}catch{}const k=new Map,I=[]
1552
- for(const n of p){const o=n?.externalSessionId
1549
+ n>=(S.get(e)??-1)&&(S.set(e,n),I.set(e,s))}}catch{}const E=new Set(y.map(e=>e?.externalSessionId).filter(Boolean))
1550
+ if(c.coldResume)for(const e of Array.from(k.keys()))if(!E.has(e))try{const t=await r.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),n=t?.data??t
1551
+ n?.externalSessionId&&"archived"===n.status&&y.push(n)}catch{}const C=new Map,A=[]
1552
+ for(const n of y){const o=n?.externalSessionId
1553
1553
  if(!o||!(0,$.sameWorkerRole)(n.agentType,t.worker))continue
1554
- const i=/\[(req\d{6})\]/.exec(String(n.title??""))
1555
- !w.has(o)&&i&&w.set(o,i[1])
1556
- let p=""
1557
- const f=w.get(o)
1558
- if(f)try{const e=await r.get(`/api/projects/${s}/requests/${f}`)
1559
- p=String((e?.data??e)?.type??"")}catch{}const g=l.has(o);(n.localId??n.id)&&k.set(o,String(n.localId??n.id))
1560
- const y=h.get(o)??Date.parse(n.updatedAt??n.createdAt??"")??0
1561
- let v={}
1562
- a.coldResume&&(m.get(o)??0)>0&&!g&&("archived"===n.status||Date.now()-y>6e4*a.warmMinutes)&&(v=await u(e,c,o,f)),I.push({...v,externalSessionId:o,agentType:n.agentType,threadRequests:m.get(o)??0,modelKey:(0,$.modelKeyOf)(t.kindModels,p),toolsKey:(0,$.toolsKeyOf)(p),lastActiveMs:y,busy:g,busyElsewhere:g&&(m.get(o)??0)>0?!!await d(e,o,t.requestId):void 0,archived:"archived"===n.status})}const S=(0,$.pickWarmCoder)(a,Date.now(),{worker:t.worker,modelKey:(0,$.modelKeyOf)(t.kindModels,t.kind),toolsKey:(0,$.toolsKeyOf)(t.kind)},I)
1563
- if(!S.reuse){const e=I.filter(e=>(e.threadRequests??0)>0&&(void 0!==e.contextTokens||void 0!==e.changedSinceLines))
1564
- return e.length&&o.info("session routing: cold thread worker weighed and not resumed",{requestId:t.requestId,why:S.why,limits:{contextTokens:a.coldMaxContextTokens,changedLines:a.coldMaxChangedLines},weighed:e.map(e=>({sessionId:e.externalSessionId,contextTokens:e.contextTokens,changedSinceLines:e.changedSinceLines}))}),{why:S.why}}const j=I.find(e=>e.externalSessionId===S.sessionId)
1565
- return o.info(S.cold?"session routing: cold worker chosen (it built this thread and its code is still current)":"session routing: warm worker chosen",{requestId:t.requestId,sessionId:S.sessionId,threadRequestsWorked:m.get(S.sessionId)??0,afterTurn:!!S.afterTurn,candidates:I.length,...S.cold?{contextTokens:j?.contextTokens,changedSinceLines:j?.changedSinceLines,archived:!!j?.archived}:{}}),{sessionId:S.sessionId,previousRequestId:w.get(S.sessionId),...S.afterTurn?{afterTurn:!0}:{},...S.cold&&j?.archived?{reopenLocalId:k.get(S.sessionId)}:{}}}catch(e){return o.warn("session routing: warm-worker survey failed; spawning",{error:(0,I.getErrorMessage)(e)}),{why:"no-candidate"}}}exports.handleStartWorkerAction=async function(e,t){const s=e,{apiClient:o,projectId:i,logger:d}=t
1554
+ const a=/\[(req\d{6})\]/.exec(String(n.title??""))
1555
+ !I.has(o)&&a&&I.set(o,a[1])
1556
+ let l="",f=[]
1557
+ const y=I.get(o)
1558
+ if(y)try{const e=await r.get(`/api/projects/${s}/requests/${y}`)
1559
+ l=String((e?.data??e)?.type??"")
1560
+ const t=(e?.data??e)?.areas
1561
+ Array.isArray(t)&&(f=t.map(String))}catch{}const v=(0,$.sharedAreas)(t.areas,f),b=m.has(o);(n.localId??n.id)&&C.set(o,String(n.localId??n.id))
1562
+ const S=(k.get(o)??0)>0,j=c.areaReuse&&v>0,E=S||j?await p(o):{},_=Math.max(w.get(o)??0,E.atMs??0)||Date.parse(n.updatedAt??n.createdAt??"")||0,x=E.providerID&&E.modelID?g.get(`${E.providerID}/${E.modelID}`):void 0,T="archived"!==n.status&&(0,$.isCacheWarm)(Date.now(),{lastActiveMs:_,providerID:E.providerID},h),P=c.coldResume&&S&&!b&&!T?await(i.changedSince??(t=>u(e,t)))(y):void 0
1563
+ A.push({...void 0!==E.contextTokens?{contextTokens:E.contextTokens}:{},...void 0!==P?{changedSinceLines:P}:{},...S||j?{contextCeiling:(0,$.contextCeilingFor)(x)}:{},providerID:E.providerID??null,externalSessionId:o,agentType:n.agentType,threadRequests:k.get(o)??0,sharedAreas:v,modelKey:(0,$.modelKeyOf)(t.kindModels,l),toolsKey:(0,$.toolsKeyOf)(l),lastActiveMs:_,busy:b,busyElsewhere:b&&(k.get(o)??0)>0?!!await d(e,o,t.requestId):void 0,archived:"archived"===n.status})}const _=(0,$.pickWarmCoder)(c,Date.now(),{worker:t.worker,modelKey:(0,$.modelKeyOf)(t.kindModels,t.kind),toolsKey:(0,$.toolsKeyOf)(t.kind)},A,h)
1564
+ if(!_.reuse){const e=A.filter(e=>(e.threadRequests??0)>0&&(e.archived||!(0,$.isCacheWarm)(Date.now(),e,h))&&(void 0!==e.contextTokens||void 0!==e.changedSinceLines))
1565
+ return e.length&&o.info("session routing: cold thread worker weighed and not resumed",{requestId:t.requestId,why:_.why,limits:{contextTokens:c.coldMaxContextTokens,changedLines:c.coldMaxChangedLines},weighed:e.map(e=>({sessionId:e.externalSessionId,contextTokens:e.contextTokens,changedSinceLines:e.changedSinceLines}))}),{why:_.why}}const x=A.find(e=>e.externalSessionId===_.sessionId)
1566
+ return o.info(_.cold?"session routing: cold worker chosen (it built this thread and its code is still current)":_.area?"session routing: warm worker of the same area chosen (no thread link)":"session routing: warm worker chosen",{requestId:t.requestId,sessionId:_.sessionId,threadRequestsWorked:k.get(_.sessionId)??0,..._.area?{sharedAreas:x?.sharedAreas}:{},contextTokens:x?.contextTokens,contextCeiling:x?.contextCeiling,provider:x?.providerID??void 0,afterTurn:!!_.afterTurn,candidates:A.length,..._.cold?{changedSinceLines:x?.changedSinceLines,archived:!!x?.archived}:{}}),{sessionId:_.sessionId,previousRequestId:I.get(_.sessionId),..._.afterTurn?{afterTurn:!0}:{},..._.area?{area:!0}:{},..._.cold&&x?.archived?{reopenLocalId:C.get(_.sessionId)}:{}}}catch(e){return o.warn("session routing: warm-worker survey failed; spawning",{error:(0,I.getErrorMessage)(e)}),{why:"no-candidate"}}}exports.handleStartWorkerAction=async function(e,t){const s=e,{apiClient:o,projectId:i,logger:d}=t
1566
1567
  if(!s.requestId)return{success:!1,message:"requestId is required",error:"missing_params"}
1567
1568
  const u=t.startWorkerDeps??{},m=await o.get(`/api/projects/${i}/requests/${s.requestId}`),w=m?.data??m
1568
1569
  if(!w||!["open"].includes(w.status))return{success:!0,message:`${s.requestId} is ${w?.status??"unknown"}: nothing to start`}
1569
- const v=C.get(s.requestId)
1570
+ const v=A.get(s.requestId)
1570
1571
  if(v)return{success:!0,message:`${s.requestId} starts on ${v} when its current turn ends`}
1571
1572
  const b=await(u.openDuplicates??r)(t,w,s.requestId),k=await(u.requestRecord??n)(t,s.requestId,w),S=function(e,t,r,s){const n=[`Implement ${e.requestId} ("${t?.title??""}") and own its full lifecycle. It was registered via ${e.source??"a registration door"}.`,s?"Its record, as get_request_information shows it now, is at the end of this message: start from it, and read the request again only when you need what changed since. If something it BUILDS ON does not exist in this project, call request_operations premise-block with the reason instead of starting. New content the request introduces is not a missing premise.":"Read it with get_request_information first. If something it BUILDS ON does not exist in this project, call request_operations premise-block with the reason instead of starting. New content the request introduces is not a missing premise.",`If its acceptance criteria ALREADY hold as the code stands, do no work: complete ${e.requestId} yourself as a verification-only completion, naming exactly which criteria hold and where. Nobody else closes it: a report saying so left req000210 in progress with nobody working it.`,'Otherwise build what it asks (its own choices go in an opening brief when the pickup briefing asks for one), run the relevant validation, and complete the request with evidence. A value the request states is the value: a version number, a label, a default. Do not bump or "improve" it (two coders raised a footer version the user had just set, 2026-09-07).']
1572
1573
  return r.length&&n.push(`Possible duplicates already open with the same title: ${r.join(", ")}. Check before doing the work twice; if one already covers it, cancel ${e.requestId} as superseded.`),e.userNote&&n.push(`From the user: ${e.userNote}`),e.rules?.length&&n.push(function(e){const t=e.filter(e=>"told"===e.enforcement),r=e.filter(e=>"told"!==e.enforcement),s=[...t.map(e=>`- [rule:${e.id}] ${e.text}`),...r.map(e=>`- [rule:${e.id}] (reviewed, ${e.severity}) ${e.text}`)]
1573
1574
  return`The workflow's rules for this work (the user's; where one conflicts with the brief above, the rule wins).${r.length?" A reviewer checks the reviewed ones by id at the review gate: 'block' ones must hold, 'advise' ones are expected.":""} If the user's instructions on this request contradict a rule, do not decide for them and do not bury it in a brief: call guidance_feedback with the id exactly as listed below (guidanceId "rule:<id>"), verdict "conflict" and the contradiction as the reason; the user gets one card to keep it, waive it here, or retire it, and you are told the answer. Follow the rule until then.\n${s.join("\n")}`}(e.rules)),s&&n.push(`\n# ${e.requestId}: ${t?.title??""}\n${s}`),n.join("\n")}(s,w,b,k),j="string"==typeof s.worker&&s.worker.trim()||(0,p.kindProfile)(w?.type).worker||"worker",E=(0,g.spawnToolMask)({kind:w?.type,hasTestProcess:"boolean"==typeof s.hasTestProcess?s.hasTestProcess:void 0});(0,g.hasMask)(E)&&d.info("session routing: narrowing the worker tool surface for this request",{requestId:s.requestId,worker:j,off:Object.keys(E)})
1574
- const A={p:s,req:w,worker:j,startPrompt:S,toolMask:E,deps:u}
1575
+ const C={p:s,req:w,worker:j,startPrompt:S,toolMask:E,deps:u}
1575
1576
  let _=null
1576
- const x=w?.filedBySession??w?.filed_by_session,P=w?.filedByAgent??w?.filed_by_agent
1577
- if("string"==typeof x&&x&&(0,$.sameWorkerRole)(P,j))try{if((await(0,f.readAllPages)(o,e=>`/api/projects/${i}/cli-sessions?status=active&limit=100&offset=${e}`)).some(e=>e?.externalSessionId===x)){const e=await(u.turnState??l)(t,x,s.requestId)
1578
- e.liveElsewhere?d.info("session routing: the filer is mid-turn on another live request; not handing it this one",{requestId:s.requestId,sessionId:x,on:e.liveElsewhere}):(_={sessionId:x},d.info("session routing: the session that filed this request keeps it",{requestId:s.requestId,sessionId:x}))}}catch{}if(_=_??await(u.pickWarm??h)(t,{worker:j,requestId:s.requestId,kind:w?.type,kindModels:s.kindModels,planDocumentId:w?.planDocumentId??w?.plan_document_id??null}),_?.sessionId){const e=_.sessionId,r=await(u.turnState??l)(t,e,s.requestId)
1577
+ const x=w?.filedBySession??w?.filed_by_session,T=w?.filedByAgent??w?.filed_by_agent
1578
+ if("string"==typeof x&&x&&(0,$.sameWorkerRole)(T,j))try{if((await(0,f.readAllPages)(o,e=>`/api/projects/${i}/cli-sessions?status=active&limit=100&offset=${e}`)).some(e=>e?.externalSessionId===x)){const e=await(u.turnState??l)(t,x,s.requestId)
1579
+ e.liveElsewhere?d.info("session routing: the filer is mid-turn on another live request; not handing it this one",{requestId:s.requestId,sessionId:x,on:e.liveElsewhere}):(_={sessionId:x},d.info("session routing: the session that filed this request keeps it",{requestId:s.requestId,sessionId:x}))}}catch{}if(_=_??await(u.pickWarm??h)(t,{worker:j,requestId:s.requestId,kind:w?.type,kindModels:s.kindModels,planDocumentId:w?.planDocumentId??w?.plan_document_id??null,areas:Array.isArray(w?.areas)?w.areas.map(String):[]}),_?.sessionId){const e=_.sessionId,r=await(u.turnState??l)(t,e,s.requestId)
1579
1580
  if(r.liveElsewhere)d.info("session routing: the chosen worker is mid-turn on another live request; spawning instead",{requestId:s.requestId,sessionId:e,on:r.liveElsewhere})
1580
- else{if(r.busy)return C.set(s.requestId,e),(u.whenIdle??((e,r)=>(0,y.whenSessionIdle)(e,async()=>(await l(t,e,s.requestId)).busy,r)))(e,e=>{(async function(e,t,r,s){const{apiClient:n,projectId:o,logger:i}=e,{p:d}=t
1581
+ else{if(r.busy)return A.set(s.requestId,e),(u.whenIdle??((e,r)=>(0,y.whenSessionIdle)(e,async()=>(await l(t,e,s.requestId)).busy,r)))(e,e=>{(async function(e,t,r,s){const{apiClient:n,projectId:o,logger:i}=e,{p:d}=t
1581
1582
  try{const u=await n.get(`/api/projects/${o}/requests/${d.requestId}`),h=u?.data??u
1582
1583
  if(!h||"open"!==h.status)return void i.info("session routing: the request moved on while its worker finished a turn; nothing to hand over",{requestId:d.requestId,status:h?.status??"unknown"})
1583
1584
  const p=await(t.deps.turnState??l)(e,r.sessionId,d.requestId)
1584
1585
  if("idle"===s&&!p.busy&&!p.liveElsewhere&&await a(e,{...t,req:h},r))return
1585
- i.info("session routing: the worker did not come free for this start; spawning",{requestId:d.requestId,sessionId:r.sessionId,why:s,busy:p.busy,on:p.liveElsewhere}),await c(e,{...t,req:h})}catch(e){i.warn("session routing: the start after a turn failed",{requestId:d.requestId,sessionId:r.sessionId,error:(0,I.getErrorMessage)(e)})}})(t,A,_,e).finally(()=>C.delete(s.requestId))}),d.info("session routing: the thread's worker is finishing a turn; the start is handed over when it ends",{requestId:s.requestId,sessionId:e,previous:_.previousRequestId}),{success:!0,message:`${s.requestId} starts on warm ${j} ${e} when its current turn ends (the thread shares the session)`}
1586
- if(await a(t,A,_))return{success:!0,message:`${s.requestId} handed to warm ${j} ${e} (the thread shares the session)`}}}else _?.why&&d.info(`session routing: spawning, because ${(0,$.whyNotReused)(_.why)}`,{requestId:s.requestId,why:_.why})
1587
- return c(t,A)}
1588
- const p=t(87780),f=t(5641),g=t(45687),m=t(28148),w=t(17039),y=t(67078),v=t(60833),b=t(2424),k=t(23687),I=t(58824),S=t(65454),$=t(64469),j=t(76379),E=t(83807),C=new Map,A=new Set(["completed","canceled","reverted"])},82928(e,exports,t){"use strict"
1586
+ i.info("session routing: the worker did not come free for this start; spawning",{requestId:d.requestId,sessionId:r.sessionId,why:s,busy:p.busy,on:p.liveElsewhere}),await c(e,{...t,req:h})}catch(e){i.warn("session routing: the start after a turn failed",{requestId:d.requestId,sessionId:r.sessionId,error:(0,I.getErrorMessage)(e)})}})(t,C,_,e).finally(()=>A.delete(s.requestId))}),d.info("session routing: the thread's worker is finishing a turn; the start is handed over when it ends",{requestId:s.requestId,sessionId:e,previous:_.previousRequestId}),{success:!0,message:`${s.requestId} starts on warm ${j} ${e} when its current turn ends (the thread shares the session)`}
1587
+ if(await a(t,C,_))return{success:!0,message:`${s.requestId} handed to warm ${j} ${e} (${_.area?"same area, separate work":"the thread shares the session"})`}}}else _?.why&&d.info(`session routing: spawning, because ${(0,$.whyNotReused)(_.why)}`,{requestId:s.requestId,why:_.why})
1588
+ return c(t,C)}
1589
+ const p=t(87780),f=t(5641),g=t(45687),m=t(28148),w=t(17039),y=t(67078),v=t(60833),b=t(2424),k=t(23687),I=t(58824),S=t(65454),$=t(64469),j=t(81570),E=t(76379),C=t(83807),A=new Map,_=new Set(["completed","canceled","reverted"])},82928(e,exports,t){"use strict"
1589
1590
  function r(e){const t=String(e??"").match(/^ProviderQuotaError(?:\(([\w-]+)\))?/)
1590
1591
  return t?"provider-switched"===t[1]?"the AI plan it was using ran out, and the project has since been switched to another model":"window-passed"===t[1]?"the AI plan it was using ran out, and the limit has since reset":"the AI plan it was using ran out":"the connection to the AI provider dropped"}function s(e,t,r){const s=/^Reviewer \(([A-Za-z_ -]+)\)/.exec(String(r??""))?.[1]
1591
1592
  if(s)return`The ${s.toLowerCase().replace(/_/g," ")} reviewer on ${t}`
@@ -1996,46 +1997,56 @@ else if(v){const e=t(i?.operations)||[],r=(Array.isArray(e)?e:[]).map(e=>e.opera
1996
1997
  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}):k&&l.push({eventType:"query_history",timing:"after",variables:u})
1997
1998
  return l.push({eventType:"tool_executed",timing:"after",variables:{tool:n,toolName:n,output:d,sessionId:o||""}}),l}
1998
1999
  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},23411(e,exports,t){"use strict"
1999
- function r(e,t={}){return!!e&&(!!exports.I5.has(e)||!!t.warmCoders&&(0,n.isBranchWorkerAgentType)(e))}function s(e,t,r){return e.enabled?r.busy?"busy":t-r.anchorMs>36e5*e.hours?"expired":r.instructionsChangedAt&&r.anchorMs<r.instructionsChangedAt?"invalidated":t-Math.max(r.anchorMs,r.lastTickMs)>=6e4*e.tickMinutes?"tick":"wait":"off"}exports.I5=exports.KX=exports.hf=void 0,exports.startKeepWarmRunner=function(e){async function t(){if(!(0,d.isProjectPaused)()&&!w){w=!0
2000
- try{const t=await async function(){if(g&&Date.now()-g.at<3e5)return g.s
2001
- try{const e=await u.get(`/api/projects/${h}`),t=function(e){const t=e?.keepWarm??{},r=Number(t.hours),s=Number(t.tickMinutes)
2000
+ function r(e,t={}){return!!e&&(!!exports.I5.has(e)||!!t.warmCoders&&(0,n.isBranchWorkerAgentType)(e))}function s(e,t,r){return e.enabled?r.busy?"busy":t-r.anchorMs>36e5*e.hours?"expired":r.instructionsChangedAt&&r.anchorMs<r.instructionsChangedAt?"invalidated":t-Math.max(r.anchorMs,r.lastTickMs)>=6e4*e.tickMinutes?"tick":"wait":"off"}exports.I5=exports.KX=exports.hf=void 0,exports.startKeepWarmRunner=function(e){async function t(){if(!(0,h.isProjectPaused)()&&!b){b=!0
2001
+ try{const t=await async function(){if(w&&Date.now()-w.at<3e5)return w.s
2002
+ try{const e=await p.get(`/api/projects/${f}`),t=function(e){const t=e?.keepWarm??{},r=Number(t.hours),s=Number(t.tickMinutes)
2002
2003
  return{enabled:void 0===t.enabled?exports.hf.enabled:!1!==t.enabled,hours:Number.isFinite(r)&&r>0?r:exports.hf.hours,tickMinutes:Number.isFinite(s)&&s>=1?s:exports.hf.tickMinutes}}((e?.data??e)?.metadata)
2003
- return g={at:Date.now(),s:t},t}catch{return g?.s??{...exports.hf}}}(),d=await async function(){if(m&&Date.now()-m.at<3e5)return m.p
2004
- try{const e=await u.get(`/api/projects/${h}`),t=(0,l.readWarmCoderPolicy)((e?.data??e)?.metadata)
2005
- return m={at:Date.now(),p:t},t}catch{return m?.p??{...l.WARM_CODER_DEFAULTS}}}()
2006
- if(!t.enabled&&!d.enabled)return
2007
- const w=(0,i.getOpenCodeUrlFromConfig)(e.projectPath)
2008
- if(!w)return
2009
- const v=e.fetchImpl??fetch,[b,k]=await Promise.all([v(`${w}/session`,{signal:AbortSignal.timeout(6e3)}),v(`${w}/session/status`,{signal:AbortSignal.timeout(6e3)})])
2010
- if(!b.ok)return
2011
- const I=await b.json(),S=k.ok?await k.json():{},$=new Set(Object.entries(S??{}).filter(([,e])=>"busy"===e?.type).map(([e])=>e)),j=new Set,E=[]
2012
- for(let e=0;e<1e3;e+=100){const t=await u.get(`/api/projects/${h}/cli-sessions?status=active&limit=100&offset=${e}`),s=t?.data??t??[]
2013
- for(const e of s)e?.externalSessionId&&r(e.agentType,{warmCoders:d.enabled})&&j.add(e.externalSessionId),e?.externalSessionId&&(0,n.isBranchWorkerAgentType)(e.agentType)&&E.push(e)
2014
- if(s.length<100)break}const C=Date.now()
2015
- let A=0,_=null
2016
- try{const t=await(0,c.fetchProviderUsage)(e.projectPath,v),r=(0,c.providerOf)(e.getDefaultModel?e.getDefaultModel():e.cachedDefaultModel),s=(0,c.spentWindow)(t,r)
2017
- s&&r&&(_=(0,c.describeWindow)(r,s))}catch{}_&&_!==y&&p.info("keep-warm: holding ticks, provider nearly spent",{why:_}),!_&&y&&p.info("keep-warm: provider usage back under the threshold; ticks resume"),y=_
2018
- for(const r of I){if(!t.enabled)break
2019
- if(!j.has(r.id))continue
2020
- if(_)continue
2004
+ return w={at:Date.now(),s:t},t}catch{return w?.s??{...exports.hf}}}(),h=await async function(){if(y&&Date.now()-y.at<3e5)return y.p
2005
+ try{const e=await p.get(`/api/projects/${f}`),t=(0,l.readWarmCoderPolicy)((e?.data??e)?.metadata)
2006
+ return y={at:Date.now(),p:t},t}catch{return y?.p??{...l.WARM_CODER_DEFAULTS}}}()
2007
+ if(!t.enabled&&!h.enabled)return
2008
+ const b=(0,i.getOpenCodeUrlFromConfig)(e.projectPath)
2009
+ if(!b)return
2010
+ const I=e.fetchImpl??fetch,[S,$]=await Promise.all([I(`${b}/session`,{signal:AbortSignal.timeout(6e3)}),I(`${b}/session/status`,{signal:AbortSignal.timeout(6e3)})])
2011
+ if(!S.ok)return
2012
+ const j=await S.json(),E=$.ok?await $.json():{},C=new Set(Object.entries(E??{}).filter(([,e])=>"busy"===e?.type).map(([e])=>e)),A=new Set,_=[]
2013
+ for(let e=0;e<1e3;e+=100){const t=await p.get(`/api/projects/${f}/cli-sessions?status=active&limit=100&offset=${e}`),s=t?.data??t??[]
2014
+ for(const e of s)e?.externalSessionId&&r(e.agentType,{warmCoders:h.enabled})&&A.add(e.externalSessionId),e?.externalSessionId&&(0,n.isBranchWorkerAgentType)(e.agentType)&&_.push(e)
2015
+ if(s.length<100)break}const x=Date.now()
2016
+ let T=0,P=null
2017
+ try{const t=await(0,c.fetchProviderUsage)(e.projectPath,I),r=(0,c.providerOf)(e.getDefaultModel?e.getDefaultModel():e.cachedDefaultModel),s=(0,c.spentWindow)(t,r)
2018
+ s&&r&&(P=(0,c.describeWindow)(r,s))}catch{}P&&P!==k&&g.info("keep-warm: holding ticks, provider nearly spent",{why:P}),!P&&k&&g.info("keep-warm: provider usage back under the threshold; ticks resume"),k=P
2019
+ for(const r of j){if(!t.enabled)break
2020
+ if(!A.has(r.id))continue
2021
+ if(P)continue
2021
2022
  const n=Number(r.time?.updated??r.time?.created??0)
2022
2023
  if(!n)continue
2023
- let i=f.get(r.id)
2024
- if(i||(i={anchorMs:n,lastTickMs:0,lastSeenUpdated:n},f.set(r.id,i)),n>i.lastSeenUpdated&&(0===i.lastTickMs||n>i.lastTickMs+12e4)&&(i.anchorMs=n),i.lastSeenUpdated=n,"tick"!==s(t,C,{anchorMs:i.anchorMs,lastTickMs:i.lastTickMs,busy:$.has(r.id),instructionsChangedAt:e.instructionsChangedAt?.()??0}))continue
2024
+ let i=m.get(r.id)
2025
+ if(i||(i={anchorMs:n,lastTickMs:0,lastSeenUpdated:n},m.set(r.id,i)),n>i.lastSeenUpdated&&(0===i.lastTickMs||n>i.lastTickMs+12e4)&&(i.anchorMs=n),i.lastSeenUpdated=n,"tick"!==s(t,x,{anchorMs:i.anchorMs,lastTickMs:i.lastTickMs,busy:C.has(r.id),instructionsChangedAt:(0,d.configReloadedAt)(e.projectPath)}))continue
2025
2026
  const a=await(0,o.deliverPrompt)(e,r.id,exports.KX,{kind:"keep-warm"})
2026
- a.ok?(i.lastTickMs=C,A++):p.warn("keep-warm tick did not land",{sessionId:r.id,error:a.error})}if(A>0&&p.info("keep-warm: ticked idle sessions",{ticked:A,hours:t.hours,tickMinutes:t.tickMinutes}),d.enabled){const e=new Map(I.map(e=>[e.id,Number(e.time?.updated??e.time?.created??0)])),t=await async function(e){if(0===e.coderRows.length)return 0
2027
+ a.ok?(i.lastTickMs=x,T++):g.warn("keep-warm tick did not land",{sessionId:r.id,error:a.error})}if(T>0&&g.info("keep-warm: ticked idle sessions",{ticked:T,hours:t.hours,tickMinutes:t.tickMinutes}),h.enabled){const t=new Map(j.map(e=>[e.id,Number(e.time?.updated??e.time?.created??0)])),r=(0,d.configReloadedAt)(e.projectPath),s=await async function(e){if(0===e.coderRows.length)return 0
2027
2028
  const t=new Set
2028
2029
  try{const r=await e.apiClient.get(`/api/projects/${e.projectId}/session-request-associations?activeOnly=true&limit=500`),s=r?.data??r??[]
2029
2030
  if(Array.isArray(s)&&s.length>=500)return e.logger.warn("keep-warm: 500+ open associations, ownership not fully known; skipping cold-coder reaping this sweep"),0
2030
2031
  for(const e of s){const r=e?.opencodeSessionId??e?.opencode_session_id
2031
- r&&t.add(String(r))}}catch(t){return e.logger.warn("keep-warm: association read failed; skipping cold-coder reaping this sweep",{error:(0,a.getErrorMessage)(t)}),0}const r=e.coderRows.map(r=>({id:String(r.localId??r.id),busy:e.busy.has(r.externalSessionId),lastActiveMs:e.lastActiveOf(r.externalSessionId,r)||0,holdsOpenRequest:t.has(r.externalSessionId)})).filter(e=>e.lastActiveMs>0)
2032
- let s=0
2033
- for(const t of function(e,t,r){return e.filter(e=>!e.busy&&!e.holdsOpenRequest&&t-e.lastActiveMs>r).map(e=>e.id)}(r,e.now,e.warmMs).slice(0,25))try{await e.apiClient.patch(`/api/projects/${e.projectId}/cli-sessions/${encodeURIComponent(t)}`,{status:"archived"}),s++}catch(r){e.logger.warn("cold coder reap failed",{sessionId:t,error:(0,a.getErrorMessage)(r)})}return s}({apiClient:u,projectId:h,logger:p,coderRows:E,busy:$,now:C,warmMs:6e4*d.warmMinutes,lastActiveOf:(t,r)=>f.get(t)?.anchorMs??e.get(t)??(Date.parse(r.updatedAt??r.createdAt??"")||0)})
2034
- t>0&&p.info("keep-warm: cold coders put away",{reaped:t})}}catch(e){p.warn("keep-warm sweep failed",{error:(0,a.getErrorMessage)(e)})}finally{w=!1}}}const{apiClient:u,projectId:h,logger:p}=e,f=new Map
2035
- let g=null,m=null,w=!1,y=null
2036
- const v=setInterval(()=>{t()},6e4),b=setTimeout(()=>{t()},5e3)
2037
- return b.unref?.(),p.info("keep-warm runner started",{scanMs:6e4}),{stop(){clearInterval(v),clearTimeout(b)}}}
2038
- const n=t(87780),o=t(17039),i=t(60833),a=t(58824),c=t(43218),l=t(64469),d=t(36799)
2032
+ r&&t.add(String(r))}}catch(t){return e.logger.warn("keep-warm: association read failed; skipping cold-coder reaping this sweep",{error:(0,a.getErrorMessage)(t)}),0}const r=[]
2033
+ for(const s of e.coderRows){const n={id:String(s.localId??s.id),busy:e.busy.has(s.externalSessionId),holdsOpenRequest:t.has(s.externalSessionId)}
2034
+ n.busy||n.holdsOpenRequest||(n.warm=await e.warmOf(s.externalSessionId,s)),r.push(n)}let s=0
2035
+ for(const t of(n=r,n.filter(e=>!e.busy&&!e.holdsOpenRequest&&!1===e.warm).map(e=>e.id)).slice(0,25))try{await e.apiClient.patch(`/api/projects/${e.projectId}/cli-sessions/${encodeURIComponent(t)}`,{status:"archived"}),s++}catch(r){e.logger.warn("cold coder reap failed",{sessionId:t,error:(0,a.getErrorMessage)(r)})}var n
2036
+ return s}({apiClient:p,projectId:f,logger:g,coderRows:_,busy:C,warmOf:(e,s)=>async function(e){if(!e.lastActiveMs)return
2037
+ if(e.reloadedAt&&e.lastActiveMs<e.reloadedAt)return!1
2038
+ if((0,l.isCacheWarm)(e.now,{lastActiveMs:e.lastActiveMs,providerID:null},e.reloadedAt))return!0
2039
+ if(e.now-e.lastActiveMs>(0,u.longestCacheWarmMs)())return!1
2040
+ const t=await e.providerOf()
2041
+ return t?(0,l.isCacheWarm)(e.now,{lastActiveMs:e.lastActiveMs,providerID:t},e.reloadedAt):void 0}({now:x,reloadedAt:r,lastActiveMs:t.get(e)||Date.parse(s.updatedAt??s.createdAt??"")||0,providerOf:async()=>{if(!v.has(e)){const t=(await(0,d.readLastCall)(b,e,I)).providerID
2042
+ t&&v.set(e,t)}return v.get(e)}})})
2043
+ s>0&&g.info("keep-warm: cold coders put away",{reaped:s})}}catch(e){g.warn("keep-warm sweep failed",{error:(0,a.getErrorMessage)(e)})}finally{b=!1}}}const{apiClient:p,projectId:f,logger:g}=e,m=new Map
2044
+ let w=null,y=null
2045
+ const v=new Map
2046
+ let b=!1,k=null
2047
+ const I=setInterval(()=>{t()},6e4),S=setTimeout(()=>{t()},5e3)
2048
+ return S.unref?.(),g.info("keep-warm runner started",{scanMs:6e4}),{stop(){clearInterval(I),clearTimeout(S)}}}
2049
+ const n=t(87780),o=t(17039),i=t(60833),a=t(58824),c=t(43218),l=t(64469),d=t(81570),u=t(89063),h=t(36799)
2039
2050
  exports.hf={enabled:!0,hours:3,tickMinutes:45},exports.KX="[keep-warm] Cache-hold tick. Reply with the single word ok and do nothing else.",exports.I5=new Set(["alchemist"])},67563(e,exports){"use strict"
2040
2051
  function t(e=6e4,t=Date.now){const r=new Map
2041
2052
  return s=>{const n=t(),o=r.get(s)
@@ -2067,7 +2078,7 @@ const t=e.indexOf("?")
2067
2078
  if(t<0)return e
2068
2079
  const r=e.slice(t+1).split("&").filter(e=>e!==exports.Dy&&!e.startsWith(`${exports.Dy}=`))
2069
2080
  return r.length>0?`${e.slice(0,t)}?${r.join("&")}`:e.slice(0,t)},exports.Ep=3e5,exports.Dy="_alk",exports.TICKETLESS_PATHS=new Set(["/health","/status","/api/registry/projects"])},73613(e,exports,t){"use strict"
2070
- function r(e){e?P.delete(e):P.clear()}async function s(e,r){const s=await t.e(293).then(()=>t(79293)),n=String(r.config.package??""),o=String(r.config.bin??""),i=await a(e),c={name:r.name,projectId:e.projectId,packageSpec:n,bin:o,environment:{...r.config.environment??{},...(0,C.getSecrets)(e.projectPath,r.name)},worktreePath:e.projectPath,egress:i.egress,egressAllowlist:i.egressAllowlist}
2081
+ function r(e){e?T.delete(e):T.clear()}async function s(e,r){const s=await t.e(293).then(()=>t(79293)),n=String(r.config.package??""),o=String(r.config.bin??""),i=await a(e),c={name:r.name,projectId:e.projectId,packageSpec:n,bin:o,environment:{...r.config.environment??{},...(0,C.getSecrets)(e.projectPath,r.name)},worktreePath:e.projectPath,egress:i.egress,egressAllowlist:i.egressAllowlist}
2071
2082
  try{const t=await s.prepareConnectionArtifact(c,e.logger)
2072
2083
  if(!t.ok)return{ok:!1,error:t.error}
2073
2084
  const r=await s.ensureConnectionBox(c,t.volume,e.logger)
@@ -2139,13 +2150,13 @@ try{const e=await fetch(`${t}/mcp`,{signal:AbortSignal.timeout(1e4)})
2139
2150
  if(!e.ok)return"ok"
2140
2151
  s=(await e.json())?.[M]?.status}catch{return"ok"}if("failed"!==s)return q=0,"connected"===s&&r(M),"ok"
2141
2152
  if(q+=1,q<2)return"waiting"
2142
- const n=P.get(M)??{failures:0,skipTicks:0}
2153
+ const n=T.get(M)??{failures:0,skipTicks:0}
2143
2154
  if(n.givenUp)return"given-up"
2144
- if(n.skipTicks>0)return n.skipTicks-=1,P.set(M,n),"waiting"
2155
+ if(n.skipTicks>0)return n.skipTicks-=1,T.set(M,n),"waiting"
2145
2156
  const{reloaded:o,error:i}=await m({...e,openCodeUrl:t})
2146
- return o?(q=0,r(M),e.logger.info("The alchemy MCP server had stopped; it is running again"),"revived"):(n.failures+=1,n.failures>=5?(n.givenUp=!0,P.set(M,n),e.logger.error("The alchemy MCP server could not be restarted",{attempts:n.failures,error:i}),console.log("\u26a0\ufe0f AlchemyLab's project tools stopped and could not be restarted. Restart alchemylab to get them back."),"given-up"):(n.skipTicks=x(n.failures),P.set(M,n),e.logger.warn("Restarting the alchemy MCP server failed, backing off",{failures:n.failures,error:i}),"failed"))}async function y(e){if(0===D.length)return[]
2147
- try{if(e.isBusy&&await e.isBusy()){if(void 0===T&&(T=Date.now()),Date.now()-T<3e5)return[]
2148
- e.logger.warn("MCP reattach: sessions have been busy for too long, reattaching anyway",{deferredForMs:Date.now()-T})}else T=void 0}catch{}const t=(0,j.getOpenCodeUrlFromConfig)(e.projectPath)
2157
+ return o?(q=0,r(M),e.logger.info("The alchemy MCP server had stopped; it is running again"),"revived"):(n.failures+=1,n.failures>=5?(n.givenUp=!0,T.set(M,n),e.logger.error("The alchemy MCP server could not be restarted",{attempts:n.failures,error:i}),console.log("\u26a0\ufe0f AlchemyLab's project tools stopped and could not be restarted. Restart alchemylab to get them back."),"given-up"):(n.skipTicks=x(n.failures),T.set(M,n),e.logger.warn("Restarting the alchemy MCP server failed, backing off",{failures:n.failures,error:i}),"failed"))}async function y(e){if(0===D.length)return[]
2158
+ try{if(e.isBusy&&await e.isBusy()){if(void 0===P&&(P=Date.now()),Date.now()-P<3e5)return[]
2159
+ e.logger.warn("MCP reattach: sessions have been busy for too long, reattaching anyway",{deferredForMs:Date.now()-P})}else P=void 0}catch{}const t=(0,j.getOpenCodeUrlFromConfig)(e.projectPath)
2149
2160
  if(!t)return[]
2150
2161
  let n
2151
2162
  try{n=await h(t)}catch{return[]}let i=null
@@ -2154,9 +2165,9 @@ t&&(i=new Set(t.filter(e=>e.enabled).map(e=>e.name)))}catch{}const a=D.filter(e=
2154
2165
  if(0===a.length)return[]
2155
2166
  const u=[]
2156
2167
  for(const n of a){if((0,C.missingSecretKeys)(e.projectPath,n.name,n.secretKeys??[]).length>0)continue
2157
- const i=P.get(n.name)??{failures:0,skipTicks:0}
2168
+ const i=T.get(n.name)??{failures:0,skipTicks:0}
2158
2169
  if(i.givenUp)continue
2159
- if(i.skipTicks>0){i.skipTicks-=1,P.set(n.name,i)
2170
+ if(i.skipTicks>0){i.skipTicks-=1,T.set(n.name,i)
2160
2171
  continue}let a=!1,l=""
2161
2172
  try{let r
2162
2173
  if("boxed"===n.transport||"repo"===n.transport){const t="repo"===n.transport?await o(e,n):await s(e,n)
@@ -2164,10 +2175,10 @@ if(!t.ok)throw new Error(`its box could not be prepared: ${t.error}`)
2164
2175
  r=t.command}const i=await fetch(`${t}/mcp`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n.name,config:c(n,(0,C.getSecrets)(e.projectPath,n.name),r)}),signal:AbortSignal.timeout(6e4)})
2165
2176
  if(a=i.ok,!a){const e=await i.text().catch(()=>"")
2166
2177
  l=`OpenCode returned HTTP ${i.status}${e?`: ${e.slice(0,200)}`:""}`}}catch(e){l=(0,E.getErrorMessage)(e)}if(a)u.push(n.name),r(n.name)
2167
- else{if(i.failures+=1,i.failures>=5){i.givenUp=!0,P.set(n.name,i)
2178
+ else{if(i.failures+=1,i.failures>=5){i.givenUp=!0,T.set(n.name,i)
2168
2179
  const t=`Could not be reattached after ${i.failures} attempts${l?`: ${l}`:""}. It will be retried on the next full sync, or when you switch it off and on.`
2169
2180
  e.logger.error("MCP connection given up on",{name:n.name,attempts:i.failures,error:l}),await d(e,n,"failed",t).catch(()=>{})
2170
- continue}i.skipTicks=x(i.failures),P.set(n.name,i),e.logger.warn("MCP reattach failed, backing off",{name:n.name,failures:i.failures,nextAttemptInTicks:i.skipTicks+1,error:l})}}return u.length>0&&e.logger.info("Reattached MCP connections the engine had dropped",{connections:u}),u}var v,b=this&&this.__createBinding||(Object.create?function(e,t,r,s){void 0===s&&(s=r)
2181
+ continue}i.skipTicks=x(i.failures),T.set(n.name,i),e.logger.warn("MCP reattach failed, backing off",{name:n.name,failures:i.failures,nextAttemptInTicks:i.skipTicks+1,error:l})}}return u.length>0&&e.logger.info("Reattached MCP connections the engine had dropped",{connections:u}),u}var v,b=this&&this.__createBinding||(Object.create?function(e,t,r,s){void 0===s&&(s=r)
2171
2182
  var n=Object.getOwnPropertyDescriptor(t,r)
2172
2183
  n&&!("get"in n?!t.__esModule:n.writable||n.configurable)||(n={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,s,n)}:function(e,t,r,s){void 0===s&&(s=r),e[s]=t[r]}),k=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),I=this&&this.__importStar||(v=function(e){return v=Object.getOwnPropertyNames||function(e){var t=[]
2173
2184
  for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[t.length]=r)
@@ -2182,8 +2193,8 @@ if(t.length>0){let r=!1
2182
2193
  try{r=!(!e.isBusy||!await e.isBusy())}catch{}if(!r)return e.logger.info("MCP connections: repository changed under a built server, rebuilding",{stale:t}),void await p(e)}await w(e),await y(e)})().catch(()=>{})},3e4),r.unref?.())},a=(t,r)=>{n||p(e).then(i=>{if(!n){if(!i.engineUnreachable||r<=0)return i.engineUnreachable&&e.logger.warn("MCP connections: the engine never answered during startup; falling back to the periodic sync"),void o()
2183
2194
  s=setTimeout(()=>a(Math.min(2*t,3e4),r-1),t),s.unref?.()}}).catch(t=>{e.logger.warn("MCP connection sync failed at start",{error:(0,E.getErrorMessage)(t)}),o()})}
2184
2195
  return a(2e3,8),{stop:()=>{n=!0,t&&clearInterval(t),r&&clearInterval(r),s&&clearTimeout(s)}}}
2185
- const S=I(t(79896)),$=I(t(16928)),j=t(60833),E=t(58824),C=t(59373),A=t(83807),_=t(49779),x=e=>e<=1?0:Math.min(2**(e-1),8),P=new Map
2186
- let T
2196
+ const S=I(t(79896)),$=I(t(16928)),j=t(60833),E=t(58824),C=t(59373),A=t(83807),_=t(49779),x=e=>e<=1?0:Math.min(2**(e-1),8),T=new Map
2197
+ let P
2187
2198
  const M="alchemy",R=new Map,O=/^([a-z][a-z0-9-]{1,38})_\*$/
2188
2199
  let q=0,D=[]},64331(e,exports){"use strict"
2189
2200
  function t(e){const t=(e??"").trim().toLowerCase(),r=t.indexOf("/")
@@ -2380,19 +2391,19 @@ const d=r.command.replace(/\s+/g," ").trim()
2380
2391
  if((i[1].endsWith("\u2026")?d.startsWith(a):d===a)&&(r.command===s||t(r.command).includes(c)))return r}}
2381
2392
  const r=new Map},43218(e,exports,t){"use strict"
2382
2393
  function r(e){if(e&&e.includes("/"))return e.split("/")[0].trim().toLowerCase()||void 0}async function s(e,t=fetch){if(!e)return null
2383
- const r=i.get(e)
2394
+ const r=a.get(e)
2384
2395
  if(r&&Date.now()-r.at<3e4)return r.value
2385
2396
  try{const r=await t(`${e.replace(/\/$/,"")}/config/providers`,{signal:AbortSignal.timeout(5e3)})
2386
2397
  if(!r.ok)return null
2387
2398
  const s=await r.json(),n={connected:(s.providers||[]).map(e=>String(e.id||"")).filter(Boolean),defaults:s.default||{}}
2388
- return i.set(e,{at:Date.now(),value:n}),n}catch{return null}}exports.b=void 0,exports.fetchProviderUsage=async function(e,t=fetch){const r=(0,n.getOpenCodeUrlFromConfig)(e)
2399
+ return a.set(e,{at:Date.now(),value:n}),n}catch{return null}}exports.b=void 0,exports.fetchProviderUsage=async function(e,t=fetch){const r=(0,n.getOpenCodeUrlFromConfig)(e)
2389
2400
  if(!r)return[]
2390
- const s=r.replace(/\/$/,""),i=o.get(s)
2391
- if(i&&Date.now()-i.at<6e4)return i.data
2401
+ const s=r.replace(/\/$/,""),o=i.get(s)
2402
+ if(o&&Date.now()-o.at<6e4)return o.data
2392
2403
  try{const e=await t(`${s}/config/usage`,{signal:AbortSignal.timeout(15e3)})
2393
- if(!e.ok)return i?.data??[]
2404
+ if(!e.ok)return o?.data??[]
2394
2405
  const r=await e.json(),n=Array.isArray(r?.providers)?r.providers:[]
2395
- return o.set(s,{at:Date.now(),data:n}),n}catch{return i?.data??[]}},exports.providerOf=r,exports.spentWindow=function(e,t,r=exports.b){if(!t)return null
2406
+ return i.set(s,{at:Date.now(),data:n}),n}catch{return o?.data??[]}},exports.providerOf=r,exports.spentWindow=function(e,t,r=exports.b){if(!t)return null
2396
2407
  const s=e.find(e=>e.providerID===t)
2397
2408
  if(!s)return null
2398
2409
  let n=null
@@ -2401,12 +2412,14 @@ return n},exports.describeWindow=function(e,t){const r=t.resetsAt?new Date(1e3*t
2401
2412
  return`${e} is ${t.usedPercent}% into its ${t.label} (resets ${r})`},exports.fetchConnectedProviders=async function(e,t=fetch){return s((0,n.getOpenCodeUrlFromConfig)(e),t)},exports.fetchConnectedProvidersAt=s,exports.runnableModel=function(e,t){if(!e||!t||0===t.connected.length)return e
2402
2413
  const s=r(e)
2403
2414
  if(!s||t.connected.includes(s))return e
2415
+ const n=(0,o.primaryProviderOf)(t.connected,e)
2416
+ if(n)return(0,o.carryModelToProvider)(e,n)??(0,o.defaultModelFor)(n)
2404
2417
  for(const e of t.connected)if(t.defaults[e])return`${e}/${t.defaults[e]}`
2405
2418
  return e}
2406
- const n=t(60833)
2419
+ const n=t(60833),o=t(95671)
2407
2420
  exports.b=(()=>{const e=Number(process.env.ALCHEMY_PROVIDER_SPENT_PERCENT)
2408
2421
  return Number.isFinite(e)&&e>0&&e<=100?e:90})()
2409
- const o=new Map,i=new Map},49816(e,exports){"use strict"
2422
+ const i=new Map,a=new Map},49816(e,exports){"use strict"
2410
2423
  async function t(e,t,s){let n=r.get(t)
2411
2424
  if(!n||Date.now()-n.at>3e4)try{const s=await e.get(`/api/projects/${t}/check-commands`),o=s?.data||s||[]
2412
2425
  n={at:Date.now(),commands:o.filter(e=>e?.isActive??e?.is_active??!0).map(e=>({command:String(e?.command||""),environmentId:e?.environmentId??e?.environment_id??void 0,name:e?.name?String(e.name):void 0,blastRadius:!0===(e?.blastRadius??e?.blast_radius),targetPath:(e?.targetPath??e?.target_path)||void 0})).filter(e=>e.command)},r.set(t,n)}catch(s){if(!n)try{const s=await e.get(`/api/projects/${t}/check-commands`),o=s?.data||s||[]
@@ -2640,7 +2653,7 @@ try{r=await i.promises.realpath(e)}catch{return!1}try{s=await i.promises.realpat
2640
2653
  const i=t(79896),a=o(t(16928))
2641
2654
  exports.ESCAPES_ROOT="path resolves outside the project (a symlink)"},53608(e,exports,t){"use strict"
2642
2655
  function r(e){const t=e.toLowerCase()
2643
- return t.endsWith(".png")?"image/png":/\.jpe?g$/.test(t)?"image/jpeg":"application/octet-stream"}async function s(e,t){return(0,P.storeLocalAsset)(e,t)}function n(e){const t=e?.config?.delivery?.approval
2656
+ return t.endsWith(".png")?"image/png":/\.jpe?g$/.test(t)?"image/jpeg":"application/octet-stream"}async function s(e,t){return(0,T.storeLocalAsset)(e,t)}function n(e){const t=e?.config?.delivery?.approval
2644
2657
  if("auto"===t||"human"===t)return t
2645
2658
  const r=e?.config?.confinement
2646
2659
  return r&&"confined"!==r?"human":"auto"}async function o(e,t,r){const s=e.getProjectId()
@@ -2778,7 +2791,7 @@ if(e){const s=(0,j.assessBlastRun)(e,t.projectPath||"")
2778
2791
  if("refuse-self"===s.verdict)return void r.json({success:!0,data:{exit:126,stdout:"",stderr:"command blocked by staging guard: "+s.reason,ms:0,timedOut:!1,gate:"blocked"}})
2779
2792
  v=!0,b=!1,w=e}}let k=["policy","human"]
2780
2793
  if(f)try{const e=await t.apiClient.get(`/api/projects/${f}/environments`),r=e?.data||e||[],s=Array.isArray(r)?r.find(e=>"live"===e?.config?.worktreeTarget):void 0,n=s?.config?.commandGate
2781
- Array.isArray(n)&&(k=n.filter(e=>"policy"===e||"agent"===e||"human"===e),T.set(f,k))}catch{const e=T.get(f)
2794
+ Array.isArray(n)&&(k=n.filter(e=>"policy"===e||"agent"===e||"human"===e),P.set(f,k))}catch{const e=P.get(f)
2782
2795
  e&&(k=e)}const S=await(0,x.isHubSession)(t.apiClient,f||void 0,"string"==typeof i?i:void 0),$=async e=>(0,x.runGatedExec)({neverHoldForHuman:S,exp:new m.WorkspaceSession(e),command:s,projectPath:t.projectPath,timeoutMs:"number"==typeof c?c:void 0,saved:b,requireApproval:v,approvalKey:"string"==typeof i&&i?i:e,gates:k,sandboxed:!1,sessionId:"string"==typeof i?i:void 0,environmentName:a||"Workspace",source:"command",registeredAllows:g,why:"string"==typeof u?u.slice(0,300):void 0,onApprovalNeeded:f?e=>{(0,y.bridgeApprovalToNotification)(t.apiClient,f,w?{...e,question:`OPERATION${w.targetPath?` on ${w.targetPath}`:""}: \`${w.name||s}\` \u2014 run it? Operations always ask; approval runs it once.`,omitAllowSession:!0}:e)}:void 0,onHumanApproved:f?e=>t.rememberApprovedCommand(f,e):void 0,onJudgeApproved:f?(e,r)=>t.rememberJudgeApprovedCommand(f,e,r):void 0}),E=h?await $(h):await(0,p.withDefaultBranchSnapshot)(t.projectPath,$)
2783
2796
  r.json({success:!0,data:E})}catch(e){r.status(500).json({success:!1,error:(0,$.getErrorMessage)(e)})}}),e.get("/api/experiments",async(e,r)=>{const s=t.getProjectId()
2784
2797
  r.json({success:!0,data:s?(0,x.listExperiments)(s):[]})}),e.get("/api/experiments/running-execs",async(e,t)=>{t.json({success:!0,data:(0,b.listRunningExecs)()})}),e.get("/api/experiments/:handle/live-frame",async(e,r)=>{try{const s=e.params.handle,n=(0,I.experimentRegistry)().peek(s)
@@ -2798,7 +2811,7 @@ if(!r||!s)return void t.status(400).json({success:!1,error:"envId and name are r
2798
2811
  const n=await(0,x.resetPersistentVolume)(r,s)
2799
2812
  if(!n.ok)return void t.status(409).json({success:!1,error:n.error})
2800
2813
  t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,$.getErrorMessage)(e)})}})}
2801
- const d=t(83807),u=t(45738),h=t(5641),p=t(51857),f=l(t(16928)),g=t(33521),m=t(77873),w=t(81618),y=t(20512),v=t(33363),b=t(29869),k=t(40713),I=t(5452),S=t(44811),$=t(58824),j=t(49816),E=t(75361),C=t(79896),A=t(84352),_=t(32504),x=t(96685),P=t(27584),T=new Map
2814
+ const d=t(83807),u=t(45738),h=t(5641),p=t(51857),f=l(t(16928)),g=t(33521),m=t(77873),w=t(81618),y=t(20512),v=t(33363),b=t(29869),k=t(40713),I=t(5452),S=t(44811),$=t(58824),j=t(49816),E=t(75361),C=t(79896),A=t(84352),_=t(32504),x=t(96685),T=t(27584),P=new Map
2802
2815
  let M=0
2803
2816
  const R=Number(process.env.ALCHEMY_MAX_AUTO_PROVISIONS||3)},63484(e,exports,t){"use strict"
2804
2817
  async function r(e,t){const r=e.getProjectId()
@@ -2820,7 +2833,7 @@ try{const r=await e.apiClient.get(`/api/projects/${t}/environments/primary`),s=r
2820
2833
  return s?.config&&!n(s.config)&&s.config.image?{env:(0,p.gateEnvironmentFromRow)(s.config,t),..."string"==typeof s.id?{id:s.id}:{}}:null}catch{return null}}(e)
2821
2834
  return r?{gateEnv:r.env,authoritative:!0,...r.id?{environmentId:r.id}:{}}:{gateEnv:{onHost:!0},authoritative:!1}}catch(t){return e.logger.debug("verify-preflight could not resolve the gate environment",{error:(0,j.getErrorMessage)(t)}),{gateEnv:{},authoritative:!1}}}async function i(e,t,r,s,n={}){const o=Date.now()
2822
2835
  let i=null
2823
- try{const c=s.filter(e=>"string"==typeof e&&!!e.trim()),d=c.slice(0,20),u=c.length-d.length,p=(0,P.simpleGit)(e.projectPath),f=(await l(e,d)).filter(e=>!e.gateEnv?.onHost)
2836
+ try{const c=s.filter(e=>"string"==typeof e&&!!e.trim()),d=c.slice(0,20),u=c.length-d.length,p=(0,T.simpleGit)(e.projectPath),f=(await l(e,d)).filter(e=>!e.gateEnv?.onHost)
2824
2837
  if(0===f.length){const s="this project has nowhere set up to run them automatically"
2825
2838
  return e.logger.info("verify-preflight",{requestId:t,sha:r.slice(0,8),commands:d.length,available:!1,reason:s}),{available:!1,reason:s,sha:r,onHost:!0}}const g=[]
2826
2839
  let v=0
@@ -2828,7 +2841,7 @@ const b=[]
2828
2841
  let k={parent:"",subject:""}
2829
2842
  try{const[e,t]=(await p.raw(["log","-1","--format=%P%x00%s",r])).trim().split("\0")
2830
2843
  e&&!e.includes(" ")&&(k={parent:e,subject:t??""})}catch{}for(const e of f){const s=[]
2831
- for(const n of e.indices){const o=(0,T.passedProofRun)(t,d[n],e.environmentId,r)??(0,T.passedRunThatWroteHead)(t,d[n],e.environmentId,k)
2844
+ for(const n of e.indices){const o=(0,P.passedProofRun)(t,d[n],e.environmentId,r)??(0,P.passedRunThatWroteHead)(t,d[n],e.environmentId,k)
2832
2845
  o&&!(0,w.ranNoTests)(o.stdout,o.stderr)?(g.push({command:d[n],ok:!0,exit:0,output:(o.stderr||o.stdout).slice(0,400),...o.stdout?{printed:o.stdout.slice(-600)}:{},...o.stderr?{error:o.stderr.slice(-600)}:{},authoritative:e.authoritative,fromWorkerRun:!0}),v++):s.push(n)}s.length&&b.push({...e,indices:s})}let I=0
2833
2846
  for(let s=b.length-1;s>=0;s--){const n=await a(e,t,r,d,b[s]).catch(()=>null)
2834
2847
  n&&(g.push(...n),I+=n.length,b.splice(s,1))}b.length>0&&(i=S.default.join(e.projectPath,".alchemy","verify-preflight",`wt-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,7)}`),await p.raw(["worktree","add","--detach",i,r]))
@@ -2841,11 +2854,11 @@ return e.logger.info("verify-preflight",{requestId:t,sha:r.slice(0,8),commands:d
2841
2854
  g.push({command:d[s.indices[t]]??e.name,ok:e.passed&&!a,exit:e.exit,timedOut:e.timedOut,...o?{couldNotRun:!0,...s.lacks?.length?{needs:s.lacks}:{}}:{},...e.passed||e.timedOut||e.exit!==h.RUN_PARSE_FAILED_EXIT?{}:{unparsed:!0},output:r,...e.stdout?{printed:e.stdout.slice(-600)}:{},...e.stderr?{error:e.stderr.slice(-600)}:{},authoritative:s.authoritative})})}else s.indices.forEach(e=>E(e,s.authoritative))}const C=f.every(e=>e.authoritative),A=g.filter(e=>!e.ok&&!e.timedOut&&!e.couldNotRun).length
2842
2855
  e.logger.info("verify-preflight",{requestId:t,sha:r.slice(0,8),commands:d.length,failed:A,dropped:u,unfinished:g.filter(e=>e.timedOut).length,couldNotRun:g.filter(e=>e.couldNotRun).length,authoritative:C,boxes:b.length,reusedFromWorkerRuns:v,fromWorkerBox:I,ms:Date.now()-o})
2843
2856
  const _=(await p.revparse([`${r}^{tree}`]).catch(()=>"")).trim()||void 0
2844
- return{available:!0,authoritative:C,sha:r,..._?{tree:_}:{},dropped:u,ms:Date.now()-o,results:g}}catch(t){return e.logger.debug("verify replay failed",{error:(0,j.getErrorMessage)(t)}),{available:!1,reason:(0,j.getErrorMessage)(t),sha:r}}finally{if(i)try{await(0,P.simpleGit)(e.projectPath).raw(["worktree","remove","--force",i])}catch(t){e.logger.debug("verify-preflight worktree cleanup failed",{error:(0,j.getErrorMessage)(t)})}}}async function a(e,t,r,s,n){const o=e.getProjectId()
2857
+ return{available:!0,authoritative:C,sha:r,..._?{tree:_}:{},dropped:u,ms:Date.now()-o,results:g}}catch(t){return e.logger.debug("verify replay failed",{error:(0,j.getErrorMessage)(t)}),{available:!1,reason:(0,j.getErrorMessage)(t),sha:r}}finally{if(i)try{await(0,T.simpleGit)(e.projectPath).raw(["worktree","remove","--force",i])}catch(t){e.logger.debug("verify-preflight worktree cleanup failed",{error:(0,j.getErrorMessage)(t)})}}}async function a(e,t,r,s,n){const o=e.getProjectId()
2845
2858
  if(!o||!n.environmentId||n.lacks?.length)return null
2846
2859
  const i=(0,M.experimentRegistry)(),a=i.list(o).find(e=>e.liveWorktree?.requestId===t&&e.environmentId===n.environmentId&&i.peek(e.handle)?.running),c=a?i.peek(a.handle):void 0
2847
2860
  if(!a?.liveWorktree||!c)return null
2848
- const l=(0,P.simpleGit)(a.liveWorktree.path),d=async()=>{const[e,t]=await Promise.all([l.revparse(["HEAD"]),l.raw(["status","--porcelain"])]),s=e.trim()
2861
+ const l=(0,T.simpleGit)(a.liveWorktree.path),d=async()=>{const[e,t]=await Promise.all([l.revparse(["HEAD"]),l.raw(["status","--porcelain"])]),s=e.trim()
2849
2862
  return!t.trim()&&!!s&&(s===r||s.startsWith(r)||r.startsWith(s))}
2850
2863
  if(!await d())return null
2851
2864
  const u=[]
@@ -2966,7 +2979,7 @@ const s=S.default.join(t,n.name,e)
2966
2979
  if(s.startsWith(S.default.join(t,n.name)+S.default.sep)&&await(0,N.realPathWithin)(S.default.join(t,n.name),s))try{const e=await A.promises.stat(s)
2967
2980
  e.isFile()&&(!r||e.mtimeMs>r.mtime)&&(r={abs:s,from:`requests/${n.name}`,mtime:e.mtimeMs})}catch{}}}catch{}return r?{abs:r.abs,from:r.from}:null}
2968
2981
  let c,l=null
2969
- if(n){const e=(0,P.simpleGit)(s.projectPath)
2982
+ if(n){const e=(0,T.simpleGit)(s.projectPath)
2970
2983
  c=await e.raw(["show",`${n}:${r}`])}else{const e=S.default.resolve(s.projectPath,r)
2971
2984
  if(!e.startsWith(S.default.resolve(s.projectPath)+S.default.sep))return void t.status(400).json({success:!1,error:"path escapes the project"})
2972
2985
  if(!await(0,N.realPathWithin)(s.projectPath,e))return void t.status(400).json({success:!1,error:N.ESCAPES_ROOT})
@@ -2977,7 +2990,7 @@ c=await A.promises.readFile(t.abs,"utf8"),l=t.from}}const d=c.length>i
2977
2990
  if(d&&(c=c.slice(0,i)),c.includes("\0")){const e=S.default.extname(r).toLowerCase(),o={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml"}
2978
2991
  if(o[e]){const i=S.default.resolve(s.projectPath,r)
2979
2992
  let c
2980
- if(n)c=Buffer.from(await(0,P.simpleGit)(s.projectPath).binaryCatFile(["blob",`${n}:${r}`]))
2993
+ if(n)c=Buffer.from(await(0,T.simpleGit)(s.projectPath).binaryCatFile(["blob",`${n}:${r}`]))
2981
2994
  else try{c=await A.promises.readFile(i)}catch(e){if("ENOENT"!==e?.code)throw e
2982
2995
  const t=await a(r)
2983
2996
  if(!t)throw e
@@ -2994,7 +3007,7 @@ c.isDirectory()?await s(a,i+1):c.name===n&&o.push(`${S.default.relative(t,a)} (r
2994
3007
  await s(t,0)}}catch{}const i=S.default.basename(s.projectPath)
2995
3008
  return void t.status(404).json({success:!1,error:o.length?`'${r}' is not in project '${i}' (default checkout or request worktrees). It may have MOVED -- found instead: ${o.join(", ")}`:`'${r}' is not in project '${i}' (default checkout or request worktrees). If you expected another project, this action server serves '${i}' -- reload so the UI re-discovers the right one.`,data:{suggestions:o,servingProject:i}})}t.status(404).json({success:!1,error:n})}}),e.get("/api/provenance",async(e,t)=>{try{const r="string"==typeof e.query.path?e.query.path:"",n=Math.max(1,parseInt(String(e.query.start??"1"),10)||1),o=parseInt(String(e.query.end??String(n)),10)||n,i=Math.min(n+300,Math.max(n,o)),a="string"==typeof e.query.ref&&/^[\w\/.-]+$/.test(e.query.ref)?e.query.ref:void 0
2996
3009
  if(!r||r.includes("..")||S.default.isAbsolute(r))return void t.status(400).json({success:!1,error:"path must be a repo-relative file path"})
2997
- const c=(0,P.simpleGit)(s.projectPath),l=["blame","--line-porcelain","-L",`${n},${i}`]
3010
+ const c=(0,T.simpleGit)(s.projectPath),l=["blame","--line-porcelain","-L",`${n},${i}`]
2998
3011
  a&&l.push(a),l.push("--",r)
2999
3012
  const d=await c.raw(l),u=[]
3000
3013
  let h=null
@@ -3026,15 +3039,15 @@ if(!r||r.startsWith("..")||S.default.isAbsolute(r)||".alchemy_lab"===r.split(S.d
3026
3039
  const s=await L.note({sessionId:o,requestId:c.requestId,worktree:c.worktree,relPath:r.split(S.default.sep).join("/"),tool:i,..."read"===i&&e.lines?{readRange:e.lines}:{}},l)
3027
3040
  s&&d.push(a.length>1?`${r}:\n${s}`:s)}t.json({success:!0,data:{note:d.length>0?d.join("\n\n"):null}})}catch(e){s.logger.debug("parallel-note failed (the tool result stays as it is)",{error:(0,j.getErrorMessage)(e)}),t.json({success:!0,data:{note:null}})}}),e.get("/api/sibling-overlap",async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0
3028
3041
  if(!r||!/^req\d+$/.test(r))return void t.status(400).json({success:!1,error:"requestId required"})
3029
- const n=s.getProjectId(),o=(0,P.simpleGit)(s.projectPath),i=(await o.raw(["for-each-ref","--format=%(refname:short)","refs/heads/requests/"])).split("\n").map(e=>e.trim().replace(/^requests\//,"")).filter(e=>/^req\d+$/.test(e)&&e!==r),a=new Set(["open","in_progress","ready_for_review","ready_for_merge","resolving_conflicts"]),c=[]
3042
+ const n=s.getProjectId(),o=(0,T.simpleGit)(s.projectPath),i=(await o.raw(["for-each-ref","--format=%(refname:short)","refs/heads/requests/"])).split("\n").map(e=>e.trim().replace(/^requests\//,"")).filter(e=>/^req\d+$/.test(e)&&e!==r),a=new Set(["open","in_progress","ready_for_review","ready_for_merge","resolving_conflicts"]),c=[]
3030
3043
  for(const e of i)try{const t=n?await s.apiClient.get(`/api/projects/${n}/requests/${e}`):null,r=t?.data??t
3031
3044
  r&&a.has(String(r.status))&&c.push({id:e,title:String(r.title??""),status:String(r.status)})}catch{}const l=await(0,D.resolveDefaultBranch)(s.projectPath),d=await(0,b.siblingOverlap)({projectPath:s.projectPath,requestId:r,siblingIds:c.map(e=>e.id),defaultBranch:l,git:o}),u=new Map(c.map(e=>[e.id,e]))
3032
3045
  t.json({success:!0,data:d.map(e=>({...e,siblingTitle:u.get(e.sibling)?.title??"",siblingStatus:u.get(e.sibling)?.status??""}))})}catch(e){s.logger.debug("sibling-overlap lookup failed",{error:(0,j.getErrorMessage)(e)}),t.json({success:!0,data:[]})}}),e.get("/api/request-diff-stats",async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0
3033
3046
  if(!r)return void t.status(400).json({success:!1,error:"requestId required"})
3034
- const n=(0,P.simpleGit)(s.projectPath),{baseSha:o,headSha:i}=await(0,x.resolveRequestDiffRange)(n,r,{status:"in_progress"}),a=o&&i?await(0,x.computeDiffStats)(n,o,i):void 0
3047
+ const n=(0,T.simpleGit)(s.projectPath),{baseSha:o,headSha:i}=await(0,x.resolveRequestDiffRange)(n,r,{status:"in_progress"}),a=o&&i?await(0,x.computeDiffStats)(n,o,i):void 0
3035
3048
  t.json({success:!0,data:a?{...a,baseSha:o,headSha:i}:null})}catch(e){s.logger.debug("request-diff-stats lookup failed",{error:(0,j.getErrorMessage)(e)}),t.json({success:!0,data:null})}}),e.post("/api/verify-preflight",async(e,t)=>{try{const{requestId:r,commands:n}=e.body??{},o=Array.isArray(n)?n.filter(e=>"string"==typeof e&&!!e.trim()):[]
3036
3049
  if(!r||0===o.length)return void t.status(400).json({success:!1,error:"requestId and a non-empty commands array are required"})
3037
- const a=(await(0,P.simpleGit)(s.projectPath).revparse([(0,$.generateRequestBranchName)(r,"")])).trim()
3050
+ const a=(await(0,T.simpleGit)(s.projectPath).revparse([(0,$.generateRequestBranchName)(r,"")])).trim()
3038
3051
  if(!0===e.body?.background){const n=Array.isArray(e.body?.proofs)?e.body.proofs.filter(e=>"string"==typeof e?.criterionId&&"string"==typeof e?.command):[]
3039
3052
  return(0,R.startPendingProof)(r,a,async()=>{const e=await i(s,r,a,o)
3040
3053
  if(!e.available||!Array.isArray(e.results))return null
@@ -3047,7 +3060,7 @@ if(!r||!/^req[0-9]{4,10}$/.test(r))return void t.status(400).json({success:!1,er
3047
3060
  const n=S.default.join(s.projectPath,".alchemy_lab","worktrees"),o=S.default.join(n,r)
3048
3061
  if(o!==S.default.join(n,S.default.basename(o))||!o.startsWith(n+S.default.sep))return void t.status(400).json({success:!1,error:"requestId must name a worktree directly under the project"})
3049
3062
  if(!await A.promises.stat(o).then(e=>e.isDirectory()).catch(()=>!1))return void t.json({success:!0,data:{committed:!1,reason:"no worktree"}})
3050
- const i=(0,P.simpleGit)(o)
3063
+ const i=(0,T.simpleGit)(o)
3051
3064
  await(0,I.assertGitNotUserFolder)(i,"commit of worktree leftovers")
3052
3065
  const a=await i.status()
3053
3066
  if(a.isClean())return void t.json({success:!0,data:{committed:!1,reason:"clean"}})
@@ -3058,13 +3071,13 @@ l&&s.logger.warn("commit-worktree-leftovers held key material back",{requestId:r
3058
3071
  const d=(await i.revparse(["HEAD"])).trim()
3059
3072
  s.logger.info("commit-worktree-leftovers",{requestId:r,files:c.length,sha:d.slice(0,8)}),t.json({success:!0,data:{committed:!0,sha:d,files:c}})}catch(e){s.logger.warn("commit-worktree-leftovers failed",{error:(0,j.getErrorMessage)(e)}),t.json({success:!0,data:{committed:!1,reason:(0,j.getErrorMessage)(e)}})}}),e.get("/api/branch-head",async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0,n=("string"==typeof e.query.branch?e.query.branch:void 0)||(r?(0,$.generateRequestBranchName)(r,""):void 0)
3060
3073
  if(!n)return void t.status(400).json({success:!1,error:"requestId or branch required"})
3061
- const o=(await(0,P.simpleGit)(s.projectPath).revparse([n])).trim(),i=(await(0,P.simpleGit)(s.projectPath).revparse([`${n}^{tree}`]).catch(()=>"")).trim()
3074
+ const o=(await(0,T.simpleGit)(s.projectPath).revparse([n])).trim(),i=(await(0,T.simpleGit)(s.projectPath).revparse([`${n}^{tree}`]).catch(()=>"")).trim()
3062
3075
  t.json({success:!0,data:{branch:n,sha:o,.../^[0-9a-f]{40}$/.test(i)?{tree:i}:{}}})}catch(e){s.logger.debug("branch-head lookup failed",{error:(0,j.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||{}
3063
3076
  if(!r||"string"!=typeof r)return void t.status(400).json({success:!1,error:"worktreePath required"})
3064
3077
  let n=null
3065
3078
  try{const e=s.getProjectId(),t=e?await(0,u.readAllPages)(s.apiClient,t=>`/api/projects/${e}/test-runs?limit=100&offset=${t}`,500):void 0
3066
3079
  t&&(n=t.some(e=>e?.worktreePath===r&&("pending"===e.status||"running"===e.status)))}catch(e){s.logger.debug("release-pool-slot sibling lookup failed",{error:(0,j.getErrorMessage)(e)})}if(!1!==n)return s.logger.info("Pool slot kept for the dispatch idle watch",{worktreePath:r,reason:null===n?"sibling check unavailable":"sibling run still active"}),void t.json({success:!0,released:!1});(0,_.releaseSlot)(r),s.logger.info("Released pool slot via report-back",{worktreePath:r}),t.json({success:!0,released:!0})}catch(e){s.logger.warn("release-pool-slot failed (non-fatal)",{error:(0,j.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,j.getErrorMessage)(e)})}})}
3067
- const u=t(5641),h=t(44600),p=t(96302),f=t(29418),g=t(96396),m=t(36160),w=t(41666),y=d(t(79896)),v=t(51857),b=t(46898),k=t(6156),I=t(75453),S=d(t(16928)),$=t(27478),j=t(58824),E=t(28295),C=t(3811),A=t(79896),_=t(60489),x=t(92456),P=t(83807),T=t(45738),M=t(5452),R=t(65856),O=t(24106),q=t(27584),D=t(52232),N=t(32559),F=t(96511),L=new k.ParallelNotes
3080
+ const u=t(5641),h=t(44600),p=t(96302),f=t(29418),g=t(96396),m=t(36160),w=t(41666),y=d(t(79896)),v=t(51857),b=t(46898),k=t(6156),I=t(75453),S=d(t(16928)),$=t(27478),j=t(58824),E=t(28295),C=t(3811),A=t(79896),_=t(60489),x=t(92456),T=t(83807),P=t(45738),M=t(5452),R=t(65856),O=t(24106),q=t(27584),D=t(52232),N=t(32559),F=t(96511),L=new k.ParallelNotes
3068
3081
  let U=null
3069
3082
  const B=new Map,H=e=>({id:String(e?.localId??e?.id??""),title:String(e?.title??""),status:String(e?.status??""),description:"string"==typeof e?.description?e.description:null})
3070
3083
  exports.COMPLETION_REPLAY_BUDGET_MS=15e4},80211(e,exports,t){"use strict"
@@ -3701,20 +3714,20 @@ let h=null
3701
3714
  const p=new Map},62366(e,exports,t){"use strict"
3702
3715
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
3703
3716
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
3704
- const s=t(60721),n=t(16130),o=t(5641),i=t(17039),a=t(53526),c=t(53691),l=t(8469),d=t(97387),u=t(52232),h=t(61161),p=r(t(7252)),f=r(t(77174)),g=r(t(98577)),m=t(27584),w=t(79896),y=t(90473),v=t(85959),b=t(5452),k=t(21178),I=t(28295),S=t(52640),$=t(95671),j=t(44192),E=t(14139),C=t(41152),A=r(t(16928)),_=r(t(79896)),x=r(t(70857)),P=t(5607),T=t(76634),M=t(31),R=t(44769),O=t(18984),q=t(58824),D=t(75736),N=t(29869),F=t(67078),L=t(60070),U=t(37210),B=t(87227),H=t(82928),G=t(76616),W=t(90629),K=t(42298),z=t(67563),V=t(6850),Y=t(58836),J=t(25146),X=t(23411),Q=t(18815),Z=t(73613),ee=t(49779),te=t(76982),re=t(53340),se=t(89063),ne=t(61292),oe=t(11017),ie=t(28148),ae=t(18649),ce=t(64306),le=t(96685),de=t(49816),ue=t(56844),he=t(84352),pe=t(40713),fe=t(75361),ge=t(72188),me=t(6688),we=t(39068),ye=t(3435),ve=t(48463),be=t(60489),ke=t(24106),Ie=t(81618),Se=t(80211),$e=t(2566),je=t(83807),Ee=t(92456),Ce=t(95040),Ae=t(60355),_e=t(31026),xe=t(73782),Pe=t(53608),Te=t(63484),Me=t(31851),Re=t(60833),Oe=t(42593),qe=t(69706),De=t(11773),Ne=t(45956),Fe=t(36799),Le=t(86116),Ue=new Set(["opencode-proxy","machine-proxy","run-tests","review-fanout"]),Be=65536
3705
- class He{rememberApprovedCommand(e,t,r){const s=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32)||"cmd",n=r?`${r}\0${t}`:t
3717
+ const s=t(60721),n=t(16130),o=t(5641),i=t(17039),a=t(53526),c=t(53691),l=t(8469),d=t(97387),u=t(52232),h=t(61161),p=r(t(7252)),f=r(t(77174)),g=r(t(98577)),m=t(27584),w=t(79896),y=t(90473),v=t(85959),b=t(5452),k=t(21178),I=t(28295),S=t(52640),$=t(95671),j=t(44192),E=t(14139),C=t(41152),A=r(t(16928)),_=r(t(79896)),x=r(t(70857)),T=t(5607),P=t(76634),M=t(31),R=t(44769),O=t(18984),q=t(58824),D=t(75736),N=t(29869),F=t(67078),L=t(60070),U=t(37210),B=t(87227),H=t(82928),G=t(76616),W=t(90629),K=t(42298),z=t(67563),V=t(6850),Y=t(58836),J=t(25146),X=t(23411),Q=t(81570),Z=t(18815),ee=t(73613),te=t(49779),re=t(76982),se=t(53340),ne=t(89063),oe=t(61292),ie=t(11017),ae=t(28148),ce=t(18649),le=t(64306),de=t(96685),ue=t(49816),he=t(56844),pe=t(84352),fe=t(40713),ge=t(75361),me=t(72188),we=t(6688),ye=t(39068),ve=t(3435),be=t(48463),ke=t(60489),Ie=t(24106),Se=t(81618),$e=t(80211),je=t(2566),Ee=t(83807),Ce=t(92456),Ae=t(95040),_e=t(60355),xe=t(31026),Te=t(73782),Pe=t(53608),Me=t(63484),Re=t(31851),Oe=t(60833),qe=t(42593),De=t(69706),Ne=t(11773),Fe=t(45956),Le=t(36799),Ue=t(86116),Be=t(13751),He=new Set(["opencode-proxy","machine-proxy","run-tests","review-fanout"]),Ge=65536
3718
+ class We{rememberApprovedCommand(e,t,r){const s=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32)||"cmd",n=r?`${r}\0${t}`:t
3706
3719
  let o=0
3707
3720
  for(let e=0;e<n.length;e++)o=(o<<5)-o+n.charCodeAt(e)|0
3708
- const i=`ok:${s}-${(o>>>0).toString(36).slice(0,4)}`;(async()=>{(await(0,de.getRegisteredAllows)(this.apiClient,e,r??null).catch(()=>[])).some(e=>e.trim()===t.trim())||(await this.apiClient.post(`/api/projects/${e}/check-commands`,{name:i,command:t,description:`You chose "Always allow this exact command" on an approval card, so this exact command runs ${r?"in this environment":"on your machine"} without asking. Delete it or switch it off to be asked again.`,timeout:120,isActive:!0,...r?{environmentId:r}:{}}),(0,de.invalidateRegisteredAllows)(e))})().catch(e=>this.logger.warn("Failed to remember approved command",{error:String(e)}))}rememberJudgeApprovedCommand(e,t,r){const s=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32)||"cmd"
3721
+ const i=`ok:${s}-${(o>>>0).toString(36).slice(0,4)}`;(async()=>{(await(0,ue.getRegisteredAllows)(this.apiClient,e,r??null).catch(()=>[])).some(e=>e.trim()===t.trim())||(await this.apiClient.post(`/api/projects/${e}/check-commands`,{name:i,command:t,description:`You chose "Always allow this exact command" on an approval card, so this exact command runs ${r?"in this environment":"on your machine"} without asking. Delete it or switch it off to be asked again.`,timeout:120,isActive:!0,...r?{environmentId:r}:{}}),(0,ue.invalidateRegisteredAllows)(e))})().catch(e=>this.logger.warn("Failed to remember approved command",{error:String(e)}))}rememberJudgeApprovedCommand(e,t,r){const s=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32)||"cmd"
3709
3722
  let n=0
3710
3723
  for(let e=0;e<t.length;e++)n=(n<<5)-n+t.charCodeAt(e)|0
3711
- const o=`agent:${s}-${(n>>>0).toString(36).slice(0,4)}`,i=new Date(Date.now()+He.JUDGE_APPROVAL_TTL_MS).toISOString()
3712
- this.apiClient.post(`/api/projects/${e}/check-commands`,{name:o,command:t,description:"Approved by the command judge. Expires on its own; delete it to withdraw the approval sooner.",timeout:120,isActive:!0,expiresAt:i,...r?{environmentId:r}:{}}).then(()=>(0,de.invalidateRegisteredAllows)(e)).catch(e=>this.logger.warn("Failed to remember judge-approved command",{error:String(e)}))}updateAuthToken(e){this.options.authToken=e,this.apiClient.setAuthToken(e),this.configurer&&(this.configurer.authToken=e)}setReconfigureOpenCode(e){this.reconfigureOpenCode=e}setRelaunchOpenCode(e){this.relaunchOpenCode=e}noteServeExit(e){if(e.pid&&this.trackedChildPids.delete(e.pid),e.pid&&this.managedExitPids.delete(e.pid))return void this.logger.info("OpenCode serve exit from a managed restart -- no action",{code:e.code,signal:e.signal,pid:e.pid})
3724
+ const o=`agent:${s}-${(n>>>0).toString(36).slice(0,4)}`,i=new Date(Date.now()+We.JUDGE_APPROVAL_TTL_MS).toISOString()
3725
+ this.apiClient.post(`/api/projects/${e}/check-commands`,{name:o,command:t,description:"Approved by the command judge. Expires on its own; delete it to withdraw the approval sooner.",timeout:120,isActive:!0,expiresAt:i,...r?{environmentId:r}:{}}).then(()=>(0,ue.invalidateRegisteredAllows)(e)).catch(e=>this.logger.warn("Failed to remember judge-approved command",{error:String(e)}))}updateAuthToken(e){this.options.authToken=e,this.apiClient.setAuthToken(e),this.configurer&&(this.configurer.authToken=e)}setReconfigureOpenCode(e){this.reconfigureOpenCode=e}setRelaunchOpenCode(e){this.relaunchOpenCode=e}noteServeExit(e){if(e.pid&&this.trackedChildPids.delete(e.pid),e.pid&&this.managedExitPids.delete(e.pid))return void this.logger.info("OpenCode serve exit from a managed restart -- no action",{code:e.code,signal:e.signal,pid:e.pid})
3713
3726
  if(!this.parkController?.noteServeDeath(`exit code=${e.code} signal=${e.signal}`))return void this.logger.info("OpenCode serve exit observed during managed lifecycle -- no action",{code:e.code,signal:e.signal,pid:e.pid})
3714
3727
  const t=Date.now()
3715
- this.serveDeathTimestamps=this.serveDeathTimestamps.filter(e=>t-e<6e5),this.serveDeathTimestamps.push(t),this.logger.warn("OpenCode serve died unexpectedly -- parked for self-heal; next needing signal revives it",{code:e.code,signal:e.signal,pid:e.pid,unexpectedDeathsLast10m:this.serveDeathTimestamps.length}),this.serveDeathTimestamps.length>=3&&this.logger.error("OpenCode serve is FLAPPING (3+ unexpected deaths in 10 minutes). Revival keeps retrying, but check the opencode-serve log for the underlying crash.",{})}constructor(e,t){this.envSessionStarts=new Map,this.leakedCallNudges=new Map,this.paintCleaned=new Set,this.runningSessions=new Set,this.lastRunningReport="",this.directReportDedup=new Map,this.cachedReceipts=null,this.cachedIntegrations=null,this.lastSettingsSyncAt=0,this.activeMcpCalls=new Map,this.ownerGuidanceStale=!1,this.instructionsChangedAt=0,this.pendingConfigReload=!1,this.pendingConfigReloadSince=0,this.lastConfigReloadAt=0,this.deferredReloadCheckInFlight=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.remoteLastOkAt=null,this.remoteFailures=0,this.remoteLastError=null,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.sseReconnectTimers=new Set,this.sseConnectGeneration=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.recordedUsageMsgs=new Map,this.inFlightProxyActions=0,this.pendingRegularActions=[],this.queuedRegularActionIds=new Set,this.inFlightServeProxyRequests=0,this.serveDeathTimestamps=[],this.managedExitPids=new Set,this.pauseApplied=null,this.childDeathTold=new Set,this.projectSubsystemsStarted=!1,this.lastParkVetoes=[],this.logger=e,this.options=t,(0,qe.installServeAuthFetch)(),this.port=t.port||3456,this.startTime=new Date
3728
+ this.serveDeathTimestamps=this.serveDeathTimestamps.filter(e=>t-e<6e5),this.serveDeathTimestamps.push(t),this.logger.warn("OpenCode serve died unexpectedly -- parked for self-heal; next needing signal revives it",{code:e.code,signal:e.signal,pid:e.pid,unexpectedDeathsLast10m:this.serveDeathTimestamps.length}),this.serveDeathTimestamps.length>=3&&this.logger.error("OpenCode serve is FLAPPING (3+ unexpected deaths in 10 minutes). Revival keeps retrying, but check the opencode-serve log for the underlying crash.",{})}constructor(e,t){this.envSessionStarts=new Map,this.leakedCallNudges=new Map,this.paintCleaned=new Set,this.runningSessions=new Set,this.lastRunningReport="",this.directReportDedup=new Map,this.cachedReceipts=null,this.cachedIntegrations=null,this.lastSettingsSyncAt=0,this.activeMcpCalls=new Map,this.ownerGuidanceStale=!1,this.pendingConfigReload=!1,this.pendingConfigReloadSince=0,this.lastConfigReloadAt=0,this.deferredReloadCheckInFlight=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.remoteLastOkAt=null,this.remoteFailures=0,this.remoteLastError=null,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.sseReconnectTimers=new Set,this.sseConnectGeneration=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.recordedUsageMsgs=new Map,this.inFlightProxyActions=0,this.pendingRegularActions=[],this.queuedRegularActionIds=new Set,this.inFlightServeProxyRequests=0,this.serveDeathTimestamps=[],this.managedExitPids=new Set,this.pauseApplied=null,this.childDeathTold=new Set,this.projectSubsystemsStarted=!1,this.lastParkVetoes=[],this.logger=e,this.options=t,(0,De.installServeAuthFetch)(),this.port=t.port||3456,this.startTime=new Date
3716
3729
  const r=(0,C.createMetricsCollector)(!1)
3717
- if((0,De.setFleetServerKey)(t.projectPath?t.projectPath.split("/").pop():void 0),this.apiClient=new j.BaseApiClient(t.remoteServerUrl,e,r,t.authToken,{timeout:35e3,serverKey:t.projectPath?t.projectPath.split("/").pop():void 0}),t.tokenManager){const e=t.tokenManager
3730
+ if((0,Ne.setFleetServerKey)(t.projectPath?t.projectPath.split("/").pop():void 0),this.apiClient=new j.BaseApiClient(t.remoteServerUrl,e,r,t.authToken,{timeout:35e3,serverKey:t.projectPath?t.projectPath.split("/").pop():void 0}),t.tokenManager){const e=t.tokenManager
3718
3731
  this.apiClient.setTokenRefresher(async()=>{const t=(await e.getToken())?.token
3719
3732
  return t&&t!==this.options.authToken&&this.updateAuthToken(t),t})}this.configurer=new O.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.hookExecutor=new U.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,p.default)(),r=e=>(0,K.isOriginAllowed)(e,{frontendOrigin:this.frontendOrigin,extra:process.env.ALCHEMY_EXTRA_UI_ORIGINS}),s=new Set
3720
3733
  e.use((e,t,r)=>{const n=e.headers.host
@@ -3738,14 +3751,14 @@ const n=this.projectId
3738
3751
  n&&s!==n?t.status(409).json({success:!1,error:`project mismatch: this action server serves ${n} (${A.default.basename(this.options.projectPath)}), not ${s}. Re-discover the action server for your project -- ports shuffle on restart.`,code:"PROJECT_MISMATCH",data:{servingProjectId:n,servingProject:A.default.basename(this.options.projectPath)}}):r()}),e.use((e,t,r)=>{const s=!0===e.headers.accept?.includes("text/event-stream")
3739
3752
  if("GET"===e.method&&d.some(t=>e.path===t||"/api/opencode/session"===t&&e.path===`${t}/`)||s&&e.path.startsWith("/api/opencode"))return"active"!==this.parkController?.getState()&&this.parkController?void t.status(503).json({success:!1,parked:!0,error:"Project is parked (idle); serve is stopped"}):void r()
3740
3753
  i.some(t=>e.path.startsWith(t))?this.ensureServeAlive(`route:${e.path}`).then(()=>r()):r()}),e.get("/health",this.handleHealth.bind(this)),this.options.projectPath&&(0,m.registerAssetRoutes)(e,{projectPath:this.options.projectPath,getProjectId:()=>this.projectId??null,logger:this.logger}),e.post("/api/promise-audit",async(e,t)=>{try{if(!this.projectId||!this.options.projectPath)return void t.status(409).json({success:!1,error:"no project bound to this action server"})
3741
- const r="string"==typeof e.body?.headRef&&e.body.headRef?e.body.headRef:await(0,u.resolveDefaultBranch)(this.options.projectPath),s=await(0,D.checkAllPromises)({repoDir:this.options.projectPath,headRef:r,projectId:this.projectId,http:{get:e=>this.apiClient.get(e),post:(e,t)=>this.apiClient.post(e,t)},logger:this.logger,runChecks:(0,Te.promiseCheckRunner)({projectPath:this.options.projectPath,apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId})})
3742
- t.json({success:!0,data:s})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/admin/shutdown",(e,t)=>{this.logger.warn("Retiring: a newer action server for this project requested takeover",{reason:e.body?.reason}),t.json({ok:!0,pid:process.pid}),setTimeout(()=>{try{process.kill(process.pid,"SIGTERM")}catch{process.exit(0)}},150)}),(0,fe.initGateLogPersistence)(this.options.projectPath),(0,ge.setActivityRequestResolver)(async e=>{const t=this.projectId
3754
+ const r="string"==typeof e.body?.headRef&&e.body.headRef?e.body.headRef:await(0,u.resolveDefaultBranch)(this.options.projectPath),s=await(0,D.checkAllPromises)({repoDir:this.options.projectPath,headRef:r,projectId:this.projectId,http:{get:e=>this.apiClient.get(e),post:(e,t)=>this.apiClient.post(e,t)},logger:this.logger,runChecks:(0,Me.promiseCheckRunner)({projectPath:this.options.projectPath,apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId})})
3755
+ t.json({success:!0,data:s})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/admin/shutdown",(e,t)=>{this.logger.warn("Retiring: a newer action server for this project requested takeover",{reason:e.body?.reason}),t.json({ok:!0,pid:process.pid}),setTimeout(()=>{try{process.kill(process.pid,"SIGTERM")}catch{process.exit(0)}},150)}),(0,ge.initGateLogPersistence)(this.options.projectPath),(0,me.setActivityRequestResolver)(async e=>{const t=this.projectId
3743
3756
  if(!t)throw new Error("no project bound")
3744
3757
  const r=`/api/projects/${t}/session-request-associations`
3745
3758
  let s=!1
3746
3759
  const n=e=>e&&"object"==typeof e&&"data"in e?e.data:e,o={getCurrentForSession:async e=>{try{const t=n(await this.apiClient.get(`${r}/current/${encodeURIComponent(e)}`))
3747
3760
  return null===t?null:t&&"string"==typeof t.requestLocalId?{requestLocalId:t.requestLocalId,ended:!0===t.ended,withdrawn:!0===t.withdrawn}:void 0}catch{return}},getActiveRequestsForSession:async e=>{try{const t=n(await this.apiClient.get(`${r}/by-session/${encodeURIComponent(e)}?activeOnly=true`))
3748
- return Array.isArray(t)?t:[]}catch(e){throw s=!0,e}}},i=await me.sessionIdentity.getSessionRequestId({getCLISessionClient:()=>null,getSessionRequestAssociationClient:()=>o},this.logger,e)
3761
+ return Array.isArray(t)?t:[]}catch(e){throw s=!0,e}}},i=await we.sessionIdentity.getSessionRequestId({getCLISessionClient:()=>null,getSessionRequestAssociationClient:()=>o},this.logger,e)
3749
3762
  if(s)throw new Error("session request lookup failed")
3750
3763
  return i})
3751
3764
  const y=async e=>Promise.all((0,v.listEntries)().map(async t=>{let r,s="",n=0,o=t.finishedAt??t.startedAt
@@ -3754,12 +3767,12 @@ try{const e=await w.promises.stat(t.logPath)
3754
3767
  n=e.size,o=e.mtime.toISOString(),r=await w.promises.open(t.logPath,"r")
3755
3768
  const i=Math.max(0,n-16384),a=Buffer.alloc(n-i)
3756
3769
  await r.read(a,0,a.length,i),s=a.toString("utf-8").split("\n").slice(-50).join("\n")}catch{}finally{await(r?.close().catch(()=>{}))}return{...t,tail:s,sizeBytes:n,modifiedAt:o}}))
3757
- return e.get("/api/commands/running",async(e,t)=>{try{t.json({success:!0,data:await y("0"!==e.query.tail)})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/model-switch",async(e,t)=>{try{t.setHeader("Cache-Control","no-cache"),t.json({success:!0,data:{pending:this.pendingConfigReload,pendingSince:this.pendingConfigReload&&this.pendingConfigReloadSince?this.pendingConfigReloadSince:null,lastReloadAt:this.lastConfigReloadAt||null,capMs:He.RELOAD_DEFER_CAP_MS,busySessionIds:await this.busySessionIds()}})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/envs/running",async(e,t)=>{try{if(t.setHeader("Cache-Control","no-cache"),!this.projectId)return void t.json({success:!0,data:{at:Date.now(),dockerAvailable:!1,hostCores:null,hostMemBytes:null,computerCores:x.default.cpus().length,computerMemBytes:x.default.totalmem(),envs:[]}})
3758
- t.json({success:!0,data:await(0,Se.runningEnvs)(this.projectId)})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/commands/activity",async(e,t)=>{try{t.setHeader("Cache-Control","no-cache"),t.json({success:!0,data:{runs:await y("0"!==e.query.tail),gate:(0,fe.listGateLog)(200),experiments:this.projectId?(0,le.listExperiments)(this.projectId):[],execs:(0,N.listRunningExecs)(),previews:(0,I.listPreviewHandles)()}})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/commands/start",async(e,t)=>{try{const{id:r,name:s,command:n,cwd:o,sessionId:i,startedAt:a,logPath:c,timeoutMs:l}=e.body??{}
3770
+ return e.get("/api/commands/running",async(e,t)=>{try{t.json({success:!0,data:await y("0"!==e.query.tail)})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/model-switch",async(e,t)=>{try{t.setHeader("Cache-Control","no-cache"),t.json({success:!0,data:{pending:this.pendingConfigReload,pendingSince:this.pendingConfigReload&&this.pendingConfigReloadSince?this.pendingConfigReloadSince:null,lastReloadAt:this.lastConfigReloadAt||null,capMs:We.RELOAD_DEFER_CAP_MS,busySessionIds:await this.busySessionIds()}})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/envs/running",async(e,t)=>{try{if(t.setHeader("Cache-Control","no-cache"),!this.projectId)return void t.json({success:!0,data:{at:Date.now(),dockerAvailable:!1,hostCores:null,hostMemBytes:null,computerCores:x.default.cpus().length,computerMemBytes:x.default.totalmem(),envs:[]}})
3771
+ t.json({success:!0,data:await(0,$e.runningEnvs)(this.projectId)})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/commands/activity",async(e,t)=>{try{t.setHeader("Cache-Control","no-cache"),t.json({success:!0,data:{runs:await y("0"!==e.query.tail),gate:(0,ge.listGateLog)(200),experiments:this.projectId?(0,de.listExperiments)(this.projectId):[],execs:(0,N.listRunningExecs)(),previews:(0,I.listPreviewHandles)()}})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/commands/start",async(e,t)=>{try{const{id:r,name:s,command:n,cwd:o,sessionId:i,startedAt:a,logPath:c,timeoutMs:l}=e.body??{}
3759
3772
  if(!(r&&s&&n&&o&&a&&c))return void t.status(400).json({success:!1,error:"missing required fields"});(0,v.addStart)({id:r,name:s,command:n,cwd:o,sessionId:i,startedAt:a,logPath:c,timeoutMs:"number"==typeof l&&l>0?l:void 0}),t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/commands/end",async(e,t)=>{try{const{id:r,exitCode:s,timedOut:n,finishedAt:o}=e.body??{}
3760
3773
  if(!r||"number"!=typeof s||!o)return void t.status(400).json({success:!1,error:"missing required fields"});(0,v.markEnd)({id:r,exitCode:s,timedOut:!!n,finishedAt:o})
3761
3774
  try{const e=(0,v.listEntries)().find(e=>e.id===r)
3762
- if(e){const t=Date.parse(o)-Date.parse(e.startedAt);(0,fe.recordGate)({source:"command",sessionId:e.sessionId,...void 0!==e.requestId?{requestId:e.requestId}:{},environmentName:"Workspace",command:e.command,action:"auto",ruleId:"registered",exit:s,ms:Number.isFinite(t)?t:void 0})}}catch{}t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/auth-status",this.handleAuthStatus.bind(this)),e.post("/api/auth-renew",async(e,t)=>{const r=this.options.tokenManager
3775
+ if(e){const t=Date.parse(o)-Date.parse(e.startedAt);(0,ge.recordGate)({source:"command",sessionId:e.sessionId,...void 0!==e.requestId?{requestId:e.requestId}:{},environmentName:"Workspace",command:e.command,action:"auto",ruleId:"registered",exit:s,ms:Number.isFinite(t)?t:void 0})}}catch{}t.json({success:!0})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/auth-status",this.handleAuthStatus.bind(this)),e.post("/api/auth-renew",async(e,t)=>{const r=this.options.tokenManager
3763
3776
  if(r)try{const e=r.getAuthStatus()
3764
3777
  if(e.isPendingAuth&&e.authUrl)return void t.json({success:!0,opened:(0,W.openBrowser)(e.authUrl),alreadyPending:!0})
3765
3778
  const{authUrl:s}=await r.startAuthenticationFlow()
@@ -3769,7 +3782,7 @@ await this.executeRevive(),t.json({success:!0,data:{port:this.openCodePort??null
3769
3782
  await this.parkController.ensureAlive(),t.json({success:!0,parkState:this.parkController.getState()})}catch(e){this.logger.error("Manual serve revive failed",{error:(0,q.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.get("/api/commits/:sha/diff",async(e,t)=>{const r=String(e.params.sha||"")
3770
3783
  if(!/^[0-9a-f]{7,64}$/i.test(r))return void t.status(400).json({success:!1,error:"sha must be a git commit hash"})
3771
3784
  const s="string"!=typeof e.query.path||e.query.path.includes("..")?void 0:e.query.path
3772
- try{const e=(0,je.simpleGit)(this.options.projectPath),n=524288,o=(await e.raw(["rev-list","--parents","-n","1",r])).trim().split(/\s+/).length>2,i=[...(0,h.documentDiffGitArgs)(),"show","--format=%H%n%an%n%ad%n%s","--patch",...o?["--first-parent","-m"]:[],r]
3785
+ try{const e=(0,Ee.simpleGit)(this.options.projectPath),n=524288,o=(await e.raw(["rev-list","--parents","-n","1",r])).trim().split(/\s+/).length>2,i=[...(0,h.documentDiffGitArgs)(),"show","--format=%H%n%an%n%ad%n%s","--patch",...o?["--first-parent","-m"]:[],r]
3773
3786
  s&&i.push("--",s)
3774
3787
  let a=await(0,h.documentDiffGit)(this.options.projectPath).raw(i)
3775
3788
  const c=a.length>n
@@ -3780,30 +3793,30 @@ try{const s=this.projectId
3780
3793
  if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
3781
3794
  let n=null
3782
3795
  try{const e=await this.apiClient.get(`/api/projects/${s}/requests/${r}`)
3783
- n=e?.data??e??null}catch{}const o=(0,je.simpleGit)(this.options.projectPath),i=async()=>{try{const e=await this.apiClient.get(`/api/projects/${s}/tool-records?requestId=${encodeURIComponent(r)}&sortBy=created_at&sortDirection=asc&limit=100`),t=e?.data??e??[],n=e=>"string"==typeof e&&/^[0-9a-f]{7,64}$/i.test(e)
3784
- return(Array.isArray(t)?t:[]).filter(e=>n(e?.metadata?.commitHash)).map(e=>({commitSha:e.metadata.commitHash,previousSha:n(e?.metadata?.previousCommitHash)?e.metadata.previousCommitHash:void 0}))}catch{return}},{baseSha:a,headSha:c}=await(0,Ee.resolveRequestDiffRange)(o,r,{mergeBaseSha:n?.mergeBaseSha,mergeCommitSha:n?.mergeCommitSha,status:n?.status},i),l=await(0,Ce.requestDocumentEdits)(this.apiClient,s,r).catch(()=>[])
3796
+ n=e?.data??e??null}catch{}const o=(0,Ee.simpleGit)(this.options.projectPath),i=async()=>{try{const e=await this.apiClient.get(`/api/projects/${s}/tool-records?requestId=${encodeURIComponent(r)}&sortBy=created_at&sortDirection=asc&limit=100`),t=e?.data??e??[],n=e=>"string"==typeof e&&/^[0-9a-f]{7,64}$/i.test(e)
3797
+ return(Array.isArray(t)?t:[]).filter(e=>n(e?.metadata?.commitHash)).map(e=>({commitSha:e.metadata.commitHash,previousSha:n(e?.metadata?.previousCommitHash)?e.metadata.previousCommitHash:void 0}))}catch{return}},{baseSha:a,headSha:c}=await(0,Ce.resolveRequestDiffRange)(o,r,{mergeBaseSha:n?.mergeBaseSha,mergeCommitSha:n?.mergeCommitSha,status:n?.status},i),l=await(0,Ae.requestDocumentEdits)(this.apiClient,s,r).catch(()=>[])
3785
3798
  if(!(a&&c||0!==l.length))return void t.status(404).json({success:!1,error:`No diff range found for ${r}.`})
3786
- if("1"===e.query.stats){const e=a&&c?await(0,Ee.computeDiffStats)(o,a,c):void 0
3799
+ if("1"===e.query.stats){const e=a&&c?await(0,Ce.computeDiffStats)(o,a,c):void 0
3787
3800
  if(!e&&0===l.length)return void t.status(404).json({success:!1,error:`No diff stats for ${r}.`})
3788
- const s=(0,Ce.withDocumentStats)(e,l)
3789
- return void t.json({success:!0,data:{requestId:r,baseSha:a,headSha:c,stats:s}})}const d=5e5,u=a&&c?await(0,h.documentDiffGit)(this.options.projectPath).raw([...(0,h.documentDiffGitArgs)(),"diff","-M50%",`${a}..${c}`]):"",p=(0,Ce.withDocumentEdits)(u,l),f=p.length>d
3790
- t.json({success:!0,data:{requestId:r,baseSha:a,headSha:c,diff:f?p.slice(0,d):p,truncated:f}})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),(0,_e.registerLinearRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient,remoteServerUrl:this.options.remoteServerUrl,authToken:this.options.authToken,importLinearIssueAsRequest:e=>this.importLinearIssueAsRequest(e)}),(0,xe.registerLocalModelRoutes)(e),e.get("/api/publish-capabilities",async(e,t)=>{try{const e=await(0,T.getPublishCapabilities)(this.options.projectPath,(0,T.defaultRunner)())
3801
+ const s=(0,Ae.withDocumentStats)(e,l)
3802
+ return void t.json({success:!0,data:{requestId:r,baseSha:a,headSha:c,stats:s}})}const d=5e5,u=a&&c?await(0,h.documentDiffGit)(this.options.projectPath).raw([...(0,h.documentDiffGitArgs)(),"diff","-M50%",`${a}..${c}`]):"",p=(0,Ae.withDocumentEdits)(u,l),f=p.length>d
3803
+ t.json({success:!0,data:{requestId:r,baseSha:a,headSha:c,diff:f?p.slice(0,d):p,truncated:f}})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),(0,xe.registerLinearRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient,remoteServerUrl:this.options.remoteServerUrl,authToken:this.options.authToken,importLinearIssueAsRequest:e=>this.importLinearIssueAsRequest(e)}),(0,Te.registerLocalModelRoutes)(e),e.get("/api/publish-capabilities",async(e,t)=>{try{const e=await(0,P.getPublishCapabilities)(this.options.projectPath,(0,P.defaultRunner)())
3791
3804
  t.json({success:!0,data:e})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/requests/:id/publish-pr",async(e,t)=>{const r=e.params.id
3792
3805
  try{const s=this.projectId
3793
3806
  if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
3794
3807
  const{base:n,draft:o}=e.body??{},i=()=>{const e={"X-Client-Type":"alchemy-local-service"},t=this.apiClient.authToken||this.options.authToken
3795
- return t&&(e.Authorization=`Bearer ${t}`),e},a=(e.body??{}).mode??"pr",c={run:(0,T.defaultRunner)(),fetchRequest:async e=>{const t=await this.apiClient.get(`/api/projects/${s}/requests/${e}`)
3808
+ return t&&(e.Authorization=`Bearer ${t}`),e},a=(e.body??{}).mode??"pr",c={run:(0,P.defaultRunner)(),fetchRequest:async e=>{const t=await this.apiClient.get(`/api/projects/${s}/requests/${e}`)
3796
3809
  return t?.data??t??null},fetchReceiptMarkdown:async e=>{const t=await fetch(`${this.options.remoteServerUrl}/api/projects/${s}/requests/${e}/receipt?format=md`,{headers:i()})
3797
3810
  if(!t.ok)throw new Error(`Receipt fetch failed: HTTP ${t.status}`)
3798
3811
  return await t.text()}},l={},d=[]
3799
- if("pr"===a||"both"===a)try{l.pr=await(0,T.publishRequestPr)({projectPath:this.options.projectPath,requestLocalId:r,base:n,draft:o},c)}catch(e){d.push(`pr: ${(0,q.getErrorMessage)(e)}`)}if("notes"===a||"both"===a)try{l.note=await(0,T.publishReceiptNote)({projectPath:this.options.projectPath,requestLocalId:r},c)}catch(e){d.push(`notes: ${(0,q.getErrorMessage)(e)}`)}if(!l.pr&&!l.note)return void t.status(500).json({success:!1,error:d.join("; ")||"nothing published"})
3800
- d.length&&(l.warnings=d),t.json({success:!0,data:l})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),(0,Ae.registerHooksRoutes)(e,{apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId}),e.get("/api/project-settings",async(e,t)=>{Date.now()-this.lastSettingsSyncAt>2e3&&(this.lastSettingsSyncAt=Date.now(),await this.syncProjectSettings().catch(()=>{})),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null,receipts:this.cachedReceipts,integrations:this.cachedIntegrations}})}),e.get("/api/agents/markdown",(e,t)=>{t.json({success:!0,data:this.configurer.getAgentMarkdowns()})}),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/after-compaction",async(e,t)=>{try{const r=this.projectId
3812
+ if("pr"===a||"both"===a)try{l.pr=await(0,P.publishRequestPr)({projectPath:this.options.projectPath,requestLocalId:r,base:n,draft:o},c)}catch(e){d.push(`pr: ${(0,q.getErrorMessage)(e)}`)}if("notes"===a||"both"===a)try{l.note=await(0,P.publishReceiptNote)({projectPath:this.options.projectPath,requestLocalId:r},c)}catch(e){d.push(`notes: ${(0,q.getErrorMessage)(e)}`)}if(!l.pr&&!l.note)return void t.status(500).json({success:!1,error:d.join("; ")||"nothing published"})
3813
+ d.length&&(l.warnings=d),t.json({success:!0,data:l})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),(0,_e.registerHooksRoutes)(e,{apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId}),e.get("/api/project-settings",async(e,t)=>{Date.now()-this.lastSettingsSyncAt>2e3&&(this.lastSettingsSyncAt=Date.now(),await this.syncProjectSettings().catch(()=>{})),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null,receipts:this.cachedReceipts,integrations:this.cachedIntegrations}})}),e.get("/api/agents/markdown",(e,t)=>{t.json({success:!0,data:this.configurer.getAgentMarkdowns()})}),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/after-compaction",async(e,t)=>{try{const r=this.projectId
3801
3814
  if(!r)return void t.json({success:!0,data:{text:""}})
3802
3815
  const s=await(0,l.afterCompactionBlock)({apiClient:this.apiClient,projectId:r,projectPath:this.options.projectPath},String(e.params.sessionId||""))
3803
3816
  t.json({success:!0,data:{text:s}})}catch(e){t.json({success:!0,data:{text:""}})}}),e.post("/api/sessions/:sessionId/deliver",async(e,r)=>{try{const s=String(e.params.sessionId||""),{message:n,kind:o,partPayload:i,noVerify:l,verifySeconds:d}=e.body||{}
3804
3817
  if(!s||"string"!=typeof n||!n.trim())return void r.status(400).json({success:!1,error:"sessionId and a non-empty message are required"});(0,a.wakeSession)(s)
3805
3818
  const{deliverPrompt:u}=await Promise.resolve().then(()=>t(17039)),h="worker-report"===o&&(0,c.isQuietReporter)(i?.fromSessionId),p=await u({projectPath:this.options.projectPath,projectId:this.projectId,apiClient:this.apiClient,logger:this.logger,cachedDefaultModel:this.cachedDefaultModel},s,n,{kind:o,partPayload:i,noVerify:!!l,...h?{noReply:h}:{},..."number"==typeof d?{verifySeconds:d}:{}})
3806
- p.ok||this.logger.warn("deliver door: delivery failed",{sessionId:s,kind:o,error:p.error,detail:p.detail}),r.json({success:!0,data:p})}catch(e){(0,ye.respondWithError)(r,this.logger,"Deliver to session",e)}}),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
3819
+ p.ok||this.logger.warn("deliver door: delivery failed",{sessionId:s,kind:o,error:p.error,detail:p.detail}),r.json({success:!0,data:p})}catch(e){(0,ve.respondWithError)(r,this.logger,"Deliver to session",e)}}),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
3807
3820
  if(!s)return void t.status(400).json({success:!1,error:"No project"})
3808
3821
  const n=await this.apiClient.get(`/api/projects/${s}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),o=(n?.data||n||[]).map(e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))
3809
3822
  t.json({success:!0,ancestors:o})}catch(e){t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.get("/api/registry/projects",async(e,r)=>{try{const{listLive:e}=await Promise.resolve().then(()=>t(52640))
@@ -3846,23 +3859,23 @@ e(String(n),String(s),o)}if("after"===a&&e.body?.callId&&this.projectId){const{c
3846
3859
  r({projectId:this.projectId,apiClient:this.apiClient,logger:this.logger},{callId:String(e.body.callId),ok:!0,outputPreview:"string"==typeof i?i:void 0})}const c=(0,B.parseToolEvents)({toolName:s,sessionId:n||"",input:o,output:i,timing:a})
3847
3860
  let l=0
3848
3861
  for(const e of c)l+=(await this.hookExecutor.processEvent({eventType:e.eventType,sessionId:n||"",variables:e.variables,timing:e.timing})).triggered
3849
- this.logger.info("tool-event received",{toolName:s,timing:a,sessionId:n||"",events:c.length,triggered:l}),r.json({success:!0,data:{events:c.length,triggered:l}})}catch(e){this.logger.error("tool-event processing failed",{error:(0,q.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/direct-report",this.handleDirectReport.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/setup-environment",async(e,t)=>{try{if(!(0,ye.validateRequest)(e,t,this.projectId))return
3862
+ this.logger.info("tool-event received",{toolName:s,timing:a,sessionId:n||"",events:c.length,triggered:l}),r.json({success:!0,data:{events:c.length,triggered:l}})}catch(e){this.logger.error("tool-event processing failed",{error:(0,q.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/direct-report",this.handleDirectReport.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/setup-environment",async(e,t)=>{try{if(!(0,ve.validateRequest)(e,t,this.projectId))return
3850
3863
  const r=this.projectId
3851
3864
  if(!r)return void t.status(400).json({success:!1,error:"project not registered"})
3852
3865
  const{requestId:s,parentSessionId:n,context:o,settleRequestId:i,callerRequestId:a}=e.body||{}
3853
3866
  let{branchName:c}=e.body||{}
3854
- !c&&i&&s===i&&(c=await(0,ve.resolveSetupBranch)(this.apiClient,r,void 0,"string"==typeof a?a:void 0,this.logger,this.options.projectPath))
3867
+ !c&&i&&s===i&&(c=await(0,be.resolveSetupBranch)(this.apiClient,r,void 0,"string"==typeof a?a:void 0,this.logger,this.options.projectPath))
3855
3868
  let l="interactive"
3856
3869
  try{const e=await this.apiClient.get(`/api/projects/${r}`),t=(e?.data||e)?.metadata?.environmentSetupMode
3857
- "auto"!==t&&"ask"!==t&&"interactive"!==t||(l=t)}catch{}const d=await(0,ce.setupAgentInWorktree)({agent:"setup-architect",promptBuilder:(e,t,r)=>(0,ve.buildEnvironmentSetupPrompt)(e,t,r,l),titleBuilder:e=>e?`[${e}] Environment setup`:"Environment setup",worktreeErrorLabel:"Failed to create setup worktree (environment)",callerContext:o,requestId:s,parentSessionId:n,branchName:c,apiClient:this.apiClient,logger:this.logger,projectId:r,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
3870
+ "auto"!==t&&"ask"!==t&&"interactive"!==t||(l=t)}catch{}const d=await(0,le.setupAgentInWorktree)({agent:"setup-architect",promptBuilder:(e,t,r)=>(0,be.buildEnvironmentSetupPrompt)(e,t,r,l),titleBuilder:e=>e?`[${e}] Environment setup`:"Environment setup",worktreeErrorLabel:"Failed to create setup worktree (environment)",callerContext:o,requestId:s,parentSessionId:n,branchName:c,apiClient:this.apiClient,logger:this.logger,projectId:r,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
3858
3871
  if(!d.success)return void t.status(d.statusCode||500).json({success:!1,error:d.error,...d.existingRequestId?{existingRequestId:d.existingRequestId}:{}})
3859
- "string"==typeof i&&i&&d.sessionId&&this.settleSetupRequestWhenDone(String(d.sessionId),i,"setup-architect"),t.json({success:!0,data:{sessionId:d.sessionId,worktreePath:d.worktreePath,branch:d.branch}})}catch(e){(0,ye.respondWithError)(t,this.logger,"Setup environment",e)}}),e.post("/api/run-tests-now",this.handleRunTestsNow.bind(this)),e.post("/api/promise-checks-at-merge",async(e,t)=>{try{const{requestId:r,baseSha:s,headSha:n,askFromSessionId:o}=e.body??{}
3872
+ "string"==typeof i&&i&&d.sessionId&&this.settleSetupRequestWhenDone(String(d.sessionId),i,"setup-architect"),t.json({success:!0,data:{sessionId:d.sessionId,worktreePath:d.worktreePath,branch:d.branch}})}catch(e){(0,ve.respondWithError)(t,this.logger,"Setup environment",e)}}),e.post("/api/run-tests-now",this.handleRunTestsNow.bind(this)),e.post("/api/promise-checks-at-merge",async(e,t)=>{try{const{requestId:r,baseSha:s,headSha:n,askFromSessionId:o}=e.body??{}
3860
3873
  if(!(r&&s&&n&&this.projectId))return void t.status(400).json({success:!1,error:"requestId, baseSha and headSha are required"})
3861
- const i=await(0,D.checkPromisesAtMerge)({repoDir:this.options.projectPath,baseSha:s,headSha:n,mergedRequestId:r,projectId:this.projectId,http:{get:e=>this.apiClient.get(e),post:(e,t)=>this.apiClient.post(e,t)},runChecks:(0,Te.promiseCheckRunner)({projectPath:this.options.projectPath,apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId}),logger:this.logger,...o?{askFromSessionId:o}:{}})
3874
+ const i=await(0,D.checkPromisesAtMerge)({repoDir:this.options.projectPath,baseSha:s,headSha:n,mergedRequestId:r,projectId:this.projectId,http:{get:e=>this.apiClient.get(e),post:(e,t)=>this.apiClient.post(e,t)},runChecks:(0,Me.promiseCheckRunner)({projectPath:this.options.projectPath,apiClient:this.apiClient,logger:this.logger,getProjectId:()=>this.projectId}),logger:this.logger,...o?{askFromSessionId:o}:{}})
3862
3875
  t.json({success:!0,data:i})}catch(e){t.json({success:!0,data:null,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/main-change-at-landing",async(e,r)=>{try{const{requestId:s,landingSha:n}=e.body??{}
3863
3876
  if(!s||!n||!this.projectId)return void r.status(400).json({success:!1,error:"requestId and landingSha are required"})
3864
3877
  const{mainChangeAtLanding:o}=await Promise.resolve().then(()=>t(69689)),i=await o({projectId:this.projectId,projectPath:this.options.projectPath,logger:this.logger,apiClient:this.apiClient},s,n)
3865
- r.json({success:!0,data:i})}catch(e){r.json({success:!0,data:null,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/flesh-out-request",async(e,t)=>{try{if(!(0,ye.validateRequest)(e,t,this.projectId,["requestId"]))return
3878
+ r.json({success:!0,data:i})}catch(e){r.json({success:!0,data:null,error:(0,q.getErrorMessage)(e)})}}),e.post("/api/flesh-out-request",async(e,t)=>{try{if(!(0,ve.validateRequest)(e,t,this.projectId,["requestId"]))return
3866
3879
  const r=this.projectId
3867
3880
  if(!r)return void t.status(400).json({success:!1,error:"project not registered"})
3868
3881
  const{requestId:s,sourceSessionId:n}=e.body||{}
@@ -3870,23 +3883,23 @@ let o=""
3870
3883
  try{const e=await this.apiClient.get(`/api/projects/${r}/requests/${s}`)
3871
3884
  o=(e?.data||e)?.title||""}catch{}let i=""
3872
3885
  if(n)try{const e=this.getOpenCodeUrl()
3873
- e&&(i=(await(0,Oe.collectMessages)(e,n,this.logger)).filter(e=>e.text&&("user"===e.role||"assistant"===e.role)).slice(-8).map(e=>`${e.role}: ${e.text}`).join("\n").slice(0,4e3))}catch{}const a=["[Planner Flavor: request-shaping] You are fleshing out a DRAFT request in the background (no user is watching this session). This is a scoped single-request job: no plan document, no decomposition \u2014 do not use plan_write.",`Draft request id: ${s}${o?` | one-liner: "${o}"`:""}.`,i?`\nContext from the session that requested this:\n${i}\n`:"","Author ONE focused request:","1. Read the draft with get_request_information.","2. Using the one-liner (and any context above) plus a quick look at the project for grounding, write a clear DESCRIPTION and 1-5 specific, testable ACCEPTANCE CRITERIA (phrased so a confined test run could demonstrate them; any content or decision that must come from the user becomes its own criterion carrying the literal marker [user-input]).","3. Apply them to the draft with request_operations (update). Keep the title unless it is unclear. Do NOT register it and do NOT change its status; it stays a draft for the user to review and register.","4. BOUNDARY: if this is bigger than one focused request (several independent pieces, open-ended), do NOT cram it into one. Leave a short note in the description recommending a quest or a split, and stop.","Work autonomously from the information you have; do not wait for user input. When done, end your session."].filter(Boolean).join("\n"),c=await(0,ie.spawnActionAgent)({agent:"planner",prompt:a,cwd:this.options.projectPath,projectPath:this.options.projectPath,projectId:r,title:o?`Flesh out: ${o.slice(0,60)}`:"Flesh out draft request",parentSessionId:n,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0,skipWorktree:!0,shapingRequestLocalId:s})
3886
+ e&&(i=(await(0,qe.collectMessages)(e,n,this.logger)).filter(e=>e.text&&("user"===e.role||"assistant"===e.role)).slice(-8).map(e=>`${e.role}: ${e.text}`).join("\n").slice(0,4e3))}catch{}const a=["[Planner Flavor: request-shaping] You are fleshing out a DRAFT request in the background (no user is watching this session). This is a scoped single-request job: no plan document, no decomposition \u2014 do not use plan_write.",`Draft request id: ${s}${o?` | one-liner: "${o}"`:""}.`,i?`\nContext from the session that requested this:\n${i}\n`:"","Author ONE focused request:","1. Read the draft with get_request_information.","2. Using the one-liner (and any context above) plus a quick look at the project for grounding, write a clear DESCRIPTION and 1-5 specific, testable ACCEPTANCE CRITERIA (phrased so a confined test run could demonstrate them; any content or decision that must come from the user becomes its own criterion carrying the literal marker [user-input]).","3. Apply them to the draft with request_operations (update). Keep the title unless it is unclear. Do NOT register it and do NOT change its status; it stays a draft for the user to review and register.","4. BOUNDARY: if this is bigger than one focused request (several independent pieces, open-ended), do NOT cram it into one. Leave a short note in the description recommending a quest or a split, and stop.","Work autonomously from the information you have; do not wait for user input. When done, end your session."].filter(Boolean).join("\n"),c=await(0,ae.spawnActionAgent)({agent:"planner",prompt:a,cwd:this.options.projectPath,projectPath:this.options.projectPath,projectId:r,title:o?`Flesh out: ${o.slice(0,60)}`:"Flesh out draft request",parentSessionId:n,apiClient:this.apiClient,logger:this.logger,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0,skipWorktree:!0,shapingRequestLocalId:s})
3874
3887
  if(!c.success||!c.sessionId)return void t.status(500).json({success:!1,error:c.error||"Failed to spawn request-shaping planner"})
3875
- t.json({success:!0,data:{sessionId:c.sessionId,requestId:s}})}catch(e){(0,ye.respondWithError)(t,this.logger,"Flesh out request",e)}}),e.post("/api/environments/verify",async(e,t)=>{const{config:r,smoke:s,branchName:n,requestId:o}=e.body||{}
3888
+ t.json({success:!0,data:{sessionId:c.sessionId,requestId:s}})}catch(e){(0,ve.respondWithError)(t,this.logger,"Flesh out request",e)}}),e.post("/api/environments/verify",async(e,t)=>{const{config:r,smoke:s,branchName:n,requestId:o}=e.body||{}
3876
3889
  if(!r||"object"!=typeof r)return void t.status(400).json({success:!1,error:"config (environment config) is required"})
3877
3890
  const i={composeFile:r.composeFile,gateImage:r.image,connEnv:r.connEnv,install:r.install,artifacts:r.artifacts,shellInit:r.shellInit},a="string"==typeof s&&s.trim()?s.trim():"true",c=this.options.projectPath,l=this.projectId
3878
3891
  if(!l)return void t.status(400).json({success:!1,error:"project not registered"})
3879
3892
  let d
3880
- try{await(0,ve.sweepStaleSetupWorktrees)(c,this.logger)
3881
- const e=await(0,ve.resolveSetupBranch)(this.apiClient,l,n,"string"==typeof o?o:void 0,this.logger,c),s=(0,ve.generateSetupId)()
3882
- d=A.default.join((0,ve.getSetupWorktreesBase)(c),`verify-${s}`),await(0,ve.createSetupWorktree)(c,d,e,this.logger)
3883
- const u=await(0,Ie.withContainerLabels)({"alchemy.purpose":"verify","alchemy.request":"string"==typeof o?o:void 0},()=>(0,ke.runConfinedGate)(d,[{name:"verify",command:a,required:!0,timeout:600}],i,{projectName:`verify-${s}`})),h=!String(r.shellInit??"").trim()&&(0,$e.carriesOwnShellInit)(a),p=h?["The smoke command sources its own setup, and the environment declares no shellInit. This verify proves the smoke command, not the environment: docker exec runs no ENTRYPOINT and reads no login profile, so every LATER command gets a bare PATH. Move the preamble into config.shellInit and verify again with the command alone."]:[]
3884
- h&&this.logger.warn("environment verify passed on a self-sourcing smoke command",{smoke:a}),t.json({success:!0,data:{ok:!u.error&&"passed"===u.status,status:u.status,error:u.error,commands:u.commands,warnings:p}})}catch(e){this.logger.warn("environment verify failed",{error:(0,q.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}finally{if(d&&_.default.existsSync(d))try{await(0,je.simpleGit)(c).raw(["worktree","remove","--force",d])}catch{}}}),(0,Pe.registerRunRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient,logger:this.logger,projectPath:this.options.projectPath,getDefaultModel:()=>this.cachedDefaultModel||void 0,envSessionStarts:this.envSessionStarts,paintCleaned:this.paintCleaned,rememberApprovedCommand:(e,t)=>this.rememberApprovedCommand(e,t),rememberJudgeApprovedCommand:(e,t,r)=>this.rememberJudgeApprovedCommand(e,t,r),visualBaselineDir:()=>this.visualBaselineDir()}),(0,Me.registerProducedFilesRoutes)(e,{projectPath:this.options.projectPath,logger:this.logger,getDocument:async e=>{const t=this.projectId
3893
+ try{await(0,be.sweepStaleSetupWorktrees)(c,this.logger)
3894
+ const e=await(0,be.resolveSetupBranch)(this.apiClient,l,n,"string"==typeof o?o:void 0,this.logger,c),s=(0,be.generateSetupId)()
3895
+ d=A.default.join((0,be.getSetupWorktreesBase)(c),`verify-${s}`),await(0,be.createSetupWorktree)(c,d,e,this.logger)
3896
+ const u=await(0,Se.withContainerLabels)({"alchemy.purpose":"verify","alchemy.request":"string"==typeof o?o:void 0},()=>(0,Ie.runConfinedGate)(d,[{name:"verify",command:a,required:!0,timeout:600}],i,{projectName:`verify-${s}`})),h=!String(r.shellInit??"").trim()&&(0,je.carriesOwnShellInit)(a),p=h?["The smoke command sources its own setup, and the environment declares no shellInit. This verify proves the smoke command, not the environment: docker exec runs no ENTRYPOINT and reads no login profile, so every LATER command gets a bare PATH. Move the preamble into config.shellInit and verify again with the command alone."]:[]
3897
+ h&&this.logger.warn("environment verify passed on a self-sourcing smoke command",{smoke:a}),t.json({success:!0,data:{ok:!u.error&&"passed"===u.status,status:u.status,error:u.error,commands:u.commands,warnings:p}})}catch(e){this.logger.warn("environment verify failed",{error:(0,q.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}finally{if(d&&_.default.existsSync(d))try{await(0,Ee.simpleGit)(c).raw(["worktree","remove","--force",d])}catch{}}}),(0,Pe.registerRunRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient,logger:this.logger,projectPath:this.options.projectPath,getDefaultModel:()=>this.cachedDefaultModel||void 0,envSessionStarts:this.envSessionStarts,paintCleaned:this.paintCleaned,rememberApprovedCommand:(e,t)=>this.rememberApprovedCommand(e,t),rememberJudgeApprovedCommand:(e,t,r)=>this.rememberJudgeApprovedCommand(e,t,r),visualBaselineDir:()=>this.visualBaselineDir()}),(0,Re.registerProducedFilesRoutes)(e,{projectPath:this.options.projectPath,logger:this.logger,getDocument:async e=>{const t=this.projectId
3885
3898
  if(!t||!/^[A-Za-z0-9_-]+$/.test(e))return null
3886
3899
  const r=await this.apiClient.get(`/api/projects/${t}/documents/${e}`).catch(()=>null),s=r?.data??r
3887
- return s&&"string"==typeof s.content?{filename:String(s.filename||`${e}.md`),content:s.content}:null}}),(0,Te.registerWorkspaceRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient,logger:this.logger,projectPath:this.options.projectPath}),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(35317,23)),s=e("npm root -g",{encoding:"utf8"}).trim()
3900
+ return s&&"string"==typeof s.content?{filename:String(s.filename||`${e}.md`),content:s.content}:null}}),(0,Me.registerWorkspaceRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient,logger:this.logger,projectPath:this.options.projectPath}),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(35317,23)),s=e("npm root -g",{encoding:"utf8"}).trim()
3888
3901
  try{_.default.accessSync(s,_.default.constants.W_OK)}catch{return void r.json({success:!1,error:"Permission denied. Run manually: sudo npm install -g alchemylab@latest"})}const{exec:n}=await Promise.resolve().then(()=>t.t(35317,23))
3889
- r.json({success:!0,message:"Update started. The CLI will restart when complete."}),n("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,q.getErrorMessage)(e)})}}),e.use("/api/opencode",(e,t)=>this.handleOpenCodeProxy(e,t)),e}handleHealth(e,t){const r=this.remoteLastOkAt?Date.now()-this.remoteLastOkAt:null,s=Date.now()-this.startTime.getTime(),n=this.remoteFailures>0?"degraded":null!==r?r<He.REMOTE_STALE_MS?"ok":"degraded":s<He.REMOTE_STALE_MS?"unknown":"degraded",o={url:this.options.remoteServerUrl??process.env.REMOTE_SERVER_URL??null,state:n,ok:"ok"===n,consecutiveFailures:this.remoteFailures,lastOkAt:this.remoteLastOkAt?new Date(this.remoteLastOkAt).toISOString():null,failingForMs:"degraded"===n?r??s:null,lastError:this.remoteLastError,polling:this.isPolling}
3902
+ r.json({success:!0,message:"Update started. The CLI will restart when complete."}),n("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,q.getErrorMessage)(e)})}}),e.use("/api/opencode",(e,t)=>this.handleOpenCodeProxy(e,t)),e}handleHealth(e,t){const r=this.remoteLastOkAt?Date.now()-this.remoteLastOkAt:null,s=Date.now()-this.startTime.getTime(),n=this.remoteFailures>0?"degraded":null!==r?r<We.REMOTE_STALE_MS?"ok":"degraded":s<We.REMOTE_STALE_MS?"unknown":"degraded",o={url:this.options.remoteServerUrl??process.env.REMOTE_SERVER_URL??null,state:n,ok:"ok"===n,consecutiveFailures:this.remoteFailures,lastOkAt:this.remoteLastOkAt?new Date(this.remoteLastOkAt).toISOString():null,failingForMs:"degraded"===n?r??s:null,lastError:this.remoteLastError,polling:this.isPolling}
3890
3903
  t.json({status:"degraded"===n&&this.isPolling?"degraded":"healthy",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),build:(0,d.readBuildInfo)(),projectId:this.projectId,projectPath:this.options.projectPath,remote:o,parkState:this.parkController?.getState(),parkVetoes:this.parkController?this.lastParkVetoes:void 0,parkIdleForMs:this.parkController?.idleForMs(),timestamp:(new Date).toISOString()})}async handleAuthStatus(e,t){const r=this.options.tokenManager
3891
3904
  if(r)try{const e=await r.hasValidToken(),s=r.getAuthStatus(),n=e?await r.getToken():null
3892
3905
  t.json({authenticated:e,isPendingAuth:s.isPendingAuth,expiresAt:n?.expiresAt??null})}catch(e){this.logger.warn("auth-status check failed",{error:(0,q.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,parkState:this.parkController?.getState(),parkVetoes:this.parkController?this.lastParkVetoes:void 0,parkIdleForMs:this.parkController?.idleForMs(),port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const r=A.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
@@ -3895,7 +3908,7 @@ const e=_.default.readFileSync(r,"utf-8"),s=JSON.parse(e)
3895
3908
  t.json({port:s.port,pid:s.pid,startedAt:s.startedAt,baseUrl:`http://localhost:${s.port}`,directory:this.options.projectPath,proxyEnabled:!0,parkState:this.parkController?.getState()??"active"})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,q.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,q.getErrorMessage)(e)})}}updateDefaultModel(e){return void 0===e?Promise.resolve():(e!==this.cachedDefaultModel&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e||""),this.stuckSessionWatchdog?.capacityChanged("model-changed")),e&&this.projectId&&this.lastAgentResyncModel!==e?this.resyncAgentMarkdowns().catch(e=>this.logger.debug("Background agent markdown re-sync failed",{error:(0,q.getErrorMessage)(e)})):Promise.resolve())}handleMcpToolCallEvent(e,t){this.parkController?.noteActivity(),"start"!==e?(this.activeMcpCalls.delete(t),0===this.activeMcpCalls.size&&this.pendingConfigReload&&this.fireDeferredReloadIfIdle("mcp-idle")):this.activeMcpCalls.set(t,Date.now())}startMcpStaleCleanup(){this.mcpStaleCleanupInterval||(this.mcpStaleCleanupInterval=setInterval(()=>{const e=Date.now()-12e4
3896
3909
  let t=0
3897
3910
  for(const[r,s]of this.activeMcpCalls)s<e&&(this.activeMcpCalls.delete(r),t++)
3898
- t>0&&this.logger.warn("Cleaned up stale MCP tool-call tracking entries",{removed:t}),this.pendingConfigReload&&this.fireDeferredReloadIfIdle("stale-cleanup")},6e4))}async anySessionBusy(){if(Date.now()-(0,i.lastPromptDeliveredAt)()<He.PROMPT_DELIVERY_BUSY_MS)return!0
3911
+ t>0&&this.logger.warn("Cleaned up stale MCP tool-call tracking entries",{removed:t}),this.pendingConfigReload&&this.fireDeferredReloadIfIdle("stale-cleanup")},6e4))}async anySessionBusy(){if(Date.now()-(0,i.lastPromptDeliveredAt)()<We.PROMPT_DELIVERY_BUSY_MS)return!0
3899
3912
  try{const e=this.getOpenCodeUrl()
3900
3913
  if(!e)return!1
3901
3914
  const t=await fetch(`${e}/session/status`,{signal:AbortSignal.timeout(5e3)})
@@ -3906,9 +3919,9 @@ if(!e)return[]
3906
3919
  const t=await fetch(`${e}/session/status`,{signal:AbortSignal.timeout(5e3)})
3907
3920
  if(!t.ok)return[]
3908
3921
  const r=await t.json()
3909
- return Object.entries(r).filter(([,e])=>!!e&&"idle"!==e.type).map(([e])=>e)}catch{return[]}}notePendingConfigReload(e,t){this.pendingConfigReload||(this.pendingConfigReload=!0,this.pendingConfigReloadSince=Date.now()),this.startPendingReloadRecheck(),this.logger.info("Deferring OpenCode config reload until MCP calls and session turns are idle",{context:e,inFlightMcp:t})}startPendingReloadRecheck(){this.pendingReloadRecheckInterval||(this.pendingReloadRecheckInterval=setInterval(()=>{this.fireDeferredReloadIfIdle("recheck")},He.RELOAD_RECHECK_MS),this.pendingReloadRecheckInterval.unref?.())}stopPendingReloadRecheck(){this.pendingReloadRecheckInterval&&(clearInterval(this.pendingReloadRecheckInterval),this.pendingReloadRecheckInterval=void 0)}async fireDeferredReloadIfIdle(e){if(this.pendingConfigReload){if(!this.deferredReloadCheckInFlight){this.deferredReloadCheckInFlight=!0
3922
+ return Object.entries(r).filter(([,e])=>!!e&&"idle"!==e.type).map(([e])=>e)}catch{return[]}}notePendingConfigReload(e,t){this.pendingConfigReload||(this.pendingConfigReload=!0,this.pendingConfigReloadSince=Date.now()),this.startPendingReloadRecheck(),this.logger.info("Deferring OpenCode config reload until MCP calls and session turns are idle",{context:e,inFlightMcp:t})}startPendingReloadRecheck(){this.pendingReloadRecheckInterval||(this.pendingReloadRecheckInterval=setInterval(()=>{this.fireDeferredReloadIfIdle("recheck")},We.RELOAD_RECHECK_MS),this.pendingReloadRecheckInterval.unref?.())}stopPendingReloadRecheck(){this.pendingReloadRecheckInterval&&(clearInterval(this.pendingReloadRecheckInterval),this.pendingReloadRecheckInterval=void 0)}async fireDeferredReloadIfIdle(e){if(this.pendingConfigReload){if(!this.deferredReloadCheckInFlight){this.deferredReloadCheckInFlight=!0
3910
3923
  try{const t=Date.now()-this.pendingConfigReloadSince
3911
- if(t<He.RELOAD_DEFER_CAP_MS){if(this.activeMcpCalls.size>0)return
3924
+ if(t<We.RELOAD_DEFER_CAP_MS){if(this.activeMcpCalls.size>0)return
3912
3925
  if(await this.anySessionBusy())return}else this.logger.warn("Config reload deferred past cap; firing despite activity (staleness beats permanent deferral)",{trigger:e,waitedMs:t})
3913
3926
  this.pendingConfigReload=!1,this.stopPendingReloadRecheck(),this.logger.info("Firing deferred OpenCode config reload",{trigger:e,waitedMs:t}),await this.reloadOpenCodeConfigNow(`deferred-${e}`)}finally{this.deferredReloadCheckInFlight=!1}}}else this.stopPendingReloadRecheck()}async applyClaudeThinking(e){const t=this.claudeThinkingSeen
3914
3927
  this.claudeThinkingSeen=e,void 0!==t&&t!==e&&this.projectId&&(this.logger.info("Claude thinking setting changed; re-syncing agents",{from:t,to:e}),await this.resyncAgentMarkdowns().catch(e=>this.logger.debug("Agent re-sync after the Claude thinking change failed",{error:(0,q.getErrorMessage)(e)})))}async applyPause(e){const t=this.getOpenCodeUrl()?.replace(/\/$/,"")
@@ -3916,20 +3929,20 @@ if(t&&(this.pauseApplied?.url!==t||this.pauseApplied.paused!==e))try{const r=awa
3916
3929
  if(!r.ok)return void this.logger.warn("The serve did not take the pause",{paused:e,status:r.status})
3917
3930
  const s=await r.json().catch(()=>({}))
3918
3931
  this.pauseApplied={url:t,paused:e}
3919
- const n=(0,Fe.setProjectPaused)(e)
3932
+ const n=(0,Le.setProjectPaused)(e)
3920
3933
  if(e){const e=Array.isArray(s.stopped)?s.stopped:[]
3921
3934
  let t=0
3922
3935
  for(const r of e)t+=await(0,N.cancelRunningExecs)({sessionId:r}).catch(()=>0)
3923
- return void this.logger.info("Project paused: every turn stopped and held",{stopped:e.length,commandsCancelled:t})}(0,Le.extendChatWatches)(n)
3936
+ return void this.logger.info("Project paused: every turn stopped and held",{stopped:e.length,commandsCancelled:t})}(0,Ue.extendChatWatches)(n)
3924
3937
  const o=Array.isArray(s.waiting)?s.waiting:[],a={projectPath:this.options.projectPath,projectId:this.projectId,apiClient:this.apiClient,logger:this.logger,cachedDefaultModel:this.cachedDefaultModel}
3925
3938
  let c=0
3926
- for(const e of o)(await(0,i.deliverPrompt)(a,e,Fe.RESUME_TEXT,{kind:"inserted-prompt"}).catch(()=>({ok:!1}))).ok?c++:this.logger.warn("A session held by the pause could not be woken",{sessionId:e})
3939
+ for(const e of o)(await(0,i.deliverPrompt)(a,e,Le.RESUME_TEXT,{kind:"inserted-prompt"}).catch(()=>({ok:!1}))).ok?c++:this.logger.warn("A session held by the pause could not be woken",{sessionId:e})
3927
3940
  this.logger.info("Project resumed",{waiting:o.length,woken:c,pausedForMs:n})}catch(t){this.logger.warn("Could not apply the pause to the serve",{paused:e,error:(0,q.getErrorMessage)(t)})}}async reloadOpenCodeConfigNow(e){try{const t=this.getOpenCodeUrl()
3928
3941
  if(!t)return!1
3929
3942
  const r=await fetch(`${t}/config/reload`,{method:"POST",signal:AbortSignal.timeout(5e3)})
3930
3943
  if(r.ok){this.logger.info("Triggered OpenCode config reload",{context:e})
3931
3944
  const t=await r.json().catch(()=>null)
3932
- return!0!==t?.deferred&&(0,s.noteServeLoadedLogins)(),this.stuckSessionWatchdog?.capacityChanged(`config-reloaded:${e}`),this.instructionsChangedAt=Date.now(),this.lastConfigReloadAt=Date.now(),!0}return this.logger.warn("OpenCode config reload returned non-OK status",{context:e,status:r.status}),!1}catch(t){return this.logger.warn("Could not reload OpenCode config",{context:e,error:(0,q.getErrorMessage)(t)}),!1}}async requestConfigReload(e){const t=this.activeMcpCalls.size
3945
+ return!0!==t?.deferred&&(0,s.noteServeLoadedLogins)(),this.stuckSessionWatchdog?.capacityChanged(`config-reloaded:${e}`),(0,Q.noteConfigReloaded)(this.options.projectPath),this.lastConfigReloadAt=Date.now(),!0}return this.logger.warn("OpenCode config reload returned non-OK status",{context:e,status:r.status}),!1}catch(t){return this.logger.warn("Could not reload OpenCode config",{context:e,error:(0,q.getErrorMessage)(t)}),!1}}async requestConfigReload(e){const t=this.activeMcpCalls.size
3933
3946
  return t>0||await this.anySessionBusy()?(this.notePendingConfigReload(e,t),"deferred"):await this.reloadOpenCodeConfigNow(e)?"reloaded":"failed"}async syncOpencodeConfigModel(e){const t=A.default.join(this.options.projectPath,"opencode.json")
3934
3947
  try{const r=await _.default.promises.readFile(t,"utf8"),s=JSON.parse(r)
3935
3948
  if(!s||"object"!=typeof s||s.model===e)return
@@ -3938,9 +3951,9 @@ const e=await this.configurer.fetchAgentsFromRemote(this.projectId)
3938
3951
  if(!e?.length)return
3939
3952
  const t=await this.configurer.fetchProjectFromRemote(this.projectId)
3940
3953
  this.ownerGuidanceStale=!1
3941
- const r=await this.configurer.agentPreambleFor(this.projectId,t?.metadata?.globalSystemPrompt),s=A.default.join(this.options.projectPath,".opencode","agent"),n=this.cachedDefaultModel||"anthropic/claude-opus-4-5",o=await this.configurer.resolveWorkflowEffort(this.projectId),i=await this.mcpGrantInputs(this.projectId),a=this.agentConfigFingerprint()
3954
+ const r=await this.configurer.agentPreambleFor(this.projectId,t?.metadata?.globalSystemPrompt),s=A.default.join(this.options.projectPath,".opencode","agent"),n=this.cachedDefaultModel||$.FLEET_DEFAULT_MODEL,o=await this.configurer.resolveWorkflowEffort(this.projectId),i=await this.mcpGrantInputs(this.projectId),a=this.agentConfigFingerprint()
3942
3955
  this.configurer.clearAgentMarkdowns()
3943
- for(const s of e)s.enabled&&this.configurer.setAgentMarkdown(s.name,this.configurer.generateAgentMarkdown(s,"alchemy",n,r,{effort:o,extraTools:(0,ee.agentToolGrants)(i.servers,s.name,i.permissions),limits:(0,$.modelLimitsOf)(t?.metadata),claudeThinking:(0,O.claudeThinkingOf)(t?.metadata)}))
3956
+ for(const s of e)s.enabled&&this.configurer.setAgentMarkdown(s.name,this.configurer.generateAgentMarkdown(s,"alchemy",n,r,{effort:o,extraTools:(0,te.agentToolGrants)(i.servers,s.name,i.permissions),limits:(0,$.modelLimitsOf)(t?.metadata),claudeThinking:(0,O.claudeThinkingOf)(t?.metadata)}))
3944
3957
  this.configurer.setAgentMarkdown("explore",this.configurer.buildExploreMarkdown(r)),await this.configurer.removeOnDiskAgentFiles(s),this.lastAgentResyncModel=n,await this.syncOpencodeConfigModel(n),this.logger.info("Re-synced agent markdowns after model change",{model:n,count:e.length}),this.agentConfigFingerprint()!==a?(this.stuckSessionWatchdog?.capacityChanged("agents-resynced"),await this.requestConfigReload("agent-resync")):this.logger.info("Agent definitions unchanged; no config reload",{context:"agent-resync"})}async checkOwnerGuidanceApply(){if(!this.ownerGuidanceStale||!this.projectId)return
3945
3958
  const e=this.getOpenCodeUrl()
3946
3959
  let t=!1,r=null
@@ -3949,8 +3962,8 @@ if(!s.ok)return
3949
3962
  const o=await s.json(),i=n.ok?await n.json():{}
3950
3963
  t=Object.values(i??{}).some(e=>"busy"===e?.type)||this.activeMcpCalls.size>0
3951
3964
  for(const e of o){const t=Number(e.time?.updated??e.time?.created??0)
3952
- t&&(null===r||t>r)&&(r=t)}}catch{return}"apply"===(0,Ne.ownerGuidanceApplyDecision)({stale:this.ownerGuidanceStale,serveUp:!!e,anyBusy:t,lastTurnMs:r,now:Date.now(),coldMs:(0,Ne.ownerGuidanceColdMs)()})&&(this.logger.info("Applying your changed guidance now: nothing here has had a turn for longer than a cache lives, so no cache is lost",{quietMs:(0,Ne.ownerGuidanceColdMs)()}),await this.resyncAgentMarkdowns().catch(e=>this.logger.warn("Applying the changed guidance failed; it stays pending",{error:(0,q.getErrorMessage)(e)})))}async mcpGrantInputs(e){const t=await this.configurer.fetchMcpServersFromRemote(e).catch(()=>[]),r=await this.configurer.fetchDefaultWorkflowFromRemote(e).catch(()=>null)
3953
- return{servers:t,permissions:r?.connectionPermissions??null}}agentConfigFingerprint(){const e=(0,te.createHash)("sha256")
3965
+ t&&(null===r||t>r)&&(r=t)}}catch{return}"apply"===(0,Fe.ownerGuidanceApplyDecision)({stale:this.ownerGuidanceStale,serveUp:!!e,anyBusy:t,lastTurnMs:r,now:Date.now(),coldMs:(0,Fe.ownerGuidanceColdMs)()})&&(this.logger.info("Applying your changed guidance now: nothing here has had a turn for longer than a cache lives, so no cache is lost",{quietMs:(0,Fe.ownerGuidanceColdMs)()}),await this.resyncAgentMarkdowns().catch(e=>this.logger.warn("Applying the changed guidance failed; it stays pending",{error:(0,q.getErrorMessage)(e)})))}async mcpGrantInputs(e){const t=await this.configurer.fetchMcpServersFromRemote(e).catch(()=>[]),r=await this.configurer.fetchDefaultWorkflowFromRemote(e).catch(()=>null)
3966
+ return{servers:t,permissions:r?.connectionPermissions??null}}agentConfigFingerprint(){const e=(0,re.createHash)("sha256")
3954
3967
  for(const t of[...this.configurer.getAgentMarkdowns()].sort((e,t)=>e.name.localeCompare(t.name)))e.update(t.name).update("\0").update(t.markdown).update("\x01")
3955
3968
  try{e.update(_.default.readFileSync(A.default.join(this.options.projectPath,"opencode.json"),"utf-8"))}catch{}return e.digest("hex")}async handleSyncAgents(e,t){try{const e=this.projectId
3956
3969
  if(!e)return void t.status(400).json({success:!1,error:"Project not registered"})
@@ -3963,10 +3976,10 @@ const n=await this.configurer.agentPreambleFor(e,s?.metadata?.globalSystemPrompt
3963
3976
  n?this.logger.info("Using global system prompt from project metadata",{projectId:e,promptLength:n.length}):this.logger.debug("No global system prompt found in project metadata")
3964
3977
  const o=A.default.join(this.options.projectPath,".opencode","agent")
3965
3978
  await _.default.promises.mkdir(o,{recursive:!0})
3966
- const i="alchemy",a=this.cachedDefaultModel||"anthropic/claude-opus-4-5",c=r.filter(e=>e.enabled),l=await this.configurer.resolveWorkflowEffort(e),d=await this.mcpGrantInputs(e),u=this.agentConfigFingerprint()
3979
+ const i="alchemy",a=this.cachedDefaultModel||$.FLEET_DEFAULT_MODEL,c=r.filter(e=>e.enabled),l=await this.configurer.resolveWorkflowEffort(e),d=await this.mcpGrantInputs(e),u=this.agentConfigFingerprint()
3967
3980
  this.configurer.clearAgentMarkdowns()
3968
3981
  let h=0
3969
- for(const e of c)try{this.configurer.setAgentMarkdown(e.name,this.configurer.generateAgentMarkdown(e,i,a,n,{effort:l,extraTools:(0,ee.agentToolGrants)(d.servers,e.name,d.permissions),limits:(0,$.modelLimitsOf)(s?.metadata),claudeThinking:(0,O.claudeThinkingOf)(s?.metadata)})),h++}catch(t){this.logger.warn("Agent markdown not generated",{name:e.name,error:(0,q.getErrorMessage)(t)})}this.configurer.setAgentMarkdown("explore",this.configurer.buildExploreMarkdown(n)),await this.configurer.removeOnDiskAgentFiles(o),this.lastAgentResyncModel=a
3982
+ for(const e of c)try{this.configurer.setAgentMarkdown(e.name,this.configurer.generateAgentMarkdown(e,i,a,n,{effort:l,extraTools:(0,te.agentToolGrants)(d.servers,e.name,d.permissions),limits:(0,$.modelLimitsOf)(s?.metadata),claudeThinking:(0,O.claudeThinkingOf)(s?.metadata)})),h++}catch(t){this.logger.warn("Agent markdown not generated",{name:e.name,error:(0,q.getErrorMessage)(t)})}this.configurer.setAgentMarkdown("explore",this.configurer.buildExploreMarkdown(n)),await this.configurer.removeOnDiskAgentFiles(o),this.lastAgentResyncModel=a
3970
3983
  try{const t=A.default.join(this.options.projectPath,"opencode.json")
3971
3984
  if(_.default.existsSync(t)){const s=_.default.readFileSync(t,"utf-8"),n=JSON.parse(s),o=await this.configurer.fetchDefaultWorkflowFromRemote(e),i=o?.bashPermissions||{}
3972
3985
  if(n.agent){for(const e of Object.values(n.agent))e&&"object"==typeof e&&"prompt"in e&&delete e.prompt
@@ -4022,18 +4035,18 @@ if(!t||t===r)throw e
4022
4035
  l=`${t}/${s}${n}`,this.logger.info("OpenCode proxy: retrying with fresh port",{oldUrl:r,newUrl:t}),c=await fetch(l,a)}if(204===c.status||"0"===c.headers.get("content-length"))return void t.status(c.status).end()
4023
4036
  const d=c.headers.get("content-type")||""
4024
4037
  let u
4025
- try{u=d.includes("application/json")?await c.json():await c.text()}catch{return void t.status(c.status).end()}t.status(c.status),d&&t.setHeader("Content-Type",d),"string"==typeof u?t.send(u):t.json(u)}finally{this.inFlightServeProxyRequests--}}catch(r){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,q.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,q.getErrorMessage)(r)})}}async handleSpawnPlanner(e,t){try{if(!(0,ye.validateRequest)(e,t,this.projectId,["planId","prompt"]))return
4038
+ try{u=d.includes("application/json")?await c.json():await c.text()}catch{return void t.status(c.status).end()}t.status(c.status),d&&t.setHeader("Content-Type",d),"string"==typeof u?t.send(u):t.json(u)}finally{this.inFlightServeProxyRequests--}}catch(r){this.openCodePort=null,this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,q.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,q.getErrorMessage)(r)})}}async handleSpawnPlanner(e,t){try{if(!(0,ve.validateRequest)(e,t,this.projectId,["planId","prompt"]))return
4026
4039
  const{agent:r,planId:s,prompt:n,title:o,parentSessionId:i,flavor:a}=e.body||{},c="auto-planner"===r?"planner":r,l="auto-planner"===r||"autonomous"===a?"autonomous":void 0,d=new Set(["planner"])
4027
4040
  if(c&&!d.has(c))return void t.status(400).json({success:!1,error:`agent must be one of: ${[...d].join(", ")}`})
4028
- const u=await(0,ae.spawnPlannerWithDoc)({agent:c||"planner",flavor:l,planId:s,prompt:n,title:o||"Planner",parentSessionId:i,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e)})
4041
+ const u=await(0,ce.spawnPlannerWithDoc)({agent:c||"planner",flavor:l,planId:s,prompt:n,title:o||"Planner",parentSessionId:i,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e)})
4029
4042
  if(!u.success||!u.sessionId)return void t.status(500).json({success:!1,error:u.error||"spawn failed"})
4030
- t.json({success:!0,data:{sessionId:u.sessionId,planId:u.planId,pid:u.pid}})}catch(e){(0,ye.respondWithError)(t,this.logger,"Spawn planner",e)}}async ensureHubAlchemist(){try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/persistent/alchemist`),t=e?.data??e
4031
- if(t&&t.externalSessionId){const e=(0,Re.getOpenCodeUrlFromConfig)(this.options.projectPath),r=e?await this.serveHasSession(e,String(t.externalSessionId)):null
4043
+ t.json({success:!0,data:{sessionId:u.sessionId,planId:u.planId,pid:u.pid}})}catch(e){(0,ve.respondWithError)(t,this.logger,"Spawn planner",e)}}async ensureHubAlchemist(){try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/persistent/alchemist`),t=e?.data??e
4044
+ if(t&&t.externalSessionId){const e=(0,Oe.getOpenCodeUrlFromConfig)(this.options.projectPath),r=e?await this.serveHasSession(e,String(t.externalSessionId)):null
4032
4045
  if(!1!==r)return void this.logger.debug("Hub alchemist already present",{sessionId:t.id,verified:!0===r})
4033
4046
  this.logger.warn("Hub alchemist row points at a session this serve does not have; ending it and creating a new hub",{sessionId:t.id,externalSessionId:t.externalSessionId}),await this.apiClient.patch(`/api/projects/${this.projectId}/cli-sessions/${t.id}`,{status:"completed"}).catch(e=>this.logger.warn("could not end the orphan hub row",{sessionId:t.id,error:(0,q.getErrorMessage)(e)}))}if(!this.projectId)return
4034
- const r=new ne.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,getDefaultModel:()=>this.cachedDefaultModel||void 0}),s="Hub",n=await r.prepareSession({agent:"alchemist",prompt:s,title:s,projectPath:this.options.projectPath})
4047
+ const r=new oe.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,getDefaultModel:()=>this.cachedDefaultModel||void 0}),s="Hub",n=await r.prepareSession({agent:"alchemist",prompt:s,title:s,projectPath:this.options.projectPath})
4035
4048
  this.logger.info("Hub alchemist created at boot (no browser required)",{sessionId:n?.sessionId??null})}catch(e){this.logger.warn("Hub alchemist bootstrap failed; project cannot be steered until one exists",{error:(0,q.getErrorMessage)(e)})}}async reapVanishedSessions(){if(!this.projectId)return
4036
- const e=(0,Re.getOpenCodeUrlFromConfig)(this.options.projectPath)
4049
+ const e=(0,Oe.getOpenCodeUrlFromConfig)(this.options.projectPath)
4037
4050
  if(!e)return
4038
4051
  let t
4039
4052
  try{const r=await fetch(`${e.replace(/\/$/,"")}/session?limit=5000`,{signal:AbortSignal.timeout(15e3)})
@@ -4048,9 +4061,9 @@ for(const e of o){s++
4048
4061
  const n=e?.externalSessionId??e?.external_session_id
4049
4062
  n&&!t.has(String(n))&&r.push({id:String(e.id),ext:String(n)})}if(o.length<100)break}}catch(e){return void this.logger.warn("vanished-session sweep could not read the session list; ending nothing",{error:(0,q.getErrorMessage)(e)})}let n=0,o=0
4050
4063
  for(const e of r)try{await this.apiClient.patch(`/api/projects/${this.projectId}/cli-sessions/${e.id}`,{status:"completed"}),n++}catch(t){o++,o<=3&&this.logger.warn("could not end a vanished session row",{sessionId:e.id,externalSessionId:e.ext,error:(0,q.getErrorMessage)(t)})}o>0&&this.logger.warn("vanished-session sweep could not end every row it meant to",{ended:n,failed:o,scanned:s}),n>0&&this.logger.info("ended rows for sessions this serve no longer has",{ended:n,scanned:s,live:t.size})}async serveHasSession(e,t){try{const r=await fetch(`${e.replace(/\/$/,"")}/session/${encodeURIComponent(t)}`,{signal:AbortSignal.timeout(5e3)})
4051
- return!!r.ok||404!==r.status&&null}catch{return null}}async handleSpawnSession(e,t){try{if(!(0,ye.validateRequest)(e,t,this.projectId,["agent","prompt"]))return
4052
- const{agent:r,prompt:s,requestId:n,parentSessionId:o,title:i,allowParallel:a,callerAgentType:c,shapingRequestLocalId:l,userOpened:d}=e.body,u=this.projectId,h=new ne.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:u,getDefaultModel:()=>this.cachedDefaultModel||void 0}),p=await h.prepareSession({agent:r,prompt:s,projectPath:this.options.projectPath,requestId:n,parentSessionId:o,title:i,allowParallel:a,callerAgentType:c,..."string"==typeof l&&/^req\d+$/.test(l)?{shapingRequestLocalId:l,skipWorktree:!0}:{}})
4053
- p?.sessionId&&o&&!0!==d&&this.watchSubagentForParent(String(p.sessionId),String(o),String(r)),t.json({success:!0,data:p})}catch(e){(0,ye.respondWithError)(t,this.logger,"Spawn session",e)}}settleSetupRequestWhenDone(e,r,s){const n=()=>{try{const{onNextSessionIdle:o}=t(67078)
4064
+ return!!r.ok||404!==r.status&&null}catch{return null}}async handleSpawnSession(e,t){try{if(!(0,ve.validateRequest)(e,t,this.projectId,["agent","prompt"]))return
4065
+ const{agent:r,prompt:s,requestId:n,parentSessionId:o,title:i,allowParallel:a,callerAgentType:c,shapingRequestLocalId:l,userOpened:d}=e.body,u=this.projectId,h=new oe.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:u,getDefaultModel:()=>this.cachedDefaultModel||void 0}),p=await h.prepareSession({agent:r,prompt:s,projectPath:this.options.projectPath,requestId:n,parentSessionId:o,title:i,allowParallel:a,callerAgentType:c,..."string"==typeof l&&/^req\d+$/.test(l)?{shapingRequestLocalId:l,skipWorktree:!0}:{}})
4066
+ p?.sessionId&&o&&!0!==d&&this.watchSubagentForParent(String(p.sessionId),String(o),String(r)),t.json({success:!0,data:p})}catch(e){(0,ve.respondWithError)(t,this.logger,"Spawn session",e)}}settleSetupRequestWhenDone(e,r,s){const n=()=>{try{const{onNextSessionIdle:o}=t(67078)
4054
4067
  o(e,()=>{(async()=>{try{const o=await this.apiClient.get(`/api/projects/${this.projectId}/session-messages?sessionId=${encodeURIComponent(e)}&direction=outgoing&messageType=question&status=pending&limit=5`).catch(()=>null),i=(Array.isArray(o?.data)?o.data:[]).filter(t=>String(t?.fromSessionId??t?.from_session_id??"")===e&&"pending"===String(t?.status??""))
4055
4068
  if(i.length>0)return this.logger.info("setup settle: architect idle on a pending question; still watching",{architectSessionId:e,setupRequestId:r,pending:i.length}),void n()
4056
4069
  let a=""
@@ -4076,49 +4089,47 @@ const{deliverPrompt:h}=await Promise.resolve().then(()=>t(17039)),p={projectPath
4076
4089
  if(d.test(u)){const t=(this.leakedCallNudges.get(e)??0)+1
4077
4090
  if(this.leakedCallNudges.set(e,t),t<=2)return await h(p,e,'[system] Your last reply wrote tool calls as plain text ("to=functions.\u2026"), so none of them ran: nothing you described was created. Make each of those calls now as real tool calls, then finish as you meant to.',{kind:"agent-context"}),this.logger.warn("subagent idle rail: tool calls came out as text; told the session and watching again",{childSessionId:e,agent:s,tries:t}),void this.watchSubagentForParent(e,r,s)
4078
4091
  u=`It wrote its tool calls as plain text instead of making them, and did again after being told twice, so nothing it describes below was actually created. Its last message:\n\n${u}`}await h(p,r,`[${s} finished] It did not file a report, so here is what it produced:\n\n${u.slice(0,8e3)}`,{kind:"subagent-idle"}),this.logger.info("subagent idle rail: woke the parent with the child's final message",{childSessionId:e,parentSessionId:r,agent:s}),await this.endFinishedChildSession(e,s)}catch(t){this.logger.warn("subagent idle rail failed (the parent stays asleep)",{childSessionId:e,parentSessionId:r,error:(0,q.getErrorMessage)(t)})}})()}),i=setTimeout(()=>{try{o()}catch{}},36e5)
4079
- i.unref?.()}catch(t){this.logger.warn("subagent idle rail could not be armed",{childSessionId:e,parentSessionId:r,error:(0,q.getErrorMessage)(t)})}}visualBaselineDir(){return A.default.join(this.options.projectPath,".alchemy_lab",E.VISUAL_BASELINES_DIR_NAME)}async handleRunTestsNow(e,r){try{if(!(0,ye.validateRequest)(e,r,this.projectId,["requestId"]))return
4092
+ i.unref?.()}catch(t){this.logger.warn("subagent idle rail could not be armed",{childSessionId:e,parentSessionId:r,error:(0,q.getErrorMessage)(t)})}}visualBaselineDir(){return A.default.join(this.options.projectPath,".alchemy_lab",E.VISUAL_BASELINES_DIR_NAME)}async handleRunTestsNow(e,r){try{if(!(0,ve.validateRequest)(e,r,this.projectId,["requestId"]))return
4080
4093
  const{requestId:s,parentSessionId:n,testProcessIds:o,commandNames:i,catchUp:a}=e.body||{},{handleRunTestsAction:c}=await Promise.resolve().then(()=>t(76555)),l={projectId:this.projectId,projectPath:this.options.projectPath,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackedChildPids.add(e),cachedDefaultModel:this.cachedDefaultModel??null},d=await c({requestId:s,parentSessionId:n,testProcessIds:o,commandNames:i,catchUp:a,mode:"auto"},l)
4081
4094
  if(!d.success)return void r.status(500).json({success:!1,error:d.error||d.message})
4082
- r.json({success:!0,data:d.data})}catch(e){(0,ye.respondWithError)(r,this.logger,"Run tests now",e)}}async handleSetupTests(e,t){try{if(!(0,ye.validateRequest)(e,t,this.projectId))return
4095
+ r.json({success:!0,data:d.data})}catch(e){(0,ve.respondWithError)(r,this.logger,"Run tests now",e)}}async handleSetupTests(e,t){try{if(!(0,ve.validateRequest)(e,t,this.projectId))return
4083
4096
  const{requestId:r,parentSessionId:s,context:n,settleRequestId:o,ask:i}=e.body||{}
4084
4097
  let{branchName:a}=e.body||{}
4085
- !a&&o&&r===o&&this.projectId&&(a=await(0,ve.resolveSetupBranch)(this.apiClient,this.projectId,void 0,void 0,this.logger,this.options.projectPath))
4098
+ !a&&o&&r===o&&this.projectId&&(a=await(0,be.resolveSetupBranch)(this.apiClient,this.projectId,void 0,void 0,this.logger,this.options.projectPath))
4086
4099
  const c="string"==typeof o&&!!o
4087
- if(!c&&this.projectId){const e=await this.apiClient.get(`/api/projects/${this.projectId}/test-setup-state`).catch(()=>null),r=(0,ve.setupRefusal)(e?.data??e)
4088
- if(r)return void t.status(409).json({success:!1,error:r})}const l=await(0,ve.gateAsksOnRecord)(this.apiClient,this.projectId,this.logger)
4100
+ if(!c&&this.projectId){const e=await this.apiClient.get(`/api/projects/${this.projectId}/test-setup-state`).catch(()=>null),r=(0,be.setupRefusal)(e?.data??e)
4101
+ if(r)return void t.status(409).json({success:!1,error:r})}const l=await(0,be.gateAsksOnRecord)(this.apiClient,this.projectId,this.logger)
4089
4102
  let d=!1
4090
4103
  try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/test-processes`),t=Array.isArray(e?.data)?e.data:Array.isArray(e)?e:[]
4091
4104
  d=t.length>0}catch{}let u=!1
4092
- if(this.projectId)try{u=await(0,ve.testGateOffForSetup)(this.apiClient,this.projectId,"string"==typeof r?r:void 0)}catch{}const h=await(0,ce.setupAgentInWorktree)({agent:"setup-architect",promptBuilder:async(e,t,r,s)=>(0,ve.buildSetupPrompt)(e,t,r,l,"string"==typeof i?i:void 0,u,s?await(0,ue.declaredFloorsIn)(s).catch(()=>[]):[],{existingProcesses:d,ownSetupRequest:c}),titleBuilder:e=>e?`[${e}] Test setup`:"Test setup",worktreeErrorLabel:"Failed to create setup worktree",callerContext:n,requestId:r,parentSessionId:s,branchName:a,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
4105
+ if(this.projectId)try{u=await(0,be.testGateOffForSetup)(this.apiClient,this.projectId,"string"==typeof r?r:void 0)}catch{}const h=await(0,le.setupAgentInWorktree)({agent:"setup-architect",promptBuilder:async(e,t,r,s)=>(0,be.buildSetupPrompt)(e,t,r,l,"string"==typeof i?i:void 0,u,s?await(0,he.declaredFloorsIn)(s).catch(()=>[]):[],{existingProcesses:d,ownSetupRequest:c}),titleBuilder:e=>e?`[${e}] Test setup`:"Test setup",worktreeErrorLabel:"Failed to create setup worktree",callerContext:n,requestId:r,parentSessionId:s,branchName:a,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
4093
4106
  if(!h.success)return void t.status(h.statusCode||500).json({success:!1,error:h.error})
4094
- "string"==typeof o&&o&&h.sessionId&&this.settleSetupRequestWhenDone(String(h.sessionId),o,"setup-architect"),t.json({success:!0,data:{sessionId:h.sessionId,worktreePath:h.worktreePath,branch:h.branch}})}catch(e){(0,ye.respondWithError)(t,this.logger,"Setup tests",e)}}async handleSetupCommands(e,t){try{if(!(0,ye.validateRequest)(e,t,this.projectId))return
4095
- const{requestId:r,parentSessionId:s,branchName:n,context:o}=e.body||{},i=await(0,ce.setupAgentInWorktree)({agent:"setup-architect",promptBuilder:(e,t,r)=>(0,ve.buildCommandSetupPrompt)(e,t,r),titleBuilder:e=>e?`[${e}] Commands setup`:"Commands setup",worktreeErrorLabel:"Failed to create setup worktree (commands)",callerContext:o,requestId:r,parentSessionId:s,branchName:n,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
4107
+ "string"==typeof o&&o&&h.sessionId&&this.settleSetupRequestWhenDone(String(h.sessionId),o,"setup-architect"),t.json({success:!0,data:{sessionId:h.sessionId,worktreePath:h.worktreePath,branch:h.branch}})}catch(e){(0,ve.respondWithError)(t,this.logger,"Setup tests",e)}}async handleSetupCommands(e,t){try{if(!(0,ve.validateRequest)(e,t,this.projectId))return
4108
+ const{requestId:r,parentSessionId:s,branchName:n,context:o}=e.body||{},i=await(0,le.setupAgentInWorktree)({agent:"setup-architect",promptBuilder:(e,t,r)=>(0,be.buildCommandSetupPrompt)(e,t,r),titleBuilder:e=>e?`[${e}] Commands setup`:"Commands setup",worktreeErrorLabel:"Failed to create setup worktree (commands)",callerContext:o,requestId:r,parentSessionId:s,branchName:n,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e),getDefaultModel:()=>this.cachedDefaultModel||void 0})
4096
4109
  if(!i.success)return void t.status(i.statusCode||500).json({success:!1,error:i.error})
4097
- t.json({success:!0,data:{sessionId:i.sessionId,worktreePath:i.worktreePath,branch:i.branch}})}catch(e){(0,ye.respondWithError)(t,this.logger,"Setup commands",e)}}async handleGetAgentByName(e,t){try{const r=this.projectId
4110
+ t.json({success:!0,data:{sessionId:i.sessionId,worktreePath:i.worktreePath,branch:i.branch}})}catch(e){(0,ve.respondWithError)(t,this.logger,"Setup commands",e)}}async handleGetAgentByName(e,t){try{const r=this.projectId
4098
4111
  if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
4099
- const{agentName:s}=e.params,n=(await(0,oe.getProjectAgents)(this.apiClient,r)).find(e=>e.name===s)
4112
+ const{agentName:s}=e.params,n=(await(0,ie.getProjectAgents)(this.apiClient,r)).find(e=>e.name===s)
4100
4113
  if(!n)return void t.status(404).json({success:!1,error:`Agent '${s}' not found`})
4101
- t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,q.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<He.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
4114
+ t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,q.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,q.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<We.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
4102
4115
  const e=A.default.join(this.options.projectPath,".alchemy_lab","project.json")
4103
4116
  try{if(!_.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
4104
4117
  const t=_.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
4105
- return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,q.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e,(0,qe.noteServe)(e)
4118
+ return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,q.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e,(0,De.noteServe)(e)
4106
4119
  const t=`http://127.0.0.1:${e}`
4107
4120
  this.openCodeUrlCache={url:t,timestamp:Date.now()},this.logger.info("OpenCode port set directly",{port:e}),(async()=>{for(let e=0;e<10;e++){try{const t=await this.configurer.refreshConnectedProviders(this.options.projectPath)
4108
- if(this.configurer.hasConnectedProviders())return this.logger.info("connected providers known",{changed:t,attempt:e}),void(t&&await this.resyncAgentMarkdowns())}catch(t){this.logger.debug("connected-provider refresh failed",{attempt:e,error:(0,q.getErrorMessage)(t)})}await new Promise(e=>setTimeout(e,3e3))}this.logger.warn("the serve never reported its connected providers; agent models were not checked against them")})(),this.cachedDefaultModel||this.seedDefaultModelFromZen(e).catch(e=>this.logger.debug("Failed to seed zen default model",{error:(0,q.getErrorMessage)(e)}))}async seedDefaultModelFromZen(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
4121
+ if(this.configurer.hasConnectedProviders())return this.logger.info("connected providers known",{changed:t,attempt:e}),void(t&&await this.resyncAgentMarkdowns())}catch(t){this.logger.debug("connected-provider refresh failed",{attempt:e,error:(0,q.getErrorMessage)(t)})}await new Promise(e=>setTimeout(e,3e3))}this.logger.warn("the serve never reported its connected providers; agent models were not checked against them")})(),this.cachedDefaultModel||this.seedDefaultModel(e).catch(e=>this.logger.debug("Failed to seed default model",{error:(0,q.getErrorMessage)(e)}))}async seedDefaultModel(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
4109
4122
  if(!t.ok)return
4110
- const r=await t.json(),s=r?.default||{},[n,o]=Object.entries(s)[0]||[]
4111
- if(!n||!o)return
4112
- const i=`${n}/${o}`
4113
- 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,q.getErrorMessage)(e)}))}getOpenCodeUrl(){if(this.parkController?.isServeDown())return null
4123
+ const r=await t.json(),s=(r?.providers||[]).map(e=>String(e.id||"")).filter(Boolean),n=(0,$.primaryProviderOf)(s),o=r?.default||{},[i,a]=Object.entries(o)[0]||[],c=n?(0,$.defaultModelFor)(n):i&&a?`${i}/${a}`:null
4124
+ c&&(this.updateDefaultModel(c),this.logger.info("Seeded defaultModel",{defaultModel:c,primaryProvider:n}),this.apiClient.post("/api/user/preferences",{defaultModel:c}).catch(e=>this.logger.debug("Failed to seed user preference",{error:(0,q.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.parkController?.isServeDown())return null
4114
4125
  if(this.openCodePort)return`http://127.0.0.1:${this.openCodePort}`
4115
4126
  if(this.openCodeUrlCache&&Date.now()-this.openCodeUrlCache.timestamp<3e4)return this.openCodeUrlCache.url
4116
- const e=(0,Re.getOpenCodeUrlFromConfig)(this.options.projectPath)
4127
+ const e=(0,Oe.getOpenCodeUrlFromConfig)(this.options.projectPath)
4117
4128
  return e&&(this.openCodeUrlCache={url:e,timestamp:Date.now()}),e}async register(){const e=this.readProjectConfig()
4118
4129
  if(!e?.id)throw new Error("No project configuration found")
4119
4130
  this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId),this.cachedDefaultModel&&this.lastAgentResyncModel!==this.cachedDefaultModel&&this.resyncAgentMarkdowns().catch(e=>this.logger.debug("Post-registration agent markdown re-sync failed",{error:(0,q.getErrorMessage)(e)})),this.startParkController(),this.startProjectSubsystems("register").catch(e=>{this.logger.error("project subsystems failed to start from register()",{error:(0,q.getErrorMessage)(e)})})
4120
4131
  try{const e=A.default.resolve(__dirname,"../../package.json"),t=JSON.parse(_.default.readFileSync(e,"utf8"))
4121
- this.clientVersion=t.version}catch{}try{const e=(0,we.findOpencodeDir)()
4132
+ this.clientVersion=t.version}catch{}try{const e=(0,ye.findOpencodeDir)()
4122
4133
  if(e){const t=JSON.parse(_.default.readFileSync(A.default.join(e,"package.json"),"utf8"))
4123
4134
  this.opencodeVersion=t.version}}catch{}const t={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:x.default.hostname(),platform:process.platform,clientVersion:this.clientVersion,opencodeVersion:this.opencodeVersion}
4124
4135
  this.logger.info("Registering with remote",{projectId:this.projectId})
@@ -4151,15 +4162,15 @@ if(r()){try{await s.cancel()}catch{}return}if(e)break
4151
4162
  const i=a.decode(o,{stream:!0})
4152
4163
  if(l){const e=i.indexOf("\n")
4153
4164
  if(-1===e)continue
4154
- l=!1,c=i.slice(e+1),u||this.logger.warn("Oversized SSE event discarded as it arrived",{type:d})}else if(c+=i,c.length>Be&&-1===c.indexOf("\n")&&!(c.length<=n.SESSION_DIFF_PARSE_CEILING&&(0,n.trimmableWhenWhole)(c.replace(/^data: /,"")))){d=(0,n.sseEventTypeOf)(c)
4165
+ l=!1,c=i.slice(e+1),u||this.logger.warn("Oversized SSE event discarded as it arrived",{type:d})}else if(c+=i,c.length>Ge&&-1===c.indexOf("\n")&&!(c.length<=n.SESSION_DIFF_PARSE_CEILING&&(0,n.trimmableWhenWhole)(c.replace(/^data: /,"")))){d=(0,n.sseEventTypeOf)(c)
4155
4166
  const e=(0,n.trimOversizedSseEvent)(c.replace(/^data: /,""),Number.POSITIVE_INFINITY)
4156
4167
  u=!!e,e?(this.sseEventBuffer.push({type:e.type,data:e,at:Date.now()}),this.logger.debug("Oversized SSE event forwarded as a summary",{type:d})):(0===this.droppedEventCount&&(this.firstDropAt=Date.now()),this.droppedEventCount++),l=!0,c=""
4157
4168
  continue}const h=c.split("\n")
4158
4169
  c=h.pop()||""
4159
4170
  for(const e of h)if(e.startsWith("data: ")){let r=null
4160
- if(e.length>Be&&(r=(0,n.trimOversizedSseEvent)(e.slice(6)),r&&"session.diff"!==r.type&&JSON.stringify(r).length>Be&&(r=null)),r)this.logger.debug("Oversized SSE event forwarded as a summary",{type:r.type,chars:e.length})
4161
- else if(e.length>Be){const t=(0,n.sseEventTypeOf)(e)
4162
- 0===this.droppedEventCount&&(this.firstDropAt=Date.now()),this.droppedEventCount++,this.logger.warn("Oversized SSE event dropped before parsing",{type:t,chars:e.length,limit:Be})
4171
+ if(e.length>Ge&&(r=(0,n.trimOversizedSseEvent)(e.slice(6)),r&&"session.diff"!==r.type&&JSON.stringify(r).length>Ge&&(r=null)),r)this.logger.debug("Oversized SSE event forwarded as a summary",{type:r.type,chars:e.length})
4172
+ else if(e.length>Ge){const t=(0,n.sseEventTypeOf)(e)
4173
+ 0===this.droppedEventCount&&(this.firstDropAt=Date.now()),this.droppedEventCount++,this.logger.warn("Oversized SSE event dropped before parsing",{type:t,chars:e.length,limit:Ge})
4163
4174
  continue}try{const s=r??JSON.parse(e.slice(6)),n=s.type||"unknown"
4164
4175
  this.sseEventBuffer.push({type:n,data:s,at:Date.now()})
4165
4176
  {const e=s?.properties?.sessionID||s?.sessionID
@@ -4183,13 +4194,16 @@ const s=await r.json(),n=this.recordedUsageMsgs.get(e),o=[]
4183
4194
  for(const t of s){const r=t?.info
4184
4195
  if(!r||"assistant"!==r.role||!r.id)continue
4185
4196
  if(n&&n.has(String(r.id)))continue
4186
- const{input:s,output:i,reasoning:a,cacheRead:c,cacheWrite:l,cost:d,hasUsage:u}=(0,se.tokensOfMessage)(t)
4197
+ const{input:s,output:i,reasoning:a,cacheRead:c,cacheWrite:l,cost:d,hasUsage:u}=(0,ne.tokensOfMessage)(t)
4187
4198
  u&&o.push({sessionId:e,messageId:String(r.id),agentType:r.agent??r.mode??null,provider:r.providerID??null,model:r.modelID??null,inputTokens:s,outputTokens:i,reasoningTokens:a,cacheReadTokens:c,cacheWriteTokens:l,cost:d})}if(0===o.length)return
4188
4199
  await this.apiClient.post(`/api/projects/${this.projectId}/usage-events`,{events:o})
4189
4200
  let i=n
4190
4201
  if(!i){if(i=new Set,this.recordedUsageMsgs.size>=300)for(const e of Array.from(this.recordedUsageMsgs.keys()).slice(0,50))this.recordedUsageMsgs.delete(e)
4191
4202
  this.recordedUsageMsgs.set(e,i)}for(const e of o)i.add(e.messageId)
4192
- this.logger.debug("Recorded session usage to remote",{sessionId:e,count:o.length})}catch(t){this.logger.debug("Usage recording failed",{sessionId:e,error:(0,q.getErrorMessage)(t)})}}async rejectPermissionAsk(e){const t=e?.properties||{},r=t?.id
4203
+ this.logger.debug("Recorded session usage to remote",{sessionId:e,count:o.length})}catch(t){this.logger.debug("Usage recording failed",{sessionId:e,error:(0,q.getErrorMessage)(t)})}}async sweepUsage(e,t=8e3){const r=this.getOpenCodeUrl()
4204
+ if(!this.projectId||!r)return
4205
+ const s=await(0,Be.sweepSessionUsage)(r,e,e=>this.recordUsageForSession(e),t)
4206
+ s.error?this.logger.warn("usage sweep failed",{error:s.error}):s.outOfTime&&this.logger.warn("usage sweep ran out of time; some sessions not read",{read:s.read})}async rejectPermissionAsk(e){const t=e?.properties||{},r=t?.id
4193
4207
  if(!r)return
4194
4208
  const s=this.getOpenCodeUrl()
4195
4209
  if(!s)return
@@ -4214,7 +4228,7 @@ const t=s?r.slice(1):r
4214
4228
  if(this.sseEventBuffer.unshift(...t),this.sseEventBuffer.length>500){const e=this.sseEventBuffer.length-500
4215
4229
  this.sseEventBuffer.splice(0,e),0===this.droppedEventCount&&(this.firstDropAt=Date.now()),this.droppedEventCount+=e}}}startPolling(){this.projectId&&(this.isPolling=!0,this.pollAbortController=new AbortController,this.logger.info("Starting action long-polling",{projectId:this.projectId}),this.longPollLoop())}async longPollLoop(){for(;this.isPolling;)try{await this.pollOnce(),this.remoteLastOkAt=Date.now(),this.remoteFailures=0,this.remoteLastError=null}catch(e){if(!this.isPolling)return
4216
4230
  this.remoteFailures+=1,this.remoteLastError=(0,q.getErrorMessage)(e),this.logger.debug("Long-poll error, retrying in 5s",{error:(0,q.getErrorMessage)(e)}),await new Promise(e=>setTimeout(e,5e3))}}async pollOnce(){if(this.isPolling&&this.projectId&&!this.updateBlocked)try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=30000`,t={"Content-Type":"application/json",Accept:"application/json"}
4217
- this.apiClient.authToken&&(t.Authorization=`Bearer ${this.apiClient.authToken}`),Object.assign(t,(0,De.machineCallerHeaders)(this.options.remoteServerUrl)),this.clientVersion&&(t["X-Client-Version"]=this.clientVersion),this.opencodeVersion&&(t["X-OpenCode-Version"]=this.opencodeVersion),this.port&&(t["X-Action-Server-Port"]=String(this.port))
4231
+ this.apiClient.authToken&&(t.Authorization=`Bearer ${this.apiClient.authToken}`),Object.assign(t,(0,Ne.machineCallerHeaders)(this.options.remoteServerUrl)),this.clientVersion&&(t["X-Client-Version"]=this.clientVersion),this.opencodeVersion&&(t["X-OpenCode-Version"]=this.opencodeVersion),this.port&&(t["X-Action-Server-Port"]=String(this.port))
4218
4232
  const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(35e3)})
4219
4233
  if(!r.ok)throw new Error(`Poll failed with status ${r.status}`)
4220
4234
  const s=await r.json(),n={success:!0,data:s.data||s}
@@ -4224,7 +4238,7 @@ this.logger.info("Received cache invalidations from remote",{invalidations:e}),(
4224
4238
  if((a>0||c>0)&&await this.ensureServeAlive("poll-work"),i.length>0){const e=i
4225
4239
  this.logger.info("Received goal nudges from remote",{scheduleNudges:e})
4226
4240
  for(const t of e)this.scheduleRunner?.nudge(t).catch(e=>{this.logger.warn("Schedule nudge dispatch failed",{docId:t,error:String(e)})})}n.success&&Array.isArray(n.data?.versionWarnings)&&this.applyVersionWarnings(n.data.versionWarnings),n.success&&n.data?.actions?.length>0&&this.dispatchActions(n.data.actions)}catch(e){throw e}}dispatchActions(e){const t=[],r=[]
4227
- for(const s of e)Ue.has(s.type)?t.push(s):r.push(s)
4241
+ for(const s of e)He.has(s.type)?t.push(s):r.push(s)
4228
4242
  for(const e of t)this.inFlightProxyActions++,this.processAction(e).catch(t=>this.logger.warn("Proxy action failed",{actionId:e.id,error:(0,q.getErrorMessage)(t)})).finally(()=>{this.inFlightProxyActions--})
4229
4243
  let s=0
4230
4244
  for(const e of r)this.queuedRegularActionIds.has(e.id)?this.logger.debug("Skipping re-delivered action already held locally",{actionId:e.id,type:e.type}):(this.queuedRegularActionIds.add(e.id),this.pendingRegularActions.push(e),s++)
@@ -4245,8 +4259,8 @@ for(const[e,t]of s){this.logger.warn("Re-dispatching test run(s) orphaned by res
4245
4259
  try{await i({requestId:e,testRunIds:t,mode:"auto"},n)}catch(t){this.logger.warn("Orphan test-run re-dispatch failed (stuck-run watchdog will backstop)",{requestId:e,error:(0,q.getErrorMessage)(t)})}}}catch(e){this.logger.warn("Orphan test-run sweep failed (non-fatal)",{error:(0,q.getErrorMessage)(e)})}}async processAction(e){this.parkController?.noteActivity(),this.logger.info("Processing action",{actionId:e.id,type:e.type})
4246
4260
  const t=Date.now()
4247
4261
  let r
4248
- try{const s={projectPath:this.options.projectPath,projectId:this.projectId,serverPort:this.port,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e),cachedDefaultModel:this.cachedDefaultModel},n=await(0,P.executeAction)(e.type,e.payload||{},s)
4249
- r={success:n.success,result:n.refused?{...n.data??{},refused:!0}:n.data,error:n.message||n.error,duration:Date.now()-t}}catch(e){r={success:!1,error:(0,q.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}):r.result?.refused?this.logger.info("Action refused (by design)",{actionId:e.id,type:e.type,why:r.error}):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,q.getErrorMessage)(t)})}}linearImportedFile(){return A.default.join(this.options.projectPath,".alchemy_lab","linear-imported.json")}async loadLinearImported(){try{const e=JSON.parse(await w.promises.readFile(this.linearImportedFile(),"utf-8"))
4262
+ try{const s={projectPath:this.options.projectPath,projectId:this.projectId,serverPort:this.port,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e),cachedDefaultModel:this.cachedDefaultModel},n=await(0,T.executeAction)(e.type,e.payload||{},s)
4263
+ r={success:n.success,result:n.refused?{...n.data??{},refused:!0}:n.data,error:n.message||n.error,duration:Date.now()-t}}catch(e){r={success:!1,error:(0,q.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}),"prune-worktree"===e.type&&this.sweepUsage(Date.now()-216e5).catch(()=>{})):r.result?.refused?this.logger.info("Action refused (by design)",{actionId:e.id,type:e.type,why:r.error}):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,q.getErrorMessage)(t)})}}linearImportedFile(){return A.default.join(this.options.projectPath,".alchemy_lab","linear-imported.json")}async loadLinearImported(){try{const e=JSON.parse(await w.promises.readFile(this.linearImportedFile(),"utf-8"))
4250
4264
  return new Set(e.importedIssueIds??[])}catch{return new Set}}async saveLinearImported(e){await w.promises.writeFile(this.linearImportedFile(),JSON.stringify({importedIssueIds:[...e]},null,2),"utf-8")}async resolveAlchemistSession(){try{const e=await this.apiClient.get(`/api/projects/${this.projectId}`),t=e?.data||e,r=t?.activeAlchemistSessionId
4251
4265
  if(r){const e=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/${r}`),t=e?.data||e
4252
4266
  if(t?.externalSessionId&&"active"===t?.status)return t.externalSessionId}const s=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/agent-types`),n=(s?.data||s||[]).find(e=>"alchemist"===e.agentType)
@@ -4290,16 +4304,16 @@ _.default.mkdirSync(A.default.dirname(e),{recursive:!0}),_.default.writeFileSync
4290
4304
  try{const e=this.readProjectConfig()
4291
4305
  if(e?.id){const{upsertEntry:r}=await Promise.resolve().then(()=>t(52640))
4292
4306
  r({projectId:e.id,projectPath:this.options.projectPath,port:this.port,pid:process.pid,startedAt:(new Date).toISOString(),lastHeartbeat:(new Date).toISOString()}),this.logger.info("Wrote running-projects registry entry",{projectId:e.id,port:this.port})}}catch(e){this.logger.warn("Failed to write project registry entry",{error:(0,q.getErrorMessage)(e)})}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,q.getErrorMessage)(e)
4293
- t.includes("Required update"),this.logger.warn("Remote registration failed, continuing in local-only mode",{error:t})}if(this.projectId&&setTimeout(()=>{this.ensureHubAlchemist()},1e4),this.projectId&&(0,le.sweepOrphanExperiments)(this.projectId,this.logger,this.options.projectPath),this.projectId){const e=Date.now()
4307
+ t.includes("Required update"),this.logger.warn("Remote registration failed, continuing in local-only mode",{error:t})}if(this.projectId&&setTimeout(()=>{this.ensureHubAlchemist()},1e4),this.projectId&&(0,de.sweepOrphanExperiments)(this.projectId,this.logger,this.options.projectPath),this.projectId){const e=Date.now()
4294
4308
  setTimeout(()=>{this.sweepOrphanTestRuns(e)},15e3)}this.startExperimentReaper(),this.startLinearAutoImportPoller()
4295
4309
  try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=0&settingsOnly=true`,t={"Content-Type":"application/json",Accept:"application/json"}
4296
- this.apiClient.authToken&&(t.Authorization=`Bearer ${this.apiClient.authToken}`),Object.assign(t,(0,De.machineCallerHeaders)(this.options.remoteServerUrl))
4310
+ this.apiClient.authToken&&(t.Authorization=`Bearer ${this.apiClient.authToken}`),Object.assign(t,(0,Ne.machineCallerHeaders)(this.options.remoteServerUrl))
4297
4311
  const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
4298
4312
  if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
4299
- void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel})),"boolean"==typeof t?.paused&&await this.applyPause(t.paused),"string"==typeof t?.claudeThinking&&(this.claudeThinkingSeen=t.claudeThinking)}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,q.getErrorMessage)(e)})}this.startPolling(),await this.startProjectSubsystems("start"),this.startMcpStaleCleanup(),this.projectId&&this.startParkController()}async startProjectSubsystems(e){if(this.projectId&&!this.projectSubsystemsStarted){this.projectSubsystemsStarted=!0,this.logger.info("Starting project subsystems",{source:e,projectId:this.projectId}),(0,Ie.setContainerProject)(this.projectId),this.startSSEEventForwarder()
4313
+ void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel})),"boolean"==typeof t?.paused&&await this.applyPause(t.paused),"string"==typeof t?.claudeThinking&&(this.claudeThinkingSeen=t.claudeThinking)}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,q.getErrorMessage)(e)})}this.startPolling(),await this.startProjectSubsystems("start"),this.startMcpStaleCleanup(),this.projectId&&this.startParkController()}async startProjectSubsystems(e){if(this.projectId&&!this.projectSubsystemsStarted){this.projectSubsystemsStarted=!0,this.logger.info("Starting project subsystems",{source:e,projectId:this.projectId}),(0,Se.setContainerProject)(this.projectId),this.startSSEEventForwarder()
4300
4314
  try{const{resumeInboxChatWatches:e}=await Promise.resolve().then(()=>t.t(86116,23))
4301
4315
  e({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient})}catch(e){this.logger.warn("inbox watch resume failed at start",{error:(0,q.getErrorMessage)(e)})}this.apiClient.post(`/api/projects/${this.projectId}/session-messages/expire-orphaned-approvals`,{}).then(e=>{const t=e?.data?.expired??0
4302
- t>0&&this.logger.info("Ended approval cards left behind by the previous engine",{expired:t})}).catch(e=>{this.logger.warn("Could not end orphaned approval cards",{error:(0,q.getErrorMessage)(e)})}),this.startStuckSessionWatchdog(),this.startScheduleRunner(),(0,re.ensureOpsDiagnostics)(this.apiClient,this.projectId,this.options.projectPath,this.logger),(0,Y.startSessionCompactor)({getOpenCodeUrl:()=>this.getOpenCodeUrl()??void 0,apiClient:this.apiClient,projectId:this.projectId||void 0,projectName:this.options.projectPath?A.default.basename(this.options.projectPath):void 0,logger:this.logger}),this.keepWarmRunner=(0,X.startKeepWarmRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,cachedDefaultModel:this.cachedDefaultModel,getDefaultModel:()=>this.cachedDefaultModel,instructionsChangedAt:()=>this.instructionsChangedAt}),this.ownerGuidanceTimer=setInterval(()=>{this.checkOwnerGuidanceApply()},6e4),this.ownerGuidanceTimer.unref?.(),this.workMetricsRunner||(this.workMetricsRunner=(0,Q.startWorkMetricsRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient})),this.mcpConnectionRunner||(this.mcpConnectionRunner=(0,Z.startMcpConnectionRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,onConfigChanged:()=>{this.requestConfigReload("mcp-connections")},isBusy:async()=>this.activeMcpCalls.size>0||await this.anySessionBusy()})),(0,J.startMonitorRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,cachedDefaultModel:this.cachedDefaultModel,serveReady:async()=>(await this.ensureServeAlive("monitor-wake"),null!=this.openCodePort&&!this.parkController?.isServeDown()),reportLiveness:e=>{this.apiClient.post("/api/liveness/report",{...e,project:this.options.projectPath?this.options.projectPath.split("/").pop():void 0}).catch(()=>{})},runOwn:async e=>(await fetch(`http://127.0.0.1:${this.port}/api/run`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(18e4)})).json(),verifyDeliveryEnv:async e=>{let r
4316
+ t>0&&this.logger.info("Ended approval cards left behind by the previous engine",{expired:t})}).catch(e=>{this.logger.warn("Could not end orphaned approval cards",{error:(0,q.getErrorMessage)(e)})}),this.startStuckSessionWatchdog(),this.startScheduleRunner(),(0,se.ensureOpsDiagnostics)(this.apiClient,this.projectId,this.options.projectPath,this.logger),(0,Y.startSessionCompactor)({getOpenCodeUrl:()=>this.getOpenCodeUrl()??void 0,apiClient:this.apiClient,projectId:this.projectId||void 0,projectName:this.options.projectPath?A.default.basename(this.options.projectPath):void 0,logger:this.logger}),this.keepWarmRunner=(0,X.startKeepWarmRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,cachedDefaultModel:this.cachedDefaultModel,getDefaultModel:()=>this.cachedDefaultModel}),this.ownerGuidanceTimer=setInterval(()=>{this.checkOwnerGuidanceApply()},6e4),this.ownerGuidanceTimer.unref?.(),this.workMetricsRunner||(this.workMetricsRunner=(0,Z.startWorkMetricsRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient})),this.mcpConnectionRunner||(this.mcpConnectionRunner=(0,ee.startMcpConnectionRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,onConfigChanged:()=>{this.requestConfigReload("mcp-connections")},isBusy:async()=>this.activeMcpCalls.size>0||await this.anySessionBusy()})),(0,J.startMonitorRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,cachedDefaultModel:this.cachedDefaultModel,serveReady:async()=>(await this.ensureServeAlive("monitor-wake"),null!=this.openCodePort&&!this.parkController?.isServeDown()),reportLiveness:e=>{this.apiClient.post("/api/liveness/report",{...e,project:this.options.projectPath?this.options.projectPath.split("/").pop():void 0}).catch(()=>{})},runOwn:async e=>(await fetch(`http://127.0.0.1:${this.port}/api/run`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(18e4)})).json(),verifyDeliveryEnv:async e=>{let r
4303
4317
  try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/environments/${e}`)
4304
4318
  r=t?.data||t}catch{throw new Error("environment fetch failed")}const s=r?.config?.delivery
4305
4319
  if(!r||!s?.targetPath)return{gone:!0,reason:"environment or its delivery config no longer exists"}
@@ -4309,8 +4323,8 @@ if(this.isProcessingAction||this.inFlightProxyActions>0)return this.parkVeto("pr
4309
4323
  if(this.inFlightServeProxyRequests>0)return this.parkVeto("serve-proxy-in-flight")
4310
4324
  if(this.hasRemoteEventConsumers)return this.parkVeto("ui-event-consumers")
4311
4325
  if(this.activeMcpCalls.size>0||this.pendingConfigReload)return this.parkVeto("mcp-or-config-activity")
4312
- if((0,be.anyPoolActivity)())return this.parkVeto("pool-activity")
4313
- if((0,he.listPendingApprovals)().length>0||(0,pe.listPendingPaintRequests)().length>0)return this.parkVeto("pending-approvals")
4326
+ if((0,ke.anyPoolActivity)())return this.parkVeto("pool-activity")
4327
+ if((0,pe.listPendingApprovals)().length>0||(0,fe.listPendingPaintRequests)().length>0)return this.parkVeto("pending-approvals")
4314
4328
  if((0,N.listRunningExecs)().length>0)return this.parkVeto("running-execs")
4315
4329
  if((0,I.listPreviewHandles)().length>0)return this.parkVeto("preview-handles")
4316
4330
  if(this.projectId&&(0,b.experimentRegistry)().countForProject(this.projectId)>0)return this.parkVeto("experiments")
@@ -4376,12 +4390,12 @@ s.success?t.json({success:!0}):t.status(400).json({success:!1,error:s.error})}as
4376
4390
  for(const e of this.versionWarnings)console.error(`\x1b[31m ${e.message}\x1b[0m`)
4377
4391
  console.error("\x1b[31m\n Functionality disabled. Update via UI or run:\n npm update -g alchemylab\x1b[0m\n"),this.parkController?.stop(),this.ownerGuidanceTimer&&clearInterval(this.ownerGuidanceTimer),await this.awaitInFlightRevival(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.scheduleRunner?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval)
4378
4392
  for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.clear(),this.logger.info("Update-required mode active. HTTP server still running on port "+this.port)}}async stop(){this.logger.info("Stopping AlchemyActionServer..."),(0,y.clearInstanceLock)(this.options.projectPath)
4379
- for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.parkController?.stop(),this.ownerGuidanceTimer&&clearInterval(this.ownerGuidanceTimer),await this.awaitInFlightRevival()
4393
+ for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),await this.sweepUsage(this.startTime.getTime(),8e3),this.parkController?.stop(),this.ownerGuidanceTimer&&clearInterval(this.ownerGuidanceTimer),await this.awaitInFlightRevival()
4380
4394
  try{await Promise.race([(0,b.experimentRegistry)().stopAll(),new Promise(e=>setTimeout(e,15e3))])}catch(e){this.logger.warn("shutdown: boxes not all stopped",{error:(0,q.getErrorMessage)(e)})}this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.scheduleRunner?.stop(),this.stopSSEEventForwarder(),this.mcpStaleCleanupInterval&&(clearInterval(this.mcpStaleCleanupInterval),this.mcpStaleCleanupInterval=void 0),this.stopPendingReloadRecheck()
4381
4395
  try{const e=A.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
4382
4396
  _.default.existsSync(e)&&_.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await Promise.resolve().then(()=>t(52640))
4383
4397
  e(this.projectId)}catch{}if(this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.runningReportInterval&&(clearInterval(this.runningReportInterval),this.runningReportInterval=void 0),this.runningReportDebounce&&(clearTimeout(this.runningReportDebounce),this.runningReportDebounce=void 0),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.registryHeartbeatInterval&&(clearInterval(this.registryHeartbeatInterval),this.registryHeartbeatInterval=void 0),this.vanishedSessionSweepInterval&&(clearInterval(this.vanishedSessionSweepInterval),this.vanishedSessionSweepInterval=void 0),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}await new Promise(e=>{if(this.server){const t=setTimeout(()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()},1e4)
4384
- this.server.close(()=>{clearTimeout(t),e()})}else e()}),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=He,He.JUDGE_APPROVAL_TTL_MS=864e5,He.PROJECT_CONFIG_TTL=3e4,He.REMOTE_STALE_MS=9e4,He.PROMPT_DELIVERY_BUSY_MS=2e4,He.RELOAD_RECHECK_MS=12e3,He.RELOAD_DEFER_CAP_MS=3e5},61292(e,exports,t){"use strict"
4398
+ this.server.close(()=>{clearTimeout(t),e()})}else e()}),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=We,We.JUDGE_APPROVAL_TTL_MS=864e5,We.PROJECT_CONFIG_TTL=3e4,We.REMOTE_STALE_MS=9e4,We.PROMPT_DELIVERY_BUSY_MS=2e4,We.RELOAD_RECHECK_MS=12e3,We.RELOAD_DEFER_CAP_MS=3e5},61292(e,exports,t){"use strict"
4385
4399
  function r(e,t){const r=String(e||"").replace(/\s+/g," ").trim().split(/(?<=[.!?])\s+/),s=/^(research only|read[- ]only|no edits?|do not|don't|never|only )/i,n=(r.find(e=>e.length>8&&!s.test(e.trim()))??r[0]??"").replace(/[.!?]+$/,"").trim()
4386
4400
  return n.length>t?`${n.slice(0,t)}...`:n}async function s(e,t){const r=y.get(t)
4387
4401
  if(r&&Date.now()-r.at<m)return{model:r.model,reason:r.model?"ok":"unconfigured"}
@@ -4551,7 +4565,7 @@ r.completed&&(s=!0)}}return s}async function C(e,t){const r=re.get(e)
4551
4565
  if(r&&!r.checking)if((0,Q.isProjectPaused)())t&&(r.deadline=setTimeout(()=>{A(e)},Z))
4552
4566
  else{r.checking=!0
4553
4567
  try{const n=(0,G.getOpenCodeUrlFromConfig)(r.projectPath)?.replace(/\/$/,"")
4554
- if(!n)return void(t&&await P(e,r,{turnDied:!0}))
4568
+ if(!n)return void(t&&await T(e,r,{turnDied:!0}))
4555
4569
  const o=await async function(e,t){try{const r=await fetch(`${e}/session/status`,{signal:AbortSignal.timeout(5e3)})
4556
4570
  if(!r.ok)return null
4557
4571
  const s=await r.json(),n=s?.[t]?.type
@@ -4576,26 +4590,26 @@ if(f){if(f.transient&&!r.resumed&&await async function(e,t,r){const n={projectId
4576
4590
  if(!o.ok||!o.messageId)return t.logger.warn("inbox-chat loop: could not resume a cut-off turn",{sessionId:e,error:o.error}),!1
4577
4591
  const i=Date.now(),a=t.exchanges[t.exchanges.length-1]
4578
4592
  return t.resumed=!0,t.serveError=void 0,a.markerMessageId=o.messageId,a.injectedAtMs=i,t.hardCapAtMs=i+ee,clearTimeout(t.deadline),t.deadline=setTimeout(()=>{A(e)},Z),s(t.projectPath),t.logger.warn("inbox-chat loop: turn cut off by a provider error; resumed it once",{sessionId:e,error:r,markerMessageId:o.messageId}),!0}(e,r,f.detail))return
4579
- return j(f.messageId),void await P(e,r,{turnDied:!0,turnError:r.resumed?`${f.detail}, twice`:f.detail,...f.refusal?{turnRefusal:f.refusal}:{}})}if(c){for(const t of r.exchanges.filter(e=>e.askAnnotationId||e.systemNoteRequestId)){const s=q(h(t),r),n=p(s,t.markerMessageId)
4593
+ return j(f.messageId),void await T(e,r,{turnDied:!0,turnError:r.resumed?`${f.detail}, twice`:f.detail,...f.refusal?{turnRefusal:f.refusal}:{}})}if(c){for(const t of r.exchanges.filter(e=>e.askAnnotationId||e.systemNoteRequestId)){const s=q(h(t),r),n=p(s,t.markerMessageId)
4580
4594
  await M(e,r,t,n?{turnText:n.reply.slice(0,te),changed:D(s,t.markerMessageId)}:{turnSilent:!0},!1)}if(0===r.exchanges.length)return void _(e,r)}const g=r.exchanges[r.exchanges.length-1],m=r.exchanges[0],w=q((c??[]).filter(e=>!(e.aside&&e.parentId!==m.markerMessageId||"assistant"===e.role&&e.parentId&&u.includes(e.parentId))),r),y=c?p(w,m.markerMessageId):null
4581
4595
  if(y)return void await x(e,r,{turnText:y.reply.slice(0,te),turnTrace:I(w,m.markerMessageId)??void 0,askedBefore:N(w,g.markerMessageId)})
4582
4596
  const k=c?S(c,g.markerMessageId):null
4583
- if(k)return void await P(e,r,{turnDied:!0,turnError:k})
4597
+ if(k)return void await T(e,r,{turnDied:!0,turnError:k})
4584
4598
  const C=c?l(c,g.markerMessageId,r.serveError):null
4585
- if(C)return r.logger.warn("inbox-chat loop: turn died before an assistant message existed",{sessionId:e,error:C}),void await P(e,r,{turnDied:!0,turnError:C})
4599
+ if(C)return r.logger.warn("inbox-chat loop: turn died before an assistant message existed",{sessionId:e,error:C}),void await T(e,r,{turnDied:!0,turnError:C})
4586
4600
  if(c&&E(w,m.markerMessageId))return r.logger.info("inbox-chat loop: turn concluded silently (no text, no error)",{sessionId:e}),void await x(e,r,{turnSilent:!0})
4587
- t&&await P(e,r,{turnDied:!0})}finally{const t=re.get(e)
4601
+ t&&await T(e,r,{turnDied:!0})}finally{const t=re.get(e)
4588
4602
  t&&(t.checking=!1)}}}async function A(e){await C(e,!0)}function _(e,t){clearTimeout(t.deadline),clearInterval(t.poll),re.get(e)===t&&re.delete(e),s(t.projectPath),function(e,t){const r=t.held??[]
4589
4603
  if(t.held=[],0===r.length)return
4590
4604
  const s=t.context,n=se
4591
4605
  !s||!n&&r.some(e=>"deliver"!==e.via)?t.logger.error("inbox-chat loop: held system notes could not be delivered (no deliverer)",{sessionId:e,held:r.length}):setImmediate(()=>{(async()=>{for(const o of r)try{if("deliver"===o.via){const e=await(0,V.deliverPrompt)(s,o.sessionId,o.message,{...o.opts??{},...o.kind?{kind:o.kind}:{},...o.partPayload?{partPayload:o.partPayload}:{}})
4592
4606
  if(!e.ok)throw new Error(e.detail??String(e.error))}else await n(o,s)
4593
4607
  t.logger.info("inbox-chat loop: held system note delivered",{sessionId:e,kind:o.kind??null})}catch(r){t.logger.error("inbox-chat loop: held system note delivery failed",{sessionId:e,error:(0,W.getErrorMessage)(r)})}})()})}(e,t)}async function x(e,t,r){const s=t.exchanges[t.exchanges.length-1],n=t.exchanges.filter(e=>e!==s&&!e.askAnnotationId).map(e=>e.exchangeKey).filter(e=>!!e)
4594
- _(e,t),await R(e,t,s,{...r,absorbs:n})}async function P(e,t,r){const s=[...t.exchanges]
4608
+ _(e,t),await R(e,t,s,{...r,absorbs:n})}async function T(e,t,r){const s=[...t.exchanges]
4595
4609
  _(e,t)
4596
- for(const n of s)await R(e,t,n,r)}function T(e,t,r=!0){if(!r||t.askAnnotationId||t.systemNoteRequestId)return[]
4610
+ for(const n of s)await R(e,t,n,r)}function P(e,t,r=!0){if(!r||t.askAnnotationId||t.systemNoteRequestId)return[]
4597
4611
  const s=e.indexOf(t)
4598
- return e.slice(0,Math.max(0,s)).filter(e=>!e.askAnnotationId&&!e.systemNoteRequestId)}async function M(e,t,r,n,o=!0){const i=T(t.exchanges,r,o)
4612
+ return e.slice(0,Math.max(0,s)).filter(e=>!e.askAnnotationId&&!e.systemNoteRequestId)}async function M(e,t,r,n,o=!0){const i=P(t.exchanges,r,o)
4599
4613
  return t.exchanges=t.exchanges.filter(e=>e!==r&&!i.includes(e)),s(t.projectPath),i.length&&t.logger.info("inbox-chat loop: earlier messages answered by this reply",{sessionId:e,markers:i.map(e=>e.markerMessageId)}),R(e,t,r,{...n,absorbs:i.map(e=>e.exchangeKey).filter(e=>!!e)})}async function R(e,t,r,s){if(r.askAnnotationId)return async function(e,t,r,s){const n={sessionId:e,...s.turnDied?{turnDied:!0}:s.turnSilent?{turnSilent:!0}:{turnText:s.turnText??""}}
4600
4614
  for(const s of[0,2e3,8e3]){s>0&&await new Promise(e=>setTimeout(e,s))
4601
4615
  try{const s=await t.apiClient.post(`/api/projects/${t.projectId}/annotations/${encodeURIComponent(r)}/ask-outcome`,n)
@@ -4636,7 +4650,7 @@ if(!s){const t=ne.get(e)
4636
4650
  if(t&&Date.now()-t.at<=oe&&c(t.detail)>=c(r))return
4637
4651
  ne.set(e,{detail:r,at:Date.now()})
4638
4652
  for(const[e,t]of ne)Date.now()-t.at>oe&&ne.delete(e)
4639
- return}s.serveError&&c(s.serveError)>=c(r)||(s.serveError=r,C(e,!1))},exports.describeServeError=a,exports.preMessageDeath=l,exports.hasActiveInboxChatTurns=function(){return re.size>0},exports.isWatchingInboxChatTurn=function(e){return re.has(e)},exports.isSubstantiveReply=d,exports.isRestatement=h,exports.pickTurnReply=p,exports.startsNewTurn=f,exports.endsExchange=g,exports.mergeReplyWithFollowUp=w,exports.turnChangedSomethingAfter=y,exports.ownView=v,exports.hasDeclaredAnswer=b,exports.summarizeTurnTrace=I,exports.extractTurnDeath=S,exports.extractTurnCutOff=$,exports.noteCutOffRelayed=j,exports.cutOffAlreadyRelayed=function(e){return!!e&&he.has(e)},exports.extractTurnSilence=E,exports.absorbedByReply=T
4653
+ return}s.serveError&&c(s.serveError)>=c(r)||(s.serveError=r,C(e,!1))},exports.describeServeError=a,exports.preMessageDeath=l,exports.hasActiveInboxChatTurns=function(){return re.size>0},exports.isWatchingInboxChatTurn=function(e){return re.has(e)},exports.isSubstantiveReply=d,exports.isRestatement=h,exports.pickTurnReply=p,exports.startsNewTurn=f,exports.endsExchange=g,exports.mergeReplyWithFollowUp=w,exports.turnChangedSomethingAfter=y,exports.ownView=v,exports.hasDeclaredAnswer=b,exports.summarizeTurnTrace=I,exports.extractTurnDeath=S,exports.extractTurnCutOff=$,exports.noteCutOffRelayed=j,exports.cutOffAlreadyRelayed=function(e){return!!e&&he.has(e)},exports.extractTurnSilence=E,exports.absorbedByReply=P
4640
4654
  const G=t(60833),W=t(58824),K=t(42593),z=t(4234),V=t(17039),Y=t(81602),J=H(t(79896)),X=H(t(16928)),Q=t(36799),Z=3e5,ee=9e5,te=8e3,re=new Map
4641
4655
  let se=null
4642
4656
  const ne=new Map,oe=6e4,ie=/^(let me|i(?:['\u2019]ll| will| am going to|['\u2019]m going to)|one (?:moment|sec)|checking|looking|on it|sure[,.!]?\s|okay[,.!]?\s|ok[,.!]?\s|first[, ]|hang on|give me)/i,ae=/^(ok(?:ay)?|done|sure|noted|got it|all set|that's it|right)[.!]?$/i,ce=/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Thai}]/u,le=/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Thai}]+/gu,de=/^(?:(?:now|next|then|first|so)[,:]?\s+)?(?:let me|i(?:['\u2019]ll| will| am going to|['\u2019]m going to)|i['\u2019]m (?:now )?(?:checking|looking|nudging|filing|starting|opening|creating|asking|waking|reading|pulling|fetching|comparing|running|updating|writing|going|registering|drafting))\b/i,ue=/get-info|:list|:search|list-areas|read_files|read_file|search|grep|query_history|request_diff|environment_list|environment_get|testing_process_list|check-status|list-sessions|read-messages|results:fetch|commands:list|read_site|:brief|:get\b/i,he=new Set},81602(e,exports){"use strict"
@@ -4654,7 +4668,27 @@ const h=r(await n.json())
4654
4668
  if(!h)continue
4655
4669
  await i.patch(`/api/projects/${a}/cli-sessions/${encodeURIComponent(String(e.id))}`,{status:"completed"}),u.push(t),l.info("Planner replaced by a new planner of the same parent; the dead one is ended",{ended:t,by:s,died:h.kind})}catch(e){l.warn("superseded planners: could not judge one planner (left as it is)",{sessionId:t,error:(0,n.getErrorMessage)(e)})}}return u}
4656
4670
  const s=t(4234),n=t(58824),o=e=>{const t=e?.data??e
4657
- return Array.isArray(t)?t:[]}},58836(e,exports,t){"use strict"
4671
+ return Array.isArray(t)?t:[]}},81570(e,exports){"use strict"
4672
+ exports.noteConfigReloaded=function(e,r=Date.now()){t.set(e,r)},exports.configReloadedAt=function(e){return t.get(e)??0},exports.readLastCall=async function(e,t,r=fetch){const s={}
4673
+ try{const n=await r(`${e.replace(/\/$/,"")}/session/${t}/message?limit=1`,{signal:AbortSignal.timeout(6e3)}),o=n.ok?await n.json():[],i=(Array.isArray(o)?o[o.length-1]:void 0)?.info
4674
+ if(!i)return s
4675
+ const a=Number(i.time?.completed??i.time?.created??0)
4676
+ a&&(s.atMs=a)
4677
+ const c=i.providerID??i.model?.providerID,l=i.modelID??i.model?.modelID
4678
+ c&&(s.providerID=String(c)),l&&(s.modelID=String(l))
4679
+ const d=i.tokens
4680
+ d&&(s.contextTokens=d.total||(d.input??0)+(d.output??0)+(d.cache?.read??0)+(d.cache?.write??0))}catch{}return s},exports.readModelLimits=async function(e,t=fetch){const s=r.get(e)
4681
+ if(s&&Date.now()-s.at<6e5)return s.value
4682
+ const n=new Map
4683
+ try{const s=await t(`${e.replace(/\/$/,"")}/config/providers`,{signal:AbortSignal.timeout(5e3)})
4684
+ if(!s.ok)return n
4685
+ const o=await s.json()
4686
+ for(const e of o?.providers??[]){const t=e?.models??{}
4687
+ for(const[r,s]of Object.entries(t)){const t=s?.limit
4688
+ if("number"!=typeof t?.context)continue
4689
+ const o={context:t.context,..."number"==typeof t.input?{input:t.input}:{},..."number"==typeof t.output?{output:t.output}:{}}
4690
+ n.set(`${e.id}/${r}`,o),s?.id&&s.id!==r&&n.set(`${e.id}/${s.id}`,o)}}r.set(e,{at:Date.now(),value:n})}catch{}return n}
4691
+ const t=new Map,r=new Map},58836(e,exports,t){"use strict"
4658
4692
  function r(e){return"alchemist"===e?1e5:6e4}exports.startSessionCompactor=function(e){let t,i,a=0
4659
4693
  const c=r=>e.apiClient.post("/api/liveness/report",{name:"session-compactor",project:e.projectName,description:`Compacts the hub session idle past its provider's cache window (~60-90 min, per-provider heuristic) with >${Math.round(60)}k context -> summarize so reuse is cheap`,intervalMs:6e5,lastRunAt:(new Date).toISOString(),runs:a,lastAction:t,lastActionAt:i,...r}).catch(()=>{}),l=async()=>{if((0,o.isProjectPaused)())return
4660
4694
  const l=Date.now()
@@ -4784,7 +4818,14 @@ let d=!1
4784
4818
  try{const e=await fetch(`http://127.0.0.1:${c.port}/health`,{signal:AbortSignal.timeout(1500)}),r=await e.json().catch(()=>({}))
4785
4819
  d=e.ok&&r?.projectPath===t}catch{d=!1}return d?{ok:!1,reason:`another action server for this project is already running (pid ${c.pid}, port ${c.port}) and did not retire. Stop it first -- two servers for one project race over the OpenCode serve registry and one of them runs stale code.`}:(i.info("Lock points at a process that does not serve this project; continuing",{lockPath:a,pid:c.pid,port:c.port}),{ok:!0})},exports.writeInstanceLock=function(e,t){try{const r=(0,n.join)(e,o.ALCHEMY_DIR_NAME);(0,s.mkdirSync)(r,{recursive:!0}),(0,s.writeFileSync)((0,n.join)(r,"action-server.lock"),JSON.stringify({pid:process.pid,port:t,startedAt:(new Date).toISOString()}))}catch{}},exports.clearInstanceLock=function(e){try{const t=(0,n.join)(e,o.ALCHEMY_DIR_NAME,"action-server.lock"),r=JSON.parse((0,s.readFileSync)(t,"utf8"))
4786
4820
  r?.pid===process.pid&&(0,s.unlinkSync)(t)}catch{}}
4787
- const s=t(79896),n=t(16928),o=t(14139),i=e=>new Promise(t=>setTimeout(t,e))},40673(e,exports){"use strict"
4821
+ const s=t(79896),n=t(16928),o=t(14139),i=e=>new Promise(t=>setTimeout(t,e))},13751(e,exports){"use strict"
4822
+ exports.sweepSessionUsage=async function(e,t,r,s=8e3,n=fetch){const o=Date.now()+s
4823
+ let i=0
4824
+ try{const s=await n(`${e}/session?start=${Math.floor(t)}&limit=200`,{signal:AbortSignal.timeout(3e3)})
4825
+ if(!s.ok)return{read:i,outOfTime:!1,error:`session list answered ${s.status}`}
4826
+ const a=await s.json()
4827
+ for(const e of Array.isArray(a)?a:[])if(e?.id&&!((e.time?.updated??t)<t)){if(Date.now()>o)return{read:i,outOfTime:!0}
4828
+ await r(e.id),i++}return{read:i,outOfTime:!1}}catch(e){return{read:i,outOfTime:!1,error:e instanceof Error?e.message:String(e)}}}},40673(e,exports){"use strict"
4788
4829
  exports.VISUAL_ENGINE_SOURCE=void 0,exports.VISUAL_ENGINE_SOURCE=String.raw`
4789
4830
  const http = require('http');
4790
4831
  const fs = require('fs');
@@ -5262,7 +5303,7 @@ for(let t=e.row;t<=r.row;t++)for(let o=e.col;o<=r.col;o++)n.push(s[t]?.[o]??"")
5262
5303
  return`${n.slice(0,8).join(", ")}${n.length>8?`, ... (${n.length})`:""}`}const r=t.map(e=>e.ref.replace(/\$/g,""))
5263
5304
  return`${t[0].table?`${t[0].table}!`:""}${2===r.length&&r[0]===r[1]?r[0]:r.join(":")}`},l=c(/<chart:categories\b[^>]*table:cell-range-address="([^"]+)"/.exec(e)?.[1]),d=[...e.matchAll(/<chart:series\b([^>]*)>/g)].map(e=>{const t=c(o(e[1],"chart:values-cell-range-address"))
5264
5305
  return`${c(o(e[1],"chart:label-cell-address"))||"series"}${t?`: ${t}`:""}${l?` by ${l}`:""}`})
5265
- return`[chart: ${T[t]??t}${r?` "${r}"`:""}${d.length?`; ${d.join("; ")}`:""}]`}function $(e,t,r){switch(e){case"text:tab":return r.push("\t"),!0
5306
+ return`[chart: ${P[t]??t}${r?` "${r}"`:""}${d.length?`; ${d.join("; ")}`:""}]`}function $(e,t,r){switch(e){case"text:tab":return r.push("\t"),!0
5266
5307
  case"text:line-break":return r.push("\n"),!0
5267
5308
  case"text:s":return r.push(" ".repeat(Math.min(100,Number(o(t,"text:c")??1)||1))),!0
5268
5309
  default:return!1}}function j(e,t=new Map){const r=[],s=[],i=new M
@@ -5270,7 +5311,7 @@ let a=0,c=0,l=null,d=null
5270
5311
  for(const u of e.matchAll(x)){const[,e,h,p,f,g]=u
5271
5312
  if(void 0!==g){if(c)continue
5272
5313
  d&&l?l[d]=(l[d]??"")+n(g):i.push(n(g))
5273
- continue}if(P.has(h)){f||(c+=e?-1:1)
5314
+ continue}if(T.has(h)){f||(c+=e?-1:1)
5274
5315
  continue}if(c)continue
5275
5316
  const m=s[s.length-1]
5276
5317
  if(e)switch(h){case"svg:title":case"svg:desc":d=null
@@ -5354,7 +5395,7 @@ let s=!1,i=null,a=null,c=null,l=null,d=null,u=0,h=!1
5354
5395
  for(const p of e.matchAll(x)){const[,e,f,g,m,w]=p
5355
5396
  if(void 0!==w){if(u)continue
5356
5397
  d&&l?l[d]=(l[d]??"")+n(w):c&&h&&c.parts.push(n(w))
5357
- continue}if(P.has(f)){m||(u+=e?-1:1)
5398
+ continue}if(T.has(f)){m||(u+=e?-1:1)
5358
5399
  continue}if(u)continue
5359
5400
  const v=()=>{if(!c||!a)return
5360
5401
  let e=c.parts.join("").trim()||c.value||""
@@ -5399,7 +5440,7 @@ let s=null
5399
5440
  const i=new M
5400
5441
  let a=0,c=0,l=null,d=null
5401
5442
  for(const u of e.matchAll(x)){const[,e,h,p,f,g]=u
5402
- if(void 0===g){if(P.has(h))f||(c+=e?-1:1)
5443
+ if(void 0===g){if(T.has(h))f||(c+=e?-1:1)
5403
5444
  else if(!c)if(e)switch(h){case"svg:title":case"svg:desc":d=null
5404
5445
  break
5405
5446
  case"text:p":case"text:h":{const e=i.close()
@@ -5431,7 +5472,7 @@ case"rtf":return{kind:"rtf",text:(0,C.rtfToText)(a),note:"Text, paragraphs and t
5431
5472
  default:throw new Error(`no text form for .${c}`)}}
5432
5473
  const E=t(12661),C=t(43701)
5433
5474
  exports.bi=52428800
5434
- const A=5e3,_={barChart:"bar",bar3DChart:"bar",lineChart:"line",line3DChart:"line",pieChart:"pie",pie3DChart:"pie",ofPieChart:"pie",doughnutChart:"doughnut",areaChart:"area",area3DChart:"area",scatterChart:"scatter",radarChart:"radar",bubbleChart:"bubble",stockChart:"stock",surfaceChart:"surface",surface3DChart:"surface"},x=/<(\/?)([A-Za-z][\w.-]*:[\w.-]+)\b([^>]*?)(\/?)>|([^<]+)/g,P=new Set(["text:note","office:annotation","text:tracked-changes","office:forms","text:sequence-decls"]),T={circle:"pie",ring:"doughnut"}
5475
+ const A=5e3,_={barChart:"bar",bar3DChart:"bar",lineChart:"line",line3DChart:"line",pieChart:"pie",pie3DChart:"pie",ofPieChart:"pie",doughnutChart:"doughnut",areaChart:"area",area3DChart:"area",scatterChart:"scatter",radarChart:"radar",bubbleChart:"bubble",stockChart:"stock",surfaceChart:"surface",surface3DChart:"surface"},x=/<(\/?)([A-Za-z][\w.-]*:[\w.-]+)\b([^>]*?)(\/?)>|([^<]+)/g,T=new Set(["text:note","office:annotation","text:tracked-changes","office:forms","text:sequence-decls"]),P={circle:"pie",ring:"doughnut"}
5435
5476
  class M{constructor(){this.stack=[]}open(e,t){this.stack.push({parts:[],heading:e,listed:t})}get current(){return this.stack[this.stack.length-1]}push(e){this.current?.parts.push(e)}pushSpaced(e){const t=this.current?.parts
5436
5477
  if(!t)return
5437
5478
  const r=t[t.length-1]??""
@@ -5474,16 +5515,16 @@ exports.t="2.1.280"},39068(e,exports,t){"use strict"
5474
5515
  function r(){const e=process.env.ALCHEMY_OPENCODE_DIR
5475
5516
  if(e)return e
5476
5517
  try{const e=("undefined"!=typeof require?require:t(54245)).resolve("alchemylab-opencode/package.json")
5477
- return(0,o.dirname)(e)}catch{}}function s(e){return e>=24}function n(e,t){return{command:function(){if(c)return c
5478
- try{const e=(0,a.execFileSync)("node",["--version"],{encoding:"utf8"}).trim()
5479
- if(s(parseInt(e.replace("v","").split(".")[0],10)))return c="node",c}catch{}const e=process.env.NVM_DIR||(0,o.join)(process.env.HOME||"",".nvm"),t=(0,o.join)(e,"versions","node")
5480
- 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)
5481
- return parseInt(t.replace("v","").split(".")[0],10)-r})
5482
- if(e.length>0){const r=(0,o.join)(t,e[0],"bin","node")
5483
- if((0,i.existsSync)(r))return c=r,c}}catch{}throw new Error(`AlchemyLab requires Node.js 24 or later to run the OpenCode server.\nYour current version: ${process.version}\nInstall a compatible version: nvm install 24 (or download from https://nodejs.org)`)}(),prependArgs:["--no-warnings",(0,o.join)(e,t)]}}exports.findOpencodeDir=r,exports.resolveServeCommand=function(){const e=r()
5484
- return e?n((0,o.join)(e,"dist"),"serve.js"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=r()
5485
- return e?n((0,o.join)(e,"dist"),"run.js"):{command:"opencode",prependArgs:[]}}
5486
- const o=t(16928),i=t(79896),a=t(35317)
5518
+ return(0,n.dirname)(e)}catch{}}function s(e,t){return{command:function(){if(c)return c
5519
+ try{const e=(0,i.execFileSync)("node",["--version"],{encoding:"utf8"}).trim()
5520
+ if((0,a.isSupportedNode)(e))return c="node",c}catch{}const e=process.env.NVM_DIR||(0,n.join)(process.env.HOME||"",".nvm"),t=(0,n.join)(e,"versions","node")
5521
+ if((0,o.existsSync)(t))try{const e=(0,o.readdirSync)(t).filter(e=>(0,a.isSupportedNode)(e)).sort((e,t)=>{const r=e.replace("v","").split(".").map(Number),s=t.replace("v","").split(".").map(Number)
5522
+ return s[0]-r[0]||s[1]-r[1]||s[2]-r[2]})
5523
+ if(e.length>0){const r=(0,n.join)(t,e[0],"bin","node")
5524
+ if((0,o.existsSync)(r))return c=r,c}}catch{}throw new Error((0,a.unsupportedNodeMessage)(process.version))}(),prependArgs:["--no-warnings",(0,n.join)(e,t)]}}exports.findOpencodeDir=r,exports.resolveServeCommand=function(){const e=r()
5525
+ return e?s((0,n.join)(e,"dist"),"serve.js"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=r()
5526
+ return e?s((0,n.join)(e,"dist"),"run.js"):{command:"opencode",prependArgs:[]}}
5527
+ const n=t(16928),o=t(79896),i=t(35317),a=t(84973)
5487
5528
  let c},69706(e,exports,t){"use strict"
5488
5529
  function r(e){try{const t=JSON.parse(a.default.readFileSync(c.default.join(e,l),"utf8"))
5489
5530
  return"string"==typeof t.password&&t.password?t.password:null}catch{return null}}function s(e){const t=process.env.OPENCODE_SERVER_USERNAME||exports.SERVE_USERNAME
@@ -5632,19 +5673,19 @@ try{await n.writeFile(e,JSON.stringify(s,null,2),"utf8"),this.logger.warn("Set o
5632
5673
  let A=null,_=null
5633
5674
  if(l?.projectId&&!l?.openCodeModel)try{_=await this.fetchProjectFromRemote(l.projectId)
5634
5675
  const e=_?.metadata?.defaultModel
5635
- "string"==typeof e&&e.includes("/")&&(A=e)}catch(e){this.logger.warn("Could not fetch project default model; falling back",{error:e instanceof Error?e.message:String(e)})}const x=l?.openCodeModel||A||b||S||"anthropic/claude-opus-4-5"
5636
- "anthropic/claude-opus-4-5"!==x||l?.openCodeModel||A||b||S||this.logger.warn("No default model configured anywhere for this project; agent files fall back to anthropic/claude-opus-4-5",{projectId:l?.projectId??null}),l?.openCodeModel?C.model=l.openCodeModel:A?C.model=A:b?C.model=b:S&&(C.model=S)
5637
- const P=p.join(s,".opencode","agent")
5638
- await n.mkdir(P,{recursive:!0})
5639
- const T=x||"anthropic/claude-opus-4-5-20251101"
5676
+ "string"==typeof e&&e.includes("/")&&(A=e)}catch(e){this.logger.warn("Could not fetch project default model; falling back",{error:e instanceof Error?e.message:String(e)})}const x=l?.openCodeModel||A||b||S||i.FLEET_DEFAULT_MODEL
5677
+ x!==i.FLEET_DEFAULT_MODEL||l?.openCodeModel||A||b||S||this.logger.warn("No default model configured anywhere for this project; agent files fall back to the fleet default model",{projectId:l?.projectId??null}),l?.openCodeModel?C.model=l.openCodeModel:A?C.model=A:b?C.model=b:S&&(C.model=S)
5678
+ const T=p.join(s,".opencode","agent")
5679
+ await n.mkdir(T,{recursive:!0})
5680
+ const P=x||i.FLEET_DEFAULT_MODEL
5640
5681
  let M,R=null,O=null,q=[],D=null
5641
5682
  if(l?.projectId)if(await this.seedAgentsFromRemote(l.projectId),O=_??await this.fetchProjectFromRemote(l.projectId),M=O?.metadata?.globalSystemPrompt,M&&this.logger.info("Using global system prompt from project metadata",{projectId:l.projectId,promptLength:M.length}),M=await this.agentPreambleFor(l.projectId,M),q=await this.fetchMcpServersFromRemote(l.projectId),D=(await this.fetchDefaultWorkflowFromRemote(l.projectId))?.connectionPermissions??null,R=await this.fetchAgentsFromRemote(l.projectId),R&&R.length>0){this.logger.info("Using agents from remote database",{count:R.length})
5642
5683
  const e=await this.resolveWorkflowEffort(l.projectId)
5643
5684
  this.clearAgentMarkdowns()
5644
- for(const t of R)t.enabled&&this.setAgentMarkdown(t.name,this.generateAgentMarkdown(t,m,T,M,{effort:e,extraTools:(0,c.agentToolGrants)(q,t.name,D),limits:(0,i.modelLimitsOf)(O?.metadata),claudeThinking:r(O?.metadata)}))
5645
- await this.removeOnDiskAgentFiles(P,[...R.filter(e=>e.enabled).map(e=>e.name),"explore"]),this.logger.info("Agent prompts held in memory (served to OpenCode over the action server)",{count:this.agentMarkdowns.size})
5685
+ for(const t of R)t.enabled&&this.setAgentMarkdown(t.name,this.generateAgentMarkdown(t,m,P,M,{effort:e,extraTools:(0,c.agentToolGrants)(q,t.name,D),limits:(0,i.modelLimitsOf)(O?.metadata),claudeThinking:r(O?.metadata)}))
5686
+ await this.removeOnDiskAgentFiles(T,[...R.filter(e=>e.enabled).map(e=>e.name),"explore"]),this.logger.info("Agent prompts held in memory (served to OpenCode over the action server)",{count:this.agentMarkdowns.size})
5646
5687
  try{const e=new Set([...R.filter(e=>e.enabled).map(e=>`${e.name}.md`),"explore.md"])
5647
- for(const t of await n.readdir(P))if(t.endsWith(".md")&&!e.has(t)){const e=p.join(P,t),r=`${e}.removed-by-alchemy-${(new Date).toISOString().slice(0,10)}`
5688
+ for(const t of await n.readdir(T))if(t.endsWith(".md")&&!e.has(t)){const e=p.join(T,t),r=`${e}.removed-by-alchemy-${(new Date).toISOString().slice(0,10)}`
5648
5689
  try{await n.rename(e,r),this.logger.info(`Moved agent file ${t} aside (no agent by that name)`,{movedTo:r})}catch(e){this.logger.warn(`Could not move agent file ${t} aside; leaving it`,{error:e instanceof Error?e.message:String(e)})}}}catch(e){this.logger.warn("Orphaned agent-file cleanup failed",{error:e instanceof Error?e.message:String(e)})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
5649
5690
  else this.logger.warn("No projectId provided - agents must be configured via database")
5650
5691
  const N={$schema:"https://opencode.ai/config.json",share:"disabled",model:x,mcp:{[m]:{type:"local",command:[a,w,s],enabled:!0,timeout:6e5}}}
@@ -5763,20 +5804,20 @@ const C=e=>{if(!j)return
5763
5804
  const t=j
5764
5805
  j=null,t.end(e)},A=e=>{u.default.writeFileSync($,e),j=u.default.createWriteStream($,{flags:"a"}),E=e.length},_=e=>{if(j&&(E+=e.length,j.write(e),E>=20971520))try{const e=j
5765
5806
  j=null,e.end(),u.default.renameSync($,`${$}.1`),A(`# opencode-serve.log \u2014 rotated ${(new Date).toISOString()} (previous in opencode-serve.log.1)\n`)}catch(e){this.logger.warn("UnifiedLauncher: opencode log rotation failed; logging stops for this launch",{error:(0,g.getErrorMessage)(e)})}}
5766
- try{u.default.mkdirSync(S,{recursive:!0}),A(`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`)}catch(e){this.logger.warn("UnifiedLauncher: Could not open opencode log file",{error:(0,g.getErrorMessage)(e)})}const x=process.env.ALCHEMY_OPENCODE_LOG_LEVEL||"WARN",P=(0,m.newServePassword)()
5767
- try{(0,m.writeServeAuth)(w,P)}catch(e){this.logger.warn("UnifiedLauncher: could not record the serve password on disk; only this process can reach the serve",{error:(0,g.getErrorMessage)(e)})}(0,m.noteServe)(null,P),(0,c.noteServeLoadedLogins)()
5768
- const T=(0,f.spawn)(I.command,[...I.prependArgs,"--port","0","--data-dir",v,"--log-level",x],{stdio:["ignore","pipe","pipe"],cwd:w,detached:!0,env:{...process.env,...(0,p.getOpencodeSpawnEnv)(w,(0,h.isDevMode)()),...y,OPENCODE_SERVER_PASSWORD:P}})
5807
+ try{u.default.mkdirSync(S,{recursive:!0}),A(`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`)}catch(e){this.logger.warn("UnifiedLauncher: Could not open opencode log file",{error:(0,g.getErrorMessage)(e)})}const x=process.env.ALCHEMY_OPENCODE_LOG_LEVEL||"WARN",T=(0,m.newServePassword)()
5808
+ try{(0,m.writeServeAuth)(w,T)}catch(e){this.logger.warn("UnifiedLauncher: could not record the serve password on disk; only this process can reach the serve",{error:(0,g.getErrorMessage)(e)})}(0,m.noteServe)(null,T),(0,c.noteServeLoadedLogins)()
5809
+ const P=(0,f.spawn)(I.command,[...I.prependArgs,"--port","0","--data-dir",v,"--log-level",x],{stdio:["ignore","pipe","pipe"],cwd:w,detached:!0,env:{...process.env,...(0,p.getOpencodeSpawnEnv)(w,(0,h.isDevMode)()),...y,OPENCODE_SERVER_PASSWORD:T}})
5769
5810
  let M="",R=!1
5770
- if(T.pid)try{const e=l.default.join(w,b,"opencode.json")
5771
- u.default.mkdirSync(l.default.dirname(e),{recursive:!0}),u.default.writeFileSync(e,JSON.stringify({pid:T.pid,port:null,startedAt:(new Date).toISOString()},null,2))}catch(e){this.logger.warn("UnifiedLauncher: could not record the serve pid (an orphan would survive a crash)",{error:(0,g.getErrorMessage)(e)})}const O=e=>{if(R)return
5811
+ if(P.pid)try{const e=l.default.join(w,b,"opencode.json")
5812
+ u.default.mkdirSync(l.default.dirname(e),{recursive:!0}),u.default.writeFileSync(e,JSON.stringify({pid:P.pid,port:null,startedAt:(new Date).toISOString()},null,2))}catch(e){this.logger.warn("UnifiedLauncher: could not record the serve pid (an orphan would survive a crash)",{error:(0,g.getErrorMessage)(e)})}const O=e=>{if(R)return
5772
5813
  M.length<65536&&(M+=e)
5773
5814
  const r=M.match(/listening on http:\/\/[^:]+:(\d+)/)
5774
5815
  if(r&&!R){const e=parseInt(r[1],10)
5775
- R=!0,(0,m.noteServe)(e,P),this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:T.pid,port:e}),this.logger.info("[plugin-diag] port-ready",{pid:T.pid,port:e,...i()}),setTimeout(()=>{this.logger.info("[plugin-diag] port-ready+5s",{pid:T.pid,...i()})},5e3).unref(),setTimeout(()=>{this.logger.info("[plugin-diag] port-ready+15s",{pid:T.pid,...i()})},15e3).unref(),T.unref(),t({success:!0,pid:T.pid,port:e,configPath:a.configPath})}}
5776
- T.stdout?.on("data",e=>{_(e),O(e.toString())}),T.stderr?.on("data",e=>{const t=e.toString()
5816
+ R=!0,(0,m.noteServe)(e,T),this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:P.pid,port:e}),this.logger.info("[plugin-diag] port-ready",{pid:P.pid,port:e,...i()}),setTimeout(()=>{this.logger.info("[plugin-diag] port-ready+5s",{pid:P.pid,...i()})},5e3).unref(),setTimeout(()=>{this.logger.info("[plugin-diag] port-ready+15s",{pid:P.pid,...i()})},15e3).unref(),P.unref(),t({success:!0,pid:P.pid,port:e,configPath:a.configPath})}}
5817
+ P.stdout?.on("data",e=>{_(e),O(e.toString())}),P.stderr?.on("data",e=>{const t=e.toString()
5777
5818
  if(j){const e=t.split("\n")
5778
5819
  for(let t=0;t<e.length;t++){const r=e[t]
5779
- 0===r.length&&t===e.length-1||_(`[stderr] ${r}\n`)}}this.reportPluginLoadFailures(t),O(t)}),T.on("exit",(t,r)=>{if(C(`\n# opencode exited code=${t} signal=${r} at=${(new Date).toISOString()}\n`),R)try{e.onServeExit?.({code:t,signal:r,pid:T.pid})}catch{}}),setTimeout(()=>{R||(R=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{pid:T.pid,outputBuffer:M.slice(0,500)}),T.unref(),t({success:!1,pid:T.pid,configPath:a.configPath,error:`opencode serve did not report a port within 30s (pid ${T.pid??"?"}); its log is in ${l.default.join(w,b,"opencode-serve.log")}. The next launch reaps it.`}))},3e4),T.on("error",e=>{R||(R=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:e.message}),C(`\n# child error: ${e.message}\n`),t({success:!1,error:`Failed to start opencode serve: ${e.message}`}))}),T.on("exit",e=>{R||(R=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:e,output:M.slice(0,500)}),t({success:!1,error:`opencode serve exited with code ${e}`}))})})}}},99498(e,exports,t){"use strict"
5820
+ 0===r.length&&t===e.length-1||_(`[stderr] ${r}\n`)}}this.reportPluginLoadFailures(t),O(t)}),P.on("exit",(t,r)=>{if(C(`\n# opencode exited code=${t} signal=${r} at=${(new Date).toISOString()}\n`),R)try{e.onServeExit?.({code:t,signal:r,pid:P.pid})}catch{}}),setTimeout(()=>{R||(R=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{pid:P.pid,outputBuffer:M.slice(0,500)}),P.unref(),t({success:!1,pid:P.pid,configPath:a.configPath,error:`opencode serve did not report a port within 30s (pid ${P.pid??"?"}); its log is in ${l.default.join(w,b,"opencode-serve.log")}. The next launch reaps it.`}))},3e4),P.on("error",e=>{R||(R=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:e.message}),C(`\n# child error: ${e.message}\n`),t({success:!1,error:`Failed to start opencode serve: ${e.message}`}))}),P.on("exit",e=>{R||(R=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:e,output:M.slice(0,500)}),t({success:!1,error:`opencode serve exited with code ${e}`}))})})}}},99498(e,exports,t){"use strict"
5780
5821
  function r(e){return e.includes(exports.CACHE_MARKER)?null:[o,a,c].every(t=>e.includes(t))?e.replace(a," const movedTexts = [];\n const movedBlocks = [];\n").replace(o,i).replace(c," firstUser.content.unshift(...movedBlocks);"):null}var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
5781
5822
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.CACHE_MARKER=void 0,exports.patchCachePointSource=r,exports.patchClaudeAuthCachePoints=function(e){if(!n.default.existsSync(e))return"missing"
5782
5823
  const t=n.default.readFileSync(e,"utf-8")
@@ -5875,17 +5916,17 @@ const A=E.filter(e=>!1===e.ok&&e.holds&&null==C.get(`${e.requestId}#${e.criterio
5875
5916
  if(A.length){const t=await n(e.runChecks,o,j.filter(e=>A.some(t=>t.requestId===e.requestId&&t.criterionId===e.criterionId)))
5876
5917
  for(const e of t){if(!1!==e.ok)continue
5877
5918
  const t=A.find(t=>t.requestId===e.requestId&&t.criterionId===e.criterionId)
5878
- t&&(t.holds=!1,t.failingBefore=!0)}}const _=E.filter(e=>!1===e.ok&&e.holds),x=Array.from(new Set([...$,...S])),P=new Set(S),T=[]
5919
+ t&&(t.holds=!1,t.failingBefore=!0)}}const _=E.filter(e=>!1===e.ok&&e.holds),x=Array.from(new Set([...$,...S])),T=new Set(S),P=[]
5879
5920
  for(const e of y){if(v.has(e.key))continue
5880
- const r=a(e.verify,x).filter(e=>P.has(e))
5921
+ const r=a(e.verify,x).filter(e=>T.has(e))
5881
5922
  if(0===r.length)continue
5882
5923
  const s=await Promise.all(r.map(async e=>({path:e,blob:(await c(t,["rev-parse",`${i}:${e}`]).catch(()=>"")).trim()||null}))),n=e.keptFiles??[]
5883
- s.every(e=>n.some(t=>t.path===e.path&&t.blob===e.blob))||T.push({requestId:e.requestId,criterionId:e.criterionId,text:e.text.slice(0,300),command:(e.verify[0]??"").slice(0,600),files:s})}const M=_.length>0||T.length>0
5884
- if(await l(p,h,{trigger:"merge",sha:i,baseSha:o,requestId:u,held:M,results:E,changedChecks:T}),g.info("promise checks at merge",{requestId:u,head:i.slice(0,8),promises:y.length,ran:E.length,failed:E.filter(e=>!1===e.ok).length,holding:_.length,checksChanged:T.length}),!M){const e=E.filter(e=>!1===e.ok).length
5924
+ s.every(e=>n.some(t=>t.path===e.path&&t.blob===e.blob))||P.push({requestId:e.requestId,criterionId:e.criterionId,text:e.text.slice(0,300),command:(e.verify[0]??"").slice(0,600),files:s})}const M=_.length>0||P.length>0
5925
+ if(await l(p,h,{trigger:"merge",sha:i,baseSha:o,requestId:u,held:M,results:E,changedChecks:P}),g.info("promise checks at merge",{requestId:u,head:i.slice(0,8),promises:y.length,ran:E.length,failed:E.filter(e=>!1===e.ok).length,holding:_.length,checksChanged:P.length}),!M){const e=E.filter(e=>!1===e.ok).length
5885
5926
  return{hold:!1,breaks:0,summary:E.length?`${E.length} earlier promise check${1===E.length?"":"s"} ran on the merged result${e?`; ${e} already failing before it`:""}; none it broke`:"no earlier promise's check reads these files"}}const R=function(e,t){const r=[]
5886
- return e.length&&r.push(1===e.length?`a promised check fails on the merged result: ${e[0].requestId} promised ${I(e[0].text)}`:`${e.length} promised checks fail on the merged result: ${e.slice(0,3).map(e=>`${e.requestId} ${I(e.text)}`).join("; ")}`),t.length&&r.push(1===t.length?`it changes the check behind the promise ${I(t[0].text)} (${t[0].files.map(e=>e.path).join(", ")})`:`it changes the checks behind ${t.length} promises: ${t.slice(0,3).map(e=>I(e.text)).join("; ")}`),r.join("; and ")}(_,T)
5927
+ return e.length&&r.push(1===e.length?`a promised check fails on the merged result: ${e[0].requestId} promised ${I(e[0].text)}`:`${e.length} promised checks fail on the merged result: ${e.slice(0,3).map(e=>`${e.requestId} ${I(e.text)}`).join("; ")}`),t.length&&r.push(1===t.length?`it changes the check behind the promise ${I(t[0].text)} (${t[0].files.map(e=>e.path).join(", ")})`:`it changes the checks behind ${t.length} promises: ${t.slice(0,3).map(e=>I(e.text)).join("; ")}`),r.join("; and ")}(_,P)
5887
5928
  return"quick-edit"===String(w?.type??"")||await async function(e,t,r){const s=e.mergedRequestId,n=t.length,o=1===n,i=t.slice(0,8).map(e=>`- ${e.requestId} promised "${e.text}". Its check: ${e.command??""}${e.output?`\n ${e.output.replace(/\n/g,"\n ")}`:""}`),a=r.slice(0,8).map(e=>`- ${e.requestId} promised "${e.text}". Its check: ${e.command}\n ${s} changes ${e.files.map(e=>e.path).join(", ")}, which that check runs.`),c=n>0,l=r.length,d=c?`${s} is not merged yet: ${o?`a promised check fails on the merged result: "${t[0].text.slice(0,160)}" (promised by ${t[0].requestId})`:`${n} promised checks fail on the merged result`}. Keep ${o?"the promise":"them"}, or retire ${o?"it":"them"}?`:`${s} changes the check behind ${1===l?`the promise "${r[0].text.slice(0,160)}" (promised by ${r[0].requestId})`:`${l} promises`}. Keep the new check, or send it back?`,u=c?[{id:"keep",label:o?"Keep it":"Keep them",description:`${s} goes back to its worker to fix the code so the check passes. The merge waits.`},{id:"update-check",label:"The check is wrong: update it",description:`${s} goes back to its worker to write a new check for ${o?"the promise":"them"}, which a reviewer checks. The merge waits.`,notePrompt:"What should the check test instead? (optional)"},{id:"retire",label:o?"Retire it":"Retire them",description:`${o?"The promise ends":"They end"}, recorded against ${s}, and ${s} merges now.`}]:[{id:"accept-check",label:"Keep it",description:`${1===l?"The promise now uses":"The promises now use"} the new check, and ${s} merges now.`},{id:"send-back",label:"Send it back",description:`${s} goes back to its worker to leave the check as it was. The merge waits.`,notePrompt:"Anything the worker should know? (optional)"},{id:"retire",label:1===l?"Retire the promise":"Retire them",description:`${1===l?"The promise ends":"They end"}, recorded against ${s}, and ${s} merges now.`}]
5888
- await e.http.post(`/api/projects/${e.projectId}/session-messages/questions`,{...e.askFromSessionId?{fromSessionId:e.askFromSessionId}:{},dedupeKey:`promise-hold:${e.mergedRequestId}`,questionType:"alternatives",question:d,options:u,context:JSON.stringify({type:"promise-hold",requestId:s,failing:n,...l?{checksChanged:l}:{},detail:[...i,...n>8?[`- and ${n-8} more`]:[],...c&&l?[`${s} also changes the check behind ${1===l?"a promise":`${l} promises`}; you are asked about that once these are settled:`]:[],...a,...l>8?[`- and ${l-8} more`]:[]].join("\n")}),priority:"high"})}(e,_,T).catch(e=>g.warn("promise hold: could not raise the card (the hold still stands)",{requestId:u,error:(0,f.getErrorMessage)(e)})),{hold:!0,breaks:_.length+T.length,summary:R}}catch(e){return g.warn("promise checks at merge failed (the merge goes on)",{requestId:u,error:(0,f.getErrorMessage)(e)}),null}},exports.checkPromisesOnMainChange=d,exports.checkAllPromises=async function(e){const t=(await c(e.repoDir,["rev-parse","--verify",`${e.headRef}^{commit}`])).trim(),s=await r(e.http,e.projectId),o=await n(e.runChecks,t,s),i=await l(e.http,e.projectId,{trigger:"all",sha:t,results:o}),a=o.filter(e=>!0===e.ok).length,d=o.filter(e=>!1===e.ok).length
5929
+ await e.http.post(`/api/projects/${e.projectId}/session-messages/questions`,{...e.askFromSessionId?{fromSessionId:e.askFromSessionId}:{},dedupeKey:`promise-hold:${e.mergedRequestId}`,questionType:"alternatives",question:d,options:u,context:JSON.stringify({type:"promise-hold",requestId:s,failing:n,...l?{checksChanged:l}:{},detail:[...i,...n>8?[`- and ${n-8} more`]:[],...c&&l?[`${s} also changes the check behind ${1===l?"a promise":`${l} promises`}; you are asked about that once these are settled:`]:[],...a,...l>8?[`- and ${l-8} more`]:[]].join("\n")}),priority:"high"})}(e,_,P).catch(e=>g.warn("promise hold: could not raise the card (the hold still stands)",{requestId:u,error:(0,f.getErrorMessage)(e)})),{hold:!0,breaks:_.length+P.length,summary:R}}catch(e){return g.warn("promise checks at merge failed (the merge goes on)",{requestId:u,error:(0,f.getErrorMessage)(e)}),null}},exports.checkPromisesOnMainChange=d,exports.checkAllPromises=async function(e){const t=(await c(e.repoDir,["rev-parse","--verify",`${e.headRef}^{commit}`])).trim(),s=await r(e.http,e.projectId),o=await n(e.runChecks,t,s),i=await l(e.http,e.projectId,{trigger:"all",sha:t,results:o}),a=o.filter(e=>!0===e.ok).length,d=o.filter(e=>!1===e.ok).length
5889
5930
  return e.logger.info("every promise checked on main",{sha:t.slice(0,8),ran:o.length,passed:a,failed:d,recorded:i}),{sha:t,ran:o.length,passed:a,failed:d,couldNotRun:o.length-a-d,recorded:i}}
5890
5931
  const u=t(31421),h=t(57975),p=t(82865),f=t(58824),g=(0,h.promisify)(u.execFile),m=e=>e&&"object"==typeof e&&"data"in e?e.data:e,w=new Set(["node","bash","sh","zsh","ruby","perl","deno","bun","tsx","ts-node","php"]),y=new Set(["pytest","jest","vitest","mocha","ava","tap","rspec","phpunit","unittest"]),v=new Set(["npx","env","time","exec"]),b=/(^|\/)(tests?|__tests__|specs?|checks?)\/|(^|\/)test_[^/]*$|[._-](test|spec|check)s?\.[a-z0-9]+$|(^|\/)(check|verify)[^/]*\.[a-z0-9]+$/i,k=e=>e.split("\n").map(e=>e.trim()).filter(Boolean),I=(e,t=100)=>`"${e.length>t?`${e.slice(0,t).trimEnd()}...`:e}"`},88918(e,exports,t){"use strict"
5891
5932
  function r(e,t,r=8e3){return new Promise(s=>{let n
@@ -5922,13 +5963,16 @@ return e.ok?e.version??"":a(3e4)}():await async function(){if(console.log(" In
5922
5963
  const e=await o()
5923
5964
  return e.ok?e.version??"":e.permissionDenied?(c(),null):a(15e3)}()}return null!==h?(console.log("\u2705 Docker ready"+(h?` (server v${h})`:"")),{status:"ready",version:h}):{status:"unavailable",reason:"setup did not complete"}}
5924
5965
  const d=t(35317),u=t(79896),h=t(23785)},89063(e,exports){"use strict"
5925
- exports.P2=exports.Zq=void 0,exports.tokensOfMessage=function(e){const t={input:0,output:0,reasoning:0,cacheRead:0,cacheWrite:0,cost:0,hasUsage:!1}
5966
+ function t(e){return exports.lS[String(e??"").toLowerCase()]??exports.Ey}function r(e){const r=t(e).atLeast
5967
+ return r-Math.min(5*s,r/10)}exports.Ey=exports.lS=void 0,exports.tokensOfMessage=function(e){const t={input:0,output:0,reasoning:0,cacheRead:0,cacheWrite:0,cost:0,hasUsage:!1}
5926
5968
  for(const r of e?.parts||[])if("step-finish"===r?.type&&r?.tokens){t.hasUsage=!0
5927
5969
  const e=r.tokens
5928
5970
  t.input+=e.input||0,t.output+=e.output||0,t.reasoning+=e.reasoning||0,t.cacheRead+=e.cache?.read||0,t.cacheWrite+=e.cache?.write||0,t.cost+=r.cost||0}const r=e?.info
5929
5971
  if(!t.hasUsage&&r?.tokens){const e=r.tokens
5930
5972
  t.input=e.input||0,t.output=e.output||0,t.reasoning=e.reasoning||0,t.cacheRead=e.cache?.read||0,t.cacheWrite=e.cache?.write||0,t.cost=r.cost||0,t.hasUsage=t.input>0||t.output>0||t.cacheRead>0||t.cacheWrite>0||t.cost>0}return t},exports.contextTokensOf=function(e){const t=e?.tokens||{}
5931
- return(t.input??0)+(t.cache?.read??0)},exports.cacheRetentionMsFor=function(e){return exports.Zq[String(e??"").toLowerCase()]??exports.P2},exports.Zq={openai:54e5,anthropic:39e5},exports.P2=36e5},82272(e,exports){"use strict"
5973
+ return(t.input??0)+(t.cache?.read??0)},exports.cacheRetentionMsFor=function(e){return t(e).atMost},exports.cacheWarmForMs=r,exports.longestCacheWarmMs=function(){return Math.max(r(null),...Object.keys(exports.lS).map(r))}
5974
+ const s=6e4
5975
+ exports.lS={anthropic:{atLeast:60*s,atMost:65*s},openai:{atLeast:60*s,atMost:90*s}},exports.Ey={atLeast:5*s,atMost:60*s}},82272(e,exports){"use strict"
5932
5976
  exports.partMeta=function(e,t){return t?{alchemy:{kind:e,payload:t}}:{alchemy:{kind:e}}},exports.alchemyMeta=function(e){return{_meta:{alchemy:e}}},exports.withAlchemyKind=function(e,t,r){const s=e._meta,n=s?.alchemy??{}
5933
5977
  return{...e,_meta:{...s,alchemy:{...n,...r,kind:t}}}}},39844(e,exports,t){"use strict"
5934
5978
  exports.deliverToSession=async function(e,t,s={}){const n=s.noVerify?0:s.verifySeconds??20
@@ -6013,8 +6057,8 @@ try{a=Number((await n.raw(["rev-list","--count",`${i}..${o}`])).trim())||0}catch
6013
6057
  try{c=(await n.raw(["grep","-l","-e","^<<<<<<< ",i,"--"])).split("\n").map(e=>e.trim()).filter(Boolean).map(e=>e.replace(/^[^:]*:/,""))}catch{}return{defaultBranch:o,mainAheadBy:a,markerFiles:c}}
6014
6058
  const r=t(83807),s=/\b(conflict|merge|main (has )?moved|behind main|ahead of|rebase|diverg)/i,n=/\b(approval|approve[ds]?|denied|gated|authoriz|permission|clear(ed)? (the|a|this) (path|command|edit))\b/i,o=/\b(associat|reattach|attached to no|not attached|no request association|task association|session association)/i
6015
6059
  exports.BLOCKED_ON_HELP='A blocked report on your own request must say what it needs, in blockedOn: "decision" (a question goes to your parent and escalates to the user) or "req000123" (a wait is declared on that request and you are told when it settles). A block that names nothing is not filed. There is no hand check: the user checks their own [user-input] criteria on the card at the end of the work, so complete the request when it is built.'},22316(e,exports){"use strict"
6016
- function t(e){return"autonomous"===e||"collaborative"===e?e:"balanced"}exports.me=exports.Ow=void 0,exports.normalizePosture=t,exports.resolveQuestionGate=function(e,r){const s=t(r)
6017
- return e.targeted?{disposition:"ask-now",reason:"targeted at a session (agent-to-agent), not the human"}:e.humanInputFloor?{disposition:"ask-now",reason:"user-input criterion on this request -- the user must be asked; outranks the posture"}:e.userAskedToBeAsked?{disposition:"ask-now",reason:"the user asked to be asked -- their words outrank the posture"}:"high"===e.priority?{disposition:"ask-now",reason:"high priority -- treated as blocking; always reaches the human"}:"escalate"===e.onExpiry?{disposition:"ask-now",reason:"asker requested escalate-on-expiry -- a human must see it"}:"collaborative"===s?{disposition:"ask-now",reason:"collaborative posture: the human wants to be consulted",hint:"Collaborative posture: keep surfacing the design choices in this work as specific, defaulted questions (options with tradeoffs, recommendation first) -- the user wants to weigh in."}:"autonomous"===s?e.hasDefault?{disposition:"self-answer",reason:"autonomous posture and the question carries a default -- the question is closed on it and the user can still change it"}:{disposition:"bounce",reason:"autonomous posture and no default -- the asker must choose for itself"}:e.hasDefault&&"low"===e.priority?{disposition:"defer-batch",reason:"balanced posture, low priority, and a default to proceed on -- held for the next checkpoint bundle"}:{disposition:"ask-now",reason:"balanced posture: questions post normally (non-blocking when a default is set)"}},exports.resolveBriefHoldPolicy=function(e){return"collaborative"===t(e)?{holdMs:exports.me,reason:"collaborative posture: the user decides the brief before the work happens"}:{holdMs:0,reason:"the worker proceeds on its stated defaults; only collaborative holds a brief"}},exports.proceedsOnDefault=function(e){return("string"==typeof e.defaultOption?""!==e.defaultOption.trim():null!=e.defaultOption)&&"escalate"!==e.onExpiry&&"how"!==e.steer&&"high"!==e.priority&&!(e.namedCriteria&&e.namedCriteria>0)},exports.resolveHoldPolicy=function(e,r={}){const s=t(e)
6060
+ function t(e){return"autonomous"===e||"collaborative"===e?e:"balanced"}exports.me=exports.Ow=void 0,exports.offersNotYet=function(e){return Array.isArray(e)&&e.some(e=>!!e&&"object"==typeof e&&("not-yet"===e.effect||!0===e.notChecked))},exports.normalizePosture=t,exports.resolveQuestionGate=function(e,r){const s=t(r)
6061
+ return e.targeted?{disposition:"ask-now",reason:"targeted at a session (agent-to-agent), not the human"}:e.userAskedToBeAsked?{disposition:"ask-now",reason:"the user asked to be asked -- their words outrank the posture"}:e.namesCriteria&&!e.offersNotYet?{disposition:"ask-now",reason:'blocking user-input question (no "not yet" option) -- the user must answer before the build goes on'}:"autonomous"===s?e.hasDefault||e.namesCriteria?{disposition:"self-answer",reason:e.namesCriteria?'autonomous posture and the question names [user-input] criteria -- answered "not yet"; the work goes on with its placeholder and the end card asks':"autonomous posture and the question carries a default -- the question is closed on it and the user can still change it"}:"high"===e.priority?{disposition:"ask-now",reason:"high priority and no default -- nothing to take, so it reaches the human"}:{disposition:"bounce",reason:"autonomous posture and no default -- the asker must choose for itself"}:e.humanInputFloor||e.namesCriteria?{disposition:"ask-now",reason:"user-input criterion on this request -- the user must be asked"}:"high"===e.priority?{disposition:"ask-now",reason:"high priority -- treated as blocking; always reaches the human"}:"escalate"===e.onExpiry?{disposition:"ask-now",reason:"asker requested escalate-on-expiry -- a human must see it"}:"collaborative"===s?{disposition:"ask-now",reason:"collaborative posture: the human wants to be consulted",hint:"Collaborative posture: keep surfacing the design choices in this work as specific, defaulted questions (options with tradeoffs, recommendation first) -- the user wants to weigh in."}:e.hasDefault&&"low"===e.priority?{disposition:"defer-batch",reason:"balanced posture, low priority, and a default to proceed on -- held for the next checkpoint bundle"}:{disposition:"ask-now",reason:"balanced posture: questions post normally (non-blocking when a default is set)"}},exports.resolveBriefHoldPolicy=function(e){return"collaborative"===t(e)?{holdMs:exports.me,reason:"collaborative posture: the user decides the brief before the work happens"}:{holdMs:0,reason:"the worker proceeds on its stated defaults; only collaborative holds a brief"}},exports.proceedsOnDefault=function(e){return("string"==typeof e.defaultOption?""!==e.defaultOption.trim():null!=e.defaultOption)&&"escalate"!==e.onExpiry&&"how"!==e.steer&&"high"!==e.priority&&!(e.namedCriteria&&e.namedCriteria>0)},exports.resolveHoldPolicy=function(e,r={}){const s=t(e)
6018
6062
  return"balanced"===s&&r.proceedsOnDefault?{holdMs:0,reason:"balanced posture and the question goes ahead on its default: no wait"}:"collaborative"===s?{holdMs:exports.me,reason:"collaborative posture: courtesy window for an inline answer; then end-turn-and-wait indefinitely"}:"balanced"===s?{holdMs:exports.Ow,reason:"balanced posture: give the human a bounded window, then degrade to async"}:{holdMs:0,reason:"autonomous posture: never hold -- async delivery"}},exports.Ow=6e5,exports.me=18e4},70508(e,exports){"use strict"
6019
6063
  exports.requestNeedingAnchor=function(e,s){if(r(s.requestId)||r(s.area))return null
6020
6064
  if(r(s.anchor)&&(!(n=s.anchor)||"object"!=typeof n||"project"!==n.type))return null
@@ -7099,43 +7143,54 @@ n.push(t),this.histograms.set(s,n)}recordValue(e,t,r={}){this.observeHistogram(e
7099
7143
  const t=Date.now()
7100
7144
  return(r={})=>{const s=Date.now()-t
7101
7145
  this.observeHistogram(e,s,r)}}getMetrics(){return{counters:Object.fromEntries(this.counters),gauges:Object.fromEntries(this.gauges),histograms:Object.fromEntries(this.histograms)}}reset(){this.counters.clear(),this.gauges.clear(),this.histograms.clear()}}},95671(e,exports,t){"use strict"
7102
- function r(e,t){const r=a[e]
7103
- if(!r)return null
7104
- const s=[...r].sort((e,t)=>t.length-e.length)
7105
- for(const e of s)if(t.includes(e))return e
7106
- return null}function s(e){const[t,...r]=e.split("/")
7107
- return{provider:t||"anthropic",modelId:r.join("/")||e}}function n(e,t,n,o,i){const l=s(e)
7108
- let d=l.provider
7109
- if("inherit"!==t){if(!o[t])return{provider:l.provider,modelId:l.modelId,fullModel:e,resolved:!1,reason:`Preferred provider '${t}' not available, using main session model`}
7110
- d=t}const u=r(l.provider,l.modelId),h=a[l.provider],p=a[d]
7111
- if(!u||!h||!p)return{provider:l.provider,modelId:l.modelId,fullModel:e,resolved:!1,reason:`Could not determine model family for '${l.modelId}', using main session model`}
7112
- const f=h.indexOf(u),g=exports.Fi[n]??0
7113
- let m
7114
- if(d===l.provider)m=f+g
7115
- else{const e=h.length>1?f/(h.length-1):0
7116
- m=Math.round(e*(p.length-1))+g}m=Math.min(m,p.length-1),m=Math.max(m,0),m=function(e,t,s){const n=a[e],o=s?.[e]
7117
- if(!n||!o)return t
7118
- const i=t=>{if(!t)return-1
7119
- const s=r(e,t)
7120
- return s?n.indexOf(s):-1},c=i(o.ceiling)
7121
- c>=0&&(t=Math.max(t,c))
7122
- const l=i(o.floor)
7123
- return l>=0&&(t=Math.min(t,l)),t}(d,m,i)
7124
- const w=p[m],y=c[d]?.[w]
7125
- return y?{provider:d,modelId:y,fullModel:`${d}/${y}`,resolved:!0}:{provider:l.provider,modelId:l.modelId,fullModel:e,resolved:!0,reason:`No known model for ${d}/${w}, using main session model`}}exports.Fi=void 0,exports.modelLimitsOf=function(e){const t=e?.modelLimits
7126
- return t&&"object"==typeof t&&!Array.isArray(t)?t:null},exports.resolveModelTier=n,exports.resolveAgentModel=function(e,t="inherit",r="same",a,c){const l=a||function(e){try{const t=(0,i.join)(e,"opencode.json")
7127
- if((0,o.existsSync)(t))return JSON.parse((0,o.readFileSync)(t,"utf8")).model||null}catch(e){}return null}(e)
7128
- if(!l)return{provider:"anthropic",modelId:"claude-sonnet-5",fullModel:"anthropic/claude-sonnet-5",resolved:!1,reason:"Could not determine main session model, using default"}
7129
- if("inherit"===t&&"same"===r&&!c?.[s(l).provider]){const e=s(l)
7130
- return{provider:e.provider,modelId:e.modelId,fullModel:l,resolved:!0}}return n(l,t,r,function(e){const t={anthropic:!1,openai:!1}
7131
- try{const r=(0,i.join)(e,"opencode.json")
7132
- if((0,o.existsSync)(r)){const e=JSON.parse((0,o.readFileSync)(r,"utf8"))
7133
- if(e.provider&&((e.provider.anthropic?.options?.apiKey||process.env.ANTHROPIC_API_KEY)&&(t.anthropic=!0),(e.provider.openai?.options?.apiKey||process.env.OPENAI_API_KEY)&&(t.openai=!0)),e.model){const{provider:r}=s(e.model)
7134
- t[r]=!0}}process.env.ANTHROPIC_API_KEY&&(t.anthropic=!0),process.env.OPENAI_API_KEY&&(t.openai=!0)}catch(e){t.anthropic=!0}return t}(e),c)}
7135
- const o=t(79896),i=t(16928),a={anthropic:["opus","sonnet","haiku"],openai:["gpt-6-astra","gpt-5.6-sol","gpt-5.6-terra","gpt-5.6-luna"]},c={anthropic:{opus:"claude-opus-5",sonnet:"claude-sonnet-5",haiku:"claude-haiku-4-5"},openai:{"gpt-6-astra":"gpt-6-astra","gpt-5.6-sol":"gpt-5.6-sol","gpt-5.6-terra":"gpt-5.6-terra","gpt-5.6-luna":"gpt-5.6-luna"}}
7146
+ function r(e){return e&&exports.nN[e]||exports.nN[exports.wR[0]]}function s(e,t){if("anthropic"===e)return u
7147
+ if("openai"!==e)return null
7148
+ if(!t)return h
7149
+ const r=t.match(/^gpt-(\d+(?:\.\d+)?)-/)?.[1]
7150
+ return"6"===r||r?.startsWith("6.")?h:"5.6"===r?p:null}function n(e,t,r){if(!r)return-1
7151
+ const s=function(e,t){return"anthropic"===e?t.match(/(?:^|-)(opus|sonnet|haiku)(?:-|$)/)?.[1]??null:"openai"===e?t.match(/^gpt-\d+(?:\.\d+)?-(astra|sol|terra|luna)(?:-|$)/)?.[1]??null:null}(e,r)??(t.rungs.includes(r)?r:null)
7152
+ return s?t.rungs.indexOf(s):-1}function o(e,t){const r=exports.nN[e],s=r?n(e,t,r.split("/").slice(1).join("/")):-1
7153
+ return s>=0?s:0}function i(e){const[t,...r]=e.split("/")
7154
+ return{provider:t||"anthropic",modelId:r.join("/")||e}}function a(e,t,r,s){const o=s?.[e]
7155
+ if(!o)return r
7156
+ const i=n(e,t,o.ceiling)
7157
+ i>=0&&(r=Math.max(r,i))
7158
+ const a=n(e,t,o.floor)
7159
+ return a>=0&&(r=Math.min(r,a)),r}function c(e,t,r,c,l){const d=i(e)
7160
+ let u=d.provider
7161
+ if("inherit"!==t){if(!c[t])return{provider:d.provider,modelId:d.modelId,fullModel:e,resolved:!1,reason:`Preferred provider '${t}' not available, using main session model`}
7162
+ u=t}const h=s(d.provider,d.modelId),p=h?n(d.provider,h,d.modelId):-1,f=u===d.provider?h:s(u)
7163
+ if(!h||p<0||!f)return{provider:d.provider,modelId:d.modelId,fullModel:e,resolved:!1,reason:`Could not determine model family for '${d.modelId}', using main session model`}
7164
+ const g=exports.Fi[r]??0
7165
+ let m=(u===d.provider?p:o(u,f)+(p-o(d.provider,h)))+g
7166
+ m=Math.min(Math.max(m,0),f.rungs.length-1),m=a(u,f,m,l)
7167
+ const w=f.models[f.rungs[m]]
7168
+ return{provider:u,modelId:w,fullModel:`${u}/${w}`,resolved:!0}}exports.Fi=exports.FLEET_DEFAULT_MODEL=exports.wR=exports.nN=void 0,exports.defaultModelFor=r,exports.primaryProviderOf=function(e,t){const r=t&&t.includes("/")?t.split("/")[0]:null
7169
+ return e?r&&e.includes(r)?r:exports.wR.find(t=>e.includes(t))??null:r},exports.modelLimitsOf=function(e){const t=e?.modelLimits
7170
+ return t&&"object"==typeof t&&!Array.isArray(t)?t:null},exports.carryModelToProvider=function(e,t,r){const{provider:c,modelId:l}=i(e)
7171
+ if(c===t)return e
7172
+ const d=s(c,l),u=s(t)
7173
+ if(!d||!u)return null
7174
+ const h=n(c,d,l)
7175
+ if(h<0)return null
7176
+ let p=o(t,u)+(h-o(c,d))
7177
+ return p=a(t,u,Math.min(Math.max(p,0),u.rungs.length-1),r),`${t}/${u.models[u.rungs[p]]}`},exports.resolveModelTier=c,exports.resolveAgentModel=function(e,t="inherit",r="same",s,n){const o=s||function(e){try{const t=(0,d.join)(e,"opencode.json")
7178
+ if((0,l.existsSync)(t))return JSON.parse((0,l.readFileSync)(t,"utf8")).model||null}catch(e){}return null}(e)
7179
+ if(!o){const{provider:e,modelId:t}=i(exports.FLEET_DEFAULT_MODEL)
7180
+ return{provider:e,modelId:t,fullModel:exports.FLEET_DEFAULT_MODEL,resolved:!1,reason:"Could not determine main session model, using default"}}if("inherit"===t&&"same"===r&&!n?.[i(o).provider]){const e=i(o)
7181
+ return{provider:e.provider,modelId:e.modelId,fullModel:o,resolved:!0}}return c(o,t,r,function(e){const t={anthropic:!1,openai:!1}
7182
+ try{const r=(0,d.join)(e,"opencode.json")
7183
+ if((0,l.existsSync)(r)){const e=JSON.parse((0,l.readFileSync)(r,"utf8"))
7184
+ if(e.provider&&((e.provider.anthropic?.options?.apiKey||process.env.ANTHROPIC_API_KEY)&&(t.anthropic=!0),(e.provider.openai?.options?.apiKey||process.env.OPENAI_API_KEY)&&(t.openai=!0)),e.model){const{provider:r}=i(e.model)
7185
+ t[r]=!0}}process.env.ANTHROPIC_API_KEY&&(t.anthropic=!0),process.env.OPENAI_API_KEY&&(t.openai=!0)}catch(e){t.anthropic=!0}return t}(e),n)}
7186
+ const l=t(79896),d=t(16928)
7187
+ exports.nN={openai:"openai/gpt-6.1-sol",anthropic:"anthropic/claude-opus-5-5"},exports.wR=["openai","anthropic"],exports.FLEET_DEFAULT_MODEL=r(null)
7188
+ const u={rungs:["opus","sonnet","haiku"],models:{opus:"claude-opus-5-5",sonnet:"claude-sonnet-5",haiku:"claude-haiku-4-5"}},h={rungs:["astra","sol","luna"],models:{astra:"gpt-6-astra",sol:"gpt-6.1-sol",luna:"gpt-6-luna"}},p={rungs:["astra","sol","terra","luna"],models:{astra:"gpt-6-astra",sol:"gpt-5.6-sol",terra:"gpt-5.6-terra",luna:"gpt-5.6-luna"}}
7136
7189
  exports.Fi={two_up:-2,one_up:-1,same:0,one_down:1,two_down:2}},19670(e,exports){"use strict"
7137
7190
  exports.namedRefsNote=function(e){const t=Array.isArray(e?.named)?e.named:[],r=Array.isArray(e?.left)?e.left:[],s=[]
7138
- return t.length&&s.push(`Internal ids were replaced with what they name, since the user cannot use them: ${t.map(e=>`${e.from} is now ${e.to}`).join("; ")}.`),r.length&&s.push(`Left as written: ${r.map(e=>`${e.id} (${e.why})`).join("; ")}. If these are AlchemyLab ids, the user cannot use them; say what they refer to instead.`),s.length?`\n\n${s.join(" ")}`:""}},90629(e,exports,t){"use strict"
7191
+ return t.length&&s.push(`Internal ids were replaced with what they name, since the user cannot use them: ${t.map(e=>`${e.from} is now ${e.to}`).join("; ")}.`),r.length&&s.push(`Left as written: ${r.map(e=>`${e.id} (${e.why})`).join("; ")}. If these are AlchemyLab ids, the user cannot use them; say what they refer to instead.`),s.length?`\n\n${s.join(" ")}`:""}},84973(e,exports){"use strict"
7192
+ exports.N=void 0,exports.isSupportedNode=function(e){const[t,r]=String(e).replace(/^v/,"").split(".").map(e=>parseInt(e,10))
7193
+ return!!Number.isFinite(t)&&(t>=24||22===t&&Number.isFinite(r)&&r>=16)},exports.unsupportedNodeMessage=function(e){return`AlchemyLab needs ${exports.N}; this is Node ${String(e).replace(/^v/,"")}.\nInstall a current Node (https://nodejs.org) or switch with nvm: \`nvm install 24 && nvm use 24\`, then run alchemy again.`},exports.N="Node 22.16 or newer (not 23), or Node 24 and newer"},90629(e,exports,t){"use strict"
7139
7194
  exports.openBrowser=function(e,t="Open the link above in a browser on any device; the login completes in this window."){const n=(0,s.platform)(),o="darwin"===n?"open":"win32"===n?"cmd":"xdg-open",i="win32"===n?["/c","start","",e]:[e],a=()=>{console.log(` No browser could be opened here. ${t}`)}
7140
7195
  if("linux"===n&&!process.env.DISPLAY&&!process.env.WAYLAND_DISPLAY)return a(),!1
7141
7196
  try{const e=(0,r.spawn)(o,i,{stdio:"ignore",detached:!0})
@@ -7153,7 +7208,21 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeUrlFro
7153
7208
  try{if(!s.default.existsSync(t))return null
7154
7209
  const r=JSON.parse(s.default.readFileSync(t,"utf-8"))
7155
7210
  if(r.port)return(0,o.noteServe)(r.port,(0,o.currentServePassword)(e)),`http://127.0.0.1:${r.port}`}catch{}return null}
7156
- const s=r(t(79896)),n=r(t(16928)),o=t(69706)},6156(e,exports,t){"use strict"
7211
+ const s=r(t(79896)),n=r(t(16928)),o=t(69706)},26457(e,exports,t){"use strict"
7212
+ function r(e){try{let t=(0,o.dirname)((0,n.realpathSync)(e))
7213
+ for(let e=0;e<4;e++){const e=(0,o.join)(t,"package.json")
7214
+ if((0,n.existsSync)(e))return"alchemylab"===JSON.parse((0,n.readFileSync)(e,"utf8")).name?t:null
7215
+ t=(0,o.dirname)(t)}}catch{}return null}function s(e){try{return String(JSON.parse((0,n.readFileSync)((0,o.join)(e,"package.json"),"utf8")).version||"?")}catch{return"?"}}exports.otherInstalls=function(e,t=process.env.PATH||""){const i=r(e)
7216
+ if(!i)return[]
7217
+ const a=new Set([i]),c=[]
7218
+ for(const e of t.split(o.delimiter)){if(!e)continue
7219
+ const t=(0,o.join)(e,"win32"===process.platform?"alchemylab.cmd":"alchemylab")
7220
+ if(!(0,n.existsSync)(t))continue
7221
+ const i=r(t)
7222
+ i&&!a.has(i)&&(a.add(i),c.push({root:i,version:s(i)}))}return c},exports.otherInstallsNotice=function(e,t){if(0===t.length)return[]
7223
+ const n=r(e)??e
7224
+ return[`Note: AlchemyLab is installed more than once. Running ${n} (v${s(n)}); also found:`,...t.map(e=>` ${e.root} (v${e.version})`)," `alchemy` runs whichever comes first on your PATH. Remove the one you do not use:"," npm uninstall -g alchemylab, or brew uninstall alchemylab."]}
7225
+ const n=t(79896),o=t(16928)},6156(e,exports,t){"use strict"
7157
7226
  function r(e){const t=new Map
7158
7227
  let r=null,s=null,n=null
7159
7228
  for(const o of e.split("\n")){if(o.startsWith("diff --git ")){s=null,n=null,r=null
@@ -7211,7 +7280,7 @@ for(const{h:e}of t){const t=e.text.split("\n"),r=Math.min(t.length,30,Math.max(h
7211
7280
  a.push(...t.slice(0,r)),p+=t.length-r,h-=r}const f=p>0?`\n(${p} more lines not shown.)`:""
7212
7281
  return`${i}\n\`\`\`diff\n${a.join("\n")}\n\`\`\`${f}\nAll of it: git diff ${e.base.slice(0,10)}..requests/${e.req.id} -- ${s.relPath}`}}},4871(e,exports){"use strict"
7213
7282
  exports.R=exports.AUTONOMOUS_PLANNER_MARKER=void 0,exports.buildAutonomousPlannerContext=function(e){const t=e?.requestId?`"${e.requestId}"`:"the request you were spawned for"
7214
- return`${!1===e?.autoBuild?exports.R:exports.AUTONOMOUS_PLANNER_MARKER}\nYou are running as an AUTONOMOUS background planner. No user is watching this session \u2014 deliver a complete draft without user interaction.\n- FIRST MOVE, before any research: decide whether ${t} is ONE piece of work. If one worker could make the change as written, do NOT write a plan -- call session_message_operations offer-handoff immediately with { summary: "single piece of work, no plan", requestIds: [${t}] } and STOP. A plan is a guiding document that yields SEVERAL requests, never a document per request; planning a one-line change costs minutes and adds nothing (measured 2026-09-02).\n- Research first: spawn research agents (subagent_operations spawn { agent: "research", prompt: "..." }) or use research_fanout, and wait for the reports before writing.\n- Fill in the plan document section by section (plan_write update for the title + first section, then append one section at a time). Your session context gives the planId; if none was given, create the document with plan_write create first.\n- Include a "Risks & Assumptions" section: when something is ambiguous, make a reasonable choice and note your reasoning there. Never write questions into the plan \u2014 plans contain decisions, not questions.\n- Snapshot when done: plan_write create-snapshot with a label like "Draft v1".\n- Decompose the plan into requests WITHOUT asking permission first.\n- Only if you genuinely cannot proceed without a scope or priority decision, ask your PARENT session (session_message_operations send, type "question", toSessionId "parent") \u2014 never wait on the user directly.\n- Finish by calling session_message_operations offer-handoff ({ summary, requestIds }), then STOP and wait. If you idle after writing the plan without offering, the server nudges you once (idle rail).`},exports.AUTONOMOUS_PLANNER_MARKER="[Planner Flavor: autonomous]",exports.R="[Planner Flavor: autonomous-plan, human-build]"},66800(e,exports,t){"use strict"
7283
+ return`${!1===e?.autoBuild?exports.R:exports.AUTONOMOUS_PLANNER_MARKER}\nYou are running as an AUTONOMOUS background planner. No user is watching this session \u2014 deliver a complete draft without user interaction.\n- FIRST MOVE, before any research: decide whether ${t} is ONE piece of work. If one worker could make the change as written, do NOT write a plan -- call session_message_operations offer-handoff immediately with { summary: "single piece of work, no plan", requestIds: [${t}] } and STOP. A plan is a guiding document that yields SEVERAL requests, never a document per request; planning a one-line change costs minutes and adds nothing (measured 2026-09-02).\n- Research first: spawn research agents (subagent_operations spawn { agent: "research", prompt: "..." }) or use research_fanout, and wait for the reports before writing.\n- Write the plan document in a few writes (plan_write update for the title and first sections, then append the rest, several sections per call). Your session context gives the planId; if none was given, create the document with plan_write create first.\n- Include a "Risks & Assumptions" section: when something is ambiguous, make a reasonable choice and note your reasoning there. Never write questions into the plan \u2014 plans contain decisions, not questions.\n- Snapshot when done: plan_write create-snapshot with a label like "Draft v1".\n- Decompose the plan into requests WITHOUT asking permission first.\n- Only if you genuinely cannot proceed without a scope or priority decision, ask your PARENT session (session_message_operations send, type "question", toSessionId "parent") \u2014 never wait on the user directly.\n- Finish by calling session_message_operations offer-handoff ({ summary, requestIds }), then STOP and wait. If you idle after writing the plan without offering, the server nudges you once (idle rail).`},exports.AUTONOMOUS_PLANNER_MARKER="[Planner Flavor: autonomous]",exports.R="[Planner Flavor: autonomous-plan, human-build]"},66800(e,exports,t){"use strict"
7215
7284
  function r(e){return new Promise(t=>{const r=n.default.createServer()
7216
7285
  r.once("error",e=>{e.code,t(!1)}),r.once("listening",()=>{r.close(()=>{t(!0)})}),r.listen(e)})}var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
7217
7286
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.isPortAvailable=r,exports.findAvailablePort=async function(e,t=50){for(let s=0;s<t;s++){const t=e+s
@@ -7466,7 +7535,7 @@ const t=null!==await this.getToken()
7466
7535
  return this.TOKEN_CACHE.hasValidToken={value:t,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:t},t}getTokenDirectoryPath(){return this.tokenDirPath}getManualTokenFilePath(){return s.default.join(this.tokenDirPath,"manual-token.json")}logTokenLocationInstructions(){const e=this.getManualTokenFilePath()
7467
7536
  console.log("========================================"),console.log("To use the alchemy CLI, you need to authenticate."),console.log(`Token file location: ${e}`),console.log("You can either:"),console.log('\n1. Use the authenticate tool (recommended)\n2. Manually place a token file at the above location\n3. Set up automatic authentication flow\n\nFor manual token placement, create a JSON file with:\n{\n "token": "your_token_here",\n "userId": "your_user_id",\n "username": "your_username", \n "role": "user",\n "expiresAt": "2025-12-31T23:59:59.000Z"\n}\n'),console.log("For more information, see the authentication documentation."),console.log("========================================")}async startAuthenticationFlow(){this.pendingAuthCheck=!0
7468
7537
  try{const{tempCode:e,authUrl:t}=await this.authFlow.startAuthFlow()
7469
- return this.tempAuthCode=e,this.logger.info("Started external authentication flow",{tempCode:e,authUrl:t}),this.authFlow.startPolling(e,async t=>{this.logger.info("Authentication flow completed",{tempCode:e.substring(0,4)+"***",username:t.username}),await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),console.log("Authentication successful!"),console.log(`Welcome, ${t.username}!`),console.log("You can now use the alchemy CLI."),console.log("========================================")},()=>{this.logger.info("Authentication flow timed out",{tempCode:e.substring(0,4)+"***"}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow timed out."),console.log("Please try again or manually place a token file."),console.log("Token file location:"),console.log(`${this.getManualTokenFilePath()}`),console.log("========================================")},t=>{this.logger.error("Authentication flow error",{tempCode:e.substring(0,4)+"***",error:t}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow error."),console.log(`Error: ${t}`),console.log("Please try again or manually place a token file."),console.log(`${this.getManualTokenFilePath()}`),console.log("========================================")}),{tempCode:e,authUrl:t}}catch(e){throw this.logger.error("Error starting authentication flow",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,e}}hasPendingAuth(){return this.pendingAuthCheck||!!this.tempAuthCode}getAuthStatus(){return this.hasPendingAuth()?{hasToken:!1,isPendingAuth:!0,tempCode:this.tempAuthCode,authUrl:this.authFlow.getAuthUrl(this.tempAuthCode)}:{hasToken:!1,isPendingAuth:!1}}async checkAuthStatus(e){if(!e)return null
7538
+ return this.tempAuthCode=e,this.logger.info("Started external authentication flow",{tempCode:e,authUrl:t}),this.authFlow.startPolling(e,async t=>{this.logger.info("Authentication flow completed",{tempCode:e.substring(0,4)+"***",username:t.username}),await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),console.log(`\u2705 Signed in as ${t.username}.`)},()=>{this.logger.info("Authentication flow timed out",{tempCode:e.substring(0,4)+"***"}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow timed out."),console.log("Please try again or manually place a token file."),console.log("Token file location:"),console.log(`${this.getManualTokenFilePath()}`),console.log("========================================")},t=>{this.logger.error("Authentication flow error",{tempCode:e.substring(0,4)+"***",error:t}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow error."),console.log(`Error: ${t}`),console.log("Please try again or manually place a token file."),console.log(`${this.getManualTokenFilePath()}`),console.log("========================================")}),{tempCode:e,authUrl:t}}catch(e){throw this.logger.error("Error starting authentication flow",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,e}}hasPendingAuth(){return this.pendingAuthCheck||!!this.tempAuthCode}getAuthStatus(){return this.hasPendingAuth()?{hasToken:!1,isPendingAuth:!0,tempCode:this.tempAuthCode,authUrl:this.authFlow.getAuthUrl(this.tempAuthCode)}:{hasToken:!1,isPendingAuth:!1}}async checkAuthStatus(e){if(!e)return null
7470
7539
  try{const t=this.authFlow.getPollingStatus()
7471
7540
  if(t.status===a.PollingStatus.AUTHENTICATED&&t.tokenInfo)return this.logger.info("Authentication polling has token available",{tempCode:e.substring(0,4)+"***",username:t.tokenInfo.username}),t.tokenInfo
7472
7541
  if(t.status===a.PollingStatus.ERROR||t.status===a.PollingStatus.TIMEOUT)return this.pendingAuthCheck=!1,this.tempAuthCode=null,null
@@ -7504,31 +7573,46 @@ c.push({index:t,processId:(u??i[0]).id,covered:!!d,needs:l})}),c}exports.toolcha
7504
7573
  return[...s.filter(e=>!r?.has(e.id)),...s.filter(e=>!!r?.has(e.id))]},exports.proofCommandsFor=function(e,t,r){const o=Array.isArray(t)?t:[]
7505
7574
  return n(o,r).filter(t=>t.processId===e).flatMap(e=>s(o[e.index]))}
7506
7575
  const o=t(67071),i={node:"node",npm:"node",npx:"node",yarn:"node",pnpm:"node",tsx:"node","ts-node":"node",corepack:"node",deno:"deno",bun:"bun",python:"python",python3:"python",pip:"python",pip3:"python",pytest:"python",uv:"python",poetry:"python",pipenv:"python",tox:"python",go:"go",gofmt:"go",cargo:"rust",rustc:"rust",ruby:"ruby",gem:"ruby",bundle:"ruby",rake:"ruby",rspec:"ruby",java:"java",javac:"java",mvn:"java",gradle:"java","./gradlew":"java",kotlin:"java",kotlinc:"java",php:"php",composer:"php",phpunit:"php",dotnet:"dotnet",swift:"swift",zig:"zig",elixir:"elixir",mix:"elixir",dart:"dart",flutter:"dart",perl:"perl",lua:"lua",julia:"julia",Rscript:"r",pdfinfo:"pdf",pdftotext:"pdf",pdftoppm:"pdf",pdfimages:"pdf",pdffonts:"pdf",pdfseparate:"pdf",pdfunite:"pdf",qpdf:"pdf",soffice:"office",libreoffice:"office",pandoc:"office","render-pdf":"office",zbarimg:"office",segno:"office"},a=new Set(["if","then","else","elif","do","while","until","!","time","exec","env","command","sudo","{"])},64469(e,exports,t){"use strict"
7507
- function r(e,t){return!(!e||!t)&&(e===t||(0,n.isBranchWorkerAgentType)(e)&&(0,n.isBranchWorkerAgentType)(t))}exports.WARM_CODER_DEFAULTS=void 0,exports.readWarmCoderPolicy=function(e){const t=e?.warmCoder??{},r=Number(t.warmMinutes),s=Number(t.maxContextTokens),n=Number(t.coldMaxContextTokens),o=Number(t.coldMaxChangedLines)
7508
- return{enabled:void 0===t.enabled?exports.WARM_CODER_DEFAULTS.enabled:!1!==t.enabled,warmMinutes:Number.isFinite(r)&&r>0?r:exports.WARM_CODER_DEFAULTS.warmMinutes,maxContextTokens:Number.isFinite(s)&&s>0?s:exports.WARM_CODER_DEFAULTS.maxContextTokens,coldResume:void 0===t.coldResume?exports.WARM_CODER_DEFAULTS.coldResume:!1!==t.coldResume,coldMaxContextTokens:Number.isFinite(n)&&n>0?n:exports.WARM_CODER_DEFAULTS.coldMaxContextTokens,coldMaxChangedLines:Number.isFinite(o)&&o>=0?o:exports.WARM_CODER_DEFAULTS.coldMaxChangedLines}},exports.pickWarmCoder=function(e,t,s,n){if(!e.enabled)return{reuse:!1,why:"off"}
7509
- if(!n.length)return{reuse:!1,why:"no-candidate"}
7510
- const o=["role","unrelated","model","tools","busy","context","archived","cold","cold-context","stale"]
7511
- let i=0
7512
- const a=e=>{i=Math.max(i,o.indexOf(e))},c=[],l=[]
7513
- for(const o of n)r(o.agentType,s.worker)?o.threadRequests?(o.modelKey??"")===(s.modelKey??"")?(o.toolsKey??"")===(s.toolsKey??"")?o.busy&&o.busyElsewhere?a("busy"):void 0!==o.contextTokens&&o.contextTokens>e.maxContextTokens?a("context"):o.archived||t-o.lastActiveMs>6e4*e.warmMinutes?e.coldResume&&!o.busy&&void 0!==o.contextTokens&&void 0!==o.changedSinceLines?o.contextTokens>e.coldMaxContextTokens?a("cold-context"):o.changedSinceLines>e.coldMaxChangedLines?a("stale"):l.push(o):a(o.archived?"archived":"cold"):c.push(o):a("tools"):a("model"):a("unrelated"):a("role")
7514
- const d=(e,t)=>(t.threadRequests??0)-(e.threadRequests??0)||Number(e.busy)-Number(t.busy)||t.lastActiveMs-e.lastActiveMs
7515
- if(c.length){const e=c.sort(d)[0]
7516
- return e.busy?{reuse:!0,sessionId:e.externalSessionId,afterTurn:!0}:{reuse:!0,sessionId:e.externalSessionId}}return l.length?{reuse:!0,sessionId:l.sort(d)[0].externalSessionId,cold:!0}:{reuse:!1,why:o[i]}},exports.whyNotReused=function(e){switch(e){case"off":return"warm reuse is off for this project"
7576
+ function r(e,t,r=0){return!!t.lastActiveMs&&!(r&&t.lastActiveMs<r)&&e-t.lastActiveMs<=(0,i.cacheWarmForMs)(t.providerID)}function s(e,t){return!(!e||!t)&&(e===t||(0,o.isBranchWorkerAgentType)(e)&&(0,o.isBranchWorkerAgentType)(t))}exports.b6=exports.nZ=exports.WARM_CODER_DEFAULTS=void 0,exports.readWarmCoderPolicy=function(e){const t=e?.warmCoder??{},r=Number(t.maxContextTokens),s=Number(t.coldMaxContextTokens),n=Number(t.coldMaxChangedLines)
7577
+ return{enabled:void 0===t.enabled?exports.WARM_CODER_DEFAULTS.enabled:!1!==t.enabled,maxContextTokens:Number.isFinite(r)&&r>0?r:exports.WARM_CODER_DEFAULTS.maxContextTokens,coldResume:void 0===t.coldResume?exports.WARM_CODER_DEFAULTS.coldResume:!1!==t.coldResume,coldMaxContextTokens:Number.isFinite(s)&&s>0?s:exports.WARM_CODER_DEFAULTS.coldMaxContextTokens,coldMaxChangedLines:Number.isFinite(n)&&n>=0?n:exports.WARM_CODER_DEFAULTS.coldMaxChangedLines,areaReuse:void 0===t.areaReuse?exports.WARM_CODER_DEFAULTS.areaReuse:!1!==t.areaReuse}},exports.isCacheWarm=r,exports.contextCeilingFor=function(e){const t=function(e){if(!(e&&e.context>0))return
7578
+ const t=Math.min(e.output??0,a)||a
7579
+ return e.input?e.input-Math.min(2e4,t):e.context-t}(e)
7580
+ return void 0===t?e&&0===e.context?Number.POSITIVE_INFINITY:exports.b6:Math.max(0,t-exports.nZ)},exports.pickWarmCoder=function(e,t,n,o,i=0){if(!e.enabled)return{reuse:!1,why:"off"}
7581
+ if(!o.length)return{reuse:!1,why:"no-candidate"}
7582
+ const a=["role","unrelated","model","tools","busy","context","archived","cold","cold-context","stale"]
7583
+ let c=0
7584
+ const l=e=>{c=Math.max(c,a.indexOf(e))},d=t=>Math.min(t.contextCeiling??exports.b6,e.maxContextTokens),u=[],h=[],p=[]
7585
+ for(const a of o){if(!s(a.agentType,n.worker)){l("role")
7586
+ continue}const o=(a.threadRequests??0)>0
7587
+ if(o||e.areaReuse&&(a.sharedAreas??0)>0)if((a.modelKey??"")===(n.modelKey??""))if((a.toolsKey??"")===(n.toolsKey??""))if(o)a.busy&&a.busyElsewhere?l("busy"):void 0!==a.contextTokens&&a.contextTokens>d(a)?l("context"):a.archived||!r(t,a,i)?e.coldResume&&!a.busy&&void 0!==a.contextTokens&&void 0!==a.changedSinceLines?a.contextTokens>e.coldMaxContextTokens?l("cold-context"):a.changedSinceLines>e.coldMaxChangedLines?l("stale"):h.push(a):l(a.archived?"archived":"cold"):u.push(a)
7588
+ else{if(a.busy){l("busy")
7589
+ continue}if(void 0===a.contextTokens||a.contextTokens>d(a)){l("context")
7590
+ continue}if(a.archived){l("archived")
7591
+ continue}if(!r(t,a,i)){l("cold")
7592
+ continue}p.push(a)}else l("tools")
7593
+ else l("model")
7594
+ else l("unrelated")}const f=(e,t)=>(t.threadRequests??0)-(e.threadRequests??0)||(t.sharedAreas??0)-(e.sharedAreas??0)||Number(e.busy)-Number(t.busy)||t.lastActiveMs-e.lastActiveMs
7595
+ if(u.length){const e=u.sort(f)[0]
7596
+ return e.busy?{reuse:!0,sessionId:e.externalSessionId,afterTurn:!0}:{reuse:!0,sessionId:e.externalSessionId}}return h.length?{reuse:!0,sessionId:h.sort(f)[0].externalSessionId,cold:!0}:p.length?{reuse:!0,sessionId:p.sort(f)[0].externalSessionId,area:!0}:{reuse:!1,why:a[c]}},exports.sharedAreas=function(e,t){if(!e?.length||!t?.length)return 0
7597
+ const r=(e,t)=>e===t||e.startsWith(`${t}/`)
7598
+ return e.filter(e=>t.some(t=>r(e,t)||r(t,e))).length},exports.whyNotReused=function(e){switch(e){case"off":return"warm reuse is off for this project"
7517
7599
  case"no-candidate":return"no worker session is open"
7518
7600
  case"role":return"no open worker plays this role"
7519
- case"unrelated":return"no warm worker on this thread"
7520
- case"model":return"the thread's warm worker runs on another model than this kind of work"
7521
- case"tools":return"the thread's warm worker has tools switched off that this kind of work needs (it did another kind)"
7522
- case"archived":return"the thread's worker is put away"
7601
+ case"unrelated":return"no worker on this thread or in its areas"
7602
+ case"model":return"the matching warm worker runs on another model than this kind of work"
7603
+ case"tools":return"the matching warm worker has tools switched off that this kind of work needs (it did another kind)"
7604
+ case"archived":return"the matching worker is put away"
7523
7605
  case"cold-context":return"the thread's worker has gone cold and its context is too long to pay for again"
7524
7606
  case"stale":return"the thread's worker has gone cold and the files it worked in changed since"
7525
- case"busy":return"the thread's warm worker is mid-turn on another unfinished request"
7526
- case"context":return"the thread's warm worker is near its context ceiling"
7527
- case"cold":return"the thread's worker has gone cold (past the warm window)"
7607
+ case"busy":return"the matching warm worker is mid-turn on other work"
7608
+ case"context":return"the matching warm worker's context is too long to take this on"
7609
+ case"cold":return"the matching worker has gone cold (its provider cache has expired or a config reload invalidated it)"
7528
7610
  default:return"no warm worker could take it"}},exports.modelKeyOf=function(e,t){const r=t?e?.[t]:void 0
7529
- return r?`${r.provider??""}/${r.tier??""}`:""},exports.toolsKeyOf=function(e){return Object.keys((0,s.spawnToolMask)({kind:e??null})).sort().join(",")},exports.sameWorkerRole=r
7530
- const s=t(45687),n=t(87780)
7531
- exports.WARM_CODER_DEFAULTS={enabled:!0,warmMinutes:50,maxContextTokens:25e4,coldResume:!0,coldMaxContextTokens:2e5,coldMaxChangedLines:150}},92825(e,exports){"use strict"
7611
+ return r?`${r.provider??""}/${r.tier??""}`:""},exports.toolsKeyOf=function(e){return Object.keys((0,n.spawnToolMask)({kind:e??null})).sort().join(",")},exports.sameWorkerRole=s
7612
+ const n=t(45687),o=t(87780),i=t(89063)
7613
+ exports.WARM_CODER_DEFAULTS={enabled:!0,maxContextTokens:25e4,coldResume:!0,coldMaxContextTokens:2e5,coldMaxChangedLines:150,areaReuse:!0},exports.nZ=1e5
7614
+ const a=32e3
7615
+ exports.b6=168e3-exports.nZ},92825(e,exports){"use strict"
7532
7616
  function t(e){const t=e??"",r=[...t.matchAll(a)].map(e=>e[1])
7533
7617
  return 0===r.length?!i.test(t):r.some(e=>!i.test(e))}function r(e,t){if(!e)return!1
7534
7618
  if(!/Implement req|Next request in this arc|Work req|pickup briefing/i.test(e))return!1
@@ -7613,18 +7697,16 @@ const s=e=>{try{return(0,d.realpathSync)(e)}catch{return(0,h.resolve)(e)}}
7613
7697
  s(r)!==s(e)&&(console.error(""),console.error("\u274c This folder is a copy that has not been re-identified."),console.error(" .alchemy_lab/project.json belongs to a project whose directory is:"),console.error(` ${r}`),console.error(" but you are starting in:"),console.error(` ${e}`),console.error(""),console.error(" Starting here would register as the ORIGINAL project and silently do no"),console.error(" work for this one. If a fork is still being created, wait for it to finish."),console.error(" If you copied this folder by hand, delete .alchemy_lab/project.json and"),console.error(" start again to get a project of its own."),console.error(""),process.exit(1))}async function i(e,t=3e5){return new Promise(r=>{const s=setInterval(async()=>{await e.hasValidToken()&&(clearInterval(s),r(!0))},2e3)
7614
7698
  setTimeout(()=>{clearInterval(s),r(!1)},t)})}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
7615
7699
  try{const{createConfigManager:r}=await e.e(60).then(()=>e.t(49060,23)),s=r(t)
7616
- return(await s.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,r,s,n="cli-init"){try{const o=(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",...(0,j.machineCallerHeaders)(e)},body:JSON.stringify({name:o,description:`Project for ${o}`,status:"active",metadata:{basePath:r,createdVia:n}})})
7700
+ return(await s.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,r,s,n="cli-init"){try{const o=(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",...(0,E.machineCallerHeaders)(e)},body:JSON.stringify({name:o,description:`Project for ${o}`,status:"active",metadata:{basePath:r,createdVia:n}})})
7617
7701
  if(i.ok){const e=await i.json()
7618
7702
  return e.data?.id||e.id}s.info(`Project registration failed (HTTP ${i.status})`)}catch(e){s.info("Project registration failed",{error:(0,y.getErrorMessage)(e)})}}let exports=s
7619
7703
  Object.defineProperty(exports,"__esModule",{value:!0})
7620
- const l=e(3444),d=e(79896),u=e(91943),h=e(16928),p=e(11253),f=e(18984),g=e(62366),m=e(73962),w=e(81187),y=e(58824),v=e(66800),b=e(88918),k=e(14139),I=e(37776),S=e(90629),$=e(69706),j=e(11773)
7621
- {const e=Number(String(process.versions.node).split(".")[0])
7622
- Number.isFinite(e)&&e<24&&(console.error(`AlchemyLab needs Node 24 or newer; this is Node ${process.versions.node}.`),console.error("Install a current Node (https://nodejs.org) or switch with nvm: `nvm install 24 && nvm use 24`, then run alchemy again."),process.exit(1))}(async function(){(0,$.installServeAuthFetch)(),parseInt(process.version.replace("v","").split(".")[0],10)<24&&(console.error(`\n\u274c Node.js ${process.version} is not supported.`),console.error(" AlchemyLab requires Node.js 24 or newer (the agent runtime needs it)."),console.error("\n Switch with: nvm install 24 && nvm use 24\n"),process.exit(1))
7704
+ const l=e(3444),d=e(79896),u=e(91943),h=e(16928),p=e(11253),f=e(18984),g=e(62366),m=e(73962),w=e(81187),y=e(58824),v=e(66800),b=e(88918),k=e(14139),I=e(37776),S=e(90629),$=e(26457),j=e(69706),E=e(11773),C=e(84973);(0,C.isSupportedNode)(process.versions.node)||(console.error((0,C.unsupportedNodeMessage)(process.versions.node)),process.exit(1)),async function(){(0,j.installServeAuthFetch)(),(0,C.isSupportedNode)(process.version)||(console.error(`\n\u274c ${(0,C.unsupportedNodeMessage)(process.version)}\n`),process.exit(1))
7623
7705
  const s=process.argv.slice(2)
7624
- let j
7625
- const E=s.indexOf("--port");-1!==E&&s[E+1]&&(j=parseInt(s[E+1],10),isNaN(j)&&(console.error("\u274c Invalid port number"),process.exit(1)))
7626
- const C=s.filter((e,t)=>"--port"!==e&&(-1===E||t!==E+1))
7627
- if(0===C.length)return void await async function(r={}){const s=process.cwd()
7706
+ let E
7707
+ const A=s.indexOf("--port");-1!==A&&s[A+1]&&(E=parseInt(s[A+1],10),isNaN(E)&&(console.error("\u274c Invalid port number"),process.exit(1)))
7708
+ const _=s.filter((e,t)=>"--port"!==e&&(-1===A||t!==A+1))
7709
+ if(0===_.length){try{for(const e of(0,$.otherInstallsNotice)(process.argv[1],(0,$.otherInstalls)(process.argv[1])))console.log(e)}catch{}return void await async function(r={}){const s=process.cwd()
7628
7710
  o(s)
7629
7711
  const l=(0,h.join)(s,k.ALCHEMY_DIR_NAME)
7630
7712
  await(0,u.mkdir)(l,{recursive:!0})
@@ -7638,8 +7720,8 @@ if(!_?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening brow
7638
7720
  const e=await A.startAuthenticationFlow()
7639
7721
  console.log(`\u{1f310} ${e.authUrl}`)
7640
7722
  const t=(0,S.openBrowser)(e.authUrl)
7641
- console.log(""),console.log(t?"\u{1f446} Browser window opened. Complete login there.":"\u{1f446} Complete the login in a browser, then return here."),console.log("\u23f1\ufe0f Waiting for authentication..."),await i(A)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),_=await A.getToken(),console.log(`\u2705 Welcome, ${_?.username||"user"}!`),console.log("")}let x=_?.token
7642
- const P=await async function(e,t,r,s){if(n(e))return!1
7723
+ console.log(""),console.log(t?"\u{1f446} Browser window opened. Complete login there.":"\u{1f446} Complete the login in a browser, then return here."),console.log("\u23f1\ufe0f Waiting for authentication..."),await i(A)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),_=await A.getToken(),console.log("")}let x=_?.token
7724
+ const T=await async function(e,t,r,s){if(n(e))return!1
7643
7725
  console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
7644
7726
  const o=await(0,I.ensureProjectRepository)(e)
7645
7727
  "created"===o.status?console.log(`\u{1f331} No git repository here. ${o.message}`):"failed"!==o.status&&o.hasHistory||(console.log(`\u26a0\ufe0f ${o.message??"This folder cannot be used as a project yet."}`),console.log(" Nothing was registered and no servers were started."),console.log(""),function(e){const t=(0,h.join)(e,k.ALCHEMY_DIR_NAME)
@@ -7648,11 +7730,11 @@ const i=(0,h.join)(e,k.ALCHEMY_DIR_NAME)
7648
7730
  let a
7649
7731
  await(0,u.mkdir)(i,{recursive:!0}),r?(console.log(`\u{1f4cb} Registering project "${(0,h.basename)(e)||"Local Project"}"...`),a=await c(t,r,e,s),a?console.log(`\u2705 Project registered: ${a}`):console.log("\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token, skipping project registration")
7650
7732
  const l=(0,h.join)(i,"project.json")
7651
- return(0,d.writeFileSync)(l,JSON.stringify({id:a||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!a,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:i}},null,2),"utf-8"),console.log(""),!0}(s,C,x,E),T=(0,h.join)(s,k.ALCHEMY_DIR_NAME,"project.json")
7733
+ return(0,d.writeFileSync)(l,JSON.stringify({id:a||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!a,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:i}},null,2),"utf-8"),console.log(""),!0}(s,C,x,E),P=(0,h.join)(s,k.ALCHEMY_DIR_NAME,"project.json")
7652
7734
  let M
7653
- try{const e=JSON.parse((0,d.readFileSync)(T,"utf-8"))
7735
+ try{const e=JSON.parse((0,d.readFileSync)(P,"utf-8"))
7654
7736
  if(M=e.id||void 0,!1===e.registeredRemotely&&x){const t=await c(C,x,s,E,"cli-init-retry")
7655
- t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(T,JSON.stringify(e,null,2),"utf-8"),M=t,console.log(`\u2705 Project registered: ${t}`))}}catch{E.warn("Could not read project.json")}const R=t()
7737
+ t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(P,JSON.stringify(e,null,2),"utf-8"),M=t,console.log(`\u2705 Project registered: ${t}`))}}catch{E.warn("Could not read project.json")}const R=t()
7656
7738
  console.log([""," .oo 8 8 o 8 "," .P 8 8 8 8 8 "," .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. "," oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 "," .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 ",".P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' ","..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+R,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::",""].join("\n")),console.log(`\u{1f4c1} Project: ${s}`)
7657
7739
  const O=r.port||await(0,v.findAvailablePort)(3456),q=new g.AlchemyActionServer(E,{projectPath:s,remoteServerUrl:C,port:O,authToken:x,tokenManager:A})
7658
7740
  try{const t=(0,m.getInstallContext)(),r=new f.MCPClientConfigurer(E,C,x),n=new w.UnifiedLauncher(E,t,r)
@@ -7673,7 +7755,7 @@ console.log(" OpenCode serve ... failed")
7673
7755
  for(const t of e.split("\n").slice(0,4))console.log(` ${t}`)
7674
7756
  console.log(` Full log: ${(0,h.join)(s,k.ALCHEMY_DIR_NAME,"server.log")}`)}console.log("")
7675
7757
  const c=`\x1b]8;;${o}\x07${o}\x1b]8;;\x07`
7676
- if(console.log(" Alchemy UI: "+c),console.log(""),P){const e=`${o.replace(/\/$/,"")}${M?`/projects/${M}`:""}/timeline?welcome=1`
7758
+ if(console.log(" Alchemy UI: "+c),console.log(""),T){const e=`${o.replace(/\/$/,"")}${M?`/projects/${M}`:""}/timeline?welcome=1`
7677
7759
  console.log(" \u{1f310} First run: opening AlchemyLab in your browser (60-second welcome tour)..."),console.log(" No AI provider connected yet? That is the next step there (Settings > Providers)."),console.log(""),(0,S.openBrowser)(e,"Open the Alchemy UI link above in a browser on any device to continue there.")}let l=!1,u=null
7678
7760
  const p=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await q.stop(),process.exit(0))}
7679
7761
  process.on("SIGTERM",p),process.on("SIGINT",p)
@@ -7683,9 +7765,8 @@ u=setInterval(async()=>{if(!b)try{if(await A.hasValidToken()){const e=(await A.g
7683
7765
  return void(e&&e!==x&&(x=e,q.updateAuthToken(e),E.info("Picked up a renewed sign-in")))}b=!0,console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
7684
7766
  const e=await A.startAuthenticationFlow()
7685
7767
  if(console.log(`\u{1f310} ${e.authUrl}`),(0,S.openBrowser)(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await i(A)){const e=await A.getToken()
7686
- e?.token&&(x=e.token,q.updateAuthToken(x)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){E.warn("Token watchdog check failed",{error:(0,y.getErrorMessage)(e)})}finally{b=!1}},3e5),await v.start(p)}catch(e){E.error("Failed to start server",{error:(0,y.getErrorMessage)(e)}),console.error(`\u274c Failed to start: ${(0,y.getErrorMessage)(e)}`),process.exit(1)}}({port:j})
7687
- const A=C[0].toLowerCase()
7688
- switch("--help"!==A&&"-h"!==A||(r(),process.exit(0)),"--version"!==A&&"-v"!==A||(console.log(`alchemy v${t()}`),process.exit(0)),A){case"serve":await async function(e={}){const r=process.cwd()
7768
+ e?.token&&(x=e.token,q.updateAuthToken(x)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){E.warn("Token watchdog check failed",{error:(0,y.getErrorMessage)(e)})}finally{b=!1}},3e5),await v.start(p)}catch(e){E.error("Failed to start server",{error:(0,y.getErrorMessage)(e)}),console.error(`\u274c Failed to start: ${(0,y.getErrorMessage)(e)}`),process.exit(1)}}({port:E})}const x=_[0].toLowerCase()
7769
+ switch("--help"!==x&&"-h"!==x||(r(),process.exit(0)),"--version"!==x&&"-v"!==x||(console.log(`alchemy v${t()}`),process.exit(0)),x){case"serve":await async function(e={}){const r=process.cwd()
7689
7770
  o(r),n(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))
7690
7771
  const s=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),i=t()
7691
7772
  console.log(`\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 AlchemyLab Server v${i.padEnd(10)} \u2551\n\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n`),console.log(`\u{1f4c1} Project: ${r}`),(await(0,b.probeDockerDaemon)()).ok||(console.log("\u26a0\ufe0f Docker not reachable, confined execution unavailable (host-sandbox fallback only)."),console.log(" Run `alchemy` (default mode) for guided Docker install/start."))
@@ -7697,13 +7778,13 @@ const f=e.port||await(0,v.findAvailablePort)(3456),m=new g.AlchemyActionServer(s
7697
7778
  try{await m.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${m.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
7698
7779
  let e=!1
7699
7780
  const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await m.stop(),process.exit(0))}
7700
- process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise(()=>{})}catch(e){s.error("Failed to start server",{error:(0,y.getErrorMessage)(e)}),console.error(`\u274c Failed to start: ${(0,y.getErrorMessage)(e)}`),process.exit(1)}}({port:j})
7781
+ process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise(()=>{})}catch(e){s.error("Failed to start server",{error:(0,y.getErrorMessage)(e)}),console.error(`\u274c Failed to start: ${(0,y.getErrorMessage)(e)}`),process.exit(1)}}({port:E})
7701
7782
  break
7702
7783
  case"logout":await async function(){const e=process.cwd()
7703
7784
  o(e)
7704
7785
  const t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),r=await a(t),s=new p.TokenManager(t,e,{remoteServerUrl:r,allowInteractiveAuth:!1})
7705
7786
  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.")}()
7706
7787
  break
7707
- default:console.error(`\u274c Unknown command: ${A}`),console.log(""),r(),process.exit(1)}})().catch(e=>{const t=e?.cause,r=t?.code||e?.code
7788
+ default:console.error(`\u274c Unknown command: ${x}`),console.log(""),r(),process.exit(1)}}().catch(e=>{const t=e?.cause,r=t?.code||e?.code
7708
7789
  if(r&&["ECONNREFUSED","ENOTFOUND","ETIMEDOUT","ECONNRESET","EHOSTUNREACH","EAI_AGAIN"].includes(r)||/fetch failed/i.test(String(e?.message))){const e=t?.address?` at ${t.address}${t.port?`:${t.port}`:""}`:""
7709
7790
  console.error(`\u274c Cannot reach the AlchemyLab server${e} (${r||"network error"}).`),console.error(" Check your connection and the server address (REMOTE_SERVER_URL if you set one), then run alchemy again."),process.exit(1)}console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)})})(),module.exports=s})()