@fitlab-ai/agent-infra 0.11.13 → 0.11.15
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 +7 -5
- 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/pr-change-report.js +10 -3
- package/dist/lib/platform/release-notes.js +2 -2
- package/dist/lib/process-data/index.js +15 -4
- 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 +22 -37
- 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-identity.js +30 -15
- 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 +122 -35
- package/dist/lib/task/short-id.js +2 -2
- 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 +8 -6
- 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/pr-change-report.ts +9 -3
- package/lib/platform/release-notes.ts +2 -2
- package/lib/process-data/index.ts +15 -4
- 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 +22 -37
- 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-identity.ts +36 -13
- 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 +135 -33
- package/lib/task/short-id.ts +5 -2
- 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/complete-task/SKILL.en.md +10 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -2
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +12 -2
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +12 -2
- 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-manual-validation/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +13 -2
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +13 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/watch-pr/SKILL.en.md +19 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +14 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +15 -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/runtime/agent-runtime.js +0 -33
- package/dist/lib/sandbox/control/agent-operation.js +0 -34
- package/dist/lib/task/runtime-paths.js +0 -10
- 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/runtime/agent-runtime.ts +0 -41
- package/lib/sandbox/control/agent-operation.ts +0 -49
- package/lib/task/runtime-paths.ts +0 -12
|
@@ -5,14 +5,13 @@ export const SANDBOX_CONTROL_STATUS_MOUNT = '/run/agent-infra/control-status';
|
|
|
5
5
|
const TASK_MARKER_KEYS = [
|
|
6
6
|
'AGENT_INFRA_SANDBOX',
|
|
7
7
|
'AGENT_INFRA_TASK_ID',
|
|
8
|
-
'AGENT_INFRA_CONTROL_TOKEN',
|
|
9
8
|
'AGENT_INFRA_CONTROL_GENERATION',
|
|
10
9
|
'AGENT_INFRA_CONTROL_DIR',
|
|
10
|
+
'AGENT_INFRA_CONTROL_ROOT_DIR',
|
|
11
11
|
'AGENT_INFRA_CONTROL_STATUS_DIR',
|
|
12
12
|
'AGENT_INFRA_RUNTIME_DIR'
|
|
13
13
|
];
|
|
14
14
|
const TASK_CONTROL_MARKER_KEYS = [
|
|
15
|
-
'AGENT_INFRA_CONTROL_TOKEN',
|
|
16
15
|
'AGENT_INFRA_CONTROL_GENERATION',
|
|
17
16
|
'AGENT_INFRA_CONTROL_DIR',
|
|
18
17
|
'AGENT_INFRA_CONTROL_STATUS_DIR'
|
|
@@ -77,7 +76,6 @@ export function hasSandboxControlMarker(env = process.env) {
|
|
|
77
76
|
export function resolveSandboxControlTransport(env = process.env, options = {}) {
|
|
78
77
|
const markerState = sandboxTaskMarkerState(env);
|
|
79
78
|
const hasAnyMarker = markerState !== 'none'
|
|
80
|
-
|| Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
|
|
81
79
|
|| Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
|
|
82
80
|
const fixedStatusDir = options.statusMountPath ?? SANDBOX_CONTROL_STATUS_MOUNT;
|
|
83
81
|
const fixedStatusProbe = path.isAbsolute(fixedStatusDir)
|
|
@@ -98,6 +96,10 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
98
96
|
if (!hasCompleteConfig) {
|
|
99
97
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
100
98
|
}
|
|
99
|
+
if (markerState === 'task-bound'
|
|
100
|
+
&& (!env.AGENT_INFRA_CONTROL_ROOT_DIR || !path.isAbsolute(env.AGENT_INFRA_CONTROL_ROOT_DIR))) {
|
|
101
|
+
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
102
|
+
}
|
|
101
103
|
const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
102
104
|
const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
103
105
|
? nativeDirectoryProbe(configuredStatusDir) : 'absent';
|
|
@@ -108,7 +110,7 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
108
110
|
? configuredStatusProbe === 'present' ? configuredStatusDir : null
|
|
109
111
|
: fixedStatusMounted ? fixedStatusDir : null;
|
|
110
112
|
const statusMounted = statusDir !== null;
|
|
111
|
-
if (env.AGENT_INFRA_EXECUTOR_MANIFEST
|
|
113
|
+
if (env.AGENT_INFRA_EXECUTOR_MANIFEST) {
|
|
112
114
|
return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
|
|
113
115
|
}
|
|
114
116
|
const taskBound = Boolean(env.AGENT_INFRA_TASK_ID);
|
|
@@ -124,9 +126,6 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
124
126
|
? 'SANDBOX_CONTROL_IDENTITY_MISSING'
|
|
125
127
|
: 'SANDBOX_CONTROL_IDENTITY_MALFORMED' };
|
|
126
128
|
}
|
|
127
|
-
if (sentinel.generation !== env.AGENT_INFRA_CONTROL_GENERATION) {
|
|
128
|
-
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_GENERATION_MISMATCH' };
|
|
129
|
-
}
|
|
130
129
|
if (sentinel.controlRootId !== env.AGENT_INFRA_CONTROL_ROOT_ID) {
|
|
131
130
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_ROOT_ID_MISMATCH' };
|
|
132
131
|
}
|
|
@@ -134,7 +133,7 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
134
133
|
&& (sentinel.taskId !== env.AGENT_INFRA_TASK_ID || !runtime)) {
|
|
135
134
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
|
|
136
135
|
}
|
|
137
|
-
if (sentinel.mode === 'branch-only' && (taskBound || runtime)) {
|
|
136
|
+
if (sentinel.mode === 'branch-only' && (taskBound || runtime || Boolean(env.AGENT_INFRA_CONTROL_ROOT_DIR))) {
|
|
138
137
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
|
|
139
138
|
}
|
|
140
139
|
return { kind: options.localWorkflow ? 'sandbox-local' : 'broker-client', reasonCode: null };
|
|
@@ -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);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { isRemovedHashShortIdInput } from '../task/short-id.js';
|
|
4
|
-
import {
|
|
5
|
-
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
3
|
+
import { configuredShortIdLength, inspectShortIdRegistry, isRemovedHashShortIdInput, normalizeShortIdInput } from '../task/short-id.js';
|
|
4
|
+
import { TASK_WORKSPACE_STATES, listTaskWorkspaces, resolveTaskWorkspace } from './task-resolver.js';
|
|
6
5
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
7
6
|
const SHORT_ID_RE = /^\d+$/;
|
|
8
7
|
function stripQuotes(value) {
|
|
@@ -42,12 +41,30 @@ function taskIdentity(repoRoot, taskId, registry) {
|
|
|
42
41
|
function cleanupTaskId(requestedRef, repoRoot) {
|
|
43
42
|
if (TASK_ID_RE.test(requestedRef))
|
|
44
43
|
return requestedRef;
|
|
45
|
-
|
|
44
|
+
const shortIdLength = configuredShortIdLength(repoRoot);
|
|
45
|
+
const normalized = normalizeShortIdInput(requestedRef, { shortIdLength });
|
|
46
|
+
if (normalized.kind === 'error')
|
|
47
|
+
throw new Error(normalized.message);
|
|
48
|
+
if (normalized.kind !== 'shortId')
|
|
46
49
|
return null;
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
throw new Error(
|
|
50
|
-
|
|
50
|
+
const registry = inspectShortIdRegistry(repoRoot, shortIdLength);
|
|
51
|
+
if (registry.status !== 'valid')
|
|
52
|
+
throw new Error(registry.error.message);
|
|
53
|
+
const match = [...registry.shortIds].find(([, shortId]) => shortId === normalized.value);
|
|
54
|
+
if (!match) {
|
|
55
|
+
throw new Error(`Short id '${normalized.value}' not found in active task registry`);
|
|
56
|
+
}
|
|
57
|
+
return match[0];
|
|
58
|
+
}
|
|
59
|
+
function cleanupTaskIdByBranch(requestedRef, repoRoot) {
|
|
60
|
+
const taskIds = new Set(TASK_WORKSPACE_STATES
|
|
61
|
+
.flatMap((state) => listTaskWorkspaces(repoRoot, state))
|
|
62
|
+
.filter((task) => task.branch === requestedRef)
|
|
63
|
+
.map((task) => task.taskId));
|
|
64
|
+
if (taskIds.size > 1) {
|
|
65
|
+
throw new Error(`SANDBOX_TASK_IDENTITY_AMBIGUOUS: branch '${requestedRef}' is bound to multiple tasks`);
|
|
66
|
+
}
|
|
67
|
+
return taskIds.values().next().value ?? null;
|
|
51
68
|
}
|
|
52
69
|
export function resolveSandboxTarget(requestedRef, repoRoot) {
|
|
53
70
|
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
@@ -94,7 +111,8 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
|
|
|
94
111
|
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
95
112
|
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
96
113
|
}
|
|
97
|
-
const taskId = cleanupTaskId(requestedRef, repoRoot)
|
|
114
|
+
const taskId = cleanupTaskId(requestedRef, repoRoot)
|
|
115
|
+
?? cleanupTaskIdByBranch(requestedRef, repoRoot);
|
|
98
116
|
if (taskId) {
|
|
99
117
|
let task;
|
|
100
118
|
try {
|
|
@@ -123,14 +141,11 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
|
|
|
123
141
|
taskState: task.state
|
|
124
142
|
};
|
|
125
143
|
}
|
|
126
|
-
const target = resolveSandboxTarget(requestedRef, repoRoot);
|
|
127
144
|
return {
|
|
128
145
|
requestedRef,
|
|
129
|
-
branch:
|
|
130
|
-
workspace:
|
|
131
|
-
|
|
132
|
-
: { mode: 'branch-only' },
|
|
133
|
-
taskState: target.workspace.mode === 'task-bound' ? 'active' : 'branch-only'
|
|
146
|
+
branch: requestedRef,
|
|
147
|
+
workspace: { mode: 'branch-only' },
|
|
148
|
+
taskState: 'branch-only'
|
|
134
149
|
};
|
|
135
150
|
}
|
|
136
151
|
export function parseSandboxWorkspaceIdentity(labels, keys) {
|
|
@@ -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);
|