@wichayutdew/pi-workflows 0.2.3 → 1.0.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.
Files changed (142) hide show
  1. package/README.md +66 -38
  2. package/dist/index.js +5903 -4782
  3. package/package.json +1 -1
  4. package/src/command-names.ts +7 -1
  5. package/src/commands.ts +126 -52
  6. package/src/config/catalog.ts +162 -0
  7. package/src/config/ceiling.ts +116 -116
  8. package/src/config/command-conflicts.ts +4 -4
  9. package/src/config/diagnostics.ts +28 -0
  10. package/src/config/load-settings.ts +37 -0
  11. package/src/config/load-types.ts +53 -0
  12. package/src/config/load-workflows.ts +135 -0
  13. package/src/config/load.ts +43 -311
  14. package/src/config/types.ts +137 -135
  15. package/src/config/validate.ts +12 -1261
  16. package/src/config/validation/permissions.ts +291 -0
  17. package/src/config/validation/prompt.ts +19 -0
  18. package/src/config/validation/settings.ts +122 -0
  19. package/src/config/validation/shared.ts +129 -0
  20. package/src/config/validation/shortcut.ts +118 -0
  21. package/src/config/validation/step.ts +228 -0
  22. package/src/config/validation/subagent.ts +288 -0
  23. package/src/config/validation/workflow.ts +156 -0
  24. package/src/config/yaml.ts +29 -0
  25. package/src/digest.ts +38 -6
  26. package/src/engine/checkpoint.ts +13 -9
  27. package/src/engine/create-run.ts +42 -0
  28. package/src/engine/gate-transitions.ts +184 -0
  29. package/src/engine/reconciliation-history.ts +88 -0
  30. package/src/engine/resume.ts +19 -7
  31. package/src/engine/run-advance.ts +122 -0
  32. package/src/engine/run-lifecycle.ts +124 -0
  33. package/src/engine/run-reconciliation.ts +116 -0
  34. package/src/engine/run-validation.ts +128 -0
  35. package/src/engine/state-types.ts +64 -0
  36. package/src/engine/state.ts +10 -193
  37. package/src/engine/transition-helpers.ts +28 -0
  38. package/src/engine/transition-types.ts +8 -0
  39. package/src/engine/transitions.ts +17 -471
  40. package/src/harness/action-context.ts +181 -0
  41. package/src/harness/catalog.ts +45 -0
  42. package/src/harness/context-idle.ts +21 -0
  43. package/src/harness/core-actions.ts +299 -0
  44. package/src/harness/delegation-control-actions.ts +250 -0
  45. package/src/harness/delegation-failure.ts +215 -0
  46. package/src/harness/delegation-plan.ts +219 -0
  47. package/src/harness/delegation-recovery-validation.ts +159 -0
  48. package/src/harness/delegation-response-actions.ts +295 -0
  49. package/src/harness/delegation-retry-policy.ts +118 -0
  50. package/src/harness/dependencies.ts +165 -0
  51. package/src/harness/gate-submission-action.ts +154 -0
  52. package/src/harness/lifecycle-actions.ts +154 -0
  53. package/src/harness/pause-actions.ts +120 -0
  54. package/src/harness/plannotator-result-actions.ts +123 -0
  55. package/src/harness/prompt-gate-actions.ts +270 -0
  56. package/src/harness/resume-action.ts +261 -0
  57. package/src/harness/start-actions.ts +183 -0
  58. package/src/harness/status-actions.ts +191 -0
  59. package/src/harness/step-execution-actions.ts +283 -0
  60. package/src/harness/types.ts +76 -0
  61. package/src/harness.ts +237 -2201
  62. package/src/index.ts +73 -11
  63. package/src/integrations/plannotator-requests.ts +127 -0
  64. package/src/integrations/plannotator-responses.ts +141 -0
  65. package/src/integrations/plannotator-types.ts +44 -0
  66. package/src/integrations/plannotator.ts +16 -235
  67. package/src/integrations/prompt-gate.ts +17 -10
  68. package/src/integrations/subagents/child-policy-envelope.ts +119 -0
  69. package/src/integrations/subagents/child-policy-paths.ts +66 -0
  70. package/src/integrations/subagents/child-policy-sections.ts +174 -0
  71. package/src/integrations/subagents/child-policy-types.ts +32 -0
  72. package/src/integrations/subagents/child-policy-validation.ts +163 -0
  73. package/src/integrations/subagents/child-runtime-completion.ts +56 -0
  74. package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
  75. package/src/integrations/subagents/child-runtime-files.ts +156 -0
  76. package/src/integrations/subagents/child-runtime-policy.ts +83 -0
  77. package/src/integrations/subagents/child-runtime-types.ts +30 -0
  78. package/src/integrations/subagents/child-runtime.ts +140 -301
  79. package/src/integrations/subagents/client-delegation.ts +181 -0
  80. package/src/integrations/subagents/client-messages.ts +66 -0
  81. package/src/integrations/subagents/client-types.ts +36 -0
  82. package/src/integrations/subagents/client.ts +127 -219
  83. package/src/integrations/subagents/delegated-result.ts +31 -0
  84. package/src/integrations/subagents/diagnostic-format.ts +40 -0
  85. package/src/integrations/subagents/diagnostic-text.ts +114 -0
  86. package/src/integrations/subagents/diagnostic-types.ts +77 -0
  87. package/src/integrations/subagents/diagnostics.ts +24 -799
  88. package/src/integrations/subagents/failure-correlation.ts +235 -0
  89. package/src/integrations/subagents/failure-transcript.ts +192 -0
  90. package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
  91. package/src/integrations/subagents/protocol-events.ts +27 -0
  92. package/src/integrations/subagents/protocol.ts +30 -375
  93. package/src/integrations/subagents/replay-audit.ts +147 -0
  94. package/src/integrations/subagents/replay-safety.ts +82 -0
  95. package/src/integrations/subagents/session-diagnostics.ts +258 -0
  96. package/src/policy/approved-command-extraction.ts +71 -0
  97. package/src/policy/approved-commands.ts +15 -432
  98. package/src/policy/bash-authorization.ts +86 -0
  99. package/src/policy/bash-read-only.ts +161 -0
  100. package/src/policy/bash-types.ts +20 -0
  101. package/src/policy/bash.ts +4 -346
  102. package/src/policy/completion-batch.ts +21 -16
  103. package/src/policy/immutable-input.ts +5 -2
  104. package/src/policy/mcp-authorization.ts +71 -0
  105. package/src/policy/restricted-command.ts +141 -0
  106. package/src/policy/restricted-git.ts +30 -0
  107. package/src/policy/reviewed-artifact.ts +97 -0
  108. package/src/policy/reviewed-command-safety.ts +150 -0
  109. package/src/policy/reviewed-command-shape.ts +49 -0
  110. package/src/policy/reviewed-repository-contract.ts +138 -0
  111. package/src/policy/reviewed-repository-cwd.ts +102 -0
  112. package/src/policy/tool-call-authorization.ts +58 -0
  113. package/src/policy/tool-selection.ts +72 -0
  114. package/src/policy/tool-types.ts +14 -0
  115. package/src/policy/tools.ts +11 -150
  116. package/src/preflight.ts +96 -61
  117. package/src/prompt/main-workflow-notice.ts +41 -0
  118. package/src/prompt/retry-task.ts +83 -0
  119. package/src/prompt/step-contract.ts +101 -0
  120. package/src/prompt/step-sections.ts +75 -0
  121. package/src/prompt/step-task.ts +156 -0
  122. package/src/prompt/template.ts +81 -0
  123. package/src/prompt.ts +10 -245
  124. package/src/runtime/completion-tool.ts +3 -0
  125. package/src/runtime/main-step-completion.ts +78 -0
  126. package/src/runtime/main-step-lifecycle.ts +37 -0
  127. package/src/runtime/main-step-policy.ts +84 -0
  128. package/src/runtime/main-step-runtime-types.ts +64 -0
  129. package/src/runtime/main-step-runtime.ts +129 -200
  130. package/src/runtime/main-step-state.ts +85 -0
  131. package/src/runtime/serial-task-queue.ts +48 -13
  132. package/src/runtime/step-result.ts +48 -27
  133. package/src/workflow-list.ts +15 -6
  134. package/src/workflow-status/format-status.ts +52 -0
  135. package/src/workflow-status/formatting.ts +178 -0
  136. package/src/workflow-status/layout.ts +109 -0
  137. package/src/workflow-status/render-board.ts +101 -0
  138. package/src/workflow-status/render-path.ts +123 -0
  139. package/src/workflow-status/render-summary.ts +144 -0
  140. package/src/workflow-status/types.ts +40 -0
  141. package/src/workflow-status/view.ts +273 -0
  142. package/src/workflow-status.ts +20 -820
@@ -1,471 +1,17 @@
1
- import type { LoadedWorkflow } from '../config/types.ts';
2
- import type { GateResolution, StepHistoryEntry, WorkflowRun } from './state.ts';
3
-
4
- export interface ReconcileResult {
5
- run?: WorkflowRun;
6
- changed: boolean;
7
- restartedStep?: string;
8
- error?: string;
9
- }
10
-
11
- function currentStep(workflow: LoadedWorkflow, run: WorkflowRun) {
12
- return workflow.definition.steps[run.currentStepId];
13
- }
14
-
15
- function withUpdate(
16
- run: WorkflowRun,
17
- changes: Partial<WorkflowRun>,
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
+ }