alchemylab 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1910,8 +1910,8 @@ const r=await e.json()
1910
1910
  if(!Array.isArray(r)||r.some(e=>e?.name===t))return!0}catch{return!0}if(Date.now()>=o)return!1
1911
1911
  i||(i=!0,e.logger.info("start-worker: waiting for the agent to load in the runtime",{agent:t})),await new Promise(e=>setTimeout(e,s))}}async function n(e,t,r){let s=""
1912
1912
  try{const r=await e.apiClient.get(`/api/projects/${e.projectId}/requests/${t}/record-for-agents`)
1913
- s=String((r?.data??r)?.text??"")}catch{}try{return function(e,t){const r=Array.isArray(e?.acceptanceCriteria)?e.acceptanceCriteria:[]
1914
- return[`## Description\n${String(e?.description??"").trim()||"_No description provided._"}`,`## Acceptance Criteria\n${(0,j.formatCriteriaWithHashes)(r)}`,...t&&t.trim()?[t.trim()]:[]].join("\n\n")}(r,s)}catch{return""}}function o(e,t,r,s){const n=[t?.roundOf?`The user wrote on the page of ${t.roundOf}, which is completed. Their message is ${e.requestId}; its record is at the end of this message. If you only answer it, what you write last in this turn is your reply to them, and nothing else is needed. If you change the project for it, the change is ${e.requestId}'s: make it and complete ${e.requestId} as you complete any request.`:`Implement ${e.requestId} ("${t?.title??""}") and own its full lifecycle. It was registered via ${e.source??"a registration door"}.`,s?"Its record, as get_request_information shows it now, is at the end of this message: start from it, and read the request again only when you need what changed since. If something it BUILDS ON does not exist in this project, call request_operations premise-block with the reason instead of starting. New content the request introduces is not a missing premise.":"Read it with get_request_information first. If something it BUILDS ON does not exist in this project, call request_operations premise-block with the reason instead of starting. New content the request introduces is not a missing premise.",...t?.roundOf?[]:[`If its acceptance criteria ALREADY hold as the code stands, do no work: complete ${e.requestId} yourself as a verification-only completion, naming exactly which criteria hold and where. Nobody else closes it: a report saying so left req000210 in progress with nobody working it.`,'Otherwise build what it asks (its own choices go in an opening brief when the pickup briefing asks for one), run the relevant validation, and complete the request with evidence. A value the request states is the value: a version number, a label, a default. Do not bump or "improve" it (two coders raised a footer version the user had just set, 2026-09-07).']]
1913
+ s=String((r?.data??r)?.text??"")}catch{}try{return function(e,t){const r=Array.isArray(e?.acceptanceCriteria)?e.acceptanceCriteria:[],s=e?.roundOf?`## ${e.roundOf}'s criteria (met when it completed)\nAn answer needs nothing more. If you change the project, your completion accounts for these again, on your change.\n${(0,j.formatCriteriaWithHashes)(r).replace(/\[UNPROVEN\] /g,"")}`:`## Acceptance Criteria\n${(0,j.formatCriteriaWithHashes)(r)}`
1914
+ return[`## Description\n${String(e?.description??"").trim()||"_No description provided._"}`,s,...t&&t.trim()?[t.trim()]:[]].join("\n\n")}(r,s)}catch{return""}}function o(e,t,r,s){const n=[t?.roundOf?`The user wrote on the page of ${t.roundOf}, which is completed. Their message is ${e.requestId}; its record is at the end of this message. If you only answer it, what you write last in this turn is your reply to them, and nothing else is needed. If you change the project for it, the change is ${e.requestId}'s: make it and complete ${e.requestId} as you complete any request.`:`Implement ${e.requestId} ("${t?.title??""}") and own its full lifecycle. It was registered via ${e.source??"a registration door"}.`,s?"Its record, as get_request_information shows it now, is at the end of this message: start from it, and read the request again only when you need what changed since. If something it BUILDS ON does not exist in this project, call request_operations premise-block with the reason instead of starting. New content the request introduces is not a missing premise.":"Read it with get_request_information first. If something it BUILDS ON does not exist in this project, call request_operations premise-block with the reason instead of starting. New content the request introduces is not a missing premise.",...t?.roundOf?[]:[`If its acceptance criteria ALREADY hold as the code stands, do no work: complete ${e.requestId} yourself as a verification-only completion, naming exactly which criteria hold and where. Nobody else closes it: a report saying so left req000210 in progress with nobody working it.`,'Otherwise build what it asks (its own choices go in an opening brief when the pickup briefing asks for one), run the relevant validation, and complete the request with evidence. A value the request states is the value: a version number, a label, a default. Do not bump or "improve" it (two coders raised a footer version the user had just set, 2026-09-07).']]
1915
1915
  return r.length&&n.push(`Possible duplicates already open with the same title: ${r.join(", ")}. Check before doing the work twice; if one already covers it, cancel ${e.requestId} as superseded.`),e.userNote&&n.push(`From the user: ${e.userNote}`),e.rules?.length&&n.push(function(e){const t=e.filter(e=>"told"===e.enforcement),r=e.filter(e=>"told"!==e.enforcement),s=[...t.map(e=>`- [guidance:${e.id}] ${e.text}`),...r.map(e=>`- [guidance:${e.id}] (reviewed, ${e.severity}) ${e.text}`)]
1916
1916
  return`The workflow's guidance for this work (the user's; where an item conflicts with the brief above, the guidance wins).${r.length?" A reviewer checks the reviewed ones by id at the review gate: 'block' ones must hold, 'advise' ones are expected.":""} When the user's own words make an exception to an item for this request, record it with guidance exception (its id as listed, their words, the scope they said); when the request contradicts an item and they have not said, guidance conflict, and follow the guidance until they answer.\n${s.join("\n")}`}(e.rules)),s&&n.push(`\n# ${e.requestId}: ${t?.title??""}\n${s}`),n.join("\n")}async function i(e,t){if(!t)return null
1917
1917
  try{const r=(0,_.simpleGit)(e),s=await r.diffSummary([`${t}..HEAD`])
@@ -1982,31 +1982,32 @@ const I=(b.get(o)??0)>0,$=c.areaReuse&&v>0,A=I||$?await p(o):{},_=Math.max(w.get
1982
1982
  C.push({...void 0!==A.contextTokens?{contextTokens:A.contextTokens}:{},...void 0!==M?{changedSinceLines:M}:{},...I||$?{contextCeiling:(0,E.contextCeilingFor)(T)}:{},providerID:A.providerID??null,externalSessionId:o,agentType:n.agentType,threadRequests:b.get(o)??0,sharedAreas:v,modelKey:(0,E.modelKeyOf)(t.kindModels,l),toolsKey:(0,E.toolsKeyOf)(l),lastActiveMs:_,busy:k,busyElsewhere:k&&(b.get(o)??0)>0?!!await u(e,o,t.requestId):void 0,archived:"archived"===n.status})}const _=(0,E.pickWarmCoder)(c,Date.now(),{worker:t.worker,modelKey:(0,E.modelKeyOf)(t.kindModels,t.kind),toolsKey:(0,E.toolsKeyOf)(t.kind)},C,d)
1983
1983
  if(!_.reuse){const e=C.filter(e=>(e.threadRequests??0)>0&&(e.archived||!(0,E.isCacheWarm)(Date.now(),e,d))&&(void 0!==e.contextTokens||void 0!==e.changedSinceLines))
1984
1984
  return e.length&&o.info("session routing: cold thread worker weighed and not resumed",{requestId:t.requestId,why:_.why,limits:{contextTokens:c.coldMaxContextTokens,changedLines:c.coldMaxChangedLines},weighed:e.map(e=>({sessionId:e.externalSessionId,contextTokens:e.contextTokens,changedSinceLines:e.changedSinceLines}))}),{why:_.why}}const T=C.find(e=>e.externalSessionId===_.sessionId)
1985
- return o.info(_.cold?"session routing: cold worker chosen (it built this thread and its code is still current)":_.area?"session routing: warm worker of the same area chosen (no thread link)":"session routing: warm worker chosen",{requestId:t.requestId,sessionId:_.sessionId,threadRequestsWorked:b.get(_.sessionId)??0,..._.area?{sharedAreas:T?.sharedAreas}:{},contextTokens:T?.contextTokens,contextCeiling:T?.contextCeiling,provider:T?.providerID??void 0,afterTurn:!!_.afterTurn,candidates:C.length,..._.cold?{changedSinceLines:T?.changedSinceLines,archived:!!T?.archived}:{}}),{sessionId:_.sessionId,previousRequestId:S.get(_.sessionId),..._.afterTurn?{afterTurn:!0}:{},..._.area?{area:!0}:{},..._.cold&&T?.archived?{reopenLocalId:j.get(_.sessionId)}:{}}}catch(e){return o.warn("session routing: warm-worker survey failed; spawning",{error:(0,$.getErrorMessage)(e)}),{why:"no-candidate"}}}exports.handleStartWorkerAction=async function(e,t){const s=e,{apiClient:i,projectId:a,logger:u}=t
1985
+ return o.info(_.cold?"session routing: cold worker chosen (it built this thread and its code is still current)":_.area?"session routing: warm worker of the same area chosen (no thread link)":"session routing: warm worker chosen",{requestId:t.requestId,sessionId:_.sessionId,threadRequestsWorked:b.get(_.sessionId)??0,..._.area?{sharedAreas:T?.sharedAreas}:{},contextTokens:T?.contextTokens,contextCeiling:T?.contextCeiling,provider:T?.providerID??void 0,afterTurn:!!_.afterTurn,candidates:C.length,..._.cold?{changedSinceLines:T?.changedSinceLines,archived:!!T?.archived}:{}}),{sessionId:_.sessionId,previousRequestId:S.get(_.sessionId),..._.afterTurn?{afterTurn:!0}:{},..._.area?{area:!0}:{},..._.cold&&T?.archived?{reopenLocalId:j.get(_.sessionId)}:{}}}catch(e){return o.warn("session routing: warm-worker survey failed; spawning",{error:(0,$.getErrorMessage)(e)}),{why:"no-candidate"}}}exports.handleStartWorkerAction=async function(e,t){const s=e,{apiClient:i,projectId:a,logger:h}=t
1986
1986
  if(!s.requestId)return{success:!1,message:"requestId is required",error:"missing_params"}
1987
- const h=t.startWorkerDeps??{},w=await i.get(`/api/projects/${a}/requests/${s.requestId}`),y=w?.data??w
1988
- if(!y||!["open"].includes(y.status))return{success:!0,message:`${s.requestId} is ${y?.status??"unknown"}: nothing to start`}
1989
- const v=M.get(s.requestId)
1990
- if(v)return{success:!0,message:`${s.requestId} starts on ${v} when its current turn ends`}
1991
- if(y.roundOf&&"string"==typeof s.unitWorker&&s.unitWorker&&null!==(0,f.kindProfile)(y.type).worker)try{const[e,r]=await Promise.all([(h.turnState??d)(t,s.unitWorker,s.requestId),(h.requestRecord??n)(t,s.requestId,y)])
1992
- if(!e.busy){const e="string"==typeof s.worker&&s.worker.trim()||(0,f.kindProfile)(y?.type).worker||"worker",n=(0,g.spawnToolMask)({kind:y?.type,hasTestProcess:"boolean"==typeof s.hasTestProcess?s.hasTestProcess:void 0}),i={p:s,req:y,worker:e,startPrompt:o(s,y,[],r),toolMask:n,deps:h}
1993
- if(await c(t,i,{sessionId:s.unitWorker,previousRequestId:y.roundOf}))return{success:!0,message:`${s.requestId} handed to ${y.roundOf}'s own ${e} ${s.unitWorker}`}}}catch(e){u.info("session routing: the unit did not go straight to its request's worker; the ordinary start takes it",{requestId:s.requestId,error:(0,$.getErrorMessage)(e)})}try{const e=async e=>{const t=await i.get(`/api/projects/${a}/requests/${e}`)
1987
+ const w=t.startWorkerDeps??{},y=await i.get(`/api/projects/${a}/requests/${s.requestId}`),v=y?.data??y
1988
+ if(!v||!["open"].includes(v.status))return{success:!0,message:`${s.requestId} is ${v?.status??"unknown"}: nothing to start`}
1989
+ const k=M.get(s.requestId)
1990
+ if(k)return{success:!0,message:`${s.requestId} starts on ${k} when its current turn ends`}
1991
+ if(v.roundOf&&"string"==typeof s.unitWorker&&s.unitWorker&&null!==(0,f.kindProfile)(v.type).worker)try{const[e,r]=await Promise.all([(w.turnState??d)(t,s.unitWorker,s.requestId),(w.requestRecord??n)(t,s.requestId,v)]),i=e.busy?e.liveElsewhere:await u(t,s.unitWorker,s.requestId).then(e=>e&&e!==v.roundOf?e:null)
1992
+ if(!e.busy&&!i){const e="string"==typeof s.worker&&s.worker.trim()||(0,f.kindProfile)(v?.type).worker||"worker",n=(0,g.spawnToolMask)({kind:v?.type,hasTestProcess:"boolean"==typeof s.hasTestProcess?s.hasTestProcess:void 0}),i={p:s,req:v,worker:e,startPrompt:o(s,v,[],r),toolMask:n,deps:w}
1993
+ if(await c(t,i,{sessionId:s.unitWorker,previousRequestId:v.roundOf}))return{success:!0,message:`${s.requestId} handed to ${v.roundOf}'s own ${e} ${s.unitWorker}`}}}catch(e){h.info("session routing: the unit did not go straight to its request's worker; the ordinary start takes it",{requestId:s.requestId,error:(0,$.getErrorMessage)(e)})}try{const e=async e=>{const t=await i.get(`/api/projects/${a}/requests/${e}`)
1994
1994
  return(t?.data??t)?.type}
1995
- t.projectPath&&await(0,x.defaultBranchHasCode)(t.projectPath,e)&&(await(0,x.reportProjectHasCode)(a,!0,(e,t)=>i.patch(e,t)),await i.post(`/api/projects/${a}/requests/${s.requestId}/test-setup-offer`,{...s.creatorSessionId?{creatorSessionId:s.creatorSessionId}:{}}))}catch(e){u.warn("test-check card could not be asked for (the start goes on)",{requestId:s.requestId,error:(0,$.getErrorMessage)(e)})}const k=await(h.openDuplicates??r)(t,y,s.requestId),S=await(h.requestRecord??n)(t,s.requestId,y),I=o(s,y,k,S),j="string"==typeof s.worker&&s.worker.trim()||(0,f.kindProfile)(y?.type).worker||"worker",A=(0,g.spawnToolMask)({kind:y?.type,hasTestProcess:"boolean"==typeof s.hasTestProcess?s.hasTestProcess:void 0});(0,g.hasMask)(A)&&u.info("session routing: narrowing the worker tool surface for this request",{requestId:s.requestId,worker:j,off:Object.keys(A)})
1996
- const C={p:s,req:y,worker:j,startPrompt:I,toolMask:A,deps:h}
1997
- let _=null
1998
- const T=y?.filedBySession??y?.filed_by_session,R=y?.filedByAgent??y?.filed_by_agent
1999
- if("string"==typeof T&&T&&(0,E.sameWorkerRole)(R,j))try{if((await(0,m.readAllPages)(i,e=>`/api/projects/${a}/cli-sessions?status=active&limit=100&offset=${e}`)).some(e=>e?.externalSessionId===T)){const e=await(h.turnState??d)(t,T,s.requestId)
2000
- e.liveElsewhere?u.info("session routing: the filer is mid-turn on another live request; not handing it this one",{requestId:s.requestId,sessionId:T,on:e.liveElsewhere}):(_={sessionId:T},u.info("session routing: the session that filed this request keeps it",{requestId:s.requestId,sessionId:T}))}}catch{}if(_=_??await(h.pickWarm??p)(t,{worker:j,requestId:s.requestId,kind:y?.type,kindModels:s.kindModels,planDocumentId:y?.planDocumentId??y?.plan_document_id??null,areas:Array.isArray(y?.areas)?y.areas.map(String):[],waitedOn:Array.isArray(y?.waitedOn)?y.waitedOn.map(String):[]}),_?.sessionId){const e=_.sessionId,r=await(h.turnState??d)(t,e,s.requestId)
2001
- if(r.liveElsewhere)u.info("session routing: the chosen worker is mid-turn on another live request; spawning instead",{requestId:s.requestId,sessionId:e,on:r.liveElsewhere})
2002
- else{if(r.busy)return M.set(s.requestId,e),(h.whenIdle??((e,r)=>(0,b.whenSessionIdle)(e,async()=>(await d(t,e,s.requestId)).busy,r)))(e,e=>{(async function(e,t,r,s){const{apiClient:n,projectId:o,logger:i}=e,{p:a}=t
1995
+ t.projectPath&&await(0,x.defaultBranchHasCode)(t.projectPath,e)&&(await(0,x.reportProjectHasCode)(a,!0,(e,t)=>i.patch(e,t)),await i.post(`/api/projects/${a}/requests/${s.requestId}/test-setup-offer`,{...s.creatorSessionId?{creatorSessionId:s.creatorSessionId}:{}}))}catch(e){h.warn("test-check card could not be asked for (the start goes on)",{requestId:s.requestId,error:(0,$.getErrorMessage)(e)})}const S=await(w.openDuplicates??r)(t,v,s.requestId),I=await(w.requestRecord??n)(t,s.requestId,v),j=o(s,v,S,I),A="string"==typeof s.worker&&s.worker.trim()||(0,f.kindProfile)(v?.type).worker||"worker",C=(0,g.spawnToolMask)({kind:v?.type,hasTestProcess:"boolean"==typeof s.hasTestProcess?s.hasTestProcess:void 0});(0,g.hasMask)(C)&&h.info("session routing: narrowing the worker tool surface for this request",{requestId:s.requestId,worker:A,off:Object.keys(C)})
1996
+ const _={p:s,req:v,worker:A,startPrompt:j,toolMask:C,deps:w}
1997
+ let T=null
1998
+ const R=v?.filedBySession??v?.filed_by_session,P=v?.filedByAgent??v?.filed_by_agent
1999
+ if("string"==typeof R&&R&&(0,E.sameWorkerRole)(P,A))try{if((await(0,m.readAllPages)(i,e=>`/api/projects/${a}/cli-sessions?status=active&limit=100&offset=${e}`)).some(e=>e?.externalSessionId===R)){const e=await(w.turnState??d)(t,R,s.requestId)
2000
+ e.liveElsewhere?h.info("session routing: the filer is mid-turn on another live request; not handing it this one",{requestId:s.requestId,sessionId:R,on:e.liveElsewhere}):(T={sessionId:R},h.info("session routing: the session that filed this request keeps it",{requestId:s.requestId,sessionId:R}))}}catch{}if(T=T??await(w.pickWarm??p)(t,{worker:A,requestId:s.requestId,kind:v?.type,kindModels:s.kindModels,planDocumentId:v?.planDocumentId??v?.plan_document_id??null,areas:Array.isArray(v?.areas)?v.areas.map(String):[],waitedOn:Array.isArray(v?.waitedOn)?v.waitedOn.map(String):[]}),T?.sessionId){const e=T.sessionId,r=await(w.turnState??d)(t,e,s.requestId)
2001
+ if(!r.busy&&!r.liveElsewhere&&v.roundOf){const n=await(w.liveElsewhere??u)(t,e,s.requestId)
2002
+ n&&n!==v.roundOf&&(r.liveElsewhere=n)}if(r.liveElsewhere)h.info("session routing: the chosen worker is mid-turn on another live request; spawning instead",{requestId:s.requestId,sessionId:e,on:r.liveElsewhere})
2003
+ else{if(r.busy)return M.set(s.requestId,e),(w.whenIdle??((e,r)=>(0,b.whenSessionIdle)(e,async()=>(await d(t,e,s.requestId)).busy,r)))(e,e=>{(async function(e,t,r,s){const{apiClient:n,projectId:o,logger:i}=e,{p:a}=t
2003
2004
  try{const u=await n.get(`/api/projects/${o}/requests/${a.requestId}`),h=u?.data??u
2004
2005
  if(!h||"open"!==h.status)return void i.info("session routing: the request moved on while its worker finished a turn; nothing to hand over",{requestId:a.requestId,status:h?.status??"unknown"})
2005
2006
  const p=await(t.deps.turnState??d)(e,r.sessionId,a.requestId)
2006
2007
  if("idle"===s&&!p.busy&&!p.liveElsewhere&&await c(e,{...t,req:h},r))return
2007
- i.info("session routing: the worker did not come free for this start; spawning",{requestId:a.requestId,sessionId:r.sessionId,why:s,busy:p.busy,on:p.liveElsewhere}),await l(e,{...t,req:h})}catch(e){i.warn("session routing: the start after a turn failed",{requestId:a.requestId,sessionId:r.sessionId,error:(0,$.getErrorMessage)(e)})}})(t,C,_,e).finally(()=>M.delete(s.requestId))}),u.info("session routing: the thread's worker is finishing a turn; the start is handed over when it ends",{requestId:s.requestId,sessionId:e,previous:_.previousRequestId}),{success:!0,message:`${s.requestId} starts on warm ${j} ${e} when its current turn ends (the thread shares the session)`}
2008
- if(await c(t,C,_))return{success:!0,message:`${s.requestId} handed to warm ${j} ${e} (${_.area?"same area, separate work":"the thread shares the session"})`}}}else _?.why&&u.info(`session routing: spawning, because ${(0,E.whyNotReused)(_.why)}`,{requestId:s.requestId,why:_.why})
2009
- return l(t,C)}
2008
+ i.info("session routing: the worker did not come free for this start; spawning",{requestId:a.requestId,sessionId:r.sessionId,why:s,busy:p.busy,on:p.liveElsewhere}),await l(e,{...t,req:h})}catch(e){i.warn("session routing: the start after a turn failed",{requestId:a.requestId,sessionId:r.sessionId,error:(0,$.getErrorMessage)(e)})}})(t,_,T,e).finally(()=>M.delete(s.requestId))}),h.info("session routing: the thread's worker is finishing a turn; the start is handed over when it ends",{requestId:s.requestId,sessionId:e,previous:T.previousRequestId}),{success:!0,message:`${s.requestId} starts on warm ${A} ${e} when its current turn ends (the thread shares the session)`}
2009
+ if(await c(t,_,T))return{success:!0,message:`${s.requestId} handed to warm ${A} ${e} (${T.area?"same area, separate work":"the thread shares the session"})`}}}else T?.why&&h.info(`session routing: spawning, because ${(0,E.whyNotReused)(T.why)}`,{requestId:s.requestId,why:T.why})
2010
+ return l(t,_)}
2010
2011
  const f=t(87780),m=t(5641),g=t(45687),w=t(28148),y=t(17039),v=t(86116),b=t(67078),k=t(60833),S=t(2424),I=t(23687),$=t(58824),j=t(65454),E=t(64469),A=t(81570),C=t(76379),_=t(83807),T=t(47912),x=t(71879),M=new Map,R=new Set(["completed","canceled","reverted"])},82928(e,exports,t){"use strict"
2011
2012
  function r(e){let t=0
2012
2013
  for(const r of[...e??[]].reverse())if("user"===r?.info?.role&&!(r.parts??[]).some(e=>!0===e?.metadata?.alchemy?.payload?.nudge&&!0===e?.metadata?.alchemy?.payload?.restart)){if(!(r.parts??[]).some(e=>!0===e?.metadata?.alchemy?.payload?.nudge))break
@@ -2162,7 +2163,7 @@ const i=e.refusal
2162
2163
  this.logger.error("REFUSED TURN: session stopped on an error another try on the same model cannot get past",{sessionId:t.id,title:t.title,errorName:e.errorName,model:i?.model,message:i?.message})
2163
2164
  const a=i?.model??"its model",l=this.resumedOn.get(t.id)
2164
2165
  this.resumedOn.delete(t.id)
2165
- const d=!!s&&"hub"!==n&&!0===this.isAgentModelPinned?.(s),u=(w={scope:i?.scope,providerID:i?.providerID,pinned:d}).pinned?"Sending it again on the same model fails the same way. This agent is pinned to that model, so a pick at the top of Steer does not move it: open Workflow agents and clear its pinned model, or pin another one, and it resumes on the new model by itself.":"provider"===w.scope?`Reconnect ${w.providerID??"the provider"} in Settings > Providers, or pick a model from another provider by clicking the model name at the top of Steer: it resumes on it by itself.`:"Sending it again on the same model fails the same way. Pick another model by clicking the model name at the top of Steer: it resumes on it by itself.",h=`${a} refused its last turn: "${i?.message??e.errorName??"unknown error"}". ${u}`,p=c(n,s,r,t.title),f=l?`Resuming it on ${l} did not work. `:"",g=()=>this.reportFleetHealth?.(`${f}${p} stopped: ${h}`,{sessionId:t.id,attention:"act",dedupKey:o,...r?{requestId:r}:{}})
2166
+ const d=!!s&&"hub"!==n&&!0===this.isAgentModelPinned?.(s),u=(w={scope:i?.scope,providerID:i?.providerID,pinned:d}).pinned?"Sending it again on the same model fails the same way. This agent is pinned to that model, so a pick at the top of Steer does not move it: open Workflow agents and clear its pinned model, or pin another one, and it resumes on the new model by itself.":"provider"===w.scope?`Reconnect ${w.providerID??"the provider"} in Settings > Providers, or pick a model from another provider by clicking the model name at the top of Steer: it resumes on it by itself.`:"Sending it again on the same model fails the same way. Pick another model by clicking the model name at the top of Steer: it resumes on it by itself.",h=i?.looped?`${a} could not use AlchemyLab's tools. ${i.message} ${u}`:`${a} refused its last turn: "${i?.message??e.errorName??"unknown error"}". ${u}`,p=c(n,s,r,t.title),f=l?`Resuming it on ${l} did not work. `:"",g=()=>this.reportFleetHealth?.(`${f}${p} stopped: ${h}`,{sessionId:t.id,attention:"act",dedupKey:o,...r?{requestId:r}:{}})
2166
2167
  var w
2167
2168
  "hub"!==n&&r&&this.raiseStalledAsk?this.raiseStalledAsk({requestId:r,sessionId:t.id,detail:("owned"===n?`${f}It stopped because ${h}`:`${f}${p} stopped: ${h}`).replace(/\.$/,""),refusedModel:i?.model??"its model",...d?{refusedPinned:!0}:{}}).then(e=>{e||g()}).catch(()=>g()):g()}noticeSilence(e,t,r,s,o,a){const c=`silent|${e.id}|${r.messageId??""}`
2168
2169
  if(this.reportedKeys.has(c))return!0
@@ -2210,8 +2211,10 @@ if(s.ok){const e=await s.json(),r=e?.[e.length-1]?.info?.providerID
2210
2211
  r&&(t=r)}}catch{}this.reportQuotaExhaustion({providerID:t,planType:void 0,message:String(s.message??"the provider is refusing requests")},r,`retry attempt ${s.attempt}`)}}reportQuotaExhaustion(e,t,r){const s=`quota|${e.providerID}|${e.resetsAt??"unknown"}`
2211
2212
  if(this.reportedKeys.has(s))return
2212
2213
  this.reportedKeys.add(s)
2213
- const n=e.resetsAt?` The limit resets ${new Date(1e3*e.resetsAt).toLocaleString("sv-SE",{timeZoneName:"short"})}.`:"",o=e.planType?` (plan: ${e.planType})`:""
2214
- this.logger.error("PROVIDER QUOTA EXHAUSTED: turns on this provider fail until the limit resets",{providerID:e.providerID,resetsAt:e.resetsAt,planType:e.planType,sessionId:t,title:r}),this.recordProviderBlock?.(e.providerID,e.resetsAt,e.planType,e.message),this.reportFleetHealth?.(`${e.providerID} usage limit reached${o}: "${e.message}". Work on ${e.providerID} models cannot take a turn until the limit resets.${n} To go on now, pick a model from another provider by clicking the model name at the top of Steer: everything this stopped resumes on it by itself. Otherwise it resumes when the limit resets.`,{sessionId:t,attention:"act",dedupKey:s})}async checkModelAttestation(e){const t=this.getOpenCodeUrl()
2214
+ const n=e.planType?` (plan: ${e.planType})`:""
2215
+ this.logger.error("PROVIDER QUOTA EXHAUSTED: turns on this provider fail until the limit resets",{providerID:e.providerID,resetsAt:e.resetsAt,planType:e.planType,sessionId:t,title:r}),this.recordProviderBlock?.(e.providerID,e.resetsAt,e.planType,e.message),this.reportFleetHealth?.(function(e,t,r="",s=new Date){let n="until the limit lifts"
2216
+ if(t){const e=new Date(1e3*t),r=e.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"})
2217
+ n=e.toDateString()===s.toDateString()?`until ${r}`:`until ${e.toLocaleDateString("en-GB",{day:"numeric",month:"short"})} ${r}`}return`${e} usage limit reached${r}. Work on ${e} models waits ${n}, then resumes by itself. To go on now, pick a model from another provider by clicking the model name at the top of Steer: what stopped resumes on it by itself.`}(e.providerID,e.resetsAt,n),{sessionId:t,attention:"act",dedupKey:s,details:`What ${e.providerID} said: ${e.message}`})}async checkModelAttestation(e){const t=this.getOpenCodeUrl()
2215
2218
  if(!t)return
2216
2219
  const r=new Set(e.map(e=>e.id))
2217
2220
  for(const e of Array.from(this.attestedAt.keys()))r.has(e)||this.attestedAt.delete(e)
@@ -2680,7 +2683,9 @@ const s=t(e),n=t(r)
2680
2683
  if(!s||!n)return!1
2681
2684
  if(s===n)return!1
2682
2685
  const o=(e,t)=>t.startsWith(e)&&/^[-_.]\d[\d._-]{5,}$/.test(t.slice(e.length))
2683
- return!o(s,n)&&!o(n,s)},exports.substitutionMessage=function(e,t,r){return`Model substitution detected${r?` (session: ${r})`:""}: AlchemyLab requested \`${e}\` but the provider reported serving \`${t}\`. Work produced in this session did NOT run on the model you selected. Check the provider account's model access and any routing in front of it.`}},25146(e,exports,t){"use strict"
2686
+ if(o(s,n)||o(n,s))return!1
2687
+ const i=e=>e.replace(/:[a-z0-9._-]+$/,"")
2688
+ return s.includes(":")===n.includes(":")||i(s)!==i(n)},exports.substitutionMessage=function(e,t,r){return`Model substitution detected${r?` (session: ${r})`:""}: AlchemyLab requested \`${e}\` but the provider reported serving \`${t}\`. Work produced in this session did NOT run on the model you selected. Check the provider account's model access and any routing in front of it.`}},25146(e,exports,t){"use strict"
2684
2689
  async function r(e,t){const r=e.condition_params||{}
2685
2690
  switch(e.condition_type){case"deadline":{const s=Number(r.deadlineSeconds)||0,n=new Date(e.created_at).getTime()+1e3*s
2686
2691
  return t.now()>=n?{fire:!0,payload:{deadlineSeconds:s}}:{fire:!1}}case"command-check":{const s=String(r.fireOn||"fail"),n=await t.runCheck(String(r.commandName||""),{monitorId:e.id,minIntervalSeconds:Math.max(30,Number(r.minIntervalSeconds)||300),trackChange:"change"===s,inlineCommand:r.command?String(r.command):void 0,ownerSessionId:e.owner_external_session_id||void 0})
@@ -2975,35 +2980,37 @@ const d=r.command.replace(/\s+/g," ").trim()
2975
2980
  if((i[1].endsWith("\u2026")?d.startsWith(a):d===a)&&(r.command===s||t(r.command).includes(c)))return r}}
2976
2981
  const r=new Map},43218(e,exports,t){"use strict"
2977
2982
  function r(e){if(e&&e.includes("/"))return e.split("/")[0].trim().toLowerCase()||void 0}async function s(e,t=fetch){if(!e)return null
2978
- const r=a.get(e)
2983
+ const r=c.get(e)
2979
2984
  if(r&&Date.now()-r.at<3e4)return r.value
2980
2985
  try{const r=await t(`${e.replace(/\/$/,"")}/config/providers`,{signal:AbortSignal.timeout(5e3)})
2981
2986
  if(!r.ok)return null
2982
2987
  const s=await r.json(),n={connected:(s.providers||[]).map(e=>String(e.id||"")).filter(Boolean),defaults:s.default||{}}
2983
- return a.set(e,{at:Date.now(),value:n}),n}catch{return null}}exports.b=void 0,exports.fetchProviderUsage=async function(e,t=fetch){const r=(0,n.getOpenCodeUrlFromConfig)(e)
2988
+ return c.set(e,{at:Date.now(),value:n}),n}catch{return null}}function n(e,t){if(!e||!t||0===t.connected.length)return e
2989
+ const s=r(e)
2990
+ if(!s||t.connected.includes(s))return e
2991
+ const n=(0,i.primaryProviderOf)(t.connected,e)
2992
+ if(n)return(0,i.carryModelToProvider)(e,n)??(0,i.defaultModelFor)(n)
2993
+ for(const e of t.connected)if(t.defaults[e])return`${e}/${t.defaults[e]}`
2994
+ return e}exports.b=void 0,exports.fetchProviderUsage=async function(e,t=fetch){const r=(0,o.getOpenCodeUrlFromConfig)(e)
2984
2995
  if(!r)return[]
2985
- const s=r.replace(/\/$/,""),o=i.get(s)
2986
- if(o&&Date.now()-o.at<6e4)return o.data
2996
+ const s=r.replace(/\/$/,""),n=a.get(s)
2997
+ if(n&&Date.now()-n.at<6e4)return n.data
2987
2998
  try{const e=await t(`${s}/config/usage`,{signal:AbortSignal.timeout(15e3)})
2988
- if(!e.ok)return o?.data??[]
2989
- const r=await e.json(),n=Array.isArray(r?.providers)?r.providers:[]
2990
- return i.set(s,{at:Date.now(),data:n}),n}catch{return o?.data??[]}},exports.providerOf=r,exports.spentWindow=function(e,t,r=exports.b){if(!t)return null
2999
+ if(!e.ok)return n?.data??[]
3000
+ const r=await e.json(),o=Array.isArray(r?.providers)?r.providers:[]
3001
+ return a.set(s,{at:Date.now(),data:o}),o}catch{return n?.data??[]}},exports.providerOf=r,exports.spentWindow=function(e,t,r=exports.b){if(!t)return null
2991
3002
  const s=e.find(e=>e.providerID===t)
2992
3003
  if(!s)return null
2993
3004
  let n=null
2994
3005
  for(const e of s.windows)e.usedPercent>=r&&(!n||e.usedPercent>n.usedPercent)&&(n=e)
2995
3006
  return n},exports.describeWindow=function(e,t){const r=t.resetsAt?new Date(1e3*t.resetsAt).toLocaleString([],{weekday:"short",hour:"2-digit",minute:"2-digit"}):"an unknown time"
2996
- return`${e} is ${t.usedPercent}% into its ${t.label} (resets ${r})`},exports.fetchConnectedProviders=async function(e,t=fetch){return s((0,n.getOpenCodeUrlFromConfig)(e),t)},exports.fetchConnectedProvidersAt=s,exports.runnableModel=function(e,t){if(!e||!t||0===t.connected.length)return e
2997
- const s=r(e)
2998
- if(!s||t.connected.includes(s))return e
2999
- const n=(0,o.primaryProviderOf)(t.connected,e)
3000
- if(n)return(0,o.carryModelToProvider)(e,n)??(0,o.defaultModelFor)(n)
3001
- for(const e of t.connected)if(t.defaults[e])return`${e}/${t.defaults[e]}`
3002
- return e}
3003
- const n=t(60833),o=t(95671)
3007
+ return`${e} is ${t.usedPercent}% into its ${t.label} (resets ${r})`},exports.fetchConnectedProviders=async function(e,t=fetch){return s((0,o.getOpenCodeUrlFromConfig)(e),t)},exports.fetchConnectedProvidersAt=s,exports.unchosenDefaultToFollow=function(e,t,r){if(!t||!e||!r||0===r.connected.length)return null
3008
+ const s=n(e,r)
3009
+ return s&&s!==e?s:null},exports.runnableModel=n
3010
+ const o=t(60833),i=t(95671)
3004
3011
  exports.b=(()=>{const e=Number(process.env.ALCHEMY_PROVIDER_SPENT_PERCENT)
3005
3012
  return Number.isFinite(e)&&e>0&&e<=100?e:90})()
