@osolmaz/pi-workflows 0.11.2 → 0.12.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 (123) hide show
  1. package/README.md +11 -7
  2. package/dist/builtins/autoimplement-command-batches.d.ts +52 -0
  3. package/dist/builtins/autoimplement-command-batches.js +258 -0
  4. package/dist/builtins/autoimplement-command-batches.js.map +1 -0
  5. package/dist/builtins/autoimplement.workflow.d.ts +29 -3
  6. package/dist/builtins/autoimplement.workflow.js +876 -231
  7. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  8. package/dist/builtins/catalog.js +3 -1
  9. package/dist/builtins/catalog.js.map +1 -1
  10. package/dist/builtins/index.d.ts +2 -0
  11. package/dist/builtins/index.js +1 -0
  12. package/dist/builtins/index.js.map +1 -1
  13. package/dist/builtins/sanity-check-session.d.ts +17 -0
  14. package/dist/builtins/sanity-check-session.js +168 -0
  15. package/dist/builtins/sanity-check-session.js.map +1 -0
  16. package/dist/builtins/sanity-check.workflow.d.ts +83 -0
  17. package/dist/builtins/sanity-check.workflow.js +398 -0
  18. package/dist/builtins/sanity-check.workflow.js.map +1 -0
  19. package/dist/controllers/sqlite.d.ts +90 -2
  20. package/dist/controllers/sqlite.js +380 -5
  21. package/dist/controllers/sqlite.js.map +1 -1
  22. package/dist/extension/decision-channels.js +7 -7
  23. package/dist/extension/deferred-turn-coordinator.d.ts +32 -0
  24. package/dist/extension/deferred-turn-coordinator.js +143 -0
  25. package/dist/extension/deferred-turn-coordinator.js.map +1 -0
  26. package/dist/extension/deferred-turn.d.ts +44 -0
  27. package/dist/extension/deferred-turn.js +110 -0
  28. package/dist/extension/deferred-turn.js.map +1 -0
  29. package/dist/extension/index.js +286 -47
  30. package/dist/extension/index.js.map +1 -1
  31. package/dist/extension/step-message.d.ts +1 -0
  32. package/dist/extension/step-message.js.map +1 -1
  33. package/dist/herdr/setup.js +4 -4
  34. package/dist/workflows/command-batch.d.ts +38 -0
  35. package/dist/workflows/command-batch.js +176 -0
  36. package/dist/workflows/command-batch.js.map +1 -0
  37. package/dist/workflows/engine.js +8 -4
  38. package/dist/workflows/engine.js.map +1 -1
  39. package/dist/workflows/index.d.ts +1 -0
  40. package/dist/workflows/index.js +1 -0
  41. package/dist/workflows/index.js.map +1 -1
  42. package/dist/workflows/schema.js +2 -1
  43. package/dist/workflows/schema.js.map +1 -1
  44. package/dist/workflows/shell.d.ts +4 -0
  45. package/dist/workflows/shell.js +6 -0
  46. package/dist/workflows/shell.js.map +1 -1
  47. package/dist/workflows/store.js +3 -1
  48. package/dist/workflows/store.js.map +1 -1
  49. package/dist/workflows/types.d.ts +4 -3
  50. package/docs/2026-08-18-herdr-piw-plan.md +1 -1
  51. package/docs/2026-08-20-durable-workflow-launch-plan.md +19 -23
  52. package/docs/CONTROLLERS.md +2 -2
  53. package/docs/DEFERRED_TURNS.md +298 -0
  54. package/docs/DESIGN_PHILOSOPHY.md +1 -1
  55. package/docs/HUMAN_DECISIONS.md +7 -7
  56. package/docs/HUMAN_DECISION_PRESENTATIONS.md +3 -3
  57. package/docs/MONITOR.md +2 -2
  58. package/docs/WORKFLOW_COMPOSITION.md +2 -2
  59. package/docs/WORKFLOW_STEP_MESSAGES.md +5 -5
  60. package/docs/WORKFLOW_UPDATES.md +28 -5
  61. package/docs/plans/2026-08-04-controller-runtime-plan.md +1 -1
  62. package/docs/plans/2026-08-05-always-on-workflows-plan.md +1 -1
  63. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +9 -9
  64. package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +1 -1
  65. package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
  66. package/docs/plans/2026-08-16-workflow-updates-plan.md +3 -3
  67. package/docs/plans/2026-08-17-bundled-skills-plan.md +6 -6
  68. package/docs/plans/2026-08-19-human-decision-gates-plan.md +5 -5
  69. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +1 -1
  70. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +1 -1
  71. package/docs/plans/2026-08-19-workflow-composition-plan.md +6 -6
  72. package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +3 -3
  73. package/docs/plans/2026-08-20-bounded-command-batches-plan.md +206 -0
  74. package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +9 -9
  75. package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +242 -0
  76. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +324 -0
  77. package/docs/plans/2026-08-21-sanity-check-plan.md +175 -0
  78. package/docs/run-bundles.md +3 -1
  79. package/docs/workflows.md +62 -19
  80. package/examples/workflows/command-batch.workflow.ts +50 -0
  81. package/examples/workflows/sanity-check.workflow.ts +1 -0
  82. package/herdr-plugin.toml +3 -3
  83. package/package.json +1 -1
  84. package/schemas/decision-presentation-v1.schema.json +1 -1
  85. package/schemas/human-decision-accepted-v1.schema.json +1 -1
  86. package/schemas/human-decision-accepted-v2.schema.json +1 -1
  87. package/schemas/human-decision-answer-attempt-v1.schema.json +1 -1
  88. package/schemas/human-decision-cancellation-v1.schema.json +1 -1
  89. package/schemas/human-decision-continuation-v1.schema.json +1 -1
  90. package/schemas/human-decision-delivery-v1.schema.json +1 -1
  91. package/schemas/human-decision-delivery-v2.schema.json +1 -1
  92. package/schemas/human-decision-receipt-v1.schema.json +1 -1
  93. package/schemas/human-decision-receipt-v2.schema.json +1 -1
  94. package/schemas/human-decision-request-v1.schema.json +1 -1
  95. package/schemas/human-decision-request-v2.schema.json +1 -1
  96. package/schemas/human-decision-resolution-v1.schema.json +1 -1
  97. package/schemas/human-decision-resolution-v2.schema.json +1 -1
  98. package/schemas/human-decision-settlement-v1.schema.json +1 -1
  99. package/skills/autodoc/SKILL.md +33 -2
  100. package/skills/autoimplement/SKILL.md +66 -12
  101. package/skills/autoplan/SKILL.md +26 -2
  102. package/skills/monitor/SKILL.md +35 -21
  103. package/skills/pi-workflows/SKILL.md +19 -5
  104. package/src/builtins/autoimplement-command-batches.ts +364 -0
  105. package/src/builtins/autoimplement.workflow.ts +1065 -261
  106. package/src/builtins/catalog.ts +3 -1
  107. package/src/builtins/index.ts +13 -0
  108. package/src/builtins/sanity-check-session.ts +205 -0
  109. package/src/builtins/sanity-check.workflow.ts +624 -0
  110. package/src/controllers/sqlite.ts +580 -7
  111. package/src/extension/decision-channels.ts +7 -7
  112. package/src/extension/deferred-turn-coordinator.ts +171 -0
  113. package/src/extension/deferred-turn.ts +166 -0
  114. package/src/extension/index.ts +359 -49
  115. package/src/extension/step-message.ts +1 -0
  116. package/src/herdr/setup.ts +4 -4
  117. package/src/workflows/command-batch.ts +254 -0
  118. package/src/workflows/engine.ts +8 -5
  119. package/src/workflows/index.ts +15 -0
  120. package/src/workflows/schema.ts +2 -1
  121. package/src/workflows/shell.ts +10 -0
  122. package/src/workflows/store.ts +3 -1
  123. package/src/workflows/types.ts +4 -3
