mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:00:43 +00:00
fix(agents): keep media generation tasks fresh
This commit is contained in:
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Tasks/media: infer agent ownership for session-scoped task records so `/tasks` agent-local fallback includes session-backed `video_generate` and other async media jobs even when the current chat session has no linked rows. Thanks @vincentkoc.
|
||||
- Agents/media: keep long-running `video_generate` and `music_generate` tasks fresh while provider jobs are still pending, so task maintenance does not mark active Discord media renders lost before completion. Thanks @vincentkoc.
|
||||
- Plugins/inspector: keep bundled plugin runtime capture quiet and config-tolerant for Codex, memory-lancedb, Feishu, Mattermost, QQBot, and Tlon so plugin-inspector JSON checks can validate the full bundled set. Thanks @vincentkoc.
|
||||
- Slack/auto-reply: keep fully consumed text reset triggers such as `new session` out of `BodyForAgent` after directive cleanup, so configured Slack reset phrases do not leak into the fresh model turn. Fixes #73137. Thanks @neeravmakwana.
|
||||
- Plugins/runtime deps: prune stale retained bundled runtime deps and keep doctor/secret channel contract scans on lightweight artifacts, so disabled bundled channels stop preserving old dependency trees or importing heavy plugin surfaces. Thanks @SymbolStar and @vincentkoc.
|
||||
|
||||
@@ -18,6 +18,7 @@ import { formatAgentInternalEventsForPrompt, type AgentInternalEvent } from "../
|
||||
import { deliverSubagentAnnouncement } from "../subagent-announce-delivery.js";
|
||||
|
||||
const log = createSubsystemLogger("agents/tools/media-generate-background-shared");
|
||||
const MEDIA_GENERATION_TASK_KEEPALIVE_INTERVAL_MS = 60_000;
|
||||
|
||||
export type MediaGenerationTaskHandle = {
|
||||
taskId: string;
|
||||
@@ -133,6 +134,30 @@ export function recordMediaGenerationTaskProgress(params: {
|
||||
});
|
||||
}
|
||||
|
||||
export async function withMediaGenerationTaskKeepalive<T>(params: {
|
||||
handle: MediaGenerationTaskHandle | null;
|
||||
progressSummary: string;
|
||||
eventSummary?: string;
|
||||
run: () => Promise<T>;
|
||||
}): Promise<T> {
|
||||
if (!params.handle) {
|
||||
return await params.run();
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
recordMediaGenerationTaskProgress({
|
||||
handle: params.handle,
|
||||
progressSummary: params.progressSummary,
|
||||
eventSummary: params.eventSummary,
|
||||
});
|
||||
}, MEDIA_GENERATION_TASK_KEEPALIVE_INTERVAL_MS);
|
||||
interval.unref?.();
|
||||
try {
|
||||
return await params.run();
|
||||
} finally {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
export function completeMediaGenerationTaskRun(params: {
|
||||
handle: MediaGenerationTaskHandle | null;
|
||||
provider: string;
|
||||
|
||||
@@ -28,6 +28,7 @@ import type { DeliveryContext } from "../../utils/delivery-context.js";
|
||||
import { buildTimeoutAbortSignal } from "../../utils/fetch-timeout.js";
|
||||
import { ToolInputError, readNumberParam, readStringParam } from "./common.js";
|
||||
import { decodeDataUrl } from "./image-tool.helpers.js";
|
||||
import { withMediaGenerationTaskKeepalive } from "./media-generate-background-shared.js";
|
||||
import {
|
||||
applyMusicGenerationModelConfigDefaults,
|
||||
buildMediaReferenceDetails,
|
||||
@@ -586,19 +587,24 @@ export function createMusicGenerateTool(options?: {
|
||||
if (shouldDetach) {
|
||||
scheduleBackgroundWork(async () => {
|
||||
try {
|
||||
const executed = await executeMusicGenerationJob({
|
||||
effectiveCfg,
|
||||
prompt,
|
||||
agentDir: options?.agentDir,
|
||||
model,
|
||||
lyrics,
|
||||
instrumental,
|
||||
durationSeconds,
|
||||
format,
|
||||
filename,
|
||||
loadedReferenceImages,
|
||||
taskHandle,
|
||||
timeoutMs,
|
||||
const executed = await withMediaGenerationTaskKeepalive({
|
||||
handle: taskHandle,
|
||||
progressSummary: "Generating music",
|
||||
run: () =>
|
||||
executeMusicGenerationJob({
|
||||
effectiveCfg,
|
||||
prompt,
|
||||
agentDir: options?.agentDir,
|
||||
model,
|
||||
lyrics,
|
||||
instrumental,
|
||||
durationSeconds,
|
||||
format,
|
||||
filename,
|
||||
loadedReferenceImages,
|
||||
taskHandle,
|
||||
timeoutMs,
|
||||
}),
|
||||
});
|
||||
completeMusicGenerationTaskRun({
|
||||
handle: taskHandle,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { VIDEO_GENERATION_TASK_KIND } from "../video-generation-task-status.js";
|
||||
import {
|
||||
announceDeliveryMocks,
|
||||
@@ -21,6 +21,7 @@ const {
|
||||
recordVideoGenerationTaskProgress,
|
||||
wakeVideoGenerationTaskCompletion,
|
||||
} = await import("./video-generate-background.js");
|
||||
const { withMediaGenerationTaskKeepalive } = await import("./media-generate-background-shared.js");
|
||||
|
||||
describe("video generate background helpers", () => {
|
||||
beforeEach(() => {
|
||||
@@ -31,6 +32,10 @@ describe("video generate background helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("creates a running task with queued progress text", () => {
|
||||
taskExecutorMocks.createRunningTaskRun.mockReturnValue({
|
||||
taskId: "task-123",
|
||||
@@ -77,6 +82,42 @@ describe("video generate background helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps long-running media tasks fresh while provider work is pending", async () => {
|
||||
vi.useFakeTimers();
|
||||
let resolveRun!: (value: string) => void;
|
||||
const runPromise = new Promise<string>((resolve) => {
|
||||
resolveRun = resolve;
|
||||
});
|
||||
const task = withMediaGenerationTaskKeepalive({
|
||||
handle: {
|
||||
taskId: "task-123",
|
||||
runId: "tool:video_generate:abc",
|
||||
requesterSessionKey: "agent:main:discord:direct:123",
|
||||
taskLabel: "friendly lobster surfing",
|
||||
},
|
||||
progressSummary: "Generating video",
|
||||
run: () => runPromise,
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
|
||||
expectRecordedTaskProgress({
|
||||
taskExecutorMocks,
|
||||
runId: "tool:video_generate:abc",
|
||||
progressSummary: "Generating video",
|
||||
});
|
||||
|
||||
resolveRun("done");
|
||||
await expect(task).resolves.toBe("done");
|
||||
const callsAfterCompletion = taskExecutorMocks.recordTaskRunProgressByRunId.mock.calls.length;
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
|
||||
expect(taskExecutorMocks.recordTaskRunProgressByRunId).toHaveBeenCalledTimes(
|
||||
callsAfterCompletion,
|
||||
);
|
||||
});
|
||||
|
||||
it("queues a completion event by default when direct send is disabled", async () => {
|
||||
announceDeliveryMocks.deliverSubagentAnnouncement.mockResolvedValue({
|
||||
delivered: true,
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {
|
||||
} from "../../video-generation/types.js";
|
||||
import { ToolInputError, readNumberParam, readStringParam } from "./common.js";
|
||||
import { decodeDataUrl } from "./image-tool.helpers.js";
|
||||
import { withMediaGenerationTaskKeepalive } from "./media-generate-background-shared.js";
|
||||
import {
|
||||
applyVideoGenerationModelConfigDefaults,
|
||||
buildMediaReferenceDetails,
|
||||
@@ -982,24 +983,29 @@ export function createVideoGenerateTool(options?: {
|
||||
if (shouldDetach) {
|
||||
scheduleBackgroundWork(async () => {
|
||||
try {
|
||||
const executed = await executeVideoGenerationJob({
|
||||
effectiveCfg,
|
||||
prompt,
|
||||
agentDir: options?.agentDir,
|
||||
model,
|
||||
size,
|
||||
aspectRatio,
|
||||
resolution,
|
||||
durationSeconds,
|
||||
audio,
|
||||
watermark,
|
||||
filename,
|
||||
loadedReferenceImages,
|
||||
loadedReferenceVideos,
|
||||
loadedReferenceAudios,
|
||||
taskHandle,
|
||||
providerOptions,
|
||||
timeoutMs,
|
||||
const executed = await withMediaGenerationTaskKeepalive({
|
||||
handle: taskHandle,
|
||||
progressSummary: "Generating video",
|
||||
run: () =>
|
||||
executeVideoGenerationJob({
|
||||
effectiveCfg,
|
||||
prompt,
|
||||
agentDir: options?.agentDir,
|
||||
model,
|
||||
size,
|
||||
aspectRatio,
|
||||
resolution,
|
||||
durationSeconds,
|
||||
audio,
|
||||
watermark,
|
||||
filename,
|
||||
loadedReferenceImages,
|
||||
loadedReferenceVideos,
|
||||
loadedReferenceAudios,
|
||||
taskHandle,
|
||||
providerOptions,
|
||||
timeoutMs,
|
||||
}),
|
||||
});
|
||||
completeVideoGenerationTaskRun({
|
||||
handle: taskHandle,
|
||||
|
||||
Reference in New Issue
Block a user