@wichayutdew/pi-workflows 1.0.0 → 2.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.
- package/README.md +312 -221
- package/agents/step.md +4 -11
- package/dist/index.js +2556 -961
- package/examples/mr-comments.workflow.yaml +18 -8
- package/examples/prompts/mr-comments/implement.md +13 -9
- package/examples/prompts/mr-comments/plan.md +44 -15
- package/examples/prompts/mr-comments/verify.md +8 -7
- package/examples/settings.yaml +7 -1
- package/examples/starter-kit/mr-comment.workflow.yaml +149 -0
- package/examples/starter-kit/mr-review.workflow.yaml +130 -0
- package/examples/starter-kit/steps/mr-comment/fetch.md +33 -0
- package/examples/starter-kit/steps/mr-comment/implement.md +38 -0
- package/examples/starter-kit/steps/mr-comment/plan.md +62 -0
- package/examples/starter-kit/steps/mr-comment/publish.md +36 -0
- package/examples/starter-kit/steps/mr-comment/verify.md +35 -0
- package/examples/starter-kit/steps/mr-review/fetch.md +25 -0
- package/examples/starter-kit/steps/mr-review/publish.md +30 -0
- package/examples/starter-kit/steps/mr-review/review.md +69 -0
- package/examples/starter-kit/steps/mr-review/verify.md +24 -0
- package/examples/starter-kit/steps/shared/prepare-workspace.md +57 -0
- package/examples/starter-kit/steps/ticket/implement.md +35 -0
- package/examples/starter-kit/steps/ticket/plan.md +40 -0
- package/examples/starter-kit/steps/ticket/verify.md +29 -0
- package/examples/starter-kit/steps/work/implement.md +34 -0
- package/examples/starter-kit/steps/work/plan.md +38 -0
- package/examples/starter-kit/steps/work/verify.md +30 -0
- package/examples/starter-kit/ticket.workflow.yaml +100 -0
- package/examples/starter-kit/work.workflow.yaml +97 -0
- package/package.json +3 -3
- package/schemas/workflow.schema.json +78 -35
- package/src/command-names.ts +1 -0
- package/src/commands.ts +33 -4
- package/src/config/ceiling.ts +6 -8
- package/src/config/load-workflows.ts +12 -1
- package/src/config/step-digests.ts +19 -0
- package/src/config/types.ts +17 -11
- package/src/config/validation/permissions.ts +5 -45
- package/src/config/validation/prompt.ts +3 -0
- package/src/config/validation/shared.ts +0 -2
- package/src/config/validation/step.ts +106 -2
- package/src/config/validation/workflow.ts +76 -0
- package/src/engine/create-run.ts +4 -0
- package/src/engine/gate-transitions.ts +58 -6
- package/src/engine/reconciliation-history.ts +61 -24
- package/src/engine/run-advance.ts +37 -4
- package/src/engine/run-lifecycle.ts +11 -0
- package/src/engine/run-reconciliation.ts +51 -21
- package/src/engine/run-validation.ts +236 -0
- package/src/engine/run-workflow-validation.ts +191 -0
- package/src/engine/state-types.ts +109 -4
- package/src/engine/state.ts +15 -1
- package/src/engine/step-trace.ts +495 -0
- package/src/engine/transitions.ts +1 -0
- package/src/harness/action-context.ts +31 -6
- package/src/harness/core-actions.ts +30 -1
- package/src/harness/delegation-control-actions.ts +27 -2
- package/src/harness/delegation-failure.ts +42 -9
- package/src/harness/delegation-plan.ts +96 -40
- package/src/harness/delegation-recovery-validation.ts +3 -1
- package/src/harness/delegation-response-actions.ts +73 -3
- package/src/harness/delegation-retry-policy.ts +5 -3
- package/src/harness/dependencies.ts +11 -2
- package/src/harness/gate-submission-action.ts +5 -31
- package/src/harness/lifecycle-actions.ts +4 -5
- package/src/harness/pause-actions.ts +10 -1
- package/src/harness/plannotator-result-actions.ts +20 -11
- package/src/harness/prompt-gate-actions.ts +40 -2
- package/src/harness/resume-action.ts +75 -1
- package/src/harness/start-actions.ts +77 -3
- package/src/harness/status-actions.ts +7 -17
- package/src/harness/step-effects.ts +61 -0
- package/src/harness/step-execution-actions.ts +181 -25
- package/src/harness/step-reporting.ts +168 -0
- package/src/harness/types.ts +1 -0
- package/src/harness/workspace-directory.ts +67 -0
- package/src/harness.ts +37 -9
- package/src/integrations/plannotator-requests.ts +40 -5
- package/src/integrations/subagents/child-policy-sections.ts +71 -72
- package/src/integrations/subagents/child-policy-types.ts +7 -7
- package/src/integrations/subagents/child-policy-validation.ts +10 -4
- package/src/integrations/subagents/child-runtime-completion.ts +1 -0
- package/src/integrations/subagents/child-runtime-files.ts +29 -86
- package/src/integrations/subagents/child-runtime-policy.ts +8 -33
- package/src/integrations/subagents/child-runtime.ts +3 -15
- package/src/integrations/subagents/delegated-result.ts +1 -0
- package/src/integrations/subagents/diagnostic-format.ts +5 -0
- package/src/integrations/subagents/diagnostic-types.ts +7 -1
- package/src/integrations/subagents/diagnostics.ts +2 -0
- package/src/integrations/subagents/failure-correlation.ts +52 -2
- package/src/integrations/subagents/failure-transcript.ts +60 -1
- package/src/integrations/subagents/replay-audit.ts +0 -1
- package/src/integrations/subagents/replay-safety.ts +12 -27
- package/src/integrations/subagents/session-diagnostics.ts +102 -3
- package/src/policy/bash-authorization.ts +2 -21
- package/src/policy/bash-types.ts +0 -5
- package/src/policy/bash.ts +1 -2
- package/src/policy/tool-call-authorization.ts +1 -7
- package/src/preflight.ts +0 -5
- package/src/prompt/retry-task.ts +11 -3
- package/src/prompt/step-contract.ts +10 -56
- package/src/prompt/step-sections.ts +8 -10
- package/src/prompt/step-task.ts +41 -10
- package/src/prompt/template.ts +5 -2
- package/src/runtime/completion-tool.ts +14 -0
- package/src/runtime/main-step-completion.ts +3 -0
- package/src/runtime/main-step-policy.ts +2 -1
- package/src/runtime/main-step-runtime-types.ts +9 -1
- package/src/runtime/main-step-runtime.ts +2 -0
- package/src/runtime/main-step-state.ts +6 -0
- package/src/runtime/main-step-trace.ts +45 -0
- package/src/runtime/step-result.ts +57 -0
- package/src/step-log.ts +178 -0
- package/src/workflow-doctor.ts +254 -0
- package/src/workflow-status/format-status.ts +32 -3
- package/src/workflow-status/render-board.ts +7 -4
- package/src/workflow-status/render-path.ts +51 -24
- package/src/workflow-status/render-step-detail.ts +379 -0
- package/src/workflow-status/render-summary.ts +22 -0
- package/src/workflow-status/transcript-reader.ts +231 -0
- package/src/workflow-status/types.ts +8 -0
- package/src/workflow-status/view.ts +161 -14
- package/src/workflow-status.ts +6 -0
- package/src/policy/approved-command-extraction.ts +0 -71
- package/src/policy/approved-commands.ts +0 -15
- package/src/policy/bash-read-only.ts +0 -161
- package/src/policy/restricted-git.ts +0 -30
- package/src/policy/reviewed-artifact.ts +0 -97
- package/src/policy/reviewed-command-safety.ts +0 -150
- package/src/policy/reviewed-command-shape.ts +0 -49
- package/src/policy/reviewed-repository-contract.ts +0 -138
- package/src/policy/reviewed-repository-cwd.ts +0 -102
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
boundedFailureField,
|
|
5
5
|
MAX_DELEGATION_RECOVERY_ATTEMPTS,
|
|
6
6
|
} from './delegation-retry-policy.ts';
|
|
7
|
+
import {
|
|
8
|
+
conciseStepFailureSummary,
|
|
9
|
+
reportFailedStep,
|
|
10
|
+
} from './step-reporting.ts';
|
|
7
11
|
import type { ActiveDelegation, DelegationFailureDetails } from './types.ts';
|
|
8
12
|
|
|
9
13
|
type HarnessActionContext = Pick<
|
|
@@ -20,6 +24,7 @@ type HarnessActionContext = Pick<
|
|
|
20
24
|
| 'mainSteps'
|
|
21
25
|
| 'pauseForExecutionFailure'
|
|
22
26
|
| 'persist'
|
|
27
|
+
| 'pi'
|
|
23
28
|
| 'restoreBaselineTools'
|
|
24
29
|
| 'run'
|
|
25
30
|
| 'sessionEpoch'
|
|
@@ -45,11 +50,13 @@ export type DelegationControlActions = {
|
|
|
45
50
|
pauseForDelegationFailure: (
|
|
46
51
|
this: HarnessActionContext,
|
|
47
52
|
reason: string,
|
|
53
|
+
failureSummary?: string,
|
|
48
54
|
) => void;
|
|
49
55
|
pauseForExecutionFailure: (
|
|
50
56
|
this: HarnessActionContext,
|
|
51
57
|
label: string,
|
|
52
58
|
reason: string,
|
|
59
|
+
failureSummary?: string,
|
|
53
60
|
) => void;
|
|
54
61
|
retainUnconfirmedDelegation: (
|
|
55
62
|
this: HarnessActionContext,
|
|
@@ -162,14 +169,16 @@ function retryDelegationAfterFailure(
|
|
|
162
169
|
function pauseForDelegationFailure(
|
|
163
170
|
this: HarnessActionContext,
|
|
164
171
|
reason: string,
|
|
172
|
+
failureSummary = reason,
|
|
165
173
|
): void {
|
|
166
|
-
this.pauseForExecutionFailure('Subagent step', reason);
|
|
174
|
+
this.pauseForExecutionFailure('Subagent step', reason, failureSummary);
|
|
167
175
|
}
|
|
168
176
|
|
|
169
177
|
function pauseForExecutionFailure(
|
|
170
178
|
this: HarnessActionContext,
|
|
171
179
|
label: string,
|
|
172
180
|
reason: string,
|
|
181
|
+
failureSummary = reason,
|
|
173
182
|
): void {
|
|
174
183
|
if (!this.run || this.run.status !== 'running') return;
|
|
175
184
|
this.mainSteps.deactivate();
|
|
@@ -179,6 +188,12 @@ function pauseForExecutionFailure(
|
|
|
179
188
|
this.dependencies.now(),
|
|
180
189
|
);
|
|
181
190
|
this.persist();
|
|
191
|
+
reportFailedStep(
|
|
192
|
+
this.pi,
|
|
193
|
+
this.catalog.workflows.get(this.run.workflowId),
|
|
194
|
+
this.run,
|
|
195
|
+
failureSummary,
|
|
196
|
+
);
|
|
182
197
|
if (this.activeDelegation) {
|
|
183
198
|
this.isolateMainSessionTools();
|
|
184
199
|
} else {
|
|
@@ -186,7 +201,7 @@ function pauseForExecutionFailure(
|
|
|
186
201
|
}
|
|
187
202
|
this.updateStatus();
|
|
188
203
|
this.latestContext?.ui.notify(
|
|
189
|
-
`Workflow paused at "${this.run.currentStepId}": ${
|
|
204
|
+
`Workflow paused at "${this.run.currentStepId}": ${conciseStepFailureSummary(failureSummary)}`,
|
|
190
205
|
'error',
|
|
191
206
|
);
|
|
192
207
|
}
|
|
@@ -198,6 +213,7 @@ function retainUnconfirmedDelegation(
|
|
|
198
213
|
): void {
|
|
199
214
|
active.cancelling = true;
|
|
200
215
|
active.progress = 'cancellation unconfirmed';
|
|
216
|
+
let didFail = false;
|
|
201
217
|
if (this.run?.status === 'running') {
|
|
202
218
|
this.run = failRun(
|
|
203
219
|
this.run,
|
|
@@ -205,6 +221,15 @@ function retainUnconfirmedDelegation(
|
|
|
205
221
|
this.dependencies.now(),
|
|
206
222
|
);
|
|
207
223
|
this.persist();
|
|
224
|
+
didFail = true;
|
|
225
|
+
}
|
|
226
|
+
if (didFail && this.run) {
|
|
227
|
+
reportFailedStep(
|
|
228
|
+
this.pi,
|
|
229
|
+
this.catalog.workflows.get(this.run.workflowId),
|
|
230
|
+
this.run,
|
|
231
|
+
reason,
|
|
232
|
+
);
|
|
208
233
|
}
|
|
209
234
|
this.isolateMainSessionTools();
|
|
210
235
|
this.updateStatus();
|
|
@@ -2,6 +2,7 @@ import type { WorkflowStepResult } from '../runtime/step-result.ts';
|
|
|
2
2
|
import {
|
|
3
3
|
failedToolName,
|
|
4
4
|
formatToolFailureDiagnostic,
|
|
5
|
+
type CompletedDelegationTranscriptAudit,
|
|
5
6
|
} from '../integrations/subagents/diagnostics.ts';
|
|
6
7
|
import type {
|
|
7
8
|
ChildStepPolicy,
|
|
@@ -28,6 +29,10 @@ import type {
|
|
|
28
29
|
export type DelegationFailureActions = {
|
|
29
30
|
delegationFailureFingerprint: (failure: DelegationFailureDetails) => string;
|
|
30
31
|
hasContradictoryCompletion: (response: SubagentDelegationResponse) => boolean;
|
|
32
|
+
completedResponseAudit: (
|
|
33
|
+
active: ActiveDelegation,
|
|
34
|
+
response: SubagentDelegationResponse,
|
|
35
|
+
) => Promise<CompletedDelegationTranscriptAudit>;
|
|
31
36
|
isRetryableTerminalFailure: (failure: DelegationFailureDetails) => boolean;
|
|
32
37
|
isSafeToRetryDelegation: (
|
|
33
38
|
policy: ChildStepPolicy,
|
|
@@ -134,8 +139,42 @@ export function createDelegationFailureActions(
|
|
|
134
139
|
dependencies: Pick<
|
|
135
140
|
WorkflowHarnessDependencies,
|
|
136
141
|
'readDelegationReplayAudit' | 'readToolFailureDiagnostic'
|
|
137
|
-
|
|
142
|
+
> &
|
|
143
|
+
Partial<
|
|
144
|
+
Pick<WorkflowHarnessDependencies, 'auditCompletedDelegationTranscript'>
|
|
145
|
+
>,
|
|
138
146
|
): DelegationFailureActions {
|
|
147
|
+
const responseIdentity = (
|
|
148
|
+
active: ActiveDelegation,
|
|
149
|
+
response: SubagentDelegationResponse,
|
|
150
|
+
): { readonly runId: string; readonly childIndex: 0 } | undefined => {
|
|
151
|
+
if (
|
|
152
|
+
response.childIndex !== 0 ||
|
|
153
|
+
typeof response.runId !== 'string' ||
|
|
154
|
+
(response.agent !== undefined && response.agent !== active.agent)
|
|
155
|
+
) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
return { runId: response.runId, childIndex: 0 };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const completedResponseAudit = async (
|
|
162
|
+
active: ActiveDelegation,
|
|
163
|
+
response: SubagentDelegationResponse,
|
|
164
|
+
): Promise<CompletedDelegationTranscriptAudit> => {
|
|
165
|
+
if (!dependencies.auditCompletedDelegationTranscript) {
|
|
166
|
+
return {
|
|
167
|
+
verified: false,
|
|
168
|
+
reason: 'completed transcript auditing is unavailable',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return dependencies.auditCompletedDelegationTranscript(
|
|
172
|
+
response.sessionFile,
|
|
173
|
+
active.trustedSessionRoot,
|
|
174
|
+
responseIdentity(active, response),
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
139
178
|
const describeDelegationFailure = async (
|
|
140
179
|
active: ActiveDelegation,
|
|
141
180
|
response: SubagentDelegationResponse,
|
|
@@ -144,13 +183,7 @@ export function createDelegationFailureActions(
|
|
|
144
183
|
const terminalError = nonEmptyTerminalError(response);
|
|
145
184
|
const error =
|
|
146
185
|
terminalError ?? 'The subagent returned no terminal error details.';
|
|
147
|
-
const
|
|
148
|
-
response.childIndex === 0 &&
|
|
149
|
-
(response.agent === undefined || response.agent === active.agent);
|
|
150
|
-
const identity =
|
|
151
|
-
isResponseIdentityValid && response.runId !== undefined
|
|
152
|
-
? { runId: response.runId, childIndex: 0 }
|
|
153
|
-
: undefined;
|
|
186
|
+
const identity = responseIdentity(active, response);
|
|
154
187
|
const [diagnostic, replayAudit] = await Promise.all([
|
|
155
188
|
dependencies.readToolFailureDiagnostic(
|
|
156
189
|
response.sessionFile,
|
|
@@ -166,7 +199,6 @@ export function createDelegationFailureActions(
|
|
|
166
199
|
{
|
|
167
200
|
task: active.transcriptTask,
|
|
168
201
|
bashPermission: active.policy.permissions.bash,
|
|
169
|
-
approvedBashCommands: active.policy.approvedBashCommands ?? [],
|
|
170
202
|
},
|
|
171
203
|
),
|
|
172
204
|
]);
|
|
@@ -205,6 +237,7 @@ export function createDelegationFailureActions(
|
|
|
205
237
|
return {
|
|
206
238
|
delegationFailureFingerprint,
|
|
207
239
|
hasContradictoryCompletion,
|
|
240
|
+
completedResponseAudit,
|
|
208
241
|
isRetryableTerminalFailure,
|
|
209
242
|
isSafeToRetryDelegation,
|
|
210
243
|
rejectedRecoveryReason,
|
|
@@ -9,10 +9,6 @@ import {
|
|
|
9
9
|
type ChildStepPolicy,
|
|
10
10
|
type SubagentDelegationRequest,
|
|
11
11
|
} from '../integrations/subagents/protocol.ts';
|
|
12
|
-
import {
|
|
13
|
-
narrowApprovedBashCommands,
|
|
14
|
-
resolveReviewedRepositoryCwd,
|
|
15
|
-
} from '../policy/approved-commands.ts';
|
|
16
12
|
import { automaticRecoveryTask, buildDelegatedStepTask } from '../prompt.ts';
|
|
17
13
|
import { WORKFLOW_COMPLETION_PARAMETERS } from '../runtime/completion-tool.ts';
|
|
18
14
|
import { MAX_DELEGATION_RECOVERY_ATTEMPTS } from './delegation-retry-policy.ts';
|
|
@@ -46,17 +42,6 @@ function delegationTranscriptBinding(
|
|
|
46
42
|
return `<pi-workflows-delegation-binding-v1>${requestId}:${policyDigest}</pi-workflows-delegation-binding-v1>`;
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
function approvedCommands(
|
|
50
|
-
run: WorkflowRun,
|
|
51
|
-
step: WorkflowStep,
|
|
52
|
-
): ReadonlyArray<string> {
|
|
53
|
-
return narrowApprovedBashCommands(
|
|
54
|
-
run.reviewedArtifact ?? '',
|
|
55
|
-
run.stepHandoff ?? '',
|
|
56
|
-
step.permissions.bash.approvedSources ?? [],
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
45
|
/**
|
|
61
46
|
* Creates one immutable subagent request from workflow state and injected
|
|
62
47
|
* identity/workspace effects.
|
|
@@ -65,7 +50,9 @@ export function createDelegationPlan(
|
|
|
65
50
|
input: DelegationPlanInput,
|
|
66
51
|
dependencies: Pick<
|
|
67
52
|
WorkflowHarnessDependencies,
|
|
68
|
-
|
|
53
|
+
| 'createDelegationWorkspace'
|
|
54
|
+
| 'createRequestId'
|
|
55
|
+
| 'resolveWorkspaceDirectory'
|
|
69
56
|
>,
|
|
70
57
|
): DelegationPlan {
|
|
71
58
|
const { workflow, run, step, latestContext, recovery } = input;
|
|
@@ -76,33 +63,100 @@ export function createDelegationPlan(
|
|
|
76
63
|
reason: `Step "${run.currentStepId}" has no subagent configuration`,
|
|
77
64
|
};
|
|
78
65
|
}
|
|
66
|
+
if (!run.cwd) {
|
|
67
|
+
return {
|
|
68
|
+
kind: 'invalid',
|
|
69
|
+
reason:
|
|
70
|
+
'Workflow run has no captured working directory; abort it and start a new run',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!run.startCwd || !latestContext?.cwd) {
|
|
74
|
+
return {
|
|
75
|
+
kind: 'invalid',
|
|
76
|
+
reason:
|
|
77
|
+
'Workflow start directory or current session directory is unavailable; abort it and start a new run',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
let canonicalStartCwd: string;
|
|
82
|
+
let canonicalSessionCwd: string;
|
|
83
|
+
try {
|
|
84
|
+
canonicalStartCwd = dependencies.resolveWorkspaceDirectory({
|
|
85
|
+
candidateCwd: run.startCwd,
|
|
86
|
+
startCwd: run.startCwd,
|
|
87
|
+
allowedRoots: ['.'],
|
|
88
|
+
});
|
|
89
|
+
canonicalSessionCwd = dependencies.resolveWorkspaceDirectory({
|
|
90
|
+
candidateCwd: latestContext.cwd,
|
|
91
|
+
startCwd: latestContext.cwd,
|
|
92
|
+
allowedRoots: ['.'],
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
kind: 'invalid',
|
|
97
|
+
reason: `Workflow start directory is no longer valid: ${
|
|
98
|
+
error instanceof Error ? error.message : String(error)
|
|
99
|
+
}`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (
|
|
103
|
+
canonicalStartCwd !== run.startCwd ||
|
|
104
|
+
canonicalSessionCwd !== canonicalStartCwd
|
|
105
|
+
) {
|
|
106
|
+
return {
|
|
107
|
+
kind: 'invalid',
|
|
108
|
+
reason:
|
|
109
|
+
'Current session cwd does not match the captured workflow start directory',
|
|
110
|
+
};
|
|
85
111
|
}
|
|
86
112
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
let delegationCwd = run.cwd;
|
|
114
|
+
const bindingEntry = [...run.history]
|
|
115
|
+
.reverse()
|
|
116
|
+
.find((entry) => entry.workspaceCwd !== undefined);
|
|
117
|
+
if (!bindingEntry && run.cwd !== run.startCwd) {
|
|
118
|
+
return {
|
|
119
|
+
kind: 'invalid',
|
|
120
|
+
reason:
|
|
121
|
+
'Workflow cwd differs from its start directory without a workspace binding',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (bindingEntry) {
|
|
125
|
+
const binding = workflow.definition.steps[bindingEntry.stepId]?.workspace;
|
|
126
|
+
if (!binding || !run.startCwd || bindingEntry.workspaceCwd !== run.cwd) {
|
|
127
|
+
return {
|
|
128
|
+
kind: 'invalid',
|
|
129
|
+
reason:
|
|
130
|
+
'Workflow workspace binding no longer matches its persisted configuration',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
delegationCwd = dependencies.resolveWorkspaceDirectory({
|
|
135
|
+
candidateCwd: run.cwd,
|
|
136
|
+
startCwd: run.startCwd,
|
|
137
|
+
allowedRoots: binding.allowedRoots,
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return {
|
|
141
|
+
kind: 'invalid',
|
|
142
|
+
reason: `Workflow workspace is no longer valid: ${
|
|
143
|
+
error instanceof Error ? error.message : String(error)
|
|
144
|
+
}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (delegationCwd !== run.cwd) {
|
|
148
|
+
return {
|
|
149
|
+
kind: 'invalid',
|
|
150
|
+
reason:
|
|
151
|
+
'Workflow workspace no longer resolves to the bound canonical directory',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
91
155
|
const requestId =
|
|
92
156
|
`${run.runId}:${run.currentStepId}:` + dependencies.createRequestId();
|
|
93
157
|
const workspace = dependencies.createDelegationWorkspace();
|
|
94
158
|
const outcomes = allowedOutcomes(workflow, run);
|
|
95
159
|
const outcomeSet = new Set(outcomes);
|
|
96
|
-
const approvedBashCommands = approvedCommands(run, step);
|
|
97
|
-
const repositoryPolicy =
|
|
98
|
-
reviewedRepository.kind === 'resolved'
|
|
99
|
-
? {
|
|
100
|
-
repositoryCwd: reviewedRepository.repositoryCwd,
|
|
101
|
-
...(reviewedRepository.bootstrapping
|
|
102
|
-
? { bootstrapCwd: reviewedRepository.cwd }
|
|
103
|
-
: {}),
|
|
104
|
-
}
|
|
105
|
-
: {};
|
|
106
160
|
const policyDigest = digest({
|
|
107
161
|
version: 1,
|
|
108
162
|
requestId,
|
|
@@ -110,10 +164,12 @@ export function createDelegationPlan(
|
|
|
110
164
|
runId: run.runId,
|
|
111
165
|
stepId: run.currentStepId,
|
|
112
166
|
stepDigest: run.currentStepDigest,
|
|
167
|
+
cwd: delegationCwd,
|
|
113
168
|
capabilityPath: workspace.capabilityPath,
|
|
114
169
|
resultPath: workspace.resultPath,
|
|
115
|
-
|
|
116
|
-
|
|
170
|
+
permissions: step.permissions,
|
|
171
|
+
outcomes,
|
|
172
|
+
...(step.workspace ? { workspace: step.workspace } : {}),
|
|
117
173
|
});
|
|
118
174
|
const policy: ChildStepPolicy = {
|
|
119
175
|
version: 1,
|
|
@@ -123,13 +179,12 @@ export function createDelegationPlan(
|
|
|
123
179
|
runId: run.runId,
|
|
124
180
|
stepId: run.currentStepId,
|
|
125
181
|
stepTitle: step.title,
|
|
182
|
+
cwd: delegationCwd,
|
|
126
183
|
policyDigest,
|
|
127
184
|
capabilityPath: workspace.capabilityPath,
|
|
128
185
|
capabilityToken: workspace.capabilityToken,
|
|
129
186
|
resultPath: workspace.resultPath,
|
|
130
187
|
permissions: structuredClone(step.permissions),
|
|
131
|
-
...(approvedBashCommands.length > 0 ? { approvedBashCommands } : {}),
|
|
132
|
-
...repositoryPolicy,
|
|
133
188
|
outcomes,
|
|
134
189
|
pauseOutcomes: Object.entries(step.transitions)
|
|
135
190
|
.filter(
|
|
@@ -138,9 +193,10 @@ export function createDelegationPlan(
|
|
|
138
193
|
.map(([outcome]) => outcome),
|
|
139
194
|
summaryMaxChars: workflow.definition.summaryMaxChars,
|
|
140
195
|
...(step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {}),
|
|
196
|
+
...(step.workspace ? { workspace: structuredClone(step.workspace) } : {}),
|
|
141
197
|
};
|
|
142
198
|
const trustedSessionRoot = deriveSubagentSessionRoot(
|
|
143
|
-
latestContext
|
|
199
|
+
latestContext.sessionManager.getSessionFile(),
|
|
144
200
|
);
|
|
145
201
|
const transcriptTask = [
|
|
146
202
|
buildDelegatedStepTask(workflow, run, ''),
|
|
@@ -25,6 +25,7 @@ export function completionMatchesResult(
|
|
|
25
25
|
'outcome',
|
|
26
26
|
'summary',
|
|
27
27
|
...(result.artifact === undefined ? [] : ['artifact']),
|
|
28
|
+
...(result.workspace === undefined ? [] : ['workspace']),
|
|
28
29
|
].sort();
|
|
29
30
|
const actualKeys = Object.keys(value).sort();
|
|
30
31
|
if (
|
|
@@ -45,7 +46,8 @@ export function completionMatchesResult(
|
|
|
45
46
|
return (
|
|
46
47
|
completion.outcome === result.outcome &&
|
|
47
48
|
completion.summary === result.summary &&
|
|
48
|
-
completion.artifact === result.artifact
|
|
49
|
+
completion.artifact === result.artifact &&
|
|
50
|
+
completion.workspace?.cwd === result.workspace?.cwd
|
|
49
51
|
);
|
|
50
52
|
} catch {
|
|
51
53
|
return false;
|
|
@@ -4,9 +4,14 @@ import {
|
|
|
4
4
|
type SubagentDelegationUpdate,
|
|
5
5
|
} from '../integrations/subagents/protocol.ts';
|
|
6
6
|
import { advanceRun } from '../engine/transitions.ts';
|
|
7
|
+
import {
|
|
8
|
+
attachSubagentTranscript,
|
|
9
|
+
recordCurrentStepResult,
|
|
10
|
+
} from '../engine/step-trace.ts';
|
|
7
11
|
import type { WorkflowStepResult } from '../runtime/step-result.ts';
|
|
8
12
|
import type { HarnessActionContext as FullHarnessActionContext } from './action-context.ts';
|
|
9
13
|
import type { ActiveDelegation, DelegationFailureDetails } from './types.ts';
|
|
14
|
+
import { resolveStepEffects } from './step-effects.ts';
|
|
10
15
|
|
|
11
16
|
type HarnessActionContext = Pick<
|
|
12
17
|
FullHarnessActionContext,
|
|
@@ -138,11 +143,51 @@ async function finishDelegation(
|
|
|
138
143
|
) {
|
|
139
144
|
return;
|
|
140
145
|
}
|
|
146
|
+
const terminalAt = this.dependencies.now();
|
|
147
|
+
if (
|
|
148
|
+
response.requestId === active.requestId &&
|
|
149
|
+
(response.agent === undefined || response.agent === active.agent) &&
|
|
150
|
+
response.sessionFile &&
|
|
151
|
+
active.trustedSessionRoot &&
|
|
152
|
+
typeof response.runId === 'string' &&
|
|
153
|
+
response.runId &&
|
|
154
|
+
response.childIndex === 0
|
|
155
|
+
) {
|
|
156
|
+
try {
|
|
157
|
+
this.run = attachSubagentTranscript(
|
|
158
|
+
this.run,
|
|
159
|
+
active.requestId,
|
|
160
|
+
{
|
|
161
|
+
trustedRoot: active.trustedSessionRoot,
|
|
162
|
+
sessionFile: response.sessionFile,
|
|
163
|
+
runId: response.runId,
|
|
164
|
+
childIndex: response.childIndex,
|
|
165
|
+
},
|
|
166
|
+
terminalAt,
|
|
167
|
+
);
|
|
168
|
+
} catch {
|
|
169
|
+
// Status evidence is best-effort and cannot alter terminal handling.
|
|
170
|
+
}
|
|
171
|
+
}
|
|
141
172
|
const workflow = this.catalog.workflows.get(this.run.workflowId);
|
|
142
173
|
const step = workflow?.definition.steps[this.run.currentStepId];
|
|
143
174
|
if (!workflow || !step) {
|
|
144
175
|
throw new Error('Active workflow configuration is unavailable');
|
|
145
176
|
}
|
|
177
|
+
if (response.status === 'completed') {
|
|
178
|
+
const transcriptAudit =
|
|
179
|
+
await this.delegationFailures.completedResponseAudit(active, response);
|
|
180
|
+
if (!transcriptAudit.verified) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Subagent "${active.agent}" completed without a verifiable terminal transcript: ${transcriptAudit.reason}`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (transcriptAudit.warning) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Subagent "${active.agent}" completed with an unresolved post-completion watchdog warning: ${transcriptAudit.warning}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
146
191
|
let recoveredTerminalFailure: DelegationFailureDetails | undefined;
|
|
147
192
|
if (
|
|
148
193
|
response.status !== 'completed' ||
|
|
@@ -237,6 +282,7 @@ async function finishDelegation(
|
|
|
237
282
|
),
|
|
238
283
|
);
|
|
239
284
|
}
|
|
285
|
+
const acceptedAt = terminalAt;
|
|
240
286
|
if (recoveredTerminalFailure) {
|
|
241
287
|
const isFalsePositive =
|
|
242
288
|
recoveredTerminalFailure.diagnostic?.correlation ===
|
|
@@ -249,29 +295,53 @@ async function finishDelegation(
|
|
|
249
295
|
);
|
|
250
296
|
}
|
|
251
297
|
if (step.gate?.submitOutcome === result.outcome) {
|
|
298
|
+
this.run = recordCurrentStepResult(this.run, result, acceptedAt);
|
|
252
299
|
await this.submitGate(
|
|
253
300
|
workflow,
|
|
254
301
|
this.run,
|
|
255
302
|
result.outcome,
|
|
303
|
+
result.summary,
|
|
256
304
|
result.artifact ?? '',
|
|
257
305
|
);
|
|
258
306
|
return;
|
|
259
307
|
}
|
|
260
308
|
|
|
309
|
+
const effects = resolveStepEffects(
|
|
310
|
+
this.run,
|
|
311
|
+
step,
|
|
312
|
+
result,
|
|
313
|
+
this.dependencies,
|
|
314
|
+
);
|
|
315
|
+
const tracedRun = recordCurrentStepResult(
|
|
316
|
+
this.run,
|
|
317
|
+
result,
|
|
318
|
+
acceptedAt,
|
|
319
|
+
effects.workspaceCwd,
|
|
320
|
+
);
|
|
261
321
|
this.run = advanceRun(
|
|
262
322
|
workflow,
|
|
263
|
-
|
|
323
|
+
tracedRun,
|
|
264
324
|
result.outcome,
|
|
265
325
|
result.summary,
|
|
266
326
|
this.dependencies.now(),
|
|
327
|
+
effects,
|
|
267
328
|
);
|
|
268
|
-
this.settleAfterTransition(workflow
|
|
329
|
+
this.settleAfterTransition(workflow, {
|
|
330
|
+
stepId: active.stepId,
|
|
331
|
+
outcome: result.outcome,
|
|
332
|
+
summary: result.summary,
|
|
333
|
+
});
|
|
269
334
|
} catch (error) {
|
|
270
335
|
const reason = error instanceof Error ? error.message : String(error);
|
|
271
336
|
cleanupAttempted = true;
|
|
272
337
|
await this.cleanupDelegation(active);
|
|
273
338
|
if (!this.retryDelegationAfterFailure(active, terminalFailure, reason)) {
|
|
274
|
-
|
|
339
|
+
const failureSummary = terminalFailure?.error
|
|
340
|
+
? `Subagent ${terminalFailure.status.replaceAll('_', ' ')}: ${terminalFailure.error}`
|
|
341
|
+
: terminalFailure
|
|
342
|
+
? `Subagent ${terminalFailure.status.replaceAll('_', ' ')}`
|
|
343
|
+
: reason;
|
|
344
|
+
this.pauseForDelegationFailure(reason, failureSummary);
|
|
275
345
|
}
|
|
276
346
|
} finally {
|
|
277
347
|
try {
|
|
@@ -32,6 +32,9 @@ export function delegationFailureFingerprint(
|
|
|
32
32
|
tool: normalizedFingerprintField(failure.diagnostic.tool),
|
|
33
33
|
call: normalizedFingerprintField(failure.diagnostic.call),
|
|
34
34
|
output: normalizedFingerprintField(failure.diagnostic.output),
|
|
35
|
+
postCompletionWarning: normalizedFingerprintField(
|
|
36
|
+
failure.diagnostic.postCompletionWarning,
|
|
37
|
+
),
|
|
35
38
|
}
|
|
36
39
|
: undefined,
|
|
37
40
|
});
|
|
@@ -62,6 +65,7 @@ export function isRetryableTerminalFailure(
|
|
|
62
65
|
failure: DelegationFailureDetails,
|
|
63
66
|
): boolean {
|
|
64
67
|
if (failure.recoveryBlocker !== undefined) return false;
|
|
68
|
+
if (failure.diagnostic?.postCompletionWarning !== undefined) return false;
|
|
65
69
|
if (
|
|
66
70
|
failure.status === 'timed_out' ||
|
|
67
71
|
failure.status === 'turn_budget_exhausted' ||
|
|
@@ -96,9 +100,7 @@ export function isSafeToRetryDelegation(
|
|
|
96
100
|
): boolean {
|
|
97
101
|
return (
|
|
98
102
|
replayAudit?.replaySafe === true &&
|
|
99
|
-
(isReplayExplicitlyAuthorized ||
|
|
100
|
-
policy.permissions.bash.mode === 'deny' ||
|
|
101
|
-
policy.permissions.bash.mode === 'read-only')
|
|
103
|
+
(isReplayExplicitlyAuthorized || policy.permissions.bash.mode === 'deny')
|
|
102
104
|
);
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type SubagentDelegationClientController,
|
|
16
16
|
} from '../integrations/subagents/client.ts';
|
|
17
17
|
import {
|
|
18
|
+
auditCompletedDelegationTranscript,
|
|
18
19
|
readDelegationReplayAudit,
|
|
19
20
|
readToolFailureDiagnostic,
|
|
20
21
|
} from '../integrations/subagents/diagnostics.ts';
|
|
@@ -28,6 +29,10 @@ import {
|
|
|
28
29
|
} from '../runtime/serial-task-queue.ts';
|
|
29
30
|
import { showWorkflowStatus } from '../workflow-status.ts';
|
|
30
31
|
import type { ActiveDelegation } from './types.ts';
|
|
32
|
+
import {
|
|
33
|
+
resolveWorkspaceDirectory,
|
|
34
|
+
type ResolveWorkspaceDirectoryOptions,
|
|
35
|
+
} from './workspace-directory.ts';
|
|
31
36
|
|
|
32
37
|
const MAX_DELEGATED_RESULT_BYTES = 1024 * 1024;
|
|
33
38
|
|
|
@@ -47,14 +52,17 @@ export type WorkflowHarnessDependencies = {
|
|
|
47
52
|
readonly removeDelegationWorkspace: (
|
|
48
53
|
resultDirectory: string,
|
|
49
54
|
) => Promise<void>;
|
|
50
|
-
readonly currentWorkingDirectory: () => string;
|
|
51
55
|
readonly waitForDelay: (delayMs: number) => Promise<void>;
|
|
56
|
+
readonly resolveWorkspaceDirectory: (
|
|
57
|
+
options: ResolveWorkspaceDirectoryOptions,
|
|
58
|
+
) => string;
|
|
52
59
|
readonly loadCatalog: typeof loadCatalog;
|
|
53
60
|
readonly requestPlannotatorReview: typeof requestPlannotatorReview;
|
|
54
61
|
readonly requestPlannotatorReviewStatus: typeof requestPlannotatorReviewStatus;
|
|
55
62
|
readonly requestPromptGateReview: typeof requestPromptGateReview;
|
|
56
63
|
readonly readDelegationReplayAudit: typeof readDelegationReplayAudit;
|
|
57
64
|
readonly readToolFailureDiagnostic: typeof readToolFailureDiagnostic;
|
|
65
|
+
readonly auditCompletedDelegationTranscript: typeof auditCompletedDelegationTranscript;
|
|
58
66
|
readonly showWorkflowStatus: typeof showWorkflowStatus;
|
|
59
67
|
readonly createSubagentClient: (
|
|
60
68
|
pi: ExtensionAPI,
|
|
@@ -132,15 +140,16 @@ const DEFAULT_DEPENDENCIES: WorkflowHarnessDependencies = {
|
|
|
132
140
|
readDelegatedResult,
|
|
133
141
|
removeDelegationWorkspace: async (resultDirectory) =>
|
|
134
142
|
rm(resultDirectory, { recursive: true, force: true }),
|
|
135
|
-
currentWorkingDirectory: () => process.cwd(),
|
|
136
143
|
waitForDelay: async (delayMs) =>
|
|
137
144
|
new Promise((resolve) => setTimeout(resolve, delayMs)),
|
|
145
|
+
resolveWorkspaceDirectory,
|
|
138
146
|
loadCatalog,
|
|
139
147
|
requestPlannotatorReview,
|
|
140
148
|
requestPlannotatorReviewStatus,
|
|
141
149
|
requestPromptGateReview,
|
|
142
150
|
readDelegationReplayAudit,
|
|
143
151
|
readToolFailureDiagnostic,
|
|
152
|
+
auditCompletedDelegationTranscript,
|
|
144
153
|
showWorkflowStatus,
|
|
145
154
|
createSubagentClient: (pi) => createSubagentDelegationClient(pi.events),
|
|
146
155
|
createMainStepRuntime: (pi) => createMainStepRuntime({ pi }),
|