3006
- const i=new Map,a=new Map},1285(e,exports,t){"use strict"
3013
+ const a=new Map,c=new Map},1285(e,exports,t){"use strict"
3007
3014
  function r(e,t){return(0,s.createHmac)("sha256",e).update(`read|${t}`).digest("base64url")}exports.J=void 0,exports.mintReadToken=function(e,t=Date.now()){const s=t+exports.J
3008
3015
  return{token:`r1.${s}.${r(e,s)}`,expiresAt:s}},exports.readTokenValid=function(e,t,s=Date.now()){if("string"!=typeof e||!e.startsWith("r1."))return!1
3009
3016
  const[,o,i]=e.split("."),a=Number(o)
@@ -3794,10 +3801,11 @@ if(!r||r.startsWith("..")||M.default.isAbsolute(r)||".alchemy_lab"===r.split(M.d
3794
3801
  const s=await te.note({sessionId:n,requestId:c.requestId,worktree:c.worktree,relPath:r.split(M.default.sep).join("/"),tool:i,..."read"===i&&e.lines?{readRange:e.lines}:{}},l)
3795
3802
  s&&d.push(a.length>1?`${r}:\n${s}`:s)}t.json({success:!0,data:{note:d.length>0?d.join("\n\n"):null}})}catch(e){o.logger.debug("parallel-note failed (the tool result stays as it is)",{error:(0,P.getErrorMessage)(e)}),t.json({success:!0,data:{note:null}})}}),e.get("/api/sibling-overlap",async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0
3796
3803
  if(!r||!/^req\d+$/.test(r))return void t.status(400).json({success:!1,error:"requestId required"})
3797
- const s=o.getProjectId(),n=(0,L.simpleGit)(o.projectPath),i=(await n.raw(["for-each-ref","--format=%(refname:short)","refs/heads/requests/"])).split("\n").map(e=>e.trim().replace(/^requests\//,"")).filter(e=>/^req\d+$/.test(e)&&e!==r),a=new Set(["open","in_progress","ready_for_review","completing","resolving_conflicts"]),c=[]
3798
- for(const e of i)try{const t=s?await o.apiClient.get(`/api/projects/${s}/requests/${e}`):null,r=t?.data??t
3799
- r&&a.has(String(r.status))&&c.push({id:e,title:String(r.title??""),status:String(r.status)})}catch{}const l=await(0,Y.resolveDefaultBranch)(o.projectPath),d=await(0,_.siblingOverlap)({projectPath:o.projectPath,requestId:r,siblingIds:c.map(e=>e.id),defaultBranch:l,git:n}),u=new Map(c.map(e=>[e.id,e]))
3800
- t.json({success:!0,data:d.map(e=>({...e,siblingTitle:u.get(e.sibling)?.title??"",siblingStatus:u.get(e.sibling)?.status??""}))})}catch(e){o.logger.debug("sibling-overlap lookup failed",{error:(0,P.getErrorMessage)(e)}),t.json({success:!0,data:[]})}}),e.get("/api/request-diff-stats",async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0
3804
+ const s=o.getProjectId(),n=(0,L.simpleGit)(o.projectPath),i=(await n.raw(["for-each-ref","--format=%(refname:short)","refs/heads/requests/"])).split("\n").map(e=>e.trim().replace(/^requests\//,"")).filter(e=>/^req\d+$/.test(e)&&e!==r),a=new Set(["open","in_progress","ready_for_review","completing","resolving_conflicts"]),c=new Set(i),l=[]
3805
+ if(s&&c.size>0){const e=await o.apiClient.get(`/api/projects/${s}/requests?inFlight=true&limit=100`),t=Array.isArray(e?.data)?e.data:[]
3806
+ for(const e of t){const t=String(e?.localId??"")
3807
+ c.has(t)&&a.has(String(e.status))&&l.push({id:t,title:String(e.title??""),status:String(e.status)})}}const d=await(0,Y.resolveDefaultBranch)(o.projectPath),u=await(0,_.siblingOverlap)({projectPath:o.projectPath,requestId:r,siblingIds:l.map(e=>e.id),defaultBranch:d,git:n}),h=new Map(l.map(e=>[e.id,e]))
3808
+ t.json({success:!0,data:u.map(e=>({...e,siblingTitle:h.get(e.sibling)?.title??"",siblingStatus:h.get(e.sibling)?.status??""}))})}catch(e){o.logger.debug("sibling-overlap lookup failed",{error:(0,P.getErrorMessage)(e)}),t.json({success:!0,data:[]})}}),e.get("/api/request-diff-stats",async(e,t)=>{try{const r="string"==typeof e.query.requestId?e.query.requestId:void 0
3801
3809
  if(!r)return void t.status(400).json({success:!1,error:"requestId required"})
3802
3810
  const s=(0,L.simpleGit)(o.projectPath),n=await(0,F.resolveRequestDiffRange)(s,r,{status:"in_progress"}),i=n.headSha,a="string"==typeof e.query.since&&/^[0-9a-f]{7,40}$/.test(e.query.since)?e.query.since:"",c=a&&i&&await s.raw(["merge-base","--is-ancestor",a,i]).then(()=>!0,()=>!1)?a:n.baseSha,l=c&&i?await(0,F.computeDiffStats)(s,c,i):void 0,d=await(0,Y.resolveDefaultBranch)(o.projectPath).catch(()=>{})
3803
3811
  t.json({success:!0,data:l?{...l,baseSha:c,headSha:i,...d?{branch:d}:{}}:null})}catch(e){o.logger.debug("request-diff-stats lookup failed",{error:(0,P.getErrorMessage)(e)}),t.json({success:!0,data:null})}}),e.post("/api/verify-preflight",async(e,t)=>{try{const{requestId:r,commands:s}=e.body??{},n=Array.isArray(s)?s.filter(e=>"string"==typeof e&&!!e.trim()):[]
@@ -4772,8 +4780,8 @@ class ht{rememberApprovedCommand(e,t,r){const s=t.rule,o=r?"in this environment"
4772
4780
  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})
4773
4781
  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})
4774
4782
  const t=Date.now()
4775
- this.serveDeathTimestamps=this.serveDeathTimestamps.filter(e=>t-e<6e5),this.serveDeathTimestamps.push(t),this.logger.warn("OpenCode serve died unexpectedly -- parked for self-heal; next needing signal revives it",{code:e.code,signal:e.signal,pid:e.pid,unexpectedDeathsLast10m:this.serveDeathTimestamps.length}),this.serveDeathTimestamps.length>=3&&this.logger.error("OpenCode serve is FLAPPING (3+ unexpected deaths in 10 minutes). Revival keeps retrying, but check the opencode-serve log for the underlying crash.",{})}constructor(e,t){this.envSessionStarts=new Map,this.leakedCallNudges=new Map,this.paintCleaned=new Set,this.runningSessions=new Set,this.lastRunningReport="",this.directReportDedup=new Map,this.cachedReceipts=null,this.cachedIntegrations=null,this.lastSettingsSyncAt=0,this.activeMcpCalls=new Map,this.ownerGuidanceStale=!1,this.workCopiesGate=(()=>{let e
4776
- return{promise:new Promise(t=>{e=t}),resolve:e}})(),this.pendingConfigReload=!1,this.pendingConfigReloadSince=0,this.lastConfigReloadAt=0,this.deferredReloadCheckInFlight=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.clientVersion=(0,i.ownVersion)(),this.remoteLastOkAt=null,this.remoteFailures=0,this.remoteLastError=null,this.isPolling=!1,this.isProcessingAction=!1,this.localSecret=(0,te.newLocalSecret)(),this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.previewTunnel=null,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.droppedEventCount=0,this.firstDropAt=0,this.sseReconnectTimers=new Set,this.sseConnectGeneration=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.serveStartedAt=0,this.registeredOnce=!1,this.recordedUsageMsgs=new Map,this.usageStepTimers=new Map,this.inFlightProxyActions=0,this.pendingRegularActions=[],this.queuedRegularActionIds=new Set,this.inFlightServeProxyRequests=0,this.serveDeathTimestamps=[],this.managedExitPids=new Set,this.pauseApplied=null,this.childDeathTold=new Set,this.mainBranchName=null,this.projectSubsystemsStarted=!1,this.lastParkVetoes=[],this.logger=e,this.options=t,(0,rt.installServeAuthFetch)(),(0,te.installLocalSecretFetch)(()=>this.options.projectPath),t.projectPath&&(process.env[n.PROJECT_ROOT_VAR]=t.projectPath),this.port=t.port||3456,this.startTime=new Date
4783
+ this.serveDeathTimestamps=this.serveDeathTimestamps.filter(e=>t-e<6e5),this.serveDeathTimestamps.push(t),this.logger.warn("OpenCode serve died unexpectedly -- parked for self-heal; next needing signal revives it",{code:e.code,signal:e.signal,pid:e.pid,unexpectedDeathsLast10m:this.serveDeathTimestamps.length}),this.serveDeathTimestamps.length>=3&&this.logger.error("OpenCode serve is FLAPPING (3+ unexpected deaths in 10 minutes). Revival keeps retrying, but check the opencode-serve log for the underlying crash.",{})}constructor(e,t){this.envSessionStarts=new Map,this.leakedCallNudges=new Map,this.paintCleaned=new Set,this.runningSessions=new Set,this.lastRunningReport="",this.directReportDedup=new Map,this.cachedReceipts=null,this.cachedIntegrations=null,this.defaultModelUnchosen=!1,this.lastSettingsSyncAt=0,this.activeMcpCalls=new Map,this.ownerGuidanceStale=!1,this.workCopiesGate=(()=>{let e
4784
+ return{promise:new Promise(t=>{e=t}),resolve:e}})(),this.pendingConfigReload=!1,this.pendingConfigReloadSince=0,this.lastConfigReloadAt=0,this.deferredReloadCheckInFlight=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.clientVersion=(0,i.ownVersion)(),this.remoteLastOkAt=null,this.remoteFailures=0,this.remoteLastError=null,this.isPolling=!1,this.isProcessingAction=!1,this.localSecret=(0,te.newLocalSecret)(),this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.previewTunnel=null,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.droppedEventCount=0,this.firstDropAt=0,this.sseReconnectTimers=new Set,this.sseConnectGeneration=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.serveStartedAt=0,this.registeredOnce=!1,this.recordedUsageMsgs=new Map,this.usageStepTimers=new Map,this.inFlightProxyActions=0,this.pendingRegularActions=[],this.queuedRegularActionIds=new Set,this.inFlightServeProxyRequests=0,this.serveDeathTimestamps=[],this.managedExitPids=new Set,this.followingDefaultTo=null,this.pauseApplied=null,this.childDeathTold=new Set,this.mainBranchName=null,this.projectSubsystemsStarted=!1,this.lastParkVetoes=[],this.logger=e,this.options=t,(0,rt.installServeAuthFetch)(),(0,te.installLocalSecretFetch)(()=>this.options.projectPath),t.projectPath&&(process.env[n.PROJECT_ROOT_VAR]=t.projectPath),this.port=t.port||3456,this.startTime=new Date
4777
4785
  const r=(0,P.createMetricsCollector)(!1)
4778
4786
  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
4779
4787
  this.apiClient.setTokenRefresher(async()=>{const t=(await e.getToken())?.token
@@ -4938,7 +4946,11 @@ if(r)try{const e=await r.hasValidToken(),s=r.getAuthStatus(),n=e?await r.getToke
4938
4946
  t.json({authenticated:e,isPendingAuth:s.isPendingAuth,expiresAt:n?.expiresAt??null})}catch(e){this.logger.warn("auth-status check failed",{error:(0,H.getErrorMessage)(e)}),t.json({authenticated:!1,isPendingAuth:!1})}else t.json({authenticated:!0,isPendingAuth:!1})}handleStatus(e,t){t.json({status:"running",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),startTime:this.startTime.toISOString(),projectId:this.projectId,serverId:this.serverId,isPolling:this.isPolling,parkState:this.parkController?.getState(),parkVetoes:this.parkController?this.lastParkVetoes:void 0,parkIdleForMs:this.parkController?.idleForMs(),port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const r=O.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
4939
4947
  try{if(!D.default.existsSync(r))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
4940
4948
  const e=D.default.readFileSync(r,"utf-8"),s=JSON.parse(e)
4941
- t.json({port:s.port,pid:s.pid,startedAt:s.startedAt,baseUrl:`http://localhost:${s.port}`,directory:this.options.projectPath,proxyEnabled:!0,parkState:this.parkController?.getState()??"active"})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,H.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,H.getErrorMessage)(e)})}}updateDefaultModel(e){return void 0===e?Promise.resolve():(e!==this.cachedDefaultModel&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e||""),this.stuckSessionWatchdog?.capacityChanged("model-changed")),e&&this.projectId&&this.lastAgentResyncModel!==e?this.resyncAgentMarkdowns().catch(e=>this.logger.debug("Background agent markdown re-sync failed",{error:(0,H.getErrorMessage)(e)})):Promise.resolve())}handleMcpToolCallEvent(e,t){this.parkController?.noteActivity(),"start"!==e?(this.activeMcpCalls.delete(t),0===this.activeMcpCalls.size&&this.pendingConfigReload&&this.fireDeferredReloadIfIdle("mcp-idle")):this.activeMcpCalls.set(t,Date.now())}startMcpStaleCleanup(){this.mcpStaleCleanupInterval||(this.mcpStaleCleanupInterval=setInterval(()=>{const e=Date.now()-12e4
4949
+ t.json({port:s.port,pid:s.pid,startedAt:s.startedAt,baseUrl:`http://localhost:${s.port}`,directory:this.options.projectPath,proxyEnabled:!0,parkState:this.parkController?.getState()??"active"})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,H.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,H.getErrorMessage)(e)})}}async followUnchosenDefault(){const e=(0,de.unchosenDefaultToFollow)(this.cachedDefaultModel,this.defaultModelUnchosen,this.configurer.getConnectedProviders())
4950
+ if(!e||!this.projectId||this.followingDefaultTo===e)return
4951
+ this.followingDefaultTo=e
4952
+ const t=this.cachedDefaultModel
4953
+ try{await this.apiClient.patch(`/api/projects/${this.projectId}`,{metadata:{defaultModel:e,defaultModelUnchosen:!0}}),this.logger.info("unchosen default model moved to a connected provider",{from:t,to:e}),await this.updateDefaultModel(e)}catch(e){this.followingDefaultTo=null,this.logger.debug("could not move the unchosen default model",{error:(0,H.getErrorMessage)(e)})}}updateDefaultModel(e){return void 0===e?Promise.resolve():(e!==this.cachedDefaultModel&&(this.cachedDefaultModel=e,this.hookExecutor.setDefaultModel(e||""),this.stuckSessionWatchdog?.capacityChanged("model-changed")),e&&this.projectId&&this.lastAgentResyncModel!==e?this.resyncAgentMarkdowns().catch(e=>this.logger.debug("Background agent markdown re-sync failed",{error:(0,H.getErrorMessage)(e)})):Promise.resolve())}handleMcpToolCallEvent(e,t){this.parkController?.noteActivity(),"start"!==e?(this.activeMcpCalls.delete(t),0===this.activeMcpCalls.size&&this.pendingConfigReload&&this.fireDeferredReloadIfIdle("mcp-idle")):this.activeMcpCalls.set(t,Date.now())}startMcpStaleCleanup(){this.mcpStaleCleanupInterval||(this.mcpStaleCleanupInterval=setInterval(()=>{const e=Date.now()-12e4
4942
4954
  let t=0
4943
4955
  for(const[r,s]of this.activeMcpCalls)s<e&&(this.activeMcpCalls.delete(r),t++)
4944
4956
  t>0&&this.logger.warn("Cleaned up stale MCP tool-call tracking entries",{removed:t}),this.pendingConfigReload&&this.fireDeferredReloadIfIdle("stale-cleanup")},6e4))}async anySessionBusy(){if(Date.now()-(0,d.lastPromptDeliveredAt)()<ht.PROMPT_DELIVERY_BUSY_MS)return!0
@@ -4957,7 +4969,10 @@ try{const t=Date.now()-this.pendingConfigReloadSince
4957
4969
  if(t<ht.RELOAD_DEFER_CAP_MS){if(this.activeMcpCalls.size>0)return
4958
4970
  if(await this.anySessionBusy())return}else this.logger.warn("Config reload deferred past cap; firing despite activity (staleness beats permanent deferral)",{trigger:e,waitedMs:t})
4959
4971
  this.pendingConfigReload=!1,this.stopPendingReloadRecheck(),this.logger.info("Firing deferred OpenCode config reload",{trigger:e,waitedMs:t}),await this.reloadOpenCodeConfigNow(`deferred-${e}`)}finally{this.deferredReloadCheckInFlight=!1}}}else this.stopPendingReloadRecheck()}async applyClaudeThinking(e){const t=this.claudeThinkingSeen
4960
- this.claudeThinkingSeen=e,void 0!==t&&t!==e&&this.projectId&&(this.logger.info("Claude thinking setting changed; re-syncing agents",{from:t,to:e}),await this.resyncAgentMarkdowns().catch(e=>this.logger.debug("Agent re-sync after the Claude thinking change failed",{error:(0,H.getErrorMessage)(e)})))}async applyPause(e){const t=this.getOpenCodeUrl()?.replace(/\/$/,"")
4972
+ this.claudeThinkingSeen=e,void 0!==t&&t!==e&&this.projectId&&(this.logger.info("Claude thinking setting changed; re-syncing agents",{from:t,to:e}),await this.resyncAgentMarkdowns().catch(e=>this.logger.debug("Agent re-sync after the Claude thinking change failed",{error:(0,H.getErrorMessage)(e)})))}async applyAnthropicCompaction(e){const t=(0,je.generatedOpencodeConfigPath)(this.options.projectPath)
4973
+ try{const r=JSON.parse(await D.default.promises.readFile(t,"utf8"))
4974
+ if(!r||"object"!=typeof r||!(0,B.withAnthropicCompaction)(r,e))return
4975
+ await D.default.promises.writeFile(t,JSON.stringify(r,null,2),"utf8"),this.logger.info("Claude's own compaction setting changed; reloading the serve's config",{on:e}),await this.requestConfigReload("anthropic-compaction")}catch(e){this.logger.debug("Claude's own compaction setting not applied yet",{error:(0,H.getErrorMessage)(e)})}}async applyPause(e){const t=this.getOpenCodeUrl()?.replace(/\/$/,"")
4961
4976
  if(t&&(this.pauseApplied?.url!==t||this.pauseApplied.paused!==e))try{const r=await fetch(`${t}/config/pause`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paused:e}),signal:AbortSignal.timeout(15e3)})
