@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,378 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { deepFreeze, errorCode, errorText, fail, isWorkflowErrorCode, jsonValue, object } from "./utils.js";
3
+ import { validateAgentOptions, validateShellOptions, workflowPrompt } from "./validation.js";
4
+ import { HARD_TERMINAL_RUN_STATES, WORKFLOW_AGENT_STATE_CHANGED_EVENT, WORKFLOW_BUDGET_EVENT, WORKFLOW_CHECKPOINT_STATE_CHANGED_EVENT, WORKFLOW_PHASE_CHANGED_EVENT, WORKFLOW_RUN_COMPLETED_EVENT, WORKFLOW_RUN_FAILED_EVENT, WORKFLOW_RUN_RESUMED_EVENT, WORKFLOW_RUN_STARTED_EVENT, WORKFLOW_RUN_STATE_CHANGED_EVENT, WORKFLOW_WORKTREE_CREATED_EVENT, WorkflowError } from "./types.js";
5
+ import { structuralPath as operationPath } from "./persistence.js";
6
+ const inheritedHostAgentPath = new AsyncLocalStorage();
7
+ const inheritedHostWorktreeOwner = new AsyncLocalStorage();
8
+ /** Drops the transient shell-activity counters a run only holds while its host process is alive. */
9
+ export function withoutActiveShells(run) {
10
+ const next = { ...run };
11
+ delete next.activeShells;
12
+ delete next.activeShellStartedAt;
13
+ delete next.activeShellsByPhase;
14
+ return next;
15
+ }
16
+ /** Reads the project cwd and Pi session ID that an extension context exposes, when present. */
17
+ export function hostSessionContext(context) {
18
+ const host = object(context) ? context : {};
19
+ const cwd = typeof host.cwd === "string" ? host.cwd : undefined;
20
+ const sessionManager = object(host.sessionManager) ? host.sessionManager : undefined;
21
+ const sessionId = typeof sessionManager?.getSessionId === "function" ? String(Reflect.apply(sessionManager.getSessionId, sessionManager, [])) : undefined;
22
+ return { ...(cwd === undefined ? {} : { cwd }), ...(sessionId === undefined ? {} : { sessionId }) };
23
+ }
24
+ export class RunLifecycle {
25
+ changed;
26
+ #state;
27
+ #active = 0;
28
+ #waiters = [];
29
+ constructor(state = "running", changed) {
30
+ this.changed = changed;
31
+ this.#state = state;
32
+ }
33
+ get state() { return this.#state; }
34
+ async enter() {
35
+ while (this.#state === "pausing" || this.#state === "paused" || this.#state === "awaiting_input")
36
+ await new Promise((resolve) => { this.#waiters.push(resolve); });
37
+ if (this.#state !== "running")
38
+ throw new WorkflowError("CANCELLED", `Run is ${this.#state}`);
39
+ this.#active += 1;
40
+ }
41
+ async leave() {
42
+ if (this.#active > 0)
43
+ this.#active -= 1;
44
+ if (this.#state === "pausing" && this.#active === 0)
45
+ await this.#set("paused", "pause");
46
+ }
47
+ async enterAwaitingInput() {
48
+ while (this.#state === "pausing" || this.#state === "paused")
49
+ await new Promise((resolve) => { this.#waiters.push(resolve); });
50
+ if (this.#state === "awaiting_input")
51
+ return;
52
+ if (this.#state !== "running")
53
+ throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot await input for ${this.#state} run`);
54
+ await this.#set("awaiting_input", "awaiting_input");
55
+ }
56
+ async resolveAwaitingInput() {
57
+ if (this.#state !== "awaiting_input")
58
+ return;
59
+ await this.#set("running", "checkpoint_resolved");
60
+ for (const resolve of this.#waiters.splice(0))
61
+ resolve();
62
+ }
63
+ async pause() {
64
+ if (this.#state !== "running")
65
+ throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot pause ${this.#state} run`);
66
+ await this.#set("pausing", "pause");
67
+ if (this.#active === 0 && this.state === "pausing")
68
+ await this.#set("paused", "pause");
69
+ }
70
+ /** Resumes a paused, interrupted, or budget-exhausted run; on a `pausing` run it cancels the pending pause instead. */
71
+ async resume() {
72
+ if (this.#state !== "pausing" && this.#state !== "paused" && this.#state !== "interrupted" && this.#state !== "budget_exhausted")
73
+ throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot resume ${this.#state} run`);
74
+ await this.#set("running", "resume");
75
+ for (const resolve of this.#waiters.splice(0))
76
+ resolve();
77
+ }
78
+ async providerPause() {
79
+ await this.leave();
80
+ if (this.#state === "running")
81
+ await this.pause();
82
+ await this.enter();
83
+ }
84
+ async terminal(state, reason) {
85
+ if (HARD_TERMINAL_RUN_STATES.has(this.#state))
86
+ throw new WorkflowError("RESUME_INCOMPATIBLE", `${this.#state} runs are terminal`);
87
+ await this.#set(state, reason ?? state);
88
+ for (const resolve of this.#waiters.splice(0))
89
+ resolve();
90
+ }
91
+ async #set(state, reason) {
92
+ const previousState = this.#state;
93
+ this.#state = state;
94
+ await this.changed?.(state, previousState, reason);
95
+ }
96
+ }
97
+ function safeEventError(error) {
98
+ const code = errorCode(error) ?? "INTERNAL_ERROR";
99
+ return { code, message: `Workflow execution failed (${code})` };
100
+ }
101
+ export class WorkflowEventPublisher {
102
+ sink;
103
+ #queues = new Map();
104
+ #budgetEvents = new Map();
105
+ #worktrees = new Map();
106
+ constructor(sink) {
107
+ this.sink = sink;
108
+ }
109
+ removeRun(runId) {
110
+ this.#queues.delete(runId);
111
+ this.#budgetEvents.delete(runId);
112
+ this.#worktrees.delete(runId);
113
+ }
114
+ seedBudget(runId, events) {
115
+ const seen = this.#budgetEvents.get(runId) ?? new Set();
116
+ for (const event of events ?? [])
117
+ seen.add(this.budgetKey(event));
118
+ this.#budgetEvents.set(runId, seen);
119
+ }
120
+ async runStarted(store, metadata) { await this.#publish(store, metadata, WORKFLOW_RUN_STARTED_EVENT, {}); }
121
+ async runResumed(store, metadata) { await this.#publish(store, metadata, WORKFLOW_RUN_RESUMED_EVENT, {}); }
122
+ async runState(store, metadata, previousState, state, reason) {
123
+ await this.#publish(store, metadata, WORKFLOW_RUN_STATE_CHANGED_EVENT, { previousState, state, ...(reason ? { reason } : {}), ...(isWorkflowErrorCode(reason) ? { errorCode: reason } : {}) });
124
+ if ((previousState === "paused" || previousState === "interrupted" || previousState === "budget_exhausted") && state === "running")
125
+ await this.runResumed(store, metadata);
126
+ }
127
+ async runCompleted(store, metadata, resultPath) { await this.#publish(store, metadata, WORKFLOW_RUN_COMPLETED_EVENT, { resultPath }); }
128
+ async runFailed(store, metadata, error, state) {
129
+ if (state === "failed")
130
+ await this.#publish(store, metadata, WORKFLOW_RUN_FAILED_EVENT, { error: safeEventError(error) });
131
+ }
132
+ async agentState(store, metadata, previous, agent) {
133
+ await this.#publish(store, metadata, WORKFLOW_AGENT_STATE_CHANGED_EVENT, { agentId: agent.id, displayLabel: agent.label ?? agent.name, ...(agent.role ? { role: agent.role } : {}), structuralPath: [...(agent.structuralPath ?? [])], ...(agent.parentId ? { parentId: agent.parentId } : {}), ...(agent.parentBreadcrumb ? { parentBreadcrumb: agent.parentBreadcrumb } : {}), ...(agent.worktreeOwner ? { worktreeOwner: agent.worktreeOwner } : {}), ...(previous ? { previousState: previous.state } : {}), state: agent.state, attempt: agent.attempts });
134
+ }
135
+ async agentStates(store, metadata, previous, current) {
136
+ const previousById = new Map(previous.map((agent) => [agent.id, agent]));
137
+ for (const agent of current) {
138
+ const old = previousById.get(agent.id);
139
+ if (!old || old.state !== agent.state || old.attempts !== agent.attempts)
140
+ await this.agentState(store, metadata, old, agent);
141
+ }
142
+ }
143
+ async phase(store, metadata, previousPhase, phase) {
144
+ if (previousPhase !== phase)
145
+ await this.#publish(store, metadata, WORKFLOW_PHASE_CHANGED_EVENT, { ...(previousPhase !== undefined ? { previousPhase } : {}), phase });
146
+ }
147
+ async checkpoint(store, metadata, name, state) { await this.#publish(store, metadata, WORKFLOW_CHECKPOINT_STATE_CHANGED_EVENT, { name, state }); }
148
+ async budget(store, metadata, run) {
149
+ const seen = this.#budgetEvents.get(store.runId) ?? new Set();
150
+ this.#budgetEvents.set(store.runId, seen);
151
+ for (const event of run.budgetEvents ?? []) {
152
+ const key = this.budgetKey(event);
153
+ if (seen.has(key))
154
+ continue;
155
+ seen.add(key);
156
+ await this.#publish(store, metadata, WORKFLOW_BUDGET_EVENT, { ...event, timestamp: event.at });
157
+ }
158
+ }
159
+ async worktree(store, metadata, worktree) {
160
+ const seen = this.#worktrees.get(store.runId) ?? new Set();
161
+ this.#worktrees.set(store.runId, seen);
162
+ if (seen.has(worktree.owner))
163
+ return;
164
+ seen.add(worktree.owner);
165
+ await this.#publish(store, metadata, WORKFLOW_WORKTREE_CREATED_EVENT, { owner: worktree.owner, branch: worktree.branch, path: worktree.path, base: worktree.base });
166
+ }
167
+ async #publish(store, metadata, name, payload) {
168
+ const base = { runId: store.runId, sessionId: store.sessionId, workflowName: metadata.name, cwd: store.cwd, runDirectory: store.directory, timestamp: Date.now() };
169
+ const previous = this.#queues.get(store.runId) ?? Promise.resolve();
170
+ const next = previous.then(() => {
171
+ try {
172
+ void Promise.resolve(this.sink?.emit(name, { ...base, ...payload })).catch(() => undefined);
173
+ }
174
+ catch { /* Best effort: listeners cannot affect a run. */ }
175
+ });
176
+ this.#queues.set(store.runId, next.catch(() => undefined));
177
+ await next;
178
+ }
179
+ budgetKey(event) { return `${String(event.budgetVersion)}:${event.type}:${event.proposalId ?? ""}`; }
180
+ }
181
+ function namedRecord(value, kind) {
182
+ if (!object(value))
183
+ fail("INVALID_METADATA", `${kind} must be a record`);
184
+ return Object.entries(value);
185
+ }
186
+ function publicWorktreeReference(reference) {
187
+ if (!object(reference) || typeof reference.path !== "string" || typeof reference.branch !== "string")
188
+ fail("WORKTREE_FAILED", "Worktree reference is invalid");
189
+ return Object.freeze({ path: reference.path, branch: reference.branch });
190
+ }
191
+ async function hostWithWorktree(name, callback, resolveWorktree, signal) {
192
+ if (typeof name !== "string" || !name.trim())
193
+ fail("INVALID_METADATA", "withWorktree name must be a non-empty string");
194
+ if (typeof callback !== "function")
195
+ fail("INVALID_METADATA", "withWorktree callback must be a function");
196
+ if (!resolveWorktree)
197
+ fail("WORKTREE_FAILED", "No worktree bridge is available");
198
+ const owner = operationPath("worktree", "named", name.trim());
199
+ const reference = publicWorktreeReference(await resolveWorktree(owner, signal));
200
+ return inheritedHostWorktreeOwner.run(owner, () => callback(reference));
201
+ }
202
+ export function workflowRunContext(cwd, sessionId, runId, workflow, args, signal) {
203
+ return Object.freeze({ cwd, sessionId, runId, workflow: deepFreeze(structuredClone(workflow)), args: deepFreeze(structuredClone(args)), signal });
204
+ }
205
+ function keyedJsonResult(entries) {
206
+ return Object.fromEntries(entries);
207
+ }
208
+ async function hostParallel(rawOperation, rawTasks) {
209
+ if (typeof rawOperation !== "string" || !rawOperation.trim())
210
+ fail("INVALID_METADATA", "parallel requires a stable explicit name");
211
+ const tasks = namedRecord(rawTasks, "parallel tasks");
212
+ for (const [name, run] of tasks) {
213
+ if (!name.trim())
214
+ fail("INVALID_METADATA", "parallel task requires a stable explicit name");
215
+ if (typeof run !== "function")
216
+ fail("INVALID_METADATA", "parallel task values must be run functions");
217
+ }
218
+ const results = await Promise.all(tasks.map(async ([name, run]) => {
219
+ try {
220
+ const parent = inheritedHostAgentPath.getStore() ?? [];
221
+ const value = await inheritedHostAgentPath.run([...parent, rawOperation, name], run);
222
+ if (!jsonValue(value))
223
+ fail("RESULT_INVALID", "parallel task result must be JSON-compatible");
224
+ return { name, value };
225
+ }
226
+ catch (error) {
227
+ const typed = error instanceof WorkflowError ? error : new WorkflowError("INTERNAL_ERROR", errorText(error));
228
+ if (typed.code === "CANCELLED")
229
+ throw typed;
230
+ return { name, error: typed };
231
+ }
232
+ }));
233
+ const failure = results.find((result) => result.error);
234
+ if (failure?.error)
235
+ throw failure.error;
236
+ return keyedJsonResult(results.flatMap((result) => "value" in result ? [[result.name, result.value]] : []));
237
+ }
238
+ async function hostPipeline(rawOperation, rawItems, rawStages) {
239
+ if (typeof rawOperation !== "string" || !rawOperation.trim())
240
+ fail("INVALID_METADATA", "pipeline requires a stable explicit name");
241
+ const items = namedRecord(rawItems, "pipeline items");
242
+ const stages = namedRecord(rawStages, "pipeline stages");
243
+ if (!stages.length)
244
+ fail("INVALID_METADATA", "pipeline requires at least one stage");
245
+ for (const [name] of items)
246
+ if (!name.trim())
247
+ fail("INVALID_METADATA", "pipeline item requires a stable explicit name");
248
+ for (const [stageName, run] of stages) {
249
+ if (!stageName.trim())
250
+ fail("INVALID_METADATA", "pipeline stage requires a stable explicit name");
251
+ if (typeof run !== "function")
252
+ fail("INVALID_METADATA", "pipeline stage values must be run functions");
253
+ }
254
+ const results = await Promise.all(items.map(async ([name, initial]) => {
255
+ let current = initial;
256
+ try {
257
+ for (const [stageName, run] of stages) {
258
+ const parent = inheritedHostAgentPath.getStore() ?? [];
259
+ const value = await inheritedHostAgentPath.run([...parent, rawOperation, name, stageName], () => run(current));
260
+ if (!jsonValue(value))
261
+ fail("RESULT_INVALID", "pipeline stage result must be JSON-compatible");
262
+ current = value;
263
+ }
264
+ if (!jsonValue(current))
265
+ fail("RESULT_INVALID", "pipeline result must be JSON-compatible");
266
+ return { name, value: current };
267
+ }
268
+ catch (error) {
269
+ const typed = error instanceof WorkflowError ? error : new WorkflowError("INTERNAL_ERROR", errorText(error));
270
+ if (typed.code === "CANCELLED")
271
+ throw typed;
272
+ return { name, error: typed };
273
+ }
274
+ }));
275
+ const failure = results.find((result) => result.error);
276
+ if (failure?.error)
277
+ throw failure.error;
278
+ return Object.fromEntries(results.flatMap((result) => "value" in result ? [[result.name, result.value]] : []));
279
+ }
280
+ export function nextNamedOccurrence(counters, label) {
281
+ const count = (counters.get(label) ?? 0) + 1;
282
+ counters.set(label, count);
283
+ return count === 1 ? label : `${label}#${String(count)}`;
284
+ }
285
+ function functionBreadcrumb(name, occurrence) { return occurrence === 1 ? name : `${name} #${String(occurrence)}`; }
286
+ export function withWorkflowFunctions(bridge, store, runContext, registry, settings = Object.freeze({})) {
287
+ const functionAgentOccurrences = new Map();
288
+ const functionShellOccurrences = new Map();
289
+ const functionInvokeOccurrences = new Map();
290
+ const functionInvokeBreadcrumbOccurrences = new Map();
291
+ const invokeFunction = async (name, input, signal, identity, breadcrumb) => {
292
+ const path = identity.path;
293
+ const structuralPath = identity.structuralPath;
294
+ const worktreeOwner = identity.worktreeOwner;
295
+ const replayed = await store.replay(path);
296
+ let stored;
297
+ const sideEffects = [];
298
+ const ownSideEffect = (effect) => {
299
+ let sideEffect;
300
+ try {
301
+ sideEffect = Promise.resolve(effect());
302
+ }
303
+ catch (error) {
304
+ sideEffect = Promise.resolve().then(() => { throw error; });
305
+ }
306
+ sideEffects.push(sideEffect);
307
+ // Own rejection immediately; Promise.all below still propagates it after the function completes.
308
+ void sideEffect.catch(() => undefined);
309
+ };
310
+ const parentBreadcrumb = breadcrumb ?? functionBreadcrumb(name, identity.occurrence);
311
+ const context = {
312
+ settings: deepFreeze(structuredClone(settings)),
313
+ run: runContext,
314
+ invoke: async (targetName, targetInput, label) => {
315
+ if (label !== undefined && (typeof label !== "string" || !label.trim()))
316
+ fail("INVALID_METADATA", "invoke label must be a non-empty string");
317
+ const inherited = inheritedHostAgentPath.getStore() ?? structuralPath;
318
+ const scopedWorktreeOwner = inheritedHostWorktreeOwner.getStore() ?? worktreeOwner;
319
+ const key = JSON.stringify([path, inherited, targetName]);
320
+ const occurrence = (functionInvokeOccurrences.get(key) ?? 0) + 1;
321
+ functionInvokeOccurrences.set(key, occurrence);
322
+ const breadcrumbName = label === undefined ? targetName : label;
323
+ const breadcrumbKey = JSON.stringify([path, inherited, breadcrumbName]);
324
+ const breadcrumbOccurrence = (functionInvokeBreadcrumbOccurrences.get(breadcrumbKey) ?? 0) + 1;
325
+ functionInvokeBreadcrumbOccurrences.set(breadcrumbKey, breadcrumbOccurrence);
326
+ const nestedPath = operationPath("function", "nested", path, ...inherited, targetName, `occurrence:${String(occurrence)}`);
327
+ const nestedIdentity = { path: nestedPath, structuralPath: [...inherited], occurrence, ...(scopedWorktreeOwner ? { worktreeOwner: scopedWorktreeOwner } : {}) };
328
+ return invokeFunction(targetName, targetInput, signal, nestedIdentity, `${parentBreadcrumb} > ${functionBreadcrumb(breadcrumbName, breadcrumbOccurrence)}`);
329
+ },
330
+ agent: async (prompt, options) => {
331
+ if (!bridge.agent || typeof prompt !== "string")
332
+ fail("AGENT_FAILED", "No agent bridge is available");
333
+ const validatedOptions = validateAgentOptions(options === undefined ? {} : options);
334
+ const scopedWorktreeOwner = inheritedHostWorktreeOwner.getStore() ?? worktreeOwner;
335
+ const inherited = inheritedHostAgentPath.getStore() ?? [];
336
+ const key = `${path}\0${JSON.stringify(inherited)}`;
337
+ const occurrence = (functionAgentOccurrences.get(key) ?? 0) + 1;
338
+ functionAgentOccurrences.set(key, occurrence);
339
+ return bridge.agent(prompt, validatedOptions, signal, { structuralPath: [...inherited], callSite: `function:${path}`, occurrence, parentBreadcrumb, ...(scopedWorktreeOwner ? { worktreeOwner: scopedWorktreeOwner } : {}) });
340
+ },
341
+ shell: async (...args) => {
342
+ if (!bridge.shell)
343
+ fail("SHELL_FAILED", "No shell bridge is available");
344
+ if (typeof args[0] !== "string")
345
+ fail("INVALID_METADATA", "shell command must be a string");
346
+ const options = validateShellOptions(args[1] === undefined ? {} : args[1]);
347
+ const scopedWorktreeOwner = inheritedHostWorktreeOwner.getStore() ?? worktreeOwner;
348
+ const inherited = inheritedHostAgentPath.getStore() ?? [];
349
+ const key = `${path}\0${JSON.stringify([inherited, scopedWorktreeOwner ?? null])}`;
350
+ const occurrence = (functionShellOccurrences.get(key) ?? 0) + 1;
351
+ functionShellOccurrences.set(key, occurrence);
352
+ return bridge.shell(args[0], options, signal, { structuralPath: [...inherited], callSite: `function:${path}`, occurrence, ...(scopedWorktreeOwner ? { worktreeOwner: scopedWorktreeOwner } : {}) });
353
+ },
354
+ prompt: workflowPrompt,
355
+ parallel: (operationName, tasks) => hostParallel(operationName, tasks),
356
+ pipeline: (operationName, items, stages) => hostPipeline(operationName, items, stages),
357
+ withWorktree: (name, callback) => hostWithWorktree(name, callback, bridge.worktree, signal),
358
+ checkpoint: async (...args) => {
359
+ if (!bridge.checkpoint || !object(args[0]) || !jsonValue(args[0]))
360
+ fail("INTERNAL_ERROR", "No checkpoint bridge is available");
361
+ return bridge.checkpoint(args[0], signal);
362
+ },
363
+ phase: (name) => { ownSideEffect(() => bridge.phase?.(name)); },
364
+ log: (message) => { ownSideEffect(() => bridge.log?.(message)); },
365
+ };
366
+ const result = await inheritedHostAgentPath.run([...structuralPath], async () => registry.invokeFunction(name, input, context, path, { get: () => replayed?.value, put: (_path, value) => { stored = value; } }));
367
+ await Promise.all(sideEffects);
368
+ if (!replayed)
369
+ await store.complete(path, stored ?? result);
370
+ return result;
371
+ };
372
+ return { ...bridge, functions: registry.globals(), function: (name, input, signal, identity) => {
373
+ const expectedPath = operationPath("function", ...identity.structuralPath, name, String(identity.occurrence));
374
+ if (identity.path !== expectedPath)
375
+ fail("INTERNAL_ERROR", "Workflow function identity path is inconsistent");
376
+ return invokeFunction(name, input, signal, identity);
377
+ } };
378
+ }
@@ -0,0 +1,116 @@
1
+ import { type Theme } from "@earendil-works/pi-coding-agent";
2
+ import { type AwaitingCheckpoint, type PersistedRun, type RunStore, type WorktreeReference } from "./persistence.js";
3
+ import { type AgentAccounting, type AgentAttemptAction, type AgentAttemptActionContext, type AgentRecord, type LaunchSnapshot, type StandaloneAgentAttemptActionContext } from "./types.js";
4
+ import { type WorkflowPhaseSelection } from "./host-phases.js";
5
+ export { SETTLED_AGENT_STATES } from "./types.js";
6
+ export interface WorkflowProgressStyles {
7
+ accent(text: string): string;
8
+ success(text: string): string;
9
+ error(text: string): string;
10
+ warning(text: string): string;
11
+ muted(text: string): string;
12
+ dim(text: string): string;
13
+ bold(text: string): string;
14
+ }
15
+ export declare function formatWorkflowProgress(run: PersistedRun, spinner?: string, styles?: WorkflowProgressStyles, now?: number, expanded?: boolean, width?: number, showHint?: boolean): string;
16
+ export declare function textBlock(text: string): {
17
+ render(width: number): string[];
18
+ invalidate(): void;
19
+ };
20
+ export declare function styledTextBlock(text: string): {
21
+ render(width: number): string[];
22
+ invalidate(): void;
23
+ };
24
+ export declare function workflowCatalogBlock(text: string, expanded: boolean): {
25
+ render(width: number): string[];
26
+ invalidate(): void;
27
+ };
28
+ export declare function workflowControlCall(name: string, args: Record<string, unknown>, theme: Theme): string;
29
+ export declare function workflowControlResult(name: string, args: Record<string, unknown>, result: CatalogToolResult, expanded: boolean, theme: Theme, isError: boolean): string;
30
+ type CatalogToolResult = {
31
+ details?: unknown;
32
+ content?: readonly {
33
+ type: string;
34
+ text?: string;
35
+ }[];
36
+ };
37
+ export declare function catalogResultValue(result: CatalogToolResult): unknown;
38
+ export declare function formatWorkflowCatalog(value: unknown, expanded: boolean, theme: Theme): string;
39
+ export declare function truncateWorkflowProgress(text: string, width: number): string[];
40
+ export declare function themeWorkflowProgressStyles(theme: Theme): WorkflowProgressStyles;
41
+ export type WorkflowProgressRefreshState = {
42
+ runId: string;
43
+ inputRun: PersistedRun;
44
+ run: PersistedRun;
45
+ lastRefreshAt: number;
46
+ runtimeStartedAt: number;
47
+ runtimeBaseMs: number;
48
+ refresh?: Promise<void>;
49
+ };
50
+ export type WorkflowProgressRenderState = {
51
+ workflowSpinner?: ReturnType<typeof setInterval>;
52
+ workflowProgress?: WorkflowProgressRefreshState;
53
+ workflowProgressComponent?: ReturnType<typeof workflowProgressBlock>;
54
+ workflowProgressFrozenAt?: number;
55
+ };
56
+ export declare function workflowProgressBlock(run: PersistedRun, theme: Theme, progress?: WorkflowProgressRefreshState, refresh?: () => Promise<PersistedRun | undefined>, invalidate?: () => void, prefix?: string, freezeAt?: number): {
57
+ render(width: number): string[];
58
+ setExpanded(value: boolean): void;
59
+ invalidate(): void;
60
+ };
61
+ export declare function formatBudgetStatus(run: Pick<PersistedRun, "budget" | "budgetVersion" | "usage" | "budgetEvents">): string[];
62
+ export declare function navigatorAttentionSortByState<T>(entries: readonly T[], stateOf: (entry: T) => string, resolvedAtOf: (entry: T) => number | undefined, stateOrder?: Readonly<Record<string, number>>): T[];
63
+ export declare function navigatorAttentionSort<T extends {
64
+ loaded: {
65
+ run: PersistedRun;
66
+ };
67
+ resolvedAt?: number | undefined;
68
+ }>(entries: readonly T[]): T[];
69
+ export declare function navigatorRunLabels(entries: readonly {
70
+ store: RunStore;
71
+ loaded: {
72
+ run: PersistedRun;
73
+ };
74
+ }[]): string[];
75
+ export declare function agentBreadcrumbParts(agent: AgentRecord, byId: Map<string, AgentRecord>, includeStructuralPath?: boolean): string[];
76
+ export declare function agentBreadcrumb(agent: AgentRecord, byId: Map<string, AgentRecord>, includeStructuralPath?: boolean): string;
77
+ export declare function formatStalledDuration(durationMs: number): string;
78
+ export interface AgentDetailPresentation {
79
+ readonly state: string;
80
+ readonly activity?: AgentRecord["activity"];
81
+ readonly lastEventAt?: number;
82
+ readonly structuralPath?: readonly string[];
83
+ readonly model?: AgentRecord["model"];
84
+ readonly role?: string;
85
+ readonly tools?: readonly string[];
86
+ readonly attempts?: number;
87
+ readonly startedAt?: number;
88
+ readonly finishedAt?: number;
89
+ readonly durationMs?: number;
90
+ readonly accounting?: AgentAccounting;
91
+ readonly error?: {
92
+ readonly code: string;
93
+ readonly message: string;
94
+ };
95
+ }
96
+ export declare function formatAgentDetail(agent: Readonly<AgentDetailPresentation>, styles?: WorkflowProgressStyles, now?: number, options?: Readonly<{
97
+ includeError?: boolean;
98
+ }>): string[];
99
+ export interface AgentActionPresentation {
100
+ readonly extensionLabels: readonly string[];
101
+ readonly hasWorktree: boolean;
102
+ readonly openPrompt: boolean;
103
+ readonly openSystemPrompt: boolean;
104
+ readonly openResult: boolean;
105
+ readonly standaloneState?: "running" | "completed" | "failed" | "stopped";
106
+ }
107
+ export declare function agentActionLabels(options: Readonly<AgentActionPresentation>): string[];
108
+ export declare function visibleAgentAttemptActions(actions: Readonly<Record<string, AgentAttemptAction>>, context: Readonly<AgentAttemptActionContext>): readonly [string, AgentAttemptAction][];
109
+ export declare function visibleStandaloneAgentAttemptActions(actions: Readonly<Record<string, AgentAttemptAction>>, context: Readonly<StandaloneAgentAttemptActionContext>): readonly [string, AgentAttemptAction][];
110
+ export declare function formatNavigatorDashboard(run: PersistedRun, checkpoints: readonly AwaitingCheckpoint[], worktrees: readonly WorktreeReference[], now?: number): string;
111
+ export declare function formatNavigatorRun(loaded: {
112
+ run: PersistedRun;
113
+ snapshot: Readonly<LaunchSnapshot>;
114
+ }, checkpoints: readonly AwaitingCheckpoint[], worktrees: readonly WorktreeReference[], now?: number): string;
115
+ export declare function formatWorkflowPhaseDashboard(run: PersistedRun, snapshot: Readonly<LaunchSnapshot>, width: number, selection?: WorkflowPhaseSelection, styles?: WorkflowProgressStyles, now?: number): string[];
116
+ export declare function formatCheckpointReview(checkpoint: AwaitingCheckpoint): string;