@north-light/cli 0.3.468 → 0.3.471

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/README.md CHANGED
@@ -5,14 +5,14 @@
5
5
  <p align="center">
6
6
  <a href="https://www.npmjs.com/package/@north-light/cli"><img alt="npm" src="https://img.shields.io/npm/v/@north-light/cli?label=npm"></a>
7
7
  <a href="https://nodejs.org"><img alt="node" src="https://img.shields.io/badge/node-%3E%3D22.19-339933"></a>
8
- <a href="https://discord.gg/afwW4saEtr"><img alt="discord" src="https://img.shields.io/badge/discord-join-5865F2?logo=discord&logoColor=white"></a>
8
+ <a href="https://discord.gg/txKS3swtWY"><img alt="discord" src="https://img.shields.io/badge/discord-join-5865F2?logo=discord&logoColor=white"></a>
9
9
  </p>
10
10
 
11
11
  crouter is an agent runtime for Node.js. Each agent is a **node**: a record with a goal, a context directory, and a detached process (its *broker*) that hosts the model session. A daemon, `crtrd`, owns every node's state and lifecycle. You drive it with the `crtr` command line, a terminal viewer, or a typed TypeScript SDK — all three are clients of the same local API. Each model session runs on [pi](https://github.com/earendil-works/pi), the open-source coding-agent harness; crouter adds the daemon, the node graph, the context system and the CLI around it.
12
12
 
13
13
  Closing a terminal does not stop an agent, and an agent that is waiting on something has no process at all. Nodes can spawn children, report to whoever subscribes to them, and share markdown documents that other agents are shown when they become relevant. crouter is not a model and does not bundle one: you sign in to a model provider during setup. It is also not a hosted service; it runs on your machine, or in a container you start.
14
14
 
