mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 19:10:58 +00:00
Matrix: send subagent binding farewells
This commit is contained in:
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Hoisted stubs referenced in vi.mock factories below
|
||||
const bindMock = vi.hoisted(() => vi.fn());
|
||||
const unbindMock = vi.hoisted(() => vi.fn());
|
||||
const getManagerMock = vi.hoisted(() => vi.fn());
|
||||
const listAllBindingsMock = vi.hoisted(() => vi.fn((): any[] => []));
|
||||
const listBindingsForAccountMock = vi.hoisted(() => vi.fn((): any[] => []));
|
||||
@@ -10,7 +11,7 @@ const resolveMatrixBaseConfigMock = vi.hoisted(() => vi.fn((): any => ({})));
|
||||
const findMatrixAccountConfigMock = vi.hoisted(() => vi.fn((): any => undefined));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/conversation-binding-runtime", () => ({
|
||||
getSessionBindingService: () => ({ bind: bindMock }),
|
||||
getSessionBindingService: () => ({ bind: bindMock, unbind: unbindMock }),
|
||||
}));
|
||||
|
||||
vi.mock("./account-config.js", () => ({
|
||||
@@ -23,6 +24,12 @@ vi.mock("./thread-bindings-shared.js", () => ({
|
||||
listAllBindings: listAllBindingsMock,
|
||||
listBindingsForAccount: listBindingsForAccountMock,
|
||||
removeBindingRecord: removeBindingRecordMock,
|
||||
resolveBindingKey: (params: {
|
||||
accountId: string;
|
||||
conversationId: string;
|
||||
parentConversationId?: string;
|
||||
}) =>
|
||||
`${params.accountId}:${params.parentConversationId?.trim() || "-"}:${params.conversationId}`,
|
||||
}));
|
||||
|
||||
import {
|
||||
@@ -280,6 +287,7 @@ describe("handleMatrixSubagentEnded", () => {
|
||||
listAllBindingsMock.mockReset();
|
||||
listBindingsForAccountMock.mockReset();
|
||||
removeBindingRecordMock.mockReset();
|
||||
unbindMock.mockReset();
|
||||
mockManager.persist.mockReset();
|
||||
});
|
||||
|
||||
@@ -319,6 +327,49 @@ describe("handleMatrixSubagentEnded", () => {
|
||||
expect(mockManager.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends farewell through the binding service when requested", async () => {
|
||||
const binding = {
|
||||
targetSessionKey: "agent:ops:subagent:child",
|
||||
targetKind: "subagent",
|
||||
accountId: "ops",
|
||||
conversationId: "$thread",
|
||||
parentConversationId: "!room:example",
|
||||
boundAt: 0,
|
||||
lastActivityAt: 0,
|
||||
};
|
||||
listBindingsForAccountMock.mockReturnValue([binding]);
|
||||
unbindMock.mockResolvedValue([
|
||||
{
|
||||
bindingId: "ops:!room:example:$thread",
|
||||
targetSessionKey: "agent:ops:subagent:child",
|
||||
targetKind: "subagent",
|
||||
conversation: {
|
||||
channel: "matrix",
|
||||
accountId: "ops",
|
||||
conversationId: "$thread",
|
||||
parentConversationId: "!room:example",
|
||||
},
|
||||
status: "active",
|
||||
boundAt: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
await handleMatrixSubagentEnded({
|
||||
targetSessionKey: "agent:ops:subagent:child",
|
||||
targetKind: "subagent",
|
||||
accountId: "ops",
|
||||
reason: "spawn-failed",
|
||||
sendFarewell: true,
|
||||
});
|
||||
|
||||
expect(unbindMock).toHaveBeenCalledWith({
|
||||
bindingId: "ops:!room:example:$thread",
|
||||
reason: "spawn-failed",
|
||||
});
|
||||
expect(removeBindingRecordMock).not.toHaveBeenCalled();
|
||||
expect(getManagerMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips persist when removeBindingRecord returns false (binding not found in store)", async () => {
|
||||
const binding = {
|
||||
targetSessionKey: "agent:ops:subagent:orphan",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
listAllBindings,
|
||||
listBindingsForAccount,
|
||||
removeBindingRecord,
|
||||
resolveBindingKey,
|
||||
} from "./thread-bindings-shared.js";
|
||||
|
||||
type MatrixSubagentSpawningEvent = {
|
||||
@@ -31,6 +32,8 @@ type MatrixSubagentEndedEvent = {
|
||||
targetSessionKey: string;
|
||||
targetKind: string;
|
||||
accountId?: string;
|
||||
reason?: string;
|
||||
sendFarewell?: boolean;
|
||||
};
|
||||
|
||||
type MatrixSubagentDeliveryTargetEvent = {
|
||||
@@ -219,8 +222,24 @@ export async function handleMatrixSubagentEnded(event: MatrixSubagentEndedEvent)
|
||||
const matching = candidates.filter(
|
||||
(entry) => entry.targetSessionKey === event.targetSessionKey && entry.targetKind === "subagent",
|
||||
);
|
||||
const removedBindingKeys = new Set<string>();
|
||||
if (event.sendFarewell) {
|
||||
const bindingService = getSessionBindingService();
|
||||
const reason = normalizeOptionalString(event.reason) || "subagent-ended";
|
||||
for (const binding of matching) {
|
||||
const bindingId = resolveBindingKey(binding);
|
||||
const removed = await bindingService.unbind({ bindingId, reason });
|
||||
if (removed.some((entry) => entry.bindingId === bindingId)) {
|
||||
removedBindingKeys.add(bindingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const affectedAccountIds = new Set<string>();
|
||||
for (const binding of matching) {
|
||||
if (removedBindingKeys.has(resolveBindingKey(binding))) {
|
||||
continue;
|
||||
}
|
||||
if (removeBindingRecord(binding)) {
|
||||
affectedAccountIds.add(binding.accountId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user