@north-light/cli 0.3.577 → 0.3.578

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/run-events.d.ts +10 -4
  4. package/dist/builtin-memory/crouter-sdk/release-notes.md +8 -0
  5. package/dist/builtin-memory/crouter-sdk/streaming.md +2 -0
  6. package/dist/builtin-memory/northlight-apps/concepts/runs.md +2 -0
  7. package/dist/builtin-memory/northlight-apps/guides/stream-a-run.md +1 -1
  8. package/dist/builtin-memory/northlight-apps/guides/test-your-app.md +1 -1
  9. package/dist/core/llm/refusal.d.ts +2 -1
  10. package/dist/core/llm/refusal.js +1 -1
  11. package/dist/core/runs/operations.d.ts +1 -1
  12. package/dist/core/runs/operations.js +5 -5
  13. package/dist/core/runtime/broker/held-deferred-inbox.d.ts +4 -0
  14. package/dist/core/runtime/broker/held-deferred-inbox.js +1 -1
  15. package/dist/core/runtime/broker/rebind.d.ts +3 -0
  16. package/dist/core/runtime/broker/rebind.js +1 -1
  17. package/dist/core/runtime/broker/run-steer.d.ts +15 -0
  18. package/dist/core/runtime/broker/run-steer.js +1 -0
  19. package/dist/core/runtime/broker/run-turn-queue.d.ts +2 -0
  20. package/dist/core/runtime/broker/run-turn-queue.js +1 -1
  21. package/dist/core/runtime/broker.js +1 -1
  22. package/dist/core/runtime/runner/blaxel/client.d.ts +2 -2
  23. package/dist/core/runtime/runner/blaxel/client.js +1 -1
  24. package/dist/core/runtime/runner/blaxel/runner.js +2 -2
  25. package/dist/daemon/api/handlers/runs.js +1 -1
  26. package/dist/daemon/runs/append-section.js +2 -2
  27. package/dist/pi-extensions/canvas-bash-valve.js +6 -6
  28. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -2
  29. package/docs/apps/concepts/runs.md +2 -0
  30. package/docs/apps/guides/stream-a-run.md +1 -1
  31. package/docs/apps/guides/test-your-app.md +1 -1
  32. package/docs/sdk/release-notes.md +8 -0
  33. package/docs/sdk/streaming.md +2 -0
  34. package/package.json +2 -2
  35. package/packages/crouter-identity/package.json +1 -1
  36. package/runtime.lock.json +162 -665
@@ -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.39";
2
+ export declare const CONTRACT_LEVEL = "1.40";
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.39",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.40",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};
@@ -32,10 +32,13 @@ export type RunActivityDTO = {
32
32
  };
33
33
  };
34
34
  /** Why a turn ended in error: `content_refused` when the vendor refused the
35
- * model call on safety grounds, `model_error` for any other failed call. */
35
+ * model call on safety grounds, `rate_limited` when every model route is
36
+ * rate-limited for longer than a turn waits, `model_error` for any other failed call. */
36
37
  export interface TurnErrorDTO {
37
- code: 'content_refused' | 'model_error';
38
+ code: 'content_refused' | 'model_error' | 'rate_limited';
38
39
  message: string;
40
+ /** ISO time the rate limit is expected to lift; present only when `code` is `rate_limited`. */
41
+ retry_at?: string;
39
42
  }
40
43
  /** The broker's stop reason for one assistant message, unchanged. */
41
44
  export type AssistantStopReasonDTO = 'stop' | 'toolUse' | 'length' | 'error' | 'aborted';
@@ -111,7 +114,10 @@ export interface NodeMessageAcceptedEvent extends RunEventBaseDTO {
111
114
  }
