@wichayutdew/pi-workflows 1.0.1 → 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 -223
- package/agents/step.md +4 -11
- package/dist/index.js +2554 -976
- package/examples/mr-comments.workflow.yaml +16 -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 -44
- package/src/command-names.ts +1 -0
- package/src/commands.ts +33 -4
- package/src/config/ceiling.ts +6 -12
- package/src/config/load-workflows.ts +12 -1
- package/src/config/step-digests.ts +19 -0
- package/src/config/types.ts +17 -22
- package/src/config/validation/permissions.ts +5 -66
- package/src/config/validation/prompt.ts +1 -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 +3 -0
- package/src/engine/gate-transitions.ts +58 -9
- 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 +235 -0
- package/src/engine/run-workflow-validation.ts +191 -0
- package/src/engine/state-types.ts +108 -5
- 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 -84
- 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 +3 -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
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"tree",
|
|
49
49
|
"trust",
|
|
50
50
|
"workflow-abort",
|
|
51
|
+
"workflow-doctor",
|
|
51
52
|
"workflow-list",
|
|
52
53
|
"workflow-pause",
|
|
53
54
|
"workflow-reload",
|
|
@@ -94,6 +95,34 @@
|
|
|
94
95
|
"type": "string",
|
|
95
96
|
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
96
97
|
},
|
|
98
|
+
"relativePath": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"minLength": 1,
|
|
101
|
+
"maxLength": 4096,
|
|
102
|
+
"pattern": "^\\S(?:.*\\S)?$",
|
|
103
|
+
"allOf": [
|
|
104
|
+
{
|
|
105
|
+
"not": {
|
|
106
|
+
"pattern": "^/"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"not": {
|
|
111
|
+
"pattern": "^[A-Za-z]:"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"not": {
|
|
116
|
+
"pattern": "^\\\\"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"not": {
|
|
121
|
+
"pattern": "\\u0000"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
},
|
|
97
126
|
"resourceName": {
|
|
98
127
|
"type": "string",
|
|
99
128
|
"pattern": "^[A-Za-z0-9_@./:+-]+$"
|
|
@@ -114,8 +143,7 @@
|
|
|
114
143
|
"required": ["file"],
|
|
115
144
|
"properties": {
|
|
116
145
|
"file": {
|
|
117
|
-
"
|
|
118
|
-
"minLength": 1
|
|
146
|
+
"$ref": "#/$defs/relativePath"
|
|
119
147
|
}
|
|
120
148
|
}
|
|
121
149
|
}
|
|
@@ -175,32 +203,17 @@
|
|
|
175
203
|
"required": ["mode"]
|
|
176
204
|
},
|
|
177
205
|
"then": {
|
|
178
|
-
"
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"
|
|
182
|
-
"allow": {
|
|
183
|
-
"minItems": 1
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"required": ["approvedSources"],
|
|
189
|
-
"properties": {
|
|
190
|
-
"approvedSources": {
|
|
191
|
-
"minItems": 1
|
|
192
|
-
}
|
|
193
|
-
}
|
|
206
|
+
"required": ["allow"],
|
|
207
|
+
"properties": {
|
|
208
|
+
"allow": {
|
|
209
|
+
"minItems": 1
|
|
194
210
|
}
|
|
195
|
-
|
|
211
|
+
}
|
|
196
212
|
},
|
|
197
213
|
"else": {
|
|
198
214
|
"properties": {
|
|
199
215
|
"allow": {
|
|
200
216
|
"maxItems": 0
|
|
201
|
-
},
|
|
202
|
-
"approvedSources": {
|
|
203
|
-
"maxItems": 0
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
}
|
|
@@ -208,33 +221,13 @@
|
|
|
208
221
|
],
|
|
209
222
|
"properties": {
|
|
210
223
|
"mode": {
|
|
211
|
-
"enum": ["deny", "
|
|
224
|
+
"enum": ["deny", "allow-list", "unrestricted"]
|
|
212
225
|
},
|
|
213
226
|
"allow": {
|
|
214
227
|
"type": "array",
|
|
215
228
|
"items": {
|
|
216
229
|
"$ref": "#/$defs/bashRule"
|
|
217
230
|
}
|
|
218
|
-
},
|
|
219
|
-
"approvedSources": {
|
|
220
|
-
"type": "array",
|
|
221
|
-
"uniqueItems": true,
|
|
222
|
-
"items": {
|
|
223
|
-
"enum": [
|
|
224
|
-
"verification-worker",
|
|
225
|
-
"verification-reviewer",
|
|
226
|
-
"remote-actions",
|
|
227
|
-
"remote-push",
|
|
228
|
-
"remote-drafts"
|
|
229
|
-
]
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
"handoffSources": {
|
|
233
|
-
"type": "array",
|
|
234
|
-
"uniqueItems": true,
|
|
235
|
-
"items": {
|
|
236
|
-
"enum": ["verification-worker", "verification-reviewer"]
|
|
237
|
-
}
|
|
238
231
|
}
|
|
239
232
|
}
|
|
240
233
|
},
|
|
@@ -555,16 +548,54 @@
|
|
|
555
548
|
"type": "integer",
|
|
556
549
|
"minimum": 1000,
|
|
557
550
|
"maximum": 30000,
|
|
558
|
-
"default":
|
|
551
|
+
"default": 30000
|
|
559
552
|
}
|
|
560
553
|
}
|
|
561
554
|
}
|
|
562
555
|
]
|
|
563
556
|
},
|
|
557
|
+
"workspace": {
|
|
558
|
+
"type": "object",
|
|
559
|
+
"additionalProperties": false,
|
|
560
|
+
"required": ["bindOn"],
|
|
561
|
+
"properties": {
|
|
562
|
+
"bindOn": {
|
|
563
|
+
"type": "array",
|
|
564
|
+
"minItems": 1,
|
|
565
|
+
"uniqueItems": true,
|
|
566
|
+
"items": {
|
|
567
|
+
"$ref": "#/$defs/identifier"
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
"allowedRoots": {
|
|
571
|
+
"type": "array",
|
|
572
|
+
"minItems": 1,
|
|
573
|
+
"maxItems": 32,
|
|
574
|
+
"uniqueItems": true,
|
|
575
|
+
"default": ["."],
|
|
576
|
+
"items": {
|
|
577
|
+
"$ref": "#/$defs/relativePath"
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
},
|
|
564
582
|
"step": {
|
|
565
583
|
"type": "object",
|
|
566
584
|
"additionalProperties": false,
|
|
567
585
|
"required": ["prompt", "transitions"],
|
|
586
|
+
"allOf": [
|
|
587
|
+
{
|
|
588
|
+
"if": {
|
|
589
|
+
"required": ["workspace"]
|
|
590
|
+
},
|
|
591
|
+
"then": {
|
|
592
|
+
"required": ["subagent"],
|
|
593
|
+
"not": {
|
|
594
|
+
"required": ["gate"]
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
],
|
|
568
599
|
"properties": {
|
|
569
600
|
"title": {
|
|
570
601
|
"type": "string",
|
|
@@ -595,6 +626,9 @@
|
|
|
595
626
|
},
|
|
596
627
|
"gate": {
|
|
597
628
|
"$ref": "#/$defs/gate"
|
|
629
|
+
},
|
|
630
|
+
"workspace": {
|
|
631
|
+
"$ref": "#/$defs/workspace"
|
|
598
632
|
}
|
|
599
633
|
}
|
|
600
634
|
}
|
package/src/command-names.ts
CHANGED
package/src/commands.ts
CHANGED
|
@@ -12,6 +12,11 @@ export type WorkflowCommandController = {
|
|
|
12
12
|
readonly workflowIds: () => ReadonlyArray<string>;
|
|
13
13
|
/** Lists loaded workflows. */
|
|
14
14
|
readonly list: (context: ExtensionCommandContext) => Promise<void>;
|
|
15
|
+
/** Diagnoses workflow graph liveness. */
|
|
16
|
+
readonly doctor: (
|
|
17
|
+
workflowId: string,
|
|
18
|
+
context: ExtensionCommandContext,
|
|
19
|
+
) => Promise<void>;
|
|
15
20
|
/** Starts a workflow. */
|
|
16
21
|
readonly start: (
|
|
17
22
|
workflowId: string,
|
|
@@ -23,8 +28,11 @@ export type WorkflowCommandController = {
|
|
|
23
28
|
reason: string,
|
|
24
29
|
context: ExtensionCommandContext,
|
|
25
30
|
) => Promise<void>;
|
|
26
|
-
/** Resumes the paused workflow. */
|
|
27
|
-
readonly resume: (
|
|
31
|
+
/** Resumes the paused workflow with optional user guidance. */
|
|
32
|
+
readonly resume: (
|
|
33
|
+
input: string,
|
|
34
|
+
context: ExtensionCommandContext,
|
|
35
|
+
) => Promise<void>;
|
|
28
36
|
/** Aborts the active workflow. */
|
|
29
37
|
readonly abort: (
|
|
30
38
|
reason: string,
|
|
@@ -98,6 +106,25 @@ export function createHarnessCommands(
|
|
|
98
106
|
handler: async (_args, context) => controller.list(context),
|
|
99
107
|
},
|
|
100
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'workflow-doctor',
|
|
111
|
+
options: {
|
|
112
|
+
description:
|
|
113
|
+
'Check workflow completion paths, unreachable steps, and cycles',
|
|
114
|
+
getArgumentCompletions: (prefix) => {
|
|
115
|
+
const completions = controller
|
|
116
|
+
.workflowIds()
|
|
117
|
+
.filter((workflowId) => workflowId.startsWith(prefix))
|
|
118
|
+
.map((workflowId) => ({
|
|
119
|
+
value: workflowId,
|
|
120
|
+
label: workflowId,
|
|
121
|
+
}));
|
|
122
|
+
return completions.length > 0 ? completions : null;
|
|
123
|
+
},
|
|
124
|
+
handler: async (args, context) =>
|
|
125
|
+
controller.doctor(args.trim(), context),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
101
128
|
createStartCommand(controller),
|
|
102
129
|
{
|
|
103
130
|
name: 'workflow-pause',
|
|
@@ -110,8 +137,10 @@ export function createHarnessCommands(
|
|
|
110
137
|
{
|
|
111
138
|
name: 'workflow-resume',
|
|
112
139
|
options: {
|
|
113
|
-
description:
|
|
114
|
-
|
|
140
|
+
description:
|
|
141
|
+
'Reload configuration and resume: /workflow-resume [guidance]',
|
|
142
|
+
handler: async (input, context) =>
|
|
143
|
+
controller.resume(input.trim(), context),
|
|
115
144
|
},
|
|
116
145
|
},
|
|
117
146
|
{
|
package/src/config/ceiling.ts
CHANGED
|
@@ -28,21 +28,10 @@ function bashWithinCeiling(
|
|
|
28
28
|
if (ceiling.mode === 'unrestricted') return true;
|
|
29
29
|
if (requested.mode === 'deny') return true;
|
|
30
30
|
if (ceiling.mode === 'deny') return false;
|
|
31
|
-
if (ceiling.mode === 'read-only') return requested.mode === 'read-only';
|
|
32
31
|
if (requested.mode !== 'allow-list') return false;
|
|
33
32
|
|
|
34
33
|
const allowedRules = new Set(ceiling.allow.map(ruleKey));
|
|
35
|
-
|
|
36
|
-
const allowedHandoffSources = new Set(ceiling.handoffSources ?? []);
|
|
37
|
-
return (
|
|
38
|
-
requested.allow.every((rule) => allowedRules.has(ruleKey(rule))) &&
|
|
39
|
-
(requested.approvedSources ?? []).every((source) =>
|
|
40
|
-
allowedSources.has(source),
|
|
41
|
-
) &&
|
|
42
|
-
(requested.handoffSources ?? []).every((source) =>
|
|
43
|
-
allowedHandoffSources.has(source),
|
|
44
|
-
)
|
|
45
|
-
);
|
|
34
|
+
return requested.allow.every((rule) => allowedRules.has(ruleKey(rule)));
|
|
46
35
|
}
|
|
47
36
|
|
|
48
37
|
function turnBudgetErrors(
|
|
@@ -128,6 +117,11 @@ export function checkWorkflowAgainstCeiling(
|
|
|
128
117
|
return Object.entries(workflow.steps).flatMap(([stepId, step]) => {
|
|
129
118
|
const path = `workflow.steps.${stepId}.permissions`;
|
|
130
119
|
return [
|
|
120
|
+
...(Object.hasOwn(step, 'workspace')
|
|
121
|
+
? [
|
|
122
|
+
`workflow.steps.${stepId}.workspace: workspace binding is unavailable to project workflows`,
|
|
123
|
+
]
|
|
124
|
+
: []),
|
|
131
125
|
...step.permissions.tools
|
|
132
126
|
.filter((tool) => !ceiling.tools.includes(tool))
|
|
133
127
|
.map(
|
|
@@ -2,6 +2,10 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
|
2
2
|
import { digest } from '../digest.ts';
|
|
3
3
|
import { createDiagnostic, errorCode, errorMessage } from './diagnostics.ts';
|
|
4
4
|
import type { ConfigFileSystem, LoadedDirectory } from './load-types.ts';
|
|
5
|
+
import {
|
|
6
|
+
digestWorkflowStep,
|
|
7
|
+
digestWorkflowStepStructure,
|
|
8
|
+
} from './step-digests.ts';
|
|
5
9
|
import type {
|
|
6
10
|
LoadedWorkflow,
|
|
7
11
|
WorkflowDefinition,
|
|
@@ -81,7 +85,13 @@ async function loadWorkflowFile(
|
|
|
81
85
|
const stepDigests = Object.fromEntries(
|
|
82
86
|
Object.entries(definition.steps).map(([stepId, step]) => [
|
|
83
87
|
stepId,
|
|
84
|
-
|
|
88
|
+
digestWorkflowStep(step, prompts[stepId] ?? ''),
|
|
89
|
+
]),
|
|
90
|
+
);
|
|
91
|
+
const stepStructuralDigests = Object.fromEntries(
|
|
92
|
+
Object.entries(definition.steps).map(([stepId, step]) => [
|
|
93
|
+
stepId,
|
|
94
|
+
digestWorkflowStepStructure(step),
|
|
85
95
|
]),
|
|
86
96
|
);
|
|
87
97
|
return {
|
|
@@ -89,6 +99,7 @@ async function loadWorkflowFile(
|
|
|
89
99
|
prompts,
|
|
90
100
|
digest: digest({ definition, prompts }),
|
|
91
101
|
stepDigests,
|
|
102
|
+
stepStructuralDigests,
|
|
92
103
|
sourcePath,
|
|
93
104
|
sourceKind,
|
|
94
105
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { digest } from '../digest.ts';
|
|
2
|
+
import type { WorkflowStep } from './types.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Digests a normalized workflow step together with its resolved prompt text.
|
|
6
|
+
*/
|
|
7
|
+
export function digestWorkflowStep(step: WorkflowStep, prompt: string): string {
|
|
8
|
+
return digest({ step, prompt });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Digests every normalized workflow-step field except the prompt specification.
|
|
13
|
+
*/
|
|
14
|
+
export function digestWorkflowStepStructure(step: WorkflowStep): string {
|
|
15
|
+
const structure = Object.fromEntries(
|
|
16
|
+
Object.entries(step).filter(([key]) => key !== 'prompt'),
|
|
17
|
+
);
|
|
18
|
+
return digest(structure);
|
|
19
|
+
}
|
package/src/config/types.ts
CHANGED
|
@@ -4,18 +4,14 @@ export const WORKFLOW_SCHEMA_VERSION = 1 as const;
|
|
|
4
4
|
export const DEFAULT_STATUS_SHORTCUT = 'ctrl+alt+w' as const satisfies KeyId;
|
|
5
5
|
export const SUBAGENT_RUNTIME_NAME_PATTERN =
|
|
6
6
|
/^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/;
|
|
7
|
+
export const MAX_WORKSPACE_PATH_CHARS = 4_096;
|
|
8
|
+
export const MAX_WORKSPACE_ALLOWED_ROOTS = 32;
|
|
7
9
|
|
|
8
10
|
export const TERMINAL_TARGETS = ['$done', '$pause'] as const;
|
|
9
11
|
export type TerminalTarget = (typeof TERMINAL_TARGETS)[number];
|
|
10
12
|
export type StepTarget = string;
|
|
11
13
|
|
|
12
|
-
export type BashMode = 'deny' | '
|
|
13
|
-
export type BashApprovalSource =
|
|
14
|
-
| 'verification-worker'
|
|
15
|
-
| 'verification-reviewer'
|
|
16
|
-
| 'remote-actions'
|
|
17
|
-
| 'remote-push'
|
|
18
|
-
| 'remote-drafts';
|
|
14
|
+
export type BashMode = 'deny' | 'allow-list' | 'unrestricted';
|
|
19
15
|
|
|
20
16
|
export type BashRule = {
|
|
21
17
|
readonly executable: string;
|
|
@@ -25,18 +21,6 @@ export type BashRule = {
|
|
|
25
21
|
export type BashPermission = {
|
|
26
22
|
readonly mode: BashMode;
|
|
27
23
|
readonly allow: ReadonlyArray<BashRule>;
|
|
28
|
-
/**
|
|
29
|
-
* Exact commands extracted from the most recent human-approved artifact.
|
|
30
|
-
* They supplement `allow` only inside the correlated step execution.
|
|
31
|
-
*/
|
|
32
|
-
readonly approvedSources?: ReadonlyArray<BashApprovalSource>;
|
|
33
|
-
/**
|
|
34
|
-
* Sources whose command-only retry handoffs may refine an already approved
|
|
35
|
-
* command. The original reviewed identity and safety envelope remain fixed.
|
|
36
|
-
*/
|
|
37
|
-
readonly handoffSources?: ReadonlyArray<
|
|
38
|
-
'verification-worker' | 'verification-reviewer'
|
|
39
|
-
>;
|
|
40
24
|
};
|
|
41
25
|
|
|
42
26
|
export type StepPermissions = {
|
|
@@ -81,9 +65,9 @@ export type StepSubagent = {
|
|
|
81
65
|
readonly toolBudget?: SubagentToolBudget;
|
|
82
66
|
readonly artifacts: boolean;
|
|
83
67
|
/**
|
|
84
|
-
* Authorizes
|
|
85
|
-
*
|
|
86
|
-
*
|
|
68
|
+
* Authorizes bounded automatic recovery when Bash can execute commands.
|
|
69
|
+
* Every failed attempt must still have a complete, mutation-safe replay
|
|
70
|
+
* audit.
|
|
87
71
|
*/
|
|
88
72
|
readonly retryToolFailures: boolean;
|
|
89
73
|
};
|
|
@@ -108,6 +92,13 @@ export type PlannotatorGate = GateDefinition & {
|
|
|
108
92
|
|
|
109
93
|
export type WorkflowGate = PromptGate | PlannotatorGate;
|
|
110
94
|
|
|
95
|
+
export type StepWorkspaceBinding = {
|
|
96
|
+
/** Outcomes whose result establishes the workspace for later steps. */
|
|
97
|
+
readonly bindOn: ReadonlyArray<string>;
|
|
98
|
+
/** Paths relative to the run-start directory that may contain the workspace. */
|
|
99
|
+
readonly allowedRoots: ReadonlyArray<string>;
|
|
100
|
+
};
|
|
101
|
+
|
|
111
102
|
export type WorkflowStep = {
|
|
112
103
|
readonly title: string;
|
|
113
104
|
readonly prompt: PromptSpec;
|
|
@@ -117,6 +108,8 @@ export type WorkflowStep = {
|
|
|
117
108
|
readonly requires: StepRequirements;
|
|
118
109
|
readonly transitions: Readonly<Record<string, StepTarget>>;
|
|
119
110
|
readonly gate?: WorkflowGate;
|
|
111
|
+
/** Optional one-time workspace binding produced by this delegated step. */
|
|
112
|
+
readonly workspace?: StepWorkspaceBinding;
|
|
120
113
|
};
|
|
121
114
|
|
|
122
115
|
export type WorkflowDefinition = {
|
|
@@ -137,6 +130,8 @@ export type LoadedWorkflow = {
|
|
|
137
130
|
readonly prompts: Readonly<Record<string, string>>;
|
|
138
131
|
readonly digest: string;
|
|
139
132
|
readonly stepDigests: Readonly<Record<string, string>>;
|
|
133
|
+
/** Per-step digest excluding only the prompt specification and resolved text. */
|
|
134
|
+
readonly stepStructuralDigests: Readonly<Record<string, string>>;
|
|
140
135
|
readonly sourcePath: string;
|
|
141
136
|
readonly sourceKind: WorkflowSourceKind;
|
|
142
137
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EMPTY_PERMISSIONS,
|
|
3
|
-
type BashApprovalSource,
|
|
4
3
|
type BashMode,
|
|
5
4
|
type BashPermission,
|
|
6
5
|
type BashRule,
|
|
@@ -8,7 +7,6 @@ import {
|
|
|
8
7
|
type StepRequirements,
|
|
9
8
|
} from '../types.ts';
|
|
10
9
|
import {
|
|
11
|
-
BASH_APPROVAL_SOURCE_PATTERN,
|
|
12
10
|
EXECUTABLE_PATTERN,
|
|
13
11
|
isJsonObject,
|
|
14
12
|
MCP_SELECTOR_PATTERN,
|
|
@@ -20,18 +18,6 @@ import {
|
|
|
20
18
|
type ValidationErrors,
|
|
21
19
|
} from './shared.ts';
|
|
22
20
|
|
|
23
|
-
const BASH_APPROVAL_SOURCES = [
|
|
24
|
-
'verification-worker',
|
|
25
|
-
'verification-reviewer',
|
|
26
|
-
'remote-actions',
|
|
27
|
-
'remote-push',
|
|
28
|
-
'remote-drafts',
|
|
29
|
-
] as const satisfies ReadonlyArray<BashApprovalSource>;
|
|
30
|
-
|
|
31
|
-
function isBashApprovalSource(value: string): value is BashApprovalSource {
|
|
32
|
-
return BASH_APPROVAL_SOURCES.some((source) => source === value);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
21
|
function emptyPermissions(): StepPermissions {
|
|
36
22
|
return {
|
|
37
23
|
tools: [],
|
|
@@ -131,23 +117,13 @@ function parseBashPermission(
|
|
|
131
117
|
errors.push(`${path}: expected an object`);
|
|
132
118
|
return { ...EMPTY_PERMISSIONS.bash, allow: [] };
|
|
133
119
|
}
|
|
134
|
-
rejectUnknownKeys(
|
|
135
|
-
value,
|
|
136
|
-
['mode', 'allow', 'approvedSources', 'handoffSources'],
|
|
137
|
-
path,
|
|
138
|
-
errors,
|
|
139
|
-
);
|
|
120
|
+
rejectUnknownKeys(value, ['mode', 'allow'], path, errors);
|
|
140
121
|
|
|
141
122
|
const mode = readString(value.mode, `${path}.mode`, errors);
|
|
142
123
|
const isValidMode =
|
|
143
|
-
mode === 'deny' ||
|
|
144
|
-
mode === 'read-only' ||
|
|
145
|
-
mode === 'allow-list' ||
|
|
146
|
-
mode === 'unrestricted';
|
|
124
|
+
mode === 'deny' || mode === 'allow-list' || mode === 'unrestricted';
|
|
147
125
|
if (!isValidMode) {
|
|
148
|
-
errors.push(
|
|
149
|
-
`${path}.mode: expected deny, read-only, allow-list, or unrestricted`,
|
|
150
|
-
);
|
|
126
|
+
errors.push(`${path}.mode: expected deny, allow-list, or unrestricted`);
|
|
151
127
|
}
|
|
152
128
|
|
|
153
129
|
const allow = Array.isArray(value.allow)
|
|
@@ -159,54 +135,17 @@ function parseBashPermission(
|
|
|
159
135
|
errors.push(`${path}.allow: expected an array`);
|
|
160
136
|
}
|
|
161
137
|
|
|
162
|
-
const approvedSources = (
|
|
163
|
-
value.approvedSources === undefined
|
|
164
|
-
? []
|
|
165
|
-
: readStringList(
|
|
166
|
-
value.approvedSources,
|
|
167
|
-
`${path}.approvedSources`,
|
|
168
|
-
errors,
|
|
169
|
-
BASH_APPROVAL_SOURCE_PATTERN,
|
|
170
|
-
)
|
|
171
|
-
).filter(isBashApprovalSource);
|
|
172
|
-
const handoffSources = (
|
|
173
|
-
value.handoffSources === undefined
|
|
174
|
-
? []
|
|
175
|
-
: readStringList(
|
|
176
|
-
value.handoffSources,
|
|
177
|
-
`${path}.handoffSources`,
|
|
178
|
-
errors,
|
|
179
|
-
/^(verification-worker|verification-reviewer)$/,
|
|
180
|
-
)
|
|
181
|
-
) as Array<'verification-worker' | 'verification-reviewer'>;
|
|
182
|
-
|
|
183
138
|
const normalizedMode: BashMode = isValidMode ? mode : 'deny';
|
|
184
139
|
if (normalizedMode !== 'allow-list' && allow.length > 0) {
|
|
185
140
|
errors.push(`${path}.allow: only valid when mode is "allow-list"`);
|
|
186
141
|
}
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
allow.length === 0 &&
|
|
190
|
-
approvedSources.length === 0
|
|
191
|
-
) {
|
|
192
|
-
errors.push(
|
|
193
|
-
`${path}: allow-list mode requires an allow rule or an approved command source`,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
if (normalizedMode !== 'allow-list' && approvedSources.length > 0) {
|
|
197
|
-
errors.push(
|
|
198
|
-
`${path}.approvedSources: only valid when mode is "allow-list"`,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
if (normalizedMode !== 'allow-list' && handoffSources.length > 0) {
|
|
202
|
-
errors.push(`${path}.handoffSources: only valid when mode is "allow-list"`);
|
|
142
|
+
if (normalizedMode === 'allow-list' && allow.length === 0) {
|
|
143
|
+
errors.push(`${path}: allow-list mode requires an allow rule`);
|
|
203
144
|
}
|
|
204
145
|
|
|
205
146
|
return {
|
|
206
147
|
mode: normalizedMode,
|
|
207
148
|
allow,
|
|
208
|
-
...(approvedSources.length > 0 ? { approvedSources } : {}),
|
|
209
|
-
...(handoffSources.length > 0 ? { handoffSources } : {}),
|
|
210
149
|
};
|
|
211
150
|
}
|
|
212
151
|
|
|
@@ -12,8 +12,6 @@ export const TOOL_PATTERN = /^[A-Za-z0-9_.:-]+$/;
|
|
|
12
12
|
export const RESOURCE_SELECTOR_PATTERN = /^[A-Za-z0-9_@./:+-]+$/;
|
|
13
13
|
export const MCP_SELECTOR_PATTERN = /^[A-Za-z0-9_.:-]+(?:\/[A-Za-z0-9_.:-]+)?$/;
|
|
14
14
|
export const EXECUTABLE_PATTERN = /^[A-Za-z0-9_./+-]+$/;
|
|
15
|
-
export const BASH_APPROVAL_SOURCE_PATTERN =
|
|
16
|
-
/^(verification-worker|verification-reviewer|remote-actions|remote-push|remote-drafts)$/;
|
|
17
15
|
|
|
18
16
|
type StringOptions = {
|
|
19
17
|
readonly pattern?: RegExp;
|