mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 23:44:33 +00:00
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:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user