@agentium/harness 4.0.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 +488 -0
- package/dist/definition.d.ts +39 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/driver-BvzvqvpB.js +2049 -0
- package/dist/driver-Cr2BGG4h.cjs +2138 -0
- package/dist/drivers.d.ts +14 -0
- package/dist/drivers.d.ts.map +1 -0
- package/dist/index.cjs +1531 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1500 -0
- package/dist/mcp-resources.d.ts +42 -0
- package/dist/mcp-resources.d.ts.map +1 -0
- package/dist/policies.d.ts +29 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/presets.d.ts +41 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/runtime/context-policy.d.ts +20 -0
- package/dist/runtime/context-policy.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +15 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/controller.d.ts +51 -0
- package/dist/runtime/controller.d.ts.map +1 -0
- package/dist/runtime/driver.d.ts +146 -0
- package/dist/runtime/driver.d.ts.map +1 -0
- package/dist/runtime/events.d.ts +83 -0
- package/dist/runtime/events.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/middleware.d.ts +10 -0
- package/dist/runtime/middleware.d.ts.map +1 -0
- package/dist/runtime/resolve.d.ts +20 -0
- package/dist/runtime/resolve.d.ts.map +1 -0
- package/dist/runtime/runtime-registry.d.ts +35 -0
- package/dist/runtime/runtime-registry.d.ts.map +1 -0
- package/dist/runtime/session-bindings.d.ts +60 -0
- package/dist/runtime/session-bindings.d.ts.map +1 -0
- package/dist/runtime/types.d.ts +176 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/testing.cjs +67 -0
- package/dist/testing.d.ts +7 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +66 -0
- package/dist/watch/definition.d.ts +11 -0
- package/dist/watch/definition.d.ts.map +1 -0
- package/dist/watch/gmail.d.ts +104 -0
- package/dist/watch/gmail.d.ts.map +1 -0
- package/dist/watch/quiet-hours.d.ts +10 -0
- package/dist/watch/quiet-hours.d.ts.map +1 -0
- package/dist/watch/runtime.d.ts +42 -0
- package/dist/watch/runtime.d.ts.map +1 -0
- package/dist/watch/types.d.ts +147 -0
- package/dist/watch/types.d.ts.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,2138 @@
|
|
|
1
|
+
let node_crypto = require("node:crypto");
|
|
2
|
+
let _agentium_core = require("@agentium/core");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let zod_v3 = require("zod/v3");
|
|
5
|
+
//#region src/runtime/middleware.ts
|
|
6
|
+
/** Stable topological sort; declaration order breaks ties between ready nodes. */
|
|
7
|
+
function sortHarnessMiddleware(middleware) {
|
|
8
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const item of middleware) {
|
|
10
|
+
if (!item.id || byId.has(item.id)) throw new Error("Harness middleware IDs must be nonempty and unique");
|
|
11
|
+
byId.set(item.id, item);
|
|
12
|
+
}
|
|
13
|
+
const edges = new Map(middleware.map((item) => [item.id, /* @__PURE__ */ new Set()]));
|
|
14
|
+
const degrees = new Map(middleware.map((item) => [item.id, 0]));
|
|
15
|
+
const link = (from, to) => {
|
|
16
|
+
if (!byId.has(from) || !byId.has(to)) throw new Error("Harness middleware references an unknown ordering ID");
|
|
17
|
+
if (!edges.get(from).has(to)) {
|
|
18
|
+
edges.get(from).add(to);
|
|
19
|
+
degrees.set(to, degrees.get(to) + 1);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
for (const item of middleware) {
|
|
23
|
+
for (const id of item.before ?? []) link(item.id, id);
|
|
24
|
+
for (const id of item.after ?? []) link(id, item.id);
|
|
25
|
+
}
|
|
26
|
+
const result = [];
|
|
27
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
28
|
+
while (result.length < middleware.length) {
|
|
29
|
+
const item = middleware.find((candidate) => !consumed.has(candidate.id) && degrees.get(candidate.id) === 0);
|
|
30
|
+
if (!item) throw new Error("Harness middleware ordering contains a cycle");
|
|
31
|
+
result.push(item);
|
|
32
|
+
consumed.add(item.id);
|
|
33
|
+
for (const id of edges.get(item.id)) degrees.set(id, degrees.get(id) - 1);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
async function runBeforeModel(middleware, messages, ctx) {
|
|
38
|
+
let current = structuredClone(messages);
|
|
39
|
+
for (const item of middleware) {
|
|
40
|
+
ctx.signal?.throwIfAborted();
|
|
41
|
+
if (!item.beforeModel) continue;
|
|
42
|
+
const previous = structuredClone(current);
|
|
43
|
+
const next = await item.beforeModel(current, ctx);
|
|
44
|
+
(0, _agentium_core.validateConversationTransform)(previous, next);
|
|
45
|
+
current = next;
|
|
46
|
+
}
|
|
47
|
+
return current;
|
|
48
|
+
}
|
|
49
|
+
async function runAfterModel(middleware, response, ctx) {
|
|
50
|
+
for (const item of middleware) {
|
|
51
|
+
ctx.signal?.throwIfAborted();
|
|
52
|
+
await item.afterModel?.(structuredClone(response), ctx);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function runAfterTool(middleware, result, ctx) {
|
|
56
|
+
for (const item of middleware) {
|
|
57
|
+
ctx.signal?.throwIfAborted();
|
|
58
|
+
await item.afterTool?.(structuredClone(result), ctx);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/runtime/context-policy.ts
|
|
63
|
+
/** Request projections never mutate the canonical history. */
|
|
64
|
+
async function projectHarnessContext(policy, history, ctx, entries = []) {
|
|
65
|
+
const original = structuredClone(history);
|
|
66
|
+
if (!policy) return {
|
|
67
|
+
messages: original,
|
|
68
|
+
provenance: []
|
|
69
|
+
};
|
|
70
|
+
const result = await policy.project({
|
|
71
|
+
history: structuredClone(original),
|
|
72
|
+
entries: structuredClone(entries)
|
|
73
|
+
}, ctx);
|
|
74
|
+
(0, _agentium_core.validateConversationTransform)(original, result.messages);
|
|
75
|
+
if (!Array.isArray(result.provenance) || result.provenance.some((item) => !item || typeof item.sourceId !== "string" || typeof item.included !== "boolean" || item.reason !== void 0 && typeof item.reason !== "string")) throw new Error("Context policy must return provenance");
|
|
76
|
+
return {
|
|
77
|
+
messages: structuredClone(result.messages),
|
|
78
|
+
provenance: structuredClone(result.provenance)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/runtime/context.ts
|
|
83
|
+
const DEFAULT_BUDGET = {
|
|
84
|
+
maxEntries: 32,
|
|
85
|
+
maxBytes: 65536,
|
|
86
|
+
maxTokens: 16e3,
|
|
87
|
+
deadlineMs: 5e3
|
|
88
|
+
};
|
|
89
|
+
function sourceMessage(sourceId, entry, text) {
|
|
90
|
+
const provenance = {
|
|
91
|
+
version: 1,
|
|
92
|
+
sourceId,
|
|
93
|
+
entryId: entry.id,
|
|
94
|
+
trust: "source",
|
|
95
|
+
...entry.source ? { source: {
|
|
96
|
+
uri: entry.source.uri,
|
|
97
|
+
...entry.source.version === void 0 ? {} : { version: entry.source.version },
|
|
98
|
+
...entry.source.locator === void 0 ? {} : { locator: entry.source.locator }
|
|
99
|
+
} } : {}
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
role: "user",
|
|
103
|
+
content: JSON.stringify({
|
|
104
|
+
kind: "harness-source-data",
|
|
105
|
+
...provenance,
|
|
106
|
+
text
|
|
107
|
+
}),
|
|
108
|
+
providerExtras: { harnessContext: provenance }
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** All limits apply across every source. Tokens are centrally estimated, never accepted from the source. */
|
|
112
|
+
async function fetchHarnessContext(sources, query, ctx, configured) {
|
|
113
|
+
const budget = {
|
|
114
|
+
...DEFAULT_BUDGET,
|
|
115
|
+
...configured
|
|
116
|
+
};
|
|
117
|
+
for (const [key, value] of Object.entries(budget)) if (value !== void 0 && (!Number.isFinite(value) || !Number.isInteger(value) || value < 0)) throw new Error(`Invalid harness context budget: ${key}`);
|
|
118
|
+
const messages = [];
|
|
119
|
+
const diagnostics = [];
|
|
120
|
+
const sourceIds = /* @__PURE__ */ new Set();
|
|
121
|
+
for (const source of sources) {
|
|
122
|
+
if (typeof source.id !== "string" || !source.id || sourceIds.has(source.id)) throw new Error("Harness context source IDs must be nonempty and unique");
|
|
123
|
+
sourceIds.add(source.id);
|
|
124
|
+
}
|
|
125
|
+
const deadline = Date.now() + budget.deadlineMs;
|
|
126
|
+
let bytes = 0;
|
|
127
|
+
let tokens = 0;
|
|
128
|
+
for (const source of sources) {
|
|
129
|
+
if (ctx.signal?.aborted) throw new _agentium_core.RunCancelledError();
|
|
130
|
+
const remaining = {
|
|
131
|
+
maxEntries: budget.maxEntries - messages.length,
|
|
132
|
+
maxBytes: budget.maxBytes - bytes,
|
|
133
|
+
...budget.maxTokens === void 0 ? {} : { maxTokens: budget.maxTokens - tokens },
|
|
134
|
+
deadlineMs: Math.max(0, deadline - Date.now())
|
|
135
|
+
};
|
|
136
|
+
if (!remaining.maxEntries || !remaining.maxBytes || remaining.maxTokens === 0 || !remaining.deadlineMs) {
|
|
137
|
+
diagnostics.push({
|
|
138
|
+
sourceId: source.id,
|
|
139
|
+
code: remaining.deadlineMs ? "budget" : "deadline",
|
|
140
|
+
message: "Shared context retrieval budget exhausted"
|
|
141
|
+
});
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
const childCtx = new _agentium_core.RunContext({
|
|
146
|
+
sessionId: ctx.sessionId,
|
|
147
|
+
runId: ctx.runId,
|
|
148
|
+
userId: ctx.userId,
|
|
149
|
+
tenantId: ctx.tenantId,
|
|
150
|
+
metadata: ctx.metadata,
|
|
151
|
+
eventBus: ctx.eventBus,
|
|
152
|
+
sessionState: ctx.sessionState,
|
|
153
|
+
dependencies: ctx.dependencies,
|
|
154
|
+
questions: ctx.questions,
|
|
155
|
+
executionPolicy: ctx.executionPolicy,
|
|
156
|
+
runMode: ctx.runMode,
|
|
157
|
+
signal: controller.signal
|
|
158
|
+
});
|
|
159
|
+
let timer;
|
|
160
|
+
let abort;
|
|
161
|
+
let entries;
|
|
162
|
+
try {
|
|
163
|
+
entries = await Promise.race([Promise.resolve().then(() => {
|
|
164
|
+
if (childCtx.signal?.aborted || ctx.signal?.aborted) throw new _agentium_core.RunCancelledError();
|
|
165
|
+
return source.fetch(query, childCtx, remaining);
|
|
166
|
+
}), new Promise((_resolve, reject) => {
|
|
167
|
+
timer = setTimeout(() => {
|
|
168
|
+
controller.abort();
|
|
169
|
+
reject(/* @__PURE__ */ new Error("deadline"));
|
|
170
|
+
}, remaining.deadlineMs);
|
|
171
|
+
abort = () => {
|
|
172
|
+
controller.abort();
|
|
173
|
+
reject(new _agentium_core.RunCancelledError());
|
|
174
|
+
};
|
|
175
|
+
ctx.signal?.addEventListener("abort", abort, { once: true });
|
|
176
|
+
if (ctx.signal?.aborted) abort();
|
|
177
|
+
})]);
|
|
178
|
+
} catch {
|
|
179
|
+
if (ctx.signal?.aborted) throw new _agentium_core.RunCancelledError();
|
|
180
|
+
diagnostics.push({
|
|
181
|
+
sourceId: source.id,
|
|
182
|
+
code: controller.signal.aborted ? "deadline" : "failed",
|
|
183
|
+
message: "Context source could not complete within its budget"
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
} finally {
|
|
187
|
+
if (timer) clearTimeout(timer);
|
|
188
|
+
if (abort) ctx.signal?.removeEventListener("abort", abort);
|
|
189
|
+
}
|
|
190
|
+
if (!Array.isArray(entries)) {
|
|
191
|
+
diagnostics.push({
|
|
192
|
+
sourceId: source.id,
|
|
193
|
+
code: "invalid",
|
|
194
|
+
message: "Context source must return an array"
|
|
195
|
+
});
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (entries.length > remaining.maxEntries) diagnostics.push({
|
|
199
|
+
sourceId: source.id,
|
|
200
|
+
code: "budget",
|
|
201
|
+
message: "Context entries beyond the shared entry limit were discarded"
|
|
202
|
+
});
|
|
203
|
+
const entryIds = /* @__PURE__ */ new Set();
|
|
204
|
+
for (const entry of entries.slice(0, remaining.maxEntries)) {
|
|
205
|
+
if (ctx.signal?.aborted) throw new _agentium_core.RunCancelledError();
|
|
206
|
+
if (Date.now() >= deadline) {
|
|
207
|
+
diagnostics.push({
|
|
208
|
+
sourceId: source.id,
|
|
209
|
+
code: "deadline",
|
|
210
|
+
message: "Shared context retrieval deadline expired"
|
|
211
|
+
});
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
if (!entry || typeof entry.id !== "string" || !entry.id || entryIds.has(entry.id) || typeof entry.text !== "string" || entry.expiresAt !== void 0 && !Number.isFinite(entry.expiresAt)) {
|
|
215
|
+
diagnostics.push({
|
|
216
|
+
sourceId: source.id,
|
|
217
|
+
code: "invalid",
|
|
218
|
+
message: "Invalid or duplicate context entry was discarded"
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (entry.source && (typeof entry.source.uri !== "string" || entry.source.version !== void 0 && typeof entry.source.version !== "string" || entry.source.locator !== void 0 && typeof entry.source.locator !== "string")) {
|
|
223
|
+
diagnostics.push({
|
|
224
|
+
sourceId: source.id,
|
|
225
|
+
entryId: entry.id,
|
|
226
|
+
code: "invalid",
|
|
227
|
+
message: "Invalid context source provenance was discarded"
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
entryIds.add(entry.id);
|
|
232
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
|
|
233
|
+
diagnostics.push({
|
|
234
|
+
sourceId: source.id,
|
|
235
|
+
entryId: entry.id,
|
|
236
|
+
code: "expired",
|
|
237
|
+
message: "Expired context entry was discarded"
|
|
238
|
+
});
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (entry.trust !== "source") diagnostics.push({
|
|
242
|
+
sourceId: source.id,
|
|
243
|
+
entryId: entry.id,
|
|
244
|
+
code: "trust_downgraded",
|
|
245
|
+
message: "Retrieved context is always source data; host trust declarations are ignored"
|
|
246
|
+
});
|
|
247
|
+
const fits = (message) => {
|
|
248
|
+
const text = message.content;
|
|
249
|
+
return Buffer.byteLength(text, "utf8") <= budget.maxBytes - bytes && (budget.maxTokens === void 0 || (0, _agentium_core.countTokens)(text) <= budget.maxTokens - tokens);
|
|
250
|
+
};
|
|
251
|
+
let message = sourceMessage(source.id, entry, entry.text);
|
|
252
|
+
if (!fits(message)) {
|
|
253
|
+
if (!fits(sourceMessage(source.id, entry, ""))) {
|
|
254
|
+
diagnostics.push({
|
|
255
|
+
sourceId: source.id,
|
|
256
|
+
entryId: entry.id,
|
|
257
|
+
code: "budget",
|
|
258
|
+
message: "Context entry metadata exceeds the remaining budget"
|
|
259
|
+
});
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
let low = 0;
|
|
263
|
+
let high = entry.text.length;
|
|
264
|
+
while (low < high) {
|
|
265
|
+
const middle = Math.ceil((low + high) / 2);
|
|
266
|
+
if (fits(sourceMessage(source.id, entry, entry.text.slice(0, middle)))) low = middle;
|
|
267
|
+
else high = middle - 1;
|
|
268
|
+
}
|
|
269
|
+
if (low > 0 && /[\uD800-\uDBFF]/.test(entry.text[low - 1])) low--;
|
|
270
|
+
message = sourceMessage(source.id, entry, entry.text.slice(0, low));
|
|
271
|
+
diagnostics.push({
|
|
272
|
+
sourceId: source.id,
|
|
273
|
+
entryId: entry.id,
|
|
274
|
+
code: "truncated",
|
|
275
|
+
message: "Context text truncated to the shared byte/token budget (tokens estimated)"
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
messages.push(message);
|
|
279
|
+
bytes += Buffer.byteLength(message.content, "utf8");
|
|
280
|
+
tokens += (0, _agentium_core.countTokens)(message.content);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
messages,
|
|
285
|
+
diagnostics
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/runtime/events.ts
|
|
290
|
+
var HarnessEventGapError = class extends Error {
|
|
291
|
+
earliestCursor;
|
|
292
|
+
code = "event_gap";
|
|
293
|
+
constructor(earliestCursor) {
|
|
294
|
+
super(`Event history gap; earliest available cursor is ${earliestCursor}`);
|
|
295
|
+
this.earliestCursor = earliestCursor;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
/** Bounded in-process history; no durability, network delivery, or audio backpressure promise. */
|
|
299
|
+
var InMemoryHarnessEventStore = class {
|
|
300
|
+
identity;
|
|
301
|
+
capacity;
|
|
302
|
+
durable = false;
|
|
303
|
+
history = [];
|
|
304
|
+
sequence = 0;
|
|
305
|
+
finished = false;
|
|
306
|
+
listeners = /* @__PURE__ */ new Set();
|
|
307
|
+
constructor(identity, capacity = 256) {
|
|
308
|
+
this.identity = identity;
|
|
309
|
+
this.capacity = capacity;
|
|
310
|
+
if (!Number.isInteger(capacity) || capacity < 2) throw new Error("Event capacity must be an integer >= 2");
|
|
311
|
+
}
|
|
312
|
+
get cursor() {
|
|
313
|
+
return this.sequence;
|
|
314
|
+
}
|
|
315
|
+
append(payload) {
|
|
316
|
+
if (this.finished) throw new Error("Event stream is already terminal");
|
|
317
|
+
const event = {
|
|
318
|
+
...this.identity,
|
|
319
|
+
schemaVersion: 1,
|
|
320
|
+
eventId: (0, node_crypto.randomUUID)(),
|
|
321
|
+
sequence: ++this.sequence,
|
|
322
|
+
timestamp: Date.now(),
|
|
323
|
+
payload: structuredClone(payload)
|
|
324
|
+
};
|
|
325
|
+
this.history.push(event);
|
|
326
|
+
if (this.history.length > this.capacity) this.history.shift();
|
|
327
|
+
if (payload.type === "run.terminal") this.finished = true;
|
|
328
|
+
for (const notify of this.listeners) notify();
|
|
329
|
+
return structuredClone(event);
|
|
330
|
+
}
|
|
331
|
+
events(options = {}) {
|
|
332
|
+
let cursor = options.after ?? 0;
|
|
333
|
+
if (!Number.isInteger(cursor) || cursor < 0 || cursor > this.sequence) throw new Error("Invalid event cursor");
|
|
334
|
+
let closed = false;
|
|
335
|
+
let queued = Promise.resolve();
|
|
336
|
+
let wake;
|
|
337
|
+
const close = () => {
|
|
338
|
+
closed = true;
|
|
339
|
+
if (wake) {
|
|
340
|
+
this.listeners.delete(wake);
|
|
341
|
+
wake();
|
|
342
|
+
wake = void 0;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
return {
|
|
346
|
+
[Symbol.asyncIterator]() {
|
|
347
|
+
return this;
|
|
348
|
+
},
|
|
349
|
+
[Symbol.asyncDispose]: async () => {
|
|
350
|
+
close();
|
|
351
|
+
},
|
|
352
|
+
next: () => {
|
|
353
|
+
const result = queued.then(async () => {
|
|
354
|
+
while (!closed) {
|
|
355
|
+
const earliest = this.history[0]?.sequence ?? 1;
|
|
356
|
+
if (cursor < earliest - 1) {
|
|
357
|
+
close();
|
|
358
|
+
throw new HarnessEventGapError(earliest - 1);
|
|
359
|
+
}
|
|
360
|
+
const next = this.history.find((event) => event.sequence > cursor);
|
|
361
|
+
if (next) {
|
|
362
|
+
cursor = next.sequence;
|
|
363
|
+
return {
|
|
364
|
+
value: structuredClone(next),
|
|
365
|
+
done: false
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (this.finished) break;
|
|
369
|
+
await new Promise((resolve) => {
|
|
370
|
+
wake = resolve;
|
|
371
|
+
this.listeners.add(resolve);
|
|
372
|
+
});
|
|
373
|
+
if (wake) this.listeners.delete(wake);
|
|
374
|
+
wake = void 0;
|
|
375
|
+
}
|
|
376
|
+
close();
|
|
377
|
+
return {
|
|
378
|
+
value: void 0,
|
|
379
|
+
done: true
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
queued = result.catch(() => {});
|
|
383
|
+
return result;
|
|
384
|
+
},
|
|
385
|
+
return: async () => {
|
|
386
|
+
close();
|
|
387
|
+
return {
|
|
388
|
+
value: void 0,
|
|
389
|
+
done: true
|
|
390
|
+
};
|
|
391
|
+
},
|
|
392
|
+
throw: async (error) => {
|
|
393
|
+
close();
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/runtime/runtime-registry.ts
|
|
401
|
+
/** Registry resolution is static; it never initializes providers or executes drivers. */
|
|
402
|
+
function resolveHarnessRuntime(definition) {
|
|
403
|
+
const references = definition.kind === "portable" ? definition.manifest.runtime : definition.runtimeReferences;
|
|
404
|
+
const direct = definition.kind === "local" ? definition.runtime : void 0;
|
|
405
|
+
const result = {
|
|
406
|
+
...direct,
|
|
407
|
+
modelRoles: direct?.modelRoles ?? references?.modelRoles
|
|
408
|
+
};
|
|
409
|
+
for (const key of [
|
|
410
|
+
"driver",
|
|
411
|
+
"controller",
|
|
412
|
+
"contextPolicy",
|
|
413
|
+
"completionPolicy"
|
|
414
|
+
]) {
|
|
415
|
+
const reference = references?.[key];
|
|
416
|
+
if (!reference) continue;
|
|
417
|
+
const matches = (definition.runtimeRegistry?.[key] ?? []).filter((entry) => entry.id === reference.id && entry.version === reference.version);
|
|
418
|
+
if (matches.length !== 1) throw new Error(`runtime.${key}: expected exactly one approved registry implementation for ${reference.id}@${reference.version}`);
|
|
419
|
+
const implementation = matches[0].implementation;
|
|
420
|
+
if (direct?.[key] && direct[key] !== implementation) throw new Error(`runtime.${key}: direct binding differs from its approved export mapping`);
|
|
421
|
+
result[key] = implementation;
|
|
422
|
+
}
|
|
423
|
+
return Object.freeze(result);
|
|
424
|
+
}
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/runtime/resolve.ts
|
|
427
|
+
var HarnessValidationError = class extends Error {
|
|
428
|
+
diagnostics;
|
|
429
|
+
constructor(path, message, code = "invalid_harness") {
|
|
430
|
+
super(`${path}: ${message}`);
|
|
431
|
+
this.name = "HarnessValidationError";
|
|
432
|
+
this.diagnostics = [{
|
|
433
|
+
code,
|
|
434
|
+
path,
|
|
435
|
+
message,
|
|
436
|
+
severity: "error"
|
|
437
|
+
}];
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const generatedIds = /* @__PURE__ */ new WeakSet();
|
|
441
|
+
const definitions = /* @__PURE__ */ new WeakSet();
|
|
442
|
+
const metadata = /* @__PURE__ */ new WeakMap();
|
|
443
|
+
const secretKeys = /* @__PURE__ */ new Set([
|
|
444
|
+
"apikey",
|
|
445
|
+
"password",
|
|
446
|
+
"secret",
|
|
447
|
+
"authorization",
|
|
448
|
+
"accesstoken",
|
|
449
|
+
"refreshtoken",
|
|
450
|
+
"clientsecret",
|
|
451
|
+
"privatekey",
|
|
452
|
+
"credentials"
|
|
453
|
+
]);
|
|
454
|
+
function fail(path, message) {
|
|
455
|
+
throw new HarnessValidationError(path, message);
|
|
456
|
+
}
|
|
457
|
+
function object(input, path) {
|
|
458
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || ![Object.prototype, null].includes(Object.getPrototypeOf(input))) fail(path, "expected a plain object");
|
|
459
|
+
return input;
|
|
460
|
+
}
|
|
461
|
+
function keys(input, allowed, path) {
|
|
462
|
+
for (const key of Object.keys(input)) if (!allowed.includes(key)) fail(`${path}.${key}`, "unknown field");
|
|
463
|
+
}
|
|
464
|
+
function id(input, path) {
|
|
465
|
+
if (typeof input !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/.test(input)) fail(path, "expected a stable nonempty identifier");
|
|
466
|
+
return input;
|
|
467
|
+
}
|
|
468
|
+
function integer(input, path, min = 0) {
|
|
469
|
+
if (typeof input !== "number" || !Number.isFinite(input) || !Number.isInteger(input) || input < min) fail(path, `expected an integer >= ${min}`);
|
|
470
|
+
return input;
|
|
471
|
+
}
|
|
472
|
+
function json(input, path, seen = /* @__PURE__ */ new Set()) {
|
|
473
|
+
if (input === null || typeof input === "boolean" || typeof input === "string") return input;
|
|
474
|
+
if (typeof input === "number" && Number.isFinite(input)) return input;
|
|
475
|
+
if (typeof input !== "object" || input === null) fail(path, "expected JSON data");
|
|
476
|
+
if (seen.has(input)) fail(path, "cyclic data is not portable");
|
|
477
|
+
seen.add(input);
|
|
478
|
+
try {
|
|
479
|
+
if (Array.isArray(input)) {
|
|
480
|
+
if (Object.getOwnPropertySymbols(input).length || Object.keys(input).some((key) => !/^(0|[1-9][0-9]*)$/.test(key))) fail(path, "array properties are not portable");
|
|
481
|
+
return Array.from({ length: input.length }, (_, index) => {
|
|
482
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, index);
|
|
483
|
+
if (!descriptor || !("value" in descriptor)) fail(`${path}[${index}]`, "sparse arrays and accessors are not portable");
|
|
484
|
+
return json(descriptor.value, `${path}[${index}]`, seen);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const source = object(input, path);
|
|
488
|
+
if (Object.getOwnPropertySymbols(source).length) fail(path, "symbol properties are not portable");
|
|
489
|
+
const result = {};
|
|
490
|
+
for (const key of Object.keys(source)) {
|
|
491
|
+
if (secretKeys.has(key.replace(/[_-]/g, "").toLowerCase())) fail(`${path}.${key}`, "credentials belong in host bindings");
|
|
492
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
493
|
+
if (!("value" in descriptor)) fail(`${path}.${key}`, "accessors are not portable");
|
|
494
|
+
Object.defineProperty(result, key, {
|
|
495
|
+
value: json(descriptor.value, `${path}.${key}`, seen),
|
|
496
|
+
enumerable: true,
|
|
497
|
+
writable: true,
|
|
498
|
+
configurable: true
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
} finally {
|
|
503
|
+
seen.delete(input);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function freeze(value) {
|
|
507
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
508
|
+
for (const child of Object.values(value)) freeze(child);
|
|
509
|
+
Object.freeze(value);
|
|
510
|
+
}
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
function validateDefaults(input, path = "defaults") {
|
|
514
|
+
const data = object(json(input, path), path);
|
|
515
|
+
keys(data, [
|
|
516
|
+
"workspace",
|
|
517
|
+
"skillDirs",
|
|
518
|
+
"contextFiles",
|
|
519
|
+
"filesystem",
|
|
520
|
+
"subagents",
|
|
521
|
+
"fileMemory",
|
|
522
|
+
"searchPastSessions"
|
|
523
|
+
], path);
|
|
524
|
+
for (const [key, value] of Object.entries(data)) if (key === "workspace") {
|
|
525
|
+
if (value === false) continue;
|
|
526
|
+
const workspace = object(value, `${path}.workspace`);
|
|
527
|
+
keys(workspace, ["path", "mode"], `${path}.workspace`);
|
|
528
|
+
if (typeof workspace.path !== "string" || !workspace.path.length || !["read", "write"].includes(workspace.mode)) fail(`${path}.workspace`, "expected a path and read/write mode");
|
|
529
|
+
} else if (key === "skillDirs") {
|
|
530
|
+
if (value !== false && (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.length))) fail(`${path}.skillDirs`, "expected a list of paths or false");
|
|
531
|
+
} else if (typeof value !== "boolean") fail(`${path}.${key}`, "expected a boolean");
|
|
532
|
+
return data;
|
|
533
|
+
}
|
|
534
|
+
function validateLimits(input, path = "limits") {
|
|
535
|
+
const data = object(json(input, path), path);
|
|
536
|
+
keys(data, ["toolRoundtrips", "maxChildDepth"], path);
|
|
537
|
+
for (const [key, value] of Object.entries(data)) integer(value, `${path}.${key}`);
|
|
538
|
+
return data;
|
|
539
|
+
}
|
|
540
|
+
function validateSelection(input, path) {
|
|
541
|
+
const data = object(input, path);
|
|
542
|
+
keys(data, [
|
|
543
|
+
"instanceId",
|
|
544
|
+
"type",
|
|
545
|
+
"version",
|
|
546
|
+
"options"
|
|
547
|
+
], path);
|
|
548
|
+
return {
|
|
549
|
+
instanceId: id(data.instanceId, `${path}.instanceId`),
|
|
550
|
+
type: id(data.type, `${path}.type`),
|
|
551
|
+
version: integer(data.version, `${path}.version`, 1),
|
|
552
|
+
options: object(json(data.options, `${path}.options`), `${path}.options`)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function validateRuntimeReferences(input, path = "runtime") {
|
|
556
|
+
const data = object(json(input, path), path);
|
|
557
|
+
keys(data, [
|
|
558
|
+
"driver",
|
|
559
|
+
"controller",
|
|
560
|
+
"contextPolicy",
|
|
561
|
+
"completionPolicy",
|
|
562
|
+
"modelRoles"
|
|
563
|
+
], path);
|
|
564
|
+
for (const [key, value] of Object.entries(data)) {
|
|
565
|
+
const item = object(value, `${path}.${key}`);
|
|
566
|
+
if (key === "modelRoles") for (const [role, name] of Object.entries(item)) {
|
|
567
|
+
id(role, `${path}.modelRoles`);
|
|
568
|
+
id(name, `${path}.modelRoles.${role}`);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
keys(item, ["id", "version"], `${path}.${key}`);
|
|
572
|
+
id(item.id, `${path}.${key}.id`);
|
|
573
|
+
integer(item.version, `${path}.${key}.version`, 1);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return freeze(data);
|
|
577
|
+
}
|
|
578
|
+
function validateHarnessManifest(input) {
|
|
579
|
+
const data = object(json(input, "manifest"), "manifest");
|
|
580
|
+
keys(data, [
|
|
581
|
+
"contractVersion",
|
|
582
|
+
"id",
|
|
583
|
+
"abilities",
|
|
584
|
+
"defaults",
|
|
585
|
+
"limits",
|
|
586
|
+
"runtime"
|
|
587
|
+
], "manifest");
|
|
588
|
+
if (data.contractVersion !== 1) fail("manifest.contractVersion", "unsupported contract version");
|
|
589
|
+
if (!Array.isArray(data.abilities)) fail("manifest.abilities", "expected an array");
|
|
590
|
+
const abilities = data.abilities.map((ability, index) => validateSelection(ability, `manifest.abilities[${index}]`));
|
|
591
|
+
unique(abilities.map((ability) => ability.instanceId), "manifest.abilities");
|
|
592
|
+
return freeze({
|
|
593
|
+
contractVersion: 1,
|
|
594
|
+
id: id(data.id, "manifest.id"),
|
|
595
|
+
abilities,
|
|
596
|
+
...data.runtime !== void 0 ? { runtime: validateRuntimeReferences(data.runtime) } : {},
|
|
597
|
+
...data.defaults !== void 0 ? { defaults: validateDefaults(data.defaults) } : {},
|
|
598
|
+
...data.limits !== void 0 ? { limits: validateLimits(data.limits) } : {}
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
function unique(values, path) {
|
|
602
|
+
if (new Set(values).size !== values.length) fail(path, "duplicate identifiers");
|
|
603
|
+
}
|
|
604
|
+
function registry(factories) {
|
|
605
|
+
if (!Array.isArray(factories)) fail("factories", "expected an approved factory registry");
|
|
606
|
+
const map = /* @__PURE__ */ new Map();
|
|
607
|
+
for (const factory of factories) {
|
|
608
|
+
if (!factory || typeof factory !== "object") fail("factory", "expected a trusted factory");
|
|
609
|
+
id(factory.type, "factory.type");
|
|
610
|
+
integer(factory.version, "factory.version", 1);
|
|
611
|
+
if (typeof factory.validateOptions !== "function" || typeof factory.describe !== "function" || typeof factory.bind !== "function") fail("factory", "missing validation, description or binding function");
|
|
612
|
+
const key = `${factory.type}@${factory.version}`;
|
|
613
|
+
if (map.has(key) && map.get(key) !== factory) fail("factories", "conflicting implementations for a registered type/version");
|
|
614
|
+
map.set(key, factory);
|
|
615
|
+
}
|
|
616
|
+
return map;
|
|
617
|
+
}
|
|
618
|
+
function snapshotRuntimeRegistry(input) {
|
|
619
|
+
keys(object(input, "runtimeRegistry"), [
|
|
620
|
+
"driver",
|
|
621
|
+
"controller",
|
|
622
|
+
"contextPolicy",
|
|
623
|
+
"completionPolicy"
|
|
624
|
+
], "runtimeRegistry");
|
|
625
|
+
const result = {};
|
|
626
|
+
for (const [key, entries] of Object.entries(input)) {
|
|
627
|
+
if (!Array.isArray(entries)) fail(`runtimeRegistry.${key}`, "expected approved registry entries");
|
|
628
|
+
const names = [];
|
|
629
|
+
result[key] = Object.freeze(entries.map((entry) => {
|
|
630
|
+
id(entry.id, `runtimeRegistry.${key}.id`);
|
|
631
|
+
integer(entry.version, `runtimeRegistry.${key}.version`, 1);
|
|
632
|
+
names.push(`${entry.id}@${entry.version}`);
|
|
633
|
+
if (!entry.implementation || typeof entry.implementation !== "object") fail(`runtimeRegistry.${key}`, "missing trusted implementation");
|
|
634
|
+
return Object.freeze({ ...entry });
|
|
635
|
+
}));
|
|
636
|
+
unique(names, `runtimeRegistry.${key}`);
|
|
637
|
+
}
|
|
638
|
+
return Object.freeze(result);
|
|
639
|
+
}
|
|
640
|
+
function normalizePortable(manifest, factories, runtimeRegistry) {
|
|
641
|
+
const approved = registry(factories);
|
|
642
|
+
const abilities = manifest.abilities.map((selection, index) => {
|
|
643
|
+
const factory = approved.get(`${selection.type}@${selection.version}`);
|
|
644
|
+
if (!factory) fail(`manifest.abilities[${index}]`, "no approved factory for ability type/version");
|
|
645
|
+
let options;
|
|
646
|
+
try {
|
|
647
|
+
options = factory.validateOptions(json(selection.options, "options"));
|
|
648
|
+
} catch {
|
|
649
|
+
fail(`manifest.abilities[${index}].options`, "ability option validation failed");
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
...selection,
|
|
653
|
+
options: object(json(options, `manifest.abilities[${index}].options`), "options")
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
const definition = Object.freeze({
|
|
657
|
+
kind: "portable",
|
|
658
|
+
manifest: validateHarnessManifest({
|
|
659
|
+
...manifest,
|
|
660
|
+
abilities
|
|
661
|
+
}),
|
|
662
|
+
factories: Object.freeze([...approved.values()]),
|
|
663
|
+
...runtimeRegistry ? { runtimeRegistry: snapshotRuntimeRegistry(runtimeRegistry) } : {}
|
|
664
|
+
});
|
|
665
|
+
resolveHarnessRuntime(definition);
|
|
666
|
+
definitions.add(definition);
|
|
667
|
+
return definition;
|
|
668
|
+
}
|
|
669
|
+
function loadHarnessManifest(input, factories, runtimeRegistry) {
|
|
670
|
+
return normalizePortable(validateHarnessManifest(input), factories, runtimeRegistry);
|
|
671
|
+
}
|
|
672
|
+
function createHarnessDefinition(input) {
|
|
673
|
+
object(input, "definition");
|
|
674
|
+
if (definitions.has(input)) return input;
|
|
675
|
+
if ("kind" in input && input.kind === "portable") {
|
|
676
|
+
keys(object(input, "definition"), [
|
|
677
|
+
"kind",
|
|
678
|
+
"manifest",
|
|
679
|
+
"factories",
|
|
680
|
+
"runtimeRegistry"
|
|
681
|
+
], "definition");
|
|
682
|
+
return loadHarnessManifest(input.manifest, input.factories, input.runtimeRegistry);
|
|
683
|
+
}
|
|
684
|
+
const local = input;
|
|
685
|
+
keys(object(input, "definition"), [
|
|
686
|
+
"kind",
|
|
687
|
+
"id",
|
|
688
|
+
"abilities",
|
|
689
|
+
"defaults",
|
|
690
|
+
"limits",
|
|
691
|
+
"runtime",
|
|
692
|
+
"runtimeReferences",
|
|
693
|
+
"runtimeRegistry"
|
|
694
|
+
], "definition");
|
|
695
|
+
if ("kind" in input && input.kind !== "local") fail("definition.kind", "unsupported definition kind");
|
|
696
|
+
if (local.abilities !== void 0 && !Array.isArray(local.abilities)) fail("abilities", "expected an ability list");
|
|
697
|
+
const abilities = [...local.abilities ?? []].map((use, index) => {
|
|
698
|
+
if (!use || typeof use !== "object") fail(`abilities[${index}]`, "expected an ability use");
|
|
699
|
+
id(use.instanceId, `abilities[${index}].instanceId`);
|
|
700
|
+
if (typeof use.validate !== "function" || typeof use.describe !== "function" || typeof use.bind !== "function") fail(`abilities[${index}]`, "missing validation, description or binding function");
|
|
701
|
+
try {
|
|
702
|
+
use.validate();
|
|
703
|
+
} catch {
|
|
704
|
+
fail(`abilities[${index}]`, "ability option validation failed");
|
|
705
|
+
}
|
|
706
|
+
const portable = use.portable ? freeze(validateSelection(json(use.portable, `abilities[${index}].portable`), `abilities[${index}].portable`)) : void 0;
|
|
707
|
+
if (portable && portable.instanceId !== use.instanceId) fail(`abilities[${index}].portable.instanceId`, "portable and local IDs must match");
|
|
708
|
+
if (portable && use.factory && (portable.type !== use.factory.type || portable.version !== use.factory.version)) fail(`abilities[${index}].factory`, "factory mapping does not match selection");
|
|
709
|
+
return Object.freeze({
|
|
710
|
+
...use,
|
|
711
|
+
...portable ? { portable } : {}
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
unique(abilities.map((use) => use.instanceId), "abilities");
|
|
715
|
+
registry(abilities.flatMap((use) => use.factory ? [use.factory] : []));
|
|
716
|
+
const definition = Object.freeze({
|
|
717
|
+
kind: "local",
|
|
718
|
+
id: local.id === void 0 ? `local-${(0, node_crypto.randomUUID)()}` : id(local.id, "definition.id"),
|
|
719
|
+
abilities: Object.freeze(abilities),
|
|
720
|
+
...local.runtime ? { runtime: Object.freeze({ ...local.runtime }) } : {},
|
|
721
|
+
...local.runtimeReferences ? { runtimeReferences: validateRuntimeReferences(local.runtimeReferences) } : {},
|
|
722
|
+
...local.runtimeRegistry ? { runtimeRegistry: snapshotRuntimeRegistry(local.runtimeRegistry) } : {},
|
|
723
|
+
...local.defaults !== void 0 ? { defaults: freeze(validateDefaults(local.defaults)) } : {},
|
|
724
|
+
...local.limits !== void 0 ? { limits: freeze(validateLimits(local.limits)) } : {}
|
|
725
|
+
});
|
|
726
|
+
resolveHarnessRuntime(definition);
|
|
727
|
+
definitions.add(definition);
|
|
728
|
+
if (local.id === void 0 || generatedIds.has(input)) generatedIds.add(definition);
|
|
729
|
+
metadata.set(definition, metadata.get(input) ?? {
|
|
730
|
+
provenance: [
|
|
731
|
+
...Object.keys(local.defaults ?? {}).map((key) => ({
|
|
732
|
+
path: `defaults.${key}`,
|
|
733
|
+
origin: definition.id
|
|
734
|
+
})),
|
|
735
|
+
...Object.keys(local.limits ?? {}).map((key) => ({
|
|
736
|
+
path: `limits.${key}`,
|
|
737
|
+
origin: definition.id
|
|
738
|
+
})),
|
|
739
|
+
...abilities.map((use) => ({
|
|
740
|
+
path: `abilities.${use.instanceId}`,
|
|
741
|
+
origin: definition.id
|
|
742
|
+
}))
|
|
743
|
+
],
|
|
744
|
+
diagnostics: []
|
|
745
|
+
});
|
|
746
|
+
return definition;
|
|
747
|
+
}
|
|
748
|
+
function asLocal(definition) {
|
|
749
|
+
if (definition.kind === "local") return definition;
|
|
750
|
+
const approved = registry(definition.factories);
|
|
751
|
+
return {
|
|
752
|
+
kind: "local",
|
|
753
|
+
id: definition.manifest.id,
|
|
754
|
+
defaults: definition.manifest.defaults,
|
|
755
|
+
limits: definition.manifest.limits,
|
|
756
|
+
runtimeReferences: definition.manifest.runtime,
|
|
757
|
+
runtimeRegistry: definition.runtimeRegistry,
|
|
758
|
+
abilities: definition.manifest.abilities.map((selection) => {
|
|
759
|
+
const factory = approved.get(`${selection.type}@${selection.version}`);
|
|
760
|
+
return {
|
|
761
|
+
instanceId: selection.instanceId,
|
|
762
|
+
portable: selection,
|
|
763
|
+
factory,
|
|
764
|
+
validate: () => {},
|
|
765
|
+
describe: () => factory.describe(selection.options),
|
|
766
|
+
bind: (ctx) => factory.bind(selection.options, ctx)
|
|
767
|
+
};
|
|
768
|
+
})
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
function extendHarnessDefinition(base, extension) {
|
|
772
|
+
keys(object(extension, "extension"), [
|
|
773
|
+
"id",
|
|
774
|
+
"abilities",
|
|
775
|
+
"disable",
|
|
776
|
+
"replaceAbilities",
|
|
777
|
+
"defaults",
|
|
778
|
+
"limits",
|
|
779
|
+
"runtime",
|
|
780
|
+
"runtimeReferences",
|
|
781
|
+
"runtimeRegistry"
|
|
782
|
+
], "extension");
|
|
783
|
+
const source = asLocal(createHarnessDefinition(base));
|
|
784
|
+
const disabled = new Set(extension.disable ?? []);
|
|
785
|
+
unique([...extension.disable ?? []], "extension.disable");
|
|
786
|
+
for (const target of disabled) if (!source.abilities.some((use) => use.instanceId === target)) fail("extension.disable", "unknown ability identifier");
|
|
787
|
+
const replacements = new Map((extension.replaceAbilities ?? []).map((use) => [use.instanceId, use]));
|
|
788
|
+
unique([...(extension.replaceAbilities ?? []).map((use) => use.instanceId)], "extension.replaceAbilities");
|
|
789
|
+
for (const target of replacements.keys()) if (disabled.has(target) || !source.abilities.some((use) => use.instanceId === target)) fail("extension.replaceAbilities", "replacement must match an enabled existing ability");
|
|
790
|
+
const defaults = {
|
|
791
|
+
...source.defaults,
|
|
792
|
+
...extension.defaults ? validateDefaults(extension.defaults) : {}
|
|
793
|
+
};
|
|
794
|
+
if (extension.defaults?.skillDirs && source.defaults?.skillDirs) defaults.skillDirs = [.../* @__PURE__ */ new Set([...source.defaults.skillDirs, ...extension.defaults.skillDirs])];
|
|
795
|
+
const definition = createHarnessDefinition({
|
|
796
|
+
id: extension.id ?? source.id,
|
|
797
|
+
runtime: {
|
|
798
|
+
...source.runtime,
|
|
799
|
+
...extension.runtime
|
|
800
|
+
},
|
|
801
|
+
runtimeReferences: extension.runtimeReferences ?? source.runtimeReferences,
|
|
802
|
+
runtimeRegistry: extension.runtimeRegistry ?? source.runtimeRegistry,
|
|
803
|
+
abilities: [...source.abilities.filter((use) => !disabled.has(use.instanceId)).map((use) => replacements.get(use.instanceId) ?? use), ...extension.abilities ?? []],
|
|
804
|
+
defaults,
|
|
805
|
+
limits: {
|
|
806
|
+
...source.limits,
|
|
807
|
+
...extension.limits ? validateLimits(extension.limits) : {}
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
if (generatedIds.has(base) && !extension.id) generatedIds.add(definition);
|
|
811
|
+
const previous = metadata.get(base);
|
|
812
|
+
const origin = extension.id ?? source.id ?? "local";
|
|
813
|
+
metadata.set(definition, {
|
|
814
|
+
provenance: [
|
|
815
|
+
...previous?.provenance ?? [],
|
|
816
|
+
...Object.keys(extension.defaults ?? {}).map((key) => ({
|
|
817
|
+
path: `defaults.${key}`,
|
|
818
|
+
origin
|
|
819
|
+
})),
|
|
820
|
+
...Object.keys(extension.limits ?? {}).map((key) => ({
|
|
821
|
+
path: `limits.${key}`,
|
|
822
|
+
origin
|
|
823
|
+
}))
|
|
824
|
+
],
|
|
825
|
+
diagnostics: [...previous?.diagnostics ?? [], ...[...disabled].map((instanceId) => ({
|
|
826
|
+
code: "ability_disabled",
|
|
827
|
+
path: `abilities.${instanceId}`,
|
|
828
|
+
message: "Ability explicitly disabled",
|
|
829
|
+
severity: "info"
|
|
830
|
+
}))]
|
|
831
|
+
});
|
|
832
|
+
return definition;
|
|
833
|
+
}
|
|
834
|
+
function abilityDescription(input, path) {
|
|
835
|
+
if (!input || !Array.isArray(input.toolNames) || !Array.isArray(input.requirements)) fail(path, "expected tool and requirement declarations");
|
|
836
|
+
for (const name of input.toolNames) id(name, `${path}.toolNames`);
|
|
837
|
+
for (const requirement of input.requirements) id(requirement, `${path}.requirements`);
|
|
838
|
+
unique(input.toolNames, `${path}.toolNames`);
|
|
839
|
+
if (input.runtimeDependent !== void 0 && typeof input.runtimeDependent !== "boolean") fail(path, "invalid runtimeDependent declaration");
|
|
840
|
+
return {
|
|
841
|
+
toolNames: [...input.toolNames],
|
|
842
|
+
requirements: [...new Set(input.requirements)],
|
|
843
|
+
...input.runtimeDependent !== void 0 ? { runtimeDependent: input.runtimeDependent } : {}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
function describeHarnessDefinition(input) {
|
|
847
|
+
const definition = createHarnessDefinition(input);
|
|
848
|
+
const local = asLocal(definition);
|
|
849
|
+
const abilities = local.abilities.map((use, index) => {
|
|
850
|
+
let description;
|
|
851
|
+
try {
|
|
852
|
+
description = use.describe();
|
|
853
|
+
} catch {
|
|
854
|
+
fail(`abilities[${index}]`, "ability description failed");
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
instanceId: use.instanceId,
|
|
858
|
+
...use.portable ? {
|
|
859
|
+
type: use.portable.type,
|
|
860
|
+
version: use.portable.version
|
|
861
|
+
} : {},
|
|
862
|
+
...abilityDescription(description, `abilities[${index}]`)
|
|
863
|
+
};
|
|
864
|
+
});
|
|
865
|
+
return freeze({
|
|
866
|
+
id: local.id,
|
|
867
|
+
kind: definition.kind,
|
|
868
|
+
contractVersion: 1,
|
|
869
|
+
abilities,
|
|
870
|
+
toolNames: abilities.flatMap((ability) => [...ability.toolNames]),
|
|
871
|
+
requirements: [...new Set(abilities.flatMap((ability) => [...ability.requirements]))],
|
|
872
|
+
runtimeDependent: abilities.some((ability) => ability.runtimeDependent),
|
|
873
|
+
defaults: local.defaults ?? {},
|
|
874
|
+
limits: local.limits ?? {},
|
|
875
|
+
diagnostics: metadata.get(input)?.diagnostics ?? []
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
function resolveHarness(input, options) {
|
|
879
|
+
if (typeof options.projectRoot !== "string" || !(0, node_path.isAbsolute)(options.projectRoot)) fail("projectRoot", "an absolute host project root is required");
|
|
880
|
+
const definition = createHarnessDefinition(input);
|
|
881
|
+
const description = describeHarnessDefinition(definition);
|
|
882
|
+
const provided = new Set(options.requirements ?? []);
|
|
883
|
+
for (const requirement of description.requirements) if (!provided.has(requirement)) fail(`requirements.${requirement}`, "missing host-provided requirement");
|
|
884
|
+
const defaults = { ...description.defaults };
|
|
885
|
+
if (defaults.workspace) defaults.workspace = typeof defaults.workspace === "string" ? {
|
|
886
|
+
path: (0, node_path.resolve)(options.projectRoot, defaults.workspace),
|
|
887
|
+
mode: "read"
|
|
888
|
+
} : {
|
|
889
|
+
...defaults.workspace,
|
|
890
|
+
path: (0, node_path.resolve)(options.projectRoot, defaults.workspace.path)
|
|
891
|
+
};
|
|
892
|
+
if (defaults.skillDirs) defaults.skillDirs = [...new Set(defaults.skillDirs.map((path) => (0, node_path.resolve)(options.projectRoot, path)))];
|
|
893
|
+
const provenance = metadata.get(input)?.provenance ?? [
|
|
894
|
+
...Object.keys(defaults).map((key) => ({
|
|
895
|
+
path: `defaults.${key}`,
|
|
896
|
+
origin: description.id
|
|
897
|
+
})),
|
|
898
|
+
...Object.keys(description.limits).map((key) => ({
|
|
899
|
+
path: `limits.${key}`,
|
|
900
|
+
origin: description.id
|
|
901
|
+
})),
|
|
902
|
+
...description.abilities.map((ability) => ({
|
|
903
|
+
path: `abilities.${ability.instanceId}`,
|
|
904
|
+
origin: description.id
|
|
905
|
+
}))
|
|
906
|
+
];
|
|
907
|
+
return Object.freeze({
|
|
908
|
+
definition,
|
|
909
|
+
defaults: freeze(defaults),
|
|
910
|
+
limits: description.limits,
|
|
911
|
+
description,
|
|
912
|
+
provenance: freeze([...provenance])
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
function exportHarnessManifest(input) {
|
|
916
|
+
const definition = createHarnessDefinition(input);
|
|
917
|
+
if (definition.kind === "portable") return definition.manifest;
|
|
918
|
+
if (generatedIds.has(input) || generatedIds.has(definition)) fail("definition.id", "an explicit stable ID is required for export");
|
|
919
|
+
for (const key of [
|
|
920
|
+
"driver",
|
|
921
|
+
"controller",
|
|
922
|
+
"contextPolicy",
|
|
923
|
+
"completionPolicy"
|
|
924
|
+
]) if (definition.runtime?.[key] && !definition.runtimeReferences?.[key]) fail(`runtime.${key}`, "approved registry reference required for export");
|
|
925
|
+
resolveHarnessRuntime(definition);
|
|
926
|
+
const abilities = definition.abilities.map((use, index) => {
|
|
927
|
+
if (!use.portable || !use.factory) fail(`abilities[${index}]`, "portable selection and approved factory mapping required for export");
|
|
928
|
+
return use.portable;
|
|
929
|
+
});
|
|
930
|
+
return normalizePortable(validateHarnessManifest({
|
|
931
|
+
contractVersion: 1,
|
|
932
|
+
id: definition.id,
|
|
933
|
+
abilities,
|
|
934
|
+
...definition.runtimeReferences || definition.runtime?.modelRoles ? { runtime: {
|
|
935
|
+
...definition.runtimeReferences,
|
|
936
|
+
...definition.runtime?.modelRoles ? { modelRoles: definition.runtime.modelRoles } : {}
|
|
937
|
+
} } : {},
|
|
938
|
+
...definition.defaults !== void 0 ? { defaults: definition.defaults } : {},
|
|
939
|
+
...definition.limits !== void 0 ? { limits: definition.limits } : {}
|
|
940
|
+
}), definition.abilities.map((use) => use.factory), definition.runtimeRegistry).manifest;
|
|
941
|
+
}
|
|
942
|
+
function canonical(value) {
|
|
943
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
944
|
+
if (value !== null && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
945
|
+
return JSON.stringify(value);
|
|
946
|
+
}
|
|
947
|
+
function hashHarnessManifest(input) {
|
|
948
|
+
return (0, node_crypto.createHash)("sha256").update(canonical(validateHarnessManifest(input)), "utf8").digest("hex");
|
|
949
|
+
}
|
|
950
|
+
async function bindHarness(resolved, ctx) {
|
|
951
|
+
const local = asLocal(resolved.definition);
|
|
952
|
+
const bindings = [];
|
|
953
|
+
const diagnostics = [];
|
|
954
|
+
let disposing;
|
|
955
|
+
const dispose = () => disposing ??= (async () => {
|
|
956
|
+
for (let index = bindings.length - 1; index >= 0; index--) try {
|
|
957
|
+
await bindings[index].dispose?.();
|
|
958
|
+
} catch {
|
|
959
|
+
diagnostics.push({
|
|
960
|
+
code: "cleanup_failed",
|
|
961
|
+
path: `abilities[${index}]`,
|
|
962
|
+
message: "Ability cleanup failed",
|
|
963
|
+
severity: "warning"
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
return freeze([...diagnostics]);
|
|
967
|
+
})();
|
|
968
|
+
try {
|
|
969
|
+
for (const [index, ability] of local.abilities.entries()) {
|
|
970
|
+
if (ctx.signal?.aborted) fail(`abilities[${index}]`, "run cancelled before binding");
|
|
971
|
+
const binding = await ability.bind(ctx);
|
|
972
|
+
if (binding && typeof binding === "object") bindings.push(binding);
|
|
973
|
+
if (!binding || !Array.isArray(binding.tools)) fail(`abilities[${index}]`, "binding must contain tools");
|
|
974
|
+
if (ctx.signal?.aborted) fail(`abilities[${index}]`, "run cancelled during binding");
|
|
975
|
+
}
|
|
976
|
+
const tools = bindings.flatMap((binding) => [...binding.tools]);
|
|
977
|
+
const promptFragments = bindings.flatMap((binding) => [...binding.promptFragments ?? []]);
|
|
978
|
+
const contextSources = bindings.flatMap((binding) => [...binding.contextSources ?? []]);
|
|
979
|
+
const middleware = bindings.flatMap((binding) => [...binding.middleware ?? []]);
|
|
980
|
+
unique(tools.map((tool) => id(tool.name, "tools.name")), "tools");
|
|
981
|
+
unique(promptFragments.map((fragment) => id(fragment.id, "promptFragments.id")), "promptFragments");
|
|
982
|
+
unique(contextSources.map((source) => id(source.id, "contextSources.id")), "contextSources");
|
|
983
|
+
unique(middleware.map((item) => id(item.id, "middleware.id")), "middleware");
|
|
984
|
+
for (const tool of tools) if (typeof tool.execute !== "function" || !tool.parameters || ("_zod" in tool.parameters ? tool.parameters._zod.def.type !== "object" : typeof tool.parameters.safeParse !== "function")) fail("tools", "invalid tool binding");
|
|
985
|
+
for (const fragment of promptFragments) if (typeof fragment.text !== "string") fail("promptFragments", "expected text");
|
|
986
|
+
for (const source of contextSources) if (typeof source.fetch !== "function") fail("contextSources", "missing fetch function");
|
|
987
|
+
return {
|
|
988
|
+
tools,
|
|
989
|
+
promptFragments,
|
|
990
|
+
contextSources,
|
|
991
|
+
middleware: sortHarnessMiddleware(middleware),
|
|
992
|
+
dispose
|
|
993
|
+
};
|
|
994
|
+
} catch (error) {
|
|
995
|
+
await dispose();
|
|
996
|
+
if (error instanceof Error && diagnostics.length) try {
|
|
997
|
+
Object.defineProperty(error, "cleanupDiagnostics", { value: [...diagnostics] });
|
|
998
|
+
} catch {}
|
|
999
|
+
throw error;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
//#endregion
|
|
1003
|
+
//#region src/runtime/session-bindings.ts
|
|
1004
|
+
var HarnessSessionConflict = class extends Error {
|
|
1005
|
+
code = "session_conflict";
|
|
1006
|
+
constructor() {
|
|
1007
|
+
super("A writer already owns this tenant/session");
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
function harnessSessionKey(identity, sessionId) {
|
|
1011
|
+
return JSON.stringify([
|
|
1012
|
+
identity.tenantId,
|
|
1013
|
+
identity.userId,
|
|
1014
|
+
sessionId
|
|
1015
|
+
]);
|
|
1016
|
+
}
|
|
1017
|
+
/** One writer per scoped session; deliberately rejects rather than silently interleaving. */
|
|
1018
|
+
var InMemoryHarnessSessionStore = class {
|
|
1019
|
+
guarantees = {
|
|
1020
|
+
durable: false,
|
|
1021
|
+
compareAndSwap: false,
|
|
1022
|
+
singleWriter: "process"
|
|
1023
|
+
};
|
|
1024
|
+
records = /* @__PURE__ */ new Map();
|
|
1025
|
+
active = /* @__PURE__ */ new Set();
|
|
1026
|
+
async acquire(identity, sessionId) {
|
|
1027
|
+
const key = harnessSessionKey(identity, sessionId);
|
|
1028
|
+
if (this.active.has(key)) throw new HarnessSessionConflict();
|
|
1029
|
+
this.active.add(key);
|
|
1030
|
+
let released = false;
|
|
1031
|
+
const check = () => {
|
|
1032
|
+
if (released) throw new Error("Session lease has been released");
|
|
1033
|
+
};
|
|
1034
|
+
return {
|
|
1035
|
+
read: () => {
|
|
1036
|
+
check();
|
|
1037
|
+
return structuredClone(this.records.get(key) ?? {
|
|
1038
|
+
history: [],
|
|
1039
|
+
state: {},
|
|
1040
|
+
revision: 0,
|
|
1041
|
+
replayable: true
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
1044
|
+
commit: (snapshot) => {
|
|
1045
|
+
check();
|
|
1046
|
+
const revision = (this.records.get(key)?.revision ?? 0) + 1;
|
|
1047
|
+
this.records.set(key, structuredClone({
|
|
1048
|
+
...snapshot,
|
|
1049
|
+
revision
|
|
1050
|
+
}));
|
|
1051
|
+
},
|
|
1052
|
+
release: () => {
|
|
1053
|
+
if (!released) {
|
|
1054
|
+
released = true;
|
|
1055
|
+
this.active.delete(key);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
/** Imported display-only transcripts are readable but never represented as complete replay state. */
|
|
1061
|
+
importLegacy(identity, sessionId, history) {
|
|
1062
|
+
const key = harnessSessionKey(identity, sessionId);
|
|
1063
|
+
if (this.active.has(key)) throw new HarnessSessionConflict();
|
|
1064
|
+
this.records.set(key, {
|
|
1065
|
+
history: structuredClone(history),
|
|
1066
|
+
state: {},
|
|
1067
|
+
revision: 0,
|
|
1068
|
+
replayable: false
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
/** Session resources live until explicit closeSession; run resources use their returned lease. */
|
|
1073
|
+
var HarnessResourcePool = class {
|
|
1074
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1075
|
+
pending = /* @__PURE__ */ new Map();
|
|
1076
|
+
async acquire(identity, sessionId, id, scope, initialize) {
|
|
1077
|
+
if (scope === "run" || scope === "host") {
|
|
1078
|
+
const resource = await initialize();
|
|
1079
|
+
if (scope === "host" && resource.ownership !== "host") {
|
|
1080
|
+
try {
|
|
1081
|
+
await resource.dispose?.();
|
|
1082
|
+
} catch {}
|
|
1083
|
+
throw new Error("Host resources must be externally owned");
|
|
1084
|
+
}
|
|
1085
|
+
let released = false;
|
|
1086
|
+
return {
|
|
1087
|
+
value: resource.value,
|
|
1088
|
+
release: async () => {
|
|
1089
|
+
if (!released) {
|
|
1090
|
+
released = true;
|
|
1091
|
+
if (resource.ownership === "runtime") await resource.dispose?.();
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
const key = harnessSessionKey(identity, sessionId);
|
|
1097
|
+
let resources = this.sessions.get(key);
|
|
1098
|
+
if (!resources) {
|
|
1099
|
+
resources = /* @__PURE__ */ new Map();
|
|
1100
|
+
this.sessions.set(key, resources);
|
|
1101
|
+
}
|
|
1102
|
+
let entry = resources.get(id);
|
|
1103
|
+
if (!entry) {
|
|
1104
|
+
const pendingKey = JSON.stringify([key, id]);
|
|
1105
|
+
let promise = this.pending.get(pendingKey);
|
|
1106
|
+
if (!promise) {
|
|
1107
|
+
promise = initialize();
|
|
1108
|
+
this.pending.set(pendingKey, promise);
|
|
1109
|
+
}
|
|
1110
|
+
try {
|
|
1111
|
+
const resource = await promise;
|
|
1112
|
+
entry = resources.get(id);
|
|
1113
|
+
if (!entry) {
|
|
1114
|
+
entry = {
|
|
1115
|
+
resource,
|
|
1116
|
+
users: 0
|
|
1117
|
+
};
|
|
1118
|
+
resources.set(id, entry);
|
|
1119
|
+
}
|
|
1120
|
+
} finally {
|
|
1121
|
+
this.pending.delete(pendingKey);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
entry.users++;
|
|
1125
|
+
let released = false;
|
|
1126
|
+
return {
|
|
1127
|
+
value: entry.resource.value,
|
|
1128
|
+
release: async () => {
|
|
1129
|
+
if (!released) {
|
|
1130
|
+
released = true;
|
|
1131
|
+
entry.users--;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
async closeSession(identity, sessionId) {
|
|
1137
|
+
const key = harnessSessionKey(identity, sessionId);
|
|
1138
|
+
const resources = this.sessions.get(key);
|
|
1139
|
+
if (!resources) return [];
|
|
1140
|
+
if ([...resources.values()].some((entry) => entry.users > 0) || [...this.pending.keys()].some((pending) => JSON.parse(pending)[0] === key)) throw new HarnessSessionConflict();
|
|
1141
|
+
this.sessions.delete(key);
|
|
1142
|
+
const failures = [];
|
|
1143
|
+
for (const [id, entry] of [...resources].reverse()) try {
|
|
1144
|
+
if (entry.resource.ownership === "runtime") await entry.resource.dispose?.();
|
|
1145
|
+
} catch {
|
|
1146
|
+
failures.push(id);
|
|
1147
|
+
}
|
|
1148
|
+
return failures;
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
//#endregion
|
|
1152
|
+
//#region src/runtime/driver.ts
|
|
1153
|
+
var HarnessUnsupportedError = class extends Error {
|
|
1154
|
+
operation;
|
|
1155
|
+
code = "unsupported";
|
|
1156
|
+
constructor(operation) {
|
|
1157
|
+
super(`Unsupported harness operation: ${operation}`);
|
|
1158
|
+
this.operation = operation;
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
var HarnessBudgetError = class extends Error {
|
|
1162
|
+
code = "budget_exhausted";
|
|
1163
|
+
constructor(message) {
|
|
1164
|
+
super(message);
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
const zero = () => ({
|
|
1168
|
+
promptTokens: 0,
|
|
1169
|
+
completionTokens: 0,
|
|
1170
|
+
totalTokens: 0
|
|
1171
|
+
});
|
|
1172
|
+
function narrow(base, selected, label) {
|
|
1173
|
+
if (new Set(selected).size !== selected.length || selected.some((value) => !base.includes(value))) throw new Error(`Cannot widen ${label} grants`);
|
|
1174
|
+
return [...selected];
|
|
1175
|
+
}
|
|
1176
|
+
/** Local lifecycle owner. Custom driver code is trusted; services enforce all intercepted effects. */
|
|
1177
|
+
var HarnessRuntime = class {
|
|
1178
|
+
sessions;
|
|
1179
|
+
resources;
|
|
1180
|
+
config;
|
|
1181
|
+
artifacts = /* @__PURE__ */ new Map();
|
|
1182
|
+
getArtifact(identity, sessionId, id) {
|
|
1183
|
+
const entry = this.artifacts.get(id);
|
|
1184
|
+
if (!entry || entry.owner !== harnessSessionKey(identity, sessionId)) return void 0;
|
|
1185
|
+
return structuredClone(entry.value);
|
|
1186
|
+
}
|
|
1187
|
+
constructor(config) {
|
|
1188
|
+
const definition = config.definition ? createHarnessDefinition(config.definition) : void 0;
|
|
1189
|
+
const bindings = definition ? resolveHarnessRuntime(definition) : {};
|
|
1190
|
+
const driver = config.driver ?? bindings.driver;
|
|
1191
|
+
if (!driver) throw new Error("A trusted execution driver is required");
|
|
1192
|
+
config = {
|
|
1193
|
+
...bindings,
|
|
1194
|
+
...config,
|
|
1195
|
+
driver,
|
|
1196
|
+
definition
|
|
1197
|
+
};
|
|
1198
|
+
if (bindings.modelRoles) {
|
|
1199
|
+
config.models = { ...config.models };
|
|
1200
|
+
for (const [role, name] of Object.entries(bindings.modelRoles)) {
|
|
1201
|
+
const binding = config.models[name];
|
|
1202
|
+
if (!binding) throw new Error(`runtime.modelRoles.${role}: missing host model binding ${name}`);
|
|
1203
|
+
config.models = {
|
|
1204
|
+
...config.models,
|
|
1205
|
+
[role]: binding
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
if (!driver.id || !Number.isInteger(driver.version) || driver.version < 1) throw new Error("Invalid execution driver identity");
|
|
1210
|
+
if (driver.capabilities.durable) throw new HarnessUnsupportedError("durable recovery without a recovery adapter");
|
|
1211
|
+
if (driver.capabilities.controls.includes("replace")) throw new HarnessUnsupportedError("interrupt-and-replace without a driver checkpoint adapter");
|
|
1212
|
+
if (driver.capabilities.policyCoverage !== "local" || !driver.capabilities.controlledExecution) throw new HarnessUnsupportedError("mandatory local execution coverage");
|
|
1213
|
+
for (const value of Object.values(config.budgets ?? {})) if (!Number.isInteger(value) || value < 0) throw new Error("Harness budgets must be nonnegative integers");
|
|
1214
|
+
this.config = {
|
|
1215
|
+
...config,
|
|
1216
|
+
projectRoot: (0, node_path.resolve)(config.projectRoot ?? process.cwd()),
|
|
1217
|
+
requirements: config.requirements && Object.freeze([...config.requirements]),
|
|
1218
|
+
budgets: { ...config.budgets },
|
|
1219
|
+
models: Object.fromEntries(Object.entries(config.models ?? {}).map(([role, binding]) => [role, Object.freeze({
|
|
1220
|
+
...binding,
|
|
1221
|
+
options: Object.freeze([...binding.options ?? []])
|
|
1222
|
+
})])),
|
|
1223
|
+
contextPolicy: config.contextPolicy && Object.freeze({
|
|
1224
|
+
id: config.contextPolicy.id,
|
|
1225
|
+
project: config.contextPolicy.project.bind(config.contextPolicy)
|
|
1226
|
+
}),
|
|
1227
|
+
completionPolicy: config.completionPolicy && Object.freeze({
|
|
1228
|
+
id: config.completionPolicy.id,
|
|
1229
|
+
evaluate: config.completionPolicy.evaluate.bind(config.completionPolicy)
|
|
1230
|
+
}),
|
|
1231
|
+
controller: config.controller && Object.freeze({
|
|
1232
|
+
id: config.controller.id,
|
|
1233
|
+
prepareRun: config.controller.prepareRun?.bind(config.controller),
|
|
1234
|
+
prepareStep: config.controller.prepareStep?.bind(config.controller)
|
|
1235
|
+
}),
|
|
1236
|
+
driver: Object.freeze({
|
|
1237
|
+
id: driver.id,
|
|
1238
|
+
version: driver.version,
|
|
1239
|
+
start: driver.start.bind(driver),
|
|
1240
|
+
capabilities: Object.freeze({
|
|
1241
|
+
...driver.capabilities,
|
|
1242
|
+
controls: Object.freeze([...driver.capabilities.controls])
|
|
1243
|
+
})
|
|
1244
|
+
}),
|
|
1245
|
+
tools: [...config.tools ?? []],
|
|
1246
|
+
grants: {
|
|
1247
|
+
toolIds: [...config.grants.toolIds],
|
|
1248
|
+
modelRoles: [...config.grants.modelRoles],
|
|
1249
|
+
requiredToolIds: [...config.grants.requiredToolIds ?? []]
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
this.sessions = config.sessionStore ?? new InMemoryHarnessSessionStore();
|
|
1253
|
+
this.resources = config.resources ?? new HarnessResourcePool();
|
|
1254
|
+
}
|
|
1255
|
+
run(input, options) {
|
|
1256
|
+
return this.start(input, options).result();
|
|
1257
|
+
}
|
|
1258
|
+
stream(input, options) {
|
|
1259
|
+
return this.start(input, options).events();
|
|
1260
|
+
}
|
|
1261
|
+
start(input, options) {
|
|
1262
|
+
if (!options.identity?.tenantId || !options.identity.userId || !options.sessionId) throw new Error("Verified tenant, actor and session identity are required");
|
|
1263
|
+
const config = this.config;
|
|
1264
|
+
if (options.runMode !== void 0 && options.runMode !== "execute" && options.runMode !== "plan") throw new Error("Invalid run mode");
|
|
1265
|
+
const grants = options.grants ? {
|
|
1266
|
+
toolIds: narrow(config.grants.toolIds, options.grants.toolIds, "tool"),
|
|
1267
|
+
modelRoles: narrow(config.grants.modelRoles, options.grants.modelRoles, "model"),
|
|
1268
|
+
requiredToolIds: [.../* @__PURE__ */ new Set([...config.grants.requiredToolIds ?? [], ...options.grants.requiredToolIds ?? []])]
|
|
1269
|
+
} : config.grants;
|
|
1270
|
+
if (grants.requiredToolIds?.some((id) => !grants.toolIds.includes(id))) throw new Error("Required tool omitted by grants");
|
|
1271
|
+
if (options.deadline !== void 0 && !Number.isFinite(options.deadline)) throw new Error("Deadline must be finite");
|
|
1272
|
+
const controller = new AbortController();
|
|
1273
|
+
const runId = (0, node_crypto.randomUUID)();
|
|
1274
|
+
const request = Object.freeze({
|
|
1275
|
+
input: structuredClone(input),
|
|
1276
|
+
identity: Object.freeze({ ...options.identity }),
|
|
1277
|
+
sessionId: options.sessionId,
|
|
1278
|
+
runId,
|
|
1279
|
+
attemptId: (0, node_crypto.randomUUID)(),
|
|
1280
|
+
parentRunId: options.parentRunId,
|
|
1281
|
+
rootRunId: options.rootRunId ?? runId,
|
|
1282
|
+
signal: controller.signal,
|
|
1283
|
+
deadline: options.deadline,
|
|
1284
|
+
grants: Object.freeze({
|
|
1285
|
+
toolIds: Object.freeze([...grants.toolIds]),
|
|
1286
|
+
modelRoles: Object.freeze([...grants.modelRoles]),
|
|
1287
|
+
requiredToolIds: Object.freeze([...grants.requiredToolIds ?? []])
|
|
1288
|
+
}),
|
|
1289
|
+
runMode: options.runMode ?? "execute"
|
|
1290
|
+
});
|
|
1291
|
+
const events = new InMemoryHarnessEventStore({
|
|
1292
|
+
runId,
|
|
1293
|
+
sessionId: request.sessionId,
|
|
1294
|
+
attemptId: request.attemptId,
|
|
1295
|
+
parentRunId: request.parentRunId,
|
|
1296
|
+
rootRunId: request.rootRunId
|
|
1297
|
+
}, config.eventCapacity);
|
|
1298
|
+
const startedAt = Date.now();
|
|
1299
|
+
config.telemetry?.emit("run.start", {
|
|
1300
|
+
runId,
|
|
1301
|
+
agentName: config.driver.id,
|
|
1302
|
+
input: "",
|
|
1303
|
+
sessionId: request.sessionId,
|
|
1304
|
+
tenantId: request.identity.tenantId,
|
|
1305
|
+
userId: request.identity.userId,
|
|
1306
|
+
parentRunId: request.parentRunId,
|
|
1307
|
+
rootRunId: request.rootRunId,
|
|
1308
|
+
attemptId: request.attemptId
|
|
1309
|
+
});
|
|
1310
|
+
const observeControl = async (operation, work) => {
|
|
1311
|
+
const controllerCallId = (0, node_crypto.randomUUID)();
|
|
1312
|
+
const identity = {
|
|
1313
|
+
runId,
|
|
1314
|
+
controllerCallId,
|
|
1315
|
+
operation
|
|
1316
|
+
};
|
|
1317
|
+
config.telemetry?.emit("controller.start", identity);
|
|
1318
|
+
try {
|
|
1319
|
+
const result = await work();
|
|
1320
|
+
controller.signal.throwIfAborted();
|
|
1321
|
+
const decision = result;
|
|
1322
|
+
config.telemetry?.emit("controller.result", {
|
|
1323
|
+
...identity,
|
|
1324
|
+
decision: decision?.stop ? "stop" : decision?.action ?? "continue",
|
|
1325
|
+
modelRole: decision?.modelRole,
|
|
1326
|
+
activeToolCount: decision?.activeToolIds?.length
|
|
1327
|
+
});
|
|
1328
|
+
return result;
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
config.telemetry?.emit("controller.error", {
|
|
1331
|
+
...identity,
|
|
1332
|
+
status: controller.signal.aborted ? "cancelled" : "error"
|
|
1333
|
+
});
|
|
1334
|
+
throw error;
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
const observeModel = (provider) => {
|
|
1338
|
+
const identity = {
|
|
1339
|
+
runId,
|
|
1340
|
+
modelCallId: (0, node_crypto.randomUUID)(),
|
|
1341
|
+
providerId: provider.providerId,
|
|
1342
|
+
modelId: provider.modelId
|
|
1343
|
+
};
|
|
1344
|
+
config.telemetry?.emit("model.start", identity);
|
|
1345
|
+
return (complete, amount) => {
|
|
1346
|
+
if (complete) config.telemetry?.emit("model.result", {
|
|
1347
|
+
...identity,
|
|
1348
|
+
usage: amount,
|
|
1349
|
+
status: "success"
|
|
1350
|
+
});
|
|
1351
|
+
else config.telemetry?.emit("model.error", {
|
|
1352
|
+
...identity,
|
|
1353
|
+
status: controller.signal.aborted ? "cancelled" : "error",
|
|
1354
|
+
...amount ? { usage: amount } : {}
|
|
1355
|
+
});
|
|
1356
|
+
};
|
|
1357
|
+
};
|
|
1358
|
+
const generateObserved = async (provider, messages, supplied) => {
|
|
1359
|
+
const end = observeModel(provider);
|
|
1360
|
+
try {
|
|
1361
|
+
const response = await provider.generate(messages, supplied);
|
|
1362
|
+
end(!controller.signal.aborted, response.usage);
|
|
1363
|
+
return response;
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
end(false);
|
|
1366
|
+
throw error;
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
let terminal = false;
|
|
1370
|
+
const inbox = [];
|
|
1371
|
+
const cancel = (reason = "Run cancelled") => {
|
|
1372
|
+
if (!terminal && !controller.signal.aborted) {
|
|
1373
|
+
events.append({
|
|
1374
|
+
type: "control",
|
|
1375
|
+
operation: "cancel",
|
|
1376
|
+
reason
|
|
1377
|
+
});
|
|
1378
|
+
controller.abort(new Error(reason));
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
const onAbort = () => cancel("Caller cancelled the run");
|
|
1382
|
+
const callerSignal = options.signal;
|
|
1383
|
+
callerSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1384
|
+
if (callerSignal?.aborted) onAbort();
|
|
1385
|
+
let timer;
|
|
1386
|
+
const scheduleDeadline = () => {
|
|
1387
|
+
if (request.deadline === void 0 || controller.signal.aborted) return;
|
|
1388
|
+
const remaining = request.deadline - Date.now();
|
|
1389
|
+
if (remaining <= 0) cancel("Run deadline exceeded");
|
|
1390
|
+
else timer = setTimeout(scheduleDeadline, Math.min(remaining, 2147483647));
|
|
1391
|
+
};
|
|
1392
|
+
scheduleDeadline();
|
|
1393
|
+
const result = Promise.resolve().then(async () => {
|
|
1394
|
+
let lease;
|
|
1395
|
+
let bound;
|
|
1396
|
+
const releases = [];
|
|
1397
|
+
const usage = zero();
|
|
1398
|
+
let modelCalls = 0;
|
|
1399
|
+
let reservedTokens = 0;
|
|
1400
|
+
const cleanupDiagnostics = [];
|
|
1401
|
+
let toolCalls = 0;
|
|
1402
|
+
let revision = 0;
|
|
1403
|
+
let currentRole = "main";
|
|
1404
|
+
let prepareDefaults = {};
|
|
1405
|
+
let final = {
|
|
1406
|
+
text: "",
|
|
1407
|
+
status: "failed"
|
|
1408
|
+
};
|
|
1409
|
+
let canonical = [];
|
|
1410
|
+
let conversations = {};
|
|
1411
|
+
let state = {};
|
|
1412
|
+
let replayable = true;
|
|
1413
|
+
let committed = false;
|
|
1414
|
+
let acceptingOperations = true;
|
|
1415
|
+
const owned = /* @__PURE__ */ new Set();
|
|
1416
|
+
const streams = /* @__PURE__ */ new Set();
|
|
1417
|
+
const track = (operation) => {
|
|
1418
|
+
owned.add(operation);
|
|
1419
|
+
operation.then(() => owned.delete(operation), () => owned.delete(operation));
|
|
1420
|
+
return operation;
|
|
1421
|
+
};
|
|
1422
|
+
const ensureActive = () => {
|
|
1423
|
+
if (request.deadline !== void 0 && Date.now() >= request.deadline) cancel("Run deadline exceeded");
|
|
1424
|
+
controller.signal.throwIfAborted();
|
|
1425
|
+
if (!acceptingOperations) throw new Error("Run has finished accepting operations");
|
|
1426
|
+
};
|
|
1427
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
1428
|
+
try {
|
|
1429
|
+
ensureActive();
|
|
1430
|
+
lease = await this.sessions.acquire(request.identity, request.sessionId);
|
|
1431
|
+
ensureActive();
|
|
1432
|
+
const snapshot = lease.read();
|
|
1433
|
+
canonical = snapshot.history;
|
|
1434
|
+
conversations = snapshot.conversations ?? {};
|
|
1435
|
+
state = snapshot.state;
|
|
1436
|
+
replayable = snapshot.replayable;
|
|
1437
|
+
let policy;
|
|
1438
|
+
let services;
|
|
1439
|
+
const ctx = new _agentium_core.RunContext({
|
|
1440
|
+
executionPolicy: {
|
|
1441
|
+
decide: (call, callCtx) => policy.decide(call, callCtx),
|
|
1442
|
+
resolveEffect: (call, callCtx) => policy.resolveEffect?.(call, callCtx) ?? "unknown"
|
|
1443
|
+
},
|
|
1444
|
+
sessionId: request.sessionId,
|
|
1445
|
+
userId: request.identity.userId,
|
|
1446
|
+
tenantId: request.identity.tenantId,
|
|
1447
|
+
runId,
|
|
1448
|
+
signal: controller.signal,
|
|
1449
|
+
runMode: request.runMode,
|
|
1450
|
+
eventBus: new _agentium_core.EventBus(),
|
|
1451
|
+
sessionState: state,
|
|
1452
|
+
metadata: {
|
|
1453
|
+
parentRunId: request.parentRunId,
|
|
1454
|
+
rootRunId: request.rootRunId
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1457
|
+
Object.defineProperty(ctx, "executionServices", {
|
|
1458
|
+
get: () => services,
|
|
1459
|
+
configurable: false
|
|
1460
|
+
});
|
|
1461
|
+
policy = {
|
|
1462
|
+
decide: async (call, callCtx) => {
|
|
1463
|
+
ensureActive();
|
|
1464
|
+
if (!request.grants.toolIds.includes(call.toolName) || activeTools.has(callCtx.runId) && !activeTools.get(callCtx.runId).includes(call.toolName)) return {
|
|
1465
|
+
action: "deny",
|
|
1466
|
+
reason: "Tool is outside the effective grants"
|
|
1467
|
+
};
|
|
1468
|
+
if (revision > 0 && !config.allowRevisionEffects) return {
|
|
1469
|
+
action: "deny",
|
|
1470
|
+
reason: "Completion revisions cannot execute effects without host opt-in"
|
|
1471
|
+
};
|
|
1472
|
+
if (toolCalls >= (config.budgets?.maxToolCalls ?? Infinity)) return {
|
|
1473
|
+
action: "deny",
|
|
1474
|
+
reason: "Aggregate tool budget exhausted"
|
|
1475
|
+
};
|
|
1476
|
+
toolCalls++;
|
|
1477
|
+
return (0, _agentium_core.evaluateExecutionPolicy)(config.executionPolicy, call, callCtx);
|
|
1478
|
+
},
|
|
1479
|
+
resolveEffect: config.executionPolicy?.resolveEffect
|
|
1480
|
+
};
|
|
1481
|
+
const resolved = config.definition ? resolveHarness(config.definition, {
|
|
1482
|
+
projectRoot: config.projectRoot ?? process.cwd(),
|
|
1483
|
+
requirements: config.requirements
|
|
1484
|
+
}) : void 0;
|
|
1485
|
+
if (resolved) bound = await bindHarness(resolved, ctx);
|
|
1486
|
+
ensureActive();
|
|
1487
|
+
const tools = [...config.tools ?? [], ...bound?.tools ?? []];
|
|
1488
|
+
if (new Set(tools.map((tool) => tool.name)).size !== tools.length) throw new Error("Duplicate runtime tool bindings");
|
|
1489
|
+
const executor = new _agentium_core.ToolExecutor(tools, {
|
|
1490
|
+
approvalManager: config.approvalManager,
|
|
1491
|
+
executionPolicy: policy
|
|
1492
|
+
});
|
|
1493
|
+
const sources = bound ? await fetchHarnessContext(bound.contextSources, typeof input === "string" ? input : JSON.stringify(input), ctx) : { messages: [] };
|
|
1494
|
+
const entries = sources.messages.map((message) => {
|
|
1495
|
+
const source = JSON.parse(String(message.content));
|
|
1496
|
+
return {
|
|
1497
|
+
id: source.entryId,
|
|
1498
|
+
text: source.text,
|
|
1499
|
+
trust: "source",
|
|
1500
|
+
source: source.source,
|
|
1501
|
+
byteLength: Buffer.byteLength(source.text)
|
|
1502
|
+
};
|
|
1503
|
+
});
|
|
1504
|
+
const middleware = bound?.middleware ?? [];
|
|
1505
|
+
const prompts = (bound?.promptFragments ?? []).map((fragment) => ({
|
|
1506
|
+
role: "system",
|
|
1507
|
+
content: fragment.text
|
|
1508
|
+
}));
|
|
1509
|
+
const prepare = async (provider, messages, supplied = {}, callCtx = ctx) => {
|
|
1510
|
+
ensureActive();
|
|
1511
|
+
if (modelCalls >= (config.budgets?.maxModelCalls ?? Infinity) || usage.totalTokens >= (config.budgets?.maxTokens ?? Infinity)) throw new HarnessBudgetError("Aggregate model/token budget exhausted");
|
|
1512
|
+
const index = modelCalls++;
|
|
1513
|
+
const next = config.controller?.prepareStep ? await observeControl("prepareStep", () => config.controller.prepareStep({
|
|
1514
|
+
index,
|
|
1515
|
+
tools: supplied.tools ?? []
|
|
1516
|
+
}, callCtx)) : void 0;
|
|
1517
|
+
const overrides = {
|
|
1518
|
+
...prepareDefaults,
|
|
1519
|
+
...next
|
|
1520
|
+
};
|
|
1521
|
+
if (overrides.stop) throw new HarnessBudgetError(overrides.stop.reason);
|
|
1522
|
+
const role = overrides.modelRole ?? "main";
|
|
1523
|
+
if (!request.grants.modelRoles.includes(role)) throw new Error("Controller selected an ungranted model role");
|
|
1524
|
+
const binding = config.models?.[role];
|
|
1525
|
+
const selected = binding?.provider ?? (role === "main" ? provider : void 0);
|
|
1526
|
+
if (!selected) throw new Error("No host model binding for selected role");
|
|
1527
|
+
const active = overrides.activeToolIds ?? (supplied.tools ?? []).map((tool) => tool.name).filter((id) => request.grants.toolIds.includes(id));
|
|
1528
|
+
narrow(request.grants.toolIds, active, "active tool");
|
|
1529
|
+
if (request.grants.requiredToolIds?.some((id) => !active.includes(id))) throw new Error("Controller omitted a required tool");
|
|
1530
|
+
if (active.some((id) => !(supplied.tools ?? []).some((tool) => tool.name === id))) throw new Error("Controller selected an unavailable tool");
|
|
1531
|
+
for (const key of Object.keys(overrides.options ?? {})) if (!binding?.options?.includes(key)) throw new HarnessUnsupportedError(`model option ${key}`);
|
|
1532
|
+
if (currentRole !== role && messages.some((message) => message.providerExtras && !message.providerExtras.harnessContext)) throw new Error("Cannot switch model roles while opaque provider continuation is present");
|
|
1533
|
+
currentRole = role;
|
|
1534
|
+
const composed = [
|
|
1535
|
+
...prompts,
|
|
1536
|
+
...sources.messages,
|
|
1537
|
+
...messages
|
|
1538
|
+
];
|
|
1539
|
+
const transformed = await runBeforeModel(middleware, composed, callCtx);
|
|
1540
|
+
const projection = await projectHarnessContext(overrides.contextPolicy ?? config.contextPolicy, transformed, callCtx, entries);
|
|
1541
|
+
ensureActive();
|
|
1542
|
+
const remaining = (config.budgets?.maxTokens ?? Infinity) - usage.totalTokens - reservedTokens;
|
|
1543
|
+
if (remaining <= 0) throw new HarnessBudgetError("Aggregate token budget exhausted or reserved by another call");
|
|
1544
|
+
for (const value of [supplied.maxTokens, overrides.options?.maxTokens]) if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new HarnessUnsupportedError("maxTokens must be a finite positive integer");
|
|
1545
|
+
const options = {
|
|
1546
|
+
...supplied,
|
|
1547
|
+
...overrides.options,
|
|
1548
|
+
signal: controller.signal,
|
|
1549
|
+
tools: supplied.tools?.filter((tool) => active.includes(tool.name)),
|
|
1550
|
+
...Number.isFinite(remaining) ? { maxTokens: Math.min(supplied.maxTokens ?? Infinity, overrides.options?.maxTokens ?? Infinity, remaining) } : {}
|
|
1551
|
+
};
|
|
1552
|
+
activeTools.set(callCtx.runId, active);
|
|
1553
|
+
const reservation = Number.isFinite(remaining) ? options.maxTokens ?? remaining : 0;
|
|
1554
|
+
reservedTokens += reservation;
|
|
1555
|
+
return {
|
|
1556
|
+
provider: selected,
|
|
1557
|
+
messages: projection.messages,
|
|
1558
|
+
options,
|
|
1559
|
+
reservation
|
|
1560
|
+
};
|
|
1561
|
+
};
|
|
1562
|
+
const account = (provider, amount) => {
|
|
1563
|
+
if ([
|
|
1564
|
+
amount.promptTokens,
|
|
1565
|
+
amount.completionTokens,
|
|
1566
|
+
amount.totalTokens
|
|
1567
|
+
].some((value) => !Number.isFinite(value) || value < 0)) throw new Error("Provider returned invalid token usage");
|
|
1568
|
+
usage.promptTokens += amount.promptTokens;
|
|
1569
|
+
usage.completionTokens += amount.completionTokens;
|
|
1570
|
+
usage.totalTokens += amount.totalTokens;
|
|
1571
|
+
for (const key of [
|
|
1572
|
+
"reasoningTokens",
|
|
1573
|
+
"cachedTokens",
|
|
1574
|
+
"audioInputTokens",
|
|
1575
|
+
"audioOutputTokens"
|
|
1576
|
+
]) if (amount[key] !== void 0) usage[key] = (usage[key] ?? 0) + amount[key];
|
|
1577
|
+
if (!controller.signal.aborted && !terminal) events.append({
|
|
1578
|
+
type: "model.complete",
|
|
1579
|
+
providerId: provider.providerId,
|
|
1580
|
+
modelId: provider.modelId,
|
|
1581
|
+
usage: {
|
|
1582
|
+
promptTokens: amount.promptTokens,
|
|
1583
|
+
completionTokens: amount.completionTokens,
|
|
1584
|
+
totalTokens: amount.totalTokens
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
};
|
|
1588
|
+
services = {
|
|
1589
|
+
ctx,
|
|
1590
|
+
signal: controller.signal,
|
|
1591
|
+
tools,
|
|
1592
|
+
definitionId: config.definition?.kind === "portable" ? config.definition.manifest.id : config.definition?.id,
|
|
1593
|
+
agentConfiguration: {
|
|
1594
|
+
defaults: resolved?.defaults ?? {},
|
|
1595
|
+
limits: resolved?.limits ?? {},
|
|
1596
|
+
projectRoot: config.projectRoot ?? process.cwd()
|
|
1597
|
+
},
|
|
1598
|
+
executionPolicy: policy,
|
|
1599
|
+
approvalManager: config.approvalManager,
|
|
1600
|
+
state,
|
|
1601
|
+
sessionKey: harnessSessionKey(request.identity, request.sessionId),
|
|
1602
|
+
get history() {
|
|
1603
|
+
return structuredClone(canonical);
|
|
1604
|
+
},
|
|
1605
|
+
controlModel: (role, messages, options = {}) => track((async () => {
|
|
1606
|
+
ensureActive();
|
|
1607
|
+
if (!request.grants.modelRoles.includes(role)) throw new Error("Ungranted control model role");
|
|
1608
|
+
const binding = config.models?.[role];
|
|
1609
|
+
if (!binding) throw new Error("Control model requires an explicit host binding");
|
|
1610
|
+
for (const key of Object.keys(options)) if (!["maxTokens", "temperature"].includes(key) || !binding.options?.includes(key)) throw new HarnessUnsupportedError(`control model option ${key}`);
|
|
1611
|
+
if (options.maxTokens !== void 0 && (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0)) throw new HarnessUnsupportedError("maxTokens must be a finite positive integer");
|
|
1612
|
+
if (options.temperature !== void 0 && (!Number.isFinite(options.temperature) || options.temperature < 0)) throw new HarnessUnsupportedError("temperature must be finite and non-negative");
|
|
1613
|
+
const projection = structuredClone(messages);
|
|
1614
|
+
if (projection.some((message) => message.role === "tool" || message.toolCalls?.length || message.providerExtras)) throw new Error("Control model calls require independent plain messages without provider/tool continuation");
|
|
1615
|
+
(0, _agentium_core.validateConversationTransform)(projection, projection);
|
|
1616
|
+
const remaining = (config.budgets?.maxTokens ?? Infinity) - usage.totalTokens - reservedTokens;
|
|
1617
|
+
if (modelCalls >= (config.budgets?.maxModelCalls ?? Infinity) || remaining <= 0) throw new HarnessBudgetError("Aggregate model/token budget exhausted");
|
|
1618
|
+
const maxTokens = Math.min(options.maxTokens ?? Infinity, remaining);
|
|
1619
|
+
const reservation = Number.isFinite(maxTokens) ? maxTokens : 0;
|
|
1620
|
+
modelCalls++;
|
|
1621
|
+
reservedTokens += reservation;
|
|
1622
|
+
try {
|
|
1623
|
+
ensureActive();
|
|
1624
|
+
const response = await generateObserved(binding.provider, projection, {
|
|
1625
|
+
...options,
|
|
1626
|
+
...Number.isFinite(maxTokens) ? { maxTokens } : {},
|
|
1627
|
+
signal: controller.signal
|
|
1628
|
+
});
|
|
1629
|
+
account(binding.provider, response.usage);
|
|
1630
|
+
ensureActive();
|
|
1631
|
+
if (response.finishReason !== "stop" || response.message.toolCalls?.length) throw new Error("Control model must return a complete tool-free response");
|
|
1632
|
+
return response;
|
|
1633
|
+
} finally {
|
|
1634
|
+
reservedTokens -= reservation;
|
|
1635
|
+
}
|
|
1636
|
+
})()),
|
|
1637
|
+
model: (provider, messages, options, callCtx) => track((async () => {
|
|
1638
|
+
const call = await prepare(provider, messages, options, callCtx);
|
|
1639
|
+
try {
|
|
1640
|
+
ensureActive();
|
|
1641
|
+
const response = await generateObserved(call.provider, call.messages, call.options);
|
|
1642
|
+
account(call.provider, response.usage);
|
|
1643
|
+
ensureActive();
|
|
1644
|
+
await runAfterModel(middleware, response, callCtx ?? ctx);
|
|
1645
|
+
return response;
|
|
1646
|
+
} finally {
|
|
1647
|
+
reservedTokens -= call.reservation;
|
|
1648
|
+
}
|
|
1649
|
+
})()),
|
|
1650
|
+
streamModel: (provider, messages, options, callCtx) => {
|
|
1651
|
+
let finishLifetime;
|
|
1652
|
+
track(new Promise((resolve) => {
|
|
1653
|
+
finishLifetime = resolve;
|
|
1654
|
+
}));
|
|
1655
|
+
const implementation = (async function* () {
|
|
1656
|
+
const call = await prepare(provider, messages, options, callCtx);
|
|
1657
|
+
const endObservation = observeModel(call.provider);
|
|
1658
|
+
let stream;
|
|
1659
|
+
let succeeded = false;
|
|
1660
|
+
let text = "";
|
|
1661
|
+
let finished = false;
|
|
1662
|
+
let amount = zero();
|
|
1663
|
+
let reason = "stop";
|
|
1664
|
+
let extras;
|
|
1665
|
+
const calls = [];
|
|
1666
|
+
try {
|
|
1667
|
+
ensureActive();
|
|
1668
|
+
stream = call.provider.stream(call.messages, call.options);
|
|
1669
|
+
while (true) {
|
|
1670
|
+
const next = await stream.next();
|
|
1671
|
+
if (!next.done && next.value.type === "finish") {
|
|
1672
|
+
if (finished) throw new Error("Provider emitted duplicate terminal usage");
|
|
1673
|
+
finished = true;
|
|
1674
|
+
reason = next.value.finishReason;
|
|
1675
|
+
extras = next.value.providerExtras;
|
|
1676
|
+
amount = next.value.usage ?? zero();
|
|
1677
|
+
account(call.provider, amount);
|
|
1678
|
+
}
|
|
1679
|
+
ensureActive();
|
|
1680
|
+
if (next.done) break;
|
|
1681
|
+
if (next.value.type === "text") text += next.value.text;
|
|
1682
|
+
else if (next.value.type === "tool_call_start") calls.push({
|
|
1683
|
+
...next.value.toolCall,
|
|
1684
|
+
args: ""
|
|
1685
|
+
});
|
|
1686
|
+
else if (next.value.type === "tool_call_delta") {
|
|
1687
|
+
const chunk = next.value;
|
|
1688
|
+
const pending = calls.find((item) => item.id === chunk.toolCallId);
|
|
1689
|
+
if (pending) pending.args += chunk.argumentsDelta;
|
|
1690
|
+
}
|
|
1691
|
+
yield next.value;
|
|
1692
|
+
}
|
|
1693
|
+
if (!finished) throw new Error("Provider stream ended without a terminal response");
|
|
1694
|
+
const response = {
|
|
1695
|
+
message: {
|
|
1696
|
+
role: "assistant",
|
|
1697
|
+
content: text || null,
|
|
1698
|
+
...calls.length ? { toolCalls: calls.map((item) => ({
|
|
1699
|
+
id: item.id,
|
|
1700
|
+
name: item.name,
|
|
1701
|
+
arguments: JSON.parse(item.args)
|
|
1702
|
+
})) } : {},
|
|
1703
|
+
...extras ? { providerExtras: extras } : {}
|
|
1704
|
+
},
|
|
1705
|
+
usage: amount,
|
|
1706
|
+
finishReason: reason === "tool_calls" || reason === "length" || reason === "content_filter" ? reason : "stop",
|
|
1707
|
+
raw: { streamed: true }
|
|
1708
|
+
};
|
|
1709
|
+
await runAfterModel(middleware, response, callCtx ?? ctx);
|
|
1710
|
+
succeeded = true;
|
|
1711
|
+
} finally {
|
|
1712
|
+
endObservation(succeeded, finished ? amount : void 0);
|
|
1713
|
+
reservedTokens -= call.reservation;
|
|
1714
|
+
await stream?.return(void 0);
|
|
1715
|
+
}
|
|
1716
|
+
})();
|
|
1717
|
+
const finish = () => {
|
|
1718
|
+
streams.delete(iterator);
|
|
1719
|
+
finishLifetime();
|
|
1720
|
+
};
|
|
1721
|
+
const iterator = {
|
|
1722
|
+
[Symbol.asyncIterator]() {
|
|
1723
|
+
return this;
|
|
1724
|
+
},
|
|
1725
|
+
[Symbol.asyncDispose]: async () => {
|
|
1726
|
+
await iterator.return(void 0);
|
|
1727
|
+
},
|
|
1728
|
+
next: (...args) => track(implementation.next(...args)).then((item) => {
|
|
1729
|
+
if (item.done) finish();
|
|
1730
|
+
return item;
|
|
1731
|
+
}, (error) => {
|
|
1732
|
+
finish();
|
|
1733
|
+
throw error;
|
|
1734
|
+
}),
|
|
1735
|
+
return: (value) => track(implementation.return(value)).finally(finish),
|
|
1736
|
+
throw: (error) => track(implementation.throw(error)).finally(finish)
|
|
1737
|
+
};
|
|
1738
|
+
streams.add(iterator);
|
|
1739
|
+
return iterator;
|
|
1740
|
+
},
|
|
1741
|
+
runOwned: (operation) => track((async () => {
|
|
1742
|
+
ensureActive();
|
|
1743
|
+
return operation();
|
|
1744
|
+
})()),
|
|
1745
|
+
observeTool: async (result, callCtx) => {
|
|
1746
|
+
ensureActive();
|
|
1747
|
+
await runAfterTool(middleware, result, callCtx ?? ctx);
|
|
1748
|
+
services.emit({
|
|
1749
|
+
type: "tool.complete",
|
|
1750
|
+
toolName: result.toolName,
|
|
1751
|
+
toolCallId: result.toolCallId,
|
|
1752
|
+
denied: Boolean(result.error)
|
|
1753
|
+
});
|
|
1754
|
+
},
|
|
1755
|
+
dispatch: (call) => track((async () => {
|
|
1756
|
+
ensureActive();
|
|
1757
|
+
const [result] = await executor.executeAll([call], ctx);
|
|
1758
|
+
ensureActive();
|
|
1759
|
+
await runAfterTool(middleware, result, ctx);
|
|
1760
|
+
services.emit({
|
|
1761
|
+
type: "tool.complete",
|
|
1762
|
+
toolName: call.name,
|
|
1763
|
+
toolCallId: call.id,
|
|
1764
|
+
denied: Boolean(result.error)
|
|
1765
|
+
});
|
|
1766
|
+
return result;
|
|
1767
|
+
})()),
|
|
1768
|
+
dispatchEffect: (name, args, execute) => track((async () => {
|
|
1769
|
+
ensureActive();
|
|
1770
|
+
let value;
|
|
1771
|
+
const tool = {
|
|
1772
|
+
name,
|
|
1773
|
+
description: "Trusted deterministic workflow step",
|
|
1774
|
+
parameters: zod_v3.z.object({}).passthrough(),
|
|
1775
|
+
execute: async (validated, callCtx) => {
|
|
1776
|
+
value = await execute(validated, callCtx);
|
|
1777
|
+
return "Completed";
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
const [result] = await new _agentium_core.ToolExecutor([tool], {
|
|
1781
|
+
approvalManager: config.approvalManager,
|
|
1782
|
+
executionPolicy: policy
|
|
1783
|
+
}).executeAll([{
|
|
1784
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1785
|
+
name,
|
|
1786
|
+
arguments: args
|
|
1787
|
+
}], ctx);
|
|
1788
|
+
ensureActive();
|
|
1789
|
+
await runAfterTool(middleware, result, ctx);
|
|
1790
|
+
services.emit({
|
|
1791
|
+
type: "tool.complete",
|
|
1792
|
+
toolName: name,
|
|
1793
|
+
toolCallId: result.toolCallId,
|
|
1794
|
+
denied: Boolean(result.error)
|
|
1795
|
+
});
|
|
1796
|
+
if (result.error) throw new Error(result.error);
|
|
1797
|
+
return value;
|
|
1798
|
+
})()),
|
|
1799
|
+
recordConversation: (executionId, messages) => {
|
|
1800
|
+
ensureActive();
|
|
1801
|
+
const combined = [...conversations[executionId] ?? [], ...structuredClone(messages)];
|
|
1802
|
+
(0, _agentium_core.validateConversationTransform)(combined, combined);
|
|
1803
|
+
conversations[executionId] = combined;
|
|
1804
|
+
if (executionId === request.runId) canonical.push(...structuredClone(messages));
|
|
1805
|
+
},
|
|
1806
|
+
append: (messages) => {
|
|
1807
|
+
ensureActive();
|
|
1808
|
+
canonical.push(...structuredClone(messages));
|
|
1809
|
+
},
|
|
1810
|
+
emit: (payload) => {
|
|
1811
|
+
ensureActive();
|
|
1812
|
+
if (Buffer.byteLength(JSON.stringify(payload)) > 65536) throw new Error("Generic event exceeds 64KB; store an artifact reference instead");
|
|
1813
|
+
events.append(payload);
|
|
1814
|
+
},
|
|
1815
|
+
takeInput: () => {
|
|
1816
|
+
ensureActive();
|
|
1817
|
+
const index = inbox.findIndex((item) => item.mode === "steer");
|
|
1818
|
+
return index < 0 ? void 0 : inbox.splice(index, 1)[0];
|
|
1819
|
+
},
|
|
1820
|
+
resource: (id, scope, initialize) => track((async () => {
|
|
1821
|
+
ensureActive();
|
|
1822
|
+
const lease = await this.resources.acquire(request.identity, request.sessionId, id, scope, initialize);
|
|
1823
|
+
releases.push(lease.release);
|
|
1824
|
+
ensureActive();
|
|
1825
|
+
return lease.value;
|
|
1826
|
+
})()),
|
|
1827
|
+
putArtifact: (value) => {
|
|
1828
|
+
ensureActive();
|
|
1829
|
+
const id = (0, node_crypto.randomUUID)();
|
|
1830
|
+
this.artifacts.set(id, {
|
|
1831
|
+
owner: services.sessionKey,
|
|
1832
|
+
value: structuredClone(value)
|
|
1833
|
+
});
|
|
1834
|
+
return id;
|
|
1835
|
+
},
|
|
1836
|
+
getArtifact: (id) => {
|
|
1837
|
+
ensureActive();
|
|
1838
|
+
return this.getArtifact(request.identity, request.sessionId, id);
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
prepareDefaults = (config.controller?.prepareRun ? await observeControl("prepareRun", () => config.controller.prepareRun(ctx)) : void 0) ?? {};
|
|
1842
|
+
if (prepareDefaults.stop) throw new HarnessBudgetError(prepareDefaults.stop.reason);
|
|
1843
|
+
events.append({
|
|
1844
|
+
type: "run.started",
|
|
1845
|
+
driverId: config.driver.id
|
|
1846
|
+
});
|
|
1847
|
+
let current = request;
|
|
1848
|
+
while (true) {
|
|
1849
|
+
ensureActive();
|
|
1850
|
+
final = await config.driver.start(current, services);
|
|
1851
|
+
if (streams.size) throw new Error("Driver returned with an unfinished model stream");
|
|
1852
|
+
ensureActive();
|
|
1853
|
+
if (!final || typeof final.text !== "string" || final.status !== void 0 && ![
|
|
1854
|
+
"completed",
|
|
1855
|
+
"failed",
|
|
1856
|
+
"cancelled",
|
|
1857
|
+
"stopped",
|
|
1858
|
+
"awaiting_input"
|
|
1859
|
+
].includes(final.status)) throw new Error("Driver returned an invalid terminal output");
|
|
1860
|
+
final = structuredClone(final);
|
|
1861
|
+
JSON.stringify(final);
|
|
1862
|
+
if (final.history) {
|
|
1863
|
+
const proposed = structuredClone(final.history);
|
|
1864
|
+
if (canonical.some((message, index) => JSON.stringify(message) !== JSON.stringify(proposed[index]))) throw new Error("Driver replaced immutable canonical history");
|
|
1865
|
+
(0, _agentium_core.validateConversationTransform)(proposed, proposed);
|
|
1866
|
+
canonical = proposed;
|
|
1867
|
+
}
|
|
1868
|
+
if (final.status && final.status !== "completed") break;
|
|
1869
|
+
const decision = config.completionPolicy ? await observeControl("completion", () => config.completionPolicy.evaluate({
|
|
1870
|
+
text: final.text,
|
|
1871
|
+
structured: final.structured,
|
|
1872
|
+
revision
|
|
1873
|
+
}, ctx)) : void 0;
|
|
1874
|
+
if (decision) {
|
|
1875
|
+
services.emit({
|
|
1876
|
+
type: "completion",
|
|
1877
|
+
action: decision.action,
|
|
1878
|
+
reason: decision.reason
|
|
1879
|
+
});
|
|
1880
|
+
if (decision.action === "stop" || decision.action === "await_input") {
|
|
1881
|
+
final = {
|
|
1882
|
+
...final,
|
|
1883
|
+
status: decision.action === "stop" ? "stopped" : "awaiting_input",
|
|
1884
|
+
reason: {
|
|
1885
|
+
code: decision.action,
|
|
1886
|
+
message: decision.reason
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
break;
|
|
1890
|
+
}
|
|
1891
|
+
if (decision.action === "revise") {
|
|
1892
|
+
if (revision >= (config.budgets?.maxRevisions ?? 0)) {
|
|
1893
|
+
final = {
|
|
1894
|
+
...final,
|
|
1895
|
+
status: "stopped",
|
|
1896
|
+
reason: {
|
|
1897
|
+
code: "revision_limit",
|
|
1898
|
+
message: "Completion revision limit reached"
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
revision++;
|
|
1904
|
+
current = {
|
|
1905
|
+
...request,
|
|
1906
|
+
input: decision.instruction
|
|
1907
|
+
};
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
const followUp = inbox.findIndex((item) => item.mode === "follow_up");
|
|
1912
|
+
if (followUp < 0) break;
|
|
1913
|
+
current = {
|
|
1914
|
+
...request,
|
|
1915
|
+
input: inbox.splice(followUp, 1)[0].input
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
const rejected = (await Promise.allSettled([...owned])).find((entry) => entry.status === "rejected");
|
|
1919
|
+
if (rejected?.status === "rejected") throw rejected.reason;
|
|
1920
|
+
ensureActive();
|
|
1921
|
+
lease.commit({
|
|
1922
|
+
history: canonical,
|
|
1923
|
+
conversations,
|
|
1924
|
+
state,
|
|
1925
|
+
replayable
|
|
1926
|
+
});
|
|
1927
|
+
committed = true;
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
final = {
|
|
1930
|
+
text: "",
|
|
1931
|
+
status: controller.signal.aborted ? "cancelled" : error instanceof HarnessBudgetError ? "stopped" : "failed",
|
|
1932
|
+
reason: {
|
|
1933
|
+
code: controller.signal.aborted ? "cancelled" : error instanceof HarnessBudgetError ? error.code : error?.code ?? "execution_failed",
|
|
1934
|
+
message: controller.signal.aborted ? "Cancellation acknowledged; already started external effects are not rolled back" : error instanceof Error ? error.message : "Execution failed"
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
} finally {
|
|
1938
|
+
acceptingOperations = false;
|
|
1939
|
+
await Promise.allSettled([...streams].map((stream) => stream.return(void 0)));
|
|
1940
|
+
await Promise.allSettled([...owned]);
|
|
1941
|
+
if (lease && !committed) try {
|
|
1942
|
+
lease.commit({
|
|
1943
|
+
history: canonical,
|
|
1944
|
+
conversations,
|
|
1945
|
+
state,
|
|
1946
|
+
replayable: false
|
|
1947
|
+
});
|
|
1948
|
+
} catch {
|
|
1949
|
+
cleanupDiagnostics.push("partial_history_commit_failed");
|
|
1950
|
+
}
|
|
1951
|
+
for (const release of releases.reverse()) try {
|
|
1952
|
+
await release();
|
|
1953
|
+
} catch {
|
|
1954
|
+
cleanupDiagnostics.push("resource_release_failed");
|
|
1955
|
+
}
|
|
1956
|
+
try {
|
|
1957
|
+
const diagnostics = await bound?.dispose();
|
|
1958
|
+
cleanupDiagnostics.push(...(diagnostics ?? []).map((item) => item.code));
|
|
1959
|
+
} catch {
|
|
1960
|
+
cleanupDiagnostics.push("binding_release_failed");
|
|
1961
|
+
}
|
|
1962
|
+
try {
|
|
1963
|
+
lease?.release();
|
|
1964
|
+
} catch {
|
|
1965
|
+
cleanupDiagnostics.push("session_release_failed");
|
|
1966
|
+
}
|
|
1967
|
+
if (timer) clearTimeout(timer);
|
|
1968
|
+
callerSignal?.removeEventListener("abort", onAbort);
|
|
1969
|
+
}
|
|
1970
|
+
if (controller.signal.aborted) final = {
|
|
1971
|
+
...final,
|
|
1972
|
+
status: "cancelled",
|
|
1973
|
+
reason: {
|
|
1974
|
+
code: "cancelled",
|
|
1975
|
+
message: "Cancellation acknowledged"
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
const terminalResult = {
|
|
1979
|
+
status: final.status ?? "completed",
|
|
1980
|
+
text: final.text,
|
|
1981
|
+
...final.structured !== void 0 ? { structured: final.structured } : {},
|
|
1982
|
+
...final.reason ? { reason: final.reason } : {},
|
|
1983
|
+
...final.artifacts ? { artifacts: final.artifacts } : {},
|
|
1984
|
+
usage: modelCalls ? usage : final.usage ?? usage,
|
|
1985
|
+
runId,
|
|
1986
|
+
sessionId: request.sessionId,
|
|
1987
|
+
finalCursor: events.cursor + 1
|
|
1988
|
+
};
|
|
1989
|
+
if (cleanupDiagnostics.length) terminalResult.cleanupDiagnostics = cleanupDiagnostics;
|
|
1990
|
+
if (Buffer.byteLength(JSON.stringify(terminalResult)) > 65536) {
|
|
1991
|
+
const id = (0, node_crypto.randomUUID)();
|
|
1992
|
+
this.artifacts.set(id, {
|
|
1993
|
+
owner: harnessSessionKey(request.identity, request.sessionId),
|
|
1994
|
+
value: structuredClone(terminalResult)
|
|
1995
|
+
});
|
|
1996
|
+
terminalResult.text = terminalResult.text.slice(0, 4096);
|
|
1997
|
+
delete terminalResult.structured;
|
|
1998
|
+
terminalResult.artifacts = [{
|
|
1999
|
+
id,
|
|
2000
|
+
mimeType: "application/json"
|
|
2001
|
+
}];
|
|
2002
|
+
if (terminalResult.reason) terminalResult.reason = {
|
|
2003
|
+
code: terminalResult.reason.code.slice(0, 128),
|
|
2004
|
+
message: terminalResult.reason.message.slice(0, 2048)
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
terminal = true;
|
|
2008
|
+
events.append({
|
|
2009
|
+
type: "run.terminal",
|
|
2010
|
+
result: terminalResult
|
|
2011
|
+
});
|
|
2012
|
+
config.telemetry?.emit("run.complete", {
|
|
2013
|
+
runId,
|
|
2014
|
+
output: {
|
|
2015
|
+
text: "",
|
|
2016
|
+
toolCalls: [],
|
|
2017
|
+
usage: terminalResult.usage,
|
|
2018
|
+
runId,
|
|
2019
|
+
agentName: config.driver.id,
|
|
2020
|
+
sessionId: request.sessionId,
|
|
2021
|
+
durationMs: Date.now() - startedAt,
|
|
2022
|
+
status: terminalResult.status === "failed" ? "error" : terminalResult.status === "awaiting_input" ? "stopped" : terminalResult.status
|
|
2023
|
+
}
|
|
2024
|
+
});
|
|
2025
|
+
return structuredClone(terminalResult);
|
|
2026
|
+
});
|
|
2027
|
+
return {
|
|
2028
|
+
runId,
|
|
2029
|
+
events: (options) => events.events(options),
|
|
2030
|
+
result: () => result.then((value) => structuredClone(value)),
|
|
2031
|
+
cancel,
|
|
2032
|
+
send: async (input, options) => {
|
|
2033
|
+
if (terminal || controller.signal.aborted) throw new Error("Run is no longer active");
|
|
2034
|
+
if (!config.driver.capabilities.controls.includes(options.mode)) throw new HarnessUnsupportedError(options.mode);
|
|
2035
|
+
if (inbox.length >= 32) throw new Error("Run input queue is full");
|
|
2036
|
+
inbox.push({
|
|
2037
|
+
input: structuredClone(input),
|
|
2038
|
+
mode: options.mode
|
|
2039
|
+
});
|
|
2040
|
+
events.append({
|
|
2041
|
+
type: "control",
|
|
2042
|
+
operation: options.mode
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
//#endregion
|
|
2049
|
+
Object.defineProperty(exports, "HarnessBudgetError", {
|
|
2050
|
+
enumerable: true,
|
|
2051
|
+
get: function() {
|
|
2052
|
+
return HarnessBudgetError;
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
Object.defineProperty(exports, "HarnessEventGapError", {
|
|
2056
|
+
enumerable: true,
|
|
2057
|
+
get: function() {
|
|
2058
|
+
return HarnessEventGapError;
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
Object.defineProperty(exports, "HarnessResourcePool", {
|
|
2062
|
+
enumerable: true,
|
|
2063
|
+
get: function() {
|
|
2064
|
+
return HarnessResourcePool;
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
Object.defineProperty(exports, "HarnessRuntime", {
|
|
2068
|
+
enumerable: true,
|
|
2069
|
+
get: function() {
|
|
2070
|
+
return HarnessRuntime;
|
|
2071
|
+
}
|
|
2072
|
+
});
|
|
2073
|
+
Object.defineProperty(exports, "HarnessSessionConflict", {
|
|
2074
|
+
enumerable: true,
|
|
2075
|
+
get: function() {
|
|
2076
|
+
return HarnessSessionConflict;
|
|
2077
|
+
}
|
|
2078
|
+
});
|
|
2079
|
+
Object.defineProperty(exports, "HarnessUnsupportedError", {
|
|
2080
|
+
enumerable: true,
|
|
2081
|
+
get: function() {
|
|
2082
|
+
return HarnessUnsupportedError;
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
Object.defineProperty(exports, "InMemoryHarnessEventStore", {
|
|
2086
|
+
enumerable: true,
|
|
2087
|
+
get: function() {
|
|
2088
|
+
return InMemoryHarnessEventStore;
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
Object.defineProperty(exports, "InMemoryHarnessSessionStore", {
|
|
2092
|
+
enumerable: true,
|
|
2093
|
+
get: function() {
|
|
2094
|
+
return InMemoryHarnessSessionStore;
|
|
2095
|
+
}
|
|
2096
|
+
});
|
|
2097
|
+
Object.defineProperty(exports, "createHarnessDefinition", {
|
|
2098
|
+
enumerable: true,
|
|
2099
|
+
get: function() {
|
|
2100
|
+
return createHarnessDefinition;
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
Object.defineProperty(exports, "describeHarnessDefinition", {
|
|
2104
|
+
enumerable: true,
|
|
2105
|
+
get: function() {
|
|
2106
|
+
return describeHarnessDefinition;
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
Object.defineProperty(exports, "exportHarnessManifest", {
|
|
2110
|
+
enumerable: true,
|
|
2111
|
+
get: function() {
|
|
2112
|
+
return exportHarnessManifest;
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
Object.defineProperty(exports, "extendHarnessDefinition", {
|
|
2116
|
+
enumerable: true,
|
|
2117
|
+
get: function() {
|
|
2118
|
+
return extendHarnessDefinition;
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
Object.defineProperty(exports, "hashHarnessManifest", {
|
|
2122
|
+
enumerable: true,
|
|
2123
|
+
get: function() {
|
|
2124
|
+
return hashHarnessManifest;
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
Object.defineProperty(exports, "loadHarnessManifest", {
|
|
2128
|
+
enumerable: true,
|
|
2129
|
+
get: function() {
|
|
2130
|
+
return loadHarnessManifest;
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
Object.defineProperty(exports, "resolveHarnessRuntime", {
|
|
2134
|
+
enumerable: true,
|
|
2135
|
+
get: function() {
|
|
2136
|
+
return resolveHarnessRuntime;
|
|
2137
|
+
}
|
|
2138
|
+
});
|