alchemylab 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -641,12 +641,12 @@ if(!t||!r)continue
641
641
  const n=`alchemy-pv-${c.id}-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,o=e.service&&String(e.service).trim()
642
642
  await s(n,!o),o?W.push({volume:n,target:r,service:o}):H.push({volume:n,target:r})}O.hostExec||(0,$.sweepBoxGit)((0,k.workDir)(e.projectPath,"box-git"))
643
643
  const G=()=>O.hostExec?new x.HostExperiment({worktreePath:q,sandbox:O.hostExec,seed:P.seed,env:w.connEnv&&"object"==typeof w.connEnv?w.connEnv:void 0}):new I.ConfinedExperiment(q,P,{projectName:`alchemy-exp-${D}`,projectId:e.projectId,readOnlySource:!N,boxGitBaseDir:(0,k.workDir)(e.projectPath,"box-git"),egress:O.egress,egressAllowlist:Array.isArray(w.egressAllowlist)?w.egressAllowlist:void 0,boxMounts:H.length?H:void 0,serviceMounts:W.length?W:void 0,session:{memory:B.memory,cpus:B.cpus,size:(0,p.boxSize)(t.size)??(0,p.boxSize)(B.size),pidsLimit:B.pidsLimit,scratchSizeMb:B.scratchMb,shmSizeMb:B.shmMb,addCaps:O.allowCaps&&Array.isArray(w.addCaps)?w.addCaps:void 0,sysctls:O.allowCaps&&w.sysctls&&"object"==typeof w.sysctls?w.sysctls:void 0,runAsRoot:O.allowRoot&&!0===w.runAsRoot||O.allowCaps&&Array.isArray(w.addCaps)&&w.addCaps.length>0||O.allowDockerSocket&&!0===w.dockerSocket,dockerSocket:O.allowDockerSocket&&!0===w.dockerSocket}}),K={"alchemy.purpose":"experiment","alchemy.session":t.sessionId,"alchemy.request":t.requestId,"alchemy.environment":c?.name}
644
- let V=G(),z=await(0,C.withContainerLabels)(K,()=>V.start())
645
- if(!z.ok&&d&&(P.install||P.artifacts?.length)&&(e.logger?.warn?.("experiment: the default install failed; opening the box without it",{error:z.error}),P.install=void 0,P.artifacts=void 0,V=G(),z=await(0,C.withContainerLabels)(K,()=>V.start())),!z.ok)throw N&&await r(e.projectPath,q),new Error(z.error||"experiment failed to start")
646
- const Y=c?.id?String(c.id):d?(0,u.defaultBoxIdentity)({image:w.image,install:P.install,artifacts:P.artifacts}):void 0,J=Array.from(new Set((0,_.resolveArtifacts)(P).flatMap(e=>e.manifests??[]))),X=Y&&!N?{box:Y,manifests:J,depsFrom:await(0,h.computeFingerprint)(q,J)}:void 0,Q=["policy","agent","human"],Z=Array.isArray(w.commandGate)?w.commandGate.filter(e=>Q.includes(e)):O.commandGate,ee=o.register({projectId:e.projectId,sessionId:t.sessionId,environmentId:c?.id,environmentName:c?.name,commandGate:Z,confinement:w.confinement||"confined",...!N&&t.requestId?{liveWorktree:{path:q,requestId:String(t.requestId),...X?{proves:X}:{}}}:{},...N&&L?{snapshot:{path:q,branch:L}}:{},exp:V,onDispose:async()=>{try{const r=V.getBlockedEgress,s="function"==typeof r?r.call(V):[],n=V.getPrepareEgress,o="function"==typeof n?n.call(V):[],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"}))]
644
+ let z=G(),V=await(0,C.withContainerLabels)(K,()=>z.start())
645
+ if(!V.ok&&d&&(P.install||P.artifacts?.length)&&(e.logger?.warn?.("experiment: the default install failed; opening the box without it",{error:V.error}),P.install=void 0,P.artifacts=void 0,z=G(),V=await(0,C.withContainerLabels)(K,()=>z.start())),!V.ok)throw N&&await r(e.projectPath,q),new Error(V.error||"experiment failed to start")
646
+ const Y=c?.id?String(c.id):d?(0,u.defaultBoxIdentity)({image:w.image,install:P.install,artifacts:P.artifacts}):void 0,J=Array.from(new Set((0,_.resolveArtifacts)(P).flatMap(e=>e.manifests??[]))),X=Y&&!N?{box:Y,manifests:J,depsFrom:await(0,h.computeFingerprint)(q,J)}:void 0,Q=["policy","agent","human"],Z=Array.isArray(w.commandGate)?w.commandGate.filter(e=>Q.includes(e)):O.commandGate,ee=o.register({projectId:e.projectId,sessionId:t.sessionId,environmentId:c?.id,environmentName:c?.name,commandGate:Z,confinement:w.confinement||"confined",...!N&&t.requestId?{liveWorktree:{path:q,requestId:String(t.requestId),...X?{proves:X}:{}}}:{},...N&&L?{snapshot:{path:q,branch:L}}:{},exp:z,onDispose:async()=>{try{const r=z.getBlockedEgress,s="function"==typeof r?r.call(z):[],n=z.getPrepareEgress,o="function"==typeof n?n.call(z):[],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"}))]
647
647
  i.length&&(await e.apiClient.post(`/api/projects/${e.projectId}/egress-attempts`,{sessionId:t.sessionId,environmentId:c?.id,confinement:w.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{}N&&await r(e.projectPath,q)}})
648
648
  if(!O.hostExec){const e=(0,A.previewSelfBaseUrl)()
649
- e&&(0,A.registerPreviewHandle)(ee.handle,`${e}/api/experiments/${ee.handle}/live-frame`,c?.name||void 0,{awaitingFrame:!0})}return b({handle:ee.handle,environmentId:c?.id,environmentName:c?.name,experimentName:i?.name,image:w.image||"node:20-alpine",hasServices:!!w.composeFile,workdir:O.hostExec?q:"/work",hostTier:!!O.hostExec,hasGit:!O.hostExec&&!0===V.hasGit,...O.hostExec||N||!t.requestId?{}:{sourceWritable:!0},...O.hostExec||"boolean"!=typeof V.hasGitBinary?{}:{gitBinary:V.hasGitBinary}})}(e,t)
649
+ e&&(0,A.registerPreviewHandle)(ee.handle,`${e}/api/experiments/${ee.handle}/live-frame`,c?.name||void 0,{awaitingFrame:!0})}return b({handle:ee.handle,environmentId:c?.id,environmentName:c?.name,experimentName:i?.name,image:w.image||"node:20-alpine",hasServices:!!w.composeFile,workdir:O.hostExec?q:"/work",hostTier:!!O.hostExec,hasGit:!O.hostExec&&!0===z.hasGit,...O.hostExec||N||!t.requestId?{}:{sourceWritable:!0},...O.hostExec||"boolean"!=typeof z.hasGitBinary?{}:{gitBinary:z.hasGitBinary}})}(e,t)
650
650
  L.set(a,l)
651
651
  try{return await l}finally{L.get(a)===l&&L.delete(a)}},exports.runGatedExec=o,exports.boxDestination=i,exports.insertResultIntoExperiment=async function(e){const r=(0,E.experimentRegistry)().use(e.handle)
652
652
  if(!r){const t=(0,E.experimentRegistry)().whyGone(e.handle)
@@ -743,7 +743,8 @@ if(t){const r=await n(M,P,l,x)
743
743
  if("settle"===(0,d.kindProfile)(D?.type).finalize){x.info("Empty branch on a drive request -- completing without merge",{requestId:l})
744
744
  try{await M.post(`/api/projects/${P}/requests/${l}/finalize-completion`,{summary:"string"==typeof D?.completionSummary&&D.completionSummary.trim()||`The drive of ${l} finished.`,acceptanceEvidence:[{evidence:"Drive request: verification receipt lives in the completion summary; no code changes by design."}],mergeResult:{mergeCommitHash:"unknown",cleanedUp:!1,method:"drive-completion",diffStats:{additions:0,deletions:0}}})}catch(e){return{success:!1,message:`Drive completion failed: ${(0,f.getErrorMessage)(e)}`,error:(0,f.getErrorMessage)(e)}}return{success:!0,message:`Drive ${l} completed (empty branch, nothing to merge)`}}if("artifact-only"===r.shape){x.info("Empty branch with artifact-only work -- completing without merge",{requestId:l,detail:r.detail})
745
745
  try{await M.post(`/api/projects/${P}/requests/${l}/finalize-completion`,{summary:`Finished ${l} with records only -- ${r.detail}. It changed no files, so it was completed with nothing new in your project.`,acceptanceEvidence:[{evidence:`Artifact-only request: ${r.detail}. Persisted outside git; the branch had no code commits to merge.`}],mergeResult:{mergeCommitHash:"unknown",cleanedUp:!1,method:"artifact-only-completion",diffStats:{additions:0,deletions:0}}})}catch(e){return{success:!1,message:`Artifact-only completion failed: ${(0,f.getErrorMessage)(e)}`,error:(0,f.getErrorMessage)(e)}}return(0,h.maybePublishReceiptOnMerge)(l),{success:!0,message:`Request ${l} completed without a merge: artifact-only work (${r.detail}), already persisted; the branch had no code commits.`,data:{artifactOnly:!0}}}if(e>0){const t=(0,m.alreadyInMainSummary)(l,B),r="string"==typeof D?.completionSummary?D.completionSummary.trim():""
746
- try{await M.post(`/api/projects/${P}/requests/${l}/finalize-completion`,{summary:r?`${r}\n\n${t}`:t,acceptanceEvidence:[{evidence:`The branch requests/${l} has the same content as ${B}: 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 ${l} as already in ${B} failed: ${(0,f.getErrorMessage)(e)}`,error:(0,f.getErrorMessage)(e)}}return x.info("Branch content-identical to the default branch -- finished as already in main",{requestId:l,aheadCount:e}),{success:!0,message:`${l} is finished: ${t}`,data:{alreadyInMain:!0}}}return{success:!1,message:t,error:"Empty branch (nothing to merge)"}}}catch(e){x.warn("Could not compute ahead count; proceeding with merge",{requestId:l,error:(0,f.getErrorMessage)(e)})}let V,z,Y
746
+ try{await M.post(`/api/projects/${P}/requests/${l}/finalize-completion`,{summary:r?`${r}\n\n${t}`:t,acceptanceEvidence:[{evidence:`The branch requests/${l} has the same content as ${B}: 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 ${l} as already in ${B} failed: ${(0,f.getErrorMessage)(e)}`,error:(0,f.getErrorMessage)(e)}}return x.info("Branch content-identical to the default branch -- finished as already in main",{requestId:l,aheadCount:e}),{success:!0,message:`${l} is finished: ${t}`,data:{alreadyInMain:!0}}}if(0===e&&"string"==typeof D?.roundOf&&D.roundOf){const e="string"==typeof D?.completionSummary?D.completionSummary.trim():""
747
+ try{await M.post(`/api/projects/${P}/requests/${l}/finalize-completion`,{summary:e||"Nothing was changed.",acceptanceEvidence:[{evidence:"This round changed nothing: its branch has no commit of its own and no record writes into the project."}],mergeResult:{mergeCommitHash:"unknown",cleanedUp:!1,method:"answer-only",diffStats:{additions:0,deletions:0,filesChanged:0}}})}catch(e){return{success:!1,message:`Finishing ${l} as an answer failed: ${(0,f.getErrorMessage)(e)}`,error:(0,f.getErrorMessage)(e)}}return x.info("A round that changed nothing -- finished as an answer",{requestId:l}),{success:!0,message:`${l} is finished: it changed nothing.`,data:{answerOnly:!0}}}return{success:!1,message:t,error:"Empty branch (nothing to merge)"}}}catch(e){x.warn("Could not compute ahead count; proceeding with merge",{requestId:l,error:(0,f.getErrorMessage)(e)})}let z,V,Y
747
748
  {const e=await(0,S.runMergeQueueGate)({projectPath:R,requestId:l,branchName:F,defaultBranch:B,requestTitle:D?.title,criteria:D?.acceptanceCriteria??D?.acceptance_criteria,acceptEnvironmentGap:u,acceptFailingTests:T,testGateOff:(0,c.checksOff)(D),source:"finalize-request",logger:x,fetchRequestRuns:async()=>{const e=await M.get(`/api/projects/${P}/requests/${l}/test-runs`),t=e?.data??e??[]
748
749
  return Array.isArray(t)?t:[]},fetchActiveProcesses:async()=>{const e=await M.get(`/api/projects/${P}/test-processes/active?forRequest=${encodeURIComponent(l)}`),t=e?.data??e??[]
749
750
  return(Array.isArray(t)?t:[t]).filter(Boolean)},evaluateMainChange:async({landingSha:e})=>{const{mainChangeAtLanding:r}=await Promise.resolve().then(()=>t(69689))
@@ -756,11 +757,11 @@ return{success:!1,message:`${o(l)}, and its tests passed: one check of a criteri
756
757
  if("error"===e.action)return{success:!1,message:`Merge queue check failed: ${e.message}`,error:e.message}
757
758
  if("sent-back"===e.action)return await(0,I.bringMainIn)({projectPath:R,requestId:l,defaultBranch:B,logger:x}),{success:!1,refused:!0,message:`${o(l)}: ${e.summary}`,error:"Held: main's newer work broke a check written for its criteria",data:{sentBack:!0}}