4962
4977
  if(!r.ok)return void this.logger.warn("The serve did not take the pause",{paused:e,status:r.status})
4963
4978
  const s=await r.json().catch(()=>({}))
@@ -5073,12 +5088,12 @@ const{agent:r,planId:s,prompt:n,title:o,parentSessionId:i,flavor:a}=e.body||{},c
5073
5088
  if(c&&!d.has(c))return void t.status(400).json({success:!1,error:`agent must be one of: ${[...d].join(", ")}`})
5074
5089
  const u=await(0,ke.spawnPlannerWithDoc)({agent:c||"planner",flavor:l,planId:s,prompt:n,title:o||"Planner",parentSessionId:i,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e)})
5075
5090
  if(!u.success||!u.sessionId)return void t.status(500).json({success:!1,error:u.error||"spawn failed"})
5076
- t.json({success:!0,data:{sessionId:u.sessionId,planId:u.planId,pid:u.pid}})}catch(e){(0,Oe.respondWithError)(t,this.logger,"Spawn planner",e)}}async ensureHubAlchemist(){try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/persistent/alchemist`),t=e?.data??e
5091
+ t.json({success:!0,data:{sessionId:u.sessionId,planId:u.planId,pid:u.pid}})}catch(e){(0,Oe.respondWithError)(t,this.logger,"Spawn planner",e)}}scheduleHubAlchemist(e){this.hubRetryTimer&&clearTimeout(this.hubRetryTimer),this.hubRetryTimer=setTimeout(async()=>{this.hubRetryTimer=void 0,await this.ensureHubAlchemist()||this.scheduleHubAlchemist(Math.min(2*e,6e4))},e),this.hubRetryTimer.unref?.()}async ensureHubAlchemist(){try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/persistent/alchemist`),t=e?.data??e
5077
5092
  if(t&&t.externalSessionId){const e=(0,et.getOpenCodeUrlFromConfig)(this.options.projectPath),r=e?await this.serveHasSession(e,String(t.externalSessionId)):null
5078
- if(!1!==r)return void this.logger.debug("Hub alchemist already present",{sessionId:t.id,verified:!0===r})
5079
- this.logger.warn("Hub alchemist row points at a session this serve does not have; ending it and creating a new hub",{sessionId:t.id,externalSessionId:t.externalSessionId}),await this.apiClient.patch(`/api/projects/${this.projectId}/cli-sessions/${t.id}`,{status:"completed"}).catch(e=>this.logger.warn("could not end the orphan hub row",{sessionId:t.id,error:(0,H.getErrorMessage)(e)}))}if(!this.projectId)return
5093
+ if(!1!==r)return this.logger.debug("Hub alchemist already present",{sessionId:t.id,verified:!0===r}),!0
5094
+ this.logger.warn("Hub alchemist row points at a session this serve does not have; ending it and creating a new hub",{sessionId:t.id,externalSessionId:t.externalSessionId}),await this.apiClient.patch(`/api/projects/${this.projectId}/cli-sessions/${t.id}`,{status:"completed"}).catch(e=>this.logger.warn("could not end the orphan hub row",{sessionId:t.id,error:(0,H.getErrorMessage)(e)}))}if(!this.projectId)return!0
5080
5095
  const r=new ye.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,getDefaultModel:()=>this.cachedDefaultModel||void 0}),s="Hub",n=await r.prepareSession({agent:"alchemist",prompt:s,title:s,projectPath:this.options.projectPath})
