





















@@ -12,6 +12,74 @@ import {
12121313const { createSessionStoreDir, openClient } = setupGatewaySessionsTestHarness();
141415+type MockCalls = {
16+mock: { calls: unknown[][] };
17+};
18+19+function isRecord(value: unknown): value is Record<string, unknown> {
20+return typeof value === "object" && value !== null;
21+}
22+23+function requireRecord(value: unknown, label: string): Record<string, unknown> {
24+expect(isRecord(value), `${label} should be an object`).toBe(true);
25+if (!isRecord(value)) {
26+throw new Error(`${label} should be an object`);
27+}
28+return value;
29+}
30+31+function requireArray(value: unknown, label: string): unknown[] {
32+expect(Array.isArray(value), `${label} should be an array`).toBe(true);
33+if (!Array.isArray(value)) {
34+throw new Error(`${label} should be an array`);
35+}
36+return value;
37+}
38+39+function expectFields(record: Record<string, unknown>, expected: Record<string, unknown>) {
40+for (const [key, value] of Object.entries(expected)) {
41+expect(record[key], key).toEqual(value);
42+}
43+}
44+45+function expectRespondPayload(respond: MockCalls): Record<string, unknown> {
46+expect(respond.mock.calls).toHaveLength(1);
47+const [ok, payload, error] = respond.mock.calls[0] ?? [];
48+expect(ok).toBe(true);
49+expect(error).toBeUndefined();
50+return requireRecord(payload, "response payload");
51+}
52+53+function findSession(
54+payload: Record<string, unknown>,
55+sessionKey: string,
56+): Record<string, unknown> {
57+const sessions = requireArray(payload.sessions, "response sessions");
58+const session = sessions.find(
59+(candidate): candidate is Record<string, unknown> =>
60+isRecord(candidate) && candidate.key === sessionKey,
61+);
62+expect(session, sessionKey).toBeDefined();
63+if (!session) {
64+throw new Error(`Missing session ${sessionKey}`);
65+}
66+return session;
67+}
68+69+function expectChangedBroadcast(
70+broadcastToConnIds: MockCalls,
71+expected: Record<string, unknown>,
72+): Record<string, unknown> {
73+expect(broadcastToConnIds.mock.calls).toHaveLength(1);
74+const [event, payload, connIds, options] = broadcastToConnIds.mock.calls[0] ?? [];
75+expect(event).toBe("sessions.changed");
76+expect(connIds).toEqual(new Set(["conn-1"]));
77+expect(options).toEqual({ dropIfSlow: true });
78+const payloadRecord = requireRecord(payload, "broadcast payload");
79+expectFields(payloadRecord, expected);
80+return payloadRecord;
81+}
82+1583test("sessions.list keeps bulk rows lightweight and uses persisted model fields", async () => {
1684const { dir } = await createSessionStoreDir();
1785testState.agentConfig = {
@@ -143,22 +211,14 @@ test("sessions.list uses the gateway model catalog for effective thinking defaul
143211} as never,
144212});
145213146-expect(respond).toHaveBeenCalledWith(
147-true,
148-expect.objectContaining({
149-defaults: expect.objectContaining({
150-thinkingDefault: "medium",
151-}),
152-sessions: expect.arrayContaining([
153-expect.objectContaining({
154-key: "agent:main:main",
155-thinkingDefault: "medium",
156-thinkingOptions: ["off", "minimal", "low", "medium", "high"],
157-}),
158-]),
159-}),
160-undefined,
161-);
214+const payload = expectRespondPayload(respond);
215+const defaults = requireRecord(payload.defaults, "response defaults");
216+expect(defaults.thinkingDefault).toBe("medium");
217+const session = findSession(payload, "agent:main:main");
218+expectFields(session, {
219+thinkingDefault: "medium",
220+thinkingOptions: ["off", "minimal", "low", "medium", "high"],
221+});
162222});
163223164224test("sessions.list marks sessions with active abortable runs", async () => {
@@ -190,18 +250,9 @@ test("sessions.list marks sessions with active abortable runs", async () => {
190250} as never,
191251});
192252193-expect(respond).toHaveBeenCalledWith(
194-true,
195-expect.objectContaining({
196-sessions: expect.arrayContaining([
197-expect.objectContaining({
198-key: "agent:main:main",
199-hasActiveRun: true,
200-}),
201-]),
202-}),
203-undefined,
204-);
253+const payload = expectRespondPayload(respond);
254+const session = findSession(payload, "agent:main:main");
255+expect(session.hasActiveRun).toBe(true);
205256});
206257207258test("sessions.list yields before responding during bulk transcript hydration", async () => {
@@ -259,19 +310,12 @@ test("sessions.list yields before responding during bulk transcript hydration",
259310260311expect(respond).not.toHaveBeenCalled();
261312await request;
262-expect(respond).toHaveBeenCalledWith(
263-true,
264-expect.objectContaining({
265-sessions: expect.arrayContaining([
266-expect.objectContaining({
267-key: "agent:main:bulk-0",
268-derivedTitle: "title 0",
269-lastMessagePreview: "last 0",
270-}),
271-]),
272-}),
273-undefined,
274-);
313+const payload = expectRespondPayload(respond);
314+const session = findSession(payload, "agent:main:bulk-0");
315+expectFields(session, {
316+derivedTitle: "title 0",
317+lastMessagePreview: "last 0",
318+});
275319});
276320277321test("sessions.list does not block on slow model catalog discovery", async () => {
@@ -311,13 +355,8 @@ test("sessions.list does not block on slow model catalog discovery", async () =>
311355await vi.advanceTimersByTimeAsync(800);
312356await request;
313357314-expect(respond).toHaveBeenCalledWith(
315-true,
316-expect.objectContaining({
317-sessions: expect.arrayContaining([expect.objectContaining({ key: "agent:main:main" })]),
318-}),
319-undefined,
320-);
358+const payload = expectRespondPayload(respond);
359+findSession(payload, "agent:main:main");
321360} finally {
322361vi.useRealTimers();
323362}
@@ -381,26 +420,18 @@ test("sessions.changed mutation events include live usage metadata", async () =>
381420isWebchatConnect: () => false,
382421});
383422384-expect(respond).toHaveBeenCalledWith(
385-true,
386-expect.objectContaining({ ok: true, key: "agent:main:main" }),
387-undefined,
388-);
389-expect(broadcastToConnIds).toHaveBeenCalledWith(
390-"sessions.changed",
391-expect.objectContaining({
392-sessionKey: "agent:main:main",
393-reason: "patch",
394-totalTokens: 6_643,
395-totalTokensFresh: true,
396-contextTokens: 123_456,
397-estimatedCostUsd: 0,
398-modelProvider: "openai-codex",
399-model: "gpt-5.3-codex-spark",
400-}),
401-new Set(["conn-1"]),
402-{ dropIfSlow: true },
403-);
423+const responsePayload = expectRespondPayload(respond);
424+expectFields(responsePayload, { ok: true, key: "agent:main:main" });
425+expectChangedBroadcast(broadcastToConnIds, {
426+sessionKey: "agent:main:main",
427+reason: "patch",
428+totalTokens: 6_643,
429+totalTokensFresh: true,
430+contextTokens: 123_456,
431+estimatedCostUsd: 0,
432+modelProvider: "openai-codex",
433+model: "gpt-5.3-codex-spark",
434+});
404435});
405436406437test("sessions.changed mutation events include live session setting metadata", async () => {
@@ -440,27 +471,19 @@ test("sessions.changed mutation events include live session setting metadata", a
440471isWebchatConnect: () => false,
441472});
442473443-expect(respond).toHaveBeenCalledWith(
444-true,
445-expect.objectContaining({ ok: true, key: "agent:main:main" }),
446-undefined,
447-);
448-expect(broadcastToConnIds).toHaveBeenCalledWith(
449-"sessions.changed",
450-expect.objectContaining({
451-sessionKey: "agent:main:main",
452-reason: "patch",
453-verboseLevel: "on",
454-responseUsage: "full",
455-fastMode: true,
456-lastChannel: "telegram",
457-lastTo: "-100123",
458-lastAccountId: "acct-1",
459-lastThreadId: 42,
460-}),
461-new Set(["conn-1"]),
462-{ dropIfSlow: true },
463-);
474+const responsePayload = expectRespondPayload(respond);
475+expectFields(responsePayload, { ok: true, key: "agent:main:main" });
476+expectChangedBroadcast(broadcastToConnIds, {
477+sessionKey: "agent:main:main",
478+reason: "patch",
479+verboseLevel: "on",
480+responseUsage: "full",
481+fastMode: true,
482+lastChannel: "telegram",
483+lastTo: "-100123",
484+lastAccountId: "acct-1",
485+lastThreadId: 42,
486+});
464487});
465488466489test("sessions.changed mutation events include sendPolicy metadata", async () => {
@@ -494,21 +517,13 @@ test("sessions.changed mutation events include sendPolicy metadata", async () =>
494517isWebchatConnect: () => false,
495518});
496519497-expect(respond).toHaveBeenCalledWith(
498-true,
499-expect.objectContaining({ ok: true, key: "agent:main:main" }),
500-undefined,
501-);
502-expect(broadcastToConnIds).toHaveBeenCalledWith(
503-"sessions.changed",
504-expect.objectContaining({
505-sessionKey: "agent:main:main",
506-reason: "patch",
507-sendPolicy: "deny",
508-}),
509-new Set(["conn-1"]),
510-{ dropIfSlow: true },
511-);
520+const responsePayload = expectRespondPayload(respond);
521+expectFields(responsePayload, { ok: true, key: "agent:main:main" });
522+expectChangedBroadcast(broadcastToConnIds, {
523+sessionKey: "agent:main:main",
524+reason: "patch",
525+sendPolicy: "deny",
526+});
512527});
513528514529test("sessions.changed mutation events include subagent ownership metadata", async () => {
@@ -547,24 +562,16 @@ test("sessions.changed mutation events include subagent ownership metadata", asy
547562isWebchatConnect: () => false,
548563});
549564550-expect(respond).toHaveBeenCalledWith(
551-true,
552-expect.objectContaining({ ok: true, key: "agent:main:subagent:child" }),
553-undefined,
554-);
555-expect(broadcastToConnIds).toHaveBeenCalledWith(
556-"sessions.changed",
557-expect.objectContaining({
558-sessionKey: "agent:main:subagent:child",
559-reason: "patch",
560-spawnedBy: "agent:main:main",
561-spawnedWorkspaceDir: "/tmp/subagent-workspace",
562-forkedFromParent: true,
563-spawnDepth: 2,
564-subagentRole: "orchestrator",
565-subagentControlScope: "children",
566-}),
567-new Set(["conn-1"]),
568-{ dropIfSlow: true },
569-);
565+const responsePayload = expectRespondPayload(respond);
566+expectFields(responsePayload, { ok: true, key: "agent:main:subagent:child" });
567+expectChangedBroadcast(broadcastToConnIds, {
568+sessionKey: "agent:main:subagent:child",
569+reason: "patch",
570+spawnedBy: "agent:main:main",
571+spawnedWorkspaceDir: "/tmp/subagent-workspace",
572+forkedFromParent: true,
573+spawnDepth: 2,
574+subagentRole: "orchestrator",
575+subagentControlScope: "children",
576+});
570577});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。