758
759
  if("review-held"===e.action){const t=await(0,I.holdForMainChangeReview)({projectPath:R,projectId:P,requestId:l,defaultBranch:B,touched:e.touched,logger:x,post:(e,t)=>M.post(e,t)})
759
- return{success:!1,refused:!0,message:`${o(l,!0)}: ${e.summary} ${t.reviewing?"A reviewer is checking its criteria against that change; it is completed after that if nothing needs fixing.":"A review of that change could not be started; send it back to rework or sign off again."}`,error:"Held: a reviewer checks main's change against the criteria first",data:{reviewHeld:!0,reviewing:t.reviewing}}}"land"===e.action&&(V=e.certifiedCommitSha)}try{z=(await L.raw(["merge-base",B,F])).trim()||void 0}catch{}const J=!1,X=V??(await L.revparse([F])).trim(),Q=await(0,v.findCommittedMarkers)(L,X)
760
+ return{success:!1,refused:!0,message:`${o(l,!0)}: ${e.summary} ${t.reviewing?"A reviewer is checking its criteria against that change; it is completed after that if nothing needs fixing.":"A review of that change could not be started; send it back to rework or sign off again."}`,error:"Held: a reviewer checks main's change against the criteria first",data:{reviewHeld:!0,reviewing:t.reviewing}}}"land"===e.action&&(z=e.certifiedCommitSha)}try{V=(await L.raw(["merge-base",B,F])).trim()||void 0}catch{}const J=!1,X=z??(await L.revparse([F])).trim(),Q=await(0,v.findCommittedMarkers)(L,X)
760
761
  if(!Q.determined||Q.files.length>0){const e=Q.files??[]
761
- return{success:!1,message:Q.determined?`Cannot merge ${l}: committed conflict markers remain in ${e.join(", ")} (the branch was freshened while this merge was queued). Its worker must resolve them and finish again first.`:`Cannot merge ${l}: the conflict-marker check could not run on the landing commit. Nothing was merged.`,error:"Unresolved conflict markers",data:{hasConflicts:!0,markerFiles:e}}}if(V){const e=await(0,$.landCertifiedMerge)(R,{defaultBranch:B,mergeCommitSha:V})
762
+ return{success:!1,message:Q.determined?`Cannot merge ${l}: committed conflict markers remain in ${e.join(", ")} (the branch was freshened while this merge was queued). Its worker must resolve them and finish again first.`:`Cannot merge ${l}: the conflict-marker check could not run on the landing commit. Nothing was merged.`,error:"Unresolved conflict markers",data:{hasConflicts:!0,markerFiles:e}}}if(z){const e=await(0,$.landCertifiedMerge)(R,{defaultBranch:B,mergeCommitSha:z})
762
763
  if(!e.landed)return{success:!1,message:"overlap"===e.refusal||"busy"===e.refusal?`${o(l)}: ${e.reason}`:`${o(l)}: ${e.reason}. Try again, and its checks run again first.`,error:"Certified landing refused",data:{landingRefused:!0,reason:e.refusal,files:e.files??[]}}
763
- Y=V,z=await(0,g.firstParentSha)(L,V)??z,x.info("merge-queue: landed certified catch-up commit (tested tree == merged tree)",{requestId:l,mergeCommit:Y.substring(0,8)})}else{const t=D?.title?`Merge ${F}-${D.title}`:`Merge ${F}`,n=async e=>{const r=await(0,j.buildMergeCommit)(R,{defaultBranch:B,branchSha:e,message:t})
764
+ Y=z,V=await(0,g.firstParentSha)(L,z)??V,x.info("merge-queue: landed certified catch-up commit (tested tree == merged tree)",{requestId:l,mergeCommit:Y.substring(0,8)})}else{const t=D?.title?`Merge ${F}-${D.title}`:`Merge ${F}`,n=async e=>{const r=await(0,j.buildMergeCommit)(R,{defaultBranch:B,branchSha:e,message:t})
764
765
  return r.ok?{built:r,landed:await(0,j.landOnDefaultBranch)(R,{defaultBranch:B,commitSha:r.commitSha,expectedHead:r.baseHead})}:{built:r,landed:null}}
765
766
  let i=await n(X)
766
767
  if(!i.built.ok&&"conflicts"===i.built.reason&&null===(0,d.kindProfile)(D?.type).worker){const e=(0,d.kindProfile)(D?.type).label
@@ -774,7 +775,7 @@ if(i=await n(e),!i.built.ok){const e="conflicts"===i.built.reason?`it still conf
774
775
  return{success:!1,message:`Merge of ${l} failed after a clean reconcile: ${e}. Nothing was merged.`,error:e}}}}if(!i.built.ok){if("nothing-to-merge"===i.built.reason)return{success:!1,message:`${o(l)}: its work holds nothing the project does not already have.`,error:"Nothing to merge"}
775
776
  const t="error"===i.built.reason?i.built.error:"the merge commit could not be built"
776
777
  return r?.__rediscovered||(await L.branch()).all.includes(F)?{success:!1,message:`Merge failed: ${t}`,error:t}:(x.warn("finalize: the request branch disappeared mid-merge; re-deciding as branchless",{requestId:l,errorMsg:t}),await e({...r,__rediscovered:!0},s))}if(!i.landed||!i.landed.landed){const e=i.landed&&!i.landed.landed?i.landed:null
777
- return{success:!1,message:`${o(l)}: ${e?.message??"its changes could not be put into the project"}`,error:"Landing refused",data:{landingRefused:!0,reason:e?.reason,files:e?.files??[]}}}Y=i.built.commitSha,x.info("Merge landed through the door",{requestId:l,mergeCommit:Y.substring(0,8),where:i.landed.where})}const Z=(Y?await(0,g.firstParentSha)(L,Y):void 0)??z,ee=await(0,g.computeDiffStats)(L,Z,Y),te=Y?await(0,v.describeAdditiveAutoMerges)(L,Z,Y):null,re=Y&&(0,c.checksOff)(D)?await(0,A.landedOnTopOf)(R,Y,l).catch(()=>[]):[],se={summary:`Finalized request ${l}`,acceptanceEvidence:[{evidence:"Finalized via GUI after review"},...T?[{evidence:'The user answered the test-cap card with "merge anyway": this merged with the tests failing, waived for this merge.'}]:[],...u?[{evidence:"The user accepted an environment gap: the box could not run the checks, and this merged without them."}]:[],...te?[{evidence:`Parts of this merge were combined automatically and not reviewed together: ${te}`}]:[]],mergeResult:{mergeCommitHash:Y||"unknown",mergeBaseSha:z,cleanedUp:J,method:"action-server-merge",diffStats:ee,...re.length?{notCheckedWith:re}:{}}}
778
+ return{success:!1,message:`${o(l)}: ${e?.message??"its changes could not be put into the project"}`,error:"Landing refused",data:{landingRefused:!0,reason:e?.reason,files:e?.files??[]}}}Y=i.built.commitSha,x.info("Merge landed through the door",{requestId:l,mergeCommit:Y.substring(0,8),where:i.landed.where})}const Z=(Y?await(0,g.firstParentSha)(L,Y):void 0)??V,ee=await(0,g.computeDiffStats)(L,Z,Y),te=Y?await(0,v.describeAdditiveAutoMerges)(L,Z,Y):null,re=Y&&(0,c.checksOff)(D)?await(0,A.landedOnTopOf)(R,Y,l).catch(()=>[]):[],se={summary:`Finalized request ${l}`,acceptanceEvidence:[{evidence:"Finalized via GUI after review"},...T?[{evidence:'The user answered the test-cap card with "merge anyway": this merged with the tests failing, waived for this merge.'}]:[],...u?[{evidence:"The user accepted an environment gap: the box could not run the checks, and this merged without them."}]:[],...te?[{evidence:`Parts of this merge were combined automatically and not reviewed together: ${te}`}]:[]],mergeResult:{mergeCommitHash:Y||"unknown",mergeBaseSha:V,cleanedUp:J,method:"action-server-merge",diffStats:ee,...re.length?{notCheckedWith:re}:{}}}
778
779
  try{await M.post(`/api/projects/${P}/requests/${l}/finalize-completion`,se)}catch(e){const t=(0,f.getErrorMessage)(e)
779
780
  return x.error("Merge landed but the server could not record it yet; the record rides this action's result",{requestId:l,mergeCommitHash:Y,error:t}),(0,$.clearMergeCandidate)(R,l).catch(()=>{}),{success:!1,message:`Code was merged (commit: ${Y?.substring(0,8)}) but server finalization failed: ${t}`,error:t,data:{mergeCommitHash:Y,partialSuccess:!0,finalizeCompletion:se}}}return(0,h.maybePublishReceiptOnMerge)(l),(0,$.clearMergeCandidate)(R,l).catch(()=>{}),{success:!0,message:`Successfully finalized request ${l}`,data:{mergeCommitHash:Y,branchDeleted:J,...te?{autoMergeNote:te}:{},...re.length?{notCheckedWith:re}:{}}}}catch(e){const t=(0,f.getErrorMessage)(e)
780
781
  return x.error("finalize-request action failed",{requestId:l,error:t}),{success:!1,message:`Failed to finalize request: ${t}`,error:t}}finally{if(q&&!N)try{(0,k.releaseMergeLease)(R,l)}catch{}}}
@@ -1353,15 +1354,15 @@ return r&&U.push(e),c.info(r?"review-fanout: a reviewer ended its turn without a
1353
1354
  q()
1354
1355
  const W=Date.now(),G=P.filter(e=>B.missingSessionIds.includes(e.sessionId)).map(e=>e.label),K=o(P,U,B.missingSessionIds)
1355
1356
  c.info("review-fanout: barrier fired",{requestId:a.requestId,reason:B.reason,reported:D.length-G.length,missing:G.length,...K.length?{silentEnds:K}:{}})
1356
- const V=v?(H??[]).filter(e=>P.some(t=>t.sessionId===e.fromSessionId)):[]
1357
- if(v){try{const{escalated:e,rework:r,reason:s}=d(V),n=P[0]?.sessionId
1358
- 0===V.length?(await t.apiClient.post(`/api/projects/${t.projectId}/requests/${a.requestId}/delivery-verdict`,{escalated:!0,reviewerSessionId:n,reason:"The delivery review did not report, so nothing looked at this request before merge."}),c.warn("review-fanout: DELIVERY did not report; escalated rather than merged",{requestId:a.requestId})):(await t.apiClient.post(`/api/projects/${t.projectId}/requests/${a.requestId}/delivery-verdict`,{escalated:e,rework:r,reviewerSessionId:n,reason:s.slice(0,800)}),c.info("review-fanout: DELIVERY verdict applied",{requestId:a.requestId,escalated:e,rework:r}))}catch(e){c.warn("review-fanout: DELIVERY verdict failed to apply",{requestId:a.requestId,error:(0,f.getErrorMessage)(e)})}return{success:!0,message:`review-fanout: delivery verdict applied for ${a.requestId}`,...K.length?{data:{silentEnds:K}}:{}}}let z=!1,Y=null
1357
+ const z=v?(H??[]).filter(e=>P.some(t=>t.sessionId===e.fromSessionId)):[]
1358
+ if(v){try{const{escalated:e,rework:r,reason:s}=d(z),n=P[0]?.sessionId
1359
+ 0===z.length?(await t.apiClient.post(`/api/projects/${t.projectId}/requests/${a.requestId}/delivery-verdict`,{escalated:!0,reviewerSessionId:n,reason:"The delivery review did not report, so nothing looked at this request before merge."}),c.warn("review-fanout: DELIVERY did not report; escalated rather than merged",{requestId:a.requestId})):(await t.apiClient.post(`/api/projects/${t.projectId}/requests/${a.requestId}/delivery-verdict`,{escalated:e,rework:r,reviewerSessionId:n,reason:s.slice(0,800)}),c.info("review-fanout: DELIVERY verdict applied",{requestId:a.requestId,escalated:e,rework:r}))}catch(e){c.warn("review-fanout: DELIVERY verdict failed to apply",{requestId:a.requestId,error:(0,f.getErrorMessage)(e)})}return{success:!0,message:`review-fanout: delivery verdict applied for ${a.requestId}`,...K.length?{data:{silentEnds:K}}:{}}}let V=!1,Y=null
1359
1360
  try{const e=await t.apiClient.get(`/api/projects/${t.projectId}/requests/${a.requestId}`)
1360
- Y=e.success?e.data:null,z="ready_for_review"===Y?.status}catch{}if(z){try{await t.apiClient.patch(`/api/projects/${t.projectId}/requests/${a.requestId}`,{reviewStatus:"completed",cliOrigin:{sessionId:a.coderSessionId},reviewRound:b?{reviewed:b}:{}}),t.logger.info("review-fanout: parked request -- barrier signaled the review finish",{requestId:a.requestId})}catch(e){t.logger.error("review-fanout: parked finish signal failed (gate stays pending -- reconciler surfaces it)",{requestId:a.requestId,error:(0,f.getErrorMessage)(e)})}let e=!0
1361
+ Y=e.success?e.data:null,V="ready_for_review"===Y?.status}catch{}if(V){try{await t.apiClient.patch(`/api/projects/${t.projectId}/requests/${a.requestId}`,{reviewStatus:"completed",cliOrigin:{sessionId:a.coderSessionId},reviewRound:b?{reviewed:b}:{}}),t.logger.info("review-fanout: parked request -- barrier signaled the review finish",{requestId:a.requestId})}catch(e){t.logger.error("review-fanout: parked finish signal failed (gate stays pending -- reconciler surfaces it)",{requestId:a.requestId,error:(0,f.getErrorMessage)(e)})}let e=!0
1361
1362
  try{const r=await t.apiClient.get(`/api/projects/${t.projectId}/requests/${a.requestId}`)
1362
1363
  e=!r.success||"ready_for_review"===r.data?.status}catch{}e&&await(0,A.injectPromptToSession)(t,a.coderSessionId,(G.length>0?`The review round on ${a.requestId} closed; ${G.join(", ")} did not report`:`All reviewers have reported on ${a.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.")}let J=!1,X=!1
1363
- if(!z){const e=await l(t,a,{expectedSessionIds:N,missing:G.length,unspawned:O,reports:H??[],reviewed:b,request:Y})
1364
- J=e.clean,e.advanced?(X=!0,c.info("review-fanout: clean review, the request went on without a worker step",{requestId:a.requestId,reviewers:D.length,reviewed:b,msAfterBarrier:Date.now()-W})):(J&&c.info("review-fanout: clean review, but the completion does not stand as it was; the worker completes again",{requestId:a.requestId,why:e.why}),await(0,A.injectPromptToSession)(t,a.coderSessionId,i(D,G)))}return{success:!0,message:`Review fan-out ${B.reason}: ${D.length-G.length}/${D.length} reviewers reported${O>0?` -- WARNING: ${O} requested reviewer${1===O?"":"s"} failed to spawn and that coverage never ran`:""}${z?" (held finish signaled)":""}${X?" (clean: went on without a worker step)":""}`,data:{reason:B.reason,reviewers:D,missing:G,parked:z,unspawned:O,...b?{reviewed:b}:{},...z?{}:{clean:J,cleanAdvance:X},...K.length?{silentEnds:K}:{}}}}
1364
+ if(!V){const e=await l(t,a,{expectedSessionIds:N,missing:G.length,unspawned:O,reports:H??[],reviewed:b,request:Y})
1365
+ J=e.clean,e.advanced?(X=!0,c.info("review-fanout: clean review, the request went on without a worker step",{requestId:a.requestId,reviewers:D.length,reviewed:b,msAfterBarrier:Date.now()-W})):(J&&c.info("review-fanout: clean review, but the completion does not stand as it was; the worker completes again",{requestId:a.requestId,why:e.why}),await(0,A.injectPromptToSession)(t,a.coderSessionId,i(D,G)))}return{success:!0,message:`Review fan-out ${B.reason}: ${D.length-G.length}/${D.length} reviewers reported${O>0?` -- WARNING: ${O} requested reviewer${1===O?"":"s"} failed to spawn and that coverage never ran`:""}${V?" (held finish signaled)":""}${X?" (clean: went on without a worker step)":""}`,data:{reason:B.reason,reviewers:D,missing:G,parked:V,unspawned:O,...b?{reviewed:b}:{},...V?{}:{clean:J,cleanAdvance:X},...K.length?{silentEnds:K}:{}}}}
1365
1366
  const f=t(58824),m=t(7202),g=t(87775),w=t(20979),y=t(35317),v=t(39023),b=t(79896),k=(0,v.promisify)(y.execFile),S=p(t(83807)),I=t(27478),$=t(95040),j=t(76982),E=t(27838),A=t(17039),C=t(53691),_=t(52232),T=t(82928),x=e=>Math.min(3e3,200*2**e)
1366
1367
  exports.FILE_REPORT_ASK='You ended your turn without filing your report, so your review reached nobody: what you write as a reply is not read. File it now with the report tool: session_message_operations send with type "report", and your findings as the summary. Then stop.'},76555(e,exports,t){"use strict"
1367
1368
  async function r(e){const t=(Array.isArray(e.results?.commands)?e.results.commands:[]).filter(e=>"failed"===e?.status&&!(0,R.isKeptCommand)(e))
@@ -1412,7 +1413,7 @@ s=e?.data??e}catch{e=!0
1412
1413
  continue}const n=s?.status
1413
1414
  if("passed"!==n&&("skipped"!==n||!0!==s?.results?.scopeSkipped)&&!(0,K.failsOnlyOnMain)(s))if((0,K.waitsOnMain)(s))e=!0
1414
1415
  else{if("failed"===n||"error"===n||"skipped"===n)return"failed"
1415
- e=!0}}return e?"pending":"green"},p=async e=>{if("failed"===e){try{(0,G.releaseMergeLease)(t,s)}catch{}try{await(0,K.clearMergeCandidate)(t,s)}catch{}try{const e=await(0,Q.resolveDefaultBranch)(t),r=await(0,V.reconcileBranchWithMain)({projectPath:t,requestId:s,branchName:n,defaultBranch:e})
1416
+ e=!0}}return e?"pending":"green"},p=async e=>{if("failed"===e){try{(0,G.releaseMergeLease)(t,s)}catch{}try{await(0,K.clearMergeCandidate)(t,s)}catch{}try{const e=await(0,Q.resolveDefaultBranch)(t),r=await(0,z.reconcileBranchWithMain)({projectPath:t,requestId:s,branchName:n,defaultBranch:e})
1416
1417
  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,j.getErrorMessage)(e)})}}else if((0,G.renewMergeLease)(t,s,{ttlMs:G.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,j.getErrorMessage)(e)})}}
1417
1418
  if(!c){const e=await h()
1418
1419
  return"pending"!==e&&await p(e),e}for(;Date.now()<u;){await new Promise(e=>setTimeout(e,d))
@@ -1450,7 +1451,7 @@ try{const se=await X.get(`/api/projects/${ee}/requests/${b}`)
1450
1451
  if(!se.success||!se.data)return{success:!1,message:`Request ${b} not found`,error:"Request not found"}
1451
1452
  const oe=se.data
1452
1453
  if("main-came-in"===Y){if("ready_for_review"!==String(oe.status))return{success:!0,message:`${b} is ${oe.status}, not waiting for sign-off; the checks after main came in are not run`}
1453
- const e=`requests/${b}`,t=await(0,V.conflictPending)(te,oe,e)
1454
+ const e=`requests/${b}`,t=await(0,z.conflictPending)(te,oe,e)
1454
1455
  if(t)return{success:!0,message:`not run on ${b}: ${t}`}
1455
1456
  const r=await(0,Q.resolveDefaultBranch)(te),s=await(0,Z.mainBroughtInSinceWork)(te,e,r).catch(()=>null)
1456
1457
  if(!s)return{success:!0,message:`nothing came into ${b} from main after its work; the checks are not run again`}
@@ -1517,7 +1518,7 @@ h=e.data,Ae.push(h.id)}else{const e=await X.post(`/api/projects/${ee}/test-runs`
1517
1518
  h=e.data,Ae.push(h.id)}}catch(e){J.error("Failed to get/create test run record",{requestId:b,processId:process.id,preCreatedId:p?.id,error:(0,j.getErrorMessage)(e)})
1518
1519
  continue}let y,v=process.config?.environment,k=process.config?.environment??null
1519
1520
  if(process.environmentId)try{const e=await X.get(`/api/projects/${ee}/environments/${process.environmentId}`),t=e?.data||e
1520
- t?.config&&(y=t.name,k=t.config,v=(0,z.gateEnvironmentFromRow)(t.config,ee,e=>J.warn("tests gate: clamped elevated environment config",{testProcessId:process.id,environmentId:t.id,environmentName:t.name,clamped:e})))}catch(e){J.warn("Failed to resolve environment_id; using embedded config",{testProcessId:process.id,error:(0,j.getErrorMessage)(e)})}if("experiment"===process.kind){try{let e="(no environment configured for this experiment; the judge has no run evidence)",r=[]
1521
+ t?.config&&(y=t.name,k=t.config,v=(0,V.gateEnvironmentFromRow)(t.config,ee,e=>J.warn("tests gate: clamped elevated environment config",{testProcessId:process.id,environmentId:t.id,environmentName:t.name,clamped:e})))}catch(e){J.warn("Failed to resolve environment_id; using embedded config",{testProcessId:process.id,error:(0,j.getErrorMessage)(e)})}if("experiment"===process.kind){try{let e="(no environment configured for this experiment; the judge has no run evidence)",r=[]
1521
1522
  const s=f(oe,J,b),n=s.commands,o=s.withVerify
1522
1523
  if(v){const s=[...(0,_.buildCapabilityProbes)((0,_.probeableRuntimes)(n)),...process.config?.commands||[],...n],o=await(0,U.withContainerLabels)({"alchemy.purpose":"check","alchemy.request":b,"alchemy.environment":y,"alchemy.check":process.name},()=>(0,O.runTestsGate)(we,{commands:s,environment:v,setup:(0,N.setupOfEnvironment)(de,process.environmentId?String(process.environmentId):null)},{})),i=o.results?.commands||[]
1523
1524
  try{const e=s.filter(e=>!0===e?.required||/^verify\[/.test(String(e?.name??""))).map(e=>String(e?.command??"").trim()).filter(e=>e.length>0).sort(),t=(0,A.createHash)("sha256").update(e.join("\n")).digest("hex")
@@ -1557,34 +1558,34 @@ let t=null
1557
1558
  try{t=await(0,L.decideCatchUpReuse)({runs:await E(),excludeRunId:h.id,process,landingTree:Ee,fingerprint:{kind:"all",value:e},changedBetween:(0,L.gitChangedBetween)(te)})}catch(e){J.debug("catch-up reuse lookup failed; running the gate",{error:(0,j.getErrorMessage)(e)})}if(t?.reuse){const r=(0,L.catchUpReusedRun)(t,process.pathGlobs??[],Ee)
1558
1559
  await X.patch(`/api/projects/${ee}/test-runs/${h.id}`,{status:"passed",results:r.results,executedFingerprint:e}),_e.push(h.id),J.info("Catch-up: main's newer work did not touch this check's paths; its earlier pass is reused",{testRunId:h.id,priorRunId:t.prior.id,processName:process.name,changedSincePass:t.changedPaths.length})
1559
1560
  continue}t&&J.info("Catch-up: the check runs again",{testRunId:h.id,processName:process.name,why:t.why})}const T=Date.now(),x={"alchemy.purpose":"check","alchemy.request":b,"alchemy.environment":y,"alchemy.check":process.name},P=(0,D.splitGateCommands)(c),F=ae.some(e=>!i(e)&&Array.isArray(e?.config?.setup)&&e.config.setup.length>0),H=Array.isArray(process.config?.setup)?process.config.setup:[],G=w&&P.verify.length>0&&!!je&&!F&&!e.commandNames?.length&&!e.skipCommandNames?.length
1560
- let V,Y=!0,Z=null
1561
+ let z,Y=!0,Z=null
1561
1562
  if(G){let e=null
1562
1563
  if(P.process.length>0){const t=(0,D.groupFingerprint)(P.process)
1563
1564
  let r=null
1564
1565
  try{r=(0,D.findProcessEvidence)(await E(),{excludeRunId:h.id,processId:process.id,testedTree:Ee,processFingerprint:t})}catch(e){J.debug("process-evidence lookup failed; running the test commands",{error:(0,j.getErrorMessage)(e)})}let s=null
1565
- if(!r&&K&&Ee)try{s=await(0,L.decideCatchUpReuse)({runs:await E(),excludeRunId:h.id,process,landingTree:Ee,fingerprint:{kind:"process",value:t},changedBetween:(0,L.gitChangedBetween)(te)})}catch(e){J.debug("catch-up reuse lookup failed; running the test commands",{error:(0,j.getErrorMessage)(e)})}r?(e=(0,D.reusedProcessResult)(r,je),Y=!1,J.info("Tests gate: the test commands already passed at this commit; only the criteria checks run",{testRunId:h.id,priorRunId:r.id,processName:process.name,commitSha:je})):s?.reuse?(e=(0,L.catchUpReusedProcess)(s,process.pathGlobs??[]),Y=!1,J.info("Catch-up: main's newer work did not touch this check's paths; its test commands are reused, the criteria checks run",{testRunId:h.id,priorRunId:s.prior.id,processName:process.name})):(s&&J.info("Catch-up: the test commands run again",{testRunId:h.id,processName:process.name,why:s.why}),e=await(0,U.withContainerLabels)(x,()=>(0,O.runTestsGate)(we,{commands:P.process,environment:v,setup:H})))}if(e?.error)V=e
1566
+ if(!r&&K&&Ee)try{s=await(0,L.decideCatchUpReuse)({runs:await E(),excludeRunId:h.id,process,landingTree:Ee,fingerprint:{kind:"process",value:t},changedBetween:(0,L.gitChangedBetween)(te)})}catch(e){J.debug("catch-up reuse lookup failed; running the test commands",{error:(0,j.getErrorMessage)(e)})}r?(e=(0,D.reusedProcessResult)(r,je),Y=!1,J.info("Tests gate: the test commands already passed at this commit; only the criteria checks run",{testRunId:h.id,priorRunId:r.id,processName:process.name,commitSha:je})):s?.reuse?(e=(0,L.catchUpReusedProcess)(s,process.pathGlobs??[]),Y=!1,J.info("Catch-up: main's newer work did not touch this check's paths; its test commands are reused, the criteria checks run",{testRunId:h.id,priorRunId:s.prior.id,processName:process.name})):(s&&J.info("Catch-up: the test commands run again",{testRunId:h.id,processName:process.name,why:s.why}),e=await(0,U.withContainerLabels)(x,()=>(0,O.runTestsGate)(we,{commands:P.process,environment:v,setup:H})))}if(e?.error)z=e
1566
1567
  else{let t=we
1567
1568
  try{t=await(0,D.prepareVerifyTree)(te,we,(await(0,I.simpleGit)(we).revparse(["HEAD"])).trim())}catch(e){J.warn("Tests gate: no clean checkout for the criteria checks; running them in the pool slot",{testRunId:h.id,error:(0,j.getErrorMessage)(e)})}Z=t
1568
1569
  const r=je?await(0,q.takeProofResultsAt)(b,je):null,o=r&&(await(0,I.simpleGit)(t).revparse(["HEAD^{tree}"]).catch(()=>"")).trim()||null,{settled:i,left:a}=(0,D.replayedVerify)(P.verify,r,o)
1569
1570
  i&&J.info("Tests gate: criteria checks the completion's replay settled at this commit are taken, not run again",{testRunId:h.id,taken:(i.results.commands??[]).length,left:a.length,commitSha:je})
1570
1571
  const c=(0,M.verifyByEnvironment)(a,p)
1571
1572
  let l=c.checkBox.length>0?await(0,U.withContainerLabels)({...x,"alchemy.check":`${process.name} (criteria)`},()=>(0,O.runTestsGate)(t,{commands:c.checkBox,environment:v,setup:H})):null
1572
- for(const[e,r]of c.pinned){const o=await s(X,ee,e),i=o?o.id===String(process.environmentId??"")?H:(0,N.setupOfEnvironment)(de,o.id):[],a=o?await(0,U.withContainerLabels)({...x,"alchemy.check":`${process.name} (criteria, ${o.name})`},()=>(0,O.runTestsGate)(t,{commands:r,environment:(0,z.gateEnvironmentFromRow)(o.config,ee),setup:i})):n(e,r)
1573
- l=(0,D.joinVerifyResults)(l,a)}l=(0,D.joinVerifyResults)(l,i),V=(0,D.mergeGroupResults)(e,l)}}else V=await(0,U.withContainerLabels)(x,()=>(0,O.runTestsGate)(we,{commands:c,environment:v,setup:H},{commandNames:e.commandNames,skipCommandNames:e.skipCommandNames}))
1573
+ for(const[e,r]of c.pinned){const o=await s(X,ee,e),i=o?o.id===String(process.environmentId??"")?H:(0,N.setupOfEnvironment)(de,o.id):[],a=o?await(0,U.withContainerLabels)({...x,"alchemy.check":`${process.name} (criteria, ${o.name})`},()=>(0,O.runTestsGate)(t,{commands:r,environment:(0,V.gateEnvironmentFromRow)(o.config,ee),setup:i})):n(e,r)
1574
+ l=(0,D.joinVerifyResults)(l,a)}l=(0,D.joinVerifyResults)(l,i),z=(0,D.mergeGroupResults)(e,l)}}else z=await(0,U.withContainerLabels)(x,()=>(0,O.runTestsGate)(we,{commands:c,environment:v,setup:H},{commandNames:e.commandNames,skipCommandNames:e.skipCommandNames}))
1574
1575
  if(!G&&w&&je&&P.verify.length>0){const e=await(0,q.takeProofResultsAt)(b,je),t=(e?.settled??[]).filter(e=>!e.ok).map(e=>({command:e.command,exit:e.exit,output:e.output}))
1575
- t.length>0&&(V=(0,D.withProofFailures)(V,P.verify,t),J.info("Tests gate: proofs the completion replay found failing fail this run too",{testRunId:h.id,failed:t.length,commitSha:je}))}const re=Y?await a(we,te,J):{screenshots:[],found:0}
1576
+ t.length>0&&(z=(0,D.withProofFailures)(z,P.verify,t),J.info("Tests gate: proofs the completion replay found failing fail this run too",{testRunId:h.id,failed:t.length,commitSha:je}))}const re=Y?await a(we,te,J):{screenshots:[],found:0}
1576
1577
  if(Z&&Z!==we){const e=await a(Z,te,J)
1577
1578
  re.screenshots=[...re.screenshots,...e.screenshots],re.found+=e.found}Y&&d(te,we,J)
1578
1579
  const se=(0,B.mainChecksOf)([process])[0]
1579
- let ie={...V.results||{},...S.length?{kept:S.map(e=>({requestId:e.requestId,criterionId:e.criterionId,text:String(e.text??"").slice(0,300),commands:e.commands.map(e=>String(e).slice(0,600))}))}:{},processFingerprint:(0,D.provenProcessFingerprint)(P.process,V.results?.commands),...Ee?{testedTree:Ee}:{},...se?{checkVersion:(0,B.checkVersionOf)(se),environmentVersion:(0,B.environmentVersionOf)(k)}:{}}
1580
- if(t&&(ie={...ie||{},scopeVerifyOnly:!0,summary:["Process commands out of scope for this change; ran criterion verify commands only.",ie?.summary].filter(Boolean).join(" \xb7 ")}),w){const e=m(V.results?.commands||[],{checkName:String(process.name??""),checkToolchains:(0,M.toolchainsOf)((process.config?.commands??[]).map(e=>e?.command)),uncovered:new Map(g.filter(e=>!e.covered).map(e=>[(0,C.criterionShortKey)(String(p[e.index]?.id??""),e.index),e.needs]))})
1580
+ let ie={...z.results||{},...S.length?{kept:S.map(e=>({requestId:e.requestId,criterionId:e.criterionId,text:String(e.text??"").slice(0,300),commands:e.commands.map(e=>String(e).slice(0,600))}))}:{},processFingerprint:(0,D.provenProcessFingerprint)(P.process,z.results?.commands),...Ee?{testedTree:Ee}:{},...se?{checkVersion:(0,B.checkVersionOf)(se),environmentVersion:(0,B.environmentVersionOf)(k)}:{}}
1581
+ if(t&&(ie={...ie||{},scopeVerifyOnly:!0,summary:["Process commands out of scope for this change; ran criterion verify commands only.",ie?.summary].filter(Boolean).join(" \xb7 ")}),w){const e=m(z.results?.commands||[],{checkName:String(process.name??""),checkToolchains:(0,M.toolchainsOf)((process.config?.commands??[]).map(e=>e?.command)),uncovered:new Map(g.filter(e=>!e.covered).map(e=>[(0,C.criterionShortKey)(String(p[e.index]?.id??""),e.index),e.needs]))})
1581
1582
  e.length&&(ie={...ie||{},acceptanceCriteria:e})}if(re.screenshots.length)ie={...ie||{},screenshots:re.screenshots}
1582
1583
  else if(re.found>0){const e=`${re.found} screenshot(s) found but failed to upload`
1583
- ie={...ie||{},summary:[ie?.summary,e].filter(Boolean).join(" \xb7 ")}}const ce=o(V.error)??("failed"===V.status?await r({apiClient:X,projectId:ee,requestId:b,processId:String(process.id),gateCommands:c||[],results:V.results,repairing:(0,W.environmentRepairInFlight)(b)}).catch(()=>null):null),ue=(V.results?.commands??[]).some(e=>("failed"===e?.status||"error"===e?.status)&&!(0,R.isKeptCommand)(e))
1584
- if("failed"===V.status&&ue&&!V.error&&!ce&&!V.results?.faultClass&&Y&&!t){const e=(0,B.mainChecksOf)([process])[0]
1584
+ ie={...ie||{},summary:[ie?.summary,e].filter(Boolean).join(" \xb7 ")}}const ce=o(z.error)??("failed"===z.status?await r({apiClient:X,projectId:ee,requestId:b,processId:String(process.id),gateCommands:c||[],results:z.results,repairing:(0,W.environmentRepairInFlight)(b)}).catch(()=>null):null),ue=(z.results?.commands??[]).some(e=>("failed"===e?.status||"error"===e?.status)&&!(0,R.isKeptCommand)(e))
1585
+ if("failed"===z.status&&ue&&!z.error&&!ce&&!z.results?.faultClass&&Y&&!t){const e=(0,B.mainChecksOf)([process])[0]
1585
1586
  if(e){const t=(0,I.simpleGit)(te),r=K?.baseSha??le?.mainSha??(je?(await t.raw(["merge-base",await(0,Q.resolveDefaultBranch)(te),je]).catch(()=>"")).trim():"");/^[0-9a-f]{40}$/.test(r)&&(ie={...ie,mainBase:{mainSha:r,checkVersion:(0,B.checkVersionOf)(e)}})}}ce&&(ie={...ie,faultClass:void 0,...ce.results,commands:ie?.commands??[]}),Array.isArray(ie?.commands)&&(ie={...ie,commands:(0,_.withVerifyCommandLines)(ie.commands,c||[])})
1586
1587
  const fe=new Set((ie?.commands||[]).map(e=>String(e?.name??""))),me=(c||[]).filter(e=>fe.has(String(e?.name??""))).filter(e=>!(0,R.isKeptCommand)(e)).filter(e=>!0===e?.required||/^verify\[/.test(String(e?.name??""))).map(e=>String(e?.command??"").trim()).filter(e=>e.length>0).sort(),ge=(0,A.createHash)("sha256").update(me.join("\n")).digest("hex")
1587
- await X.patch(`/api/projects/${ee}/test-runs/${h.id}`,{status:ce?"skipped":V.status,results:ie,executedFingerprint:ge}),_e.push(h.id),J.info("Deterministic Tests gate completed",{testRunId:h.id,processName:process.name,status:V.status,phaseMs:V.phaseMs,beforeGateMs:T-ne,totalMs:Date.now()-ne})}catch(e){J.error("Deterministic Tests gate errored",{testRunId:h.id,error:(0,j.getErrorMessage)(e)}),await X.patch(`/api/projects/${ee}/test-runs/${h.id}`,o((0,j.getErrorMessage)(e))??{status:"error",results:{commands:[],summary:`Tests gate error: ${(0,j.getErrorMessage)(e)}`,faultClass:"fail_environment"}}).catch(()=>{}),_e.push(h.id)}continue}const S=w(oe,process,h,ee,e.commandNames,e.skipCommandNames),$="auto-tester"
1588
+ await X.patch(`/api/projects/${ee}/test-runs/${h.id}`,{status:ce?"skipped":z.status,results:ie,executedFingerprint:ge}),_e.push(h.id),J.info("Deterministic Tests gate completed",{testRunId:h.id,processName:process.name,status:z.status,phaseMs:z.phaseMs,beforeGateMs:T-ne,totalMs:Date.now()-ne})}catch(e){J.error("Deterministic Tests gate errored",{testRunId:h.id,error:(0,j.getErrorMessage)(e)}),await X.patch(`/api/projects/${ee}/test-runs/${h.id}`,o((0,j.getErrorMessage)(e))??{status:"error",results:{commands:[],summary:`Tests gate error: ${(0,j.getErrorMessage)(e)}`,faultClass:"fail_environment"}}).catch(()=>{}),_e.push(h.id)}continue}const S=w(oe,process,h,ee,e.commandNames,e.skipCommandNames),$="auto-tester"
1588
1589
  try{let e
1589
1590
  const t=await(0,T.spawnActionAgent)({getDefaultModel:()=>c.cachedDefaultModel||void 0,agent:$,prompt:S,cwd:we,projectPath:te,projectId:ee,title:`[${b}] Test: ${process.name}`,requestId:b,parentSessionId:G,allowParallel:!0,apiClient:X,logger:J,trackChildPid:re,onSessionPrepared:t=>{e=t,Me.addSession(t)}})
1590
1591
  if(!t.success)throw e&&Me.markSessionDone(e),new Error(t.error||"Spawn failed")
@@ -1595,7 +1596,7 @@ let Pe=null
1595
1596
  if(K){const e={projectPath:te,projectId:ee,requestId:b,branchName:ie,testRunIds:Ae,apiClient:X,logger:J,resume:K.resume}
1596
1597
  0===Ce.length?(Pe=await h({...e,watch:!1}),"pending"===Pe&&h({...e,watch:!0}).catch(e=>J.warn("merge-queue: catch-up watcher crashed; lease TTL will unblock the queue",{requestId:b,error:(0,j.getErrorMessage)(e)}))):(Pe="pending",h({...e,watch:!0}).catch(e=>J.warn("merge-queue: catch-up watcher crashed; lease TTL will unblock the queue",{requestId:b,error:(0,j.getErrorMessage)(e)})))}return{success:!0,message:`Started ${Ce.length+_e.length} test run(s) for request ${b}`,data:{testRunIds:Ae,agentsSpawned:Ce.length,processCount:ae.length,worktreePath:we,spawnedSessionIds:Te,...K?{catchUp:{mergeCommitSha:K.mergeCommitSha,settled:Pe}}:{}}}}catch(e){const t=(0,j.getErrorMessage)(e)
1597
1598
  return J.error("run-tests action failed",{requestId:b,error:t}),{success:!1,message:`Failed to run tests: ${t}`,error:t}}}
