@telnyx/agent-harness 0.1.0-beta.0
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/README.md +179 -0
- package/dist/approvals.d.ts +140 -0
- package/dist/approvals.js +699 -0
- package/dist/channel.d.ts +178 -0
- package/dist/channel.js +184 -0
- package/dist/contract.d.ts +50 -0
- package/dist/contract.js +278 -0
- package/dist/durable.d.ts +81 -0
- package/dist/durable.js +650 -0
- package/dist/harness.d.ts +199 -0
- package/dist/harness.js +1223 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +19 -0
- package/dist/lifecycle.d.ts +9 -0
- package/dist/lifecycle.js +25 -0
- package/dist/node-adapter.d.ts +71 -0
- package/dist/node-adapter.js +736 -0
- package/dist/ports.d.ts +99 -0
- package/dist/ports.js +3 -0
- package/dist/runtime-adapter.d.ts +15 -0
- package/dist/runtime-adapter.js +70 -0
- package/dist/runtime-config.d.ts +38 -0
- package/dist/runtime-config.js +104 -0
- package/dist/scheduling.d.ts +46 -0
- package/dist/scheduling.js +425 -0
- package/dist/steps.d.ts +52 -0
- package/dist/steps.js +310 -0
- package/dist/tool-context.d.ts +8 -0
- package/dist/tool-context.js +9 -0
- package/dist/workspace.d.ts +151 -0
- package/dist/workspace.js +404 -0
- package/package.json +58 -0
package/dist/contract.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { AGENT_HARNESS_PORTS_VERSION, } from "./ports.js";
|
|
3
|
+
export { AGENT_HARNESS_PORTS_VERSION } from "./ports.js";
|
|
4
|
+
const initialContractState = {
|
|
5
|
+
nested: { keep: true },
|
|
6
|
+
remove: "delete-me",
|
|
7
|
+
};
|
|
8
|
+
async function withFixture(factory, run) {
|
|
9
|
+
const fixture = await factory();
|
|
10
|
+
try {
|
|
11
|
+
await run(fixture);
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
await fixture.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register the executable v2 ports contract against an adapter factory.
|
|
19
|
+
*
|
|
20
|
+
* Runtime and plain-Node adapters call this same function; adapter-specific
|
|
21
|
+
* tests may add stricter checks, but may not weaken these shared semantics.
|
|
22
|
+
*/
|
|
23
|
+
export function defineAgentHarnessPortsContract(label, factory) {
|
|
24
|
+
describe(`${label} — AgentHarnessPorts v2 contract`, () => {
|
|
25
|
+
it("advertises the exact contract version, stable host identity, deterministic clock, and selected environment", async () => {
|
|
26
|
+
await withFixture(factory, async ({ ports, reopen, rewrapLive }) => {
|
|
27
|
+
expect(ports.version).toBe(AGENT_HARNESS_PORTS_VERSION);
|
|
28
|
+
expect(ports.identity.id).toBe("contract-agent");
|
|
29
|
+
expect(ports.clock.now()).toBe(1_700_000_000_000);
|
|
30
|
+
expect(ports.environment).toEqual({ MODEL: "deterministic-model" });
|
|
31
|
+
expect("SECRET" in ports.environment).toBe(false);
|
|
32
|
+
expect(rewrapLive()).not.toBe(ports);
|
|
33
|
+
expect(rewrapLive().activation).toBe(ports.activation);
|
|
34
|
+
const reopened = await reopen();
|
|
35
|
+
expect(reopened.version).toBe(AGENT_HARNESS_PORTS_VERSION);
|
|
36
|
+
expect(reopened.identity.id).toBe("contract-agent");
|
|
37
|
+
expect(reopened.activation).not.toBe(ports.activation);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it("preserves AgentMessage/StoredMessage fields and monotonic insertion order across reopen", async () => {
|
|
41
|
+
await withFixture(factory, async ({ ports, reopen }) => {
|
|
42
|
+
await expect(ports.messages.append({ role: "user", content: "first" })).resolves.toBe(1);
|
|
43
|
+
await expect(ports.messages.append({ role: "assistant", content: "second" })).resolves.toBe(2);
|
|
44
|
+
await expect(ports.messages.appendMany([
|
|
45
|
+
{
|
|
46
|
+
role: "assistant",
|
|
47
|
+
content: "tool request",
|
|
48
|
+
toolCalls: [{ id: "call-1", name: "lookup", args: { q: "x" } }],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
role: "tool",
|
|
52
|
+
content: "tool result",
|
|
53
|
+
name: "lookup",
|
|
54
|
+
toolCallId: "call-1",
|
|
55
|
+
},
|
|
56
|
+
])).resolves.toBe(4);
|
|
57
|
+
const firstRead = await ports.messages.all();
|
|
58
|
+
expect(firstRead.map((message) => message.seq)).toEqual([1, 2, 3, 4]);
|
|
59
|
+
expect(firstRead.map((message) => message.content)).toEqual([
|
|
60
|
+
"first",
|
|
61
|
+
"second",
|
|
62
|
+
"tool request",
|
|
63
|
+
"tool result",
|
|
64
|
+
]);
|
|
65
|
+
expect(firstRead[2]?.toolCalls?.[0]).toEqual({
|
|
66
|
+
id: "call-1",
|
|
67
|
+
name: "lookup",
|
|
68
|
+
args: { q: "x" },
|
|
69
|
+
});
|
|
70
|
+
expect(firstRead.every((message) => message.at instanceof Date)).toBe(true);
|
|
71
|
+
const reopened = await reopen();
|
|
72
|
+
expect((await reopened.messages.all()).map((message) => message.seq)).toEqual([1, 2, 3, 4]);
|
|
73
|
+
expect((await reopened.messages.last(2)).map((message) => message.seq)).toEqual([3, 4]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
it("rolls back rows and sequence state after failed single and batch message writes", async () => {
|
|
77
|
+
await withFixture(factory, async ({ ports }) => {
|
|
78
|
+
const invalid = {
|
|
79
|
+
role: "assistant",
|
|
80
|
+
content: "invalid",
|
|
81
|
+
toolCalls: [{ id: "bad", name: "bad", args: () => "not storage-safe" }],
|
|
82
|
+
};
|
|
83
|
+
await expect(ports.messages.append({ role: "user", content: "kept" })).resolves.toBe(1);
|
|
84
|
+
const beforeSingle = await ports.messages.all();
|
|
85
|
+
await expect(ports.messages.append(invalid)).rejects.toThrow();
|
|
86
|
+
await expect(ports.messages.all()).resolves.toEqual(beforeSingle);
|
|
87
|
+
await expect(ports.messages.count()).resolves.toBe(1);
|
|
88
|
+
await expect(ports.messages.append({ role: "assistant", content: "after single failure" })).resolves.toBe(2);
|
|
89
|
+
const beforeBatch = await ports.messages.all();
|
|
90
|
+
await expect(ports.messages.appendMany([{ role: "user", content: "valid" }, invalid])).rejects.toThrow();
|
|
91
|
+
await expect(ports.messages.all()).resolves.toEqual(beforeBatch);
|
|
92
|
+
await expect(ports.messages.count()).resolves.toBe(2);
|
|
93
|
+
await expect(ports.messages.append({ role: "assistant", content: "after batch failure" })).resolves.toBe(3);
|
|
94
|
+
expect((await ports.messages.all()).map((message) => message.seq)).toEqual([1, 2, 3]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
it("uses durable RFC 7396 merge and replace state semantics across reopen", async () => {
|
|
98
|
+
await withFixture(factory, async ({ ports, reopen }) => {
|
|
99
|
+
await expect(ports.state.merge({ nested: { added: "yes" }, remove: null })).resolves.toEqual({ nested: { keep: true, added: "yes" } });
|
|
100
|
+
const reopened = await reopen();
|
|
101
|
+
await expect(reopened.state.get()).resolves.toEqual({
|
|
102
|
+
nested: { keep: true, added: "yes" },
|
|
103
|
+
});
|
|
104
|
+
await expect(reopened.state.replace({ nested: {}, replacement: true })).resolves.toEqual({ nested: {}, replacement: true });
|
|
105
|
+
await expect(reopened.state.get()).resolves.toEqual({
|
|
106
|
+
nested: {},
|
|
107
|
+
replacement: true,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
it("can reject after merge and replace commit when the post-commit state hook fails", async () => {
|
|
112
|
+
await withFixture(factory, async ({ ports, stateHook }) => {
|
|
113
|
+
stateHook.failNext();
|
|
114
|
+
await expect(ports.state.merge({ nested: { added: "committed-before-hook" }, remove: null })).rejects.toThrow("injected state hook failure");
|
|
115
|
+
await expect(ports.state.get()).resolves.toEqual({
|
|
116
|
+
nested: { keep: true, added: "committed-before-hook" },
|
|
117
|
+
});
|
|
118
|
+
stateHook.failNext();
|
|
119
|
+
await expect(ports.state.replace({ nested: {}, replacement: true })).rejects.toThrow("injected state hook failure");
|
|
120
|
+
await expect(ports.state.get()).resolves.toEqual({
|
|
121
|
+
nested: {},
|
|
122
|
+
replacement: true,
|
|
123
|
+
});
|
|
124
|
+
expect(stateHook.calls()).toEqual([
|
|
125
|
+
{
|
|
126
|
+
prev: initialContractState,
|
|
127
|
+
next: { nested: { keep: true, added: "committed-before-hook" } },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
prev: { nested: { keep: true, added: "committed-before-hook" } },
|
|
131
|
+
next: { nested: {}, replacement: true },
|
|
132
|
+
},
|
|
133
|
+
]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
it("propagates state persistence failures without replacing the last durable value", async () => {
|
|
137
|
+
await withFixture(factory, async ({ ports }) => {
|
|
138
|
+
const before = await ports.state.get();
|
|
139
|
+
await expect(ports.state.replace({ nested: {}, invalid: () => "not storage-safe" })).rejects.toThrow();
|
|
140
|
+
await expect(ports.state.get()).resolves.toEqual(before);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
it("exposes synchronous SQL exec and commits or rolls back transactionSync atomically", async () => {
|
|
144
|
+
await withFixture(factory, async ({ ports, reopen }) => {
|
|
145
|
+
ports.sql.exec("CREATE TABLE ledger (value INTEGER NOT NULL)");
|
|
146
|
+
const result = ports.sql.transactionSync(() => {
|
|
147
|
+
ports.sql.exec("INSERT INTO ledger VALUES (?)", 1);
|
|
148
|
+
ports.sql.exec("INSERT INTO ledger VALUES (?)", 2);
|
|
149
|
+
return "committed";
|
|
150
|
+
});
|
|
151
|
+
expect(result).toBe("committed");
|
|
152
|
+
expect(ports.sql.exec("SELECT value FROM ledger ORDER BY value").toArray()).toEqual([
|
|
153
|
+
{ value: 1 },
|
|
154
|
+
{ value: 2 },
|
|
155
|
+
]);
|
|
156
|
+
const boom = new Error("transaction failed");
|
|
157
|
+
expect(() => ports.sql.transactionSync(() => {
|
|
158
|
+
ports.sql.exec("INSERT INTO ledger VALUES (?)", 3);
|
|
159
|
+
throw boom;
|
|
160
|
+
})).toThrow(boom);
|
|
161
|
+
expect(ports.sql.exec("SELECT count(*) AS count FROM ledger").toArray()).toEqual([
|
|
162
|
+
{ count: 2 },
|
|
163
|
+
]);
|
|
164
|
+
const reopened = await reopen();
|
|
165
|
+
expect(reopened.sql.exec("SELECT count(*) AS count FROM ledger").toArray()).toEqual([
|
|
166
|
+
{ count: 2 },
|
|
167
|
+
]);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
it("maps queue, delayed schedule, recurrence, stable-id upsert, cancel, and pending durability without a run-status ledger", async () => {
|
|
171
|
+
await withFixture(factory, async ({ ports, reopen }) => {
|
|
172
|
+
expect(ports.tasks.delivery).toBe("at-least-once");
|
|
173
|
+
await expect(ports.tasks.queue("process", { version: 1 }, { id: "stable", maxRetries: 2 })).resolves.toBe("stable");
|
|
174
|
+
await expect(ports.tasks.queue("process", { version: 2 }, { id: "stable", maxRetries: 3 })).resolves.toBe("stable");
|
|
175
|
+
await expect(ports.tasks.schedule(5, "later", { delay: true }, { id: "later" })).resolves.toBe("later");
|
|
176
|
+
await expect(ports.tasks.every(3, "repeat", { recurring: true }, { id: "repeat" })).resolves.toBe("repeat");
|
|
177
|
+
const pending = await ports.tasks.list();
|
|
178
|
+
expect(pending).toHaveLength(3);
|
|
179
|
+
expect(pending.every((task) => task.createdAt instanceof Date)).toBe(true);
|
|
180
|
+
expect(pending.find((task) => task.id === "stable")).toMatchObject({
|
|
181
|
+
name: "process",
|
|
182
|
+
payload: { version: 2 },
|
|
183
|
+
due: 1_700_000_000_000,
|
|
184
|
+
attempts: 0,
|
|
185
|
+
maxRetries: 3,
|
|
186
|
+
});
|
|
187
|
+
expect(pending.find((task) => task.id === "later")).toMatchObject({
|
|
188
|
+
due: 1_700_000_005_000,
|
|
189
|
+
everyMs: undefined,
|
|
190
|
+
});
|
|
191
|
+
expect(pending.find((task) => task.id === "repeat")).toMatchObject({
|
|
192
|
+
due: 1_700_000_003_000,
|
|
193
|
+
everyMs: 3_000,
|
|
194
|
+
});
|
|
195
|
+
expect(pending.every((task) => !("status" in task))).toBe(true);
|
|
196
|
+
const reopened = await reopen();
|
|
197
|
+
expect((await reopened.tasks.list()).map((task) => task.id).sort()).toEqual([
|
|
198
|
+
"later",
|
|
199
|
+
"repeat",
|
|
200
|
+
"stable",
|
|
201
|
+
]);
|
|
202
|
+
await expect(reopened.tasks.cancel("later")).resolves.toBe(true);
|
|
203
|
+
await expect(reopened.tasks.cancel("later")).resolves.toBe(false);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
it("propagates scheduler validation failures for invalid recurrence and non-finite deadlines", async () => {
|
|
207
|
+
await withFixture(factory, async ({ ports }) => {
|
|
208
|
+
await expect(ports.tasks.every(0, "invalid", undefined, { id: "invalid" })).rejects.toThrow("positive");
|
|
209
|
+
await expect(ports.tasks.schedule(Number.NaN, "invalid", undefined, { id: "nan" })).rejects.toThrow();
|
|
210
|
+
await expect(ports.tasks.schedule(Number.POSITIVE_INFINITY, "invalid", undefined, { id: "infinite" })).rejects.toThrow();
|
|
211
|
+
await expect(ports.tasks.every(Number.POSITIVE_INFINITY, "invalid", undefined, { id: "every-infinite" })).rejects.toThrow();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
it("redelivers a failed due task without inventing a run-status ledger", async () => {
|
|
215
|
+
await withFixture(factory, async ({ ports, delivery }) => {
|
|
216
|
+
delivery.failNext();
|
|
217
|
+
await ports.tasks.queue("contractDelivery", "payload", {
|
|
218
|
+
id: "retry-id",
|
|
219
|
+
maxRetries: 1,
|
|
220
|
+
});
|
|
221
|
+
expect(await delivery.drainDue()).toBe(1);
|
|
222
|
+
expect(delivery.calls()).toEqual(["payload"]);
|
|
223
|
+
expect(await ports.tasks.list()).toMatchObject([
|
|
224
|
+
{ id: "retry-id", attempts: 1, due: 1_700_000_001_000 },
|
|
225
|
+
]);
|
|
226
|
+
delivery.advanceBy(1_000);
|
|
227
|
+
expect(await delivery.drainDue()).toBe(1);
|
|
228
|
+
expect(delivery.calls()).toEqual(["payload", "payload"]);
|
|
229
|
+
expect(await ports.tasks.list()).toEqual([]);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
it("keeps a recurring task cancelled when its handler cancels itself and throws", async () => {
|
|
233
|
+
await withFixture(factory, async ({ ports, delivery }) => {
|
|
234
|
+
await ports.tasks.every(1, "selfCancel", undefined, { id: "self-cancel" });
|
|
235
|
+
delivery.advanceBy(1_000);
|
|
236
|
+
expect(await delivery.selfCancelAndThrow()).toBe(1);
|
|
237
|
+
expect(await ports.tasks.list()).toEqual([]);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
it("keeps a throwing handler's same-id replacement while carrying retry accounting", async () => {
|
|
241
|
+
await withFixture(factory, async ({ ports, delivery }) => {
|
|
242
|
+
await ports.tasks.queue("selfReplace", { version: 1 }, {
|
|
243
|
+
id: "self-replace",
|
|
244
|
+
maxRetries: 3,
|
|
245
|
+
});
|
|
246
|
+
expect(await delivery.selfReplaceAndThrow()).toBe(1);
|
|
247
|
+
expect(await ports.tasks.list()).toMatchObject([
|
|
248
|
+
{
|
|
249
|
+
id: "self-replace",
|
|
250
|
+
name: "replacement",
|
|
251
|
+
payload: { version: 2 },
|
|
252
|
+
due: 1_700_000_060_000,
|
|
253
|
+
attempts: 1,
|
|
254
|
+
maxRetries: 3,
|
|
255
|
+
},
|
|
256
|
+
]);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
it("preserves monotonic EventLog ordering across reopen", async () => {
|
|
260
|
+
await withFixture(factory, async ({ ports, reopen }) => {
|
|
261
|
+
await expect(ports.events.emit("progress", { step: 1 })).resolves.toBe(1);
|
|
262
|
+
await expect(ports.events.emit("progress", { step: 2 })).resolves.toBe(2);
|
|
263
|
+
expect((await ports.events.read(0)).map((event) => event.seq)).toEqual([1, 2]);
|
|
264
|
+
expect((await ports.events.read(1)).map((event) => event.seq)).toEqual([2]);
|
|
265
|
+
expect((await ports.events.read()).every((event) => event.at instanceof Date)).toBe(true);
|
|
266
|
+
const beforeFailure = await ports.events.read();
|
|
267
|
+
await expect(ports.events.emit("invalid", { callback: () => undefined })).rejects.toThrow();
|
|
268
|
+
await expect(ports.events.read()).resolves.toEqual(beforeFailure);
|
|
269
|
+
await expect(ports.events.count()).resolves.toBe(2);
|
|
270
|
+
await expect(ports.events.emit("progress", { step: 3 })).resolves.toBe(3);
|
|
271
|
+
expect((await ports.events.read()).map((event) => event.seq)).toEqual([1, 2, 3]);
|
|
272
|
+
const reopened = await reopen();
|
|
273
|
+
expect((await reopened.events.read()).map((event) => event.seq)).toEqual([1, 2, 3]);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { TaskRecord } from "@telnyx/edge-runtime/internal";
|
|
2
|
+
import { type AgentHarnessPorts } from "./ports.js";
|
|
3
|
+
import { type HarnessStepContext } from "./steps.js";
|
|
4
|
+
export declare const HARNESS_RUN_STATUSES: readonly ["accepted", "running", "awaiting_approval", "resume_pending", "completed", "failed", "canceled", "outcome_unknown"];
|
|
5
|
+
export type HarnessRunStatus = (typeof HARNESS_RUN_STATUSES)[number];
|
|
6
|
+
export interface HarnessRun {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly status: HarnessRunStatus;
|
|
10
|
+
readonly attempts: number;
|
|
11
|
+
readonly inputBytes: number;
|
|
12
|
+
readonly createdAt: number;
|
|
13
|
+
readonly updatedAt: number;
|
|
14
|
+
/** Redacted stable classification; no provider body, URL, header, or tool payload is retained. */
|
|
15
|
+
readonly failureCode?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface HarnessRunAcceptance {
|
|
18
|
+
readonly runId: string;
|
|
19
|
+
readonly duplicate: boolean;
|
|
20
|
+
readonly status: HarnessRunStatus;
|
|
21
|
+
}
|
|
22
|
+
export interface HarnessRunListOptions {
|
|
23
|
+
readonly limit?: number;
|
|
24
|
+
readonly status?: HarnessRunStatus;
|
|
25
|
+
}
|
|
26
|
+
export interface HarnessRunAcceptanceOptions {
|
|
27
|
+
readonly key: string;
|
|
28
|
+
/** Evaluated inside the durable admission transaction before a run is inserted. */
|
|
29
|
+
readonly admit?: () => boolean;
|
|
30
|
+
/** Exact actor-journal boundary captured before this run can begin execution. */
|
|
31
|
+
readonly journalSeq?: number;
|
|
32
|
+
}
|
|
33
|
+
/** Internal control signal: the caller's durable admission fence rejected this run. */
|
|
34
|
+
export declare class HarnessAdmissionRejected extends Error {
|
|
35
|
+
constructor();
|
|
36
|
+
}
|
|
37
|
+
export interface HarnessRunLedger {
|
|
38
|
+
accept(input: string, options: HarnessRunAcceptanceOptions): Promise<HarnessRunAcceptance>;
|
|
39
|
+
get(runId: string): Promise<HarnessRun | undefined>;
|
|
40
|
+
status(runId: string): Promise<HarnessRunStatus | undefined>;
|
|
41
|
+
list(options?: HarnessRunListOptions): Promise<readonly HarnessRun[]>;
|
|
42
|
+
cancel(runId: string): Promise<HarnessRun | undefined>;
|
|
43
|
+
/** Re-enqueue accepted and interrupted runs. Recovery is at-least-once, never exactly-once. */
|
|
44
|
+
recover(): Promise<number>;
|
|
45
|
+
/** Claim and execute an admitted run; host task handlers call this with their durable run id. */
|
|
46
|
+
run(runId: string): Promise<HarnessRun | undefined>;
|
|
47
|
+
/** Execute one task emitted by `accept()` after validating its private harness envelope. */
|
|
48
|
+
dispatch(task: Readonly<Pick<TaskRecord, "name" | "payload">>): Promise<HarnessRun | undefined>;
|
|
49
|
+
}
|
|
50
|
+
interface HarnessRunExecutionLedger extends HarnessRunLedger {
|
|
51
|
+
/** Private dispatch fence: only the attested approval batch can claim a continuation. */
|
|
52
|
+
run(runId: string, resumeBatch?: string): Promise<HarnessRun | undefined>;
|
|
53
|
+
resume(runId: string): Promise<HarnessRun | undefined>;
|
|
54
|
+
pauseForApproval(runId: string): void;
|
|
55
|
+
resumeForApproval(runId: string): void;
|
|
56
|
+
cancelForApproval(runId: string): void;
|
|
57
|
+
expireForApproval(runId: string): Promise<void>;
|
|
58
|
+
cleanupCanceledRun(runId: string): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
export type HarnessCheckpoint = (point: "before_effect" | "after_effect_before_memo" | "after_memo" | "before_final_run_commit") => void;
|
|
61
|
+
/** Internal deterministic crash signal. It intentionally leaves durable state at the interrupted boundary. */
|
|
62
|
+
export declare class HarnessInjectedCrash extends Error {
|
|
63
|
+
constructor(cause: unknown);
|
|
64
|
+
}
|
|
65
|
+
/** Internal control signal: approval state was durably recorded before pausing. */
|
|
66
|
+
export declare class HarnessApprovalPause extends Error {
|
|
67
|
+
constructor();
|
|
68
|
+
}
|
|
69
|
+
/** Internal schedule-only rollback signal that never inspects arbitrary caught errors. */
|
|
70
|
+
export declare function createScheduleAdmissionRollbackError(step: string, cause: unknown): Error;
|
|
71
|
+
export interface HarnessRunLedgerOptions {
|
|
72
|
+
/** Deterministic test seam for crash-window recovery coverage. */
|
|
73
|
+
readonly checkpoints?: HarnessCheckpoint;
|
|
74
|
+
/** Approval-capable harnesses bind admission to a host-attested subject. */
|
|
75
|
+
readonly requireApprovalIdentity?: boolean;
|
|
76
|
+
}
|
|
77
|
+
export declare function ensureHarnessDurabilitySchema(ports: AgentHarnessPorts): void;
|
|
78
|
+
export declare function createHarnessRunLedger(ports: AgentHarnessPorts, execute: (input: string, signal: AbortSignal, steps: HarnessStepContext) => Promise<unknown>, options?: HarnessRunLedgerOptions): HarnessRunLedger;
|
|
79
|
+
export declare function createHarnessRunExecutionLedger(ports: AgentHarnessPorts, execute: (input: string, signal: AbortSignal, steps: HarnessStepContext) => Promise<unknown>, options?: HarnessRunLedgerOptions): HarnessRunExecutionLedger;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=durable.d.ts.map
|