@wichayutdew/pi-workflows 0.3.0 → 1.0.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.
- package/README.md +55 -39
- package/dist/index.js +5771 -4794
- package/examples/mr-comments.workflow.yaml +2 -0
- package/package.json +1 -1
- package/schemas/workflow.schema.json +10 -1
- package/src/command-names.ts +7 -1
- package/src/commands.ts +126 -52
- package/src/config/catalog.ts +162 -0
- package/src/config/ceiling.ts +120 -116
- package/src/config/command-conflicts.ts +4 -4
- package/src/config/diagnostics.ts +28 -0
- package/src/config/load-settings.ts +37 -0
- package/src/config/load-types.ts +53 -0
- package/src/config/load-workflows.ts +135 -0
- package/src/config/load.ts +43 -311
- package/src/config/types.ts +150 -137
- package/src/config/validate.ts +12 -1261
- package/src/config/validation/permissions.ts +312 -0
- package/src/config/validation/prompt.ts +21 -0
- package/src/config/validation/settings.ts +122 -0
- package/src/config/validation/shared.ts +129 -0
- package/src/config/validation/shortcut.ts +118 -0
- package/src/config/validation/step.ts +228 -0
- package/src/config/validation/subagent.ts +288 -0
- package/src/config/validation/workflow.ts +156 -0
- package/src/config/yaml.ts +29 -0
- package/src/digest.ts +38 -6
- package/src/engine/checkpoint.ts +13 -9
- package/src/engine/create-run.ts +43 -0
- package/src/engine/gate-transitions.ts +187 -0
- package/src/engine/reconciliation-history.ts +88 -0
- package/src/engine/resume.ts +19 -7
- package/src/engine/run-advance.ts +122 -0
- package/src/engine/run-lifecycle.ts +124 -0
- package/src/engine/run-reconciliation.ts +116 -0
- package/src/engine/run-validation.ts +129 -0
- package/src/engine/state-types.ts +66 -0
- package/src/engine/state.ts +10 -193
- package/src/engine/transition-helpers.ts +28 -0
- package/src/engine/transition-types.ts +8 -0
- package/src/engine/transitions.ts +17 -471
- package/src/harness/action-context.ts +181 -0
- package/src/harness/catalog.ts +45 -0
- package/src/harness/context-idle.ts +21 -0
- package/src/harness/core-actions.ts +299 -0
- package/src/harness/delegation-control-actions.ts +250 -0
- package/src/harness/delegation-failure.ts +215 -0
- package/src/harness/delegation-plan.ts +219 -0
- package/src/harness/delegation-recovery-validation.ts +159 -0
- package/src/harness/delegation-response-actions.ts +295 -0
- package/src/harness/delegation-retry-policy.ts +118 -0
- package/src/harness/dependencies.ts +165 -0
- package/src/harness/gate-submission-action.ts +154 -0
- package/src/harness/lifecycle-actions.ts +154 -0
- package/src/harness/pause-actions.ts +120 -0
- package/src/harness/plannotator-result-actions.ts +123 -0
- package/src/harness/prompt-gate-actions.ts +270 -0
- package/src/harness/resume-action.ts +261 -0
- package/src/harness/start-actions.ts +183 -0
- package/src/harness/status-actions.ts +191 -0
- package/src/harness/step-execution-actions.ts +283 -0
- package/src/harness/types.ts +76 -0
- package/src/harness.ts +236 -2303
- package/src/index.ts +73 -11
- package/src/integrations/plannotator-requests.ts +127 -0
- package/src/integrations/plannotator-responses.ts +141 -0
- package/src/integrations/plannotator-types.ts +44 -0
- package/src/integrations/plannotator.ts +16 -235
- package/src/integrations/prompt-gate.ts +17 -10
- package/src/integrations/subagents/child-policy-envelope.ts +119 -0
- package/src/integrations/subagents/child-policy-paths.ts +66 -0
- package/src/integrations/subagents/child-policy-sections.ts +186 -0
- package/src/integrations/subagents/child-policy-types.ts +32 -0
- package/src/integrations/subagents/child-policy-validation.ts +163 -0
- package/src/integrations/subagents/child-runtime-completion.ts +56 -0
- package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
- package/src/integrations/subagents/child-runtime-files.ts +156 -0
- package/src/integrations/subagents/child-runtime-policy.ts +83 -0
- package/src/integrations/subagents/child-runtime-types.ts +30 -0
- package/src/integrations/subagents/child-runtime.ts +140 -301
- package/src/integrations/subagents/client-delegation.ts +181 -0
- package/src/integrations/subagents/client-messages.ts +66 -0
- package/src/integrations/subagents/client-types.ts +36 -0
- package/src/integrations/subagents/client.ts +127 -219
- package/src/integrations/subagents/delegated-result.ts +31 -0
- package/src/integrations/subagents/diagnostic-format.ts +40 -0
- package/src/integrations/subagents/diagnostic-text.ts +114 -0
- package/src/integrations/subagents/diagnostic-types.ts +77 -0
- package/src/integrations/subagents/diagnostics.ts +24 -977
- package/src/integrations/subagents/failure-correlation.ts +235 -0
- package/src/integrations/subagents/failure-transcript.ts +192 -0
- package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
- package/src/integrations/subagents/protocol-events.ts +27 -0
- package/src/integrations/subagents/protocol.ts +30 -375
- package/src/integrations/subagents/replay-audit.ts +147 -0
- package/src/integrations/subagents/replay-safety.ts +82 -0
- package/src/integrations/subagents/session-diagnostics.ts +258 -0
- package/src/policy/approved-command-extraction.ts +71 -0
- package/src/policy/approved-commands.ts +15 -432
- package/src/policy/bash-authorization.ts +86 -0
- package/src/policy/bash-read-only.ts +161 -0
- package/src/policy/bash-types.ts +20 -0
- package/src/policy/bash.ts +4 -346
- package/src/policy/completion-batch.ts +21 -16
- package/src/policy/immutable-input.ts +5 -2
- package/src/policy/mcp-authorization.ts +71 -0
- package/src/policy/restricted-command.ts +141 -0
- package/src/policy/restricted-git.ts +30 -0
- package/src/policy/reviewed-artifact.ts +97 -0
- package/src/policy/reviewed-command-safety.ts +150 -0
- package/src/policy/reviewed-command-shape.ts +49 -0
- package/src/policy/reviewed-repository-contract.ts +138 -0
- package/src/policy/reviewed-repository-cwd.ts +102 -0
- package/src/policy/tool-call-authorization.ts +58 -0
- package/src/policy/tool-selection.ts +72 -0
- package/src/policy/tool-types.ts +14 -0
- package/src/policy/tools.ts +11 -150
- package/src/preflight.ts +96 -61
- package/src/prompt/main-workflow-notice.ts +41 -0
- package/src/prompt/retry-task.ts +83 -0
- package/src/prompt/step-contract.ts +101 -0
- package/src/prompt/step-sections.ts +75 -0
- package/src/prompt/step-task.ts +156 -0
- package/src/prompt/template.ts +83 -0
- package/src/prompt.ts +10 -254
- package/src/runtime/completion-tool.ts +3 -0
- package/src/runtime/main-step-completion.ts +78 -0
- package/src/runtime/main-step-lifecycle.ts +37 -0
- package/src/runtime/main-step-policy.ts +84 -0
- package/src/runtime/main-step-runtime-types.ts +64 -0
- package/src/runtime/main-step-runtime.ts +129 -200
- package/src/runtime/main-step-state.ts +85 -0
- package/src/runtime/serial-task-queue.ts +48 -13
- package/src/runtime/step-result.ts +48 -27
- package/src/workflow-list.ts +15 -6
- package/src/workflow-status/format-status.ts +52 -0
- package/src/workflow-status/formatting.ts +178 -0
- package/src/workflow-status/layout.ts +109 -0
- package/src/workflow-status/render-board.ts +101 -0
- package/src/workflow-status/render-path.ts +123 -0
- package/src/workflow-status/render-summary.ts +144 -0
- package/src/workflow-status/types.ts +40 -0
- package/src/workflow-status/view.ts +273 -0
- package/src/workflow-status.ts +20 -820
|
@@ -1,471 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
now: number,
|
|
19
|
-
): WorkflowRun {
|
|
20
|
-
return { ...run, ...changes, updatedAt: now };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function allowedOutcomes(
|
|
24
|
-
workflow: LoadedWorkflow,
|
|
25
|
-
run: WorkflowRun,
|
|
26
|
-
): string[] {
|
|
27
|
-
const step = currentStep(workflow, run);
|
|
28
|
-
if (!step) return [];
|
|
29
|
-
const gateResolutionOutcomes = step.gate
|
|
30
|
-
? new Set([step.gate.approvedOutcome, step.gate.rejectedOutcome])
|
|
31
|
-
: undefined;
|
|
32
|
-
return [
|
|
33
|
-
...Object.keys(step.transitions).filter(
|
|
34
|
-
(outcome) => !gateResolutionOutcomes?.has(outcome),
|
|
35
|
-
),
|
|
36
|
-
...(step.gate ? [step.gate.submitOutcome] : []),
|
|
37
|
-
];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function pauseRun(
|
|
41
|
-
run: WorkflowRun,
|
|
42
|
-
reason: string,
|
|
43
|
-
now: number,
|
|
44
|
-
): WorkflowRun {
|
|
45
|
-
if (run.status !== 'running' && run.status !== 'awaiting-gate') {
|
|
46
|
-
return withUpdate(run, { pauseReason: reason || run.pauseReason }, now);
|
|
47
|
-
}
|
|
48
|
-
return withUpdate(
|
|
49
|
-
run,
|
|
50
|
-
{
|
|
51
|
-
status: 'paused',
|
|
52
|
-
pausedFrom: run.status,
|
|
53
|
-
pauseReason: reason || `Paused during step "${run.currentStepId}"`,
|
|
54
|
-
failedStepId: undefined,
|
|
55
|
-
},
|
|
56
|
-
now,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function failRun(
|
|
61
|
-
run: WorkflowRun,
|
|
62
|
-
reason: string,
|
|
63
|
-
now: number,
|
|
64
|
-
): WorkflowRun {
|
|
65
|
-
const paused = pauseRun(run, reason, now);
|
|
66
|
-
if (paused.status !== 'paused') return paused;
|
|
67
|
-
return {
|
|
68
|
-
...paused,
|
|
69
|
-
failedStepId: paused.currentStepId,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function resumeRun(run: WorkflowRun, now: number): WorkflowRun {
|
|
74
|
-
if (run.status !== 'paused') return run;
|
|
75
|
-
return withUpdate(
|
|
76
|
-
run,
|
|
77
|
-
{
|
|
78
|
-
status: run.pausedFrom ?? (run.pendingGate ? 'awaiting-gate' : 'running'),
|
|
79
|
-
pauseReason: undefined,
|
|
80
|
-
pausedFrom: undefined,
|
|
81
|
-
failedStepId: undefined,
|
|
82
|
-
},
|
|
83
|
-
now,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function abortRun(
|
|
88
|
-
run: WorkflowRun,
|
|
89
|
-
reason: string,
|
|
90
|
-
now: number,
|
|
91
|
-
): WorkflowRun {
|
|
92
|
-
return withUpdate(
|
|
93
|
-
run,
|
|
94
|
-
{
|
|
95
|
-
status: 'aborted',
|
|
96
|
-
pauseReason: reason || 'Aborted by user',
|
|
97
|
-
pausedFrom: undefined,
|
|
98
|
-
failedStepId: undefined,
|
|
99
|
-
pendingGate: undefined,
|
|
100
|
-
},
|
|
101
|
-
now,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function advanceRun(
|
|
106
|
-
workflow: LoadedWorkflow,
|
|
107
|
-
run: WorkflowRun,
|
|
108
|
-
outcome: string,
|
|
109
|
-
summary: string,
|
|
110
|
-
now: number,
|
|
111
|
-
): WorkflowRun {
|
|
112
|
-
if (run.status !== 'running') {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`workflow is ${run.status}; only a running workflow can advance`,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
const step = currentStep(workflow, run);
|
|
118
|
-
if (!step)
|
|
119
|
-
throw new Error(`current step "${run.currentStepId}" no longer exists`);
|
|
120
|
-
if (step.gate?.submitOutcome === outcome) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
`outcome "${outcome}" must be submitted through the configured gate`,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const target = step.transitions[outcome];
|
|
127
|
-
if (!target) {
|
|
128
|
-
throw new Error(
|
|
129
|
-
`outcome "${outcome}" is not valid for step "${run.currentStepId}"`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
if (target === '$pause') {
|
|
133
|
-
return withUpdate(
|
|
134
|
-
run,
|
|
135
|
-
{
|
|
136
|
-
status: 'paused',
|
|
137
|
-
pausedFrom: 'running',
|
|
138
|
-
pauseReason: summary || `Step "${run.currentStepId}" requested a pause`,
|
|
139
|
-
lastSummary: summary,
|
|
140
|
-
},
|
|
141
|
-
now,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const completed: StepHistoryEntry = {
|
|
146
|
-
stepId: run.currentStepId,
|
|
147
|
-
stepDigest: run.currentStepDigest,
|
|
148
|
-
outcome,
|
|
149
|
-
summary,
|
|
150
|
-
completedAt: now,
|
|
151
|
-
};
|
|
152
|
-
if (target === '$done') {
|
|
153
|
-
return withUpdate(
|
|
154
|
-
run,
|
|
155
|
-
{
|
|
156
|
-
status: 'completed',
|
|
157
|
-
history: [...run.history, completed],
|
|
158
|
-
stepHandoff: summary,
|
|
159
|
-
lastSummary: summary,
|
|
160
|
-
gateFeedback: '',
|
|
161
|
-
pausedFrom: undefined,
|
|
162
|
-
pendingGate: undefined,
|
|
163
|
-
},
|
|
164
|
-
now,
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const nextStep = workflow.definition.steps[target];
|
|
169
|
-
if (!nextStep)
|
|
170
|
-
throw new Error(`transition target "${target}" does not exist`);
|
|
171
|
-
const nextVisitCount = (run.visits[target] ?? 0) + 1;
|
|
172
|
-
const visits = { ...run.visits, [target]: nextVisitCount };
|
|
173
|
-
const overVisitLimit = nextVisitCount > workflow.definition.maxStepVisits;
|
|
174
|
-
return withUpdate(
|
|
175
|
-
run,
|
|
176
|
-
{
|
|
177
|
-
status: overVisitLimit ? 'paused' : 'running',
|
|
178
|
-
currentStepId: target,
|
|
179
|
-
currentStepDigest: workflow.stepDigests[target] ?? '',
|
|
180
|
-
visits,
|
|
181
|
-
history: [...run.history, completed],
|
|
182
|
-
stepHandoff: summary,
|
|
183
|
-
lastSummary: summary,
|
|
184
|
-
gateFeedback: '',
|
|
185
|
-
...(nextStep.gate ? { reviewedArtifact: '' } : {}),
|
|
186
|
-
...(overVisitLimit
|
|
187
|
-
? {
|
|
188
|
-
pausedFrom: 'running' as const,
|
|
189
|
-
pauseReason: `Step "${target}" exceeded maxStepVisits (${workflow.definition.maxStepVisits})`,
|
|
190
|
-
}
|
|
191
|
-
: { pausedFrom: undefined, pauseReason: undefined }),
|
|
192
|
-
},
|
|
193
|
-
now,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function beginGate(
|
|
198
|
-
workflow: LoadedWorkflow,
|
|
199
|
-
run: WorkflowRun,
|
|
200
|
-
outcome: string,
|
|
201
|
-
artifact: string,
|
|
202
|
-
requestId: string,
|
|
203
|
-
now: number,
|
|
204
|
-
): WorkflowRun {
|
|
205
|
-
if (run.status !== 'running') {
|
|
206
|
-
throw new Error(
|
|
207
|
-
`workflow is ${run.status}; gate submission requires a running workflow`,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
const step = currentStep(workflow, run);
|
|
211
|
-
if (!step?.gate) throw new Error(`step "${run.currentStepId}" has no gate`);
|
|
212
|
-
if (outcome !== step.gate.submitOutcome) {
|
|
213
|
-
throw new Error(`gate expects outcome "${step.gate.submitOutcome}"`);
|
|
214
|
-
}
|
|
215
|
-
if (!artifact.trim())
|
|
216
|
-
throw new Error('gate submission requires a non-empty artifact');
|
|
217
|
-
if (!requestId) throw new Error('gate submission requires a request id');
|
|
218
|
-
|
|
219
|
-
return withUpdate(
|
|
220
|
-
run,
|
|
221
|
-
{
|
|
222
|
-
status: 'awaiting-gate',
|
|
223
|
-
pendingGate: {
|
|
224
|
-
provider: step.gate.provider,
|
|
225
|
-
requestId,
|
|
226
|
-
stepId: run.currentStepId,
|
|
227
|
-
artifact,
|
|
228
|
-
submittedOutcome: outcome,
|
|
229
|
-
requestedAt: now,
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
now,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export function attachGateReviewId(
|
|
237
|
-
run: WorkflowRun,
|
|
238
|
-
reviewId: string,
|
|
239
|
-
now: number,
|
|
240
|
-
): WorkflowRun {
|
|
241
|
-
if (!run.pendingGate) throw new Error('workflow has no pending gate');
|
|
242
|
-
if (run.pendingGate.provider !== 'plannotator') {
|
|
243
|
-
throw new Error('only a Plannotator gate can have a review id');
|
|
244
|
-
}
|
|
245
|
-
return withUpdate(
|
|
246
|
-
run,
|
|
247
|
-
{ pendingGate: { ...run.pendingGate, reviewId } },
|
|
248
|
-
now,
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function failGate(
|
|
253
|
-
run: WorkflowRun,
|
|
254
|
-
reason: string,
|
|
255
|
-
now: number,
|
|
256
|
-
): WorkflowRun {
|
|
257
|
-
if (!run.pendingGate) return run;
|
|
258
|
-
return withUpdate(
|
|
259
|
-
run,
|
|
260
|
-
{
|
|
261
|
-
status: 'running',
|
|
262
|
-
pendingGate: undefined,
|
|
263
|
-
gateFeedback: reason,
|
|
264
|
-
},
|
|
265
|
-
now,
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export function storeGateResolution(
|
|
270
|
-
run: WorkflowRun,
|
|
271
|
-
resolution: GateResolution,
|
|
272
|
-
now: number,
|
|
273
|
-
): WorkflowRun {
|
|
274
|
-
if (!run.pendingGate) return run;
|
|
275
|
-
return withUpdate(
|
|
276
|
-
run,
|
|
277
|
-
{ pendingGate: { ...run.pendingGate, resolution } },
|
|
278
|
-
now,
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export function resolveGate(
|
|
283
|
-
workflow: LoadedWorkflow,
|
|
284
|
-
run: WorkflowRun,
|
|
285
|
-
resolution: GateResolution,
|
|
286
|
-
now: number,
|
|
287
|
-
): WorkflowRun {
|
|
288
|
-
const pending = run.pendingGate;
|
|
289
|
-
if (!pending) throw new Error('workflow has no pending gate');
|
|
290
|
-
const step = workflow.definition.steps[pending.stepId];
|
|
291
|
-
if (!step?.gate)
|
|
292
|
-
throw new Error(`gated step "${pending.stepId}" no longer exists`);
|
|
293
|
-
if (run.currentStepId !== pending.stepId) {
|
|
294
|
-
throw new Error('gate result does not match the current step');
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const outcome = resolution.approved
|
|
298
|
-
? step.gate.approvedOutcome
|
|
299
|
-
: step.gate.rejectedOutcome;
|
|
300
|
-
const summary = resolution.approved
|
|
301
|
-
? pending.artifact
|
|
302
|
-
: resolution.feedback
|
|
303
|
-
? `Gate ${resolution.approved ? 'approved' : 'rejected'}: ${resolution.feedback}`
|
|
304
|
-
: `Gate ${resolution.approved ? 'approved' : 'rejected'}`;
|
|
305
|
-
const runnable = withUpdate(
|
|
306
|
-
run,
|
|
307
|
-
{
|
|
308
|
-
status: 'running',
|
|
309
|
-
pendingGate: undefined,
|
|
310
|
-
...(resolution.approved ? { reviewedArtifact: pending.artifact } : {}),
|
|
311
|
-
pausedFrom: undefined,
|
|
312
|
-
pauseReason: undefined,
|
|
313
|
-
gateFeedback: resolution.feedback,
|
|
314
|
-
},
|
|
315
|
-
now,
|
|
316
|
-
);
|
|
317
|
-
const advanced = advanceRun(workflow, runnable, outcome, summary, now);
|
|
318
|
-
return {
|
|
319
|
-
...advanced,
|
|
320
|
-
gateFeedback: resolution.feedback,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function rebuildVisits(
|
|
325
|
-
history: readonly StepHistoryEntry[],
|
|
326
|
-
currentStepId: string,
|
|
327
|
-
): Record<string, number> {
|
|
328
|
-
const visits: Record<string, number> = {};
|
|
329
|
-
for (const entry of history) {
|
|
330
|
-
visits[entry.stepId] = (visits[entry.stepId] ?? 0) + 1;
|
|
331
|
-
}
|
|
332
|
-
visits[currentStepId] = (visits[currentStepId] ?? 0) + 1;
|
|
333
|
-
return visits;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function retainedReviewedArtifact(
|
|
337
|
-
workflow: LoadedWorkflow,
|
|
338
|
-
run: WorkflowRun,
|
|
339
|
-
history: readonly StepHistoryEntry[],
|
|
340
|
-
): string {
|
|
341
|
-
const reviewedArtifact = run.reviewedArtifact ?? '';
|
|
342
|
-
if (!reviewedArtifact) return '';
|
|
343
|
-
const sourceRetained = history.some((entry) => {
|
|
344
|
-
const gate = workflow.definition.steps[entry.stepId]?.gate;
|
|
345
|
-
return (
|
|
346
|
-
gate !== undefined &&
|
|
347
|
-
entry.outcome === gate.approvedOutcome &&
|
|
348
|
-
entry.summary === reviewedArtifact
|
|
349
|
-
);
|
|
350
|
-
});
|
|
351
|
-
return sourceRetained ? reviewedArtifact : '';
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function refreshApprovedGateHistory(
|
|
355
|
-
run: WorkflowRun,
|
|
356
|
-
workflow: LoadedWorkflow,
|
|
357
|
-
): WorkflowRun {
|
|
358
|
-
const reviewedArtifact = run.reviewedArtifact ?? '';
|
|
359
|
-
if (!reviewedArtifact) return run;
|
|
360
|
-
let changed = false;
|
|
361
|
-
const history = run.history.map((entry) => {
|
|
362
|
-
const gate = workflow.definition.steps[entry.stepId]?.gate;
|
|
363
|
-
const currentDigest = workflow.stepDigests[entry.stepId];
|
|
364
|
-
if (
|
|
365
|
-
gate &&
|
|
366
|
-
currentDigest &&
|
|
367
|
-
entry.outcome === gate.approvedOutcome &&
|
|
368
|
-
entry.summary === reviewedArtifact &&
|
|
369
|
-
entry.stepDigest !== currentDigest
|
|
370
|
-
) {
|
|
371
|
-
changed = true;
|
|
372
|
-
return { ...entry, stepDigest: currentDigest };
|
|
373
|
-
}
|
|
374
|
-
return entry;
|
|
375
|
-
});
|
|
376
|
-
return changed ? { ...run, history } : run;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export function reconcileRun(
|
|
380
|
-
run: WorkflowRun,
|
|
381
|
-
workflow: LoadedWorkflow,
|
|
382
|
-
now: number,
|
|
383
|
-
): ReconcileResult {
|
|
384
|
-
if (run.workflowId !== workflow.definition.id) {
|
|
385
|
-
return {
|
|
386
|
-
changed: false,
|
|
387
|
-
error: `run belongs to "${run.workflowId}", not "${workflow.definition.id}"`,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
if (run.workflowDigest === workflow.digest) {
|
|
391
|
-
return { run, changed: false };
|
|
392
|
-
}
|
|
393
|
-
if (!workflow.definition.steps[run.currentStepId]) {
|
|
394
|
-
return {
|
|
395
|
-
changed: true,
|
|
396
|
-
error: `current step "${run.currentStepId}" was removed; abort or restore the configuration`,
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
const reconciledRun = refreshApprovedGateHistory(run, workflow);
|
|
400
|
-
|
|
401
|
-
const changedHistoryIndex = reconciledRun.history.findIndex(
|
|
402
|
-
(entry) => workflow.stepDigests[entry.stepId] !== entry.stepDigest,
|
|
403
|
-
);
|
|
404
|
-
if (changedHistoryIndex >= 0) {
|
|
405
|
-
const changedEntry = reconciledRun.history[changedHistoryIndex];
|
|
406
|
-
if (!changedEntry || !workflow.definition.steps[changedEntry.stepId]) {
|
|
407
|
-
return {
|
|
408
|
-
changed: true,
|
|
409
|
-
error:
|
|
410
|
-
'a completed step was removed; abort or restore the configuration',
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
const retainedHistory = reconciledRun.history.slice(0, changedHistoryIndex);
|
|
414
|
-
const restartedStep = changedEntry.stepId;
|
|
415
|
-
const stepHandoff = retainedHistory.at(-1)?.summary ?? '';
|
|
416
|
-
const reviewedArtifact = retainedReviewedArtifact(
|
|
417
|
-
workflow,
|
|
418
|
-
reconciledRun,
|
|
419
|
-
retainedHistory,
|
|
420
|
-
);
|
|
421
|
-
return {
|
|
422
|
-
changed: true,
|
|
423
|
-
restartedStep,
|
|
424
|
-
run: withUpdate(
|
|
425
|
-
reconciledRun,
|
|
426
|
-
{
|
|
427
|
-
workflowDigest: workflow.digest,
|
|
428
|
-
status: 'paused',
|
|
429
|
-
currentStepId: restartedStep,
|
|
430
|
-
currentStepDigest: workflow.stepDigests[restartedStep] ?? '',
|
|
431
|
-
history: retainedHistory,
|
|
432
|
-
visits: rebuildVisits(retainedHistory, restartedStep),
|
|
433
|
-
reviewedArtifact,
|
|
434
|
-
stepHandoff,
|
|
435
|
-
lastSummary: stepHandoff,
|
|
436
|
-
pendingGate: undefined,
|
|
437
|
-
pausedFrom: 'running',
|
|
438
|
-
failedStepId: undefined,
|
|
439
|
-
pauseReason: `Configuration changed; restarted step "${restartedStep}"`,
|
|
440
|
-
gateFeedback: '',
|
|
441
|
-
},
|
|
442
|
-
now,
|
|
443
|
-
),
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const currentDigest = workflow.stepDigests[run.currentStepId] ?? '';
|
|
448
|
-
const currentChanged = currentDigest !== reconciledRun.currentStepDigest;
|
|
449
|
-
return {
|
|
450
|
-
changed: true,
|
|
451
|
-
...(currentChanged ? { restartedStep: run.currentStepId } : {}),
|
|
452
|
-
run: withUpdate(
|
|
453
|
-
reconciledRun,
|
|
454
|
-
{
|
|
455
|
-
workflowDigest: workflow.digest,
|
|
456
|
-
currentStepDigest: currentDigest,
|
|
457
|
-
...(currentChanged
|
|
458
|
-
? {
|
|
459
|
-
status: 'paused' as const,
|
|
460
|
-
pendingGate: undefined,
|
|
461
|
-
pausedFrom: 'running' as const,
|
|
462
|
-
failedStepId: undefined,
|
|
463
|
-
pauseReason: `Configuration changed; restarted step "${run.currentStepId}"`,
|
|
464
|
-
gateFeedback: '',
|
|
465
|
-
}
|
|
466
|
-
: {}),
|
|
467
|
-
},
|
|
468
|
-
now,
|
|
469
|
-
),
|
|
470
|
-
};
|
|
471
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
attachGateReviewId,
|
|
3
|
+
beginGate,
|
|
4
|
+
failGate,
|
|
5
|
+
resolveGate,
|
|
6
|
+
storeGateResolution,
|
|
7
|
+
} from './gate-transitions.ts';
|
|
8
|
+
export { advanceRun } from './run-advance.ts';
|
|
9
|
+
export {
|
|
10
|
+
abortRun,
|
|
11
|
+
allowedOutcomes,
|
|
12
|
+
failRun,
|
|
13
|
+
pauseRun,
|
|
14
|
+
resumeRun,
|
|
15
|
+
} from './run-lifecycle.ts';
|
|
16
|
+
export { reconcileRun } from './run-reconciliation.ts';
|
|
17
|
+
export type { ReconcileResult } from './transition-types.ts';
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionCommandContext,
|
|
4
|
+
ExtensionContext,
|
|
5
|
+
} from '@earendil-works/pi-coding-agent';
|
|
6
|
+
import type { KeyId } from '@earendil-works/pi-tui';
|
|
7
|
+
import type {
|
|
8
|
+
LoadedWorkflow,
|
|
9
|
+
WorkflowCatalog,
|
|
10
|
+
WorkflowStep,
|
|
11
|
+
} from '../config/types.ts';
|
|
12
|
+
import type { WorkflowRun } from '../engine/state.ts';
|
|
13
|
+
import type { PromptGateReviewResult } from '../integrations/prompt-gate.ts';
|
|
14
|
+
import type { SubagentDelegationClientController } from '../integrations/subagents/client.ts';
|
|
15
|
+
import type {
|
|
16
|
+
SubagentDelegationResponse,
|
|
17
|
+
SubagentDelegationUpdate,
|
|
18
|
+
} from '../integrations/subagents/protocol.ts';
|
|
19
|
+
import type { MainStepRuntimeController } from '../runtime/main-step-runtime.ts';
|
|
20
|
+
import type { SerialTaskQueueController } from '../runtime/serial-task-queue.ts';
|
|
21
|
+
import type { WorkflowStepResult } from '../runtime/step-result.ts';
|
|
22
|
+
import type { WorkflowStatusSnapshot } from '../workflow-status.ts';
|
|
23
|
+
import type { DelegationFailureActions } from './delegation-failure.ts';
|
|
24
|
+
import type { WorkflowHarnessDependencies } from './dependencies.ts';
|
|
25
|
+
import type {
|
|
26
|
+
ActiveDelegation,
|
|
27
|
+
ActivePromptReview,
|
|
28
|
+
DelegationRecovery,
|
|
29
|
+
DelegationFailureDetails,
|
|
30
|
+
MainStepIdentity,
|
|
31
|
+
WorkflowStartContext,
|
|
32
|
+
} from './types.ts';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Internal composition surface shared by the harness action modules.
|
|
36
|
+
*
|
|
37
|
+
* The class facade owns this state. Action modules receive it as their dynamic
|
|
38
|
+
* `this` value so their dependencies stay explicit and independently testable.
|
|
39
|
+
*/
|
|
40
|
+
export type HarnessActionContext = {
|
|
41
|
+
pi: ExtensionAPI;
|
|
42
|
+
dependencies: WorkflowHarnessDependencies;
|
|
43
|
+
delegationFailures: DelegationFailureActions;
|
|
44
|
+
subagents: SubagentDelegationClientController;
|
|
45
|
+
mainSteps: MainStepRuntimeController;
|
|
46
|
+
catalog: WorkflowCatalog;
|
|
47
|
+
run: WorkflowRun | undefined;
|
|
48
|
+
latestContext: ExtensionContext | undefined;
|
|
49
|
+
availableSkills: Set<string>;
|
|
50
|
+
isSessionActive: boolean;
|
|
51
|
+
sessionEpoch: number;
|
|
52
|
+
activeDelegation: ActiveDelegation | undefined;
|
|
53
|
+
activePromptReview: ActivePromptReview | undefined;
|
|
54
|
+
registeredWorkflowCommands: Set<string>;
|
|
55
|
+
catalogLoadSequence: number;
|
|
56
|
+
mutationQueue: SerialTaskQueueController;
|
|
57
|
+
statusShortcut: KeyId;
|
|
58
|
+
statusShortcutLabel: string;
|
|
59
|
+
statusRefreshTimer: ReturnType<typeof setInterval> | undefined;
|
|
60
|
+
isStatusOverlayOpen: boolean;
|
|
61
|
+
legacyProgressWidgetContext: ExtensionContext | undefined;
|
|
62
|
+
workflowIds: () => Array<string>;
|
|
63
|
+
list: (context: ExtensionCommandContext) => Promise<void>;
|
|
64
|
+
start: (
|
|
65
|
+
workflowId: string,
|
|
66
|
+
input: string,
|
|
67
|
+
context: ExtensionCommandContext,
|
|
68
|
+
) => Promise<void>;
|
|
69
|
+
startNow: (
|
|
70
|
+
workflowId: string,
|
|
71
|
+
input: string,
|
|
72
|
+
startContext: WorkflowStartContext,
|
|
73
|
+
sessionEpoch: number,
|
|
74
|
+
) => Promise<void>;
|
|
75
|
+
pause: (reason: string, context: ExtensionCommandContext) => Promise<void>;
|
|
76
|
+
pauseNow: (reason: string, context: ExtensionCommandContext) => Promise<void>;
|
|
77
|
+
resume: (context: ExtensionCommandContext) => Promise<void>;
|
|
78
|
+
resumeNow: (context: ExtensionCommandContext) => Promise<void>;
|
|
79
|
+
abort: (reason: string, context: ExtensionCommandContext) => Promise<void>;
|
|
80
|
+
abortNow: (reason: string, context: ExtensionCommandContext) => Promise<void>;
|
|
81
|
+
reload: (context: ExtensionCommandContext) => Promise<void>;
|
|
82
|
+
reloadNow: (context: ExtensionCommandContext) => Promise<void>;
|
|
83
|
+
workflowStatusSnapshot: () => WorkflowStatusSnapshot | undefined;
|
|
84
|
+
registerLifecycle: () => void;
|
|
85
|
+
registerPolicy: () => void;
|
|
86
|
+
registerMultilineCommandInput: () => void;
|
|
87
|
+
launchCurrentStep: (
|
|
88
|
+
workflow: LoadedWorkflow,
|
|
89
|
+
recovery?: DelegationRecovery,
|
|
90
|
+
) => void;
|
|
91
|
+
launchMainStep: (
|
|
92
|
+
workflow: LoadedWorkflow,
|
|
93
|
+
run: WorkflowRun,
|
|
94
|
+
step: WorkflowStep,
|
|
95
|
+
) => void;
|
|
96
|
+
queueMainStepResult: (
|
|
97
|
+
identity: MainStepIdentity,
|
|
98
|
+
result: WorkflowStepResult | undefined,
|
|
99
|
+
context: ExtensionContext,
|
|
100
|
+
) => Promise<void>;
|
|
101
|
+
finishMainStep: (
|
|
102
|
+
identity: MainStepIdentity,
|
|
103
|
+
result: WorkflowStepResult | undefined,
|
|
104
|
+
context: ExtensionContext,
|
|
105
|
+
) => Promise<void>;
|
|
106
|
+
handleDelegationUpdate: (
|
|
107
|
+
active: ActiveDelegation,
|
|
108
|
+
update: SubagentDelegationUpdate,
|
|
109
|
+
) => void;
|
|
110
|
+
queueDelegationResponse: (
|
|
111
|
+
active: ActiveDelegation,
|
|
112
|
+
response: SubagentDelegationResponse,
|
|
113
|
+
) => void;
|
|
114
|
+
queueDelegationFailure: (active: ActiveDelegation, reason: string) => void;
|
|
115
|
+
finishDelegation: (
|
|
116
|
+
active: ActiveDelegation,
|
|
117
|
+
response: SubagentDelegationResponse,
|
|
118
|
+
) => Promise<void>;
|
|
119
|
+
cancelActiveDelegation: (reason: string) => Promise<boolean>;
|
|
120
|
+
cleanupDelegation: (active: ActiveDelegation) => Promise<void>;
|
|
121
|
+
retryDelegationAfterFailure: (
|
|
122
|
+
active: ActiveDelegation,
|
|
123
|
+
failure: DelegationFailureDetails | undefined,
|
|
124
|
+
reason: string,
|
|
125
|
+
) => boolean;
|
|
126
|
+
pauseForDelegationFailure: (reason: string) => void;
|
|
127
|
+
pauseForExecutionFailure: (label: string, reason: string) => void;
|
|
128
|
+
retainUnconfirmedDelegation: (
|
|
129
|
+
active: ActiveDelegation,
|
|
130
|
+
reason: string,
|
|
131
|
+
) => void;
|
|
132
|
+
releaseMainAfterCancellation: (active: ActiveDelegation) => void;
|
|
133
|
+
submitGate: (
|
|
134
|
+
workflow: LoadedWorkflow,
|
|
135
|
+
originalRun: WorkflowRun,
|
|
136
|
+
outcome: string,
|
|
137
|
+
artifact: string,
|
|
138
|
+
) => Promise<void>;
|
|
139
|
+
launchPromptReview: (
|
|
140
|
+
workflow: LoadedWorkflow,
|
|
141
|
+
run: WorkflowRun,
|
|
142
|
+
context: ExtensionContext | undefined,
|
|
143
|
+
) => void;
|
|
144
|
+
queuePromptReviewResult: (
|
|
145
|
+
active: ActivePromptReview,
|
|
146
|
+
result: PromptGateReviewResult,
|
|
147
|
+
) => void;
|
|
148
|
+
queuePromptReviewFailure: (
|
|
149
|
+
active: ActivePromptReview,
|
|
150
|
+
reason: string,
|
|
151
|
+
) => void;
|
|
152
|
+
finishPromptReview: (
|
|
153
|
+
active: ActivePromptReview,
|
|
154
|
+
result: PromptGateReviewResult,
|
|
155
|
+
) => Promise<void>;
|
|
156
|
+
pausePromptGate: (requestId: string, reason: string, failed: boolean) => void;
|
|
157
|
+
cancelPromptReview: () => void;
|
|
158
|
+
registerPlannotatorResults: () => void;
|
|
159
|
+
handlePlannotatorResult: (data: unknown) => Promise<void>;
|
|
160
|
+
settleAfterTransition: (workflow: LoadedWorkflow) => void;
|
|
161
|
+
preflight: (workflow: LoadedWorkflow, stepId: string) => Array<string>;
|
|
162
|
+
isolateMainSessionTools: () => void;
|
|
163
|
+
restoreBaselineTools: () => void;
|
|
164
|
+
captureSkills: (skills: ReadonlyArray<{ name: string }> | undefined) => void;
|
|
165
|
+
enqueueMutation: (
|
|
166
|
+
context: ExtensionContext,
|
|
167
|
+
operation: (sessionEpoch: number) => Promise<void>,
|
|
168
|
+
) => Promise<void>;
|
|
169
|
+
persist: () => void;
|
|
170
|
+
restoreFromSession: (context: ExtensionContext) => void;
|
|
171
|
+
reloadCatalog: (
|
|
172
|
+
context: ExtensionContext,
|
|
173
|
+
announce: boolean,
|
|
174
|
+
) => Promise<boolean>;
|
|
175
|
+
updateStatus: () => void;
|
|
176
|
+
refreshStatusWhileRunning: () => void;
|
|
177
|
+
stopStatusRefresh: () => void;
|
|
178
|
+
registerWorkflowStatusShortcut: () => void;
|
|
179
|
+
openWorkflowStatus: (context: ExtensionContext) => void;
|
|
180
|
+
showWorkflowStatus: (context: ExtensionContext) => Promise<void>;
|
|
181
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DEFAULT_SETTINGS, type WorkflowCatalog } from '../config/types.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates the initial empty catalog value used before configuration is loaded.
|
|
5
|
+
*/
|
|
6
|
+
export function createEmptyCatalog(): WorkflowCatalog {
|
|
7
|
+
return {
|
|
8
|
+
workflows: new Map(),
|
|
9
|
+
settings: DEFAULT_SETTINGS,
|
|
10
|
+
diagnostics: [],
|
|
11
|
+
userDirectory: '',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Formats a bounded diagnostic summary suitable for a Pi notification.
|
|
17
|
+
*/
|
|
18
|
+
export function formatCatalogDiagnostics(catalog: WorkflowCatalog): string {
|
|
19
|
+
const shownDiagnostics = catalog.diagnostics
|
|
20
|
+
.slice(0, 3)
|
|
21
|
+
.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`);
|
|
22
|
+
const remainingCount = catalog.diagnostics.length - shownDiagnostics.length;
|
|
23
|
+
return [
|
|
24
|
+
...shownDiagnostics,
|
|
25
|
+
...(remainingCount > 0 ? [`${remainingCount} more diagnostic(s)`] : []),
|
|
26
|
+
].join('\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extracts the latest advertised skill names from a Pi system prompt.
|
|
31
|
+
*/
|
|
32
|
+
export function parseAvailableSkills(
|
|
33
|
+
systemPrompt: string,
|
|
34
|
+
): Array<{ name: string }> {
|
|
35
|
+
const sections = [
|
|
36
|
+
...systemPrompt.matchAll(
|
|
37
|
+
/<available_skills>([\s\S]*?)<\/available_skills>/g,
|
|
38
|
+
),
|
|
39
|
+
];
|
|
40
|
+
const section = sections.at(-1)?.[1] ?? '';
|
|
41
|
+
return [...section.matchAll(/<name>([^<]+)<\/name>/g)].flatMap((match) => {
|
|
42
|
+
const name = match[1]?.trim();
|
|
43
|
+
return name ? [{ name }] : [];
|
|
44
|
+
});
|
|
45
|
+
}
|