@osolmaz/pi-workflows 0.10.0 → 0.11.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.
- package/README.md +23 -12
- package/dist/builtins/autodoc.workflow.d.ts +58 -0
- package/dist/builtins/autodoc.workflow.js +266 -0
- package/dist/builtins/autodoc.workflow.js.map +1 -0
- package/dist/builtins/autoimplement.workflow.d.ts +210 -11
- package/dist/builtins/autoimplement.workflow.js +245 -13
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/{autodevise.workflow.d.ts → autoplan.workflow.d.ts} +9 -9
- package/dist/builtins/{autodevise.workflow.js → autoplan.workflow.js} +20 -20
- package/dist/builtins/autoplan.workflow.js.map +1 -0
- package/dist/builtins/catalog.js +8 -4
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +6 -3
- package/dist/builtins/index.js +5 -2
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/monitor.workflow.d.ts +4 -0
- package/dist/builtins/monitor.workflow.js +134 -9
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +83 -0
- package/dist/builtins/plan-approval.workflow.js +148 -0
- package/dist/builtins/plan-approval.workflow.js.map +1 -0
- package/dist/builtins/plan-presentation.d.ts +7 -0
- package/dist/builtins/plan-presentation.js +44 -0
- package/dist/builtins/plan-presentation.js.map +1 -0
- package/dist/extension/decision-channels.d.ts +132 -0
- package/dist/extension/decision-channels.js +1082 -0
- package/dist/extension/decision-channels.js.map +1 -0
- package/dist/extension/index.d.ts +1 -0
- package/dist/extension/index.js +426 -11
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/widget.js +26 -2
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-tool.d.ts +1 -37
- package/dist/extension/workflow-tool.js +1 -42
- package/dist/extension/workflow-tool.js.map +1 -1
- package/dist/host/rpc-bridge.js +4 -21
- package/dist/host/rpc-bridge.js.map +1 -1
- package/dist/render/graph-render.js +33 -8
- package/dist/render/graph-render.js.map +1 -1
- package/dist/viewer/render.js +36 -2
- package/dist/viewer/render.js.map +1 -1
- package/dist/workflows/composition.js +15 -1
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/decision-presentation.d.ts +18 -0
- package/dist/workflows/decision-presentation.js +417 -0
- package/dist/workflows/decision-presentation.js.map +1 -0
- package/dist/workflows/engine.d.ts +2 -1
- package/dist/workflows/engine.js +86 -4
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +87 -0
- package/dist/workflows/human-decision.js +583 -0
- package/dist/workflows/human-decision.js.map +1 -0
- package/dist/workflows/index.d.ts +3 -1
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +15 -0
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.js +7 -0
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +72 -0
- package/dist/workflows/tool-input.js +141 -0
- package/dist/workflows/tool-input.js.map +1 -0
- package/dist/workflows/types.d.ts +223 -0
- package/docs/HUMAN_DECISIONS.md +371 -0
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
- package/docs/MONITOR.md +17 -6
- package/docs/WORKFLOW_COMPOSITION.md +13 -6
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
- package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
- package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
- package/docs/plans/2026-08-19-workflow-composition-plan.md +25 -17
- package/docs/run-bundles.md +23 -2
- package/docs/workflows.md +43 -5
- package/examples/workflows/approved-plan.workflow.ts +58 -0
- package/examples/workflows/autoplan.workflow.ts +1 -0
- package/examples/workflows/human-decision.workflow.ts +62 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +2 -1
- package/schemas/decision-presentation-v1.schema.json +83 -0
- package/schemas/human-decision-accepted-v1.schema.json +44 -0
- package/schemas/human-decision-accepted-v2.schema.json +50 -0
- package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
- package/schemas/human-decision-cancellation-v1.schema.json +15 -0
- package/schemas/human-decision-continuation-v1.schema.json +16 -0
- package/schemas/human-decision-delivery-v1.schema.json +28 -0
- package/schemas/human-decision-delivery-v2.schema.json +36 -0
- package/schemas/human-decision-receipt-v1.schema.json +33 -0
- package/schemas/human-decision-receipt-v2.schema.json +39 -0
- package/schemas/human-decision-request-v1.schema.json +59 -0
- package/schemas/human-decision-request-v2.schema.json +69 -0
- package/schemas/human-decision-resolution-v1.schema.json +27 -0
- package/schemas/human-decision-resolution-v2.schema.json +27 -0
- package/schemas/human-decision-settlement-v1.schema.json +28 -0
- package/skills/autodoc/SKILL.md +43 -0
- package/skills/autoimplement/SKILL.md +58 -0
- package/skills/autoimplement/agents/openai.yaml +4 -0
- package/skills/autoplan/SKILL.md +25 -0
- package/skills/monitor/SKILL.md +3 -1
- package/skills/pi-workflows/SKILL.md +3 -3
- package/src/builtins/autodoc.workflow.ts +325 -0
- package/src/builtins/autoimplement.workflow.ts +281 -14
- package/src/builtins/{autodevise.workflow.ts → autoplan.workflow.ts} +32 -32
- package/src/builtins/catalog.ts +8 -4
- package/src/builtins/index.ts +25 -6
- package/src/builtins/monitor.workflow.ts +162 -8
- package/src/builtins/plan-approval.workflow.ts +190 -0
- package/src/builtins/plan-presentation.ts +57 -0
- package/src/extension/decision-channels.ts +1533 -0
- package/src/extension/index.ts +499 -9
- package/src/extension/widget.ts +39 -2
- package/src/extension/workflow-tool.ts +5 -81
- package/src/host/rpc-bridge.ts +7 -30
- package/src/render/graph-render.ts +40 -3
- package/src/viewer/render.ts +56 -3
- package/src/workflows/composition.ts +16 -1
- package/src/workflows/decision-presentation.ts +501 -0
- package/src/workflows/engine.ts +108 -4
- package/src/workflows/human-decision.ts +819 -0
- package/src/workflows/index.ts +69 -0
- package/src/workflows/schema.ts +17 -0
- package/src/workflows/store.ts +8 -0
- package/src/workflows/tool-input.ts +202 -0
- package/src/workflows/types.ts +265 -0
- package/dist/builtins/autodevise.workflow.js.map +0 -1
- package/examples/workflows/autodevise.workflow.ts +0 -1
|
@@ -7,12 +7,15 @@ import {
|
|
|
7
7
|
includedResult,
|
|
8
8
|
shell,
|
|
9
9
|
} from "../workflows/definition.js";
|
|
10
|
+
import { digest } from "../workflows/human-decision.js";
|
|
10
11
|
import type {
|
|
11
12
|
ShellActionExecution,
|
|
12
13
|
ShellActionResult,
|
|
13
14
|
WorkflowNodeContext,
|
|
14
15
|
} from "../workflows/types.js";
|
|
15
|
-
import
|
|
16
|
+
import autodocWorkflow, { type AutodocInput } from "./autodoc.workflow.js";
|
|
17
|
+
import autoplanWorkflow, { type AutoplanInput } from "./autoplan.workflow.js";
|
|
18
|
+
import planApprovalWorkflow, { type PlanApprovalInput } from "./plan-approval.workflow.js";
|
|
16
19
|
|
|
17
20
|
export type AutoimplementInput = {
|
|
18
21
|
task: string;
|
|
@@ -22,6 +25,25 @@ export type AutoimplementInput = {
|
|
|
22
25
|
repository?: string;
|
|
23
26
|
baseBranch?: string;
|
|
24
27
|
merge?: boolean;
|
|
28
|
+
documents?: string[];
|
|
29
|
+
documentation?: {
|
|
30
|
+
status: "current";
|
|
31
|
+
planDigest: string;
|
|
32
|
+
documents: string[];
|
|
33
|
+
};
|
|
34
|
+
approval?: {
|
|
35
|
+
audience: string;
|
|
36
|
+
maxReplans: number;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type ExistingPlanDiscovery = {
|
|
41
|
+
route: "found" | "blocked";
|
|
42
|
+
plan?: unknown;
|
|
43
|
+
documentation?: "current" | "missing" | "stale";
|
|
44
|
+
documents: string[];
|
|
45
|
+
reason: string;
|
|
46
|
+
evidence: unknown;
|
|
25
47
|
};
|
|
26
48
|
|
|
27
49
|
type StructuredCommand = {
|
|
@@ -94,6 +116,51 @@ function parseInput(value: unknown): AutoimplementInput {
|
|
|
94
116
|
if (input.merge !== undefined && typeof input.merge !== "boolean") {
|
|
95
117
|
throw new Error("autoimplement merge must be a boolean");
|
|
96
118
|
}
|
|
119
|
+
const documents = input.documents;
|
|
120
|
+
if (
|
|
121
|
+
documents !== undefined &&
|
|
122
|
+
(!Array.isArray(documents) || documents.some((item) => typeof item !== "string"))
|
|
123
|
+
) {
|
|
124
|
+
throw new Error("autoimplement documents must be an array of strings");
|
|
125
|
+
}
|
|
126
|
+
let documentation: AutoimplementInput["documentation"];
|
|
127
|
+
if (input.documentation !== undefined) {
|
|
128
|
+
if (input.plan === undefined) {
|
|
129
|
+
throw new Error("autoimplement documentation requires an explicit plan");
|
|
130
|
+
}
|
|
131
|
+
const raw = requireRecord(input.documentation, "autoimplement documentation");
|
|
132
|
+
if (raw.status !== "current") {
|
|
133
|
+
throw new Error("autoimplement documentation status must be current");
|
|
134
|
+
}
|
|
135
|
+
const planDigest = requireString(raw.planDigest, "autoimplement documentation planDigest");
|
|
136
|
+
if (planDigest !== digest(input.plan)) {
|
|
137
|
+
throw new Error("autoimplement documentation planDigest does not match the explicit plan");
|
|
138
|
+
}
|
|
139
|
+
if (!Array.isArray(raw.documents) || raw.documents.some((item) => typeof item !== "string")) {
|
|
140
|
+
throw new Error("autoimplement documentation documents must be an array of strings");
|
|
141
|
+
}
|
|
142
|
+
documentation = {
|
|
143
|
+
status: "current",
|
|
144
|
+
planDigest,
|
|
145
|
+
documents: [...raw.documents] as string[],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
let approval: AutoimplementInput["approval"];
|
|
149
|
+
if (input.approval !== undefined) {
|
|
150
|
+
const raw = requireRecord(input.approval, "autoimplement approval");
|
|
151
|
+
const maxReplans = raw.maxReplans ?? 3;
|
|
152
|
+
if (
|
|
153
|
+
!Number.isInteger(maxReplans) ||
|
|
154
|
+
(maxReplans as number) < 1 ||
|
|
155
|
+
(maxReplans as number) > 20
|
|
156
|
+
) {
|
|
157
|
+
throw new Error("autoimplement approval maxReplans must be from 1 through 20");
|
|
158
|
+
}
|
|
159
|
+
approval = {
|
|
160
|
+
audience: requireString(raw.audience, "autoimplement approval audience"),
|
|
161
|
+
maxReplans: maxReplans as number,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
97
164
|
return {
|
|
98
165
|
task: requireString(input.task, "autoimplement task"),
|
|
99
166
|
...(input.plan !== undefined ? { plan: input.plan } : {}),
|
|
@@ -106,6 +173,42 @@ function parseInput(value: unknown): AutoimplementInput {
|
|
|
106
173
|
? { baseBranch: requireString(input.baseBranch, "baseBranch") }
|
|
107
174
|
: {}),
|
|
108
175
|
merge: input.merge === true,
|
|
176
|
+
...(documents !== undefined ? { documents: [...documents] as string[] } : {}),
|
|
177
|
+
...(documentation !== undefined ? { documentation } : {}),
|
|
178
|
+
...(approval !== undefined ? { approval } : {}),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function parseExistingPlan(value: unknown): ExistingPlanDiscovery {
|
|
183
|
+
const result = requireRecord(value, "existing plan discovery");
|
|
184
|
+
if (result.route !== "found" && result.route !== "blocked") {
|
|
185
|
+
throw new Error("existing plan discovery route must be found or blocked");
|
|
186
|
+
}
|
|
187
|
+
if (result.route === "found") {
|
|
188
|
+
if (result.plan === undefined) throw new Error("found plan must include plan");
|
|
189
|
+
if (
|
|
190
|
+
result.documentation !== "current" &&
|
|
191
|
+
result.documentation !== "missing" &&
|
|
192
|
+
result.documentation !== "stale"
|
|
193
|
+
) {
|
|
194
|
+
throw new Error("found plan documentation must be current, missing, or stale");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (
|
|
198
|
+
!Array.isArray(result.documents) ||
|
|
199
|
+
result.documents.some((item) => typeof item !== "string")
|
|
200
|
+
) {
|
|
201
|
+
throw new Error("existing plan documents must be an array of strings");
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
route: result.route,
|
|
205
|
+
...(result.plan !== undefined ? { plan: result.plan } : {}),
|
|
206
|
+
...(result.documentation !== undefined
|
|
207
|
+
? { documentation: result.documentation as "current" | "missing" | "stale" }
|
|
208
|
+
: {}),
|
|
209
|
+
documents: [...result.documents] as string[],
|
|
210
|
+
reason: requireString(result.reason, "existing plan discovery reason"),
|
|
211
|
+
evidence: result.evidence ?? null,
|
|
109
212
|
};
|
|
110
213
|
}
|
|
111
214
|
|
|
@@ -210,10 +313,43 @@ function latestCiCommand(context: WorkflowNodeContext): StructuredCommand {
|
|
|
210
313
|
|
|
211
314
|
function currentPlan(context: WorkflowNodeContext): unknown {
|
|
212
315
|
const adopted = context.outputs.adoptPlan as { plan?: unknown } | undefined;
|
|
213
|
-
|
|
316
|
+
if (adopted?.plan !== undefined) return adopted.plan;
|
|
317
|
+
const documented = context.outputs.documentation as
|
|
318
|
+
| { exit?: string; output?: { plan?: unknown } }
|
|
319
|
+
| undefined;
|
|
320
|
+
if (documented?.exit === "ready" && documented.output?.plan !== undefined) {
|
|
321
|
+
return documented.output.plan;
|
|
322
|
+
}
|
|
323
|
+
const discovered = context.outputs.findPlan as ExistingPlanDiscovery | undefined;
|
|
324
|
+
if (discovered?.route === "found" && discovered.plan !== undefined) return discovered.plan;
|
|
325
|
+
return (context.input as AutoimplementInput).plan;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function currentPlanDigest(context: WorkflowNodeContext): string {
|
|
329
|
+
const adopted = context.outputs.adoptPlan as { planDigest?: unknown } | undefined;
|
|
330
|
+
if (typeof adopted?.planDigest === "string") return adopted.planDigest;
|
|
331
|
+
const documented = context.outputs.documentation as
|
|
332
|
+
| { exit?: string; output?: { planDigest?: unknown } }
|
|
333
|
+
| undefined;
|
|
334
|
+
if (documented?.exit === "ready" && typeof documented.output?.planDigest === "string") {
|
|
335
|
+
return documented.output.planDigest;
|
|
336
|
+
}
|
|
337
|
+
const plan = currentPlan(context);
|
|
338
|
+
if (plan === undefined) throw new Error("autoimplement does not have a selected plan");
|
|
339
|
+
return digest(plan);
|
|
214
340
|
}
|
|
215
341
|
|
|
216
342
|
function latestIssue(context: WorkflowNodeContext): unknown {
|
|
343
|
+
const approval = context.outputs.approval as
|
|
344
|
+
| { exit?: string; output?: { instructions?: unknown } }
|
|
345
|
+
| undefined;
|
|
346
|
+
if (approval?.exit === "replan" && typeof approval.output?.instructions === "string") {
|
|
347
|
+
return {
|
|
348
|
+
source: "human-replan",
|
|
349
|
+
instructions: approval.output.instructions,
|
|
350
|
+
priorPlanDigest: currentPlanDigest(context),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
217
353
|
const ids = [
|
|
218
354
|
"classifyImplementation",
|
|
219
355
|
"classifyVerification",
|
|
@@ -287,7 +423,11 @@ function latestBlockedReason(context: WorkflowNodeContext): { reason: string; ev
|
|
|
287
423
|
"classifyImplementation",
|
|
288
424
|
"classifyVerification",
|
|
289
425
|
"triageReview",
|
|
426
|
+
"replanGuard",
|
|
290
427
|
"adoptPlan",
|
|
428
|
+
"findPlan",
|
|
429
|
+
"documentation",
|
|
430
|
+
"approval",
|
|
291
431
|
];
|
|
292
432
|
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
293
433
|
const step = context.state.steps[index];
|
|
@@ -311,12 +451,48 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
311
451
|
presentationPrompt:
|
|
312
452
|
"Summarize what was implemented, the review rounds by severity, the CI result, the PR or merge result, and any remaining limitation. Include exact validation commands.",
|
|
313
453
|
startAt: "prepare",
|
|
314
|
-
maxSteps:
|
|
454
|
+
maxSteps: 240,
|
|
315
455
|
includes: {
|
|
456
|
+
documentation: includeWorkflow(autodocWorkflow, {
|
|
457
|
+
input: (context): AutodocInput => {
|
|
458
|
+
const request = context.input as AutoimplementInput;
|
|
459
|
+
const discovery = context.outputs.findPlan as ExistingPlanDiscovery | undefined;
|
|
460
|
+
const plan = currentPlan(context);
|
|
461
|
+
if (plan === undefined) throw new Error("autoimplement documentation is missing a plan");
|
|
462
|
+
return {
|
|
463
|
+
task: request.task,
|
|
464
|
+
plan,
|
|
465
|
+
...(request.repository !== undefined ? { repository: request.repository } : {}),
|
|
466
|
+
documents:
|
|
467
|
+
request.documents ?? request.documentation?.documents ?? discovery?.documents ?? [],
|
|
468
|
+
evidence: latestIssue(context),
|
|
469
|
+
};
|
|
470
|
+
},
|
|
471
|
+
}),
|
|
472
|
+
approval: includeWorkflow(planApprovalWorkflow, {
|
|
473
|
+
input: (context): PlanApprovalInput => {
|
|
474
|
+
const request = context.input as AutoimplementInput;
|
|
475
|
+
if (request.approval === undefined) {
|
|
476
|
+
throw new Error("autoimplement approval was entered without an approval policy");
|
|
477
|
+
}
|
|
478
|
+
const plan = currentPlan(context);
|
|
479
|
+
if (plan === undefined) throw new Error("autoimplement approval is missing a plan");
|
|
480
|
+
const revisions = context.state.steps.filter(
|
|
481
|
+
(step) => step.nodeId === "approval/approve",
|
|
482
|
+
).length;
|
|
483
|
+
return {
|
|
484
|
+
task: request.task,
|
|
485
|
+
plan,
|
|
486
|
+
planDigest: currentPlanDigest(context),
|
|
487
|
+
audience: request.approval.audience,
|
|
488
|
+
revision: revisions + 1,
|
|
489
|
+
};
|
|
490
|
+
},
|
|
491
|
+
}),
|
|
316
492
|
redesign: includeWorkflow({
|
|
317
|
-
workflow: "
|
|
318
|
-
contract:
|
|
319
|
-
input: (context):
|
|
493
|
+
workflow: "autoplan",
|
|
494
|
+
contract: autoplanWorkflow,
|
|
495
|
+
input: (context): AutoplanInput => {
|
|
320
496
|
const request = context.input as AutoimplementInput;
|
|
321
497
|
return {
|
|
322
498
|
problem: request.task,
|
|
@@ -340,25 +516,87 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
340
516
|
},
|
|
341
517
|
nodes: {
|
|
342
518
|
prepare: compute({
|
|
343
|
-
run: ({ input }) =>
|
|
344
|
-
|
|
345
|
-
|
|
519
|
+
run: ({ input }) => {
|
|
520
|
+
const request = input as AutoimplementInput;
|
|
521
|
+
return {
|
|
522
|
+
route:
|
|
523
|
+
request.plan === undefined
|
|
524
|
+
? "find"
|
|
525
|
+
: request.documentation?.status === "current"
|
|
526
|
+
? "ready"
|
|
527
|
+
: "document",
|
|
528
|
+
};
|
|
529
|
+
},
|
|
530
|
+
}),
|
|
531
|
+
findPlan: agent({
|
|
532
|
+
statusDetail: "finding existing plan",
|
|
533
|
+
prompt: ({ input }) => {
|
|
534
|
+
const request = input as AutoimplementInput;
|
|
535
|
+
return [
|
|
536
|
+
"Find the clear plan that has already been selected for this task.",
|
|
537
|
+
"Use the current conversation context and referenced canonical documents.",
|
|
538
|
+
"Do not devise, improve, replace, document, or implement a plan.",
|
|
539
|
+
"Return blocked when no single clear existing plan can be found.",
|
|
540
|
+
"Report whether its canonical documentation is current, missing, or stale.",
|
|
541
|
+
`Task: ${request.task}`,
|
|
542
|
+
`Repository: ${request.repository ?? "current repository"}`,
|
|
543
|
+
`Referenced documents: ${JSON.stringify(request.documents ?? [])}`,
|
|
544
|
+
].join("\n");
|
|
545
|
+
},
|
|
546
|
+
expectedOutput:
|
|
547
|
+
'{ "route": "found" | "blocked", "plan": {} (required when found), "documentation": "current" | "missing" | "stale" (required when found), "documents": ["canonical file"], "reason": "reason", "evidence": "evidence" }',
|
|
548
|
+
validate: parseExistingPlan,
|
|
549
|
+
}),
|
|
550
|
+
routeFoundPlan: compute({
|
|
551
|
+
run: ({ outputs }) => {
|
|
552
|
+
const discovered = outputs.findPlan as ExistingPlanDiscovery;
|
|
553
|
+
if (discovered.route !== "found" || discovered.plan === undefined) {
|
|
554
|
+
return { route: "blocked", reason: discovered.reason, evidence: discovered.evidence };
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
route: discovered.documentation === "current" ? "ready" : "document",
|
|
558
|
+
plan: discovered.plan,
|
|
559
|
+
reason: discovered.reason,
|
|
560
|
+
evidence: discovered.evidence,
|
|
561
|
+
};
|
|
562
|
+
},
|
|
346
563
|
}),
|
|
347
564
|
adoptPlan: compute({
|
|
348
565
|
run: ({ outputs }) => {
|
|
349
|
-
const result = includedResult(
|
|
566
|
+
const result = includedResult(autoplanWorkflow, outputs.redesign);
|
|
350
567
|
if (result.exit !== "ready") throw new Error("redesign did not return a ready plan");
|
|
351
568
|
return {
|
|
352
|
-
route: result.output.changed ? "
|
|
569
|
+
route: result.output.changed ? "document" : "blocked",
|
|
353
570
|
plan: result.output.plan,
|
|
354
571
|
planDigest: result.output.planDigest,
|
|
355
572
|
changed: result.output.changed,
|
|
356
573
|
reason: result.output.changed
|
|
357
|
-
? "The plan changed in response to new evidence."
|
|
574
|
+
? "The plan changed in response to new evidence and must be documented."
|
|
358
575
|
: "Redesign returned the same plan for the same unresolved evidence.",
|
|
359
576
|
};
|
|
360
577
|
},
|
|
361
578
|
}),
|
|
579
|
+
maybeApproval: compute({
|
|
580
|
+
run: ({ input }) => ({
|
|
581
|
+
route: (input as AutoimplementInput).approval === undefined ? "implement" : "approve",
|
|
582
|
+
}),
|
|
583
|
+
}),
|
|
584
|
+
replanGuard: compute({
|
|
585
|
+
run: (context) => {
|
|
586
|
+
const request = context.input as AutoimplementInput;
|
|
587
|
+
const limit = request.approval?.maxReplans ?? 3;
|
|
588
|
+
const replans = context.state.steps.filter(
|
|
589
|
+
(step) => step.nodeId === "approval/replan",
|
|
590
|
+
).length;
|
|
591
|
+
return replans > limit
|
|
592
|
+
? {
|
|
593
|
+
route: "blocked",
|
|
594
|
+
reason: `Plan approval reached the ${limit}-replan safety limit.`,
|
|
595
|
+
evidence: context.outputs.approval,
|
|
596
|
+
}
|
|
597
|
+
: { route: "redesign", replans, limit };
|
|
598
|
+
},
|
|
599
|
+
}),
|
|
362
600
|
implement: agent({
|
|
363
601
|
timeoutMs: 60 * 60_000,
|
|
364
602
|
statusDetail: "implementing",
|
|
@@ -367,6 +605,8 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
367
605
|
return [
|
|
368
606
|
`Implement this task end-to-end: ${request.task}`,
|
|
369
607
|
`Plan: ${JSON.stringify(currentPlan(context))}`,
|
|
608
|
+
`Authorized scope: ${request.scope ?? request.repository ?? "the current repository and task"}`,
|
|
609
|
+
`Constraints: ${JSON.stringify(request.constraints ?? [])}`,
|
|
370
610
|
"Follow repository instructions and use the most elegant long-term production-ready implementation without unnecessary work.",
|
|
371
611
|
"If implementation exposes a new design or scope problem, report it precisely instead of forcing the old plan.",
|
|
372
612
|
"Do not merge yet.",
|
|
@@ -728,13 +968,40 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
728
968
|
edges: [
|
|
729
969
|
{
|
|
730
970
|
from: "prepare",
|
|
731
|
-
switch: {
|
|
971
|
+
switch: {
|
|
972
|
+
on: "$.route",
|
|
973
|
+
cases: { find: "findPlan", document: "documentation", ready: "maybeApproval" },
|
|
974
|
+
},
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
from: "findPlan",
|
|
978
|
+
switch: { on: "$.route", cases: { found: "routeFoundPlan", blocked: "blocked" } },
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
from: "routeFoundPlan",
|
|
982
|
+
switch: {
|
|
983
|
+
on: "$.route",
|
|
984
|
+
cases: { ready: "maybeApproval", document: "documentation", blocked: "blocked" },
|
|
985
|
+
},
|
|
732
986
|
},
|
|
733
987
|
{ from: "redesign.ready", to: "adoptPlan" },
|
|
734
988
|
{ from: "redesign.blocked", to: "blocked" },
|
|
735
989
|
{
|
|
736
990
|
from: "adoptPlan",
|
|
737
|
-
switch: { on: "$.route", cases: {
|
|
991
|
+
switch: { on: "$.route", cases: { document: "documentation", blocked: "blocked" } },
|
|
992
|
+
},
|
|
993
|
+
{ from: "documentation.ready", to: "maybeApproval" },
|
|
994
|
+
{ from: "documentation.blocked", to: "blocked" },
|
|
995
|
+
{
|
|
996
|
+
from: "maybeApproval",
|
|
997
|
+
switch: { on: "$.route", cases: { approve: "approval", implement: "implement" } },
|
|
998
|
+
},
|
|
999
|
+
{ from: "approval.continue", to: "implement" },
|
|
1000
|
+
{ from: "approval.stop", to: "blocked" },
|
|
1001
|
+
{ from: "approval.replan", to: "replanGuard" },
|
|
1002
|
+
{
|
|
1003
|
+
from: "replanGuard",
|
|
1004
|
+
switch: { on: "$.route", cases: { redesign: "redesign", blocked: "blocked" } },
|
|
738
1005
|
},
|
|
739
1006
|
{ from: "implement", to: "classifyImplementation" },
|
|
740
1007
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { agent, compute, defineWorkflow } from "../workflows/definition.js";
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type AutoplanInput = {
|
|
5
5
|
problem: string;
|
|
6
6
|
scope?: string;
|
|
7
7
|
constraints?: string[];
|
|
@@ -9,7 +9,7 @@ export type AutodeviseInput = {
|
|
|
9
9
|
newEvidence?: unknown;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export type
|
|
12
|
+
export type AutoplanReady = {
|
|
13
13
|
status: "ready";
|
|
14
14
|
frame: unknown;
|
|
15
15
|
proposal: unknown;
|
|
@@ -21,7 +21,7 @@ export type AutodeviseReady = {
|
|
|
21
21
|
changed: boolean;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export type
|
|
24
|
+
export type AutoplanBlocked = {
|
|
25
25
|
status: "blocked";
|
|
26
26
|
frame: unknown;
|
|
27
27
|
proposal: unknown;
|
|
@@ -44,18 +44,18 @@ function requireString(value: unknown, label: string): string {
|
|
|
44
44
|
return value.trim();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function parseInput(value: unknown):
|
|
48
|
-
const input = requireRecord(value, "
|
|
47
|
+
function parseInput(value: unknown): AutoplanInput {
|
|
48
|
+
const input = requireRecord(value, "autoplan input");
|
|
49
49
|
const constraints = input.constraints;
|
|
50
50
|
if (
|
|
51
51
|
constraints !== undefined &&
|
|
52
52
|
(!Array.isArray(constraints) || constraints.some((item) => typeof item !== "string"))
|
|
53
53
|
) {
|
|
54
|
-
throw new Error("
|
|
54
|
+
throw new Error("autoplan constraints must be an array of strings");
|
|
55
55
|
}
|
|
56
56
|
return {
|
|
57
|
-
problem: requireString(input.problem, "
|
|
58
|
-
...(input.scope !== undefined ? { scope: requireString(input.scope, "
|
|
57
|
+
problem: requireString(input.problem, "autoplan problem"),
|
|
58
|
+
...(input.scope !== undefined ? { scope: requireString(input.scope, "autoplan scope") } : {}),
|
|
59
59
|
...(constraints !== undefined ? { constraints: [...constraints] as string[] } : {}),
|
|
60
60
|
...(input.previousPlan !== undefined ? { previousPlan: input.previousPlan } : {}),
|
|
61
61
|
...(input.newEvidence !== undefined ? { newEvidence: input.newEvidence } : {}),
|
|
@@ -63,13 +63,13 @@ function parseInput(value: unknown): AutodeviseInput {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function parseSelection(value: unknown): Record<string, unknown> {
|
|
66
|
-
const selection = requireRecord(value, "
|
|
66
|
+
const selection = requireRecord(value, "autoplan selection");
|
|
67
67
|
if (selection.status !== "ready" && selection.status !== "blocked") {
|
|
68
|
-
throw new Error("
|
|
68
|
+
throw new Error("autoplan selection status must be ready or blocked");
|
|
69
69
|
}
|
|
70
|
-
requireString(selection.selected, "
|
|
71
|
-
requireString(selection.why, "
|
|
72
|
-
if (selection.status === "blocked") requireString(selection.blocker, "
|
|
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
73
|
return selection;
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -77,12 +77,12 @@ function digest(value: unknown): string {
|
|
|
77
77
|
return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export const
|
|
80
|
+
export const autoplanWorkflow = defineWorkflow({
|
|
81
81
|
source: import.meta.url,
|
|
82
|
-
contractId: "pi-workflows.
|
|
83
|
-
name: "
|
|
82
|
+
contractId: "pi-workflows.autoplan.v1",
|
|
83
|
+
name: "autoplan",
|
|
84
84
|
input: parseInput,
|
|
85
|
-
title: ({ input }) => `
|
|
85
|
+
title: ({ input }) => `autoplan: ${input.problem.slice(0, 60)}`,
|
|
86
86
|
presentationPrompt: [
|
|
87
87
|
"Present the selected practical solution and its implementation plan.",
|
|
88
88
|
"Briefly state how the ideal informed the choice and what was excluded as outside scope.",
|
|
@@ -93,18 +93,18 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
93
93
|
exits: {
|
|
94
94
|
ready: {
|
|
95
95
|
from: "finalize",
|
|
96
|
-
validate: (value: unknown):
|
|
96
|
+
validate: (value: unknown): AutoplanReady => value as AutoplanReady,
|
|
97
97
|
},
|
|
98
98
|
blocked: {
|
|
99
99
|
from: "blocked",
|
|
100
|
-
validate: (value: unknown):
|
|
100
|
+
validate: (value: unknown): AutoplanBlocked => value as AutoplanBlocked,
|
|
101
101
|
},
|
|
102
102
|
},
|
|
103
103
|
nodes: {
|
|
104
104
|
frame: agent({
|
|
105
105
|
statusDetail: "framing the problem",
|
|
106
106
|
prompt: ({ input }) => {
|
|
107
|
-
const request = input as
|
|
107
|
+
const request = input as AutoplanInput;
|
|
108
108
|
return [
|
|
109
109
|
`Frame this problem: ${request.problem}`,
|
|
110
110
|
`Authorized scope: ${request.scope ?? "infer it conservatively from the request and current project"}.`,
|
|
@@ -116,7 +116,7 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
116
116
|
].join("\n");
|
|
117
117
|
},
|
|
118
118
|
expectedOutput: `{ "problem": "concise statement", "success": ["criterion"], "inScope": ["change"], "outOfScope": ["change"], "constraints": ["constraint"], "controlBoundary": "what can change" }`,
|
|
119
|
-
validate: (value) => requireRecord(value, "
|
|
119
|
+
validate: (value) => requireRecord(value, "autoplan frame"),
|
|
120
120
|
}),
|
|
121
121
|
propose: agent({
|
|
122
122
|
statusDetail: "devising a solution",
|
|
@@ -129,7 +129,7 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
129
129
|
`Problem frame: ${JSON.stringify(outputs.frame)}`,
|
|
130
130
|
].join("\n"),
|
|
131
131
|
expectedOutput: `{ "solution": "proposal", "rationale": "why", "parts": ["part"], "tradeoffs": ["trade-off"] }`,
|
|
132
|
-
validate: (value) => requireRecord(value, "
|
|
132
|
+
validate: (value) => requireRecord(value, "autoplan proposal"),
|
|
133
133
|
}),
|
|
134
134
|
ideal: agent({
|
|
135
135
|
statusDetail: "describing the ideal end state",
|
|
@@ -141,10 +141,10 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
141
141
|
"Explain the practical value beyond the proposal.",
|
|
142
142
|
`Problem frame: ${JSON.stringify(outputs.frame)}`,
|
|
143
143
|
`Proposal: ${JSON.stringify(outputs.propose)}`,
|
|
144
|
-
`New evidence: ${JSON.stringify((input as
|
|
144
|
+
`New evidence: ${JSON.stringify((input as AutoplanInput).newEvidence ?? null)}`,
|
|
145
145
|
].join("\n"),
|
|
146
146
|
expectedOutput: `{ "ideal": "ideal end state", "outsideDependencies": ["dependency"], "additionalValue": ["benefit"] }`,
|
|
147
|
-
validate: (value) => requireRecord(value, "
|
|
147
|
+
validate: (value) => requireRecord(value, "autoplan ideal"),
|
|
148
148
|
}),
|
|
149
149
|
choose: agent({
|
|
150
150
|
statusDetail: "choosing the practical solution",
|
|
@@ -177,11 +177,11 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
177
177
|
"Do not implement the plan.",
|
|
178
178
|
`Frame: ${JSON.stringify(outputs.frame)}`,
|
|
179
179
|
`Selection: ${JSON.stringify(outputs.choose)}`,
|
|
180
|
-
`Previous plan: ${JSON.stringify((input as
|
|
181
|
-
`New evidence: ${JSON.stringify((input as
|
|
180
|
+
`Previous plan: ${JSON.stringify((input as AutoplanInput).previousPlan ?? null)}`,
|
|
181
|
+
`New evidence: ${JSON.stringify((input as AutoplanInput).newEvidence ?? null)}`,
|
|
182
182
|
].join("\n"),
|
|
183
183
|
expectedOutput: `{ "summary": "approach", "steps": [{ "change": "change", "where": "location", "verification": "evidence" }], "contracts": ["impact"], "tests": ["test"], "risks": [{ "risk": "risk", "mitigation": "mitigation" }], "boundaries": ["excluded work"] }`,
|
|
184
|
-
validate: (value) => requireRecord(value, "
|
|
184
|
+
validate: (value) => requireRecord(value, "autoplan plan"),
|
|
185
185
|
}),
|
|
186
186
|
blocked: compute({
|
|
187
187
|
run: ({ outputs }) => {
|
|
@@ -192,13 +192,13 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
192
192
|
proposal: outputs.propose,
|
|
193
193
|
ideal: outputs.ideal,
|
|
194
194
|
selection,
|
|
195
|
-
reason: requireString(selection.blocker, "
|
|
196
|
-
} satisfies
|
|
195
|
+
reason: requireString(selection.blocker, "autoplan blocker"),
|
|
196
|
+
} satisfies AutoplanBlocked;
|
|
197
197
|
},
|
|
198
198
|
}),
|
|
199
199
|
finalize: compute({
|
|
200
200
|
run: ({ outputs, input }) => {
|
|
201
|
-
const request = input as
|
|
201
|
+
const request = input as AutoplanInput;
|
|
202
202
|
const planDigest = digest(outputs.plan);
|
|
203
203
|
const previousPlanDigest =
|
|
204
204
|
request.previousPlan === undefined ? undefined : digest(request.previousPlan);
|
|
@@ -212,7 +212,7 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
212
212
|
planDigest,
|
|
213
213
|
...(previousPlanDigest !== undefined ? { previousPlanDigest } : {}),
|
|
214
214
|
changed: previousPlanDigest === undefined || previousPlanDigest !== planDigest,
|
|
215
|
-
} satisfies
|
|
215
|
+
} satisfies AutoplanReady;
|
|
216
216
|
},
|
|
217
217
|
}),
|
|
218
218
|
},
|
|
@@ -228,4 +228,4 @@ export const autodeviseWorkflow = defineWorkflow({
|
|
|
228
228
|
],
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
-
export default
|
|
231
|
+
export default autoplanWorkflow;
|
package/src/builtins/catalog.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { BuiltinWorkflowCatalog } from "../workflows/catalog.js";
|
|
2
|
-
import
|
|
2
|
+
import autodocWorkflow from "./autodoc.workflow.js";
|
|
3
3
|
import autoimplementWorkflow from "./autoimplement.workflow.js";
|
|
4
|
+
import autoplanWorkflow from "./autoplan.workflow.js";
|
|
4
5
|
import monitorWorkflow from "./monitor.workflow.js";
|
|
6
|
+
import planApprovalWorkflow from "./plan-approval.workflow.js";
|
|
5
7
|
|
|
6
8
|
export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
|
|
7
|
-
{ id: "
|
|
8
|
-
{ id: "
|
|
9
|
+
{ id: "autoplan", revision: "1", definition: autoplanWorkflow },
|
|
10
|
+
{ id: "autodoc", revision: "1", definition: autodocWorkflow },
|
|
11
|
+
{ id: "autoimplement", revision: "4", definition: autoimplementWorkflow },
|
|
12
|
+
{ id: "plan-approval", revision: "2", definition: planApprovalWorkflow },
|
|
9
13
|
{
|
|
10
14
|
id: "monitor",
|
|
11
|
-
revision: "
|
|
15
|
+
revision: "7",
|
|
12
16
|
definition: monitorWorkflow,
|
|
13
17
|
legacySources: [
|
|
14
18
|
{
|
package/src/builtins/index.ts
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
type
|
|
4
|
-
type
|
|
5
|
-
type
|
|
6
|
-
} from "./
|
|
2
|
+
autoplanWorkflow,
|
|
3
|
+
type AutoplanBlocked,
|
|
4
|
+
type AutoplanInput,
|
|
5
|
+
type AutoplanReady,
|
|
6
|
+
} from "./autoplan.workflow.js";
|
|
7
|
+
export {
|
|
8
|
+
autodocWorkflow,
|
|
9
|
+
type AutodocBlocked,
|
|
10
|
+
type AutodocInput,
|
|
11
|
+
type DocumentedPlan,
|
|
12
|
+
} from "./autodoc.workflow.js";
|
|
7
13
|
export {
|
|
8
14
|
autoimplementWorkflow,
|
|
9
15
|
type AutoimplementBlocked,
|
|
10
16
|
type AutoimplementCompleted,
|
|
11
17
|
type AutoimplementInput,
|
|
18
|
+
type ExistingPlanDiscovery,
|
|
12
19
|
} from "./autoimplement.workflow.js";
|
|
13
|
-
export {
|
|
20
|
+
export {
|
|
21
|
+
default as monitorWorkflow,
|
|
22
|
+
type MonitorInput,
|
|
23
|
+
type MonitorRepairPolicy,
|
|
24
|
+
} from "./monitor.workflow.js";
|
|
25
|
+
export { presentPlan } from "./plan-presentation.js";
|
|
26
|
+
export {
|
|
27
|
+
planApprovalWorkflow,
|
|
28
|
+
type PlanApprovalContinue,
|
|
29
|
+
type PlanApprovalInput,
|
|
30
|
+
type PlanApprovalReplan,
|
|
31
|
+
type PlanApprovalStop,
|
|
32
|
+
} from "./plan-approval.workflow.js";
|