fix(team-mailbox): dedupe pendingInjectedMessageIds across turns

Without ack between turns the same unread messageIds would re-enter
pendingInjectedMessageIds on every poll, unboundedly growing the list
(state.json observed 8-12 copies of the same id under runtime pressure).

Wrap the append with a Set so pending state stays idempotent regardless
of how many times the transform hook polls before ack lands.
This commit is contained in:
YeonGyu-Kim
2026-04-19 20:58:24 +09:00
parent 22f5b59a13
commit 247494a9ae
2 changed files with 26 additions and 2 deletions

View File

@@ -7,7 +7,7 @@ import { tmpdir } from "node:os"
import path from "node:path"
import { TeamModeConfigSchema } from "../../../config/schema/team-mode"
import { createRuntimeState } from "../team-state-store/store"
import { createRuntimeState, loadRuntimeState } from "../team-state-store/store"
import type { TeamSpec } from "../types"
import { sendMessage } from "./send"
@@ -144,4 +144,28 @@ describe("pollAndBuildInjection", () => {
expect(inboxEntries).toContain(`${secondMessageId}.json`)
expect(inboxEntries).not.toContain("processed")
})
test("deduplicates pendingInjectedMessageIds when the same unread message surfaces across turns", async () => {
// given
const { teamRunId, config } = await setupRuntime(["m1"])
const messageId = randomUUID()
await sendMessage({
version: 1,
messageId,
from: "lead",
to: "m1",
kind: "message",
body: "persistent",
timestamp: 100,
}, teamRunId, config, { isLead: true, activeMembers: ["m1"] })
// when
await pollAndBuildInjection("session-1", "m1", teamRunId, config, "turn-A")
await pollAndBuildInjection("session-1", "m1", teamRunId, config, "turn-B")
const runtimeState = await loadRuntimeState(teamRunId, config)
const member = runtimeState.members.find((entry) => entry.name === "m1")
// then
expect(member?.pendingInjectedMessageIds).toEqual([messageId])
})
})

View File

@@ -78,7 +78,7 @@ export async function pollAndBuildInjection(
? {
...member,
lastInjectedTurnMarker: turnMarker,
pendingInjectedMessageIds: [...member.pendingInjectedMessageIds, ...messageIds],
pendingInjectedMessageIds: Array.from(new Set([...member.pendingInjectedMessageIds, ...messageIds])),
}
: member
)),