@fitlab-ai/agent-infra 0.9.5 → 0.9.6
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/dist/bin/internal-cli.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +126 -16
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +48 -4
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/git/workflow.js +0 -1
- package/dist/lib/internal/codex-lifecycle.js +100 -2
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +5 -4
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +15 -16
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +318 -89
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/executor.js +136 -0
- package/dist/lib/sandbox/control/lifecycle.js +348 -0
- package/dist/lib/sandbox/control/protocol.js +62 -26
- package/dist/lib/sandbox/control/server.js +222 -102
- package/dist/lib/sandbox/control/state.js +181 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +35 -24
- package/dist/lib/sandbox/recovery.js +262 -45
- package/dist/lib/sandbox/workspace-view.js +73 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/process-control.js +6 -0
- package/dist/lib/task/artifact-lifecycle.js +5 -4
- package/dist/lib/task/codex-orchestration.js +161 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/delegation-receipts.js +25 -0
- package/dist/lib/task/events.js +10 -4
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/orchestration.js +133 -4
- package/dist/lib/task/verification-engine.js +38 -6
- package/dist/lib/task/verification.js +1 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +142 -15
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +51 -3
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/git/workflow.ts +0 -1
- package/lib/internal/codex-lifecycle.ts +107 -3
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +4 -4
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +17 -21
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +368 -103
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/executor.ts +163 -0
- package/lib/sandbox/control/lifecycle.ts +342 -0
- package/lib/sandbox/control/protocol.ts +96 -69
- package/lib/sandbox/control/server.ts +239 -110
- package/lib/sandbox/control/state.ts +196 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +31 -13
- package/lib/sandbox/recovery.ts +301 -60
- package/lib/sandbox/workspace-view.ts +81 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/process-control.ts +11 -0
- package/lib/task/artifact-lifecycle.ts +5 -4
- package/lib/task/codex-orchestration.ts +233 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/delegation-receipts.ts +52 -3
- package/lib/task/events.ts +9 -4
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/orchestration.ts +199 -4
- package/lib/task/verification-engine.ts +42 -8
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +48 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +44 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +44 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
|
@@ -154,11 +154,47 @@ function inspectLocalReleaseFacts(cwd, version, run = command) {
|
|
|
154
154
|
const localTagConflict = Boolean(tagCommit && !localTag && !localTagAncestor);
|
|
155
155
|
const postSubject = `chore: prepare next dev iteration after v${version}`;
|
|
156
156
|
const postLog = localTag || localTagAncestor
|
|
157
|
-
? git(cwd, ['log', '--format=%s', `${tag}..HEAD`], run) ?? ''
|
|
157
|
+
? git(cwd, ['log', '--format=%H%x00%s', `${tag}..HEAD`], run) ?? ''
|
|
158
158
|
: '';
|
|
159
|
-
const postCommit = postLog.split('\n').
|
|
159
|
+
const postCommit = postLog.split('\n').map((line) => line.split('\0'))
|
|
160
|
+
.find(([, subject]) => subject === postSubject)?.[0] ?? null;
|
|
160
161
|
return { localTag, localTagAncestor, localTagConflict, postCommit };
|
|
161
162
|
}
|
|
163
|
+
function inspectPostReleaseFacts(cwd, commit, run = command) {
|
|
164
|
+
const inspected = inspectGitWorkflow(cwd);
|
|
165
|
+
const snapshot = inspected.snapshot;
|
|
166
|
+
const branch = snapshot?.branch ?? '';
|
|
167
|
+
const remoteLine = branch ? git(cwd, ['ls-remote', '--heads', 'origin', `refs/heads/${branch}`], run) : null;
|
|
168
|
+
const remoteHead = remoteLine?.split(/\s+/)[0] ?? null;
|
|
169
|
+
const packageJson = commit ? git(cwd, ['show', `${commit}:package.json`], run, false) : null;
|
|
170
|
+
let newVersion = null;
|
|
171
|
+
if (packageJson) {
|
|
172
|
+
try {
|
|
173
|
+
const version = JSON.parse(packageJson).version;
|
|
174
|
+
newVersion = typeof version === 'string' ? version : null;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
newVersion = null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const paths = commit ? git(cwd, ['diff-tree', '--root', '--no-commit-id', '--name-only', '-r', '-z', commit], run, false) : null;
|
|
181
|
+
const changedPaths = paths ? paths.split('\0').filter(Boolean)
|
|
182
|
+
.sort((left, right) => Buffer.from(left).compare(Buffer.from(right))) : [];
|
|
183
|
+
const digest = commit ? git(cwd, ['show', `${commit}:${DEMO_DIGEST_PATH}`], run) : null;
|
|
184
|
+
return {
|
|
185
|
+
commit,
|
|
186
|
+
isHead: Boolean(commit && snapshot?.head === commit),
|
|
187
|
+
published: Boolean(commit && remoteHead === commit),
|
|
188
|
+
branch,
|
|
189
|
+
upstream: snapshot?.upstream ?? null,
|
|
190
|
+
remoteHead,
|
|
191
|
+
newVersion,
|
|
192
|
+
changedPaths,
|
|
193
|
+
demoInputSha256: digest && /^[0-9a-f]{64}$/.test(digest) ? digest : null,
|
|
194
|
+
worktree: snapshot?.worktree ?? [],
|
|
195
|
+
staged: snapshot?.staged ?? []
|
|
196
|
+
};
|
|
197
|
+
}
|
|
162
198
|
function releaseSmokeStatus(workflows, version, tagCommit) {
|
|
163
199
|
const manualTitle = `Post-Release Smoke v${version}`;
|
|
164
200
|
const targets = workflows.filter((run) => {
|
|
@@ -209,12 +245,15 @@ async function inspectFacts(cwd, version) {
|
|
|
209
245
|
const workflows = platform.workflows;
|
|
210
246
|
const smoke = releaseSmokeStatus(workflows, version, localTagTarget);
|
|
211
247
|
return {
|
|
212
|
-
|
|
248
|
+
localTag: local.localTag,
|
|
249
|
+
localTagAncestor: local.localTagAncestor,
|
|
250
|
+
localTagConflict: local.localTagConflict,
|
|
213
251
|
remoteBranch: Boolean(remoteBranch && remoteBranch.split(/\s+/)[0] === head), remoteTag: Boolean(remoteTag),
|
|
214
252
|
githubRelease: platform.platform.type !== 'github'
|
|
215
253
|
? true
|
|
216
254
|
: platform.status === 'blocked' ? null : Boolean(platform.release?.published),
|
|
217
|
-
npm: npm.published, homebrew: homebrew.published, smoke
|
|
255
|
+
npm: npm.published, homebrew: homebrew.published, smoke,
|
|
256
|
+
post: inspectPostReleaseFacts(cwd, local.postCommit)
|
|
218
257
|
};
|
|
219
258
|
}
|
|
220
259
|
function parsePorcelainPath(record) {
|
|
@@ -245,13 +284,21 @@ function updateReleaseMetadata(cwd, version) {
|
|
|
245
284
|
fs.writeFileSync(filePath, content);
|
|
246
285
|
}
|
|
247
286
|
}
|
|
287
|
+
function optionValues(args, name) {
|
|
288
|
+
const values = [];
|
|
289
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
290
|
+
if (args[index] === name)
|
|
291
|
+
values.push(args[index + 1] ?? '');
|
|
292
|
+
}
|
|
293
|
+
return values;
|
|
294
|
+
}
|
|
248
295
|
async function releaseWorkflow(args = []) {
|
|
249
296
|
const [action, rawVersion, ...rest] = args;
|
|
250
297
|
const version = String(rawVersion || '').replace(/^v/, '');
|
|
251
298
|
const cwdIndex = rest.indexOf('--cwd');
|
|
252
299
|
const cwd = path.resolve(cwdIndex >= 0 && rest[cwdIndex + 1] ? rest[cwdIndex + 1] : process.cwd());
|
|
253
|
-
if (!['inspect', 'prepare', 'publish', 'post'].includes(action || '') || !semver.valid(version)) {
|
|
254
|
-
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'RELEASE_INPUT_INVALID', message: 'Usage: release-workflow inspect|prepare|publish|post <version>' } })}\n`);
|
|
300
|
+
if (!['inspect', 'prepare', 'publish', 'post-prepare', 'post-publish'].includes(action || '') || !semver.valid(version)) {
|
|
301
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'RELEASE_INPUT_INVALID', message: 'Usage: release-workflow inspect|prepare|publish|post-prepare|post-publish <version>' } })}\n`);
|
|
255
302
|
process.exitCode = 1;
|
|
256
303
|
return;
|
|
257
304
|
}
|
|
@@ -331,10 +378,56 @@ async function releaseWorkflow(args = []) {
|
|
|
331
378
|
process.exitCode = result.status === 'failed' ? 1 : result.status === 'degraded' ? 2 : 0;
|
|
332
379
|
return;
|
|
333
380
|
}
|
|
381
|
+
if (action === 'post-publish') {
|
|
382
|
+
const expectedValues = optionValues(rest, '--expected-sha256');
|
|
383
|
+
if (expectedValues.length !== 1 || !/^sha256:[0-9a-f]{64}$/.test(expectedValues[0])) {
|
|
384
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_INPUT_INVALID', message: 'post-publish requires one --expected-sha256 sha256:<64 hex>' } })}\n`);
|
|
385
|
+
process.exitCode = 1;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (before.phase === 'complete') {
|
|
389
|
+
process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const confirmation = 'postConfirmation' in before ? before.postConfirmation : null;
|
|
393
|
+
if (before.phase !== 'post-prepared' || !confirmation) {
|
|
394
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: confirmation ? 'RELEASE_PHASE_INVALID' : 'RELEASE_POST_CONFIRMATION_UNAVAILABLE', message: 'Post-release facts cannot authorize publish' } })}\n`);
|
|
395
|
+
process.exitCode = 1;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (confirmation.sha256 !== expectedValues[0]) {
|
|
399
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_POST_SNAPSHOT_MISMATCH', message: 'Post-release confirmation snapshot changed' } })}\n`);
|
|
400
|
+
process.exitCode = 1;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const channelsComplete = before.facts.githubRelease === true && before.facts.npm === true && before.facts.homebrew === true;
|
|
404
|
+
if (!channelsComplete || before.facts.smoke !== 'success') {
|
|
405
|
+
process.stdout.write(`${JSON.stringify({ status: before.facts.smoke === 'failed' ? 'failed' : 'blocked', changed: false, snapshot: before, error: { code: 'RELEASE_CHANNELS_PENDING', message: 'Release channels or smoke workflow are not complete' } })}\n`);
|
|
406
|
+
process.exitCode = before.facts.smoke === 'failed' ? 1 : 2;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const pushed = pushGitRefs({ cwd, remote: 'origin', refs: [before.facts.post.branch] });
|
|
410
|
+
const snapshot = releaseSnapshot(version, await inspectFacts(cwd, version));
|
|
411
|
+
if (snapshot.phase === 'complete') {
|
|
412
|
+
process.stdout.write(`${JSON.stringify({ ...pushed, status: 'applied', changed: true, snapshot, error: null })}\n`);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const blocked = pushed.status !== 'failed';
|
|
416
|
+
process.stdout.write(`${JSON.stringify({ ...pushed, status: blocked ? 'blocked' : 'failed', snapshot, error: pushed.error ?? { code: 'RELEASE_PROGRESS_PENDING', message: 'Post-release push is not visible at the expected remote head' } })}\n`);
|
|
417
|
+
process.exitCode = blocked ? 2 : 1;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
334
420
|
if (before.phase === 'complete') {
|
|
335
421
|
process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
|
|
336
422
|
return;
|
|
337
423
|
}
|
|
424
|
+
if (before.phase === 'post-prepared') {
|
|
425
|
+
const confirmation = 'postConfirmation' in before ? before.postConfirmation : null;
|
|
426
|
+
const status = confirmation ? 'no-op' : 'blocked';
|
|
427
|
+
process.stdout.write(`${JSON.stringify({ status, changed: false, snapshot: before, error: confirmation ? null : { code: 'RELEASE_POST_CONFIRMATION_UNAVAILABLE', message: 'Post commit is not the clean current HEAD' } })}\n`);
|
|
428
|
+
process.exitCode = confirmation ? 0 : 2;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
338
431
|
const channelsComplete = before.facts.githubRelease === true && before.facts.npm === true && before.facts.homebrew === true;
|
|
339
432
|
if (!['published', 'post-pending'].includes(before.phase) || !channelsComplete || before.facts.smoke !== 'success') {
|
|
340
433
|
process.stdout.write(`${JSON.stringify({ status: before.facts.smoke === 'failed' ? 'failed' : 'blocked', changed: false, snapshot: before, error: { code: 'RELEASE_CHANNELS_PENDING', message: 'Release channels or smoke workflow are not complete' } })}\n`);
|
|
@@ -373,10 +466,14 @@ async function releaseWorkflow(args = []) {
|
|
|
373
466
|
process.exitCode = 1;
|
|
374
467
|
return;
|
|
375
468
|
}
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
469
|
+
const snapshot = releaseSnapshot(version, await inspectFacts(cwd, version));
|
|
470
|
+
const confirmation = 'postConfirmation' in snapshot ? snapshot.postConfirmation : null;
|
|
471
|
+
if (snapshot.phase !== 'post-prepared' || !confirmation) {
|
|
472
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, demo, snapshot, error: { code: 'RELEASE_POST_CONFIRMATION_UNAVAILABLE', message: 'Prepared post commit did not produce a confirmation snapshot' } })}\n`);
|
|
473
|
+
process.exitCode = 1;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
process.stdout.write(`${JSON.stringify({ status: 'applied', changed: true, demo, snapshot, error: null })}\n`);
|
|
380
477
|
}
|
|
381
|
-
export { changedPaths, computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
|
|
478
|
+
export { changedPaths, computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
|
|
382
479
|
//# sourceMappingURL=release-workflow.js.map
|
|
@@ -1,24 +1,61 @@
|
|
|
1
|
-
import { requestSandboxControl } from '../sandbox/control/client.js';
|
|
1
|
+
import { requestSandboxControl, SandboxControlClientError } from '../sandbox/control/client.js';
|
|
2
2
|
import { serveSandboxControl } from '../sandbox/control/server.js';
|
|
3
|
-
|
|
3
|
+
import { runSandboxControlExecutor } from '../sandbox/control/executor.js';
|
|
4
|
+
async function sandboxControl(args) {
|
|
4
5
|
const [operation, ...rest] = args;
|
|
5
6
|
if (operation === 'serve') {
|
|
6
7
|
const manifestIndex = rest.indexOf('--manifest');
|
|
7
8
|
const manifest = manifestIndex >= 0 ? rest[manifestIndex + 1] : undefined;
|
|
8
9
|
if (!manifest)
|
|
9
10
|
throw new Error('sandbox-control serve requires --manifest');
|
|
10
|
-
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const abort = () => controller.abort();
|
|
13
|
+
process.once('SIGINT', abort);
|
|
14
|
+
process.once('SIGTERM', abort);
|
|
15
|
+
try {
|
|
16
|
+
await serveSandboxControl(manifest, controller.signal);
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
process.off('SIGINT', abort);
|
|
20
|
+
process.off('SIGTERM', abort);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (operation === 'execute') {
|
|
25
|
+
const requestIndex = rest.indexOf('--request');
|
|
26
|
+
const nonceIndex = rest.indexOf('--nonce');
|
|
27
|
+
const request = requestIndex >= 0 ? rest[requestIndex + 1] : undefined;
|
|
28
|
+
const nonce = nonceIndex >= 0 ? rest[nonceIndex + 1] : undefined;
|
|
29
|
+
if (!request || !nonce)
|
|
30
|
+
throw new Error('sandbox-control execute requires --request and --nonce');
|
|
31
|
+
await runSandboxControlExecutor(request, nonce);
|
|
11
32
|
return;
|
|
12
33
|
}
|
|
13
34
|
if (operation === 'client') {
|
|
14
35
|
const [family = '', ...commandArgs] = rest;
|
|
15
|
-
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
response = requestSandboxControl({ family, args: commandArgs });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (!(error instanceof SandboxControlClientError))
|
|
42
|
+
throw error;
|
|
43
|
+
process.stderr.write(`${error.detail.message}\n`);
|
|
44
|
+
process.exitCode = error.detail.retryable ? 75 : 1;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
16
47
|
process.stdout.write(response.stdout);
|
|
17
48
|
process.stderr.write(response.stderr);
|
|
18
|
-
|
|
49
|
+
if (response.phase === 'rejected') {
|
|
50
|
+
process.stderr.write(response.error?.message ?? response.stderr);
|
|
51
|
+
process.exitCode = response.error?.retryable ? 75 : 1;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
process.exitCode = response.exitCode ?? 1;
|
|
55
|
+
}
|
|
19
56
|
return;
|
|
20
57
|
}
|
|
21
|
-
throw new Error('Usage: agent-infra-internal sandbox-control serve --manifest <path> | client <family> [args...]');
|
|
58
|
+
throw new Error('Usage: agent-infra-internal sandbox-control serve --manifest <path> | execute --request <path> --nonce <nonce> | client <family> [args...]');
|
|
22
59
|
}
|
|
23
60
|
export { sandboxControl };
|
|
24
61
|
//# sourceMappingURL=sandbox-control.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { requestSandboxTaskCreate } from '../sandbox/control/client.js';
|
|
3
|
+
import { requestSandboxTaskCreate, SandboxControlClientError } from '../sandbox/control/client.js';
|
|
4
4
|
import { SANDBOX_CONTROL_MAX_BYTES } from '../sandbox/control/protocol.js';
|
|
5
5
|
import { validateTaskCreateCandidate } from '../task/create.js';
|
|
6
6
|
import { createTask } from '../task/create-service.js';
|
|
7
|
-
function failed(code, message) {
|
|
7
|
+
function failed(code, message, retryable = false) {
|
|
8
8
|
return {
|
|
9
|
-
status: 'failed', changed: false, task: { id: null, shortId: null }, issue: null,
|
|
10
|
-
operations: [], warnings: [], error: { code, message, retryable
|
|
9
|
+
status: retryable ? 'blocked' : 'failed', changed: false, task: { id: null, shortId: null }, issue: null,
|
|
10
|
+
operations: [], warnings: [], error: { code, message, retryable }
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
function output(result) {
|
|
@@ -31,6 +31,10 @@ function taskCreate(args) {
|
|
|
31
31
|
const candidate = validateTaskCreateCandidate(JSON.parse(fs.readFileSync(inputPath, 'utf8')));
|
|
32
32
|
if (process.env.AGENT_INFRA_CONTROL_TOKEN) {
|
|
33
33
|
const response = requestSandboxTaskCreate({ candidate });
|
|
34
|
+
if (response.phase === 'rejected') {
|
|
35
|
+
output(failed(response.error?.code ?? 'SANDBOX_CONTROL_REJECTED', response.error?.message ?? response.stderr, response.error?.retryable ?? false));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
34
38
|
process.stdout.write(response.stdout);
|
|
35
39
|
process.stderr.write(response.stderr);
|
|
36
40
|
process.exitCode = response.exitCode;
|
|
@@ -39,6 +43,10 @@ function taskCreate(args) {
|
|
|
39
43
|
output(createTask(candidate, { repoRoot: process.cwd() }));
|
|
40
44
|
}
|
|
41
45
|
catch (error) {
|
|
46
|
+
if (error instanceof SandboxControlClientError) {
|
|
47
|
+
output(failed(error.detail.code, error.detail.message, error.detail.retryable));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
42
50
|
const message = error instanceof Error ? error.message : String(error);
|
|
43
51
|
const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_INPUT_INVALID';
|
|
44
52
|
output(failed(code, message));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
2
|
-
import { abortCommitIntent, activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, completeCommitIntent, pauseOrchestration,
|
|
2
|
+
import { abortCommitIntent, activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, completeCommitIntent, pauseOrchestration, recoverCommitIntent, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, startCommitAttempt, statusCommitIntent, statusOrchestration, terminateCommitAttempt } from '../task/orchestration.js';
|
|
3
|
+
import { prepareCodexOrchestrationDelegation } from '../task/codex-orchestration.js';
|
|
3
4
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
4
5
|
const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|hook-stop|advance|pause|status|commit-start|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-terminate|commit-status> [options]\n';
|
|
5
6
|
function usageFailure(message) {
|
|
@@ -10,7 +11,7 @@ function usageFailure(message) {
|
|
|
10
11
|
process.stderr.write(USAGE);
|
|
11
12
|
process.exitCode = 2;
|
|
12
13
|
}
|
|
13
|
-
function taskOrchestration(args = []) {
|
|
14
|
+
async function taskOrchestration(args = []) {
|
|
14
15
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
15
16
|
process.stdout.write(USAGE);
|
|
16
17
|
return;
|
|
@@ -260,11 +261,11 @@ function taskOrchestration(args = []) {
|
|
|
260
261
|
usageFailure(`intent 'prepare' requires '${missing}'`);
|
|
261
262
|
return;
|
|
262
263
|
}
|
|
263
|
-
result =
|
|
264
|
+
result = await prepareCodexOrchestrationDelegation(taskRef, {
|
|
264
265
|
client: values['--client'],
|
|
265
266
|
requestedModel: values['--requested-model'],
|
|
266
267
|
requestedReasoningEffort: values['--requested-reasoning-effort']
|
|
267
|
-
}, coreOptions);
|
|
268
|
+
}, { orchestrationOptions: coreOptions });
|
|
268
269
|
}
|
|
269
270
|
else if (intent === 'hook-start') {
|
|
270
271
|
const missing = requireValues([
|
|
@@ -20,6 +20,7 @@ const ARTIFACT_TITLES = {
|
|
|
20
20
|
code: '实现报告',
|
|
21
21
|
'review-code': '代码审查',
|
|
22
22
|
'manual-validation': '人工验证报告',
|
|
23
|
+
'validation-run': '验证运行证据',
|
|
23
24
|
'pr-review': 'PR 审查报告'
|
|
24
25
|
};
|
|
25
26
|
function normalizeCommentContent(content) {
|
|
@@ -52,7 +53,7 @@ function renderTaskComment(content, taskId, agent) {
|
|
|
52
53
|
}
|
|
53
54
|
function artifactIdentity(artifact) {
|
|
54
55
|
const stem = path.basename(artifact, '.md');
|
|
55
|
-
const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|pr-review)(?:-r(\d+))?$/);
|
|
56
|
+
const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r(\d+))?$/);
|
|
56
57
|
if (!match)
|
|
57
58
|
throw new Error(`unsupported artifact '${artifact}'`);
|
|
58
59
|
const base = ARTIFACT_TITLES[match[1]];
|
|
@@ -1,34 +1,42 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
function byteSort(values) {
|
|
3
|
+
return [...values].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
|
|
4
|
+
}
|
|
1
5
|
function deriveReleasePhase(facts) {
|
|
2
6
|
if (!facts.localTag && !facts.localTagAncestor)
|
|
3
7
|
return 'unprepared';
|
|
8
|
+
if (facts.post.commit)
|
|
9
|
+
return facts.post.published ? 'complete' : 'post-prepared';
|
|
4
10
|
if (!facts.remoteBranch && !facts.remoteTag)
|
|
5
11
|
return 'prepared';
|
|
6
12
|
if (!facts.remoteBranch || !facts.remoteTag)
|
|
7
13
|
return 'partially-published';
|
|
8
14
|
if (facts.githubRelease !== true || facts.npm !== true || facts.homebrew !== true)
|
|
9
15
|
return 'published';
|
|
10
|
-
|
|
11
|
-
return 'post-pending';
|
|
12
|
-
return 'complete';
|
|
16
|
+
return 'post-pending';
|
|
13
17
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
18
|
+
function createPostConfirmation(version, post) {
|
|
19
|
+
if (!post.commit || !post.isHead || post.worktree.length || post.staged.length)
|
|
20
|
+
return null;
|
|
21
|
+
const canonical = {
|
|
22
|
+
version,
|
|
23
|
+
commit: post.commit,
|
|
24
|
+
branch: post.branch,
|
|
25
|
+
upstream: post.upstream,
|
|
26
|
+
remoteHead: post.remoteHead,
|
|
27
|
+
newVersion: post.newVersion,
|
|
28
|
+
changedPaths: byteSort(post.changedPaths),
|
|
29
|
+
demoInputSha256: post.demoInputSha256,
|
|
30
|
+
worktree: byteSort(post.worktree),
|
|
31
|
+
staged: byteSort(post.staged)
|
|
32
|
+
};
|
|
33
|
+
const sha256 = `sha256:${crypto.createHash('sha256').update(JSON.stringify(canonical), 'utf8').digest('hex')}`;
|
|
34
|
+
return { ...canonical, sha256 };
|
|
16
35
|
}
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (before.facts.localTagConflict)
|
|
22
|
-
return { status: 'failed', changed: false, before, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag ${before.tag} is not reachable from HEAD` } };
|
|
23
|
-
const allowed = input.action === 'prepare' ? before.phase === 'unprepared'
|
|
24
|
-
: input.action === 'publish' ? before.facts.localTag && ['prepared', 'partially-published'].includes(before.phase)
|
|
25
|
-
: ['published', 'post-pending'].includes(before.phase);
|
|
26
|
-
if (!allowed)
|
|
27
|
-
return { status: before.phase === 'complete' ? 'no-op' : 'failed', changed: false, before, snapshot: before, error: before.phase === 'complete' ? null : { code: 'RELEASE_PHASE_INVALID', message: `Cannot ${input.action} from ${before.phase}` } };
|
|
28
|
-
await input[input.action]?.();
|
|
29
|
-
const snapshot = releaseSnapshot(input.version, await input.inspect());
|
|
30
|
-
const progressed = snapshot.phase !== before.phase;
|
|
31
|
-
return { status: progressed ? 'applied' : 'blocked', changed: progressed, before, snapshot, error: progressed ? null : { code: 'RELEASE_PROGRESS_PENDING', message: `${input.action} has not reached the next observable phase` } };
|
|
36
|
+
function releaseSnapshot(version, facts) {
|
|
37
|
+
const snapshot = { version, tag: `v${version.replace(/^v/, '')}`, phase: deriveReleasePhase(facts), facts };
|
|
38
|
+
const postConfirmation = createPostConfirmation(version, facts.post);
|
|
39
|
+
return postConfirmation ? { ...snapshot, postConfirmation } : snapshot;
|
|
32
40
|
}
|
|
33
|
-
export { deriveReleasePhase, releaseSnapshot
|
|
41
|
+
export { createPostConfirmation, deriveReleasePhase, releaseSnapshot };
|
|
34
42
|
//# sourceMappingURL=workflow.js.map
|
|
@@ -2,9 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
|
-
import { execFileSync
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
8
7
|
import * as p from '@clack/prompts';
|
|
9
8
|
import pc from 'picocolors';
|
|
10
9
|
import { listAgentClientAdapters } from '../../agent-clients/registry.js';
|
|
@@ -17,10 +16,10 @@ import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSa
|
|
|
17
16
|
import { parseSandboxWorkspaceIdentity, resolveSandboxTarget, sameSandboxWorkspaceIdentity } from '../workspace-identity.js';
|
|
18
17
|
import { createSandboxCapabilityPlan, runSandboxHooks, runBoundedSandboxHookCommand } from '../agent-client-reconciler.js';
|
|
19
18
|
import { resolveTools, tmpfsSeedStagingPath, tmpfsSeedTargetPath, toolConfigDirCandidates } from '../tools.js';
|
|
20
|
-
import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts } from '../recovery.js';
|
|
19
|
+
import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts, startSandboxControlBroker } from '../recovery.js';
|
|
21
20
|
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
|
|
22
21
|
import { sandboxCoreBindMounts } from '../mounts.js';
|
|
23
|
-
import { assertSandboxTaskSource, materializeSandboxControl, materializeSandboxWorkspaceView } from '../workspace-view.js';
|
|
22
|
+
import { assertSandboxTaskSource, materializeSandboxControl, materializeSandboxWorkspaceView, prepareSandboxWorkspaceMountTargets } from '../workspace-view.js';
|
|
24
23
|
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.js';
|
|
25
24
|
import { validateSelinuxDisableEnv } from '../engines/selinux.js';
|
|
26
25
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
@@ -65,11 +64,6 @@ const MANAGED_GITHUB_GIT_CONFIG = [
|
|
|
65
64
|
'[url "https://github.com/"]',
|
|
66
65
|
'\tinsteadOf = git@github.com:'
|
|
67
66
|
];
|
|
68
|
-
function internalCliPath() {
|
|
69
|
-
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
70
|
-
const extension = path.extname(fileURLToPath(import.meta.url));
|
|
71
|
-
return path.resolve(directory, '..', '..', '..', 'bin', `internal-cli${extension}`);
|
|
72
|
-
}
|
|
73
67
|
function resolveToolDirs(config, tools, branch) {
|
|
74
68
|
return tools.map((tool) => {
|
|
75
69
|
const candidates = toolConfigDirCandidates(tool, config.project, branch);
|
|
@@ -895,8 +889,12 @@ export async function create(args) {
|
|
|
895
889
|
const requestedDescription = target.workspace.mode === 'task-bound'
|
|
896
890
|
? `task-bound:${target.workspace.taskId}`
|
|
897
891
|
: target.workspace.mode;
|
|
892
|
+
const recoveryTarget = target.workspace.mode === 'task-bound'
|
|
893
|
+
? target.workspace.taskId
|
|
894
|
+
: branch;
|
|
898
895
|
throw new Error(`SANDBOX_WORKSPACE_IDENTITY_CONFLICT: container '${name}' is ${existingDescription}, `
|
|
899
|
-
+ `but this request is ${requestedDescription}.
|
|
896
|
+
+ `but this request is ${requestedDescription}. `
|
|
897
|
+
+ `Run 'ai sandbox start --recreate ${recoveryTarget}' on the host, then retry.`);
|
|
900
898
|
}
|
|
901
899
|
}
|
|
902
900
|
message('Removing old container instance...');
|
|
@@ -962,6 +960,7 @@ export async function create(args) {
|
|
|
962
960
|
container,
|
|
963
961
|
identity: target.workspace
|
|
964
962
|
});
|
|
963
|
+
prepareSandboxWorkspaceMountTargets(worktree);
|
|
965
964
|
const control = materializeSandboxControl({
|
|
966
965
|
base: effectiveConfig.controlBase,
|
|
967
966
|
repoRoot: effectiveConfig.repoRoot,
|
|
@@ -982,6 +981,7 @@ export async function create(args) {
|
|
|
982
981
|
shellConfigHostDir: hostShellConfig.hostDir,
|
|
983
982
|
workspaceViewRoot: workspaceView.root,
|
|
984
983
|
controlDir: control.channelDir,
|
|
984
|
+
controlStatusDir: control.statusDir,
|
|
985
985
|
...(target.workspace.mode === 'task-bound'
|
|
986
986
|
? {
|
|
987
987
|
taskSource: assertSandboxTaskSource(effectiveConfig.repoRoot, target.workspace.taskId),
|
|
@@ -1052,7 +1052,11 @@ export async function create(args) {
|
|
|
1052
1052
|
'-e',
|
|
1053
1053
|
`AGENT_INFRA_CONTROL_TOKEN=${control.token}`,
|
|
1054
1054
|
'-e',
|
|
1055
|
+
`AGENT_INFRA_CONTROL_GENERATION=${control.generation}`,
|
|
1056
|
+
'-e',
|
|
1055
1057
|
'AGENT_INFRA_CONTROL_DIR=/run/agent-infra/control',
|
|
1058
|
+
'-e',
|
|
1059
|
+
'AGENT_INFRA_CONTROL_STATUS_DIR=/run/agent-infra/control-status',
|
|
1056
1060
|
'--label',
|
|
1057
1061
|
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
|
1058
1062
|
...coreVolumes,
|
|
@@ -1070,12 +1074,7 @@ export async function create(args) {
|
|
|
1070
1074
|
'/workspace',
|
|
1071
1075
|
effectiveConfig.imageName
|
|
1072
1076
|
]);
|
|
1073
|
-
|
|
1074
|
-
broker.once('error', () => {
|
|
1075
|
-
// Fresh readiness verification below remains the source of truth;
|
|
1076
|
-
// consume detached spawn errors so they cannot crash the command.
|
|
1077
|
-
});
|
|
1078
|
-
broker.unref();
|
|
1077
|
+
await startSandboxControlBroker(effectiveConfig.repoRoot, control.manifestPath);
|
|
1079
1078
|
createdTmpfsSeedPlan = tmpfsSeedPlan;
|
|
1080
1079
|
}
|
|
1081
1080
|
finally {
|
|
@@ -5,12 +5,13 @@ import * as p from '@clack/prompts';
|
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { loadConfig } from '../config.js';
|
|
7
7
|
import { safeNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.js';
|
|
8
|
-
import { detectEngine } from '../engine.js';
|
|
8
|
+
import { detectEngine, ENGINES } from '../engine.js';
|
|
9
9
|
import { hostJoin } from '../engines/wsl2-paths.js';
|
|
10
10
|
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
|
|
11
11
|
import { parseLabels } from './ls.js';
|
|
12
12
|
import { runEngine, runSafe } from '../shell.js';
|
|
13
13
|
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
|
|
14
|
+
import { createCleanPermit, formatWorktreeSnapshot, inspectWorktrees } from '../worktree-safety.js';
|
|
14
15
|
const USAGE = `Usage: ai sandbox prune [--dry-run]`;
|
|
15
16
|
function listChildDirs(base) {
|
|
16
17
|
if (!fs.existsSync(base)) {
|
|
@@ -83,12 +84,15 @@ export function collectOrphanGroups(config, tools, activeBranches) {
|
|
|
83
84
|
}
|
|
84
85
|
return groups;
|
|
85
86
|
}
|
|
86
|
-
export function removeOrphanGroups(config, groups) {
|
|
87
|
+
export function removeOrphanGroups(config, groups, permits, allowRegisteredPathFallback = false) {
|
|
87
88
|
let removedWorktrees = false;
|
|
88
89
|
for (const group of groups) {
|
|
89
90
|
for (const dir of group.dirs) {
|
|
90
91
|
if (group.kind === 'worktree') {
|
|
91
|
-
|
|
92
|
+
const permit = permits.get(path.resolve(dir));
|
|
93
|
+
if (!permit)
|
|
94
|
+
throw new Error(`Missing worktree removal permit: ${dir}`);
|
|
95
|
+
removeWorktreeDir(config.repoRoot, group.base, dir, permit, { allowRegisteredPathFallback });
|
|
92
96
|
removedWorktrees = true;
|
|
93
97
|
}
|
|
94
98
|
else {
|
|
@@ -115,7 +119,7 @@ function writeGroups(groups) {
|
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
|
-
export async function prune(args) {
|
|
122
|
+
export async function prune(args, dependencies = {}) {
|
|
119
123
|
const { values } = parseArgs({
|
|
120
124
|
args,
|
|
121
125
|
strict: true,
|
|
@@ -128,9 +132,9 @@ export async function prune(args) {
|
|
|
128
132
|
process.stdout.write(`${USAGE}\n`);
|
|
129
133
|
return;
|
|
130
134
|
}
|
|
131
|
-
const config = loadConfig();
|
|
132
|
-
const tools = [...createSandboxCapabilityPlan(config).cleanupInventory];
|
|
133
|
-
const engine = detectEngine(config);
|
|
135
|
+
const config = dependencies.config ?? loadConfig();
|
|
136
|
+
const tools = dependencies.tools ?? [...createSandboxCapabilityPlan(config).cleanupInventory];
|
|
137
|
+
const engine = dependencies.engine ?? detectEngine(config);
|
|
134
138
|
const psArgs = [
|
|
135
139
|
'ps',
|
|
136
140
|
'-a',
|
|
@@ -139,15 +143,27 @@ export async function prune(args) {
|
|
|
139
143
|
'--format',
|
|
140
144
|
'{{.Labels}}'
|
|
141
145
|
];
|
|
142
|
-
let labelsOutput;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
let labelsOutput = dependencies.labelsOutput;
|
|
147
|
+
if (labelsOutput === undefined) {
|
|
148
|
+
try {
|
|
149
|
+
labelsOutput = runEngine(engine, 'docker', psArgs);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
throw new Error('Unable to determine active sandbox branches: docker ps failed');
|
|
153
|
+
}
|
|
148
154
|
}
|
|
155
|
+
const confirm = dependencies.confirm ?? p.confirm;
|
|
156
|
+
const isCancel = dependencies.isCancel ?? p.isCancel;
|
|
149
157
|
const groups = collectOrphanGroups(config, tools, activeBranchesFromLabels(config, labelsOutput));
|
|
150
158
|
const count = orphanCount(groups);
|
|
159
|
+
const worktrees = groups.filter((group) => group.kind === 'worktree').flatMap((group) => group.dirs);
|
|
160
|
+
const inspections = inspectWorktrees(worktrees);
|
|
161
|
+
const blockers = inspections.filter((inspection) => inspection.status !== 'clean');
|
|
162
|
+
const permits = new Map();
|
|
163
|
+
for (const inspection of inspections) {
|
|
164
|
+
if (inspection.status === 'clean')
|
|
165
|
+
permits.set(inspection.snapshot.worktree, createCleanPermit(inspection.snapshot));
|
|
166
|
+
}
|
|
151
167
|
p.intro(pc.cyan(`Pruning orphaned sandbox state for ${config.project}`));
|
|
152
168
|
if (count === 0) {
|
|
153
169
|
p.log.success('No orphaned sandbox state dirs found');
|
|
@@ -155,19 +171,27 @@ export async function prune(args) {
|
|
|
155
171
|
return;
|
|
156
172
|
}
|
|
157
173
|
writeGroups(groups);
|
|
174
|
+
for (const blocker of blockers) {
|
|
175
|
+
p.log.error(blocker.status === 'failed'
|
|
176
|
+
? `${JSON.stringify(blocker.worktree)}: ${blocker.message}`
|
|
177
|
+
: formatWorktreeSnapshot(blocker.snapshot));
|
|
178
|
+
}
|
|
158
179
|
if (values['dry-run']) {
|
|
159
180
|
p.outro(pc.green('Dry run complete'));
|
|
160
181
|
return;
|
|
161
182
|
}
|
|
162
|
-
|
|
183
|
+
if (blockers.length > 0) {
|
|
184
|
+
throw new Error('Refusing to prune because worktree preflight found blocker(s); no orphan state was deleted');
|
|
185
|
+
}
|
|
186
|
+
const shouldRemove = await confirm({
|
|
163
187
|
message: `Remove ${count} orphaned sandbox state dirs?`,
|
|
164
188
|
initialValue: true
|
|
165
189
|
});
|
|
166
|
-
if (
|
|
190
|
+
if (isCancel(shouldRemove) || !shouldRemove) {
|
|
167
191
|
p.outro('Cancelled');
|
|
168
192
|
return;
|
|
169
193
|
}
|
|
170
|
-
const removedWorktrees = removeOrphanGroups(config, groups);
|
|
194
|
+
const removedWorktrees = removeOrphanGroups(config, groups, permits, engine === ENGINES.WSL2);
|
|
171
195
|
if (removedWorktrees) {
|
|
172
196
|
runSafe('git', ['-C', config.repoRoot, 'worktree', 'prune']);
|
|
173
197
|
}
|