1598
- const y=t(64721),v=t(75453),b=t(44600),k=t(79896),S=t(16928),I=t(83807),$=t(63905),j=t(58824),E=t(7202),A=t(76982),C=t(65454),_=t(36160),T=t(28148),x=t(67842),M=t(29418),R=t(54380),P=t(60489),O=t(41666),D=t(79075),q=t(65856),N=t(73775),F=t(87780),L=t(49987),U=t(81618),B=t(80872),H=t(22604),W=t(77238),G=t(56509),K=t(87192),V=t(35510),z=t(96302),Y=t(14139),J=t(40673),X=t(27584),Q=t(52232),Z=t(82865),ee=t(63484),te="alchemy:visual-check"
1599
+ const y=t(64721),v=t(75453),b=t(44600),k=t(79896),S=t(16928),I=t(83807),$=t(63905),j=t(58824),E=t(7202),A=t(76982),C=t(65454),_=t(36160),T=t(28148),x=t(67842),M=t(29418),R=t(54380),P=t(60489),O=t(41666),D=t(79075),q=t(65856),N=t(73775),F=t(87780),L=t(49987),U=t(81618),B=t(80872),H=t(22604),W=t(77238),G=t(56509),K=t(87192),z=t(35510),V=t(96302),Y=t(14139),J=t(40673),X=t(27584),Q=t(52232),Z=t(82865),ee=t(63484),te="alchemy:visual-check"
1599
1600
  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"
1600
1601
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
1601
1602
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.setupAgentInWorktree=async function(e){const{projectPath:t,projectId:r,apiClient:a,logger:c,trackChildPid:l}=e
@@ -2020,7 +2021,7 @@ for(const e of t)e?.localId&&"draft"===e?.status&&"pending"===e?.intake?.status&
2020
2021
  return s},exports.sessionsHeldByRefusal=function(e){return!!E&&e.length>0&&E.heldByRefusal(e)}
2021
2022
  const l=t(87780),d=t(64331),u=t(43218),h=t(4234),p=t(4234),f=t(60721),m=t(86116),g=t(58824),w=t(36799),y=t(4334),v=new Set(["waiting","blocksEverySession","userQuestionCodes","openCardCodes"]),b=["waitingOnSessions","pendingQuestionSessions","monitorArmedSessions","handedOverSessions"],k=new Set(["opening-brief","stalled-agent"]),S=18e5,I=new Set(["busy","running"]),$=["auto-tester","acceptance-judge"],j=["reviewer","criteria-reviewer","guidance-judge","command-judge","promise-judge","research"]
2022
2023
  let E=null
