pi-crew 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +5 -0
  4. package/agents/analyst.md +11 -11
  5. package/agents/critic.md +11 -11
  6. package/agents/executor.md +11 -11
  7. package/agents/explorer.md +11 -11
  8. package/agents/planner.md +11 -11
  9. package/agents/reviewer.md +11 -11
  10. package/agents/security-reviewer.md +11 -11
  11. package/agents/test-engineer.md +11 -11
  12. package/agents/verifier.md +11 -11
  13. package/agents/writer.md +11 -11
  14. package/docs/refactor-tasks-phase3.md +394 -394
  15. package/docs/refactor-tasks-phase4.md +564 -564
  16. package/docs/refactor-tasks-phase5.md +402 -402
  17. package/docs/refactor-tasks-phase6.md +662 -662
  18. package/docs/research-extension-examples.md +297 -297
  19. package/docs/research-extension-system.md +324 -324
  20. package/docs/research-optimization-plan.md +548 -548
  21. package/docs/research-pi-coding-agent.md +357 -357
  22. package/docs/research-source-pi-crew-reference.md +174 -174
  23. package/docs/resource-formats.md +10 -8
  24. package/docs/runtime-flow.md +148 -148
  25. package/docs/source-runtime-refactor-map.md +83 -83
  26. package/docs/usage.md +6 -0
  27. package/index.ts +6 -6
  28. package/package.json +3 -3
  29. package/schema.json +2 -2
  30. package/src/agents/agent-serializer.ts +34 -34
  31. package/src/config/config.ts +8 -4
  32. package/src/extension/cross-extension-rpc.ts +82 -82
  33. package/src/extension/import-index.ts +18 -2
  34. package/src/extension/register.ts +11 -1
  35. package/src/extension/registration/compaction-guard.ts +125 -125
  36. package/src/extension/registration/subagent-helpers.ts +30 -6
  37. package/src/extension/registration/subagent-tools.ts +8 -3
  38. package/src/extension/result-watcher.ts +98 -98
  39. package/src/extension/run-import.ts +12 -2
  40. package/src/extension/run-index.ts +12 -2
  41. package/src/extension/run-maintenance.ts +24 -24
  42. package/src/extension/team-tool/api.ts +54 -14
  43. package/src/extension/team-tool/cancel.ts +31 -31
  44. package/src/extension/team-tool/doctor.ts +179 -179
  45. package/src/extension/team-tool/inspect.ts +41 -41
  46. package/src/extension/team-tool/lifecycle-actions.ts +79 -79
  47. package/src/extension/team-tool/plan.ts +19 -19
  48. package/src/extension/team-tool/status.ts +73 -73
  49. package/src/observability/correlation.ts +35 -35
  50. package/src/observability/event-to-metric.ts +54 -54
  51. package/src/observability/exporters/adapter.ts +24 -24
  52. package/src/observability/exporters/otlp-exporter.ts +65 -65
  53. package/src/observability/exporters/prometheus-exporter.ts +47 -47
  54. package/src/observability/metric-registry.ts +72 -72
  55. package/src/observability/metric-retention.ts +46 -46
  56. package/src/observability/metric-sink.ts +51 -51
  57. package/src/observability/metrics-primitives.ts +166 -166
  58. package/src/prompt/prompt-runtime.ts +68 -68
  59. package/src/runtime/agent-control.ts +64 -64
  60. package/src/runtime/agent-memory.ts +72 -72
  61. package/src/runtime/agent-observability.ts +114 -113
  62. package/src/runtime/async-marker.ts +26 -26
  63. package/src/runtime/background-runner.ts +53 -53
  64. package/src/runtime/crash-recovery.ts +56 -56
  65. package/src/runtime/crew-agent-records.ts +54 -9
  66. package/src/runtime/crew-agent-runtime.ts +58 -58
  67. package/src/runtime/deadletter.ts +36 -36
  68. package/src/runtime/direct-run.ts +35 -35
  69. package/src/runtime/foreground-control.ts +82 -82
  70. package/src/runtime/green-contract.ts +46 -46
  71. package/src/runtime/group-join.ts +88 -88
  72. package/src/runtime/heartbeat-gradient.ts +28 -28
  73. package/src/runtime/heartbeat-watcher.ts +80 -80
  74. package/src/runtime/live-agent-control.ts +87 -78
  75. package/src/runtime/live-agent-manager.ts +85 -85
  76. package/src/runtime/live-control-realtime.ts +36 -36
  77. package/src/runtime/live-session-runtime.ts +299 -299
  78. package/src/runtime/manifest-cache.ts +248 -212
  79. package/src/runtime/model-fallback.ts +261 -261
  80. package/src/runtime/parallel-research.ts +44 -44
  81. package/src/runtime/parallel-utils.ts +99 -99
  82. package/src/runtime/pi-json-output.ts +111 -111
  83. package/src/runtime/policy-engine.ts +78 -78
  84. package/src/runtime/post-exit-stdio-guard.ts +86 -86
  85. package/src/runtime/process-status.ts +56 -56
  86. package/src/runtime/progress-event-coalescer.ts +43 -43
  87. package/src/runtime/recovery-recipes.ts +74 -74
  88. package/src/runtime/retry-executor.ts +59 -59
  89. package/src/runtime/role-permission.ts +39 -39
  90. package/src/runtime/session-usage.ts +79 -79
  91. package/src/runtime/sidechain-output.ts +28 -28
  92. package/src/runtime/subagent-manager.ts +80 -12
  93. package/src/runtime/task-display.ts +38 -38
  94. package/src/runtime/task-output-context.ts +127 -106
  95. package/src/runtime/task-runner/live-executor.ts +98 -98
  96. package/src/runtime/task-runner/progress.ts +111 -111
  97. package/src/runtime/task-runner/result-utils.ts +14 -14
  98. package/src/runtime/task-runner/state-helpers.ts +22 -22
  99. package/src/runtime/team-runner.ts +1 -1
  100. package/src/runtime/worker-heartbeat.ts +21 -21
  101. package/src/runtime/worker-startup.ts +57 -57
  102. package/src/schema/config-schema.ts +21 -21
  103. package/src/schema/team-tool-schema.ts +100 -100
  104. package/src/state/artifact-store.ts +122 -108
  105. package/src/state/contracts.ts +105 -105
  106. package/src/state/jsonl-writer.ts +77 -77
  107. package/src/state/mailbox.ts +67 -22
  108. package/src/state/state-store.ts +36 -5
  109. package/src/state/task-claims.ts +42 -42
  110. package/src/state/usage.ts +29 -29
  111. package/src/subagents/async-entry.ts +1 -1
  112. package/src/subagents/index.ts +3 -3
  113. package/src/subagents/live/control.ts +1 -1
  114. package/src/subagents/live/manager.ts +1 -1
  115. package/src/subagents/live/realtime.ts +1 -1
  116. package/src/subagents/live/session-runtime.ts +1 -1
  117. package/src/subagents/manager.ts +1 -1
  118. package/src/subagents/spawn.ts +1 -1
  119. package/src/teams/discover-teams.ts +27 -5
  120. package/src/teams/team-serializer.ts +38 -36
  121. package/src/types/diff.d.ts +18 -18
  122. package/src/ui/crew-footer.ts +101 -101
  123. package/src/ui/crew-select-list.ts +111 -111
  124. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  125. package/src/ui/dynamic-border.ts +25 -25
  126. package/src/ui/layout-primitives.ts +106 -106
  127. package/src/ui/loaders.ts +158 -158
  128. package/src/ui/mascot.ts +441 -441
  129. package/src/ui/render-diff.ts +119 -119
  130. package/src/ui/run-dashboard.ts +5 -2
  131. package/src/ui/run-snapshot-cache.ts +19 -8
  132. package/src/ui/spinner.ts +17 -17
  133. package/src/ui/status-colors.ts +54 -54
  134. package/src/ui/syntax-highlight.ts +116 -116
  135. package/src/ui/transcript-viewer.ts +15 -1
  136. package/src/utils/completion-dedupe.ts +63 -63
  137. package/src/utils/file-coalescer.ts +84 -84
  138. package/src/utils/frontmatter.ts +36 -36
  139. package/src/utils/fs-watch.ts +31 -31
  140. package/src/utils/git.ts +262 -262
  141. package/src/utils/ids.ts +12 -12
  142. package/src/utils/names.ts +26 -26
  143. package/src/utils/paths.ts +3 -2
  144. package/src/utils/safe-paths.ts +34 -0
  145. package/src/utils/sleep.ts +32 -32
  146. package/src/utils/timings.ts +31 -31
  147. package/src/utils/visual.ts +159 -159
  148. package/src/workflows/discover-workflows.ts +30 -3
  149. package/src/workflows/validate-workflow.ts +40 -40
  150. package/src/worktree/branch-freshness.ts +45 -45
  151. package/teams/default.team.md +12 -12
  152. package/teams/fast-fix.team.md +11 -11
  153. package/teams/implementation.team.md +18 -18
  154. package/teams/parallel-research.team.md +14 -14
  155. package/teams/research.team.md +11 -11
  156. package/teams/review.team.md +12 -12
  157. package/workflows/default.workflow.md +29 -29
  158. package/workflows/fast-fix.workflow.md +22 -22
  159. package/workflows/implementation.workflow.md +38 -38
  160. package/workflows/parallel-research.workflow.md +46 -46
  161. package/workflows/research.workflow.md +22 -22
  162. package/workflows/review.workflow.md +30 -30
