@marcoscale98/pi-extensible-workflows 5.14.1-fork.1

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 (204) hide show
  1. package/CHANGELOG.md +536 -0
  2. package/README.md +64 -0
  3. package/dist/src/agent-execution.d.ts +338 -0
  4. package/dist/src/agent-execution.js +1633 -0
  5. package/dist/src/background-widget.d.ts +74 -0
  6. package/dist/src/background-widget.js +1340 -0
  7. package/dist/src/budget.d.ts +38 -0
  8. package/dist/src/budget.js +188 -0
  9. package/dist/src/changelog.d.ts +6 -0
  10. package/dist/src/changelog.js +112 -0
  11. package/dist/src/decoders.d.ts +96 -0
  12. package/dist/src/decoders.js +629 -0
  13. package/dist/src/execution.d.ts +19 -0
  14. package/dist/src/execution.js +749 -0
  15. package/dist/src/herdr.d.ts +53 -0
  16. package/dist/src/herdr.js +270 -0
  17. package/dist/src/host-delivery.d.ts +95 -0
  18. package/dist/src/host-delivery.js +376 -0
  19. package/dist/src/host-navigator.d.ts +83 -0
  20. package/dist/src/host-navigator.js +1087 -0
  21. package/dist/src/host-phases.d.ts +89 -0
  22. package/dist/src/host-phases.js +320 -0
  23. package/dist/src/host-recovery.d.ts +141 -0
  24. package/dist/src/host-recovery.js +356 -0
  25. package/dist/src/host-runtime.d.ts +78 -0
  26. package/dist/src/host-runtime.js +378 -0
  27. package/dist/src/host-view.d.ts +116 -0
  28. package/dist/src/host-view.js +868 -0
  29. package/dist/src/host.d.ts +40 -0
  30. package/dist/src/host.js +1641 -0
  31. package/dist/src/index.d.ts +29 -0
  32. package/dist/src/index.js +13725 -0
  33. package/dist/src/index.js.map +6 -0
  34. package/dist/src/io.d.ts +14 -0
  35. package/dist/src/io.js +82 -0
  36. package/dist/src/package-identity.d.ts +4 -0
  37. package/dist/src/package-identity.js +6 -0
  38. package/dist/src/paths.d.ts +14 -0
  39. package/dist/src/paths.js +51 -0
  40. package/dist/src/persistence.d.ts +5 -0
  41. package/dist/src/persistence.js +5 -0
  42. package/dist/src/pi-runtime-adapter.d.ts +27 -0
  43. package/dist/src/pi-runtime-adapter.js +243 -0
  44. package/dist/src/pi-runtime-runner.d.ts +42 -0
  45. package/dist/src/pi-runtime-runner.js +505 -0
  46. package/dist/src/registry.d.ts +48 -0
  47. package/dist/src/registry.js +379 -0
  48. package/dist/src/retention.d.ts +17 -0
  49. package/dist/src/retention.js +195 -0
  50. package/dist/src/runtime/agent-runner.d.ts +143 -0
  51. package/dist/src/runtime/agent-runner.js +12 -0
  52. package/dist/src/runtime/index.d.ts +2 -0
  53. package/dist/src/runtime/index.js +2 -0
  54. package/dist/src/runtime/namespace.d.ts +14 -0
  55. package/dist/src/runtime/namespace.js +1 -0
  56. package/dist/src/runtime/workflow-result.d.ts +2 -0
  57. package/dist/src/runtime/workflow-result.js +6 -0
  58. package/dist/src/session-handoff.d.ts +2 -0
  59. package/dist/src/session-handoff.js +82 -0
  60. package/dist/src/session-lease.d.ts +12 -0
  61. package/dist/src/session-lease.js +180 -0
  62. package/dist/src/store.d.ts +95 -0
  63. package/dist/src/store.js +951 -0
  64. package/dist/src/subagent-manager-handle.d.ts +4 -0
  65. package/dist/src/subagent-manager-handle.js +6 -0
  66. package/dist/src/tool-timing.d.ts +12 -0
  67. package/dist/src/tool-timing.js +33 -0
  68. package/dist/src/trajectory-contracts.d.ts +8 -0
  69. package/dist/src/trajectory-contracts.js +9 -0
  70. package/dist/src/trajectory-host-handle.d.ts +12 -0
  71. package/dist/src/trajectory-host-handle.js +6 -0
  72. package/dist/src/trajectory.d.ts +128 -0
  73. package/dist/src/trajectory.js +594 -0
  74. package/dist/src/types.d.ts +960 -0
  75. package/dist/src/types.js +60 -0
  76. package/dist/src/utils.d.ts +57 -0
  77. package/dist/src/utils.js +225 -0
  78. package/dist/src/validation.d.ts +29 -0
  79. package/dist/src/validation.js +1100 -0
  80. package/dist/src/workflow-artifacts.d.ts +14 -0
  81. package/dist/src/workflow-artifacts.js +40 -0
  82. package/dist/starter/index.d.ts +1 -0
  83. package/dist/starter/index.js +103 -0
  84. package/dist/starter/index.js.map +6 -0
  85. package/dist/starter/review-loop.d.ts +72 -0
  86. package/dist/starter/review-loop.js +43 -0
  87. package/dist/starter/roles/developer.md +6 -0
  88. package/dist/starter/roles/oracle.md +18 -0
  89. package/dist/starter/roles/researcher.md +20 -0
  90. package/dist/starter/roles/reviewer.md +19 -0
  91. package/dist/starter/roles/scout.md +18 -0
  92. package/dist/subagents/index.d.ts +8 -0
  93. package/dist/subagents/index.js +8021 -0
  94. package/dist/subagents/index.js.map +6 -0
  95. package/dist/subagents/src/contracts.d.ts +173 -0
  96. package/dist/subagents/src/contracts.js +68 -0
  97. package/dist/subagents/src/decode.d.ts +15 -0
  98. package/dist/subagents/src/decode.js +182 -0
  99. package/dist/subagents/src/index.d.ts +12 -0
  100. package/dist/subagents/src/index.js +180 -0
  101. package/dist/subagents/src/manager.d.ts +3 -0
  102. package/dist/subagents/src/manager.js +1636 -0
  103. package/dist/subagents/src/navigator.d.ts +5 -0
  104. package/dist/subagents/src/navigator.js +627 -0
  105. package/dist/subagents/src/view.d.ts +70 -0
  106. package/dist/subagents/src/view.js +387 -0
  107. package/dist/subagents/src/worktree.d.ts +34 -0
  108. package/dist/subagents/src/worktree.js +68 -0
  109. package/dist/trajectory/assets/favicon.png +0 -0
  110. package/dist/trajectory/assets/index.html +772 -0
  111. package/dist/trajectory/assets/marked.min.js +6 -0
  112. package/dist/trajectory/assets/morphdom.min.js +1 -0
  113. package/dist/trajectory/index.d.ts +5 -0
  114. package/dist/trajectory/index.js +3459 -0
  115. package/dist/trajectory/index.js.map +6 -0
  116. package/dist/trajectory/src/assets/favicon.png +0 -0
  117. package/dist/trajectory/src/assets/index.html +772 -0
  118. package/dist/trajectory/src/assets/marked.min.js +6 -0
  119. package/dist/trajectory/src/assets/morphdom.min.js +1 -0
  120. package/dist/trajectory/src/export.d.ts +25 -0
  121. package/dist/trajectory/src/export.js +80 -0
  122. package/dist/trajectory/src/index.d.ts +26 -0
  123. package/dist/trajectory/src/index.js +732 -0
  124. package/dist/trajectory/src/server.d.ts +7 -0
  125. package/dist/trajectory/src/server.js +2534 -0
  126. package/dist/trajectory/src/server.js.map +6 -0
  127. package/examples/advanced-patterns/auto-model-router.mjs +78 -0
  128. package/examples/workflow-extension-template/README.md +37 -0
  129. package/examples/workflow-extension-template/extension.test.mjs +59 -0
  130. package/examples/workflow-extension-template/index.js +51 -0
  131. package/examples/workflow-extension-template/roles/reviewer.md +4 -0
  132. package/package.json +105 -0
  133. package/skills/pi-extensible-workflows/SKILL.md +160 -0
  134. package/src/agent-execution.ts +1533 -0
  135. package/src/background-widget.ts +1496 -0
  136. package/src/budget.ts +109 -0
  137. package/src/changelog.ts +114 -0
  138. package/src/decoders.ts +567 -0
  139. package/src/execution.ts +652 -0
  140. package/src/herdr.ts +240 -0
  141. package/src/host-delivery.ts +346 -0
  142. package/src/host-navigator.ts +871 -0
  143. package/src/host-phases.ts +264 -0
  144. package/src/host-recovery.ts +333 -0
  145. package/src/host-runtime.ts +373 -0
  146. package/src/host-view.ts +828 -0
  147. package/src/host.ts +1502 -0
  148. package/src/index.ts +29 -0
  149. package/src/io.ts +73 -0
  150. package/src/package-identity.ts +7 -0
  151. package/src/paths.ts +49 -0
  152. package/src/persistence.ts +11 -0
  153. package/src/pi-runtime-adapter.ts +247 -0
  154. package/src/pi-runtime-runner.ts +436 -0
  155. package/src/registry.ts +321 -0
  156. package/src/retention.ts +178 -0
  157. package/src/runtime/agent-runner.ts +158 -0
  158. package/src/runtime/index.ts +2 -0
  159. package/src/runtime/namespace.ts +16 -0
  160. package/src/runtime/workflow-result.ts +8 -0
  161. package/src/session-handoff.ts +78 -0
  162. package/src/session-lease.ts +125 -0
  163. package/src/store.ts +832 -0
  164. package/src/subagent-manager-handle.ts +8 -0
  165. package/src/tool-timing.ts +47 -0
  166. package/src/trajectory-contracts.ts +13 -0
  167. package/src/trajectory-host-handle.ts +16 -0
  168. package/src/trajectory.ts +543 -0
  169. package/src/types.ts +374 -0
  170. package/src/utils.ts +192 -0
  171. package/src/validation.ts +910 -0
  172. package/src/workflow-artifacts.ts +35 -0
  173. package/starter/index.ts +35 -0
  174. package/starter/prompts/council.md +16 -0
  175. package/starter/prompts/deep-research.md +16 -0
  176. package/starter/prompts/oracle.md +14 -0
  177. package/starter/prompts/parallel-review.md +22 -0
  178. package/starter/prompts/parallel-scout.md +16 -0
  179. package/starter/prompts/review-loop.md +14 -0
  180. package/starter/prompts/review.md +14 -0
  181. package/starter/prompts/scout.md +14 -0
  182. package/starter/review-loop.ts +73 -0
  183. package/starter/roles/developer.md +6 -0
  184. package/starter/roles/oracle.md +18 -0
  185. package/starter/roles/researcher.md +20 -0
  186. package/starter/roles/reviewer.md +19 -0
  187. package/starter/roles/scout.md +18 -0
  188. package/subagents/README.md +145 -0
  189. package/subagents/index.ts +8 -0
  190. package/subagents/src/contracts.ts +176 -0
  191. package/subagents/src/decode.ts +158 -0
  192. package/subagents/src/index.ts +194 -0
  193. package/subagents/src/manager.ts +1560 -0
  194. package/subagents/src/navigator.ts +488 -0
  195. package/subagents/src/view.ts +376 -0
  196. package/subagents/src/worktree.ts +99 -0
  197. package/trajectory/index.ts +5 -0
  198. package/trajectory/src/assets/favicon.png +0 -0
  199. package/trajectory/src/assets/index.html +772 -0
  200. package/trajectory/src/assets/marked.min.js +6 -0
  201. package/trajectory/src/assets/morphdom.min.js +1 -0
  202. package/trajectory/src/export.ts +85 -0
  203. package/trajectory/src/index.ts +604 -0
  204. package/trajectory/src/server.ts +447 -0
