
























@@ -718,9 +718,40 @@ describe("talk.session unified handlers", () => {
718718},
719719});
720720721+const joinRespond = vi.fn();
722+await talkHandlers["talk.session.join"]({
723+req: { type: "req", id: "2", method: "talk.session.join" },
724+params: { sessionId: session.sessionId, token: session.token },
725+client: { connId: "conn-1" } as never,
726+isWebchatConnect: () => false,
727+respond: joinRespond as never,
728+context: {
729+ broadcastToConnIds,
730+} as never,
731+});
732+expect(joinRespond).toHaveBeenCalledWith(
733+true,
734+expect.objectContaining({
735+id: session.sessionId,
736+room: expect.objectContaining({
737+activeClientId: "conn-1",
738+}),
739+}),
740+undefined,
741+);
742+expect(broadcastToConnIds).toHaveBeenCalledWith(
743+"talk.event",
744+expect.objectContaining({
745+handoffId: session.sessionId,
746+talkEvent: expect.objectContaining({ type: "session.ready" }),
747+}),
748+new Set(["conn-1"]),
749+{ dropIfSlow: true },
750+);
751+721752const startRespond = vi.fn();
722753await talkHandlers["talk.session.startTurn"]({
723-req: { type: "req", id: "2", method: "talk.session.startTurn" },
754+req: { type: "req", id: "3", method: "talk.session.startTurn" },
724755params: { sessionId: session.sessionId, turnId: "turn-1" },
725756client: { connId: "conn-1" } as never,
726757isWebchatConnect: () => false,
@@ -752,14 +783,162 @@ describe("talk.session unified handlers", () => {
752783753784const closeRespond = vi.fn();
754785await talkHandlers["talk.session.close"]({
755-req: { type: "req", id: "3", method: "talk.session.close" },
786+req: { type: "req", id: "4", method: "talk.session.close" },
756787params: { sessionId: session.sessionId },
757788client: { connId: "conn-1" } as never,
758789isWebchatConnect: () => false,
759790respond: closeRespond as never,
760-context: {} as never,
791+context: {
792+ broadcastToConnIds,
793+} as never,
761794});
762795expect(closeRespond).toHaveBeenCalledWith(true, { ok: true }, undefined);
796+expect(broadcastToConnIds).toHaveBeenCalledWith(
797+"talk.event",
798+expect.objectContaining({
799+handoffId: session.sessionId,
800+talkEvent: expect.objectContaining({ type: "session.closed", final: true }),
801+}),
802+new Set(["conn-1"]),
803+{ dropIfSlow: true },
804+);
805+});
806+807+it("requires managed-room ownership before turn control", async () => {
808+const broadcastToConnIds = vi.fn();
809+const createRespond = vi.fn();
810+await talkHandlers["talk.session.create"]({
811+req: { type: "req", id: "1", method: "talk.session.create" },
812+params: {
813+mode: "stt-tts",
814+transport: "managed-room",
815+sessionKey: "session:main",
816+},
817+client: { connId: "creator" } as never,
818+isWebchatConnect: () => false,
819+respond: createRespond as never,
820+context: {
821+getRuntimeConfig: () => ({}) as OpenClawConfig,
822+} as never,
823+});
824+const session = createRespond.mock.calls[0]?.[1] as { sessionId: string; token: string };
825+826+const unjoinedStartRespond = vi.fn();
827+await talkHandlers["talk.session.startTurn"]({
828+req: { type: "req", id: "2", method: "talk.session.startTurn" },
829+params: { sessionId: session.sessionId, turnId: "turn-1" },
830+client: { connId: "creator" } as never,
831+isWebchatConnect: () => false,
832+respond: unjoinedStartRespond as never,
833+context: { broadcastToConnIds } as never,
834+});
835+expect(unjoinedStartRespond).toHaveBeenCalledWith(
836+false,
837+undefined,
838+expect.objectContaining({
839+code: ErrorCodes.INVALID_REQUEST,
840+message: "talk.session.startTurn requires the active managed-room connection",
841+}),
842+);
843+844+await talkHandlers["talk.session.join"]({
845+req: { type: "req", id: "3", method: "talk.session.join" },
846+params: { sessionId: session.sessionId, token: session.token },
847+client: { connId: "conn-1" } as never,
848+isWebchatConnect: () => false,
849+respond: vi.fn() as never,
850+context: { broadcastToConnIds } as never,
851+});
852+853+const staleStartRespond = vi.fn();
854+await talkHandlers["talk.session.startTurn"]({
855+req: { type: "req", id: "4", method: "talk.session.startTurn" },
856+params: { sessionId: session.sessionId, turnId: "turn-1" },
857+client: { connId: "conn-2" } as never,
858+isWebchatConnect: () => false,
859+respond: staleStartRespond as never,
860+context: { broadcastToConnIds } as never,
861+});
862+expect(staleStartRespond).toHaveBeenCalledWith(
863+false,
864+undefined,
865+expect.objectContaining({
866+code: ErrorCodes.INVALID_REQUEST,
867+message: "talk.session.startTurn requires the active managed-room connection",
868+}),
869+);
870+871+await talkHandlers["talk.session.startTurn"]({
872+req: { type: "req", id: "5", method: "talk.session.startTurn" },
873+params: { sessionId: session.sessionId, turnId: "turn-1" },
874+client: { connId: "conn-1" } as never,
875+isWebchatConnect: () => false,
876+respond: vi.fn() as never,
877+context: { broadcastToConnIds } as never,
878+});
879+880+const staleEndRespond = vi.fn();
881+await talkHandlers["talk.session.endTurn"]({
882+req: { type: "req", id: "6", method: "talk.session.endTurn" },
883+params: { sessionId: session.sessionId, turnId: "turn-1" },
884+client: { connId: "conn-2" } as never,
885+isWebchatConnect: () => false,
886+respond: staleEndRespond as never,
887+context: { broadcastToConnIds } as never,
888+});
889+expect(staleEndRespond).toHaveBeenCalledWith(
890+false,
891+undefined,
892+expect.objectContaining({
893+code: ErrorCodes.INVALID_REQUEST,
894+message: "talk.session.endTurn requires the active managed-room connection",
895+}),
896+);
897+898+const staleCancelRespond = vi.fn();
899+await talkHandlers["talk.session.cancelTurn"]({
900+req: { type: "req", id: "7", method: "talk.session.cancelTurn" },
901+params: { sessionId: session.sessionId, turnId: "turn-1" },
902+client: { connId: "conn-2" } as never,
903+isWebchatConnect: () => false,
904+respond: staleCancelRespond as never,
905+context: { broadcastToConnIds } as never,
906+});
907+expect(staleCancelRespond).toHaveBeenCalledWith(
908+false,
909+undefined,
910+expect.objectContaining({
911+code: ErrorCodes.INVALID_REQUEST,
912+message: "talk.session.cancelTurn requires the active managed-room connection",
913+}),
914+);
915+916+const staleCloseRespond = vi.fn();
917+await talkHandlers["talk.session.close"]({
918+req: { type: "req", id: "8", method: "talk.session.close" },
919+params: { sessionId: session.sessionId },
920+client: { connId: "conn-2" } as never,
921+isWebchatConnect: () => false,
922+respond: staleCloseRespond as never,
923+context: { broadcastToConnIds } as never,
924+});
925+expect(staleCloseRespond).toHaveBeenCalledWith(
926+false,
927+undefined,
928+expect.objectContaining({
929+code: ErrorCodes.INVALID_REQUEST,
930+message: "talk.session.close requires the active managed-room connection",
931+}),
932+);
933+934+await talkHandlers["talk.session.close"]({
935+req: { type: "req", id: "9", method: "talk.session.close" },
936+params: { sessionId: session.sessionId },
937+client: { connId: "conn-1" } as never,
938+isWebchatConnect: () => false,
939+respond: vi.fn() as never,
940+context: { broadcastToConnIds } as never,
941+});
763942});
764943765944it("keeps direct-tools managed-room sessions behind admin scope", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。