5081
- this.logger.info("Hub alchemist created at boot (no browser required)",{sessionId:n?.sessionId??null})}catch(e){this.logger.warn("Hub alchemist bootstrap failed; project cannot be steered until one exists",{error:(0,H.getErrorMessage)(e)})}}async reapVanishedSessions(){if(!this.projectId)return
5096
+ return this.logger.info("Hub alchemist created at boot (no browser required)",{sessionId:n?.sessionId??null}),!0}catch(e){return this.logger.warn("Hub alchemist bootstrap failed; trying again shortly",{error:(0,H.getErrorMessage)(e)}),!1}}async reapVanishedSessions(){if(!this.projectId)return
5082
5097
  const e=(0,et.getOpenCodeUrlFromConfig)(this.options.projectPath)
5083
5098
  if(!e)return
5084
5099
  let t
@@ -5166,7 +5181,7 @@ const t=D.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
5166
5181
  return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,H.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e,(0,rt.noteServe)(e)
5167
5182
  const t=`http://127.0.0.1:${e}`
5168
5183
  this.openCodeUrlCache={url:t,timestamp:Date.now()},this.logger.info("OpenCode port set directly",{port:e}),this.serveStartedAt=Date.now(),this.stuckSessionWatchdog?.serveStarted(this.serveStartedAt),(async()=>{for(let e=0;e<10;e++){try{const t=await this.configurer.refreshConnectedProviders(this.options.projectPath)
5169
- if(this.configurer.hasConnectedProviders())return this.logger.info("connected providers known",{changed:t,attempt:e}),void(t&&await this.resyncAgentMarkdowns())}catch(t){this.logger.debug("connected-provider refresh failed",{attempt:e,error:(0,H.getErrorMessage)(t)})}await new Promise(e=>setTimeout(e,3e3))}this.logger.warn("the serve never reported its connected providers; agent models were not checked against them")})(),this.cachedDefaultModel||this.seedDefaultModel(e).catch(e=>this.logger.debug("Failed to seed default model",{error:(0,H.getErrorMessage)(e)}))}async seedDefaultModel(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
5184
+ if(this.configurer.hasConnectedProviders())return this.logger.info("connected providers known",{changed:t,attempt:e}),t&&await this.resyncAgentMarkdowns(),void await this.followUnchosenDefault()}catch(t){this.logger.debug("connected-provider refresh failed",{attempt:e,error:(0,H.getErrorMessage)(t)})}await new Promise(e=>setTimeout(e,3e3))}this.logger.warn("the serve never reported its connected providers; agent models were not checked against them")})(),this.cachedDefaultModel||this.seedDefaultModel(e).catch(e=>this.logger.debug("Failed to seed default model",{error:(0,H.getErrorMessage)(e)}))}async seedDefaultModel(e){const t=await fetch(`http://localhost:${e}/config/providers`,{signal:AbortSignal.timeout(5e3)})
5170
5185
  if(!t.ok)return
5171
5186
  const r=await t.json(),s=(r?.providers||[]).map(e=>String(e.id||"")).filter(Boolean),n=(0,x.primaryProviderOf)(s),o=r?.default||{},[i,a]=Object.entries(o)[0]||[],c=n?(0,x.defaultModelFor)(n):i&&a?`${i}/${a}`:null
5172
5187
  c&&(this.updateDefaultModel(c),this.logger.info("Seeded defaultModel",{defaultModel:c,primaryProvider:n}),this.apiClient.post("/api/user/preferences",{defaultModel:c}).catch(e=>this.logger.debug("Failed to seed user preference",{error:(0,H.getErrorMessage)(e)})))}getOpenCodeUrl(){if(this.parkController?.isServeDown())return null
@@ -5292,8 +5307,8 @@ r&&(t["X-Main-Sha"]=r)
5292
5307
  const s=await fetch(e,{headers:t,signal:AbortSignal.timeout(35e3)})
5293
5308
  if(!s.ok)throw new Error(`Poll failed with status ${s.status}`)
5294
5309
  const n=await s.json(),o={success:!0,data:n.data||n}
5295
- if(o.success&&o.data&&void 0!==o.data.hasRemoteEventConsumers&&(this.hasRemoteEventConsumers=o.data.hasRemoteEventConsumers,this.hasRemoteEventConsumers&&this.parkController?.noteActivity()),o.success&&o.data?.settings){const{defaultModel:e,paused:t,claudeThinking:r}=o.data.settings
5296
- void 0!==e&&await this.updateDefaultModel(e),"boolean"==typeof t&&await this.applyPause(t),"string"==typeof r&&await this.applyClaudeThinking(r)}if(o.success&&!0===o.data?.previewTunnel&&this.projectId&&(this.previewTunnel||(this.previewTunnel=new T.PreviewTunnelClient({remoteServerUrl:this.options.remoteServerUrl,projectId:this.projectId,projectPath:this.options.projectPath,headers:()=>({...this.apiClient.authToken?{Authorization:`Bearer ${this.apiClient.authToken}`}:{},...(0,st.machineCallerHeaders)(this.options.remoteServerUrl)}),ports:()=>({ownPort:this.port,blocked:[...this.openCodePort?[this.openCodePort]:[],...(0,_.listLive)().map(e=>e.port).filter(e=>e!==this.port)]}),logger:this.logger})),this.previewTunnel.want()),o.success&&Array.isArray(o.data?.invalidations)&&o.data.invalidations.length>0){const e=o.data.invalidations
5310
+ if(o.success&&o.data&&void 0!==o.data.hasRemoteEventConsumers&&(this.hasRemoteEventConsumers=o.data.hasRemoteEventConsumers,this.hasRemoteEventConsumers&&this.parkController?.noteActivity()),o.success&&o.data?.settings){const{defaultModel:e,paused:t,claudeThinking:r,anthropicCompaction:s,defaultModelUnchosen:n}=o.data.settings
5311
+ "boolean"==typeof n&&(this.defaultModelUnchosen=n),void 0!==e&&await this.updateDefaultModel(e),await this.followUnchosenDefault(),"boolean"==typeof t&&await this.applyPause(t),"string"==typeof r&&await this.applyClaudeThinking(r),"boolean"==typeof s&&await this.applyAnthropicCompaction(s)}if(o.success&&!0===o.data?.previewTunnel&&this.projectId&&(this.previewTunnel||(this.previewTunnel=new T.PreviewTunnelClient({remoteServerUrl:this.options.remoteServerUrl,projectId:this.projectId,projectPath:this.options.projectPath,headers:()=>({...this.apiClient.authToken?{Authorization:`Bearer ${this.apiClient.authToken}`}:{},...(0,st.machineCallerHeaders)(this.options.remoteServerUrl)}),ports:()=>({ownPort:this.port,blocked:[...this.openCodePort?[this.openCodePort]:[],...(0,_.listLive)().map(e=>e.port).filter(e=>e!==this.port)]}),logger:this.logger})),this.previewTunnel.want()),o.success&&Array.isArray(o.data?.invalidations)&&o.data.invalidations.length>0){const e=o.data.invalidations
5297
5312
  this.logger.info("Received cache invalidations from remote",{invalidations:e}),(e.includes("quests")||e.includes("documents")||e.includes("plans"))&&this.scheduleRunner?.invalidateCache(),e.includes("alchemist")&&this.scheduleRunner?.invalidateAlchemistCache(),e.includes("owner-guidance")&&(this.ownerGuidanceStale||this.logger.info("Your guidance changed; it reaches this project's agents at the next moment that costs nothing"),this.ownerGuidanceStale=!0),e.includes("agents")&&(this.logger.info("Agent definitions changed on the remote; re-syncing markdowns"),this.resyncAgentMarkdowns().catch(e=>this.logger.warn("Agent markdown re-sync after invalidation failed",{error:(0,H.getErrorMessage)(e)})))}const i=o.success?o.data?.scheduleNudges??o.data?.questNudges:void 0,a=Array.isArray(i)?i:[],c=o.success&&Array.isArray(o.data?.actions)?o.data.actions.length:0,l=a.length
5298
5313
  if((c>0||l>0)&&await this.ensureServeAlive("poll-work"),a.length>0){const e=a
5299
5314
  this.logger.info("Received goal nudges from remote",{scheduleNudges:e})
@@ -5358,13 +5373,13 @@ continue}throw new Error(`Port ${this.port} is already in use (tried 50 ports)`)
5358
5373
  try{const e=this.readProjectConfig()
5359
5374
  if(e?.id){const{upsertEntry:r}=await Promise.resolve().then(()=>t(52640))
5360
5375
  r({projectId:e.id,projectPath:this.options.projectPath,port:this.port,pid:process.pid,startedAt:(new Date).toISOString(),lastHeartbeat:(new Date).toISOString()}),this.logger.info("Wrote running-projects registry entry",{projectId:e.id,port:this.port})}}catch(e){this.logger.warn("Failed to write project registry entry",{error:(0,H.getErrorMessage)(e)})}try{await this.register(),this.startHeartbeat()}catch(e){const t=(0,H.getErrorMessage)(e)
5361
- t.includes("Required update"),this.logger.warn("Remote registration failed, continuing in local-only mode",{error:t})}if(this.projectId&&(setTimeout(()=>{this.ensureHubAlchemist()},1e4),setTimeout(()=>{this.reconcileSetupRunPass()},12e3),this.setupRunReconcileTimer=setInterval(()=>{this.reconcileSetupRunPass()},3e5),this.setupRunReconcileTimer.unref?.()),this.projectId&&(0,Ie.sweepOrphanExperiments)(this.projectId,this.logger,this.options.projectPath),this.projectId){const e=Date.now()
5376
+ t.includes("Required update"),this.logger.warn("Remote registration failed, continuing in local-only mode",{error:t})}if(this.projectId&&(this.scheduleHubAlchemist(1e4),setTimeout(()=>{this.reconcileSetupRunPass()},12e3),this.setupRunReconcileTimer=setInterval(()=>{this.reconcileSetupRunPass()},3e5),this.setupRunReconcileTimer.unref?.()),this.projectId&&(0,Ie.sweepOrphanExperiments)(this.projectId,this.logger,this.options.projectPath),this.projectId){const e=Date.now()
5362
5377
  setTimeout(()=>{this.sweepOrphanTestRuns(e)},15e3)}this.startExperimentReaper(),this.startLinearAutoImportPoller()
5363
5378
  try{const e=`${this.options.remoteServerUrl}/api/projects/${this.projectId}/actions/pending?timeout=0&settingsOnly=true`,t={"Content-Type":"application/json",Accept:"application/json"}
5364
5379
  this.apiClient.authToken&&(t.Authorization=`Bearer ${this.apiClient.authToken}`),Object.assign(t,(0,st.machineCallerHeaders)(this.options.remoteServerUrl))
5365
5380
  const r=await fetch(e,{headers:t,signal:AbortSignal.timeout(8e3)})
5366
5381
  if(r.ok){const e=await r.json(),t=e?.data?.settings||e?.settings
5367
- void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel})),"boolean"==typeof t?.paused&&await this.applyPause(t.paused),"string"==typeof t?.claudeThinking&&(this.claudeThinkingSeen=t.claudeThinking)}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,H.getErrorMessage)(e)})}this.startPolling(),await this.startProjectSubsystems("start"),this.startMcpStaleCleanup(),this.projectId&&this.startParkController()}async startProjectSubsystems(e){if(this.projectId&&!this.projectSubsystemsStarted){this.projectSubsystemsStarted=!0,this.logger.info("Starting project subsystems",{source:e,projectId:this.projectId}),(0,Fe.setContainerProject)(this.projectId),this.startSSEEventForwarder()
5382
+ "boolean"==typeof t?.defaultModelUnchosen&&(this.defaultModelUnchosen=t.defaultModelUnchosen),void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel})),"boolean"==typeof t?.paused&&await this.applyPause(t.paused),"string"==typeof t?.claudeThinking&&(this.claudeThinkingSeen=t.claudeThinking)}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,H.getErrorMessage)(e)})}this.startPolling(),await this.startProjectSubsystems("start"),this.startMcpStaleCleanup(),this.projectId&&this.startParkController()}async startProjectSubsystems(e){if(this.projectId&&!this.projectSubsystemsStarted){this.projectSubsystemsStarted=!0,this.logger.info("Starting project subsystems",{source:e,projectId:this.projectId}),(0,Fe.setContainerProject)(this.projectId),this.startSSEEventForwarder()
5368
5383
  try{const{resumeInboxChatWatches:e}=await Promise.resolve().then(()=>t.t(86116,23))
5369
5384
  e({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient})}catch(e){this.logger.warn("inbox watch resume failed at start",{error:(0,H.getErrorMessage)(e)})}this.apiClient.post(`/api/projects/${this.projectId}/session-messages/expire-orphaned-approvals`,{}).then(e=>{const t=e?.data?.expired??0
5370
5385
  t>0&&this.logger.info("Ended approval cards left behind by the previous engine",{expired:t})}).catch(e=>{this.logger.warn("Could not end orphaned approval cards",{error:(0,H.getErrorMessage)(e)})}),this.startStuckSessionWatchdog(),this.startScheduleRunner(),(0,ge.ensureOpsDiagnostics)(this.apiClient,this.projectId,this.options.projectPath,this.logger),(0,ae.startSessionCompactor)({getOpenCodeUrl:()=>this.getOpenCodeUrl()??void 0,apiClient:this.apiClient,projectId:this.projectId||void 0,projectName:this.options.projectPath?O.default.basename(this.options.projectPath):void 0,logger:this.logger}),this.keepWarmRunner=(0,le.startKeepWarmRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,cachedDefaultModel:this.cachedDefaultModel,getDefaultModel:()=>this.cachedDefaultModel}),this.ownerGuidanceTimer=setInterval(()=>{this.checkOwnerGuidanceApply()},6e4),this.ownerGuidanceTimer.unref?.(),this.workMetricsRunner||(this.workMetricsRunner=(0,he.startWorkMetricsRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient})),this.mcpConnectionRunner||(this.mcpConnectionRunner=(0,pe.startMcpConnectionRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,onConfigChanged:()=>{this.requestConfigReload("mcp-connections")},isBusy:async()=>this.activeMcpCalls.size>0||await this.anySessionBusy()})),(0,ce.startMonitorRunner)({projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,cachedDefaultModel:this.cachedDefaultModel,serveReady:async()=>(await this.ensureServeAlive("monitor-wake"),null!=this.openCodePort&&!this.parkController?.isServeDown()),reportLiveness:e=>{this.apiClient.post("/api/liveness/report",{...e,project:this.options.projectPath?this.options.projectPath.split("/").pop():void 0}).catch(()=>{})},runOwn:e=>this.runOwn(e),verifyDeliveryEnv:async e=>{let r
@@ -5428,7 +5443,7 @@ for(const r of t?.data||t||[]){if(!(0,Y.isWaitDeclaringQuestion)(r))continue
5428
5443
  const t=r?.fromSessionId||r?.from_external_session_id
5429
5444
  "string"==typeof t&&t&&e.add(t)}const r=await this.apiClient.get(`/api/projects/${this.projectId}/monitors?status=armed`)
5430
5445
  for(const t of r?.data||[]){const r=t?.owner_external_session_id
5431
- "string"==typeof r&&r&&e.add(r)}}catch{}return e},reportLiveness:e=>{this.apiClient.post("/api/liveness/report",{...e,project:this.options.projectPath?this.options.projectPath.split("/").pop():void 0}).catch(()=>{})},reportFleetHealth:(e,t)=>{this.projectId&&this.apiClient.post(`/api/projects/${this.projectId}/session-messages/reports`,{fromSessionId:t.sessionId,status:"act"===t.attention?"blocked":"success",summary:e,...t.dedupKey?{details:`[dedup:${t.dedupKey}]`}:{},extraContent:{byMonitor:!0,...t.dedupKey?{dedupKey:t.dedupKey}:{}},...t.requestId?{requestId:t.requestId}:{},attention:t.attention}).catch(t=>{this.logger.warn("Fleet-health report did not land on the steer stream",{error:(0,H.getErrorMessage)(t),summary:e.slice(0,120)})})},closeStalledAsk:async({requestId:e,sessionId:t,why:r})=>{if(this.projectId)try{await this.apiClient.post(`/api/projects/${this.projectId}/requests/${e}/stalled-ask/resumed`,{sessionId:t,why:r})}catch(r){this.logger.warn("stalled-ask close after resume failed",{requestId:e,sessionId:t,error:(0,H.getErrorMessage)(r)})}},raiseStalledAsk:async({requestId:e,sessionId:t,detail:r,refusedModel:s,refusedPinned:n})=>{if(!this.projectId)return!1
5446
+ "string"==typeof r&&r&&e.add(r)}}catch{}return e},reportLiveness:e=>{this.apiClient.post("/api/liveness/report",{...e,project:this.options.projectPath?this.options.projectPath.split("/").pop():void 0}).catch(()=>{})},reportFleetHealth:(e,t)=>{this.projectId&&this.apiClient.post(`/api/projects/${this.projectId}/session-messages/reports`,{fromSessionId:t.sessionId,status:"act"===t.attention?"blocked":"success",summary:e,...t.details?{details:t.details}:t.dedupKey?{details:`[dedup:${t.dedupKey}]`}:{},extraContent:{byMonitor:!0,...t.dedupKey?{dedupKey:t.dedupKey}:{}},...t.requestId?{requestId:t.requestId}:{},attention:t.attention}).catch(t=>{this.logger.warn("Fleet-health report did not land on the steer stream",{error:(0,H.getErrorMessage)(t),summary:e.slice(0,120)})})},closeStalledAsk:async({requestId:e,sessionId:t,why:r})=>{if(this.projectId)try{await this.apiClient.post(`/api/projects/${this.projectId}/requests/${e}/stalled-ask/resumed`,{sessionId:t,why:r})}catch(r){this.logger.warn("stalled-ask close after resume failed",{requestId:e,sessionId:t,error:(0,H.getErrorMessage)(r)})}},raiseStalledAsk:async({requestId:e,sessionId:t,detail:r,refusedModel:s,refusedPinned:n})=>{if(!this.projectId)return!1
5432
5447
  try{const o=await this.apiClient.post(`/api/projects/${this.projectId}/requests/${e}/stalled-ask`,{sessionId:t,detail:r,...s?{refusedModel:s}:{},...n?{refusedPinned:!0}:{}}),i=o?.data??o
5433
5448
  return!0===i?.created||"string"==typeof i?.reason||null===i?.messageCode}catch(t){return this.logger.warn("stalled-ask mint failed; falling back to the plain report",{requestId:e,error:(0,H.getErrorMessage)(t)}),!1}},getStrandedCandidates:async()=>{if(!this.projectId)return[]
5434
5449
  try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/requests/stranded-candidates`),t=e?.data||e
@@ -5448,9 +5463,9 @@ if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"
5448
5463
  const s=await this.stuckSessionWatchdog.retrySession(r)
5449
5464
  s.success?t.json({success:!0}):t.status(400).json({success:!1,error:s.error})}async enterUpdateMode(){if(!this.updateBlocked){this.updateBlocked=!0,this.logger.error("Entering update-required mode \u2014 shutting down all services"),console.error("\n\x1b[31m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),console.error("\u2551 UPDATE REQUIRED \u2551"),console.error("\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\x1b[0m")
5450
5465
  for(const e of this.versionWarnings)console.error(`\x1b[31m ${e.message}\x1b[0m`)
5451
- console.error("\x1b[31m\n Functionality disabled. Update via UI or run:\n npm update -g alchemylab\x1b[0m\n"),this.parkController?.stop(),this.ownerGuidanceTimer&&clearInterval(this.ownerGuidanceTimer),this.setupRunReconcileTimer&&clearInterval(this.setupRunReconcileTimer),this.signInTimer&&clearInterval(this.signInTimer),await this.awaitInFlightRevival(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.scheduleRunner?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval)
5466
+ console.error("\x1b[31m\n Functionality disabled. Update via UI or run:\n npm update -g alchemylab\x1b[0m\n"),this.parkController?.stop(),this.ownerGuidanceTimer&&clearInterval(this.ownerGuidanceTimer),this.setupRunReconcileTimer&&clearInterval(this.setupRunReconcileTimer),this.hubRetryTimer&&clearTimeout(this.hubRetryTimer),this.signInTimer&&clearInterval(this.signInTimer),await this.awaitInFlightRevival(),this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.scheduleRunner?.stop(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval)
5452
5467
  for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.clear(),this.logger.info("Update-required mode active. HTTP server still running on port "+this.port)}}async stop(){this.logger.info("Stopping AlchemyActionServer..."),(0,o.markServerStopping)(),(0,S.clearInstanceLock)(this.options.projectPath),(0,Ze.closeExhibitPreviewBrowser)()
5453
- for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.previewTunnel?.close("shutting down"),await this.sweepUsage(this.startTime.getTime(),8e3),this.parkController?.stop(),this.ownerGuidanceTimer&&clearInterval(this.ownerGuidanceTimer),this.setupRunReconcileTimer&&clearInterval(this.setupRunReconcileTimer),this.signInTimer&&clearInterval(this.signInTimer),await this.awaitInFlightRevival()
5468
+ for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.previewTunnel?.close("shutting down"),await this.sweepUsage(this.startTime.getTime(),8e3),this.parkController?.stop(),this.ownerGuidanceTimer&&clearInterval(this.ownerGuidanceTimer),this.setupRunReconcileTimer&&clearInterval(this.setupRunReconcileTimer),this.hubRetryTimer&&clearTimeout(this.hubRetryTimer),this.signInTimer&&clearInterval(this.signInTimer),await this.awaitInFlightRevival()
5454
5469
  try{await Promise.race([(0,j.experimentRegistry)().stopAll(),new Promise(e=>setTimeout(e,15e3))])}catch(e){this.logger.warn("shutdown: boxes not all stopped",{error:(0,H.getErrorMessage)(e)})}this.killTrackedProcesses(),this.stuckSessionWatchdog?.stop(),this.scheduleRunner?.stop(),this.stopSSEEventForwarder(),this.mcpStaleCleanupInterval&&(clearInterval(this.mcpStaleCleanupInterval),this.mcpStaleCleanupInterval=void 0),this.stopPendingReloadRecheck()
5455
5470
  try{const e=O.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
5456
5471
  D.default.existsSync(e)&&D.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await Promise.resolve().then(()=>t(52640))
@@ -5791,30 +5806,30 @@ if("number"!=typeof t?.context)continue
5791
5806
  const o={context:t.context,..."number"==typeof t.input?{input:t.input}:{},..."number"==typeof t.output?{output:t.output}:{}}
5792
5807
  n.set(`${e.id}/${r}`,o),s?.id&&s.id!==r&&n.set(`${e.id}/${s.id}`,o)}}r.set(e,{at:Date.now(),value:n})}catch{}return n}
5793
5808
  const t=new Map,r=new Map},58836(e,exports,t){"use strict"
5794
- function r(e){return"alchemist"===e?1e5:6e4}exports.startSessionCompactor=function(e){let t,i,a=0
5795
- const c=r=>e.apiClient.post("/api/liveness/report",{name:"session-compactor",project:e.projectName,description:`Compacts the hub session idle past its provider's cache window (~60-90 min, per-provider heuristic) with >${Math.round(60)}k context -> summarize so reuse is cheap`,intervalMs:6e5,lastRunAt:(new Date).toISOString(),runs:a,lastAction:t,lastActionAt:i,...r}).catch(()=>{}),l=async()=>{if((0,o.isProjectPaused)())return
5796
- const l=Date.now()
5797
- a++
5809
+ function r(e,t){const r=e?.info??e??{}
5810
+ if((0,n.isCompactionSummary)(r))return{compact:!1,why:"already compacted"}
5811
+ const s=(0,n.contextTokensOf)(r)
5812
+ return s<("alchemist"===t.agentType?1e5:6e4)?{compact:!1,why:"small"}:r.time&&!r.time.completed?{compact:!1,why:"mid-turn"}:r.providerID&&r.modelID?t.idleMs<(0,n.cacheRetentionMsFor)(r.providerID)?{compact:!1,why:"cache plausibly still warm"}:{compact:!0,ctx:s}:{compact:!1,why:"no model"}}exports.startSessionCompactor=function(e){let t,n,i=0
5813
+ const a=r=>e.apiClient.post("/api/liveness/report",{name:"session-compactor",project:e.projectName,description:`Compacts the hub session idle past its provider's cache window (~60-90 min, per-provider heuristic) with >${Math.round(60)}k context -> summarize so reuse is cheap`,intervalMs:6e5,lastRunAt:(new Date).toISOString(),runs:i,lastAction:t,lastActionAt:n,...r}).catch(()=>{}),c=async()=>{if((0,o.isProjectPaused)())return
5814
+ const c=Date.now()
5815
+ i++
5798
5816
  try{const o=e.getOpenCodeUrl()
5799
- if(!o||!e.projectId)return void await c({lastDurationMs:Date.now()-l})
5800
- const a=await(0,s.readAllPages)(e.apiClient,t=>`/api/projects/${e.projectId}/cli-sessions?status=active&limit=100&offset=${t}`),d=new Map
5801
- for(const e of a){const t=e.externalSessionId||e.external_session_id,r=e.agentType||e.agent_type
5802
- t&&"alchemist"===r&&"active"===(e.status??"active")&&d.set(t,{title:e.title||t,agentType:r})}if(0===d.size)return void await c({lastDurationMs:Date.now()-l})
5803
- const u=await fetch(`${o}/session?limit=1000`,{signal:AbortSignal.timeout(1e4)}),h=await u.json().catch(()=>[]),p=Date.now()
5804
- for(const s of Array.isArray(h)?h:[]){const a=d.get(s.id)
5805
- if(!a)continue
5806
- const c=a.title,l=s.time?.updated??s.time?.created??0
5807
- if(p-l<27e5)continue
5808
- let u
5817
+ if(!o||!e.projectId)return void await a({lastDurationMs:Date.now()-c})
5818
+ const i=await(0,s.readAllPages)(e.apiClient,t=>`/api/projects/${e.projectId}/cli-sessions?status=active&limit=100&offset=${t}`),l=new Map
5819
+ for(const e of i){const t=e.externalSessionId||e.external_session_id,r=e.agentType||e.agent_type
5820
+ t&&"alchemist"===r&&"active"===(e.status??"active")&&l.set(t,{title:e.title||t,agentType:r})}if(0===l.size)return void await a({lastDurationMs:Date.now()-c})
5821
+ const d=await fetch(`${o}/session?limit=1000`,{signal:AbortSignal.timeout(1e4)}),u=await d.json().catch(()=>[]),h=Date.now()
5822
+ for(const s of Array.isArray(u)?u:[]){const i=l.get(s.id)
5823
+ if(!i)continue
5824
+ const a=i.title,c=s.time?.updated??s.time?.created??0
5825
+ if(h-c<27e5)continue
5826
+ let d
5809
5827
  try{const e=await fetch(`${o}/session/${s.id}/message?limit=1`,{signal:AbortSignal.timeout(1e4)}),t=await e.json()
5810
- u=Array.isArray(t)?t[t.length-1]:void 0}catch{continue}const h=u?.info??u??{},f=(0,n.contextTokensOf)(h)
5811
- if(f<r(a.agentType))continue
5812
- if(h.time&&!h.time.completed)continue
5813
- if(!h.providerID||!h.modelID)continue
5814
- if(p-l<(0,n.cacheRetentionMsFor)(h.providerID))continue
5815
- const m=await fetch(`${o}/session/${s.id}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({providerID:h.providerID,modelID:h.modelID}),signal:AbortSignal.timeout(12e4)})
5816
- m.ok?(t=`compacted ${a.agentType} ${c} (${Math.round(f/1e3)}k ctx, idle ${Math.round((p-l)/6e4)}m)`,i=(new Date).toISOString(),e.logger.info("Session compactor: compacted an idle session",{sessionId:s.id,title:c,ctx:f})):e.logger.warn("Session compactor: summarize failed",{sessionId:s.id,status:m.status})}await c({lastDurationMs:Date.now()-l})}catch(e){await c({lastError:String(e).slice(0,200),lastDurationMs:Date.now()-l})}}
5817
- setInterval(l,6e5),l()}
5828
+ d=Array.isArray(t)?t[t.length-1]:void 0}catch{continue}const u=d?.info??d??{},p=r(d,{agentType:i.agentType,idleMs:h-c})
5829
+ if(!p.compact)continue
5830
+ const f=p.ctx,m=await fetch(`${o}/session/${s.id}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({providerID:u.providerID,modelID:u.modelID}),signal:AbortSignal.timeout(12e4)})
5831
+ m.ok?(t=`compacted ${i.agentType} ${a} (${Math.round(f/1e3)}k ctx, idle ${Math.round((h-c)/6e4)}m)`,n=(new Date).toISOString(),e.logger.info("Session compactor: compacted an idle session",{sessionId:s.id,title:a,ctx:f})):e.logger.warn("Session compactor: summarize failed",{sessionId:s.id,status:m.status})}await a({lastDurationMs:Date.now()-c})}catch(e){await a({lastError:String(e).slice(0,200),lastDurationMs:Date.now()-c})}}
5832
+ setInterval(c,6e5),c()}
5818
5833
  const s=t(5641),n=t(89063),o=t(36799)},67078(e,exports){"use strict"
5819
5834
  function t(e,t){let s=r.get(e)
5820
5835
  return s||(s=new Set,r.set(e,s)),s.add(t),()=>{const s=r.get(e)
@@ -5968,14 +5983,15 @@ function t(e,t){if((t.parts??[]).some(e=>"tool"===e.type&&"error"===e.state?.sta
5968
5983
  if("MessageAbortedError"===e?.name)return{by:"stopped"}
5969
5984
  if("APIError"===e?.name){const t=e.data?.statusCode
5970
5985
  return{by:"provider",..."number"==typeof t?{status:t}:{}}}return{by:"unknown"}}function r(e,t){const r=e?.data??{},s="number"==typeof r.statusCode?r.statusCode:void 0,n=("string"==typeof r.message&&r.message.trim()?r.message.trim():String(e?.name??"an unknown error")).replace(/\s+/g," ").slice(0,300),o=t?.providerID||("string"==typeof r.providerID?r.providerID:void 0),i=t?.modelID||("string"==typeof r.modelID?r.modelID:void 0)
5971
- return{message:n,...o&&i?{model:`${o}/${i}`}:{},...o?{providerID:o}:{},scope:"ProviderAuthError"===e?.name||401===s||403===s?"provider":"model"}}function s(e){return(e.parts??[]).filter(e=>"text"===e.type&&"string"==typeof e.text).map(e=>String(e.text)).join(" ").trim()}function n(e){return(e.parts??[]).some(e=>"tool"===e.type||"text"===e.type&&String(e.text??"").trim().length>0)}function o(e){if(!e||!e.name)return!1
5986
+ return{message:n,...o&&i?{model:`${o}/${i}`}:{},...o?{providerID:o}:{},scope:"ProviderAuthError"===e?.name||401===s||403===s?"provider":"model",..."repeated-tool-failure"===r.metadata?.alchemyStop?{looped:!0}:{}}}function s(e){return(e.parts??[]).filter(e=>"text"===e.type&&"string"==typeof e.text).map(e=>String(e.text)).join(" ").trim()}function n(e){return(e.parts??[]).some(e=>"tool"===e.type||"text"===e.type&&String(e.text??"").trim().length>0)}function o(e){if(!e||!e.name)return!1
5972
5987
  if("ProviderQuotaError"===e.name)return!0
5973
5988
  const t=e.data??{},r="number"==typeof t.statusCode?t.statusCode:void 0,s=`${"string"==typeof t.message?t.message:""} ${"string"==typeof t.responseBody?t.responseBody:""}`
5974
- return"ProviderAuthError"===e.name||"ProviderModelNotFoundError"===e.name||401===r||403===r||!1===t.isRetryable||/revoked|authentication_error|invalid[_ ]api[_ ]key|unauthorized|model ?not ?found/i.test(s)}function i(e){return"user"===e.info.role&&!!e.parts?.length&&e.parts.every(e=>!0===e.metadata?.alchemy?.noReply)}exports.yX=void 0,exports.turnRefusal=r,exports.refusalLifted=function(e,t){if(!e||!t||!t.includes("/"))return null
5989
+ return"ProviderAuthError"===e.name||"ProviderModelNotFoundError"===e.name||401===r||403===r||!1===t.isRetryable||/revoked|authentication_error|invalid[_ ]api[_ ]key|unauthorized|model ?not ?found/i.test(s)}function i(e){return"user"===e.info.role&&!!e.parts?.length&&e.parts.every(e=>!0===e.metadata?.alchemy?.noReply)}exports.yX=exports.i0=void 0,exports.turnRefusal=r,exports.refusalLifted=function(e,t){if(!e||!t||!t.includes("/"))return null
5975
5990
  const r=t.trim().toLowerCase()
5976
5991
  if("provider"===e.scope){const t=(e.providerID??e.model?.split("/")[0]??"").trim().toLowerCase()
5977
5992
  return t&&r.split("/")[0]!==t?"model-switched":null}return e.model&&r!==e.model.trim().toLowerCase()?"model-switched":null},exports.quotaBlockLifted=function(e,t,r){if(!e)return null
5978
5993
  if(e.resetsAt&&1e3*e.resetsAt<r)return"window-passed"
5994
+ if(!e.resetsAt&&e.observedAt&&r-e.observedAt>exports.i0)return"window-passed"
5979
5995
  const s=(t??"").includes("/")?t.split("/")[0].trim().toLowerCase():"",n=(e.providerID??"").trim().toLowerCase()
5980
5996
  return s&&n&&s!==n?"provider-switched":null},exports.isFatalTurnError=o,exports.isQuietDelivery=i,exports.assembleTurnEvidence=function(e,a){let d=e.length
5981
5997
  for(;d>0&&i(e[d-1]);)d--
@@ -5987,7 +6003,7 @@ if("assistant"!==h.info.role)return{kind:"no-messages"}
5987
6003
  const f=h.info,m=f.error
5988
6004
  if(!f.time.completed)return p(h)?{kind:"generating",messageId:f.id}:{kind:"unfinished",messageId:f.id,finish:f.finish}
5989
6005
  if(m&&"object"==typeof m&&m.name){if("ProviderQuotaError"===m.name){const e=m.data??{}
5990
- return{kind:"quota",messageId:f.id,errorName:m.name,quota:{providerID:"string"==typeof e.providerID&&e.providerID?e.providerID:"the provider",resetsAt:"number"==typeof e.resetsAt?e.resetsAt:void 0,planType:"string"==typeof e.planType?e.planType:void 0,message:"string"==typeof e.message&&e.message?e.message:"usage limit reached"}}}return o(m)?{kind:"fatal-error",messageId:f.id,errorName:m.name,finish:f.finish,refusal:r(m,f)}:{kind:"retryable-error",messageId:f.id,errorName:m.name,finish:f.finish,cause:t(m,h)}}const g=f.finish,w=n(h)
6006
+ return{kind:"quota",messageId:f.id,errorName:m.name,quota:{providerID:"string"==typeof e.providerID&&e.providerID?e.providerID:"the provider",resetsAt:"number"==typeof e.resetsAt?e.resetsAt:void 0,observedAt:f.time.completed,planType:"string"==typeof e.planType?e.planType:void 0,message:"string"==typeof e.message&&e.message?e.message:"usage limit reached"}}}return o(m)?{kind:"fatal-error",messageId:f.id,errorName:m.name,finish:f.finish,refusal:r(m,f)}:{kind:"retryable-error",messageId:f.id,errorName:m.name,finish:f.finish,cause:t(m,h)}}const g=f.finish,w=n(h)
5991
6007
  if(g&&l.has(g)&&!w)return{kind:"dead-end",messageId:f.id,finish:g,providerID:f.providerID,modelID:f.modelID}
5992
6008
  if("tool-calls"===g)return{kind:"interrupted-toolchain",messageId:f.id,finish:g}
5993
6009
  const y=s(h)
@@ -6007,7 +6023,7 @@ case"unfinished":case"interrupted-toolchain":case"lost-prompt":return"hub"===r?{
6007
6023
  case"clean-text":return"owned"===r?{act:"unconfuse-then-report"}:{act:"none"}
6008
6024
  default:return{act:"none"}}},exports.retryLooksLikeRefusal=function(e){return!(!e||"retry"!==e.type)&&!((e.attempt??0)<exports.yX)&&d.test(String(e.message??""))}
6009
6025
  const a=/MCP error -32000|\bConnection closed\b|\bNot connected\b/i,c=300,l=new Set(["content-filter","length","error","other","unknown"])
6010
- exports.yX=3
6026
+ exports.i0=6e5,exports.yX=3
6011
6027
  const d=/\b(exceeded|exhausted|quota|out of (credit|credits)|insufficient|subscribe|upgrade your plan|billing)\b/i},90473(e,exports,t){"use strict"
6012
6028
  function r(e,t){const r=(0,a.readActionServerFile)(e)
6013
6029
  return r.port===t&&r.secret?{[a.LOCAL_SECRET_HEADER]:r.secret}:{}}async function s(e){const{port:t,projectPath:s,logger:n,pollMs:o=500,pollAttempts:i=16}=e,a=`http://127.0.0.1:${t}`
@@ -6733,23 +6749,35 @@ else{const r=await(0,i.resolveDefaultBranch)(e)
6733
6749
  await u.raw(["worktree","add","-b",c,d,r]),a.info("Pre-spawn: created worktree with new branch from latest main",{requestId:t,branchName:c,mainBranch:r,worktreePath:d})}}catch(e){const r=e instanceof Error?e.message:String(e)
6734
6750
  a.warn("Pre-spawn: failed to create worktree (MCP process will retry)",{requestId:t,error:r})}}
6735
6751
  const r=t(79896),s=t(16928),n=t(83807),o=t(7202),i=t(52232)},35419(e,exports,t){"use strict"
6736
- function r(e){return(0,o.join)(e,i.ALCHEMY_DIR_NAME,"opencode-config.json")}function s(e){return(0,o.join)((0,n.homedir)(),i.ALCHEMY_DIR_NAME,e?"opencode-dev":"opencode")}exports.t=void 0,exports.generatedOpencodeConfigPath=r,exports.getGlobalConfigDir=s,exports.getOpencodeSpawnEnv=function(e,t){return{ANTHROPIC_CLI_VERSION:process.env.ANTHROPIC_CLI_VERSION??exports.t,OPENCODE_CLIENT:"alchemy",OPENCODE_ENABLE_EXA:"1",OPENCODE_DATA_DIR:(0,o.join)(e,i.ALCHEMY_DIR_NAME,"opencode"),OPENCODE_CONFIG_DIR:s(t),ALCHEMY_PROTECTED_ROOT:e,OPENCODE_CONFIG:r(e),OPENCODE_DISABLE_PROJECT_CONFIG:"1",OPENCODE_DISABLE_CLAUDE_CODE:"1",OPENCODE_DISABLE_EXTERNAL_SKILLS:"1"}}
6752
+ function r(e){return(0,o.join)(e,i.ALCHEMY_DIR_NAME,"opencode-config.json")}function s(e){return(0,o.join)((0,n.homedir)(),i.ALCHEMY_DIR_NAME,e?"opencode-dev":"opencode")}exports.t=void 0,exports.generatedOpencodeConfigPath=r,exports.getGlobalConfigDir=s,exports.getOpencodeSpawnEnv=function(e,t){const n=process.env.ALCHEMYLAB_ANTHROPIC_COMPACTION
6753
+ return{...void 0===n?{}:{ALCHEMYLAB_ANTHROPIC_COMPACTION:n},ANTHROPIC_CLI_VERSION:process.env.ANTHROPIC_CLI_VERSION??exports.t,OPENCODE_CLIENT:"cli",ALCHEMYLAB:"1",OPENCODE_ENABLE_EXA:"1",OPENCODE_DATA_DIR:(0,o.join)(e,i.ALCHEMY_DIR_NAME,"opencode"),OPENCODE_CONFIG_DIR:s(t),ALCHEMY_PROTECTED_ROOT:e,OPENCODE_CONFIG:r(e),OPENCODE_DISABLE_PROJECT_CONFIG:"1",OPENCODE_DISABLE_CLAUDE_CODE:"1",OPENCODE_DISABLE_EXTERNAL_SKILLS:"1"}}
6737
6754
  const n=t(70857),o=t(16928),i=t(14139)
6738
6755
  exports.t="2.1.280"},39068(e,exports,t){"use strict"
6739
6756
  function r(){const e=process.env.ALCHEMY_OPENCODE_DIR
6740
6757
  if(e)return e
6741
6758
  try{const e=("undefined"!=typeof require?require:t(54245)).resolve("alchemylab-opencode/package.json")
6742
- return(0,n.dirname)(e)}catch{}}function s(e,t){return{command:function(){if(c)return c
6743
- try{const e=(0,i.execFileSync)("node",["--version"],{encoding:"utf8"}).trim()
6744
- if((0,a.isSupportedNode)(e))return c="node",c}catch{}const e=process.env.NVM_DIR||(0,n.join)(process.env.HOME||"",".nvm"),t=(0,n.join)(e,"versions","node")
6745
- if((0,o.existsSync)(t))try{const e=(0,o.readdirSync)(t).filter(e=>(0,a.isSupportedNode)(e)).sort((e,t)=>{const r=e.replace("v","").split(".").map(Number),s=t.replace("v","").split(".").map(Number)
6759
+ return(0,a.dirname)(e)}catch{}}function s(){if(u)return u
6760
+ try{const e=(0,l.execFileSync)("node",["--version"],{encoding:"utf8"}).trim()
6761
+ if((0,d.isSupportedNode)(e))return u="node",u}catch{}const e=process.env.NVM_DIR||(0,a.join)(process.env.HOME||"",".nvm"),t=(0,a.join)(e,"versions","node")
6762
+ if((0,c.existsSync)(t))try{const e=(0,c.readdirSync)(t).filter(e=>(0,d.isSupportedNode)(e)).sort((e,t)=>{const r=e.replace("v","").split(".").map(Number),s=t.replace("v","").split(".").map(Number)
6746
6763
  return s[0]-r[0]||s[1]-r[1]||s[2]-r[2]})
6747
- if(e.length>0){const r=(0,n.join)(t,e[0],"bin","node")
6748
- if((0,o.existsSync)(r))return c=r,c}}catch{}throw new Error((0,a.unsupportedNodeMessage)(process.version))}(),prependArgs:["--no-warnings",(0,n.join)(e,t)]}}exports.findOpencodeDir=r,exports.resolveServeCommand=function(){const e=r()
6749
- return e?s((0,n.join)(e,"dist"),"serve.js"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=r()
6750
- return e?s((0,n.join)(e,"dist"),"run.js"):{command:"opencode",prependArgs:[]}}
6751
- const n=t(16928),o=t(79896),i=t(35317),a=t(84973)
6752
- let c},69706(e,exports,t){"use strict"
6764
+ if(e.length>0){const r=(0,a.join)(t,e[0],"bin","node")
6765
+ if((0,c.existsSync)(r))return u=r,u}}catch{}throw new Error((0,d.unsupportedNodeMessage)(process.version))}function n(e,t){return{command:s(),prependArgs:["--no-warnings",(0,a.join)(e,t)]}}function o(e,t,r){const s=(0,l.spawnSync)(e,[t],{cwd:r,encoding:"utf8",stdio:["ignore","pipe","pipe"],timeout:12e4}),n=`${s.stdout??""}${s.stderr??""}`
6766
+ if(s.error||0!==s.status)throw Object.assign(new Error(s.error?.message??`exit ${s.status}`),{stderr:n})
6767
+ return n}function i(e,t=o){if(!e||process.env.ALCHEMY_OPENCODE_DIR)return"not-needed"
6768
+ if(h?.dir===e)return h.state
6769
+ const r=(0,a.join)(e,"postinstall.cjs"),n=(t,r="")=>(h={dir:e,state:t,detail:r},t)
6770
+ if(!(0,c.existsSync)(r)||!(0,c.existsSync)((0,a.join)(e,"patches")))return n("not-needed")
6771
+ let i=""
6772
+ try{i=String(JSON.parse((0,c.readFileSync)((0,a.join)(e,"package.json"),"utf8")).version??"")}catch{}const l=(0,a.join)(e,exports.BY)
6773
+ try{if(i&&(0,c.readFileSync)(l,"utf8")===i)return n("done")}catch{}try{const o=t(s(),r,e)
6774
+ return i&&(0,c.writeFileSync)(l,i),n("applied",String(o??"").trim())}catch(t){const r=String(t?.stderr||t?.message||t).trim().split("\n").slice(-3).join(" ")
6775
+ return process.stderr.write(`AlchemyLab could not apply its engine's dependency patches in ${e} (${r}); the engine starts without them.\n`),n("failed",r)}}exports.BY=void 0,exports.findOpencodeDir=r,exports.lastEnginePatchState=function(){return h},exports.resolveServeCommand=function(){const e=r()
6776
+ return e?(i(e),n((0,a.join)(e,"dist"),"serve.js")):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=r()
6777
+ return e?(i(e),n((0,a.join)(e,"dist"),"run.js")):{command:"opencode",prependArgs:[]}}
6778
+ const a=t(16928),c=t(79896),l=t(35317),d=t(84973)
6779
+ let u,h
6780
+ exports.BY=".alchemylab-patches-applied"},69706(e,exports,t){"use strict"
6753
6781
  function r(e){try{const t=JSON.parse(a.default.readFileSync(c.default.join(e,l),"utf8"))
6754
6782
  return"string"==typeof t.password&&t.password?t.password:null}catch{return null}}function s(e){const t=process.env.OPENCODE_SERVER_USERNAME||exports.SERVE_USERNAME
6755
6783
  return`Basic ${Buffer.from(`${t}:${e}`).toString("base64")}`}function n(e){if(!d.port)return!1
@@ -6850,13 +6878,13 @@ else if("failed"===u.status||!u.hasHistory)throw function(e){const t=(0,c.join)(
6850
6878
  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."])
6851
6879
  const h=(0,c.join)(e,g.ALCHEMY_DIR_NAME)
6852
6880
  let p
6853
- 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")
6881
+ await(0,a.mkdir)(h,{recursive:!0}),o?(d(`\u{1f4cb} Adding "${(0,c.basename)(e)||"this folder"}" to your AlchemyLab account...`),p=await n(t,o,e,l),d(p?"\u2705 Project added.":s(r()))):d("\u26a0\ufe0f No auth token, skipping project registration")
6854
6882
  const f=(0,c.join)(h,"project.json")
6855
6883
  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)
6856
6884
  let S
6857
6885
  try{const e=JSON.parse((0,i.readFileSync)(m,"utf-8"))
6858
6886
  if(S=e.id||void 0,!1===e.registeredRemotely&&d){const a=await n(o,d,t,u,"cli-init-retry")
6859
- 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)}})
6887
+ 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 added.")):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)}})
6860
6888
  if(!s.ok)throw new Error(`HTTP ${s.status}`)
6861
6889
  return"GET"===e?s.json():void 0},$=d&&S?async e=>{const t=await I("GET",`/api/projects/${S}/requests/${e}`)
6862
6890
  return(t?.data??t)?.type}:void 0,j=await(0,y.defaultBranchHasCode)(t,$).catch(()=>!1)
@@ -6885,7 +6913,8 @@ if(!Number.isFinite(r)||r<=0)return 0
6885
6913
  let s=""
6886
6914
  try{s=new URL(e).hostname}catch{return 0}return"localhost"===s||"127.0.0.1"===s||"::1"===s?Math.min(r,2e3):0}function s(e,t,r){if(r&&"stores"===r.op)try{return{ok:!0,result:{projects:e.projects()}}}catch(e){return{ok:!1,error:e instanceof Error?e.message.slice(0,200):"the content store failed"}}if(r&&"drop"===r.op&&"string"==typeof r.projectId)try{return{ok:!0,result:{removed:e.drop(r.projectId)}}}catch(e){return{ok:!1,error:e instanceof Error?e.message.slice(0,200):"the content store failed"}}if(r&&"put"===r.op&&"string"==typeof r.projectId)try{const t=r.args??{}
6887
6915
  return{ok:!0,result:{ids:e.put(r.projectId,Array.isArray(t.items)?t.items:[])}}}catch(e){return{ok:!1,error:e instanceof Error?e.message.slice(0,200):"the content store failed"}}if(r&&"has"===r.op&&"string"==typeof r.projectId)try{const t=r.args??{}
6888
- return{ok:!0,result:{ids:e.hasMany(r.projectId,Array.isArray(t.ids)?t.ids.map(String):[])}}}catch(e){return{ok:!1,error:e instanceof Error?e.message.slice(0,200):"the content store failed"}}if(!r||"string"!=typeof r.projectId||!t.has(r.projectId)&&!e.has(r.projectId))return{ok:!1,error:"this computer keeps no words for that project"}
6916
+ return{ok:!0,result:{ids:e.hasMany(r.projectId,Array.isArray(t.ids)?t.ids.map(String):[])}}}catch(e){return{ok:!1,error:e instanceof Error?e.message.slice(0,200):"the content store failed"}}if(r&&"getAcross"===r.op)try{const s=r.args?.projects&&"object"==typeof r.args.projects?r.args.projects:{},n={}
6917
+ for(const[r,o]of Object.entries(s))if(t.has(r)||e.has(r))try{n[r]=e.getMany(r,Array.isArray(o)?o.map(String):[])}catch{}return{ok:!0,result:{items:n}}}catch(e){return{ok:!1,error:e instanceof Error?e.message.slice(0,200):"the content store failed"}}if(!r||"string"!=typeof r.projectId||!t.has(r.projectId)&&!e.has(r.projectId))return{ok:!1,error:"this computer keeps no words for that project"}
6889
6918
  try{const t=r.args??{}
6890
6919
  switch(r.op){case"put":return{ok:!0,result:{ids:e.put(r.projectId,Array.isArray(t.items)?t.items:[])}}
6891
6920
  case"getMany":return{ok:!0,result:{items:e.getMany(r.projectId,Array.isArray(t.ids)?t.ids.map(String):[])}}
@@ -7392,132 +7421,138 @@ if(!o.ok)throw new Error(`HTTP ${o.status}`)
7392
7421
  const i=await o.json(),a=Array.isArray(i?.data)?i.data:Array.isArray(i)?i:[]
7393
7422
  for(const e of a){const t=e?.metadata?.schedule
7394
7423
  t&&"object"==typeof t&&!0===t.enabled&&r.push(t)}if(a.length<100)break}return r}}},86090(e,exports,t){"use strict"
7395
- function r(){return b.default.resolve(process.argv[1])}function s(e){const t=(0,A.servicePaths)(e),r=(0,C.readServiceConfig)(t)
7424
+ function r(){return S.default.resolve(process.argv[1])}function s(e){const t=(0,_.servicePaths)(e),r=(0,T.readServiceConfig)(t)
7396
7425
  return r?{paths:t,cfg:r}:void 0}async function n(e,t){if(!process.stdin.isTTY)return t
7397
- const r=k.default.createInterface({input:process.stdin,output:process.stdout}),s=await new Promise(s=>r.question(`${e} ${t?"[Y/n]":"[y/N]"} `,s))
7426
+ const r=I.default.createInterface({input:process.stdin,output:process.stdout}),s=await new Promise(s=>r.question(`${e} ${t?"[Y/n]":"[y/N]"} `,s))
7398
7427
  r.close()
7399
7428
  const n=s.trim().toLowerCase()
7400
7429
  return""===n?t:"y"===n||"yes"===n}async function o(e,t){const s={}
7401
7430
  process.env.ALCHEMY_LAB_HOME&&(s.ALCHEMY_LAB_HOME=process.env.ALCHEMY_LAB_HOME)
7402
- 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})
7403
- 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
7404
- 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}`)
7431
+ try{const n=await(0,H.installLoginItem)({nodePath:process.execPath,cliPath:r(),remoteServerUrl:t.remoteServerUrl,envPath:process.env.PATH??"/usr/bin:/bin",env:s,logPath:e.log})
7432
+ 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,R.isServiceRunning)(e)||(console.log("Starting the AlchemyLab service..."),await(0,R.startServiceInBackground)(e,t,r())||(console.error(a(e.log)),process.exit(1)))}function a(e){const t=c(e)
7433
+ return`\u274c The AlchemyLab service did not start${t?`: ${t}`:"."}\n Its log: ${e}`}function c(e){let t=""
7434
+ try{const r=k.default.openSync(e,"r")
7435
+ try{const e=k.default.fstatSync(r).size,s=Math.min(e,16384),n=Buffer.alloc(s)
7436
+ k.default.readSync(r,n,0,s,e-s),t=n.toString("utf8")}finally{k.default.closeSync(r)}}catch{return""}const r=t.split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("at "))
7437
+ for(let e=r.length-1;e>=0;e--){const t=/Error:\s*(.+)$/.exec(r[e])
7438
+ if(t)return t[1].replace(/^Error:\s*/,"").slice(0,300)}return""}function l(e){return e.state}async function d(e){let t,r
7439
+ try{r=await(0,R.request)(e,"ping"),t=await(0,R.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}`)
7405
7440
  const s=t.projects??[]
