fix: context overflow compaction and subagent announce improvements (#11664) (thanks @tyler6204)

* initial commit

* feat: implement deriveSessionTotalTokens function and update usage tests

* Added deriveSessionTotalTokens function to calculate total tokens based on usage and context tokens.
* Updated usage tests to include cases for derived session total tokens.
* Refactored session usage calculations in multiple files to utilize the new function for improved accuracy.

* fix: restore overflow truncation fallback + changelog/test hardening (#11551) (thanks @tyler6204)
This commit is contained in:
Tyler Yust
2026-02-07 20:02:32 -08:00
committed by GitHub
parent 8fae55e8e0
commit 191da1feb5
31 changed files with 889 additions and 178 deletions

View File

@@ -17,7 +17,11 @@ import {
mergeDeliveryContext,
normalizeDeliveryContext,
} from "../utils/delivery-context.js";
import { isEmbeddedPiRunActive, queueEmbeddedPiMessage } from "./pi-embedded.js";
import {
isEmbeddedPiRunActive,
queueEmbeddedPiMessage,
waitForEmbeddedPiRunEnd,
} from "./pi-embedded.js";
import { type AnnounceQueueItem, enqueueAnnounce } from "./subagent-announce-queue.js";
import { readLatestAssistantReply } from "./tools/agent-step.js";
@@ -288,6 +292,35 @@ async function buildSubagentStatsLine(params: {
return `Stats: ${parts.join(" \u2022 ")}`;
}
function loadSessionEntryByKey(sessionKey: string) {
const cfg = loadConfig();
const agentId = resolveAgentIdFromSessionKey(sessionKey);
const storePath = resolveStorePath(cfg.session?.store, { agentId });
const store = loadSessionStore(storePath);
return store[sessionKey];
}
async function readLatestAssistantReplyWithRetry(params: {
sessionKey: string;
initialReply?: string;
maxWaitMs: number;
}): Promise<string | undefined> {
let reply = params.initialReply?.trim() ? params.initialReply : undefined;
if (reply) {
return reply;
}
const deadline = Date.now() + Math.max(0, Math.min(params.maxWaitMs, 15_000));
while (Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 300));
const latest = await readLatestAssistantReply({ sessionKey: params.sessionKey });
if (latest?.trim()) {
return latest;
}
}
return reply;
}
export function buildSubagentSystemPrompt(params: {
requesterSessionKey?: string;
requesterOrigin?: DeliveryContext;
@@ -365,12 +398,33 @@ export async function runSubagentAnnounceFlow(params: {
announceType?: SubagentAnnounceType;
}): Promise<boolean> {
let didAnnounce = false;
let shouldDeleteChildSession = params.cleanup === "delete";
try {
const requesterOrigin = normalizeDeliveryContext(params.requesterOrigin);
const childSessionId = (() => {
const entry = loadSessionEntryByKey(params.childSessionKey);
return typeof entry?.sessionId === "string" && entry.sessionId.trim()
? entry.sessionId.trim()
: undefined;
})();
const settleTimeoutMs = Math.min(Math.max(params.timeoutMs, 1), 120_000);
let reply = params.roundOneReply;
let outcome: SubagentRunOutcome | undefined = params.outcome;
// Lifecycle "end" can arrive before auto-compaction retries finish. If the
// subagent is still active, wait for the embedded run to fully settle.
if (childSessionId && isEmbeddedPiRunActive(childSessionId)) {
const settled = await waitForEmbeddedPiRunEnd(childSessionId, settleTimeoutMs);
if (!settled && isEmbeddedPiRunActive(childSessionId)) {
// The child run is still active (e.g., compaction retry still in progress).
// Defer announcement so we don't report stale/partial output.
// Keep the child session so output is not lost while the run is still active.
shouldDeleteChildSession = false;
return false;
}
}
if (!reply && params.waitForCompletion !== false) {
const waitMs = Math.min(params.timeoutMs, 60_000);
const waitMs = settleTimeoutMs;
const wait = await callGateway<{
status?: string;
startedAt?: number;
@@ -403,17 +457,27 @@ export async function runSubagentAnnounceFlow(params: {
outcome = { status: "timeout" };
}
}
reply = await readLatestAssistantReply({
sessionKey: params.childSessionKey,
});
reply = await readLatestAssistantReply({ sessionKey: params.childSessionKey });
}
if (!reply) {
reply = await readLatestAssistantReply({
reply = await readLatestAssistantReply({ sessionKey: params.childSessionKey });
}
if (!reply?.trim()) {
reply = await readLatestAssistantReplyWithRetry({
sessionKey: params.childSessionKey,
initialReply: reply,
maxWaitMs: params.timeoutMs,
});
}
if (!reply?.trim() && childSessionId && isEmbeddedPiRunActive(childSessionId)) {
// Avoid announcing "(no output)" while the child run is still producing output.
shouldDeleteChildSession = false;
return false;
}
if (!outcome) {
outcome = { status: "unknown" };
}
@@ -508,7 +572,7 @@ export async function runSubagentAnnounceFlow(params: {
// Best-effort
}
}
if (params.cleanup === "delete") {
if (shouldDeleteChildSession) {
try {
await callGateway({
method: "sessions.delete",