2023
- class A{heldByRefusal(e){return e.some(e=>this.refused.has(e))}constructor(e){this.settledAt=new Map,this.lastStrandedSweepAt=0,this.stallChecked=new Map,this.settledSkip=new Map,this.unconfused=new Map,this.attestedAt=new Map,this.attestReported=new Set,this.reportedKeys=new Set,this.frozenNudges=0,this.lastFrozenAction=null,this.orphanRetries=new Map,this.resumedRecently=new Set,this.capStopped=new Map,this.refused=new Map,this.judgeNow=new Map,this.resumedOn=new Map,this.liftTold=new Set,this.liftedWhy=new Map,this.checking=!1,this.recheckRequested=!1,this.stuckSessions=new Map,this.notifiedSessions=new Set,this.testInjectedIds=new Set,this.serveStartedAt=0,this.processServeStartedAt=0,this.agentListCache=null,this.lastResumeLift=new Map,E=this,this.logger=e.logger,this.getOpenCodeUrl=e.getOpenCodeUrl,this.onStuckDetected=e.onStuckDetected,this.onStuckResolved=e.onStuckResolved,this.checkIntervalMs=e.checkIntervalMs??3e4,this.stuckThresholdMs=e.stuckThresholdMs??3e5,this.maxRetries=e.maxRetries??3,this.getDefaultModel=e.getDefaultModel,this.reloadServe=e.reloadServe,this.getLiveRequestLocalIds=e.getLiveRequestLocalIds,this.getOwnedRequestForSession=e.getOwnedRequestForSession,this.orphanThresholdMs=e.orphanThresholdMs??12e4,this.maxOrphanRetries=e.maxOrphanRetries??2,this.getSessionAgentType=e.getSessionAgentType,this.getPendingQuestionAskerIds=e.getPendingQuestionAskerIds,this.getSessionSettled=e.getSessionSettled,this.reportLiveness=e.reportLiveness,this.reportFleetHealth=e.reportFleetHealth,this.recordProviderBlock=e.recordProviderBlock,this.clearCapStopped=e.clearCapStopped,this.deliver=e.deliver,this.raiseStalledAsk=e.raiseStalledAsk,this.closeStalledAsk=e.closeStalledAsk,this.frozenTurnThresholdMs=e.frozenTurnThresholdMs??6e5,this.abortSettleMs=e.abortSettleMs??1e3,this.hasLiveCommand=e.hasLiveCommand,this.stalledWorkThresholdMs=e.stalledWorkThresholdMs??24e4,this.getRequestWaitState=e.getRequestWaitState,this.getStrandedCandidates=e.getStrandedCandidates,this.fetchRecentReportDedupKeys=e.fetchRecentReportDedupKeys,this.getAgentModel=e.getAgentModel,this.isAgentModelPinned=e.isAgentModelPinned,this.resumeHubTurn=e.resumeHubTurn}turnFailed(e,t){e&&(0,h.isFatalTurnError)(t)&&(this.judgeNow.set(e,0),setTimeout(()=>{this.check().catch(()=>{})},1500).unref?.())}start(){this.intervalTimer||(this.logger.info("Stuck session watchdog starting",{checkInterval:this.checkIntervalMs,stuckThreshold:this.stuckThresholdMs,maxRetries:this.maxRetries}),this.seedReportDedup(),setTimeout(()=>this.check().catch(()=>{}),5e3),this.intervalTimer=setInterval(()=>{this.check().catch(()=>{})},this.checkIntervalMs))}stop(){this.intervalTimer&&(clearInterval(this.intervalTimer),this.intervalTimer=void 0),this.stuckSessions.clear(),this.notifiedSessions.clear(),this.orphanRetries.clear(),this.resumedRecently.clear(),this.stallChecked.clear(),this.settledSkip.clear(),this.unconfused.clear(),this.logger.info("Stuck session watchdog stopped")}getStuckSessions(){return Array.from(this.stuckSessions.values())}isStuck(e){return this.stuckSessions.has(e)}injectTestStuck(e,t){const r=e||`test-stuck-${Date.now()}`,s={sessionId:r,messageId:`test-msg-${Date.now()}`,detectedAt:Date.now(),retryCount:0,lastMessageCreated:Date.now()-this.stuckThresholdMs-1e3,sessionTitle:t||"Test Stuck Session"}
2024
+ class A{heldByRefusal(e){return e.some(e=>this.refused.has(e))}constructor(e){this.settledAt=new Map,this.lastStrandedSweepAt=0,this.stallChecked=new Map,this.settledSkip=new Map,this.unconfused=new Map,this.attestedAt=new Map,this.attestReported=new Set,this.reportedKeys=new Set,this.frozenNudges=0,this.lastFrozenAction=null,this.orphanRetries=new Map,this.resumedRecently=new Set,this.capStopped=new Map,this.refused=new Map,this.judgeNow=new Map,this.resumedOn=new Map,this.liftTold=new Set,this.liftedWhy=new Map,this.checking=!1,this.recheckRequested=!1,this.stuckSessions=new Map,this.notifiedSessions=new Set,this.testInjectedIds=new Set,this.serveStartedAt=0,this.processServeStartedAt=0,this.agentListCache=null,this.lastResumeLift=new Map,E=this,this.logger=e.logger,this.getOpenCodeUrl=e.getOpenCodeUrl,this.onStuckDetected=e.onStuckDetected,this.onStuckResolved=e.onStuckResolved,this.checkIntervalMs=e.checkIntervalMs??3e4,this.stuckThresholdMs=e.stuckThresholdMs??3e5,this.maxRetries=e.maxRetries??3,this.getDefaultModel=e.getDefaultModel,this.reloadServe=e.reloadServe,this.getLiveRequestLocalIds=e.getLiveRequestLocalIds,this.getOwnedRequestForSession=e.getOwnedRequestForSession,this.orphanThresholdMs=e.orphanThresholdMs??12e4,this.maxOrphanRetries=e.maxOrphanRetries??2,this.getSessionAgentType=e.getSessionAgentType,this.getPendingQuestionAskerIds=e.getPendingQuestionAskerIds,this.getSessionSettled=e.getSessionSettled,this.reportLiveness=e.reportLiveness,this.reportFleetHealth=e.reportFleetHealth,this.recordProviderBlock=e.recordProviderBlock,this.clearCapStopped=e.clearCapStopped,this.deliver=e.deliver,this.raiseStalledAsk=e.raiseStalledAsk,this.closeStalledAsk=e.closeStalledAsk,this.frozenTurnThresholdMs=e.frozenTurnThresholdMs??6e5,this.abortSettleMs=e.abortSettleMs??1e3,this.hasLiveCommand=e.hasLiveCommand,this.stalledWorkThresholdMs=e.stalledWorkThresholdMs??24e4,this.awaitedReplyThresholdMs=e.awaitedReplyThresholdMs??12e4,this.isAwaitedReply=e.isAwaitedReply??m.isWatchingInboxChatTurn,this.getRequestWaitState=e.getRequestWaitState,this.getStrandedCandidates=e.getStrandedCandidates,this.fetchRecentReportDedupKeys=e.fetchRecentReportDedupKeys,this.getAgentModel=e.getAgentModel,this.isAgentModelPinned=e.isAgentModelPinned,this.resumeHubTurn=e.resumeHubTurn}turnFailed(e,t){e&&(0,h.isFatalTurnError)(t)&&(this.judgeNow.set(e,0),setTimeout(()=>{this.check().catch(()=>{})},1500).unref?.())}start(){this.intervalTimer||(this.logger.info("Stuck session watchdog starting",{checkInterval:this.checkIntervalMs,stuckThreshold:this.stuckThresholdMs,maxRetries:this.maxRetries}),this.seedReportDedup(),setTimeout(()=>this.check().catch(()=>{}),5e3),this.intervalTimer=setInterval(()=>{this.check().catch(()=>{})},this.checkIntervalMs))}stop(){this.intervalTimer&&(clearInterval(this.intervalTimer),this.intervalTimer=void 0),this.stuckSessions.clear(),this.notifiedSessions.clear(),this.orphanRetries.clear(),this.resumedRecently.clear(),this.stallChecked.clear(),this.settledSkip.clear(),this.unconfused.clear(),this.logger.info("Stuck session watchdog stopped")}getStuckSessions(){return Array.from(this.stuckSessions.values())}isStuck(e){return this.stuckSessions.has(e)}injectTestStuck(e,t){const r=e||`test-stuck-${Date.now()}`,s={sessionId:r,messageId:`test-msg-${Date.now()}`,detectedAt:Date.now(),retryCount:0,lastMessageCreated:Date.now()-this.stuckThresholdMs-1e3,sessionTitle:t||"Test Stuck Session"}
2024
2025
  return this.stuckSessions.set(r,s),this.testInjectedIds.add(r),this.notifiedSessions.add(r),this.onStuckDetected?.(s),s}clearTestStuck(e){return!!this.testInjectedIds.has(e)&&(this.testInjectedIds.delete(e),this.stuckSessions.delete(e),this.notifiedSessions.delete(e),this.onStuckResolved?.(e),!0)}async check(){if(this.checking)this.recheckRequested=!0
2025
2026
  else{this.checking=!0
2026
2027
  try{await this.checkOnce()}finally{this.checking=!1,this.recheckRequested&&(this.recheckRequested=!1,this.check().catch(()=>{}))}}}capacityChanged(e){this.agentListCache=null,0===this.capStopped.size&&0===this.refused.size||(this.logger.info("Capacity may have changed; judging the sessions a provider cap or refusal stopped",{reason:e,stopped:this.capStopped.size,refused:this.refused.size}),this.check().catch(()=>{}))}serveStarted(e=Date.now()){this.processServeStartedAt||(this.processServeStartedAt=e),this.serveStartedAt=e,this.logger.info("The serve started; turns cut by the stop are judged now",{at:e}),this.check().catch(()=>{})}getCapStopped(){return Array.from(this.capStopped.keys())}async checkOnce(){if((0,w.isProjectPaused)())return
@@ -2052,20 +2053,21 @@ if(this.resumedRecently.has(r.id)&&!e)continue
2052
2053
  const s=this.capStopped.has(r.id)||this.refused.has(r.id)||e,a=r.time?.updated??0,c=(0,y.awakeSince)(a,p),l=I.has(t[r.id]?.type)
2053
2054
  if(l&&this.hasLiveCommand?.(r.id))continue
2054
2055
  let u=!1
2055
- if(l&&c<this.frozenTurnThresholdMs){if(c>=this.stalledWorkThresholdMs)try{const e=await fetch(`${n}/session/${r.id}/message?limit=6`,{signal:AbortSignal.timeout(3e3)}),t=e.ok?await e.json():[]
2056
+ const f=this.isAwaitedReply(r.id)?Math.min(this.stalledWorkThresholdMs,this.awaitedReplyThresholdMs):this.stalledWorkThresholdMs
2057
+ if(l&&c<this.frozenTurnThresholdMs){if(c>=f)try{const e=await fetch(`${n}/session/${r.id}/message?limit=6`,{signal:AbortSignal.timeout(3e3)}),t=e.ok?await e.json():[]
2056
2058
  let s=t.length
2057
2059
  for(;s>0&&(0,h.isQuietDelivery)(t[s-1]);)s--
2058
2060
  if(t.length>0&&0===s)throw new Error("only quiet deliveries in view")
2059
2061
  const o=t[s-1]??null,i=o?.info?.role,a=(Array.isArray(o?.parts)?o.parts:[]).filter(e=>e&&("tool"===e.type||"text"===e.type||"reasoning"===e.type)),c=a[a.length-1],l=a.some(e=>"tool"===e.type&&"running"===e.state?.status)||!!c&&("tool"===c.type&&"pending"!==c.state?.status||"tool"!==c.type&&String(c.text??"").trim().length>0)
2060
2062
  u=!o||"user"===i||"assistant"===i&&!l}catch{u=!1}if(!u){this.stuckSessions.has(r.id)&&!this.testInjectedIds.has(r.id)&&(this.stuckSessions.delete(r.id),this.notifiedSessions.delete(r.id),this.onStuckResolved?.(r.id))
2061
- continue}this.logger.warn("Running turn with no streamed parts past the stalled-work threshold; treating as frozen",{sessionId:r.id,idleForMs:c})}const f=(r.title||"").match(o)?.[1],g=f?void 0:(r.title||"").match(d)?.[1]
2062
- if(!(f||g||m.has(r.id)||s))continue
2063
- const v=f?void 0:(r.title||"").match(i)?.[1]
2064
- if((v||g)&&!s&&this.stallChecked.get(r.id)===a)continue
2065
- const b=u?this.stalledWorkThresholdMs:(f||v||g)&&!l?this.orphanThresholdMs:this.frozenTurnThresholdMs,k=!l&&a>0&&a<this.serveStartedAt
2066
- if(!s&&(!k||this.stallChecked.get(r.id)===a)&&c<b)continue
2067
- const S=this.settledSkip.get(r.id)
2068
- !s&&S&&S.updated===a&&p-S.at<18e5||w.push({s:r,tag:f,updated:a,cut:k,running:l,gate:b})}const v=new Set(e.map(e=>e.id))
2063
+ continue}this.logger.warn("Running turn with no streamed parts past the stalled-work threshold; treating as frozen",{sessionId:r.id,idleForMs:c})}const g=(r.title||"").match(o)?.[1],v=g?void 0:(r.title||"").match(d)?.[1]
2064
+ if(!(g||v||m.has(r.id)||s))continue
2065
+ const b=g?void 0:(r.title||"").match(i)?.[1]
2066
+ if((b||v)&&!s&&this.stallChecked.get(r.id)===a)continue
2067
+ const k=u?f:(g||b||v)&&!l?this.orphanThresholdMs:this.frozenTurnThresholdMs,S=!l&&a>0&&a<this.serveStartedAt
2068
+ if(!s&&(!S||this.stallChecked.get(r.id)===a)&&c<k)continue
2069
+ const $=this.settledSkip.get(r.id)
2070
+ !s&&$&&$.updated===a&&p-$.at<18e5||w.push({s:r,tag:g,updated:a,cut:S,running:l,gate:k})}const v=new Set(e.map(e=>e.id))
2069
2071
  for(const e of Array.from(this.stallChecked.keys()))v.has(e)||this.stallChecked.delete(e)
2070
2072
  for(const e of Array.from(this.settledSkip.keys()))v.has(e)||this.settledSkip.delete(e)
2071
2073
  for(const e of Array.from(this.unconfused.keys()))v.has(e)||this.unconfused.delete(e)
@@ -2094,7 +2096,7 @@ if(!t.ok)continue
2094
2096
  M=await t.json()}catch(t){this.logger.debug("Stall check failed for session",{sessionId:e.id,error:(0,g.getErrorMessage)(t)})
2095
2097
  continue}const R="resume"===_&&!!E
2096
2098
  if(m&&!R&&(0,y.awakeSince)(d,p)<j&&!this.capStopped.has(e.id)&&!this.refused.has(e.id)&&!this.judgeNow.has(e.id))continue
2097
- let P=(0,h.assembleTurnEvidence)(M,{now:p,minInFlightMs:this.stalledWorkThresholdMs,...R?{cutBefore:this.serveStartedAt}:{}})
2099
+ let P=(0,h.assembleTurnEvidence)(M,{now:p,minInFlightMs:this.isAwaitedReply(e.id)?Math.min(this.stalledWorkThresholdMs,this.awaitedReplyThresholdMs):this.stalledWorkThresholdMs,...R?{cutBefore:this.serveStartedAt}:{}})
2098
2100
  const O=new Set(["clean-text","empty","no-messages","dead-end","generating"])
2099
2101
  if(x&&O.has(P.kind))continue
2100
2102
  if("quota"===P.kind){const t=await this.modelForSession(e.id)??this.getDefaultModel?.(),r=(0,h.quotaBlockLifted)(P.quota,t,p)
@@ -3657,9 +3659,9 @@ e.post("/api/site-shots",async(e,t)=>{const{url:r,fullPage:s,width:n}=e.body??{}
3657
3659
  if(!r||"string"!=typeof r||!/^https?:\/\//i.test(r))return void t.status(400).json({success:!1,error:"http(s) url required"})
3658
3660
  const i=await u(r)
3659
3661
  if(i)t.status(403).json({success:!1,error:i})
3660
- else if(o.getProjectId())try{const{buf:e,mime:i}=await(0,z.shootUrl)(r,{fullPage:!!s,width:"number"==typeof n?n:void 0}),a=8388608
3662
+ else if(o.getProjectId())try{const{buf:e,mime:i}=await(0,V.shootUrl)(r,{fullPage:!!s,width:"number"==typeof n?n:void 0}),a=8388608
3661
3663
  if(e.length>a)return void t.status(413).json({success:!1,error:`screenshot is ${e.length} bytes (cap ${a}); retry without fullPage or with a narrower width`})
3662
- const c=`data:${i};base64,${e.toString("base64")}`,l=await(0,V.storeLocalAsset)(o.projectPath,c).catch(()=>{})
3664
+ const c=`data:${i};base64,${e.toString("base64")}`,l=await(0,z.storeLocalAsset)(o.projectPath,c).catch(()=>{})
3663
3665
  if(!l)return void t.status(502).json({success:!1,error:"asset upload failed"})
3664
3666
  t.json({success:!0,data:{filename:l,dataUrl:c,url:r}})}catch(e){t.status(500).json({success:!1,error:(0,P.getErrorMessage)(e)})}else t.status(503).json({success:!1,error:"project not registered with the remote yet"})}),e.post("/api/site-text",async(e,r)=>{const{url:s}=e.body??{}
3665
3667
  if(!s||"string"!=typeof s||!/^https?:\/\//i.test(s))return void r.status(400).json({success:!1,error:"http(s) url required"})
@@ -3694,7 +3696,7 @@ if(!f){const r=Number(e.body?.width),s=Number.isFinite(r)&&r>0?Math.min(1920,Mat
3694
3696
  u=await a.newPage({viewport:{width:s,height:n},deviceScaleFactor:1,ignoreHTTPSErrors:!0}),u.on("console",e=>{"error"===e.type()&&t.push(String(e.text()).slice(0,300))}),u.on("pageerror",e=>t.push(String(e?.message??e).slice(0,300)))}f&&u.url()===s||(await u.goto(s,{waitUntil:"domcontentloaded",timeout:3e4}),await u.waitForLoadState("load",{timeout:1e4}).catch(()=>{}),await u.waitForTimeout(300))
3695
3697
  let i=null
3696
3698
  const p=async e=>{try{const t=`data:image/png;base64,${(await u.screenshot({type:"png"})).toString("base64")}`
3697
- return"final"===e&&(i=t),`asset:${await(0,V.storeLocalAsset)(o.projectPath,t)}`}catch{return null}}
3699
+ return"final"===e&&(i=t),`asset:${await(0,z.storeLocalAsset)(o.projectPath,t)}`}catch{return null}}
3698
3700
  for(let e=0;e<n.length;e++){if(Date.now()-g>9e4){w.push({step:e,action:"aborted",ok:!1,note:"drive exceeded 90s"})
3699
3701
  break}const t=n[e]??{},r=String(t.action||"").toLowerCase(),s="string"==typeof t.selector?t.selector:void 0,o="string"==typeof t.text?t.text:void 0,i={step:e,action:r,...s?{selector:s}:{},...o?{text:o}:{}}
3700
3702
  try{if("click"===r){if(!s)throw new Error("click needs a selector")
@@ -3718,7 +3720,7 @@ try{const e=M.default.normalize(r).split(M.default.sep).join("/").replace(/\/+$/
3718
3720
  if(e.startsWith("..")||(0,D.refusedPreviewPath)(e))return void t.status(400).json({success:!1,error:`${r} is not a site directory (hidden and AlchemyLab folders are not served)`})
3719
3721
  const i=await(0,D.previewRootFor)(o.projectPath,e,()=>(0,C.ensureDefaultBranchSnapshot)(o.projectPath),a),c=M.default.join(i.root,"index.html")
3720
3722
  try{await q.promises.access(c)}catch{return void t.status(400).json({success:!1,error:i.main?`no index.html in ${r} on main (a preview of main serves what main has committed, plus build output from commands run for main; files only in the project folder are not served)`:`no index.html in ${r} (build it first, or point at the build output)`})}const{url:l}=await(0,D.serveSite)(i.root,i.main?{refresh:()=>(0,C.ensureDefaultBranchSnapshot)(o.projectPath)}:{}),d=a?`${a}/${"."===e?"":e}`.replace(/\/$/,""):r,u="string"==typeof s&&s.trim()?s.trim():d.replace(/[^a-zA-Z0-9_-]+/g,"-").replace(/^-|-$/g,""),h=(0,O.registerPreviewHandle)(u,l,"string"==typeof n&&n.trim()?n.trim():void 0,{requestId:a,fallbackLabel:d})
3721
- t.json({success:!0,data:{...h,dir:r,ref:`live:${h.name}`}})}catch(e){t.status(500).json({success:!1,error:(0,P.getErrorMessage)(e)})}}),e.delete("/api/previews/:name",(e,t)=>{(0,O.unregisterPreviewHandle)(e.params.name),t.json({success:!0,data:{name:e.params.name,deleted:!0}})}),e.get("/api/file",async(e,t)=>{try{const r="string"==typeof e.query.path?e.query.path:"",s="string"==typeof e.query.ref&&/^[\w\/.-]+$/.test(e.query.ref)&&!e.query.ref.startsWith("-")?e.query.ref:void 0,n=(0,X.refusedRepoPath)(r)
3723
+ t.json({success:!0,data:{...h,dir:r,ref:`live:${h.name}`}})}catch(e){t.status(500).json({success:!1,error:(0,P.getErrorMessage)(e)})}}),e.delete("/api/previews/:name",(e,t)=>{const r=(0,O.getPreviewHandle)(e.params.name),s=!!r&&(0,D.stopSiteAt)(r.url);(0,O.unregisterPreviewHandle)(e.params.name),t.json({success:!0,data:{name:e.params.name,deleted:!0,stopped:s}})}),e.get("/api/file",async(e,t)=>{try{const r="string"==typeof e.query.path?e.query.path:"",s="string"==typeof e.query.ref&&/^[\w\/.-]+$/.test(e.query.ref)&&!e.query.ref.startsWith("-")?e.query.ref:void 0,n=(0,X.refusedRepoPath)(r)
3722
3724
  if(n)return void t.status(400).json({success:!1,error:n})
3723
3725
  const i=524288,a=async e=>{const t=(0,w.requestWorktreesDir)(o.projectPath)
3724
3726
  let r=null
@@ -3849,7 +3851,7 @@ let s=null
3849
3851
  try{const e=o.getProjectId(),t=e?await(0,v.readAllPages)(o.apiClient,t=>`/api/projects/${e}/test-runs?limit=100&offset=${t}`,500):void 0
3850
3852
  if(t){const e=e=>"string"==typeof e?(0,g.worktreeOnThisComputer)(M.default.join(o.projectPath,y.ALCHEMY_DIR_NAME),e):null
3851
3853
  s=t.some(t=>e(t?.worktreePath)===e(r)&&("pending"===t.status||"running"===t.status))}}catch(e){o.logger.debug("release-pool-slot sibling lookup failed",{error:(0,P.getErrorMessage)(e)})}if(!1!==s)return o.logger.info("Pool slot kept for the dispatch idle watch",{worktreePath:r,reason:null===s?"sibling check unavailable":"sibling run still active"}),void t.json({success:!0,released:!1});(0,N.releaseSlot)((0,g.worktreeOnThisComputer)(M.default.join(o.projectPath,y.ALCHEMY_DIR_NAME),r)),o.logger.info("Released pool slot via report-back",{worktreePath:r}),t.json({success:!0,released:!0})}catch(e){o.logger.warn("release-pool-slot failed (non-fatal)",{error:(0,P.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,P.getErrorMessage)(e)})}})}
3852
- const m=t(76982),g=t(71856),w=t(7202),y=t(14139),v=t(5641),b=t(44600),k=t(96302),S=t(44876),I=t(29418),$=t(96396),j=t(36160),E=t(41666),A=f(t(79896)),C=t(51857),_=t(46898),T=t(6156),x=t(75453),M=f(t(16928)),R=t(27478),P=t(58824),O=t(28295),D=t(3811),q=t(79896),N=t(60489),F=t(92456),L=t(83807),U=t(45738),B=t(65856),H=t(82167),W=t(99970),G=t(24106),K=t(73775),V=t(27584),z=t(3031),Y=t(52232),J=t(62040),X=t(32559),Q=t(96511),Z=new Map,ee=new Map,te=new T.ParallelNotes
3854
+ const m=t(76982),g=t(71856),w=t(7202),y=t(14139),v=t(5641),b=t(44600),k=t(96302),S=t(44876),I=t(29418),$=t(96396),j=t(36160),E=t(41666),A=f(t(79896)),C=t(51857),_=t(46898),T=t(6156),x=t(75453),M=f(t(16928)),R=t(27478),P=t(58824),O=t(28295),D=t(3811),q=t(79896),N=t(60489),F=t(92456),L=t(83807),U=t(45738),B=t(65856),H=t(82167),W=t(99970),G=t(24106),K=t(73775),z=t(27584),V=t(3031),Y=t(52232),J=t(62040),X=t(32559),Q=t(96511),Z=new Map,ee=new Map,te=new T.ParallelNotes
3853
3855
  let re=null
3854
3856
  const se=e=>({id:String(e?.localId??e?.id??""),title:String(e?.title??""),status:String(e?.status??""),description:"string"==typeof e?.description?e.description:null})
3855
3857
  exports.COMPLETION_REPLAY_BUDGET_MS=15e4
@@ -4758,7 +4760,7 @@ let h=null
4758
4760
  const p=new Map},62366(e,exports,t){"use strict"
4759
4761
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
4760
4762
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
4761
- const s=t(84920),n=t(71856),o=t(65856),i=t(42403),a=t(60721),c=t(16130),l=t(5641),d=t(17039),u=t(53526),h=t(53691),p=t(8469),f=t(97387),m=t(52232),g=t(61161),w=r(t(7252)),y=r(t(77174)),v=r(t(98577)),b=t(27584),k=t(79896),S=t(90473),I=t(27207),$=t(85959),j=t(5452),E=t(60885),A=t(21178),C=t(28295),_=t(52640),T=t(65232),x=t(95671),M=t(44192),R=t(14139),P=t(41152),O=r(t(16928)),D=r(t(79896)),q=r(t(70857)),N=t(5607),F=t(76634),L=t(31),U=t(90524),B=t(18984),H=t(58824),W=t(29869),G=t(67078),K=t(60070),V=t(37210),z=t(87227),Y=t(82928),J=t(76616),X=t(15047),Q=t(90629),Z=t(42298),ee=t(67563),te=t(41950),re=t(88087),se=t(1285),ne=t(20564),oe=Object.keys(N.actionHandlers).join(","),ie=t(6850),ae=t(58836),ce=t(25146),le=t(23411),de=t(43218),ue=t(81570),he=t(18815),pe=t(73613),fe=t(49779),me=t(76982),ge=t(53340),we=t(89063),ye=t(61292),ve=t(11017),be=t(28148),ke=t(18649),Se=t(64306),Ie=t(96685),$e=t(49816),je=t(35419),Ee=t(33521),Ae=t(56844),Ce=t(84352),_e=t(40713),Te=t(75361),xe=t(75167),Me=t(72188),Re=t(6688),Pe=t(39068),Oe=t(3435),De=t(48463),qe=t(60489),Ne=t(24106),Fe=t(81618),Le=t(80211),Ue=t(2566),Be=t(83807),He=t(92456),We=t(95040),Ge=t(60355),Ke=t(31026),Ve=t(73782),ze=t(53608),Ye=t(63484),Je=t(31851),Xe=t(84102),Qe=t(75979),Ze=t(79329),et=t(60833),tt=t(42593),rt=t(69706),st=t(11773),nt=t(20990),ot=t(45956),it=t(36799),at=t(86116),ct=t(13751),lt=t(13791),dt=new Set(["opencode-proxy","machine-proxy","run-tests","review-fanout","main-checks","check-target","judge-proposal"]),ut=65536
4763
+ const s=t(84920),n=t(71856),o=t(65856),i=t(42403),a=t(60721),c=t(16130),l=t(5641),d=t(17039),u=t(53526),h=t(53691),p=t(8469),f=t(97387),m=t(52232),g=t(61161),w=r(t(7252)),y=r(t(77174)),v=r(t(98577)),b=t(27584),k=t(79896),S=t(90473),I=t(27207),$=t(85959),j=t(5452),E=t(60885),A=t(21178),C=t(28295),_=t(52640),T=t(65232),x=t(95671),M=t(44192),R=t(14139),P=t(41152),O=r(t(16928)),D=r(t(79896)),q=r(t(70857)),N=t(5607),F=t(76634),L=t(31),U=t(90524),B=t(18984),H=t(58824),W=t(29869),G=t(67078),K=t(60070),z=t(37210),V=t(87227),Y=t(82928),J=t(76616),X=t(15047),Q=t(90629),Z=t(42298),ee=t(67563),te=t(41950),re=t(88087),se=t(1285),ne=t(20564),oe=Object.keys(N.actionHandlers).join(","),ie=t(6850),ae=t(58836),ce=t(25146),le=t(23411),de=t(43218),ue=t(81570),he=t(18815),pe=t(73613),fe=t(49779),me=t(76982),ge=t(53340),we=t(89063),ye=t(61292),ve=t(11017),be=t(28148),ke=t(18649),Se=t(64306),Ie=t(96685),$e=t(49816),je=t(35419),Ee=t(33521),Ae=t(56844),Ce=t(84352),_e=t(40713),Te=t(75361),xe=t(75167),Me=t(72188),Re=t(6688),Pe=t(39068),Oe=t(3435),De=t(48463),qe=t(60489),Ne=t(24106),Fe=t(81618),Le=t(80211),Ue=t(2566),Be=t(83807),He=t(92456),We=t(95040),Ge=t(60355),Ke=t(31026),ze=t(73782),Ve=t(53608),Ye=t(63484),Je=t(31851),Xe=t(84102),Qe=t(75979),Ze=t(79329),et=t(60833),tt=t(42593),rt=t(69706),st=t(11773),nt=t(20990),ot=t(45956),it=t(36799),at=t(86116),ct=t(13751),lt=t(13791),dt=new Set(["opencode-proxy","machine-proxy","run-tests","review-fanout","main-checks","check-target","judge-proposal"]),ut=65536
4762
4764
  class ht{rememberApprovedCommand(e,t,r){const s=t.rule,o=r?"in this environment":"on your machine",i=(0,Ee.isStandingRulePattern)(s),a=`You chose "Always allow" on the approval card for \`${t.askedCommand.slice(0,300)}\`${t.approvalId?` (card ${t.approvalId})`:""}${t.sessionId?`, asked by agent session ${t.sessionId}`:""}, on ${t.at.replace("T"," ").slice(0,16)} UTC.`;(async()=>{(await(0,$e.getRegisteredAllows)(this.apiClient,e,r??null).catch(()=>[])).some(e=>("string"==typeof e?e:e.exact).trim()===s.trim())||(await this.apiClient.post(`/api/projects/${e}/check-commands`,{name:"ok:card",command:(0,n.commandAsStored)(s),description:`${a} ${i?`It allows exactly this shape, where <file> is any one file inside the project, ${o}`:`This exact command runs ${o}`} without asking. Delete it or switch it off to be asked again.`,timeout:120,isActive:!0,exactMatch:!0,...r?{environmentId:r}:{}}),(0,$e.invalidateRegisteredAllows)(e))})().catch(e=>this.logger.warn("Failed to remember approved command",{error:String(e)}))}rememberJudgeApprovedCommand(e,t,r){const s=new Date(Date.now()+ht.JUDGE_APPROVAL_TTL_MS).toISOString()
4763
4765
  this.apiClient.post(`/api/projects/${e}/check-commands`,{name:"agent:judge",command:(0,n.commandAsStored)(t),description:"Approved by the command judge. Expires on its own; delete it to withdraw the approval sooner.",timeout:120,isActive:!0,expiresAt:s,exactMatch:!0,...r?{environmentId:r}:{}}).then(()=>(0,$e.invalidateRegisteredAllows)(e)).catch(e=>this.logger.warn("Failed to remember judge-approved command",{error:String(e)}))}get workCopiesReady(){return this.workCopiesGate.promise}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})
4764
4766
  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})
@@ -4768,7 +4770,7 @@ return{promise:new Promise(t=>{e=t}),resolve:e}})(),this.pendingConfigReload=!1,
4768
4770
  const r=(0,P.createMetricsCollector)(!1)
4769
4771
  if((0,st.setFleetServerKey)(t.projectPath?t.projectPath.split("/").pop():void 0),this.apiClient=new M.BaseApiClient(t.remoteServerUrl,e,r,t.authToken,{timeout:35e3,serverKey:t.projectPath?t.projectPath.split("/").pop():void 0}),t.tokenManager){const e=t.tokenManager
4770
4772
  this.apiClient.setTokenRefresher(async()=>{const t=(await e.getToken())?.token
4771
- return t&&t!==this.options.authToken&&this.updateAuthToken(t),t})}this.configurer=new B.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.hookExecutor=new V.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,w.default)(),r=e=>(0,Z.isOriginAllowed)(e,{frontendOrigin:this.frontendOrigin,extra:process.env.ALCHEMY_EXTRA_UI_ORIGINS}),s=new Set
4773
+ return t&&t!==this.options.authToken&&this.updateAuthToken(t),t})}this.configurer=new B.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.hookExecutor=new z.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,w.default)(),r=e=>(0,Z.isOriginAllowed)(e,{frontendOrigin:this.frontendOrigin,extra:process.env.ALCHEMY_EXTRA_UI_ORIGINS}),s=new Set
4772
4774
  e.use((e,t,r)=>{const n=e.headers.host
4773
4775
  if((0,Z.isLoopbackHost)(n))return r()
4774
4776
  s.has(String(n))||(s.add(String(n)),this.logger.warn("Refused a request addressed to a name other than localhost (DNS rebinding?)",{host:n,path:e.path})),t.status(403).json({success:!1,error:Z.HOST_REFUSED_MESSAGE})}),e.use((0,Z.inertActiveContent)()),e.use((e,t,s)=>{const n=e.headers.origin
@@ -4832,7 +4834,7 @@ if("1"===e.query.stats){const e=a&&c?await(0,He.computeDiffStats)(o,a,c):void 0
4832
4834
  if(!e&&0===l.length)return void t.status(404).json({success:!1,error:`No diff stats for ${r}.`})
4833
4835
  const s=(0,We.withDocumentStats)(e,l)
4834
4836
  return void t.json({success:!0,data:{requestId:r,baseSha:a,headSha:c,stats:s}})}const d=5e5,u=a&&c?await(0,g.documentDiffGit)(this.options.projectPath).raw([...(0,g.documentDiffGitArgs)(),"diff","-M50%",`${a}..${c}`]):"",h=(0,We.withDocumentEdits)(u,l),p=h.length>d
4835
- t.json({success:!0,data:{requestId:r,baseSha:a,headSha:c,diff:p?h.slice(0,d):h,truncated:p}})}catch(e){t.status(500).json({success:!1,error:(0,H.getErrorMessage)(e)})}}),(0,Ke.registerLinearRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient}),(0,Ve.registerLocalModelRoutes)(e),(0,Qe.registerExhibitPreviewRoutes)(e),e.get("/api/publish-capabilities",async(e,t)=>{try{const e=await(0,F.getPublishCapabilities)(this.options.projectPath,(0,F.defaultRunner)())
4837
+ t.json({success:!0,data:{requestId:r,baseSha:a,headSha:c,diff:p?h.slice(0,d):h,truncated:p}})}catch(e){t.status(500).json({success:!1,error:(0,H.getErrorMessage)(e)})}}),(0,Ke.registerLinearRoutes)(e,{getProjectId:()=>this.projectId,apiClient:this.apiClient}),(0,ze.registerLocalModelRoutes)(e),(0,Qe.registerExhibitPreviewRoutes)(e),e.get("/api/publish-capabilities",async(e,t)=>{try{const e=await(0,F.getPublishCapabilities)(this.options.projectPath,(0,F.defaultRunner)())
4836
4838
  t.json({success:!0,data:e})}catch(e){t.status(500).json({success:!1,error:(0,H.getErrorMessage)(e)})}}),e.post("/api/requests/:id/publish-pr",async(e,t)=>{const r=e.params.id
4837
4839
  try{const s=this.projectId
4838
4840
  if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
@@ -4888,7 +4890,7 @@ r.json({success:!0,data:a})}catch(e){this.logger.error("Hook event processing fa
4888
4890
  if(!s||"before"!==a&&"after"!==a)return void r.status(400).json({success:!1,error:"toolName and timing ('before'|'after') are required"})
4889
4891
  if(n){const{notifyPlannerHandoffToolEvent:e}=await t.e(991).then(()=>t(18991))
4890
4892
  e(String(n),String(s),o)}if("after"===a&&e.body?.callId&&this.projectId){const{closeMcpReceipt:r}=await t.e(111).then(()=>t(56111))
4891
- 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,z.parseToolEvents)({toolName:s,sessionId:n||"",input:o,output:i,timing:a})
4893
+ 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,V.parseToolEvents)({toolName:s,sessionId:n||"",input:o,output:i,timing:a})
4892
4894
  let l=0
4893
4895
  for(const e of c)l+=(await this.hookExecutor.processEvent({eventType:e.eventType,sessionId:n||"",variables:e.variables,timing:e.timing})).triggered
4894
4896
  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,H.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,H.getErrorMessage)(e)})}}),e.post("/api/web-call",async(e,r)=>{try{const{tool:s,sessionId:n,callId:o,input:i,metadata:a,error:c}=e.body||{}
@@ -4917,7 +4919,7 @@ try{await(0,De.sweepStaleSetupWorktrees)(a,this.logger)
4917
4919
  const e=await(0,De.resolveSetupBranch)(this.apiClient,c,n,"string"==typeof o?o:void 0,this.logger,a),s=(0,De.generateSetupId)()
4918
4920
  d=O.default.join((0,De.getSetupWorktreesBase)(a),`verify-${s}`),await(0,De.createSetupWorktree)(a,d,e,this.logger)
4919
4921
  const u=await(0,Fe.withContainerLabels)({"alchemy.purpose":"verify","alchemy.request":"string"==typeof o?o:void 0},()=>(0,Ne.runConfinedGate)(d,[{name:"verify",command:i,required:!0,timeout:600}],l,{projectName:`verify-${s}`})),h=!String(r.shellInit??"").trim()&&(0,Ue.carriesOwnShellInit)(i),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."]:[]
4920
- h&&this.logger.warn("environment verify passed on a self-sourcing smoke command",{smoke:i}),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,H.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,H.getErrorMessage)(e)})}finally{if(d&&D.default.existsSync(d))try{await(0,Be.simpleGit)(a).raw(["worktree","remove","--force",d])}catch{}}}),(0,ze.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,r)=>this.rememberApprovedCommand(e,t,r),rememberJudgeApprovedCommand:(e,t,r)=>this.rememberJudgeApprovedCommand(e,t,r),visualBaselineDir:()=>this.visualBaselineDir()}),(0,Xe.registerRelaySettingsRoutes)(e,{projectPath:this.options.projectPath}),(0,Je.registerProducedFilesRoutes)(e,{projectPath:this.options.projectPath,logger:this.logger,getDocument:async e=>{const t=this.projectId
4922
+ h&&this.logger.warn("environment verify passed on a self-sourcing smoke command",{smoke:i}),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,H.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,H.getErrorMessage)(e)})}finally{if(d&&D.default.existsSync(d))try{await(0,Be.simpleGit)(a).raw(["worktree","remove","--force",d])}catch{}}}),(0,Ve.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,r)=>this.rememberApprovedCommand(e,t,r),rememberJudgeApprovedCommand:(e,t,r)=>this.rememberJudgeApprovedCommand(e,t,r),visualBaselineDir:()=>this.visualBaselineDir()}),(0,Xe.registerRelaySettingsRoutes)(e,{projectPath:this.options.projectPath}),(0,Je.registerProducedFilesRoutes)(e,{projectPath:this.options.projectPath,logger:this.logger,getDocument:async e=>{const t=this.projectId
4921
4923
  if(!t||!/^[A-Za-z0-9_-]+$/.test(e))return null
4922
4924
  const r=await this.apiClient.get(`/api/projects/${t}/documents/${e}`).catch(()=>null),s=r?.data??r
4923
4925
  return s&&"string"==typeof s.content?{filename:String(s.filename||`${e}.md`),content:s.content}:null}}),(0,Ye.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()
@@ -5699,12 +5701,12 @@ const s=e.slice(r+1).find(e=>"assistant"===e.role&&e.created>0)
5699
5701
  return s?new Date(s.created).toISOString():void 0}function W(e,t){const r=e.findIndex(e=>e.id===t)
5700
5702
  return e.slice(r+1).filter(e=>"assistant"===e.role&&e.text.trim()).map(e=>e.id)}var G,K=this&&this.__createBinding||(Object.create?function(e,t,r,s){void 0===s&&(s=r)
5701
5703
  var n=Object.getOwnPropertyDescriptor(t,r)
5702
- 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]}),V=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),z=this&&this.__importStar||(G=function(e){return G=Object.getOwnPropertyNames||function(e){var t=[]
5704
+ 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]}),z=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),V=this&&this.__importStar||(G=function(e){return G=Object.getOwnPropertyNames||function(e){var t=[]
5703
5705
  for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[t.length]=r)
5704
5706
  return t},G(e)},function(e){if(e&&e.__esModule)return e
5705
5707
  var t={}
5706
5708
  if(null!=e)for(var r=G(e),s=0;s<r.length;s++)"default"!==r[s]&&K(t,e,r[s])
5707
- return V(t,e),t})
5709
+ return z(t,e),t})
5708
5710
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.extendChatWatches=function(e){if(e>0)for(const t of ce.values())t.hardCapAtMs+=e},exports.persistedExchanges=o,exports.resumeInboxChatWatches=function(e){try{const t=s(e.projectPath)
5709
5711
  if(!re.existsSync(t))return
5710
5712
  const r=JSON.parse(re.readFileSync(t,"utf-8"))
@@ -5727,7 +5729,7 @@ s.checking=!0
5727
5729
  try{await r()
5728
5730
  const n=await T(e,s,t)
5729
5731
  return n&&s.logger.warn("inbox-chat loop: turn stopped answering; ended it and resumed it once",{sessionId:e,markerMessageId:s.exchanges[s.exchanges.length-1].markerMessageId}),n}finally{s.checking=!1}},exports.extractTurnSilence=x,exports.absorbedByReply=q
5730
- const Y=t(60833),J=t(58824),X=t(42593),Q=t(24068),Z=t(4234),ee=t(17039),te=t(81602),re=z(t(79896)),se=z(t(16928)),ne=t(36799),oe=3e5,ie=9e5,ae=8e3,ce=new Map
5732
+ const Y=t(60833),J=t(58824),X=t(42593),Q=t(24068),Z=t(4234),ee=t(17039),te=t(81602),re=V(t(79896)),se=V(t(16928)),ne=t(36799),oe=3e5,ie=9e5,ae=8e3,ce=new Map
5731
5733
  let le=null
5732
5734
  const de=new Map,ue=6e4,he=/^(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,pe=/^(ok(?:ay)?|done|sure|noted|got it|all set|that's it|right)[.!]?$/i,fe=/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Thai}]/u,me=/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Thai}]+/gu,ge=/^(?:(?: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,we=/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,ye=new Set},81602(e,exports){"use strict"
5733
5735
  exports.Cr=exports.UT=void 0,exports.wakeThatTellsUser=function(e,t){const r="string"==typeof e?exports.Cr.get(e):void 0
@@ -5892,8 +5894,10 @@ return l.set(a,{server:h,port:p,root:a}),{port:p,url:`http://127.0.0.1:${p}`}},e
5892
5894
  return l.get(t)?.server.close(),void l.delete(t)}for(const[,e]of l)e.server.close()
5893
5895
  l.clear()},exports.stopSitesUnder=function(e){const t=(0,i.resolve)(e),r=[]
5894
5896
  for(const[e,s]of l)(e===t||e.startsWith(t+i.sep))&&(s.server.close(),l.delete(e),r.push(`http://127.0.0.1:${s.port}`))
5895
- return r},exports.listServedSites=function(){return Array.from(l.values()).map(e=>({root:e.root,port:e.port}))}
5896
- const s=t(58611),n=t(79896),o=t(91943),i=t(16928),a=t(7202),c={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".ico":"image/x-icon",".avif":"image/avif",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".txt":"text/plain; charset=utf-8",".pdf":"application/pdf"},l=new Map},3031(e,exports,t){"use strict"
5897
+ return r},exports.stopSiteAt=function(e){let t=0
5898
+ try{t=Number(new URL(e).port)}catch{return!1}for(const[e,r]of l)if(r.port===t)return r.server.close(),l.delete(e),!0
5899
+ return!1},exports.listServedSites=function(){return Array.from(l.values()).map(e=>({root:e.root,port:e.port}))}
5900
+ const s=t(58611),n=t(79896),o=t(91943),i=t(16928),a=t(7202),c={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".wasm":"application/wasm",".webmanifest":"application/manifest+json",".xml":"application/xml; charset=utf-8",".mp3":"audio/mpeg",".wav":"audio/wav",".ogg":"audio/ogg",".mp4":"video/mp4",".webm":"video/webm",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".ico":"image/x-icon",".avif":"image/avif",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".txt":"text/plain; charset=utf-8",".pdf":"application/pdf"},l=new Map},3031(e,exports,t){"use strict"
5897
5901
  exports.shootUrl=async function(e,r={}){const s=Math.min(1920,Math.max(320,"number"==typeof r.width?Math.round(r.width):1280)),{chromium:n}=t(71257)
5898
5902
  let o
5899
5903
  try{o=await n.launch({channel:"chrome",args:["--no-sandbox"]})}catch{o=await n.launch({args:["--no-sandbox"]})}try{const t=await o.newPage({viewport:{width:s,height:900},deviceScaleFactor:1,ignoreHTTPSErrors:!0})
@@ -6812,56 +6816,60 @@ if(!n.ok){try{o.default.rmdirSync(s)}catch{}return{ok:!1,reason:n.reason}}return
6812
6816
  exports.printBanner=function(){const e=(0,r.ownVersion)()??"unknown"
6813
6817
  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"+e,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::",""].join("\n"))}
6814
6818
  const r=t(42403)},38778(e,exports,t){"use strict"
6815
- async function r(e,t,r,s,n="cli-init"){try{const o=(0,i.basename)(r)||"Local Project",c=process.env.ALCHEMY_MACHINE_ID,l=(0,a.folderKeyFromEnv)(),d=await fetch(`${e}/api/projects`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,"X-Client-Type":"alchemy-local-service",...(0,w.machineCallerHeaders)(e)},body:JSON.stringify({name:o,description:`Project for ${o}`,status:"active",metadata:{basePath:r,createdVia:n},...c?{machineId:c}:{},...l?{folderKey:l}:{}})})
6816
- if(b=void 0,d.ok){const e=await d.json()
6817
- return e.data?.id||e.id}if(s.info(`Project registration failed (HTTP ${d.status})`),d.status>=400&&d.status<500){const e=await d.json().catch(()=>null),t=e?.error?.message??e?.error??e?.message
6818
- "string"==typeof t&&t.trim()&&(b=t.trim())}}catch(e){s.info("Project registration failed",{error:(0,h.getErrorMessage)(e)})}}async function s(e){if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
6819
+ function r(){return S}function s(e){return e?`\u26a0\ufe0f This project is not registered with AlchemyLab yet: ${e}.`.replace(/\.\.$/,"."):"\u26a0\ufe0f This project is not registered with AlchemyLab yet: the hosted service did not answer. Run `alchemy` again to try once more."}async function n(e,t,r,s,n="cli-init"){try{const o=(0,c.basename)(r)||"Local Project",i=process.env.ALCHEMY_MACHINE_ID,a=(0,l.folderKeyFromEnv)()
6820
+ let d=await u()
6821
+ for(let h=0;h<6&&503===d.status;h++)await new Promise(e=>setTimeout(e,2e3)),d=await u()
6822
+ async function u(){return fetch(`${e}/api/projects`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,"X-Client-Type":"alchemy-local-service",...(0,v.machineCallerHeaders)(e)},body:JSON.stringify({name:o,description:`Project for ${o}`,status:"active",metadata:{basePath:r,createdVia:n},...i?{machineId:i}:{},...a?{folderKey:a}:{}})})}if(S=void 0,d.ok){const p=await d.json()
6823
+ return p.data?.id||p.id}if(s.info(`Project registration failed (HTTP ${d.status})`),401===d.status)S="the sign-in on this computer is no longer accepted. Run `alchemy logout`, then `alchemy`"
6824
+ else if(503===d.status)S="the hosted service has not heard from this computer yet. Run `alchemy` again in a moment"
6825
+ else if(d.status>=400&&d.status<500){const m=await d.json().catch(()=>null),g=m?.error?.message??m?.error??m?.message
6826
+ "string"==typeof g&&g.trim()&&(S=g.trim())}}catch(w){s.info("Project registration failed",{error:(0,f.getErrorMessage)(w)})}}async function o(e){if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
6819
6827
  if(e.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
6820
6828
  try{const t=await fetch(e+"/health")
6821
6829
  if(t.ok){const e=await t.json()
6822
- if(e?.data?.frontendUrl)return e.data.frontendUrl}}catch{}return e.replace(/:\d+/,":3000")}exports.ProjectRefused=void 0,exports.checkProjectIdentity=function(e){const t=(0,y.identityPath)(e)
6823
- if(!(0,n.existsSync)(t))return
6830
+ if(e?.data?.frontendUrl)return e.data.frontendUrl}}catch{}return e.replace(/:\d+/,":3000")}exports.ProjectRefused=void 0,exports.checkProjectIdentity=function(e){const t=(0,b.identityPath)(e)
6831
+ if(!(0,i.existsSync)(t))return
6824
6832
  let r
6825
- try{r=JSON.parse((0,n.readFileSync)(t,"utf8"))?.metadata?.basePath}catch{return}if(!r)return
6826
- const s=e=>{try{return(0,n.realpathSync)(e)}catch{return(0,i.resolve)(e)}}
6827
- if(s(r)!==s(e))throw new v(["\u274c This folder is a copy that has not been re-identified."," .alchemy_lab/project.json belongs to a project whose directory is:",` ${r}`," but you are starting in:",` ${e}`,""," Starting here would register as the ORIGINAL project and silently do no"," work for this one. If a fork is still being created, wait for it to finish."," If you copied this folder by hand, delete .alchemy_lab/project.json and"," start again to get a project of its own."])},exports.prepareProject=async function(e){const{projectPath:t,remoteServerUrl:s,authToken:c,logger:l}=e,d=e.say??(e=>console.log(e)),u=await async function(e,t,s,a,c){if(function(e){return(0,n.existsSync)((0,i.join)(e,f.ALCHEMY_DIR_NAME,"project.json"))}(e))return!1
6828
- c("\u{1f4c1} Initializing AlchemyLab project..."),c("")
6829
- const l=await(0,m.ensureProjectRepository)(e)
6830
- if("created"===l.status)c(`\u{1f331} ${l.message}`)
6831
- else if("failed"===l.status||!l.hasHistory)throw function(e){const t=(0,i.join)(e,f.ALCHEMY_DIR_NAME)
6832
- try{(0,n.readdirSync)(t).every(e=>"server.log"===e)&&(0,n.rmSync)(t,{recursive:!0,force:!0})}catch{}}(e),new v([`\u26a0\ufe0f ${l.message??"This folder cannot be used as a project yet."}`," Nothing was registered and no servers were started."])
6833
- const d=(0,i.join)(e,f.ALCHEMY_DIR_NAME)
6834
- let u
6835
- await(0,o.mkdir)(d,{recursive:!0}),s?(c(`\u{1f4cb} Registering project "${(0,i.basename)(e)||"Local Project"}"...`),u=await r(t,s,e,a),c(u?`\u2705 Project registered: ${u}`:b?`\u26a0\ufe0f Not registered: ${b}`:"\u26a0\ufe0f Project registration failed (will retry next run)")):c("\u26a0\ufe0f No auth token, skipping project registration")
6836
- const h=(0,i.join)(d,"project.json")
6837
- return(0,n.writeFileSync)(h,JSON.stringify({id:u||null,name:(0,i.basename)(e)||"Local Project",status:"active",registeredRemotely:!!u,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:d}},null,2),"utf-8"),c(""),!0}(t,s,c,l,d),p=(0,y.identityPath)(t)
6838
- let k
6839
- try{const e=JSON.parse((0,n.readFileSync)(p,"utf-8"))
6840
- if(k=e.id||void 0,!1===e.registeredRemotely&&c){const o=await r(s,c,t,l,"cli-init-retry")
6841
- o&&(e.id=o,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,n.writeFileSync)(p,JSON.stringify(e,null,2),"utf-8"),k=o,d(`\u2705 Project registered: ${o}`))}}catch{l.warn("Could not read project.json")}const S=async(e,t,r)=>{const n=await fetch(`${s}${t}`,{method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${c}`,"X-Client-Type":"alchemy-local-service",...(0,w.machineCallerHeaders)(s)},...void 0===r?{}:{body:JSON.stringify(r)}})
6842
- if(!n.ok)throw new Error(`HTTP ${n.status}`)
6843
- return"GET"===e?n.json():void 0},I=c&&k?async e=>{const t=await S("GET",`/api/projects/${k}/requests/${e}`)
6844
- return(t?.data??t)?.type}:void 0,$=await(0,g.defaultBranchHasCode)(t,I).catch(()=>!1)
6845
- let j=Promise.resolve()
6846
- if(c){j=(0,g.reportProjectHasCode)(k,$,(e,t)=>S("PATCH",e,t))
6847
- const e=(0,a.folderKeyFromEnv)()
6848
- k&&e&&S("PATCH",`/api/projects/${k}`,{folderKey:e}).catch(e=>l.info("Folder key not recorded yet",{error:(0,h.getErrorMessage)(e)}))}return{firstRun:u,projectId:k,hasCode:$,codeReported:j}},exports.startProject=async function(e){const{projectPath:t,remoteServerUrl:r,tokenManager:o,authToken:a,projectId:m,logger:g}=e,w=e.port||await(0,p.findAvailablePort)(3456),y=new l.AlchemyActionServer(g,{projectPath:t,remoteServerUrl:r,port:w,authToken:a,tokenManager:o,retireTwins:e.retireTwins,onIdlePark:e.onIdlePark}),v=(0,d.getInstallContext)(),b=new c.MCPClientConfigurer(g,r,a),k=new u.UnifiedLauncher(g,v,b)
6849
- y.setReconfigureOpenCode(async()=>{const e=(await o.getToken())?.token??a,s=new c.MCPClientConfigurer(g,r,e),{installPath:n,nodePath:i}=v,l=await s.configureOpenCode(n,t,i,m?{projectId:m}:void 0,!0)
6850
- return{success:l.success,error:l.error}}),y.setRelaunchOpenCode(async()=>{const e=(await o.getToken())?.token??a,s=new c.MCPClientConfigurer(g,r,e)
6851
- return new u.UnifiedLauncher(g,v,s).launch({projectPath:t,settings:m?{projectId:m}:void 0,onServeExit:e=>y.noteServeExit(e)})})
6852
- try{const e=(0,i.join)(t,f.ALCHEMY_DIR_NAME,"opencode.json")
6853
- if((0,n.existsSync)(e)){const t=JSON.parse((0,n.readFileSync)(e,"utf-8"))
6854
- t&&null!=t.port&&(0,n.writeFileSync)(e,JSON.stringify({...t,port:null},null,2))}}catch{}const S=y.start(),[,I,$]=await Promise.all([S,s(r),Promise.race([y.workCopiesReady,S]).then(()=>k.launch({projectPath:t,settings:m?{projectId:m}:void 0,onServeExit:e=>y.noteServeExit(e)})).catch(e=>(g.warn("Failed to launch OpenCode serve",{error:(0,h.getErrorMessage)(e)}),{success:!1,error:(0,h.getErrorMessage)(e)}))])
6833
+ try{r=JSON.parse((0,i.readFileSync)(t,"utf8"))?.metadata?.basePath}catch{return}if(!r)return
6834
+ const s=e=>{try{return(0,i.realpathSync)(e)}catch{return(0,c.resolve)(e)}}
6835
+ if(s(r)!==s(e))throw new k(["\u274c This folder is a copy that has not been re-identified."," .alchemy_lab/project.json belongs to a project whose directory is:",` ${r}`," but you are starting in:",` ${e}`,""," Starting here would register as the ORIGINAL project and silently do no"," work for this one. If a fork is still being created, wait for it to finish."," If you copied this folder by hand, delete .alchemy_lab/project.json and"," start again to get a project of its own."])},exports.prepareProject=async function(e){const{projectPath:t,remoteServerUrl:o,authToken:d,logger:u}=e,h=e.say??(e=>console.log(e)),p=await async function(e,t,o,l,d){if(function(e){return(0,i.existsSync)((0,c.join)(e,g.ALCHEMY_DIR_NAME,"project.json"))}(e))return!1
6836
+ d("\u{1f4c1} Initializing AlchemyLab project..."),d("")
6837
+ const u=await(0,w.ensureProjectRepository)(e)
6838
+ if("created"===u.status)d(`\u{1f331} ${u.message}`)
6839
+ else if("failed"===u.status||!u.hasHistory)throw function(e){const t=(0,c.join)(e,g.ALCHEMY_DIR_NAME)
6840
+ try{(0,i.readdirSync)(t).every(e=>"server.log"===e)&&(0,i.rmSync)(t,{recursive:!0,force:!0})}catch{}}(e),new k([`\u26a0\ufe0f ${u.message??"This folder cannot be used as a project yet."}`," Nothing was registered and no servers were started."])
6841
+ const h=(0,c.join)(e,g.ALCHEMY_DIR_NAME)
6842
+ let p
6843
+ await(0,a.mkdir)(h,{recursive:!0}),o?(d(`\u{1f4cb} Registering project "${(0,c.basename)(e)||"Local Project"}"...`),p=await n(t,o,e,l),d(p?`\u2705 Project registered: ${p}`:s(r()))):d("\u26a0\ufe0f No auth token, skipping project registration")
6844
+ const f=(0,c.join)(h,"project.json")
6845
+ return(0,i.writeFileSync)(f,JSON.stringify({id:p||null,name:(0,c.basename)(e)||"Local Project",status:"active",registeredRemotely:!!p,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:h}},null,2),"utf-8"),d(""),!0}(t,o,d,u,h),m=(0,b.identityPath)(t)
6846
+ let S
6847
+ try{const e=JSON.parse((0,i.readFileSync)(m,"utf-8"))
6848
+ if(S=e.id||void 0,!1===e.registeredRemotely&&d){const a=await n(o,d,t,u,"cli-init-retry")
6849
+ a?(e.id=a,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,i.writeFileSync)(m,JSON.stringify(e,null,2),"utf-8"),S=a,h(`\u2705 Project registered: ${a}`)):h(s(r()))}}catch{u.warn("Could not read project.json")}const I=async(e,t,r)=>{const s=await fetch(`${o}${t}`,{method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${d}`,"X-Client-Type":"alchemy-local-service",...(0,v.machineCallerHeaders)(o)},...void 0===r?{}:{body:JSON.stringify(r)}})
6850
+ if(!s.ok)throw new Error(`HTTP ${s.status}`)
6851
+ return"GET"===e?s.json():void 0},$=d&&S?async e=>{const t=await I("GET",`/api/projects/${S}/requests/${e}`)
6852
+ return(t?.data??t)?.type}:void 0,j=await(0,y.defaultBranchHasCode)(t,$).catch(()=>!1)
6853
+ let E=Promise.resolve()
6854
+ if(d){E=(0,y.reportProjectHasCode)(S,j,(e,t)=>I("PATCH",e,t))
6855
+ const e=(0,l.folderKeyFromEnv)()
6856
+ S&&e&&I("PATCH",`/api/projects/${S}`,{folderKey:e}).catch(e=>u.info("Folder key not recorded yet",{error:(0,f.getErrorMessage)(e)}))}return{firstRun:p,projectId:S,hasCode:j,codeReported:E}},exports.startProject=async function(e){const{projectPath:t,remoteServerUrl:r,tokenManager:s,authToken:n,projectId:a,logger:l}=e,w=e.port||await(0,m.findAvailablePort)(3456),y=new u.AlchemyActionServer(l,{projectPath:t,remoteServerUrl:r,port:w,authToken:n,tokenManager:s,retireTwins:e.retireTwins,onIdlePark:e.onIdlePark}),v=(0,h.getInstallContext)(),b=new d.MCPClientConfigurer(l,r,n),k=new p.UnifiedLauncher(l,v,b)
6857
+ y.setReconfigureOpenCode(async()=>{const e=(await s.getToken())?.token??n,o=new d.MCPClientConfigurer(l,r,e),{installPath:i,nodePath:c}=v,u=await o.configureOpenCode(i,t,c,a?{projectId:a}:void 0,!0)
6858
+ return{success:u.success,error:u.error}}),y.setRelaunchOpenCode(async()=>{const e=(await s.getToken())?.token??n,o=new d.MCPClientConfigurer(l,r,e)
6859
+ return new p.UnifiedLauncher(l,v,o).launch({projectPath:t,settings:a?{projectId:a}:void 0,onServeExit:e=>y.noteServeExit(e)})})
6860
+ try{const e=(0,c.join)(t,g.ALCHEMY_DIR_NAME,"opencode.json")
6861
+ if((0,i.existsSync)(e)){const t=JSON.parse((0,i.readFileSync)(e,"utf-8"))
6862
+ t&&null!=t.port&&(0,i.writeFileSync)(e,JSON.stringify({...t,port:null},null,2))}}catch{}const S=y.start(),[,I,$]=await Promise.all([S,o(r),Promise.race([y.workCopiesReady,S]).then(()=>k.launch({projectPath:t,settings:a?{projectId:a}:void 0,onServeExit:e=>y.noteServeExit(e)})).catch(e=>(l.warn("Failed to launch OpenCode serve",{error:(0,f.getErrorMessage)(e)}),{success:!1,error:(0,f.getErrorMessage)(e)}))])
6855
6863
  let j
6856
6864
  if($.success){const e="port"in $?$.port:void 0,r="pid"in $?$.pid:void 0
6857
6865
  if(j={success:!0,port:e,pid:r},e){r&&y.trackChildPid(r),y.setOpenCodePort(e)
6858
- const s=(0,i.join)(t,f.ALCHEMY_DIR_NAME,"opencode.json");(0,n.writeFileSync)(s,JSON.stringify({port:e,pid:r,startedAt:(new Date).toISOString()},null,2)),g.info("Wrote OpenCode config",{path:s,port:e})}}else j={success:!1,error:"error"in $&&$.error?String($.error):"no reason reported"}
6859
- return{actionServer:y,frontendUrl:I,opencode:j}},exports.resolveFrontendUrl=s,exports.getRemoteServerUrl=async function(e){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
6866
+ const s=(0,c.join)(t,g.ALCHEMY_DIR_NAME,"opencode.json");(0,i.writeFileSync)(s,JSON.stringify({port:e,pid:r,startedAt:(new Date).toISOString()},null,2)),l.info("Wrote OpenCode config",{path:s,port:e})}}else j={success:!1,error:"error"in $&&$.error?String($.error):"no reason reported"}
6867
+ return{actionServer:y,frontendUrl:I,opencode:j}},exports.resolveFrontendUrl=o,exports.getRemoteServerUrl=async function(e){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
6860
6868
  try{const{createConfigManager:r}=await t.e(60).then(()=>t.t(49060,23)),s=r(e)
6861
6869
  return(await s.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}
6862
- const n=t(79896),o=t(91943),i=t(16928),a=t(40390),c=t(18984),l=t(62366),d=t(73962),u=t(81187),h=t(58824),p=t(66800),f=t(14139),m=t(37776),g=t(71879),w=t(11773),y=t(20990)
6863
- class v extends Error{constructor(e){super(e.join("\n")),this.lines=e}}let b
6864
- exports.ProjectRefused=v},89122(e,exports,t){"use strict"
6870
+ const i=t(79896),a=t(91943),c=t(16928),l=t(40390),d=t(18984),u=t(62366),h=t(73962),p=t(81187),f=t(58824),m=t(66800),g=t(14139),w=t(37776),y=t(71879),v=t(11773),b=t(20990)
6871
+ class k extends Error{constructor(e){super(e.join("\n")),this.lines=e}}let S
6872
+ exports.ProjectRefused=k},89122(e,exports,t){"use strict"
6865
6873
  function r(e,t=process.env){const r=Number(t.ALCHEMY_DEV_WORDS_DELAY_MS??0)
6866
6874
  if(!Number.isFinite(r)||r<=0)return 0
6867
6875
  let s=""
@@ -7195,21 +7203,7 @@ const o=Array.isArray(n?.data?.wakes)?n.data.wakes:[]
7195
7203
  for(const e of o)"string"==typeof e.projectId&&("work"!==e.reason&&"start"!==e.reason&&"stop"!==e.reason||this.opts.wake(e.projectId,e.reason))
7196
7204
  const i=Array.isArray(n?.data?.creates)?n.data.creates:[]
7197
7205
  for(const e of i)"string"==typeof e.create&&this.opts.create?.(e.create)}catch(t){if(this.stopped)return
7198
- e=Math.min(Math.max(2*e,5e3),3e4),e>=3e4&&this.opts.logger.warn("The hosted service is not answering the wake poll",{error:t instanceof Error?t.message:String(t)})}}else e=6e4}}}},48893(e,exports,t){"use strict"
7199
- var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
7200
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.migrationCandidates=function(e,t={}){let r
7201
- try{r=JSON.parse(s.default.readFileSync(e,"utf8"))}catch{return[]}if(!Array.isArray(r))return[]
7202
- const i=new Set,a=[]
7203
- for(const e of r){if("string"!=typeof e?.projectId||"string"!=typeof e?.projectPath)continue
7204
- const r=(0,o.checkApprovable)(e.projectPath,t)
7205
- if(!r.ok||i.has(r.path))continue
7206
- let c
7207
- try{c=JSON.parse(s.default.readFileSync(n.default.join(r.path,".alchemy_lab","project.json"),"utf8"))}catch{continue}if(c?.id!==e.projectId)continue
7208
- const l=c?.metadata?.basePath
7209
- if("string"!=typeof l)continue
7210
- let d
7211
- try{d=s.default.realpathSync(l)}catch{continue}d===r.path&&(i.add(r.path),a.push({path:r.path,dev:r.dev,ino:r.ino,projectId:e.projectId,name:"string"==typeof c?.name&&c.name?c.name:n.default.basename(r.path)}))}return a.sort((e,t)=>e.path.localeCompare(t.path))}
7212
- const s=r(t(79896)),n=r(t(16928)),o=t(79616)},67518(e,exports,t){"use strict"
7206
+ e=Math.min(Math.max(2*e,5e3),3e4),e>=3e4&&this.opts.logger.warn("The hosted service is not answering the wake poll",{error:t instanceof Error?t.message:String(t)})}}else e=6e4}}}},67518(e,exports,t){"use strict"
7213
7207
  function r(e){return String(e).replace(/[\u0000-\u0009\u000b-\u001f\u007f]/g,"").split("\n").map(e=>`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(" & return & ")}function s(e,t){for(const r of String(t.PATH??"").split(":")){if(!r)continue
7214
7208
  const t=a.default.join(r,e)
7215
7209
  try{return i.default.accessSync(t,i.default.constants.X_OK),t}catch{}}return null}var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
@@ -7395,16 +7389,16 @@ r.close()
7395
7389
  const n=s.trim().toLowerCase()
7396
7390
  return""===n?t:"y"===n||"yes"===n}async function o(e,t){const s={}
7397
7391
  process.env.ALCHEMY_LAB_HOME&&(s.ALCHEMY_LAB_HOME=process.env.ALCHEMY_LAB_HOME)
7398
- try{const n=await(0,B.installLoginItem)({nodePath:process.execPath,cliPath:r(),remoteServerUrl:t.remoteServerUrl,envPath:process.env.PATH??"/usr/bin:/bin",env:s,logPath:e.log})
7399
- console.log(n.loaded?`AlchemyLab will start when you log in (${n.file}).`:`Written to ${n.file}, but it could not be loaded now: ${n.detail??"no reason given"}. It loads at your next login.`),"darwin"===process.platform&&console.log('Started at login, macOS may ask once whether "node" may use a project in Documents, Desktop or Downloads. Answer it at the computer.')}catch(e){console.error(`Could not set AlchemyLab to start at login: ${e instanceof Error?e.message:String(e)}`)}}async function i(e,t){await(0,M.isServiceRunning)(e)||(console.log("Starting the AlchemyLab service..."),await(0,M.startServiceInBackground)(e,t,r())||(console.error(`\u274c The AlchemyLab service did not start. Its log: ${e.log}`),process.exit(1)))}function a(e){return e.state}async function c(e){let t,r
7400
- try{r=await(0,M.request)(e,"ping"),t=await(0,M.request)(e,"status")}catch{return void console.log("AlchemyLab service: not running. `alchemy service` starts it.")}console.log(`AlchemyLab service: running (pid ${r.pid}) for ${r.remote}`)
7392
+ try{const n=await(0,U.installLoginItem)({nodePath:process.execPath,cliPath:r(),remoteServerUrl:t.remoteServerUrl,envPath:process.env.PATH??"/usr/bin:/bin",env:s,logPath:e.log})
7393
+ console.log(n.loaded?`AlchemyLab will start every time this computer starts (${n.file}).`:`Written to ${n.file}, but it could not be loaded now: ${n.detail??"no reason given"}. It loads the next time this computer starts.`),"darwin"===process.platform&&console.log('Started with the computer, macOS may ask once whether "node" may use a project in Documents, Desktop or Downloads. Answer it at the computer.')}catch(e){console.error(`Could not set AlchemyLab to start at login: ${e instanceof Error?e.message:String(e)}`)}}async function i(e,t){await(0,x.isServiceRunning)(e)||(console.log("Starting the AlchemyLab service..."),await(0,x.startServiceInBackground)(e,t,r())||(console.error(`\u274c The AlchemyLab service did not start. Its log: ${e.log}`),process.exit(1)))}function a(e){return e.state}async function c(e){let t,r
7394
+ try{r=await(0,x.request)(e,"ping"),t=await(0,x.request)(e,"status")}catch{return void console.log("AlchemyLab service: not running. `alchemy service` starts it.")}console.log(`AlchemyLab service: running (pid ${r.pid}) for ${r.remote}`)
7401
7395
  const s=t.projects??[]
7402
7396
  if(0===s.length)return void console.log(" No projects on this computer yet. Run `alchemy` in a folder to add it.")
7403
7397
  const n=Math.max(8,...s.map(e=>e.name.length))+2
7404
7398
  console.log(` ${"PROJECT".padEnd(n)}${"STATE".padEnd(10)}${"PORT".padEnd(7)}FOLDER`)
7405
7399
  for(const e of s)console.log(` ${e.name.padEnd(n)}${a(e).padEnd(10)}${String(e.port??"").padEnd(7)}${e.path}`),e.reason&&console.log(` ${"".padEnd(n)}${e.reason}`)
7406
7400
  s.some(e=>"parked"===e.state||"stopped"===e.state)&&console.log(" A parked or stopped project connects when you open it in the app.")}function l(e){console.error(`\u274c ${e.reason??"It did not start."}`)
7407
- for(const t of(e.lines??[]).slice(-8))console.error(` ${t}`)}function d(e){const t=async(t,r={},s=12e4)=>{const n=await(0,M.request)(e,"new-computer",{op:t,...r},s)
7401
+ for(const t of(e.lines??[]).slice(-8))console.error(` ${t}`)}function d(e){const t=async(t,r={},s=12e4)=>{const n=await(0,x.request)(e,"new-computer",{op:t,...r},s)
7408
7402
  if(!n.ok)throw new Error(n.reason??"The AlchemyLab service did not do it.")
7409
7403
  return n}
7410
7404
  return{candidates:async()=>{const e=await t("candidates",{},4e4)
@@ -7414,7 +7408,8 @@ return{say:(e="")=>console.log(e),ask:t=>new Promise(r=>e.question(`${t} `,e=>r(
7414
7408
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.serviceSetUp=s,exports.runService=async function(e,t){if(t.includes("--foreground"))return async function(e){const t=(0,A.servicePaths)(e);(0,A.ensurePrivateDir)(t.dir)
7415
7409
  const s=(0,C.readServiceConfig)(t)
7416
7410
  s||(console.error("The AlchemyLab service is not set up for this server yet. Run `alchemy service` in a terminal first."),process.exit(1))
7417
- const n=(0,S.createLogger)({serviceName:"alchemy-service",console:!1,logToFile:!0,logFilePath:t.log,baseDir:t.dir}),o=new x.Supervisor({paths:t,config:s,logger:n,childModule:r(),childArgs:e=>["project-run","--project",e.path,"--expect",`${e.dev}:${e.ino}`],childEnv:()=>({...(0,C.childEnv)((0,C.readServiceConfig)(t)??s),ALCHEMY_MACHINE_ID:(0,p.machineIdentity)(t).id}),approvalEnv:{installDir:W},version:(0,j.ownVersion)()??void 0,onChange:()=>u?.noteChanged(),cap:()=>(0,C.readServiceConfig)(t)?.cap??(0,x.defaultCap)(),machineId:(0,p.machineIdentity)(t).id,confirmIdentity:async()=>(await c).register(),signedOut:()=>!0===u?.signedOut,startAsNew:(0,N.startAsNewProject)({remoteServerUrl:s.remoteServerUrl,machineId:(0,p.machineIdentity)(t).id,getToken:async()=>(await k.getToken())?.token}),connected:()=>i()}),i=async()=>{const e=(e,t)=>Promise.race([e,new Promise(e=>{const r=setTimeout(()=>e(!1),t)
7411
+ const n=(0,S.createLogger)({serviceName:"alchemy-service",console:!1,logToFile:!0,logFilePath:t.log,baseDir:t.dir}),o=new T.Supervisor({paths:t,config:s,logger:n,childModule:r(),childArgs:e=>["project-run","--project",e.path,"--expect",`${e.dev}:${e.ino}`],childEnv:()=>({...(0,C.childEnv)((0,C.readServiceConfig)(t)??s),ALCHEMY_MACHINE_ID:(0,p.machineIdentity)(t).id}),approvalEnv:{installDir:H},version:(0,j.ownVersion)()??void 0,onChange:()=>u?.noteChanged(),cap:()=>(0,C.readServiceConfig)(t)?.cap??(0,T.defaultCap)(),machineId:(0,p.machineIdentity)(t).id,confirmIdentity:async()=>(await c).register(),signedOut:()=>!0===u?.signedOut,startAsNew:(0,q.startAsNewProject)({remoteServerUrl:s.remoteServerUrl,machineId:(0,p.machineIdentity)(t).id,getToken:async()=>(await k.getToken())?.token}),connected:()=>i()}),i=async()=>{k.forgetCachedSignIn()
7412
+ const e=(e,t)=>Promise.race([e,new Promise(e=>{const r=setTimeout(()=>e(!1),t)
7418
7413
  r.unref?.()})]),t=await e(u?u.register(1e4):Promise.resolve(!1),12e3),r=await e(h?h.connectNow():Promise.resolve(!1),12e3)
7419
7414
  return!0===t&&!0===r}
7420
7415
  let a=()=>{}
@@ -7422,43 +7417,43 @@ const c=new Promise(e=>{a=e})
7422
7417
  let l,d,u,h,g,w
7423
7418
  const v=async(e=0)=>{l?.(),await(u?.stop().catch(()=>{})),h?.stop(),w?.stop(),g?.stop(),d?.close(),await o.stop(),process.exit(e)}
7424
7419
  process.on("SIGTERM",()=>{v(0)}),process.on("SIGINT",()=>{v(0)})
7425
- try{await o.start(()=>{v(0)})}catch(e){throw e instanceof x.AlreadyRunningError&&(console.error(e.message),process.exit(0)),e}const k=new I.TokenManager(n,t.dir,{remoteServerUrl:s.remoteServerUrl,allowInteractiveAuth:!1})
7426
- await k.initialize(),l=(0,R.startSignInWatchdog)(k,e=>n.info(e)),u=new q.MachineLink({paths:t,remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await k.getToken())?.token,projects:()=>o.status(),wake:(e,t)=>o.wake(e,t),archived:e=>o.setArchived(e),answered:(0,N.onListAnswered)({supervisor:o,remoteServerUrl:s.remoteServerUrl,machineId:(0,p.machineIdentity)(t).id,getToken:async()=>(await k.getToken())?.token,logger:n}),version:(0,j.ownVersion)()??void 0,create:e=>{M.create(e,!1).then(t=>n.info("A folder asked for from another device",{name:e,ok:t.ok,reason:t.reason}))},logger:n}),a(u),u.start(),h=new f.ContentLink({paths:t,remoteServerUrl:s.remoteServerUrl,privateData:!0===(0,C.readServiceConfig)(t)?.privateData,getToken:async()=>(await k.getToken())?.token,projectIds:()=>o.status().map(e=>e.projectId).filter(e=>"string"==typeof e),logger:n}),h.start(),g=new y.WordsBackup({store:h.store,stateFile:b.default.join(t.dir,"words-backup.json"),settings:()=>(0,y.backupSettingsOf)((0,C.readServiceConfig)(t)?.wordsBackup),privateData:()=>h?.privateData??!0===(0,C.readServiceConfig)(t)?.privateData,logger:n}),g.start()
7420
+ try{await o.start(()=>{v(0)})}catch(e){throw e instanceof T.AlreadyRunningError&&(console.error(e.message),process.exit(0)),e}const k=new I.TokenManager(n,t.dir,{remoteServerUrl:s.remoteServerUrl,allowInteractiveAuth:!1})
7421
+ await k.initialize(),l=(0,M.startSignInWatchdog)(k,e=>n.info(e)),u=new D.MachineLink({paths:t,remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await k.getToken())?.token,projects:()=>o.status(),wake:(e,t)=>o.wake(e,t),archived:e=>o.setArchived(e),answered:(0,q.onListAnswered)({supervisor:o,remoteServerUrl:s.remoteServerUrl,machineId:(0,p.machineIdentity)(t).id,getToken:async()=>(await k.getToken())?.token,logger:n}),version:(0,j.ownVersion)()??void 0,create:e=>{R.create(e,!1).then(t=>n.info("A folder asked for from another device",{name:e,ok:t.ok,reason:t.reason}))},logger:n}),a(u),u.start(),h=new f.ContentLink({paths:t,remoteServerUrl:s.remoteServerUrl,privateData:!0===(0,C.readServiceConfig)(t)?.privateData,getToken:async()=>(await k.getToken())?.token,projectIds:()=>o.status().map(e=>e.projectId).filter(e=>"string"==typeof e),logger:n}),h.start(),g=new y.WordsBackup({store:h.store,stateFile:b.default.join(t.dir,"words-backup.json"),settings:()=>(0,y.backupSettingsOf)((0,C.readServiceConfig)(t)?.wordsBackup),privateData:()=>h?.privateData??!0===(0,C.readServiceConfig)(t)?.privateData,logger:n}),g.start()
7427
7422
  const $=async e=>{const r=h?await h.setPrivateData(e):{privateData:e,switching:!1},s=(0,C.readServiceConfig)(t)
7428
7423
  return s&&s.privateData!==r.privateData&&(0,C.writeServiceConfig)(t,{...s,privateData:r.privateData}),n.info(r.privateData?"This install keeps its content on this computer":"This install keeps its content on the hosted service"),r}
7429
7424
  o.handle("connect-now",async()=>({ok:await i()})),o.handle("move-words-here",async()=>{const e=await $(!0)
7430
7425
  return{ok:!e.refused,reason:e.refused,privateData:e.privateData,switching:e.switching}})
7431
7426
  const E=new m.NewComputer({machineId:(0,p.machineIdentity)(t).id,remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await k.getToken())?.token,channelKey:()=>(0,f.channelKey)(t),store:h.store,backup:g,defaultFolder:()=>(0,y.backupSettingsOf)((0,C.readServiceConfig)(t)?.wordsBackup).folder})
7432
7427
  let _
7433
- o.handle("new-computer",e=>(0,m.newComputerCommand)(E,e)),o.handle("computer-gone",async e=>h?await h.sayGone(String(e.machineId??"")):{ok:!1,reason:"The words channel is not running."}),o.handle("pair-channel",async e=>h?await h.pairAgain(String(e.code??"")):{ok:!1,reason:"The words channel is not running."}),w=new F.ScheduleWaker({remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await k.getToken())?.token,parked:()=>o.status().filter(e=>"parked"===e.state&&e.projectId).map(e=>e.projectId),wake:e=>o.wake(e,"work"),logger:n}),w.start(),(0,O.resolveFrontendUrl)(s.remoteServerUrl).then(e=>{try{/^https?:\/\//.test(e)&&(_=new URL(e).origin)}catch{}})
7434
- const T=(0,U.nativeDialogs)(),M=new L.Adding({supervisor:o,dialogs:T,logger:n,approvalEnv:{installDir:W}}),P=e=>{const r=(0,C.readServiceConfig)(t)
7428
+ o.handle("new-computer",e=>(0,m.newComputerCommand)(E,e)),o.handle("computer-gone",async e=>h?await h.sayGone(String(e.machineId??"")):{ok:!1,reason:"The words channel is not running."}),o.handle("pair-channel",async e=>h?await h.pairAgain(String(e.code??"")):{ok:!1,reason:"The words channel is not running."}),w=new N.ScheduleWaker({remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await k.getToken())?.token,parked:()=>o.status().filter(e=>"parked"===e.state&&e.projectId).map(e=>e.projectId),wake:e=>o.wake(e,"work"),logger:n}),w.start(),(0,P.resolveFrontendUrl)(s.remoteServerUrl).then(e=>{try{/^https?:\/\//.test(e)&&(_=new URL(e).origin)}catch{}})
7429
+ const x=(0,L.nativeDialogs)(),R=new F.Adding({supervisor:o,dialogs:x,logger:n,approvalEnv:{installDir:H}}),U=e=>{const r=(0,C.readServiceConfig)(t)
7435
7430
  if(!r)throw new Error("the service has no configuration");(0,C.writeServiceConfig)(t,{...r,wordsBackup:{...r.wordsBackup??{},...e}})}
7436
- d=await(0,D.startDoor)({supervisor:o,adding:M,wordsBackup:{view:()=>{const e=(0,y.backupSettingsOf)((0,C.readServiceConfig)(t)?.wordsBackup)
7437
- return{settings:e,state:g.state(),runs:g.ownRuns(),folderAvailable:g.folderAvailable(e.folder)}},set:e=>P(e),pick:async()=>{const e=await T.pickFolder("Choose a folder for backups of your words (one your backup tool or iCloud Drive covers)")
7438
- return e&&P({folder:e}),e},run:()=>g.run(),restore:e=>g.restore(e)},privateData:{get:()=>h?.privateData??!0===(0,C.readServiceConfig)(t)?.privateData,set:$},newComputer:{api:E,pickBackupFolder:()=>T.pickFolder('Choose the "AlchemyLab Backups" folder of your old computer (restored or copied onto this one)')},verifyMachineTicket:async e=>{const t=(await k.getToken())?.token
7431
+ d=await(0,O.startDoor)({supervisor:o,adding:R,wordsBackup:{view:()=>{const e=(0,y.backupSettingsOf)((0,C.readServiceConfig)(t)?.wordsBackup)
7432
+ return{settings:e,state:g.state(),runs:g.ownRuns(),folderAvailable:g.folderAvailable(e.folder)}},set:e=>U(e),pick:async()=>{const e=await x.pickFolder("Choose a folder for backups of your words (one your backup tool or iCloud Drive covers)")
7433
+ return e&&U({folder:e}),e},run:()=>g.run(),restore:e=>g.restore(e)},privateData:{get:()=>h?.privateData??!0===(0,C.readServiceConfig)(t)?.privateData,set:$},newComputer:{api:E,pickBackupFolder:()=>x.pickFolder('Choose the "AlchemyLab Backups" folder of your old computer (restored or copied onto this one)')},verifyMachineTicket:async e=>{const t=(await k.getToken())?.token
7439
7434
  if(!t)throw new Error("this computer is not signed in")
7440
- const r=await fetch(`${s.remoteServerUrl}/api/verify-machine-ticket`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,"X-Client-Type":"alchemy-local-service",...(0,H.machineCallerHeaders)(s.remoteServerUrl)},body:JSON.stringify({ticket:e}),signal:AbortSignal.timeout(1e4)})
7435
+ const r=await fetch(`${s.remoteServerUrl}/api/verify-machine-ticket`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,"X-Client-Type":"alchemy-local-service",...(0,B.machineCallerHeaders)(s.remoteServerUrl)},body:JSON.stringify({ticket:e}),signal:AbortSignal.timeout(1e4)})
7441
7436
  if(r.status>=400&&r.status<500)return{ok:!1}
7442
7437
  if(!r.ok)throw new Error(`HTTP ${r.status}`)
7443
7438
  const n=await r.json(),o=n?.data??n
7444
7439
  return{ok:!0===o?.ok,expiresAt:o?.expiresAt??null}},logger:n,remoteServerUrl:s.remoteServerUrl,version:(0,j.ownVersion)()??void 0,machineId:u.id,frontendOrigin:()=>_,verifyTicket:async(e,t)=>{const r=(await k.getToken())?.token
7445
7440
  if(!r)throw new Error("this computer is not signed in")
7446
- const n=await fetch(`${s.remoteServerUrl}/api/projects/${encodeURIComponent(e)}/verify-local-ticket`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,"X-Client-Type":"alchemy-local-service",...(0,H.machineCallerHeaders)(s.remoteServerUrl)},body:JSON.stringify({ticket:t}),signal:AbortSignal.timeout(1e4)})
7441
+ const n=await fetch(`${s.remoteServerUrl}/api/projects/${encodeURIComponent(e)}/verify-local-ticket`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,"X-Client-Type":"alchemy-local-service",...(0,B.machineCallerHeaders)(s.remoteServerUrl)},body:JSON.stringify({ticket:t}),signal:AbortSignal.timeout(1e4)})
7447
7442
  if(n.status>=400&&n.status<500)return{ok:!1}
7448
7443
  if(!n.ok)throw new Error(`HTTP ${n.status}`)
7449
7444
  const o=await n.json(),i=o?.data??o
7450
- return{ok:!0===i?.ok,expiresAt:i?.expiresAt??null}}},s.doorPort??(0,D.defaultDoorPort)(s.remoteServerUrl)),await new Promise(()=>{})}(e)
7445
+ return{ok:!0===i?.ok,expiresAt:i?.expiresAt??null}}},s.doorPort??(0,O.defaultDoorPort)(s.remoteServerUrl)),await new Promise(()=>{})}(e)
7451
7446
  const s=(0,A.servicePaths)(e)
7452
- if("install"===t[0]||"uninstall"===t[0]){if("uninstall"===t[0]){const t=await(0,B.uninstallLoginItem)(e)
7453
- return void console.log(t.removed?`AlchemyLab no longer starts when you log in (${t.file} removed).`:"AlchemyLab was not set to start at login.")}const r=(0,C.readServiceConfig)(s)
7454
- return r||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),void await o(s,r)}if("stop"===t[0]){if(!await(0,M.isServiceRunning)(s))return void console.log("The AlchemyLab service is not running.")
7455
- await(0,M.request)(s,"shutdown")
7456
- for(let e=0;e<240&&await(0,M.isServiceRunning)(s);e++)await new Promise(e=>setTimeout(e,250))
7447
+ if("install"===t[0]||"uninstall"===t[0]){if("uninstall"===t[0]){const t=await(0,U.uninstallLoginItem)(e)
7448
+ return void console.log(t.removed?`AlchemyLab no longer starts when this computer starts (${t.file} removed).`:"AlchemyLab was not set to start at login.")}const r=(0,C.readServiceConfig)(s)
7449
+ return r||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),void await o(s,r)}if("stop"===t[0]){if(!await(0,x.isServiceRunning)(s))return void console.log("The AlchemyLab service is not running.")
7450
+ await(0,x.request)(s,"shutdown")
7451
+ for(let e=0;e<240&&await(0,x.isServiceRunning)(s);e++)await new Promise(e=>setTimeout(e,250))
7457
7452
  return void console.log("The AlchemyLab service stopped, and with it every project it ran. They come back when it starts again.")}if("computer-gone"===t[0]){const e=t.slice(1).join(" ").trim()
7458
- e||(console.error("Usage: alchemy service computer-gone <name or id> (the computer that is gone, by its name in Settings, General, Computers; Copy id there gives its id)"),process.exit(1)),await(0,M.isServiceRunning)(s)||(console.error("Start the AlchemyLab service first: `alchemy service`."),process.exit(1))
7459
- const r=await(0,M.request)(s,"computer-gone",{machineId:e})
7460
- return r.ok||(console.error(`Not recorded: ${r.reason??"the hosted service refused it."}`),process.exit(1)),void console.log("Recorded: that computer is gone. Its projects can now be moved to this computer without a backup: `alchemy service new-computer`.")}if("pair"===t[0]){t[1]||(console.error("Usage: alchemy service pair <code> (Settings, Data in the app gives the code)"),process.exit(1)),await(0,M.isServiceRunning)(s)||(console.error("Start the AlchemyLab service first: `alchemy service`."),process.exit(1))
7461
- const e=await(0,M.request)(s,"pair-channel",{code:t[1]})
7453
+ e||(console.error("Usage: alchemy service computer-gone <name or id> (the computer that is gone, by its name in Settings, General, Computers; Copy id there gives its id)"),process.exit(1)),await(0,x.isServiceRunning)(s)||(console.error("Start the AlchemyLab service first: `alchemy service`."),process.exit(1))
7454
+ const r=await(0,x.request)(s,"computer-gone",{machineId:e})
7455
+ return r.ok||(console.error(`Not recorded: ${r.reason??"the hosted service refused it."}`),process.exit(1)),void console.log("Recorded: that computer is gone. Its projects can now be moved to this computer without a backup: `alchemy service new-computer`.")}if("pair"===t[0]){t[1]||(console.error("Usage: alchemy service pair <code> (Settings, Data in the app gives the code)"),process.exit(1)),await(0,x.isServiceRunning)(s)||(console.error("Start the AlchemyLab service first: `alchemy service`."),process.exit(1))
7456
+ const e=await(0,x.request)(s,"pair-channel",{code:t[1]})
7462
7457
  return e.ok||(console.error(`Not paired: ${e.reason??"the hosted service refused it."}`),process.exit(1)),void console.log("This computer is paired again: the hosted service accepts its new channel key.")}if("new-computer"===t[0]){const e=(0,C.readServiceConfig)(s)
7463
7458
  return e||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),await i(s,e),void await async function(e){process.stdin.isTTY||(console.error("`alchemy service new-computer` asks questions: run it in a terminal."),process.exit(1))
7464
7459
  const t=d(e),r=u()
@@ -7467,54 +7462,51 @@ if("moved"===s||"no-candidates"===s){const s=v.default.realpathSync(process.cwd(
7467
7462
  if(i){const t=(0,m.attachFolder)(s,i,(0,p.machineIdentity)(e).id)
7468
7463
  r.say(t.ok?`This folder is ${i.name||"that project"} now. Run \`alchemy\` here to start it.`:`Not done: ${t.reason}`)}}}catch(e){r.say(`Stopped: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1}finally{r.close()}}(s)}if("move-words-here"===t[0]){const e=(0,C.readServiceConfig)(s)
7469
7464
  e||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),await i(s,e)
7470
- const t=await(0,M.request)(s,"move-words-here",{},9e4)
7465
+ const t=await(0,x.request)(s,"move-words-here",{},9e4)
7471
7466
  return t.ok&&!0===t.privateData||(console.error(`Not started: ${t.reason??"the hosted service refused it."}`),process.exit(1)),void console.log("This computer keeps the words of its projects from now on. The words the hosted service holds for them are moving here; Settings, Data shows the count still held, per project.")}(0,A.ensurePrivateDir)(s.dir)
7472
7467
  let a=(0,C.readServiceConfig)(s)
7473
7468
  const l=!a
7474
- a?process.env.ALCHEMY_OPENCODE_DIR&&process.env.ALCHEMY_OPENCODE_DIR!==a.opencodeDir&&(a={...a,opencodeDir:process.env.ALCHEMY_OPENCODE_DIR},(0,C.writeServiceConfig)(s,a),console.log(`Projects will run with the OpenCode build in ${a.opencodeDir} from their next start.`)):(a={remoteServerUrl:e,opencodeDir:process.env.ALCHEMY_OPENCODE_DIR||void 0,privateData:!0,createdAt:(new Date).toISOString()},(0,C.writeServiceConfig)(s,a),console.log(`AlchemyLab service set up for ${e}.`),console.log('Your projects\' words (requests, messages, documents, results) are kept on this computer, and backed up daily to the folder "AlchemyLab Backups" in your home folder. Keep that folder in your own backup: a lost disk without it loses the words.')),l&&"ready"!==(await(0,E.runDockerPreflight)({interactive:!0===process.stdin.isTTY&&!0===process.stdout.isTTY})).status&&console.log("Running without Docker: agents fall back to the host sandbox. AlchemyLab picks Docker up when it is there."),!l||!process.stdin.isTTY||"darwin"!==process.platform&&"linux"!==process.platform||(0,B.loginItemInstalled)(e)||(await n("Start AlchemyLab when you log in?",!0)?await o(s,a):console.log("Not set. `alchemy service install` sets it later."))
7475
- const h=await(0,M.isServiceRunning)(s),k=new _.ProjectsStore(s.projects)
7476
- k.exists()||h||await async function(e){const t=(0,T.migrationCandidates)(b.default.join((0,A.alchemyLabHome)(),"running-projects.json"),{installDir:W})
7477
- if(0!==t.length)if(process.stdin.isTTY){console.log(""),console.log("These projects already have AlchemyLab on this computer:")
7478
- for(const e of t)console.log(` ${e.name.padEnd(24)} ${e.path}`)
7479
- if(await n(`Keep these ${t.length} projects in AlchemyLab?`,!0)){for(const r of t)e.add({path:r.path,dev:r.dev,ino:r.ino,projectId:r.projectId,name:r.name,wanted:"stopped",addedAt:(new Date).toISOString(),addedVia:"migration"})
7480
- console.log("Added. Each starts when you open it in the app, or with `alchemy start <name>`.")}else e.touch(),console.log("None added. Run `alchemy` in a folder to add it.")}else console.log(`${t.length} existing projects can be added; run \`alchemy service\` in a terminal to choose.`)
7481
- else e.touch()}(k),h||await(0,M.startServiceInBackground)(s,a,r())||(console.error(`\u274c The AlchemyLab service did not start. Its log: ${s.log}`),process.exit(1)),await c(s)},exports.printStatus=c,exports.runControl=async function(e,t,r){const o=function(e){const t=s(e)
7469
+ a?process.env.ALCHEMY_OPENCODE_DIR&&process.env.ALCHEMY_OPENCODE_DIR!==a.opencodeDir&&(a={...a,opencodeDir:process.env.ALCHEMY_OPENCODE_DIR},(0,C.writeServiceConfig)(s,a),console.log(`Projects will run with the OpenCode build in ${a.opencodeDir} from their next start.`)):(a={remoteServerUrl:e,opencodeDir:process.env.ALCHEMY_OPENCODE_DIR||void 0,privateData:!0,createdAt:(new Date).toISOString()},(0,C.writeServiceConfig)(s,a),console.log(`AlchemyLab service set up for ${e}.`),console.log('Your projects\' words (requests, messages, documents, results) are kept on this computer, and backed up daily to the folder "AlchemyLab Backups" in your home folder. Keep that folder in your own backup: a lost disk without it loses the words.')),l&&"ready"!==(await(0,E.runDockerPreflight)({interactive:!0===process.stdin.isTTY&&!0===process.stdout.isTTY})).status&&console.log("Running without Docker: agents fall back to the host sandbox. AlchemyLab picks Docker up when it is there."),!l||!process.stdin.isTTY||"darwin"!==process.platform&&"linux"!==process.platform||(0,U.loginItemInstalled)(e)||(await n("Start AlchemyLab every time this computer starts?",!0)?await o(s,a):console.log("Not set. `alchemy service install` sets it later."))
7470
+ const h=await(0,x.isServiceRunning)(s),k=new _.ProjectsStore(s.projects)
7471
+ k.exists()||h||k.touch(),h||await(0,x.startServiceInBackground)(s,a,r())||(console.error(`\u274c The AlchemyLab service did not start. Its log: ${s.log}`),process.exit(1)),await c(s)},exports.printStatus=c,exports.runControl=async function(e,t,r){const o=function(e){const t=s(e)
7482
7472
  return t||(console.error("The AlchemyLab service is not set up yet. Run `alchemy service` once to set it up."),process.exit(1)),t.paths}(e),a=(0,C.readServiceConfig)(o)
7483
7473
  if("status"===t)return c(o)
7484
- if("approve"===t){const e=(await(0,M.request)(o,"approvals")).approvals??[]
7474
+ if("approve"===t){const e=(await(0,x.request)(o,"approvals")).approvals??[]
7485
7475
  if(0===e.length)return void console.log("Nothing is waiting for your approval.")
7486
7476
  for(const t of e){console.log(""),console.log(t.message)
7487
7477
  const e=await n("Approve?",!1)
7488
- await(0,M.request)(o,"approve",{approvalId:t.id,yes:e}),console.log(e?"Approved.":"Declined.")}return}await i(o,a)
7478
+ await(0,x.request)(o,"approve",{approvalId:t.id,yes:e}),console.log(e?"Approved.":"Declined.")}return}await i(o,a)
7489
7479
  const d=function(e){if(!e)return v.default.realpathSync(process.cwd())
7490
7480
  if(e.startsWith("/")||e.startsWith(".")||e.startsWith("~")){const t=e.startsWith("~")?b.default.join(process.env.HOME??"",e.slice(1)):b.default.resolve(e)
7491
7481
  try{return v.default.realpathSync(t)}catch{return t}}return e}(r)
7492
- if("add"===t){const e=await(0,M.request)(o,"add",{path:d})
7482
+ if("add"===t){const e=await(0,x.request)(o,"add",{path:d})
7493
7483
  return e.ok||(console.error(`\u274c ${e.reason}`),process.exit(1)),void console.log(e.added?`Added ${e.project.path}.`:`${e.project.path} is already on this computer.`)}if("start"===t||"restart"===t){console.log(`${"start"===t?"Starting":"Restarting"} ${b.default.basename(d)}...`)
7494
- const e=await(0,M.request)(o,t,{ref:d},18e4)
7484
+ const e=await(0,x.request)(o,t,{ref:d},18e4)
7495
7485
  for(const t of e.lines??[])e.ok&&console.log(t)
7496
- return e.ok||(l(e),process.exit(e.pending?0:1)),void console.log(`Running, action server on port ${e.port}.`)}if("stop"===t||"remove"===t){const e=await(0,M.request)(o,t,{ref:d},12e4)
7486
+ return e.ok||(l(e),process.exit(e.pending?0:1)),void console.log(`Running, action server on port ${e.port}.`)}if("stop"===t||"remove"===t){const e=await(0,x.request)(o,t,{ref:d},12e4)
7497
7487
  if(e.ok||(console.error(`\u274c ${e.reason}`),process.exit(1)),console.log("stop"===t?"Stopped.":"Removed from this computer. The folder and its history are untouched."),"remove"===t&&Array.isArray(e.keptWork)&&e.keptWork.length){console.log(`Kept, because ${1===e.keptWork.length?"it holds":"they hold"} work that was never committed:`)
7498
7488
  for(const t of e.keptWork)console.log(` ${t}`)}return}console.error(`Unknown command ${t}`),process.exit(1)},exports.runSupervisedDefault=async function(e,r){const{paths:s,cfg:n}=r,o=v.default.realpathSync(process.cwd()),a=(0,S.createLogger)({serviceName:"alchemy-cli",console:!1,logToFile:!0,logFilePath:s.log,baseDir:s.dir}),c=new I.TokenManager(a,o,{remoteServerUrl:e,allowInteractiveAuth:!1})
7499
7489
  await c.initialize()
7500
- const h=await(0,R.ensureSignedIn)(c)
7501
- await i(s,n),await(0,M.request)(s,"connect-now",{},3e4).catch(()=>{}),await async function(e){const t=b.default.join(e.dir,"new-computer-asked.json")
7490
+ const h=await(0,M.ensureSignedIn)(c)
7491
+ await i(s,n)
7492
+ const p=await(0,x.request)(s,"connect-now",{},3e4).catch(()=>{})
7493
+ p?.ok||await(0,x.request)(s,"connect-now",{},3e4).catch(()=>{}),await async function(e){const t=b.default.join(e.dir,"new-computer-asked.json")
7502
7494
  if(!process.stdin.isTTY||!process.stdout.isTTY||v.default.existsSync(t))return
7503
- if(((await(0,M.request)(e,"status")).projects??[]).length>0)return
7495
+ if(((await(0,x.request)(e,"status")).projects??[]).length>0)return
7504
7496
  const r=d(e),{candidates:s}=await r.candidates()
7505
7497
  if(0===s.length)return
7506
7498
  const n=u()
7507
7499
  try{await(0,g.runNewComputerFlow)(r,n,{explicit:!1}),v.default.writeFileSync(t,JSON.stringify({askedAt:(new Date).toISOString()})+"\n",{mode:384}),n.say("")}finally{n.close()}}(s).catch(()=>{})
7508
- const p=await(0,M.request)(s,"add",{path:o})
7509
- p.ok||(console.error(`\u274c ${p.reason}`),process.exit(1))
7510
- const f=await(0,M.request)(s,"start",{ref:o},18e4)
7511
- for(const e of f.lines??[])console.log(e)
7512
- f.ok||(l(f),process.exit(1)),(0,P.printBanner)(),console.log(`\u{1f4c1} Project: ${o}`),console.log(""),console.log(" Action server .... port "+f.port)
7513
- const m=await(0,O.resolveFrontendUrl)(e)
7514
- if(console.log(""),console.log(` Alchemy UI: \x1b]8;;${m}\x07${m}\x1b]8;;\x07`),console.log(""),f.firstRun){const e=`${m.replace(/\/$/,"")}${f.projectId?`/projects/${f.projectId}`:""}/timeline?welcome=1`
7515
- 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,$.openBrowser)(e,"Open the Alchemy UI link above in a browser on any device to continue there.")}const{InteractiveShell:w}=await t.e(994).then(()=>t.t(75994,23)),y=new w({projectPath:o,logger:a,remoteServerUrl:e,authToken:h,projectId:f.projectId,hasCode:!0===f.hasCode})
7516
- await y.start(async()=>{console.log(""),console.log(`AlchemyLab keeps running ${b.default.basename(o)}. \`alchemy stop\` in this folder stops it.`),process.exit(0)})}
7517
- const p=t(60134),f=t(89122),m=t(98074),g=t(62671),w=t(14139),y=t(79564),v=h(t(79896)),b=h(t(16928)),k=h(t(23785)),S=t(12782),I=t(11253),$=t(90629),j=t(42403),E=t(88918),A=t(18944),C=t(30030),_=t(52800),T=t(48893),x=t(81480),M=t(56257),R=t(19071),P=t(64504),O=t(38778),D=t(35382),q=t(60134),N=t(71918),F=t(36968),L=t(76231),U=t(67518),B=t(99269),H=t(11773),W=(()=>{let e=__dirname
7500
+ const f=await(0,x.request)(s,"add",{path:o})
7501
+ f.ok||(console.error(`\u274c ${f.reason}`),process.exit(1))
7502
+ const m=await(0,x.request)(s,"start",{ref:o},18e4)
7503
+ for(const e of m.lines??[])console.log(e)
7504
+ m.ok||(l(m),process.exit(1)),(0,R.printBanner)(),console.log(`\u{1f4c1} Project: ${o}`),console.log(""),console.log(" Action server .... port "+m.port)
7505
+ const w=await(0,P.resolveFrontendUrl)(e)
7506
+ if(console.log(""),console.log(` Alchemy UI: \x1b]8;;${w}\x07${w}\x1b]8;;\x07`),console.log(""),m.firstRun){const e=`${w.replace(/\/$/,"")}${m.projectId?`/projects/${m.projectId}`:""}/timeline?welcome=1`
7507
+ 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,$.openBrowser)(e,"Open the Alchemy UI link above in a browser on any device to continue there.")}const{InteractiveShell:y}=await t.e(994).then(()=>t.t(75994,23)),k=new y({projectPath:o,logger:a,remoteServerUrl:e,authToken:h,projectId:m.projectId,hasCode:!0===m.hasCode})
7508
+ await k.start(async()=>{console.log(""),console.log(`AlchemyLab keeps running ${b.default.basename(o)}. \`alchemy stop\` in this folder stops it.`),process.exit(0)})}
7509
+ const p=t(60134),f=t(89122),m=t(98074),g=t(62671),w=t(14139),y=t(79564),v=h(t(79896)),b=h(t(16928)),k=h(t(23785)),S=t(12782),I=t(11253),$=t(90629),j=t(42403),E=t(88918),A=t(18944),C=t(30030),_=t(52800),T=t(81480),x=t(56257),M=t(19071),R=t(64504),P=t(38778),O=t(35382),D=t(60134),q=t(71918),N=t(36968),F=t(76231),L=t(67518),U=t(99269),B=t(11773),H=(()=>{let e=__dirname
7518
7510
  for(let t=0;t<6;t++){try{if("alchemylab"===JSON.parse(v.default.readFileSync(b.default.join(e,"package.json"),"utf8"))?.name)return e}catch{}const t=b.default.dirname(e)
7519
7511
  if(t===e)break
7520
7512
  e=t}})()},30030(e,exports,t){"use strict"
@@ -7523,13 +7515,13 @@ if(t&&"string"==typeof t.remoteServerUrl&&/^https?:\/\//.test(t.remoteServerUrl)
7523
7515
  return e.opencodeDir?r.ALCHEMY_OPENCODE_DIR=e.opencodeDir:delete r.ALCHEMY_OPENCODE_DIR,r}
7524
7516
  const r=t(18944)},19071(e,exports,t){"use strict"
7525
7517
  async function r(e,t=3e5){return new Promise(r=>{const s=setInterval(async()=>{await e.hasValidToken()&&(clearInterval(s),r(!0))},2e3)
7526
- setTimeout(()=>{clearInterval(s),r(!1)},t)})}exports.waitForAuth=r,exports.ensureSignedIn=async function(e){let t=await e.getToken()
7527
- if(t?.token)return t.token
7528
- console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
7529
- const n=await e.startAuthenticationFlow()
7530
- console.log(`\u{1f310} ${n.authUrl}`)
7531
- const o=(0,s.openBrowser)(n.authUrl)
7532
- return console.log(""),console.log(o?"\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 r(e)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),t=await e.getToken(),console.log(""),t?.token},exports.startSignInWatchdog=function(e,t,n=3e5){let o=!1
7518
+ setTimeout(()=>{clearInterval(s),r(!1)},t)})}exports.waitForAuth=r,exports.ensureSignedIn=async function(e){let t=await e.getToken(),n=!1
7519
+ if(t?.token&&await e.signInRefused()&&(n=!0,await e.removeToken(),t=null),t?.token)return t.token
7520
+ console.log(""),console.log(n?"\u{1f511} The sign-in on this computer is no longer accepted. Opening browser to sign in again...":"\u{1f511} Not logged in. Opening browser to authenticate...")
7521
+ const o=await e.startAuthenticationFlow()
7522
+ console.log(`\u{1f310} ${o.authUrl}`)
7523
+ const i=(0,s.openBrowser)(o.authUrl)
7524
+ return console.log(""),console.log(i?"\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 r(e)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),t=await e.getToken(),console.log(""),t?.token},exports.startSignInWatchdog=function(e,t,n=3e5){let o=!1
7533
7525
  const i=setInterval(async()=>{if(!o)try{if(await e.hasValidToken())return
7534
7526
  o=!0,t("Sign-in expired; opening a browser to sign in again")
7535
7527
  const n=await e.startAuthenticationFlow();(0,s.openBrowser)(n.authUrl,`Sign in again: ${n.authUrl}`),t(await r(e)?"Signed in again":"Sign-in not completed; asking again in five minutes")}catch(e){t(`Sign-in check failed: ${e instanceof Error?e.message:String(e)}`)}finally{o=!1}},n)
@@ -7908,8 +7900,8 @@ for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
7908
7900
  t[v.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${k}_${n}`]=e}if(!K)for(const e of v.WEB_TOOLS)t[v.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${k}_${e}`]=!1
7909
7901
  Object.assign(t,(0,c.agentToolGrants)(B,e.name,H)),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name||"reviewer"===e.name||"criteria-reviewer"===e.name)&&void 0===t.task&&(t.task=!1),W.agent[e.name]={tools:t,...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}W.agent.general={...W.agent.general,disable:!0},W.agent.explore={...W.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[`${k}_search`]:!0,[`${k}_read_files`]:!0,[`${k}_view_asset`]:!0,[`${k}_query_history`]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
7910
7902
  const e=this.buildExploreMarkdown(F)
7911
- this.setAgentMarkdown("explore",e),this.logger.info("Explore agent override held in memory"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(W.agent).length})}const V=e=>"string"==typeof e&&e.includes(".opencode/plugin/"),z=(await this.fetchPluginManifest())["opencode-claude-auth"],Y=W.plugin,J=Array.isArray(Y)?Y.filter(e=>!V(e)):[]
7912
- z&&this.applyAuthPluginPin(J,z),J.length>0?W.plugin=J:delete W.plugin
7903
+ this.setAgentMarkdown("explore",e),this.logger.info("Explore agent override held in memory"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(W.agent).length})}const z=e=>"string"==typeof e&&e.includes(".opencode/plugin/"),V=(await this.fetchPluginManifest())["opencode-claude-auth"],Y=W.plugin,J=Array.isArray(Y)?Y.filter(e=>!z(e)):[]
7904
+ V&&this.applyAuthPluginPin(J,V),J.length>0?W.plugin=J:delete W.plugin
7913
7905
  try{const e=["opencode.jsonc",".opencode/opencode.json",".opencode/opencode.jsonc",".opencode/plugin",".opencode/tool",".opencode/command"].filter(e=>(0,d.existsSync)(y.join(s,e)))
7914
7906
  e.length>0&&this.logger.warn("This repository carries OpenCode config the AlchemyLab runtime does not load (only the generated config is read)",{ignored:e})}catch{}W.instructions=function(e,t){const r=["AGENTS.md","CLAUDE.md","CONTEXT.md"].map(t=>(0,u.join)(e,t)).find(e=>(0,d.existsSync)(e))??(0,u.join)(e,"AGENTS.md"),s=(Array.isArray(t)?t:[]).filter(e=>"string"==typeof e&&e.trim().length>0).map(t=>t.startsWith("~/")||(0,u.isAbsolute)(t)?t:(0,u.join)(e,t))
7915
7907
  return[...new Set([r,...s])]}(s,C.instructions)
