@north-light/cli 0.3.422 → 0.3.423

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.
@@ -1,24 +1,24 @@
1
- var B=Object.defineProperty;var i=(e,n)=>B(e,"name",{value:n,configurable:!0});import{randomUUID as L}from"node:crypto";import{createServer as $}from"node:http";import{Router as j}from"./router.js";import{OPERATIONS as V}from"./operations.js";import{errorCodes as x}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as F,RUNTIME_VERSION_HEADER as G,minimumRuntimeRefusal as Y}from"../../api/runtime-version.js";import{crtrVersion as J}from"../../shared/crtr-version.js";import{AppPrincipalResolver as z}from"./principal.js";import{handleRunOperation as Q,integer as W}from"./handlers/runs.js";import{parseLlmRequest as K,runLlm as Z}from"../../core/llm/run.js";import{parseUsageMonth as ee,usageFor as te}from"../../core/usage/ledger.js";import{capFields as ne}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as re,MODEL_CONTEXT_BODY_MAX_BYTES as ae}from"../../core/llm/body-limits.js";import{RunError as d,runIsRunning as oe}from"../../core/runs/operations.js";import{requireRun as C}from"./router.js";import{deferHead as se}from"./deferred-head.js";import{readRunEvents as U,registerGrantStream as ie,subscribeRunEvents as ue}from"../../core/runs/events.js";import{activeGrant as ce}from"../../core/grants/mirror.js";import{releaseGrantWaiters as de}from"../../core/grants/watch.js";import{emitEvent as le}from"../../core/events/emit.js";import{runNotices as fe}from"../run-notices.js";import{handleVendorSignIn as me,isVendorSignInPath as pe}from"./vendor-signin.js";import{listAppProfiles as he}from"./handlers/app-profiles.js";import{handleTransferDownload as _e,handleTransferOperation as ye,TRANSFER_OPERATIONS as we}from"./handlers/transfer.js";import{handleQuestionOperation as ve,QUESTION_OPERATIONS as ge}from"./handlers/questions.js";import{vendorStatus as Oe}from"./handlers/vendor-status.js";import{handleMemoryOperation as Re,MEMORY_OPERATIONS as Se}from"./handlers/memory.js";import{abortOnClose as be,callerFromApp as Ee,handleProviderOperation as Ne,PROVIDER_OPERATIONS as Te,providerFailure as Ae}from"./handlers/providers.js";function Pe(e){return Object.prototype.hasOwnProperty.call(x,e)?x[e]:void 0}i(Pe,"fixedRow");function Ie(e){const n=Pe(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}i(Ie,"errorType");function Ce(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...r}=e;return r}i(Ce,"appDetails");function P(e,n,r){const u=Ce(e.details);return{error:{code:e.code,message:e.message,...u===void 0?{}:{details:u},type:Ie(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...r?{run_id:r}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...ne(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}i(P,"envelope");function Me(e,n,r,u){const s=n instanceof d?n:new d(500,"server_failure","runtime request failed");if(n instanceof d||console.error("[app-listener]",n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
2
- data: ${JSON.stringify(P(s,r,u))}
1
+ var B=Object.defineProperty;var i=(e,n)=>B(e,"name",{value:n,configurable:!0});import{randomUUID as $}from"node:crypto";import{createServer as j}from"node:http";import{Router as F}from"./router.js";import{OPERATIONS as G}from"./operations.js";import{errorCodes as L}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as Y,RUNTIME_VERSION_HEADER as z,minimumRuntimeRefusal as Q}from"../../api/runtime-version.js";import{CONTRACT_HEADER as W,CONTRACT_LEVEL as U,REQUIRED_CONTRACT_HEADER as K,contractRefusal as Z}from"../../api/contract.js";import{crtrVersion as H}from"../../shared/crtr-version.js";import{AppPrincipalResolver as ee}from"./principal.js";import{handleRunOperation as te,integer as ne}from"./handlers/runs.js";import{parseLlmRequest as re,runLlm as ae}from"../../core/llm/run.js";import{parseUsageMonth as oe,usageFor as se}from"../../core/usage/ledger.js";import{capFields as ie}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as ue,MODEL_CONTEXT_BODY_MAX_BYTES as ce}from"../../core/llm/body-limits.js";import{RunError as d,runIsRunning as de}from"../../core/runs/operations.js";import{requireRun as M}from"./router.js";import{deferHead as le}from"./deferred-head.js";import{readRunEvents as V,registerGrantStream as fe,subscribeRunEvents as me}from"../../core/runs/events.js";import{activeGrant as pe}from"../../core/grants/mirror.js";import{releaseGrantWaiters as he}from"../../core/grants/watch.js";import{emitEvent as _e}from"../../core/events/emit.js";import{runNotices as ye}from"../run-notices.js";import{handleVendorSignIn as we,isVendorSignInPath as ve}from"./vendor-signin.js";import{listAppProfiles as ge}from"./handlers/app-profiles.js";import{handleTransferDownload as Oe,handleTransferOperation as Re,TRANSFER_OPERATIONS as Ee}from"./handlers/transfer.js";import{handleQuestionOperation as Se,QUESTION_OPERATIONS as Te}from"./handlers/questions.js";import{vendorStatus as Ae}from"./handlers/vendor-status.js";import{handleMemoryOperation as be,MEMORY_OPERATIONS as Ne}from"./handlers/memory.js";import{abortOnClose as Ce,callerFromApp as Ie,handleProviderOperation as Pe,PROVIDER_OPERATIONS as He,providerFailure as Me}from"./handlers/providers.js";function De(e){return Object.prototype.hasOwnProperty.call(L,e)?L[e]:void 0}i(De,"fixedRow");function ke(e){const n=De(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}i(ke,"errorType");function xe(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...r}=e;return r}i(xe,"appDetails");function I(e,n,r){const u=xe(e.details);return{error:{code:e.code,message:e.message,...u===void 0?{}:{details:u},type:ke(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...r?{run_id:r}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...ie(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}i(I,"envelope");function Je(e,n,r,u){const s=n instanceof d?n:new d(500,"server_failure","runtime request failed");if(n instanceof d||console.error("[app-listener]",n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
2
+ data: ${JSON.stringify(I(s,r,u))}
3
3
 
4
- `);return}e.writeHead(s.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":r,"X-Content-Type-Options":"nosniff",...s.code==="all_routes_exhausted"?{"Retry-After":String(s.details.retry_after_s)}:{}}),e.end(JSON.stringify(P(s,r,u)))}i(Me,"fail");function He(e){return Array.isArray(e)?e[0]:e}i(He,"headerValue");function v(e,n,r){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(r))}i(v,"json");async function A(e,n=re){const r=[];let u=0;for await(const s of e){if(u+=s.length,u>n)throw new d(400,"invalid_request","request body too large");r.push(s)}if(u)try{return JSON.parse(Buffer.concat(r).toString("utf8"))}catch{throw new d(400,"invalid_request","invalid JSON body")}}i(A,"bodyOf");function ke(e,n){const r=e.headers["last-event-id"]??n.get("after");if(Array.isArray(r))throw new d(400,"invalid_request","invalid cursor","after");return W(r??null,"after",0,Number.MAX_SAFE_INTEGER,0)}i(ke,"cursor");function De(e){return`id: ${e.sequence_number}
4
+ `);return}e.writeHead(s.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":r,"X-Content-Type-Options":"nosniff",...s.code==="all_routes_exhausted"?{"Retry-After":String(s.details.retry_after_s)}:{}}),e.end(JSON.stringify(I(s,r,u)))}i(Je,"fail");function X(e){return Array.isArray(e)?e[0]:e}i(X,"headerValue");function w(e,n,r){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(r))}i(w,"json");async function C(e,n=ue){const r=[];let u=0;for await(const s of e){if(u+=s.length,u>n)throw new d(400,"invalid_request","request body too large");r.push(s)}if(u)try{return JSON.parse(Buffer.concat(r).toString("utf8"))}catch{throw new d(400,"invalid_request","invalid JSON body")}}i(C,"bodyOf");function Le(e,n){const r=e.headers["last-event-id"]??n.get("after");if(Array.isArray(r))throw new d(400,"invalid_request","invalid cursor","after");return ne(r??null,"after",0,Number.MAX_SAFE_INTEGER,0)}i(Le,"cursor");function Ue(e){return`id: ${e.sequence_number}
5
5
  event: ${e.type}
6
6
  data: ${JSON.stringify(e)}
7
7
 
8
- `}i(De,"frame");const xe=6e4;function Je(e,n,r,u,s,g,O,E={}){C(u,r);let f=U(r,s);if(s>f.latest_sequence)throw new d(400,"invalid_request","cursor is beyond the run","after");if(s<f.latest_sequence&&(f.earliest_sequence===null||s<f.earliest_sequence-1))throw new d(409,"stream_gap","events have expired",void 0,{earliest_sequence:f.earliest_sequence,latest_sequence:f.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":O==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),O==="sse"?n.write(`: connected
8
+ `}i(Ue,"frame");const Ve=6e4;function Xe(e,n,r,u,s,v,g,A={}){M(u,r);let l=V(r,s);if(s>l.latest_sequence)throw new d(400,"invalid_request","cursor is beyond the run","after");if(s<l.latest_sequence&&(l.earliest_sequence===null||s<l.earliest_sequence-1))throw new d(409,"stream_gap","events have expired",void 0,{earliest_sequence:l.earliest_sequence,latest_sequence:l.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":g==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),g==="sse"?n.write(`: connected
9
9
 
10
- `):n.flushHeaders();let S=s;const a=fe().streamOpened(r);let t=!1,m=!1,y=!1,h;const _=new Promise(p=>{h=p}),w=i(()=>{t||(t=!0,a(S),clearInterval(I),clearInterval(k),R(),b(),n.off("close",w),n.off("drain",o),n.end(),h())},"end"),o=i(()=>{y=!1,N()},"onDrain"),c=i(p=>{t||n.write(O==="sse"?`event: error
11
- data: ${JSON.stringify(P(p,g,r))}
10
+ `):n.flushHeaders();let E=s;const a=ye().streamOpened(r);let t=!1,p=!1,_=!1,S;const y=new Promise(h=>{S=h}),f=i(()=>{t||(t=!0,a(E),clearInterval(T),clearInterval(x),b(),R(),n.off("close",f),n.off("drain",O),n.end(),S())},"end"),O=i(()=>{_=!1,c()},"onDrain"),o=i(h=>{t||n.write(g==="sse"?`event: error
11
+ data: ${JSON.stringify(I(h,v,r))}
12
12
 
13
- `:`${JSON.stringify({type:"error",...P(p,g,r)})}
14
- `),w()},"error"),N=i(()=>{if(!(t||m)){m=!0;try{if(!T()){l();return}if(C(u,r),f=U(r,S),f.backlog_count>1e3||f.backlog_bytes>32*1024*1024){c(new d(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:S}));return}for(const p of f.events){if(y)break;y=!n.write(O==="sse"?De(p):`${JSON.stringify(p)}
15
- `),S=p.sequence_number}}catch(p){c(p instanceof d?p:new d(500,"server_failure","stream failed"))}finally{m=!1}}},"flush"),l=i(()=>c(new d(401,"grant_revoked","grant is not active")),"revoked"),T=i(()=>"class"in u||ce(u.grant)!==null,"grantActive"),R=ue(r,N),b="class"in u?()=>{}:ie(u.grant,l),I=setInterval(()=>{!t&&!y&&O==="sse"&&n.write(`: keepalive
13
+ `:`${JSON.stringify({type:"error",...I(h,v,r)})}
14
+ `),f()},"error"),c=i(()=>{if(!(t||p)){p=!0;try{if(!m()){N();return}if(M(u,r),l=V(r,E),l.backlog_count>1e3||l.backlog_bytes>32*1024*1024){o(new d(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:E}));return}for(const h of l.events){if(_)break;_=!n.write(g==="sse"?Ue(h):`${JSON.stringify(h)}
15
+ `),E=h.sequence_number}}catch(h){o(h instanceof d?h:new d(500,"server_failure","stream failed"))}finally{p=!1}}},"flush"),N=i(()=>o(new d(401,"grant_revoked","grant is not active")),"revoked"),m=i(()=>"class"in u||pe(u.grant)!==null,"grantActive"),b=me(r,c),R="class"in u?()=>{}:fe(u.grant,N),T=setInterval(()=>{!t&&!_&&g==="sse"&&n.write(`: keepalive
16
16
 
17
- `)},15e3),q=E.idleMs??xe,X=E.isRunning??oe;let M=Date.now(),H=S;const k=setInterval(()=>{if(t||O!=="sse")return;const p=Date.now();let D=!0;try{D=X(r)}catch{}if(D||S!==H){M=p,H=S;return}p-M<q||y||(n.write(`event: idle
17
+ `)},15e3),P=A.idleMs??Ve,q=A.isRunning??de;let D=Date.now(),k=E;const x=setInterval(()=>{if(t||g!=="sse")return;const h=Date.now();let J=!0;try{J=q(r)}catch{}if(J||E!==k){D=h,k=E;return}h-D<P||_||(n.write(`event: idle
18
18
  data: {}
19
19
 
20
- `),w())},E.idleCheckMs??1e3);return k.unref(),n.on("drain",o),n.on("close",w),N(),_}i(Je,"streamRunEvents");function mt(e,n,r,u={}){const s=e.listener;if(!e.issuer||!e.audience||!s?.address||!Number.isInteger(s.port)||s.port<1||s.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(s.address))throw new Error("runtime listener must bind a specific address");const g=new z(e.issuer,e.audience,r,e.jwksUrl),O=new j,E=new Map;for(const a of V.filter(t=>t.stage!==void 0)){const t=i(()=>({status:500}),"handler");O.register({method:a.method,pattern:a.path,handler:t}),E.set(t,a)}const f=$((a,t)=>{(async()=>{se(a,t,u.deferHead);const m=L();t.setHeader("Request-Id",m),t.setHeader(G,J());let y;try{const h=Y(He(a.headers[F.toLowerCase()]),J());if(h!==null)throw h.status===400?new d(400,h.code,h.message,h.param):new d(426,h.code,h.message,void 0,h.details);const _=new URL(a.url??"/","http://runtime.local");if(pe(_.pathname)){await me(a,t,_,g);return}if((a.method!=="GET"||_.pathname!=="/v1/health")&&(!g.isReady()||!n()))throw new d(503,"runtime_starting","runtime_starting");const w=O.match(a.method??"GET",_.pathname);if(!w)throw new d(404,"not_found","unknown operation");const o=E.get(w.handler);if(!o)throw new d(404,"not_found","unknown operation");if(y=w.params.run_id,o.name==="health"){v(t,200,{status:"ok",api_version:"2026-09-23"});return}if(o.name==="runtime.sync-notice"){if(!await g.verifySyncNotice(a.headers.authorization)){t.writeHead(204),t.end();return}le({level:"info",event:"daemon.grants.sync_notice",fields:{request_id:m}});try{await r({fresh:!0})}catch{throw new d(503,"service_unavailable","grant sync failed")}t.writeHead(204),t.end();return}if(o.name==="runtime.vendor-status"){await g.verifyDirectoryRead(a.headers.authorization,"vendor_status"),v(t,200,Oe());return}const c=await g.resolve(a.headers.authorization,o.scope);if(t.setHeader("Request-Id",m),y&&C(c,y,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(o.name),o.name==="run.delete"),o.stream==="bytes"){await _e(a,t,c,_.searchParams);return}if(o.stream){Je(a,t,y,c,ke(a,_.searchParams),m,"sse",u.stream);return}if(o.name==="llm"){const l=K(await A(a,ae)),T=new AbortController,R=i(()=>T.abort(),"abort");t.on("close",R);try{const b=await Z(l,T.signal,l.stream?I=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(I)}
20
+ `),f())},A.idleCheckMs??1e3);return x.unref(),n.on("drain",O),n.on("close",f),c(),y}i(Xe,"streamRunEvents");function wt(e,n,r,u={}){const s=e.listener;if(!e.issuer||!e.audience||!s?.address||!Number.isInteger(s.port)||s.port<1||s.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(s.address))throw new Error("runtime listener must bind a specific address");const v=new ee(e.issuer,e.audience,r,e.jwksUrl),g=new F,A=new Map;for(const a of G.filter(t=>t.stage!==void 0)){const t=i(()=>({status:500}),"handler");g.register({method:a.method,pattern:a.path,handler:t}),A.set(t,a)}const l=j((a,t)=>{(async()=>{le(a,t,u.deferHead);const p=$();t.setHeader("Request-Id",p),t.setHeader(z,H()),t.setHeader(W,U);let _;try{const S=X(a.headers[Y.toLowerCase()]),y=Q(S,H())??Z(X(a.headers[K.toLowerCase()]),U,{legacyMinimum:S,runtimeVersion:H()});if(y!==null)throw y.status===400?new d(400,y.code,y.message,y.param):new d(426,y.code,y.message,void 0,y.details);const f=new URL(a.url??"/","http://runtime.local");if(ve(f.pathname)){await we(a,t,f,v);return}if((a.method!=="GET"||f.pathname!=="/v1/health")&&(!v.isReady()||!n()))throw new d(503,"runtime_starting","runtime_starting");const O=g.match(a.method??"GET",f.pathname);if(!O)throw new d(404,"not_found","unknown operation");const o=A.get(O.handler);if(!o)throw new d(404,"not_found","unknown operation");if(_=O.params.run_id,o.name==="health"){w(t,200,{status:"ok",api_version:"2026-09-23"});return}if(o.name==="runtime.sync-notice"){if(!await v.verifySyncNotice(a.headers.authorization)){t.writeHead(204),t.end();return}_e({level:"info",event:"daemon.grants.sync_notice",fields:{request_id:p}});try{await r({fresh:!0})}catch{throw new d(503,"service_unavailable","grant sync failed")}t.writeHead(204),t.end();return}if(o.name==="runtime.vendor-status"){await v.verifyDirectoryRead(a.headers.authorization,"vendor_status"),w(t,200,Ae());return}const c=await v.resolve(a.headers.authorization,o.scope);if(t.setHeader("Request-Id",p),_&&M(c,_,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(o.name),o.name==="run.delete"),o.stream==="bytes"){await Oe(a,t,c,f.searchParams);return}if(o.stream){Xe(a,t,_,c,Le(a,f.searchParams),p,"sse",u.stream);return}if(o.name==="llm"){const m=re(await C(a,ce)),b=new AbortController,R=i(()=>b.abort(),"abort");t.on("close",R);try{const T=await ae(m,b.signal,m.stream?P=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(P)}
21
21
 
22
- `)}:void 0,void 0,{grantee:c.grantee,spend_cap:c.spend_cap});l.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
22
+ `)}:void 0,void 0,{grantee:c.grantee,spend_cap:c.spend_cap});m.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
23
23
 
24
- `)):v(t,200,b.completion)}finally{t.off("close",R)}return}if(o.name==="usage.get"){v(t,200,te(c.grantee,c.spend_cap,ee(_.searchParams.get("month"))));return}if(o.name==="profile.list"){v(t,200,he(c));return}if(ge.has(o.name)){const l=await ve(o.name,c,w.params.question_id,o.method==="POST"?await A(a):void 0);v(t,l.status,l.body);return}if(Te.has(o.name)){const{signal:l,release:T}=be(t);try{const R=await Ne(o.name,Ee(c),{params:w.params,body:o.method==="POST"?await A(a):void 0,query:_.searchParams},{requestId:m,signal:l});v(t,R.status,R.body)}catch(R){const b=Ae(R,m,!0);t.writeHead(b.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...b.headers}),t.end(JSON.stringify(b.body))}finally{T()}return}if(Se.has(o.name)){const l=Re(o.name,{class:"app",app:c.grantee,scopes:c.resolved},_.searchParams);v(t,l.status,l.body);return}if(we.has(o.name)){const l=await ye(o,c,w.params,_.searchParams,await A(a));v(t,l.status,l.body);return}const N=await Q(o,c,y,_.searchParams,await A(a),a.headers["idempotency-key"]);v(t,N.status,N.body)}catch(h){Me(t,h,m,y)}})()});return{ready:new Promise((a,t)=>{f.once("error",t),f.listen(s.port,s.address,()=>{f.off("error",t),a()})}),close:i(async()=>{g.close(),de(),f.closeAllConnections(),await new Promise((a,t)=>f.close(m=>m?t(m):a()))},"close")}}i(mt,"createAppListener");export{xe as RUN_STREAM_IDLE_MS,mt as createAppListener,ke as cursor,Je as streamRunEvents};
24
+ `)):w(t,200,T.completion)}finally{t.off("close",R)}return}if(o.name==="usage.get"){w(t,200,se(c.grantee,c.spend_cap,oe(f.searchParams.get("month"))));return}if(o.name==="profile.list"){w(t,200,ge(c));return}if(Te.has(o.name)){const m=await Se(o.name,c,O.params.question_id,o.method==="POST"?await C(a):void 0);w(t,m.status,m.body);return}if(He.has(o.name)){const{signal:m,release:b}=Ce(t);try{const R=await Pe(o.name,Ie(c),{params:O.params,body:o.method==="POST"?await C(a):void 0,query:f.searchParams},{requestId:p,signal:m});w(t,R.status,R.body)}catch(R){const T=Me(R,p,!0);t.writeHead(T.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...T.headers}),t.end(JSON.stringify(T.body))}finally{b()}return}if(Ne.has(o.name)){const m=be(o.name,{class:"app",app:c.grantee,scopes:c.resolved},f.searchParams);w(t,m.status,m.body);return}if(Ee.has(o.name)){const m=await Re(o,c,O.params,f.searchParams,await C(a));w(t,m.status,m.body);return}const N=await te(o,c,_,f.searchParams,await C(a),a.headers["idempotency-key"]);w(t,N.status,N.body)}catch(S){Je(t,S,p,_)}})()});return{ready:new Promise((a,t)=>{l.once("error",t),l.listen(s.port,s.address,()=>{l.off("error",t),a()})}),close:i(async()=>{v.close(),he(),l.closeAllConnections(),await new Promise((a,t)=>l.close(p=>p?t(p):a()))},"close")}}i(wt,"createAppListener");export{Ve as RUN_STREAM_IDLE_MS,wt as createAppListener,Le as cursor,Xe as streamRunEvents};
@@ -1 +1 @@
1
- var q=Object.defineProperty;var u=(t,n)=>q(t,"name",{value:n,configurable:!0});import{createServer as x}from"node:http";import{chmodSync as ee,existsSync as C,unlinkSync as B}from"node:fs";import{MINIMUM_RUNTIME_VERSION_HEADER as j,RUNTIME_VERSION_HEADER as O,minimumRuntimeRefusal as re}from"../../api/runtime-version.js";import{crtrVersion as M}from"../../shared/crtr-version.js";import{apiSocketPath as te}from"../../core/canvas/paths.js";import{resolveNodeToken as G}from"../../core/canvas/node-tokens.js";import{grantFor as oe,hasSyncedGrants as ne}from"../../core/grants/mirror.js";import{readConfig as U}from"../../core/config.js";import{peerUid as ie}from"../../native/linux.js";import{getApiToken as se,listScopedApiTokens as le}from"../../core/secrets.js";import{emitEvent as g}from"../../core/events/emit.js";import{envCrtrdTcp as ae,envCrtrdToken as ce}from"../../shared/env.js";import{Router as L}from"./router.js";import{handleAttachUpgrade as ue,refuseUpgrade as R}from"./bridge.js";import{boundDaemonControl as pe}from"../control.js";import{attachRoutes as de}from"./handlers/attach.js";import{bashJobRoutes as me}from"./handlers/bash-jobs.js";import{bashRoutes as fe}from"./handlers/bash.js";import{hookExecRoutes as ge}from"./handlers/hook-exec.js";import{brokerOperationRoutes as W}from"./handlers/broker-ops.js";import{brokerSignalRoutes as he,brokerSignalStreams as Ae}from"./handlers/broker-signals.js";import{brokerRecoveryRoutes as $,nodeFaultRoutes as Re}from"./handlers/broker-recovery.js";import{canvasRoutes as ye}from"./handlers/canvas.js";import{chatInventoryRoutes as ve}from"./handlers/chat-inventory.js";import{daemonRoutes as be}from"./handlers/daemon.js";import{fileRoutes as we}from"./handlers/files.js";import{focusRoutes as Se}from"./handlers/focus.js";import{analyticsRoutes as Ee}from"./handlers/analytics.js";import{memoryReadsRoutes as ke}from"./handlers/memory-reads.js";import{memoryRoutes as Te}from"./handlers/memory.js";import{healthRoutes as _e}from"./handlers/health.js";import{humanRequestRoutes as Ce}from"./handlers/human-requests.js";import{inboxRoutes as Oe}from"./handlers/inbox.js";import{feedbackCommentRoutes as Ue}from"./handlers/feedback-comments.js";import{objectRoutes as He}from"./handlers/objects.js";import{customObjectRoutes as Ne}from"./handlers/custom-objects.js";import{docRoutes as ze}from"./handlers/docs.js";import{deliveryRoutes as D}from"./handlers/delivery.js";import{deliveryRetryRoutes as Pe}from"./handlers/delivery-retry.js";import{packageDocRoutes as Ie}from"./handlers/package-docs.js";import{brokerMailRoutes as J,messageRoutes as xe}from"./handlers/messages.js";import{modelAuthRoutes as Be}from"./handlers/modelauth.js";import{llmRoutes as je}from"./handlers/llm.js";import{providerRoutes as Me}from"./handlers/providers.js";import{modelConfigRoutes as Ge}from"./handlers/model-config.js";import{nodeRoutes as Le}from"./handlers/nodes.js";import{nodeRecordRoutes as We}from"./handlers/node-records.js";import{nodeOutcomeRoutes as $e}from"./handlers/node-outcomes.js";import{nodeEventRoutes as De}from"./handlers/node-events.js";import{profileRoutes as Je}from"./handlers/profiles.js";import{prospectiveChatInventoryRoutes as Fe}from"./handlers/prospective-chat-inventory.js";import{reportRoutes as Ve}from"./handlers/reports.js";import{runRoutes as Ye}from"./handlers/run-routes.js";import{reviewCommentRoutes as Ze}from"./handlers/review-comments.js";import{reviewRoutes as Ke}from"./handlers/reviews.js";import{subscriptionRoutes as Qe}from"./handlers/subscriptions.js";import{structuredRequestRoutes as Xe}from"./handlers/structured-requests.js";import{cronRoutes as qe}from"./handlers/crons.js";import{setCronRequestHandler as F}from"../cron/run-socket.js";import{worktreeRoutes as er}from"./handlers/worktree.js";import{limitsRoutes as rr}from"./handlers/limits.js";function tr(t){return new L().registerAll(_e(t)).registerAll(be).registerAll(Le).registerAll(We).registerAll($e).registerAll(Pe).registerAll(De).registerAll(me).registerAll(fe).registerAll(ge).registerAll(W).registerAll(he).registerAll(D).registerAll($).registerAll(Re).registerAll(er).registerAll(rr).registerAll(J).registerAll(xe).registerAll(Ve).registerAll(Ye).registerAll(Xe).registerAll(de).registerAll(ye).registerAll(Ee).registerAll(ke).registerAll(Te).registerAll(we).registerAll(He).registerAll(Ne).registerAll(ze).registerAll(Ie).registerAll(Se).registerAll(Qe).registerAll(qe).registerAll(Je).registerAll(Be).registerAll(je).registerAll(Me).registerAll(Ge).registerAll(Ke).registerAll(Ze).registerAll(Oe).registerAll(Ce).registerAll(ve).registerAll(Fe).registerAll(Ue)}u(tr,"buildRouter");function or(t,n){if(t.toUpperCase()!=="GET")return null;const i=n.split("/").filter(m=>m.length>0);if(i.length!==4||i[0]!=="v1"||i[1]!=="nodes"||i[3]!=="attach")return null;const p=i[2];return p===void 0||p===""?null:decodeURIComponent(p)}u(or,"attachNodeId");function nr(t){const n=t.trim();if(n==="")return null;const i=n.lastIndexOf(":");if(i<0)return null;const p=n.slice(0,i),m=n.slice(i+1),d=Number(m);return!Number.isInteger(d)||d<0||d>65535?null:{host:p===""?"0.0.0.0":p,port:d}}u(nr,"parseTcp");function ir(t,n){return`${t.includes(":")?`[${t}]`:t}:${n}`}u(ir,"formatTcpAddress");const w={code:"unauthorized",message:"missing or invalid bearer token"},k={code:"owner_only",message:"attach is owner-only; a scoped token does not reach it"},H={class:"anonymous",app:"",node:"",run:null,uid:-1,sub:"",grant:null,client_id:null,scopes:[]},A=new WeakMap;function V(t){return A.get(t)}u(V,"socketPrincipal");function Y(t,n){const i=process.platform==="linux"?ie(t.socket):null,p=t.headers.authorization;if(p===void 0)return i===null||n.has(i)?{class:"person"}:"unauthorized";if(typeof p!="string"||!p.startsWith("Bearer "))return"unauthorized";if(p.slice(7).startsWith("crtr_pub_"))return H;const m=G(p.slice(7));if(!m||i!==null&&i!==m.uid)return"unauthorized";const d=U("user").runtime?.issuer;if(d&&!ne(d.replace(/\/$/,"")))return"runtime_starting";const S=m.grant??oe("app:terminal",m.sub)?.id??null;return{class:"broker",...m,grant:S}}u(Y,"resolveSocketPrincipal");function sr(t){const n=new Map;for(const i of le("user"))n.set(i.token,{principal:{class:"app",app:i.grantee,scopes:i.scopes},ceiling:i.scopes});return n.set(t,{principal:{class:"person"},ceiling:null}),n}u(sr,"resolveBearers");function Z(t,n){const i=t.headers.authorization;if(!(typeof i!="string"||!i.startsWith("Bearer ")))return n.get(i.slice(7))}u(Z,"authorize");function T(t){t.writeHead(401,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:w}))}u(T,"writeUnauthorized");function lr(t,n){t.writeHead(503,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:{code:"startup_blocked",message:n}}))}u(lr,"writeStartupBlocked");function K(t){return new Promise(n=>{try{t.closeAllConnections()}catch{}t.close(()=>n())})}u(K,"closeServer");function At(t={}){let n=null;const i=tr(()=>n),p=new L().registerAll(W).registerAll(D).registerAll($).registerAll(J),m=u((e,r)=>{const o=pe()?.startupBlock()??null;return o===null||e==="GET"&&(r==="/healthz"||r==="/v1/status")||e==="POST"&&(r==="/v1/daemon/migrate"||r==="/v1/daemon/admit")||o.phase==="recovering"&&p.match(e,r)!==null?null:o},"startupBlockFor"),d=te(),S=new Set([process.getuid?.()??0,...U("user").runtime?.person_uids??[]]),y=u((e,r,o)=>{r.setHeader(O,M());const a=e.headers[j.toLowerCase()],f=re(Array.isArray(a)?a[0]:a,M());if(f!==null){r.writeHead(f.status,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:f.code,message:f.message,type:"invalid_request_error",origin:"runtime",retryable:!1,..."param"in f?{param:f.param}:{details:f.details}}}));return}const s=u(l=>{const b=V(e);g({event:"api.request.failed",level:"error",...b?.class==="broker"?{node_id:b.node}:{},error:l instanceof Error?l:new Error(String(l))});try{r.headersSent||r.writeHead(500,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"internal",message:"internal error"}}))}catch{}},"internalFailure");let c;try{c=new URL(e.url??"/","http://127.0.0.1")}catch{try{r.writeHead(400,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"invalid_request",message:"request URL is invalid"}}))}catch{}return}try{const l=m((e.method??"GET").toUpperCase(),c.pathname);if(l!==null){lr(r,l.message);return}i.handle(e,r,o,V(e)??{class:"person"}).catch(s)}catch(l){s(l)}},"onRequest"),_=u((e,r,o)=>{r.on("error",()=>{});try{const a=new URL(e.url??"/","http://127.0.0.1"),f=m((e.method??"GET").toUpperCase(),a.pathname);if(f!==null){R(r,503,"Service Unavailable","startup_blocked",f.message);return}const s=or(e.method??"GET",a.pathname);if(s===null){r.destroy();return}ue(e,r,o,s).catch(()=>{try{r.destroy()}catch{}})}catch{try{r.destroy()}catch{}}},"onUpgrade"),Q=u(e=>{e.on("error",r=>{g({event:"api.server.failed",level:"error",error:r})})},"bindErrorHandler"),v=x((e,r)=>{try{const o=Y(e,S);if(o==="unauthorized"){g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),T(r);return}if(o==="runtime_starting"){r.writeHead(503,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"runtime_starting",message:"grant sync is not ready",type:"server_error",origin:"runtime",retryable:!0}}));return}A.set(e,o),o.class==="broker"&&g({event:"api.request.principal",level:o.app==="app:terminal"?"debug":"info",node_id:o.node,fields:{principal:`${o.app} node:${o.node}`,method:e.method,path:e.url}}),y(e,r,null)}catch(o){g({event:"api.principal.failed",level:"error",error:o instanceof Error?o:new Error(String(o))}),T(r)}});v.on("upgrade",(e,r,o)=>{try{const a=Y(e,S);if(a==="unauthorized"||a==="runtime_starting"){a==="unauthorized"&&g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),R(r,a==="unauthorized"?401:503,"Unauthorized",a,"socket principal unavailable");return}if(a.class!=="person"&&a.class!=="broker"){R(r,403,"Forbidden",k.code,k.message);return}A.set(e,a),_(e,r,o)}catch(a){g({event:"api.principal.failed",level:"error",error:a instanceof Error?a:new Error(String(a))}),R(r,401,"Unauthorized",w.code,w.message)}}),F((e,r,o)=>{A.set(e,o),y(e,r,null)});let N=!1,z,P;const X=new Promise((e,r)=>{z=e,P=r});v.once("listening",()=>{N=!0,z()}),v.on("error",e=>{g({event:"api.server.failed",level:"error",error:e}),N||P(e)});try{C(d)&&B(d)}catch{}v.listen(d,()=>{try{ee(d,process.platform==="linux"&&C("/run/crtr/launcher.sock")?438:384)}catch(e){g({event:"api.socket.chmod_failed",level:"error",error:e instanceof Error?e:new Error(String(e))})}});let h;const E=t.tcp??ae()??U("user").api.tcp;if(E!==void 0&&E!==""){const e=nr(E);if(e===null)g({event:"api.tcp.invalid",level:"error",error:new Error(`invalid CRTRD_TCP/--tcp spec: ${E}`)});else{const r=t.token??ce()??se("user"),o=r===void 0?void 0:sr(r),a=o===void 0?(s,c)=>{const l=s.headers.authorization;if(typeof l=="string"&&l.startsWith("Bearer crtr_pub_"))A.set(s,H);else if(typeof l=="string"&&l.startsWith("Bearer ")&&G(l.slice(7))){T(c);return}y(s,c,null)}:(s,c)=>{if((s.method??"GET").toUpperCase()==="OPTIONS"){c.writeHead(204,{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":`authorization, content-type, ${j.toLowerCase()}`,"Access-Control-Allow-Methods":"GET, POST, PATCH, PUT, DELETE, OPTIONS","Access-Control-Max-Age":"600"}),c.end();return}const l=Z(s,o);if(l===void 0&&s.headers.authorization?.startsWith("Bearer crtr_pub_")){A.set(s,H),c.setHeader("Access-Control-Allow-Origin","*"),c.setHeader("Access-Control-Expose-Headers",O),y(s,c,null);return}if(l===void 0){T(c);return}A.set(s,l.principal),c.setHeader("Access-Control-Allow-Origin","*"),c.setHeader("Access-Control-Expose-Headers",O),y(s,c,l.ceiling)},f=o===void 0?_:(s,c,l)=>{const b=Z(s,o);if(b===void 0){R(c,401,"Unauthorized",w.code,w.message);return}if(b.ceiling!==null){R(c,403,"Forbidden",k.code,k.message);return}_(s,c,l)};h=x(a),h.on("upgrade",f),Q(h),h.once("listening",()=>{const s=h?.address();if(s!==null&&typeof s=="object"){const{address:c,port:l}=s;n=ir(c,l)}}),h.listen(e.port,e.host)}}let I=!1;return{ready:X,tcpAddress:u(()=>n,"tcpAddress"),close:u(async()=>{if(!I){I=!0,F(null),Ae.close(),await Promise.all([K(v),h!==void 0?K(h):Promise.resolve()]);try{C(d)&&B(d)}catch{}}},"close")}}u(At,"createApiServer");export{tr as buildRouter,At as createApiServer,V as socketPrincipal};
1
+ var se=Object.defineProperty;var a=(t,n)=>se(t,"name",{value:n,configurable:!0});import{createServer as j}from"node:http";import{chmodSync as le,existsSync as U,unlinkSync as D}from"node:fs";import{MINIMUM_RUNTIME_VERSION_HEADER as G,RUNTIME_VERSION_HEADER as H,minimumRuntimeRefusal as ae}from"../../api/runtime-version.js";import{CONTRACT_HEADER as N,CONTRACT_LEVEL as W,REQUIRED_CONTRACT_HEADER as J,contractRefusal as ue}from"../../api/contract.js";import{crtrVersion as z}from"../../shared/crtr-version.js";import{apiSocketPath as ce}from"../../core/canvas/paths.js";import{resolveNodeToken as F}from"../../core/canvas/node-tokens.js";import{grantFor as pe,hasSyncedGrants as de}from"../../core/grants/mirror.js";import{readConfig as P}from"../../core/config.js";import{peerUid as me}from"../../native/linux.js";import{getApiToken as fe,listScopedApiTokens as ge}from"../../core/secrets.js";import{emitEvent as g}from"../../core/events/emit.js";import{envCrtrdTcp as he,envCrtrdToken as Re}from"../../shared/env.js";import{Router as V}from"./router.js";import{handleAttachUpgrade as Ae,refuseUpgrade as A}from"./bridge.js";import{boundDaemonControl as ye}from"../control.js";import{attachRoutes as ve}from"./handlers/attach.js";import{bashJobRoutes as be}from"./handlers/bash-jobs.js";import{bashRoutes as Ee}from"./handlers/bash.js";import{hookExecRoutes as we}from"./handlers/hook-exec.js";import{brokerOperationRoutes as Q}from"./handlers/broker-ops.js";import{brokerSignalRoutes as Te,brokerSignalStreams as Ce}from"./handlers/broker-signals.js";import{brokerRecoveryRoutes as Y,nodeFaultRoutes as Se}from"./handlers/broker-recovery.js";import{canvasRoutes as ke}from"./handlers/canvas.js";import{chatInventoryRoutes as _e}from"./handlers/chat-inventory.js";import{daemonRoutes as Oe}from"./handlers/daemon.js";import{fileRoutes as Ue}from"./handlers/files.js";import{focusRoutes as He}from"./handlers/focus.js";import{analyticsRoutes as Ne}from"./handlers/analytics.js";import{memoryReadsRoutes as ze}from"./handlers/memory-reads.js";import{memoryRoutes as Pe}from"./handlers/memory.js";import{healthRoutes as Ie}from"./handlers/health.js";import{humanRequestRoutes as xe}from"./handlers/human-requests.js";import{inboxRoutes as $e}from"./handlers/inbox.js";import{feedbackCommentRoutes as Le}from"./handlers/feedback-comments.js";import{objectRoutes as Be}from"./handlers/objects.js";import{customObjectRoutes as Me}from"./handlers/custom-objects.js";import{docRoutes as je}from"./handlers/docs.js";import{deliveryRoutes as Z}from"./handlers/delivery.js";import{deliveryRetryRoutes as De}from"./handlers/delivery-retry.js";import{packageDocRoutes as Ge}from"./handlers/package-docs.js";import{brokerMailRoutes as K,messageRoutes as We}from"./handlers/messages.js";import{modelAuthRoutes as Je}from"./handlers/modelauth.js";import{llmRoutes as Fe}from"./handlers/llm.js";import{providerRoutes as Ve}from"./handlers/providers.js";import{modelConfigRoutes as Qe}from"./handlers/model-config.js";import{nodeRoutes as Ye}from"./handlers/nodes.js";import{nodeRecordRoutes as Ze}from"./handlers/node-records.js";import{nodeOutcomeRoutes as Ke}from"./handlers/node-outcomes.js";import{nodeEventRoutes as Xe}from"./handlers/node-events.js";import{profileRoutes as qe}from"./handlers/profiles.js";import{prospectiveChatInventoryRoutes as er}from"./handlers/prospective-chat-inventory.js";import{reportRoutes as rr}from"./handlers/reports.js";import{runRoutes as tr}from"./handlers/run-routes.js";import{reviewCommentRoutes as or}from"./handlers/review-comments.js";import{reviewRoutes as nr}from"./handlers/reviews.js";import{subscriptionRoutes as ir}from"./handlers/subscriptions.js";import{structuredRequestRoutes as sr}from"./handlers/structured-requests.js";import{cronRoutes as lr}from"./handlers/crons.js";import{setCronRequestHandler as X}from"../cron/run-socket.js";import{worktreeRoutes as ar}from"./handlers/worktree.js";import{limitsRoutes as ur}from"./handlers/limits.js";function cr(t){return new V().registerAll(Ie(t)).registerAll(Oe).registerAll(Ye).registerAll(Ze).registerAll(Ke).registerAll(De).registerAll(Xe).registerAll(be).registerAll(Ee).registerAll(we).registerAll(Q).registerAll(Te).registerAll(Z).registerAll(Y).registerAll(Se).registerAll(ar).registerAll(ur).registerAll(K).registerAll(We).registerAll(rr).registerAll(tr).registerAll(sr).registerAll(ve).registerAll(ke).registerAll(Ne).registerAll(ze).registerAll(Pe).registerAll(Ue).registerAll(Be).registerAll(Me).registerAll(je).registerAll(Ge).registerAll(He).registerAll(ir).registerAll(lr).registerAll(qe).registerAll(Je).registerAll(Fe).registerAll(Ve).registerAll(Qe).registerAll(nr).registerAll(or).registerAll($e).registerAll(xe).registerAll(_e).registerAll(er).registerAll(Le)}a(cr,"buildRouter");function pr(t,n){if(t.toUpperCase()!=="GET")return null;const s=n.split("/").filter(f=>f.length>0);if(s.length!==4||s[0]!=="v1"||s[1]!=="nodes"||s[3]!=="attach")return null;const d=s[2];return d===void 0||d===""?null:decodeURIComponent(d)}a(pr,"attachNodeId");function dr(t){const n=t.trim();if(n==="")return null;const s=n.lastIndexOf(":");if(s<0)return null;const d=n.slice(0,s),f=n.slice(s+1),m=Number(f);return!Number.isInteger(m)||m<0||m>65535?null:{host:d===""?"0.0.0.0":d,port:m}}a(dr,"parseTcp");function mr(t,n){return`${t.includes(":")?`[${t}]`:t}:${n}`}a(mr,"formatTcpAddress");const y={code:"unauthorized",message:"missing or invalid bearer token"},S={code:"owner_only",message:"attach is owner-only; a scoped token does not reach it"},I={class:"anonymous",app:"",node:"",run:null,uid:-1,sub:"",grant:null,client_id:null,scopes:[]},v=new WeakMap;function q(t){return v.get(t)}a(q,"socketPrincipal");function ee(t,n){const s=process.platform==="linux"?me(t.socket):null,d=t.headers.authorization;if(d===void 0)return s===null||n.has(s)?{class:"person"}:"unauthorized";if(typeof d!="string"||!d.startsWith("Bearer "))return"unauthorized";if(d.slice(7).startsWith("crtr_pub_"))return I;const f=F(d.slice(7));if(!f||s!==null&&s!==f.uid)return"unauthorized";const m=P("user").runtime?.issuer;if(m&&!de(m.replace(/\/$/,"")))return"runtime_starting";const T=f.grant??pe("app:terminal",f.sub)?.id??null;return{class:"broker",...f,grant:T}}a(ee,"resolveSocketPrincipal");function fr(t){const n=new Map;for(const s of ge("user"))n.set(s.token,{principal:{class:"app",app:s.grantee,scopes:s.scopes},ceiling:s.scopes});return n.set(t,{principal:{class:"person"},ceiling:null}),n}a(fr,"resolveBearers");function re(t,n){const s=t.headers.authorization;if(!(typeof s!="string"||!s.startsWith("Bearer ")))return n.get(s.slice(7))}a(re,"authorize");function k(t){t.setHeader(H,z()),t.setHeader(N,W)}a(k,"advertiseRuntime");function _(t){t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Expose-Headers",`${H}, ${N}`)}a(_,"exposeRuntimeHeaders");function te(t,n){return(t.method??"GET").toUpperCase()!=="OPTIONS"?!1:(k(n),n.writeHead(204,{"Access-Control-Allow-Origin":"*","Access-Control-Expose-Headers":`${H}, ${N}`,"Access-Control-Allow-Headers":`authorization, content-type, ${G.toLowerCase()}, ${J.toLowerCase()}`,"Access-Control-Allow-Methods":"GET, POST, PATCH, PUT, DELETE, OPTIONS","Access-Control-Max-Age":"600"}),n.end(),!0)}a(te,"answerPreflight");function w(t,n=!1){k(t),n&&_(t),t.writeHead(401,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:y}))}a(w,"writeUnauthorized");function gr(t,n){t.writeHead(503,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:{code:"startup_blocked",message:n}}))}a(gr,"writeStartupBlocked");function oe(t){return new Promise(n=>{try{t.closeAllConnections()}catch{}t.close(()=>n())})}a(oe,"closeServer");function St(t={}){let n=null;const s=cr(()=>n),d=new V().registerAll(Q).registerAll(Z).registerAll(Y).registerAll(K),f=a((e,r)=>{const i=ye()?.startupBlock()??null;return i===null||e==="GET"&&(r==="/healthz"||r==="/v1/status")||e==="POST"&&(r==="/v1/daemon/migrate"||r==="/v1/daemon/admit")||i.phase==="recovering"&&d.match(e,r)!==null?null:i},"startupBlockFor"),m=ce(),T=new Set([process.getuid?.()??0,...P("user").runtime?.person_uids??[]]),b=a((e,r,i)=>{k(r);const u=a(p=>Array.isArray(p)?p[0]:p,"first"),R=u(e.headers[G.toLowerCase()]),o=ae(R,z())??ue(u(e.headers[J.toLowerCase()]),W,{legacyMinimum:R,runtimeVersion:z()});if(o!==null){r.writeHead(o.status,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:o.code,message:o.message,type:"invalid_request_error",origin:"runtime",retryable:!1,..."param"in o?{param:o.param}:{details:o.details}}}));return}const l=a(p=>{const M=q(e);g({event:"api.request.failed",level:"error",...M?.class==="broker"?{node_id:M.node}:{},error:p instanceof Error?p:new Error(String(p))});try{r.headersSent||r.writeHead(500,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"internal",message:"internal error"}}))}catch{}},"internalFailure");let c;try{c=new URL(e.url??"/","http://127.0.0.1")}catch{try{r.writeHead(400,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"invalid_request",message:"request URL is invalid"}}))}catch{}return}try{const p=f((e.method??"GET").toUpperCase(),c.pathname);if(p!==null){gr(r,p.message);return}s.handle(e,r,i,q(e)??{class:"person"}).catch(l)}catch(p){l(p)}},"onRequest"),O=a((e,r,i)=>{r.on("error",()=>{});try{const u=new URL(e.url??"/","http://127.0.0.1"),R=f((e.method??"GET").toUpperCase(),u.pathname);if(R!==null){A(r,503,"Service Unavailable","startup_blocked",R.message);return}const o=pr(e.method??"GET",u.pathname);if(o===null){r.destroy();return}Ae(e,r,i,o).catch(()=>{try{r.destroy()}catch{}})}catch{try{r.destroy()}catch{}}},"onUpgrade"),ne=a(e=>{e.on("error",r=>{g({event:"api.server.failed",level:"error",error:r})})},"bindErrorHandler"),E=j((e,r)=>{try{const i=ee(e,T);if(i==="unauthorized"){g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),w(r);return}if(i==="runtime_starting"){k(r),r.writeHead(503,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"runtime_starting",message:"grant sync is not ready",type:"server_error",origin:"runtime",retryable:!0}}));return}v.set(e,i),i.class==="broker"&&g({event:"api.request.principal",level:i.app==="app:terminal"?"debug":"info",node_id:i.node,fields:{principal:`${i.app} node:${i.node}`,method:e.method,path:e.url}}),b(e,r,null)}catch(i){g({event:"api.principal.failed",level:"error",error:i instanceof Error?i:new Error(String(i))}),w(r)}});E.on("upgrade",(e,r,i)=>{try{const u=ee(e,T);if(u==="unauthorized"||u==="runtime_starting"){u==="unauthorized"&&g({event:"api.principal.denied",level:"warn",fields:{method:e.method}}),A(r,u==="unauthorized"?401:503,"Unauthorized",u,"socket principal unavailable");return}if(u.class!=="person"&&u.class!=="broker"){A(r,403,"Forbidden",S.code,S.message);return}v.set(e,u),O(e,r,i)}catch(u){g({event:"api.principal.failed",level:"error",error:u instanceof Error?u:new Error(String(u))}),A(r,401,"Unauthorized",y.code,y.message)}}),X((e,r,i)=>{v.set(e,i),b(e,r,null)});let x=!1,$,L;const ie=new Promise((e,r)=>{$=e,L=r});E.once("listening",()=>{x=!0,$()}),E.on("error",e=>{g({event:"api.server.failed",level:"error",error:e}),x||L(e)});try{U(m)&&D(m)}catch{}E.listen(m,()=>{try{le(m,process.platform==="linux"&&U("/run/crtr/launcher.sock")?438:384)}catch(e){g({event:"api.socket.chmod_failed",level:"error",error:e instanceof Error?e:new Error(String(e))})}});let h;const C=t.tcp??he()??P("user").api.tcp;if(C!==void 0&&C!==""){const e=dr(C);if(e===null)g({event:"api.tcp.invalid",level:"error",error:new Error(`invalid CRTRD_TCP/--tcp spec: ${C}`)});else{const r=t.token??Re()??fe("user"),i=r===void 0?void 0:fr(r),u=i===void 0?(o,l)=>{if(te(o,l))return;const c=o.headers.authorization;if(typeof c=="string"&&c.startsWith("Bearer crtr_pub_"))v.set(o,I),_(l);else if(o.headers.origin!==void 0){w(l,!0);return}else if(typeof c=="string"&&c.startsWith("Bearer ")&&F(c.slice(7))){w(l,!0);return}b(o,l,null)}:(o,l)=>{if(te(o,l))return;const c=re(o,i);if(c===void 0&&o.headers.authorization?.startsWith("Bearer crtr_pub_")){v.set(o,I),_(l),b(o,l,null);return}if(c===void 0){w(l,!0);return}v.set(o,c.principal),_(l),b(o,l,c.ceiling)},R=i===void 0?(o,l,c)=>{if(o.headers.origin!==void 0){A(l,401,"Unauthorized",y.code,y.message);return}O(o,l,c)}:(o,l,c)=>{const p=re(o,i);if(p===void 0){A(l,401,"Unauthorized",y.code,y.message);return}if(p.ceiling!==null){A(l,403,"Forbidden",S.code,S.message);return}O(o,l,c)};h=j(u),h.on("upgrade",R),ne(h),h.once("listening",()=>{const o=h?.address();if(o!==null&&typeof o=="object"){const{address:l,port:c}=o;n=mr(l,c)}}),h.listen(e.port,e.host)}}let B=!1;return{ready:ie,tcpAddress:a(()=>n,"tcpAddress"),close:a(async()=>{if(!B){B=!0,X(null),Ce.close(),await Promise.all([oe(E),h!==void 0?oe(h):Promise.resolve()]);try{U(m)&&D(m)}catch{}}},"close")}}a(St,"createApiServer");export{cr as buildRouter,St as createApiServer,q as socketPrincipal};
@@ -3,6 +3,7 @@
3
3
  Use this only for a release explicitly authorized by Silas. `.github/workflows/publish.yml` is the source of truth; this is its manual path when Actions cannot run. Work on clean, current `main`, with `npm whoami` showing the publishing account. Verify the code first (`npm run build:api`, `npm run build`, `npx tsc --noEmit`, relevant `npm run test:isolated -- …`). Do not start a release while another agent owns uncommitted changes in this shared checkout.
4
4
 
5
5
  1. Check the six latest registry versions with `npm view <name> version` for each of `crouter`, `@north-light/api`, `@north-light/env-docker`, `@north-light/identity`, `@north-light/sdk` and `@north-light/plugin`. Fetch `origin/main`, ensure the release commit is current, and choose a patch version greater than every published version. Do **not** use the workflow's `git reset --hard` in a shared checkout.
6
- 2. Run `node scripts/release-version.mjs "$VERSION"`. It sets all six manifests to `$VERSION`, pins every dependency between them (root → identity, SDK → api and identity, plugin → api and identity) to exactly `$VERSION` with no `^`, writes `SDK_VERSION` in `packages/crouter-sdk/src/version.ts`, the oldest runtime that SDK accepts, and renames the `## Next release` section of `docs/sdk/release-notes.md` to `## $VERSION`. One release is one version set: an app pinning `@north-light/sdk@$VERSION` gets exactly this release's api and identity. Run `npm install --package-lock-only --ignore-scripts`, then `node scripts/release-version.mjs --check` (prints `one version set`). Check `git diff` and commit only the six manifests, `packages/crouter-sdk/src/version.ts`, `docs/sdk/release-notes.md` and `package-lock.json` as `chore: release v$VERSION`. Create `git tag -a "v$VERSION" -m "chore: release v$VERSION"`, then `git push --atomic origin HEAD:main "refs/tags/v$VERSION"`. If rejected due to a concurrent push, remove the *local* release tag, integrate fresh main, then recheck versions and retry; never push a tag separately.
7
- 3. From the tagged release commit run `npm ci`. Publish in dependency order, checking `npm view "$PACKAGE@$VERSION" version` before each and skipping only on a positive sighting: `npm run build:identity` then `npm publish` in `packages/crouter-identity`; `npm run build` then `CRTR_PACK_MINIFY=1 npm publish` at root (Docker must be running: the pack cross-builds the Linux x64 and arm64 native addons into `dist/native/linux/<arch>/addon.node` and fails the release if it cannot); `npm run build:api` then publish in `packages/crouter-api`; `npm run build:env-docker` then publish in `packages/crouter-env-docker`; `npm run build:api && npm run build:identity && npm run build:sdk` then publish in `packages/crouter-sdk`; `npm run build:api && npm run build:identity && npm run build:plugin` then `CRTR_PACK_MINIFY=1 npm publish` in `packages/crouter-plugin`. Use npm's interactive authentication/2FA where required (see `personal-operations/npm-2fa-cli`); a read-only token cannot publish. Provenance remains off because the source repository is private. Do not build or publish the Docker image in this no-Actions path.
8
- 4. Confirm all six exact versions with `npm view`. In a fresh temporary directory, `npm init -y && npm install @north-light/sdk@$VERSION`, then prove both `@north-light/sdk/errors` (`errorCodes`) and `@north-light/sdk/stores/postgres` resolve via `node --input-type=module -e '…'`. Record any partial publish and resume from the exact release tag rather than cutting another version.
6
+ 2. Run `node scripts/release-version.mjs "$VERSION"`. It sets all six manifests to `$VERSION`, pins every dependency between them (root → identity, SDK → api and identity, plugin → api and identity) to exactly `$VERSION` with no `^`, writes `SDK_VERSION` in `packages/crouter-sdk/src/version.ts` (informational: compatibility is the contract level), and renames the `## Next release` section of `docs/sdk/release-notes.md` to `## $VERSION`. One release is one version set: an app pinning `@north-light/sdk@$VERSION` gets exactly this release's api and identity. Run `npm install --package-lock-only --ignore-scripts`, then `node scripts/contract.mjs release`: it fails on an unacknowledged breaking `/v1` change or an undeclared watched file (the same check CI runs), then writes `contract/surface.txt`, `CONTRACT_LEVEL` in `src/api/contract.ts` (a minor bump only when the surface changed or a declaration says `bump`), `crouterContract` in the SDK manifest, and resets `contract/next.json`. Run `node scripts/release-version.mjs --check` (prints `one version set`). Check `git diff` and commit only the six manifests, `packages/crouter-sdk/src/version.ts`, `docs/sdk/release-notes.md`, `package-lock.json`, `contract/surface.txt`, `contract/next.json` and `src/api/contract.ts` as `chore: release v$VERSION`. Create `git tag -a "v$VERSION" -m "chore: release v$VERSION"`, then `git push --atomic origin HEAD:main "refs/tags/v$VERSION"`. If rejected due to a concurrent push, remove the *local* release tag, integrate fresh main, then recheck versions and retry; never push a tag separately.
7
+ 3. From the tagged release commit run `npm ci`. Publish in dependency order, checking `npm view "$PACKAGE@$VERSION" version` before each and skipping only on a positive sighting: `npm run build:identity` then `npm publish` in `packages/crouter-identity`; `npm run build` then `CRTR_PACK_MINIFY=1 npm publish` at root (Docker must be running: the pack cross-builds the Linux x64 and arm64 native addons into `dist/native/linux/<arch>/addon.node` and fails the release if it cannot); `npm run build:api` then publish in `packages/crouter-api`; `npm run build:env-docker` then publish in `packages/crouter-env-docker`; `npm run build:api && npm run build:identity && npm run build:sdk` then `npm publish --tag next` in `packages/crouter-sdk` (its `prepublishOnly` guard refuses any other form, including `--tag latest`; every SDK release lands on `next`); `npm run build:api && npm run build:identity && npm run build:plugin` then `CRTR_PACK_MINIFY=1 npm publish` in `packages/crouter-plugin`. Use npm's interactive authentication/2FA where required (see `personal-operations/npm-2fa-cli`); a read-only token cannot publish. Provenance remains off because the source repository is private. Do not build or publish the Docker image in this no-Actions path.
8
+ 4. Confirm all six exact versions with `npm view`. In a fresh temporary directory, `npm init -y && npm install @north-light/sdk@$VERSION`, then prove both `@north-light/sdk/errors` (`errorCodes`) and `@north-light/sdk/stores/postgres` resolve via `node --input-type=module -e '…'`. Record any partial publish and finish it from the exact release tag rather than cutting another version: `gh workflow run publish.yml -f release_tag=v$VERSION` runs `recover-release`, which verifies the tag is a release commit on main, checks it out, and publishes only the packages and image the registry does not yet show (SDK to `next`; it never writes the image `:latest`, so if the recovered tag is the newest release, `:latest` follows the next normal release).
9
+ 5. Promote `latest` last, through CI only: `gh workflow run publish.yml -f promote=true` runs the `promote-sdk` job, the one writer of `latest`. It shares the `sdk-dist-tag` concurrency group with the automatic runs (they queue, never overlap), reads the router's `/contract` itself, re-reads `latest` right before the write and moves it only if unchanged. It also runs after each SDK publish and hourly, once the npm trusted-publisher entry for `@north-light/sdk` has "Allow npm dist-tag" ticked. `PROD_CONTRACT_URL=<the router's /contract URL> node scripts/promote-sdk.mjs` is a read-only dry run; its `--apply` refuses outside Actions. **Never `npm dist-tag add` the SDK by hand.** The one exception is Actions fully down: a person then runs the printed target by hand through the npm 2FA wrapper only after re-reading `npm view @north-light/sdk dist-tags` and confirming no `promote-sdk` run is active (`gh run list --workflow publish.yml`) and that the target is above the current `latest`.
@@ -24,7 +24,7 @@ Every class below extends `APIError`, which carries `status`, `code`, `message`,
24
24
  | Condition | Class |
25
25
  |---|---|
26
26
  | SDK configuration error | `CrouterError` with `status: 0`, `code: 'crouter_error'` |
27
- | The runtime is older than the SDK, or names no version. The hosting router or the runtime can refuse this *before* handling (the SDK sends `Crouter-Minimum-Runtime-Version`): nothing was accepted and no idempotency key was recorded. A hosted runtime is always refused first once the hosting router has this check. Only a runtime that is not behind such a router and is older than the check release handles the request first, and then the SDK raises after | `RuntimeVersionError` with `status: 426`, `code: 'runtime_version_unsupported'`; `sdkVersion` and `runtimeVersion` (or `null`) name both, and it is never retryable. See [Release notes](./release-notes.md#the-sdk-refuses-a-runtime-older-than-itself-breaking) |
27
+ | The runtime does not serve the contract level the SDK requires, or names none. The hosting router or the runtime refuses this *before* handling (the SDK sends `Crouter-Required-Contract` and a frozen `Crouter-Minimum-Runtime-Version`): nothing was accepted and no idempotency key was recorded. A runtime that predates the contract level handles the request first, and the SDK then raises after | `RuntimeVersionError` with `status: 426`, `code: 'runtime_version_unsupported'`; `requiredContract` and `runtimeContract` (or `null`) name both levels, `sdkVersion` and `runtimeVersion` stay for logging, and it is never retryable. A level is `MAJOR.MINOR`; the runtime satisfies it when it serves the same major and a minor at or above. See [Release notes](./release-notes.md) |
28
28
  | HTTP 400 | `BadRequestError` |
29
29
  | HTTP 401 | `AuthenticationError` |
30
30
  | HTTP 403 | `PermissionDeniedError` |
@@ -25,6 +25,8 @@ npm i @north-light/sdk
25
25
 
26
26
  One dependency. It re-exports every data type you need, so you do not also install `@north-light/api`.
27
27
 
28
+ `@latest` always works against production: a release reaches `latest` only once production serves its contract level. The `next` tag carries every release the moment it is published and may need a newer runtime than production runs (see [Errors](./errors.md) for `RuntimeVersionError`). Pin an exact version in your app.
29
+
28
30
  ## 3. Node, on the owner's own daemon
29
31
 
30
32
  Pass nothing. The client resolves the same socket path the `crtr` CLI does, and starts the daemon if it is not already up.
@@ -7,6 +7,16 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
7
7
 
8
8
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
9
9
 
10
+ ## 0.3.423
11
+
12
+ ### Compatibility is a contract level, not the package version (additive)
13
+
14
+ The SDK no longer refuses a runtime because its release number is lower than the SDK's. It sends `Crouter-Required-Contract: <CONTRACT_LEVEL>` and refuses a runtime that does not serve that level (`MAJOR.MINOR`, same major, minor at or above). The level changes only when the `/v1` surface changes, so an ordinary release no longer makes production answer 426. `RuntimeVersionError` keeps `status` 426 and code `runtime_version_unsupported`, and gains `requiredContract` and `runtimeContract`; `sdkVersion` and `runtimeVersion` stay. Apps that catch the error need no change.
15
+
16
+ - Every SDK release is published to the `next` dist-tag; `latest` moves only once production serves its level. `@latest` therefore always works against production.
17
+ - The SDK now tolerates values it does not know: an unknown run event `type` is passed to your listener as-is, and an unknown run `status` ends `runs.wait` and the run watcher, reporting the raw status (`RunEnded.ended` can be `'unknown'`). Handle a status or event type you do not recognize rather than assuming the union is closed.
18
+ - `FakeCloud` takes `contract` and `routedContract` options and refuses an incompatible call at ingress, before any interceptor runs.
19
+
10
20
  ## 0.3.419
11
21
 
12
22
  ### Testing mode: `app_in_testing` (additive)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.422",
3
+ "version": "0.3.423",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -97,6 +97,9 @@
97
97
  "build:plugin": "tsc -p packages/crouter-plugin/tsconfig.json",
98
98
  "typecheck:pi-packages": "npx tsc -p src/builtin-pi-packages/pi-crtr-extensions/tsconfig.json",
99
99
  "test:runtime-packaging": "node --test scripts/runtime-smoke.test.mjs scripts/pack-args.test.mjs scripts/ensure-native-linux.test.mjs scripts/release-version.test.mjs",
100
+ "contract:check": "node scripts/contract.mjs check",
101
+ "contract:surface": "node scripts/contract.mjs generate",
102
+ "test:contract": "node --conditions=crtr-src --import tsx/esm --test scripts/contract.test.mjs scripts/promote-sdk.test.mjs scripts/release-version.test.mjs",
100
103
  "test:build-output": "node scripts/build-concurrency-test.mjs",
101
104
  "check:cli-no-canvas": "node scripts/check-cli-no-canvas.mjs",
102
105
  "check:cli-cold-start": "node scripts/check-cli-cold-start.mjs",
@@ -134,7 +137,7 @@
134
137
  },
135
138
  "license": "UNLICENSED",
136
139
  "dependencies": {
137
- "@north-light/identity": "0.3.422",
140
+ "@north-light/identity": "0.3.423",
138
141
  "@earendil-works/pi-agent-core": "1.1.0",
139
142
  "@earendil-works/pi-ai": "1.1.0",
140
143
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.422",
3
+ "version": "0.3.423",
4
4
  "description": "crouter cloud access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.422",
3
+ "version": "0.3.423",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.422",
9
+ "version": "0.3.423",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -18,7 +18,7 @@
18
18
  "@earendil-works/pi-ai": "1.1.0",
19
19
  "@earendil-works/pi-coding-agent": "1.1.0",
20
20
  "@earendil-works/pi-tui": "1.1.0",
21
- "@north-light/identity": "0.3.422",
21
+ "@north-light/identity": "0.3.423",
22
22
  "cron-parser": "^5.6.0",
23
23
  "esbuild": "^0.27.7",
24
24
  "jose": "^6.2.12",
@@ -12095,17 +12095,17 @@
12095
12095
  },
12096
12096
  "packages/crouter-api": {
12097
12097
  "name": "@north-light/api",
12098
- "version": "0.3.422",
12098
+ "version": "0.3.423",
12099
12099
  "license": "UNLICENSED"
12100
12100
  },
12101
12101
  "packages/crouter-env-docker": {
12102
12102
  "name": "@north-light/env-docker",
12103
- "version": "0.3.422",
12103
+ "version": "0.3.423",
12104
12104
  "license": "UNLICENSED"
12105
12105
  },
12106
12106
  "packages/crouter-identity": {
12107
12107
  "name": "@north-light/identity",
12108
- "version": "0.3.422",
12108
+ "version": "0.3.423",
12109
12109
  "license": "UNLICENSED",
12110
12110
  "dependencies": {
12111
12111
  "jose": "^6.2.1"
@@ -12113,20 +12113,20 @@
12113
12113
  },
12114
12114
  "packages/crouter-plugin": {
12115
12115
  "name": "@north-light/plugin",
12116
- "version": "0.3.422",
12116
+ "version": "0.3.423",
12117
12117
  "license": "UNLICENSED",
12118
12118
  "dependencies": {
12119
- "@north-light/api": "0.3.422",
12120
- "@north-light/identity": "0.3.422"
12119
+ "@north-light/api": "0.3.423",
12120
+ "@north-light/identity": "0.3.423"
12121
12121
  }
12122
12122
  },
12123
12123
  "packages/crouter-sdk": {
12124
12124
  "name": "@north-light/sdk",
12125
- "version": "0.3.422",
12125
+ "version": "0.3.423",
12126
12126
  "license": "UNLICENSED",
12127
12127
  "dependencies": {
12128
- "@north-light/api": "0.3.422",
12129
- "@north-light/identity": "0.3.422",
12128
+ "@north-light/api": "0.3.423",
12129
+ "@north-light/identity": "0.3.423",
12130
12130
  "jose": "^6.2.1"
12131
12131
  },
12132
12132
  "bin": {