7406
7441
  if(0===s.length)return void console.log(" No projects on this computer yet. Run `alchemy` in a folder to add it.")
7407
7442
  const n=Math.max(8,...s.map(e=>e.name.length))+2
7408
7443
  console.log(` ${"PROJECT".padEnd(n)}${"STATE".padEnd(10)}${"PORT".padEnd(7)}FOLDER`)
7409
- 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}`)
7410
- 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."}`)
7411
- 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)
7444
+ for(const e of s)console.log(` ${e.name.padEnd(n)}${l(e).padEnd(10)}${String(e.port??"").padEnd(7)}${e.path}`),e.reason&&console.log(` ${"".padEnd(n)}${e.reason}`)
7445
+ 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 u(e){console.error(`\u274c ${e.reason??"It did not start."}`)
7446
+ for(const t of(e.lines??[]).slice(-8))console.error(` ${t}`)}function h(e){const t=async(t,r={},s=12e4)=>{const n=await(0,R.request)(e,"new-computer",{op:t,...r},s)
7412
7447
  if(!n.ok)throw new Error(n.reason??"The AlchemyLab service did not do it.")
7413
7448
  return n}
7414
7449
  return{candidates:async()=>{const e=await t("candidates",{},4e4)
7415
7450
  return{candidates:e.candidates??[],here:e.here??[]}},backups:async(e,r)=>{const s=await t("backups",{folder:e,from:r})
7416
- return{folder:s.folder,runs:s.runs??[],others:Number(s.others)||0}},restore:async(e,r)=>(await t("restore",{folder:e,run:r},18e5)).results??[],preview:async e=>(await t("preview",{from:e},3e5)).preview,move:async(e,r)=>(await t("move",{from:e,withoutWords:r},9e5)).result,forget:async e=>{await t("forget",{from:e},4e4)}}}function u(){const e=k.default.createInterface({input:process.stdin,output:process.stdout})
7417
- return{say:(e="")=>console.log(e),ask:t=>new Promise(r=>e.question(`${t} `,e=>r(e.trim()))),close:()=>e.close()}}var h=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
7418
- 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)
7419
- const s=(0,C.readServiceConfig)(t)
7451
+ return{folder:s.folder,runs:s.runs??[],others:Number(s.others)||0}},restore:async(e,r)=>(await t("restore",{folder:e,run:r},18e5)).results??[],preview:async e=>(await t("preview",{from:e},3e5)).preview,move:async(e,r)=>(await t("move",{from:e,withoutWords:r},9e5)).result,forget:async e=>{await t("forget",{from:e},4e4)}}}function p(){const e=I.default.createInterface({input:process.stdin,output:process.stdout})
7452
+ return{say:(e="")=>console.log(e),ask:t=>new Promise(r=>e.question(`${t} `,e=>r(e.trim()))),close:()=>e.close()}}var f=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
7453
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.serviceSetUp=s,exports.runService=async function(e,t,s={}){if(t.includes("--foreground"))return async function(e){const t=(0,_.servicePaths)(e);(0,_.ensurePrivateDir)(t.dir)
7454
+ const s=(0,T.readServiceConfig)(t)
7420
7455
  s||(console.error("The AlchemyLab service is not set up for this server yet. Run `alchemy service` in a terminal first."),process.exit(1))
7421
- 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()
7456
+ const n=(0,$.createLogger)({serviceName:"alchemy-service",console:!1,logToFile:!0,logFilePath:t.log,baseDir:t.dir}),o=new M.Supervisor({paths:t,config:s,logger:n,childModule:r(),childArgs:e=>["project-run","--project",e.path,"--expect",`${e.dev}:${e.ino}`],childEnv:()=>({...(0,T.childEnv)((0,T.readServiceConfig)(t)??s),ALCHEMY_MACHINE_ID:(0,m.machineIdentity)(t).id}),approvalEnv:{installDir:G},version:(0,A.ownVersion)()??void 0,onChange:()=>u?.noteChanged(),cap:()=>(0,T.readServiceConfig)(t)?.cap??(0,M.defaultCap)(),machineId:(0,m.machineIdentity)(t).id,confirmIdentity:async()=>(await c).register(),signedOut:()=>!0===u?.signedOut,startAsNew:(0,F.startAsNewProject)({remoteServerUrl:s.remoteServerUrl,machineId:(0,m.machineIdentity)(t).id,getToken:async()=>(await v.getToken())?.token}),connected:()=>i()}),i=async()=>{v.forgetCachedSignIn()
7422
7457
  const e=(e,t)=>Promise.race([e,new Promise(e=>{const r=setTimeout(()=>e(!1),t)
7423
7458
  r.unref?.()})]),t=await e(u?u.register(1e4):Promise.resolve(!1),12e3),r=await e(h?h.connectNow():Promise.resolve(!1),12e3)
7424
7459
  return!0===t&&!0===r}
7425
7460
  let a=()=>{}
7426
7461
  const c=new Promise(e=>{a=e})
7427
- let l,d,u,h,g,w
7428
- const v=async(e=0)=>{l?.(),await(u?.stop().catch(()=>{})),h?.stop(),w?.stop(),g?.stop(),d?.close(),await o.stop(),process.exit(e)}
7429
- process.on("SIGTERM",()=>{v(0)}),process.on("SIGINT",()=>{v(0)})
7430
- 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})
7431
- 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()
7432
- const $=async e=>{const r=h?await h.setPrivateData(e):{privateData:e,switching:!1},s=(0,C.readServiceConfig)(t)
7433
- 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}
7434
- o.handle("connect-now",async()=>({ok:await i()})),o.handle("move-words-here",async()=>{const e=await $(!0)
7462
+ let l,d,u,h,p,f
7463
+ const y=async(e=0)=>{l?.(),await(u?.stop().catch(()=>{})),h?.stop(),f?.stop(),p?.stop(),d?.close(),await o.stop(),process.exit(e)}
7464
+ process.on("SIGTERM",()=>{y(0)}),process.on("SIGINT",()=>{y(0)})
7465
+ try{await o.start(()=>{y(0)})}catch(e){throw e instanceof M.AlreadyRunningError&&(console.error(e.message),process.exit(0)),e}const v=new j.TokenManager(n,t.dir,{remoteServerUrl:s.remoteServerUrl,allowInteractiveAuth:!1})
7466
+ await v.initialize(),l=(0,P.startSignInWatchdog)(v,e=>n.info(e)),u=new N.MachineLink({paths:t,remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await v.getToken())?.token,projects:()=>o.status(),wake:(e,t)=>o.wake(e,t),archived:e=>o.setArchived(e),answered:(0,F.onListAnswered)({supervisor:o,remoteServerUrl:s.remoteServerUrl,machineId:(0,m.machineIdentity)(t).id,getToken:async()=>(await v.getToken())?.token,logger:n}),version:(0,A.ownVersion)()??void 0,create:e=>{x.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 g.ContentLink({paths:t,remoteServerUrl:s.remoteServerUrl,privateData:!0===(0,T.readServiceConfig)(t)?.privateData,getToken:async()=>(await v.getToken())?.token,projectIds:()=>o.status().map(e=>e.projectId).filter(e=>"string"==typeof e),logger:n}),h.start(),p=new b.WordsBackup({store:h.store,stateFile:S.default.join(t.dir,"words-backup.json"),settings:()=>(0,b.backupSettingsOf)((0,T.readServiceConfig)(t)?.wordsBackup),privateData:()=>h?.privateData??!0===(0,T.readServiceConfig)(t)?.privateData,logger:n}),p.start()
7467
+ const k=async e=>{const r=h?await h.setPrivateData(e):{privateData:e,switching:!1},s=(0,T.readServiceConfig)(t)
7468
+ return s&&s.privateData!==r.privateData&&(0,T.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}
7469
+ o.handle("connect-now",async()=>({ok:await i()})),o.handle("move-words-here",async()=>{const e=await k(!0)
7435
7470
  return{ok:!e.refused,reason:e.refused,privateData:e.privateData,switching:e.switching}})
7436
- 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})
7437
- let _
7438
- 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{}})
7439
- 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)
7440
- if(!r)throw new Error("the service has no configuration");(0,C.writeServiceConfig)(t,{...r,wordsBackup:{...r.wordsBackup??{},...e}})}
7441
- d=await(0,O.startDoor)({supervisor:o,adding:R,wordsBackup:{view:()=>{const e=(0,y.backupSettingsOf)((0,C.readServiceConfig)(t)?.wordsBackup)
7442
- 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)")
7443
- 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
7471
+ const I=new w.NewComputer({machineId:(0,m.machineIdentity)(t).id,remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await v.getToken())?.token,channelKey:()=>(0,g.channelKey)(t),store:h.store,backup:p,defaultFolder:()=>(0,b.backupSettingsOf)((0,T.readServiceConfig)(t)?.wordsBackup).folder})
7472
+ let E
7473
+ o.handle("new-computer",e=>(0,w.newComputerCommand)(I,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."}),f=new L.ScheduleWaker({remoteServerUrl:s.remoteServerUrl,getToken:async()=>(await v.getToken())?.token,parked:()=>o.status().filter(e=>"parked"===e.state&&e.projectId).map(e=>e.projectId),wake:e=>o.wake(e,"work"),logger:n}),f.start(),(0,D.resolveFrontendUrl)(s.remoteServerUrl).then(e=>{try{/^https?:\/\//.test(e)&&(E=new URL(e).origin)}catch{}})
7474
+ const C=(0,B.nativeDialogs)(),x=new U.Adding({supervisor:o,dialogs:C,logger:n,approvalEnv:{installDir:G}}),R=e=>{const r=(0,T.readServiceConfig)(t)
7475
+ if(!r)throw new Error("the service has no configuration");(0,T.writeServiceConfig)(t,{...r,wordsBackup:{...r.wordsBackup??{},...e}})}
7476
+ d=await(0,q.startDoor)({supervisor:o,adding:x,wordsBackup:{view:()=>{const e=(0,b.backupSettingsOf)((0,T.readServiceConfig)(t)?.wordsBackup)
7477
+ return{settings:e,state:p.state(),runs:p.ownRuns(),folderAvailable:p.folderAvailable(e.folder)}},set:e=>R(e),pick:async()=>{const e=await C.pickFolder("Choose a folder for backups of your words (one your backup tool or iCloud Drive covers)")
7478
+ return e&&R({folder:e}),e},run:()=>p.run(),restore:e=>p.restore(e)},privateData:{get:()=>h?.privateData??!0===(0,T.readServiceConfig)(t)?.privateData,set:k},newComputer:{api:I,pickBackupFolder:()=>C.pickFolder('Choose the "AlchemyLab Backups" folder of your old computer (restored or copied onto this one)')},verifyMachineTicket:async e=>{const t=(await v.getToken())?.token
7444
7479
  if(!t)throw new Error("this computer is not signed in")
7445
- 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)})
7480
+ 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,W.machineCallerHeaders)(s.remoteServerUrl)},body:JSON.stringify({ticket:e}),signal:AbortSignal.timeout(1e4)})
7446
7481
  if(r.status>=400&&r.status<500)return{ok:!1}
7447
7482
  if(!r.ok)throw new Error(`HTTP ${r.status}`)
7448
7483
  const n=await r.json(),o=n?.data??n
7449
- 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
7484
+ return{ok:!0===o?.ok,expiresAt:o?.expiresAt??null}},logger:n,remoteServerUrl:s.remoteServerUrl,version:(0,A.ownVersion)()??void 0,machineId:u.id,frontendOrigin:()=>E,verifyTicket:async(e,t)=>{const r=(await v.getToken())?.token
7450
7485
  if(!r)throw new Error("this computer is not signed in")
7451
- 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)})
7486
+ 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,W.machineCallerHeaders)(s.remoteServerUrl)},body:JSON.stringify({ticket:t}),signal:AbortSignal.timeout(1e4)})
7452
7487
  if(n.status>=400&&n.status<500)return{ok:!1}
7453
7488
  if(!n.ok)throw new Error(`HTTP ${n.status}`)
7454
7489
  const o=await n.json(),i=o?.data??o
