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/README.md +1 -1
- package/dist/cli.js +474 -393
- package/dist/http-server.js +651 -623
- package/dist/index.js +725 -697
- package/dist/mcp-direct.js +1109 -1081
- package/package.json +4 -4
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=
|
|
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,
|
|
447
|
-
const r=await(0,
|
|
448
|
-
n=p.default.join((0,
|
|
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},
|
|
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&&!
|
|
455
|
-
else{await(0,
|
|
456
|
-
const r=await(0,
|
|
457
|
-
R=p.default.join((0,
|
|
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})}
|
|
463
|
-
const L=()=>
|
|
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)):
|
|
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(!
|
|
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:
|
|
470
|
-
|
|
471
|
-
try{return await c}finally{
|
|
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,
|
|
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),
|
|
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,
|
|
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,
|
|
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,
|
|
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),
|
|
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)})))),
|
|
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===
|
|
1041
|
-
const M=
|
|
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=
|
|
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=
|
|
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=>
|
|
1049
|
-
if(y){try{const{escalated:e,rework:r,reason:s}=i(U),n=
|
|
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${
|
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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),
|
|
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
|
-
|
|
1235
|
-
const R=
|
|
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}
|
|
1238
|
-
let O={...
|
|
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(
|
|
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:
|
|
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:
|
|
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),
|
|
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
|
|
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-
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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(!
|
|
1528
|
-
if(
|
|
1529
|
-
|
|
1530
|
-
if(
|
|
1531
|
-
|
|
1532
|
-
n.
|
|
1533
|
-
try{const
|
|
1534
|
-
if(!
|
|
1535
|
-
const
|
|
1536
|
-
if(
|
|
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)
|
|
1539
|
-
for(const[t,r]of Object.entries(e??{}))"busy"===r?.type&&
|
|
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&&
|
|
1542
|
-
for(const t of e)(t?.localId??t?.id)&&
|
|
1543
|
-
const
|
|
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
|
-
|
|
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>=(
|
|
1550
|
-
if(
|
|
1551
|
-
n?.externalSessionId&&"archived"===n.status&&
|
|
1552
|
-
for(const n of
|
|
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
|
|
1555
|
-
!
|
|
1556
|
-
let
|
|
1557
|
-
const
|
|
1558
|
-
if(
|
|
1559
|
-
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
return o.info(
|
|
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=
|
|
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
|
|
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,
|
|
1577
|
-
if("string"==typeof x&&x&&(0,$.sameWorkerRole)(
|
|
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
|
|
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,
|
|
1586
|
-
if(await a(t,
|
|
1587
|
-
return c(t,
|
|
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(
|
|
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,
|
|
2000
|
-
try{const t=await async function(){if(
|
|
2001
|
-
try{const e=await
|
|
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
|
|
2004
|
-
try{const e=await
|
|
2005
|
-
return
|
|
2006
|
-
if(!t.enabled&&!
|
|
2007
|
-
const
|
|
2008
|
-
if(!
|
|
2009
|
-
const
|
|
2010
|
-
if(!
|
|
2011
|
-
const
|
|
2012
|
-
for(let e=0;e<1e3;e+=100){const t=await
|
|
2013
|
-
for(const e of s)e?.externalSessionId&&r(e.agentType,{warmCoders:
|
|
2014
|
-
if(s.length<100)break}const
|
|
2015
|
-
let
|
|
2016
|
-
try{const t=await(0,c.fetchProviderUsage)(e.projectPath,
|
|
2017
|
-
s&&r&&(
|
|
2018
|
-
for(const r of
|
|
2019
|
-
if(!
|
|
2020
|
-
if(
|
|
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=
|
|
2024
|
-
if(i||(i={anchorMs:n,lastTickMs:0,lastSeenUpdated:n},
|
|
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=
|
|
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=
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
t
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
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?
|
|
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=
|
|
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,
|
|
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,
|
|
2147
|
-
try{if(e.isBusy&&await e.isBusy()){if(void 0===
|
|
2148
|
-
e.logger.warn("MCP reattach: sessions have been busy for too long, reattaching anyway",{deferredForMs:Date.now()-
|
|
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=
|
|
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,
|
|
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,
|
|
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),
|
|
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),
|
|
2186
|
-
let
|
|
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=
|
|
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
|
|
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
|
|
2391
|
-
if(
|
|
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
|
|
2404
|
+
if(!e.ok)return o?.data??[]
|
|
2394
2405
|
const r=await e.json(),n=Array.isArray(r?.providers)?r.providers:[]
|
|
2395
|
-
return
|
|
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
|
|
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,
|
|
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),
|
|
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),
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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),
|
|
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)),
|
|
3705
|
-
class
|
|
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,
|
|
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()+
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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:
|
|
3758
|
-
t.json({success:!0,data:await(0
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
3881
|
-
const e=await(0,
|
|
3882
|
-
d=A.default.join((0,
|
|
3883
|
-
const u=await(0,
|
|
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,
|
|
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,
|
|
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<
|
|
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)()<
|
|
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")},
|
|
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<
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
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,
|
|
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,
|
|
3953
|
-
return{servers:t,permissions:r?.connectionPermissions??null}}agentConfigFingerprint(){const e=(0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
4031
|
-
if(t&&t.externalSessionId){const e=(0,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
4053
|
-
p?.sessionId&&o&&!0!==d&&this.watchSubagentForParent(String(p.sessionId),String(o),String(r)),t.json({success:!0,data:p})}catch(e){(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
4088
|
-
if(r)return void t.status(409).json({success:!1,error:r})}const l=await(0,
|
|
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,
|
|
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,
|
|
4095
|
-
const{requestId:r,parentSessionId:s,branchName:n,context:o}=e.body||{},i=await(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,
|
|
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,
|
|
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<
|
|
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,
|
|
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.
|
|
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||{},[
|
|
4111
|
-
|
|
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,
|
|
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,
|
|
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>
|
|
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>
|
|
4161
|
-
else if(e.length>
|
|
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:
|
|
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,
|
|
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
|
|
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,
|
|
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)
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
4313
|
-
if((0,
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
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=
|
|
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:[]}},
|
|
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))},
|
|
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: ${
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
5478
|
-
try{const e=(0,
|
|
5479
|
-
if(
|
|
5480
|
-
if((0,
|
|
5481
|
-
return
|
|
5482
|
-
if(e.length>0){const r=(0,
|
|
5483
|
-
if((0,
|
|
5484
|
-
return e?
|
|
5485
|
-
return e?
|
|
5486
|
-
const
|
|
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||
|
|
5636
|
-
|
|
5637
|
-
const
|
|
5638
|
-
await n.mkdir(
|
|
5639
|
-
const
|
|
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,
|
|
5645
|
-
await this.removeOnDiskAgentFiles(
|
|
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(
|
|
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",
|
|
5767
|
-
try{(0,m.writeServeAuth)(w,
|
|
5768
|
-
const
|
|
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(
|
|
5771
|
-
u.default.mkdirSync(l.default.dirname(e),{recursive:!0}),u.default.writeFileSync(e,JSON.stringify({pid:
|
|
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,
|
|
5776
|
-
|
|
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)}),
|
|
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])),
|
|
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=>
|
|
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))||
|
|
5884
|
-
if(await l(p,h,{trigger:"merge",sha:i,baseSha:o,requestId:u,held:M,results:E,changedChecks:
|
|
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 ")}(_,
|
|
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,_,
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
7103
|
-
if(
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
return
|
|
7107
|
-
return
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
if(!
|
|
7112
|
-
const
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
if(!
|
|
7118
|
-
const
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
return
|
|
7126
|
-
return t&&"object"==typeof t&&!Array.isArray(t)?t:null},exports.
|
|
7127
|
-
if(
|
|
7128
|
-
|
|
7129
|
-
if(
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
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(" ")}`:""}},
|
|
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)},
|
|
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-
|
|
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(
|
|
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,
|
|
7508
|
-
return{enabled:void 0===t.enabled?exports.WARM_CODER_DEFAULTS.enabled:!1!==t.enabled,
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
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
|
|
7520
|
-
case"model":return"the
|
|
7521
|
-
case"tools":return"the
|
|
7522
|
-
case"archived":return"the
|
|
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
|
|
7526
|
-
case"context":return"the
|
|
7527
|
-
case"cold":return"the
|
|
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,
|
|
7530
|
-
const
|
|
7531
|
-
exports.WARM_CODER_DEFAULTS={enabled:!0,
|
|
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,
|
|
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(
|
|
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
|
|
7625
|
-
const
|
|
7626
|
-
const
|
|
7627
|
-
if(0===
|
|
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(
|
|
7642
|
-
const
|
|
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),
|
|
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)(
|
|
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)(
|
|
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(""),
|
|
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:
|
|
7687
|
-
const
|
|
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:
|
|
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: ${
|
|
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})()
|