@caupulican/pi-agent-core 0.85.0 → 0.85.4

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.
@@ -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;AAqB7D,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EAGb,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;AAyCD;;;;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;AA2JD;;;;;;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;AAu2BD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAO7F"}
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;AAyB7D,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EAGb,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;AAmLD;;;;;;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;AA47BD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAO7F"}
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { EventStream } from "@caupulican/pi-ai/event-stream";
6
6
  import { streamSimple } from "@caupulican/pi-ai/stream";
7
- import { formatToolRepairStandingRule } from "@caupulican/pi-ai/tool-repair-registry";
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
9
  import { assessToolFailure, clearToolFailure, createToolFailureMemoryTracker, createToolFailureResult, normalizeToolSignature, rememberToolFailure, sanitizeToolFailureContext, toolFailureCorrection, } from "./tool-failure-memory.js";
10
10
  import { DEFAULT_MAX_STALL_TURNS } from "./types.js";
@@ -98,6 +98,51 @@ function createAgentStream() {
98
98
  * enough that it's indistinguishable from legitimate varied work, so we don't chase it.
99
99
  */
100
100
  const STALL_WINDOW_PERIODS = 4;
101
+ function textProtocolOperationArguments(toolCall) {
102
+ const args = toolCall.arguments;
103
+ if (!args || typeof args !== "object" || Array.isArray(args))
104
+ return args ?? null;
105
+ const record = args;
106
+ if ((toolCall.name === "write" || toolCall.name === "edit") && typeof record.path === "string") {
107
+ return { path: record.path };
108
+ }
109
+ return args;
110
+ }
111
+ function textProtocolBatchSignature(toolCalls) {
112
+ return normalizeToolSignature(toolCalls.map((toolCall) => [toolCall.name, textProtocolOperationArguments(toolCall)]));
113
+ }
114
+ function toolCallRecord(toolCall) {
115
+ const args = toolCall.arguments;
116
+ return args && typeof args === "object" && !Array.isArray(args) ? args : undefined;
117
+ }
118
+ function toolResultPhase(result) {
119
+ if (!result.details || typeof result.details !== "object" || Array.isArray(result.details))
120
+ return undefined;
121
+ const phase = result.details.phase;
122
+ return typeof phase === "string" ? phase : undefined;
123
+ }
124
+ function repeatsSuccessfulTextProtocolBatch(incomingSignature, toolCalls, previous) {
125
+ if (!previous)
126
+ return false;
127
+ if (incomingSignature === previous.signature)
128
+ return true;
129
+ if (toolCalls.length !== previous.calls.length || toolCalls.length !== previous.messages.length)
130
+ return false;
131
+ return toolCalls.every((toolCall, index) => {
132
+ const previousCall = previous.calls[index];
133
+ const previousResult = previous.messages[index];
134
+ if (!previousCall || !previousResult)
135
+ return false;
136
+ const currentArgs = toolCallRecord(toolCall);
137
+ const previousArgs = toolCallRecord(previousCall);
138
+ if (currentArgs?.path !== previousArgs?.path)
139
+ return false;
140
+ const phase = toolResultPhase(previousResult);
141
+ if (phase === "written")
142
+ return toolCall.name === "write" && previousCall.name === "write";
143
+ return phase === "edited" && toolCall.name === "edit" && previousCall.name === "edit";
144
+ });
145
+ }
101
146
  /**
102
147
  * Apply one request-local preflight without mutating persistent loop configuration.
103
148
  * Shared with isolated tool-free provider calls so every transport boundary has identical
@@ -144,6 +189,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
144
189
  const validationFailureTracker = { repeats: 0 };
145
190
  const repairTeachTracker = new Map();
146
191
  let toolFailureMemory = createToolFailureMemoryTracker(currentContext.messages);
192
+ let lastSuccessfulTextProtocolBatch;
147
193
  // Check for steering messages at start (user may have typed while waiting)
148
194
  let pendingMessages = (await config.getSteeringMessages?.()) || [];
149
195
  // Outer loop: continues when queued follow-up messages arrive after agent would stop
@@ -159,6 +205,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
159
205
  }
160
206
  // Process pending messages (inject before next assistant response)
161
207
  if (pendingMessages.length > 0) {
208
+ lastSuccessfulTextProtocolBatch = undefined;
162
209
  for (const message of pendingMessages) {
163
210
  await emit({ type: "message_start", message });
164
211
  await emit({ type: "message_end", message });
@@ -180,13 +227,34 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
180
227
  const toolResults = [];
181
228
  hasMoreToolCalls = false;
182
229
  if (toolCalls.length > 0) {
183
- const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
230
+ const textProtocolBatch = toolCalls.every((toolCall) => toolCall.source === "text-protocol");
231
+ const incomingBatchSignature = textProtocolBatchSignature(toolCalls);
232
+ const previousSuccessfulTextProtocolResults = textProtocolBatch &&
233
+ repeatsSuccessfulTextProtocolBatch(incomingBatchSignature, toolCalls, lastSuccessfulTextProtocolBatch)
234
+ ? lastSuccessfulTextProtocolBatch?.messages
235
+ : undefined;
236
+ const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
184
237
  toolResults.push(...executedToolBatch.messages);
185
238
  hasMoreToolCalls = !executedToolBatch.terminate;
186
239
  for (const result of toolResults) {
187
240
  currentContext.messages.push(result);
188
241
  newMessages.push(result);
189
242
  }
243
+ if (!previousSuccessfulTextProtocolResults) {
244
+ lastSuccessfulTextProtocolBatch =
245
+ textProtocolBatch &&
246
+ toolResults.length === toolCalls.length &&
247
+ toolResults.every((result) => !result.isError)
248
+ ? {
249
+ signature: textProtocolBatchSignature(toolCalls),
250
+ messages: toolResults,
251
+ calls: toolCalls,
252
+ }
253
+ : undefined;
254
+ }
255
+ }
256
+ else {
257
+ lastSuccessfulTextProtocolBatch = undefined;
190
258
  }
191
259
  await emit({ type: "turn_end", message, toolResults });
192
260
  // Runaway-loop backstop (cost guard): detect a model stuck repeating one action.
@@ -358,27 +426,36 @@ async function streamAssistantResponse(context, config, signal, emit, streamFn)
358
426
  /**
359
427
  * Execute tool calls from an assistant message.
360
428
  */
361
- async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
429
+ async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
362
430
  const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
363
431
  const hasSequentialToolCall = toolCalls.some((tc) => currentContext.tools?.find((t) => t.name === tc.name)?.executionMode === "sequential");
364
432
  if (config.toolExecution === "sequential" || hasSequentialToolCall) {
365
- return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
433
+ return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
434
+ }
435
+ return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
436
+ }
437
+ async function prepareAndStartToolCall(currentContext, assistantMessage, toolCall, index, config, validationFailureTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
438
+ const preparation = previousSuccessfulTextProtocolResults
439
+ ? createRepeatedSuccessfulToolCallOutcome(previousSuccessfulTextProtocolResults[index])
440
+ : await prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, signal);
441
+ await emitToolExecutionStart(toolCall, emit);
442
+ if (preparation.kind === "immediate") {
443
+ emitToolArgumentValidationTelemetry(config, preparation.validationEvent, "not_run", "none");
444
+ return { kind: "finalized", finalized: finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory) };
366
445
  }
