@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.
- package/CHANGELOG.md +50 -0
- package/README.md +7 -5
- package/dist/adapters/sandbox/microsandbox.d.ts +2 -0
- package/dist/adapters/sandbox/microsandbox.js +238 -0
- package/dist/adapters/sandbox/virtual.d.ts +4 -0
- package/dist/adapters/sandbox/virtual.js +125 -0
- package/dist/configuration.d.ts +1 -1
- package/dist/configuration.js +1 -1
- package/dist/core/executors.d.ts +32 -0
- package/dist/core/executors.js +60 -0
- package/dist/core/main.js +7 -12
- package/dist/core/runtime.d.ts +24 -1
- package/dist/core/runtime.js +364 -43
- package/dist/core/store.d.ts +14 -1
- package/dist/core/store.js +59 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mcp/pool.d.ts +13 -1
- package/dist/mcp/pool.js +53 -19
- package/dist/mcp/snapshot.d.ts +4 -0
- package/dist/model/catalog.d.ts +12 -0
- package/dist/model/catalog.js +29 -0
- package/dist/model/models.d.ts +3 -1
- package/dist/model/models.js +4 -2
- package/dist/model/pi-model.d.ts +9 -3
- package/dist/model/pi-model.js +47 -11
- package/dist/model/settings.d.ts +0 -2
- package/dist/model/settings.js +0 -47
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +1 -1
- package/dist/sandbox/manager.d.ts +61 -0
- package/dist/sandbox/manager.js +239 -0
- package/dist/sandbox/policy.d.ts +13 -0
- package/dist/sandbox/policy.js +67 -0
- package/dist/sandbox/select.d.ts +35 -0
- package/dist/sandbox/select.js +76 -0
- package/dist/sandbox/tools.d.ts +25 -0
- package/dist/sandbox/tools.js +165 -0
- package/dist/sandbox/types.d.ts +66 -0
- package/dist/vault/service.d.ts +41 -1
- package/dist/vault/service.js +291 -4
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/package.json +9 -4
- package/dist/config.d.ts +0 -20
- package/dist/model/defaults.d.ts +0 -17
- package/dist/model/defaults.js +0 -21
- package/dist/model/http-model.d.ts +0 -12
- package/dist/model/http-model.js +0 -299
- package/dist/node/local-sessions.d.ts +0 -5
- package/dist/node/local-sessions.js +0 -174
- package/dist/server/ag-ui.d.ts +0 -8
- package/dist/server/ag-ui.js +0 -76
- package/dist/server/delivery.d.ts +0 -24
- package/dist/server/delivery.js +0 -107
- package/dist/server/digests.d.ts +0 -13
- package/dist/server/digests.js +0 -39
- package/dist/server/host.d.ts +0 -70
- package/dist/server/host.js +0 -647
- package/dist/session/api.d.ts +0 -10
- package/dist/session/api.js +0 -15
- package/dist/session/default.d.ts +0 -5
- package/dist/session/default.js +0 -30
- package/dist/session/handle.d.ts +0 -27
- package/dist/session/handle.js +0 -199
- package/dist/session/index.d.ts +0 -2
- package/dist/session/index.js +0 -2
- package/dist/sessions/host.d.ts +0 -39
- package/dist/sessions/host.js +0 -359
- package/dist/sessions/store.d.ts +0 -41
- package/dist/sessions/store.js +0 -33
- /package/dist/{config.js → sandbox/types.js} +0 -0
package/dist/session/default.js
DELETED
|
@@ -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
|
-
}
|
package/dist/session/handle.d.ts
DELETED
|
@@ -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>;
|
package/dist/session/handle.js
DELETED
|
@@ -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
|
-
}
|
package/dist/session/index.d.ts
DELETED
package/dist/session/index.js
DELETED
package/dist/sessions/host.d.ts
DELETED
|
@@ -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;
|
package/dist/sessions/host.js
DELETED
|
@@ -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
|
-
}
|