@caupulican/pi-agent-core 0.86.11 → 0.86.17
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/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +181 -70
- package/dist/agent-loop.js.map +1 -1
- package/dist/session/session-manager.d.ts +5 -0
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +38 -0
- package/dist/session/session-manager.js.map +1 -1
- package/dist/tool-failure-memory.d.ts +10 -0
- package/dist/tool-failure-memory.d.ts.map +1 -1
- package/dist/tool-failure-memory.js +237 -70
- package/dist/tool-failure-memory.js.map +1 -1
- package/dist/tool-failure-recovery-gate.d.ts +60 -0
- package/dist/tool-failure-recovery-gate.d.ts.map +1 -0
- package/dist/tool-failure-recovery-gate.js +386 -0
- package/dist/tool-failure-recovery-gate.js.map +1 -0
- package/dist/types.d.ts +56 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/dist/agent-loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAmC7D,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EAEb,uBAAuB,EACvB,sBAAsB,EACtB,QAAQ,EACR,kBAAkB,EAClB,MAAM,YAAY,CAAC;AAIpB,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,SAAS,CACxB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAEzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAIzC;AAuBD,wBAAsB,YAAY,CACjC,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAgBzB;AAED,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzB;AAyFD;;;;GAIG;AACH,wBAAsB,gCAAgC,CAAC,OAAO,EAAE;IAC/D,gBAAgB,CAAC,EAAE,CAClB,OAAO,EAAE,uBAAuB,EAChC,MAAM,CAAC,EAAE,WAAW,KAChB,sBAAsB,GAAG,SAAS,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAAC;IACtF,KAAK,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,EAAE,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmB9B;AAqLD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CA8CxC;AA+hCD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAO7F"}
|
package/dist/agent-loop.js
CHANGED
|
@@ -6,7 +6,8 @@ import { EventStream } from "@caupulican/pi-ai/event-stream";
|
|
|
6
6
|
import { streamSimple } from "@caupulican/pi-ai/stream";
|
|
7
7
|
import { formatToolRepairStandingRule, REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE, } from "@caupulican/pi-ai/tool-repair-registry";
|
|
8
8
|
import { ToolArgumentValidationError, validateToolArguments, } from "@caupulican/pi-ai/validation";
|
|
9
|
-
import { assessToolFailure, clearToolFailure, createToolFailureMemoryTracker, createToolFailureResult, normalizeToolSignature, rememberToolFailure, sanitizeToolFailureContext, toolFailureCorrection, } from "./tool-failure-memory.js";
|
|
9
|
+
import { assessToolFailure, clearToolFailure, createRepeatedToolFailureResult, createToolFailureMemoryTracker, createToolFailureRecoveryExhaustedResult, createToolFailureResult, getUnresolvedToolFailure, normalizeToolSignature, rememberToolFailure, sanitizeToolFailureContext, toolFailureCorrection, } from "./tool-failure-memory.js";
|
|
10
|
+
import { TOOL_FAILURE_RECOVERY_ACCOUNTING_WAVE_SIZE, ToolFailureRecoveryGate, } from "./tool-failure-recovery-gate.js";
|
|
10
11
|
import { DEFAULT_MAX_STALL_TURNS } from "./types.js";
|
|
11
12
|
import { createEmptyUsage } from "./usage.js";
|
|
12
13
|
/**
|
|
@@ -189,6 +190,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
189
190
|
const validationFailureTracker = { repeats: 0 };
|
|
190
191
|
const repairTeachTracker = new Map();
|
|
191
192
|
let toolFailureMemory = createToolFailureMemoryTracker(currentContext.messages);
|
|
193
|
+
const toolFailureRecoveryGate = new ToolFailureRecoveryGate();
|
|
192
194
|
let lastSuccessfulTextProtocolBatch;
|
|
193
195
|
// Check for steering messages at start (user may have typed while waiting)
|
|
194
196
|
let pendingMessages = (await config.getSteeringMessages?.()) || [];
|
|
@@ -233,7 +235,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
233
235
|
repeatsSuccessfulTextProtocolBatch(incomingBatchSignature, toolCalls, lastSuccessfulTextProtocolBatch)
|
|
234
236
|
? lastSuccessfulTextProtocolBatch?.messages
|
|
235
237
|
: undefined;
|
|
236
|
-
const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
|
|
238
|
+
const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, previousSuccessfulTextProtocolResults, signal, emit);
|
|
237
239
|
toolResults.push(...executedToolBatch.messages);
|
|
238
240
|
hasMoreToolCalls = !executedToolBatch.terminate;
|
|
239
241
|
for (const result of toolResults) {
|
|
@@ -426,82 +428,131 @@ async function streamAssistantResponse(context, config, signal, emit, streamFn)
|
|
|
426
428
|
/**
|
|
427
429
|
* Execute tool calls from an assistant message.
|
|
428
430
|
*/
|
|
429
|
-
async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
|
|
431
|
+
async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, previousSuccessfulTextProtocolResults, signal, emit) {
|
|
430
432
|
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
|
431
433
|
const hasSequentialToolCall = toolCalls.some((tc) => currentContext.tools?.find((t) => t.name === tc.name)?.executionMode === "sequential");
|
|
434
|
+
const execCtx = {
|
|
435
|
+
context: currentContext,
|
|
436
|
+
assistantMessage,
|
|
437
|
+
config,
|
|
438
|
+
validationFailureTracker,
|
|
439
|
+
repairTeachTracker,
|
|
440
|
+
toolFailureMemory,
|
|
441
|
+
toolFailureRecoveryGate,
|
|
442
|
+
previousSuccessfulResults: previousSuccessfulTextProtocolResults,
|
|
443
|
+
signal,
|
|
444
|
+
emit,
|
|
445
|
+
};
|
|
432
446
|
if (config.toolExecution === "sequential" || hasSequentialToolCall) {
|
|
433
|
-
return executeToolCallsSequential(
|
|
447
|
+
return executeToolCallsSequential(execCtx, toolCalls);
|
|
434
448
|
}
|
|
435
|
-
return executeToolCallsParallel(
|
|
449
|
+
return executeToolCallsParallel(execCtx, toolCalls);
|
|
436
450
|
}
|
|
437
|
-
async function prepareAndStartToolCall(
|
|
438
|
-
const preparation =
|
|
439
|
-
? createRepeatedSuccessfulToolCallOutcome(
|
|
440
|
-
: await prepareToolCall(
|
|
441
|
-
await emitToolExecutionStart(toolCall, emit);
|
|
451
|
+
async function prepareAndStartToolCall(execCtx, toolCall, index) {
|
|
452
|
+
const preparation = execCtx.previousSuccessfulResults
|
|
453
|
+
? createRepeatedSuccessfulToolCallOutcome(execCtx.previousSuccessfulResults[index])
|
|
454
|
+
: await prepareToolCall(execCtx.context, execCtx.assistantMessage, toolCall, execCtx.config, execCtx.validationFailureTracker, execCtx.toolFailureMemory, execCtx.toolFailureRecoveryGate, execCtx.signal);
|
|
455
|
+
await emitToolExecutionStart(toolCall, execCtx.emit);
|
|
442
456
|
if (preparation.kind === "immediate") {
|
|
443
|
-
emitToolArgumentValidationTelemetry(config, preparation.validationEvent, "not_run", "none");
|
|
444
|
-
return {
|
|
457
|
+
emitToolArgumentValidationTelemetry(execCtx.config, preparation.validationEvent, "not_run", "none");
|
|
458
|
+
return {
|
|
459
|
+
kind: "finalized",
|
|
460
|
+
finalized: finalizeRejectedToolCall(toolCall, preparation, execCtx.toolFailureMemory, execCtx.toolFailureRecoveryGate),
|
|
461
|
+
};
|
|
445
462
|
}
|
|
446
463
|
return { kind: "prepared", preparation };
|
|
447
464
|
}
|
|
448
|
-
async function
|
|
465
|
+
async function finalizeStartedToolCall(execCtx, started) {
|
|
466
|
+
if (started.kind === "finalized")
|
|
467
|
+
return started.finalized;
|
|
468
|
+
return executeAndFinalizePreparedToolCall(execCtx.context, execCtx.assistantMessage, started.preparation, execCtx.config, execCtx.repairTeachTracker, execCtx.toolFailureMemory, execCtx.toolFailureRecoveryGate, execCtx.signal, execCtx.emit);
|
|
469
|
+
}
|
|
470
|
+
async function executeToolCallsSequential(execCtx, toolCalls) {
|
|
449
471
|
const finalizedCalls = [];
|
|
450
472
|
const messages = [];
|
|
451
473
|
for (const [index, toolCall] of toolCalls.entries()) {
|
|
452
|
-
const started = await prepareAndStartToolCall(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
finalized = started.finalized;
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, started.preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
459
|
-
}
|
|
460
|
-
await emitToolExecutionEnd(finalized, emit);
|
|
474
|
+
const started = await prepareAndStartToolCall(execCtx, toolCall, index);
|
|
475
|
+
const finalized = applyToolFailureRecoveryEffect(execCtx.toolFailureRecoveryGate, await finalizeStartedToolCall(execCtx, started));
|
|
476
|
+
await emitToolExecutionEnd(finalized, execCtx.emit);
|
|
461
477
|
const toolResultMessage = createToolResultMessage(finalized);
|
|
462
|
-
await emitToolResultMessage(toolResultMessage, emit);
|
|
478
|
+
await emitToolResultMessage(toolResultMessage, execCtx.emit);
|
|
463
479
|
finalizedCalls.push(finalized);
|
|
464
480
|
messages.push(toolResultMessage);
|
|
465
|
-
if (signal?.aborted) {
|
|
481
|
+
if (execCtx.signal?.aborted) {
|
|
466
482
|
break;
|
|
467
483
|
}
|
|
468
484
|
}
|
|
469
485
|
return {
|
|
470
486
|
messages,
|
|
471
|
-
terminate: shouldTerminateToolBatch(finalizedCalls),
|
|
487
|
+
terminate: execCtx.toolFailureRecoveryGate.isHalted() || shouldTerminateToolBatch(finalizedCalls),
|
|
472
488
|
};
|
|
473
489
|
}
|
|
474
|
-
async function executeToolCallsParallel(
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
490
|
+
async function executeToolCallsParallel(execCtx, toolCalls) {
|
|
491
|
+
const orderedFinalizedCalls = [];
|
|
492
|
+
let nextIndex = 0;
|
|
493
|
+
// Account each bounded concurrent wave before launching more calls. Otherwise a single
|
|
494
|
+
// assistant batch could start an unbounded number of failures before the circuit observes one.
|
|
495
|
+
while (nextIndex < toolCalls.length && !execCtx.signal?.aborted) {
|
|
496
|
+
const waveEnd = Math.min(nextIndex + TOOL_FAILURE_RECOVERY_ACCOUNTING_WAVE_SIZE, toolCalls.length);
|
|
497
|
+
const wave = [];
|
|
498
|
+
const completionOrder = [];
|
|
499
|
+
for (; nextIndex < waveEnd; nextIndex++) {
|
|
500
|
+
const toolCall = toolCalls[nextIndex];
|
|
501
|
+
const started = await prepareAndStartToolCall(execCtx, toolCall, nextIndex);
|
|
502
|
+
if (started.kind === "finalized") {
|
|
503
|
+
await emitToolExecutionEnd(started.finalized, execCtx.emit);
|
|
504
|
+
wave.push(started.finalized);
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
wave.push(async () => {
|
|
508
|
+
const finalized = await finalizeStartedToolCall(execCtx, started);
|
|
509
|
+
completionOrder.push(finalized);
|
|
510
|
+
return finalized;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (execCtx.signal?.aborted) {
|
|
514
|
+
nextIndex++;
|
|
482
515
|
break;
|
|
483
516
|
}
|
|
484
|
-
continue;
|
|
485
517
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
518
|
+
const unappliedWave = await Promise.all(wave.map((entry) => (typeof entry === "function" ? entry() : Promise.resolve(entry))));
|
|
519
|
+
const appliedByOriginal = new Map();
|
|
520
|
+
const finalizedWave = unappliedWave.map((finalized) => {
|
|
521
|
+
const applied = applyToolFailureRecoveryEffect(execCtx.toolFailureRecoveryGate, finalized);
|
|
522
|
+
appliedByOriginal.set(finalized, applied);
|
|
523
|
+
return applied;
|
|
490
524
|
});
|
|
491
|
-
|
|
492
|
-
|
|
525
|
+
for (const finalized of completionOrder) {
|
|
526
|
+
await emitToolExecutionEnd(appliedByOriginal.get(finalized) ?? finalized, execCtx.emit);
|
|
493
527
|
}
|
|
528
|
+
orderedFinalizedCalls.push(...finalizedWave);
|
|
494
529
|
}
|
|
495
|
-
const orderedFinalizedCalls = await Promise.all(finalizedCalls.map((entry) => (typeof entry === "function" ? entry() : Promise.resolve(entry))));
|
|
496
530
|
const messages = [];
|
|
497
531
|
for (const finalized of orderedFinalizedCalls) {
|
|
498
532
|
const toolResultMessage = createToolResultMessage(finalized);
|
|
499
|
-
await emitToolResultMessage(toolResultMessage, emit);
|
|
533
|
+
await emitToolResultMessage(toolResultMessage, execCtx.emit);
|
|
500
534
|
messages.push(toolResultMessage);
|
|
501
535
|
}
|
|
502
536
|
return {
|
|
503
537
|
messages,
|
|
504
|
-
terminate: shouldTerminateToolBatch(orderedFinalizedCalls),
|
|
538
|
+
terminate: execCtx.toolFailureRecoveryGate.isHalted() || shouldTerminateToolBatch(orderedFinalizedCalls),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function applyToolFailureRecoveryEffect(gate, finalized) {
|
|
542
|
+
const halt = gate.apply(finalized.executionGateEffect);
|
|
543
|
+
if (!halt)
|
|
544
|
+
return finalized;
|
|
545
|
+
return createRecoveryExhaustedToolCallOutcome(finalized, halt);
|
|
546
|
+
}
|
|
547
|
+
function createRecoveryExhaustedToolCallOutcome(finalized, halt) {
|
|
548
|
+
const exhaustedResult = createToolFailureRecoveryExhaustedResult(halt.record, halt.diagnostic);
|
|
549
|
+
return {
|
|
550
|
+
...finalized,
|
|
551
|
+
result: {
|
|
552
|
+
...exhaustedResult,
|
|
553
|
+
...(finalized.result.usage ? { usage: finalized.result.usage } : {}),
|
|
554
|
+
},
|
|
555
|
+
isError: true,
|
|
505
556
|
};
|
|
506
557
|
}
|
|
507
558
|
const DEFAULT_TOOL_VALIDATION_ESCALATION_THRESHOLD = 3;
|
|
@@ -527,6 +578,18 @@ function createRepeatedSuccessfulToolCallOutcome(previousResult) {
|
|
|
527
578
|
...(previousResult ? { repeatedSuccessfulCall: { previousToolCallId: previousResult.toolCallId } } : {}),
|
|
528
579
|
};
|
|
529
580
|
}
|
|
581
|
+
function createAbortedToolCallOutcome(validationEvent, executionGateReservation) {
|
|
582
|
+
return {
|
|
583
|
+
kind: "immediate",
|
|
584
|
+
result: createErrorToolResult("Operation aborted"),
|
|
585
|
+
isError: true,
|
|
586
|
+
phase: "cancelled",
|
|
587
|
+
failureCode: "aborted",
|
|
588
|
+
correction: "Retry only if the operation is still required.",
|
|
589
|
+
...(executionGateReservation ? { executionGateReservation } : {}),
|
|
590
|
+
validationEvent,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
530
593
|
function shouldTerminateToolBatch(finalizedCalls) {
|
|
531
594
|
return finalizedCalls.length > 0 && finalizedCalls.every((finalized) => finalized.result.terminate === true);
|
|
532
595
|
}
|
|
@@ -621,7 +684,7 @@ function handleValidationFailure(error, config, tracker) {
|
|
|
621
684
|
});
|
|
622
685
|
return `${error.message}\n\nRepeated validation failure (${tracker.repeats} identical attempts). Use this full schema and example before retrying:\n${error.enrichment}`;
|
|
623
686
|
}
|
|
624
|
-
async function prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, signal) {
|
|
687
|
+
async function prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, toolFailureMemory, toolFailureRecoveryGate, signal) {
|
|
625
688
|
const tool = currentContext.tools?.find((candidate) => candidate.name === toolCall.name);
|
|
626
689
|
if (!tool) {
|
|
627
690
|
return {
|
|
@@ -646,6 +709,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
646
709
|
};
|
|
647
710
|
}
|
|
648
711
|
let validationEvent;
|
|
712
|
+
let executionGateReservation;
|
|
649
713
|
try {
|
|
650
714
|
const preparedToolCall = prepareToolCallArguments(tool, toolCall);
|
|
651
715
|
const validatedArgs = validateToolArguments(tool, preparedToolCall, {
|
|
@@ -664,6 +728,28 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
664
728
|
toolCall.rawArguments ??= toolCall.arguments;
|
|
665
729
|
toolCall.arguments = validatedArgs;
|
|
666
730
|
}
|
|
731
|
+
const unresolvedRecord = getUnresolvedToolFailure(toolFailureMemory, toolCall.name, validatedArgs);
|
|
732
|
+
const admission = toolFailureRecoveryGate.admit(tool, validatedArgs, unresolvedRecord);
|
|
733
|
+
if (admission.kind === "blocked") {
|
|
734
|
+
const failureCode = admission.exhausted ? "recovery_exhausted" : "repeated_failed_operation";
|
|
735
|
+
const result = admission.exhausted
|
|
736
|
+
? createToolFailureRecoveryExhaustedResult(admission.record, admission.diagnostic ?? "Tool failure recovery budget exhausted.")
|
|
737
|
+
: createRepeatedToolFailureResult(admission.record);
|
|
738
|
+
const memoryRecord = result.details.piToolFailureMemory;
|
|
739
|
+
toolFailureMemory.set(admission.record.failureKey, memoryRecord);
|
|
740
|
+
return {
|
|
741
|
+
kind: "immediate",
|
|
742
|
+
result,
|
|
743
|
+
isError: true,
|
|
744
|
+
phase: admission.record.phase,
|
|
745
|
+
failureCode,
|
|
746
|
+
correction: memoryRecord.correction,
|
|
747
|
+
diagnostic: memoryRecord.diagnostic,
|
|
748
|
+
repeatedToolFailure: true,
|
|
749
|
+
validationEvent: createValidationBounceTelemetry(config, toolCall, failureCode),
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
executionGateReservation = admission.reservation;
|
|
667
753
|
if (config.beforeToolCall) {
|
|
668
754
|
const beforeResult = await config.beforeToolCall({
|
|
669
755
|
assistantMessage,
|
|
@@ -672,15 +758,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
672
758
|
context: currentContext,
|
|
673
759
|
}, signal);
|
|
674
760
|
if (signal?.aborted) {
|
|
675
|
-
return
|
|
676
|
-
kind: "immediate",
|
|
677
|
-
result: createErrorToolResult("Operation aborted"),
|
|
678
|
-
isError: true,
|
|
679
|
-
phase: "cancelled",
|
|
680
|
-
failureCode: "aborted",
|
|
681
|
-
correction: "Retry only if the operation is still required.",
|
|
682
|
-
validationEvent,
|
|
683
|
-
};
|
|
761
|
+
return createAbortedToolCallOutcome(validationEvent, executionGateReservation);
|
|
684
762
|
}
|
|
685
763
|
if (beforeResult?.block) {
|
|
686
764
|
const reason = beforeResult.reason || "Tool execution was blocked";
|
|
@@ -692,26 +770,20 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
692
770
|
failureCode: "blocked",
|
|
693
771
|
correction: "Choose an allowed approach or request the required authority before retrying.",
|
|
694
772
|
diagnostic: reason,
|
|
773
|
+
...(executionGateReservation ? { executionGateReservation } : {}),
|
|
695
774
|
validationEvent,
|
|
696
775
|
};
|
|
697
776
|
}
|
|
698
777
|
}
|
|
699
778
|
if (signal?.aborted) {
|
|
700
|
-
return
|
|
701
|
-
kind: "immediate",
|
|
702
|
-
result: createErrorToolResult("Operation aborted"),
|
|
703
|
-
isError: true,
|
|
704
|
-
phase: "cancelled",
|
|
705
|
-
failureCode: "aborted",
|
|
706
|
-
correction: "Retry only if the operation is still required.",
|
|
707
|
-
validationEvent,
|
|
708
|
-
};
|
|
779
|
+
return createAbortedToolCallOutcome(validationEvent, executionGateReservation);
|
|
709
780
|
}
|
|
710
781
|
return {
|
|
711
782
|
kind: "prepared",
|
|
712
783
|
toolCall,
|
|
713
784
|
tool,
|
|
714
785
|
args: validatedArgs,
|
|
786
|
+
...(executionGateReservation ? { executionGateReservation } : {}),
|
|
715
787
|
validationEvent,
|
|
716
788
|
};
|
|
717
789
|
}
|
|
@@ -731,13 +803,29 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
731
803
|
? validationFailureCorrection(validationEvent, toolCall.name)
|
|
732
804
|
: toolFailureCorrection(message, "rejected", "preflight"),
|
|
733
805
|
diagnostic: isToolArgumentValidationError(error) ? undefined : message,
|
|
806
|
+
...(executionGateReservation ? { executionGateReservation } : {}),
|
|
734
807
|
validationEvent,
|
|
735
808
|
};
|
|
736
809
|
}
|
|
737
810
|
}
|
|
738
|
-
function finalizeRejectedToolCall(toolCall, outcome, tracker) {
|
|
811
|
+
function finalizeRejectedToolCall(toolCall, outcome, tracker, toolFailureRecoveryGate) {
|
|
812
|
+
if (outcome.repeatedToolFailure) {
|
|
813
|
+
return {
|
|
814
|
+
toolCall,
|
|
815
|
+
result: outcome.result,
|
|
816
|
+
isError: true,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
739
819
|
const record = rememberToolFailure(tracker, toolCall.name, toolCall.arguments, "rejected", outcome.failureCode, outcome.correction, outcome.diagnostic, outcome.phase);
|
|
740
|
-
const
|
|
820
|
+
const halt = toolFailureRecoveryGate.apply({
|
|
821
|
+
kind: "failure",
|
|
822
|
+
record,
|
|
823
|
+
args: toolCall.arguments,
|
|
824
|
+
...(outcome.executionGateReservation ? { reservation: outcome.executionGateReservation } : {}),
|
|
825
|
+
});
|
|
826
|
+
const failureResult = halt
|
|
827
|
+
? createToolFailureRecoveryExhaustedResult(halt.record, halt.diagnostic)
|
|
828
|
+
: createToolFailureResult(record, outcome.result.terminate);
|
|
741
829
|
return {
|
|
742
830
|
toolCall,
|
|
743
831
|
result: outcome.repeatedSuccessfulCall
|
|
@@ -780,12 +868,12 @@ function getBackgroundToolCallDelay(config) {
|
|
|
780
868
|
return undefined;
|
|
781
869
|
return delay;
|
|
782
870
|
}
|
|
783
|
-
async function executeAndFinalizePreparedToolCall(currentContext, assistantMessage, prepared, config, repairTeachTracker, toolFailureMemory, foregroundSignal, emit) {
|
|
871
|
+
async function executeAndFinalizePreparedToolCall(currentContext, assistantMessage, prepared, config, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, foregroundSignal, emit) {
|
|
784
872
|
const backgroundDelay = getBackgroundToolCallDelay(config);
|
|
785
873
|
if (!config.handoffToolCall ||
|
|
786
874
|
(backgroundDelay === undefined && config.subscribeToolCallHandoffRequest === undefined)) {
|
|
787
875
|
const executed = await executePreparedToolCall(prepared, foregroundSignal, emit);
|
|
788
|
-
return finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, repairTeachTracker, toolFailureMemory, foregroundSignal);
|
|
876
|
+
return finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, foregroundSignal);
|
|
789
877
|
}
|
|
790
878
|
const executionAbort = createLinkedToolAbort(foregroundSignal);
|
|
791
879
|
const startedAt = Date.now();
|
|
@@ -795,7 +883,8 @@ async function executeAndFinalizePreparedToolCall(currentContext, assistantMessa
|
|
|
795
883
|
if (emitForegroundUpdates)
|
|
796
884
|
return emit(event);
|
|
797
885
|
});
|
|
798
|
-
|
|
886
|
+
const finalized = await finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, executionAbort.signal);
|
|
887
|
+
return finalized;
|
|
799
888
|
})();
|
|
800
889
|
completion.then(executionAbort.detachForeground, executionAbort.detachForeground);
|
|
801
890
|
let deadlineTimer;
|
|
@@ -831,6 +920,9 @@ async function executeAndFinalizePreparedToolCall(currentContext, assistantMessa
|
|
|
831
920
|
return outcome.value;
|
|
832
921
|
if (executionAbort.signal.aborted)
|
|
833
922
|
return completion;
|
|
923
|
+
let handoffAccepted = false;
|
|
924
|
+
const handedOffCompletion = completion.then((finalized) => handoffAccepted ? applyToolFailureRecoveryEffect(toolFailureRecoveryGate, finalized) : finalized);
|
|
925
|
+
void handedOffCompletion.catch(() => undefined);
|
|
834
926
|
let handoff;
|
|
835
927
|
try {
|
|
836
928
|
handoff = config.handoffToolCall({
|
|
@@ -839,13 +931,14 @@ async function executeAndFinalizePreparedToolCall(currentContext, assistantMessa
|
|
|
839
931
|
args: prepared.args,
|
|
840
932
|
context: currentContext,
|
|
841
933
|
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
842
|
-
completion,
|
|
934
|
+
completion: handedOffCompletion,
|
|
843
935
|
cancel: executionAbort.cancel,
|
|
844
936
|
});
|
|
845
937
|
}
|
|
846
938
|
catch {
|
|
847
939
|
return completion;
|
|
848
940
|
}
|
|
941
|
+
handoffAccepted = handoff !== undefined;
|
|
849
942
|
if (!handoff)
|
|
850
943
|
return completion;
|
|
851
944
|
emitForegroundUpdates = false;
|
|
@@ -913,11 +1006,12 @@ function appendRepairTeachNotes(result, toolCall, tracker, config) {
|
|
|
913
1006
|
taught: true,
|
|
914
1007
|
};
|
|
915
1008
|
}
|
|
916
|
-
async function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, repairTeachTracker, toolFailureMemory, signal) {
|
|
1009
|
+
async function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, signal) {
|
|
917
1010
|
let result = executed.result;
|
|
918
1011
|
let isError = executed.isError;
|
|
919
1012
|
let failureMessage = executed.failureMessage ?? "";
|
|
920
1013
|
let errorClass = executed.errorClass;
|
|
1014
|
+
let executionGateEffect;
|
|
921
1015
|
if (config.afterToolCall) {
|
|
922
1016
|
try {
|
|
923
1017
|
const afterResult = await config.afterToolCall({
|
|
@@ -949,11 +1043,27 @@ async function finalizeExecutedToolCall(currentContext, assistantMessage, prepar
|
|
|
949
1043
|
const usage = result.usage;
|
|
950
1044
|
const effectiveFailureMessage = failureMessage || result.content.find((block) => block.type === "text")?.text || "Tool execution failed";
|
|
951
1045
|
const assessment = assessToolFailure(effectiveFailureMessage, "failed", errorClass);
|
|
952
|
-
const
|
|
1046
|
+
const recoveryPlan = toolFailureRecoveryGate.planFailure(prepared.tool, prepared.args, assessment.failureCode, currentContext.tools ?? [], prepared.executionGateReservation);
|
|
1047
|
+
const record = rememberToolFailure(toolFailureMemory, prepared.toolCall.name, prepared.args, "failed", assessment.failureCode, recoveryPlan.guidance, assessment.diagnostic, assessment.phase);
|
|
1048
|
+
executionGateEffect = {
|
|
1049
|
+
kind: "failure",
|
|
1050
|
+
record,
|
|
1051
|
+
args: prepared.args,
|
|
1052
|
+
targets: recoveryPlan.targets,
|
|
1053
|
+
...(prepared.executionGateReservation ? { reservation: prepared.executionGateReservation } : {}),
|
|
1054
|
+
};
|
|
953
1055
|
result = { ...createToolFailureResult(record, result.terminate), usage };
|
|
954
1056
|
}
|
|
955
1057
|
else {
|
|
956
1058
|
clearToolFailure(toolFailureMemory, prepared.toolCall.name, prepared.args);
|
|
1059
|
+
if (!executed.isError) {
|
|
1060
|
+
executionGateEffect = {
|
|
1061
|
+
kind: "success",
|
|
1062
|
+
tool: prepared.tool,
|
|
1063
|
+
args: prepared.args,
|
|
1064
|
+
evidenceResult: executed.result,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
957
1067
|
}
|
|
958
1068
|
const repaired = isError
|
|
959
1069
|
? { result, taught: false }
|
|
@@ -963,6 +1073,7 @@ async function finalizeExecutedToolCall(currentContext, assistantMessage, prepar
|
|
|
963
1073
|
toolCall: prepared.toolCall,
|
|
964
1074
|
result: repaired.result,
|
|
965
1075
|
isError,
|
|
1076
|
+
executionGateEffect,
|
|
966
1077
|
};
|
|
967
1078
|
}
|
|
968
1079
|
function createErrorToolResult(message) {
|