@osolmaz/pi-workflows 0.12.0 → 0.13.0

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 (257) hide show
  1. package/README.md +56 -31
  2. package/dist/builtins/autodoc.workflow.d.ts +4 -4
  3. package/dist/builtins/autoimplement.workflow.d.ts +797 -142
  4. package/dist/builtins/autoimplement.workflow.js +17 -105
  5. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  6. package/dist/builtins/autoplan.workflow.d.ts +126 -13
  7. package/dist/builtins/autoplan.workflow.js +200 -29
  8. package/dist/builtins/autoplan.workflow.js.map +1 -1
  9. package/dist/builtins/catalog.js +7 -5
  10. package/dist/builtins/catalog.js.map +1 -1
  11. package/dist/builtins/index.d.ts +4 -2
  12. package/dist/builtins/index.js +3 -1
  13. package/dist/builtins/index.js.map +1 -1
  14. package/dist/builtins/monitor.workflow.d.ts +56 -23
  15. package/dist/builtins/monitor.workflow.js +508 -329
  16. package/dist/builtins/monitor.workflow.js.map +1 -1
  17. package/dist/builtins/pi-agent-group.d.ts +72 -0
  18. package/dist/builtins/pi-agent-group.js +1087 -0
  19. package/dist/builtins/pi-agent-group.js.map +1 -0
  20. package/dist/builtins/plain-summary.workflow.d.ts +32 -0
  21. package/dist/builtins/plain-summary.workflow.js +134 -0
  22. package/dist/builtins/plain-summary.workflow.js.map +1 -0
  23. package/dist/builtins/plan-approval.workflow.d.ts +39 -5
  24. package/dist/builtins/plan-approval.workflow.js +92 -14
  25. package/dist/builtins/plan-approval.workflow.js.map +1 -1
  26. package/dist/builtins/plan-change.workflow.d.ts +449 -0
  27. package/dist/builtins/plan-change.workflow.js +256 -0
  28. package/dist/builtins/plan-change.workflow.js.map +1 -0
  29. package/dist/builtins/plan-presentation.js +2 -2
  30. package/dist/builtins/plan-presentation.js.map +1 -1
  31. package/dist/builtins/sanity-check.workflow.d.ts +50 -6
  32. package/dist/builtins/sanity-check.workflow.js +150 -28
  33. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  34. package/dist/controllers/effects.d.ts +3 -2
  35. package/dist/controllers/effects.js +8 -1
  36. package/dist/controllers/effects.js.map +1 -1
  37. package/dist/controllers/index.d.ts +1 -1
  38. package/dist/controllers/index.js +1 -1
  39. package/dist/controllers/index.js.map +1 -1
  40. package/dist/controllers/manager.d.ts +1 -0
  41. package/dist/controllers/manager.js +41 -17
  42. package/dist/controllers/manager.js.map +1 -1
  43. package/dist/controllers/sqlite.d.ts +107 -85
  44. package/dist/controllers/sqlite.js +1611 -1486
  45. package/dist/controllers/sqlite.js.map +1 -1
  46. package/dist/controllers/store.d.ts +9 -8
  47. package/dist/controllers/store.js +1 -31
  48. package/dist/controllers/store.js.map +1 -1
  49. package/dist/controllers/types.d.ts +3 -0
  50. package/dist/controllers/workflow-engine-scheduler.d.ts +1 -1
  51. package/dist/controllers/workflow-engine-scheduler.js +3 -5
  52. package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
  53. package/dist/controllers/workflows.d.ts +2 -2
  54. package/dist/controllers/workflows.js +11 -10
  55. package/dist/controllers/workflows.js.map +1 -1
  56. package/dist/extension/controller-host.js +2 -2
  57. package/dist/extension/controller-host.js.map +1 -1
  58. package/dist/extension/decision-channels.d.ts +4 -2
  59. package/dist/extension/decision-channels.js +333 -146
  60. package/dist/extension/decision-channels.js.map +1 -1
  61. package/dist/extension/executor.d.ts +9 -2
  62. package/dist/extension/executor.js +89 -2
  63. package/dist/extension/executor.js.map +1 -1
  64. package/dist/extension/herdr-viewer.d.ts +0 -1
  65. package/dist/extension/herdr-viewer.js +0 -4
  66. package/dist/extension/herdr-viewer.js.map +1 -1
  67. package/dist/extension/index.js +209 -127
  68. package/dist/extension/index.js.map +1 -1
  69. package/dist/extension/recorder.d.ts +3 -10
  70. package/dist/extension/recorder.js +13 -22
  71. package/dist/extension/recorder.js.map +1 -1
  72. package/dist/extension/session-events.d.ts +2 -2
  73. package/dist/extension/step-message.d.ts +7 -2
  74. package/dist/extension/step-message.js +94 -2
  75. package/dist/extension/step-message.js.map +1 -1
  76. package/dist/extension/widget.js +29 -3
  77. package/dist/extension/widget.js.map +1 -1
  78. package/dist/host/rpc-executor.d.ts +3 -0
  79. package/dist/host/rpc-executor.js +2 -0
  80. package/dist/host/rpc-executor.js.map +1 -1
  81. package/dist/host/runner.d.ts +3 -6
  82. package/dist/host/runner.js +83 -32
  83. package/dist/host/runner.js.map +1 -1
  84. package/dist/render/graph-render.js +15 -10
  85. package/dist/render/graph-render.js.map +1 -1
  86. package/dist/state/database.d.ts +35 -0
  87. package/dist/state/database.js +287 -0
  88. package/dist/state/database.js.map +1 -0
  89. package/dist/state/index.d.ts +4 -0
  90. package/dist/state/index.js +5 -0
  91. package/dist/state/index.js.map +1 -0
  92. package/dist/state/json.d.ts +6 -0
  93. package/dist/state/json.js +38 -0
  94. package/dist/state/json.js.map +1 -0
  95. package/dist/state/mutation.d.ts +57 -0
  96. package/dist/state/mutation.js +255 -0
  97. package/dist/state/mutation.js.map +1 -0
  98. package/dist/state/schema.d.ts +6 -0
  99. package/dist/state/schema.js +473 -0
  100. package/dist/state/schema.js.map +1 -0
  101. package/dist/viewer/cli.d.ts +2 -2
  102. package/dist/viewer/cli.js +142 -122
  103. package/dist/viewer/cli.js.map +1 -1
  104. package/dist/viewer/render.d.ts +4 -4
  105. package/dist/viewer/render.js +9 -23
  106. package/dist/viewer/render.js.map +1 -1
  107. package/dist/viewer/session-reducer.d.ts +1 -1
  108. package/dist/viewer/session-reducer.js +1 -1
  109. package/dist/viewer/tui.d.ts +3 -3
  110. package/dist/viewer/tui.js +15 -13
  111. package/dist/viewer/tui.js.map +1 -1
  112. package/dist/viewer/watch.d.ts +2 -5
  113. package/dist/viewer/watch.js +13 -13
  114. package/dist/viewer/watch.js.map +1 -1
  115. package/dist/workflows/catalog.js +7 -2
  116. package/dist/workflows/catalog.js.map +1 -1
  117. package/dist/workflows/composition.js +20 -2
  118. package/dist/workflows/composition.js.map +1 -1
  119. package/dist/workflows/decision-presentation.d.ts +1 -1
  120. package/dist/workflows/decision-presentation.js +51 -38
  121. package/dist/workflows/decision-presentation.js.map +1 -1
  122. package/dist/workflows/definition.d.ts +6 -2
  123. package/dist/workflows/definition.js +17 -0
  124. package/dist/workflows/definition.js.map +1 -1
  125. package/dist/workflows/engine.d.ts +7 -7
  126. package/dist/workflows/engine.js +196 -115
  127. package/dist/workflows/engine.js.map +1 -1
  128. package/dist/workflows/errors.d.ts +16 -3
  129. package/dist/workflows/errors.js +18 -3
  130. package/dist/workflows/errors.js.map +1 -1
  131. package/dist/workflows/human-decision.d.ts +45 -8
  132. package/dist/workflows/human-decision.js +641 -274
  133. package/dist/workflows/human-decision.js.map +1 -1
  134. package/dist/workflows/index.d.ts +5 -6
  135. package/dist/workflows/index.js +4 -5
  136. package/dist/workflows/index.js.map +1 -1
  137. package/dist/workflows/progress.d.ts +1 -0
  138. package/dist/workflows/progress.js +15 -3
  139. package/dist/workflows/progress.js.map +1 -1
  140. package/dist/workflows/schema.js +27 -1
  141. package/dist/workflows/schema.js.map +1 -1
  142. package/dist/workflows/store.d.ts +83 -103
  143. package/dist/workflows/store.js +907 -951
  144. package/dist/workflows/store.js.map +1 -1
  145. package/dist/workflows/types.d.ts +88 -126
  146. package/docs/CONTROLLERS.md +10 -8
  147. package/docs/DEFERRED_TURNS.md +2 -2
  148. package/docs/DESIGN_PHILOSOPHY.md +4 -2
  149. package/docs/HUMAN_DECISIONS.md +36 -47
  150. package/docs/HUMAN_DECISION_PRESENTATIONS.md +14 -24
  151. package/docs/MONITOR.md +179 -237
  152. package/docs/SQLITE_STATE.md +219 -0
  153. package/docs/WORKFLOW_COMPOSITION.md +27 -26
  154. package/docs/WORKFLOW_STEP_MESSAGES.md +20 -18
  155. package/docs/WORKFLOW_UPDATES.md +10 -9
  156. package/docs/development.md +9 -9
  157. package/docs/live-replay-protocol.md +28 -37
  158. package/docs/plans/2026-08-16-workflow-updates-plan.md +1 -1
  159. package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
  160. package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +1 -1
  161. package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +322 -0
  162. package/docs/plans/2026-08-21-sanity-check-plan.md +217 -95
  163. package/docs/plans/2026-08-22-goal-finishing-monitor-plan.md +200 -0
  164. package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +701 -0
  165. package/docs/plans/2026-08-23-sqlite-state-plan.md +970 -0
  166. package/docs/plans/replayable-run-bundles-implementation-plan.md +2 -2
  167. package/docs/plans/session-event-replay-implementation-plan.md +1 -1
  168. package/docs/plans/tui-viewer-implementation-plan.md +1 -1
  169. package/docs/session-event-journal.md +63 -434
  170. package/docs/tui-viewer.md +13 -15
  171. package/docs/workflows.md +127 -53
  172. package/examples/workflows/approved-plan.workflow.ts +19 -46
  173. package/examples/workflows/plain-summary.workflow.ts +1 -0
  174. package/herdr-plugin.toml +1 -1
  175. package/package.json +7 -7
  176. package/plugins/herdr/viewer.mjs +1 -11
  177. package/schemas/human-decision-accepted-v1.schema.json +15 -3
  178. package/schemas/human-decision-continuation-v1.schema.json +10 -1
  179. package/schemas/human-decision-delivery-v1.schema.json +8 -0
  180. package/schemas/human-decision-receipt-v1.schema.json +8 -0
  181. package/schemas/human-decision-request-v1.schema.json +24 -4
  182. package/skills/autoimplement/SKILL.md +27 -0
  183. package/skills/autoplan/SKILL.md +5 -4
  184. package/skills/monitor/SKILL.md +91 -135
  185. package/skills/pi-workflows/SKILL.md +9 -8
  186. package/skills/sanity-check/SKILL.md +44 -0
  187. package/src/builtins/autoimplement.workflow.ts +19 -118
  188. package/src/builtins/autoplan.workflow.ts +290 -40
  189. package/src/builtins/catalog.ts +7 -5
  190. package/src/builtins/index.ts +18 -1
  191. package/src/builtins/monitor.workflow.ts +673 -382
  192. package/src/builtins/pi-agent-group.ts +1407 -0
  193. package/src/builtins/plain-summary.workflow.ts +185 -0
  194. package/src/builtins/plan-approval.workflow.ts +157 -24
  195. package/src/builtins/plan-change.workflow.ts +321 -0
  196. package/src/builtins/plan-presentation.ts +2 -2
  197. package/src/builtins/sanity-check.workflow.ts +248 -48
  198. package/src/controllers/effects.ts +7 -0
  199. package/src/controllers/index.ts +0 -6
  200. package/src/controllers/manager.ts +72 -32
  201. package/src/controllers/sqlite.ts +2420 -1948
  202. package/src/controllers/store.ts +17 -38
  203. package/src/controllers/types.ts +3 -0
  204. package/src/controllers/workflow-engine-scheduler.ts +4 -8
  205. package/src/controllers/workflows.ts +45 -21
  206. package/src/extension/controller-host.ts +1 -2
  207. package/src/extension/decision-channels.ts +456 -203
  208. package/src/extension/executor.ts +105 -2
  209. package/src/extension/herdr-viewer.ts +0 -5
  210. package/src/extension/index.ts +251 -151
  211. package/src/extension/recorder.ts +13 -22
  212. package/src/extension/session-events.ts +2 -2
  213. package/src/extension/step-message.ts +106 -4
  214. package/src/extension/widget.ts +32 -5
  215. package/src/host/rpc-executor.ts +4 -0
  216. package/src/host/runner.ts +93 -41
  217. package/src/render/graph-render.ts +12 -6
  218. package/src/state/database.ts +375 -0
  219. package/src/state/index.ts +29 -0
  220. package/src/state/json.ts +42 -0
  221. package/src/state/mutation.ts +435 -0
  222. package/src/state/schema.ts +475 -0
  223. package/src/viewer/cli.ts +151 -144
  224. package/src/viewer/render.ts +19 -30
  225. package/src/viewer/session-reducer.ts +1 -1
  226. package/src/viewer/tui.ts +18 -17
  227. package/src/viewer/watch.ts +13 -17
  228. package/src/workflows/catalog.ts +7 -2
  229. package/src/workflows/composition.ts +21 -3
  230. package/src/workflows/decision-presentation.ts +56 -43
  231. package/src/workflows/definition.ts +36 -3
  232. package/src/workflows/engine.ts +228 -130
  233. package/src/workflows/errors.ts +27 -3
  234. package/src/workflows/human-decision.ts +921 -349
  235. package/src/workflows/index.ts +20 -30
  236. package/src/workflows/progress.ts +18 -3
  237. package/src/workflows/schema.ts +36 -1
  238. package/src/workflows/store.ts +1300 -1163
  239. package/src/workflows/types.ts +100 -137
  240. package/dist/builtins/sanity-check-session.d.ts +0 -17
  241. package/dist/builtins/sanity-check-session.js +0 -168
  242. package/dist/builtins/sanity-check-session.js.map +0 -1
  243. package/dist/workflows/artifacts.d.ts +0 -40
  244. package/dist/workflows/artifacts.js +0 -155
  245. package/dist/workflows/artifacts.js.map +0 -1
  246. package/dist/workflows/migrate-sources.d.ts +0 -42
  247. package/dist/workflows/migrate-sources.js +0 -133
  248. package/dist/workflows/migrate-sources.js.map +0 -1
  249. package/docs/run-bundles.md +0 -481
  250. package/schemas/human-decision-accepted-v2.schema.json +0 -50
  251. package/schemas/human-decision-delivery-v2.schema.json +0 -36
  252. package/schemas/human-decision-receipt-v2.schema.json +0 -39
  253. package/schemas/human-decision-request-v2.schema.json +0 -69
  254. package/schemas/human-decision-resolution-v2.schema.json +0 -27
  255. package/src/builtins/sanity-check-session.ts +0 -205
  256. package/src/workflows/artifacts.ts +0 -188
  257. package/src/workflows/migrate-sources.ts +0 -178
