@north-light/cli 0.3.492 → 0.3.494

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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.13";
2
+ export declare const CONTRACT_LEVEL = "1.14";
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.13",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.14",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};
@@ -25,6 +25,14 @@ export interface ReviveResultDTO {
25
25
  * already running, `frozen` found no free broker slot and left the row
26
26
  * waiting for one. */
27
27
  outcome: 'launched' | 'already-live' | 'frozen';
28
+ /** Whether the node's `view.sock` accepted a connection, checked by the
29
+ * daemon in its own namespace (a caller's sandbox cannot see another node's
30
+ * socket) for up to 10 s, ending early if the launched broker exits. `true`
31
+ * means the node is attachable; nothing more — it does not show the task was
32
+ * delivered or the model is making progress. `false` means no connection
33
+ * yet: a slow start, an exited broker, or (`frozen`) no broker launched.
34
+ * Absent from a daemon that predates the field: readiness is then unknown. */
35
+ ready?: boolean;
28
36
  }
29
37
  /** `POST /v1/nodes/{id}/relaunch-root` result. A null id means the target
30
38
  * was not a relaunchable live root, or its replacement failed to launch. */
@@ -118,6 +118,28 @@ Crtr-Caller: node=<id>; machine=<id>; profile=<id>; run=<id>
118
118
 
119
119
  The fields appear in that order. A field with no value is left out, and a call with none omits the header. A service uses `node` to link the calling node to an object it owns. The value comes from crtrd's view of the caller; a manifest cannot set `Crtr-Caller`, `Crtr-Signature` or `Crtr-Request-Id`.
120
120
 
121
+ ### Object kinds and the calling node
122
+
123
+ A plugin written with the kit declares canvas object kinds in `definePlugin({objectKinds})` instead of writing `object_kinds` by hand. Each key is the kind's name relative to the plugin (`repo`), lowercase letters, digits and `-`, with no `.`:
124
+
125
+ ```ts
126
+ definePlugin({
127
+ name: 'github',
128
+ objectKinds: {
129
+ repo: {
130
+ description: 'A GitHub repository',
131
+ events: { 'pr-opened': { level: 5 }, 'ci-failed': { level: 8, supersedes: true } },
132
+ read: { command: ['repo', 'read'] },
133
+ },
134
+ },
135
+ commands: { /* … */ },
136
+ });
137
+ ```
138
+
139
+ The served `bundle.json` carries `object_kinds` with each key prefixed `<providerId>.<name>`: `providerId` is the `auth.providerId` the handler was given, else the plugin name, the same value used in scope names. `definePlugin` throws `PluginDefinitionError` for a name with a `.` or capitals, a blank description, more than 64 events, a level outside 1-10, or a `read.command` that is not a non-streaming leaf of the plugin. Building the bundle throws when the prefixed kind is one crouter would refuse (a provider id with capitals or `_`, or a reserved first segment such as `repo` or `report`).
140
+
141
+ Every leaf's `ctx.caller.crtr` carries the calling node, parsed from `Crtr-Caller` by `parseCrtrCaller(header)` (also exported): `{ node?, machine?, profile?, run? }`, each field present only when the header carried it, and `crtr` itself absent when it carried none. **The header is asserted by the calling runtime, not verified:** the bearer token does not cover it. It tells you which of the verified `sub`'s nodes made the call, for example to link that node to an object. Never authorize on it; authorize on `ctx.caller.sub`.
142
+
121
143
  ### `Crtr-Signature`
122
144
 
123
145
  crtrd signs a call to a local plugin when the plugin holds a token and the request goes to the origin of its installed endpoint. Any other URL gets `Crtr-Caller` and no signature.
@@ -56,7 +56,7 @@ There is one request path. The client issues every request through the Web `fetc
56
56
 
57
57
  ## Autostart
58
58
 
59
- With `autostart` on (the default for a socket), a request that finds a cold socket runs `crtr sys daemon start`, waits for the daemon to serve, and retries the request once. This is what makes `npm i -g crouter` followed by `new Crouter()` work on a machine that has never run the daemon.
59
+ With `autostart` on (the default for a socket), a request that finds a cold socket runs `crtr sys daemon start`, waits for the daemon to serve, and retries the request once. This is what makes `npm i -g @north-light/cli` followed by `new Crouter()` work on a machine that has never run the daemon.
60
60
 
61
61
  Autostart is Node-only and applies only to the socket transport. A `baseURL` client cannot start a daemon it may not even share a machine with, so `autostart` defaults to `false` there; setting it to `true` on a `baseURL` client throws `CrouterError` (`autostart is only valid for the local socket transport`).
62
62
 
@@ -9,6 +9,40 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
9
9
 
10
10
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
11
11
 
12
+ ## 0.3.493
13
+
14
+ An app can act on a person's hosted runtime with no sign-in of theirs, and `@north-light/plugin` can declare canvas object kinds. Everything below is additive: no request or response an existing app sends or reads changes.
15
+
16
+ ### `oauth.appClient(userId)`: the app acts on a person's hosted runtime (additive)
17
+
18
+ `await oauth.appClient('user:<id>')` returns a client on that person's hosted runtime without a stored connection or refresh token. The SDK signs a `client_credentials` request with the `OAuth2Client`'s key (`private_key_jwt`) for `sub=user:<id>`, and the directory answers a token and the person's `runtimeUrl`. The app must be registered with the `app_token_for_user` operation, and the person must hold an active grant for it. Tokens are cached per person until a minute before they expire, and one mint is in flight per person however many callers ask. The client mints again on expiry and once after a `401`. A `userId` that is not `user:<id>` throws `CrouterError`.
19
+
20
+ - `NoHostedRuntimeError` (`no_hosted_runtime`, status 409, not retryable): the person has no hosted runtime to act on.
21
+ - `NotGrantedError` (`not_granted`, status 409, not retryable): the person has no active grant for the app, so they have not signed in to it.
22
+ - A runtime still provisioning (503), a directory 5xx and a 429 throw an error with `retryable: true`. `invalid_client` is a `401` `unauthorized` that names the signing key.
23
+
24
+ `no_hosted_runtime` and `not_granted` are appended to `sdkErrorCodes`, so the codes already in the list keep their positions. `NoHostedRuntimeError` and `NotGrantedError` are exported from `@north-light/sdk`. See [Errors](/docs/errors) and [Connections](/docs/concepts/connections#acting-without-a-sign-in).
25
+
26
+ ### `customObjects` and `canvas` on `AppClient` (additive, type change)
27
+
28
+ `AppClient` (the type of `oauth.client(userId)` and `oauth.appClient(userId)`) now offers two namespaces the app listener already served to an app grant token:
29
+
30
+ - `customObjects.register`, `publish` and `unregister` act on the custom objects your app owns. `receive` and `ack` stay owner-daemon only.
31
+ - `canvas.watch` and `canvas.unwatch` link a node to an object your app owns, under `crtr:objects:link:node`. Pass `watcher` (a node id) and, for a node of another of the account's machines, `watcher_machine`. `unwatch` takes the same `watcher` and `watcher_machine` to remove that node's watch; without `watcher` it removes the app's own watch.
32
+
33
+ Every other `canvas` method and `customObjects.receive`/`ack` remain absent from the type and throw `CrouterError` if reached. `Crouter` is still assignable to `AppClient`.
34
+
35
+ ### `@north-light/plugin`: `objectKinds` and `LeafCaller.crtr` (additive)
36
+
37
+ - `definePlugin({objectKinds})` declares canvas object kinds by name relative to the plugin (`repo`), each `{ description, events: { <name>: { level: 1-10, supersedes? } }, read?: { command: [...] } }`. The bundle the kit serves carries them in `bundle.json` `object_kinds`, keyed `<providerId>.<name>`: `providerId` is the `auth.providerId` the handler was given, else the plugin name. `definePlugin` throws `PluginDefinitionError` for a relative name that is not lowercase letters, digits and `-`, a blank description, more than 64 events, a level outside 1-10, and a `read.command` that is not a non-streaming leaf of the plugin. Building the bundle throws if the prefixed kind is not one crouter accepts (a provider id with capitals or `_`, or one of crouter's reserved roots such as `repo` or `report`).
38
+ - `ctx.caller.crtr` carries the calling node: `{ node?, machine?, profile?, run? }`, parsed by the exported `parseCrtrCaller(header)` from the request's `Crtr-Caller`. It is absent when the header names no field. The runtime asserts that header and the bearer token does not cover it, so use it to tell which of the verified `sub`'s nodes called, never to authorize; authorize on `sub`.
39
+
40
+ See [Local plugins](https://crouter.docs.northlight.ai/docs/plugin/deploying#local-plugins) and [Object kinds and the calling node](https://crouter.docs.northlight.ai/docs/plugin/deploying#object-kinds-and-the-calling-node).
41
+
42
+ ### `nodes.revive` reports `ready` (additive)
43
+
44
+ `ReviveResultDTO` gains an optional `ready`. The daemon checks, in its own namespace, whether the revived node's `view.sock` accepts a connection (up to 10 s, ending early if the launched broker exits) and returns the answer. `true` means the node is attachable; it does not show the task was delivered or the model is making progress. `false` means no connection yet, including `outcome: 'frozen'`. A daemon that predates the field sends none, so treat an absent `ready` as unknown.
45
+
12
46
  ## 0.3.488
13
47
 
14
48
  ### Developer CLI: `--help` exits 0, `login` is safe to re-run (CLI only, no app change)
@@ -40,13 +40,13 @@ Every namespace exported by the client. Namespaces are camelCase. Verbs are `cre
40
40
  | `grant` | `get` (`wait`, `since`: hold until `version` changes) | `GET /v1/grant` |
41
41
  | `health` | `get` | `GET /v1/health` |
42
42
 
43
- `runs`, `uploads`, `attachments`, `shares`, `questions`, `docs` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; `oauth.client(userId)` returns a client typed to exactly that set (`AppClient`); `nodes`, `bash`, `canvas`, `crons`, `human`, `models`, `customObjects`, `structuredRequests`, `system`, `auth`, `files.read/write/list` and `profiles.ensure/retrieve` are the owner's own daemon and are not served to an app. An app schedules work with its own timer calling `runs.start`, or a run's agent runs `crtr cron add` under `crtr:schedule`. See [[crouter-sdk/guides/apps-on-northlight]] and [[crouter-sdk/guides/apps-with-many-users]]. `memory` is the person's `public` documents ([[crouter-sdk/memory]]). `docs` writes, reads and edits documents the calling app owns: `docs.write({name, body, preview, kind?, rules?})` returns the canonical `name`; `docs.get(name)` returns `body`, `revision`, `rules` and the edit `base`; `docs.edit(name, {body, rationale, base?})` changes it in place and refuses a stale `base` with `ConflictError` (`stale_revision`). To keep one document current, `get` it (catch `NotFoundError` on first run to `write`), compare `body` to your text, and `edit` only when they differ. `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
43
+ `runs`, `uploads`, `attachments`, `shares`, `questions`, `docs` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; `oauth.client(userId)` returns a client typed to exactly that set (`AppClient`); `customObjects` (`register`, `publish`, `unregister`) and `canvas` (`watch`, `unwatch`) are served too, for the custom objects your app owns; `oauth.appClient(userId)` returns the same `AppClient` type without a stored sign-in ([[northlight-apps/concepts/connections]]). `nodes`, `bash`, `crons`, `human`, `models`, `structuredRequests`, `system`, `auth`, the rest of `canvas` and `customObjects` (`receive`, `ack`), `files.read/write/list` and `profiles.ensure/retrieve` are the owner's own daemon and are not served to an app. An app schedules work with its own timer calling `runs.start`, or a run's agent runs `crtr cron add` under `crtr:schedule`. See [[crouter-sdk/guides/apps-on-northlight]] and [[crouter-sdk/guides/apps-with-many-users]]. `memory` is the person's `public` documents ([[crouter-sdk/memory]]). `docs` writes, reads and edits documents the calling app owns: `docs.write({name, body, preview, kind?, rules?})` returns the canonical `name`; `docs.get(name)` returns `body`, `revision`, `rules` and the edit `base`; `docs.edit(name, {body, rationale, base?})` changes it in place and refuses a stale `base` with `ConflictError` (`stale_revision`). To keep one document current, `get` it (catch `NotFoundError` on first run to `write`), compare `body` to your text, and `edit` only when they differ. `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
44
44
 
45
45
  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`.
46
46
 
47
47
  ## Custom objects
48
48
 
49
- `client.customObjects` registers an external service or script as an object on the canvas, with a declared event catalog and a queue, URL or argv receiver, and publishes its events to the objects watching it. `register(body)` returns the object and, once, its publish token; `publish(ref, body)` sends one declared event (a custom object may publish with that token alone); `receive(ref, { after?, signal? })` is an async iterator over a queue receiver's deliveries and replays every unacknowledged delivery on each connect; `ack(ref, deliveryIds)` acknowledges them; `unregister(ref)` deletes the object. The same objects are managed from a terminal with `crtr custom-object` and watched with `crtr canvas watch`.
49
+ `client.customObjects` registers an external service or script as an object on the canvas, with a declared event catalog and a queue, URL or argv receiver, and publishes its events to the objects watching it. `register(body)` returns the object and, once, its publish token; `publish(ref, body)` sends one declared event (a custom object may publish with that token alone); `receive(ref, { after?, signal? })` is an async iterator over a queue receiver's deliveries and replays every unacknowledged delivery on each connect; `ack(ref, deliveryIds)` acknowledges them; `unregister(ref)` deletes the object. An app reaches `register`, `publish` and `unregister` on its own objects through `oauth.client(userId)` or `oauth.appClient(userId)`, and links a node to one with `client.canvas.watch(ref, { watcher, watcher_machine? })` and `client.canvas.unwatch(ref, { watcher, watcher_machine? })` under `crtr:objects:link:node` (without `watcher`, `unwatch` removes the app's own watch). The same objects are managed from a terminal with `crtr custom-object` and watched with `crtr canvas watch`.
50
50
 
51
51
  ## Files and bash
52
52
 
@@ -100,6 +100,23 @@ await oauth.refreshIdleConnections({olderThanMs: 30 * 24 * 60 * 60 * 1000});
100
100
 
101
101
  It refreshes every connection last saved before the cutoff, through the store's lock, and returns `{refreshed, refused, failures}`. A refused connection goes through `onRefusal` and is removed. `failures` holds connections that could not be refreshed and are still stored; the next run retries them. The SDK starts no timer. The store needs `listStale(before)`, which both `MemoryConnectionStore` and `PostgresConnectionStore` have.
102
102
 
103
+ ## Acting without a sign-in
104
+
105
+ A server that acts for a person on their hosted runtime, for example to publish events to the objects they watch, uses `oauth.appClient(userId)` instead of `oauth.client(userId)`. It stores no connection and refreshes nothing: the SDK signs a `client_credentials` request with your app's key for `sub=user:<id>`, and the directory answers a token and the person's `runtimeUrl`.
106
+
107
+ ```ts
108
+ const runtime = await oauth.appClient('user:o1');
109
+ await runtime.customObjects.publish(ref, {event: 'changed', body: '…'});
110
+ ```
111
+
112
+ It needs the app registered with the `app_token_for_user` operation, and an active grant from the person. The token is cached per person until a minute before it expires, the client mints a new one on expiry and once after a `401`, and concurrent callers share one mint. It returns the same `AppClient` as `oauth.client`, so `customObjects` (`register`, `publish`, `unregister`) and `canvas` (`watch`, `unwatch`) are available and the owner-daemon namespaces are not.
113
+
114
+ | Error | Code | Means |
115
+ |---|---|---|
116
+ | `NoHostedRuntimeError` | `no_hosted_runtime` (409) | The person has no hosted runtime. Not retryable. |
117
+ | `NotGrantedError` | `not_granted` (409) | The person has no active grant for your app, so they have not signed in to it. Not retryable. |
118
+ | any error with `retryable: true` | `runtime_provisioning` (503), a directory 5xx or a 429 | Try again later. |
119
+
103
120
  ## Signing in a visitor
104
121
 
105
122
  A person who only needs to be recognized, not connected, signs in with identity only: request `openid`, optionally with `email` and `profile`, and no runtime scope. Finish with `exchangeIdentity`, not `exchangeCode`. An `Identity` is not a connection: no runtime URL, grant or refresh token, nothing goes in the store, and you keep your own session. To act for that person later, send them through `authorizeUrl` again with the runtime scopes you need.
@@ -22,6 +22,8 @@ Codes the SDK raises itself are listed in `sdkErrorCodes`, and `CrouterError.cod
22
22
  | `oauth_nonce_missing` | `exchangeIdentity` was called without the `nonce` from `authorizeUrl`. | Save the nonce in the visitor's session and pass it back. |
23
23
  | `oauth_state_mismatch` | The callback's `state` was missing or differed from the saved one. | Refuse the callback. |
24
24
  | `not_connected` | `oauth.client(userId)` or a refresh found no stored connection for that person. | Show "sign in again". |
25
+ | `no_hosted_runtime` (409, `NoHostedRuntimeError`) | `oauth.appClient(userId)`: the person has no hosted runtime to act on. Not retryable. | Skip them, or ask them to set up a hosted runtime. |
26
+ | `not_granted` (409, `NotGrantedError`) | `oauth.appClient(userId)`: the person has no active grant for your app. Not retryable. | Send them through sign-in ([[northlight-apps/concepts/connections]]). |
25
27
  | `refusal_handler_reentry` | An `onRefusal` handler called `oauth.client` (or a refresh) for the person whose connection it is handling. | Don't call the runtime for `refusal.userId` inside the handler. |
26
28
  | `unauthorized` (401, `AuthenticationError`) | The directory refused your app's client assertion (`invalid_client`) on `exchangeCode`, a refresh or `revoke`, including a refresh inside a client from `oauth.client(userId)`. The message names the key id (`kid`) you signed with and asks whether that key's public JWK is registered. `details` is `{oauth_error: 'invalid_client', kid}`, or `key_thumbprint` instead of `kid` for a key with no `kid`. | Check that the key's public JWK is on the app's **Sign-in** tab ([[northlight-apps/guides/manage-your-app]]). |
27
29
  | `run_idle`, `waiting_on_user` | `runs.parse`: the run stopped without submitting, or waits on a question and you passed no `onQuestion`. | See [[northlight-apps/guides/typed-results]]. |
