@north-light/cli 0.3.573 → 0.3.575

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.
Files changed (37) hide show
  1. package/dist/clients/attach/viewer.js +1 -1
  2. package/dist/core/canvas/canvas.d.ts +2 -0
  3. package/dist/core/canvas/canvas.js +18 -18
  4. package/dist/core/canvas/crons.d.ts +6 -2
  5. package/dist/core/canvas/crons.js +11 -9
  6. package/dist/core/grants/mirror.d.ts +5 -0
  7. package/dist/core/grants/mirror.js +2 -2
  8. package/dist/core/provider-error-recognition.js +1 -1
  9. package/dist/core/providers/bundles.js +2 -2
  10. package/dist/core/providers/call.js +1 -1
  11. package/dist/core/providers/files.js +1 -1
  12. package/dist/core/runs/operations.js +1 -1
  13. package/dist/core/runtime/host.js +3 -3
  14. package/dist/core/runtime/revive.js +2 -2
  15. package/dist/core/runtime/spawn.js +2 -2
  16. package/dist/core/runtime/view-socket.d.ts +24 -3
  17. package/dist/core/runtime/view-socket.js +2 -2
  18. package/dist/core/storage/client.js +2 -2
  19. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  20. package/dist/daemon/api/handlers/crons.js +1 -1
  21. package/dist/daemon/api/handlers/docs.js +1 -1
  22. package/dist/daemon/api/handlers/objects.d.ts +7 -1
  23. package/dist/daemon/api/handlers/objects.js +1 -1
  24. package/dist/daemon/boot-migrations.d.ts +1 -1
  25. package/dist/daemon/boot-migrations.js +1 -1
  26. package/dist/daemon/cron/lease-recovery.js +1 -1
  27. package/dist/daemon/cron/narrowed.d.ts +2 -2
  28. package/dist/daemon/cron/narrowed.js +2 -2
  29. package/dist/daemon/cron/settle.js +1 -1
  30. package/dist/daemon/cron-run.js +2 -2
  31. package/dist/daemon/crtrd.js +1 -1
  32. package/dist/launcher/run.js +2 -2
  33. package/dist/launcher/spec.d.ts +3 -1
  34. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  35. package/package.json +2 -2
  36. package/packages/crouter-identity/package.json +1 -1
  37. package/runtime.lock.json +13 -13
