@north-light/cli 0.3.581 → 0.3.582

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 (83) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/health.d.ts +2 -0
  4. package/dist/api/error-codes.d.ts +5 -0
  5. package/dist/api/error-codes.js +1 -1
  6. package/dist/api/errors.js +1 -1
  7. package/dist/build-root.d.ts +11 -4
  8. package/dist/build-root.js +1 -1
  9. package/dist/cli.js +7 -7
  10. package/dist/clients/attach/viewer.js +428 -428
  11. package/dist/commands/api-client.d.ts +5 -0
  12. package/dist/commands/api-client.js +3 -3
  13. package/dist/commands/sys/desktop.d.ts +5 -0
  14. package/dist/commands/sys/desktop.js +1 -0
  15. package/dist/commands/sys/import/move.d.ts +55 -0
  16. package/dist/commands/sys/import/move.js +8 -0
  17. package/dist/commands/sys/import/resume.js +1 -1
  18. package/dist/commands/sys/import/start.d.ts +3 -4
  19. package/dist/commands/sys/import/start.js +1 -4
  20. package/dist/commands/sys/signin.js +2 -2
  21. package/dist/commands/sys.js +1 -1
  22. package/dist/core/canvas/pid.d.ts +6 -0
  23. package/dist/core/canvas/pid.js +5 -5
  24. package/dist/core/command-plugins/compose.js +1 -1
  25. package/dist/core/command.d.ts +17 -8
  26. package/dist/core/command.js +9 -9
  27. package/dist/core/directory/issuer.d.ts +3 -0
  28. package/dist/core/directory/issuer.js +1 -1
  29. package/dist/core/directory/person-credential.d.ts +28 -3
  30. package/dist/core/directory/person-credential.js +1 -1
  31. package/dist/core/directory/reach.d.ts +3 -0
  32. package/dist/core/directory/reach.js +1 -0
  33. package/dist/core/grants/mirror.d.ts +3 -0
  34. package/dist/core/grants/mirror.js +2 -2
  35. package/dist/core/graph/bodies-boot-copy.d.ts +16 -2
  36. package/dist/core/graph/bodies-boot-copy.js +1 -1
  37. package/dist/core/graph/bodies.d.ts +13 -4
  38. package/dist/core/graph/bodies.js +1 -1
  39. package/dist/core/graph/documents.js +10 -10
  40. package/dist/core/graph/repo-sync/exchange.js +2 -2
  41. package/dist/core/graph/repo-sync/ref-format.d.ts +7 -2
  42. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  43. package/dist/core/import/bundle/layout.d.ts +2 -1
  44. package/dist/core/import/export/cloud.d.ts +4 -2
  45. package/dist/core/import/export/cloud.js +1 -1
  46. package/dist/core/import/export/dump.js +3 -3
  47. package/dist/core/import/load/copy.js +5 -5
  48. package/dist/core/import/load/rewrite.js +9 -5
  49. package/dist/core/import/load/stage.js +1 -1
  50. package/dist/core/import/load/verify.js +8 -7
  51. package/dist/core/io.js +4 -4
  52. package/dist/core/northlight/previews.d.ts +22 -0
  53. package/dist/core/northlight/previews.js +1 -0
  54. package/dist/core/providers/call.js +1 -1
  55. package/dist/core/providers/device.js +1 -1
  56. package/dist/core/providers/files.d.ts +2 -1
  57. package/dist/core/providers/files.js +1 -1
  58. package/dist/core/runtime/broker/cli-relay.js +1 -1
  59. package/dist/core/runtime/broker/model-adapter.js +3 -3
  60. package/dist/core/runtime/broker-ownership.d.ts +1 -0
  61. package/dist/core/runtime/broker-ownership.js +1 -0
  62. package/dist/core/runtime/first-run-offer.d.ts +7 -2
  63. package/dist/core/runtime/first-run-offer.js +10 -2
  64. package/dist/core/runtime/front-door.js +4 -4
  65. package/dist/core/runtime/runner/machine/executor.d.ts +7 -1
  66. package/dist/core/runtime/runner/machine/executor.js +1 -1
  67. package/dist/daemon/api/handlers/daemon.js +1 -1
  68. package/dist/daemon/api/handlers/providers.js +1 -1
  69. package/dist/daemon/api/router.js +1 -1
  70. package/dist/daemon/boot-migrations.js +1 -1
  71. package/dist/daemon/cron/machine-exec.d.ts +6 -0
  72. package/dist/daemon/cron/machine-exec.js +1 -1
  73. package/dist/daemon/crtrd.d.ts +1 -1
  74. package/dist/daemon/crtrd.js +14 -14
  75. package/dist/daemon/machine-daemon.d.ts +7 -4
  76. package/dist/daemon/machine-daemon.js +1 -1
  77. package/dist/daemon/machine-link/config.d.ts +32 -1
  78. package/dist/daemon/machine-link/config.js +3 -3
  79. package/dist/daemon/machine-link/move-notify.d.ts +4 -0
  80. package/dist/daemon/machine-link/move-notify.js +1 -0
  81. package/package.json +2 -2
  82. package/packages/crouter-identity/package.json +1 -1
  83. package/runtime.lock.json +13 -13
@@ -1 +1 @@
1
- var m=Object.defineProperty;var a=(e,n)=>m(e,"name",{value:n,configurable:!0});import{directory as l,normalizeMachineId as p}from"../peer/directory.js";import{peerCall as f}from"../peer/client.js";import{ProviderError as d}from"./errors.js";const s={current:{list:a(()=>l.list(),"list"),lastList:a(()=>l.lastList(),"lastList"),call:a((e,n,i)=>f(e,n,i),"call")}},v="/v1/device/call",x=6e4,g=a(e=>({machine_id:e.machine_id,name:e.name,last_seen:e.last_seen}),"candidateView");function u(e,n){const i=e.map(t=>`${t.name} (${t.machine_id})`).join(", ");return new d("machine_required",n===void 0?`This account has ${e.length} devices: ${i}. Pick one with --machine <machine_id>.`:`${n} is not one of this account's devices. Devices: ${i}. Pick one with --machine <machine_id>.`,{param:"machine",details:{candidates:e.map(g)}})}a(u,"machineRequired");const _=a(e=>{const n=e.last_seen===null?NaN:Date.parse(e.last_seen);return Number.isFinite(n)?n:-1/0},"seenAt");async function E(e,n=s.current,i={}){let t;try{t=await n.list()}catch(r){const c=n.lastList();if(c===null)throw new d("provider_unavailable",`The directory could not be reached to find your devices: ${r instanceof Error?r.message:String(r)}`,{origin:"directory"});t=c}const o=t.filter(r=>r.kind==="device");if(o.length===0)throw new d("provider_not_connected","No device is connected to this account. Install Northlight Desktop on your Mac and sign in.",{origin:"directory"});if(e!==void 0){const r=p(e),c=o.find(h=>h.machine_id===r);if(!c)throw u(o,e);return c}if(o.length>1&&i.newest===!0)return o.reduce((r,c)=>_(c)>_(r)?c:r);if(o.length>1)throw u(o,void 0);return o[0]}a(E,"selectDevice");function w(e){return{tool:e.tool,arguments:e.arguments,version:e.version,request_id:e.requestId,...e.runId===null?{}:{run_id:e.runId},deadline_at:e.deadlineAt,...e.upload===null?{}:{upload:e.upload}}}a(w,"deviceBody");async function b(e,n,i,t,o=s.current){const r=Math.max(1,n.deadlineAt-Date.now());return o.call(e.machine_id,{method:"POST",path:v,body:w(n)},{envelope:i,device:!0,signal:AbortSignal.any([t,AbortSignal.timeout(r)])})}a(b,"sendDeviceCall");function k(e,n,i){switch(n){case"timeout":return new d("device_timeout",`${e.name} did not answer in time.`,{origin:"runtime",details:{machine_id:e.machine_id}});case"directory_unreachable":case"no_directory":return new d("provider_unavailable",i,{origin:"directory"});case"unknown_machine":return new d("provider_not_connected",`${e.name} is no longer on this account.`,{origin:"directory"});case"not_a_crtr_peer":return new d("server_error",i);default:return new d("device_offline",i.trim()===""?`${e.name} is offline.`:i,{origin:"runtime",details:{machine_id:e.machine_id}})}}a(k,"deviceUnreachable");function I(e,n,i,t,o){if(n===401)return new d("device_offline",`${e.name}: Desktop is signed out. Sign in to Northlight Desktop again.`,{origin:"runtime",details:{machine_id:e.machine_id}});if(n===426){const r=o?.error?.details?.supported;return new d("peer_version_too_old",t??`${e.name} does not serve this device major. Update Northlight Desktop.`,{origin:"runtime",details:{machine_id:e.machine_id,...Array.isArray(r)?{supported:r}:{}}})}return n===502&&i==="machine_disconnected"?new d("device_disconnected",`${e.name} disconnected during the call; the action may have run.`,{origin:"runtime",details:{machine_id:e.machine_id}}):i==="payload_too_large"||i==="response_too_large"?new d("provider_error",t??"The call or its answer was too large for the relay (16 MiB).",{origin:"runtime",status:413,details:{code:i,message:t??null,status:n}}):null}a(I,"deviceFailure");export{x as DEFAULT_DEVICE_REQUEST_MS,v as DEVICE_CALL_PATH,w as deviceBody,s as deviceDeps,I as deviceFailure,k as deviceUnreachable,E as selectDevice,b as sendDeviceCall};
1
+ var p=Object.defineProperty;var a=(e,n)=>p(e,"name",{value:n,configurable:!0});import{directory as c,normalizeMachineId as m}from"../peer/directory.js";import{peerCall as f}from"../peer/client.js";import{ProviderError as s}from"./errors.js";const l={current:{list:a(()=>c.list(),"list"),lastList:a(()=>c.lastList(),"lastList"),call:a((e,n,i)=>f(e,n,i),"call")}},g="/v1/device/call",A=6e4,v=a(e=>({machine_id:e.machine_id,name:e.name,last_seen:e.last_seen}),"candidateView");function u(e,n){const i=e.map(r=>`${r.name} (${r.machine_id})`).join(", ");return new s("machine_required",n===void 0?`This account has ${e.length} devices: ${i}. Pick one with --machine <machine_id>.`:`${n} is not one of this account's devices. Devices: ${i}. Pick one with --machine <machine_id>.`,{param:"machine",details:{candidates:e.map(v)}})}a(u,"machineRequired");const h=a(e=>{const n=e.last_seen===null?NaN:Date.parse(e.last_seen);return Number.isFinite(n)?n:-1/0},"seenAt");async function x(e,n=l.current,i={}){let r;try{r=await n.list()}catch(t){const d=n.lastList();if(d===null)throw new s("provider_unavailable",`The directory could not be reached to find your devices: ${t instanceof Error?t.message:String(t)}`,{origin:"directory"});r=d}const o=r.filter(t=>t.kind==="device");if(o.length===0){const t="This needs the Northlight app on your Mac, and none is paired to this account. Run `crtr sys desktop install`: it downloads Northlight Desktop from Northlight, installs it in Applications, opens it and pairs it to this account.",d="Tell the person in one or two sentences what the Northlight app gives them for this task (their own signed-in browser, Messages, files and shell on this Mac) and ask once whether to install it; on yes run `crtr sys desktop install` yourself. If they already said no to it in this conversation, do not ask again: say it needs the app and carry on without it.";throw new s("provider_not_connected",t,{origin:"provider",details:{code:"provider_not_connected",message:t,next:d,status:403}})}if(e!==void 0){const t=m(e),d=o.find(_=>_.machine_id===t);if(!d)throw u(o,e);return d}if(o.length>1&&i.newest===!0)return o.reduce((t,d)=>h(d)>h(t)?d:t);if(o.length>1)throw u(o,void 0);return o[0]}a(x,"selectDevice");function w(e){return{tool:e.tool,arguments:e.arguments,version:e.version,request_id:e.requestId,...e.runId===null?{}:{run_id:e.runId},deadline_at:e.deadlineAt,...e.upload===null?{}:{upload:e.upload}}}a(w,"deviceBody");async function b(e,n,i,r,o=l.current){const t=Math.max(1,n.deadlineAt-Date.now());return o.call(e.machine_id,{method:"POST",path:g,body:w(n)},{envelope:i,device:!0,signal:AbortSignal.any([r,AbortSignal.timeout(t)])})}a(b,"sendDeviceCall");function E(e,n,i){switch(n){case"timeout":return new s("device_timeout",`${e.name} did not answer in time.`,{origin:"runtime",details:{machine_id:e.machine_id}});case"directory_unreachable":case"no_directory":return new s("provider_unavailable",i,{origin:"directory"});case"unknown_machine":return new s("provider_not_connected",`${e.name} is no longer on this account.`,{origin:"directory"});case"not_a_crtr_peer":return new s("server_error",i);default:return new s("device_offline",i.trim()===""?`${e.name} is offline.`:i,{origin:"runtime",details:{machine_id:e.machine_id}})}}a(E,"deviceUnreachable");function N(e,n,i,r,o){if(n===401)return new s("device_offline",`${e.name}: Desktop is signed out. Sign in to Northlight Desktop again.`,{origin:"runtime",details:{machine_id:e.machine_id}});if(n===426){const t=o?.error?.details?.supported;return new s("peer_version_too_old",r??`${e.name} does not serve this device major. Update Northlight Desktop.`,{origin:"runtime",details:{machine_id:e.machine_id,...Array.isArray(t)?{supported:t}:{}}})}return n===502&&i==="machine_disconnected"?new s("device_disconnected",`${e.name} disconnected during the call; the action may have run.`,{origin:"runtime",details:{machine_id:e.machine_id}}):i==="payload_too_large"||i==="response_too_large"?new s("provider_error",r??"The call or its answer was too large for the relay (16 MiB).",{origin:"runtime",status:413,details:{code:i,message:r??null,status:n}}):null}a(N,"deviceFailure");export{A as DEFAULT_DEVICE_REQUEST_MS,g as DEVICE_CALL_PATH,w as deviceBody,l as deviceDeps,N as deviceFailure,E as deviceUnreachable,x as selectDevice,b as sendDeviceCall};
@@ -3,6 +3,7 @@ import { type StorageClient } from '../storage/client.js';
3
3
  * reads; a `ProviderCaller` is passed as is. */
