

























@@ -0,0 +1,212 @@
1+import { describe, expect, it } from "vitest";
2+3+import type { TaskFlowRecord } from "./task-flow-registry.types.js";
4+import type { TaskRecord, TaskRegistrySummary } from "./task-registry.types.js";
5+6+import {
7+mapTaskFlowDetail,
8+mapTaskFlowView,
9+mapTaskRunAggregateSummary,
10+mapTaskRunDetail,
11+mapTaskRunView,
12+} from "./task-domain-views.js";
13+14+function makeTask(overrides: Partial<TaskRecord> = {}): TaskRecord {
15+return {
16+taskId: "task-1",
17+runtime: "subagent",
18+requesterSessionKey: "agent:main:main",
19+ownerKey: "agent:main:main",
20+scopeKind: "session",
21+task: "Investigate flaky delivery",
22+status: "running",
23+deliveryStatus: "pending",
24+notifyPolicy: "done_only",
25+createdAt: 100,
26+ ...overrides,
27+};
28+}
29+30+function makeFlow(overrides: Partial<TaskFlowRecord> = {}): TaskFlowRecord {
31+return {
32+flowId: "flow-1",
33+syncMode: "managed",
34+ownerKey: "agent:main:main",
35+revision: 3,
36+status: "waiting",
37+notifyPolicy: "state_changes",
38+goal: "Ship a safe fix",
39+createdAt: 10,
40+updatedAt: 20,
41+ ...overrides,
42+};
43+}
44+45+function makeSummary(overrides: Partial<TaskRegistrySummary> = {}): TaskRegistrySummary {
46+return {
47+total: 2,
48+active: 1,
49+terminal: 1,
50+failures: 1,
51+byStatus: {
52+queued: 0,
53+running: 1,
54+succeeded: 0,
55+failed: 1,
56+timed_out: 0,
57+cancelled: 0,
58+lost: 0,
59+},
60+byRuntime: {
61+subagent: 1,
62+acp: 0,
63+cli: 1,
64+cron: 0,
65+},
66+ ...overrides,
67+};
68+}
69+70+describe("task domain view mappers", () => {
71+it("maps task registry summaries without sharing mutable count objects", () => {
72+const summary = makeSummary();
73+74+const view = mapTaskRunAggregateSummary(summary);
75+76+expect(view).toEqual(summary);
77+expect(view.byStatus).not.toBe(summary.byStatus);
78+expect(view.byRuntime).not.toBe(summary.byRuntime);
79+});
80+81+it("maps task run records to the public task run view contract", () => {
82+const task = makeTask({
83+taskId: "task-full",
84+runtime: "cli",
85+sourceId: "source-1",
86+requesterSessionKey: "agent:main:telegram:chat-1",
87+ownerKey: "agent:main:main",
88+scopeKind: "system",
89+childSessionKey: "agent:main:subagent:child-1",
90+parentFlowId: "flow-1",
91+parentTaskId: "task-parent",
92+agentId: "main",
93+runId: "run-1",
94+label: "diagnostics",
95+task: "Run diagnostics",
96+status: "failed",
97+deliveryStatus: "failed",
98+notifyPolicy: "state_changes",
99+createdAt: 100,
100+startedAt: 110,
101+endedAt: 200,
102+lastEventAt: 190,
103+cleanupAfter: 1_000,
104+error: "Command failed",
105+progressSummary: "Checking logs",
106+terminalSummary: "Diagnostics failed",
107+terminalOutcome: "blocked",
108+});
109+110+expect(mapTaskRunView(task)).toEqual({
111+id: "task-full",
112+runtime: "cli",
113+sourceId: "source-1",
114+sessionKey: "agent:main:telegram:chat-1",
115+ownerKey: "agent:main:main",
116+scope: "system",
117+childSessionKey: "agent:main:subagent:child-1",
118+flowId: "flow-1",
119+parentTaskId: "task-parent",
120+agentId: "main",
121+runId: "run-1",
122+label: "diagnostics",
123+title: "Run diagnostics",
124+status: "failed",
125+deliveryStatus: "failed",
126+notifyPolicy: "state_changes",
127+createdAt: 100,
128+startedAt: 110,
129+endedAt: 200,
130+lastEventAt: 190,
131+cleanupAfter: 1_000,
132+error: "Command failed",
133+progressSummary: "Checking logs",
134+terminalSummary: "Diagnostics failed",
135+terminalOutcome: "blocked",
136+});
137+});
138+139+it("keeps task run detail aligned with the task run view shape", () => {
140+const task = makeTask({ taskId: "task-detail", runId: "run-detail" });
141+142+expect(mapTaskRunDetail(task)).toEqual(mapTaskRunView(task));
143+});
144+145+it("maps task flow records to public flow views without sharing requester origins", () => {
146+const requesterOrigin = {
147+channel: "telegram",
148+to: "chat-1",
149+threadId: 123,
150+deliveryIntent: { id: "intent-1", kind: "outbound_queue" as const },
151+};
152+const flow = makeFlow({
153+ requesterOrigin,
154+currentStep: "wait_for_task",
155+cancelRequestedAt: 18,
156+endedAt: 30,
157+});
158+159+const view = mapTaskFlowView(flow);
160+161+expect(view).toEqual({
162+id: "flow-1",
163+ownerKey: "agent:main:main",
164+ requesterOrigin,
165+status: "waiting",
166+notifyPolicy: "state_changes",
167+goal: "Ship a safe fix",
168+currentStep: "wait_for_task",
169+cancelRequestedAt: 18,
170+createdAt: 10,
171+updatedAt: 20,
172+endedAt: 30,
173+});
174+expect(view.requesterOrigin).not.toBe(requesterOrigin);
175+});
176+177+it("maps flow details with supplied task summary and nested task views", () => {
178+const task = makeTask({ taskId: "task-child", parentFlowId: "flow-1" });
179+const summary = makeSummary();
180+const flow = makeFlow({
181+stateJson: { phase: "waiting" },
182+waitJson: { kind: "task", taskId: "task-child" },
183+blockedTaskId: "task-child",
184+blockedSummary: "Waiting for child task",
185+});
186+187+const detail = mapTaskFlowDetail({ flow, tasks: [task], summary });
188+189+expect(detail).toEqual({
190+ ...mapTaskFlowView(flow),
191+state: { phase: "waiting" },
192+wait: { kind: "task", taskId: "task-child" },
193+blocked: {
194+taskId: "task-child",
195+summary: "Waiting for child task",
196+},
197+tasks: [mapTaskRunView(task)],
198+taskSummary: mapTaskRunAggregateSummary(summary),
199+});
200+expect(detail.taskSummary.byStatus).not.toBe(summary.byStatus);
201+expect(detail.taskSummary.byRuntime).not.toBe(summary.byRuntime);
202+});
203+204+it("summarizes nested tasks when flow detail callers do not provide a summary", () => {
205+const running = makeTask({ taskId: "task-running", status: "running", runtime: "subagent" });
206+const failed = makeTask({ taskId: "task-failed", status: "failed", runtime: "cli" });
207+208+const detail = mapTaskFlowDetail({ flow: makeFlow(), tasks: [running, failed] });
209+210+expect(detail.taskSummary).toEqual(makeSummary());
211+});
212+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。