Skip to content

Commit 210cccb

Browse files
committed
fix(tasks): index async media tasks by agent
1 parent a6bb026 commit 210cccb

5 files changed

Lines changed: 122 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
1313

1414
### Fixes
1515

16+
- 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.
1617
- 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.
1718
- 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.
1819
- 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.

src/auto-reply/reply/commands-tasks.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,29 @@ describe("buildTasksReply", () => {
8484
expect(reply.text).toContain("approval denied");
8585
});
8686

87+
it("lists session-backed video generation tasks for the current session", async () => {
88+
createRunningTaskRun({
89+
runtime: "cli",
90+
taskKind: "video_generation",
91+
sourceId: "video_generate:openai",
92+
requesterSessionKey: "agent:main:main",
93+
childSessionKey: "agent:main:main",
94+
runId: "tool:video_generate:tasks-visible",
95+
label: "Video generation",
96+
task: "friendly lobster surfing",
97+
progressSummary: "Queued video generation",
98+
deliveryStatus: "not_applicable",
99+
notifyPolicy: "silent",
100+
});
101+
102+
const reply = await buildTasksReplyForTest();
103+
104+
expect(reply.text).toContain("Current session: 1 active · 1 total");
105+
expect(reply.text).toContain("🟢 Video generation");
106+
expect(reply.text).toContain("CLI · running");
107+
expect(reply.text).toContain("Queued video generation");
108+
});
109+
87110
it("sanitizes leaked internal runtime context from visible task details", async () => {
88111
createRunningTaskRun({
89112
runtime: "acp",
@@ -184,6 +207,31 @@ describe("buildTasksReply", () => {
184207
expect(reply.text).not.toContain("hidden progress detail");
185208
});
186209

210+
it("counts session-backed video generation tasks in agent-local fallback", async () => {
211+
createRunningTaskRun({
212+
runtime: "cli",
213+
taskKind: "video_generation",
214+
sourceId: "video_generate:openai",
215+
requesterSessionKey: "agent:main:other-session",
216+
childSessionKey: "agent:main:other-session",
217+
runId: "tool:video_generate:tasks-agent-fallback",
218+
label: "Video generation",
219+
task: "hidden video background task",
220+
progressSummary: "Queued video generation",
221+
deliveryStatus: "not_applicable",
222+
notifyPolicy: "silent",
223+
});
224+
225+
const reply = await buildTasksReplyForTest({
226+
sessionKey: "agent:main:empty-session",
227+
});
228+
229+
expect(reply.text).toContain("All clear - nothing linked to this session right now.");
230+
expect(reply.text).toContain("Agent-local: 1 active · 1 total");
231+
expect(reply.text).not.toContain("hidden video background task");
232+
expect(reply.text).not.toContain("Queued video generation");
233+
});
234+
187235
it("uses the canonical target session agent for agent-local fallback counts", async () => {
188236
createRunningTaskRun({
189237
runtime: "subagent",

src/plugins/captured-registration.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@ describe("captured plugin registration", () => {
1818
label: "Captured Provider",
1919
auth: [],
2020
});
21+
api.registerVideoGenerationProvider({
22+
id: "captured-video",
23+
label: "Captured Video",
24+
defaultModel: "captured-video-model",
25+
capabilities: {
26+
generate: { maxVideos: 1 },
27+
},
28+
generateVideo: async () => ({
29+
provider: "captured-video",
30+
model: "captured-video-model",
31+
videos: [],
32+
}),
33+
});
34+
api.registerMusicGenerationProvider({
35+
id: "captured-music",
36+
label: "Captured Music",
37+
defaultModel: "captured-music-model",
38+
capabilities: {
39+
generate: { maxTracks: 1 },
40+
},
41+
generateMusic: async () => ({
42+
tracks: [],
43+
}),
44+
});
2145
api.registerTextTransforms({
2246
input: [{ from: /red basket/g, to: "blue basket" }],
2347
output: [{ from: /blue basket/g, to: "red basket" }],
@@ -54,6 +78,12 @@ describe("captured plugin registration", () => {
5478

5579
expect(captured.tools.map((tool) => tool.name)).toEqual(["captured-tool"]);
5680
expect(captured.providers.map((provider) => provider.id)).toEqual(["captured-provider"]);
81+
expect(captured.videoGenerationProviders.map((provider) => provider.id)).toEqual([
82+
"captured-video",
83+
]);
84+
expect(captured.musicGenerationProviders.map((provider) => provider.id)).toEqual([
85+
"captured-music",
86+
]);
5787
expect(captured.textTransforms).toHaveLength(1);
5888
expect(captured.textTransforms[0]?.input).toHaveLength(1);
5989
expect(captured.agentToolResultMiddlewares).toHaveLength(1);

src/tasks/task-registry.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
getTaskById,
2626
getTaskRegistrySummary,
2727
isParentFlowLinkError,
28+
listTasksForAgentId,
2829
listTasksForOwnerKey,
2930
listTaskRecords,
3031
linkTaskToFlowById,
@@ -1407,6 +1408,29 @@ describe("task-registry", () => {
14071408
});
14081409
});
14091410

1411+
it("infers agent ids for session-scoped tasks", async () => {
1412+
await withTaskRegistryTempDir(async (root) => {
1413+
process.env.OPENCLAW_STATE_DIR = root;
1414+
resetTaskRegistryForTests({ persist: false });
1415+
1416+
const created = createTaskRecord({
1417+
runtime: "cli",
1418+
taskKind: "video_generation",
1419+
sourceId: "video_generate:openai",
1420+
requesterSessionKey: "agent:main:discord:direct:123",
1421+
childSessionKey: "agent:main:discord:direct:123",
1422+
runId: "tool:video_generate:agent-index",
1423+
task: "Generate a lobster video",
1424+
status: "running",
1425+
deliveryStatus: "not_applicable",
1426+
notifyPolicy: "silent",
1427+
});
1428+
1429+
expect(created.agentId).toBe("main");
1430+
expect(listTasksForAgentId("main").map((task) => task.taskId)).toEqual([created.taskId]);
1431+
});
1432+
});
1433+
14101434
it("projects inspection-time orphaned tasks as lost without mutating the registry", async () => {
14111435
await withTaskRegistryTempDir(async (root) => {
14121436
process.env.OPENCLAW_STATE_DIR = root;

src/tasks/task-registry.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,18 @@ function mergeExistingTaskForCreate(
813813
return updateTask(existing.taskId, patch) ?? cloneTaskRecord(existing);
814814
}
815815

816+
function resolveTaskAgentId(params: {
817+
explicitAgentId?: string;
818+
ownerKey: string;
819+
requesterSessionKey: string;
820+
}): string | undefined {
821+
return (
822+
normalizeOptionalString(params.explicitAgentId) ??
823+
parseAgentSessionKey(params.ownerKey)?.agentId ??
824+
parseAgentSessionKey(params.requesterSessionKey)?.agentId
825+
);
826+
}
827+
816828
function taskTerminalDeliveryIdempotencyKey(task: TaskRecord): string {
817829
const outcome = task.status === "succeeded" ? (task.terminalOutcome ?? "default") : "default";
818830
return `task-terminal:${task.taskId}:${task.status}:${outcome}`;
@@ -1493,6 +1505,11 @@ export function createTaskRecord(params: {
14931505
requesterSessionKey,
14941506
ownerKey: params.ownerKey,
14951507
});
1508+
const agentId = resolveTaskAgentId({
1509+
explicitAgentId: params.agentId,
1510+
ownerKey,
1511+
requesterSessionKey,
1512+
});
14961513
assertTaskOwner({
14971514
ownerKey,
14981515
scopeKind,
@@ -1513,7 +1530,7 @@ export function createTaskRecord(params: {
15131530
task: params.task,
15141531
});
15151532
if (existing) {
1516-
return mergeExistingTaskForCreate(existing, params);
1533+
return mergeExistingTaskForCreate(existing, { ...params, agentId });
15171534
}
15181535
const now = Date.now();
15191536
const taskId = crypto.randomUUID();
@@ -1542,7 +1559,7 @@ export function createTaskRecord(params: {
15421559
childSessionKey: params.childSessionKey,
15431560
parentFlowId: normalizeOptionalString(params.parentFlowId),
15441561
parentTaskId: normalizeOptionalString(params.parentTaskId),
1545-
agentId: normalizeOptionalString(params.agentId),
1562+
agentId,
15461563
runId: normalizeOptionalString(params.runId),
15471564
label: normalizeOptionalString(params.label),
15481565
task: params.task,

0 commit comments

Comments
 (0)