367
- return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
446
+ return { kind: "prepared", preparation };
368
447
  }
369
- async function executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
448
+ async function executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
370
449
  const finalizedCalls = [];
371
450
  const messages = [];
372
- for (const toolCall of toolCalls) {
373
- const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, signal);
374
- await emitToolExecutionStart(toolCall, emit);
451
+ for (const [index, toolCall] of toolCalls.entries()) {
452
+ const started = await prepareAndStartToolCall(currentContext, assistantMessage, toolCall, index, config, validationFailureTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
375
453
  let finalized;
376
- if (preparation.kind === "immediate") {
377
- emitToolArgumentValidationTelemetry(config, preparation.validationEvent, "not_run", "none");
378
- finalized = finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory);
454
+ if (started.kind === "finalized") {
455
+ finalized = started.finalized;
379
456
  }
380
457
  else {
381
- finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
458
+ finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, started.preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
382
459
  }
383
460
  await emitToolExecutionEnd(finalized, emit);
384
461
  const toolResultMessage = createToolResultMessage(finalized);
@@ -394,23 +471,20 @@ async function executeToolCallsSequential(currentContext, assistantMessage, tool
394
471
  terminate: shouldTerminateToolBatch(finalizedCalls),
395
472
  };
396
473
  }
397
- async function executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
474
+ async function executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
398
475
  const finalizedCalls = [];
399
- for (const toolCall of toolCalls) {
400
- const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, signal);
401
- await emitToolExecutionStart(toolCall, emit);
402
- if (preparation.kind === "immediate") {
403
- emitToolArgumentValidationTelemetry(config, preparation.validationEvent, "not_run", "none");
404
- const finalized = finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory);
405
- await emitToolExecutionEnd(finalized, emit);
406
- finalizedCalls.push(finalized);
476
+ for (const [index, toolCall] of toolCalls.entries()) {
477
+ const started = await prepareAndStartToolCall(currentContext, assistantMessage, toolCall, index, config, validationFailureTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
478
+ if (started.kind === "finalized") {
479
+ await emitToolExecutionEnd(started.finalized, emit);
480
+ finalizedCalls.push(started.finalized);
407
481
  if (signal?.aborted) {
408
482
  break;
409
483
  }
410
484
  continue;
411
485
  }
412
486
  finalizedCalls.push(async () => {
413
- const finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
487
+ const finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, started.preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
414
488
  await emitToolExecutionEnd(finalized, emit);
415
489
  return finalized;
416
490
  });
@@ -432,6 +506,27 @@ async function executeToolCallsParallel(currentContext, assistantMessage, toolCa
432
506
  }
433
507
  const DEFAULT_TOOL_VALIDATION_ESCALATION_THRESHOLD = 3;
434
508
  const TOOL_REPAIR_TEACH_EVERY = 5;
509
+ const REPEATED_SUCCESS_RESULT_MAX_CHARS = 2_048;
510
+ function createRepeatedSuccessfulToolCallOutcome(previousResult) {
511
+ const previousText = previousResult?.content
512
+ .filter((block) => block.type === "text")
513
+ .map((block) => block.text)
514
+ .join("\n")
515
+ .slice(0, REPEATED_SUCCESS_RESULT_MAX_CHARS);
516
+ const diagnostic = previousText
517
+ ? `${REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.diagnostic} Previous successful result: ${previousText}`
518
+ : REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.diagnostic;
519
+ return {
520
+ kind: "immediate",
521
+ result: createErrorToolResult(diagnostic),
522
+ isError: true,
523
+ phase: "execution",
524
+ failureCode: REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.failureCode,
525
+ correction: REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.guidance,
526
+ diagnostic,
527
+ ...(previousResult ? { repeatedSuccessfulCall: { previousToolCallId: previousResult.toolCallId } } : {}),
528
+ };
529
+ }
435
530
  function shouldTerminateToolBatch(finalizedCalls) {
436
531
  return finalizedCalls.length > 0 && finalizedCalls.every((finalized) => finalized.result.terminate === true);
437
532
  }
@@ -527,25 +622,27 @@ function handleValidationFailure(error, config, tracker) {
527
622
  return `${error.message}\n\nRepeated validation failure (${tracker.repeats} identical attempts). Use this full schema and example before retrying:\n${error.enrichment}`;
528
623
  }
529
624
  async function prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, signal) {
530
- if (toolCall.errorMessage) {
625
+ const tool = currentContext.tools?.find((candidate) => candidate.name === toolCall.name);
626
+ if (!tool) {
531
627
  return {
532
628
  kind: "immediate",
533
- result: createErrorToolResult(toolCall.errorMessage),
629
+ result: createErrorToolResult(`Tool ${toolCall.name} not found`),
534
630
  isError: true,
535
- failureCode: "malformed_call",
536
- correction: "Resend one complete JSON argument object matching the current tool schema.",
631
+ phase: "validation",
632
+ failureCode: "unknown_tool",
633
+ correction: "Choose a tool from the currently available tool list.",
537
634
  validationEvent: createValidationBounceTelemetry(config, toolCall, "unknown_tool"),
538
635
  };
539
636
  }
540
- const tool = currentContext.tools?.find((t) => t.name === toolCall.name);
541
- if (!tool) {
637
+ if (toolCall.errorMessage) {
542
638
  return {
543
639
  kind: "immediate",
544
- result: createErrorToolResult(`Tool ${toolCall.name} not found`),
640
+ result: createErrorToolResult(toolCall.errorMessage),
545
641
  isError: true,
546
- failureCode: "unknown_tool",
547
- correction: "Choose a tool from the currently available tool list.",
548
- validationEvent: createValidationBounceTelemetry(config, toolCall, "unknown_tool"),
642
+ phase: "validation",
643
+ failureCode: "malformed_call",
644
+ correction: "Resend one complete JSON argument object matching the current tool schema.",
645
+ validationEvent: createValidationBounceTelemetry(config, toolCall, "malformed_call"),
549
646
  };
550
647
  }
551
648
  let validationEvent;
@@ -579,6 +676,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
579
676
  kind: "immediate",
580
677
  result: createErrorToolResult("Operation aborted"),
581
678
  isError: true,
679
+ phase: "cancelled",
582
680
  failureCode: "aborted",
583
681
  correction: "Retry only if the operation is still required.",
584
682
  validationEvent,
@@ -590,6 +688,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
590
688
  kind: "immediate",
591
689
  result: createErrorToolResult(reason),
592
690
  isError: true,
691
+ phase: "policy",
593
692
  failureCode: "blocked",
594
693
  correction: "Choose an allowed approach or request the required authority before retrying.",
595
694
  diagnostic: reason,
@@ -602,6 +701,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
602
701
  kind: "immediate",
603
702
  result: createErrorToolResult("Operation aborted"),
604
703
  isError: true,
704
+ phase: "cancelled",
605
705
  failureCode: "aborted",
606
706
  correction: "Retry only if the operation is still required.",
607
707
  validationEvent,
@@ -625,19 +725,30 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
625
725
  kind: "immediate",
626
726
  result: createErrorToolResult(message),
627
727
  isError: true,
728
+ phase: isToolArgumentValidationError(error) ? "validation" : "preflight",
628
729
  failureCode: isToolArgumentValidationError(error) ? "invalid_arguments" : "preflight_error",
629
730
  correction: isToolArgumentValidationError(error)
630
731
  ? validationFailureCorrection(validationEvent, toolCall.name)
631
- : toolFailureCorrection(message, "rejected"),
732
+ : toolFailureCorrection(message, "rejected", "preflight"),
733
+ diagnostic: isToolArgumentValidationError(error) ? undefined : message,
632
734
  validationEvent,
633
735
  };
634
736
  }
635
737
  }
636
738
  function finalizeRejectedToolCall(toolCall, outcome, tracker) {
637
- const record = rememberToolFailure(tracker, toolCall.name, toolCall.arguments, "rejected", outcome.failureCode, outcome.correction, outcome.diagnostic);
739
+ const record = rememberToolFailure(tracker, toolCall.name, toolCall.arguments, "rejected", outcome.failureCode, outcome.correction, outcome.diagnostic, outcome.phase);
740
+ const failureResult = createToolFailureResult(record, outcome.result.terminate);
638
741
  return {
639
742
  toolCall,
640
- result: createToolFailureResult(record, outcome.result.terminate),
743
+ result: outcome.repeatedSuccessfulCall
744
+ ? {
745
+ ...failureResult,
746
+ details: {
747
+ ...failureResult.details,
748
+ piRepeatedSuccessfulCall: outcome.repeatedSuccessfulCall,
749
+ },
750
+ }
751
+ : failureResult,
641
752
  isError: true,
642
753
  };
643
754
  }
@@ -838,7 +949,7 @@ async function finalizeExecutedToolCall(currentContext, assistantMessage, prepar
838
949
  const usage = result.usage;
839
950
  const effectiveFailureMessage = failureMessage || result.content.find((block) => block.type === "text")?.text || "Tool execution failed";
840
951
  const assessment = assessToolFailure(effectiveFailureMessage, "failed", errorClass);
841
- const record = rememberToolFailure(toolFailureMemory, prepared.toolCall.name, prepared.args, "failed", assessment.failureCode, assessment.guidance, assessment.diagnostic);
952
+ const record = rememberToolFailure(toolFailureMemory, prepared.toolCall.name, prepared.args, "failed", assessment.failureCode, assessment.guidance, assessment.diagnostic, assessment.phase);
842
953
  result = { ...createToolFailureResult(record, result.terminate), usage };
843
954
  }
844
955
  else {