@deepstrike/sdk 0.2.50 → 0.2.51
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 +83 -60
- package/dist/index.d.ts +5 -7
- package/dist/index.js +3 -3
- package/dist/kernel.d.ts +61 -31
- package/dist/runtime/canonical-kernel-step.d.ts +143 -0
- package/dist/runtime/canonical-kernel-step.js +1444 -0
- package/dist/runtime/execution-plane.d.ts +0 -3
- package/dist/runtime/execution-plane.js +0 -24
- package/dist/runtime/facade.js +3 -0
- package/dist/runtime/kernel-event-log.js +7 -13
- package/dist/runtime/kernel-journal.d.ts +264 -0
- package/dist/runtime/kernel-journal.js +741 -0
- package/dist/runtime/kernel-primitives-dashboard.d.ts +0 -2
- package/dist/runtime/kernel-primitives-dashboard.js +1 -8
- package/dist/runtime/kernel-step.d.ts +29 -109
- package/dist/runtime/kernel-step.js +47 -317
- package/dist/runtime/os-snapshot.d.ts +2 -2
- package/dist/runtime/os-snapshot.js +2 -6
- package/dist/runtime/payload-store.d.ts +16 -0
- package/dist/runtime/payload-store.js +80 -0
- package/dist/runtime/runner.d.ts +31 -114
- package/dist/runtime/runner.js +689 -774
- package/dist/runtime/session-log.d.ts +34 -32
- package/dist/runtime/session-log.js +21 -131
- package/dist/runtime/session-repair.d.ts +2 -36
- package/dist/runtime/session-repair.js +2 -47
- package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
- package/dist/runtime/sub-agent-orchestrator.js +42 -40
- package/dist/types/agent.d.ts +22 -19
- package/dist/types/agent.js +26 -42
- package/dist/workflow/public.d.ts +1 -1
- package/dist/workflow/public.js +1 -1
- package/package.json +2 -2
- package/dist/runtime/kernel-rebuild.d.ts +0 -13
- package/dist/runtime/kernel-rebuild.js +0 -75
- package/dist/runtime/kernel-transaction-log.d.ts +0 -61
- package/dist/runtime/kernel-transaction-log.js +0 -149
- package/dist/runtime/large-result-spool.d.ts +0 -93
- package/dist/runtime/large-result-spool.js +0 -214
|
@@ -0,0 +1,1444 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { getKernel } from "../kernel.js";
|
|
3
|
+
import { JournalCasConflictError, MAX_CHAIN_POSITION as JOURNAL_MAX_CHAIN_POSITION, } from "./kernel-journal.js";
|
|
4
|
+
import { encodeCanonicalContentParts, kernelMessageToSdk, renderedContextToSdk, } from "./kernel-step.js";
|
|
5
|
+
export const MAX_CHAIN_POSITION = JOURNAL_MAX_CHAIN_POSITION;
|
|
6
|
+
function asObject(value) {
|
|
7
|
+
return value && typeof value === "object" ? value : {};
|
|
8
|
+
}
|
|
9
|
+
function totalUsageTokens(terminal) {
|
|
10
|
+
const usage = asObject(terminal.usage);
|
|
11
|
+
const input = Number(usage.input_tokens ?? 0);
|
|
12
|
+
const output = Number(usage.output_tokens ?? 0);
|
|
13
|
+
return Number.isSafeInteger(input + output) ? input + output : 0;
|
|
14
|
+
}
|
|
15
|
+
export function canonicalUnsupportedEffectResolution(effectId, effectKind) {
|
|
16
|
+
return {
|
|
17
|
+
kind: "resolve_effect",
|
|
18
|
+
effect_id: effectId,
|
|
19
|
+
outcome: {
|
|
20
|
+
status: "failed",
|
|
21
|
+
failure: {
|
|
22
|
+
kind: "protocol_error",
|
|
23
|
+
message: `unknown canonical effect kind: ${effectKind}`,
|
|
24
|
+
retryable: false,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** The only ABI-v3 planned-step → Node host-action projection. */
|
|
30
|
+
export function canonicalActionFromPlannedStep(plannedStep) {
|
|
31
|
+
if (plannedStep.disposition.kind === "terminal") {
|
|
32
|
+
const terminal = plannedStep.disposition.terminal;
|
|
33
|
+
const usage = asObject(terminal.usage);
|
|
34
|
+
let termination = String(terminal.kind ?? "failed");
|
|
35
|
+
let turnsUsed = Number(usage.turns ?? 0);
|
|
36
|
+
if (terminal.kind === "agent") {
|
|
37
|
+
const result = asObject(terminal.result);
|
|
38
|
+
termination = String(result.termination ?? "completed");
|
|
39
|
+
turnsUsed = Number(result.turns_used ?? turnsUsed);
|
|
40
|
+
const finalMessage = asObject(result.final_message);
|
|
41
|
+
const pace = asObject(result.pace_decision);
|
|
42
|
+
if (Object.keys(finalMessage).length > 0) {
|
|
43
|
+
return {
|
|
44
|
+
kind: "done",
|
|
45
|
+
effectId: "",
|
|
46
|
+
result: {
|
|
47
|
+
termination,
|
|
48
|
+
turnsUsed,
|
|
49
|
+
totalTokensUsed: totalUsageTokens(terminal),
|
|
50
|
+
finalMessage: {
|
|
51
|
+
role: String(finalMessage.role ?? "assistant"),
|
|
52
|
+
content: String(finalMessage.content ?? ""),
|
|
53
|
+
toolCalls: (Array.isArray(finalMessage.tool_calls) ? finalMessage.tool_calls : [])
|
|
54
|
+
.map(value => {
|
|
55
|
+
const call = asObject(value);
|
|
56
|
+
return {
|
|
57
|
+
id: String(call.call_id ?? ""),
|
|
58
|
+
name: String(call.name ?? ""),
|
|
59
|
+
arguments: JSON.stringify(call.arguments ?? {}),
|
|
60
|
+
};
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
...(Object.keys(pace).length > 0
|
|
64
|
+
? {
|
|
65
|
+
paceDecision: {
|
|
66
|
+
action: String(pace.action ?? "stop"),
|
|
67
|
+
...(pace.delay_ms !== undefined ? { delayMs: Number(pace.delay_ms) } : {}),
|
|
68
|
+
reason: String(pace.reason ?? ""),
|
|
69
|
+
...(pace.coerced_from ? { coercedFrom: String(pace.coerced_from) } : {}),
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
: {}),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (terminal.kind === "workflow") {
|
|
78
|
+
const outcome = asObject(terminal.outcome);
|
|
79
|
+
termination = String(outcome.status ?? "completed");
|
|
80
|
+
}
|
|
81
|
+
else if (terminal.kind === "cancelled") {
|
|
82
|
+
termination = String(terminal.reason ?? "cancelled");
|
|
83
|
+
}
|
|
84
|
+
else if (terminal.kind === "failed") {
|
|
85
|
+
const failure = asObject(terminal.failure);
|
|
86
|
+
termination = failure.code === "provider_recovery_exhausted"
|
|
87
|
+
? "context_overflow"
|
|
88
|
+
: "error";
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
kind: "done",
|
|
92
|
+
effectId: "",
|
|
93
|
+
result: {
|
|
94
|
+
termination,
|
|
95
|
+
turnsUsed,
|
|
96
|
+
totalTokensUsed: totalUsageTokens(terminal),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const published = plannedStep.disposition.effects ?? [];
|
|
101
|
+
if (published.length === 0)
|
|
102
|
+
return null;
|
|
103
|
+
if (published.length !== 1) {
|
|
104
|
+
throw new Error(`Node runner expects one canonical effect at a time, received ${published.length}`);
|
|
105
|
+
}
|
|
106
|
+
const envelope = asObject(published[0]);
|
|
107
|
+
const effectId = String(envelope.effect_id ?? "");
|
|
108
|
+
const effect = asObject(envelope.effect);
|
|
109
|
+
if (!effectId)
|
|
110
|
+
throw new Error("canonical effect is missing effect_id");
|
|
111
|
+
switch (effect.kind) {
|
|
112
|
+
case "call_provider":
|
|
113
|
+
return {
|
|
114
|
+
kind: "call_provider",
|
|
115
|
+
effectId,
|
|
116
|
+
context: renderedContextToSdk(asObject(effect.context)),
|
|
117
|
+
tools: (Array.isArray(effect.tools) ? effect.tools : []).map(raw => {
|
|
118
|
+
const tool = asObject(raw);
|
|
119
|
+
return {
|
|
120
|
+
name: String(tool.name ?? ""),
|
|
121
|
+
description: String(tool.description ?? ""),
|
|
122
|
+
parameters: JSON.stringify(tool.parameters ?? {}),
|
|
123
|
+
};
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
case "execute_tools":
|
|
127
|
+
return {
|
|
128
|
+
kind: "execute_tool",
|
|
129
|
+
effectId,
|
|
130
|
+
calls: (Array.isArray(effect.calls) ? effect.calls : []).map(raw => {
|
|
131
|
+
const call = asObject(raw);
|
|
132
|
+
return {
|
|
133
|
+
id: String(call.call_id ?? ""),
|
|
134
|
+
name: String(call.name ?? ""),
|
|
135
|
+
arguments: JSON.stringify(call.arguments ?? {}),
|
|
136
|
+
};
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
case "request_approval":
|
|
140
|
+
return {
|
|
141
|
+
kind: "request_approval",
|
|
142
|
+
effectId,
|
|
143
|
+
requests: (Array.isArray(effect.requests) ? effect.requests : []).map(raw => {
|
|
144
|
+
const request = asObject(raw);
|
|
145
|
+
return {
|
|
146
|
+
callId: String(request.call_id ?? ""),
|
|
147
|
+
tool: String(request.tool_name ?? ""),
|
|
148
|
+
arguments: JSON.stringify(request.arguments ?? {}),
|
|
149
|
+
reason: String(request.reason ?? ""),
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
152
|
+
};
|
|
153
|
+
case "spawn_tasks":
|
|
154
|
+
return {
|
|
155
|
+
kind: "spawn_workflow",
|
|
156
|
+
effectId,
|
|
157
|
+
nodes: (Array.isArray(effect.tasks) ? effect.tasks : []).map(raw => {
|
|
158
|
+
const task = asObject(raw);
|
|
159
|
+
const spec = asObject(task.spec);
|
|
160
|
+
return {
|
|
161
|
+
agent_id: String(task.task_id ?? ""),
|
|
162
|
+
task_id: String(task.task_id ?? ""),
|
|
163
|
+
attempt_id: String(task.attempt_id ?? ""),
|
|
164
|
+
launch_token: String(task.launch_token ?? ""),
|
|
165
|
+
node_id: String(task.node_id ?? ""),
|
|
166
|
+
goal: String(spec.goal ?? ""),
|
|
167
|
+
role: String(spec.role ?? "custom"),
|
|
168
|
+
isolation: String(spec.isolation ?? "shared"),
|
|
169
|
+
context_inheritance: String(spec.context_inheritance ?? "none"),
|
|
170
|
+
...(spec.metadata && typeof spec.metadata === "object"
|
|
171
|
+
? asObject(spec.metadata)
|
|
172
|
+
: {}),
|
|
173
|
+
};
|
|
174
|
+
}),
|
|
175
|
+
...(effect.budget ? { budget: asObject(effect.budget) } : {}),
|
|
176
|
+
};
|
|
177
|
+
case "preempt_tasks": {
|
|
178
|
+
const attempts = (Array.isArray(effect.attempts) ? effect.attempts : []).map(raw => {
|
|
179
|
+
const attempt = asObject(raw);
|
|
180
|
+
return {
|
|
181
|
+
task_id: String(attempt.task_id ?? ""),
|
|
182
|
+
attempt_id: String(attempt.attempt_id ?? ""),
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
kind: "preempt_sub_agents",
|
|
187
|
+
effectId,
|
|
188
|
+
attempts,
|
|
189
|
+
agentIds: attempts.map(attempt => attempt.task_id),
|
|
190
|
+
reason: String(effect.reason ?? ""),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
case "persist_memory":
|
|
194
|
+
return {
|
|
195
|
+
kind: "persist_memory",
|
|
196
|
+
effectId,
|
|
197
|
+
memory: asObject(effect.memory),
|
|
198
|
+
};
|
|
199
|
+
case "query_memory":
|
|
200
|
+
return {
|
|
201
|
+
kind: "query_memory",
|
|
202
|
+
effectId,
|
|
203
|
+
query: asObject(effect.query),
|
|
204
|
+
requestedK: Number(effect.requested_k ?? 0),
|
|
205
|
+
};
|
|
206
|
+
case "archive_page_out": {
|
|
207
|
+
const payload = asObject(effect.payload);
|
|
208
|
+
let archived = [];
|
|
209
|
+
try {
|
|
210
|
+
const decoded = JSON.parse(String(payload.content ?? ""));
|
|
211
|
+
if (Array.isArray(decoded)) {
|
|
212
|
+
archived = decoded.map(value => kernelMessageToSdk(asObject(value)));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Persistence still uses the opaque body and digest. Only optional presentation-side
|
|
217
|
+
// summarization is skipped if the archived message batch cannot be decoded.
|
|
218
|
+
}
|
|
219
|
+
const compressed = (plannedStep.observations ?? [])
|
|
220
|
+
.find(observation => observation.kind === "compressed");
|
|
221
|
+
const pressureAction = compressed ? String(compressed.action ?? "") : "";
|
|
222
|
+
return {
|
|
223
|
+
kind: "archive_page_out",
|
|
224
|
+
effectId,
|
|
225
|
+
handleId: String(effect.handle_id ?? ""),
|
|
226
|
+
payload,
|
|
227
|
+
archived,
|
|
228
|
+
...(pressureAction ? { action: pressureAction } : {}),
|
|
229
|
+
...(compressed?.summary ? { summary: String(compressed.summary) } : {}),
|
|
230
|
+
...(pressureAction
|
|
231
|
+
? {
|
|
232
|
+
tier: ["context_collapse", "auto_compact"].includes(pressureAction)
|
|
233
|
+
? "semantic"
|
|
234
|
+
: "durable",
|
|
235
|
+
}
|
|
236
|
+
: {}),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
case "load_payload":
|
|
240
|
+
return {
|
|
241
|
+
kind: "load_payload",
|
|
242
|
+
effectId,
|
|
243
|
+
handleId: String(effect.handle_id ?? ""),
|
|
244
|
+
payloadRef: String(effect.payload_ref ?? ""),
|
|
245
|
+
};
|
|
246
|
+
case "evaluate_milestone": {
|
|
247
|
+
const request = asObject(effect.request);
|
|
248
|
+
return {
|
|
249
|
+
kind: "evaluate_milestone",
|
|
250
|
+
effectId,
|
|
251
|
+
phaseId: String(request.phase_id ?? ""),
|
|
252
|
+
criteria: [],
|
|
253
|
+
requiredEvidence: [],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
return {
|
|
258
|
+
kind: "unsupported_effect",
|
|
259
|
+
effectId,
|
|
260
|
+
effectKind: String(effect.kind),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
export class CanonicalKernelRejectedError extends Error {
|
|
265
|
+
fault;
|
|
266
|
+
constructor(faultJson) {
|
|
267
|
+
let fault;
|
|
268
|
+
try {
|
|
269
|
+
fault = JSON.parse(faultJson);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
fault = { code: "invalid_fault", message: faultJson };
|
|
273
|
+
}
|
|
274
|
+
super(`${String(fault.code ?? "kernel_rejected")}: ${String(fault.message ?? "canonical input rejected")}`);
|
|
275
|
+
this.name = "CanonicalKernelRejectedError";
|
|
276
|
+
this.fault = fault;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A record is already authoritative once the journal append returns. A later native commit failure
|
|
281
|
+
* is therefore a rebuild boundary, never an abort boundary.
|
|
282
|
+
*/
|
|
283
|
+
export class CanonicalKernelRebuildRequiredError extends Error {
|
|
284
|
+
constructor(message, options) {
|
|
285
|
+
super(message, options);
|
|
286
|
+
this.name = "CanonicalKernelRebuildRequiredError";
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function chainPosition(value, label) {
|
|
290
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) {
|
|
291
|
+
throw new RangeError(`${label} must be a canonical decimal integer`);
|
|
292
|
+
}
|
|
293
|
+
const parsed = Number(value);
|
|
294
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed >= MAX_CHAIN_POSITION) {
|
|
295
|
+
throw new RangeError(`${label} must be below ${MAX_CHAIN_POSITION}`);
|
|
296
|
+
}
|
|
297
|
+
return parsed;
|
|
298
|
+
}
|
|
299
|
+
function parsePlannedStep(json) {
|
|
300
|
+
if (!json)
|
|
301
|
+
throw new Error("canonical replay is missing plannedStepJson");
|
|
302
|
+
return JSON.parse(json);
|
|
303
|
+
}
|
|
304
|
+
function parseAdvice(json) {
|
|
305
|
+
return json ? JSON.parse(json) : undefined;
|
|
306
|
+
}
|
|
307
|
+
function isCasConflict(error) {
|
|
308
|
+
return error instanceof JournalCasConflictError ||
|
|
309
|
+
error?.name === "JournalCasConflictError";
|
|
310
|
+
}
|
|
311
|
+
function isCheckpointRequired(preparation) {
|
|
312
|
+
if (preparation.status !== "rejected")
|
|
313
|
+
return false;
|
|
314
|
+
try {
|
|
315
|
+
const fault = JSON.parse(preparation.faultJson);
|
|
316
|
+
return fault.code === "checkpoint_required";
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Node's canonical durable staged-transition host.
|
|
324
|
+
*
|
|
325
|
+
* It owns no scheduler truth. Core prepares opaque record bytes; `KernelJournal` makes those bytes
|
|
326
|
+
* authoritative; only then may core commit and expose the planned effects/terminal to the runner.
|
|
327
|
+
*/
|
|
328
|
+
export class CanonicalKernelHost {
|
|
329
|
+
kernel;
|
|
330
|
+
journal;
|
|
331
|
+
operationId;
|
|
332
|
+
constructor(kernel, journal, operationId) {
|
|
333
|
+
this.kernel = kernel;
|
|
334
|
+
this.journal = journal;
|
|
335
|
+
this.operationId = operationId;
|
|
336
|
+
if (!operationId)
|
|
337
|
+
throw new TypeError("canonical kernel operationId must not be empty");
|
|
338
|
+
}
|
|
339
|
+
async transition(input, options = {}) {
|
|
340
|
+
const inputJson = JSON.stringify({
|
|
341
|
+
abi_version: getKernel().kernelAbiVersion(),
|
|
342
|
+
operation_id: this.operationId,
|
|
343
|
+
input_id: options.inputId ?? `node-input-${randomUUID()}`,
|
|
344
|
+
observed_at_ms: options.observedAtMs ?? String(Date.now()),
|
|
345
|
+
input,
|
|
346
|
+
});
|
|
347
|
+
await this.journal.stageOutboundEnvelope(this.operationId, inputJson);
|
|
348
|
+
try {
|
|
349
|
+
const transition = await this.transitionEnvelope(inputJson, 1, 1);
|
|
350
|
+
await this.journal.clearOutboundEnvelope(this.operationId);
|
|
351
|
+
return transition;
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
// Append-acked records own the input; rejected inputs will not retry this envelope.
|
|
355
|
+
// Append-before failures leave the staged bytes so wake can drain byte-identical retries.
|
|
356
|
+
if (error instanceof CanonicalKernelRebuildRequiredError
|
|
357
|
+
|| error instanceof CanonicalKernelRejectedError) {
|
|
358
|
+
await this.journal.clearOutboundEnvelope(this.operationId);
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Restore the latest installed checkpoint and its authoritative record tail in place. */
|
|
364
|
+
async restore() {
|
|
365
|
+
const checkpoint = await this.journal.latestCheckpoint(this.operationId);
|
|
366
|
+
const records = await this.journal.recordsAfter(this.operationId, checkpoint?.covered_head);
|
|
367
|
+
return this.kernel.restore(checkpoint ? Buffer.from(checkpoint.checkpoint_bytes) : undefined, records.map(record => Buffer.from(record.record_bytes)));
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Replay a crash-window outbound envelope with identical bytes (adjudication 5e.3).
|
|
371
|
+
* No-op when nothing is staged. Clears the stage after commit/replay/reject.
|
|
372
|
+
*/
|
|
373
|
+
async drainOutboundEnvelope() {
|
|
374
|
+
const pending = await this.journal.readOutboundEnvelope(this.operationId);
|
|
375
|
+
if (!pending)
|
|
376
|
+
return undefined;
|
|
377
|
+
try {
|
|
378
|
+
const transition = await this.transitionEnvelope(pending, 1, 1);
|
|
379
|
+
await this.journal.clearOutboundEnvelope(this.operationId);
|
|
380
|
+
return transition;
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (error instanceof CanonicalKernelRebuildRequiredError
|
|
384
|
+
|| error instanceof CanonicalKernelRejectedError) {
|
|
385
|
+
await this.journal.clearOutboundEnvelope(this.operationId);
|
|
386
|
+
}
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Execute the full §12.3 install/ack/reclaim boundary. */
|
|
391
|
+
async checkpoint() {
|
|
392
|
+
const candidate = this.kernel.checkpointCandidate();
|
|
393
|
+
const throughStepSeq = chainPosition(candidate.throughStepSeq, "checkpoint throughStepSeq");
|
|
394
|
+
const previous = await this.journal.latestCheckpoint(this.operationId);
|
|
395
|
+
let installed;
|
|
396
|
+
try {
|
|
397
|
+
installed = await this.journal.compareAndInstallCheckpoint(this.operationId, previous?.checkpoint_id, candidate.coveredHead, {
|
|
398
|
+
checkpoint_id: candidate.ackToken,
|
|
399
|
+
through_step_seq: throughStepSeq,
|
|
400
|
+
state_digest: candidate.stateDigest,
|
|
401
|
+
checkpoint_bytes: candidate.checkpointBytes,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
if (!isCasConflict(error))
|
|
406
|
+
throw error;
|
|
407
|
+
const winner = await this.journal.latestCheckpoint(this.operationId);
|
|
408
|
+
if (!winner || winner.checkpoint_id !== candidate.ackToken)
|
|
409
|
+
throw error;
|
|
410
|
+
installed = winner;
|
|
411
|
+
}
|
|
412
|
+
await this.journal.ackCheckpoint(this.operationId, installed.checkpoint_id);
|
|
413
|
+
this.kernel.ackCheckpoint(candidate.throughStepSeq, candidate.coveredHead);
|
|
414
|
+
await this.journal.pruneAckedPrefix(this.operationId);
|
|
415
|
+
return { ...installed, acknowledged: true };
|
|
416
|
+
}
|
|
417
|
+
async transitionEnvelope(inputJson, casRetriesLeft, checkpointRetriesLeft) {
|
|
418
|
+
const preparation = this.kernel.prepare(inputJson);
|
|
419
|
+
if (preparation.status === "rejected") {
|
|
420
|
+
if (checkpointRetriesLeft > 0 && isCheckpointRequired(preparation)) {
|
|
421
|
+
await this.checkpoint();
|
|
422
|
+
return this.transitionEnvelope(inputJson, casRetriesLeft, checkpointRetriesLeft - 1);
|
|
423
|
+
}
|
|
424
|
+
throw new CanonicalKernelRejectedError(preparation.faultJson);
|
|
425
|
+
}
|
|
426
|
+
if (preparation.status === "replayed") {
|
|
427
|
+
return {
|
|
428
|
+
inputJson,
|
|
429
|
+
stepSeq: chainPosition(preparation.stepSeq, "replayed stepSeq"),
|
|
430
|
+
recordDigest: preparation.recordDigest,
|
|
431
|
+
plannedStep: parsePlannedStep(preparation.plannedStepJson),
|
|
432
|
+
replayed: true,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
const stepSeq = chainPosition(preparation.stepSeq, "prepared stepSeq");
|
|
436
|
+
let appended = false;
|
|
437
|
+
try {
|
|
438
|
+
const receipt = await this.journal.compareAndAppend(this.operationId, preparation.expectedHead, {
|
|
439
|
+
step_seq: stepSeq,
|
|
440
|
+
record_digest: preparation.recordDigest,
|
|
441
|
+
record_bytes: preparation.recordBytes,
|
|
442
|
+
});
|
|
443
|
+
appended = true;
|
|
444
|
+
const committed = this.kernel.commit(preparation.prepareToken, receipt.record_digest);
|
|
445
|
+
if (committed.recordDigest !== preparation.recordDigest ||
|
|
446
|
+
chainPosition(committed.stepSeq, "committed stepSeq") !== stepSeq) {
|
|
447
|
+
throw new Error("canonical commit receipt disagrees with the durably appended record");
|
|
448
|
+
}
|
|
449
|
+
const checkpointAdvice = parseAdvice(committed.checkpointAdviceJson);
|
|
450
|
+
const transition = {
|
|
451
|
+
inputJson,
|
|
452
|
+
stepSeq,
|
|
453
|
+
recordDigest: committed.recordDigest,
|
|
454
|
+
plannedStep: parsePlannedStep(committed.plannedStepJson),
|
|
455
|
+
...(checkpointAdvice ? { checkpointAdvice } : {}),
|
|
456
|
+
replayed: false,
|
|
457
|
+
};
|
|
458
|
+
if (checkpointAdvice)
|
|
459
|
+
await this.checkpoint();
|
|
460
|
+
return transition;
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
if (appended) {
|
|
464
|
+
try {
|
|
465
|
+
await this.restore();
|
|
466
|
+
}
|
|
467
|
+
catch (restoreError) {
|
|
468
|
+
throw new CanonicalKernelRebuildRequiredError("canonical record is durable, commit failed, and journal rebuild also failed", { cause: new AggregateError([error, restoreError]) });
|
|
469
|
+
}
|
|
470
|
+
throw new CanonicalKernelRebuildRequiredError("canonical record is durable but commit could not be published; runtime rebuilt from journal", { cause: error });
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
this.kernel.abort(preparation.prepareToken);
|
|
474
|
+
}
|
|
475
|
+
catch (abortError) {
|
|
476
|
+
throw new AggregateError([error, abortError], "canonical append failed and prepare could not be aborted");
|
|
477
|
+
}
|
|
478
|
+
if (casRetriesLeft > 0 && isCasConflict(error)) {
|
|
479
|
+
await this.restore();
|
|
480
|
+
return this.transitionEnvelope(inputJson, casRetriesLeft - 1, checkpointRetriesLeft);
|
|
481
|
+
}
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function canonicalProviderMessage(raw) {
|
|
487
|
+
const content = raw.content;
|
|
488
|
+
return {
|
|
489
|
+
role: String(raw.role ?? "assistant"),
|
|
490
|
+
content: typeof content === "string" ? content : JSON.stringify(content ?? ""),
|
|
491
|
+
...((Array.isArray(raw.tool_calls) && raw.tool_calls.length > 0)
|
|
492
|
+
? {
|
|
493
|
+
tool_calls: raw.tool_calls.map(value => {
|
|
494
|
+
const call = asObject(value);
|
|
495
|
+
return {
|
|
496
|
+
call_id: String(call.call_id ?? call.id ?? ""),
|
|
497
|
+
name: String(call.name ?? ""),
|
|
498
|
+
arguments: canonicalProviderToolArguments(String(call.name ?? ""), asObject(call.arguments)),
|
|
499
|
+
};
|
|
500
|
+
}),
|
|
501
|
+
}
|
|
502
|
+
: {}),
|
|
503
|
+
...(raw.token_count !== undefined ? { tokens: Number(raw.token_count) } : {}),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function canonicalProviderToolArguments(name, argumentsValue) {
|
|
507
|
+
if (name === "start_workflow") {
|
|
508
|
+
const wrapped = asObject(argumentsValue.spec);
|
|
509
|
+
return canonicalWorkflowSpec(Object.keys(wrapped).length > 0 ? wrapped : argumentsValue);
|
|
510
|
+
}
|
|
511
|
+
if (name === "submit_workflow_nodes") {
|
|
512
|
+
const spec = canonicalWorkflowSpec({
|
|
513
|
+
nodes: Array.isArray(argumentsValue.nodes) ? argumentsValue.nodes : [],
|
|
514
|
+
});
|
|
515
|
+
return { nodes: spec.nodes };
|
|
516
|
+
}
|
|
517
|
+
return argumentsValue;
|
|
518
|
+
}
|
|
519
|
+
function canonicalInitialMessage(raw) {
|
|
520
|
+
if (Array.isArray(raw.content)) {
|
|
521
|
+
return {
|
|
522
|
+
role: String(raw.role ?? "user"),
|
|
523
|
+
content: encodeCanonicalContentParts(raw.content),
|
|
524
|
+
...(raw.token_count !== undefined ? { tokens: Number(raw.token_count) } : {}),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
const message = canonicalProviderMessage(raw);
|
|
528
|
+
delete message.tool_calls;
|
|
529
|
+
return message;
|
|
530
|
+
}
|
|
531
|
+
function logicalRunSpec(raw, goal) {
|
|
532
|
+
if (!raw)
|
|
533
|
+
return undefined;
|
|
534
|
+
const filter = asObject(raw.capability_filter);
|
|
535
|
+
return {
|
|
536
|
+
goal: String(raw.goal ?? goal),
|
|
537
|
+
...(raw.role ? { role: raw.role } : {}),
|
|
538
|
+
...(raw.isolation ? { isolation: raw.isolation } : {}),
|
|
539
|
+
...(raw.context_inheritance ? { context_inheritance: raw.context_inheritance } : {}),
|
|
540
|
+
...(raw.verification_contract_id ? { verification_contract_id: raw.verification_contract_id } : {}),
|
|
541
|
+
...((Array.isArray(filter.allowed_kinds) && filter.allowed_kinds.length > 0) ||
|
|
542
|
+
(Array.isArray(filter.allowed_ids) && filter.allowed_ids.length > 0)
|
|
543
|
+
? {
|
|
544
|
+
capability_filter: {
|
|
545
|
+
...(Array.isArray(filter.allowed_kinds) && filter.allowed_kinds.length > 0
|
|
546
|
+
? { allowed_kinds: filter.allowed_kinds }
|
|
547
|
+
: {}),
|
|
548
|
+
...(Array.isArray(filter.allowed_ids) && filter.allowed_ids.length > 0
|
|
549
|
+
? { allowed_ids: filter.allowed_ids }
|
|
550
|
+
: {}),
|
|
551
|
+
},
|
|
552
|
+
}
|
|
553
|
+
: {}),
|
|
554
|
+
...(Object.prototype.hasOwnProperty.call(raw, "exposure_baseline")
|
|
555
|
+
? { exposure_baseline: raw.exposure_baseline }
|
|
556
|
+
: {}),
|
|
557
|
+
...(raw.loop_round && typeof raw.loop_round === "object"
|
|
558
|
+
? {
|
|
559
|
+
loop_round: {
|
|
560
|
+
...(asObject(raw.loop_round).max_rounds !== undefined
|
|
561
|
+
? { max_rounds: Number(asObject(raw.loop_round).max_rounds) }
|
|
562
|
+
: {}),
|
|
563
|
+
...(asObject(raw.loop_round).min_sleep_ms !== undefined
|
|
564
|
+
? { min_sleep_ms: String(asObject(raw.loop_round).min_sleep_ms) }
|
|
565
|
+
: {}),
|
|
566
|
+
...(asObject(raw.loop_round).max_sleep_ms !== undefined
|
|
567
|
+
? { max_sleep_ms: String(asObject(raw.loop_round).max_sleep_ms) }
|
|
568
|
+
: {}),
|
|
569
|
+
...(asObject(raw.loop_round).default_action !== undefined
|
|
570
|
+
? { default_action: asObject(raw.loop_round).default_action }
|
|
571
|
+
: {}),
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
: {}),
|
|
575
|
+
...(raw.metadata && typeof raw.metadata === "object" ? { metadata: raw.metadata } : {}),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function canonicalWorkflowSpec(raw) {
|
|
579
|
+
const nodes = Array.isArray(raw.nodes) ? raw.nodes.map(asObject) : [];
|
|
580
|
+
const nodeIds = nodes.map((_node, index) => `wf-node${index}`);
|
|
581
|
+
return {
|
|
582
|
+
nodes: nodes.map((node, index) => {
|
|
583
|
+
const unsupported = [];
|
|
584
|
+
if (node.kind !== undefined
|
|
585
|
+
|| node.reducer !== undefined
|
|
586
|
+
|| node.loop !== undefined
|
|
587
|
+
|| node.classify !== undefined
|
|
588
|
+
|| node.tournament !== undefined)
|
|
589
|
+
unsupported.push("kind");
|
|
590
|
+
if (node.trust !== undefined && node.trust !== "trusted")
|
|
591
|
+
unsupported.push("trust");
|
|
592
|
+
const depPolicy = node.dep_policy ?? node.depPolicy;
|
|
593
|
+
if (depPolicy !== undefined && depPolicy !== "all_success")
|
|
594
|
+
unsupported.push("dep_policy");
|
|
595
|
+
if (node.token_budget !== undefined || node.tokenBudget !== undefined)
|
|
596
|
+
unsupported.push("token_budget");
|
|
597
|
+
if (node.max_turns !== undefined || node.maxTurns !== undefined)
|
|
598
|
+
unsupported.push("max_turns");
|
|
599
|
+
if (node.max_wall_ms !== undefined || node.maxWallMs !== undefined)
|
|
600
|
+
unsupported.push("max_wall_ms");
|
|
601
|
+
const inheritance = node.context_inheritance ?? node.contextInheritance;
|
|
602
|
+
if (unsupported.length > 0) {
|
|
603
|
+
throw new CanonicalKernelRejectedError(JSON.stringify({
|
|
604
|
+
code: "unsupported_effect",
|
|
605
|
+
message: `workflow node ${index} uses fields absent from canonical WorkflowNode: ${unsupported.join(", ")}`,
|
|
606
|
+
}));
|
|
607
|
+
}
|
|
608
|
+
const taskValue = node.task;
|
|
609
|
+
const task = asObject(taskValue);
|
|
610
|
+
const goal = typeof taskValue === "string"
|
|
611
|
+
? taskValue
|
|
612
|
+
: String(task.goal ?? node.goal ?? "");
|
|
613
|
+
const rawDependsOn = Array.isArray(node.depends_on)
|
|
614
|
+
? node.depends_on
|
|
615
|
+
: Array.isArray(node.dependsOn) ? node.dependsOn : [];
|
|
616
|
+
const dependsOn = rawDependsOn.length > 0
|
|
617
|
+
? rawDependsOn.map(value => nodeIds[Number(value)] ?? String(value))
|
|
618
|
+
: [];
|
|
619
|
+
const modelHint = node.model_hint ?? node.modelHint;
|
|
620
|
+
const outputSchema = node.output_schema ?? node.outputSchema;
|
|
621
|
+
const runSpec = logicalRunSpec({
|
|
622
|
+
goal,
|
|
623
|
+
...(node.role ? { role: node.role } : {}),
|
|
624
|
+
...(node.isolation ? { isolation: node.isolation } : {}),
|
|
625
|
+
...(inheritance ? { context_inheritance: inheritance } : {}),
|
|
626
|
+
...((modelHint !== undefined || outputSchema !== undefined)
|
|
627
|
+
? {
|
|
628
|
+
metadata: {
|
|
629
|
+
...(modelHint !== undefined ? { model_hint: modelHint } : {}),
|
|
630
|
+
...(outputSchema !== undefined ? { output_schema: outputSchema } : {}),
|
|
631
|
+
},
|
|
632
|
+
}
|
|
633
|
+
: {}),
|
|
634
|
+
}, goal);
|
|
635
|
+
return {
|
|
636
|
+
node_id: nodeIds[index],
|
|
637
|
+
task: {
|
|
638
|
+
goal,
|
|
639
|
+
...(Array.isArray(task.criteria) && task.criteria.length > 0 ? { criteria: task.criteria } : {}),
|
|
640
|
+
...(task.lane ? { lane: task.lane } : {}),
|
|
641
|
+
},
|
|
642
|
+
...(dependsOn.length > 0 ? { depends_on: dependsOn } : {}),
|
|
643
|
+
...(runSpec ? { run_spec: runSpec } : {}),
|
|
644
|
+
};
|
|
645
|
+
}),
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function sha256(value) {
|
|
649
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
650
|
+
}
|
|
651
|
+
function providerStopReason(value) {
|
|
652
|
+
if (typeof value !== "string" || value.length === 0)
|
|
653
|
+
return undefined;
|
|
654
|
+
const normalized = value.toLowerCase();
|
|
655
|
+
if (["end_turn", "tool_use", "max_tokens", "stop_sequence", "content_filter"].includes(normalized)) {
|
|
656
|
+
return normalized;
|
|
657
|
+
}
|
|
658
|
+
return "other";
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Canonical operation runtime used by the Node host.
|
|
662
|
+
* Every durable transition below is one of the canonical ABI's five input classes; no legacy
|
|
663
|
+
* envelope or synthesized host transaction reaches core or storage.
|
|
664
|
+
*/
|
|
665
|
+
export class CanonicalRunnerRuntime {
|
|
666
|
+
options;
|
|
667
|
+
host;
|
|
668
|
+
config;
|
|
669
|
+
initialContext = { messages: [], knowledge: [], capabilities: [] };
|
|
670
|
+
configured = false;
|
|
671
|
+
started = false;
|
|
672
|
+
turns = 0;
|
|
673
|
+
lastAction = null;
|
|
674
|
+
newMessages = [];
|
|
675
|
+
hostObservations = [];
|
|
676
|
+
spawnedTasks = 0;
|
|
677
|
+
memoryBindingId;
|
|
678
|
+
payloadInlineThreshold = 50 * 1024;
|
|
679
|
+
payloadPreviewBytes = 2 * 1024;
|
|
680
|
+
constructor(kernel, journal, operationId, options) {
|
|
681
|
+
this.options = options;
|
|
682
|
+
this.host = new CanonicalKernelHost(kernel, journal, operationId);
|
|
683
|
+
this.memoryBindingId = options.memoryBindingId ?? "node-memory";
|
|
684
|
+
this.config = {
|
|
685
|
+
execution_policy: {
|
|
686
|
+
max_context_tokens: options.maxContextTokens,
|
|
687
|
+
...(options.maxTurns !== undefined ? { max_turns: options.maxTurns } : {}),
|
|
688
|
+
...(options.maxTotalTokens !== undefined ? { max_total_tokens: String(options.maxTotalTokens) } : {}),
|
|
689
|
+
...(options.maxWallMs !== undefined ? { max_wall_ms: String(options.maxWallMs) } : {}),
|
|
690
|
+
},
|
|
691
|
+
host_effect_support: {
|
|
692
|
+
supported: [
|
|
693
|
+
"call_provider",
|
|
694
|
+
"execute_tools",
|
|
695
|
+
"request_approval",
|
|
696
|
+
"spawn_tasks",
|
|
697
|
+
"preempt_tasks",
|
|
698
|
+
"persist_memory",
|
|
699
|
+
"query_memory",
|
|
700
|
+
"archive_page_out",
|
|
701
|
+
"load_payload",
|
|
702
|
+
"evaluate_milestone",
|
|
703
|
+
],
|
|
704
|
+
},
|
|
705
|
+
kernel_limits: {
|
|
706
|
+
max_json_depth: 64,
|
|
707
|
+
max_collection_entries: 65_536,
|
|
708
|
+
collection_limits: {
|
|
709
|
+
tool_catalog: 4_096,
|
|
710
|
+
skill_catalog: 4_096,
|
|
711
|
+
knowledge_entries: 65_536,
|
|
712
|
+
initial_messages: 65_536,
|
|
713
|
+
capability_grants: 65_536,
|
|
714
|
+
governance_rules: 65_536,
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
get operationId() {
|
|
720
|
+
return this.host.operationId;
|
|
721
|
+
}
|
|
722
|
+
get journal() {
|
|
723
|
+
return this.host.journal;
|
|
724
|
+
}
|
|
725
|
+
turn() {
|
|
726
|
+
return this.turns;
|
|
727
|
+
}
|
|
728
|
+
isTerminal() {
|
|
729
|
+
return ["completed", "cancelled", "failed"].includes(this.host.kernel.lifecycle());
|
|
730
|
+
}
|
|
731
|
+
recoveryContentBytes() {
|
|
732
|
+
return Math.max(1_024, this.options.maxContextTokens * 4);
|
|
733
|
+
}
|
|
734
|
+
preservedRefs() {
|
|
735
|
+
return [];
|
|
736
|
+
}
|
|
737
|
+
drainNewMessages() {
|
|
738
|
+
return this.newMessages.splice(0);
|
|
739
|
+
}
|
|
740
|
+
drainHostObservations() {
|
|
741
|
+
return this.hostObservations.splice(0);
|
|
742
|
+
}
|
|
743
|
+
terminal() {
|
|
744
|
+
const terminal = this.host.kernel.terminalJson();
|
|
745
|
+
return terminal ? JSON.parse(terminal) : undefined;
|
|
746
|
+
}
|
|
747
|
+
localSubagentsSpawned() {
|
|
748
|
+
return this.spawnedTasks;
|
|
749
|
+
}
|
|
750
|
+
async restore() {
|
|
751
|
+
await this.host.restore();
|
|
752
|
+
this.configured = this.host.kernel.lifecycle() !== "created";
|
|
753
|
+
this.started = !["created", "configured"].includes(this.host.kernel.lifecycle());
|
|
754
|
+
// A crash between stage and append-ack leaves a byte-identical envelope; drain it before
|
|
755
|
+
// the host effect loop so retries never remint observed_at_ms.
|
|
756
|
+
await this.host.drainOutboundEnvelope();
|
|
757
|
+
this.lastAction = this.currentAction();
|
|
758
|
+
}
|
|
759
|
+
resumeAction() {
|
|
760
|
+
this.lastAction = this.currentAction();
|
|
761
|
+
return this.lastAction;
|
|
762
|
+
}
|
|
763
|
+
async startAgent(taskValue, runSpecValue) {
|
|
764
|
+
await this.ensureConfigured();
|
|
765
|
+
const goal = String(taskValue.goal ?? "");
|
|
766
|
+
const action = await this.commit({
|
|
767
|
+
kind: "start_operation",
|
|
768
|
+
entry: {
|
|
769
|
+
kind: "agent",
|
|
770
|
+
task: {
|
|
771
|
+
goal,
|
|
772
|
+
...(Array.isArray(taskValue.criteria) && taskValue.criteria.length > 0
|
|
773
|
+
? { criteria: taskValue.criteria }
|
|
774
|
+
: {}),
|
|
775
|
+
},
|
|
776
|
+
...(runSpecValue ? { run_spec: logicalRunSpec(runSpecValue, goal) } : {}),
|
|
777
|
+
},
|
|
778
|
+
initial_context: this.initialContext,
|
|
779
|
+
});
|
|
780
|
+
this.started = true;
|
|
781
|
+
return action;
|
|
782
|
+
}
|
|
783
|
+
async startWorkflow(specValue) {
|
|
784
|
+
await this.ensureConfigured();
|
|
785
|
+
const action = await this.commit({
|
|
786
|
+
kind: "start_operation",
|
|
787
|
+
entry: {
|
|
788
|
+
kind: "workflow",
|
|
789
|
+
spec: canonicalWorkflowSpec(specValue),
|
|
790
|
+
},
|
|
791
|
+
initial_context: this.initialContext,
|
|
792
|
+
});
|
|
793
|
+
this.started = true;
|
|
794
|
+
return action;
|
|
795
|
+
}
|
|
796
|
+
async applyHostEvent(event) {
|
|
797
|
+
if (!this.started && this.applyBootstrapEvent(event))
|
|
798
|
+
return null;
|
|
799
|
+
let input;
|
|
800
|
+
switch (event.kind) {
|
|
801
|
+
case "provider_result": {
|
|
802
|
+
const message = canonicalProviderMessage(asObject(event.message));
|
|
803
|
+
this.newMessages.push({
|
|
804
|
+
role: message.role,
|
|
805
|
+
content: String(message.content ?? ""),
|
|
806
|
+
toolCalls: (Array.isArray(message.tool_calls) ? message.tool_calls : []).map(raw => {
|
|
807
|
+
const call = asObject(raw);
|
|
808
|
+
return {
|
|
809
|
+
id: String(call.call_id ?? ""),
|
|
810
|
+
name: String(call.name ?? ""),
|
|
811
|
+
arguments: JSON.stringify(call.arguments ?? {}),
|
|
812
|
+
};
|
|
813
|
+
}),
|
|
814
|
+
});
|
|
815
|
+
this.turns += 1;
|
|
816
|
+
input = {
|
|
817
|
+
kind: "resolve_effect",
|
|
818
|
+
effect_id: String(event.effect_id ?? ""),
|
|
819
|
+
outcome: {
|
|
820
|
+
status: "succeeded",
|
|
821
|
+
result: {
|
|
822
|
+
kind: "provider",
|
|
823
|
+
outcome: {
|
|
824
|
+
kind: "completed",
|
|
825
|
+
message,
|
|
826
|
+
...(event.observed_input_tokens !== undefined
|
|
827
|
+
? { observed_input_tokens: Number(event.observed_input_tokens) }
|
|
828
|
+
: {}),
|
|
829
|
+
...(event.observed_output_tokens !== undefined
|
|
830
|
+
? { observed_output_tokens: Number(event.observed_output_tokens) }
|
|
831
|
+
: {}),
|
|
832
|
+
...(providerStopReason(event.stop_reason)
|
|
833
|
+
? { stop_reason: providerStopReason(event.stop_reason) }
|
|
834
|
+
: {}),
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
};
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
case "provider_error": {
|
|
842
|
+
const message = String(event.message ?? "");
|
|
843
|
+
const contextOverflow = /context|token.*limit|too long/i.test(message);
|
|
844
|
+
input = contextOverflow
|
|
845
|
+
? {
|
|
846
|
+
kind: "resolve_effect",
|
|
847
|
+
effect_id: String(event.effect_id ?? ""),
|
|
848
|
+
outcome: {
|
|
849
|
+
status: "succeeded",
|
|
850
|
+
result: { kind: "provider", outcome: { kind: "context_overflow" } },
|
|
851
|
+
},
|
|
852
|
+
}
|
|
853
|
+
: this.failedEffect(event, "transport_exhausted", message, true);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
case "tool_results": {
|
|
857
|
+
const results = [];
|
|
858
|
+
for (const value of Array.isArray(event.results) ? event.results : []) {
|
|
859
|
+
const result = asObject(value);
|
|
860
|
+
const callId = String(result.call_id ?? "");
|
|
861
|
+
const output = String(result.output ?? "");
|
|
862
|
+
const isError = Boolean(result.is_error);
|
|
863
|
+
const disposition = result.is_fatal ? "fatal" : "recoverable";
|
|
864
|
+
const bytes = Buffer.byteLength(output, "utf8");
|
|
865
|
+
if (bytes > this.payloadInlineThreshold && this.options.persistPayload) {
|
|
866
|
+
const persisted = await this.options.persistPayload(callId, output, this.payloadPreviewBytes);
|
|
867
|
+
results.push({
|
|
868
|
+
kind: "external",
|
|
869
|
+
call_id: callId,
|
|
870
|
+
payload_ref: persisted.payloadRef,
|
|
871
|
+
digest: persisted.digest,
|
|
872
|
+
original_size: persisted.originalSize,
|
|
873
|
+
preview: persisted.preview,
|
|
874
|
+
...(isError ? { is_error: true } : {}),
|
|
875
|
+
disposition,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
results.push({
|
|
880
|
+
kind: "inline",
|
|
881
|
+
call_id: callId,
|
|
882
|
+
result: {
|
|
883
|
+
output,
|
|
884
|
+
...(isError ? { is_error: true } : {}),
|
|
885
|
+
disposition,
|
|
886
|
+
...(result.token_count !== null && result.token_count !== undefined
|
|
887
|
+
? { tokens: Number(result.token_count) }
|
|
888
|
+
: {}),
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
this.newMessages.push({ role: "tool", content: output, toolCalls: [] });
|
|
893
|
+
}
|
|
894
|
+
input = this.succeededEffect(event, { kind: "tools", results });
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case "approval_result":
|
|
898
|
+
input = this.succeededEffect(event, {
|
|
899
|
+
kind: "approval",
|
|
900
|
+
approved_call_ids: Array.isArray(event.approved_calls) ? event.approved_calls : [],
|
|
901
|
+
denied_call_ids: Array.isArray(event.denied_calls) ? event.denied_calls : [],
|
|
902
|
+
});
|
|
903
|
+
break;
|
|
904
|
+
case "workflow_spawn_result": {
|
|
905
|
+
const spawn = this.lastAction?.kind === "spawn_workflow" ? this.lastAction : undefined;
|
|
906
|
+
this.spawnedTasks += spawn?.nodes.length ?? 0;
|
|
907
|
+
input = this.succeededEffect(event, {
|
|
908
|
+
kind: "tasks_spawned",
|
|
909
|
+
attempts: (spawn?.nodes ?? []).map(node => ({
|
|
910
|
+
task_id: String(node.task_id ?? node.agent_id ?? ""),
|
|
911
|
+
attempt_id: String(node.attempt_id ?? ""),
|
|
912
|
+
outcome: { status: "started" },
|
|
913
|
+
})),
|
|
914
|
+
});
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
case "preempt_result": {
|
|
918
|
+
const preempt = this.lastAction?.kind === "preempt_sub_agents" ? this.lastAction : undefined;
|
|
919
|
+
input = this.succeededEffect(event, {
|
|
920
|
+
kind: "tasks_preempted",
|
|
921
|
+
attempts: (preempt?.attempts ?? []).map(attempt => ({
|
|
922
|
+
...attempt,
|
|
923
|
+
outcome: { status: "preempted" },
|
|
924
|
+
})),
|
|
925
|
+
});
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
case "sub_agent_completed": {
|
|
929
|
+
const raw = asObject(event.result);
|
|
930
|
+
const result = asObject(raw.result);
|
|
931
|
+
const submittedNodes = Array.isArray(raw.submitted_nodes)
|
|
932
|
+
? raw.submitted_nodes.map(asObject)
|
|
933
|
+
: [];
|
|
934
|
+
const taskId = String(raw.agent_id ?? "");
|
|
935
|
+
const pending = this.pendingEffects().find(effect => {
|
|
936
|
+
const kind = asObject(effect.effect);
|
|
937
|
+
return kind.kind === "spawn_tasks" &&
|
|
938
|
+
(Array.isArray(kind.tasks) ? kind.tasks : []).some(task => asObject(task).task_id === taskId);
|
|
939
|
+
});
|
|
940
|
+
const launch = pending
|
|
941
|
+
? (Array.isArray(asObject(pending.effect).tasks) ? asObject(pending.effect).tasks : [])
|
|
942
|
+
.map(asObject).find(task => task.task_id === taskId)
|
|
943
|
+
: undefined;
|
|
944
|
+
input = {
|
|
945
|
+
kind: "deliver_external_event",
|
|
946
|
+
event: {
|
|
947
|
+
kind: "child_completed",
|
|
948
|
+
task_id: taskId,
|
|
949
|
+
attempt_id: String(launch?.attempt_id ?? `${taskId}:attempt:1`),
|
|
950
|
+
result: {
|
|
951
|
+
status: result.termination === "completed" ? "completed" : "failed",
|
|
952
|
+
...(asObject(result.final_message).content
|
|
953
|
+
? { output: String(asObject(result.final_message).content) }
|
|
954
|
+
: {}),
|
|
955
|
+
...(!["completed", "max_turns", "token_budget"].includes(String(result.termination))
|
|
956
|
+
? { error: String(result.termination ?? "failed") }
|
|
957
|
+
: {}),
|
|
958
|
+
usage: {
|
|
959
|
+
input_tokens: "0",
|
|
960
|
+
output_tokens: String(result.total_tokens_used ?? 0),
|
|
961
|
+
turns: Number(result.turns_used ?? 0),
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
...(submittedNodes.length > 0
|
|
965
|
+
? {
|
|
966
|
+
parent_requests: [{
|
|
967
|
+
kind: "append_workflow_nodes",
|
|
968
|
+
nodes: asObject(canonicalWorkflowSpec({ nodes: submittedNodes })).nodes,
|
|
969
|
+
}],
|
|
970
|
+
}
|
|
971
|
+
: {}),
|
|
972
|
+
},
|
|
973
|
+
};
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
case "memory_persist_result":
|
|
977
|
+
input = event.error
|
|
978
|
+
? this.failedEffect(event, "storage_unavailable", String(event.error), true)
|
|
979
|
+
: this.succeededEffect(event, {
|
|
980
|
+
kind: "memory_persisted",
|
|
981
|
+
receipt: {
|
|
982
|
+
binding_id: this.memoryBindingId,
|
|
983
|
+
record_ref: String(event.record_ref ?? `memory:${randomUUID()}`),
|
|
984
|
+
digest: String(event.digest ?? sha256(String(event.record_ref ?? event.effect_id ?? ""))),
|
|
985
|
+
},
|
|
986
|
+
});
|
|
987
|
+
break;
|
|
988
|
+
case "memory_query_result":
|
|
989
|
+
input = event.error
|
|
990
|
+
? this.failedEffect(event, "storage_unavailable", String(event.error), true)
|
|
991
|
+
: this.succeededEffect(event, {
|
|
992
|
+
kind: "memory_queried",
|
|
993
|
+
recalls: (Array.isArray(event.hits) ? event.hits : []).map(value => {
|
|
994
|
+
const hit = asObject(value);
|
|
995
|
+
const record = asObject(hit.record);
|
|
996
|
+
return {
|
|
997
|
+
record_ref: String(record.record_id ?? `memory:${randomUUID()}`),
|
|
998
|
+
name: String(record.name ?? ""),
|
|
999
|
+
kind: String(record.kind ?? "reference"),
|
|
1000
|
+
content: String(record.content ?? ""),
|
|
1001
|
+
...(typeof hit.score === "number" ? { score: hit.score } : {}),
|
|
1002
|
+
};
|
|
1003
|
+
}),
|
|
1004
|
+
});
|
|
1005
|
+
break;
|
|
1006
|
+
case "page_out_archive_result":
|
|
1007
|
+
input = event.error
|
|
1008
|
+
? this.failedEffect(event, "storage_unavailable", String(event.error), true)
|
|
1009
|
+
: this.pageOutResolution(event);
|
|
1010
|
+
break;
|
|
1011
|
+
case "milestone_result": {
|
|
1012
|
+
const result = asObject(event.result);
|
|
1013
|
+
input = this.succeededEffect(event, {
|
|
1014
|
+
kind: "milestone_evaluated",
|
|
1015
|
+
result: {
|
|
1016
|
+
phase_id: String(result.phase_id ?? ""),
|
|
1017
|
+
passed: Boolean(result.passed),
|
|
1018
|
+
...(!result.passed && result.reason ? { notes: String(result.reason) } : {}),
|
|
1019
|
+
},
|
|
1020
|
+
});
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
case "payload_loaded":
|
|
1024
|
+
input = this.succeededEffect(event, {
|
|
1025
|
+
kind: "payload_loaded",
|
|
1026
|
+
handle_id: String(event.handle_id ?? ""),
|
|
1027
|
+
payload: event.payload,
|
|
1028
|
+
});
|
|
1029
|
+
break;
|
|
1030
|
+
case "payload_load_failed":
|
|
1031
|
+
input = this.failedEffect(event, "storage_unavailable", String(event.error ?? "payload load failed"), true);
|
|
1032
|
+
break;
|
|
1033
|
+
case "cancel_operation":
|
|
1034
|
+
input = {
|
|
1035
|
+
kind: "host_control",
|
|
1036
|
+
command: {
|
|
1037
|
+
kind: "cancel",
|
|
1038
|
+
reason: event.reason ?? "user",
|
|
1039
|
+
pending_call_ids: Array.isArray(event.pending_call_ids) ? event.pending_call_ids : [],
|
|
1040
|
+
},
|
|
1041
|
+
};
|
|
1042
|
+
break;
|
|
1043
|
+
case "deliver_signal":
|
|
1044
|
+
input = { kind: "deliver_external_event", event: this.canonicalSignal(event) };
|
|
1045
|
+
break;
|
|
1046
|
+
case "update_task":
|
|
1047
|
+
input = { kind: "host_control", command: { kind: "update_task", update: event.update } };
|
|
1048
|
+
break;
|
|
1049
|
+
case "add_knowledge_message":
|
|
1050
|
+
input = {
|
|
1051
|
+
kind: "host_control",
|
|
1052
|
+
command: {
|
|
1053
|
+
kind: "seed_knowledge",
|
|
1054
|
+
entries: [{
|
|
1055
|
+
content: String(event.content ?? ""),
|
|
1056
|
+
...(event.key ? { key: event.key } : {}),
|
|
1057
|
+
...(event.tokens !== undefined ? { tokens: Number(event.tokens) } : {}),
|
|
1058
|
+
...(event.pinned ? { pinned: true } : {}),
|
|
1059
|
+
}],
|
|
1060
|
+
},
|
|
1061
|
+
};
|
|
1062
|
+
break;
|
|
1063
|
+
case "remove_knowledge":
|
|
1064
|
+
input = {
|
|
1065
|
+
kind: "host_control",
|
|
1066
|
+
command: {
|
|
1067
|
+
kind: "apply_knowledge_mutation",
|
|
1068
|
+
mutation: { remove: [String(event.key ?? "")] },
|
|
1069
|
+
},
|
|
1070
|
+
};
|
|
1071
|
+
break;
|
|
1072
|
+
case "skill_deactivated":
|
|
1073
|
+
input = {
|
|
1074
|
+
kind: "host_control",
|
|
1075
|
+
command: {
|
|
1076
|
+
kind: "apply_skill_activation",
|
|
1077
|
+
deactivate: [String(event.name ?? "")],
|
|
1078
|
+
},
|
|
1079
|
+
};
|
|
1080
|
+
break;
|
|
1081
|
+
case "unsupported_effect":
|
|
1082
|
+
input = canonicalUnsupportedEffectResolution(String(event.effect_id ?? ""), String(event.effect_kind ?? ""));
|
|
1083
|
+
break;
|
|
1084
|
+
case "capability_command":
|
|
1085
|
+
input = { kind: "host_control", command: this.canonicalCapabilityCommand(asObject(event.command)) };
|
|
1086
|
+
break;
|
|
1087
|
+
case "add_history_message":
|
|
1088
|
+
throw new Error("running ABI v3 operations accept history only through effects or external events");
|
|
1089
|
+
default:
|
|
1090
|
+
throw new Error(`Node host fact has no canonical ABI input: ${String(event.kind)}`);
|
|
1091
|
+
}
|
|
1092
|
+
return this.commit(input);
|
|
1093
|
+
}
|
|
1094
|
+
async ensureConfigured() {
|
|
1095
|
+
if (this.configured)
|
|
1096
|
+
return;
|
|
1097
|
+
await this.commit({ kind: "configure_operation", config: this.config });
|
|
1098
|
+
this.configured = true;
|
|
1099
|
+
}
|
|
1100
|
+
async commit(input) {
|
|
1101
|
+
let nextInput = input;
|
|
1102
|
+
for (;;) {
|
|
1103
|
+
const transition = await this.host.transition(nextInput);
|
|
1104
|
+
if (!transition.replayed) {
|
|
1105
|
+
for (const raw of transition.plannedStep.observations ?? []) {
|
|
1106
|
+
const kind = String(raw.kind ?? "");
|
|
1107
|
+
if (!kind)
|
|
1108
|
+
throw new Error("canonical observation is missing kind");
|
|
1109
|
+
this.hostObservations.push({ ...raw, kind });
|
|
1110
|
+
}
|
|
1111
|
+
if (transition.checkpointAdvice) {
|
|
1112
|
+
this.hostObservations.push({
|
|
1113
|
+
kind: "checkpoint_advised",
|
|
1114
|
+
...transition.checkpointAdvice,
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
this.lastAction = canonicalActionFromPlannedStep(transition.plannedStep);
|
|
1119
|
+
if (this.lastAction?.kind !== "unsupported_effect")
|
|
1120
|
+
return this.lastAction;
|
|
1121
|
+
nextInput = canonicalUnsupportedEffectResolution(this.lastAction.effectId, this.lastAction.effectKind);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
currentAction() {
|
|
1125
|
+
const terminal = this.host.kernel.terminalJson();
|
|
1126
|
+
if (terminal) {
|
|
1127
|
+
return canonicalActionFromPlannedStep({
|
|
1128
|
+
disposition: { kind: "terminal", terminal: JSON.parse(terminal) },
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
const effects = this.pendingEffects();
|
|
1132
|
+
return canonicalActionFromPlannedStep({
|
|
1133
|
+
disposition: { kind: "effects", effects },
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
pendingEffects() {
|
|
1137
|
+
return JSON.parse(this.host.kernel.pendingEffectsJson());
|
|
1138
|
+
}
|
|
1139
|
+
succeededEffect(event, result) {
|
|
1140
|
+
return {
|
|
1141
|
+
kind: "resolve_effect",
|
|
1142
|
+
effect_id: String(event.effect_id ?? ""),
|
|
1143
|
+
outcome: { status: "succeeded", result },
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
failedEffect(event, kind, message, retryable) {
|
|
1147
|
+
return {
|
|
1148
|
+
kind: "resolve_effect",
|
|
1149
|
+
effect_id: String(event.effect_id ?? ""),
|
|
1150
|
+
outcome: {
|
|
1151
|
+
status: "failed",
|
|
1152
|
+
failure: {
|
|
1153
|
+
kind,
|
|
1154
|
+
message,
|
|
1155
|
+
...(retryable !== undefined ? { retryable } : {}),
|
|
1156
|
+
},
|
|
1157
|
+
},
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
pageOutResolution(event) {
|
|
1161
|
+
const pending = this.pendingEffects().find(effect => effect.effect_id === event.effect_id);
|
|
1162
|
+
const effect = asObject(pending?.effect);
|
|
1163
|
+
const payload = asObject(effect.payload);
|
|
1164
|
+
const content = String(payload.content ?? "");
|
|
1165
|
+
return this.succeededEffect(event, {
|
|
1166
|
+
kind: "page_out_archived",
|
|
1167
|
+
receipt: {
|
|
1168
|
+
handle_id: String(effect.handle_id ?? ""),
|
|
1169
|
+
payload_ref: String(event.payload_ref ?? `payload:${randomUUID()}`),
|
|
1170
|
+
digest: String(payload.digest ?? sha256(content)),
|
|
1171
|
+
original_size: String(payload.original_size ?? Buffer.byteLength(content, "utf8")),
|
|
1172
|
+
},
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
canonicalSignal(event) {
|
|
1176
|
+
const signal = asObject(event.signal);
|
|
1177
|
+
const deliveryId = String(event.delivery_id ?? randomUUID());
|
|
1178
|
+
const payload = deliveryId.startsWith("injected-") && typeof signal.summary === "string"
|
|
1179
|
+
? signal.summary
|
|
1180
|
+
: signal.payload ?? {};
|
|
1181
|
+
return {
|
|
1182
|
+
kind: "deliver_signal",
|
|
1183
|
+
delivery_id: deliveryId,
|
|
1184
|
+
attempt: Number(event.attempt ?? 1),
|
|
1185
|
+
signal: {
|
|
1186
|
+
signal_id: String(signal.signal_id ?? signal.id ?? randomUUID()),
|
|
1187
|
+
...(signal.source ? { source: signal.source } : {}),
|
|
1188
|
+
target: signal.recipient
|
|
1189
|
+
? { kind: "task", task_id: String(signal.recipient) }
|
|
1190
|
+
: { kind: "operation" },
|
|
1191
|
+
...(signal.urgency ? { urgency: signal.urgency } : {}),
|
|
1192
|
+
payload,
|
|
1193
|
+
...(signal.timestamp_ms !== undefined ? { source_timestamp_ms: String(signal.timestamp_ms) } : {}),
|
|
1194
|
+
...(signal.dedupe_key ? { dedupe_key: signal.dedupe_key } : {}),
|
|
1195
|
+
},
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
canonicalCapabilityCommand(command) {
|
|
1199
|
+
const capability = asObject(command.capability);
|
|
1200
|
+
if (command.action === "mount") {
|
|
1201
|
+
return {
|
|
1202
|
+
kind: "apply_capability_patch",
|
|
1203
|
+
patch: {
|
|
1204
|
+
mount: [{
|
|
1205
|
+
kind: String(capability.kind ?? "tool"),
|
|
1206
|
+
id: String(capability.id ?? ""),
|
|
1207
|
+
...(capability.description ? { description: capability.description } : {}),
|
|
1208
|
+
}],
|
|
1209
|
+
},
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
return {
|
|
1213
|
+
kind: "apply_capability_patch",
|
|
1214
|
+
patch: {
|
|
1215
|
+
unmount: [{
|
|
1216
|
+
kind: String(command.kind ?? "tool"),
|
|
1217
|
+
id: String(command.id ?? ""),
|
|
1218
|
+
}],
|
|
1219
|
+
},
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
applyBootstrapEvent(event) {
|
|
1223
|
+
switch (event.kind) {
|
|
1224
|
+
case "set_tokenizer":
|
|
1225
|
+
return true;
|
|
1226
|
+
case "set_plan_tool_enabled":
|
|
1227
|
+
this.featurePolicy().plan_tool_enabled = Boolean(event.enabled);
|
|
1228
|
+
return true;
|
|
1229
|
+
case "set_tools":
|
|
1230
|
+
this.config.tool_catalog = Array.isArray(event.tools) ? event.tools.map(asObject) : [];
|
|
1231
|
+
return true;
|
|
1232
|
+
case "add_system_message":
|
|
1233
|
+
this.initialContext.messages.push({
|
|
1234
|
+
role: "system",
|
|
1235
|
+
content: String(event.content ?? ""),
|
|
1236
|
+
...(event.tokens !== undefined ? { tokens: Number(event.tokens) } : {}),
|
|
1237
|
+
});
|
|
1238
|
+
return true;
|
|
1239
|
+
case "add_knowledge_message":
|
|
1240
|
+
this.initialContext.knowledge.push({
|
|
1241
|
+
content: String(event.content ?? ""),
|
|
1242
|
+
...(event.key ? { key: event.key } : {}),
|
|
1243
|
+
...(event.tokens !== undefined ? { tokens: Number(event.tokens) } : {}),
|
|
1244
|
+
...(event.pinned ? { pinned: true } : {}),
|
|
1245
|
+
});
|
|
1246
|
+
return true;
|
|
1247
|
+
case "set_available_skills":
|
|
1248
|
+
this.config.skill_catalog = event.skills ?? [];
|
|
1249
|
+
return true;
|
|
1250
|
+
case "set_stable_core_tools":
|
|
1251
|
+
this.featurePolicy().stable_core_tool_ids = event.tool_ids ?? [];
|
|
1252
|
+
return true;
|
|
1253
|
+
case "set_memory_enabled":
|
|
1254
|
+
this.featurePolicy().memory_enabled = Boolean(event.enabled);
|
|
1255
|
+
if (event.enabled) {
|
|
1256
|
+
this.config.memory_access = {
|
|
1257
|
+
binding_id: this.memoryBindingId,
|
|
1258
|
+
capabilities: { read: true, write: true },
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
return true;
|
|
1262
|
+
case "set_knowledge_enabled":
|
|
1263
|
+
this.featurePolicy().knowledge_enabled = Boolean(event.enabled);
|
|
1264
|
+
return true;
|
|
1265
|
+
case "set_memory_policy":
|
|
1266
|
+
this.config.memory_policy = {
|
|
1267
|
+
...(event.stale_warning_days !== undefined ? { stale_warning_days: event.stale_warning_days } : {}),
|
|
1268
|
+
...(event.retrieval_top_k !== undefined ? { retrieval_top_k: event.retrieval_top_k } : {}),
|
|
1269
|
+
...(event.validation_enabled !== undefined ? { validation_enabled: event.validation_enabled } : {}),
|
|
1270
|
+
...(event.max_content_bytes !== undefined ? { max_content_bytes: event.max_content_bytes } : {}),
|
|
1271
|
+
...(event.max_name_length !== undefined ? { max_name_length: event.max_name_length } : {}),
|
|
1272
|
+
...(event.promotion_recall_threshold !== undefined
|
|
1273
|
+
? { promotion_recall_threshold: String(event.promotion_recall_threshold) }
|
|
1274
|
+
: {}),
|
|
1275
|
+
};
|
|
1276
|
+
return true;
|
|
1277
|
+
case "load_milestone_contract": {
|
|
1278
|
+
const contract = asObject(event.contract);
|
|
1279
|
+
this.config.verification_contracts = [{
|
|
1280
|
+
contract_id: "node-default",
|
|
1281
|
+
phases: (Array.isArray(contract.phases) ? contract.phases : []).map(value => {
|
|
1282
|
+
const phase = asObject(value);
|
|
1283
|
+
return {
|
|
1284
|
+
phase_id: String(phase.id ?? ""),
|
|
1285
|
+
unlocks: (Array.isArray(phase.unlocks) ? phase.unlocks : []).map(unlock => {
|
|
1286
|
+
const item = asObject(unlock);
|
|
1287
|
+
return typeof unlock === "string" ? unlock : String(item.id ?? "");
|
|
1288
|
+
}).filter(Boolean),
|
|
1289
|
+
};
|
|
1290
|
+
}),
|
|
1291
|
+
}];
|
|
1292
|
+
return true;
|
|
1293
|
+
}
|
|
1294
|
+
case "preload_history":
|
|
1295
|
+
this.initialContext.messages.push(...(Array.isArray(event.messages) ? event.messages : []).map(value => canonicalInitialMessage(asObject(value))));
|
|
1296
|
+
return true;
|
|
1297
|
+
case "add_history_message":
|
|
1298
|
+
this.initialContext.messages.push(canonicalInitialMessage(asObject(event.message)));
|
|
1299
|
+
return true;
|
|
1300
|
+
case "configure_run":
|
|
1301
|
+
this.mergeHostConfig(asObject(event.config));
|
|
1302
|
+
return true;
|
|
1303
|
+
default:
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
featurePolicy() {
|
|
1308
|
+
const current = asObject(this.config.feature_policy);
|
|
1309
|
+
this.config.feature_policy = current;
|
|
1310
|
+
return current;
|
|
1311
|
+
}
|
|
1312
|
+
executionPolicy() {
|
|
1313
|
+
return asObject(this.config.execution_policy);
|
|
1314
|
+
}
|
|
1315
|
+
mergeHostConfig(config) {
|
|
1316
|
+
if (config.governance) {
|
|
1317
|
+
const governance = asObject(config.governance);
|
|
1318
|
+
this.config.governance_policy = {
|
|
1319
|
+
...governance,
|
|
1320
|
+
rate_limits: (Array.isArray(governance.rate_limits) ? governance.rate_limits : []).map(value => {
|
|
1321
|
+
const rule = asObject(value);
|
|
1322
|
+
return { ...rule, window_ms: String(rule.window_ms ?? 0) };
|
|
1323
|
+
}),
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
if (config.context_policy)
|
|
1327
|
+
this.config.context_policy = config.context_policy;
|
|
1328
|
+
if (config.signal_policy) {
|
|
1329
|
+
const signal = asObject(config.signal_policy);
|
|
1330
|
+
const { version: _version, ...rest } = signal;
|
|
1331
|
+
this.config.signal_policy = {
|
|
1332
|
+
...rest,
|
|
1333
|
+
...(rest.ttl_ms !== undefined ? { ttl_ms: String(rest.ttl_ms) } : {}),
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
if (config.scheduler_policy) {
|
|
1337
|
+
const scheduler = asObject(config.scheduler_policy);
|
|
1338
|
+
const { version: _version, ...rest } = scheduler;
|
|
1339
|
+
this.config.scheduler_policy = rest;
|
|
1340
|
+
}
|
|
1341
|
+
if (config.resource_quota) {
|
|
1342
|
+
const quota = asObject(config.resource_quota);
|
|
1343
|
+
const window = Array.isArray(quota.memory_writes_per_window)
|
|
1344
|
+
? quota.memory_writes_per_window
|
|
1345
|
+
: undefined;
|
|
1346
|
+
this.config.resource_quota = {
|
|
1347
|
+
...quota,
|
|
1348
|
+
...(window
|
|
1349
|
+
? {
|
|
1350
|
+
memory_writes_per_window: {
|
|
1351
|
+
max_events: Number(window[0] ?? 0),
|
|
1352
|
+
window_ms: String(window[1] ?? 0),
|
|
1353
|
+
},
|
|
1354
|
+
}
|
|
1355
|
+
: {}),
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
if (config.budget_grant) {
|
|
1359
|
+
const grant = asObject(config.budget_grant);
|
|
1360
|
+
this.config.budget_grant = {
|
|
1361
|
+
...grant,
|
|
1362
|
+
...(grant.tokens !== undefined ? { tokens: String(grant.tokens) } : {}),
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
if (config.prompt_budget) {
|
|
1366
|
+
const context = asObject(this.config.context_policy);
|
|
1367
|
+
context.prompt_budget = config.prompt_budget;
|
|
1368
|
+
this.config.context_policy = context;
|
|
1369
|
+
}
|
|
1370
|
+
const execution = this.executionPolicy();
|
|
1371
|
+
if (config.repeat_fuse)
|
|
1372
|
+
execution.repeat_fuse = config.repeat_fuse;
|
|
1373
|
+
if (config.criteria_gate !== undefined)
|
|
1374
|
+
execution.criteria_gate_enabled = config.criteria_gate;
|
|
1375
|
+
if (config.entropy_watch) {
|
|
1376
|
+
const entropy = asObject(config.entropy_watch);
|
|
1377
|
+
execution.entropy_watch = {
|
|
1378
|
+
...entropy,
|
|
1379
|
+
...(typeof entropy.threshold === "number"
|
|
1380
|
+
? { threshold_ppm: Math.round(entropy.threshold * 1_000_000) }
|
|
1381
|
+
: {}),
|
|
1382
|
+
...(typeof entropy.hysteresis === "number"
|
|
1383
|
+
? { hysteresis_ppm: Math.round(entropy.hysteresis * 1_000_000) }
|
|
1384
|
+
: {}),
|
|
1385
|
+
};
|
|
1386
|
+
delete asObject(execution.entropy_watch).threshold;
|
|
1387
|
+
delete asObject(execution.entropy_watch).hysteresis;
|
|
1388
|
+
}
|
|
1389
|
+
if (config.tool_dispatch_gate !== undefined) {
|
|
1390
|
+
this.featurePolicy().tool_dispatch_gate = config.tool_dispatch_gate;
|
|
1391
|
+
}
|
|
1392
|
+
if (config.knowledge_budget_ratio !== undefined) {
|
|
1393
|
+
const context = asObject(this.config.context_policy);
|
|
1394
|
+
context.knowledge_budget_ppm = Math.round(Number(config.knowledge_budget_ratio) * 1_000_000);
|
|
1395
|
+
this.config.context_policy = context;
|
|
1396
|
+
}
|
|
1397
|
+
if (config.reliability) {
|
|
1398
|
+
const reliability = asObject(config.reliability);
|
|
1399
|
+
this.config.recovery_policy = {
|
|
1400
|
+
...(reliability.provider_recovery_attempts !== undefined
|
|
1401
|
+
? { provider_recovery_attempts: reliability.provider_recovery_attempts }
|
|
1402
|
+
: {}),
|
|
1403
|
+
...(reliability.output_recovery_attempts !== undefined
|
|
1404
|
+
? { output_recovery_attempts: reliability.output_recovery_attempts }
|
|
1405
|
+
: {}),
|
|
1406
|
+
};
|
|
1407
|
+
if (reliability.max_input_bytes !== undefined) {
|
|
1408
|
+
this.config.kernel_limits = {
|
|
1409
|
+
...asObject(this.config.kernel_limits),
|
|
1410
|
+
max_input_bytes: reliability.max_input_bytes,
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
export async function canonicalKernelApply(runtime, pending, event) {
|
|
1417
|
+
await runtime.applyHostEvent(event);
|
|
1418
|
+
const observations = runtime.drainHostObservations();
|
|
1419
|
+
pending.push(...observations);
|
|
1420
|
+
return observations;
|
|
1421
|
+
}
|
|
1422
|
+
export async function canonicalKernelMaybeAction(runtime, pending, event) {
|
|
1423
|
+
const action = await runtime.applyHostEvent(event);
|
|
1424
|
+
pending.push(...runtime.drainHostObservations());
|
|
1425
|
+
return action;
|
|
1426
|
+
}
|
|
1427
|
+
export async function canonicalKernelAction(runtime, pending, event) {
|
|
1428
|
+
const action = await canonicalKernelMaybeAction(runtime, pending, event);
|
|
1429
|
+
if (!action)
|
|
1430
|
+
throw new Error("canonical kernel transition must return one host action");
|
|
1431
|
+
return action;
|
|
1432
|
+
}
|
|
1433
|
+
export async function canonicalStartAgent(runtime, pending, task, runSpec) {
|
|
1434
|
+
const action = await runtime.startAgent(task, runSpec);
|
|
1435
|
+
pending.push(...runtime.drainHostObservations());
|
|
1436
|
+
if (!action)
|
|
1437
|
+
throw new Error("canonical agent root must return one host action");
|
|
1438
|
+
return action;
|
|
1439
|
+
}
|
|
1440
|
+
export async function canonicalStartWorkflow(runtime, pending, spec) {
|
|
1441
|
+
const action = await runtime.startWorkflow(spec);
|
|
1442
|
+
pending.push(...runtime.drainHostObservations());
|
|
1443
|
+
return action;
|
|
1444
|
+
}
|