@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
@@ -0,0 +1,191 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from '@earendil-works/pi-coding-agent';
5
+ import type { KeyId } from '@earendil-works/pi-tui';
6
+ import type { WorkflowCatalog } from '../config/types.ts';
7
+ import type { WorkflowRun } from '../engine/state.ts';
8
+ import type { MainStepRuntimeController } from '../runtime/main-step-runtime.ts';
9
+ import {
10
+ workflowStatusIcon,
11
+ type WorkflowStatusExecution,
12
+ type WorkflowStatusSnapshot,
13
+ } from '../workflow-status.ts';
14
+ import type { WorkflowHarnessDependencies } from './dependencies.ts';
15
+ import type { ActiveDelegation } from './types.ts';
16
+
17
+ const STATUS_KEY = 'pi-workflows';
18
+ const LEGACY_PROGRESS_WIDGET_KEY = 'pi-workflows-progress';
19
+ const STATUS_REFRESH_INTERVAL_MS = 250;
20
+
21
+ type StatusContext = {
22
+ pi: ExtensionAPI;
23
+ dependencies: WorkflowHarnessDependencies;
24
+ catalog: WorkflowCatalog;
25
+ run: WorkflowRun | undefined;
26
+ latestContext: ExtensionContext | undefined;
27
+ activeDelegation: ActiveDelegation | undefined;
28
+ mainSteps: MainStepRuntimeController;
29
+ statusShortcut: KeyId;
30
+ statusShortcutLabel: string;
31
+ statusRefreshTimer: ReturnType<typeof setInterval> | undefined;
32
+ isStatusOverlayOpen: boolean;
33
+ legacyProgressWidgetContext: ExtensionContext | undefined;
34
+ workflowStatusSnapshot: () => WorkflowStatusSnapshot | undefined;
35
+ updateStatus: () => void;
36
+ stopStatusRefresh: () => void;
37
+ showWorkflowStatus: (context: ExtensionContext) => Promise<void>;
38
+ };
39
+
40
+ export type StatusActions = {
41
+ workflowStatusSnapshot: (
42
+ this: StatusContext,
43
+ ) => WorkflowStatusSnapshot | undefined;
44
+ updateStatus: (this: StatusContext) => void;
45
+ refreshStatusWhileRunning: (this: StatusContext) => void;
46
+ stopStatusRefresh: (this: StatusContext) => void;
47
+ registerWorkflowStatusShortcut: (this: StatusContext) => void;
48
+ openWorkflowStatus: (this: StatusContext, context: ExtensionContext) => void;
49
+ showWorkflowStatus: (
50
+ this: StatusContext,
51
+ context: ExtensionContext,
52
+ ) => Promise<void>;
53
+ };
54
+
55
+ function workflowStatusSnapshot(
56
+ this: StatusContext,
57
+ ): WorkflowStatusSnapshot | undefined {
58
+ if (!this.run) return undefined;
59
+ const workflow = this.catalog.workflows.get(this.run.workflowId);
60
+ let execution: WorkflowStatusExecution | undefined;
61
+ if (this.activeDelegation) {
62
+ execution = {
63
+ kind: 'subagent',
64
+ agent: this.activeDelegation.agent,
65
+ requestId: this.activeDelegation.requestId,
66
+ progress: this.activeDelegation.progress ?? 'starting',
67
+ };
68
+ } else if (this.mainSteps.activeStepId) {
69
+ execution = { kind: 'main' };
70
+ }
71
+ return {
72
+ run: this.run,
73
+ now: this.dependencies.now(),
74
+ ...(workflow ? { workflow } : {}),
75
+ ...(execution ? { execution } : {}),
76
+ };
77
+ }
78
+
79
+ function updateStatus(this: StatusContext): void {
80
+ refreshStatusWhileRunning.call(this);
81
+ if (!this.latestContext) return;
82
+ if (this.legacyProgressWidgetContext !== this.latestContext) {
83
+ this.latestContext.ui.setWidget(LEGACY_PROGRESS_WIDGET_KEY, undefined);
84
+ this.legacyProgressWidgetContext = this.latestContext;
85
+ }
86
+ if (!this.run) {
87
+ this.latestContext.ui.setStatus(STATUS_KEY, undefined);
88
+ return;
89
+ }
90
+ const snapshot = this.workflowStatusSnapshot();
91
+ if (this.run.status !== 'running') {
92
+ this.latestContext.ui.setStatus(STATUS_KEY, undefined);
93
+ return;
94
+ }
95
+ this.latestContext.ui.setStatus(
96
+ STATUS_KEY,
97
+ `${workflowStatusIcon(this.run, snapshot?.now)} ${this.run.workflowId}: working · ${this.statusShortcutLabel}`,
98
+ );
99
+ }
100
+
101
+ function refreshStatusWhileRunning(this: StatusContext): void {
102
+ if (this.run?.status === 'running' && this.latestContext) {
103
+ if (this.statusRefreshTimer) return;
104
+ this.statusRefreshTimer = this.dependencies.scheduleInterval(() => {
105
+ this.updateStatus();
106
+ }, STATUS_REFRESH_INTERVAL_MS);
107
+ this.statusRefreshTimer.unref();
108
+ return;
109
+ }
110
+ this.stopStatusRefresh();
111
+ }
112
+
113
+ function stopStatusRefresh(this: StatusContext): void {
114
+ if (this.statusRefreshTimer) {
115
+ this.dependencies.cancelInterval(this.statusRefreshTimer);
116
+ }
117
+ this.statusRefreshTimer = undefined;
118
+ }
119
+
120
+ function registerWorkflowStatusShortcut(this: StatusContext): void {
121
+ this.pi.registerShortcut(this.statusShortcut, {
122
+ description: 'Toggle workflow status',
123
+ handler: async (extensionContext) => {
124
+ this.latestContext = extensionContext;
125
+ if (this.isStatusOverlayOpen) return;
126
+ if (!this.run) {
127
+ extensionContext.ui.notify(
128
+ 'No workflow checkpoint in this session',
129
+ 'info',
130
+ );
131
+ return;
132
+ }
133
+ await this.showWorkflowStatus(extensionContext);
134
+ },
135
+ });
136
+ }
137
+
138
+ function openWorkflowStatus(
139
+ this: StatusContext,
140
+ context: ExtensionContext,
141
+ ): void {
142
+ void this.showWorkflowStatus(context).catch((error: unknown) => {
143
+ context.ui.notify(
144
+ `Cannot open workflow status: ${error instanceof Error ? error.message : String(error)}`,
145
+ 'error',
146
+ );
147
+ });
148
+ }
149
+
150
+ async function showWorkflowStatus(
151
+ this: StatusContext,
152
+ context: ExtensionContext,
153
+ ): Promise<void> {
154
+ if (
155
+ this.isStatusOverlayOpen ||
156
+ !this.run ||
157
+ !context.hasUI ||
158
+ context.mode !== 'tui'
159
+ ) {
160
+ return;
161
+ }
162
+ this.isStatusOverlayOpen = true;
163
+ try {
164
+ await this.dependencies.showWorkflowStatus(
165
+ context,
166
+ () => this.workflowStatusSnapshot(),
167
+ this.statusShortcut,
168
+ {
169
+ scheduleRefresh: this.dependencies.scheduleInterval,
170
+ cancelRefresh: this.dependencies.cancelInterval,
171
+ },
172
+ );
173
+ } finally {
174
+ this.isStatusOverlayOpen = false;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Returns the status-related workflow actions for composition by the harness.
180
+ */
181
+ export function createStatusActions(): StatusActions {
182
+ return {
183
+ workflowStatusSnapshot,
184
+ updateStatus,
185
+ refreshStatusWhileRunning,
186
+ stopStatusRefresh,
187
+ registerWorkflowStatusShortcut,
188
+ openWorkflowStatus,
189
+ showWorkflowStatus,
190
+ };
191
+ }
@@ -0,0 +1,283 @@
1
+ import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
2
+ import type { LoadedWorkflow, WorkflowStep } from '../config/types.ts';
3
+ import type { WorkflowRun } from '../engine/state.ts';
4
+ import { advanceRun, allowedOutcomes } from '../engine/transitions.ts';
5
+ import { digest } from '../digest.ts';
6
+ import type { SubagentDelegationResponse } from '../integrations/subagents/protocol.ts';
7
+ import { buildMainStepTask } from '../prompt.ts';
8
+ import { narrowApprovedBashCommands } from '../policy/approved-commands.ts';
9
+ import type { MainStepExecution } from '../runtime/main-step-runtime.ts';
10
+ import type { WorkflowStepResult } from '../runtime/step-result.ts';
11
+ import type { HarnessActionContext as FullHarnessActionContext } from './action-context.ts';
12
+ import { createDelegationPlan } from './delegation-plan.ts';
13
+ import type { DelegationRecovery, MainStepIdentity } from './types.ts';
14
+
15
+ type HarnessActionContext = Pick<
16
+ FullHarnessActionContext,
17
+ | 'activeDelegation'
18
+ | 'catalog'
19
+ | 'dependencies'
20
+ | 'finishMainStep'
21
+ | 'handleDelegationUpdate'
22
+ | 'isSessionActive'
23
+ | 'latestContext'
24
+ | 'launchMainStep'
25
+ | 'mainSteps'
26
+ | 'mutationQueue'
27
+ | 'pauseForExecutionFailure'
28
+ | 'pi'
29
+ | 'queueDelegationFailure'
30
+ | 'queueDelegationResponse'
31
+ | 'queueMainStepResult'
32
+ | 'run'
33
+ | 'sessionEpoch'
34
+ | 'settleAfterTransition'
35
+ | 'subagents'
36
+ | 'submitGate'
37
+ | 'updateStatus'
38
+ >;
39
+
40
+ export type StepExecutionActions = {
41
+ launchCurrentStep: (
42
+ this: HarnessActionContext,
43
+ workflow: LoadedWorkflow,
44
+ recovery?: DelegationRecovery,
45
+ ) => void;
46
+ launchMainStep: (
47
+ this: HarnessActionContext,
48
+ workflow: LoadedWorkflow,
49
+ run: WorkflowRun,
50
+ step: WorkflowStep,
51
+ ) => void;
52
+ queueMainStepResult: (
53
+ this: HarnessActionContext,
54
+ identity: MainStepIdentity,
55
+ result: WorkflowStepResult | undefined,
56
+ context: ExtensionContext,
57
+ ) => Promise<void>;
58
+ finishMainStep: (
59
+ this: HarnessActionContext,
60
+ identity: MainStepIdentity,
61
+ result: WorkflowStepResult | undefined,
62
+ context: ExtensionContext,
63
+ ) => Promise<void>;
64
+ };
65
+
66
+ function launchCurrentStep(
67
+ this: HarnessActionContext,
68
+ workflow: LoadedWorkflow,
69
+ recovery?: DelegationRecovery,
70
+ ): void {
71
+ const run = this.run;
72
+ if (
73
+ !run ||
74
+ run.status !== 'running' ||
75
+ this.activeDelegation ||
76
+ this.mainSteps.activeStepId
77
+ ) {
78
+ return;
79
+ }
80
+ const step = workflow.definition.steps[run.currentStepId];
81
+ if (!step) {
82
+ this.pauseForExecutionFailure(
83
+ 'Workflow',
84
+ `Step "${run.currentStepId}" is missing from the workflow`,
85
+ );
86
+ return;
87
+ }
88
+ if (!step.subagent) {
89
+ this.launchMainStep(workflow, run, step);
90
+ return;
91
+ }
92
+
93
+ const plan = createDelegationPlan(
94
+ {
95
+ workflow,
96
+ run,
97
+ step,
98
+ sessionEpoch: this.sessionEpoch,
99
+ latestContext: this.latestContext,
100
+ recovery,
101
+ },
102
+ this.dependencies,
103
+ );
104
+ if (plan.kind === 'invalid') {
105
+ this.pauseForExecutionFailure('Subagent step', plan.reason);
106
+ return;
107
+ }
108
+ const { active, request } = plan;
109
+
110
+ this.activeDelegation = active;
111
+ this.updateStatus();
112
+ this.latestContext?.ui.notify(
113
+ `Delegated "${run.currentStepId}" to subagent "${active.agent}"`,
114
+ 'info',
115
+ );
116
+ let delegation: Promise<SubagentDelegationResponse>;
117
+ try {
118
+ delegation = this.subagents.delegate(request, {
119
+ onUpdate: (update) => {
120
+ this.handleDelegationUpdate(active, update);
121
+ },
122
+ onLateTerminal: (response) => {
123
+ this.queueDelegationResponse(active, response);
124
+ },
125
+ });
126
+ } catch (error) {
127
+ this.queueDelegationFailure(
128
+ active,
129
+ error instanceof Error ? error.message : String(error),
130
+ );
131
+ return;
132
+ }
133
+ void delegation.then(
134
+ (response) => {
135
+ this.queueDelegationResponse(active, response);
136
+ },
137
+ (error: unknown) => {
138
+ this.queueDelegationFailure(
139
+ active,
140
+ error instanceof Error ? error.message : String(error),
141
+ );
142
+ },
143
+ );
144
+ }
145
+
146
+ function launchMainStep(
147
+ this: HarnessActionContext,
148
+ workflow: LoadedWorkflow,
149
+ run: WorkflowRun,
150
+ step: WorkflowStep,
151
+ ): void {
152
+ const approvedBashCommands = narrowApprovedBashCommands(
153
+ run.reviewedArtifact ?? '',
154
+ run.stepHandoff ?? '',
155
+ step.permissions.bash.approvedSources ?? [],
156
+ );
157
+ const identity: MainStepIdentity = {
158
+ runId: run.runId,
159
+ stepId: run.currentStepId,
160
+ stepDigest: run.currentStepDigest,
161
+ sessionEpoch: this.sessionEpoch,
162
+ };
163
+ const policyDigest = digest({
164
+ version: 1,
165
+ execution: 'main',
166
+ workflowId: workflow.definition.id,
167
+ runId: run.runId,
168
+ stepId: run.currentStepId,
169
+ stepDigest: run.currentStepDigest,
170
+ permissions: step.permissions,
171
+ approvedBashCommands,
172
+ nonce: this.dependencies.createRequestId(),
173
+ });
174
+ const execution: MainStepExecution = {
175
+ workflowId: workflow.definition.id,
176
+ runId: run.runId,
177
+ stepId: run.currentStepId,
178
+ stepDigest: run.currentStepDigest,
179
+ policyDigest,
180
+ step: structuredClone(step),
181
+ approvedBashCommands,
182
+ outcomes: allowedOutcomes(workflow, run),
183
+ summaryMaxChars: workflow.definition.summaryMaxChars,
184
+ ...(step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {}),
185
+ onSettled: (result, context) =>
186
+ this.queueMainStepResult(identity, result, context),
187
+ };
188
+
189
+ try {
190
+ this.mainSteps.activate(execution);
191
+ this.updateStatus();
192
+ this.latestContext?.ui.notify(
193
+ `Started "${run.currentStepId}" in the main agent`,
194
+ 'info',
195
+ );
196
+ this.pi.sendUserMessage(buildMainStepTask(workflow, run), {
197
+ deliverAs: 'followUp',
198
+ });
199
+ } catch (error) {
200
+ this.mainSteps.deactivate();
201
+ this.pauseForExecutionFailure(
202
+ 'Main-agent step',
203
+ error instanceof Error ? error.message : String(error),
204
+ );
205
+ }
206
+ }
207
+
208
+ function queueMainStepResult(
209
+ this: HarnessActionContext,
210
+ identity: MainStepIdentity,
211
+ result: WorkflowStepResult | undefined,
212
+ context: ExtensionContext,
213
+ ): Promise<void> {
214
+ return this.mutationQueue
215
+ .run(() => this.finishMainStep(identity, result, context))
216
+ .catch((error: unknown) => {
217
+ this.pauseForExecutionFailure(
218
+ 'Main-agent step',
219
+ error instanceof Error ? error.message : String(error),
220
+ );
221
+ });
222
+ }
223
+
224
+ async function finishMainStep(
225
+ this: HarnessActionContext,
226
+ identity: MainStepIdentity,
227
+ result: WorkflowStepResult | undefined,
228
+ context: ExtensionContext,
229
+ ): Promise<void> {
230
+ this.latestContext = context;
231
+ if (
232
+ !this.isSessionActive ||
233
+ this.sessionEpoch !== identity.sessionEpoch ||
234
+ !this.run ||
235
+ this.run.status !== 'running' ||
236
+ this.run.runId !== identity.runId ||
237
+ this.run.currentStepId !== identity.stepId ||
238
+ this.run.currentStepDigest !== identity.stepDigest
239
+ ) {
240
+ return;
241
+ }
242
+ if (!result) {
243
+ throw new Error(
244
+ 'agent settled without calling workflow_complete_step exactly once',
245
+ );
246
+ }
247
+
248
+ const workflow = this.catalog.workflows.get(this.run.workflowId);
249
+ const step = workflow?.definition.steps[this.run.currentStepId];
250
+ if (!workflow || !step) {
251
+ throw new Error('Active workflow configuration is unavailable');
252
+ }
253
+ if (step.gate?.submitOutcome === result.outcome) {
254
+ await this.submitGate(
255
+ workflow,
256
+ this.run,
257
+ result.outcome,
258
+ result.artifact ?? '',
259
+ );
260
+ return;
261
+ }
262
+
263
+ this.run = advanceRun(
264
+ workflow,
265
+ this.run,
266
+ result.outcome,
267
+ result.summary,
268
+ this.dependencies.now(),
269
+ );
270
+ this.settleAfterTransition(workflow);
271
+ }
272
+
273
+ /**
274
+ * Returns main and delegated step-launch actions for harness composition.
275
+ */
276
+ export function createStepExecutionActions(): StepExecutionActions {
277
+ return {
278
+ launchCurrentStep,
279
+ launchMainStep,
280
+ queueMainStepResult,
281
+ finishMainStep,
282
+ };
283
+ }
@@ -0,0 +1,76 @@
1
+ import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
2
+ import type { DelegationReplayAudit } from '../integrations/subagents/diagnostics.ts';
3
+ import type {
4
+ ChildStepPolicy,
5
+ SubagentDelegationStatus,
6
+ } from '../integrations/subagents/protocol.ts';
7
+ import type { WorkflowHarnessDependencies } from './dependencies.ts';
8
+
9
+ export type ActiveDelegation = {
10
+ requestId: string;
11
+ runId: string;
12
+ stepId: string;
13
+ stepDigest: string;
14
+ sessionEpoch: number;
15
+ resultDirectory: string;
16
+ policy: ChildStepPolicy;
17
+ transcriptTask: string;
18
+ agent: string;
19
+ trustedSessionRoot?: string;
20
+ broadRecoveryAuthorized: boolean;
21
+ recoveryAttemptCount: number;
22
+ recoveryFailures: ReadonlyArray<DelegationRecoveryFailure>;
23
+ progress?: string;
24
+ cancelling?: boolean;
25
+ };
26
+
27
+ export type DelegationRecoveryBlocker =
28
+ | 'cancelled'
29
+ | 'detached'
30
+ | 'inconsistent-timeout'
31
+ | 'interrupted'
32
+ | 'reported-mutation'
33
+ | 'stopped';
34
+
35
+ export type DelegationFailureDetails = {
36
+ reason: string;
37
+ status: SubagentDelegationStatus;
38
+ error?: string;
39
+ exitCode?: number;
40
+ recoveryBlocker?: DelegationRecoveryBlocker;
41
+ diagnostic?: Awaited<
42
+ ReturnType<WorkflowHarnessDependencies['readToolFailureDiagnostic']>
43
+ >;
44
+ replayAudit?: DelegationReplayAudit;
45
+ };
46
+
47
+ export type MainStepIdentity = {
48
+ runId: string;
49
+ stepId: string;
50
+ stepDigest: string;
51
+ sessionEpoch: number;
52
+ };
53
+
54
+ export type ActivePromptReview = {
55
+ requestId: string;
56
+ runId: string;
57
+ stepId: string;
58
+ sessionEpoch: number;
59
+ abortController: AbortController;
60
+ };
61
+
62
+ export type WorkflowStartContext = {
63
+ context: ExtensionContext;
64
+ skills: () => ReadonlyArray<{ name: string }> | undefined;
65
+ waitForIdle: () => Promise<void>;
66
+ };
67
+
68
+ export type DelegationRecoveryFailure = {
69
+ readonly fingerprint: string;
70
+ readonly reason: string;
71
+ };
72
+
73
+ export type DelegationRecovery = {
74
+ readonly attempt: number;
75
+ readonly failures: ReadonlyArray<DelegationRecoveryFailure>;
76
+ };