

























@@ -56,6 +56,54 @@ function joinPromptSections(...sections: Array<string | undefined>): string {
5656return promptSections.join("\n\n");
5757}
585859+function requireRecord(value: unknown, label: string): Record<string, unknown> {
60+if (!value || typeof value !== "object" || Array.isArray(value)) {
61+throw new Error(`expected ${label} to be an object`);
62+}
63+return value as Record<string, unknown>;
64+}
65+66+function requireMockCallArg(
67+mock: { mock: { calls: unknown[][] } },
68+index: number,
69+): Record<string, unknown> {
70+const call = mock.mock.calls[index];
71+if (!call) {
72+throw new Error(`expected mock call ${index}`);
73+}
74+return requireRecord(call[0], `mock call ${index} arg`);
75+}
76+77+function expectBlockReplyText(onBlockReply: { mock: { calls: unknown[][] } }, text: string): void {
78+expect(
79+onBlockReply.mock.calls.some(
80+(call) => requireRecord(call[0], "block reply payload").text === text,
81+),
82+).toBe(true);
83+}
84+85+function expectNoBlockReplyText(
86+onBlockReply: { mock: { calls: unknown[][] } },
87+text: string,
88+): void {
89+expect(
90+onBlockReply.mock.calls.some(
91+(call) => requireRecord(call[0], "block reply payload").text === text,
92+),
93+).toBe(false);
94+}
95+96+function expectNoBlockReplyTextIncludes(
97+onBlockReply: { mock: { calls: unknown[][] } },
98+fragment: string,
99+): void {
100+expect(
101+onBlockReply.mock.calls.some((call) =>
102+String(requireRecord(call[0], "block reply payload").text).includes(fragment),
103+),
104+).toBe(false);
105+}
106+59107function registerFollowupTestSessionStore(
60108storePath: string,
61109sessionStore: Record<string, SessionEntry>,
@@ -577,11 +625,7 @@ describe("createFollowupRunner runtime config", () => {
577625await runner(queued);
578626579627expect(queued.run.config).toBe(runtimeConfig);
580-expect(runPreflightCompactionIfNeededMock).toHaveBeenCalledWith(
581-expect.objectContaining({
582-cfg: runtimeConfig,
583-}),
584-);
628+expect(requireMockCallArg(runPreflightCompactionIfNeededMock, 0).cfg).toBe(runtimeConfig);
585629const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as
586630| {
587631config?: unknown;
@@ -1174,14 +1218,11 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
11741218});
1175121911761220expect(onBlockReply).not.toHaveBeenCalled();
1177-expect(persistSpy).toHaveBeenCalledWith(
1178-expect.objectContaining({
1179- storePath,
1180- sessionKey,
1181-modelUsed: "claude-opus-4-6",
1182-providerUsed: "anthropic",
1183-}),
1184-);
1221+const persistCall = requireMockCallArg(persistSpy, 0);
1222+expect(persistCall.storePath).toBe(storePath);
1223+expect(persistCall.sessionKey).toBe(sessionKey);
1224+expect(persistCall.modelUsed).toBe("claude-opus-4-6");
1225+expect(persistCall.providerUsed).toBe("anthropic");
11851226expect(sessionStore[sessionKey]?.totalTokens).toBe(400);
11861227expect(sessionStore[sessionKey]?.model).toBe("claude-opus-4-6");
11871228// Accumulated usage is still stored for usage/cost tracking.
@@ -1234,13 +1275,10 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
12341275),
12351276).resolves.toBeUndefined();
123612771237-expect(persistSpy).toHaveBeenCalledWith(
1238-expect.objectContaining({
1239- storePath,
1240- sessionKey,
1241- cfg,
1242-}),
1243-);
1278+const persistCall = requireMockCallArg(persistSpy, 0);
1279+expect(persistCall.storePath).toBe(storePath);
1280+expect(persistCall.sessionKey).toBe(sessionKey);
1281+expect(persistCall.cfg).toBe(cfg);
12441282persistSpy.mockRestore();
12451283});
12461284@@ -1292,11 +1330,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
12921330),
12931331).resolves.toBeUndefined();
129413321295-expect(persistSpy).toHaveBeenCalledWith(
1296-expect.objectContaining({
1297-providerUsed: "anthropic",
1298-}),
1299-);
1333+expect(requireMockCallArg(persistSpy, 0).providerUsed).toBe("anthropic");
13001334expect(persistSpy.mock.calls[0]?.[0]?.usageIsContextSnapshot).toBeUndefined();
13011335persistSpy.mockRestore();
13021336});
@@ -1317,18 +1351,11 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
1317135113181352expect(routeReplyMock).toHaveBeenCalledTimes(2);
13191353expect(onBlockReply).toHaveBeenCalledTimes(1);
1320-expect(onBlockReply).toHaveBeenCalledWith(
1321-expect.objectContaining({
1322-isError: true,
1323-text: expect.stringContaining("could not deliver it to the originating channel"),
1324-}),
1325-);
1326-expect(onBlockReply).not.toHaveBeenCalledWith(
1327-expect.objectContaining({ text: "hello world!" }),
1328-);
1329-expect(onBlockReply).not.toHaveBeenCalledWith(
1330-expect.objectContaining({ text: "second payload" }),
1331-);
1354+const reply = requireMockCallArg(onBlockReply, 0);
1355+expect(reply.isError).toBe(true);
1356+expect(String(reply.text)).toContain("could not deliver it to the originating channel");
1357+expectNoBlockReplyText(onBlockReply, "hello world!");
1358+expectNoBlockReplyText(onBlockReply, "second payload");
13321359});
1333136013341361it("does not emit cross-channel route-failure notice when a later payload routes", async () => {
@@ -1348,11 +1375,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
13481375});
1349137613501377expect(routeReplyMock).toHaveBeenCalledTimes(2);
1351-expect(onBlockReply).not.toHaveBeenCalledWith(
1352-expect.objectContaining({
1353-text: expect.stringContaining("could not deliver it to the originating channel"),
1354-}),
1355-);
1378+expectNoBlockReplyTextIncludes(onBlockReply, "could not deliver it to the originating channel");
13561379});
1357138013581381it("uses dispatcher when origin routing metadata is incomplete", async () => {
@@ -1367,7 +1390,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
1367139013681391expect(routeReplyMock).not.toHaveBeenCalled();
13691392expect(onBlockReply).toHaveBeenCalledTimes(1);
1370-expect(onBlockReply).toHaveBeenCalledWith(expect.objectContaining({ text: "hello world!" }));
1393+expectBlockReplyText(onBlockReply, "hello world!");
13711394});
1372139513731396it("keeps message-tool-only queued followup finals private", async () => {
@@ -1385,12 +1408,9 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
13851408} as FollowupRun,
13861409});
138714101388-expect(runEmbeddedPiAgentMock).toHaveBeenCalledWith(
1389-expect.objectContaining({
1390-sourceReplyDeliveryMode: "message_tool_only",
1391-forceMessageTool: true,
1392-}),
1393-);
1411+const runArg = requireMockCallArg(runEmbeddedPiAgentMock, 0);
1412+expect(runArg.sourceReplyDeliveryMode).toBe("message_tool_only");
1413+expect(runArg.forceMessageTool).toBe(true);
13941414expect(routeReplyMock).not.toHaveBeenCalled();
13951415expect(onBlockReply).not.toHaveBeenCalled();
13961416});
@@ -1411,19 +1431,15 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
1411143114121432expect(routeReplyMock).not.toHaveBeenCalled();
14131433expect(onBlockReply).toHaveBeenCalledTimes(1);
1414-expect(onBlockReply).toHaveBeenCalledWith(expect.objectContaining({ text: "hello world!" }));
1415-expect(resolveProviderFollowupFallbackRouteMock).toHaveBeenCalledWith(
1416-expect.objectContaining({
1417-provider: "anthropic",
1418-context: expect.objectContaining({
1419-provider: "anthropic",
1420-modelId: "claude",
1421-originRoutable: true,
1422-dispatcherAvailable: true,
1423-payload: expect.objectContaining({ text: "hello world!" }),
1424-}),
1425-}),
1426-);
1434+expectBlockReplyText(onBlockReply, "hello world!");
1435+const routeArg = requireMockCallArg(resolveProviderFollowupFallbackRouteMock, 0);
1436+expect(routeArg.provider).toBe("anthropic");
1437+const context = requireRecord(routeArg.context, "provider fallback context");
1438+expect(context.provider).toBe("anthropic");
1439+expect(context.modelId).toBe("claude");
1440+expect(context.originRoutable).toBe(true);
1441+expect(context.dispatcherAvailable).toBe(true);
1442+expect(requireRecord(context.payload, "provider fallback payload").text).toBe("hello world!");
14271443});
1428144414291445it("lets provider followup route hooks drop payloads explicitly", async () => {
@@ -1501,12 +1517,9 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
1501151715021518expect(routeReplyMock).not.toHaveBeenCalled();
15031519expect(onBlockReply).toHaveBeenCalledTimes(1);
1504-expect(onBlockReply).toHaveBeenCalledWith(
1505-expect.objectContaining({
1506-text: DELIVERY_NO_REPLY_RUNTIME_CONTRACT.silentText,
1507-mediaUrl: "file:///tmp/followup.png",
1508-}),
1509-);
1520+const reply = requireMockCallArg(onBlockReply, 0);
1521+expect(reply.text).toBe(DELIVERY_NO_REPLY_RUNTIME_CONTRACT.silentText);
1522+expect(reply.mediaUrl).toBe("file:///tmp/followup.png");
15101523});
1511152415121525it("falls back to dispatcher when successful output has no complete origin route", async () => {
@@ -1521,9 +1534,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
1521153415221535expect(routeReplyMock).not.toHaveBeenCalled();
15231536expect(onBlockReply).toHaveBeenCalledTimes(1);
1524-expect(onBlockReply).toHaveBeenCalledWith(
1525-expect.objectContaining({ text: DELIVERY_NO_REPLY_RUNTIME_CONTRACT.dispatcherText }),
1526-);
1537+expectBlockReplyText(onBlockReply, DELIVERY_NO_REPLY_RUNTIME_CONTRACT.dispatcherText);
15271538});
1528153915291540it("falls back to dispatcher when same-channel origin routing fails", async () => {
@@ -1547,7 +1558,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
1547155815481559expect(routeReplyMock).toHaveBeenCalled();
15491560expect(onBlockReply).toHaveBeenCalledTimes(1);
1550-expect(onBlockReply).toHaveBeenCalledWith(expect.objectContaining({ text: "hello world!" }));
1561+expectBlockReplyText(onBlockReply, "hello world!");
15511562});
1552156315531564it("routes followups with originating account/thread metadata", async () => {
@@ -1562,14 +1573,11 @@ describe("createFollowupRunner messaging delivery and dedupe", () => {
15621573} as FollowupRun,
15631574});
156415751565-expect(routeReplyMock).toHaveBeenCalledWith(
1566-expect.objectContaining({
1567-channel: "discord",
1568-to: "channel:C1",
1569-accountId: "work",
1570-threadId: "1739142736.000100",
1571-}),
1572-);
1576+const routeArg = requireMockCallArg(routeReplyMock, 0);
1577+expect(routeArg.channel).toBe("discord");
1578+expect(routeArg.to).toBe("channel:C1");
1579+expect(routeArg.accountId).toBe("work");
1580+expect(routeArg.threadId).toBe("1739142736.000100");
15731581expect(onBlockReply).not.toHaveBeenCalled();
15741582});
15751583});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。