@osolmaz/pi-workflows 0.10.0 → 0.11.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 (135) hide show
  1. package/README.md +32 -14
  2. package/dist/builtins/autodoc.workflow.d.ts +58 -0
  3. package/dist/builtins/autodoc.workflow.js +266 -0
  4. package/dist/builtins/autodoc.workflow.js.map +1 -0
  5. package/dist/builtins/autoimplement.workflow.d.ts +212 -11
  6. package/dist/builtins/autoimplement.workflow.js +431 -22
  7. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  8. package/dist/builtins/{autodevise.workflow.d.ts → autoplan.workflow.d.ts} +9 -9
  9. package/dist/builtins/{autodevise.workflow.js → autoplan.workflow.js} +20 -20
  10. package/dist/builtins/autoplan.workflow.js.map +1 -0
  11. package/dist/builtins/catalog.js +8 -4
  12. package/dist/builtins/catalog.js.map +1 -1
  13. package/dist/builtins/index.d.ts +6 -3
  14. package/dist/builtins/index.js +5 -2
  15. package/dist/builtins/index.js.map +1 -1
  16. package/dist/builtins/monitor.workflow.d.ts +4 -0
  17. package/dist/builtins/monitor.workflow.js +134 -9
  18. package/dist/builtins/monitor.workflow.js.map +1 -1
  19. package/dist/builtins/plan-approval.workflow.d.ts +83 -0
  20. package/dist/builtins/plan-approval.workflow.js +148 -0
  21. package/dist/builtins/plan-approval.workflow.js.map +1 -0
  22. package/dist/builtins/plan-presentation.d.ts +7 -0
  23. package/dist/builtins/plan-presentation.js +44 -0
  24. package/dist/builtins/plan-presentation.js.map +1 -0
  25. package/dist/extension/decision-channels.d.ts +132 -0
  26. package/dist/extension/decision-channels.js +1082 -0
  27. package/dist/extension/decision-channels.js.map +1 -0
  28. package/dist/extension/index.d.ts +1 -0
  29. package/dist/extension/index.js +426 -11
  30. package/dist/extension/index.js.map +1 -1
  31. package/dist/extension/widget.js +26 -2
  32. package/dist/extension/widget.js.map +1 -1
  33. package/dist/extension/workflow-tool.d.ts +1 -37
  34. package/dist/extension/workflow-tool.js +1 -42
  35. package/dist/extension/workflow-tool.js.map +1 -1
  36. package/dist/herdr/setup.d.ts +13 -1
  37. package/dist/herdr/setup.js +349 -36
  38. package/dist/herdr/setup.js.map +1 -1
  39. package/dist/host/rpc-bridge.js +4 -21
  40. package/dist/host/rpc-bridge.js.map +1 -1
  41. package/dist/render/graph-render.js +33 -8
  42. package/dist/render/graph-render.js.map +1 -1
  43. package/dist/viewer/cli.d.ts +1 -0
  44. package/dist/viewer/cli.js +21 -10
  45. package/dist/viewer/cli.js.map +1 -1
  46. package/dist/viewer/render.js +36 -2
  47. package/dist/viewer/render.js.map +1 -1
  48. package/dist/workflows/composition.js +15 -1
  49. package/dist/workflows/composition.js.map +1 -1
  50. package/dist/workflows/decision-presentation.d.ts +18 -0
  51. package/dist/workflows/decision-presentation.js +417 -0
  52. package/dist/workflows/decision-presentation.js.map +1 -0
  53. package/dist/workflows/engine.d.ts +2 -1
  54. package/dist/workflows/engine.js +86 -4
  55. package/dist/workflows/engine.js.map +1 -1
  56. package/dist/workflows/human-decision.d.ts +87 -0
  57. package/dist/workflows/human-decision.js +583 -0
  58. package/dist/workflows/human-decision.js.map +1 -0
  59. package/dist/workflows/index.d.ts +3 -1
  60. package/dist/workflows/index.js +2 -0
  61. package/dist/workflows/index.js.map +1 -1
  62. package/dist/workflows/schema.js +15 -0
  63. package/dist/workflows/schema.js.map +1 -1
  64. package/dist/workflows/store.js +7 -0
  65. package/dist/workflows/store.js.map +1 -1
  66. package/dist/workflows/tool-input.d.ts +72 -0
  67. package/dist/workflows/tool-input.js +141 -0
  68. package/dist/workflows/tool-input.js.map +1 -0
  69. package/dist/workflows/types.d.ts +223 -0
  70. package/docs/HUMAN_DECISIONS.md +371 -0
  71. package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
  72. package/docs/MONITOR.md +17 -6
  73. package/docs/WORKFLOW_COMPOSITION.md +13 -6
  74. package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
  75. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
  76. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
  77. package/docs/plans/2026-08-19-workflow-composition-plan.md +25 -17
  78. package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +138 -0
  79. package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +104 -0
  80. package/docs/run-bundles.md +23 -2
  81. package/docs/workflows.md +47 -5
  82. package/examples/workflows/approved-plan.workflow.ts +58 -0
  83. package/examples/workflows/autoplan.workflow.ts +1 -0
  84. package/examples/workflows/human-decision.workflow.ts +62 -0
  85. package/herdr-plugin.toml +1 -1
  86. package/package.json +2 -1
  87. package/schemas/decision-presentation-v1.schema.json +83 -0
  88. package/schemas/human-decision-accepted-v1.schema.json +44 -0
  89. package/schemas/human-decision-accepted-v2.schema.json +50 -0
  90. package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
  91. package/schemas/human-decision-cancellation-v1.schema.json +15 -0
  92. package/schemas/human-decision-continuation-v1.schema.json +16 -0
  93. package/schemas/human-decision-delivery-v1.schema.json +28 -0
  94. package/schemas/human-decision-delivery-v2.schema.json +36 -0
  95. package/schemas/human-decision-receipt-v1.schema.json +33 -0
  96. package/schemas/human-decision-receipt-v2.schema.json +39 -0
  97. package/schemas/human-decision-request-v1.schema.json +59 -0
  98. package/schemas/human-decision-request-v2.schema.json +69 -0
  99. package/schemas/human-decision-resolution-v1.schema.json +27 -0
  100. package/schemas/human-decision-resolution-v2.schema.json +27 -0
  101. package/schemas/human-decision-settlement-v1.schema.json +28 -0
  102. package/skills/autodoc/SKILL.md +43 -0
  103. package/skills/autoimplement/SKILL.md +58 -0
  104. package/skills/autoimplement/agents/openai.yaml +4 -0
  105. package/skills/autoplan/SKILL.md +25 -0
  106. package/skills/monitor/SKILL.md +3 -1
  107. package/skills/pi-workflows/SKILL.md +3 -3
  108. package/src/builtins/autodoc.workflow.ts +325 -0
  109. package/src/builtins/autoimplement.workflow.ts +493 -23
  110. package/src/builtins/{autodevise.workflow.ts → autoplan.workflow.ts} +32 -32
  111. package/src/builtins/catalog.ts +8 -4
  112. package/src/builtins/index.ts +25 -6
  113. package/src/builtins/monitor.workflow.ts +162 -8
  114. package/src/builtins/plan-approval.workflow.ts +190 -0
  115. package/src/builtins/plan-presentation.ts +57 -0
  116. package/src/extension/decision-channels.ts +1533 -0
  117. package/src/extension/index.ts +499 -9
  118. package/src/extension/widget.ts +39 -2
  119. package/src/extension/workflow-tool.ts +5 -81
  120. package/src/herdr/setup.ts +429 -39
  121. package/src/host/rpc-bridge.ts +7 -30
  122. package/src/render/graph-render.ts +40 -3
  123. package/src/viewer/cli.ts +22 -10
  124. package/src/viewer/render.ts +56 -3
  125. package/src/workflows/composition.ts +16 -1
  126. package/src/workflows/decision-presentation.ts +501 -0
  127. package/src/workflows/engine.ts +108 -4
  128. package/src/workflows/human-decision.ts +819 -0
  129. package/src/workflows/index.ts +69 -0
  130. package/src/workflows/schema.ts +17 -0
  131. package/src/workflows/store.ts +8 -0
  132. package/src/workflows/tool-input.ts +202 -0
  133. package/src/workflows/types.ts +265 -0
  134. package/dist/builtins/autodevise.workflow.js.map +0 -1
  135. package/examples/workflows/autodevise.workflow.ts +0 -1
