@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.
Files changed (39) hide show
  1. package/README.md +83 -60
  2. package/dist/index.d.ts +5 -7
  3. package/dist/index.js +3 -3
  4. package/dist/kernel.d.ts +61 -31
  5. package/dist/runtime/canonical-kernel-step.d.ts +143 -0
  6. package/dist/runtime/canonical-kernel-step.js +1444 -0
  7. package/dist/runtime/execution-plane.d.ts +0 -3
  8. package/dist/runtime/execution-plane.js +0 -24
  9. package/dist/runtime/facade.js +3 -0
  10. package/dist/runtime/kernel-event-log.js +7 -13
  11. package/dist/runtime/kernel-journal.d.ts +264 -0
  12. package/dist/runtime/kernel-journal.js +741 -0
  13. package/dist/runtime/kernel-primitives-dashboard.d.ts +0 -2
  14. package/dist/runtime/kernel-primitives-dashboard.js +1 -8
  15. package/dist/runtime/kernel-step.d.ts +29 -109
  16. package/dist/runtime/kernel-step.js +47 -317
  17. package/dist/runtime/os-snapshot.d.ts +2 -2
  18. package/dist/runtime/os-snapshot.js +2 -6
  19. package/dist/runtime/payload-store.d.ts +16 -0
  20. package/dist/runtime/payload-store.js +80 -0
  21. package/dist/runtime/runner.d.ts +31 -114
  22. package/dist/runtime/runner.js +689 -774
  23. package/dist/runtime/session-log.d.ts +34 -32
  24. package/dist/runtime/session-log.js +21 -131
  25. package/dist/runtime/session-repair.d.ts +2 -36
  26. package/dist/runtime/session-repair.js +2 -47
  27. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  28. package/dist/runtime/sub-agent-orchestrator.js +42 -40
  29. package/dist/types/agent.d.ts +22 -19
  30. package/dist/types/agent.js +26 -42
  31. package/dist/workflow/public.d.ts +1 -1
  32. package/dist/workflow/public.js +1 -1
  33. package/package.json +2 -2
  34. package/dist/runtime/kernel-rebuild.d.ts +0 -13
  35. package/dist/runtime/kernel-rebuild.js +0 -75
  36. package/dist/runtime/kernel-transaction-log.d.ts +0 -61
  37. package/dist/runtime/kernel-transaction-log.js +0 -149
  38. package/dist/runtime/large-result-spool.d.ts +0 -93
  39. 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
+ }