4
4
  export interface FileCallCaller {
5
5
  class: 'person' | 'app' | 'broker';
6
+ sub?: string | null;
6
7
  grantee: string | null;
7
8
  resolved: readonly string[] | null;
8
9
  nodeScopes: readonly string[] | null;
@@ -63,7 +64,7 @@ export interface ResolvedFileResult {
63
64
  export declare const FILE_RESULT_FETCH_BUDGET_MS = 110000;
64
65
  /** Step 2 of a `provider.call` for a leaf with files, before any token is
65
66
  * minted: D3-FILE-1..3 for each file argument, then D3-FILE-6 for the output.
66
- * Refusals: `invalid_request` (`param`), `scope_missing` (`scope`),
67
+ * Refusals: `invalid_request` (`param`; also a file output on a cloud role), `scope_missing` (`scope`),
67
68
  * `not_found`, `storage_unavailable`. A leaf with no files never touches
68
69
  * storage. */
69
70
  export declare function prepareFileCall(caller: FileCallCaller, leaf: LeafFiles, args: Record<string, unknown>, options?: FileOptions): Promise<PreparedFileCall>;
@@ -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"&&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};
1
+ var te=Object.defineProperty;var u=(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 M}from"../layout.js";import{RunError as F}from"../runs/operations.js";import{createFileBeneath as le,existsBeneath as q,openBeneath as ce,removeTreeBeneath as I,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 me}from"../canvas/person-state.js";import{invalidStorageName as N,storageClient as T,StorageRefused as U,UPLOAD_MAX_BYTES as B}from"../storage/client.js";import{contentTypeFor as _e,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 d}from"./errors.js";const Ee=11e4,Ae="app:terminal";function H(e){if(e.class==="person")return e.grantee??Ae;if(!e.grantee)throw new d("server_error","provider caller has no grantee");return e.grantee}u(H,"grantee");function Ce(e){if(e.class==="person"&&e.sub)throw new d("invalid_request","the control plane cannot read files from your computer, so a file argument cannot be sent from here");return e.class==="person"?null:{app:H(e),resolved:e.resolved??[],launched:e.class==="broker"?e.nodeScopes:null}}u(Ce,"fileCaller");function y(e){return e instanceof d?e:e instanceof U?new d("storage_unavailable",`storage refused: ${e.message}`):e instanceof F&&e.code==="storage_unavailable"&&e.retryable?new d("storage_unavailable",e.message):e}u(y,"storageFailure");const b=u(e=>new d("provider_error",e,{origin:"provider"}),"providerError");function Fe(e,t,n){if(typeof n!="string"||!n||!se(n)||n.includes("\0"))throw new d("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 d("scope_missing",i.message,{param:t,...i.scope?{scope:i.scope}:{}}):i.code==="not_found"||i.code==="invalid_request"?new d("not_found",`no such file: ${n}`,{param:t}):i:i}try{if(r.size>B)throw new d("invalid_request",`${t} exceeds 100 MiB`,{param:t});const i=re(r.path),o=N(i);if(o)throw new d("invalid_request",`${t}: file name cannot be sent: ${o}`,{param:t});return{param:t,file:r,name:i,mediaType:_e(r.path)}}catch(i){throw D(r.fd),i}}u(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)}}u(Te,"copyOutbound");async function P(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)}}u(P,"copyArguments");function k(e){const t=N(e);if(t)throw new d("server_error",`file output ${e} cannot name an upload: ${t}`)}u(k,"checkOutputField");async function W(e,t,n){k(t);const r=H(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}}u(W,"createResultUpload");function X(e){return e?{"Crtr-Upload-Id":e.upload_id,"Crtr-Upload-Link":e.upload_link}:{}}u(X,"uploadHeaders");function Y(e,t){const n={...e};for(const r of t)n[r.param]=r.url;return n}u(Y,"withUrls");async function Je(e,t,n,r={}){const i=r.storage??T;if(t.output&&(k(t.output),me()))throw new d("invalid_request","this command returns a file, which cannot be delivered to your computer from here yet, so it is not available from a moved Mac");const o=await P(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)}}u(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 P(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)}}u(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}`}u(Re,"candidate");async function Qe(e,t,n,r={}){const i={result:n,file:null,cleanup:u(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(m){throw m instanceof U?b(`${o.field} names an upload with nothing stored`):y(m)}if(h.size===null)throw h.body.destroy(),new d("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}`);const E=o.grantee,$=e.node_id!==null,J=await we(E),f=$?M(E).nodes:M(E).files,A=$?C(f,e.node_id):f,v=$?C(A,"attachments"):f,K={uid:process.getuid?.()??0,gid:J.gid},O=$&&!q(f,A),Q=$&&!O&&!q(f,v);let w=null,S=null,x=!1;const L=u(async()=>{if(!x){if(x=!0,S)try{await be(S)}catch{}if(w)try{pe(f,w)}catch{}try{O?I(f,A):Q&&I(f,v)}catch{}}},"cleanup");try{he(f,v,()=>{},!0);for(let l=1;;l++){const _=C(v,Re(o.field,l));if(!await ze(_))try{le(f,_,Buffer.alloc(0),432),w=_;break}catch(g){if(g.code!=="EEXIST")throw g}}let m=0;const j=oe("sha256"),V=new de({transform(l,_,g){m+=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 d("storage_unavailable","the file result could not be fetched within 110 s"):l instanceof F?y(l):new d("storage_unavailable",`storage stopped sending ${o.field}: ${l instanceof Error?l.message:String(l)}`)}if(m!==p)throw new d("storage_unavailable",`storage sent ${m} 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 _=await $e(o.upload_id,l);if(!_||_.delete_pending_since)throw new d("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 d("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(m){throw h.body.destroy(),await L(),m}return{result:{...c,[o.field]:w},file:{path:w,upload_id:o.upload_id},cleanup:L}}u(Qe,"resolveFileResult");export{Ee as FILE_RESULT_FETCH_BUDGET_MS,Je as prepareFileCall,Ke as resignForRetry,Qe as resolveFileResult};
@@ -1 +1 @@
1
- var S=Object.defineProperty;var f=(i,d)=>S(i,"name",{value:d,configurable:!0});import{createServer as _,request as h}from"node:http";import{request as k}from"node:https";import{chmodSync as A,existsSync as R,rmSync as B,unlinkSync as y}from"node:fs";import{join as l}from"node:path";import{apiSocketPath as C,BROKER_RELEASED_MARKER as b,BROKER_RELAY_GENERATION as v,nodeRunDir as E}from"../../canvas/paths.js";import{writeFileBeneath as O}from"../../spaces/open-beneath.js";import{currentCausedBy as q}from"./turn-cause.js";import{brokerApiUrl as x}from"./daemon-ops.js";async function j(i,d){const o=l(E(i),"cli.sock"),m=x(),w=f((r,e,t,c)=>{if(m===void 0)return h({socketPath:C(),path:r,method:e,headers:t},c);const s=new URL(r??"/",m);return delete t.host,(s.protocol==="https:"?k:h)(s,{method:e,headers:t},c)},"forward"),n=_((r,e)=>{const t={...r.headers,authorization:`Bearer ${d}`},c=q();c!==void 0&&t["crtr-caused-by"]===void 0&&(t["crtr-caused-by"]=c),delete t.connection;const s=w(r.url,r.method,t,a=>{e.writeHead(a.statusCode??502,a.headers),a.pipe(e)});s.on("error",a=>{e.headersSent||e.writeHead(503,{"content-type":"application/json"}),e.end(JSON.stringify({error:{code:"daemon_unavailable",message:a.message}}))}),r.pipe(s),e.on("close",()=>s.destroy())});R(o)&&y(o),await new Promise((r,e)=>{n.once("error",e),n.listen(o,()=>{n.off("error",e),r()})}),A(o,384);const p=E(i),u=process.env.CRTR_EXECUTION_ID;if(!u)throw new Error("broker relay has no execution id");return O(p,l(p,v),u,{atomic:!0}),B(l(p,b),{force:!0}),{path:o,close(){n.closeAllConnections(),n.close(),R(o)&&y(o)}}}f(j,"serveCliRelay");export{j as serveCliRelay};
1
+ var b=Object.defineProperty;var p=(c,f)=>b(c,"name",{value:f,configurable:!0});import{createServer as _,request as y}from"node:http";import{request as k}from"node:https";import{chmodSync as v,existsSync as R,rmSync as A,unlinkSync as E}from"node:fs";import{join as u}from"node:path";import{apiSocketPath as B,BROKER_RELEASED_MARKER as C,BROKER_RELAY_GENERATION as O,nodeRunDir as S}from"../../canvas/paths.js";import{writeFileBeneath as N}from"../../spaces/open-beneath.js";import{currentCausedBy as U}from"./turn-cause.js";import{brokerApiUrl as q}from"./daemon-ops.js";import{northlightUnreachable as x}from"../../directory/reach.js";async function F(c,f){const o=u(S(c),"cli.sock"),d=q(),w=p((r,e,t,i)=>{if(d===void 0)return y({socketPath:B(),path:r,method:e,headers:t},i);const s=new URL(r??"/",d);return delete t.host,(s.protocol==="https:"?k:y)(s,{method:e,headers:t},i)},"forward"),a=_((r,e)=>{const t={...r.headers,authorization:`Bearer ${f}`},i=U();i!==void 0&&t["crtr-caused-by"]===void 0&&(t["crtr-caused-by"]=i),delete t.connection;const s=w(r.url,r.method,t,n=>{e.writeHead(n.statusCode??502,n.headers),n.pipe(e)});s.on("error",n=>{if(e.headersSent||e.writeHead(503,{"content-type":"application/json"}),d===void 0)return void e.end(JSON.stringify({error:{code:"daemon_unavailable",message:n.message}}));const m=x(n,d);e.end(JSON.stringify({error:{code:m.code,message:m.message,retryable:!0,details:m.details}}))}),r.pipe(s),e.on("close",()=>s.destroy())});R(o)&&E(o),await new Promise((r,e)=>{a.once("error",e),a.listen(o,()=>{a.off("error",e),r()})}),v(o,384);const l=S(c),h=process.env.CRTR_EXECUTION_ID;if(!h)throw new Error("broker relay has no execution id");return N(l,u(l,O),h,{atomic:!0}),A(u(l,C),{force:!0}),{path:o,close(){a.closeAllConnections(),a.close(),R(o)&&E(o)}}}p(F,"serveCliRelay");export{F as serveCliRelay};
@@ -1,3 +1,3 @@
1
- var j=Object.defineProperty;var c=(e,n)=>j(e,"name",{value:n,configurable:!0});import{setTimeout as W}from"node:timers/promises";import{createAssistantMessageEventStream as J,getCurrentSystemPrompt as Y,getCurrentTools as z}from"@earendil-works/pi-ai";import{socketFetch as G}from"../../../api/node-transport.js";import{DEFER_HEAD_REQUEST_HEADER as X,DEFERRED_HEAD_CONTENT_TYPE as K,DEFERRED_HEAD_MAGIC as Q}from"../../../daemon/api/deferred-head.js";import{apiSocketPath as V}from"../../canvas/paths.js";import{currentNodeToken as Z}from"./node-token.js";import{brokerApiUrl as C}from"./daemon-ops.js";import{currentCausedBy as ee}from"./turn-cause.js";import{envModelExact as N,envModelIntent as te}from"../../../shared/env.js";import{MODEL_CONTEXT_BODY_MAX_BYTES as v}from"../../llm/body-limits.js";import{modelRequestFromConfig as ne}from"../../model-routes.js";import{parseProviderRateLimit as re,providerFailureDiagnostic as A}from"../../provider-failure.js";import{UPSTREAM_MODEL_UNAVAILABLE as S}from"../../fault-classifier.js";function Ae(e,n){e.checkAuth=async t=>e.getProvider(t)?{type:"api_key"}:void 0,e.getAvailable=async t=>e.getModels(t),e.getAvailableSnapshot=()=>e.getModels(),e.streamSimple=(t,d,o)=>ue(t,d,o,n)}c(Ae,"configureBrokerModelRuntime");const oe=300,U="crtr:llm-refusal";class k extends Error{static{c(this,"LlmRefusalError")}retryAt;rateLimit;code;status;constructor(n,t,d,o,r){super(n),this.retryAt=t,this.rateLimit=d,this.code=o,this.status=r}}class T extends Error{static{c(this,"UpstreamModelUnavailableError")}}function q(e,n){const t=e===void 0?"the request body":`the request body (${e} bytes)`;return new Error(`request_too_large: ${t} exceeds the ${n}-byte model request limit; compact the conversation to shrink the context`)}c(q,"contextTooLargeError");function D(e){const n=e.details?.rate_limit;if(!n||typeof n!="object")return;const t=re(n);if(t===void 0)return;const{provider:d,model:o}=n;return typeof d=="string"&&d!==""&&typeof o=="string"&&o!==""?{...t,model:`${d}/${o}`}:t}c(D,"refusalRateLimit");function Se(e){for(const n of e.diagnostics??[]){if(n.type!==U)continue;const t=n.details?.retryAt;if(typeof t=="number"&&Number.isFinite(t)&&t>0)return t}}c(Se,"refusalRetryAt");class B extends Error{static{c(this,"ContentFilteredError")}}function F(e){return typeof e=="string"?e:e.map(n=>n.type==="text"?{type:"text",text:n.text}:{type:"image_url",image_url:{url:`data:${n.mimeType};base64,${n.data}`}})}c(F,"wireContent");function se(e){const n=c(o=>o.role==="assistant"&&(o.stopReason==="error"||o.stopReason==="aborted"),"incomplete"),t=e.some(n)?e.filter(o=>!n(o)):e;let d=t.length;for(;d>0&&t[d-1]?.role==="assistant";)d--;return d===t.length?t:t.slice(0,d)}c(se,"wireHistory");function ie(e,n,t,d){const o=[],r=Y(n.messages),a=z(n.messages);r?o.push({role:"system",content:r}):n.systemPrompt&&o.push({role:"system",content:n.systemPrompt});for(const f of se(n.messages))f.role==="user"?o.push({role:"user",content:F(f.content)}):f.role==="toolResult"?o.push({role:"tool",content:F(f.content),tool_call_id:f.toolCallId}):f.role==="assistant"&&o.push({role:"assistant",content:f.content.filter(i=>i.type==="text").map(i=>i.text).join("")||null,tool_calls:f.content.filter(i=>i.type==="toolCall").map(i=>({id:i.id,type:"function",function:{name:i.name,arguments:JSON.stringify(i.arguments)}}))});const _=`${e.provider}/${e.id}`,y=d??te();return{model:ne(_,y,N())?.kind==="logical"?y:_,model_policy:N()?"exact":"substitute",stream:!0,messages:o,...a.length?{tools:a.map(f=>({type:"function",function:{name:f.name,description:f.description,parameters:f.parameters}}))}:{},...t.reasoning?{reasoning_effort:t.reasoning}:{},...t.temperature!==void 0?{temperature:t.temperature}:{},...t.maxTokens!==void 0?{max_tokens:t.maxTokens}:{}}}c(ie,"requestBody");function L(e,n){if(typeof n!="string")return!1;const t=n.indexOf("/");return t<1||t===n.length-1?!1:(e.provider=n.slice(0,t),e.model=n.slice(t+1),!0)}c(L,"applyServedModel");function P(e){if(e.details?.served_model!==void 0)return e.details.served_model;const n=e.details?.rate_limit;return typeof n?.provider=="string"&&typeof n.model=="string"?`${n.provider}/${n.model}`:void 0}c(P,"refusalRoute");function H(e){if(!e||typeof e!="object"||!("error"in e))throw new Error("llm returned an invalid response");const n=e.error;if(!n||typeof n.message!="string")throw new Error("llm returned an invalid refusal");return n}c(H,"parseRefusal");function ae(){const e=C();return e===void 0?"http://localhost/v1/llm":new URL("/v1/llm",e).toString()}c(ae,"llmUrl");function le(){return C()===void 0?G(V()):globalThis.fetch}c(le,"llmFetch");async function ce(e){if(!e.headers.get("content-type")?.startsWith(K))return e;if(!e.body)throw new Error("deferred head response has no body");const n=e.body.getReader();let t=new Uint8Array(0),d=!1;for(;;){const o=t.indexOf(10);if(o<0){const{value:i,done:w}=await n.read();if(w)throw new Error("deferred head response ended before its head");const h=new Uint8Array(t.length+i.length);h.set(t),h.set(i,t.length),t=h;continue}const r=new TextDecoder().decode(t.subarray(0,o)).trim();if(t=t.subarray(o+1),!d){if(`${r}
2
- `!==Q)throw new Error("deferred head response has no magic line");d=!0;continue}if(r==="")continue;const a=JSON.parse(r),_=new Headers;for(const[i,w]of Object.entries(a.headers))for(const h of Array.isArray(w)?w:[w])_.append(i,h);const y=t,f=new ReadableStream({start(i){y.length>0&&i.enqueue(y)},async pull(i){const{value:w,done:h}=await n.read();h?i.close():i.enqueue(w)},cancel(i){return n.cancel(i)}});return new Response(a.status===204?null:f,{status:a.status,headers:_})}}c(ce,"unwrapDeferredHead");function de(){const e=ee();return e===void 0?{}:{"crtr-caused-by":e}}c(de,"causedByHeader");function ue(e,n,t={},d){const o=J(),r={role:"assistant",content:[],api:e.api,provider:e.provider,model:e.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"pending",timestamp:Date.now()};return(async()=>{const a=Z();if(!a)throw new Error("llm requires a node token");const _=JSON.stringify(ie(e,n,t,d)),y=Buffer.byteLength(_);if(y>v)throw q(y,v);let f=0;for(;;){t.signal?.throwIfAborted();const i=await ce(await le()(ae(),{method:"POST",headers:{authorization:`Bearer ${a}`,"content-type":"application/json",[X]:"1",...de()},body:_,signal:t.signal}));if(!i.ok){const u=H(await i.json());if(i.status===413||u.code==="payload_too_large")throw q(y,v);if(L(r,P(u)),u.retryable!==!0)throw new k(u.message,void 0,D(u),u.code,i.status);const s=u.retry_after_s;if(typeof s!="number"||!Number.isFinite(s)||s<=0)throw u.code===S?new T(u.message):new Error(u.message);const m=oe-f;if(s>m){const p=Date.now()+s*1e3;throw new k(`${u.message} (retry after ${s}s, at ${new Date(p).toISOString()})`,p,D(u))}await W(s*1e3,void 0,{signal:t.signal}),f+=s;continue}if(!i.body)throw new Error("llm returned no stream");o.push({type:"start",partial:r});const w=i.body.getReader(),h=new TextDecoder;let x="",b="stop",O,I=!1,M=!1;const $=c(u=>{const s=JSON.parse(u);if(s.error){const l=H(s);throw L(r,P(l)),l.retryable!==!0?new k(l.message,void 0,D(l),l.code):l.code===S?new T(l.message):new Error(l.message)}if(s.served_model){if(M=!0,s.served_model.indexOf("/")<1)throw new Error("llm returned an invalid served_model");L(r,s.served_model)}const m=s.choices?.[0],p=m?.delta;if(p?.reasoning_content){const l=r.content.findIndex(R=>R.type==="thinking"),g=l<0?{type:"thinking",thinking:""}:r.content[l],E=l<0?r.content.push(g)-1:l;if(l<0&&o.push({type:"thinking_start",contentIndex:E,partial:r}),g.type!=="thinking")throw new Error("invalid thinking block");g.thinking+=p.reasoning_content,o.push({type:"thinking_delta",contentIndex:E,delta:p.reasoning_content,partial:r})}if(p?.content){const l=r.content.findIndex(R=>R.type==="text"),g=l<0?{type:"text",text:""}:r.content[l],E=l<0?r.content.push(g)-1:l;if(l<0&&o.push({type:"text_start",contentIndex:E,partial:r}),g.type!=="text")throw new Error("invalid text block");g.text+=p.content,o.push({type:"text_delta",contentIndex:E,delta:p.content,partial:r})}for(const l of p?.tool_calls??[]){const g={type:"toolCall",id:l.id,name:l.function.name,arguments:JSON.parse(l.function.arguments)},E=r.content.push(g)-1;o.push({type:"toolcall_start",contentIndex:E,partial:r}),o.push({type:"toolcall_end",contentIndex:E,toolCall:g,partial:r})}m?.finish_reason&&(b=m.finish_reason),m?.message?.refusal&&(O=m.message.refusal),s.usage&&(I=!0,r.usage={input:s.usage.prompt_tokens,output:s.usage.completion_tokens,cacheRead:s.usage.cache_read_tokens,cacheWrite:s.usage.cache_write_tokens,totalTokens:s.usage.total_tokens,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:typeof s.usage.usd_equivalent=="number"&&Number.isFinite(s.usage.usd_equivalent)?s.usage.usd_equivalent:0}})},"apply");for(;;){const{value:u,done:s}=await w.read();if(s)break;x+=h.decode(u,{stream:!0});let m;for(;(m=x.indexOf(`
3
- `))>=0;){const p=x.slice(0,m).trim();x=x.slice(m+1),p&&$(p)}}if(x+=h.decode(),x.trim()&&$(x.trim()),!I||!M)throw new Error("llm stream ended without usage or served_model");for(const[u,s]of r.content.entries())s.type==="text"&&o.push({type:"text_end",contentIndex:u,content:s.text,partial:r}),s.type==="thinking"&&o.push({type:"thinking_end",contentIndex:u,content:s.thinking,partial:r});if(b==="content_filter")throw new B(O??"Content filtered");if(!["stop","tool_calls","length"].includes(b))throw new Error(`llm returned unknown finish reason: ${b}`);r.stopReason=b==="tool_calls"?"toolUse":b==="length"?"length":"stop",o.push({type:"done",reason:r.stopReason,message:r}),o.end();return}})().catch(a=>{r.stopReason=t.signal?.aborted?"aborted":"error",r.errorMessage=a instanceof Error?a.message:String(a),a instanceof k&&(r.diagnostics=[{type:U,timestamp:Date.now(),...a.retryAt!==void 0?{details:{retryAt:a.retryAt}}:{}},...a.rateLimit!==void 0?[A({status:429,ratelimit:a.rateLimit})]:[],...a.rateLimit===void 0&&a.code==="cap_exhausted"?[A({status:a.status??402,code:a.code})]:[]]),a instanceof T&&(r.diagnostics=[A({status:503,code:S})]),a instanceof B&&r.stopReason==="error"&&(r.rawStopReason="content_filter"),o.push({type:"error",reason:r.stopReason,error:r}),o.end()}),o}c(ue,"streamBrokerModel");export{oe as LLM_BROKER_RETRY_WINDOW_S,U as LLM_REFUSAL_DIAGNOSTIC,Ae as configureBrokerModelRuntime,Se as refusalRetryAt,ie as requestBody,ue as streamBrokerModel,ce as unwrapDeferredHead,se as wireHistory};
1
+ var J=Object.defineProperty;var d=(e,n)=>J(e,"name",{value:n,configurable:!0});import{setTimeout as Y}from"node:timers/promises";import{createAssistantMessageEventStream as z,getCurrentSystemPrompt as G,getCurrentTools as X}from"@earendil-works/pi-ai";import{socketFetch as K}from"../../../api/node-transport.js";import{DEFER_HEAD_REQUEST_HEADER as Q,DEFERRED_HEAD_CONTENT_TYPE as V,DEFERRED_HEAD_MAGIC as Z}from"../../../daemon/api/deferred-head.js";import{apiSocketPath as ee}from"../../canvas/paths.js";import{northlightUnreachable as te}from"../../directory/reach.js";import{currentNodeToken as ne}from"./node-token.js";import{brokerApiUrl as A}from"./daemon-ops.js";import{currentCausedBy as re}from"./turn-cause.js";import{envModelExact as U,envModelIntent as oe}from"../../../shared/env.js";import{MODEL_CONTEXT_BODY_MAX_BYTES as S}from"../../llm/body-limits.js";import{modelRequestFromConfig as se}from"../../model-routes.js";import{parseProviderRateLimit as ie,providerFailureDiagnostic as T}from"../../provider-failure.js";import{UPSTREAM_MODEL_UNAVAILABLE as D}from"../../fault-classifier.js";function De(e,n){e.checkAuth=async t=>e.getProvider(t)?{type:"api_key"}:void 0,e.getAvailable=async t=>e.getModels(t),e.getAvailableSnapshot=()=>e.getModels(),e.streamSimple=(t,u,o)=>pe(t,u,o,n)}d(De,"configureBrokerModelRuntime");const ae=300,q="crtr:llm-refusal";class k extends Error{static{d(this,"LlmRefusalError")}retryAt;rateLimit;code;status;constructor(n,t,u,o,r){super(n),this.retryAt=t,this.rateLimit=u,this.code=o,this.status=r}}class R extends Error{static{d(this,"UpstreamModelUnavailableError")}}function B(e,n){const t=e===void 0?"the request body":`the request body (${e} bytes)`;return new Error(`request_too_large: ${t} exceeds the ${n}-byte model request limit; compact the conversation to shrink the context`)}d(B,"contextTooLargeError");function L(e){const n=e.details?.rate_limit;if(!n||typeof n!="object")return;const t=ie(n);if(t===void 0)return;const{provider:u,model:o}=n;return typeof u=="string"&&u!==""&&typeof o=="string"&&o!==""?{...t,model:`${u}/${o}`}:t}d(L,"refusalRateLimit");function Le(e){for(const n of e.diagnostics??[]){if(n.type!==q)continue;const t=n.details?.retryAt;if(typeof t=="number"&&Number.isFinite(t)&&t>0)return t}}d(Le,"refusalRetryAt");class F extends Error{static{d(this,"ContentFilteredError")}}function P(e){return typeof e=="string"?e:e.map(n=>n.type==="text"?{type:"text",text:n.text}:{type:"image_url",image_url:{url:`data:${n.mimeType};base64,${n.data}`}})}d(P,"wireContent");function ce(e){const n=d(o=>o.role==="assistant"&&(o.stopReason==="error"||o.stopReason==="aborted"),"incomplete"),t=e.some(n)?e.filter(o=>!n(o)):e;let u=t.length;for(;u>0&&t[u-1]?.role==="assistant";)u--;return u===t.length?t:t.slice(0,u)}d(ce,"wireHistory");function le(e,n,t,u){const o=[],r=G(n.messages),i=X(n.messages);r?o.push({role:"system",content:r}):n.systemPrompt&&o.push({role:"system",content:n.systemPrompt});for(const f of ce(n.messages))f.role==="user"?o.push({role:"user",content:P(f.content)}):f.role==="toolResult"?o.push({role:"tool",content:P(f.content),tool_call_id:f.toolCallId}):f.role==="assistant"&&o.push({role:"assistant",content:f.content.filter(c=>c.type==="text").map(c=>c.text).join("")||null,tool_calls:f.content.filter(c=>c.type==="toolCall").map(c=>({id:c.id,type:"function",function:{name:c.name,arguments:JSON.stringify(c.arguments)}}))});const _=`${e.provider}/${e.id}`,y=u??oe();return{model:se(_,y,U())?.kind==="logical"?y:_,model_policy:U()?"exact":"substitute",stream:!0,messages:o,...i.length?{tools:i.map(f=>({type:"function",function:{name:f.name,description:f.description,parameters:f.parameters}}))}:{},...t.reasoning?{reasoning_effort:t.reasoning}:{},...t.temperature!==void 0?{temperature:t.temperature}:{},...t.maxTokens!==void 0?{max_tokens:t.maxTokens}:{}}}d(le,"requestBody");function O(e,n){if(typeof n!="string")return!1;const t=n.indexOf("/");return t<1||t===n.length-1?!1:(e.provider=n.slice(0,t),e.model=n.slice(t+1),!0)}d(O,"applyServedModel");function H(e){if(e.details?.served_model!==void 0)return e.details.served_model;const n=e.details?.rate_limit;return typeof n?.provider=="string"&&typeof n.model=="string"?`${n.provider}/${n.model}`:void 0}d(H,"refusalRoute");function j(e){if(!e||typeof e!="object"||!("error"in e))throw new Error("llm returned an invalid response");const n=e.error;if(!n||typeof n.message!="string")throw new Error("llm returned an invalid refusal");return n}d(j,"parseRefusal");function W(){const e=A();return e===void 0?"http://localhost/v1/llm":new URL("/v1/llm",e).toString()}d(W,"llmUrl");function de(){return A()===void 0?K(ee()):globalThis.fetch}d(de,"llmFetch");async function ue(e){if(!e.headers.get("content-type")?.startsWith(V))return e;if(!e.body)throw new Error("deferred head response has no body");const n=e.body.getReader();let t=new Uint8Array(0),u=!1;for(;;){const o=t.indexOf(10);if(o<0){const{value:c,done:p}=await n.read();if(p)throw new Error("deferred head response ended before its head");const w=new Uint8Array(t.length+c.length);w.set(t),w.set(c,t.length),t=w;continue}const r=new TextDecoder().decode(t.subarray(0,o)).trim();if(t=t.subarray(o+1),!u){if(`${r}
2
+ `!==Z)throw new Error("deferred head response has no magic line");u=!0;continue}if(r==="")continue;const i=JSON.parse(r),_=new Headers;for(const[c,p]of Object.entries(i.headers))for(const w of Array.isArray(p)?p:[p])_.append(c,w);const y=t,f=new ReadableStream({start(c){y.length>0&&c.enqueue(y)},async pull(c){const{value:p,done:w}=await n.read();w?c.close():c.enqueue(p)},cancel(c){return n.cancel(c)}});return new Response(i.status===204?null:f,{status:i.status,headers:_})}}d(ue,"unwrapDeferredHead");function fe(){const e=re();return e===void 0?{}:{"crtr-caused-by":e}}d(fe,"causedByHeader");function pe(e,n,t={},u){const o=z(),r={role:"assistant",content:[],api:e.api,provider:e.provider,model:e.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"pending",timestamp:Date.now()};return(async()=>{const i=ne();if(!i)throw new Error("llm requires a node token");const _=JSON.stringify(le(e,n,t,u)),y=Buffer.byteLength(_);if(y>S)throw B(y,S);let f=0;for(;;){t.signal?.throwIfAborted();const c=await de()(W(),{method:"POST",headers:{authorization:`Bearer ${i}`,"content-type":"application/json",[Q]:"1",...fe()},body:_,signal:t.signal}).catch(l=>{const s=W();throw A()===void 0||t.signal?.aborted?l:new R(te(l,s).message)}),p=await ue(c);if(!p.ok){const l=j(await p.json());if(p.status===413||l.code==="payload_too_large")throw B(y,S);if(O(r,H(l)),l.retryable!==!0)throw new k(l.message,void 0,L(l),l.code,p.status);const s=l.retry_after_s;if(typeof s!="number"||!Number.isFinite(s)||s<=0)throw l.code===D?new R(l.message):new Error(l.message);const h=ae-f;if(s>h){const m=Date.now()+s*1e3;throw new k(`${l.message} (retry after ${s}s, at ${new Date(m).toISOString()})`,m,L(l))}await Y(s*1e3,void 0,{signal:t.signal}),f+=s;continue}if(!p.body)throw new Error("llm returned no stream");o.push({type:"start",partial:r});const w=p.body.getReader(),I=new TextDecoder;let x="",b="stop",M,$=!1,C=!1;const N=d(l=>{const s=JSON.parse(l);if(s.error){const a=j(s);throw O(r,H(a)),a.retryable!==!0?new k(a.message,void 0,L(a),a.code):a.code===D?new R(a.message):new Error(a.message)}if(s.served_model){if(C=!0,s.served_model.indexOf("/")<1)throw new Error("llm returned an invalid served_model");O(r,s.served_model)}const h=s.choices?.[0],m=h?.delta;if(m?.reasoning_content){const a=r.content.findIndex(v=>v.type==="thinking"),g=a<0?{type:"thinking",thinking:""}:r.content[a],E=a<0?r.content.push(g)-1:a;if(a<0&&o.push({type:"thinking_start",contentIndex:E,partial:r}),g.type!=="thinking")throw new Error("invalid thinking block");g.thinking+=m.reasoning_content,o.push({type:"thinking_delta",contentIndex:E,delta:m.reasoning_content,partial:r})}if(m?.content){const a=r.content.findIndex(v=>v.type==="text"),g=a<0?{type:"text",text:""}:r.content[a],E=a<0?r.content.push(g)-1:a;if(a<0&&o.push({type:"text_start",contentIndex:E,partial:r}),g.type!=="text")throw new Error("invalid text block");g.text+=m.content,o.push({type:"text_delta",contentIndex:E,delta:m.content,partial:r})}for(const a of m?.tool_calls??[]){const g={type:"toolCall",id:a.id,name:a.function.name,arguments:JSON.parse(a.function.arguments)},E=r.content.push(g)-1;o.push({type:"toolcall_start",contentIndex:E,partial:r}),o.push({type:"toolcall_end",contentIndex:E,toolCall:g,partial:r})}h?.finish_reason&&(b=h.finish_reason),h?.message?.refusal&&(M=h.message.refusal),s.usage&&($=!0,r.usage={input:s.usage.prompt_tokens,output:s.usage.completion_tokens,cacheRead:s.usage.cache_read_tokens,cacheWrite:s.usage.cache_write_tokens,totalTokens:s.usage.total_tokens,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:typeof s.usage.usd_equivalent=="number"&&Number.isFinite(s.usage.usd_equivalent)?s.usage.usd_equivalent:0}})},"apply");for(;;){const{value:l,done:s}=await w.read();if(s)break;x+=I.decode(l,{stream:!0});let h;for(;(h=x.indexOf(`
3
+ `))>=0;){const m=x.slice(0,h).trim();x=x.slice(h+1),m&&N(m)}}if(x+=I.decode(),x.trim()&&N(x.trim()),!$||!C)throw new Error("llm stream ended without usage or served_model");for(const[l,s]of r.content.entries())s.type==="text"&&o.push({type:"text_end",contentIndex:l,content:s.text,partial:r}),s.type==="thinking"&&o.push({type:"thinking_end",contentIndex:l,content:s.thinking,partial:r});if(b==="content_filter")throw new F(M??"Content filtered");if(!["stop","tool_calls","length"].includes(b))throw new Error(`llm returned unknown finish reason: ${b}`);r.stopReason=b==="tool_calls"?"toolUse":b==="length"?"length":"stop",o.push({type:"done",reason:r.stopReason,message:r}),o.end();return}})().catch(i=>{r.stopReason=t.signal?.aborted?"aborted":"error",r.errorMessage=i instanceof Error?i.message:String(i),i instanceof k&&(r.diagnostics=[{type:q,timestamp:Date.now(),...i.retryAt!==void 0?{details:{retryAt:i.retryAt}}:{}},...i.rateLimit!==void 0?[T({status:429,ratelimit:i.rateLimit})]:[],...i.rateLimit===void 0&&i.code==="cap_exhausted"?[T({status:i.status??402,code:i.code})]:[]]),i instanceof R&&(r.diagnostics=[T({status:503,code:D})]),i instanceof F&&r.stopReason==="error"&&(r.rawStopReason="content_filter"),o.push({type:"error",reason:r.stopReason,error:r}),o.end()}),o}d(pe,"streamBrokerModel");export{ae as LLM_BROKER_RETRY_WINDOW_S,q as LLM_REFUSAL_DIAGNOSTIC,De as configureBrokerModelRuntime,Le as refusalRetryAt,le as requestBody,pe as streamBrokerModel,ue as unwrapDeferredHead,ce as wireHistory};
@@ -0,0 +1 @@
1
+ export declare function brokerCommandDeclaresCanvasHome(command: string, home: string): boolean;
@@ -0,0 +1 @@
1
+ var o=Object.defineProperty;var a=(e,n)=>o(e,"name",{value:n,configurable:!0});function c(e,n){return e.includes(` --canvas-home ${n} `)}a(c,"brokerCommandDeclaresCanvasHome");export{c as brokerCommandDeclaresCanvasHome};
@@ -1,9 +1,14 @@
1
- export declare const FIRST_RUN_OFFER_SENTENCE = "Want a quick tour of how this works? Say yes, or just tell me what you're working on.";
1
+ /** The first message on a computer not yet signed in to Northlight: the tour and sign-in offered together. */
2
+ export declare const FIRST_RUN_OFFER_SENTENCE = "Want a quick tour of how this works? I can also sign you in to Northlight, so I can use your apps like Gmail and Slack, search the web and text you. Say which, or just tell me what you're working on.";
3
+ /** The first message on a computer already signed in: the tour alone. */
4
+ export declare const FIRST_RUN_TOUR_SENTENCE = "Want a quick tour of how this works? Say yes, or just tell me what you're working on.";
2
5
  /** The prompt the front door gives a freshly booted root, and whether the
3
6
  * viewer should play the initial-whip animation (only for a prompt the user
4
- * typed). */
7
+ * typed). `signedIn` is whether this computer's records already live in a
8
+ * Northlight account. */
5
9
  export declare function frontDoorBootPrompt(facts: {
6
10
  offerTour: boolean;
11
+ signedIn: boolean;
7
12
  userPrompt: string | undefined;
8
13
  }): {
9
14
  prompt: string | undefined;
@@ -1,5 +1,13 @@
1
- var o=Object.defineProperty;var e=(t,r)=>o(t,"name",{value:r,configurable:!0});import{formatCard as n}from"../../shared/generated-context.js";const i="Want a quick tour of how this works? Say yes, or just tell me what you're working on.",s=n("first-run",{},`Open this conversation with exactly this sentence and nothing else:
1
+ var r=Object.defineProperty;var n=(t,o)=>r(t,"name",{value:o,configurable:!0});import{formatCard as e}from"../../shared/generated-context.js";const i="Want a quick tour of how this works? I can also sign you in to Northlight, so I can use your apps like Gmail and Slack, search the web and text you. Say which, or just tell me what you're working on.",s="Want a quick tour of how this works? Say yes, or just tell me what you're working on.",a=e("first-run",{},`Open this conversation with exactly this sentence and nothing else:
2
2
 
3
3
  ${i}
4
4
 
5
- If the user says yes, run \`crtr sys tutorial -h\`, pick the track it names, and teach it one lesson per invocation \u2014 in your own words, letting them try each thing before moving on. If the user says anything else, drop the tour entirely and do what they asked.`);function u(t){return t.userPrompt!==void 0&&t.userPrompt.trim()!==""?{prompt:t.userPrompt,initialWhip:!0}:t.offerTour?{prompt:s,initialWhip:!1}:{prompt:void 0,initialWhip:!1}}e(u,"frontDoorBootPrompt");export{i as FIRST_RUN_OFFER_SENTENCE,u as frontDoorBootPrompt};
5
+ Read the user's answer:
6
+ - Sign in (or yes to signing in): run \`crtr sys signin\` and follow what it prints.
7
+ - Tour (or yes to the tour): run \`crtr sys tutorial basic\` and teach it one lesson per invocation \u2014 in your own words, letting them try each thing before moving on.
8
+ - Both: sign in first. When this conversation resumes after the sign-in, start the tour.
9
+ - Anything else: drop both offers and do what they asked. Do not mention the tour or signing in again in this conversation. A command that later answers that it needs a Northlight account still gets its one ask; that answer says how.`),h=e("first-run",{},`Open this conversation with exactly this sentence and nothing else:
10
+
11
+ ${s}
12
+
13
+ If the user says yes, run \`crtr sys tutorial -h\`, pick the track it names, and teach it one lesson per invocation \u2014 in your own words, letting them try each thing before moving on. If the user says anything else, drop the tour entirely and do what they asked.`);function c(t){return t.userPrompt!==void 0&&t.userPrompt.trim()!==""?{prompt:t.userPrompt,initialWhip:!0}:t.offerTour?{prompt:t.signedIn?h:a,initialWhip:!1}:{prompt:void 0,initialWhip:!1}}n(c,"frontDoorBootPrompt");export{i as FIRST_RUN_OFFER_SENTENCE,s as FIRST_RUN_TOUR_SENTENCE,c as frontDoorBootPrompt};
@@ -1,11 +1,11 @@
1
- var _=Object.defineProperty;var m=(i,s)=>_(i,"name",{value:s,configurable:!0});import{existsSync as x,statSync as C}from"node:fs";import{resolve as T}from"node:path";import{SUBTREE_NAMES as L}from"../../build-root.js";import{FRONT_DOOR_ENV as b}from"./front-door-env.js";import{frontDoorBootPrompt as A}from"./first-run-offer.js";import{looksLikeNodeIdToken as W}from"./node-id-token.js";import{markTourOffered as $,recordSetupCompleted as B,setupCompleted as M,tourOfferPending as G}from"./setup-marker.js";function I(i){try{return x(i)&&C(i).isDirectory()}catch{return!1}}m(I,"isDir");const V=new Set(["--name","--kind","--profile","--pick-profile","--continue","-c","--resume","-r"]);async function U(){let i=!1;const s=m(()=>{i=!0},"onSigint");process.once("SIGINT",s);try{const{runSetup:r}=await import("../../commands/sys/setup-wizard.js"),e=await r();if(i){const c=new Error("Interrupted");throw c.name="AbortError",c}await B(e)}finally{process.removeListener("SIGINT",s)}}m(U,"runFirstSetup");function Y(i){let s=process.cwd(),r,e,c,u=!1,p,d;const o=[];for(let f=0;f<i.length;f++){const n=i[f];if(n==="--name")r=i[++f];else if(n==="--kind")e=i[++f];else if(n==="--profile")c=i[++f];else if(n==="--pick-profile")u=!0;else if(n==="--continue"||n==="-c")p="continue";else if(n==="--resume"||n==="-r"){p="pick";const w=i[f+1];w!==void 0&&W(w)&&!I(T(w))&&(d=w,f++)}else n.startsWith("--")||o.push(n)}o.length>0&&I(T(o[0]))&&(s=T(o.shift()));const g=o.length>0?o.join(" "):void 0;return{cwd:s,prompt:g,name:r,kind:e,profile:c,pickProfile:u,resume:p,resumeId:d}}m(Y,"parseRootArgs");import{FRONT_DOOR_ENV as ie}from"./front-door-env.js";async function ee(i){const s=i.slice(2),r=s[0];if(process.env[b]||r==="-h"||r==="--help"||r==="--version"||r==="-v"||r!==void 0&&L.includes(r))return!1;if(r!==void 0){const t=/\s/.test(r),a=!r.startsWith("-")&&I(T(r)),l=V.has(r);if(!t&&!a&&!l)return!1;if(a){const{buildRoot:D}=await import("../../build-root.js");if((await D()).subtrees.some(E=>E.name===r))return!1}}const e=Y(s),c=m(async()=>{if(e.profile===void 0)return;const{resolveProfileOperand:t}=await import("../profiles/manifest.js");e.profile=t(e.profile).profileId},"resolveProfileFlag");e.resumeId===void 0&&await c();const u=process.stdin.isTTY&&process.stdout.isTTY;let p=!1;u&&!M()&&(await U(),p=!0);let d=null,o,g=process.cwd();if(e.resumeId!==void 0){const{resolveResumeTarget:t,noResumeTargetMessage:a}=await import("./resume-root.js"),l=await t(e.resumeId);"error"in l&&(process.stderr.write(`${l.error}
1
+ var E=Object.defineProperty;var m=(i,s)=>E(i,"name",{value:s,configurable:!0});import{existsSync as _,statSync as x}from"node:fs";import{resolve as I}from"node:path";import{SUBTREE_NAMES as L}from"../../build-root.js";import{FRONT_DOOR_ENV as b}from"./front-door-env.js";import{frontDoorBootPrompt as A}from"./first-run-offer.js";import{readMachineConfig as W}from"../../daemon/machine-link/config.js";import{looksLikeNodeIdToken as $}from"./node-id-token.js";import{markTourOffered as M,recordSetupCompleted as B,setupCompleted as G,tourOfferPending as V}from"./setup-marker.js";function T(i){try{return _(i)&&x(i).isDirectory()}catch{return!1}}m(T,"isDir");const U=new Set(["--name","--kind","--profile","--pick-profile","--continue","-c","--resume","-r"]);async function Y(){let i=!1;const s=m(()=>{i=!0},"onSigint");process.once("SIGINT",s);try{const{runSetup:r}=await import("../../commands/sys/setup-wizard.js"),e=await r();if(i){const c=new Error("Interrupted");throw c.name="AbortError",c}await B(e)}finally{process.removeListener("SIGINT",s)}}m(Y,"runFirstSetup");function j(i){let s=process.cwd(),r,e,c,u=!1,p,d;const o=[];for(let f=0;f<i.length;f++){const n=i[f];if(n==="--name")r=i[++f];else if(n==="--kind")e=i[++f];else if(n==="--profile")c=i[++f];else if(n==="--pick-profile")u=!0;else if(n==="--continue"||n==="-c")p="continue";else if(n==="--resume"||n==="-r"){p="pick";const w=i[f+1];w!==void 0&&$(w)&&!T(I(w))&&(d=w,f++)}else n.startsWith("--")||o.push(n)}o.length>0&&T(I(o[0]))&&(s=I(o.shift()));const g=o.length>0?o.join(" "):void 0;return{cwd:s,prompt:g,name:r,kind:e,profile:c,pickProfile:u,resume:p,resumeId:d}}m(j,"parseRootArgs");import{FRONT_DOOR_ENV as ne}from"./front-door-env.js";async function te(i){const s=i.slice(2),r=s[0];if(process.env[b]||r==="-h"||r==="--help"||r==="--version"||r==="-v"||r!==void 0&&L.includes(r))return!1;if(r!==void 0){const t=/\s/.test(r),a=!r.startsWith("-")&&T(I(r)),l=U.has(r);if(!t&&!a&&!l)return!1;if(a){const{buildRoot:C}=await import("../../build-root.js");if((await C()).subtrees.some(D=>D.name===r))return!1}}const e=j(s),c=m(async()=>{if(e.profile===void 0)return;const{resolveProfileOperand:t}=await import("../profiles/manifest.js");e.profile=t(e.profile).profileId},"resolveProfileFlag");e.resumeId===void 0&&await c();const u=process.stdin.isTTY&&process.stdout.isTTY;let p=!1;u&&!G()&&(await Y(),p=!0);let d=null,o,g=process.cwd();if(e.resumeId!==void 0){const{resolveResumeTarget:t,noResumeTargetMessage:a}=await import("./resume-root.js"),l=await t(e.resumeId);"error"in l&&(process.stderr.write(`${l.error}
2
2
  `),process.exit(1)),"none"in l?(e.resumeId.includes("-")&&(process.stderr.write(`${a(e.resumeId)}
3
- `),process.exit(1)),e.prompt=e.prompt===void 0?e.resumeId:`${e.resumeId} ${e.prompt}`,e.resumeId=void 0,await c()):(d=l.node.profile_id,o=l.node.node_id,I(l.node.cwd)&&(g=l.node.cwd))}if(o===void 0){const{selectProfileForCwd:t,selectProfileForCwdReadOnly:a}=await import("../profiles/select.js");p&&e.profile===void 0&&process.stdout.write(`
3
+ `),process.exit(1)),e.prompt=e.prompt===void 0?e.resumeId:`${e.resumeId} ${e.prompt}`,e.resumeId=void 0,await c()):(d=l.node.profile_id,o=l.node.node_id,T(l.node.cwd)&&(g=l.node.cwd))}if(o===void 0){const{selectProfileForCwd:t,selectProfileForCwdReadOnly:a}=await import("../profiles/select.js");p&&e.profile===void 0&&process.stdout.write(`
4
4
  Choose a profile
5
- `),d=e.profile??(u?await t(e.cwd,void 0,e.pickProfile):a(e.cwd))}const{exitForMissingProviderCredential:f,hasConfiguredProviderCredential:n}=await import("./provider-credential-gate.js"),{cliClient:w}=await import("../../commands/api-client.js"),R=d===null?[]:(await w().listProfileEnv(d)).names;if(!await n(R)){u||f();const{runFrontDoorLogin:t}=await import("./front-door-login.js");await t()||f()}const{inTmux:h}=await import("./placement-tmux.js");if(u&&!h()){const{TMUX_STARTED_ENV:t,handoffToTmux:a}=await import("./tmux-handoff.js");process.env[t]===void 0&&a(s,o!==void 0?null:d,g)}const[{bootRoot:k},{resumeRoot:v},{rendererWarning:y,missingPackageWarning:N,piAgentDir:O}]=await Promise.all([import("./boot-root.js"),import("./resume-root.js"),import("./package-health.js")]);try{const t=N(O());t!==null&&process.stderr.write(`
5
+ `),d=e.profile??(u?await t(e.cwd,void 0,e.pickProfile):a(e.cwd))}const{exitForMissingProviderCredential:f,hasConfiguredProviderCredential:n}=await import("./provider-credential-gate.js"),{cliClient:w}=await import("../../commands/api-client.js"),R=d===null?[]:(await w().listProfileEnv(d)).names;if(!await n(R)){u||f();const{runFrontDoorLogin:t}=await import("./front-door-login.js");await t()||f()}const{inTmux:P}=await import("./placement-tmux.js");if(u&&!P()){const{TMUX_STARTED_ENV:t,handoffToTmux:a}=await import("./tmux-handoff.js");process.env[t]===void 0&&a(s,o!==void 0?null:d,g)}const[{bootRoot:k},{resumeRoot:v},{rendererWarning:y,missingPackageWarning:N,piAgentDir:O}]=await Promise.all([import("./boot-root.js"),import("./resume-root.js"),import("./package-health.js")]);try{const t=N(O());t!==null&&process.stderr.write(`
6
6
  ${t}
7
7
 
8
8
  `);const a=y();a!==null&&process.stderr.write(`
9
9
  ${a}
10
10
 
11
- `)}catch{}if(o!==void 0){const{resumeNodeById:t}=await import("./resume-root.js");await t(o)}if(e.resume!==void 0&&await v(e.cwd,e.resume==="pick")==="aborted")return!0;const P=u&&(p||await G()),{prompt:S,initialWhip:F}=A({offerTour:P,userPrompt:e.prompt});return P&&h()&&await $(),await k({...e,prompt:S,initialWhip:F,selectedProfileId:d??void 0}),!0}m(ee,"maybeBootRoot");export{ie as FRONT_DOOR_ENV,ee as maybeBootRoot,Y as parseRootArgs};
11
+ `)}catch{}if(o!==void 0){const{resumeNodeById:t}=await import("./resume-root.js");await t(o)}if(e.resume!==void 0&&await v(e.cwd,e.resume==="pick")==="aborted")return!0;const h=u&&(p||await V()),{prompt:S,initialWhip:F}=A({offerTour:h,signedIn:h&&W()!==void 0,userPrompt:e.prompt});return h&&P()&&await M(),await k({...e,prompt:S,initialWhip:F,selectedProfileId:d??void 0}),!0}m(te,"maybeBootRoot");export{ne as FRONT_DOOR_ENV,te as maybeBootRoot,j as parseRootArgs};
@@ -8,16 +8,22 @@ export interface MachineExecutorDeps {
8
8
  /** Seconds `stop` waits after SIGTERM before SIGKILL. */
9
9
  stopGraceS?: number;
10
10
  }
11
- /** Broker processes: `{pid, execution, node}` for each `broker-cli.js ... --epoch <execution> <node>` command line. */
11
+ /** Broker processes: `{pid, execution, node}` for each `broker-cli.js ... --epoch <execution> <node>` command line. Throws when the process table cannot
12
+ * be read: an unreadable table is not an empty one, and a caller that read it as "no brokers" would start a second broker or report a stop that did not happen. */
12
13
  export declare function findBrokers(): Array<{
13
14
  pid: number;
14
15
  execution: string;
15
16
  node: string;
17
+ command: string;
16
18
  }>;
17
19
  /** The env a machine's broker starts with: what the process runner's `buildBrokerEnv` builds for the same node. The environment's secret rows come from the
18
20
  * control plane in the start bundle with the profile's metadata (this role has no database and no profiles, so the profile is never looked up here); the operational base, the profile's metadata, the app's pi dir and the launch recipe's env
19
21
  * go over them in `buildBrokerEnv`'s order, and the link's own settings are pinned last. */
20
22
  export declare function machineBrokerEnv(bundle: StartBundleDTO, deps: Pick<MachineExecutorDeps, 'apiUrl' | 'environmentId'>, executionId: string): NodeJS.ProcessEnv;
23
+ /** Stop every broker of this crtr home (a machine daemon starting with no sign-in, design-crtr-northlight-signin component 5). A broker of another
24
+ * home on the same computer (its `--canvas-home` differs) is not touched. Rejects when the process table cannot be read or a broker's tree survives
25
+ * SIGKILL, so the caller never reports stopped while something of this home may still run. */
26
+ export declare function stopAllBrokers(graceMs?: number): Promise<void>;
21
27
  export declare function createMachineExecutor(deps: MachineExecutorDeps): {
22
28
  [O in Exclude<keyof MachineOpTable, 'cron.run' | 'cron.cancel'>]: (args: MachineOpTable[O]['args'], signal?: AbortSignal) => Promise<MachineOpTable[O]['result']>;
23
29
  };
@@ -1 +1 @@
1
- var T=Object.defineProperty;var c=(r,n)=>T(r,"name",{value:n,configurable:!0});import{spawn as P}from"node:child_process";import{closeSync as O,existsSync as y,mkdirSync as S,openSync as D,statSync as $}from"node:fs";import{rm as C}from"node:fs/promises";import{dirname as G,join as I}from"node:path";import{fileURLToPath as L}from"node:url";import{jobDir as N,nodeRunDir as v,crtrHome as A,viewSocketPath as U}from"../../../canvas/paths.js";import{captureTeardownSnapshot as B,isAnyPidAlive as F,killProcessTreePids as E,listProcessTable as M}from"../../../canvas/pid.js";import{scopesFilePath as j}from"../../../canvas/paths.js";import{writeFileBeneath as R}from"../../../spaces/open-beneath.js";import{FRONT_DOOR_ENV as K}from"../../front-door-env.js";import{composeAppProcessEnv as V,overlayBrokerEnv as H}from"../../spawn-env.js";const k=c(()=>I(G(L(import.meta.url)),"..","..","broker-cli.js"),"brokerEntry"),b=c(r=>{if(r!==void 0)try{process.kill(-r,"SIGKILL")}catch{}},"killGroup"),J=c(r=>new Promise(n=>setTimeout(n,r)),"sleep");function W(){const r=[];for(const{pid:n,command:i}of M()??[]){const e=/broker-cli\.js .*--epoch (\S+) (\S+)\s*$/.exec(i);e!==null&&r.push({pid:n,execution:e[1],node:e[2]})}return r}c(W,"findBrokers");async function z(r,n,i){const e=await fetch(`${r.replace(/\/$/,"")}/v1/executions/${n}/start`,{headers:{authorization:`Bearer ${i}`},signal:AbortSignal.timeout(3e4)});if(!e.ok)throw new Error(`the control plane answered ${e.status} for the start bundle of ${n}`);return await e.json()}c(z,"fetchBundle");function X(r,n,i){const e=r.launch,o=H({argv:[],env:e.env??{}},V(r.node.cwd,null,r.node.app,r.machine_env??{},{}));return Object.assign(o,{CRTR_API_URL:n.apiUrl,CRTR_EXECUTION_ID:i,CRTR_ENVIRONMENT_ID:n.environmentId,CRTR_VIEW_WSS:"0",[K]:"1"})}c(X,"machineBrokerEnv");function se(r){const n=(r.stopGraceS??10)*1e3,i=c(e=>W().find(o=>o.execution===e),"brokerOf");return{async start(e,o){const t=i(e.executionId);if(t!==void 0)return{pid:t.pid};if(o?.throwIfAborted(),!y(e.cwd)||!$(e.cwd).isDirectory())throw new Error(`the node's cwd ${e.cwd} is not a directory on this machine`);if(!y(k()))throw new Error(`the broker entry ${k()} does not exist`);const u=await z(r.apiUrl,e.executionId,e.nodeToken);o?.throwIfAborted();const a=u.launch,s=v(e.nodeId);S(s,{recursive:!0}),R(s,I(s,"broker-launch.json"),e.launchJson,{atomic:!0}),a.env?.CRTR_SCOPES_FILE!==void 0&&R(s,j(e.nodeId),JSON.stringify(a.scopes??[]),{atomic:!0});const m=N(e.nodeId);S(m,{recursive:!0});const f=D(I(m,"broker.log"),"a"),p=X(u,r,e.executionId);delete p.CRTR_NODE_TOKEN;try{o?.throwIfAborted();const d=P(process.execPath,[k(),"--canvas-home",A(),"--epoch",e.executionId,e.nodeId],{cwd:e.cwd,detached:!0,stdio:["ignore",f,f,"pipe"],env:p}),l=d.stdio[3];if(l==null||!("end"in l))throw b(d.pid),new Error("broker token pipe did not open");const x=await new Promise((_,h)=>{d.once("error",h),d.once("spawn",()=>{d.off("error",h),l.once("error",w=>{b(d.pid),h(w)}),l.end(e.nodeToken,w=>{w?h(w):_(d.pid)})})});return d.unref(),{pid:x}}finally{O(f)}},async wake({execution:e}){const o=i(e.executionId);if(o===void 0)return"no_broker";try{return process.kill(o.pid,"SIGUSR1"),"signalled"}catch(t){if(t.code==="ESRCH")return"no_broker";throw t}},async stop({execution:e,options:o}){const t=i(e.executionId);if(t===void 0)return{confirmed:!0};const{tree:u,identities:a}=B(t.pid,null,o.excludePid),s=c(async(m,f)=>{for(let p=0;F(m);p+=100){if(p>=f)return!1;await J(100)}return!0},"waitUntilDead");try{process.kill(t.pid,"SIGTERM")}catch{}return await s([t.pid],n),E(u,"SIGTERM",a),await s(u,2e3)?{confirmed:!0}:(E(u,"SIGKILL",a),await s(u,5e3)?{confirmed:!0}:{confirmed:!1,error:`broker process tree of ${t.pid} survived SIGKILL`})},async status({execution:e}){const o=i(e.executionId);return o===void 0?{state:"gone"}:{state:"running",pid:o.pid}},async delete({nodeId:e}){return await C(v(e),{recursive:!0,force:!0}),null},async viewer({execution:e}){return{kind:"unix",path:U(e.nodeId)}}}}c(se,"createMachineExecutor");export{se as createMachineExecutor,W as findBrokers,X as machineBrokerEnv};
1
+ var D=Object.defineProperty;var c=(r,n)=>D(r,"name",{value:n,configurable:!0});import{spawn as O}from"node:child_process";import{closeSync as $,existsSync as b,mkdirSync as y,openSync as A,statSync as G}from"node:fs";import{rm as L}from"node:fs/promises";import{dirname as N,join as h}from"node:path";import{fileURLToPath as B}from"node:url";import{jobDir as U,nodeRunDir as v,crtrHome as E,viewSocketPath as j}from"../../../canvas/paths.js";import{captureTeardownSnapshot as F,isAnyPidAlive as M,killProcessTreePids as S,listProcessTable as K}from"../../../canvas/pid.js";import{scopesFilePath as H}from"../../../canvas/paths.js";import{writeFileBeneath as R}from"../../../spaces/open-beneath.js";import{FRONT_DOOR_ENV as V}from"../../front-door-env.js";import{brokerCommandDeclaresCanvasHome as J}from"../../broker-ownership.js";import{composeAppProcessEnv as W,overlayBrokerEnv as z}from"../../spawn-env.js";const w=c(()=>h(N(B(import.meta.url)),"..","..","broker-cli.js"),"brokerEntry"),x=c(r=>{if(r!==void 0)try{process.kill(-r,"SIGKILL")}catch{}},"killGroup"),X=c(r=>new Promise(n=>setTimeout(n,r)),"sleep");function _(){const r=[],n=K();if(n===null)throw new Error("the process table could not be read, so which brokers are running is unknown");for(const{pid:t,command:e}of n){const o=/broker-cli\.js .*--epoch (\S+) (\S+)\s*$/.exec(e);o!==null&&r.push({pid:t,execution:o[1],node:o[2],command:e})}return r}c(_,"findBrokers");async function q(r,n,t){const e=await fetch(`${r.replace(/\/$/,"")}/v1/executions/${n}/start`,{headers:{authorization:`Bearer ${t}`},signal:AbortSignal.timeout(3e4)});if(!e.ok)throw new Error(`the control plane answered ${e.status} for the start bundle of ${n}`);return await e.json()}c(q,"fetchBundle");function Q(r,n,t){const e=r.launch,o=z({argv:[],env:e.env??{}},W(r.node.cwd,null,r.node.app,r.machine_env??{},{}));return Object.assign(o,{CRTR_API_URL:n.apiUrl,CRTR_EXECUTION_ID:t,CRTR_ENVIRONMENT_ID:n.environmentId,CRTR_VIEW_WSS:"0",[V]:"1"})}c(Q,"machineBrokerEnv");async function T(r,n,t){const{tree:e,identities:o}=F(r,null,t);if(o===null)return{confirmed:!1,error:`the process tree of broker ${r} could not be read, so shutdown cannot be confirmed`};const i=c(async(u,l)=>{for(let a=0;M(u);a+=100){if(a>=l)return!1;await X(100)}return!0},"waitUntilDead");try{process.kill(r,"SIGTERM")}catch{}return await i([r],n),S(e,"SIGTERM",o),await i(e,2e3)?{confirmed:!0}:(S(e,"SIGKILL",o),await i(e,5e3)?{confirmed:!0}:{confirmed:!1,error:`broker process tree of ${r} survived SIGKILL`})}c(T,"stopBroker");async function ue(r=1e4){const t=(await Promise.all(_().filter(e=>J(e.command,E())).map(e=>T(e.pid,r)))).filter(e=>!e.confirmed);if(t.length>0)throw new Error(t.map(e=>e.error??"a broker did not stop").join("; "))}c(ue,"stopAllBrokers");function le(r){const n=(r.stopGraceS??10)*1e3,t=c(e=>_().find(o=>o.execution===e),"brokerOf");return{async start(e,o){const i=t(e.executionId);if(i!==void 0)return{pid:i.pid};if(o?.throwIfAborted(),!b(e.cwd)||!G(e.cwd).isDirectory())throw new Error(`the node's cwd ${e.cwd} is not a directory on this machine`);if(!b(w()))throw new Error(`the broker entry ${w()} does not exist`);const u=await q(r.apiUrl,e.executionId,e.nodeToken);o?.throwIfAborted();const l=u.launch,a=v(e.nodeId);y(a,{recursive:!0}),R(a,h(a,"broker-launch.json"),e.launchJson,{atomic:!0}),l.env?.CRTR_SCOPES_FILE!==void 0&&R(a,H(e.nodeId),JSON.stringify(l.scopes??[]),{atomic:!0});const k=U(e.nodeId);y(k,{recursive:!0});const m=A(h(k,"broker.log"),"a"),I=Q(u,r,e.executionId);delete I.CRTR_NODE_TOKEN;try{o?.throwIfAborted();const s=O(process.execPath,[w(),"--canvas-home",E(),"--epoch",e.executionId,e.nodeId],{cwd:e.cwd,detached:!0,stdio:["ignore",m,m,"pipe"],env:I}),d=s.stdio[3];if(d==null||!("end"in d))throw x(s.pid),new Error("broker token pipe did not open");const P=await new Promise((C,f)=>{s.once("error",f),s.once("spawn",()=>{s.off("error",f),d.once("error",p=>{x(s.pid),f(p)}),d.end(e.nodeToken,p=>{p?f(p):C(s.pid)})})});return s.unref(),{pid:P}}finally{$(m)}},async wake({execution:e}){const o=t(e.executionId);if(o===void 0)return"no_broker";try{return process.kill(o.pid,"SIGUSR1"),"signalled"}catch(i){if(i.code==="ESRCH")return"no_broker";throw i}},async stop({execution:e,options:o}){const i=t(e.executionId);return i===void 0?{confirmed:!0}:await T(i.pid,n,o.excludePid)},async status({execution:e}){const o=t(e.executionId);return o===void 0?{state:"gone"}:{state:"running",pid:o.pid}},async delete({nodeId:e}){return await L(v(e),{recursive:!0,force:!0}),null},async viewer({execution:e}){return{kind:"unix",path:j(e.nodeId)}}}}c(le,"createMachineExecutor");export{le as createMachineExecutor,_ as findBrokers,Q as machineBrokerEnv,ue as stopAllBrokers};
@@ -1 +1 @@
1
- var i=Object.defineProperty;var a=(t,e)=>i(t,"name",{value:e,configurable:!0});import{drainState as u,endDrain as m,isDrained as c,settleDrain as l,startDrain as y}from"../../drain.js";import{toMigrateStateDTO as b}from"../map.js";import{migrateLooseKeys as h}from"../../../core/layout-migrate/loose-keys.js";import{boundDaemonControl as s}from"../../control.js";import{ownerOnly as f}from"../router.js";function p(){const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot hand over"};const{graceMs:e}=t.requestHandover();return{status:202,body:{accepted:!0,predecessor_pid:process.pid,predecessor_epoch:t.epoch,grace_ms:e}}}a(p,"handleRestart");async function _(t){if(t.body!==void 0)throw{status:400,code:"invalid_request",message:"admission request must not include a body"};const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot admit normal work"};return{status:200,body:await e.admitStartup()}}a(_,"handleAdmit");async function v(t){const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot run the canvas import"};const n=t.body;if(n!==void 0&&(typeof n!="object"||n===null||Array.isArray(n)))throw{status:400,code:"invalid_request",message:"migration request body must be an object"};const o=n??{};if(o.dry_run!==void 0&&typeof o.dry_run!="boolean")throw{status:400,code:"invalid_request",message:"dry_run must be a boolean"};const d=await h(o.dry_run===!0),r=await e.repairMigrations({dryRun:o.dry_run===!0});return{status:200,body:{...b(r.result,{resumed:r.resumed,resumeError:r.resumeError}),loose_keys:d}}}a(v,"handleMigrate");async function w(t){const e=t.body??{};if(typeof e!="object"||e===null||Array.isArray(e))throw{status:400,code:"invalid_request",message:"drain request body must be an object"};const n=e.settle_limit_ms;if(n!==void 0&&(typeof n!="number"||!Number.isFinite(n)||n<0))throw{status:400,code:"invalid_request",message:"settle_limit_ms must be a non-negative number"};return await y(),{status:200,body:n===void 0?await u():await l({limitMs:n})}}a(w,"handleDrain");async function g(){const t=await c();return{status:200,body:{...await m(),was_drained:t}}}a(g,"handleEndDrain");const O=f([{method:"POST",pattern:"/v1/daemon/restart",handler:a(t=>p(),"handler")},{method:"POST",pattern:"/v1/daemon/migrate",handler:v},{method:"POST",pattern:"/v1/daemon/admit",handler:_},{method:"POST",pattern:"/v1/daemon/drain",handler:w},{method:"DELETE",pattern:"/v1/daemon/drain",handler:g}]);export{O as daemonRoutes};
1
+ var d=Object.defineProperty;var n=(t,e)=>d(t,"name",{value:e,configurable:!0});import{drainState as u,endDrain as m,isDrained as c,settleDrain as l,startDrain as y}from"../../drain.js";import{ApiError as h}from"../../../api/errors.js";import{readMoveRecord as b}from"../../machine-link/config.js";import{toMigrateStateDTO as f}from"../map.js";import{migrateLooseKeys as p}from"../../../core/layout-migrate/loose-keys.js";import{boundDaemonControl as s}from"../../control.js";import{ownerOnly as _}from"../router.js";function v(){const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot hand over"};const{graceMs:e}=t.requestHandover();return{status:202,body:{accepted:!0,predecessor_pid:process.pid,predecessor_epoch:t.epoch,grace_ms:e}}}n(v,"handleRestart");async function w(t){if(t.body!==void 0)throw{status:400,code:"invalid_request",message:"admission request must not include a body"};const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot admit normal work"};return{status:200,body:await e.admitStartup()}}n(w,"handleAdmit");async function g(t){const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot run the canvas import"};const o=t.body;if(o!==void 0&&(typeof o!="object"||o===null||Array.isArray(o)))throw{status:400,code:"invalid_request",message:"migration request body must be an object"};const r=o??{};if(r.dry_run!==void 0&&typeof r.dry_run!="boolean")throw{status:400,code:"invalid_request",message:"dry_run must be a boolean"};const i=await p(r.dry_run===!0),a=await e.repairMigrations({dryRun:r.dry_run===!0});return{status:200,body:{...f(a.result,{resumed:a.resumed,resumeError:a.resumeError}),loose_keys:i}}}n(g,"handleMigrate");async function D(t){const e=t.body??{};if(typeof e!="object"||e===null||Array.isArray(e))throw{status:400,code:"invalid_request",message:"drain request body must be an object"};const o=e.settle_limit_ms;if(o!==void 0&&(typeof o!="number"||!Number.isFinite(o)||o<0))throw{status:400,code:"invalid_request",message:"settle_limit_ms must be a non-negative number"};return await y(),{status:200,body:o===void 0?await u():await l({limitMs:o})}}n(D,"handleDrain");async function q(){if(b()!==void 0)throw new h(409,"conflict","a move to Northlight is unsettled on this computer, so its pause stays: run `crtr sys signin` to settle it");const t=await c();return{status:200,body:{...await m(),was_drained:t}}}n(q,"handleEndDrain");const R=_([{method:"POST",pattern:"/v1/daemon/restart",handler:n(t=>v(),"handler")},{method:"POST",pattern:"/v1/daemon/migrate",handler:g},{method:"POST",pattern:"/v1/daemon/admit",handler:w},{method:"POST",pattern:"/v1/daemon/drain",handler:D},{method:"DELETE",pattern:"/v1/daemon/drain",handler:q}]);export{R as daemonRoutes};
@@ -1 +1 @@
1
- var q=Object.defineProperty;var s=(e,r)=>q(e,"name",{value:r,configurable:!0});import{randomUUID as G}from"node:crypto";import{cloudPersonScope as N}from"../../../core/runs/operations.js";import{directoryIssuer as T}from"../../../core/directory/issuer.js";import{activeGrantForApp as F,allGrants as x,grantFor as C,localGrants as j,providerIdsFromGrants as y}from"../../../core/grants/mirror.js";import{getNode as k}from"../../../core/canvas/canvas.js";import{scopeAllowed as B}from"../../../core/scopes.js";import{ProviderError as f,providerErrorBody as D,scopeMissing as M}from"../../../core/providers/errors.js";import{callProvider as U,providerTools as z}from"../../../core/providers/call.js";import{providerInstallState as L}from"../../../core/providers/bundles.js";import{installedProviderPlugins as V,providerRootClaims as Z,resolveProviderRef as W}from"../../../core/providers/roots.js";import{isCloudRole as h}from"../../../core/canvas/person-state.js";import{listAudit as J,writeProviderAudit as X}from"../../../core/providers/audit.js";import{emitEvent as S}from"../../../core/events/emit.js";import{resolvedScopes as P}from"../router.js";const Ae=new Set(["provider.tools","provider.call","audit.list"]);async function H(e){if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null,origin:{node:null,run:null,machine:e.via}};const r=await C(e.actor.app,e.sub);if(!r)throw new f("grant_revoked",`app ${e.actor.app} has no active grant`);return{class:"app",sub:e.sub,grantee:e.actor.app,client_id:null,resolved:r.resolved,nodeScopes:e.actor.node_scopes,run_id:null,node_id:null,origin:{node:e.actor.node,run:e.actor.place?.run??null,machine:e.via}}}if(e.class==="machine"||e.class==="import")throw new f("permission_error","this operation is not available to the caller");if(e.class==="person"&&e.sub!==void 0)throw new f("permission_error","provider calls are made by the install's person, a node or an app");if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=await F(e.app);if(!r)throw new f("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:await P(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:await P(e),nodeScopes:e.scopes,run_id:e.run??(await k(e.node))?.run_id??null,node_id:e.node}}s(H,"callerFromRequest");function Ee(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(Ee,"callerFromApp");const K=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function Q(e){const r=K.exec(e);return r!==null&&r[1]!=="crtr"}s(Q,"isProviderScope");const A=new WeakMap;function Y(e){let r=A.get(e);return r===void 0&&(r=new Set(e),A.set(e,r)),r}s(Y,"heldSet");function E(e,r){return e===null?!0:Q(r)?Y(e).has(r):B(e,r)}s(E,"holds");function R(e,r){return e.class==="person"?!0:E(e.resolved??[],r)&&E(e.nodeScopes,r)}s(R,"callerAllows");function Re(e,r,n){const o=`${r}:${n}`;if(!R(e,o))throw M(o)}s(Re,"requireGroup");function ee(){return!!T()}s(ee,"providerOperationsEnabled");const re=/^[A-Za-z0-9_-]{1,64}$/;function m(e,r){return new f("invalid_request",e,{param:r})}s(m,"invalid");function I(e){if(typeof e!="string"||!re.test(e)||e==="crtr")throw m("provider must be a provider id","provider");return e}s(I,"parseProvider");function oe(e){if(!e||typeof e!="object"||Array.isArray(e))throw new f("invalid_request","a JSON object is required");const r=e,n=I(r.provider),o=r.tool;if(typeof o!="string"||!o.trim())throw m("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw m("arguments must be an object","arguments");const a=r.version;if(a!==void 0&&(typeof a!="number"||!Number.isInteger(a)||a<1))throw m("version must be an integer >= 1","version");const d=r.approval;if(d!==void 0&&(typeof d!="string"||!d))throw m("approval must be a question id","approval");const u=r.machine;if(u!==void 0&&(typeof u!="string"||!u.trim()))throw m("machine must be a machine id","machine");return{provider:n,tool:o.trim(),arguments:i,...a===void 0?{}:{version:a},...d===void 0?{}:{approval:d},...u===void 0?{}:{machine:u.trim()}}}s(oe,"parseCallInput");async function te(e,r=new URLSearchParams){const n=s((l,v,w)=>{const p=r.get(l);if(p===null)return v;if(!/^\d+$/.test(p)||Number(p)>w||Number(p)<1)throw m(`invalid ${l}`,l);return Number(p)},"numeric"),o=r.has("after")?n("after",0,Number.MAX_SAFE_INTEGER):void 0,i=n("limit",100,1e3),a=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(l=>r.has(l)).map(l=>[l,r.get(l)])),d=N(e.sub),u=d===void 0?{}:{sub:d};return{status:200,body:await J(e.class==="person"?null:e.grantee,{after:o,limit:i,...u,...a})}}s(te,"auditList");async function ne(e,r,n){let o=r;const i=performance.now(),a=V(),{skipped:d}=Z(a),u=h()?await x():await j(),l=h()?new Set(y(u.filter(t=>t.sub===e.sub))):null,v=s(t=>!d.has(t)&&(l===null||l.has(t)),"shown"),w=new Set(a.filter(t=>t.enabled&&t.manifest.provider?.id===t.name&&v(t.name)).map(t=>t.name)),p=y(u).filter(v),_=[...new Set([...w,...p])].sort();if(o!==void 0){const t=W(o);if(t===null)throw new f("not_found",`provider "${o}" is not available`);o=t}if(o!==void 0&&!_.includes(o))throw new f("not_found",`provider "${o}" is not available`);const $=o===void 0?_:[o],O=e.class==="person"?u.flatMap(t=>t.status==="active"&&(!h()||t.sub===e.sub)?t.resolved_scopes.map(({scope:c})=>c):[]):e.resolved??[],b=await Promise.all($.map(async t=>{if(w.has(t))return z(e,t);if(!O.some(g=>g.startsWith(`${t}:`)&&R(e,g)))return null;const c=L(t);return c===void 0?null:{provider:t,display_name:t,status:c.status,...c.error?{error:c.error}:{},groups:[],commands:[]}})).then(t=>t.filter(c=>c!==null));if(S({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-i,fields:{caller:e.class,provider:o??null,providers:b.length}}),o!==void 0){if(b.length===0)throw new f("not_found",`provider "${o}" is not available`);return{status:200,body:b[0]}}return{status:200,body:{providers:b}}}s(ne,"providerTools");async function se(e,r,n){return U(e,r,n)}s(se,"providerCall");async function ie(e,r,n,o){if(!ee())throw new f("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return await te(r,n.query);if(e==="provider.tools")return ne(r,n.params.provider===void 0?void 0:I(n.params.provider),o);if(e==="provider.call"){let i;try{i=oe(n.body)}catch(a){const d=n.body&&typeof n.body=="object"&&!Array.isArray(n.body)?n.body:{};throw await X({request_id:o.requestId,caller:r,provider:typeof d.provider=="string"?d.provider:"",tool:typeof d.tool=="string"?d.tool:"",outcome:`refused:${a instanceof f?a.code:"invalid_request"}`}),a}return se(r,i,o)}throw new f("not_found","unknown operation")}s(ie,"handleProviderOperation");function ae(e){const r=new AbortController,n=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",n),{signal:r.signal,release:s(()=>e.off("close",n),"release")}}s(ae,"abortOnClose");function de(e,r,n,o){const i=D(e,r,n,o??void 0);return i.status>=500&&!(e instanceof f)&&S({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(de,"providerFailure");const Ie=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:n})=>({method:e,pattern:r,handler:s(async o=>{const i=G(),{signal:a,release:d}=ae(o.res);let u=null;try{return u=await H(o.principal),{...await ie(n,u,{params:o.params,body:o.body,query:new URL(o.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:a,...o.causedBy===void 0?{}:{causedBy:o.causedBy}}),headers:{"Request-Id":i}}}catch(l){return de(l,i,!1,u?.run_id)}finally{d()}},"handler")}));export{Ae as PROVIDER_OPERATIONS,ae as abortOnClose,R as callerAllows,Ee as callerFromApp,H as callerFromRequest,ie as handleProviderOperation,oe as parseCallInput,I as parseProvider,de as providerFailure,ee as providerOperationsEnabled,Ie as providerRoutes,Re as requireGroup};
1
+ var q=Object.defineProperty;var s=(e,r)=>q(e,"name",{value:r,configurable:!0});import{randomUUID as G}from"node:crypto";import{cloudPersonScope as N}from"../../../core/runs/operations.js";import{directoryIssuer as T}from"../../../core/directory/issuer.js";import{activeGrantForApp as F,allGrants as x,grantFor as C,localGrants as j,providerIdsFromGrants as S}from"../../../core/grants/mirror.js";import{getNode as k}from"../../../core/canvas/canvas.js";import{scopeAllowed as B}from"../../../core/scopes.js";import{ProviderError as f,providerErrorBody as D,scopeMissing as M}from"../../../core/providers/errors.js";import{callProvider as U,providerTools as z}from"../../../core/providers/call.js";import{providerInstallState as L}from"../../../core/providers/bundles.js";import{installedProviderPlugins as V,providerRootClaims as Z,resolveProviderRef as W}from"../../../core/providers/roots.js";import{isCloudRole as h}from"../../../core/canvas/person-state.js";import{listAudit as J,writeProviderAudit as X}from"../../../core/providers/audit.js";import{emitEvent as P}from"../../../core/events/emit.js";import{resolvedScopes as _}from"../router.js";const Ae=new Set(["provider.tools","provider.call","audit.list"]);async function H(e){if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null,origin:{node:null,run:null,machine:e.via}};const r=await C(e.actor.app,e.sub);if(!r)throw new f("grant_revoked",`app ${e.actor.app} has no active grant`);return{class:"app",sub:e.sub,grantee:e.actor.app,client_id:null,resolved:r.resolved,nodeScopes:e.actor.node_scopes,run_id:null,node_id:null,origin:{node:e.actor.node,run:e.actor.place?.run??null,machine:e.via}}}if(e.class==="machine"||e.class==="import")throw new f("permission_error","this operation is not available to the caller");if(e.class==="person"&&e.sub!==void 0){if(!h())throw new f("permission_error","provider calls are made by the install's person, a node or an app");return{class:"person",sub:e.sub,grantee:null,client_id:null,resolved:await _(e),nodeScopes:null,run_id:null,node_id:null}}if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=await F(e.app);if(!r)throw new f("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:await _(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:await _(e),nodeScopes:e.scopes,run_id:e.run??(await k(e.node))?.run_id??null,node_id:e.node}}s(H,"callerFromRequest");function Ee(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(Ee,"callerFromApp");const K=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function Q(e){const r=K.exec(e);return r!==null&&r[1]!=="crtr"}s(Q,"isProviderScope");const A=new WeakMap;function Y(e){let r=A.get(e);return r===void 0&&(r=new Set(e),A.set(e,r)),r}s(Y,"heldSet");function E(e,r){return e===null?!0:Q(r)?Y(e).has(r):B(e,r)}s(E,"holds");function R(e,r){return e.class==="person"&&e.resolved===null?!0:E(e.resolved??[],r)&&E(e.nodeScopes,r)}s(R,"callerAllows");function Re(e,r,n){const o=`${r}:${n}`;if(!R(e,o))throw M(o)}s(Re,"requireGroup");function ee(){return!!T()}s(ee,"providerOperationsEnabled");const re=/^[A-Za-z0-9_-]{1,64}$/;function v(e,r){return new f("invalid_request",e,{param:r})}s(v,"invalid");function I(e){if(typeof e!="string"||!re.test(e)||e==="crtr")throw v("provider must be a provider id","provider");return e}s(I,"parseProvider");function oe(e){if(!e||typeof e!="object"||Array.isArray(e))throw new f("invalid_request","a JSON object is required");const r=e,n=I(r.provider),o=r.tool;if(typeof o!="string"||!o.trim())throw v("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw v("arguments must be an object","arguments");const d=r.version;if(d!==void 0&&(typeof d!="number"||!Number.isInteger(d)||d<1))throw v("version must be an integer >= 1","version");const a=r.approval;if(a!==void 0&&(typeof a!="string"||!a))throw v("approval must be a question id","approval");const l=r.machine;if(l!==void 0&&(typeof l!="string"||!l.trim()))throw v("machine must be a machine id","machine");return{provider:n,tool:o.trim(),arguments:i,...d===void 0?{}:{version:d},...a===void 0?{}:{approval:a},...l===void 0?{}:{machine:l.trim()}}}s(oe,"parseCallInput");async function te(e,r=new URLSearchParams){const n=s((u,p,b)=>{const m=r.get(u);if(m===null)return p;if(!/^\d+$/.test(m)||Number(m)>b||Number(m)<1)throw v(`invalid ${u}`,u);return Number(m)},"numeric"),o=r.has("after")?n("after",0,Number.MAX_SAFE_INTEGER):void 0,i=n("limit",100,1e3),d=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(u=>r.has(u)).map(u=>[u,r.get(u)])),a=N(e.sub),l=a===void 0?{}:{sub:a};return{status:200,body:await J(e.class==="person"?null:e.grantee,{after:o,limit:i,...l,...d})}}s(te,"auditList");async function ne(e,r,n){let o=r;const i=performance.now(),d=V(),{skipped:a}=Z(d),l=h()?await x():await j(),u=h()?new Set(S(l.filter(t=>t.sub===e.sub))):null,p=s(t=>!a.has(t)&&(u===null||u.has(t)),"shown"),b=new Set(d.filter(t=>t.enabled&&t.manifest.provider?.id===t.name&&p(t.name)).map(t=>t.name)),m=S(l).filter(p),g=[...new Set([...b,...m])].sort();if(o!==void 0){const t=W(o);if(t===null)throw new f("not_found",`provider "${o}" is not available`);o=t}if(o!==void 0&&!g.includes(o))throw new f("not_found",`provider "${o}" is not available`);const $=o===void 0?g:[o],O=e.class==="person"?l.flatMap(t=>t.status==="active"&&(!h()||t.sub===e.sub)?t.resolved_scopes.map(({scope:c})=>c):[]):e.resolved??[],w=await Promise.all($.map(async t=>{if(b.has(t))return z(e,t);if(!O.some(y=>y.startsWith(`${t}:`)&&R(e,y)))return null;const c=L(t);return c===void 0?null:{provider:t,display_name:t,status:c.status,...c.error?{error:c.error}:{},groups:[],commands:[]}})).then(t=>t.filter(c=>c!==null));if(P({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-i,fields:{caller:e.class,provider:o??null,providers:w.length}}),o!==void 0){if(w.length===0)throw new f("not_found",`provider "${o}" is not available`);return{status:200,body:w[0]}}return{status:200,body:{providers:w}}}s(ne,"providerTools");async function se(e,r,n){return U(e,r,n)}s(se,"providerCall");async function ie(e,r,n,o){if(!ee())throw new f("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return await te(r,n.query);if(e==="provider.tools")return ne(r,n.params.provider===void 0?void 0:I(n.params.provider),o);if(e==="provider.call"){let i;try{i=oe(n.body)}catch(d){const a=n.body&&typeof n.body=="object"&&!Array.isArray(n.body)?n.body:{};throw await X({request_id:o.requestId,caller:r,provider:typeof a.provider=="string"?a.provider:"",tool:typeof a.tool=="string"?a.tool:"",outcome:`refused:${d instanceof f?d.code:"invalid_request"}`}),d}return se(r,i,o)}throw new f("not_found","unknown operation")}s(ie,"handleProviderOperation");function de(e){const r=new AbortController,n=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",n),{signal:r.signal,release:s(()=>e.off("close",n),"release")}}s(de,"abortOnClose");function ae(e,r,n,o){const i=D(e,r,n,o??void 0);return i.status>=500&&!(e instanceof f)&&P({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(ae,"providerFailure");const Ie=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:n})=>({method:e,pattern:r,handler:s(async o=>{const i=G(),{signal:d,release:a}=de(o.res);let l=null;try{return l=await H(o.principal),{...await ie(n,l,{params:o.params,body:o.body,query:new URL(o.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:d,...o.causedBy===void 0?{}:{causedBy:o.causedBy}}),headers:{"Request-Id":i}}}catch(u){return ae(u,i,!1,l?.run_id)}finally{a()}},"handler")}));export{Ae as PROVIDER_OPERATIONS,de as abortOnClose,R as callerAllows,Ee as callerFromApp,H as callerFromRequest,ie as handleProviderOperation,oe as parseCallInput,I as parseProvider,ae as providerFailure,ee as providerOperationsEnabled,Ie as providerRoutes,Re as requireGroup};
@@ -1 +1 @@
1
- var P=Object.defineProperty;var d=(e,t)=>P(e,"name",{value:t,configurable:!0});import{toErrorBody as H}from"./map.js";import{emitEvent as D}from"../../core/events/emit.js";import{scopeAllowed as _,ScopeDeniedError as I}from"../../core/scopes.js";import{coveredByAny as L}from"../../shared/env.js";import{ApiError as u}from"../../api/errors.js";import{activeGrant as F,grantFor as S}from"../../core/grants/mirror.js";import{getNode as w}from"../../core/canvas/canvas.js";import{ownedRun as J,RunError as b}from"../../core/runs/operations.js";import{openDb as x}from"../../core/canvas/db.js";import{check as Y,permits as M}from"../../core/permissions/evaluator.js";import{readerFor as R}from"./reader.js";import{checkCronFire as Z}from"../cron/authority.js";import{operationFor as V}from"./operations.js";import{CONTROL_BODY_MAX_BYTES as X}from"../../core/llm/body-limits.js";import{getRequest as A,getRequestByInboxTicket as p}from"../../core/human/requests.js";import{requireVisibleReview as g}from"../../core/review/store.js";import{getComment as v}from"../../core/review/comments.js";import{getStructuredRequest as K}from"../../core/canvas/structured-requests.js";async function Q(e,t,o,n,s){const r=e.path,a=s&&typeof s=="object"&&!Array.isArray(s)?s:{};if(r==="/v1/human/requests"||r==="/v1/human/reviews"){const i=r.endsWith("/reviews")?a.origin_node_id:a.requester_node_id,c=r==="/v1/human/requests"&&i==null;if(i!==t.node&&!c)throw new u(403,"other_app_run","a broker may create a human request only for its own node");const h=a.source;if(h&&typeof h=="object"&&"nodeId"in h&&h.nodeId!==t.node)throw new u(403,"other_app_run","request source must name the calling node")}if(r==="/v1/human/reviews/self/comments"&&e.method==="GET"&&n.get("actor_node_id")!==t.node)throw new u(403,"other_app_run","self review requires the calling node");if(r.includes("/comments")&&e.method!=="GET"&&a.actor_node_id!==t.node)throw new u(403,"other_app_run","comment author must be the calling node");if(o.comment_id&&!await v(o.comment_id))throw new u(404,"not_found","comment not found")}d(Q,"authorizeHumanBody");function O(e){return e.class==="broker"||e.class==="cron"}d(O,"boundedByStoredScopes");function k(e){return e.class==="person"||e.class==="anonymous"||e.class==="machine"||e.class==="import"?null:e.class==="forwarded"?e.actor.kind==="app"?e.actor.app:null:e.app}d(k,"appOf");function y(e){return e.class==="person"&&e.sub===void 0||e.class==="forwarded"&&e.actor.kind==="person"}d(y,"actsAsPerson");async function C(e){const t=await S(e.actor.app,e.sub);if(!t)throw new u(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);return t}d(C,"forwardedGrant");async function $(e){if(e.class==="person"||e.class==="anonymous"||e.class==="machine"||e.class==="import")return[];if(e.class==="forwarded"){if(e.actor.kind==="person")return[];const{resolved:o}=await C(e),n=e.actor.node_scopes;return n===null?o:n.filter(s=>L(o,s))}if(e.class==="app")return e.scopes;if(e.class==="cron"){const o=await Z({grantee:e.app,sub:e.sub,grant_id:e.grant,scopes:e.scopes});if(o.allowed)return o.effectiveScopes;throw o.reason==="grant_missing"||o.reason==="grant_revoked"?new u(401,"grant_revoked",o.message):new u(403,"scope_missing",o.message)}const t=e.grant?await F(e.grant):await S(e.app,e.sub);if(t&&t.grantee===e.app&&t.sub===e.sub)return t.resolved;throw new u(401,"grant_revoked","the node grant is not active")}d($,"resolvedScopes");function N(e){return"class"in e?k(e):e.grantee}d(N,"runCallerApp");function T(e){return"class"in e?e:{class:"app",app:e.grantee,scopes:e.resolved,sub:e.sub}}d(T,"asRequestPrincipal");async function f(e,t,o){const n=T(e);if(y(n))return!0;if(n.class==="anonymous"||n.class==="machine"||n.class==="import")return!1;const s=await x();return await Y(s,await R(s,{principal:n}),t,o)}d(f,"mayOnNode");async function Te(e){if(y(e))return null;if(e.class==="anonymous"||e.class==="machine"||e.class==="import")return new Set;const t=await x(),o=await M(t,await R(t,{principal:e}),"read"),n=await t.prepareUncached(`SELECT o.id FROM objects o WHERE o.type = 'node' AND o.deleted_at IS NULL AND ${o.sql}`).all(...o.params);return new Set(n.map(s=>s.id))}d(Te,"readableNodeIds");async function W(e,t,o){const n=T(e);return y(n)?!0:n.class==="anonymous"||n.class==="machine"||n.class==="import"?!1:k(n)===t?!0:_(await $(n),o)&&(!O(n)||_(n.scopes,o))}d(W,"mayReadApp");async function We(e,t,o){return o===void 0?await W(e,t,`crtr:conversations:read:${t}`):await f(e,"read",o)}d(We,"canReadRun");async function ze(e,t,o=!1,n=!1){const s=await w(t);if(!s||s.parent!==null||s.run_id!==t||!s.grantee||s.deleting_at&&!n)throw new b(404,"not_found","run not found");if(!await f(e,"read",t))throw new b(404,"not_found","run not found");if(o&&N(e)!==null&&N(e)!==s.grantee)throw new b(403,"other_app_run","cannot change another app's run");return await J(t,s.grantee,n)}d(ze,"requireRun");async function ee(e,t,o){return o===void 0?await W(e,t,`crtr:objects:read:${t}`):await f(e,"read",o)}d(ee,"canReadNode");const z=new Set(["messages.post.58","messages.post.59"]);async function te(e,t,o){if(!e.classes.includes(t.class)&&!(t.class!=="anonymous"&&t.class!=="machine"&&t.class!=="import"&&t.class!=="forwarded"&&e.classes.includes("anonymous")))throw new u(403,"permission_error","this operation is not available to the caller");if(t.class==="forwarded"&&t.actor.kind==="app"&&await C(t),t.class==="anonymous"||t.class==="machine"||t.class==="import"||y(t))return;if(t.class==="person")return await oe(e,t,o);const n=await $(t);if(e.scope&&(!_(n,e.scope)||O(t)&&!_(t.scopes,e.scope)))throw new u(403,"scope_missing",`missing scope ${e.scope}`);if(t.class==="app")return;const s=k(t);t.class==="broker"&&await ae(e,t,o,n);const r=o.id;if((e.resource==="node-read"||e.resource==="node-write")&&r!==void 0){const a=await w(r);if(!a)return;if(e.resource==="node-write"){const i=z.has(e.name);if(a.grantee!==s&&!i)throw new u(403,"other_app_run","cannot change another app's node");if(!await f(t,i&&a.grantee!==s?"message":"read",r))throw a.grantee!==s?new u(403,"other_app_run","cannot change another app's node"):new u(404,"not_found","node not found")}if(e.resource==="node-read"&&!await f(t,"read",r))throw new u(404,"not_found","node not found")}}d(te,"authorizeOperation");async function oe(e,t,o){if(e.resource==="daemon-bash")throw new u(403,"permission_error","a shell on the daemon host is not available to a person bearer");if(e.resource==="daemon-control")throw new u(403,"permission_error","restarting, migrating, admitting or draining the daemon is not available to a person bearer");if(e.resource==="human")return await ne(e,t,o);if(o.request_id!==void 0&&e.path.startsWith("/v1/structured-requests/"))return await re(e,t,o.request_id);const n=o.id;if(!(e.resource!=="node-read"&&e.resource!=="node-write"||n===void 0||!await w(n))&&!await f(t,z.has(e.name)?"message":"read",n))throw new u(404,"not_found","node not found")}d(oe,"authorizePersonNode");async function ne(e,t,o){const n=o.ticket_id!==void 0?new u(404,"ticket_not_found","no matching ticket"):new u(404,"not_found","no request with that id"),s=o.request_id?await A(o.request_id):o.ticket_id?await p(o.ticket_id):null,r=o.comment_id?await v(o.comment_id):null,a=o.review_id?await g(o.review_id).catch(()=>null):r?await g(r.review_id).catch(()=>null):null;if((o.request_id??o.ticket_id??o.review_id??o.comment_id)===void 0)return;const c=s?.requester_node_id??a?.origin_node_id;if(!c||!await w(c)||!await f(t,e.method==="GET"?"read":"edit",c))throw n}d(ne,"authorizePersonHuman");async function re(e,t,o){const n=await K(o);if(n!==null&&(n.node_id===null||!await w(n.node_id)||!await f(t,e.method==="GET"?"read":"edit",n.node_id)))throw new u(404,"structured_request_not_found",`unknown structured request: ${o}`)}d(re,"authorizePersonStructuredRequest");async function se(e,t,o){const n=o&&typeof o=="object"&&!Array.isArray(o)?o:{},s=e.path;if(s==="/v1/human/requests"||s==="/v1/human/reviews"){const r=n.source,a=s.endsWith("/reviews")?n.origin_node_id:n.requester_node_id;if(typeof a!="string")throw new u(403,"permission_error","a request must name a node the person acts on");const i=s.endsWith("/requests")&&r&&typeof r=="object"&&"nodeId"in r?r.nodeId:void 0;for(const c of[a,i])if(c!==void 0&&(typeof c!="string"||!await w(c)||!await f(t,"edit",c)))throw new u(404,"not_found","node not found")}if(s==="/v1/structured-requests"){const r=n.target,a=r&&typeof r=="object"&&"kind"in r&&r.kind==="node"&&"node_id"in r?r.node_id:void 0;if(typeof a=="string"&&await w(a)&&!await f(t,"message",a))throw new u(404,"node_not_found",`unknown node: ${a}`)}}d(se,"authorizePersonBody");async function ae(e,t,o,n){if(e.resource==="daemon-bash"&&(!_(n,"crtr:files:rw:user:.")||!_(t.scopes,"crtr:files:rw:user:.")))throw new u(403,"scope_missing","missing scope crtr:files:rw:user:.");if(e.resource==="human"){const r=e.path,a=e.method==="GET";if(!a&&(r.endsWith("/respond")||r.endsWith("/submit")||r.endsWith("/dismiss")||r.endsWith("/progress")||r.endsWith("/comment-ranges")||r.includes("/feedback-comments")))throw new u(403,"permission_error","only the person may answer or edit the human inbox");const i=o.request_id?await A(o.request_id):o.ticket_id?await p(o.ticket_id):null,c=o.comment_id?await v(o.comment_id):null,h=o.review_id?await g(o.review_id):c?await g(c.review_id):null,m=i?.requester_node_id??h?.origin_node_id;if(m!=null){const l=await w(m);if(l&&(a?!await ee(t,l.grantee??"app:terminal",m):l.grantee!==t.app))throw new u(a?404:403,a?"not_found":"other_app_run","human request belongs to another app");if(l&&!a&&!await f(t,"read",m))throw new u(404,"not_found","human request not found")}else if(i||h)throw new u(403,"permission_error","only the person may access a request without a node")}const s=o.id;if(e.resource==="node-own"&&s!==void 0&&s!==t.node)throw new u(403,"other_app_run","the operation requires the calling node")}d(ae,"authorizeBrokerOnly");function de(e,t){if(!_(e,t))throw new I("the bearer token",t)}d(de,"assertCeiling");function ue(){return new u(403,"owner_only","this operation is owner-only; a scoped token does not reach it")}d(ue,"ownerOnlyError");const ie="Crtr-Caused-By",ce=/^(?:message|request|cron_run):[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;function fe(e){const t=e[ie.toLowerCase()];return typeof t=="string"&&ce.test(t)?t:void 0}d(fe,"causedByOf");function Ue(e){return e.map(t=>({...t,ownerOnly:!0}))}d(Ue,"ownerOnly");class Ge{static{d(this,"Router")}routes=[];register(t){const o=V(t.method,t.pattern);if(!o)throw new Error(`route has no operation row: ${t.method} ${t.pattern}`);if(this.routes.some(n=>n.method===t.method.toUpperCase()&&n.operation.path===t.pattern))throw new Error(`duplicate route: ${t.method} ${t.pattern}`);return this.routes.push({operation:o,method:t.method.toUpperCase(),segments:U(t.pattern),handler:t.handler,maxBodyBytes:t.maxBodyBytes??X}),this}registerAll(t){for(const o of t)this.register(o);return this}match(t,o){const n=t.toUpperCase(),s=U(o);for(const r of this.routes){if(r.method!==n)continue;const a=he(r.segments,s);if(a!==null)return{handler:r.handler,params:a,operation:r.operation,maxBodyBytes:r.maxBodyBytes}}return null}async handle(t,o,n,s={class:"person"}){try{const r=new URL(t.url??"/","http://127.0.0.1"),a=(t.method??"GET").toUpperCase(),i=this.match(a,r.pathname);if(i===null){j(o,404,"not_found",`no route for ${a} ${r.pathname}`);return}if(await te(i.operation,s,i.params),n!==null){if(i.operation.classes.length===1&&i.operation.classes[0]==="person")throw ue();i.operation.scope&&de(n,i.operation.scope)}let c;try{c=await we(t,i.maxBodyBytes)}catch(E){const B=E instanceof G?"payload_too_large":"invalid_request";j(o,B==="payload_too_large"?413:400,B,E.message);return}s.class==="person"&&s.sub!==void 0&&await se(i.operation,s,c),s.class==="broker"&&i.operation.resource==="human"&&await Q(i.operation,s,i.params,r.searchParams,c);const h=fe(t.headers),m={...h===void 0?{}:{causedBy:h},method:a,path:r.pathname,params:i.params,query:r.searchParams,body:c,ceiling:n,principal:s,...s.class==="anonymous"&&typeof t.headers.authorization=="string"?{publishToken:t.headers.authorization.slice(7)}:{},req:t,res:o},l=await i.handler(m);o.writableEnded||me(o,l)}catch(r){if(r instanceof Error&&r.code==="person_unresolved"){q(o,503,{error:{code:"runtime_starting",message:"grant sync is not ready",type:"server_error",origin:"runtime",retryable:!0}});return}const{status:a,body:i}=H(r);a>=500&&D({event:"api.request.failed",level:"error",error:r instanceof Error?r:new Error(String(r)),...s.class==="broker"?{node_id:s.node}:{},fields:{method:(t.method??"GET").toUpperCase(),path:t.url??""}}),q(o,a,i)}}}function U(e){return e.split("/").filter(t=>t.length>0)}d(U,"splitPath");function he(e,t){if(e.length!==t.length)return null;const o={};for(let n=0;n<e.length;n++){const s=e[n],r=t[n];if(s.startsWith(":"))o[s.slice(1)]=decodeURIComponent(r);else if(s!==r)return null}return o}d(he,"matchSegments");class G extends Error{static{d(this,"BodyTooLargeError")}}async function we(e,t){const o=[];let n=0;for await(const r of e){const a=r;if(n+=a.length,n>t)throw new G(`request body exceeds ${t} bytes`);o.push(a)}if(n===0)return;const s=Buffer.concat(o).toString("utf8").trim();if(s!=="")try{return JSON.parse(s)}catch{throw new Error("request body is not valid JSON")}}d(we,"readJsonBody");function me(e,t){if(e.headersSent)return;const o={...t.headers??{}};if(t.rawBody!==void 0){e.writeHead(t.status,o),e.end(t.rawBody);return}if(t.status===204||t.body===void 0){e.writeHead(t.status,o),e.end();return}o["content-type"]===void 0&&(o["content-type"]="application/json; charset=utf-8"),e.writeHead(t.status,o),e.end(JSON.stringify(t.body))}d(me,"writeResult");function q(e,t,o){if(e.headersSent){e.end();return}e.writeHead(t,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}d(q,"writeJson");function j(e,t,o,n){q(e,t,{error:{code:o,message:n,origin:"runtime"}})}d(j,"writeError");export{ie as CAUSED_BY_HEADER,Ge as Router,y as actsAsPerson,k as appOf,de as assertCeiling,ee as canReadNode,We as canReadRun,fe as causedByOf,f as mayOnNode,Ue as ownerOnly,ue as ownerOnlyError,Te as readableNodeIds,ze as requireRun,$ as resolvedScopes,N as runCallerApp};
1
+ var j=Object.defineProperty;var d=(e,t)=>j(e,"name",{value:t,configurable:!0});import{toErrorBody as H}from"./map.js";import{emitEvent as D}from"../../core/events/emit.js";import{scopeAllowed as _,ScopeDeniedError as I}from"../../core/scopes.js";import{coveredByAny as L}from"../../shared/env.js";import{ApiError as u}from"../../api/errors.js";import{activeGrant as F,activeGrantsOfPerson as J,grantFor as S}from"../../core/grants/mirror.js";import{getNode as w}from"../../core/canvas/canvas.js";import{ownedRun as M,RunError as b}from"../../core/runs/operations.js";import{openDb as x}from"../../core/canvas/db.js";import{check as Y,permits as Z}from"../../core/permissions/evaluator.js";import{readerFor as R}from"./reader.js";import{checkCronFire as V}from"../cron/authority.js";import{operationFor as X}from"./operations.js";import{CONTROL_BODY_MAX_BYTES as K}from"../../core/llm/body-limits.js";import{getRequest as A,getRequestByInboxTicket as O}from"../../core/human/requests.js";import{requireVisibleReview as g}from"../../core/review/store.js";import{getComment as v}from"../../core/review/comments.js";import{getStructuredRequest as Q}from"../../core/canvas/structured-requests.js";async function ee(e,t,o,n,s){const r=e.path,a=s&&typeof s=="object"&&!Array.isArray(s)?s:{};if(r==="/v1/human/requests"||r==="/v1/human/reviews"){const i=r.endsWith("/reviews")?a.origin_node_id:a.requester_node_id,c=r==="/v1/human/requests"&&i==null;if(i!==t.node&&!c)throw new u(403,"other_app_run","a broker may create a human request only for its own node");const h=a.source;if(h&&typeof h=="object"&&"nodeId"in h&&h.nodeId!==t.node)throw new u(403,"other_app_run","request source must name the calling node")}if(r==="/v1/human/reviews/self/comments"&&e.method==="GET"&&n.get("actor_node_id")!==t.node)throw new u(403,"other_app_run","self review requires the calling node");if(r.includes("/comments")&&e.method!=="GET"&&a.actor_node_id!==t.node)throw new u(403,"other_app_run","comment author must be the calling node");if(o.comment_id&&!await v(o.comment_id))throw new u(404,"not_found","comment not found")}d(ee,"authorizeHumanBody");function C(e){return e.class==="broker"||e.class==="cron"}d(C,"boundedByStoredScopes");function k(e){return e.class==="person"||e.class==="anonymous"||e.class==="machine"||e.class==="import"?null:e.class==="forwarded"?e.actor.kind==="app"?e.actor.app:null:e.app}d(k,"appOf");function y(e){return e.class==="person"&&e.sub===void 0||e.class==="forwarded"&&e.actor.kind==="person"}d(y,"actsAsPerson");async function $(e){const t=await S(e.actor.app,e.sub);if(!t)throw new u(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);return t}d($,"forwardedGrant");async function p(e){if(e.class==="person"&&e.sub!==void 0)return(await J(e.sub)).flatMap(o=>o.resolved);if(e.class==="person"||e.class==="anonymous"||e.class==="machine"||e.class==="import")return[];if(e.class==="forwarded"){if(e.actor.kind==="person")return[];const{resolved:o}=await $(e),n=e.actor.node_scopes;return n===null?o:n.filter(s=>L(o,s))}if(e.class==="app")return e.scopes;if(e.class==="cron"){const o=await V({grantee:e.app,sub:e.sub,grant_id:e.grant,scopes:e.scopes});if(o.allowed)return o.effectiveScopes;throw o.reason==="grant_missing"||o.reason==="grant_revoked"?new u(401,"grant_revoked",o.message):new u(403,"scope_missing",o.message)}const t=e.grant?await F(e.grant):await S(e.app,e.sub);if(t&&t.grantee===e.app&&t.sub===e.sub)return t.resolved;throw new u(401,"grant_revoked","the node grant is not active")}d(p,"resolvedScopes");function N(e){return"class"in e?k(e):e.grantee}d(N,"runCallerApp");function T(e){return"class"in e?e:{class:"app",app:e.grantee,scopes:e.resolved,sub:e.sub}}d(T,"asRequestPrincipal");async function f(e,t,o){const n=T(e);if(y(n))return!0;if(n.class==="anonymous"||n.class==="machine"||n.class==="import")return!1;const s=await x();return await Y(s,await R(s,{principal:n}),t,o)}d(f,"mayOnNode");async function We(e){if(y(e))return null;if(e.class==="anonymous"||e.class==="machine"||e.class==="import")return new Set;const t=await x(),o=await Z(t,await R(t,{principal:e}),"read"),n=await t.prepareUncached(`SELECT o.id FROM objects o WHERE o.type = 'node' AND o.deleted_at IS NULL AND ${o.sql}`).all(...o.params);return new Set(n.map(s=>s.id))}d(We,"readableNodeIds");async function W(e,t,o){const n=T(e);return y(n)?!0:n.class==="anonymous"||n.class==="machine"||n.class==="import"?!1:k(n)===t?!0:_(await p(n),o)&&(!C(n)||_(n.scopes,o))}d(W,"mayReadApp");async function ze(e,t,o){return o===void 0?await W(e,t,`crtr:conversations:read:${t}`):await f(e,"read",o)}d(ze,"canReadRun");async function Ge(e,t,o=!1,n=!1){const s=await w(t);if(!s||s.parent!==null||s.run_id!==t||!s.grantee||s.deleting_at&&!n)throw new b(404,"not_found","run not found");if(!await f(e,"read",t))throw new b(404,"not_found","run not found");if(o&&N(e)!==null&&N(e)!==s.grantee)throw new b(403,"other_app_run","cannot change another app's run");return await M(t,s.grantee,n)}d(Ge,"requireRun");async function te(e,t,o){return o===void 0?await W(e,t,`crtr:objects:read:${t}`):await f(e,"read",o)}d(te,"canReadNode");const z=new Set(["messages.post.58","messages.post.59"]);async function oe(e,t,o){if(!e.classes.includes(t.class)&&!(t.class!=="anonymous"&&t.class!=="machine"&&t.class!=="import"&&t.class!=="forwarded"&&e.classes.includes("anonymous")))throw new u(403,"permission_error","this operation is not available to the caller");if(t.class==="forwarded"&&t.actor.kind==="app"&&await $(t),t.class==="anonymous"||t.class==="machine"||t.class==="import"||y(t))return;if(t.class==="person")return await ne(e,t,o);const n=await p(t);if(e.scope&&(!_(n,e.scope)||C(t)&&!_(t.scopes,e.scope)))throw new u(403,"scope_missing",`missing scope ${e.scope}`);if(t.class==="app")return;const s=k(t);t.class==="broker"&&await de(e,t,o,n);const r=o.id;if((e.resource==="node-read"||e.resource==="node-write")&&r!==void 0){const a=await w(r);if(!a)return;if(e.resource==="node-write"){const i=z.has(e.name);if(a.grantee!==s&&!i)throw new u(403,"other_app_run","cannot change another app's node");if(!await f(t,i&&a.grantee!==s?"message":"read",r))throw a.grantee!==s?new u(403,"other_app_run","cannot change another app's node"):new u(404,"not_found","node not found")}if(e.resource==="node-read"&&!await f(t,"read",r))throw new u(404,"not_found","node not found")}}d(oe,"authorizeOperation");async function ne(e,t,o){if(e.resource==="daemon-bash")throw new u(403,"permission_error","a shell on the daemon host is not available to a person bearer");if(e.resource==="daemon-control")throw new u(403,"permission_error","restarting, migrating, admitting or draining the daemon is not available to a person bearer");if(e.resource==="human")return await re(e,t,o);if(o.request_id!==void 0&&e.path.startsWith("/v1/structured-requests/"))return await se(e,t,o.request_id);const n=o.id;if(!(e.resource!=="node-read"&&e.resource!=="node-write"||n===void 0||!await w(n))&&!await f(t,z.has(e.name)?"message":"read",n))throw new u(404,"not_found","node not found")}d(ne,"authorizePersonNode");async function re(e,t,o){const n=o.ticket_id!==void 0?new u(404,"ticket_not_found","no matching ticket"):new u(404,"not_found","no request with that id"),s=o.request_id?await A(o.request_id):o.ticket_id?await O(o.ticket_id):null,r=o.comment_id?await v(o.comment_id):null,a=o.review_id?await g(o.review_id).catch(()=>null):r?await g(r.review_id).catch(()=>null):null;if((o.request_id??o.ticket_id??o.review_id??o.comment_id)===void 0)return;const c=s?.requester_node_id??a?.origin_node_id;if(!c||!await w(c)||!await f(t,e.method==="GET"?"read":"edit",c))throw n}d(re,"authorizePersonHuman");async function se(e,t,o){const n=await Q(o);if(n!==null&&(n.node_id===null||!await w(n.node_id)||!await f(t,e.method==="GET"?"read":"edit",n.node_id)))throw new u(404,"structured_request_not_found",`unknown structured request: ${o}`)}d(se,"authorizePersonStructuredRequest");async function ae(e,t,o){const n=o&&typeof o=="object"&&!Array.isArray(o)?o:{},s=e.path;if(s==="/v1/human/requests"||s==="/v1/human/reviews"){const r=n.source,a=s.endsWith("/reviews")?n.origin_node_id:n.requester_node_id;if(typeof a!="string")throw new u(403,"permission_error","a request must name a node the person acts on");const i=s.endsWith("/requests")&&r&&typeof r=="object"&&"nodeId"in r?r.nodeId:void 0;for(const c of[a,i])if(c!==void 0&&(typeof c!="string"||!await w(c)||!await f(t,"edit",c)))throw new u(404,"not_found","node not found")}if(s==="/v1/structured-requests"){const r=n.target,a=r&&typeof r=="object"&&"kind"in r&&r.kind==="node"&&"node_id"in r?r.node_id:void 0;if(typeof a=="string"&&await w(a)&&!await f(t,"message",a))throw new u(404,"node_not_found",`unknown node: ${a}`)}}d(ae,"authorizePersonBody");async function de(e,t,o,n){if(e.resource==="daemon-bash"&&(!_(n,"crtr:files:rw:user:.")||!_(t.scopes,"crtr:files:rw:user:.")))throw new u(403,"scope_missing","missing scope crtr:files:rw:user:.");if(e.resource==="human"){const r=e.path,a=e.method==="GET";if(!a&&(r.endsWith("/respond")||r.endsWith("/submit")||r.endsWith("/dismiss")||r.endsWith("/progress")||r.endsWith("/comment-ranges")||r.includes("/feedback-comments")))throw new u(403,"permission_error","only the person may answer or edit the human inbox");const i=o.request_id?await A(o.request_id):o.ticket_id?await O(o.ticket_id):null,c=o.comment_id?await v(o.comment_id):null,h=o.review_id?await g(o.review_id):c?await g(c.review_id):null,m=i?.requester_node_id??h?.origin_node_id;if(m!=null){const l=await w(m);if(l&&(a?!await te(t,l.grantee??"app:terminal",m):l.grantee!==t.app))throw new u(a?404:403,a?"not_found":"other_app_run","human request belongs to another app");if(l&&!a&&!await f(t,"read",m))throw new u(404,"not_found","human request not found")}else if(i||h)throw new u(403,"permission_error","only the person may access a request without a node")}const s=o.id;if(e.resource==="node-own"&&s!==void 0&&s!==t.node)throw new u(403,"other_app_run","the operation requires the calling node")}d(de,"authorizeBrokerOnly");function ue(e,t){if(!_(e,t))throw new I("the bearer token",t)}d(ue,"assertCeiling");function ie(){return new u(403,"owner_only","this operation is owner-only; a scoped token does not reach it")}d(ie,"ownerOnlyError");const ce="Crtr-Caused-By",fe=/^(?:message|request|cron_run):[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;function he(e){const t=e[ce.toLowerCase()];return typeof t=="string"&&fe.test(t)?t:void 0}d(he,"causedByOf");function Ue(e){return e.map(t=>({...t,ownerOnly:!0}))}d(Ue,"ownerOnly");class Pe{static{d(this,"Router")}routes=[];register(t){const o=X(t.method,t.pattern);if(!o)throw new Error(`route has no operation row: ${t.method} ${t.pattern}`);if(this.routes.some(n=>n.method===t.method.toUpperCase()&&n.operation.path===t.pattern))throw new Error(`duplicate route: ${t.method} ${t.pattern}`);return this.routes.push({operation:o,method:t.method.toUpperCase(),segments:G(t.pattern),handler:t.handler,maxBodyBytes:t.maxBodyBytes??K}),this}registerAll(t){for(const o of t)this.register(o);return this}match(t,o){const n=t.toUpperCase(),s=G(o);for(const r of this.routes){if(r.method!==n)continue;const a=we(r.segments,s);if(a!==null)return{handler:r.handler,params:a,operation:r.operation,maxBodyBytes:r.maxBodyBytes}}return null}async handle(t,o,n,s={class:"person"}){try{const r=new URL(t.url??"/","http://127.0.0.1"),a=(t.method??"GET").toUpperCase(),i=this.match(a,r.pathname);if(i===null){P(o,404,"not_found",`no route for ${a} ${r.pathname}`);return}if(await oe(i.operation,s,i.params),n!==null){if(i.operation.classes.length===1&&i.operation.classes[0]==="person")throw ie();i.operation.scope&&ue(n,i.operation.scope)}let c;try{c=await me(t,i.maxBodyBytes)}catch(E){const B=E instanceof U?"payload_too_large":"invalid_request";P(o,B==="payload_too_large"?413:400,B,E.message);return}s.class==="person"&&s.sub!==void 0&&await ae(i.operation,s,c),s.class==="broker"&&i.operation.resource==="human"&&await ee(i.operation,s,i.params,r.searchParams,c);const h=he(t.headers),m={...h===void 0?{}:{causedBy:h},method:a,path:r.pathname,params:i.params,query:r.searchParams,body:c,ceiling:n,principal:s,...s.class==="anonymous"&&typeof t.headers.authorization=="string"?{publishToken:t.headers.authorization.slice(7)}:{},req:t,res:o},l=await i.handler(m);o.writableEnded||_e(o,l)}catch(r){if(r instanceof Error&&r.code==="person_unresolved"){q(o,503,{error:{code:"runtime_starting",message:"grant sync is not ready",type:"server_error",origin:"runtime",retryable:!0}});return}const{status:a,body:i}=H(r);a>=500&&D({event:"api.request.failed",level:"error",error:r instanceof Error?r:new Error(String(r)),...s.class==="broker"?{node_id:s.node}:{},fields:{method:(t.method??"GET").toUpperCase(),path:t.url??""}}),q(o,a,i)}}}function G(e){return e.split("/").filter(t=>t.length>0)}d(G,"splitPath");function we(e,t){if(e.length!==t.length)return null;const o={};for(let n=0;n<e.length;n++){const s=e[n],r=t[n];if(s.startsWith(":"))o[s.slice(1)]=decodeURIComponent(r);else if(s!==r)return null}return o}d(we,"matchSegments");class U extends Error{static{d(this,"BodyTooLargeError")}}async function me(e,t){const o=[];let n=0;for await(const r of e){const a=r;if(n+=a.length,n>t)throw new U(`request body exceeds ${t} bytes`);o.push(a)}if(n===0)return;const s=Buffer.concat(o).toString("utf8").trim();if(s!=="")try{return JSON.parse(s)}catch{throw new Error("request body is not valid JSON")}}d(me,"readJsonBody");function _e(e,t){if(e.headersSent)return;const o={...t.headers??{}};if(t.rawBody!==void 0){e.writeHead(t.status,o),e.end(t.rawBody);return}if(t.status===204||t.body===void 0){e.writeHead(t.status,o),e.end();return}o["content-type"]===void 0&&(o["content-type"]="application/json; charset=utf-8"),e.writeHead(t.status,o),e.end(JSON.stringify(t.body))}d(_e,"writeResult");function q(e,t,o){if(e.headersSent){e.end();return}e.writeHead(t,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}d(q,"writeJson");function P(e,t,o,n){q(e,t,{error:{code:o,message:n,origin:"runtime"}})}d(P,"writeError");export{ce as CAUSED_BY_HEADER,Pe as Router,y as actsAsPerson,k as appOf,ue as assertCeiling,te as canReadNode,ze as canReadRun,he as causedByOf,f as mayOnNode,Ue as ownerOnly,ie as ownerOnlyError,We as readableNodeIds,Ge as requireRun,p as resolvedScopes,N as runCallerApp};
@@ -1 +1 @@
1
- var i=Object.defineProperty;var r=(e,t)=>i(e,"name",{value:t,configurable:!0});import{migrateLegacyInboxIdentities as a}from"../core/feed/inbox-migrate.js";import{backfillComputedRev as s}from"../core/grants/mirror.js";import{copyBodyFilesToTable as n}from"../core/graph/bodies-boot-copy.js";import{isCloudRole as o}from"../core/canvas/person-state.js";import{emitEvent as c}from"../core/events/emit.js";const h=[{id:"legacy-inbox-identities",description:"normalize pre-cutover inbox entry/operation ids (UUID-spelled or missing) and cursors (ISO-timestamp boundaries) into canonical 32-lowercase-hex identities the strict inbox reader requires.",apply:r(()=>{const e=a();return{changed:e.nodes>0||e.failures>0,summary:{...e}}},"apply")},{id:"grant-computed-rev",description:"mark grant rows a pre-C-D2 build wrote (scopes computed, computed_rev NULL) as computed for their revision, so the terminal grant reads as active before the first directory sync after an upgrade or rollback (NL-550). A cloud role skips it: the directory owns its shared rows.",apply:r(async()=>{if(o())return{changed:!1,summary:{}};try{const e=await s();return{changed:e>0,summary:{rows:e}}}catch(e){return c({level:"warn",event:"daemon.boot_migration.failed",error:e,fields:{migration:"grant-computed-rev"}}),{changed:!1,summary:{}}}},"apply")},{id:"bodies-to-table",description:"copy document text from the old <home>/bodies files into the bodies table (D106). Verifies each file by sha256, skips and reports mismatches, leaves the files for rollback. A cloud role copies nothing.",apply:r(async()=>{if(o())return{changed:!1,summary:{}};const e=await n();return{changed:e.copied>0||e.mismatched.length>0,summary:{...e}}},"apply")}];export{h as BOOT_MIGRATIONS};
1
+ var i=Object.defineProperty;var r=(e,t)=>i(e,"name",{value:t,configurable:!0});import{migrateLegacyInboxIdentities as a}from"../core/feed/inbox-migrate.js";import{backfillComputedRev as s}from"../core/grants/mirror.js";import{copyBodyFilesToTable as n,repairBodyHashes as d}from"../core/graph/bodies-boot-copy.js";import{isCloudRole as o}from"../core/canvas/person-state.js";import{emitEvent as m}from"../core/events/emit.js";const f=[{id:"legacy-inbox-identities",description:"normalize pre-cutover inbox entry/operation ids (UUID-spelled or missing) and cursors (ISO-timestamp boundaries) into canonical 32-lowercase-hex identities the strict inbox reader requires.",apply:r(()=>{const e=a();return{changed:e.nodes>0||e.failures>0,summary:{...e}}},"apply")},{id:"grant-computed-rev",description:"mark grant rows a pre-C-D2 build wrote (scopes computed, computed_rev NULL) as computed for their revision, so the terminal grant reads as active before the first directory sync after an upgrade or rollback (NL-550). A cloud role skips it: the directory owns its shared rows.",apply:r(async()=>{if(o())return{changed:!1,summary:{}};try{const e=await s();return{changed:e>0,summary:{rows:e}}}catch(e){return m({level:"warn",event:"daemon.boot_migration.failed",error:e,fields:{migration:"grant-computed-rev"}}),{changed:!1,summary:{}}}},"apply")},{id:"bodies-to-table",description:"copy document text from the old <home>/bodies files into the bodies table (D106). Verifies each file by sha256, skips and reports mismatches, leaves the files for rollback. A cloud role copies nothing.",apply:r(async()=>{if(o())return{changed:!1,summary:{}};const e=await n();return{changed:e.copied>0||e.mismatched.length>0,summary:{...e}}},"apply")},{id:"body-hash-repair",description:"rekey bodies rows PR #822 stored under the hash of the text before NUL became U+FFFD (D106): every revision naming such a hash names the hash of the stored text, and the invalid old-key row is removed. Runs in every role and after bodies-to-table, before readers serve. A mismatch with no intact original (a retained <home>/bodies file or a repo ref body) that becomes exactly the stored text stops the boot and is reported, never rehashed.",apply:r(async()=>{const e=await d();if(e.unresolved.length>0)throw new Error(`body-hash-repair: ${e.unresolved.length} bodies row(s) do not hash to their key and no intact original explains them: ${e.unresolved.join(", ")}`);return{changed:e.rekeyed>0||e.removed>0,summary:{...e}}},"apply")}];export{f as BOOT_MIGRATIONS};
@@ -3,3 +3,9 @@ export declare const machineCronMarkerDir: () => string;
3
3
  export declare function runMachineCron(args: MachineCronRunArgs): Promise<MachineCronRunResult>;
4
4
  /** Stop a run. A run that has not started is canceled for good: a marker holding a canceled result is left, so a `cron.run` that arrives later returns it without spawning. */
5
5
  export declare function cancelMachineCron(args: MachineCronCancelArgs): Promise<MachineCronCancelResult>;
6
+ /** Stop every process group a machine cron started on this home and left running: SIGTERM to the group, then SIGKILL after 5 s. A command can leave members
7
+ * behind after its leader (`bash -c` exits, a backgrounded `sleep 600 &` keeps running), with or without a recorded result, so every marker with a
8
+ * group is checked. The group is signalled only when `groupIsRuns` proves it is the run's; unknown ownership rejects sign-out.
9
+ * A group that survives SIGKILL, or a process table that cannot be read, rejects: the caller must not report stopped. A run with no recorded result then
10
+ * holds `exitCode: null`, "stopped: signed out of Northlight", which the control plane reads on the run's next `cron.run`. */
11
+ export declare function stopOrphanedMachineCrons(): Promise<void>;
@@ -1 +1 @@
1
- var x=Object.defineProperty;var i=(e,t)=>x(e,"name",{value:t,configurable:!0});import{closeSync as f,fsyncSync as h,mkdirSync as k,openSync as p,readFileSync as D,readdirSync as b,renameSync as v,rmSync as A,statSync as N,writeSync as w}from"node:fs";import{join as y}from"node:path";import{contextDir as P,crtrHome as H}from"../../core/canvas/paths.js";import{runCommandOnce as $}from"../../core/runtime/run-command.js";import{ADMITTED_HOST_PATH_ENV as T,buildOperationalEnvBase as U}from"../../core/runtime/spawn-env.js";import{readMachineConfig as F}from"../machine-link/config.js";import{CRON_OUTPUT_HEAD_BYTES as E,EMPTY_STDOUT_SHA256 as M,formatCronOutputHead as _}from"./capture.js";const j=168*36e5,l=new Map,a=i(()=>y(H(),"runtime","machine-cron"),"machineCronMarkerDir"),m=i(e=>y(a(),`${encodeURIComponent(e)}.json`),"markerPath");function C(e){const t=p(e,"r");try{h(t)}finally{f(t)}}i(C,"fsyncDirectory");function R(e,t){const n=p(e,"wx");try{w(n,t),h(n)}finally{f(n)}C(a())}i(R,"createMarkerFile");function J(e,t){const n=`${e}.${process.pid}.tmp`,o=p(n,"w");try{w(o,t),h(o)}finally{f(o)}v(n,e),C(a())}i(J,"replaceMarkerFile");function S(e){let t;try{t=D(m(e),"utf8")}catch(n){return n.code==="ENOENT"?{kind:"none"}:{kind:"unreadable"}}try{const n=JSON.parse(t);return n===null||typeof n!="object"||n.runId!==e?{kind:"unreadable"}:{kind:"marker",marker:n}}catch{return{kind:"unreadable"}}}i(S,"readMarker");function B(e){try{for(const t of b(a())){const n=y(a(),t);e-N(n).mtimeMs>j&&A(n,{force:!0})}}catch{}}i(B,"sweepMarkers");const c=i((e,t)=>({exitCode:null,stdoutHead:null,stderrHead:e.slice(0,E),durationMs:Date.now()-t,stdoutSha256:M}),"failed");function O(e,t,n){return e.kind==="unreadable"?c(`run ${t} has a marker on this machine that cannot be read, so whether it ran is unknown; it is not run again`,n):e.marker.result??c(`run ${t} was started on this machine and its outcome was not recorded (the daemon that ran it ended); it is not run again`,n)}i(O,"answerFromMarker");function X(e){const t=e.grantee==="app:terminal",n=U({targetCwd:e.cwd,targetProfileId:t?e.profileId:null,skipSpawnAllow:!t}),o={...n,...e.env,CRTR_RUN_ID:e.runId,CRTR_CRON_ID:e.cronId},u=F();return u!==void 0&&(o.CRTR_API_URL=u.apiUrl,o.CRTR_ENVIRONMENT_ID=u.environmentId),e.contextAnchor!==null&&(o.CRTR_CONTEXT_DIR=P(e.contextAnchor.node,e.contextAnchor.grantee)),o[T]=n[T],o}i(X,"machineRunEnv");function W(e){const t=l.get(e.runId);if(t!==void 0)return t.promise;const n=Date.now();k(a(),{recursive:!0});const o=S(e.runId);if(o.kind!=="none")return Promise.resolve(O(o,e.runId,n));if(Date.parse(e.leaseExpiresAt)<=n)return Promise.resolve({...c(`lease expired at ${e.leaseExpiresAt} before the run started; nothing ran`,n),notStarted:!0});B(n);let u;try{u=X(e)}catch(d){return Promise.resolve({...c(`run could not be armed on this machine: ${d.message}`,n),notStarted:!0})}const I={runId:e.runId,startedAt:new Date(n).toISOString(),result:null};try{R(m(e.runId),JSON.stringify(I))}catch(d){if(d.code==="EEXIST"){const r=S(e.runId);if(r.kind!=="none")return Promise.resolve(O(r,e.runId,n))}return Promise.resolve({...c(`run ${e.runId} could not be recorded before it started: ${d.message}; nothing ran`,n),notStarted:!0})}const s={promise:Promise.resolve(c("unreachable",n)),command:null};return s.promise=(async()=>{let d;try{s.command=$({command:e.command,cwd:e.cwd,env:u,timeoutMs:e.timeoutS*1e3,headBytes:E});const r=await s.command.completion;d=r.processError!==null?c(r.processError.message,n):{exitCode:r.timedOut?-1:r.exitCode,stdoutHead:r.stdout===""?null:_(r.stdout,r.stdoutTruncated),stderrHead:r.stderr===""?null:_(r.stderr,r.stderrTruncated),durationMs:r.durationMs,stdoutSha256:r.stdoutSha256}}catch(r){d=c(`run failed to spawn: ${r.message}`,n)}return J(m(e.runId),JSON.stringify({...I,result:d})),d})().finally(()=>{l.delete(e.runId)}),l.set(e.runId,s),s.promise}i(W,"runMachineCron");async function Z(e){const t=l.get(e.runId);if(t!==void 0)return t.command===null?{canceled:!1}:(t.command.terminate(),{canceled:!0});if(k(a(),{recursive:!0}),S(e.runId).kind!=="none")return{canceled:!1};const n={runId:e.runId,startedAt:new Date().toISOString(),result:{exitCode:null,stdoutHead:null,stderrHead:"canceled before it started; nothing ran",durationMs:0,stdoutSha256:M}};try{R(m(e.runId),JSON.stringify(n))}catch(o){if(o.code==="EEXIST")return{canceled:!1};throw o}return{canceled:!0}}i(Z,"cancelMachineCron");export{Z as cancelMachineCron,a as machineCronMarkerDir,W as runMachineCron};
1
+ var j=Object.defineProperty;var s=(e,t)=>j(e,"name",{value:t,configurable:!0});import{closeSync as S,fsyncSync as E,mkdirSync as k,openSync as b,readFileSync as J,readdirSync as M,renameSync as G,rmSync as F,statSync as K,writeSync as O}from"node:fs";import{join as T}from"node:path";import{contextDir as L,crtrHome as V}from"../../core/canvas/paths.js";import{capturePidIdentities as v,identitiesMatch as R,isPidAlive as B,killProcessGroup as N,pidsInProcessGroup as X}from"../../core/canvas/pid.js";import{runCommandOnce as Y}from"../../core/runtime/run-command.js";import{ADMITTED_HOST_PATH_ENV as C,buildOperationalEnvBase as W}from"../../core/runtime/spawn-env.js";import{readMachineConfig as q}from"../machine-link/config.js";import{CRON_OUTPUT_HEAD_BYTES as $,EMPTY_STDOUT_SHA256 as g,formatCronOutputHead as P}from"./capture.js";const z=168*36e5,Q=1e3,y=new Map,f=s(()=>T(V(),"runtime","machine-cron"),"machineCronMarkerDir"),m=s(e=>T(f(),`${encodeURIComponent(e)}.json`),"markerPath");function D(e){const t=b(e,"r");try{E(t)}finally{S(t)}}s(D,"fsyncDirectory");function x(e,t){const n=b(e,"wx");try{O(n,t),E(n)}finally{S(n)}D(f())}s(x,"createMarkerFile");function I(e,t){const n=`${e}.${process.pid}.tmp`,r=b(n,"w");try{O(r,t),E(r)}finally{S(r)}G(n,e),D(f())}s(I,"replaceMarkerFile");function w(e){let t;try{t=J(m(e),"utf8")}catch(n){return n.code==="ENOENT"?{kind:"none"}:{kind:"unreadable"}}try{const n=JSON.parse(t);return n===null||typeof n!="object"||n.runId!==e?{kind:"unreadable"}:{kind:"marker",marker:n}}catch{return{kind:"unreadable"}}}s(w,"readMarker");function Z(e){try{for(const t of M(f())){const n=T(f(),t);e-K(n).mtimeMs>z&&F(n,{force:!0})}}catch{}}s(Z,"sweepMarkers");const l=s((e,t)=>({exitCode:null,stdoutHead:null,stderrHead:e.slice(0,$),durationMs:Date.now()-t,stdoutSha256:g}),"failed");function A(e,t,n){return e.kind==="unreadable"?l(`run ${t} has a marker on this machine that cannot be read, so whether it ran is unknown; it is not run again`,n):e.marker.result??l(`run ${t} was started on this machine and its outcome was not recorded (the daemon that ran it ended); it is not run again`,n)}s(A,"answerFromMarker");function ee(e){const t=e.grantee==="app:terminal",n=W({targetCwd:e.cwd,targetProfileId:t?e.profileId:null,skipSpawnAllow:!t}),r={...n,...e.env,CRTR_RUN_ID:e.runId,CRTR_CRON_ID:e.cronId},d=q();return d!==void 0&&(r.CRTR_API_URL=d.apiUrl,r.CRTR_ENVIRONMENT_ID=d.environmentId),e.contextAnchor!==null&&(r.CRTR_CONTEXT_DIR=L(e.contextAnchor.node,e.contextAnchor.grantee)),r[C]=n[C],r}s(ee,"machineRunEnv");function fe(e){const t=y.get(e.runId);if(t!==void 0)return t.promise;const n=Date.now();k(f(),{recursive:!0});const r=w(e.runId);if(r.kind!=="none")return Promise.resolve(A(r,e.runId,n));if(Date.parse(e.leaseExpiresAt)<=n)return Promise.resolve({...l(`lease expired at ${e.leaseExpiresAt} before the run started; nothing ran`,n),notStarted:!0});Z(n);let d;try{d=ee(e)}catch(c){return Promise.resolve({...l(`run could not be armed on this machine: ${c.message}`,n),notStarted:!0})}const o={runId:e.runId,startedAt:new Date(n).toISOString(),result:null};try{x(m(e.runId),JSON.stringify(o))}catch(c){if(c.code==="EEXIST"){const a=w(e.runId);if(a.kind!=="none")return Promise.resolve(A(a,e.runId,n))}return Promise.resolve({...l(`run ${e.runId} could not be recorded before it started: ${c.message}; nothing ran`,n),notStarted:!0})}const i={promise:Promise.resolve(l("unreachable",n)),command:null};return i.promise=(async()=>{let c,a;try{i.command=Y({command:e.command,cwd:e.cwd,env:d,timeoutMs:e.timeoutS*1e3,headBytes:$});const p=i.command.pid;if(p!==null){o.pgid=p,I(m(e.runId),JSON.stringify(o));const _=v([p])?.get(p);if(_===void 0)throw new Error(`run ${e.runId}: the spawned command's identity could not be captured`);o.leaderIdentity=_,I(m(e.runId),JSON.stringify(o))}o.pgid!==void 0&&(a=setInterval(()=>{try{U(o,e.runId)}catch{}},Q)),a?.unref();const u=await i.command.completion;clearInterval(a),o.pgid!==void 0&&U(o,e.runId),c=u.processError!==null?l(u.processError.message,n):{exitCode:u.timedOut?-1:u.exitCode,stdoutHead:u.stdout===""?null:P(u.stdout,u.stdoutTruncated),stderrHead:u.stderr===""?null:P(u.stderr,u.stderrTruncated),durationMs:u.durationMs,stdoutSha256:u.stdoutSha256}}catch(p){o.pgid!==void 0&&o.leaderIdentity===void 0&&(i.command?.terminate(),await H(o.pgid)),c=l(`run failed: ${p.message}`,n)}return clearInterval(a),I(m(e.runId),JSON.stringify({...o,result:c})),c})().finally(()=>{y.delete(e.runId)}),y.set(e.runId,i),i.promise}s(fe,"runMachineCron");async function me(e){const t=y.get(e.runId);if(t!==void 0)return t.command===null?{canceled:!1}:(t.command.terminate(),{canceled:!0});if(k(f(),{recursive:!0}),w(e.runId).kind!=="none")return{canceled:!1};const n={runId:e.runId,startedAt:new Date().toISOString(),result:{exitCode:null,stdoutHead:null,stderrHead:"canceled before it started; nothing ran",durationMs:0,stdoutSha256:g}};try{x(m(e.runId),JSON.stringify(n))}catch(r){if(r.code==="EEXIST")return{canceled:!1};throw r}return{canceled:!0}}s(me,"cancelMachineCron");function h(e){const t=X(e);if(t===null)throw new Error("the process table could not be read");return t}s(h,"pidsInGroup");async function H(e){N(e,"SIGTERM");for(let t=0;h(e).length>0&&t<5e3;t+=100)await new Promise(n=>setTimeout(n,100));if(h(e).length>0){N(e,"SIGKILL");for(let t=0;h(e).length>0&&t<2e3;t+=100)await new Promise(n=>setTimeout(n,100));if(h(e).length>0)throw new Error(`process group ${e} survived SIGKILL`)}}s(H,"stopProcessGroup");function U(e,t){const n=e.pgid;if(B(n))return;const r=h(n).filter(i=>i!==n);if(r.length===0)return;const d=v(r);if(d===null||r.some(i=>!d.has(i)))throw new Error(`run ${t}: identities of process group ${n}'s surviving members could not be captured`);const o=r.map(i=>({pid:i,identity:d.get(i)}));e.survivors=o,I(m(t),JSON.stringify(e))}s(U,"recordSurvivors");function ne(e,t,n){const r=v(n);if(r===null)throw new Error(`run ${e.runId}: the process table could not be read, so its process group ${t} was not checked`);const d=r.get(t);if(d!==void 0){if(e.leaderIdentity===void 0)throw new Error(`run ${e.runId}: process group ${t} has no recorded leader identity; cannot safely stop it`);return R(d,e.leaderIdentity)}if(e.survivors?.some(o=>{const i=r.get(o.pid);return i!==void 0&&R(i,o.identity)}))return!0;throw new Error(`run ${e.runId}: process group ${t} still has members but no recorded member matches; cannot safely stop it`)}s(ne,"groupIsRuns");async function pe(){let e;try{e=M(f()).filter(r=>r.endsWith(".json"))}catch(r){if(r.code==="ENOENT")return;throw r}const t=e.map(async r=>{const d=w(decodeURIComponent(r.slice(0,-5)));if(d.kind==="unreadable")throw new Error(`machine cron marker ${r} cannot be read; cannot safely stop its process group`);if(d.kind==="none"||d.marker.pgid===void 0)return;const{marker:o}=d,i=o.pgid,c=h(i);if(c.length===0)return;if(o.leaderIdentity===void 0)throw new Error(`run ${o.runId}: process group ${i} has no recorded leader identity; cannot safely stop it`);if(!ne(o,i,c))return;const a=Date.parse(o.startedAt);await H(i),o.result===null&&I(m(o.runId),JSON.stringify({...o,result:l("stopped: signed out of Northlight",Number.isNaN(a)?Date.now():a)}))}),n=(await Promise.allSettled(t)).filter(r=>r.status==="rejected");if(n.length>0)throw new Error(n.map(r=>r.reason.message).join("; "))}s(pe,"stopOrphanedMachineCrons");export{me as cancelMachineCron,f as machineCronMarkerDir,fe as runMachineCron,pe as stopOrphanedMachineCrons};
@@ -1,6 +1,6 @@
1
1
  import { type DetachedWorkLifecycle } from './reconcilers/broker-supervision.js';
2
2
  import { DaemonFleet } from './fleet.js';
3
- export declare function brokerCommandDeclaresCanvasHome(command: string, home: string): boolean;
3
+ export { brokerCommandDeclaresCanvasHome } from '../core/runtime/broker-ownership.js';
4
4
  /** The `--epoch <id>` tag host.ts stamps into every broker's argv, or null
5
5
  * for a legacy/untagged broker (which the census treats as prior-epoch). */
6
6
  export declare function brokerCommandEpoch(command: string): string | null;