@@ -7919,8 +7911,8 @@ const Q={...C,...W}
7919
7911
  await w.mkdir(y.dirname(I),{recursive:!0}),await w.writeFile(I,JSON.stringify(Q,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:I})
7920
7912
  try{const e=await Promise.resolve().then(()=>t.t(70857,23)),r=y.join(e.homedir(),".alchemy_lab","opencode","config"),s=y.join(r,"opencode.json")
7921
7913
  let n=null
7922
- try{n=JSON.parse(await w.readFile(s,"utf-8"))}catch{}const o=n??{},i=Array.isArray(o.plugin)?o.plugin.slice():[],a=i.filter(e=>!V(e))
7923
- z&&this.applyAuthPluginPin(a,z),i.length===a.length&&!i.some((e,t)=>e!==a[t])&&n||(o.plugin=a,await w.mkdir(r,{recursive:!0}),await w.writeFile(s,JSON.stringify(o,null,2),"utf8"),this.logger.info("Updated global opencode config plugin list",{globalConfigPath:s,plugins:a}))}catch(e){this.logger.warn("Failed to update global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(T&&!l)try{let e={}
7914
+ try{n=JSON.parse(await w.readFile(s,"utf-8"))}catch{}const o=n??{},i=Array.isArray(o.plugin)?o.plugin.slice():[],a=i.filter(e=>!z(e))
7915
+ V&&this.applyAuthPluginPin(a,V),i.length===a.length&&!i.some((e,t)=>e!==a[t])&&n||(o.plugin=a,await w.mkdir(r,{recursive:!0}),await w.writeFile(s,JSON.stringify(o,null,2),"utf8"),this.logger.info("Updated global opencode config plugin list",{globalConfigPath:s,plugins:a}))}catch(e){this.logger.warn("Failed to update global alchemy opencode config",{error:e instanceof Error?e.message:"Unknown error"})}if(T&&!l)try{let e={}
7924
7916
  try{const t=await w.readFile(x,"utf-8")
7925
7917
  e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=M,await w.writeFile(x,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:x}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPluginsGlobal:r,cleanupLegacyProjectPlugins:n}=await t.e(939).then(()=>t.t(49939,23)),o=await r(b??!1,this.logger,e)
7926
7918
  o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors}),await n(s,this.logger)}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:I}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,n.getErrorMessage)(e)}),{success:!1,error:(0,n.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=v,v.WEB_TOOLS=["webfetch","websearch","codesearch","capture_site","read_site","fetch_asset"],v.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","webfetch","websearch","codesearch","agent","task","question","todoread","todowrite"]),v.rerouteLogged=new Set},6688(e,exports,t){"use strict"
@@ -10008,7 +10000,8 @@ if(t.status===a.PollingStatus.POLLING)return null
10008
10000
  const r=this.authFlow.getPollingUrl(e),s=await fetch(r)
10009
10001
  if(s.ok){const e=await s.json()
10010
10002
  if(e.authenticated&&e.token){const t=this.authFlow.parseTokenResponse(e)
10011
- if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
10003
+ if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async signInRefused(){try{const e=await this.getToken()
10004
+ return!!e&&401===(await fetch(`${this.remoteServerUrl}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${e.token}`},signal:AbortSignal.timeout(1e4)})).status}catch{return!1}}forgetCachedSignIn(){this.invalidateCache()}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
10012
10005
  try{const e=await this.getToken()
10013
10006
  if(!e)return this.logger.debug("No token available for validation"),!1
10014
10007
  const t=`${this.remoteServerUrl}/auth/validate`
@@ -10252,7 +10245,7 @@ o&&o(e)
10252
10245
  for(var a=0;a<n.length;a++)t[n[a]]=1})(s)}}})()
