mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:40:44 +00:00
fix(cron): keep implicit isolated delivery out of main
This commit is contained in:
@@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Active Memory: use the configured recall timeout as the blocking prompt-build hook budget by default and move cold-start setup grace behind explicit `setupGraceTimeoutMs` config, so the plugin no longer silently extends 15000 ms configs to 45000 ms on the main lane. Fixes #75843. Thanks @vishutdhar.
|
||||
- Plugins/web-provider: reuse the active gateway plugin registry for runtime web provider resolution after deriving the same candidate plugin ids as the loader path, avoiding a redundant `loadOpenClawPlugins` call on every request while preserving origin and scope filters. Fixes #75513. Thanks @jochen.
|
||||
- Crestodian/CLI: exit non-zero when interactive Crestodian is invoked without a TTY, so scripts and CI no longer treat the setup error as success. Fixes #73646 and supersedes #73928 and #74059. Thanks @bittoby, @luyao618, and @Linux2010.
|
||||
- Cron: keep implicit/default isolated cron announce deliveries out of the main session awareness queue, so isolated jobs do not accumulate in the main conversation. Fixes #61426. Thanks @Lihannon.
|
||||
- Agents/sandbox: preserve existing workspace file modes when sandbox edits atomically replace files, so 0644 files do not collapse to 0600 after Write/Edit/apply_patch. Fixes #44077. Thanks @patosullivan.
|
||||
- Agents/models: keep legacy CLI runtime model refs such as `claude-cli/*` in the configured allowlist after canonical runtime migration, so cron `payload.model` overrides keep working. Fixes #75753. Thanks @RyanSandoval.
|
||||
- Codex/app-server: restart the shared Codex app-server client once when it closes during startup thread resume, preserving the existing thread binding instead of retrying `thread/start` on a closed client. Thanks @vincentkoc.
|
||||
|
||||
@@ -104,4 +104,35 @@ describe("runCronIsolatedAgentTurn cron delivery awareness", () => {
|
||||
expect(peekSystemEvents("global")).toEqual(["global cron digest"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not queue main-session awareness for implicit last-target delivery", async () => {
|
||||
await withTempCronHome(async (home) => {
|
||||
const storePath = await writeDefaultAgentSessionStoreEntries({
|
||||
"agent:main:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: Date.now(),
|
||||
lastProvider: "telegram",
|
||||
lastChannel: "telegram",
|
||||
lastTo: "123",
|
||||
},
|
||||
});
|
||||
const deps = createCliDeps();
|
||||
mockAgentPayloads([{ text: "implicit cron digest" }]);
|
||||
|
||||
const result = await runAnnounceTurn({
|
||||
home,
|
||||
storePath,
|
||||
sessionKey: "cron:job-1",
|
||||
deps,
|
||||
delivery: {
|
||||
mode: "announce",
|
||||
channel: "last",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(result.delivered).toBe(true);
|
||||
expect(peekSystemEvents("agent:main:main")).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -135,8 +135,12 @@ function makeBaseParams(overrides: {
|
||||
sessionTarget?: string;
|
||||
deliveryBestEffort?: boolean;
|
||||
runSessionKey?: string;
|
||||
resolvedDeliveryMode?: "explicit" | "implicit";
|
||||
}): Parameters<typeof dispatchCronDelivery>[0] {
|
||||
const resolvedDelivery = makeResolvedDelivery();
|
||||
const resolvedDelivery = {
|
||||
...makeResolvedDelivery(),
|
||||
mode: overrides.resolvedDeliveryMode ?? "explicit",
|
||||
} satisfies Extract<DeliveryTargetResolution, { ok: true }>;
|
||||
const runStartedAt = overrides.runStartedAt ?? Date.now();
|
||||
return {
|
||||
cfg: {} as never,
|
||||
@@ -422,7 +426,7 @@ describe("dispatchCronDelivery — double-announce guard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("queues main-session awareness for isolated cron jobs after delivery", async () => {
|
||||
it("queues main-session awareness for isolated cron jobs with explicit delivery targets", async () => {
|
||||
vi.mocked(countActiveDescendantRuns).mockReturnValue(0);
|
||||
vi.mocked(isLikelyInterimCronMessage).mockReturnValue(false);
|
||||
|
||||
@@ -443,6 +447,23 @@ describe("dispatchCronDelivery — double-announce guard", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("skips main-session awareness for isolated cron jobs with implicit delivery targets", async () => {
|
||||
vi.mocked(countActiveDescendantRuns).mockReturnValue(0);
|
||||
vi.mocked(isLikelyInterimCronMessage).mockReturnValue(false);
|
||||
|
||||
const params = makeBaseParams({
|
||||
synthesizedText: "Implicit cron update.",
|
||||
resolvedDeliveryMode: "implicit",
|
||||
});
|
||||
const state = await dispatchCronDelivery(params);
|
||||
|
||||
expect(state.result).toBeUndefined();
|
||||
expect(state.delivered).toBe(true);
|
||||
expect(state.deliveryAttempted).toBe(true);
|
||||
expect(deliverOutboundPayloads).toHaveBeenCalledTimes(1);
|
||||
expect(enqueueSystemEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips awareness text when direct delivery strips a silent caption", async () => {
|
||||
vi.mocked(countActiveDescendantRuns).mockReturnValue(0);
|
||||
vi.mocked(isLikelyInterimCronMessage).mockReturnValue(false);
|
||||
|
||||
@@ -361,10 +361,18 @@ function buildDirectCronDeliveryIdempotencyKey(params: {
|
||||
return `cron-direct-delivery:v1:${executionId}:${params.delivery.channel}:${accountId}:${normalizedTo}:${threadId}`;
|
||||
}
|
||||
|
||||
function shouldQueueCronAwareness(job: CronJob, deliveryBestEffort: boolean): boolean {
|
||||
// Keep issue #52136 scoped to isolated runs. Session-bound cron jobs keep
|
||||
// their existing behavior, and best-effort sends may only partially deliver.
|
||||
return job.sessionTarget === "isolated" && !deliveryBestEffort;
|
||||
function shouldQueueCronAwareness(params: {
|
||||
job: CronJob;
|
||||
delivery: SuccessfulDeliveryTarget;
|
||||
deliveryBestEffort: boolean;
|
||||
}): boolean {
|
||||
// Keep issue #52136 scoped to isolated runs with an explicit delivery target.
|
||||
// Default isolated announce delivery must not mirror text into the main session.
|
||||
return (
|
||||
params.job.sessionTarget === "isolated" &&
|
||||
!params.deliveryBestEffort &&
|
||||
params.delivery.mode === "explicit"
|
||||
);
|
||||
}
|
||||
|
||||
function resolveCronAwarenessMainSessionKey(params: {
|
||||
@@ -688,7 +696,14 @@ export async function dispatchCronDelivery(
|
||||
// Intentionally leave partial success uncached: replay may duplicate the
|
||||
// successful subset, but caching it here would permanently drop the
|
||||
// failed payloads by converting the replay into delivered=true.
|
||||
if (delivered && shouldQueueCronAwareness(params.job, params.deliveryBestEffort)) {
|
||||
if (
|
||||
delivered &&
|
||||
shouldQueueCronAwareness({
|
||||
job: params.job,
|
||||
delivery,
|
||||
deliveryBestEffort: params.deliveryBestEffort,
|
||||
})
|
||||
) {
|
||||
await queueCronAwarenessSystemEvent({
|
||||
cfg: params.cfgWithAgentDefaults,
|
||||
jobId: params.job.id,
|
||||
|
||||
Reference in New Issue
Block a user