@@ -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 AutodeviseInput = {
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 AutodeviseReady = {
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 AutodeviseBlocked = {
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): AutodeviseInput {
48
- const input = requireRecord(value, "autodevise input");
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("autodevise constraints must be an array of strings");
54
+ throw new Error("autoplan constraints must be an array of strings");
55
55
  }
56
56
  return {
57
- problem: requireString(input.problem, "autodevise problem"),
58
- ...(input.scope !== undefined ? { scope: requireString(input.scope, "autodevise 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, "autodevise selection");
66
+ const selection = requireRecord(value, "autoplan selection");
67
67
  if (selection.status !== "ready" && selection.status !== "blocked") {
68
- throw new Error("autodevise selection status must be ready or blocked");
68
+ throw new Error("autoplan selection status must be ready or blocked");
69
69
  }
70
- requireString(selection.selected, "autodevise selected solution");
71
- requireString(selection.why, "autodevise selection reason");
72
- if (selection.status === "blocked") requireString(selection.blocker, "autodevise 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 autodeviseWorkflow = defineWorkflow({
80
+ export const autoplanWorkflow = defineWorkflow({
81
81
  source: import.meta.url,
82
- contractId: "pi-workflows.autodevise.v1",
83
- name: "autodevise",
82
+ contractId: "pi-workflows.autoplan.v1",
83
+ name: "autoplan",
84
84
  input: parseInput,
85
- title: ({ input }) => `autodevise: ${input.problem.slice(0, 60)}`,
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): AutodeviseReady => value as AutodeviseReady,
96
+ validate: (value: unknown): AutoplanReady => value as AutoplanReady,
97
97
  },
98
98
  blocked: {
99
99
  from: "blocked",
100
- validate: (value: unknown): AutodeviseBlocked => value as AutodeviseBlocked,
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 AutodeviseInput;
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, "autodevise frame"),
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, "autodevise proposal"),
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 AutodeviseInput).newEvidence ?? null)}`,
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, "autodevise ideal"),
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 AutodeviseInput).previousPlan ?? null)}`,
181
- `New evidence: ${JSON.stringify((input as AutodeviseInput).newEvidence ?? null)}`,
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, "autodevise plan"),
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, "autodevise blocker"),
196
- } satisfies AutodeviseBlocked;
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 AutodeviseInput;
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 AutodeviseReady;
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 autodeviseWorkflow;
231
+ export default autoplanWorkflow;
@@ -1,14 +1,18 @@
1
1
  import { BuiltinWorkflowCatalog } from "../workflows/catalog.js";
