@osolmaz/pi-workflows 0.9.1 → 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 +64 -19
- 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 +353 -0
- package/dist/builtins/autoimplement.workflow.js +961 -0
- package/dist/builtins/autoimplement.workflow.js.map +1 -0
- package/dist/builtins/autoplan.workflow.d.ts +58 -0
- package/dist/builtins/autoplan.workflow.js +190 -0
- package/dist/builtins/autoplan.workflow.js.map +1 -0
- package/dist/builtins/catalog.js +9 -1
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +6 -0
- package/dist/builtins/index.js +7 -0
- package/dist/builtins/index.js.map +1 -0
- package/dist/builtins/monitor.workflow.d.ts +29 -3
- package/dist/builtins/monitor.workflow.js +325 -13
- 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 +46 -10
- 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/catalog.d.ts +1 -0
- package/dist/workflows/catalog.js +6 -0
- package/dist/workflows/catalog.js.map +1 -1
- package/dist/workflows/composition.d.ts +45 -0
- package/dist/workflows/composition.js +485 -0
- package/dist/workflows/composition.js.map +1 -0
- 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/decision.d.ts +11 -5
- package/dist/workflows/decision.js.map +1 -1
- package/dist/workflows/definition.d.ts +22 -3
- package/dist/workflows/definition.js +46 -3
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +2 -1
- package/dist/workflows/engine.js +199 -18
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/graph.js +8 -6
- package/dist/workflows/graph.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 +5 -2
- package/dist/workflows/index.js +4 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/loader.d.ts +5 -4
- package/dist/workflows/loader.js +118 -18
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/schema.d.ts +3 -1
- package/dist/workflows/schema.js +64 -2
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.js +39 -2
- 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 +300 -2
- package/docs/CONTROLLERS.md +1 -1
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +371 -0
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
- package/docs/MONITOR.md +46 -18
- package/docs/WORKFLOW_COMPOSITION.md +333 -0
- 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 +308 -0
- package/docs/run-bundles.md +45 -10
- package/docs/workflows.md +106 -15
- package/examples/workflows/approved-plan.workflow.ts +58 -0
- package/examples/workflows/autoimplement.workflow.ts +1 -92
- 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 +6 -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 +8 -1
- package/skills/pi-workflows/SKILL.md +5 -3
- package/src/builtins/autodoc.workflow.ts +325 -0
- package/src/builtins/autoimplement.workflow.ts +1123 -0
- package/src/builtins/autoplan.workflow.ts +231 -0
- package/src/builtins/catalog.ts +9 -1
- package/src/builtins/index.ts +32 -0
- package/src/builtins/monitor.workflow.ts +396 -15
- 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 +54 -5
- package/src/viewer/render.ts +56 -3
- package/src/workflows/catalog.ts +7 -0
- package/src/workflows/composition.ts +642 -0
- package/src/workflows/decision-presentation.ts +501 -0
- package/src/workflows/decision.ts +12 -5
- package/src/workflows/definition.ts +118 -8
- package/src/workflows/engine.ts +257 -20
- package/src/workflows/graph.ts +8 -6
- package/src/workflows/human-decision.ts +819 -0
- package/src/workflows/index.ts +89 -0
- package/src/workflows/loader.ts +186 -18
- package/src/workflows/schema.ts +79 -2
- package/src/workflows/store.ts +45 -2
- package/src/workflows/tool-input.ts +202 -0
- package/src/workflows/types.ts +374 -2
- package/examples/workflows/elegant-solution.workflow.ts +0 -95
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
action,
|
|
3
|
+
agent,
|
|
4
|
+
compute,
|
|
5
|
+
defineWorkflow,
|
|
6
|
+
includeWorkflow,
|
|
7
|
+
includedResult,
|
|
8
|
+
notify,
|
|
9
|
+
shell,
|
|
10
|
+
} from "../workflows/definition.js";
|
|
2
11
|
import {
|
|
3
12
|
estimateProgress,
|
|
4
13
|
formatProgressReport,
|
|
@@ -11,6 +20,10 @@ import type {
|
|
|
11
20
|
WorkflowProgressData,
|
|
12
21
|
} from "../workflows/types.js";
|
|
13
22
|
import { validateProgressData } from "../workflows/updates.js";
|
|
23
|
+
import autodocWorkflow, { type AutodocInput } from "./autodoc.workflow.js";
|
|
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";
|
|
14
27
|
|
|
15
28
|
const MIN_INTERVAL_MINUTES = 1;
|
|
16
29
|
const MAX_INTERVAL_MINUTES = 24 * 60;
|
|
@@ -27,22 +40,49 @@ const MAX_REASON_CHARS = 2_000;
|
|
|
27
40
|
const SLEEP_TIMEOUT_MARGIN_MS = 60_000;
|
|
28
41
|
const NODE_TIMEOUT_MARGIN_MS = 2 * 60_000;
|
|
29
42
|
|
|
43
|
+
export type MonitorRepairPolicy = {
|
|
44
|
+
authorized: true;
|
|
45
|
+
scope?: string;
|
|
46
|
+
constraints?: string[];
|
|
47
|
+
repository?: string;
|
|
48
|
+
baseBranch?: string;
|
|
49
|
+
merge?: boolean;
|
|
50
|
+
approval?: {
|
|
51
|
+
audience: string;
|
|
52
|
+
maxReplans: number;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
30
56
|
export type MonitorInput = {
|
|
31
57
|
task: string;
|
|
32
58
|
everyMinutes?: number;
|
|
33
59
|
stopWhen?: string;
|
|
34
60
|
maxChecks?: number;
|
|
35
61
|
checkTimeoutMinutes?: number;
|
|
62
|
+
repair?: MonitorRepairPolicy;
|
|
36
63
|
};
|
|
37
64
|
|
|
38
|
-
type MonitorConfig =
|
|
39
|
-
|
|
65
|
+
type MonitorConfig = {
|
|
66
|
+
task: string;
|
|
67
|
+
everyMinutes: number;
|
|
68
|
+
stopWhen: string;
|
|
69
|
+
maxChecks: number;
|
|
70
|
+
checkTimeoutMinutes: number;
|
|
71
|
+
repair?: MonitorRepairPolicy;
|
|
72
|
+
};
|
|
73
|
+
type MonitorRoute = "continue" | "repair" | "stop";
|
|
40
74
|
type MonitorTrack = { key: string; data: WorkflowProgressData };
|
|
75
|
+
type MonitorRepairRequest = {
|
|
76
|
+
problem: string;
|
|
77
|
+
evidence: unknown;
|
|
78
|
+
issueFingerprint: string;
|
|
79
|
+
};
|
|
41
80
|
type MonitorCheck = {
|
|
42
81
|
route: MonitorRoute;
|
|
43
82
|
observation: string;
|
|
44
83
|
report: string;
|
|
45
84
|
progress?: { tracks: MonitorTrack[] };
|
|
85
|
+
repair?: MonitorRepairRequest;
|
|
46
86
|
reason: string;
|
|
47
87
|
};
|
|
48
88
|
type MonitorEstimate = { tracks: ProgressTrackState[] };
|
|
@@ -79,7 +119,14 @@ async function waitForUpdateSlot(signal: AbortSignal): Promise<void> {
|
|
|
79
119
|
|
|
80
120
|
export function prepareMonitorInput(input: unknown): MonitorConfig {
|
|
81
121
|
const value = requireRecord(input, "monitor input") as Partial<MonitorInput>;
|
|
82
|
-
const allowed = new Set([
|
|
122
|
+
const allowed = new Set([
|
|
123
|
+
"task",
|
|
124
|
+
"everyMinutes",
|
|
125
|
+
"stopWhen",
|
|
126
|
+
"maxChecks",
|
|
127
|
+
"checkTimeoutMinutes",
|
|
128
|
+
"repair",
|
|
129
|
+
]);
|
|
83
130
|
for (const field of Object.keys(value)) {
|
|
84
131
|
if (!allowed.has(field)) throw new Error(`monitor input field ${field} is not supported`);
|
|
85
132
|
}
|
|
@@ -109,6 +156,51 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
|
|
|
109
156
|
`checkTimeoutMinutes must be an integer from ${MIN_CHECK_TIMEOUT_MINUTES} through ${MAX_CHECK_TIMEOUT_MINUTES}`,
|
|
110
157
|
);
|
|
111
158
|
}
|
|
159
|
+
let repair: MonitorRepairPolicy | undefined;
|
|
160
|
+
if (value.repair !== undefined) {
|
|
161
|
+
const raw = requireRecord(value.repair, "repair policy");
|
|
162
|
+
if (raw.authorized !== true) throw new Error("repair policy must set authorized to true");
|
|
163
|
+
if (
|
|
164
|
+
raw.constraints !== undefined &&
|
|
165
|
+
(!Array.isArray(raw.constraints) || raw.constraints.some((item) => typeof item !== "string"))
|
|
166
|
+
) {
|
|
167
|
+
throw new Error("repair constraints must be an array of strings");
|
|
168
|
+
}
|
|
169
|
+
if (raw.merge !== undefined && typeof raw.merge !== "boolean") {
|
|
170
|
+
throw new Error("repair merge must be a boolean");
|
|
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
|
+
}
|
|
188
|
+
repair = {
|
|
189
|
+
authorized: true,
|
|
190
|
+
...(raw.scope !== undefined
|
|
191
|
+
? { scope: requireBoundedString(raw.scope, "repair scope", 4_000) }
|
|
192
|
+
: {}),
|
|
193
|
+
...(raw.constraints !== undefined ? { constraints: [...raw.constraints] as string[] } : {}),
|
|
194
|
+
...(raw.repository !== undefined
|
|
195
|
+
? { repository: requireBoundedString(raw.repository, "repair repository", 4_000) }
|
|
196
|
+
: {}),
|
|
197
|
+
...(raw.baseBranch !== undefined
|
|
198
|
+
? { baseBranch: requireBoundedString(raw.baseBranch, "repair base branch", 256) }
|
|
199
|
+
: {}),
|
|
200
|
+
...(raw.merge !== undefined ? { merge: raw.merge !== false } : {}),
|
|
201
|
+
...(approval !== undefined ? { approval } : {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
112
204
|
return {
|
|
113
205
|
task,
|
|
114
206
|
everyMinutes,
|
|
@@ -118,6 +210,7 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
|
|
|
118
210
|
: requireBoundedString(value.stopWhen, "stopWhen", 4_000),
|
|
119
211
|
maxChecks,
|
|
120
212
|
checkTimeoutMinutes,
|
|
213
|
+
...(repair !== undefined ? { repair } : {}),
|
|
121
214
|
};
|
|
122
215
|
}
|
|
123
216
|
|
|
@@ -130,13 +223,17 @@ function completedChecks(context: WorkflowNodeContext): number {
|
|
|
130
223
|
.length;
|
|
131
224
|
}
|
|
132
225
|
|
|
133
|
-
export function validateMonitorCheck(output: unknown): MonitorCheck {
|
|
226
|
+
export function validateMonitorCheck(output: unknown, repairAuthorized = false): MonitorCheck {
|
|
134
227
|
const value = requireRecord(output, "monitor check output");
|
|
135
|
-
const allowed = new Set(["route", "observation", "report", "progress", "reason"]);
|
|
228
|
+
const allowed = new Set(["route", "observation", "report", "progress", "repair", "reason"]);
|
|
136
229
|
for (const key of Object.keys(value))
|
|
137
230
|
if (!allowed.has(key)) throw new Error(`monitor check field ${key} is not supported`);
|
|
138
|
-
if (value.route !== "continue" && value.route !== "stop")
|
|
139
|
-
throw new Error("route must be continue or stop");
|
|
231
|
+
if (value.route !== "continue" && value.route !== "repair" && value.route !== "stop") {
|
|
232
|
+
throw new Error("route must be continue, repair, or stop");
|
|
233
|
+
}
|
|
234
|
+
if (value.route === "repair" && !repairAuthorized) {
|
|
235
|
+
throw new Error("route repair requires explicit monitor repair authorization");
|
|
236
|
+
}
|
|
140
237
|
const check: MonitorCheck = {
|
|
141
238
|
route: value.route,
|
|
142
239
|
observation: requireBoundedString(value.observation, "observation", MAX_OBSERVATION_CHARS),
|
|
@@ -144,6 +241,21 @@ export function validateMonitorCheck(output: unknown): MonitorCheck {
|
|
|
144
241
|
reason: requireBoundedString(value.reason, "reason", MAX_REASON_CHARS),
|
|
145
242
|
};
|
|
146
243
|
if (value.progress !== undefined) check.progress = validateMonitorProgress(value.progress);
|
|
244
|
+
if (value.repair !== undefined) {
|
|
245
|
+
const repair = requireRecord(value.repair, "monitor repair request");
|
|
246
|
+
check.repair = {
|
|
247
|
+
problem: requireBoundedString(repair.problem, "repair problem", 8_000),
|
|
248
|
+
evidence: repair.evidence ?? null,
|
|
249
|
+
issueFingerprint: requireBoundedString(
|
|
250
|
+
repair.issueFingerprint,
|
|
251
|
+
"repair issue fingerprint",
|
|
252
|
+
256,
|
|
253
|
+
),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (value.route === "repair" && check.repair === undefined) {
|
|
257
|
+
throw new Error("route repair requires repair details");
|
|
258
|
+
}
|
|
147
259
|
return check;
|
|
148
260
|
}
|
|
149
261
|
|
|
@@ -190,6 +302,88 @@ function estimateTracks(outputs: Record<string, unknown>): MonitorEstimate {
|
|
|
190
302
|
};
|
|
191
303
|
}
|
|
192
304
|
|
|
305
|
+
function repeatedRepairWithoutProgress(context: WorkflowNodeContext): boolean {
|
|
306
|
+
const current = context.outputs.check as MonitorCheck;
|
|
307
|
+
const fingerprint = current.repair?.issueFingerprint;
|
|
308
|
+
if (fingerprint === undefined) return false;
|
|
309
|
+
const steps = context.state.steps;
|
|
310
|
+
const currentCheckIndex = steps.findLastIndex((step) => step.nodeId === "check");
|
|
311
|
+
for (let index = currentCheckIndex - 1; index >= 0; index -= 1) {
|
|
312
|
+
const step = steps[index];
|
|
313
|
+
if (step?.nodeId !== "check") continue;
|
|
314
|
+
const prior = step.output as MonitorCheck;
|
|
315
|
+
if (prior.repair?.issueFingerprint !== fingerprint) continue;
|
|
316
|
+
return steps
|
|
317
|
+
.slice(index + 1, currentCheckIndex)
|
|
318
|
+
.some((candidate) => candidate.nodeId === "implementation");
|
|
319
|
+
}
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
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
|
+
|
|
363
|
+
function repairBlockedReason(outputs: Record<string, unknown>): string {
|
|
364
|
+
const guard = outputs.repairGuard as { reason?: string; route?: string } | undefined;
|
|
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;
|
|
373
|
+
const implementation = outputs.implementation as
|
|
374
|
+
| { exit?: string; output?: { reason?: string } }
|
|
375
|
+
| undefined;
|
|
376
|
+
const design = outputs.initialDesign as
|
|
377
|
+
| { exit?: string; output?: { reason?: string } }
|
|
378
|
+
| undefined;
|
|
379
|
+
return (
|
|
380
|
+
implementation?.output?.reason ??
|
|
381
|
+
documentation?.output?.reason ??
|
|
382
|
+
design?.output?.reason ??
|
|
383
|
+
"The repair did not produce new verified progress."
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
193
387
|
function reportMessage(context: WorkflowNodeContext): string {
|
|
194
388
|
const check = context.outputs.check as MonitorCheck;
|
|
195
389
|
const estimate = context.outputs.estimate as MonitorEstimate;
|
|
@@ -219,7 +413,10 @@ function reportMessage(context: WorkflowNodeContext): string {
|
|
|
219
413
|
}
|
|
220
414
|
|
|
221
415
|
const monitorWorkflow: WorkflowDefinition = defineWorkflow({
|
|
416
|
+
source: import.meta.url,
|
|
417
|
+
contractId: "pi-workflows.monitor.v1",
|
|
222
418
|
name: "monitor",
|
|
419
|
+
input: prepareMonitorInput,
|
|
223
420
|
title: ({ input }) => {
|
|
224
421
|
try {
|
|
225
422
|
return `monitor: ${prepareMonitorInput(input).task.slice(0, 80)}`;
|
|
@@ -228,7 +425,104 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
|
|
|
228
425
|
}
|
|
229
426
|
},
|
|
230
427
|
startAt: "prepare",
|
|
231
|
-
maxSteps:
|
|
428
|
+
maxSteps: 200_000,
|
|
429
|
+
includes: {
|
|
430
|
+
initialDesign: includeWorkflow({
|
|
431
|
+
workflow: "autoplan",
|
|
432
|
+
contract: autoplanWorkflow,
|
|
433
|
+
input: ({ outputs }) => {
|
|
434
|
+
const config = configFrom(outputs);
|
|
435
|
+
const repair = (outputs.check as MonitorCheck).repair;
|
|
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);
|
|
441
|
+
return {
|
|
442
|
+
problem: repair.problem,
|
|
443
|
+
...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
|
|
444
|
+
...(config.repair?.constraints !== undefined
|
|
445
|
+
? { constraints: config.repair.constraints }
|
|
446
|
+
: {}),
|
|
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,
|
|
491
|
+
};
|
|
492
|
+
},
|
|
493
|
+
}),
|
|
494
|
+
implementation: includeWorkflow({
|
|
495
|
+
workflow: "autoimplement",
|
|
496
|
+
contract: autoimplementWorkflow,
|
|
497
|
+
input: ({ outputs }) => {
|
|
498
|
+
const config = configFrom(outputs);
|
|
499
|
+
const repair = (outputs.check as MonitorCheck).repair;
|
|
500
|
+
const documented = currentRepairPlan(outputs);
|
|
501
|
+
if (repair === undefined) throw new Error("monitor repair details are missing");
|
|
502
|
+
const request: AutoimplementInput = {
|
|
503
|
+
task: repair.problem,
|
|
504
|
+
plan: documented.plan,
|
|
505
|
+
documentation: {
|
|
506
|
+
status: "current",
|
|
507
|
+
planDigest: documented.planDigest,
|
|
508
|
+
documents: documented.documents,
|
|
509
|
+
},
|
|
510
|
+
...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
|
|
511
|
+
...(config.repair?.constraints !== undefined
|
|
512
|
+
? { constraints: config.repair.constraints }
|
|
513
|
+
: {}),
|
|
514
|
+
...(config.repair?.repository !== undefined
|
|
515
|
+
? { repository: config.repair.repository }
|
|
516
|
+
: {}),
|
|
517
|
+
...(config.repair?.baseBranch !== undefined
|
|
518
|
+
? { baseBranch: config.repair.baseBranch }
|
|
519
|
+
: {}),
|
|
520
|
+
merge: config.repair?.merge === true,
|
|
521
|
+
};
|
|
522
|
+
return request;
|
|
523
|
+
},
|
|
524
|
+
}),
|
|
525
|
+
},
|
|
232
526
|
nodes: {
|
|
233
527
|
prepare: compute({ run: ({ input }) => prepareMonitorInput(input) }),
|
|
234
528
|
check: agent({
|
|
@@ -248,15 +542,21 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
|
|
|
248
542
|
priorEstimate?.tracks.length
|
|
249
543
|
? `Previous progress: ${formatProgressReport(priorEstimate.tracks.map((track) => track.estimate))}`
|
|
250
544
|
: "There is no previous measured progress.",
|
|
251
|
-
|
|
545
|
+
config.repair === undefined
|
|
546
|
+
? "Observe only. This monitor has no mutation authorization."
|
|
547
|
+
: "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.",
|
|
548
|
+
"Use available tools to inspect the current source of truth.",
|
|
252
549
|
"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.",
|
|
253
550
|
"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.",
|
|
254
|
-
|
|
551
|
+
config.repair === undefined
|
|
552
|
+
? "Choose route continue or stop."
|
|
553
|
+
: "Choose route continue, repair, or stop.",
|
|
255
554
|
].join("\n\n");
|
|
256
555
|
},
|
|
257
556
|
expectedOutput:
|
|
258
|
-
'{ "route": "continue" | "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), "reason": "short reason" }',
|
|
259
|
-
validate: (output) =>
|
|
557
|
+
'{ "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" }',
|
|
558
|
+
validate: (output, context) =>
|
|
559
|
+
validateMonitorCheck(output, configFrom(context.outputs).repair !== undefined),
|
|
260
560
|
}),
|
|
261
561
|
estimate: compute({ run: ({ outputs }) => estimateTracks(outputs) }),
|
|
262
562
|
publish_progress: action({
|
|
@@ -289,9 +589,55 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
|
|
|
289
589
|
checks,
|
|
290
590
|
};
|
|
291
591
|
}
|
|
592
|
+
if (check.route === "repair") return { route: "repair", reason: check.reason, checks };
|
|
292
593
|
return { route: "continue", reason: check.reason, checks };
|
|
293
594
|
},
|
|
294
595
|
}),
|
|
596
|
+
repairGuard: compute({
|
|
597
|
+
run: (context) =>
|
|
598
|
+
repeatedRepairWithoutProgress(context)
|
|
599
|
+
? {
|
|
600
|
+
route: "blocked",
|
|
601
|
+
reason:
|
|
602
|
+
"The same issue returned after a completed repair with no changed target evidence.",
|
|
603
|
+
}
|
|
604
|
+
: { route: "repair", reason: "The issue is new or has changed evidence." },
|
|
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
|
+
}),
|
|
625
|
+
repairBlocked: compute({
|
|
626
|
+
run: (context) => ({
|
|
627
|
+
reason: repairBlockedReason(context.outputs),
|
|
628
|
+
observation: (context.outputs.check as MonitorCheck).observation,
|
|
629
|
+
checks: completedChecks(context),
|
|
630
|
+
reported: true,
|
|
631
|
+
}),
|
|
632
|
+
}),
|
|
633
|
+
repairReport: notify({
|
|
634
|
+
statusDetail: "reporting blocked monitor repair",
|
|
635
|
+
kind: "final",
|
|
636
|
+
message: ({ outputs }) => {
|
|
637
|
+
const result = outputs.repairBlocked as { reason: string };
|
|
638
|
+
return `Automatic repair stopped: ${result.reason}`;
|
|
639
|
+
},
|
|
640
|
+
}),
|
|
295
641
|
schedule: action({
|
|
296
642
|
statusDetail: "scheduling next monitor check",
|
|
297
643
|
run: async ({ outputs, publishUpdate }) => {
|
|
@@ -331,11 +677,17 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
|
|
|
331
677
|
run: ({ outputs }) => {
|
|
332
678
|
const check = outputs.check as MonitorCheck;
|
|
333
679
|
const decision = outputs.decide as { reason?: string; checks?: number } | undefined;
|
|
680
|
+
const repair = outputs.repairBlocked as { reason?: string } | undefined;
|
|
334
681
|
return {
|
|
335
|
-
reason: decision?.reason ?? check.reason,
|
|
682
|
+
reason: repair?.reason ?? decision?.reason ?? check.reason,
|
|
336
683
|
observation: check.observation,
|
|
337
684
|
checks: decision?.checks ?? 1,
|
|
338
685
|
reported: true,
|
|
686
|
+
...(outputs.implementation !== undefined
|
|
687
|
+
? { repair: outputs.implementation }
|
|
688
|
+
: repair !== undefined
|
|
689
|
+
? { repair }
|
|
690
|
+
: {}),
|
|
339
691
|
};
|
|
340
692
|
},
|
|
341
693
|
}),
|
|
@@ -346,7 +698,36 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
|
|
|
346
698
|
{ from: "estimate", to: "publish_progress" },
|
|
347
699
|
{ from: "publish_progress", to: "report" },
|
|
348
700
|
{ from: "report", to: "decide" },
|
|
349
|
-
{
|
|
701
|
+
{
|
|
702
|
+
from: "decide",
|
|
703
|
+
switch: {
|
|
704
|
+
on: "$.route",
|
|
705
|
+
cases: { stop: "finish", continue: "schedule", repair: "repairGuard" },
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
from: "repairGuard",
|
|
710
|
+
switch: { on: "$.route", cases: { repair: "initialDesign", blocked: "repairBlocked" } },
|
|
711
|
+
},
|
|
712
|
+
{ from: "initialDesign.ready", to: "documentation" },
|
|
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
|
+
},
|
|
727
|
+
{ from: "implementation.completed", to: "check" },
|
|
728
|
+
{ from: "implementation.blocked", to: "repairBlocked" },
|
|
729
|
+
{ from: "repairBlocked", to: "repairReport" },
|
|
730
|
+
{ from: "repairReport", to: "finish" },
|
|
350
731
|
{ from: "schedule", to: "sleep" },
|
|
351
732
|
{ from: "sleep", to: "check" },
|
|
352
733
|
],
|