@north-light/cli 0.3.444 → 0.3.446
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/builtin-memory/crouter-sdk/release-notes.md +6 -0
- package/dist/clients/attach/viewer.js +127 -127
- package/dist/core/audit.d.ts +23 -0
- package/dist/core/audit.js +3 -0
- package/dist/core/canvas/migrations.js +12 -12
- package/dist/core/graph/reader.js +3 -3
- package/dist/daemon/api/app-listener.js +12 -12
- package/dist/daemon/api/handlers/bash.d.ts +1 -1
- package/dist/daemon/api/handlers/bash.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/human-requests.d.ts +1 -1
- package/dist/daemon/api/handlers/human-requests.js +1 -1
- package/dist/daemon/api/handlers/messages.js +1 -1
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/operations.d.ts +1 -1
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/principal.d.ts +3 -1
- package/dist/daemon/api/principal.js +1 -1
- package/dist/daemon/api/router.d.ts +29 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/peer/handlers.d.ts +2 -0
- package/dist/daemon/peer/handlers.js +1 -0
- package/dist/daemon/peer/ingress.d.ts +34 -0
- package/dist/daemon/peer/ingress.js +1 -0
- package/docs/sdk/release-notes.md +6 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1 +1 @@
|
|
|
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};
|
|
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{peerRoutes as be}from"../peer/handlers.js";import{bashJobRoutes as Ee}from"./handlers/bash-jobs.js";import{bashRoutes as we}from"./handlers/bash.js";import{hookExecRoutes as Te}from"./handlers/hook-exec.js";import{brokerOperationRoutes as Q}from"./handlers/broker-ops.js";import{brokerSignalRoutes as Ce,brokerSignalStreams as Se}from"./handlers/broker-signals.js";import{brokerRecoveryRoutes as Y,nodeFaultRoutes as ke}from"./handlers/broker-recovery.js";import{canvasRoutes as _e}from"./handlers/canvas.js";import{chatInventoryRoutes as Oe}from"./handlers/chat-inventory.js";import{daemonRoutes as Ue}from"./handlers/daemon.js";import{fileRoutes as He}from"./handlers/files.js";import{focusRoutes as Ne}from"./handlers/focus.js";import{analyticsRoutes as ze}from"./handlers/analytics.js";import{memoryReadsRoutes as Pe}from"./handlers/memory-reads.js";import{memoryRoutes as Ie}from"./handlers/memory.js";import{healthRoutes as xe}from"./handlers/health.js";import{humanRequestRoutes as $e}from"./handlers/human-requests.js";import{inboxRoutes as Le}from"./handlers/inbox.js";import{feedbackCommentRoutes as Be}from"./handlers/feedback-comments.js";import{objectRoutes as Me}from"./handlers/objects.js";import{customObjectRoutes as je}from"./handlers/custom-objects.js";import{docRoutes as De}from"./handlers/docs.js";import{deliveryRoutes as Z}from"./handlers/delivery.js";import{deliveryRetryRoutes as Ge}from"./handlers/delivery-retry.js";import{packageDocRoutes as We}from"./handlers/package-docs.js";import{brokerMailRoutes as K,messageRoutes as Je}from"./handlers/messages.js";import{modelAuthRoutes as Fe}from"./handlers/modelauth.js";import{llmRoutes as Ve}from"./handlers/llm.js";import{providerRoutes as Qe}from"./handlers/providers.js";import{modelConfigRoutes as Ye}from"./handlers/model-config.js";import{nodeRoutes as Ze}from"./handlers/nodes.js";import{nodeRecordRoutes as Ke}from"./handlers/node-records.js";import{nodeOutcomeRoutes as Xe}from"./handlers/node-outcomes.js";import{nodeEventRoutes as qe}from"./handlers/node-events.js";import{profileRoutes as er}from"./handlers/profiles.js";import{prospectiveChatInventoryRoutes as rr}from"./handlers/prospective-chat-inventory.js";import{reportRoutes as tr}from"./handlers/reports.js";import{runRoutes as or}from"./handlers/run-routes.js";import{reviewCommentRoutes as nr}from"./handlers/review-comments.js";import{reviewRoutes as ir}from"./handlers/reviews.js";import{subscriptionRoutes as sr}from"./handlers/subscriptions.js";import{structuredRequestRoutes as lr}from"./handlers/structured-requests.js";import{cronRoutes as ar}from"./handlers/crons.js";import{setCronRequestHandler as X}from"../cron/run-socket.js";import{worktreeRoutes as ur}from"./handlers/worktree.js";import{limitsRoutes as cr}from"./handlers/limits.js";function pr(t){return new V().registerAll(xe(t)).registerAll(Ue).registerAll(Ze).registerAll(Ke).registerAll(Xe).registerAll(Ge).registerAll(qe).registerAll(Ee).registerAll(we).registerAll(Te).registerAll(Q).registerAll(Ce).registerAll(Z).registerAll(Y).registerAll(ke).registerAll(ur).registerAll(cr).registerAll(K).registerAll(Je).registerAll(tr).registerAll(or).registerAll(lr).registerAll(ve).registerAll(_e).registerAll(ze).registerAll(Pe).registerAll(Ie).registerAll(He).registerAll(Me).registerAll(je).registerAll(De).registerAll(We).registerAll(Ne).registerAll(sr).registerAll(ar).registerAll(er).registerAll(Fe).registerAll(Ve).registerAll(Qe).registerAll(Ye).registerAll(ir).registerAll(nr).registerAll(Le).registerAll($e).registerAll(Oe).registerAll(rr).registerAll(Be).registerAll(be)}a(pr,"buildRouter");function dr(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(dr,"attachNodeId");function mr(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(mr,"parseTcp");function fr(t,n){return`${t.includes(":")?`[${t}]`:t}:${n}`}a(fr,"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 gr(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(gr,"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 hr(t,n){t.writeHead(503,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:{code:"startup_blocked",message:n}}))}a(hr,"writeStartupBlocked");function oe(t){return new Promise(n=>{try{t.closeAllConnections()}catch{}t.close(()=>n())})}a(oe,"closeServer");function _t(t={}){let n=null;const s=pr(()=>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){hr(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=dr(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=mr(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:gr(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=fr(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),Se.close(),await Promise.all([oe(E),h!==void 0?oe(h):Promise.resolve()]);try{U(m)&&D(m)}catch{}}},"close")}}a(_t,"createApiServer");export{pr as buildRouter,_t as createApiServer,q as socketPrincipal};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var n=Object.defineProperty;var o=(t,r)=>n(t,"name",{value:r,configurable:!0});import{ApiError as p}from"../../api/errors.js";const e=o(t=>()=>{throw new p(501,"not_implemented",`${t} is not implemented on this machine yet`)},"notImplemented"),d=[{method:"POST",pattern:"/v1/peer/events",handler:e("peer.events")},{method:"POST",pattern:"/v1/peer/outbox/claim",handler:e("peer.outbox.claim")},{method:"POST",pattern:"/v1/peer/outbox/ack",handler:e("peer.outbox.ack")},{method:"POST",pattern:"/v1/peer/locate",handler:e("peer.locate")}];export{d as peerRoutes};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type JwksCache, type MachineTokenClaims } from '@north-light/identity';
|
|
3
|
+
import { Router, type RequestPrincipal } from '../api/router.js';
|
|
4
|
+
export interface PeerIngressContext {
|
|
5
|
+
/** The directory the machine tokens come from. */
|
|
6
|
+
issuer: string;
|
|
7
|
+
/** Its key set (the app listener's cache, or the laptop's own). */
|
|
8
|
+
jwks: JwksCache;
|
|
9
|
+
/** The audience machine tokens must carry: `machineUrl()` on the laptop, `runtime.audience` on the hosted runtime. Null before it is known: every request is refused 503. */
|
|
10
|
+
audience(): string | null;
|
|
11
|
+
/** The account owner (`user:<id>`) this runtime belongs to. Null when it has none: every request is refused 401. */
|
|
12
|
+
owner(): string | null;
|
|
13
|
+
/** This machine's id (`runtime:<id>`). */
|
|
14
|
+
self(): string;
|
|
15
|
+
/** The router that serves the request; defaults to the daemon's own (`buildRouter(() => null)`), built on first use. */
|
|
16
|
+
router?: () => Router | Promise<Router>;
|
|
17
|
+
/** False while the JWKS or grants are not loaded: every request is refused 503 (audited). Defaults to ready. */
|
|
18
|
+
ready?: () => boolean;
|
|
19
|
+
}
|
|
20
|
+
/** The principal a verified token plus envelope yields, or the refusal. The checks are the C2 row: the envelope's `act.sub` is the token's `machine_id`, its `sub` the token's `sub`. */
|
|
21
|
+
export declare function peerPrincipal(claims: Pick<MachineTokenClaims, 'sub' | 'machine_id'>, envelopeHeader: string | undefined): {
|
|
22
|
+
principal: RequestPrincipal & {
|
|
23
|
+
class: 'forwarded' | 'machine';
|
|
24
|
+
};
|
|
25
|
+
} | {
|
|
26
|
+
status: number;
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
/** The account owner this runtime belongs to: the owner a successful `crtr sys join` recorded (a laptop), else — on a hosted runtime, which is
|
|
31
|
+
* provisioned without join — the one `sub` its mirrored grants all carry. Null when neither names exactly one owner. Never throws. */
|
|
32
|
+
export declare function runtimeOwner(): string | null;
|
|
33
|
+
/** Serve one peer request. Never throws. Every answer writes one `audit_rows` row: a request whose token never verified is recorded with `via` = `unauthenticated` and no identity. */
|
|
34
|
+
export declare function servePeer(req: IncomingMessage, res: ServerResponse, ctx: PeerIngressContext): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var S=Object.defineProperty;var s=(e,n)=>S(e,"name",{value:n,configurable:!0});import{randomUUID as q}from"node:crypto";import{verifyMachineToken as j}from"@north-light/identity";import{decodeEnvelope as A,ENVELOPE_HEADER as C,EnvelopeError as H}from"../../core/peer/envelope.js";import{peerVersionRefusal as I,PEER_VERSION_HEADER as P}from"../../core/peer/version.js";import{auditOutcome as k,writePeerAudit as U}from"../../core/audit.js";import{emitEvent as R}from"../../core/events/emit.js";import{joinedOwner as D}from"../../core/directory/join.js";import{allGrants as L}from"../../core/grants/mirror.js";import{errorCodes as N}from"../../api/error-codes.js";let z;function T(){return z??=import("../api/server.js").then(e=>e.buildRouter(()=>null)),z}s(T,"defaultRouter");function V(e,n,r,t,u,c){if(e.headersSent){e.destroy();return}const p=N[r];e.writeHead(n,{"content-type":"application/json; charset=utf-8","Request-Id":u,"X-Content-Type-Options":"nosniff"}),e.end(JSON.stringify({error:{code:r,message:t,...c===void 0?{}:{details:c},...p?{type:p.type}:{},request_id:u}}))}s(V,"refuse");const _=s((e,n)=>{const r=e.headers[n.toLowerCase()];return Array.isArray(r)?r[0]:r},"header");function G(e,n){if(n===void 0)return{principal:{class:"machine",via:e.machine_id,sub:e.sub}};let r;try{r=A(n)}catch(u){return{status:400,code:"invalid_request",message:u instanceof H?u.message:"invalid envelope"}}if(r.act.sub!==e.machine_id||r.sub!==e.sub)return{status:401,code:"unauthorized",message:"the envelope does not match the machine token"};const t=r.act.act;return t===void 0?{principal:{class:"forwarded",via:e.machine_id,sub:e.sub,actor:{kind:"person"}}}:t.place===null?{status:400,code:"invalid_request",message:"the envelope names an app with no run place"}:{principal:{class:"forwarded",via:e.machine_id,sub:e.sub,actor:{kind:"app",app:t.sub,node:t.node,node_scopes:t.node_scopes,place:t.place}}}}s(G,"peerPrincipal");function Z(){try{const e=D();if(e!==null)return e;const n=new Set(L().map(r=>r.sub));return n.size===1?[...n][0]:null}catch{return null}}s(Z,"runtimeOwner");async function x(e,n,r){const t=n.getHeader("Request-Id")?.toString()??q();n.setHeader("Request-Id",t);const u=_(e,C),c=new URL(e.url??"/","http://peer.local"),p=(e.method??"GET").toUpperCase();let d={class:u===void 0?"machine":"forwarded",grantee:null,sub:null,via:"unauthenticated",operation:`${p} ${c.pathname}`,node_id:null},v=!1;const b=s((a,l={})=>{if(!v){v=!0;try{U({request_id:t,resource:c.pathname,...d,...l,outcome:a})}catch(o){R({level:"error",event:"daemon.peer.audit_failed",error:o instanceof Error?o:new Error(String(o)),fields:{request_id:t}})}}},"record"),i=s((a,l,o,m)=>{b(k(a)),V(n,a,l,o,t,m)},"deny");try{if(r.ready&&!r.ready()){i(503,"runtime_starting","runtime_starting");return}const a=_(e,"authorization"),l=r.audience();if(l===null){i(503,"runtime_starting","this machine does not yet know its own address");return}if(!a?.startsWith("Bearer ")||a.length<=7){i(401,"unauthorized","a machine token is required");return}let o;try{o=await j({token:a.slice(7),jwks:r.jwks,issuer:r.issuer,audience:l})}catch{i(401,"unauthorized","invalid machine token");return}d={...d,grantee:o.sub,sub:o.sub,via:o.machine_id};const m=r.owner();if(m===null||o.sub!==m){i(401,"unauthorized","the machine token is for another account");return}if(o.machine_id===r.self()){i(401,"unauthorized","a machine does not call itself");return}const g=await(r.router??T)(),y=g.match(p,c.pathname);d={...d,operation:y?.operation.name??d.operation,node_id:y?.params.id??null};const h=I(_(e,P));if(h!==null){i(426,h.code,h.message,h.details);return}const f=G(o,u);if("status"in f){i(f.status,f.code,f.message);return}const{principal:w}=f,E=w.class==="forwarded"&&w.actor.kind==="app"?w.actor:null,O={grantee:E?.app??o.sub,node_id:E?.node??d.node_id};n.once("close",()=>b(n.writableFinished?k(n.statusCode):"aborted",O)),await g.handle(e,n,null,w)}catch(a){R({level:"error",event:"daemon.peer.ingress_failed",error:a instanceof Error?a:new Error(String(a)),fields:{request_id:t}}),i(500,"server_failure","peer request failed")}}s(x,"servePeer");export{G as peerPrincipal,Z as runtimeOwner,x as servePeer};
|
|
@@ -7,6 +7,12 @@ 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.445
|
|
11
|
+
|
|
12
|
+
### Peer requests between an account's machines (additive, no app change)
|
|
13
|
+
|
|
14
|
+
A runtime now serves requests from another of the account's machines (a machine token plus a `Crtr-Envelope`). Nothing an app sends or reads changes: an app grant token takes the same path as before. The router gains a `forwarded` caller class on the object, node-read, message and canvas read rows, four machine-only `peer.*` rows that answer `501 not_implemented` until they ship, and two new refusal codes on peer requests only (`app_not_granted`, `peer_version_too_old`).
|
|
15
|
+
|
|
10
16
|
## 0.3.423
|
|
11
17
|
|
|
12
18
|
### Compatibility is a contract level, not the package version (additive)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.446",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
},
|
|
138
138
|
"license": "UNLICENSED",
|
|
139
139
|
"dependencies": {
|
|
140
|
-
"@north-light/identity": "0.3.
|
|
140
|
+
"@north-light/identity": "0.3.446",
|
|
141
141
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
142
142
|
"@earendil-works/pi-ai": "1.1.0",
|
|
143
143
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.446",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.446",
|
|
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.
|
|
21
|
+
"@north-light/identity": "0.3.446",
|
|
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.
|
|
12098
|
+
"version": "0.3.446",
|
|
12099
12099
|
"license": "UNLICENSED"
|
|
12100
12100
|
},
|
|
12101
12101
|
"packages/crouter-env-docker": {
|
|
12102
12102
|
"name": "@north-light/env-docker",
|
|
12103
|
-
"version": "0.3.
|
|
12103
|
+
"version": "0.3.446",
|
|
12104
12104
|
"license": "UNLICENSED"
|
|
12105
12105
|
},
|
|
12106
12106
|
"packages/crouter-identity": {
|
|
12107
12107
|
"name": "@north-light/identity",
|
|
12108
|
-
"version": "0.3.
|
|
12108
|
+
"version": "0.3.446",
|
|
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.
|
|
12116
|
+
"version": "0.3.446",
|
|
12117
12117
|
"license": "UNLICENSED",
|
|
12118
12118
|
"dependencies": {
|
|
12119
|
-
"@north-light/api": "0.3.
|
|
12120
|
-
"@north-light/identity": "0.3.
|
|
12119
|
+
"@north-light/api": "0.3.446",
|
|
12120
|
+
"@north-light/identity": "0.3.446"
|
|
12121
12121
|
}
|
|
12122
12122
|
},
|
|
12123
12123
|
"packages/crouter-sdk": {
|
|
12124
12124
|
"name": "@north-light/sdk",
|
|
12125
|
-
"version": "0.3.
|
|
12125
|
+
"version": "0.3.446",
|
|
12126
12126
|
"license": "UNLICENSED",
|
|
12127
12127
|
"dependencies": {
|
|
12128
|
-
"@north-light/api": "0.3.
|
|
12129
|
-
"@north-light/identity": "0.3.
|
|
12128
|
+
"@north-light/api": "0.3.446",
|
|
12129
|
+
"@north-light/identity": "0.3.446",
|
|
12130
12130
|
"jose": "^6.2.1"
|
|
12131
12131
|
},
|
|
12132
12132
|
"bin": {
|