@fitlab-ai/agent-infra 0.9.7 → 0.9.9
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
- package/dist/lib/decide.js +68 -14
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +3 -4
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +161 -288
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/pr-summary.js +53 -13
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +389 -311
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/container-identity.js +1 -1
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +145 -5
- package/dist/lib/sandbox/control/lifecycle.js +395 -73
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/control/state.js +27 -11
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/mounts.js +20 -5
- package/dist/lib/sandbox/recovery.js +78 -10
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/workspace-view.js +62 -40
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/server/process-state.js +51 -1
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +279 -17
- package/dist/lib/task/events.js +9 -5
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +199 -719
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +138 -59
- package/dist/lib/task/verification.js +15 -2
- package/dist/lib/task/workflow-warning-intents.js +54 -3
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
- package/lib/decide.ts +59 -14
- package/lib/git/workflow.ts +11 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +3 -4
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +32 -107
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/pr-summary.ts +65 -14
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +88 -4
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/container-identity.ts +3 -3
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +172 -6
- package/lib/sandbox/control/lifecycle.ts +465 -77
- package/lib/sandbox/control/protocol.ts +85 -9
- package/lib/sandbox/control/state.ts +35 -18
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/mounts.ts +23 -5
- package/lib/sandbox/recovery.ts +90 -9
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +73 -43
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/server/process-state.ts +48 -1
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +304 -19
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +719 -0
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +239 -922
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +145 -64
- package/lib/task/verification.ts +18 -3
- package/lib/task/workflow-warning-intents.ts +61 -4
- package/lib/task/write.ts +4 -1
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -4,66 +4,107 @@ import { parseArgs } from 'node:util';
|
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { loadConfig } from '../config.js';
|
|
7
|
-
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, shareBranchDir, shellConfigDirCandidates, worktreeDirCandidates } from '../constants.js';
|
|
7
|
+
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, shareBranchDir, shellConfigDirCandidates, worktreeDirCandidates } from '../constants.js';
|
|
8
8
|
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedVm } from '../engine.js';
|
|
9
9
|
import { pruneSandboxDanglingImages } from '../image-prune.js';
|
|
10
10
|
import { assertManagedPath, removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
|
|
11
11
|
import { runEngine, runOk, runOkEngine, runSafe, runSafeEngine } from '../shell.js';
|
|
12
|
-
import {
|
|
12
|
+
import { parseSandboxWorkspaceIdentity, resolveSandboxCleanupTarget, sameSandboxWorkspaceIdentity } from '../workspace-identity.js';
|
|
13
13
|
import { sandboxControlPaths, sandboxWorkspaceViewPaths } from '../workspace-view.js';
|
|
14
14
|
import { removeSandboxControlRoot, readSandboxControlManifest } from '../control/lifecycle.js';
|
|
15
15
|
import { inspectSandboxControlContainer } from '../control/container-identity.js';
|
|
16
16
|
import { toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.js';
|
|
17
17
|
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
|
|
18
18
|
import { fetchSandboxRows } from './list-running.js';
|
|
19
|
-
import {
|
|
20
|
-
import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectWorktrees, verifyWorktreePermit } from '../worktree-safety.js';
|
|
19
|
+
import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees, verifyWorktreePermit } from '../worktree-safety.js';
|
|
21
20
|
const USAGE = `Usage:
|
|
22
|
-
ai sandbox rm <branch>
|
|
23
|
-
ai sandbox rm --unbound [--dry-run] [--yes] Remove
|
|
21
|
+
ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
|
|
22
|
+
ai sandbox rm --unbound [--dry-run] [--yes] Remove completed task-bound and branch-only sandboxes; active, blocked, and archive tasks are protected
|
|
24
23
|
ai sandbox rm --purge Tear down ALL sandboxes for the project (containers, worktrees, image, VM)`;
|
|
25
24
|
export { assertManagedPath } from '../managed-fs.js';
|
|
26
25
|
function projectToolDirs(config, tools) {
|
|
27
26
|
return tools.flatMap((tool) => toolProjectDirCandidates(tool, config.project));
|
|
28
27
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
28
|
+
function isMissingPathError(error) {
|
|
29
|
+
return Boolean(error && typeof error === 'object' && 'code' in error
|
|
30
|
+
&& error.code === 'ENOENT');
|
|
31
|
+
}
|
|
32
|
+
export function sandboxManagedPathKey(candidate, platform = process.platform, resolveExistingPath = (resolved) => fs.realpathSync.native(resolved)) {
|
|
33
|
+
const resolved = path.resolve(candidate);
|
|
34
|
+
let identity = resolved;
|
|
35
|
+
try {
|
|
36
|
+
identity = resolveExistingPath(resolved);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (!isMissingPathError(error)) {
|
|
40
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: unable to determine managed path identity for '${resolved}'`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return platform === 'win32' ? identity.toLowerCase() : identity;
|
|
44
|
+
}
|
|
45
|
+
function recoveryContexts(config, target, worktrees) {
|
|
46
|
+
const candidates = worktreeDirCandidates(config, target.effectiveBranch).map((candidate) => path.resolve(candidate));
|
|
47
|
+
const existingCandidates = candidates.filter((candidate) => fs.existsSync(candidate));
|
|
48
|
+
if (existingCandidates.length !== 1)
|
|
49
|
+
return new Map();
|
|
50
|
+
const contexts = new Map();
|
|
51
|
+
for (const worktree of worktrees) {
|
|
52
|
+
const resolvedWorktree = path.resolve(worktree);
|
|
53
|
+
if (resolvedWorktree !== existingCandidates[0])
|
|
54
|
+
continue;
|
|
55
|
+
for (const root of target.controlRoots.filter((candidate) => fs.existsSync(candidate))) {
|
|
56
|
+
assertLegacyCandidateEvidence(root, config.controlBase, config, target.effectiveBranch, target.matchedContainers);
|
|
57
|
+
assertControlRootMatchesTarget(root, target.effectiveBranch, target.workspace);
|
|
58
|
+
}
|
|
59
|
+
contexts.set(resolvedWorktree, {
|
|
60
|
+
repoRoot: config.repoRoot,
|
|
61
|
+
worktreeBase: config.worktreeBase,
|
|
62
|
+
branch: target.effectiveBranch,
|
|
63
|
+
identitySource: target.workspace.mode,
|
|
64
|
+
taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return contexts;
|
|
68
|
+
}
|
|
69
|
+
function resolveRmTarget(config, tools, cleanupTarget, options = {}) {
|
|
70
|
+
assertValidBranchName(cleanupTarget.branch);
|
|
31
71
|
const engine = detectEngine(config);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
72
|
+
const branch = cleanupTarget.branch;
|
|
73
|
+
const effectiveBranch = branch;
|
|
74
|
+
const worktreeCandidates = worktreeDirCandidates(config, branch);
|
|
75
|
+
const toolCandidates = tools.map((tool) => ({
|
|
35
76
|
tool,
|
|
36
77
|
candidates: toolConfigDirCandidates(tool, config.project, branch)
|
|
37
78
|
}));
|
|
79
|
+
const shellCandidates = shellConfigDirCandidates(config, branch);
|
|
38
80
|
const existing = runEngine(engine, 'docker', ['ps', '-a', '--format', '{{.Names}}']).split('\n').filter(Boolean);
|
|
39
|
-
const matchedContainers =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
candidates: toolConfigDirCandidates(tool, config.project, effectiveBranch)
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const workspace = resolveSandboxTarget(effectiveBranch, config.repoRoot).workspace;
|
|
57
|
-
const identities = workspace.mode === 'task-bound'
|
|
58
|
-
? [workspace, { mode: 'branch-only' }]
|
|
59
|
-
: [workspace];
|
|
60
|
-
const containers = [...new Set([...containerNameCandidates(config, effectiveBranch), ...matchedContainers])];
|
|
81
|
+
const matchedContainers = options.discoveredContainers
|
|
82
|
+
? options.discoveredContainers.map((container) => {
|
|
83
|
+
if (!existing.includes(container)) {
|
|
84
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: discovered container '${container}' is no longer present`);
|
|
85
|
+
}
|
|
86
|
+
return container;
|
|
87
|
+
})
|
|
88
|
+
: containerNameCandidates(config, branch).filter((name) => existing.includes(name));
|
|
89
|
+
const workspace = cleanupTarget.workspace;
|
|
90
|
+
const identities = [workspace];
|
|
91
|
+
const containers = options.discoveredContainers
|
|
92
|
+
? matchedContainers
|
|
93
|
+
: [...new Set([...containerNameCandidates(config, effectiveBranch), ...matchedContainers])];
|
|
61
94
|
const controlRoots = containers.flatMap((container) => identities.map((identity) => sandboxControlPaths({
|
|
62
95
|
base: config.controlBase, project: config.project, container, identity
|
|
63
96
|
}).root));
|
|
64
97
|
const workspaceViewRoots = containers.flatMap((container) => identities.map((identity) => sandboxWorkspaceViewPaths({
|
|
65
98
|
base: config.workspaceViewBase, project: config.project, container, identity
|
|
66
99
|
}).root));
|
|
100
|
+
const managedPathCandidates = [...new Set([
|
|
101
|
+
...worktreeCandidates,
|
|
102
|
+
...toolCandidates.flatMap(({ candidates }) => candidates),
|
|
103
|
+
...shellCandidates,
|
|
104
|
+
shareBranchDir(config, branch),
|
|
105
|
+
...controlRoots,
|
|
106
|
+
...workspaceViewRoots
|
|
107
|
+
].map((candidate) => path.resolve(candidate)))];
|
|
67
108
|
return {
|
|
68
109
|
branch,
|
|
69
110
|
effectiveBranch,
|
|
@@ -71,24 +112,157 @@ function resolveRmTarget(config, tools, branch) {
|
|
|
71
112
|
matchedContainers,
|
|
72
113
|
existingWorktrees: worktreeCandidates.filter((candidate) => fs.existsSync(candidate)),
|
|
73
114
|
toolCandidates,
|
|
115
|
+
managedPathCandidates,
|
|
74
116
|
workspace,
|
|
75
117
|
controlRoots: [...new Set(controlRoots)],
|
|
76
118
|
workspaceViewRoots: [...new Set(workspaceViewRoots)]
|
|
77
119
|
};
|
|
78
120
|
}
|
|
121
|
+
function inspectNamedContainerIdentity(target, container) {
|
|
122
|
+
const raw = runSafeEngine(target.engine, 'docker', [
|
|
123
|
+
'inspect', '--format', '{{json .}}', container
|
|
124
|
+
]).trim();
|
|
125
|
+
let parsed;
|
|
126
|
+
try {
|
|
127
|
+
parsed = JSON.parse(raw);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new Error(`SANDBOX_WORKSPACE_IDENTITY_UNKNOWN: container '${container}' inspection is invalid`);
|
|
131
|
+
}
|
|
132
|
+
const value = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
|
|
133
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
134
|
+
throw new Error(`SANDBOX_WORKSPACE_IDENTITY_UNKNOWN: container '${container}' inspection is incomplete`);
|
|
135
|
+
}
|
|
136
|
+
const candidate = value;
|
|
137
|
+
if (typeof candidate.Id !== 'string' || !candidate.Id
|
|
138
|
+
|| !candidate.Config?.Labels || typeof candidate.Config.Labels !== 'object'
|
|
139
|
+
|| Array.isArray(candidate.Config.Labels)
|
|
140
|
+
|| Object.values(candidate.Config.Labels).some((value) => typeof value !== 'string')) {
|
|
141
|
+
throw new Error(`SANDBOX_WORKSPACE_IDENTITY_UNKNOWN: container '${container}' inspection is incomplete`);
|
|
142
|
+
}
|
|
143
|
+
return { id: candidate.Id, labels: candidate.Config.Labels };
|
|
144
|
+
}
|
|
145
|
+
function assertMatchedContainerIdentities(config, target) {
|
|
146
|
+
const current = new Map();
|
|
147
|
+
for (const container of target.matchedContainers) {
|
|
148
|
+
const observation = inspectNamedContainerIdentity(target, container);
|
|
149
|
+
const stringLabels = observation.labels;
|
|
150
|
+
const branch = stringLabels[sandboxBranchLabel(config)];
|
|
151
|
+
if (branch !== target.branch) {
|
|
152
|
+
throw new Error(`SANDBOX_WORKSPACE_IDENTITY_CONFLICT: container '${container}' branch is ${JSON.stringify(branch ?? null)}, `
|
|
153
|
+
+ `but this request is ${JSON.stringify(target.branch)}`);
|
|
154
|
+
}
|
|
155
|
+
const identity = parseSandboxWorkspaceIdentity(stringLabels, {
|
|
156
|
+
mode: sandboxWorkspaceModeLabel(config),
|
|
157
|
+
taskId: sandboxTaskIdLabel(config)
|
|
158
|
+
});
|
|
159
|
+
if (identity.mode === 'legacy-invalid' || !sameSandboxWorkspaceIdentity(identity, target.workspace)) {
|
|
160
|
+
const existingDescription = identity.mode === 'task-bound'
|
|
161
|
+
? `task-bound:${identity.taskId}`
|
|
162
|
+
: identity.mode;
|
|
163
|
+
const requestedDescription = target.workspace.mode === 'task-bound'
|
|
164
|
+
? `task-bound:${target.workspace.taskId}`
|
|
165
|
+
: target.workspace.mode;
|
|
166
|
+
throw new Error(`SANDBOX_WORKSPACE_IDENTITY_CONFLICT: container '${container}' is ${existingDescription}, `
|
|
167
|
+
+ `but this request is ${requestedDescription}`);
|
|
168
|
+
}
|
|
169
|
+
current.set(container, observation);
|
|
170
|
+
}
|
|
171
|
+
return current;
|
|
172
|
+
}
|
|
173
|
+
function controlRootContainer(config, root) {
|
|
174
|
+
const relative = path.relative(path.join(config.controlBase, config.project), root);
|
|
175
|
+
const [container, identity, ...extra] = relative.split(path.sep);
|
|
176
|
+
if (!container || !identity || extra.length > 0) {
|
|
177
|
+
throw new Error(`SANDBOX_CONTROL_TARGET_EVIDENCE_INVALID: ${root}`);
|
|
178
|
+
}
|
|
179
|
+
return container;
|
|
180
|
+
}
|
|
79
181
|
function assertControlRootMatchesTarget(root, effectiveBranch, workspace) {
|
|
80
182
|
const manifestPath = path.join(root, 'manifest.json');
|
|
81
|
-
if (!fs.existsSync(manifestPath))
|
|
183
|
+
if (!fs.existsSync(manifestPath)) {
|
|
184
|
+
if (workspace.mode === 'task-bound') {
|
|
185
|
+
throw new Error(`SANDBOX_CONTROL_TARGET_EVIDENCE_MISSING: ${root}`);
|
|
186
|
+
}
|
|
82
187
|
return;
|
|
188
|
+
}
|
|
83
189
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
84
190
|
const identityMatches = workspace.mode === 'task-bound'
|
|
85
191
|
? manifest.mode === 'task-bound' && manifest.taskId === workspace.taskId
|
|
86
192
|
: manifest.mode === 'branch-only';
|
|
87
|
-
|
|
88
|
-
if (manifest.branch !== effectiveBranch || (!identityMatches && !branchOnlyFallback)) {
|
|
193
|
+
if (manifest.branch !== effectiveBranch || !identityMatches) {
|
|
89
194
|
throw new Error(`SANDBOX_CONTROL_TARGET_MISMATCH: ${root}`);
|
|
90
195
|
}
|
|
91
196
|
}
|
|
197
|
+
function preflightRmTarget(config, target) {
|
|
198
|
+
const current = assertMatchedContainerIdentities(config, target);
|
|
199
|
+
const manifests = [];
|
|
200
|
+
for (const root of target.controlRoots.filter((candidate) => fs.existsSync(candidate))) {
|
|
201
|
+
assertLegacyCandidateEvidence(root, config.controlBase, config, target.effectiveBranch, target.matchedContainers);
|
|
202
|
+
assertControlRootMatchesTarget(root, target.effectiveBranch, target.workspace);
|
|
203
|
+
const manifestPath = path.join(root, 'manifest.json');
|
|
204
|
+
if (!fs.existsSync(manifestPath))
|
|
205
|
+
continue;
|
|
206
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
207
|
+
const rootContainer = controlRootContainer(config, root);
|
|
208
|
+
if (manifest.container !== rootContainer) {
|
|
209
|
+
throw new Error(`SANDBOX_CONTROL_TARGET_MISMATCH: ${root}`);
|
|
210
|
+
}
|
|
211
|
+
const observed = current.get(manifest.container);
|
|
212
|
+
if (observed && (observed.id !== manifest.containerIdentity.id
|
|
213
|
+
|| Object.entries(manifest.containerIdentity.labels).some(([key, value]) => observed.labels[key] !== value))) {
|
|
214
|
+
throw new Error(`SANDBOX_CONTROL_TARGET_MISMATCH: ${root}`);
|
|
215
|
+
}
|
|
216
|
+
manifests.push(manifest);
|
|
217
|
+
}
|
|
218
|
+
if (target.workspace.mode !== 'task-bound')
|
|
219
|
+
return;
|
|
220
|
+
const matched = new Set(target.matchedContainers);
|
|
221
|
+
const manifestContainers = manifests.map((manifest) => manifest.container);
|
|
222
|
+
if (matched.size > 0 && manifestContainers.some((container) => !matched.has(container))) {
|
|
223
|
+
throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH: manifest container is outside the cleanup target');
|
|
224
|
+
}
|
|
225
|
+
for (const container of matched) {
|
|
226
|
+
const matches = manifests.filter((manifest) => manifest.container === container);
|
|
227
|
+
if (matches.length !== 1) {
|
|
228
|
+
throw new Error(`SANDBOX_CONTROL_TARGET_EVIDENCE_MISSING: container '${container}'`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (matched.size === 0 && manifests.length > 0) {
|
|
232
|
+
const supported = new Set(containerNameCandidates(config, target.effectiveBranch));
|
|
233
|
+
if (manifests.some((manifest) => !supported.has(manifest.container))) {
|
|
234
|
+
throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH: manifest container is outside the cleanup target');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function assertCleanupGroupPathsDoNotOverlap(groups) {
|
|
239
|
+
const owners = new Map();
|
|
240
|
+
for (const group of groups) {
|
|
241
|
+
for (const candidate of group.target.managedPathCandidates ?? []) {
|
|
242
|
+
const key = sandboxManagedPathKey(candidate);
|
|
243
|
+
const existingBranch = owners.get(key);
|
|
244
|
+
if (existingBranch && existingBranch !== group.cleanupTarget.branch) {
|
|
245
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: managed path '${candidate}' is shared by branches `
|
|
246
|
+
+ `${JSON.stringify(existingBranch)} and ${JSON.stringify(group.cleanupTarget.branch)}`);
|
|
247
|
+
}
|
|
248
|
+
owners.set(key, group.cleanupTarget.branch);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function assertWorktreeBranchesMatchGroups(groups, inspections) {
|
|
253
|
+
const byPath = new Map(inspections.flatMap((inspection) => inspection.status === 'failed'
|
|
254
|
+
? []
|
|
255
|
+
: [[sandboxManagedPathKey(inspection.snapshot.worktree), inspection.snapshot.branch]]));
|
|
256
|
+
for (const group of groups) {
|
|
257
|
+
for (const worktree of group.target.existingWorktrees) {
|
|
258
|
+
const registeredBranch = byPath.get(sandboxManagedPathKey(worktree));
|
|
259
|
+
if (registeredBranch && registeredBranch !== group.cleanupTarget.branch) {
|
|
260
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: worktree '${worktree}' is registered to branch `
|
|
261
|
+
+ `${JSON.stringify(registeredBranch)}, but cleanup targets ${JSON.stringify(group.cleanupTarget.branch)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
92
266
|
function assertLegacyCandidateEvidence(candidate, base, config, effectiveBranch, matchedContainers) {
|
|
93
267
|
const relative = path.relative(path.join(base, config.project), candidate);
|
|
94
268
|
const [container, identity] = relative.split(path.sep);
|
|
@@ -128,7 +302,7 @@ async function removeExactSandboxContainer(engine, manifest, timeoutMs) {
|
|
|
128
302
|
throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${before.reason}`);
|
|
129
303
|
if (before.state === 'absent')
|
|
130
304
|
return;
|
|
131
|
-
if (before.running && !runOkEngine(engine, 'docker', ['stop', manifest.containerIdentity.id], { timeout: remaining() })) {
|
|
305
|
+
if (before.running && !runOkEngine(engine, 'docker', ['stop', '--timeout', '1', manifest.containerIdentity.id], { timeout: remaining() })) {
|
|
132
306
|
const afterStop = await inspect();
|
|
133
307
|
if (afterStop.state !== 'absent' && afterStop.state !== 'found') {
|
|
134
308
|
throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterStop.reason}`);
|
|
@@ -195,8 +369,14 @@ function cleanPermits(inspections) {
|
|
|
195
369
|
}
|
|
196
370
|
return permits;
|
|
197
371
|
}
|
|
198
|
-
async function authorizeWorktrees(worktrees, { allowDirtyDiscard, assumeYes }, { interactive = Boolean(process.stdin.isTTY), confirm = p.confirm, isCancel = p.isCancel } = {}) {
|
|
199
|
-
const inspections =
|
|
372
|
+
async function authorizeWorktrees(worktrees, { allowDirtyDiscard, assumeYes }, { interactive = Boolean(process.stdin.isTTY), confirm = p.confirm, isCancel = p.isCancel, recovery = new Map() } = {}) {
|
|
373
|
+
const inspections = worktrees.map((worktree) => {
|
|
374
|
+
const inspection = inspectWorktrees([worktree])[0];
|
|
375
|
+
const context = recovery.get(path.resolve(worktree));
|
|
376
|
+
return inspection.status === 'failed' && context
|
|
377
|
+
? inspectRecoveredWorktree(worktree, context)
|
|
378
|
+
: inspection;
|
|
379
|
+
});
|
|
200
380
|
const failures = inspections.filter((inspection) => inspection.status === 'failed');
|
|
201
381
|
if (failures.length > 0)
|
|
202
382
|
throw new Error(`Unable to inspect worktree(s):\n${blockerMessage(failures)}`);
|
|
@@ -219,20 +399,28 @@ async function authorizeWorktrees(worktrees, { allowDirtyDiscard, assumeYes }, {
|
|
|
219
399
|
return permits;
|
|
220
400
|
}
|
|
221
401
|
async function rmOne(config, tools, branch, options = {}) {
|
|
222
|
-
const target = options.target ?? resolveRmTarget(config, tools, branch);
|
|
402
|
+
const target = options.target ?? resolveRmTarget(config, tools, options.cleanupTarget ?? resolveSandboxCleanupTarget(branch, config.repoRoot));
|
|
223
403
|
const { effectiveBranch, engine, matchedContainers, existingWorktrees, toolCandidates } = target;
|
|
224
404
|
const { workspace, controlRoots, workspaceViewRoots } = target;
|
|
405
|
+
preflightRmTarget(config, target);
|
|
225
406
|
const confirm = options.prompt?.confirm ?? p.confirm;
|
|
226
407
|
const isCancel = options.prompt?.isCancel ?? p.isCancel;
|
|
227
408
|
if (!options.quiet) {
|
|
228
409
|
p.intro(pc.cyan(`Removing sandbox for ${branch}`));
|
|
229
410
|
}
|
|
411
|
+
const recovery = options.permits
|
|
412
|
+
? new Map()
|
|
413
|
+
: recoveryContexts(config, target, existingWorktrees);
|
|
230
414
|
const permits = options.permits
|
|
231
415
|
? new Map(options.permits)
|
|
232
416
|
: await authorizeWorktrees(existingWorktrees, {
|
|
233
417
|
allowDirtyDiscard: options.allowDirtyDiscard ?? true,
|
|
234
418
|
assumeYes: Boolean(options.assumeYes)
|
|
235
|
-
},
|
|
419
|
+
}, {
|
|
420
|
+
...options.prompt,
|
|
421
|
+
interactive: options.interactive ?? Boolean(process.stdin.isTTY),
|
|
422
|
+
recovery
|
|
423
|
+
});
|
|
236
424
|
for (const worktree of existingWorktrees) {
|
|
237
425
|
const permit = permits.get(path.resolve(worktree));
|
|
238
426
|
if (!permit)
|
|
@@ -251,19 +439,27 @@ async function rmOne(config, tools, branch, options = {}) {
|
|
|
251
439
|
p.outro('Cancelled');
|
|
252
440
|
return;
|
|
253
441
|
}
|
|
442
|
+
preflightRmTarget(config, target);
|
|
254
443
|
const coordinatedContainers = new Set();
|
|
255
444
|
for (const root of controlRoots.filter((candidate) => fs.existsSync(candidate))) {
|
|
256
445
|
assertLegacyCandidateEvidence(root, config.controlBase, config, effectiveBranch, matchedContainers);
|
|
257
446
|
assertControlRootMatchesTarget(root, effectiveBranch, workspace);
|
|
258
|
-
const
|
|
447
|
+
const manifestPath = path.join(root, 'manifest.json');
|
|
448
|
+
if (!fs.existsSync(manifestPath))
|
|
449
|
+
continue;
|
|
450
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
259
451
|
coordinatedContainers.add(manifest.container);
|
|
260
452
|
await removeSandboxControlRoot(root, {
|
|
261
453
|
inspectContainer: (timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }),
|
|
262
454
|
removeContainer: (timeoutMs) => removeExactSandboxContainer(engine, manifest, timeoutMs)
|
|
263
455
|
});
|
|
456
|
+
removeEmptyManagedParent(path.join(config.controlBase, config.project), root);
|
|
264
457
|
}
|
|
265
458
|
const uncoordinatedContainers = matchedContainers.filter((name) => !coordinatedContainers.has(name));
|
|
266
459
|
if (uncoordinatedContainers.length > 0) {
|
|
460
|
+
if (workspace.mode === 'task-bound') {
|
|
461
|
+
throw new Error(`SANDBOX_CONTROL_TARGET_EVIDENCE_MISSING: container '${uncoordinatedContainers[0]}'`);
|
|
462
|
+
}
|
|
267
463
|
const spinner = p.spinner();
|
|
268
464
|
spinner.start(`Stopping container(s): ${uncoordinatedContainers.join(', ')}`);
|
|
269
465
|
for (const name of uncoordinatedContainers) {
|
|
@@ -500,18 +696,80 @@ async function rmPurge(config, tools, prompt = {}) {
|
|
|
500
696
|
}
|
|
501
697
|
async function rmUnbound(config, tools, options) {
|
|
502
698
|
const engine = detectEngine(config);
|
|
503
|
-
const { running, nonRunning } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
504
|
-
const
|
|
699
|
+
const { running, nonRunning } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) });
|
|
700
|
+
const rows = [...running, ...nonRunning];
|
|
505
701
|
p.intro(pc.cyan(`Removing sandboxes not bound to an active task for ${config.project}`));
|
|
506
|
-
if (
|
|
702
|
+
if (rows.length === 0) {
|
|
507
703
|
p.outro('No removable sandboxes: every container is bound to an active task (or none exist)');
|
|
508
704
|
return;
|
|
509
705
|
}
|
|
510
|
-
const
|
|
511
|
-
|
|
706
|
+
const candidates = rows.map((row) => {
|
|
707
|
+
if (!row.branch || !row.workspaceMode || row.workspaceMode === 'legacy-invalid') {
|
|
708
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: container '${row.name}' has incomplete workspace identity`);
|
|
709
|
+
}
|
|
710
|
+
if (row.workspaceMode === 'task-bound') {
|
|
711
|
+
if (!row.taskId) {
|
|
712
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: container '${row.name}' has no task id`);
|
|
713
|
+
}
|
|
714
|
+
const cleanupTarget = resolveSandboxCleanupTarget(row.taskId, config.repoRoot, { allowProtected: true });
|
|
715
|
+
if (cleanupTarget.branch !== row.branch
|
|
716
|
+
|| cleanupTarget.workspace.mode !== 'task-bound'
|
|
717
|
+
|| cleanupTarget.workspace.taskId !== row.taskId) {
|
|
718
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: container '${row.name}' task identity conflicts with task.md`);
|
|
719
|
+
}
|
|
720
|
+
return { row, cleanupTarget };
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
row,
|
|
724
|
+
cleanupTarget: {
|
|
725
|
+
requestedRef: row.branch,
|
|
726
|
+
branch: row.branch,
|
|
727
|
+
workspace: { mode: 'branch-only' },
|
|
728
|
+
taskState: 'branch-only'
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
});
|
|
732
|
+
const branchIdentities = new Map();
|
|
733
|
+
const groupedCandidates = new Map();
|
|
734
|
+
for (const candidate of candidates) {
|
|
735
|
+
const identity = candidate.cleanupTarget.workspace.mode === 'task-bound'
|
|
736
|
+
? `task-bound:${candidate.cleanupTarget.workspace.taskId}`
|
|
737
|
+
: 'branch-only';
|
|
738
|
+
const existingIdentity = branchIdentities.get(candidate.cleanupTarget.branch);
|
|
739
|
+
if (existingIdentity && existingIdentity !== identity) {
|
|
740
|
+
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: branch '${candidate.cleanupTarget.branch}' has conflicting workspace identities`);
|
|
741
|
+
}
|
|
742
|
+
branchIdentities.set(candidate.cleanupTarget.branch, identity);
|
|
743
|
+
const groupKey = `${candidate.cleanupTarget.branch}\0${identity}`;
|
|
744
|
+
const group = groupedCandidates.get(groupKey) ?? [];
|
|
745
|
+
group.push(candidate);
|
|
746
|
+
groupedCandidates.set(groupKey, group);
|
|
747
|
+
}
|
|
748
|
+
const groups = [...groupedCandidates.values()].map((groupCandidates) => {
|
|
749
|
+
const cleanupTarget = groupCandidates[0].cleanupTarget;
|
|
750
|
+
const target = resolveRmTarget(config, tools, cleanupTarget, {
|
|
751
|
+
discoveredContainers: groupCandidates.map(({ row }) => row.name)
|
|
752
|
+
});
|
|
753
|
+
preflightRmTarget(config, target);
|
|
754
|
+
return { candidates: groupCandidates, cleanupTarget, target };
|
|
755
|
+
});
|
|
756
|
+
assertCleanupGroupPathsDoNotOverlap(groups);
|
|
757
|
+
const removableGroups = groups.filter(({ cleanupTarget }) => cleanupTarget.taskState === 'completed' || cleanupTarget.taskState === 'branch-only');
|
|
758
|
+
const removable = removableGroups.flatMap(({ candidates: groupCandidates }) => groupCandidates);
|
|
759
|
+
for (const { candidates: groupCandidates, cleanupTarget } of groups.filter(({ cleanupTarget }) => cleanupTarget.taskState !== 'completed' && cleanupTarget.taskState !== 'branch-only')) {
|
|
760
|
+
for (const { row } of groupCandidates) {
|
|
761
|
+
p.log.message(`Skipped protected sandbox ${row.name} (${cleanupTarget.taskState})`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (removableGroups.length === 0) {
|
|
765
|
+
p.outro('No removable sandboxes: every container is bound to a protected task (or none exist)');
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const inspections = inspectWorktrees(removableGroups.flatMap(({ target }) => target.existingWorktrees));
|
|
769
|
+
assertWorktreeBranchesMatchGroups(removableGroups, inspections);
|
|
512
770
|
const blockers = inspectionBlockers(inspections);
|
|
513
771
|
const permits = cleanPermits(inspections);
|
|
514
|
-
for (const row of removable) {
|
|
772
|
+
for (const { row } of removable) {
|
|
515
773
|
p.log.message(`${row.name} ${row.branch}`);
|
|
516
774
|
}
|
|
517
775
|
if (blockers.length > 0)
|
|
@@ -527,25 +785,28 @@ async function rmUnbound(config, tools, options) {
|
|
|
527
785
|
throw new Error('Refusing to remove sandboxes without confirmation in a non-interactive shell; pass --yes to proceed.');
|
|
528
786
|
}
|
|
529
787
|
const failures = [];
|
|
530
|
-
|
|
788
|
+
let failedRows = 0;
|
|
789
|
+
for (const group of removableGroups) {
|
|
531
790
|
try {
|
|
532
|
-
await rmOne(config, tools,
|
|
791
|
+
await rmOne(config, tools, group.cleanupTarget.branch, {
|
|
533
792
|
assumeYes: options.assumeYes,
|
|
534
793
|
quiet: true,
|
|
535
|
-
target:
|
|
794
|
+
target: group.target,
|
|
795
|
+
cleanupTarget: group.cleanupTarget,
|
|
536
796
|
permits,
|
|
537
797
|
allowDirtyDiscard: false
|
|
538
798
|
});
|
|
539
799
|
}
|
|
540
800
|
catch (error) {
|
|
541
|
-
|
|
801
|
+
failedRows += group.candidates.length;
|
|
802
|
+
failures.push({ branch: group.cleanupTarget.branch, message: error instanceof Error ? error.message : String(error) });
|
|
542
803
|
}
|
|
543
804
|
}
|
|
544
805
|
if (failures.length > 0) {
|
|
545
806
|
for (const failure of failures) {
|
|
546
807
|
p.log.error(`Failed to remove '${failure.branch}': ${failure.message}`);
|
|
547
808
|
}
|
|
548
|
-
throw new Error(`Removed ${removable.length -
|
|
809
|
+
throw new Error(`Removed ${removable.length - failedRows}/${removable.length} sandbox(es); ${failures.length} failed`);
|
|
549
810
|
}
|
|
550
811
|
p.outro(pc.green(`Removed ${removable.length} sandbox(es)`));
|
|
551
812
|
}
|
|
@@ -595,8 +856,8 @@ export async function rm(args) {
|
|
|
595
856
|
});
|
|
596
857
|
return;
|
|
597
858
|
}
|
|
598
|
-
const
|
|
599
|
-
await rmOne(config, tools,
|
|
859
|
+
const cleanupTarget = resolveSandboxCleanupTarget(positionals[0] ?? '', config.repoRoot);
|
|
860
|
+
await rmOne(config, tools, cleanupTarget.branch, { cleanupTarget });
|
|
600
861
|
}
|
|
601
862
|
export { authorizeWorktrees, rmOne, rmPurge };
|
|
602
863
|
//# sourceMappingURL=rm.js.map
|