

























@@ -795,15 +795,20 @@ export async function runEmbeddedPiAgent(
795795const postCompactionGuard = createPostCompactionLoopGuard(
796796params.config?.tools?.loopDetection?.postCompactionGuard,
797797);
798-let lastObservedToolCallHistoryIndex = (() => {
798+// Monotonic outcome seq (incremented by recordToolCallOutcome on each
799+// observable push). We use a delta on this counter instead of an
800+// absolute index into state.toolCallHistory, which is trimmed at
801+// historySize and would silently shift records out from under an
802+// index cursor in long-running sessions.
803+let lastObservedToolOutcomeSeq = (() => {
799804if (!params.sessionKey && !params.sessionId) {
800805return 0;
801806}
802807const state = getDiagnosticSessionState({
803808 ...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
804809 ...(params.sessionId ? { sessionId: params.sessionId } : {}),
805810});
806-return state.toolCallHistory?.length ?? 0;
811+return state.toolOutcomeSeq ?? 0;
807812})();
808813let lastRetryFailoverReason: FailoverReason | null = null;
809814let planningOnlyRetryInstruction: string | null = null;
@@ -1220,6 +1225,16 @@ export async function runEmbeddedPiAgent(
12201225// records that completed during this attempt (populated by the
12211226// before-tool-call hook's recordToolCallOutcome) and feeds them
12221227// into the guard. Disarms automatically once the window expires.
1228+//
1229+// Cursor scheme: rather than index into state.toolCallHistory
1230+// (which trims at historySize and silently drops records on busy
1231+// sessions), we read state.toolOutcomeSeq, a monotonic counter
1232+// that recordToolCallOutcome increments on every observable push.
1233+// The delta currentSeq - lastObservedSeq tells us how many new
1234+// records have appeared globally; we then scan that many entries
1235+// from the tail of toolCallHistory. The tail-slice is trim-safe:
1236+// even if the buffer was full, the most recent N records are the
1237+// ones that survive.
12231238if (postCompactionGuard.snapshot().armed) {
12241239const guardSessionState =
12251240params.sessionKey || params.sessionId
@@ -1229,38 +1244,43 @@ export async function runEmbeddedPiAgent(
12291244})
12301245 : undefined;
12311246const history = guardSessionState?.toolCallHistory ?? [];
1232-for (let i = lastObservedToolCallHistoryIndex; i < history.length; i += 1) {
1233-const record = history[i];
1234-if (!record || !record.resultHash) {
1235-continue;
1236-}
1237-if (params.runId && record.runId && record.runId !== params.runId) {
1238-continue;
1239-}
1240-const verdict = postCompactionGuard.observe({
1241-toolName: record.toolName,
1242-argsHash: record.argsHash,
1243-resultHash: record.resultHash,
1244-});
1245-if (verdict.shouldAbort) {
1246-throw PostCompactionLoopPersistedError.fromVerdict(verdict);
1247-}
1248-if (!postCompactionGuard.snapshot().armed) {
1249-break;
1247+const currentSeq = guardSessionState?.toolOutcomeSeq ?? 0;
1248+const newRecordCount = Math.max(0, currentSeq - lastObservedToolOutcomeSeq);
1249+if (newRecordCount > 0) {
1250+const startIndex = Math.max(0, history.length - newRecordCount);
1251+for (let i = startIndex; i < history.length; i += 1) {
1252+const record = history[i];
1253+if (!record || typeof record.resultHash !== "string") {
1254+continue;
1255+}
1256+if (params.runId && record.runId && record.runId !== params.runId) {
1257+continue;
1258+}
1259+const verdict = postCompactionGuard.observe({
1260+toolName: record.toolName,
1261+argsHash: record.argsHash,
1262+resultHash: record.resultHash,
1263+});
1264+if (verdict.shouldAbort) {
1265+throw PostCompactionLoopPersistedError.fromVerdict(verdict);
1266+}
1267+if (!postCompactionGuard.snapshot().armed) {
1268+break;
1269+}
12501270}
12511271}
1252-lastObservedToolCallHistoryIndex = history.length;
1272+lastObservedToolOutcomeSeq = currentSeq;
12531273} else {
1254-// Keep index aligned with current history length so freshly armed
1255-// windows only see records from the post-compaction-retry attempt.
1274+// Keep cursor aligned with the current global outcome seq so a
1275+// freshly-armed window only sees records pushed AFTER arming.
12561276const guardSessionState =
12571277params.sessionKey || params.sessionId
12581278 ? getDiagnosticSessionState({
12591279 ...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
12601280 ...(params.sessionId ? { sessionId: params.sessionId } : {}),
12611281})
12621282 : undefined;
1263-lastObservedToolCallHistoryIndex = guardSessionState?.toolCallHistory?.length ?? 0;
1283+lastObservedToolOutcomeSeq = guardSessionState?.toolOutcomeSeq ?? 0;
12641284}
1265128512661286const {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。