@north-light/cli 0.3.429 → 0.3.431

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/builtin-memory/crouter-sdk/README.md +2 -2
  2. package/dist/builtin-memory/crouter-sdk/client.md +4 -4
  3. package/dist/builtin-memory/crouter-sdk/error-envelope.md +3 -3
  4. package/dist/builtin-memory/crouter-sdk/getting-started.md +2 -2
  5. package/dist/builtin-memory/crouter-sdk/guides/README.md +1 -1
  6. package/dist/builtin-memory/crouter-sdk/guides/{apps-on-crouter-cloud.md → apps-on-northlight.md} +1 -1
  7. package/dist/builtin-memory/crouter-sdk/guides/apps-with-many-users.md +3 -3
  8. package/dist/builtin-memory/crouter-sdk/guides/typed-extraction.md +1 -1
  9. package/dist/builtin-memory/crouter-sdk/release-notes.md +2 -2
  10. package/dist/builtin-memory/crouter-sdk/resources.md +1 -1
  11. package/dist/builtin-memory/crouter-sdk/testing.md +3 -3
  12. package/dist/builtin-memory/internal/plugins.md +1 -1
  13. package/dist/clients/attach/viewer.js +4 -4
  14. package/dist/clients/inbox/tui/input.js +1 -1
  15. package/dist/clients/inbox/tui/page-body.js +1 -1
  16. package/dist/clients/inbox/tui/render.js +2 -2
  17. package/dist/commands/node/lifecycle.js +1 -1
  18. package/dist/commands/sys/setup-core.d.ts +1 -1
  19. package/dist/commands/sys/setup-core.js +3 -3
  20. package/dist/core/command.d.ts +1 -1
  21. package/dist/core/command.js +1 -1
  22. package/dist/core/model-auth/flows.d.ts +1 -1
  23. package/dist/core/providers/call.js +1 -1
  24. package/dist/core/runtime/broker-protocol.d.ts +2 -2
  25. package/dist/daemon/api/bridge.d.ts +1 -1
  26. package/dist/daemon/reconcilers/live-obligation.d.ts +1 -1
  27. package/dist/launcher/users.js +4 -4
  28. package/dist/pi-extensions/canvas-review-boundary.d.ts +1 -1
  29. package/docs/release-local.md +1 -1
  30. package/docs/sdk/README.md +2 -2
  31. package/docs/sdk/client.md +4 -4
  32. package/docs/sdk/error-envelope.md +2 -2
  33. package/docs/sdk/getting-started.md +2 -2
  34. package/docs/sdk/guides/README.md +1 -1
  35. package/docs/sdk/guides/{apps-on-crouter-cloud.md → apps-on-northlight.md} +2 -2
  36. package/docs/sdk/guides/apps-with-many-users.md +3 -3
  37. package/docs/sdk/guides/typed-extraction.md +1 -1
  38. package/docs/sdk/release-notes.md +2 -2
  39. package/docs/sdk/resources.md +1 -1
  40. package/docs/sdk/testing.md +3 -3
  41. package/package.json +2 -2
  42. package/packages/crouter-identity/package.json +2 -2
  43. package/runtime.lock.json +12 -12
@@ -6,4 +6,4 @@ Use this only for a release explicitly authorized by Silas. `.github/workflows/p
6
6
  2. Run `node scripts/release-version.mjs "$VERSION"`. It sets all six manifests to `$VERSION`, pins every dependency between them (root → identity, SDK → api and identity, plugin → api and identity) to exactly `$VERSION` with no `^`, writes `SDK_VERSION` in `packages/crouter-sdk/src/version.ts` (informational: compatibility is the contract level), and renames the `## Next release` section of `docs/sdk/release-notes.md` to `## $VERSION`. One release is one version set: an app pinning `@north-light/sdk@$VERSION` gets exactly this release's api and identity. Run `npm install --package-lock-only --ignore-scripts`, then `node scripts/contract.mjs release`: it fails on an unacknowledged breaking `/v1` change or an undeclared watched file (the same check CI runs), then writes `contract/surface.txt`, `CONTRACT_LEVEL` in `src/api/contract.ts` (a minor bump only when the surface changed or a declaration says `bump`), `crouterContract` in the SDK manifest, and resets `contract/next.json`. Run `node scripts/release-version.mjs --check` (prints `one version set`). Check `git diff` and commit only the six manifests, `packages/crouter-sdk/src/version.ts`, `docs/sdk/release-notes.md`, `package-lock.json`, `contract/surface.txt`, `contract/next.json` and `src/api/contract.ts` as `chore: release v$VERSION`. Create `git tag -a "v$VERSION" -m "chore: release v$VERSION"`, then `git push --atomic origin HEAD:main "refs/tags/v$VERSION"`. If rejected due to a concurrent push, remove the *local* release tag, integrate fresh main, then recheck versions and retry; never push a tag separately.
