




















@@ -93,7 +93,7 @@ function waitForSessionsChangedMessagePhase(
9393);
9494}
959596-async function emitTranscriptUpdateAndCollectEvents(params: {
96+async function emitTranscriptUpdateAndCollectMessageEvent(params: {
9797ws: Awaited<ReturnType<Awaited<ReturnType<typeof createGatewaySuiteHarness>>["openWs"]>>;
9898sessionKey: string;
9999sessionFile: string;
@@ -102,7 +102,6 @@ async function emitTranscriptUpdateAndCollectEvents(params: {
102102messageSeq?: number;
103103}) {
104104const messageEventPromise = waitForSessionMessageEvent(params.ws, params.sessionKey);
105-const changedEventPromise = waitForSessionsChangedMessagePhase(params.ws, params.sessionKey);
106105107106emitSessionTranscriptUpdate({
108107sessionFile: params.sessionFile,
@@ -112,11 +111,8 @@ async function emitTranscriptUpdateAndCollectEvents(params: {
112111 ...(typeof params.messageSeq === "number" ? { messageSeq: params.messageSeq } : {}),
113112});
114113115-const [messageEvent, changedEvent] = await Promise.all([
116-messageEventPromise,
117-changedEventPromise,
118-]);
119-return { messageEvent, changedEvent };
114+const messageEvent = await messageEventPromise;
115+return { messageEvent };
120116}
121117122118async function expectNoMessageWithin(params: {
@@ -317,7 +313,7 @@ describe("session.message websocket events", () => {
317313);
318314319315await withOperatorSessionSubscriber(async (ws) => {
320-const { messageEvent } = await emitTranscriptUpdateAndCollectEvents({
316+const { messageEvent } = await emitTranscriptUpdateAndCollectMessageEvent({
321317 ws,
322318sessionKey: "agent:main:main",
323319sessionFile: transcriptPath,
@@ -441,7 +437,57 @@ describe("session.message websocket events", () => {
441437});
442438});
443439444-test("includes live usage metadata on session.message and sessions.changed transcript events", async () => {
440+test("does not duplicate displayable transcript updates with sessions.changed", async () => {
441+const storePath = await createSessionStoreFile();
442+await writeSessionStore({
443+entries: {
444+main: {
445+sessionId: "sess-main",
446+updatedAt: Date.now(),
447+},
448+},
449+ storePath,
450+});
451+452+await withOperatorSessionSubscriber(async (ws) => {
453+const messageEventPromise = waitForSessionMessageEvent(ws, "agent:main:main");
454+await expectNoMessageWithin({
455+action: () => {
456+emitSessionTranscriptUpdate({
457+sessionFile: path.join(path.dirname(storePath), "sess-main.jsonl"),
458+sessionKey: "agent:main:main",
459+message: {
460+role: "assistant",
461+content: [{ type: "text", text: "single frame" }],
462+timestamp: Date.now(),
463+},
464+messageId: "msg-single-frame",
465+messageSeq: 1,
466+});
467+},
468+watch: (timeoutMs) =>
469+onceMessage(
470+ws,
471+(message) =>
472+message.type === "event" &&
473+message.event === "sessions.changed" &&
474+(message.payload as { phase?: string; sessionKey?: string } | undefined)?.phase ===
475+"message" &&
476+(message.payload as { sessionKey?: string } | undefined)?.sessionKey ===
477+"agent:main:main",
478+timeoutMs,
479+),
480+});
481+const messageEvent = await messageEventPromise;
482+expectRecordFields(messageEvent.payload, {
483+sessionKey: "agent:main:main",
484+messageId: "msg-single-frame",
485+messageSeq: 1,
486+});
487+});
488+});
489+490+test("includes live usage metadata on session.message transcript events", async () => {
445491const storePath = await createSessionStoreFile();
446492await writeSessionStore({
447493entries: {
@@ -482,7 +528,7 @@ describe("session.message websocket events", () => {
482528);
483529484530await withOperatorSessionSubscriber(async (ws) => {
485-const { messageEvent, changedEvent } = await emitTranscriptUpdateAndCollectEvents({
531+const { messageEvent } = await emitTranscriptUpdateAndCollectMessageEvent({
486532 ws,
487533sessionKey: "agent:main:main",
488534sessionFile: transcriptPath,
@@ -500,18 +546,6 @@ describe("session.message websocket events", () => {
500546modelProvider: "openai",
501547model: "gpt-5.4",
502548});
503-expectRecordFields(changedEvent.payload, {
504-sessionKey: "agent:main:main",
505-phase: "message",
506-messageId: "msg-usage",
507-messageSeq: 1,
508-totalTokens: 2_400,
509-totalTokensFresh: true,
510-contextTokens: 123_456,
511-estimatedCostUsd: 0.0042,
512-modelProvider: "openai",
513-model: "gpt-5.4",
514-});
515549});
516550});
517551@@ -528,7 +562,7 @@ describe("session.message websocket events", () => {
528562});
529563530564await withOperatorSessionSubscriber(async (ws) => {
531-const { messageEvent, changedEvent } = await emitTranscriptUpdateAndCollectEvents({
565+const { messageEvent } = await emitTranscriptUpdateAndCollectMessageEvent({
532566 ws,
533567sessionKey: "agent:main:main",
534568sessionFile: path.join(path.dirname(storePath), "missing-transcript.jsonl"),
@@ -540,25 +574,18 @@ describe("session.message websocket events", () => {
540574messageId: "msg-carried-seq",
541575messageSeq: 7,
542576});
543-544577expectRecordFields(messageEvent.payload, {
545578sessionKey: "agent:main:main",
546579messageId: "msg-carried-seq",
547580messageSeq: 7,
548581});
549-expectRecordFields(changedEvent.payload, {
550-sessionKey: "agent:main:main",
551-phase: "message",
552-messageId: "msg-carried-seq",
553-messageSeq: 7,
554-});
555582const payload = requireRecord(messageEvent.payload, "session.message payload");
556583const message = requireRecord(payload.message, "session.message payload message");
557584expect((message["__openclaw"] as { seq?: unknown } | undefined)?.seq).toBe(7);
558585});
559586});
560587561-test("includes spawnedBy metadata on session.message and sessions.changed transcript events", async () => {
588+test("includes spawnedBy metadata on session.message transcript events", async () => {
562589const storePath = await createSessionStoreFile();
563590const transcriptPath = path.join(path.dirname(storePath), "sess-child.jsonl");
564591await writeSessionStore({
@@ -605,16 +632,6 @@ describe("session.message websocket events", () => {
605632(message.payload as { sessionKey?: string } | undefined)?.sessionKey ===
606633"agent:main:child",
607634);
608-const changedEventPromise = onceMessage(
609-ws,
610-(message) =>
611-message.type === "event" &&
612-message.event === "sessions.changed" &&
613-(message.payload as { phase?: string; sessionKey?: string } | undefined)?.phase ===
614-"message" &&
615-(message.payload as { sessionKey?: string } | undefined)?.sessionKey ===
616-"agent:main:child",
617-);
618635619636emitSessionTranscriptUpdate({
620637sessionFile: transcriptPath,
@@ -623,10 +640,7 @@ describe("session.message websocket events", () => {
623640messageId: "msg-spawn",
624641});
625642626-const [messageEvent, changedEvent] = await Promise.all([
627-messageEventPromise,
628-changedEventPromise,
629-]);
643+const messageEvent = await messageEventPromise;
630644expectRecordFields(messageEvent.payload, {
631645sessionKey: "agent:main:child",
632646spawnedBy: "agent:main:main",
@@ -637,23 +651,12 @@ describe("session.message websocket events", () => {
637651subagentControlScope: "children",
638652parentSessionKey: "agent:main:main",
639653});
640-expectRecordFields(changedEvent.payload, {
641-sessionKey: "agent:main:child",
642-phase: "message",
643-spawnedBy: "agent:main:main",
644-spawnedWorkspaceDir: "/tmp/subagent-workspace",
645-forkedFromParent: true,
646-spawnDepth: 2,
647-subagentRole: "orchestrator",
648-subagentControlScope: "children",
649-parentSessionKey: "agent:main:main",
650-});
651654} finally {
652655ws.close();
653656}
654657});
655658656-test("includes route thread metadata on session.message and sessions.changed transcript events", async () => {
659+test("includes route thread metadata on session.message transcript events", async () => {
657660const storePath = await createSessionStoreFile();
658661const transcriptPath = path.join(path.dirname(storePath), "sess-thread.jsonl");
659662await writeSessionStore({
@@ -685,7 +688,7 @@ describe("session.message websocket events", () => {
685688);
686689687690await withOperatorSessionSubscriber(async (ws) => {
688-const { messageEvent, changedEvent } = await emitTranscriptUpdateAndCollectEvents({
691+const { messageEvent } = await emitTranscriptUpdateAndCollectMessageEvent({
689692 ws,
690693sessionKey: "agent:main:main",
691694sessionFile: transcriptPath,
@@ -699,14 +702,6 @@ describe("session.message websocket events", () => {
699702lastAccountId: "acct-1",
700703lastThreadId: 42,
701704});
702-expectRecordFields(changedEvent.payload, {
703-sessionKey: "agent:main:main",
704-phase: "message",
705-lastChannel: "telegram",
706-lastTo: "-100123",
707-lastAccountId: "acct-1",
708-lastThreadId: 42,
709-});
710705});
711706});
712707此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。