fix: agent-only announce path, BB message IDs, sender identity, SSRF allowlist (#23970)

* fix(agents): defer announces until descendant cleanup settles

* fix(bluebubbles): harden message metadata extraction

* feat(contributors): rank by composite score (commits, PRs, LOC, tenure)

* refactor(control-ui): move method guard after path checks to improve request handling

* fix subagent completion announce when only current run is pending

* fix(subagents): keep orchestrator runs active until descendants finish

* fix: prepare PR feedback follow-ups (#23970) (thanks @tyler6204)
This commit is contained in:
Tyler Yust
2026-03-01 22:52:11 -08:00
committed by GitHub
parent cfba64c9db
commit f918b336d1
22 changed files with 814 additions and 155 deletions

View File

@@ -32,6 +32,8 @@ import {
import {
countActiveDescendantRunsFromRuns,
countActiveRunsForSessionFromRuns,
countPendingDescendantRunsExcludingRunFromRuns,
countPendingDescendantRunsFromRuns,
findRunIdsByChildSessionKeyFromRuns,
listDescendantRunsForRequesterFromRuns,
listRunsForRequesterFromRuns,
@@ -63,10 +65,15 @@ const MAX_ANNOUNCE_RETRY_DELAY_MS = 8_000;
*/
const MAX_ANNOUNCE_RETRY_COUNT = 3;
/**
* Announce entries older than this are force-expired even if delivery never
* succeeded. Guards against stale registry entries surviving gateway restarts.
* Non-completion announce entries older than this are force-expired even if
* delivery never succeeded.
*/
const ANNOUNCE_EXPIRY_MS = 5 * 60_000; // 5 minutes
/**
* Completion-message flows can wait for descendants to finish, but this hard
* cap prevents indefinite pending state when descendants never fully settle.
*/
const ANNOUNCE_COMPLETION_HARD_EXPIRY_MS = 30 * 60_000; // 30 minutes
type SubagentRunOrphanReason = "missing-session-entry" | "missing-session-id";
/**
* Embedded runs can emit transient lifecycle `error` events while provider/model
@@ -445,7 +452,11 @@ function resumeSubagentRun(runId: string) {
persistSubagentRuns();
return;
}
if (typeof entry.endedAt === "number" && Date.now() - entry.endedAt > ANNOUNCE_EXPIRY_MS) {
if (
entry.expectsCompletionMessage !== true &&
typeof entry.endedAt === "number" &&
Date.now() - entry.endedAt > ANNOUNCE_EXPIRY_MS
) {
logAnnounceGiveUp(entry, "expiry");
entry.cleanupCompletedAt = Date.now();
persistSubagentRuns();
@@ -462,6 +473,7 @@ function resumeSubagentRun(runId: string) {
) {
const waitMs = Math.max(1, earliestRetryAt - now);
setTimeout(() => {
resumedRuns.delete(runId);
resumeSubagentRun(runId);
}, waitMs).unref?.();
resumedRuns.add(runId);
@@ -709,8 +721,10 @@ async function finalizeSubagentCleanup(
const deferredDecision = resolveDeferredCleanupDecision({
entry,
now,
activeDescendantRuns: Math.max(0, countActiveDescendantRuns(entry.childSessionKey)),
// Defer until descendants are fully settled, including post-end cleanup.
activeDescendantRuns: Math.max(0, countPendingDescendantRuns(entry.childSessionKey)),
announceExpiryMs: ANNOUNCE_EXPIRY_MS,
announceCompletionHardExpiryMs: ANNOUNCE_COMPLETION_HARD_EXPIRY_MS,
maxAnnounceRetryCount: MAX_ANNOUNCE_RETRY_COUNT,
deferDescendantDelayMs: MIN_ANNOUNCE_RETRY_DELAY_MS,
resolveAnnounceRetryDelayMs,
@@ -753,6 +767,7 @@ async function finalizeSubagentCleanup(
// Applies to both keep/delete cleanup modes so delete-runs are only removed
// after a successful announce (or terminal give-up).
entry.cleanupHandled = false;
// Clear the in-flight resume marker so the scheduled retry can run again.
resumedRuns.delete(runId);
persistSubagentRuns();
if (deferredDecision.resumeDelayMs == null) {
@@ -815,9 +830,10 @@ function retryDeferredCompletedAnnounces(excludeRunId?: string) {
if (suppressAnnounceForSteerRestart(entry)) {
continue;
}
// Force-expire announces that have been pending too long (#18264).
// Force-expire stale non-completion announces; completion-message flows can
// stay pending while descendants run for a long time.
const endedAgo = now - (entry.endedAt ?? now);
if (endedAgo > ANNOUNCE_EXPIRY_MS) {
if (entry.expectsCompletionMessage !== true && endedAgo > ANNOUNCE_EXPIRY_MS) {
logAnnounceGiveUp(entry, "expiry");
entry.cleanupCompletedAt = now;
persistSubagentRuns();
@@ -1214,6 +1230,24 @@ export function countActiveDescendantRuns(rootSessionKey: string): number {
);
}
export function countPendingDescendantRuns(rootSessionKey: string): number {
return countPendingDescendantRunsFromRuns(
getSubagentRunsSnapshotForRead(subagentRuns),
rootSessionKey,
);
}
export function countPendingDescendantRunsExcludingRun(
rootSessionKey: string,
excludeRunId: string,
): number {
return countPendingDescendantRunsExcludingRunFromRuns(
getSubagentRunsSnapshotForRead(subagentRuns),
rootSessionKey,
excludeRunId,
);
}
export function listDescendantRunsForRequester(rootSessionKey: string): SubagentRunRecord[] {
return listDescendantRunsForRequesterFromRuns(
getSubagentRunsSnapshotForRead(subagentRuns),