@@ -1,2 +1,2 @@
1
- var se=Object.defineProperty;var o=(e,n)=>se(e,"name",{value:n,configurable:!0});import{existsSync as g,readdirSync as U,readFileSync as S,rmSync as $,statSync as E}from"node:fs";import{join as d}from"node:path";import{validateCommandManifest as ue}from"../../api/command-manifest/manifest.js";import{crtrHome as W}from"../canvas/paths.js";import{withCanvasWrite as z}from"../canvas/db.js";import{bundleVersion as de,isInstalledGeneration as ce,recordCheckedGeneration as fe}from"../command-plugins/bundle.js";import{readState as le,updateConfig as pe,updateState as me}from"../config.js";import{directoryIssuer as G}from"../directory/issuer.js";import{emitEvent as m}from"../events/emit.js";import{describeOutboundFailure as ve,outboundFetch as he,outboundRefusal as q,OutboundError as we}from"../events/outbound.js";import{withExclusiveDirectoryLockAsync as Z}from"../exclusive-lock.js";import{atomicWriteText as T,ensureDir as x,pathExists as ge}from"../fs-utils.js";import{reconcilePackages as L}from"../graph/package-reconcile.js";import{readPluginManifest as j}from"../manifest.js";import{bundleSwapLockPath as V}from"../plugin-swap-lock.js";import{pluginsDir as $e,userScopeRoot as J}from"../scope.js";import{listInstalledPlugins as ye}from"../resolver.js";import{localGrants as A,providerIdsFromGrants as K}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const be="[A-Za-z0-9_-]{1,64}",Q=new RegExp(`^${be}$`),_e=3e4,X=3e4;function v(e){return d($e("user"),e)}o(v,"pluginRoot");function C(){return d(W(),"providers")}o(C,"providersRoot");function Y(e,n){return d(C(),e,`v${n}`)}o(Y,"majorDir");function B(e){if(!Q.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}o(B,"assertId");function I(){const e=G();if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e}o(I,"directoryBase");async function ee(e,n){const r=`${I()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await he("bundle_fetch",r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(_e)})}catch(i){return{status:"failed",message:`could not reach ${r}: ${i instanceof Error?i.message:String(i)}`,cause:i}}if(t.status===304){if(n.ifNoneMatch===void 0){const i=`HTTP 304 from ${r} without a conditional request`;return{status:"failed",message:i,cause:q("bundle_fetch",r,i)}}return{status:304}}if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300){await t.body?.cancel().catch(()=>{});{const i=`HTTP ${t.status} from ${r}`;return{status:"failed",message:i,cause:q("bundle_fetch",r,i)}}}let a;try{a=new Uint8Array(await t.arrayBuffer())}catch(i){return{status:"failed",message:`reading ${r} failed: ${i instanceof Error?i.message:String(i)}`,cause:new we("bundle_fetch",r,i)}}const s=t.headers.get("etag");return{status:200,raw:a,...s!==null?{etag:s}:{}}}o(ee,"fetchProviderBundle");const M=new WeakSet;class k extends Error{static{o(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function ne(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(a=>r[a]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}o(ne,"wrongBundle");function N(e,n,r){m({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof k?{issues:r.issues}:{}},error:r})}o(N,"logInvalid");function re(e,n,r){m({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r instanceof Error?r.message:String(r),...ve(r instanceof Error?r.cause:void 0)}})}o(re,"logFailure");async function te(){return import("../../commands/pkg/plugin-manage.js")}o(te,"pluginManage");const y=new Map,b=o(e=>`${W()}\0${e}`,"installKey");function Pe(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}o(Pe,"publicInstallCause");function Se(e){if(h(e)===void 0)return y.get(b(e))??{status:"installing"}}o(Se,"providerInstallState");function Ie(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${Se(e)?.error??"bundle installation pending"}`)}o(Ie,"unavailableProviderError");function h(e){const n=j(v(e));if(!(n?.provider===void 0||n.transport?.kind!=="http"&&n.transport?.kind!=="machine")&&ge(d(v(e),"commands.json"))){try{if(E(d(v(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:n.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:n.transport.endpoint}}}}o(h,"installedManifest");async function R(e,n,r=!1){const t=J();h(e)===void 0&&y.set(b(e),{status:"installing"});try{return x(d(t,"tmp")),await Z(V(t,e),async()=>{const a=v(e),s=j(a);if(s!==null&&s.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const i=h(e)!==void 0;if(i&&r)return"unchanged";const f=i&&n?le("user").plugins[e]?.etag:void 0,u=await ee(e,f!==void 0?{ifNoneMatch:f}:{});if(u.status===304)return"unchanged";if(u.status===404||u.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${u.message})`,{param:"provider"});if(u.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${u.message}`,{cause:u.cause});const l=de(u.raw);if(i&&ce(e,"user",a,l))return await fe("user",e,u.etag,{swapped:!1}),"unchanged";const{installPluginArchive:w}=await te();try{await w(e,u.raw,{scope:"user",scopeRootPath:t,root:a,version:l,etag:u.etag,enable:!i,bundle:{endpoint:`${I()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:I()},accept:o(p=>ne(e,void 0,p),"accept")})}catch(p){N(e,void 0,p);const F=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw M.add(F),F}try{await z(async p=>await L(p))}catch(p){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return m({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:l,replaced:i}}),y.delete(b(e)),i?"updated":"installed"},{timeoutMs:X,timeoutError:o(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(a){throw h(e)===void 0&&y.set(b(e),{status:"install_failed",error:Pe(a)}),a instanceof c?a:(m({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:a}),Ie(e))}}o(R,"refreshInstalled");async function en(e){B(e),h(e)===void 0&&await R(e,!1,!0)}o(en,"ensureProviderBundle");async function nn(e,n){return B(e),n.version!==void 0&&h(e)?.provider.major!==n.version?D(e,n.version,n.conditional):R(e,n.conditional)}o(nn,"refreshProviderBundle");async function oe(e,n,r){const{validatePluginArchive:t}=await te(),a=await t(r);if(a.bundle===void 0)throw new k(`provider "${e}" major ${n} bundle is invalid: ${a.issues.map(i=>i.message).join("; ")}`,a.issues);const s=ne(e,n,a.bundle);if(s!==void 0)throw new k(`provider "${e}" major ${n} bundle is invalid: ${s}`,[]);if(a.bundle.transport===void 0)throw new k(`provider "${e}" major ${n} bundle declares no transport`,[]);return a.bundle}o(oe,"validateArchive");function Me(e){x(e),T(d(e,"retired"),`${new Date().toISOString()}
2
- `),$(d(e,"bundle.tar"),{force:!0}),$(d(e,"etag"),{force:!0})}o(Me,"retire");function ae(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}o(ae,"retiredError");async function D(e,n,r){const t=Y(e,n),a=d(t,"bundle.tar"),s=g(a),i=s&&r&&g(d(t,"etag"))?S(d(t,"etag"),"utf8"):void 0,f=await ee(e,{version:n,...i!==void 0?{ifNoneMatch:i}:{}});if(f.status===304)return"unchanged";if(f.status===410)throw Me(t),m({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),ae(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`,{cause:f.cause});try{await oe(e,n,f.raw)}catch(l){N(e,n,l);const w=new c(s?"provider_unavailable":"not_found",l instanceof Error?l.message:String(l),{param:"version"});throw M.add(w),w}x(t),$(d(t,"retired"),{force:!0});const u=s&&Buffer.from(f.raw).equals(S(a));return u||T(a,f.raw),f.etag!==void 0?T(d(t,"etag"),f.etag):$(d(t,"etag"),{force:!0}),u?"unchanged":s?"updated":"installed"}o(D,"refreshPinned");function ke(){const e=[],n=C();if(!g(n))return e;for(const r of U(n)){if(!Q.test(r))continue;let t;try{t=U(d(n,r))}catch{continue}for(const a of t){const s=/^v([1-9][0-9]*)$/.exec(a);s&&g(d(n,r,a,"bundle.tar"))&&e.push([r,Number(s[1])])}}return e}o(ke,"cachedMajors");function Ee(e){const n=ue(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}o(Ee,"readCommands");const _=new Map,H=new Map;function Te(e,n){const r=E(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,a=H.get(e);if(a?.stamp===t)return a.commands;const s=Ee(JSON.parse(S(n,"utf8")));return H.set(e,{stamp:t,commands:s}),s}o(Te,"installedCommands");async function rn(e,n){B(e);const r=h(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const l=v(e),w=Te(e,d(l,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:w,transport:r.transport,source:"installed",root:l}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=Y(e,n);if(g(d(t,"retired")))throw ae(e,n);const a=d(t,"bundle.tar");g(a)||await D(e,n,!1);const s=`${e}/${n}`,i=E(a),f=`${i.mtimeMs}:${i.size}`;let u=_.get(s)?.stamp===f?_.get(s).bundle:void 0;if(u===void 0){try{u=await oe(e,n,new Uint8Array(S(a)))}catch(l){throw N(e,n,l),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}_.set(s,{stamp:f,bundle:u})}return{id:e,major:n,provider:u.provider,commands:u.commands,transport:u.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:u.transport.endpoint},source:"pinned"}}o(rn,"resolveProviderBundle");let P=null,O=!1;function xe(e,n){return e?.provider?.id===n&&e.bundle?.endpoint===`${I()}/providers/${encodeURIComponent(n)}/plugin`}o(xe,"isDirectoryBundle");async function ie(e){return K(await A()).includes(e)}o(ie,"grantNames");async function je(e){await Z(V(J(),e),async()=>{if(!(await ie(e)||!xe(j(v(e)),e))){$(v(e),{recursive:!0,force:!0}),await pe("user",n=>{delete n.plugins[e]}),await me("user",n=>{delete n.plugins[e]}),$(d(C(),e),{recursive:!0,force:!0}),H.delete(e),y.delete(b(e));for(const n of[..._.keys()])n.startsWith(`${e}/`)&&_.delete(n);try{await z(async n=>await L(n))}catch(n){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:n})}m({level:"info",event:"daemon.providers.bundle_uninstalled",fields:{provider:e}})}},{timeoutMs:X,timeoutError:o(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}o(je,"uninstallProvider");async function Ae(){if(!(await A()).every(e=>e.status!=="active")){for(const e of ye("user"))if(!(e.manifest.provider===void 0||await ie(e.name)))try{await je(e.name)}catch(n){m({level:"error",event:"daemon.providers.bundle_uninstall_failed",fields:{provider:e.name},error:n})}}}o(Ae,"uninstallDisconnected");async function Ce(){if(!G())return;const e=K(await A());for(const n of e)try{await R(n,!0)}catch(r){r instanceof Object&&M.has(r)||re(n,void 0,r)}await Ae();for(const[n,r]of ke())try{await D(n,r,!0)}catch(t){if(t instanceof c&&t.code==="provider_version_retired"||t instanceof Object&&M.has(t))continue;re(n,r,t)}}o(Ce,"syncOnce");function tn(){return P!==null?(O=!0,P):(P=(async()=>{try{do O=!1,await Ce();while(O)}finally{P=null}})(),P)}o(tn,"syncProviderBundles");export{en as ensureProviderBundle,Se as providerInstallState,nn as refreshProviderBundle,rn as resolveProviderBundle,tn as syncProviderBundles,Ie as unavailableProviderError};
1
+ var se=Object.defineProperty;var a=(e,n)=>se(e,"name",{value:n,configurable:!0});import{existsSync as $,readdirSync as U,readFileSync as P,rmSync as y,statSync as T}from"node:fs";import{join as d}from"node:path";import{validateCommandManifest as ue}from"../../api/command-manifest/manifest.js";import{crtrHome as W}from"../canvas/paths.js";import{withCanvasWrite as z}from"../canvas/db.js";import{bundleVersion as de,isInstalledGeneration as ce,recordCheckedGeneration as le}from"../command-plugins/bundle.js";import{readState as fe,updateConfig as pe,updateState as me}from"../config.js";import{directoryIssuer as G}from"../directory/issuer.js";import{emitEvent as m}from"../events/emit.js";import{describeOutboundFailure as ve,outboundFetch as he,outboundRefusal as L,OutboundError as ge}from"../events/outbound.js";import{withExclusiveDirectoryLockAsync as q}from"../exclusive-lock.js";import{atomicWriteText as A,ensureDir as x,pathExists as we}from"../fs-utils.js";import{reconcilePackages as Z}from"../graph/package-reconcile.js";import{readPluginManifest as j}from"../manifest.js";import{bundleSwapLockPath as V}from"../plugin-swap-lock.js";import{pluginsDir as $e,userScopeRoot as J}from"../scope.js";import{listInstalledPlugins as ye}from"../resolver.js";import{localGrants as C,providerIdsFromGrants as K}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const be="[A-Za-z0-9_-]{1,64}",Q=new RegExp(`^${be}$`),_e=3e4,X=3e4;function v(e){return d($e("user"),e)}a(v,"pluginRoot");function D(){return d(W(),"providers")}a(D,"providersRoot");function Y(e,n){return d(D(),e,`v${n}`)}a(Y,"majorDir");function B(e){if(!Q.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}a(B,"assertId");function M(){const e=G();if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e}a(M,"directoryBase");async function ee(e,n){const r=`${M()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await he("bundle_fetch",r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(_e)})}catch(i){return{status:"failed",message:`could not reach ${r}: ${i instanceof Error?i.message:String(i)}`,cause:i}}if(t.status===304){if(n.ifNoneMatch===void 0){const i=`HTTP 304 from ${r} without a conditional request`;return{status:"failed",message:i,cause:L("bundle_fetch",r,i)}}return{status:304}}if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300){await t.body?.cancel().catch(()=>{});{const i=`HTTP ${t.status} from ${r}`;return{status:"failed",message:i,cause:L("bundle_fetch",r,i)}}}let o;try{o=new Uint8Array(await t.arrayBuffer())}catch(i){return{status:"failed",message:`reading ${r} failed: ${i instanceof Error?i.message:String(i)}`,cause:new ge("bundle_fetch",r,i)}}const s=t.headers.get("etag");return{status:200,raw:o,...s!==null?{etag:s}:{}}}a(ee,"fetchProviderBundle");const E=new WeakSet;class I extends Error{static{a(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function ne(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(o=>r[o]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}a(ne,"wrongBundle");function N(e,n,r){m({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof I?{issues:r.issues}:{}},error:r})}a(N,"logInvalid");function re(e,n,r){m({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r instanceof Error?r.message:String(r),...ve(r instanceof Error?r.cause:void 0)}})}a(re,"logFailure");async function te(){return import("../../commands/pkg/plugin-manage.js")}a(te,"pluginManage");const b=new Map,_=a(e=>`${W()}\0${e}`,"installKey");function Se(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}a(Se,"publicInstallCause");function Pe(e){if(h(e)===void 0)return b.get(_(e))??{status:"installing"}}a(Pe,"providerInstallState");function Me(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${Pe(e)?.error??"bundle installation pending"}`)}a(Me,"unavailableProviderError");function h(e){const n=j(v(e));if(!(n?.provider===void 0||n.transport?.kind!=="http"&&n.transport?.kind!=="machine")&&we(d(v(e),"commands.json"))){try{if(T(d(v(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:n.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:n.transport.endpoint}}}}a(h,"installedManifest");async function R(e,n,r=!1){const t=J();h(e)===void 0&&b.set(_(e),{status:"installing"});try{return x(d(t,"tmp")),await q(V(t,e),async()=>{const o=v(e),s=j(o);if(s!==null&&s.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const i=h(e)!==void 0;if(i&&r)return"unchanged";const l=i&&n?fe("user").plugins[e]?.etag:void 0,u=await ee(e,l!==void 0?{ifNoneMatch:l}:{});if(u.status===304)return"unchanged";if(u.status===404||u.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${u.message})`,{param:"provider"});if(u.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${u.message}`,{cause:u.cause});const f=de(u.raw);if(i&&ce(e,"user",o,f))return await le("user",e,u.etag,{swapped:!1}),"unchanged";const{installPluginArchive:w}=await te();try{await w(e,u.raw,{scope:"user",scopeRootPath:t,root:o,version:f,etag:u.etag,enable:!i,bundle:{endpoint:`${M()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:M()},accept:a(p=>ne(e,void 0,p),"accept")})}catch(p){N(e,void 0,p);const F=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw E.add(F),F}try{await z(async p=>await Z(p))}catch(p){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return m({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:f,replaced:i}}),b.delete(_(e)),i?"updated":"installed"},{timeoutMs:X,timeoutError:a(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(o){throw h(e)===void 0&&b.set(_(e),{status:"install_failed",error:Se(o)}),o instanceof c?o:(m({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:o}),Me(e))}}a(R,"refreshInstalled");async function nn(e){B(e),h(e)===void 0&&await R(e,!1,!0)}a(nn,"ensureProviderBundle");async function rn(e,n){return B(e),n.version!==void 0&&h(e)?.provider.major!==n.version?H(e,n.version,n.conditional):R(e,n.conditional)}a(rn,"refreshProviderBundle");async function oe(e,n,r){const{validatePluginArchive:t}=await te(),o=await t(r);if(o.bundle===void 0)throw new I(`provider "${e}" major ${n} bundle is invalid: ${o.issues.map(i=>i.message).join("; ")}`,o.issues);const s=ne(e,n,o.bundle);if(s!==void 0)throw new I(`provider "${e}" major ${n} bundle is invalid: ${s}`,[]);if(o.bundle.transport===void 0)throw new I(`provider "${e}" major ${n} bundle declares no transport`,[]);return o.bundle}a(oe,"validateArchive");function Ee(e){x(e),A(d(e,"retired"),`${new Date().toISOString()}
2
+ `),y(d(e,"bundle.tar"),{force:!0}),y(d(e,"etag"),{force:!0})}a(Ee,"retire");function ae(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}a(ae,"retiredError");async function H(e,n,r){const t=Y(e,n),o=d(t,"bundle.tar"),s=$(o),i=s&&r&&$(d(t,"etag"))?P(d(t,"etag"),"utf8"):void 0,l=await ee(e,{version:n,...i!==void 0?{ifNoneMatch:i}:{}});if(l.status===304)return"unchanged";if(l.status===410)throw Ee(t),m({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),ae(e,n);if(l.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(l.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${l.message}`,{cause:l.cause});try{await oe(e,n,l.raw)}catch(f){N(e,n,f);const w=new c(s?"provider_unavailable":"not_found",f instanceof Error?f.message:String(f),{param:"version"});throw E.add(w),w}x(t),y(d(t,"retired"),{force:!0});const u=s&&Buffer.from(l.raw).equals(P(o));return u||A(o,l.raw),l.etag!==void 0?A(d(t,"etag"),l.etag):y(d(t,"etag"),{force:!0}),u?"unchanged":s?"updated":"installed"}a(H,"refreshPinned");function Ie(){const e=[],n=D();if(!$(n))return e;for(const r of U(n)){if(!Q.test(r))continue;let t;try{t=U(d(n,r))}catch{continue}for(const o of t){const s=/^v([1-9][0-9]*)$/.exec(o);s&&$(d(n,r,o,"bundle.tar"))&&e.push([r,Number(s[1])])}}return e}a(Ie,"cachedMajors");function ke(e){const n=ue(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}a(ke,"readCommands");const S=new Map,O=new Map;function Te(e,n){const r=T(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,o=O.get(e);if(o?.stamp===t)return o.commands;const s=ke(JSON.parse(P(n,"utf8")));return O.set(e,{stamp:t,commands:s}),s}a(Te,"installedCommands");async function tn(e,n){B(e);const r=h(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const f=v(e),w=Te(e,d(f,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:w,transport:r.transport,source:"installed",root:f}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=Y(e,n);if($(d(t,"retired")))throw ae(e,n);const o=d(t,"bundle.tar");$(o)||await H(e,n,!1);const s=`${e}/${n}`,i=T(o),l=`${i.mtimeMs}:${i.size}`;let u=S.get(s)?.stamp===l?S.get(s).bundle:void 0;if(u===void 0){try{u=await oe(e,n,new Uint8Array(P(o)))}catch(f){throw N(e,n,f),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}S.set(s,{stamp:l,bundle:u})}return{id:e,major:n,provider:u.provider,commands:u.commands,transport:u.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:u.transport.endpoint},source:"pinned"}}a(tn,"resolveProviderBundle");let g=null;const Ae=4*6e4;function k(e,n){e.stage=n,e.stageAt=Date.now()}a(k,"enter");function xe(e,n){return e?.provider?.id===n&&e.bundle?.endpoint===`${M()}/providers/${encodeURIComponent(n)}/plugin`}a(xe,"isDirectoryBundle");async function ie(e){return K(await C()).includes(e)}a(ie,"grantNames");async function je(e){await q(V(J(),e),async()=>{if(!(await ie(e)||!xe(j(v(e)),e))){y(v(e),{recursive:!0,force:!0}),await pe("user",n=>{delete n.plugins[e]}),await me("user",n=>{delete n.plugins[e]}),y(d(D(),e),{recursive:!0,force:!0}),O.delete(e),b.delete(_(e));for(const n of[...S.keys()])n.startsWith(`${e}/`)&&S.delete(n);try{await z(async n=>await Z(n))}catch(n){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:n})}m({level:"info",event:"daemon.providers.bundle_uninstalled",fields:{provider:e}})}},{timeoutMs:X,timeoutError:a(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}a(je,"uninstallProvider");async function Ce(){if(!(await C()).every(e=>e.status!=="active")){for(const e of ye("user"))if(!(e.manifest.provider===void 0||await ie(e.name)))try{await je(e.name)}catch(n){m({level:"error",event:"daemon.providers.bundle_uninstall_failed",fields:{provider:e.name},error:n})}}}a(Ce,"uninstallDisconnected");async function De(e){if(!G())return;k(e,"reading grants");const n=K(await C());for(const r of n){k(e,`refreshing installed bundle ${r}`);try{await R(r,!0)}catch(t){t instanceof Object&&E.has(t)||re(r,void 0,t)}}k(e,"uninstalling disconnected bundles"),await Ce();for(const[r,t]of Ie()){k(e,`refreshing pinned bundle ${r} v${t}`);try{await H(r,t,!0)}catch(o){if(o instanceof c&&o.code==="provider_version_retired"||o instanceof Object&&E.has(o))continue;re(r,t,o)}}}a(De,"syncOnce");function on(){if(g!==null){g.rerun=!0;const n=Date.now()-g.stageAt;return n>=Ae&&m({level:"error",event:"daemon.providers.sync_stalled",fields:{stage:g.stage,stalled_ms:n}}),g.promise}const e={promise:Promise.resolve(),stage:"starting",stageAt:Date.now(),rerun:!1};return g=e,e.promise=(async()=>{try{do e.rerun=!1,await De(e);while(e.rerun)}finally{g=null}})(),e.promise}a(on,"syncProviderBundles");export{nn as ensureProviderBundle,Pe as providerInstallState,rn as refreshProviderBundle,tn as resolveProviderBundle,on as syncProviderBundles,Me as unavailableProviderError};
@@ -1 +1 @@
1
- var ie=Object.defineProperty;var g=(e,r)=>ie(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as J}from"../../api/command-manifest/result.js";import{callerAllows as ae,requireGroup as se}from"../../daemon/api/handlers/providers.js";import{callerTerminalGrant as de}from"../grants/terminal.js";import{findRefusal as ue,payerOf as le,refusalError as pe,refusedMark as ce,writeCharge as fe}from"../ledger/index.js";import{listInstalledPlugins as me}from"../resolver.js";import{ProviderError as l}from"./errors.js";import{resolveProviderRef as ge}from"./roots.js";import{providerToolPath as ve}from"../plugin-objects-manifest.js";import{ensureProviderBundle as he,refreshProviderBundle as we,resolveProviderBundle as H,unavailableProviderError as ye}from"./bundles.js";import{prepareFileCall as be,resignForRetry as K,resolveFileResult as W}from"./files.js";import{consumeApproval as _e,requireProviderApproval as Ae}from"./approval.js";import{outboundToken as je}from"./mint.js";import{discardFile as ke}from"./token-cache.js";import{registerProviderCall as Pe}from"./inflight.js";import{buildRequestPlan as $e}from"../command-plugins/transport/http-plan.js";import{newTraceId as qe}from"../../shared/traceparent.js";import{sendProviderRequest as Ce,ProviderTransportError as Ee}from"./request.js";import{acceptProviderReceipt as Re}from"./receipts.js";import{writeProviderAudit as Te}from"./audit.js";import{selectDevice as Ie,sendDeviceCall as Me,deviceFailure as Se,deviceUnreachable as X,DEFAULT_DEVICE_REQUEST_MS as Be}from"./device.js";import{isLibsSearch as Y,wantsBundling as Ne,callLibsSearch as Oe,bundleExec as Fe,mapLibOutcome as De,reportLibCalls as Ue}from"./device-libs.js";import{ownMachineId as Le}from"../peer/directory.js";import{directoryIssuer as Je}from"../directory/issuer.js";import{formatCrtrCaller as Z}from"../command-plugins/caller-header.js";import{getNode as He}from"../canvas/canvas.js";import{envelopeForProviderCaller as xe}from"../peer/envelope.js";const Ge="app:terminal";function ee(e){const r=[],i=g((c,t,n)=>{const a=[...t,c.name],s=n||c.tier==="hidden";if(c.kind==="branch"){for(const v of c.children)i(v,a,s);return}const j=ve(e.roots.length,a);r.push({tool:j,leaf:c,hidden:s})},"walk");for(const c of e.roots)i(c,[],!1);return r}g(ee,"providerLeaves");function re(e){if(e.kind!=="branch")return null;const r=[];for(const h of e.children)if(h.kind==="branch"){const _=re(h);_&&r.push(_)}else if("group"in h&&h.group){const{kind:_,name:d,description:A,whenToUse:w,tier:y,summary:k,params:P,output:o,effects:p,group:m}=h;r.push({kind:_,name:d,description:A,whenToUse:w,...y===void 0?{}:{tier:y},summary:k,params:P,output:o,effects:p,group:m})}if(r.length===0)return null;const{kind:i,name:c,description:t,whenToUse:n,tier:a,summary:s,model:j,rootEntry:v}=e;return{kind:i,name:c,description:t,whenToUse:n,...a===void 0?{}:{tier:a},summary:s,...j===void 0?{}:{model:j},...v===void 0?{}:{rootEntry:v},children:r}}g(re,"providerHelpBranch");async function $r(e,r){const i=await H(r),c=ee(i.commands),t=me("user").find(n=>n.name===r)?.manifest.description??r;return{provider:r,status:"available",major:i.major,display_name:t,transport:i.transport.kind,groups:i.provider.groups.filter(n=>ae(e,`${r}:${n.group}`)).map(n=>({group:n.group,leaves:c.filter(({leaf:a,hidden:s})=>!s&&a.group===n.group).map(({tool:a,leaf:s})=>({tool:a,description:s.description,summary:s.summary,params:s.params,output:s.output}))})),commands:i.commands.roots.flatMap(n=>{const a=re(n);return a?[a]:[]})}}g($r,"providerTools");async function ze(e){return await Re(e.headers["crtr-receipt"],e)}g(ze,"onProviderResponse");async function Qe(e){if(e.class==="person"){const r=(await de())?.sub;if(!r)throw new l("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}g(Qe,"identity");function Ve(e,r){return[`${e.id}:${r.group}`]}g(Ve,"callScope");function Ke(e,r){const i=ee(e.commands).find(c=>c.tool===r);if(!i)throw new l("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!i.leaf.group)throw new l("not_found",`tool "${r}" has no group`,{param:"tool"});return i.leaf}g(Ke,"leafFor");function x(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}g(x,"errorBody");function We(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const i=Number(r);if(Number.isFinite(i)&&i>=0)return i;const c=Date.parse(r);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}g(We,"retryAfter");function N(e,r,i,c=!1){const t=x(r),n=typeof t.code=="string"?t.code:void 0,a=typeof t.message=="string"?t.message:`provider answered HTTP ${e}`,s=typeof t.field=="string"?t.field:void 0,v={origin:"provider",details:{code:n??null,message:a,...s?{field:s}:{},...typeof t.next=="string"?{next:t.next}:{},...t.received!==void 0?{received:t.received}:{},status:e},...s?{param:s}:{}};return e===429?new l("provider_rate_limited",a,{...v,retry_after_s:We(i["retry-after"])}):e===401||e>=500?new l("provider_unavailable",a,v):e===403&&n==="provider_refused"?new l("provider_refused",a,v):e===403&&n==="approval_required"?new l("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...v,status:e}):new l("provider_error",a,{...v,status:e})}g(N,"mapProviderResponse");function te(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}g(te,"approvalShape");async function Xe(e){const r=e.origin!==void 0?Z({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):Z({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await He(e.node_id))?.profile_id??void 0:void 0,machine:Je()!==null?Le():void 0});return r===""?{}:{"Crtr-Caller":r}}g(Xe,"crtrCallerHeader");async function qr(e,r,i){const c=ge(r.provider);if(c===null)throw new l("not_found",`provider "${r.provider}" is not available`,{param:"provider"});const t={...r,provider:c},n=new AbortController,a=g(()=>n.abort(i.signal.reason),"abort");i.signal.aborted?a():i.signal.addEventListener("abort",a,{once:!0});let s=i.requestId;const j=i.traceId??qe(),v=Pe({request_id:s,run_id:e.run_id,node_id:e.node_id,controller:n});let h,_,d,A,w,y,k,P="allowed";try{try{await he(t.provider)}catch{throw ye(t.provider)}let o=await H(t.provider,t.version),p=!1,m,O,$,G=!1,z=!1,F,Q=0;for(;;){const b=Ke(o,t.tool);if(d=`${o.id}:${b.group}`,se(e,o.id,b.group),o.transport.kind!=="machine"&&t.machine!==void 0)throw new l("invalid_argument",`provider "${o.id}" is not served by a device, so it takes no machine`,{param:"machine"});o.transport.kind==="machine"&&F===void 0&&(F=await Ie(t.machine,void 0,{newest:Y(o.major,t.tool)}),Q=Date.now()+(o.commands.timeouts?.requestMs??Be));let D=null;if(t.approval&&!G){const u=await _e(e,t,te(o,b));$=u.option,D=u.prepared,k=t.approval,s=u.requestId,G=!0}const T={params:b.params.filter(u=>"type"in u&&u.type==="file").map(u=>u.name),output:b.output.find(u=>u.type==="file")?.name??null},V=JSON.stringify(T);if(O!==void 0&&O!==V)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});O=V,D&&(m=await K(e,T,t.arguments,D,{signal:n.signal})),m??=await be(e,T,t.arguments,{signal:n.signal});const{sub:q,grantee:I}=await Qe(e);if(o.transport.kind==="machine"){const u=await Ye(e,o,t,b,m,{sub:q,requestId:s,deadlineAt:Q,signal:n.signal,machine:F});return y=u.responder,h=u.cleanup,{status:200,body:u.result}}const C=await je({audience:o.transport.endpoint,sub:q,grantee:I,initiating_app:I,scope:Ve(o,b)},{signal:n.signal});A=C.jti;const U=e.class==="person"?Ge:I,E=le({call:"tool",tokenPayer:C.payer,sub:q}),L=await ue({scope:"call",payer:E.payer,funding:E.funding,sub:q,grantee:U,provider:`app:${o.id}`});if(L!==null)throw await fe({request:s,kind:"tool",what:`${o.id}:${t.tool}`,usd:0,payer:E.payer,funding:E.funding,sub:q,grantee:U,node:e.node_id,run:e.run_id,provider:`app:${o.id}`,causedBy:i.causedBy??null,refused:ce(L)}),pe(L);const oe={registration:{name:o.id,endpoint:o.transport.endpoint},...o.commands.baseUrl?{baseUrl:o.commands.baseUrl}:{},...o.commands.timeouts?{timeouts:o.commands.timeouts}:{},params:b.params,rest:b.rest,output:b.output,commandPath:[o.id,...t.tool.split(" ")]},ne=$e(oe,m.arguments,void 0,{authorization:`Bearer ${C.access_token}`,requestId:s,traceId:j,fileMode:"pass-through",extraHeaders:{...i.causedBy?{"Crtr-Caused-By":i.causedBy}:{},...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await Xe(e),...$?{"Crtr-Approval":$}:{},...m.headers}});_??=Date.now()+(o.commands.timeouts?.requestMs??3e4);let f;try{if(f=await Ce(ne,{connectMs:o.commands.timeouts?.connectMs,requestMs:o.commands.timeouts?.requestMs,deadlineAt:_,signal:n.signal}),n.signal.aborted)throw n.signal.reason??new Error("provider call aborted")}catch(u){throw n.signal.aborted?n.signal.reason??u:u instanceof Ee?new l("provider_unavailable",u.message,{origin:"provider",details:{status:null,code:u.kind}}):u}if(w??=await ze({provider:o.id,tool:t.tool,request_id:s,run_id:e.run_id,sub:q,grantee:I,payer:C.payer,ledger_grantee:U,funding:E.funding,node_id:e.node_id,caused_by:i.causedBy??null,jti:C.jti,status:f.status,headers:f.headers}),y=f.headers["crtr-responder"],f.status===401&&ke(C.key),f.status>=200&&f.status<300){const u=f.body;if(!u||typeof u!="object"||Array.isArray(u))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let S;try{S=await W(e,m,u,{signal:n.signal})}catch(R){throw R instanceof l&&R.code==="provider_error"?new l("provider_bad_response",R.message,{origin:"provider",details:{status:f.status,message:R.message}}):R}h=S.cleanup;const B=J(b.output,S.result);if(B)throw new l("provider_bad_response",`provider result has an invalid output field "${B.field}"`,{origin:"provider",param:B.field,details:{status:f.status,...B}});if(n.signal.aborted)throw n.signal.reason??new Error("provider call aborted");return{status:200,body:S.result}}const M=x(f.body);if(f.status===403&&M.code==="approval_required"&&!$&&!z){z=!0;const u=await Ae(e,t,s,M.question,n.signal,te(o,b),m);if(k=u.questionId,$=u.option,m=await K(e,T,t.arguments,m,{signal:n.signal}),_=void 0,n.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(f.status===403&&M.code==="approval_required"&&$)throw N(f.status,f.body,f.headers,!0);if(f.status!==401&&M.manifest_stale===!0&&!p){p=!0,await we(o.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}}),o=await H(o.id,t.version);continue}throw N(f.status,f.body,f.headers,!!$)}}catch(o){throw o instanceof l&&o.question_id&&(k=o.question_id),y===void 0&&o instanceof l&&typeof o.responder=="string"&&(y=o.responder),P=`refused:${o instanceof l?o.code:"server_error"}`,await h?.(),o}finally{try{await Te({request_id:s,caller:e,provider:t.provider,tool:t.tool,scope_checked:d,outcome:P,jti:A,receipt_id:w,responder:y,question_id:k})}finally{v()}i.signal.removeEventListener("abort",a)}}g(qr,"callProvider");async function Ye(e,r,i,c,t,n){const{machine:a}=n,s=await xe(e,n.sub),j={machine:a,envelope:s,signal:n.signal,deadlineAt:n.deadlineAt,mapFailure:N};if(Y(r.major,i.tool)){const p=await Oe(j,t.arguments,r.major),m=J(c.output,p);if(m)throw new l("provider_bad_response",`the libraries result has an invalid output field "${m.field}"`,{origin:"provider",param:m.field,details:{status:200,...m}});return{result:p,responder:void 0,cleanup:void 0}}let v=t.arguments,h=null,_="";if(Ne(r.major,i.tool,t.arguments)){const p=await Fe(j,t.arguments,r.major);if(n.deadlineAt-Date.now()<=0)throw X(a,"timeout","");p!==null&&(v=p.arguments,h=p.services,_=p.release)}const d=await Me(a,{tool:i.tool,arguments:v,version:r.major,requestId:n.requestId,runId:e.run_id,deadlineAt:n.deadlineAt,upload:t.upload===null?null:{id:t.upload.upload_id,link:t.upload.upload_link}},s,n.signal);if(n.signal.aborted)throw n.signal.reason??new Error("provider call aborted");if("unreachable"in d)throw X(a,d.unreachable.reason,d.unreachable.message);const A=d.headers["crtr-responder"],w=x(d.body);if(d.status<200||d.status>=300){const p=Se(a,d.status,typeof w.code=="string"?w.code:void 0,typeof w.message=="string"?w.message:void 0,d.body),m=p===null&&d.status>=500&&typeof w.code=="string"?new l("provider_error",typeof w.message=="string"?w.message:`the device answered HTTP ${d.status}`,{origin:"runtime",status:d.status,details:{code:w.code,status:d.status}}):null;throw Object.assign(p??m??N(d.status,d.body,d.headers),{responder:A})}const y=d.body&&typeof d.body=="object"&&!Array.isArray(d.body)?d.body.result:void 0;if(!y||typeof y!="object"||Array.isArray(y))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:d.status,message:"the device result is not a JSON object"}}),{responder:A});let k=y;if(h!==null){const p=De(k,h);if(Ue(a,s,_,p.calls),"error"in p)throw Object.assign(p.error,{responder:A});k=p.result}let P;try{P=await W(e,t,k,{signal:n.signal})}catch(p){throw p instanceof l&&p.code==="provider_error"?Object.assign(new l("provider_bad_response",p.message,{origin:"provider",details:{status:d.status,message:p.message}}),{responder:A}):p}const o=J(c.output,P.result);if(o)throw await P.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${o.field}"`,{origin:"provider",param:o.field,details:{status:d.status,...o}}),{responder:A});return{result:P.result,responder:A,cleanup:P.cleanup}}g(Ye,"callDevice");export{qr as callProvider,Xe as crtrCallerHeader,Qe as identity,N as mapProviderResponse,ze as onProviderResponse,ee as providerLeaves,$r as providerTools};
1
+ var de=Object.defineProperty;var m=(e,r)=>de(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as x}from"../../api/command-manifest/result.js";import{callerAllows as ue,requireGroup as le}from"../../daemon/api/handlers/providers.js";import{callerTerminalGrant as pe}from"../grants/terminal.js";import{findRefusal as ce,payerOf as fe,refusalError as me,refusedMark as ve,writeCharge as ge}from"../ledger/index.js";import{listInstalledPlugins as he}from"../resolver.js";import{ProviderError as l}from"./errors.js";import{resolveProviderRef as we}from"./roots.js";import{providerToolPath as ye}from"../plugin-objects-manifest.js";import{ensureProviderBundle as be,refreshProviderBundle as X,resolveProviderBundle as O,unavailableProviderError as _e}from"./bundles.js";import{prepareFileCall as Ae,resignForRetry as Y,resolveFileResult as Z}from"./files.js";import{consumeApproval as je,requireProviderApproval as ke}from"./approval.js";import{outboundToken as Pe}from"./mint.js";import{discardFile as $e}from"./token-cache.js";import{registerProviderCall as qe}from"./inflight.js";import{buildRequestPlan as Ce}from"../command-plugins/transport/http-plan.js";import{newTraceId as Ee}from"../../shared/traceparent.js";import{sendProviderRequest as Re,ProviderTransportError as Te}from"./request.js";import{acceptProviderReceipt as Ie}from"./receipts.js";import{writeProviderAudit as Me}from"./audit.js";import{selectDevice as Se,sendDeviceCall as Be,deviceFailure as Ne,deviceUnreachable as ee,DEFAULT_DEVICE_REQUEST_MS as Oe}from"./device.js";import{isLibsSearch as re,wantsBundling as Fe,callLibsSearch as De,bundleExec as Ue,mapLibOutcome as Le,reportLibCalls as Je}from"./device-libs.js";import{ownMachineId as He}from"../peer/directory.js";import{directoryIssuer as xe}from"../directory/issuer.js";import{formatCrtrCaller as te}from"../command-plugins/caller-header.js";import{getNode as Ge}from"../canvas/canvas.js";import{envelopeForProviderCaller as ze}from"../peer/envelope.js";const Qe="app:terminal";function oe(e){const r=[],n=m((c,t,i)=>{const s=[...t,c.name],d=i||c.tier==="hidden";if(c.kind==="branch"){for(const v of c.children)n(v,s,d);return}const j=ye(e.roots.length,s);r.push({tool:j,leaf:c,hidden:d})},"walk");for(const c of e.roots)n(c,[],!1);return r}m(oe,"providerLeaves");function ie(e){if(e.kind!=="branch")return null;const r=[];for(const g of e.children)if(g.kind==="branch"){const b=ie(g);b&&r.push(b)}else if("group"in g&&g.group){const{kind:b,name:u,description:_,whenToUse:h,tier:w,summary:k,params:P,output:o,effects:p,group:A}=g;r.push({kind:b,name:u,description:_,whenToUse:h,...w===void 0?{}:{tier:w},summary:k,params:P,output:o,effects:p,group:A})}if(r.length===0)return null;const{kind:n,name:c,description:t,whenToUse:i,tier:s,summary:d,model:j,rootEntry:v}=e;return{kind:n,name:c,description:t,whenToUse:i,...s===void 0?{}:{tier:s},summary:d,...j===void 0?{}:{model:j},...v===void 0?{}:{rootEntry:v},children:r}}m(ie,"providerHelpBranch");async function Cr(e,r){const n=await O(r),c=oe(n.commands),t=he("user").find(i=>i.name===r)?.manifest.description??r;return{provider:r,status:"available",major:n.major,display_name:t,transport:n.transport.kind,groups:n.provider.groups.filter(i=>ue(e,`${r}:${i.group}`)).map(i=>({group:i.group,leaves:c.filter(({leaf:s,hidden:d})=>!d&&s.group===i.group).map(({tool:s,leaf:d})=>({tool:s,description:d.description,summary:d.summary,params:d.params,output:d.output}))})),commands:n.commands.roots.flatMap(i=>{const s=ie(i);return s?[s]:[]})}}m(Cr,"providerTools");async function Ve(e){return await Ie(e.headers["crtr-receipt"],e)}m(Ve,"onProviderResponse");async function Ke(e){if(e.class==="person"){const r=(await pe())?.sub;if(!r)throw new l("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}m(Ke,"identity");function We(e,r){return[`${e.id}:${r.group}`]}m(We,"callScope");function Xe(e,r){const n=oe(e.commands).find(c=>c.tool===r);if(!n)throw new l("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new l("not_found",`tool "${r}" has no group`,{param:"tool"});return n.leaf}m(Xe,"leafFor");function G(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}m(G,"errorBody");function Ye(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const n=Number(r);if(Number.isFinite(n)&&n>=0)return n;const c=Date.parse(r);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}m(Ye,"retryAfter");function F(e,r,n,c=!1){const t=G(r),i=typeof t.code=="string"?t.code:void 0,s=typeof t.message=="string"?t.message:`provider answered HTTP ${e}`,d=typeof t.field=="string"?t.field:void 0,v={origin:"provider",details:{code:i??null,message:s,...d?{field:d}:{},...typeof t.next=="string"?{next:t.next}:{},...t.received!==void 0?{received:t.received}:{},status:e},...d?{param:d}:{}};return e===429?new l("provider_rate_limited",s,{...v,retry_after_s:Ye(n["retry-after"])}):e===401||e>=500?new l("provider_unavailable",s,v):e===403&&i==="provider_refused"?new l("provider_refused",s,v):e===403&&i==="approval_required"?new l("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...v,status:e}):new l("provider_error",s,{...v,status:e})}m(F,"mapProviderResponse");function ne(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}m(ne,"approvalShape");async function Ze(e){const r=e.origin!==void 0?te({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):te({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await Ge(e.node_id))?.profile_id??void 0:void 0,machine:xe()!==null?He():void 0});return r===""?{}:{"Crtr-Caller":r}}m(Ze,"crtrCallerHeader");async function Er(e,r,n){const c=we(r.provider);if(c===null)throw new l("not_found",`provider "${r.provider}" is not available`,{param:"provider"});const t={...r,provider:c},i=new AbortController,s=m(()=>i.abort(n.signal.reason),"abort");n.signal.aborted?s():n.signal.addEventListener("abort",s,{once:!0});let d=n.requestId;const j=n.traceId??Ee(),v=qe({request_id:d,run_id:e.run_id,node_id:e.node_id,controller:i});let g,b,u,_,h,w,k,P="allowed";try{try{await be(t.provider)}catch{throw _e(t.provider)}let o=await O(t.provider,t.version),p=!1,A=!1,$,D,C,z=!1,Q=!1,U,V=0;for(;;){const y=Xe(o,t.tool);if(u=`${o.id}:${y.group}`,le(e,o.id,y.group),o.transport.kind!=="machine"&&t.machine!==void 0)throw new l("invalid_argument",`provider "${o.id}" is not served by a device, so it takes no machine`,{param:"machine"});o.transport.kind==="machine"&&U===void 0&&(U=await Se(t.machine,void 0,{newest:re(o.major,t.tool)}),V=Date.now()+(o.commands.timeouts?.requestMs??Oe));let L=null;if(t.approval&&!z){const a=await je(e,t,ne(o,y));C=a.option,L=a.prepared,k=t.approval,d=a.requestId,z=!0}const M={params:y.params.filter(a=>"type"in a&&a.type==="file").map(a=>a.name),output:y.output.find(a=>a.type==="file")?.name??null},K=JSON.stringify(M);if(D!==void 0&&D!==K)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});D=K,L&&($=await Y(e,M,t.arguments,L,{signal:i.signal})),$??=await Ae(e,M,t.arguments,{signal:i.signal});const{sub:R,grantee:S}=await Ke(e);if(o.transport.kind==="machine"){const a=await er(e,o,t,y,$,{sub:R,requestId:d,deadlineAt:V,signal:i.signal,machine:U});return w=a.responder,g=a.cleanup,{status:200,body:a.result}}const W=o.transport.endpoint;let E;try{E=await Pe({audience:W,sub:R,grantee:S,initiating_app:S,scope:We(o,y)},{signal:i.signal})}catch(a){if(!(a instanceof l)||a.details?.directory_error!=="invalid_target"||A)throw a;A=!0,await X(o.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}});const q=await O(o.id,t.version);if(q.transport.kind==="machine"||q.transport.endpoint===W)throw a;o=q;continue}_=E.jti;const J=e.class==="person"?Qe:S,T=fe({call:"tool",tokenPayer:E.payer,sub:R}),H=await ce({scope:"call",payer:T.payer,funding:T.funding,sub:R,grantee:J,provider:`app:${o.id}`});if(H!==null)throw await ge({request:d,kind:"tool",what:`${o.id}:${t.tool}`,usd:0,payer:T.payer,funding:T.funding,sub:R,grantee:J,node:e.node_id,run:e.run_id,provider:`app:${o.id}`,causedBy:n.causedBy??null,refused:ve(H)}),me(H);const ae={registration:{name:o.id,endpoint:o.transport.endpoint},...o.commands.baseUrl?{baseUrl:o.commands.baseUrl}:{},...o.commands.timeouts?{timeouts:o.commands.timeouts}:{},params:y.params,rest:y.rest,output:y.output,commandPath:[o.id,...t.tool.split(" ")]},se=Ce(ae,$.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:d,traceId:j,fileMode:"pass-through",extraHeaders:{...n.causedBy?{"Crtr-Caused-By":n.causedBy}:{},...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await Ze(e),...C?{"Crtr-Approval":C}:{},...$.headers}});b??=Date.now()+(o.commands.timeouts?.requestMs??3e4);let f;try{if(f=await Re(se,{connectMs:o.commands.timeouts?.connectMs,requestMs:o.commands.timeouts?.requestMs,deadlineAt:b,signal:i.signal}),i.signal.aborted)throw i.signal.reason??new Error("provider call aborted")}catch(a){throw i.signal.aborted?i.signal.reason??a:a instanceof Te?new l("provider_unavailable",a.message,{origin:"provider",details:{status:null,code:a.kind}}):a}if(h??=await Ve({provider:o.id,tool:t.tool,request_id:d,run_id:e.run_id,sub:R,grantee:S,payer:E.payer,ledger_grantee:J,funding:T.funding,node_id:e.node_id,caused_by:n.causedBy??null,jti:E.jti,status:f.status,headers:f.headers}),w=f.headers["crtr-responder"],f.status===401&&$e(E.key),f.status>=200&&f.status<300){const a=f.body;if(!a||typeof a!="object"||Array.isArray(a))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let q;try{q=await Z(e,$,a,{signal:i.signal})}catch(I){throw I instanceof l&&I.code==="provider_error"?new l("provider_bad_response",I.message,{origin:"provider",details:{status:f.status,message:I.message}}):I}g=q.cleanup;const N=x(y.output,q.result);if(N)throw new l("provider_bad_response",`provider result has an invalid output field "${N.field}"`,{origin:"provider",param:N.field,details:{status:f.status,...N}});if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");return{status:200,body:q.result}}const B=G(f.body);if(f.status===403&&B.code==="approval_required"&&!C&&!Q){Q=!0;const a=await ke(e,t,d,B.question,i.signal,ne(o,y),$);if(k=a.questionId,C=a.option,$=await Y(e,M,t.arguments,$,{signal:i.signal}),b=void 0,i.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(f.status===403&&B.code==="approval_required"&&C)throw F(f.status,f.body,f.headers,!0);if(f.status!==401&&B.manifest_stale===!0&&!p){p=!0,await X(o.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}}),o=await O(o.id,t.version);continue}throw F(f.status,f.body,f.headers,!!C)}}catch(o){throw o instanceof l&&o.question_id&&(k=o.question_id),w===void 0&&o instanceof l&&typeof o.responder=="string"&&(w=o.responder),P=`refused:${o instanceof l?o.code:"server_error"}`,await g?.(),o}finally{try{await Me({request_id:d,caller:e,provider:t.provider,tool:t.tool,scope_checked:u,outcome:P,jti:_,receipt_id:h,responder:w,question_id:k})}finally{v()}n.signal.removeEventListener("abort",s)}}m(Er,"callProvider");async function er(e,r,n,c,t,i){const{machine:s}=i,d=await ze(e,i.sub),j={machine:s,envelope:d,signal:i.signal,deadlineAt:i.deadlineAt,mapFailure:F};if(re(r.major,n.tool)){const p=await De(j,t.arguments,r.major),A=x(c.output,p);if(A)throw new l("provider_bad_response",`the libraries result has an invalid output field "${A.field}"`,{origin:"provider",param:A.field,details:{status:200,...A}});return{result:p,responder:void 0,cleanup:void 0}}let v=t.arguments,g=null,b="";if(Fe(r.major,n.tool,t.arguments)){const p=await Ue(j,t.arguments,r.major);if(i.deadlineAt-Date.now()<=0)throw ee(s,"timeout","");p!==null&&(v=p.arguments,g=p.services,b=p.release)}const u=await Be(s,{tool:n.tool,arguments:v,version:r.major,requestId:i.requestId,runId:e.run_id,deadlineAt:i.deadlineAt,upload:t.upload===null?null:{id:t.upload.upload_id,link:t.upload.upload_link}},d,i.signal);if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");if("unreachable"in u)throw ee(s,u.unreachable.reason,u.unreachable.message);const _=u.headers["crtr-responder"],h=G(u.body);if(u.status<200||u.status>=300){const p=Ne(s,u.status,typeof h.code=="string"?h.code:void 0,typeof h.message=="string"?h.message:void 0,u.body),A=p===null&&u.status>=500&&typeof h.code=="string"?new l("provider_error",typeof h.message=="string"?h.message:`the device answered HTTP ${u.status}`,{origin:"runtime",status:u.status,details:{code:h.code,status:u.status}}):null;throw Object.assign(p??A??F(u.status,u.body,u.headers),{responder:_})}const w=u.body&&typeof u.body=="object"&&!Array.isArray(u.body)?u.body.result:void 0;if(!w||typeof w!="object"||Array.isArray(w))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:u.status,message:"the device result is not a JSON object"}}),{responder:_});let k=w;if(g!==null){const p=Le(k,g);if(Je(s,d,b,p.calls),"error"in p)throw Object.assign(p.error,{responder:_});k=p.result}let P;try{P=await Z(e,t,k,{signal:i.signal})}catch(p){throw p instanceof l&&p.code==="provider_error"?Object.assign(new l("provider_bad_response",p.message,{origin:"provider",details:{status:u.status,message:p.message}}),{responder:_}):p}const o=x(c.output,P.result);if(o)throw await P.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${o.field}"`,{origin:"provider",param:o.field,details:{status:u.status,...o}}),{responder:_});return{result:P.result,responder:_,cleanup:P.cleanup}}m(er,"callDevice");export{Er as callProvider,Ze as crtrCallerHeader,Ke as identity,F as mapProviderResponse,Ve as onProviderResponse,oe as providerLeaves,Cr as providerTools};
@@ -1 +1 @@
1
- var te=Object.defineProperty;var d=(e,t)=>te(e,"name",{value:t,configurable:!0});import{createHash as oe}from"node:crypto";import{closeSync as D,constants as ne,createWriteStream as ie}from"node:fs";import{basename as re,extname as ae,isAbsolute as se,join as C}from"node:path";import{Transform as de}from"node:stream";import{pipeline as ue}from"node:stream/promises";import{appSpace as I}from"../layout.js";import{RunError as F}from"../runs/operations.js";import{createFileBeneath as le,existsBeneath as M,openBeneath as ce,removeTreeBeneath as N,setModeBeneath as fe,unlinkBeneath as pe,withDirBeneath as he}from"../spaces/open-beneath.js";import{ensureAppSpace as we}from"../spaces/space.js";import{isCloudRole as _e}from"../canvas/person-state.js";import{invalidStorageName as H,storageClient as T,StorageRefused as U,UPLOAD_MAX_BYTES as B}from"../storage/client.js";import{contentTypeFor as me,openCallerFile as ge}from"../storage/download.js";import{withCanvasWrite as ye}from"../canvas/db.js";import{deleteUploadUse as be,getUpload as $e,insertUpload as ve,insertUploadUses as Se,setUploadSize as Ue,uploadUseAt as ze}from"../storage/tables.js";import{ProviderError as u}from"./errors.js";const Ee=11e4,Ae="app:terminal";function P(e){if(e.class==="person")return e.grantee??Ae;if(!e.grantee)throw new u("server_error","provider caller has no grantee");return e.grantee}d(P,"grantee");function Ce(e){return e.class==="person"?null:{app:P(e),resolved:e.resolved??[],launched:e.class==="broker"?e.nodeScopes:null}}d(Ce,"fileCaller");function y(e){return e instanceof u?e:e instanceof U?new u("storage_unavailable",`storage refused: ${e.message}`):e instanceof F&&e.code==="storage_unavailable"?new u("storage_unavailable",e.message):e}d(y,"storageFailure");const b=d(e=>new u("provider_error",e,{origin:"provider"}),"providerError");function Fe(e,t,n){if(typeof n!="string"||!n||!se(n)||n.includes("\0"))throw new u("invalid_request",`${t} must be an absolute runtime path`,{param:t});let r;try{r=ge(Ce(e),n)}catch(i){throw i instanceof F?i.code==="scope_missing"?new u("scope_missing",i.message,{param:t,...i.scope?{scope:i.scope}:{}}):i.code==="not_found"||i.code==="invalid_request"?new u("not_found",`no such file: ${n}`,{param:t}):i:i}try{if(r.size>B)throw new u("invalid_request",`${t} exceeds 100 MiB`,{param:t});const i=re(r.path),o=H(i);if(o)throw new u("invalid_request",`${t}: file name cannot be sent: ${o}`,{param:t});return{param:t,file:r,name:i,mediaType:me(r.path)}}catch(i){throw D(r.fd),i}}d(Fe,"checkFileArgument");async function Te(e,t){try{const n=await e.createOutbound({name:t.name,size:t.file.size,media_type:t.mediaType});await e.putFileBytes(n.upload_link,t.file.fd,t.file.size,t.mediaType);const r=await e.signOutbound(n.object_id);return{param:t.param,path:t.file.path,name:t.name,object_id:n.object_id,url:r.url}}catch(n){throw y(n)}}d(Te,"copyOutbound");async function k(e,t,n,r){const i=[];try{for(const a of t)n[a]===void 0||n[a]===null||i.push(Fe(e,a,n[a]));if(!i.length)return[];let o;try{o=r()}catch(a){throw y(a)}const c=[];for(const a of i)c.push(await Te(o,a));return c}finally{for(const o of i)D(o.file.fd)}}d(k,"copyArguments");function q(e){const t=H(e);if(t)throw new u("server_error",`file output ${e} cannot name an upload: ${t}`)}d(q,"checkOutputField");async function W(e,t,n){q(t);const r=P(e),i="application/octet-stream";let o;try{o=await n().createResultUpload({name:t,media_type:i,max_size:B})}catch(c){throw y(c)}return await ve({upload_id:o.upload_id,grantee:r,name:t,media_type:i,size:B,created_at:new Date().toISOString()}),{field:t,upload_id:o.upload_id,upload_link:o.upload_link,grantee:r}}d(W,"createResultUpload");function X(e){return e?{"Crtr-Upload-Id":e.upload_id,"Crtr-Upload-Link":e.upload_link}:{}}d(X,"uploadHeaders");function Y(e,t){const n={...e};for(const r of t)n[r.param]=r.url;return n}d(Y,"withUrls");async function Je(e,t,n,r={}){const i=r.storage??T;t.output&&q(t.output);const o=await k(e,t.params,n,i),c=t.output?await W(e,t.output,i):null;return{arguments:Y(n,o),copies:o,upload:c,headers:X(c)}}d(Je,"prepareFileCall");async function Ke(e,t,n,r,i={}){const o=i.storage??T,c=[];for(const s of r.copies)try{const p=await o().signOutbound(s.object_id);c.push({...s,url:p.url})}catch(p){if(!(p instanceof U))throw y(p);c.push(...await k(e,[s.param],n,o))}let a=r.upload;if(a)try{const s=await o().signUploadLink(a.upload_id);a={...a,upload_link:s.upload_link}}catch(s){if(!(s instanceof U))throw y(s);s.status===404&&(a=await W(e,t.output??a.field,o))}return{arguments:Y(n,c),copies:c,upload:a,headers:X(a)}}d(Ke,"resignForRetry");const Be=/^[0-9a-f]{64}$/i;function Re(e,t){if(t===1)return e;const n=ae(e);return n&&n!==e?`${e.slice(0,-n.length)}-${t}${n}`:`${e}-${t}`}d(Re,"candidate");async function Qe(e,t,n,r={}){const i={result:n,file:null,cleanup:d(async()=>{},"cleanup")},o=t.upload;if(!o||!n||typeof n!="object"||Array.isArray(n))return i;const c=n,a=c[o.field];if(a==null)return i;if(typeof a!="object"||Array.isArray(a))throw b(`${o.field} must be {upload_id, size, sha256}`);const s=a;if(typeof s.upload_id!="string"||typeof s.size!="number"||!Number.isSafeInteger(s.size)||s.size<0||typeof s.sha256!="string"||!Be.test(s.sha256))throw b(`${o.field} must be {upload_id, size, sha256}`);if(s.upload_id!==o.upload_id)throw b(`${o.field} names an upload other than Crtr-Upload-Id`);const p=s.size,G=s.sha256.toLowerCase(),z=AbortSignal.timeout(r.budgetMs??Ee),R=r.signal?AbortSignal.any([z,r.signal]):z;let h;try{h=await(r.storage??T)().fetchUpload(o.upload_id,R)}catch(_){throw _ instanceof U?b(`${o.field} names an upload with nothing stored`):y(_)}if(h.size===null)throw h.body.destroy(),new u("storage_unavailable",`storage did not say how many bytes ${o.field} holds`);if(h.size!==p)throw h.body.destroy(),b(`${o.field} claims ${p} bytes; storage holds ${h.size}`);if(_e())throw new u("storage_unavailable","a cloud role has no folder to land a file result in");const E=o.grantee,$=e.node_id!==null,J=await we(E),f=$?I(E).nodes:I(E).files,A=$?C(f,e.node_id):f,v=$?C(A,"attachments"):f,K={uid:process.getuid?.()??0,gid:J.gid},O=$&&!M(f,A),Q=$&&!O&&!M(f,v);let w=null,S=null,x=!1;const L=d(async()=>{if(!x){if(x=!0,S)try{await be(S)}catch{}if(w)try{pe(f,w)}catch{}try{O?N(f,A):Q&&N(f,v)}catch{}}},"cleanup");try{he(f,v,()=>{},!0);for(let l=1;;l++){const m=C(v,Re(o.field,l));if(!await ze(m))try{le(f,m,Buffer.alloc(0),432),w=m;break}catch(g){if(g.code!=="EEXIST")throw g}}let _=0;const j=oe("sha256"),V=new de({transform(l,m,g){_+=l.length,j.update(l),g(null,l)}}),Z=ce(f,w,ne.O_WRONLY);try{await ue(h.body,V,ie("",{fd:Z,autoClose:!0}),{signal:R})}catch(l){throw z.aborted?new u("storage_unavailable","the file result could not be fetched within 110 s"):l instanceof F?y(l):new u("storage_unavailable",`storage stopped sending ${o.field}: ${l instanceof Error?l.message:String(l)}`)}if(_!==p)throw new u("storage_unavailable",`storage sent ${_} of ${p} bytes of ${o.field}`);if(j.digest("hex")!==G)throw b(`${o.field}: the stored bytes do not match sha256`);fe(f,w,432,K);const ee=w;await ye(async l=>{const m=await $e(o.upload_id,l);if(!m||m.delete_pending_since)throw new u("not_found",`${o.field}: the upload was deleted while it was fetched`);if(await Ue(o.upload_id,p,l),!e.run_id)return;const g=await l.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(e.run_id);if(!g||g.deleting_at)throw new u("not_found",`run ${e.run_id} was deleted while its file result was fetched`);S={upload_id:o.upload_id,run_id:e.run_id,path:ee},await Se([S],l)})}catch(_){throw h.body.destroy(),await L(),_}return{result:{...c,[o.field]:w},file:{path:w,upload_id:o.upload_id},cleanup:L}}d(Qe,"resolveFileResult");export{Ee as FILE_RESULT_FETCH_BUDGET_MS,Je as prepareFileCall,Ke as resignForRetry,Qe as resolveFileResult};
1
+ var te=Object.defineProperty;var d=(e,t)=>te(e,"name",{value:t,configurable:!0});import{createHash as oe}from"node:crypto";import{closeSync as D,constants as ne,createWriteStream as ie}from"node:fs";import{basename as re,extname as ae,isAbsolute as se,join as C}from"node:path";import{Transform as de}from"node:stream";import{pipeline as ue}from"node:stream/promises";import{appSpace as I}from"../layout.js";import{RunError as F}from"../runs/operations.js";import{createFileBeneath as le,existsBeneath as M,openBeneath as ce,removeTreeBeneath as N,setModeBeneath as fe,unlinkBeneath as pe,withDirBeneath as he}from"../spaces/open-beneath.js";import{ensureAppSpace as we}from"../spaces/space.js";import{isCloudRole as _e}from"../canvas/person-state.js";import{invalidStorageName as H,storageClient as T,StorageRefused as U,UPLOAD_MAX_BYTES as B}from"../storage/client.js";import{contentTypeFor as me,openCallerFile as ge}from"../storage/download.js";import{withCanvasWrite as ye}from"../canvas/db.js";import{deleteUploadUse as be,getUpload as $e,insertUpload as ve,insertUploadUses as Se,setUploadSize as Ue,uploadUseAt as ze}from"../storage/tables.js";import{ProviderError as u}from"./errors.js";const Ee=11e4,Ae="app:terminal";function P(e){if(e.class==="person")return e.grantee??Ae;if(!e.grantee)throw new u("server_error","provider caller has no grantee");return e.grantee}d(P,"grantee");function Ce(e){return e.class==="person"?null:{app:P(e),resolved:e.resolved??[],launched:e.class==="broker"?e.nodeScopes:null}}d(Ce,"fileCaller");function y(e){return e instanceof u?e:e instanceof U?new u("storage_unavailable",`storage refused: ${e.message}`):e instanceof F&&e.code==="storage_unavailable"&&e.retryable?new u("storage_unavailable",e.message):e}d(y,"storageFailure");const b=d(e=>new u("provider_error",e,{origin:"provider"}),"providerError");function Fe(e,t,n){if(typeof n!="string"||!n||!se(n)||n.includes("\0"))throw new u("invalid_request",`${t} must be an absolute runtime path`,{param:t});let r;try{r=ge(Ce(e),n)}catch(i){throw i instanceof F?i.code==="scope_missing"?new u("scope_missing",i.message,{param:t,...i.scope?{scope:i.scope}:{}}):i.code==="not_found"||i.code==="invalid_request"?new u("not_found",`no such file: ${n}`,{param:t}):i:i}try{if(r.size>B)throw new u("invalid_request",`${t} exceeds 100 MiB`,{param:t});const i=re(r.path),o=H(i);if(o)throw new u("invalid_request",`${t}: file name cannot be sent: ${o}`,{param:t});return{param:t,file:r,name:i,mediaType:me(r.path)}}catch(i){throw D(r.fd),i}}d(Fe,"checkFileArgument");async function Te(e,t){try{const n=await e.createOutbound({name:t.name,size:t.file.size,media_type:t.mediaType});await e.putFileBytes(n.upload_link,t.file.fd,t.file.size,t.mediaType);const r=await e.signOutbound(n.object_id);return{param:t.param,path:t.file.path,name:t.name,object_id:n.object_id,url:r.url}}catch(n){throw y(n)}}d(Te,"copyOutbound");async function k(e,t,n,r){const i=[];try{for(const a of t)n[a]===void 0||n[a]===null||i.push(Fe(e,a,n[a]));if(!i.length)return[];let o;try{o=r()}catch(a){throw y(a)}const c=[];for(const a of i)c.push(await Te(o,a));return c}finally{for(const o of i)D(o.file.fd)}}d(k,"copyArguments");function q(e){const t=H(e);if(t)throw new u("server_error",`file output ${e} cannot name an upload: ${t}`)}d(q,"checkOutputField");async function W(e,t,n){q(t);const r=P(e),i="application/octet-stream";let o;try{o=await n().createResultUpload({name:t,media_type:i,max_size:B})}catch(c){throw y(c)}return await ve({upload_id:o.upload_id,grantee:r,name:t,media_type:i,size:B,created_at:new Date().toISOString()}),{field:t,upload_id:o.upload_id,upload_link:o.upload_link,grantee:r}}d(W,"createResultUpload");function X(e){return e?{"Crtr-Upload-Id":e.upload_id,"Crtr-Upload-Link":e.upload_link}:{}}d(X,"uploadHeaders");function Y(e,t){const n={...e};for(const r of t)n[r.param]=r.url;return n}d(Y,"withUrls");async function Je(e,t,n,r={}){const i=r.storage??T;t.output&&q(t.output);const o=await k(e,t.params,n,i),c=t.output?await W(e,t.output,i):null;return{arguments:Y(n,o),copies:o,upload:c,headers:X(c)}}d(Je,"prepareFileCall");async function Ke(e,t,n,r,i={}){const o=i.storage??T,c=[];for(const s of r.copies)try{const p=await o().signOutbound(s.object_id);c.push({...s,url:p.url})}catch(p){if(!(p instanceof U))throw y(p);c.push(...await k(e,[s.param],n,o))}let a=r.upload;if(a)try{const s=await o().signUploadLink(a.upload_id);a={...a,upload_link:s.upload_link}}catch(s){if(!(s instanceof U))throw y(s);s.status===404&&(a=await W(e,t.output??a.field,o))}return{arguments:Y(n,c),copies:c,upload:a,headers:X(a)}}d(Ke,"resignForRetry");const Be=/^[0-9a-f]{64}$/i;function Re(e,t){if(t===1)return e;const n=ae(e);return n&&n!==e?`${e.slice(0,-n.length)}-${t}${n}`:`${e}-${t}`}d(Re,"candidate");async function Qe(e,t,n,r={}){const i={result:n,file:null,cleanup:d(async()=>{},"cleanup")},o=t.upload;if(!o||!n||typeof n!="object"||Array.isArray(n))return i;const c=n,a=c[o.field];if(a==null)return i;if(typeof a!="object"||Array.isArray(a))throw b(`${o.field} must be {upload_id, size, sha256}`);const s=a;if(typeof s.upload_id!="string"||typeof s.size!="number"||!Number.isSafeInteger(s.size)||s.size<0||typeof s.sha256!="string"||!Be.test(s.sha256))throw b(`${o.field} must be {upload_id, size, sha256}`);if(s.upload_id!==o.upload_id)throw b(`${o.field} names an upload other than Crtr-Upload-Id`);const p=s.size,G=s.sha256.toLowerCase(),z=AbortSignal.timeout(r.budgetMs??Ee),R=r.signal?AbortSignal.any([z,r.signal]):z;let h;try{h=await(r.storage??T)().fetchUpload(o.upload_id,R)}catch(_){throw _ instanceof U?b(`${o.field} names an upload with nothing stored`):y(_)}if(h.size===null)throw h.body.destroy(),new u("storage_unavailable",`storage did not say how many bytes ${o.field} holds`);if(h.size!==p)throw h.body.destroy(),b(`${o.field} claims ${p} bytes; storage holds ${h.size}`);if(_e())throw new u("storage_unavailable","a cloud role has no folder to land a file result in");const E=o.grantee,$=e.node_id!==null,J=await we(E),f=$?I(E).nodes:I(E).files,A=$?C(f,e.node_id):f,v=$?C(A,"attachments"):f,K={uid:process.getuid?.()??0,gid:J.gid},O=$&&!M(f,A),Q=$&&!O&&!M(f,v);let w=null,S=null,x=!1;const L=d(async()=>{if(!x){if(x=!0,S)try{await be(S)}catch{}if(w)try{pe(f,w)}catch{}try{O?N(f,A):Q&&N(f,v)}catch{}}},"cleanup");try{he(f,v,()=>{},!0);for(let l=1;;l++){const m=C(v,Re(o.field,l));if(!await ze(m))try{le(f,m,Buffer.alloc(0),432),w=m;break}catch(g){if(g.code!=="EEXIST")throw g}}let _=0;const j=oe("sha256"),V=new de({transform(l,m,g){_+=l.length,j.update(l),g(null,l)}}),Z=ce(f,w,ne.O_WRONLY);try{await ue(h.body,V,ie("",{fd:Z,autoClose:!0}),{signal:R})}catch(l){throw z.aborted?new u("storage_unavailable","the file result could not be fetched within 110 s"):l instanceof F?y(l):new u("storage_unavailable",`storage stopped sending ${o.field}: ${l instanceof Error?l.message:String(l)}`)}if(_!==p)throw new u("storage_unavailable",`storage sent ${_} of ${p} bytes of ${o.field}`);if(j.digest("hex")!==G)throw b(`${o.field}: the stored bytes do not match sha256`);fe(f,w,432,K);const ee=w;await ye(async l=>{const m=await $e(o.upload_id,l);if(!m||m.delete_pending_since)throw new u("not_found",`${o.field}: the upload was deleted while it was fetched`);if(await Ue(o.upload_id,p,l),!e.run_id)return;const g=await l.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(e.run_id);if(!g||g.deleting_at)throw new u("not_found",`run ${e.run_id} was deleted while its file result was fetched`);S={upload_id:o.upload_id,run_id:e.run_id,path:ee},await Se([S],l)})}catch(_){throw h.body.destroy(),await L(),_}return{result:{...c,[o.field]:w},file:{path:w,upload_id:o.upload_id},cleanup:L}}d(Qe,"resolveFileResult");export{Ee as FILE_RESULT_FETCH_BUDGET_MS,Je as prepareFileCall,Ke as resignForRetry,Qe as resolveFileResult};
@@ -4,7 +4,7 @@ var Me=Object.defineProperty;var c=(e,t)=>Me(e,"name",{value:t,configurable:!0})
4
4
  AND e.sequence_number > COALESCE((SELECT MAX(c.sequence_number) FROM run_events c
5
5
  WHERE c.run_id = e.run_id AND c.node_id = e.node_id AND c.type = 'node.turn.completed'), 0)
6
6
  )) LIMIT 1`).get(e.node_id,e.node_id),s=!e.last_settled_at&&e.status!=="done";return t||s||await mt(e)?"running":(await me(e.node_id)).length?"waiting_on_user":await(await A()).prepare(`SELECT 1 FROM deliveries d JOIN nodes n ON n.node_id = d.destination_id
7
- WHERE n.run_id = ? AND ${Ge("d","'armed', 'pending'")}
7
+ WHERE n.run_id = ? AND ${Ge("d","'armed', 'pending', 'handed_off'")}
8
8
  AND (n.status IN ('active', 'idle') OR ${Ye("n")}) LIMIT 1`).get(e.node_id)?"running":"idle"}c(C,"runStatus");async function Wn(e){const t=await O(e);return t!==null&&await C(t)==="running"}c(Wn,"runIsRunning");async function W(e){await Z(e.node_id),e=await O(e.node_id)??e;const t=await C(e),s=t==="settled"?[]:await me(e.node_id),n=await Qe(e.node_id)||void 0,o=t==="settled"?await lt(e):null,m=o?.kind==="result"?await re(e.node_id):void 0,i=Xe(e.node_id);return{run_id:e.node_id,name:e.name,status:t,...o?{outcome:o}:{},...m===void 0?{}:{result:m},...n!==void 0?{last_report:n}:{},...i?{recap:i}:{},pending_question_ids:s,open_notice_ids:t==="settled"?[]:await Et(e.node_id),...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(X())),payer:await J(e.grant_id),scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:await gt(e.node_id,t)}}c(W,"runObject");const z=864e5,Ee=255;function Fn(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>Ee)throw N(`Idempotency-Key must be 1-${Ee} characters`);return e}}c(Fn,"idempotencyKeyOf");function Dt(e){if(Le()){if(!e)throw new R(403,"permission_error","a cloud role serves this call only for a token that names a person");return e}}c(Dt,"cloudPersonScope");function k(e,t){const s=Dt(e);return s===void 0?t:`${s}:${t}`}c(k,"personKey");async function H(e,t,s,n){n&&await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`idempotency:${e}:${t}`),n??=await B();const o=await n.prepare("SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!o||Date.now()-Date.parse(o.created_at)>=z)){if(o.body_hash!==s)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:o.run_id,response:JSON.parse(o.response_json)}}}c(H,"findIdempotent");async function ye(e,t,s,n,o,m,i){await e.prepare("DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,s,new Date(Date.now()-z).toISOString()),await e.prepare("INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,s,n,o,JSON.stringify(m),i)}c(ye,"recordIdempotent");async function qn(e=Date.now()){return Number((await(await B()).prepare("DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-z).toISOString())).changes)}c(qn,"pruneExpiredIdempotencyKeys");async function he(e,t,s,n,o){const m=n.text;let i=0;return{messageId:(await ze(t,{kind:"message",label:m.slice(0,120),senderNodeId:"human",senderName:s,bodyJson:o?{body:m}:{body:m,start_turn:!1},urgency:o?"normal":"deferred",wake:o?1:0,withinEnqueue:c(async(u,l)=>{n.uses.length&&(await le(s,n.uses,u),await pe(n.uses.map(({upload_id:g,path:E,blob_key:y})=>({upload_id:g,path:E,blob_key:y,run_id:t})),u)),i=(await ce(u,t,t,"node.message.accepted",{message_id:l,start_turn:o})).sequence_number},"withinEnqueue")})).messageId,sequence:i}}c(he,"acceptRunMessage");class Re extends Error{static{c(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}const Mt=1440*60*1e3;function Se(e){const t=typeof e=="string"?nt(e):null;if(t===null||!Number.isSafeInteger(t))throw N(`deadline '${String(e)}' is not a duration; use one or more <int>[smhd] parts such as 10m or 1h30m`,"deadline");if(t<=0)throw N("deadline must be greater than zero","deadline");if(t>Mt)throw N("deadline must be at most 24h","deadline");return t}c(Se,"parseRunDeadlineMs");async function Hn(e,t,s,n=we){if(t.isolated&&!it()&&await rt((t.profile===void 0?await te(e.grantee):ne(t.profile)).profileId)===null)throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");t.deadline!==void 0&&Se(t.deadline);const{causedBy:o,...m}=t,i=Q("sha256").update(JSON.stringify(m)).digest("hex");if(s){const a=await H(e.grantee,k(e.sub,s),i);if(a){const p=await q(a.run_id,e.grantee);return!p.pi_pid&&await C(p)==="running"&&(t.output_schema!==void 0&&!p.outcome_at&&await ut(p.node_id)===null&&await re(p.node_id)===void 0&&await oe(p.node_id,"terminal",t.output_schema),await j(p.node_id,{resume:!0,capacity:"freeze"})),a.response}}await yt(e.grantee);const r=t.profile===void 0?await te(e.grantee):ne(t.profile),u=r.manifest.grantee??"app:terminal";if(u!==e.grantee){const a=`crtr:memory:rw:${u}:profile:${r.profileId}`;if(!e.resolved.some(p=>P(p,a))||t.scopes!==null&&!t.scopes.some(p=>P(p,a)))throw new R(403,"scope_missing",`missing scope ${a}`,"profile",void 0,a)}if(t.model!==void 0){const a=t.model;if(tt(a)){if(a.includes("/")&&await J(e.grant)!=="app"){const p=a.lastIndexOf("/"),D=a.slice(0,p),v=a.slice(p+1);if(["ultra","strong","medium","light"].includes(v)&&(await st({kind:"logical",family:D,strength:v,spec:a},Y(e.grantee).files,r.profileId)).every(S=>S.family!==D))throw N(`model '${a}' names a model family that is not configured in this daemon`,"model")}}else if(at(a)===void 0)throw N(`model '${a}' is not a strength (ultra, strong, medium, light), a family/strength, or a provider/model id`,"model")}const l=await Ke(),g=We(Y(e.grantee).nodes,l);if(t.isolated&&t.cwd!==void 0){const a=Ce(g,ke(t.cwd));if(!V(t.cwd)||a===".."||a.startsWith(`..${xe}`)||V(a))throw N("cwd must be inside the isolated root node folder","cwd")}const E=await n(l,e.grantee,t.prompt),y=E.text,d=t.message===void 0?void 0:await n(l,e.grantee,t.message,E.uses.map(a=>a.path)),_=await J(e.grant);let h;try{const a=dt(r,t.model);await et({kind:ot(r),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??g:Y(e.grantee).files,profile:r.profileId,prompt:y,nodeId:l,isolated:t.isolated??!1,...t.causedBy===void 0?{}:{causedBy:t.causedBy},...a===void 0?{}:{model:a},...t.deadline===void 0?{}:{deadlineAt:new Date(Date.now()+Se(t.deadline)).toISOString()},...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:l,event_cursor:1,usage_json:JSON.stringify(X())},withinCreate:c(async(p,D)=>{if(s){const S=await H(e.grantee,k(e.sub,s),i,p);if(S)throw new Re(S)}const v=E.uses;if(v.length&&(await le(e.grantee,v,p),await pe(v.map(({upload_id:S,path:I,blob_key:U})=>({upload_id:S,path:I,blob_key:U,run_id:l})),p)),h={run_id:l,node_id:l,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:_,status:"running",created_at:D.created,metadata:t.metadata,sequence_number:0},await ce(p,l,l,"run.started",{scopes:t.scopes,payer:_,usage:X()}),d){const S=await he(p,l,e.grantee,d,!1);h.message={run_id:l,message_id:S.messageId,sequence_number:S.sequence}}s&&await ye(p,e.grantee,k(e.sub,s),i,l,h,D.created)},"withinCreate")},t.output_schema===void 0?void 0:async p=>{await oe(p,"terminal",t.output_schema)})}catch(a){if(a instanceof Re)return a.winner.response;if(t.model!==void 0&&a instanceof Te&&a.code==="usage")throw N(a.message,"model");if(a instanceof Ie&&(a.code==="isolated_run"||a.code==="invalid_request")){const p=a.details&&typeof a.details=="object"&&"param"in a.details&&typeof a.details.param=="string"?a.details.param:void 0;throw new R(a.status,a.code,a.message,p)}throw a}if(!h)throw new Error("run creation did not commit");return h}c(Hn,"startRun");async function Un(e,t){let s;if(t.after)try{const r=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!r||typeof r!="object"||!("activity"in r)||typeof r.activity!="string"||!("id"in r)||typeof r.id!="string")throw new Error("invalid cursor");s={activity:r.activity,id:r.id}}catch{throw N("invalid list cursor","after")}const n=await(await A()).prepareUncached(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
9
9
  WHERE run_id = node_id AND deleting_at IS NULL ${e===null?"":"AND grantee = ?"}${t.sub===void 0?"":" AND sub = ?"}
10
10
  ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e],...t.sub===void 0?[]:[t.sub]),o=await _e(await x((await _e(n,async({node_id:r})=>!t.visible||await t.visible(r))).filter(({activity:r,node_id:u})=>!s||r<s.activity||r===s.activity&&u<s.id),async r=>({root:await O(r.node_id),activity:r.activity})),async({root:r})=>!t.status||await C(r)===t.status),m=o.slice(0,t.limit),i=m.at(-1);return{runs:await x(m,async({root:r})=>await W(r)),next:o.length>t.limit&&i?Buffer.from(JSON.stringify({activity:i.activity,id:i.root.node_id})).toString("base64url"):null}}c(Un,"listRuns");async function $n(e,t,s){const n=await q(e,t),o=c(async()=>await W(await q(e,t)),"read");return!s||ge.includes(await C(n))?await o():new Promise((m,i)=>{let r=c(()=>{},"unsubscribe");const u=c(()=>{clearTimeout(d),clearInterval(y),r()},"settle"),l=c(async()=>{u();try{m(await o())}catch(_){i(_)}},"finish"),g=c(async()=>{ge.includes(String((await o()).status))&&await l()},"check"),E=c(()=>{g().catch(_=>{u(),i(_)})},"run"),y=setInterval(E,250),d=setTimeout(()=>{l()},s*1e3);wt(e,E).then(_=>{r=_},_=>{u(),i(_)})})}c($n,"waitRun");const At=5;async function Pn(e,t,s=we,n=!0,o){const m=e.status==="canceled"||e.outcome_reason==="closed",i=e.grantee??"app:terminal",r=Q("sha256").update(JSON.stringify(["run.message",e.node_id,t,n])).digest("hex"),u=c(async g=>{const E=await O(e.node_id),y=await(await A()).prepare("SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(g.message_id));return E&&!m&&n&&!E.pi_pid&&y&&await j(e.node_id,{resume:!0,capacity:"freeze"}),g},"replay");if(o){const g=await H(i,k(e.sub,o),r);if(g)return u(g.response)}if(m)throw new R(409,"run_settled","canceled run cannot receive a message");const l=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;for(let g=1;;g++){const E=await s(e.node_id,i,t),y=await F(async d=>{if(o){const a=await H(i,k(e.sub,o),r,d);if(a)return{replayed:a.response}}if(E.uses.length){await _t(d,e.node_id);for(const a of E.uses)if(await bt(e.node_id,a.path,d))return"path_taken"}await l?.(e.node_id);const _=await he(d,e.node_id,i,E,n),h={run_id:e.node_id,message_id:_.messageId,sequence_number:_.sequence};return o&&await ye(d,i,k(e.sub,o),r,e.node_id,h,new Date().toISOString()),{result:h}});if(y==="path_taken"){if(g>=At)throw new R(503,"service_unavailable","attachments of concurrent messages kept taking each other's file names; retry");continue}return"replayed"in y?u(y.replayed):(n&&!e.pi_pid&&await j(e.node_id,{resume:!0,capacity:"freeze"}),y.result)}}c(Pn,"messageRun");async function It(e){const t=c(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let s=0;s<2;s++){const n=await O(e);if(!n)throw G();if(!ae(n)){if(n.streaming)throw t();return}const o=(await K(e))?.executionId;try{await Ze(e)==="turn"&&fe({nodes:[e]});return}catch(m){if(!(m instanceof Ve))throw m;const i=await O(e);if(!i)throw G();if(!i.streaming)return;if(s===0&&ae(i)&&(await K(e))?.executionId!==o)continue;throw t()}}throw t()}c(It,"interruptNode");async function Bn(e,t="stop"){const s=t==="stop"?(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id)).map(({node_id:n})=>n):[e.node_id];return await Promise.all(s.map(n=>It(n))),await W(await O(e.node_id)??e)}c(Bn,"interruptRun");async function jn(e,t={}){const s=(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(i=>i.node_id);fe({run:e.node_id});const n=[];if(await C(e)!=="settled")n.push(...(await de(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"})).closed);else for(const i of s){if(i===e.node_id)continue;const r=await O(i);r&&r.status!=="done"&&r.status!=="canceled"&&r.status!=="dead"&&n.push(...(await de(i,{preserveEmpty:!0,stopReason:"run_canceled"})).closed)}if(await ue({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(i=>se(i,"run_canceled")))).some(i=>!i.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const m=await O(e.node_id);return await W(m??e)}c(jn,"cancelRun");const Ne=200;async function Jn(e,t){if(typeof t!="string"||!t.trim())throw N("name is required","name");const s=t.trim();if(s.length>Ne)throw N(`name exceeds ${Ne} characters`,"name");const n=await Pe(e.node_id,{name:s});return n.window&&je(n.window,Be(n)),await W(n)}c(Jn,"renameRun");async function Yn(e,t={config:St,client:Rt}){await ue({run_id:e.node_id},"the run was deleted");const s=await F(async u=>(await u.prepare("UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),(await u.prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(l=>l.node_id)));if((await Promise.all(s.map(u=>se(u,"run_deleted",{deleting:!0})))).some(u=>!u.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const o=await x(s,async u=>{const l=await Ue(u);if(l.app===null)throw new Error(`run node missing during deletion: ${u}`);return l}),m=await(await A()).prepare("SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:u}of m)try{await ee().delete(u)}catch(l){if(!Je(l))throw l}const i=await F(async u=>{for(const y of s){const d=await He(u,y);d!==null&&d.deleted_at===null&&await qe(u,d)}const l=await vt([e.node_id],u);await u.prepare("DELETE FROM run_events WHERE run_id = ?").run(e.node_id),await u.prepare("DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const g=s.map(()=>"?").join(","),E=await u.prepareUncached(`SELECT message_id FROM messages WHERE sender_node_id IN (${g}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${g})`).all(...s,...s);await u.prepareUncached(`DELETE FROM deliveries WHERE destination_id IN (${g})`).run(...s);for(const{message_id:y}of E)await u.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(y,y);return l});for(const u of o)await $e(u);const{notifyRunDeleted:r}=await import("./events.js");return r(e.node_id),{uploads_pending:await Tt(i,t)}}c(Yn,"deleteRun");async function Tt(e,t){if(!e.length||!t.config())return e.length;let s;try{s=t.client()}catch{return e.length}const n=new Set,o=e.map(i=>s.deleteUpload(i).then(async()=>{await Nt(i),n.add(i)},()=>{}));let m;return await Promise.race([Promise.all(o),new Promise(i=>{m=setTimeout(i,t.budgetMs??Ot),m.unref?.()})]),clearTimeout(m),e.length-n.size}c(Tt,"deleteReleasedUploads");async function Gn(e,t,s){await q(e,t),await Z(e);const n=await F(async d=>{const _=await d.prepare("SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e),h=new Map;for(const S of _)h.set(S.node_id,(await d.prepare("SELECT session_id FROM sessions WHERE node_id = ? GROUP BY session_id ORDER BY min(created_at)").all(S.node_id)).map(I=>I.session_id));const p=(await d.prepare("SELECT event_cursor FROM nodes WHERE node_id = ?").get(e)).event_cursor-1,D=await d.prepare(`SELECT node_id, sequence_number FROM run_events
@@ -1,3 +1,3 @@
1
- var Pe=Object.defineProperty;var a=(e,t)=>Pe(e,"name",{value:t,configurable:!0});import{assertGranteeRunsHere as xe}from"./unsandboxed.js";import{spawn as Te}from"node:child_process";import{mkdirSync as le,existsSync as j,unlinkSync as ue,openSync as G,closeSync as D,writeSync as Oe,chmodSync as Ne,lstatSync as De}from"node:fs";import{ensureAppSpace as Ce}from"../spaces/space.js";import{sandboxCacheEnv as Le}from"../spaces/cache-env.js";import{compileLaunch as Ae,launchStillGranted as Be}from"../spaces/mounts.js";import{grantsGeneration as fe}from"../grants/mirror.js";import{LAUNCHER_SOCKET as q}from"../../launcher/spec.js";import{launcherRequest as F}from"../../launcher/client.js";import{piAgentDir as de}from"./package-health.js";import{existsBeneath as z,readFileBeneath as $e,readTextBeneathIfExists as je,setModeBeneath as b,withDirBeneath as V,writeFileBeneath as M}from"../spaces/open-beneath.js";import{dirname as X,join as p,resolve as Ge,isAbsolute as Fe}from"node:path";import{fileURLToPath as me}from"node:url";import{connectViewSocket as Me}from"./view-socket.js";import{appSpace as K}from"../layout.js";import{readConfig as Ke,readMergedLaunchConfig as Je}from"../config.js";import{loadRotationConfig as He}from"../subscription-state.js";import{getNode as w}from"../canvas/index.js";import{withCanvasWrite as Ue}from"../canvas/db.js";import{requestExecutionStop as We}from"../canvas/executions.js";import{scopesFilePath as Q,crtrHome as pe,nodeRunDir as Y,nodeRunJobDir as qe,jobDir as ze,viewSocketPath as Ve,nodeAgentDir as Xe,contextDir as Qe,nodeHome as Ye}from"../canvas/paths.js";import{encodeFrame as he}from"./broker-protocol.js";import{buildBrokerEnv as Ze}from"./spawn-env-rows.js";import{hostExecPathFresh as Ie}from"./branded-host.js";import{isPidAlive as Z,killProcessTreePids as we,isAnyPidAlive as er,captureTeardownSnapshot as rr}from"../canvas/pid.js";import{assertMandatoryCanvasExtensions as tr}from"./canvas-extensions.js";import{appMemoryFull as nr,appMemoryTooSmall as or,boundFleet as ir,brokerCapReached as sr,brokerThresholdsForDaemon as ar}from"./fleet.js";import{admitBrokerMemory as cr}from"./memory-admission.js";import{emitEvent as g}from"../events/emit.js";import{bindBrokerEventSource as lr,eventSource as ur}from"../events/source.js";import{envBrokerEngine as fr}from"../../shared/env.js";import{conversationIdFromPath as dr,conversationStore as mr,ConversationNotFoundError as ge}from"../conversation-store/index.js";import{mintNodeToken as pr}from"../canvas/node-tokens.js";import{personId as hr}from"../canvas/person.js";function J(){const e=X(me(import.meta.url));return p(e,"broker-cli.js")}a(J,"resolveBrokerEntry");const wr="@earendil-works/pi-coding-agent";function Ee(){const e=p(de(),"models.json"),t=je(X(e),e);if(t===null)return null;const o=JSON.parse(t),n=a(r=>Array.isArray(r)?r.map(n):r===null||typeof r!="object"?r:Object.fromEntries(Object.entries(r).filter(([s])=>!/^(apiKey|authorization|headers|token|secret)$/i.test(s)).map(([s,c])=>[s,n(c)])),"scrub");return{text:t,clean:`${JSON.stringify(n(o),null,2)}
2
- `,credentialed:JSON.stringify(o)!==JSON.stringify(n(o))}}a(Ee,"scrubbedBrokerModels");function gr(e,t){const o=p(de(),"models.json"),n=p(t,"models.json"),r=Ee();if(r!==null){if(o===n&&r.text!==r.clean){if(r.credentialed)throw new Error("terminal models.json contains credentials; move them into daemon secrets");b(e,n,432);return}M(e,n,r.clean,{atomic:!0,mode:432}),b(e,n,432)}}a(gr,"writeBrokerModels");function _e(e){return e.env.CRTR_BROKER_ENGINE??fr()}a(_e,"resolveEngineSpec");function ke(e){return e.includes("/")||e.endsWith(".jsonl")}a(ke,"isPlainLaunchSessionPath");function Er(e,t){return Fe(e)?e:Ge(t,e)}a(Er,"resolvePlainLaunchPath");async function _r(e,t,o,n){if(o==="--session"&&!ke(n))throw new Error(`[broker] refusing to launch ${e}: launch recipe passes bare --session id '${n}' \u2014 the broker fatally rejects bare ids; pass the .jsonl path instead.`);if(!ke(n))return;const r=Er(n,t),s=dr(r);try{if(s===null)throw new ge(r);await mr().header(s)}catch(c){if(!(c instanceof ge))throw c;const l=o==="--session"?"session file":"fork source";throw new Error(`[broker] refusing to launch ${e}: launch recipe references missing ${l} '${n}' (${r})`)}}a(_r,"preflightLaunchSessionArg");async function kr(e,t,o){const n=Ee();return JSON.stringify({...t,configSnapshot:{user:Ke("user"),launch:Je(o,t.env.CRTR_PROFILE_ID||null),rotation:await He()},scopes:(await w(e))?.scopes??null,models:n===null?null:JSON.parse(n.clean)})}a(kr,"brokerLaunchJson");async function vr(e,t,o,n){const r=Y(e);le(r,{recursive:!0}),process.platform==="linux"&&j(q)&&De(r).uid===process.getuid()&&Ne(r,504);const s=p(r,"broker-launch.json");M(r,s,n??await kr(e,t,o),{atomic:!0}),t.env.CRTR_SCOPES_FILE!==void 0&&M(r,Q(e),JSON.stringify((await w(e))?.scopes??[]),{atomic:!0});const c=$e(r,s).toString("utf8"),l=JSON.parse(c);if(!Array.isArray(l.argv))throw new Error(`[broker] refusing to launch ${e}: broker-launch.json is missing the argv array`);const k=_e(t);try{const u=import.meta.resolve(k);if(u.startsWith("file:")&&!j(me(u)))throw new Error(`resolved to missing file ${u}`)}catch(u){throw new Error(`[broker] refusing to launch ${e}: cannot resolve broker engine '${k}' from ${J()} \u2014 ${u.message}`)}const f=l.argv,O=[];for(let u=0;u<f.length;u++){const R=f[u];if(R==="-e"){const C=f[++u];C!==void 0&&O.push(C);continue}if(R!=="--session"&&R!=="--fork")continue;const x=f[++u];x!==void 0&&await _r(e,o,R,x)}try{tr(O)}catch(u){throw new Error(`[broker] refusing to launch ${e}: ${u.message}`)}}a(vr,"preflightBrokerLaunch");const yr=2e3,Sr=2e3,br=150;async function H(e,t){const o=Date.now()+t;for(;er(e);){const n=o-Date.now();if(n<=0)return!1;await new Promise(r=>setTimeout(r,Math.min(br,n)))}return!0}a(H,"waitForTreeExit");async function Rr(e,t,o){return e.length===0?{confirmed:!0}:o&&await H(e,yr)?{confirmed:!0}:(we(e,"SIGTERM",t),await H(e,Sr)?{confirmed:!0}:(we(e,"SIGKILL",t),await H(e,5e3)?{confirmed:!0}:{confirmed:!1,error:new Error(`broker process tree for ${e.join(", ")} remained alive after SIGKILL`)}))}a(Rr,"terminateBrokerTree");async function ve(e,t){const o=await w(e),n=o?.pi_pid,r=n!=null?rr(n,o?.pi_pid_identity??null,t.excludePid):null;return r?.reused===!0&&g({level:"warn",event:"broker.teardown.pid_reuse_refused",node_id:e,fields:{recorded_pid:n,expected_identity:o?.pi_pid_identity??null}}),{tree:r?.tree??[],identities:r?.identities??null,nodeId:e,launcherPid:j(q)&&r?.tree.includes(n)?n:null}}a(ve,"prepareBrokerTeardown");async function ye(e){if(e.launcherPid!==null){try{await F({operation:"stop",pid:e.launcherPid})}catch(t){return Z(e.launcherPid)?{confirmed:!1,error:t instanceof Error?t:new Error(String(t))}:{confirmed:!0}}return await H([e.launcherPid],5e3)?{confirmed:!0}:{confirmed:!1,error:new Error(`launcher pid ${e.launcherPid} remained alive`)}}return new Promise(t=>{let o=!1,n=!1;const r=a(k=>{n||(n=!0,t(k))},"settle"),s=a(()=>{try{ue(Ve(e.nodeId))}catch{}},"cleanupSocket"),c=a(k=>{Rr(e.tree,e.identities,k).then(r,f=>r({confirmed:!1,error:f instanceof Error?f:new Error(String(f))}))},"finish"),l=Me(e.nodeId);l.once("error",()=>{o||(s(),c(!1))}),l.once("connect",()=>{o=!0;try{l.write(he({type:"hello",role:"controller",client_id:`daemon-shutdown-${process.pid}`})),l.write(he({type:"shutdown"}))}catch{}l.end(),s(),c(!0)})})}a(ye,"runPreparedBrokerTeardown");async function at(e,t={},o){let n;try{await We(e,{afterTurn:t.startAfter!==void 0,reason:t.reason??"teardown"}),n=await ve(e,t)}catch(r){const s={confirmed:!1,error:r instanceof Error?r:new Error(String(r))};return g({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:s.error}),s}return await new Promise(r=>{const s=a(()=>{ye(n).then(async c=>{if(c.confirmed){if(o!==void 0)try{await o()}catch(l){g({level:"error",event:"broker.teardown.after_failed",node_id:e,error:l})}}else g({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:c.error});r(c)},c=>{const l={confirmed:!1,error:c instanceof Error?c:new Error(String(c))};g({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:l.error}),r(l)})},"run");t.startAfter===void 0?setImmediate(s):t.startAfter.then(s,s)})}a(at,"requestBrokerTeardown");function Se(){return process.platform==="linux"&&j(q)}a(Se,"isolatedBrokerLauncherAvailable");const ct={async launch(e,t,o){if(xe((await w(e))?.grantee),(await w(e))?.isolated===!0&&!Se())throw new Error("isolated broker requires the Linux root launcher");await vr(e,t,o.cwd,o.launchJson);const n=ir();if(!await n.reserveSlot(e))throw sr(ar().automaticReviveCap,await n.occupancy());let r=null,s,c=!1;try{const l=ze(e);le(l,{recursive:!0}),r=G(p(l,"broker.log"),"a");const k=_e(t),f={...await Ze(t),CRTR_BROKER_ENGINE:k};if(k!==wr){const i=t.env.NODE_OPTIONS??process.env.NODE_OPTIONS;i!==void 0&&(f.NODE_OPTIONS=i);const d=t.env.CRTR_TEST_PG_URL??process.env.CRTR_TEST_PG_URL;d!==void 0&&(f.CRTR_TEST_PG_URL=d)}ur()===void 0&&lr(e);const O=(await w(e))?.run_id;if(O){const i=await w(O);if(!i||i.deleting_at)throw new Error("run is deleting")}const u=t.env.CRTR_EXECUTION_ID;if(!u)throw new Error("broker launch has no execution id");const R=await Ue(i=>pr(i,u,e,new Date().toISOString())),x=Se();if((await w(e))?.isolated===!0&&!x)throw new Error("isolated broker requires the Linux root launcher");if(process.platform==="linux"&&process.env.CRTR_LAUNCHER_SOCKET&&!x)throw new Error("root launcher socket is unavailable; refusing an unsandboxed broker");if(x){const i=await w(e);if(!i)throw new Error(`missing node ${e}`);const d=i.grantee??"app:terminal",P=await cr(e,{useMemo:!1});if(P.verdict==="denied")throw nr(P);if(P.verdict==="too_small")throw or(P);const v=await Ce(d);Object.assign(f,Le(d));const y={uid:process.getuid(),gid:v.gid},h=Y(e);for(const[m,E]of[[K(d).nodes,Xe(e,d)],[K(d).nodes,Qe(e,d)],[h,qe(e)]])V(m,E,()=>{},!0),b(m,E,1528,y);b(h,p(h,"broker-launch.json"),416,y),z(h,Q(e))&&b(h,Q(e),416,y);const re=p(h,"system-prompt.md");z(h,re)&&b(h,re,432,y);for(const m of["output-schema.json","situational-context.md"]){const E=p(h,m);z(h,E)&&b(h,E,432,y)}const te=i.isolated===!0,S=Ye(e,d,te);te&&(V(K(d).nodes,S,()=>{},!0),b(K(d).nodes,S,1528,y),f.HOME=S,f.PI_CODING_AGENT_DIR=p(S,".pi","agent"));const W=p(S,".pi","agent");V(S,W,()=>{},!0),gr(S,W);const ne=p(W,"settings.json"),be=p(X(J()),"../../builtin-pi-packages/pi-crtr-extensions");M(S,ne,`${JSON.stringify({packages:[be]})}
3
- `,{atomic:!0,mode:432}),b(S,ne,432);const oe=p(Y(e),`token-${u}`),A=G(oe,"wx+",384);ue(oe),Oe(A,R);const ie=[G(`/proc/self/fd/${A}`,"r"),G(`/proc/self/fd/${A}`,"r")];D(A);const se={nodeId:e,app:d,sub:i.sub??hr(),grantId:i.grant_id??null,scopes:i.scopes??null,isolated:i.isolated===!0,...i.isolated===!0?{cwd:i.cwd}:{},command:[process.execPath,J(),"--canvas-home",pe(),"--epoch",n.epoch(),e],env:Object.fromEntries(Object.entries({...f,...i.isolated===!0?{PWD:i.cwd}:{}}).filter(m=>m[1]!==void 0)),auditMissing:a(m=>g({level:"warn",event:"broker.mount.missing",node_id:e,fields:{source:m}}),"auditMissing")};try{let m;for(const N of ie){const B=await fe(),_=await Ae(se);_.broker=!0;const $=await F({operation:"launch",spec:_},[N]);if(!("pid"in $))throw new Error("launcher did not return a pid");if(await fe()===B||await Be(se,_)){m=$.pid;break}g({level:"info",event:"broker.launch.regranted",node_id:e,fields:{pid:$.pid}}),await F({operation:"stop",pid:$.pid,signal:"SIGKILL"}).catch(()=>{})}if(m===void 0)throw new Error("grant changed during every launch attempt");const E=m,Re=Date.now(),ae=new Promise(N=>{const B=setInterval(()=>{Z(E)||(clearInterval(B),F({operation:"exit.status",pid:E}).then(_=>"oomKilled"in _&&_.oomKilled,()=>!1).then(_=>{g({level:_?"warn":"info",event:"broker.exited",node_id:e,fields:{pid:E,uptime_ms:Date.now()-Re,oom_killed:_}}),N({code:null,signal:null,..._?{oomKilled:!0}:{}})}))},250);B.unref()});D(r),r=null;const ce={pid:E,exited:ae};return n.register(e,ce),c=!0,ae.then(async N=>await n.onChildExit(e,N,u)),ce}finally{for(const m of ie)D(m)}}const C=await Ie(),T=Te(C,[J(),"--canvas-home",pe(),"--epoch",n.epoch(),e],{cwd:o.cwd,detached:!0,stdio:["ignore",r,r,"pipe"],env:f});s=T;const L=T.stdio[3];if(!L||!("end"in L))throw new Error("broker token pipe did not open");L.on("error",i=>g({event:"broker.token_pipe.failed",level:"error",node_id:e,error:i})),L.end(R),D(r),r=null;const I=Date.now(),ee=new Promise(i=>{let d=!1;const P=a(v=>{d||(d=!0,i(v))},"finish");T.once("exit",(v,y)=>{g({level:v===0?"info":"warn",event:"broker.exited",node_id:e,fields:{code:v,signal:y,uptime_ms:Date.now()-I}}),P({code:v,signal:y})}),T.once("error",v=>{g({level:"error",event:"broker.launch.failed",node_id:e,error:v,fields:{uptime_ms:Date.now()-I}}),P({code:null,signal:null})})});T.unref();const U={pid:T.pid??null,exited:ee};return U.pid!==null&&(n.register(e,U),c=!0,ee.then(async i=>await n.onChildExit(e,i,u))),U}catch(l){if(!c&&s?.pid!=null)try{s.kill("SIGTERM")}catch{}throw l}finally{c||n.releaseReservation(e),r!==null&&D(r)}},async isAlive(e){return Z((typeof e=="string"?await w(e):e)?.pi_pid)},async teardown(e,t={}){try{return ye(await ve(e,t))}catch(o){return Promise.resolve({confirmed:!1,error:o instanceof Error?o:new Error(String(o))})}},async signal(e,t){const o=(await w(e))?.pi_pid;if(o!=null)try{process.kill(o,t)}catch{}}};export{kr as brokerLaunchJson,ct as headlessBrokerHost,Se as isolatedBrokerLauncherAvailable,vr as preflightBrokerLaunch,at as requestBrokerTeardown};
1
+ var xe=Object.defineProperty;var a=(e,t)=>xe(e,"name",{value:t,configurable:!0});import{assertGranteeRunsHere as Te}from"./unsandboxed.js";import{spawn as Oe}from"node:child_process";import{mkdirSync as ue,existsSync as j,unlinkSync as fe,openSync as G,closeSync as C,writeSync as Ne,chmodSync as De,lstatSync as Ce}from"node:fs";import{ensureAppSpace as Le}from"../spaces/space.js";import{sandboxCacheEnv as Ae}from"../spaces/cache-env.js";import{compileLaunch as Be,launchStillGranted as $e}from"../spaces/mounts.js";import{grantsGeneration as de}from"../grants/mirror.js";import{LAUNCHER_SOCKET as q}from"../../launcher/spec.js";import{launcherRequest as F}from"../../launcher/client.js";import{piAgentDir as me}from"./package-health.js";import{existsBeneath as z,readFileBeneath as je,readTextBeneathIfExists as Ge,setModeBeneath as b,withDirBeneath as V,writeFileBeneath as M}from"../spaces/open-beneath.js";import{dirname as X,join as p,resolve as Fe,isAbsolute as Me}from"node:path";import{fileURLToPath as pe}from"node:url";import{connectViewSocket as Ke}from"./view-socket.js";import{appSpace as K}from"../layout.js";import{readConfig as Je,readMergedLaunchConfig as He}from"../config.js";import{loadRotationConfig as Ue}from"../subscription-state.js";import{getNode as g}from"../canvas/index.js";import{withCanvasWrite as We}from"../canvas/db.js";import{requestExecutionStop as qe}from"../canvas/executions.js";import{scopesFilePath as Q,crtrHome as he,nodeRunDir as Y,nodeRunJobDir as ze,jobDir as Ve,viewSocketPath as Xe,nodeAgentDir as Qe,contextDir as Ye,nodeHome as Ze}from"../canvas/paths.js";import{encodeFrame as we}from"./broker-protocol.js";import{buildBrokerEnv as Ie}from"./spawn-env-rows.js";import{hostExecPathFresh as er}from"./branded-host.js";import{isPidAlive as Z,killProcessTreePids as ge,isAnyPidAlive as rr,captureTeardownSnapshot as tr}from"../canvas/pid.js";import{assertMandatoryCanvasExtensions as nr}from"./canvas-extensions.js";import{appMemoryFull as or,appMemoryTooSmall as ir,boundFleet as sr,brokerCapReached as ar,brokerThresholdsForDaemon as cr}from"./fleet.js";import{admitBrokerMemory as lr}from"./memory-admission.js";import{emitEvent as E}from"../events/emit.js";import{bindBrokerEventSource as ur,eventSource as fr}from"../events/source.js";import{envBrokerEngine as dr}from"../../shared/env.js";import{conversationIdFromPath as mr,conversationStore as pr,ConversationNotFoundError as Ee}from"../conversation-store/index.js";import{mintNodeToken as hr}from"../canvas/node-tokens.js";import{personId as wr}from"../canvas/person.js";function J(){const e=X(pe(import.meta.url));return p(e,"broker-cli.js")}a(J,"resolveBrokerEntry");const gr="@earendil-works/pi-coding-agent";function _e(){const e=p(me(),"models.json"),t=Ge(X(e),e);if(t===null)return null;const o=JSON.parse(t),n=a(r=>Array.isArray(r)?r.map(n):r===null||typeof r!="object"?r:Object.fromEntries(Object.entries(r).filter(([s])=>!/^(apiKey|authorization|headers|token|secret)$/i.test(s)).map(([s,c])=>[s,n(c)])),"scrub");return{text:t,clean:`${JSON.stringify(n(o),null,2)}
2
+ `,credentialed:JSON.stringify(o)!==JSON.stringify(n(o))}}a(_e,"scrubbedBrokerModels");function Er(e,t){const o=p(me(),"models.json"),n=p(t,"models.json"),r=_e();if(r!==null){if(o===n&&r.text!==r.clean){if(r.credentialed)throw new Error("terminal models.json contains credentials; move them into daemon secrets");b(e,n,432);return}M(e,n,r.clean,{atomic:!0,mode:432}),b(e,n,432)}}a(Er,"writeBrokerModels");function ke(e){return e.env.CRTR_BROKER_ENGINE??dr()}a(ke,"resolveEngineSpec");function ve(e){return e.includes("/")||e.endsWith(".jsonl")}a(ve,"isPlainLaunchSessionPath");function _r(e,t){return Me(e)?e:Fe(t,e)}a(_r,"resolvePlainLaunchPath");async function kr(e,t,o,n){if(o==="--session"&&!ve(n))throw new Error(`[broker] refusing to launch ${e}: launch recipe passes bare --session id '${n}' \u2014 the broker fatally rejects bare ids; pass the .jsonl path instead.`);if(!ve(n))return;const r=_r(n,t),s=mr(r);try{if(s===null)throw new Ee(r);await pr().header(s)}catch(c){if(!(c instanceof Ee))throw c;const l=o==="--session"?"session file":"fork source";throw new Error(`[broker] refusing to launch ${e}: launch recipe references missing ${l} '${n}' (${r})`)}}a(kr,"preflightLaunchSessionArg");async function vr(e,t,o){const n=_e();return JSON.stringify({...t,configSnapshot:{user:Je("user"),launch:He(o,t.env.CRTR_PROFILE_ID||null),rotation:await Ue()},scopes:(await g(e))?.scopes??null,models:n===null?null:JSON.parse(n.clean)})}a(vr,"brokerLaunchJson");async function yr(e,t,o,n){const r=Y(e);ue(r,{recursive:!0}),process.platform==="linux"&&j(q)&&Ce(r).uid===process.getuid()&&De(r,504);const s=p(r,"broker-launch.json");M(r,s,n??await vr(e,t,o),{atomic:!0}),t.env.CRTR_SCOPES_FILE!==void 0&&M(r,Q(e),JSON.stringify((await g(e))?.scopes??[]),{atomic:!0});const c=je(r,s).toString("utf8"),l=JSON.parse(c);if(!Array.isArray(l.argv))throw new Error(`[broker] refusing to launch ${e}: broker-launch.json is missing the argv array`);const k=ke(t);try{const u=import.meta.resolve(k);if(u.startsWith("file:")&&!j(pe(u)))throw new Error(`resolved to missing file ${u}`)}catch(u){throw new Error(`[broker] refusing to launch ${e}: cannot resolve broker engine '${k}' from ${J()} \u2014 ${u.message}`)}const f=l.argv,N=[];for(let u=0;u<f.length;u++){const P=f[u];if(P==="-e"){const L=f[++u];L!==void 0&&N.push(L);continue}if(P!=="--session"&&P!=="--fork")continue;const T=f[++u];T!==void 0&&await kr(e,o,P,T)}try{nr(N)}catch(u){throw new Error(`[broker] refusing to launch ${e}: ${u.message}`)}}a(yr,"preflightBrokerLaunch");const Sr=2e3,br=2e3,Rr=150;async function H(e,t){const o=Date.now()+t;for(;rr(e);){const n=o-Date.now();if(n<=0)return!1;await new Promise(r=>setTimeout(r,Math.min(Rr,n)))}return!0}a(H,"waitForTreeExit");async function Pr(e,t,o){return e.length===0?{confirmed:!0}:o&&await H(e,Sr)?{confirmed:!0}:(ge(e,"SIGTERM",t),await H(e,br)?{confirmed:!0}:(ge(e,"SIGKILL",t),await H(e,5e3)?{confirmed:!0}:{confirmed:!1,error:new Error(`broker process tree for ${e.join(", ")} remained alive after SIGKILL`)}))}a(Pr,"terminateBrokerTree");async function ye(e,t){const o=await g(e),n=o?.pi_pid,r=n!=null?tr(n,o?.pi_pid_identity??null,t.excludePid):null;return r?.reused===!0&&E({level:"warn",event:"broker.teardown.pid_reuse_refused",node_id:e,fields:{recorded_pid:n,expected_identity:o?.pi_pid_identity??null}}),{tree:r?.tree??[],identities:r?.identities??null,nodeId:e,launcherPid:j(q)&&r?.tree.includes(n)?n:null}}a(ye,"prepareBrokerTeardown");async function Se(e){if(e.launcherPid!==null){try{await F({operation:"stop",pid:e.launcherPid})}catch(t){return Z(e.launcherPid)?{confirmed:!1,error:t instanceof Error?t:new Error(String(t))}:{confirmed:!0}}return await H([e.launcherPid],5e3)?{confirmed:!0}:{confirmed:!1,error:new Error(`launcher pid ${e.launcherPid} remained alive`)}}return new Promise(t=>{let o=!1,n=!1;const r=a(k=>{n||(n=!0,t(k))},"settle"),s=a(()=>{try{fe(Xe(e.nodeId))}catch{}},"cleanupSocket"),c=a(k=>{Pr(e.tree,e.identities,k).then(r,f=>r({confirmed:!1,error:f instanceof Error?f:new Error(String(f))}))},"finish"),l=Ke(e.nodeId);l.once("error",()=>{o||(s(),c(!1))}),l.once("connect",()=>{o=!0;try{l.write(we({type:"hello",role:"controller",client_id:`daemon-shutdown-${process.pid}`})),l.write(we({type:"shutdown"}))}catch{}l.end(),s(),c(!0)})})}a(Se,"runPreparedBrokerTeardown");async function ct(e,t={},o){let n;try{await qe(e,{afterTurn:t.startAfter!==void 0,reason:t.reason??"teardown"}),n=await ye(e,t)}catch(r){const s={confirmed:!1,error:r instanceof Error?r:new Error(String(r))};return E({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:s.error}),s}return await new Promise(r=>{const s=a(()=>{Se(n).then(async c=>{if(c.confirmed){if(o!==void 0)try{await o()}catch(l){E({level:"error",event:"broker.teardown.after_failed",node_id:e,error:l})}}else E({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:c.error});r(c)},c=>{const l={confirmed:!1,error:c instanceof Error?c:new Error(String(c))};E({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:l.error}),r(l)})},"run");t.startAfter===void 0?setImmediate(s):t.startAfter.then(s,s)})}a(ct,"requestBrokerTeardown");function be(){return process.platform==="linux"&&j(q)}a(be,"isolatedBrokerLauncherAvailable");const lt={async launch(e,t,o){if(Te((await g(e))?.grantee),(await g(e))?.isolated===!0&&!be())throw new Error("isolated broker requires the Linux root launcher");await yr(e,t,o.cwd,o.launchJson);const n=sr();if(!await n.reserveSlot(e))throw ar(cr().automaticReviveCap,await n.occupancy());let r=null,s,c=!1;try{const l=Ve(e);ue(l,{recursive:!0}),r=G(p(l,"broker.log"),"a");const k=ke(t),f={...await Ie(t),CRTR_BROKER_ENGINE:k};if(k!==gr){const i=t.env.NODE_OPTIONS??process.env.NODE_OPTIONS;i!==void 0&&(f.NODE_OPTIONS=i);const d=t.env.CRTR_TEST_PG_URL??process.env.CRTR_TEST_PG_URL;d!==void 0&&(f.CRTR_TEST_PG_URL=d)}fr()===void 0&&ur(e);const N=(await g(e))?.run_id;if(N){const i=await g(N);if(!i||i.deleting_at)throw new Error("run is deleting")}const u=t.env.CRTR_EXECUTION_ID;if(!u)throw new Error("broker launch has no execution id");const P=await We(i=>hr(i,u,e,new Date().toISOString())),T=be();if((await g(e))?.isolated===!0&&!T)throw new Error("isolated broker requires the Linux root launcher");if(process.platform==="linux"&&process.env.CRTR_LAUNCHER_SOCKET&&!T)throw new Error("root launcher socket is unavailable; refusing an unsandboxed broker");if(T){const i=await g(e);if(!i)throw new Error(`missing node ${e}`);const d=i.grantee??"app:terminal",x=await lr(e,{useMemo:!1});if(x.verdict==="denied")throw or(x);if(x.verdict==="too_small")throw ir(x);const v=await Le(d);Object.assign(f,Ae(d));const y={uid:process.getuid(),gid:v.gid},w=Y(e);for(const[m,_]of[[K(d).nodes,Qe(e,d)],[K(d).nodes,Ye(e,d)],[w,ze(e)]])V(m,_,()=>{},!0),b(m,_,1528,y);b(w,p(w,"broker-launch.json"),416,y),z(w,Q(e))&&b(w,Q(e),416,y);const re=p(w,"system-prompt.md");z(w,re)&&b(w,re,432,y);for(const m of["output-schema.json","situational-context.md"]){const _=p(w,m);z(w,_)&&b(w,_,432,y)}const te=i.isolated===!0,S=Ze(e,d,te);te&&(V(K(d).nodes,S,()=>{},!0),b(K(d).nodes,S,1528,y),f.HOME=S,f.PI_CODING_AGENT_DIR=p(S,".pi","agent"));const W=p(S,".pi","agent");V(S,W,()=>{},!0),Er(S,W);const ne=p(W,"settings.json"),Re=p(X(J()),"../../builtin-pi-packages/pi-crtr-extensions");M(S,ne,`${JSON.stringify({packages:[Re]})}
3
+ `,{atomic:!0,mode:432}),b(S,ne,432);const oe=p(Y(e),`token-${u}`),B=G(oe,"wx+",384);fe(oe),Ne(B,P);const ie=[G(`/proc/self/fd/${B}`,"r"),G(`/proc/self/fd/${B}`,"r")];C(B);const se={nodeId:e,app:d,sub:i.sub??wr(),grantId:i.grant_id??null,scopes:i.scopes??null,isolated:i.isolated===!0,...i.isolated===!0?{cwd:i.cwd}:{},command:[process.execPath,J(),"--canvas-home",he(),"--epoch",n.epoch(),e],env:Object.fromEntries(Object.entries({...f,...i.isolated===!0?{PWD:i.cwd}:{}}).filter(m=>m[1]!==void 0)),auditMissing:a(m=>E({level:"warn",event:"broker.mount.missing",node_id:e,fields:{source:m}}),"auditMissing")};try{let m;for(const D of ie){const $=await de(),h=await Be(se);h.broker=!0;const R=await F({operation:"launch",spec:h},[D]);if(!("pid"in R))throw new Error("launcher did not return a pid");if(await de()===$||await $e(se,h)){m=R.pid;break}E({level:"info",event:"broker.launch.regranted",node_id:e,fields:{pid:R.pid}}),await F({operation:"stop",pid:R.pid,signal:"SIGKILL"}).catch(()=>{})}if(m===void 0)throw new Error("grant changed during every launch attempt");const _=m,Pe=Date.now(),ae=new Promise(D=>{const $=setInterval(()=>{Z(_)||(clearInterval($),F({operation:"exit.status",pid:_}).then(h=>"oomKilled"in h?{oomKilled:h.oomKilled,code:h.code??null,signal:h.signal??null}:{oomKilled:!1,code:null,signal:null},()=>({oomKilled:!1,code:null,signal:null})).then(({oomKilled:h,code:R,signal:le})=>{E({level:h||R!==0?"warn":"info",event:"broker.exited",node_id:e,fields:{pid:_,code:R,signal:le,uptime_ms:Date.now()-Pe,oom_killed:h}}),D({code:R,signal:le,...h?{oomKilled:!0}:{}})}))},250);$.unref()});C(r),r=null;const ce={pid:_,exited:ae};return n.register(e,ce),c=!0,ae.then(async D=>await n.onChildExit(e,D,u)),ce}finally{for(const m of ie)C(m)}}const L=await er(),O=Oe(L,[J(),"--canvas-home",he(),"--epoch",n.epoch(),e],{cwd:o.cwd,detached:!0,stdio:["ignore",r,r,"pipe"],env:f});s=O;const A=O.stdio[3];if(!A||!("end"in A))throw new Error("broker token pipe did not open");A.on("error",i=>E({event:"broker.token_pipe.failed",level:"error",node_id:e,error:i})),A.end(P),C(r),r=null;const I=Date.now(),ee=new Promise(i=>{let d=!1;const x=a(v=>{d||(d=!0,i(v))},"finish");O.once("exit",(v,y)=>{E({level:v===0?"info":"warn",event:"broker.exited",node_id:e,fields:{code:v,signal:y,uptime_ms:Date.now()-I}}),x({code:v,signal:y})}),O.once("error",v=>{E({level:"error",event:"broker.launch.failed",node_id:e,error:v,fields:{uptime_ms:Date.now()-I}}),x({code:null,signal:null})})});O.unref();const U={pid:O.pid??null,exited:ee};return U.pid!==null&&(n.register(e,U),c=!0,ee.then(async i=>await n.onChildExit(e,i,u))),U}catch(l){if(!c&&s?.pid!=null)try{s.kill("SIGTERM")}catch{}throw l}finally{c||n.releaseReservation(e),r!==null&&C(r)}},async isAlive(e){return Z((typeof e=="string"?await g(e):e)?.pi_pid)},async teardown(e,t={}){try{return Se(await ye(e,t))}catch(o){return Promise.resolve({confirmed:!1,error:o instanceof Error?o:new Error(String(o))})}},async signal(e,t){const o=(await g(e))?.pi_pid;if(o!=null)try{process.kill(o,t)}catch{}}};export{vr as brokerLaunchJson,lt as headlessBrokerHost,be as isolatedBrokerLauncherAvailable,yr as preflightBrokerLaunch,ct as requestBrokerTeardown};
@@ -1,3 +1,3 @@
1
- var ie=Object.defineProperty;var l=(e,r)=>ie(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as te}from"./unsandboxed.js";import{existsSync as H}from"node:fs";import{conversationIdFromPath as v,conversationStore as p,ConversationNotFoundError as M}from"../conversation-store/index.js";import{isAbsolute as ne}from"node:path";import{findNodeBySessionFile as ae,getNode as S,updateNode as $,beginExecution as oe,ExecutionAlreadyLive as se,recordExecutionPid as le,abortExecution as N,endLiveExecution as ce,unbindSession as U,recordLaunch as ue,setFrozen as fe,clearFrozen as z,fullName as we}from"../canvas/index.js";import{emitEvent as K}from"../events/emit.js";import{consumeDeadlineOnWake as de}from"../feed/messages.js";import{isParked as G,transition as C}from"./lifecycle.js";import{publishNotice as he}from"./close.js";import{InputError as F}from"../io.js";import{buildPiArgv as _}from"./launch.js";import{buildReviveKickoff as pe,drainBearings as me,hasRetainedReports as ve,readGoal as g,readYieldMessage as _e,RUNTIME_RESTART_CONTINUATION as ye}from"./kickoff.js";import{readRoadmap as J}from"./roadmap.js";import{encodeKickoffOrigin as ke,KICKOFF_ORIGIN_ENV as ge}from"./stamp/protocol.js";import{headlessBrokerHost as be,brokerLaunchJson as Re}from"./host.js";import{processRunner as xe}from"./runner/process.js";import{runnerForEnvironment as Ee}from"./runner/index.js";import{blaxelEnvironmentOf as W}from"./blaxel-environment.js";import{recordLaunchFailure as Se}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as $e,checkBilledWake as Ne,EnvironmentImagePending as Ce,imageForWake as Fe}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Ae,liveExecutionForNode as Pe,setExecutionLaunchJson as Be}from"../canvas/executions.js";import{appMemoryFull as Oe,appMemoryTooSmall as De,boundFleet as j,brokerCapReached as V,brokerThresholdsForDaemon as A}from"./fleet.js";import{admitBrokerMemory as Te}from"./memory-admission.js";import{beginBootFaultAttempt as Le,clearFault as He,hasAdmittedTurn as Me}from"../canvas/recovery.js";import{clearNodeLedger as X,forkNodeLedger as Ue,noticeChangedWhileDone as ze}from"./ledger.js";import{forkSourceNode as Ke,rootOfSpine as Ge}from"./nodes.js";import{checkStart as Je}from"./start-check.js";import{isReviewCompanionBound as We}from"../review/companion.js";import{assertProfileActive as je}from"../profiles/manifest.js";import{isPidAlive as Ve}from"../canvas/pid.js";import{crtrHome as Xe,jobDir as Ye}from"../canvas/paths.js";import{contextDir as qe,ensureNodeDirs as Qe,nodeAgentDir as Ze}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as Ie}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as er}from"../command-hooks/transport/exec-lifecycle.js";async function rr(e,r){const i=e.pi_session_file,t=i?v(i):null;if(t===null)return{};try{const a=await p().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof M)return{};throw a}}l(rr,"resumeArgs");async function Y(e){const r=v(e);if(r===null)return!1;try{return await p().header(r),!0}catch(i){if(i instanceof M)return!1;throw i}}l(Y,"conversationExists");async function ir(e){const r=v(e);return r===null?!0:(await p().read(r)).entries.some(i=>i.type==="message")}l(ir,"sessionHasConversation");async function tr(e){const r=v(e);if(r===null)return!0;const i=(await p().header(r)).header.cwd;return typeof i!="string"||H(i)}l(tr,"sessionCwdExists");async function nr(e,r,i,t){const a=r.pi_session_file;if(!a||!await Y(a))return{};const f=await ae(a);return f!==null&&f.node_id!==e&&Ve(f.pi_pid)?{}:t&&!await tr(a)?{}:await rr(r,i)}l(nr,"resumableSessionArgs");async function ar(e){return await J(e)===null&&await _e(e)===null&&!await ve(e)}l(ar,"isUnstartedBirth");async function Gr(e){const r=await g(e);if(r!==null&&r.trim()!=="")return!0;const i=await J(e);return i!==null&&i.trim()!==""}l(Gr,"hasFreshGroundState");async function Jr(e,r){const i=await q(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}l(Jr,"reviveNode");async function Wr(e,r,i){return q(e,{...r,viewerAttach:!0},i)}l(Wr,"decideAttachRevive");async function q(e,r,i){let t=await S(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await S(e)??t,t.run_id&&(await S(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);je(t.profile_id),te(t.grantee);const a=j();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&G(t))&&(await Je(t),await W(t.profile_id??null)!==null&&await Ne(t)),await a.has(e))return await C(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&G(t))return{parked:!0};const f=await a.reserveSlot(e);let o=null;if(f){try{o=await Te(e)}catch(w){throw a.releaseReservation(e),w}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw De(o);if(!f||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?Oe(o):V(A().automaticReviveCap,await a.occupancy());return t.frozen_at==null&&(await fe(e,new Date().toISOString()),await ce(e,{reason:"frozen"}),K({level:"info",event:"broker.frozen",node_id:e,fields:o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:A().automaticReviveCap}})),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await or(e,t,r)}}finally{a.releaseReservation(e)}}l(q,"decideRevive");async function or(e,r,i){const t=await W(r.profile_id??null);if(t===null&&!H(r.cwd)){const s=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new F({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:s})}r.host_kind!=="broker"&&(await $(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await We(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);const f=r.fork_from!=null&&r.pi_session_id==null,o=r.fork_source_file;if(f&&(typeof o!="string"||!ne(o)))throw new F({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const w=f&&typeof o=="string";if(w&&!await Y(o))throw new F({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${o}`,next:`Restore ${o}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:Q}=A();if(t!==null){const s=await Fe(t);if(s.state==="pending")throw new Ce(t,s.error)}const Z=t===null?xe:Ee("blaxel");let u;try{u=await oe(e,{daemonEpoch:j().epoch(),cap:Q,...t===null?{}:{runner:"blaxel",release:$e()}})}catch(s){if(s instanceof Ae)throw V(s.cap,s.live);if(s instanceof se)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw s}try{r.cycles=(r.cycles??0)+1,await $(e,{cycles:r.cycles});const b=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,h=w?{}:await nr(e,r,b,t===null),y=h.resumeSessionPath!==void 0,P=y&&h.newCycle===!0,m=y&&!P,B=!y&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await ar(e),R=m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await Me(e)&&!await ir(h.resumeSessionPath)?(await g(e))?.trim():void 0,O=R!==void 0&&R!=="",I=w||B||O,D=r.cycle_pending===!0||P;await $(e,{cycle_pending:D}),r.cycle_pending=D,!w&&!y&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);let c,d=null;const x=l(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(w){const n=await g(e),E=v(o);if(E===null)throw new Error(`invalid fork source ${o}`);const re=(await p().header(await p().fork(E,e))).path;c=_(r,{resumeSessionPath:re,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(d=x()),await Ue(e,(await Ke(o))?.node_id??null)}else if(O)c=_(r,{...h,prompt:R}),d=x();else if(m)c=_(r,{...h,...i.wakeReason==="runtime-restart-abort"?{prompt:ye}:{}}),i.wakeReason==="runtime-restart-abort"&&(d={type:"runtime",kind:"restart-continuation"});else if(B){const n=(await g(e))?.trim();c=_(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(d=x()),await X(e)}else{const n=await me(r,u);c=_(r,{...h,prompt:await pe(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),d=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await X(e)}c.env={...c.env,CRTR_SUBTREE:await Ge(e)},d!==null&&(c.env[ge]=ke(d)),r.status==="done"&&r.last_settled_at!=null&&await ze(e,r.last_settled_at),await C(e,r.intent==="refresh"?"refresh-launch":"revive"),await He(e,{preserveEpisode:!0});const T=await Le(e);i.recovery!==!0&&i.viewerAttach!==!0&&await de(e),await Qe(e,r.grantee);const ee=Ie(r.cwd,r.profile_id).plans.get("node:start")??[];await er(ee,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await Ze(e),contextDir:await qe(e),jobDir:Ye(e)},runtime:{isBirth:I,canvasHome:Xe(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:u};const L=await Re(e,c,r.cwd);await Be(u,L);let k;try{const n=await Pe(e);if(k=await Z.start({executionId:u,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:L,cwd:r.cwd,resuming:m}),t===null&&k.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){try{await C(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await he(e,"notice.crash",`Child crashed \u2014 ${we(r)} (${e}) failed to relaunch and is now dead: ${n.message}
1
+ var te=Object.defineProperty;var l=(e,r)=>te(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ne}from"./unsandboxed.js";import{existsSync as M}from"node:fs";import{conversationIdFromPath as v,conversationStore as p,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as ae}from"node:path";import{findNodeBySessionFile as oe,getNode as $,updateNode as N,beginExecution as se,ExecutionAlreadyLive as le,recordExecutionPid as ce,abortExecution as C,endLiveExecution as ue,unbindSession as U,recordLaunch as fe,setFrozen as we,clearFrozen as G,fullName as de}from"../canvas/index.js";import{emitEvent as K}from"../events/emit.js";import{consumeDeadlineOnWake as he}from"../feed/messages.js";import{isParked as W,transition as F}from"./lifecycle.js";import{publishNotice as pe}from"./close.js";import{InputError as A}from"../io.js";import{buildPiArgv as _}from"./launch.js";import{buildReviveKickoff as me,drainBearings as ve,hasRetainedReports as _e,readGoal as g,readYieldMessage as ye,RUNTIME_RESTART_CONTINUATION as ke}from"./kickoff.js";import{readRoadmap as J}from"./roadmap.js";import{encodeKickoffOrigin as ge,KICKOFF_ORIGIN_ENV as be}from"./stamp/protocol.js";import{headlessBrokerHost as Re,brokerLaunchJson as xe}from"./host.js";import{processRunner as Ee}from"./runner/process.js";import{runnerForEnvironment as Se}from"./runner/index.js";import{blaxelEnvironmentOf as j}from"./blaxel-environment.js";import{recordLaunchFailure as $e}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as Ne,checkBilledWake as Ce,EnvironmentImagePending as Fe,imageForWake as Ae}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Pe,liveExecutionForNode as Be,setExecutionLaunchJson as Oe}from"../canvas/executions.js";import{appMemoryFull as De,appMemoryTooSmall as Te,boundFleet as V,brokerCapReached as X,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as Le}from"./memory-admission.js";import{beginBootFaultAttempt as ze,clearFault as Me,hasAdmittedTurn as He}from"../canvas/recovery.js";import{clearNodeLedger as Y,forkNodeLedger as Ue,noticeChangedWhileDone as Ge}from"./ledger.js";import{forkSourceNode as Ke,rootOfSpine as We}from"./nodes.js";import{ApiError as Je}from"../../api/errors.js";import{grantSnapshotMissing as je}from"../../daemon/readiness.js";import{checkStart as Ve}from"./start-check.js";import{isReviewCompanionBound as Xe}from"../review/companion.js";import{assertProfileActive as Ye}from"../profiles/manifest.js";import{isPidAlive as qe}from"../canvas/pid.js";import{crtrHome as Qe,jobDir as Ze}from"../canvas/paths.js";import{contextDir as Ie,ensureNodeDirs as er,nodeAgentDir as rr}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as ir}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as tr}from"../command-hooks/transport/exec-lifecycle.js";async function nr(e,r){const i=e.pi_session_file,t=i?v(i):null;if(t===null)return{};try{const a=await p().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}l(nr,"resumeArgs");async function q(e){const r=v(e);if(r===null)return!1;try{return await p().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}l(q,"conversationExists");async function ar(e){const r=v(e);return r===null?!0:(await p().read(r)).entries.some(i=>i.type==="message")}l(ar,"sessionHasConversation");async function or(e){const r=v(e);if(r===null)return!0;const i=(await p().header(r)).header.cwd;return typeof i!="string"||M(i)}l(or,"sessionCwdExists");async function sr(e,r,i,t){const a=r.pi_session_file;if(!a||!await q(a))return{};const w=await oe(a);return w!==null&&w.node_id!==e&&qe(w.pi_pid)?{}:t&&!await or(a)?{}:await nr(r,i)}l(sr,"resumableSessionArgs");async function lr(e){return await J(e)===null&&await ye(e)===null&&!await _e(e)}l(lr,"isUnstartedBirth");async function Xr(e){const r=await g(e);if(r!==null&&r.trim()!=="")return!0;const i=await J(e);return i!==null&&i.trim()!==""}l(Xr,"hasFreshGroundState");async function Yr(e,r){const i=await Q(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}l(Yr,"reviveNode");async function qr(e,r,i){return Q(e,{...r,viewerAttach:!0},i)}l(qr,"decideAttachRevive");async function Q(e,r,i){let t=await $(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await $(e)??t,t.run_id&&(await $(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);Ye(t.profile_id),ne(t.grantee);const a=V(),w=t.grantee==="app:terminal"&&await je();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&W(t))&&(await Ve(t),await j(t.profile_id??null)!==null&&await Ce(t)),await a.has(e))return await F(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&W(t))return{parked:!0};if(w){if(r.capacity==="refuse")throw new Je(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await Z(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const c=await a.reserveSlot(e);let o=null;if(c){try{o=await Le(e)}catch(b){throw a.releaseReservation(e),b}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw Te(o);if(!c||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?De(o):X(P().automaticReviveCap,await a.occupancy());return await Z(e,t,o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await cr(e,t,r)}}finally{a.releaseReservation(e)}}l(Q,"decideRevive");async function Z(e,r,i){r.frozen_at==null&&(await we(e,new Date().toISOString()),await ue(e,{reason:"frozen"}),K({level:"info",event:"broker.frozen",node_id:e,fields:i}))}l(Z,"freezeForWait");async function cr(e,r,i){const t=await j(r.profile_id??null);if(t===null&&!M(r.cwd)){const s=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new A({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:s})}r.host_kind!=="broker"&&(await N(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await Xe(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);const w=r.fork_from!=null&&r.pi_session_id==null,c=r.fork_source_file;if(w&&(typeof c!="string"||!ae(c)))throw new A({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const o=w&&typeof c=="string";if(o&&!await q(c))throw new A({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${c}`,next:`Restore ${c}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:b}=P();if(t!==null){const s=await Ae(t);if(s.state==="pending")throw new Fe(t,s.error)}const I=t===null?Ee:Se("blaxel");let f;try{f=await se(e,{daemonEpoch:V().epoch(),cap:b,...t===null?{}:{runner:"blaxel",release:Ne()}})}catch(s){if(s instanceof Pe)throw X(s.cap,s.live);if(s instanceof le)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw s}try{r.cycles=(r.cycles??0)+1,await N(e,{cycles:r.cycles});const R=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,h=o?{}:await sr(e,r,R,t===null),y=h.resumeSessionPath!==void 0,B=y&&h.newCycle===!0,m=y&&!B,O=!y&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await lr(e),x=m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await He(e)&&!await ar(h.resumeSessionPath)?(await g(e))?.trim():void 0,D=x!==void 0&&x!=="",ee=o||O||D,T=r.cycle_pending===!0||B;await N(e,{cycle_pending:T}),r.cycle_pending=T,!o&&!y&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);let u,d=null;const E=l(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(o){const n=await g(e),S=v(c);if(S===null)throw new Error(`invalid fork source ${c}`);const ie=(await p().header(await p().fork(S,e))).path;u=_(r,{resumeSessionPath:ie,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(d=E()),await Ue(e,(await Ke(c))?.node_id??null)}else if(D)u=_(r,{...h,prompt:x}),d=E();else if(m)u=_(r,{...h,...i.wakeReason==="runtime-restart-abort"?{prompt:ke}:{}}),i.wakeReason==="runtime-restart-abort"&&(d={type:"runtime",kind:"restart-continuation"});else if(O){const n=(await g(e))?.trim();u=_(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(d=E()),await Y(e)}else{const n=await ve(r,f);u=_(r,{...h,prompt:await me(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(u.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),d=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await Y(e)}u.env={...u.env,CRTR_SUBTREE:await We(e)},d!==null&&(u.env[be]=ge(d)),r.status==="done"&&r.last_settled_at!=null&&await Ge(e,r.last_settled_at),await F(e,r.intent==="refresh"?"refresh-launch":"revive"),await Me(e,{preserveEpisode:!0});const L=await ze(e);i.recovery!==!0&&i.viewerAttach!==!0&&await he(e),await er(e,r.grantee);const re=ir(r.cwd,r.profile_id).plans.get("node:start")??[];await tr(re,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await rr(e),contextDir:await Ie(e),jobDir:Ze(e)},runtime:{isBirth:ee,canvasHome:Qe(),profile:r.profile_id??null}}),u.env={...u.env,CRTR_EXECUTION_ID:f};const z=await xe(e,u,r.cwd);await Oe(f,z);let k;try{const n=await Be(e);if(k=await I.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:m}),t===null&&k.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){try{await F(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await pe(e,"notice.crash",`Child crashed \u2014 ${de(r)} (${e}) failed to relaunch and is now dead: ${n.message}
2
2
 
3
- It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await N(u,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await ue(e,new Date().toISOString()),t!==null)return await z(e),{window:null,session:r.tmux_session??null,resumed:m,faultExplanation:T.explanation,outcome:"launched"};if(await le(u,e,k.pid)==="shutdown")throw await N(u,{reason:"superseded"}),be.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await z(e),{window:null,session:r.tmux_session??null,resumed:m,launch:k,faultExplanation:T.explanation,outcome:"launched"}}catch(s){throw await N(u,{reason:"launch_failed"}),await Se(u,s).catch(b=>{K({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:b})}),s}}l(or,"launchRevive");export{Wr as decideAttachRevive,Gr as hasFreshGroundState,rr as resumeArgs,Jr as reviveNode};
3
+ It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await C(f,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await fe(e,new Date().toISOString()),t!==null)return await G(e),{window:null,session:r.tmux_session??null,resumed:m,faultExplanation:L.explanation,outcome:"launched"};if(await ce(f,e,k.pid)==="shutdown")throw await C(f,{reason:"superseded"}),Re.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await G(e),{window:null,session:r.tmux_session??null,resumed:m,launch:k,faultExplanation:L.explanation,outcome:"launched"}}catch(s){throw await C(f,{reason:"launch_failed"}),await $e(f,s).catch(R=>{K({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:R})}),s}}l(cr,"launchRevive");export{qr as decideAttachRevive,Xr as hasFreshGroundState,nr as resumeArgs,Yr as reviveNode};
@@ -1,3 +1,3 @@
1
- var we=Object.defineProperty;var d=(e,o)=>we(e,"name",{value:o,configurable:!0});import{accessSync as me,constants as he}from"node:fs";import{isAbsolute as pe,resolve as _e,join as ke}from"node:path";import{appSpace as ve}from"../layout.js";import{ApiError as ee}from"../../api/errors.js";import{spawnNode as ye,forkSourceNode as ge,currentNodeContext as be,rootOfSpine as xe,newNodeId as Ce,preflightNodeId as Ee}from"./nodes.js";import{resolveProfileOperand as Se}from"../profiles/manifest.js";import{selectProfileForCwd as Oe,selectProfileForCwdReadOnly as Re}from"../profiles/select.js";import{buildLaunchSpecAsync as ne,buildPiArgv as $e}from"./launch.js";import{runsOnBlaxel as Fe,sandboxWorktreeRequest as Ie}from"../../daemon/api/environment-of.js";import{createManagedWorktreeAsync as Be,rollbackManagedWorktreeAsync as Ne}from"../worktree-mutation-async.js";import{usage as u,brokerLaunchFailed as B}from"../errors.js";import{writeGoal as Pe}from"./kickoff.js";import{appendSituationalContext as De,formatSituationalProse as Te}from"./situational-context.js";import{ensureRoadmap as Le}from"./roadmap.js";import{buildWakeBearings as Ae}from"./bearings.js";import{encodeKickoffOrigin as Me,KICKOFF_ORIGIN_ENV as We}from"./stamp/protocol.js";import{ExecutionCapReached as je,beginExecution as ze,crtrHome as He,getNode as l,fullName as oe,jobDir as Ke,recordExecutionPid as Ge,abortExecution as M,setFrozen as W}from"../canvas/index.js";import{contextDir as j,nodeAgentDir as z}from"../canvas/node-agent-paths.js";import{copyTreeBeneath as Ue}from"../spaces/open-beneath.js";import{conversationIdFromPath as N,conversationStore as E}from"../conversation-store/index.js";import{appMemoryTooSmall as Ve,boundFleet as Xe,brokerThresholdsForDaemon as re}from"./fleet.js";import{admitBrokerMemory as qe}from"./memory-admission.js";import{emitEvent as H}from"../events/emit.js";import{blaxelEnvironmentOf as Ye}from"./blaxel-environment.js";import{openViewerWindow as Je,focusOf as Qe,windowOfPane as Ze}from"./placement.js";import{waitForBrokerViewSocket as en}from"./placement-tmux.js";import{transition as k}from"./lifecycle.js";import{beginBootFaultAttempt as nn}from"../canvas/recovery.js";import{headlessBrokerHost as K}from"./host.js";import{ensureDaemon as on}from"../../daemon/manage.js";import{readConfig as rn}from"../config.js";import{modelRequestFromConfig as tn,unregisteredConcreteModel as an}from"../model-routes.js";import{openModelRegistry as dn}from"./model-registry.js";import{MODEL_SPEC_NEXT as sn}from"./model-selection.js";import{forkNodeLedger as cn}from"./ledger.js";import{appRungForGrant as ln}from"../llm/app-rungs.js";import{APP_FAMILY as G}from"../model-routes.js";import{discoverLifecycleHookRegistry as fn}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as un}from"../command-hooks/transport/exec-lifecycle.js";import{someAsync as wn}from"../async-array.js";import{checkStart as mn}from"./start-check.js";function v(e){return{detail:{schema:"crtr.node-outcome-detail/v1",message:e instanceof Error?e.message:String(e)}}}d(v,"launchFailureOutcome");async function hn(e,o,a){try{return await K.launch(e,o,a)}catch(i){try{await k(e,"crash",{reason:"launch_failed",outcome:v(i)})}catch{}const t=i.message;throw B(t.startsWith("[broker]")?t:`[broker] refusing to launch ${e}: ${t}`,"Inspect the cause above, fix the launch recipe or environment, then retry.")}}d(hn,"launchBrokerOrCrash");async function pn(e,o){try{await k(e,"crash",{reason:"launch_failed",outcome:v(o)})}catch{}try{K.teardown(e)}catch{}}d(pn,"crashAndTearDown");async function _n(e){const o=(await E().list()).map(({conversation_id:t,path:s})=>({id:t,path:s})),a=o.filter(t=>t.id===e),i=a.length>0?a:o.filter(t=>t.id.startsWith(e));if(i.length===0)throw u(`--fork-from '${e}': no pi session with that id found.`,{next:"Pass a live node id, a real session uuid/prefix, or an absolute .jsonl session path."});if(i.length>1)throw u(`--fork-from '${e}': ambiguous \u2014 ${i.length} sessions match that id prefix; pass the full id or the .jsonl path.`,{next:"Pass the full session uuid or an absolute .jsonl path to disambiguate."});return i[0].path}d(_n,"resolveSessionUuid");async function ie(e){const o=e.trim();if(o==="")throw u("--fork-from requires a node id, session file, or session uuid.",{next:"Pass a live node id, an absolute .jsonl session path, or a session uuid/prefix."});if(o.includes("/")||o.endsWith(".jsonl")){const i=N(pe(o)?o:_e(process.cwd(),o));if(i===null)throw u(`--fork-from '${o}': invalid conversation path.`,{next:"Pass a conversation file path or id."});return(await E().header(i)).path}const a=await l(o);if(a!==null){const i=a.pi_session_file;if(i==null||i==="")throw u(`node ${o} has no pi session yet \u2014 it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`,{next:"Pick a node that has captured a session, or drop --fork-from."});const t=N(i);if(t===null)throw u(`node ${o} has no valid conversation id.`,{next:"Pick a node with a captured conversation."});return(await E().header(t)).path}return await _n(o)}d(ie,"resolveForkSource");async function kn(e,o,a,i={}){return e===null?null:e!==void 0&&e!==""?Se(e).profileId:o!==null?(await l(o))?.profile_id??null:i.readOnlySelection===!0?Re(a):Oe(a)}d(kn,"resolveProfileId");function te(e){const o=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return tn(e.model,o,e.modelExact===!0)}d(te,"requestFromLaunch");async function ae(e,o,a){const i=a??(e.modelIntent?`${e.modelIntent.family}/${e.modelIntent.strength}`:e.model),t=await ln(o,i,e.modelExact===!0);if(t===null)return e;const{CRTR_MODEL_INTENT:s,...c}=e.env??{};return{...e,model:t.model,modelIntent:{family:G,strength:t.strength},modelExact:void 0,env:{...c,CRTR_MODEL_INTENT:`${G}/${t.strength}`}}}d(ae,"appPaidLaunch");async function de(e,o,a){if(e===void 0||e.kind==="logical"&&e.family!==G)return;const i=await an(e.kind==="logical"?{kind:"concrete",spec:e.spec}:e,a??await dn(),o);if(i!==void 0)throw u(`model '${i.request.spec}' is not registered in this daemon.`,{received:i.request.spec,next:sn})}d(de,"assertLaunchModelRegistered");function vn(e,o,a){const i=e??o??null;if(!a&&i===null)throw new Error("spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent");return i}d(vn,"resolveSpawner");async function se(e,o,a){try{on()}catch{}const i=be(),t=e.root===!0,s=vn(e.parent,i.nodeId??null,t),c=e.mode??"base",y=e.rootLifecycle??(t?"resident":"terminal"),ce=t&&y==="resident",O=s===null?null:await l(s),U=e.creator===void 0||e.creator===null?null:await l(e.creator);e.nodeId!==void 0&&await Ee(e.nodeId);const g=e.forkFrom!==void 0?await ie(e.forkFrom):void 0,P=g===void 0?null:await ge(g),D=await wn([O,U,P],async f=>f?.isolated===!0||f?.run_id!=null&&(await l(f.run_id))?.isolated===!0);if(D&&e.isolated===!1)throw new ee(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});const R=D||e.isolated===!0;if(R&&e.worktree===!0)throw new ee(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const S=e.worktree===!0,w=R||S?e.nodeId??await Ce():e.nodeId,$=R?e.run!==void 0&&t&&!D?e.cwd:ke(ve(e.run?.grantee??O?.grantee??"app:terminal").nodes,w):e.cwd,m=await kn(e.profile,s,$),V=e.run?.grant_id??O?.grant_id??null,X=await ae((await ne(e.kind,c,{lifecycle:y,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:$,profileId:m})).launch,V,e.model);await de(te(X),{cwd:$,profileId:m});const T=e.run??U??O??P;await mn({grantee:T?.grantee??"app:terminal",sub:T?.sub,grant_id:T?.grant_id});const q=P?.node_id??null;let b,F=$;const L=S&&await Fe(m)?await Ie(m,e.worktreeCwd??e.cwd,w,e.worktreeBase):void 0;try{S&&L===void 0&&(b=await Be(e.worktreeCwd??e.cwd,w,e.worktreeBase),F=b.path);const f=S?await ae((await ne(e.kind,c,{lifecycle:y,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:F,profileId:m})).launch,V,e.model):X;L!==void 0&&(f.worktree=L),S&&await de(te(f),{cwd:F,profileId:m});const n=await ye({kind:e.kind,mode:c,lifecycle:y,cwd:F,name:e.name??e.kind,description:e.description,parent:t?null:s,creator:e.creator,...e.actor===void 0?{}:{actor:e.actor},forkSourceNode:q,spawnedBy:t?s??void 0:void 0,profile_id:m,...e.scopes===void 0?{}:{scopes:e.scopes},isolated:R,...e.run===void 0?{}:{run:e.run},...e.causedBy===void 0?{}:{causedBy:e.causedBy},...e.withinCreate===void 0?{}:{withinCreate:e.withinCreate},forkFrom:e.forkFrom,forkSourceFile:g,modelOverride:e.model===void 0?void 0:f.model,...b!==void 0?{managedWorktree:b}:{},...w!==void 0?{nodeId:w}:{},...e.reviewBinding===void 0?{}:{reviewBinding:e.reviewBinding},...e.deadlineAt===void 0?{}:{deadlineAt:e.deadlineAt},...e.outcomeDelivery===void 0?{}:{outcomeDelivery:e.outcomeDelivery},launch:f});if(a?.(n.node_id),e.forkFrom!==void 0)try{await cn(n.node_id,q??null)}catch(r){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(r)}),r}if(await Pe(n.node_id,e.prompt),e.situationalContext!==void 0&&await De(n.node_id,Te(e.situationalContext)),o!==void 0)try{o(n.node_id)}catch(r){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(r)}),r}const A=e.prompt!=="",le=`${A&&e.wakeOrigin!==void 0?`${Ae(e.wakeOrigin)}
1
+ var me=Object.defineProperty;var d=(e,o)=>me(e,"name",{value:o,configurable:!0});import{accessSync as he,constants as pe}from"node:fs";import{isAbsolute as _e,resolve as ke,join as ve}from"node:path";import{appSpace as ge}from"../layout.js";import{ApiError as ne}from"../../api/errors.js";import{spawnNode as ye,forkSourceNode as be,currentNodeContext as xe,rootOfSpine as $e,newNodeId as Se,preflightNodeId as Ce}from"./nodes.js";import{resolveProfileOperand as Ee}from"../profiles/manifest.js";import{selectProfileForCwd as Re,selectProfileForCwdReadOnly as Oe}from"../profiles/select.js";import{buildLaunchSpecAsync as oe,buildPiArgv as Fe}from"./launch.js";import{runsOnBlaxel as Ie,sandboxWorktreeRequest as Be}from"../../daemon/api/environment-of.js";import{createManagedWorktreeAsync as De,rollbackManagedWorktreeAsync as Ne}from"../worktree-mutation-async.js";import{usage as w,brokerLaunchFailed as D}from"../errors.js";import{writeGoal as Pe}from"./kickoff.js";import{appendSituationalContext as Le,formatSituationalProse as Me}from"./situational-context.js";import{ensureRoadmap as Te}from"./roadmap.js";import{buildWakeBearings as Ae}from"./bearings.js";import{encodeKickoffOrigin as We,KICKOFF_ORIGIN_ENV as je}from"./stamp/protocol.js";import{ExecutionCapReached as ze,beginExecution as He,crtrHome as Ke,getNode as u,fullName as re,jobDir as Ge,recordExecutionPid as Ue,abortExecution as j,setFrozen as z}from"../canvas/index.js";import{contextDir as H,nodeAgentDir as K}from"../canvas/node-agent-paths.js";import{copyTreeBeneath as Ve}from"../spaces/open-beneath.js";import{conversationIdFromPath as N,conversationStore as S}from"../conversation-store/index.js";import{appMemoryTooSmall as Xe,boundFleet as qe,brokerThresholdsForDaemon as te}from"./fleet.js";import{admitBrokerMemory as Ye}from"./memory-admission.js";import{emitEvent as P}from"../events/emit.js";import{blaxelEnvironmentOf as Je}from"./blaxel-environment.js";import{openViewerWindow as Qe,focusOf as Ze,windowOfPane as en}from"./placement.js";import{awaitBrokerViewSocket as nn}from"./view-socket.js";import{readLogSnapshot as on}from"../events/read.js";import{transition as k}from"./lifecycle.js";import{beginBootFaultAttempt as rn}from"../canvas/recovery.js";import{headlessBrokerHost as G}from"./host.js";import{ensureDaemon as tn}from"../../daemon/manage.js";import{readConfig as an}from"../config.js";import{modelRequestFromConfig as dn,unregisteredConcreteModel as sn}from"../model-routes.js";import{openModelRegistry as ln}from"./model-registry.js";import{MODEL_SPEC_NEXT as cn}from"./model-selection.js";import{forkNodeLedger as un}from"./ledger.js";import{appRungForGrant as fn}from"../llm/app-rungs.js";import{APP_FAMILY as U}from"../model-routes.js";import{discoverLifecycleHookRegistry as wn}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as mn}from"../command-hooks/transport/exec-lifecycle.js";import{someAsync as hn}from"../async-array.js";import{checkStart as pn}from"./start-check.js";function v(e){return{detail:{schema:"crtr.node-outcome-detail/v1",message:e instanceof Error?e.message:String(e)}}}d(v,"launchFailureOutcome");async function _n(e,o,a){try{return await G.launch(e,o,a)}catch(r){try{await k(e,"crash",{reason:"launch_failed",outcome:v(r)})}catch{}const i=r.message;throw D(i.startsWith("[broker]")?i:`[broker] refusing to launch ${e}: ${i}`,"Inspect the cause above, fix the launch recipe or environment, then retry.")}}d(_n,"launchBrokerOrCrash");function kn(e){return on({nodeId:e,component:"broker",pattern:"broker.startup."},!1).items.flatMap(a=>a.kind==="event"&&a.envelope.event.startsWith("broker.startup.")&&a.envelope.event!=="broker.startup.warning"?[a.envelope.event.slice(15)]:[]).at(-1)??null}d(kn,"lastStartupStep");function vn(e,o){const a=kn(e),r=a===null?"it had logged no startup step":`the last startup step it logged was \`${a}\``;if(o.reason==="exited"){const{code:i,signal:s}=o.status;return{text:`exited (code ${i??"null"}, signal ${s??"null"}) before binding its view socket; ${r}`,fields:{reason:"exited",code:i,signal:s,last_startup_step:a}}}return{text:`was still starting after ${Math.round(o.waitedMs/1e3)} s without binding its view socket, so the launcher stopped it; ${r}`,fields:{reason:"timeout",waited_ms:o.waitedMs,last_startup_step:a}}}d(vn,"notReadyDescription");async function gn(e,o){try{await k(e,"crash",{reason:"launch_failed",outcome:v(o)})}catch{}try{G.teardown(e)}catch{}}d(gn,"crashAndTearDown");async function yn(e){const o=(await S().list()).map(({conversation_id:i,path:s})=>({id:i,path:s})),a=o.filter(i=>i.id===e),r=a.length>0?a:o.filter(i=>i.id.startsWith(e));if(r.length===0)throw w(`--fork-from '${e}': no pi session with that id found.`,{next:"Pass a live node id, a real session uuid/prefix, or an absolute .jsonl session path."});if(r.length>1)throw w(`--fork-from '${e}': ambiguous \u2014 ${r.length} sessions match that id prefix; pass the full id or the .jsonl path.`,{next:"Pass the full session uuid or an absolute .jsonl path to disambiguate."});return r[0].path}d(yn,"resolveSessionUuid");async function ie(e){const o=e.trim();if(o==="")throw w("--fork-from requires a node id, session file, or session uuid.",{next:"Pass a live node id, an absolute .jsonl session path, or a session uuid/prefix."});if(o.includes("/")||o.endsWith(".jsonl")){const r=N(_e(o)?o:ke(process.cwd(),o));if(r===null)throw w(`--fork-from '${o}': invalid conversation path.`,{next:"Pass a conversation file path or id."});return(await S().header(r)).path}const a=await u(o);if(a!==null){const r=a.pi_session_file;if(r==null||r==="")throw w(`node ${o} has no pi session yet \u2014 it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`,{next:"Pick a node that has captured a session, or drop --fork-from."});const i=N(r);if(i===null)throw w(`node ${o} has no valid conversation id.`,{next:"Pick a node with a captured conversation."});return(await S().header(i)).path}return await yn(o)}d(ie,"resolveForkSource");async function bn(e,o,a,r={}){return e===null?null:e!==void 0&&e!==""?Ee(e).profileId:o!==null?(await u(o))?.profile_id??null:r.readOnlySelection===!0?Oe(a):Re(a)}d(bn,"resolveProfileId");function ae(e){const o=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return dn(e.model,o,e.modelExact===!0)}d(ae,"requestFromLaunch");async function de(e,o,a){const r=a??(e.modelIntent?`${e.modelIntent.family}/${e.modelIntent.strength}`:e.model),i=await fn(o,r,e.modelExact===!0);if(i===null)return e;const{CRTR_MODEL_INTENT:s,...l}=e.env??{};return{...e,model:i.model,modelIntent:{family:U,strength:i.strength},modelExact:void 0,env:{...l,CRTR_MODEL_INTENT:`${U}/${i.strength}`}}}d(de,"appPaidLaunch");async function se(e,o,a){if(e===void 0||e.kind==="logical"&&e.family!==U)return;const r=await sn(e.kind==="logical"?{kind:"concrete",spec:e.spec}:e,a??await ln(),o);if(r!==void 0)throw w(`model '${r.request.spec}' is not registered in this daemon.`,{received:r.request.spec,next:cn})}d(se,"assertLaunchModelRegistered");function xn(e,o,a){const r=e??o??null;if(!a&&r===null)throw new Error("spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent");return r}d(xn,"resolveSpawner");async function le(e,o,a){try{tn()}catch{}const r=xe(),i=e.root===!0,s=xn(e.parent,r.nodeId??null,i),l=e.mode??"base",g=e.rootLifecycle??(i?"resident":"terminal"),ce=i&&g==="resident",E=s===null?null:await u(s),V=e.creator===void 0||e.creator===null?null:await u(e.creator);e.nodeId!==void 0&&await Ce(e.nodeId);const y=e.forkFrom!==void 0?await ie(e.forkFrom):void 0,L=y===void 0?null:await be(y),M=await hn([E,V,L],async f=>f?.isolated===!0||f?.run_id!=null&&(await u(f.run_id))?.isolated===!0);if(M&&e.isolated===!1)throw new ne(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});const R=M||e.isolated===!0;if(R&&e.worktree===!0)throw new ne(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const C=e.worktree===!0,m=R||C?e.nodeId??await Se():e.nodeId,O=R?e.run!==void 0&&i&&!M?e.cwd:ve(ge(e.run?.grantee??E?.grantee??"app:terminal").nodes,m):e.cwd,h=await bn(e.profile,s,O),X=e.run?.grant_id??E?.grant_id??null,q=await de((await oe(e.kind,l,{lifecycle:g,hasManager:!i,model:e.model,modelOverride:e.model!==void 0,cwd:O,profileId:h})).launch,X,e.model);await se(ae(q),{cwd:O,profileId:h});const T=e.run??V??E??L;await pn({grantee:T?.grantee??"app:terminal",sub:T?.sub,grant_id:T?.grant_id});const Y=L?.node_id??null;let b,F=O;const A=C&&await Ie(h)?await Be(h,e.worktreeCwd??e.cwd,m,e.worktreeBase):void 0;try{C&&A===void 0&&(b=await De(e.worktreeCwd??e.cwd,m,e.worktreeBase),F=b.path);const f=C?await de((await oe(e.kind,l,{lifecycle:g,hasManager:!i,model:e.model,modelOverride:e.model!==void 0,cwd:F,profileId:h})).launch,X,e.model):q;A!==void 0&&(f.worktree=A),C&&await se(ae(f),{cwd:F,profileId:h});const n=await ye({kind:e.kind,mode:l,lifecycle:g,cwd:F,name:e.name??e.kind,description:e.description,parent:i?null:s,creator:e.creator,...e.actor===void 0?{}:{actor:e.actor},forkSourceNode:Y,spawnedBy:i?s??void 0:void 0,profile_id:h,...e.scopes===void 0?{}:{scopes:e.scopes},isolated:R,...e.run===void 0?{}:{run:e.run},...e.causedBy===void 0?{}:{causedBy:e.causedBy},...e.withinCreate===void 0?{}:{withinCreate:e.withinCreate},forkFrom:e.forkFrom,forkSourceFile:y,modelOverride:e.model===void 0?void 0:f.model,...b!==void 0?{managedWorktree:b}:{},...m!==void 0?{nodeId:m}:{},...e.reviewBinding===void 0?{}:{reviewBinding:e.reviewBinding},...e.deadlineAt===void 0?{}:{deadlineAt:e.deadlineAt},...e.outcomeDelivery===void 0?{}:{outcomeDelivery:e.outcomeDelivery},launch:f});if(a?.(n.node_id),e.forkFrom!==void 0)try{await un(n.node_id,Y??null)}catch(t){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(t)}),t}if(await Pe(n.node_id,e.prompt),e.situationalContext!==void 0&&await Le(n.node_id,Me(e.situationalContext)),o!==void 0)try{o(n.node_id)}catch(t){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(t)}),t}const W=e.prompt!=="",ue=`${W&&e.wakeOrigin!==void 0?`${Ae(e.wakeOrigin)}
2
2
 
3
- `:""}${e.prompt}`;c==="orchestrator"&&await Le(n.node_id,{goal:e.prompt.trim()}),await yn(n);let Y;if(g!==void 0)try{const r=N(g);if(r===null)throw new Error(`invalid fork source ${g}`);const _=await E().fork(r,n.node_id);Y=(await E().header(_)).path}catch(r){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(r)}),r}const x=$e(n,{prompt:le,resumeSessionPath:Y});if(x.env={...x.env,CRTR_SUBTREE:await xe(n.node_id)},A){const r=e.wakeOrigin!==void 0?{type:"runtime",kind:"wake",wake:e.wakeOrigin}:{type:"runtime",kind:"kickoff",...n.spawned_by!=null&&n.spawned_by!==""?{spawnedBy:n.spawned_by}:{}};x.env[We]=Me(r)}const h=Xe();if(await Ye(n.profile_id??null)!==null)return await W(n.node_id,new Date().toISOString()),H({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"remote_environment"}}),{node:await l(n.node_id),frozen:!0,window:null,session:null};const J=await h.reserveSlot(n.node_id),p=J?await qe(n.node_id):null;if(p?.verdict==="too_small"){h.releaseReservation(n.node_id);const r=Ve(p);throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(r)}),r}if(!J||p?.verdict==="denied")return p?.verdict==="denied"&&h.releaseReservation(n.node_id),await W(n.node_id,new Date().toISOString()),H({level:"info",event:"broker.frozen",node_id:n.node_id,fields:p?.verdict==="denied"?{reason:"app_memory",free_bytes:p.free_bytes,needed_bytes:p.needed_bytes}:{reason:"broker_cap",live_count:await h.occupancy(),hard_cap:re().automaticReviveCap}}),{node:await l(n.node_id),frozen:!0,window:null,session:null};const fe=await nn(n.node_id),{automaticReviveCap:ue}=re();let C;try{C=await ze(n.node_id,{daemonEpoch:h.epoch(),cap:ue})}catch(r){if(!(r instanceof je))throw r;return h.releaseReservation(n.node_id),await W(n.node_id,new Date().toISOString()),H({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"broker_cap",live_count:r.live,hard_cap:r.cap}}),{node:await l(n.node_id),frozen:!0,window:null,session:null}}x.env={...x.env,CRTR_EXECUTION_ID:C};let I;try{I=await hn(n.node_id,x,{cwd:n.cwd,name:oe(n),resuming:!1})}catch(r){throw await M(C,{reason:"launch_failed"}),r}finally{h.releaseReservation(n.node_id)}if(I.pid===null){const r=new Error(`broker host returned no pid for ${n.node_id}`);throw await M(C,{reason:"launch_failed"}),await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(r)}),B(`failed to launch the broker engine for ${n.node_id} (${n.name}) \u2014 the node was not started.`,`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then verify the pi engine can start in this environment.`)}if(await Ge(C,n.node_id,I.pid)==="shutdown")throw await M(C,{reason:"superseded"}),K.teardown(n.node_id),B(`broker execution was superseded for ${n.node_id}`,"Retry spawning the node.");if(ce&&e.readiness==="await"&&!await en(n.node_id,I.exited)){const r=await fe.explanation();throw await pn(n.node_id,new Error(r??`broker view socket never bound for ${n.node_id}`)),B(r!==null?`the root broker ${n.node_id} could not start \u2014 ${r}`:`the root broker ${n.node_id} never bound its view socket \u2014 it was not started.`,r!==null?"Resolve the cause named above, then retry.":`The broker engine exited before binding its view socket. Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then retry.`)}let Q=null,Z=null;if(!t&&s!==null&&rn("user").auto_open_child_viewers)try{const r=await Qe(s);if(r!==null&&r.pane!==null&&r.pane!==""){const _=await Je(n.node_id,r.session??"",{name:oe(n),cwd:e.cwd,besidePane:r.pane,waitForBroker:!0,initialWhip:A});_!==null&&_.pane!==null&&_.pane!==""&&(Z=_.session,Q=Ze(_.pane))}}catch{}return{node:await l(n.node_id),frozen:!1,window:Q,session:Z}}catch(f){throw b!==void 0&&w!==void 0&&await l(w)===null&&await Ne(b),f}}d(se,"spawnChildPrepared");async function wo(e){return se(e)}d(wo,"spawnChild");async function yn(e){const o=fn(e.cwd,e.profile_id).plans.get("node:start")??[];await un(o,{node:{id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,cwd:e.cwd,nodeDir:await z(e.node_id),contextDir:await j(e.node_id),jobDir:Ke(e.node_id)},runtime:{isBirth:!0,canvasHome:He(),profile:e.profile_id??null}})}d(yn,"invokeBirthLifecycleHooks");const gn=new Set(["initial-prompt.md","yield-message.md","result.json","jobs"]);async function mo(e,o,a){const i=await l(e);if(i===null)throw u(`unknown node: ${e}`,{received:e,next:"List nodes and pass the id of a node with a captured pi session."});const t=await ie(e),s=await j(e);try{await E().header(N(t)),me(s,he.R_OK)}catch(c){throw new Error(`cannot fork ${e}: its conversation or context is unreadable \u2014 ${c.message}`)}return se({kind:i.kind,mode:"base",cwd:i.cwd,prompt:"",root:!0,readiness:"await",forkFrom:e,...a===void 0?{}:{actor:a},profile:i.profile_id,model:i.model_override??void 0,...o===void 0?{}:{scopes:o}},async c=>{const y=await j(c);Ue(await z(e),s,await z(c),y,gn)})}d(mo,"forkNode");export{ae as appPaidLaunch,de as assertLaunchModelRegistered,mo as forkNode,yn as invokeBirthLifecycleHooks,ie as resolveForkSource,kn as resolveProfileId,vn as resolveSpawner,wo as spawnChild,se as spawnChildPrepared};
3
+ `:""}${e.prompt}`;l==="orchestrator"&&await Te(n.node_id,{goal:e.prompt.trim()}),await $n(n);let J;if(y!==void 0)try{const t=N(y);if(t===null)throw new Error(`invalid fork source ${y}`);const c=await S().fork(t,n.node_id);J=(await S().header(c)).path}catch(t){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(t)}),t}const x=Fe(n,{prompt:ue,resumeSessionPath:J});if(x.env={...x.env,CRTR_SUBTREE:await $e(n.node_id)},W){const t=e.wakeOrigin!==void 0?{type:"runtime",kind:"wake",wake:e.wakeOrigin}:{type:"runtime",kind:"kickoff",...n.spawned_by!=null&&n.spawned_by!==""?{spawnedBy:n.spawned_by}:{}};x.env[je]=We(t)}const p=qe();if(await Je(n.profile_id??null)!==null)return await z(n.node_id,new Date().toISOString()),P({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"remote_environment"}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const Q=await p.reserveSlot(n.node_id),_=Q?await Ye(n.node_id):null;if(_?.verdict==="too_small"){p.releaseReservation(n.node_id);const t=Xe(_);throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(t)}),t}if(!Q||_?.verdict==="denied")return _?.verdict==="denied"&&p.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),P({level:"info",event:"broker.frozen",node_id:n.node_id,fields:_?.verdict==="denied"?{reason:"app_memory",free_bytes:_.free_bytes,needed_bytes:_.needed_bytes}:{reason:"broker_cap",live_count:await p.occupancy(),hard_cap:te().automaticReviveCap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const fe=await rn(n.node_id),{automaticReviveCap:we}=te();let $;try{$=await He(n.node_id,{daemonEpoch:p.epoch(),cap:we})}catch(t){if(!(t instanceof ze))throw t;return p.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),P({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"broker_cap",live_count:t.live,hard_cap:t.cap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null}}x.env={...x.env,CRTR_EXECUTION_ID:$};let I;try{I=await _n(n.node_id,x,{cwd:n.cwd,name:re(n),resuming:!1})}catch(t){throw await j($,{reason:"launch_failed"}),t}finally{p.releaseReservation(n.node_id)}if(I.pid===null){const t=new Error(`broker host returned no pid for ${n.node_id}`);throw await j($,{reason:"launch_failed"}),await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(t)}),D(`failed to launch the broker engine for ${n.node_id} (${n.name}) \u2014 the node was not started.`,`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then verify the pi engine can start in this environment.`)}if(await Ue($,n.node_id,I.pid)==="shutdown")throw await j($,{reason:"superseded"}),G.teardown(n.node_id),D(`broker execution was superseded for ${n.node_id}`,"Retry spawning the node.");if(ce&&e.readiness==="await"){const t=await nn(n.node_id,I.exited);if(!t.ready){const c=vn(n.node_id,t);P({level:"error",event:"broker.launch.not_ready",node_id:n.node_id,fields:c.fields});const B=await fe.explanation();throw await gn(n.node_id,new Error(B??c.text)),D(B!==null?`the root broker ${n.node_id} could not start \u2014 ${B}`:`the root broker ${n.node_id} ${c.text} \u2014 it was not started.`,B!==null?"Resolve the cause named above, then retry.":`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then retry.`)}}let Z=null,ee=null;if(!i&&s!==null&&an("user").auto_open_child_viewers)try{const t=await Ze(s);if(t!==null&&t.pane!==null&&t.pane!==""){const c=await Qe(n.node_id,t.session??"",{name:re(n),cwd:e.cwd,besidePane:t.pane,waitForBroker:!0,initialWhip:W});c!==null&&c.pane!==null&&c.pane!==""&&(ee=c.session,Z=en(c.pane))}}catch{}return{node:await u(n.node_id),frozen:!1,window:Z,session:ee}}catch(f){throw b!==void 0&&m!==void 0&&await u(m)===null&&await Ne(b),f}}d(le,"spawnChildPrepared");async function ko(e){return le(e)}d(ko,"spawnChild");async function $n(e){const o=wn(e.cwd,e.profile_id).plans.get("node:start")??[];await mn(o,{node:{id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,cwd:e.cwd,nodeDir:await K(e.node_id),contextDir:await H(e.node_id),jobDir:Ge(e.node_id)},runtime:{isBirth:!0,canvasHome:Ke(),profile:e.profile_id??null}})}d($n,"invokeBirthLifecycleHooks");const Sn=new Set(["initial-prompt.md","yield-message.md","result.json","jobs"]);async function vo(e,o,a){const r=await u(e);if(r===null)throw w(`unknown node: ${e}`,{received:e,next:"List nodes and pass the id of a node with a captured pi session."});const i=await ie(e),s=await H(e);try{await S().header(N(i)),he(s,pe.R_OK)}catch(l){throw new Error(`cannot fork ${e}: its conversation or context is unreadable \u2014 ${l.message}`)}return le({kind:r.kind,mode:"base",cwd:r.cwd,prompt:"",root:!0,readiness:"await",forkFrom:e,...a===void 0?{}:{actor:a},profile:r.profile_id,model:r.model_override??void 0,...o===void 0?{}:{scopes:o}},async l=>{const g=await H(l);Ve(await K(e),s,await K(l),g,Sn)})}d(vo,"forkNode");export{de as appPaidLaunch,se as assertLaunchModelRegistered,vo as forkNode,$n as invokeBirthLifecycleHooks,ie as resolveForkSource,bn as resolveProfileId,xn as resolveSpawner,ko as spawnChild,le as spawnChildPrepared};
@@ -10,10 +10,31 @@ export declare function connectViewSocket(nodeId: string): Socket;
10
10
  * `timeoutMs`. A view.sock that is a symlink rejects with the refusal, so no
11
11
  * wait reads a planted link as a broker that is still starting. */
12
12
  export declare function probeViewSocket(nodeId: string, timeoutMs: number): Promise<boolean>;
13
+ /** How a wait for a broker's view.sock ended: it accepted, the launch handle reported the broker's exit (with the
14
+ * status Node observed), or the deadline passed with the broker still running. */
15
+ export type BrokerViewWait = {
16
+ ready: true;
17
+ } | {
18
+ ready: false;
19
+ reason: 'exited';
20
+ status: {
21
+ code: number | null;
22
+ signal: NodeJS.Signals | null;
23
+ };
24
+ } | {
25
+ ready: false;
26
+ reason: 'timeout';
27
+ waitedMs: number;
28
+ };
13
29
  /** Wait for a launched broker's view.sock, failing fast when its launch handle
14
- * exits. `exited` remains optional for existing callers that only need the same
15
- * bounded readiness deadline without a handle; `waitMs` shortens that deadline
16
- * for a caller holding an HTTP request open. */
30
+ * exits and saying which way the wait ended. `exited` is optional for callers
31
+ * that only need the bounded readiness deadline without a handle; `waitMs`
32
+ * shortens that deadline for a caller holding an HTTP request open. */
33
+ export declare function awaitBrokerViewSocket(nodeId: string, exited?: Promise<{
34
+ code: number | null;
35
+ signal: NodeJS.Signals | null;
36
+ }>, waitMs?: number): Promise<BrokerViewWait>;
37
+ /** {@link awaitBrokerViewSocket} for callers that only branch on readiness. */
17
38
  export declare function waitForBrokerViewSocket(nodeId: string, exited?: Promise<{
18
39
  code: number | null;
19
40
  signal: NodeJS.Signals | null;
@@ -1,3 +1,3 @@
1
- var m=Object.defineProperty;var a=(n,e)=>m(n,"name",{value:e,configurable:!0});import{EventEmitter as p}from"node:events";import{createConnection as E,Socket as S}from"node:net";import{WebSocket as u}from"ws";import{CLIENT_READ_CAPS as x}from"./broker-protocol.js";import{nodeRunDir as y,viewSocketPath as O}from"../canvas/paths.js";import{isBeneathRefusal as _,socketBeneath as k}from"../spaces/open-beneath.js";const w=3e4,l=100,A=w;function d(n){return new Promise(e=>{setTimeout(e,n)})}a(d,"sleep");function R(n){let e;try{e=k(y(n),O(n))}catch(t){const r=new S;return process.nextTick(()=>r.destroy(t)),r}const s=E(e.path);return s.once("connect",e.release),s.once("close",e.release),s}a(R,"connectViewSocket");function T(n,e){return new Promise((s,t)=>{let r=!1,o=null;const f=a(i=>{r||(r=!0,o!==null&&o.destroy(),i instanceof Error?t(i):s(i))},"finish");o=R(n),o.once("connect",()=>f(!0)),o.once("error",i=>f(_(i)?i:!1)),o.setTimeout(e,()=>f(!1))})}a(T,"probeViewSocket");async function K(n,e,s=w){const t=Date.now()+s,r=e?.then(()=>"exit");for(;Date.now()<=t;){const o=Math.max(0,t-Date.now()),f=T(n,Math.min(l,o)).then(c=>c?"ready":"retry"),i=r===void 0?await f:await Promise.race([f,r]);if(i==="ready")return!0;if(i==="exit")return!1;if(r!==void 0){if(await Promise.race([d(l).then(()=>"sleep"),r])==="exit")return!1}else await d(l)}return!1}a(K,"waitForBrokerViewSocket");const I=4180;function C(n,e){return new B(n,e)}a(C,"connectWssView");class B extends p{static{a(this,"WssViewLink")}ws;failed=!1;ended=!1;terminated=!1;pending=[];constructor(e,s){super(),this.ws=new u(e,{headers:s,maxPayload:x.maxLineBytes}),this.ws.on("open",()=>{for(const t of this.pending.splice(0))this.ws.send(t);this.emit("connect")}),this.ws.on("message",t=>{const r=Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t));this.emit("data",r.at(-1)===10?r:Buffer.concat([r,Buffer.from(`
1
+ var m=Object.defineProperty;var c=(n,e)=>m(n,"name",{value:e,configurable:!0});import{EventEmitter as p}from"node:events";import{createConnection as y,Socket as E}from"node:net";import{WebSocket as u}from"ws";import{CLIENT_READ_CAPS as S}from"./broker-protocol.js";import{nodeRunDir as x,viewSocketPath as k}from"../canvas/paths.js";import{isBeneathRefusal as O,socketBeneath as _}from"../spaces/open-beneath.js";const w=3e4,d=100,K=w;function h(n){return new Promise(e=>{setTimeout(e,n)})}c(h,"sleep");function R(n){let e;try{e=_(x(n),k(n))}catch(t){const r=new E;return process.nextTick(()=>r.destroy(t)),r}const s=y(e.path);return s.once("connect",e.release),s.once("close",e.release),s}c(R,"connectViewSocket");function T(n,e){return new Promise((s,t)=>{let r=!1,i=null;const f=c(o=>{r||(r=!0,i!==null&&i.destroy(),o instanceof Error?t(o):s(o))},"finish");i=R(n),i.once("connect",()=>f(!0)),i.once("error",o=>f(O(o)?o:!1)),i.setTimeout(e,()=>f(!1))})}c(T,"probeViewSocket");async function B(n,e,s=w){const t=Date.now()+s,r=e?.then(i=>({exit:i}));for(;Date.now()<=t;){const i=Math.max(0,t-Date.now()),f=T(n,Math.min(d,i)).then(a=>a?"ready":"retry"),o=r===void 0?await f:await Promise.race([f,r]);if(o==="ready")return{ready:!0};if(typeof o=="object")return{ready:!1,reason:"exited",status:o.exit};if(r!==void 0){const a=await Promise.race([h(d).then(()=>"sleep"),r]);if(typeof a=="object")return{ready:!1,reason:"exited",status:a.exit}}else await h(d)}return{ready:!1,reason:"timeout",waitedMs:s}}c(B,"awaitBrokerViewSocket");async function I(n,e,s=w){return(await B(n,e,s)).ready}c(I,"waitForBrokerViewSocket");const M=4180;function C(n,e){return new g(n,e)}c(C,"connectWssView");class g extends p{static{c(this,"WssViewLink")}ws;failed=!1;ended=!1;terminated=!1;pending=[];constructor(e,s){super(),this.ws=new u(e,{headers:s,maxPayload:S.maxLineBytes}),this.ws.on("open",()=>{for(const t of this.pending.splice(0))this.ws.send(t);this.emit("connect")}),this.ws.on("message",t=>{const r=Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t));this.emit("data",r.at(-1)===10?r:Buffer.concat([r,Buffer.from(`
2
2
  `)]))}),this.ws.on("unexpected-response",(t,r)=>{this.fail(Object.assign(new Error(`broker view refused the upgrade (${r.statusCode??"no status"})`),{code:"ECONNREFUSED"})),this.ws.terminate()}),this.ws.on("error",t=>this.fail(t)),this.ws.on("close",t=>{this.ended||(t===1006&&!this.terminated&&this.fail(Object.assign(new Error("broker view connection dropped (1006)"),{code:"ECONNRESET"})),this.ended=!0,this.emit("close",this.failed))})}get destroyed(){return this.ended||this.ws.readyState===u.CLOSING||this.ws.readyState===u.CLOSED}write(e){const s=e.endsWith(`
3
- `)?e.slice(0,-1):e;return this.ws.readyState===u.CONNECTING?(this.pending.push(s),!0):this.ws.readyState!==u.OPEN?!1:(this.ws.send(s),!0)}destroy(){return this.terminated=!0,this.ws.terminate(),this}fail(e){this.failed||(this.failed=!0,this.emit("error",e))}}async function M(n,e,s,t=w){const r=Date.now()+t;let o=!1;for(s?.then(()=>{o=!0});Date.now()<=r&&!o;){if(await new Promise(i=>{const c=C(n,e),h=setTimeout(()=>{c.destroy(),i(!1)},Math.max(1,r-Date.now()));c.on("connect",()=>{clearTimeout(h),c.destroy(),i(!0)}),c.on("error",()=>{}),c.on("close",()=>{clearTimeout(h),i(!1)})}))return!0;if(Date.now()+l>r)break;await d(l)}return!1}a(M,"waitForWssView");export{I as BROKER_VIEW_PORT,A as BROKER_VIEW_SOCKET_WAIT_MS,R as connectViewSocket,C as connectWssView,T as probeViewSocket,K as waitForBrokerViewSocket,M as waitForWssView};
3
+ `)?e.slice(0,-1):e;return this.ws.readyState===u.CONNECTING?(this.pending.push(s),!0):this.ws.readyState!==u.OPEN?!1:(this.ws.send(s),!0)}destroy(){return this.terminated=!0,this.ws.terminate(),this}fail(e){this.failed||(this.failed=!0,this.emit("error",e))}}async function F(n,e,s,t=w){const r=Date.now()+t;let i=!1;for(s?.then(()=>{i=!0});Date.now()<=r&&!i;){if(await new Promise(o=>{const a=C(n,e),l=setTimeout(()=>{a.destroy(),o(!1)},Math.max(1,r-Date.now()));a.on("connect",()=>{clearTimeout(l),a.destroy(),o(!0)}),a.on("error",()=>{}),a.on("close",()=>{clearTimeout(l),o(!1)})}))return!0;if(Date.now()+d>r)break;await h(d)}return!1}c(F,"waitForWssView");export{M as BROKER_VIEW_PORT,K as BROKER_VIEW_SOCKET_WAIT_MS,B as awaitBrokerViewSocket,R as connectViewSocket,C as connectWssView,T as probeViewSocket,I as waitForBrokerViewSocket,F as waitForWssView};