@@ -1 +1 @@
1
- var w=Object.defineProperty;var s=(e,r)=>w(e,"name",{value:r,configurable:!0});import{defineLeaf as g}from"../core/command.js";import{InputError as a}from"../core/io.js";import{requireScope as p}from"../core/errors.js";import{cliClient as y,rethrowAsCliError as u}from"./api-client.js";import{recordedPidLiveness as k}from"../core/canvas/pid.js";import{fullName as b}from"../core/canvas/labels.js";import{faultNeedsYou as x}from"../core/canvas/status-glyph.js";import{waitForViewSocketReady as R}from"../core/runtime/view-socket.js";function E(e){return e===null||e.link!=="pi\u2192provider"?!1:x(e)?!0:e.retry.by==="daemon"}s(E,"isNowEligibleFault");const C=g({name:"revive",description:"reopen a window for a done/idle/dead/canceled node",whenToUse:"you want to bring a dormant node back yourself \u2014 reopen a window for one that is done, idle, dead, or canceled: resume a node you closed with `node lifecycle close`, reopen a finished worker for a follow-up, or restart a crashed one now instead of waiting. It resumes the saved conversation by default, or can restart the node clean. You rarely need this for crashes \u2014 the daemon auto-revives those; reach for it to bring a node back on demand, to revive a canceled node the daemon will never touch on its own, or to kick a hanging one early (--now). To mass-reconnect EVERY disconnected node at once (after a reboot, a killed login/tmux session, or the daemon being down a while), use `canvas revive --all` instead",help:{name:"node lifecycle revive",summary:"relaunch a node's broker engine (resuming its saved conversation), or kick a hanging one (--now)",params:[{kind:"positional",name:"node",required:!0,constraint:"Node id to revive."},{kind:"flag",name:"fresh",type:"bool",required:!1,default:!1,constraint:"When set, do NOT do a true resume. If the node already has a session `.jsonl`, this instead CYCLES it in place \u2014 reuses that SAME file, resets the tree leaf, and roots a fresh `crtr-cycle` branch (a clean-context restart that still carries the file's history, output `resumed:false`). Only when no `.jsonl` exists yet does this start a genuinely brand-new session (also `resumed:false`). Default (no --fresh): a true resume \u2014 replays the existing `.jsonl` as-is (`resumed:true`)."},{kind:"flag",name:"now",type:"bool",required:!1,default:!1,constraint:'On-demand kick of an active-fault node (parked on a `pi\u2192provider` fault, broker still alive). Its broker pid is alive, so an ordinary revive no-ops (double-launch guard) \u2014 --now SIGTERMs the live broker instead, so the daemon\'s crash\u2192grace\u2192resume path brings it back in ~20s. Valid for a live `pi\u2192provider` fault that is either a daemon-owned auto retry (kicks it early), or ANY fault the canvas dashboard flags "needs you" (e.g. exhausted retries, an auth fault you just fixed) \u2014 that disposition otherwise has no recovery verb. Mutually exclusive with --fresh.'},{kind:"flag",name:"reopen",type:"bool",required:!1,default:!1,constraint:"Clear a latched node's finalization latch (final_report/finalized_at only \u2014 the old report file and every delivered feed event are untouched) before reviving it. Rejected against a node that is not finalized. Mutually exclusive with --now."}],output:[{name:"resumed",type:"boolean",required:!1,constraint:"True ONLY for a true resume (the existing `.jsonl` replayed as-is). False for a --fresh revive, whether that landed as an in-place CYCLE (an existing `.jsonl` reused with a fresh `crtr-cycle` branch \u2014 pi is still told --session, but this is not a resume) or a genuinely fresh launch (no `.jsonl` existed). Absent for a --now kick."},{name:"ready",type:"boolean",required:!1,constraint:"True when the revived broker's view.sock accepted a connection before return \u2014 the node is immediately attachable/drivable. Absent for a --now kick."},{name:"kicked",type:"boolean",required:!1,constraint:"True when --now SIGTERM'd the hanging node's live broker (the daemon resumes it on the saved session within ~20s). Absent for an ordinary revive."},{name:"message",type:"string",required:!1,constraint:"Kick confirmation naming the signalled node, pid, and the daemon's ~20s resume handoff. Present only for --now."}],outputKind:"object",effects:["Launches the node's detached headless broker engine (no tmux window).","Updates the node's canvas record: status=active, intent=null.","Blocks until the broker's view.sock accepts a connection (up to ~30s), so a caller can attach/dial immediately on return.","--now: SIGTERMs the live broker instead of launching anything (the daemon's own crash-grace path resumes it)."]},run:s(async e=>{p("crtr:act","crtr node lifecycle revive");const r=e.now??!1,n=e.node,l=e.fresh??!1,d=e.reopen??!1;if(r&&d)throw new a({error:"conflicting_args",message:"`--now` kicks a LIVE broker (nothing to reopen \u2014 a finalized node has no live broker); `--reopen` is meaningless there.",next:"Run `crtr node lifecycle revive <node> --now` on a hanging node, or `crtr node lifecycle revive <node> --reopen` (no --now) to re-task a finalized one."});if(r&&l)throw new a({error:"conflicting_args",message:"`--now` kicks a live hanging broker (SIGTERM \u2192 daemon resumes the SAVED session); `--fresh` is meaningless there.",next:"Run `crtr node lifecycle revive <node> --now` (resumes), or `crtr node lifecycle revive <node> --fresh` (no --now) to relaunch clean."});const c=y();if(r){let o;try{o=await c.getNode(n)}catch(v){u(v)}const t=o.pi_pid,m=o.fault??null;if(t==null||k(t,o.pi_pid_identity??null)!=="alive")throw new a({error:"not_hanging",message:`${n} has no live broker \u2014 nothing to kick. --now is for a HANGING node (live broker parked on an active fault).`,next:"Revive a dormant node with `crtr node lifecycle revive "+n+"` (no --now)."});if(!E(m))throw new a({error:"not_hanging",message:`${n} is live but has no pi\u2192provider fault eligible for --now (either healthy, or a client-owned retry / non-provider fault already being handled elsewhere) \u2014 --now refuses to SIGTERM a node that isn't stuck on a kickable provider fault.`,next:"Use --now only on a node the canvas shows as hanging (\u26A0) with a `pi\u2192provider` fault. For a routine relaunch, omit --now."});if(o.streaming===!0)throw new a({error:"busy",message:`${n} is mid-turn right now (busy) \u2014 --now refuses to SIGTERM a working engine; it has recovered on its own.`,next:"Re-check the canvas \u2014 if it's no longer marked hanging (\u26A0), nothing to do."});try{process.kill(t,"SIGTERM")}catch{}return{kicked:!0,message:`Sent SIGTERM to ${b(o)} (${n}, pid ${t}). The daemon will resume it on the saved session within ~20s (crash\u2192grace\u2192resume).`}}const i=process.env.CRTR_CRON_ID;let h;try{h=await c.reviveNode(n,{resume:!l,reopen:d,...i!==void 0&&i!==""?{cron_id:i}:{}})}catch(o){u(o)}const f=await R(n);return{resumed:h.resumed,ready:f}},"run")});export{E as isNowEligibleFault,C as nodeReviveLeaf};
1
+ var v=Object.defineProperty;var d=(e,r)=>v(e,"name",{value:r,configurable:!0});import{defineLeaf as w}from"../core/command.js";import{InputError as t}from"../core/io.js";import{requireScope as g}from"../core/errors.js";import{cliClient as p,rethrowAsCliError as u}from"./api-client.js";import{recordedPidLiveness as k}from"../core/canvas/pid.js";import{fullName as y}from"../core/canvas/labels.js";import{faultNeedsYou as b}from"../core/canvas/status-glyph.js";function x(e){return e===null||e.link!=="pi\u2192provider"?!1:b(e)?!0:e.retry.by==="daemon"}d(x,"isNowEligibleFault");const G=w({name:"revive",description:"reopen a window for a done/idle/dead/canceled node",whenToUse:"you want to bring a dormant node back yourself \u2014 reopen a window for one that is done, idle, dead, or canceled: resume a node you closed with `node lifecycle close`, reopen a finished worker for a follow-up, or restart a crashed one now instead of waiting. It resumes the saved conversation by default, or can restart the node clean. You rarely need this for crashes \u2014 the daemon auto-revives those; reach for it to bring a node back on demand, to revive a canceled node the daemon will never touch on its own, or to kick a hanging one early (--now). To mass-reconnect EVERY disconnected node at once (after a reboot, a killed login/tmux session, or the daemon being down a while), use `canvas revive --all` instead",help:{name:"node lifecycle revive",summary:"relaunch a node's broker engine (resuming its saved conversation), or kick a hanging one (--now)",params:[{kind:"positional",name:"node",required:!0,constraint:"Node id to revive."},{kind:"flag",name:"fresh",type:"bool",required:!1,default:!1,constraint:"When set, do NOT do a true resume. If the node already has a session `.jsonl`, this instead CYCLES it in place \u2014 reuses that SAME file, resets the tree leaf, and roots a fresh `crtr-cycle` branch (a clean-context restart that still carries the file's history, output `resumed:false`). Only when no `.jsonl` exists yet does this start a genuinely brand-new session (also `resumed:false`). Default (no --fresh): a true resume \u2014 replays the existing `.jsonl` as-is (`resumed:true`)."},{kind:"flag",name:"now",type:"bool",required:!1,default:!1,constraint:'On-demand kick of an active-fault node (parked on a `pi\u2192provider` fault, broker still alive). Its broker pid is alive, so an ordinary revive no-ops (double-launch guard) \u2014 --now SIGTERMs the live broker instead, so the daemon\'s crash\u2192grace\u2192resume path brings it back in ~20s. Valid for a live `pi\u2192provider` fault that is either a daemon-owned auto retry (kicks it early), or ANY fault the canvas dashboard flags "needs you" (e.g. exhausted retries, an auth fault you just fixed) \u2014 that disposition otherwise has no recovery verb. Mutually exclusive with --fresh.'},{kind:"flag",name:"reopen",type:"bool",required:!1,default:!1,constraint:"Clear a latched node's finalization latch (final_report/finalized_at only \u2014 the old report file and every delivered feed event are untouched) before reviving it. Rejected against a node that is not finalized. Mutually exclusive with --now."}],output:[{name:"resumed",type:"boolean",required:!1,constraint:"True ONLY for a true resume (the existing `.jsonl` replayed as-is). False for a --fresh revive, whether that landed as an in-place CYCLE (an existing `.jsonl` reused with a fresh `crtr-cycle` branch \u2014 pi is still told --session, but this is not a resume) or a genuinely fresh launch (no `.jsonl` existed). Absent for a --now kick."},{name:"ready",type:"boolean",required:!1,constraint:"True when the daemon saw the node's view.sock accept a connection within ~10s of the revive \u2014 the node is attachable. It does not show the task was delivered or the model is making progress. False when it did not (still starting, broker exited, or no free broker slot). Absent for a --now kick, and when the daemon predates this field (readiness unknown)."},{name:"kicked",type:"boolean",required:!1,constraint:"True when --now SIGTERM'd the hanging node's live broker (the daemon resumes it on the saved session within ~20s). Absent for an ordinary revive."},{name:"message",type:"string",required:!1,constraint:"Kick confirmation naming the signalled node, pid, and the daemon's ~20s resume handoff. Present only for --now."}],outputKind:"object",effects:["Launches the node's detached headless broker engine (no tmux window).","Updates the node's canvas record: status=active, intent=null.","Waits up to ~10s for the daemon to see the broker's view.sock accept a connection, so `ready:true` means a caller can attach on return.","--now: SIGTERMs the live broker instead of launching anything (the daemon's own crash-grace path resumes it)."]},run:d(async e=>{g("crtr:act","crtr node lifecycle revive");const r=e.now??!1,o=e.node,l=e.fresh??!1,c=e.reopen??!1;if(r&&c)throw new t({error:"conflicting_args",message:"`--now` kicks a LIVE broker (nothing to reopen \u2014 a finalized node has no live broker); `--reopen` is meaningless there.",next:"Run `crtr node lifecycle revive <node> --now` on a hanging node, or `crtr node lifecycle revive <node> --reopen` (no --now) to re-task a finalized one."});if(r&&l)throw new t({error:"conflicting_args",message:"`--now` kicks a live hanging broker (SIGTERM \u2192 daemon resumes the SAVED session); `--fresh` is meaningless there.",next:"Run `crtr node lifecycle revive <node> --now` (resumes), or `crtr node lifecycle revive <node> --fresh` (no --now) to relaunch clean."});const h=p();if(r){let n;try{n=await h.getNode(o)}catch(m){u(m)}const i=n.pi_pid,f=n.fault??null;if(i==null||k(i,n.pi_pid_identity??null)!=="alive")throw new t({error:"not_hanging",message:`${o} has no live broker \u2014 nothing to kick. --now is for a HANGING node (live broker parked on an active fault).`,next:"Revive a dormant node with `crtr node lifecycle revive "+o+"` (no --now)."});if(!x(f))throw new t({error:"not_hanging",message:`${o} is live but has no pi\u2192provider fault eligible for --now (either healthy, or a client-owned retry / non-provider fault already being handled elsewhere) \u2014 --now refuses to SIGTERM a node that isn't stuck on a kickable provider fault.`,next:"Use --now only on a node the canvas shows as hanging (\u26A0) with a `pi\u2192provider` fault. For a routine relaunch, omit --now."});if(n.streaming===!0)throw new t({error:"busy",message:`${o} is mid-turn right now (busy) \u2014 --now refuses to SIGTERM a working engine; it has recovered on its own.`,next:"Re-check the canvas \u2014 if it's no longer marked hanging (\u26A0), nothing to do."});try{process.kill(i,"SIGTERM")}catch{}return{kicked:!0,message:`Sent SIGTERM to ${y(n)} (${o}, pid ${i}). The daemon will resume it on the saved session within ~20s (crash\u2192grace\u2192resume).`}}const s=process.env.CRTR_CRON_ID;let a;try{a=await h.reviveNode(o,{resume:!l,reopen:c,...s!==void 0&&s!==""?{cron_id:s}:{}})}catch(n){u(n)}return{resumed:a.resumed,...a.ready!==void 0?{ready:a.ready}:{}}},"run")});export{x as isNowEligibleFault,G as nodeReviveLeaf};
@@ -1,4 +1,3 @@
1
- var de=Object.defineProperty;var p=(t,e)=>de(t,"name",{value:e,configurable:!0});import{randomUUID as ce}from"node:crypto";import{streamSimple as fe}from"@earendil-works/pi-ai/compat";import{expandModelCandidates as Y,parseModelSpec as ue,probeRouteAvailability as me}from"../model-routes.js";import{readMergedLaunchConfig as ee}from"../config.js";import{openModelRegistry as pe}from"../runtime/model-registry.js";import{coolingUntilFor as te,isManagedProvider as E,markSubscriptionRateLimited as he,markSubscriptionSuccess as ye,readSubscriptionPool as _e,retryAfterMsFromHeaders as Z,retryAfterMsFromError as ge,refreshSubscriptionCredential as ve,subscriptionNeedsSuccessWrite as be}from"../subscription-state.js";import{classifyRateLimit as we,describeRateLimitScope as oe,rateLimitHeaders as xe}from"../rate-limit-scope.js";import{RunError as l}from"../runs/operations.js";import{recognizeProviderError as ne}from"../provider-error-recognition.js";import{isSafetyRefusal as ke}from"./refusal.js";import{assertUnderCap as je,recordModelUsage as qe}from"../usage/ledger.js";function Ae(t){return typeof t=="string"||t===null||Array.isArray(t)&&t.every(e=>e&&typeof e=="object"&&(e.type==="text"&&typeof e.text=="string"||e.type==="image_url"&&typeof e.image_url?.url=="string"&&/^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(e.image_url.url)))}p(Ae,"validContent");function re(t){return Array.isArray(t)?t.map(e=>{if(e.type==="text")return e;const[,n,r]=/^data:(image\/(?:png|jpeg|gif|webp));base64,(.*)$/.exec(e.image_url.url);return{type:"image",mimeType:n,data:r}}):t??""}p(re,"imageContent");function We(t){if(!t||typeof t!="object"||Array.isArray(t))throw new l(400,"invalid_request","body must be an object");const e=t;if(!Array.isArray(e.messages)||!e.messages.every(n=>n&&typeof n=="object"&&["system","user","assistant","tool"].includes(n.role)&&Ae(n.content)))throw new l(400,"invalid_request","messages must be chat messages","messages");if(e.messages.some(n=>["system","assistant"].includes(n.role)&&typeof n.content!="string"&&n.content!==null))throw new l(400,"invalid_request","system and assistant content must be text","messages");if(e.model!==void 0&&typeof e.model!="string")throw new l(400,"invalid_request","model must be a string","model");if(e.model_policy!==void 0&&!["exact","substitute"].includes(e.model_policy))throw new l(400,"invalid_request","invalid model_policy","model_policy");if(e.payer!==void 0&&!["user","app"].includes(e.payer))throw new l(400,"invalid_request","invalid payer","payer");if(e.reasoning_effort!==void 0&&!["minimal","low","medium","high","xhigh","max"].includes(e.reasoning_effort))throw new l(400,"invalid_request","invalid reasoning_effort","reasoning_effort");if(e.stream!==void 0&&typeof e.stream!="boolean")throw new l(400,"invalid_request","stream must be boolean","stream");if(e.tools!==void 0&&(!Array.isArray(e.tools)||!e.tools.every(n=>n?.type==="function"&&typeof n.function?.name=="string"&&n.function.name.length>0&&(n.function.description===void 0||typeof n.function.description=="string")&&(n.function.parameters===void 0||n.function.parameters!==null&&typeof n.function.parameters=="object"&&!Array.isArray(n.function.parameters)&&n.function.parameters.type==="object"))))throw new l(400,"invalid_request","invalid tools","tools");if(e.tool_choice!==void 0&&e.tool_choice!=="auto"&&e.tool_choice!=="none"&&e.tool_choice!=="required"&&(typeof e.tool_choice!="object"||e.tool_choice===null||e.tool_choice.type!=="function"||typeof e.tool_choice.function?.name!="string"))throw new l(400,"invalid_request","invalid tool_choice","tool_choice");if(e.messages.some(n=>n.role==="assistant"&&n.tool_calls!==void 0&&(!Array.isArray(n.tool_calls)||!n.tool_calls.every(r=>r&&typeof r.id=="string"&&typeof r.function?.name=="string"&&typeof r.function.arguments=="string"&&Me(r.function.arguments)))))throw new l(400,"invalid_request","invalid assistant tool_calls","messages");if(e.stop!==void 0&&!(typeof e.stop=="string"||Array.isArray(e.stop)&&e.stop.every(n=>typeof n=="string")))throw new l(400,"invalid_request","invalid stop","stop");if(e.seed!==void 0&&!Number.isSafeInteger(e.seed))throw new l(400,"invalid_request","invalid seed","seed");for(const[n,r]of[["temperature",e.temperature],["top_p",e.top_p],["max_tokens",e.max_tokens]])if(r!==void 0&&(typeof r!="number"||!Number.isFinite(r)||n==="temperature"&&(r<0||r>2)||n==="top_p"&&(r<0||r>1)||n==="max_tokens"&&(!Number.isSafeInteger(r)||r<1)))throw new l(400,"invalid_request",`invalid ${n}`,n);if(e.response_format!==void 0){const n=e.response_format;if(!n||typeof n!="object"||Array.isArray(n)||!["text","json_object","json_schema"].includes(n.type)||n.type==="json_schema"&&(typeof n.json_schema?.name!="string"||!n.json_schema.name||!n.json_schema.schema||typeof n.json_schema.schema!="object"||Array.isArray(n.json_schema.schema)))throw new l(400,"invalid_request","invalid response_format","response_format")}if(e.metadata!==void 0&&Buffer.byteLength(JSON.stringify(e.metadata)??"")>4096)throw new l(400,"invalid_request","metadata exceeds 4 KB","metadata");return e}p(We,"parseLlmRequest");function Me(t){try{const e=JSON.parse(t);return e!==null&&typeof e=="object"&&!Array.isArray(e)}catch{return!1}}p(Me,"isJsonObject");function $e(t,e){const n=Date.now(),r=t.messages.map(s=>s.role==="system"?{role:"system",content:s.content??"",timestamp:n}:s.role==="tool"?{role:"toolResult",toolCallId:s.tool_call_id??"",toolName:"",content:typeof s.content=="string"||s.content===null?[{type:"text",text:s.content??""}]:re(s.content),isError:!1,timestamp:n}:s.role==="assistant"?{role:"assistant",content:[...s.content?[{type:"text",text:s.content}]:[],...(s.tool_calls??[]).map(c=>({type:"toolCall",id:c.id,name:c.function.name,arguments:JSON.parse(c.function.arguments)}))],api:"openai-completions",provider:"openai",model:t.model??"",usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"stop",timestamp:n}:{role:"user",content:re(s.content),timestamp:n});if(e){const s=r.filter(f=>f.role==="system").map(f=>f.content).join(`
2
- `).slice(0,16384),c=r.findIndex(f=>f.role==="user");if(s&&c>=0){const f=r[c];r[c]={...f,content:typeof f.content=="string"?`${s}
1
+ var de=Object.defineProperty;var u=(t,e)=>de(t,"name",{value:e,configurable:!0});import{randomUUID as ce}from"node:crypto";import{streamSimple as fe}from"@earendil-works/pi-ai/compat";import{expandModelCandidates as Y,parseModelSpec as ue,probeRouteAvailability as me}from"../model-routes.js";import{readMergedLaunchConfig as ee}from"../config.js";import{openModelRegistry as pe}from"../runtime/model-registry.js";import{coolingUntilFor as te,isManagedProvider as E,markSubscriptionRateLimited as he,markSubscriptionSuccess as _e,readSubscriptionPool as ye,retryAfterMsFromHeaders as Z,retryAfterMsFromError as ge,refreshSubscriptionCredential as ve,subscriptionNeedsSuccessWrite as be}from"../subscription-state.js";import{classifyRateLimit as we,describeRateLimitScope as oe,rateLimitHeaders as xe}from"../rate-limit-scope.js";import{RunError as l}from"../runs/operations.js";import{recognizeProviderError as ne}from"../provider-error-recognition.js";import{isSafetyRefusal as ke}from"./refusal.js";import{assertUnderCap as je,recordModelUsage as qe}from"../usage/ledger.js";function Ae(t){return typeof t=="string"||t===null||Array.isArray(t)&&t.every(e=>e&&typeof e=="object"&&(e.type==="text"&&typeof e.text=="string"||e.type==="image_url"&&typeof e.image_url?.url=="string"&&/^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(e.image_url.url)))}u(Ae,"validContent");function re(t){return Array.isArray(t)?t.map(e=>{if(e.type==="text")return e;const[,n,r]=/^data:(image\/(?:png|jpeg|gif|webp));base64,(.*)$/.exec(e.image_url.url);return{type:"image",mimeType:n,data:r}}):t??""}u(re,"imageContent");function We(t){if(!t||typeof t!="object"||Array.isArray(t))throw new l(400,"invalid_request","body must be an object");const e=t;if(!Array.isArray(e.messages)||!e.messages.every(n=>n&&typeof n=="object"&&["system","user","assistant","tool"].includes(n.role)&&Ae(n.content)))throw new l(400,"invalid_request","messages must be chat messages","messages");if(e.messages.some(n=>["system","assistant"].includes(n.role)&&typeof n.content!="string"&&n.content!==null))throw new l(400,"invalid_request","system and assistant content must be text","messages");if(e.model!==void 0&&typeof e.model!="string")throw new l(400,"invalid_request","model must be a string","model");if(e.model_policy!==void 0&&!["exact","substitute"].includes(e.model_policy))throw new l(400,"invalid_request","invalid model_policy","model_policy");if(e.payer!==void 0&&!["user","app"].includes(e.payer))throw new l(400,"invalid_request","invalid payer","payer");if(e.reasoning_effort!==void 0&&!["minimal","low","medium","high","xhigh","max"].includes(e.reasoning_effort))throw new l(400,"invalid_request","invalid reasoning_effort","reasoning_effort");if(e.stream!==void 0&&typeof e.stream!="boolean")throw new l(400,"invalid_request","stream must be boolean","stream");if(e.tools!==void 0&&(!Array.isArray(e.tools)||!e.tools.every(n=>n?.type==="function"&&typeof n.function?.name=="string"&&n.function.name.length>0&&(n.function.description===void 0||typeof n.function.description=="string")&&(n.function.parameters===void 0||n.function.parameters!==null&&typeof n.function.parameters=="object"&&!Array.isArray(n.function.parameters)&&n.function.parameters.type==="object"))))throw new l(400,"invalid_request","invalid tools","tools");if(e.tool_choice!==void 0&&e.tool_choice!=="auto"&&e.tool_choice!=="none"&&e.tool_choice!=="required"&&(typeof e.tool_choice!="object"||e.tool_choice===null||e.tool_choice.type!=="function"||typeof e.tool_choice.function?.name!="string"))throw new l(400,"invalid_request","invalid tool_choice","tool_choice");if(e.messages.some(n=>n.role==="assistant"&&n.tool_calls!==void 0&&(!Array.isArray(n.tool_calls)||!n.tool_calls.every(r=>r&&typeof r.id=="string"&&typeof r.function?.name=="string"&&typeof r.function.arguments=="string"&&Me(r.function.arguments)))))throw new l(400,"invalid_request","invalid assistant tool_calls","messages");if(e.stop!==void 0&&!(typeof e.stop=="string"||Array.isArray(e.stop)&&e.stop.every(n=>typeof n=="string")))throw new l(400,"invalid_request","invalid stop","stop");if(e.seed!==void 0&&!Number.isSafeInteger(e.seed))throw new l(400,"invalid_request","invalid seed","seed");for(const[n,r]of[["temperature",e.temperature],["top_p",e.top_p],["max_tokens",e.max_tokens]])if(r!==void 0&&(typeof r!="number"||!Number.isFinite(r)||n==="temperature"&&(r<0||r>2)||n==="top_p"&&(r<0||r>1)||n==="max_tokens"&&(!Number.isSafeInteger(r)||r<1)))throw new l(400,"invalid_request",`invalid ${n}`,n);if(e.response_format!==void 0){const n=e.response_format;if(!n||typeof n!="object"||Array.isArray(n)||!["text","json_object","json_schema"].includes(n.type)||n.type==="json_schema"&&(typeof n.json_schema?.name!="string"||!n.json_schema.name||!n.json_schema.schema||typeof n.json_schema.schema!="object"||Array.isArray(n.json_schema.schema)))throw new l(400,"invalid_request","invalid response_format","response_format")}if(e.metadata!==void 0&&Buffer.byteLength(JSON.stringify(e.metadata)??"")>4096)throw new l(400,"invalid_request","metadata exceeds 4 KB","metadata");return e}u(We,"parseLlmRequest");function Me(t){try{const e=JSON.parse(t);return e!==null&&typeof e=="object"&&!Array.isArray(e)}catch{return!1}}u(Me,"isJsonObject");function Re(t){const e=Date.now(),n=t.messages.map(a=>a.role==="system"?{role:"system",content:a.content??"",timestamp:e}:a.role==="tool"?{role:"toolResult",toolCallId:a.tool_call_id??"",toolName:"",content:typeof a.content=="string"||a.content===null?[{type:"text",text:a.content??""}]:re(a.content),isError:!1,timestamp:e}:a.role==="assistant"?{role:"assistant",content:[...a.content?[{type:"text",text:a.content}]:[],...(a.tool_calls??[]).map(w=>({type:"toolCall",id:w.id,name:w.function.name,arguments:JSON.parse(w.function.arguments)}))],api:"openai-completions",provider:"openai",model:t.model??"",usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"stop",timestamp:e}:{role:"user",content:re(a.content),timestamp:e}),r=n.findIndex(a=>a.role!=="system"),y=n.slice(0,r<0?n.length:r).map(a=>a.content),m=y.join(`
3
2
 
4
- ${f.content}`:[{type:"text",text:s},...f.content]}}s&&c<0&&r.unshift({role:"user",content:s,timestamp:n});for(let f=r.length-1;f>=0;f--)r[f]?.role==="system"&&r.splice(f,1)}const _=t.tools?.map(({function:s})=>({name:s.name,description:s.description??"",parameters:s.parameters??{type:"object",properties:{}}}));return{messages:r,..._?{tools:_}:{}}}p($e,"contextOf");function se(t,e,n,r){if(!t){const s=ee(n,r),c=Object.values({...s.modelRoutes??{},...s.legacyModelRoutes??{}}),f=c.length?[s.modelLadders.defaultProvider,...s.modelRouting?.familyOrder??[],...c.map(k=>k.family)].find(k=>c.some(I=>I.family===k)):s.modelLadders.defaultProvider??"anthropic";return{kind:"logical",family:f,strength:"strong",spec:`${f}/strong`}}const _=/^([^/]+)\/(ultra|strong|medium|light)$/.exec(t);return _?{kind:"logical",family:_[1],strength:_[2],spec:t}:{kind:e==="exact"?"exact":"concrete",spec:t}}p(se,"requestFor");function Re(t,e){const{input:n,output:r,cacheRead:_,cacheWrite:s}=t.cost;return n===0&&r===0&&_===0&&s===0?null:Number.isFinite(e)&&e>=0?e:null}p(Re,"usdEquivalent");function Ie(t,e){return t.compat?.allowedFallbackModels?.find(r=>r.provider===t.provider&&r.model===e)}p(Ie,"allowedFallback");async function He(t,e,n,r,_){if(t.payer==="app")throw new l(403,"payer_not_app","app-paid inference routes are not registered");je(_);const s=await pe(),c=se(t.model,t.model_policy,r?.cwd,r?.profileId),f=p(m=>m.routeId!=="concrete"&&m.routeId!=="exact","configured"),k=p(m=>{const a=s.find(m.providerId,m.modelId);return a!==void 0&&(!t.reasoning_effort||a.reasoning)&&(t.seed===void 0&&t.response_format===void 0||a.api==="openai-completions")&&(t.stop===void 0||["openai-completions","anthropic-messages"].includes(a.api))},"matchesFeatures"),I=Y(c.kind==="exact"?{kind:"concrete",spec:c.spec}:c,r?.cwd,r?.profileId).filter(f);let w;if(c.kind==="logical")w=t.model_policy==="exact"?I.filter(m=>m.family===c.family&&m.strength===c.strength).slice(0,1):I;else{const m=ue(c.spec),a=I.filter(u=>u.providerId===m?.providerId&&u.modelId===m.modelId&&(t.model_policy!=="exact"||u.thinkingLevel===m.thinkingLevel)&&k(u));if(t.model_policy==="exact")w=a.slice(0,1);else if(a.length)w=a;else{const u=ee(r?.cwd,r?.profileId),q=[...Object.values(u.modelRoutes??{}).flatMap(i=>Object.entries(i.models)),...[u.modelLadders.anthropic,u.modelLadders.openai].flatMap(i=>Object.entries(i))].find(([,i])=>i===c.spec)?.[0],L=se(void 0,"substitute",r?.cwd,r?.profileId);w=q&&L.kind==="logical"?Y({...L,strength:q,spec:c.spec},r?.cwd,r?.profileId).filter(i=>f(i)&&i.strength===q):[]}}if(w=w.filter(k),w.length===0)throw t.model_policy==="exact"?new l(400,"substitution_refused","no eligible model route is configured"):new l(404,"model_unavailable","no eligible model route is configured");const G=`chatcmpl-${ce()}`;let j=1/0,y,U;for(const m of w){if(!me(m,s).authenticated)continue;const a=s.find(m.providerId,m.modelId);if(!a)continue;const u=m.providerId,S=E(u)?_e(u).filter(i=>!i.authFailure):[],q=S.filter(i=>te(i,a.id)<=Date.now());if(S.length&&!q.length){const i=Math.min(...S.map(A=>te(A,a.id)));i<j&&(j=i,U=`${a.provider}/${a.id}`);continue}const L=S.length?q:[void 0];for(const i of L){const A=Date.now();let Q=i?.access;if(i&&i.expires<=Date.now()&&E(u)){const o=s.getProvider(u)?.auth.oauth;if(!o)throw new l(503,"upstream_model_unavailable",`no OAuth refresh for ${u}`,void 0,{served_model:`${a.provider}/${a.id}`});Q=(await ve(u,i.label,async(g,X)=>{const P=await o.refresh({type:"oauth",refresh:g,access:"",expires:0,...X?{clientId:X}:{}},e??new AbortController().signal),z=P.clientId;return{refresh:P.refresh,access:P.access,expires:P.expires,...typeof z=="string"&&z?{clientId:z}:{}}})).access}const ie=$e(t,!!i),M=i?void 0:await s.getApiKeyAndHeaders(a);if(M&&!M.ok)continue;const v=`${a.provider}/${a.id}`,$=t.model??v;let x=!1,V,b=3e4,R;const J=[],T=[],O=[];let d;const N=p((o,h=null)=>({id:G,object:"chat.completion.chunk",created:Math.floor(A/1e3),model:v,served_model:v,requested_model:$,substituted:$!==v&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test($),choices:[{index:0,delta:o,finish_reason:h}]}),"chunk"),D=p(o=>{J.push(o),n?.(o)},"emit"),ae=fe(a,ie,{apiKey:Q??(M?.ok?M.apiKey:void 0),...M?.ok?{headers:M.headers}:{},signal:e,reasoning:t.reasoning_effort??m.thinkingLevel,...t.temperature!==void 0?{temperature:t.temperature}:{},...t.max_tokens!==void 0?{maxTokens:t.max_tokens}:{},...t.top_p!==void 0?{samplingParams:{top_p:t.top_p}}:{},...t.tool_choice==="none"?{toolChoice:"none"}:{},onPayload:p(o=>{if(!o||typeof o!="object")return;const h=t.tool_choice,g=h==="required"?a.api==="anthropic-messages"?{type:"any"}:"required":typeof h=="object"&&h!==null?a.api==="anthropic-messages"?{type:"tool",name:h.function.name}:h:void 0;return{...o,...g?{tool_choice:g}:{},...t.stop!==void 0?{[a.api==="anthropic-messages"?"stop_sequences":"stop"]:typeof t.stop=="string"?[t.stop]:t.stop}:{},...t.seed!==void 0?{seed:t.seed}:{},...t.response_format!==void 0?{response_format:t.response_format}:{}}},"onPayload"),fetch:p(async(o,h)=>{const g=await fetch(o,h);return V=g.status,g.status===429&&(x=!0,R=g.headers,b=Z(g.headers)??15e3),g},"fetch"),onResponse:p(o=>{o.status===429&&(x=!0,R=o.headers,b=Z(o.headers)??15e3)},"onResponse")});try{for await(const o of ae)o.type==="text_delta"&&(T.push(o.delta),D(N({content:o.delta}))),o.type==="thinking_delta"&&(O.push(o.delta),D(N({reasoning_content:o.delta}))),o.type==="done"&&(d=o.message),o.type==="error"&&(d=o.error)}catch(o){const h=ge(o);R===void 0&&o&&typeof o=="object"&&"headers"in o&&Object.keys(xe(o.headers)).length&&(R=o.headers);const g=ne({status:o&&typeof o=="object"&&"status"in o?o.status:void 0,message:o instanceof Error?o.message:String(o)});if(!x&&g.kind!=="rate-limit")throw J.length?new l(503,"upstream_model_unavailable",o instanceof Error?o.message:String(o),void 0,{served_model:v}):o;x=!0,b=h??b}if(!x&&d?.stopReason==="error"&&ne({message:d.errorMessage}).kind==="rate-limit"&&(x=!0,b=15e3),x){const o=we(R,a.id);if(Z(R)===void 0&&o.resetAt!==void 0&&(b=Math.max(b,o.resetAt-Date.now())),y={provider:u,model:a.id,...i?{account:i.label}:{},observation:o},console.error(`[llm] 429 from ${v}${i?` account "${i.label}"`:""}: scope=${oe(o.scope)} retry_after_ms=${b} headers=${JSON.stringify(o.headers)}`),i&&E(u)&&await he(u,i.label,b,A,Date.now(),o.scope),Date.now()+b<j&&(j=Date.now()+b,U=v),T.length||O.length)throw new l(503,"service_unavailable","vendor rate limit after output began",void 0,{served_model:v});continue}const F=ke(d);if(V===400&&t.tool_choice!==void 0&&t.tool_choice!=="auto"&&t.tool_choice!=="none"&&/\btool[_ ]choice\b/i.test(d?.errorMessage??"")&&/not supported|does not support|unsupported|cannot|must be auto/i.test(d?.errorMessage??""))throw new l(400,"invalid_request",d.errorMessage,"tool_choice",{served_model:v});if(!d||d.stopReason==="error"&&!F||d.stopReason==="aborted")throw new l(503,"upstream_model_unavailable",d?.errorMessage??"model request failed",void 0,{served_model:v});i&&E(u)&&be(i,a.id)&&await ye(u,i.label,A,a.id);const W=d.content.filter(o=>o.type==="toolCall").map(o=>({id:o.id,type:"function",function:{name:o.name,arguments:JSON.stringify(o.arguments)}})),H=F?"content_filter":d.stopReason==="toolUse"?"tool_calls":d.stopReason==="length"?"length":"stop",K=d.responseModel?Ie(a,d.responseModel):void 0,C=K?`${a.provider}/${K.model}`:v,B={prompt_tokens:d.usage.input,completion_tokens:d.usage.output,total_tokens:d.usage.totalTokens,cache_read_tokens:d.usage.cacheRead,cache_write_tokens:d.usage.cacheWrite,usd_equivalent:Re(K??a,d.usage.cost.total),served_model:C};_&&qe(_.grantee,"user",C,d.usage,B.usd_equivalent);const le={id:G,object:"chat.completion",created:Math.floor(A/1e3),model:C,served_model:C,requested_model:$,substituted:$!==C&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test($),choices:[{index:0,message:{role:"assistant",content:T.join("")||null,...O.length?{reasoning_content:O.join("")}:{},...W.length?{tool_calls:W}:{},...F?{refusal:d.errorMessage??"Content filtered"}:{}},finish_reason:H}],usage:B,...t.metadata!==void 0?{metadata:t.metadata}:{}};for(const[o,h]of W.entries())D(N({tool_calls:[{index:o,...h}]}));return D({...N({},H),...F?{choices:[{index:0,delta:{},finish_reason:H,message:{refusal:d.errorMessage??"Content filtered"}}]}:{},usage:B}),{completion:le,chunks:J}}}throw j!==1/0?new l(429,"all_routes_exhausted","all eligible model routes are rate-limited",void 0,{retry_after_s:Math.max(1,Math.ceil((j-Date.now())/1e3)),served_model:y?`${y.provider}/${y.model}`:U,...y?{rate_limit:{provider:y.provider,model:y.model,...y.account?{account:y.account}:{},scope:oe(y.observation.scope),...y.observation.claim?{claim:y.observation.claim}:{},headers:y.observation.headers}}:{}},void 0,!0):t.model_policy==="exact"?new l(400,"substitution_refused","the requested model is unavailable"):new l(404,"model_unavailable","no usable model route is available")}p(He,"runLlm");export{We as parseLlmRequest,He as runLlm};
3
+ `),p=t.tools?.map(({function:a})=>({name:a.name,description:a.description??"",parameters:a.parameters??{type:"object",properties:{}}}));return{...m?{systemPrompt:m}:{},messages:n.slice(y.length),...p?{tools:p}:{}}}u(Re,"contextOf");function se(t,e,n,r){if(!t){const m=ee(n,r),p=Object.values({...m.modelRoutes??{},...m.legacyModelRoutes??{}}),a=p.length?[m.modelLadders.defaultProvider,...m.modelRouting?.familyOrder??[],...p.map(w=>w.family)].find(w=>p.some(I=>I.family===w)):m.modelLadders.defaultProvider??"anthropic";return{kind:"logical",family:a,strength:"strong",spec:`${a}/strong`}}const y=/^([^/]+)\/(ultra|strong|medium|light)$/.exec(t);return y?{kind:"logical",family:y[1],strength:y[2],spec:t}:{kind:e==="exact"?"exact":"concrete",spec:t}}u(se,"requestFor");function $e(t,e){const{input:n,output:r,cacheRead:y,cacheWrite:m}=t.cost;return n===0&&r===0&&y===0&&m===0?null:Number.isFinite(e)&&e>=0?e:null}u($e,"usdEquivalent");function Ie(t,e){return t.compat?.allowedFallbackModels?.find(r=>r.provider===t.provider&&r.model===e)}u(Ie,"allowedFallback");async function He(t,e,n,r,y){if(t.payer==="app")throw new l(403,"payer_not_app","app-paid inference routes are not registered");je(y);const m=await pe(),p=se(t.model,t.model_policy,r?.cwd,r?.profileId),a=u(f=>f.routeId!=="concrete"&&f.routeId!=="exact","configured"),w=u(f=>{const s=m.find(f.providerId,f.modelId);return s!==void 0&&(!t.reasoning_effort||s.reasoning)&&(t.seed===void 0&&t.response_format===void 0||s.api==="openai-completions")&&(t.stop===void 0||["openai-completions","anthropic-messages"].includes(s.api))},"matchesFeatures"),I=Y(p.kind==="exact"?{kind:"concrete",spec:p.spec}:p,r?.cwd,r?.profileId).filter(a);let x;if(p.kind==="logical")x=t.model_policy==="exact"?I.filter(f=>f.family===p.family&&f.strength===p.strength).slice(0,1):I;else{const f=ue(p.spec),s=I.filter(c=>c.providerId===f?.providerId&&c.modelId===f.modelId&&(t.model_policy!=="exact"||c.thinkingLevel===f.thinkingLevel)&&w(c));if(t.model_policy==="exact")x=s.slice(0,1);else if(s.length)x=s;else{const c=ee(r?.cwd,r?.profileId),q=[...Object.values(c.modelRoutes??{}).flatMap(i=>Object.entries(i.models)),...[c.modelLadders.anthropic,c.modelLadders.openai].flatMap(i=>Object.entries(i))].find(([,i])=>i===p.spec)?.[0],L=se(void 0,"substitute",r?.cwd,r?.profileId);x=q&&L.kind==="logical"?Y({...L,strength:q,spec:p.spec},r?.cwd,r?.profileId).filter(i=>a(i)&&i.strength===q):[]}}if(x=x.filter(w),x.length===0)throw t.model_policy==="exact"?new l(400,"substitution_refused","no eligible model route is configured"):new l(404,"model_unavailable","no eligible model route is configured");const G=`chatcmpl-${ce()}`;let j=1/0,_,J;for(const f of x){if(!me(f,m).authenticated)continue;const s=m.find(f.providerId,f.modelId);if(!s)continue;const c=f.providerId,S=E(c)?ye(c).filter(i=>!i.authFailure):[],q=S.filter(i=>te(i,s.id)<=Date.now());if(S.length&&!q.length){const i=Math.min(...S.map(A=>te(A,s.id)));i<j&&(j=i,J=`${s.provider}/${s.id}`);continue}const L=S.length?q:[void 0];for(const i of L){const A=Date.now();let Q=i?.access;if(i&&i.expires<=Date.now()&&E(c)){const o=m.getProvider(c)?.auth.oauth;if(!o)throw new l(503,"upstream_model_unavailable",`no OAuth refresh for ${c}`,void 0,{served_model:`${s.provider}/${s.id}`});Q=(await ve(c,i.label,async(g,X)=>{const P=await o.refresh({type:"oauth",refresh:g,access:"",expires:0,...X?{clientId:X}:{}},e??new AbortController().signal),B=P.clientId;return{refresh:P.refresh,access:P.access,expires:P.expires,...typeof B=="string"&&B?{clientId:B}:{}}})).access}const ie=Re(t),M=i?void 0:await m.getApiKeyAndHeaders(s);if(M&&!M.ok)continue;const v=`${s.provider}/${s.id}`,R=t.model??v;let k=!1,V,b=3e4,$;const U=[],T=[],O=[];let d;const N=u((o,h=null)=>({id:G,object:"chat.completion.chunk",created:Math.floor(A/1e3),model:v,served_model:v,requested_model:R,substituted:R!==v&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test(R),choices:[{index:0,delta:o,finish_reason:h}]}),"chunk"),D=u(o=>{U.push(o),n?.(o)},"emit"),ae=fe(s,ie,{apiKey:Q??(M?.ok?M.apiKey:void 0),...M?.ok?{headers:M.headers}:{},signal:e,reasoning:t.reasoning_effort??f.thinkingLevel,...t.temperature!==void 0?{temperature:t.temperature}:{},...t.max_tokens!==void 0?{maxTokens:t.max_tokens}:{},...t.top_p!==void 0?{samplingParams:{top_p:t.top_p}}:{},...t.tool_choice==="none"?{toolChoice:"none"}:{},onPayload:u(o=>{if(!o||typeof o!="object")return;const h=t.tool_choice,g=h==="required"?s.api==="anthropic-messages"?{type:"any"}:"required":typeof h=="object"&&h!==null?s.api==="anthropic-messages"?{type:"tool",name:h.function.name}:h:void 0;return{...o,...g?{tool_choice:g}:{},...t.stop!==void 0?{[s.api==="anthropic-messages"?"stop_sequences":"stop"]:typeof t.stop=="string"?[t.stop]:t.stop}:{},...t.seed!==void 0?{seed:t.seed}:{},...t.response_format!==void 0?{response_format:t.response_format}:{}}},"onPayload"),fetch:u(async(o,h)=>{const g=await fetch(o,h);return V=g.status,g.status===429&&(k=!0,$=g.headers,b=Z(g.headers)??15e3),g},"fetch"),onResponse:u(o=>{o.status===429&&(k=!0,$=o.headers,b=Z(o.headers)??15e3)},"onResponse")});try{for await(const o of ae)o.type==="text_delta"&&(T.push(o.delta),D(N({content:o.delta}))),o.type==="thinking_delta"&&(O.push(o.delta),D(N({reasoning_content:o.delta}))),o.type==="done"&&(d=o.message),o.type==="error"&&(d=o.error)}catch(o){const h=ge(o);$===void 0&&o&&typeof o=="object"&&"headers"in o&&Object.keys(xe(o.headers)).length&&($=o.headers);const g=ne({status:o&&typeof o=="object"&&"status"in o?o.status:void 0,message:o instanceof Error?o.message:String(o)});if(!k&&g.kind!=="rate-limit")throw U.length?new l(503,"upstream_model_unavailable",o instanceof Error?o.message:String(o),void 0,{served_model:v}):o;k=!0,b=h??b}if(!k&&d?.stopReason==="error"&&ne({message:d.errorMessage}).kind==="rate-limit"&&(k=!0,b=15e3),k){const o=we($,s.id);if(Z($)===void 0&&o.resetAt!==void 0&&(b=Math.max(b,o.resetAt-Date.now())),_={provider:c,model:s.id,...i?{account:i.label}:{},observation:o},console.error(`[llm] 429 from ${v}${i?` account "${i.label}"`:""}: scope=${oe(o.scope)} retry_after_ms=${b} headers=${JSON.stringify(o.headers)}`),i&&E(c)&&await he(c,i.label,b,A,Date.now(),o.scope),Date.now()+b<j&&(j=Date.now()+b,J=v),T.length||O.length)throw new l(503,"service_unavailable","vendor rate limit after output began",void 0,{served_model:v});continue}const F=ke(d);if(V===400&&t.tool_choice!==void 0&&t.tool_choice!=="auto"&&t.tool_choice!=="none"&&/\btool[_ ]choice\b/i.test(d?.errorMessage??"")&&/not supported|does not support|unsupported|cannot|must be auto/i.test(d?.errorMessage??""))throw new l(400,"invalid_request",d.errorMessage,"tool_choice",{served_model:v});if(!d||d.stopReason==="error"&&!F||d.stopReason==="aborted")throw new l(503,"upstream_model_unavailable",d?.errorMessage??"model request failed",void 0,{served_model:v});i&&E(c)&&be(i,s.id)&&await _e(c,i.label,A,s.id);const W=d.content.filter(o=>o.type==="toolCall").map(o=>({id:o.id,type:"function",function:{name:o.name,arguments:JSON.stringify(o.arguments)}})),H=F?"content_filter":d.stopReason==="toolUse"?"tool_calls":d.stopReason==="length"?"length":"stop",K=d.responseModel?Ie(s,d.responseModel):void 0,C=K?`${s.provider}/${K.model}`:v,z={prompt_tokens:d.usage.input,completion_tokens:d.usage.output,total_tokens:d.usage.totalTokens,cache_read_tokens:d.usage.cacheRead,cache_write_tokens:d.usage.cacheWrite,usd_equivalent:$e(K??s,d.usage.cost.total),served_model:C};y&&qe(y.grantee,"user",C,d.usage,z.usd_equivalent);const le={id:G,object:"chat.completion",created:Math.floor(A/1e3),model:C,served_model:C,requested_model:R,substituted:R!==C&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test(R),choices:[{index:0,message:{role:"assistant",content:T.join("")||null,...O.length?{reasoning_content:O.join("")}:{},...W.length?{tool_calls:W}:{},...F?{refusal:d.errorMessage??"Content filtered"}:{}},finish_reason:H}],usage:z,...t.metadata!==void 0?{metadata:t.metadata}:{}};for(const[o,h]of W.entries())D(N({tool_calls:[{index:o,...h}]}));return D({...N({},H),...F?{choices:[{index:0,delta:{},finish_reason:H,message:{refusal:d.errorMessage??"Content filtered"}}]}:{},usage:z}),{completion:le,chunks:U}}}throw j!==1/0?new l(429,"all_routes_exhausted","all eligible model routes are rate-limited",void 0,{retry_after_s:Math.max(1,Math.ceil((j-Date.now())/1e3)),served_model:_?`${_.provider}/${_.model}`:J,..._?{rate_limit:{provider:_.provider,model:_.model,..._.account?{account:_.account}:{},scope:oe(_.observation.scope),..._.observation.claim?{claim:_.observation.claim}:{},headers:_.observation.headers}}:{}},void 0,!0):t.model_policy==="exact"?new l(400,"substitution_refused","the requested model is unavailable"):new l(404,"model_unavailable","no usable model route is available")}u(He,"runLlm");export{We as parseLlmRequest,He as runLlm};
@@ -10,12 +10,11 @@ export declare function connectViewSocket(nodeId: string): Socket;
10
10
  * `timeoutMs`. A view.sock that is a symlink rejects with the refusal, so no
11
11
  * wait reads a planted link as a broker that is still starting. */
12
12
  export declare function probeViewSocket(nodeId: string, timeoutMs: number): Promise<boolean>;
13
- /** Bounded retry for the revive leaf, which has no launch handle to observe. */
14
- export declare function waitForViewSocketReady(nodeId: string): Promise<boolean>;
15
13
  /** Wait for a launched broker's view.sock, failing fast when its launch handle
16
14
  * exits. `exited` remains optional for existing callers that only need the same
17
- * bounded readiness deadline without a handle. */
15
+ * bounded readiness deadline without a handle; `waitMs` shortens that deadline
16
+ * for a caller holding an HTTP request open. */
18
17
  export declare function waitForBrokerViewSocket(nodeId: string, exited?: Promise<{
19
18
  code: number | null;
20
19
  signal: NodeJS.Signals | null;
21
- }>): Promise<boolean>;
20
+ }>, waitMs?: number): Promise<boolean>;
@@ -1 +1 @@
1
- var m=Object.defineProperty;var a=(t,e)=>m(t,"name",{value:e,configurable:!0});import{createConnection as p,Socket as S}from"node:net";import{nodeRunDir as d,viewSocketPath as h}from"../canvas/paths.js";import{isBeneathRefusal as x,socketBeneath as _}from"../spaces/open-beneath.js";const u=3e4,l=100,T=u;function f(t){return new Promise(e=>{setTimeout(e,t)})}a(f,"sleep");function E(t){let e;try{e=_(d(t),h(t))}catch(i){const c=new S;return process.nextTick(()=>c.destroy(i)),c}const r=p(e.path);return r.once("connect",e.release),r.once("close",e.release),r}a(E,"connectViewSocket");function w(t,e){return new Promise((r,i)=>{let c=!1,n=null;const s=a(o=>{c||(c=!0,n!==null&&n.destroy(),o instanceof Error?i(o):r(o))},"finish");n=E(t),n.once("connect",()=>s(!0)),n.once("error",o=>s(x(o)?o:!1)),n.setTimeout(e,()=>s(!1))})}a(w,"probeViewSocket");async function B(t){const e=Date.now()+u;for(;Date.now()<=e;){if(await w(t,l))return!0;await f(l)}return!1}a(B,"waitForViewSocketReady");async function C(t,e){const r=Date.now()+u,i=e?.then(()=>"exit");for(;Date.now()<=r;){const c=Math.max(0,r-Date.now()),n=w(t,Math.min(l,c)).then(o=>o?"ready":"retry"),s=i===void 0?await n:await Promise.race([n,i]);if(s==="ready")return!0;if(s==="exit")return!1;if(i!==void 0){if(await Promise.race([f(l).then(()=>"sleep"),i])==="exit")return!1}else await f(l)}return!1}a(C,"waitForBrokerViewSocket");export{T as BROKER_VIEW_SOCKET_WAIT_MS,E as connectViewSocket,w as probeViewSocket,C as waitForBrokerViewSocket,B as waitForViewSocketReady};
1
+ var w=Object.defineProperty;var a=(t,e)=>w(t,"name",{value:e,configurable:!0});import{createConnection as p,Socket as S}from"node:net";import{nodeRunDir as _,viewSocketPath as h}from"../canvas/paths.js";import{isBeneathRefusal as x,socketBeneath as E}from"../spaces/open-beneath.js";const u=3e4,l=100,B=u;function m(t){return new Promise(e=>{setTimeout(e,t)})}a(m,"sleep");function d(t){let e;try{e=E(_(t),h(t))}catch(c){const n=new S;return process.nextTick(()=>n.destroy(c)),n}const i=p(e.path);return i.once("connect",e.release),i.once("close",e.release),i}a(d,"connectViewSocket");function O(t,e){return new Promise((i,c)=>{let n=!1,r=null;const s=a(o=>{n||(n=!0,r!==null&&r.destroy(),o instanceof Error?c(o):i(o))},"finish");r=d(t),r.once("connect",()=>s(!0)),r.once("error",o=>s(x(o)?o:!1)),r.setTimeout(e,()=>s(!1))})}a(O,"probeViewSocket");async function C(t,e,i=u){const c=Date.now()+i,n=e?.then(()=>"exit");for(;Date.now()<=c;){const r=Math.max(0,c-Date.now()),s=O(t,Math.min(l,r)).then(f=>f?"ready":"retry"),o=n===void 0?await s:await Promise.race([s,n]);if(o==="ready")return!0;if(o==="exit")return!1;if(n!==void 0){if(await Promise.race([m(l).then(()=>"sleep"),n])==="exit")return!1}else await m(l)}return!1}a(C,"waitForBrokerViewSocket");export{B as BROKER_VIEW_SOCKET_WAIT_MS,d as connectViewSocket,O as probeViewSocket,C as waitForBrokerViewSocket};
@@ -1 +1 @@
1
- var Se=Object.defineProperty;var a=(e,t)=>Se(e,"name",{value:t,configurable:!0});import{prepared as X}from"../../../core/sqlite-statements.js";import{envProfileId as Re}from"../../../shared/env.js";import{pausedCanvasProfiles as Oe}from"../../../core/profiles/manifest.js";import{existsSync as N,realpathSync as Z,statSync as q}from"node:fs";import{isAbsolute as F,resolve as T}from"node:path";import{appendSituationalContext as Ie,formatSituationalProse as $e}from"../../../core/runtime/situational-context.js";import{closeNode as Ce}from"../../../core/runtime/close.js";import{reviveNode as Ae}from"../../../core/runtime/revive.js";import{nowIso as G}from"../../../core/fs-utils.js";import{assertLaunchModelRegistered as B,forkNode as Ne,resolveForkSource as Te,spawnChildPrepared as Ee}from"../../../core/runtime/spawn.js";import{forkSourceNode as Pe,newNodeId as ee}from"../../../core/runtime/nodes.js";import{abandonCreateRequest as ne,childrenOf as De,completeCreateRequest as te,findCreateRequest as je,getNode as k,recordCreateRequestNode as Le,reserveCreateRequest as oe,reserveCreateRequestNode as Me,listNodes as re,recordRespawnAttempt as Fe,subscribersOf as Ge,subscriptionsOf as ie,setMessageWait as Be,updateNode as A}from"../../../core/canvas/canvas.js";import{subtreeIds as Ke}from"../../../core/canvas/nav-model.js";import{withCanvasWrite as K}from"../../../core/canvas/db.js";import{nodeObject as xe}from"../../../core/graph/objects.js";import{recordActedOn as We}from"../../../core/graph/edges.js";import{publish as Ve}from"../../../core/graph/events.js";import{readerFor as Je}from"../reader.js";import{relocateSessionCwd as ze,unbindSession as Ue}from"../../../core/canvas/sessions.js";import{ticketCountsForNodes as He}from"../../../core/canvas/attention.js";import{isSafeNodeId as Ye}from"../../../core/canvas/paths.js";import{contextDir as de,nodeAgentDir as x}from"../../../core/canvas/node-agent-paths.js";import{checkCronBirth as Qe}from"../../cron/authority.js";import{cronBirthPlacement as Xe}from"../../cron/armed-context.js";import{existsBeneath as Ze,isAbsentOrRefused as qe,isBeneathRefusal as en,readdirBeneath as nn}from"../../../core/spaces/open-beneath.js";import{readNodeMessagesPage as tn,readNodeSession as on,readNodeSnapshot as rn,transcriptMarkdown as dn}from"../../../core/runtime/node-read.js";import{assembleNodeSubject as sn}from"../../../core/substrate/subject.js";import{reviveAll as an}from"../../../core/runtime/revive-all.js";import{promoteAsync as ln,requestYieldAsync as cn,reshapeNodeAsync as W}from"../../../core/runtime/promote.js";import{recycleNode as fn}from"../../../core/runtime/recycle.js";import{relaunchRoot as un}from"../../../core/runtime/reset.js";import{assertReopenGate as pn}from"../../../core/runtime/reopen.js";import{writeYieldMessage as mn,readGoal as hn}from"../../../core/runtime/kickoff.js";import{ensureRoadmap as wn,hasRoadmap as yn}from"../../../core/runtime/roadmap.js";import{renameWindow as _n}from"../../../core/runtime/placement.js";import{buildLaunchSpecAsync as gn,explicitModelIntent as bn,normalizeModel as vn,readMergedLaunchConfigAsync as se}from"../../../core/runtime/launch.js";import{modelRequestFromConfig as kn}from"../../../core/model-routes.js";import{persistDormantModel as Sn,setModelLive as Rn}from"../../../core/runtime/model-swap.js";import{waitForBrokerViewSocket as On}from"../../../core/runtime/view-socket.js";import{BrokerUnreachableError as In}from"../../../core/runtime/broker-request.js";import{boundFleet as V}from"../../../core/runtime/fleet.js";import{admitLegacyStructuredOutput as $n}from"../../structured-requests/sidecar.js";import{assertOutputSchemaAccepted as Cn}from"../../../core/runtime/structured-output.js";import{OUTCOME_PAYLOAD_MAX_BYTES as ae}from"../../../core/canvas/types.js";import{HUMAN_ACTION_NAME_PATTERN as An}from"../../../core/human/action-binding.js";import{resolveHumanAction as Nn}from"../../../core/human-actions.js";import{parseDurationMs as Tn}from"../../../core/wake.js";import{claimWarmNode as En,refillWarmPool as le,resumeWarmClaim as Pn,warmPoolEnabled as Dn}from"../../../core/runtime/warm-pool.js";import{assertLaunchKind as J,isLaunchKind as jn,resolveLaunchTarget as Ln}from"../../../core/runtime/launch-target.js";import{fullName as Mn}from"../../../core/canvas/labels.js";import{reportsForNode as ce}from"./reports.js";import{requireUsableProfile as Fn}from"./profiles.js";import{assertProfileActive as Gn,loadExactProfileManifest as Bn}from"../../../core/profiles/manifest.js";import{getCron as fe}from"../../../core/canvas/crons.js";import{cronWakeOrigin as Kn}from"../../../core/runtime/bearings.js";import{boundDaemonControl as xn}from"../../control.js";import{notFound as E,usage as h}from"../../../core/errors.js";import{assertScope as Wn,validateScopes as ue}from"../../../core/scopes.js";import{scopeAllowed as O}from"../../../shared/env.js";import{toCloseResultDTO as Vn,toNodeDetailDTO as z,toNodeSummaryDTO as Jn,toReviveResultDTO as zn}from"../map.js";import{ApiError as m}from"../../../api/index.js";import{InputError as w}from"../../../core/io.js";import{brokerMayReach as U,canReadNode as pe,resolvedScopes as Un}from"../router.js";import{forwardVerb as Hn,queryWithoutMachine as Yn,relayError as Qn,unknownError as Xn,withMachine as Zn}from"../../peer/forward.js";import{fileRoot as qn}from"../../../core/spaces/file-root.js";import{activeGrant as et,terminalGrant as nt}from"../../../core/grants/mirror.js";import{readConfig as tt}from"../../../core/config.js";import{hasConfiguredProviderCredential as ot,NO_PROVIDER_CREDENTIAL_MESSAGE as me}from"../../../core/runtime/provider-credential-gate.js";function y(e){const t=k(e);if(t===null)throw E(`unknown node: ${e}`,{received:e});return t}a(y,"requireMeta");function H(e){return{subscribesTo:ie(e),subscribers:Ge(e),children:De(e)}}a(H,"edgesFor");function g(e){const t=y(e);return z(t,H(e))}a(g,"detailOf");function rt(e){let t=ce(e).length;try{t+=nn(x(e),de(e)).length}catch(n){if(!qe(n))throw n}return t}a(rt,"countFiles");function it(e){if(typeof e!="object"||e===null)throw h("a node create requires a JSON body");const t=e;if(t.kind!==void 0&&(typeof t.kind!="string"||t.kind.trim()===""))throw h("kind must be a non-empty string when provided");if(t.description!==void 0&&(typeof t.description!="string"||t.description.trim()===""))throw h("description must be a non-empty string when provided");if(t.creator!==void 0&&(typeof t.creator!="string"||t.creator.trim()===""))throw h("creator must be a non-empty node id when provided");if(t.scopes!==void 0&&ue(t.scopes),t.isolated!==void 0&&typeof t.isolated!="boolean")throw new m(400,"invalid_request","isolated must be a boolean",{param:"isolated"});const n=t.root_lifecycle;if(n!==void 0&&n!=="resident"&&n!=="terminal")throw h("root_lifecycle must be 'resident' or 'terminal'");if(t.request_key!==void 0&&(typeof t.request_key!="string"||t.request_key.trim()===""||t.request_key.length>128))throw h("request_key must be a non-empty string of at most 128 characters when provided");return t}a(it,"parseCreateBody");function dt(e){if(e===void 0)return;const t=Tn(e),n=t===null?null:new Date(Date.now()+t);if(t===null||!Number.isFinite(t)||t<=0||n===null||!Number.isFinite(n.getTime()))throw new m(422,"invalid_deadline","`deadline` must be a positive representable duration");return n.toISOString()}a(dt,"parseDeadline");function st(e){if(e!==void 0){if(typeof e!="object"||e===null||Array.isArray(e))throw new m(400,"invalid_request","`outcome_delivery` must be an object");if(typeof e.action!="string"||!An.test(e.action))throw new m(400,"invalid_request","`outcome_delivery.action` must be a declared action name");if(e.payload!==void 0){let t;try{t=JSON.stringify(e.payload)}catch{throw new m(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable")}if(t===void 0)throw new m(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable");if(Buffer.byteLength(t,"utf8")>ae)throw new m(413,"payload_too_large",`serialized payload exceeds ${ae} bytes`)}return e}}a(st,"parseOutcomeDelivery");function at(e,t){const n=Nn(e.action,t);if(!("kind"in n))return{actionName:e.action,argv:n.argv,cwd:n.cwd,configOrigin:n.configOrigin,...e.payload===void 0?{}:{payload:e.payload}};throw n.kind==="unknown_action"?new m(422,"unknown_action",`no action named ${e.action} is declared from ${t}`):new m(422,"invalid_action",`${e.action} is declared in ${n.configOrigin} but unusable: ${n.reason}`,{reason:n.reason,configOrigin:n.configOrigin})}a(at,"resolveOutcomeDelivery");function lt(e,t,n){const r={kind:e.kind,cwd:n,prompt:e.prompt??""};return e.mode!==void 0&&(r.mode=e.mode),e.name!==void 0&&(r.name=e.name),e.description!==void 0&&(r.description=e.description),t!==void 0&&(r.parent=t),e.creator!==void 0&&(r.creator=e.creator),e.scopes!==void 0&&(r.scopes=e.scopes),e.isolated!==void 0&&(r.isolated=e.isolated),e.root===!0&&(r.root=!0),e.root_lifecycle!==void 0&&(r.rootLifecycle=e.root_lifecycle),(e.worktree===!0||typeof e.worktree=="string")&&(r.worktree=!0),typeof e.worktree=="string"&&(r.worktreeBase=e.worktree),e.worktree!==void 0&&(r.worktreeCwd=T(e.pin_cwd??e.cwd??n)),e.fork_from!==void 0&&(r.forkFrom=e.fork_from),e.model!==void 0&&(r.model=e.model),e.profile!==void 0&&(r.profile=e.profile),e.situational_context!==void 0&&(r.situationalContext=e.situational_context),e.node_id!==void 0&&(r.nodeId=e.node_id),r}a(lt,"buildRecipe");function ct(e,t,n){return!Dn()||e.prefer_warm!==!0||e.root!==!0||e.parent!=null||e.root_lifecycle==="terminal"||(e.prompt??"")!==""||e.model!==void 0||e.fork_from!==void 0||e.worktree!==void 0||e.node_id!==void 0||e.scopes!==void 0||e.isolated!==void 0?null:{kind:e.kind,mode:e.mode??"base",cwd:t,profileId:n,model:e.model??null,situationalContext:e.situational_context??null,name:e.name??null,creator:e.creator??null}}a(ct,"warmRequestFor");import{resolveCreateKind as rr}from"../../../core/runtime/launch-target.js";function he(e){const t=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return kn(e.model,t,e.modelExact===!0)}a(he,"requestFromLaunch");function ft(e,t){if(t===null)return e;if(e.scopes===void 0)return{...e,scopes:[...t]};const n=e.scopes.filter(r=>!O(t,r));if(n.length===0)return e;throw new m(403,"scope_denied",`scopes exceed the bearer token's ceiling: ${n.join(", ")}`,{scopes:n,ceiling:t})}a(ft,"withinCeiling");async function we(e,t,n){const r=(await gn(t.kind,t.mode,{lifecycle:t.lifecycle,hasManager:e.parent!==null,model:t.model,modelOverride:!0,cwd:e.cwd,profileId:e.profile_id??null})).launch;await B(he(r),{cwd:e.cwd,profileId:e.profile_id??null},n)}a(we,"assertNodeModelChangeRegistered");function ye(e,t){const n=e.principal;if(n.class!=="broker"&&n.class!=="cron")return;const r=qn({app:n.app,resolved:Un(n),launched:n.scopes},t,"read");if("root"in r)return;const o="missing"in r?r.missing:null;throw new m(403,"scope_missing",o?`the child's directory ${t} needs missing scope ${o}`:`no permission of this run names the child's directory ${t}`,void 0,void 0,o?{scope:o}:void 0)}a(ye,"assertCallerReaches");const Y=new Map;async function ut(e,t={},n={}){const r=ft(it(e.body),e.ceiling);if(e.principal.class==="broker"){const c=e.principal;if(r.prefer_warm===!0&&c.app!=="app:terminal"&&(r.prefer_warm=!1),r.parent!=null){const i=y(r.parent);if(i.grantee!==c.app)throw new m(403,"other_app_run","cannot create a child under another app's node");if(!U(c,r.parent,i.grantee??"app:terminal"))throw E(`unknown node: ${r.parent}`,{received:r.parent})}if(r.creator!==void 0&&r.creator!==c.node)throw new m(403,"other_app_run","cannot create for another node");if(r.fork_from!==void 0){let i=null;try{i=Pe(Te(r.fork_from))}catch{}if(i?.grantee!==c.app||!U(c,i.node_id,i.grantee))throw new m(403,"other_app_run","cannot fork another app's conversation")}r.creator=c.node}if(e.principal.class==="cron"){const c=e.principal;for(const l of["parent","creator","fork_from","node_id"])if(r[l]!=null)throw new m(403,"permission_error",`a cron cannot set ${l} on a node it starts`);r.prefer_warm=!1;const i=Qe({grantee:c.app,sub:c.sub,grant_id:c.grant,scopes:c.scopes});if(!i.allowed)throw new m(i.reason==="scope_missing"?403:401,i.reason==="scope_missing"?"scope_missing":"grant_revoked",i.message);if(r.scopes!==void 0){const l=r.scopes.find(p=>!O(i.effectiveScopes,p)||!O(c.scopes,p));if(l!==void 0)throw new m(403,"scope_missing",`scope ${l} is not held by this cron`,void 0,void 0,{scope:l})}else c.scopes!==null&&(r.scopes=[...c.scopes]);r.node_id=ee(),c.isolated&&(r.isolated=!0,r.cwd=x(r.node_id,c.app))}r.creator!==void 0&&Wn(y(r.creator),"crtr:act");const o=r.request_key?.trim();if(o===void 0||r.node_id!==void 0&&Ye(r.node_id)&&k(r.node_id)!==null&&je(o)?.nodeId!==r.node_id)return P(e,r,t,n);const d=Y.get(o);if(d!==void 0)return d;const s=pt(e,r,o,t,n);Y.set(o,s);try{return await s}finally{Y.delete(o)}}a(ut,"handleCreate");async function pt(e,t,n,r,o){const d=oe(n);if(d.nodeId!==null&&k(d.nodeId)!==null){if(d.state!=="warm_claimed"){const s=k(d.nodeId);if(e.principal.class!=="person"&&!pe(e.principal,s.grantee??"app:terminal",d.nodeId))throw new m(409,"create_request_conflict","request_key belongs to another caller");return{status:201,body:g(d.nodeId)}}return P(e,t,r,o,d)}try{return await P(e,t,r,o,d)}catch(s){const c=oe(n);throw(c.nodeId===null||k(c.nodeId)===null)&&ne(n,c.nodeId),s}}a(pt,"handleKeyedCreateRequest");async function P(e,t,n,r,o=void 0){const d=r.deadlineAt??dt(t.deadline),s=st(t.outcome_delivery),c=e.principal.class==="cron"?Xe({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,cron_id:e.principal.cron,origin_run_id:e.principal.origin_run,isolated:e.principal.isolated,creator:fe(e.principal.cron)?.created_by??null},t.node_id):null;c!==null&&(t.root=c.root===!0);const i=c?.parent??t.parent??void 0;if(i==null&&t.root!==!0)throw h("a node create requires a parent (or root: true for an independent root)");let l=null;if(t.output_schema!==void 0){try{l=JSON.parse(t.output_schema)}catch{throw h("output_schema must be a JSON schema string")}if(l===null||typeof l!="object"||Array.isArray(l))throw h("output_schema must be a JSON schema object");await Cn(l)}const{profileId:p,kind:u,cwd:f,launch:_}=await Ln(t,{validateKind:ke});ye(e,f),t.worktree!==void 0&&ye(e,T(t.pin_cwd??t.cwd??f));const S={...t,kind:u},I=s===void 0?void 0:at(s,f);if(await B(he(_),{cwd:f,profileId:p},n.modelRegistry),!await ot(p))throw new m(412,"no_provider_credential",me,{error:"no_provider_credential",message:me,next:"The person running this runtime logs a provider in with `crtr sys setup`, or stores a provider key on the profile."});let j=null;{const $=o===void 0||o.nodeId===null||o.state==="warm_claimed"?ct(S,f,p):null,L=l===null&&r.beforeBrokerLaunch===void 0?void 0:v=>{l!==null&&$n(v,"terminal",l),r.beforeBrokerLaunch?.(v)};if(o?.state==="warm_claimed"&&o.nodeId!==null){const v=$===null?null:await Pn($,o.nodeId,L);if(v===null)throw ne(o.requestKey,o.nodeId),new m(503,"create_resume_failed",`could not resume warm claim ${o.nodeId}; retry the create`);return te(o.requestKey,v),{status:201,body:g(v)}}if($!==null){const v=await En($,{...d===void 0?{}:{deadlineAt:d},...I===void 0?{}:{outcomeDelivery:I},...o===void 0?{}:{requestKey:o.requestKey}},L);if(v!==null)return o!==void 0&&te(o.requestKey,v),le($),{status:201,body:g(v)};j=$}const M=o?.nodeId??t.node_id??(o===void 0?void 0:ee());o!==void 0&&o.nodeId===null&&Me(o.requestKey,M);const R=lt(M===void 0?S:{...S,node_id:M},i,f);c!==null&&(c.rootLifecycle!==void 0&&(R.rootLifecycle=c.rootLifecycle),c.scopes!==void 0&&S.scopes===void 0&&(R.scopes=c.scopes),c.run!==void 0&&(R.run=c.run),c.withinCreate!==void 0&&(R.withinCreate=c.withinCreate)),d!==void 0&&(R.deadlineAt=d),I!==void 0&&(R.outcomeDelivery=I),R.readiness=t.root===!0?"await":"launch";const Q=await Ee(R,L,()=>{j!==null&&le(j)});return o!==void 0&&Le(o.requestKey,Q.node.node_id),{status:201,body:g(Q.node.node_id)}}}a(P,"handleCreateRequest");function b(e,t){const n=e.query.getAll(t);if(n.length>1)throw h(`${t} must appear at most once`);return n[0]}a(b,"singleQueryValue");function _e(e,t){if(e!==void 0){if(e==="1"||e==="true")return!0;if(e==="0"||e==="false")return!1;throw h(`${t} must be true or false`)}}a(_e,"optionalBoolean");function D(e,t,n){if(e!==void 0){if(n.includes(e))return e;throw h(`${t} must be one of: ${n.join(", ")}`)}}a(D,"oneOf");function mt(e){const t=new Set(["status","lifecycle","kind","mode","profile_id","profile_prefix","parent","top_level","under","hanging","include"]);for(const[_]of e.query)if(!t.has(_))throw h(`unknown nodes query parameter: ${_}`);const n=D(b(e,"status"),"status",["active","idle","done","dead","canceled"]),r=D(b(e,"lifecycle"),"lifecycle",["terminal","resident"]),o=b(e,"kind"),d=D(b(e,"mode"),"mode",["base","orchestrator"]),s=b(e,"profile_id"),c=b(e,"profile_prefix"),i=b(e,"parent"),l=_e(b(e,"top_level"),"top_level"),p=b(e,"under"),u=_e(b(e,"hanging"),"hanging"),f=D(b(e,"include"),"include",["activity"]);if(i!==void 0&&l===!0)throw h("parent and top_level=true cannot be combined");return{...n!==void 0?{status:n}:{},...r!==void 0?{lifecycle:r}:{},...o!==void 0?{kind:o}:{},...d!==void 0?{mode:d}:{},...s!==void 0?{profile_id:s}:{},...c!==void 0?{profile_prefix:c}:{},...i!==void 0?{parent:i}:{},...l!==void 0?{top_level:l}:{},...p!==void 0?{under:p}:{},...u!==void 0?{hanging:u}:{},...f!==void 0?{include:f}:{}}}a(mt,"parseListQuery");function ht(e){const t=mt(e);let n=t.status!==void 0?re({status:t.status}):re();if(e.principal.class!=="person"&&(n=n.filter(s=>pe(e.principal,s.grantee??"app:terminal",s.node_id))),t.lifecycle!==void 0&&(n=n.filter(s=>s.lifecycle===t.lifecycle)),t.kind!==void 0&&(n=n.filter(s=>s.kind===t.kind)),t.mode!==void 0&&(n=n.filter(s=>s.mode===t.mode)),t.profile_id!==void 0&&(n=n.filter(s=>s.profile_id===t.profile_id)),t.profile_prefix!==void 0&&(n=n.filter(s=>s.profile_id?.startsWith(t.profile_prefix))),t.parent!==void 0&&(n=n.filter(s=>s.parent===t.parent)),t.top_level===!0&&(n=n.filter(s=>s.parent===null)),t.under!==void 0){const s=new Set(Ke(t.under));n=n.filter(c=>s.has(c.node_id))}t.hanging===!0&&(n=n.filter(s=>s.parent!==null&&k(s.parent)===null));const r=t.include==="activity"?He(n.map(s=>s.node_id)):null,o=n.length>0?Oe():new Set;return{status:200,body:n.map(s=>{const c=Jn(s,s.profile_id!==null&&o.has(s.profile_id));if(r!==null){const i=ce(s.node_id);c.activity={latest_report:i[0]??null,final_report:s.final_report===null?null:i.find(l=>l.name===s.final_report)??null,pending_human_count:r[s.node_id]??0}}return c})}}a(ht,"handleList");async function wt(e){const t=e.params.id,n=await Hn(e,t,{method:"GET",path:`/v1/nodes/${encodeURIComponent(t)}`,query:Yn(e)});if(n.kind==="unknown")throw Xn(t,n.unreachable);if(n.kind==="remote"){if(n.status>=400)throw Qn(n.status,n.body);return{status:n.status,body:Zn(n.body,n.machine)}}return y(t),{status:200,body:g(t)}}a(wt,"handleDetail");async function yt(e){const t=e.params.id;y(t);const n=await rn(t);return{status:200,body:{node_id:t,snapshot:{messages:n.snapshot.messages,...n.snapshot.messageIds===void 0?{}:{messageIds:n.snapshot.messageIds},messageVisibility:n.snapshot.messageVisibility,turnVisibility:n.snapshot.turnVisibility,stats:n.snapshot.stats,state:n.snapshot.state,display:n.snapshot.display,...n.snapshot.queued===void 0?{}:{queued:n.snapshot.queued},...n.snapshot.toolGroupSummaries===void 0?{}:{toolGroupSummaries:n.snapshot.toolGroupSummaries},...n.snapshot.turnReplies===void 0?{}:{turnReplies:n.snapshot.turnReplies},...n.snapshot.workingActivity===void 0?{}:{workingActivity:n.snapshot.workingActivity}},commands:n.commands.map(o=>({name:o.name,description:o.description,source:o.source})),captured_at:G()}}}a(yt,"handleSnapshot");async function _t(e){const t=e.params.id,n=sn(t);if(n===null)throw E(`unknown node: ${t}`,{received:t});return{status:200,body:n}}a(_t,"handleSubject");function gt(e){for(const[d]of e.query)if(d!=="cursor"&&d!=="limit")throw h(`unknown messages query parameter: ${d}`);const t=e.query.getAll("cursor"),n=e.query.getAll("limit");if(t.length>1)throw h("cursor must appear at most once");if(n.length>1)throw h("limit must appear at most once");if(n.length===0)return t.length===0?{}:{cursor:t[0]};const r=n[0],o=Number(r);if(!Number.isSafeInteger(o)||r==="")throw h("limit must be an integer from 1 through 500");return t.length===0?{limit:o}:{cursor:t[0],limit:o}}a(gt,"parseMessagesQuery");async function bt(e){const t=e.params.id,n=await tn(t,gt(e));return{status:200,body:{node_id:t,messages:n.messages,...n.messageIds===void 0?{}:{message_ids:n.messageIds},message_visibility:n.messageVisibility,next_cursor:n.nextCursor,captured_at:G()}}}a(bt,"handleMessages");function vt(e){const t=e.params.id;y(t);const n=on(t);return{status:200,body:{node_id:t,session_file:n.sessionFile,session_jsonl:n.sessionJsonl,system_prompt:n.systemPrompt,captured_at:G()}}}a(vt,"handleSession");async function kt(e){const t=e.params.id;return y(t),{status:200,body:{node_id:t,markdown:await dn(t)}}}a(kt,"handleTranscript");function St(e){const t=e.params.id;y(t);const n=a((o,d)=>({node_id:o,label:k(o)?.name??o,dir:de(o),self:d,files:rt(o)}),"root"),r=[n(t,!0),...ie(t).map(o=>n(o.node_id,!1))].filter(o=>Rt(o.node_id,o.dir)||o.files>0);return{status:200,body:{node_id:t,roots:r}}}a(St,"handleContext");function Rt(e,t){try{return Ze(x(e),t)}catch(n){if(en(n))return!1;throw n}}a(Rt,"hasDir");async function Ot(e){const t=y(e.params.id),n=e.principal.class==="broker"?e.principal.scopes:e.ceiling,r=n===null?void 0:t.scopes==null?[...n]:[...new Set([...t.scopes.filter(d=>O(n,d)),...n.filter(d=>O(t.scopes,d))])],o=await Ne(t.node_id,r,e.principal.class==="broker"?e.principal.node:void 0);return{status:201,body:g(o.node.node_id)}}a(Ot,"handleFork");async function It(e){const t=e.params.id;y(t);const n=e.body,r=n?.resume??!0,o=n?.reopen??!1;pn(t,o);const d=!r&&n?.cron_id!==void 0?fe(n.cron_id):null,s=await Ae(t,{resume:r,capacity:"refuse",...d!==null?{wakeReason:Kn(d,"schedule_revive")}:{}});s.outcome!=="already-live"&&Fe(t,{failures:0,notBefore:null});const c=k(t)?.status??"active";return{status:200,body:zn(t,s,c)}}a(It,"handleRevive");async function $t(e){const t=e.params.id;return{status:200,body:{newNodeId:(await un(t))?.newNodeId??null}}}a($t,"handleRelaunchRoot");function Ct(e){const t=e.params.id;y(t);const n=e.body;if(n?.caller_pid!==void 0&&(!Number.isInteger(n.caller_pid)||n.caller_pid<=0))throw h("caller_pid must be a positive integer when provided");const r=xn(),o=Ce(t,{rootEvent:n?.finish===!0?"finish":"cancel",callerPid:n?.caller_pid,...e.principal.class==="broker"?{closer:e.principal.node}:{},...r===null?{}:{registerDetached:r.registerDetached}});return{status:200,body:Vn(o)}}a(Ct,"handleClose");async function At(e){const t=e.params.id;y(t);const n=await fn(t);return{status:200,body:{recycled:n.recycled,finalized:n.finalized,new_root:n.newRoot,delivered:n.delivered}}}a(At,"handleRecycle");async function Nt(e){const t=e.params.id;return y(t),await W(t,{lifecycle:"terminal"}),C(e,t,["lifecycle"]),{status:200,body:g(t)}}a(Nt,"handleDemote");async function Tt(e,t={}){const n=e.params.id,r=y(n),o=e.body??{};o.kind!==void 0&&await J(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await we(r,{kind:o.kind??r.kind,mode:"orchestrator",lifecycle:o.resident===!0?"resident":r.lifecycle,model:o.model},t.modelRegistry);const d=await ln(n,{...o.kind!==void 0?{kind:o.kind}:{},...o.resident===!0?{resident:!0}:{},...o.model!==void 0?{model:o.model}:{}});C(e,n,["mode",...o.kind!==void 0?["kind"]:[],...o.resident===!0?["lifecycle"]:[],...o.model!==void 0?["model"]:[]]);const s=z(d.meta,H(n));return s.roadmap_written=d.roadmapWritten,s.roadmap_ref=d.roadmapRef,s.goal_path=d.goalPath,{status:200,body:s}}a(Tt,"handlePromote");async function Et(e,t={}){const n=e.params.id,r=y(n),o=e.body??{};o.kind!==void 0&&await J(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await we(r,{kind:o.kind??r.kind,mode:o.promote===!0?"orchestrator":r.mode,lifecycle:r.lifecycle,model:o.model},t.modelRegistry);const d=o.note??"Refresh: continue against your roadmap or goal.";mn(n,d);const s=await cn(n,{...o.promote===!0?{promote:!0}:{},...o.kind!==void 0?{kind:o.kind}:{},...o.model!==void 0?{model:o.model}:{}});return{status:200,body:z(s.meta,H(n))}}a(Et,"handleYield");function Pt(e){const t=e.params.id;y(t);const n=e.body;if(n===void 0||typeof n.controller!="string"||n.controller.trim()==="")throw h("a wait requires a controller node id");const r=k(n.controller);if(r===null)throw E(`unknown controller: ${n.controller}`,{received:n.controller});if(r.status!=="active"&&r.status!=="idle")throw new m(422,"unprocessable",`controller ${n.controller} is ${r.status}`);return Be(t,n.controller),{status:200,body:g(t)}}a(Pt,"handleWait");async function Dt(e){const t=e.principal,n=await an(t.class==="broker"?o=>o.grantee===t.app&&U(t,o.node_id,o.grantee):void 0);return{status:200,body:{revived:[...n.revived],failed:[...n.failed],frozen:[...n.frozen]}}}a(Dt,"handleReviveAll");const jt=new Set(["interrupt","steer","after-turn","next-turn"]);function Lt(e){if(e===null)return null;const t=a(r=>{throw new w({error:"invalid_listen",message:r,received:e,field:"listen",next:'Send an object mapping levels "1".."10" to interrupt|steer|after-turn|next-turn (e.g. {"3":"next-turn"}), or null to reset to the global default.'})},"bad");if(typeof e!="object"||Array.isArray(e))return t("listen must be an object or null");const n=Object.entries(e);if(n.length===0)return t("listen must name at least one level");for(const[r,o]of n)/^(?:[1-9]|10)$/.test(r)||t(`listen level must be 1..10: ${r}`),(typeof o!="string"||!jt.has(o))&&t(`listen delivery must be interrupt|steer|after-turn|next-turn: ${String(o)}`);return JSON.stringify(Object.fromEntries(n))}a(Lt,"listenJson");function C(e,t,n,r){n.length===0&&r===void 0||K(o=>{if(r?.(o),n.length===0)return;const d=xe(o,t),s=Je(o,e),c=s.class==="person"?s.person:s.class==="app"?s.app:s.node;We(o,c,d),Ve(o,{event:"configured",object:d,actor:c,attrs:{changed:n.join(",")}})})}a(C,"recordConfigured");function ge(e){const t=V().get(e);if(t===void 0)return!1;try{return process.kill(t.pid,"SIGTERM"),!0}catch{return!1}}a(ge,"restartLiveBroker");function be(e,t){try{return Z(e)===Z(t)}catch{return!1}}a(be,"sameDirectory");async function Mt(e,t={}){const n=e.params.id,r=e.body;let o;if(r===void 0)o={};else if(typeof r=="object"&&r!==null&&!Array.isArray(r))o=r;else throw new w({error:"cwd_invalid",message:"cwd repair requires a non-null object with a non-empty cwd string",received:r,field:"cwd",next:"Send a config patch with a non-empty absolute cwd string."});if(o.profile!==void 0){if(typeof o.profile!="string"||o.profile==="")throw new w({error:"profile_invalid",message:"profile must be a non-empty profile id or name",received:o.profile,field:"profile",next:"Send a profile id or exact name."});if(Object.keys(o).some(u=>u!=="profile"&&u!=="restart"&&o[u]!==void 0))throw new w({error:"profile_not_exclusive",message:"profile can only be combined with restart",field:"profile",next:"Send profile (and optionally restart) alone."});const i=y(n),l=Fn(e,o.profile);Gn(l.profileId);const p=Bn(l.profileId).manifest.grantee??"app:terminal";if(p!==(i.grantee??"app:terminal"))throw new w({error:"profile_grantee_mismatch",message:`profile ${l.profileId} belongs to ${p}, node ${n} to ${i.grantee}`,received:o.profile,field:"profile",next:"Pick a profile owned by the same app as the node."});return(i.profile_id??null)!==l.profileId&&(jn(i.kind)?(await J(i.kind,i.cwd,l.profileId),await W(n,{profileId:l.profileId})):A(n,{profile_id:l.profileId}),C(e,n,["profile"])),o.restart===!0&&ge(n),{status:200,body:g(n)}}if(Object.prototype.hasOwnProperty.call(o,"cwd")){const i=o.cwd;if(typeof i!="string"||i==="")throw new w({error:"cwd_invalid",message:"cwd must be a non-empty string",received:i,field:"cwd",next:"Send a non-empty absolute cwd string."});if(Object.keys(o).some(f=>f!=="cwd"&&f!=="keep_session"&&f!=="restart"&&o[f]!==void 0))throw new w({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config field",received:o,field:"cwd",next:"Send cwd as the only config field."});const l=y(n),p=N(l.cwd);if(o.keep_session===!0||p&&F(i)&&N(i)&&be(l.cwd,i)){if(!F(i))throw new w({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const f=T(i);let _=!1;try{_=q(f).isDirectory()}catch{}if(!_)throw new w({error:"cwd_missing",message:`cwd is not an existing directory: ${f}`,received:i,field:"cwd",next:"Send an existing absolute directory."});if(p&&!be(l.cwd,f))throw new w({error:"cwd_not_same_directory",message:`${f} is not the same directory as the recorded ${l.cwd}; --keep-session only relocates an alias`,received:i,field:"cwd",next:"Drop --keep-session to repair a gone cwd, or pass the real path of the recorded directory."});if(l.managed_worktree?.state==="open")throw new w({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot relocate its cwd`,received:i,field:"cwd",next:"Reconcile the managed worktree first."});const S=l.cwd;return S!==f&&(K(I=>{A(n,{cwd:f}),X(I,"UPDATE crons SET cwd = ? WHERE anchor_node = ? AND cwd = ?").run(f,n,S),ze(n,S,f)}),C(e,n,["cwd"])),o.restart===!0&&ge(n),{status:200,body:g(n)}}if(V().has(n)){const f=`${n} cannot repair its cwd while its broker is live`;throw new m(409,"cwd_node_live",f,{error:"cwd_node_live",message:f,received:i,field:"cwd",next:"Let the node become dormant, then retry the cwd repair."})}if(l.fork_from!=null)throw new w({error:"cwd_node_forked",message:`${n} is a fork and cannot repair its cwd; recorded cwd: ${l.cwd}`,received:i,field:"cwd",next:`Restore ${l.cwd}, then revive the fork normally.`});if(l.managed_worktree?.state==="open")throw new w({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot repair its cwd`,received:i,field:"cwd",next:"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon flow."});if(N(l.cwd))throw new w({error:"cwd_still_present",message:`${n} recorded cwd still exists: ${l.cwd}; cwd repair is not a relocation command`,received:i,field:"cwd",next:"This command only repairs a node whose recorded cwd is gone."});if(!F(i))throw new w({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const u=T(i);if(!N(u))throw new w({error:"cwd_missing",message:`cwd does not exist: ${u}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."});try{if(!q(u).isDirectory())throw new w({error:"cwd_not_a_directory",message:`cwd is not a directory: ${u}`,received:i,field:"cwd",next:"Send an existing absolute directory."})}catch(f){throw f instanceof w?f:new w({error:"cwd_missing",message:`cwd does not exist: ${u}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."})}return K(()=>{A(n,{cwd:u}),Ue(n)}),C(e,n,["cwd"]),{status:200,body:g(n)}}let d=y(n);const s=o.listen===void 0?void 0:Lt(o.listen);if(o.scopes!==void 0){const i=ue(o.scopes),l=d.grantee==="app:terminal"?nt():d.grant_id?et(d.grant_id):null,p=d.grantee==="app:terminal"&&!tt("user").runtime?.issuer;if(!i.every(u=>O(d.scopes,u)&&(l?O(l.resolved,u):p)))throw new w({error:"unknown_scope",message:"scopes may only narrow the node's current effective set",received:i,field:"scopes",next:"Send only scopes the node currently holds."});if(i.includes("crtr:act")&&!O(i,"crtr:llm"))throw new m(400,"invalid_scope","crtr:act requires crtr:llm");d=A(n,{scopes:i})}if(o.situational_context!==void 0&&Ie(n,$e(o.situational_context)),o.model!==void 0){const i=(await se(d.cwd,d.profile_id??null)).modelLadders,l=bn(o.model,i),p=vn(o.model,i);await B({kind:"concrete",spec:p},{cwd:d.cwd,profileId:d.profile_id??null},t.modelRegistry);const u=V();let f=!1;if(u.isClaimed(n)&&await On(n,u.get(n)?.handle.exited)&&(f=await Rn(n,p,l).then(()=>!0,_=>{if(_ instanceof In)return!1;throw _})),!f){if(u.isClaimed(n)){const _=`${n}'s broker did not accept the model switch; the model was not changed`;throw new m(409,"broker_not_ready",_,{error:"broker_not_ready",message:_,received:o.model,field:"model",next:"Check the node with `crtr node show`, then retry the model change."})}if(!p.includes("/"))throw h(`model must resolve to a provider/id spec: ${o.model}`);Sn(n,p,l)}d=y(n)}if(o.kind!==void 0||o.mode!==void 0||o.lifecycle!==void 0){o.kind!==void 0&&await ke(o.kind);const i=o.kind??d.kind,l=o.mode??d.mode,p=o.lifecycle??d.lifecycle;let u;l==="orchestrator"&&!yn(n)&&(u=a(()=>{const f=hn(n);wn(n,f!==null&&f.trim()!==""?{goal:f.trim()}:{})},"beforePersist")),d=await W(n,{...o.kind!==void 0?{kind:i}:{},...o.mode!==void 0?{mode:l}:{},...o.lifecycle!==void 0?{lifecycle:p}:{}},u)}o.name!==void 0&&(d=A(n,{name:o.name}),d.window&&_n(d.window,Mn(d)));const c=["scopes","situational_context","model","kind","mode","lifecycle","name","listen"].filter(i=>o[i]!==void 0);return C(e,n,c,s===void 0?void 0:i=>{X(i,"UPDATE objects SET listen_json = ? WHERE id = ? AND type = 'node'").run(s,n)}),{status:200,body:g(n)}}a(Mt,"handleConfig");async function ve(){return(await se(process.cwd(),Re()||null)).kinds}a(ve,"daemonKinds");async function ke(e){const t=Object.keys(await ve());if(!t.includes(e))throw h(`unknown kind: ${e}`,{received:e,next:`Valid kinds: ${t.sort().join(", ")}.`})}a(ke,"assertKindServer");async function Ft(){const e=await ve();return{status:200,body:{kinds:Object.keys(e).sort().map(n=>({kind:n,when_to_use:e[n]?.whenToUse??null,available_to:e[n]?.availableTo??null}))}}}a(Ft,"handleListKinds");const nr=[{method:"POST",pattern:"/v1/nodes",scope:"crtr:act",handler:ut},{method:"GET",pattern:"/v1/nodes",handler:ht},{method:"GET",pattern:"/v1/kinds",handler:a(()=>Ft(),"handler")},{method:"POST",pattern:"/v1/nodes/revive-all",scope:"crtr:act",handler:Dt},{method:"GET",pattern:"/v1/nodes/:id",handler:wt},{method:"GET",pattern:"/v1/nodes/:id/snapshot",handler:yt},{method:"GET",pattern:"/v1/nodes/:id/subject",handler:_t},{method:"GET",pattern:"/v1/nodes/:id/messages",handler:bt},{method:"GET",pattern:"/v1/nodes/:id/session",handler:vt},{method:"GET",pattern:"/v1/nodes/:id/transcript",handler:kt},{method:"GET",pattern:"/v1/nodes/:id/context",handler:St},{method:"POST",pattern:"/v1/nodes/:id/fork",scope:"crtr:act",handler:Ot},{method:"POST",pattern:"/v1/nodes/:id/revive",scope:"crtr:act",handler:It},{method:"POST",pattern:"/v1/nodes/:id/relaunch-root",scope:"crtr:act",handler:$t},{method:"POST",pattern:"/v1/nodes/:id/close",handler:Ct},{method:"POST",pattern:"/v1/nodes/:id/recycle",scope:"crtr:act",handler:At},{method:"POST",pattern:"/v1/nodes/:id/demote",scope:"crtr:act",handler:Nt},{method:"POST",pattern:"/v1/nodes/:id/promote",scope:"crtr:act",handler:Tt},{method:"POST",pattern:"/v1/nodes/:id/yield",scope:"crtr:act",handler:Et},{method:"POST",pattern:"/v1/nodes/:id/wait",handler:Pt},{method:"PATCH",pattern:"/v1/nodes/:id/config",handler:Mt}];export{Mt as handleConfig,ut as handleCreate,Ot as handleFork,ht as handleList,Tt as handlePromote,Et as handleYield,nr as nodeRoutes,rr as resolveCreateKind};
1
+ var Re=Object.defineProperty;var a=(e,t)=>Re(e,"name",{value:t,configurable:!0});import{prepared as X}from"../../../core/sqlite-statements.js";import{envProfileId as Oe}from"../../../shared/env.js";import{pausedCanvasProfiles as Ie}from"../../../core/profiles/manifest.js";import{existsSync as N,realpathSync as Z,statSync as q}from"node:fs";import{isAbsolute as G,resolve as T}from"node:path";import{appendSituationalContext as $e,formatSituationalProse as Ae}from"../../../core/runtime/situational-context.js";import{closeNode as Ce}from"../../../core/runtime/close.js";import{reviveNode as Ne}from"../../../core/runtime/revive.js";import{nowIso as B}from"../../../core/fs-utils.js";import{assertLaunchModelRegistered as x,forkNode as Te,resolveForkSource as Ee,spawnChildPrepared as Pe}from"../../../core/runtime/spawn.js";import{forkSourceNode as De,newNodeId as ee}from"../../../core/runtime/nodes.js";import{abandonCreateRequest as ne,childrenOf as je,completeCreateRequest as te,findCreateRequest as Le,getNode as k,recordCreateRequestNode as Me,reserveCreateRequest as oe,reserveCreateRequestNode as Fe,listNodes as re,recordRespawnAttempt as Ge,subscribersOf as Be,subscriptionsOf as ie,setMessageWait as xe,updateNode as C}from"../../../core/canvas/canvas.js";import{subtreeIds as Ke}from"../../../core/canvas/nav-model.js";import{withCanvasWrite as K}from"../../../core/canvas/db.js";import{nodeObject as We}from"../../../core/graph/objects.js";import{recordActedOn as Ve}from"../../../core/graph/edges.js";import{publish as ze}from"../../../core/graph/events.js";import{readerFor as Je}from"../reader.js";import{relocateSessionCwd as Ue,unbindSession as Ye}from"../../../core/canvas/sessions.js";import{ticketCountsForNodes as He}from"../../../core/canvas/attention.js";import{isSafeNodeId as Qe}from"../../../core/canvas/paths.js";import{contextDir as de,nodeAgentDir as W}from"../../../core/canvas/node-agent-paths.js";import{checkCronBirth as Xe}from"../../cron/authority.js";import{cronBirthPlacement as Ze}from"../../cron/armed-context.js";import{existsBeneath as qe,isAbsentOrRefused as en,isBeneathRefusal as nn,readdirBeneath as tn}from"../../../core/spaces/open-beneath.js";import{readNodeMessagesPage as on,readNodeSession as rn,readNodeSnapshot as dn,transcriptMarkdown as sn}from"../../../core/runtime/node-read.js";import{assembleNodeSubject as an}from"../../../core/substrate/subject.js";import{reviveAll as ln}from"../../../core/runtime/revive-all.js";import{promoteAsync as cn,requestYieldAsync as fn,reshapeNodeAsync as V}from"../../../core/runtime/promote.js";import{recycleNode as un}from"../../../core/runtime/recycle.js";import{relaunchRoot as pn}from"../../../core/runtime/reset.js";import{assertReopenGate as mn}from"../../../core/runtime/reopen.js";import{writeYieldMessage as hn,readGoal as wn}from"../../../core/runtime/kickoff.js";import{ensureRoadmap as yn,hasRoadmap as _n}from"../../../core/runtime/roadmap.js";import{renameWindow as gn}from"../../../core/runtime/placement.js";import{buildLaunchSpecAsync as bn,explicitModelIntent as vn,normalizeModel as kn,readMergedLaunchConfigAsync as se}from"../../../core/runtime/launch.js";import{modelRequestFromConfig as Sn}from"../../../core/model-routes.js";import{persistDormantModel as Rn,setModelLive as On}from"../../../core/runtime/model-swap.js";import{waitForBrokerViewSocket as ae}from"../../../core/runtime/view-socket.js";import{BrokerUnreachableError as In}from"../../../core/runtime/broker-request.js";import{boundFleet as E}from"../../../core/runtime/fleet.js";import{admitLegacyStructuredOutput as $n}from"../../structured-requests/sidecar.js";import{assertOutputSchemaAccepted as An}from"../../../core/runtime/structured-output.js";import{OUTCOME_PAYLOAD_MAX_BYTES as le}from"../../../core/canvas/types.js";import{HUMAN_ACTION_NAME_PATTERN as Cn}from"../../../core/human/action-binding.js";import{resolveHumanAction as Nn}from"../../../core/human-actions.js";import{parseDurationMs as Tn}from"../../../core/wake.js";import{claimWarmNode as En,refillWarmPool as ce,resumeWarmClaim as Pn,warmPoolEnabled as Dn}from"../../../core/runtime/warm-pool.js";import{assertLaunchKind as z,isLaunchKind as jn,resolveLaunchTarget as Ln}from"../../../core/runtime/launch-target.js";import{fullName as Mn}from"../../../core/canvas/labels.js";import{reportsForNode as fe}from"./reports.js";import{requireUsableProfile as Fn}from"./profiles.js";import{assertProfileActive as Gn,loadExactProfileManifest as Bn}from"../../../core/profiles/manifest.js";import{getCron as ue}from"../../../core/canvas/crons.js";import{cronWakeOrigin as xn}from"../../../core/runtime/bearings.js";import{boundDaemonControl as Kn}from"../../control.js";import{notFound as P,usage as h}from"../../../core/errors.js";import{assertScope as Wn,validateScopes as pe}from"../../../core/scopes.js";import{scopeAllowed as O}from"../../../shared/env.js";import{toCloseResultDTO as Vn,toNodeDetailDTO as J,toNodeSummaryDTO as zn,toReviveResultDTO as Jn}from"../map.js";import{ApiError as m}from"../../../api/index.js";import{InputError as w}from"../../../core/io.js";import{brokerMayReach as U,canReadNode as me,resolvedScopes as Un}from"../router.js";import{forwardVerb as Yn,queryWithoutMachine as Hn,relayError as Qn,unknownError as Xn,withMachine as Zn}from"../../peer/forward.js";import{fileRoot as qn}from"../../../core/spaces/file-root.js";import{activeGrant as et,terminalGrant as nt}from"../../../core/grants/mirror.js";import{readConfig as tt}from"../../../core/config.js";import{hasConfiguredProviderCredential as ot,NO_PROVIDER_CREDENTIAL_MESSAGE as he}from"../../../core/runtime/provider-credential-gate.js";function y(e){const t=k(e);if(t===null)throw P(`unknown node: ${e}`,{received:e});return t}a(y,"requireMeta");function Y(e){return{subscribesTo:ie(e),subscribers:Be(e),children:je(e)}}a(Y,"edgesFor");function g(e){const t=y(e);return J(t,Y(e))}a(g,"detailOf");function rt(e){let t=fe(e).length;try{t+=tn(W(e),de(e)).length}catch(n){if(!en(n))throw n}return t}a(rt,"countFiles");function it(e){if(typeof e!="object"||e===null)throw h("a node create requires a JSON body");const t=e;if(t.kind!==void 0&&(typeof t.kind!="string"||t.kind.trim()===""))throw h("kind must be a non-empty string when provided");if(t.description!==void 0&&(typeof t.description!="string"||t.description.trim()===""))throw h("description must be a non-empty string when provided");if(t.creator!==void 0&&(typeof t.creator!="string"||t.creator.trim()===""))throw h("creator must be a non-empty node id when provided");if(t.scopes!==void 0&&pe(t.scopes),t.isolated!==void 0&&typeof t.isolated!="boolean")throw new m(400,"invalid_request","isolated must be a boolean",{param:"isolated"});const n=t.root_lifecycle;if(n!==void 0&&n!=="resident"&&n!=="terminal")throw h("root_lifecycle must be 'resident' or 'terminal'");if(t.request_key!==void 0&&(typeof t.request_key!="string"||t.request_key.trim()===""||t.request_key.length>128))throw h("request_key must be a non-empty string of at most 128 characters when provided");return t}a(it,"parseCreateBody");function dt(e){if(e===void 0)return;const t=Tn(e),n=t===null?null:new Date(Date.now()+t);if(t===null||!Number.isFinite(t)||t<=0||n===null||!Number.isFinite(n.getTime()))throw new m(422,"invalid_deadline","`deadline` must be a positive representable duration");return n.toISOString()}a(dt,"parseDeadline");function st(e){if(e!==void 0){if(typeof e!="object"||e===null||Array.isArray(e))throw new m(400,"invalid_request","`outcome_delivery` must be an object");if(typeof e.action!="string"||!Cn.test(e.action))throw new m(400,"invalid_request","`outcome_delivery.action` must be a declared action name");if(e.payload!==void 0){let t;try{t=JSON.stringify(e.payload)}catch{throw new m(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable")}if(t===void 0)throw new m(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable");if(Buffer.byteLength(t,"utf8")>le)throw new m(413,"payload_too_large",`serialized payload exceeds ${le} bytes`)}return e}}a(st,"parseOutcomeDelivery");function at(e,t){const n=Nn(e.action,t);if(!("kind"in n))return{actionName:e.action,argv:n.argv,cwd:n.cwd,configOrigin:n.configOrigin,...e.payload===void 0?{}:{payload:e.payload}};throw n.kind==="unknown_action"?new m(422,"unknown_action",`no action named ${e.action} is declared from ${t}`):new m(422,"invalid_action",`${e.action} is declared in ${n.configOrigin} but unusable: ${n.reason}`,{reason:n.reason,configOrigin:n.configOrigin})}a(at,"resolveOutcomeDelivery");function lt(e,t,n){const r={kind:e.kind,cwd:n,prompt:e.prompt??""};return e.mode!==void 0&&(r.mode=e.mode),e.name!==void 0&&(r.name=e.name),e.description!==void 0&&(r.description=e.description),t!==void 0&&(r.parent=t),e.creator!==void 0&&(r.creator=e.creator),e.scopes!==void 0&&(r.scopes=e.scopes),e.isolated!==void 0&&(r.isolated=e.isolated),e.root===!0&&(r.root=!0),e.root_lifecycle!==void 0&&(r.rootLifecycle=e.root_lifecycle),(e.worktree===!0||typeof e.worktree=="string")&&(r.worktree=!0),typeof e.worktree=="string"&&(r.worktreeBase=e.worktree),e.worktree!==void 0&&(r.worktreeCwd=T(e.pin_cwd??e.cwd??n)),e.fork_from!==void 0&&(r.forkFrom=e.fork_from),e.model!==void 0&&(r.model=e.model),e.profile!==void 0&&(r.profile=e.profile),e.situational_context!==void 0&&(r.situationalContext=e.situational_context),e.node_id!==void 0&&(r.nodeId=e.node_id),r}a(lt,"buildRecipe");function ct(e,t,n){return!Dn()||e.prefer_warm!==!0||e.root!==!0||e.parent!=null||e.root_lifecycle==="terminal"||(e.prompt??"")!==""||e.model!==void 0||e.fork_from!==void 0||e.worktree!==void 0||e.node_id!==void 0||e.scopes!==void 0||e.isolated!==void 0?null:{kind:e.kind,mode:e.mode??"base",cwd:t,profileId:n,model:e.model??null,situationalContext:e.situational_context??null,name:e.name??null,creator:e.creator??null}}a(ct,"warmRequestFor");import{resolveCreateKind as ir}from"../../../core/runtime/launch-target.js";function we(e){const t=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return Sn(e.model,t,e.modelExact===!0)}a(we,"requestFromLaunch");function ft(e,t){if(t===null)return e;if(e.scopes===void 0)return{...e,scopes:[...t]};const n=e.scopes.filter(r=>!O(t,r));if(n.length===0)return e;throw new m(403,"scope_denied",`scopes exceed the bearer token's ceiling: ${n.join(", ")}`,{scopes:n,ceiling:t})}a(ft,"withinCeiling");async function ye(e,t,n){const r=(await bn(t.kind,t.mode,{lifecycle:t.lifecycle,hasManager:e.parent!==null,model:t.model,modelOverride:!0,cwd:e.cwd,profileId:e.profile_id??null})).launch;await x(we(r),{cwd:e.cwd,profileId:e.profile_id??null},n)}a(ye,"assertNodeModelChangeRegistered");function _e(e,t){const n=e.principal;if(n.class!=="broker"&&n.class!=="cron")return;const r=qn({app:n.app,resolved:Un(n),launched:n.scopes},t,"read");if("root"in r)return;const o="missing"in r?r.missing:null;throw new m(403,"scope_missing",o?`the child's directory ${t} needs missing scope ${o}`:`no permission of this run names the child's directory ${t}`,void 0,void 0,o?{scope:o}:void 0)}a(_e,"assertCallerReaches");const H=new Map;async function ut(e,t={},n={}){const r=ft(it(e.body),e.ceiling);if(e.principal.class==="broker"){const c=e.principal;if(r.prefer_warm===!0&&c.app!=="app:terminal"&&(r.prefer_warm=!1),r.parent!=null){const i=y(r.parent);if(i.grantee!==c.app)throw new m(403,"other_app_run","cannot create a child under another app's node");if(!U(c,r.parent,i.grantee??"app:terminal"))throw P(`unknown node: ${r.parent}`,{received:r.parent})}if(r.creator!==void 0&&r.creator!==c.node)throw new m(403,"other_app_run","cannot create for another node");if(r.fork_from!==void 0){let i=null;try{i=De(Ee(r.fork_from))}catch{}if(i?.grantee!==c.app||!U(c,i.node_id,i.grantee))throw new m(403,"other_app_run","cannot fork another app's conversation")}r.creator=c.node}if(e.principal.class==="cron"){const c=e.principal;for(const l of["parent","creator","fork_from","node_id"])if(r[l]!=null)throw new m(403,"permission_error",`a cron cannot set ${l} on a node it starts`);r.prefer_warm=!1;const i=Xe({grantee:c.app,sub:c.sub,grant_id:c.grant,scopes:c.scopes});if(!i.allowed)throw new m(i.reason==="scope_missing"?403:401,i.reason==="scope_missing"?"scope_missing":"grant_revoked",i.message);if(r.scopes!==void 0){const l=r.scopes.find(p=>!O(i.effectiveScopes,p)||!O(c.scopes,p));if(l!==void 0)throw new m(403,"scope_missing",`scope ${l} is not held by this cron`,void 0,void 0,{scope:l})}else c.scopes!==null&&(r.scopes=[...c.scopes]);r.node_id=ee(),c.isolated&&(r.isolated=!0,r.cwd=W(r.node_id,c.app))}r.creator!==void 0&&Wn(y(r.creator),"crtr:act");const o=r.request_key?.trim();if(o===void 0||r.node_id!==void 0&&Qe(r.node_id)&&k(r.node_id)!==null&&Le(o)?.nodeId!==r.node_id)return D(e,r,t,n);const s=H.get(o);if(s!==void 0)return s;const d=pt(e,r,o,t,n);H.set(o,d);try{return await d}finally{H.delete(o)}}a(ut,"handleCreate");async function pt(e,t,n,r,o){const s=oe(n);if(s.nodeId!==null&&k(s.nodeId)!==null){if(s.state!=="warm_claimed"){const d=k(s.nodeId);if(e.principal.class!=="person"&&!me(e.principal,d.grantee??"app:terminal",s.nodeId))throw new m(409,"create_request_conflict","request_key belongs to another caller");return{status:201,body:g(s.nodeId)}}return D(e,t,r,o,s)}try{return await D(e,t,r,o,s)}catch(d){const c=oe(n);throw(c.nodeId===null||k(c.nodeId)===null)&&ne(n,c.nodeId),d}}a(pt,"handleKeyedCreateRequest");async function D(e,t,n,r,o=void 0){const s=r.deadlineAt??dt(t.deadline),d=st(t.outcome_delivery),c=e.principal.class==="cron"?Ze({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,cron_id:e.principal.cron,origin_run_id:e.principal.origin_run,isolated:e.principal.isolated,creator:ue(e.principal.cron)?.created_by??null},t.node_id):null;c!==null&&(t.root=c.root===!0);const i=c?.parent??t.parent??void 0;if(i==null&&t.root!==!0)throw h("a node create requires a parent (or root: true for an independent root)");let l=null;if(t.output_schema!==void 0){try{l=JSON.parse(t.output_schema)}catch{throw h("output_schema must be a JSON schema string")}if(l===null||typeof l!="object"||Array.isArray(l))throw h("output_schema must be a JSON schema object");await An(l)}const{profileId:p,kind:u,cwd:f,launch:_}=await Ln(t,{validateKind:Se});_e(e,f),t.worktree!==void 0&&_e(e,T(t.pin_cwd??t.cwd??f));const S={...t,kind:u},I=d===void 0?void 0:at(d,f);if(await x(we(_),{cwd:f,profileId:p},n.modelRegistry),!await ot(p))throw new m(412,"no_provider_credential",he,{error:"no_provider_credential",message:he,next:"The person running this runtime logs a provider in with `crtr sys setup`, or stores a provider key on the profile."});let L=null;{const $=o===void 0||o.nodeId===null||o.state==="warm_claimed"?ct(S,f,p):null,M=l===null&&r.beforeBrokerLaunch===void 0?void 0:v=>{l!==null&&$n(v,"terminal",l),r.beforeBrokerLaunch?.(v)};if(o?.state==="warm_claimed"&&o.nodeId!==null){const v=$===null?null:await Pn($,o.nodeId,M);if(v===null)throw ne(o.requestKey,o.nodeId),new m(503,"create_resume_failed",`could not resume warm claim ${o.nodeId}; retry the create`);return te(o.requestKey,v),{status:201,body:g(v)}}if($!==null){const v=await En($,{...s===void 0?{}:{deadlineAt:s},...I===void 0?{}:{outcomeDelivery:I},...o===void 0?{}:{requestKey:o.requestKey}},M);if(v!==null)return o!==void 0&&te(o.requestKey,v),ce($),{status:201,body:g(v)};L=$}const F=o?.nodeId??t.node_id??(o===void 0?void 0:ee());o!==void 0&&o.nodeId===null&&Fe(o.requestKey,F);const R=lt(F===void 0?S:{...S,node_id:F},i,f);c!==null&&(c.rootLifecycle!==void 0&&(R.rootLifecycle=c.rootLifecycle),c.scopes!==void 0&&S.scopes===void 0&&(R.scopes=c.scopes),c.run!==void 0&&(R.run=c.run),c.withinCreate!==void 0&&(R.withinCreate=c.withinCreate)),s!==void 0&&(R.deadlineAt=s),I!==void 0&&(R.outcomeDelivery=I),R.readiness=t.root===!0?"await":"launch";const Q=await Pe(R,M,()=>{L!==null&&ce(L)});return o!==void 0&&Me(o.requestKey,Q.node.node_id),{status:201,body:g(Q.node.node_id)}}}a(D,"handleCreateRequest");function b(e,t){const n=e.query.getAll(t);if(n.length>1)throw h(`${t} must appear at most once`);return n[0]}a(b,"singleQueryValue");function ge(e,t){if(e!==void 0){if(e==="1"||e==="true")return!0;if(e==="0"||e==="false")return!1;throw h(`${t} must be true or false`)}}a(ge,"optionalBoolean");function j(e,t,n){if(e!==void 0){if(n.includes(e))return e;throw h(`${t} must be one of: ${n.join(", ")}`)}}a(j,"oneOf");function mt(e){const t=new Set(["status","lifecycle","kind","mode","profile_id","profile_prefix","parent","top_level","under","hanging","include"]);for(const[_]of e.query)if(!t.has(_))throw h(`unknown nodes query parameter: ${_}`);const n=j(b(e,"status"),"status",["active","idle","done","dead","canceled"]),r=j(b(e,"lifecycle"),"lifecycle",["terminal","resident"]),o=b(e,"kind"),s=j(b(e,"mode"),"mode",["base","orchestrator"]),d=b(e,"profile_id"),c=b(e,"profile_prefix"),i=b(e,"parent"),l=ge(b(e,"top_level"),"top_level"),p=b(e,"under"),u=ge(b(e,"hanging"),"hanging"),f=j(b(e,"include"),"include",["activity"]);if(i!==void 0&&l===!0)throw h("parent and top_level=true cannot be combined");return{...n!==void 0?{status:n}:{},...r!==void 0?{lifecycle:r}:{},...o!==void 0?{kind:o}:{},...s!==void 0?{mode:s}:{},...d!==void 0?{profile_id:d}:{},...c!==void 0?{profile_prefix:c}:{},...i!==void 0?{parent:i}:{},...l!==void 0?{top_level:l}:{},...p!==void 0?{under:p}:{},...u!==void 0?{hanging:u}:{},...f!==void 0?{include:f}:{}}}a(mt,"parseListQuery");function ht(e){const t=mt(e);let n=t.status!==void 0?re({status:t.status}):re();if(e.principal.class!=="person"&&(n=n.filter(d=>me(e.principal,d.grantee??"app:terminal",d.node_id))),t.lifecycle!==void 0&&(n=n.filter(d=>d.lifecycle===t.lifecycle)),t.kind!==void 0&&(n=n.filter(d=>d.kind===t.kind)),t.mode!==void 0&&(n=n.filter(d=>d.mode===t.mode)),t.profile_id!==void 0&&(n=n.filter(d=>d.profile_id===t.profile_id)),t.profile_prefix!==void 0&&(n=n.filter(d=>d.profile_id?.startsWith(t.profile_prefix))),t.parent!==void 0&&(n=n.filter(d=>d.parent===t.parent)),t.top_level===!0&&(n=n.filter(d=>d.parent===null)),t.under!==void 0){const d=new Set(Ke(t.under));n=n.filter(c=>d.has(c.node_id))}t.hanging===!0&&(n=n.filter(d=>d.parent!==null&&k(d.parent)===null));const r=t.include==="activity"?He(n.map(d=>d.node_id)):null,o=n.length>0?Ie():new Set;return{status:200,body:n.map(d=>{const c=zn(d,d.profile_id!==null&&o.has(d.profile_id));if(r!==null){const i=fe(d.node_id);c.activity={latest_report:i[0]??null,final_report:d.final_report===null?null:i.find(l=>l.name===d.final_report)??null,pending_human_count:r[d.node_id]??0}}return c})}}a(ht,"handleList");async function wt(e){const t=e.params.id,n=await Yn(e,t,{method:"GET",path:`/v1/nodes/${encodeURIComponent(t)}`,query:Hn(e)});if(n.kind==="unknown")throw Xn(t,n.unreachable);if(n.kind==="remote"){if(n.status>=400)throw Qn(n.status,n.body);return{status:n.status,body:Zn(n.body,n.machine)}}return y(t),{status:200,body:g(t)}}a(wt,"handleDetail");async function yt(e){const t=e.params.id;y(t);const n=await dn(t);return{status:200,body:{node_id:t,snapshot:{messages:n.snapshot.messages,...n.snapshot.messageIds===void 0?{}:{messageIds:n.snapshot.messageIds},messageVisibility:n.snapshot.messageVisibility,turnVisibility:n.snapshot.turnVisibility,stats:n.snapshot.stats,state:n.snapshot.state,display:n.snapshot.display,...n.snapshot.queued===void 0?{}:{queued:n.snapshot.queued},...n.snapshot.toolGroupSummaries===void 0?{}:{toolGroupSummaries:n.snapshot.toolGroupSummaries},...n.snapshot.turnReplies===void 0?{}:{turnReplies:n.snapshot.turnReplies},...n.snapshot.workingActivity===void 0?{}:{workingActivity:n.snapshot.workingActivity}},commands:n.commands.map(o=>({name:o.name,description:o.description,source:o.source})),captured_at:B()}}}a(yt,"handleSnapshot");async function _t(e){const t=e.params.id,n=an(t);if(n===null)throw P(`unknown node: ${t}`,{received:t});return{status:200,body:n}}a(_t,"handleSubject");function gt(e){for(const[s]of e.query)if(s!=="cursor"&&s!=="limit")throw h(`unknown messages query parameter: ${s}`);const t=e.query.getAll("cursor"),n=e.query.getAll("limit");if(t.length>1)throw h("cursor must appear at most once");if(n.length>1)throw h("limit must appear at most once");if(n.length===0)return t.length===0?{}:{cursor:t[0]};const r=n[0],o=Number(r);if(!Number.isSafeInteger(o)||r==="")throw h("limit must be an integer from 1 through 500");return t.length===0?{limit:o}:{cursor:t[0],limit:o}}a(gt,"parseMessagesQuery");async function bt(e){const t=e.params.id,n=await on(t,gt(e));return{status:200,body:{node_id:t,messages:n.messages,...n.messageIds===void 0?{}:{message_ids:n.messageIds},message_visibility:n.messageVisibility,next_cursor:n.nextCursor,captured_at:B()}}}a(bt,"handleMessages");function vt(e){const t=e.params.id;y(t);const n=rn(t);return{status:200,body:{node_id:t,session_file:n.sessionFile,session_jsonl:n.sessionJsonl,system_prompt:n.systemPrompt,captured_at:B()}}}a(vt,"handleSession");async function kt(e){const t=e.params.id;return y(t),{status:200,body:{node_id:t,markdown:await sn(t)}}}a(kt,"handleTranscript");function St(e){const t=e.params.id;y(t);const n=a((o,s)=>({node_id:o,label:k(o)?.name??o,dir:de(o),self:s,files:rt(o)}),"root"),r=[n(t,!0),...ie(t).map(o=>n(o.node_id,!1))].filter(o=>Rt(o.node_id,o.dir)||o.files>0);return{status:200,body:{node_id:t,roots:r}}}a(St,"handleContext");function Rt(e,t){try{return qe(W(e),t)}catch(n){if(nn(n))return!1;throw n}}a(Rt,"hasDir");async function Ot(e){const t=y(e.params.id),n=e.principal.class==="broker"?e.principal.scopes:e.ceiling,r=n===null?void 0:t.scopes==null?[...n]:[...new Set([...t.scopes.filter(s=>O(n,s)),...n.filter(s=>O(t.scopes,s))])],o=await Te(t.node_id,r,e.principal.class==="broker"?e.principal.node:void 0);return{status:201,body:g(o.node.node_id)}}a(Ot,"handleFork");const It=1e4;async function $t(e){const t=e.params.id;y(t);const n=e.body,r=n?.resume??!0,o=n?.reopen??!1;mn(t,o);const s=!r&&n?.cron_id!==void 0?ue(n.cron_id):null,d=await Ne(t,{resume:r,capacity:"refuse",...s!==null?{wakeReason:xn(s,"schedule_revive")}:{}});d.outcome!=="already-live"&&Ge(t,{failures:0,notBefore:null});const c=k(t)?.status??"active",i=(d.launch??E().get(t)?.handle)?.exited,l=d.outcome!=="frozen"&&await ae(t,i,It);return{status:200,body:Jn(t,d,c,l)}}a($t,"handleRevive");async function At(e){const t=e.params.id;return{status:200,body:{newNodeId:(await pn(t))?.newNodeId??null}}}a(At,"handleRelaunchRoot");function Ct(e){const t=e.params.id;y(t);const n=e.body;if(n?.caller_pid!==void 0&&(!Number.isInteger(n.caller_pid)||n.caller_pid<=0))throw h("caller_pid must be a positive integer when provided");const r=Kn(),o=Ce(t,{rootEvent:n?.finish===!0?"finish":"cancel",callerPid:n?.caller_pid,...e.principal.class==="broker"?{closer:e.principal.node}:{},...r===null?{}:{registerDetached:r.registerDetached}});return{status:200,body:Vn(o)}}a(Ct,"handleClose");async function Nt(e){const t=e.params.id;y(t);const n=await un(t);return{status:200,body:{recycled:n.recycled,finalized:n.finalized,new_root:n.newRoot,delivered:n.delivered}}}a(Nt,"handleRecycle");async function Tt(e){const t=e.params.id;return y(t),await V(t,{lifecycle:"terminal"}),A(e,t,["lifecycle"]),{status:200,body:g(t)}}a(Tt,"handleDemote");async function Et(e,t={}){const n=e.params.id,r=y(n),o=e.body??{};o.kind!==void 0&&await z(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await ye(r,{kind:o.kind??r.kind,mode:"orchestrator",lifecycle:o.resident===!0?"resident":r.lifecycle,model:o.model},t.modelRegistry);const s=await cn(n,{...o.kind!==void 0?{kind:o.kind}:{},...o.resident===!0?{resident:!0}:{},...o.model!==void 0?{model:o.model}:{}});A(e,n,["mode",...o.kind!==void 0?["kind"]:[],...o.resident===!0?["lifecycle"]:[],...o.model!==void 0?["model"]:[]]);const d=J(s.meta,Y(n));return d.roadmap_written=s.roadmapWritten,d.roadmap_ref=s.roadmapRef,d.goal_path=s.goalPath,{status:200,body:d}}a(Et,"handlePromote");async function Pt(e,t={}){const n=e.params.id,r=y(n),o=e.body??{};o.kind!==void 0&&await z(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await ye(r,{kind:o.kind??r.kind,mode:o.promote===!0?"orchestrator":r.mode,lifecycle:r.lifecycle,model:o.model},t.modelRegistry);const s=o.note??"Refresh: continue against your roadmap or goal.";hn(n,s);const d=await fn(n,{...o.promote===!0?{promote:!0}:{},...o.kind!==void 0?{kind:o.kind}:{},...o.model!==void 0?{model:o.model}:{}});return{status:200,body:J(d.meta,Y(n))}}a(Pt,"handleYield");function Dt(e){const t=e.params.id;y(t);const n=e.body;if(n===void 0||typeof n.controller!="string"||n.controller.trim()==="")throw h("a wait requires a controller node id");const r=k(n.controller);if(r===null)throw P(`unknown controller: ${n.controller}`,{received:n.controller});if(r.status!=="active"&&r.status!=="idle")throw new m(422,"unprocessable",`controller ${n.controller} is ${r.status}`);return xe(t,n.controller),{status:200,body:g(t)}}a(Dt,"handleWait");async function jt(e){const t=e.principal,n=await ln(t.class==="broker"?o=>o.grantee===t.app&&U(t,o.node_id,o.grantee):void 0);return{status:200,body:{revived:[...n.revived],failed:[...n.failed],frozen:[...n.frozen]}}}a(jt,"handleReviveAll");const Lt=new Set(["interrupt","steer","after-turn","next-turn"]);function Mt(e){if(e===null)return null;const t=a(r=>{throw new w({error:"invalid_listen",message:r,received:e,field:"listen",next:'Send an object mapping levels "1".."10" to interrupt|steer|after-turn|next-turn (e.g. {"3":"next-turn"}), or null to reset to the global default.'})},"bad");if(typeof e!="object"||Array.isArray(e))return t("listen must be an object or null");const n=Object.entries(e);if(n.length===0)return t("listen must name at least one level");for(const[r,o]of n)/^(?:[1-9]|10)$/.test(r)||t(`listen level must be 1..10: ${r}`),(typeof o!="string"||!Lt.has(o))&&t(`listen delivery must be interrupt|steer|after-turn|next-turn: ${String(o)}`);return JSON.stringify(Object.fromEntries(n))}a(Mt,"listenJson");function A(e,t,n,r){n.length===0&&r===void 0||K(o=>{if(r?.(o),n.length===0)return;const s=We(o,t),d=Je(o,e),c=d.class==="person"?d.person:d.class==="app"?d.app:d.node;Ve(o,c,s),ze(o,{event:"configured",object:s,actor:c,attrs:{changed:n.join(",")}})})}a(A,"recordConfigured");function be(e){const t=E().get(e);if(t===void 0)return!1;try{return process.kill(t.pid,"SIGTERM"),!0}catch{return!1}}a(be,"restartLiveBroker");function ve(e,t){try{return Z(e)===Z(t)}catch{return!1}}a(ve,"sameDirectory");async function Ft(e,t={}){const n=e.params.id,r=e.body;let o;if(r===void 0)o={};else if(typeof r=="object"&&r!==null&&!Array.isArray(r))o=r;else throw new w({error:"cwd_invalid",message:"cwd repair requires a non-null object with a non-empty cwd string",received:r,field:"cwd",next:"Send a config patch with a non-empty absolute cwd string."});if(o.profile!==void 0){if(typeof o.profile!="string"||o.profile==="")throw new w({error:"profile_invalid",message:"profile must be a non-empty profile id or name",received:o.profile,field:"profile",next:"Send a profile id or exact name."});if(Object.keys(o).some(u=>u!=="profile"&&u!=="restart"&&o[u]!==void 0))throw new w({error:"profile_not_exclusive",message:"profile can only be combined with restart",field:"profile",next:"Send profile (and optionally restart) alone."});const i=y(n),l=Fn(e,o.profile);Gn(l.profileId);const p=Bn(l.profileId).manifest.grantee??"app:terminal";if(p!==(i.grantee??"app:terminal"))throw new w({error:"profile_grantee_mismatch",message:`profile ${l.profileId} belongs to ${p}, node ${n} to ${i.grantee}`,received:o.profile,field:"profile",next:"Pick a profile owned by the same app as the node."});return(i.profile_id??null)!==l.profileId&&(jn(i.kind)?(await z(i.kind,i.cwd,l.profileId),await V(n,{profileId:l.profileId})):C(n,{profile_id:l.profileId}),A(e,n,["profile"])),o.restart===!0&&be(n),{status:200,body:g(n)}}if(Object.prototype.hasOwnProperty.call(o,"cwd")){const i=o.cwd;if(typeof i!="string"||i==="")throw new w({error:"cwd_invalid",message:"cwd must be a non-empty string",received:i,field:"cwd",next:"Send a non-empty absolute cwd string."});if(Object.keys(o).some(f=>f!=="cwd"&&f!=="keep_session"&&f!=="restart"&&o[f]!==void 0))throw new w({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config field",received:o,field:"cwd",next:"Send cwd as the only config field."});const l=y(n),p=N(l.cwd);if(o.keep_session===!0||p&&G(i)&&N(i)&&ve(l.cwd,i)){if(!G(i))throw new w({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const f=T(i);let _=!1;try{_=q(f).isDirectory()}catch{}if(!_)throw new w({error:"cwd_missing",message:`cwd is not an existing directory: ${f}`,received:i,field:"cwd",next:"Send an existing absolute directory."});if(p&&!ve(l.cwd,f))throw new w({error:"cwd_not_same_directory",message:`${f} is not the same directory as the recorded ${l.cwd}; --keep-session only relocates an alias`,received:i,field:"cwd",next:"Drop --keep-session to repair a gone cwd, or pass the real path of the recorded directory."});if(l.managed_worktree?.state==="open")throw new w({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot relocate its cwd`,received:i,field:"cwd",next:"Reconcile the managed worktree first."});const S=l.cwd;return S!==f&&(K(I=>{C(n,{cwd:f}),X(I,"UPDATE crons SET cwd = ? WHERE anchor_node = ? AND cwd = ?").run(f,n,S),Ue(n,S,f)}),A(e,n,["cwd"])),o.restart===!0&&be(n),{status:200,body:g(n)}}if(E().has(n)){const f=`${n} cannot repair its cwd while its broker is live`;throw new m(409,"cwd_node_live",f,{error:"cwd_node_live",message:f,received:i,field:"cwd",next:"Let the node become dormant, then retry the cwd repair."})}if(l.fork_from!=null)throw new w({error:"cwd_node_forked",message:`${n} is a fork and cannot repair its cwd; recorded cwd: ${l.cwd}`,received:i,field:"cwd",next:`Restore ${l.cwd}, then revive the fork normally.`});if(l.managed_worktree?.state==="open")throw new w({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot repair its cwd`,received:i,field:"cwd",next:"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon flow."});if(N(l.cwd))throw new w({error:"cwd_still_present",message:`${n} recorded cwd still exists: ${l.cwd}; cwd repair is not a relocation command`,received:i,field:"cwd",next:"This command only repairs a node whose recorded cwd is gone."});if(!G(i))throw new w({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const u=T(i);if(!N(u))throw new w({error:"cwd_missing",message:`cwd does not exist: ${u}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."});try{if(!q(u).isDirectory())throw new w({error:"cwd_not_a_directory",message:`cwd is not a directory: ${u}`,received:i,field:"cwd",next:"Send an existing absolute directory."})}catch(f){throw f instanceof w?f:new w({error:"cwd_missing",message:`cwd does not exist: ${u}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."})}return K(()=>{C(n,{cwd:u}),Ye(n)}),A(e,n,["cwd"]),{status:200,body:g(n)}}let s=y(n);const d=o.listen===void 0?void 0:Mt(o.listen);if(o.scopes!==void 0){const i=pe(o.scopes),l=s.grantee==="app:terminal"?nt():s.grant_id?et(s.grant_id):null,p=s.grantee==="app:terminal"&&!tt("user").runtime?.issuer;if(!i.every(u=>O(s.scopes,u)&&(l?O(l.resolved,u):p)))throw new w({error:"unknown_scope",message:"scopes may only narrow the node's current effective set",received:i,field:"scopes",next:"Send only scopes the node currently holds."});if(i.includes("crtr:act")&&!O(i,"crtr:llm"))throw new m(400,"invalid_scope","crtr:act requires crtr:llm");s=C(n,{scopes:i})}if(o.situational_context!==void 0&&$e(n,Ae(o.situational_context)),o.model!==void 0){const i=(await se(s.cwd,s.profile_id??null)).modelLadders,l=vn(o.model,i),p=kn(o.model,i);await x({kind:"concrete",spec:p},{cwd:s.cwd,profileId:s.profile_id??null},t.modelRegistry);const u=E();let f=!1;if(u.isClaimed(n)&&await ae(n,u.get(n)?.handle.exited)&&(f=await On(n,p,l).then(()=>!0,_=>{if(_ instanceof In)return!1;throw _})),!f){if(u.isClaimed(n)){const _=`${n}'s broker did not accept the model switch; the model was not changed`;throw new m(409,"broker_not_ready",_,{error:"broker_not_ready",message:_,received:o.model,field:"model",next:"Check the node with `crtr node show`, then retry the model change."})}if(!p.includes("/"))throw h(`model must resolve to a provider/id spec: ${o.model}`);Rn(n,p,l)}s=y(n)}if(o.kind!==void 0||o.mode!==void 0||o.lifecycle!==void 0){o.kind!==void 0&&await Se(o.kind);const i=o.kind??s.kind,l=o.mode??s.mode,p=o.lifecycle??s.lifecycle;let u;l==="orchestrator"&&!_n(n)&&(u=a(()=>{const f=wn(n);yn(n,f!==null&&f.trim()!==""?{goal:f.trim()}:{})},"beforePersist")),s=await V(n,{...o.kind!==void 0?{kind:i}:{},...o.mode!==void 0?{mode:l}:{},...o.lifecycle!==void 0?{lifecycle:p}:{}},u)}o.name!==void 0&&(s=C(n,{name:o.name}),s.window&&gn(s.window,Mn(s)));const c=["scopes","situational_context","model","kind","mode","lifecycle","name","listen"].filter(i=>o[i]!==void 0);return A(e,n,c,d===void 0?void 0:i=>{X(i,"UPDATE objects SET listen_json = ? WHERE id = ? AND type = 'node'").run(d,n)}),{status:200,body:g(n)}}a(Ft,"handleConfig");async function ke(){return(await se(process.cwd(),Oe()||null)).kinds}a(ke,"daemonKinds");async function Se(e){const t=Object.keys(await ke());if(!t.includes(e))throw h(`unknown kind: ${e}`,{received:e,next:`Valid kinds: ${t.sort().join(", ")}.`})}a(Se,"assertKindServer");async function Gt(){const e=await ke();return{status:200,body:{kinds:Object.keys(e).sort().map(n=>({kind:n,when_to_use:e[n]?.whenToUse??null,available_to:e[n]?.availableTo??null}))}}}a(Gt,"handleListKinds");const tr=[{method:"POST",pattern:"/v1/nodes",scope:"crtr:act",handler:ut},{method:"GET",pattern:"/v1/nodes",handler:ht},{method:"GET",pattern:"/v1/kinds",handler:a(()=>Gt(),"handler")},{method:"POST",pattern:"/v1/nodes/revive-all",scope:"crtr:act",handler:jt},{method:"GET",pattern:"/v1/nodes/:id",handler:wt},{method:"GET",pattern:"/v1/nodes/:id/snapshot",handler:yt},{method:"GET",pattern:"/v1/nodes/:id/subject",handler:_t},{method:"GET",pattern:"/v1/nodes/:id/messages",handler:bt},{method:"GET",pattern:"/v1/nodes/:id/session",handler:vt},{method:"GET",pattern:"/v1/nodes/:id/transcript",handler:kt},{method:"GET",pattern:"/v1/nodes/:id/context",handler:St},{method:"POST",pattern:"/v1/nodes/:id/fork",scope:"crtr:act",handler:Ot},{method:"POST",pattern:"/v1/nodes/:id/revive",scope:"crtr:act",handler:$t},{method:"POST",pattern:"/v1/nodes/:id/relaunch-root",scope:"crtr:act",handler:At},{method:"POST",pattern:"/v1/nodes/:id/close",handler:Ct},{method:"POST",pattern:"/v1/nodes/:id/recycle",scope:"crtr:act",handler:Nt},{method:"POST",pattern:"/v1/nodes/:id/demote",scope:"crtr:act",handler:Tt},{method:"POST",pattern:"/v1/nodes/:id/promote",scope:"crtr:act",handler:Et},{method:"POST",pattern:"/v1/nodes/:id/yield",scope:"crtr:act",handler:Pt},{method:"POST",pattern:"/v1/nodes/:id/wait",handler:Dt},{method:"PATCH",pattern:"/v1/nodes/:id/config",handler:Ft}];export{Ft as handleConfig,ut as handleCreate,Ot as handleFork,ht as handleList,Et as handlePromote,Pt as handleYield,tr as nodeRoutes,ir as resolveCreateKind};
@@ -80,7 +80,7 @@ export declare function toNodeDetailDTO(meta: NodeMeta, edges: NodeEdgesInput):
80
80
  * actually happened — the double-revive no-op (already-alive) returns without a
81
81
  * `launch` handle, so its absence is the "nothing relaunched" signal. `status`
82
82
  * is the caller's post-revive row read. */
83
- export declare function toReviveResultDTO(nodeId: string, result: ReviveResult, status: NodeStatusDTO): ReviveResultDTO;
83
+ export declare function toReviveResultDTO(nodeId: string, result: ReviveResult, status: NodeStatusDTO, ready: boolean): ReviveResultDTO;
84
84
  /** Project a `closeNode` cascade result. Field names align 1:1. */
85
85
  export declare function toCloseResultDTO(result: CloseNodeResult): CloseResultDTO;
86
86
  /** Project a feed `push` result into a push-report result. `transitioned` is
@@ -1,2 +1,2 @@
1
- var g=Object.defineProperty;var r=(e,t)=>g(e,"name",{value:t,configurable:!0});import{join as h}from"node:path";import{readOutputResult as b}from"../../core/runtime/structured-output.js";import{ReviewOperationError as _}from"../../core/review/types.js";import{cronSinkDisplay as y}from"../../core/canvas/crons.js";import{isProfilePaused as c,profileDir as x,sanitizeProfileMetadata as O}from"../../core/profiles/manifest.js";import{nodeMetaPath as D,transcriptPath as T,viewSocketPath as E}from"../../core/canvas/paths.js";import{appForNode as f,contextDir as S,nodeAgentDir as A}from"../../core/canvas/node-agent-paths.js";import{FinalizationError as R}from"../../core/feed/feed.js";import{NodeIdConflictError as z}from"../../core/runtime/nodes.js";import{ScopeDeniedError as N}from"../../core/scopes.js";import{isBeneathRefusal as C}from"../../core/spaces/open-beneath.js";import{CrtrError as s}from"../../core/errors.js";import{WorktreeError as j}from"../../core/worktree.js";import{ApiError as q}from"../../api/errors.js";import{RunError as l}from"../../core/runs/operations.js";import{ExitCode as o}from"../../types.js";function ce(e,t){const{report:n}=e;return{stores:n.stores.map(i=>({root:i.root,owner:{...i.owner},docs:i.docs,...i.skipped===void 0?{}:{skipped:i.skipped}})),counts:n.counts,rewrites:n.rewrites,dropped_fields:n.droppedFields,skipped:n.skipped,resolutions:n.resolutions.map(i=>({kind:i.kind,message:i.message,paths:i.paths})),blockers:e.blockers.map(i=>({kind:i.kind,message:i.message,paths:i.paths})),dryRun:e.dryRun,pending:e.pending,resumed:t.resumed,...t.resumeError===void 0?{}:{resume_error:t.resumeError}}}r(ce,"toMigrateStateDTO");function F(e,t){try{return b(e,t)??null}catch{return null}}r(F,"readStructuredResult");function I(e){return e.outcome_at===null||e.outcome_at===void 0||e.outcome_kind===null||e.outcome_kind===void 0||e.outcome_reason===null||e.outcome_reason===void 0?null:{kind:e.outcome_kind,reason:e.outcome_reason,settled_at:e.outcome_at}}r(I,"toNodeOutcomeSummaryDTO");function p(e){if(e.outcome_at===null||e.outcome_at===void 0||e.outcome_kind===null||e.outcome_kind===void 0||e.outcome_reason===null||e.outcome_reason===void 0)return null;let t=null;if(e.outcome_detail_json!==null&&e.outcome_detail_json!==void 0)try{t=JSON.parse(e.outcome_detail_json)}catch{t=null}const n=e.final_report??null,i={node_id:e.node_id,revision:e.outcome_revision??1,settled_at:e.outcome_at,final_report:n===null?null:`${e.node_id}/${n}`,structured_result:e.outcome_kind==="result"?F(e.node_id,e.grantee):null,detail:t};return e.outcome_kind==="result"?{...i,kind:"result",reason:e.outcome_reason,declined:null}:e.outcome_reason==="declined"?{...i,kind:"failure",reason:"declined",declined:t?.declined??null}:{...i,kind:"failure",reason:e.outcome_reason,declined:null}}r(p,"toNodeOutcomeDTO");function fe(e){const t=p(e);return t===null||t.reason==="parked"?null:t.kind==="result"||t.reason==="finished"?{...t,kind:"result",reason:"finalized",declined:null}:t.reason==="declined"?{...t,reason:"declined",details:t.declined}:t.reason==="deadline_exceeded"?{...t,reason:"deadline_exceeded",declined:null}:t.reason==="cap_exhausted"?{...t,reason:"cap_exhausted",declined:null}:t.reason==="closed"||t.reason==="stranded"||t.reason==="retired"?{...t,reason:"canceled",declined:null}:{...t,reason:"runtime_error",declined:null,details:{crouter_reason:t.reason}}}r(fe,"toRunOutcomeDTO");function pe(e){const t=e.lastFailure;return{node_id:e.nodeId,state:e.state,action:e.actionName,attempt:e.attempt,next_attempt_at:e.nextAttemptAt===0?null:new Date(e.nextAttemptAt).toISOString(),accepted_at:e.acceptedAt===null?null:new Date(e.acceptedAt).toISOString(),permanent_failed_at:e.permanentFailedAt===null?null:new Date(e.permanentFailedAt).toISOString(),last_failure:t===null?null:{kind:t.kind,...t.exitCode===void 0?{}:{exit_code:t.exitCode},...t.signal===void 0?{}:{signal:t.signal},...t.message===void 0?{}:{message:t.message}},created_at:new Date(e.createdAt).toISOString(),updated_at:new Date(e.updatedAt).toISOString()}}r(pe,"toOutcomeDeliveryDTO");function me(e){return{node_id:e.node_id,name:e.name,description:e.description,cycles:e.cycles,kind:e.kind,status:e.status,frozen_at:e.frozen_at,pi_pid:e.pi_pid,telemetry_context_tokens:e.telemetry_context_tokens,telemetry_last_activity:e.telemetry_last_activity,fault:e.fault&&e.fault.link!=="broker\u2194crtrd"?{...e.fault,link:e.fault.link}:null,streaming:e.streaming}}r(me,"toGraphNodeDTO");function ve(e,t=c(e.profile_id)){return{node_id:e.node_id,name:e.name,description:e.description,cycles:e.cycles,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind,profile_id:e.profile_id,scopes:e.scopes,isolated:e.isolated,grantee:e.grantee??f(e.node_id),profile_paused:t,parent:e.parent,created:e.created,intent:e.intent,waiting_for:e.waiting_for,frozen_at:e.frozen_at??null,terminal_reason:e.terminal_reason??null,pi_pid:e.pi_pid,pi_pid_identity:e.pi_pid_identity,window:e.window,tmux_session:e.tmux_session,pane:e.pane,final_report:e.final_report??null,finalized_at:e.finalized_at??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,outcome:I(e),fault:e.fault&&e.fault.link!=="broker\u2194crtrd"?{...e.fault,link:e.fault.link}:null,streaming:e.streaming??!1}}r(ve,"toNodeSummaryDTO");function P(e,t){return{parent:e.parent??null,spawned_by:e.spawned_by??null,subscribes_to:t.subscribesTo.map(n=>n.node_id),subscribers:t.subscribers.map(n=>n.node_id),children:[...t.children]}}r(P,"toNodeEdgesDTO");function U(e,t){return{node_dir:A(e,t),context_dir:S(e,t),meta_path:D(e),transcript_path:T(e),view_socket:E(e)}}r(U,"toNodePathsDTO");function G(e){const t={state:e.state,path:e.path,branch:e.branch,repo_root:e.repo_root,base_ref:e.base_ref,base_sha:e.base_sha,created:e.created};return e.cleanup!==void 0&&(t.cleanup=e.cleanup),e.closed!==void 0&&(t.closed=e.closed),e.abandoned!==void 0&&(t.abandoned={...e.abandoned}),t}r(G,"toNodeWorktreeDTO");function ke(e,t){const n=f(e.node_id),i={node_id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,profile_id:e.profile_id??null,scopes:e.scopes??null,isolated:e.isolated===!0,grantee:n,profile_paused:c(e.profile_id),parent:e.parent??null,created:e.created,intent:e.intent??null,waiting_for:e.waiting_for??null,frozen_at:e.frozen_at??null,terminal_reason:e.terminal_reason??null,pi_pid:e.pi_pid??null,pi_pid_identity:e.pi_pid_identity??null,window:e.window??null,tmux_session:e.tmux_session??null,pane:e.pane??null,final_report:e.final_report??null,finalized_at:e.finalized_at??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,outcome:p(e),fault:e.fault&&e.fault.link!=="broker\u2194crtrd"?{...e.fault,link:e.fault.link}:null,streaming:e.streaming??!1,creator:e.creator??null,edges:P(e,t),paths:U(e.node_id,n)};return e.description!==void 0&&(i.description=e.description),e.title!==void 0&&(i.title=e.title),e.icon!==void 0&&(i.icon=e.icon),e.cycles!==void 0&&(i.cycles=e.cycles),i.context_tokens=e.telemetry_context_tokens??e.telemetry_tokens_in??null,e.pi_session_id!==void 0&&(i.pi_session_id=e.pi_session_id??null),i.model_override=e.model_override??null,e.pi_session_file!==void 0&&(i.pi_session_file=e.pi_session_file??null),e.review_binding!==void 0&&(i.review_binding=e.review_binding===null?null:{...e.review_binding.kind===void 0?{}:{kind:e.review_binding.kind},review_id:e.review_binding.review_id,origin_node_id:e.review_binding.origin_node_id,branch_file:e.review_binding.branch_file,target_file:e.review_binding.target_file}),e.managed_worktree!=null&&(i.worktree=G(e.managed_worktree)),i}r(ke,"toNodeDetailDTO");function ge(e,t,n){return{node_id:e,revived:t.launch!==void 0,resumed:t.resumed,status:n,outcome:t.outcome}}r(ge,"toReviveResultDTO");function he(e){return{root:e.root,closed:[...e.closed],spared:[...e.spared]}}r(he,"toCloseResultDTO");function be(e,t,n){const i={report:e.report,notified:[...e.deliveredTo]};return t!==void 0&&(i.transitioned=t),n!==void 0&&(i.worktree_auto_dropped=!0,i.worktree_auto_dropped_path=n),i}r(be,"toReportResultDTO");function ye(e){return{node_id:e.node_id,active:e.active,created:e.created}}r(ye,"toSubscriptionDTO");function M(e){if(e===null||e==="")return[];try{const t=JSON.parse(e);return typeof t=="object"&&t!==null&&!Array.isArray(t)?Object.keys(t):[]}catch{return[]}}r(M,"envKeys");function xe(e,t){return{cron_id:e.cron_id,name:e.name,created_by:e.created_by,command:e.command,grant_id:e.grant_id,scopes:e.scopes,origin_run_id:e.origin_run_id,isolated:e.isolated,fire_at:e.fire_at,recur:e.recur,tz:e.tz,expires_at:e.expires_at,anchor_node:e.anchor_node,cancel_on_wake:e.cancel_on_wake,cwd:e.cwd,profile:e.profile,scope:e.scope,env_keys:M(e.env_json),run_timeout_s:e.run_timeout_s,overlap:e.overlap,on_output:e.on_output,sink:y(e.sink),tier:e.tier,state:e.state,held:e.held,run_state:e.run_state,last_run:t==null?null:{finished:t.finished,exit_code:t.exit_code,delivered:t.delivered},created:e.created,updated:e.updated}}r(xe,"toCronDTO");function Oe(e){return{run_id:e.run_id,started:e.started,finished:e.finished,duration_ms:e.duration_ms,exit_code:e.exit_code,stdout_head:e.stdout_head,stderr_head:e.stderr_head,delivered:e.delivered}}r(Oe,"toCronRunDTO");function De(e){return{id:e.profileId,app:e.manifest.grantee??"app:terminal",name:e.manifest.name,projects:e.manifest.projects.map(t=>({...t})),delivery_limits:{...e.manifest.delivery_limits},paused_at:e.manifest.paused_at,default_kind:e.manifest.default_kind??"general",metadata:O(e.manifest.metadata),default_dir:null,created_at:e.manifest.created_at,last_used_at:e.manifest.last_used_at,profile_path:x(e.profileId)}}r(De,"toProfileDTO");function m(e,t={}){const n={review_id:e.review_id,origin_kind:e.origin_kind,origin_node_id:e.origin_node_id,review_dir:e.review_dir,companion_node_id:e.companion_node_id,file:e.file,state:e.state,created:e.created,source_missing:e.source_missing,anchor_base_sha256:e.anchor_base_sha256};return e.document_name!==null&&(n.document=e.document_name),e.opened_at!==null&&(n.opened_at=e.opened_at),e.submit_requested_at!==null&&(n.submit_requested_at=e.submit_requested_at),e.approved_at!==null&&(n.approved_at=e.approved_at),e.changed!==null&&(n.changed=e.changed),e.comments_total!==null&&(n.comments_total=e.comments_total),e.comments_unresolved!==null&&(n.comments_unresolved=e.comments_unresolved),e.canceled_at!==null&&(n.canceled_at=e.canceled_at),e.cancel_reason!==null&&(n.cancel_reason=e.cancel_reason),e.cancel_actor!==null&&(n.cancel_actor=e.cancel_actor),t.result_path!==void 0&&(n.result_path=t.result_path),e.delivery_error!==null&&(n.delivery_error=e.delivery_error),e.projection_error!==null&&(n.projection_error=e.projection_error),n}r(m,"toReviewDTO");function Te(e){return{comment_id:e.comment_id,review_id:e.review_id,status:e.status,revision:e.revision,text:e.text,anchor:{...e.anchor},anchor_state:e.anchor_state,anchor_line_text:e.anchor_line_text,anchor_doc_hash:e.anchor_doc_hash,author:{kind:e.author.kind,...e.author.node_id===null?{}:{node_id:e.author.node_id}},updated_by:{kind:e.updated_by.kind,...e.updated_by.node_id===null?{}:{node_id:e.updated_by.node_id}},created_at:e.created_at,updated_at:e.updated_at,...e.resolved_at===null?{}:{resolved_at:e.resolved_at},...e.deleted_at===null?{}:{deleted_at:e.deleted_at},created_seq:e.created_seq}}r(Te,"toReviewCommentDTO");function B(e){return{id:e.id,quote:e.quote,...e.context===void 0?{}:{context:e.context},note:e.note,status:e.status,created_at:e.createdAt,...e.resolvedAt===void 0?{}:{resolved_at:e.resolvedAt}}}r(B,"toPageFeedbackCommentDTO");function Ee(e){return{companion_node_id:e.companionNodeId??null,open_count:e.comments.filter(t=>t.status==="open").length,comments:e.comments.map(B)}}r(Ee,"toPageFeedbackDTO");function Se(e){return{seq:e.seq,review_id:e.review_id,comment_id:e.comment_id,kind:e.kind,state_change:e.state_change===1,revision_after:e.revision_after,actor:{kind:e.actor.kind,...e.actor.node_id===null?{}:{node_id:e.actor.node_id}},at:e.at,operation_id:e.operation_id,...e.payload===void 0?{}:{payload:e.payload}}}r(Se,"toReviewCommentEventDTO");function Ae(e){return{file:e.file,submitted:e.submitted,approved:e.approved,comments:e.comments.map(n=>({...n})),submittedAt:e.submittedAt,savedAt:e.savedAt,reviewId:e.review_id,changed:e.changed,baselineSha256:e.baseline_sha256,approvedSha256:e.approved_sha256,companionNodeId:e.companion_node_id,commentsTotal:e.comments_total,commentsUnresolved:e.comments_unresolved}}r(Ae,"toFeedbackResultDTO");const L=!0,W=Object.freeze({review_origin_missing:{status:400,code:"review_origin_unavailable"},review_origin_unforkable:{status:400,code:"review_origin_unavailable"},review_branch_unavailable:{status:409,code:"review_branch_unavailable"},branch_lost:{status:409,code:"review_branch_unavailable"},review_companion_unbound:{status:409,code:"review_companion_unbound"},review_bridge_unsubscribed:{status:409,code:"review_bridge_unavailable"},review_target_unreviewable:{status:400,code:"review_file_unreadable"},source_unreadable:{status:409,code:"review_source_unreadable"},delivery_unroutable:{status:409,code:"review_delivery_unroutable"},unknown_review:{status:404,code:"unknown_review"},review_terminal:{status:409,code:"review_terminal_conflict"},review_required:{status:400,code:"review_required"},stale_document:{status:409,code:"stale_document"},stale_revision:{status:409,code:"stale_revision"},unknown_comment:{status:404,code:"unknown_comment"},comment_deleted:{status:409,code:"comment_deleted"},invalid_anchor:{status:400,code:"invalid_anchor"},anchor_out_of_range:{status:400,code:"anchor_out_of_range"},text_too_long:{status:400,code:"text_too_long"},comment_limit_reached:{status:409,code:"comment_limit_reached"},stale_comment:{status:409,code:"stale_comment"},range_batch_incomplete:{status:409,code:"range_batch_incomplete"},range_batch_stale:{status:409,code:"range_batch_stale"}});function $(e){const t=v(e);if(typeof t=="object"&&t!==null&&typeof t.status=="number"&&typeof t.code=="string")return{status:t.status,code:t.code};if(t instanceof R)return t.code==="unknown_node"?{status:404,code:"node_not_found"}:t.code==="already_finalized"?{status:409,code:"already_finalized"}:{status:409,code:"finalization_forbidden"};if(t instanceof z)return{status:409,code:"node_id_exists"};if(t instanceof N)return{status:403,code:"scope_denied"};if(C(t))return{status:400,code:"invalid_request"};if(t instanceof _)return W[t.code];if(t instanceof s)return["kind_owned","name_taken","hop_limit","not_a_queue"].includes(t.code)?{status:409,code:t.code}:t.code==="invalid_publish_token"?{status:401,code:t.code}:t.code==="forbidden"||t.code==="argv_forbidden"||t.code==="scope_missing"?{status:403,code:t.code}:{status:H(t.exitCode),code:t.code};if(t instanceof Error){const n=t.message;if(/unknown node/i.test(n))return{status:404,code:"node_not_found"};if(/finalization latch/i.test(n))return{status:409,code:"already_finalized"};if(/illegal lifecycle transition/i.test(n))return{status:409,code:"illegal_transition"}}return{status:500,code:"internal"}}r($,"errorToStatus");function v(e){if(!(e instanceof j))return e;const t=e.code==="unknown_node"?o.NOT_FOUND:o.GENERAL,n={error:e.code,message:e.message,next:e.next};return e.detail!==void 0&&(n.received=e.detail),new s(e.code,e.message,t,n)}r(v,"normalizeThrow");function K(e){if(e.code!=="review_terminal")return e.detail;const{review:t,...n}=e.detail;return{...n,review:V(t)}}r(K,"reviewErrorDetails");function V(e){return e.state!=="approved"?m(e):m(e,{result_path:h(e.review_dir,"result.json")})}r(V,"terminalConflictReviewDTO");function H(e){switch(e){case o.NOT_FOUND:return 404;case o.USAGE:return 400;case o.AMBIGUOUS:return 400;case o.NETWORK:return 503;case o.GENERAL:return 400;default:return 500}}r(H,"statusForExitCode");function J(e){const n=(e instanceof Error?e.message:String(e)).split(`
1
+ var g=Object.defineProperty;var r=(e,t)=>g(e,"name",{value:t,configurable:!0});import{join as h}from"node:path";import{readOutputResult as b}from"../../core/runtime/structured-output.js";import{ReviewOperationError as _}from"../../core/review/types.js";import{cronSinkDisplay as y}from"../../core/canvas/crons.js";import{isProfilePaused as c,profileDir as x,sanitizeProfileMetadata as O}from"../../core/profiles/manifest.js";import{nodeMetaPath as D,transcriptPath as T,viewSocketPath as E}from"../../core/canvas/paths.js";import{appForNode as f,contextDir as S,nodeAgentDir as A}from"../../core/canvas/node-agent-paths.js";import{FinalizationError as R}from"../../core/feed/feed.js";import{NodeIdConflictError as z}from"../../core/runtime/nodes.js";import{ScopeDeniedError as N}from"../../core/scopes.js";import{isBeneathRefusal as C}from"../../core/spaces/open-beneath.js";import{CrtrError as s}from"../../core/errors.js";import{WorktreeError as j}from"../../core/worktree.js";import{ApiError as q}from"../../api/errors.js";import{RunError as l}from"../../core/runs/operations.js";import{ExitCode as o}from"../../types.js";function ce(e,t){const{report:n}=e;return{stores:n.stores.map(i=>({root:i.root,owner:{...i.owner},docs:i.docs,...i.skipped===void 0?{}:{skipped:i.skipped}})),counts:n.counts,rewrites:n.rewrites,dropped_fields:n.droppedFields,skipped:n.skipped,resolutions:n.resolutions.map(i=>({kind:i.kind,message:i.message,paths:i.paths})),blockers:e.blockers.map(i=>({kind:i.kind,message:i.message,paths:i.paths})),dryRun:e.dryRun,pending:e.pending,resumed:t.resumed,...t.resumeError===void 0?{}:{resume_error:t.resumeError}}}r(ce,"toMigrateStateDTO");function F(e,t){try{return b(e,t)??null}catch{return null}}r(F,"readStructuredResult");function I(e){return e.outcome_at===null||e.outcome_at===void 0||e.outcome_kind===null||e.outcome_kind===void 0||e.outcome_reason===null||e.outcome_reason===void 0?null:{kind:e.outcome_kind,reason:e.outcome_reason,settled_at:e.outcome_at}}r(I,"toNodeOutcomeSummaryDTO");function p(e){if(e.outcome_at===null||e.outcome_at===void 0||e.outcome_kind===null||e.outcome_kind===void 0||e.outcome_reason===null||e.outcome_reason===void 0)return null;let t=null;if(e.outcome_detail_json!==null&&e.outcome_detail_json!==void 0)try{t=JSON.parse(e.outcome_detail_json)}catch{t=null}const n=e.final_report??null,i={node_id:e.node_id,revision:e.outcome_revision??1,settled_at:e.outcome_at,final_report:n===null?null:`${e.node_id}/${n}`,structured_result:e.outcome_kind==="result"?F(e.node_id,e.grantee):null,detail:t};return e.outcome_kind==="result"?{...i,kind:"result",reason:e.outcome_reason,declined:null}:e.outcome_reason==="declined"?{...i,kind:"failure",reason:"declined",declined:t?.declined??null}:{...i,kind:"failure",reason:e.outcome_reason,declined:null}}r(p,"toNodeOutcomeDTO");function fe(e){const t=p(e);return t===null||t.reason==="parked"?null:t.kind==="result"||t.reason==="finished"?{...t,kind:"result",reason:"finalized",declined:null}:t.reason==="declined"?{...t,reason:"declined",details:t.declined}:t.reason==="deadline_exceeded"?{...t,reason:"deadline_exceeded",declined:null}:t.reason==="cap_exhausted"?{...t,reason:"cap_exhausted",declined:null}:t.reason==="closed"||t.reason==="stranded"||t.reason==="retired"?{...t,reason:"canceled",declined:null}:{...t,reason:"runtime_error",declined:null,details:{crouter_reason:t.reason}}}r(fe,"toRunOutcomeDTO");function pe(e){const t=e.lastFailure;return{node_id:e.nodeId,state:e.state,action:e.actionName,attempt:e.attempt,next_attempt_at:e.nextAttemptAt===0?null:new Date(e.nextAttemptAt).toISOString(),accepted_at:e.acceptedAt===null?null:new Date(e.acceptedAt).toISOString(),permanent_failed_at:e.permanentFailedAt===null?null:new Date(e.permanentFailedAt).toISOString(),last_failure:t===null?null:{kind:t.kind,...t.exitCode===void 0?{}:{exit_code:t.exitCode},...t.signal===void 0?{}:{signal:t.signal},...t.message===void 0?{}:{message:t.message}},created_at:new Date(e.createdAt).toISOString(),updated_at:new Date(e.updatedAt).toISOString()}}r(pe,"toOutcomeDeliveryDTO");function me(e){return{node_id:e.node_id,name:e.name,description:e.description,cycles:e.cycles,kind:e.kind,status:e.status,frozen_at:e.frozen_at,pi_pid:e.pi_pid,telemetry_context_tokens:e.telemetry_context_tokens,telemetry_last_activity:e.telemetry_last_activity,fault:e.fault&&e.fault.link!=="broker\u2194crtrd"?{...e.fault,link:e.fault.link}:null,streaming:e.streaming}}r(me,"toGraphNodeDTO");function ve(e,t=c(e.profile_id)){return{node_id:e.node_id,name:e.name,description:e.description,cycles:e.cycles,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind,profile_id:e.profile_id,scopes:e.scopes,isolated:e.isolated,grantee:e.grantee??f(e.node_id),profile_paused:t,parent:e.parent,created:e.created,intent:e.intent,waiting_for:e.waiting_for,frozen_at:e.frozen_at??null,terminal_reason:e.terminal_reason??null,pi_pid:e.pi_pid,pi_pid_identity:e.pi_pid_identity,window:e.window,tmux_session:e.tmux_session,pane:e.pane,final_report:e.final_report??null,finalized_at:e.finalized_at??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,outcome:I(e),fault:e.fault&&e.fault.link!=="broker\u2194crtrd"?{...e.fault,link:e.fault.link}:null,streaming:e.streaming??!1}}r(ve,"toNodeSummaryDTO");function P(e,t){return{parent:e.parent??null,spawned_by:e.spawned_by??null,subscribes_to:t.subscribesTo.map(n=>n.node_id),subscribers:t.subscribers.map(n=>n.node_id),children:[...t.children]}}r(P,"toNodeEdgesDTO");function U(e,t){return{node_dir:A(e,t),context_dir:S(e,t),meta_path:D(e),transcript_path:T(e),view_socket:E(e)}}r(U,"toNodePathsDTO");function G(e){const t={state:e.state,path:e.path,branch:e.branch,repo_root:e.repo_root,base_ref:e.base_ref,base_sha:e.base_sha,created:e.created};return e.cleanup!==void 0&&(t.cleanup=e.cleanup),e.closed!==void 0&&(t.closed=e.closed),e.abandoned!==void 0&&(t.abandoned={...e.abandoned}),t}r(G,"toNodeWorktreeDTO");function ke(e,t){const n=f(e.node_id),i={node_id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,profile_id:e.profile_id??null,scopes:e.scopes??null,isolated:e.isolated===!0,grantee:n,profile_paused:c(e.profile_id),parent:e.parent??null,created:e.created,intent:e.intent??null,waiting_for:e.waiting_for??null,frozen_at:e.frozen_at??null,terminal_reason:e.terminal_reason??null,pi_pid:e.pi_pid??null,pi_pid_identity:e.pi_pid_identity??null,window:e.window??null,tmux_session:e.tmux_session??null,pane:e.pane??null,final_report:e.final_report??null,finalized_at:e.finalized_at??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,outcome:p(e),fault:e.fault&&e.fault.link!=="broker\u2194crtrd"?{...e.fault,link:e.fault.link}:null,streaming:e.streaming??!1,creator:e.creator??null,edges:P(e,t),paths:U(e.node_id,n)};return e.description!==void 0&&(i.description=e.description),e.title!==void 0&&(i.title=e.title),e.icon!==void 0&&(i.icon=e.icon),e.cycles!==void 0&&(i.cycles=e.cycles),i.context_tokens=e.telemetry_context_tokens??e.telemetry_tokens_in??null,e.pi_session_id!==void 0&&(i.pi_session_id=e.pi_session_id??null),i.model_override=e.model_override??null,e.pi_session_file!==void 0&&(i.pi_session_file=e.pi_session_file??null),e.review_binding!==void 0&&(i.review_binding=e.review_binding===null?null:{...e.review_binding.kind===void 0?{}:{kind:e.review_binding.kind},review_id:e.review_binding.review_id,origin_node_id:e.review_binding.origin_node_id,branch_file:e.review_binding.branch_file,target_file:e.review_binding.target_file}),e.managed_worktree!=null&&(i.worktree=G(e.managed_worktree)),i}r(ke,"toNodeDetailDTO");function ge(e,t,n,i){return{node_id:e,revived:t.launch!==void 0,resumed:t.resumed,status:n,outcome:t.outcome,ready:i}}r(ge,"toReviveResultDTO");function he(e){return{root:e.root,closed:[...e.closed],spared:[...e.spared]}}r(he,"toCloseResultDTO");function be(e,t,n){const i={report:e.report,notified:[...e.deliveredTo]};return t!==void 0&&(i.transitioned=t),n!==void 0&&(i.worktree_auto_dropped=!0,i.worktree_auto_dropped_path=n),i}r(be,"toReportResultDTO");function ye(e){return{node_id:e.node_id,active:e.active,created:e.created}}r(ye,"toSubscriptionDTO");function M(e){if(e===null||e==="")return[];try{const t=JSON.parse(e);return typeof t=="object"&&t!==null&&!Array.isArray(t)?Object.keys(t):[]}catch{return[]}}r(M,"envKeys");function xe(e,t){return{cron_id:e.cron_id,name:e.name,created_by:e.created_by,command:e.command,grant_id:e.grant_id,scopes:e.scopes,origin_run_id:e.origin_run_id,isolated:e.isolated,fire_at:e.fire_at,recur:e.recur,tz:e.tz,expires_at:e.expires_at,anchor_node:e.anchor_node,cancel_on_wake:e.cancel_on_wake,cwd:e.cwd,profile:e.profile,scope:e.scope,env_keys:M(e.env_json),run_timeout_s:e.run_timeout_s,overlap:e.overlap,on_output:e.on_output,sink:y(e.sink),tier:e.tier,state:e.state,held:e.held,run_state:e.run_state,last_run:t==null?null:{finished:t.finished,exit_code:t.exit_code,delivered:t.delivered},created:e.created,updated:e.updated}}r(xe,"toCronDTO");function Oe(e){return{run_id:e.run_id,started:e.started,finished:e.finished,duration_ms:e.duration_ms,exit_code:e.exit_code,stdout_head:e.stdout_head,stderr_head:e.stderr_head,delivered:e.delivered}}r(Oe,"toCronRunDTO");function De(e){return{id:e.profileId,app:e.manifest.grantee??"app:terminal",name:e.manifest.name,projects:e.manifest.projects.map(t=>({...t})),delivery_limits:{...e.manifest.delivery_limits},paused_at:e.manifest.paused_at,default_kind:e.manifest.default_kind??"general",metadata:O(e.manifest.metadata),default_dir:null,created_at:e.manifest.created_at,last_used_at:e.manifest.last_used_at,profile_path:x(e.profileId)}}r(De,"toProfileDTO");function m(e,t={}){const n={review_id:e.review_id,origin_kind:e.origin_kind,origin_node_id:e.origin_node_id,review_dir:e.review_dir,companion_node_id:e.companion_node_id,file:e.file,state:e.state,created:e.created,source_missing:e.source_missing,anchor_base_sha256:e.anchor_base_sha256};return e.document_name!==null&&(n.document=e.document_name),e.opened_at!==null&&(n.opened_at=e.opened_at),e.submit_requested_at!==null&&(n.submit_requested_at=e.submit_requested_at),e.approved_at!==null&&(n.approved_at=e.approved_at),e.changed!==null&&(n.changed=e.changed),e.comments_total!==null&&(n.comments_total=e.comments_total),e.comments_unresolved!==null&&(n.comments_unresolved=e.comments_unresolved),e.canceled_at!==null&&(n.canceled_at=e.canceled_at),e.cancel_reason!==null&&(n.cancel_reason=e.cancel_reason),e.cancel_actor!==null&&(n.cancel_actor=e.cancel_actor),t.result_path!==void 0&&(n.result_path=t.result_path),e.delivery_error!==null&&(n.delivery_error=e.delivery_error),e.projection_error!==null&&(n.projection_error=e.projection_error),n}r(m,"toReviewDTO");function Te(e){return{comment_id:e.comment_id,review_id:e.review_id,status:e.status,revision:e.revision,text:e.text,anchor:{...e.anchor},anchor_state:e.anchor_state,anchor_line_text:e.anchor_line_text,anchor_doc_hash:e.anchor_doc_hash,author:{kind:e.author.kind,...e.author.node_id===null?{}:{node_id:e.author.node_id}},updated_by:{kind:e.updated_by.kind,...e.updated_by.node_id===null?{}:{node_id:e.updated_by.node_id}},created_at:e.created_at,updated_at:e.updated_at,...e.resolved_at===null?{}:{resolved_at:e.resolved_at},...e.deleted_at===null?{}:{deleted_at:e.deleted_at},created_seq:e.created_seq}}r(Te,"toReviewCommentDTO");function B(e){return{id:e.id,quote:e.quote,...e.context===void 0?{}:{context:e.context},note:e.note,status:e.status,created_at:e.createdAt,...e.resolvedAt===void 0?{}:{resolved_at:e.resolvedAt}}}r(B,"toPageFeedbackCommentDTO");function Ee(e){return{companion_node_id:e.companionNodeId??null,open_count:e.comments.filter(t=>t.status==="open").length,comments:e.comments.map(B)}}r(Ee,"toPageFeedbackDTO");function Se(e){return{seq:e.seq,review_id:e.review_id,comment_id:e.comment_id,kind:e.kind,state_change:e.state_change===1,revision_after:e.revision_after,actor:{kind:e.actor.kind,...e.actor.node_id===null?{}:{node_id:e.actor.node_id}},at:e.at,operation_id:e.operation_id,...e.payload===void 0?{}:{payload:e.payload}}}r(Se,"toReviewCommentEventDTO");function Ae(e){return{file:e.file,submitted:e.submitted,approved:e.approved,comments:e.comments.map(n=>({...n})),submittedAt:e.submittedAt,savedAt:e.savedAt,reviewId:e.review_id,changed:e.changed,baselineSha256:e.baseline_sha256,approvedSha256:e.approved_sha256,companionNodeId:e.companion_node_id,commentsTotal:e.comments_total,commentsUnresolved:e.comments_unresolved}}r(Ae,"toFeedbackResultDTO");const L=!0,W=Object.freeze({review_origin_missing:{status:400,code:"review_origin_unavailable"},review_origin_unforkable:{status:400,code:"review_origin_unavailable"},review_branch_unavailable:{status:409,code:"review_branch_unavailable"},branch_lost:{status:409,code:"review_branch_unavailable"},review_companion_unbound:{status:409,code:"review_companion_unbound"},review_bridge_unsubscribed:{status:409,code:"review_bridge_unavailable"},review_target_unreviewable:{status:400,code:"review_file_unreadable"},source_unreadable:{status:409,code:"review_source_unreadable"},delivery_unroutable:{status:409,code:"review_delivery_unroutable"},unknown_review:{status:404,code:"unknown_review"},review_terminal:{status:409,code:"review_terminal_conflict"},review_required:{status:400,code:"review_required"},stale_document:{status:409,code:"stale_document"},stale_revision:{status:409,code:"stale_revision"},unknown_comment:{status:404,code:"unknown_comment"},comment_deleted:{status:409,code:"comment_deleted"},invalid_anchor:{status:400,code:"invalid_anchor"},anchor_out_of_range:{status:400,code:"anchor_out_of_range"},text_too_long:{status:400,code:"text_too_long"},comment_limit_reached:{status:409,code:"comment_limit_reached"},stale_comment:{status:409,code:"stale_comment"},range_batch_incomplete:{status:409,code:"range_batch_incomplete"},range_batch_stale:{status:409,code:"range_batch_stale"}});function $(e){const t=v(e);if(typeof t=="object"&&t!==null&&typeof t.status=="number"&&typeof t.code=="string")return{status:t.status,code:t.code};if(t instanceof R)return t.code==="unknown_node"?{status:404,code:"node_not_found"}:t.code==="already_finalized"?{status:409,code:"already_finalized"}:{status:409,code:"finalization_forbidden"};if(t instanceof z)return{status:409,code:"node_id_exists"};if(t instanceof N)return{status:403,code:"scope_denied"};if(C(t))return{status:400,code:"invalid_request"};if(t instanceof _)return W[t.code];if(t instanceof s)return["kind_owned","name_taken","hop_limit","not_a_queue"].includes(t.code)?{status:409,code:t.code}:t.code==="invalid_publish_token"?{status:401,code:t.code}:t.code==="forbidden"||t.code==="argv_forbidden"||t.code==="scope_missing"?{status:403,code:t.code}:{status:H(t.exitCode),code:t.code};if(t instanceof Error){const n=t.message;if(/unknown node/i.test(n))return{status:404,code:"node_not_found"};if(/finalization latch/i.test(n))return{status:409,code:"already_finalized"};if(/illegal lifecycle transition/i.test(n))return{status:409,code:"illegal_transition"}}return{status:500,code:"internal"}}r($,"errorToStatus");function v(e){if(!(e instanceof j))return e;const t=e.code==="unknown_node"?o.NOT_FOUND:o.GENERAL,n={error:e.code,message:e.message,next:e.next};return e.detail!==void 0&&(n.received=e.detail),new s(e.code,e.message,t,n)}r(v,"normalizeThrow");function K(e){if(e.code!=="review_terminal")return e.detail;const{review:t,...n}=e.detail;return{...n,review:V(t)}}r(K,"reviewErrorDetails");function V(e){return e.state!=="approved"?m(e):m(e,{result_path:h(e.review_dir,"result.json")})}r(V,"terminalConflictReviewDTO");function H(e){switch(e){case o.NOT_FOUND:return 404;case o.USAGE:return 400;case o.AMBIGUOUS:return 400;case o.NETWORK:return 503;case o.GENERAL:return 400;default:return 500}}r(H,"statusForExitCode");function J(e){const n=(e instanceof Error?e.message:String(e)).split(`
2
2
  `,1)[0].trim();return n===""?"internal error":n.length>500?`${n.slice(0,497)}\u2026`:n}r(J,"boundedCause");function Re(e){const t=v(e),{status:n,code:i}=$(t);if(n>=500){const a={error:{code:i,message:J(t)}};return t instanceof l&&(a.error.origin="runtime",a.error.retryable=t.retryable),{status:n,body:a}}const k=t instanceof Error?t.message:String(t),u=t instanceof _?K(t):(t instanceof s||t instanceof q||t instanceof l)&&t.details!==void 0?t.details:void 0,d={error:{code:i,message:k}};return u!==void 0&&(d.error.details=u),t instanceof l&&(t.param&&(d.error.param=t.param),t.scope&&(d.error.scope=t.scope),d.error.origin="runtime",d.error.retryable=t.retryable),{status:n,body:d}}r(Re,"toErrorBody");export{W as REVIEW_ERROR_MAP,$ as errorToStatus,he as toCloseResultDTO,xe as toCronDTO,Oe as toCronRunDTO,Re as toErrorBody,Ae as toFeedbackResultDTO,me as toGraphNodeDTO,ce as toMigrateStateDTO,ke as toNodeDetailDTO,P as toNodeEdgesDTO,p as toNodeOutcomeDTO,I as toNodeOutcomeSummaryDTO,U as toNodePathsDTO,ve as toNodeSummaryDTO,G as toNodeWorktreeDTO,pe as toOutcomeDeliveryDTO,B as toPageFeedbackCommentDTO,Ee as toPageFeedbackDTO,De as toProfileDTO,be as toReportResultDTO,Te as toReviewCommentDTO,Se as toReviewCommentEventDTO,m as toReviewDTO,ge as toReviveResultDTO,fe as toRunOutcomeDTO,ye as toSubscriptionDTO};
@@ -97,6 +97,23 @@ await oauth.refreshIdleConnections({olderThanMs: 30 * 24 * 60 * 60 * 1000});
97
97
 
98
98
  It refreshes every connection last saved before the cutoff, through the store's lock, and returns `{refreshed, refused, failures}`. A refused connection goes through `onRefusal` and is removed. `failures` holds connections that could not be refreshed and are still stored; the next run retries them. The SDK starts no timer. The store needs `listStale(before)`, which both `MemoryConnectionStore` and `PostgresConnectionStore` have.
99
99
 
100
+ ## Acting without a sign-in
101
+
102
+ A server that acts for a person on their hosted runtime, for example to publish events to the objects they watch, uses `oauth.appClient(userId)` instead of `oauth.client(userId)`. It stores no connection and refreshes nothing: the SDK signs a `client_credentials` request with your app's key for `sub=user:<id>`, and the directory answers a token and the person's `runtimeUrl`.
103
+
104
+ ```ts
105
+ const runtime = await oauth.appClient('user:o1');
106
+ await runtime.customObjects.publish(ref, {event: 'changed', body: '…'});
107
+ ```
108
+
109
+ It needs the app registered with the `app_token_for_user` operation, and an active grant from the person. The token is cached per person until a minute before it expires, the client mints a new one on expiry and once after a `401`, and concurrent callers share one mint. It returns the same `AppClient` as `oauth.client`, so `customObjects` (`register`, `publish`, `unregister`) and `canvas` (`watch`, `unwatch`) are available and the owner-daemon namespaces are not.
110
+
111
+ | Error | Code | Means |
112
+ |---|---|---|
113
+ | `NoHostedRuntimeError` | `no_hosted_runtime` (409) | The person has no hosted runtime. Not retryable. |
114
+ | `NotGrantedError` | `not_granted` (409) | The person has no active grant for your app, so they have not signed in to it. Not retryable. |
115
+ | any error with `retryable: true` | `runtime_provisioning` (503), a directory 5xx or a 429 | Try again later. |
116
+
100
117
  ## Signing in a visitor
101
118
 
102
119
  A person who only needs to be recognized, not connected, signs in with identity only: request `openid`, optionally with `email` and `profile`, and no runtime scope. Finish with `exchangeIdentity`, not `exchangeCode`. An `Identity` is not a connection: no runtime URL, grant or refresh token, nothing goes in the store, and you keep your own session. To act for that person later, send them through `authorizeUrl` again with the runtime scopes you need.
@@ -20,6 +20,8 @@ Codes the SDK raises itself are listed in `sdkErrorCodes`, and `CrouterError.cod
20
20
  | `oauth_nonce_missing` | `exchangeIdentity` was called without the `nonce` from `authorizeUrl`. | Save the nonce in the visitor's session and pass it back. |
21
21
  | `oauth_state_mismatch` | The callback's `state` was missing or differed from the saved one. | Refuse the callback. |
22
22
  | `not_connected` | `oauth.client(userId)` or a refresh found no stored connection for that person. | Show "sign in again". |
23
+ | `no_hosted_runtime` (409, `NoHostedRuntimeError`) | `oauth.appClient(userId)`: the person has no hosted runtime to act on. Not retryable. | Skip them, or ask them to set up a hosted runtime. |
24
+ | `not_granted` (409, `NotGrantedError`) | `oauth.appClient(userId)`: the person has no active grant for your app. Not retryable. | Send them through sign-in ([Connections](/docs/concepts/connections#acting-without-a-sign-in)). |
23
25
  | `refusal_handler_reentry` | An `onRefusal` handler called `oauth.client` (or a refresh) for the person whose connection it is handling. | Don't call the runtime for `refusal.userId` inside the handler. |
24
26
  | `unauthorized` (401, `AuthenticationError`) | The directory refused your app's client assertion (`invalid_client`) on `exchangeCode`, a refresh or `revoke`, including a refresh inside a client from `oauth.client(userId)`. The message names the key id (`kid`) you signed with and asks whether that key's public JWK is registered. `details` is `{oauth_error: 'invalid_client', kid}`, or `key_thumbprint` instead of `kid` for a key with no `kid`. | Check that the key's public JWK is on the app's **Sign-in** tab ([Manage your app](/docs/guides/manage-your-app)). |
25
27
  | `run_idle`, `waiting_on_user` | `runs.parse`: the run stopped without submitting, or waits on a question and you passed no `onQuestion`. | See [Typed results](/docs/guides/typed-results#stopping-without-a-result-run_idle). |
@@ -116,6 +116,28 @@ Crtr-Caller: node=<id>; machine=<id>; profile=<id>; run=<id>
116
116
 
117
117
  The fields appear in that order. A field with no value is left out, and a call with none omits the header. A service uses `node` to link the calling node to an object it owns. The value comes from crtrd's view of the caller; a manifest cannot set `Crtr-Caller`, `Crtr-Signature` or `Crtr-Request-Id`.
118
118
 
119
+ ### Object kinds and the calling node
120
+
121
+ A plugin written with the kit declares canvas object kinds in `definePlugin({objectKinds})` instead of writing `object_kinds` by hand. Each key is the kind's name relative to the plugin (`repo`), lowercase letters, digits and `-`, with no `.`:
122
+
123
+ ```ts
124
+ definePlugin({
125
+ name: 'github',
126
+ objectKinds: {
127
+ repo: {
128
+ description: 'A GitHub repository',
129
+ events: { 'pr-opened': { level: 5 }, 'ci-failed': { level: 8, supersedes: true } },
130
+ read: { command: ['repo', 'read'] },
131
+ },
132
+ },
133
+ commands: { /* … */ },
134
+ });
135
+ ```
136
+
137
+ The served `bundle.json` carries `object_kinds` with each key prefixed `<providerId>.<name>`: `providerId` is the `auth.providerId` the handler was given, else the plugin name, the same value used in scope names. `definePlugin` throws `PluginDefinitionError` for a name with a `.` or capitals, a blank description, more than 64 events, a level outside 1-10, or a `read.command` that is not a non-streaming leaf of the plugin. Building the bundle throws when the prefixed kind is one crouter would refuse (a provider id with capitals or `_`, or a reserved first segment such as `repo` or `report`).
138
+
139
+ Every leaf's `ctx.caller.crtr` carries the calling node, parsed from `Crtr-Caller` by `parseCrtrCaller(header)` (also exported): `{ node?, machine?, profile?, run? }`, each field present only when the header carried it, and `crtr` itself absent when it carried none. **The header is asserted by the calling runtime, not verified:** the bearer token does not cover it. It tells you which of the verified `sub`'s nodes made the call, for example to link that node to an object. Never authorize on it; authorize on `ctx.caller.sub`.
140
+
119
141
  ### `Crtr-Signature`
120
142
 
121
143
  crtrd signs a call to a local plugin when the plugin holds a token and the request goes to the origin of its installed endpoint. Any other URL gets `Crtr-Caller` and no signature.
@@ -54,7 +54,7 @@ There is one request path. The client issues every request through the Web `fetc
54
54
 
55
55
  ## Autostart
56
56
 
57
- With `autostart` on (the default for a socket), a request that finds a cold socket runs `crtr sys daemon start`, waits for the daemon to serve, and retries the request once. This is what makes `npm i -g crouter` followed by `new Crouter()` work on a machine that has never run the daemon.
57
+ With `autostart` on (the default for a socket), a request that finds a cold socket runs `crtr sys daemon start`, waits for the daemon to serve, and retries the request once. This is what makes `npm i -g @north-light/cli` followed by `new Crouter()` work on a machine that has never run the daemon.
58
58
 
59
59
  Autostart is Node-only and applies only to the socket transport. A `baseURL` client cannot start a daemon it may not even share a machine with, so `autostart` defaults to `false` there; setting it to `true` on a `baseURL` client throws `CrouterError` (`autostart is only valid for the local socket transport`).
60
60
 
@@ -7,6 +7,40 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
7
7
 
8
8
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
9
9
 
10
+ ## 0.3.493
11
+
12
+ An app can act on a person's hosted runtime with no sign-in of theirs, and `@north-light/plugin` can declare canvas object kinds. Everything below is additive: no request or response an existing app sends or reads changes.
13
+
14
+ ### `oauth.appClient(userId)`: the app acts on a person's hosted runtime (additive)
15
+
16
+ `await oauth.appClient('user:<id>')` returns a client on that person's hosted runtime without a stored connection or refresh token. The SDK signs a `client_credentials` request with the `OAuth2Client`'s key (`private_key_jwt`) for `sub=user:<id>`, and the directory answers a token and the person's `runtimeUrl`. The app must be registered with the `app_token_for_user` operation, and the person must hold an active grant for it. Tokens are cached per person until a minute before they expire, and one mint is in flight per person however many callers ask. The client mints again on expiry and once after a `401`. A `userId` that is not `user:<id>` throws `CrouterError`.
17
+
18
+ - `NoHostedRuntimeError` (`no_hosted_runtime`, status 409, not retryable): the person has no hosted runtime to act on.
19
+ - `NotGrantedError` (`not_granted`, status 409, not retryable): the person has no active grant for the app, so they have not signed in to it.
20
+ - A runtime still provisioning (503), a directory 5xx and a 429 throw an error with `retryable: true`. `invalid_client` is a `401` `unauthorized` that names the signing key.
21
+
22
+ `no_hosted_runtime` and `not_granted` are appended to `sdkErrorCodes`, so the codes already in the list keep their positions. `NoHostedRuntimeError` and `NotGrantedError` are exported from `@north-light/sdk`. See [Errors](/docs/errors) and [Connections](/docs/concepts/connections#acting-without-a-sign-in).
23
+
24
+ ### `customObjects` and `canvas` on `AppClient` (additive, type change)
25
+
26
+ `AppClient` (the type of `oauth.client(userId)` and `oauth.appClient(userId)`) now offers two namespaces the app listener already served to an app grant token:
27
+
28
+ - `customObjects.register`, `publish` and `unregister` act on the custom objects your app owns. `receive` and `ack` stay owner-daemon only.
29
+ - `canvas.watch` and `canvas.unwatch` link a node to an object your app owns, under `crtr:objects:link:node`. Pass `watcher` (a node id) and, for a node of another of the account's machines, `watcher_machine`. `unwatch` takes the same `watcher` and `watcher_machine` to remove that node's watch; without `watcher` it removes the app's own watch.
30
+
31
+ Every other `canvas` method and `customObjects.receive`/`ack` remain absent from the type and throw `CrouterError` if reached. `Crouter` is still assignable to `AppClient`.
32
+
33
+ ### `@north-light/plugin`: `objectKinds` and `LeafCaller.crtr` (additive)
34
+
35
+ - `definePlugin({objectKinds})` declares canvas object kinds by name relative to the plugin (`repo`), each `{ description, events: { <name>: { level: 1-10, supersedes? } }, read?: { command: [...] } }`. The bundle the kit serves carries them in `bundle.json` `object_kinds`, keyed `<providerId>.<name>`: `providerId` is the `auth.providerId` the handler was given, else the plugin name. `definePlugin` throws `PluginDefinitionError` for a relative name that is not lowercase letters, digits and `-`, a blank description, more than 64 events, a level outside 1-10, and a `read.command` that is not a non-streaming leaf of the plugin. Building the bundle throws if the prefixed kind is not one crouter accepts (a provider id with capitals or `_`, or one of crouter's reserved roots such as `repo` or `report`).
36
+ - `ctx.caller.crtr` carries the calling node: `{ node?, machine?, profile?, run? }`, parsed by the exported `parseCrtrCaller(header)` from the request's `Crtr-Caller`. It is absent when the header names no field. The runtime asserts that header and the bearer token does not cover it, so use it to tell which of the verified `sub`'s nodes called, never to authorize; authorize on `sub`.
37
+
38
+ See [Local plugins](https://crouter.docs.northlight.ai/docs/plugin/deploying#local-plugins) and [Object kinds and the calling node](https://crouter.docs.northlight.ai/docs/plugin/deploying#object-kinds-and-the-calling-node).
39
+
40
+ ### `nodes.revive` reports `ready` (additive)
41
+
42
+ `ReviveResultDTO` gains an optional `ready`. The daemon checks, in its own namespace, whether the revived node's `view.sock` accepts a connection (up to 10 s, ending early if the launched broker exits) and returns the answer. `true` means the node is attachable; it does not show the task was delivered or the model is making progress. `false` means no connection yet, including `outcome: 'frozen'`. A daemon that predates the field sends none, so treat an absent `ready` as unknown.
43
+
10
44
  ## 0.3.488
11
45
 
12
46
  ### Developer CLI: `--help` exits 0, `login` is safe to re-run (CLI only, no app change)
@@ -38,13 +38,13 @@ Every namespace exported by the client. Namespaces are camelCase. Verbs are `cre
38
38
  | `grant` | `get` (`wait`, `since`: hold until `version` changes) | `GET /v1/grant` |
39
39
  | `health` | `get` | `GET /v1/health` |
40
40
 
41
- `runs`, `uploads`, `attachments`, `shares`, `questions`, `docs` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; `oauth.client(userId)` returns a client typed to exactly that set (`AppClient`); `nodes`, `bash`, `canvas`, `crons`, `human`, `models`, `customObjects`, `structuredRequests`, `system`, `auth`, `files.read/write/list` and `profiles.ensure/retrieve` are the owner's own daemon and are not served to an app. An app schedules work with its own timer calling `runs.start`, or a run's agent runs `crtr cron add` under `crtr:schedule`. 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)). `docs` writes, reads and edits documents the calling app owns: `docs.write({name, body, preview, kind?, rules?})` returns the canonical `name`; `docs.get(name)` returns `body`, `revision`, `rules` and the edit `base`; `docs.edit(name, {body, rationale, base?})` changes it in place and refuses a stale `base` with `ConflictError` (`stale_revision`). To keep one document current, `get` it (catch `NotFoundError` on first run to `write`), compare `body` to your text, and `edit` only when they differ. `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
41
+ `runs`, `uploads`, `attachments`, `shares`, `questions`, `docs` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; `oauth.client(userId)` returns a client typed to exactly that set (`AppClient`); `customObjects` (`register`, `publish`, `unregister`) and `canvas` (`watch`, `unwatch`) are served too, for the custom objects your app owns; `oauth.appClient(userId)` returns the same `AppClient` type without a stored sign-in ([Connections](../apps/concepts/connections.md#acting-without-a-sign-in)). `nodes`, `bash`, `crons`, `human`, `models`, `structuredRequests`, `system`, `auth`, the rest of `canvas` and `customObjects` (`receive`, `ack`), `files.read/write/list` and `profiles.ensure/retrieve` are the owner's own daemon and are not served to an app. An app schedules work with its own timer calling `runs.start`, or a run's agent runs `crtr cron add` under `crtr:schedule`. 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)). `docs` writes, reads and edits documents the calling app owns: `docs.write({name, body, preview, kind?, rules?})` returns the canonical `name`; `docs.get(name)` returns `body`, `revision`, `rules` and the edit `base`; `docs.edit(name, {body, rationale, base?})` changes it in place and refuses a stale `base` with `ConflictError` (`stale_revision`). To keep one document current, `get` it (catch `NotFoundError` on first run to `write`), compare `body` to your text, and `edit` only when they differ. `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
42
42
 
43
43
  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`.
44
44
 
45
45
  ## Custom objects
46
46
 
47
- `client.customObjects` registers an external service or script as an object on the canvas, with a declared event catalog and a queue, URL or argv receiver, and publishes its events to the objects watching it. `register(body)` returns the object and, once, its publish token; `publish(ref, body)` sends one declared event (a custom object may publish with that token alone); `receive(ref, { after?, signal? })` is an async iterator over a queue receiver's deliveries and replays every unacknowledged delivery on each connect; `ack(ref, deliveryIds)` acknowledges them; `unregister(ref)` deletes the object. The same objects are managed from a terminal with `crtr custom-object` and watched with `crtr canvas watch`.
47
+ `client.customObjects` registers an external service or script as an object on the canvas, with a declared event catalog and a queue, URL or argv receiver, and publishes its events to the objects watching it. `register(body)` returns the object and, once, its publish token; `publish(ref, body)` sends one declared event (a custom object may publish with that token alone); `receive(ref, { after?, signal? })` is an async iterator over a queue receiver's deliveries and replays every unacknowledged delivery on each connect; `ack(ref, deliveryIds)` acknowledges them; `unregister(ref)` deletes the object. An app reaches `register`, `publish` and `unregister` on its own objects through `oauth.client(userId)` or `oauth.appClient(userId)`, and links a node to one with `client.canvas.watch(ref, { watcher, watcher_machine? })` and `client.canvas.unwatch(ref, { watcher, watcher_machine? })` under `crtr:objects:link:node` (without `watcher`, `unwatch` removes the app's own watch). The same objects are managed from a terminal with `crtr custom-object` and watched with `crtr canvas watch`.
48
48
 
49
49
  ## Files and bash
50
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.492",
3
+ "version": "0.3.494",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -138,7 +138,7 @@
138
138
  },
139
139
  "license": "UNLICENSED",
140
140
  "dependencies": {
141
- "@north-light/identity": "0.3.492",
141
+ "@north-light/identity": "0.3.494",
142
142
  "@earendil-works/pi-agent-core": "1.1.0",
143
143
  "@earendil-works/pi-ai": "1.1.0",
144
144
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.492",
3
+ "version": "0.3.494",
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.492",
3
+ "version": "0.3.494",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.492",
9
+ "version": "0.3.494",
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.492",
21
+ "@north-light/identity": "0.3.494",
22
22
  "cron-parser": "^5.6.0",
23
23
  "esbuild": "^0.27.7",
24
24
  "jose": "^6.2.12",
@@ -12116,17 +12116,17 @@
12116
12116
  },
12117
12117
  "packages/crouter-api": {
12118
12118
  "name": "@north-light/api",
12119
- "version": "0.3.492",
12119
+ "version": "0.3.494",
12120
12120
  "license": "UNLICENSED"
12121
12121
  },
12122
12122
  "packages/crouter-env-docker": {
12123
12123
  "name": "@north-light/env-docker",
12124
- "version": "0.3.492",
12124
+ "version": "0.3.494",
12125
12125
  "license": "UNLICENSED"
12126
12126
  },
12127
12127
  "packages/crouter-identity": {
12128
12128
  "name": "@north-light/identity",
12129
- "version": "0.3.492",
12129
+ "version": "0.3.494",
12130
12130
  "license": "UNLICENSED",
12131
12131
  "dependencies": {
12132
12132
  "jose": "^6.2.1"
@@ -12134,20 +12134,20 @@
12134
12134
  },
12135
12135
  "packages/crouter-plugin": {
12136
12136
  "name": "@north-light/plugin",
12137
- "version": "0.3.492",
12137
+ "version": "0.3.494",
12138
12138
  "license": "UNLICENSED",
12139
12139
  "dependencies": {
12140
- "@north-light/api": "0.3.492",
12141
- "@north-light/identity": "0.3.492"
12140
+ "@north-light/api": "0.3.494",
12141
+ "@north-light/identity": "0.3.494"
12142
12142
  }
12143
12143
  },
12144
12144
  "packages/crouter-sdk": {
12145
12145
  "name": "@north-light/sdk",
12146
- "version": "0.3.492",
12146
+ "version": "0.3.494",
12147
12147
  "license": "UNLICENSED",
12148
12148
  "dependencies": {
12149
- "@north-light/api": "0.3.492",
12150
- "@north-light/identity": "0.3.492",
12149
+ "@north-light/api": "0.3.494",
12150
+ "@north-light/identity": "0.3.494",
12151
12151
  "jose": "^6.2.1"
12152
12152
  },
12153
12153
  "bin": {