7455
- return{ok:!0===i?.ok,expiresAt:i?.expiresAt??null}}},s.doorPort??(0,O.defaultDoorPort)(s.remoteServerUrl)),await new Promise(()=>{})}(e)
7456
- const s=(0,A.servicePaths)(e)
7457
- if("install"===t[0]||"uninstall"===t[0]){if("uninstall"===t[0]){const t=await(0,U.uninstallLoginItem)(e)
7458
- 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)
7459
- 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.")
7460
- await(0,x.request)(s,"shutdown")
7461
- for(let e=0;e<240&&await(0,x.isServiceRunning)(s);e++)await new Promise(e=>setTimeout(e,250))
7490
+ return{ok:!0===i?.ok,expiresAt:i?.expiresAt??null}}},s.doorPort??(0,q.defaultDoorPort)(s.remoteServerUrl)),await new Promise(()=>{})}(e)
7491
+ const c=(0,_.servicePaths)(e)
7492
+ if("install"===t[0]||"uninstall"===t[0]){if("uninstall"===t[0]){const t=await(0,H.uninstallLoginItem)(e)
7493
+ 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,T.readServiceConfig)(c)
7494
+ return r||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),void await o(c,r)}if("stop"===t[0]){if(!await(0,R.isServiceRunning)(c))return void console.log("The AlchemyLab service is not running.")
7495
+ await(0,R.request)(c,"shutdown")
7496
+ for(let e=0;e<240&&await(0,R.isServiceRunning)(c);e++)await new Promise(e=>setTimeout(e,250))
7462
7497
  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()
7463
- 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))
7464
- const r=await(0,x.request)(s,"computer-gone",{machineId:e})
7465
- 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))
7466
- const e=await(0,x.request)(s,"pair-channel",{code:t[1]})
7467
- 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)
7468
- 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))
7469
- const t=d(e),r=u()
7470
- try{const s=await(0,g.runNewComputerFlow)(t,r,{explicit:!0})
7471
- if("moved"===s||"no-candidates"===s){const s=v.default.realpathSync(process.cwd()),n=v.default.existsSync(b.default.join(s,w.ALCHEMY_DIR_NAME,"project.json")),{here:o}=await t.candidates(),i=n?null:await(0,g.pickMovedProject)(o,s,r)
7472
- if(i){const t=(0,m.attachFolder)(s,i,(0,p.machineIdentity)(e).id)
7473
- 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)
7474
- e||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),await i(s,e)
7475
- const t=await(0,x.request)(s,"move-words-here",{},9e4)
7476
- 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)
7477
- let a=(0,C.readServiceConfig)(s)
7478
- const l=!a
7479
- 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."))
7480
- const h=await(0,x.isServiceRunning)(s),k=new _.ProjectsStore(s.projects)
7481
- 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
- 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
- if("status"===t)return c(o)
7484
- if("approve"===t){const e=(await(0,x.request)(o,"approvals")).approvals??[]
7498
+ 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,R.isServiceRunning)(c)||(console.error("Start the AlchemyLab service first: `alchemy service`."),process.exit(1))
7499
+ const r=await(0,R.request)(c,"computer-gone",{machineId:e})
7500
+ 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,R.isServiceRunning)(c)||(console.error("Start the AlchemyLab service first: `alchemy service`."),process.exit(1))
7501
+ const e=await(0,R.request)(c,"pair-channel",{code:t[1]})
7502
+ 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,T.readServiceConfig)(c)
7503
+ return e||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),await i(c,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))
7504
+ const t=h(e),r=p()
7505
+ try{const s=await(0,y.runNewComputerFlow)(t,r,{explicit:!0})
7506
+ if("moved"===s||"no-candidates"===s){const s=k.default.realpathSync(process.cwd()),n=k.default.existsSync(S.default.join(s,v.ALCHEMY_DIR_NAME,"project.json")),{here:o}=await t.candidates(),i=n?null:await(0,y.pickMovedProject)(o,s,r)
7507
+ if(i){const t=(0,w.attachFolder)(s,i,(0,m.machineIdentity)(e).id)
7508
+ 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()}}(c)}if("move-words-here"===t[0]){const e=(0,T.readServiceConfig)(c)
7509
+ e||(console.error("Set the service up first: `alchemy service`."),process.exit(1)),await i(c,e)
7510
+ const t=await(0,R.request)(c,"move-words-here",{},9e4)
7511
+ 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,_.ensurePrivateDir)(c.dir)
7512
+ let l=(0,T.readServiceConfig)(c)
7513
+ const u=!l
7514
+ l?process.env.ALCHEMY_OPENCODE_DIR&&process.env.ALCHEMY_OPENCODE_DIR!==l.opencodeDir&&(l={...l,opencodeDir:process.env.ALCHEMY_OPENCODE_DIR},(0,T.writeServiceConfig)(c,l),console.log(`Projects will run with the OpenCode build in ${l.opencodeDir} from their next start.`)):(l={remoteServerUrl:e,opencodeDir:process.env.ALCHEMY_OPENCODE_DIR||void 0,privateData:!0,createdAt:(new Date).toISOString()},(0,T.writeServiceConfig)(c,l),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.')),u&&"ready"!==(await(0,C.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."),!u||!process.stdin.isTTY||"darwin"!==process.platform&&"linux"!==process.platform||(0,H.loginItemInstalled)(e)||(await n("Start AlchemyLab every time this computer starts?",!0)?await o(c,l):console.log("Not set. `alchemy service install` sets it later."))
7515
+ const f=await(0,R.isServiceRunning)(c),I=new x.ProjectsStore(c.projects)
7516
+ I.exists()||f||I.touch(),f||await(0,R.startServiceInBackground)(c,l,r())||(console.error(a(c.log)),process.exit(1)),s.forFolder?console.log("AlchemyLab service: running."):await d(c)},exports.serviceDidNotStart=a,exports.lastLogReason=c,exports.printStatus=d,exports.runControl=async function(e,t,r){const o=function(e){const t=s(e)
7517
+ 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,T.readServiceConfig)(o)
7518
+ if("status"===t)return d(o)
7519
+ if("approve"===t){const e=(await(0,R.request)(o,"approvals")).approvals??[]
7485
7520
  if(0===e.length)return void console.log("Nothing is waiting for your approval.")
7486
7521
  for(const t of e){console.log(""),console.log(t.message)
7487
7522
  const e=await n("Approve?",!1)
7488
- await(0,x.request)(o,"approve",{approvalId:t.id,yes:e}),console.log(e?"Approved.":"Declined.")}return}await i(o,a)
7489
- const d=function(e){if(!e)return v.default.realpathSync(process.cwd())
7490
- 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
- try{return v.default.realpathSync(t)}catch{return t}}return e}(r)
7492
- if("add"===t){const e=await(0,x.request)(o,"add",{path:d})
7493
- 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,x.request)(o,t,{ref:d},18e4)
7523
+ await(0,R.request)(o,"approve",{approvalId:t.id,yes:e}),console.log(e?"Approved.":"Declined.")}return}await i(o,a)
7524
+ const c=function(e){if(!e)return k.default.realpathSync(process.cwd())
7525
+ if(e.startsWith("/")||e.startsWith(".")||e.startsWith("~")){const t=e.startsWith("~")?S.default.join(process.env.HOME??"",e.slice(1)):S.default.resolve(e)
7526
+ try{return k.default.realpathSync(t)}catch{return t}}return e}(r)
7527
+ if("add"===t){const e=await(0,R.request)(o,"add",{path:c})
7528
+ 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"} ${S.default.basename(c)}...`)
7529
+ const e=await(0,R.request)(o,t,{ref:c},18e4)
7495
7530
  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,x.request)(o,t,{ref:d},12e4)
7531
+ return e.ok||(u(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,R.request)(o,t,{ref:c},12e4)
7497
7532
  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
- 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})
7533
+ 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=k.default.realpathSync(process.cwd()),a=(0,$.createLogger)({serviceName:"alchemy-cli",console:!1,logToFile:!0,logFilePath:s.log,baseDir:s.dir}),c=new j.TokenManager(a,o,{remoteServerUrl:e,allowInteractiveAuth:!1})
7499
7534
  await c.initialize()
7500
- const h=await(0,M.ensureSignedIn)(c)
7535
+ const l=await(0,P.ensureSignedIn)(c)
7501
7536
  await i(s,n)
7502
- const p=await(0,x.request)(s,"connect-now",{},3e4).catch(()=>{})
7503
- 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")
7504
- if(!process.stdin.isTTY||!process.stdout.isTTY||v.default.existsSync(t))return
7505
- if(((await(0,x.request)(e,"status")).projects??[]).length>0)return
7506
- const r=d(e),{candidates:s}=await r.candidates()
7537
+ const d=await(0,R.request)(s,"connect-now",{},3e4).catch(()=>{})
7538
+ d?.ok||await(0,R.request)(s,"connect-now",{},3e4).catch(()=>{}),await async function(e){const t=S.default.join(e.dir,"new-computer-asked.json")
7539
+ if(!process.stdin.isTTY||!process.stdout.isTTY||k.default.existsSync(t))return
7540
+ if(((await(0,R.request)(e,"status")).projects??[]).length>0)return
7541
+ const r=h(e),{candidates:s}=await r.candidates()
7507
7542
  if(0===s.length)return
7508
- const n=u()
7509
- 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(()=>{})
7510
- const f=await(0,x.request)(s,"add",{path:o})
7543
+ const n=p()
7544
+ try{await(0,y.runNewComputerFlow)(r,n,{explicit:!1}),k.default.writeFileSync(t,JSON.stringify({askedAt:(new Date).toISOString()})+"\n",{mode:384}),n.say("")}finally{n.close()}}(s).catch(()=>{})
7545
+ const f=await(0,R.request)(s,"add",{path:o})
7511
7546
  f.ok||(console.error(`\u274c ${f.reason}`),process.exit(1))
7512
- const m=await(0,x.request)(s,"start",{ref:o},18e4)
7547
+ const m=await(0,R.request)(s,"start",{ref:o},18e4)
7513
7548
  for(const e of m.lines??[])console.log(e)
7514
- 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)
7515
- const w=await(0,P.resolveFrontendUrl)(e)
7516
- 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`
7517
- 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})
7518
- 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)})}
7519
- 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
7520
- 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)
7549
+ m.ok||(u(m),process.exit(1)),(0,O.printBanner)(),console.log(`\u{1f4c1} Project: ${o}`),console.log(""),console.log(" Action server .... port "+m.port)
7550
+ const g=await(0,D.resolveFrontendUrl)(e)
7551
+ if(console.log(""),console.log(` Alchemy UI: \x1b]8;;${g}\x07${g}\x1b]8;;\x07`),console.log(""),m.firstRun){const e=`${g.replace(/\/$/,"")}${m.projectId?`/projects/${m.projectId}`:""}/timeline?welcome=1`
7552
+ 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,E.openBrowser)(e,`Open this link in a browser on any device to continue there, with the tour: ${e}`)}const{InteractiveShell:w}=await t.e(994).then(()=>t.t(75994,23)),v=new w({projectPath:o,logger:a,remoteServerUrl:e,authToken:l,projectId:m.projectId,hasCode:!0===m.hasCode})
7553
+ await v.start(async()=>{console.log(""),console.log(`AlchemyLab keeps running ${S.default.basename(o)}. \`alchemy stop\` in this folder stops it.`),process.exit(0)})}
7554
+ const m=t(60134),g=t(89122),w=t(98074),y=t(62671),v=t(14139),b=t(79564),k=f(t(79896)),S=f(t(16928)),I=f(t(23785)),$=t(12782),j=t(11253),E=t(90629),A=t(42403),C=t(88918),_=t(18944),T=t(30030),x=t(52800),M=t(81480),R=t(56257),P=t(19071),O=t(64504),D=t(38778),q=t(35382),N=t(60134),F=t(71918),L=t(36968),U=t(76231),B=t(67518),H=t(99269),W=t(11773),G=(()=>{let e=__dirname
7555
+ for(let t=0;t<6;t++){try{if("alchemylab"===JSON.parse(k.default.readFileSync(S.default.join(e,"package.json"),"utf8"))?.name)return e}catch{}const t=S.default.dirname(e)
7521
7556
  if(t===e)break
7522
7557
  e=t}})()},30030(e,exports,t){"use strict"
7523
7558
  exports.readServiceConfig=function(e){const t=(0,r.readJson)(e.config)
@@ -7762,17 +7797,20 @@ const d=Number(s.prepare("SELECT v FROM meta WHERE k = 'generation'").get()?.v??
7762
7797
  i.prepare("UPDATE meta SET v = max(v, ?) WHERE k = 'generation'").run(d),i.exec("COMMIT")}finally{s.close()}}}finally{i.close()}if(t===p.items+e)return{projectId:s,restored:!1,unchanged:!0,items:t,keptFromLive:e}
7763
7798
  if(n.default.chmodSync(u,384),n.default.existsSync(d)){n.default.renameSync(d,`${d}.before-restore-${a}`)
7764
7799
  for(const e of["-wal","-shm"])n.default.existsSync(d+e)&&n.default.renameSync(d+e,`${d}.before-restore-${a}${e}`)}return n.default.renameSync(u,d),{projectId:s,restored:!0,items:p.items+e,keptFromLive:e}}finally{o.unhold(s),n.default.rmSync(u,{force:!0})}}}},18984(e,exports,t){"use strict"
7765
- function r(e){return"summary"===e?.claudeThinking?"summary":"omitted"}exports.MCPClientConfigurer=exports.Vr=exports.Es=exports.R1=exports.NK=void 0,exports.claudeThinkingOf=r
7766
- const s=t(75632),n=t(58824),o=t(89796),i=t(95671),a=t(43218),c=t(49779),l=t(11773),d=t(79896),u=t(16928),h=t(35317),p=t(91943),f=t(39023),m=t(35419)
7800
+ function r(e){return"summary"===e?.claudeThinking?"summary":"omitted"}function s(e,t){const r=void 0!==e.compaction?.anthropic
7801
+ if(t?!0===e.compaction?.anthropic:!r)return!1
7802
+ const{anthropic:s,...n}=e.compaction??{}
7803
+ return t?e.compaction={...n,anthropic:!0}:Object.keys(n).length>0?e.compaction=n:delete e.compaction,!0}exports.MCPClientConfigurer=exports.Vr=exports.Es=exports.R1=exports.NK=void 0,exports.claudeThinkingOf=r,exports.withAnthropicCompaction=s
7804
+ const n=t(75632),o=t(58824),i=t(89796),a=t(95671),c=t(43218),l=t(49779),d=t(11773),u=t(79896),h=t(16928),p=t(35317),f=t(91943),m=t(39023),g=t(35419)
7767
7805
  exports.NK="medium"
7768
- const g=new Set(["low","medium","high","xhigh","max"])
7769
- let w=exports.NK,y=[]
7806
+ const w=new Set(["low","medium","high","xhigh","max"])
7807
+ let y=exports.NK,v=[]
7770
7808
  exports.R1=new Set(["$schema","model","mcp","agent","mode","permission","plugin","tools","small_model","share","autoshare"]),exports.Es=new Set(["theme","keybinds","tui","layout","username","watcher","instructions"]),exports.Vr=new Set(["reviewer","acceptance-judge","command-judge","guidance-judge","intake-reviewer"])
7771
- class v{constructor(e,t,r){this.logger=e,this.agentMarkdowns=new Map,this.pinnedAgents=new Set,this.connectedProviders=null,this.remoteServerUrl=t||process.env.REMOTE_SERVER_URL,this.authToken=r}async seedAgentsFromRemote(e){const t=this.remoteServerUrl
7809
+ class b{constructor(e,t,r){this.logger=e,this.agentMarkdowns=new Map,this.pinnedAgents=new Set,this.connectedProviders=null,this.remoteServerUrl=t||process.env.REMOTE_SERVER_URL,this.authToken=r}async seedAgentsFromRemote(e){const t=this.remoteServerUrl
7772
7810
  if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot initialize agents"),!1
7773
7811
  try{const r=`${t}/api/agents/seed`
7774
7812
  this.logger.info("Ensuring built-in agents exist",{url:r})
7775
- const s={"Content-Type":"application/json","X-Client-Type":"alchemy-local-service",...(0,l.machineCallerHeaders)(t)}
7813
+ const s={"Content-Type":"application/json","X-Client-Type":"alchemy-local-service",...(0,d.machineCallerHeaders)(t)}
7776
7814
  this.authToken&&(s.Authorization=`Bearer ${this.authToken}`)
7777
7815
  const n=await fetch(r,{method:"POST",headers:s})
7778
7816
  n.ok||this.logger.warn("Failed to seed global agents",{status:n.status,statusText:n.statusText})
@@ -7781,16 +7819,16 @@ this.logger.info("Initializing project agent activations",{url:o})
7781
7819
  const i=await fetch(o,{method:"POST",headers:s})
7782
7820
  if(!i.ok)return this.logger.warn("Failed to initialize project agents",{status:i.status,statusText:i.statusText}),!1
7783
7821
  const a=await i.json()
7784
- return this.logger.info("Initialized project agent activations",{initialized:a.data?.initialized||0}),!0}catch(e){return this.logger.warn("Error initializing agents from remote",{error:(0,n.getErrorMessage)(e)}),!1}}async fetchPluginManifest(){const e={"opencode-claude-auth":"1.5.3"},t=this.remoteServerUrl
7822
+ return this.logger.info("Initialized project agent activations",{initialized:a.data?.initialized||0}),!0}catch(e){return this.logger.warn("Error initializing agents from remote",{error:(0,o.getErrorMessage)(e)}),!1}}async fetchPluginManifest(){const e={"opencode-claude-auth":"1.5.3"},t=this.remoteServerUrl
7785
7823
  if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, using baseline plugin manifest"),{...e}
7786
7824
  const r=`${t}/api/distribution/plugin-versions`
7787
- try{const s={"Content-Type":"application/json","X-Client-Type":"alchemy-local-service",...(0,l.machineCallerHeaders)(t)}
7825
+ try{const s={"Content-Type":"application/json","X-Client-Type":"alchemy-local-service",...(0,d.machineCallerHeaders)(t)}
7788
7826
  this.authToken&&(s.Authorization=`Bearer ${this.authToken}`)
7789
7827
  const n=await fetch(r,{method:"GET",headers:s})
7790
7828
  if(!n.ok)return this.logger.warn("Plugin manifest fetch returned non-OK; using baseline",{status:n.status}),{...e}
7791
7829
  const o=await n.json(),i=o?.data?.plugins??o?.plugins??{},a={...e}
7792
7830
  for(const[e,t]of Object.entries(i)){const r=t?.version
7793
- "string"==typeof r&&r.trim()&&(a[e]=r.trim())}return this.logger.info("Fetched plugin manifest",{manifest:a}),a}catch(t){return this.logger.warn("Plugin manifest fetch failed; using baseline",{error:(0,n.getErrorMessage)(t)}),{...e}}}async sweepLegacyAuthPluginFiles(e){const r=await Promise.resolve().then(()=>t.t(91943,23)),s=await Promise.resolve().then(()=>t.t(16928,23)),n=(await Promise.resolve().then(()=>t.t(70857,23))).homedir(),o=new Set
7831
+ "string"==typeof r&&r.trim()&&(a[e]=r.trim())}return this.logger.info("Fetched plugin manifest",{manifest:a}),a}catch(t){return this.logger.warn("Plugin manifest fetch failed; using baseline",{error:(0,o.getErrorMessage)(t)}),{...e}}}async sweepLegacyAuthPluginFiles(e){const r=await Promise.resolve().then(()=>t.t(91943,23)),s=await Promise.resolve().then(()=>t.t(16928,23)),n=(await Promise.resolve().then(()=>t.t(70857,23))).homedir(),o=new Set
7794
7832
  let i=e
7795
7833
  for(let e=0;e<6;e++){o.add(s.join(i,".opencode","plugin","anthropic-auth.js"))
7796
7834
  const e=s.dirname(i)
@@ -7813,17 +7851,17 @@ this.authToken&&(s.Authorization=`Bearer ${this.authToken}`)
7813
7851
  const n=await fetch(r,{method:"GET",headers:s})
7814
7852
  if(!n.ok)return this.logger.warn("Failed to fetch agents from remote",{status:n.status,statusText:n.statusText}),null
7815
7853
  const o=await n.json(),i=o.data||o
7816
- return this.logger.info("Fetched agents from remote",{count:i.length}),i}catch(e){return this.logger.warn("Error fetching agents from remote",{error:(0,n.getErrorMessage)(e)}),null}}async fetchDefaultWorkflowFromRemote(e){const t=this.remoteServerUrl
7854
+ return this.logger.info("Fetched agents from remote",{count:i.length}),i}catch(e){return this.logger.warn("Error fetching agents from remote",{error:(0,o.getErrorMessage)(e)}),null}}async fetchDefaultWorkflowFromRemote(e){const t=this.remoteServerUrl
7817
7855
  if(!t)return null
7818
7856
  try{const r=await fetch(`${t}/api/projects/${e}/default-workflow`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:`Bearer ${this.authToken}`}:{}}})
7819
7857
  if(!r.ok)return this.logger.warn("Failed to fetch default workflow from remote",{projectId:e,status:r.status}),null
7820
7858
  const s=await r.json()
7821
7859
  return s.data||s||null}catch(t){return this.logger.warn("Error fetching default workflow from remote",{projectId:e,error:t instanceof Error?t.message:String(t)}),null}}async fetchMcpServersFromRemote(e){const t=this.remoteServerUrl
7822
- if(!t)return y
7860
+ if(!t)return v
7823
7861
  try{const r=await fetch(`${t}/api/projects/${e}/mcp-servers`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:`Bearer ${this.authToken}`}:{}}})
7824
- if(!r.ok)return this.logger.warn("Failed to fetch MCP connections; keeping the last known denies",{projectId:e,status:r.status,known:y.length}),y
7862
+ if(!r.ok)return this.logger.warn("Failed to fetch MCP connections; keeping the last known denies",{projectId:e,status:r.status,known:v.length}),v
7825
7863
  const s=await r.json(),n=s.data||s||[]
7826
- return Array.isArray(n)?(y=n,n):y}catch(t){return this.logger.warn("Error fetching MCP connections; keeping the last known denies",{projectId:e,error:t instanceof Error?t.message:String(t),known:y.length}),y}}async fetchOwnerGuidanceFromRemote(e){const t=this.remoteServerUrl
7864
+ return Array.isArray(n)?(v=n,n):v}catch(t){return this.logger.warn("Error fetching MCP connections; keeping the last known denies",{projectId:e,error:t instanceof Error?t.message:String(t),known:v.length}),v}}async fetchOwnerGuidanceFromRemote(e){const t=this.remoteServerUrl
7827
7865
  if(!t)return[]
7828
7866
  try{const r=await fetch(`${t}/api/projects/${e}/owner-guidance`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:`Bearer ${this.authToken}`}:{}}})
7829
7867
  if(!r.ok)return[]
@@ -7835,97 +7873,97 @@ if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot fetch project f
7835
7873
  try{const r=await fetch(`${t}/api/projects/${e}`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:`Bearer ${this.authToken}`}:{}}})
7836
7874
  if(!r.ok)return this.logger.warn("Failed to fetch project from remote",{projectId:e,status:r.status,statusText:r.statusText}),null
7837
7875
  const s=await r.json()
