@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,376 @@
1
+ import type { AgentToolResult, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth } from "@earendil-works/pi-tui";
3
+ import { formatCost, formatStalledDuration, sanitizeDisplayText, WORKFLOW_AGENT_STALL_THRESHOLD_MS } from "../../src/index.js";
4
+ import type { SubagentRunRequest, SubagentStatus } from "./contracts.js";
5
+
6
+ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
7
+ const TERMINAL_STATES = new Set<SubagentStatus["state"]>(["completed", "failed", "stopped"]);
8
+ const MAX_INSPECT_VALUE_CHARS = 4000;
9
+ const MAX_INSPECT_TOOL_CALLS = 32;
10
+ const MAX_BACKGROUND_WIDGET_ROWS = 10;
11
+ type SubagentRenderArgs = Partial<SubagentRunRequest> & { id?: string };
12
+
13
+ type ProgressComponent = ReturnType<typeof subagentProgressBlock>;
14
+ export type SubagentRenderState = {
15
+ subagentSpinner?: ReturnType<typeof setInterval>;
16
+ subagentStatus?: SubagentStatus;
17
+ subagentProgressComponent?: ProgressComponent;
18
+ subagentProgressFrozenAt?: number;
19
+ };
20
+
21
+ function textBlock(text: string) {
22
+ return {
23
+ render(width: number): string[] { return text.split("\n").map((line) => truncateToWidth(line, Math.max(1, width), "…")); },
24
+ invalidate() {},
25
+ };
26
+ }
27
+
28
+ function roleName(value: unknown): string | undefined {
29
+ if (typeof value === "string") return value.trim() || undefined;
30
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
31
+ const name = (value as Record<string, unknown>).name;
32
+ return typeof name === "string" ? name.trim() || undefined : undefined;
33
+ }
34
+
35
+ function modeName(value: unknown): "background" | "foreground" | undefined {
36
+ return value === "background" || value === "foreground" ? value : undefined;
37
+ }
38
+
39
+ function requestMetadata(args: SubagentRenderArgs, metadataAvailable: boolean): string {
40
+ const mode = modeName(args.mode) ?? (metadataAvailable ? "background" : undefined);
41
+ const role = metadataAvailable ? roleName(args.role) ?? "none" : undefined;
42
+ return [mode === undefined ? undefined : `mode=${mode}`, role === undefined ? undefined : `role=${role}`].filter((value): value is string => value !== undefined).join(" ");
43
+ }
44
+
45
+ function label(args: SubagentRenderArgs): string {
46
+ if (typeof args.label === "string" && args.label.trim()) return args.label.trim();
47
+ const role = roleName(args.role);
48
+ if (role) return role;
49
+ if (typeof args.id === "string" && args.id) return args.id.slice(0, 8);
50
+ return "subagent";
51
+ }
52
+
53
+ export function formatSubagentPreview(args: Partial<SubagentRunRequest>): string {
54
+ return `subagent ${label(args)} ${requestMetadata(args, true)}`;
55
+ }
56
+
57
+ export function renderSubagentCall(args: Partial<SubagentRunRequest>, context?: { state?: SubagentRenderState; isError?: boolean }) {
58
+ const preview = formatSubagentPreview(args);
59
+ return {
60
+ render(width: number): string[] {
61
+ if (context?.state?.subagentStatus && !context.isError) return [];
62
+ return [truncateToWidth(preview, Math.max(1, width), "…")];
63
+ },
64
+ invalidate() {},
65
+ };
66
+ }
67
+
68
+ function statusValue(value: unknown): SubagentStatus | undefined {
69
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
70
+ const record = value as Record<string, unknown>;
71
+ if (typeof record.id !== "string" || (record.state !== "running" && record.state !== "completed" && record.state !== "failed" && record.state !== "stopped")) return undefined;
72
+ return value as SubagentStatus;
73
+ }
74
+
75
+ function runtime(startedAt: number | undefined, finishedAt: number | undefined, now: number): string {
76
+ if (startedAt === undefined) return "";
77
+ const seconds = Math.max(0, Math.floor(((finishedAt ?? now) - startedAt) / 1000));
78
+ if (seconds < 60) return `${String(seconds)}s`;
79
+ const minutes = Math.floor(seconds / 60);
80
+ const remainingSeconds = seconds % 60;
81
+ if (minutes < 60) return `${String(minutes)}m${remainingSeconds ? ` ${String(remainingSeconds)}s` : ""}`;
82
+ const hours = Math.floor(minutes / 60);
83
+ const remainingMinutes = minutes % 60;
84
+ return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
85
+ }
86
+
87
+ function stateGlyph(state: SubagentStatus["state"], spinner: string): string {
88
+ if (state === "running") return spinner;
89
+ return state === "completed" ? "✓" : "✗";
90
+ }
91
+
92
+ function stateColor(state: SubagentStatus["state"]): "accent" | "success" | "error" {
93
+ if (state === "running") return "accent";
94
+ return state === "completed" ? "success" : "error";
95
+ }
96
+
97
+ function activity(status: SubagentStatus): string | undefined {
98
+ if (status.state !== "running") return undefined;
99
+ const current = status.progress?.activity;
100
+ if (current?.kind === "reasoning" || current?.kind === "text") {
101
+ const name = current.kind === "reasoning" ? "reasoning" : "responding";
102
+ const text = sanitizeDisplayText(current.text);
103
+ return text && text !== name ? `${name} · ${text}` : name;
104
+ }
105
+ if (current?.kind === "tool") return sanitizeDisplayText(current.text);
106
+ return sanitizeDisplayText([...(status.progress?.toolCalls ?? [])].reverse().find(({ state }) => state === "running")?.name ?? "");
107
+ }
108
+
109
+ function stalledDuration(status: SubagentStatus, now: number): number | undefined {
110
+ const lastEventAt = status.progress?.lastEventAt;
111
+ if (status.state !== "running" || lastEventAt === undefined || !Number.isFinite(lastEventAt)) return undefined;
112
+ const duration = now - lastEventAt;
113
+ return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : undefined;
114
+ }
115
+
116
+ function accounting(status: SubagentStatus): string | undefined {
117
+ const value = status.progress?.accounting;
118
+ if (!value) return undefined;
119
+ const total = value.input + value.output + value.cacheRead + value.cacheWrite;
120
+ return `tokens=${String(total)} cost=${formatCost(value.cost) || "$0.00"}`;
121
+ }
122
+
123
+ function timestamp(value: number | undefined): string | undefined {
124
+ if (value === undefined || !Number.isFinite(value) || value < 0) return undefined;
125
+ const date = new Date(value);
126
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
127
+ }
128
+
129
+ function boundedValue(value: unknown): string {
130
+ let text: string;
131
+ try {
132
+ const serialized: unknown = JSON.stringify(value, null, 2);
133
+ text = typeof serialized === "string" ? serialized : String(value);
134
+ } catch {
135
+ text = String(value);
136
+ }
137
+ return text.length > MAX_INSPECT_VALUE_CHARS ? `${text.slice(0, MAX_INSPECT_VALUE_CHARS)}…` : text;
138
+ }
139
+
140
+ function formatSubagentProgress(status: SubagentStatus, args: SubagentRenderArgs, theme: Theme, spinner: string, now: number, expanded: boolean): string {
141
+ const color = stateColor(status.state);
142
+ const elapsed = runtime(status.startedAt, status.finishedAt, now);
143
+ const metadata = requestMetadata(args, args.id === undefined);
144
+ const lines = [
145
+ `${theme.fg(color, stateGlyph(status.state, spinner))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`,
146
+ ];
147
+ const current = activity(status);
148
+ const stalled = stalledDuration(status, now);
149
+ if (current) lines.push(` ${theme.fg("accent", spinner)} ${theme.fg("dim", current)}${stalled === undefined ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
150
+ else if (stalled !== undefined) lines.push(` ${theme.fg("warning", `stalled? ${formatStalledDuration(stalled)}`)}`);
151
+ if (status.error) lines.push(` ${theme.fg("error", `${status.error.code}: ${status.error.message}`)}`);
152
+ if (expanded) {
153
+ lines.push(` ${theme.fg("dim", `id=${status.id}`)}`);
154
+ const model = status.progress?.state?.model;
155
+ if (model) lines.push(` ${theme.fg("dim", `model=${model.provider}/${model.model}${model.thinking ? `:${model.thinking}` : ""}`)}`);
156
+ const usage = accounting(status);
157
+ if (usage) lines.push(` ${theme.fg("dim", usage)}`);
158
+ if (status.worktree) lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
159
+ }
160
+ return lines.join("\n");
161
+ }
162
+
163
+ function formatSubagentLaunch(status: SubagentStatus, args: SubagentRenderArgs, theme: Theme, expanded: boolean): string {
164
+ const metadata = requestMetadata(args, args.id === undefined);
165
+ const lines = [
166
+ `${theme.fg("success", "✓")} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg("success", "[launched]")}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}`,
167
+ ];
168
+ if (expanded) lines.push(` ${theme.fg("dim", `id=${status.id}`)}`);
169
+ return lines.join("\n");
170
+ }
171
+
172
+ function formatSubagentInspection(status: SubagentStatus, args: { id?: string }, details: unknown, theme: Theme, expanded: boolean): string {
173
+ const lines = [formatSubagentProgress(status, { id: args.id ?? status.id }, theme, "◇", Date.now(), false)];
174
+ if (!expanded) return lines[0] ?? "";
175
+ lines.push(` ${theme.fg("dim", `id=${status.id}`)}`);
176
+ const startedAt = timestamp(status.startedAt);
177
+ const finishedAt = timestamp(status.finishedAt);
178
+ const lastEventAt = timestamp(status.progress?.lastEventAt);
179
+ if (startedAt) lines.push(` ${theme.fg("dim", `startedAt=${startedAt}`)}`);
180
+ if (finishedAt) lines.push(` ${theme.fg("dim", `finishedAt=${finishedAt}`)}`);
181
+ if (lastEventAt) lines.push(` ${theme.fg("dim", `lastEventAt=${lastEventAt}`)}`);
182
+ const model = status.progress?.state?.model;
183
+ if (model) lines.push(` ${theme.fg("dim", `model=${model.provider}/${model.model}${model.thinking ? `:${model.thinking}` : ""}`)}`);
184
+ const currentActivity = status.progress?.activity;
185
+ if (currentActivity) lines.push(` ${theme.fg("dim", `activity=${currentActivity.kind}${currentActivity.text ? ` ${sanitizeDisplayText(currentActivity.text)}` : ""}`)}`);
186
+ const accounting = status.progress?.accounting;
187
+ if (accounting) lines.push(` ${theme.fg("dim", `accounting=input=${String(accounting.input)} output=${String(accounting.output)} cacheRead=${String(accounting.cacheRead)} cacheWrite=${String(accounting.cacheWrite)} cost=${formatCost(accounting.cost) || "$0.00"}`)}`);
188
+ if (status.worktree) lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
189
+ const toolCalls = status.progress?.toolCalls;
190
+ if (toolCalls?.length) {
191
+ lines.push(` ${theme.fg("dim", `toolCalls=${String(toolCalls.length)}`)}`);
192
+ for (const call of toolCalls.slice(-MAX_INSPECT_TOOL_CALLS)) lines.push(` ${theme.fg("dim", `${call.name} [${call.state}]`)}`);
193
+ }
194
+ const record = typeof details === "object" && details !== null && !Array.isArray(details) ? details as Record<string, unknown> : undefined;
195
+ if (record && Object.prototype.hasOwnProperty.call(record, "value")) {
196
+ lines.push(` ${theme.fg("dim", "value:")}`);
197
+ for (const line of boundedValue(record.value).split("\n")) lines.push(` ${line}`);
198
+ }
199
+ return lines.join("\n");
200
+ }
201
+
202
+ export function subagentProgressBlock(status: SubagentStatus, args: SubagentRenderArgs, theme: Theme, freezeAt?: number) {
203
+ let current = status;
204
+ let currentTheme = theme;
205
+ let expanded = false;
206
+ let frozenAt = freezeAt ?? Date.now();
207
+ return {
208
+ id: status.id,
209
+ update(next: SubagentStatus, nextTheme: Theme, nextFreezeAt?: number) {
210
+ current = next;
211
+ currentTheme = nextTheme;
212
+ if (nextFreezeAt !== undefined) frozenAt = nextFreezeAt;
213
+ },
214
+ setExpanded(value: boolean) { expanded = value; },
215
+ render(width: number): string[] {
216
+ const now = TERMINAL_STATES.has(current.state) ? frozenAt : Date.now();
217
+ if (!TERMINAL_STATES.has(current.state)) frozenAt = now;
218
+ const frame = SPINNER[Math.floor(now / 80) % SPINNER.length] ?? "◇";
219
+ return formatSubagentProgress(current, args, currentTheme, frame, now, expanded).split("\n").map((line) => truncateToWidth(line, Math.max(1, width), "…"));
220
+ },
221
+ invalidate() {},
222
+ };
223
+ }
224
+
225
+ function resultText(result: AgentToolResult<unknown>): string {
226
+ return result.content.filter(({ type }) => type === "text").map((content) => content.type === "text" ? content.text : "").join("\n");
227
+ }
228
+
229
+ export function renderSubagentResult(result: AgentToolResult<unknown>, options: { isPartial: boolean; expanded: boolean }, theme: Theme, context: { args: SubagentRenderArgs; state: SubagentRenderState; invalidate(): void; isError?: boolean }) {
230
+ const incoming = statusValue(result.details);
231
+ const state = context.state;
232
+ if (incoming) state.subagentStatus = state.subagentStatus?.id === incoming.id ? { ...state.subagentStatus, ...incoming } : incoming;
233
+ const status = state.subagentStatus;
234
+
235
+ if (status?.state === "running" && options.isPartial && !state.subagentSpinner) {
236
+ state.subagentSpinner = setInterval(() => { context.invalidate(); }, 80);
237
+ state.subagentSpinner.unref();
238
+ } else if ((!options.isPartial || status?.state !== "running") && state.subagentSpinner) {
239
+ clearInterval(state.subagentSpinner);
240
+ delete state.subagentSpinner;
241
+ }
242
+
243
+ if (!status || context.isError) return textBlock(resultText(result));
244
+ if (!options.isPartial && status.state === "running") return textBlock(formatSubagentLaunch(status, context.args, theme, options.expanded));
245
+ if (TERMINAL_STATES.has(status.state)) state.subagentProgressFrozenAt ??= Date.now();
246
+ else delete state.subagentProgressFrozenAt;
247
+ let component = state.subagentProgressComponent;
248
+ if (!component || component.id !== status.id) {
249
+ component = subagentProgressBlock(status, context.args, theme, state.subagentProgressFrozenAt);
250
+ state.subagentProgressComponent = component;
251
+ } else {
252
+ component.update(status, theme, state.subagentProgressFrozenAt);
253
+ }
254
+ component.setExpanded(options.expanded);
255
+ return component;
256
+ }
257
+
258
+ type WidgetRun = { status: Readonly<SubagentStatus>; request: Readonly<SubagentRunRequest> };
259
+
260
+ export function createSubagentBackgroundWidget() {
261
+ const key = "piewf-subagents-background";
262
+ const runs = new Map<string, WidgetRun>();
263
+ let context: ExtensionContext | undefined;
264
+ let requestRender: (() => void) | undefined;
265
+ let timer: ReturnType<typeof setInterval> | undefined;
266
+ let showing = false;
267
+
268
+ const hide = (): void => {
269
+ if (timer) clearInterval(timer);
270
+ timer = undefined;
271
+ requestRender = undefined;
272
+ if (showing) {
273
+ try { context?.ui.setWidget(key, undefined); } catch { /* The session UI may already be closing. */ }
274
+ }
275
+ showing = false;
276
+ };
277
+ const paint = (): void => {
278
+ if (context?.mode !== "tui" || runs.size === 0) {
279
+ hide();
280
+ return;
281
+ }
282
+ if (showing) {
283
+ requestRender?.();
284
+ return;
285
+ }
286
+ try {
287
+ context.ui.setWidget(key, (tui, theme) => {
288
+ requestRender = () => { tui.requestRender(); };
289
+ return {
290
+ render(width: number): string[] {
291
+ const now = Date.now();
292
+ const frame = SPINNER[Math.floor(now / 80) % SPINNER.length] ?? "◇";
293
+ const title = theme.bold(theme.fg("accent", `Subagents (${String(runs.size)} running)`));
294
+ const blocks = [...runs.values()].map(({ status, request }) => formatSubagentProgress(status, request, theme, frame, now, false).split("\n"));
295
+ const allRunsFit = blocks.reduce((rows, block) => rows + block.length, 1) <= MAX_BACKGROUND_WIDGET_ROWS;
296
+ const runRowBudget = allRunsFit ? MAX_BACKGROUND_WIDGET_ROWS - 1 : MAX_BACKGROUND_WIDGET_ROWS - 2;
297
+ const lines = [title];
298
+ let runRows = 0;
299
+ let displayedRuns = 0;
300
+ for (const block of blocks) {
301
+ if (runRows + block.length > runRowBudget) break;
302
+ lines.push(...block);
303
+ runRows += block.length;
304
+ displayedRuns += 1;
305
+ }
306
+ const hiddenRuns = blocks.length - displayedRuns;
307
+ if (hiddenRuns > 0) lines.push(`… ${String(hiddenRuns)} more`);
308
+ return lines.map((line) => truncateToWidth(line, Math.max(1, width), "…"));
309
+ },
310
+ invalidate() {},
311
+ };
312
+ }, { placement: "belowEditor" });
313
+ showing = true;
314
+ timer = setInterval(() => { requestRender?.(); }, 80);
315
+ timer.unref();
316
+ } catch {
317
+ hide();
318
+ }
319
+ };
320
+
321
+ return {
322
+ start(next: ExtensionContext): void {
323
+ hide();
324
+ runs.clear();
325
+ context = next.mode === "tui" ? next : undefined;
326
+ },
327
+ update(status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>): void {
328
+ if (request.mode !== "background" || context === undefined) return;
329
+ if (TERMINAL_STATES.has(status.state)) runs.delete(status.id);
330
+ else runs.set(status.id, { status, request });
331
+ paint();
332
+ },
333
+ dispose(): void {
334
+ hide();
335
+ runs.clear();
336
+ context = undefined;
337
+ },
338
+ };
339
+ }
340
+
341
+
342
+ export function renderSubagentInspectCall(args: { id?: string }, theme: Theme) {
343
+ const title = theme.fg("toolTitle", theme.bold("subagents_inspect"));
344
+ return textBlock(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", "all")}`);
345
+ }
346
+
347
+ export function renderSubagentInspectResult(result: AgentToolResult<unknown>, options: { expanded: boolean }, theme: Theme, args: { id?: string }) {
348
+ const status = statusValue(result.details);
349
+ if (status) return textBlock(formatSubagentInspection(status, args, result.details, theme, options.expanded));
350
+ if (Array.isArray(result.details)) {
351
+ const statuses = result.details.map(statusValue).filter((value): value is SubagentStatus => value !== undefined);
352
+ const lines = [theme.bold(theme.fg("accent", `Subagents (${String(statuses.length)})`)), ...statuses.map((entry) => {
353
+ const color = stateColor(entry.state);
354
+ return ` ${theme.fg(color, stateGlyph(entry.state, "◇"))} ${entry.id.slice(0, 8)} ${theme.fg(color, `[${entry.state}]`)}`;
355
+ })];
356
+ return textBlock(lines.join("\n"));
357
+ }
358
+ return textBlock(resultText(result));
359
+ }
360
+
361
+ export function renderSubagentControlCall(name: string, args: { id: string; message?: string }, theme: Theme) {
362
+ const title = theme.fg("toolTitle", theme.bold(name));
363
+ const message = args.message ? ` ${theme.fg("dim", args.message)}` : "";
364
+ return textBlock(`${title} ${theme.fg("accent", args.id)}${message}`);
365
+ }
366
+
367
+ export function renderSubagentControlResult(result: AgentToolResult<unknown>, theme: Theme) {
368
+ const status = statusValue(result.details);
369
+ if (status) {
370
+ const color = stateColor(status.state);
371
+ return textBlock(`${theme.fg(color, stateGlyph(status.state, "◇"))} ${status.id.slice(0, 8)} ${theme.fg(color, `[${status.state}]`)}`);
372
+ }
373
+ const record = typeof result.details === "object" && result.details !== null && !Array.isArray(result.details) ? result.details as Record<string, unknown> : undefined;
374
+ if (record && typeof record.id === "string") return textBlock(`${theme.fg("success", "✓")} ${record.id.slice(0, 8)}`);
375
+ return textBlock(resultText(result));
376
+ }
@@ -0,0 +1,99 @@
1
+ import { join } from "node:path";
2
+ import { LAUNCH_SNAPSHOT_IDENTITY_VERSION, RunStore, type LaunchSnapshot, type PersistedRun, type WorktreeReference } from "../../src/index.js";
3
+
4
+ export interface SubagentWorktreeContext {
5
+ readonly cwd: string;
6
+ readonly sessionId: string;
7
+ readonly runId: string;
8
+ readonly name: string;
9
+ readonly owner: string;
10
+ readonly worktreePostCreateCommand?: readonly string[];
11
+ readonly worktreePostCreateCommandSource?: string;
12
+ }
13
+
14
+ export interface SubagentWorktreeRunStore {
15
+ recordSystemPrompt(entry: { sessionId: string; attempt: number; turn: number; prompt: string }): Promise<void>;
16
+ validateWorktree(owner: string, cwd?: string): Promise<WorktreeReference>;
17
+ worktree(owner: string): Promise<WorktreeReference>;
18
+ snapshotWorktree(owner: string): Promise<string>;
19
+ }
20
+
21
+ export interface SubagentWorktreeHandle {
22
+ readonly path: string;
23
+ readonly branch: string;
24
+ readonly cwd: string;
25
+ readonly runStore: SubagentWorktreeRunStore;
26
+ cleanup(): Promise<void>;
27
+ }
28
+
29
+ export interface SubagentWorktreeAdapter {
30
+ create(context: Readonly<SubagentWorktreeContext>): Promise<SubagentWorktreeHandle>;
31
+ cleanup?(context: Readonly<SubagentWorktreeContext>): Promise<void>;
32
+ }
33
+
34
+ function syntheticRun(context: Readonly<SubagentWorktreeContext>): PersistedRun {
35
+ return {
36
+ id: context.runId,
37
+ workflowName: "subagents",
38
+ cwd: context.cwd,
39
+ sessionId: context.sessionId,
40
+ state: "running",
41
+ agentSessions: [],
42
+ agents: [],
43
+ };
44
+ }
45
+
46
+ function syntheticSnapshot(context: Readonly<SubagentWorktreeContext>): LaunchSnapshot {
47
+ return {
48
+ identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
49
+ launchMode: "background",
50
+ script: "return null;",
51
+ args: null,
52
+ metadata: { name: "subagents" },
53
+ settings: { concurrency: 1, ...(context.worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand: context.worktreePostCreateCommand }) },
54
+ ...(context.worktreePostCreateCommandSource === undefined ? {} : { settingsSources: { concurrency: "subagents", modelAliases: "subagents", worktreePostCreateCommand: context.worktreePostCreateCommandSource } }),
55
+ models: [],
56
+ tools: [],
57
+ agentTypes: [],
58
+ schemas: [],
59
+ };
60
+ }
61
+ export function createRunStoreWorktreeAdapter(home: string): SubagentWorktreeAdapter {
62
+ let createQueue: Promise<void> = Promise.resolve();
63
+ return {
64
+ create(context) {
65
+ const operation = createQueue.then(async () => {
66
+ const store = new RunStore(context.cwd, context.sessionId, context.runId, home);
67
+ await store.create(syntheticRun(context), syntheticSnapshot(context));
68
+ try {
69
+ const reference = await store.worktree(context.owner);
70
+ let cleaned = false;
71
+ return {
72
+ path: reference.path,
73
+ branch: reference.branch,
74
+ cwd: reference.cwd,
75
+ runStore: store,
76
+ async cleanup() {
77
+ if (cleaned) return;
78
+ cleaned = true;
79
+ await store.delete(true);
80
+ },
81
+ };
82
+ } catch (error) {
83
+ await store.delete(true).catch(() => undefined);
84
+ throw error;
85
+ }
86
+ });
87
+ // Git worktree metadata is shared by all RunStore instances for this adapter.
88
+ createQueue = operation.then(() => undefined, () => undefined);
89
+ return operation;
90
+ },
91
+ async cleanup(context) {
92
+ await new RunStore(context.cwd, context.sessionId, context.runId, home).delete(true);
93
+ },
94
+ };
95
+ }
96
+
97
+ export function defaultWorktreeHome(storageDirectory: string): string {
98
+ return join(storageDirectory, ".worktrees");
99
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Public package entry point for the bundled Trajectory extension.
3
+ */
4
+ export * from "./src/index.js";
5
+ export { default } from "./src/index.js";