@@ -4,12 +4,14 @@ import autoimplementWorkflow from "./autoimplement.workflow.js";
4
4
  import autoplanWorkflow from "./autoplan.workflow.js";
5
5
  import monitorWorkflow from "./monitor.workflow.js";
6
6
  import planApprovalWorkflow from "./plan-approval.workflow.js";
7
+ import sanityCheckWorkflow from "./sanity-check.workflow.js";
7
8
 
8
9
  export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
9
10
  { id: "autoplan", revision: "1", definition: autoplanWorkflow },
10
11
  { id: "autodoc", revision: "1", definition: autodocWorkflow },
11
- { id: "autoimplement", revision: "4", definition: autoimplementWorkflow },
12
+ { id: "autoimplement", revision: "6", definition: autoimplementWorkflow },
12
13
  { id: "plan-approval", revision: "2", definition: planApprovalWorkflow },
14
+ { id: "sanity-check", revision: "1", definition: sanityCheckWorkflow },
13
15
  {
14
16
  id: "monitor",
15
17
  revision: "7",
@@ -17,12 +17,25 @@ export {
17
17
  type AutoimplementInput,
18
18
  type ExistingPlanDiscovery,
19
19
  } from "./autoimplement.workflow.js";
20
+ export type { AutoimplementConcurrency } from "./autoimplement-command-batches.js";
20
21
  export {
21
22
  default as monitorWorkflow,
22
23
  type MonitorInput,
23
24
  type MonitorRepairPolicy,
24
25
  } from "./monitor.workflow.js";
25
26
  export { presentPlan } from "./plan-presentation.js";
27
+ export {
28
+ sanityCheckWorkflow,
29
+ type ContributionEvidence,
30
+ type SanityCheckArea,
31
+ type SanityCheckAreaResult,
32
+ type SanityCheckEvidence,
33
+ type SanityCheckInput,
34
+ type SanityCheckMode,
35
+ type SanityCheckResult,
36
+ type SanityCheckReview,
37
+ type SanityCheckVerdict,
38
+ } from "./sanity-check.workflow.js";
26
39
  export {
27
40
  planApprovalWorkflow,
28
41
  type PlanApprovalContinue,
@@ -0,0 +1,205 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { runCommandBatch, type CommandBatchItemResult } from "../workflows/command-batch.js";
5
+ import { extractJsonValue } from "../workflows/json.js";
6
+
7
+ const SESSION_TIMEOUT_MS = 15 * 60_000;
8
+ const SESSION_OUTPUT_CHARS = 1_000_000;
9
+ const MAX_PROMPT_CHARS = 96_000;
10
+ const MAX_ERROR_CHARS = 2_000;
11
+
12
+ export type IsolatedReviewRequest = {
13
+ id: string;
14
+ prompt: string;
15
+ };
16
+
17
+ export type PiInvocation = {
18
+ command: string;
19
+ prefixArgs: string[];
20
+ };
21
+
22
+ export type IsolatedReviewOptions = {
23
+ invocation?: PiInvocation;
24
+ timeoutMs?: number;
25
+ maxOutputChars?: number;
26
+ maxConcurrency?: number;
27
+ };
28
+
29
+ export async function runIsolatedReviewSessions(
30
+ requests: IsolatedReviewRequest[],
31
+ cwd: string,
32
+ signal: AbortSignal,
33
+ options: IsolatedReviewOptions = {},
34
+ ): Promise<Record<string, unknown>> {
35
+ if (requests.length === 0) return {};
36
+ const ids = new Set<string>();
37
+ for (const request of requests) {
38
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(request.id)) {
39
+ throw new Error(`Invalid isolated review id: ${request.id}`);
40
+ }
41
+ if (ids.has(request.id)) throw new Error(`Duplicate isolated review id: ${request.id}`);
42
+ ids.add(request.id);
43
+ if (request.prompt.length > MAX_PROMPT_CHARS) {
44
+ throw new Error(
45
+ `Isolated review prompt ${request.id} exceeds ${MAX_PROMPT_CHARS} characters`,
46
+ );
47
+ }
48
+ }
49
+
50
+ const promptDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-sanity-check-"));
51
+ try {
52
+ const invocation = options.invocation ?? resolvePiInvocation();
53
+ const items = await Promise.all(
54
+ requests.map(async (request) => {
55
+ const promptPath = path.join(promptDir, `${request.id}.md`);
56
+ await fs.writeFile(promptPath, request.prompt, { encoding: "utf8", mode: 0o600 });
57
+ return {
58
+ id: request.id,
59
+ command: invocation.command,
60
+ args: [
61
+ ...invocation.prefixArgs,
62
+ "--mode",
63
+ "json",
64
+ "--print",
65
+ "--no-session",
66
+ "--no-extensions",
67
+ "--no-skills",
68
+ "--no-context-files",
69
+ "--tools",
70
+ "read,grep,find,ls",
71
+ `@${promptPath}`,
72
+ "Follow the attached review instructions and return only the requested JSON.",
73
+ ],
74
+ cwd,
75
+ timeoutMs: options.timeoutMs ?? SESSION_TIMEOUT_MS,
76
+ maxOutputChars: options.maxOutputChars ?? SESSION_OUTPUT_CHARS,
77
+ };
78
+ }),
79
+ );
80
+ const batch = await runCommandBatch(
81
+ {
82
+ items,
83
+ maxConcurrency: options.maxConcurrency ?? requests.length,
84
+ },
85
+ { signal },
86
+ );
87
+ const outputs: Record<string, unknown> = {};
88
+ for (const item of batch.items) {
89
+ outputs[item.id] = parseSessionResult(item);
90
+ }
91
+ return outputs;
92
+ } finally {
93
+ await fs.rm(promptDir, { recursive: true, force: true });
94
+ }
95
+ }
96
+
97
+ export function resolvePiInvocation(): PiInvocation {
98
+ const currentScript = process.argv[1];
99
+ const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/");
100
+ const scriptName = currentScript === undefined ? "" : path.basename(currentScript).toLowerCase();
101
+ const isPiPackageCli =
102
+ scriptName === "cli.js" &&
103
+ currentScript?.split(path.sep).some((segment) => segment === "pi-coding-agent");
104
+ const isPiScript = /^(pi|pi\.[cm]?js)$/.test(scriptName) || isPiPackageCli;
105
+ if (currentScript && !isBunVirtualScript && isPiScript) {
106
+ return {
107
+ command: process.execPath,
108
+ prefixArgs: [currentScript, ...inheritedPiArgs(process.argv.slice(2))],
109
+ };
110
+ }
111
+ const execName = path.basename(process.execPath).toLowerCase();
112
+ if (/^pi(\.exe)?$/.test(execName)) {
113
+ return { command: process.execPath, prefixArgs: inheritedPiArgs(process.argv.slice(1)) };
114
+ }
115
+ return { command: "pi", prefixArgs: [] };
116
+ }
117
+
118
+ export function parsePiJsonOutput(stdout: string): unknown {
119
+ let finalText = "";
120
+ for (const line of stdout.split("\n")) {
121
+ if (!line.trim()) continue;
122
+ let event: Record<string, unknown>;
123
+ try {
124
+ event = JSON.parse(line) as Record<string, unknown>;
125
+ } catch {
126
+ continue;
127
+ }
128
+ if (event.type !== "message_end" || !isRecord(event.message)) continue;
129
+ const message = event.message;
130
+ if (message.role !== "assistant") continue;
131
+ if (message.stopReason === "error" || message.stopReason === "aborted") {
132
+ throw new Error(
133
+ boundedError(
134
+ typeof message.errorMessage === "string"
135
+ ? message.errorMessage
136
+ : `Pi session stopped with ${String(message.stopReason)}`,
137
+ ),
138
+ );
139
+ }
140
+ const text = messageText(message.content);
141
+ if (text.trim()) finalText = text;
142
+ }
143
+ if (!finalText) throw new Error("Isolated Pi session returned no assistant JSON output");
144
+ return extractJsonValue(finalText);
145
+ }
146
+
147
+ function parseSessionResult(result: CommandBatchItemResult): unknown {
148
+ if (result.outcome !== "succeeded") {
149
+ throw new Error(
150
+ `Isolated Pi session ${result.id} ${result.outcome}: ${boundedError(result.error ?? result.stderr)}`,
151
+ );
152
+ }
153
+ if (result.stdoutTruncated) {
154
+ throw new Error(`Isolated Pi session ${result.id} exceeded its output limit`);
155
+ }
156
+ try {
157
+ return parsePiJsonOutput(result.stdout);
158
+ } catch (error) {
159
+ throw new Error(
160
+ `Isolated Pi session ${result.id} returned invalid output: ${boundedError(errorMessage(error))}`,
161
+ );
162
+ }
163
+ }
164
+
165
+ function inheritedPiArgs(args: string[]): string[] {
166
+ const inherited: string[] = [];
167
+ for (let index = 0; index < args.length; index += 1) {
168
+ const arg = args[index];
169
+ if (arg === "--offline") {
170
+ inherited.push(arg);
171
+ continue;
172
+ }
173
+ if (arg !== "--provider" && arg !== "--model" && arg !== "--thinking") continue;
174
+ const value = args[index + 1];
175
+ if (value !== undefined) {
176
+ inherited.push(arg, value);
177
+ index += 1;
178
+ }
179
+ }
180
+ return inherited;
181
+ }
182
+
183
+ function messageText(content: unknown): string {
184
+ if (typeof content === "string") return content;
185
+ if (!Array.isArray(content)) return "";
186
+ return content
187
+ .map((part) =>
188
+ isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "",
189
+ )
190
+ .filter(Boolean)
191
+ .join("\n");
192
+ }
193
+
194
+ function isRecord(value: unknown): value is Record<string, unknown> {
195
+ return value !== null && typeof value === "object" && !Array.isArray(value);
196
+ }
197
+
198
+ function errorMessage(error: unknown): string {
199
+ return error instanceof Error ? error.message : String(error);
200
+ }
201
+
202
+ function boundedError(message: string): string {
203
+ const value = message.trim() || "unknown failure";
204
+ return value.length <= MAX_ERROR_CHARS ? value : `${value.slice(0, MAX_ERROR_CHARS)}…`;
205
+ }