diff --git a/src/agents/acp-spawn.ts b/src/agents/acp-spawn.ts index 30aa7255664..771a7c93750 100644 --- a/src/agents/acp-spawn.ts +++ b/src/agents/acp-spawn.ts @@ -962,6 +962,7 @@ export async function spawnAcpDirect( runId: childRunId, label: params.label, task: params.task, + preferMetadata: true, status: "running", deliveryStatus: requesterInternalKey.trim() ? "pending" : "parent_missing", startedAt: Date.now(), @@ -993,6 +994,7 @@ export async function spawnAcpDirect( runId: childRunId, label: params.label, task: params.task, + preferMetadata: true, status: "running", deliveryStatus: requesterInternalKey.trim() ? "pending" : "parent_missing", startedAt: Date.now(), diff --git a/src/tasks/task-registry.test.ts b/src/tasks/task-registry.test.ts index 62ac081a84c..01bbfeb6387 100644 --- a/src/tasks/task-registry.test.ts +++ b/src/tasks/task-registry.test.ts @@ -548,6 +548,7 @@ describe("task-registry", () => { childSessionKey: "agent:main:acp:child", runId: "run-shared-delivery", task: "Spawn ACP child", + preferMetadata: true, status: "succeeded", deliveryStatus: "pending", }); @@ -561,11 +562,56 @@ describe("task-registry", () => { ); expect(findTaskByRunId("run-shared-delivery")).toMatchObject({ taskId: directTask.taskId, + task: "Spawn ACP child", deliveryStatus: "delivered", }); }); }); + it("adopts preferred ACP spawn metadata when collapsing onto an earlier direct record", async () => { + await withTempDir({ prefix: "openclaw-task-registry-" }, async (root) => { + process.env.OPENCLAW_STATE_DIR = root; + resetTaskRegistryForTests(); + + const directTask = createTaskRecord({ + runtime: "acp", + requesterSessionKey: "agent:main:main", + requesterOrigin: { + channel: "telegram", + to: "telegram:123", + }, + childSessionKey: "agent:main:acp:child", + runId: "run-collapse-preferred", + task: "Direct ACP child", + status: "running", + deliveryStatus: "pending", + }); + + const spawnedTask = createTaskRecord({ + runtime: "acp", + requesterSessionKey: "agent:main:main", + requesterOrigin: { + channel: "telegram", + to: "telegram:123", + }, + childSessionKey: "agent:main:acp:child", + runId: "run-collapse-preferred", + label: "Quant patch", + task: "Implement the feature and report back", + preferMetadata: true, + status: "running", + deliveryStatus: "pending", + }); + + expect(spawnedTask.taskId).toBe(directTask.taskId); + expect(findTaskByRunId("run-collapse-preferred")).toMatchObject({ + taskId: directTask.taskId, + label: "Quant patch", + task: "Implement the feature and report back", + }); + }); + }); + it("collapses ACP run-owned task creation onto the existing spawned task", async () => { await withTempDir({ prefix: "openclaw-task-registry-" }, async (root) => { process.env.OPENCLAW_STATE_DIR = root; diff --git a/src/tasks/task-registry.ts b/src/tasks/task-registry.ts index 767550fec1c..b945b462725 100644 --- a/src/tasks/task-registry.ts +++ b/src/tasks/task-registry.ts @@ -247,6 +247,7 @@ function mergeExistingTaskForCreate( agentId?: string; label?: string; task: string; + preferMetadata?: boolean; deliveryStatus?: TaskDeliveryStatus; notifyPolicy?: TaskNotifyPolicy; }, @@ -265,8 +266,17 @@ function mergeExistingTaskForCreate( if (params.agentId?.trim() && !existing.agentId?.trim()) { patch.agentId = params.agentId.trim(); } - if (params.label?.trim() && !existing.label?.trim()) { - patch.label = params.label.trim(); + const nextLabel = params.label?.trim(); + if (params.preferMetadata) { + if (nextLabel && normalizeComparableText(existing.label) !== nextLabel) { + patch.label = nextLabel; + } + const nextTask = params.task.trim(); + if (nextTask && normalizeComparableText(existing.task) !== nextTask) { + patch.task = nextTask; + } + } else if (nextLabel && !existing.label?.trim()) { + patch.label = nextLabel; } if (params.deliveryStatus === "pending" && existing.deliveryStatus !== "delivered") { patch.deliveryStatus = "pending"; @@ -752,6 +762,7 @@ export function createTaskRecord(params: { runId?: string; label?: string; task: string; + preferMetadata?: boolean; status?: TaskStatus; deliveryStatus?: TaskDeliveryStatus; notifyPolicy?: TaskNotifyPolicy;