@fitlab-ai/agent-infra 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +56 -67
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +2 -1
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-orchestration.js +156 -291
- package/dist/lib/platform/pr-summary.js +48 -10
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +137 -5
- package/dist/lib/sandbox/control/lifecycle.js +3 -1
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/delegation-receipts.js +243 -2
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +187 -761
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +113 -60
- package/dist/lib/task/verification.js +2 -1
- package/dist/lib/task/workflow-warning-intents.js +50 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +83 -91
- package/lib/git/workflow.ts +11 -2
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +2 -1
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-orchestration.ts +20 -103
- package/lib/platform/pr-summary.ts +55 -11
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +163 -5
- package/lib/sandbox/control/lifecycle.ts +2 -1
- package/lib/sandbox/control/protocol.ts +80 -7
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +3 -3
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/delegation-receipts.ts +258 -1
- package/lib/task/finalization.ts +719 -0
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +217 -982
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +118 -63
- package/lib/task/verification.ts +3 -2
- package/lib/task/workflow-warning-intents.ts +57 -2
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -4,8 +4,17 @@ import path from 'node:path';
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { getProcessStartTime } from '../../server/process-state.ts';
|
|
6
6
|
import { createTask } from '../../task/create-service.ts';
|
|
7
|
+
import { applyTaskFinalization } from '../../task/finalization.ts';
|
|
8
|
+
import { verifyTaskEvent } from '../../task/verification.ts';
|
|
7
9
|
import { bindSandboxControlTask, type SandboxControlExecution, type SandboxControlManifest, type SandboxControlRequest } from './protocol.ts';
|
|
8
10
|
import { atomicWriteJson, executionPath, terminateSandboxControlExecution } from './state.ts';
|
|
11
|
+
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
12
|
+
import {
|
|
13
|
+
closeCodexControllerRegistration,
|
|
14
|
+
CodexControllerRegistrationError,
|
|
15
|
+
openCodexControllerRegistration,
|
|
16
|
+
resolveCodexControllerBinding
|
|
17
|
+
} from './controller-registration.ts';
|
|
9
18
|
|
|
10
19
|
export type SandboxControlExecutionResult = {
|
|
11
20
|
exitCode: number;
|
|
@@ -132,7 +141,70 @@ function waitForGate(nonce: string, timeoutMs = 2_000): Promise<void> {
|
|
|
132
141
|
});
|
|
133
142
|
}
|
|
134
143
|
|
|
135
|
-
function
|
|
144
|
+
function controllerFailure(error: unknown): SandboxControlExecutionResult {
|
|
145
|
+
const code = error instanceof CodexControllerRegistrationError
|
|
146
|
+
? error.code
|
|
147
|
+
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
148
|
+
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
149
|
+
const payload = {
|
|
150
|
+
version: 1,
|
|
151
|
+
status: 'failed',
|
|
152
|
+
changed: false,
|
|
153
|
+
lease: null,
|
|
154
|
+
error: { code, message: `${code}: controller request failed`, retryable: false }
|
|
155
|
+
};
|
|
156
|
+
return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function orchestrationFailure(code: string, message: string): SandboxControlExecutionResult {
|
|
160
|
+
return {
|
|
161
|
+
exitCode: 1,
|
|
162
|
+
stdout: `${JSON.stringify({
|
|
163
|
+
status: 'failed', changed: false, taskId: null, run: null, next: null,
|
|
164
|
+
error: { code, message }
|
|
165
|
+
})}\n`,
|
|
166
|
+
stderr: ''
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function finalizationResult(result: ReturnType<typeof applyTaskFinalization>): SandboxControlExecutionResult {
|
|
171
|
+
const payload = {
|
|
172
|
+
version: 1,
|
|
173
|
+
status: result.status,
|
|
174
|
+
changed: result.changed,
|
|
175
|
+
accepted: true,
|
|
176
|
+
result,
|
|
177
|
+
error: result.error
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
exitCode: result.status === 'completed' ? 0 : result.status === 'blocked' ? 2 : 1,
|
|
181
|
+
stdout: `${JSON.stringify(payload)}\n`,
|
|
182
|
+
stderr: ''
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isCodexPrepare(args: readonly string[]): boolean {
|
|
187
|
+
if (args[1] !== 'prepare') return false;
|
|
188
|
+
const values: string[] = [];
|
|
189
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
190
|
+
if (args[index] === '--client') values.push(args[index + 1] ?? '');
|
|
191
|
+
else if (args[index]?.startsWith('--client=')) values.push(args[index]!.slice('--client='.length));
|
|
192
|
+
}
|
|
193
|
+
return values.length === 1 && values[0] === 'codex';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
type ExecuteRequestOptions = Readonly<{
|
|
197
|
+
buildIdentity?: typeof computeLifecycleBuildIdentity;
|
|
198
|
+
resolveControllerBinding?: typeof resolveCodexControllerBinding;
|
|
199
|
+
spawnDomain?: typeof spawnSync;
|
|
200
|
+
}>;
|
|
201
|
+
|
|
202
|
+
export function executeRequest(
|
|
203
|
+
manifest: SandboxControlManifest,
|
|
204
|
+
manifestPath: string,
|
|
205
|
+
request: SandboxControlRequest,
|
|
206
|
+
options: ExecuteRequestOptions = {}
|
|
207
|
+
): SandboxControlExecutionResult {
|
|
136
208
|
if (request.family === 'task-create') {
|
|
137
209
|
const result = createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
138
210
|
return {
|
|
@@ -141,15 +213,101 @@ function executeRequest(manifest: SandboxControlManifest, request: SandboxContro
|
|
|
141
213
|
stderr: ''
|
|
142
214
|
};
|
|
143
215
|
}
|
|
216
|
+
if (request.family === 'codex-controller') {
|
|
217
|
+
try {
|
|
218
|
+
if (request.command === 'verify') {
|
|
219
|
+
const binding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
220
|
+
manifest,
|
|
221
|
+
manifestPath,
|
|
222
|
+
proof: request.controllerProof!,
|
|
223
|
+
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
exitCode: 0,
|
|
227
|
+
stdout: `${JSON.stringify({
|
|
228
|
+
version: 1,
|
|
229
|
+
status: 'verified',
|
|
230
|
+
changed: false,
|
|
231
|
+
lease: null,
|
|
232
|
+
binding: {
|
|
233
|
+
taskId: manifest.taskId,
|
|
234
|
+
controlGeneration: binding.controlGeneration,
|
|
235
|
+
controllerInstanceDigest: binding.instanceDigest
|
|
236
|
+
},
|
|
237
|
+
error: null
|
|
238
|
+
})}\n`,
|
|
239
|
+
stderr: ''
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const result = request.command === 'open'
|
|
243
|
+
? openCodexControllerRegistration({
|
|
244
|
+
manifest,
|
|
245
|
+
manifestPath,
|
|
246
|
+
controllerProcess: request.controllerProcess!,
|
|
247
|
+
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
248
|
+
})
|
|
249
|
+
: closeCodexControllerRegistration({
|
|
250
|
+
manifest,
|
|
251
|
+
manifestPath,
|
|
252
|
+
proof: request.controllerProof!
|
|
253
|
+
});
|
|
254
|
+
return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return controllerFailure(error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (request.family === 'task-finalization') {
|
|
260
|
+
return finalizationResult(applyTaskFinalization(
|
|
261
|
+
{ taskRef: manifest.taskId!, intent: 'complete', agent: request.agent },
|
|
262
|
+
{ repoRoot: manifest.repoRoot, preflight: (input, options) => verifyTaskEvent(
|
|
263
|
+
{ ...input, event: 'complete-task.hard-preflight' }, options
|
|
264
|
+
) }
|
|
265
|
+
));
|
|
266
|
+
}
|
|
144
267
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId!);
|
|
145
|
-
|
|
146
|
-
|
|
268
|
+
let controllerBinding: Readonly<{ instanceDigest: string; controlGeneration: string }> | null = null;
|
|
269
|
+
if (request.family === 'task-orchestration') {
|
|
270
|
+
if (isCodexPrepare(request.args)) {
|
|
271
|
+
if (!request.controllerProof) {
|
|
272
|
+
return orchestrationFailure(
|
|
273
|
+
'CODEX_SANDBOX_CONTROLLER_PROOF_REQUIRED',
|
|
274
|
+
'Codex prepare requires a current controller lease proof'
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
controllerBinding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
279
|
+
manifest,
|
|
280
|
+
manifestPath,
|
|
281
|
+
proof: request.controllerProof,
|
|
282
|
+
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
283
|
+
});
|
|
284
|
+
} catch (error) {
|
|
285
|
+
const code = error instanceof CodexControllerRegistrationError
|
|
286
|
+
? error.code
|
|
287
|
+
: 'CODEX_SANDBOX_CONTROLLER_PROOF_INVALID';
|
|
288
|
+
return orchestrationFailure(code, `${code}: Codex controller proof was rejected`);
|
|
289
|
+
}
|
|
290
|
+
} else if (request.controllerProof !== null) {
|
|
291
|
+
return orchestrationFailure(
|
|
292
|
+
'CODEX_SANDBOX_CONTROLLER_PROOF_INVALID',
|
|
293
|
+
'Controller proof is only accepted for canonical Codex prepare'
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
|
|
297
|
+
}
|
|
298
|
+
const result = (options.spawnDomain ?? spawnSync)(
|
|
147
299
|
process.execPath,
|
|
148
300
|
nodeEntryArgs(process.argv[1]!, [request.family, ...boundArgs]),
|
|
149
301
|
{
|
|
150
302
|
cwd: manifest.repoRoot,
|
|
151
303
|
encoding: 'utf8',
|
|
152
|
-
env: {
|
|
304
|
+
env: {
|
|
305
|
+
...safeEnv(process.env),
|
|
306
|
+
AGENT_INFRA_RUNTIME_DIR: manifest.runtimeDir,
|
|
307
|
+
...(controllerBinding
|
|
308
|
+
? { AGENT_INFRA_CONTROL_CONTROLLER_BINDING: JSON.stringify(controllerBinding) }
|
|
309
|
+
: {})
|
|
310
|
+
}
|
|
153
311
|
}
|
|
154
312
|
);
|
|
155
313
|
return { exitCode: result.status ?? 1, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
|
|
@@ -164,7 +322,7 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
|
|
|
164
322
|
if (!manifestPath) throw new Error('SANDBOX_CONTROL_EXECUTOR_MANIFEST_MISSING');
|
|
165
323
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as SandboxControlManifest;
|
|
166
324
|
if (fs.realpathSync.native(manifest.repoRoot) !== root) throw new Error('SANDBOX_CONTROL_EXECUTOR_ROOT_INVALID');
|
|
167
|
-
const result = executeRequest(manifest, request);
|
|
325
|
+
const result = executeRequest(manifest, manifestPath, request);
|
|
168
326
|
process.stdout.write(result.stdout);
|
|
169
327
|
process.stderr.write(result.stderr);
|
|
170
328
|
process.exitCode = result.exitCode;
|
|
@@ -105,7 +105,7 @@ async function waitForStartupTransition(
|
|
|
105
105
|
): Promise<void> {
|
|
106
106
|
const transitionPath = path.join(root, BROKER_STARTING_FILE);
|
|
107
107
|
const deadline = Date.now() + timeoutMs;
|
|
108
|
-
while (
|
|
108
|
+
while (true) {
|
|
109
109
|
const transition = readStartupOwner(transitionPath);
|
|
110
110
|
if (!fs.existsSync(transitionPath)) return;
|
|
111
111
|
if (transition && !ownerLive(transition.owner, identityProbe)) {
|
|
@@ -116,6 +116,7 @@ async function waitForStartupTransition(
|
|
|
116
116
|
}
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
119
|
+
if (Date.now() >= deadline) break;
|
|
119
120
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
120
121
|
}
|
|
121
122
|
throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { validateTaskCreateCandidate, type TaskCreateCandidateV1 } from '../../task/create.ts';
|
|
2
|
+
import { normalizeAgentToken } from '../../agent-clients/tokens.ts';
|
|
2
3
|
import type { ProcessIdentity } from '../../server/process-state.ts';
|
|
4
|
+
import type { CodexControllerLeaseProofV1 } from './controller-registration.ts';
|
|
3
5
|
|
|
4
6
|
export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
|
|
5
7
|
export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
|
|
6
8
|
export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
7
9
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
8
10
|
export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
|
|
9
|
-
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-orchestration', 'task-create'] as const;
|
|
11
|
+
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-orchestration', 'task-finalization', 'task-create', 'codex-controller'] as const;
|
|
10
12
|
export type SandboxControlTimingPolicy = Readonly<{
|
|
11
13
|
controlTickMs: number;
|
|
12
14
|
parkedBindingInitialMs: number;
|
|
@@ -43,15 +45,25 @@ export type SandboxControlBrokerOwner = ProcessIdentity & Readonly<{
|
|
|
43
45
|
generation: string;
|
|
44
46
|
}>;
|
|
45
47
|
type RequestBase = Readonly<{
|
|
46
|
-
version:
|
|
48
|
+
version: 3; id: string; token: string; generation: string; issuedAt: number; expiresAt: number;
|
|
49
|
+
controllerProcess: ProcessIdentity | null;
|
|
50
|
+
controllerProof: CodexControllerLeaseProofV1 | null;
|
|
47
51
|
}>;
|
|
48
52
|
export type SandboxTaskCommandRequest = RequestBase & Readonly<{
|
|
49
53
|
family: 'task-lifecycle' | 'task-orchestration'; args: string[];
|
|
50
54
|
}>;
|
|
55
|
+
export type SandboxTaskFinalizationRequest = RequestBase & Readonly<{
|
|
56
|
+
family: 'task-finalization'; operation: 'complete'; agent: string; args: [];
|
|
57
|
+
}>;
|
|
51
58
|
export type SandboxTaskCreateRequest = RequestBase & Readonly<{
|
|
52
59
|
family: 'task-create'; candidate: TaskCreateCandidateV1;
|
|
53
60
|
}>;
|
|
54
|
-
export type
|
|
61
|
+
export type SandboxCodexControllerRequest = RequestBase & Readonly<{
|
|
62
|
+
family: 'codex-controller';
|
|
63
|
+
command: 'open' | 'close' | 'verify';
|
|
64
|
+
args: [];
|
|
65
|
+
}>;
|
|
66
|
+
export type SandboxControlRequest = SandboxTaskCommandRequest | SandboxTaskFinalizationRequest | SandboxTaskCreateRequest | SandboxCodexControllerRequest;
|
|
55
67
|
export type SandboxControlError = Readonly<{ code: string; message: string; retryable: boolean }>;
|
|
56
68
|
export type SandboxControlResponse = Readonly<{
|
|
57
69
|
version: 2; id: string; phase: 'accepted' | 'completed' | 'rejected'; exitCode: number | null;
|
|
@@ -101,7 +113,7 @@ export function validateSandboxControlRequest(
|
|
|
101
113
|
}
|
|
102
114
|
const request = value as Record<string, unknown>;
|
|
103
115
|
if (
|
|
104
|
-
request.version !==
|
|
116
|
+
request.version !== 3 || typeof request.id !== 'string'
|
|
105
117
|
|| !/^[a-f0-9-]{16,64}$/.test(request.id) || request.token !== manifest.token
|
|
106
118
|
|| typeof request.family !== 'string' || !isSandboxControlFamily(request.family)
|
|
107
119
|
|| !Number.isSafeInteger(request.issuedAt) || !Number.isSafeInteger(request.expiresAt)
|
|
@@ -120,13 +132,47 @@ export function validateSandboxControlRequest(
|
|
|
120
132
|
fail('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit');
|
|
121
133
|
}
|
|
122
134
|
if (request.family === 'task-create') {
|
|
123
|
-
const expected = ['candidate', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
135
|
+
const expected = ['candidate', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
124
136
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')) {
|
|
125
137
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
126
138
|
}
|
|
139
|
+
if (request.controllerProcess !== null || request.controllerProof !== null) {
|
|
140
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-create cannot carry controller authority');
|
|
141
|
+
}
|
|
127
142
|
return { ...request, candidate: validateTaskCreateCandidate(request.candidate) } as SandboxTaskCreateRequest;
|
|
128
143
|
}
|
|
129
|
-
|
|
144
|
+
if (request.family === 'codex-controller') {
|
|
145
|
+
const expected = ['args', 'command', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
146
|
+
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
147
|
+
|| !Array.isArray(request.args) || request.args.length !== 0
|
|
148
|
+
|| !['open', 'close', 'verify'].includes(request.command as string)
|
|
149
|
+
|| !validControllerProcess(request.controllerProcess)
|
|
150
|
+
|| (request.command === 'open' && request.controllerProof !== null)
|
|
151
|
+
|| (request.command !== 'open' && !validControllerProof(request.controllerProof))
|
|
152
|
+
|| (request.command === 'verify'
|
|
153
|
+
&& JSON.stringify(request.controllerProcess) !== JSON.stringify((request.controllerProof as CodexControllerLeaseProofV1).controllerProcess))) {
|
|
154
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller request schema is invalid');
|
|
155
|
+
}
|
|
156
|
+
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
157
|
+
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot register a Codex controller');
|
|
158
|
+
}
|
|
159
|
+
return request as SandboxCodexControllerRequest;
|
|
160
|
+
}
|
|
161
|
+
if (request.family === 'task-finalization') {
|
|
162
|
+
const expected = ['agent', 'args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
|
|
163
|
+
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
164
|
+
|| request.operation !== 'complete'
|
|
165
|
+
|| !Array.isArray(request.args) || request.args.length !== 0
|
|
166
|
+
|| typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent
|
|
167
|
+
|| request.controllerProcess !== null || request.controllerProof !== null) {
|
|
168
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization request schema or authorization is invalid');
|
|
169
|
+
}
|
|
170
|
+
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
171
|
+
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot finalize tasks');
|
|
172
|
+
}
|
|
173
|
+
return request as SandboxTaskFinalizationRequest;
|
|
174
|
+
}
|
|
175
|
+
const expected = ['args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
130
176
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
131
177
|
|| !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
|
|
132
178
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
@@ -141,15 +187,42 @@ export function validateSandboxControlRequest(
|
|
|
141
187
|
&& request.args.some((arg) => arg === '--git-worktree-root' || arg.startsWith('--git-worktree-root='))) {
|
|
142
188
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'worktree binding is reserved for the control broker');
|
|
143
189
|
}
|
|
190
|
+
if (request.controllerProcess !== null
|
|
191
|
+
|| (request.controllerProof !== null && !validControllerProof(request.controllerProof))) {
|
|
192
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller authority is invalid');
|
|
193
|
+
}
|
|
194
|
+
if (request.family !== 'task-orchestration' && request.controllerProof !== null) {
|
|
195
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller proof is not allowed for this family');
|
|
196
|
+
}
|
|
144
197
|
return request as SandboxTaskCommandRequest;
|
|
145
198
|
}
|
|
146
199
|
|
|
147
200
|
export function bindSandboxControlTask(request: SandboxControlRequest, taskId: string): string[] {
|
|
148
|
-
if (request.family === 'task-create'
|
|
201
|
+
if (request.family === 'task-create' || request.family === 'codex-controller' || request.family === 'task-finalization') {
|
|
202
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
|
|
203
|
+
}
|
|
149
204
|
if (request.args.length === 0) fail('SANDBOX_CONTROL_REQUEST_INVALID', 'command arguments are required');
|
|
150
205
|
return [taskId, ...request.args.slice(1)];
|
|
151
206
|
}
|
|
152
207
|
|
|
208
|
+
function validControllerProcess(value: unknown): value is ProcessIdentity {
|
|
209
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
210
|
+
const processValue = value as Record<string, unknown>;
|
|
211
|
+
return Object.keys(processValue).sort().join(',') === 'pid,startTime'
|
|
212
|
+
&& Number.isSafeInteger(processValue.pid) && (processValue.pid as number) > 0
|
|
213
|
+
&& Number.isSafeInteger(processValue.startTime) && (processValue.startTime as number) >= 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function validControllerProof(value: unknown): value is CodexControllerLeaseProofV1 {
|
|
217
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
218
|
+
const proof = value as Record<string, unknown>;
|
|
219
|
+
return Object.keys(proof).sort().join(',') === 'controllerProcess,leaseId,leaseSecret,version'
|
|
220
|
+
&& proof.version === 1
|
|
221
|
+
&& typeof proof.leaseId === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseId)
|
|
222
|
+
&& typeof proof.leaseSecret === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseSecret)
|
|
223
|
+
&& validControllerProcess(proof.controllerProcess);
|
|
224
|
+
}
|
|
225
|
+
|
|
153
226
|
export function controlError(error: unknown): SandboxControlError {
|
|
154
227
|
if (error instanceof SandboxControlProtocolError) {
|
|
155
228
|
return { code: error.code, message: error.message, retryable: error.retryable };
|
package/lib/sandbox/index.ts
CHANGED
|
@@ -15,9 +15,11 @@ Commands:
|
|
|
15
15
|
rebuild [--quiet] [--refresh]
|
|
16
16
|
Rebuild the sandbox image (--refresh pulls base + tools)
|
|
17
17
|
refresh Sync host Claude Code credentials to all sandbox copies
|
|
18
|
-
rm <branch> | --unbound | --purge
|
|
19
|
-
Remove one sandbox
|
|
20
|
-
|
|
18
|
+
rm <branch | TASK-id> | --unbound | --purge
|
|
19
|
+
Remove one sandbox; use a full TASK-id for task-bound
|
|
20
|
+
cleanup and a branch for branch-only cleanup. --unbound
|
|
21
|
+
removes completed task-bound and branch-only sandboxes;
|
|
22
|
+
active, blocked, and archive tasks are protected.
|
|
21
23
|
start [--recreate] <branch | TASK-id | N>
|
|
22
24
|
Verify or recover an existing sandbox container;
|
|
23
25
|
optionally replace only the container on failure
|
|
@@ -36,6 +36,11 @@ export function removeWorktreeDir(
|
|
|
36
36
|
throw new Error(`Worktree permit target mismatch: ${dir}`);
|
|
37
37
|
}
|
|
38
38
|
verifyWorktreePermit(permit);
|
|
39
|
+
if (permit.snapshot.source === 'recovered') {
|
|
40
|
+
removeManagedDir(worktreeBase, dir);
|
|
41
|
+
runSafeFn('git', ['-C', repoRoot, 'worktree', 'prune']);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
39
44
|
try {
|
|
40
45
|
runFn('git', ['-C', repoRoot, 'worktree', 'remove', dir, '--force']);
|
|
41
46
|
} catch (error) {
|
|
@@ -5,7 +5,14 @@ import { isRemovedHashShortIdInput } from '../task/short-id.ts';
|
|
|
5
5
|
|
|
6
6
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
7
7
|
const SHORT_ID_RE = /^\d+$/;
|
|
8
|
-
const
|
|
8
|
+
export const TASK_WORKSPACE_STATES = ['active', 'completed', 'blocked', 'archive'] as const;
|
|
9
|
+
export type TaskWorkspaceState = typeof TASK_WORKSPACE_STATES[number];
|
|
10
|
+
export type TaskWorkspace = Readonly<{
|
|
11
|
+
taskId: string;
|
|
12
|
+
branch: string;
|
|
13
|
+
state: TaskWorkspaceState;
|
|
14
|
+
taskMd: string;
|
|
15
|
+
}>;
|
|
9
16
|
|
|
10
17
|
function resolveShortIdStrict(arg: string, repoRoot: string): string {
|
|
11
18
|
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
@@ -27,16 +34,30 @@ function stripQuotes(value: string): string {
|
|
|
27
34
|
return value.replace(/^(["'])(.*)\1$/, '$2');
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
function readTaskContent(repoRoot: string, taskId: string): string {
|
|
31
|
-
for (const
|
|
32
|
-
const taskPath = path.join(repoRoot, '.agents', 'workspace',
|
|
37
|
+
function readTaskContent(repoRoot: string, taskId: string): { content: string; state: TaskWorkspaceState; taskMd: string } {
|
|
38
|
+
for (const state of TASK_WORKSPACE_STATES) {
|
|
39
|
+
const taskPath = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
|
|
33
40
|
if (fs.existsSync(taskPath)) {
|
|
34
|
-
return fs.readFileSync(taskPath, 'utf8');
|
|
41
|
+
return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
|
|
35
42
|
}
|
|
36
43
|
}
|
|
37
44
|
throw new Error(`Task not found: ${taskId}`);
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspace[] {
|
|
48
|
+
return TASK_WORKSPACE_STATES.flatMap((state) => {
|
|
49
|
+
const taskMd = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
|
|
50
|
+
if (!fs.existsSync(taskMd)) return [];
|
|
51
|
+
const content = fs.readFileSync(taskMd, 'utf8');
|
|
52
|
+
return [{
|
|
53
|
+
taskId,
|
|
54
|
+
branch: resolveBranchFromTaskContent(content, taskId),
|
|
55
|
+
state,
|
|
56
|
+
taskMd
|
|
57
|
+
}];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
40
61
|
function resolveBranchFromTaskContent(content: string, taskId: string): string {
|
|
41
62
|
const frontmatterBranch = content.match(/^branch:\s*(.+)$/m);
|
|
42
63
|
if (frontmatterBranch?.[1]?.trim()) {
|
|
@@ -57,12 +78,42 @@ export function resolveTaskBranch(arg: string, repoRoot: string): string {
|
|
|
57
78
|
}
|
|
58
79
|
if (SHORT_ID_RE.test(arg)) {
|
|
59
80
|
const taskId = resolveShortIdStrict(arg, repoRoot);
|
|
60
|
-
const content = readTaskContent(repoRoot, taskId);
|
|
81
|
+
const { content } = readTaskContent(repoRoot, taskId);
|
|
61
82
|
return resolveBranchFromTaskContent(content, taskId);
|
|
62
83
|
}
|
|
63
84
|
if (!TASK_ID_RE.test(arg)) {
|
|
64
85
|
return arg;
|
|
65
86
|
}
|
|
66
|
-
const content = readTaskContent(repoRoot, arg);
|
|
87
|
+
const { content } = readTaskContent(repoRoot, arg);
|
|
67
88
|
return resolveBranchFromTaskContent(content, arg);
|
|
68
89
|
}
|
|
90
|
+
|
|
91
|
+
export function resolveTaskWorkspace(taskId: string, repoRoot: string): TaskWorkspace {
|
|
92
|
+
if (!TASK_ID_RE.test(taskId)) throw new Error(`Invalid task id: ${taskId}`);
|
|
93
|
+
const matches = taskWorkspaceCandidates(repoRoot, taskId);
|
|
94
|
+
if (matches.length === 0) throw new Error(`Task not found: ${taskId}`);
|
|
95
|
+
if (matches.length !== 1) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`SANDBOX_TASK_STATE_AMBIGUOUS: task ${taskId} exists in ${matches.map((match) => match.state).join(', ')}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return matches[0]!;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function listTaskWorkspaces(repoRoot: string, state: TaskWorkspaceState): TaskWorkspace[] {
|
|
104
|
+
const stateRoot = path.join(repoRoot, '.agents', 'workspace', state);
|
|
105
|
+
if (!fs.existsSync(stateRoot)) return [];
|
|
106
|
+
return fs.readdirSync(stateRoot, { withFileTypes: true })
|
|
107
|
+
.filter((entry) => entry.isDirectory() && TASK_ID_RE.test(entry.name))
|
|
108
|
+
.flatMap((entry) => {
|
|
109
|
+
const taskMd = path.join(stateRoot, entry.name, 'task.md');
|
|
110
|
+
if (!fs.existsSync(taskMd)) return [];
|
|
111
|
+
const content = fs.readFileSync(taskMd, 'utf8');
|
|
112
|
+
return [{
|
|
113
|
+
taskId: entry.name,
|
|
114
|
+
branch: resolveBranchFromTaskContent(content, entry.name),
|
|
115
|
+
state,
|
|
116
|
+
taskMd
|
|
117
|
+
}];
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRemovedHashShortIdInput } from '../task/short-id.ts';
|
|
4
|
+
import {
|
|
5
|
+
resolveTaskWorkspace,
|
|
6
|
+
type TaskWorkspaceState
|
|
7
|
+
} from './task-resolver.ts';
|
|
4
8
|
|
|
5
9
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
10
|
const SHORT_ID_RE = /^\d+$/;
|
|
@@ -9,6 +13,17 @@ export type SandboxWorkspaceIdentity =
|
|
|
9
13
|
| Readonly<{ mode: 'task-bound'; taskId: string; shortId: string }>
|
|
10
14
|
| Readonly<{ mode: 'branch-only' }>;
|
|
11
15
|
|
|
16
|
+
export type SandboxWorkspaceKey =
|
|
17
|
+
| Readonly<{ mode: 'task-bound'; taskId: string }>
|
|
18
|
+
| Readonly<{ mode: 'branch-only' }>;
|
|
19
|
+
|
|
20
|
+
export type SandboxCleanupTarget = Readonly<{
|
|
21
|
+
requestedRef: string;
|
|
22
|
+
branch: string;
|
|
23
|
+
workspace: SandboxWorkspaceKey;
|
|
24
|
+
taskState: TaskWorkspaceState | 'branch-only';
|
|
25
|
+
}>;
|
|
26
|
+
|
|
12
27
|
export type SandboxContainerWorkspaceIdentity =
|
|
13
28
|
| Readonly<{ mode: 'task-bound'; taskId: string }>
|
|
14
29
|
| Readonly<{ mode: 'branch-only' }>
|
|
@@ -101,6 +116,39 @@ export function resolveSandboxTarget(requestedRef: string, repoRoot: string): Sa
|
|
|
101
116
|
return { requestedRef, branch: requestedRef, workspace: { mode: 'branch-only' } };
|
|
102
117
|
}
|
|
103
118
|
|
|
119
|
+
export function resolveSandboxCleanupTarget(
|
|
120
|
+
requestedRef: string,
|
|
121
|
+
repoRoot: string,
|
|
122
|
+
options: Readonly<{ allowProtected?: boolean }> = {}
|
|
123
|
+
): SandboxCleanupTarget {
|
|
124
|
+
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
125
|
+
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (TASK_ID_RE.test(requestedRef)) {
|
|
129
|
+
const task = resolveTaskWorkspace(requestedRef, repoRoot);
|
|
130
|
+
if (!options.allowProtected && (task.state === 'blocked' || task.state === 'archive')) {
|
|
131
|
+
throw new Error(`SANDBOX_CLEANUP_STATE_UNSUPPORTED: task ${requestedRef} is ${task.state}`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
requestedRef,
|
|
135
|
+
branch: task.branch,
|
|
136
|
+
workspace: { mode: 'task-bound', taskId: task.taskId },
|
|
137
|
+
taskState: task.state
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const target = resolveSandboxTarget(requestedRef, repoRoot);
|
|
142
|
+
return {
|
|
143
|
+
requestedRef,
|
|
144
|
+
branch: target.branch,
|
|
145
|
+
workspace: target.workspace.mode === 'task-bound'
|
|
146
|
+
? { mode: 'task-bound', taskId: target.workspace.taskId }
|
|
147
|
+
: { mode: 'branch-only' },
|
|
148
|
+
taskState: target.workspace.mode === 'task-bound' ? 'active' : 'branch-only'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
104
152
|
export function parseSandboxWorkspaceIdentity(
|
|
105
153
|
labels: Readonly<Record<string, string>>,
|
|
106
154
|
keys: Readonly<{ mode: string; taskId: string }>
|
|
@@ -115,8 +163,8 @@ export function parseSandboxWorkspaceIdentity(
|
|
|
115
163
|
}
|
|
116
164
|
|
|
117
165
|
export function sameSandboxWorkspaceIdentity(
|
|
118
|
-
left: SandboxWorkspaceIdentity | SandboxContainerWorkspaceIdentity,
|
|
119
|
-
right: SandboxWorkspaceIdentity | SandboxContainerWorkspaceIdentity
|
|
166
|
+
left: SandboxWorkspaceIdentity | SandboxWorkspaceKey | SandboxContainerWorkspaceIdentity,
|
|
167
|
+
right: SandboxWorkspaceIdentity | SandboxWorkspaceKey | SandboxContainerWorkspaceIdentity
|
|
120
168
|
): boolean {
|
|
121
169
|
return left.mode === right.mode
|
|
122
170
|
&& (left.mode !== 'task-bound' || (right.mode === 'task-bound' && left.taskId === right.taskId));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import type { SandboxWorkspaceIdentity } from './workspace-identity.ts';
|
|
4
|
+
import type { SandboxWorkspaceIdentity, SandboxWorkspaceKey } from './workspace-identity.ts';
|
|
5
5
|
import type { SandboxControlManifest } from './control/protocol.ts';
|
|
6
6
|
|
|
7
7
|
export type SandboxWorkspaceView = Readonly<{
|
|
@@ -30,7 +30,7 @@ export function sandboxWorkspaceViewPaths(params: Readonly<{
|
|
|
30
30
|
base: string;
|
|
31
31
|
project: string;
|
|
32
32
|
container: string;
|
|
33
|
-
identity: SandboxWorkspaceIdentity;
|
|
33
|
+
identity: SandboxWorkspaceIdentity | SandboxWorkspaceKey;
|
|
34
34
|
}>): SandboxWorkspaceView {
|
|
35
35
|
const identityKey = params.identity.mode === 'task-bound'
|
|
36
36
|
? `task-bound:${params.identity.taskId}`
|
|
@@ -60,7 +60,7 @@ export function sandboxControlPaths(params: Readonly<{
|
|
|
60
60
|
base: string;
|
|
61
61
|
project: string;
|
|
62
62
|
container: string;
|
|
63
|
-
identity: SandboxWorkspaceIdentity;
|
|
63
|
+
identity: SandboxWorkspaceIdentity | SandboxWorkspaceKey;
|
|
64
64
|
}>): Readonly<{ root: string; channelDir: string; statusDir: string; processingDir: string; runtimeDir: string; manifestPath: string }> {
|
|
65
65
|
const identityKey = params.identity.mode === 'task-bound'
|
|
66
66
|
? `task-bound:${params.identity.taskId}`
|