@fitlab-ai/agent-infra 0.12.1 → 0.12.2
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 +0 -2
- package/README.zh-CN.md +0 -2
- package/dist/bin/internal-cli.js +2 -17
- package/dist/lib/agent-clients/adapter.js +0 -13
- package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
- package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
- package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
- package/dist/lib/agent-clients/adapters/codex.js +0 -52
- package/dist/lib/agent-clients/adapters/opencode.js +0 -5
- package/dist/lib/agent-clients/adapters/traecli.js +0 -5
- package/dist/lib/agent-clients/config.js +19 -54
- package/dist/lib/agent-clients/registry.js +1 -12
- package/dist/lib/agent-clients/schema.js +0 -18
- package/dist/lib/agent-clients/tokens.js +1 -2
- package/dist/lib/agent-clients/types.js +0 -1
- package/dist/lib/decide.js +2 -0
- package/dist/lib/git/worktree-identity.js +1 -9
- package/dist/lib/internal/agent-client.js +2 -62
- package/dist/lib/internal/cli-route-inventory.js +1 -5
- package/dist/lib/internal/manual-validation.js +18 -10
- package/dist/lib/internal/task-create.js +1 -1
- package/dist/lib/internal/task-event.js +1 -8
- package/dist/lib/internal/task-finalization.js +0 -1
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +4 -20
- package/dist/lib/internal/task-review.js +4 -4
- package/dist/lib/platform/comment-safety.js +1 -7
- package/dist/lib/platform/in-label-sync.js +1 -14
- package/dist/lib/platform/issue-comments.js +1 -16
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/operation-coordinator.js +4 -2
- package/dist/lib/platform/pr-change-report.js +1 -4
- package/dist/lib/platform/pr-checks.js +2 -8
- package/dist/lib/platform/provider-validation.js +0 -20
- package/dist/lib/platform/pull-requests.js +73 -84
- package/dist/lib/platform/releases.js +1 -43
- package/dist/lib/platform/verification-required.js +0 -2
- package/dist/lib/platform/verification-sync.js +1 -79
- package/dist/lib/process-data/normalize.js +1 -185
- package/dist/lib/process-data/privacy.js +1 -16
- package/dist/lib/process-data/sources.js +2 -279
- package/dist/lib/render.js +0 -12
- package/dist/lib/run/index.js +15 -10
- package/dist/lib/run/skills.js +0 -3
- package/dist/lib/sandbox/commands/create.js +40 -53
- package/dist/lib/sandbox/commands/list-running.js +0 -18
- package/dist/lib/sandbox/commands/start.js +7 -8
- package/dist/lib/sandbox/constants.js +0 -6
- package/dist/lib/sandbox/control/client.js +0 -1
- package/dist/lib/sandbox/control/container-identity.js +1 -16
- package/dist/lib/sandbox/control/executor.js +0 -4
- package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
- package/dist/lib/sandbox/control/lifecycle.js +21 -37
- package/dist/lib/sandbox/control/native-file-lock.js +0 -3
- package/dist/lib/sandbox/control/server.js +18 -38
- package/dist/lib/sandbox/control/state.js +1 -9
- package/dist/lib/sandbox/dockerfile.js +0 -6
- package/dist/lib/sandbox/engine.js +0 -14
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
- package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
- package/dist/lib/sandbox/recovery.js +24 -26
- package/dist/lib/sandbox/removal.js +11 -519
- package/dist/lib/sandbox/task-resolver.js +0 -41
- package/dist/lib/sandbox/tools.js +1 -4
- package/dist/lib/sandbox/workspace-view.js +1 -2
- package/dist/lib/server/display.js +0 -6
- package/dist/lib/task/activity-intent.js +45 -40
- package/dist/lib/task/activity-log.js +9 -16
- package/dist/lib/task/artifact-command.js +1 -1
- package/dist/lib/task/artifact-lifecycle.js +61 -63
- package/dist/lib/task/artifact-name.js +0 -6
- package/dist/lib/task/artifact-operations.js +3 -3
- package/dist/lib/task/capabilities.js +4 -5
- package/dist/lib/task/commands/log.js +0 -6
- package/dist/lib/task/commands/status.js +17 -60
- package/dist/lib/task/commit-operation.js +28 -39
- package/dist/lib/task/completion-facts.js +56 -43
- package/dist/lib/task/control-authority.js +23 -256
- package/dist/lib/task/control-recovery.js +3 -5
- package/dist/lib/task/create.js +2 -4
- package/dist/lib/task/decision-intents.js +8 -9
- package/dist/lib/task/delivery-target.js +1 -40
- package/dist/lib/task/events.js +84 -107
- package/dist/lib/task/finalization.js +44 -8
- package/dist/lib/task/ledger-intents.js +1 -1
- package/dist/lib/task/lifecycle-execution.js +38 -33
- package/dist/lib/task/lifecycle.js +49 -41
- package/dist/lib/task/local-artifact-finalization.js +1 -4
- package/dist/lib/task/manual-validation-evidence.js +1 -37
- package/dist/lib/task/platform-operation-recovery.js +67 -24
- package/dist/lib/task/pr-delivery-fact.js +2 -5
- package/dist/lib/task/qualification-confirmation.js +5 -7
- package/dist/lib/task/resolve-ref.js +1 -25
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +1 -21
- package/dist/lib/task/review-fingerprint.js +0 -4
- package/dist/lib/task/secure-fs.js +0 -56
- package/dist/lib/task/short-id.js +1 -39
- package/dist/lib/task/skill-scope.js +0 -1
- package/dist/lib/task/verification-engine.js +58 -282
- package/dist/lib/task/verification.js +6 -3
- package/dist/lib/task/write.js +11 -1
- package/lib/agent-clients/adapter.ts +0 -76
- package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
- package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
- package/lib/agent-clients/adapters/claude-code.ts +0 -9
- package/lib/agent-clients/adapters/codex.ts +0 -56
- package/lib/agent-clients/adapters/opencode.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +0 -5
- package/lib/agent-clients/config.ts +20 -61
- package/lib/agent-clients/registry.ts +1 -34
- package/lib/agent-clients/schema.ts +0 -18
- package/lib/agent-clients/tokens.ts +1 -2
- package/lib/agent-clients/types.ts +0 -15
- package/lib/decide.ts +1 -0
- package/lib/git/worktree-identity.ts +0 -10
- package/lib/internal/agent-client.ts +2 -70
- package/lib/internal/cli-route-inventory.ts +1 -4
- package/lib/internal/manual-validation.ts +16 -9
- package/lib/internal/task-create.ts +0 -1
- package/lib/internal/task-event.ts +1 -5
- package/lib/internal/task-finalization.ts +0 -1
- package/lib/internal/task-lifecycle.ts +3 -5
- package/lib/internal/task-operation-registry.ts +3 -19
- package/lib/internal/task-review.ts +4 -4
- package/lib/platform/comment-safety.ts +0 -10
- package/lib/platform/github-provider.ts +0 -2
- package/lib/platform/in-label-sync.ts +0 -16
- package/lib/platform/issue-comments.ts +0 -21
- package/lib/platform/issues.ts +0 -2
- package/lib/platform/operation-coordinator.ts +4 -2
- package/lib/platform/pr-change-report.ts +0 -5
- package/lib/platform/pr-checks.ts +1 -10
- package/lib/platform/pr-review.ts +1 -1
- package/lib/platform/provider-bridge.ts +0 -1
- package/lib/platform/provider-loader.ts +1 -5
- package/lib/platform/provider-validation.ts +1 -49
- package/lib/platform/pull-requests.ts +64 -99
- package/lib/platform/releases.ts +1 -40
- package/lib/platform/verification-required.ts +0 -2
- package/lib/platform/verification-sync.ts +0 -94
- package/lib/process-data/normalize.ts +1 -186
- package/lib/process-data/privacy.ts +2 -19
- package/lib/process-data/sources.ts +3 -283
- package/lib/render.ts +0 -15
- package/lib/run/index.ts +13 -7
- package/lib/run/skills.ts +0 -4
- package/lib/sandbox/commands/create.ts +37 -74
- package/lib/sandbox/commands/list-running.ts +0 -19
- package/lib/sandbox/commands/start.ts +7 -10
- package/lib/sandbox/constants.ts +0 -8
- package/lib/sandbox/control/client.ts +0 -1
- package/lib/sandbox/control/container-identity.ts +1 -16
- package/lib/sandbox/control/executor.ts +1 -5
- package/lib/sandbox/control/identity-sentinel.ts +0 -8
- package/lib/sandbox/control/lifecycle.ts +18 -51
- package/lib/sandbox/control/native-file-lock.ts +0 -8
- package/lib/sandbox/control/protocol.ts +0 -2
- package/lib/sandbox/control/server.ts +13 -39
- package/lib/sandbox/control/state.ts +1 -10
- package/lib/sandbox/dockerfile.ts +0 -11
- package/lib/sandbox/engine.ts +0 -17
- package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
- package/lib/sandbox/engines/wsl2-paths.ts +0 -4
- package/lib/sandbox/recovery.ts +24 -26
- package/lib/sandbox/removal.ts +11 -696
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/task-resolver.ts +0 -48
- package/lib/sandbox/tools.ts +1 -5
- package/lib/sandbox/workspace-view.ts +2 -4
- package/lib/server/display.ts +0 -14
- package/lib/server/process-control.ts +1 -1
- package/lib/task/activity-intent.ts +36 -44
- package/lib/task/activity-log.ts +9 -22
- package/lib/task/artifact-command.ts +1 -1
- package/lib/task/artifact-lifecycle.ts +58 -67
- package/lib/task/artifact-name.ts +0 -7
- package/lib/task/artifact-operations.ts +3 -3
- package/lib/task/capabilities.ts +5 -6
- package/lib/task/commands/log.ts +0 -7
- package/lib/task/commands/status.ts +14 -79
- package/lib/task/commit-operation.ts +29 -51
- package/lib/task/completion-facts.ts +45 -42
- package/lib/task/control-authority.ts +27 -317
- package/lib/task/control-recovery.ts +2 -5
- package/lib/task/create.ts +2 -5
- package/lib/task/decision-intents.ts +5 -5
- package/lib/task/delivery-target.ts +0 -40
- package/lib/task/events.ts +89 -123
- package/lib/task/finalization.ts +41 -9
- package/lib/task/ledger-intents.ts +1 -1
- package/lib/task/lifecycle-execution.ts +37 -89
- package/lib/task/lifecycle.ts +48 -44
- package/lib/task/local-artifact-finalization.ts +0 -7
- package/lib/task/manual-validation-evidence.ts +1 -47
- package/lib/task/platform-operation-recovery.ts +50 -26
- package/lib/task/pr-delivery-fact.ts +2 -11
- package/lib/task/qualification-confirmation.ts +6 -6
- package/lib/task/resolve-ref.ts +0 -23
- package/lib/task/review-command.ts +2 -2
- package/lib/task/review-finalization.ts +1 -27
- package/lib/task/review-fingerprint.ts +0 -8
- package/lib/task/secure-fs.ts +0 -42
- package/lib/task/short-id.ts +0 -43
- package/lib/task/skill-scope.ts +0 -1
- package/lib/task/verification-engine.ts +56 -287
- package/lib/task/verification.ts +5 -5
- package/lib/task/write.ts +12 -1
- package/package.json +2 -2
- package/templates/.agents/README.en.md +1 -3
- package/templates/.agents/README.zh-CN.md +0 -2
- package/templates/.agents/rules/README.en.md +0 -1
- package/templates/.agents/rules/README.zh-CN.md +0 -1
- package/templates/.agents/rules/commit-and-pr.en.md +1 -1
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +3 -3
- package/templates/.agents/rules/task-management.zh-CN.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/commit/SKILL.en.md +2 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
- package/templates/.agents/templates/task.en.md +1 -0
- package/templates/.agents/templates/task.zh-CN.md +1 -0
- package/templates/.claude/settings.json +0 -24
- package/templates/.codex/hooks.json +0 -50
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
- package/dist/lib/internal/task-orchestration.js +0 -71
- package/dist/lib/platform/verification.js +0 -6
- package/dist/lib/run/prompt.js +0 -2
- package/dist/lib/sandbox/engines/authority.js +0 -289
- package/dist/lib/task/delegation-receipts.js +0 -410
- package/dist/lib/task/orchestration.js +0 -1233
- package/dist/lib/task/verification-checks.js +0 -9
- package/dist/lib/task/workspace-snapshot.js +0 -121
- package/dist/lifecycle-build-manifest.json +0 -6
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
- package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
- package/lib/internal/task-orchestration.ts +0 -74
- package/lib/platform/verification.ts +0 -11
- package/lib/run/prompt.ts +0 -1
- package/lib/sandbox/engines/authority.ts +0 -365
- package/lib/task/delegation-receipts.ts +0 -547
- package/lib/task/orchestration.ts +0 -1651
- package/lib/task/verification-checks.ts +0 -10
- package/lib/task/workspace-snapshot.ts +0 -144
- package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
- package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
- package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
- package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
- package/templates/.claude/commands/run-task.en.md +0 -8
- package/templates/.claude/commands/run-task.zh-CN.md +0 -8
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
- package/templates/.opencode/commands/run-task.en.md +0 -11
- package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
3
|
import * as p from '@clack/prompts';
|
|
5
4
|
import pc from 'picocolors';
|
|
6
5
|
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, shareBranchDir, shellConfigDirCandidates, worktreeDirCandidates } from './constants.js';
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import { assertManagedPath,
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import { detectEngine } from './engine.js';
|
|
7
|
+
import './image-prune.js';
|
|
8
|
+
import { assertManagedPath, removeWorktreeDir } from './managed-fs.js';
|
|
9
|
+
import { runEngine, runOk, runSafe, runSafeEngine } from './shell.js';
|
|
10
|
+
import { resolveSandboxCleanupTarget, resolveSandboxTarget } from './workspace-identity.js';
|
|
12
11
|
import { sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import { commandForSandboxAuthority } from './engines/authority.js';
|
|
16
|
-
import { acquireSandboxResourceLock } from './control/native-file-lock.js';
|
|
12
|
+
import './control/lifecycle.js';
|
|
13
|
+
import './control/container-identity.js';
|
|
17
14
|
import { toolConfigDirCandidates, toolProjectDirCandidates } from './tools.js';
|
|
18
|
-
import
|
|
15
|
+
import '../server/process-state.js';
|
|
19
16
|
import { releaseStaleShortIdRegistry } from '../task/short-id.js';
|
|
20
17
|
import { fetchSandboxRows } from './commands/list-running.js';
|
|
21
|
-
import
|
|
22
|
-
import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees
|
|
18
|
+
import './task-cleanup.js';
|
|
19
|
+
import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees } from './worktree-safety.js';
|
|
23
20
|
function projectToolDirs(config, tools) {
|
|
24
21
|
return tools.flatMap((tool) => toolProjectDirCandidates(tool, config.project));
|
|
25
22
|
}
|
|
@@ -61,105 +58,6 @@ function addOwnerWriteToDirectories(root) {
|
|
|
61
58
|
function isPermissionRemovalError(error) {
|
|
62
59
|
return ['EACCES', 'EPERM', 'ENOTEMPTY'].includes(removalErrorCode(error) ?? '');
|
|
63
60
|
}
|
|
64
|
-
function canonicalPath(input) {
|
|
65
|
-
try {
|
|
66
|
-
return fs.realpathSync.native(input);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return path.resolve(input);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function isMissingTaskRecordError(error) {
|
|
73
|
-
return error instanceof Error && /^Task not found: TASK-\d{8}-\d{6}$/.test(error.message);
|
|
74
|
-
}
|
|
75
|
-
function digestCleanupValue(value) {
|
|
76
|
-
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
77
|
-
}
|
|
78
|
-
function removalTargetDigest(config, target) {
|
|
79
|
-
return digestCleanupValue({
|
|
80
|
-
project: config.project,
|
|
81
|
-
branch: target.effectiveBranch,
|
|
82
|
-
workspace: target.workspace,
|
|
83
|
-
controlRoots: target.controlRoots.map((candidate) => path.resolve(candidate)),
|
|
84
|
-
workspaceViewRoots: target.workspaceViewRoots.map((candidate) => path.resolve(candidate)),
|
|
85
|
-
managedPathCandidates: (target.managedPathCandidates ?? []).map((candidate) => path.resolve(candidate)).sort()
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
function removalPermitDigest(permits) {
|
|
89
|
-
return digestCleanupValue([...permits.entries()]
|
|
90
|
-
.map(([worktree, permit]) => [path.resolve(worktree), permit.mode, permit.snapshot.identity])
|
|
91
|
-
.sort(([left], [right]) => String(left).localeCompare(String(right))));
|
|
92
|
-
}
|
|
93
|
-
function removalPermitCommits(permits) {
|
|
94
|
-
return [...permits.entries()]
|
|
95
|
-
.map(([worktree, permit]) => ({
|
|
96
|
-
path: path.resolve(worktree),
|
|
97
|
-
mode: permit.mode,
|
|
98
|
-
snapshot: permit.snapshot
|
|
99
|
-
}))
|
|
100
|
-
.sort((left, right) => left.path.localeCompare(right.path));
|
|
101
|
-
}
|
|
102
|
-
function removalTargetCommit(config, target, permits, removeWorktree, removeBranch, removeShare) {
|
|
103
|
-
return {
|
|
104
|
-
branch: target.effectiveBranch,
|
|
105
|
-
project: config.project,
|
|
106
|
-
controlRoot: path.resolve(target.controlRoots[0] ?? path.join(config.controlBase, config.project)),
|
|
107
|
-
targetDigest: removalTargetDigest(config, target),
|
|
108
|
-
permitDigest: removalPermitDigest(permits),
|
|
109
|
-
removeWorktree,
|
|
110
|
-
removeBranch,
|
|
111
|
-
removeShare,
|
|
112
|
-
worktreePaths: target.existingWorktrees.map((candidate) => path.resolve(candidate)).sort(),
|
|
113
|
-
workspaceViewPaths: target.workspaceViewRoots.map((candidate) => path.resolve(candidate)).sort(),
|
|
114
|
-
toolPaths: target.toolCandidates.flatMap(({ candidates }) => candidates.map((candidate) => path.resolve(candidate))).sort(),
|
|
115
|
-
shellPaths: shellConfigDirCandidates(config, target.effectiveBranch).map((candidate) => path.resolve(candidate)).sort(),
|
|
116
|
-
sharePath: path.resolve(shareBranchDir(config, target.effectiveBranch)),
|
|
117
|
-
permits: removalPermitCommits(permits)
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function assertRemovalSelectionMatches(expected, actual) {
|
|
121
|
-
const normalize = (value) => ({
|
|
122
|
-
...value,
|
|
123
|
-
controlRoot: '',
|
|
124
|
-
permits: value.permits
|
|
125
|
-
});
|
|
126
|
-
if (JSON.stringify(normalize(expected)) !== JSON.stringify(normalize(actual))) {
|
|
127
|
-
throw new Error('SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH');
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function advanceRemovalJournals(project, target, targetDigest, phase, resourceLocks) {
|
|
131
|
-
return listSandboxRemovalJournals({ branch: target.effectiveBranch, project, targetDigest }).map((journal) => {
|
|
132
|
-
if (sandboxRemovalPhaseIndex(journal.phase) >= sandboxRemovalPhaseIndex(phase))
|
|
133
|
-
return journal;
|
|
134
|
-
const lock = resourceLocks.get(journal.target.controlRoot);
|
|
135
|
-
if (!lock)
|
|
136
|
-
throw new Error('SANDBOX_CONTROL_REMOVAL_LOCK_MISMATCH');
|
|
137
|
-
return advanceSandboxRemovalJournalToPhase(journal, phase, lock);
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
function prepareRemovalAction(project, target, targetDigest, startPhase, completedPhase, resourceLocks) {
|
|
141
|
-
const journals = listSandboxRemovalJournals({
|
|
142
|
-
branch: target.effectiveBranch,
|
|
143
|
-
project,
|
|
144
|
-
targetDigest
|
|
145
|
-
});
|
|
146
|
-
const startIndex = sandboxRemovalPhaseIndex(startPhase);
|
|
147
|
-
const completedIndex = sandboxRemovalPhaseIndex(completedPhase);
|
|
148
|
-
const shouldRun = journals.length === 0
|
|
149
|
-
|| journals.some((journal) => sandboxRemovalPhaseIndex(journal.phase) < completedIndex);
|
|
150
|
-
const recovering = journals.some((journal) => {
|
|
151
|
-
const index = sandboxRemovalPhaseIndex(journal.phase);
|
|
152
|
-
return index >= startIndex && index < completedIndex;
|
|
153
|
-
});
|
|
154
|
-
advanceRemovalJournals(project, target, targetDigest, startPhase, resourceLocks);
|
|
155
|
-
return { run: shouldRun, recovering };
|
|
156
|
-
}
|
|
157
|
-
function assertRemovalPathsAbsent(paths) {
|
|
158
|
-
const remaining = paths.filter((candidate) => fs.existsSync(candidate));
|
|
159
|
-
if (remaining.length > 0) {
|
|
160
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${remaining[0]}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
61
|
function assertBranchRemovalIdentity(config, branch, permits) {
|
|
164
62
|
if (!runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', `refs/heads/${branch}`]))
|
|
165
63
|
return;
|
|
@@ -174,412 +72,6 @@ function assertBranchRemovalIdentity(config, branch, permits) {
|
|
|
174
72
|
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
175
73
|
}
|
|
176
74
|
}
|
|
177
|
-
function removalTombstonePath(targetDigest, kind, source) {
|
|
178
|
-
const sourceDigest = createHash('sha256')
|
|
179
|
-
.update(`${kind}\0${path.resolve(source)}`)
|
|
180
|
-
.digest('hex')
|
|
181
|
-
.slice(0, 24);
|
|
182
|
-
return path.join(path.dirname(path.resolve(source)), `.agent-infra-removal-${targetDigest.slice(0, 16)}-${kind}-${sourceDigest}`);
|
|
183
|
-
}
|
|
184
|
-
const REMOVAL_TOMBSTONE_MARKER = '.agent-infra-removal-ownership.json';
|
|
185
|
-
const REMOVAL_TOMBSTONE_PAYLOAD = 'payload';
|
|
186
|
-
function removalSourceClaimPath(source, ownership) {
|
|
187
|
-
const claimDigest = createHash('sha256')
|
|
188
|
-
.update(JSON.stringify(ownership))
|
|
189
|
-
.digest('hex')
|
|
190
|
-
.slice(0, 24);
|
|
191
|
-
return path.join(source, `.agent-infra-removal-source-${claimDigest}.json`);
|
|
192
|
-
}
|
|
193
|
-
function removalSourceIdentity(source) {
|
|
194
|
-
const stat = fs.lstatSync(source, { bigint: true });
|
|
195
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.dev === 0n || stat.ino === 0n) {
|
|
196
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
197
|
-
}
|
|
198
|
-
return { dev: String(stat.dev), ino: String(stat.ino) };
|
|
199
|
-
}
|
|
200
|
-
function sameRemovalSourceIdentity(left, right) {
|
|
201
|
-
return left.dev === right.dev && left.ino === right.ino;
|
|
202
|
-
}
|
|
203
|
-
function removalSourceClaimMatches(source, ownership, identity) {
|
|
204
|
-
try {
|
|
205
|
-
const claimPath = removalSourceClaimPath(source, ownership);
|
|
206
|
-
const claimStat = fs.lstatSync(claimPath);
|
|
207
|
-
if (!claimStat.isFile() || claimStat.isSymbolicLink())
|
|
208
|
-
return false;
|
|
209
|
-
const record = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
|
|
210
|
-
return record.version === ownership.version
|
|
211
|
-
&& record.targetDigest === ownership.targetDigest
|
|
212
|
-
&& record.permitDigest === ownership.permitDigest
|
|
213
|
-
&& record.kind === ownership.kind
|
|
214
|
-
&& typeof record.source === 'string'
|
|
215
|
-
&& path.resolve(record.source) === path.resolve(ownership.source)
|
|
216
|
-
&& record.sourceIdentity?.dev === identity.dev
|
|
217
|
-
&& record.sourceIdentity?.ino === identity.ino;
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
function isOwnedRemovalSourceClaim(source, ownership) {
|
|
224
|
-
try {
|
|
225
|
-
const claimPath = removalSourceClaimPath(source, ownership);
|
|
226
|
-
const claimStat = fs.lstatSync(claimPath);
|
|
227
|
-
if (!claimStat.isFile() || claimStat.isSymbolicLink())
|
|
228
|
-
return false;
|
|
229
|
-
const record = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
|
|
230
|
-
return record.version === ownership.version
|
|
231
|
-
&& record.targetDigest === ownership.targetDigest
|
|
232
|
-
&& record.permitDigest === ownership.permitDigest
|
|
233
|
-
&& record.kind === ownership.kind
|
|
234
|
-
&& typeof record.source === 'string'
|
|
235
|
-
&& path.resolve(record.source) === path.resolve(ownership.source);
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function removeOwnedSourceClaim(source, ownership) {
|
|
242
|
-
try {
|
|
243
|
-
const claimPath = removalSourceClaimPath(source, ownership);
|
|
244
|
-
if (isOwnedRemovalSourceClaim(source, ownership))
|
|
245
|
-
fs.unlinkSync(claimPath);
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
// Preserve the source when the claim cannot be revalidated.
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function readRemovalTombstoneRecord(root, tombstone) {
|
|
252
|
-
assertManagedPath(root, tombstone);
|
|
253
|
-
try {
|
|
254
|
-
const tombstoneStat = fs.lstatSync(tombstone);
|
|
255
|
-
const markerStat = fs.lstatSync(removalTombstoneMarker(tombstone));
|
|
256
|
-
if (!tombstoneStat.isDirectory() || tombstoneStat.isSymbolicLink()
|
|
257
|
-
|| !markerStat.isFile() || markerStat.isSymbolicLink())
|
|
258
|
-
return null;
|
|
259
|
-
const record = JSON.parse(fs.readFileSync(removalTombstoneMarker(tombstone), 'utf8'));
|
|
260
|
-
if (record.version !== 1
|
|
261
|
-
|| typeof record.targetDigest !== 'string'
|
|
262
|
-
|| typeof record.permitDigest !== 'string'
|
|
263
|
-
|| typeof record.kind !== 'string'
|
|
264
|
-
|| typeof record.source !== 'string'
|
|
265
|
-
|| record.sourceIdentity?.dev === undefined
|
|
266
|
-
|| record.sourceIdentity.ino === undefined)
|
|
267
|
-
return null;
|
|
268
|
-
return record;
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function removalTombstonePayload(tombstone) {
|
|
275
|
-
return path.join(tombstone, REMOVAL_TOMBSTONE_PAYLOAD);
|
|
276
|
-
}
|
|
277
|
-
function removalTombstoneMarker(tombstone) {
|
|
278
|
-
return path.join(tombstone, REMOVAL_TOMBSTONE_MARKER);
|
|
279
|
-
}
|
|
280
|
-
function isOwnedRemovalTombstone(root, tombstone, ownership) {
|
|
281
|
-
const record = readRemovalTombstoneRecord(root, tombstone);
|
|
282
|
-
return record !== null
|
|
283
|
-
&& record.version === 1
|
|
284
|
-
&& record.targetDigest === ownership.targetDigest
|
|
285
|
-
&& record.permitDigest === ownership.permitDigest
|
|
286
|
-
&& record.kind === ownership.kind
|
|
287
|
-
&& typeof record.source === 'string'
|
|
288
|
-
&& path.resolve(record.source) === path.resolve(ownership.source);
|
|
289
|
-
}
|
|
290
|
-
function isOwnedRemovalPayload(root, tombstone, ownership) {
|
|
291
|
-
const record = readRemovalTombstoneRecord(root, tombstone);
|
|
292
|
-
if (!record || record.targetDigest !== ownership.targetDigest
|
|
293
|
-
|| record.permitDigest !== ownership.permitDigest
|
|
294
|
-
|| record.kind !== ownership.kind
|
|
295
|
-
|| path.resolve(record.source) !== path.resolve(ownership.source))
|
|
296
|
-
return false;
|
|
297
|
-
try {
|
|
298
|
-
const payload = removalTombstonePayload(tombstone);
|
|
299
|
-
const payloadIdentity = removalSourceIdentity(payload);
|
|
300
|
-
return sameRemovalSourceIdentity(payloadIdentity, record.sourceIdentity)
|
|
301
|
-
&& removalSourceClaimMatches(payload, ownership, record.sourceIdentity);
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function assertOwnedRemovalTombstone(root, tombstone, ownership) {
|
|
308
|
-
if (!isOwnedRemovalTombstone(root, tombstone, ownership)) {
|
|
309
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${ownership.source}`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
function shouldStageManagedRemoval(source, tombstone, recovering) {
|
|
313
|
-
return fs.existsSync(source) || (recovering && fs.existsSync(tombstone));
|
|
314
|
-
}
|
|
315
|
-
function stageManagedRemoval(root, source, tombstone, recovering, ownership) {
|
|
316
|
-
assertManagedPath(root, source);
|
|
317
|
-
assertManagedPath(root, tombstone);
|
|
318
|
-
const sourceExists = fs.existsSync(source);
|
|
319
|
-
const tombstoneExists = fs.existsSync(tombstone);
|
|
320
|
-
if (tombstoneExists) {
|
|
321
|
-
assertOwnedRemovalTombstone(root, tombstone, ownership);
|
|
322
|
-
if (sourceExists) {
|
|
323
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
324
|
-
}
|
|
325
|
-
if (recovering) {
|
|
326
|
-
if (!isOwnedRemovalPayload(root, tombstone, ownership)) {
|
|
327
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
328
|
-
}
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
332
|
-
}
|
|
333
|
-
if (recovering) {
|
|
334
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${source}`);
|
|
335
|
-
}
|
|
336
|
-
if (!sourceExists)
|
|
337
|
-
return;
|
|
338
|
-
// The operator authorizes cleanup of this managed target after execution stops.
|
|
339
|
-
// Path checks detect conflicts; they do not provide an atomic source-instance move.
|
|
340
|
-
const expectedIdentity = removalSourceIdentity(source);
|
|
341
|
-
const claimPath = removalSourceClaimPath(source, ownership);
|
|
342
|
-
try {
|
|
343
|
-
fs.writeFileSync(claimPath, `${JSON.stringify({ ...ownership, sourceIdentity: expectedIdentity })}\n`, {
|
|
344
|
-
encoding: 'utf8',
|
|
345
|
-
mode: 0o600,
|
|
346
|
-
flag: 'wx'
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
catch {
|
|
350
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
351
|
-
}
|
|
352
|
-
if (!sameRemovalSourceIdentity(expectedIdentity, removalSourceIdentity(source))) {
|
|
353
|
-
removeOwnedSourceClaim(source, ownership);
|
|
354
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
355
|
-
}
|
|
356
|
-
let moved = false;
|
|
357
|
-
try {
|
|
358
|
-
fs.mkdirSync(tombstone, { mode: 0o700 });
|
|
359
|
-
fs.writeFileSync(removalTombstoneMarker(tombstone), `${JSON.stringify({ ...ownership, sourceIdentity: expectedIdentity })}\n`, {
|
|
360
|
-
encoding: 'utf8',
|
|
361
|
-
mode: 0o600,
|
|
362
|
-
flag: 'wx'
|
|
363
|
-
});
|
|
364
|
-
fs.renameSync(source, removalTombstonePayload(tombstone));
|
|
365
|
-
moved = true;
|
|
366
|
-
if (!isOwnedRemovalPayload(root, tombstone, ownership)) {
|
|
367
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
catch (error) {
|
|
371
|
-
if (!moved)
|
|
372
|
-
removeOwnedSourceClaim(source, ownership);
|
|
373
|
-
throw error;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
function cleanupManagedTombstones(root, targetDigest, permitDigest, kind, sources) {
|
|
377
|
-
for (const source of sources) {
|
|
378
|
-
const tombstone = removalTombstonePath(targetDigest, kind, source);
|
|
379
|
-
if (!fs.existsSync(tombstone))
|
|
380
|
-
continue;
|
|
381
|
-
const ownership = { version: 1, targetDigest, permitDigest, kind, source };
|
|
382
|
-
if (!isOwnedRemovalTombstone(root, tombstone, ownership))
|
|
383
|
-
continue;
|
|
384
|
-
if (!isOwnedRemovalPayload(root, tombstone, ownership)) {
|
|
385
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
|
|
386
|
-
}
|
|
387
|
-
removeManagedDir(root, tombstone);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
function branchRemovalTombstoneRef(targetDigest) {
|
|
391
|
-
return `refs/agent-infra/sandbox-removal/${targetDigest}/branch`;
|
|
392
|
-
}
|
|
393
|
-
function branchPermitHead(branch, permits) {
|
|
394
|
-
const heads = new Set([...permits.values()]
|
|
395
|
-
.filter((permit) => permit.snapshot.branch === branch)
|
|
396
|
-
.map((permit) => permit.snapshot.head));
|
|
397
|
-
return heads.size === 1 ? [...heads][0] : null;
|
|
398
|
-
}
|
|
399
|
-
function stageBranchRemoval(config, branch, targetDigest, permits, recovering) {
|
|
400
|
-
const expectedHead = branchPermitHead(branch, permits);
|
|
401
|
-
if (!expectedHead)
|
|
402
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
403
|
-
const branchName = `refs/heads/${branch}`;
|
|
404
|
-
const tombstone = branchRemovalTombstoneRef(targetDigest);
|
|
405
|
-
const branchExists = runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', branchName]);
|
|
406
|
-
const tombstoneExists = runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', tombstone]);
|
|
407
|
-
if (branchExists && tombstoneExists) {
|
|
408
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
409
|
-
}
|
|
410
|
-
if (recovering) {
|
|
411
|
-
if (branchExists && !tombstoneExists) {
|
|
412
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${branch}`);
|
|
413
|
-
}
|
|
414
|
-
if (tombstoneExists && runSafe('git', ['-C', config.repoRoot, 'rev-parse', tombstone]) !== expectedHead) {
|
|
415
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
416
|
-
}
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (!branchExists)
|
|
420
|
-
return;
|
|
421
|
-
assertBranchRemovalIdentity(config, branch, permits);
|
|
422
|
-
const actualHead = runSafe('git', ['-C', config.repoRoot, 'rev-parse', branchName]);
|
|
423
|
-
if (!runOk('git', ['-C', config.repoRoot, 'update-ref', tombstone, actualHead, '0'.repeat(40)])) {
|
|
424
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
425
|
-
}
|
|
426
|
-
if (!runOk('git', ['-C', config.repoRoot, 'update-ref', '-d', branchName, actualHead])) {
|
|
427
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
function cleanupBranchTombstone(config, branch, targetDigest, permits) {
|
|
431
|
-
const tombstone = branchRemovalTombstoneRef(targetDigest);
|
|
432
|
-
if (!runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', tombstone]))
|
|
433
|
-
return;
|
|
434
|
-
const expectedHead = branchPermitHead(branch, permits);
|
|
435
|
-
if (!expectedHead || runSafe('git', ['-C', config.repoRoot, 'rev-parse', tombstone]) !== expectedHead) {
|
|
436
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
437
|
-
}
|
|
438
|
-
if (!runOk('git', ['-C', config.repoRoot, 'update-ref', '-d', tombstone, expectedHead])) {
|
|
439
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
function worktreeRegistration(config, worktree) {
|
|
443
|
-
const lines = runSafe('git', ['-C', config.repoRoot, 'worktree', 'list', '--porcelain']).split('\n');
|
|
444
|
-
let current = null;
|
|
445
|
-
const flush = () => {
|
|
446
|
-
if (!current || path.resolve(current.path) !== path.resolve(worktree))
|
|
447
|
-
return null;
|
|
448
|
-
return { head: current.head, branch: current.branch };
|
|
449
|
-
};
|
|
450
|
-
for (const line of lines) {
|
|
451
|
-
if (line.startsWith('worktree ')) {
|
|
452
|
-
const previous = flush();
|
|
453
|
-
if (previous)
|
|
454
|
-
return previous;
|
|
455
|
-
current = { path: line.slice('worktree '.length), head: '', branch: null };
|
|
456
|
-
}
|
|
457
|
-
else if (current && line.startsWith('HEAD ')) {
|
|
458
|
-
current.head = line.slice('HEAD '.length);
|
|
459
|
-
}
|
|
460
|
-
else if (current && line.startsWith('branch ')) {
|
|
461
|
-
current.branch = line.slice('branch '.length);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return flush();
|
|
465
|
-
}
|
|
466
|
-
function assertWorktreeRegistrationIdentity(config, worktree, permit) {
|
|
467
|
-
const registration = worktreeRegistration(config, worktree);
|
|
468
|
-
if (!registration)
|
|
469
|
-
return;
|
|
470
|
-
if (registration.head !== permit.snapshot.head
|
|
471
|
-
|| registration.branch !== `refs/heads/${permit.snapshot.branch}`) {
|
|
472
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${worktree}`);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
function pruneWorktreeRegistration(config, worktree) {
|
|
476
|
-
runSafe('git', ['-C', config.repoRoot, 'worktree', 'prune']);
|
|
477
|
-
if (worktreeRegistration(config, worktree)) {
|
|
478
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${worktree}`);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
function stageWorktreeRemoval(config, worktree, permit, tombstone, recovering, ownership) {
|
|
482
|
-
assertManagedPath(config.worktreeBase, worktree);
|
|
483
|
-
assertManagedPath(config.worktreeBase, tombstone);
|
|
484
|
-
const worktreeExists = fs.existsSync(worktree);
|
|
485
|
-
const tombstoneExists = fs.existsSync(tombstone);
|
|
486
|
-
if (worktreeExists || !recovering)
|
|
487
|
-
verifyWorktreePermit(permit);
|
|
488
|
-
if (recovering) {
|
|
489
|
-
if (worktreeExists || !tombstoneExists) {
|
|
490
|
-
if (worktreeExists && tombstoneExists) {
|
|
491
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${worktree}`);
|
|
492
|
-
}
|
|
493
|
-
throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${worktree}`);
|
|
494
|
-
}
|
|
495
|
-
assertOwnedRemovalTombstone(config.worktreeBase, tombstone, ownership);
|
|
496
|
-
assertWorktreeRegistrationIdentity(config, worktree, permit);
|
|
497
|
-
pruneWorktreeRegistration(config, worktree);
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
if (!worktreeExists)
|
|
501
|
-
return;
|
|
502
|
-
stageManagedRemoval(config.worktreeBase, worktree, tombstone, false, ownership);
|
|
503
|
-
pruneWorktreeRegistration(config, worktree);
|
|
504
|
-
}
|
|
505
|
-
function cleanupCompletedRemovalArtifacts(config, target, committedTarget, targetDigest) {
|
|
506
|
-
const journals = listSandboxRemovalJournals({
|
|
507
|
-
branch: target.effectiveBranch,
|
|
508
|
-
project: config.project,
|
|
509
|
-
targetDigest
|
|
510
|
-
});
|
|
511
|
-
if (journals.length === 0)
|
|
512
|
-
return;
|
|
513
|
-
const completed = (phase) => journals.every((journal) => (sandboxRemovalPhaseIndex(journal.phase) >= sandboxRemovalPhaseIndex(phase)));
|
|
514
|
-
if (completed('workspace-removed')) {
|
|
515
|
-
cleanupManagedTombstones(config.workspaceViewBase, targetDigest, committedTarget.permitDigest, 'workspace', target.workspaceViewRoots);
|
|
516
|
-
cleanupManagedTombstones(path.join(config.controlBase, config.project), targetDigest, committedTarget.permitDigest, 'workspace', target.controlRoots);
|
|
517
|
-
cleanupManagedTombstones(config.worktreeBase, targetDigest, committedTarget.permitDigest, 'worktree', committedTarget.worktreePaths);
|
|
518
|
-
}
|
|
519
|
-
if (completed('branch-removed')) {
|
|
520
|
-
cleanupBranchTombstone(config, target.effectiveBranch, targetDigest, new Map(committedTarget.permits.map((permit) => [path.resolve(permit.path), {
|
|
521
|
-
mode: permit.mode,
|
|
522
|
-
snapshot: permit.snapshot
|
|
523
|
-
}])));
|
|
524
|
-
}
|
|
525
|
-
if (completed('tool-removed')) {
|
|
526
|
-
cleanupManagedTombstones(config.home, targetDigest, committedTarget.permitDigest, 'tool', committedTarget.toolPaths);
|
|
527
|
-
}
|
|
528
|
-
if (completed('shell-removed')) {
|
|
529
|
-
cleanupManagedTombstones(config.shellConfigBase, targetDigest, committedTarget.permitDigest, 'shell', committedTarget.shellPaths);
|
|
530
|
-
}
|
|
531
|
-
if (completed('share-removed')) {
|
|
532
|
-
cleanupManagedTombstones(config.shareBase, targetDigest, committedTarget.permitDigest, 'share', [committedTarget.sharePath]);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
function removalActionPaths(target, config, phase) {
|
|
536
|
-
if (phase === 'workspace-removed') {
|
|
537
|
-
return [...target.workspaceViewRoots, ...target.controlRoots];
|
|
538
|
-
}
|
|
539
|
-
if (phase === 'tool-removed') {
|
|
540
|
-
return target.toolCandidates.flatMap(({ candidates }) => candidates);
|
|
541
|
-
}
|
|
542
|
-
if (phase === 'shell-removed') {
|
|
543
|
-
return shellConfigDirCandidates(config, target.effectiveBranch);
|
|
544
|
-
}
|
|
545
|
-
if (phase === 'share-removed') {
|
|
546
|
-
return [shareBranchDir(config, target.effectiveBranch)];
|
|
547
|
-
}
|
|
548
|
-
return [];
|
|
549
|
-
}
|
|
550
|
-
function permitsFromJournal(journals, target, expectedTargetDigest) {
|
|
551
|
-
const permits = new Map();
|
|
552
|
-
for (const journal of journals) {
|
|
553
|
-
if (journal.target.targetDigest !== expectedTargetDigest)
|
|
554
|
-
continue;
|
|
555
|
-
if (journal.target.branch !== target.effectiveBranch) {
|
|
556
|
-
throw new Error('SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH');
|
|
557
|
-
}
|
|
558
|
-
for (const entry of journal.target.permits) {
|
|
559
|
-
if (path.resolve(entry.path) !== path.resolve(entry.snapshot.worktree)) {
|
|
560
|
-
throw new Error('SANDBOX_CONTROL_REMOVAL_PERMIT_MISMATCH');
|
|
561
|
-
}
|
|
562
|
-
permits.set(path.resolve(entry.path), {
|
|
563
|
-
mode: entry.mode,
|
|
564
|
-
snapshot: entry.snapshot
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return permits;
|
|
569
|
-
}
|
|
570
|
-
export function sandboxManagedPathKey(candidate, platform = process.platform, resolveExistingPath = (resolved) => fs.realpathSync.native(resolved)) {
|
|
571
|
-
const resolved = path.resolve(candidate);
|
|
572
|
-
let identity = resolved;
|
|
573
|
-
try {
|
|
574
|
-
identity = resolveExistingPath(resolved);
|
|
575
|
-
}
|
|
576
|
-
catch (error) {
|
|
577
|
-
if (!isMissingPathError(error)) {
|
|
578
|
-
throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: unable to determine managed path identity for '${resolved}'`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
return platform === 'win32' ? identity.toLowerCase() : identity;
|
|
582
|
-
}
|
|
583
75
|
function resolveRmTarget(config, tools, cleanupTarget, options = {}) {
|
|
584
76
|
assertValidBranchName(cleanupTarget.branch);
|
|
585
77
|
const engine = detectEngine(config);
|
|
@@ -941,7 +433,7 @@ async function rmUnbound(config, tools, options) {
|
|
|
941
433
|
p.outro(`Dry run: ${targets.length} sandbox(es) found, nothing deleted`);
|
|
942
434
|
return;
|
|
943
435
|
}
|
|
944
|
-
for (const {
|
|
436
|
+
for (const { cleanupTarget, target } of targets) {
|
|
945
437
|
await runRmOne(config, tools, cleanupTarget.branch, {
|
|
946
438
|
assumeYes: options.assumeYes,
|
|
947
439
|
target,
|
|
@@ -1,37 +1,11 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
1
|
import fs from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { isRemovedHashShortIdInput } from '../task/short-id.js';
|
|
5
3
|
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
|
|
6
4
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
7
|
-
const SHORT_ID_RE = /^\d+$/;
|
|
8
5
|
export const TASK_WORKSPACE_STATES = ['active', 'completed', 'blocked', 'archive'];
|
|
9
|
-
function resolveShortIdStrict(arg, repoRoot) {
|
|
10
|
-
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
11
|
-
if (!fs.existsSync(scriptPath)) {
|
|
12
|
-
throw new Error(`Short id '${arg}' provided but task-short-id.js script is missing at ${scriptPath}`);
|
|
13
|
-
}
|
|
14
|
-
const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
|
|
15
|
-
if (result.status !== 0) {
|
|
16
|
-
throw new Error(`Short id '${arg}' not found in active task registry: ${(result.stderr || '').trim()}`);
|
|
17
|
-
}
|
|
18
|
-
return result.stdout.trim();
|
|
19
|
-
}
|
|
20
6
|
function stripQuotes(value) {
|
|
21
7
|
return value.replace(/^(["'])(.*)\1$/, '$2');
|
|
22
8
|
}
|
|
23
|
-
function readTaskContent(repoRoot, taskId) {
|
|
24
|
-
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
25
|
-
for (const state of TASK_WORKSPACE_STATES) {
|
|
26
|
-
const taskPath = state === 'archive'
|
|
27
|
-
? path.join(taskRoot, state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
|
|
28
|
-
: path.join(taskRoot, state, taskId, 'task.md');
|
|
29
|
-
if (fs.existsSync(taskPath)) {
|
|
30
|
-
return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
34
|
-
}
|
|
35
9
|
function taskWorkspaceCandidates(repoRoot, taskId) {
|
|
36
10
|
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
37
11
|
const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
|
|
@@ -89,21 +63,6 @@ function resolveBranchFromTaskContent(content, taskId) {
|
|
|
89
63
|
}
|
|
90
64
|
throw new Error(`Task ${taskId} has no branch field in task.md`);
|
|
91
65
|
}
|
|
92
|
-
export function resolveTaskBranch(arg, repoRoot) {
|
|
93
|
-
if (isRemovedHashShortIdInput(arg)) {
|
|
94
|
-
throw new Error(`Invalid task short id '${arg}': task short ids must use bare digits`);
|
|
95
|
-
}
|
|
96
|
-
if (SHORT_ID_RE.test(arg)) {
|
|
97
|
-
const taskId = resolveShortIdStrict(arg, repoRoot);
|
|
98
|
-
const { content } = readTaskContent(repoRoot, taskId);
|
|
99
|
-
return resolveBranchFromTaskContent(content, taskId);
|
|
100
|
-
}
|
|
101
|
-
if (!TASK_ID_RE.test(arg)) {
|
|
102
|
-
return arg;
|
|
103
|
-
}
|
|
104
|
-
const { content } = readTaskContent(repoRoot, arg);
|
|
105
|
-
return resolveBranchFromTaskContent(content, arg);
|
|
106
|
-
}
|
|
107
66
|
export function resolveTaskWorkspace(taskId, repoRoot) {
|
|
108
67
|
if (!TASK_ID_RE.test(taskId))
|
|
109
68
|
throw new Error(`Invalid task id: ${taskId}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
|
|
3
|
-
import { safeNameCandidates
|
|
3
|
+
import { safeNameCandidates } from './constants.js';
|
|
4
4
|
import { hostJoin } from './engines/wsl2-paths.js';
|
|
5
5
|
export function tmpfsSeedStagingPath(toolId, index) {
|
|
6
6
|
return `/run/agent-infra/tmpfs-seeds/${toolId}/${index}`;
|
|
@@ -233,9 +233,6 @@ export function parseCustomTools(value, options) {
|
|
|
233
233
|
export function resolveTools(config) {
|
|
234
234
|
return [...createSandboxCapabilityPlan(config).tools];
|
|
235
235
|
}
|
|
236
|
-
export function toolConfigDir(tool, project, branch) {
|
|
237
|
-
return hostJoin(tool.sandboxBase, project, sanitizeBranchName(branch));
|
|
238
|
-
}
|
|
239
236
|
export function toolConfigDirCandidates(tool, project, branch) {
|
|
240
237
|
return safeNameCandidates(branch).map((name) => hostJoin(tool.sandboxBase, project, name));
|
|
241
238
|
}
|
|
@@ -295,8 +295,7 @@ export function finalizeSandboxControlManifest(setup, identity) {
|
|
|
295
295
|
const manifest = {
|
|
296
296
|
...setup.manifestDraft,
|
|
297
297
|
engine: identity.engine,
|
|
298
|
-
containerIdentity: { id: identity.id, labels: { ...identity.labels } }
|
|
299
|
-
authorityEvidence: identity.authorityEvidence
|
|
298
|
+
containerIdentity: { id: identity.id, labels: { ...identity.labels } }
|
|
300
299
|
};
|
|
301
300
|
const persistedManifest = {
|
|
302
301
|
...manifest,
|
|
@@ -4,9 +4,6 @@ export function textMessage(text) {
|
|
|
4
4
|
export function markdownMessage(markdown) {
|
|
5
5
|
return { kind: 'markdown', markdown };
|
|
6
6
|
}
|
|
7
|
-
export function tableMessage(columns, rows, title) {
|
|
8
|
-
return { kind: 'table', title, columns, rows };
|
|
9
|
-
}
|
|
10
7
|
export function statusCard(title, tone, fields, body) {
|
|
11
8
|
return { kind: 'status-card', title, tone, fields, body };
|
|
12
9
|
}
|
|
@@ -20,9 +17,6 @@ export function streamEvent(title, phase, text, exitCode, signal) {
|
|
|
20
17
|
message.signal = signal;
|
|
21
18
|
return message;
|
|
22
19
|
}
|
|
23
|
-
export function commandResult(title, exitCode, signal, stdout, stderr) {
|
|
24
|
-
return { kind: 'command-result', title, exitCode, signal, stdout, stderr };
|
|
25
|
-
}
|
|
26
20
|
export function normalizeOutbound(message) {
|
|
27
21
|
return typeof message === 'string' ? textMessage(message) : message;
|
|
28
22
|
}
|