@wichayutdew/pi-workflows 2.5.1 → 2.6.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 +4 -1
- package/dist/index.js +1133 -2409
- package/examples/starter-kit/agents/planner.md +7 -0
- package/examples/starter-kit/agents/reviewer.md +6 -0
- package/examples/starter-kit/agents/scout.md +6 -0
- package/examples/starter-kit/agents/worker.md +7 -0
- package/examples/starter-kit/agents/workspace-preparer.md +6 -0
- package/examples/starter-kit/investigate.workflow.yaml +114 -0
- package/examples/starter-kit/mr-comment.workflow.yaml +114 -54
- package/examples/starter-kit/mr-review.workflow.yaml +77 -56
- package/examples/starter-kit/settings.yaml +3 -0
- package/examples/starter-kit/steps/investigate/investigate.md +77 -0
- package/examples/starter-kit/steps/investigate/retrieve.md +63 -0
- package/examples/starter-kit/steps/investigate/validate.md +46 -0
- package/examples/starter-kit/steps/mr-comment/checkout-source.md +67 -0
- package/examples/starter-kit/steps/mr-comment/fetch.md +34 -26
- package/examples/starter-kit/steps/mr-comment/implement.md +34 -27
- package/examples/starter-kit/steps/mr-comment/plan.md +54 -40
- package/examples/starter-kit/steps/mr-comment/publish.md +28 -27
- package/examples/starter-kit/steps/mr-comment/verify.md +42 -35
- package/examples/starter-kit/steps/mr-review/fetch.md +52 -22
- package/examples/starter-kit/steps/mr-review/publish-approved.md +49 -0
- package/examples/starter-kit/steps/mr-review/review-for-approval.md +142 -0
- package/examples/starter-kit/steps/mr-review/verify-published.md +37 -0
- package/examples/starter-kit/steps/shared/prepare-workspace.md +91 -73
- package/examples/starter-kit/steps/shared/publish-remote.md +45 -0
- package/examples/starter-kit/steps/ticket/implement.md +59 -25
- package/examples/starter-kit/steps/ticket/plan.md +145 -72
- package/examples/starter-kit/steps/ticket/verify.md +80 -45
- package/examples/starter-kit/steps/work/implement.md +60 -24
- package/examples/starter-kit/steps/work/plan.md +129 -57
- package/examples/starter-kit/steps/work/verify.md +58 -22
- package/examples/starter-kit/ticket.workflow.yaml +75 -36
- package/examples/starter-kit/work.workflow.yaml +63 -33
- package/package.json +3 -16
- package/schemas/workflow.schema.json +2 -15
- package/src/agents/profile.ts +98 -0
- package/src/config/ceiling.ts +0 -82
- package/src/config/types.ts +6 -53
- package/src/config/validation/settings.ts +2 -14
- package/src/config/validation/step.ts +12 -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/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/status-actions.ts +1 -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/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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wichayutdew/pi-workflows",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "A declarative, pauseable workflow harness for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
23
|
"src",
|
|
24
|
-
"agents",
|
|
25
24
|
"examples",
|
|
26
25
|
"schemas",
|
|
27
26
|
"README.md",
|
|
@@ -30,12 +29,7 @@
|
|
|
30
29
|
"pi": {
|
|
31
30
|
"extensions": [
|
|
32
31
|
"./src/index.ts"
|
|
33
|
-
]
|
|
34
|
-
"subagents": {
|
|
35
|
-
"agents": [
|
|
36
|
-
"./agents"
|
|
37
|
-
]
|
|
38
|
-
}
|
|
32
|
+
]
|
|
39
33
|
},
|
|
40
34
|
"scripts": {
|
|
41
35
|
"build": "bun build ./src/index.ts --outdir=dist --target=node --format=esm --packages=external",
|
|
@@ -44,9 +38,9 @@
|
|
|
44
38
|
"lint": "bun --bun eslint .",
|
|
45
39
|
"lint:fix": "bun --bun eslint . --fix",
|
|
46
40
|
"test": "bun test --no-orphans --reporter=dots",
|
|
41
|
+
"test:e2e": "bun --config=bunfig.e2e.toml test --no-orphans ./test/e2e/direct-worker-runtime.test.ts --reporter=dots",
|
|
47
42
|
"test:coverage": "bun --config=bunfig.coverage.toml test --no-orphans --reporter=dots && bun run coverage:check",
|
|
48
43
|
"coverage:check": "bun .github/scripts/check-coverage.mjs coverage/full/lcov.info",
|
|
49
|
-
"test:e2e": "bun --config=bunfig.e2e.toml test --no-orphans test/e2e/workflow-runtime.test.ts --reporter=dots",
|
|
50
44
|
"typecheck": "tsc --noEmit",
|
|
51
45
|
"check": "bun run lint && bun run format:check && bun run typecheck && bun run test:coverage && bun run test:e2e && bun run build"
|
|
52
46
|
},
|
|
@@ -56,14 +50,8 @@
|
|
|
56
50
|
"peerDependencies": {
|
|
57
51
|
"@earendil-works/pi-coding-agent": "*",
|
|
58
52
|
"@earendil-works/pi-tui": "*",
|
|
59
|
-
"pi-subagents": ">=0.36.0",
|
|
60
53
|
"typebox": "*"
|
|
61
54
|
},
|
|
62
|
-
"peerDependenciesMeta": {
|
|
63
|
-
"pi-subagents": {
|
|
64
|
-
"optional": true
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
55
|
"devDependencies": {
|
|
68
56
|
"@earendil-works/pi-coding-agent": "^0.81.1",
|
|
69
57
|
"@earendil-works/pi-tui": "^0.81.1",
|
|
@@ -72,7 +60,6 @@
|
|
|
72
60
|
"@types/node": "^24.0.0",
|
|
73
61
|
"eslint": "^10.7.0",
|
|
74
62
|
"globals": "^17.7.0",
|
|
75
|
-
"pi-subagents": "0.36.0",
|
|
76
63
|
"prettier": "^3.9.5",
|
|
77
64
|
"typebox": "^1.1.38",
|
|
78
65
|
"typescript": "^5.9.3",
|
|
@@ -597,19 +597,6 @@
|
|
|
597
597
|
"type": "object",
|
|
598
598
|
"additionalProperties": false,
|
|
599
599
|
"required": ["prompt", "transitions"],
|
|
600
|
-
"allOf": [
|
|
601
|
-
{
|
|
602
|
-
"if": {
|
|
603
|
-
"required": ["workspace"]
|
|
604
|
-
},
|
|
605
|
-
"then": {
|
|
606
|
-
"required": ["subagent"],
|
|
607
|
-
"not": {
|
|
608
|
-
"required": ["gate"]
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
],
|
|
613
600
|
"properties": {
|
|
614
601
|
"title": {
|
|
615
602
|
"type": "string",
|
|
@@ -618,8 +605,8 @@
|
|
|
618
605
|
"prompt": {
|
|
619
606
|
"$ref": "#/$defs/prompt"
|
|
620
607
|
},
|
|
621
|
-
"
|
|
622
|
-
"$ref": "#/$defs/
|
|
608
|
+
"agent": {
|
|
609
|
+
"$ref": "#/$defs/subagentRuntimeName"
|
|
623
610
|
},
|
|
624
611
|
"permissions": {
|
|
625
612
|
"$ref": "#/$defs/permissions"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parse } from 'yaml';
|
|
5
|
+
import { defaultUserWorkflowDirectory } from '../config/load.ts';
|
|
6
|
+
|
|
7
|
+
export const THINKING_LEVELS = [
|
|
8
|
+
'off',
|
|
9
|
+
'minimal',
|
|
10
|
+
'low',
|
|
11
|
+
'medium',
|
|
12
|
+
'high',
|
|
13
|
+
'xhigh',
|
|
14
|
+
'max',
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
18
|
+
|
|
19
|
+
export type AgentProfile = {
|
|
20
|
+
readonly prompt: string;
|
|
21
|
+
readonly model?: string;
|
|
22
|
+
readonly thinking?: ThinkingLevel;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
26
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
27
|
+
|
|
28
|
+
/** Parses the optional YAML frontmatter and role prompt in one agent profile. */
|
|
29
|
+
export function parseAgentProfile(source: string, name: string): AgentProfile {
|
|
30
|
+
if (!source.startsWith('---\n')) return { prompt: source.trim() };
|
|
31
|
+
const end = source.indexOf('\n---\n', 4);
|
|
32
|
+
if (end === -1) {
|
|
33
|
+
throw new Error(`workflow agent profile is invalid: ${name}`);
|
|
34
|
+
}
|
|
35
|
+
const metadata: unknown = parse(source.slice(4, end));
|
|
36
|
+
if (!isRecord(metadata)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`workflow agent profile metadata must be an object: ${name}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const unknownKey = Object.keys(metadata).find(
|
|
42
|
+
(key) => key !== 'model' && key !== 'thinking',
|
|
43
|
+
);
|
|
44
|
+
if (unknownKey) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`workflow agent profile has unknown metadata "${unknownKey}": ${name}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const model = metadata.model;
|
|
50
|
+
if (model !== undefined && (typeof model !== 'string' || !model.trim())) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`workflow agent profile model must be a non-empty string: ${name}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const thinking = metadata.thinking;
|
|
56
|
+
if (
|
|
57
|
+
thinking !== undefined &&
|
|
58
|
+
(typeof thinking !== 'string' ||
|
|
59
|
+
!THINKING_LEVELS.includes(thinking as ThinkingLevel))
|
|
60
|
+
) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`workflow agent profile thinking must be one of ${THINKING_LEVELS.join(', ')}: ${name}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const prompt = source.slice(end + 5).trim();
|
|
66
|
+
if (!prompt)
|
|
67
|
+
throw new Error(`workflow agent profile prompt is empty: ${name}`);
|
|
68
|
+
return {
|
|
69
|
+
prompt,
|
|
70
|
+
...(typeof model === 'string' ? { model: model.trim() } : {}),
|
|
71
|
+
...(typeof thinking === 'string'
|
|
72
|
+
? { thinking: thinking as ThinkingLevel }
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Loads a user-owned agent profile, with a bundled prompt-only fallback. */
|
|
78
|
+
export function loadAgentProfile(
|
|
79
|
+
name: string,
|
|
80
|
+
userDirectory = defaultUserWorkflowDirectory(),
|
|
81
|
+
): AgentProfile {
|
|
82
|
+
const userPath = join(userDirectory, 'agents', `${name}.md`);
|
|
83
|
+
const bundledUrl = new URL(
|
|
84
|
+
`../../examples/starter-kit/agents/${name}.md`,
|
|
85
|
+
import.meta.url,
|
|
86
|
+
);
|
|
87
|
+
const path = existsSync(userPath) ? userPath : fileURLToPath(bundledUrl);
|
|
88
|
+
try {
|
|
89
|
+
return parseAgentProfile(readFileSync(path, 'utf8'), name);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof Error && error.message.startsWith('workflow agent')) {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`workflow agent profile is unavailable: ${name}`, {
|
|
95
|
+
cause: error,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/config/ceiling.ts
CHANGED
|
@@ -2,8 +2,6 @@ import type {
|
|
|
2
2
|
BashPermission,
|
|
3
3
|
BashRule,
|
|
4
4
|
PermissionCeiling,
|
|
5
|
-
StepSubagent,
|
|
6
|
-
SubagentPermissionCeiling,
|
|
7
5
|
WorkflowDefinition,
|
|
8
6
|
} from './types.ts';
|
|
9
7
|
|
|
@@ -34,81 +32,6 @@ function bashWithinCeiling(
|
|
|
34
32
|
return requested.allow.every((rule) => allowedRules.has(ruleKey(rule)));
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
function turnBudgetErrors(
|
|
38
|
-
subagent: StepSubagent,
|
|
39
|
-
ceiling: SubagentPermissionCeiling,
|
|
40
|
-
path: string,
|
|
41
|
-
): Array<string> {
|
|
42
|
-
if (!subagent.turnBudget) {
|
|
43
|
-
return [`${path}.turnBudget: required for a project workflow`];
|
|
44
|
-
}
|
|
45
|
-
return [
|
|
46
|
-
...(subagent.turnBudget.maxTurns > ceiling.maxTurns
|
|
47
|
-
? [`${path}.turnBudget.maxTurns: exceeds the user permission ceiling`]
|
|
48
|
-
: []),
|
|
49
|
-
...((subagent.turnBudget.graceTurns ?? 0) > ceiling.maxGraceTurns
|
|
50
|
-
? [`${path}.turnBudget.graceTurns: exceeds the user permission ceiling`]
|
|
51
|
-
: []),
|
|
52
|
-
];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function toolBudgetErrors(
|
|
56
|
-
subagent: StepSubagent,
|
|
57
|
-
ceiling: SubagentPermissionCeiling,
|
|
58
|
-
path: string,
|
|
59
|
-
): Array<string> {
|
|
60
|
-
if (!subagent.toolBudget) {
|
|
61
|
-
return [`${path}.toolBudget: required for a project workflow`];
|
|
62
|
-
}
|
|
63
|
-
return [
|
|
64
|
-
...(subagent.toolBudget.hard > ceiling.maxToolCalls
|
|
65
|
-
? [`${path}.toolBudget.hard: exceeds the user permission ceiling`]
|
|
66
|
-
: []),
|
|
67
|
-
...(subagent.toolBudget.block !== '*'
|
|
68
|
-
? [`${path}.toolBudget.block: must be "*" for a project workflow`]
|
|
69
|
-
: []),
|
|
70
|
-
];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function subagentErrors(
|
|
74
|
-
subagent: StepSubagent | undefined,
|
|
75
|
-
ceiling: SubagentPermissionCeiling | undefined,
|
|
76
|
-
path: string,
|
|
77
|
-
): Array<string> {
|
|
78
|
-
if (!subagent) return [];
|
|
79
|
-
if (!ceiling) {
|
|
80
|
-
return [`${path}: subagent execution exceeds the user permission ceiling`];
|
|
81
|
-
}
|
|
82
|
-
return [
|
|
83
|
-
...(!ceiling.agents.includes(subagent.agent)
|
|
84
|
-
? [
|
|
85
|
-
`${path}.agent: "${subagent.agent}" exceeds the user permission ceiling`,
|
|
86
|
-
]
|
|
87
|
-
: []),
|
|
88
|
-
...(!ceiling.contexts.includes(subagent.context)
|
|
89
|
-
? [
|
|
90
|
-
`${path}.context: "${subagent.context}" exceeds the user permission ceiling`,
|
|
91
|
-
]
|
|
92
|
-
: []),
|
|
93
|
-
...(subagent.model && !ceiling.models.includes(subagent.model)
|
|
94
|
-
? [
|
|
95
|
-
`${path}.model: "${subagent.model}" exceeds the user permission ceiling`,
|
|
96
|
-
]
|
|
97
|
-
: []),
|
|
98
|
-
...(subagent.timeoutMs > ceiling.maxTimeoutMs
|
|
99
|
-
? [`${path}.timeoutMs: exceeds the user permission ceiling`]
|
|
100
|
-
: []),
|
|
101
|
-
...(subagent.artifacts && !ceiling.artifacts
|
|
102
|
-
? [`${path}.artifacts: exceeds the user permission ceiling`]
|
|
103
|
-
: []),
|
|
104
|
-
...(subagent.retryToolFailures && !ceiling.retryToolFailures
|
|
105
|
-
? [`${path}.retryToolFailures: exceeds the user permission ceiling`]
|
|
106
|
-
: []),
|
|
107
|
-
...turnBudgetErrors(subagent, ceiling, path),
|
|
108
|
-
...toolBudgetErrors(subagent, ceiling, path),
|
|
109
|
-
];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
35
|
/** Return every project-workflow permission that exceeds the user ceiling. */
|
|
113
36
|
export function checkWorkflowAgainstCeiling(
|
|
114
37
|
workflow: WorkflowDefinition,
|
|
@@ -149,11 +72,6 @@ export function checkWorkflowAgainstCeiling(
|
|
|
149
72
|
...(!bashWithinCeiling(step.permissions.bash, ceiling.bash)
|
|
150
73
|
? [`${path}.bash: exceeds the user permission ceiling`]
|
|
151
74
|
: []),
|
|
152
|
-
...subagentErrors(
|
|
153
|
-
step.subagent,
|
|
154
|
-
ceiling.subagent,
|
|
155
|
-
`workflow.steps.${stepId}.subagent`,
|
|
156
|
-
),
|
|
157
75
|
];
|
|
158
76
|
});
|
|
159
77
|
}
|
package/src/config/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { KeyId } from '@earendil-works/pi-tui';
|
|
|
2
2
|
|
|
3
3
|
export const WORKFLOW_SCHEMA_VERSION = 1 as const;
|
|
4
4
|
export const DEFAULT_STATUS_SHORTCUT = 'ctrl+alt+w' as const satisfies KeyId;
|
|
5
|
-
export const
|
|
5
|
+
export const AGENT_PROFILE_NAME_PATTERN =
|
|
6
6
|
/^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/;
|
|
7
7
|
export const MAX_WORKSPACE_PATH_CHARS = 4_096;
|
|
8
8
|
export const MAX_WORKSPACE_ALLOWED_ROOTS = 32;
|
|
@@ -41,35 +41,9 @@ export type StepRequirements = {
|
|
|
41
41
|
readonly skills: ReadonlyArray<string>;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
export type
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
readonly maxTurns: number;
|
|
48
|
-
readonly graceTurns?: number;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export type SubagentToolBudget = {
|
|
52
|
-
readonly hard: number;
|
|
53
|
-
readonly soft?: number;
|
|
54
|
-
readonly block?: ReadonlyArray<string> | '*';
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type StepSubagent = {
|
|
58
|
-
/** Pi Subagents agent profile launched for this isolated step. */
|
|
59
|
-
readonly agent: string;
|
|
60
|
-
/** Workflow steps always use a fresh context. */
|
|
61
|
-
readonly context: SubagentContext;
|
|
62
|
-
readonly model?: string;
|
|
63
|
-
readonly timeoutMs: number;
|
|
64
|
-
readonly turnBudget?: SubagentTurnBudget;
|
|
65
|
-
readonly toolBudget?: SubagentToolBudget;
|
|
66
|
-
readonly artifacts: boolean;
|
|
67
|
-
/**
|
|
68
|
-
* Authorizes bounded automatic recovery when Bash can execute commands.
|
|
69
|
-
* Every failed attempt must still have a complete, mutation-safe replay
|
|
70
|
-
* audit.
|
|
71
|
-
*/
|
|
72
|
-
readonly retryToolFailures: boolean;
|
|
44
|
+
export type StepAgent = {
|
|
45
|
+
/** Workflow-owned role prompt applied to this main-agent step. */
|
|
46
|
+
readonly name: string;
|
|
73
47
|
};
|
|
74
48
|
|
|
75
49
|
export type PromptSpec =
|
|
@@ -102,8 +76,8 @@ export type StepWorkspaceBinding = {
|
|
|
102
76
|
export type WorkflowStep = {
|
|
103
77
|
readonly title: string;
|
|
104
78
|
readonly prompt: PromptSpec;
|
|
105
|
-
/**
|
|
106
|
-
readonly
|
|
79
|
+
/** Optional workflow-owned role prompt for this main-agent step. */
|
|
80
|
+
readonly agent?: StepAgent;
|
|
107
81
|
readonly permissions: StepPermissions;
|
|
108
82
|
readonly requires: StepRequirements;
|
|
109
83
|
readonly transitions: Readonly<Record<string, StepTarget>>;
|
|
@@ -142,19 +116,6 @@ export type PermissionCeiling = {
|
|
|
142
116
|
readonly extensions: ReadonlyArray<string>;
|
|
143
117
|
readonly skills: ReadonlyArray<string>;
|
|
144
118
|
readonly bash: BashPermission;
|
|
145
|
-
readonly subagent?: SubagentPermissionCeiling;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
export type SubagentPermissionCeiling = {
|
|
149
|
-
readonly agents: ReadonlyArray<string>;
|
|
150
|
-
readonly contexts: ReadonlyArray<SubagentContext>;
|
|
151
|
-
readonly models: ReadonlyArray<string>;
|
|
152
|
-
readonly maxTimeoutMs: number;
|
|
153
|
-
readonly maxTurns: number;
|
|
154
|
-
readonly maxGraceTurns: number;
|
|
155
|
-
readonly maxToolCalls: number;
|
|
156
|
-
readonly artifacts: boolean;
|
|
157
|
-
readonly retryToolFailures: boolean;
|
|
158
119
|
};
|
|
159
120
|
|
|
160
121
|
export type WorkflowSettings = {
|
|
@@ -192,14 +153,6 @@ export const EMPTY_REQUIREMENTS = {
|
|
|
192
153
|
skills: [],
|
|
193
154
|
} as const satisfies StepRequirements;
|
|
194
155
|
|
|
195
|
-
export const DEFAULT_STEP_SUBAGENT = {
|
|
196
|
-
agent: 'pi-workflows.step',
|
|
197
|
-
context: 'fresh',
|
|
198
|
-
timeoutMs: 900_000,
|
|
199
|
-
artifacts: false,
|
|
200
|
-
retryToolFailures: false,
|
|
201
|
-
} as const satisfies StepSubagent;
|
|
202
|
-
|
|
203
156
|
export const DEFAULT_SETTINGS = {
|
|
204
157
|
version: WORKFLOW_SCHEMA_VERSION,
|
|
205
158
|
allowProjectWorkflows: false,
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
type ValidationResult,
|
|
13
13
|
} from './shared.ts';
|
|
14
14
|
import { readStatusShortcut } from './shortcut.ts';
|
|
15
|
-
import { parseSubagentPermissionCeiling } from './subagent.ts';
|
|
16
15
|
|
|
17
16
|
function parsePermissionCeiling(
|
|
18
17
|
value: unknown,
|
|
@@ -26,7 +25,7 @@ function parsePermissionCeiling(
|
|
|
26
25
|
}
|
|
27
26
|
rejectUnknownKeys(
|
|
28
27
|
value,
|
|
29
|
-
['tools', 'mcp', 'extensions', 'skills', 'bash'
|
|
28
|
+
['tools', 'mcp', 'extensions', 'skills', 'bash'],
|
|
30
29
|
path,
|
|
31
30
|
errors,
|
|
32
31
|
);
|
|
@@ -43,18 +42,7 @@ function parsePermissionCeiling(
|
|
|
43
42
|
path,
|
|
44
43
|
errors,
|
|
45
44
|
);
|
|
46
|
-
|
|
47
|
-
value.subagent === undefined
|
|
48
|
-
? undefined
|
|
49
|
-
: parseSubagentPermissionCeiling(
|
|
50
|
-
value.subagent,
|
|
51
|
-
`${path}.subagent`,
|
|
52
|
-
errors,
|
|
53
|
-
);
|
|
54
|
-
return {
|
|
55
|
-
...permissions,
|
|
56
|
-
...(subagent ? { subagent } : {}),
|
|
57
|
-
};
|
|
45
|
+
return permissions;
|
|
58
46
|
}
|
|
59
47
|
|
|
60
48
|
/** Validate and normalize untrusted user workflow settings. */
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
rejectUnknownKeys,
|
|
18
18
|
type ValidationErrors,
|
|
19
19
|
} from './shared.ts';
|
|
20
|
-
import {
|
|
20
|
+
import { AGENT_PROFILE_NAME_PATTERN, type StepAgent } from '../types.ts';
|
|
21
21
|
|
|
22
22
|
function parsePrompt(
|
|
23
23
|
value: unknown,
|
|
@@ -248,7 +248,7 @@ export function parseWorkflowStep(
|
|
|
248
248
|
[
|
|
249
249
|
'title',
|
|
250
250
|
'prompt',
|
|
251
|
-
'
|
|
251
|
+
'agent',
|
|
252
252
|
'permissions',
|
|
253
253
|
'requires',
|
|
254
254
|
'transitions',
|
|
@@ -264,11 +264,15 @@ export function parseWorkflowStep(
|
|
|
264
264
|
? stepId
|
|
265
265
|
: readString(value.title, `${path}.title`, errors);
|
|
266
266
|
const prompt = parsePrompt(value.prompt, `${path}.prompt`, errors);
|
|
267
|
-
const
|
|
268
|
-
value.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
const agentName =
|
|
268
|
+
value.agent === undefined
|
|
269
|
+
? undefined
|
|
270
|
+
: readString(value.agent, `${path}.agent`, errors, {
|
|
271
|
+
pattern: AGENT_PROFILE_NAME_PATTERN,
|
|
272
|
+
});
|
|
273
|
+
const agent: StepAgent | undefined = agentName
|
|
274
|
+
? { name: agentName }
|
|
275
|
+
: undefined;
|
|
272
276
|
const permissions = parsePermissions(
|
|
273
277
|
value.permissions,
|
|
274
278
|
`${path}.permissions`,
|
|
@@ -323,7 +327,7 @@ export function parseWorkflowStep(
|
|
|
323
327
|
return {
|
|
324
328
|
title,
|
|
325
329
|
prompt,
|
|
326
|
-
...(
|
|
330
|
+
...(agent ? { agent } : {}),
|
|
327
331
|
permissions,
|
|
328
332
|
requires,
|
|
329
333
|
transitions,
|
|
@@ -91,9 +91,6 @@ function validateWorkspaceGraph(
|
|
|
91
91
|
`${workspacePath}: only one workspace-binding step is allowed; "${firstBinderId}" also configures workspace binding`,
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
|
-
if (!step.subagent) {
|
|
95
|
-
errors.push(`${workspacePath}: workspace binding requires a subagent`);
|
|
96
|
-
}
|
|
97
94
|
if (step.gate) {
|
|
98
95
|
errors.push(
|
|
99
96
|
`${workspacePath}: workspace binding is not allowed on a gated step`,
|
|
@@ -110,14 +107,13 @@ function validateWorkspaceGraph(
|
|
|
110
107
|
}
|
|
111
108
|
return target && Object.hasOwn(steps, target) ? [target] : [];
|
|
112
109
|
});
|
|
113
|
-
validateWorkspaceDescendants(steps, downstream
|
|
110
|
+
validateWorkspaceDescendants(steps, downstream);
|
|
114
111
|
});
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
function validateWorkspaceDescendants(
|
|
118
115
|
steps: Readonly<Record<string, WorkflowStep>>,
|
|
119
116
|
initialStepIds: ReadonlyArray<string>,
|
|
120
|
-
errors: ValidationErrors,
|
|
121
117
|
): void {
|
|
122
118
|
const pending = [...initialStepIds];
|
|
123
119
|
const visited = new Set<string>();
|
|
@@ -127,11 +123,6 @@ function validateWorkspaceDescendants(
|
|
|
127
123
|
visited.add(stepId);
|
|
128
124
|
const step = steps[stepId];
|
|
129
125
|
if (!step) continue;
|
|
130
|
-
if (!step.subagent) {
|
|
131
|
-
errors.push(
|
|
132
|
-
`workflow.steps.${stepId}.subagent: every nonterminal step reachable after workspace binding must use a subagent`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
126
|
Object.values(step.transitions).forEach((target) => {
|
|
136
127
|
if (
|
|
137
128
|
target !== '$done' &&
|
|
@@ -183,9 +183,6 @@ export function validateRunWorkflowSemantics(
|
|
|
183
183
|
if (sameWorkflowDigest && currentStepChanged) {
|
|
184
184
|
return `current step "${run.currentStepId}" does not match the active workflow digest`;
|
|
185
185
|
}
|
|
186
|
-
if (boundWorkspaceCwd && !currentStep.subagent) {
|
|
187
|
-
return `bound workflow current step "${run.currentStepId}" must use a subagent`;
|
|
188
|
-
}
|
|
189
186
|
if (currentStepChanged) return undefined;
|
|
190
187
|
return validatePendingGate(run, currentStep);
|
|
191
188
|
}
|
|
@@ -46,7 +46,7 @@ export type SubagentTranscriptReference = {
|
|
|
46
46
|
readonly trustedRoot: string;
|
|
47
47
|
/** Exact child session returned by the delegation protocol. */
|
|
48
48
|
readonly sessionFile: string;
|
|
49
|
-
/**
|
|
49
|
+
/** Legacy child-run identity used to confine the session path. */
|
|
50
50
|
readonly runId: string;
|
|
51
51
|
readonly childIndex: number;
|
|
52
52
|
};
|
|
@@ -20,14 +20,11 @@ import type { MainStepRuntimeController } from '../runtime/main-step-runtime.ts'
|
|
|
20
20
|
import type { SerialTaskQueueController } from '../runtime/serial-task-queue.ts';
|
|
21
21
|
import type { WorkflowStepResult } from '../runtime/step-result.ts';
|
|
22
22
|
import type { WorkflowStatusSnapshot } from '../workflow-status.ts';
|
|
23
|
-
import type { DelegationFailureActions } from './delegation-failure.ts';
|
|
24
23
|
import type { WorkflowHarnessDependencies } from './dependencies.ts';
|
|
25
24
|
import type { SettledStepReport } from './step-reporting.ts';
|
|
26
25
|
import type {
|
|
27
26
|
ActiveDelegation,
|
|
28
27
|
ActivePromptReview,
|
|
29
|
-
DelegationRecovery,
|
|
30
|
-
DelegationFailureDetails,
|
|
31
28
|
MainStepIdentity,
|
|
32
29
|
WorkflowStartContext,
|
|
33
30
|
} from './types.ts';
|
|
@@ -41,7 +38,6 @@ import type {
|
|
|
41
38
|
export type HarnessActionContext = {
|
|
42
39
|
pi: ExtensionAPI;
|
|
43
40
|
dependencies: WorkflowHarnessDependencies;
|
|
44
|
-
delegationFailures: DelegationFailureActions;
|
|
45
41
|
subagents: SubagentDelegationClientController;
|
|
46
42
|
mainSteps: MainStepRuntimeController;
|
|
47
43
|
catalog: WorkflowCatalog;
|
|
@@ -98,10 +94,7 @@ export type HarnessActionContext = {
|
|
|
98
94
|
registerLifecycle: () => void;
|
|
99
95
|
registerPolicy: () => void;
|
|
100
96
|
registerMultilineCommandInput: () => void;
|
|
101
|
-
launchCurrentStep: (
|
|
102
|
-
workflow: LoadedWorkflow,
|
|
103
|
-
recovery?: DelegationRecovery,
|
|
104
|
-
) => void;
|
|
97
|
+
launchCurrentStep: (workflow: LoadedWorkflow) => void;
|
|
105
98
|
launchMainStep: (
|
|
106
99
|
workflow: LoadedWorkflow,
|
|
107
100
|
run: WorkflowRun,
|
|
@@ -142,11 +135,6 @@ export type HarnessActionContext = {
|
|
|
142
135
|
) => Promise<void>;
|
|
143
136
|
cancelActiveDelegation: (reason: string) => Promise<boolean>;
|
|
144
137
|
cleanupDelegation: (active: ActiveDelegation) => Promise<void>;
|
|
145
|
-
retryDelegationAfterFailure: (
|
|
146
|
-
active: ActiveDelegation,
|
|
147
|
-
failure: DelegationFailureDetails | undefined,
|
|
148
|
-
reason: string,
|
|
149
|
-
) => boolean;
|
|
150
138
|
pauseForDelegationFailure: (reason: string, failureSummary?: string) => void;
|
|
151
139
|
pauseForExecutionFailure: (
|
|
152
140
|
label: string,
|
|
@@ -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,
|