@@ -0,0 +1,594 @@
1
+ import { createHash } from "node:crypto";
2
+ import { open, readFile, readdir, stat } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager, getAgentDir, SettingsManager, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
6
+ import { listRunIds, RunStore } from "./persistence.js";
7
+ import { navigatorAttentionSortByState } from "./host-view.js";
8
+ import { normalizeSubagentRunRequest } from "../subagents/src/contracts.js";
9
+ import { statusValue, subagentErrorValue } from "../subagents/src/decode.js";
10
+ import { isNodeError, jsonValue, object, resourcePatternHasMagic, selectResourcesByLayers } from "./utils.js";
11
+ import { canonicalPath, sameFilesystemPath } from "./paths.js";
12
+ export const TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
13
+ const TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
14
+ export { isTrajectoryAction, isTrajectoryTarget, trajectoryActionError } from "./trajectory-contracts.js";
15
+ function sessionFile(reference) {
16
+ const locator = reference?.locator;
17
+ if (!object(locator) || typeof locator.sessionFile !== "string" || !locator.sessionFile)
18
+ return undefined;
19
+ return locator.sessionFile;
20
+ }
21
+ function transcriptToolCallId(value) {
22
+ if (!object(value))
23
+ return undefined;
24
+ if (typeof value.toolCallId === "string")
25
+ return value.toolCallId;
26
+ const message = object(value.message) ? value.message : undefined;
27
+ if (message && typeof message.toolCallId === "string")
28
+ return message.toolCallId;
29
+ if (!message || !Array.isArray(message.content))
30
+ return undefined;
31
+ for (const part of message.content)
32
+ if (object(part) && typeof part.id === "string")
33
+ return part.id;
34
+ return undefined;
35
+ }
36
+ function isTimingTranscriptEntry(value) { return object(value) && value.type === "custom" && value.customType === "pi-workflows:tool-timing"; }
37
+ function timingToolCallId(value) {
38
+ if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data))
39
+ return undefined;
40
+ return typeof value.data.toolCallId === "string" ? value.data.toolCallId : undefined;
41
+ }
42
+ class OversizedTranscriptError extends Error {
43
+ }
44
+ async function readTranscript(path, throwOnError = false) {
45
+ let handle;
46
+ try {
47
+ const info = await stat(path);
48
+ if (!info.isFile())
49
+ return [];
50
+ const startOffset = Math.max(0, info.size - TRAJECTORY_MAX_TRANSCRIPT_BYTES);
51
+ const length = info.size - startOffset;
52
+ handle = await open(path, "r");
53
+ const buffer = Buffer.alloc(length);
54
+ let bytesRead = 0;
55
+ while (bytesRead < length) {
56
+ const result = await handle.read(buffer, bytesRead, length - bytesRead, startOffset + bytesRead);
57
+ if (result.bytesRead === 0)
58
+ break;
59
+ bytesRead += result.bytesRead;
60
+ }
61
+ let content = buffer.subarray(0, bytesRead).toString("utf8");
62
+ if (startOffset > 0) {
63
+ const boundary = Buffer.alloc(1);
64
+ const boundaryRead = await handle.read(boundary, 0, 1, startOffset - 1);
65
+ const startsAtRecordBoundary = boundaryRead.bytesRead === 1 && boundary[0] === 0x0a;
66
+ const firstLine = content.indexOf("\n");
67
+ if (firstLine < 0) {
68
+ if (!startsAtRecordBoundary && throwOnError)
69
+ throw new OversizedTranscriptError("Transcript is too large");
70
+ if (!startsAtRecordBoundary)
71
+ return [];
72
+ }
73
+ else if (firstLine === content.length - 1 && content.slice(0, firstLine).trim() && !startsAtRecordBoundary) {
74
+ if (throwOnError)
75
+ throw new OversizedTranscriptError("Transcript is too large");
76
+ return [];
77
+ }
78
+ else
79
+ content = content.slice(firstLine + 1);
80
+ }
81
+ const entries = [];
82
+ for (const line of content.split("\n")) {
83
+ if (!line.trim())
84
+ continue;
85
+ try {
86
+ const value = JSON.parse(line);
87
+ if (object(value))
88
+ entries.push(value);
89
+ }
90
+ catch { /* A partially written JSONL line is ignored until the next poll. */ }
91
+ }
92
+ const retainedEntries = entries.filter((entry) => !isTimingTranscriptEntry(entry)).slice(-TRAJECTORY_MAX_NON_TIMING_ENTRIES);
93
+ const retainedIds = new Set(retainedEntries.map(transcriptToolCallId).filter((id) => id !== undefined));
94
+ const retainedTiming = entries.filter((entry) => { const id = timingToolCallId(entry); return id !== undefined && retainedIds.has(id); });
95
+ const retained = new Set([...retainedEntries, ...retainedTiming]);
96
+ return entries.filter((entry) => retained.has(entry));
97
+ }
98
+ catch (error) {
99
+ if (throwOnError)
100
+ throw error;
101
+ return [];
102
+ }
103
+ finally {
104
+ await handle?.close();
105
+ }
106
+ }
107
+ function transcriptPaths(run) {
108
+ const paths = new Map();
109
+ for (const agent of run.agents) {
110
+ const path = sessionFile(agent.attemptDetails?.at(-1)?.session);
111
+ if (path)
112
+ paths.set(agent.id, path);
113
+ }
114
+ return paths;
115
+ }
116
+ async function runTranscripts(run) {
117
+ const result = {};
118
+ for (const [agentId, path] of transcriptPaths(run))
119
+ result[agentId] = await readTranscript(path);
120
+ return result;
121
+ }
122
+ export function applySystemPrompts(run, prompts) {
123
+ if (!prompts.length)
124
+ return run;
125
+ const bySession = new Map();
126
+ for (const entry of prompts)
127
+ bySession.set(entry.sessionId, entry.prompt);
128
+ const agents = run.agents.map((agent) => {
129
+ if (agent.systemPrompt)
130
+ return agent;
131
+ const sessionId = agent.attemptDetails?.at(-1)?.session?.sessionId;
132
+ const prompt = typeof sessionId === "string" ? bySession.get(sessionId) : undefined;
133
+ if (prompt === undefined)
134
+ return agent;
135
+ return { ...agent, systemPrompt: prompt };
136
+ });
137
+ if (agents.every((agent, index) => agent === run.agents[index]))
138
+ return run;
139
+ return { ...run, agents };
140
+ }
141
+ function toolDefinitionsFor(tools, catalog) {
142
+ return tools.flatMap((name) => {
143
+ const description = catalog.get(name);
144
+ return description ? [{ name, description }] : [];
145
+ });
146
+ }
147
+ export function applyToolDescriptions(run, catalog) {
148
+ const agents = run.agents.map((agent) => {
149
+ if (agent.toolDefinitions?.length)
150
+ return agent;
151
+ const toolDefinitions = toolDefinitionsFor(agent.tools, catalog);
152
+ if (!toolDefinitions.length)
153
+ return agent;
154
+ return { ...agent, toolDefinitions };
155
+ });
156
+ if (agents.every((agent, index) => agent === run.agents[index]))
157
+ return run;
158
+ return { ...run, agents };
159
+ }
160
+ function piToolCatalog(cwd = process.cwd()) {
161
+ return new Map([
162
+ createReadToolDefinition(cwd),
163
+ createBashToolDefinition(cwd),
164
+ createGrepToolDefinition(cwd),
165
+ createFindToolDefinition(cwd),
166
+ createLsToolDefinition(cwd),
167
+ createEditToolDefinition(cwd),
168
+ createWriteToolDefinition(cwd),
169
+ ].map((tool) => [tool.name, tool.description]));
170
+ }
171
+ export function withPiToolDescriptions(run) {
172
+ return applyToolDescriptions(run, piToolCatalog());
173
+ }
174
+ export function withPiToolDescriptionsForTools(tools, cwd) {
175
+ return toolDefinitionsFor(tools, piToolCatalog(cwd));
176
+ }
177
+ function canonicalExtensionSelector(selector, base) {
178
+ const negated = selector.startsWith("!");
179
+ const body = negated ? selector.slice(1) : selector;
180
+ if (body === "*" || body === "**" || body.startsWith("**/"))
181
+ return selector;
182
+ const resolved = resolve(base, body);
183
+ if (resourcePatternHasMagic(body))
184
+ return `${negated ? "!" : ""}${resolved}`;
185
+ return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
186
+ }
187
+ function skillNameFromPath(path) {
188
+ const file = basename(path);
189
+ return file.toLowerCase() === "skill.md" ? basename(dirname(path)) : file;
190
+ }
191
+ const discoveredResourcesByCwd = new Map();
192
+ async function discoveredResources(cwd) {
193
+ const cached = discoveredResourcesByCwd.get(cwd);
194
+ if (cached)
195
+ return cached;
196
+ const pending = (async () => {
197
+ const agentDir = getAgentDir();
198
+ const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
199
+ const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
200
+ const resolved = await packageManager.resolve();
201
+ return {
202
+ extensions: [...new Set(resolved.extensions.filter((entry) => entry.enabled).map((entry) => canonicalPath(entry.path)))],
203
+ skills: [...new Set(resolved.skills.filter((entry) => entry.enabled).map((entry) => skillNameFromPath(entry.path)))],
204
+ };
205
+ })();
206
+ discoveredResourcesByCwd.set(cwd, pending);
207
+ // A cached rejection would keep the fallback empty for the rest of the process lifetime.
208
+ pending.catch(() => discoveredResourcesByCwd.delete(cwd));
209
+ return pending;
210
+ }
211
+ async function resolveResourceInspection(inspection, cwd, selectorCwd = cwd) {
212
+ if (!inspection || inspection.skills.length && inspection.extensions.length)
213
+ return inspection;
214
+ let discovered;
215
+ try {
216
+ discovered = await discoveredResources(cwd);
217
+ }
218
+ catch {
219
+ return inspection;
220
+ }
221
+ const sources = inspection.selectorSources ?? { global: inspection.selectors, project: {} };
222
+ const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.role?.skills, sources.call?.skills], discovered.skills);
223
+ const extensions = inspection.extensions.length ? inspection.extensions : selectResourcesByLayers([sources.global.extensions, sources.project.extensions, sources.role?.extensions, sources.call?.extensions].map((layer) => layer?.map((selector) => canonicalExtensionSelector(selector, selectorCwd))), discovered.extensions);
224
+ return skills === inspection.skills && extensions === inspection.extensions ? inspection : { ...inspection, skills, extensions };
225
+ }
226
+ export async function withResolvedAttemptResources(attempt, cwd) {
227
+ const resourceSelectors = await resolveResourceInspection(attempt.setup.resourceSelectors, cwd, attempt.setup.cwd || cwd);
228
+ if (resourceSelectors === attempt.setup.resourceSelectors)
229
+ return attempt;
230
+ return { ...attempt, setup: { ...attempt.setup, ...(resourceSelectors === undefined ? {} : { resourceSelectors }) } };
231
+ }
232
+ export async function withResolvedResources(run, cwd) {
233
+ const agents = await Promise.all(run.agents.map(async (agent) => {
234
+ const details = agent.attemptDetails;
235
+ const last = details?.at(-1);
236
+ if (!details || !last)
237
+ return agent;
238
+ const resourceSelectors = await resolveResourceInspection(last.setup.resourceSelectors, cwd, last.setup.cwd || cwd);
239
+ if (resourceSelectors === last.setup.resourceSelectors)
240
+ return agent;
241
+ return { ...agent, attemptDetails: [...details.slice(0, -1), { ...last, setup: { ...last.setup, ...(resourceSelectors === undefined ? {} : { resourceSelectors }) } }] };
242
+ }));
243
+ if (agents.every((agent, index) => agent === run.agents[index]))
244
+ return run;
245
+ return { ...run, agents };
246
+ }
247
+ function withoutAgentActivities(run) {
248
+ const agents = run.agents.map((agent) => {
249
+ if (agent.activity === undefined)
250
+ return agent;
251
+ const next = { ...agent };
252
+ delete next.activity;
253
+ return next;
254
+ });
255
+ return agents.every((agent, index) => agent === run.agents[index]) ? run : { ...run, agents };
256
+ }
257
+ async function loadTrajectoryRun(store, includeTranscripts = true) {
258
+ const value = await store.load();
259
+ const summary = await store.loadSummary().catch(() => undefined);
260
+ const prompts = await store.systemPrompts().catch(() => []);
261
+ const run = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
262
+ return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...(summary?.createdAt === undefined ? {} : { createdAt: summary.createdAt }), transcripts: includeTranscripts ? await runTranscripts(run) : {} };
263
+ }
264
+ export async function loadTrajectoryRuns(cwd, sessionId, home = homedir(), overlay) {
265
+ const loaded = [];
266
+ for (const runId of await listRunIds(cwd, sessionId, home, false)) {
267
+ try {
268
+ const value = await loadTrajectoryRun(new RunStore(cwd, sessionId, runId, home));
269
+ loaded.push(overlay ? { ...value, run: overlay(value.run) } : value);
270
+ }
271
+ catch { /* Ignore corrupt or concurrently removed runs. */ }
272
+ }
273
+ return loaded;
274
+ }
275
+ async function fileMtime(path) { return stat(path).then((value) => value.mtimeMs).catch(() => undefined); }
276
+ /** Lists the session's runs, reloading one only when its state, journal, or a transcript (per `transcriptSignature`) changed. */
277
+ function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature, overlay) {
278
+ const cache = new Map();
279
+ const changed = async (store, entry) => {
280
+ if (await fileMtime(join(store.directory, "state.json")) !== entry.stateMtimeMs)
281
+ return true;
282
+ if (await fileMtime(join(store.directory, "journal.json")) !== entry.journalMtimeMs)
283
+ return true;
284
+ for (const [agentId, path] of transcriptPaths(entry.value.run))
285
+ if (await transcriptSignature(path) !== entry.transcripts.get(agentId))
286
+ return true;
287
+ return false;
288
+ };
289
+ return async () => {
290
+ const ids = await listRunIds(cwd, sessionId, home, false);
291
+ const current = new Set(ids);
292
+ for (const runId of cache.keys())
293
+ if (!current.has(runId))
294
+ cache.delete(runId);
295
+ const loaded = [];
296
+ for (const runId of ids) {
297
+ const store = new RunStore(cwd, sessionId, runId, home);
298
+ try {
299
+ let entry = cache.get(runId);
300
+ if (!entry || await changed(store, entry)) {
301
+ const value = await load(store);
302
+ const transcripts = new Map();
303
+ for (const [agentId, path] of transcriptPaths(value.run))
304
+ transcripts.set(agentId, await transcriptSignature(path));
305
+ entry = { value, stateMtimeMs: (await fileMtime(join(store.directory, "state.json"))) ?? 0, journalMtimeMs: (await fileMtime(join(store.directory, "journal.json"))) ?? 0, transcripts };
306
+ cache.set(runId, entry);
307
+ }
308
+ loaded.push(overlay ? { ...entry.value, run: overlay(entry.value.run) } : entry.value);
309
+ }
310
+ catch {
311
+ cache.delete(runId); /* Ignore corrupt or concurrently removed runs. */
312
+ }
313
+ }
314
+ return loaded;
315
+ };
316
+ }
317
+ export function createTrajectoryRunLoader(cwd, sessionId, home = homedir(), overlay) {
318
+ return createCachedRunLoader(cwd, sessionId, home, (store) => loadTrajectoryRun(store), fileMtime, overlay);
319
+ }
320
+ function transcriptRevision(info) {
321
+ const digest = createHash("sha256").update(`${String(info.mtimeNs)}:${String(info.size)}`).digest();
322
+ return Math.max(1, digest.readUIntBE(0, 6));
323
+ }
324
+ async function transcriptMetadata(path) {
325
+ if (path === undefined)
326
+ return { revision: 0, status: "missing" };
327
+ try {
328
+ const info = await stat(path, { bigint: true });
329
+ const bytes = info.size > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(info.size);
330
+ if (!info.isFile())
331
+ return { revision: transcriptRevision(info), status: "failed" };
332
+ const revision = transcriptRevision(info);
333
+ if (info.size === 0n)
334
+ return { revision, status: "empty", bytes: 0, timing: [] };
335
+ try {
336
+ const entries = await readTranscript(path, true);
337
+ return { revision, status: "available", bytes, timing: entries.filter(isTimingTranscriptEntry) };
338
+ }
339
+ catch (error) {
340
+ if (error instanceof OversizedTranscriptError)
341
+ return { revision, status: "oversized", bytes, timing: [] };
342
+ throw error;
343
+ }
344
+ }
345
+ catch (error) {
346
+ if (isNodeError(error, "ENOENT"))
347
+ return { revision: 0, status: "missing" };
348
+ return { revision: 0, status: "failed" };
349
+ }
350
+ }
351
+ async function metadataForRun(value) {
352
+ const transcripts = {};
353
+ for (const [agentId, path] of transcriptPaths(value.run))
354
+ transcripts[agentId] = await transcriptMetadata(path);
355
+ return { ...value, transcripts };
356
+ }
357
+ async function fileSignature(path) {
358
+ try {
359
+ const info = await stat(path, { bigint: true });
360
+ return info.isFile() ? `${String(info.mtimeNs)}:${String(info.size)}` : undefined;
361
+ }
362
+ catch {
363
+ return undefined;
364
+ }
365
+ }
366
+ export function createTrajectoryRunMetadataLoader(cwd, sessionId, home = homedir(), overlay) {
367
+ return createCachedRunLoader(cwd, sessionId, home, async (store) => metadataForRun(await loadTrajectoryRun(store, false)), fileSignature, overlay);
368
+ }
369
+ export function createTrajectorySubagentMetadataLoader(cwd, sessionId, agentDir, overlay) {
370
+ const loader = createTrajectorySubagentLoader(cwd, sessionId, agentDir, undefined, false);
371
+ const cache = new Map();
372
+ return async () => {
373
+ const values = await loader();
374
+ const current = new Set(values.map((value) => value.id));
375
+ for (const id of cache.keys())
376
+ if (!current.has(id))
377
+ cache.delete(id);
378
+ return Promise.all(values.map(async (value) => {
379
+ const currentValue = overlay ? overlay(value) : value;
380
+ const path = sessionFile(currentValue.attempt?.session);
381
+ const signature = path === undefined ? undefined : await fileSignature(path);
382
+ let entry = cache.get(value.id);
383
+ if (!entry || entry.path !== path || entry.signature !== signature) {
384
+ entry = { path, signature, value: await transcriptMetadata(path) };
385
+ cache.set(value.id, entry);
386
+ }
387
+ return { ...currentValue, transcript: entry.value };
388
+ }));
389
+ };
390
+ }
391
+ function safeTrajectorySubagentId(id) { return id !== "." && id !== ".." && /^[A-Za-z0-9._-]+$/.test(id); }
392
+ export function createTrajectoryTranscriptLoader(cwd, sessionId, home = homedir(), agentDir, resolveSubagentSession) {
393
+ return async (request) => {
394
+ let path;
395
+ try {
396
+ if (request.runId !== undefined && request.agentId !== undefined && request.subagentId === undefined) {
397
+ const value = await new RunStore(cwd, sessionId, request.runId, home).load();
398
+ path = sessionFile(value.run.agents.find((agent) => agent.id === request.agentId)?.attemptDetails?.at(-1)?.session);
399
+ }
400
+ else if (request.subagentId !== undefined && request.runId === undefined && request.agentId === undefined) {
401
+ if (!safeTrajectorySubagentId(request.subagentId))
402
+ return { status: "missing", revision: 0, entries: [], error: "Transcript not found" };
403
+ const rawStatus = await readSubagentJson(join(agentDir, "subagents", request.subagentId, "status.json"));
404
+ const status = statusValue(rawStatus);
405
+ const statusRecord = object(rawStatus) ? rawStatus : undefined;
406
+ const statusCwd = statusRecord === undefined ? undefined : statusRecord.cwd;
407
+ if (!status || status.sessionId !== sessionId || typeof statusCwd === "string" && !sameFilesystemPath(statusCwd, cwd))
408
+ return { status: "missing", revision: 0, entries: [], error: "Transcript not found" };
409
+ path = sessionFile(resolveSubagentSession?.(request.subagentId) ?? status.attemptDetails?.at(-1)?.session);
410
+ }
411
+ }
412
+ catch (error) {
413
+ return isNodeError(error, "ENOENT") ? { status: "missing", revision: 0, entries: [], error: "Transcript not found" } : { status: "failed", revision: 0, entries: [], error: "Transcript failed" };
414
+ }
415
+ const metadata = await transcriptMetadata(path);
416
+ if (request.revision !== undefined && request.revision !== metadata.revision)
417
+ return { ...metadata, entries: [], error: "Transcript revision is stale" };
418
+ if (metadata.status === "missing")
419
+ return { ...metadata, entries: [], error: "Transcript not found" };
420
+ if (metadata.status === "empty")
421
+ return { ...metadata, entries: [] };
422
+ if (path === undefined)
423
+ return { status: "missing", revision: 0, entries: [], error: "Transcript not found" };
424
+ if (metadata.status === "failed" || metadata.status === "oversized")
425
+ return { ...metadata, entries: [], error: metadata.status === "oversized" ? "Transcript is too large" : "Transcript failed" };
426
+ try {
427
+ const entries = await readTranscript(path, true);
428
+ if (Buffer.byteLength(JSON.stringify(entries)) > TRAJECTORY_MAX_TRANSCRIPT_BYTES)
429
+ return { ...metadata, status: "oversized", entries: [], error: "Transcript is too large" };
430
+ return { ...metadata, entries };
431
+ }
432
+ catch {
433
+ return { ...metadata, status: "failed", entries: [], error: "Transcript failed" };
434
+ }
435
+ };
436
+ }
437
+ const TRAJECTORY_SUBAGENT_FILES = ["status.json", "request.json", "result.json", "failure.json"];
438
+ const TRAJECTORY_SUBAGENT_ATTENTION_ORDER = { running: 0, failed: 1, stopped: 2, completed: 3 };
439
+ async function readSubagentJson(path, optional = false, maxBytes) {
440
+ try {
441
+ if (maxBytes !== undefined) {
442
+ const info = await stat(path);
443
+ if (!info.isFile() || info.size > maxBytes)
444
+ return undefined;
445
+ }
446
+ return JSON.parse(await readFile(path, "utf8"));
447
+ }
448
+ catch (error) {
449
+ if (optional && isNodeError(error, "ENOENT"))
450
+ return undefined;
451
+ throw error;
452
+ }
453
+ }
454
+ async function subagentFileBytes(path) {
455
+ try {
456
+ const info = await stat(path);
457
+ return info.isFile() ? info.size : undefined;
458
+ }
459
+ catch {
460
+ return undefined;
461
+ }
462
+ }
463
+ function subagentArtifact(path, bytes) { return bytes === undefined ? undefined : { truncated: true, path, bytes }; }
464
+ function boundedSubagentJson(value) { return jsonValue(value) ? value : undefined; }
465
+ function withoutProgressActivity(progress) {
466
+ const rest = { ...progress };
467
+ delete rest.activity;
468
+ return rest;
469
+ }
470
+ async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscript = true) {
471
+ try {
472
+ const id = basename(directory);
473
+ const status = statusValue(await readSubagentJson(join(directory, "status.json")));
474
+ if (!status || status.id !== id)
475
+ return undefined;
476
+ if (status.sessionId !== sessionId)
477
+ return "foreign";
478
+ const progress = status.progress === undefined ? undefined : withoutProgressActivity(status.progress);
479
+ const request = normalizeSubagentRunRequest(await readSubagentJson(join(directory, "request.json")));
480
+ const resultPath = join(directory, "result.json");
481
+ const failurePath = join(directory, "failure.json");
482
+ const resultBytes = await subagentFileBytes(resultPath);
483
+ const failureBytes = await subagentFileBytes(failurePath);
484
+ const resultValue = await readSubagentJson(resultPath, true, DEFAULT_MAX_BYTES);
485
+ const failureValue = await readSubagentJson(failurePath, true, DEFAULT_MAX_BYTES);
486
+ const resultTruncated = resultValue === undefined && resultBytes !== undefined && resultBytes > DEFAULT_MAX_BYTES;
487
+ const failureTruncated = failureValue === undefined && failureBytes !== undefined && failureBytes > DEFAULT_MAX_BYTES;
488
+ if (!resultTruncated && resultValue !== undefined && boundedSubagentJson(resultValue) === undefined || !failureTruncated && failureValue !== undefined && subagentErrorValue(failureValue) === undefined)
489
+ return undefined;
490
+ const resultArtifact = resultTruncated ? subagentArtifact(resultPath, resultBytes) : undefined;
491
+ const failureArtifact = failureTruncated ? subagentArtifact(failurePath, failureBytes) : undefined;
492
+ const result = resultArtifact ?? (resultValue === undefined ? undefined : boundedSubagentJson(resultValue));
493
+ const failure = failureArtifact ?? (failureValue === undefined ? undefined : subagentErrorValue(failureValue));
494
+ const rawAttempt = status.attemptDetails?.at(-1);
495
+ const attempt = rawAttempt === undefined ? undefined : await withResolvedAttemptResources(rawAttempt, cwd);
496
+ const tools = progress?.state?.tools ?? attempt?.setup.tools ?? [];
497
+ const toolDefinitions = toolDefinitionsFor(tools, piToolCatalog(cwd));
498
+ const model = progress?.state?.model ?? attempt?.setup.model;
499
+ const transcriptPath = sessionFile(attempt?.session);
500
+ const transcript = !includeTranscript ? [] : transcriptPath === undefined ? [] : await readTranscript(transcriptPath);
501
+ return {
502
+ id, sessionId, cwd, mode: request.mode ?? "background", state: status.state, request, tools, transcript,
503
+ ...(request.label === undefined ? {} : { label: request.label }),
504
+ ...(request.role === undefined ? {} : { role: request.role }),
505
+ ...(status.startedAt === undefined ? {} : { startedAt: status.startedAt }),
506
+ ...(status.finishedAt === undefined ? {} : { finishedAt: status.finishedAt }),
507
+ ...(status.attempts === undefined ? {} : { attempts: status.attempts }),
508
+ ...(status.error === undefined ? {} : { error: status.error }),
509
+ ...(status.worktree === undefined ? {} : { worktree: status.worktree }),
510
+ ...(model === undefined ? {} : { model }),
511
+ ...(toolDefinitions.length ? { toolDefinitions } : {}),
512
+ ...(progress === undefined ? {} : { progress }),
513
+ ...(attempt === undefined ? {} : { attempt }),
514
+ ...(result === undefined ? {} : { result }),
515
+ ...(failure === undefined ? {} : { failure }),
516
+ };
517
+ }
518
+ catch {
519
+ return undefined;
520
+ }
521
+ }
522
+ async function subagentCacheEntryChanged(directory, entry) {
523
+ for (const file of TRAJECTORY_SUBAGENT_FILES)
524
+ if (await fileMtime(join(directory, file)) !== entry.fileMtimes.get(file))
525
+ return true;
526
+ const transcriptPath = sessionFile(entry.value.attempt?.session);
527
+ return transcriptPath !== undefined && await fileMtime(transcriptPath) !== entry.transcriptMtime;
528
+ }
529
+ export function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, includeTranscript = true) {
530
+ const root = join(agentDir, "subagents");
531
+ const cache = new Map();
532
+ const negativeCache = new Map();
533
+ return async () => {
534
+ let entries;
535
+ try {
536
+ entries = await readdir(root, { withFileTypes: true });
537
+ }
538
+ catch (error) {
539
+ if (isNodeError(error, "ENOENT"))
540
+ return [];
541
+ throw error;
542
+ }
543
+ const ids = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
544
+ const current = new Set(ids);
545
+ for (const id of cache.keys())
546
+ if (!current.has(id))
547
+ cache.delete(id);
548
+ for (const id of negativeCache.keys())
549
+ if (!current.has(id))
550
+ negativeCache.delete(id);
551
+ const loaded = [];
552
+ for (const id of ids) {
553
+ const directory = join(root, id);
554
+ const statusPath = join(directory, "status.json");
555
+ try {
556
+ const statusMtime = await fileMtime(statusPath);
557
+ if (negativeCache.has(id) && negativeCache.get(id) === statusMtime)
558
+ continue;
559
+ negativeCache.delete(id);
560
+ let entry = cache.get(id);
561
+ if (!entry || await subagentCacheEntryChanged(directory, entry)) {
562
+ const value = await loadTrajectorySubagent(directory, cwd, sessionId, includeTranscript);
563
+ if (value === "foreign") {
564
+ cache.delete(id);
565
+ negativeCache.set(id, statusMtime);
566
+ continue;
567
+ }
568
+ if (!value) {
569
+ cache.delete(id);
570
+ continue;
571
+ }
572
+ const fileMtimes = new Map();
573
+ for (const file of TRAJECTORY_SUBAGENT_FILES)
574
+ fileMtimes.set(file, await fileMtime(join(directory, file)));
575
+ const transcriptPath = sessionFile(value.attempt?.session);
576
+ entry = { value, fileMtimes, transcriptMtime: transcriptPath === undefined ? undefined : await fileMtime(transcriptPath) };
577
+ cache.set(id, entry);
578
+ }
579
+ loaded.push(overlay ? overlay(entry.value) : entry.value);
580
+ }
581
+ catch {
582
+ cache.delete(id); /* Ignore corrupt or concurrently removed subagents. */
583
+ }
584
+ }
585
+ const attention = navigatorAttentionSortByState(loaded, (entry) => entry.state, () => undefined, TRAJECTORY_SUBAGENT_ATTENTION_ORDER);
586
+ return attention.sort((left, right) => {
587
+ if (left.state !== right.state)
588
+ return 0;
589
+ const leftTime = left.finishedAt ?? left.startedAt ?? -1;
590
+ const rightTime = right.finishedAt ?? right.startedAt ?? -1;
591
+ return rightTime - leftTime || left.id.localeCompare(right.id);
592
+ });
593
+ };
594
+ }