@fitlab-ai/agent-infra 0.11.13 → 0.11.14
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 -7
- package/dist/lib/agent-clients/adapter.js +0 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +5 -9
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -3
- package/dist/lib/agent-clients/adapters/codex.js +0 -2
- package/dist/lib/internal/cli-route-inventory.js +0 -1
- package/dist/lib/internal/task-finalization.js +6 -4
- package/dist/lib/internal/task-lifecycle.js +3 -1
- package/dist/lib/internal/task-operation-registry.js +1 -3
- package/dist/lib/internal/task-validate.js +14 -124
- package/dist/lib/platform/release-notes.js +2 -2
- package/dist/lib/sandbox/commands/control-status.js +0 -1
- package/dist/lib/sandbox/commands/create.js +5 -4
- package/dist/lib/sandbox/config.js +12 -0
- package/dist/lib/sandbox/control/audit.js +1 -2
- package/dist/lib/sandbox/control/client.js +12 -47
- package/dist/lib/sandbox/control/executor.js +6 -10
- package/dist/lib/sandbox/control/host-recovery.js +89 -27
- package/dist/lib/sandbox/control/lifecycle.js +20 -16
- package/dist/lib/sandbox/control/protocol.js +8 -36
- package/dist/lib/sandbox/control/server.js +2 -21
- package/dist/lib/sandbox/environment.js +7 -8
- package/dist/lib/sandbox/mounts.js +71 -59
- package/dist/lib/sandbox/recovery.js +2 -3
- package/dist/lib/sandbox/removal.js +19 -4
- package/dist/lib/sandbox/workspace-view.js +41 -4
- package/dist/lib/task/control-authority.js +39 -17
- package/dist/lib/task/control-recovery.js +1 -2
- package/dist/lib/task/finalization.js +13 -2
- package/dist/lib/task/lifecycle.js +92 -33
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +0 -27
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +6 -12
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -7
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -4
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -3
- package/lib/agent-clients/adapters/codex.ts +0 -2
- package/lib/internal/cli-route-inventory.ts +0 -1
- package/lib/internal/task-finalization.ts +6 -4
- package/lib/internal/task-lifecycle.ts +3 -1
- package/lib/internal/task-operation-registry.ts +1 -3
- package/lib/internal/task-validate.ts +13 -127
- package/lib/platform/release-notes.ts +2 -2
- package/lib/sandbox/commands/control-status.ts +0 -1
- package/lib/sandbox/commands/create.ts +5 -4
- package/lib/sandbox/config.ts +13 -0
- package/lib/sandbox/control/audit.ts +1 -2
- package/lib/sandbox/control/client.ts +13 -61
- package/lib/sandbox/control/executor.ts +7 -13
- package/lib/sandbox/control/host-recovery.ts +94 -21
- package/lib/sandbox/control/lifecycle.ts +18 -16
- package/lib/sandbox/control/protocol.ts +9 -45
- package/lib/sandbox/control/server.ts +2 -17
- package/lib/sandbox/environment.ts +7 -8
- package/lib/sandbox/mounts.ts +92 -75
- package/lib/sandbox/recovery.ts +2 -3
- package/lib/sandbox/removal.ts +16 -4
- package/lib/sandbox/workspace-view.ts +40 -5
- package/lib/task/control-authority.ts +40 -26
- package/lib/task/control-recovery.ts +2 -3
- package/lib/task/finalization.ts +15 -2
- package/lib/task/lifecycle.ts +103 -31
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -4
- package/templates/.agents/rules/task-short-id.en.md +18 -12
- package/templates/.agents/rules/task-short-id.zh-CN.md +12 -7
- package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -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/controller-client.js +0 -130
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +0 -127
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +0 -131
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-registration.js +0 -275
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js +0 -106
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +0 -345
- package/dist/lib/sandbox/control/agent-operation.js +0 -34
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +0 -158
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +0 -163
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +0 -151
- package/lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts +0 -352
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts +0 -109
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +0 -437
- package/lib/sandbox/control/agent-operation.ts +0 -49
|
@@ -2,10 +2,13 @@ import path from 'node:path';
|
|
|
2
2
|
import { shareBranchDir, shareCommonDir, shellConfigDirCandidates, worktreeDirCandidates } from './constants.js';
|
|
3
3
|
import { sandboxWorkspaceViewStatePaths } from './workspace-view.js';
|
|
4
4
|
import { SANDBOX_GIT_COMMON_DIR } from './git-metadata.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
function workspaceStateMounts(workspaceViewRoot, taskBound) {
|
|
6
|
+
return sandboxWorkspaceViewStatePaths(workspaceViewRoot)
|
|
7
|
+
// The writable completed-task mount below is nested under this path. Keep
|
|
8
|
+
// the worktree target visible so Docker can mount it without creating a
|
|
9
|
+
// mountpoint inside a read-only parent bind mount.
|
|
10
|
+
.filter(({ state }) => !(taskBound && state === 'completed'))
|
|
11
|
+
.map(({ state, hostPath }) => taskBound && state === 'active'
|
|
9
12
|
? {
|
|
10
13
|
hostPaths: [path.join(hostPath, '.short-ids.json')],
|
|
11
14
|
containerPath: '/workspace/.agents/workspace/active/.short-ids.json',
|
|
@@ -16,17 +19,69 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
|
|
|
16
19
|
containerPath: path.posix.join('/workspace/.agents/workspace', state),
|
|
17
20
|
readOnly: true
|
|
18
21
|
});
|
|
22
|
+
}
|
|
23
|
+
function assertGitMetadataPair(overrides) {
|
|
19
24
|
if (Boolean(overrides.gitCommonDir) !== Boolean(overrides.worktreeGitFile)) {
|
|
20
25
|
throw new Error('SANDBOX_GIT_METADATA_INCOMPLETE');
|
|
21
26
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
}
|
|
28
|
+
function gitCommonMount(gitCommonDir) {
|
|
29
|
+
return gitCommonDir ? [{ hostPaths: [gitCommonDir], containerPath: SANDBOX_GIT_COMMON_DIR, readOnly: false }] : [];
|
|
30
|
+
}
|
|
31
|
+
function worktreeGitFileMount(worktreeGitFile) {
|
|
32
|
+
return worktreeGitFile ? [{ hostPaths: [worktreeGitFile], containerPath: '/workspace/.git', readOnly: true }] : [];
|
|
33
|
+
}
|
|
34
|
+
function taskWorkspaceMounts(taskSources, taskBound, overrides) {
|
|
35
|
+
if (!taskBound)
|
|
36
|
+
return [];
|
|
37
|
+
const taskId = overrides.taskId;
|
|
38
|
+
const mounts = [{
|
|
39
|
+
hostPaths: taskSources,
|
|
40
|
+
containerPath: path.posix.join('/workspace/.agents/workspace/active', taskId),
|
|
41
|
+
readOnly: false
|
|
42
|
+
}];
|
|
43
|
+
if (overrides.completedTaskSource)
|
|
44
|
+
mounts.push({
|
|
45
|
+
hostPaths: [overrides.completedTaskSource],
|
|
46
|
+
containerPath: path.posix.join('/workspace/.agents/workspace/completed', taskId),
|
|
47
|
+
readOnly: false
|
|
48
|
+
});
|
|
49
|
+
return mounts;
|
|
50
|
+
}
|
|
51
|
+
function sharedMounts(config, branch, shellConfigHostDir) {
|
|
52
|
+
return [{
|
|
53
|
+
hostPaths: [shareCommonDir(config)], containerPath: '/share/common', readOnly: false
|
|
54
|
+
}, {
|
|
55
|
+
hostPaths: [shareBranchDir(config, branch)], containerPath: '/share/branch', readOnly: false
|
|
56
|
+
}, {
|
|
57
|
+
hostPaths: shellConfigHostDir ? [shellConfigHostDir] : shellConfigDirCandidates(config, branch),
|
|
58
|
+
containerPath: '/home/devuser/.host-shell-config', readOnly: true
|
|
59
|
+
}];
|
|
60
|
+
}
|
|
61
|
+
function controlMounts(overrides, taskBound) {
|
|
62
|
+
const mounts = [{
|
|
63
|
+
hostPaths: [overrides.controlStatusDir], containerPath: '/run/agent-infra/control-status', readOnly: true
|
|
64
|
+
}, {
|
|
65
|
+
hostPaths: [overrides.controlDir], containerPath: '/run/agent-infra/control', readOnly: false
|
|
66
|
+
}];
|
|
67
|
+
if (taskBound && overrides.runtimeDir)
|
|
68
|
+
mounts.push({
|
|
69
|
+
hostPaths: [overrides.runtimeDir], containerPath: '/run/agent-infra/runtime', readOnly: false
|
|
70
|
+
});
|
|
71
|
+
if (taskBound && overrides.controlRootDir)
|
|
72
|
+
mounts.push({
|
|
73
|
+
hostPaths: [overrides.controlRootDir], containerPath: '/run/agent-infra/control-root', readOnly: false
|
|
74
|
+
});
|
|
75
|
+
return mounts;
|
|
76
|
+
}
|
|
77
|
+
export function sandboxCoreBindMounts(config, branch, overrides) {
|
|
78
|
+
const taskSources = overrides.taskSources ?? [];
|
|
79
|
+
const taskBound = Boolean(taskSources.length > 0 && overrides.taskId);
|
|
80
|
+
assertGitMetadataPair(overrides);
|
|
81
|
+
if (taskBound && !overrides.controlRootDir)
|
|
82
|
+
throw new Error('SANDBOX_CONTROL_ROOT_MISSING');
|
|
28
83
|
const mounts = [
|
|
29
|
-
...(
|
|
84
|
+
...gitCommonMount(overrides.gitCommonDir),
|
|
30
85
|
{
|
|
31
86
|
hostPaths: overrides.worktree
|
|
32
87
|
? [overrides.worktree]
|
|
@@ -34,55 +89,12 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
|
|
|
34
89
|
containerPath: '/workspace',
|
|
35
90
|
readOnly: false
|
|
36
91
|
},
|
|
37
|
-
...(
|
|
38
|
-
...
|
|
92
|
+
...worktreeGitFileMount(overrides.worktreeGitFile),
|
|
93
|
+
...workspaceStateMounts(overrides.workspaceViewRoot, taskBound),
|
|
94
|
+
...taskWorkspaceMounts(taskSources, taskBound, overrides),
|
|
95
|
+
...sharedMounts(config, branch, overrides.shellConfigHostDir),
|
|
96
|
+
...controlMounts(overrides, taskBound)
|
|
39
97
|
];
|
|
40
|
-
if (taskBound) {
|
|
41
|
-
const taskMountPath = path.posix.join('/workspace/.agents/workspace/active', overrides.taskId);
|
|
42
|
-
mounts.push({
|
|
43
|
-
hostPaths: taskSources,
|
|
44
|
-
containerPath: taskMountPath,
|
|
45
|
-
readOnly: false
|
|
46
|
-
});
|
|
47
|
-
if (overrides.completedTaskSource) {
|
|
48
|
-
mounts.push({
|
|
49
|
-
hostPaths: [overrides.completedTaskSource],
|
|
50
|
-
containerPath: path.posix.join('/workspace/.agents/workspace/completed', overrides.taskId),
|
|
51
|
-
readOnly: false
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
mounts.push({
|
|
56
|
-
hostPaths: [shareCommonDir(config)],
|
|
57
|
-
containerPath: '/share/common',
|
|
58
|
-
readOnly: false
|
|
59
|
-
}, {
|
|
60
|
-
hostPaths: [shareBranchDir(config, branch)],
|
|
61
|
-
containerPath: '/share/branch',
|
|
62
|
-
readOnly: false
|
|
63
|
-
}, {
|
|
64
|
-
hostPaths: overrides.shellConfigHostDir
|
|
65
|
-
? [overrides.shellConfigHostDir]
|
|
66
|
-
: shellConfigDirCandidates(config, branch),
|
|
67
|
-
containerPath: '/home/devuser/.host-shell-config',
|
|
68
|
-
readOnly: true
|
|
69
|
-
});
|
|
70
|
-
mounts.push({
|
|
71
|
-
hostPaths: [overrides.controlStatusDir],
|
|
72
|
-
containerPath: '/run/agent-infra/control-status',
|
|
73
|
-
readOnly: true
|
|
74
|
-
}, {
|
|
75
|
-
hostPaths: [overrides.controlDir],
|
|
76
|
-
containerPath: '/run/agent-infra/control',
|
|
77
|
-
readOnly: false
|
|
78
|
-
});
|
|
79
|
-
if (taskBound && overrides.runtimeDir) {
|
|
80
|
-
mounts.push({
|
|
81
|
-
hostPaths: [overrides.runtimeDir],
|
|
82
|
-
containerPath: '/run/agent-infra/runtime',
|
|
83
|
-
readOnly: false
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
98
|
return mounts;
|
|
87
99
|
}
|
|
88
100
|
//# sourceMappingURL=mounts.js.map
|
|
@@ -174,7 +174,6 @@ 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,
|
|
178
177
|
project: params.config.project,
|
|
179
178
|
container: params.container,
|
|
180
179
|
identity: params.workspace
|
|
@@ -523,7 +522,6 @@ function expectedMounts(params) {
|
|
|
523
522
|
});
|
|
524
523
|
const control = sandboxControlPaths({
|
|
525
524
|
base: config.controlBase ?? path.join(config.home, '.agent-infra', 'sandbox-control'),
|
|
526
|
-
repoRoot: config.repoRoot,
|
|
527
525
|
project: config.project,
|
|
528
526
|
container: params.container,
|
|
529
527
|
identity: params.workspace
|
|
@@ -546,6 +544,7 @@ function expectedMounts(params) {
|
|
|
546
544
|
}),
|
|
547
545
|
workspaceViewRoot: view.root,
|
|
548
546
|
controlDir: control.channelDir,
|
|
547
|
+
...(params.workspace.mode === 'task-bound' ? { controlRootDir: control.root } : {}),
|
|
549
548
|
controlStatusDir: control.statusDir,
|
|
550
549
|
...(taskSources === null
|
|
551
550
|
? {}
|
|
@@ -926,7 +925,7 @@ export async function ensureSandboxReady(params) {
|
|
|
926
925
|
let completedReentry = null;
|
|
927
926
|
if (params.reentry === 'completed' && params.workspace?.mode === 'task-bound') {
|
|
928
927
|
const control = sandboxControlPaths({
|
|
929
|
-
base: params.config.controlBase,
|
|
928
|
+
base: params.config.controlBase, project: params.config.project,
|
|
930
929
|
container: params.row.name, identity: params.workspace
|
|
931
930
|
});
|
|
932
931
|
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,
|
|
571
|
+
base: config.controlBase, 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
|
|
@@ -763,6 +763,21 @@ async function rmPurgeCore(config, tools, prompt = {}) {
|
|
|
763
763
|
const containers = runEngine(engine, 'docker', [
|
|
764
764
|
'ps', '-a', '--filter', `label=${sandboxLabel(config)}`, '--format', '{{.Names}}'
|
|
765
765
|
]).split('\n').filter(Boolean);
|
|
766
|
+
const controlRoots = rows.flatMap((row) => {
|
|
767
|
+
if (!row.branch)
|
|
768
|
+
return [];
|
|
769
|
+
const identity = row.workspaceMode === 'task-bound' && row.taskId
|
|
770
|
+
? { mode: 'task-bound', taskId: row.taskId }
|
|
771
|
+
: row.workspaceMode === 'branch-only'
|
|
772
|
+
? { mode: 'branch-only' }
|
|
773
|
+
: null;
|
|
774
|
+
if (!identity)
|
|
775
|
+
return [];
|
|
776
|
+
return [sandboxControlPaths({
|
|
777
|
+
base: config.controlBase, project: config.project,
|
|
778
|
+
container: row.name, identity
|
|
779
|
+
}).root];
|
|
780
|
+
});
|
|
766
781
|
const completedTaskIds = [...new Set(rows
|
|
767
782
|
.filter((row) => row.workspaceMode === 'task-bound' && row.taskId)
|
|
768
783
|
.map((row) => row.taskId))];
|
|
@@ -801,9 +816,9 @@ async function rmPurgeCore(config, tools, prompt = {}) {
|
|
|
801
816
|
else
|
|
802
817
|
cleanupCompleted = false;
|
|
803
818
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
819
|
+
fs.rmSync(path.join(config.workspaceViewBase, config.project), { recursive: true, force: true });
|
|
820
|
+
for (const root of new Set(controlRoots))
|
|
821
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
807
822
|
if (cleanupCompleted) {
|
|
808
823
|
for (const taskId of completedTaskIds)
|
|
809
824
|
releaseStaleShortIdRegistry(config.repoRoot, taskId);
|
|
@@ -2,7 +2,6 @@ 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';
|
|
6
5
|
import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
|
|
7
6
|
export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
|
|
8
7
|
'active',
|
|
@@ -27,7 +26,17 @@ export function sandboxWorkspaceViewPaths(params) {
|
|
|
27
26
|
}
|
|
28
27
|
export function sandboxControlPaths(params) {
|
|
29
28
|
const root = params.identity.mode === 'task-bound'
|
|
30
|
-
?
|
|
29
|
+
? (() => {
|
|
30
|
+
if (!/^TASK-\d{8}-\d{6}$/.test(params.identity.taskId)
|
|
31
|
+
|| !params.project || path.basename(params.project) !== params.project || params.project === '.' || params.project === '..') {
|
|
32
|
+
throw new Error('SANDBOX_CONTROL_ROOT_INVALID');
|
|
33
|
+
}
|
|
34
|
+
const candidate = path.resolve(params.base, params.project, 'tasks', params.identity.taskId);
|
|
35
|
+
const relative = path.relative(path.resolve(params.base), candidate);
|
|
36
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
37
|
+
throw new Error('SANDBOX_CONTROL_ROOT_INVALID');
|
|
38
|
+
return candidate;
|
|
39
|
+
})()
|
|
31
40
|
: (() => {
|
|
32
41
|
const digest = createHash('sha256').update('branch-only').digest('hex').slice(0, 16);
|
|
33
42
|
return path.resolve(params.base, params.project, params.container, digest);
|
|
@@ -50,6 +59,22 @@ function assertSafeDirectory(directory, expectedBase) {
|
|
|
50
59
|
throw new Error(`Sandbox workspace view must not be a symbolic link: ${directory}`);
|
|
51
60
|
}
|
|
52
61
|
}
|
|
62
|
+
function assertSafeTaskBoundControlRoot(basePath, root) {
|
|
63
|
+
const relative = path.relative(basePath, root);
|
|
64
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
65
|
+
throw new Error(`Sandbox control root escapes its configured base: ${root}`);
|
|
66
|
+
}
|
|
67
|
+
if (fs.existsSync(basePath) && fs.lstatSync(basePath).isSymbolicLink()) {
|
|
68
|
+
throw new Error(`Sandbox control base must not be a symbolic link: ${basePath}`);
|
|
69
|
+
}
|
|
70
|
+
let current = basePath;
|
|
71
|
+
for (const segment of relative.split(path.sep)) {
|
|
72
|
+
current = path.join(current, segment);
|
|
73
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
74
|
+
throw new Error(`Sandbox control root must not contain a symbolic link: ${current}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
53
78
|
function assertSafeMountTargetPath(target, worktreeRoot, canonicalWorktreeRoot) {
|
|
54
79
|
const relative = path.relative(worktreeRoot, target);
|
|
55
80
|
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
@@ -203,7 +228,12 @@ export function materializeSandboxWorkspaceView(params) {
|
|
|
203
228
|
}
|
|
204
229
|
export function materializeSandboxControl(params) {
|
|
205
230
|
const { root, channelDir, statusDir, processingDir, runtimeDir, manifestPath } = sandboxControlPaths(params);
|
|
206
|
-
|
|
231
|
+
if (params.identity.mode === 'task-bound') {
|
|
232
|
+
assertSafeTaskBoundControlRoot(path.resolve(params.base), root);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
assertSafeDirectory(root, path.resolve(params.base));
|
|
236
|
+
}
|
|
207
237
|
if (fs.existsSync(root)) {
|
|
208
238
|
if (!params.replacementLease || path.resolve(params.replacementLease.root) !== root) {
|
|
209
239
|
throw new Error('SANDBOX_CONTROL_REPLACEMENT_REQUIRED');
|
|
@@ -261,9 +291,16 @@ export function finalizeSandboxControlManifest(setup, identity) {
|
|
|
261
291
|
containerIdentity: { id: identity.id, labels: { ...identity.labels } },
|
|
262
292
|
authorityEvidence: identity.authorityEvidence
|
|
263
293
|
};
|
|
294
|
+
const persistedManifest = {
|
|
295
|
+
...manifest,
|
|
296
|
+
channelDir: 'channel',
|
|
297
|
+
publicStatusDir: 'public',
|
|
298
|
+
processingDir: 'processing',
|
|
299
|
+
runtimeDir: 'runtime'
|
|
300
|
+
};
|
|
264
301
|
const temporary = `${setup.manifestPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
265
302
|
try {
|
|
266
|
-
fs.writeFileSync(temporary, `${JSON.stringify(
|
|
303
|
+
fs.writeFileSync(temporary, `${JSON.stringify(persistedManifest)}\n`, { mode: 0o600, flag: 'wx' });
|
|
267
304
|
fs.renameSync(temporary, setup.manifestPath);
|
|
268
305
|
}
|
|
269
306
|
finally {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
3
3
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
4
|
+
import { sandboxControlPaths } from '../sandbox/workspace-view.js';
|
|
4
5
|
import { getAgentClientAdapter } from '../agent-clients/registry.js';
|
|
5
6
|
import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, awaitOrchestrationDelegationActivation, beginOrResumeOrchestration, dispatchOrchestrationDelegation, OrchestrationStateError, pauseOrchestration, prepareOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from './orchestration.js';
|
|
6
7
|
import { applyTaskFinalization } from './finalization.js';
|
|
@@ -25,21 +26,14 @@ function absolute(name, value) {
|
|
|
25
26
|
invalidContext(`${name} must be absolute`);
|
|
26
27
|
return path.resolve(value);
|
|
27
28
|
}
|
|
28
|
-
function binding(value) {
|
|
29
|
-
if (value === null || value === undefined)
|
|
30
|
-
return value;
|
|
31
|
-
if (!/^[a-f0-9]{64}$/u.test(value.instanceDigest) || !value.controlGeneration) {
|
|
32
|
-
invalidContext('controller binding is invalid');
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
29
|
export function createDirectHostExecutionContext(params) {
|
|
37
30
|
return {
|
|
38
31
|
source: 'direct-host',
|
|
39
32
|
mode: 'direct-host',
|
|
40
33
|
repoRoot: absolute('repoRoot', params.repoRoot),
|
|
41
|
-
...(params.
|
|
42
|
-
...(params.
|
|
34
|
+
...(params.controlBase === undefined ? {} : { controlBase: absolute('controlBase', params.controlBase) }),
|
|
35
|
+
...(params.project === undefined ? {} : { project: params.project }),
|
|
36
|
+
...(params.runtimeDir === undefined ? {} : { runtimeDir: absolute('runtimeDir', params.runtimeDir) })
|
|
43
37
|
};
|
|
44
38
|
}
|
|
45
39
|
export function createSandboxExecutorExecutionContext(params) {
|
|
@@ -59,8 +53,7 @@ export function createSandboxExecutorExecutionContext(params) {
|
|
|
59
53
|
taskId: params.taskId,
|
|
60
54
|
generation: params.generation,
|
|
61
55
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
62
|
-
requestId: params.requestId
|
|
63
|
-
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
56
|
+
requestId: params.requestId
|
|
64
57
|
};
|
|
65
58
|
}
|
|
66
59
|
export function assertTaskControlExecutionContext(context) {
|
|
@@ -70,7 +63,10 @@ export function assertTaskControlExecutionContext(context) {
|
|
|
70
63
|
absolute('repoRoot', context.repoRoot);
|
|
71
64
|
if (context.runtimeDir !== undefined)
|
|
72
65
|
absolute('runtimeDir', context.runtimeDir);
|
|
73
|
-
|
|
66
|
+
if (Boolean(context.controlBase) !== Boolean(context.project))
|
|
67
|
+
invalidContext('control configuration is incomplete');
|
|
68
|
+
if (context.controlBase !== undefined)
|
|
69
|
+
absolute('controlBase', context.controlBase);
|
|
74
70
|
return;
|
|
75
71
|
}
|
|
76
72
|
if (context.mode !== 'task-bound-sandbox' || !/^TASK-\d{8}-\d{6}$/u.test(context.taskId)
|
|
@@ -84,7 +80,6 @@ export function assertTaskControlExecutionContext(context) {
|
|
|
84
80
|
if (path.resolve(context.runtimeDir) !== path.join(path.dirname(path.resolve(context.manifestPath)), 'runtime')) {
|
|
85
81
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
86
82
|
}
|
|
87
|
-
binding(context.controllerBinding);
|
|
88
83
|
}
|
|
89
84
|
function boundTaskRef(context, taskRef) {
|
|
90
85
|
return context.source === 'sandbox-executor' ? context.taskId : taskRef;
|
|
@@ -115,6 +110,8 @@ function lifecycleTaskId(request, repoRoot) {
|
|
|
115
110
|
}
|
|
116
111
|
}
|
|
117
112
|
async function applyLifecycleWithAuthority(context, request) {
|
|
113
|
+
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
114
|
+
const controlRoot = taskId ? controlRootForTask(context, taskId) : undefined;
|
|
118
115
|
const execute = async (underTaskLock = false) => {
|
|
119
116
|
if (request.intent === 'recover-started') {
|
|
120
117
|
const adapter = isAgentClientId(request.agent)
|
|
@@ -130,10 +127,17 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
130
127
|
return adapter.recoverStarted(request, { repoRoot: context.repoRoot });
|
|
131
128
|
}
|
|
132
129
|
return underTaskLock
|
|
133
|
-
? applyTaskLifecycleWithinTaskLock(request, {
|
|
134
|
-
|
|
130
|
+
? applyTaskLifecycleWithinTaskLock(request, {
|
|
131
|
+
repoRoot: context.repoRoot,
|
|
132
|
+
...(controlRoot ? { controlRoot } : {}),
|
|
133
|
+
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {})
|
|
134
|
+
}, taskId)
|
|
135
|
+
: applyTaskLifecycle(request, {
|
|
136
|
+
repoRoot: context.repoRoot,
|
|
137
|
+
...(controlRoot ? { controlRoot } : {}),
|
|
138
|
+
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {})
|
|
139
|
+
});
|
|
135
140
|
};
|
|
136
|
-
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
137
141
|
if (!taskId)
|
|
138
142
|
return await execute();
|
|
139
143
|
try {
|
|
@@ -153,6 +157,18 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
153
157
|
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
154
158
|
}
|
|
155
159
|
}
|
|
160
|
+
function controlRootForTask(context, taskId) {
|
|
161
|
+
if (context.source === 'sandbox-executor')
|
|
162
|
+
return path.dirname(context.manifestPath);
|
|
163
|
+
if (!context.controlBase || !context.project)
|
|
164
|
+
return undefined;
|
|
165
|
+
return sandboxControlPaths({
|
|
166
|
+
base: context.controlBase,
|
|
167
|
+
project: context.project,
|
|
168
|
+
container: '',
|
|
169
|
+
identity: { mode: 'task-bound', taskId }
|
|
170
|
+
}).root;
|
|
171
|
+
}
|
|
156
172
|
function orchestration(context, operation) {
|
|
157
173
|
const taskRef = boundTaskRef(context, operation.taskRef);
|
|
158
174
|
const input = operation.input;
|
|
@@ -219,6 +235,10 @@ export function dispatchTaskControlOperation(context, operation) {
|
|
|
219
235
|
});
|
|
220
236
|
}
|
|
221
237
|
if (operation.family === 'task-finalization') {
|
|
238
|
+
const resolved = resolveTaskRef(boundTaskRef(context, operation.request.taskRef), { repoRoot: context.repoRoot });
|
|
239
|
+
const controlRoot = context.source === 'sandbox-executor'
|
|
240
|
+
? path.dirname(context.manifestPath)
|
|
241
|
+
: resolved.ok ? controlRootForTask(context, resolved.taskId) : undefined;
|
|
222
242
|
return applyTaskFinalization({
|
|
223
243
|
...operation.request,
|
|
224
244
|
taskRef: boundTaskRef(context, operation.request.taskRef)
|
|
@@ -228,6 +248,8 @@ export function dispatchTaskControlOperation(context, operation) {
|
|
|
228
248
|
controlBinding: { generation: context.generation, requestId: context.requestId },
|
|
229
249
|
manifestPath: context.manifestPath
|
|
230
250
|
} : {}),
|
|
251
|
+
...(controlRoot ? { controlRoot } : {}),
|
|
252
|
+
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {}),
|
|
231
253
|
preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
|
|
232
254
|
});
|
|
233
255
|
}
|
|
@@ -19,8 +19,7 @@ export const SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES = Object.freeze([
|
|
|
19
19
|
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'];
|
|
20
20
|
export const SANDBOX_CONTROL_RECOVERY_OPERATIONS = Object.freeze([
|
|
21
21
|
...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle', intent, class: 'lifecycle-mutation' })),
|
|
22
|
-
{ family: 'task-create', intent: 'create', class: 'task-create' }
|
|
23
|
-
...['controller.close', 'controller.verify'].map((intent) => ({ family: 'agent-client', intent, class: 'agent-client-operation' }))
|
|
22
|
+
{ family: 'task-create', intent: 'create', class: 'task-create' }
|
|
24
23
|
]);
|
|
25
24
|
export function digestControlRecoveryIntent(family, intent) {
|
|
26
25
|
return createHash('sha256').update(`${family}\0${intent}`, 'utf8').digest('hex');
|
|
@@ -894,7 +894,13 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
894
894
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
895
895
|
}
|
|
896
896
|
else {
|
|
897
|
-
const result = lifecycle({ taskRef: taskId, intent: 'complete', agent: request.agent }, {
|
|
897
|
+
const result = lifecycle({ taskRef: taskId, intent: 'complete', agent: request.agent }, {
|
|
898
|
+
repoRoot,
|
|
899
|
+
prepareOnly: true,
|
|
900
|
+
...(options.controlRoot ? { controlRoot: options.controlRoot } : {}),
|
|
901
|
+
...(options.controlProject ? { controlProject: options.controlProject } : {}),
|
|
902
|
+
...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {})
|
|
903
|
+
});
|
|
898
904
|
lifecycleResult = lifecycleStep(result);
|
|
899
905
|
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
900
906
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: lifecycleResult.error });
|
|
@@ -1037,7 +1043,12 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1037
1043
|
});
|
|
1038
1044
|
receipt = updateReceipt(repoRoot, receipt, { verification: 'pending' });
|
|
1039
1045
|
const lifecycleRequest = { taskRef: resolved.taskId, intent: 'complete', agent: request.agent };
|
|
1040
|
-
const lifecycleOptions = {
|
|
1046
|
+
const lifecycleOptions = {
|
|
1047
|
+
repoRoot,
|
|
1048
|
+
...(options.controlRoot ? { controlRoot: options.controlRoot } : {}),
|
|
1049
|
+
...(options.controlProject ? { controlProject: options.controlProject } : {}),
|
|
1050
|
+
...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {})
|
|
1051
|
+
};
|
|
1041
1052
|
const result = options.lifecycle
|
|
1042
1053
|
? options.lifecycle(lifecycleRequest, lifecycleOptions)
|
|
1043
1054
|
: applyTaskLifecycleWithinTaskLock(lifecycleRequest, lifecycleOptions, resolved.taskId);
|
|
@@ -215,6 +215,96 @@ function transferTaskContents(sourcePath, targetPath) {
|
|
|
215
215
|
throw new Error('completed target does not match task source after copy');
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
+
function pathExists(candidate) {
|
|
219
|
+
try {
|
|
220
|
+
fs.lstatSync(candidate);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
if (error.code === 'ENOENT')
|
|
225
|
+
return false;
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function assertTaskControlRootPath(controlRoot, taskId, expectedProject) {
|
|
230
|
+
if (path.basename(controlRoot) !== taskId || path.basename(path.dirname(controlRoot)) !== 'tasks'
|
|
231
|
+
|| (expectedProject !== undefined && path.basename(path.dirname(path.dirname(controlRoot))) !== expectedProject)) {
|
|
232
|
+
throw new Error('control root path does not match the task/project layout');
|
|
233
|
+
}
|
|
234
|
+
const managedBase = path.dirname(path.dirname(path.dirname(controlRoot)));
|
|
235
|
+
let current = managedBase;
|
|
236
|
+
for (const segment of path.relative(managedBase, controlRoot).split(path.sep).filter(Boolean)) {
|
|
237
|
+
const stat = fs.lstatSync(current);
|
|
238
|
+
if (stat.isSymbolicLink())
|
|
239
|
+
throw new Error('control root ancestor is a symbolic link');
|
|
240
|
+
current = path.join(current, segment);
|
|
241
|
+
}
|
|
242
|
+
const stat = fs.lstatSync(controlRoot);
|
|
243
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
244
|
+
throw new Error('control root is not a real directory');
|
|
245
|
+
}
|
|
246
|
+
function assertTaskControlManifestIdentity(controlRoot, taskId, repoRoot, expectedProject) {
|
|
247
|
+
const controlManifest = path.join(controlRoot, 'manifest.json');
|
|
248
|
+
if (!fs.existsSync(controlManifest))
|
|
249
|
+
throw new Error('control manifest is missing');
|
|
250
|
+
const manifestStat = fs.lstatSync(controlManifest);
|
|
251
|
+
if (!manifestStat.isFile() || manifestStat.isSymbolicLink())
|
|
252
|
+
throw new Error('control manifest is not a real file');
|
|
253
|
+
const manifest = JSON.parse(fs.readFileSync(controlManifest, 'utf8'));
|
|
254
|
+
const project = expectedProject ?? path.basename(path.dirname(path.dirname(controlRoot)));
|
|
255
|
+
if (manifest.mode !== 'task-bound' || manifest.taskId !== taskId || manifest.project !== project
|
|
256
|
+
|| typeof manifest.repoRoot !== 'string' || fs.realpathSync.native(manifest.repoRoot) !== fs.realpathSync.native(repoRoot)) {
|
|
257
|
+
throw new Error('control manifest identity does not match the task');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function taskBoundControlMoveSafety(request, controlRoot, options, taskId, repoRoot, validateManifestIdentity) {
|
|
261
|
+
if (!controlRoot)
|
|
262
|
+
return null;
|
|
263
|
+
const controlManifest = path.join(controlRoot, 'manifest.json');
|
|
264
|
+
const controlLease = path.join(controlRoot, 'lease.json');
|
|
265
|
+
const stateExists = pathExists(controlRoot) || pathExists(controlManifest) || pathExists(controlLease);
|
|
266
|
+
if (stateExists) {
|
|
267
|
+
try {
|
|
268
|
+
assertTaskControlRootPath(controlRoot, taskId, options.controlProject);
|
|
269
|
+
if (validateManifestIdentity) {
|
|
270
|
+
assertTaskControlManifestIdentity(controlRoot, taskId, repoRoot, options.controlProject);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const manifestStat = fs.lstatSync(controlManifest);
|
|
274
|
+
if (!manifestStat.isFile() || manifestStat.isSymbolicLink())
|
|
275
|
+
throw new Error('control manifest is not a real file');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return { code: 'LIFECYCLE_RUNTIME_MOVE_UNSAFE', message: 'task-bound sandbox control root is missing, malformed, or belongs to another task' };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (request.intent !== 'complete' && (pathExists(controlManifest) || pathExists(controlLease))) {
|
|
283
|
+
return { code: 'LIFECYCLE_RUNTIME_MOVE_UNSAFE', message: 'task-bound sandbox control state exists; stop and remove the verified sandbox before moving the task directory' };
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
function orchestrationMoveSafety(sourcePath) {
|
|
288
|
+
const orchestrationPath = path.join(sourcePath, '.runtime', 'orchestration.json');
|
|
289
|
+
if (!pathExists(orchestrationPath))
|
|
290
|
+
return null;
|
|
291
|
+
try {
|
|
292
|
+
const run = JSON.parse(fs.readFileSync(orchestrationPath, 'utf8'));
|
|
293
|
+
if (run.status === 'running' || run.pendingDelegation !== null && run.pendingDelegation !== undefined) {
|
|
294
|
+
return { code: 'LIFECYCLE_RUNTIME_MOVE_UNSAFE', message: 'task orchestration has an active or pending delegation; settle it before moving the task directory' };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return { code: 'LIFECYCLE_RUNTIME_MOVE_UNSAFE', message: 'task orchestration state is unreadable; refusing to move the task directory' };
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
function runtimeMoveSafety(request, movingTaskDirectory, options, taskId, repoRoot, sourcePath, validateControlManifestIdentity) {
|
|
303
|
+
if (!movingTaskDirectory)
|
|
304
|
+
return null;
|
|
305
|
+
const controlSafety = taskBoundControlMoveSafety(request, options.controlRoot, options, taskId, repoRoot, validateControlManifestIdentity);
|
|
306
|
+
return controlSafety ?? orchestrationMoveSafety(sourcePath);
|
|
307
|
+
}
|
|
218
308
|
function targetContainsTask(targetPath, taskId, request) {
|
|
219
309
|
try {
|
|
220
310
|
const stat = fs.lstatSync(targetPath);
|
|
@@ -546,37 +636,6 @@ function applyTaskLifecycleUnlocked(requestInput, options = {}) {
|
|
|
546
636
|
const targetPath = path.join(repoRoot, '.agents', 'workspace', spec.target, taskId);
|
|
547
637
|
const requiredSteps = stepsForIntent(request.intent);
|
|
548
638
|
const movingTaskDirectory = sourcePath !== targetPath;
|
|
549
|
-
const assertRuntimeMoveSafe = () => {
|
|
550
|
-
if (!movingTaskDirectory)
|
|
551
|
-
return null;
|
|
552
|
-
const controlManifest = path.join(sourcePath, '.runtime', 'sandbox-control', 'manifest.json');
|
|
553
|
-
const controlLease = path.join(sourcePath, '.runtime', 'sandbox-control', 'lease.json');
|
|
554
|
-
if (request.intent !== 'complete' && (fs.existsSync(controlManifest) || fs.existsSync(controlLease))) {
|
|
555
|
-
return {
|
|
556
|
-
code: 'LIFECYCLE_RUNTIME_MOVE_UNSAFE',
|
|
557
|
-
message: 'task-bound sandbox control state exists; stop and remove the verified sandbox before moving the task directory'
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
const orchestrationPath = path.join(sourcePath, '.runtime', 'orchestration.json');
|
|
561
|
-
if (fs.existsSync(orchestrationPath)) {
|
|
562
|
-
try {
|
|
563
|
-
const run = JSON.parse(fs.readFileSync(orchestrationPath, 'utf8'));
|
|
564
|
-
if (run.status === 'running' || run.pendingDelegation !== null && run.pendingDelegation !== undefined) {
|
|
565
|
-
return {
|
|
566
|
-
code: 'LIFECYCLE_RUNTIME_MOVE_UNSAFE',
|
|
567
|
-
message: 'task orchestration has an active or pending delegation; settle it before moving the task directory'
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
return {
|
|
573
|
-
code: 'LIFECYCLE_RUNTIME_MOVE_UNSAFE',
|
|
574
|
-
message: 'task orchestration state is unreadable; refusing to move the task directory'
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
return null;
|
|
579
|
-
};
|
|
580
639
|
const currentTaskFile = path.join(sourcePath, 'task.md');
|
|
581
640
|
const existingJournal = fs.existsSync(path.join(sourcePath, '.task-lifecycle.json'));
|
|
582
641
|
if (sourceState === spec.target && !existingJournal) {
|
|
@@ -599,7 +658,7 @@ function applyTaskLifecycleUnlocked(requestInput, options = {}) {
|
|
|
599
658
|
&& !(request.intent === 'complete' && targetContainsTask(targetPath, taskId, request))) {
|
|
600
659
|
return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
601
660
|
}
|
|
602
|
-
const moveSafety =
|
|
661
|
+
const moveSafety = runtimeMoveSafety(request, movingTaskDirectory, options, taskId, repoRoot, sourcePath, true);
|
|
603
662
|
if (moveSafety)
|
|
604
663
|
return failed(request, moveSafety, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
605
664
|
const content = io.readFileSync(currentTaskFile);
|
|
@@ -757,7 +816,7 @@ function applyTaskLifecycleUnlocked(requestInput, options = {}) {
|
|
|
757
816
|
if (fs.existsSync(targetPath) && !fs.existsSync(path.join(sourcePath, 'task.md')))
|
|
758
817
|
completed.add('directory-moved');
|
|
759
818
|
if (!completed.has('directory-moved')) {
|
|
760
|
-
const moveSafety =
|
|
819
|
+
const moveSafety = runtimeMoveSafety(request, movingTaskDirectory, options, taskId, repoRoot, sourcePath, false);
|
|
761
820
|
if (moveSafety)
|
|
762
821
|
return failed(request, moveSafety, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, completedSteps: [...completed], changed: completed.size > 0, timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
|
|
763
822
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.14",
|
|
4
|
+
"sourceInputHash": "24a7d69b687f9ea8fdee5f47f7e59d1b53e13452a7f4f8edd9ca5b494cb5a0d4",
|
|
5
|
+
"internalExecutableBuildHash": "9825f6d7ca5d82e4c5f774b94f0d5e94e1b312583406eeb89a74d717ad757c4a"
|
|
6
6
|
}
|