@wichayutdew/pi-workflows 2.5.1 → 2.7.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 +34 -1
- package/dist/index.js +1315 -2467
- package/examples/starter-kit/agents/planner.md +11 -0
- package/examples/starter-kit/agents/reviewer.md +10 -0
- package/examples/starter-kit/agents/scout.md +10 -0
- package/examples/starter-kit/agents/worker.md +11 -0
- package/examples/starter-kit/agents/workspace-preparer.md +10 -0
- package/examples/starter-kit/investigate.workflow.yaml +84 -0
- package/examples/starter-kit/jira.workflow.yaml +75 -0
- package/examples/starter-kit/mr-comment.workflow.yaml +86 -93
- package/examples/starter-kit/mr-review.workflow.yaml +52 -88
- package/examples/starter-kit/settings.yaml +4 -0
- package/examples/starter-kit/steps/investigate/investigate.md +40 -0
- package/examples/starter-kit/steps/investigate/retrieve.md +28 -0
- package/examples/starter-kit/steps/investigate/validate.md +20 -0
- package/examples/starter-kit/steps/jira/create.md +25 -0
- package/examples/starter-kit/steps/jira/draft.md +18 -0
- package/examples/starter-kit/steps/jira/plan.md +30 -0
- package/examples/starter-kit/steps/mr-comment/checkout-source.md +14 -0
- package/examples/starter-kit/steps/mr-comment/fetch.md +11 -28
- package/examples/starter-kit/steps/mr-comment/implement.md +9 -26
- package/examples/starter-kit/steps/mr-comment/plan.md +46 -55
- package/examples/starter-kit/steps/mr-comment/publish.md +11 -31
- package/examples/starter-kit/steps/mr-comment/verify.md +7 -34
- package/examples/starter-kit/steps/mr-review/fetch.md +13 -21
- package/examples/starter-kit/steps/mr-review/publish-approved.md +18 -0
- package/examples/starter-kit/steps/mr-review/review-for-approval.md +53 -0
- package/examples/starter-kit/steps/mr-review/verify-published.md +16 -0
- package/examples/starter-kit/steps/shared/prepare-workspace.md +11 -89
- package/examples/starter-kit/steps/shared/publish-remote.md +16 -0
- package/examples/starter-kit/steps/ticket/implement.md +10 -23
- package/examples/starter-kit/steps/ticket/plan.md +49 -98
- package/examples/starter-kit/steps/ticket/verify.md +14 -65
- package/examples/starter-kit/steps/work/implement.md +11 -22
- package/examples/starter-kit/steps/work/plan.md +43 -59
- package/examples/starter-kit/steps/work/verify.md +14 -25
- package/examples/starter-kit/ticket.workflow.yaml +57 -61
- package/examples/starter-kit/work.workflow.yaml +49 -57
- package/package.json +9 -19
- package/schemas/workflow.schema.json +63 -15
- package/scripts/patch-herdr-agent-state.mjs +36 -0
- package/src/agents/profile.ts +98 -0
- package/src/config/ceiling.ts +0 -82
- package/src/config/types.ts +15 -53
- package/src/config/validation/settings.ts +2 -14
- package/src/config/validation/step.ts +129 -8
- package/src/config/validation/workflow.ts +1 -10
- package/src/engine/run-workflow-validation.ts +0 -3
- package/src/engine/state-types.ts +1 -1
- package/src/harness/action-context.ts +1 -13
- package/src/harness/artifact-contract.ts +46 -0
- package/src/harness/delegation-control-actions.ts +4 -63
- package/src/harness/delegation-plan.ts +26 -80
- package/src/harness/delegation-response-actions.ts +22 -135
- package/src/harness/dependencies.ts +1 -12
- package/src/harness/gate-submission-action.ts +28 -0
- package/src/harness/status-actions.ts +20 -0
- package/src/harness/step-execution-actions.ts +2 -9
- package/src/harness/types.ts +2 -40
- package/src/harness.ts +2 -17
- package/src/herdr-workflow-state.ts +65 -0
- package/src/index.ts +4 -9
- package/src/integrations/subagents/child-policy-validation.ts +6 -7
- package/src/integrations/subagents/child-runtime-dependencies.ts +1 -1
- package/src/integrations/subagents/child-runtime-policy.ts +1 -7
- package/src/integrations/subagents/child-runtime.ts +18 -9
- package/src/integrations/subagents/client.ts +240 -98
- package/src/integrations/subagents/protocol-events.ts +32 -15
- package/src/integrations/subagents/protocol.ts +1 -1
- package/src/preflight.ts +0 -8
- package/src/prompt/main-workflow-notice.ts +8 -15
- package/src/prompt/step-task.ts +7 -6
- package/src/workflow-status/format-status.ts +5 -1
- package/src/workflow-status/render-step-detail.ts +94 -0
- package/src/workflow-status/types.ts +1 -0
- package/src/workflow-status/view.ts +49 -14
- package/agents/step.md +0 -32
- package/examples/mr-comments.workflow.yaml +0 -125
- package/examples/prompts/mr-comments/implement.md +0 -17
- package/examples/prompts/mr-comments/inspect.md +0 -5
- package/examples/prompts/mr-comments/plan.md +0 -54
- package/examples/prompts/mr-comments/verify.md +0 -9
- package/examples/settings.yaml +0 -27
- package/examples/starter-kit/steps/mr-review/publish.md +0 -48
- package/examples/starter-kit/steps/mr-review/review.md +0 -76
- package/examples/starter-kit/steps/mr-review/verify.md +0 -35
- package/src/config/validation/subagent.ts +0 -288
- package/src/harness/delegation-failure.ts +0 -248
- package/src/harness/delegation-recovery-validation.ts +0 -161
- package/src/harness/delegation-retry-policy.ts +0 -120
- package/src/integrations/subagents/client-delegation.ts +0 -181
- package/src/integrations/subagents/client-messages.ts +0 -66
- package/src/integrations/subagents/client-types.ts +0 -36
- package/src/integrations/subagents/diagnostic-format.ts +0 -45
- package/src/integrations/subagents/diagnostic-text.ts +0 -114
- package/src/integrations/subagents/diagnostic-types.ts +0 -83
- package/src/integrations/subagents/diagnostics.ts +0 -26
- package/src/integrations/subagents/failure-correlation.ts +0 -285
- package/src/integrations/subagents/failure-transcript.ts +0 -252
- package/src/integrations/subagents/hidden-bash-failure.ts +0 -98
- package/src/integrations/subagents/replay-audit.ts +0 -146
- package/src/integrations/subagents/replay-safety.ts +0 -67
- package/src/integrations/subagents/session-diagnostics.ts +0 -357
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ArtifactContract } from '../config/types.ts';
|
|
2
|
+
|
|
3
|
+
function countOccurrences(value: string, substring: string): number {
|
|
4
|
+
let count = 0;
|
|
5
|
+
let offset = 0;
|
|
6
|
+
while (true) {
|
|
7
|
+
const match = value.indexOf(substring, offset);
|
|
8
|
+
if (match === -1) return count;
|
|
9
|
+
count += 1;
|
|
10
|
+
offset = match + substring.length;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validateArtifactContract(
|
|
15
|
+
artifact: string,
|
|
16
|
+
contract: ArtifactContract | undefined,
|
|
17
|
+
): string | undefined {
|
|
18
|
+
if (!contract) return undefined;
|
|
19
|
+
if (artifact.length > contract.maxChars) {
|
|
20
|
+
return `gate artifact exceeds ${contract.maxChars} characters`;
|
|
21
|
+
}
|
|
22
|
+
const required = contract.requiredSubstrings.find(
|
|
23
|
+
(substring) => !artifact.includes(substring),
|
|
24
|
+
);
|
|
25
|
+
if (required) {
|
|
26
|
+
return `gate artifact is missing required text: ${JSON.stringify(required)}`;
|
|
27
|
+
}
|
|
28
|
+
const forbidden = contract.forbiddenSubstrings.find((substring) =>
|
|
29
|
+
artifact.includes(substring),
|
|
30
|
+
);
|
|
31
|
+
if (forbidden) {
|
|
32
|
+
return `gate artifact contains forbidden text: ${JSON.stringify(forbidden)}`;
|
|
33
|
+
}
|
|
34
|
+
for (const group of contract.equalOccurrenceGroups) {
|
|
35
|
+
const counts = group.map((substring) =>
|
|
36
|
+
countOccurrences(artifact, substring),
|
|
37
|
+
);
|
|
38
|
+
if (counts.some((count) => count === 0)) {
|
|
39
|
+
return `gate artifact is missing required repeated text: ${JSON.stringify(group)}`;
|
|
40
|
+
}
|
|
41
|
+
if (!counts.every((count) => count === counts[0])) {
|
|
42
|
+
return `gate artifact has unequal repeated text counts: ${JSON.stringify(group)}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
import { failRun } from '../engine/transitions.ts';
|
|
2
2
|
import type { HarnessActionContext as FullHarnessActionContext } from './action-context.ts';
|
|
3
|
-
import {
|
|
4
|
-
boundedFailureField,
|
|
5
|
-
MAX_DELEGATION_RECOVERY_ATTEMPTS,
|
|
6
|
-
} from './delegation-retry-policy.ts';
|
|
7
3
|
import {
|
|
8
4
|
conciseStepFailureSummary,
|
|
9
5
|
reportFailedStep,
|
|
10
6
|
} from './step-reporting.ts';
|
|
11
|
-
import type { ActiveDelegation
|
|
7
|
+
import type { ActiveDelegation } from './types.ts';
|
|
8
|
+
|
|
9
|
+
const boundedFailureField = (value: string): string =>
|
|
10
|
+
value.length <= 500 ? value : `${value.slice(0, 499)}…`;
|
|
12
11
|
|
|
13
12
|
type HarnessActionContext = Pick<
|
|
14
13
|
FullHarnessActionContext,
|
|
15
14
|
| 'activeDelegation'
|
|
16
15
|
| 'catalog'
|
|
17
16
|
| 'cleanupDelegation'
|
|
18
|
-
| 'delegationFailures'
|
|
19
17
|
| 'dependencies'
|
|
20
|
-
| 'isSessionActive'
|
|
21
18
|
| 'isolateMainSessionTools'
|
|
22
19
|
| 'latestContext'
|
|
23
|
-
| 'launchCurrentStep'
|
|
24
20
|
| 'mainSteps'
|
|
25
21
|
| 'pauseForExecutionFailure'
|
|
26
22
|
| 'persist'
|
|
27
23
|
| 'pi'
|
|
28
24
|
| 'restoreBaselineTools'
|
|
29
25
|
| 'run'
|
|
30
|
-
| 'sessionEpoch'
|
|
31
26
|
| 'subagents'
|
|
32
27
|
| 'updateStatus'
|
|
33
28
|
>;
|
|
@@ -41,12 +36,6 @@ export type DelegationControlActions = {
|
|
|
41
36
|
this: HarnessActionContext,
|
|
42
37
|
active: ActiveDelegation,
|
|
43
38
|
) => Promise<void>;
|
|
44
|
-
retryDelegationAfterFailure: (
|
|
45
|
-
this: HarnessActionContext,
|
|
46
|
-
active: ActiveDelegation,
|
|
47
|
-
failure: DelegationFailureDetails | undefined,
|
|
48
|
-
reason: string,
|
|
49
|
-
) => boolean;
|
|
50
39
|
pauseForDelegationFailure: (
|
|
51
40
|
this: HarnessActionContext,
|
|
52
41
|
reason: string,
|
|
@@ -119,53 +108,6 @@ async function cleanupDelegation(
|
|
|
119
108
|
}
|
|
120
109
|
}
|
|
121
110
|
|
|
122
|
-
function retryDelegationAfterFailure(
|
|
123
|
-
this: HarnessActionContext,
|
|
124
|
-
active: ActiveDelegation,
|
|
125
|
-
failure: DelegationFailureDetails | undefined,
|
|
126
|
-
reason: string,
|
|
127
|
-
): boolean {
|
|
128
|
-
if (!failure) return false;
|
|
129
|
-
const fingerprint =
|
|
130
|
-
this.delegationFailures.delegationFailureFingerprint(failure);
|
|
131
|
-
const isRepeatedFailure = active.recoveryFailures.some(
|
|
132
|
-
(previousFailure) => previousFailure.fingerprint === fingerprint,
|
|
133
|
-
);
|
|
134
|
-
if (
|
|
135
|
-
active.recoveryAttemptCount >= MAX_DELEGATION_RECOVERY_ATTEMPTS ||
|
|
136
|
-
isRepeatedFailure ||
|
|
137
|
-
!this.delegationFailures.isRetryableTerminalFailure(failure) ||
|
|
138
|
-
!this.delegationFailures.isSafeToRetryDelegation(
|
|
139
|
-
active.policy,
|
|
140
|
-
active.broadRecoveryAuthorized,
|
|
141
|
-
failure.replayAudit,
|
|
142
|
-
) ||
|
|
143
|
-
!this.isSessionActive ||
|
|
144
|
-
this.sessionEpoch !== active.sessionEpoch ||
|
|
145
|
-
!this.run ||
|
|
146
|
-
this.run.status !== 'running' ||
|
|
147
|
-
this.run.runId !== active.runId ||
|
|
148
|
-
this.run.currentStepId !== active.stepId ||
|
|
149
|
-
this.run.currentStepDigest !== active.stepDigest ||
|
|
150
|
-
this.activeDelegation
|
|
151
|
-
) {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
const workflow = this.catalog.workflows.get(this.run.workflowId);
|
|
155
|
-
if (!workflow) return false;
|
|
156
|
-
|
|
157
|
-
const attempt = active.recoveryAttemptCount + 1;
|
|
158
|
-
this.latestContext?.ui.notify(
|
|
159
|
-
`Automatic recovery for "${active.stepId}" after a subagent failure (${attempt}/${MAX_DELEGATION_RECOVERY_ATTEMPTS})`,
|
|
160
|
-
'warning',
|
|
161
|
-
);
|
|
162
|
-
this.launchCurrentStep(workflow, {
|
|
163
|
-
attempt,
|
|
164
|
-
failures: [...active.recoveryFailures, { fingerprint, reason }],
|
|
165
|
-
});
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
111
|
function pauseForDelegationFailure(
|
|
170
112
|
this: HarnessActionContext,
|
|
171
113
|
reason: string,
|
|
@@ -266,7 +208,6 @@ export function createDelegationControlActions(): DelegationControlActions {
|
|
|
266
208
|
return {
|
|
267
209
|
cancelActiveDelegation,
|
|
268
210
|
cleanupDelegation,
|
|
269
|
-
retryDelegationAfterFailure,
|
|
270
211
|
pauseForDelegationFailure,
|
|
271
212
|
pauseForExecutionFailure,
|
|
272
213
|
retainUnconfirmedDelegation,
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { loadAgentProfile } from '../agents/profile.ts';
|
|
2
3
|
import type { LoadedWorkflow, WorkflowStep } from '../config/types.ts';
|
|
3
4
|
import type { WorkflowRun } from '../engine/state.ts';
|
|
4
5
|
import { allowedOutcomes } from '../engine/transitions.ts';
|
|
5
6
|
import { digest } from '../digest.ts';
|
|
6
|
-
import { deriveSubagentSessionRoot } from '../integrations/subagents/diagnostics.ts';
|
|
7
7
|
import {
|
|
8
8
|
encodeChildPolicy,
|
|
9
9
|
type ChildStepPolicy,
|
|
10
10
|
type SubagentDelegationRequest,
|
|
11
11
|
} from '../integrations/subagents/protocol.ts';
|
|
12
|
-
import {
|
|
13
|
-
import { WORKFLOW_COMPLETION_PARAMETERS } from '../runtime/completion-tool.ts';
|
|
14
|
-
import { MAX_DELEGATION_RECOVERY_ATTEMPTS } from './delegation-retry-policy.ts';
|
|
12
|
+
import { buildDelegatedStepTask } from '../prompt.ts';
|
|
15
13
|
import type { WorkflowHarnessDependencies } from './dependencies.ts';
|
|
16
|
-
import type { ActiveDelegation
|
|
14
|
+
import type { ActiveDelegation } from './types.ts';
|
|
17
15
|
|
|
18
16
|
type DelegationPlanInput = {
|
|
19
17
|
workflow: LoadedWorkflow;
|
|
@@ -21,7 +19,6 @@ type DelegationPlanInput = {
|
|
|
21
19
|
step: WorkflowStep;
|
|
22
20
|
sessionEpoch: number;
|
|
23
21
|
latestContext: ExtensionContext | undefined;
|
|
24
|
-
recovery: DelegationRecovery | undefined;
|
|
25
22
|
};
|
|
26
23
|
|
|
27
24
|
export type DelegationPlan =
|
|
@@ -35,13 +32,6 @@ export type DelegationPlan =
|
|
|
35
32
|
request: SubagentDelegationRequest;
|
|
36
33
|
};
|
|
37
34
|
|
|
38
|
-
function delegationTranscriptBinding(
|
|
39
|
-
requestId: string,
|
|
40
|
-
policyDigest: string,
|
|
41
|
-
): string {
|
|
42
|
-
return `<pi-workflows-delegation-binding-v1>${requestId}:${policyDigest}</pi-workflows-delegation-binding-v1>`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
35
|
/**
|
|
46
36
|
* Creates one immutable subagent request from workflow state and injected
|
|
47
37
|
* identity/workspace effects.
|
|
@@ -55,12 +45,21 @@ export function createDelegationPlan(
|
|
|
55
45
|
| 'resolveWorkspaceDirectory'
|
|
56
46
|
>,
|
|
57
47
|
): DelegationPlan {
|
|
58
|
-
const { workflow, run, step, latestContext
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
48
|
+
const { workflow, run, step, latestContext } = input;
|
|
49
|
+
const agent = step.agent?.name;
|
|
50
|
+
if (!agent) {
|
|
51
|
+
return {
|
|
52
|
+
kind: 'invalid',
|
|
53
|
+
reason: `Step "${run.currentStepId}" has no agent profile`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
let agentProfile;
|
|
57
|
+
try {
|
|
58
|
+
agentProfile = loadAgentProfile(agent);
|
|
59
|
+
} catch (error) {
|
|
61
60
|
return {
|
|
62
61
|
kind: 'invalid',
|
|
63
|
-
reason:
|
|
62
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
64
63
|
};
|
|
65
64
|
}
|
|
66
65
|
if (!run.cwd) {
|
|
@@ -160,7 +159,7 @@ export function createDelegationPlan(
|
|
|
160
159
|
const policyDigest = digest({
|
|
161
160
|
version: 1,
|
|
162
161
|
requestId,
|
|
163
|
-
agent
|
|
162
|
+
agent,
|
|
164
163
|
runId: run.runId,
|
|
165
164
|
stepId: run.currentStepId,
|
|
166
165
|
stepDigest: run.currentStepDigest,
|
|
@@ -174,7 +173,7 @@ export function createDelegationPlan(
|
|
|
174
173
|
const policy: ChildStepPolicy = {
|
|
175
174
|
version: 1,
|
|
176
175
|
requestId,
|
|
177
|
-
agent
|
|
176
|
+
agent,
|
|
178
177
|
workflowId: workflow.definition.id,
|
|
179
178
|
runId: run.runId,
|
|
180
179
|
stepId: run.currentStepId,
|
|
@@ -195,22 +194,7 @@ export function createDelegationPlan(
|
|
|
195
194
|
...(step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {}),
|
|
196
195
|
...(step.workspace ? { workspace: structuredClone(step.workspace) } : {}),
|
|
197
196
|
};
|
|
198
|
-
const
|
|
199
|
-
latestContext.sessionManager.getSessionFile(),
|
|
200
|
-
);
|
|
201
|
-
const transcriptTask = [
|
|
202
|
-
buildDelegatedStepTask(workflow, run, ''),
|
|
203
|
-
delegationTranscriptBinding(requestId, policyDigest),
|
|
204
|
-
...(recovery
|
|
205
|
-
? [
|
|
206
|
-
automaticRecoveryTask(
|
|
207
|
-
recovery.failures.map(({ reason }) => reason),
|
|
208
|
-
recovery.attempt,
|
|
209
|
-
MAX_DELEGATION_RECOVERY_ATTEMPTS,
|
|
210
|
-
),
|
|
211
|
-
]
|
|
212
|
-
: []),
|
|
213
|
-
].join('\n\n');
|
|
197
|
+
const task = buildDelegatedStepTask(workflow, run, '');
|
|
214
198
|
const active: ActiveDelegation = {
|
|
215
199
|
requestId,
|
|
216
200
|
runId: run.runId,
|
|
@@ -219,56 +203,18 @@ export function createDelegationPlan(
|
|
|
219
203
|
sessionEpoch: input.sessionEpoch,
|
|
220
204
|
resultDirectory: workspace.resultDirectory,
|
|
221
205
|
policy,
|
|
222
|
-
transcriptTask,
|
|
223
|
-
agent
|
|
224
|
-
...(trustedSessionRoot ? { trustedSessionRoot } : {}),
|
|
225
|
-
broadRecoveryAuthorized: subagent.retryToolFailures,
|
|
226
|
-
recoveryAttemptCount: recovery?.attempt ?? 0,
|
|
227
|
-
recoveryFailures: recovery?.failures ?? [],
|
|
206
|
+
transcriptTask: task,
|
|
207
|
+
agent,
|
|
228
208
|
};
|
|
229
209
|
const request: SubagentDelegationRequest = {
|
|
230
210
|
version: 1,
|
|
231
211
|
requestId,
|
|
232
|
-
agent
|
|
233
|
-
task: `${encodeChildPolicy(policy)}\n\n${
|
|
234
|
-
// Workflow steps are isolation boundaries. Parent/sibling transcripts are
|
|
235
|
-
// replaced by the explicit workflow handoff.
|
|
236
|
-
context: 'fresh',
|
|
212
|
+
agent,
|
|
213
|
+
task: `${encodeChildPolicy(policy)}\n\n${task}`,
|
|
237
214
|
cwd: delegationCwd,
|
|
238
|
-
timeoutMs:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
output: false,
|
|
242
|
-
// TypeBox schemas are structurally JSON objects, but the upstream contract
|
|
243
|
-
// exposes a generic record.
|
|
244
|
-
outputSchema: WORKFLOW_COMPLETION_PARAMETERS as unknown as Record<
|
|
245
|
-
string,
|
|
246
|
-
unknown
|
|
247
|
-
>,
|
|
248
|
-
agentContract: { version: 1 },
|
|
249
|
-
artifacts: subagent.artifacts,
|
|
250
|
-
...(subagent.model ? { model: subagent.model } : {}),
|
|
251
|
-
...(subagent.turnBudget
|
|
252
|
-
? { turnBudget: structuredClone(subagent.turnBudget) }
|
|
253
|
-
: {}),
|
|
254
|
-
...(subagent.toolBudget
|
|
255
|
-
? {
|
|
256
|
-
toolBudget: {
|
|
257
|
-
hard: subagent.toolBudget.hard,
|
|
258
|
-
...(subagent.toolBudget.soft === undefined
|
|
259
|
-
? {}
|
|
260
|
-
: { soft: subagent.toolBudget.soft }),
|
|
261
|
-
...(subagent.toolBudget.block === undefined
|
|
262
|
-
? {}
|
|
263
|
-
: {
|
|
264
|
-
block:
|
|
265
|
-
subagent.toolBudget.block === '*'
|
|
266
|
-
? '*'
|
|
267
|
-
: [...subagent.toolBudget.block],
|
|
268
|
-
}),
|
|
269
|
-
},
|
|
270
|
-
}
|
|
271
|
-
: {}),
|
|
215
|
+
timeoutMs: 900_000,
|
|
216
|
+
...(agentProfile.model ? { model: agentProfile.model } : {}),
|
|
217
|
+
...(agentProfile.thinking ? { thinking: agentProfile.thinking } : {}),
|
|
272
218
|
};
|
|
273
219
|
|
|
274
220
|
return { kind: 'ready', active, request };
|
|
@@ -4,13 +4,10 @@ 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
|
+
import { recordCurrentStepResult } from '../engine/step-trace.ts';
|
|
11
8
|
import type { WorkflowStepResult } from '../runtime/step-result.ts';
|
|
12
9
|
import type { HarnessActionContext as FullHarnessActionContext } from './action-context.ts';
|
|
13
|
-
import type { ActiveDelegation
|
|
10
|
+
import type { ActiveDelegation } from './types.ts';
|
|
14
11
|
import { resolveStepEffects } from './step-effects.ts';
|
|
15
12
|
|
|
16
13
|
type HarnessActionContext = Pick<
|
|
@@ -18,7 +15,6 @@ type HarnessActionContext = Pick<
|
|
|
18
15
|
| 'activeDelegation'
|
|
19
16
|
| 'catalog'
|
|
20
17
|
| 'cleanupDelegation'
|
|
21
|
-
| 'delegationFailures'
|
|
22
18
|
| 'dependencies'
|
|
23
19
|
| 'finishDelegation'
|
|
24
20
|
| 'isSessionActive'
|
|
@@ -27,7 +23,6 @@ type HarnessActionContext = Pick<
|
|
|
27
23
|
| 'pauseForDelegationFailure'
|
|
28
24
|
| 'releaseMainAfterCancellation'
|
|
29
25
|
| 'retainUnconfirmedDelegation'
|
|
30
|
-
| 'retryDelegationAfterFailure'
|
|
31
26
|
| 'run'
|
|
32
27
|
| 'sessionEpoch'
|
|
33
28
|
| 'settleAfterTransition'
|
|
@@ -70,11 +65,22 @@ function handleDelegationUpdate(
|
|
|
70
65
|
): void {
|
|
71
66
|
if (this.activeDelegation !== active) return;
|
|
72
67
|
const progress = [
|
|
68
|
+
update.activity,
|
|
73
69
|
update.currentTool ? `tool ${update.currentTool}` : undefined,
|
|
74
70
|
update.toolCount !== undefined ? `${update.toolCount} calls` : undefined,
|
|
75
71
|
update.tokens !== undefined ? `${update.tokens} tokens` : undefined,
|
|
76
72
|
].filter((part): part is string => part !== undefined);
|
|
77
73
|
active.progress = progress.join(', ') || 'running';
|
|
74
|
+
if (update.detail) {
|
|
75
|
+
const previous = active.activityLog ?? [];
|
|
76
|
+
const replacesPreviousResponse =
|
|
77
|
+
update.detail.startsWith('response: ') &&
|
|
78
|
+
previous.at(-1)?.startsWith('response: ');
|
|
79
|
+
active.activityLog = [
|
|
80
|
+
...(replacesPreviousResponse ? previous.slice(0, -1) : previous),
|
|
81
|
+
update.detail,
|
|
82
|
+
].slice(-8);
|
|
83
|
+
}
|
|
78
84
|
this.updateStatus();
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -128,7 +134,6 @@ async function finishDelegation(
|
|
|
128
134
|
return;
|
|
129
135
|
}
|
|
130
136
|
this.activeDelegation = undefined;
|
|
131
|
-
let terminalFailure: DelegationFailureDetails | undefined;
|
|
132
137
|
let cleanupAttempted = false;
|
|
133
138
|
|
|
134
139
|
try {
|
|
@@ -144,80 +149,15 @@ async function finishDelegation(
|
|
|
144
149
|
return;
|
|
145
150
|
}
|
|
146
151
|
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
|
-
}
|
|
172
152
|
const workflow = this.catalog.workflows.get(this.run.workflowId);
|
|
173
153
|
const step = workflow?.definition.steps[this.run.currentStepId];
|
|
174
154
|
if (!workflow || !step) {
|
|
175
155
|
throw new Error('Active workflow configuration is unavailable');
|
|
176
156
|
}
|
|
177
|
-
if (response.status
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
}
|
|
191
|
-
let recoveredTerminalFailure: DelegationFailureDetails | undefined;
|
|
192
|
-
if (
|
|
193
|
-
response.status !== 'completed' ||
|
|
194
|
-
this.delegationFailures.hasContradictoryCompletion(response)
|
|
195
|
-
) {
|
|
196
|
-
const failure = await this.delegationFailures.describeDelegationFailure(
|
|
197
|
-
active,
|
|
198
|
-
response,
|
|
199
|
-
);
|
|
200
|
-
terminalFailure = failure;
|
|
201
|
-
if (
|
|
202
|
-
response.status !== 'failed' ||
|
|
203
|
-
failure.diagnostic?.completionAfterFailure !== true
|
|
204
|
-
) {
|
|
205
|
-
throw new Error(failure.reason);
|
|
206
|
-
}
|
|
207
|
-
const projectionError = this.delegationFailures.recoveredProjectionError(
|
|
208
|
-
active,
|
|
209
|
-
response,
|
|
210
|
-
failure.diagnostic,
|
|
157
|
+
if (response.status !== 'completed') {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Workflow worker "${active.agent}" ${response.status.replaceAll('_', ' ')}${response.error ? `: ${response.error}` : ''}`,
|
|
211
160
|
);
|
|
212
|
-
if (projectionError) {
|
|
213
|
-
throw new Error(
|
|
214
|
-
this.delegationFailures.rejectedRecoveryReason(
|
|
215
|
-
failure,
|
|
216
|
-
projectionError,
|
|
217
|
-
),
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
recoveredTerminalFailure = failure;
|
|
221
161
|
}
|
|
222
162
|
const requiredSkillWarning =
|
|
223
163
|
step.requires.skills.length > 0
|
|
@@ -233,15 +173,6 @@ async function finishDelegation(
|
|
|
233
173
|
try {
|
|
234
174
|
serializedResult = await this.dependencies.readDelegatedResult(active);
|
|
235
175
|
} catch (error) {
|
|
236
|
-
if (recoveredTerminalFailure) {
|
|
237
|
-
throw new Error(
|
|
238
|
-
this.delegationFailures.rejectedRecoveryReason(
|
|
239
|
-
recoveredTerminalFailure,
|
|
240
|
-
error,
|
|
241
|
-
),
|
|
242
|
-
{ cause: error },
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
176
|
if (hasErrorCode(error, 'ENOENT')) {
|
|
246
177
|
throw new Error(
|
|
247
178
|
`Subagent "${active.agent}" completed without producing the required correlated structured_output result`,
|
|
@@ -251,49 +182,12 @@ async function finishDelegation(
|
|
|
251
182
|
throw error;
|
|
252
183
|
}
|
|
253
184
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (recoveredTerminalFailure) {
|
|
260
|
-
throw new Error(
|
|
261
|
-
this.delegationFailures.rejectedRecoveryReason(
|
|
262
|
-
recoveredTerminalFailure,
|
|
263
|
-
error,
|
|
264
|
-
),
|
|
265
|
-
{ cause: error },
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
throw error;
|
|
269
|
-
}
|
|
270
|
-
if (
|
|
271
|
-
recoveredTerminalFailure?.diagnostic &&
|
|
272
|
-
!this.delegationFailures.completionMatchesResult(
|
|
273
|
-
recoveredTerminalFailure.diagnostic,
|
|
274
|
-
result,
|
|
275
|
-
active.policy,
|
|
276
|
-
)
|
|
277
|
-
) {
|
|
278
|
-
throw new Error(
|
|
279
|
-
this.delegationFailures.rejectedRecoveryReason(
|
|
280
|
-
recoveredTerminalFailure,
|
|
281
|
-
'structured_output transcript value does not match the correlated result',
|
|
282
|
-
),
|
|
283
|
-
);
|
|
284
|
-
}
|
|
185
|
+
const rawResult: unknown = JSON.parse(serializedResult);
|
|
186
|
+
const result: WorkflowStepResult = parseDelegatedStepResult(
|
|
187
|
+
rawResult,
|
|
188
|
+
active.policy,
|
|
189
|
+
);
|
|
285
190
|
const acceptedAt = terminalAt;
|
|
286
|
-
if (recoveredTerminalFailure) {
|
|
287
|
-
const isFalsePositive =
|
|
288
|
-
recoveredTerminalFailure.diagnostic?.correlation ===
|
|
289
|
-
'successful-output-before-completion';
|
|
290
|
-
this.latestContext?.ui.notify(
|
|
291
|
-
isFalsePositive
|
|
292
|
-
? `Accepted "${active.stepId}" because the trusted child transcript proved the terminal tool error was a false positive and produced a matching structured result`
|
|
293
|
-
: `Accepted "${active.stepId}" because the child resolved an earlier tool failure and produced a valid structured result`,
|
|
294
|
-
'warning',
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
191
|
if (step.gate?.submitOutcome === result.outcome) {
|
|
298
192
|
this.run = recordCurrentStepResult(this.run, result, acceptedAt);
|
|
299
193
|
await this.submitGate(
|
|
@@ -335,14 +229,7 @@ async function finishDelegation(
|
|
|
335
229
|
const reason = error instanceof Error ? error.message : String(error);
|
|
336
230
|
cleanupAttempted = true;
|
|
337
231
|
await this.cleanupDelegation(active);
|
|
338
|
-
|
|
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);
|
|
345
|
-
}
|
|
232
|
+
this.pauseForDelegationFailure(reason);
|
|
346
233
|
} finally {
|
|
347
234
|
try {
|
|
348
235
|
if (!cleanupAttempted) await this.cleanupDelegation(active);
|
|
@@ -14,11 +14,6 @@ import {
|
|
|
14
14
|
createSubagentDelegationClient,
|
|
15
15
|
type SubagentDelegationClientController,
|
|
16
16
|
} from '../integrations/subagents/client.ts';
|
|
17
|
-
import {
|
|
18
|
-
auditCompletedDelegationTranscript,
|
|
19
|
-
readDelegationReplayAudit,
|
|
20
|
-
readToolFailureDiagnostic,
|
|
21
|
-
} from '../integrations/subagents/diagnostics.ts';
|
|
22
17
|
import {
|
|
23
18
|
createMainStepRuntime,
|
|
24
19
|
type MainStepRuntimeController,
|
|
@@ -61,9 +56,6 @@ export type WorkflowHarnessDependencies = {
|
|
|
61
56
|
readonly requestPlannotatorReview: typeof requestPlannotatorReview;
|
|
62
57
|
readonly requestPlannotatorReviewStatus: typeof requestPlannotatorReviewStatus;
|
|
63
58
|
readonly requestPromptGateReview: typeof requestPromptGateReview;
|
|
64
|
-
readonly readDelegationReplayAudit: typeof readDelegationReplayAudit;
|
|
65
|
-
readonly readToolFailureDiagnostic: typeof readToolFailureDiagnostic;
|
|
66
|
-
readonly auditCompletedDelegationTranscript: typeof auditCompletedDelegationTranscript;
|
|
67
59
|
readonly showWorkflowStatus: typeof showWorkflowStatus;
|
|
68
60
|
readonly createSubagentClient: (
|
|
69
61
|
pi: ExtensionAPI,
|
|
@@ -150,11 +142,8 @@ const DEFAULT_DEPENDENCIES: WorkflowHarnessDependencies = {
|
|
|
150
142
|
requestPlannotatorReview,
|
|
151
143
|
requestPlannotatorReviewStatus,
|
|
152
144
|
requestPromptGateReview,
|
|
153
|
-
readDelegationReplayAudit,
|
|
154
|
-
readToolFailureDiagnostic,
|
|
155
|
-
auditCompletedDelegationTranscript,
|
|
156
145
|
showWorkflowStatus,
|
|
157
|
-
createSubagentClient: (
|
|
146
|
+
createSubagentClient: () => createSubagentDelegationClient(),
|
|
158
147
|
createMainStepRuntime: (pi) => createMainStepRuntime({ pi }),
|
|
159
148
|
createMutationQueue: createSerialTaskQueue,
|
|
160
149
|
flushUnwrittenSession,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { LoadedWorkflow } from '../config/types.ts';
|
|
2
2
|
import type { WorkflowRun } from '../engine/state.ts';
|
|
3
|
+
import { validateArtifactContract } from './artifact-contract.ts';
|
|
3
4
|
import {
|
|
4
5
|
attachGateReviewId,
|
|
6
|
+
advanceRun,
|
|
5
7
|
beginGate,
|
|
6
8
|
failGate,
|
|
7
9
|
failRun,
|
|
@@ -19,6 +21,7 @@ type HarnessActionContext = Pick<
|
|
|
19
21
|
| 'pi'
|
|
20
22
|
| 'restoreBaselineTools'
|
|
21
23
|
| 'run'
|
|
24
|
+
| 'settleAfterTransition'
|
|
22
25
|
| 'sessionEpoch'
|
|
23
26
|
| 'updateStatus'
|
|
24
27
|
>;
|
|
@@ -63,6 +66,31 @@ async function submitGate(
|
|
|
63
66
|
this.dependencies.createRequestId();
|
|
64
67
|
const step = workflow.definition.steps[originalRun.currentStepId];
|
|
65
68
|
if (!step?.gate) throw new Error('Current step has no gate');
|
|
69
|
+
const contractError = validateArtifactContract(
|
|
70
|
+
artifact,
|
|
71
|
+
step.gate.artifactContract,
|
|
72
|
+
);
|
|
73
|
+
if (contractError) {
|
|
74
|
+
if (step.gate.artifactContract?.onValidationFailure !== 'retry') {
|
|
75
|
+
throw new Error(contractError);
|
|
76
|
+
}
|
|
77
|
+
const retrySummary = `Artifact contract failed: ${contractError}`;
|
|
78
|
+
this.run = advanceRun(
|
|
79
|
+
workflow,
|
|
80
|
+
originalRun,
|
|
81
|
+
'retry',
|
|
82
|
+
retrySummary,
|
|
83
|
+
this.dependencies.now(),
|
|
84
|
+
);
|
|
85
|
+
this.persist();
|
|
86
|
+
this.updateStatus();
|
|
87
|
+
this.settleAfterTransition(workflow, {
|
|
88
|
+
stepId: originalRun.currentStepId,
|
|
89
|
+
outcome: 'retry',
|
|
90
|
+
summary: retrySummary,
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
66
94
|
|
|
67
95
|
this.run = beginGate(
|
|
68
96
|
workflow,
|
|
@@ -63,6 +63,7 @@ function workflowStatusSnapshot(
|
|
|
63
63
|
agent: this.activeDelegation.agent,
|
|
64
64
|
requestId: this.activeDelegation.requestId,
|
|
65
65
|
progress: this.activeDelegation.progress ?? 'starting',
|
|
66
|
+
activityLog: this.activeDelegation.activityLog ?? [],
|
|
66
67
|
};
|
|
67
68
|
} else if (this.mainSteps.activeStepId) {
|
|
68
69
|
execution = { kind: 'main' };
|
|
@@ -77,6 +78,25 @@ function workflowStatusSnapshot(
|
|
|
77
78
|
|
|
78
79
|
function updateStatus(this: StatusContext): void {
|
|
79
80
|
refreshStatusWhileRunning.call(this);
|
|
81
|
+
const run = this.run;
|
|
82
|
+
this.pi.events.emit('pi-workflows:state', {
|
|
83
|
+
state:
|
|
84
|
+
run?.status === 'running'
|
|
85
|
+
? 'working'
|
|
86
|
+
: run?.status === 'awaiting-gate' || run?.status === 'paused'
|
|
87
|
+
? 'blocked'
|
|
88
|
+
: run?.status === 'completed'
|
|
89
|
+
? 'completed'
|
|
90
|
+
: 'interrupted',
|
|
91
|
+
workflowId: run?.workflowId,
|
|
92
|
+
stepId: run?.currentStepId,
|
|
93
|
+
message:
|
|
94
|
+
run?.status === 'completed'
|
|
95
|
+
? `Workflow "${run.workflowId}" completed`
|
|
96
|
+
: run?.status === 'paused'
|
|
97
|
+
? run.pauseReason
|
|
98
|
+
: undefined,
|
|
99
|
+
});
|
|
80
100
|
if (!this.latestContext) return;
|
|
81
101
|
if (this.legacyProgressWidgetContext !== this.latestContext) {
|
|
82
102
|
this.latestContext.ui.setWidget(LEGACY_PROGRESS_WIDGET_KEY, undefined);
|