@wichayutdew/pi-workflows 0.2.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -38
- package/dist/index.js +5903 -4782
- package/package.json +1 -1
- package/src/command-names.ts +7 -1
- package/src/commands.ts +126 -52
- package/src/config/catalog.ts +162 -0
- package/src/config/ceiling.ts +116 -116
- package/src/config/command-conflicts.ts +4 -4
- package/src/config/diagnostics.ts +28 -0
- package/src/config/load-settings.ts +37 -0
- package/src/config/load-types.ts +53 -0
- package/src/config/load-workflows.ts +135 -0
- package/src/config/load.ts +43 -311
- package/src/config/types.ts +137 -135
- package/src/config/validate.ts +12 -1261
- package/src/config/validation/permissions.ts +291 -0
- package/src/config/validation/prompt.ts +19 -0
- package/src/config/validation/settings.ts +122 -0
- package/src/config/validation/shared.ts +129 -0
- package/src/config/validation/shortcut.ts +118 -0
- package/src/config/validation/step.ts +228 -0
- package/src/config/validation/subagent.ts +288 -0
- package/src/config/validation/workflow.ts +156 -0
- package/src/config/yaml.ts +29 -0
- package/src/digest.ts +38 -6
- package/src/engine/checkpoint.ts +13 -9
- package/src/engine/create-run.ts +42 -0
- package/src/engine/gate-transitions.ts +184 -0
- package/src/engine/reconciliation-history.ts +88 -0
- package/src/engine/resume.ts +19 -7
- package/src/engine/run-advance.ts +122 -0
- package/src/engine/run-lifecycle.ts +124 -0
- package/src/engine/run-reconciliation.ts +116 -0
- package/src/engine/run-validation.ts +128 -0
- package/src/engine/state-types.ts +64 -0
- package/src/engine/state.ts +10 -193
- package/src/engine/transition-helpers.ts +28 -0
- package/src/engine/transition-types.ts +8 -0
- package/src/engine/transitions.ts +17 -471
- package/src/harness/action-context.ts +181 -0
- package/src/harness/catalog.ts +45 -0
- package/src/harness/context-idle.ts +21 -0
- package/src/harness/core-actions.ts +299 -0
- package/src/harness/delegation-control-actions.ts +250 -0
- package/src/harness/delegation-failure.ts +215 -0
- package/src/harness/delegation-plan.ts +219 -0
- package/src/harness/delegation-recovery-validation.ts +159 -0
- package/src/harness/delegation-response-actions.ts +295 -0
- package/src/harness/delegation-retry-policy.ts +118 -0
- package/src/harness/dependencies.ts +165 -0
- package/src/harness/gate-submission-action.ts +154 -0
- package/src/harness/lifecycle-actions.ts +154 -0
- package/src/harness/pause-actions.ts +120 -0
- package/src/harness/plannotator-result-actions.ts +123 -0
- package/src/harness/prompt-gate-actions.ts +270 -0
- package/src/harness/resume-action.ts +261 -0
- package/src/harness/start-actions.ts +183 -0
- package/src/harness/status-actions.ts +191 -0
- package/src/harness/step-execution-actions.ts +283 -0
- package/src/harness/types.ts +76 -0
- package/src/harness.ts +237 -2201
- package/src/index.ts +73 -11
- package/src/integrations/plannotator-requests.ts +127 -0
- package/src/integrations/plannotator-responses.ts +141 -0
- package/src/integrations/plannotator-types.ts +44 -0
- package/src/integrations/plannotator.ts +16 -235
- package/src/integrations/prompt-gate.ts +17 -10
- package/src/integrations/subagents/child-policy-envelope.ts +119 -0
- package/src/integrations/subagents/child-policy-paths.ts +66 -0
- package/src/integrations/subagents/child-policy-sections.ts +174 -0
- package/src/integrations/subagents/child-policy-types.ts +32 -0
- package/src/integrations/subagents/child-policy-validation.ts +163 -0
- package/src/integrations/subagents/child-runtime-completion.ts +56 -0
- package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
- package/src/integrations/subagents/child-runtime-files.ts +156 -0
- package/src/integrations/subagents/child-runtime-policy.ts +83 -0
- package/src/integrations/subagents/child-runtime-types.ts +30 -0
- package/src/integrations/subagents/child-runtime.ts +140 -301
- package/src/integrations/subagents/client-delegation.ts +181 -0
- package/src/integrations/subagents/client-messages.ts +66 -0
- package/src/integrations/subagents/client-types.ts +36 -0
- package/src/integrations/subagents/client.ts +127 -219
- package/src/integrations/subagents/delegated-result.ts +31 -0
- package/src/integrations/subagents/diagnostic-format.ts +40 -0
- package/src/integrations/subagents/diagnostic-text.ts +114 -0
- package/src/integrations/subagents/diagnostic-types.ts +77 -0
- package/src/integrations/subagents/diagnostics.ts +24 -799
- package/src/integrations/subagents/failure-correlation.ts +235 -0
- package/src/integrations/subagents/failure-transcript.ts +192 -0
- package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
- package/src/integrations/subagents/protocol-events.ts +27 -0
- package/src/integrations/subagents/protocol.ts +30 -375
- package/src/integrations/subagents/replay-audit.ts +147 -0
- package/src/integrations/subagents/replay-safety.ts +82 -0
- package/src/integrations/subagents/session-diagnostics.ts +258 -0
- package/src/policy/approved-command-extraction.ts +71 -0
- package/src/policy/approved-commands.ts +15 -432
- package/src/policy/bash-authorization.ts +86 -0
- package/src/policy/bash-read-only.ts +161 -0
- package/src/policy/bash-types.ts +20 -0
- package/src/policy/bash.ts +4 -346
- package/src/policy/completion-batch.ts +21 -16
- package/src/policy/immutable-input.ts +5 -2
- package/src/policy/mcp-authorization.ts +71 -0
- package/src/policy/restricted-command.ts +141 -0
- package/src/policy/restricted-git.ts +30 -0
- package/src/policy/reviewed-artifact.ts +97 -0
- package/src/policy/reviewed-command-safety.ts +150 -0
- package/src/policy/reviewed-command-shape.ts +49 -0
- package/src/policy/reviewed-repository-contract.ts +138 -0
- package/src/policy/reviewed-repository-cwd.ts +102 -0
- package/src/policy/tool-call-authorization.ts +58 -0
- package/src/policy/tool-selection.ts +72 -0
- package/src/policy/tool-types.ts +14 -0
- package/src/policy/tools.ts +11 -150
- package/src/preflight.ts +96 -61
- package/src/prompt/main-workflow-notice.ts +41 -0
- package/src/prompt/retry-task.ts +83 -0
- package/src/prompt/step-contract.ts +101 -0
- package/src/prompt/step-sections.ts +75 -0
- package/src/prompt/step-task.ts +156 -0
- package/src/prompt/template.ts +81 -0
- package/src/prompt.ts +10 -245
- package/src/runtime/completion-tool.ts +3 -0
- package/src/runtime/main-step-completion.ts +78 -0
- package/src/runtime/main-step-lifecycle.ts +37 -0
- package/src/runtime/main-step-policy.ts +84 -0
- package/src/runtime/main-step-runtime-types.ts +64 -0
- package/src/runtime/main-step-runtime.ts +129 -200
- package/src/runtime/main-step-state.ts +85 -0
- package/src/runtime/serial-task-queue.ts +48 -13
- package/src/runtime/step-result.ts +48 -27
- package/src/workflow-list.ts +15 -6
- package/src/workflow-status/format-status.ts +52 -0
- package/src/workflow-status/formatting.ts +178 -0
- package/src/workflow-status/layout.ts +109 -0
- package/src/workflow-status/render-board.ts +101 -0
- package/src/workflow-status/render-path.ts +123 -0
- package/src/workflow-status/render-summary.ts +144 -0
- package/src/workflow-status/types.ts +40 -0
- package/src/workflow-status/view.ts +273 -0
- package/src/workflow-status.ts +20 -820
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open, realpath } from 'node:fs/promises';
|
|
3
|
+
import {
|
|
4
|
+
basename,
|
|
5
|
+
dirname,
|
|
6
|
+
isAbsolute,
|
|
7
|
+
join,
|
|
8
|
+
relative,
|
|
9
|
+
resolve,
|
|
10
|
+
sep,
|
|
11
|
+
} from 'node:path';
|
|
12
|
+
import type {
|
|
13
|
+
DelegationReplayAudit,
|
|
14
|
+
DelegationReplayExpectation,
|
|
15
|
+
SessionTail,
|
|
16
|
+
SubagentSessionIdentity,
|
|
17
|
+
ToolFailureDiagnostic,
|
|
18
|
+
} from './diagnostic-types.ts';
|
|
19
|
+
import { parseToolFailureDiagnostic } from './failure-correlation.ts';
|
|
20
|
+
import { parseDelegationReplayAudit } from './replay-audit.ts';
|
|
21
|
+
|
|
22
|
+
const SESSION_FILE_NAME = 'session.jsonl';
|
|
23
|
+
const SESSION_RUN_DIRECTORY = /^run-\d+$/;
|
|
24
|
+
const SESSION_FILE_SUFFIX = '.jsonl';
|
|
25
|
+
const MAX_SESSION_TAIL_BYTES = 1024 * 1024;
|
|
26
|
+
|
|
27
|
+
export type SubagentDiagnosticPathInspection = {
|
|
28
|
+
readonly isFile: () => boolean;
|
|
29
|
+
readonly isSymbolicLink: () => boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type SubagentDiagnosticFileSnapshot =
|
|
33
|
+
SubagentDiagnosticPathInspection & {
|
|
34
|
+
readonly dev: number | bigint;
|
|
35
|
+
readonly ino: number | bigint;
|
|
36
|
+
readonly size: number;
|
|
37
|
+
readonly mtimeMs: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type SubagentDiagnosticFileHandle = {
|
|
41
|
+
readonly stat: () => Promise<SubagentDiagnosticFileSnapshot>;
|
|
42
|
+
readonly read: (
|
|
43
|
+
buffer: Uint8Array,
|
|
44
|
+
offset: number,
|
|
45
|
+
length: number,
|
|
46
|
+
position: number,
|
|
47
|
+
) => Promise<{ readonly bytesRead: number }>;
|
|
48
|
+
readonly close: () => Promise<void>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type SubagentDiagnosticFileSystem = {
|
|
52
|
+
readonly inspect: (path: string) => Promise<SubagentDiagnosticPathInspection>;
|
|
53
|
+
readonly realPath: (path: string) => Promise<string>;
|
|
54
|
+
readonly openReadOnlyNoFollow: (
|
|
55
|
+
path: string,
|
|
56
|
+
) => Promise<SubagentDiagnosticFileHandle>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type SubagentDiagnosticDependencies = {
|
|
60
|
+
readonly fileSystem: SubagentDiagnosticFileSystem;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const DEFAULT_DIAGNOSTIC_DEPENDENCIES = {
|
|
64
|
+
fileSystem: {
|
|
65
|
+
inspect: lstat,
|
|
66
|
+
realPath: realpath,
|
|
67
|
+
openReadOnlyNoFollow: (path: string) =>
|
|
68
|
+
open(path, constants.O_RDONLY | constants.O_NOFOLLOW),
|
|
69
|
+
},
|
|
70
|
+
} as const satisfies SubagentDiagnosticDependencies;
|
|
71
|
+
|
|
72
|
+
const isSessionFilePath = (path: string): boolean =>
|
|
73
|
+
isAbsolute(path) &&
|
|
74
|
+
!path.includes('\0') &&
|
|
75
|
+
basename(path) === SESSION_FILE_NAME &&
|
|
76
|
+
SESSION_RUN_DIRECTORY.test(basename(dirname(path)));
|
|
77
|
+
|
|
78
|
+
const pathIsWithin = (root: string, candidate: string): boolean => {
|
|
79
|
+
const relativePath = relative(resolve(root), resolve(candidate));
|
|
80
|
+
return (
|
|
81
|
+
relativePath !== '' &&
|
|
82
|
+
relativePath !== '..' &&
|
|
83
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
84
|
+
!isAbsolute(relativePath)
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const isValidSessionIdentity = (identity: SubagentSessionIdentity): boolean =>
|
|
89
|
+
identity.runId.length > 0 &&
|
|
90
|
+
!identity.runId.includes('\0') &&
|
|
91
|
+
basename(identity.runId) === identity.runId &&
|
|
92
|
+
identity.runId !== '.' &&
|
|
93
|
+
identity.runId !== '..' &&
|
|
94
|
+
Number.isSafeInteger(identity.childIndex) &&
|
|
95
|
+
identity.childIndex >= 0;
|
|
96
|
+
|
|
97
|
+
const readStableTail = async (
|
|
98
|
+
handle: SubagentDiagnosticFileHandle,
|
|
99
|
+
): Promise<SessionTail | undefined> => {
|
|
100
|
+
const opened = await handle.stat();
|
|
101
|
+
if (!opened.isFile()) return undefined;
|
|
102
|
+
|
|
103
|
+
const bytesToRead = Math.min(opened.size, MAX_SESSION_TAIL_BYTES);
|
|
104
|
+
if (bytesToRead === 0) return { content: '', truncated: false };
|
|
105
|
+
|
|
106
|
+
const start = opened.size - bytesToRead;
|
|
107
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
108
|
+
const { bytesRead } = await handle.read(buffer, 0, bytesToRead, start);
|
|
109
|
+
const afterRead = await handle.stat();
|
|
110
|
+
const didFileChange =
|
|
111
|
+
afterRead.dev !== opened.dev ||
|
|
112
|
+
afterRead.ino !== opened.ino ||
|
|
113
|
+
afterRead.size !== opened.size ||
|
|
114
|
+
afterRead.mtimeMs !== opened.mtimeMs;
|
|
115
|
+
if (didFileChange) return undefined;
|
|
116
|
+
|
|
117
|
+
let content = buffer.subarray(0, bytesRead).toString('utf8');
|
|
118
|
+
if (start > 0) {
|
|
119
|
+
const firstNewline = content.indexOf('\n');
|
|
120
|
+
content = firstNewline === -1 ? '' : content.slice(firstNewline + 1);
|
|
121
|
+
}
|
|
122
|
+
return { content, truncated: start > 0 };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
type ReadContainedSessionTailOptions = {
|
|
126
|
+
readonly sessionFile: string;
|
|
127
|
+
readonly trustedRoot: string;
|
|
128
|
+
readonly identity: SubagentSessionIdentity;
|
|
129
|
+
readonly dependencies: SubagentDiagnosticDependencies;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const readContainedSessionTail = async ({
|
|
133
|
+
sessionFile,
|
|
134
|
+
trustedRoot,
|
|
135
|
+
identity,
|
|
136
|
+
dependencies,
|
|
137
|
+
}: ReadContainedSessionTailOptions): Promise<SessionTail | undefined> => {
|
|
138
|
+
const expectedSessionFile = resolve(
|
|
139
|
+
trustedRoot,
|
|
140
|
+
identity.runId,
|
|
141
|
+
`run-${identity.childIndex}`,
|
|
142
|
+
SESSION_FILE_NAME,
|
|
143
|
+
);
|
|
144
|
+
if (
|
|
145
|
+
!isSessionFilePath(sessionFile) ||
|
|
146
|
+
!isAbsolute(trustedRoot) ||
|
|
147
|
+
trustedRoot.includes('\0') ||
|
|
148
|
+
!pathIsWithin(trustedRoot, sessionFile) ||
|
|
149
|
+
!isValidSessionIdentity(identity) ||
|
|
150
|
+
resolve(sessionFile) !== expectedSessionFile
|
|
151
|
+
) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const resolvedSessionFile = resolve(sessionFile);
|
|
156
|
+
const inspected = await dependencies.fileSystem.inspect(resolvedSessionFile);
|
|
157
|
+
if (inspected.isSymbolicLink() || !inspected.isFile()) return undefined;
|
|
158
|
+
|
|
159
|
+
const [canonicalRoot, canonicalSessionFile] = await Promise.all([
|
|
160
|
+
dependencies.fileSystem.realPath(trustedRoot),
|
|
161
|
+
dependencies.fileSystem.realPath(resolvedSessionFile),
|
|
162
|
+
]);
|
|
163
|
+
if (!pathIsWithin(canonicalRoot, canonicalSessionFile)) return undefined;
|
|
164
|
+
|
|
165
|
+
const handle =
|
|
166
|
+
await dependencies.fileSystem.openReadOnlyNoFollow(canonicalSessionFile);
|
|
167
|
+
try {
|
|
168
|
+
return await readStableTail(handle);
|
|
169
|
+
} finally {
|
|
170
|
+
await handle.close();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Derives the trusted pi-subagents session root beside a parent session file.
|
|
176
|
+
*/
|
|
177
|
+
export const deriveSubagentSessionRoot = (
|
|
178
|
+
parentSessionFile: string | undefined,
|
|
179
|
+
): string | undefined => {
|
|
180
|
+
if (
|
|
181
|
+
!parentSessionFile ||
|
|
182
|
+
!isAbsolute(parentSessionFile) ||
|
|
183
|
+
parentSessionFile.includes('\0')
|
|
184
|
+
) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
const parentName = basename(parentSessionFile);
|
|
188
|
+
if (
|
|
189
|
+
!parentName.endsWith(SESSION_FILE_SUFFIX) ||
|
|
190
|
+
parentName === SESSION_FILE_SUFFIX
|
|
191
|
+
) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return join(
|
|
195
|
+
dirname(parentSessionFile),
|
|
196
|
+
parentName.slice(0, -SESSION_FILE_SUFFIX.length),
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Reads and correlates a tool failure from a confined child session file.
|
|
202
|
+
*
|
|
203
|
+
* File-system operations are dependency-injected for deterministic use.
|
|
204
|
+
*/
|
|
205
|
+
export const readToolFailureDiagnostic = async (
|
|
206
|
+
sessionFile: string | undefined,
|
|
207
|
+
trustedRoot: string | undefined,
|
|
208
|
+
identity: SubagentSessionIdentity | undefined,
|
|
209
|
+
expectedTool?: string,
|
|
210
|
+
terminalError?: string,
|
|
211
|
+
dependencies: SubagentDiagnosticDependencies = DEFAULT_DIAGNOSTIC_DEPENDENCIES,
|
|
212
|
+
): Promise<ToolFailureDiagnostic | undefined> => {
|
|
213
|
+
if (!sessionFile || !trustedRoot || !identity) return undefined;
|
|
214
|
+
try {
|
|
215
|
+
const tail = await readContainedSessionTail({
|
|
216
|
+
sessionFile,
|
|
217
|
+
trustedRoot,
|
|
218
|
+
identity,
|
|
219
|
+
dependencies,
|
|
220
|
+
});
|
|
221
|
+
return parseToolFailureDiagnostic(
|
|
222
|
+
tail?.content ?? '',
|
|
223
|
+
expectedTool,
|
|
224
|
+
terminalError,
|
|
225
|
+
tail?.truncated !== true,
|
|
226
|
+
);
|
|
227
|
+
} catch {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Reads and audits replay safety from a confined child session file.
|
|
234
|
+
*
|
|
235
|
+
* File-system operations are dependency-injected for deterministic use.
|
|
236
|
+
*/
|
|
237
|
+
export const readDelegationReplayAudit = async (
|
|
238
|
+
sessionFile: string | undefined,
|
|
239
|
+
trustedRoot: string | undefined,
|
|
240
|
+
identity: SubagentSessionIdentity | undefined,
|
|
241
|
+
expectation: DelegationReplayExpectation,
|
|
242
|
+
dependencies: SubagentDiagnosticDependencies = DEFAULT_DIAGNOSTIC_DEPENDENCIES,
|
|
243
|
+
): Promise<DelegationReplayAudit | undefined> => {
|
|
244
|
+
if (!sessionFile || !trustedRoot || !identity) return undefined;
|
|
245
|
+
try {
|
|
246
|
+
const tail = await readContainedSessionTail({
|
|
247
|
+
sessionFile,
|
|
248
|
+
trustedRoot,
|
|
249
|
+
identity,
|
|
250
|
+
dependencies,
|
|
251
|
+
});
|
|
252
|
+
return tail
|
|
253
|
+
? parseDelegationReplayAudit(tail.content, expectation, !tail.truncated)
|
|
254
|
+
: undefined;
|
|
255
|
+
} catch {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { BashApprovalSource } from '../config/types.ts';
|
|
2
|
+
import {
|
|
3
|
+
parseJsonDocuments,
|
|
4
|
+
remoteActionCommands,
|
|
5
|
+
verificationCommands,
|
|
6
|
+
} from './reviewed-artifact.ts';
|
|
7
|
+
import {
|
|
8
|
+
isSafeRemoteActionCommand,
|
|
9
|
+
isSafeVerificationCommand,
|
|
10
|
+
} from './reviewed-command-safety.ts';
|
|
11
|
+
|
|
12
|
+
const commandsFromSource = (
|
|
13
|
+
document: unknown,
|
|
14
|
+
source: BashApprovalSource,
|
|
15
|
+
): Array<string> => {
|
|
16
|
+
if (source === 'verification-worker') {
|
|
17
|
+
return verificationCommands(document, 'worker').filter(
|
|
18
|
+
isSafeVerificationCommand,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
if (source === 'verification-reviewer') {
|
|
22
|
+
return verificationCommands(document, 'reviewer').filter(
|
|
23
|
+
isSafeVerificationCommand,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return remoteActionCommands(document).filter(isSafeRemoteActionCommand);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extracts exact Bash capabilities from human-approved, machine-readable JSON.
|
|
31
|
+
*
|
|
32
|
+
* @param artifact - Artifact displayed by the review gate.
|
|
33
|
+
* @param sources - Approved artifact sections to inspect.
|
|
34
|
+
* @returns Unique safe commands in source order.
|
|
35
|
+
*/
|
|
36
|
+
export const extractApprovedBashCommands = (
|
|
37
|
+
artifact: string,
|
|
38
|
+
sources: ReadonlyArray<BashApprovalSource>,
|
|
39
|
+
): Array<string> => {
|
|
40
|
+
if (!artifact.trim() || sources.length === 0) return [];
|
|
41
|
+
|
|
42
|
+
const commands = parseJsonDocuments(artifact).flatMap((document) =>
|
|
43
|
+
sources.flatMap((source) => commandsFromSource(document, source)),
|
|
44
|
+
);
|
|
45
|
+
return [...new Set(commands)];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Narrows reviewed commands to those retained by the latest step handoff.
|
|
50
|
+
*
|
|
51
|
+
* A child can remove reviewed authority, but its unreviewed output can never
|
|
52
|
+
* introduce a new Bash capability.
|
|
53
|
+
*
|
|
54
|
+
* @param artifact - Original human-approved artifact.
|
|
55
|
+
* @param handoff - Latest completed-step handoff.
|
|
56
|
+
* @param sources - Approved artifact sections to inspect.
|
|
57
|
+
* @returns Commands present in both artifacts.
|
|
58
|
+
*/
|
|
59
|
+
export const narrowApprovedBashCommands = (
|
|
60
|
+
artifact: string,
|
|
61
|
+
handoff: string,
|
|
62
|
+
sources: ReadonlyArray<BashApprovalSource>,
|
|
63
|
+
): Array<string> => {
|
|
64
|
+
const approvedCommands = extractApprovedBashCommands(artifact, sources);
|
|
65
|
+
if (approvedCommands.length === 0) return [];
|
|
66
|
+
|
|
67
|
+
const retainedCommands = new Set(
|
|
68
|
+
extractApprovedBashCommands(handoff, sources),
|
|
69
|
+
);
|
|
70
|
+
return approvedCommands.filter((command) => retainedCommands.has(command));
|
|
71
|
+
};
|