7
7
  3. From the tagged release commit run `npm ci`. Publish in dependency order, checking `npm view "$PACKAGE@$VERSION" version` before each and skipping only on a positive sighting: `npm run build:identity` then `npm publish` in `packages/crouter-identity`; `npm run build` then `CRTR_PACK_MINIFY=1 npm publish` at root (Docker must be running: the pack cross-builds the Linux x64 and arm64 native addons into `dist/native/linux/<arch>/addon.node` and fails the release if it cannot); `npm run build:api` then publish in `packages/crouter-api`; `npm run build:env-docker` then publish in `packages/crouter-env-docker`; `npm run build:api && npm run build:identity && npm run build:sdk` then `npm publish --tag next` in `packages/crouter-sdk` (its `prepublishOnly` guard refuses any other form, including `--tag latest`; every SDK release lands on `next`); `npm run build:api && npm run build:identity && npm run build:plugin` then `CRTR_PACK_MINIFY=1 npm publish` in `packages/crouter-plugin`. Use npm's interactive authentication/2FA where required (see `personal-operations/npm-2fa-cli`); a read-only token cannot publish. Provenance remains off because the source repository is private. Do not build or publish the Docker image in this no-Actions path.
8
8
  4. Confirm all six exact versions with `npm view`. In a fresh temporary directory, `npm init -y && npm install @north-light/sdk@$VERSION`, then prove both `@north-light/sdk/errors` (`errorCodes`) and `@north-light/sdk/stores/postgres` resolve via `node --input-type=module -e '…'`. Record any partial publish and finish it from the exact release tag rather than cutting another version: `gh workflow run publish.yml -f release_tag=v$VERSION` runs `recover-release`, which verifies the tag is a release commit on main, checks it out, and publishes only the packages and image the registry does not yet show (SDK to `next`; it never writes the image `:latest`, so if the recovered tag is the newest release, `:latest` follows the next normal release).
9
- 5. Promote `latest` last, through CI only: `gh workflow run publish.yml -f promote=true` runs the `promote-sdk` job, the one writer of `latest`. It shares the `sdk-dist-tag` concurrency group with the automatic runs (they queue, never overlap), reads the router's `/contract` itself, re-reads `latest` right before the write and moves it only if unchanged. It also runs after each SDK publish and hourly, once the npm trusted-publisher entry for `@north-light/sdk` has "Allow npm dist-tag" ticked. `PROD_CONTRACT_URL=<the router's /contract URL> node scripts/promote-sdk.mjs` is a read-only dry run; its `--apply` refuses outside Actions. **Never `npm dist-tag add` the SDK by hand.** The one exception is Actions fully down: a person then runs the printed target by hand through the npm 2FA wrapper only after re-reading `npm view @north-light/sdk dist-tags` and confirming no `promote-sdk` run is active (`gh run list --workflow publish.yml`) and that the target is above the current `latest`.
9
+ 5. Promote `latest` last, through CI only: `gh workflow run publish.yml -f promote=true` runs the `promote-sdk` job, the one writer of `latest`. It shares the `sdk-dist-tag` concurrency group with the automatic runs (they queue, never overlap), reads the router's `/contract` itself, re-reads `latest` right before the write and moves it only if unchanged. It also runs after each SDK publish and hourly. `PROD_CONTRACT_URL=<the router's /contract URL> node scripts/promote-sdk.mjs` is a read-only dry run; its `--apply` refuses outside Actions. **Never `npm dist-tag add` the SDK by hand.** The one exception is Actions fully down: a person then runs the printed target by hand through the npm 2FA wrapper only after re-reading `npm view @north-light/sdk dist-tags` and confirming no `promote-sdk` run is active (`gh run list --workflow publish.yml`) and that the target is above the current `latest`.
@@ -26,7 +26,7 @@ if (run.kind === 'result') console.log(run.output_parsed.root_cause);
26
26
 
27
27
  ## Go live with an app key
28
28
 
