mirror of https://github.com/openclaw/openclaw.git
feat(slack): include thread metadata (thread_ts, parent_user_id) in agent context
Adds thread_ts and parent_user_id to the Slack message footer for thread replies, giving agents awareness of thread context. Top-level messages remain unchanged. Includes tests verifying: - Thread replies include thread_ts and parent_user_id in footer - Top-level messages exclude thread metadata
This commit is contained in:
parent
23e4183608
commit
2b9d5e6e30
|
|
@ -235,4 +235,149 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
|||
expect(prepared).toBeTruthy();
|
||||
expect(prepared!.ctxPayload.MessageThreadId).toBe("1.000");
|
||||
});
|
||||
|
||||
it("includes thread_ts and parent_user_id metadata in thread replies", async () => {
|
||||
const slackCtx = createSlackMonitorContext({
|
||||
cfg: {
|
||||
channels: { slack: { enabled: true } },
|
||||
} as OpenClawConfig,
|
||||
accountId: "default",
|
||||
botToken: "token",
|
||||
app: { client: {} } as App,
|
||||
runtime: {} as RuntimeEnv,
|
||||
botUserId: "B1",
|
||||
teamId: "T1",
|
||||
apiAppId: "A1",
|
||||
historyLimit: 0,
|
||||
sessionScope: "per-sender",
|
||||
mainKey: "main",
|
||||
dmEnabled: true,
|
||||
dmPolicy: "open",
|
||||
allowFrom: [],
|
||||
groupDmEnabled: true,
|
||||
groupDmChannels: [],
|
||||
defaultRequireMention: true,
|
||||
groupPolicy: "open",
|
||||
useAccessGroups: false,
|
||||
reactionMode: "off",
|
||||
reactionAllowlist: [],
|
||||
replyToMode: "off",
|
||||
threadHistoryScope: "thread",
|
||||
threadInheritParent: false,
|
||||
slashCommand: {
|
||||
enabled: false,
|
||||
name: "openclaw",
|
||||
sessionPrefix: "slack:slash",
|
||||
ephemeral: true,
|
||||
},
|
||||
textLimit: 4000,
|
||||
ackReactionScope: "group-mentions",
|
||||
mediaMaxBytes: 1024,
|
||||
removeAckAfterReply: false,
|
||||
});
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
slackCtx.resolveUserName = async () => ({ name: "Alice" }) as any;
|
||||
|
||||
const account: ResolvedSlackAccount = {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
appTokenSource: "config",
|
||||
config: {},
|
||||
};
|
||||
|
||||
const message: SlackMessageEvent = {
|
||||
channel: "D123",
|
||||
channel_type: "im",
|
||||
user: "U1",
|
||||
text: "this is a reply",
|
||||
ts: "1.002",
|
||||
thread_ts: "1.000",
|
||||
parent_user_id: "U2",
|
||||
} as SlackMessageEvent;
|
||||
|
||||
const prepared = await prepareSlackMessage({
|
||||
ctx: slackCtx,
|
||||
account,
|
||||
message,
|
||||
opts: { source: "message" },
|
||||
});
|
||||
|
||||
expect(prepared).toBeTruthy();
|
||||
// Verify thread metadata is in the message footer
|
||||
expect(prepared!.ctxPayload.Body).toMatch(
|
||||
/\[slack message id: 1\.002 channel: D123 thread_ts: 1\.000 parent_user: U2\]/,
|
||||
);
|
||||
});
|
||||
|
||||
it("excludes thread_ts from top-level messages", async () => {
|
||||
const slackCtx = createSlackMonitorContext({
|
||||
cfg: {
|
||||
channels: { slack: { enabled: true } },
|
||||
} as OpenClawConfig,
|
||||
accountId: "default",
|
||||
botToken: "token",
|
||||
app: { client: {} } as App,
|
||||
runtime: {} as RuntimeEnv,
|
||||
botUserId: "B1",
|
||||
teamId: "T1",
|
||||
apiAppId: "A1",
|
||||
historyLimit: 0,
|
||||
sessionScope: "per-sender",
|
||||
mainKey: "main",
|
||||
dmEnabled: true,
|
||||
dmPolicy: "open",
|
||||
allowFrom: [],
|
||||
groupDmEnabled: true,
|
||||
groupDmChannels: [],
|
||||
defaultRequireMention: true,
|
||||
groupPolicy: "open",
|
||||
useAccessGroups: false,
|
||||
reactionMode: "off",
|
||||
reactionAllowlist: [],
|
||||
replyToMode: "off",
|
||||
threadHistoryScope: "thread",
|
||||
threadInheritParent: false,
|
||||
slashCommand: {
|
||||
enabled: false,
|
||||
name: "openclaw",
|
||||
sessionPrefix: "slack:slash",
|
||||
ephemeral: true,
|
||||
},
|
||||
textLimit: 4000,
|
||||
ackReactionScope: "group-mentions",
|
||||
mediaMaxBytes: 1024,
|
||||
removeAckAfterReply: false,
|
||||
});
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
slackCtx.resolveUserName = async () => ({ name: "Alice" }) as any;
|
||||
|
||||
const account: ResolvedSlackAccount = {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
appTokenSource: "config",
|
||||
config: {},
|
||||
};
|
||||
|
||||
const message: SlackMessageEvent = {
|
||||
channel: "D123",
|
||||
channel_type: "im",
|
||||
user: "U1",
|
||||
text: "hello",
|
||||
ts: "1.000",
|
||||
} as SlackMessageEvent;
|
||||
|
||||
const prepared = await prepareSlackMessage({
|
||||
ctx: slackCtx,
|
||||
account,
|
||||
message,
|
||||
opts: { source: "message" },
|
||||
});
|
||||
|
||||
expect(prepared).toBeTruthy();
|
||||
// Top-level messages should NOT have thread_ts in the footer
|
||||
expect(prepared!.ctxPayload.Body).toMatch(/\[slack message id: 1\.000 channel: D123\]$/);
|
||||
expect(prepared!.ctxPayload.Body).not.toContain("thread_ts");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -399,7 +399,10 @@ export async function prepareSlackMessage(params: {
|
|||
GroupSubject: isRoomish ? roomLabel : undefined,
|
||||
From: slackFrom,
|
||||
}) ?? (isDirectMessage ? senderName : roomLabel);
|
||||
const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}]`;
|
||||
const threadInfo = message.thread_ts
|
||||
? ` thread_ts: ${message.thread_ts}${message.parent_user_id ? ` parent_user: ${message.parent_user_id}` : ""}`
|
||||
: "";
|
||||
const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
|
||||
const storePath = resolveStorePath(ctx.cfg.session?.store, {
|
||||
agentId: route.agentId,
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue