























@@ -0,0 +1,234 @@
1+import { describe, expect, it } from "vitest";
2+import {
3+buildSubagentRunReadIndexFromRuns,
4+countActiveDescendantRunsFromRuns,
5+getSubagentRunByChildSessionKeyFromRuns,
6+listRunsForControllerFromRuns,
7+type SubagentRunReadIndex,
8+} from "./subagent-registry-queries.js";
9+import type { SubagentRunRecord } from "./subagent-registry.types.js";
10+11+function makeRun(overrides: Partial<SubagentRunRecord>): SubagentRunRecord {
12+const runId = overrides.runId ?? "run-default";
13+const childSessionKey = overrides.childSessionKey ?? `agent:main:subagent:${runId}`;
14+const requesterSessionKey = overrides.requesterSessionKey ?? "agent:main:main";
15+return {
16+ runId,
17+ childSessionKey,
18+controllerSessionKey: overrides.controllerSessionKey,
19+ requesterSessionKey,
20+requesterDisplayKey: requesterSessionKey,
21+task: "test task",
22+cleanup: "keep",
23+createdAt: overrides.createdAt ?? Date.now(),
24+ ...overrides,
25+};
26+}
27+28+function toRunMap(runs: SubagentRunRecord[]): Map<string, SubagentRunRecord> {
29+return new Map(runs.map((run) => [run.runId, run]));
30+}
31+32+function listRunsForController(
33+index: SubagentRunReadIndex,
34+controllerSessionKey: string,
35+): readonly SubagentRunRecord[] {
36+return index.runsByControllerSessionKey.get(controllerSessionKey.trim()) ?? [];
37+}
38+39+describe("subagent registry read index", () => {
40+it("matches existing query helpers while reusing one indexed snapshot", () => {
41+const now = Date.now();
42+const root = "agent:main:main";
43+const parent = "agent:main:subagent:parent";
44+const liveChild = "agent:main:subagent:parent:subagent:live-child";
45+const movedChild = "agent:main:subagent:moved-child";
46+const runs = toRunMap([
47+makeRun({
48+runId: "run-parent",
49+childSessionKey: parent,
50+controllerSessionKey: root,
51+requesterSessionKey: root,
52+createdAt: now - 5_000,
53+startedAt: now - 4_500,
54+endedAt: now - 2_500,
55+}),
56+makeRun({
57+runId: "run-live-child",
58+childSessionKey: liveChild,
59+controllerSessionKey: parent,
60+requesterSessionKey: parent,
61+createdAt: now - 2_000,
62+startedAt: now - 1_500,
63+}),
64+makeRun({
65+runId: "run-moved-old",
66+childSessionKey: movedChild,
67+controllerSessionKey: root,
68+requesterSessionKey: root,
69+createdAt: now - 4_000,
70+startedAt: now - 3_500,
71+}),
72+makeRun({
73+runId: "run-moved-new",
74+childSessionKey: movedChild,
75+controllerSessionKey: "agent:main:other-controller",
76+requesterSessionKey: "agent:main:other-controller",
77+createdAt: now - 1_000,
78+startedAt: now - 900,
79+}),
80+]);
81+82+const index = buildSubagentRunReadIndexFromRuns({ runs, now });
83+84+expect(listRunsForController(index, root)).toEqual(listRunsForControllerFromRuns(runs, root));
85+expect(index.getDisplaySubagentRun(parent)).toEqual(
86+getSubagentRunByChildSessionKeyFromRuns(runs, parent),
87+);
88+expect(index.countActiveDescendantRuns(root)).toBe(
89+countActiveDescendantRunsFromRuns(runs, root),
90+);
91+expect(index.countActiveDescendantRuns(root)).toBe(1);
92+});
93+94+it("handles empty registry snapshots", () => {
95+const runs = new Map<string, SubagentRunRecord>();
96+const index = buildSubagentRunReadIndexFromRuns({ runs });
97+98+expect(listRunsForController(index, "agent:main:main")).toEqual([]);
99+expect(index.getDisplaySubagentRun("agent:main:subagent:missing")).toBeNull();
100+expect(index.countActiveDescendantRuns("agent:main:main")).toBe(0);
101+});
102+103+it("uses requesterSessionKey when controllerSessionKey is missing", () => {
104+const root = "agent:main:main";
105+const run = makeRun({
106+runId: "run-controller-fallback",
107+childSessionKey: "agent:main:subagent:fallback-child",
108+requesterSessionKey: root,
109+controllerSessionKey: undefined,
110+});
111+const runs = toRunMap([run]);
112+const index = buildSubagentRunReadIndexFromRuns({ runs });
113+114+expect(listRunsForController(index, root)).toEqual(listRunsForControllerFromRuns(runs, root));
115+expect(listRunsForController(index, root)).toEqual([run]);
116+});
117+118+it("keeps moved middle descendants under the latest requester", () => {
119+const now = Date.now();
120+const root = "agent:main:root";
121+const otherRoot = "agent:main:other-root";
122+const middle = "agent:main:subagent:middle";
123+const grandchild = "agent:main:subagent:grandchild";
124+const runs = toRunMap([
125+makeRun({
126+runId: "run-middle-old",
127+childSessionKey: middle,
128+controllerSessionKey: root,
129+requesterSessionKey: root,
130+createdAt: now - 3_000,
131+startedAt: now - 2_900,
132+}),
133+makeRun({
134+runId: "run-grandchild",
135+childSessionKey: grandchild,
136+controllerSessionKey: middle,
137+requesterSessionKey: middle,
138+createdAt: now - 2_000,
139+startedAt: now - 1_900,
140+}),
141+makeRun({
142+runId: "run-middle-moved",
143+childSessionKey: middle,
144+controllerSessionKey: otherRoot,
145+requesterSessionKey: otherRoot,
146+createdAt: now - 1_000,
147+startedAt: now - 900,
148+}),
149+]);
150+const index = buildSubagentRunReadIndexFromRuns({ runs, now });
151+152+expect(index.countActiveDescendantRuns(root)).toBe(
153+countActiveDescendantRunsFromRuns(runs, root),
154+);
155+expect(index.countActiveDescendantRuns(root)).toBe(0);
156+expect(index.countActiveDescendantRuns(otherRoot)).toBe(
157+countActiveDescendantRunsFromRuns(runs, otherRoot),
158+);
159+expect(index.countActiveDescendantRuns(otherRoot)).toBe(2);
160+});
161+162+it("keeps one snapshot stable for the lifetime of the context", () => {
163+const root = "agent:main:main";
164+const runs = toRunMap([
165+makeRun({
166+runId: "run-original",
167+childSessionKey: "agent:main:subagent:original",
168+requesterSessionKey: root,
169+controllerSessionKey: root,
170+}),
171+]);
172+const index = buildSubagentRunReadIndexFromRuns({ runs });
173+174+runs.set(
175+"run-added-after-context",
176+makeRun({
177+runId: "run-added-after-context",
178+childSessionKey: "agent:main:subagent:added",
179+requesterSessionKey: root,
180+controllerSessionKey: root,
181+}),
182+);
183+184+expect(listRunsForController(index, root).map((run) => run.runId)).toEqual(["run-original"]);
185+expect(
186+listRunsForController(buildSubagentRunReadIndexFromRuns({ runs }), root).map(
187+(run) => run.runId,
188+),
189+).toEqual(["run-original", "run-added-after-context"]);
190+});
191+192+it("normalizes display lookup keys for whitespace-padded child session keys", () => {
193+const normalizedChildSessionKey = "agent:main:subagent:whitespace-child";
194+const run = makeRun({
195+runId: "run-whitespace-child",
196+childSessionKey: ` ${normalizedChildSessionKey} `,
197+requesterSessionKey: "agent:main:main",
198+});
199+const runs = toRunMap([run]);
200+const index = buildSubagentRunReadIndexFromRuns({ runs });
201+202+expect(index.getDisplaySubagentRun(normalizedChildSessionKey)).toBe(run);
203+});
204+205+it("keeps the display-row preference for in-memory records over persisted snapshots", () => {
206+const childSessionKey = "agent:main:subagent:display-child";
207+const persistedRuns = toRunMap([
208+makeRun({
209+runId: "run-persisted-newer",
210+ childSessionKey,
211+requesterSessionKey: "agent:main:main",
212+createdAt: 200,
213+startedAt: 200,
214+}),
215+]);
216+const inMemoryRuns = toRunMap([
217+makeRun({
218+runId: "run-memory-older-ended",
219+ childSessionKey,
220+requesterSessionKey: "agent:main:main",
221+createdAt: 100,
222+startedAt: 100,
223+endedAt: 150,
224+}),
225+]);
226+227+const index = buildSubagentRunReadIndexFromRuns({
228+runs: persistedRuns,
229+inMemoryRuns: inMemoryRuns.values(),
230+});
231+232+expect(index.getDisplaySubagentRun(childSessionKey)?.runId).toBe("run-memory-older-ended");
233+});
234+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。