29
- Once you have installed the SDK and tried the local flow in [Getting started](./getting-started.md), build against production: [Build and register an app](./guides/apps-on-crouter-cloud.md#build-and-register-an-app) takes you from signing up at `https://account.northlight.ai` and joining your laptop (`crtr sys join`) to a running app on your laptop, registered with `crtr cloud apps register`. Generate the key and pull the settings with:
29
+ Once you have installed the SDK and tried the local flow in [Getting started](./getting-started.md), build against production: [Build and register an app](./guides/apps-on-northlight.md#build-and-register-an-app) takes you from signing up at `https://account.northlight.ai` and joining your laptop (`crtr sys join`) to a running app on your laptop, registered with `crtr cloud apps register`. Generate the key and pull the settings with:
30
30
 
31
31
  ```bash
32
32
  npx @north-light/sdk keygen --app <client id>
@@ -83,7 +83,7 @@ The directory rejects the key until its public JWK is added to the app. To chang
83
83
  | [Docker environment](./docker.md) | `start()`, `attach()`, `connection()` from `@north-light/env-docker` |
84
84
  | [Testing your app](./testing.md) | `FakeCloud` from `@north-light/sdk/testing`: a fake directory and runtime your real `OAuth2Client` and `Crouter` talk to through `fetch` |
85
85
  | [Release notes](./release-notes.md) | Behavior changes an existing app must act on, newest first |
86
- | [Apps on crouter cloud](./guides/apps-on-crouter-cloud.md) | Building and registering an app against production (joining a laptop, `crtr cloud apps` registration, keys, env file, scopes, running locally, the `crtr cloud apps` commands); typed results from app runs with `runs.parse`, declines, bounding a run, `run_idle`, and waiting on a provider your app requires |
86
+ | [Apps on Northlight](./guides/apps-on-northlight.md) | Building and registering an app against production (joining a laptop, `crtr cloud apps` registration, keys, env file, scopes, running locally, the `crtr cloud apps` commands); typed results from app runs with `runs.parse`, declines, bounding a run, `run_idle`, and waiting on a provider your app requires |
87
87
  | [Apps with many users](./guides/apps-with-many-users.md) | Per-person runtime connections, app-owned user access, privacy, and spending |
88
88
 
89
89
  ## Phases
@@ -208,7 +208,7 @@ watcher.nudge();
208
208
  await watcher.stop();
209
209
  ```
210
210
 
211
- - **`users()`** returns the crouter cloud user ids whose grant the app follows, read from its own rows. The watcher calls it at `start()`, every `sweepMs` (default 30 000), after each delivery, and on `nudge()`. A user it no longer returns is forgotten: no further reads, and a user who comes back is delivered their current grant again. Each followed person holds one request on their runtime per turn, so return only people whose change the app will act on.
211
+ - **`users()`** returns the Northlight user ids whose grant the app follows, read from its own rows. The watcher calls it at `start()`, every `sweepMs` (default 30 000), after each delivery, and on `nudge()`. A user it no longer returns is forgotten: no further reads, and a user who comes back is delivered their current grant again. Each followed person holds one request on their runtime per turn, so return only people whose change the app will act on.
212
212
  - **Each turn** is one `grant.get` on `oauth.client(userId)`; then the user goes to the back of the queue, so with more followed users than `concurrency` (default 50) every user still gets a turn in round-robin order. A user whose last reading was delivered reads `grant.get({wait, since: version})` (`wait` integer 1–25, default 25): the runtime holds the read until the grant's `version` changes or `wait` seconds pass. A user with nothing delivered yet (their first turn, or after `onChanged` threw) reads at once.
213
213
  - **`onChanged({userId, grant, previous})`** is level-triggered. The first event for a user in a process carries the current grant and `previous: null`, so a restart delivers current state again; later events carry the grant last delivered as `previous`. Delivery is at least once per `(userId, version)` and never repeated for an unchanged `version`; make the handler idempotent (guard the write on the row still waiting).
214
214
  - **If `onChanged` throws**, the same version is delivered again after 5 s, doubling to 5 min, and the error goes to `onError`. A transient runtime error (5xx, `runtime_starting`, a router 504, a network failure), or a runtime 401 `grant_revoked` the client's one forced refresh did not clear, backs off the same way. An answer without `version` (a runtime older than 0.3.400) is reported and backs off. A missing connection (`not_connected`) or a refused one (which `onRefusal` already handled, once) drops that user and reports to `onError`; the next sweep decides again from `users()`.
@@ -217,7 +217,7 @@ await watcher.stop();
217
217
 
218
218
  ### Show what a person's month has cost
219
219
 
220
- A person's grant caps what your app may spend of their AI subscription each UTC month (`grant.spend_cap`; crouter cloud sets $50). `usage.get()` answers where the month stands:
220
+ A person's grant caps what your app may spend of their AI subscription each UTC month (`grant.spend_cap`; Northlight sets $50). `usage.get()` answers where the month stands:
221
221
 
222
222
  ```ts
223
223
  const usage = await runtime.usage.get(); // or {month: '2026-09'}
@@ -229,9 +229,9 @@ const usage = await runtime.usage.get(); // or {month: '2026-09'}
229
229
  - **At the cap.** Once `usd_equivalent` reaches `spend_cap`, user-paid model and provider calls answer 402 `cap_exhausted` (a `RateLimitError` with `retryable: false`, `userAction: 'raise_cap'` and `resetAt`) until `reset_at`, the first instant of the next UTC month. A run whose model call is refused ends with outcome reason `cap_exhausted`. The check runs before each call, so the call that crosses the cap completes and the next one is refused. Do not retry; tell the person when the count resets.
230
230
  - `spend_cap: null` means the grant is not capped.
231
231
 
232
- ### Link to the app's settings on crouter cloud
232
+ ### Link to the app's settings on Northlight
233
233
 
234
- Each person who uses your app has a page on their crouter cloud account where they change or remove what the app may do. Link to it with the URL the directory advertises; never compose the path yourself:
234
+ Each person who uses your app has a page on their Northlight account where they change or remove what the app may do. Link to it with the URL the directory advertises; never compose the path yourself:
235
235
 
236
236
  ```ts
237
237
  const href = await oauth.accountAppUrl(); // e.g. https://<directory>/account/apps/<your client id>
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: App-facing error envelope
3
- description: Contract 2 codes and the HTTP refusal body for crouter cloud apps.
3
+ description: Contract 2 codes and the HTTP refusal body for Northlight apps.
4
4
  ---
5
5
 
6
6
  # App-facing error envelope
7
7
 
8
- crouter cloud's app-facing HTTP endpoints use contract 2's error body. An app's **own** endpoints can use the same shape with `origin: 'app'`. The SDK publishes the fixed code tuples:
8
+ Northlight's app-facing HTTP endpoints use contract 2's error body. An app's **own** endpoints can use the same shape with `origin: 'app'`. The SDK publishes the fixed code tuples:
9
9
 
10
10
  ```ts
11
11
  import { errorCodes, type ErrorEnvelope } from '@north-light/sdk/errors';
@@ -174,7 +174,7 @@ An outcome is returned, never thrown — including a decline and a failure. Only
174
174
 
175
175
  ## Go live with an app key
176
176
 
177
- Build against production from your laptop: [Build and register an app](./guides/apps-on-crouter-cloud.md#build-and-register-an-app) walks the whole path. In short, sign up at `https://account.northlight.ai`, create a join token at `https://account.northlight.ai/account/machines` and run `crtr sys join --directory https://account.northlight.ai --token <token>` on your laptop, register the app with `crtr cloud apps register` using a `http://localhost:<port>` origin and a `http://localhost:<port>/auth/callback` redirect URI while you develop, then generate the key and pull its settings with:
177
+ Build against production from your laptop: [Build and register an app](./guides/apps-on-northlight.md#build-and-register-an-app) walks the whole path. In short, sign up at `https://account.northlight.ai`, create a join token at `https://account.northlight.ai/account/machines` and run `crtr sys join --directory https://account.northlight.ai --token <token>` on your laptop, register the app with `crtr cloud apps register` using a `http://localhost:<port>` origin and a `http://localhost:<port>/auth/callback` redirect URI while you develop, then generate the key and pull its settings with:
178
178
 
179
179
  ```bash
180
180
  npx @north-light/sdk keygen --app <client id>
@@ -185,7 +185,7 @@ npx @north-light/sdk env --app <client id>
185
185
 
186
186
  `env` runs `crtr cloud apps get --app-id <client id>` and writes `CROUTER_ISSUER`, `CROUTER_CLIENT_ID`, and `APP_ORIGIN` into `local/app.env` (`--out <path>` writes another file). It updates those three lines in place, keeps every other line and comment, and creates the file with mode `0600` when it doesn't exist. It never writes `CROUTER_KEY_FILE` or anything secret. It prints the values it set and the key file your app will read.
187
187
 
188
- Both commands reach your app through this machine's `crtr`, so the machine must be joined to your Northlight account. On one that isn't, they say so, print a join step and exit 2. Against production, [join your laptop](./guides/apps-on-crouter-cloud.md#1-account-and-join) first.
188
+ Both commands reach your app through this machine's `crtr`, so the machine must be joined to your Northlight account. On one that isn't, they say so, print a join step and exit 2. Against production, [join your laptop](./guides/apps-on-northlight.md#1-account-and-join) first.
189
189
 
190
190
  An app built on the SDK is configured from four variables:
191
191
 
@@ -13,7 +13,7 @@ These recipes turn the SDK namespaces into complete applications: a bounded extr
13
13
  | [Event-driven assistant](./event-driven-assistant.md) | Events arrive over time from a webhook, queue, or watcher. | A resident node that wakes for each event. |
14
14
  | [Fan-out pipeline](./fan-out-pipeline.md) | One task needs independent research before a synthesis. | An orchestrator's final report. |
15
15
  | [Human approval](./human-approval.md) | A person must decide before work continues. | A node resumed by an inbox answer. |
16
- | [Apps on crouter cloud](./apps-on-crouter-cloud.md) | You are building and registering an app, or it starts runs on a person's runtime and needs typed data back. | A validated result from `runs.parse`, or an explicit decline or `run_idle`. |
16
+ | [Apps on Northlight](./apps-on-northlight.md) | You are building and registering an app, or it starts runs on a person's runtime and needs typed data back. | A validated result from `runs.parse`, or an explicit decline or `run_idle`. |
17
17
  | [Apps with many users](./apps-with-many-users.md) | People interact using separate runtime connections. | App-owned access controls, attribution, and per-person spending. |
18
18
 
19
19
  Start with typed extraction for a request/response job. Use a resident node when the same assistant should react again later. Use an orchestrator only when child work can proceed independently; otherwise keep the composition in your application.
@@ -1,9 +1,9 @@
1
1
  ---
2
- title: Apps on crouter cloud
2
+ title: Apps on Northlight
3
3
  description: When you are building and registering an app on Northlight, or an app starts runs on a person's runtime and needs data back, read this because it takes you from sign-up to a running app against production, and runs.parse returns a typed, validated result where the transcript is not a data channel.
4
4
  ---
5
5
 
6
- # Apps on crouter cloud
6
+ # Apps on Northlight
7
7
 
8
8
  This page first walks through [building and registering an app](#build-and-register-an-app) against production with `crtr cloud apps`, then covers [managing it](#manage-an-app-with-crtr-cloud-apps), then runs.
9
9
 
@@ -5,13 +5,13 @@ description: Keep each person's runtime connection separate while building an ap
5
5
 
6
6
  # Apps with many users
7
7
 
8
- An app can bring people together without putting all of their agents or data on one runtime. **Each person's runtime holds that person's agents, memory, files, conversations, AI subscription, phone, and device.** Your app's server holds its user table keyed by the crouter cloud `sub`, one stored `Connection` per person in a `ConnectionStore` (runtime URL, rotating refresh token, grant id), in-app roles and settings, and data shared between users. Sign in with crouter cloud is the initial sign-in for every app user, including a visitor. Request the app's grants during sign-in from a person whose runtime your app will use. A visitor who only needs to be recognized signs in with identity only (see [Signing in a visitor](#signing-in-a-visitor)). crouter cloud permissions decide what your app can do on each person's runtime. Your app decides what its users can do to each other.
8
+ An app can bring people together without putting all of their agents or data on one runtime. **Each person's runtime holds that person's agents, memory, files, conversations, AI subscription, phone, and device.** Your app's server holds its user table keyed by the Northlight `sub`, one stored `Connection` per person in a `ConnectionStore` (runtime URL, rotating refresh token, grant id), in-app roles and settings, and data shared between users. Sign in with Northlight is the initial sign-in for every app user, including a visitor. Request the app's grants during sign-in from a person whose runtime your app will use. A visitor who only needs to be recognized signs in with identity only (see [Signing in a visitor](#signing-in-a-visitor)). Northlight permissions decide what your app can do on each person's runtime. Your app decides what its users can do to each other.
9
9
 
10
10
  Keep the connection and refresh token on the server, not in a visitor's browser. The server can call `providers.call` on a stored connection outside any run, even when nobody is present and the person is offline. Access tokens last 10 minutes; refresh tokens rotate on each use and expire after 90 days unused. Give `OAuth2Client` a persistent `ConnectionStore` with a cross-process lock as `store`, reach each person's runtime with `await oauth.client(userId)`, and schedule `oauth.refreshIdleConnections({ olderThanMs: 30 * 24 * 60 * 60 * 1000 })` on a timer. Act on a dead connection in one place, `onRefusal`, as [When a refresh is refused](../client.md#when-a-refresh-is-refused) describes, and delete a person's data only on `grant_removed` (`kind: 'removed'`). Learn when a background run ends with `oauth.watchRuns(...)` instead of a poll loop: see [Learn when a background run ends](../client.md#learn-when-a-background-run-ends). See [Client construction](../client.md#an-app-connection-and-refresh).
11
11
 
12
12
  ## Signing in a visitor
13
13
 
14
- A visitor who only needs to be recognized, not connected, signs in with identity only. Request `openid`, adding `email` for their email and `profile` for their name and photo, and no runtime scope. The directory shows one line ("Know who you are on crouter cloud"), writes no grant, and answers with an ID token and an access token usable only at `/userinfo`: no refresh token, grant, or runtime URL. Finish that sign-in with `exchangeIdentity`, not `exchangeCode`:
14
+ A visitor who only needs to be recognized, not connected, signs in with identity only. Request `openid`, adding `email` for their email and `profile` for their name and photo, and no runtime scope. The directory shows one line ("Know who you are on Northlight"), writes no grant, and answers with an ID token and an access token usable only at `/userinfo`: no refresh token, grant, or runtime URL. Finish that sign-in with `exchangeIdentity`, not `exchangeCode`:
15
15
 
16
16
  ```ts
17
17
  const {url, codeVerifier, state, nonce} = await oauth.authorizeUrl({scopes: ['openid', 'email', 'profile'], state: sessionState});
@@ -33,7 +33,7 @@ An `Identity` is not a connection. It has no runtime URL, grant, or refresh toke
33
33
 
34
34
  When visitor A causes your server to call person B's runtime using **B's connection**, the runtime records the app as `grantee` and B as `sub`. It does not know or record that A caused the call. Even a request outside a run, with nobody present, has this attribution. Keep your own record of the app user and action when you need it; do not send an app-user identity as a runtime principal.
35
35
 
36
- A visitor chatting with B's clone starts an app run on **B's runtime and B's AI subscription**, using the app's grant from B. The app rate-limits visitors; B's per-app spend cap is the platform backstop. crouter cloud's developer terms allow spending B's connection because of another user's action **only for what B turned on in your app, limited per person**. crouter cloud records nothing about the other user. B can control the app's spend cap or remove the app.
36
+ A visitor chatting with B's clone starts an app run on **B's runtime and B's AI subscription**, using the app's grant from B. The app rate-limits visitors; B's per-app spend cap is the platform backstop. Northlight's developer terms allow spending B's connection because of another user's action **only for what B turned on in your app, limited per person**. Northlight records nothing about the other user. B can control the app's spend cap or remove the app.
37
37
 
38
38
  ## The app's agent and documents
39
39
 
@@ -19,6 +19,6 @@ package @north-light/cli@0.3.332
19
19
 
20
20
  Handle every outcome arm. A result contains `output_parsed`; a decline says the agent could not honestly meet the schema; another failure carries its reason and detail. Transport and daemon failures still throw, so let those reach your normal application error handling.
21
21
 
22
- **From an app.** An app holding a person's connection does the same thing with `runs.parse({prompt, output_schema})`, which returns the run outcome union (`finalized` with `output_parsed` typed from the schema, `declined` with `details`, `canceled`, `deadline_exceeded`, `runtime_error`). It also covers `run_idle` and questions. See [Apps on crouter cloud](./apps-on-crouter-cloud.md#getting-a-result-back-from-a-run).
22
+ **From an app.** An app holding a person's connection does the same thing with `runs.parse({prompt, output_schema})`, which returns the run outcome union (`finalized` with `output_parsed` typed from the schema, `declined` with `details`, `canceled`, `deadline_exceeded`, `runtime_error`). It also covers `run_idle` and questions. See [Apps on Northlight](./apps-on-northlight.md#getting-a-result-back-from-a-run).
23
23
 
24
24
  This is the right shape for bounded work: one prompt, one answer, and no future message. If a user or external event must return to the same agent later, use a resident node instead. See [nodes and the canvas](../../concepts/nodes-and-the-canvas.md) for why an SDK node has durable identity, and [lifecycle and wakes](../../concepts/lifecycle-and-wakes.md) for the terminal-versus-resident decision.
@@ -171,7 +171,7 @@ The SDK now sends `Crouter-Minimum-Runtime-Version: <SDK_VERSION>` on every requ
171
171
 
172
172
  ### `grant.get` dependencies are one `{ state, message?, next }` entry per provider (breaking)
173
173
 
174
- `GrantGetDTO.dependencies[provider]` was a bare `SetupState` string; it is now a `ProviderDependency` (exported from `@north-light/sdk` and `@north-light/api`): `{ state, message?, next }`. `state` is the setup state the string used to be. `message` is the provider's own sentence for `pending` and `failed` (its `message_pending` / `message_failed`), so delete any copy of a provider's prose your app hard-codes and show `message` instead. `next` is the absolute URL of the directory's setup page for that provider and your app; link the person there (append `return_to` with your registered redirect origin) instead of building the URL yourself. **Change every read of `dependencies[p]` to `dependencies[p].state`** (or `dependencies[p]?.state` where the entry may be missing), and carry `message` and `next` into your `provider_setup_pending` details. The runtime reads only this shape from the directory's grant sync: a runtime on this release paired with a directory that still sends bare strings reports `dependencies` as `{}`, so roll the directory change with it. See [Waiting on a provider your app requires](./guides/apps-on-crouter-cloud.md#waiting-on-a-provider-your-app-requires).
174
+ `GrantGetDTO.dependencies[provider]` was a bare `SetupState` string; it is now a `ProviderDependency` (exported from `@north-light/sdk` and `@north-light/api`): `{ state, message?, next }`. `state` is the setup state the string used to be. `message` is the provider's own sentence for `pending` and `failed` (its `message_pending` / `message_failed`), so delete any copy of a provider's prose your app hard-codes and show `message` instead. `next` is the absolute URL of the directory's setup page for that provider and your app; link the person there (append `return_to` with your registered redirect origin) instead of building the URL yourself. **Change every read of `dependencies[p]` to `dependencies[p].state`** (or `dependencies[p]?.state` where the entry may be missing), and carry `message` and `next` into your `provider_setup_pending` details. The runtime reads only this shape from the directory's grant sync: a runtime on this release paired with a directory that still sends bare strings reports `dependencies` as `{}`, so roll the directory change with it. See [Waiting on a provider your app requires](./guides/apps-on-northlight.md#waiting-on-a-provider-your-app-requires).
175
175
 
176
176
  ## 0.3.393
177
177
 
@@ -211,7 +211,7 @@ A runtime on this release requires the `rev` claim in every app access token, so
211
211
 
212
212
  ### `runs.get` reports what a running run is doing (additive)
213
213
 
214
- `RunObject.activity` is `{phase: 'starting' | 'thinking' | 'writing' | 'between_turns'}` or `{phase: 'tool', tool: {name, summary}}` while the run is `running`, and `null` otherwise, so a polling app can show progress from `runs.get` without reading `runs.trace`. `runs.list` entries carry it too. See [Showing a background run's progress](./guides/apps-on-crouter-cloud.md#showing-a-background-runs-progress). A test double that builds a `RunObject` literal must add `activity`.
214
+ `RunObject.activity` is `{phase: 'starting' | 'thinking' | 'writing' | 'between_turns'}` or `{phase: 'tool', tool: {name, summary}}` while the run is `running`, and `null` otherwise, so a polling app can show progress from `runs.get` without reading `runs.trace`. `runs.list` entries carry it too. See [Showing a background run's progress](./guides/apps-on-northlight.md#showing-a-background-runs-progress). A test double that builds a `RunObject` literal must add `activity`.
215
215
 
216
216
  ## 0.3.374
217
217
 
@@ -37,7 +37,7 @@ Every namespace exported by the client. Namespaces are camelCase. Verbs are `cre
37
37
  | `grant` | `get` (`wait`, `since`: hold until `version` changes) | `GET /v1/grant` |
38
38
  | `health` | `get` | `GET /v1/health` |
39
39
 
40
- `runs`, `uploads`, `attachments`, `shares`, `questions` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; see [Apps on crouter cloud](./guides/apps-on-crouter-cloud.md) and [Apps with many users](./guides/apps-with-many-users.md). `memory` is the person's `public` documents ([Memory](./memory.md)). `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
40
+ `runs`, `uploads`, `attachments`, `shares`, `questions` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; see [Apps on Northlight](./guides/apps-on-northlight.md) and [Apps with many users](./guides/apps-with-many-users.md). `memory` is the person's `public` documents ([Memory](./memory.md)). `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
41
41
 
42
42
  Action methods keep the product's literal name (`fork`, `revive`, `promote`, `yield`) rather than being renamed into a generic verb. Every request-capable method takes `RequestOptions` as its final argument after its path, body, or query arguments. `RequestOptions` is `{ headers?, signal?, timeout?, maxRetries? }`; stream event options exclude `timeout` and add `after`.
43
43
 
@@ -5,7 +5,7 @@ description: FakeCloud from @north-light/sdk/testing — a fake directory and ru
5
5
 
6
6
  # Testing your app
7
7
 
8
- `@north-light/sdk/testing` exports `FakeCloud`, a fake crouter cloud: the directory (discovery, token refresh, JWKS) and each person's runtime (grant, runs, memory, providers, run notices). It answers through `fetch`. Your app's production path — `new OAuth2Client(...)`, `oauth.client(userId)`, `runs`, `memory`, `grant`, `providers`, `watchRuns`, `watchGrants`, `onRefusal`, `forwardRunEvents`, `verifyRunNotice` — runs against it unchanged. The only differences from production are the `fetch` and `issuer` you pass in.
8
+ `@north-light/sdk/testing` exports `FakeCloud`, a fake Northlight: the directory (discovery, token refresh, JWKS) and each person's runtime (grant, runs, memory, providers, run notices). It answers through `fetch`. Your app's production path — `new OAuth2Client(...)`, `oauth.client(userId)`, `runs`, `memory`, `grant`, `providers`, `watchRuns`, `watchGrants`, `onRefusal`, `forwardRunEvents`, `verifyRunNotice` — runs against it unchanged. The only differences from production are the `fetch` and `issuer` you pass in.
9
9
 
10
10
  There is no mock `Crouter`. An object typed as `Crouter` would re-implement `RunReply`, `RunStream`, the watchers, retries and error mapping, so your tests would check a copy of the SDK instead of the SDK. FakeCloud fakes only the wire, so every SDK behaviour under test is the one you ship.
11
11
 
@@ -40,7 +40,7 @@ Options: `new FakeCloud({clientId, issuer = 'https://directory.test', runtimeUrl
40
40
 
41
41
  ### Isolated runs
42
42
 
43
- The fake stands in for a crouter cloud guest, where the Linux root launcher always exists, so `runs.start({isolated: true})` starts a run by default and `run.params.isolated` is `true`. The checks `startRun` makes without the launcher are mirrored: `isolated` must be a boolean, `cwd` is accepted only with `isolated: true` and must be an absolute path inside the run's root folder, `/apps/<clientId>/nodes/<run id>` (else 400 `invalid_request`, `param: cwd`), and `worktree` is refused. Nothing in the fake enforces the sandbox itself (no network, no `files/` mount).
43
+ The fake stands in for a Northlight guest, where the Linux root launcher always exists, so `runs.start({isolated: true})` starts a run by default and `run.params.isolated` is `true`. The checks `startRun` makes without the launcher are mirrored: `isolated` must be a boolean, `cwd` is accepted only with `isolated: true` and must be an absolute path inside the run's root folder, `/apps/<clientId>/nodes/<run id>` (else 400 `invalid_request`, `param: cwd`), and `worktree` is refused. Nothing in the fake enforces the sandbox itself (no network, no `files/` mount).
44
44
 
45
45
  `isolatedRuns: 'unavailable'` models a runtime without the launcher, such as a local daemon on a Mac: every isolated start answers 503 `isolated_unavailable` (`param: isolated`, retryable, so `runs.start` sends it three times before throwing). Use it to test your app's handling of that refusal.
46
46
 
@@ -95,7 +95,7 @@ The real `OAuth2Client` handles all of it: your `onRefusal` runs with the refusa
95
95
 
96
96
  ## Testing mode
97
97
 
98
- An unverified third-party app is *in testing* on crouter cloud: only its admin and up to 100 named testers can sign in, and anyone else is refused. `cloud.testing` puts the fake's app in that state so your app can test the refusal.
98
+ An unverified third-party app is *in testing* on Northlight: only its admin and up to 100 named testers can sign in, and anyone else is refused. `cloud.testing` puts the fake's app in that state so your app can test the refusal.
99
99
 
100
100
  ```ts
101
101
  cloud.user('user:tess').setAccount({ email: 'tess@example.com' }); // the identifiers the account holds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.429",
3
+ "version": "0.3.431",
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.429",
140
+ "@north-light/identity": "0.3.431",
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,7 +1,7 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.429",
4
- "description": "crouter cloud access-token verification and scope rules",
3
+ "version": "0.3.431",
4
+ "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.429",
3
+ "version": "0.3.431",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.429",
9
+ "version": "0.3.431",
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.429",
21
+ "@north-light/identity": "0.3.431",
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.429",
12098
+ "version": "0.3.431",
12099
12099
  "license": "UNLICENSED"
12100
12100
  },
12101
12101
  "packages/crouter-env-docker": {
12102
12102
  "name": "@north-light/env-docker",
12103
- "version": "0.3.429",
12103
+ "version": "0.3.431",
12104
12104
  "license": "UNLICENSED"
12105
12105
  },
12106
12106
  "packages/crouter-identity": {
12107
12107
  "name": "@north-light/identity",
12108
- "version": "0.3.429",
12108
+ "version": "0.3.431",
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.429",
12116
+ "version": "0.3.431",
12117
12117
  "license": "UNLICENSED",
12118
12118
  "dependencies": {
12119
- "@north-light/api": "0.3.429",
12120
- "@north-light/identity": "0.3.429"
12119
+ "@north-light/api": "0.3.431",
12120
+ "@north-light/identity": "0.3.431"
12121
12121
  }
12122
12122
  },
12123
12123
  "packages/crouter-sdk": {
12124
12124
  "name": "@north-light/sdk",
12125
- "version": "0.3.429",
12125
+ "version": "0.3.431",
12126
12126
  "license": "UNLICENSED",
12127
12127
  "dependencies": {
12128
- "@north-light/api": "0.3.429",
12129
- "@north-light/identity": "0.3.429",
12128
+ "@north-light/api": "0.3.431",
12129
+ "@north-light/identity": "0.3.431",
12130
12130
  "jose": "^6.2.1"
12131
12131
  },
12132
12132
  "bin": {