@fitlab-ai/agent-infra 0.9.3 → 0.9.5
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 +3 -1
- package/README.zh-CN.md +3 -1
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +144 -123
- package/dist/lib/agent-clients/adapter.js +94 -4
- package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
- package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
- package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +382 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +163 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -0
- package/dist/lib/agent-clients/adapters/codex.js +2 -2
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
- package/dist/lib/agent-clients/adapters/opencode.js +15 -3
- package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/dist/lib/agent-clients/registry.js +9 -1
- package/dist/lib/git/worktree-identity.js +64 -0
- package/dist/lib/internal/codex-lifecycle.js +202 -0
- package/dist/lib/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +45 -13
- package/dist/lib/internal/task-create.js +48 -0
- package/dist/lib/internal/task-ledger.js +12 -3
- package/dist/lib/internal/task-lifecycle.js +19 -1
- package/dist/lib/internal/task-orchestration.js +170 -27
- package/dist/lib/platform/github-client.js +19 -2
- package/dist/lib/platform/pr-checks.js +1 -1
- package/dist/lib/platform/pull-requests.js +47 -1
- package/dist/lib/platform/releases.js +13 -5
- package/dist/lib/process-data/index.js +239 -0
- package/dist/lib/process-data/normalize.js +198 -0
- package/dist/lib/process-data/privacy.js +33 -0
- package/dist/lib/process-data/reconcile.js +95 -0
- package/dist/lib/process-data/repair.js +58 -0
- package/dist/lib/process-data/sources.js +345 -0
- package/dist/lib/process-data/store.js +282 -0
- package/dist/lib/process-data/types.js +2 -0
- package/dist/lib/run/index.js +4 -3
- package/dist/lib/run/tui.js +7 -8
- package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
- package/dist/lib/sandbox/capture.js +7 -2
- package/dist/lib/sandbox/commands/create.js +110 -228
- package/dist/lib/sandbox/commands/enter.js +15 -47
- package/dist/lib/sandbox/commands/list-running.js +16 -6
- package/dist/lib/sandbox/commands/ls.js +10 -6
- package/dist/lib/sandbox/commands/rebuild.js +4 -3
- package/dist/lib/sandbox/commands/refresh.js +4 -84
- package/dist/lib/sandbox/commands/rm.js +24 -3
- package/dist/lib/sandbox/commands/show.js +12 -3
- package/dist/lib/sandbox/commands/start.js +7 -4
- package/dist/lib/sandbox/config.js +2 -0
- package/dist/lib/sandbox/constants.js +6 -0
- package/dist/lib/sandbox/control/client.js +58 -0
- package/dist/lib/sandbox/control/protocol.js +54 -0
- package/dist/lib/sandbox/control/server.js +144 -0
- package/dist/lib/sandbox/dockerfile.js +36 -13
- package/dist/lib/sandbox/mounts.js +17 -4
- package/dist/lib/sandbox/recovery.js +128 -5
- package/dist/lib/sandbox/redaction.js +12 -0
- package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/dist/lib/sandbox/workspace-identity.js +95 -0
- package/dist/lib/sandbox/workspace-view.js +109 -0
- package/dist/lib/task/activity-intent.js +257 -51
- package/dist/lib/task/activity-log.js +32 -5
- package/dist/lib/task/commit-finalization.js +278 -0
- package/dist/lib/task/commit-intent.js +165 -0
- package/dist/lib/task/create-service.js +137 -0
- package/dist/lib/task/create.js +333 -0
- package/dist/lib/task/decision-intents.js +31 -17
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/implementation-inputs.js +54 -7
- package/dist/lib/task/ledger-intents.js +35 -1
- package/dist/lib/task/orchestration.js +505 -34
- package/dist/lib/task/resolve-ref.js +41 -1
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/workspace-snapshot.js +78 -3
- package/lib/agent-clients/adapter.ts +130 -6
- package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
- package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
- package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
- package/lib/agent-clients/adapters/claude-code.ts +21 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +442 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +307 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +250 -0
- package/lib/agent-clients/adapters/codex.ts +2 -2
- package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
- package/lib/agent-clients/adapters/opencode.ts +18 -3
- package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/lib/agent-clients/registry.ts +9 -1
- package/lib/git/worktree-identity.ts +86 -0
- package/lib/internal/codex-lifecycle.ts +204 -0
- package/lib/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +50 -14
- package/lib/internal/task-create.ts +49 -0
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +22 -1
- package/lib/internal/task-orchestration.ts +117 -21
- package/lib/platform/github-client.ts +20 -2
- package/lib/platform/pr-checks.ts +1 -1
- package/lib/platform/pull-requests.ts +49 -1
- package/lib/platform/releases.ts +16 -4
- package/lib/process-data/index.ts +224 -0
- package/lib/process-data/normalize.ts +202 -0
- package/lib/process-data/privacy.ts +41 -0
- package/lib/process-data/reconcile.ts +110 -0
- package/lib/process-data/repair.ts +64 -0
- package/lib/process-data/sources.ts +354 -0
- package/lib/process-data/store.ts +315 -0
- package/lib/process-data/types.ts +135 -0
- package/lib/run/index.ts +5 -3
- package/lib/run/tui.ts +11 -8
- package/lib/sandbox/agent-client-reconciler.ts +44 -2
- package/lib/sandbox/capture.ts +9 -2
- package/lib/sandbox/commands/create.ts +127 -260
- package/lib/sandbox/commands/enter.ts +13 -59
- package/lib/sandbox/commands/list-running.ts +23 -6
- package/lib/sandbox/commands/ls.ts +22 -6
- package/lib/sandbox/commands/rebuild.ts +4 -3
- package/lib/sandbox/commands/refresh.ts +4 -126
- package/lib/sandbox/commands/rm.ts +24 -3
- package/lib/sandbox/commands/show.ts +29 -3
- package/lib/sandbox/commands/start.ts +6 -4
- package/lib/sandbox/config.ts +4 -0
- package/lib/sandbox/constants.ts +8 -0
- package/lib/sandbox/control/client.ts +79 -0
- package/lib/sandbox/control/protocol.ts +110 -0
- package/lib/sandbox/control/server.ts +155 -0
- package/lib/sandbox/dockerfile.ts +66 -13
- package/lib/sandbox/mounts.ts +24 -4
- package/lib/sandbox/recovery.ts +163 -6
- package/lib/sandbox/redaction.ts +14 -0
- package/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/lib/sandbox/tool-types.ts +16 -0
- package/lib/sandbox/workspace-identity.ts +123 -0
- package/lib/sandbox/workspace-view.ts +150 -0
- package/lib/task/activity-intent.ts +350 -61
- package/lib/task/activity-log.ts +46 -7
- package/lib/task/commit-finalization.ts +376 -0
- package/lib/task/commit-intent.ts +230 -0
- package/lib/task/create-service.ts +171 -0
- package/lib/task/create.ts +372 -0
- package/lib/task/decision-intents.ts +32 -12
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/implementation-inputs.ts +62 -8
- package/lib/task/ledger-intents.ts +43 -3
- package/lib/task/orchestration.ts +673 -41
- package/lib/task/resolve-ref.ts +46 -1
- package/lib/task/review-fingerprint.ts +9 -0
- package/lib/task/workspace-snapshot.ts +98 -4
- package/package.json +7 -7
- package/templates/.agents/hooks/lifecycle-delegation.js +47 -3
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +11 -2
- package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
- package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +3 -0
- package/templates/.agents/rules/task-management.zh-CN.md +3 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +21 -17
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +28 -16
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +50 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +56 -0
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.claude/rules/tool-preferences.en.md +20 -0
- package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
- package/templates/.codex/hooks.json +50 -0
- package/templates/.opencode/commands/watch-pr.en.md +1 -1
- package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
package/dist/lib/run/tui.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { renderAgentClientInvocation } from '../agent-clients/invocation.js';
|
|
2
2
|
import { getAgentClientAdapter } from '../agent-clients/registry.js';
|
|
3
3
|
const TUI_NAMES = new Set(['claude', 'codex', 'antigravity', 'opencode']);
|
|
4
|
+
const TUI_AGENT_CLIENT_IDS = Object.freeze({
|
|
5
|
+
claude: 'claude-code',
|
|
6
|
+
codex: 'codex',
|
|
7
|
+
antigravity: 'antigravity-cli',
|
|
8
|
+
opencode: 'opencode'
|
|
9
|
+
});
|
|
4
10
|
function isTuiName(value) {
|
|
5
11
|
return typeof value === 'string' && TUI_NAMES.has(value);
|
|
6
12
|
}
|
|
@@ -18,14 +24,7 @@ export function selectTui(skill, options) {
|
|
|
18
24
|
return 'codex';
|
|
19
25
|
}
|
|
20
26
|
export function renderPrompt(params) {
|
|
21
|
-
|
|
22
|
-
if (params.tui === 'codex') {
|
|
23
|
-
return renderAgentClientInvocation(getAgentClientAdapter('codex').invocation, {
|
|
24
|
-
skillName: params.skill,
|
|
25
|
-
args: params.args
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
return `/${suffix}`;
|
|
27
|
+
return renderAgentClientInvocation(getAgentClientAdapter(TUI_AGENT_CLIENT_IDS[params.tui]).invocation, { skillName: params.skill, args: params.args });
|
|
29
28
|
}
|
|
30
29
|
export function buildTuiCommand(tui, prompt) {
|
|
31
30
|
if (tui === 'claude')
|
|
@@ -62,6 +62,27 @@ function assertUniqueTools(tools) {
|
|
|
62
62
|
seen.add(tool.id);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
function unique(values) {
|
|
66
|
+
return Object.freeze([...new Set(values)]);
|
|
67
|
+
}
|
|
68
|
+
function sandboxImageContribution(adapters, tools) {
|
|
69
|
+
const homePrefix = '/home/devuser/';
|
|
70
|
+
const derivedExclusions = tools.map((tool) => {
|
|
71
|
+
if (!tool.containerMount.startsWith(homePrefix)) {
|
|
72
|
+
throw new Error(`Agent Client '${tool.id}' container mount must be below ${homePrefix.slice(0, -1)}`);
|
|
73
|
+
}
|
|
74
|
+
return tool.containerMount.slice(homePrefix.length);
|
|
75
|
+
});
|
|
76
|
+
return Object.freeze({
|
|
77
|
+
dockerfileFragments: unique(adapters.flatMap((adapter) => adapter.sandbox.image?.dockerfileFragment
|
|
78
|
+
? [adapter.sandbox.image.dockerfileFragment]
|
|
79
|
+
: [])),
|
|
80
|
+
dotfilesExclusions: unique([
|
|
81
|
+
...derivedExclusions,
|
|
82
|
+
...adapters.flatMap((adapter) => adapter.sandbox.image?.dotfilesExclusions ?? [])
|
|
83
|
+
])
|
|
84
|
+
});
|
|
85
|
+
}
|
|
65
86
|
function hooksByPhase(adapters) {
|
|
66
87
|
const phases = [
|
|
67
88
|
'prepare',
|
|
@@ -108,6 +129,7 @@ function createSandboxCapabilityPlan(config) {
|
|
|
108
129
|
}
|
|
109
130
|
const tools = Object.freeze([...nonClientTools, ...selectedTools, ...selectedCustomTools]);
|
|
110
131
|
assertUniqueTools(tools);
|
|
132
|
+
const image = sandboxImageContribution(selectedAgentClients, selectedTools);
|
|
111
133
|
const phaseHooks = hooksByPhase(selectedAgentClients);
|
|
112
134
|
const hookProjection = selectedAgentClients.flatMap((adapter) => adapter.sandbox.hooks.map((hook) => ({
|
|
113
135
|
adapterId: adapter.id,
|
|
@@ -134,7 +156,8 @@ function createSandboxCapabilityPlan(config) {
|
|
|
134
156
|
tools: Object.freeze(tools.map(runtimeToolProjection)),
|
|
135
157
|
selectedAgentClients: Object.freeze(selectedAgentClients.map((adapter) => adapter.id)),
|
|
136
158
|
hooks: Object.freeze(hookProjection),
|
|
137
|
-
recoveryChecks: recoveryCheckProjection
|
|
159
|
+
recoveryChecks: recoveryCheckProjection,
|
|
160
|
+
image
|
|
138
161
|
});
|
|
139
162
|
const cleanupInventory = Object.freeze([
|
|
140
163
|
agentInfraTool(config.home),
|
|
@@ -148,6 +171,7 @@ function createSandboxCapabilityPlan(config) {
|
|
|
148
171
|
hooksByPhase: phaseHooks,
|
|
149
172
|
recoveryChecks,
|
|
150
173
|
aliases: Object.freeze(listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases)),
|
|
174
|
+
image,
|
|
151
175
|
imageSignature: hash(tools.map(({ id, install }) => ({ id, install }))),
|
|
152
176
|
runtimeSignature: hash(runtimeProjection),
|
|
153
177
|
runtimeProjection,
|
|
@@ -230,5 +254,5 @@ function runBoundedSandboxHookCommand(command, options) {
|
|
|
230
254
|
});
|
|
231
255
|
});
|
|
232
256
|
}
|
|
233
|
-
export { DEFAULT_SANDBOX_HOOK_TIMEOUT_MS, createSandboxCapabilityPlan, runBoundedSandboxHookCommand, runSandboxHooks };
|
|
257
|
+
export { DEFAULT_SANDBOX_HOOK_TIMEOUT_MS, createSandboxCapabilityPlan, runBoundedSandboxHookCommand, runSandboxHooks, sandboxImageContribution };
|
|
234
258
|
//# sourceMappingURL=agent-client-reconciler.js.map
|
|
@@ -6,6 +6,7 @@ import { commandForEngine } from './shell.js';
|
|
|
6
6
|
import { ensureSandboxReady } from './recovery.js';
|
|
7
7
|
import { hostTimezoneEnvFlags, terminalEnvFlags } from './commands/enter.js';
|
|
8
8
|
import { fetchSandboxRows, selectSandboxContainer, startSandboxContainer } from './commands/list-running.js';
|
|
9
|
+
import { resolveSandboxTarget } from './workspace-identity.js';
|
|
9
10
|
async function spawnCapture(file, args) {
|
|
10
11
|
return new Promise((resolve, reject) => {
|
|
11
12
|
const child = spawn(file, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
@@ -122,6 +123,8 @@ EOF
|
|
|
122
123
|
export async function runInSandbox(request, options = {}) {
|
|
123
124
|
const config = options.engine ? null : loadConfig();
|
|
124
125
|
const engine = options.engine ?? detectEngine(config);
|
|
126
|
+
const workspace = request.workspace
|
|
127
|
+
?? (config ? resolveSandboxTarget(request.taskRef, config.repoRoot).workspace : undefined);
|
|
125
128
|
const rows = options.rows ??
|
|
126
129
|
(() => {
|
|
127
130
|
const fetched = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
@@ -136,6 +139,7 @@ export async function runInSandbox(request, options = {}) {
|
|
|
136
139
|
if (options.ensureReady) {
|
|
137
140
|
container = (await options.ensureReady({
|
|
138
141
|
branch: request.branch,
|
|
142
|
+
workspace,
|
|
139
143
|
row: found,
|
|
140
144
|
allowRecreate: request.recreate ?? false
|
|
141
145
|
})).container;
|
|
@@ -145,11 +149,12 @@ export async function runInSandbox(request, options = {}) {
|
|
|
145
149
|
config,
|
|
146
150
|
engine,
|
|
147
151
|
branch: request.branch,
|
|
152
|
+
workspace,
|
|
148
153
|
row: found,
|
|
149
154
|
allowRecreate: request.recreate,
|
|
150
|
-
recreate: async (
|
|
155
|
+
recreate: async () => {
|
|
151
156
|
const { create } = await import('./commands/create.js');
|
|
152
|
-
await create([
|
|
157
|
+
await create([request.taskRef, '--no-refresh']);
|
|
153
158
|
}
|
|
154
159
|
})).container;
|
|
155
160
|
}
|
|
@@ -2,32 +2,33 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
|
-
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import * as p from '@clack/prompts';
|
|
8
9
|
import pc from 'picocolors';
|
|
9
10
|
import { listAgentClientAdapters } from '../../agent-clients/registry.js';
|
|
10
11
|
import { loadConfig } from '../config.js';
|
|
11
|
-
import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, sandboxRuntimeCapabilityLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
|
|
12
|
+
import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, sandboxRuntimeCapabilityLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
|
|
12
13
|
import { prepareDockerfile } from '../dockerfile.js';
|
|
13
14
|
import { assertBuildProxyCompatibility, buildProxyFailureHint, prepareBuildProxy, redactBuildProxyValues } from '../build-proxy.js';
|
|
14
15
|
import { detectEngine, ensureDocker } from '../engine.js';
|
|
15
16
|
import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from '../shell.js';
|
|
16
|
-
import {
|
|
17
|
+
import { parseSandboxWorkspaceIdentity, resolveSandboxTarget, sameSandboxWorkspaceIdentity } from '../workspace-identity.js';
|
|
17
18
|
import { createSandboxCapabilityPlan, runSandboxHooks, runBoundedSandboxHookCommand } from '../agent-client-reconciler.js';
|
|
18
19
|
import { resolveTools, tmpfsSeedStagingPath, tmpfsSeedTargetPath, toolConfigDirCandidates } from '../tools.js';
|
|
19
20
|
import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts } from '../recovery.js';
|
|
20
21
|
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
|
|
21
22
|
import { sandboxCoreBindMounts } from '../mounts.js';
|
|
23
|
+
import { assertSandboxTaskSource, materializeSandboxControl, materializeSandboxWorkspaceView } from '../workspace-view.js';
|
|
22
24
|
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.js';
|
|
23
25
|
import { validateSelinuxDisableEnv } from '../engines/selinux.js';
|
|
24
26
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
25
27
|
import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.js';
|
|
26
28
|
import { removeDirRecursive } from '../../remove-dir.js';
|
|
27
|
-
import {
|
|
29
|
+
import { redactCommandError } from '../redaction.js';
|
|
28
30
|
import { detectHostTimezone } from '../host-timezone.js';
|
|
29
31
|
import { buildImageSignature, buildSandboxImageArgs, isRefreshDisabled, isRefreshDue, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
|
|
30
|
-
export { ensureCodexModelInheritance, ensureCodexWorkspaceTrust } from '../../agent-clients/adapters/codex-sandbox.js';
|
|
31
32
|
const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
|
|
32
33
|
const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
|
|
33
34
|
const CONTAINER_HOME = '/home/devuser';
|
|
@@ -64,6 +65,11 @@ const MANAGED_GITHUB_GIT_CONFIG = [
|
|
|
64
65
|
'[url "https://github.com/"]',
|
|
65
66
|
'\tinsteadOf = git@github.com:'
|
|
66
67
|
];
|
|
68
|
+
function internalCliPath() {
|
|
69
|
+
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const extension = path.extname(fileURLToPath(import.meta.url));
|
|
71
|
+
return path.resolve(directory, '..', '..', '..', 'bin', `internal-cli${extension}`);
|
|
72
|
+
}
|
|
67
73
|
function resolveToolDirs(config, tools, branch) {
|
|
68
74
|
return tools.map((tool) => {
|
|
69
75
|
const candidates = toolConfigDirCandidates(tool, config.project, branch);
|
|
@@ -544,191 +550,6 @@ export function assertBranchAvailable(repoRoot, branch, { allowedWorktrees = [],
|
|
|
544
550
|
}
|
|
545
551
|
}
|
|
546
552
|
}
|
|
547
|
-
function readHostJsonSafe(filePath) {
|
|
548
|
-
if (!filePath || !fs.existsSync(filePath)) {
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
try {
|
|
552
|
-
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
553
|
-
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
554
|
-
}
|
|
555
|
-
catch {
|
|
556
|
-
return null;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
const CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS = [
|
|
560
|
-
'model',
|
|
561
|
-
'fallbackModel',
|
|
562
|
-
'availableModels',
|
|
563
|
-
'modelOverrides',
|
|
564
|
-
'enforceAvailableModels',
|
|
565
|
-
'advisorModel',
|
|
566
|
-
'apiKeyHelper',
|
|
567
|
-
'effortLevel'
|
|
568
|
-
];
|
|
569
|
-
function isJsonObjectRecord(value) {
|
|
570
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
571
|
-
}
|
|
572
|
-
function mergeMissingStringEnvFields(target, source) {
|
|
573
|
-
if (!isJsonObjectRecord(source.env)) {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
if (Object.hasOwn(target, 'env') && !isJsonObjectRecord(target.env)) {
|
|
577
|
-
return false;
|
|
578
|
-
}
|
|
579
|
-
let targetEnv = target.env;
|
|
580
|
-
let changed = false;
|
|
581
|
-
for (const [key, value] of Object.entries(source.env)) {
|
|
582
|
-
if (typeof value !== 'string' || value === '') {
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
if (!targetEnv) {
|
|
586
|
-
targetEnv = {};
|
|
587
|
-
target.env = targetEnv;
|
|
588
|
-
}
|
|
589
|
-
if (!Object.hasOwn(targetEnv, key)) {
|
|
590
|
-
targetEnv[key] = value;
|
|
591
|
-
changed = true;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return changed;
|
|
595
|
-
}
|
|
596
|
-
function mergeMissingTopLevelSettings(target, source) {
|
|
597
|
-
let changed = false;
|
|
598
|
-
for (const key of CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS) {
|
|
599
|
-
if (!Object.hasOwn(source, key) || Object.hasOwn(target, key)) {
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
const value = source[key];
|
|
603
|
-
if (value === null || value === undefined || value === '') {
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
target[key] = value;
|
|
607
|
-
changed = true;
|
|
608
|
-
}
|
|
609
|
-
return changed;
|
|
610
|
-
}
|
|
611
|
-
export function ensureClaudeOnboarding(toolDir, hostHomeDir) {
|
|
612
|
-
const claudeJsonPath = path.join(toolDir, '.claude.json');
|
|
613
|
-
let data = {};
|
|
614
|
-
if (fs.existsSync(claudeJsonPath)) {
|
|
615
|
-
try {
|
|
616
|
-
data = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8'));
|
|
617
|
-
}
|
|
618
|
-
catch {
|
|
619
|
-
// malformed JSON, start fresh
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
let changed = false;
|
|
623
|
-
if (!data.hasCompletedOnboarding) {
|
|
624
|
-
data.hasCompletedOnboarding = true;
|
|
625
|
-
changed = true;
|
|
626
|
-
}
|
|
627
|
-
if (!data.projects) {
|
|
628
|
-
data.projects = {};
|
|
629
|
-
changed = true;
|
|
630
|
-
}
|
|
631
|
-
if (!data.projects['/workspace']) {
|
|
632
|
-
data.projects['/workspace'] = {};
|
|
633
|
-
changed = true;
|
|
634
|
-
}
|
|
635
|
-
if (!data.projects['/workspace'].hasTrustDialogAccepted) {
|
|
636
|
-
data.projects['/workspace'].hasTrustDialogAccepted = true;
|
|
637
|
-
changed = true;
|
|
638
|
-
}
|
|
639
|
-
if (hostHomeDir) {
|
|
640
|
-
const hostClaudeJson = readHostJsonSafe(path.join(hostHomeDir, '.claude.json'));
|
|
641
|
-
if (hostClaudeJson
|
|
642
|
-
&& typeof hostClaudeJson.model === 'string'
|
|
643
|
-
&& hostClaudeJson.model !== ''
|
|
644
|
-
&& !Object.hasOwn(data, 'model')) {
|
|
645
|
-
data.model = hostClaudeJson.model;
|
|
646
|
-
changed = true;
|
|
647
|
-
}
|
|
648
|
-
// Claude Code launch-pins a default effort per model generation (for
|
|
649
|
-
// example xhigh for Opus 4.7). The saved effortLevel is honored only after
|
|
650
|
-
// a top-level boolean `unpin*LaunchEffort: true` flag unlocks it, so mirror
|
|
651
|
-
// those flags here with the existing first-write semantics.
|
|
652
|
-
//
|
|
653
|
-
// Pattern matching avoids one patch per future model generation. If
|
|
654
|
-
// Anthropic changes the naming convention, this block will no-op and should
|
|
655
|
-
// be revisited.
|
|
656
|
-
if (hostClaudeJson) {
|
|
657
|
-
for (const key of Object.keys(hostClaudeJson)) {
|
|
658
|
-
if (/^unpin.*LaunchEffort$/.test(key)
|
|
659
|
-
&& hostClaudeJson[key] === true
|
|
660
|
-
&& !Object.hasOwn(data, key)) {
|
|
661
|
-
data[key] = true;
|
|
662
|
-
changed = true;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
if (changed) {
|
|
668
|
-
fs.writeFileSync(claudeJsonPath, JSON.stringify(data, null, 4), 'utf8');
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
export function ensureClaudeSettings(toolDir, hostHomeDir) {
|
|
672
|
-
const settingsPath = path.join(toolDir, 'settings.json');
|
|
673
|
-
let data = {};
|
|
674
|
-
if (fs.existsSync(settingsPath)) {
|
|
675
|
-
try {
|
|
676
|
-
data = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
677
|
-
}
|
|
678
|
-
catch {
|
|
679
|
-
// malformed JSON, start fresh
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
let changed = false;
|
|
683
|
-
if (data.skipDangerousModePermissionPrompt !== true) {
|
|
684
|
-
data.skipDangerousModePermissionPrompt = true;
|
|
685
|
-
changed = true;
|
|
686
|
-
}
|
|
687
|
-
if (hostHomeDir) {
|
|
688
|
-
const hostSettings = readHostJsonSafe(path.join(hostHomeDir, '.claude', 'settings.json'));
|
|
689
|
-
if (hostSettings) {
|
|
690
|
-
changed = mergeMissingStringEnvFields(data, hostSettings) || changed;
|
|
691
|
-
changed = mergeMissingTopLevelSettings(data, hostSettings) || changed;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
if (changed) {
|
|
695
|
-
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 4), 'utf8');
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
export function ensureOpenCodeModelInheritance(toolDir, hostHomeDir) {
|
|
699
|
-
if (!hostHomeDir) {
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
const hostConfigPath = path.join(hostHomeDir, '.config', 'opencode', 'opencode.json');
|
|
703
|
-
const hostJson = readHostJsonSafe(hostConfigPath);
|
|
704
|
-
if (!hostJson) {
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
const sandboxConfigPath = path.join(toolDir, 'opencode.json');
|
|
708
|
-
let sandboxJson = {};
|
|
709
|
-
if (fs.existsSync(sandboxConfigPath)) {
|
|
710
|
-
const existing = readHostJsonSafe(sandboxConfigPath);
|
|
711
|
-
if (!existing) {
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
sandboxJson = existing;
|
|
715
|
-
}
|
|
716
|
-
let changed = false;
|
|
717
|
-
for (const key of ['model', 'small_model']) {
|
|
718
|
-
if (Object.hasOwn(sandboxJson, key)) {
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
const value = hostJson[key];
|
|
722
|
-
if (typeof value !== 'string' || value === '') {
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
sandboxJson[key] = value;
|
|
726
|
-
changed = true;
|
|
727
|
-
}
|
|
728
|
-
if (changed) {
|
|
729
|
-
fs.writeFileSync(sandboxConfigPath, JSON.stringify(sandboxJson, null, 2), 'utf8');
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
553
|
export function sandboxAliasesPath(home) {
|
|
733
554
|
return hostJoin(home, '.agent-infra', 'aliases', 'sandbox.sh');
|
|
734
555
|
}
|
|
@@ -848,10 +669,10 @@ export async function create(args) {
|
|
|
848
669
|
throw new Error(USAGE);
|
|
849
670
|
}
|
|
850
671
|
validateSelinuxDisableEnv();
|
|
851
|
-
validateClaudeCredentialsEnvOverride();
|
|
852
672
|
const config = loadConfig();
|
|
853
673
|
const [branchOrTaskId = '', base] = positionals;
|
|
854
|
-
const
|
|
674
|
+
const target = resolveSandboxTarget(branchOrTaskId, config.repoRoot);
|
|
675
|
+
const branch = target.branch;
|
|
855
676
|
assertValidBranchName(branch);
|
|
856
677
|
const effectiveConfig = {
|
|
857
678
|
...config,
|
|
@@ -866,39 +687,43 @@ export async function create(args) {
|
|
|
866
687
|
const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
|
|
867
688
|
const tools = [...capabilityPlan.tools];
|
|
868
689
|
const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
690
|
+
const prepareHookResults = await runSandboxHooks({
|
|
691
|
+
hooks: capabilityPlan.hooksByPhase.prepare,
|
|
692
|
+
phase: 'prepare',
|
|
693
|
+
context: {
|
|
694
|
+
config: effectiveConfig,
|
|
695
|
+
plan: capabilityPlan,
|
|
696
|
+
create: {
|
|
697
|
+
hostHome: effectiveConfig.home,
|
|
698
|
+
hostEnv: { ...process.env },
|
|
699
|
+
project: effectiveConfig.project,
|
|
700
|
+
resolvedTools
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
runCommand: runBoundedSandboxHookCommand
|
|
704
|
+
});
|
|
705
|
+
const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
|
|
706
|
+
if (prepareFailure) {
|
|
707
|
+
throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
|
|
708
|
+
}
|
|
873
709
|
const effectiveResolvedTools = resolvedTools;
|
|
874
710
|
const container = containerName(effectiveConfig, branch);
|
|
875
711
|
const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
|
|
876
712
|
const shareCommon = shareCommonDir(effectiveConfig);
|
|
877
713
|
const shareBranch = shareBranchDir(effectiveConfig, branch);
|
|
878
|
-
const preparedDockerfile = prepareDockerfile(effectiveConfig);
|
|
714
|
+
const preparedDockerfile = prepareDockerfile(effectiveConfig, capabilityPlan.image);
|
|
879
715
|
const baseBranch = base ?? runSafe('git', ['-C', effectiveConfig.repoRoot, 'branch', '--show-current']);
|
|
880
716
|
const expectedImageSignature = buildImageSignature(preparedDockerfile, tools);
|
|
881
717
|
const engine = detectEngine(effectiveConfig);
|
|
882
718
|
let createdTmpfsSeedPlan = [];
|
|
883
719
|
p.intro(pc.cyan('AI Sandbox'));
|
|
884
720
|
p.log.info(`Project: ${pc.bold(effectiveConfig.project)} | Branch: ${pc.bold(branch)} | Base: ${pc.bold(baseBranch || 'HEAD')}`);
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
+ ' To enable it: run "claude" once on the host to complete login, then re-run "ai sandbox create".');
|
|
721
|
+
for (const result of prepareHookResults) {
|
|
722
|
+
if (result.status === 'warning' && result.message)
|
|
723
|
+
p.log.warn(result.message);
|
|
889
724
|
}
|
|
890
725
|
try {
|
|
891
726
|
p.log.step('Checking container engine...');
|
|
892
|
-
const prepareHookResults = await runSandboxHooks({
|
|
893
|
-
hooks: capabilityPlan.hooksByPhase.prepare,
|
|
894
|
-
phase: 'prepare',
|
|
895
|
-
context: { config: effectiveConfig, plan: capabilityPlan },
|
|
896
|
-
runCommand: runBoundedSandboxHookCommand
|
|
897
|
-
});
|
|
898
|
-
const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
|
|
899
|
-
if (prepareFailure) {
|
|
900
|
-
throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
|
|
901
|
-
}
|
|
902
727
|
await ensureDocker(effectiveConfig, (detail) => {
|
|
903
728
|
p.log.info(` ${detail}`);
|
|
904
729
|
});
|
|
@@ -1048,6 +873,32 @@ export async function create(args) {
|
|
|
1048
873
|
const matchedContainers = containerNameCandidates(effectiveConfig, branch)
|
|
1049
874
|
.filter((name) => existing.includes(name));
|
|
1050
875
|
if (matchedContainers.length > 0) {
|
|
876
|
+
for (const name of matchedContainers) {
|
|
877
|
+
const rawLabels = runSafeEngine(engine, 'docker', [
|
|
878
|
+
'inspect', '--format', '{{json .Config.Labels}}', name
|
|
879
|
+
]).trim();
|
|
880
|
+
let labels = {};
|
|
881
|
+
try {
|
|
882
|
+
labels = JSON.parse(rawLabels);
|
|
883
|
+
}
|
|
884
|
+
catch {
|
|
885
|
+
labels = {};
|
|
886
|
+
}
|
|
887
|
+
const existingIdentity = parseSandboxWorkspaceIdentity(labels, {
|
|
888
|
+
mode: sandboxWorkspaceModeLabel(effectiveConfig),
|
|
889
|
+
taskId: sandboxTaskIdLabel(effectiveConfig)
|
|
890
|
+
});
|
|
891
|
+
if (!sameSandboxWorkspaceIdentity(existingIdentity, target.workspace)) {
|
|
892
|
+
const existingDescription = existingIdentity.mode === 'task-bound'
|
|
893
|
+
? `task-bound:${existingIdentity.taskId}`
|
|
894
|
+
: existingIdentity.mode;
|
|
895
|
+
const requestedDescription = target.workspace.mode === 'task-bound'
|
|
896
|
+
? `task-bound:${target.workspace.taskId}`
|
|
897
|
+
: target.workspace.mode;
|
|
898
|
+
throw new Error(`SANDBOX_WORKSPACE_IDENTITY_CONFLICT: container '${name}' is ${existingDescription}, `
|
|
899
|
+
+ `but this request is ${requestedDescription}. Run 'ai sandbox rm ${branch}' and retry.`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
1051
902
|
message('Removing old container instance...');
|
|
1052
903
|
for (const name of matchedContainers) {
|
|
1053
904
|
runSafeEngine(engine, 'docker', ['stop', name]);
|
|
@@ -1091,6 +942,8 @@ export async function create(args) {
|
|
|
1091
942
|
plan: capabilityPlan,
|
|
1092
943
|
create: {
|
|
1093
944
|
hostHome: effectiveConfig.home,
|
|
945
|
+
hostEnv: { ...process.env },
|
|
946
|
+
project: effectiveConfig.project,
|
|
1094
947
|
resolvedTools: effectiveResolvedTools
|
|
1095
948
|
}
|
|
1096
949
|
},
|
|
@@ -1101,22 +954,23 @@ export async function create(args) {
|
|
|
1101
954
|
throw new Error(beforeCreateFailure.message
|
|
1102
955
|
?? `Sandbox hook '${beforeCreateFailure.hookId}' failed.`);
|
|
1103
956
|
}
|
|
1104
|
-
const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
|
|
1105
|
-
if (claudeCodeEntry) {
|
|
1106
|
-
ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
|
|
1107
|
-
ensureClaudeSettings(claudeCodeEntry.dir, effectiveConfig.home);
|
|
1108
|
-
// prepareClaudeCredentials wrote OAuth credentials or confirmed
|
|
1109
|
-
// provider/API settings are enough for Claude Code. If no usable
|
|
1110
|
-
// auth was present, the claude-code entry was removed above.
|
|
1111
|
-
}
|
|
1112
|
-
const opencodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'opencode');
|
|
1113
|
-
if (opencodeEntry) {
|
|
1114
|
-
// The TUI reads <toolDir>/opencode.json via OPENCODE_CONFIG pinned in tools.js.
|
|
1115
|
-
ensureOpenCodeModelInheritance(opencodeEntry.dir, effectiveConfig.home);
|
|
1116
|
-
}
|
|
1117
957
|
const toolVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) => tool.tmpfs ? [] : ['-v', volumeArg(engine, dir, tool.containerMount)]);
|
|
1118
958
|
const tmpfsArgs = effectiveResolvedTools.flatMap(({ tool }) => tool.tmpfs ? buildTmpfsRunArgs(tool.containerMount, tool.tmpfs) : []);
|
|
1119
|
-
const
|
|
959
|
+
const workspaceView = materializeSandboxWorkspaceView({
|
|
960
|
+
base: effectiveConfig.workspaceViewBase,
|
|
961
|
+
project: effectiveConfig.project,
|
|
962
|
+
container,
|
|
963
|
+
identity: target.workspace
|
|
964
|
+
});
|
|
965
|
+
const control = materializeSandboxControl({
|
|
966
|
+
base: effectiveConfig.controlBase,
|
|
967
|
+
repoRoot: effectiveConfig.repoRoot,
|
|
968
|
+
worktreeRoot: worktree,
|
|
969
|
+
project: effectiveConfig.project,
|
|
970
|
+
container,
|
|
971
|
+
branch,
|
|
972
|
+
identity: target.workspace
|
|
973
|
+
});
|
|
1120
974
|
hostShellConfig = prepareHostShellConfig({
|
|
1121
975
|
home: effectiveConfig.home,
|
|
1122
976
|
project: effectiveConfig.project,
|
|
@@ -1125,7 +979,15 @@ export async function create(args) {
|
|
|
1125
979
|
});
|
|
1126
980
|
const coreVolumes = sandboxCoreBindMounts(effectiveConfig, branch, {
|
|
1127
981
|
worktree,
|
|
1128
|
-
shellConfigHostDir: hostShellConfig.hostDir
|
|
982
|
+
shellConfigHostDir: hostShellConfig.hostDir,
|
|
983
|
+
workspaceViewRoot: workspaceView.root,
|
|
984
|
+
controlDir: control.channelDir,
|
|
985
|
+
...(target.workspace.mode === 'task-bound'
|
|
986
|
+
? {
|
|
987
|
+
taskSource: assertSandboxTaskSource(effectiveConfig.repoRoot, target.workspace.taskId),
|
|
988
|
+
taskId: target.workspace.taskId
|
|
989
|
+
}
|
|
990
|
+
: {})
|
|
1129
991
|
}).flatMap(({ hostPaths, containerPath, readOnly }) => [
|
|
1130
992
|
'-v',
|
|
1131
993
|
volumeArg(engine, hostPaths[0], containerPath, readOnly ? ':ro' : '')
|
|
@@ -1157,7 +1019,6 @@ export async function create(args) {
|
|
|
1157
1019
|
'-v',
|
|
1158
1020
|
volumeArg(engine, hostPath, path.posix.join(tool.containerMount, containerSubpath))
|
|
1159
1021
|
]));
|
|
1160
|
-
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
1161
1022
|
fs.mkdirSync(shareCommon, { recursive: true });
|
|
1162
1023
|
fs.mkdirSync(shareBranch, { recursive: true });
|
|
1163
1024
|
fs.mkdirSync(clipboardHostDir(effectiveConfig.home), { recursive: true, mode: 0o700 });
|
|
@@ -1179,6 +1040,20 @@ export async function create(args) {
|
|
|
1179
1040
|
'--label',
|
|
1180
1041
|
`${sandboxBranchLabel(effectiveConfig)}=${branch}`,
|
|
1181
1042
|
'--label',
|
|
1043
|
+
`${sandboxWorkspaceModeLabel(effectiveConfig)}=${target.workspace.mode}`,
|
|
1044
|
+
...(target.workspace.mode === 'task-bound'
|
|
1045
|
+
? [
|
|
1046
|
+
'--label',
|
|
1047
|
+
`${sandboxTaskIdLabel(effectiveConfig)}=${target.workspace.taskId}`,
|
|
1048
|
+
'-e',
|
|
1049
|
+
`AGENT_INFRA_TASK_ID=${target.workspace.taskId}`
|
|
1050
|
+
]
|
|
1051
|
+
: []),
|
|
1052
|
+
'-e',
|
|
1053
|
+
`AGENT_INFRA_CONTROL_TOKEN=${control.token}`,
|
|
1054
|
+
'-e',
|
|
1055
|
+
'AGENT_INFRA_CONTROL_DIR=/run/agent-infra/control',
|
|
1056
|
+
'--label',
|
|
1182
1057
|
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
|
1183
1058
|
...coreVolumes,
|
|
1184
1059
|
...buildClipboardVolumeArgs(engine, effectiveConfig.home),
|
|
@@ -1195,6 +1070,12 @@ export async function create(args) {
|
|
|
1195
1070
|
'/workspace',
|
|
1196
1071
|
effectiveConfig.imageName
|
|
1197
1072
|
]);
|
|
1073
|
+
const broker = spawn(process.execPath, [internalCliPath(), 'sandbox-control', 'serve', '--manifest', control.manifestPath], { cwd: effectiveConfig.repoRoot, detached: true, stdio: 'ignore' });
|
|
1074
|
+
broker.once('error', () => {
|
|
1075
|
+
// Fresh readiness verification below remains the source of truth;
|
|
1076
|
+
// consume detached spawn errors so they cannot crash the command.
|
|
1077
|
+
});
|
|
1078
|
+
broker.unref();
|
|
1198
1079
|
createdTmpfsSeedPlan = tmpfsSeedPlan;
|
|
1199
1080
|
}
|
|
1200
1081
|
finally {
|
|
@@ -1277,10 +1158,11 @@ export async function create(args) {
|
|
|
1277
1158
|
preparedDockerfile.cleanup();
|
|
1278
1159
|
}
|
|
1279
1160
|
p.log.step('Verifying setup...');
|
|
1280
|
-
assertFreshSandboxReady({
|
|
1161
|
+
await assertFreshSandboxReady({
|
|
1281
1162
|
config: effectiveConfig,
|
|
1282
1163
|
engine,
|
|
1283
1164
|
branch,
|
|
1165
|
+
workspace: target.workspace,
|
|
1284
1166
|
container,
|
|
1285
1167
|
copiedEntries: createdTmpfsSeedPlan
|
|
1286
1168
|
});
|