mirror of https://github.com/openclaw/openclaw.git
refactor: lazy load cron subagent followup runtime
This commit is contained in:
parent
9919e978ca
commit
cc57bcfe2f
|
|
@ -53,9 +53,12 @@ vi.mock("../../infra/system-events.js", () => ({
|
|||
enqueueSystemEvent: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./subagent-followup.js", () => ({
|
||||
vi.mock("./subagent-followup-hints.js", () => ({
|
||||
expectsSubagentFollowup: vi.fn().mockReturnValue(false),
|
||||
isLikelyInterimCronMessage: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
vi.mock("./subagent-followup.runtime.js", () => ({
|
||||
readDescendantSubagentFallbackReply: vi.fn().mockResolvedValue(undefined),
|
||||
waitForDescendantSubagentSummary: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
|
@ -73,12 +76,11 @@ import {
|
|||
} from "./delivery-dispatch.js";
|
||||
import type { DeliveryTargetResolution } from "./delivery-target.js";
|
||||
import type { RunCronAgentTurnResult } from "./run.js";
|
||||
import { expectsSubagentFollowup, isLikelyInterimCronMessage } from "./subagent-followup-hints.js";
|
||||
import {
|
||||
expectsSubagentFollowup,
|
||||
isLikelyInterimCronMessage,
|
||||
readDescendantSubagentFallbackReply,
|
||||
waitForDescendantSubagentSummary,
|
||||
} from "./subagent-followup.js";
|
||||
} from "./subagent-followup.runtime.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
|
|
|
|||
|
|
@ -20,12 +20,7 @@ import type { CronJob, CronRunTelemetry } from "../types.js";
|
|||
import type { DeliveryTargetResolution } from "./delivery-target.js";
|
||||
import { pickSummaryFromOutput } from "./helpers.js";
|
||||
import type { RunCronAgentTurnResult } from "./run.js";
|
||||
import {
|
||||
expectsSubagentFollowup,
|
||||
isLikelyInterimCronMessage,
|
||||
readDescendantSubagentFallbackReply,
|
||||
waitForDescendantSubagentSummary,
|
||||
} from "./subagent-followup.js";
|
||||
import { expectsSubagentFollowup, isLikelyInterimCronMessage } from "./subagent-followup-hints.js";
|
||||
|
||||
function normalizeDeliveryTarget(channel: string, to: string): string {
|
||||
const channelLower = channel.trim().toLowerCase();
|
||||
|
|
@ -141,6 +136,9 @@ type CompletedDirectCronDelivery = {
|
|||
};
|
||||
|
||||
let gatewayCallRuntimePromise: Promise<typeof import("../../gateway/call.runtime.js")> | undefined;
|
||||
let subagentFollowupRuntimePromise:
|
||||
| Promise<typeof import("./subagent-followup.runtime.js")>
|
||||
| undefined;
|
||||
|
||||
const COMPLETED_DIRECT_CRON_DELIVERIES = new Map<string, CompletedDirectCronDelivery>();
|
||||
|
||||
|
|
@ -149,6 +147,13 @@ async function loadGatewayCallRuntime(): Promise<typeof import("../../gateway/ca
|
|||
return await gatewayCallRuntimePromise;
|
||||
}
|
||||
|
||||
async function loadSubagentFollowupRuntime(): Promise<
|
||||
typeof import("./subagent-followup.runtime.js")
|
||||
> {
|
||||
subagentFollowupRuntimePromise ??= import("./subagent-followup.runtime.js");
|
||||
return await subagentFollowupRuntimePromise;
|
||||
}
|
||||
|
||||
function cloneDeliveryResults(
|
||||
results: readonly OutboundDeliveryResult[],
|
||||
): OutboundDeliveryResult[] {
|
||||
|
|
@ -545,21 +550,27 @@ export async function dispatchCronDelivery(
|
|||
const initialSynthesizedText = synthesizedText.trim();
|
||||
let activeSubagentRuns = countActiveDescendantRuns(params.agentSessionKey);
|
||||
const expectedSubagentFollowup = expectsSubagentFollowup(initialSynthesizedText);
|
||||
const shouldCheckCompletedDescendants =
|
||||
activeSubagentRuns === 0 && isLikelyInterimCronMessage(initialSynthesizedText);
|
||||
const needsSubagentFollowupRuntime =
|
||||
shouldCheckCompletedDescendants || activeSubagentRuns > 0 || expectedSubagentFollowup;
|
||||
const subagentFollowupRuntime = needsSubagentFollowupRuntime
|
||||
? await loadSubagentFollowupRuntime()
|
||||
: undefined;
|
||||
// Also check for already-completed descendants. If the subagent finished
|
||||
// before delivery-dispatch runs, activeSubagentRuns is 0 and
|
||||
// expectedSubagentFollowup may be false (e.g. cron said "on it" which
|
||||
// doesn't match the narrow hint list). We still need to use the
|
||||
// descendant's output instead of the interim cron text.
|
||||
const completedDescendantReply =
|
||||
activeSubagentRuns === 0 && isLikelyInterimCronMessage(initialSynthesizedText)
|
||||
? await readDescendantSubagentFallbackReply({
|
||||
sessionKey: params.agentSessionKey,
|
||||
runStartedAt: params.runStartedAt,
|
||||
})
|
||||
: undefined;
|
||||
const completedDescendantReply = shouldCheckCompletedDescendants
|
||||
? await subagentFollowupRuntime?.readDescendantSubagentFallbackReply({
|
||||
sessionKey: params.agentSessionKey,
|
||||
runStartedAt: params.runStartedAt,
|
||||
})
|
||||
: undefined;
|
||||
const hadDescendants = activeSubagentRuns > 0 || Boolean(completedDescendantReply);
|
||||
if (activeSubagentRuns > 0 || expectedSubagentFollowup) {
|
||||
let finalReply = await waitForDescendantSubagentSummary({
|
||||
let finalReply = await subagentFollowupRuntime?.waitForDescendantSubagentSummary({
|
||||
sessionKey: params.agentSessionKey,
|
||||
initialReply: initialSynthesizedText,
|
||||
timeoutMs: params.timeoutMs,
|
||||
|
|
@ -567,7 +578,7 @@ export async function dispatchCronDelivery(
|
|||
});
|
||||
activeSubagentRuns = countActiveDescendantRuns(params.agentSessionKey);
|
||||
if (!finalReply && activeSubagentRuns === 0) {
|
||||
finalReply = await readDescendantSubagentFallbackReply({
|
||||
finalReply = await subagentFollowupRuntime?.readDescendantSubagentFallbackReply({
|
||||
sessionKey: params.agentSessionKey,
|
||||
runStartedAt: params.runStartedAt,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ import { buildCronAgentDefaultsConfig } from "./run-config.js";
|
|||
import { resolveCronAgentSessionKey } from "./session-key.js";
|
||||
import { resolveCronSession } from "./session.js";
|
||||
import { resolveCronSkillsSnapshot } from "./skills-snapshot.js";
|
||||
import { isLikelyInterimCronMessage } from "./subagent-followup.js";
|
||||
import { isLikelyInterimCronMessage } from "./subagent-followup-hints.js";
|
||||
|
||||
let sessionStoreRuntimePromise:
|
||||
| Promise<typeof import("../../config/sessions/store.runtime.js")>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
const SUBAGENT_FOLLOWUP_HINTS = [
|
||||
"subagent spawned",
|
||||
"spawned a subagent",
|
||||
"auto-announce when done",
|
||||
"both subagents are running",
|
||||
"wait for them to report back",
|
||||
] as const;
|
||||
|
||||
const INTERIM_CRON_HINTS = [
|
||||
"on it",
|
||||
"pulling everything together",
|
||||
"give me a few",
|
||||
"give me a few min",
|
||||
"few minutes",
|
||||
"let me compile",
|
||||
"i'll gather",
|
||||
"i will gather",
|
||||
"working on it",
|
||||
"retrying now",
|
||||
"should be about",
|
||||
"should have your summary",
|
||||
"it'll auto-announce when done",
|
||||
"it will auto-announce when done",
|
||||
...SUBAGENT_FOLLOWUP_HINTS,
|
||||
] as const;
|
||||
|
||||
function normalizeHintText(value: string): string {
|
||||
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
export function isLikelyInterimCronMessage(value: string): boolean {
|
||||
const normalized = normalizeHintText(value);
|
||||
if (!normalized) {
|
||||
// Empty text after payload filtering means the agent either returned
|
||||
// NO_REPLY (deliberately silent) or produced no deliverable content.
|
||||
// Do not treat this as an interim acknowledgement that needs a rerun.
|
||||
return false;
|
||||
}
|
||||
const words = normalized.split(" ").filter(Boolean).length;
|
||||
return words <= 45 && INTERIM_CRON_HINTS.some((hint) => normalized.includes(hint));
|
||||
}
|
||||
|
||||
export function expectsSubagentFollowup(value: string): boolean {
|
||||
const normalized = normalizeHintText(value);
|
||||
return Boolean(normalized && SUBAGENT_FOLLOWUP_HINTS.some((hint) => normalized.includes(hint)));
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export {
|
||||
readDescendantSubagentFallbackReply,
|
||||
waitForDescendantSubagentSummary,
|
||||
} from "./subagent-followup.js";
|
||||
|
|
@ -5,9 +5,8 @@ vi.hoisted(() => {
|
|||
process.env.OPENCLAW_TEST_FAST = "1";
|
||||
});
|
||||
|
||||
import { expectsSubagentFollowup, isLikelyInterimCronMessage } from "./subagent-followup-hints.js";
|
||||
import {
|
||||
expectsSubagentFollowup,
|
||||
isLikelyInterimCronMessage,
|
||||
readDescendantSubagentFallbackReply,
|
||||
waitForDescendantSubagentSummary,
|
||||
} from "./subagent-followup.js";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { listDescendantRunsForRequester } from "../../agents/subagent-registry-r
|
|||
import { readLatestAssistantReply } from "../../agents/tools/agent-step.js";
|
||||
import { SILENT_REPLY_TOKEN } from "../../auto-reply/tokens.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { expectsSubagentFollowup, isLikelyInterimCronMessage } from "./subagent-followup-hints.js";
|
||||
export { expectsSubagentFollowup, isLikelyInterimCronMessage } from "./subagent-followup-hints.js";
|
||||
|
||||
function resolveCronSubagentTimings() {
|
||||
const fastTestMode = process.env.OPENCLAW_TEST_FAST === "1";
|
||||
|
|
@ -12,53 +14,6 @@ function resolveCronSubagentTimings() {
|
|||
};
|
||||
}
|
||||
|
||||
const SUBAGENT_FOLLOWUP_HINTS = [
|
||||
"subagent spawned",
|
||||
"spawned a subagent",
|
||||
"auto-announce when done",
|
||||
"both subagents are running",
|
||||
"wait for them to report back",
|
||||
] as const;
|
||||
|
||||
const INTERIM_CRON_HINTS = [
|
||||
"on it",
|
||||
"pulling everything together",
|
||||
"give me a few",
|
||||
"give me a few min",
|
||||
"few minutes",
|
||||
"let me compile",
|
||||
"i'll gather",
|
||||
"i will gather",
|
||||
"working on it",
|
||||
"retrying now",
|
||||
"should be about",
|
||||
"should have your summary",
|
||||
"it'll auto-announce when done",
|
||||
"it will auto-announce when done",
|
||||
...SUBAGENT_FOLLOWUP_HINTS,
|
||||
] as const;
|
||||
|
||||
function normalizeHintText(value: string): string {
|
||||
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
export function isLikelyInterimCronMessage(value: string): boolean {
|
||||
const normalized = normalizeHintText(value);
|
||||
if (!normalized) {
|
||||
// Empty text after payload filtering means the agent either returned
|
||||
// NO_REPLY (deliberately silent) or produced no deliverable content.
|
||||
// Do not treat this as an interim acknowledgement that needs a rerun.
|
||||
return false;
|
||||
}
|
||||
const words = normalized.split(" ").filter(Boolean).length;
|
||||
return words <= 45 && INTERIM_CRON_HINTS.some((hint) => normalized.includes(hint));
|
||||
}
|
||||
|
||||
export function expectsSubagentFollowup(value: string): boolean {
|
||||
const normalized = normalizeHintText(value);
|
||||
return Boolean(normalized && SUBAGENT_FOLLOWUP_HINTS.some((hint) => normalized.includes(hint)));
|
||||
}
|
||||
|
||||
export async function readDescendantSubagentFallbackReply(params: {
|
||||
sessionKey: string;
|
||||
runStartedAt: number;
|
||||
|
|
|
|||
Loading…
Reference in New Issue