@caupulican/pi-agent-core 0.93.4 → 0.93.7
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 +4 -1
- package/dist/agent-loop.js.map +1 -1
- package/dist/tool-failure-memory.d.ts +8 -2
- package/dist/tool-failure-memory.d.ts.map +1 -1
- package/dist/tool-failure-memory.js +67 -22
- package/dist/tool-failure-memory.js.map +1 -1
- package/package.json +2 -2
|
@@ -7,7 +7,7 @@ const TOOL_FAILURE_EXECUTION_KEY = Symbol("ToolFailureExecutionKey");
|
|
|
7
7
|
const MAX_OPERATION_CHARS = 240;
|
|
8
8
|
const MAX_FAILURE_CODE_CHARS = 48;
|
|
9
9
|
const MAX_DIAGNOSTIC_CHARS = 240;
|
|
10
|
-
const MAX_CORRECTION_CHARS =
|
|
10
|
+
const MAX_CORRECTION_CHARS = 480;
|
|
11
11
|
const MAX_TOOL_FAILURE_EVIDENCE_CHARS = 1_600;
|
|
12
12
|
const MAX_ACTIVE_FAILURE_EVIDENCE_CHARS = 2_400;
|
|
13
13
|
const MAX_TOOL_NAME_CHARS = 64;
|
|
@@ -16,8 +16,8 @@ const MAX_ACTIVE_FAILURES = 8;
|
|
|
16
16
|
const MAX_TRACKED_FAILURES = 64;
|
|
17
17
|
const REPAIRABLE_REJECTION_CODES = new Set(["invalid_arguments", "malformed_call", "unknown_tool"]);
|
|
18
18
|
const LEGACY_GENERIC_EXECUTION_CORRECTION = "Change the arguments or approach before retrying; do not resend the unchanged operation.";
|
|
19
|
-
const BLOCKED_REPLAY_CAVEMAN_CORRECTION = "
|
|
20
|
-
const CLOSED_OPERATION_CAVEMAN_CORRECTION = "
|
|
19
|
+
const BLOCKED_REPLAY_CAVEMAN_CORRECTION = "Blocked: this exact operation will not run again this session — change the operation or continue other work.";
|
|
20
|
+
const CLOSED_OPERATION_CAVEMAN_CORRECTION = "Closed: this exact operation was not executed and will not run again this session — use a different operation or continue other work.";
|
|
21
21
|
function truncate(value, maxChars) {
|
|
22
22
|
if (value.length <= maxChars)
|
|
23
23
|
return value;
|
|
@@ -438,6 +438,19 @@ function boundedFailureCode(value) {
|
|
|
438
438
|
* always the last line it appends — outranks any earlier, non-authoritative line that matches.
|
|
439
439
|
*/
|
|
440
440
|
const EXIT_STATUS_LINE_PATTERN = /^(?:\S+\s+)?(?:exit(?:ed)?(?:\s+with)?(?:\s+code)?|exitcode)\s*[:=]?\s*(-?\d+)\b/i;
|
|
441
|
+
/**
|
|
442
|
+
* bash.ts appends `cwd: <dir>` after its exit trailer on failures. The line is harness status, not
|
|
443
|
+
* a cause: it must never displace a real diagnostic (a path may contain words like "error"), and it
|
|
444
|
+
* reaches the model through the process-exit evidence tail instead.
|
|
445
|
+
*/
|
|
446
|
+
const CWD_STATUS_LINE_PATTERN = /^cwd:\s/i;
|
|
447
|
+
/** Tool-owned status/marker lines that carry no cause and never belong in diagnostic or evidence. */
|
|
448
|
+
function isFailureStatusLine(line) {
|
|
449
|
+
return (/^command (?:exited with code|timed out after|aborted|killed after)\b/i.test(line) ||
|
|
450
|
+
/^outcome:\s*(?:failed|aborted|timeout|output_limit)\b/i.test(line) ||
|
|
451
|
+
/^exitcode:\s*-?\d+\b/i.test(line) ||
|
|
452
|
+
/^(?:stdout|stderr):(?:\s*\(empty\))?$/i.test(line));
|
|
453
|
+
}
|
|
441
454
|
function processExitFailureCode(message) {
|
|
442
455
|
const lines = message.split(/\r\n|\n/);
|
|
443
456
|
for (let index = lines.length - 1; index >= 0; index--) {
|
|
@@ -522,7 +535,9 @@ function extractFailureDiagnostic(message, allowUnclassifiedFallback, requireStr
|
|
|
522
535
|
const stderrLines = rawLines
|
|
523
536
|
.slice(stderrMarkerIndex + 1)
|
|
524
537
|
.map((line) => line.trim())
|
|
525
|
-
.filter((line) => line.length > 0 &&
|
|
538
|
+
.filter((line) => line.length > 0 &&
|
|
539
|
+
!/^command (?:exited with code|timed out after|aborted|killed after)\b/i.test(line) &&
|
|
540
|
+
!CWD_STATUS_LINE_PATTERN.test(line));
|
|
526
541
|
if (stderrLines.length > 0) {
|
|
527
542
|
const classified = stderrLines.find((line) => strongDiagnosticPattern.test(line));
|
|
528
543
|
if (classified)
|
|
@@ -539,11 +554,7 @@ function extractFailureDiagnostic(message, allowUnclassifiedFallback, requireStr
|
|
|
539
554
|
}
|
|
540
555
|
const lines = rawLines
|
|
541
556
|
.map((line) => line.trim())
|
|
542
|
-
.filter((line) => line.length > 0 &&
|
|
543
|
-
!/^command (?:exited with code|timed out after|aborted|killed after)\b/i.test(line) &&
|
|
544
|
-
!/^outcome:\s*(?:failed|aborted|timeout|output_limit)\b/i.test(line) &&
|
|
545
|
-
!/^exitcode:\s*-?\d+\b/i.test(line) &&
|
|
546
|
-
!/^(?:stdout|stderr):(?:\s*\(empty\))?$/i.test(line));
|
|
557
|
+
.filter((line) => line.length > 0 && !isFailureStatusLine(line) && !CWD_STATUS_LINE_PATTERN.test(line));
|
|
547
558
|
if (lines.length === 0)
|
|
548
559
|
return undefined;
|
|
549
560
|
const diagnosticPattern = requireStrongSignal
|
|
@@ -555,6 +566,35 @@ function extractFailureDiagnostic(message, allowUnclassifiedFallback, requireStr
|
|
|
555
566
|
const diagnostic = classified ?? (allowUnclassifiedFallback ? lines.at(-1) : undefined);
|
|
556
567
|
return diagnostic ? truncateMiddle(diagnostic, MAX_DIAGNOSTIC_CHARS) : undefined;
|
|
557
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* Bounded raw-output tail of an executed process-exit failure. Evidence is the raw-data channel:
|
|
571
|
+
* it keeps the trailing lines that strong-signal diagnostic classification refuses to promote, so
|
|
572
|
+
* strictness never destroys the output needed to construct a changed operation.
|
|
573
|
+
*/
|
|
574
|
+
function extractProcessExitEvidence(message) {
|
|
575
|
+
const lines = sanitizeBinaryOutput(message)
|
|
576
|
+
.replaceAll("\r\n", "\n")
|
|
577
|
+
.split("\n")
|
|
578
|
+
.map((line) => line.trim())
|
|
579
|
+
.filter((line) => line.length > 0 && !isFailureStatusLine(line));
|
|
580
|
+
if (lines.length === 0)
|
|
581
|
+
return undefined;
|
|
582
|
+
let start = lines.length - 1;
|
|
583
|
+
let retainedChars = lines[start].length;
|
|
584
|
+
while (start > 0 && retainedChars + 1 + lines[start - 1].length <= MAX_TOOL_FAILURE_EVIDENCE_CHARS) {
|
|
585
|
+
start--;
|
|
586
|
+
retainedChars += 1 + lines[start].length;
|
|
587
|
+
}
|
|
588
|
+
let tail = lines.slice(start).join("\n");
|
|
589
|
+
if (tail.length > MAX_TOOL_FAILURE_EVIDENCE_CHARS) {
|
|
590
|
+
let tailStart = tail.length - MAX_TOOL_FAILURE_EVIDENCE_CHARS;
|
|
591
|
+
const firstCode = tail.charCodeAt(tailStart);
|
|
592
|
+
if (firstCode >= 0xdc00 && firstCode <= 0xdfff)
|
|
593
|
+
tailStart++;
|
|
594
|
+
tail = tail.slice(tailStart);
|
|
595
|
+
}
|
|
596
|
+
return sanitizeToolFailureEvidence(tail);
|
|
597
|
+
}
|
|
558
598
|
export function assessToolFailure(message, state, errorClass) {
|
|
559
599
|
const policy = getToolExecutionErrorPolicy(message);
|
|
560
600
|
const exitFailureCode = processExitFailureCode(message);
|
|
@@ -562,14 +602,17 @@ export function assessToolFailure(message, state, errorClass) {
|
|
|
562
602
|
const diagnostic = state === "failed" && (!policy || policy.retainDiagnostic)
|
|
563
603
|
? extractFailureDiagnostic(message, exitFailureCode === undefined && (errorClass !== undefined || retainPolicyDiagnostic), exitFailureCode !== undefined && !retainPolicyDiagnostic)
|
|
564
604
|
: undefined;
|
|
605
|
+
const evidence = state === "failed" && exitFailureCode !== undefined ? extractProcessExitEvidence(message) : undefined;
|
|
565
606
|
const failureCode = policy?.failureCode ?? classifyToolFailure(message, errorClass);
|
|
566
607
|
return {
|
|
567
608
|
failureCode,
|
|
568
609
|
phase: policy?.phase ?? inferToolFailurePhase(state, failureCode),
|
|
569
610
|
...(diagnostic ? { diagnostic } : {}),
|
|
611
|
+
...(evidence ? { evidence } : {}),
|
|
570
612
|
guidance: policy
|
|
571
613
|
? truncate(policy.guidance, MAX_CORRECTION_CHARS)
|
|
572
614
|
: fallbackFailureGuidance(state, diagnostic !== undefined, inferToolFailurePhase(state, failureCode)),
|
|
615
|
+
...(policy ? { policyGuidance: truncate(policy.guidance, MAX_CORRECTION_CHARS) } : {}),
|
|
573
616
|
...(policy?.attemptMemory === "discard" ? { attemptMemory: "discard" } : {}),
|
|
574
617
|
};
|
|
575
618
|
}
|
|
@@ -592,6 +635,7 @@ function readFailureRecord(details) {
|
|
|
592
635
|
return undefined;
|
|
593
636
|
}
|
|
594
637
|
const diagnostic = typeof candidate.diagnostic === "string" ? truncate(candidate.diagnostic, MAX_DIAGNOSTIC_CHARS) : undefined;
|
|
638
|
+
const note = typeof candidate.note === "string" ? truncate(candidate.note, MAX_DIAGNOSTIC_CHARS) : undefined;
|
|
595
639
|
const evidence = sanitizeToolFailureEvidence(candidate.evidence);
|
|
596
640
|
const retainedCorrection = typeof candidate.correction === "string" ? truncate(candidate.correction, MAX_CORRECTION_CHARS) : undefined;
|
|
597
641
|
const correction = candidate.state === "failed" && retainedCorrection === LEGACY_GENERIC_EXECUTION_CORRECTION
|
|
@@ -616,6 +660,7 @@ function readFailureRecord(details) {
|
|
|
616
660
|
phase,
|
|
617
661
|
failureCode: boundedFailureCode(candidate.failureCode),
|
|
618
662
|
diagnostic,
|
|
663
|
+
note,
|
|
619
664
|
evidence,
|
|
620
665
|
correction,
|
|
621
666
|
};
|
|
@@ -925,6 +970,7 @@ function formatRecordJson(record, includeOperation = false, evidence = record.ev
|
|
|
925
970
|
...(includeOperation ? { operation: record.operation } : {}),
|
|
926
971
|
failure_code: record.failureCode,
|
|
927
972
|
...(record.diagnostic ? { diagnostic: record.diagnostic } : {}),
|
|
973
|
+
...(record.note ? { note: record.note } : {}),
|
|
928
974
|
...(evidence ? { evidence } : {}),
|
|
929
975
|
...guidance,
|
|
930
976
|
...(record.attemptMemory === "discard" ? { attempt_memory: "discarded" } : {}),
|
|
@@ -955,32 +1001,31 @@ export function createToolFailureResult(record, terminate) {
|
|
|
955
1001
|
};
|
|
956
1002
|
}
|
|
957
1003
|
export function createRepeatedToolFailureResult(record) {
|
|
958
|
-
const retainedRecord =
|
|
959
|
-
|
|
960
|
-
correction: BLOCKED_REPLAY_CAVEMAN_CORRECTION,
|
|
961
|
-
};
|
|
962
|
-
const diagnostic = truncateMiddle(`The unchanged operation was not executed. Unchanged replay blocked after ${record.failureCode}`, MAX_DIAGNOSTIC_CHARS);
|
|
1004
|
+
const retainedRecord = retainBlockedToolFailure(record);
|
|
1005
|
+
const replayNotice = truncateMiddle(`The unchanged operation was not executed. Unchanged replay blocked after ${record.failureCode}`, MAX_DIAGNOSTIC_CHARS);
|
|
963
1006
|
const blockedResult = createToolFailureResult({
|
|
964
1007
|
...retainedRecord,
|
|
965
1008
|
state: "rejected",
|
|
966
1009
|
failureCode: "repeated_failed_operation",
|
|
967
|
-
diagnostic
|
|
968
|
-
|
|
1010
|
+
...(retainedRecord.diagnostic
|
|
1011
|
+
? { note: replayNotice }
|
|
1012
|
+
: { diagnostic: replayNotice, correction: BLOCKED_REPLAY_CAVEMAN_CORRECTION }),
|
|
969
1013
|
});
|
|
970
1014
|
return {
|
|
971
1015
|
...blockedResult,
|
|
972
|
-
// The visible result
|
|
973
|
-
// authoritative cause so later blocks do not
|
|
1016
|
+
// The visible result leads with the retained root cause and carries the replay notice as a
|
|
1017
|
+
// note, while retained memory keeps the authoritative cause so later blocks do not
|
|
1018
|
+
// recursively wrap synthetic failures.
|
|
974
1019
|
details: { piToolFailureMemory: retainedRecord },
|
|
975
1020
|
};
|
|
976
1021
|
}
|
|
977
|
-
export function createToolFailureRecoveryExhaustedResult(record,
|
|
1022
|
+
export function createToolFailureRecoveryExhaustedResult(record, note) {
|
|
978
1023
|
const retainedRecord = retainBlockedToolFailure(record);
|
|
979
1024
|
const exhaustedResult = createToolFailureResult({
|
|
980
1025
|
...retainedRecord,
|
|
981
1026
|
state: "rejected",
|
|
982
1027
|
failureCode: "recovery_exhausted",
|
|
983
|
-
|
|
1028
|
+
note: truncateMiddle(note, MAX_DIAGNOSTIC_CHARS),
|
|
984
1029
|
correction: "Stop retrying tools in this run. Report the unresolved failure and the user or environment action required to continue.",
|
|
985
1030
|
}, true);
|
|
986
1031
|
return {
|
|
@@ -988,7 +1033,7 @@ export function createToolFailureRecoveryExhaustedResult(record, diagnostic) {
|
|
|
988
1033
|
details: { piToolFailureMemory: retainedRecord },
|
|
989
1034
|
};
|
|
990
1035
|
}
|
|
991
|
-
export function createToolFailureOperationExhaustedResult(record,
|
|
1036
|
+
export function createToolFailureOperationExhaustedResult(record, note) {
|
|
992
1037
|
const retainedRecord = {
|
|
993
1038
|
...retainBlockedToolFailure(record),
|
|
994
1039
|
correction: CLOSED_OPERATION_CAVEMAN_CORRECTION,
|
|
@@ -997,7 +1042,7 @@ export function createToolFailureOperationExhaustedResult(record, diagnostic) {
|
|
|
997
1042
|
...retainedRecord,
|
|
998
1043
|
state: "rejected",
|
|
999
1044
|
failureCode: "operation_recovery_exhausted",
|
|
1000
|
-
|
|
1045
|
+
note: truncateMiddle(note, MAX_DIAGNOSTIC_CHARS),
|
|
1001
1046
|
}, false);
|
|
1002
1047
|
return {
|
|
1003
1048
|
...exhaustedResult,
|