@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,89 @@
1
+ import { type PersistedRun } from "./persistence.js";
2
+ import { type AgentRecord, type LaunchSnapshot, type RunState, type WorkflowPhaseShellActivity } from "./types.js";
3
+ export declare const WORKFLOW_PHASE_STATES: readonly ["not started", "running", "completed", "failed", "cancelled", "interrupted", "budget_exhausted"];
4
+ export type WorkflowPhaseState = (typeof WORKFLOW_PHASE_STATES)[number];
5
+ export interface WorkflowPhaseAgentCounts {
6
+ total: number;
7
+ completed: number;
8
+ running: number;
9
+ failed: number;
10
+ cancelled: number;
11
+ pending: number;
12
+ }
13
+ export interface WorkflowPhaseView {
14
+ id: string;
15
+ name: string;
16
+ occurrence: number;
17
+ state: WorkflowPhaseState;
18
+ observed: boolean;
19
+ afterAgent?: number;
20
+ agents: readonly AgentRecord[];
21
+ counts: WorkflowPhaseAgentCounts;
22
+ shellActivity?: WorkflowPhaseShellActivity;
23
+ }
24
+ export interface WorkflowPhaseModel {
25
+ runState: RunState;
26
+ declaredPhases: readonly string[];
27
+ phases: readonly WorkflowPhaseView[];
28
+ currentPhaseIndex?: number;
29
+ currentPhaseId?: string;
30
+ counts: Readonly<Partial<Record<WorkflowPhaseState, number>>>;
31
+ unassignedAgents?: readonly AgentRecord[];
32
+ }
33
+ type WorkflowPhaseSource = readonly string[] | Pick<LaunchSnapshot, "phases"> | undefined;
34
+ export declare function phaseAgentCounts(agents: readonly AgentRecord[]): WorkflowPhaseAgentCounts;
35
+ /** The live shell activity recorded for one phase index; `-1` is the preflight scope before any phase. */
36
+ export declare function shellActivityFor(run: Pick<PersistedRun, "activeShellsByPhase">, phaseIndex: number): WorkflowPhaseShellActivity | undefined;
37
+ type WorkflowPhaseRun = Omit<Pick<PersistedRun, "state" | "phase" | "phaseHistory" | "agents" | "activeShellsByPhase">, "phaseHistory"> & {
38
+ phaseHistory?: unknown;
39
+ };
40
+ export declare function buildWorkflowPhaseModel(run: WorkflowPhaseRun, source?: WorkflowPhaseSource): WorkflowPhaseModel;
41
+ export interface WorkflowPhaseSelection {
42
+ phaseId?: string | undefined;
43
+ agentId?: string | undefined;
44
+ nodeId?: string | undefined;
45
+ expandedNodeIds?: readonly string[] | undefined;
46
+ treeOnly?: boolean | undefined;
47
+ detailsOnly?: boolean | undefined;
48
+ actions?: {
49
+ title: string;
50
+ options: readonly string[];
51
+ index: number;
52
+ } | undefined;
53
+ }
54
+ export type WorkflowPhaseTreeNodeKind = "workflow" | "phase" | "operation" | "agent" | "shell";
55
+ export interface WorkflowPhaseTreeNode {
56
+ id: string;
57
+ kind: WorkflowPhaseTreeNodeKind;
58
+ label: string;
59
+ depth: number;
60
+ phaseId: string;
61
+ operationPath: readonly string[];
62
+ parentId?: string;
63
+ children: readonly string[];
64
+ state: WorkflowPhaseState | RunState | AgentRecord["state"];
65
+ agentId?: string;
66
+ agent?: AgentRecord;
67
+ phase?: WorkflowPhaseView;
68
+ shellActivity?: WorkflowPhaseShellActivity;
69
+ }
70
+ export interface WorkflowPhaseTree {
71
+ roots: readonly string[];
72
+ nodes: readonly WorkflowPhaseTreeNode[];
73
+ byId: ReadonlyMap<string, WorkflowPhaseTreeNode>;
74
+ }
75
+ export interface WorkflowPhaseTreeSelection {
76
+ nodeId?: string | undefined;
77
+ }
78
+ export type WorkflowPhaseTreeDirection = "up" | "down" | "left" | "right";
79
+ export declare function workflowPhaseTreePath(kind: WorkflowPhaseTreeNodeKind, phaseId: string, operationPath: readonly string[], agentId?: string): string;
80
+ export declare function buildWorkflowPhaseTree(model: WorkflowPhaseModel): WorkflowPhaseTree;
81
+ export declare function workflowPhaseTreeVisibleNodes(tree: WorkflowPhaseTree, expanded?: ReadonlySet<string>): readonly WorkflowPhaseTreeNode[];
82
+ export declare function workflowPhaseTreeInitialExpanded(tree: WorkflowPhaseTree): ReadonlySet<string>;
83
+ export declare function preserveWorkflowPhaseTreeSelection(tree: WorkflowPhaseTree, selection: WorkflowPhaseTreeSelection): WorkflowPhaseTreeSelection;
84
+ export declare function navigateWorkflowPhaseTree(tree: WorkflowPhaseTree, selectedNodeId: string | undefined, expandedNodeIds: ReadonlySet<string>, direction: WorkflowPhaseTreeDirection): {
85
+ nodeId?: string;
86
+ expandedNodeIds: ReadonlySet<string>;
87
+ };
88
+ export declare function preserveWorkflowPhaseSelection(model: WorkflowPhaseModel, selection: WorkflowPhaseSelection): WorkflowPhaseSelection;
89
+ export {};
@@ -0,0 +1,320 @@
1
+ import { object } from "./utils.js";
2
+ export const WORKFLOW_PHASE_STATES = ["not started", "running", "completed", "failed", "cancelled", "interrupted", "budget_exhausted"];
3
+ function phaseNames(source) {
4
+ const phases = source === undefined ? [] : Array.isArray(source) ? source : object(source) ? source.phases ?? [] : [];
5
+ return phases.filter((phase) => typeof phase === "string" && phase.trim() !== "").map((phase) => phase.trim());
6
+ }
7
+ export function phaseAgentCounts(agents) {
8
+ const counts = { total: agents.length, completed: 0, running: 0, failed: 0, cancelled: 0, pending: 0 };
9
+ for (const agent of agents) {
10
+ if (agent.state === "completed")
11
+ counts.completed += 1;
12
+ else if (agent.state === "running")
13
+ counts.running += 1;
14
+ else if (agent.state === "failed")
15
+ counts.failed += 1;
16
+ else if (agent.state === "cancelled")
17
+ counts.cancelled += 1;
18
+ else
19
+ counts.pending += 1;
20
+ }
21
+ return counts;
22
+ }
23
+ /** The live shell activity recorded for one phase index; `-1` is the preflight scope before any phase. */
24
+ export function shellActivityFor(run, phaseIndex) {
25
+ return run.activeShellsByPhase?.find((activity) => activity.phaseIndex === phaseIndex && activity.active > 0);
26
+ }
27
+ function phaseState(runState, counts, isLatest, shellActive = false) {
28
+ if (!isLatest && !shellActive)
29
+ return "completed";
30
+ if (runState === "failed")
31
+ return "failed";
32
+ if (runState === "stopped")
33
+ return "cancelled";
34
+ if (runState === "interrupted")
35
+ return "interrupted";
36
+ if (runState === "budget_exhausted")
37
+ return "budget_exhausted";
38
+ if (counts.failed > 0)
39
+ return "failed";
40
+ if (shellActive)
41
+ return "running";
42
+ if (counts.cancelled > 0)
43
+ return "cancelled";
44
+ if (counts.running > 0 || counts.pending > 0)
45
+ return "running";
46
+ return runState === "completed" ? "completed" : "running";
47
+ }
48
+ export function buildWorkflowPhaseModel(run, source) {
49
+ const declaredPhases = phaseNames(source);
50
+ const rawHistory = Array.isArray(run.phaseHistory) ? run.phaseHistory : [];
51
+ const observed = [];
52
+ let boundary = 0;
53
+ for (const record of rawHistory) {
54
+ if (!object(record) || typeof record.phase !== "string" || !record.phase.trim() || typeof record.afterAgent !== "number" || !Number.isSafeInteger(record.afterAgent))
55
+ continue;
56
+ boundary = Math.max(boundary, Math.min(run.agents.length, Math.max(0, record.afterAgent)));
57
+ observed.push({ name: record.phase.trim(), afterAgent: boundary });
58
+ }
59
+ if (!observed.length && typeof run.phase === "string" && run.phase.trim())
60
+ observed.push({ name: run.phase.trim(), afterAgent: 0 });
61
+ const observedEntries = observed.map((entry, index) => ({ ...entry, index, agents: run.agents.slice(entry.afterAgent, observed[index + 1]?.afterAgent ?? run.agents.length) }));
62
+ const matchedDeclarations = new Set();
63
+ const declarationIndices = observedEntries.map((entry) => {
64
+ const index = declaredPhases.findIndex((name, candidate) => !matchedDeclarations.has(candidate) && name === entry.name);
65
+ if (index >= 0)
66
+ matchedDeclarations.add(index);
67
+ return index >= 0 ? index : undefined;
68
+ });
69
+ const entries = observedEntries.map((entry, index) => ({ name: entry.name, observedIndex: index, ...(declarationIndices[index] === undefined ? {} : { declarationIndex: declarationIndices[index] }) }));
70
+ for (const [declarationIndex, name] of declaredPhases.entries()) {
71
+ if (matchedDeclarations.has(declarationIndex))
72
+ continue;
73
+ const insertion = entries.findIndex((entry) => entry.declarationIndex !== undefined && entry.declarationIndex > declarationIndex);
74
+ const pending = { name };
75
+ if (insertion < 0)
76
+ entries.push(pending);
77
+ else
78
+ entries.splice(insertion, 0, pending);
79
+ }
80
+ const occurrences = new Map();
81
+ const phases = entries.map((entry) => {
82
+ const occurrence = (occurrences.get(entry.name) ?? 0) + 1;
83
+ occurrences.set(entry.name, occurrence);
84
+ const observation = entry.observedIndex === undefined ? undefined : observedEntries[entry.observedIndex];
85
+ const agents = observation?.agents ?? [];
86
+ const shellActivity = observation ? shellActivityFor(run, observation.index) : undefined;
87
+ const counts = phaseAgentCounts(agents);
88
+ const state = observation ? phaseState(run.state, counts, entry.observedIndex === observedEntries.length - 1, shellActivity !== undefined) : "not started";
89
+ return { id: `${entry.name}#${String(occurrence)}`, name: entry.name, occurrence, state, observed: observation !== undefined, ...(observation ? { afterAgent: observation.afterAgent } : {}), agents, counts, ...(shellActivity ? { shellActivity } : {}) };
90
+ });
91
+ const preflightActivity = shellActivityFor(run, -1);
92
+ const phasesWithPreflight = preflightActivity ? [{ id: "preflight", name: "Preflight", occurrence: 1, state: phaseState(run.state, phaseAgentCounts([]), true, true), observed: true, afterAgent: 0, agents: [], counts: phaseAgentCounts([]), shellActivity: preflightActivity }, ...phases] : phases;
93
+ let currentPhaseIndex;
94
+ for (let index = phasesWithPreflight.length - 1; index >= 0; index -= 1) {
95
+ if (phasesWithPreflight[index]?.observed) {
96
+ currentPhaseIndex = index;
97
+ break;
98
+ }
99
+ }
100
+ const counts = {};
101
+ for (const phase of phasesWithPreflight)
102
+ counts[phase.state] = (counts[phase.state] ?? 0) + 1;
103
+ const current = currentPhaseIndex === undefined ? undefined : phasesWithPreflight[currentPhaseIndex];
104
+ const assigned = new Set(observedEntries.flatMap(({ agents }) => agents.map((agent) => agent.id)));
105
+ const unassignedAgents = run.agents.filter((agent) => !assigned.has(agent.id));
106
+ const result = { runState: run.state, declaredPhases, phases: phasesWithPreflight, counts };
107
+ if (current !== undefined && currentPhaseIndex !== undefined) {
108
+ result.currentPhaseIndex = currentPhaseIndex;
109
+ result.currentPhaseId = current.id;
110
+ }
111
+ if (unassignedAgents.length)
112
+ result.unassignedAgents = unassignedAgents;
113
+ return result;
114
+ }
115
+ export function workflowPhaseTreePath(kind, phaseId, operationPath, agentId) {
116
+ if (kind === "workflow")
117
+ return "workflow";
118
+ const root = `phase/${encodeURIComponent(phaseId)}`;
119
+ if (kind === "phase")
120
+ return root;
121
+ if (kind === "shell")
122
+ return `${root}/shell`;
123
+ const operation = operationPath.map((part) => encodeURIComponent(part)).join("/");
124
+ if (kind === "operation")
125
+ return `${root}/operation/${operation}`;
126
+ return operation ? `${root}/operation/${operation}/agent/${encodeURIComponent(agentId ?? "")}` : `${root}/agent/${encodeURIComponent(agentId ?? "")}`;
127
+ }
128
+ function workflowPhaseTreeAggregateState(states) {
129
+ if (!states.length || states.every((state) => state === "completed"))
130
+ return "completed";
131
+ if (states.some((state) => state === "failed"))
132
+ return "failed";
133
+ if (states.some((state) => state === "cancelled"))
134
+ return "cancelled";
135
+ if (states.some((state) => state === "running"))
136
+ return "running";
137
+ return "queued";
138
+ }
139
+ export function buildWorkflowPhaseTree(model) {
140
+ const drafts = new Map();
141
+ const roots = [];
142
+ const add = (node, parentId) => {
143
+ const existing = drafts.get(node.id);
144
+ if (existing)
145
+ return existing;
146
+ const draft = { ...node, ...(parentId === undefined ? {} : { parentId }), children: [] };
147
+ drafts.set(draft.id, draft);
148
+ if (parentId === undefined)
149
+ roots.push(draft.id);
150
+ else
151
+ drafts.get(parentId)?.children.push(draft.id);
152
+ return draft;
153
+ };
154
+ const samePath = (left, right) => left.length === right.length && left.every((part, index) => part === right[index]);
155
+ const addPhase = (phaseId, label, agents, phase) => {
156
+ const phaseNode = add({ id: workflowPhaseTreePath("phase", phaseId, []), kind: "phase", label, depth: 0, phaseId, operationPath: [], state: phase?.state ?? workflowPhaseTreeAggregateState(agents.map((agent) => agent.state)), ...(phase ? { phase } : {}) }, workflowNode.id);
157
+ if (phase?.shellActivity)
158
+ add({ id: workflowPhaseTreePath("shell", phaseId, []), kind: "shell", label: `shell [running] (${String(phase.shellActivity.active)} active)`, depth: 0, phaseId, operationPath: [], state: "running", phase, shellActivity: phase.shellActivity }, phaseNode.id);
159
+ const operationNodes = new Map();
160
+ const entries = agents.map((agent) => {
161
+ const structuralPath = [...(agent.structuralPath ?? [])];
162
+ return { agent, structuralPath, operationPath: [...structuralPath, ...(agent.parentBreadcrumb ? [agent.parentBreadcrumb] : [])], defaultParentId: phaseNode.id };
163
+ });
164
+ const agentEntries = new Map(entries.map((entry) => [entry.agent.id, entry]));
165
+ const acceptedParents = new Map();
166
+ const wouldCycle = (childId, parentId) => {
167
+ const seen = new Set([childId]);
168
+ let current = parentId;
169
+ while (current) {
170
+ if (seen.has(current))
171
+ return true;
172
+ seen.add(current);
173
+ current = acceptedParents.get(current);
174
+ }
175
+ return false;
176
+ };
177
+ for (const entry of entries) {
178
+ const parent = entry.agent.parentId ? agentEntries.get(entry.agent.parentId) : undefined;
179
+ if (parent && !wouldCycle(entry.agent.id, parent.agent.id))
180
+ acceptedParents.set(entry.agent.id, parent.agent.id);
181
+ }
182
+ const operationChain = (path, owner, startIndex = 0) => {
183
+ let parent = owner;
184
+ for (let index = startIndex; index < path.length; index += 1) {
185
+ const prefix = path.slice(0, index + 1);
186
+ const key = `${owner.id}:${JSON.stringify(prefix)}`;
187
+ const existing = operationNodes.get(key);
188
+ if (existing) {
189
+ parent = existing;
190
+ continue;
191
+ }
192
+ const suffix = path.slice(startIndex, index + 1).map((part) => encodeURIComponent(part)).join("/");
193
+ const id = owner.id === phaseNode.id ? workflowPhaseTreePath("operation", phaseId, prefix) : `${owner.id}/operation/${suffix}`;
194
+ const operation = add({ id, kind: "operation", label: prefix.at(-1) ?? "", depth: 0, phaseId, operationPath: prefix, state: "queued", ...(phase ? { phase } : {}) }, parent.id);
195
+ operationNodes.set(key, operation);
196
+ parent = operation;
197
+ }
198
+ return parent;
199
+ };
200
+ for (const entry of entries) {
201
+ if (!acceptedParents.has(entry.agent.id))
202
+ entry.defaultParentId = operationChain(entry.operationPath, phaseNode).id;
203
+ }
204
+ for (const entry of entries) {
205
+ entry.node = add({ id: workflowPhaseTreePath("agent", phaseId, entry.structuralPath, entry.agent.id), kind: "agent", label: entry.agent.label ?? entry.agent.name, depth: 0, phaseId, operationPath: entry.structuralPath, state: entry.agent.state, agentId: entry.agent.id, agent: entry.agent }, phaseNode.id);
206
+ }
207
+ const attach = (node, parentId) => {
208
+ const previous = node.parentId ? drafts.get(node.parentId) : undefined;
209
+ if (previous)
210
+ previous.children = previous.children.filter((childId) => childId !== node.id);
211
+ node.parentId = parentId;
212
+ const parent = drafts.get(parentId);
213
+ if (parent && !parent.children.includes(node.id))
214
+ parent.children.push(node.id);
215
+ };
216
+ for (const entry of entries) {
217
+ const node = entry.node;
218
+ if (!node)
219
+ continue;
220
+ const parentId = acceptedParents.get(entry.agent.id);
221
+ const parent = parentId ? agentEntries.get(parentId) : undefined;
222
+ const parentNode = parent?.node;
223
+ if (parent && parentNode) {
224
+ if (samePath(entry.operationPath, parent.operationPath))
225
+ entry.defaultParentId = parentNode.id;
226
+ else {
227
+ const commonLength = entry.operationPath.findIndex((part, index) => parent.operationPath[index] !== part);
228
+ const startIndex = commonLength < 0 ? Math.min(entry.operationPath.length, parent.operationPath.length) : commonLength;
229
+ entry.defaultParentId = operationChain(entry.operationPath, parentNode, startIndex).id;
230
+ }
231
+ }
232
+ attach(node, entry.defaultParentId);
233
+ }
234
+ const setDepth = (node, depth, seen = new Set()) => {
235
+ if (seen.has(node.id))
236
+ return;
237
+ seen.add(node.id);
238
+ node.depth = depth;
239
+ for (const childId of node.children) {
240
+ const child = drafts.get(childId);
241
+ if (child)
242
+ setDepth(child, depth + 1, seen);
243
+ }
244
+ };
245
+ setDepth(phaseNode, 0);
246
+ const statesFor = (node, seen = new Set()) => {
247
+ if (seen.has(node.id))
248
+ return [];
249
+ const nextSeen = new Set(seen).add(node.id);
250
+ return node.children.flatMap((childId) => {
251
+ const child = drafts.get(childId);
252
+ return child?.kind === "agent" ? [child.agent?.state ?? "queued", ...statesFor(child, nextSeen)] : child ? statesFor(child, nextSeen) : [];
253
+ });
254
+ };
255
+ for (const operation of operationNodes.values())
256
+ operation.state = workflowPhaseTreeAggregateState(statesFor(operation));
257
+ };
258
+ const workflowNode = add({ id: "workflow", kind: "workflow", label: "Workflow", depth: 0, phaseId: "", operationPath: [], state: model.runState });
259
+ for (const phase of model.phases)
260
+ addPhase(phase.id, `${phase.name}${phase.occurrence > 1 ? ` #${String(phase.occurrence)}` : ""}`, phase.agents, phase);
261
+ if (model.unassignedAgents?.length)
262
+ addPhase("unassigned", "Unassigned", model.unassignedAgents);
263
+ const nodes = [...drafts.values()].map((node) => ({ ...node, children: [...node.children] }));
264
+ return { roots, nodes, byId: new Map(nodes.map((node) => [node.id, node])) };
265
+ }
266
+ export function workflowPhaseTreeVisibleNodes(tree, expanded = new Set()) {
267
+ const visible = [];
268
+ const visit = (id) => {
269
+ const node = tree.byId.get(id);
270
+ if (!node)
271
+ return;
272
+ visible.push(node);
273
+ if (expanded.has(node.id))
274
+ for (const childId of node.children)
275
+ visit(childId);
276
+ };
277
+ for (const root of tree.roots)
278
+ visit(root);
279
+ return visible;
280
+ }
281
+ export function workflowPhaseTreeInitialExpanded(tree) {
282
+ return new Set(tree.nodes.filter((node) => node.children.length > 0).map((node) => node.id));
283
+ }
284
+ export function preserveWorkflowPhaseTreeSelection(tree, selection) {
285
+ const node = (selection.nodeId ? tree.byId.get(selection.nodeId) : undefined) ?? tree.nodes[0];
286
+ return node ? { nodeId: node.id } : {};
287
+ }
288
+ export function navigateWorkflowPhaseTree(tree, selectedNodeId, expandedNodeIds, direction) {
289
+ const expanded = new Set(expandedNodeIds);
290
+ const current = (selectedNodeId ? tree.byId.get(selectedNodeId) : undefined) ?? tree.nodes[0];
291
+ if (!current)
292
+ return { expandedNodeIds: expanded };
293
+ if (direction === "left") {
294
+ if (current.children.length && expanded.delete(current.id))
295
+ return { nodeId: current.id, expandedNodeIds: expanded };
296
+ return { nodeId: current.parentId ?? current.id, expandedNodeIds: expanded };
297
+ }
298
+ if (direction === "right") {
299
+ if (current.children.length && !expanded.has(current.id)) {
300
+ expanded.add(current.id);
301
+ return { nodeId: current.id, expandedNodeIds: expanded };
302
+ }
303
+ return { nodeId: current.children[0] ?? current.id, expandedNodeIds: expanded };
304
+ }
305
+ const visible = workflowPhaseTreeVisibleNodes(tree, expanded);
306
+ const index = Math.max(0, visible.findIndex((node) => node.id === current.id));
307
+ const next = visible[(index + (direction === "up" ? visible.length - 1 : 1)) % visible.length];
308
+ return { nodeId: next?.id ?? current.id, expandedNodeIds: expanded };
309
+ }
310
+ export function preserveWorkflowPhaseSelection(model, selection) {
311
+ const phase = model.phases.find((candidate) => candidate.id === selection.phaseId) ?? (model.currentPhaseIndex === undefined ? undefined : model.phases[model.currentPhaseIndex]) ?? model.phases[0];
312
+ if (!phase)
313
+ return model.unassignedAgents?.length ? { nodeId: workflowPhaseTreePath("phase", "unassigned", []) } : {};
314
+ const tree = buildWorkflowPhaseTree(model);
315
+ const selectedAgent = selection.agentId ? phase.agents.find((candidate) => candidate.id === selection.agentId) : undefined;
316
+ const selectedCandidate = selection.nodeId ? tree.byId.get(selection.nodeId) : undefined;
317
+ const selected = selectedCandidate?.phaseId === phase.id ? selectedCandidate : selectedAgent ? tree.byId.get(workflowPhaseTreePath("agent", phase.id, selectedAgent.structuralPath ?? [], selectedAgent.id)) : undefined;
318
+ const nodeId = selected?.id ?? tree.byId.get(workflowPhaseTreePath("phase", phase.id, []))?.id;
319
+ return { phaseId: phase.id, ...(selection.agentId && phase.agents.some((agent) => agent.id === selection.agentId) ? { agentId: selection.agentId } : phase.agents[0] ? { agentId: phase.agents[0].id } : {}), ...(nodeId ? { nodeId } : {}), ...(selection.expandedNodeIds ? { expandedNodeIds: selection.expandedNodeIds } : {}) };
320
+ }
@@ -0,0 +1,141 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ import { FairAgentScheduler, WorkflowAgentExecutor, type AgentDefinition, type AgentExecutionRoot, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
4
+ import { RunStore, type PersistedRun } from "./persistence.js";
5
+ import { WorkflowError, type AgentIdentity, type AgentResourcePolicy, type BudgetApprovalRequest, type JsonValue, type LaunchSnapshot, type ModelSpec, type RunState, type ShellIdentity, type ShellOptions, type ShellResult, type WorkflowMetadata, type WorkflowWorktreeReference } from "./types.js";
6
+ import { type WorkflowRegistryApi } from "./registry.js";
7
+ import { RunLifecycle, WorkflowEventPublisher, type WorkflowRunRecord, type WorkflowToolUpdate } from "./host-runtime.js";
8
+ import { type CompletionDeliveryContext, type CompletionDeliveryResult } from "./host-delivery.js";
9
+ /** The subset of Pi's model registry the host relies on; every member is optional because older or headless hosts expose fewer of them. */
10
+ export type ModelRegistryCapability = {
11
+ getAll?: () => readonly Model<Api>[];
12
+ getAvailable?: () => readonly Model<Api>[];
13
+ find?: (provider: string, model: string) => Model<Api> | undefined;
14
+ refresh?: () => Promise<void>;
15
+ getError?: () => string | undefined;
16
+ };
17
+ export type WorkflowRecoveryContext = {
18
+ model: {
19
+ provider: string;
20
+ id: string;
21
+ } | undefined;
22
+ modelRegistry: ModelRegistryCapability | undefined;
23
+ deliveryContext: CompletionDeliveryContext;
24
+ signal?: AbortSignal;
25
+ resolvedAliases?: Readonly<Record<string, string>>;
26
+ blockedAliases?: ReadonlySet<string>;
27
+ blockedAliasTargets?: Readonly<Record<string, string>>;
28
+ };
29
+ export type WorkflowRecoveryDependencies = {
30
+ pi: Pick<ExtensionAPI, "getThinkingLevel">;
31
+ home: string | undefined;
32
+ runs: Map<string, WorkflowRunRecord>;
33
+ scheduler: FairAgentScheduler;
34
+ eventPublisher: WorkflowEventPublisher;
35
+ persistRunState: (store: RunStore, metadata: WorkflowMetadata, update: (run: PersistedRun) => PersistedRun | Promise<PersistedRun>) => Promise<PersistedRun>;
36
+ projectTrusted: (context: unknown) => boolean;
37
+ resumeHostContext: (context: unknown) => WorkflowRecoveryContext;
38
+ ensureSessionLease: (cwd: string, sessionId: string) => Promise<void>;
39
+ coordinateRunMutation?: <T>(task: () => Promise<T>) => Promise<T>;
40
+ createAgentExecutor: (root: Omit<AgentExecutionRoot, "agentDir" | "agentSetupHooks">) => WorkflowAgentExecutor;
41
+ activeSnapshotTools: (tools: readonly string[], active: ReadonlySet<string> | "session") => Set<string>;
42
+ frozenResourcePolicy: (policy: AgentResourcePolicy) => () => AgentResourcePolicy;
43
+ resolveLaunchPrologue: (input: {
44
+ snapshot: Readonly<LaunchSnapshot>;
45
+ cwd: string;
46
+ trustedProject: boolean;
47
+ rootModel: ModelSpec;
48
+ modelRegistry?: WorkflowRecoveryContext["modelRegistry"];
49
+ signal: AbortSignal;
50
+ resolvedAliases?: Readonly<Record<string, string>>;
51
+ blockedAliases?: ReadonlySet<string>;
52
+ blockedAliasTargets?: Readonly<Record<string, string>>;
53
+ withPreflight: boolean;
54
+ }) => Promise<{
55
+ active: Set<string>;
56
+ settingsPath: string;
57
+ currentPolicy: AgentResourcePolicy;
58
+ previousAliases: Readonly<Record<string, string>>;
59
+ knownModels: ReadonlySet<string>;
60
+ availableModels: ReadonlySet<string>;
61
+ currentAliases: Readonly<Record<string, string>>;
62
+ blockedAliases: ReadonlySet<string>;
63
+ blockedAliasTargets: Readonly<Record<string, string>>;
64
+ snapshot: Readonly<LaunchSnapshot>;
65
+ script: string | undefined;
66
+ }>;
67
+ resumeRoles: (store: RunStore, snapshot: Readonly<LaunchSnapshot>, cwd: string, trustedProject: boolean) => {
68
+ definitions: Readonly<Record<string, AgentDefinition>>;
69
+ capture: (role: string, model: ModelSpec) => Promise<void>;
70
+ };
71
+ workflowAgentHandler: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string, captureRole?: (role: string, model: ModelSpec) => Promise<void>) => (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>;
72
+ shellForRun: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>;
73
+ resolveWorktree: (store: RunStore, metadata: WorkflowMetadata, owner: string) => Promise<Readonly<WorkflowWorktreeReference>>;
74
+ checkpointBridge: (runId: string, store: RunStore, metadata: WorkflowMetadata, foreground: boolean, ui?: {
75
+ select?: (prompt: string, options: string[]) => Promise<string | undefined>;
76
+ }, headless?: boolean) => (raw: Readonly<Record<string, JsonValue>>, signal: AbortSignal) => Promise<boolean>;
77
+ phaseBridge: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle) => (phase: string) => Promise<void>;
78
+ logBridge: (store: RunStore, lifecycle: RunLifecycle, workflowName: string) => (message: string) => Promise<void>;
79
+ lifecycleFor: (store: RunStore, state: RunState, metadata: WorkflowMetadata) => RunLifecycle;
80
+ createProviderErrorRecovery: (host: unknown, fallbackModels: ReadonlySet<string>, abort: () => void) => ((failure: AgentProviderFailure) => Promise<AgentProviderRecovery>) | undefined;
81
+ cleanupTerminalRun: (runId: string) => Promise<void>;
82
+ deliver: (content: string) => void;
83
+ deliverTerminal: (store: RunStore, content: string | (() => string | Promise<string>), failure?: boolean) => Promise<void>;
84
+ workflowToolUpdate: (run: PersistedRun) => WorkflowToolUpdate;
85
+ registry: WorkflowRegistryApi;
86
+ modelSpec: (value: string, fallback: ModelSpec) => ModelSpec;
87
+ };
88
+ export declare function persistedFailure(run: PersistedRun, error: WorkflowError): PersistedRun;
89
+ export declare function createWorkflowRecovery(deps: WorkflowRecoveryDependencies): {
90
+ refreshPausedRunAliases: (run: WorkflowRunRecord, context?: {
91
+ model: {
92
+ provider: string;
93
+ id: string;
94
+ } | undefined;
95
+ modelRegistry: WorkflowRecoveryContext["modelRegistry"];
96
+ projectTrusted?: boolean;
97
+ }) => Promise<void>;
98
+ coldResumeRun: (run: WorkflowRunRecord, hasUI: boolean, ui: {
99
+ select?: (prompt: string, options: string[]) => Promise<string | undefined>;
100
+ }, trustedProject: boolean, context?: {
101
+ model: {
102
+ provider: string;
103
+ id: string;
104
+ } | undefined;
105
+ modelRegistry: WorkflowRecoveryContext["modelRegistry"];
106
+ deliveryContext: CompletionDeliveryContext;
107
+ signal?: AbortSignal | undefined;
108
+ resolvedAliases?: Readonly<Record<string, string>>;
109
+ blockedAliases?: ReadonlySet<string>;
110
+ blockedAliasTargets?: Readonly<Record<string, string>>;
111
+ }, modeOverride?: boolean, waitForCompletion?: boolean) => Promise<{
112
+ value: JsonValue;
113
+ resultPath: string;
114
+ resultBytes: number;
115
+ completion: CompletionDeliveryResult;
116
+ } | undefined>;
117
+ applyBudgetDecision: (request: BudgetApprovalRequest, approved: boolean, context?: unknown, signal?: AbortSignal, waitForCompletion?: boolean) => Promise<{
118
+ state: "running" | "completed" | "budget_exhausted";
119
+ approved: boolean;
120
+ value?: JsonValue;
121
+ run?: PersistedRun;
122
+ completion?: CompletionDeliveryResult;
123
+ }>;
124
+ answerBudgetDecision: (runId: string, proposalId: string, approved: boolean, silent?: boolean, context?: unknown, signal?: AbortSignal, waitForCompletion?: boolean) => Promise<{
125
+ state: "running" | "completed" | "budget_exhausted";
126
+ approved: boolean;
127
+ value?: JsonValue;
128
+ run?: PersistedRun;
129
+ completion?: CompletionDeliveryResult;
130
+ } | undefined>;
131
+ budgetDecisionDelivery: (metadata: WorkflowMetadata, request: BudgetApprovalRequest) => string;
132
+ resumeWorkflowRun: (runId: string, rawPatch?: unknown, context?: unknown, signal?: AbortSignal, modeOverride?: boolean, waitForCompletion?: boolean, expectedState?: string) => Promise<Record<string, JsonValue>>;
133
+ retryWorkflowRun: (runId: string, context: unknown, signal?: AbortSignal, modeOverride?: boolean, expectedState?: string) => Promise<{
134
+ runId: string;
135
+ parentRunId: string;
136
+ state: "running" | "completed";
137
+ value?: JsonValue;
138
+ run?: PersistedRun;
139
+ completion?: CompletionDeliveryResult;
140
+ }>;
141
+ };