10253
10246
  let s={};(()=>{"use strict"
10254
10247
  function t(){return(0,i.ownVersion)()??"unknown"}function r(){const e=t()
10255
- console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start this folder's project and open its shell. The\n AlchemyLab service runs the project (the first run sets\n the service up), and leaving the shell leaves it running.\n service Set up the AlchemyLab service for this computer and start it\n ("alchemy service stop" stops it and its projects;\n "alchemy service install" starts it when you log in;\n "alchemy service uninstall" stops starting it at login;\n "alchemy service pair <code>" pairs this computer again,\n with the code Settings, Data in the app gives;\n "alchemy service computer-gone <name>" says another of your\n computers is gone, by its name in Settings, General, Computers\n (or its id), so its projects can move without a backup;\n "alchemy service new-computer" is for a computer that\n replaces another one: it brings that computer's words back\n from a backup, then moves its projects here)\n status Projects on this computer and their state\n start [folder] Start a project (default: this folder)\n stop [folder] Stop a project\n restart [folder]\n add [folder] Add a folder as a project without starting it\n remove [folder] Remove a project from this computer (files are untouched)\n approve Answer requests the app made that need you at this computer\n logout Log out and remove stored auth token\n help Show this help message\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy logout # Log out\n\nNOTES:\n On startup, alchemy checks for Docker and offers to install or start it.\n Docker powers confined execution (sealed environments, experiments, test\n gates); without it, agents fall back to the host sandbox.\n\nFor more information, visit: https://alchemylabcode.com/\n`)}function n(e){try{(0,I.checkProjectIdentity)(e)}catch(e){if(e instanceof I.ProjectRefused){console.error("")
10248
+ console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start this folder's project and open its shell. The\n AlchemyLab service runs the project (the first run sets\n the service up), and leaving the shell leaves it running.\n service Set up the AlchemyLab service for this computer and start it\n ("alchemy service stop" stops it and its projects;\n "alchemy service install" starts it every time this computer starts;\n "alchemy service uninstall" stops that;\n "alchemy service pair <code>" pairs this computer again,\n with the code Settings, Data in the app gives;\n "alchemy service computer-gone <name>" says another of your\n computers is gone, by its name in Settings, General, Computers\n (or its id), so its projects can move without a backup;\n "alchemy service new-computer" is for a computer that\n replaces another one: it brings that computer's words back\n from a backup, then moves its projects here)\n status Projects on this computer and their state\n start [folder] Start a project (default: this folder)\n stop [folder] Stop a project\n restart [folder]\n add [folder] Add a folder as a project without starting it\n remove [folder] Remove a project from this computer (files are untouched)\n approve Answer requests the app made that need you at this computer\n logout Log out and remove stored auth token\n help Show this help message\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy logout # Log out\n\nNOTES:\n On startup, alchemy checks for Docker and offers to install or start it.\n Docker powers confined execution (sealed environments, experiments, test\n gates); without it, agents fall back to the host sandbox.\n\nFor more information, visit: https://alchemylabcode.com/\n`)}function n(e){try{(0,I.checkProjectIdentity)(e)}catch(e){if(e instanceof I.ProjectRefused){console.error("")
10256
10249
  for(const t of e.lines)console.error(t)
10257
10250
  console.error(""),process.exit(1)}throw e}}function o(){return(0,a.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:process.cwd()})}let exports=s