2
- import autodeviseWorkflow from "./autodevise.workflow.js";
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: "autodevise", revision: "1", definition: autodeviseWorkflow },
8
- { id: "autoimplement", revision: "1", definition: autoimplementWorkflow },
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: "4",
15
+ revision: "7",
12
16
  definition: monitorWorkflow,
13
17
  legacySources: [
14
18
  {
@@ -1,13 +1,32 @@
1
1
  export {
2
- autodeviseWorkflow,
3
- type AutodeviseBlocked,
4
- type AutodeviseInput,
5
- type AutodeviseReady,
6
- } from "./autodevise.workflow.js";
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 { default as monitorWorkflow, type MonitorInput } from "./monitor.workflow.js";
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";
@@ -20,8 +20,10 @@ import type {
20
20
  WorkflowProgressData,
21
21
  } from "../workflows/types.js";
22
22
  import { validateProgressData } from "../workflows/updates.js";
23
- import autodeviseWorkflow from "./autodevise.workflow.js";
23
+ import autodocWorkflow, { type AutodocInput } from "./autodoc.workflow.js";
24
24
  import autoimplementWorkflow, { type AutoimplementInput } from "./autoimplement.workflow.js";
25
+ import autoplanWorkflow from "./autoplan.workflow.js";
26
+ import planApprovalWorkflow, { type PlanApprovalInput } from "./plan-approval.workflow.js";
25
27
 
26
28
  const MIN_INTERVAL_MINUTES = 1;
27
29
  const MAX_INTERVAL_MINUTES = 24 * 60;
@@ -45,6 +47,10 @@ export type MonitorRepairPolicy = {
45
47
  repository?: string;
46
48
  baseBranch?: string;
47
49
  merge?: boolean;
50
+ approval?: {
51
+ audience: string;
52
+ maxReplans: number;
53
+ };
48
54
  };
49
55
 
50
56
  export type MonitorInput = {
@@ -163,6 +169,22 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
163
169
  if (raw.merge !== undefined && typeof raw.merge !== "boolean") {
164
170
  throw new Error("repair merge must be a boolean");
165
171
  }
172
+ let approval: MonitorRepairPolicy["approval"];
173
+ if (raw.approval !== undefined) {
174
+ const value = requireRecord(raw.approval, "repair approval");
175
+ const maxReplans = value.maxReplans ?? 3;
176
+ if (
177
+ !Number.isInteger(maxReplans) ||
178
+ (maxReplans as number) < 1 ||
179
+ (maxReplans as number) > 20
180
+ ) {
181
+ throw new Error("repair approval maxReplans must be from 1 through 20");
182
+ }
183
+ approval = {
184
+ audience: requireBoundedString(value.audience, "repair approval audience", 200),
185
+ maxReplans: maxReplans as number,
186
+ };
187
+ }
166
188
  repair = {
167
189
  authorized: true,
168
190
  ...(raw.scope !== undefined
@@ -176,6 +198,7 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
176
198
  ? { baseBranch: requireBoundedString(raw.baseBranch, "repair base branch", 256) }
177
199
  : {}),
178
200
  ...(raw.merge !== undefined ? { merge: raw.merge !== false } : {}),
201
+ ...(approval !== undefined ? { approval } : {}),
179
202
  };
180
203
  }
181
204
  return {
@@ -297,9 +320,56 @@ function repeatedRepairWithoutProgress(context: WorkflowNodeContext): boolean {
297
320
  return false;
298
321
  }
299
322
 
323
+ function currentRepairPlan(outputs: Record<string, unknown>): {
324
+ plan: unknown;
325
+ planDigest: string;
326
+ documents: string[];
327
+ } {
328
+ const documentation = outputs.documentation as
329
+ | {
330
+ exit?: string;
331
+ output?: {
332
+ plan?: unknown;
333
+ planDigest?: string;
334
+ documentation?: { files?: string[] };
335
+ };
336
+ }
337
+ | undefined;
338
+ if (
339
+ documentation?.exit === "ready" &&
340
+ documentation.output?.plan !== undefined &&
341
+ typeof documentation.output.planDigest === "string"
342
+ ) {
343
+ return {
344
+ plan: documentation.output.plan,
345
+ planDigest: documentation.output.planDigest,
346
+ documents: documentation.output.documentation?.files ?? [],
347
+ };
348
+ }
349
+ const design = includedResult(autoplanWorkflow, outputs.initialDesign);
350
+ if (design.exit !== "ready") throw new Error("monitor design did not return a ready plan");
351
+ return { plan: design.output.plan, planDigest: design.output.planDigest, documents: [] };
352
+ }
353
+
354
+ function latestReplanInstructions(outputs: Record<string, unknown>): string | undefined {
355
+ const approval = outputs.approval as
356
+ | { exit?: string; output?: { instructions?: unknown } }
357
+ | undefined;
358
+ return approval?.exit === "replan" && typeof approval.output?.instructions === "string"
359
+ ? approval.output.instructions
360
+ : undefined;
361
+ }
362
+
300
363
  function repairBlockedReason(outputs: Record<string, unknown>): string {
301
364
  const guard = outputs.repairGuard as { reason?: string; route?: string } | undefined;
302
365
  if (guard?.route === "blocked" && guard.reason !== undefined) return guard.reason;
366
+ const replan = outputs.replanGuard as { reason?: string; route?: string } | undefined;
367
+ if (replan?.route === "blocked" && replan.reason !== undefined) return replan.reason;
368
+ const approval = outputs.approval as { exit?: string; output?: { status?: string } } | undefined;
369
+ if (approval?.exit === "stop") return "The operator stopped the proposed repair.";
370
+ const documentation = outputs.documentation as
371
+ | { exit?: string; output?: { reason?: string } }
372
+ | undefined;
303
373
  const implementation = outputs.implementation as
304
374
  | { exit?: string; output?: { reason?: string } }
305
375
  | undefined;
@@ -308,6 +378,7 @@ function repairBlockedReason(outputs: Record<string, unknown>): string {
308
378
  | undefined;
309
379
  return (
310
380
  implementation?.output?.reason ??
381
+ documentation?.output?.reason ??
311
382
  design?.output?.reason ??
312
383
  "The repair did not produce new verified progress."
313
384
  );
@@ -357,19 +428,66 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
357
428
  maxSteps: 200_000,
358
429
  includes: {
359
430
  initialDesign: includeWorkflow({
360
- workflow: "autodevise",
361
- contract: autodeviseWorkflow,
431
+ workflow: "autoplan",
432
+ contract: autoplanWorkflow,
362
433
  input: ({ outputs }) => {
363
434
  const config = configFrom(outputs);
364
435
  const repair = (outputs.check as MonitorCheck).repair;
365
436
  if (repair === undefined) throw new Error("monitor repair details are missing");
437
+ const prior = outputs.initialDesign as
438
+ | { exit?: string; output?: { plan?: unknown } }
439
+ | undefined;
440
+ const instructions = latestReplanInstructions(outputs);
366
441
  return {
367
442
  problem: repair.problem,
368
443
  ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
369
444
  ...(config.repair?.constraints !== undefined
370
445
  ? { constraints: config.repair.constraints }
371
446
  : {}),
372
- newEvidence: repair.evidence,
447
+ ...(prior?.exit === "ready" && prior.output?.plan !== undefined
448
+ ? { previousPlan: prior.output.plan }
449
+ : {}),
450
+ newEvidence:
451
+ instructions === undefined
452
+ ? repair.evidence
453
+ : { observedEvidence: repair.evidence, operatorInstructions: instructions },
454
+ };
455
+ },
456
+ }),
457
+ documentation: includeWorkflow(autodocWorkflow, {
458
+ input: ({ outputs }): AutodocInput => {
459
+ const config = configFrom(outputs);
460
+ const repair = (outputs.check as MonitorCheck).repair;
461
+ const design = includedResult(autoplanWorkflow, outputs.initialDesign);
462
+ if (design.exit !== "ready") throw new Error("monitor design did not return a ready plan");
463
+ if (repair === undefined) throw new Error("monitor repair details are missing");
464
+ return {
465
+ task: repair.problem,
466
+ plan: design.output.plan,
467
+ ...(config.repair?.repository !== undefined
468
+ ? { repository: config.repair.repository }
469
+ : {}),
470
+ evidence: latestReplanInstructions(outputs) ?? repair.evidence,
471
+ };
472
+ },
473
+ }),
474
+ approval: includeWorkflow(planApprovalWorkflow, {
475
+ input: ({ outputs, state }): PlanApprovalInput => {
476
+ const config = configFrom(outputs);
477
+ const repair = (outputs.check as MonitorCheck).repair;
478
+ const approval = config.repair?.approval;
479
+ if (repair === undefined || approval === undefined) {
480
+ throw new Error("monitor approval was entered without an approval policy");
481
+ }
482
+ const current = currentRepairPlan(outputs);
483
+ const revision =
484
+ state.steps.filter((step) => step.nodeId === "approval/approve").length + 1;
485
+ return {
486
+ task: repair.problem,
487
+ plan: current.plan,
488
+ planDigest: current.planDigest,
489
+ audience: approval.audience,
490
+ revision,
373
491
  };
374
492
  },
375
493
  }),
@@ -379,12 +497,16 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
379
497
  input: ({ outputs }) => {
380
498
  const config = configFrom(outputs);
381
499
  const repair = (outputs.check as MonitorCheck).repair;
382
- const design = includedResult(autodeviseWorkflow, outputs.initialDesign);
383
- if (design.exit !== "ready") throw new Error("monitor design did not return a ready plan");
500
+ const documented = currentRepairPlan(outputs);
384
501
  if (repair === undefined) throw new Error("monitor repair details are missing");
385
502
  const request: AutoimplementInput = {
386
503
  task: repair.problem,
387
- plan: design.output.plan,
504
+ plan: documented.plan,
505
+ documentation: {
506
+ status: "current",
507
+ planDigest: documented.planDigest,
508
+ documents: documented.documents,
509
+ },
388
510
  ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
389
511
  ...(config.repair?.constraints !== undefined
390
512
  ? { constraints: config.repair.constraints }
@@ -481,6 +603,25 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
481
603
  }
482
604
  : { route: "repair", reason: "The issue is new or has changed evidence." },
483
605
  }),
606
+ approvalRoute: compute({
607
+ run: ({ outputs }) => ({
608
+ route: configFrom(outputs).repair?.approval === undefined ? "implement" : "approve",
609
+ }),
610
+ }),
611
+ replanGuard: compute({
612
+ run: (context) => {
613
+ const limit = configFrom(context.outputs).repair?.approval?.maxReplans ?? 3;
614
+ const replans = context.state.steps.filter(
615
+ (step) => step.nodeId === "approval/replan",
616
+ ).length;
617
+ return replans > limit
618
+ ? {
619
+ route: "blocked",
620
+ reason: `Monitor repair reached the ${limit}-replan safety limit.`,
621
+ }
622
+ : { route: "design", replans, limit };
623
+ },
624
+ }),
484
625
  repairBlocked: compute({
485
626
  run: (context) => ({
486
627
  reason: repairBlockedReason(context.outputs),
@@ -568,8 +709,21 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
568
709
  from: "repairGuard",
569
710
  switch: { on: "$.route", cases: { repair: "initialDesign", blocked: "repairBlocked" } },
570
711
  },
571
- { from: "initialDesign.ready", to: "implementation" },
712
+ { from: "initialDesign.ready", to: "documentation" },
572
713
  { from: "initialDesign.blocked", to: "repairBlocked" },
714
+ { from: "documentation.ready", to: "approvalRoute" },
715
+ { from: "documentation.blocked", to: "repairBlocked" },
716
+ {
717
+ from: "approvalRoute",
718
+ switch: { on: "$.route", cases: { approve: "approval", implement: "implementation" } },
719
+ },
720
+ { from: "approval.continue", to: "implementation" },
721
+ { from: "approval.stop", to: "repairBlocked" },
722
+ { from: "approval.replan", to: "replanGuard" },
723
+ {
724
+ from: "replanGuard",
725
+ switch: { on: "$.route", cases: { design: "initialDesign", blocked: "repairBlocked" } },
726
+ },
573
727
  { from: "implementation.completed", to: "check" },
574
728
  { from: "implementation.blocked", to: "repairBlocked" },
575
729
  { from: "repairBlocked", to: "repairReport" },