fix(team-mode): defer live mailbox acks
This commit is contained in:
@@ -17,18 +17,24 @@ export async function ackMessages(
|
||||
|
||||
for (const messageId of messageIds) {
|
||||
const messageFileName = `${messageId}.json`
|
||||
const sourcePath = path.join(inboxDir, messageFileName)
|
||||
const sourcePaths = [
|
||||
path.join(inboxDir, messageFileName),
|
||||
path.join(inboxDir, `.delivering-${messageFileName}`),
|
||||
]
|
||||
const targetPath = path.join(processedDir, messageFileName)
|
||||
|
||||
try {
|
||||
await rename(sourcePath, targetPath)
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err.code === "ENOENT") {
|
||||
continue
|
||||
}
|
||||
for (const sourcePath of sourcePaths) {
|
||||
try {
|
||||
await rename(sourcePath, targetPath)
|
||||
break
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err.code === "ENOENT") {
|
||||
continue
|
||||
}
|
||||
|
||||
throw error
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +530,7 @@ describe("createTeamSendMessageTool", () => {
|
||||
expect(message.from).toBe("m1")
|
||||
})
|
||||
|
||||
test("acks the message after live delivery so the transform hook does not redeliver", async () => {
|
||||
test("keeps live-delivered messages reserved until the recipient idles", async () => {
|
||||
// given
|
||||
const fixture = await createTeamFixture()
|
||||
const { client } = createRecordingClient()
|
||||
@@ -546,9 +546,13 @@ describe("createTeamSendMessageTool", () => {
|
||||
// then
|
||||
const inboxDir = getInboxDir(resolveBaseDir(fixture.config), fixture.teamRunId, "m2")
|
||||
const inboxEntries = (await readdir(inboxDir)).filter((entry) => entry.endsWith(".json"))
|
||||
const processedEntries = (await readdir(path.join(inboxDir, "processed"))).filter((entry) => entry.endsWith(".json"))
|
||||
expect(inboxEntries).toHaveLength(0)
|
||||
expect(processedEntries).toHaveLength(1)
|
||||
expect(inboxEntries).toHaveLength(1)
|
||||
expect(inboxEntries[0]?.startsWith(".delivering-")).toBe(true)
|
||||
|
||||
const { loadRuntimeState: loadState } = await import("../team-state-store/store")
|
||||
const runtimeState = await loadState(fixture.teamRunId, fixture.config)
|
||||
const recipient = runtimeState.members.find((member) => member.name === "m2")
|
||||
expect(recipient?.pendingInjectedMessageIds).toHaveLength(1)
|
||||
})
|
||||
|
||||
test("broadcast fans out live delivery to every member except the sender", async () => {
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { randomUUID } from "node:crypto"
|
||||
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import { type ToolDefinition, tool } from "@opencode-ai/plugin/tool"
|
||||
import { z } from "zod"
|
||||
|
||||
import type { TeamModeConfig } from "../../../config/schema/team-mode"
|
||||
import { promptAsyncAfterSessionIdle } from "../../../hooks/shared/prompt-async-gate"
|
||||
import { log } from "../../../shared/logger"
|
||||
import { applyMemberSessionRouting, buildMemberPromptBody } from "../member-session-routing"
|
||||
import { lookupTeamSession } from "../team-session-registry"
|
||||
import { loadRuntimeState } from "../team-state-store/store"
|
||||
import { buildEnvelope } from "../team-mailbox/poll"
|
||||
import {
|
||||
commitDeliveryReservation,
|
||||
releaseDeliveryReservation,
|
||||
reserveMessageForDelivery,
|
||||
} from "../team-mailbox/reservation"
|
||||
import { BroadcastNotPermittedError, sendMessage } from "../team-mailbox/send"
|
||||
import { promptAsyncAfterSessionIdle } from "../../../hooks/shared/prompt-async-gate"
|
||||
|
||||
import { lookupTeamSession } from "../team-session-registry"
|
||||
import { loadRuntimeState, transitionRuntimeState } from "../team-state-store/store"
|
||||
import type { Message } from "../types"
|
||||
import { MessageSchema } from "../types"
|
||||
|
||||
@@ -135,6 +133,25 @@ async function releaseReservationSafely(
|
||||
}
|
||||
}
|
||||
|
||||
async function markLiveDeliveryPending(
|
||||
teamRunId: string,
|
||||
recipientName: string,
|
||||
messageId: string,
|
||||
config: TeamModeConfig,
|
||||
): Promise<void> {
|
||||
await transitionRuntimeState(teamRunId, (currentRuntimeState) => ({
|
||||
...currentRuntimeState,
|
||||
members: currentRuntimeState.members.map((member) => (
|
||||
member.name === recipientName
|
||||
? {
|
||||
...member,
|
||||
pendingInjectedMessageIds: Array.from(new Set([...member.pendingInjectedMessageIds, messageId])),
|
||||
}
|
||||
: member
|
||||
)),
|
||||
}), config)
|
||||
}
|
||||
|
||||
async function deliverLive(
|
||||
client: LiveDeliveryClient,
|
||||
message: Message,
|
||||
@@ -207,8 +224,8 @@ async function deliverLive(
|
||||
})
|
||||
continue
|
||||
}
|
||||
await commitDeliveryReservation(reservation)
|
||||
log("[team-mailbox] live delivery committed", {
|
||||
await markLiveDeliveryPending(teamRunId, recipientName, message.messageId, config)
|
||||
log("[team-mailbox] live delivery reserved until recipient idle", {
|
||||
teamRunId,
|
||||
recipient: recipientName,
|
||||
recipientSessionId,
|
||||
|
||||
Reference in New Issue
Block a user