fix(qa): preserve structured thread action targets

This commit is contained in:
Dallin Romney
2026-08-21 03:14:12 -07:00
parent 54a289000b
commit 81bcf11e08
4 changed files with 34 additions and 16 deletions
@@ -74,10 +74,12 @@ describe("qa-channel direct message actions", () => {
const threadPayload = extractToolPayload(threadResult) as {
thread: { id: string; title: string };
target: string;
threadId: string;
};
expect(threadPayload.thread.id).toMatch(/^thread-/);
expect(threadPayload.thread.title).toBe("QA thread");
expect(threadPayload.target).toContain(threadPayload.thread.id);
expect(threadPayload.target).toBe("channel:qa-room");
expect(threadPayload.threadId).toBe(threadPayload.thread.id);
const replyResult = await handleAction({
channel: "qa-channel",
@@ -86,6 +88,7 @@ describe("qa-channel direct message actions", () => {
accountId: "default",
params: {
target: threadPayload.target,
threadId: threadPayload.threadId,
message: "thread reply",
text: "ignored legacy reply",
},
@@ -111,6 +114,7 @@ describe("qa-channel direct message actions", () => {
accountId: "default",
params: {
to: threadPayload.target,
threadId: threadPayload.threadId,
messageId: outbound.id,
emoji: "white_check_mark",
},
@@ -123,6 +127,7 @@ describe("qa-channel direct message actions", () => {
accountId: "default",
params: {
target: threadPayload.target,
threadId: threadPayload.threadId,
messageId: outbound.id,
message: "message (edited)",
text: "ignored legacy edit",
@@ -136,6 +141,7 @@ describe("qa-channel direct message actions", () => {
accountId: "default",
params: {
to: threadPayload.target,
threadId: threadPayload.threadId,
messageId: outbound.id,
},
});
@@ -165,6 +171,7 @@ describe("qa-channel direct message actions", () => {
accountId: "default",
params: {
to: threadPayload.target,
threadId: threadPayload.threadId,
messageId: outbound.id,
},
});
@@ -188,7 +195,7 @@ describe("qa-channel direct message actions", () => {
cfg,
accountId: "default",
params: {
target: "channel:canonical-room",
target: "channel:canonical/room",
to: "channel:legacy-to-room",
channelId: "legacy-channel-id-room",
threadName: "Canonical target thread",
@@ -197,10 +204,12 @@ describe("qa-channel direct message actions", () => {
const threadPayload = extractToolPayload(threadResult) as {
thread: { id: string; conversationId: string };
target: string;
threadId: string;
};
expect(threadPayload.thread.conversationId).toBe("canonical-room");
expect(threadPayload.target).toBe(`thread:canonical-room/${threadPayload.thread.id}`);
expect(threadPayload.thread.conversationId).toBe("canonical/room");
expect(threadPayload.target).toBe("channel:canonical/room");
expect(threadPayload.threadId).toBe(threadPayload.thread.id);
const replyResult = await handleAction({
channel: "qa-channel",
@@ -209,13 +218,14 @@ describe("qa-channel direct message actions", () => {
accountId: "default",
params: {
target: threadPayload.target,
threadId: threadPayload.threadId,
channelId: "legacy-reply-room",
message: "canonical target reply",
},
});
expect(extractToolPayload(replyResult)).toMatchObject({
message: {
conversation: { id: "canonical-room", kind: "channel" },
conversation: { id: "canonical/room", kind: "channel" },
text: "canonical target reply",
threadId: threadPayload.thread.id,
},
@@ -246,6 +256,7 @@ describe("qa-channel direct message actions", () => {
const threadPayload = extractToolPayload(threadResult) as {
thread: { id: string; title: string };
target: string;
threadId: string;
};
expect(threadPayload.thread.title).toBe("Legacy thread");
@@ -279,6 +290,7 @@ describe("qa-channel direct message actions", () => {
accountId: "default",
params: {
to: threadPayload.target,
threadId: threadPayload.threadId,
messageId: outbound.id,
text: "legacy edit",
},
+5 -1
View File
@@ -214,7 +214,11 @@ export const qaChannelMessageActions: ChannelMessageActionAdapter = {
});
return jsonResult({
thread,
target: `thread:${target.conversationId}/${thread.id}`,
target: buildQaTarget({
chatType: target.conversationKind,
conversationId: target.conversationId,
}),
threadId: thread.id,
});
}
case "thread-reply": {
+9 -4
View File
@@ -7,6 +7,7 @@ export function createQaSelfCheckScenario(options?: {
}): QaScenarioDefinition {
const waitTimeoutMs = options?.waitTimeoutMs ?? 5_000;
let lifecycleTarget: string | undefined;
let lifecycleThreadId: string | undefined;
return {
name: "Synthetic Slack-class roundtrip",
steps: [
@@ -39,12 +40,13 @@ export function createQaSelfCheckScenario(options?: {
});
const threadPayload = extractQaToolPayload(
threadResult as Parameters<typeof extractQaToolPayload>[0],
) as { target?: string; thread?: { id?: string } } | undefined;
const threadId = threadPayload?.thread?.id;
if (!threadId || !threadPayload?.target) {
) as { target?: string; threadId?: string; thread?: { id?: string } } | undefined;
const threadId = threadPayload?.threadId;
if (!threadId || threadId !== threadPayload?.thread?.id || !threadPayload.target) {
throw new Error("thread-create did not return thread id and target");
}
lifecycleTarget = threadPayload.target;
lifecycleThreadId = threadId;
await state.addInboundMessage({
conversation: { id: "qa-room", kind: "channel", title: "QA Room" },
@@ -78,12 +80,13 @@ export function createQaSelfCheckScenario(options?: {
if (!outboundMessage) {
throw new Error("threaded outbound message not found");
}
if (!lifecycleTarget) {
if (!lifecycleTarget || !lifecycleThreadId) {
throw new Error("thread target not found");
}
await performAction("react", {
to: lifecycleTarget,
threadId: lifecycleThreadId,
messageId: outboundMessage.id,
emoji: "white_check_mark",
});
@@ -97,6 +100,7 @@ export function createQaSelfCheckScenario(options?: {
await performAction("edit", {
to: lifecycleTarget,
threadId: lifecycleThreadId,
messageId: outboundMessage.id,
text: "qa-echo: inside thread (edited)",
});
@@ -110,6 +114,7 @@ export function createQaSelfCheckScenario(options?: {
await performAction("delete", {
to: lifecycleTarget,
threadId: lifecycleThreadId,
messageId: outboundMessage.id,
});
const deleted = await state.readMessage({ messageId: outboundMessage.id });
+3 -6
View File
@@ -93,7 +93,8 @@ describe("createQaSelfCheckScenario", () => {
if (action === "thread-create") {
return {
details: {
target: "thread:qa-room/thread-1",
target: "channel:qa-room",
threadId: "thread-1",
thread: { id: "thread-1" },
},
};
@@ -120,11 +121,7 @@ describe("createQaSelfCheckScenario", () => {
await threadStep.run({ state: testState, performAction });
await lifecycleStep.run({ state: testState, performAction });
expect(targets).toEqual([
"thread:qa-room/thread-1",
"thread:qa-room/thread-1",
"thread:qa-room/thread-1",
]);
expect(targets).toEqual(["channel:qa-room", "channel:qa-room", "channel:qa-room"]);
const deletedMessage = state.getSnapshot().messages.find((message) => message.deleted);
if (!deletedMessage) {
throw new Error("self-check did not preserve its deleted message tombstone");