mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
fix(tasks): index async media tasks by agent
This commit is contained in:
@@ -13,6 +13,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.
|
||||
- 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.
|
||||
|
||||
@@ -84,6 +84,29 @@ describe("buildTasksReply", () => {
|
||||
expect(reply.text).toContain("approval denied");
|
||||
});
|
||||
|
||||
it("lists session-backed video generation tasks for the current session", async () => {
|
||||
createRunningTaskRun({
|
||||
runtime: "cli",
|
||||
taskKind: "video_generation",
|
||||
sourceId: "video_generate:openai",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
childSessionKey: "agent:main:main",
|
||||
runId: "tool:video_generate:tasks-visible",
|
||||
label: "Video generation",
|
||||
task: "friendly lobster surfing",
|
||||
progressSummary: "Queued video generation",
|
||||
deliveryStatus: "not_applicable",
|
||||
notifyPolicy: "silent",
|
||||
});
|
||||
|
||||
const reply = await buildTasksReplyForTest();
|
||||
|
||||
expect(reply.text).toContain("Current session: 1 active · 1 total");
|
||||
expect(reply.text).toContain("🟢 Video generation");
|
||||
expect(reply.text).toContain("CLI · running");
|
||||
expect(reply.text).toContain("Queued video generation");
|
||||
});
|
||||
|
||||
it("sanitizes leaked internal runtime context from visible task details", async () => {
|
||||
createRunningTaskRun({
|
||||
runtime: "acp",
|
||||
@@ -184,6 +207,31 @@ describe("buildTasksReply", () => {
|
||||
expect(reply.text).not.toContain("hidden progress detail");
|
||||
});
|
||||
|
||||
it("counts session-backed video generation tasks in agent-local fallback", async () => {
|
||||
createRunningTaskRun({
|
||||
runtime: "cli",
|
||||
taskKind: "video_generation",
|
||||
sourceId: "video_generate:openai",
|
||||
requesterSessionKey: "agent:main:other-session",
|
||||
childSessionKey: "agent:main:other-session",
|
||||
runId: "tool:video_generate:tasks-agent-fallback",
|
||||
label: "Video generation",
|
||||
task: "hidden video background task",
|
||||
progressSummary: "Queued video generation",
|
||||
deliveryStatus: "not_applicable",
|
||||
notifyPolicy: "silent",
|
||||
});
|
||||
|
||||
const reply = await buildTasksReplyForTest({
|
||||
sessionKey: "agent:main:empty-session",
|
||||
});
|
||||
|
||||
expect(reply.text).toContain("All clear - nothing linked to this session right now.");
|
||||
expect(reply.text).toContain("Agent-local: 1 active · 1 total");
|
||||
expect(reply.text).not.toContain("hidden video background task");
|
||||
expect(reply.text).not.toContain("Queued video generation");
|
||||
});
|
||||
|
||||
it("uses the canonical target session agent for agent-local fallback counts", async () => {
|
||||
createRunningTaskRun({
|
||||
runtime: "subagent",
|
||||
|
||||
@@ -18,6 +18,30 @@ describe("captured plugin registration", () => {
|
||||
label: "Captured Provider",
|
||||
auth: [],
|
||||
});
|
||||
api.registerVideoGenerationProvider({
|
||||
id: "captured-video",
|
||||
label: "Captured Video",
|
||||
defaultModel: "captured-video-model",
|
||||
capabilities: {
|
||||
generate: { maxVideos: 1 },
|
||||
},
|
||||
generateVideo: async () => ({
|
||||
provider: "captured-video",
|
||||
model: "captured-video-model",
|
||||
videos: [],
|
||||
}),
|
||||
});
|
||||
api.registerMusicGenerationProvider({
|
||||
id: "captured-music",
|
||||
label: "Captured Music",
|
||||
defaultModel: "captured-music-model",
|
||||
capabilities: {
|
||||
generate: { maxTracks: 1 },
|
||||
},
|
||||
generateMusic: async () => ({
|
||||
tracks: [],
|
||||
}),
|
||||
});
|
||||
api.registerTextTransforms({
|
||||
input: [{ from: /red basket/g, to: "blue basket" }],
|
||||
output: [{ from: /blue basket/g, to: "red basket" }],
|
||||
@@ -54,6 +78,12 @@ describe("captured plugin registration", () => {
|
||||
|
||||
expect(captured.tools.map((tool) => tool.name)).toEqual(["captured-tool"]);
|
||||
expect(captured.providers.map((provider) => provider.id)).toEqual(["captured-provider"]);
|
||||
expect(captured.videoGenerationProviders.map((provider) => provider.id)).toEqual([
|
||||
"captured-video",
|
||||
]);
|
||||
expect(captured.musicGenerationProviders.map((provider) => provider.id)).toEqual([
|
||||
"captured-music",
|
||||
]);
|
||||
expect(captured.textTransforms).toHaveLength(1);
|
||||
expect(captured.textTransforms[0]?.input).toHaveLength(1);
|
||||
expect(captured.agentToolResultMiddlewares).toHaveLength(1);
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getTaskById,
|
||||
getTaskRegistrySummary,
|
||||
isParentFlowLinkError,
|
||||
listTasksForAgentId,
|
||||
listTasksForOwnerKey,
|
||||
listTaskRecords,
|
||||
linkTaskToFlowById,
|
||||
@@ -1407,6 +1408,29 @@ describe("task-registry", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("infers agent ids for session-scoped tasks", async () => {
|
||||
await withTaskRegistryTempDir(async (root) => {
|
||||
process.env.OPENCLAW_STATE_DIR = root;
|
||||
resetTaskRegistryForTests({ persist: false });
|
||||
|
||||
const created = createTaskRecord({
|
||||
runtime: "cli",
|
||||
taskKind: "video_generation",
|
||||
sourceId: "video_generate:openai",
|
||||
requesterSessionKey: "agent:main:discord:direct:123",
|
||||
childSessionKey: "agent:main:discord:direct:123",
|
||||
runId: "tool:video_generate:agent-index",
|
||||
task: "Generate a lobster video",
|
||||
status: "running",
|
||||
deliveryStatus: "not_applicable",
|
||||
notifyPolicy: "silent",
|
||||
});
|
||||
|
||||
expect(created.agentId).toBe("main");
|
||||
expect(listTasksForAgentId("main").map((task) => task.taskId)).toEqual([created.taskId]);
|
||||
});
|
||||
});
|
||||
|
||||
it("projects inspection-time orphaned tasks as lost without mutating the registry", async () => {
|
||||
await withTaskRegistryTempDir(async (root) => {
|
||||
process.env.OPENCLAW_STATE_DIR = root;
|
||||
|
||||
@@ -813,6 +813,18 @@ function mergeExistingTaskForCreate(
|
||||
return updateTask(existing.taskId, patch) ?? cloneTaskRecord(existing);
|
||||
}
|
||||
|
||||
function resolveTaskAgentId(params: {
|
||||
explicitAgentId?: string;
|
||||
ownerKey: string;
|
||||
requesterSessionKey: string;
|
||||
}): string | undefined {
|
||||
return (
|
||||
normalizeOptionalString(params.explicitAgentId) ??
|
||||
parseAgentSessionKey(params.ownerKey)?.agentId ??
|
||||
parseAgentSessionKey(params.requesterSessionKey)?.agentId
|
||||
);
|
||||
}
|
||||
|
||||
function taskTerminalDeliveryIdempotencyKey(task: TaskRecord): string {
|
||||
const outcome = task.status === "succeeded" ? (task.terminalOutcome ?? "default") : "default";
|
||||
return `task-terminal:${task.taskId}:${task.status}:${outcome}`;
|
||||
@@ -1493,6 +1505,11 @@ export function createTaskRecord(params: {
|
||||
requesterSessionKey,
|
||||
ownerKey: params.ownerKey,
|
||||
});
|
||||
const agentId = resolveTaskAgentId({
|
||||
explicitAgentId: params.agentId,
|
||||
ownerKey,
|
||||
requesterSessionKey,
|
||||
});
|
||||
assertTaskOwner({
|
||||
ownerKey,
|
||||
scopeKind,
|
||||
@@ -1513,7 +1530,7 @@ export function createTaskRecord(params: {
|
||||
task: params.task,
|
||||
});
|
||||
if (existing) {
|
||||
return mergeExistingTaskForCreate(existing, params);
|
||||
return mergeExistingTaskForCreate(existing, { ...params, agentId });
|
||||
}
|
||||
const now = Date.now();
|
||||
const taskId = crypto.randomUUID();
|
||||
@@ -1542,7 +1559,7 @@ export function createTaskRecord(params: {
|
||||
childSessionKey: params.childSessionKey,
|
||||
parentFlowId: normalizeOptionalString(params.parentFlowId),
|
||||
parentTaskId: normalizeOptionalString(params.parentTaskId),
|
||||
agentId: normalizeOptionalString(params.agentId),
|
||||
agentId,
|
||||
runId: normalizeOptionalString(params.runId),
|
||||
label: normalizeOptionalString(params.label),
|
||||
task: params.task,
|
||||
|
||||
Reference in New Issue
Block a user