@fitlab-ai/agent-infra 0.11.11 → 0.11.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +26 -19
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/github-client.js +33 -3
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +14 -201
- package/dist/lib/sandbox/control/executor.js +12 -125
- package/dist/lib/sandbox/control/lifecycle.js +66 -4
- package/dist/lib/sandbox/control/protocol.js +15 -54
- package/dist/lib/sandbox/control/server.js +216 -281
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/mounts.js +9 -1
- package/dist/lib/sandbox/recovery.js +31 -10
- package/dist/lib/sandbox/removal.js +28 -5
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +56 -19
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +56 -264
- package/dist/lib/task/control-recovery.js +2 -3
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +35 -6
- package/dist/lib/task/lifecycle.js +274 -115
- package/dist/lib/task/orchestration.js +201 -194
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +32 -26
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +30 -18
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +7 -1
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +20 -254
- package/lib/sandbox/control/executor.ts +13 -136
- package/lib/sandbox/control/lifecycle.ts +67 -4
- package/lib/sandbox/control/protocol.ts +22 -66
- package/lib/sandbox/control/server.ts +21 -95
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/mounts.ts +10 -1
- package/lib/sandbox/recovery.ts +38 -17
- package/lib/sandbox/removal.ts +33 -7
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +50 -18
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +66 -378
- package/lib/task/control-recovery.ts +3 -4
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +37 -7
- package/lib/task/lifecycle.ts +257 -109
- package/lib/task/orchestration.ts +260 -237
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +30 -25
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
- package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- 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/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
|
@@ -9,7 +9,7 @@ import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
|
|
|
9
9
|
import { toEnginePath } from './engines/wsl2-paths.js';
|
|
10
10
|
import { sandboxCoreBindMounts } from './mounts.js';
|
|
11
11
|
import { resolveSandboxGitMetadata } from './git-metadata.js';
|
|
12
|
-
import { assertSandboxTaskSource, sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
|
|
12
|
+
import { assertSandboxTaskSource, sandboxCompletedTaskMountSource, sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
|
|
13
13
|
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
14
14
|
import { parseSandboxWorkspaceIdentity, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
|
|
15
15
|
import { inspectWorktree } from './worktree-safety.js';
|
|
@@ -174,6 +174,7 @@ export async function startSandboxControlBroker(repoRoot, manifestPath) {
|
|
|
174
174
|
async function ensureSandboxControlBroker(params) {
|
|
175
175
|
const control = sandboxControlPaths({
|
|
176
176
|
base: params.config.controlBase ?? path.join(params.config.home, '.agent-infra', 'sandbox-control'),
|
|
177
|
+
repoRoot: params.config.repoRoot,
|
|
177
178
|
project: params.config.project,
|
|
178
179
|
container: params.container,
|
|
179
180
|
identity: params.workspace
|
|
@@ -459,25 +460,38 @@ export function startCompletedSandboxContainer(params) {
|
|
|
459
460
|
const activeRoot = path.join(params.config.repoRoot, '.agents', 'workspace', 'active');
|
|
460
461
|
const historicalSource = path.join(activeRoot, params.taskId);
|
|
461
462
|
fs.mkdirSync(activeRoot, { recursive: true });
|
|
463
|
+
let replacedEmptyRoot = false;
|
|
464
|
+
let emptyRootMode = 0o700;
|
|
462
465
|
try {
|
|
463
|
-
fs.lstatSync(historicalSource);
|
|
464
|
-
|
|
466
|
+
const existing = fs.lstatSync(historicalSource);
|
|
467
|
+
if (existing.isSymbolicLink() || !existing.isDirectory() || fs.readdirSync(historicalSource).length > 0) {
|
|
468
|
+
throw new Error(`SANDBOX_COMPLETED_SOURCE_CONFLICT: historical task source already exists at ${historicalSource}`);
|
|
469
|
+
}
|
|
470
|
+
emptyRootMode = existing.mode & 0o777;
|
|
471
|
+
fs.rmdirSync(historicalSource);
|
|
472
|
+
replacedEmptyRoot = true;
|
|
465
473
|
}
|
|
466
474
|
catch (error) {
|
|
467
475
|
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
|
|
468
476
|
throw error;
|
|
469
477
|
}
|
|
470
|
-
|
|
478
|
+
let temporaryLink = false;
|
|
471
479
|
try {
|
|
480
|
+
fs.symlinkSync(completedSource, historicalSource, process.platform === 'win32' ? 'junction' : 'dir');
|
|
481
|
+
temporaryLink = true;
|
|
472
482
|
params.start(params.engine, params.container);
|
|
473
483
|
}
|
|
474
484
|
finally {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
485
|
+
if (temporaryLink) {
|
|
486
|
+
const temporarySource = fs.lstatSync(historicalSource);
|
|
487
|
+
const resolvedSource = fs.realpathSync.native(historicalSource);
|
|
488
|
+
if (!temporarySource.isSymbolicLink() || resolvedSource !== completedSource) {
|
|
489
|
+
throw new Error(`SANDBOX_COMPLETED_SOURCE_CHANGED: refusing to remove unexpected path at ${historicalSource}`);
|
|
490
|
+
}
|
|
491
|
+
fs.unlinkSync(historicalSource);
|
|
479
492
|
}
|
|
480
|
-
|
|
493
|
+
if (replacedEmptyRoot)
|
|
494
|
+
fs.mkdirSync(historicalSource, { mode: emptyRootMode });
|
|
481
495
|
}
|
|
482
496
|
}
|
|
483
497
|
function sameHostSource(left, right) {
|
|
@@ -509,6 +523,7 @@ function expectedMounts(params) {
|
|
|
509
523
|
});
|
|
510
524
|
const control = sandboxControlPaths({
|
|
511
525
|
base: config.controlBase ?? path.join(config.home, '.agent-infra', 'sandbox-control'),
|
|
526
|
+
repoRoot: config.repoRoot,
|
|
512
527
|
project: config.project,
|
|
513
528
|
container: params.container,
|
|
514
529
|
identity: params.workspace
|
|
@@ -517,6 +532,7 @@ function expectedMounts(params) {
|
|
|
517
532
|
? recoveryTaskSources(config.repoRoot, params.workspace.taskId)
|
|
518
533
|
: null;
|
|
519
534
|
const taskId = params.workspace.mode === 'task-bound' ? params.workspace.taskId : null;
|
|
535
|
+
const completedTaskSource = taskId ? sandboxCompletedTaskMountSource(config.repoRoot, taskId) : null;
|
|
520
536
|
const worktrees = worktreeDirCandidates(config, branch).filter((candidate) => fs.existsSync(path.join(candidate, '.git')));
|
|
521
537
|
const gitMetadata = worktrees.length === 1
|
|
522
538
|
? resolveSandboxGitMetadata(worktrees[0], control.channelDir)
|
|
@@ -536,6 +552,7 @@ function expectedMounts(params) {
|
|
|
536
552
|
: {
|
|
537
553
|
runtimeDir: control.runtimeDir,
|
|
538
554
|
taskSources: taskSources.accessiblePaths,
|
|
555
|
+
completedTaskSource: completedTaskSource,
|
|
539
556
|
taskId: taskId
|
|
540
557
|
})
|
|
541
558
|
}).map((mount) => ({
|
|
@@ -546,6 +563,10 @@ function expectedMounts(params) {
|
|
|
546
563
|
&& mount.containerPath === `/workspace/.agents/workspace/active/${taskId}`
|
|
547
564
|
? { sourceAccessiblePaths: taskSources.accessiblePaths }
|
|
548
565
|
: {}),
|
|
566
|
+
...(completedTaskSource !== null && taskId !== null
|
|
567
|
+
&& mount.containerPath === `/workspace/.agents/workspace/completed/${taskId}`
|
|
568
|
+
? { sourceAccessiblePaths: [completedTaskSource] }
|
|
569
|
+
: {}),
|
|
549
570
|
expectedRW: !mount.readOnly
|
|
550
571
|
}));
|
|
551
572
|
const live = tools.flatMap((tool) => (tool.hostLiveMounts ?? []).flatMap(({ hostPath, containerSubpath }) => {
|
|
@@ -905,7 +926,7 @@ export async function ensureSandboxReady(params) {
|
|
|
905
926
|
let completedReentry = null;
|
|
906
927
|
if (params.reentry === 'completed' && params.workspace?.mode === 'task-bound') {
|
|
907
928
|
const control = sandboxControlPaths({
|
|
908
|
-
base: params.config.controlBase, project: params.config.project,
|
|
929
|
+
base: params.config.controlBase, repoRoot: params.config.repoRoot, project: params.config.project,
|
|
909
930
|
container: params.row.name, identity: params.workspace
|
|
910
931
|
});
|
|
911
932
|
const manifest = readSandboxControlManifest(control.manifestPath);
|
|
@@ -568,7 +568,7 @@ function resolveRmTarget(config, tools, cleanupTarget, options = {}) {
|
|
|
568
568
|
? matchedContainers
|
|
569
569
|
: [...new Set([...containerNameCandidates(config, effectiveBranch), ...matchedContainers])];
|
|
570
570
|
const controlRoots = containers.flatMap((container) => identities.map((identity) => sandboxControlPaths({
|
|
571
|
-
base: config.controlBase, project: config.project, container, identity
|
|
571
|
+
base: config.controlBase, repoRoot: config.repoRoot, project: config.project, container, identity
|
|
572
572
|
}).root));
|
|
573
573
|
const workspaceViewRoots = containers.flatMap((container) => identities.map((identity) => sandboxWorkspaceViewPaths({
|
|
574
574
|
base: config.workspaceViewBase, project: config.project, container, identity
|
|
@@ -711,7 +711,9 @@ async function removeUncheckedSandbox(config, target, options) {
|
|
|
711
711
|
}
|
|
712
712
|
for (const root of controlRoots) {
|
|
713
713
|
fs.rmSync(root, { recursive: true, force: true });
|
|
714
|
-
|
|
714
|
+
if (target.workspace.mode !== 'task-bound') {
|
|
715
|
+
removeEmptyManagedParent(path.join(config.controlBase, config.project), root);
|
|
716
|
+
}
|
|
715
717
|
}
|
|
716
718
|
for (const root of workspaceViewRoots) {
|
|
717
719
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -735,13 +737,15 @@ async function removeUncheckedSandbox(config, target, options) {
|
|
|
735
737
|
assertBranchRemovalIdentity(config, effectiveBranch, worktreePermits);
|
|
736
738
|
runSafe('git', ['-C', config.repoRoot, 'branch', '-D', effectiveBranch]);
|
|
737
739
|
}
|
|
738
|
-
|
|
739
|
-
releaseStaleShortIdRegistry(config.repoRoot, target.workspace.taskId);
|
|
740
|
-
}
|
|
740
|
+
releaseTaskBoundSandboxIdentity(config.repoRoot, target.workspace);
|
|
741
741
|
if (!options.quiet)
|
|
742
742
|
p.outro('Sandbox removed');
|
|
743
743
|
return null;
|
|
744
744
|
}
|
|
745
|
+
function releaseTaskBoundSandboxIdentity(repoRoot, workspace) {
|
|
746
|
+
if (workspace.mode === 'task-bound')
|
|
747
|
+
releaseStaleShortIdRegistry(repoRoot, workspace.taskId);
|
|
748
|
+
}
|
|
745
749
|
async function rmOneCore(config, tools, branch, options = {}) {
|
|
746
750
|
const target = options.target ?? resolveRmTarget(config, tools, options.cleanupTarget ?? resolveSandboxCleanupTarget(branch, config.repoRoot));
|
|
747
751
|
return removeUncheckedSandbox(config, target, options);
|
|
@@ -754,11 +758,20 @@ async function rmPurgeCore(config, tools, prompt = {}) {
|
|
|
754
758
|
const confirm = prompt.confirm ?? p.confirm;
|
|
755
759
|
const isCancel = prompt.isCancel ?? p.isCancel;
|
|
756
760
|
p.intro(pc.cyan(`Removing all sandboxes for ${config.project}`));
|
|
761
|
+
const listed = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) });
|
|
762
|
+
const rows = [...listed.running, ...listed.nonRunning];
|
|
757
763
|
const containers = runEngine(engine, 'docker', [
|
|
758
764
|
'ps', '-a', '--filter', `label=${sandboxLabel(config)}`, '--format', '{{.Names}}'
|
|
759
765
|
]).split('\n').filter(Boolean);
|
|
766
|
+
const completedTaskIds = [...new Set(rows
|
|
767
|
+
.filter((row) => row.workspaceMode === 'task-bound' && row.taskId)
|
|
768
|
+
.map((row) => row.taskId))];
|
|
760
769
|
for (const name of containers)
|
|
761
770
|
runSafeEngine(engine, 'docker', ['rm', '-f', name]);
|
|
771
|
+
if (!sandboxContainersRemoved(engine, containers)) {
|
|
772
|
+
throw new Error(`SANDBOX_REMOVAL_CONTAINER_STILL_PRESENT: ${containers.join(', ')}`);
|
|
773
|
+
}
|
|
774
|
+
let cleanupCompleted = true;
|
|
762
775
|
const worktrees = fs.existsSync(config.worktreeBase)
|
|
763
776
|
? fs.readdirSync(config.worktreeBase).map((entry) => path.join(config.worktreeBase, entry))
|
|
764
777
|
: [];
|
|
@@ -769,6 +782,8 @@ async function rmPurgeCore(config, tools, prompt = {}) {
|
|
|
769
782
|
fs.rmSync(worktree, { recursive: true, force: true });
|
|
770
783
|
runSafe('git', ['-C', config.repoRoot, 'worktree', 'prune']);
|
|
771
784
|
}
|
|
785
|
+
else
|
|
786
|
+
cleanupCompleted = false;
|
|
772
787
|
}
|
|
773
788
|
for (const dir of projectToolDirs(config, tools))
|
|
774
789
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
@@ -776,15 +791,23 @@ async function rmPurgeCore(config, tools, prompt = {}) {
|
|
|
776
791
|
const selected = await confirm({ message: `Remove all shell config dirs in ${config.shellConfigBase}?`, initialValue: true });
|
|
777
792
|
if (!isCancel(selected) && selected)
|
|
778
793
|
fs.rmSync(config.shellConfigBase, { recursive: true, force: true });
|
|
794
|
+
else
|
|
795
|
+
cleanupCompleted = false;
|
|
779
796
|
}
|
|
780
797
|
if (fs.existsSync(config.shareBase)) {
|
|
781
798
|
const selected = await confirm({ message: `Remove all share dirs for project (${config.shareBase})?`, initialValue: true });
|
|
782
799
|
if (!isCancel(selected) && selected)
|
|
783
800
|
fs.rmSync(config.shareBase, { recursive: true, force: true });
|
|
801
|
+
else
|
|
802
|
+
cleanupCompleted = false;
|
|
784
803
|
}
|
|
785
804
|
for (const base of [config.workspaceViewBase, config.controlBase]) {
|
|
786
805
|
fs.rmSync(path.join(base, config.project), { recursive: true, force: true });
|
|
787
806
|
}
|
|
807
|
+
if (cleanupCompleted) {
|
|
808
|
+
for (const taskId of completedTaskIds)
|
|
809
|
+
releaseStaleShortIdRegistry(config.repoRoot, taskId);
|
|
810
|
+
}
|
|
788
811
|
const removeImage = await confirm({ message: `Remove image ${config.imageName}?`, initialValue: false });
|
|
789
812
|
if (!isCancel(removeImage) && removeImage)
|
|
790
813
|
runSafeEngine(engine, 'docker', ['rmi', config.imageName]);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRemovedHashShortIdInput } from '../task/short-id.js';
|
|
4
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
5
|
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
5
6
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
7
|
const SHORT_ID_RE = /^\d+$/;
|
|
@@ -38,6 +39,16 @@ function taskIdentity(repoRoot, taskId, registry) {
|
|
|
38
39
|
}
|
|
39
40
|
return { mode: 'task-bound', taskId, shortId: shortIds[0] };
|
|
40
41
|
}
|
|
42
|
+
function cleanupTaskId(requestedRef, repoRoot) {
|
|
43
|
+
if (TASK_ID_RE.test(requestedRef))
|
|
44
|
+
return requestedRef;
|
|
45
|
+
if (!SHORT_ID_RE.test(requestedRef))
|
|
46
|
+
return null;
|
|
47
|
+
const task = resolveTaskRef(requestedRef, { repoRoot });
|
|
48
|
+
if (!task.ok)
|
|
49
|
+
throw new Error(task.message);
|
|
50
|
+
return task.taskId;
|
|
51
|
+
}
|
|
41
52
|
export function resolveSandboxTarget(requestedRef, repoRoot) {
|
|
42
53
|
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
43
54
|
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
@@ -83,26 +94,27 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
|
|
|
83
94
|
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
84
95
|
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
85
96
|
}
|
|
86
|
-
|
|
97
|
+
const taskId = cleanupTaskId(requestedRef, repoRoot);
|
|
98
|
+
if (taskId) {
|
|
87
99
|
let task;
|
|
88
100
|
try {
|
|
89
|
-
task = resolveTaskWorkspace(
|
|
101
|
+
task = resolveTaskWorkspace(taskId, repoRoot);
|
|
90
102
|
}
|
|
91
103
|
catch (error) {
|
|
92
|
-
if (!(error instanceof Error && error.message === `Task not found: ${
|
|
104
|
+
if (!(error instanceof Error && error.message === `Task not found: ${taskId}`))
|
|
93
105
|
throw error;
|
|
94
|
-
const branch = options.resolveMissingTask?.(
|
|
106
|
+
const branch = options.resolveMissingTask?.(taskId);
|
|
95
107
|
if (!branch)
|
|
96
108
|
throw error;
|
|
97
109
|
return {
|
|
98
110
|
requestedRef,
|
|
99
111
|
branch,
|
|
100
|
-
workspace: { mode: 'task-bound', taskId
|
|
112
|
+
workspace: { mode: 'task-bound', taskId },
|
|
101
113
|
taskState: 'unknown'
|
|
102
114
|
};
|
|
103
115
|
}
|
|
104
116
|
if (!options.allowProtected && (task.state === 'blocked' || task.state === 'archive')) {
|
|
105
|
-
throw new Error(`SANDBOX_CLEANUP_STATE_UNSUPPORTED: task ${
|
|
117
|
+
throw new Error(`SANDBOX_CLEANUP_STATE_UNSUPPORTED: task ${taskId} is ${task.state}`);
|
|
106
118
|
}
|
|
107
119
|
return {
|
|
108
120
|
requestedRef,
|
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
5
|
+
import { resolveTaskRuntimeRoot } from '../task/runtime-paths.js';
|
|
5
6
|
import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
|
|
6
7
|
export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
|
|
7
8
|
'active',
|
|
@@ -25,11 +26,12 @@ export function sandboxWorkspaceViewPaths(params) {
|
|
|
25
26
|
return { root };
|
|
26
27
|
}
|
|
27
28
|
export function sandboxControlPaths(params) {
|
|
28
|
-
const
|
|
29
|
-
?
|
|
30
|
-
:
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const root = params.identity.mode === 'task-bound'
|
|
30
|
+
? path.join(resolveTaskRuntimeRoot(params.identity.taskId, { repoRoot: params.repoRoot }), 'sandbox-control')
|
|
31
|
+
: (() => {
|
|
32
|
+
const digest = createHash('sha256').update('branch-only').digest('hex').slice(0, 16);
|
|
33
|
+
return path.resolve(params.base, params.project, params.container, digest);
|
|
34
|
+
})();
|
|
33
35
|
return {
|
|
34
36
|
root,
|
|
35
37
|
channelDir: path.join(root, 'channel'),
|
|
@@ -109,13 +111,16 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
|
109
111
|
}
|
|
110
112
|
return canonical;
|
|
111
113
|
}
|
|
112
|
-
export function prepareSandboxWorkspaceMountTargets(worktreeRoot) {
|
|
114
|
+
export function prepareSandboxWorkspaceMountTargets(worktreeRoot, taskId) {
|
|
113
115
|
const resolvedWorktreeRoot = path.resolve(worktreeRoot);
|
|
114
116
|
const canonicalWorktreeRoot = fs.realpathSync.native(resolvedWorktreeRoot);
|
|
115
117
|
const workspaceRoot = path.resolve(resolvedWorktreeRoot, '.agents', 'workspace');
|
|
116
118
|
const statePaths = sandboxWorkspaceViewStatePaths(workspaceRoot);
|
|
117
119
|
const registryTarget = path.join(workspaceRoot, 'active', '.short-ids.json');
|
|
118
|
-
|
|
120
|
+
const completedTaskTarget = taskId ? path.join(workspaceRoot, 'completed', taskId) : null;
|
|
121
|
+
if (taskId && !/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
122
|
+
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
123
|
+
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget, ...(completedTaskTarget ? [completedTaskTarget] : [])]) {
|
|
119
124
|
assertSafeMountTargetPath(target, resolvedWorktreeRoot, canonicalWorktreeRoot);
|
|
120
125
|
}
|
|
121
126
|
fs.mkdirSync(workspaceRoot, { recursive: true, mode: 0o700 });
|
|
@@ -123,9 +128,52 @@ export function prepareSandboxWorkspaceMountTargets(worktreeRoot) {
|
|
|
123
128
|
fs.mkdirSync(hostPath, { recursive: true, mode: 0o700 });
|
|
124
129
|
fs.chmodSync(hostPath, 0o700);
|
|
125
130
|
}
|
|
131
|
+
if (completedTaskTarget)
|
|
132
|
+
fs.mkdirSync(completedTaskTarget, { recursive: true, mode: 0o700 });
|
|
126
133
|
fs.chmodSync(workspaceRoot, 0o700);
|
|
127
134
|
fs.closeSync(fs.openSync(registryTarget, 'a', 0o600));
|
|
128
135
|
}
|
|
136
|
+
export function prepareSandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
137
|
+
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
138
|
+
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
139
|
+
const workspaceRoot = path.resolve(repoRoot, '.agents', 'workspace');
|
|
140
|
+
fs.mkdirSync(path.join(workspaceRoot, 'completed'), { recursive: true, mode: 0o700 });
|
|
141
|
+
const completedRoot = path.join(workspaceRoot, 'completed');
|
|
142
|
+
const completedStat = fs.lstatSync(completedRoot);
|
|
143
|
+
if (!completedStat.isDirectory() || completedStat.isSymbolicLink())
|
|
144
|
+
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
145
|
+
const target = path.join(completedRoot, taskId);
|
|
146
|
+
try {
|
|
147
|
+
const stat = fs.lstatSync(target);
|
|
148
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || fs.readdirSync(target).length > 0) {
|
|
149
|
+
throw new Error(`SANDBOX_COMPLETED_MOUNT_CONFLICT: ${target}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (error.code !== 'ENOENT')
|
|
154
|
+
throw error;
|
|
155
|
+
fs.mkdirSync(target, { mode: 0o700 });
|
|
156
|
+
}
|
|
157
|
+
return sandboxCompletedTaskMountSource(repoRoot, taskId);
|
|
158
|
+
}
|
|
159
|
+
export function sandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
160
|
+
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
161
|
+
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
162
|
+
const source = path.resolve(repoRoot, '.agents', 'workspace', 'completed', taskId);
|
|
163
|
+
const stat = fs.lstatSync(source);
|
|
164
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
165
|
+
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
166
|
+
const canonicalWorkspace = fs.realpathSync.native(path.join(repoRoot, '.agents', 'workspace'));
|
|
167
|
+
const canonicalSource = fs.realpathSync.native(source);
|
|
168
|
+
const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
|
|
169
|
+
const workspaceRelative = path.relative(canonicalRepoRoot, canonicalWorkspace);
|
|
170
|
+
const relative = path.relative(canonicalWorkspace, canonicalSource);
|
|
171
|
+
if (workspaceRelative.startsWith('..') || path.isAbsolute(workspaceRelative)
|
|
172
|
+
|| path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
|
|
173
|
+
|| relative.startsWith('..') || path.isAbsolute(relative))
|
|
174
|
+
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
175
|
+
return canonicalSource;
|
|
176
|
+
}
|
|
129
177
|
export function materializeSandboxWorkspaceView(params) {
|
|
130
178
|
const projectRoot = path.resolve(params.base, params.project);
|
|
131
179
|
const { root } = sandboxWorkspaceViewPaths(params);
|
|
@@ -155,7 +203,7 @@ export function materializeSandboxWorkspaceView(params) {
|
|
|
155
203
|
}
|
|
156
204
|
export function materializeSandboxControl(params) {
|
|
157
205
|
const { root, channelDir, statusDir, processingDir, runtimeDir, manifestPath } = sandboxControlPaths(params);
|
|
158
|
-
assertSafeDirectory(root, path.resolve(params.base));
|
|
206
|
+
assertSafeDirectory(root, params.identity.mode === 'task-bound' ? path.dirname(root) : path.resolve(params.base));
|
|
159
207
|
if (fs.existsSync(root)) {
|
|
160
208
|
if (!params.replacementLease || path.resolve(params.replacementLease.root) !== root) {
|
|
161
209
|
throw new Error('SANDBOX_CONTROL_REPLACEMENT_REQUIRED');
|
|
@@ -168,18 +216,7 @@ export function materializeSandboxControl(params) {
|
|
|
168
216
|
fs.mkdirSync(consumedDir, { recursive: true, mode: 0o700 });
|
|
169
217
|
for (const directory of [statusDir, processingDir, runtimeDir]) {
|
|
170
218
|
assertSafeDirectory(directory, root);
|
|
171
|
-
if (directory === runtimeDir)
|
|
172
|
-
fs.rmSync(directory, { recursive: true, force: true });
|
|
173
|
-
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
174
|
-
}
|
|
175
|
-
for (const directory of [
|
|
176
|
-
path.join(runtimeDir, 'clients'),
|
|
177
|
-
path.join(runtimeDir, 'clients', 'codex', 'capabilities'),
|
|
178
|
-
path.join(runtimeDir, 'clients', 'codex', 'lifecycle')
|
|
179
|
-
]) {
|
|
180
|
-
assertSafeDirectory(directory, runtimeDir);
|
|
181
219
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
182
|
-
fs.chmodSync(directory, 0o700);
|
|
183
220
|
}
|
|
184
221
|
for (const queue of ['requests', 'responses']) {
|
|
185
222
|
const directory = path.join(channelDir, queue);
|
|
@@ -58,9 +58,9 @@ function collectTasks(repoRoot, state) {
|
|
|
58
58
|
const dir = path.join(repoRoot, '.agents', 'workspace', state);
|
|
59
59
|
if (!fs.existsSync(dir))
|
|
60
60
|
return [];
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const shortIdByTaskId = state === 'active' || state === 'completed'
|
|
62
|
+
? loadShortIdByTaskId(repoRoot)
|
|
63
|
+
: new Map();
|
|
64
64
|
const rows = [];
|
|
65
65
|
for (const entry of fs.readdirSync(dir).sort()) {
|
|
66
66
|
if (!TASK_ID_RE.test(entry))
|