diff --git a/src/features/background-agent/session-stream-activity.test.ts b/src/features/background-agent/session-stream-activity.test.ts new file mode 100644 index 000000000..76e4751c1 --- /dev/null +++ b/src/features/background-agent/session-stream-activity.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from "bun:test" +import { + hasOutputSignalFromPart, + resolveSessionNextPartInfo, +} from "./session-stream-activity" + +describe("session.next stream activity", () => { + test("#given text delta event #when resolving part info #then it counts as output activity", () => { + // given + const timestamp = "2026-05-21T03:00:00.000Z" + + // when + const partInfo = resolveSessionNextPartInfo("session.next.text.delta", { + sessionID: "ses-active", + timestamp, + }) + + // then + expect(partInfo?.type).toBe("text") + expect(partInfo?.field).toBe("text") + expect(partInfo?.activityTime).toEqual(new Date(timestamp)) + expect(hasOutputSignalFromPart(partInfo, "ses-active")).toBe(true) + }) + + test("#given metadata stream event #when resolving part info #then it refreshes activity without counting as output", () => { + // given + const timestamp = "2026-05-21T03:00:00.000Z" + + // when + const partInfo = resolveSessionNextPartInfo("session.next.compaction.started", { + sessionID: "ses-active", + timestamp, + }) + + // then + expect(partInfo?.type).toBeUndefined() + expect(partInfo?.field).toBeUndefined() + expect(partInfo?.activityTime).toEqual(new Date(timestamp)) + expect(hasOutputSignalFromPart(partInfo, "ses-active")).toBe(false) + }) +}) diff --git a/src/features/background-agent/session-stream-activity.ts b/src/features/background-agent/session-stream-activity.ts index ad6c1f0a4..7ba5d429b 100644 --- a/src/features/background-agent/session-stream-activity.ts +++ b/src/features/background-agent/session-stream-activity.ts @@ -72,10 +72,11 @@ export function resolveMessagePartInfo(properties: unknown): MessagePartInfo | u return nestedPart ? buildPartInfo(nestedPart, props) : buildPartInfo(props, undefined) } -function sessionNextType(eventType: string): string { +function sessionNextType(eventType: string): string | undefined { + if (eventType.startsWith("session.next.text.")) return "text" if (eventType.startsWith("session.next.reasoning.")) return "reasoning" if (eventType.startsWith("session.next.tool.") && eventType !== "session.next.tool.called") return "tool_result" - return "text" + return undefined } function isTrackedSessionNextActivityEvent(eventType: string): boolean { @@ -114,14 +115,15 @@ export function resolveSessionNextPartInfo(eventType: string, properties: unknow } } + const type = sessionNextType(eventType) return { id: getStringField(props, "callID"), sessionID, - type: sessionNextType(eventType), + type, tool: undefined, input: undefined, state: undefined, - field: eventType.endsWith(".delta") ? sessionNextType(eventType) : undefined, + field: eventType.endsWith(".delta") ? type : undefined, activityTime: getDateField(props, "timestamp"), } }