@fitlab-ai/agent-infra 0.11.12 → 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-lifecycle/store.js +11 -25
- 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/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +29 -20
- package/dist/lib/internal/task-lifecycle.js +4 -2
- package/dist/lib/internal/task-operation-registry.js +1 -3
- package/dist/lib/internal/task-validate.js +14 -124
- package/dist/lib/platform/github-client.js +33 -3
- 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 +11 -6
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/config.js +12 -0
- package/dist/lib/sandbox/control/audit.js +1 -2
- package/dist/lib/sandbox/control/client.js +15 -110
- package/dist/lib/sandbox/control/executor.js +9 -59
- package/dist/lib/sandbox/control/host-recovery.js +89 -27
- package/dist/lib/sandbox/control/lifecycle.js +75 -9
- package/dist/lib/sandbox/control/protocol.js +11 -50
- package/dist/lib/sandbox/control/server.js +203 -253
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/environment.js +7 -8
- package/dist/lib/sandbox/mounts.js +71 -52
- package/dist/lib/sandbox/recovery.js +30 -12
- package/dist/lib/sandbox/removal.js +37 -3
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +89 -6
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +41 -17
- package/dist/lib/task/control-recovery.js +2 -4
- package/dist/lib/task/finalization.js +27 -2
- package/dist/lib/task/lifecycle.js +294 -63
- package/dist/lib/task/orchestration.js +26 -72
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +4 -11
- 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 +21 -27
- 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/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +33 -19
- package/lib/internal/task-lifecycle.ts +4 -2
- package/lib/internal/task-operation-registry.ts +1 -3
- package/lib/internal/task-validate.ts +13 -127
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/release-notes.ts +2 -2
- package/lib/sandbox/commands/control-status.ts +0 -1
- package/lib/sandbox/commands/create.ts +11 -5
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/config.ts +13 -0
- package/lib/sandbox/control/audit.ts +1 -2
- package/lib/sandbox/control/client.ts +17 -138
- package/lib/sandbox/control/executor.ts +8 -65
- package/lib/sandbox/control/host-recovery.ts +94 -21
- package/lib/sandbox/control/lifecycle.ts +75 -10
- package/lib/sandbox/control/protocol.ts +12 -62
- package/lib/sandbox/control/server.ts +11 -61
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/environment.ts +7 -8
- package/lib/sandbox/mounts.ts +93 -68
- package/lib/sandbox/recovery.ts +37 -19
- package/lib/sandbox/removal.ts +36 -5
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +81 -7
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +41 -26
- package/lib/task/control-recovery.ts +3 -5
- package/lib/task/finalization.ts +29 -2
- package/lib/task/lifecycle.ts +282 -57
- package/lib/task/orchestration.ts +30 -68
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +3 -9
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +1 -0
- package/templates/.agents/rules/README.zh-CN.md +1 -0
- 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/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- 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/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -26
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -23
- 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/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
- 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
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
recoverSandboxControl,
|
|
3
3
|
requestSandboxControl,
|
|
4
|
-
requestSandboxTaskFinalization,
|
|
5
4
|
SandboxControlClientError
|
|
6
5
|
} from '../sandbox/control/client.ts';
|
|
7
|
-
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
8
6
|
import { serveSandboxControl } from '../sandbox/control/server.ts';
|
|
9
7
|
import { runSandboxControlExecutor } from '../sandbox/control/executor.ts';
|
|
10
8
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
11
9
|
import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.ts';
|
|
12
|
-
import { serializeTaskFinalizationEnvelope, type TaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
|
|
13
|
-
|
|
14
|
-
type FinalizationStatus = 'completed' | 'failed' | 'blocked' | 'unknown';
|
|
15
|
-
|
|
16
|
-
function writeFinalizationEnvelope(
|
|
17
|
-
status: FinalizationStatus,
|
|
18
|
-
accepted: boolean,
|
|
19
|
-
error: { code: string; message: string; retryable: boolean },
|
|
20
|
-
changed: boolean | null = false,
|
|
21
|
-
result: unknown = null,
|
|
22
|
-
requestId: string | null = null
|
|
23
|
-
): void {
|
|
24
|
-
process.stdout.write(serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error }));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function finalizationErrorStatus(error: { retryable: boolean; code: string }): FinalizationStatus {
|
|
28
|
-
if (error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') return 'unknown';
|
|
29
|
-
return error.retryable ? 'blocked' : 'failed';
|
|
30
|
-
}
|
|
31
10
|
|
|
32
11
|
function writeClientError(error: SandboxControlClientError): void {
|
|
33
12
|
process.stderr.write(`${error.detail.message}\n`);
|
|
@@ -45,78 +24,6 @@ function recoveredExitCode(response: Awaited<ReturnType<typeof recoverSandboxCon
|
|
|
45
24
|
return response.exitCode ?? 1;
|
|
46
25
|
}
|
|
47
26
|
|
|
48
|
-
function finalizationResponseOutput(response: Awaited<ReturnType<typeof requestSandboxTaskFinalization>>): string {
|
|
49
|
-
try {
|
|
50
|
-
const value = JSON.parse(response.stdout) as Partial<TaskFinalizationEnvelope>;
|
|
51
|
-
if (value.version === 2
|
|
52
|
-
&& value.status
|
|
53
|
-
&& ['completed', 'failed', 'blocked', 'unknown'].includes(value.status)
|
|
54
|
-
&& (typeof value.changed === 'boolean' || value.changed === null)) {
|
|
55
|
-
return serializeTaskFinalizationEnvelope({
|
|
56
|
-
status: value.status,
|
|
57
|
-
changed: value.changed,
|
|
58
|
-
accepted: true,
|
|
59
|
-
requestId: response.id,
|
|
60
|
-
result: value.result ?? null,
|
|
61
|
-
error: value.error ?? null
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
} catch {
|
|
65
|
-
// Preserve non-envelope output for diagnostics from the executor.
|
|
66
|
-
}
|
|
67
|
-
return response.stdout;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function sandboxFinalizationClient(args: string[]): void {
|
|
71
|
-
if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
|
|
72
|
-
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
|
|
73
|
-
writeFinalizationEnvelope('failed', false, error);
|
|
74
|
-
process.stderr.write('Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n');
|
|
75
|
-
process.exitCode = 1;
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const agent = normalizeAgentToken(args[3]);
|
|
79
|
-
if (!agent) {
|
|
80
|
-
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: `invalid --agent: ${AGENT_USAGE_HINT}`, retryable: false };
|
|
81
|
-
writeFinalizationEnvelope('failed', false, error);
|
|
82
|
-
process.exitCode = 1;
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
let response;
|
|
86
|
-
try {
|
|
87
|
-
response = requestSandboxTaskFinalization({ agent });
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (!(error instanceof SandboxControlClientError)) throw error;
|
|
90
|
-
const status = finalizationErrorStatus(error.detail);
|
|
91
|
-
writeFinalizationEnvelope(status, error.accepted, error.detail, status === 'unknown' ? null : false, null, error.requestId);
|
|
92
|
-
writeClientError(error);
|
|
93
|
-
process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (response.phase === 'rejected') {
|
|
97
|
-
const error = response.error ?? {
|
|
98
|
-
code: 'SANDBOX_CONTROL_REJECTED',
|
|
99
|
-
message: 'sandbox control rejected the finalization request',
|
|
100
|
-
retryable: false
|
|
101
|
-
};
|
|
102
|
-
const status = finalizationErrorStatus(error);
|
|
103
|
-
writeFinalizationEnvelope(
|
|
104
|
-
status,
|
|
105
|
-
error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN',
|
|
106
|
-
error,
|
|
107
|
-
status === 'unknown' ? null : false,
|
|
108
|
-
null,
|
|
109
|
-
response.id
|
|
110
|
-
);
|
|
111
|
-
process.stderr.write(`${error.message}\n`);
|
|
112
|
-
process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
process.stdout.write(finalizationResponseOutput(response));
|
|
116
|
-
process.stderr.write(response.stderr);
|
|
117
|
-
process.exitCode = response.exitCode ?? 1;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
27
|
async function sandboxControl(args: string[]): Promise<void> {
|
|
121
28
|
if (!ensureInternalHandlerRoute('sandbox-control', args)) return;
|
|
122
29
|
const [operation, ...rest] = args;
|
|
@@ -165,10 +72,6 @@ async function sandboxControl(args: string[]): Promise<void> {
|
|
|
165
72
|
}
|
|
166
73
|
if (internalHandlerRoute('sandbox-control', 'client', operation ?? '')) {
|
|
167
74
|
const [family = '', ...commandArgs] = rest;
|
|
168
|
-
if (family === 'task-finalization') {
|
|
169
|
-
sandboxFinalizationClient(commandArgs);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
75
|
let response;
|
|
173
76
|
try {
|
|
174
77
|
response = requestSandboxControl({ family, args: commandArgs });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import {
|
|
2
3
|
createDirectHostExecutionContext,
|
|
3
4
|
dispatchTaskControlOperation,
|
|
@@ -6,8 +7,10 @@ import {
|
|
|
6
7
|
import { applyTaskFinalization } from '../task/finalization.ts';
|
|
7
8
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
8
9
|
import { resolveSandboxControlTransport } from './task-operation-registry.ts';
|
|
9
|
-
import {
|
|
10
|
+
import { quiesceSandboxControlForTaskCompletion } from '../sandbox/control/lifecycle.ts';
|
|
11
|
+
import { loadSandboxControlLocation } from '../sandbox/config.ts';
|
|
10
12
|
import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
|
|
13
|
+
import { verifyTaskEvent } from '../task/verification.ts';
|
|
11
14
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
12
15
|
|
|
13
16
|
const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
|
|
@@ -79,28 +82,39 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
79
82
|
};
|
|
80
83
|
const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
|
|
81
84
|
if (transport.kind === 'sandbox-local') {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
? clientError.detail
|
|
92
|
-
: { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
93
|
-
const status = detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN'
|
|
94
|
-
? 'unknown'
|
|
95
|
-
: detail.retryable ? 'blocked' : 'failed';
|
|
96
|
-
const changed = status === 'unknown' ? null : false;
|
|
97
|
-
process.stdout.write(envelope(status, changed, clientError?.accepted ?? false, null, detail, clientError?.requestId ?? null));
|
|
98
|
-
process.exitCode = exitCode(status);
|
|
85
|
+
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
86
|
+
const taskId = process.env.AGENT_INFRA_TASK_ID;
|
|
87
|
+
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
88
|
+
const controlRootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
|
|
89
|
+
const controlRoot = process.env.AGENT_INFRA_CONTROL_ROOT_DIR;
|
|
90
|
+
if (!statusDir || !taskId || !generation || !controlRootId || !controlRoot || taskId !== resolved.taskId) {
|
|
91
|
+
const detail = { code: 'TASK_CONTROL_TRANSPORT_INVALID', message: 'sandbox task control identity is incomplete', retryable: false };
|
|
92
|
+
process.stdout.write(envelope('failed', false, true, null, detail));
|
|
93
|
+
process.exitCode = 1;
|
|
99
94
|
return;
|
|
100
95
|
}
|
|
96
|
+
const result = await applyTaskFinalization(boundOperation.request, {
|
|
97
|
+
repoRoot,
|
|
98
|
+
beforeLifecycle: async (currentTaskId) => {
|
|
99
|
+
if (currentTaskId !== taskId) throw new Error('SANDBOX_CONTROL_IDENTITY_MISMATCH');
|
|
100
|
+
await quiesceSandboxControlForTaskCompletion(
|
|
101
|
+
controlRoot,
|
|
102
|
+
{ taskId, generation, controlRootId, statusDir }
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
preflight: (request, options) => verifyTaskEvent(
|
|
106
|
+
{ ...request, event: 'complete-task.hard-preflight' }, options
|
|
107
|
+
)
|
|
108
|
+
});
|
|
109
|
+
const status = result.status === 'prepared' ? 'failed' : result.status;
|
|
110
|
+
process.stdout.write(envelope(status, result.changed, true, result, result.error
|
|
111
|
+
?? (result.status === 'prepared' ? { code: 'TASK_FINALIZATION_PREPARATION_INCOMPLETE', message: 'finalization did not commit', retryable: true } : null)));
|
|
112
|
+
process.exitCode = exitCode(status);
|
|
113
|
+
return;
|
|
101
114
|
}
|
|
115
|
+
const controlLocation = loadSandboxControlLocation(repoRoot);
|
|
102
116
|
const result = await dispatchTaskControlOperation(
|
|
103
|
-
createDirectHostExecutionContext({ repoRoot }),
|
|
117
|
+
createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }),
|
|
104
118
|
boundOperation
|
|
105
119
|
);
|
|
106
120
|
if (result.status === 'prepared') {
|
|
@@ -9,8 +9,9 @@ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
|
9
9
|
import type { TaskLifecycleResult } from '../task/lifecycle.ts';
|
|
10
10
|
import type { AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult } from '../agent-clients/adapter.ts';
|
|
11
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
12
|
+
import { loadSandboxControlLocation } from '../sandbox/config.ts';
|
|
12
13
|
|
|
13
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\n`;
|
|
14
|
+
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.filter((intent) => intent !== 'complete').join(', ')}\nrecover-started: --auto\n`;
|
|
14
15
|
|
|
15
16
|
function usageFailure(message: string): void {
|
|
16
17
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -59,8 +60,9 @@ async function taskLifecycle(args: string[] = []): Promise<void> {
|
|
|
59
60
|
repoRoot = resolved.repoRoot;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const controlLocation = loadSandboxControlLocation(repoRoot);
|
|
62
64
|
const result = await dispatchTaskControlOperation(
|
|
63
|
-
createDirectHostExecutionContext({ repoRoot }),
|
|
65
|
+
createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }),
|
|
64
66
|
operation
|
|
65
67
|
) as TaskLifecycleResult | LifecycleRecoveryResult;
|
|
66
68
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
@@ -60,8 +60,6 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
60
60
|
descriptor('internal', 'codex-lifecycle', 'resolve-start', 'conditional', 'progress'),
|
|
61
61
|
descriptor('internal', 'codex-lifecycle', 'resolve-stop', 'conditional', 'progress'),
|
|
62
62
|
descriptor('internal', 'codex-lifecycle', 'consume', 'conditional', 'progress'),
|
|
63
|
-
descriptor('internal', 'codex-sandbox-controller', 'verify-context', 'task-bound', 'diagnostic'),
|
|
64
|
-
descriptor('internal', 'codex-sandbox-controller', 'run', 'task-bound', 'progress'),
|
|
65
63
|
descriptor('internal', 'git-workflow', 'inspect', 'conditional', 'diagnostic'),
|
|
66
64
|
descriptor('internal', 'git-workflow', 'preview-tree', 'conditional', 'diagnostic'),
|
|
67
65
|
descriptor('internal', 'git-workflow', 'snapshot', 'conditional', 'diagnostic'),
|
|
@@ -213,7 +211,7 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
|
|
|
213
211
|
]);
|
|
214
212
|
|
|
215
213
|
export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
216
|
-
'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle',
|
|
214
|
+
'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle',
|
|
217
215
|
'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
|
|
218
216
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
219
217
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
@@ -2,27 +2,9 @@ import { execFileSync, spawnSync } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { randomUUID } from 'node:crypto';
|
|
6
5
|
import { loadConfig } from '../sandbox/config.ts';
|
|
7
|
-
import { detectEngine } from '../sandbox/engine.ts';
|
|
8
|
-
import { runEngine } from '../sandbox/shell.ts';
|
|
9
|
-
import { sandboxBranchLabel, sandboxLabel } from '../sandbox/constants.ts';
|
|
10
|
-
import { fetchSandboxRows, selectSandboxContainer } from '../sandbox/commands/list-running.ts';
|
|
11
|
-
import { containerNameCandidates } from '../sandbox/constants.ts';
|
|
12
6
|
import { resolveSandboxTarget } from '../sandbox/workspace-identity.ts';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
appendSandboxControlAudit,
|
|
16
|
-
readActiveLease,
|
|
17
|
-
readSandboxControlStatus
|
|
18
|
-
} from '../sandbox/control/state.ts';
|
|
19
|
-
import type { SandboxControlLease } from '../sandbox/control/protocol.ts';
|
|
20
|
-
import { readSandboxControlManifest } from '../sandbox/control/lifecycle.ts';
|
|
21
|
-
import {
|
|
22
|
-
SANDBOX_CONTROL_FUTURE_SKEW_MS,
|
|
23
|
-
SANDBOX_CONTROL_STATUS_STALE_MS
|
|
24
|
-
} from '../sandbox/control/protocol.ts';
|
|
25
|
-
import { getProcessStartTime } from '../server/process-state.ts';
|
|
7
|
+
import { resolveBranchWorktree } from '../git/branch-worktree.ts';
|
|
26
8
|
import { assertGitWorktreeBinding } from '../git/worktree-identity.ts';
|
|
27
9
|
import { createManualValidationEvidence, manualValidationEvidenceSummary } from '../task/manual-validation-evidence.ts';
|
|
28
10
|
import type { ManualValidationCleanup } from '../task/manual-validation-evidence.ts';
|
|
@@ -31,7 +13,6 @@ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-in
|
|
|
31
13
|
|
|
32
14
|
const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] [--evidence-file <path>] -- <command> [args...]`;
|
|
33
15
|
const MAX_TIMEOUT_MS = 60 * 60 * 1000;
|
|
34
|
-
const RECOVERY_GRACE_MS = 30_000;
|
|
35
16
|
|
|
36
17
|
type ValidateOptions = {
|
|
37
18
|
target: string;
|
|
@@ -108,20 +89,6 @@ function git(root: string, args: string[]): string {
|
|
|
108
89
|
return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
109
90
|
}
|
|
110
91
|
|
|
111
|
-
function waitForState(statusDir: string, predicate: (state: ReturnType<typeof readSandboxControlStatus>) => boolean, timeoutMs = 5_000): void {
|
|
112
|
-
const deadline = Date.now() + timeoutMs;
|
|
113
|
-
while (Date.now() < deadline) {
|
|
114
|
-
try {
|
|
115
|
-
const status = readSandboxControlStatus(statusDir);
|
|
116
|
-
if (predicate(status)) return;
|
|
117
|
-
} catch {
|
|
118
|
-
// Atomic status publication may be between renames.
|
|
119
|
-
}
|
|
120
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
121
|
-
}
|
|
122
|
-
throw new Error('SANDBOX_VALIDATION_BROKER_STATE_TIMEOUT');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
92
|
function snapshotValidation(repoRoot: string, commit: string, options: ValidateOptions) {
|
|
126
93
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-validation-'));
|
|
127
94
|
const worktree = path.join(tempRoot, 'worktree');
|
|
@@ -150,109 +117,28 @@ function snapshotValidation(repoRoot: string, commit: string, options: ValidateO
|
|
|
150
117
|
}
|
|
151
118
|
|
|
152
119
|
function inplaceValidation(config: ReturnType<typeof loadConfig>, target: ReturnType<typeof resolveSandboxTarget>, options: ValidateOptions) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
base: config.controlBase, repoRoot: config.repoRoot, project: config.project,
|
|
159
|
-
container: row.name, identity: target.workspace
|
|
160
|
-
});
|
|
161
|
-
const manifest = readSandboxControlManifest(control.manifestPath);
|
|
162
|
-
const initial = readSandboxControlStatus(control.statusDir);
|
|
163
|
-
const now = Date.now();
|
|
164
|
-
if (initial.generation !== manifest.generation
|
|
165
|
-
|| now - initial.updatedAt > SANDBOX_CONTROL_STATUS_STALE_MS
|
|
166
|
-
|| initial.updatedAt > now + SANDBOX_CONTROL_FUTURE_SKEW_MS) {
|
|
167
|
-
throw new Error('SANDBOX_VALIDATION_BROKER_STATUS_STALE');
|
|
168
|
-
}
|
|
169
|
-
if (initial.state !== 'healthy' || initial.activeRequestId !== null) {
|
|
170
|
-
throw new Error('SANDBOX_VALIDATION_BROKER_NOT_IDLE');
|
|
171
|
-
}
|
|
172
|
-
const startTime = getProcessStartTime(process.pid);
|
|
173
|
-
if (!startTime) throw new Error('SANDBOX_VALIDATION_OWNER_IDENTITY_UNAVAILABLE');
|
|
174
|
-
const leasePath = path.join(control.root, 'lease.json');
|
|
175
|
-
const nonce = randomUUID();
|
|
176
|
-
const issuedAt = Date.now();
|
|
177
|
-
const lease: SandboxControlLease = {
|
|
178
|
-
version: 2, generation: manifest.generation, nonce,
|
|
179
|
-
owner: { pid: process.pid, startTime }, issuedAt,
|
|
180
|
-
expiresAt: issuedAt + Math.min(options.timeoutMs + RECOVERY_GRACE_MS, MAX_TIMEOUT_MS + RECOVERY_GRACE_MS),
|
|
181
|
-
taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
|
|
182
|
-
branch: target.branch, reason: 'manual-validation'
|
|
183
|
-
};
|
|
184
|
-
const originalBranch = git(manifest.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
185
|
-
const originalHead = git(manifest.worktreeRoot, ['rev-parse', 'HEAD']);
|
|
186
|
-
fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
|
|
187
|
-
let stopped = false;
|
|
120
|
+
const targetRoot = resolveBranchWorktree(config.repoRoot, target.branch);
|
|
121
|
+
if (!targetRoot) throw new Error(`TASK_VALIDATION_WORKTREE_NOT_FOUND: ${target.branch}`);
|
|
122
|
+
const worktree = assertGitWorktreeBinding(config.repoRoot, targetRoot, target.branch);
|
|
123
|
+
const originalBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
124
|
+
const originalHead = git(worktree.worktreeRoot, ['rev-parse', 'HEAD']);
|
|
188
125
|
let restored = false;
|
|
189
126
|
try {
|
|
190
|
-
|
|
191
|
-
waitForState(control.statusDir, (status) => status.state === 'parked' && status.reasonCode === 'SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
192
|
-
if (row.running) {
|
|
193
|
-
runEngine(engine, 'docker', ['stop', row.name]);
|
|
194
|
-
stopped = true;
|
|
195
|
-
}
|
|
196
|
-
const refreshedRows = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
197
|
-
const refreshed = selectSandboxContainer(
|
|
198
|
-
[...refreshedRows.running, ...refreshedRows.nonRunning],
|
|
199
|
-
containerNameCandidates(config, target.branch)
|
|
200
|
-
);
|
|
201
|
-
if (!refreshed || refreshed.running) throw new Error('SANDBOX_VALIDATION_CONTAINER_STILL_RUNNING');
|
|
202
|
-
const activeLease = readActiveLease(manifest);
|
|
203
|
-
if (!activeLease || activeLease.nonce !== nonce) throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
|
|
204
|
-
assertGitWorktreeBinding(config.repoRoot, manifest.worktreeRoot, target.branch);
|
|
205
|
-
const result = runValidationCommand(options.command, manifest.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
|
|
127
|
+
const result = runValidationCommand(options.command, worktree.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
|
|
206
128
|
return { ...result, cleanup: () => restored ? 'completed' : 'pending' };
|
|
207
129
|
} finally {
|
|
208
|
-
const recoveryErrors: string[] = [];
|
|
209
130
|
try {
|
|
210
|
-
const currentBranch = git(
|
|
211
|
-
if (currentBranch !== originalBranch) git(
|
|
131
|
+
const currentBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
132
|
+
if (currentBranch !== originalBranch) git(worktree.worktreeRoot, ['switch', originalBranch]);
|
|
212
133
|
} catch {
|
|
213
134
|
try {
|
|
214
|
-
git(
|
|
215
|
-
git(
|
|
216
|
-
} catch (fallbackError) {
|
|
217
|
-
recoveryErrors.push(`git binding restore failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (stopped) {
|
|
221
|
-
try {
|
|
222
|
-
runEngine(engine, 'docker', ['start', row.name]);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
recoveryErrors.push(`container start failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
try {
|
|
228
|
-
const currentLease = JSON.parse(fs.readFileSync(leasePath, 'utf8')) as { nonce?: unknown };
|
|
229
|
-
if (currentLease.nonce !== nonce) throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
|
|
230
|
-
} catch (error) {
|
|
231
|
-
recoveryErrors.push(error instanceof Error ? error.message : String(error));
|
|
232
|
-
}
|
|
233
|
-
if (recoveryErrors.length === 0) {
|
|
234
|
-
try {
|
|
235
|
-
fs.unlinkSync(leasePath);
|
|
236
|
-
appendSandboxControlAudit(manifest, 'lease-release', { ownerPid: process.pid });
|
|
237
|
-
waitForState(control.statusDir, (status) => status.state === 'healthy');
|
|
238
|
-
restored = true;
|
|
135
|
+
git(worktree.worktreeRoot, ['checkout', '--detach', originalHead]);
|
|
136
|
+
git(worktree.worktreeRoot, ['switch', originalBranch]);
|
|
239
137
|
} catch (error) {
|
|
240
|
-
|
|
241
|
-
if (!fs.existsSync(leasePath)) {
|
|
242
|
-
fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
|
|
243
|
-
}
|
|
244
|
-
appendSandboxControlAudit(manifest, 'lease-release-failed', { ownerPid: process.pid });
|
|
245
|
-
} catch (leaseError) {
|
|
246
|
-
recoveryErrors.push(`lease preservation failed: ${leaseError instanceof Error ? leaseError.message : String(leaseError)}`);
|
|
247
|
-
}
|
|
248
|
-
recoveryErrors.push(error instanceof Error ? error.message : String(error));
|
|
138
|
+
throw new Error(`TASK_VALIDATION_WORKTREE_RESTORE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
|
|
249
139
|
}
|
|
250
140
|
}
|
|
251
|
-
|
|
252
|
-
throw new Error(
|
|
253
|
-
`SANDBOX_VALIDATION_RECOVERY_INCOMPLETE: ${recoveryErrors.join('; ')}; run 'ai sandbox start ${target.branch}'`
|
|
254
|
-
);
|
|
255
|
-
}
|
|
141
|
+
restored = true;
|
|
256
142
|
}
|
|
257
143
|
}
|
|
258
144
|
|
|
@@ -51,6 +51,33 @@ function boundedFailureDetail(result: RunResult): string {
|
|
|
51
51
|
: `${diagnostic.slice(0, ERROR_DETAIL_LIMIT)}… [truncated]`;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function requestDiagnostics(args: string[], request: RequestOptions, attempt: number, errorMessage: string): string {
|
|
55
|
+
if (process.env.AGENT_INFRA_GH_DIAGNOSTICS !== '1' && !/rate.?limit/iu.test(errorMessage)) return '';
|
|
56
|
+
|
|
57
|
+
const apiIndex = args.indexOf('api');
|
|
58
|
+
const endpoint = apiIndex < 0
|
|
59
|
+
? args.slice(0, 2).filter((arg) => !arg.startsWith('-')).join(' ')
|
|
60
|
+
: args.slice(apiIndex + 1).find((arg) => !arg.startsWith('-') && !arg.includes('=')) || 'unknown';
|
|
61
|
+
const safeEndpoint = endpoint.startsWith('http')
|
|
62
|
+
? (() => {
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(endpoint);
|
|
65
|
+
return `${url.host}${url.pathname}`;
|
|
66
|
+
} catch {
|
|
67
|
+
return 'unknown';
|
|
68
|
+
}
|
|
69
|
+
})()
|
|
70
|
+
: endpoint.split('?')[0] || 'unknown';
|
|
71
|
+
const methodIndex = args.findIndex((arg) => arg === '-X' || arg === '--method');
|
|
72
|
+
const methodFromArgs = methodIndex >= 0 ? args[methodIndex + 1] : undefined;
|
|
73
|
+
const method = methodFromArgs || request.method || (safeEndpoint === 'graphql' ? 'POST' : 'GET');
|
|
74
|
+
const tokenEnvironment = ['GH_TOKEN', 'GITHUB_TOKEN', 'GH_ENTERPRISE_TOKEN', 'GITHUB_ENTERPRISE_TOKEN']
|
|
75
|
+
.filter((name) => Boolean(process.env[name]));
|
|
76
|
+
const configDirOverride = Boolean(process.env.GH_CONFIG_DIR);
|
|
77
|
+
|
|
78
|
+
return ` [gh-diagnostic endpoint=${safeEndpoint} method=${method} attempt=${attempt} tokenEnv=${tokenEnvironment.join(',') || 'none'} ghConfigDirOverride=${configDirOverride}]`;
|
|
79
|
+
}
|
|
80
|
+
|
|
54
81
|
function defaultRunner(args: string[], options: RunOptions): RunResult {
|
|
55
82
|
const command = process.env.AGENT_INFRA_GH_BIN || 'gh';
|
|
56
83
|
let prefix: string[] = [];
|
|
@@ -168,13 +195,17 @@ function createGitHubClient(options: ClientOptions = {}): GitHubClient {
|
|
|
168
195
|
const result = runner(args, request);
|
|
169
196
|
if (result.status === 0) return { ok: true, value: result.stdout };
|
|
170
197
|
const error = classifyGitHubFailure(result);
|
|
171
|
-
|
|
198
|
+
const requestDetail = requestDiagnostics(args, request, attempt + 1, error.message);
|
|
199
|
+
const contextualError = requestDetail
|
|
200
|
+
? { ...error, message: `${error.message}${requestDetail}` }
|
|
201
|
+
: error;
|
|
202
|
+
if (!retryableMethod || !error.requestRetryable) return { ok: false, error: clientError(contextualError) };
|
|
172
203
|
if (attempt >= delays.length) {
|
|
173
204
|
return {
|
|
174
205
|
ok: false,
|
|
175
206
|
error: error.code === 'NETWORK_TRANSIENT'
|
|
176
|
-
? { ...clientError(
|
|
177
|
-
: clientError(
|
|
207
|
+
? { ...clientError(contextualError), code: 'NETWORK_RETRY_EXHAUSTED', message: `${contextualError.message} (retry exhausted)` }
|
|
208
|
+
: clientError(contextualError)
|
|
178
209
|
};
|
|
179
210
|
}
|
|
180
211
|
sleep(delays[attempt]!);
|
|
@@ -136,8 +136,8 @@ async function releaseNoteContext(
|
|
|
136
136
|
options: ReleaseNoteOptions = {}
|
|
137
137
|
) {
|
|
138
138
|
const historyLimit = input.historyLimit ?? 3;
|
|
139
|
-
if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit >
|
|
140
|
-
return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'from-tag, to-tag, branch, and a history limit from 1 to
|
|
139
|
+
if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 100) {
|
|
140
|
+
return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'from-tag, to-tag, branch, and a history limit from 1 to 100 are required', retryable: false } };
|
|
141
141
|
}
|
|
142
142
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
143
143
|
const platformType = options.platformType ?? configuredPlatform(cwd);
|
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
materializeSandboxControl,
|
|
92
92
|
materializeSandboxWorkspaceView,
|
|
93
93
|
prepareSandboxWorkspaceMountTargets,
|
|
94
|
+
prepareSandboxCompletedTaskMountSource,
|
|
94
95
|
sandboxControlPaths,
|
|
95
96
|
} from '../workspace-view.ts';
|
|
96
97
|
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.ts';
|
|
@@ -1107,7 +1108,6 @@ export async function create(
|
|
|
1107
1108
|
const container = containerName(effectiveConfig, branch);
|
|
1108
1109
|
const controlPaths = sandboxControlPaths({
|
|
1109
1110
|
base: effectiveConfig.controlBase,
|
|
1110
|
-
repoRoot: effectiveConfig.repoRoot,
|
|
1111
1111
|
project: effectiveConfig.project,
|
|
1112
1112
|
container,
|
|
1113
1113
|
identity: target.workspace
|
|
@@ -1451,7 +1451,10 @@ export async function create(
|
|
|
1451
1451
|
const taskSource = target.workspace.mode === 'task-bound'
|
|
1452
1452
|
? assertSandboxTaskSource(effectiveConfig.repoRoot, target.workspace.taskId)
|
|
1453
1453
|
: null;
|
|
1454
|
-
|
|
1454
|
+
const completedTaskSource = target.workspace.mode === 'task-bound'
|
|
1455
|
+
? prepareSandboxCompletedTaskMountSource(effectiveConfig.repoRoot, target.workspace.taskId)
|
|
1456
|
+
: null;
|
|
1457
|
+
prepareSandboxWorkspaceMountTargets(worktree, target.workspace.mode === 'task-bound' ? target.workspace.taskId : undefined);
|
|
1455
1458
|
if (previousCutoverSnapshot) {
|
|
1456
1459
|
assertSandboxControlCutoverSnapshot(previousCutoverSnapshot);
|
|
1457
1460
|
if (previousManifest) {
|
|
@@ -1489,11 +1492,13 @@ export async function create(
|
|
|
1489
1492
|
shellConfigHostDir: hostShellConfig.hostDir,
|
|
1490
1493
|
workspaceViewRoot: workspaceView.root,
|
|
1491
1494
|
controlDir: control.channelDir,
|
|
1495
|
+
...(target.workspace.mode === 'task-bound' ? { controlRootDir: control.root } : {}),
|
|
1492
1496
|
controlStatusDir: control.statusDir,
|
|
1493
1497
|
...(target.workspace.mode === 'task-bound' ? { runtimeDir: control.runtimeDir } : {}),
|
|
1494
1498
|
...(target.workspace.mode === 'task-bound'
|
|
1495
1499
|
? {
|
|
1496
1500
|
taskSources: [taskSource!],
|
|
1501
|
+
completedTaskSource: completedTaskSource!,
|
|
1497
1502
|
taskId: target.workspace.taskId
|
|
1498
1503
|
}
|
|
1499
1504
|
: {})
|
|
@@ -1583,8 +1588,6 @@ export async function create(
|
|
|
1583
1588
|
]
|
|
1584
1589
|
: []),
|
|
1585
1590
|
'-e',
|
|
1586
|
-
`AGENT_INFRA_CONTROL_TOKEN=${control.token}`,
|
|
1587
|
-
'-e',
|
|
1588
1591
|
`AGENT_INFRA_CONTROL_GENERATION=${control.generation}`,
|
|
1589
1592
|
'-e',
|
|
1590
1593
|
'AGENT_INFRA_CONTROL_DIR=/run/agent-infra/control',
|
|
@@ -1593,7 +1596,10 @@ export async function create(
|
|
|
1593
1596
|
'-e',
|
|
1594
1597
|
`AGENT_INFRA_CONTROL_ROOT_ID=${control.controlRootId}`,
|
|
1595
1598
|
...(target.workspace.mode === 'task-bound'
|
|
1596
|
-
? [
|
|
1599
|
+
? [
|
|
1600
|
+
'-e', 'AGENT_INFRA_CONTROL_ROOT_DIR=/run/agent-infra/control-root',
|
|
1601
|
+
'-e', 'AGENT_INFRA_RUNTIME_DIR=/run/agent-infra/runtime'
|
|
1602
|
+
]
|
|
1597
1603
|
: []),
|
|
1598
1604
|
'--label',
|
|
1599
1605
|
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
|
@@ -18,11 +18,10 @@ export { containerListFormat, parseLabels } from './list-running.ts';
|
|
|
18
18
|
const USAGE = `Usage: ai sandbox ls
|
|
19
19
|
|
|
20
20
|
Lists all containers for the current project. The '#' column is a
|
|
21
|
-
display-only row number; the 'SHORT' column shows the
|
|
22
|
-
|
|
23
|
-
.
|
|
24
|
-
|
|
25
|
-
A SHORT value of '-' means this container has no active task short id and may
|
|
21
|
+
display-only row number; the 'SHORT' column shows the task short id bound to
|
|
22
|
+
each task-bound container, or '-' if no short id is registered. Pass the
|
|
23
|
+
SHORT value to "ai sandbox exec" (e.g. 'ai sandbox exec 11').
|
|
24
|
+
A SHORT value of '-' means this container has no task short id and may
|
|
26
25
|
be eligible for "ai sandbox rm --unbound" cleanup. Use a full TASK-id to
|
|
27
26
|
remove a specific task-bound sandbox.
|
|
28
27
|
|
|
@@ -13,7 +13,7 @@ import { fetchSandboxRows } from './list-running.ts';
|
|
|
13
13
|
import { rmOne, rmPurge, rmUnbound } from '../removal.ts';
|
|
14
14
|
|
|
15
15
|
const USAGE = `Usage:
|
|
16
|
-
ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
|
|
16
|
+
ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a retained short id or full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
|
|
17
17
|
ai sandbox rm --unbound [--dry-run] [--yes] Remove sandboxes not bound to an active task short id
|
|
18
18
|
ai sandbox rm --purge Tear down ALL sandboxes for the project (containers, worktrees, image, VM)`;
|
|
19
19
|
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -237,3 +237,16 @@ export function loadConfig({
|
|
|
237
237
|
}
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
|
+
|
|
241
|
+
export function loadSandboxControlLocation(repoRoot = detectRepoRoot()): Readonly<{
|
|
242
|
+
controlBase: string;
|
|
243
|
+
project: string;
|
|
244
|
+
}> | null {
|
|
245
|
+
const configPath = path.join(repoRoot, '.agents', '.airc.json');
|
|
246
|
+
if (!fs.existsSync(configPath)) return null;
|
|
247
|
+
const airc = JSON.parse(fs.readFileSync(configPath, 'utf8')) as AircConfig;
|
|
248
|
+
if (typeof airc.project !== 'string' || !airc.project) return null;
|
|
249
|
+
const home = homedir();
|
|
250
|
+
if (!home) throw new Error('sandbox: home directory is required');
|
|
251
|
+
return { controlBase: hostJoin(home, '.agent-infra', 'sandbox-control'), project: airc.project };
|
|
252
|
+
}
|
|
@@ -41,8 +41,7 @@ const AUDIT_MAX_BYTES = 1024 * 1024;
|
|
|
41
41
|
|
|
42
42
|
export function requestAuditFields(manifest: SandboxControlManifest, request: SandboxControlRequest) {
|
|
43
43
|
return {
|
|
44
|
-
requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId
|
|
45
|
-
requestGeneration: request.generation, requestIssuedAt: request.issuedAt, requestExpiresAt: request.expiresAt
|
|
44
|
+
requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId
|
|
46
45
|
};
|
|
47
46
|
}
|
|
48
47
|
|