mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 08:12:21 +00:00
fix: deliver subagent completion announces to Slack without invalid thread_ts (#31105)
* fix(subagent): avoid invalid Slack thread_ts for bound completion announces * build: regenerate host env security policy swift --------- Co-authored-by: User <user@example.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -849,6 +849,67 @@ describe("subagent announce formatting", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("does not force Slack threadId from bound conversation id", async () => {
|
||||
sendSpy.mockClear();
|
||||
agentSpy.mockClear();
|
||||
sessionStore = {
|
||||
"agent:main:subagent:test": {
|
||||
sessionId: "child-session-slack-bound",
|
||||
},
|
||||
"agent:main:main": {
|
||||
sessionId: "requester-session-slack-bound",
|
||||
},
|
||||
};
|
||||
chatHistoryMock.mockResolvedValueOnce({
|
||||
messages: [{ role: "assistant", content: [{ type: "text", text: "done" }] }],
|
||||
});
|
||||
registerSessionBindingAdapter({
|
||||
channel: "slack",
|
||||
accountId: "acct-1",
|
||||
listBySession: (targetSessionKey: string) =>
|
||||
targetSessionKey === "agent:main:subagent:test"
|
||||
? [
|
||||
{
|
||||
bindingId: "slack:acct-1:C123",
|
||||
targetSessionKey,
|
||||
targetKind: "subagent",
|
||||
conversation: {
|
||||
channel: "slack",
|
||||
accountId: "acct-1",
|
||||
conversationId: "C123",
|
||||
},
|
||||
status: "active",
|
||||
boundAt: Date.now(),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
resolveByConversation: () => null,
|
||||
});
|
||||
|
||||
const didAnnounce = await runSubagentAnnounceFlow({
|
||||
childSessionKey: "agent:main:subagent:test",
|
||||
childRunId: "run-direct-slack-bound",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
requesterOrigin: {
|
||||
channel: "slack",
|
||||
to: "channel:C123",
|
||||
accountId: "acct-1",
|
||||
},
|
||||
...defaultOutcomeAnnounce,
|
||||
expectsCompletionMessage: true,
|
||||
spawnMode: "session",
|
||||
});
|
||||
|
||||
expect(didAnnounce).toBe(true);
|
||||
expect(sendSpy).toHaveBeenCalledTimes(1);
|
||||
expect(agentSpy).not.toHaveBeenCalled();
|
||||
const call = sendSpy.mock.calls[0]?.[0] as { params?: Record<string, unknown> };
|
||||
expect(call?.params?.channel).toBe("slack");
|
||||
expect(call?.params?.to).toBe("channel:C123");
|
||||
expect(call?.params?.threadId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("routes manual completion direct-send for telegram forum topics", async () => {
|
||||
sendSpy.mockClear();
|
||||
agentSpy.mockClear();
|
||||
|
||||
@@ -517,7 +517,14 @@ async function resolveSubagentCompletionOrigin(params: {
|
||||
channel: route.binding.conversation.channel,
|
||||
accountId: route.binding.conversation.accountId,
|
||||
to: `channel:${route.binding.conversation.conversationId}`,
|
||||
threadId: route.binding.conversation.conversationId,
|
||||
// `conversationId` identifies the target conversation (channel/DM/thread),
|
||||
// but it is not always a thread identifier. Passing it as `threadId` breaks
|
||||
// Slack DM/top-level delivery by forcing an invalid thread_ts. Preserve only
|
||||
// explicit requester thread hints for channels that actually use threading.
|
||||
threadId:
|
||||
requesterOrigin?.threadId != null && requesterOrigin.threadId !== ""
|
||||
? String(requesterOrigin.threadId)
|
||||
: undefined,
|
||||
};
|
||||
return {
|
||||
// Bound target is authoritative; requester hints fill only missing fields.
|
||||
|
||||
Reference in New Issue
Block a user