@@ -1,212 +1,248 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { closeWatcher, watchWithErrorHandler } from "../utils/fs-watch.ts";
4
- import { findRepoRoot, projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
5
- import type { TeamRunManifest } from "../state/types.ts";
6
- import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
7
-
8
- export interface ManifestCache {
9
- list(limit?: number): TeamRunManifest[];
10
- get(runId: string): TeamRunManifest | undefined;
11
- clear(runId?: string): void;
12
- dispose(): void;
13
- }
14
-
15
- interface CachedManifest {
16
- path: string;
17
- manifest: TeamRunManifest;
18
- mtimeMs: number;
19
- size: number;
20
- loadedAtMs: number;
21
- }
22
-
23
- interface CachedList {
24
- runs: TeamRunManifest[];
25
- limit?: number;
26
- expireAtMs: number;
27
- }
28
-
29
- export interface ManifestCacheOptions {
30
- debounceMs?: number;
31
- watch?: boolean;
32
- maxEntries?: number;
33
- }
34
-
35
- const DEFAULT_TTL_MS = 500;
36
-
37
- interface ParsedEntry {
38
- runId: string;
39
- path: string;
40
- manifest?: TeamRunManifest;
41
- }
42
-
43
- function manifestPathForRun(root: string, runId: string): string {
44
- return path.join(root, runId, DEFAULT_PATHS.state.manifestFile);
45
- }
46
-
47
- function parseManifest(filePath: string): TeamRunManifest | undefined {
48
- try {
49
- return JSON.parse(fs.readFileSync(filePath, "utf-8")) as TeamRunManifest;
50
- } catch {
51
- return undefined;
52
- }
53
- }
54
-
55
- function parseManifestIfChanged(filePath: string, previous?: CachedManifest): CachedManifest | undefined {
56
- let stat: fs.Stats;
57
- try {
58
- stat = fs.statSync(filePath);
59
- } catch {
60
- return undefined;
61
- }
62
- if (previous && previous.mtimeMs === stat.mtimeMs && previous.size === stat.size) {
63
- return previous;
64
- }
65
- const manifest = parseManifest(filePath);
66
- if (!manifest) return undefined;
67
- return {
68
- path: filePath,
69
- manifest,
70
- mtimeMs: stat.mtimeMs,
71
- size: stat.size,
72
- loadedAtMs: Date.now(),
73
- };
74
- }
75
-
76
- function listRunRoots(cwd: string): string[] {
77
- const base = findRepoRoot(cwd) ? projectCrewRoot(cwd) : userCrewRoot();
78
- return [path.join(base, DEFAULT_PATHS.state.runsSubdir)];
79
- }
80
-
81
- function collectRoots(root: string): ParsedEntry[] {
82
- if (!fs.existsSync(root)) return [];
83
- let entries: string[];
84
- try {
85
- entries = fs.readdirSync(root);
86
- } catch {
87
- return [];
88
- }
89
- return entries
90
- .filter((entry) => entry.length > 0)
91
- .map((entry) => ({ runId: entry, path: manifestPathForRun(root, entry) }));
92
- }
93
-
94
- export function createManifestCache(cwd: string, options: ManifestCacheOptions = {}): ManifestCache {
95
- const ttlMs = options.debounceMs ?? DEFAULT_TTL_MS;
96
- const maxEntries = options.maxEntries ?? DEFAULT_CACHE.manifestMaxEntries;
97
- const roots = listRunRoots(cwd);
98
- const manifestIndex = new Map<string, CachedManifest>();
99
- const listCache = new Map<number, CachedList>();
100
- let listTimer: ReturnType<typeof setTimeout> | undefined;
101
- let watchers: fs.FSWatcher[] = [];
102
-
103
- function invalidate(runId?: string): void {
104
- if (runId) {
105
- manifestIndex.delete(runId);
106
- } else {
107
- manifestIndex.clear();
108
- }
109
- listCache.clear();
110
- }
111
-
112
- function scheduleListRefresh(): void {
113
- if (listTimer) {
114
- clearTimeout(listTimer);
115
- }
116
- listTimer = setTimeout(() => {
117
- listTimer = undefined;
118
- listCache.clear();
119
- }, ttlMs);
120
- listTimer.unref?.();
121
- }
122
-
123
- function loadManifest(runId: string, rootsToCheck: string[]): CachedManifest | undefined {
124
- let cached = manifestIndex.get(runId);
125
- for (const root of rootsToCheck) {
126
- const manifestPath = manifestPathForRun(root, runId);
127
- const parsed = parseManifestIfChanged(manifestPath, cached);
128
- if (parsed) {
129
- if (!cached || parsed.mtimeMs !== cached.mtimeMs || parsed.size !== cached.size) {
130
- manifestIndex.set(runId, parsed);
131
- if (manifestIndex.size > maxEntries) {
132
- const oldest = [...manifestIndex.values()].sort((a, b) => a.loadedAtMs - b.loadedAtMs)[0];
133
- if (oldest) manifestIndex.delete(oldest.manifest.runId);
134
- }
135
- }
136
- return manifestIndex.get(runId);
137
- }
138
- }
139
- return undefined;
140
- }
141
-
142
- function list(limit = DEFAULT_CACHE.manifestMaxEntries): TeamRunManifest[] {
143
- const now = Date.now();
144
- const cached = listCache.get(limit);
145
- if (cached && cached.expireAtMs > now) {
146
- return cached.runs;
147
- }
148
- const parsedEntries = roots.flatMap((root) => collectRoots(root));
149
- const unique = new Map<string, CachedManifest | undefined>();
150
- for (const entry of parsedEntries) {
151
- if (entry.runId.length === 0) continue;
152
- let cached = manifestIndex.get(entry.runId);
153
- const parsed = parseManifestIfChanged(entry.path, cached);
154
- if (parsed) {
155
- cached = parsed;
156
- manifestIndex.set(entry.runId, cached);
157
- }
158
- if (cached) unique.set(entry.runId, cached);
159
- }
160
-
161
-
162
- const runs = [...unique.values()].filter((value): value is CachedManifest => value !== undefined).map((value) => value.manifest);
163
- const sorted = runs.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
164
- const limited = sorted.slice(0, Math.max(0, limit));
165
- if (manifestIndex.size > maxEntries) {
166
- const removeCount = manifestIndex.size - maxEntries;
167
- const oldest = [...manifestIndex.values()].sort((a, b) => a.loadedAtMs - b.loadedAtMs).slice(0, removeCount);
168
- for (const entry of oldest) manifestIndex.delete(entry.manifest.runId);
169
- }
170
- const result = limited;
171
- listCache.set(limit, { runs: result, limit, expireAtMs: now + ttlMs });
172
- return result;
173
- }
174
-
175
- function get(runId: string): TeamRunManifest | undefined {
176
- const cached = loadManifest(runId, roots);
177
- if (cached) return cached.manifest;
178
- return undefined;
179
- }
180
-
181
- if (options.watch ?? true) {
182
- for (const root of roots) {
183
- const watcher = watchWithErrorHandler(root, () => {
184
- scheduleListRefresh();
185
- }, () => {
186
- scheduleListRefresh();
187
- });
188
- if (watcher) {
189
- watcher.unref?.();
190
- watchers.push(watcher);
191
- }
192
- }
193
- }
194
-
195
- return {
196
- list,
197
- get,
198
- clear(runId) {
199
- invalidate(runId);
200
- },
201
- dispose() {
202
- if (listTimer) {
203
- clearTimeout(listTimer);
204
- listTimer = undefined;
205
- }
206
- for (const watcher of watchers) closeWatcher(watcher);
207
- watchers = [];
208
- manifestIndex.clear();
209
- listCache.clear();
210
- },
211
- };
212
- }
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { closeWatcher, watchWithErrorHandler } from "../utils/fs-watch.ts";
4
+ import { findRepoRoot, projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
5
+ import { isSafePathId, resolveContainedRelativePath, resolveRealContainedPath } from "../utils/safe-paths.ts";
6
+ import type { TeamRunManifest } from "../state/types.ts";
7
+ import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
8
+
9
+ export interface ManifestCache {
10
+ list(limit?: number): TeamRunManifest[];
11
+ get(runId: string): TeamRunManifest | undefined;
12
+ clear(runId?: string): void;
13
+ dispose(): void;
14
+ }
15
+
16
+ interface CachedManifest {
17
+ path: string;
18
+ manifest: TeamRunManifest;
19
+ mtimeMs: number;
20
+ size: number;
21
+ loadedAtMs: number;
22
+ }
23
+
24
+ interface CachedList {
25
+ runs: TeamRunManifest[];
26
+ limit?: number;
27
+ expireAtMs: number;
28
+ }
29
+
30
+ export interface ManifestCacheOptions {
31
+ debounceMs?: number;
32
+ watch?: boolean;
33
+ maxEntries?: number;
34
+ }
35
+
36
+ const DEFAULT_TTL_MS = 500;
37
+
38
+ interface ParsedEntry {
39
+ runId: string;
40
+ path: string;
41
+ manifest?: TeamRunManifest;
42
+ }
43
+
44
+ function manifestPathForRun(root: string, runId: string): string | undefined {
45
+ if (!isSafePathId(runId)) return undefined;
46
+ try {
47
+ return path.join(resolveRealContainedPath(root, runId), DEFAULT_PATHS.state.manifestFile);
48
+ } catch {
49
+ return undefined;
50
+ }
51
+ }
52
+
53
+ function parseManifest(filePath: string): TeamRunManifest | undefined {
54
+ try {
55
+ return JSON.parse(fs.readFileSync(filePath, "utf-8")) as TeamRunManifest;
56
+ } catch {
57
+ return undefined;
58
+ }
59
+ }
60
+
61
+ function sameFilesystemPath(left: string, right: string): boolean {
62
+ if (path.resolve(left) === path.resolve(right)) return true;
63
+ try {
64
+ return fs.realpathSync.native(left) === fs.realpathSync.native(right);
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ function validateManifestForRoot(root: string, runId: string, manifest: TeamRunManifest): boolean {
71
+ try {
72
+ if (!isSafePathId(runId)) return false;
73
+ const stateRoot = resolveContainedRelativePath(root, runId, "runId");
74
+ const crewRoot = path.dirname(path.dirname(root));
75
+ const artifactsRoot = resolveContainedRelativePath(path.join(crewRoot, DEFAULT_PATHS.state.artifactsSubdir), runId, "runId");
76
+ if (manifest.runId !== runId || !sameFilesystemPath(manifest.stateRoot, stateRoot) || !sameFilesystemPath(manifest.tasksPath, path.join(stateRoot, DEFAULT_PATHS.state.tasksFile)) || !sameFilesystemPath(manifest.eventsPath, path.join(stateRoot, DEFAULT_PATHS.state.eventsFile)) || !sameFilesystemPath(manifest.artifactsRoot, artifactsRoot)) return false;
77
+ if (fs.existsSync(artifactsRoot)) {
78
+ if (fs.lstatSync(artifactsRoot).isSymbolicLink()) return false;
79
+ resolveRealContainedPath(path.dirname(artifactsRoot), path.basename(artifactsRoot));
80
+ }
81
+ return true;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ function parseManifestIfChanged(root: string, runId: string, filePath: string, previous?: CachedManifest): CachedManifest | undefined {
88
+ let stat: fs.Stats;
89
+ try {
90
+ stat = fs.statSync(filePath);
91
+ } catch {
92
+ return undefined;
93
+ }
94
+ if (previous && previous.mtimeMs === stat.mtimeMs && previous.size === stat.size) {
95
+ return validateManifestForRoot(root, runId, previous.manifest) ? previous : undefined;
96
+ }
97
+ const manifest = parseManifest(filePath);
98
+ if (!manifest || !validateManifestForRoot(root, runId, manifest)) return undefined;
99
+ return {
100
+ path: filePath,
101
+ manifest,
102
+ mtimeMs: stat.mtimeMs,
103
+ size: stat.size,
104
+ loadedAtMs: Date.now(),
105
+ };
106
+ }
107
+
108
+ function listRunRoots(cwd: string): string[] {
109
+ const base = findRepoRoot(cwd) ? projectCrewRoot(cwd) : userCrewRoot();
110
+ return [path.join(base, DEFAULT_PATHS.state.runsSubdir)];
111
+ }
112
+
113
+ function collectRoots(root: string): ParsedEntry[] {
114
+ if (!fs.existsSync(root)) return [];
115
+ let entries: string[];
116
+ try {
117
+ entries = fs.readdirSync(root);
118
+ } catch {
119
+ return [];
120
+ }
121
+ return entries
122
+ .filter((entry) => entry.length > 0 && isSafePathId(entry))
123
+ .map((entry) => ({ runId: entry, path: manifestPathForRun(root, entry) }))
124
+ .filter((entry): entry is ParsedEntry => entry.path !== undefined);
125
+ }
126
+
127
+ export function createManifestCache(cwd: string, options: ManifestCacheOptions = {}): ManifestCache {
128
+ const ttlMs = options.debounceMs ?? DEFAULT_TTL_MS;
129
+ const maxEntries = options.maxEntries ?? DEFAULT_CACHE.manifestMaxEntries;
130
+ const roots = listRunRoots(cwd);
131
+ const manifestIndex = new Map<string, CachedManifest>();
132
+ const listCache = new Map<number, CachedList>();
133
+ let listTimer: ReturnType<typeof setTimeout> | undefined;
134
+ let watchers: fs.FSWatcher[] = [];
135
+
136
+ function invalidate(runId?: string): void {
137
+ if (runId) {
138
+ manifestIndex.delete(runId);
139
+ } else {
140
+ manifestIndex.clear();
141
+ }
142
+ listCache.clear();
143
+ }
144
+
145
+ function scheduleListRefresh(): void {
146
+ if (listTimer) {
147
+ clearTimeout(listTimer);
148
+ }
149
+ listTimer = setTimeout(() => {
150
+ listTimer = undefined;
151
+ listCache.clear();
152
+ }, ttlMs);
153
+ listTimer.unref?.();
154
+ }
155
+
156
+ function loadManifest(runId: string, rootsToCheck: string[]): CachedManifest | undefined {
157
+ let cached = manifestIndex.get(runId);
158
+ if (!isSafePathId(runId)) return undefined;
159
+ for (const root of rootsToCheck) {
160
+ const manifestPath = manifestPathForRun(root, runId);
161
+ if (!manifestPath) continue;
162
+ const parsed = parseManifestIfChanged(root, runId, manifestPath, cached);
163
+ if (parsed) {
164
+ if (!cached || parsed.mtimeMs !== cached.mtimeMs || parsed.size !== cached.size) {
165
+ manifestIndex.set(runId, parsed);
166
+ if (manifestIndex.size > maxEntries) {
167
+ const oldest = [...manifestIndex.values()].sort((a, b) => a.loadedAtMs - b.loadedAtMs)[0];
168
+ if (oldest) manifestIndex.delete(oldest.manifest.runId);
169
+ }
170
+ }
171
+ return manifestIndex.get(runId);
172
+ }
173
+ }
174
+ return undefined;
175
+ }
176
+
177
+ function list(limit = DEFAULT_CACHE.manifestMaxEntries): TeamRunManifest[] {
178
+ const now = Date.now();
179
+ const cached = listCache.get(limit);
180
+ if (cached && cached.expireAtMs > now) {
181
+ return cached.runs;
182
+ }
183
+ const parsedEntries = roots.flatMap((root) => collectRoots(root));
184
+ const unique = new Map<string, CachedManifest | undefined>();
185
+ for (const entry of parsedEntries) {
186
+ if (entry.runId.length === 0) continue;
187
+ let cached = manifestIndex.get(entry.runId);
188
+ const root = path.dirname(path.dirname(entry.path));
189
+ const parsed = parseManifestIfChanged(root, entry.runId, entry.path, cached);
190
+ if (parsed) {
191
+ cached = parsed;
192
+ manifestIndex.set(entry.runId, cached);
193
+ }
194
+ if (cached) unique.set(entry.runId, cached);
195
+ }
196
+
197
+
198
+ const runs = [...unique.values()].filter((value): value is CachedManifest => value !== undefined).map((value) => value.manifest);
199
+ const sorted = runs.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
200
+ const limited = sorted.slice(0, Math.max(0, limit));
201
+ if (manifestIndex.size > maxEntries) {
202
+ const removeCount = manifestIndex.size - maxEntries;
203
+ const oldest = [...manifestIndex.values()].sort((a, b) => a.loadedAtMs - b.loadedAtMs).slice(0, removeCount);
204
+ for (const entry of oldest) manifestIndex.delete(entry.manifest.runId);
205
+ }
206
+ const result = limited;
207
+ listCache.set(limit, { runs: result, limit, expireAtMs: now + ttlMs });
208
+ return result;
209
+ }
210
+
211
+ function get(runId: string): TeamRunManifest | undefined {
212
+ const cached = loadManifest(runId, roots);
213
+ if (cached) return cached.manifest;
214
+ return undefined;
215
+ }
216
+
217
+ if (options.watch ?? true) {
218
+ for (const root of roots) {
219
+ const watcher = watchWithErrorHandler(root, () => {
220
+ scheduleListRefresh();
221
+ }, () => {
222
+ scheduleListRefresh();
223
+ });
224
+ if (watcher) {
225
+ watcher.unref?.();
226
+ watchers.push(watcher);
227
+ }
228
+ }
229
+ }
230
+
231
+ return {
232
+ list,
233
+ get,
234
+ clear(runId) {
235
+ invalidate(runId);
236
+ },
237
+ dispose() {
238
+ if (listTimer) {
239
+ clearTimeout(listTimer);
240
+ listTimer = undefined;
241
+ }
242
+ for (const watcher of watchers) closeWatcher(watcher);
243
+ watchers = [];
244
+ manifestIndex.clear();
245
+ listCache.clear();
246
+ },
247
+ };
248
+ }