@osolmaz/pi-workflows 0.12.1 → 0.13.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 (233) hide show
  1. package/README.md +59 -33
  2. package/dist/builtins/autodoc.workflow.d.ts +194 -7
  3. package/dist/builtins/autodoc.workflow.js +156 -30
  4. package/dist/builtins/autodoc.workflow.js.map +1 -1
  5. package/dist/builtins/autoimplement-command-batches.d.ts +1 -0
  6. package/dist/builtins/autoimplement-command-batches.js +29 -31
  7. package/dist/builtins/autoimplement-command-batches.js.map +1 -1
  8. package/dist/builtins/autoimplement.workflow.d.ts +1619 -97
  9. package/dist/builtins/autoimplement.workflow.js +416 -153
  10. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  11. package/dist/builtins/autoplan.workflow.d.ts +126 -13
  12. package/dist/builtins/autoplan.workflow.js +200 -29
  13. package/dist/builtins/autoplan.workflow.js.map +1 -1
  14. package/dist/builtins/catalog.js +7 -5
  15. package/dist/builtins/catalog.js.map +1 -1
  16. package/dist/builtins/change-verification.workflow.d.ts +110 -0
  17. package/dist/builtins/change-verification.workflow.js +860 -0
  18. package/dist/builtins/change-verification.workflow.js.map +1 -0
  19. package/dist/builtins/index.d.ts +2 -1
  20. package/dist/builtins/index.js +1 -0
  21. package/dist/builtins/index.js.map +1 -1
  22. package/dist/builtins/monitor.workflow.d.ts +56 -21
  23. package/dist/builtins/monitor.workflow.js +499 -217
  24. package/dist/builtins/monitor.workflow.js.map +1 -1
  25. package/dist/builtins/plain-summary.workflow.d.ts +32 -0
  26. package/dist/builtins/plain-summary.workflow.js +134 -0
  27. package/dist/builtins/plain-summary.workflow.js.map +1 -0
  28. package/dist/builtins/plan-change.workflow.d.ts +527 -26
  29. package/dist/builtins/plan-change.workflow.js +26 -0
  30. package/dist/builtins/plan-change.workflow.js.map +1 -1
  31. package/dist/builtins/sanity-check.workflow.d.ts +45 -3
  32. package/dist/builtins/sanity-check.workflow.js +45 -7
  33. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  34. package/dist/builtins/workspace-preparation.workflow.d.ts +75 -0
  35. package/dist/builtins/workspace-preparation.workflow.js +498 -0
  36. package/dist/builtins/workspace-preparation.workflow.js.map +1 -0
  37. package/dist/controllers/effects.d.ts +3 -2
  38. package/dist/controllers/effects.js +8 -1
  39. package/dist/controllers/effects.js.map +1 -1
  40. package/dist/controllers/index.d.ts +1 -1
  41. package/dist/controllers/index.js +1 -1
  42. package/dist/controllers/index.js.map +1 -1
  43. package/dist/controllers/manager.d.ts +1 -0
  44. package/dist/controllers/manager.js +41 -17
  45. package/dist/controllers/manager.js.map +1 -1
  46. package/dist/controllers/sqlite.d.ts +107 -85
  47. package/dist/controllers/sqlite.js +1611 -1486
  48. package/dist/controllers/sqlite.js.map +1 -1
  49. package/dist/controllers/store.d.ts +9 -8
  50. package/dist/controllers/store.js +1 -31
  51. package/dist/controllers/store.js.map +1 -1
  52. package/dist/controllers/types.d.ts +3 -0
  53. package/dist/controllers/workflow-engine-scheduler.d.ts +1 -1
  54. package/dist/controllers/workflow-engine-scheduler.js +3 -5
  55. package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
  56. package/dist/controllers/workflows.d.ts +2 -2
  57. package/dist/controllers/workflows.js +11 -10
  58. package/dist/controllers/workflows.js.map +1 -1
  59. package/dist/extension/controller-host.js +2 -2
  60. package/dist/extension/controller-host.js.map +1 -1
  61. package/dist/extension/decision-channels.d.ts +2 -0
  62. package/dist/extension/decision-channels.js +311 -118
  63. package/dist/extension/decision-channels.js.map +1 -1
  64. package/dist/extension/executor.d.ts +9 -2
  65. package/dist/extension/executor.js +90 -6
  66. package/dist/extension/executor.js.map +1 -1
  67. package/dist/extension/herdr-viewer.d.ts +0 -1
  68. package/dist/extension/herdr-viewer.js +0 -4
  69. package/dist/extension/herdr-viewer.js.map +1 -1
  70. package/dist/extension/index.js +156 -117
  71. package/dist/extension/index.js.map +1 -1
  72. package/dist/extension/recorder.d.ts +3 -10
  73. package/dist/extension/recorder.js +13 -22
  74. package/dist/extension/recorder.js.map +1 -1
  75. package/dist/extension/step-message.d.ts +7 -2
  76. package/dist/extension/step-message.js +94 -2
  77. package/dist/extension/step-message.js.map +1 -1
  78. package/dist/extension/widget.js +6 -0
  79. package/dist/extension/widget.js.map +1 -1
  80. package/dist/host/rpc-executor.d.ts +3 -0
  81. package/dist/host/rpc-executor.js +2 -0
  82. package/dist/host/rpc-executor.js.map +1 -1
  83. package/dist/host/runner.d.ts +3 -6
  84. package/dist/host/runner.js +83 -32
  85. package/dist/host/runner.js.map +1 -1
  86. package/dist/render/graph-render.js +14 -8
  87. package/dist/render/graph-render.js.map +1 -1
  88. package/dist/state/database.d.ts +35 -0
  89. package/dist/state/database.js +287 -0
  90. package/dist/state/database.js.map +1 -0
  91. package/dist/state/index.d.ts +4 -0
  92. package/dist/state/index.js +5 -0
  93. package/dist/state/index.js.map +1 -0
  94. package/dist/state/json.d.ts +6 -0
  95. package/dist/state/json.js +38 -0
  96. package/dist/state/json.js.map +1 -0
  97. package/dist/state/mutation.d.ts +57 -0
  98. package/dist/state/mutation.js +255 -0
  99. package/dist/state/mutation.js.map +1 -0
  100. package/dist/state/schema.d.ts +6 -0
  101. package/dist/state/schema.js +473 -0
  102. package/dist/state/schema.js.map +1 -0
  103. package/dist/viewer/cli.d.ts +2 -2
  104. package/dist/viewer/cli.js +142 -122
  105. package/dist/viewer/cli.js.map +1 -1
  106. package/dist/viewer/render.d.ts +4 -4
  107. package/dist/viewer/render.js +2 -17
  108. package/dist/viewer/render.js.map +1 -1
  109. package/dist/viewer/session-reducer.d.ts +1 -1
  110. package/dist/viewer/session-reducer.js +1 -1
  111. package/dist/viewer/tui.d.ts +3 -3
  112. package/dist/viewer/tui.js +15 -13
  113. package/dist/viewer/tui.js.map +1 -1
  114. package/dist/viewer/watch.d.ts +2 -5
  115. package/dist/viewer/watch.js +13 -13
  116. package/dist/viewer/watch.js.map +1 -1
  117. package/dist/workflows/composition.js +12 -2
  118. package/dist/workflows/composition.js.map +1 -1
  119. package/dist/workflows/definition.d.ts +6 -2
  120. package/dist/workflows/definition.js +17 -0
  121. package/dist/workflows/definition.js.map +1 -1
  122. package/dist/workflows/engine.d.ts +6 -6
  123. package/dist/workflows/engine.js +183 -103
  124. package/dist/workflows/engine.js.map +1 -1
  125. package/dist/workflows/errors.d.ts +3 -3
  126. package/dist/workflows/errors.js +3 -3
  127. package/dist/workflows/human-decision.d.ts +29 -4
  128. package/dist/workflows/human-decision.js +547 -283
  129. package/dist/workflows/human-decision.js.map +1 -1
  130. package/dist/workflows/index.d.ts +4 -5
  131. package/dist/workflows/index.js +3 -4
  132. package/dist/workflows/index.js.map +1 -1
  133. package/dist/workflows/schema.js +17 -1
  134. package/dist/workflows/schema.js.map +1 -1
  135. package/dist/workflows/store.d.ts +83 -103
  136. package/dist/workflows/store.js +902 -951
  137. package/dist/workflows/store.js.map +1 -1
  138. package/dist/workflows/types.d.ts +55 -81
  139. package/docs/CONTROLLERS.md +10 -8
  140. package/docs/DEFERRED_TURNS.md +2 -2
  141. package/docs/DESIGN_PHILOSOPHY.md +4 -2
  142. package/docs/HUMAN_DECISIONS.md +13 -14
  143. package/docs/MONITOR.md +179 -231
  144. package/docs/SQLITE_STATE.md +219 -0
  145. package/docs/WORKFLOW_COMPOSITION.md +27 -19
  146. package/docs/WORKFLOW_STEP_MESSAGES.md +20 -18
  147. package/docs/WORKFLOW_UPDATES.md +10 -9
  148. package/docs/development.md +9 -9
  149. package/docs/live-replay-protocol.md +28 -37
  150. package/docs/plans/2026-08-16-workflow-updates-plan.md +1 -1
  151. package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
  152. package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +1 -1
  153. package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +1 -1
  154. package/docs/plans/2026-08-21-sanity-check-plan.md +30 -16
  155. package/docs/plans/2026-08-22-goal-finishing-monitor-plan.md +200 -0
  156. package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +701 -0
  157. package/docs/plans/2026-08-23-sqlite-state-plan.md +970 -0
  158. package/docs/plans/2026-08-24-change-scoped-verification-plan.md +419 -0
  159. package/docs/plans/replayable-run-bundles-implementation-plan.md +2 -2
  160. package/docs/plans/session-event-replay-implementation-plan.md +1 -1
  161. package/docs/plans/tui-viewer-implementation-plan.md +1 -1
  162. package/docs/session-event-journal.md +63 -434
  163. package/docs/tui-viewer.md +13 -15
  164. package/docs/workflows.md +111 -62
  165. package/examples/workflows/plain-summary.workflow.ts +1 -0
  166. package/herdr-plugin.toml +1 -1
  167. package/package.json +1 -1
  168. package/plugins/herdr/viewer.mjs +1 -11
  169. package/skills/autodoc/SKILL.md +7 -0
  170. package/skills/autoimplement/SKILL.md +4 -0
  171. package/skills/autoplan/SKILL.md +5 -4
  172. package/skills/monitor/SKILL.md +91 -163
  173. package/skills/pi-workflows/SKILL.md +7 -7
  174. package/skills/sanity-check/SKILL.md +1 -1
  175. package/src/builtins/autodoc.workflow.ts +184 -33
  176. package/src/builtins/autoimplement-command-batches.ts +39 -33
  177. package/src/builtins/autoimplement.workflow.ts +483 -175
  178. package/src/builtins/autoplan.workflow.ts +290 -40
  179. package/src/builtins/catalog.ts +7 -5
  180. package/src/builtins/change-verification.workflow.ts +1143 -0
  181. package/src/builtins/index.ts +7 -1
  182. package/src/builtins/monitor.workflow.ts +667 -250
  183. package/src/builtins/plain-summary.workflow.ts +185 -0
  184. package/src/builtins/plan-change.workflow.ts +35 -0
  185. package/src/builtins/sanity-check.workflow.ts +62 -7
  186. package/src/builtins/workspace-preparation.workflow.ts +668 -0
  187. package/src/controllers/effects.ts +7 -0
  188. package/src/controllers/index.ts +0 -6
  189. package/src/controllers/manager.ts +72 -32
  190. package/src/controllers/sqlite.ts +2420 -1948
  191. package/src/controllers/store.ts +17 -38
  192. package/src/controllers/types.ts +3 -0
  193. package/src/controllers/workflow-engine-scheduler.ts +4 -8
  194. package/src/controllers/workflows.ts +45 -21
  195. package/src/extension/controller-host.ts +1 -2
  196. package/src/extension/decision-channels.ts +427 -144
  197. package/src/extension/executor.ts +106 -6
  198. package/src/extension/herdr-viewer.ts +0 -5
  199. package/src/extension/index.ts +180 -132
  200. package/src/extension/recorder.ts +13 -22
  201. package/src/extension/step-message.ts +106 -4
  202. package/src/extension/widget.ts +8 -0
  203. package/src/host/rpc-executor.ts +4 -0
  204. package/src/host/runner.ts +93 -41
  205. package/src/render/graph-render.ts +11 -4
  206. package/src/state/database.ts +375 -0
  207. package/src/state/index.ts +29 -0
  208. package/src/state/json.ts +42 -0
  209. package/src/state/mutation.ts +435 -0
  210. package/src/state/schema.ts +475 -0
  211. package/src/viewer/cli.ts +151 -144
  212. package/src/viewer/render.ts +12 -24
  213. package/src/viewer/session-reducer.ts +1 -1
  214. package/src/viewer/tui.ts +18 -17
  215. package/src/viewer/watch.ts +13 -17
  216. package/src/workflows/composition.ts +12 -3
  217. package/src/workflows/definition.ts +36 -3
  218. package/src/workflows/engine.ts +212 -116
  219. package/src/workflows/errors.ts +3 -3
  220. package/src/workflows/human-decision.ts +795 -340
  221. package/src/workflows/index.ts +15 -19
  222. package/src/workflows/schema.ts +19 -1
  223. package/src/workflows/store.ts +1295 -1163
  224. package/src/workflows/types.ts +61 -81
  225. package/dist/workflows/artifacts.d.ts +0 -40
  226. package/dist/workflows/artifacts.js +0 -155
  227. package/dist/workflows/artifacts.js.map +0 -1
  228. package/dist/workflows/migrate-sources.d.ts +0 -42
  229. package/dist/workflows/migrate-sources.js +0 -133
  230. package/dist/workflows/migrate-sources.js.map +0 -1
  231. package/docs/run-bundles.md +0 -481
  232. package/src/workflows/artifacts.ts +0 -188
  233. package/src/workflows/migrate-sources.ts +0 -178
