@north-light/cli 0.3.482 → 0.3.483
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/api/dto/grants.d.ts +6 -4
- package/dist/builtin-memory/crouter-sdk/release-notes.md +10 -0
- package/dist/builtin-memory/northlight-apps/concepts/providers.md +4 -3
- package/dist/builtin-memory/northlight-apps/guides/grant-changes.md +11 -9
- package/dist/clients/attach/viewer.js +1 -1
- package/dist/core/grants/mirror.d.ts +1 -1
- package/dist/core/grants/mirror.js +2 -2
- package/docs/apps/concepts/providers.md +4 -3
- package/docs/apps/guides/grant-changes.md +11 -9
- package/docs/sdk/release-notes.md +10 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
package/dist/api/contract.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
|
|
2
|
-
export declare const CONTRACT_LEVEL = "1.
|
|
2
|
+
export declare const CONTRACT_LEVEL = "1.9";
|
|
3
3
|
/** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
|
|
4
4
|
export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
|
|
5
5
|
/** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
|
package/dist/api/contract.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.
|
|
1
|
+
var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.9",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
|
package/dist/api/dto/grants.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
/** A provider's setup state for the account (directory spec, "Provider setup state"). */
|
|
2
|
-
export type SetupState = 'not_started' | 'pending' | 'ready' | 'failed';
|
|
1
|
+
/** A provider's setup state for the account (directory spec, "Provider setup state"). `unavailable` means the provider does not offer this setup to the account; it is not `failed`, and there is nothing for the person to do. */
|
|
2
|
+
export type SetupState = 'not_started' | 'pending' | 'ready' | 'failed' | 'unavailable';
|
|
3
3
|
/** One provider the grantee requires, as the directory's grant sync carried it. */
|
|
4
4
|
export interface ProviderDependency {
|
|
5
5
|
state: SetupState;
|
|
6
|
-
/** The provider's own sentence for `pending` and `
|
|
6
|
+
/** The provider's own sentence for `pending`, `failed` and `unavailable` (its `message_pending` / `message_failed` / `message_unavailable`); absent for `not_started` and `ready`. */
|
|
7
7
|
message?: string;
|
|
8
|
+
/** Where in its journey the provider is, when it reports one (for example `researching`, or `downloaded` on `device`). The values belong to the provider and may grow, so treat an unknown one as no step. Absent when the provider reports none. Changes `version` like any other field. */
|
|
9
|
+
step?: string;
|
|
8
10
|
/** Absolute URL of the directory's setup page for this provider and this app (`<issuer>/connect/setup/<provider>?client_id=<grantee>`); the app may append `return_to` (its own registered redirect origin) so Continue brings the person back. */
|
|
9
11
|
next: string;
|
|
10
12
|
}
|
|
@@ -21,7 +23,7 @@ export interface GrantGetDTO {
|
|
|
21
23
|
}[];
|
|
22
24
|
spend_cap: number | null;
|
|
23
25
|
payer: 'user';
|
|
24
|
-
/** `{ <
|
|
26
|
+
/** `{ <setup id>: ProviderDependency }` (a setup id is a provider id such as `context`, or a dotted level such as `context.deep`) as the directory's grant sync carried it; `{}` when the grantee has none. The daemon performs no `requires` check: an app follows this with the SDK grant watch (`oauth.watchGrants`, or `grant.get` with `wait`/`since`) and refuses its own operation with `provider_setup_pending`. When an entry's `state` is not `ready`, show its `message` (the provider's own sentence, when it has one) and send the person to its `next` (the directory's setup page; not for `unavailable`, where there is nothing for them to do) — never copy the provider's prose into the app. */
|
|
25
27
|
dependencies: Record<string, ProviderDependency>;
|
|
26
28
|
/** Opaque; changes exactly when `rev`, `resolved_scopes`, `spend_cap`, `payer` or `dependencies` changes (never on a periodic sync alone or a new token). Pass it back as `since` with `wait` to hold the read until it changes. */
|
|
27
29
|
version: string;
|
|
@@ -9,6 +9,16 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
|
|
|
9
9
|
|
|
10
10
|
Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
|
|
11
11
|
|
|
12
|
+
## 0.3.483
|
|
13
|
+
|
|
14
|
+
### `unavailable`, `step` and `setupLink` on `grant.get` dependencies (additive for the SDK; a type change for exhaustive switches)
|
|
15
|
+
|
|
16
|
+
- `SetupState` gains `unavailable`: the provider does not offer this setup to the person's account. It is not `failed`. If you `switch` over `state` exhaustively, add a case; show `message` and do not link `next`.
|
|
17
|
+
- `ProviderDependency` gains an optional `step: string` (for example `researching`, or `downloaded` on `device`). Its values belong to the provider and may grow: ignore one you do not know. A change to `step` alone changes `grant.get`'s `version`, so `oauth.watchGrants` delivers it.
|
|
18
|
+
- `dependencies` keys may be dotted setup ids such as `context.deep`.
|
|
19
|
+
- New `setupLink(dependency, {returnTo})` returns `dependency.next` with `return_to` set and URL-encoded, keeping the query `next` already has (such as `client_id`). Use it instead of building the link yourself.
|
|
20
|
+
- A runtime older than this release drops a dependency whose state is `unavailable` and silently loses `step`, so an app that reads either needs a runtime on this release.
|
|
21
|
+
|
|
12
22
|
## 0.3.482
|
|
13
23
|
|
|
14
24
|
### `runs.start` accepts `deadline` (additive, no app change)
|
|
@@ -52,11 +52,11 @@ A call is refused when the scope is missing from the grant. The refusal is an `A
|
|
|
52
52
|
|
|
53
53
|
Some apps do not work until a provider is set up for the person. Name it under **What your app requires** on the **What it asks for** tab. The person is then taken through that provider's setup the first time they sign in. Context, for example, asks for a profile URL, and its state stays `pending` for a few minutes afterwards.
|
|
54
54
|
|
|
55
|
-
Read the state on each request that depends on it. `runtime.grant.get()` returns `dependencies`, one entry per
|
|
55
|
+
Read the state on each request that depends on it. `runtime.grant.get()` returns `dependencies`, one entry per setup id the app requires or lists as optional, plus any the directory implies (such as `device` for `context.deep`), keyed by setup id (or `{}` when it has none):
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
58
|
const {dependencies} = await runtime.grant.get();
|
|
59
|
-
const context = dependencies['context']; // {state, message?, next} or undefined
|
|
59
|
+
const context = dependencies['context']; // {state, message?, next, step?} or undefined
|
|
60
60
|
if (context?.state !== 'ready') {
|
|
61
61
|
// Show context.message and link context.next; never hard-code either.
|
|
62
62
|
}
|
|
@@ -68,8 +68,9 @@ if (context?.state !== 'ready') {
|
|
|
68
68
|
| `pending` | The provider is working. |
|
|
69
69
|
| `ready` | Calls that depend on it work. |
|
|
70
70
|
| `failed` | Setup failed. `message` says why. |
|
|
71
|
+
| `unavailable` | The provider does not offer this to the person's account. `message` says so; there is nothing for them to do. |
|
|
71
72
|
|
|
72
|
-
`message` is the provider's own sentence, present for `pending` and `
|
|
73
|
+
`message` is the provider's own sentence, present for `pending`, `failed` and `unavailable`. `step` is present when the provider reports where things stand (for example `researching`); its values may grow, so ignore one you do not know. `next` is the absolute URL of the setup page for this provider and your app; `setupLink(dependency, {returnTo})` from `@north-light/sdk` adds a `return_to` with your app's registered origin so the person returns to you, and keeps the query `next` already carries. A key can contain a dot (`context.deep`). An entry is missing when the runtime has not synced the grant since the app began requiring the provider: treat that as `not_started`. The runtime does not block your calls for you, so refuse the dependent operation yourself. To act the moment a state turns `ready`, follow it with `oauth.watchGrants`; see [[northlight-apps/guides/grant-changes]].
|
|
73
74
|
|
|
74
75
|
## Checked and not checked
|
|
75
76
|
|
|
@@ -41,31 +41,33 @@ await watcher.stop();
|
|
|
41
41
|
|
|
42
42
|
## Wait on a provider your app requires
|
|
43
43
|
|
|
44
|
-
An app whose registration `requires` a provider (the console's **What your app requires**) needs that provider set up for the person before the operations that depend on it work. The runtime does not check this for you. `grant.get()` returns `dependencies`: one entry per
|
|
44
|
+
An app whose registration `requires` a provider (the console's **What your app requires**) needs that provider set up for the person before the operations that depend on it work. The runtime does not check this for you. `grant.get()` returns `dependencies`: one entry per setup id the app requires or lists as optional (plus any the directory implies, such as `device` for `context.deep`), keyed by setup id, or `{}` when it has none. Read it on each request that needs the provider; do not store it.
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
|
-
import type
|
|
47
|
+
import {setupLink, type ProviderDependency} from '@north-light/sdk';
|
|
48
48
|
|
|
49
|
-
// ProviderDependency is { state: 'not_started' | 'pending' | 'ready' | 'failed'; message?: string; next: string }.
|
|
49
|
+
// ProviderDependency is { state: 'not_started' | 'pending' | 'ready' | 'failed' | 'unavailable'; message?: string; next: string; step?: string }.
|
|
50
50
|
const {dependencies} = await runtime.grant.get();
|
|
51
51
|
const context: ProviderDependency | undefined = dependencies['context'];
|
|
52
52
|
if (context?.state !== 'ready') {
|
|
53
53
|
// Show the provider's own sentence and link its setup page; never hard-code either.
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// Nothing to link when the provider does not offer this to the person (`unavailable`).
|
|
55
|
+
const next = context && context.state !== 'unavailable' ? setupLink(context, {returnTo: 'https://app.example.com'}) : undefined; // your registered redirect origin
|
|
56
56
|
throw new AppError('provider_setup_pending', context?.message ?? 'Finish setting up Context, then try again.', {
|
|
57
57
|
status: 403,
|
|
58
58
|
retryable: false,
|
|
59
|
-
details: {provider: 'context', state: context?.state ?? 'not_started', message: context?.message, next
|
|
59
|
+
details: {provider: 'context', state: context?.state ?? 'not_started', message: context?.message, next},
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
`AppError` stands for your app's own typed error ([[northlight-apps/errors]]).
|
|
65
65
|
|
|
66
|
-
- `state` is `not_started` (the person has not done their part), `pending` (the provider is working), `ready` or `
|
|
67
|
-
- `message` is the provider's own sentence for `pending` and `
|
|
68
|
-
- `
|
|
66
|
+
- `state` is `not_started` (the person has not done their part), `pending` (the provider is working), `ready`, `failed`, or `unavailable` (the provider does not offer this to the person's account; there is nothing for them to do, so do not link `next`).
|
|
67
|
+
- `message` is the provider's own sentence for `pending`, `failed` and `unavailable`. It is absent for `not_started` and `ready`, so keep a short fallback.
|
|
68
|
+
- `step` is present when the provider reports where the person or the work is, for example `researching`. Its values belong to the provider and may grow: show a known one in your own words and ignore an unknown one. A change to `step` alone changes the grant's `version`, so `watchGrants` delivers it.
|
|
69
|
+
- `next` is the absolute URL of the directory's setup page for this provider and your app. Use `setupLink(dependency, {returnTo})` to add `return_to` (your app's registered redirect origin) so the person comes back to you. It keeps the query `next` already has, such as `client_id`, and encodes `returnTo`; do not build the URL yourself.
|
|
70
|
+
- A key can contain a dot. `context.deep` is a level of the `context` provider; read it as `dependencies['context.deep']`.
|
|
69
71
|
- An entry is missing when the runtime has not synced the grant since the app began requiring the provider. Treat it as `not_started`.
|
|
70
72
|
|
|
71
73
|
Refuse the dependent operation with `provider_setup_pending` (403), carrying `state`, `message` and `next` in `details`. To act when the state turns `ready`, use `watchGrants` above instead of polling.
|
|
@@ -1729,7 +1729,7 @@ ${u}`,"1").then(l=>{let d=a.options[Number.parseInt(l,10)-1]?.id;if(d===void 0)t
|
|
|
1729
1729
|
`));return}s(E),i.login(p,"oauth",_).then(()=>{m||(r(`Logged in to ${f.name}`),n(),y())}).catch(C).finally(()=>s(void 0))}else w.showPrompt(`Enter API key for ${f.name}:`,"sk-...").then(async I=>{if(!m){if(I.trim()){if(!E())return;await f_n(p,{type:"api_key",key:I.trim()},o),r(`API key saved for ${f.name}`),n()}else r("No key entered \u2014 login cancelled");y()}}).catch(()=>y())},d=new iS("login",a,l,()=>c());return{component:d,focus:d}}}async function W_n(t,e,n){let{runtime:r,authPath:i}=await F_n(t),o=await U_n(r);return({close:s})=>{let a=u=>{let l=o.find(d=>d.id===u);(async()=>{try{RS(u)?await T_n(u):await zJe(u,i),s(),n(`Logged out of ${l?.name??u}`),e()}catch(d){s(),n(`Logout error: ${d instanceof Error?d.message:String(d)}`)}})()},c=new iS("logout",o,a,()=>s());return{component:c,focus:c}}}var H_n=h(()=>{"use strict";js();VJe();D_n();Vi();O_n();Khe();B_n();uc()});function j_n(t,e){return qxn({nodeId:t.nodeId,profileId:t.meta.profile_id??void 0,remote:t.remote,role:t.role,isBrokerOffline:e})}function q_n(t,e,n,r){let{nodeId:i,socket:o,tui:s,editor:a,containers:c}=t,{setNotice:u}=r,l=()=>o.send({type:"reload_auth"}),d=n.permits("auth")?{openLoginPicker:()=>{let f=r.agentDir()??GR();p.openAuthPicker(()=>Q_n(s,f,l,m=>u(m)))},openLogoutPicker:()=>{let f=r.agentDir()??GR();p.openAuthPicker(()=>W_n(f,l,m=>u(m)))}}:{},p=new qhe(s,a,e,{onCommand:f=>o.send(f),onDialogResponse:f=>o.send(f),onNotice:f=>u(f),onGraph:r.toggleGraph,onInbox:r.toggleInbox,onHelp:r.toggleHelp,onNodeMetadata:r.openNodeMetadata,onQuit:r.quit,onCopy:()=>r.copyTarget("last-assistant"),onCopyTarget:r.copyTarget,onToggleMouse:()=>{let f=r.toggleMouse();u(f?"Mouse: wheel scrolling on, drag to select and copy":"Mouse: off \u2014 tmux drag-select and copy-mode restored")},onColor:r.setChipColor,onPromptSent:r.onPromptSent,frecency:r.frecency,knownRefNames:()=>a.getResolvedRefNames(),onToggleToolsExpand:()=>{let f=r.toggleToolsExpanded();u(f.kind==="summaries"?f.shown?"Tool calls: summarized":"Tool calls: shown":f.expanded?"Tool output: expanded":"Tool output: collapsed")},onFoldTools:()=>u(r.toggleFoldTools()?"Finished tool calls: folded to one line":"Finished tool calls: shown in full"),onShowWorking:()=>{let f=r.toggleShowWorking();u(f===null?"Consolidated replies are off (crtr sys config set consolidate_replies --value true, then re-attach)":f?"Working: shown":"Working: hidden behind each reply")},onToggleThinking:()=>u(r.toggleThinking()?"Thinking blocks: hidden":"Thinking blocks: visible"),onRequest:f=>{let m=n.refusal("engine-drive");return m===void 0?o.request(f):Promise.reject(new Error(m))},onMountPicker:f=>{c.pickerPanel.addChild(f),s.requestRender()},onUnmountPicker:()=>{c.pickerPanel.clear(),s.requestRender()},isDormant:r.isDormant,onDormantSubmit:n.permits("wake-dormant")?f=>{(async()=>{try{let m=await Vt().sendMessage(i,{body:f,from:"human"});u(m.revived?"\u21BB waking node\u2026":"\u2709 delivered \u2014 node reads on next wake")}catch(m){u(`Couldn't wake node: ${m instanceof Error?m.message:String(m)}`)}})()}:void 0,...d},n);return p}var G_n=h(()=>{"use strict";p_n();mhe();uc();H_n();Yz()});async function L6(t,e){await Vt().respondHumanRequest(t,{responses:e})}async function z_n(t,e){await Vt().dismissHumanRequest(t,e)}function V_n(t){if(t instanceof ln)return t.status===503||t.code==="daemon_unavailable";let e=t?.code;return e==="ECONNREFUSED"||e==="ENOENT"}function wu(t){return V_n(t)?"could not reach crtrd \u2014 nothing was submitted":t instanceof ln||t instanceof Error?t.message:String(t)}function B6(t){return V_n(t)?"unreachable":t instanceof ln&&t.status===409&&t.code==="already_settled"?"already_settled":"other"}var iU=h(()=>{"use strict";ud();uc()});var K_n=h(()=>{"use strict";Rr()});function Y_n(t){let e=t?.diagnostics;return Array.isArray(e)&&e.some(n=>n?.type===Shi)}var Shi,J_n=h(()=>{"use strict";Shi="crtr:managed-provider-cooling"});function rXe(t){return typeof t=="string"?t:""}function hf(t,e,n={}){return{kind:t,matchedOn:e,...n}}function Zg(t,e){return e.matchedOn==="text"?{...t,textKind:e.kind,textMatch:e.match,textMatches:e.textMatches}:t}function X_n(t){let e=Chi.flatMap(([r,i])=>{let o=t.match(i);return o?[{kind:r,match:o[0]}]:[]}),n=e[0];return n===void 0?hf("other","none"):{...hf(n.kind,"text",{match:n.match}),textMatches:e}}function Z_n(t){let e=t.retryAfterMs,n=X_n(t.message??""),r=rXe(t.code)||rXe(t.errno);if(/^(ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOENT|ENOTFOUND|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH)$/i.test(r))return Zg(hf("connection","errno",{retryAfterMs:e}),n);if(/^ELOCKED$/i.test(r))return Zg(hf("overloaded","errno",{retryAfterMs:e}),n);let i=typeof t.status=="number"?t.status:void 0;if(i!==void 0){if(i===401||i===403)return Zg(hf("auth","status",{status:i,retryAfterMs:e}),n);if(i===429)return Zg(hf("rate-limit","status",{status:i,retryAfterMs:e}),n);if(i===500||i===502||i===503||i===504)return Zg(hf("transient-http","status",{status:i,retryAfterMs:e}),n);if(i>=500&&i<=599)return Zg(hf("overloaded","status",{status:i,retryAfterMs:e}),n);if(i>=400&&i<=499)return Zg(hf("protocol","status",{status:i,retryAfterMs:e}),n)}let o=typeof t.wsCloseCode=="number"?t.wsCloseCode:void 0;if(o===1008)return Zg(hf("protocol","websocket-close",{retryAfterMs:e}),n);if(o===1006)return Zg(hf("connection","websocket-close",{retryAfterMs:e}),n);if(o===1011){let s=rXe(t.wsCloseReason),a=X_n(s);return/no running broker/i.test(s)||a.kind==="connection"||a.kind==="connection-only"?Zg(hf("connection","websocket-close",{retryAfterMs:e}),n):Zg(hf("other","websocket-close",{retryAfterMs:e}),n)}return e===void 0?n:{...n,retryAfterMs:e}}var Chi,evn=h(()=>{"use strict";Chi=[["rate-limit",/rate.?limit|\b429\b|too many requests|usage[ _]limit|usage_not_included|usagelimiterror|insufficient_quota|quota exceeded/i],["protocol",/\b413\b|payload too large|entity too large|request too large|entity\.too\.large|body exceeds/i],["auth",/invalid_grant|refresh token|unauthori[sz]ed|invalid.?api.?key|authentication failed|\b401\b|\b403\b/i],["context-overflow",/context overflow recovery failed/i],["protocol",/websocket.*(?:closed.*\b(?:1002|1003|1007|1008|1009|1010)\b|protocol error|policy violation|unsupported data|invalid (?:frame|payload)|message too big|mandatory extension)/i],["overloaded-only",/lock file is already being held/i],["overloaded",/overloaded|temporarily unavailable/i],["service-unavailable",/service.?unavailable/i],["overloaded-only",/internal server error|api.?error|\b5\d\d\b|capacity|server.{0,3}busy/i],["connection",/connection|econnreset|etimedout|enotfound|econnrefused|network|fetch failed|socket hang|timed? out|timeout|websocket\s+(?:was\s+)?closed|websocket stream closed before response\.completed|websocket\s+error|websocket.*interrupt/i],["socket",/socket/i],["connection-only",/eai_again|epipe|ehostunreach|enetunreach|\bterminated\b|other side closed|premature close/i]]});function Jx(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}var oU=h(()=>{"use strict"});function La(t){return typeof t=="string"&&t.trim()!==""?t.trim():void 0}function Jhe(t){return typeof t=="number"&&Number.isFinite(t)?t:void 0}function Xhe(t){return typeof t=="string"||typeof t=="number"?t:void 0}function sU(t){return Jx(t)?t:void 0}function Thi(t){let e=La(t.errorMessage)??La(t.message)??La(t.reason)??La(t.statusText),n={...e===void 0?{}:{message:e},...Jhe(t.status)===void 0?{}:{status:Jhe(t.status)},...Xhe(t.code)===void 0?{}:{code:Xhe(t.code)},...La(t.errno)===void 0?{}:{errno:La(t.errno)},...La(t.body)===void 0?{}:{body:La(t.body)}};return Object.keys(n).length===0?void 0:n}function Zhe(t){let e=sU(t),n=sU(e?.headers);if(e===void 0||n===void 0)return;let r={};for(let[i,o]of Object.entries(n)){if(Object.keys(r).length>=khi)break;typeof o=="string"&&(r[i.toLowerCase()]=o.slice(0,Rhi))}return{...La(e.scope)===void 0?{}:{scope:La(e.scope)},...La(e.claim)===void 0?{}:{claim:La(e.claim)},...La(e.account)===void 0?{}:{account:La(e.account)},...La(e.model)===void 0?{}:{model:La(e.model)},headers:r}}function Dhi(t){let e=sU(t)?.diagnostics;if(Array.isArray(e))for(let n of e){let r=sU(n);if(r?.type!==Ihi)continue;let i=sU(r.details);if(i===void 0)continue;let o=Array.isArray(i.causes)?i.causes.flatMap(s=>{let a=sU(s),c=a===void 0?void 0:Thi(a);return c===void 0?[]:[c]}):void 0;return{...Jhe(i.status)===void 0?{}:{status:Jhe(i.status)},...Xhe(i.code)===void 0?{}:{code:Xhe(i.code)},...La(i.errno)===void 0?{}:{errno:La(i.errno)},...La(i.body)===void 0?{}:{body:La(i.body)},...o===void 0||o.length===0?{}:{causes:o},...Zhe(i.ratelimit)===void 0?{}:{ratelimit:Zhe(i.ratelimit)}}}}function tvn(t){return Dhi(t)}var Ihi,khi,Rhi,iXe=h(()=>{"use strict";oU();Ihi="crtr:provider-failure";khi=48,Rhi=256});function Mhi(t){switch(t.kind){case"rate-limit":return"rate-limit";case"auth":return"auth";case"protocol":return"protocol";case"context-overflow":return"context-overflow";case"overloaded":case"overloaded-only":case"transient-http":return"overloaded";case"connection":case"connection-only":return"connection";case"socket":case"service-unavailable":case"other":return"other"}}function Ohi(t,e){return e==="auth"||e==="protocol"||e==="context-overflow"?"fatal":t==="pi\u2192provider"?e==="connection"||e==="rate-limit"||e==="overloaded"?"auto":"fatal":"manual"}function Nhi(t){if(typeof t=="string")return t;if(!Jx(t))return"";let e=[t.message,t.errorMessage,t.reason,t.body,t.statusText];for(let r of e)if(typeof r=="string"&&r!=="")return r;let n=t.error;if(Jx(n)){let r=n.message;if(typeof r=="string"&&r!=="")return r;let i=n.reason;if(typeof i=="string"&&i!=="")return i}return""}function ege(t,e){if(t==="pi\u2192provider"&&Y_n(e))return{kind:"rate-limit",disposition:"auto"};let n=Jx(e)?e:{},r=tvn(e),i=r?.causes??[],o=Z_n({code:n.code??r?.code??i.find(a=>a.code!==void 0)?.code,errno:n.errno??r?.errno??i.find(a=>a.errno!==void 0)?.errno,status:n.status??r?.status,wsCloseCode:n.code,wsCloseReason:n.reason,message:[Nhi(e),r?.body,...i.flatMap(a=>[a.message,a.body])].filter(a=>typeof a=="string"&&a!=="").join(" ")}),s=Mhi(o);return{kind:s,disposition:Ohi(t,s)}}var tge=h(()=>{"use strict";J_n();evn();iXe();oU()});function nvn(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function Lhi(t){if(!nvn(t)||typeof t.provider!="string"||t.provider===""||typeof t.model!="string"||t.model==="")return;let e=typeof t.status=="number"&&Number.isFinite(t.status)?t.status:void 0,n=typeof t.code=="string"||typeof t.code=="number"?t.code:void 0,r=typeof t.errno=="string"?t.errno:void 0,i=typeof t.body=="string"?t.body:void 0,o=Zhe(t.ratelimit),s=Array.isArray(t.causes)?t.causes.flatMap(a=>{if(!nvn(a))return[];let c={...typeof a.message=="string"?{message:a.message}:{},...typeof a.status=="number"&&Number.isFinite(a.status)?{status:a.status}:{},...typeof a.code=="string"||typeof a.code=="number"?{code:a.code}:{},...typeof a.errno=="string"?{errno:a.errno}:{},...typeof a.body=="string"?{body:a.body}:{}};return Object.keys(c).length===0?[]:[c]}):void 0;return{provider:t.provider,model:t.model,...e===void 0?{}:{status:e},...n===void 0?{}:{code:n},...r===void 0?{}:{errno:r},...i===void 0?{}:{body:i},...s===void 0||s.length===0?{}:{causes:s},...o===void 0?{}:{ratelimit:o}}}function rvn(t){let e=t.provider_error_json,n=typeof e=="string"?Lhi(JSON.parse(e)):void 0;if(typeof e=="string"&&n===void 0)throw new Error(`invalid provider error on stored fault ${t.fault_id}`);let r={disposition:t.retry_disposition,...t.retry_by===null?{}:{by:t.retry_by},...t.retry_attempt===null?{}:{attempt:t.retry_attempt},...t.retry_max===null?{}:{max:t.retry_max},...t.retry_next_at===null?{}:{nextAt:t.retry_next_at}};return{faultId:t.fault_id,link:t.link,op:t.op,kind:t.kind,message:t.message,since:t.since,operation_id:t.operation_id,retry:r,...t.anchor_entry_id===null?{}:{anchorEntryId:t.anchor_entry_id},...n===void 0?{}:{providerError:n}}}var J6s,Phi,X6s,oXe=h(()=>{"use strict";Ht();di();Rr();tge();iXe();Of();LQ();J6s=5*6e4,Phi=["pi\u2192provider","viewer\u2194broker","relay\u2194broker","viewer\u2194crtrd","daemon\u2192node","crtr\u2192pi"],X6s=[...Phi,"broker\u2194crtrd"]});var ivn=h(()=>{"use strict"});var Bhi,lVs,$6=h(()=>{"use strict";Ht();di();Rr();ivn();Of();Bhi=`SELECT d.delivery_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
1730
1730
|
WHERE d.destination_id = ? AND d.wake = ? AND d.state = 'pending' AND d.canceled_at IS NULL
|
|
1731
1731
|
AND NOT (d.wake = 0 AND m.kind = 'message' AND coalesce(json_extract(m.body_json, '$.start_turn'), 1) = 0)`,lVs=`UPDATE deliveries SET state = 'handed_off', attempts = attempts + 1, claim_owner = ?, claimed_at = ?, updated_at = ?
|
|
1732
|
-
WHERE delivery_id IN (${Bhi})`});var aU,F6=h(()=>{"use strict";_o();A0();aU=["crtr:llm","crtr:act","crtr:schedule","crtr:memory:rw:user","crtr:memory:manage:user","crtr:files:rw:user:."]});var nge=h(()=>{"use strict";ud();Vo()});function ovn(t){if(!Array.isArray(t))throw new TypeError("grant scopes must be a list");return t.map(e=>{if(!e||typeof e!="object"||typeof e.scope!="string"||!["always","ask"].includes(e.mode))throw new TypeError("invalid grant scope entry");return{scope:e.scope,mode:e.mode}})}function Fhi(t){if(!t||typeof t!="object"||Array.isArray(t))return null;let{state:e,message:n,next:r}=t;return typeof e!="string"||!$hi.includes(e)||n!==void 0&&typeof n!="string"||typeof r!="string"||!r?null:{state:e,...n===void 0?{}:{message:n},next:r}}function Uhi(t){if(!t||typeof t!="object"||Array.isArray(t))return{};let e={};for(let[n,r]of Object.entries(t)){let i=Fhi(r);i&&(e[n]=i)}return e}function Qhi(t){return{id:t.id,runtime:t.runtime,sub:t.sub,grantee:t.grantee,rev:t.rev,scopes:ovn(JSON.parse(t.scopes_json)),resolved_scopes:ovn(JSON.parse(t.resolved_scopes_json)),spend_cap:t.spend_cap,expires_at:t.expires_at,status:t.status,revoked_at:t.revoked_at,approved_at:t.approved_at,payer:t.payer,dependencies:Uhi(JSON.parse(t.dependencies_json)),run_notice_url:t.run_notice_url??null,synced_at:t.synced_at}}function svn(t){if(!t||t.status!=="active")return null;try{return Whi(Qhi(t))}catch(e){if(e instanceof TypeError||e instanceof SyntaxError)return null;throw e}}function Whi(t){if(t.status!=="active")return null;try{let e=t.resolved_scopes.map(({scope:n})=>zV(n));return{...t,resolved:t.resolved_scopes.flatMap(({mode:n},r)=>n==="always"?[e[r]]:[])}}catch(e){if(e instanceof TypeError||e instanceof SyntaxError)return null;throw e}}function U6(t){return svn(ce(ir(),"SELECT * FROM grants WHERE id = ?").get(t))}function rge(t,e){return svn(ce(ir(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(t,e))}var $hi,cU=h(()=>{"use strict";Ht();hb();di();nge();$hi=["not_started","pending","ready","failed"]});var avn=h(()=>{"use strict";Xx()});var cvn=h(()=>{"use strict"});var uvn=h(()=>{"use strict";js();Xx()});var aXe=h(()=>{"use strict"});var lvn=h(()=>{"use strict";js();Xx();aXe();Of()});var Xx=h(()=>{"use strict";Vi();avn();cvn();uvn();lvn();aXe()});var uXe=h(()=>{"use strict"});function ige(t,e,n){if(n.length===0)return;let r=ce(t,"INSERT INTO memory_reads (node_id, name, scope, at, type) VALUES (?, ?, ?, ?, ?)");for(let i of n)r.run(e,i.name,i.scope,i.at,i.type)}var XVs,lXe=h(()=>{"use strict";Ht();di();XVs=720*60*60*1e3});function TD(t,e=ir()){let n=ce(e,`SELECT n.run_id, n.isolated, r.isolated AS root_isolated FROM nodes n
|
|
1732
|
+
WHERE delivery_id IN (${Bhi})`});var aU,F6=h(()=>{"use strict";_o();A0();aU=["crtr:llm","crtr:act","crtr:schedule","crtr:memory:rw:user","crtr:memory:manage:user","crtr:files:rw:user:."]});var nge=h(()=>{"use strict";ud();Vo()});function ovn(t){if(!Array.isArray(t))throw new TypeError("grant scopes must be a list");return t.map(e=>{if(!e||typeof e!="object"||typeof e.scope!="string"||!["always","ask"].includes(e.mode))throw new TypeError("invalid grant scope entry");return{scope:e.scope,mode:e.mode}})}function Fhi(t){if(!t||typeof t!="object"||Array.isArray(t))return null;let{state:e,message:n,next:r,step:i}=t;return typeof e!="string"||!$hi.includes(e)||n!==void 0&&typeof n!="string"||i!==void 0&&typeof i!="string"||typeof r!="string"||!r?null:{state:e,...n===void 0?{}:{message:n},next:r,...i===void 0?{}:{step:i}}}function Uhi(t){if(!t||typeof t!="object"||Array.isArray(t))return{};let e={};for(let[n,r]of Object.entries(t)){let i=Fhi(r);i&&(e[n]=i)}return e}function Qhi(t){return{id:t.id,runtime:t.runtime,sub:t.sub,grantee:t.grantee,rev:t.rev,scopes:ovn(JSON.parse(t.scopes_json)),resolved_scopes:ovn(JSON.parse(t.resolved_scopes_json)),spend_cap:t.spend_cap,expires_at:t.expires_at,status:t.status,revoked_at:t.revoked_at,approved_at:t.approved_at,payer:t.payer,dependencies:Uhi(JSON.parse(t.dependencies_json)),run_notice_url:t.run_notice_url??null,synced_at:t.synced_at}}function svn(t){if(!t||t.status!=="active")return null;try{return Whi(Qhi(t))}catch(e){if(e instanceof TypeError||e instanceof SyntaxError)return null;throw e}}function Whi(t){if(t.status!=="active")return null;try{let e=t.resolved_scopes.map(({scope:n})=>zV(n));return{...t,resolved:t.resolved_scopes.flatMap(({mode:n},r)=>n==="always"?[e[r]]:[])}}catch(e){if(e instanceof TypeError||e instanceof SyntaxError)return null;throw e}}function U6(t){return svn(ce(ir(),"SELECT * FROM grants WHERE id = ?").get(t))}function rge(t,e){return svn(ce(ir(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(t,e))}var $hi,cU=h(()=>{"use strict";Ht();hb();di();nge();$hi=["not_started","pending","ready","failed","unavailable"]});var avn=h(()=>{"use strict";Xx()});var cvn=h(()=>{"use strict"});var uvn=h(()=>{"use strict";js();Xx()});var aXe=h(()=>{"use strict"});var lvn=h(()=>{"use strict";js();Xx();aXe();Of()});var Xx=h(()=>{"use strict";Vi();avn();cvn();uvn();lvn();aXe()});var uXe=h(()=>{"use strict"});function ige(t,e,n){if(n.length===0)return;let r=ce(t,"INSERT INTO memory_reads (node_id, name, scope, at, type) VALUES (?, ?, ?, ?, ?)");for(let i of n)r.run(e,i.name,i.scope,i.at,i.type)}var XVs,lXe=h(()=>{"use strict";Ht();di();XVs=720*60*60*1e3});function TD(t,e=ir()){let n=ce(e,`SELECT n.run_id, n.isolated, r.isolated AS root_isolated FROM nodes n
|
|
1733
1733
|
LEFT JOIN nodes r ON r.node_id = n.run_id WHERE n.node_id = ?`).get(t);if(!n){let r=ce(e,"SELECT run_id FROM deleted_isolated_nodes WHERE node_id = ?").get(t);return r?{run:r.run_id,isolated:!0}:null}return{run:n.run_id??t,isolated:n.isolated===1||n.root_isolated===1}}function pvn(t,e=ir()){let n=ce(e,"SELECT registered_by FROM custom_objects WHERE object = ?").get(t);return n?.registered_by?TD(n.registered_by,e)??{run:n.registered_by,isolated:!1}:null}function fvn(t,e){return t.run===e.run||!t.isolated&&!e.isolated}var oge=h(()=>{"use strict";di()});function sge(t,e,n,r,i,o){if(e.class==="person"||r.length===0||i!==null&&i.type!=="document")return null;let s=e.launched;if(s==null||o==="read"&&i?.kind==="report"&&r[0].type==="node"&&qhi(t,e,r[0].id))return null;let a=age(t,mvn(t,n),o,r);return s.some(c=>qc(c,a))?null:a}function qhi(t,e,n){let r=e.class==="node"?TD(e.node.id,t):e.class==="person"?null:e.place??null,i=TD(n,t);return r!==null&&i!==null&&r.run===i.run}function age(t,e,n,r){let i=r[0];return i.type!=="profile"?`crtr:memory:${n}:${e}`:`crtr:memory:${n}:${e}:profile:${mvn(t,i)}`}var mvn,cge=h(()=>{"use strict";Ht();hb();oge();mvn=(t,e)=>ce(t,"SELECT external_id FROM identities WHERE object = ?").get(e.rowid).external_id});function gvn(t,e,n){if(e.class==="person"||n.length===0||n[0].type!=="node"||e.class==="app"&&e.place===void 0)return!0;let r=hvn.get(e);if(r===void 0&&(r=e.class==="app"?e.place:e.class==="node"?TD(e.node.id,t):e.place??pvn(e.object.rowid,t),hvn.set(e,r)),r===null)return!0;let i=TD(n[0].id,t);return i===null?!r.isolated:fvn(r,i)}var yvn,hvn,Avn=h(()=>{"use strict";hb();Q6();cge();oge();yvn={identity:Zx,sync:"runtime",canRead(t,e,n,r,i){if(e.class==="person"||r.length===0)return!0;if(e.app.rowid===n.rowid)return gvn(t,e,r)&&sge(t,e,n,r,i,"read")===null;let o=Zx(t,n).external_id,s=a=>e.scopes!==null&&e.scopes.some(c=>qc(c,a));return r[0].type==="node"?s(`crtr:objects:read:${o}`)||i.type==="document"&&i.kind==="report"&&s(`crtr:conversations:read:${o}`):s(age(t,o,"read",r))},canChange(t,e,n,r,i){if(e.class==="person")return!0;if(e.app.rowid===n.rowid)return r.length===0||gvn(t,e,r)&&sge(t,e,n,r,i,"rw")===null;if(r.length===0||r[0].type==="node")return!1;let o=age(t,Zx(t,n).external_id,"rw",r);return e.scopes!==null&&e.scopes.some(s=>qc(s,o))}},hvn=new WeakMap});var wvn,xvn=h(()=>{"use strict";Ht();hb();Q6();wvn={identity:Zx,sync:"runtime",canRead(t,e,n,r,i){return e.class==="person"||e.class==="object"&&e.owner.rowid===n.rowid?!0:e.scopes===null?!1:e.app.rowid===n.rowid||r.length===0?!0:r[0].type!=="document"?!1:e.scopes.some(o=>qc(o,"crtr:memory:manage:user"))?!0:i.privacy==="public"},canChange(t,e,n,r,i){if(e.class==="person"||e.class==="object"&&e.owner.rowid===n.rowid||e.app.rowid===n.rowid||e.scopes?.some(c=>qc(c,"crtr:memory:manage:user")))return!0;if(r.length===0)return e.scopes?.some(c=>qc(c,"crtr:memory:rw:user"))??!1;if(r[0].type!=="document")return!1;let o=`crtr:memory:rw:user:${i.name}`;if(e.scopes===null||!e.scopes.some(c=>qc(c,o)))return!1;let s=ce(t,`SELECT author.type, author.owner AS app FROM revisions r JOIN objects author ON author.rowid = r.author
|
|
1734
1734
|
WHERE r.document = ? ORDER BY r.number, r.created_at LIMIT 1`).get(r[0].rowid);return s?s.type==="person"||s.app===e.app.rowid?!0:ce(t,`SELECT g.resolved_scopes_json FROM grants g JOIN identities i ON i.external_id = g.grantee
|
|
1735
1735
|
WHERE i.object = ? AND i.scheme = 'app'`).all(s.app).some(({resolved_scopes_json:c})=>JSON.parse(c).some(({scope:u,mode:l})=>l==="always"&&qc(u,"crtr:memory:manage:user"))):!1}}});import{join as dXe,relative as uge,resolve as Ghi,sep as lge}from"node:path";function uU(t,e){let n=uge(t,e);return n===""||n!==".."&&!n.startsWith(`..${lge}`)&&!n.startsWith(lge)}function pXe(t,e,n){let r=Ghi(e);if(t===null||process.platform!=="linux"&&t.app==="app:terminal"){if(process.platform!=="linux")return{root:"/"};let p=[C9(),Sh()].find(f=>uU(f,r));return p?{root:p}:{missing:null}}let i=qa(t.app);if(t.run&&uU(i.root,r)){let[p,f]=uge(i.root,r).split(lge);if(p==="nodes"&&f)return t.run.reaches(f)?{root:dXe(i.nodes,f)}:{hidden:!0};if(t.run.isolated)return{hidden:!0}}for(let p of[i.files,i.nodes])if(uU(p,r))return{root:p};let o=p=>t.resolved.some(f=>qc(f,p))&&(t.launched===null||t.launched.some(f=>qc(f,p))),s=(p,f)=>{let m=uge(f,r)||".",g=x=>x.flatMap(E=>{let _=/^crtr:files:(read|rw):(user|app:[A-Za-z0-9_-]{1,64}):(.+)$/.exec(E);return!_||_[2]!==p||n==="rw"&&_[1]!=="rw"?[]:_[3]==="."||_[3]===m||m.startsWith(`${_[3]}/`)?[dXe(f,_[3]==="."?"":_[3])]:[]}),y=g(t.resolved),w=t.launched===null?y:g(t.launched);return!y.length||!w.length?{missing:`crtr:files:${n}:${p}:${m}`}:{root:[...y,...w].reduce((x,E)=>E.length>x.length?E:x)}};if(uU(XM(),r))return s("user",XM());let[a,c,u,l]=uge(Sh(),r).split(lge);if(!uU(Sh(),r)||!a||!/^[A-Za-z0-9_-]{1,64}$/.test(a))return{missing:null};let d=qa(`app:${a}`);return c==="files"?s(`app:${a}`,d.files):c!=="nodes"||n!=="read"?{missing:null}:o(`crtr:objects:read:app:${a}`)?{root:d.nodes}:u&&l==="attachments"&&o(`crtr:conversations:read:app:${a}`)?{root:dXe(d.nodes,u,l)}:{missing:`crtr:objects:read:app:${a}`}}var dge=h(()=>{"use strict";hb();Vi()});function fXe(t,e,n,r){if(e.class==="object")return e.repos.some(s=>s.rowid===n.rowid);if(e.class!=="node")return!1;let i=[...e.checkouts,...r==="read"?e.projectCheckouts:[]].filter(s=>s.repo.rowid===n.rowid);if(i.length===0)return!1;let o={app:Zx(t,e.app).external_id,resolved:e.scopes??[],launched:null};return i.some(s=>"root"in pXe(o,s.root,r))}var bvn,mXe=h(()=>{"use strict";dge();Q6();bvn={identity:Zx,sync:"repo-ref",canRead(t,e,n,r){return e.class==="person"||r.length===0?!0:fXe(t,e,n,"read")},canChange(t,e,n,r,i){return i.privacy!=="public"?!1:e.class==="person"?!0:fXe(t,e,n,"rw")}}});function Zx(t,e){let n=ce(t,"SELECT object, scheme, external_id FROM identities WHERE object = ?").get(e.rowid);if(!n)throw new Error(`${e.type} ${e.id} has no identity`);return n}var Evn,Q6=h(()=>{"use strict";Ht();Avn();xvn();mXe();Evn={person:wvn,app:yvn,repo:bvn}});function pge(t,e){return ce(t,`WITH RECURSIVE chain(rowid, depth) AS (
|
|
@@ -20,7 +20,7 @@ export interface GrantRecord {
|
|
|
20
20
|
revoked_at: string | null;
|
|
21
21
|
approved_at: string | null;
|
|
22
22
|
payer: 'user' | 'app' | null;
|
|
23
|
-
/** The directory's setup entry (`{ state, message?, next }`) per
|
|
23
|
+
/** The directory's setup entry (`{ state, message?, next, step? }`) per setup id the grantee requires. Absent (an older directory) reads as `{}`. */
|
|
24
24
|
dependencies?: GrantDependencies;
|
|
25
25
|
/** The grantee's registered run-notice endpoint (https), or null: runs of this grant send no notice. */
|
|
26
26
|
run_notice_url?: string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var y=Object.defineProperty;var t=(e,r)=>y(e,"name",{value:r,configurable:!0});import{createHash as _}from"node:crypto";import{prepared as
|
|
1
|
+
var y=Object.defineProperty;var t=(e,r)=>y(e,"name",{value:r,configurable:!0});import{createHash as _}from"node:crypto";import{prepared as s}from"../sqlite-statements.js";import{parseScope as S}from"@north-light/identity";import{openDb as i,withCanvasWrite as v}from"../canvas/db.js";import{isUnsandboxedMachine as T}from"../runtime/unsandboxed.js";const m=["not_started","pending","ready","failed","unavailable"];function c(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(c,"entries");function O(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:a,next:u,step:n}=e;return typeof r!="string"||!m.includes(r)||a!==void 0&&typeof a!="string"||n!==void 0&&typeof n!="string"||typeof u!="string"||!u?null:{state:r,...a===void 0?{}:{message:a},next:u,...n===void 0?{}:{step:n}}}t(O,"dependencyOf");function E(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[a,u]of Object.entries(e)){const n=O(u);n&&(r[a]=n)}return r}t(E,"dependenciesOf");function l(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:c(JSON.parse(e.scopes_json)),resolved_scopes:c(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:E(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,synced_at:e.synced_at}}t(l,"record");function p(e){if(!e||e.status!=="active")return null;try{return x(l(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(p,"active");function d(e){return e.grantee!=="app:terminal"&&T()}t(d,"mirrorOnly");function M(){return R().filter(e=>!d(e))}t(M,"localGrants");function R(){return s(i(),"SELECT * FROM grants").all().flatMap(e=>{try{return[l(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(R,"allGrants");function x(e){if(e.status!=="active")return null;try{const r=e.resolved_scopes.map(({scope:a})=>S(a));return{...e,resolved:e.resolved_scopes.flatMap(({mode:a},u)=>a==="always"?[r[u]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(x,"activeRecord");function f(e){return Array.isArray(e)?`[${e.map(f).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${f(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(f,"canonicalJson");function b(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{}};return _("sha256").update(f(r)).digest("base64url").slice(0,22)}t(b,"grantVersion");function k(e){return!!s(i(),"SELECT 1 FROM grants WHERE id = ?").get(e)}t(k,"hasGrant");function H(e){return s(i(),"SELECT rev, status FROM grants WHERE id = ?").get(e)??null}t(H,"grantSyncState");function W(e,r){return s(i(),"SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,r)??null}t(W,"grantSyncStateFor");function j(e){const r=s(i(),"SELECT * FROM grants WHERE id = ?").get(e);if(!r)return null;try{return l(r)}catch(a){if(a instanceof TypeError||a instanceof SyntaxError)return null;throw a}}t(j,"grantRecord");function D(e){return p(s(i(),"SELECT * FROM grants WHERE id = ?").get(e))}t(D,"activeGrant");function G(e,r){return p(s(i(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,r))}t(G,"grantFor");function I(e){if(d({grantee:e}))return null;const r=s(i(),"SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return p(r[0])}t(I,"activeGrantForApp");function J(e){const r=s(i(),"SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return r&&r.url&&!d(r)?{url:r.url,sub:r.sub,grantee:r.grantee}:null}t(J,"runNoticeTarget");function U(){const e=s(i(),"SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return p(e[0])}t(U,"terminalGrant");function w(){return s(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value??null}t(w,"grantsSource");function $(e){return s(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value===e}t($,"hasSyncedGrants");function V(e){return s(e,"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value!=="local"?0:(s(e,"DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number(s(e,"DELETE FROM grants").run().changes))}t(V,"dropLocalGrantMirror");function P(e,r,a){const u=new Set;for(const n of e){if(u.has(n.id)||!n.id||!n.runtime||!n.sub||!n.grantee||!Number.isInteger(n.rev)||!["active","revoked","removed"].includes(n.status))throw new TypeError("invalid grant sync response");u.add(n.id),c(n.scopes),c(n.resolved_scopes)}v(n=>{n.exec("DELETE FROM grants");const g=s(n,`INSERT INTO grants
|
|
2
2
|
(id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
|
|
3
3
|
status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, synced_at)
|
|
4
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const o of e)g.run(o.id,o.runtime,o.sub,o.grantee,o.rev,JSON.stringify(o.scopes),JSON.stringify(o.resolved_scopes),o.spend_cap,o.expires_at,o.status,o.revoked_at,o.approved_at,o.payer,JSON.stringify(
|
|
4
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const o of e)g.run(o.id,o.runtime,o.sub,o.grantee,o.rev,JSON.stringify(o.scopes),JSON.stringify(o.resolved_scopes),o.spend_cap,o.expires_at,o.status,o.revoked_at,o.approved_at,o.payer,JSON.stringify(E(o.dependencies)),o.run_notice_url??null,o.synced_at);s(n,"INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(a),s(n,"INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT)").run()})}t(P,"replaceGrants");function X(){const e=s(i(),"SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(X,"grantsGeneration");export{D as activeGrant,I as activeGrantForApp,x as activeRecord,R as allGrants,E as dependenciesOf,V as dropLocalGrantMirror,G as grantFor,j as grantRecord,H as grantSyncState,W as grantSyncStateFor,b as grantVersion,X as grantsGeneration,w as grantsSource,k as hasGrant,$ as hasSyncedGrants,M as localGrants,d as mirrorOnly,P as replaceGrants,J as runNoticeTarget,U as terminalGrant};
|
|
@@ -35,11 +35,11 @@ A call is refused when the scope is missing from the grant. The refusal is an `A
|
|
|
35
35
|
|
|
36
36
|
Some apps do not work until a provider is set up for the person. Name it under **What your app requires** on the **What it asks for** tab. The person is then taken through that provider's setup the first time they sign in. Context, for example, asks for a profile URL, and its state stays `pending` for a few minutes afterwards.
|
|
37
37
|
|
|
38
|
-
Read the state on each request that depends on it. `runtime.grant.get()` returns `dependencies`, one entry per
|
|
38
|
+
Read the state on each request that depends on it. `runtime.grant.get()` returns `dependencies`, one entry per setup id the app requires or lists as optional, plus any the directory implies (such as `device` for `context.deep`), keyed by setup id (or `{}` when it has none):
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
41
|
const {dependencies} = await runtime.grant.get();
|
|
42
|
-
const context = dependencies['context']; // {state, message?, next} or undefined
|
|
42
|
+
const context = dependencies['context']; // {state, message?, next, step?} or undefined
|
|
43
43
|
if (context?.state !== 'ready') {
|
|
44
44
|
// Show context.message and link context.next; never hard-code either.
|
|
45
45
|
}
|
|
@@ -51,8 +51,9 @@ if (context?.state !== 'ready') {
|
|
|
51
51
|
| `pending` | The provider is working. |
|
|
52
52
|
| `ready` | Calls that depend on it work. |
|
|
53
53
|
| `failed` | Setup failed. `message` says why. |
|
|
54
|
+
| `unavailable` | The provider does not offer this to the person's account. `message` says so; there is nothing for them to do. |
|
|
54
55
|
|
|
55
|
-
`message` is the provider's own sentence, present for `pending` and `
|
|
56
|
+
`message` is the provider's own sentence, present for `pending`, `failed` and `unavailable`. `step` is present when the provider reports where things stand (for example `researching`); its values may grow, so ignore one you do not know. `next` is the absolute URL of the setup page for this provider and your app; `setupLink(dependency, {returnTo})` from `@north-light/sdk` adds a `return_to` with your app's registered origin so the person returns to you, and keeps the query `next` already carries. A key can contain a dot (`context.deep`). An entry is missing when the runtime has not synced the grant since the app began requiring the provider: treat that as `not_started`. The runtime does not block your calls for you, so refuse the dependent operation yourself. To act the moment a state turns `ready`, follow it with `oauth.watchGrants`; see [Grant changes](/docs/guides/grant-changes).
|
|
56
57
|
|
|
57
58
|
## Checked and not checked
|
|
58
59
|
|
|
@@ -39,31 +39,33 @@ await watcher.stop();
|
|
|
39
39
|
|
|
40
40
|
## Wait on a provider your app requires
|
|
41
41
|
|
|
42
|
-
An app whose registration `requires` a provider (the console's **What your app requires**) needs that provider set up for the person before the operations that depend on it work. The runtime does not check this for you. `grant.get()` returns `dependencies`: one entry per
|
|
42
|
+
An app whose registration `requires` a provider (the console's **What your app requires**) needs that provider set up for the person before the operations that depend on it work. The runtime does not check this for you. `grant.get()` returns `dependencies`: one entry per setup id the app requires or lists as optional (plus any the directory implies, such as `device` for `context.deep`), keyed by setup id, or `{}` when it has none. Read it on each request that needs the provider; do not store it.
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
|
-
import type
|
|
45
|
+
import {setupLink, type ProviderDependency} from '@north-light/sdk';
|
|
46
46
|
|
|
47
|
-
// ProviderDependency is { state: 'not_started' | 'pending' | 'ready' | 'failed'; message?: string; next: string }.
|
|
47
|
+
// ProviderDependency is { state: 'not_started' | 'pending' | 'ready' | 'failed' | 'unavailable'; message?: string; next: string; step?: string }.
|
|
48
48
|
const {dependencies} = await runtime.grant.get();
|
|
49
49
|
const context: ProviderDependency | undefined = dependencies['context'];
|
|
50
50
|
if (context?.state !== 'ready') {
|
|
51
51
|
// Show the provider's own sentence and link its setup page; never hard-code either.
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// Nothing to link when the provider does not offer this to the person (`unavailable`).
|
|
53
|
+
const next = context && context.state !== 'unavailable' ? setupLink(context, {returnTo: 'https://app.example.com'}) : undefined; // your registered redirect origin
|
|
54
54
|
throw new AppError('provider_setup_pending', context?.message ?? 'Finish setting up Context, then try again.', {
|
|
55
55
|
status: 403,
|
|
56
56
|
retryable: false,
|
|
57
|
-
details: {provider: 'context', state: context?.state ?? 'not_started', message: context?.message, next
|
|
57
|
+
details: {provider: 'context', state: context?.state ?? 'not_started', message: context?.message, next},
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
`AppError` stands for your app's own typed error ([Errors](/docs/errors)).
|
|
63
63
|
|
|
64
|
-
- `state` is `not_started` (the person has not done their part), `pending` (the provider is working), `ready` or `
|
|
65
|
-
- `message` is the provider's own sentence for `pending` and `
|
|
66
|
-
- `
|
|
64
|
+
- `state` is `not_started` (the person has not done their part), `pending` (the provider is working), `ready`, `failed`, or `unavailable` (the provider does not offer this to the person's account; there is nothing for them to do, so do not link `next`).
|
|
65
|
+
- `message` is the provider's own sentence for `pending`, `failed` and `unavailable`. It is absent for `not_started` and `ready`, so keep a short fallback.
|
|
66
|
+
- `step` is present when the provider reports where the person or the work is, for example `researching`. Its values belong to the provider and may grow: show a known one in your own words and ignore an unknown one. A change to `step` alone changes the grant's `version`, so `watchGrants` delivers it.
|
|
67
|
+
- `next` is the absolute URL of the directory's setup page for this provider and your app. Use `setupLink(dependency, {returnTo})` to add `return_to` (your app's registered redirect origin) so the person comes back to you. It keeps the query `next` already has, such as `client_id`, and encodes `returnTo`; do not build the URL yourself.
|
|
68
|
+
- A key can contain a dot. `context.deep` is a level of the `context` provider; read it as `dependencies['context.deep']`.
|
|
67
69
|
- An entry is missing when the runtime has not synced the grant since the app began requiring the provider. Treat it as `not_started`.
|
|
68
70
|
|
|
69
71
|
Refuse the dependent operation with `provider_setup_pending` (403), carrying `state`, `message` and `next` in `details`. To act when the state turns `ready`, use `watchGrants` above instead of polling.
|
|
@@ -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.483
|
|
11
|
+
|
|
12
|
+
### `unavailable`, `step` and `setupLink` on `grant.get` dependencies (additive for the SDK; a type change for exhaustive switches)
|
|
13
|
+
|
|
14
|
+
- `SetupState` gains `unavailable`: the provider does not offer this setup to the person's account. It is not `failed`. If you `switch` over `state` exhaustively, add a case; show `message` and do not link `next`.
|
|
15
|
+
- `ProviderDependency` gains an optional `step: string` (for example `researching`, or `downloaded` on `device`). Its values belong to the provider and may grow: ignore one you do not know. A change to `step` alone changes `grant.get`'s `version`, so `oauth.watchGrants` delivers it.
|
|
16
|
+
- `dependencies` keys may be dotted setup ids such as `context.deep`.
|
|
17
|
+
- New `setupLink(dependency, {returnTo})` returns `dependency.next` with `return_to` set and URL-encoded, keeping the query `next` already has (such as `client_id`). Use it instead of building the link yourself.
|
|
18
|
+
- A runtime older than this release drops a dependency whose state is `unavailable` and silently loses `step`, so an app that reads either needs a runtime on this release.
|
|
19
|
+
|
|
10
20
|
## 0.3.482
|
|
11
21
|
|
|
12
22
|
### `runs.start` accepts `deadline` (additive, no app change)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.483",
|
|
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.483",
|
|
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.483",
|
|
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.483",
|
|
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.483",
|
|
22
22
|
"cron-parser": "^5.6.0",
|
|
23
23
|
"esbuild": "^0.27.7",
|
|
24
24
|
"jose": "^6.2.12",
|
|
@@ -12116,17 +12116,17 @@
|
|
|
12116
12116
|
},
|
|
12117
12117
|
"packages/crouter-api": {
|
|
12118
12118
|
"name": "@north-light/api",
|
|
12119
|
-
"version": "0.3.
|
|
12119
|
+
"version": "0.3.483",
|
|
12120
12120
|
"license": "UNLICENSED"
|
|
12121
12121
|
},
|
|
12122
12122
|
"packages/crouter-env-docker": {
|
|
12123
12123
|
"name": "@north-light/env-docker",
|
|
12124
|
-
"version": "0.3.
|
|
12124
|
+
"version": "0.3.483",
|
|
12125
12125
|
"license": "UNLICENSED"
|
|
12126
12126
|
},
|
|
12127
12127
|
"packages/crouter-identity": {
|
|
12128
12128
|
"name": "@north-light/identity",
|
|
12129
|
-
"version": "0.3.
|
|
12129
|
+
"version": "0.3.483",
|
|
12130
12130
|
"license": "UNLICENSED",
|
|
12131
12131
|
"dependencies": {
|
|
12132
12132
|
"jose": "^6.2.1"
|
|
@@ -12134,20 +12134,20 @@
|
|
|
12134
12134
|
},
|
|
12135
12135
|
"packages/crouter-plugin": {
|
|
12136
12136
|
"name": "@north-light/plugin",
|
|
12137
|
-
"version": "0.3.
|
|
12137
|
+
"version": "0.3.483",
|
|
12138
12138
|
"license": "UNLICENSED",
|
|
12139
12139
|
"dependencies": {
|
|
12140
|
-
"@north-light/api": "0.3.
|
|
12141
|
-
"@north-light/identity": "0.3.
|
|
12140
|
+
"@north-light/api": "0.3.483",
|
|
12141
|
+
"@north-light/identity": "0.3.483"
|
|
12142
12142
|
}
|
|
12143
12143
|
},
|
|
12144
12144
|
"packages/crouter-sdk": {
|
|
12145
12145
|
"name": "@north-light/sdk",
|
|
12146
|
-
"version": "0.3.
|
|
12146
|
+
"version": "0.3.483",
|
|
12147
12147
|
"license": "UNLICENSED",
|
|
12148
12148
|
"dependencies": {
|
|
12149
|
-
"@north-light/api": "0.3.
|
|
12150
|
-
"@north-light/identity": "0.3.
|
|
12149
|
+
"@north-light/api": "0.3.483",
|
|
12150
|
+
"@north-light/identity": "0.3.483",
|
|
12151
12151
|
"jose": "^6.2.1"
|
|
12152
12152
|
},
|
|
12153
12153
|
"bin": {
|