@nylorun/runtime 0.7.0-beta → 0.9.0-beta

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 (72) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +7 -5
  3. package/dist/adapters/sandbox/microsandbox.d.ts +2 -0
  4. package/dist/adapters/sandbox/microsandbox.js +238 -0
  5. package/dist/adapters/sandbox/virtual.d.ts +4 -0
  6. package/dist/adapters/sandbox/virtual.js +125 -0
  7. package/dist/configuration.d.ts +1 -1
  8. package/dist/configuration.js +1 -1
  9. package/dist/core/executors.d.ts +32 -0
  10. package/dist/core/executors.js +60 -0
  11. package/dist/core/main.js +7 -12
  12. package/dist/core/runtime.d.ts +24 -1
  13. package/dist/core/runtime.js +364 -43
  14. package/dist/core/store.d.ts +14 -1
  15. package/dist/core/store.js +59 -3
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +0 -1
  18. package/dist/mcp/pool.d.ts +13 -1
  19. package/dist/mcp/pool.js +53 -19
  20. package/dist/mcp/snapshot.d.ts +4 -0
  21. package/dist/model/catalog.d.ts +12 -0
  22. package/dist/model/catalog.js +29 -0
  23. package/dist/model/models.d.ts +3 -1
  24. package/dist/model/models.js +4 -2
  25. package/dist/model/pi-model.d.ts +9 -3
  26. package/dist/model/pi-model.js +47 -11
  27. package/dist/model/settings.d.ts +0 -2
  28. package/dist/model/settings.js +0 -47
  29. package/dist/node/index.d.ts +1 -1
  30. package/dist/node/index.js +1 -1
  31. package/dist/sandbox/manager.d.ts +61 -0
  32. package/dist/sandbox/manager.js +239 -0
  33. package/dist/sandbox/policy.d.ts +13 -0
  34. package/dist/sandbox/policy.js +67 -0
  35. package/dist/sandbox/select.d.ts +35 -0
  36. package/dist/sandbox/select.js +76 -0
  37. package/dist/sandbox/tools.d.ts +25 -0
  38. package/dist/sandbox/tools.js +165 -0
  39. package/dist/sandbox/types.d.ts +66 -0
  40. package/dist/vault/service.d.ts +41 -1
  41. package/dist/vault/service.js +291 -4
  42. package/dist/version.d.ts +1 -0
  43. package/dist/version.js +3 -0
  44. package/package.json +9 -4
  45. package/dist/config.d.ts +0 -20
  46. package/dist/model/defaults.d.ts +0 -17
  47. package/dist/model/defaults.js +0 -21
  48. package/dist/model/http-model.d.ts +0 -12
  49. package/dist/model/http-model.js +0 -299
  50. package/dist/node/local-sessions.d.ts +0 -5
  51. package/dist/node/local-sessions.js +0 -174
  52. package/dist/server/ag-ui.d.ts +0 -8
  53. package/dist/server/ag-ui.js +0 -76
  54. package/dist/server/delivery.d.ts +0 -24
  55. package/dist/server/delivery.js +0 -107
  56. package/dist/server/digests.d.ts +0 -13
  57. package/dist/server/digests.js +0 -39
  58. package/dist/server/host.d.ts +0 -70
  59. package/dist/server/host.js +0 -647
  60. package/dist/session/api.d.ts +0 -10
  61. package/dist/session/api.js +0 -15
  62. package/dist/session/default.d.ts +0 -5
  63. package/dist/session/default.js +0 -30
  64. package/dist/session/handle.d.ts +0 -27
  65. package/dist/session/handle.js +0 -199
  66. package/dist/session/index.d.ts +0 -2
  67. package/dist/session/index.js +0 -2
  68. package/dist/sessions/host.d.ts +0 -39
  69. package/dist/sessions/host.js +0 -359
  70. package/dist/sessions/store.d.ts +0 -41
  71. package/dist/sessions/store.js +0 -33
  72. /package/dist/{config.js → sandbox/types.js} +0 -0
