mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:20:43 +00:00
fix(agents): keep queued announces session-only without route
This commit is contained in:
@@ -62,6 +62,9 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/subagents: keep queued subagent announces session-only when the
|
||||
requester has no external channel target, avoiding ambiguous multi-channel
|
||||
delivery failures. Fixes #59201. Thanks @larrylhollan.
|
||||
- Gateway/subagents: keep direct-loopback backend RPCs authenticated with the
|
||||
shared gateway token/password off stale CLI paired-device scope baselines, so
|
||||
internal calls no longer hit `scope-upgrade` pairing prompts while remote,
|
||||
|
||||
@@ -10,8 +10,10 @@ import {
|
||||
sendMessage as runtimeSendMessage,
|
||||
} from "./subagent-announce-delivery.runtime.js";
|
||||
import { resolveAnnounceOrigin } from "./subagent-announce-origin.js";
|
||||
import { resetAnnounceQueuesForTests } from "./subagent-announce-queue.js";
|
||||
|
||||
afterEach(() => {
|
||||
resetAnnounceQueuesForTests();
|
||||
__testing.setDepsForTest();
|
||||
});
|
||||
|
||||
@@ -226,6 +228,138 @@ describe("resolveAnnounceOrigin threaded route targets", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("deliverSubagentAnnouncement queued delivery", () => {
|
||||
async function deliverQueuedAnnouncement(params: {
|
||||
requesterOrigin?: {
|
||||
channel?: string;
|
||||
to?: string;
|
||||
accountId?: string;
|
||||
threadId?: string | number;
|
||||
};
|
||||
}) {
|
||||
const callGateway = createGatewayMock();
|
||||
let activityChecks = 0;
|
||||
__testing.setDepsForTest({
|
||||
callGateway,
|
||||
getRequesterSessionActivity: () => ({
|
||||
sessionId: "paperclip-session",
|
||||
isActive: activityChecks++ === 0,
|
||||
}),
|
||||
loadConfig: () =>
|
||||
({
|
||||
messages: {
|
||||
queue: {
|
||||
mode: "followup",
|
||||
debounceMs: 0,
|
||||
},
|
||||
},
|
||||
}) as never,
|
||||
});
|
||||
|
||||
const result = await deliverSubagentAnnouncement({
|
||||
requesterSessionKey: "agent:eng:paperclip:issue:123",
|
||||
targetRequesterSessionKey: "agent:eng:paperclip:issue:123",
|
||||
triggerMessage: "child done",
|
||||
steerMessage: "child done",
|
||||
requesterOrigin: params.requesterOrigin,
|
||||
requesterIsSubagent: false,
|
||||
expectsCompletionMessage: false,
|
||||
directIdempotencyKey: "announce-no-external-route",
|
||||
});
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
delivered: true,
|
||||
path: "queued",
|
||||
}),
|
||||
);
|
||||
await vi.waitFor(() => expect(callGateway).toHaveBeenCalledTimes(1));
|
||||
return callGateway;
|
||||
}
|
||||
|
||||
it("keeps queued announces with no external route session-only", async () => {
|
||||
const callGateway = await deliverQueuedAnnouncement({});
|
||||
|
||||
expect(callGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
method: "agent",
|
||||
params: expect.objectContaining({
|
||||
sessionKey: "agent:eng:paperclip:issue:123",
|
||||
deliver: false,
|
||||
channel: undefined,
|
||||
accountId: undefined,
|
||||
to: undefined,
|
||||
threadId: undefined,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps queued announces with channel-only origins session-only", async () => {
|
||||
const callGateway = await deliverQueuedAnnouncement({
|
||||
requesterOrigin: {
|
||||
channel: "slack",
|
||||
},
|
||||
});
|
||||
|
||||
expect(callGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
params: expect.objectContaining({
|
||||
deliver: false,
|
||||
channel: undefined,
|
||||
to: undefined,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps queued announces with internal origins session-only", async () => {
|
||||
const callGateway = await deliverQueuedAnnouncement({
|
||||
requesterOrigin: {
|
||||
channel: "webchat",
|
||||
to: "internal:room",
|
||||
accountId: "acct-1",
|
||||
threadId: "thread-1",
|
||||
},
|
||||
});
|
||||
|
||||
expect(callGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
params: expect.objectContaining({
|
||||
deliver: false,
|
||||
channel: undefined,
|
||||
accountId: undefined,
|
||||
to: undefined,
|
||||
threadId: undefined,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves queued external route fields when channel and target are present", async () => {
|
||||
const callGateway = await deliverQueuedAnnouncement({
|
||||
requesterOrigin: {
|
||||
channel: "slack",
|
||||
to: "channel:C123",
|
||||
accountId: "acct-1",
|
||||
threadId: "171.222",
|
||||
},
|
||||
});
|
||||
|
||||
expect(callGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
params: expect.objectContaining({
|
||||
deliver: true,
|
||||
channel: "slack",
|
||||
accountId: "acct-1",
|
||||
to: "channel:C123",
|
||||
threadId: "171.222",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deliverSubagentAnnouncement completion delivery", () => {
|
||||
it("keeps completion announces session-internal while preserving route context for active requesters", async () => {
|
||||
const callGateway = createGatewayMock();
|
||||
|
||||
@@ -356,6 +356,14 @@ async function sendAnnounce(item: AnnounceQueueItem) {
|
||||
const origin = item.origin;
|
||||
const threadId =
|
||||
origin?.threadId != null && origin.threadId !== "" ? String(origin.threadId) : undefined;
|
||||
const deliveryTarget = !requesterIsSubagent
|
||||
? resolveExternalBestEffortDeliveryTarget({
|
||||
channel: origin?.channel,
|
||||
to: origin?.to,
|
||||
accountId: origin?.accountId,
|
||||
threadId,
|
||||
})
|
||||
: { deliver: false };
|
||||
const idempotencyKey = buildAnnounceIdempotencyKey(
|
||||
resolveQueueAnnounceId({
|
||||
announceId: item.announceId,
|
||||
@@ -368,11 +376,11 @@ async function sendAnnounce(item: AnnounceQueueItem) {
|
||||
params: {
|
||||
sessionKey: item.sessionKey,
|
||||
message: item.prompt,
|
||||
channel: requesterIsSubagent ? undefined : origin?.channel,
|
||||
accountId: requesterIsSubagent ? undefined : origin?.accountId,
|
||||
to: requesterIsSubagent ? undefined : origin?.to,
|
||||
threadId: requesterIsSubagent ? undefined : threadId,
|
||||
deliver: !requesterIsSubagent,
|
||||
channel: deliveryTarget.deliver ? deliveryTarget.channel : undefined,
|
||||
accountId: deliveryTarget.deliver ? deliveryTarget.accountId : undefined,
|
||||
to: deliveryTarget.deliver ? deliveryTarget.to : undefined,
|
||||
threadId: deliveryTarget.deliver ? deliveryTarget.threadId : undefined,
|
||||
deliver: deliveryTarget.deliver,
|
||||
internalEvents: item.internalEvents,
|
||||
inputProvenance: {
|
||||
kind: "inter_session",
|
||||
|
||||
Reference in New Issue
Block a user