@@ -1,5 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { agent, compute, defineWorkflow } from "../workflows/definition.js";
2
+ import { agent, compute, defineWorkflow, includeWorkflow } from "../workflows/definition.js";
3
+ import plainSummaryWorkflow, {
4
+ type PlainSummaryInput,
5
+ type PlainSummaryResult,
6
+ } from "./plain-summary.workflow.js";
3
7
 
4
8
  export type AutoplanInput = {
5
9
  problem: string;
@@ -9,13 +13,47 @@ export type AutoplanInput = {
9
13
  newEvidence?: unknown;
10
14
  };
11
15
 
16
+ export type AutoplanCandidate = {
17
+ id: string;
18
+ title: string;
19
+ gist: string;
20
+ solution: string;
21
+ rationale: string;
22
+ parts: string[];
23
+ tradeoffs: string[];
24
+ };
25
+
26
+ export type AutoplanProposal = {
27
+ candidates: AutoplanCandidate[];
28
+ previousPlan?:
29
+ | { status: "candidate"; candidateId: string }
30
+ | { status: "rejected"; reason: string };
31
+ };
32
+
33
+ export type AutoplanIdeal = {
34
+ ideal: string;
35
+ outsideDependencies: string[];
36
+ additionalValue: string[];
37
+ };
38
+
39
+ export type AutoplanSelection = {
40
+ status: "ready" | "blocked";
41
+ selectedId: string;
42
+ why: string;
43
+ relationshipToIdeal: string;
44
+ rejected: Array<{ id: string; reason: string }>;
45
+ compromises: string[];
46
+ blocker?: string;
47
+ };
48
+
12
49
  export type AutoplanReady = {
13
50
  status: "ready";
14
51
  frame: unknown;
15
- proposal: unknown;
16
- ideal: unknown;
17
- selection: unknown;
52
+ proposal: AutoplanProposal;
53
+ ideal: AutoplanIdeal;
54
+ selection: AutoplanSelection;
18
55
  plan: unknown;
56
+ plainSummary: PlainSummaryResult;
19
57
  planDigest: string;
20
58
  previousPlanDigest?: string;
21
59
  changed: boolean;
@@ -24,9 +62,10 @@ export type AutoplanReady = {
24
62
  export type AutoplanBlocked = {
25
63
  status: "blocked";
26
64
  frame: unknown;
27
- proposal: unknown;
28
- ideal: unknown;
29
- selection: unknown;
65
+ proposal: AutoplanProposal;
66
+ ideal: AutoplanIdeal;
67
+ selection: AutoplanSelection;
68
+ plainSummary: PlainSummaryResult;
30
69
  reason: string;
31
70
  };
32
71
 
@@ -44,6 +83,24 @@ function requireString(value: unknown, label: string): string {
44
83
  return value.trim();
45
84
  }
46
85
 
86
+ function requireBoundedString(value: unknown, label: string, maxChars: number): string {
87
+ const text = requireString(value, label);
88
+ if (text.length > maxChars) throw new Error(`${label} exceeds ${maxChars} characters`);
89
+ return text;
90
+ }
91
+
92
+ function requireStringArray(
93
+ value: unknown,
94
+ label: string,
95
+ maxItems = 32,
96
+ maxChars = 2_000,
97
+ ): string[] {
98
+ if (!Array.isArray(value) || value.length > maxItems) {
99
+ throw new Error(`${label} must be an array with at most ${maxItems} strings`);
100
+ }
101
+ return value.map((item, index) => requireBoundedString(item, `${label}[${index}]`, maxChars));
102
+ }
103
+
47
104
  function parseInput(value: unknown): AutoplanInput {
48
105
  const input = requireRecord(value, "autoplan input");
49
106
  const constraints = input.constraints;
@@ -62,34 +119,216 @@ function parseInput(value: unknown): AutoplanInput {
62
119
  };
63
120
  }
64
121
 
65
- function parseSelection(value: unknown): Record<string, unknown> {
122
+ function parseProposal(value: unknown, previousPlanSupplied: boolean): AutoplanProposal {
123
+ const proposal = requireRecord(value, "autoplan proposal");
124
+ if (
125
+ !Array.isArray(proposal.candidates) ||
126
+ proposal.candidates.length < 2 ||
127
+ proposal.candidates.length > 4
128
+ ) {
129
+ throw new Error("autoplan proposal must contain two through four candidates");
130
+ }
131
+ const ids = new Set<string>();
132
+ const candidates = proposal.candidates.map((value, index): AutoplanCandidate => {
133
+ const candidate = requireRecord(value, `autoplan candidate ${index + 1}`);
134
+ const id = requireString(candidate.id, `autoplan candidate ${index + 1} id`);
135
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(id)) {
136
+ throw new Error(`autoplan candidate id ${JSON.stringify(id)} is invalid`);
137
+ }
138
+ if (id === "ideal" || ids.has(id)) {
139
+ throw new Error(`autoplan candidate id ${JSON.stringify(id)} is reserved or duplicated`);
140
+ }
141
+ ids.add(id);
142
+ return {
143
+ id,
144
+ title: requireBoundedString(candidate.title, `autoplan candidate ${id} title`, 200),
145
+ gist: requireBoundedString(candidate.gist, `autoplan candidate ${id} gist`, 1_000),
146
+ solution: requireBoundedString(
147
+ candidate.solution,
148
+ `autoplan candidate ${id} solution`,
149
+ 20_000,
150
+ ),
151
+ rationale: requireBoundedString(
152
+ candidate.rationale,
153
+ `autoplan candidate ${id} rationale`,
154
+ 5_000,
155
+ ),
156
+ parts: requireStringArray(candidate.parts, `autoplan candidate ${id} parts`),
157
+ tradeoffs: requireStringArray(candidate.tradeoffs, `autoplan candidate ${id} tradeoffs`),
158
+ };
159
+ });
160
+
161
+ let previousPlan: AutoplanProposal["previousPlan"];
162
+ if (proposal.previousPlan !== undefined) {
163
+ const account = requireRecord(proposal.previousPlan, "autoplan previousPlan account");
164
+ if (account.status === "candidate") {
165
+ const candidateId = requireString(account.candidateId, "autoplan previousPlan candidateId");
166
+ if (!ids.has(candidateId)) {
167
+ throw new Error("autoplan previousPlan candidateId must name a candidate");
168
+ }
169
+ previousPlan = { status: "candidate", candidateId };
170
+ } else if (account.status === "rejected") {
171
+ previousPlan = {
172
+ status: "rejected",
173
+ reason: requireBoundedString(
174
+ account.reason,
175
+ "autoplan previousPlan rejection reason",
176
+ 5_000,
177
+ ),
178
+ };
179
+ } else {
180
+ throw new Error("autoplan previousPlan status must be candidate or rejected");
181
+ }
182
+ }
183
+ if (previousPlanSupplied && previousPlan === undefined) {
184
+ throw new Error("autoplan proposal must account for the supplied previous plan");
185
+ }
186
+ return { candidates, ...(previousPlan !== undefined ? { previousPlan } : {}) };
187
+ }
188
+
189
+ function parseIdeal(value: unknown): AutoplanIdeal {
190
+ const ideal = requireRecord(value, "autoplan ideal");
191
+ return {
192
+ ideal: requireBoundedString(ideal.ideal, "autoplan ideal end state", 20_000),
193
+ outsideDependencies: requireStringArray(
194
+ ideal.outsideDependencies,
195
+ "autoplan ideal outsideDependencies",
196
+ ),
197
+ additionalValue: requireStringArray(ideal.additionalValue, "autoplan ideal additionalValue"),
198
+ };
199
+ }
200
+
201
+ function parseSelection(value: unknown, proposal: AutoplanProposal): AutoplanSelection {
66
202
  const selection = requireRecord(value, "autoplan selection");
67
203
  if (selection.status !== "ready" && selection.status !== "blocked") {
68
204
  throw new Error("autoplan selection status must be ready or blocked");
69
205
  }
70
- requireString(selection.selected, "autoplan selected solution");
71
- requireString(selection.why, "autoplan selection reason");
72
- if (selection.status === "blocked") requireString(selection.blocker, "autoplan blocker");
73
- return selection;
206
+ const validIds = new Set([...proposal.candidates.map((candidate) => candidate.id), "ideal"]);
207
+ const selectedId = requireString(selection.selectedId, "autoplan selectedId");
208
+ if (!validIds.has(selectedId)) throw new Error("autoplan selectedId must name a candidate");
209
+ if (!Array.isArray(selection.rejected)) {
210
+ throw new Error("autoplan rejected plans must be an array");
211
+ }
212
+ const rejectedIds = new Set<string>();
213
+ const rejected = selection.rejected.map((value, index) => {
214
+ const rejection = requireRecord(value, `autoplan rejection ${index + 1}`);
215
+ const id = requireString(rejection.id, `autoplan rejection ${index + 1} id`);
216
+ if (!validIds.has(id) || id === selectedId || rejectedIds.has(id)) {
217
+ throw new Error(`autoplan rejected id ${JSON.stringify(id)} is invalid or duplicated`);
218
+ }
219
+ rejectedIds.add(id);
220
+ return {
221
+ id,
222
+ reason: requireBoundedString(rejection.reason, `autoplan rejection ${id} reason`, 5_000),
223
+ };
224
+ });
225
+ const expectedRejected = [...validIds].filter((id) => id !== selectedId).sort();
226
+ if (JSON.stringify([...rejectedIds].sort()) !== JSON.stringify(expectedRejected)) {
227
+ throw new Error("autoplan selection must reject every non-selected candidate exactly once");
228
+ }
229
+ const blocker =
230
+ selection.status === "blocked"
231
+ ? requireString(selection.blocker, "autoplan blocker")
232
+ : undefined;
233
+ return {
234
+ status: selection.status,
235
+ selectedId,
236
+ why: requireBoundedString(selection.why, "autoplan selection reason", 5_000),
237
+ relationshipToIdeal: requireBoundedString(
238
+ selection.relationshipToIdeal,
239
+ "autoplan relationshipToIdeal",
240
+ 5_000,
241
+ ),
242
+ rejected,
243
+ compromises: requireStringArray(selection.compromises, "autoplan compromises"),
244
+ ...(blocker !== undefined ? { blocker } : {}),
245
+ };
74
246
  }
75
247
 
76
248
  function digest(value: unknown): string {
77
249
  return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
78
250
  }
79
251
 
252
+ function plainSummaryResult(value: unknown, label: string): PlainSummaryResult {
253
+ const included = requireRecord(value, label);
254
+ if (included.exit !== "completed") throw new Error(`${label} must use the completed exit`);
255
+ const result = requireRecord(included.output, `${label} output`);
256
+ return { text: requireString(result.text, `${label} text`) };
257
+ }
258
+
259
+ function candidateSummary(
260
+ proposal: AutoplanProposal,
261
+ ideal: AutoplanIdeal,
262
+ id: string,
263
+ ): { id: string; title: string; gist: string } {
264
+ if (id === "ideal") return { id, title: "Ideal end state", gist: ideal.ideal };
265
+ const candidate = proposal.candidates.find((item) => item.id === id);
266
+ if (candidate === undefined)
267
+ throw new Error(`autoplan candidate ${JSON.stringify(id)} is missing`);
268
+ return { id, title: candidate.title, gist: candidate.gist };
269
+ }
270
+
271
+ function summaryInput(
272
+ outputs: Record<string, unknown>,
273
+ blocked: boolean,
274
+ input: AutoplanInput,
275
+ ): PlainSummaryInput {
276
+ const proposal = outputs.propose as AutoplanProposal;
277
+ const selection = outputs.choose as AutoplanSelection;
278
+ const ideal = outputs.ideal as AutoplanIdeal;
279
+ const selected = candidateSummary(proposal, ideal, selection.selectedId);
280
+ return {
281
+ source: {
282
+ status: selection.status,
283
+ selected,
284
+ why: selection.why,
285
+ implementationPlan: blocked ? null : outputs.plan,
286
+ rejected: selection.rejected.map((rejection) => ({
287
+ ...candidateSummary(proposal, ideal, rejection.id),
288
+ reason: rejection.reason,
289
+ })),
290
+ relationshipToIdeal: selection.relationshipToIdeal,
291
+ compromises: selection.compromises,
292
+ ...(proposal.previousPlan !== undefined && input.previousPlan !== undefined
293
+ ? { previousPlan: { plan: input.previousPlan, account: proposal.previousPlan } }
294
+ : {}),
295
+ ...(selection.blocker !== undefined ? { blocker: selection.blocker } : {}),
296
+ },
297
+ purpose: blocked
298
+ ? "Explain plainly why planning is blocked and summarize every considered plan."
299
+ : "Explain the recommended plan plainly, summarize its main implementation steps, and give one-line reasons for rejecting every other plan.",
300
+ mustInclude: [
301
+ selected.title,
302
+ ...selection.rejected.map(
303
+ (rejection) => candidateSummary(proposal, ideal, rejection.id).title,
304
+ ),
305
+ ...(proposal.previousPlan !== undefined && input.previousPlan !== undefined
306
+ ? ["Previous plan"]
307
+ : []),
308
+ ...(blocked ? [selection.blocker as string] : ["The plan is selected for approval"]),
309
+ ],
310
+ maxChars: 2_500,
311
+ maxSentences: 12,
312
+ format: "mixed",
313
+ };
314
+ }
315
+
80
316
  export const autoplanWorkflow = defineWorkflow({
81
317
  source: import.meta.url,
82
318
  contractId: "pi-workflows.autoplan.v1",
83
319
  name: "autoplan",
84
320
  input: parseInput,
85
321
  title: ({ input }) => `autoplan: ${input.problem.slice(0, 60)}`,
86
- presentationPrompt: [
87
- "Present the selected practical solution and its implementation plan.",
88
- "Briefly state how the ideal informed the choice and what was excluded as outside scope.",
89
- "Do not ask the user to choose between the options.",
90
- ].join("\n"),
91
322
  startAt: "frame",
92
- maxSteps: 10,
323
+ maxSteps: 16,
324
+ includes: {
325
+ readySummary: includeWorkflow(plainSummaryWorkflow, {
326
+ input: ({ outputs, input }) => summaryInput(outputs, false, input as AutoplanInput),
327
+ }),
328
+ blockedSummary: includeWorkflow(plainSummaryWorkflow, {
329
+ input: ({ outputs, input }) => summaryInput(outputs, true, input as AutoplanInput),
330
+ }),
331
+ },
93
332
  exits: {
94
333
  ready: {
95
334
  from: "finalize",
@@ -119,32 +358,36 @@ export const autoplanWorkflow = defineWorkflow({
119
358
  validate: (value) => requireRecord(value, "autoplan frame"),
120
359
  }),
121
360
  propose: agent({
122
- statusDetail: "devising a solution",
123
- prompt: ({ outputs }) =>
361
+ statusDetail: "devising candidate solutions",
362
+ prompt: ({ outputs, input }) =>
124
363
  [
125
- "Devise the most elegant, long-term production-ready solution within the framed scope.",
364
+ "Devise two through four distinct practical solutions within the framed scope.",
365
+ "Each candidate needs a stable lowercase id, short title, plain gist, full solution, rationale, parts, and trade-offs.",
126
366
  "Prefer a small number of general parts, clear ownership boundaries, and existing public interfaces.",
127
367
  "Avoid one-off mechanisms and unnecessary infrastructure.",
368
+ "When a previous plan exists, mark it as a candidate or reject it with a reason.",
128
369
  "Do not implement anything.",
129
370
  `Problem frame: ${JSON.stringify(outputs.frame)}`,
371
+ `Previous plan: ${JSON.stringify((input as AutoplanInput).previousPlan ?? null)}`,
130
372
  ].join("\n"),
131
- expectedOutput: `{ "solution": "proposal", "rationale": "why", "parts": ["part"], "tradeoffs": ["trade-off"] }`,
132
- validate: (value) => requireRecord(value, "autoplan proposal"),
373
+ expectedOutput: `{ "candidates": [{ "id": "stable-id", "title": "short title", "gist": "plain gist", "solution": "full proposal", "rationale": "why", "parts": ["part"], "tradeoffs": ["trade-off"] }], "previousPlan": { "status": "candidate", "candidateId": "id" } | { "status": "rejected", "reason": "reason" } }`,
374
+ validate: (value, { input }) =>
375
+ parseProposal(value, (input as AutoplanInput).previousPlan !== undefined),
133
376
  }),
134
377
  ideal: agent({
135
378
  statusDetail: "describing the ideal end state",
136
379
  prompt: ({ outputs, input }) =>
137
380
  [
138
- "Set the proposal aside and describe the holy grail for this problem.",
139
- "The holy grail can match the proposal or exceed the current scope.",
381
+ "Set the practical candidates aside and describe the holy grail for this problem.",
382
+ "The holy grail can match a candidate or exceed the current scope.",
140
383
  "Name dependencies outside our authority instead of assuming they can change.",
141
- "Explain the practical value beyond the proposal.",
384
+ "Explain the practical value beyond the candidates.",
142
385
  `Problem frame: ${JSON.stringify(outputs.frame)}`,
143
- `Proposal: ${JSON.stringify(outputs.propose)}`,
386
+ `Candidates: ${JSON.stringify(outputs.propose)}`,
144
387
  `New evidence: ${JSON.stringify((input as AutoplanInput).newEvidence ?? null)}`,
145
388
  ].join("\n"),
146
389
  expectedOutput: `{ "ideal": "ideal end state", "outsideDependencies": ["dependency"], "additionalValue": ["benefit"] }`,
147
- validate: (value) => requireRecord(value, "autoplan ideal"),
390
+ validate: parseIdeal,
148
391
  }),
149
392
  choose: agent({
150
393
  statusDetail: "choosing the practical solution",
@@ -152,17 +395,19 @@ export const autoplanWorkflow = defineWorkflow({
152
395
  [
153
396
  "Choose the right solution without asking the user to decide.",
154
397
  "Choose the ideal when it is production-ready, proportionate, in scope, and implementable through interfaces we control.",
155
- "Otherwise choose the strongest practical in-scope solution with a clear path toward the ideal.",
398
+ "Otherwise choose the strongest practical in-scope candidate with a clear path toward the ideal.",
156
399
  "Do not block only because the ideal depends on work outside our authority.",
157
400
  "Do not make an upstream change, unrelated repository, new service, or unapproved resource a requirement.",
158
401
  "Prefer the simpler choice when options give materially equivalent results.",
402
+ "Return one rejection reason for every non-selected candidate, including the ideal when it is not selected.",
159
403
  "Return blocked only when no truthful in-scope solution can meet the success criteria.",
160
404
  `Frame: ${JSON.stringify(outputs.frame)}`,
161
- `Proposal: ${JSON.stringify(outputs.propose)}`,
405
+ `Candidates: ${JSON.stringify(outputs.propose)}`,
406
+ `Ideal candidate id: ideal`,
162
407
  `Ideal: ${JSON.stringify(outputs.ideal)}`,
163
408
  ].join("\n"),
164
- expectedOutput: `{ "status": "ready" | "blocked", "selected": "solution", "why": "reason", "relationshipToIdeal": "relationship", "excluded": ["excluded work"], "compromises": ["compromise"], "blocker": "required only when blocked" }`,
165
- validate: parseSelection,
409
+ expectedOutput: `{ "status": "ready" | "blocked", "selectedId": "candidate-id-or-ideal", "why": "reason", "relationshipToIdeal": "relationship", "rejected": [{ "id": "other-id", "reason": "why it lost" }], "compromises": ["compromise"], "blocker": "required only when blocked" }`,
410
+ validate: (value, { outputs }) => parseSelection(value, outputs.propose as AutoplanProposal),
166
411
  }),
167
412
  plan: agent({
168
413
  timeoutMs: 30 * 60_000,
@@ -177,6 +422,7 @@ export const autoplanWorkflow = defineWorkflow({
177
422
  "Do not implement the plan.",
178
423
  `Frame: ${JSON.stringify(outputs.frame)}`,
179
424
  `Selection: ${JSON.stringify(outputs.choose)}`,
425
+ `Candidates: ${JSON.stringify(outputs.propose)}`,
180
426
  `Previous plan: ${JSON.stringify((input as AutoplanInput).previousPlan ?? null)}`,
181
427
  `New evidence: ${JSON.stringify((input as AutoplanInput).newEvidence ?? null)}`,
182
428
  ].join("\n"),
@@ -185,13 +431,14 @@ export const autoplanWorkflow = defineWorkflow({
185
431
  }),
186
432
  blocked: compute({
187
433
  run: ({ outputs }) => {
188
- const selection = outputs.choose as Record<string, unknown>;
434
+ const selection = outputs.choose as AutoplanSelection;
189
435
  return {
190
436
  status: "blocked",
191
437
  frame: outputs.frame,
192
- proposal: outputs.propose,
193
- ideal: outputs.ideal,
438
+ proposal: outputs.propose as AutoplanProposal,
439
+ ideal: outputs.ideal as AutoplanIdeal,
194
440
  selection,
441
+ plainSummary: plainSummaryResult(outputs.blockedSummary, "autoplan blocked summary"),
195
442
  reason: requireString(selection.blocker, "autoplan blocker"),
196
443
  } satisfies AutoplanBlocked;
197
444
  },
@@ -205,10 +452,11 @@ export const autoplanWorkflow = defineWorkflow({
205
452
  return {
206
453
  status: "ready",
207
454
  frame: outputs.frame,
208
- proposal: outputs.propose,
209
- ideal: outputs.ideal,
210
- selection: outputs.choose,
455
+ proposal: outputs.propose as AutoplanProposal,
456
+ ideal: outputs.ideal as AutoplanIdeal,
457
+ selection: outputs.choose as AutoplanSelection,
211
458
  plan: outputs.plan,
459
+ plainSummary: plainSummaryResult(outputs.readySummary, "autoplan ready summary"),
212
460
  planDigest,
213
461
  ...(previousPlanDigest !== undefined ? { previousPlanDigest } : {}),
214
462
  changed: previousPlanDigest === undefined || previousPlanDigest !== planDigest,
@@ -222,9 +470,11 @@ export const autoplanWorkflow = defineWorkflow({
222
470
  { from: "ideal", to: "choose" },
223
471
  {
224
472
  from: "choose",
225
- switch: { on: "$.status", cases: { ready: "plan", blocked: "blocked" } },
473
+ switch: { on: "$.status", cases: { ready: "plan", blocked: "blockedSummary" } },
226
474
  },
227
- { from: "plan", to: "finalize" },
475
+ { from: "plan", to: "readySummary" },
476
+ { from: "readySummary.completed", to: "finalize" },
477
+ { from: "blockedSummary.completed", to: "blocked" },
228
478
  ],
229
479
  });
230
480
 
@@ -3,18 +3,20 @@ import autodocWorkflow from "./autodoc.workflow.js";
3
3
  import autoimplementWorkflow from "./autoimplement.workflow.js";
4
4
  import autoplanWorkflow from "./autoplan.workflow.js";
5
5
  import monitorWorkflow from "./monitor.workflow.js";
6
+ import plainSummaryWorkflow from "./plain-summary.workflow.js";
6
7
  import planApprovalWorkflow from "./plan-approval.workflow.js";
7
8
  import sanityCheckWorkflow from "./sanity-check.workflow.js";
8
9
 
9
10
  export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
10
- { id: "autoplan", revision: "1", definition: autoplanWorkflow },
11
+ { id: "plain-summary", revision: "1", definition: plainSummaryWorkflow },
12
+ { id: "autoplan", revision: "2", definition: autoplanWorkflow },
11
13
  { id: "autodoc", revision: "1", definition: autodocWorkflow },
12
- { id: "autoimplement", revision: "6", definition: autoimplementWorkflow },
13
- { id: "plan-approval", revision: "2", definition: planApprovalWorkflow },
14
- { id: "sanity-check", revision: "1", definition: sanityCheckWorkflow },
14
+ { id: "autoimplement", revision: "9", definition: autoimplementWorkflow },
15
+ { id: "plan-approval", revision: "4", definition: planApprovalWorkflow },
16
+ { id: "sanity-check", revision: "4", definition: sanityCheckWorkflow },
15
17
  {
16
18
  id: "monitor",
17
- revision: "7",
19
+ revision: "11",
18
20
  definition: monitorWorkflow,
19
21
  legacySources: [
20
22
  {
@@ -21,8 +21,20 @@ export type { AutoimplementConcurrency } from "./autoimplement-command-batches.j
21
21
  export {
22
22
  default as monitorWorkflow,
23
23
  type MonitorInput,
24
- type MonitorRepairPolicy,
24
+ type MonitorObservation,
25
25
  } from "./monitor.workflow.js";
26
+ export {
27
+ plainSummaryWorkflow,
28
+ type PlainSummaryFormat,
29
+ type PlainSummaryInput,
30
+ type PlainSummaryResult,
31
+ } from "./plain-summary.workflow.js";
32
+ export {
33
+ planChangeWorkflow,
34
+ type PlanChangeBlocked,
35
+ type PlanChangeInput,
36
+ type PlanChangeReady,
37
+ } from "./plan-change.workflow.js";
26
38
  export { presentPlan } from "./plan-presentation.js";
27
39
  export {
28
40
  sanityCheckWorkflow,
@@ -38,8 +50,13 @@ export {
38
50
  } from "./sanity-check.workflow.js";
39
51
  export {
40
52
  planApprovalWorkflow,
53
+ parsePlanApprovalPolicy,
41
54
  type PlanApprovalContinue,
42
55
  type PlanApprovalInput,
56
+ type PlanApprovalMode,
57
+ type PlanApprovalPolicy,
43
58
  type PlanApprovalReplan,
59
+ type PlanApprovalResolution,
44
60
  type PlanApprovalStop,
61
+ type ResolvedPlanApprovalPolicy,
45
62
  } from "./plan-approval.workflow.js";