112
115
  export interface NodeTurnStartedEvent extends RunEventBaseDTO {
113
116
  type: 'node.turn.started';
114
- /** `message_ids`: the accepted `run.message` messages this turn delivers (empty for the prompt, an answer, or a child's report). */
117
+ /**
118
+ * `message_ids`: the accepted `run.message` messages this turn delivers (empty for the prompt, an answer, or a child's report).
119
+ * A turn can emit more than one `node.turn.started`: each time a steered `run.message` joins the open turn at the agent's next step, another one arrives with that message's `message_ids` and its own `entry_id`. A single `node.turn.completed` ends the turn and carries every joined id.
120
+ */
115
121
  input: {
116
122
  text: string;
117
123
  message_ids: string[];
@@ -156,7 +162,7 @@ export interface NodeToolCallCompletedEvent extends RunEventBaseDTO, Redactable
156
162
  }
157
163
  export interface NodeTurnCompletedEvent extends RunEventBaseDTO {
158
164
  type: 'node.turn.completed';
159
- /** The same as the turn's `node.turn.started` `input.message_ids`. */
165
+ /** Every `input.message_ids` of the turn's `node.turn.started` events, in order (more than one when messages joined the turn). */
160
166
  message_ids: string[];
161
167
  /** The run's `run.get` status when the turn ended. */
162
168
  run_status: Exclude<RunStatusDTO, 'settled'>;
@@ -9,6 +9,10 @@ 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.578
13
+
14
+ `runs.message` now steers a running root turn by default: the agent reads it at its next step. Pass `{ steer: false }` to wait for the current turn to end and start a separate turn. `start_turn: false` remains deferred and cannot use `steer: true`. When a later message joins a turn, `runs.reply` stops yielding text for the earlier message at that boundary, but still waits for the turn's completion. An app on an older SDK gets steering as soon as the runtime upgrades, because it cannot send `steer: false`; its `runs.reply` then streams the answer to a joined later message as part of the earlier reply. Upgrade the SDK to get the `RunReply` boundary and the opt-out.
15
+
12
16
  ## 0.3.577
13
17
 
14
18
  `POST /v1/nodes` with a pinned `cwd` that is not an existing directory on the runtime's host now answers `cwd_missing` instead of creating a node whose broker then fails to launch. A sandbox, isolated or worktree node is not checked. `POST /v1/nodes/{id}/messages` with `reopen` to a dormant node whose recorded cwd is gone now answers `cwd_missing` before the reopen is committed or any mail is queued, instead of accepting the message and reporting `wake_failed`.
@@ -17,6 +21,10 @@ Changes to `@north-light/sdk` that can change what an existing app sees. Newest
17
21
 
18
22
  On a cloud role, `provider.tools` now reports a granted provider as `available` once its bundle is installed in the deployment's shared plugin directory. It no longer answers `500` when a person has no materialized bundle; a granted provider still being installed reports `installing` or `install_failed`. Providers without that person's grant remain hidden. The Mac's plugin settings and provider tools behavior are unchanged.
19
23
 
24
+ ## Next release
25
+
26
+ A turn that ends because every model route is rate-limited for longer than a turn waits now reports `error.code: 'rate_limited'` with `error.retry_at` (ISO time the limit is expected to lift), on `turn.completed` and `runs.get().last_turn_error`. It was `model_error` with the time only inside `message`. The run stays `idle` and usable, as before. An app that matches `error.code` exhaustively should handle the new value; one that treats any other code as a model error needs no change.
27
+
20
28
  ## 0.3.572
21
29
 
22
30
  `POST /v1/runs` works on a cloud role (a runtime started with `--role api` or `--role reconcile`). It answered `500 server_failure` because the role has no root launcher and the run start tried to prepare an app OS user and folder. Behavior on a person's Mac and on a hosted guest with a launcher is unchanged.
@@ -70,6 +70,8 @@ Every event except `error` carries `node_id` and `sequence_number` in addition t
70
70
  | `node.settled` | `{ node_id, sequence_number, outcome }` — terminal; the daemon ends the response after it |
71
71
  | `error` | `{ error: { code: 'stream_gap' \| 'stream_dropped' \| 'stream_error', message, details?: { earliest_sequence?: number } } }` — `stream_gap` is recoverable; the other codes terminate the response |
72
72
 
73
+ A turn can emit more than one `node.turn.started`. A `run.message` sent while the turn is running steers by default, so the agent reads it at its next step and the stream emits another `node.turn.started` whose `input.message_ids` holds the joined message; the turn ends with one `node.turn.completed` whose `message_ids` lists every message that joined it. A reply to one message therefore ends at the turn's completion, not at the next `node.turn.started`; the SDK's `RunReply` stops yielding text once a later message joins. Send `{ steer: false }` to start a separate turn after the current one ends.
74
+
73
75
  `summary` on a tool-call event states whether arguments are null, an array and its item count, an object and its field count, or a primitive type. It never includes argument values or tool output.
74
76
 
75
77
  **Deliberately not carried:** thinking deltas, the model's tool-call construction deltas, raw tool output, and the system prompt. Those belong to the owner's viewer, not to an application's run stream.
@@ -33,6 +33,8 @@ const run = await runtime.runs.wait(started.run_id); // status is 'idle' once th
33
33
 
34
34
  `runs.wait` returns on `waiting_on_user`, `idle` and `settled`, so check the status before you read a result.
35
35
 
36
+ A turn that fails on a model error leaves the run `idle`, not `settled`, with `last_turn_error` set (`{code, message}`); the same error is on that turn's `turn.completed` event. The run stays usable, and a retry is a `runs.message`. When `code` is `rate_limited`, every model route is rate-limited and `retry_at` (an ISO time) says when the limit is expected to lift: send the retry after `retry_at`, or `runs.cancel` the run, rather than wait for its deadline. A message sent before `retry_at` fails the same way.
37
+
36
38
  ## Outcome and result
37
39
 
38
40
  A `settled` run has an `outcome`:
@@ -44,7 +44,7 @@ A chat app sends a message and shows the answer to it. `runs.reply(runId, sent,
44
44
  const sent = await runtime.runs.message(runId, 'What did the report say?');
45
45
  for await (const event of runtime.runs.reply(runId, sent)) {
46
46
  if (event.type === 'text.delta') append(event.delta);
47
- if (event.type === 'turn.completed' && event.error) showError(event.error); // {code: 'content_refused' | 'model_error', message}
47
+ if (event.type === 'turn.completed' && event.error) showError(event.error); // {code: 'content_refused' | 'model_error' | 'rate_limited', message, retry_at?}
48
48
  }
49
49
  // or collect it: text as it streams, then the whole text and how the turn ended
50
50
  const {text, ended, error, outcome} = await runtime.runs.reply(runId, sent).collect({onDelta: (delta) => append(delta)});
@@ -62,7 +62,7 @@ A responder gets `{run, kind: 'start' | 'message', messages}`. It is called afte
62
62
  | Method | Effect |
63
63
  |---|---|
64
64
  | `reply(text, {chunks = 2, messageIds?})` | One turn: `node.turn.started`, text deltas, `node.output_text.done`, `node.turn.completed`. Delivers `messageIds` (default: every pending message). The run goes `idle` (`waiting_on_user` with an open question). |
65
- | `failTurn({code: 'model_error' \| 'content_refused', message})` | The same turn ending `stop_reason: 'error'`; `runs.get` shows `last_turn_error`; `runs.reply(...).collect()` ends `error`. |
65
+ | `failTurn({code: 'model_error' \| 'content_refused' \| 'rate_limited', message, retry_at?})` | The same turn ending `stop_reason: 'error'`; `runs.get` shows `last_turn_error`; `runs.reply(...).collect()` ends `error`. `retry_at` (ISO) is required with `rate_limited` and refused with any other code. |
66
66
  | `setActivity(activity, {at?})` | Fake-only: status `running` with this `activity` until the next event. The only way to reach `writing` or a streaming tool call. |
67
67
  | `settle(ending)` | `{result}`, `{declined: {reason, code, retryable}}`, `{failure: 'canceled' \| 'deadline_exceeded'}`, or `{runtimeError: reason}`. Emits `node.settled` with the outcome the runtime writes. |
68
68
  | `idle()` | Goes idle without a new turn (stopped without submitting). |
@@ -6,8 +6,9 @@ export declare function isSafetyRefusal(message: {
6
6
  } | undefined): boolean;
7
7
  /** The error a run's turn reports when its last assistant message stopped with `error`. */
8
8
  export type TurnError = TurnErrorDTO;
9
+ /** `rateLimitedUntil`: epoch ms the daemon's final rate-limit refusal said the routes free up; it makes the error `rate_limited`. */
9
10
  export declare function turnErrorOf(message: {
10
11
  stopReason?: string;
11
12
  errorMessage?: string;
12
13
  rawStopReason?: string;
13
- }): TurnError | undefined;
14
+ }, rateLimitedUntil?: number): TurnError | undefined;
@@ -1 +1 @@
1
- var t=Object.defineProperty;var r=(e,n)=>t(e,"name",{value:n,configurable:!0});const o=new Set(["refusal","sensitive","content_filter","incomplete.content_filter","content_filtered","guardrail_intervened","SAFETY","PROHIBITED_CONTENT","BLOCKLIST","SPII","RECITATION"]);function i(e){return e?.rawStopReason!==void 0&&o.has(e.rawStopReason)}r(i,"isSafetyRefusal");function u(e){if(e.stopReason==="error")return{code:i(e)?"content_refused":"model_error",message:e.errorMessage&&e.errorMessage!==""?e.errorMessage:"model request failed"}}r(u,"turnErrorOf");export{i as isSafetyRefusal,u as turnErrorOf};
1
+ var o=Object.defineProperty;var t=(e,r)=>o(e,"name",{value:r,configurable:!0});const i=new Set(["refusal","sensitive","content_filter","incomplete.content_filter","content_filtered","guardrail_intervened","SAFETY","PROHIBITED_CONTENT","BLOCKLIST","SPII","RECITATION"]);function f(e){return e?.rawStopReason!==void 0&&i.has(e.rawStopReason)}t(f,"isSafetyRefusal");function u(e,r){if(e.stopReason!=="error")return;const n=e.errorMessage&&e.errorMessage!==""?e.errorMessage:"model request failed";return r!==void 0?{code:"rate_limited",message:n,retry_at:new Date(r).toISOString()}:{code:f(e)?"content_refused":"model_error",message:n}}t(u,"turnErrorOf");export{f as isSafetyRefusal,u as turnErrorOf};
@@ -62,7 +62,7 @@ export declare function listRuns(grantee: string | null, options: {
62
62
  next: string | null;
63
63
  }>;
64
64
  export declare function waitRun(id: string, grantee: string, seconds: number): Promise<Record<string, unknown>>;
65
- export declare function messageRun(root: NodeMeta, original: string, resolveAttachments?: AttachmentResolver, startTurn?: boolean, idempotencyKey?: string): Promise<{
65
+ export declare function messageRun(root: NodeMeta, original: string, resolveAttachments?: AttachmentResolver, startTurn?: boolean, idempotencyKey?: string, steer?: boolean): Promise<{
66
66
  run_id: string;
67
67
  message_id: string;
68
68
  sequence_number: number;
@@ -1,14 +1,14 @@
1
- var Me=Object.defineProperty;var c=(e,t)=>Me(e,"name",{value:t,configurable:!0});import{coveredByAny as Ae}from"../../shared/env.js";import{createHash as Q}from"node:crypto";import{ApiError as Ie}from"../../api/errors.js";import{CrtrError as Te}from"../errors.js";import{isCloudRole as Le}from"../canvas/person.js";import{isAbsolute as V,relative as Ce,resolve as ke,sep as xe,join as We}from"node:path";import{SessionManager as Fe}from"@earendil-works/pi-coding-agent";import{covers as P}from"@north-light/identity";import{openDb as B,withCanvasWrite as F}from"../canvas/db.js";import{deleteNodeWithOwned as qe}from"../graph/deletion.js";import{getById as He}from"../graph/objects.js";import{repairRunConversations as Z}from"../canvas/sessions.js";import{getNode as O,nodeFiles as Ue,removeNodeFiles as $e,updateNode as Pe}from"../canvas/canvas.js";import{fullName as Be}from"../canvas/labels.js";import{renameWindow as je}from"../runtime/placement.js";import{liveExecutionForNode as K}from"../canvas/executions.js";import{conversationStore as ee,isConversationNotFound as Je}from"../conversation-store/index.js";import{PARKED_RESIDENT_SQL as Ye}from"../canvas/canvas.js";import{WAKE_MAIL_SQL as Ge}from"../canvas/deliveries.js";import{readNodeRecap as Xe}from"../canvas/node-recap.js";import{enqueueNodeMail as ze}from"../feed/messages.js";import{newestReportBody as Qe}from"../feed/reports.js";import{ensureAppProfile as te,loadExactProfileManifest as ne}from"../profiles/manifest.js";import{stopNodeSandboxes as se}from"../spaces/stop.js";import{BrokerUnreachableError as Ve}from"../runtime/broker-request.js";import{interruptLive as Ze}from"../runtime/interactive-deliver.js";import{isBrokerLive as ae}from"../runtime/model-swap.js";import{newNodeId as Ke}from"../runtime/nodes.js";import{reviveNode as j}from"../runtime/revive.js";import{spawnChildPrepared as et}from"../runtime/spawn.js";import{isPortableModelToken as tt}from"../runtime/launch.js";import{parseDurationMs as nt}from"../wake.js";import{expandModelCandidates as st,parseModelSpec as at}from"../model-routes.js";import{payerOfGrant as J}from"../grants/mirror.js";import{isolatedBrokerLauncherAvailable as it}from"../runtime/host.js";import{blaxelEnvironmentOf as rt}from"../runtime/blaxel-environment.js";import{resolveRunKind as ot,resolveRunModel as dt}from"../runtime/launch-target.js";import{cycleAwareMessages as ct}from"../runtime/session-cycles.js";import{originFor as ie}from"../runtime/stamp/protocol.js";import{readOutputRequest as ut,readOutputResult as re}from"../runtime/output-result.js";import{admitLegacyStructuredOutput as oe}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as mt}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as lt}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as pt}from"../../daemon/runs/observer.js";import{closeNode as de}from"../runtime/close.js";import{parseScope as ft}from"@north-light/identity";import{appendRunEvent as ce,lockRunRoot as _t,subscribeRunEvents as wt}from"./events.js";import{runActivity as gt}from"./activity.js";import{cancelOpenQuestions as ue,openNoticeIds as Et,openQuestionIds as me}from"./questions.js";import{appSpace as Y}from"../layout.js";import{ensureAppSpace as yt}from"../spaces/space.js";import{assertUploadsUsable as le,resolveStorageUrls as ht}from"../storage/attachments.js";import{storageClient as Rt,storageConfig as St}from"../storage/client.js";import{deleteUploadRow as Nt,insertUploadUses as pe,releaseRunUploads as vt,runUsesPath as bt}from"../storage/tables.js";import{abortProviderCalls as fe}from"../providers/inflight.js";import{filterAsync as _e,mapAsync as x}from"../async-array.js";const we=c((e,t,s,n)=>ht(t,e,s,n?{taken:n}:{}),"defaultResolver"),Ot=3e3;class R extends Error{static{c(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,s,n,o,m,i,r=t===503){super(n),this.status=t,this.code=s,this.param=o,this.details=m,this.scope=i,this.retryable=r}}const N=c((e,t)=>new R(400,"invalid_request",e,t),"invalid"),G=c(()=>new R(404,"not_found","run not found"),"missing"),X=c(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),A=c(async()=>await B(),"db");async function q(e,t,s=!1){const n=await O(e);if(!n||n.parent!==null||n.run_id!==e||n.grantee!==t||!s&&n.deleting_at)throw G();return n}c(q,"ownedRun");const ge=["waiting_on_user","idle","settled"];async function C(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=await(await A()).prepare(`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
1
+ var Me=Object.defineProperty;var c=(e,t)=>Me(e,"name",{value:t,configurable:!0});import{coveredByAny as Ae}from"../../shared/env.js";import{createHash as Q}from"node:crypto";import{ApiError as Ie}from"../../api/errors.js";import{CrtrError as Te}from"../errors.js";import{isCloudRole as Le}from"../canvas/person.js";import{isAbsolute as V,relative as Ce,resolve as ke,sep as xe,join as We}from"node:path";import{SessionManager as Fe}from"@earendil-works/pi-coding-agent";import{covers as P}from"@north-light/identity";import{openDb as B,withCanvasWrite as F}from"../canvas/db.js";import{deleteNodeWithOwned as qe}from"../graph/deletion.js";import{getById as He}from"../graph/objects.js";import{repairRunConversations as Z}from"../canvas/sessions.js";import{getNode as O,nodeFiles as Ue,removeNodeFiles as $e,updateNode as Pe}from"../canvas/canvas.js";import{fullName as Be}from"../canvas/labels.js";import{renameWindow as je}from"../runtime/placement.js";import{liveExecutionForNode as K}from"../canvas/executions.js";import{conversationStore as ee,isConversationNotFound as Je}from"../conversation-store/index.js";import{PARKED_RESIDENT_SQL as Ye}from"../canvas/canvas.js";import{WAKE_MAIL_SQL as Ge}from"../canvas/deliveries.js";import{readNodeRecap as Xe}from"../canvas/node-recap.js";import{enqueueNodeMail as ze}from"../feed/messages.js";import{newestReportBody as Qe}from"../feed/reports.js";import{ensureAppProfile as te,loadExactProfileManifest as ne}from"../profiles/manifest.js";import{stopNodeSandboxes as se}from"../spaces/stop.js";import{BrokerUnreachableError as Ve}from"../runtime/broker-request.js";import{interruptLive as Ze}from"../runtime/interactive-deliver.js";import{isBrokerLive as ae}from"../runtime/model-swap.js";import{newNodeId as Ke}from"../runtime/nodes.js";import{reviveNode as j}from"../runtime/revive.js";import{spawnChildPrepared as et}from"../runtime/spawn.js";import{isPortableModelToken as tt}from"../runtime/launch.js";import{parseDurationMs as nt}from"../wake.js";import{expandModelCandidates as st,parseModelSpec as at}from"../model-routes.js";import{payerOfGrant as J}from"../grants/mirror.js";import{isolatedBrokerLauncherAvailable as it}from"../runtime/host.js";import{blaxelEnvironmentOf as rt}from"../runtime/blaxel-environment.js";import{resolveRunKind as ot,resolveRunModel as dt}from"../runtime/launch-target.js";import{cycleAwareMessages as ct}from"../runtime/session-cycles.js";import{originFor as ie}from"../runtime/stamp/protocol.js";import{readOutputRequest as ut,readOutputResult as re}from"../runtime/output-result.js";import{admitLegacyStructuredOutput as oe}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as mt}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as lt}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as ft}from"../../daemon/runs/observer.js";import{closeNode as de}from"../runtime/close.js";import{parseScope as pt}from"@north-light/identity";import{appendRunEvent as ce,lockRunRoot as _t,subscribeRunEvents as wt}from"./events.js";import{runActivity as gt}from"./activity.js";import{cancelOpenQuestions as ue,openNoticeIds as Et,openQuestionIds as me}from"./questions.js";import{appSpace as Y}from"../layout.js";import{ensureAppSpace as yt}from"../spaces/space.js";import{assertUploadsUsable as le,resolveStorageUrls as ht}from"../storage/attachments.js";import{storageClient as Rt,storageConfig as St}from"../storage/client.js";import{deleteUploadRow as Nt,insertUploadUses as fe,releaseRunUploads as vt,runUsesPath as bt}from"../storage/tables.js";import{abortProviderCalls as pe}from"../providers/inflight.js";import{filterAsync as _e,mapAsync as x}from"../async-array.js";const we=c((e,t,s,n)=>ht(t,e,s,n?{taken:n}:{}),"defaultResolver"),Ot=3e3;class R extends Error{static{c(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,s,n,d,f,r,i=t===503){super(n),this.status=t,this.code=s,this.param=d,this.details=f,this.scope=r,this.retryable=i}}const N=c((e,t)=>new R(400,"invalid_request",e,t),"invalid"),G=c(()=>new R(404,"not_found","run not found"),"missing"),X=c(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),A=c(async()=>await B(),"db");async function q(e,t,s=!1){const n=await O(e);if(!n||n.parent!==null||n.run_id!==e||n.grantee!==t||!s&&n.deleting_at)throw G();return n}c(q,"ownedRun");const ge=["waiting_on_user","idle","settled"];async function C(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=await(await A()).prepare(`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
2
2
  (n.last_settled_at IS NULL OR EXISTS (
3
3
  SELECT 1 FROM run_events e WHERE e.run_id = ? AND e.node_id = n.node_id AND e.type = 'node.turn.started'
4
4
  AND e.sequence_number > COALESCE((SELECT MAX(c.sequence_number) FROM run_events c
5
5
  WHERE c.run_id = e.run_id AND c.node_id = e.node_id AND c.type = 'node.turn.completed'), 0)
6
6
  )) LIMIT 1`).get(e.node_id,e.node_id),s=!e.last_settled_at&&e.status!=="done";return t||s||await mt(e)?"running":(await me(e.node_id)).length?"waiting_on_user":await(await A()).prepare(`SELECT 1 FROM deliveries d JOIN nodes n ON n.node_id = d.destination_id
7
7
  WHERE n.run_id = ? AND ${Ge("d","'armed', 'pending', 'handed_off'")}
8
- AND (n.status IN ('active', 'idle') OR ${Ye("n")}) LIMIT 1`).get(e.node_id)?"running":"idle"}c(C,"runStatus");async function Wn(e){const t=await O(e);return t!==null&&await C(t)==="running"}c(Wn,"runIsRunning");async function W(e){await Z(e.node_id),e=await O(e.node_id)??e;const t=await C(e),s=t==="settled"?[]:await me(e.node_id),n=await Qe(e.node_id)||void 0,o=t==="settled"?await lt(e):null,m=o?.kind==="result"?await re(e.node_id):void 0,i=Xe(e.node_id);return{run_id:e.node_id,name:e.name,status:t,...o?{outcome:o}:{},...m===void 0?{}:{result:m},...n!==void 0?{last_report:n}:{},...i?{recap:i}:{},pending_question_ids:s,open_notice_ids:t==="settled"?[]:await Et(e.node_id),...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(X())),payer:await J(e.grant_id),scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:await gt(e.node_id,t)}}c(W,"runObject");const z=864e5,Ee=255;function Fn(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>Ee)throw N(`Idempotency-Key must be 1-${Ee} characters`);return e}}c(Fn,"idempotencyKeyOf");function Dt(e){if(Le()){if(!e)throw new R(403,"permission_error","a cloud role serves this call only for a token that names a person");return e}}c(Dt,"cloudPersonScope");function k(e,t){const s=Dt(e);return s===void 0?t:`${s}:${t}`}c(k,"personKey");async function H(e,t,s,n){n&&await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`idempotency:${e}:${t}`),n??=await B();const o=await n.prepare("SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!o||Date.now()-Date.parse(o.created_at)>=z)){if(o.body_hash!==s)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:o.run_id,response:JSON.parse(o.response_json)}}}c(H,"findIdempotent");async function ye(e,t,s,n,o,m,i){await e.prepare("DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,s,new Date(Date.now()-z).toISOString()),await e.prepare("INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,s,n,o,JSON.stringify(m),i)}c(ye,"recordIdempotent");async function qn(e=Date.now()){return Number((await(await B()).prepare("DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-z).toISOString())).changes)}c(qn,"pruneExpiredIdempotencyKeys");async function he(e,t,s,n,o){const m=n.text;let i=0;return{messageId:(await ze(t,{kind:"message",label:m.slice(0,120),senderNodeId:"human",senderName:s,bodyJson:o?{body:m}:{body:m,start_turn:!1},urgency:o?"normal":"deferred",wake:o?1:0,withinEnqueue:c(async(u,l)=>{n.uses.length&&(await le(s,n.uses,u),await pe(n.uses.map(({upload_id:g,path:E,blob_key:y})=>({upload_id:g,path:E,blob_key:y,run_id:t})),u)),i=(await ce(u,t,t,"node.message.accepted",{message_id:l,start_turn:o})).sequence_number},"withinEnqueue")})).messageId,sequence:i}}c(he,"acceptRunMessage");class Re extends Error{static{c(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}const Mt=1440*60*1e3;function Se(e){const t=typeof e=="string"?nt(e):null;if(t===null||!Number.isSafeInteger(t))throw N(`deadline '${String(e)}' is not a duration; use one or more <int>[smhd] parts such as 10m or 1h30m`,"deadline");if(t<=0)throw N("deadline must be greater than zero","deadline");if(t>Mt)throw N("deadline must be at most 24h","deadline");return t}c(Se,"parseRunDeadlineMs");async function Hn(e,t,s,n=we){if(t.isolated&&!it()&&await rt((t.profile===void 0?await te(e.grantee):ne(t.profile)).profileId)===null)throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");t.deadline!==void 0&&Se(t.deadline);const{causedBy:o,...m}=t,i=Q("sha256").update(JSON.stringify(m)).digest("hex");if(s){const a=await H(e.grantee,k(e.sub,s),i);if(a){const p=await q(a.run_id,e.grantee);return!p.pi_pid&&await C(p)==="running"&&(t.output_schema!==void 0&&!p.outcome_at&&await ut(p.node_id)===null&&await re(p.node_id)===void 0&&await oe(p.node_id,"terminal",t.output_schema),await j(p.node_id,{resume:!0,capacity:"freeze"})),a.response}}await yt(e.grantee);const r=t.profile===void 0?await te(e.grantee):ne(t.profile),u=r.manifest.grantee??"app:terminal";if(u!==e.grantee){const a=`crtr:memory:rw:${u}:profile:${r.profileId}`;if(!e.resolved.some(p=>P(p,a))||t.scopes!==null&&!t.scopes.some(p=>P(p,a)))throw new R(403,"scope_missing",`missing scope ${a}`,"profile",void 0,a)}if(t.model!==void 0){const a=t.model;if(tt(a)){if(a.includes("/")&&await J(e.grant)!=="app"){const p=a.lastIndexOf("/"),D=a.slice(0,p),v=a.slice(p+1);if(["ultra","strong","medium","light"].includes(v)&&(await st({kind:"logical",family:D,strength:v,spec:a},Y(e.grantee).files,r.profileId)).every(S=>S.family!==D))throw N(`model '${a}' names a model family that is not configured in this daemon`,"model")}}else if(at(a)===void 0)throw N(`model '${a}' is not a strength (ultra, strong, medium, light), a family/strength, or a provider/model id`,"model")}const l=await Ke(),g=We(Y(e.grantee).nodes,l);if(t.isolated&&t.cwd!==void 0){const a=Ce(g,ke(t.cwd));if(!V(t.cwd)||a===".."||a.startsWith(`..${xe}`)||V(a))throw N("cwd must be inside the isolated root node folder","cwd")}const E=await n(l,e.grantee,t.prompt),y=E.text,d=t.message===void 0?void 0:await n(l,e.grantee,t.message,E.uses.map(a=>a.path)),_=await J(e.grant);let h;try{const a=dt(r,t.model);await et({kind:ot(r),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??g:Y(e.grantee).files,profile:r.profileId,prompt:y,nodeId:l,isolated:t.isolated??!1,...t.causedBy===void 0?{}:{causedBy:t.causedBy},...a===void 0?{}:{model:a},...t.deadline===void 0?{}:{deadlineAt:new Date(Date.now()+Se(t.deadline)).toISOString()},...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:l,event_cursor:1,usage_json:JSON.stringify(X())},withinCreate:c(async(p,D)=>{if(s){const S=await H(e.grantee,k(e.sub,s),i,p);if(S)throw new Re(S)}const v=E.uses;if(v.length&&(await le(e.grantee,v,p),await pe(v.map(({upload_id:S,path:I,blob_key:U})=>({upload_id:S,path:I,blob_key:U,run_id:l})),p)),h={run_id:l,node_id:l,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:_,status:"running",created_at:D.created,metadata:t.metadata,sequence_number:0},await ce(p,l,l,"run.started",{scopes:t.scopes,payer:_,usage:X()}),d){const S=await he(p,l,e.grantee,d,!1);h.message={run_id:l,message_id:S.messageId,sequence_number:S.sequence}}s&&await ye(p,e.grantee,k(e.sub,s),i,l,h,D.created)},"withinCreate")},t.output_schema===void 0?void 0:async p=>{await oe(p,"terminal",t.output_schema)})}catch(a){if(a instanceof Re)return a.winner.response;if(t.model!==void 0&&a instanceof Te&&a.code==="usage")throw N(a.message,"model");if(a instanceof Ie&&(a.code==="isolated_run"||a.code==="invalid_request")){const p=a.details&&typeof a.details=="object"&&"param"in a.details&&typeof a.details.param=="string"?a.details.param:void 0;throw new R(a.status,a.code,a.message,p)}throw a}if(!h)throw new Error("run creation did not commit");return h}c(Hn,"startRun");async function Un(e,t){let s;if(t.after)try{const r=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!r||typeof r!="object"||!("activity"in r)||typeof r.activity!="string"||!("id"in r)||typeof r.id!="string")throw new Error("invalid cursor");s={activity:r.activity,id:r.id}}catch{throw N("invalid list cursor","after")}const n=await(await A()).prepareUncached(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
8
+ AND (n.status IN ('active', 'idle') OR ${Ye("n")}) LIMIT 1`).get(e.node_id)?"running":"idle"}c(C,"runStatus");async function Wn(e){const t=await O(e);return t!==null&&await C(t)==="running"}c(Wn,"runIsRunning");async function W(e){await Z(e.node_id),e=await O(e.node_id)??e;const t=await C(e),s=t==="settled"?[]:await me(e.node_id),n=await Qe(e.node_id)||void 0,d=t==="settled"?await lt(e):null,f=d?.kind==="result"?await re(e.node_id):void 0,r=Xe(e.node_id);return{run_id:e.node_id,name:e.name,status:t,...d?{outcome:d}:{},...f===void 0?{}:{result:f},...n!==void 0?{last_report:n}:{},...r?{recap:r}:{},pending_question_ids:s,open_notice_ids:t==="settled"?[]:await Et(e.node_id),...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(X())),payer:await J(e.grant_id),scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:await gt(e.node_id,t)}}c(W,"runObject");const z=864e5,Ee=255;function Fn(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>Ee)throw N(`Idempotency-Key must be 1-${Ee} characters`);return e}}c(Fn,"idempotencyKeyOf");function Dt(e){if(Le()){if(!e)throw new R(403,"permission_error","a cloud role serves this call only for a token that names a person");return e}}c(Dt,"cloudPersonScope");function k(e,t){const s=Dt(e);return s===void 0?t:`${s}:${t}`}c(k,"personKey");async function H(e,t,s,n){n&&await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`idempotency:${e}:${t}`),n??=await B();const d=await n.prepare("SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!d||Date.now()-Date.parse(d.created_at)>=z)){if(d.body_hash!==s)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:d.run_id,response:JSON.parse(d.response_json)}}}c(H,"findIdempotent");async function ye(e,t,s,n,d,f,r){await e.prepare("DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,s,new Date(Date.now()-z).toISOString()),await e.prepare("INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,s,n,d,JSON.stringify(f),r)}c(ye,"recordIdempotent");async function qn(e=Date.now()){return Number((await(await B()).prepare("DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-z).toISOString())).changes)}c(qn,"pruneExpiredIdempotencyKeys");async function he(e,t,s,n,d,f=!1){const r=n.text;let i=0;return{messageId:(await ze(t,{kind:"message",label:r.slice(0,120),senderNodeId:"human",senderName:s,bodyJson:d?{body:r,...f?{steer:!0}:{}}:{body:r,start_turn:!1},urgency:d?"normal":"deferred",wake:d?1:0,withinEnqueue:c(async(m,h)=>{n.uses.length&&(await le(s,n.uses,m),await fe(n.uses.map(({upload_id:g,path:E,blob_key:o})=>({upload_id:g,path:E,blob_key:o,run_id:t})),m)),i=(await ce(m,t,t,"node.message.accepted",{message_id:h,start_turn:d})).sequence_number},"withinEnqueue")})).messageId,sequence:i}}c(he,"acceptRunMessage");class Re extends Error{static{c(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}const Mt=1440*60*1e3;function Se(e){const t=typeof e=="string"?nt(e):null;if(t===null||!Number.isSafeInteger(t))throw N(`deadline '${String(e)}' is not a duration; use one or more <int>[smhd] parts such as 10m or 1h30m`,"deadline");if(t<=0)throw N("deadline must be greater than zero","deadline");if(t>Mt)throw N("deadline must be at most 24h","deadline");return t}c(Se,"parseRunDeadlineMs");async function Hn(e,t,s,n=we){if(t.isolated&&!it()&&await rt((t.profile===void 0?await te(e.grantee):ne(t.profile)).profileId)===null)throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");t.deadline!==void 0&&Se(t.deadline);const{causedBy:d,...f}=t,r=Q("sha256").update(JSON.stringify(f)).digest("hex");if(s){const a=await H(e.grantee,k(e.sub,s),r);if(a){const l=await q(a.run_id,e.grantee);return!l.pi_pid&&await C(l)==="running"&&(t.output_schema!==void 0&&!l.outcome_at&&await ut(l.node_id)===null&&await re(l.node_id)===void 0&&await oe(l.node_id,"terminal",t.output_schema),await j(l.node_id,{resume:!0,capacity:"freeze"})),a.response}}await yt(e.grantee);const i=t.profile===void 0?await te(e.grantee):ne(t.profile),u=i.manifest.grantee??"app:terminal";if(u!==e.grantee){const a=`crtr:memory:rw:${u}:profile:${i.profileId}`;if(!e.resolved.some(l=>P(l,a))||t.scopes!==null&&!t.scopes.some(l=>P(l,a)))throw new R(403,"scope_missing",`missing scope ${a}`,"profile",void 0,a)}if(t.model!==void 0){const a=t.model;if(tt(a)){if(a.includes("/")&&await J(e.grant)!=="app"){const l=a.lastIndexOf("/"),D=a.slice(0,l),v=a.slice(l+1);if(["ultra","strong","medium","light"].includes(v)&&(await st({kind:"logical",family:D,strength:v,spec:a},Y(e.grantee).files,i.profileId)).every(S=>S.family!==D))throw N(`model '${a}' names a model family that is not configured in this daemon`,"model")}}else if(at(a)===void 0)throw N(`model '${a}' is not a strength (ultra, strong, medium, light), a family/strength, or a provider/model id`,"model")}const m=await Ke(),h=We(Y(e.grantee).nodes,m);if(t.isolated&&t.cwd!==void 0){const a=Ce(h,ke(t.cwd));if(!V(t.cwd)||a===".."||a.startsWith(`..${xe}`)||V(a))throw N("cwd must be inside the isolated root node folder","cwd")}const g=await n(m,e.grantee,t.prompt),E=g.text,o=t.message===void 0?void 0:await n(m,e.grantee,t.message,g.uses.map(a=>a.path)),p=await J(e.grant);let y;try{const a=dt(i,t.model);await et({kind:ot(i),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??h:Y(e.grantee).files,profile:i.profileId,prompt:E,nodeId:m,isolated:t.isolated??!1,...t.causedBy===void 0?{}:{causedBy:t.causedBy},...a===void 0?{}:{model:a},...t.deadline===void 0?{}:{deadlineAt:new Date(Date.now()+Se(t.deadline)).toISOString()},...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:m,event_cursor:1,usage_json:JSON.stringify(X())},withinCreate:c(async(l,D)=>{if(s){const S=await H(e.grantee,k(e.sub,s),r,l);if(S)throw new Re(S)}const v=g.uses;if(v.length&&(await le(e.grantee,v,l),await fe(v.map(({upload_id:S,path:I,blob_key:U})=>({upload_id:S,path:I,blob_key:U,run_id:m})),l)),y={run_id:m,node_id:m,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:p,status:"running",created_at:D.created,metadata:t.metadata,sequence_number:0},await ce(l,m,m,"run.started",{scopes:t.scopes,payer:p,usage:X()}),o){const S=await he(l,m,e.grantee,o,!1);y.message={run_id:m,message_id:S.messageId,sequence_number:S.sequence}}s&&await ye(l,e.grantee,k(e.sub,s),r,m,y,D.created)},"withinCreate")},t.output_schema===void 0?void 0:async l=>{await oe(l,"terminal",t.output_schema)})}catch(a){if(a instanceof Re)return a.winner.response;if(t.model!==void 0&&a instanceof Te&&a.code==="usage")throw N(a.message,"model");if(a instanceof Ie&&(a.code==="isolated_run"||a.code==="invalid_request")){const l=a.details&&typeof a.details=="object"&&"param"in a.details&&typeof a.details.param=="string"?a.details.param:void 0;throw new R(a.status,a.code,a.message,l)}throw a}if(!y)throw new Error("run creation did not commit");return y}c(Hn,"startRun");async function Un(e,t){let s;if(t.after)try{const i=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!i||typeof i!="object"||!("activity"in i)||typeof i.activity!="string"||!("id"in i)||typeof i.id!="string")throw new Error("invalid cursor");s={activity:i.activity,id:i.id}}catch{throw N("invalid list cursor","after")}const n=await(await A()).prepareUncached(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
9
9
  WHERE run_id = node_id AND deleting_at IS NULL ${e===null?"":"AND grantee = ?"}${t.sub===void 0?"":" AND sub = ?"}
10
- ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e],...t.sub===void 0?[]:[t.sub]),o=await _e(await x((await _e(n,async({node_id:r})=>!t.visible||await t.visible(r))).filter(({activity:r,node_id:u})=>!s||r<s.activity||r===s.activity&&u<s.id),async r=>({root:await O(r.node_id),activity:r.activity})),async({root:r})=>!t.status||await C(r)===t.status),m=o.slice(0,t.limit),i=m.at(-1);return{runs:await x(m,async({root:r})=>await W(r)),next:o.length>t.limit&&i?Buffer.from(JSON.stringify({activity:i.activity,id:i.root.node_id})).toString("base64url"):null}}c(Un,"listRuns");async function $n(e,t,s){const n=await q(e,t),o=c(async()=>await W(await q(e,t)),"read");return!s||ge.includes(await C(n))?await o():new Promise((m,i)=>{let r=c(()=>{},"unsubscribe");const u=c(()=>{clearTimeout(d),clearInterval(y),r()},"settle"),l=c(async()=>{u();try{m(await o())}catch(_){i(_)}},"finish"),g=c(async()=>{ge.includes(String((await o()).status))&&await l()},"check"),E=c(()=>{g().catch(_=>{u(),i(_)})},"run"),y=setInterval(E,250),d=setTimeout(()=>{l()},s*1e3);wt(e,E).then(_=>{r=_},_=>{u(),i(_)})})}c($n,"waitRun");const At=5;async function Pn(e,t,s=we,n=!0,o){const m=e.status==="canceled"||e.outcome_reason==="closed",i=e.grantee??"app:terminal",r=Q("sha256").update(JSON.stringify(["run.message",e.node_id,t,n])).digest("hex"),u=c(async g=>{const E=await O(e.node_id),y=await(await A()).prepare("SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(g.message_id));return E&&!m&&n&&!E.pi_pid&&y&&await j(e.node_id,{resume:!0,capacity:"freeze"}),g},"replay");if(o){const g=await H(i,k(e.sub,o),r);if(g)return u(g.response)}if(m)throw new R(409,"run_settled","canceled run cannot receive a message");const l=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;for(let g=1;;g++){const E=await s(e.node_id,i,t),y=await F(async d=>{if(o){const a=await H(i,k(e.sub,o),r,d);if(a)return{replayed:a.response}}if(E.uses.length){await _t(d,e.node_id);for(const a of E.uses)if(await bt(e.node_id,a.path,d))return"path_taken"}await l?.(e.node_id);const _=await he(d,e.node_id,i,E,n),h={run_id:e.node_id,message_id:_.messageId,sequence_number:_.sequence};return o&&await ye(d,i,k(e.sub,o),r,e.node_id,h,new Date().toISOString()),{result:h}});if(y==="path_taken"){if(g>=At)throw new R(503,"service_unavailable","attachments of concurrent messages kept taking each other's file names; retry");continue}return"replayed"in y?u(y.replayed):(n&&!e.pi_pid&&await j(e.node_id,{resume:!0,capacity:"freeze"}),y.result)}}c(Pn,"messageRun");async function It(e){const t=c(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let s=0;s<2;s++){const n=await O(e);if(!n)throw G();if(!ae(n)){if(n.streaming)throw t();return}const o=(await K(e))?.executionId;try{await Ze(e)==="turn"&&fe({nodes:[e]});return}catch(m){if(!(m instanceof Ve))throw m;const i=await O(e);if(!i)throw G();if(!i.streaming)return;if(s===0&&ae(i)&&(await K(e))?.executionId!==o)continue;throw t()}}throw t()}c(It,"interruptNode");async function Bn(e,t="stop"){const s=t==="stop"?(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id)).map(({node_id:n})=>n):[e.node_id];return await Promise.all(s.map(n=>It(n))),await W(await O(e.node_id)??e)}c(Bn,"interruptRun");async function jn(e,t={}){const s=(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(i=>i.node_id);fe({run:e.node_id});const n=[];if(await C(e)!=="settled")n.push(...(await de(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"})).closed);else for(const i of s){if(i===e.node_id)continue;const r=await O(i);r&&r.status!=="done"&&r.status!=="canceled"&&r.status!=="dead"&&n.push(...(await de(i,{preserveEmpty:!0,stopReason:"run_canceled"})).closed)}if(await ue({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(i=>se(i,"run_canceled")))).some(i=>!i.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const m=await O(e.node_id);return await W(m??e)}c(jn,"cancelRun");const Ne=200;async function Jn(e,t){if(typeof t!="string"||!t.trim())throw N("name is required","name");const s=t.trim();if(s.length>Ne)throw N(`name exceeds ${Ne} characters`,"name");const n=await Pe(e.node_id,{name:s});return n.window&&je(n.window,Be(n)),await W(n)}c(Jn,"renameRun");async function Yn(e,t={config:St,client:Rt}){await ue({run_id:e.node_id},"the run was deleted");const s=await F(async u=>(await u.prepare("UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),(await u.prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(l=>l.node_id)));if((await Promise.all(s.map(u=>se(u,"run_deleted",{deleting:!0})))).some(u=>!u.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const o=await x(s,async u=>{const l=await Ue(u);if(l.app===null)throw new Error(`run node missing during deletion: ${u}`);return l}),m=await(await A()).prepare("SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:u}of m)try{await ee().delete(u)}catch(l){if(!Je(l))throw l}const i=await F(async u=>{for(const y of s){const d=await He(u,y);d!==null&&d.deleted_at===null&&await qe(u,d)}const l=await vt([e.node_id],u);await u.prepare("DELETE FROM run_events WHERE run_id = ?").run(e.node_id),await u.prepare("DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const g=s.map(()=>"?").join(","),E=await u.prepareUncached(`SELECT message_id FROM messages WHERE sender_node_id IN (${g}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${g})`).all(...s,...s);await u.prepareUncached(`DELETE FROM deliveries WHERE destination_id IN (${g})`).run(...s);for(const{message_id:y}of E)await u.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(y,y);return l});for(const u of o)await $e(u);const{notifyRunDeleted:r}=await import("./events.js");return r(e.node_id),{uploads_pending:await Tt(i,t)}}c(Yn,"deleteRun");async function Tt(e,t){if(!e.length||!t.config())return e.length;let s;try{s=t.client()}catch{return e.length}const n=new Set,o=e.map(i=>s.deleteUpload(i).then(async()=>{await Nt(i),n.add(i)},()=>{}));let m;return await Promise.race([Promise.all(o),new Promise(i=>{m=setTimeout(i,t.budgetMs??Ot),m.unref?.()})]),clearTimeout(m),e.length-n.size}c(Tt,"deleteReleasedUploads");async function Gn(e,t,s){await q(e,t),await Z(e);const n=await F(async d=>{const _=await d.prepare("SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e),h=new Map;for(const S of _)h.set(S.node_id,(await d.prepare("SELECT session_id FROM sessions WHERE node_id = ? GROUP BY session_id ORDER BY min(created_at)").all(S.node_id)).map(I=>I.session_id));const p=(await d.prepare("SELECT event_cursor FROM nodes WHERE node_id = ?").get(e)).event_cursor-1,D=await d.prepare(`SELECT node_id, sequence_number FROM run_events
10
+ ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e],...t.sub===void 0?[]:[t.sub]),d=await _e(await x((await _e(n,async({node_id:i})=>!t.visible||await t.visible(i))).filter(({activity:i,node_id:u})=>!s||i<s.activity||i===s.activity&&u<s.id),async i=>({root:await O(i.node_id),activity:i.activity})),async({root:i})=>!t.status||await C(i)===t.status),f=d.slice(0,t.limit),r=f.at(-1);return{runs:await x(f,async({root:i})=>await W(i)),next:d.length>t.limit&&r?Buffer.from(JSON.stringify({activity:r.activity,id:r.root.node_id})).toString("base64url"):null}}c(Un,"listRuns");async function $n(e,t,s){const n=await q(e,t),d=c(async()=>await W(await q(e,t)),"read");return!s||ge.includes(await C(n))?await d():new Promise((f,r)=>{let i=c(()=>{},"unsubscribe");const u=c(()=>{clearTimeout(o),clearInterval(E),i()},"settle"),m=c(async()=>{u();try{f(await d())}catch(p){r(p)}},"finish"),h=c(async()=>{ge.includes(String((await d()).status))&&await m()},"check"),g=c(()=>{h().catch(p=>{u(),r(p)})},"run"),E=setInterval(g,250),o=setTimeout(()=>{m()},s*1e3);wt(e,g).then(p=>{i=p},p=>{u(),r(p)})})}c($n,"waitRun");const At=5;async function Pn(e,t,s=we,n=!0,d,f=!0){const r=e.status==="canceled"||e.outcome_reason==="closed",i=e.grantee??"app:terminal",u=Q("sha256").update(JSON.stringify(["run.message",e.node_id,t,n,...n&&!f?["steer:false"]:[]])).digest("hex"),m=c(async g=>{const E=await O(e.node_id),o=await(await A()).prepare("SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(g.message_id));return E&&!r&&n&&!E.pi_pid&&o&&await j(e.node_id,{resume:!0,capacity:"freeze"}),g},"replay");if(d){const g=await H(i,k(e.sub,d),u);if(g)return m(g.response)}if(r)throw new R(409,"run_settled","canceled run cannot receive a message");const h=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;for(let g=1;;g++){const E=await s(e.node_id,i,t),o=await F(async p=>{if(d){const l=await H(i,k(e.sub,d),u,p);if(l)return{replayed:l.response}}if(E.uses.length){await _t(p,e.node_id);for(const l of E.uses)if(await bt(e.node_id,l.path,p))return"path_taken"}await h?.(e.node_id);const y=await he(p,e.node_id,i,E,n,n&&f),a={run_id:e.node_id,message_id:y.messageId,sequence_number:y.sequence};return d&&await ye(p,i,k(e.sub,d),u,e.node_id,a,new Date().toISOString()),{result:a}});if(o==="path_taken"){if(g>=At)throw new R(503,"service_unavailable","attachments of concurrent messages kept taking each other's file names; retry");continue}return"replayed"in o?m(o.replayed):(n&&!e.pi_pid&&await j(e.node_id,{resume:!0,capacity:"freeze"}),o.result)}}c(Pn,"messageRun");async function It(e){const t=c(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let s=0;s<2;s++){const n=await O(e);if(!n)throw G();if(!ae(n)){if(n.streaming)throw t();return}const d=(await K(e))?.executionId;try{await Ze(e)==="turn"&&pe({nodes:[e]});return}catch(f){if(!(f instanceof Ve))throw f;const r=await O(e);if(!r)throw G();if(!r.streaming)return;if(s===0&&ae(r)&&(await K(e))?.executionId!==d)continue;throw t()}}throw t()}c(It,"interruptNode");async function Bn(e,t="stop"){const s=t==="stop"?(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id)).map(({node_id:n})=>n):[e.node_id];return await Promise.all(s.map(n=>It(n))),await W(await O(e.node_id)??e)}c(Bn,"interruptRun");async function jn(e,t={}){const s=(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(r=>r.node_id);pe({run:e.node_id});const n=[];if(await C(e)!=="settled")n.push(...(await de(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"})).closed);else for(const r of s){if(r===e.node_id)continue;const i=await O(r);i&&i.status!=="done"&&i.status!=="canceled"&&i.status!=="dead"&&n.push(...(await de(r,{preserveEmpty:!0,stopReason:"run_canceled"})).closed)}if(await ue({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(r=>se(r,"run_canceled")))).some(r=>!r.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const f=await O(e.node_id);return await W(f??e)}c(jn,"cancelRun");const Ne=200;async function Jn(e,t){if(typeof t!="string"||!t.trim())throw N("name is required","name");const s=t.trim();if(s.length>Ne)throw N(`name exceeds ${Ne} characters`,"name");const n=await Pe(e.node_id,{name:s});return n.window&&je(n.window,Be(n)),await W(n)}c(Jn,"renameRun");async function Yn(e,t={config:St,client:Rt}){await ue({run_id:e.node_id},"the run was deleted");const s=await F(async u=>(await u.prepare("UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),(await u.prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(m=>m.node_id)));if((await Promise.all(s.map(u=>se(u,"run_deleted",{deleting:!0})))).some(u=>!u.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const d=await x(s,async u=>{const m=await Ue(u);if(m.app===null)throw new Error(`run node missing during deletion: ${u}`);return m}),f=await(await A()).prepare("SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:u}of f)try{await ee().delete(u)}catch(m){if(!Je(m))throw m}const r=await F(async u=>{for(const E of s){const o=await He(u,E);o!==null&&o.deleted_at===null&&await qe(u,o)}const m=await vt([e.node_id],u);await u.prepare("DELETE FROM run_events WHERE run_id = ?").run(e.node_id),await u.prepare("DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const h=s.map(()=>"?").join(","),g=await u.prepareUncached(`SELECT message_id FROM messages WHERE sender_node_id IN (${h}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${h})`).all(...s,...s);await u.prepareUncached(`DELETE FROM deliveries WHERE destination_id IN (${h})`).run(...s);for(const{message_id:E}of g)await u.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(E,E);return m});for(const u of d)await $e(u);const{notifyRunDeleted:i}=await import("./events.js");return i(e.node_id),{uploads_pending:await Tt(r,t)}}c(Yn,"deleteRun");async function Tt(e,t){if(!e.length||!t.config())return e.length;let s;try{s=t.client()}catch{return e.length}const n=new Set,d=e.map(r=>s.deleteUpload(r).then(async()=>{await Nt(r),n.add(r)},()=>{}));let f;return await Promise.race([Promise.all(d),new Promise(r=>{f=setTimeout(r,t.budgetMs??Ot),f.unref?.()})]),clearTimeout(f),e.length-n.size}c(Tt,"deleteReleasedUploads");async function Gn(e,t,s){await q(e,t),await Z(e);const n=await F(async o=>{const p=await o.prepare("SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e),y=new Map;for(const S of p)y.set(S.node_id,(await o.prepare("SELECT session_id FROM sessions WHERE node_id = ? GROUP BY session_id ORDER BY min(created_at)").all(S.node_id)).map(I=>I.session_id));const l=(await o.prepare("SELECT event_cursor FROM nodes WHERE node_id = ?").get(e)).event_cursor-1,D=await o.prepare(`SELECT node_id, sequence_number FROM run_events
11
11
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.done', 'node.output_text.done', 'usage')
12
- ORDER BY sequence_number`).all(e,p),v=await d.prepare(`SELECT node_id, sequence_number, type, body_json FROM run_events
12
+ ORDER BY sequence_number`).all(e,l),v=await o.prepare(`SELECT node_id, sequence_number, type, body_json FROM run_events
13
13
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.delta', 'node.output_text.delta')
14
- ORDER BY sequence_number`).all(e,p);return{nodes:_,sessionIds:h,latestSequence:p,completed:D,deltas:v}}),{nodes:o,sessionIds:m,latestSequence:i,completed:r,deltas:u}=n,l=await x(o,async d=>({node_id:d.node_id,parent_node_id:d.parent,conversations:await x(m.get(d.node_id)??[],async _=>{const{header:h,entries:a}=await ee().read(_);if(s==="entries")return{conversation_id:_,header:h,entries:a};const p=Fe.inMemory(h.cwd,void 0,[h,...a]),D=ct(p)??[],v=new Map(a.map((w,b)=>[w.id,b])),S=a.filter(w=>w.type==="custom"&&w.customType==="crtr-system-prompt").map(w=>({id:w.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in w?w.data:void 0,timestamp:w.timestamp}})),I=a.filter(w=>w.type==="custom_message"&&w.customType==="app_message").map(w=>({id:w.id,message:{role:"custom",customType:"app_message",content:"content"in w?w.content:"",timestamp:w.timestamp}})),U=d.node_id===e?a.find(w=>{if(w.type!=="message"||w.message.role!=="user")return!1;const b=ie(a,w.message.timestamp);return b?.type==="runtime"&&b.kind==="kickoff"})?.id:void 0,ve=[...D.filter(({id:w})=>!I.some(b=>b.id===w)),...S,...I].sort((w,b)=>(v.get(w.id)??Number.MAX_SAFE_INTEGER)-(v.get(b.id)??Number.MAX_SAFE_INTEGER)).map(({id:w,message:b})=>{const f=b,T=f.usage,$=f.role==="user"?ie(a,Number(f.timestamp)):null,be=$?.type==="human"||w===U,L=f.role==="toolResult"?"tool_result":f.role==="bashExecution"?"bash_execution":f.role==="compactionSummary"?"compaction_summary":f.role==="branchSummary"?"branch_summary":f.role==="custom"&&f.customType==="crtr-system-prompt"?"system":f.role==="user"?be?"user":"custom":f.role,Oe=L==="custom"?f.role==="custom"?f.customType:$?.type==="runtime"?$.kind:"unknown":void 0,De=L==="system"&&f.customType==="crtr-system-prompt"?[{type:"text",text:f.data?.text??""}]:typeof f.content=="string"?[{type:"text",text:f.content}]:Array.isArray(f.content)?f.content.map(M=>M.type==="toolCall"?{type:"tool_call",tool_call_id:M.id,name:M.name,arguments:M.arguments}:M.type==="image"?{type:"image",mime_type:M.mimeType}:M.type==="thinking"?{type:"thinking",text:M.thinking}:M):[];return{entry_id:w,role:L,...L==="custom"?{custom_type:Oe}:{},time:typeof f.timestamp=="string"?f.timestamp:new Date(Number(f.timestamp??Date.now())).toISOString(),content:De,...L==="assistant"?{model:f.model,provider:f.provider,stop_reason:f.stopReason,...f.errorMessage?{error_message:f.errorMessage}:{},...T?{usage:{input_tokens:T.input??0,output_tokens:T.output??0,cache_read_tokens:T.cacheRead??0,cache_write_tokens:T.cacheWrite??0,cost_usd:T.cost?.total??0}}:{}}:{},...L==="tool_result"?{tool_call_id:f.toolCallId,tool_name:f.toolName,is_error:f.isError}:{}}})??[];return{conversation_id:_,messages:ve}})})),g=new Map(await Promise.all(l.map(async d=>[d.node_id,await pt(d.node_id)])));for(const d of r){const _=g.get(d.node_id);_!=null&&d.sequence_number>=_&&g.set(d.node_id,null)}const E=new Map;for(const d of u){const _=g.get(d.node_id);if(_==null||d.sequence_number<_)continue;const h=JSON.parse(d.body_json),a=E.get(d.node_id)??{thinking:"",text:""};d.type==="node.thinking.delta"?a.thinking+=h.delta:a.text+=h.delta,E.set(d.node_id,a)}const y=l.map(d=>({...d,in_progress_message:g.get(d.node_id)==null?null:E.get(d.node_id)??{thinking:"",text:""}}));return{run_id:e,latest_sequence:i,nodes:y}}c(Gn,"traceRun");function Xn(e,t,s){if(t===void 0){if(!e.some(n=>P(n,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(n=>typeof n!="string"))throw N("scopes must be a list of scope strings","scopes");for(const n of t){try{ft(n)}catch{throw N(`invalid scope ${n}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))throw N(`own-app files cannot narrow a run: ${n}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))&&!Ae(e,n))throw new R(403,"scope_missing",`missing scope ${n}`,void 0,void 0,n)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}c(Xn,"requestedScopes");export{z as IDEMPOTENCY_WINDOW_MS,Mt as RUN_DEADLINE_MAX_MS,Ot as RUN_DELETE_STORAGE_BUDGET_MS,Ne as RUN_NAME_MAX,R as RunError,jn as cancelRun,Dt as cloudPersonScope,Yn as deleteRun,Fn as idempotencyKeyOf,Bn as interruptRun,Un as listRuns,Pn as messageRun,q as ownedRun,Se as parseRunDeadlineMs,qn as pruneExpiredIdempotencyKeys,Jn as renameRun,Xn as requestedScopes,Wn as runIsRunning,W as runObject,Hn as startRun,Gn as traceRun,$n as waitRun};
14
+ ORDER BY sequence_number`).all(e,l);return{nodes:p,sessionIds:y,latestSequence:l,completed:D,deltas:v}}),{nodes:d,sessionIds:f,latestSequence:r,completed:i,deltas:u}=n,m=await x(d,async o=>({node_id:o.node_id,parent_node_id:o.parent,conversations:await x(f.get(o.node_id)??[],async p=>{const{header:y,entries:a}=await ee().read(p);if(s==="entries")return{conversation_id:p,header:y,entries:a};const l=Fe.inMemory(y.cwd,void 0,[y,...a]),D=ct(l)??[],v=new Map(a.map((w,b)=>[w.id,b])),S=a.filter(w=>w.type==="custom"&&w.customType==="crtr-system-prompt").map(w=>({id:w.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in w?w.data:void 0,timestamp:w.timestamp}})),I=a.filter(w=>w.type==="custom_message"&&w.customType==="app_message").map(w=>({id:w.id,message:{role:"custom",customType:"app_message",content:"content"in w?w.content:"",timestamp:w.timestamp}})),U=o.node_id===e?a.find(w=>{if(w.type!=="message"||w.message.role!=="user")return!1;const b=ie(a,w.message.timestamp);return b?.type==="runtime"&&b.kind==="kickoff"})?.id:void 0,ve=[...D.filter(({id:w})=>!I.some(b=>b.id===w)),...S,...I].sort((w,b)=>(v.get(w.id)??Number.MAX_SAFE_INTEGER)-(v.get(b.id)??Number.MAX_SAFE_INTEGER)).map(({id:w,message:b})=>{const _=b,T=_.usage,$=_.role==="user"?ie(a,Number(_.timestamp)):null,be=$?.type==="human"||w===U,L=_.role==="toolResult"?"tool_result":_.role==="bashExecution"?"bash_execution":_.role==="compactionSummary"?"compaction_summary":_.role==="branchSummary"?"branch_summary":_.role==="custom"&&_.customType==="crtr-system-prompt"?"system":_.role==="user"?be?"user":"custom":_.role,Oe=L==="custom"?_.role==="custom"?_.customType:$?.type==="runtime"?$.kind:"unknown":void 0,De=L==="system"&&_.customType==="crtr-system-prompt"?[{type:"text",text:_.data?.text??""}]:typeof _.content=="string"?[{type:"text",text:_.content}]:Array.isArray(_.content)?_.content.map(M=>M.type==="toolCall"?{type:"tool_call",tool_call_id:M.id,name:M.name,arguments:M.arguments}:M.type==="image"?{type:"image",mime_type:M.mimeType}:M.type==="thinking"?{type:"thinking",text:M.thinking}:M):[];return{entry_id:w,role:L,...L==="custom"?{custom_type:Oe}:{},time:typeof _.timestamp=="string"?_.timestamp:new Date(Number(_.timestamp??Date.now())).toISOString(),content:De,...L==="assistant"?{model:_.model,provider:_.provider,stop_reason:_.stopReason,..._.errorMessage?{error_message:_.errorMessage}:{},...T?{usage:{input_tokens:T.input??0,output_tokens:T.output??0,cache_read_tokens:T.cacheRead??0,cache_write_tokens:T.cacheWrite??0,cost_usd:T.cost?.total??0}}:{}}:{},...L==="tool_result"?{tool_call_id:_.toolCallId,tool_name:_.toolName,is_error:_.isError}:{}}})??[];return{conversation_id:p,messages:ve}})})),h=new Map(await Promise.all(m.map(async o=>[o.node_id,await ft(o.node_id)])));for(const o of i){const p=h.get(o.node_id);p!=null&&o.sequence_number>=p&&h.set(o.node_id,null)}const g=new Map;for(const o of u){const p=h.get(o.node_id);if(p==null||o.sequence_number<p)continue;const y=JSON.parse(o.body_json),a=g.get(o.node_id)??{thinking:"",text:""};o.type==="node.thinking.delta"?a.thinking+=y.delta:a.text+=y.delta,g.set(o.node_id,a)}const E=m.map(o=>({...o,in_progress_message:h.get(o.node_id)==null?null:g.get(o.node_id)??{thinking:"",text:""}}));return{run_id:e,latest_sequence:r,nodes:E}}c(Gn,"traceRun");function Xn(e,t,s){if(t===void 0){if(!e.some(n=>P(n,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(n=>typeof n!="string"))throw N("scopes must be a list of scope strings","scopes");for(const n of t){try{pt(n)}catch{throw N(`invalid scope ${n}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))throw N(`own-app files cannot narrow a run: ${n}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))&&!Ae(e,n))throw new R(403,"scope_missing",`missing scope ${n}`,void 0,void 0,n)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}c(Xn,"requestedScopes");export{z as IDEMPOTENCY_WINDOW_MS,Mt as RUN_DEADLINE_MAX_MS,Ot as RUN_DELETE_STORAGE_BUDGET_MS,Ne as RUN_NAME_MAX,R as RunError,jn as cancelRun,Dt as cloudPersonScope,Yn as deleteRun,Fn as idempotencyKeyOf,Bn as interruptRun,Un as listRuns,Pn as messageRun,q as ownedRun,Se as parseRunDeadlineMs,qn as pruneExpiredIdempotencyKeys,Jn as renameRun,Xn as requestedScopes,Wn as runIsRunning,W as runObject,Hn as startRun,Gn as traceRun,$n as waitRun};
@@ -1,6 +1,10 @@
1
+ import type { RunSteerBridge } from './run-steer.js';
1
2
  export declare const HELD_DEFERRED_INBOX_PROMPT_JOIN: unique symbol;
2
3
  export declare const BROKER_IDLE_TURN_START: unique symbol;
3
4
  export declare const BROKER_RUN_TURN_BUSY: unique symbol;
5
+ export declare const BROKER_RUN_STEER: unique symbol;
6
+ export type BrokerRunSteer = Pick<RunSteerBridge, 'steerable' | 'accept' | 'takeUnconsumed'>;
7
+ export declare function brokerRunSteer(): BrokerRunSteer | undefined;
4
8
  export declare const BROKER_DISPOSE_AND_EXIT: unique symbol;
5
9
  /** The broker's one exit path, for an extension whose failure must end the process (a conversation store that stays down). */
6
10
  export declare function brokerDisposeAndExit(): ((reason: string, status?: number) => void) | undefined;
@@ -1 +1 @@
1
- var n=Object.defineProperty;var r=(o,t)=>n(o,"name",{value:t,configurable:!0});const e=Symbol.for("@crouton-kit/crtr:held-deferred-inbox-prompt-join"),i=Symbol.for("@crouton-kit/crtr:broker-idle-turn-start"),u=Symbol.for("@crouton-kit/crtr:broker-run-turn-busy"),c=Symbol.for("@crouton-kit/crtr:broker-dispose-and-exit");function s(){const o=globalThis[c];return typeof o=="function"?o:void 0}r(s,"brokerDisposeAndExit");function b(){const o=globalThis[u];return typeof o=="function"&&o()}r(b,"brokerRunTurnBusy");function p(){const o=globalThis[e];return typeof o=="function"?o:void 0}r(p,"heldDeferredInboxPromptJoin");function d(){const o=globalThis[i];return typeof o=="function"?o:void 0}r(d,"brokerIdleTurnStart");export{c as BROKER_DISPOSE_AND_EXIT,i as BROKER_IDLE_TURN_START,u as BROKER_RUN_TURN_BUSY,e as HELD_DEFERRED_INBOX_PROMPT_JOIN,s as brokerDisposeAndExit,d as brokerIdleTurnStart,b as brokerRunTurnBusy,p as heldDeferredInboxPromptJoin};
1
+ var n=Object.defineProperty;var r=(o,t)=>n(o,"name",{value:t,configurable:!0});const e=Symbol.for("@crouton-kit/crtr:held-deferred-inbox-prompt-join"),i=Symbol.for("@crouton-kit/crtr:broker-idle-turn-start"),u=Symbol.for("@crouton-kit/crtr:broker-run-turn-busy"),c=Symbol.for("@crouton-kit/crtr:broker-run-steer");function b(){return globalThis[c]}r(b,"brokerRunSteer");const f=Symbol.for("@crouton-kit/crtr:broker-dispose-and-exit");function l(){const o=globalThis[f];return typeof o=="function"?o:void 0}r(l,"brokerDisposeAndExit");function p(){const o=globalThis[u];return typeof o=="function"&&o()}r(p,"brokerRunTurnBusy");function R(){const o=globalThis[e];return typeof o=="function"?o:void 0}r(R,"heldDeferredInboxPromptJoin");function d(){const o=globalThis[i];return typeof o=="function"?o:void 0}r(d,"brokerIdleTurnStart");export{f as BROKER_DISPOSE_AND_EXIT,i as BROKER_IDLE_TURN_START,c as BROKER_RUN_STEER,u as BROKER_RUN_TURN_BUSY,e as HELD_DEFERRED_INBOX_PROMPT_JOIN,l as brokerDisposeAndExit,d as brokerIdleTurnStart,b as brokerRunSteer,p as brokerRunTurnBusy,R as heldDeferredInboxPromptJoin};
@@ -7,6 +7,7 @@ import type { BrokerSession } from './read-ops.js';
7
7
  import type { ToolGroupTracker } from './tool-groups.js';
8
8
  import type { TurnReplyTracker } from './turn-replies.js';
9
9
  import type { OperationId } from '../../events/types.js';
10
+ import type { RunSteerBridge } from './run-steer.js';
10
11
  export interface SubscriptionGeneration {
11
12
  id: number;
12
13
  session: BrokerSession;
@@ -34,6 +35,8 @@ type RebindDriverDeps = {
34
35
  registry: BrokerClientRegistry;
35
36
  toolGroups: ToolGroupTracker;
36
37
  turnReplies: TurnReplyTracker;
38
+ runSteer: RunSteerBridge;
39
+ runNode: boolean;
37
40
  /** The bound session's full visible message list (cycle-aware), used to seed
38
41
  * the open consolidated-reply turn. */
39
42
  transcriptMessages: (session: BrokerSession) => readonly unknown[];
@@ -1 +1 @@
1
- var k=Object.defineProperty;var l=(d,e)=>k(d,"name",{value:e,configurable:!0});import{mkdirSync as v,writeFileSync as I}from"node:fs";import{join as A}from"node:path";import{nodeRunJobDir as _}from"../../canvas/paths.js";import{emitEvent as u}from"../../events/emit.js";import{operationIdContext as c}from"../../events/operation-id.js";import{installPersonaTransitionGate as w}from"../broker-persona-guidance.js";import{installRetryCardElision as E}from"./retry-card-elision.js";import{abortSessionFromExtension as T}from"./extension-abort.js";import{brokerRecovery as S,mutateBrokerFault as x,mutateBrokerProviderRetry as F,postBrokerIdle as P,settleBroker as V}from"./daemon-ops.js";import{brokerSignalClient as C}from"./signal-stream.js";const f=2,m=3e4,b=Symbol("replacement-deadline-exceeded"),N="(interrupted)",g=Symbol.for("crtr.trailing-thinking-guard");function M(d){for(let e=d.length-1;e>=0;e--){const t=d[e];if(t!==void 0){if(t.type==="text"){if(t.text.trim().length===0)continue;return t}if(t.type==="thinking"){const i=t.thinkingSignature,n=typeof i=="string"&&i.trim().length>0;if(t.redacted!==!0&&t.thinking.trim().length===0&&!n)continue;return t}return t}}}l(M,"lastSerializedBlock");function y(d){return d.map(e=>e.role!=="assistant"||e.stopReason==="error"||e.stopReason==="aborted"||M(e.content)?.type!=="thinking"?e:{...e,content:[...e.content,{type:"text",text:N}]})}l(y,"guardTrailingThinking");function q(d){const e=d.convertToLlm;if(e[g]===!0)return;const t=l(i=>{const n=e(i);return n instanceof Promise?n.then(y):y(n)},"wrapped");t[g]=!0,d.convertToLlm=t}l(q,"installTrailingThinkingGuard");class X{static{l(this,"RebindDriver")}deps;sessionValue;servicesValue;unsubscribe;installed;nextSubscriptionGeneration=0;rebindTail=Promise.resolve();latestRebindRequest=0;replacementTail=Promise.resolve();admittedReplacements=0;rebindsInFlight=0;rebinding;agentRunActiveValue=!1;shutdownRequested=!1;constructor(e){this.deps=e,this.sessionValue=e.session,this.servicesValue=e.services}session(){return this.sessionValue}services(){return this.servicesValue}installedGeneration(){return this.installed}isAgentRunActive(){return this.agentRunActiveValue}isReplacing(){return this.admittedReplacements>0}pendingRebind(){return this.rebinding?.done}requestShutdown(){if(!this.agentRunActiveValue){this.deps.disposeAndExit("shutdown-hook");return}this.shutdownRequested=!0}setRebindSession(){this.deps.runtime?.setRebindSession(e=>this.enqueueRebind(e,this.deps.runtime.services))}enqueueRebind(e,t){const i=++this.latestRebindRequest;if(this.rebindsInFlight++===0){let o;this.rebinding={done:new Promise(a=>{o=a}),resolve:o}}const n=this.rebindTail.then(async()=>{if(i!==this.latestRebindRequest)return;if(this.installed!==void 0){typeof this.sessionValue.sessionFile=="string"&&this.sessionValue.sessionFile!==""&&await F(this.deps.nodeId,{op:"invalidate",session_id:this.sessionValue.sessionManager.getSessionId()});const s=(await S(this.deps.nodeId)).fault;s?.link==="pi\u2192provider"&&s.retry.disposition==="auto"&&s.retry.by==="daemon"&&await x(this.deps.nodeId,{op:"clear",link:"pi\u2192provider",preserve_episode:!0})}this.installed?.executingTools.clear(),this.removeInstalledSubscription(),this.deps.setActiveSession(e),this.deps.projection().reset(),this.deps.faultRetry().reset(),this.agentRunActiveValue=!1,this.deps.onSessionRebind(),this.deps.toolGroups.resetForSession(),this.sessionValue=e,this.servicesValue=t;const o=e.agent;o&&(w(this.deps.nodeId,{agent:o,sessionManager:e.sessionManager}),E(o),o.steeringMode="all",o.followUpMode="all",q(o));const a={id:++this.nextSubscriptionGeneration,session:e,fallback:{blocked:!1},stagedProviderAgentEnd:null,stagedWatchdogAbort:!1,stagedRefreshAbort:!1,stagedOverflowFailure:null,executingTools:new Set};if(this.unsubscribe=this.sessionValue.subscribe(s=>{if(!this.deps.projection().isCurrent(a))return;if(c.current()!==void 0){this.executeSubscriptionCallback(a,s);return}const r=a.fallback;if(s.type==="turn_start"&&r.storedId!==void 0){r.storedId=void 0,r.blocked=!0,c.fresh(()=>{u({level:"warn",event:"broker.turn.fallback_overlap",fields:{generation:a.id,engine_event:s.type}}),this.executeSubscriptionCallback(a,s)});return}if(s.type==="turn_start"&&!r.blocked){c.fresh(R=>{r.storedId=R,this.executeSubscriptionCallback(a,s)});return}const h=r.storedId;if(h!==void 0&&!r.blocked){c.run(h,()=>{this.executeSubscriptionCallback(a,s),s.type==="agent_settled"&&r.storedId===h&&(r.storedId=void 0)});return}this.executeSubscriptionCallback(a,s)}),this.installed=a,await e.bindExtensions({uiContext:this.deps.uiContext(),mode:"rpc",abortHandler:l(()=>T(e,()=>{const s=this.installed;s?.session===e&&(s.stagedRefreshAbort=!0)}),"abortHandler"),shutdownHandler:l(()=>this.requestShutdown(),"shutdownHandler")}),i===this.latestRebindRequest){if(this.deps.cfg.editorName!==void 0&&this.deps.cfg.editorName!=="")try{e.setSessionName(this.deps.cfg.editorName)}catch{}try{this.deps.turnReplies.seed(this.deps.transcriptMessages(e))}catch(s){u({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`turn reply seed failed: ${String(s)}`}})}this.deps.projection().rearmIfStreaming(a),this.captureSystemPrompt(e)}}).finally(()=>{--this.rebindsInFlight>0||(this.rebinding?.resolve(),this.rebinding=void 0)});return this.rebindTail=n.catch(()=>{}),n}runReplacement(e,t,i){const n=this.deps.runtime;if(n===void 0){this.deps.registry.sendFrame(e,{type:"error",code:"engine_error",message:"session replacement unsupported at this engine pin"});return}if(this.admittedReplacements>=f){c.fresh(()=>u({level:"warn",event:"broker.session_replacement.rejected",outcome:"skipped",fields:{op:t,reason:"capacity",admitted:f,cap:f}})),this.ackTo(e,t,!1,`session replacement busy (${f} active or queued)`);return}this.admittedReplacements++;const o=this.replacementTail.then(async()=>{try{await c.fresh(async()=>{const a=process.hrtime.bigint();let s;try{const p=new Promise(h=>{s=setTimeout(()=>h(b),m)}),r=await Promise.race([i(n),p]);if(r===b){u({level:"error",event:"broker.session_replacement.timed_out",outcome:"failed",duration_ms:Number(process.hrtime.bigint()-a)/1e6,fields:{op:t,deadline_ms:m,mutation_outcome:"indeterminate"}}),this.ackTo(e,t,!1,"session replacement timed out; broker restarting because session state is indeterminate"),this.deps.disposeAndExit("session-replacement-timeout",1);return}r.cancelled||this.deps.reWelcomeAll(),this.ackTo(e,t,!r.cancelled,r.cancelled?r.detail??"cancelled by extension":void 0)}catch(p){u({level:"error",event:"broker.session_replacement.failed",outcome:"failed",error:p,fields:{op:t,mutation_outcome:"indeterminate"}}),this.ackTo(e,t,!1,"session replacement failed; broker restarting because session state is indeterminate"),this.deps.disposeAndExit("session-replacement-failed",1)}finally{clearTimeout(s)}})}finally{this.admittedReplacements--,this.admittedReplacements===0&&this.deps.onReplacementIdle?.()}});this.replacementTail=o.catch(()=>{})}removeInstalledSubscription(){this.unsubscribe?.(),this.unsubscribe=void 0,this.installed=void 0}captureSystemPrompt(e){try{const t=_(this.deps.nodeId);v(t,{recursive:!0}),I(A(t,"system-prompt.md"),e.systemPrompt,"utf8")}catch{}}executeSubscriptionCallback(e,t){try{t.type==="agent_start"&&(this.agentRunActiveValue=!0),t.type==="agent_end"&&(e.stagedProviderAgentEnd=t,e.stagedOverflowFailure=null),t.type==="compaction_end"&&t.reason==="overflow"&&t.willRetry===!1&&typeof t.errorMessage=="string"&&(e.stagedOverflowFailure={errorMessage:t.errorMessage}),t.type==="agent_settled"&&(this.agentRunActiveValue=!1,this.publishAndScheduleProviderRecovery(e)),this.deps.toolGroups.observeToolGroupEvent(t),this.deps.turnReplies.observe(t),this.deps.projection().relayEvent(t,e)}catch(i){u({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`event relay threw: ${String(i)}`}})}}async publishAndScheduleProviderRecovery(e){try{if(await this.deps.faultRetry().publishSettledProviderFault(e),this.shutdownRequested){this.deps.disposeAndExit("shutdown-after-settle");return}const t=this.deps.faultRetry();await t.schedule()!=="armed"&&!t.fallbackInFlight()&&await t.faultIsFatal()&&await this.idleAfterFatalFault(e)}catch(t){this.deps.faultRetry().abandonTurnEnd(),u({level:"error",event:"broker.daemon.operation.failed",node_id:this.deps.nodeId,error:t,fields:{operation:"provider_fault_settlement"}}),this.shutdownRequested&&this.deps.disposeAndExit("shutdown-after-settle")}}async idleAfterFatalFault(e){const t=l(()=>this.installed===e&&!this.agentRunActiveValue&&!e.session.isStreaming,"stillSettled");!t()||(await V(this.deps.nodeId,{stopReason:"error",pushedFinal:!1,faultSettled:!0})).action!=="stay_resident"||!t()||(await P(this.deps.nodeId),C().stop())}ackTo(e,t,i=!0,n,o){this.deps.registry.sendFrame(e,{type:"ack",for:t,ok:i,...n!==void 0?{detail:n}:{},...o!==void 0?{id:o}:{}})}}export{X as RebindDriver};
1
+ var k=Object.defineProperty;var l=(a,e)=>k(a,"name",{value:e,configurable:!0});import{mkdirSync as v,writeFileSync as I}from"node:fs";import{join as A}from"node:path";import{nodeRunJobDir as _}from"../../canvas/paths.js";import{emitEvent as u}from"../../events/emit.js";import{operationIdContext as c}from"../../events/operation-id.js";import{installPersonaTransitionGate as w}from"../broker-persona-guidance.js";import{installRetryCardElision as E}from"./retry-card-elision.js";import{abortSessionFromExtension as T}from"./extension-abort.js";import{brokerRecovery as S,mutateBrokerFault as x,mutateBrokerProviderRetry as F,postBrokerIdle as P,settleBroker as V}from"./daemon-ops.js";import{brokerSignalClient as C}from"./signal-stream.js";const f=2,m=3e4,b=Symbol("replacement-deadline-exceeded"),N="(interrupted)",g=Symbol.for("crtr.trailing-thinking-guard");function M(a){for(let e=a.length-1;e>=0;e--){const t=a[e];if(t!==void 0){if(t.type==="text"){if(t.text.trim().length===0)continue;return t}if(t.type==="thinking"){const i=t.thinkingSignature,o=typeof i=="string"&&i.trim().length>0;if(t.redacted!==!0&&t.thinking.trim().length===0&&!o)continue;return t}return t}}}l(M,"lastSerializedBlock");function y(a){return a.map(e=>e.role!=="assistant"||e.stopReason==="error"||e.stopReason==="aborted"||M(e.content)?.type!=="thinking"?e:{...e,content:[...e.content,{type:"text",text:N}]})}l(y,"guardTrailingThinking");function q(a){const e=a.convertToLlm;if(e[g]===!0)return;const t=l(i=>{const o=e(i);return o instanceof Promise?o.then(y):y(o)},"wrapped");t[g]=!0,a.convertToLlm=t}l(q,"installTrailingThinkingGuard");class X{static{l(this,"RebindDriver")}deps;sessionValue;servicesValue;unsubscribe;installed;nextSubscriptionGeneration=0;rebindTail=Promise.resolve();latestRebindRequest=0;replacementTail=Promise.resolve();admittedReplacements=0;rebindsInFlight=0;rebinding;agentRunActiveValue=!1;shutdownRequested=!1;constructor(e){this.deps=e,this.sessionValue=e.session,this.servicesValue=e.services}session(){return this.sessionValue}services(){return this.servicesValue}installedGeneration(){return this.installed}isAgentRunActive(){return this.agentRunActiveValue}isReplacing(){return this.admittedReplacements>0}pendingRebind(){return this.rebinding?.done}requestShutdown(){if(!this.agentRunActiveValue){this.deps.disposeAndExit("shutdown-hook");return}this.shutdownRequested=!0}setRebindSession(){this.deps.runtime?.setRebindSession(e=>this.enqueueRebind(e,this.deps.runtime.services))}enqueueRebind(e,t){const i=++this.latestRebindRequest;if(this.rebindsInFlight++===0){let r;this.rebinding={done:new Promise(d=>{r=d}),resolve:r}}const o=this.rebindTail.then(async()=>{if(i!==this.latestRebindRequest)return;if(this.installed!==void 0){typeof this.sessionValue.sessionFile=="string"&&this.sessionValue.sessionFile!==""&&await F(this.deps.nodeId,{op:"invalidate",session_id:this.sessionValue.sessionManager.getSessionId()});const s=(await S(this.deps.nodeId)).fault;s?.link==="pi\u2192provider"&&s.retry.disposition==="auto"&&s.retry.by==="daemon"&&await x(this.deps.nodeId,{op:"clear",link:"pi\u2192provider",preserve_episode:!0})}this.installed?.executingTools.clear(),this.removeInstalledSubscription(),this.deps.setActiveSession(e),this.deps.projection().reset(),this.deps.faultRetry().reset(),this.agentRunActiveValue=!1,this.deps.onSessionRebind(),this.deps.toolGroups.resetForSession(),this.sessionValue=e,this.servicesValue=t;const r=e.agent;r&&(w(this.deps.nodeId,{agent:r,sessionManager:e.sessionManager}),E(r),r.steeringMode="all",this.deps.runNode&&this.deps.runSteer.bind(r,e),r.followUpMode="all",q(r));const d={id:++this.nextSubscriptionGeneration,session:e,fallback:{blocked:!1},stagedProviderAgentEnd:null,stagedWatchdogAbort:!1,stagedRefreshAbort:!1,stagedOverflowFailure:null,executingTools:new Set};if(this.unsubscribe=this.sessionValue.subscribe(s=>{if(!this.deps.projection().isCurrent(d))return;if(c.current()!==void 0){this.executeSubscriptionCallback(d,s);return}const n=d.fallback;if(s.type==="turn_start"&&n.storedId!==void 0){n.storedId=void 0,n.blocked=!0,c.fresh(()=>{u({level:"warn",event:"broker.turn.fallback_overlap",fields:{generation:d.id,engine_event:s.type}}),this.executeSubscriptionCallback(d,s)});return}if(s.type==="turn_start"&&!n.blocked){c.fresh(R=>{n.storedId=R,this.executeSubscriptionCallback(d,s)});return}const h=n.storedId;if(h!==void 0&&!n.blocked){c.run(h,()=>{this.executeSubscriptionCallback(d,s),s.type==="agent_settled"&&n.storedId===h&&(n.storedId=void 0)});return}this.executeSubscriptionCallback(d,s)}),this.installed=d,await e.bindExtensions({uiContext:this.deps.uiContext(),mode:"rpc",abortHandler:l(()=>T(e,()=>{const s=this.installed;s?.session===e&&(s.stagedRefreshAbort=!0)}),"abortHandler"),shutdownHandler:l(()=>this.requestShutdown(),"shutdownHandler")}),i===this.latestRebindRequest){if(this.deps.cfg.editorName!==void 0&&this.deps.cfg.editorName!=="")try{e.setSessionName(this.deps.cfg.editorName)}catch{}try{this.deps.turnReplies.seed(this.deps.transcriptMessages(e))}catch(s){u({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`turn reply seed failed: ${String(s)}`}})}this.deps.projection().rearmIfStreaming(d),this.captureSystemPrompt(e)}}).finally(()=>{--this.rebindsInFlight>0||(this.rebinding?.resolve(),this.rebinding=void 0)});return this.rebindTail=o.catch(()=>{}),o}runReplacement(e,t,i){const o=this.deps.runtime;if(o===void 0){this.deps.registry.sendFrame(e,{type:"error",code:"engine_error",message:"session replacement unsupported at this engine pin"});return}if(this.admittedReplacements>=f){c.fresh(()=>u({level:"warn",event:"broker.session_replacement.rejected",outcome:"skipped",fields:{op:t,reason:"capacity",admitted:f,cap:f}})),this.ackTo(e,t,!1,`session replacement busy (${f} active or queued)`);return}this.admittedReplacements++;const r=this.replacementTail.then(async()=>{try{await c.fresh(async()=>{const d=process.hrtime.bigint();let s;try{const p=new Promise(h=>{s=setTimeout(()=>h(b),m)}),n=await Promise.race([i(o),p]);if(n===b){u({level:"error",event:"broker.session_replacement.timed_out",outcome:"failed",duration_ms:Number(process.hrtime.bigint()-d)/1e6,fields:{op:t,deadline_ms:m,mutation_outcome:"indeterminate"}}),this.ackTo(e,t,!1,"session replacement timed out; broker restarting because session state is indeterminate"),this.deps.disposeAndExit("session-replacement-timeout",1);return}n.cancelled||this.deps.reWelcomeAll(),this.ackTo(e,t,!n.cancelled,n.cancelled?n.detail??"cancelled by extension":void 0)}catch(p){u({level:"error",event:"broker.session_replacement.failed",outcome:"failed",error:p,fields:{op:t,mutation_outcome:"indeterminate"}}),this.ackTo(e,t,!1,"session replacement failed; broker restarting because session state is indeterminate"),this.deps.disposeAndExit("session-replacement-failed",1)}finally{clearTimeout(s)}})}finally{this.admittedReplacements--,this.admittedReplacements===0&&this.deps.onReplacementIdle?.()}});this.replacementTail=r.catch(()=>{})}removeInstalledSubscription(){this.unsubscribe?.(),this.unsubscribe=void 0,this.installed=void 0}captureSystemPrompt(e){try{const t=_(this.deps.nodeId);v(t,{recursive:!0}),I(A(t,"system-prompt.md"),e.systemPrompt,"utf8")}catch{}}executeSubscriptionCallback(e,t){try{t.type==="agent_start"&&(this.agentRunActiveValue=!0),t.type==="agent_end"&&(e.stagedProviderAgentEnd=t,e.stagedOverflowFailure=null),t.type==="compaction_end"&&t.reason==="overflow"&&t.willRetry===!1&&typeof t.errorMessage=="string"&&(e.stagedOverflowFailure={errorMessage:t.errorMessage}),t.type==="agent_settled"&&(this.agentRunActiveValue=!1,this.publishAndScheduleProviderRecovery(e)),this.deps.toolGroups.observeToolGroupEvent(t),this.deps.turnReplies.observe(t),this.deps.projection().relayEvent(t,e)}catch(i){u({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`event relay threw: ${String(i)}`}})}}async publishAndScheduleProviderRecovery(e){try{if(await this.deps.faultRetry().publishSettledProviderFault(e),this.shutdownRequested){this.deps.disposeAndExit("shutdown-after-settle");return}const t=this.deps.faultRetry();await t.schedule()!=="armed"&&!t.fallbackInFlight()&&await t.faultIsFatal()&&await this.idleAfterFatalFault(e)}catch(t){this.deps.faultRetry().abandonTurnEnd(),u({level:"error",event:"broker.daemon.operation.failed",node_id:this.deps.nodeId,error:t,fields:{operation:"provider_fault_settlement"}}),this.shutdownRequested&&this.deps.disposeAndExit("shutdown-after-settle")}}async idleAfterFatalFault(e){const t=l(()=>this.installed===e&&!this.agentRunActiveValue&&!e.session.isStreaming,"stillSettled");!t()||(await V(this.deps.nodeId,{stopReason:"error",pushedFinal:!1,faultSettled:!0})).action!=="stay_resident"||!t()||(await P(this.deps.nodeId),C().stop())}ackTo(e,t,i=!0,o,r){this.deps.registry.sendFrame(e,{type:"ack",for:t,ok:i,...o!==void 0?{detail:o}:{},...r!==void 0?{id:r}:{}})}}export{X as RebindDriver};
@@ -0,0 +1,15 @@
1
+ import type { Agent } from '@earendil-works/pi-agent-core';
2
+ import type { BrokerSession } from './read-ops.js';
3
+ export type RunSteerItem = {
4
+ text: string;
5
+ messageIds: string[];
6
+ cards: readonly string[];
7
+ };
8
+ export type RunSteerBridge = {
9
+ steerable(): boolean;
10
+ accept(items: readonly RunSteerItem[]): 'accepted' | 'not_steerable';
11
+ takeUnconsumed(): RunSteerItem[];
12
+ bind(agent: Agent, session: BrokerSession): void;
13
+ };
14
+ /** This list is separate from pi's viewer-controlled queue until pi's next step. */
15
+ export declare function createRunSteerBridge(steerable: () => boolean): RunSteerBridge;
@@ -0,0 +1 @@
1
+ var f=Object.defineProperty;var m=(n,t)=>f(n,"name",{value:t,configurable:!0});import{announceMessageOrigin as y}from"../stamp/channel.js";import{SITUATIONAL_CONTEXT_CUSTOM_TYPE as b}from"../../../shared/generated-context.js";function x(n){const t=[];let u;return{steerable:n,accept(a){return n()?(t.push(...a),"accepted"):"not_steerable"},takeUnconsumed(){return t.splice(0)},bind(a,c){const s=a.steeringQueue;if(typeof s?.drain!="function")throw new Error("pi agent has no step-boundary steering drain");u?.();const o=new WeakMap;u=c.subscribe(e=>{if(e.type!=="message_end"||e.message.role!=="user")return;const r=o.get(e.message);r!==void 0&&(o.delete(e.message),c.sessionManager.appendCustomEntry("crtr-turn-input",{message_ids:r.messageIds}),y(r.text,{type:"human"}))});const d=s.drain.bind(s);s.drain=()=>{const e=d(),g=t.splice(0).map(i=>{const p={role:"user",content:[{type:"text",text:i.text}],timestamp:Date.now()};return o.set(p,i),[...i.cards.map(l=>({role:"custom",customType:b,content:l,display:!0,timestamp:Date.now()})),p]});return[...e,...g.flat()]}}}}m(x,"createRunSteerBridge");export{x as createRunSteerBridge};
@@ -23,6 +23,8 @@ export type RunTurnQueue = {
23
23
  drain(): void;
24
24
  /** Whether a run node must not start an inbox turn now. */
25
25
  busy(): boolean;
26
+ /** A live run with no earlier turn queued can accept a step-boundary steer. */
27
+ steerable(): boolean;
26
28
  /** The launch prompt will be dispatched; hold inbox turns behind it. */
27
29
  holdForKickoff(): void;
28
30
  /** The launch prompt ran (or will not run); release held inbox turns. */
@@ -1 +1 @@
1
- var R=Object.defineProperty;var i=(n,e)=>R(n,"name",{value:e,configurable:!0});function A(n){const{runNode:e,session:c,turnAdmission:p}=n,o=[];let f=!1,r=!1;const v=i(()=>n.isReplacing()||n.isAgentRunActive()||c().isStreaming,"engineBusy"),d=i(()=>r||f||v(),"mustQueue"),a=i(()=>{if(d())return;const t=o.shift();t&&g(t.text,t.messageIds)},"drain"),g=i((t,s=[])=>{const l=n.pendingRebind();if(l!==void 0){l.then(()=>g(t,s));return}if(e&&d()){o.push({text:t,messageIds:s});return}e&&(f=!0),p.admit(async h=>{const u=c();if(e&&u.isStreaming)return o.unshift({text:t,messageIds:s}),h(),Promise.resolve();const m=e?await n.prependDeferred(u):[];return e&&s.length+m.length>0&&u.sessionManager.appendCustomEntry("crtr-turn-input",{message_ids:[...m,...s]}),u.prompt(t,{preflightResult:h,...!e&&u.isStreaming?{streamingBehavior:"steer"}:{}})}).catch(n.onError).finally(()=>{e&&(f=!1,a())})},"start");return{start:g,drain:a,busy:i(()=>n.pendingRebind()!==void 0||r||f||n.pendingDispatches()>0||n.isAgentRunActive()||c().isStreaming,"busy"),holdForKickoff:i(()=>{e&&(r=!0)},"holdForKickoff"),releaseKickoff:i(()=>{r&&(r=!1,a())},"releaseKickoff"),queued:i(()=>o,"queued")}}i(A,"createRunTurnQueue");export{A as createRunTurnQueue};
1
+ var A=Object.defineProperty;var e=(n,i)=>A(n,"name",{value:i,configurable:!0});function S(n){const{runNode:i,session:a,turnAdmission:m}=n,s=[];let u=!1,c=!1,r=!1;const p=e(()=>n.isReplacing()||n.isAgentRunActive()||a().isStreaming,"engineBusy"),d=e(()=>r||u||p(),"mustQueue"),g=e(()=>{if(d())return;const t=s.shift();t&&l(t.text,t.messageIds)},"drain"),l=e((t,f=[])=>{const h=n.pendingRebind();if(h!==void 0){h.then(()=>l(t,f));return}if(i&&d()){s.push({text:t,messageIds:f});return}i&&(u=!0),m.admit(async v=>{const o=a();if(i&&o.isStreaming)return s.unshift({text:t,messageIds:f}),v(),Promise.resolve();const R=i?await n.prependDeferred(o):[];return i&&f.length+R.length>0&&o.sessionManager.appendCustomEntry("crtr-turn-input",{message_ids:[...R,...f]}),o.prompt(t,{preflightResult:e(()=>{c=!0,v()},"preflightResult"),...!i&&o.isStreaming?{streamingBehavior:"steer"}:{}})}).catch(n.onError).finally(()=>{i&&(u=!1,c=!1,g())})},"start");return{start:l,drain:g,busy:e(()=>n.pendingRebind()!==void 0||r||u||n.pendingDispatches()>0||n.isAgentRunActive()||a().isStreaming,"busy"),steerable:e(()=>i&&!r&&n.pendingRebind()===void 0&&!n.isReplacing()&&n.pendingDispatches()===0&&n.isAgentRunActive()&&(!u||c)&&s.length===0,"steerable"),holdForKickoff:e(()=>{i&&(r=!0)},"holdForKickoff"),releaseKickoff:e(()=>{r&&(r=!1,g())},"releaseKickoff"),queued:e(()=>s,"queued")}}e(S,"createRunTurnQueue");export{S as createRunTurnQueue};