10258
10251
  Object.defineProperty(exports,"__esModule",{value:!0})
@@ -8337,7 +8337,8 @@ if(t.status===a.PollingStatus.POLLING)return null
8337
8337
  const r=this.authFlow.getPollingUrl(e),n=await fetch(r)
8338
8338
  if(n.ok){const e=await n.json()
8339
8339
  if(e.authenticated&&e.token){const t=this.authFlow.parseTokenResponse(e)
8340
- if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
8340
+ if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async signInRefused(){try{const e=await this.getToken()
8341
+ return!!e&&401===(await fetch(`${this.remoteServerUrl}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${e.token}`},signal:AbortSignal.timeout(1e4)})).status}catch{return!1}}forgetCachedSignIn(){this.invalidateCache()}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
8341
8342
  try{const e=await this.getToken()
8342
8343
  if(!e)return this.logger.debug("No token available for validation"),!1
8343
8344
  const t=`${this.remoteServerUrl}/auth/validate`
package/dist/index.js CHANGED
@@ -8433,7 +8433,8 @@ if(t.status===a.PollingStatus.POLLING)return null
8433
8433
  const r=this.authFlow.getPollingUrl(e),n=await fetch(r)
8434
8434
  if(n.ok){const e=await n.json()
8435
8435
  if(e.authenticated&&e.token){const t=this.authFlow.parseTokenResponse(e)
8436
- if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
8436
+ if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async signInRefused(){try{const e=await this.getToken()
8437
+ return!!e&&401===(await fetch(`${this.remoteServerUrl}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${e.token}`},signal:AbortSignal.timeout(1e4)})).status}catch{return!1}}forgetCachedSignIn(){this.invalidateCache()}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
8437
8438
  try{const e=await this.getToken()
8438
8439
  if(!e)return this.logger.debug("No token available for validation"),!1
8439
8440
  const t=`${this.remoteServerUrl}/auth/validate`
@@ -8204,7 +8204,8 @@ if(t.status===a.PollingStatus.POLLING)return null
8204
8204
  const r=this.authFlow.getPollingUrl(e),n=await fetch(r)
8205
8205
  if(n.ok){const e=await n.json()
8206
8206
  if(e.authenticated&&e.token){const t=this.authFlow.parseTokenResponse(e)
8207
- if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
8207
+ if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async signInRefused(){try{const e=await this.getToken()
8208
+ return!!e&&401===(await fetch(`${this.remoteServerUrl}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${e.token}`},signal:AbortSignal.timeout(1e4)})).status}catch{return!1}}forgetCachedSignIn(){this.invalidateCache()}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
8208
8209
  try{const e=await this.getToken()
8209
8210
  if(!e)return this.logger.debug("No token available for validation"),!1
8210
8211
  const t=`${this.remoteServerUrl}/auth/validate`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemylab",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "private": false,
5
5
  "description": "AlchemyLab: the agentic workbench. Work is a request, with criteria, gates and a receipt.",
6
6
  "author": "Testfire Software",