@@ -1,30 +0,0 @@
1
- let defaultRuntime;
2
- let bannerShown = false;
3
- let createRuntime;
4
- /** Wired from `server/host.ts` after `Runtime` is defined (avoids init cycles). */
5
- export function installDefaultRuntimeFactory(factory) {
6
- createRuntime = factory;
7
- }
8
- export function setDefaultRuntime(runtime) {
9
- defaultRuntime = runtime;
10
- }
11
- export function getDefaultRuntime() {
12
- if (defaultRuntime)
13
- return defaultRuntime;
14
- if (!createRuntime)
15
- throw new Error("Default Runtime factory is not installed");
16
- defaultRuntime = createRuntime();
17
- maybeBanner();
18
- return defaultRuntime;
19
- }
20
- function maybeBanner() {
21
- if (bannerShown || typeof process === "undefined")
22
- return;
23
- if (process.env.NYLORUN_QUIET === "1")
24
- return;
25
- bannerShown = true;
26
- const model = process.env.MODEL_PROVIDER && process.env.MODEL
27
- ? `${process.env.MODEL_PROVIDER}/${process.env.MODEL}`
28
- : "unset";
29
- console.info(`nylorun ▸ local runtime · loop runs in this process · sessions in memory · model ${model} (your key)`);
30
- }
@@ -1,27 +0,0 @@
1
- import type { BuiltAgent, JsonObject, JsonValue, ModelAdapter } from "@nylorun/core/define";
2
- import type { Session } from "@nylorun/harness";
3
- import type { RuntimeConfig } from "../config.js";
4
- import type { SessionHost } from "../sessions/host.js";
5
- export type OpenSessionOptions = {
6
- readonly id?: string;
7
- /** Trusted host bag — keep the name `info` (never `user`). */
8
- readonly info?: unknown;
9
- /** Initial durable session memory (`ExecutionState.state`). */
10
- readonly state?: JsonObject;
11
- };
12
- export type SessionHandle<Output = unknown> = Session<{
13
- readonly id: string;
14
- }, Output> & {
15
- readonly agentId: string;
16
- };
17
- export type SessionRuntime = {
18
- readonly host: SessionHost;
19
- readonly config: RuntimeConfig;
20
- resolveModel(): ModelAdapter;
21
- };
22
- export declare function createSessionHandle(runtime: SessionRuntime, agent: BuiltAgent<any, any>, options?: OpenSessionOptions): SessionHandle;
23
- /** Resolve a wait by harness waitId (H6). */
24
- export declare function resolveWait(runtime: SessionRuntime, agent: BuiltAgent<any, any>, sessionId: string, waitId: string, value?: JsonValue, options?: {
25
- readonly info?: unknown;
26
- readonly onModelCall?: ModelAdapter;
27
- }): Promise<void>;
@@ -1,199 +0,0 @@
1
- export function createSessionHandle(runtime, agent, options = {}) {
2
- const id = options.id ?? crypto.randomUUID();
3
- let seedState = options.state;
4
- const info = options.info;
5
- const submitOptions = () => {
6
- const sessionState = seedState;
7
- seedState = undefined;
8
- return {
9
- info: info === undefined
10
- ? { sessionId: id }
11
- : { ...info, sessionId: id },
12
- onModelCall: runtime.resolveModel(),
13
- ...(sessionState ? { sessionState } : {}),
14
- };
15
- };
16
- const handle = {
17
- id,
18
- agentId: agent.id,
19
- async input(message) {
20
- try {
21
- const result = await runtime.host.submit(agent, id, message, submitOptions());
22
- const cursor = String((await runtime.host.read(agent.id, id))?.events.at(-1)?.seq ?? 0);
23
- return {
24
- status: "accepted",
25
- turnId: result.state.plan?.turnId ?? result.state.executionId,
26
- cursor,
27
- };
28
- }
29
- catch (error) {
30
- return {
31
- status: "rejected",
32
- error: {
33
- code: error && typeof error === "object" && "code" in error
34
- ? String(error.code)
35
- : "runtime.input-rejected",
36
- message: error instanceof Error ? error.message : String(error),
37
- },
38
- };
39
- }
40
- },
41
- async *stream(opts) {
42
- let after = opts?.after ? Number(opts.after) : 0;
43
- if (!Number.isFinite(after) || after < 0)
44
- after = 0;
45
- const queue = [];
46
- let notify;
47
- const wake = () => {
48
- notify?.();
49
- notify = undefined;
50
- };
51
- const unsubscribe = runtime.host.subscribe(agent.id, id, (event) => {
52
- queue.push(event);
53
- wake();
54
- });
55
- try {
56
- const existing = await runtime.host.read(agent.id, id);
57
- for (const event of existing?.events ?? [])
58
- if (event.seq > after)
59
- queue.push(event);
60
- for (;;) {
61
- while (queue.length) {
62
- const event = queue.shift();
63
- if (event.seq <= after)
64
- continue;
65
- after = event.seq;
66
- const mapped = mapEvent(event, id);
67
- if (mapped)
68
- yield mapped;
69
- }
70
- await new Promise((resolve) => {
71
- notify = resolve;
72
- });
73
- }
74
- }
75
- finally {
76
- unsubscribe();
77
- }
78
- },
79
- async history() {
80
- const document = await runtime.host.read(agent.id, id);
81
- return (document?.events ?? [])
82
- .filter((event) => ["final", "interaction.required", "error", "cancelled"].includes(event.type))
83
- .map((event) => event.payload);
84
- },
85
- async approve(interactionId, approved) {
86
- await runtime.host.submit(agent, id, { kind: "approve", interactionId, approved }, submitOptions());
87
- },
88
- async respond(interactionId, value) {
89
- await runtime.host.submit(agent, id, { kind: "respond", interactionId, value }, submitOptions());
90
- },
91
- async cancel() {
92
- await runtime.host.cancel(agent, id);
93
- },
94
- };
95
- return handle;
96
- }
97
- /** Resolve a wait by harness waitId (H6). */
98
- export async function resolveWait(runtime, agent, sessionId, waitId, value, options) {
99
- const input = {
100
- kind: "wait-resolve",
101
- waitId,
102
- ...(value === undefined ? {} : { value }),
103
- };
104
- await runtime.host.submit(agent, sessionId, input, {
105
- info: options?.info,
106
- onModelCall: options?.onModelCall ?? runtime.resolveModel(),
107
- });
108
- }
109
- function mapEvent(event, sessionId) {
110
- const turnId = typeof event.payload.turnId === "string"
111
- ? event.payload.turnId
112
- : typeof event.payload.executionId === "string"
113
- ? event.payload.executionId
114
- : sessionId;
115
- const cursor = String(event.seq);
116
- const at = event.ts;
117
- switch (event.type) {
118
- case "session.run.started":
119
- return { type: "turn.started", cursor, at, sessionId, turnId };
120
- case "tool.started":
121
- return {
122
- type: "tool.started",
123
- tool: String(event.payload.toolName ?? "tool"),
124
- sessionId,
125
- turnId,
126
- };
127
- case "tool.progress":
128
- return {
129
- type: "tool.progress",
130
- tool: String(event.payload.toolName ?? "tool"),
131
- sessionId,
132
- turnId,
133
- };
134
- case "tool.completed":
135
- return {
136
- type: event.payload.outcome === "failed" ? "tool.failed" : "tool.completed",
137
- tool: String(event.payload.toolName ?? "tool"),
138
- sessionId,
139
- turnId,
140
- };
141
- case "interaction.required": {
142
- const interaction = event.payload.interaction;
143
- return {
144
- type: "interaction.required",
145
- id: String(interaction?.id ?? ""),
146
- prompt: String(interaction?.prompt ?? ""),
147
- ...(interaction?.metadata ? { options: interaction.metadata } : {}),
148
- sessionId,
149
- turnId,
150
- };
151
- }
152
- case "final":
153
- return {
154
- type: "turn.settled",
155
- result: {
156
- status: "completed",
157
- output: event.payload.output,
158
- },
159
- sessionId,
160
- turnId,
161
- };
162
- case "error":
163
- return {
164
- type: "turn.settled",
165
- result: {
166
- status: "failed",
167
- error: {
168
- code: String(event.payload.code ?? "execution.failed"),
169
- message: String(event.payload.message ?? "failed"),
170
- },
171
- },
172
- sessionId,
173
- turnId,
174
- };
175
- case "cancelled":
176
- return {
177
- type: "turn.settled",
178
- result: { status: "cancelled" },
179
- sessionId,
180
- turnId,
181
- };
182
- default:
183
- if (event.type === "model.requested" || event.type === "model.completed")
184
- return {
185
- type: "status",
186
- status: "thinking",
187
- sessionId,
188
- turnId,
189
- };
190
- if (event.type.startsWith("tool."))
191
- return {
192
- type: "status",
193
- status: "calling_tool",
194
- sessionId,
195
- turnId,
196
- };
197
- return undefined;
198
- }
199
- }
@@ -1,2 +0,0 @@
1
- export { createSessionHandle, resolveWait, type OpenSessionOptions, type SessionHandle, type SessionRuntime, } from "./handle.js";
2
- export { getDefaultRuntime, setDefaultRuntime, installDefaultRuntimeFactory, } from "./default.js";
@@ -1,2 +0,0 @@
1
- export { createSessionHandle, resolveWait, } from "./handle.js";
2
- export { getDefaultRuntime, setDefaultRuntime, installDefaultRuntimeFactory, } from "./default.js";
@@ -1,39 +0,0 @@
1
- import type { BuiltAgent, JsonObject, ModelAdapter } from "@nylorun/core/define";
2
- import type { ExecutionInput, ExecutionState, RunResult } from "@nylorun/harness";
3
- import type { CanonicalEvent, SessionStore, StoredSession, SessionSummary } from "./store.js";
4
- export interface SubmitOptions {
5
- readonly runId?: string;
6
- readonly info?: unknown;
7
- readonly signal?: AbortSignal;
8
- readonly onModelCall: ModelAdapter;
9
- readonly secrets?: readonly string[];
10
- readonly onEvent?: (event: CanonicalEvent) => void;
11
- readonly started?: Record<string, unknown>;
12
- /** Seed `ExecutionState.state` when creating a new checkpoint (openSession). */
13
- readonly sessionState?: JsonObject;
14
- }
15
- /** The built-in single-process coordinator. Storage adapters do not implement execution. */
16
- export declare class SessionHost {
17
- readonly store: SessionStore;
18
- private readonly queues;
19
- private readonly generations;
20
- private readonly active;
21
- private readonly listeners;
22
- private closed;
23
- constructor(store: SessionStore);
24
- private key;
25
- read(agentId: string, sessionId: string): Promise<StoredSession | undefined>;
26
- list(agentId: string): Promise<readonly SessionSummary[]>;
27
- delete(agentId: string, sessionId: string): Promise<boolean>;
28
- subscribe(agentId: string, sessionId: string, listener: (event: CanonicalEvent) => void): () => void;
29
- submit(agent: BuiltAgent<any, any>, sessionId: string, input: ExecutionInput, options: SubmitOptions): Promise<RunResult<any>>;
30
- cancel(agent: BuiltAgent<any, any>, sessionId: string): Promise<void>;
31
- interrupt(agent: BuiltAgent<any, any>, sessionId: string, input: ExecutionInput, options: SubmitOptions): Promise<RunResult<any>>;
32
- close(): Promise<void>;
33
- }
34
- /**
35
- * Map DX `wait-resolve` onto harness approve/respond/settle using wait metadata.
36
- * Engine `ExecutionInput` already includes `wait-resolve`; resume is wired via
37
- * existing interaction/deferred kinds until full wait-id resume lands.
38
- */
39
- export declare function resolveEngineInput(state: ExecutionState | undefined, input: ExecutionInput): ExecutionInput;
@@ -1,359 +0,0 @@
1
- import { HarnessError, isHarnessError } from "@nylorun/core/define";
2
- import { bindingFromAgent, createRunState, run, } from "@nylorun/harness/run";
3
- import { scrub } from "../redact.js";
4
- /** The built-in single-process coordinator. Storage adapters do not implement execution. */
5
- export class SessionHost {
6
- store;
7
- queues = new Map();
8
- generations = new Map();
9
- active = new Map();
10
- listeners = new Map();
11
- closed = false;
12
- constructor(store) {
13
- this.store = store;
14
- }
15
- key(agentId, sessionId) {
16
- return JSON.stringify([agentId, sessionId]);
17
- }
18
- async read(agentId, sessionId) {
19
- const document = await this.store.get(agentId, sessionId);
20
- if (document?.active && !this.active.has(this.key(agentId, sessionId)))
21
- return { ...document, status: "interrupted" };
22
- return document;
23
- }
24
- async list(agentId) {
25
- return (await this.store.list(agentId)).map((session) => session.status === "running" &&
26
- !this.active.has(this.key(agentId, session.session))
27
- ? { ...session, status: "interrupted" }
28
- : session);
29
- }
30
- async delete(agentId, sessionId) {
31
- const key = this.key(agentId, sessionId);
32
- if (this.active.has(key))
33
- throw new Error("Cannot delete a session while a run is active");
34
- if ("delete" in this.store && typeof this.store.delete === "function")
35
- return this.store.delete(agentId, sessionId);
36
- const existing = await this.store.get(agentId, sessionId);
37
- if (!existing)
38
- return false;
39
- // Stores without delete: archive by removing via put tombstone is not supported;
40
- // require ManagedSessionStore.delete or memorySessions.delete.
41
- throw new Error("Session store does not support delete");
42
- }
43
- subscribe(agentId, sessionId, listener) {
44
- const key = this.key(agentId, sessionId);
45
- let listeners = this.listeners.get(key);
46
- if (!listeners) {
47
- listeners = new Set();
48
- this.listeners.set(key, listeners);
49
- }
50
- listeners.add(listener);
51
- return () => {
52
- listeners.delete(listener);
53
- if (!listeners.size)
54
- this.listeners.delete(key);
55
- };
56
- }
57
- submit(agent, sessionId, input, options) {
58
- const key = this.key(agent.id, sessionId);
59
- const generation = this.generations.get(key) ?? 0;
60
- const prior = this.queues.get(key) ?? Promise.resolve();
61
- const work = prior
62
- .catch(() => { })
63
- .then(async () => {
64
- if ((this.generations.get(key) ?? 0) !== generation)
65
- throw new Error("Queued input cancelled by interruption");
66
- if (this.closed)
67
- throw new Error("Runtime is closed");
68
- options.signal?.throwIfAborted();
69
- const stored = await this.store.get(agent.id, sessionId);
70
- // Cancellation/shutdown can arrive while an asynchronous store is loading.
71
- if ((this.generations.get(key) ?? 0) !== generation)
72
- throw new Error("Queued input cancelled by interruption");
73
- if (this.closed)
74
- throw new Error("Runtime is closed");
75
- options.signal?.throwIfAborted();
76
- if (stored?.active ||
77
- stored?.status === "interrupted" ||
78
- stored?.status === "archived")
79
- throw new Error("This session is interrupted or archived. Reconcile external effects or start a new session.");
80
- const controller = new AbortController();
81
- this.active.set(key, controller);
82
- const abort = () => controller.abort(options.signal?.reason);
83
- options.signal?.addEventListener("abort", abort, { once: true });
84
- if (options.signal?.aborted)
85
- abort();
86
- const now = Date.now();
87
- const runId = options.runId ?? crypto.randomUUID();
88
- let document = stored ?? {
89
- version: 1,
90
- id: sessionId,
91
- agentId: agent.id,
92
- status: "running",
93
- startedAt: now,
94
- updatedAt: now,
95
- events: [],
96
- };
97
- const title = typeof input === "string"
98
- ? input
99
- : "text" in input
100
- ? input.text
101
- : "content" in input
102
- ? input.content
103
- .filter((part) => part.type === "text")
104
- .map((part) => part.text)
105
- .join(" ")
106
- : undefined;
107
- const events = [...document.events];
108
- let sequence = events.at(-1)?.seq ?? 0;
109
- const add = (type, payload, publish = true) => {
110
- const event = {
111
- session: sessionId,
112
- seq: ++sequence,
113
- ts: new Date().toISOString(),
114
- type,
115
- payload: scrub({ ...payload, runId }, options.secrets ?? []),
116
- };
117
- events.push(event);
118
- if (publish)
119
- publishEvent(event);
120
- return event;
121
- };
122
- const publishEvent = (event) => {
123
- for (const listener of [
124
- options.onEvent,
125
- ...(this.listeners.get(key) ?? []),
126
- ]) {
127
- try {
128
- listener?.(event);
129
- }
130
- catch {
131
- /* Delivery cannot acknowledge or block persistence. */
132
- }
133
- }
134
- };
135
- const save = async () => {
136
- document = {
137
- ...document,
138
- updatedAt: Date.now(),
139
- events: [...events],
140
- };
141
- await this.store.put(agent.id, sessionId, document);
142
- };
143
- try {
144
- document = {
145
- ...document,
146
- status: "running",
147
- active: { runId, input },
148
- ...(document.title === undefined && title
149
- ? { title: title.slice(0, 120) }
150
- : {}),
151
- };
152
- add("session.run.started", options.started ?? {
153
- input_kind: typeof input === "string"
154
- ? "user-message"
155
- : "kind" in input
156
- ? input.kind
157
- : "user-message",
158
- ...(typeof input === "string" ? { input } : {}),
159
- });
160
- await save();
161
- const binding = bindingFromAgent(agent);
162
- const engineInput = resolveEngineInput(document.state, input);
163
- const state = ensureEngineState(binding, document.state, options.sessionState);
164
- const result = await run({
165
- binding,
166
- state,
167
- input: engineInput,
168
- info: options.info,
169
- signal: controller.signal,
170
- onModelCall: options.onModelCall,
171
- onEvent: (event) => {
172
- add(event.type, event);
173
- },
174
- record: async (next) => {
175
- document = { ...document, state: next };
176
- await save();
177
- },
178
- });
179
- const { active: _, ...rest } = document;
180
- document = {
181
- ...rest,
182
- state: result.state,
183
- status: result.status === "paused" ? "waiting" : result.status,
184
- };
185
- const terminal = result.status === "completed"
186
- ? add("final", { output: result.output }, false)
187
- : result.status === "paused"
188
- ? add("interaction.required", {
189
- pending: result.pending,
190
- ...(result.pending.find((item) => item.interaction)
191
- ?.interaction
192
- ? {
193
- interaction: result.pending.find((item) => item.interaction).interaction,
194
- }
195
- : {}),
196
- }, false)
197
- : add(result.status === "failed" ? "error" : "cancelled", result.status === "failed" ? { ...result.error } : {}, false);
198
- await save();
199
- publishEvent(terminal);
200
- return result;
201
- }
202
- catch (error) {
203
- if (isHarnessError(error) &&
204
- [
205
- "execution.invalid-input",
206
- "execution.invalid-state",
207
- "execution.incompatible",
208
- ].includes(error.code)) {
209
- // Keep the prior continuation, but commit the rejected attempt so event
210
- // sequence numbers already observed by subscribers are never reused.
211
- const rejected = add("error", {
212
- message: error instanceof Error ? error.message : String(error),
213
- }, false);
214
- const { active: _, ...rest } = document;
215
- await this.store.put(agent.id, sessionId, {
216
- ...(stored ?? { ...rest, status: "failed" }),
217
- updatedAt: Date.now(),
218
- events: [...events],
219
- });
220
- publishEvent(rejected);
221
- throw error;
222
- }
223
- // A failed commit must leave the durable active marker intact. Never replay it.
224
- add("error", {
225
- message: error instanceof Error ? error.message : String(error),
226
- });
227
- throw error;
228
- }
229
- finally {
230
- options.signal?.removeEventListener("abort", abort);
231
- this.active.delete(key);
232
- }
233
- });
234
- this.queues.set(key, work);
235
- void work
236
- .finally(() => {
237
- if (this.queues.get(key) === work)
238
- this.queues.delete(key);
239
- })
240
- .catch(() => { });
241
- return work;
242
- }
243
- async cancel(agent, sessionId) {
244
- const key = this.key(agent.id, sessionId);
245
- this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
246
- this.active.get(key)?.abort(new Error("Run cancelled"));
247
- const prior = this.queues.get(key) ?? Promise.resolve();
248
- const work = prior
249
- .catch(() => { })
250
- .then(async () => {
251
- const stored = await this.store.get(agent.id, sessionId);
252
- if (stored?.active || stored?.status === "interrupted")
253
- throw new Error("This session was interrupted; reconcile its external effects before cancellation.");
254
- if (stored?.state?.status !== "paused")
255
- return;
256
- const binding = bindingFromAgent(agent);
257
- const result = await run({
258
- binding,
259
- state: stored.state,
260
- input: { kind: "continue" },
261
- signal: AbortSignal.abort(new Error("Paused execution cancelled")),
262
- onModelCall: async () => {
263
- throw new Error("Cancelled execution cannot call a model");
264
- },
265
- });
266
- const event = {
267
- session: sessionId,
268
- seq: (stored.events.at(-1)?.seq ?? 0) + 1,
269
- ts: new Date().toISOString(),
270
- type: "cancelled",
271
- payload: { executionId: result.state.executionId },
272
- };
273
- await this.store.put(agent.id, sessionId, {
274
- ...stored,
275
- state: result.state,
276
- status: "cancelled",
277
- updatedAt: Date.now(),
278
- events: [...stored.events, event],
279
- });
280
- for (const listener of this.listeners.get(key) ?? []) {
281
- try {
282
- listener(event);
283
- }
284
- catch {
285
- /* Observation cannot change committed cancellation. */
286
- }
287
- }
288
- });
289
- this.queues.set(key, work);
290
- try {
291
- await work;
292
- }
293
- finally {
294
- if (this.queues.get(key) === work)
295
- this.queues.delete(key);
296
- }
297
- }
298
- async interrupt(agent, sessionId, input, options) {
299
- await this.cancel(agent, sessionId);
300
- return this.submit(agent, sessionId, input, options);
301
- }
302
- async close() {
303
- this.closed = true;
304
- for (const controller of this.active.values())
305
- controller.abort(new Error("Runtime closing"));
306
- await Promise.allSettled(this.queues.values());
307
- this.listeners.clear();
308
- if ("close" in this.store && typeof this.store.close === "function")
309
- await this.store.close();
310
- }
311
- }
312
- function ensureEngineState(binding, existing, sessionState) {
313
- if (existing) {
314
- if (!sessionState || Object.keys(existing.state ?? {}).length > 0)
315
- return existing;
316
- return {
317
- ...existing,
318
- state: { ...(existing.state ?? {}), ...sessionState },
319
- };
320
- }
321
- return createRunState(binding, {
322
- ...(sessionState ? { state: sessionState } : {}),
323
- });
324
- }
325
- /**
326
- * Map DX `wait-resolve` onto harness approve/respond/settle using wait metadata.
327
- * Engine `ExecutionInput` already includes `wait-resolve`; resume is wired via
328
- * existing interaction/deferred kinds until full wait-id resume lands.
329
- */
330
- export function resolveEngineInput(state, input) {
331
- if (typeof input !== "object" || !("kind" in input) || input.kind !== "wait-resolve")
332
- return input;
333
- const call = state?.plan?.calls.find((item) => item.wait?.waitId === input.waitId);
334
- if (!call)
335
- throw new HarnessError("execution.invalid-input", "No pending wait matches waitId");
336
- if (call.status === "interaction" && call.interaction) {
337
- if (call.interaction.kind === "approval")
338
- return {
339
- kind: "approve",
340
- interactionId: call.interaction.id,
341
- approved: input.value !== false && input.value !== "false",
342
- };
343
- return {
344
- kind: "respond",
345
- interactionId: call.interaction.id,
346
- value: input.value ?? null,
347
- };
348
- }
349
- if (call.status === "deferred")
350
- return {
351
- kind: "settle",
352
- invocationId: call.invocationId,
353
- outcome: {
354
- kind: "completed",
355
- output: input.value ?? null,
356
- },
357
- };
358
- throw new HarnessError("execution.invalid-input", "Wait is no longer pending");
359
- }