@@ -2,20 +2,20 @@ import { action, agent, compute, defineWorkflow, includeWorkflow, includedResult
2
2
  import { estimateProgress, formatProgressReport, } from "../workflows/progress.js";
3
3
  import { validateProgressData } from "../workflows/updates.js";
4
4
  import autoimplementWorkflow, {} from "./autoimplement.workflow.js";
5
- import { parsePlanApprovalPolicy } from "./plan-approval.workflow.js";
5
+ import { parsePlanApprovalPolicy, } from "./plan-approval.workflow.js";
6
6
  import planChangeWorkflow, {} from "./plan-change.workflow.js";
7
7
  const MIN_INTERVAL_MINUTES = 1;
8
8
  const MAX_INTERVAL_MINUTES = 24 * 60;
9
9
  const DEFAULT_INTERVAL_MINUTES = 30;
10
- const MIN_CHECK_TIMEOUT_MINUTES = 5;
11
- const MAX_CHECK_TIMEOUT_MINUTES = 24 * 60;
12
- const DEFAULT_MIN_CHECK_TIMEOUT_MINUTES = 60;
10
+ const DEFAULT_CHECK_TIMEOUT_MINUTES = 60;
13
11
  const DEFAULT_MAX_CHECKS = 1_000;
14
12
  const MAX_CHECKS = 1_000;
15
13
  const MAX_TRACKS = 256;
16
14
  const MAX_OBSERVATION_CHARS = 8_000;
17
15
  const MAX_REPORT_CHARS = 4_000;
18
16
  const MAX_REASON_CHARS = 2_000;
17
+ const MAX_ACTION_TEXT_CHARS = 8_000;
18
+ const MAX_ID_CHARS = 256;
19
19
  const SLEEP_TIMEOUT_MARGIN_MS = 60_000;
20
20
  const NODE_TIMEOUT_MARGIN_MS = 2 * 60_000;
21
21
  function requireRecord(value, label) {
@@ -24,6 +24,11 @@ function requireRecord(value, label) {
24
24
  }
25
25
  return value;
26
26
  }
27
+ function requireExactKeys(value, allowed, label) {
28
+ const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
29
+ if (unexpected !== undefined)
30
+ throw new Error(`${label} field ${unexpected} is not supported`);
31
+ }
27
32
  function requireBoundedString(value, label, maxChars) {
28
33
  if (typeof value !== "string")
29
34
  throw new Error(`${label} must be a string`);
@@ -34,6 +39,12 @@ function requireBoundedString(value, label, maxChars) {
34
39
  throw new Error(`${label} must be at most ${maxChars} characters`);
35
40
  return trimmed;
36
41
  }
42
+ function requireStringArray(value, label) {
43
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
44
+ throw new Error(`${label} must be an array of strings`);
45
+ }
46
+ return value.map((item, index) => requireBoundedString(item, `${label}[${index}]`, MAX_ACTION_TEXT_CHARS));
47
+ }
37
48
  async function waitForUpdateSlot(signal) {
38
49
  await new Promise((resolve, reject) => {
39
50
  const onAbort = () => {
@@ -52,18 +63,8 @@ async function waitForUpdateSlot(signal) {
52
63
  }
53
64
  export function prepareMonitorInput(input) {
54
65
  const value = requireRecord(input, "monitor input");
55
- const allowed = new Set([
56
- "task",
57
- "everyMinutes",
58
- "stopWhen",
59
- "maxChecks",
60
- "checkTimeoutMinutes",
61
- "repair",
62
- ]);
63
- for (const field of Object.keys(value)) {
64
- if (!allowed.has(field))
65
- throw new Error(`monitor input field ${field} is not supported`);
66
- }
66
+ const allowed = ["task", "everyMinutes", "stopWhen", "maxChecks"];
67
+ requireExactKeys(value, allowed, "monitor input");
67
68
  const task = requireBoundedString(value.task, "task", 8_000);
68
69
  const everyMinutes = value.everyMinutes ?? DEFAULT_INTERVAL_MINUTES;
69
70
  if (!Number.isInteger(everyMinutes) ||
@@ -75,41 +76,6 @@ export function prepareMonitorInput(input) {
75
76
  if (!Number.isInteger(maxChecks) || maxChecks <= 0 || maxChecks > MAX_CHECKS) {
76
77
  throw new Error(`maxChecks must be an integer from 1 through ${MAX_CHECKS}`);
77
78
  }
78
- const checkTimeoutMinutes = value.checkTimeoutMinutes ?? Math.max(DEFAULT_MIN_CHECK_TIMEOUT_MINUTES, everyMinutes);
79
- if (!Number.isInteger(checkTimeoutMinutes) ||
80
- checkTimeoutMinutes < MIN_CHECK_TIMEOUT_MINUTES ||
81
- checkTimeoutMinutes > MAX_CHECK_TIMEOUT_MINUTES) {
82
- throw new Error(`checkTimeoutMinutes must be an integer from ${MIN_CHECK_TIMEOUT_MINUTES} through ${MAX_CHECK_TIMEOUT_MINUTES}`);
83
- }
84
- let repair;
85
- if (value.repair !== undefined) {
86
- const raw = requireRecord(value.repair, "repair policy");
87
- if (raw.authorized !== true)
88
- throw new Error("repair policy must set authorized to true");
89
- if (raw.constraints !== undefined &&
90
- (!Array.isArray(raw.constraints) || raw.constraints.some((item) => typeof item !== "string"))) {
91
- throw new Error("repair constraints must be an array of strings");
92
- }
93
- if (raw.merge !== undefined && typeof raw.merge !== "boolean") {
94
- throw new Error("repair merge must be a boolean");
95
- }
96
- const approval = parsePlanApprovalPolicy(raw.approval);
97
- repair = {
98
- authorized: true,
99
- ...(raw.scope !== undefined
100
- ? { scope: requireBoundedString(raw.scope, "repair scope", 4_000) }
101
- : {}),
102
- ...(raw.constraints !== undefined ? { constraints: [...raw.constraints] } : {}),
103
- ...(raw.repository !== undefined
104
- ? { repository: requireBoundedString(raw.repository, "repair repository", 4_000) }
105
- : {}),
106
- ...(raw.baseBranch !== undefined
107
- ? { baseBranch: requireBoundedString(raw.baseBranch, "repair base branch", 256) }
108
- : {}),
109
- ...(raw.merge !== undefined ? { merge: raw.merge !== false } : {}),
110
- approval,
111
- };
112
- }
113
79
  return {
114
80
  task,
115
81
  everyMinutes,
@@ -117,66 +83,243 @@ export function prepareMonitorInput(input) {
117
83
  ? "Stop only when the user explicitly asks to stop."
118
84
  : requireBoundedString(value.stopWhen, "stopWhen", 4_000),
119
85
  maxChecks,
120
- checkTimeoutMinutes,
121
- ...(repair !== undefined ? { repair } : {}),
122
86
  };
123
87
  }
124
88
  function configFrom(outputs) {
125
89
  return outputs.prepare;
126
90
  }
127
- function completedChecks(context) {
128
- return context.state.steps.filter((step) => step.nodeId === "check" && step.outcome === "ok")
91
+ function completedObservations(context) {
92
+ return context.state.steps.filter((step) => step.nodeId === "observe" && step.outcome === "ok")
129
93
  .length;
130
94
  }
131
- export function validateMonitorCheck(output, repairAuthorized = false) {
132
- const value = requireRecord(output, "monitor check output");
133
- const allowed = new Set(["route", "observation", "report", "progress", "repair", "reason"]);
134
- for (const key of Object.keys(value))
135
- if (!allowed.has(key))
136
- throw new Error(`monitor check field ${key} is not supported`);
137
- if (value.route !== "continue" && value.route !== "repair" && value.route !== "stop") {
138
- throw new Error("route must be continue, repair, or stop");
139
- }
140
- if (value.route === "repair" && !repairAuthorized) {
141
- throw new Error("route repair requires explicit monitor repair authorization");
142
- }
143
- const check = {
95
+ function parseAuthority(input) {
96
+ const value = requireRecord(input, "action authority");
97
+ requireExactKeys(value, [
98
+ "status",
99
+ "basis",
100
+ "allowedMutations",
101
+ "forbiddenMutations",
102
+ "costLimit",
103
+ "providerRuntime",
104
+ "requiredChecks",
105
+ "stopConditions",
106
+ "allowedRecoveryActions",
107
+ "repository",
108
+ "baseBranch",
109
+ "merge",
110
+ "repairApproval",
111
+ ], "action authority");
112
+ if (value.status !== "authorized" && value.status !== "outside") {
113
+ throw new Error("action authority status must be authorized or outside");
114
+ }
115
+ if (typeof value.merge !== "boolean")
116
+ throw new Error("action authority merge must be boolean");
117
+ const approval = value.repairApproval === undefined
118
+ ? undefined
119
+ : requireRecord(value.repairApproval, "repair approval");
120
+ return {
121
+ status: value.status,
122
+ basis: requireBoundedString(value.basis, "action authority basis", MAX_ACTION_TEXT_CHARS),
123
+ allowedMutations: requireStringArray(value.allowedMutations, "action authority allowedMutations"),
124
+ forbiddenMutations: requireStringArray(value.forbiddenMutations, "action authority forbiddenMutations"),
125
+ costLimit: requireBoundedString(value.costLimit, "action authority costLimit", MAX_ACTION_TEXT_CHARS),
126
+ providerRuntime: requireBoundedString(value.providerRuntime, "action authority providerRuntime", MAX_ACTION_TEXT_CHARS),
127
+ requiredChecks: requireStringArray(value.requiredChecks, "action authority requiredChecks"),
128
+ stopConditions: requireStringArray(value.stopConditions, "action authority stopConditions"),
129
+ allowedRecoveryActions: requireStringArray(value.allowedRecoveryActions, "action authority allowedRecoveryActions"),
130
+ ...(value.repository !== undefined
131
+ ? {
132
+ repository: requireBoundedString(value.repository, "action authority repository", 4_000),
133
+ }
134
+ : {}),
135
+ ...(value.baseBranch !== undefined
136
+ ? {
137
+ baseBranch: requireBoundedString(value.baseBranch, "action authority baseBranch", 256),
138
+ }
139
+ : {}),
140
+ merge: value.merge,
141
+ repairApproval: parsePlanApprovalPolicy(approval),
142
+ };
143
+ }
144
+ function parseCostSafety(input) {
145
+ const value = requireRecord(input, "action cost");
146
+ requireExactKeys(value, ["paidAction", "status", "evidence"], "action cost");
147
+ if (typeof value.paidAction !== "boolean")
148
+ throw new Error("action cost paidAction must be boolean");
149
+ if (value.status !== "not-applicable" &&
150
+ value.status !== "within-limit" &&
151
+ value.status !== "missing" &&
152
+ value.status !== "exceeded") {
153
+ throw new Error("action cost status is invalid");
154
+ }
155
+ return {
156
+ paidAction: value.paidAction,
157
+ status: value.status,
158
+ evidence: requireBoundedString(value.evidence, "action cost evidence", MAX_ACTION_TEXT_CHARS),
159
+ };
160
+ }
161
+ function parseDefectSafety(input) {
162
+ const value = requireRecord(input, "action defect");
163
+ requireExactKeys(value, ["sharedCodeOrDataDefect", "paidWorkers", "evidence"], "action defect");
164
+ if (typeof value.sharedCodeOrDataDefect !== "boolean") {
165
+ throw new Error("action defect sharedCodeOrDataDefect must be boolean");
166
+ }
167
+ if (value.paidWorkers !== "not-applicable" &&
168
+ value.paidWorkers !== "stopped" &&
169
+ value.paidWorkers !== "running") {
170
+ throw new Error("action defect paidWorkers is invalid");
171
+ }
172
+ return {
173
+ sharedCodeOrDataDefect: value.sharedCodeOrDataDefect,
174
+ paidWorkers: value.paidWorkers,
175
+ evidence: requireBoundedString(value.evidence, "action defect evidence", MAX_ACTION_TEXT_CHARS),
176
+ };
177
+ }
178
+ function parseActionRequest(input) {
179
+ const value = requireRecord(input, "monitor action request");
180
+ requireExactKeys(value, [
181
+ "kind",
182
+ "incomplete",
183
+ "evidence",
184
+ "nextAction",
185
+ "authority",
186
+ "cost",
187
+ "defect",
188
+ "verification",
189
+ "failureId",
190
+ "targetStateId",
191
+ ], "monitor action request");
192
+ if (value.kind !== "advance" && value.kind !== "recover" && value.kind !== "repair") {
193
+ throw new Error("monitor action kind must be advance, recover, or repair");
194
+ }
195
+ const request = {
196
+ kind: value.kind,
197
+ incomplete: requireBoundedString(value.incomplete, "monitor action incomplete work", MAX_ACTION_TEXT_CHARS),
198
+ evidence: value.evidence ?? null,
199
+ nextAction: requireBoundedString(value.nextAction, "monitor action nextAction", MAX_ACTION_TEXT_CHARS),
200
+ authority: parseAuthority(value.authority),
201
+ cost: parseCostSafety(value.cost),
202
+ defect: parseDefectSafety(value.defect),
203
+ verification: requireBoundedString(value.verification, "monitor action verification", MAX_ACTION_TEXT_CHARS),
204
+ failureId: requireBoundedString(value.failureId, "monitor action failureId", MAX_ID_CHARS),
205
+ targetStateId: requireBoundedString(value.targetStateId, "monitor action targetStateId", MAX_ID_CHARS),
206
+ };
207
+ if (request.authority.status !== "authorized") {
208
+ throw new Error("route act requires action authority status authorized");
209
+ }
210
+ if (request.authority.allowedMutations.length === 0) {
211
+ throw new Error("route act requires at least one allowed mutation");
212
+ }
213
+ if (request.cost.paidAction &&
214
+ (request.cost.status === "missing" || request.cost.status === "exceeded")) {
215
+ throw new Error("route act cannot launch paid work without verified remaining authority");
216
+ }
217
+ if (request.cost.paidAction && request.cost.status !== "within-limit") {
218
+ throw new Error("paid route act requires cost status within-limit");
219
+ }
220
+ if (!request.cost.paidAction && request.cost.status !== "not-applicable") {
221
+ throw new Error("unpaid route act requires cost status not-applicable");
222
+ }
223
+ if (request.kind === "repair" &&
224
+ request.defect.sharedCodeOrDataDefect &&
225
+ request.defect.paidWorkers === "running") {
226
+ throw new Error("paid workers must stop before a shared code or data repair");
227
+ }
228
+ return request;
229
+ }
230
+ export function validateMonitorObservation(output) {
231
+ const value = requireRecord(output, "monitor observation output");
232
+ requireExactKeys(value, [
233
+ "route",
234
+ "goalState",
235
+ "workState",
236
+ "observation",
237
+ "report",
238
+ "targetStateId",
239
+ "authorizedActions",
240
+ "progress",
241
+ "action",
242
+ "reason",
243
+ ], "monitor observation");
244
+ if (value.route !== "wait" && value.route !== "act" && value.route !== "stop") {
245
+ throw new Error("route must be wait, act, or stop");
246
+ }
247
+ if (value.goalState !== "complete" &&
248
+ value.goalState !== "incomplete" &&
249
+ value.goalState !== "blocked") {
250
+ throw new Error("goalState must be complete, incomplete, or blocked");
251
+ }
252
+ if (value.workState !== "running" &&
253
+ value.workState !== "waiting" &&
254
+ value.workState !== "idle" &&
255
+ value.workState !== "failed" &&
256
+ value.workState !== "stopped" &&
257
+ value.workState !== "unknown") {
258
+ throw new Error("workState is invalid");
259
+ }
260
+ const observation = {
144
261
  route: value.route,
262
+ goalState: value.goalState,
263
+ workState: value.workState,
145
264
  observation: requireBoundedString(value.observation, "observation", MAX_OBSERVATION_CHARS),
146
265
  report: requireBoundedString(value.report, "report", MAX_REPORT_CHARS),
266
+ targetStateId: requireBoundedString(value.targetStateId, "targetStateId", MAX_ID_CHARS),
267
+ authorizedActions: requireStringArray(value.authorizedActions, "authorizedActions"),
147
268
  reason: requireBoundedString(value.reason, "reason", MAX_REASON_CHARS),
148
269
  };
149
270
  if (value.progress !== undefined)
150
- check.progress = validateMonitorProgress(value.progress);
151
- if (value.repair !== undefined) {
152
- const repair = requireRecord(value.repair, "monitor repair request");
153
- check.repair = {
154
- problem: requireBoundedString(repair.problem, "repair problem", 8_000),
155
- evidence: repair.evidence ?? null,
156
- issueFingerprint: requireBoundedString(repair.issueFingerprint, "repair issue fingerprint", 256),
157
- };
271
+ observation.progress = validateMonitorProgress(value.progress);
272
+ if (value.action !== undefined)
273
+ observation.action = parseActionRequest(value.action);
274
+ if (observation.route === "act" && observation.action === undefined) {
275
+ throw new Error("route act requires action details");
276
+ }
277
+ if (observation.action !== undefined &&
278
+ observation.action.targetStateId !== observation.targetStateId) {
279
+ throw new Error("monitor action targetStateId must match the observed targetStateId");
280
+ }
281
+ if (observation.route !== "act" && observation.action !== undefined) {
282
+ throw new Error("action details are only valid for route act");
283
+ }
284
+ if (observation.route === "act" && observation.goalState !== "incomplete") {
285
+ throw new Error("route act requires goalState incomplete");
286
+ }
287
+ if (observation.route === "act" &&
288
+ observation.workState !== "idle" &&
289
+ observation.workState !== "failed" &&
290
+ observation.workState !== "stopped") {
291
+ throw new Error("route act requires idle, failed, or stopped work");
158
292
  }
159
- if (value.route === "repair" && check.repair === undefined) {
160
- throw new Error("route repair requires repair details");
293
+ if (observation.route === "wait" && observation.goalState !== "incomplete") {
294
+ throw new Error("route wait requires goalState incomplete");
161
295
  }
162
- return check;
296
+ if (observation.route === "wait" &&
297
+ observation.workState !== "running" &&
298
+ observation.workState !== "waiting") {
299
+ throw new Error("route wait requires running work or an external wait");
300
+ }
301
+ if (observation.goalState === "complete" && observation.route !== "stop") {
302
+ throw new Error("goalState complete requires route stop");
303
+ }
304
+ if (observation.goalState === "blocked" && observation.route !== "stop") {
305
+ throw new Error("goalState blocked requires route stop");
306
+ }
307
+ return observation;
163
308
  }
164
309
  function validateMonitorProgress(input) {
165
310
  const value = requireRecord(input, "progress");
166
- if (Object.keys(value).some((key) => key !== "tracks"))
167
- throw new Error("progress only supports tracks");
311
+ requireExactKeys(value, ["tracks"], "progress");
168
312
  if (!Array.isArray(value.tracks) || value.tracks.length < 1 || value.tracks.length > MAX_TRACKS) {
169
313
  throw new Error(`progress.tracks must contain 1 through ${MAX_TRACKS} entries`);
170
314
  }
171
315
  const keys = new Set();
172
316
  const tracks = value.tracks.map((raw, index) => {
173
317
  const track = requireRecord(raw, `progress.tracks[${index}]`);
174
- if (Object.keys(track).some((key) => key !== "key" && key !== "data")) {
175
- throw new Error(`progress.tracks[${index}] has an unsupported field`);
176
- }
318
+ requireExactKeys(track, ["key", "data"], `progress.tracks[${index}]`);
177
319
  const key = requireBoundedString(track.key, `progress.tracks[${index}].key`, 128);
178
- if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(key))
320
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(key)) {
179
321
  throw new Error(`progress.tracks[${index}].key is invalid`);
322
+ }
180
323
  if (keys.has(key))
181
324
  throw new Error(`progress track key ${key} is duplicated`);
182
325
  keys.add(key);
@@ -187,15 +330,18 @@ function validateMonitorProgress(input) {
187
330
  });
188
331
  return { tracks };
189
332
  }
333
+ function currentObservation(outputs) {
334
+ return (outputs.guard ?? outputs.observe);
335
+ }
190
336
  function estimateTracks(outputs) {
191
- const check = outputs.check;
192
- if (check.progress === undefined)
337
+ const observation = currentObservation(outputs);
338
+ if (observation.progress === undefined)
193
339
  return { tracks: [] };
194
340
  const previous = outputs.estimate;
195
341
  const previousByKey = new Map((previous?.tracks ?? []).map((track) => [track.key, track]));
196
342
  const at = new Date().toISOString();
197
343
  return {
198
- tracks: check.progress.tracks.map((track) => {
344
+ tracks: observation.progress.tracks.map((track) => {
199
345
  const samples = [
200
346
  ...(previousByKey.get(track.key)?.samples ?? []),
201
347
  { at, data: track.data },
@@ -204,26 +350,71 @@ function estimateTracks(outputs) {
204
350
  }),
205
351
  };
206
352
  }
207
- function repeatedRepairWithoutProgress(context) {
208
- const current = context.outputs.check;
209
- const fingerprint = current.repair?.issueFingerprint;
210
- if (fingerprint === undefined)
353
+ function actionFrom(outputs) {
354
+ const request = currentObservation(outputs).action;
355
+ if (request === undefined)
356
+ throw new Error("monitor action details are missing");
357
+ return request;
358
+ }
359
+ function repeatedCompletedRepair(context) {
360
+ const action = context.outputs.observe.action;
361
+ if (action?.kind !== "repair")
211
362
  return false;
212
- const steps = context.state.steps;
213
- const currentCheckIndex = steps.findLastIndex((step) => step.nodeId === "check");
214
- for (let index = currentCheckIndex - 1; index >= 0; index -= 1) {
215
- const step = steps[index];
216
- if (step?.nodeId !== "check")
363
+ const currentIndex = context.state.steps.findLastIndex((step) => step.nodeId === "observe");
364
+ for (let index = currentIndex - 1; index >= 0; index -= 1) {
365
+ const step = context.state.steps[index];
366
+ if (step?.nodeId !== "observe")
217
367
  continue;
218
368
  const prior = step.output;
219
- if (prior.repair?.issueFingerprint !== fingerprint)
369
+ if (prior.action?.kind !== "repair" ||
370
+ prior.action.failureId !== action.failureId ||
371
+ prior.action.targetStateId !== action.targetStateId) {
220
372
  continue;
221
- return steps
222
- .slice(index + 1, currentCheckIndex)
223
- .some((candidate) => candidate.nodeId === "implementation");
373
+ }
374
+ return context.state.steps
375
+ .slice(index + 1, currentIndex)
376
+ .some((candidate) => candidate.nodeId === "repairComplete" && candidate.outcome === "ok");
224
377
  }
225
378
  return false;
226
379
  }
380
+ function guardObservation(context) {
381
+ const observation = context.outputs.observe;
382
+ if (!repeatedCompletedRepair(context))
383
+ return observation;
384
+ const action = observation.action;
385
+ if (action === undefined)
386
+ return observation;
387
+ return {
388
+ route: "stop",
389
+ goalState: "blocked",
390
+ workState: observation.workState,
391
+ observation: observation.observation,
392
+ report: "The same failure and target state returned after one completed repair.",
393
+ targetStateId: observation.targetStateId,
394
+ authorizedActions: observation.authorizedActions,
395
+ ...(observation.progress !== undefined ? { progress: observation.progress } : {}),
396
+ reason: `Repair stopped because ${action.failureId} returned in target state ${action.targetStateId}.`,
397
+ };
398
+ }
399
+ export function validateMonitorActionResult(output, expected) {
400
+ const value = requireRecord(output, "monitor action result");
401
+ requireExactKeys(value, ["status", "summary", "evidence", "verification", "failureId", "targetStateId"], "monitor action result");
402
+ if (value.status !== "succeeded" && value.status !== "failed" && value.status !== "blocked") {
403
+ throw new Error("monitor action result status must be succeeded, failed, or blocked");
404
+ }
405
+ const result = {
406
+ status: value.status,
407
+ summary: requireBoundedString(value.summary, "monitor action result summary", MAX_REPORT_CHARS),
408
+ evidence: value.evidence ?? null,
409
+ verification: requireBoundedString(value.verification, "monitor action result verification", MAX_ACTION_TEXT_CHARS),
410
+ failureId: requireBoundedString(value.failureId, "monitor action result failureId", MAX_ID_CHARS),
411
+ targetStateId: requireBoundedString(value.targetStateId, "monitor action result targetStateId", MAX_ID_CHARS),
412
+ };
413
+ if (result.failureId !== expected.failureId || result.targetStateId !== expected.targetStateId) {
414
+ throw new Error("monitor action result must preserve the requested failure and target-state IDs");
415
+ }
416
+ return result;
417
+ }
227
418
  function currentRepairPlan(outputs) {
228
419
  const result = includedResult(planChangeWorkflow, outputs.planChange);
229
420
  if (result.exit !== "ready")
@@ -234,35 +425,100 @@ function currentRepairPlan(outputs) {
234
425
  documents: result.output.documents,
235
426
  };
236
427
  }
428
+ function authorityScope(actionRequest) {
429
+ const authority = actionRequest.authority;
430
+ return [
431
+ `Authorization basis: ${authority.basis}`,
432
+ `Allowed mutations: ${authority.allowedMutations.join("; ")}`,
433
+ `Forbidden mutations: ${authority.forbiddenMutations.join("; ") || "none recorded"}`,
434
+ `Cost limit: ${authority.costLimit}`,
435
+ `Provider/runtime contract: ${authority.providerRuntime}`,
436
+ `Allowed recovery actions: ${authority.allowedRecoveryActions.join("; ") || "none recorded"}`,
437
+ ].join("\n");
438
+ }
439
+ function authorityConstraints(actionRequest) {
440
+ const authority = actionRequest.authority;
441
+ return [
442
+ ...authority.forbiddenMutations.map((item) => `Forbidden: ${item}`),
443
+ `Cost limit: ${authority.costLimit}`,
444
+ `Provider/runtime contract: ${authority.providerRuntime}`,
445
+ ...authority.requiredChecks.map((item) => `Required check: ${item}`),
446
+ ...authority.stopConditions.map((item) => `Stop condition: ${item}`),
447
+ `Repair verification: ${actionRequest.verification}`,
448
+ ];
449
+ }
237
450
  function repairBlockedReason(outputs) {
238
- const guard = outputs.repairGuard;
239
- if (guard?.route === "blocked" && guard.reason !== undefined)
240
- return guard.reason;
241
451
  const planChange = outputs.planChange;
242
452
  const implementation = outputs.implementation;
243
453
  return (implementation?.output?.reason ??
244
454
  planChange?.output?.reason ??
245
- "The repair did not produce new verified progress.");
455
+ "The repair did not produce a verified result.");
456
+ }
457
+ function recordedActionFromStep(step) {
458
+ if (step.outcome !== "ok")
459
+ return undefined;
460
+ if (step.nodeId === "act") {
461
+ const result = step.output;
462
+ return {
463
+ status: result.status,
464
+ summary: result.summary,
465
+ evidence: result.evidence,
466
+ verification: result.verification,
467
+ failureId: result.failureId,
468
+ targetStateId: result.targetStateId,
469
+ };
470
+ }
471
+ if (step.nodeId === "repairComplete")
472
+ return step.output;
473
+ return undefined;
474
+ }
475
+ function latestRecordedAction(context) {
476
+ for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
477
+ const step = context.state.steps[index];
478
+ if (step === undefined)
479
+ continue;
480
+ const recorded = recordedActionFromStep(step);
481
+ if (recorded !== undefined)
482
+ return recorded;
483
+ }
484
+ return undefined;
246
485
  }
247
486
  function reportMessage(context) {
248
- const check = context.outputs.check;
487
+ const observation = currentObservation(context.outputs);
249
488
  const estimate = context.outputs.estimate;
250
489
  const config = configFrom(context.outputs);
251
- const suffix = [];
252
- if (estimate.tracks.length > 0) {
253
- suffix.push(formatProgressReport(estimate.tracks.map((track) => track.estimate), check.route === "continue" ? config.everyMinutes : undefined, new Date(), 2_000));
254
- }
255
- if (check.route === "continue" && completedChecks(context) >= config.maxChecks) {
256
- suffix.push(`Reached the ${config.maxChecks}-check safety limit.`);
257
- }
258
- const suffixText = suffix.filter(Boolean).join("\n");
259
- if (suffixText.length === 0)
260
- return check.report;
261
- const reportBudget = Math.max(1, MAX_REPORT_CHARS - suffixText.length - 1);
262
- const report = check.report.length <= reportBudget
263
- ? check.report
264
- : `${check.report.slice(0, Math.max(0, reportBudget - 1))}…`;
265
- return `${report}\n${suffixText}`;
490
+ const progress = estimate.tracks.length === 0
491
+ ? "Progress: unavailable"
492
+ : formatProgressReport(estimate.tracks.map((track) => track.estimate), undefined, new Date(), 2_000);
493
+ const lines = [
494
+ `Monitor: ${observation.route === "stop" ? "stopping" : "active"}`,
495
+ `Goal: ${observation.goalState}`,
496
+ `Work: ${observation.workState}`,
497
+ progress,
498
+ ];
499
+ const lastAction = latestRecordedAction(context);
500
+ if (lastAction !== undefined)
501
+ lines.push(`Last action: ${lastAction.summary}`);
502
+ if (observation.action !== undefined && completedObservations(context) < config.maxChecks) {
503
+ lines.push(`Next action: ${observation.action.nextAction}`);
504
+ }
505
+ if (observation.route === "wait" && completedObservations(context) < config.maxChecks) {
506
+ lines.push(`Next check: ${config.everyMinutes} minutes`);
507
+ }
508
+ if (completedObservations(context) >= config.maxChecks && observation.route !== "stop") {
509
+ lines.push(`Safety limit: reached ${config.maxChecks} observations`);
510
+ }
511
+ lines.push(`Status: ${observation.report}`);
512
+ const message = lines.join("\n");
513
+ return message.length <= MAX_REPORT_CHARS
514
+ ? message
515
+ : `${message.slice(0, MAX_REPORT_CHARS - 1)}…`;
516
+ }
517
+ function previousActionPrompt(context) {
518
+ const actionRecord = latestRecordedAction(context);
519
+ return actionRecord === undefined
520
+ ? "There is no previous completed action."
521
+ : `Previous action result: ${JSON.stringify(actionRecord)}`;
266
522
  }
267
523
  const monitorWorkflow = defineWorkflow({
268
524
  source: import.meta.url,
@@ -282,25 +538,24 @@ const monitorWorkflow = defineWorkflow({
282
538
  includes: {
283
539
  planChange: includeWorkflow(planChangeWorkflow, {
284
540
  input: ({ outputs }) => {
285
- const config = configFrom(outputs);
286
- const repair = outputs.check.repair;
287
- if (repair === undefined)
288
- throw new Error("monitor repair details are missing");
289
- const prior = outputs.planChange;
541
+ const request = actionFrom(outputs);
542
+ if (request.kind !== "repair")
543
+ throw new Error("monitor repair action is missing");
290
544
  return {
291
- task: repair.problem,
292
- ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
293
- ...(config.repair?.constraints !== undefined
294
- ? { constraints: config.repair.constraints }
545
+ task: request.nextAction,
546
+ scope: authorityScope(request),
547
+ constraints: authorityConstraints(request),
548
+ ...(request.authority.repository !== undefined
549
+ ? { repository: request.authority.repository }
295
550
  : {}),
296
- ...(config.repair?.repository !== undefined
297
- ? { repository: config.repair.repository }
298
- : {}),
299
- ...(prior?.exit === "ready" && prior.output?.plan !== undefined
300
- ? { previousPlan: prior.output.plan }
301
- : {}),
302
- newEvidence: repair.evidence,
303
- approval: parsePlanApprovalPolicy(config.repair?.approval),
551
+ newEvidence: {
552
+ incomplete: request.incomplete,
553
+ evidence: request.evidence,
554
+ failureId: request.failureId,
555
+ targetStateId: request.targetStateId,
556
+ defect: request.defect,
557
+ },
558
+ approval: request.authority.repairApproval,
304
559
  };
305
560
  },
306
561
  }),
@@ -308,120 +563,145 @@ const monitorWorkflow = defineWorkflow({
308
563
  workflow: "autoimplement",
309
564
  contract: autoimplementWorkflow,
310
565
  input: ({ outputs }) => {
311
- const config = configFrom(outputs);
312
- const repair = outputs.check.repair;
566
+ const request = actionFrom(outputs);
313
567
  const documented = currentRepairPlan(outputs);
314
- if (repair === undefined)
315
- throw new Error("monitor repair details are missing");
316
- const request = {
317
- task: repair.problem,
568
+ if (request.kind !== "repair")
569
+ throw new Error("monitor repair action is missing");
570
+ if (request.authority.repository === undefined) {
571
+ throw new Error("monitor repair requires an absolute repository for workspace preparation");
572
+ }
573
+ const implementationInput = {
574
+ task: request.nextAction,
318
575
  plan: documented.plan,
319
576
  documentation: {
320
577
  status: "current",
321
578
  planDigest: documented.planDigest,
322
579
  documents: documented.documents,
323
580
  },
324
- ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
325
- ...(config.repair?.constraints !== undefined
326
- ? { constraints: config.repair.constraints }
327
- : {}),
328
- ...(config.repair?.repository !== undefined
329
- ? { repository: config.repair.repository }
581
+ scope: authorityScope(request),
582
+ constraints: authorityConstraints(request),
583
+ repository: request.authority.repository,
584
+ ...(request.authority.baseBranch !== undefined
585
+ ? { baseBranch: request.authority.baseBranch }
330
586
  : {}),
331
- ...(config.repair?.baseBranch !== undefined
332
- ? { baseBranch: config.repair.baseBranch }
333
- : {}),
334
- approval: parsePlanApprovalPolicy(config.repair?.approval),
335
- merge: config.repair?.merge === true,
587
+ approval: request.authority.repairApproval,
588
+ merge: request.authority.merge,
336
589
  };
337
- return request;
590
+ return implementationInput;
338
591
  },
339
592
  }),
340
593
  },
341
594
  nodes: {
342
595
  prepare: compute({ run: ({ input }) => prepareMonitorInput(input) }),
343
- check: agent({
344
- statusDetail: "checking monitored target",
345
- timeoutMs: ({ outputs }) => configFrom(outputs).checkTimeoutMinutes * 60_000,
596
+ observe: agent({
597
+ statusDetail: "observing monitored goal",
598
+ timeoutMs: DEFAULT_CHECK_TIMEOUT_MINUTES * 60_000,
346
599
  prompt: (context) => {
347
600
  const config = configFrom(context.outputs);
348
- const previous = context.outputs.check;
601
+ const previous = context.outputs.observe;
349
602
  const priorEstimate = context.outputs.estimate;
350
603
  return [
351
- `Perform monitoring check ${completedChecks(context) + 1} of at most ${config.maxChecks}.`,
352
- `Task: ${config.task}`,
604
+ `Perform read-only observation ${completedObservations(context) + 1} of at most ${config.maxChecks}.`,
605
+ `Goal and authority: ${config.task}`,
353
606
  `Stop when: ${config.stopWhen}`,
607
+ "Use normal read-only tools to inspect authoritative target state, durable outputs, failures, checkpoints, active work, and applicable authority. Do not mutate any file, process, Job, service, remote resource, or configuration during this step.",
608
+ "Preserve the exact goal, allowed files and systems, forbidden changes, cost ceiling, provider/runtime contract, required checks, stop conditions, and allowed recovery actions from the task, conversation, and repository instructions. Do not invent permission.",
609
+ "List the safe actions the user has already authorized, even when no action is needed now. Choose wait only when useful target work is moving or an external event must finish. Choose act only when the goal is incomplete, work is idle, failed, or stopped, and one safe action is fully authorized. Choose stop when the goal is complete or safe continuation is blocked.",
610
+ "For act, describe one exact action. Use advance for normal next work, recover for an operational restart or resume, and repair only for a code or configuration defect. A normal start, resume, or restart must not become repair. If a paid action lacks verified remaining authority or exceeds its limit, choose stop. Before repair of a shared code or data defect, affected paid workers must already be stopped.",
611
+ "You are the regular Pi model and observation adapter. Publish measured facts with workflow update when useful. Do not require the target to implement a Pi-specific API, file, store, schema, command, service, transport, or dependency. Do not invent counts, rates, or ETA values.",
354
612
  previous === undefined
355
- ? "There is no previous observation."
356
- : `Previous accepted observation: ${previous.observation}`,
613
+ ? "There is no previous accepted observation."
614
+ : `Previous accepted observation: ${JSON.stringify(previous)}`,
615
+ previousActionPrompt(context),
357
616
  priorEstimate?.tracks.length
358
617
  ? `Previous progress: ${formatProgressReport(priorEstimate.tracks.map((track) => track.estimate))}`
359
618
  : "There is no previous measured progress.",
360
- config.repair === undefined
361
- ? "Observe only. This monitor has no mutation authorization."
362
- : "Repair is explicitly authorized within the supplied repair policy. Choose repair only for a concrete issue that can be changed within that scope. Include a stable issue fingerprint based on the issue and observed target state. Do not change protected model, benchmark, credential, hardware, spending, or scope decisions.",
363
- "Use available tools to inspect the current source of truth.",
364
- "You are the regular Pi model running this check and the observation adapter. When useful measurable facts appear during the check, publish them with workflow action update. Include the latest tracks in the final submission. Do not require the monitored target to implement a Pi-specific progress API, file, store, schema, or command.",
365
- "Every accepted check must include a concise user-facing report. Add progress tracks only when the target provides measurable facts. Submit observed counts and target-provided finish times; do not invent rates or an ETA.",
366
- config.repair === undefined
367
- ? "Choose route continue or stop."
368
- : "Choose route continue, repair, or stop.",
369
619
  ].join("\n\n");
370
620
  },
371
- expectedOutput: '{ "route": "continue" | "repair" | "stop", "observation": "current factual state", "report": "concise status update", "progress": { "tracks": [{ "key": "stable-key", "data": { "schema": "pi-workflows.progress.v1", "status": "running", "completed": 1, "total": 2, "unit": "items" } }] } (optional), "repair": { "problem": "fixable issue", "evidence": "observed evidence", "issueFingerprint": "stable issue and target-state fingerprint" } (required for repair), "reason": "short reason" }',
372
- validate: (output, context) => validateMonitorCheck(output, configFrom(context.outputs).repair !== undefined),
621
+ expectedOutput: '{ "route": "wait" | "act" | "stop", "goalState": "complete" | "incomplete" | "blocked", "workState": "running" | "waiting" | "idle" | "failed" | "stopped" | "unknown", "observation": "factual state", "report": "concise factual summary", "targetStateId": "stable observed target-state ID", "authorizedActions": ["safe action already authorized by the user"], "progress": { "tracks": [{ "key": "stable-key", "data": { "schema": "pi-workflows.progress.v1", "status": "running", "completed": 1, "total": 2, "unit": "items" } }] } (optional), "action": { "kind": "advance" | "recover" | "repair", "incomplete": "what remains", "evidence": {}, "nextAction": "one exact action", "authority": { "status": "authorized", "basis": "existing authority", "allowedMutations": ["allowed file, system, or resource"], "forbiddenMutations": [], "costLimit": "recorded limit or not applicable", "providerRuntime": "recorded contract or not applicable", "requiredChecks": [], "stopConditions": [], "allowedRecoveryActions": [], "repository": "optional absolute path", "baseBranch": "optional branch", "merge": false, "repairApproval": { "mode": "auto" | "required" | "skip" } }, "cost": { "paidAction": false, "status": "not-applicable" | "within-limit", "evidence": "cost evidence" }, "defect": { "sharedCodeOrDataDefect": false, "paidWorkers": "not-applicable" | "stopped" | "running", "evidence": "worker evidence" }, "verification": "how to prove success", "failureId": "stable failure ID", "targetStateId": "stable target-state ID" } (required only for act), "reason": "short reason" }',
622
+ validate: validateMonitorObservation,
373
623
  }),
624
+ guard: compute({ run: guardObservation }),
374
625
  estimate: compute({ run: ({ outputs }) => estimateTracks(outputs) }),
375
626
  publish_progress: action({
376
627
  statusDetail: "publishing monitor progress",
377
628
  run: async ({ outputs, publishUpdate, signal }) => {
378
- const check = outputs.check;
379
- if (check.progress === undefined)
629
+ const observation = currentObservation(outputs);
630
+ if (observation.progress === undefined)
380
631
  return { published: 0 };
381
- for (const track of check.progress.tracks) {
632
+ for (const track of observation.progress.tracks) {
382
633
  await waitForUpdateSlot(signal);
383
634
  await publishUpdate({ type: "progress", key: track.key, data: track.data });
384
635
  }
385
- return { published: check.progress.tracks.length };
636
+ return { published: observation.progress.tracks.length };
386
637
  },
387
638
  }),
388
639
  report: notify({
389
640
  statusDetail: "queueing monitor update",
390
- message: (context) => reportMessage(context),
641
+ message: reportMessage,
391
642
  kind: "progress",
392
643
  }),
393
644
  decide: compute({
394
645
  run: (context) => {
395
- const check = context.outputs.check;
646
+ const observation = currentObservation(context.outputs);
396
647
  const config = configFrom(context.outputs);
397
- const checks = completedChecks(context);
398
- if (check.route === "stop")
399
- return { route: "stop", reason: check.reason, checks };
648
+ const checks = completedObservations(context);
649
+ if (observation.route === "stop")
650
+ return { route: "stop", reason: observation.reason, checks };
400
651
  if (checks >= config.maxChecks) {
401
652
  return {
402
653
  route: "stop",
403
- reason: `Reached the ${config.maxChecks}-check safety limit.`,
654
+ reason: `Reached the ${config.maxChecks}-observation safety limit.`,
404
655
  checks,
405
656
  };
406
657
  }
407
- if (check.route === "repair")
408
- return { route: "repair", reason: check.reason, checks };
409
- return { route: "continue", reason: check.reason, checks };
658
+ if (observation.route === "wait")
659
+ return { route: "wait", reason: observation.reason, checks };
660
+ const request = actionFrom(context.outputs);
661
+ return { route: request.kind, reason: observation.reason, checks };
410
662
  },
411
663
  }),
412
- repairGuard: compute({
413
- run: (context) => repeatedRepairWithoutProgress(context)
414
- ? {
415
- route: "blocked",
416
- reason: "The same issue returned after a completed repair with no changed target evidence.",
417
- }
418
- : { route: "repair", reason: "The issue is new or has changed evidence." },
664
+ act: agent({
665
+ statusDetail: "performing authorized monitor action",
666
+ timeoutMs: DEFAULT_CHECK_TIMEOUT_MINUTES * 60_000,
667
+ prompt: ({ outputs }) => {
668
+ const request = actionFrom(outputs);
669
+ if (request.kind === "repair")
670
+ throw new Error("repair must use the composed repair path");
671
+ return [
672
+ `Perform this one ${request.kind} action with normal tools: ${request.nextAction}`,
673
+ `Incomplete work: ${request.incomplete}`,
674
+ `Evidence: ${JSON.stringify(request.evidence)}`,
675
+ `Authorization: ${JSON.stringify(request.authority)}`,
676
+ `Cost safety: ${JSON.stringify(request.cost)}`,
677
+ `Defect safety: ${JSON.stringify(request.defect)}`,
678
+ `Verification: ${request.verification}`,
679
+ "Perform only the stated action and only on the allowed files, systems, and resources. Do not plan, document, redesign, broaden scope, change a protected contract, or perform another action. Verify the direct result before submitting. Preserve the supplied failure and target-state IDs exactly.",
680
+ ].join("\n\n");
681
+ },
682
+ expectedOutput: '{ "status": "succeeded" | "failed" | "blocked", "summary": "action performed and real result", "evidence": {}, "verification": "verification performed", "failureId": "unchanged failure ID", "targetStateId": "unchanged target-state ID" }',
683
+ validate: (output, context) => validateMonitorActionResult(output, actionFrom(context.outputs)),
684
+ }),
685
+ repairComplete: compute({
686
+ run: ({ outputs }) => {
687
+ const request = actionFrom(outputs);
688
+ if (request.kind !== "repair")
689
+ throw new Error("monitor repair action is missing");
690
+ return {
691
+ status: "succeeded",
692
+ summary: request.nextAction,
693
+ evidence: outputs.implementation,
694
+ verification: request.verification,
695
+ failureId: request.failureId,
696
+ targetStateId: request.targetStateId,
697
+ };
698
+ },
419
699
  }),
420
700
  repairBlocked: compute({
421
701
  run: (context) => ({
422
702
  reason: repairBlockedReason(context.outputs),
423
- observation: context.outputs.check.observation,
424
- checks: completedChecks(context),
703
+ observation: currentObservation(context.outputs).observation,
704
+ checks: completedObservations(context),
425
705
  reported: true,
426
706
  }),
427
707
  }),
@@ -430,11 +710,11 @@ const monitorWorkflow = defineWorkflow({
430
710
  kind: "final",
431
711
  message: ({ outputs }) => {
432
712
  const result = outputs.repairBlocked;
433
- return `Automatic repair stopped: ${result.reason}`;
713
+ return `Monitor repair stopped: ${result.reason}`;
434
714
  },
435
715
  }),
436
716
  schedule: action({
437
- statusDetail: "scheduling next monitor check",
717
+ statusDetail: "scheduling next monitor observation",
438
718
  run: async ({ outputs, publishUpdate }) => {
439
719
  const config = configFrom(outputs);
440
720
  const lastCheckAt = new Date().toISOString();
@@ -453,7 +733,7 @@ const monitorWorkflow = defineWorkflow({
453
733
  },
454
734
  }),
455
735
  sleep: shell({
456
- statusDetail: "waiting for next monitor check",
736
+ statusDetail: "waiting for next monitor observation",
457
737
  timeoutMs: MAX_INTERVAL_MINUTES * 60_000 + NODE_TIMEOUT_MARGIN_MS,
458
738
  exec: ({ outputs }) => {
459
739
  const sleepMs = configFrom(outputs).everyMinutes * 60_000;
@@ -468,26 +748,24 @@ const monitorWorkflow = defineWorkflow({
468
748
  }),
469
749
  finish: compute({
470
750
  run: ({ outputs }) => {
471
- const check = outputs.check;
751
+ const observation = currentObservation(outputs);
472
752
  const decision = outputs.decide;
473
753
  const repair = outputs.repairBlocked;
474
754
  return {
475
- reason: repair?.reason ?? decision?.reason ?? check.reason,
476
- observation: check.observation,
755
+ reason: repair?.reason ?? decision?.reason ?? observation.reason,
756
+ observation: observation.observation,
757
+ goalState: repair === undefined ? observation.goalState : "blocked",
758
+ workState: observation.workState,
477
759
  checks: decision?.checks ?? 1,
478
760
  reported: true,
479
- ...(outputs.implementation !== undefined
480
- ? { repair: outputs.implementation }
481
- : repair !== undefined
482
- ? { repair }
483
- : {}),
484
761
  };
485
762
  },
486
763
  }),
487
764
  },
488
765
  edges: [
489
- { from: "prepare", to: "check" },
490
- { from: "check", to: "estimate" },
766
+ { from: "prepare", to: "observe" },
767
+ { from: "observe", to: "guard" },
768
+ { from: "guard", to: "estimate" },
491
769
  { from: "estimate", to: "publish_progress" },
492
770
  { from: "publish_progress", to: "report" },
493
771
  { from: "report", to: "decide" },
@@ -495,21 +773,25 @@ const monitorWorkflow = defineWorkflow({
495
773
  from: "decide",
496
774
  switch: {
497
775
  on: "$.route",
498
- cases: { stop: "finish", continue: "schedule", repair: "repairGuard" },
776
+ cases: {
777
+ stop: "finish",
778
+ wait: "schedule",
779
+ advance: "act",
780
+ recover: "act",
781
+ repair: "planChange",
782
+ },
499
783
  },
500
784
  },
501
- {
502
- from: "repairGuard",
503
- switch: { on: "$.route", cases: { repair: "planChange", blocked: "repairBlocked" } },
504
- },
785
+ { from: "act", to: "observe" },
505
786
  { from: "planChange.ready", to: "implementation" },
506
787
  { from: "planChange.blocked", to: "repairBlocked" },
507
- { from: "implementation.completed", to: "check" },
788
+ { from: "implementation.completed", to: "repairComplete" },
508
789
  { from: "implementation.blocked", to: "repairBlocked" },
790
+ { from: "repairComplete", to: "observe" },
509
791
  { from: "repairBlocked", to: "repairReport" },
510
792
  { from: "repairReport", to: "finish" },
511
793
  { from: "schedule", to: "sleep" },
512
- { from: "sleep", to: "check" },
794
+ { from: "sleep", to: "observe" },
513
795
  ],
514
796
  });
515
797
  export default monitorWorkflow;