7838
- return s.data||s}catch(t){return this.logger.error("Error fetching project from remote",{projectId:e,error:(0,n.getErrorMessage)(t)}),null}}getAgentMarkdowns(){return[...this.agentMarkdowns].map(([e,t])=>({name:e,markdown:t}))}setAgentMarkdown(e,t){this.agentMarkdowns.set(e,t)}agentModelOf(e){const t=this.agentMarkdowns.get(e),r=t?/^model:\s*"([^"\n]+)"\s*$/m.exec(t):null
7876
+ return s.data||s}catch(t){return this.logger.error("Error fetching project from remote",{projectId:e,error:(0,o.getErrorMessage)(t)}),null}}getAgentMarkdowns(){return[...this.agentMarkdowns].map(([e,t])=>({name:e,markdown:t}))}setAgentMarkdown(e,t){this.agentMarkdowns.set(e,t)}agentModelOf(e){const t=this.agentMarkdowns.get(e),r=t?/^model:\s*"([^"\n]+)"\s*$/m.exec(t):null
7839
7877
  return r&&r[1].includes("/")?r[1]:null}agentPinnedOf(e){return this.pinnedAgents.has(e)}clearAgentMarkdowns(){this.agentMarkdowns.clear()}async removeOnDiskAgentFiles(e,r=[]){const s=await Promise.resolve().then(()=>t.t(91943,23)),n=await Promise.resolve().then(()=>t.t(16928,23)),o=new Set(r.map(e=>`${e}.md`)),i=(new Date).toISOString().slice(0,10)
7840
7878
  try{for(const t of await s.readdir(e)){if(!t.endsWith(".md"))continue
7841
7879
  const r=n.join(e,t)
7842
7880
  let a=""
7843
7881
  try{a=(await s.readFile(r,"utf8")).slice(0,400)}catch{}if(o.has(t)&&/^---[\s\S]*\bmode:\s*(primary|subagent|all)\b/m.test(a)){await s.unlink(r)
7844
7882
  continue}const c=`${r}.removed-by-alchemy-${i}`
7845
- try{await s.rename(r,c),this.logger.warn("Moved an agent file AlchemyLab did not write out of the way (agent prompts are served from memory now)",{from:r,movedTo:c})}catch(e){this.logger.warn("Could not move an unrecognised agent file aside; leaving it",{path:r,error:e instanceof Error?e.message:String(e)})}}}catch{}}buildExploreMarkdown(e){return`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${e?e.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`}async resolveWorkflowEffort(e){try{const t=await this.fetchDefaultWorkflowFromRemote(e),r="string"==typeof t?.effort&&g.has(t.effort)?t.effort:exports.NK
7846
- r!==w&&this.logger.info("Effort dial read from the workflow",{effort:r,was:w}),w=r}catch{}return w}hasConnectedProviders(){return null!==this.connectedProviders}async refreshConnectedProviders(e){const t=await(0,a.fetchConnectedProviders)(e)
7883
+ try{await s.rename(r,c),this.logger.warn("Moved an agent file AlchemyLab did not write out of the way (agent prompts are served from memory now)",{from:r,movedTo:c})}catch(e){this.logger.warn("Could not move an unrecognised agent file aside; leaving it",{path:r,error:e instanceof Error?e.message:String(e)})}}}catch{}}buildExploreMarkdown(e){return`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${e?e.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`}async resolveWorkflowEffort(e){try{const t=await this.fetchDefaultWorkflowFromRemote(e),r="string"==typeof t?.effort&&w.has(t.effort)?t.effort:exports.NK
7884
+ r!==y&&this.logger.info("Effort dial read from the workflow",{effort:r,was:y}),y=r}catch{}return y}hasConnectedProviders(){return null!==this.connectedProviders}getConnectedProviders(){return this.connectedProviders}async refreshConnectedProviders(e){const t=await(0,c.fetchConnectedProviders)(e)
7847
7885
  if(!t)return!1
7848
7886
  const r=JSON.stringify(t)!==JSON.stringify(this.connectedProviders)
7849
- return this.connectedProviders=t,r}generateAgentMarkdown(e,t,r,n,o={}){let c=r
7850
- if(e.model?.providerId&&e.model?.modelId?this.pinnedAgents.add(e.name):this.pinnedAgents.delete(e.name),e.model?.providerId&&e.model?.modelId)c=`${e.model.providerId}/${e.model.modelId}`
7851
- else if(e.preferredModelTier&&"same"!==e.preferredModelTier){const t=(0,i.resolveModelTier)(r,e.preferredProvider||"inherit",e.preferredModelTier,{anthropic:!0,openai:!0},o.limits)
7852
- t.resolved&&(c=t.fullModel)}const l=(0,a.runnableModel)(c,this.connectedProviders)
7853
- if(l&&l!==c){const t=`${e.name}:${c}->${l}`
7854
- v.rerouteLogged.has(t)||(v.rerouteLogged.add(t),this.logger.warn("agent model provider not connected; the agent runs on a connected provider's default until it is",{agent:e.name,configured:c,running:l})),c=l}const d=[...Object.entries(e.tools||{}).map(([e,r])=>{const s=e.replace(/-/g,"_")
7855
- return` ${v.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${s}`}: ${r}`}),...Object.entries(o.extraTools||{}).map(([e,t])=>` ${e}: ${t}`)].join("\n")
7856
- let u=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${c}"\n`
7857
- u+=`variant: "${e.variant||o.effort||w}"\n`
7887
+ return this.connectedProviders=t,r}generateAgentMarkdown(e,t,r,s,o={}){let i=r
7888
+ if(e.model?.providerId&&e.model?.modelId?this.pinnedAgents.add(e.name):this.pinnedAgents.delete(e.name),e.model?.providerId&&e.model?.modelId)i=`${e.model.providerId}/${e.model.modelId}`
7889
+ else if(e.preferredModelTier&&"same"!==e.preferredModelTier){const t=(0,a.resolveModelTier)(r,e.preferredProvider||"inherit",e.preferredModelTier,{anthropic:!0,openai:!0},o.limits)
7890
+ t.resolved&&(i=t.fullModel)}const l=(0,c.runnableModel)(i,this.connectedProviders)
7891
+ if(l&&l!==i){const t=`${e.name}:${i}->${l}`
7892
+ b.rerouteLogged.has(t)||(b.rerouteLogged.add(t),this.logger.warn("agent model provider not connected; the agent runs on a connected provider's default until it is",{agent:e.name,configured:i,running:l})),i=l}const d=[...Object.entries(e.tools||{}).map(([e,r])=>{const s=e.replace(/-/g,"_")
7893
+ return` ${b.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${s}`}: ${r}`}),...Object.entries(o.extraTools||{}).map(([e,t])=>` ${e}: ${t}`)].join("\n")
7894
+ let u=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${i}"\n`
7895
+ u+=`variant: "${e.variant||o.effort||y}"\n`
7858
7896
  const h=[]
7859
- "summary"===o.claudeThinking&&c.startsWith("anthropic/")&&h.push(' thinking:\n display: "summarized"'),exports.Vr.has(e.name)&&c.startsWith("anthropic/")&&h.push(' cacheTtl: "5m"')
7860
- const p=(0,s.toolScopesFor)(e.name,t,e.tools||{})
7897
+ "summary"===o.claudeThinking&&i.startsWith("anthropic/")&&h.push(' thinking:\n display: "summarized"'),exports.Vr.has(e.name)&&i.startsWith("anthropic/")&&h.push(' cacheTtl: "5m"')
7898
+ const p=(0,n.toolScopesFor)(e.name,t,e.tools||{})
7861
7899
  if(Object.keys(p).length>0&&h.push(` toolScopes: ${JSON.stringify(p)}`),h.length>0&&(u+=`options:\n${h.join("\n")}\n`),e.color&&(u+=`color: "${e.color}"\n`),u+=`tools:\n${d}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){u+="permission:\n"
7862
7900
  for(const[t,r]of Object.entries(e.opencodePermission))if("object"==typeof r&&null!==r){u+=` ${t}:\n`
7863
- for(const[e,t]of Object.entries(r))u+=` "${e}": ${t}\n`}else u+=` ${t}: ${r}\n`}return void 0!==e.temperature&&(u+=`temperature: ${e.temperature}\n`),"number"==typeof e.maxSteps&&Number.isInteger(e.maxSteps)&&e.maxSteps>0&&(u+=`steps: ${e.maxSteps}\n`),u+="---\n\n",n&&(u+=n.trim()+"\n\n"),e.prompt&&(u+=e.prompt),e.promptAddition&&(u+="\n\n"+e.promptAddition),u}async configureOpenCode(e,s,a="node",l,g){try{const w=await Promise.resolve().then(()=>t.t(91943,23)),y=await Promise.resolve().then(()=>t.t(16928,23)),b=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),k="alchemy",S=y.join(e,"mcp-direct.js")
7864
- this.logger.info("Configuring OpenCode",{isDevMode:b,serverName:k,scriptPath:S,projectPath:s}),await(0,o.ensureAlchemyIgnoreEntries)(s,this.logger),await this.sweepLegacyAuthPluginFiles(s)
7865
- const I=(0,m.generatedOpencodeConfigPath)(s),$=y.join(s,"opencode.json")
7866
- let j=null,E=null,A=null
7867
- const C={}
7868
- let _=null,T=!1
7869
- const x=y.join(s,".alchemy_lab","config.json")
7870
- if(!l)try{const e=await w.readFile(x,"utf-8"),t=JSON.parse(e)
7871
- t?.openCodeTools?(j=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:j})):T=!0,t?.openCodeModel&&(E=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),T=!0}{let e=null
7872
- try{e=JSON.parse(await w.readFile(I,"utf-8"))}catch{}const t=await async function(e,t){const r=(0,u.join)(e,"opencode.json")
7901
+ for(const[e,t]of Object.entries(r))u+=` "${e}": ${t}\n`}else u+=` ${t}: ${r}\n`}return void 0!==e.temperature&&(u+=`temperature: ${e.temperature}\n`),"number"==typeof e.maxSteps&&Number.isInteger(e.maxSteps)&&e.maxSteps>0&&(u+=`steps: ${e.maxSteps}\n`),u+="---\n\n",s&&(u+=s.trim()+"\n\n"),e.prompt&&(u+=e.prompt),e.promptAddition&&(u+="\n\n"+e.promptAddition),u}async configureOpenCode(e,n,c="node",d,w){try{const v=await Promise.resolve().then(()=>t.t(91943,23)),k=await Promise.resolve().then(()=>t.t(16928,23)),S=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),I="alchemy",$=k.join(e,"mcp-direct.js")
7902
+ this.logger.info("Configuring OpenCode",{isDevMode:S,serverName:I,scriptPath:$,projectPath:n}),await(0,i.ensureAlchemyIgnoreEntries)(n,this.logger),await this.sweepLegacyAuthPluginFiles(n)
7903
+ const j=(0,g.generatedOpencodeConfigPath)(n),E=k.join(n,"opencode.json")
7904
+ let A=null,C=null,_=null
7905
+ const T={}
7906
+ let x=null,M=!1
7907
+ const R=k.join(n,".alchemy_lab","config.json")
7908
+ if(!d)try{const e=await v.readFile(R,"utf-8"),t=JSON.parse(e)
7909
+ t?.openCodeTools?(A=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:A})):M=!0,t?.openCodeModel&&(C=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),M=!0}{let e=null
7910
+ try{e=JSON.parse(await v.readFile(j,"utf-8"))}catch{}const t=await async function(e,t){const r=(0,h.join)(e,"opencode.json")
7873
7911
  let s
7874
- try{s=JSON.parse(await(0,p.readFile)(r,"utf8"))}catch{return null}if(!s||"object"!=typeof s||Array.isArray(s)||!s.mcp||"object"!=typeof s.mcp||!("alchemy"in s.mcp))return null
7875
- try{return await(0,f.promisify)(h.execFile)("git",["ls-files","--error-unmatch","--","opencode.json"],{cwd:e}),null}catch{}const o=Object.fromEntries(Object.entries(s).filter(([e])=>!exports.R1.has(e)&&"instructions"!==e&&"skills"!==e))
7876
- try{0===Object.keys(o).length?await(0,p.unlink)(r):await(0,p.writeFile)(r,JSON.stringify(o,null,2),"utf8"),t?.info("Moved the generated OpenCode config out of the project folder",{removed:0===Object.keys(o).length,keptUserKeys:Object.keys(o)})}catch(e){t?.warn("Could not clear the old generated opencode.json from the project folder",{error:(0,n.getErrorMessage)(e)})}return s}(s,this.logger)
7877
- e=e??t,!l&&e?.mode?.alchemy?.tools&&(A=e.mode.alchemy.tools,this.logger.info("Found existing tool settings in the generated OpenCode config",{existingTools:A})),!l&&e?.model&&(_=e.model)
7878
- try{const e=JSON.parse(await w.readFile($,"utf-8")),{kept:t,setAside:r}=function(e){const t={},r={}
7912
+ try{s=JSON.parse(await(0,f.readFile)(r,"utf8"))}catch{return null}if(!s||"object"!=typeof s||Array.isArray(s)||!s.mcp||"object"!=typeof s.mcp||!("alchemy"in s.mcp))return null
7913
+ try{return await(0,m.promisify)(p.execFile)("git",["ls-files","--error-unmatch","--","opencode.json"],{cwd:e}),null}catch{}const n=Object.fromEntries(Object.entries(s).filter(([e])=>!exports.R1.has(e)&&"instructions"!==e&&"skills"!==e))
7914
+ try{0===Object.keys(n).length?await(0,f.unlink)(r):await(0,f.writeFile)(r,JSON.stringify(n,null,2),"utf8"),t?.info("Moved the generated OpenCode config out of the project folder",{removed:0===Object.keys(n).length,keptUserKeys:Object.keys(n)})}catch(e){t?.warn("Could not clear the old generated opencode.json from the project folder",{error:(0,o.getErrorMessage)(e)})}return s}(n,this.logger)
7915
+ e=e??t,!d&&e?.mode?.alchemy?.tools&&(_=e.mode.alchemy.tools,this.logger.info("Found existing tool settings in the generated OpenCode config",{existingTools:_})),!d&&e?.model&&(x=e.model)
7916
+ try{const e=JSON.parse(await v.readFile(E,"utf-8")),{kept:t,setAside:r}=function(e){const t={},r={}
7879
7917
  if(!e||"object"!=typeof e||Array.isArray(e))return{kept:t,setAside:r}
7880
7918
  for(const[s,n]of Object.entries(e))if(!exports.R1.has(s))if(exports.Es.has(s)){if("instructions"===s&&Array.isArray(n)){const e=n.filter(e=>"string"==typeof e&&!/^[a-z][a-z0-9+.-]*:\/\//i.test(e)),o=n.filter(t=>!e.includes(t))
7881
7919
  e.length>0&&(t[s]=e),o.length>0&&(r[s]=o)
7882
7920
  continue}t[s]=n}else r[s]=n
7883
7921
  return{kept:t,setAside:r}}(e)
7884
- Object.assign(C,t),Object.keys(t).length>0&&this.logger.info("Keeping opencode.json keys AlchemyLab does not manage",{keys:Object.keys(t)}),Object.keys(r).length>0&&this.logger.warn("Not loading opencode.json keys: they run commands, change the runtime or send data elsewhere (providers are connected in Settings); the file is left as it is",{keys:Object.keys(r)})}catch{this.logger.info("No opencode.json of the user's in the project folder")}}const M=l?.openCodeTools||j||A||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===l?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==g,todoread:!1,todowrite:!1},R={tools:M}
7885
- let P=null,O=null
7886
- if(l?.projectId&&!l?.openCodeModel)try{O=await this.fetchProjectFromRemote(l.projectId)
7887
- const e=O?.metadata?.defaultModel
7888
- "string"==typeof e&&e.includes("/")&&(P=e)}catch(e){this.logger.warn("Could not fetch project default model; falling back",{error:e instanceof Error?e.message:String(e)})}const D=l?.openCodeModel||P||E||_||i.FLEET_DEFAULT_MODEL
7889
- D!==i.FLEET_DEFAULT_MODEL||l?.openCodeModel||P||E||_||this.logger.warn("No default model configured anywhere for this project; agent files fall back to the fleet default model",{projectId:l?.projectId??null}),l?.openCodeModel?R.model=l.openCodeModel:P?R.model=P:E?R.model=E:_&&(R.model=_)
7890
- const q=y.join(s,".opencode","agent")
7891
- await w.mkdir(q,{recursive:!0})
7892
- const N=D||i.FLEET_DEFAULT_MODEL
7893
- let F,L=null,U=null,B=[],H=null
7894
- if(l?.projectId)if(await this.seedAgentsFromRemote(l.projectId),U=O??await this.fetchProjectFromRemote(l.projectId),F=U?.metadata?.globalSystemPrompt,F&&this.logger.info("Using global system prompt from project metadata",{projectId:l.projectId,promptLength:F.length}),F=await this.agentPreambleFor(l.projectId,F),B=await this.fetchMcpServersFromRemote(l.projectId),H=(await this.fetchDefaultWorkflowFromRemote(l.projectId))?.connectionPermissions??null,L=await this.fetchAgentsFromRemote(l.projectId),L&&L.length>0){this.logger.info("Using agents from remote database",{count:L.length})
7895
- const e=await this.resolveWorkflowEffort(l.projectId)
7922
+ Object.assign(T,t),Object.keys(t).length>0&&this.logger.info("Keeping opencode.json keys AlchemyLab does not manage",{keys:Object.keys(t)}),Object.keys(r).length>0&&this.logger.warn("Not loading opencode.json keys: they run commands, change the runtime or send data elsewhere (providers are connected in Settings); the file is left as it is",{keys:Object.keys(r)})}catch{this.logger.info("No opencode.json of the user's in the project folder")}}const P=d?.openCodeTools||A||_||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===d?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==w,todoread:!1,todowrite:!1},O={tools:P}
7923
+ let D=null,q=null
7924
+ if(d?.projectId&&!d?.openCodeModel)try{q=await this.fetchProjectFromRemote(d.projectId)
7925
+ const e=q?.metadata?.defaultModel
7926
+ "string"==typeof e&&e.includes("/")&&(D=e)}catch(e){this.logger.warn("Could not fetch project default model; falling back",{error:e instanceof Error?e.message:String(e)})}const N=d?.openCodeModel||D||C||x||a.FLEET_DEFAULT_MODEL
7927
+ N!==a.FLEET_DEFAULT_MODEL||d?.openCodeModel||D||C||x||this.logger.warn("No default model configured anywhere for this project; agent files fall back to the fleet default model",{projectId:d?.projectId??null}),d?.openCodeModel?O.model=d.openCodeModel:D?O.model=D:C?O.model=C:x&&(O.model=x)
7928
+ const F=k.join(n,".opencode","agent")
7929
+ await v.mkdir(F,{recursive:!0})
7930
+ const L=N||a.FLEET_DEFAULT_MODEL
7931
+ let U,B=null,H=null,W=[],G=null
7932
+ if(d?.projectId)if(await this.seedAgentsFromRemote(d.projectId),H=q??await this.fetchProjectFromRemote(d.projectId),U=H?.metadata?.globalSystemPrompt,U&&this.logger.info("Using global system prompt from project metadata",{projectId:d.projectId,promptLength:U.length}),U=await this.agentPreambleFor(d.projectId,U),W=await this.fetchMcpServersFromRemote(d.projectId),G=(await this.fetchDefaultWorkflowFromRemote(d.projectId))?.connectionPermissions??null,B=await this.fetchAgentsFromRemote(d.projectId),B&&B.length>0){this.logger.info("Using agents from remote database",{count:B.length})
7933
+ const e=await this.resolveWorkflowEffort(d.projectId)
7896
7934
  this.clearAgentMarkdowns()
7897
- for(const t of L)t.enabled&&this.setAgentMarkdown(t.name,this.generateAgentMarkdown(t,k,N,F,{effort:e,extraTools:(0,c.agentToolGrants)(B,t.name,H),limits:(0,i.modelLimitsOf)(U?.metadata),claudeThinking:r(U?.metadata)}))
7898
- await this.removeOnDiskAgentFiles(q,[...L.filter(e=>e.enabled).map(e=>e.name),"explore"]),this.logger.info("Agent prompts held in memory (served to OpenCode over the action server)",{count:this.agentMarkdowns.size})
7899
- try{const e=new Set([...L.filter(e=>e.enabled).map(e=>`${e.name}.md`),"explore.md"])
7900
- for(const t of await w.readdir(q))if(t.endsWith(".md")&&!e.has(t)){const e=y.join(q,t),r=`${e}.removed-by-alchemy-${(new Date).toISOString().slice(0,10)}`
7901
- try{await w.rename(e,r),this.logger.info(`Moved agent file ${t} aside (no agent by that name)`,{movedTo:r})}catch(e){this.logger.warn(`Could not move agent file ${t} aside; leaving it`,{error:e instanceof Error?e.message:String(e)})}}}catch(e){this.logger.warn("Orphaned agent-file cleanup failed",{error:e instanceof Error?e.message:String(e)})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
7935
+ for(const t of B)t.enabled&&this.setAgentMarkdown(t.name,this.generateAgentMarkdown(t,I,L,U,{effort:e,extraTools:(0,l.agentToolGrants)(W,t.name,G),limits:(0,a.modelLimitsOf)(H?.metadata),claudeThinking:r(H?.metadata)}))
7936
+ await this.removeOnDiskAgentFiles(F,[...B.filter(e=>e.enabled).map(e=>e.name),"explore"]),this.logger.info("Agent prompts held in memory (served to OpenCode over the action server)",{count:this.agentMarkdowns.size})
7937
+ try{const e=new Set([...B.filter(e=>e.enabled).map(e=>`${e.name}.md`),"explore.md"])
7938
+ for(const t of await v.readdir(F))if(t.endsWith(".md")&&!e.has(t)){const e=k.join(F,t),r=`${e}.removed-by-alchemy-${(new Date).toISOString().slice(0,10)}`
7939
+ try{await v.rename(e,r),this.logger.info(`Moved agent file ${t} aside (no agent by that name)`,{movedTo:r})}catch(e){this.logger.warn(`Could not move agent file ${t} aside; leaving it`,{error:e instanceof Error?e.message:String(e)})}}}catch(e){this.logger.warn("Orphaned agent-file cleanup failed",{error:e instanceof Error?e.message:String(e)})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
7902
7940
  else this.logger.warn("No projectId provided - agents must be configured via database")
7903
- const W={$schema:"https://opencode.ai/config.json",share:"disabled",model:D,mcp:{[k]:{type:"local",command:[a,S,s],enabled:!0,timeout:6e5}}}
7904
- b&&process.env.REMOTE_SERVER_URL&&(W.mcp[k].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL})
7905
- const G=l?.projectId?await this.fetchDefaultWorkflowFromRemote(l.projectId):null,K=!1!==G?.webAccess
7906
- if(W.permission={...W.permission??{},bash:"deny"},Object.assign(W.permission,(0,c.globalDenyRules)(B)),L&&L.length>0){W.agent={}
7907
- for(const e of L){if(!e.enabled)continue
7941
+ const K={$schema:"https://opencode.ai/config.json",share:"disabled",model:N,mcp:{[I]:{type:"local",command:[c,$,n],enabled:!0,timeout:6e5}}}
7942
+ S&&process.env.REMOTE_SERVER_URL&&(K.mcp[I].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL})
7943
+ const z=d?.projectId?await this.fetchDefaultWorkflowFromRemote(d.projectId):null,V=!1!==z?.webAccess
7944
+ if(K.permission={...K.permission??{},bash:"deny"},Object.assign(K.permission,(0,l.globalDenyRules)(W)),B&&B.length>0){K.agent={}
7945
+ for(const e of B){if(!e.enabled)continue
7908
7946
  const t={}
7909
7947
  for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
7910
- 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
7911
- 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}}
7912
- const e=this.buildExploreMarkdown(F)
7913
- 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)):[]
7914
- V&&this.applyAuthPluginPin(J,V),J.length>0?W.plugin=J:delete W.plugin
7915
- 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)))
7916
- 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))
7917
- return[...new Set([r,...s])]}(s,C.instructions)
7918
- const X=[".opencode/skill",".opencode/skills",".claude/skills",".agents/skills"].map(e=>y.join(s,e)).filter(e=>(0,d.existsSync)(e))
7919
- X.length>0&&(W.skills={paths:X})
7920
- const Q={...C,...W}
7921
- 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})
7922
- 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")
7948
+ t[b.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${I}_${n}`]=e}if(!V)for(const e of b.WEB_TOOLS)t[b.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${I}_${e}`]=!1
7949
+ Object.assign(t,(0,l.agentToolGrants)(W,e.name,G)),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),K.agent[e.name]={tools:t,...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}K.agent.general={...K.agent.general,disable:!0},K.agent.explore={...K.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[`${I}_search`]:!0,[`${I}_read_files`]:!0,[`${I}_view_asset`]:!0,[`${I}_query_history`]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
7950
+ const e=this.buildExploreMarkdown(U)
7951
+ 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(K.agent).length})}const Y=e=>"string"==typeof e&&e.includes(".opencode/plugin/"),J=(await this.fetchPluginManifest())["opencode-claude-auth"],X=K.plugin,Q=Array.isArray(X)?X.filter(e=>!Y(e)):[]
7952
+ J&&this.applyAuthPluginPin(Q,J),Q.length>0?K.plugin=Q:delete K.plugin
7953
+ try{const e=["opencode.jsonc",".opencode/opencode.json",".opencode/opencode.jsonc",".opencode/plugin",".opencode/tool",".opencode/command"].filter(e=>(0,u.existsSync)(k.join(n,e)))
7954
+ 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{}K.instructions=function(e,t){const r=["AGENTS.md","CLAUDE.md","CONTEXT.md"].map(t=>(0,h.join)(e,t)).find(e=>(0,u.existsSync)(e))??(0,h.join)(e,"AGENTS.md"),s=(Array.isArray(t)?t:[]).filter(e=>"string"==typeof e&&e.trim().length>0).map(t=>t.startsWith("~/")||(0,h.isAbsolute)(t)?t:(0,h.join)(e,t))
7955
+ return[...new Set([r,...s])]}(n,T.instructions)
7956
+ const Z=[".opencode/skill",".opencode/skills",".claude/skills",".agents/skills"].map(e=>k.join(n,e)).filter(e=>(0,u.existsSync)(e))
7957
+ Z.length>0&&(K.skills={paths:Z})
7958
+ const ee={...T,...K}
7959
+ s(ee,(y=H?.metadata,!0===y?.anthropicCompaction)),await v.mkdir(k.dirname(j),{recursive:!0}),await v.writeFile(j,JSON.stringify(ee,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:j})
7960
+ try{const e=await Promise.resolve().then(()=>t.t(70857,23)),r=k.join(e.homedir(),".alchemy_lab","opencode","config"),s=k.join(r,"opencode.json")
7923
7961
  let n=null
7924
- 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))
7925
- 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={}
7926
- try{const t=await w.readFile(x,"utf-8")
7927
- 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)
7928
- 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"
7962
+ try{n=JSON.parse(await v.readFile(s,"utf-8"))}catch{}const o=n??{},i=Array.isArray(o.plugin)?o.plugin.slice():[],a=i.filter(e=>!Y(e))
7963
+ J&&this.applyAuthPluginPin(a,J),i.length===a.length&&!i.some((e,t)=>e!==a[t])&&n||(o.plugin=a,await v.mkdir(r,{recursive:!0}),await v.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(M&&!d)try{let e={}
7964
+ try{const t=await v.readFile(R,"utf-8")
7965
+ e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=P,await v.writeFile(R,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:R}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPluginsGlobal:r,cleanupLegacyProjectPlugins:s}=await t.e(939).then(()=>t.t(49939,23)),o=await r(S??!1,this.logger,e)
7966
+ o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors}),await s(n,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:j}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,o.getErrorMessage)(e)}),{success:!1,error:(0,o.getErrorMessage)(e)}}var y}}exports.MCPClientConfigurer=b,b.WEB_TOOLS=["webfetch","websearch","codesearch","capture_site","read_site","fetch_asset"],b.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"]),b.rerouteLogged=new Set},6688(e,exports,t){"use strict"
7929
7967
  exports.sessionIdentity=void 0
7930
7968
  const r=t(16698),s=t(79896),n=t(16928),o=t(43545),i=t(79414),a=t(87280)
7931
7969
  class c{constructor(){this.parentCache=new Map,this.sessionRequestCache=new Map,this.endedUnderSession=new Map,this.callScope=new r.AsyncLocalStorage,this.agentTypeCache=new Map}setIdentity(e){const t=this.identity
@@ -8000,10 +8038,10 @@ t.info("UnifiedLauncher: reaping stale serve from a previous action-server life"
8000
8038
  try{process.kill(-n,"SIGTERM")}catch{try{process.kill(n,"SIGTERM")}catch{}}await new Promise(e=>setTimeout(e,1500))
8001
8039
  try{process.kill(n,0)
8002
8040
  try{process.kill(-n,"SIGKILL")}catch{try{process.kill(n,"SIGKILL")}catch{}}t.warn("UnifiedLauncher: stale serve needed SIGKILL",{pid:n})}catch{}}catch(e){t.warn("UnifiedLauncher: stale-serve reap failed (continuing with launch)",{error:(0,m.getErrorMessage)(e)})}}(w,this.logger)
8003
- const{resolveServeCommand:v,findOpencodeDir:k}=await Promise.resolve().then(()=>t(39068))
8041
+ const{resolveServeCommand:v,findOpencodeDir:k,lastEnginePatchState:S}=await Promise.resolve().then(()=>t(39068))
8004
8042
  if(!k())try{(0,f.execFileSync)("win32"===process.platform?"where":"which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"alchemylab-opencode package not installed and no system `opencode` on PATH."}}this.logger.info("UnifiedLauncher: Launching opencode serve in background",{command:"opencode serve",cwd:w})
8005
- const S=v()
8006
- return new Promise(t=>{const v=l.default.join(w,".alchemy_lab","opencode"),k=process.env.XDG_DATA_HOME||l.default.join(d.default.homedir(),".local","share")
8043
+ const I=v(),$=S()
8044
+ return"applied"===$?.state?this.logger.info("UnifiedLauncher: applied the engine's dependency patches (its install script had not run)",{dir:$.dir,output:$.detail}):"failed"===$?.state&&this.logger.warn("UnifiedLauncher: the engine's dependency patches could not be applied; it starts without them",{dir:$.dir,error:$.detail}),new Promise(t=>{const v=l.default.join(w,".alchemy_lab","opencode"),k=process.env.XDG_DATA_HOME||l.default.join(d.default.homedir(),".local","share")
8007
8045
  try{u.default.mkdirSync(l.default.join(k,"opencode"),{recursive:!0})}catch(e){this.logger.warn("UnifiedLauncher: Could not pre-create opencode globalData dir",{error:(0,m.getErrorMessage)(e)})}try{!function(e){const t=function(){const e=l.default.join(d.default.homedir(),".alchemy_lab","opencode","config","opencode.json")
8008
8046
  try{const t=JSON.parse(u.default.readFileSync(e,"utf-8"))
8009
8047
  if(Array.isArray(t.plugin)){const e=t.plugin.find(e=>"string"==typeof e&&/^opencode-claude-auth@/.test(e))
@@ -8012,15 +8050,15 @@ if(-1!==t)return e.slice(t+1)}}}catch{}return"1.5.3"}(),i=l.default.join(d.defau
8012
8050
  if(!u.default.existsSync(c))try{u.default.mkdirSync(a,{recursive:!0}),u.default.writeFileSync(l.default.join(a,"package.json"),JSON.stringify({name:"alchemylab-claude-auth-wrapper",private:!0,version:"0.0.0"},null,2)),e.info("UnifiedLauncher: Pre-installing opencode-claude-auth plugin",{version:t,targetDir:a}),(0,f.execFileSync)("npm",["install",`opencode-claude-auth@${t}`,"--no-audit","--no-fund","--silent","--ignore-scripts"],{cwd:a,stdio:"pipe",timeout:12e4}),e.info("UnifiedLauncher: Pre-installed opencode-claude-auth plugin",{version:t})}catch(t){e.warn("UnifiedLauncher: claude-auth pre-install failed; opencode will install lazily and first OAuth click may crash",{error:(0,m.getErrorMessage)(t)})}if(u.default.existsSync(c)&&(r(c,e,`opencode-claude-auth@${t}`),s(l.default.join(l.default.dirname(c),"credentials.js"),e,`opencode-claude-auth@${t}`),n(c,e,`opencode-claude-auth@${t}`),o(c,e,`opencode-claude-auth@${t}`)),u.default.existsSync(i)){let t
8013
8051
  try{t=u.default.readdirSync(i)}catch{return}for(const a of t){if(!a.startsWith("opencode-claude-auth@"))continue
8014
8052
  const t=l.default.join(i,a,"node_modules","opencode-claude-auth","dist","index.js")
8015
- t!==c&&(r(t,e,a),s(l.default.join(l.default.dirname(t),"credentials.js"),e,a),n(t,e,a),o(t,e,a))}}}(this.logger)}catch(e){this.logger.warn("UnifiedLauncher: claude-auth ensure-patched failed",{error:(0,m.getErrorMessage)(e)})}const I=l.default.join(v,"log"),$=l.default.join(I,"opencode-serve.log")
8053
+ t!==c&&(r(t,e,a),s(l.default.join(l.default.dirname(t),"credentials.js"),e,a),n(t,e,a),o(t,e,a))}}}(this.logger)}catch(e){this.logger.warn("UnifiedLauncher: claude-auth ensure-patched failed",{error:(0,m.getErrorMessage)(e)})}const S=l.default.join(v,"log"),$=l.default.join(S,"opencode-serve.log")
8016
8054
  let j=null,E=0
8017
8055
  const A=e=>{if(!j)return
8018
8056
  const t=j
8019
8057
  j=null,t.end(e)},C=e=>{u.default.writeFileSync($,e),j=u.default.createWriteStream($,{flags:"a"}),E=e.length},_=e=>{if(j&&(E+=e.length,j.write(e),E>=20971520))try{const e=j
8020
8058
  j=null,e.end(),u.default.renameSync($,`${$}.1`),C(`# opencode-serve.log \u2014 rotated ${(new Date).toISOString()} (previous in opencode-serve.log.1)\n`)}catch(e){this.logger.warn("UnifiedLauncher: opencode log rotation failed; logging stops for this launch",{error:(0,m.getErrorMessage)(e)})}}
8021
- try{u.default.mkdirSync(I,{recursive:!0}),C(`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`)}catch(e){this.logger.warn("UnifiedLauncher: Could not open opencode log file",{error:(0,m.getErrorMessage)(e)})}const T=process.env.ALCHEMY_OPENCODE_LOG_LEVEL||"WARN",x=(0,g.newServePassword)()
8059
+ try{u.default.mkdirSync(S,{recursive:!0}),C(`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`)}catch(e){this.logger.warn("UnifiedLauncher: Could not open opencode log file",{error:(0,m.getErrorMessage)(e)})}const T=process.env.ALCHEMY_OPENCODE_LOG_LEVEL||"WARN",x=(0,g.newServePassword)()
8022
8060
  try{(0,g.writeServeAuth)(w,x)}catch(e){this.logger.warn("UnifiedLauncher: could not record the serve password on disk; only this process can reach the serve",{error:(0,m.getErrorMessage)(e)})}(0,g.noteServe)(null,x),(0,c.noteServeLoadedLogins)()
8023
- const M=(0,f.spawn)(S.command,[...S.prependArgs,"--port","0","--data-dir",v,"--log-level",T],{stdio:["ignore","pipe","pipe"],cwd:w,detached:!0,env:{...process.env,...(0,p.getOpencodeSpawnEnv)(w,(0,h.isDevMode)()),...y,OPENCODE_SERVER_PASSWORD:x}})
8061
+ const M=(0,f.spawn)(I.command,[...I.prependArgs,"--port","0","--data-dir",v,"--log-level",T],{stdio:["ignore","pipe","pipe"],cwd:w,detached:!0,env:{...process.env,...(0,p.getOpencodeSpawnEnv)(w,(0,h.isDevMode)()),...y,OPENCODE_SERVER_PASSWORD:x}})
8024
8062
  let R="",P=!1
8025
8063
  if(M.pid)try{const e=l.default.join(w,b,"opencode.json")
8026
8064
  u.default.mkdirSync(l.default.dirname(e),{recursive:!0}),u.default.writeFileSync(e,JSON.stringify({pid:M.pid,port:null,startedAt:(new Date).toISOString()},null,2))}catch(e){this.logger.warn("UnifiedLauncher: could not record the serve pid (an orphan would survive a crash)",{error:(0,m.getErrorMessage)(e)})}const O=e=>{if(P)return
@@ -8146,16 +8184,16 @@ return e.ok?e.version??"":a(3e4)}():await async function(){if(console.log(" In
8146
8184
  const e=await o()
8147
8185
  return e.ok?e.version??"":e.permissionDenied?(c(),null):a(15e3)}()}return null!==h?(console.log("\u2705 Docker ready"+(h?` (server v${h})`:"")),{status:"ready",version:h}):{status:"unavailable",reason:"setup did not complete"}}
8148
8186
  const d=t(35317),u=t(79896),h=t(23785)},89063(e,exports){"use strict"
8149
- function t(e){return exports.lS[String(e??"").toLowerCase()]??exports.Ey}function r(e){const r=t(e).atLeast
8150
- return r-Math.min(5*s,r/10)}exports.Ey=exports.lS=void 0,exports.tokensOfMessage=function(e){const t={input:0,output:0,reasoning:0,cacheRead:0,cacheWrite:0,cost:0,hasUsage:!1}
8187
+ function t(e){return"assistant"===e?.role&&!0===e?.summary}function r(e){return exports.lS[String(e??"").toLowerCase()]??exports.Ey}function s(e){const t=r(e).atLeast
8188
+ return t-Math.min(5*n,t/10)}exports.Ey=exports.lS=void 0,exports.tokensOfMessage=function(e){const t={input:0,output:0,reasoning:0,cacheRead:0,cacheWrite:0,cost:0,hasUsage:!1}
8151
8189
  for(const r of e?.parts||[])if("step-finish"===r?.type&&r?.tokens){t.hasUsage=!0
8152
8190
  const e=r.tokens
8153
8191
  t.input+=e.input||0,t.output+=e.output||0,t.reasoning+=e.reasoning||0,t.cacheRead+=e.cache?.read||0,t.cacheWrite+=e.cache?.write||0,t.cost+=r.cost||0}const r=e?.info
8154
8192
  if(!t.hasUsage&&r?.tokens){const e=r.tokens
8155
- t.input=e.input||0,t.output=e.output||0,t.reasoning=e.reasoning||0,t.cacheRead=e.cache?.read||0,t.cacheWrite=e.cache?.write||0,t.cost=r.cost||0,t.hasUsage=t.input>0||t.output>0||t.cacheRead>0||t.cacheWrite>0||t.cost>0}return t},exports.contextTokensOf=function(e){const t=e?.tokens||{}
8156
- return(t.input??0)+(t.cache?.read??0)},exports.cacheRetentionMsFor=function(e){return t(e).atMost},exports.cacheWarmForMs=r,exports.longestCacheWarmMs=function(){return Math.max(r(null),...Object.keys(exports.lS).map(r))}
8157
- const s=6e4
8158
- exports.lS={anthropic:{atLeast:60*s,atMost:65*s},openai:{atLeast:60*s,atMost:90*s}},exports.Ey={atLeast:5*s,atMost:60*s}},75632(e,exports){"use strict"
8193
+ t.input=e.input||0,t.output=e.output||0,t.reasoning=e.reasoning||0,t.cacheRead=e.cache?.read||0,t.cacheWrite=e.cache?.write||0,t.cost=r.cost||0,t.hasUsage=t.input>0||t.output>0||t.cacheRead>0||t.cacheWrite>0||t.cost>0}return t},exports.contextTokensOf=function(e){const r=e?.tokens||{}
8194
+ return t(e)?r.output??0:(r.input??0)+(r.cache?.read??0)},exports.isCompactionSummary=t,exports.cacheRetentionMsFor=function(e){return r(e).atMost},exports.cacheWarmForMs=s,exports.longestCacheWarmMs=function(){return Math.max(s(null),...Object.keys(exports.lS).map(s))}
8195
+ const n=6e4
8196
+ exports.lS={anthropic:{atLeast:60*n,atMost:65*n},openai:{atLeast:60*n,atMost:90*n}},exports.Ey={atLeast:5*n,atMost:60*n}},75632(e,exports){"use strict"
8159
8197
  exports.REGISTER_BY_SETUP_RUN=exports.b=void 0,exports.toolScopesFor=function(e,t,r){const s=exports.b[e]
8160
8198
  if(!s)return{}
8161
8199
  const n={}
@@ -8701,14 +8739,16 @@ if(!t)return`NOT-MEASURED-YET:it is measured after the work is completed, on the
8701
8739
  const s=String(t.note??"").replace(/\s+/g," ").trim().slice(0,300)
8702
8740
  return`${"met"===t.verdict?"MEASURED-MET":"unmet"===t.verdict?"MEASURED-WORSE":"MEASURED"}:${s||"measured"}; ${r}`}const s=n(e)
8703
8741
  return s?`CHECK-AT-COMPLETION:${s}`:!0===e?.proven?"reviewer"===t.by?"PROVEN:review":"you"===t.by?"PROVEN:user-hand-check":"driver"===t.by?"PROVEN:drive":"PROVEN:test":"not-checked"===r.driver?.verdict?"NOT-CHECKED:drive":"answer"===t.reword?.from?`REWORD:${String(t.reword.note??"the answer it was written for changed").slice(0,300)}; reword it with request_operations update, its key and userSaid "${t.reword.ref}"`:t.notARealTest?"NOT-A-REAL-TEST":t.checkFails?"met"===r.reviewer?.verdict?"FAILED:test (review met, check fails)":"FAILED:test":t.disputed?`DISPUTED:its check passes, but the reviewer says it does not hold${t.disputed.note?` ("${String(t.disputed.note).slice(0,200)}")`:""}; strengthen the check so it tests what the criterion says, or fix the code`:"could-not-run"===r.instrument?.verdict?"COULD-NOT-RUN:test (its check could not run here; it says nothing either way)":"unmet"===r.reviewer?.verdict?"UNMET:review":!0===r.reviewer?.reword?"REWORD:a reviewer found its words no longer fit what was asked; reword it with the user's words, or ask them":"withdrawn"===r.reviewer?.verdict?"RE-REVIEW:its review was withdrawn (the work changed after it, or a new pass is due)":e?.coder?"ATTESTED":"UNPROVEN"}function n(e){const t=e?.proof?.checkSetup
8704
- return"string"==typeof t&&t?`its check starts when the request is completed: set up at completion (setup run ${t}). The check runs what this request adds, so it is registered once the request is completed, and its first run on main is the proof, written on the criterion then. Decided by that run only, never by a review: registering the check is not a part of this request left undone. What the check will run is in the diff and is yours to read`:null}function o(e){const r=new Map
8742
+ return"string"==typeof t&&t?`its check starts when the request is completed: set up at completion (setup run ${t}). The check runs what this request adds, so it is registered once the request is completed, and its first run on main is the proof, written on the criterion then. Decided by that run only, never by a review: registering the check is not a part of this request left undone. What the check will run is in the diff and is yours to read`:null}function o(e){const t=String(e??"").trim(),r=/^\[?(\d+[a-z])\](?:\s|$)/i.exec(t)??/^(\d+[a-z])$/i.exec(t)
8743
+ return r?r[1].toLowerCase():t}function i(e){const r=new c
8705
8744
  for(let s=0;s<e.length;s++){const n=t(e[s].id,s)
8706
- r.set(n,e[s])}return r}function i(e){if(0===e.length)return"No acceptance criteria defined."
8707
- const t=o(e),r=[]
8745
+ r.set(n,e[s])}return r}function a(e){if(0===e.length)return"No acceptance criteria defined."
8746
+ const t=i(e),r=[]
8708
8747
  for(const[e,n]of t){const t=s(n),o="objective"===n.kind||"subjective"===n.kind||"gate"===n.kind?` [${n.kind.toUpperCase()}]`:"",i=n.standing?n.waived?" [WAIVED for this request by the user; it no longer binds, do not work on or judge it]":` [FROM THE ${n.standing.processName?`PROCESS "${String(n.standing.processName).slice(0,60)}"`:"WORKFLOW"}: every request that follows it carries it; it cannot be dropped or reworded; if it does not apply, the guidance tool with guidanceId "[guidance:${n.standing.ruleKey?String(n.standing.ruleKey):String(n.standing.id??"")}]": exception when the user said so, else conflict]`:"",a="string"==typeof n.environment&&n.environment.trim()?` [its checks run in the "${n.environment.trim().slice(0,80)}" environment]`:""
8709
8748
  r.push(`[${e}] [${t}]${o}${i}${a} ${n.description}`)}return r.join("\n")}exports.formatCriteriaWithHashes=void 0,exports.criterionShortKey=t,exports.latestRow=r,exports.reviewedSha=function(e){const t=r(e,["reviewer"])
8710
8749
  return t&&("met"===t.verdict||"unmet"===t.verdict)&&"string"==typeof t.commit&&t.commit?t.commit:null},exports.criterionPlainLine=function(e){const t=String(e?.description??"").trim(),r=n(e)
8711
- return t&&r?`${t} (${r})`:t},exports.buildCriteriaKeyMap=o,exports.formatCriteriaWithKeys=i,exports.formatCriteriaWithHashes=i},75453(e,exports,t){"use strict"
8750
+ return t&&r?`${t} (${r})`:t},exports.buildCriteriaKeyMap=i,exports.formatCriteriaWithKeys=a
8751
+ class c extends Map{get(e){return super.get(e)??super.get(o(e))}has(e){return super.has(e)||super.has(o(e))}}exports.formatCriteriaWithHashes=a},75453(e,exports,t){"use strict"
8712
8752
  function r(e,t,r={}){return new Promise(s=>{const n=(0,y.execFile)(r.gitBinary||"git",t,{cwd:e,env:{...process.env,...r.env||{}},maxBuffer:33554432},(e,t,r)=>{s({code:e?"number"==typeof e.code?e.code:1:0,stdout:String(t??""),stderr:String(r??"")})})
8713
8753
  void 0!==r.stdin&&n.stdin?.end(r.stdin)})}async function s(e,t,s){const n=await r(e,["rev-parse","--verify","--quiet",`${t}^{commit}`],{gitBinary:s})
8714
8754
  return 0===n.code?n.stdout.trim():null}function n(e){try{return v.realpathSync(e)}catch{return b.resolve(e)}}async function o(e,t){const s=await r(e,["worktree","list","--porcelain"],{gitBinary:t})
@@ -9285,19 +9325,21 @@ s&&(t={...t,toSha:s})
9285
9325
  try{const r=await e.post(`/api/projects/${e.projectId}/requests/${e.requestId}/main-change-review`,{touched:t,landing:!0}),s=r?.data??r
9286
9326
  return{reviewing:!0===s?.reviewing}}catch(t){return e.logger.warn("held for a review of main's change: the review could not be asked for",{requestId:e.requestId,error:t instanceof Error?t.message:String(t)}),{reviewing:!1}}}
9287
9327
  const s=t(35317),n=t(35510)},49779(e,exports){"use strict"
9288
- function t(e){return e.replace(/[^a-zA-Z0-9_-]/g,"_")}function r(e,r){return`${t(e)}_${t(r)}`}function s(e){return`${t(e)}_*`}exports.x4=exports.Tt=void 0,exports.validateRepoConfig=function(e){if(void 0!==e.command)return"A repository connection derives its own command from the image; give it a dockerfile instead"
9289
- const t=e=>{const t=e.replace(/\\/g,"/")
9290
- return!t.startsWith("/")&&!/^[a-zA-Z]:/.test(t)&&!t.split("/").includes("..")},r=e.dockerfile
9291
- if("string"!=typeof r||!r.trim())return"A repository connection needs the dockerfile to build, as a path inside the repository, for example mcp/server.Dockerfile"
9292
- if(!t(r.trim()))return`dockerfile must be a path inside the repository, not "${r}"`
9328
+ function t(e){return/[\r\n]/.test(e)||e.trim().length>240}function r(e){return e.replace(/[^a-zA-Z0-9_-]/g,"_")}function s(e,t){return`${r(e)}_${r(t)}`}function n(e){return`${r(e)}_*`}exports.x4=exports.Tt=void 0,exports.validateRepoConfig=function(e){if(void 0!==e.command)return"A repository connection derives its own command from the image; give it a dockerfile instead"
9329
+ const r=e=>{const t=e.replace(/\\/g,"/")
9330
+ return!t.startsWith("/")&&!/^[a-zA-Z]:/.test(t)&&!t.split("/").includes("..")},s=e.dockerfile
9331
+ if("string"!=typeof s||!s.trim())return"A repository connection needs the dockerfile to build, as a path inside the repository, for example mcp/server.Dockerfile"
9332
+ if(t(s))return"dockerfile is the path of a Dockerfile in this repository, for example mcp/server.Dockerfile, not its contents. To run a published image, the repository needs a Dockerfile that says FROM <the image>: have it written in the repository first, then give its path here."
9333
+ if(!r(s.trim()))return`dockerfile must be a path inside the repository, not "${s}"`
9293
9334
  if(void 0!==e.context){if("string"!=typeof e.context||!e.context.trim())return"context, when given, is a directory inside the repository"
9294
- if(!t(e.context.trim()))return`context must be a directory inside the repository, not "${e.context}"`}return null},exports.namespacePattern=s,exports.globalDenyRules=function(e){const t={}
9295
- for(const r of e)t[s(r.name)]="deny"
9296
- return t},exports.agentToolGrants=function(e,t,n){const o=new Set(n?.[t]??[])
9335
+ if(t(e.context))return"context is the path of a directory in this repository (the docker build context), for example mcp/, not a description. Leave it out to build from the repository root."
9336
+ if(!r(e.context.trim()))return`context must be a directory inside the repository, not "${e.context}"`}return null},exports.namespacePattern=n,exports.globalDenyRules=function(e){const t={}
9337
+ for(const r of e)t[n(r.name)]="deny"
9338
+ return t},exports.agentToolGrants=function(e,t,r){const o=new Set(r?.[t]??[])
9297
9339
  if(0===o.size)return{}
9298
9340
  const i={}
9299
- for(const t of e)if(t.enabled&&o.has(t.name))if(null!==t.allowedTools)for(const e of t.allowedTools)i[r(t.name,e)]=!0
9300
- else i[s(t.name)]=!0
9341
+ for(const t of e)if(t.enabled&&o.has(t.name))if(null!==t.allowedTools)for(const e of t.allowedTools)i[s(t.name,e)]=!0
9342
+ else i[n(t.name)]=!0
9301
9343
  return i},exports.diffManifests=function(e,t){if(!e||!t)return null
9302
9344
  const r=new Map(e.map(e=>[e.name,e])),s=new Map(t.map(e=>[e.name,e])),n=[...s.keys()].filter(e=>!r.has(e)).sort(),o=[...r.keys()].filter(e=>!s.has(e)).sort(),i=[...s.entries()].filter(([e,t])=>!0===r.get(e)?.readOnly&&!0!==t.readOnly).map(([e])=>e).sort()
9303
9345
  return 0===n.length&&0===o.length&&0===i.length?null:{added:n,removed:o,nowWrites:i}},exports.describeDrift=function(e,t){const r=[]
@@ -10267,7 +10309,7 @@ const j=s.filter((e,t)=>"--port"!==e&&(-1===$||t!==$+1))
10267
10309
  if(0===j.length){try{for(const e of(0,m.otherInstallsNotice)(process.argv[1],(0,m.otherInstalls)(process.argv[1])))console.log(e)}catch{}if(void 0===i){const e=await(0,I.getRemoteServerUrl)(o())
10268
10310
  let t=(0,k.serviceSetUp)(e)
10269
10311
  if(!t){const r=o(),s=new d.TokenManager(r,process.cwd(),{remoteServerUrl:e,allowInteractiveAuth:!1})
10270
- await s.initialize(),await(0,v.ensureSignedIn)(s),await(0,k.runService)(e,[]),t=(0,k.serviceSetUp)(e)}if(t)return void await(0,k.runSupervisedDefault)(e,t)}return void await async function(t={}){const r=process.cwd()
10312
+ await s.initialize(),await(0,v.ensureSignedIn)(s),await(0,k.runService)(e,[],{forFolder:!0}),t=(0,k.serviceSetUp)(e)}if(t)return void await(0,k.runSupervisedDefault)(e,t)}return void await async function(t={}){const r=process.cwd()
10271
10313
  n(r)
10272
10314
  const s=(0,l.join)(r,p.ALCHEMY_DIR_NAME)
10273
10315
  await(0,c.mkdir)(s,{recursive:!0})
@@ -10287,7 +10329,7 @@ for(const t of e.error.split("\n").slice(0,4))console.log(` ${t}`)
10287
10329
  console.log(` Full log: ${(0,l.join)(t,p.ALCHEMY_DIR_NAME,"server.log")}`)}}(o,r),console.log("")
10288
10330
  const i=`\x1b]8;;${n}\x07${n}\x1b]8;;\x07`
10289
10331
  if(console.log(" Alchemy UI: "+i),console.log(""),k){const e=`${n.replace(/\/$/,"")}${S?`/projects/${S}`:""}/timeline?welcome=1`
10290
- 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(""),await j,(0,f.openBrowser)(e,"Open the Alchemy UI link above in a browser on any device to continue there.")}let c=!1,d=null
10332
+ 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(""),await j,(0,f.openBrowser)(e,`Open this link in a browser on any device to continue there, with the tour: ${e}`)}let c=!1,d=null
10291
10333
  const h=async()=>{c||(c=!0,d&&clearInterval(d),console.log(""),console.log("Shutting down..."),await s.stop(),process.exit(0))}
10292
10334
  process.on("SIGTERM",h),process.on("SIGINT",h)
10293
10335
  const{InteractiveShell:w}=await e.e(994).then(()=>e.t(75994,23)),b=new w({projectPath:r,logger:a,remoteServerUrl:m,authToken:y,projectId:S,hasCode:$})