
























11// Subagent announce delivery tests cover the last-mile routing used when child
22// runs report progress or completion back to the requester session.
3+import fs from "node:fs/promises";
4+import os from "node:os";
5+import path from "node:path";
36import { afterEach, describe, expect, it, vi } from "vitest";
47import { OutboundDeliveryError } from "../infra/outbound/deliver-types.js";
58import {
@@ -4849,3 +4852,82 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
48494852});
48504853});
48514854});
4855+4856+describe("deliverSubagentAnnouncement requester session backfill (issue #86034)", () => {
4857+// Regression: image_generate launched from a non-direct-reply turn (heartbeat,
4858+// cron, subagent spawn) supplies a completion origin missing `to`. The
4859+// already-loaded requester session entry carries `lastTo`/`lastChannel`, so
4860+// effectiveDirectOrigin must backfill from it before the deliverability gate,
4861+// otherwise generated media is silently dropped.
4862+it("backfills to/channel/accountId from the requester session entry when completion origin is missing them", async () => {
4863+const agentId = `backfill-${Date.now()}-${Math.random().toString(16).slice(2)}`;
4864+const sessionKey = `agent:${agentId}:telegram:5866004662`;
4865+const storeTemplate = path.join(
4866+os.tmpdir(),
4867+`openclaw-86034-session-${agentId}-{agentId}.json`,
4868+);
4869+const storePath = storeTemplate.replaceAll("{agentId}", agentId);
4870+await fs.writeFile(
4871+storePath,
4872+JSON.stringify(
4873+{
4874+[sessionKey]: {
4875+sessionId: "telegram-session-1",
4876+updatedAt: Date.now(),
4877+channel: "telegram",
4878+lastChannel: "telegram",
4879+lastTo: "5866004662",
4880+lastAccountId: "bot-1",
4881+},
4882+},
4883+null,
4884+2,
4885+),
4886+"utf-8",
4887+);
4888+4889+const dispatchGatewayMethodInProcess = createInProcessGatewayMock({
4890+result: { payloads: [{ text: "requester voice completion" }] },
4891+});
4892+testing.setDepsForTest({
4893+ dispatchGatewayMethodInProcess,
4894+getRequesterSessionActivity: () => ({
4895+sessionId: "telegram-session-1",
4896+isActive: false,
4897+}),
4898+getRuntimeConfig: () => ({ session: { store: storeTemplate } }) as never,
4899+});
4900+4901+const result = await deliverSubagentAnnouncement({
4902+requesterSessionKey: sessionKey,
4903+targetRequesterSessionKey: sessionKey,
4904+triggerMessage: "image done",
4905+steerMessage: "image done",
4906+// Origin carries channel/accountId but NOT `to` — simulates an
4907+// image_generate task created off the direct-reply path.
4908+requesterOrigin: { channel: "telegram", accountId: "bot-1" },
4909+requesterSessionOrigin: { channel: "telegram", accountId: "bot-1" },
4910+completionDirectOrigin: { channel: "telegram", accountId: "bot-1" },
4911+directOrigin: { channel: "telegram", accountId: "bot-1" },
4912+requesterIsSubagent: false,
4913+expectsCompletionMessage: true,
4914+bestEffortDeliver: true,
4915+directIdempotencyKey: "announce-86034-backfill",
4916+sourceTool: "image_generate",
4917+});
4918+4919+expectRecordFields(result, {
4920+delivered: true,
4921+path: "direct",
4922+});
4923+// The deliverability decision must see the backfilled `to`.
4924+expectInProcessAgentParams(dispatchGatewayMethodInProcess, {
4925+deliver: true,
4926+channel: "telegram",
4927+accountId: "bot-1",
4928+to: "5866004662",
4929+});
4930+4931+await fs.rm(storePath, { force: true });
4932+});
4933+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。