@deepstrike/wasm 0.2.22 → 0.2.25
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/dist/governance.d.ts +9 -0
- package/dist/governance.js +25 -0
- package/dist/harness/index.d.ts +13 -0
- package/dist/harness/index.js +26 -17
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -0
- package/dist/providers/anthropic.js +46 -1
- package/dist/runtime/execution-plane.js +25 -5
- package/dist/runtime/runner.d.ts +12 -0
- package/dist/runtime/runner.js +362 -294
- package/dist/tools/errors.d.ts +42 -0
- package/dist/tools/errors.js +97 -0
- package/dist/tools/index.d.ts +8 -1
- package/dist/tools/index.js +2 -1
- package/dist/types.d.ts +15 -0
- package/package.json +2 -2
package/dist/runtime/runner.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { resolvePermissionRequest } from "./execution-plane.js";
|
|
2
|
-
import { governancePolicyToKernelEvent } from "../governance.js";
|
|
2
|
+
import { governancePolicyToKernelEvent, governanceFilterSchema } from "../governance.js";
|
|
3
3
|
import { getKernel } from "./kernel.js";
|
|
4
4
|
import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-replay.js";
|
|
5
5
|
import { sanitizeReplayText } from "./replay-sanitize.js";
|
|
6
|
+
import { formatToolError } from "../tools/errors.js";
|
|
6
7
|
import { buildLlmCompletedEvent, buildRunTerminalEvent, buildWorkflowNodeCompletedEvent, buildWorkflowNodesSubmittedEvent, recoverCompletedWorkflowNodes, recoverSubmittedWorkflowNodes, repairEventsForRecovery, } from "./session-repair.js";
|
|
7
8
|
import { forceCompact, kernelAction, kernelApply, kernelMaybeAction, messageToKernelMessage, skillMetadataToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
|
|
8
9
|
import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, submitWorkflowNodesToKernel, submitWorkflowToKernel, workflowBudgetNote, workflowNodeToManifest, workflowNodeToSpec, workflowSpecToKernel, } from "./types/agent.js";
|
|
@@ -434,335 +435,402 @@ export class RuntimeRunner {
|
|
|
434
435
|
...(m.maxNameLength !== undefined ? { max_name_length: m.maxNameLength } : {}),
|
|
435
436
|
});
|
|
436
437
|
}
|
|
438
|
+
// I4: pre-fetch memory into the knowledge partition before the first LLM turn (mirrors Node).
|
|
439
|
+
if (!resumeMidRun && this.opts.preQueryMemory && this.opts.dreamStore && this.opts.agentId) {
|
|
440
|
+
try {
|
|
441
|
+
const queries = await this.opts.preQueryMemory({ goal });
|
|
442
|
+
const entries = [];
|
|
443
|
+
for (const q of queries ?? []) {
|
|
444
|
+
if (typeof q !== "string" || !q.trim())
|
|
445
|
+
continue;
|
|
446
|
+
const hits = await this.opts.dreamStore.search(this.opts.agentId, q, 5);
|
|
447
|
+
for (const hit of hits) {
|
|
448
|
+
entries.push({ content: `[memory score=${hit.score.toFixed(3)}] ${hit.text}`, source: "memory" });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (entries.length > 0) {
|
|
452
|
+
kernelApply(runtime, this.pendingObservations, { kind: "page_in", entries });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch { /* errs-open */ }
|
|
456
|
+
}
|
|
437
457
|
let action = resumeMidRun
|
|
438
458
|
? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
|
|
439
459
|
: kernelAction(runtime, this.pendingObservations, startPayload);
|
|
440
460
|
let hasAttemptedReactiveCompact = false;
|
|
441
461
|
// P0-C: the skill loaded and in effect going into the current turn → per-turn `activeSkill` metric.
|
|
442
462
|
let activeSkill;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (this.interrupted) {
|
|
449
|
-
action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
if (this.opts.signalSource) {
|
|
453
|
-
const sig = await this.opts.signalSource.nextSignal();
|
|
454
|
-
if (sig) {
|
|
455
|
-
const sigAction = kernelMaybeAction(runtime, this.pendingObservations, signalToKernelEvent(sig));
|
|
456
|
-
if (sigAction)
|
|
457
|
-
action = sigAction;
|
|
463
|
+
// I0b: kernel-throw safety net — see Node runner for full rationale.
|
|
464
|
+
try {
|
|
465
|
+
while (!runtime.isTerminal()) {
|
|
466
|
+
if (action.kind === "execute_tool") {
|
|
467
|
+
await this.applyKernelPageIn(runtime, sessionId);
|
|
458
468
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// M5 v2.1: top-level auto-pivot at the safe point (kernel in Reason, not suspended). Loop-top
|
|
464
|
-
// placement catches every path to `call_provider` (incl. post-approval-resume), so a queued
|
|
465
|
-
// authored spec is never stranded. Drains the queue; fires once per authored batch.
|
|
466
|
-
if (this.pendingAuthoredWorkflows.length > 0) {
|
|
467
|
-
action = await this.driveAuthoredWorkflows(runtime, action);
|
|
469
|
+
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
470
|
+
if (this.interrupted) {
|
|
471
|
+
action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
|
|
472
|
+
break;
|
|
468
473
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
let shouldRetry = false;
|
|
479
|
-
const abortSignal = this.abortController?.signal;
|
|
480
|
-
try {
|
|
481
|
-
for await (const evt of this.opts.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState, abortSignal)) {
|
|
482
|
-
// #2-B-ii: a preempting interrupt fires abortController — stop consuming the live stream.
|
|
483
|
-
if (abortSignal?.aborted)
|
|
484
|
-
break;
|
|
485
|
-
if (evt.type === "usage") {
|
|
486
|
-
const usageEvt = evt;
|
|
487
|
-
turnTokens = usageEvt.totalTokens;
|
|
488
|
-
turnInputTokens = usageEvt.inputTokens ?? 0;
|
|
489
|
-
turnOutputTokens = usageEvt.outputTokens ?? 0;
|
|
490
|
-
// P0-C: capture the prompt-cache split for the tool-gating hit-rate baseline.
|
|
491
|
-
turnCacheReadTokens = usageEvt.cacheReadInputTokens ?? 0;
|
|
492
|
-
turnCacheCreationTokens = usageEvt.cacheCreationInputTokens ?? 0;
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
yield evt;
|
|
496
|
-
if (evt.type === "text_delta")
|
|
497
|
-
finalText += evt.delta;
|
|
498
|
-
else if (evt.type === "tool_call") {
|
|
499
|
-
const tc = evt;
|
|
500
|
-
finalToolCalls.push({ id: tc.id, name: tc.name, arguments: JSON.stringify(tc.arguments) });
|
|
501
|
-
}
|
|
474
|
+
if (this.opts.signalSource) {
|
|
475
|
+
const sig = await this.opts.signalSource.nextSignal();
|
|
476
|
+
if (sig) {
|
|
477
|
+
const sigAction = kernelMaybeAction(runtime, this.pendingObservations, signalToKernelEvent(sig));
|
|
478
|
+
if (sigAction)
|
|
479
|
+
action = sigAction;
|
|
480
|
+
// I0a: Critical signal carries user_abort intent; see Node runner for full rationale.
|
|
481
|
+
if (sig.urgency === "critical")
|
|
482
|
+
this.interrupted = true;
|
|
502
483
|
}
|
|
503
484
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
485
|
+
if (runtime.isTerminal())
|
|
486
|
+
break;
|
|
487
|
+
if (action.kind === "call_provider") {
|
|
488
|
+
// M5 v2.1: top-level auto-pivot at the safe point (kernel in Reason, not suspended). Loop-top
|
|
489
|
+
// placement catches every path to `call_provider` (incl. post-approval-resume), so a queued
|
|
490
|
+
// authored spec is never stranded. Drains the queue; fires once per authored batch.
|
|
491
|
+
if (this.pendingAuthoredWorkflows.length > 0) {
|
|
492
|
+
action = await this.driveAuthoredWorkflows(runtime, action);
|
|
493
|
+
}
|
|
494
|
+
const finalToolCalls = [];
|
|
495
|
+
let finalText = "";
|
|
496
|
+
// I5: governance schema-level pre-filter — see Node runner for full rationale.
|
|
497
|
+
let context = action.context;
|
|
498
|
+
let tools = action.tools;
|
|
499
|
+
if (this.opts.governancePolicy && this.opts.governancePolicy.surfaceDeniedInSystem !== false) {
|
|
500
|
+
const { allowed, denied } = governanceFilterSchema(tools, this.opts.governancePolicy);
|
|
501
|
+
if (denied.length > 0) {
|
|
502
|
+
tools = allowed;
|
|
503
|
+
const note = `[governance] the following tools are denied for this run and will fail if called: ${denied.join(", ")}.`;
|
|
504
|
+
context = {
|
|
505
|
+
...context,
|
|
506
|
+
systemKnowledge: context.systemKnowledge
|
|
507
|
+
? `${context.systemKnowledge}\n\n${note}`
|
|
508
|
+
: note,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
let turnTokens = 0;
|
|
513
|
+
let turnInputTokens = 0;
|
|
514
|
+
let turnOutputTokens = 0;
|
|
515
|
+
let turnCacheReadTokens = 0;
|
|
516
|
+
let turnCacheCreationTokens = 0;
|
|
517
|
+
let turnCacheReadBySlot;
|
|
518
|
+
let shouldRetry = false;
|
|
519
|
+
const abortSignal = this.abortController?.signal;
|
|
520
|
+
try {
|
|
521
|
+
for await (const evt of this.opts.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState, abortSignal)) {
|
|
522
|
+
// #2-B-ii: a preempting interrupt fires abortController — stop consuming the live stream.
|
|
523
|
+
if (abortSignal?.aborted)
|
|
524
|
+
break;
|
|
525
|
+
if (evt.type === "usage") {
|
|
526
|
+
const usageEvt = evt;
|
|
527
|
+
turnTokens = usageEvt.totalTokens;
|
|
528
|
+
turnInputTokens = usageEvt.inputTokens ?? 0;
|
|
529
|
+
turnOutputTokens = usageEvt.outputTokens ?? 0;
|
|
530
|
+
// P0-C: capture the prompt-cache split for the tool-gating hit-rate baseline.
|
|
531
|
+
turnCacheReadTokens = usageEvt.cacheReadInputTokens ?? 0;
|
|
532
|
+
turnCacheCreationTokens = usageEvt.cacheCreationInputTokens ?? 0;
|
|
533
|
+
// I1: per-slot attribution forwarded to TurnMetrics; undefined on non-Anthropic providers.
|
|
534
|
+
turnCacheReadBySlot = usageEvt.cacheReadInputTokensBySlot;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
yield evt;
|
|
538
|
+
if (evt.type === "text_delta")
|
|
539
|
+
finalText += evt.delta;
|
|
540
|
+
else if (evt.type === "tool_call") {
|
|
541
|
+
const tc = evt;
|
|
542
|
+
finalToolCalls.push({ id: tc.id, name: tc.name, arguments: JSON.stringify(tc.arguments) });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
508
545
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
546
|
+
catch (err) {
|
|
547
|
+
// #2-B-ii: an aborted in-flight request surfaces as an AbortError — treat as an interrupt.
|
|
548
|
+
if (abortSignal?.aborted) {
|
|
549
|
+
this.interrupted = true;
|
|
550
|
+
}
|
|
551
|
+
const errMsg = formatToolError(err).toLowerCase();
|
|
552
|
+
if ((errMsg.includes("413") || errMsg.includes("too long") || errMsg.includes("context length exceeded") || errMsg.includes("context_length_exceeded")) &&
|
|
553
|
+
!hasAttemptedReactiveCompact) {
|
|
554
|
+
hasAttemptedReactiveCompact = true;
|
|
555
|
+
if (forceCompact(runtime, this.pendingObservations)) {
|
|
556
|
+
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
557
|
+
shouldRetry = true;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (!shouldRetry) {
|
|
561
|
+
yield { type: "error", message: formatToolError(err) };
|
|
562
|
+
action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
|
|
563
|
+
break;
|
|
516
564
|
}
|
|
517
565
|
}
|
|
518
|
-
|
|
519
|
-
|
|
566
|
+
// #2-B-ii: stream aborted (preempt/interrupt) via the break path — end the turn now.
|
|
567
|
+
if (abortSignal?.aborted) {
|
|
520
568
|
action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
|
|
521
569
|
break;
|
|
522
570
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
571
|
+
if (shouldRetry) {
|
|
572
|
+
action = {
|
|
573
|
+
kind: "call_provider",
|
|
574
|
+
context: runtime.render(),
|
|
575
|
+
tools,
|
|
576
|
+
};
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const assistantMessage = {
|
|
580
|
+
role: "assistant",
|
|
581
|
+
content: finalText,
|
|
582
|
+
toolCalls: finalToolCalls,
|
|
583
|
+
tokenCount: turnOutputTokens || turnTokens || undefined,
|
|
534
584
|
};
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (!nextAction && hasSuspended) {
|
|
553
|
-
const resolved = await this.resolveKernelSuspend(runtime, sessionId);
|
|
554
|
-
for (const evt of resolved.events)
|
|
555
|
-
yield evt;
|
|
556
|
-
nextAction = kernelAction(runtime, this.pendingObservations, {
|
|
557
|
-
kind: "resume",
|
|
558
|
-
approved_calls: resolved.approved,
|
|
559
|
-
denied_calls: resolved.denied,
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
action = nextAction ?? kernelAction(runtime, this.pendingObservations, providerEvent);
|
|
563
|
-
const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
|
|
564
|
-
await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
|
|
565
|
-
turn: runtime.turn(),
|
|
566
|
-
content: finalText,
|
|
567
|
-
tokenCount: turnOutputTokens || turnTokens || undefined,
|
|
568
|
-
toolCalls: finalToolCalls,
|
|
569
|
-
providerReplay,
|
|
570
|
-
}));
|
|
571
|
-
// P0-C: per-turn tool-gating telemetry. `activeSkill` reflects the skill in effect GOING INTO
|
|
572
|
-
// this turn; a `skill` call here only takes effect next turn — emit first, then advance.
|
|
573
|
-
if (this.opts.onTurnMetrics) {
|
|
574
|
-
try {
|
|
575
|
-
this.opts.onTurnMetrics({
|
|
576
|
-
turn: runtime.turn(),
|
|
577
|
-
toolsExposed: tools.length,
|
|
578
|
-
toolsCalled: finalToolCalls.length,
|
|
579
|
-
activeSkill,
|
|
580
|
-
inputTokens: turnInputTokens,
|
|
581
|
-
cacheReadTokens: turnCacheReadTokens,
|
|
582
|
-
cacheCreationTokens: turnCacheCreationTokens,
|
|
585
|
+
const providerEvent = {
|
|
586
|
+
kind: "provider_result",
|
|
587
|
+
message: messageToKernelMessage(assistantMessage),
|
|
588
|
+
...(turnInputTokens > 0 ? { observed_input_tokens: turnInputTokens } : {}),
|
|
589
|
+
...(turnOutputTokens > 0 ? { observed_output_tokens: turnOutputTokens } : {}),
|
|
590
|
+
now_ms: Date.now(),
|
|
591
|
+
};
|
|
592
|
+
let nextAction = kernelMaybeAction(runtime, this.pendingObservations, providerEvent);
|
|
593
|
+
const hasSuspended = this.pendingObservations.some(o => o.kind === "suspended");
|
|
594
|
+
if (!nextAction && hasSuspended) {
|
|
595
|
+
const resolved = await this.resolveKernelSuspend(runtime, sessionId);
|
|
596
|
+
for (const evt of resolved.events)
|
|
597
|
+
yield evt;
|
|
598
|
+
nextAction = kernelAction(runtime, this.pendingObservations, {
|
|
599
|
+
kind: "resume",
|
|
600
|
+
approved_calls: resolved.approved,
|
|
601
|
+
denied_calls: resolved.denied,
|
|
583
602
|
});
|
|
584
603
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
604
|
+
action = nextAction ?? kernelAction(runtime, this.pendingObservations, providerEvent);
|
|
605
|
+
const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
|
|
606
|
+
await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
|
|
607
|
+
turn: runtime.turn(),
|
|
608
|
+
content: finalText,
|
|
609
|
+
tokenCount: turnOutputTokens || turnTokens || undefined,
|
|
610
|
+
toolCalls: finalToolCalls,
|
|
611
|
+
providerReplay,
|
|
612
|
+
}));
|
|
613
|
+
// P0-C: per-turn tool-gating telemetry. `activeSkill` reflects the skill in effect GOING INTO
|
|
614
|
+
// this turn; a `skill` call here only takes effect next turn — emit first, then advance.
|
|
615
|
+
if (this.opts.onTurnMetrics) {
|
|
616
|
+
try {
|
|
617
|
+
this.opts.onTurnMetrics({
|
|
618
|
+
turn: runtime.turn(),
|
|
619
|
+
toolsExposed: tools.length,
|
|
620
|
+
toolsCalled: finalToolCalls.length,
|
|
621
|
+
activeSkill,
|
|
622
|
+
inputTokens: turnInputTokens,
|
|
623
|
+
cacheReadTokens: turnCacheReadTokens,
|
|
624
|
+
cacheCreationTokens: turnCacheCreationTokens,
|
|
625
|
+
...(turnCacheReadBySlot ? { cacheReadTokensBySlot: turnCacheReadBySlot } : {}),
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
catch { /* metrics must never break the run */ }
|
|
593
629
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const runCtx = {
|
|
601
|
-
agentId: this.opts.agentId,
|
|
602
|
-
skillContentMap: this.opts.skillContentMap,
|
|
603
|
-
dreamStore: this.opts.dreamStore,
|
|
604
|
-
knowledgeSource: this.opts.knowledgeSource,
|
|
605
|
-
onToolSuspend: this.opts.onToolSuspend,
|
|
606
|
-
onPermissionRequest: this.opts.onPermissionRequest,
|
|
607
|
-
};
|
|
608
|
-
const toolResults = [];
|
|
609
|
-
// R3-1: intercept `submit_workflow_nodes` — it can't apply to this runner's kernel (when this
|
|
610
|
-
// runner is a workflow node, the workflow lives in the parent). Surface the nodes as an event;
|
|
611
|
-
// the orchestrator collects them and `runWorkflow` sends them to the parent kernel.
|
|
612
|
-
// M5 v1: `start_workflow` (author a sub-workflow) flattens to the same append path.
|
|
613
|
-
const submitCalls = allCalls.filter(c => c.name === "submit_workflow_nodes" || c.name === "start_workflow");
|
|
614
|
-
const normalCalls = allCalls.filter(c => c.name !== "submit_workflow_nodes" && c.name !== "start_workflow");
|
|
615
|
-
for (const call of submitCalls) {
|
|
616
|
-
// M5 v2.1: a TOP-LEVEL agent authoring a whole sub-workflow via `start_workflow` — record the
|
|
617
|
-
// spec and AUTO-PIVOT once this tool turn resolves. A workflow-NODE's `start_workflow` (and
|
|
618
|
-
// every `submit_workflow_nodes`) instead FLATTENS for the parent `runWorkflow` to append.
|
|
619
|
-
if (call.name === "start_workflow" && !this.opts.isWorkflowNode) {
|
|
620
|
-
const spec = parseStartWorkflowSpec(call.arguments);
|
|
621
|
-
if (spec) {
|
|
622
|
-
this.pendingAuthoredWorkflows.push(spec);
|
|
623
|
-
const out = "workflow authored; executing now";
|
|
624
|
-
toolResults.push({ callId: call.id, output: out, isError: false });
|
|
625
|
-
yield { type: "tool_result", callId: call.id, content: out, isError: false };
|
|
626
|
-
continue;
|
|
630
|
+
const skillCall = finalToolCalls.find(c => c.name === "skill");
|
|
631
|
+
if (skillCall) {
|
|
632
|
+
try {
|
|
633
|
+
const name = JSON.parse(skillCall.arguments || "{}").name;
|
|
634
|
+
if (name)
|
|
635
|
+
activeSkill = name;
|
|
627
636
|
}
|
|
637
|
+
catch { /* malformed skill args — leave activeSkill unchanged */ }
|
|
628
638
|
}
|
|
629
|
-
const nodes = call.name === "start_workflow"
|
|
630
|
-
? parseStartWorkflowArgs(call.arguments)
|
|
631
|
-
: parseSubmitWorkflowNodesArgs(call.arguments);
|
|
632
|
-
yield { type: "workflow_nodes_submitted", nodes };
|
|
633
|
-
toolResults.push({ callId: call.id, output: "submitted", isError: false });
|
|
634
|
-
yield { type: "tool_result", callId: call.id, content: "submitted", isError: false };
|
|
635
639
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
640
|
+
else if (action.kind === "execute_tool") {
|
|
641
|
+
const allCalls = action.calls;
|
|
642
|
+
await this.opts.sessionLog.append(sessionId, { kind: "tool_requested", turn: runtime.turn(), calls: allCalls });
|
|
643
|
+
const runCtx = {
|
|
644
|
+
agentId: this.opts.agentId,
|
|
645
|
+
skillContentMap: this.opts.skillContentMap,
|
|
646
|
+
dreamStore: this.opts.dreamStore,
|
|
647
|
+
knowledgeSource: this.opts.knowledgeSource,
|
|
648
|
+
onToolSuspend: this.opts.onToolSuspend,
|
|
649
|
+
onPermissionRequest: this.opts.onPermissionRequest,
|
|
650
|
+
};
|
|
651
|
+
const toolResults = [];
|
|
652
|
+
// R3-1: intercept `submit_workflow_nodes` — it can't apply to this runner's kernel (when this
|
|
653
|
+
// runner is a workflow node, the workflow lives in the parent). Surface the nodes as an event;
|
|
654
|
+
// the orchestrator collects them and `runWorkflow` sends them to the parent kernel.
|
|
655
|
+
// M5 v1: `start_workflow` (author a sub-workflow) flattens to the same append path.
|
|
656
|
+
const submitCalls = allCalls.filter(c => c.name === "submit_workflow_nodes" || c.name === "start_workflow");
|
|
657
|
+
const normalCalls = allCalls.filter(c => c.name !== "submit_workflow_nodes" && c.name !== "start_workflow");
|
|
658
|
+
for (const call of submitCalls) {
|
|
659
|
+
// M5 v2.1: a TOP-LEVEL agent authoring a whole sub-workflow via `start_workflow` — record the
|
|
660
|
+
// spec and AUTO-PIVOT once this tool turn resolves. A workflow-NODE's `start_workflow` (and
|
|
661
|
+
// every `submit_workflow_nodes`) instead FLATTENS for the parent `runWorkflow` to append.
|
|
662
|
+
if (call.name === "start_workflow" && !this.opts.isWorkflowNode) {
|
|
663
|
+
const spec = parseStartWorkflowSpec(call.arguments);
|
|
664
|
+
if (spec) {
|
|
665
|
+
this.pendingAuthoredWorkflows.push(spec);
|
|
666
|
+
const out = "workflow authored; executing now";
|
|
667
|
+
toolResults.push({ callId: call.id, output: out, isError: false });
|
|
668
|
+
yield { type: "tool_result", callId: call.id, content: out, isError: false };
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const nodes = call.name === "start_workflow"
|
|
673
|
+
? parseStartWorkflowArgs(call.arguments)
|
|
674
|
+
: parseSubmitWorkflowNodesArgs(call.arguments);
|
|
675
|
+
yield { type: "workflow_nodes_submitted", nodes };
|
|
676
|
+
toolResults.push({ callId: call.id, output: "submitted", isError: false });
|
|
677
|
+
yield { type: "tool_result", callId: call.id, content: "submitted", isError: false };
|
|
647
678
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
679
|
+
for await (const evt of this.opts.executionPlane.executeAll(normalCalls, runCtx)) {
|
|
680
|
+
yield evt;
|
|
681
|
+
if (evt.type === "tool_result") {
|
|
682
|
+
const tre = evt;
|
|
683
|
+
toolResults.push({
|
|
684
|
+
callId: tre.callId,
|
|
685
|
+
output: tre.content,
|
|
686
|
+
isError: tre.isError,
|
|
687
|
+
isFatal: tre.isFatal,
|
|
688
|
+
errorKind: tre.errorKind,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
else if (evt.type === "tool_argument_repaired") {
|
|
692
|
+
const tare = evt;
|
|
693
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
694
|
+
kind: "tool_argument_repaired",
|
|
695
|
+
turn: runtime.turn(),
|
|
696
|
+
tool: tare.name,
|
|
697
|
+
original_arguments: tare.originalArguments,
|
|
698
|
+
repaired_arguments: tare.repairedArguments,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
else if (evt.type === "tool_denied") {
|
|
702
|
+
const tde = evt;
|
|
703
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
704
|
+
kind: "tool_denied",
|
|
705
|
+
turn: runtime.turn(),
|
|
706
|
+
call_id: tde.callId,
|
|
707
|
+
tool_name: tde.toolName,
|
|
708
|
+
reason: tde.reason,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
else if (evt.type === "permission_request") {
|
|
712
|
+
const pre = evt;
|
|
713
|
+
const turn = runtime.turn();
|
|
714
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
715
|
+
kind: "permission_requested",
|
|
716
|
+
turn,
|
|
717
|
+
tool: pre.toolName,
|
|
718
|
+
arguments: typeof pre.arguments === "string" ? pre.arguments : JSON.stringify(pre.arguments),
|
|
719
|
+
reason: pre.reason,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
else if (evt.type === "permission_resolved") {
|
|
723
|
+
const resolved = evt;
|
|
724
|
+
const turn = runtime.turn();
|
|
725
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
726
|
+
kind: "permission_resolved",
|
|
727
|
+
turn,
|
|
728
|
+
approved: resolved.approved,
|
|
729
|
+
responder: resolved.responder,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
657
732
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
})
|
|
733
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
734
|
+
kind: "tool_completed",
|
|
735
|
+
turn: runtime.turn(),
|
|
736
|
+
results: toolResults.map(r => ({
|
|
737
|
+
call_id: r.callId,
|
|
738
|
+
output: r.output,
|
|
739
|
+
is_error: r.isError,
|
|
740
|
+
token_count: r.tokenCount,
|
|
741
|
+
})),
|
|
742
|
+
});
|
|
743
|
+
for (const call of allCalls) {
|
|
744
|
+
const result = toolResults.find(r => r.callId === call.id);
|
|
745
|
+
if (result) {
|
|
746
|
+
this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
|
|
747
|
+
}
|
|
667
748
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
749
|
+
// P1-B B3: a successfully-resolved `skill` call activates that skill for the next turn.
|
|
750
|
+
for (const call of allCalls) {
|
|
751
|
+
if (call.name !== "skill")
|
|
752
|
+
continue;
|
|
753
|
+
const res = toolResults.find(r => r.callId === call.id);
|
|
754
|
+
if (!res || res.isError)
|
|
755
|
+
continue;
|
|
756
|
+
try {
|
|
757
|
+
const name = JSON.parse(call.arguments || "{}").name;
|
|
758
|
+
if (name)
|
|
759
|
+
kernelApply(runtime, this.pendingObservations, { kind: "skill_activated", name });
|
|
760
|
+
}
|
|
761
|
+
catch { /* skip */ }
|
|
678
762
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
763
|
+
action = kernelAction(runtime, this.pendingObservations, {
|
|
764
|
+
kind: "tool_results",
|
|
765
|
+
results: toolResults.map(toolResultToKernel),
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
else if (action.kind === "evaluate_milestone") {
|
|
769
|
+
const milestonePolicy = this.opts.milestonePolicy ?? "require_verifier";
|
|
770
|
+
if (milestonePolicy === "auto_pass") {
|
|
771
|
+
action = kernelAction(runtime, this.pendingObservations, {
|
|
772
|
+
kind: "milestone_result",
|
|
773
|
+
result: milestoneCheckResultToKernel(milestoneCheckPass(action.phaseId)),
|
|
687
774
|
});
|
|
775
|
+
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
688
776
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
for (const call of allCalls) {
|
|
701
|
-
const result = toolResults.find(r => r.callId === call.id);
|
|
702
|
-
if (result) {
|
|
703
|
-
this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
|
|
777
|
+
else if (this.opts.onMilestoneEvaluate) {
|
|
778
|
+
const check = await this.opts.onMilestoneEvaluate({
|
|
779
|
+
phaseId: action.phaseId,
|
|
780
|
+
criteria: action.criteria ?? [],
|
|
781
|
+
requiredEvidence: action.requiredEvidence ?? [],
|
|
782
|
+
});
|
|
783
|
+
action = kernelAction(runtime, this.pendingObservations, {
|
|
784
|
+
kind: "milestone_result",
|
|
785
|
+
result: milestoneCheckResultToKernel(check),
|
|
786
|
+
});
|
|
787
|
+
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
704
788
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
789
|
+
else {
|
|
790
|
+
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
791
|
+
const turnsUsed = Math.max(1, runtime.turn());
|
|
792
|
+
await this.opts.sessionLog.append(sessionId, buildRunTerminalEvent({
|
|
793
|
+
reason: "milestone_pending",
|
|
794
|
+
turnsUsed,
|
|
795
|
+
totalTokens: 0,
|
|
796
|
+
}));
|
|
797
|
+
yield { type: "done", iterations: turnsUsed, totalTokens: 0, status: "milestone_pending" };
|
|
798
|
+
this.activeKernel = null;
|
|
799
|
+
this.currentSessionId = null;
|
|
800
|
+
return;
|
|
717
801
|
}
|
|
718
|
-
catch { /* skip */ }
|
|
719
802
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
results: toolResults.map(toolResultToKernel),
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
else if (action.kind === "evaluate_milestone") {
|
|
726
|
-
const milestonePolicy = this.opts.milestonePolicy ?? "require_verifier";
|
|
727
|
-
if (milestonePolicy === "auto_pass") {
|
|
728
|
-
action = kernelAction(runtime, this.pendingObservations, {
|
|
729
|
-
kind: "milestone_result",
|
|
730
|
-
result: milestoneCheckResultToKernel(milestoneCheckPass(action.phaseId)),
|
|
731
|
-
});
|
|
732
|
-
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
733
|
-
}
|
|
734
|
-
else if (this.opts.onMilestoneEvaluate) {
|
|
735
|
-
const check = await this.opts.onMilestoneEvaluate({
|
|
736
|
-
phaseId: action.phaseId,
|
|
737
|
-
criteria: action.criteria ?? [],
|
|
738
|
-
requiredEvidence: action.requiredEvidence ?? [],
|
|
739
|
-
});
|
|
740
|
-
action = kernelAction(runtime, this.pendingObservations, {
|
|
741
|
-
kind: "milestone_result",
|
|
742
|
-
result: milestoneCheckResultToKernel(check),
|
|
743
|
-
});
|
|
744
|
-
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
745
|
-
}
|
|
746
|
-
else {
|
|
747
|
-
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
748
|
-
const turnsUsed = Math.max(1, runtime.turn());
|
|
749
|
-
await this.opts.sessionLog.append(sessionId, buildRunTerminalEvent({
|
|
750
|
-
reason: "milestone_pending",
|
|
751
|
-
turnsUsed,
|
|
752
|
-
totalTokens: 0,
|
|
753
|
-
}));
|
|
754
|
-
yield { type: "done", iterations: turnsUsed, totalTokens: 0, status: "milestone_pending" };
|
|
755
|
-
this.activeKernel = null;
|
|
756
|
-
this.currentSessionId = null;
|
|
757
|
-
return;
|
|
803
|
+
else if (action.kind === "done") {
|
|
804
|
+
break;
|
|
758
805
|
}
|
|
759
806
|
}
|
|
760
|
-
|
|
761
|
-
|
|
807
|
+
}
|
|
808
|
+
catch (err) {
|
|
809
|
+
// I0b: kernel rejection (or any other thrown error inside the loop) is observable here —
|
|
810
|
+
// emit run_terminal so downstream code sees a clean end rather than mid-loop EOF.
|
|
811
|
+
const errMsg = formatToolError(err);
|
|
812
|
+
const code = err.code;
|
|
813
|
+
const isInvalidArg = code === "InvalidArg" ||
|
|
814
|
+
errMsg.toLowerCase().includes("invalidarg") ||
|
|
815
|
+
errMsg.toLowerCase().includes("invalid argument");
|
|
816
|
+
const reason = isInvalidArg ? "invalid_arg" : "error";
|
|
817
|
+
yield { type: "error", message: errMsg };
|
|
818
|
+
try {
|
|
819
|
+
await this.opts.sessionLog.append(sessionId, buildRunTerminalEvent({
|
|
820
|
+
reason,
|
|
821
|
+
turnsUsed: runtime.turn() || 0,
|
|
822
|
+
totalTokens: 0,
|
|
823
|
+
}));
|
|
762
824
|
}
|
|
825
|
+
catch { /* session log failure must not mask the original error */ }
|
|
826
|
+
yield { type: "done", iterations: runtime.turn() || 0, totalTokens: 0, status: reason };
|
|
827
|
+
this.activeKernel = null;
|
|
828
|
+
this.currentSessionId = null;
|
|
829
|
+
return;
|
|
763
830
|
}
|
|
764
831
|
const result = action.kind === "done" ? action.result : undefined;
|
|
765
|
-
|
|
832
|
+
// I0a: preserve preempt intent when loop exits without clean kernel-done (see Node runner for full rationale).
|
|
833
|
+
const status = result?.termination ?? (this.interrupted ? "user_abort" : "error");
|
|
766
834
|
const turnsUsed = result ? Math.max(1, result.turnsUsed) : runtime.turn() || 0;
|
|
767
835
|
const totalTokens = result?.totalTokensUsed ?? 0;
|
|
768
836
|
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
@@ -931,7 +999,7 @@ export class RuntimeRunner {
|
|
|
931
999
|
return ok(reducer(inputs), "completed");
|
|
932
1000
|
}
|
|
933
1001
|
catch (err) {
|
|
934
|
-
return ok(`reducer "${node.reducer}" threw: ${
|
|
1002
|
+
return ok(`reducer "${node.reducer}" threw: ${formatToolError(err)}`, "error");
|
|
935
1003
|
}
|
|
936
1004
|
}
|
|
937
1005
|
/**
|