15
- [Docs](https://docs.northlight.ai) · [Getting started](https://docs.northlight.ai/docs/cli/first-session) · [Concepts](https://docs.northlight.ai/docs/concepts) · [SDK](https://docs.northlight.ai/docs/sdk) · [Plugins](https://docs.northlight.ai/docs/plugin) · [Marketplace](https://github.com/crouton-labs/crouter-official-marketplace) · [Discord](https://discord.gg/afwW4saEtr)
15
+ [Docs](https://crouter.docs.northlight.ai) · [Getting started](https://crouter.docs.northlight.ai/docs/cli/first-session) · [Concepts](https://crouter.docs.northlight.ai/docs/concepts) · [SDK](https://crouter.docs.northlight.ai/docs/sdk) · [Plugins](https://crouter.docs.northlight.ai/docs/plugin) · [Marketplace](https://github.com/crouton-labs/crouter-official-marketplace) · [Discord](https://discord.gg/txKS3swtWY)
16
16
 
17
17
  ## Install
18
18
 
@@ -62,7 +62,7 @@ crtr canvas browse # interactive navigator with tabs and search
62
62
  crtr canvas attention list # nodes waiting on you
63
63
  ```
64
64
 
65
- The [first-session guide](https://docs.northlight.ai/docs/cli/first-session) walks through this in order.
65
+ The [first-session guide](https://crouter.docs.northlight.ai/docs/cli/first-session) walks through this in order.
66
66
 
67
67
  ## What it does
68
68
 
@@ -99,7 +99,7 @@ flowchart LR
99
99
 
100
100
  `crtrd` is the only writer of canvas state and the only launcher of brokers. The CLI, the viewer and SDK applications call its `/v1` API; none of them opens the canvas database or starts an engine of its own. On one machine they use an owner-only Unix socket. A browser or a remote process uses a TCP listener and a bearer token (`crtr sys connect`).
101
101
 
102
- What this costs: a daemon has to be running (the CLI and SDK start it on first use), and a network failure on a remote client fails a request loudly rather than replaying it. What it buys is one owner for state, so a node outlives the terminal, the viewer, or the application that created it. [Why a daemon](https://docs.northlight.ai/docs/concepts/why-a-daemon) covers the reasoning.
102
+ What this costs: a daemon has to be running (the CLI and SDK start it on first use), and a network failure on a remote client fails a request loudly rather than replaying it. What it buys is one owner for state, so a node outlives the terminal, the viewer, or the application that created it. [Why a daemon](https://crouter.docs.northlight.ai/docs/concepts/why-a-daemon) covers the reasoning.
103
103
 
104
104
  ## Use crouter from an application
105
105
 
@@ -123,7 +123,7 @@ if (run.kind === 'result') console.log(run.output_parsed.name, run.output_parsed
123
123
  else console.error(run.reason, run.detail);
124
124
  ```
125
125
 
126
- `nodes.parse` returns a typed result or an explicit failure; it never hands back text you have to parse. `nodes.stream` pipes a run's text and tool calls to a UI. See the [SDK docs](https://docs.northlight.ai/docs/sdk) and the [recipes](https://docs.northlight.ai/docs/sdk/guides).
126
+ `nodes.parse` returns a typed result or an explicit failure; it never hands back text you have to parse. `nodes.stream` pipes a run's text and tool calls to a UI. See the [SDK docs](https://crouter.docs.northlight.ai/docs/sdk) and the [recipes](https://crouter.docs.northlight.ai/docs/sdk/guides).
127
127
 
128
128
  ## Run it in a container
129
129
 
@@ -137,7 +137,7 @@ The image runs `crtrd` headless behind TCP; every request needs the bearer token
137
137
 
138
138
  ## Extend crouter
139
139
 
140
- An application exposes typed operations as native `crtr` commands with [`@north-light/plugin`](https://github.com/Northlight-Systems/crouter/tree/main/packages/crouter-plugin): define a command tree once and it produces the HTTP handler and the archive that `crtr pkg plugin install --endpoint <url> --name <name>` reads. The [plugin docs](https://docs.northlight.ai/docs/plugin) cover commands, parameters, output, errors and deployment.
140
+ An application exposes typed operations as native `crtr` commands with [`@north-light/plugin`](https://github.com/Northlight-Systems/crouter/tree/main/packages/crouter-plugin): define a command tree once and it produces the HTTP handler and the archive that `crtr pkg plugin install --endpoint <url> --name <name>` reads. The [plugin docs](https://crouter.docs.northlight.ai/docs/plugin) cover commands, parameters, output, errors and deployment.
141
141
 
142
142
  Ready-made plugins live in the [official marketplace](https://github.com/crouton-labs/crouter-official-marketplace): `exa-search`, `capture`, `tsym`, `bird`, `github-asset`, `dev`, `vercel`, `fly`, `railway`, `cloudflare`, and others. The first-run installer offers to register it.
143
143
 
@@ -152,7 +152,7 @@ Ready-made plugins live in the [official marketplace](https://github.com/crouton
152
152
  | [`@north-light/env-docker`](https://www.npmjs.com/package/@north-light/env-docker) | Start or attach to a `crtrd` container | [`packages/crouter-env-docker`](https://github.com/Northlight-Systems/crouter/tree/main/packages/crouter-env-docker) |
153
153
  | [`@north-light/identity`](https://www.npmjs.com/package/@north-light/identity) | Verify access tokens and evaluate scopes | [`packages/crouter-identity`](https://github.com/Northlight-Systems/crouter/tree/main/packages/crouter-identity) |
154
154
 
155
- Most applications need only `@north-light/sdk`. The other repository contents: [`docs/`](https://github.com/Northlight-Systems/crouter/tree/main/docs) is the documentation source, [`apps/docs`](https://github.com/Northlight-Systems/crouter/tree/main/apps/docs) is the TanStack Start site that publishes it at docs.northlight.ai, [`docker/`](https://github.com/Northlight-Systems/crouter/tree/main/docker) builds the `crtrd` image, and [`examples/`](https://github.com/Northlight-Systems/crouter/tree/main/examples) holds the SDK recipes and a localhost demo.
155
+ Most applications need only `@north-light/sdk`. The other repository contents: [`docs/`](https://github.com/Northlight-Systems/crouter/tree/main/docs) is the documentation source, [`apps/docs`](https://github.com/Northlight-Systems/crouter/tree/main/apps/docs) is the TanStack Start site that publishes it at crouter.docs.northlight.ai, [`docker/`](https://github.com/Northlight-Systems/crouter/tree/main/docker) builds the `crtrd` image, and [`examples/`](https://github.com/Northlight-Systems/crouter/tree/main/examples) holds the SDK recipes and a localhost demo.
156
156
 
157
157
  ## Develop
158
158
 
@@ -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.6";
2
+ export declare const CONTRACT_LEVEL = "1.7";
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`. */
@@ -1 +1 @@
1
- var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.6",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};
1
+ var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.7",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};
@@ -69,14 +69,14 @@ Not breaking: no type or wire shape an app reads is removed or changed.
69
69
 
70
70
  ### Monthly spend cap enforced; `usage.get` (additive; one behavior change)
71
71
 
72
- - **Behavior change:** user-paid model calls (`/v1/llm`) and provider calls now count against the grant's `spend_cap` (from `grant.get()`; crouter cloud sets $50) for the current UTC month. Once the month reaches it, the next call answers 402 `cap_exhausted` (a `RateLimitError`, `retryable: false`, `userAction: 'raise_cap'`, `resetAt` the first instant of next month) before any model or provider is called. A run whose model call is refused this way ends with outcome reason `cap_exhausted`. The check runs before each call, so the call that crosses the cap still completes. App-paid usage never counts. A grant with `spend_cap: null` is not capped.
72
+ - **Behavior change:** user-paid model calls (`/v1/llm`) and provider calls now count against the grant's `spend_cap` (from `grant.get()`; Northlight sets $50) for the current UTC month. Once the month reaches it, the next call answers 402 `cap_exhausted` (a `RateLimitError`, `retryable: false`, `userAction: 'raise_cap'`, `resetAt` the first instant of next month) before any model or provider is called. A run whose model call is refused this way ends with outcome reason `cap_exhausted`. The check runs before each call, so the call that crosses the cap still completes. App-paid usage never counts. A grant with `spend_cap: null` is not capped.
73
73
  - New `client.usage.get({month?})` (`month` is `YYYY-MM`, UTC; default the current month) answers `SpendUsageDTO`: `usd_equivalent` (user-paid spend, provider receipts included), `by_model`, `app_paid`, `spend_cap` and `reset_at`. See [[crouter-sdk/client]].
74
74
  - A completion's `usage.usd_equivalent` is now the served model's list-price cost; before it was always `null`. It stays `null` for a model with no list price, and that usage adds nothing to the month.
75
75
 
76
76
  ### `npx @north-light/sdk env` (additive); keygen on an unjoined machine exits 2
77
77
 
78
78
  - New `npx @north-light/sdk env --app <client id> [--out <path>]` writes `CROUTER_ISSUER`, `CROUTER_CLIENT_ID` and `APP_ORIGIN` from `crtr cloud apps get` into `local/app.env`, keeping every other line. See [[crouter-sdk/getting-started]].
79
- - `keygen --app` on a machine whose `crtr` isn't joined to a crouter cloud account now prints the join step and the manual steps and exits 2. A missing `crtr` keeps its message and exit 0.
79
+ - `keygen --app` on a machine whose `crtr` isn't joined to a Northlight account now prints the join step and the manual steps and exits 2. A missing `crtr` keeps its message and exit 0.
80
80
 
81
81
  ### One deadline per SDK call, and a deadline on every OAuth/JWKS fetch (additive, with one behaviour change)
82
82
 
@@ -92,7 +92,7 @@ Not breaking: no type or wire shape an app reads is removed or changed. Apps pin
92
92
 
93
93
  ### `@north-light/sdk/testing`: isolated runs start, `FakeCall.runId`, one `runs` shape (one breaking change)
94
94
 
95
- - `runs.start({isolated: true})` now starts a run on `FakeCloud`, as it does on a crouter cloud guest; it used to answer 503 `isolated_unavailable`. `run.params.isolated` records it, and a `cwd` outside the run's root folder is 400 `param: cwd`, as the runtime checks it. The new option `isolatedRuns: 'unavailable'` restores the 503 for testing that path. If you worked around the refusal by deleting `body.isolated` in an intercept, remove the intercept.
95
+ - `runs.start({isolated: true})` now starts a run on `FakeCloud`, as it does on a Northlight guest; it used to answer 503 `isolated_unavailable`. `run.params.isolated` records it, and a `cwd` outside the run's root folder is 400 `param: cwd`, as the runtime checks it. The new option `isolatedRuns: 'unavailable'` restores the 503 for testing that path. If you worked around the refusal by deleting `body.isolated` in an intercept, remove the intercept.
96
96
  - `FakeCall` gains `runId` and `questionId` (the ids the path addresses, else `null`), so tests no longer split `path`.
97
97
  - `FakeCall.userId` is now the bearer's person whenever the cloud knows them, including a person with no refresh token; it is `null` only for a missing or unknown bearer.
98
98
  - **Breaking:** `cloud.user(id).runs` is now `{all, respond}`, the same shape as `cloud.runs`, instead of an array carrying `respond`. Replace `user.runs[i]`, `user.runs.find(...)` and `[...user.runs]` with `user.runs.all…`.
@@ -106,7 +106,7 @@ New `@north-light/sdk/testing`: `FakeCloud`, a fake directory and runtime your r
106
106
 
107
107
  ### `oauth.accountAppUrl()` (additive), and a failed discovery read is retried (behaviour fix)
108
108
 
109
- - New `OAuth2Client.accountAppUrl()` returns the app's page on the signed-in person's crouter cloud account, as the directory's discovery document advertises it (`account_app_uri_template`). Use it instead of building `<issuer>/account/apps/<client id>` yourself. It needs no user or connection and throws `CrouterError` naming the fault when the directory advertises no template or an invalid one. See [[crouter-sdk/client]].
109
+ - New `OAuth2Client.accountAppUrl()` returns the app's page on the signed-in person's Northlight account, as the directory's discovery document advertises it (`account_app_uri_template`). Use it instead of building `<issuer>/account/apps/<client id>` yourself. It needs no user or connection and throws `CrouterError` naming the fault when the directory advertises no template or an invalid one. See [[crouter-sdk/client]].
110
110
  - **Behaviour fix:** `OAuth2Client`'s discovery read now times out after 10 seconds, and a read that failed or timed out is retried by the next call that needs it. Before, one failed read was kept for the client's life, so every later `authorizeUrl`, `exchangeCode`, refresh and `verifyIdToken` failed until the process restarted, and a hung directory stalled them indefinitely. A successful read is still kept for the client's life.
111
111
 
112
112
  ## 0.3.405
@@ -199,7 +199,7 @@ The SDK now sends `Crouter-Minimum-Runtime-Version: <SDK_VERSION>` on every requ
199
199
  - `refusalOf` returns a `Refusal` only for an error the SDK raised while refreshing a stored connection, not for every directory-origin `AuthenticationError`.
200
200
  - Concurrent refreshes in one process are shared per user and refresh token, so a client holding a newer token never joins an older refusal.
201
201
 
202
- **Callers to update:** crouter cloud's chat app (`first-party-apps/chat/src/directory-client.ts`, which builds `oauth.client(connection, store)`) and the context app (`first-party-apps/context/req-run.mjs`). Any app that called `refreshIdleConnections(store, …)`, read `RefreshFailure.reason`, or imported `refreshRefusal` must move to the shapes above.
202
+ **Callers to update:** Northlight's chat app (`first-party-apps/chat/src/directory-client.ts`, which builds `oauth.client(connection, store)`) and the context app (`first-party-apps/context/req-run.mjs`). Any app that called `refreshIdleConnections(store, …)`, read `RefreshFailure.reason`, or imported `refreshRefusal` must move to the shapes above.
203
203
 
204
204
  ### `RunWatcher`: learn when a background run ends (additive)
205
205
 
@@ -41,6 +41,7 @@ export declare function startRun(principal: AppPrincipal, input: {
41
41
  profile?: string;
42
42
  isolated?: boolean;
43
43
  cwd?: string;
44
+ model?: string;
44
45
  output_schema?: Record<string, unknown>;
45
46
  message?: string;
46
47
  }, idempotencyKey?: string, resolveAttachments?: AttachmentResolver): Promise<Record<string, unknown>>;
@@ -1,12 +1,12 @@
1
- var Se=Object.defineProperty;var u=(e,t)=>Se(e,"name",{value:t,configurable:!0});import{prepared as E}from"../sqlite-statements.js";import{coveredByAny as Ne}from"../../shared/env.js";import{createHash as $}from"node:crypto";import{ApiError as ve}from"../../api/errors.js";import{isAbsolute as J,relative as Oe,resolve as be,sep as De,join as Te}from"node:path";import{SessionManager as Ie}from"@earendil-works/pi-coding-agent";import{covers as W}from"@north-light/identity";import{openDb as Y,withCanvasWrite as A}from"../canvas/db.js";import{deleteNodeWithOwned as Me}from"../graph/deletion.js";import{getById as Ae}from"../graph/objects.js";import{repairRunConversations as G}from"../canvas/sessions.js";import{getNode as S,nodeFiles as Le,removeNodeFiles as Ce,updateNode as xe}from"../canvas/canvas.js";import{fullName as ke}from"../canvas/labels.js";import{renameWindow as We}from"../runtime/placement.js";import{liveExecutionForNode as X}from"../canvas/executions.js";import{conversationStore as z,isConversationNotFound as Fe}from"../conversation-store/index.js";import{enqueueNodeMail as He}from"../feed/messages.js";import{newestReportBody as qe}from"../feed/reports.js";import{ensureAppProfile as Ue,loadExactProfileManifest as Be}from"../profiles/manifest.js";import{stopNodeSandboxes as Q}from"../spaces/stop.js";import{BrokerUnreachableError as Pe}from"../runtime/broker-request.js";import{interruptLive as je}from"../runtime/interactive-deliver.js";import{isBrokerLive as V}from"../runtime/model-swap.js";import{newNodeId as $e}from"../runtime/nodes.js";import{writeGoal as Je}from"../runtime/kickoff.js";import{reviveNode as F}from"../runtime/revive.js";import{spawnChildPrepared as Ye}from"../runtime/spawn.js";import{isolatedBrokerLauncherAvailable as Ge}from"../runtime/host.js";import{resolveRunKind as Xe}from"../runtime/launch-target.js";import{cycleAwareMessages as ze}from"../runtime/session-cycles.js";import{originFor as Z}from"../runtime/stamp/protocol.js";import{readOutputRequest as Qe,readOutputResult as K}from"../runtime/structured-output.js";import{admitLegacyStructuredOutput as ee}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as Ve}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as Ze}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as Ke}from"../../daemon/runs/observer.js";import{closeNode as te}from"../runtime/close.js";import{parseScope as et}from"@north-light/identity";import{appendRunEvent as ne,subscribeRunEvents as tt}from"./events.js";import{runActivity as nt}from"./activity.js";import{cancelOpenQuestions as se,openQuestionIds as re}from"./questions.js";import{appSpace as oe}from"../layout.js";import{ensureAppSpace as st}from"../spaces/space.js";import{assertUploadsUsable as ie,resolveStorageUrls as rt}from"../storage/attachments.js";import{storageClient as ot,storageConfig as it}from"../storage/client.js";import{deleteUploadRow as at,insertUploadUses as ae,releaseRunUploads as dt}from"../storage/tables.js";import{abortProviderCalls as de}from"../providers/inflight.js";const ce=u((e,t,r)=>rt(t,e,r),"defaultResolver"),ct=3e3;function an(e){return Number(E(e,"UPDATE idempotency_keys SET grantee = 'app:terminal' WHERE grantee = 'user:local'").run().changes)}u(an,"rewriteLocalIdempotencyGrantee");class R extends Error{static{u(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,r,n,i,c,a,o=t===503){super(n),this.status=t,this.code=r,this.param=i,this.details=c,this.scope=a,this.retryable=o}}const O=u((e,t)=>new R(400,"invalid_request",e,t),"invalid"),H=u(()=>new R(404,"not_found","run not found"),"missing"),q=u(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),b=u(()=>Y(),"db");function L(e,t,r=!1){const n=S(e);if(!n||n.parent!==null||n.run_id!==e||n.grantee!==t||!r&&n.deleting_at)throw H();return n}u(L,"ownedRun");const U=["waiting_on_user","idle","settled"];function I(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=E(b(),`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
1
+ var Se=Object.defineProperty;var m=(e,t)=>Se(e,"name",{value:t,configurable:!0});import{prepared as w}from"../sqlite-statements.js";import{coveredByAny as Ne}from"../../shared/env.js";import{createHash as J}from"node:crypto";import{ApiError as ve}from"../../api/errors.js";import{CrtrError as Oe}from"../errors.js";import{isAbsolute as Y,relative as be,resolve as De,sep as Te,join as Ie}from"node:path";import{SessionManager as Me}from"@earendil-works/pi-coding-agent";import{covers as W}from"@north-light/identity";import{openDb as G,withCanvasWrite as L}from"../canvas/db.js";import{deleteNodeWithOwned as Ae}from"../graph/deletion.js";import{getById as Le}from"../graph/objects.js";import{repairRunConversations as X}from"../canvas/sessions.js";import{getNode as S,nodeFiles as Ce,removeNodeFiles as xe,updateNode as ke}from"../canvas/canvas.js";import{fullName as We}from"../canvas/labels.js";import{renameWindow as Fe}from"../runtime/placement.js";import{liveExecutionForNode as z}from"../canvas/executions.js";import{conversationStore as Q,isConversationNotFound as He}from"../conversation-store/index.js";import{enqueueNodeMail as qe}from"../feed/messages.js";import{newestReportBody as Ue}from"../feed/reports.js";import{ensureAppProfile as Pe,loadExactProfileManifest as Be}from"../profiles/manifest.js";import{stopNodeSandboxes as V}from"../spaces/stop.js";import{BrokerUnreachableError as $e}from"../runtime/broker-request.js";import{interruptLive as je}from"../runtime/interactive-deliver.js";import{isBrokerLive as Z}from"../runtime/model-swap.js";import{newNodeId as Je}from"../runtime/nodes.js";import{writeGoal as Ye}from"../runtime/kickoff.js";import{reviveNode as F}from"../runtime/revive.js";import{spawnChildPrepared as Ge}from"../runtime/spawn.js";import{isPortableModelToken as Xe}from"../runtime/launch.js";import{expandModelCandidates as ze,parseModelSpec as Qe}from"../model-routes.js";import{isolatedBrokerLauncherAvailable as Ve}from"../runtime/host.js";import{resolveRunKind as Ze}from"../runtime/launch-target.js";import{cycleAwareMessages as Ke}from"../runtime/session-cycles.js";import{originFor as K}from"../runtime/stamp/protocol.js";import{readOutputRequest as et,readOutputResult as ee}from"../runtime/structured-output.js";import{admitLegacyStructuredOutput as te}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as tt}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as nt}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as st}from"../../daemon/runs/observer.js";import{closeNode as ne}from"../runtime/close.js";import{parseScope as rt}from"@north-light/identity";import{appendRunEvent as se,subscribeRunEvents as ot}from"./events.js";import{runActivity as it}from"./activity.js";import{cancelOpenQuestions as re,openQuestionIds as oe}from"./questions.js";import{appSpace as H}from"../layout.js";import{ensureAppSpace as dt}from"../spaces/space.js";import{assertUploadsUsable as ie,resolveStorageUrls as at}from"../storage/attachments.js";import{storageClient as ct,storageConfig as ut}from"../storage/client.js";import{deleteUploadRow as mt,insertUploadUses as de,releaseRunUploads as lt}from"../storage/tables.js";import{abortProviderCalls as ae}from"../providers/inflight.js";const ce=m((e,t,r)=>at(t,e,r),"defaultResolver"),_t=3e3;function pn(e){return Number(w(e,"UPDATE idempotency_keys SET grantee = 'app:terminal' WHERE grantee = 'user:local'").run().changes)}m(pn,"rewriteLocalIdempotencyGrantee");class R extends Error{static{m(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,r,s,i,c,d,o=t===503){super(s),this.status=t,this.code=r,this.param=i,this.details=c,this.scope=d,this.retryable=o}}const N=m((e,t)=>new R(400,"invalid_request",e,t),"invalid"),q=m(()=>new R(404,"not_found","run not found"),"missing"),U=m(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),b=m(()=>G(),"db");function C(e,t,r=!1){const s=S(e);if(!s||s.parent!==null||s.run_id!==e||s.grantee!==t||!r&&s.deleting_at)throw q();return s}m(C,"ownedRun");const P=["waiting_on_user","idle","settled"];function I(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=w(b(),`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
2
2
  (n.last_settled_at IS NULL OR EXISTS (
3
3
  SELECT 1 FROM run_events e WHERE e.run_id = ? AND e.node_id = n.node_id AND e.type = 'node.turn.started'
4
4
  AND e.sequence_number > COALESCE((SELECT MAX(c.sequence_number) FROM run_events c
5
5
  WHERE c.run_id = e.run_id AND c.node_id = e.node_id AND c.type = 'node.turn.completed'), 0)
6
- )) LIMIT 1`).get(e.node_id,e.node_id),r=E(b(),"SELECT 1 FROM deliveries WHERE destination_id IN (SELECT node_id FROM nodes WHERE run_id = ?) AND state IN ('armed', 'pending') AND wake = 1 LIMIT 1").get(e.node_id);return t||r||!e.last_settled_at||Ve(e)?"running":re(e.node_id).length?"waiting_on_user":"idle"}u(I,"runStatus");function dn(e){const t=S(e);return t!==null&&I(t)==="running"}u(dn,"runIsRunning");function M(e){G(e.node_id),e=S(e.node_id)??e;const t=I(e),r=t==="settled"?[]:re(e.node_id),n=qe(e.node_id)||void 0,i=t==="settled"?Ze(e):null,c=i?.kind==="result"?K(e.node_id,e.grantee):void 0;return{run_id:e.node_id,name:e.name,status:t,...i?{outcome:i}:{},...c===void 0?{}:{result:c},...n!==void 0?{last_report:n}:{},pending_question_ids:r,...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(q())),payer:"user",scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:nt(e.node_id,t)}}u(M,"runObject");const B=864e5,ue=255;function cn(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>ue)throw O(`Idempotency-Key must be 1-${ue} characters`);return e}}u(cn,"idempotencyKeyOf");function C(e,t,r,n=Y()){const i=E(n,"SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!i||Date.now()-Date.parse(i.created_at)>=B)){if(i.body_hash!==r)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:i.run_id,response:JSON.parse(i.response_json)}}}u(C,"findIdempotent");function me(e,t,r,n,i,c,a){E(e,"DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,r,new Date(Date.now()-B).toISOString()),E(e,"INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,r,n,i,JSON.stringify(c),a)}u(me,"recordIdempotent");function un(e=Date.now()){return A(t=>Number(E(t,"DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-B).toISOString()).changes))}u(un,"pruneExpiredIdempotencyKeys");function le(e,t,r,n,i){const c=n.text;let a=0;return{messageId:He(t,{kind:"message",label:c.slice(0,120),senderNodeId:"human",senderName:r,bodyJson:i?{body:c}:{body:c,start_turn:!1},urgency:i?"normal":"deferred",wake:i?1:0,withinEnqueue:u((d,p)=>{n.uses.length&&(ie(r,n.uses,d),ae(n.uses.map(({upload_id:y,path:g})=>({upload_id:y,path:g,run_id:t})),d)),a=ne(d,t,t,"node.message.accepted",{message_id:p,start_turn:i}).sequence_number},"withinEnqueue")}).messageId,sequence:a}}u(le,"acceptRunMessage");class _e extends Error{static{u(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}async function mn(e,t,r,n=ce){if(t.isolated&&!Ge())throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");const i=$("sha256").update(JSON.stringify(t)).digest("hex");if(r){const s=C(e.grantee,r,i);if(s){const m=L(s.run_id,e.grantee);return!m.pi_pid&&I(m)==="running"&&(t.output_schema!==void 0&&!m.outcome_at&&Qe(m.node_id).state==="absent"&&K(m.node_id,m.grantee)===void 0&&ee(m.node_id,"terminal",t.output_schema),await F(m.node_id,{resume:!0,capacity:"freeze"})),s.response}}await st(e.grantee);const c=t.profile===void 0?Ue(e.grantee):Be(t.profile),a=c.manifest.grantee??"app:terminal";if(a!==e.grantee){const s=`crtr:memory:rw:${a}:profile:${c.profileId}`;if(!e.resolved.some(m=>W(m,s))||t.scopes!==null&&!t.scopes.some(m=>W(m,s)))throw new R(403,"scope_missing",`missing scope ${s}`,"profile",void 0,s)}const o=$e(),d=Te(oe(e.grantee).nodes,o);if(t.isolated&&t.cwd!==void 0){const s=Oe(d,be(t.cwd));if(!J(t.cwd)||s===".."||s.startsWith(`..${De}`)||J(s))throw O("cwd must be inside the isolated root node folder","cwd")}const p=await n(o,e.grantee,t.prompt),y=p.text;let g;try{g=t.message===void 0?void 0:await n(o,e.grantee,t.message)}catch(s){throw p.cleanup(),s}let f;try{await Ye({kind:Xe(c),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??d:oe(e.grantee).files,profile:c.profileId,prompt:y,nodeId:o,isolated:t.isolated??!1,...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:o,event_cursor:1,usage_json:JSON.stringify(q())},withinCreate:u((s,m)=>{if(r){const w=C(e.grantee,r,i,s);if(w)throw new _e(w)}const h=p.uses;if(h.length&&(ie(e.grantee,h,s),ae(h.map(({upload_id:w,path:x})=>({upload_id:w,path:x,run_id:o})),s)),Je(o,y),f={run_id:o,node_id:o,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:"user",status:"running",created_at:m.created,metadata:t.metadata,sequence_number:0},ne(s,o,o,"run.started",{scopes:t.scopes,payer:"user",usage:q()}),g){const w=le(s,o,e.grantee,g,!1);f.message={run_id:o,message_id:w.messageId,sequence_number:w.sequence}}r&&me(s,e.grantee,r,i,o,f,m.created)},"withinCreate")},t.output_schema===void 0?void 0:s=>{ee(s,"terminal",t.output_schema)})}catch(s){if(S(o)||(p.cleanup(),g?.cleanup()),s instanceof _e)return s.winner.response;if(s instanceof ve&&(s.code==="isolated_run"||s.code==="invalid_request")){const m=s.details&&typeof s.details=="object"&&"param"in s.details&&typeof s.details.param=="string"?s.details.param:void 0;throw new R(s.status,s.code,s.message,m)}throw s}if(!f)throw new Error("run creation did not commit");return f}u(mn,"startRun");function ln(e,t){let r;if(t.after)try{const o=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!o||typeof o!="object"||!("activity"in o)||typeof o.activity!="string"||!("id"in o)||typeof o.id!="string")throw new Error("invalid cursor");r={activity:o.activity,id:o.id}}catch{throw O("invalid list cursor","after")}const i=b().prepare(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
6
+ )) LIMIT 1`).get(e.node_id,e.node_id),r=w(b(),"SELECT 1 FROM deliveries WHERE destination_id IN (SELECT node_id FROM nodes WHERE run_id = ?) AND state IN ('armed', 'pending') AND wake = 1 LIMIT 1").get(e.node_id);return t||r||!e.last_settled_at||tt(e)?"running":oe(e.node_id).length?"waiting_on_user":"idle"}m(I,"runStatus");function fn(e){const t=S(e);return t!==null&&I(t)==="running"}m(fn,"runIsRunning");function A(e){X(e.node_id),e=S(e.node_id)??e;const t=I(e),r=t==="settled"?[]:oe(e.node_id),s=Ue(e.node_id)||void 0,i=t==="settled"?nt(e):null,c=i?.kind==="result"?ee(e.node_id,e.grantee):void 0;return{run_id:e.node_id,name:e.name,status:t,...i?{outcome:i}:{},...c===void 0?{}:{result:c},...s!==void 0?{last_report:s}:{},pending_question_ids:r,...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(U())),payer:"user",scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:it(e.node_id,t)}}m(A,"runObject");const B=864e5,ue=255;function gn(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>ue)throw N(`Idempotency-Key must be 1-${ue} characters`);return e}}m(gn,"idempotencyKeyOf");function x(e,t,r,s=G()){const i=w(s,"SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!i||Date.now()-Date.parse(i.created_at)>=B)){if(i.body_hash!==r)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:i.run_id,response:JSON.parse(i.response_json)}}}m(x,"findIdempotent");function me(e,t,r,s,i,c,d){w(e,"DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,r,new Date(Date.now()-B).toISOString()),w(e,"INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,r,s,i,JSON.stringify(c),d)}m(me,"recordIdempotent");function En(e=Date.now()){return L(t=>Number(w(t,"DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-B).toISOString()).changes))}m(En,"pruneExpiredIdempotencyKeys");function le(e,t,r,s,i){const c=s.text;let d=0;return{messageId:qe(t,{kind:"message",label:c.slice(0,120),senderNodeId:"human",senderName:r,bodyJson:i?{body:c}:{body:c,start_turn:!1},urgency:i?"normal":"deferred",wake:i?1:0,withinEnqueue:m((a,p)=>{s.uses.length&&(ie(r,s.uses,a),de(s.uses.map(({upload_id:y,path:E})=>({upload_id:y,path:E,run_id:t})),a)),d=se(a,t,t,"node.message.accepted",{message_id:p,start_turn:i}).sequence_number},"withinEnqueue")}).messageId,sequence:d}}m(le,"acceptRunMessage");class _e extends Error{static{m(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}async function wn(e,t,r,s=ce){if(t.isolated&&!Ve())throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");const i=J("sha256").update(JSON.stringify(t)).digest("hex");if(r){const n=x(e.grantee,r,i);if(n){const u=C(n.run_id,e.grantee);return!u.pi_pid&&I(u)==="running"&&(t.output_schema!==void 0&&!u.outcome_at&&et(u.node_id).state==="absent"&&ee(u.node_id,u.grantee)===void 0&&te(u.node_id,"terminal",t.output_schema),await F(u.node_id,{resume:!0,capacity:"freeze"})),n.response}}await dt(e.grantee);const c=t.profile===void 0?Pe(e.grantee):Be(t.profile),d=c.manifest.grantee??"app:terminal";if(d!==e.grantee){const n=`crtr:memory:rw:${d}:profile:${c.profileId}`;if(!e.resolved.some(u=>W(u,n))||t.scopes!==null&&!t.scopes.some(u=>W(u,n)))throw new R(403,"scope_missing",`missing scope ${n}`,"profile",void 0,n)}if(t.model!==void 0){const n=t.model;if(Xe(n)){if(n.includes("/")){const u=n.lastIndexOf("/"),h=n.slice(0,u),g=n.slice(u+1);if(["ultra","strong","medium","light"].includes(g)&&ze({kind:"logical",family:h,strength:g,spec:n},H(e.grantee).files,c.profileId).every(M=>M.family!==h))throw N(`model '${n}' names a model family that is not configured in this daemon`,"model")}}else if(Qe(n)===void 0)throw N(`model '${n}' is not a strength (ultra, strong, medium, light), a family/strength, or a provider/model id`,"model")}const o=Je(),a=Ie(H(e.grantee).nodes,o);if(t.isolated&&t.cwd!==void 0){const n=be(a,De(t.cwd));if(!Y(t.cwd)||n===".."||n.startsWith(`..${Te}`)||Y(n))throw N("cwd must be inside the isolated root node folder","cwd")}const p=await s(o,e.grantee,t.prompt),y=p.text;let E;try{E=t.message===void 0?void 0:await s(o,e.grantee,t.message)}catch(n){throw p.cleanup(),n}let f;try{await Ge({kind:Ze(c),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??a:H(e.grantee).files,profile:c.profileId,prompt:y,nodeId:o,isolated:t.isolated??!1,...t.model===void 0?{}:{model:t.model},...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:o,event_cursor:1,usage_json:JSON.stringify(U())},withinCreate:m((n,u)=>{if(r){const g=x(e.grantee,r,i,n);if(g)throw new _e(g)}const h=p.uses;if(h.length&&(ie(e.grantee,h,n),de(h.map(({upload_id:g,path:M})=>({upload_id:g,path:M,run_id:o})),n)),Ye(o,y),f={run_id:o,node_id:o,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:"user",status:"running",created_at:u.created,metadata:t.metadata,sequence_number:0},se(n,o,o,"run.started",{scopes:t.scopes,payer:"user",usage:U()}),E){const g=le(n,o,e.grantee,E,!1);f.message={run_id:o,message_id:g.messageId,sequence_number:g.sequence}}r&&me(n,e.grantee,r,i,o,f,u.created)},"withinCreate")},t.output_schema===void 0?void 0:n=>{te(n,"terminal",t.output_schema)})}catch(n){if(S(o)||(p.cleanup(),E?.cleanup()),n instanceof _e)return n.winner.response;if(t.model!==void 0&&n instanceof Oe&&n.code==="usage")throw N(n.message,"model");if(n instanceof ve&&(n.code==="isolated_run"||n.code==="invalid_request")){const u=n.details&&typeof n.details=="object"&&"param"in n.details&&typeof n.details.param=="string"?n.details.param:void 0;throw new R(n.status,n.code,n.message,u)}throw n}if(!f)throw new Error("run creation did not commit");return f}m(wn,"startRun");function hn(e,t){let r;if(t.after)try{const o=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!o||typeof o!="object"||!("activity"in o)||typeof o.activity!="string"||!("id"in o)||typeof o.id!="string")throw new Error("invalid cursor");r={activity:o.activity,id:o.id}}catch{throw N("invalid list cursor","after")}const i=b().prepare(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
7
7
  WHERE run_id = node_id AND deleting_at IS NULL ${e===null?"":"AND grantee = ?"}
8
- ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e]).filter(({node_id:o})=>!t.visible||t.visible(o)).filter(({activity:o,node_id:d})=>!r||o<r.activity||o===r.activity&&d<r.id).map(o=>({root:S(o.node_id),activity:o.activity})).filter(({root:o})=>!t.status||I(o)===t.status),c=i.slice(0,t.limit),a=c.at(-1);return{runs:c.map(({root:o})=>M(o)),next:i.length>t.limit&&a?Buffer.from(JSON.stringify({activity:a.activity,id:a.root.node_id})).toString("base64url"):null}}u(ln,"listRuns");async function _n(e,t,r){const n=L(e,t),i=u(()=>M(L(e,t)),"read");return!r||U.includes(I(n))?i():new Promise((c,a)=>{const o=u(()=>{clearTimeout(y),clearInterval(p),d();try{c(i())}catch(g){a(g)}},"finish"),d=tt(e,()=>{try{U.includes(String(i().status))&&o()}catch(g){clearTimeout(y),clearInterval(p),d(),a(g)}}),p=setInterval(()=>{U.includes(String(i().status))&&o()},250),y=setTimeout(o,r*1e3)})}u(_n,"waitRun");async function pn(e,t,r=ce,n=!0,i){const c=e.status==="canceled"||e.outcome_reason==="closed",a=e.grantee??"app:terminal",o=$("sha256").update(JSON.stringify(["run.message",e.node_id,t,n])).digest("hex"),d=u(async f=>{const s=S(e.node_id),m=E(b(),"SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(f.message_id));return s&&!c&&n&&!s.pi_pid&&m&&await F(e.node_id,{resume:!0,capacity:"freeze"}),f},"replay");if(i){const f=C(a,i,o);if(f)return d(f.response)}if(c)throw new R(409,"run_settled","canceled run cannot receive a message");const p=await r(e.node_id,a,t),y=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;let g;try{g=A(f=>{if(i){const h=C(a,i,o,f);if(h)return{replayed:h.response}}y?.(e.node_id);const s=le(f,e.node_id,a,p,n),m={run_id:e.node_id,message_id:s.messageId,sequence_number:s.sequence};return i&&me(f,a,i,o,e.node_id,m,new Date().toISOString()),{result:m}})}catch(f){throw p.cleanup(),f}return"replayed"in g?(p.cleanup(),d(g.replayed)):(n&&!e.pi_pid&&await F(e.node_id,{resume:!0,capacity:"freeze"}),g.result)}u(pn,"messageRun");async function ut(e){const t=u(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let r=0;r<2;r++){const n=S(e);if(!n)throw H();if(!V(n)){if(n.streaming)throw t();return}const i=X(e)?.executionId;try{await je(e)==="turn"&&de({nodes:[e]});return}catch(c){if(!(c instanceof Pe))throw c;const a=S(e);if(!a)throw H();if(!a.streaming)return;if(r===0&&V(a)&&X(e)?.executionId!==i)continue;throw t()}}throw t()}u(ut,"interruptNode");async function fn(e,t="stop"){const r=t==="stop"?E(b(),"SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id).map(({node_id:n})=>n):[e.node_id];return await Promise.all(r.map(n=>ut(n))),M(S(e.node_id)??e)}u(fn,"interruptRun");async function gn(e,t={}){const r=E(b(),"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(a=>a.node_id);de({run:e.node_id});const n=[];if(I(e)!=="settled")n.push(...te(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"}).closed);else for(const a of r){if(a===e.node_id)continue;const o=S(a);o&&o.status!=="done"&&o.status!=="canceled"&&o.status!=="dead"&&n.push(...te(a,{preserveEmpty:!0,stopReason:"run_canceled"}).closed)}if(await se({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(a=>Q(a,"run_canceled")))).some(a=>!a.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const c=S(e.node_id);return M(c??e)}u(gn,"cancelRun");const pe=200;function En(e,t){if(typeof t!="string"||!t.trim())throw O("name is required","name");const r=t.trim();if(r.length>pe)throw O(`name exceeds ${pe} characters`,"name");const n=xe(e.node_id,{name:r});return n.window&&We(n.window,ke(n)),M(n)}u(En,"renameRun");async function wn(e,t={config:it,client:ot}){await se({run_id:e.node_id},"the run was deleted");const r=A(d=>(E(d,"UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),E(d,"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(p=>p.node_id)));if((await Promise.all(r.map(d=>Q(d,"run_deleted",{deleting:!0})))).some(d=>!d.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const i=r.map(d=>{const p=Le(d);if(p.app===null)throw new Error(`run node missing during deletion: ${d}`);return p}),c=E(b(),"SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:d}of c)try{z().delete(d)}catch(p){if(!Fe(p))throw p}const a=A(d=>{for(const f of r){const s=Ae(d,f);s!==null&&s.deleted_at===null&&Me(d,s)}const p=dt([e.node_id],d);E(d,"DELETE FROM run_events WHERE run_id = ?").run(e.node_id),E(d,"DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const y=r.map(()=>"?").join(","),g=d.prepare(`SELECT message_id FROM messages WHERE sender_node_id IN (${y}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${y})`).all(...r,...r);d.prepare(`DELETE FROM deliveries WHERE destination_id IN (${y})`).run(...r);for(const{message_id:f}of g)E(d,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(f,f);return p});for(const d of i)Ce(d);const{notifyRunDeleted:o}=await import("./events.js");return o(e.node_id),{uploads_pending:await mt(a,t)}}u(wn,"deleteRun");async function mt(e,t){if(!e.length||!t.config())return e.length;let r;try{r=t.client()}catch{return e.length}const n=new Set,i=e.map(a=>r.deleteUpload(a).then(()=>{at(a),n.add(a)},()=>{}));let c;return await Promise.race([Promise.all(i),new Promise(a=>{c=setTimeout(a,t.budgetMs??ct),c.unref?.()})]),clearTimeout(c),e.length-n.size}u(mt,"deleteReleasedUploads");function yn(e,t,r){L(e,t),G(e);const n=b();n.exec("BEGIN");try{const c=E(n,"SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e).map(s=>({node_id:s.node_id,parent_node_id:s.parent,conversations:E(n,"SELECT DISTINCT session_id FROM sessions WHERE node_id = ? ORDER BY created_at").all(s.node_id).map(({session_id:m})=>{const{header:h,entries:w}=z().read(m);if(r==="entries")return{conversation_id:m,header:h,entries:w};const x=Ie.inMemory(h.cwd,void 0,[h,...w]),fe=ze(x)??[],P=new Map(w.map((_,N)=>[_.id,N])),ge=w.filter(_=>_.type==="custom"&&_.customType==="crtr-system-prompt").map(_=>({id:_.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in _?_.data:void 0,timestamp:_.timestamp}})),j=w.filter(_=>_.type==="custom_message"&&_.customType==="app_message").map(_=>({id:_.id,message:{role:"custom",customType:"app_message",content:"content"in _?_.content:"",timestamp:_.timestamp}})),Ee=s.node_id===e?w.find(_=>{if(_.type!=="message"||_.message.role!=="user")return!1;const N=Z(w,_.message.timestamp);return N?.type==="runtime"&&N.kind==="kickoff"})?.id:void 0,we=[...fe.filter(({id:_})=>!j.some(N=>N.id===_)),...ge,...j].sort((_,N)=>(P.get(_.id)??Number.MAX_SAFE_INTEGER)-(P.get(N.id)??Number.MAX_SAFE_INTEGER)).map(({id:_,message:N})=>{const l=N,D=l.usage,k=l.role==="user"?Z(w,Number(l.timestamp)):null,ye=k?.type==="human"||_===Ee,T=l.role==="toolResult"?"tool_result":l.role==="bashExecution"?"bash_execution":l.role==="compactionSummary"?"compaction_summary":l.role==="branchSummary"?"branch_summary":l.role==="custom"&&l.customType==="crtr-system-prompt"?"system":l.role==="user"?ye?"user":"custom":l.role,he=T==="custom"?l.role==="custom"?l.customType:k?.type==="runtime"?k.kind:"unknown":void 0,Re=T==="system"&&l.customType==="crtr-system-prompt"?[{type:"text",text:l.data?.text??""}]:typeof l.content=="string"?[{type:"text",text:l.content}]:Array.isArray(l.content)?l.content.map(v=>v.type==="toolCall"?{type:"tool_call",tool_call_id:v.id,name:v.name,arguments:v.arguments}:v.type==="image"?{type:"image",mime_type:v.mimeType}:v.type==="thinking"?{type:"thinking",text:v.thinking}:v):[];return{entry_id:_,role:T,...T==="custom"?{custom_type:he}:{},time:typeof l.timestamp=="string"?l.timestamp:new Date(Number(l.timestamp??Date.now())).toISOString(),content:Re,...T==="assistant"?{model:l.model,provider:l.provider,stop_reason:l.stopReason,...l.errorMessage?{error_message:l.errorMessage}:{},...D?{usage:{input_tokens:D.input??0,output_tokens:D.output??0,cache_read_tokens:D.cacheRead??0,cache_write_tokens:D.cacheWrite??0,cost_usd:D.cost?.total??0}}:{}}:{},...T==="tool_result"?{tool_call_id:l.toolCallId,tool_name:l.toolName,is_error:l.isError}:{}}})??[];return{conversation_id:m,messages:we}})})),o=E(n,"SELECT event_cursor FROM nodes WHERE node_id = ?").get(e).event_cursor-1,d=new Map(c.map(s=>[s.node_id,Ke(s.node_id)])),p=E(n,`SELECT node_id, sequence_number FROM run_events
8
+ ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e]).filter(({node_id:o})=>!t.visible||t.visible(o)).filter(({activity:o,node_id:a})=>!r||o<r.activity||o===r.activity&&a<r.id).map(o=>({root:S(o.node_id),activity:o.activity})).filter(({root:o})=>!t.status||I(o)===t.status),c=i.slice(0,t.limit),d=c.at(-1);return{runs:c.map(({root:o})=>A(o)),next:i.length>t.limit&&d?Buffer.from(JSON.stringify({activity:d.activity,id:d.root.node_id})).toString("base64url"):null}}m(hn,"listRuns");async function yn(e,t,r){const s=C(e,t),i=m(()=>A(C(e,t)),"read");return!r||P.includes(I(s))?i():new Promise((c,d)=>{const o=m(()=>{clearTimeout(y),clearInterval(p),a();try{c(i())}catch(E){d(E)}},"finish"),a=ot(e,()=>{try{P.includes(String(i().status))&&o()}catch(E){clearTimeout(y),clearInterval(p),a(),d(E)}}),p=setInterval(()=>{P.includes(String(i().status))&&o()},250),y=setTimeout(o,r*1e3)})}m(yn,"waitRun");async function Rn(e,t,r=ce,s=!0,i){const c=e.status==="canceled"||e.outcome_reason==="closed",d=e.grantee??"app:terminal",o=J("sha256").update(JSON.stringify(["run.message",e.node_id,t,s])).digest("hex"),a=m(async f=>{const n=S(e.node_id),u=w(b(),"SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(f.message_id));return n&&!c&&s&&!n.pi_pid&&u&&await F(e.node_id,{resume:!0,capacity:"freeze"}),f},"replay");if(i){const f=x(d,i,o);if(f)return a(f.response)}if(c)throw new R(409,"run_settled","canceled run cannot receive a message");const p=await r(e.node_id,d,t),y=s&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;let E;try{E=L(f=>{if(i){const h=x(d,i,o,f);if(h)return{replayed:h.response}}y?.(e.node_id);const n=le(f,e.node_id,d,p,s),u={run_id:e.node_id,message_id:n.messageId,sequence_number:n.sequence};return i&&me(f,d,i,o,e.node_id,u,new Date().toISOString()),{result:u}})}catch(f){throw p.cleanup(),f}return"replayed"in E?(p.cleanup(),a(E.replayed)):(s&&!e.pi_pid&&await F(e.node_id,{resume:!0,capacity:"freeze"}),E.result)}m(Rn,"messageRun");async function pt(e){const t=m(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let r=0;r<2;r++){const s=S(e);if(!s)throw q();if(!Z(s)){if(s.streaming)throw t();return}const i=z(e)?.executionId;try{await je(e)==="turn"&&ae({nodes:[e]});return}catch(c){if(!(c instanceof $e))throw c;const d=S(e);if(!d)throw q();if(!d.streaming)return;if(r===0&&Z(d)&&z(e)?.executionId!==i)continue;throw t()}}throw t()}m(pt,"interruptNode");async function Sn(e,t="stop"){const r=t==="stop"?w(b(),"SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id).map(({node_id:s})=>s):[e.node_id];return await Promise.all(r.map(s=>pt(s))),A(S(e.node_id)??e)}m(Sn,"interruptRun");async function Nn(e,t={}){const r=w(b(),"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(d=>d.node_id);ae({run:e.node_id});const s=[];if(I(e)!=="settled")s.push(...ne(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"}).closed);else for(const d of r){if(d===e.node_id)continue;const o=S(d);o&&o.status!=="done"&&o.status!=="canceled"&&o.status!=="dead"&&s.push(...ne(d,{preserveEmpty:!0,stopReason:"run_canceled"}).closed)}if(await re({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...s])].map(d=>V(d,"run_canceled")))).some(d=>!d.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const c=S(e.node_id);return A(c??e)}m(Nn,"cancelRun");const pe=200;function vn(e,t){if(typeof t!="string"||!t.trim())throw N("name is required","name");const r=t.trim();if(r.length>pe)throw N(`name exceeds ${pe} characters`,"name");const s=ke(e.node_id,{name:r});return s.window&&Fe(s.window,We(s)),A(s)}m(vn,"renameRun");async function On(e,t={config:ut,client:ct}){await re({run_id:e.node_id},"the run was deleted");const r=L(a=>(w(a,"UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),w(a,"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(p=>p.node_id)));if((await Promise.all(r.map(a=>V(a,"run_deleted",{deleting:!0})))).some(a=>!a.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const i=r.map(a=>{const p=Ce(a);if(p.app===null)throw new Error(`run node missing during deletion: ${a}`);return p}),c=w(b(),"SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:a}of c)try{Q().delete(a)}catch(p){if(!He(p))throw p}const d=L(a=>{for(const f of r){const n=Le(a,f);n!==null&&n.deleted_at===null&&Ae(a,n)}const p=lt([e.node_id],a);w(a,"DELETE FROM run_events WHERE run_id = ?").run(e.node_id),w(a,"DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const y=r.map(()=>"?").join(","),E=a.prepare(`SELECT message_id FROM messages WHERE sender_node_id IN (${y}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${y})`).all(...r,...r);a.prepare(`DELETE FROM deliveries WHERE destination_id IN (${y})`).run(...r);for(const{message_id:f}of E)w(a,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(f,f);return p});for(const a of i)xe(a);const{notifyRunDeleted:o}=await import("./events.js");return o(e.node_id),{uploads_pending:await ft(d,t)}}m(On,"deleteRun");async function ft(e,t){if(!e.length||!t.config())return e.length;let r;try{r=t.client()}catch{return e.length}const s=new Set,i=e.map(d=>r.deleteUpload(d).then(()=>{mt(d),s.add(d)},()=>{}));let c;return await Promise.race([Promise.all(i),new Promise(d=>{c=setTimeout(d,t.budgetMs??_t),c.unref?.()})]),clearTimeout(c),e.length-s.size}m(ft,"deleteReleasedUploads");function bn(e,t,r){C(e,t),X(e);const s=b();s.exec("BEGIN");try{const c=w(s,"SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e).map(n=>({node_id:n.node_id,parent_node_id:n.parent,conversations:w(s,"SELECT DISTINCT session_id FROM sessions WHERE node_id = ? ORDER BY created_at").all(n.node_id).map(({session_id:u})=>{const{header:h,entries:g}=Q().read(u);if(r==="entries")return{conversation_id:u,header:h,entries:g};const M=Me.inMemory(h.cwd,void 0,[h,...g]),fe=Ke(M)??[],$=new Map(g.map((_,v)=>[_.id,v])),ge=g.filter(_=>_.type==="custom"&&_.customType==="crtr-system-prompt").map(_=>({id:_.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in _?_.data:void 0,timestamp:_.timestamp}})),j=g.filter(_=>_.type==="custom_message"&&_.customType==="app_message").map(_=>({id:_.id,message:{role:"custom",customType:"app_message",content:"content"in _?_.content:"",timestamp:_.timestamp}})),Ee=n.node_id===e?g.find(_=>{if(_.type!=="message"||_.message.role!=="user")return!1;const v=K(g,_.message.timestamp);return v?.type==="runtime"&&v.kind==="kickoff"})?.id:void 0,we=[...fe.filter(({id:_})=>!j.some(v=>v.id===_)),...ge,...j].sort((_,v)=>($.get(_.id)??Number.MAX_SAFE_INTEGER)-($.get(v.id)??Number.MAX_SAFE_INTEGER)).map(({id:_,message:v})=>{const l=v,D=l.usage,k=l.role==="user"?K(g,Number(l.timestamp)):null,he=k?.type==="human"||_===Ee,T=l.role==="toolResult"?"tool_result":l.role==="bashExecution"?"bash_execution":l.role==="compactionSummary"?"compaction_summary":l.role==="branchSummary"?"branch_summary":l.role==="custom"&&l.customType==="crtr-system-prompt"?"system":l.role==="user"?he?"user":"custom":l.role,ye=T==="custom"?l.role==="custom"?l.customType:k?.type==="runtime"?k.kind:"unknown":void 0,Re=T==="system"&&l.customType==="crtr-system-prompt"?[{type:"text",text:l.data?.text??""}]:typeof l.content=="string"?[{type:"text",text:l.content}]:Array.isArray(l.content)?l.content.map(O=>O.type==="toolCall"?{type:"tool_call",tool_call_id:O.id,name:O.name,arguments:O.arguments}:O.type==="image"?{type:"image",mime_type:O.mimeType}:O.type==="thinking"?{type:"thinking",text:O.thinking}:O):[];return{entry_id:_,role:T,...T==="custom"?{custom_type:ye}:{},time:typeof l.timestamp=="string"?l.timestamp:new Date(Number(l.timestamp??Date.now())).toISOString(),content:Re,...T==="assistant"?{model:l.model,provider:l.provider,stop_reason:l.stopReason,...l.errorMessage?{error_message:l.errorMessage}:{},...D?{usage:{input_tokens:D.input??0,output_tokens:D.output??0,cache_read_tokens:D.cacheRead??0,cache_write_tokens:D.cacheWrite??0,cost_usd:D.cost?.total??0}}:{}}:{},...T==="tool_result"?{tool_call_id:l.toolCallId,tool_name:l.toolName,is_error:l.isError}:{}}})??[];return{conversation_id:u,messages:we}})})),o=w(s,"SELECT event_cursor FROM nodes WHERE node_id = ?").get(e).event_cursor-1,a=new Map(c.map(n=>[n.node_id,st(n.node_id)])),p=w(s,`SELECT node_id, sequence_number FROM run_events
9
9
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.done', 'node.output_text.done', 'usage')
10
- ORDER BY sequence_number`).all(e,o);for(const s of p){const m=d.get(s.node_id);m!=null&&s.sequence_number>=m&&d.set(s.node_id,null)}const y=E(n,`SELECT node_id, sequence_number, type, body_json FROM run_events
10
+ ORDER BY sequence_number`).all(e,o);for(const n of p){const u=a.get(n.node_id);u!=null&&n.sequence_number>=u&&a.set(n.node_id,null)}const y=w(s,`SELECT node_id, sequence_number, type, body_json FROM run_events
11
11
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.delta', 'node.output_text.delta')
12
- ORDER BY sequence_number`).all(e,o),g=new Map;for(const s of y){const m=d.get(s.node_id);if(m==null||s.sequence_number<m)continue;const h=JSON.parse(s.body_json),w=g.get(s.node_id)??{thinking:"",text:""};s.type==="node.thinking.delta"?w.thinking+=h.delta:w.text+=h.delta,g.set(s.node_id,w)}const f=c.map(s=>({...s,in_progress_message:d.get(s.node_id)==null?null:g.get(s.node_id)??{thinking:"",text:""}}));return n.exec("COMMIT"),{run_id:e,latest_sequence:o,nodes:f}}catch(i){throw n.exec("ROLLBACK"),i}}u(yn,"traceRun");function hn(e,t,r){if(t===void 0){if(!e.some(n=>W(n,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(n=>typeof n!="string"))throw O("scopes must be a list of scope strings","scopes");for(const n of t){try{et(n)}catch{throw O(`invalid scope ${n}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(n)&&n.split(":")[4]===r.slice(4))throw O(`own-app files cannot narrow a run: ${n}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(n)&&n.split(":")[4]===r.slice(4))&&!Ne(e,n))throw new R(403,"scope_missing",`missing scope ${n}`,void 0,void 0,n)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}u(hn,"requestedScopes");export{B as IDEMPOTENCY_WINDOW_MS,ct as RUN_DELETE_STORAGE_BUDGET_MS,pe as RUN_NAME_MAX,R as RunError,gn as cancelRun,wn as deleteRun,cn as idempotencyKeyOf,fn as interruptRun,ln as listRuns,pn as messageRun,L as ownedRun,un as pruneExpiredIdempotencyKeys,En as renameRun,hn as requestedScopes,an as rewriteLocalIdempotencyGrantee,dn as runIsRunning,M as runObject,mn as startRun,yn as traceRun,_n as waitRun};
12
+ ORDER BY sequence_number`).all(e,o),E=new Map;for(const n of y){const u=a.get(n.node_id);if(u==null||n.sequence_number<u)continue;const h=JSON.parse(n.body_json),g=E.get(n.node_id)??{thinking:"",text:""};n.type==="node.thinking.delta"?g.thinking+=h.delta:g.text+=h.delta,E.set(n.node_id,g)}const f=c.map(n=>({...n,in_progress_message:a.get(n.node_id)==null?null:E.get(n.node_id)??{thinking:"",text:""}}));return s.exec("COMMIT"),{run_id:e,latest_sequence:o,nodes:f}}catch(i){throw s.exec("ROLLBACK"),i}}m(bn,"traceRun");function Dn(e,t,r){if(t===void 0){if(!e.some(s=>W(s,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(s=>typeof s!="string"))throw N("scopes must be a list of scope strings","scopes");for(const s of t){try{rt(s)}catch{throw N(`invalid scope ${s}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(s)&&s.split(":")[4]===r.slice(4))throw N(`own-app files cannot narrow a run: ${s}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(s)&&s.split(":")[4]===r.slice(4))&&!Ne(e,s))throw new R(403,"scope_missing",`missing scope ${s}`,void 0,void 0,s)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}m(Dn,"requestedScopes");export{B as IDEMPOTENCY_WINDOW_MS,_t as RUN_DELETE_STORAGE_BUDGET_MS,pe as RUN_NAME_MAX,R as RunError,Nn as cancelRun,On as deleteRun,gn as idempotencyKeyOf,Sn as interruptRun,hn as listRuns,Rn as messageRun,C as ownedRun,En as pruneExpiredIdempotencyKeys,vn as renameRun,Dn as requestedScopes,pn as rewriteLocalIdempotencyGrantee,fn as runIsRunning,A as runObject,wn as startRun,bn as traceRun,yn as waitRun};
@@ -1 +1 @@
1
- var R=Object.defineProperty;var m=(a,t)=>R(a,"name",{value:t,configurable:!0});import{callerGrant as h}from"../principal.js";import{grantVersion as g}from"../../../core/grants/mirror.js";import{waitGrantChange as S}from"../../../core/grants/watch.js";import{RunError as n,idempotencyKeyOf as b,cancelRun as $,deleteRun as N,interruptRun as O,listRuns as A,messageRun as j,renameRun as x,requestedScopes as B,startRun as J,traceRun as z,waitRun as C}from"../../../core/runs/operations.js";import{canReadRun as v,requireRun as f,runCallerApp as E}from"../router.js";import{waitSeconds as y}from"../long-poll-wait.js";import{outputSchemaProblem as K}from"../../../core/runtime/structured-output.js";function l(a){if(!a||typeof a!="object"||Array.isArray(a))throw new n(400,"invalid_request","a JSON object is required");return a}m(l,"object");function q(a,t,i,u,d){if(a===null)return d;if(!/^(0|[1-9]\d*)$/.test(a)||!Number.isSafeInteger(Number(a))||Number(a)<i||Number(a)>u)throw new n(400,"invalid_request",`invalid ${t}`,t);return Number(a)}m(q,"integer");const T=256*1024;async function W(a,t,i,u,d,_){switch(a.name){case"grant.get":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=y("grant.get",u),s=u.get("since");if(s!==null&&!/^[A-Za-z0-9_-]{1,64}$/.test(s))throw new n(400,"invalid_request","invalid since","since");let o=h(t);return e&&s!==null&&g(o)===s&&(await S(t.grant,e),o=h(t)),{status:200,body:{sub:t.sub,grantee:t.grantee,client_id:t.client_id,grant:t.grant,rev:o.rev,resolved_scopes:o.resolved_scopes,spend_cap:o.spend_cap,exp:t.exp,payer:"user",dependencies:o.dependencies??{},version:g(o)}}}case"run.start":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=l(d),s=["mode","parent","fork_from","node_id","env","tools","system_prompt"];for(const r of s)if(e[r]!==void 0)throw new n(403,"owner_only",`${r} is owner-only`,r);const o=["payer","model","model_policy","deadline","kind"];for(const r of o)if(e[r]!==void 0)throw new n(400,"invalid_request",`${r} is not available`,r);for(const r of Object.keys(e))if(!["prompt","scopes","metadata","profile","isolated","cwd","worktree","output_schema","message"].includes(r))throw new n(400,"invalid_request",`unknown field ${r}`,r);if(e.profile!==void 0&&(typeof e.profile!="string"||!e.profile))throw new n(400,"invalid_request","profile must be a profile id","profile");if(e.isolated!==void 0&&typeof e.isolated!="boolean")throw new n(400,"invalid_request","isolated must be a boolean","isolated");if(e.cwd!==void 0&&(e.isolated!==!0||typeof e.cwd!="string"||!e.cwd))throw new n(400,"invalid_request","cwd is available only for an isolated run and must be an absolute path inside its root folder","cwd");if(e.worktree!==void 0&&e.isolated!==!0)throw new n(400,"invalid_request","worktree is not available","worktree");if(e.isolated===!0&&e.worktree!==void 0)throw new n(400,"invalid_request","worktree is unavailable for an isolated run","worktree");if(typeof e.prompt!="string"||!e.prompt.trim())throw new n(400,"invalid_request","prompt is required","prompt");if(e.message!==void 0&&(typeof e.message!="string"||!e.message.trim()))throw new n(400,"invalid_request","message must be a non-empty string","message");const c=e.metadata===void 0?{}:l(e.metadata);if(Buffer.byteLength(JSON.stringify(c))>4096)throw new n(400,"invalid_request","metadata exceeds 4 KB","metadata");let w;if(e.output_schema!==void 0){const r=e.output_schema;if(!r||typeof r!="object"||Array.isArray(r))throw new n(400,"invalid_request","output_schema must be a JSON Schema object","output_schema");if(Buffer.byteLength(JSON.stringify(r))>T)throw new n(400,"invalid_request","output_schema exceeds 256 KB","output_schema");const p=await K(r);if(p!==null)throw new n(400,"invalid_request",`output_schema is not usable: ${p}`,"output_schema");w=r}const k=B(t.resolved,e.scopes,t.grantee);return{status:201,body:await J(t,{prompt:e.prompt,scopes:k,metadata:c,profile:e.profile,isolated:e.isolated,cwd:e.cwd,...w===void 0?{}:{output_schema:w},...e.message===void 0?{}:{message:e.message}},b(_))}}case"run.list":{const e=u.get("status")??void 0;if(e&&!["running","waiting_on_user","idle","settled"].includes(e))throw new n(400,"invalid_request","invalid status","status");const s=u.get("app");if(s!==null&&!/^(?:app:)?[a-z0-9][a-z0-9-]*$/.test(s))throw new n(400,"invalid_request","invalid app","app");const o=s===null?E(t):s.startsWith("app:")?s:`app:${s}`;if(o!==null&&!v(t,o))throw new n(404,"not_found","run not found");return{status:200,body:A(o,{status:e,after:u.get("after")??void 0,limit:q(u.get("limit"),"limit",1,100,50),..."class"in t&&(t.class==="broker"||t.class==="cron")&&o!==null?{visible:m(c=>v(t,o,c),"visible")}:{}})}}case"run.get":{const e=y("run.get",u),s=f(t,i),o=await C(i,s.grantee,e);return f(t,i),{status:200,body:o}}case"run.message":{const e=l(d);if(typeof e.message!="string"||!e.message.trim())throw new n(400,"invalid_request","message is required","message");if(e.start_turn!==void 0&&typeof e.start_turn!="boolean")throw new n(400,"invalid_request","start_turn must be a boolean","start_turn");return{status:202,body:await j(f(t,i,!0),e.message,void 0,e.start_turn,b(_))}}case"run.interrupt":{const e=l(d===void 0?{}:d);if(e.children!==void 0&&e.children!=="stop"&&e.children!=="continue")throw new n(400,"invalid_request","invalid children","children");return{status:200,body:await O(f(t,i,!0),e.children)}}case"run.cancel":{const e=l(d===void 0?{}:d);if(e.keep_schedules!==void 0&&typeof e.keep_schedules!="boolean")throw new n(400,"invalid_request","invalid keep_schedules","keep_schedules");return{status:200,body:await $(f(t,i,!0),{keepSchedules:e.keep_schedules})}}case"run.rename":{const e=l(d);for(const s of Object.keys(e))if(s!=="name")throw new n(400,"invalid_request",`unknown field ${s}`,s);return{status:200,body:x(f(t,i,!0),e.name)}}case"run.delete":{const{uploads_pending:e}=await N(f(t,i,!0,!0));return{status:200,body:{run_id:i,uploads_pending:e}}}case"trace.get":{const e=u.get("format")??void 0;if(e&&e!=="entries")throw new n(400,"invalid_request","unknown trace format","format");const s=f(t,i);return{status:200,body:z(i,s.grantee,e)}}default:throw new n(404,"not_found","unknown operation")}}m(W,"handleRunOperation");export{W as handleRunOperation,q as integer};
1
+ var R=Object.defineProperty;var m=(a,t)=>R(a,"name",{value:t,configurable:!0});import{callerGrant as h}from"../principal.js";import{grantVersion as g}from"../../../core/grants/mirror.js";import{waitGrantChange as S}from"../../../core/grants/watch.js";import{RunError as n,idempotencyKeyOf as b,cancelRun as $,deleteRun as N,interruptRun as O,listRuns as A,messageRun as j,renameRun as x,requestedScopes as B,startRun as J,traceRun as z,waitRun as C}from"../../../core/runs/operations.js";import{canReadRun as v,requireRun as f,runCallerApp as E}from"../router.js";import{waitSeconds as y}from"../long-poll-wait.js";import{outputSchemaProblem as K}from"../../../core/runtime/structured-output.js";function l(a){if(!a||typeof a!="object"||Array.isArray(a))throw new n(400,"invalid_request","a JSON object is required");return a}m(l,"object");function q(a,t,i,u,d){if(a===null)return d;if(!/^(0|[1-9]\d*)$/.test(a)||!Number.isSafeInteger(Number(a))||Number(a)<i||Number(a)>u)throw new n(400,"invalid_request",`invalid ${t}`,t);return Number(a)}m(q,"integer");const T=256*1024;async function W(a,t,i,u,d,_){switch(a.name){case"grant.get":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=y("grant.get",u),s=u.get("since");if(s!==null&&!/^[A-Za-z0-9_-]{1,64}$/.test(s))throw new n(400,"invalid_request","invalid since","since");let o=h(t);return e&&s!==null&&g(o)===s&&(await S(t.grant,e),o=h(t)),{status:200,body:{sub:t.sub,grantee:t.grantee,client_id:t.client_id,grant:t.grant,rev:o.rev,resolved_scopes:o.resolved_scopes,spend_cap:o.spend_cap,exp:t.exp,payer:"user",dependencies:o.dependencies??{},version:g(o)}}}case"run.start":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=l(d),s=["mode","parent","fork_from","node_id","env","tools","system_prompt"];for(const r of s)if(e[r]!==void 0)throw new n(403,"owner_only",`${r} is owner-only`,r);const o=["payer","model_policy","deadline","kind"];for(const r of o)if(e[r]!==void 0)throw new n(400,"invalid_request",`${r} is not available`,r);for(const r of Object.keys(e))if(!["prompt","scopes","metadata","profile","isolated","cwd","worktree","output_schema","message","model"].includes(r))throw new n(400,"invalid_request",`unknown field ${r}`,r);if(e.model!==void 0&&(typeof e.model!="string"||!e.model.trim()))throw new n(400,"invalid_request","model must be a non-empty string","model");if(e.profile!==void 0&&(typeof e.profile!="string"||!e.profile))throw new n(400,"invalid_request","profile must be a profile id","profile");if(e.isolated!==void 0&&typeof e.isolated!="boolean")throw new n(400,"invalid_request","isolated must be a boolean","isolated");if(e.cwd!==void 0&&(e.isolated!==!0||typeof e.cwd!="string"||!e.cwd))throw new n(400,"invalid_request","cwd is available only for an isolated run and must be an absolute path inside its root folder","cwd");if(e.worktree!==void 0&&e.isolated!==!0)throw new n(400,"invalid_request","worktree is not available","worktree");if(e.isolated===!0&&e.worktree!==void 0)throw new n(400,"invalid_request","worktree is unavailable for an isolated run","worktree");if(typeof e.prompt!="string"||!e.prompt.trim())throw new n(400,"invalid_request","prompt is required","prompt");if(e.message!==void 0&&(typeof e.message!="string"||!e.message.trim()))throw new n(400,"invalid_request","message must be a non-empty string","message");const w=e.metadata===void 0?{}:l(e.metadata);if(Buffer.byteLength(JSON.stringify(w))>4096)throw new n(400,"invalid_request","metadata exceeds 4 KB","metadata");let c;if(e.output_schema!==void 0){const r=e.output_schema;if(!r||typeof r!="object"||Array.isArray(r))throw new n(400,"invalid_request","output_schema must be a JSON Schema object","output_schema");if(Buffer.byteLength(JSON.stringify(r))>T)throw new n(400,"invalid_request","output_schema exceeds 256 KB","output_schema");const p=await K(r);if(p!==null)throw new n(400,"invalid_request",`output_schema is not usable: ${p}`,"output_schema");c=r}const k=B(t.resolved,e.scopes,t.grantee);return{status:201,body:await J(t,{prompt:e.prompt,scopes:k,metadata:w,profile:e.profile,isolated:e.isolated,cwd:e.cwd,...e.model===void 0?{}:{model:e.model},...c===void 0?{}:{output_schema:c},...e.message===void 0?{}:{message:e.message}},b(_))}}case"run.list":{const e=u.get("status")??void 0;if(e&&!["running","waiting_on_user","idle","settled"].includes(e))throw new n(400,"invalid_request","invalid status","status");const s=u.get("app");if(s!==null&&!/^(?:app:)?[a-z0-9][a-z0-9-]*$/.test(s))throw new n(400,"invalid_request","invalid app","app");const o=s===null?E(t):s.startsWith("app:")?s:`app:${s}`;if(o!==null&&!v(t,o))throw new n(404,"not_found","run not found");return{status:200,body:A(o,{status:e,after:u.get("after")??void 0,limit:q(u.get("limit"),"limit",1,100,50),..."class"in t&&(t.class==="broker"||t.class==="cron")&&o!==null?{visible:m(w=>v(t,o,w),"visible")}:{}})}}case"run.get":{const e=y("run.get",u),s=f(t,i),o=await C(i,s.grantee,e);return f(t,i),{status:200,body:o}}case"run.message":{const e=l(d);if(typeof e.message!="string"||!e.message.trim())throw new n(400,"invalid_request","message is required","message");if(e.start_turn!==void 0&&typeof e.start_turn!="boolean")throw new n(400,"invalid_request","start_turn must be a boolean","start_turn");return{status:202,body:await j(f(t,i,!0),e.message,void 0,e.start_turn,b(_))}}case"run.interrupt":{const e=l(d===void 0?{}:d);if(e.children!==void 0&&e.children!=="stop"&&e.children!=="continue")throw new n(400,"invalid_request","invalid children","children");return{status:200,body:await O(f(t,i,!0),e.children)}}case"run.cancel":{const e=l(d===void 0?{}:d);if(e.keep_schedules!==void 0&&typeof e.keep_schedules!="boolean")throw new n(400,"invalid_request","invalid keep_schedules","keep_schedules");return{status:200,body:await $(f(t,i,!0),{keepSchedules:e.keep_schedules})}}case"run.rename":{const e=l(d);for(const s of Object.keys(e))if(s!=="name")throw new n(400,"invalid_request",`unknown field ${s}`,s);return{status:200,body:x(f(t,i,!0),e.name)}}case"run.delete":{const{uploads_pending:e}=await N(f(t,i,!0,!0));return{status:200,body:{run_id:i,uploads_pending:e}}}case"trace.get":{const e=u.get("format")??void 0;if(e&&e!=="entries")throw new n(400,"invalid_request","unknown trace format","format");const s=f(t,i);return{status:200,body:z(i,s.grantee,e)}}default:throw new n(404,"not_found","unknown operation")}}m(W,"handleRunOperation");export{W as handleRunOperation,q as integer};
@@ -67,14 +67,14 @@ Not breaking: no type or wire shape an app reads is removed or changed.
67
67
 
68
68
  ### Monthly spend cap enforced; `usage.get` (additive; one behavior change)
69
69
 
70
- - **Behavior change:** user-paid model calls (`/v1/llm`) and provider calls now count against the grant's `spend_cap` (from `grant.get()`; crouter cloud sets $50) for the current UTC month. Once the month reaches it, the next call answers 402 `cap_exhausted` (a `RateLimitError`, `retryable: false`, `userAction: 'raise_cap'`, `resetAt` the first instant of next month) before any model or provider is called. A run whose model call is refused this way ends with outcome reason `cap_exhausted`. The check runs before each call, so the call that crosses the cap still completes. App-paid usage never counts. A grant with `spend_cap: null` is not capped.
70
+ - **Behavior change:** user-paid model calls (`/v1/llm`) and provider calls now count against the grant's `spend_cap` (from `grant.get()`; Northlight sets $50) for the current UTC month. Once the month reaches it, the next call answers 402 `cap_exhausted` (a `RateLimitError`, `retryable: false`, `userAction: 'raise_cap'`, `resetAt` the first instant of next month) before any model or provider is called. A run whose model call is refused this way ends with outcome reason `cap_exhausted`. The check runs before each call, so the call that crosses the cap still completes. App-paid usage never counts. A grant with `spend_cap: null` is not capped.
71
71
  - New `client.usage.get({month?})` (`month` is `YYYY-MM`, UTC; default the current month) answers `SpendUsageDTO`: `usd_equivalent` (user-paid spend, provider receipts included), `by_model`, `app_paid`, `spend_cap` and `reset_at`. See [Show what a person's month has cost](./client.md#show-what-a-persons-month-has-cost).
72
72
  - A completion's `usage.usd_equivalent` is now the served model's list-price cost; before it was always `null`. It stays `null` for a model with no list price, and that usage adds nothing to the month.
73
73
 
74
74
  ### `npx @north-light/sdk env` (additive); keygen on an unjoined machine exits 2
75
75
 
76
76
  - New `npx @north-light/sdk env --app <client id> [--out <path>]` writes `CROUTER_ISSUER`, `CROUTER_CLIENT_ID` and `APP_ORIGIN` from `crtr cloud apps get` into `local/app.env`, keeping every other line. See [Go live with an app key](./getting-started.md#go-live-with-an-app-key).
77
- - `keygen --app` on a machine whose `crtr` isn't joined to a crouter cloud account now prints the join step and the manual steps and exits 2. A missing `crtr` keeps its message and exit 0.
77
+ - `keygen --app` on a machine whose `crtr` isn't joined to a Northlight account now prints the join step and the manual steps and exits 2. A missing `crtr` keeps its message and exit 0.
78
78
 
79
79
  ### One deadline per SDK call, and a deadline on every OAuth/JWKS fetch (additive, with one behaviour change)
80
80
 
@@ -90,7 +90,7 @@ Not breaking: no type or wire shape an app reads is removed or changed. Apps pin
90
90
 
91
91
  ### `@north-light/sdk/testing`: isolated runs start, `FakeCall.runId`, one `runs` shape (one breaking change)
92
92
 
93
- - `runs.start({isolated: true})` now starts a run on `FakeCloud`, as it does on a crouter cloud guest; it used to answer 503 `isolated_unavailable`. `run.params.isolated` records it, and a `cwd` outside the run's root folder is 400 `param: cwd`, as the runtime checks it. The new option `isolatedRuns: 'unavailable'` restores the 503 for testing that path. If you worked around the refusal by deleting `body.isolated` in an intercept, remove the intercept.
93
+ - `runs.start({isolated: true})` now starts a run on `FakeCloud`, as it does on a Northlight guest; it used to answer 503 `isolated_unavailable`. `run.params.isolated` records it, and a `cwd` outside the run's root folder is 400 `param: cwd`, as the runtime checks it. The new option `isolatedRuns: 'unavailable'` restores the 503 for testing that path. If you worked around the refusal by deleting `body.isolated` in an intercept, remove the intercept.
94
94
  - `FakeCall` gains `runId` and `questionId` (the ids the path addresses, else `null`), so tests no longer split `path`.
95
95
  - `FakeCall.userId` is now the bearer's person whenever the cloud knows them, including a person with no refresh token; it is `null` only for a missing or unknown bearer.
96
96
  - **Breaking:** `cloud.user(id).runs` is now `{all, respond}`, the same shape as `cloud.runs`, instead of an array carrying `respond`. Replace `user.runs[i]`, `user.runs.find(...)` and `[...user.runs]` with `user.runs.all…`.
@@ -104,7 +104,7 @@ New `@north-light/sdk/testing`: `FakeCloud`, a fake directory and runtime your r
104
104
 
105
105
  ### `oauth.accountAppUrl()` (additive), and a failed discovery read is retried (behaviour fix)
106
106
 
107
- - New `OAuth2Client.accountAppUrl()` returns the app's page on the signed-in person's crouter cloud account, as the directory's discovery document advertises it (`account_app_uri_template`). Use it instead of building `<issuer>/account/apps/<client id>` yourself. It needs no user or connection and throws `CrouterError` naming the fault when the directory advertises no template or an invalid one. See [Link to the app's settings on crouter cloud](./client.md#link-to-the-apps-settings-on-crouter-cloud).
107
+ - New `OAuth2Client.accountAppUrl()` returns the app's page on the signed-in person's Northlight account, as the directory's discovery document advertises it (`account_app_uri_template`). Use it instead of building `<issuer>/account/apps/<client id>` yourself. It needs no user or connection and throws `CrouterError` naming the fault when the directory advertises no template or an invalid one. See [Link to the app's settings on Northlight](./client.md#link-to-the-apps-settings-on-northlight).
108
108
  - **Behaviour fix:** `OAuth2Client`'s discovery read now times out after 10 seconds, and a read that failed or timed out is retried by the next call that needs it. Before, one failed read was kept for the client's life, so every later `authorizeUrl`, `exchangeCode`, refresh and `verifyIdToken` failed until the process restarted, and a hung directory stalled them indefinitely. A successful read is still kept for the client's life.
109
109
 
110
110
  ## 0.3.405
@@ -197,7 +197,7 @@ The SDK now sends `Crouter-Minimum-Runtime-Version: <SDK_VERSION>` on every requ
197
197
  - `refusalOf` returns a `Refusal` only for an error the SDK raised while refreshing a stored connection, not for every directory-origin `AuthenticationError`.
198
198
  - Concurrent refreshes in one process are shared per user and refresh token, so a client holding a newer token never joins an older refusal.
199
199
 
200
- **Callers to update:** crouter cloud's chat app (`first-party-apps/chat/src/directory-client.ts`, which builds `oauth.client(connection, store)`) and the context app (`first-party-apps/context/req-run.mjs`). Any app that called `refreshIdleConnections(store, …)`, read `RefreshFailure.reason`, or imported `refreshRefusal` must move to the shapes above.
200
+ **Callers to update:** Northlight's chat app (`first-party-apps/chat/src/directory-client.ts`, which builds `oauth.client(connection, store)`) and the context app (`first-party-apps/context/req-run.mjs`). Any app that called `refreshIdleConnections(store, …)`, read `RefreshFailure.reason`, or imported `refreshRefusal` must move to the shapes above.
201
201
 
202
202
  ### `RunWatcher`: learn when a background run ends (additive)
203
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.468",
3
+ "version": "0.3.471",
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.468",
140
+ "@north-light/identity": "0.3.471",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.468",
3
+ "version": "0.3.471",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.468",
3
+ "version": "0.3.471",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.468",
9
+ "version": "0.3.471",
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.468",
21
+ "@north-light/identity": "0.3.471",
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.468",
12098
+ "version": "0.3.471",
12099
12099
  "license": "UNLICENSED"
12100
12100
  },
12101
12101
  "packages/crouter-env-docker": {
12102
12102
  "name": "@north-light/env-docker",
12103
- "version": "0.3.468",
12103
+ "version": "0.3.471",
12104
12104
  "license": "UNLICENSED"
12105
12105
  },
12106
12106
  "packages/crouter-identity": {
12107
12107
  "name": "@north-light/identity",
12108
- "version": "0.3.468",
12108
+ "version": "0.3.471",
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.468",
12116
+ "version": "0.3.471",
12117
12117
  "license": "UNLICENSED",
12118
12118
  "dependencies": {
12119
- "@north-light/api": "0.3.468",
12120
- "@north-light/identity": "0.3.468"
12119
+ "@north-light/api": "0.3.471",
12120
+ "@north-light/identity": "0.3.471"
12121
12121
  }
12122
12122
  },
12123
12123
  "packages/crouter-sdk": {
12124
12124
  "name": "@north-light/sdk",
12125
- "version": "0.3.468",
12125
+ "version": "0.3.471",
12126
12126
  "license": "UNLICENSED",
12127
12127
  "dependencies": {
12128
- "@north-light/api": "0.3.468",
12129
- "@north-light/identity": "0.3.468",
12128
+ "@north-light/api": "0.3.471",
12129
+ "@north-light/identity": "0.3.471",
12130
12130
  "jose": "^6.2.1"
12131
12131
  },
12132
12132
  "bin": {