@fitlab-ai/agent-infra 0.9.3 → 0.9.4
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 +2 -0
- package/README.zh-CN.md +2 -0
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +134 -123
- package/dist/lib/agent-clients/adapter.js +92 -2
- 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/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/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +45 -13
- 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 +122 -18
- 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 +109 -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 +44 -0
- package/dist/lib/sandbox/control/protocol.js +35 -0
- package/dist/lib/sandbox/control/server.js +109 -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 +103 -0
- package/dist/lib/task/activity-intent.js +257 -51
- package/dist/lib/task/commit-finalization.js +267 -0
- package/dist/lib/task/commit-intent.js +165 -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 +363 -28
- package/dist/lib/task/resolve-ref.js +41 -1
- package/lib/agent-clients/adapter.ts +126 -2
- 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/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/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +50 -14
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +22 -1
- package/lib/internal/task-orchestration.ts +81 -11
- 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 +126 -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 +55 -0
- package/lib/sandbox/control/protocol.ts +75 -0
- package/lib/sandbox/control/server.ts +117 -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 +143 -0
- package/lib/task/activity-intent.ts +350 -61
- package/lib/task/commit-finalization.ts +356 -0
- package/lib/task/commit-intent.ts +230 -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 +501 -33
- package/lib/task/resolve-ref.ts +46 -1
- package/package.json +1 -1
- 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 +2 -0
- package/templates/.agents/rules/task-management.zh-CN.md +2 -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 -11
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -11
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -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/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/.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
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getProcessStartTime } from '../../server/process-state.ts';
|
|
5
|
+
import {
|
|
6
|
+
bindSandboxControlTask,
|
|
7
|
+
validateSandboxControlRequest,
|
|
8
|
+
type SandboxControlManifest,
|
|
9
|
+
type SandboxControlResponse
|
|
10
|
+
} from './protocol.ts';
|
|
11
|
+
|
|
12
|
+
function readManifest(manifestPath: string): SandboxControlManifest {
|
|
13
|
+
const stat = fs.lstatSync(manifestPath);
|
|
14
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
15
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as SandboxControlManifest;
|
|
16
|
+
if (
|
|
17
|
+
manifest.version !== 1
|
|
18
|
+
|| typeof manifest.repoRoot !== 'string'
|
|
19
|
+
|| typeof manifest.channelDir !== 'string'
|
|
20
|
+
|| typeof manifest.token !== 'string'
|
|
21
|
+
) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
22
|
+
if (path.resolve(manifest.channelDir) !== path.join(path.dirname(path.resolve(manifestPath)), 'channel')) {
|
|
23
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
24
|
+
}
|
|
25
|
+
return manifest;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertRealDirectory(directory: string, parent?: string): void {
|
|
29
|
+
const stat = fs.lstatSync(directory);
|
|
30
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
31
|
+
if (parent && path.dirname(fs.realpathSync.native(directory)) !== fs.realpathSync.native(parent)) {
|
|
32
|
+
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function writeResponse(directory: string, response: SandboxControlResponse): void {
|
|
37
|
+
const temporary = path.join(directory, `.${response.id}.tmp`);
|
|
38
|
+
fs.writeFileSync(temporary, `${JSON.stringify(response)}\n`, { mode: 0o600, flag: 'wx' });
|
|
39
|
+
fs.renameSync(temporary, path.join(directory, `${response.id}.json`));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function serveSandboxControl(manifestPath: string, signal: AbortSignal = new AbortController().signal): void {
|
|
43
|
+
const manifest = readManifest(manifestPath);
|
|
44
|
+
const brokerPath = path.join(path.dirname(manifestPath), 'broker.json');
|
|
45
|
+
const startTime = getProcessStartTime(process.pid);
|
|
46
|
+
if (!startTime) throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
47
|
+
fs.writeFileSync(brokerPath, `${JSON.stringify({
|
|
48
|
+
version: 1,
|
|
49
|
+
pid: process.pid,
|
|
50
|
+
startTime,
|
|
51
|
+
token: manifest.token
|
|
52
|
+
})}\n`, { mode: 0o600, flag: 'wx' });
|
|
53
|
+
const requestsDir = path.join(manifest.channelDir, 'requests');
|
|
54
|
+
const responsesDir = path.join(manifest.channelDir, 'responses');
|
|
55
|
+
assertRealDirectory(manifest.channelDir);
|
|
56
|
+
fs.mkdirSync(requestsDir, { recursive: true, mode: 0o700 });
|
|
57
|
+
fs.mkdirSync(responsesDir, { recursive: true, mode: 0o700 });
|
|
58
|
+
assertRealDirectory(requestsDir, manifest.channelDir);
|
|
59
|
+
assertRealDirectory(responsesDir, manifest.channelDir);
|
|
60
|
+
const consumed = new Set<string>();
|
|
61
|
+
while (!signal.aborted) {
|
|
62
|
+
const current = readManifest(manifestPath);
|
|
63
|
+
if (current.token !== manifest.token) return;
|
|
64
|
+
assertRealDirectory(requestsDir, manifest.channelDir);
|
|
65
|
+
assertRealDirectory(responsesDir, manifest.channelDir);
|
|
66
|
+
for (const name of fs.readdirSync(requestsDir).sort()) {
|
|
67
|
+
if (!/^[a-f0-9-]{16,64}\.json$/.test(name)) continue;
|
|
68
|
+
const requestPath = path.join(requestsDir, name);
|
|
69
|
+
let id = name.slice(0, -5);
|
|
70
|
+
let response: SandboxControlResponse;
|
|
71
|
+
try {
|
|
72
|
+
const stat = fs.lstatSync(requestPath);
|
|
73
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_REQUEST_INVALID');
|
|
74
|
+
const request = validateSandboxControlRequest(
|
|
75
|
+
JSON.parse(fs.readFileSync(requestPath, 'utf8')),
|
|
76
|
+
manifest
|
|
77
|
+
);
|
|
78
|
+
id = request.id;
|
|
79
|
+
if (consumed.has(id)) throw new Error('SANDBOX_CONTROL_REQUEST_REPLAYED');
|
|
80
|
+
consumed.add(id);
|
|
81
|
+
const result = spawnSync(
|
|
82
|
+
process.execPath,
|
|
83
|
+
['--experimental-strip-types', '--no-warnings', process.argv[1]!, request.family, ...bindSandboxControlTask(request, manifest.taskId!)],
|
|
84
|
+
{
|
|
85
|
+
cwd: manifest.repoRoot,
|
|
86
|
+
encoding: 'utf8',
|
|
87
|
+
env: Object.fromEntries(
|
|
88
|
+
Object.entries(process.env).filter(([key]) => !key.startsWith('AGENT_INFRA_CONTROL_'))
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
response = {
|
|
93
|
+
version: 1,
|
|
94
|
+
id,
|
|
95
|
+
exitCode: result.status ?? 1,
|
|
96
|
+
stdout: result.stdout ?? '',
|
|
97
|
+
stderr: result.stderr ?? ''
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
response = {
|
|
101
|
+
version: 1,
|
|
102
|
+
id,
|
|
103
|
+
exitCode: 1,
|
|
104
|
+
stdout: '',
|
|
105
|
+
stderr: `${error instanceof Error ? error.message : String(error)}\n`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
fs.unlinkSync(requestPath);
|
|
110
|
+
writeResponse(responsesDir, response);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -9,6 +9,23 @@ const RUNTIMES_DIR = path.join(
|
|
|
9
9
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
10
10
|
'runtimes'
|
|
11
11
|
);
|
|
12
|
+
const AGENT_CLIENT_RUNTIMES_DIR = path.join(
|
|
13
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
14
|
+
'..',
|
|
15
|
+
'agent-clients',
|
|
16
|
+
'adapters',
|
|
17
|
+
'runtimes'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
type DockerfileImageContribution = Readonly<{
|
|
21
|
+
dockerfileFragments: readonly string[];
|
|
22
|
+
dotfilesExclusions: readonly string[];
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
const EMPTY_IMAGE_CONTRIBUTION: DockerfileImageContribution = Object.freeze({
|
|
26
|
+
dockerfileFragments: Object.freeze([]),
|
|
27
|
+
dotfilesExclusions: Object.freeze([])
|
|
28
|
+
});
|
|
12
29
|
|
|
13
30
|
type DockerfileConfig = {
|
|
14
31
|
repoRoot: string;
|
|
@@ -29,7 +46,29 @@ export function availableRuntimes(): string[] {
|
|
|
29
46
|
.sort();
|
|
30
47
|
}
|
|
31
48
|
|
|
32
|
-
function
|
|
49
|
+
function exclusionsFragment(exclusions: readonly string[]): string {
|
|
50
|
+
return [
|
|
51
|
+
"RUN mkdir -p /etc/agent-infra && cat > /etc/agent-infra/dotfiles-exclusions <<'EXCLUSIONS'",
|
|
52
|
+
...exclusions,
|
|
53
|
+
'EXCLUSIONS'
|
|
54
|
+
].join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function adapterFragmentPath(fragment: string): string {
|
|
58
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(fragment)) {
|
|
59
|
+
throw new Error(`Invalid Agent Client Dockerfile fragment: ${fragment}`);
|
|
60
|
+
}
|
|
61
|
+
const fragmentPath = path.join(AGENT_CLIENT_RUNTIMES_DIR, `${fragment}.dockerfile`);
|
|
62
|
+
if (!fs.existsSync(fragmentPath)) {
|
|
63
|
+
throw new Error(`Unknown Agent Client Dockerfile fragment: ${fragment}`);
|
|
64
|
+
}
|
|
65
|
+
return fragmentPath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function dockerfileContent(
|
|
69
|
+
config: DockerfileConfig,
|
|
70
|
+
image: DockerfileImageContribution = EMPTY_IMAGE_CONTRIBUTION
|
|
71
|
+
): string {
|
|
33
72
|
if (config.dockerfile) {
|
|
34
73
|
const customPath = path.resolve(config.repoRoot, config.dockerfile);
|
|
35
74
|
if (!fs.existsSync(customPath)) {
|
|
@@ -48,26 +87,37 @@ function dockerfileContent(config: DockerfileConfig): string {
|
|
|
48
87
|
}
|
|
49
88
|
|
|
50
89
|
const fragments = [
|
|
51
|
-
'base.dockerfile',
|
|
52
|
-
|
|
53
|
-
|
|
90
|
+
fs.readFileSync(path.join(RUNTIMES_DIR, 'base.dockerfile'), 'utf8').trimEnd(),
|
|
91
|
+
exclusionsFragment(image.dotfilesExclusions),
|
|
92
|
+
...image.dockerfileFragments.map((fragment) =>
|
|
93
|
+
fs.readFileSync(adapterFragmentPath(fragment), 'utf8').trimEnd()
|
|
94
|
+
),
|
|
95
|
+
...config.runtimes.map((runtime) =>
|
|
96
|
+
fs.readFileSync(path.join(RUNTIMES_DIR, `${runtime}.dockerfile`), 'utf8').trimEnd()
|
|
97
|
+
),
|
|
98
|
+
fs.readFileSync(path.join(RUNTIMES_DIR, 'ai-tools.dockerfile'), 'utf8').trimEnd()
|
|
54
99
|
];
|
|
55
100
|
|
|
56
101
|
const content = fragments
|
|
57
|
-
.map((fragment) => fs.readFileSync(path.join(RUNTIMES_DIR, fragment), 'utf8').trimEnd())
|
|
58
102
|
.join('\n\n');
|
|
59
103
|
|
|
60
104
|
return `${content}\n`;
|
|
61
105
|
}
|
|
62
106
|
|
|
63
|
-
export function dockerfileSignature(
|
|
107
|
+
export function dockerfileSignature(
|
|
108
|
+
config: DockerfileConfig,
|
|
109
|
+
image: DockerfileImageContribution = EMPTY_IMAGE_CONTRIBUTION
|
|
110
|
+
): string {
|
|
64
111
|
return createHash('sha256')
|
|
65
|
-
.update(dockerfileContent(config))
|
|
112
|
+
.update(dockerfileContent(config, image))
|
|
66
113
|
.digest('hex')
|
|
67
114
|
.slice(0, 12);
|
|
68
115
|
}
|
|
69
116
|
|
|
70
|
-
export function prepareDockerfile(
|
|
117
|
+
export function prepareDockerfile(
|
|
118
|
+
config: DockerfileConfig,
|
|
119
|
+
image: DockerfileImageContribution = EMPTY_IMAGE_CONTRIBUTION
|
|
120
|
+
): { path: string; signature: string; cleanup: () => void } {
|
|
71
121
|
if (config.dockerfile) {
|
|
72
122
|
const customPath = path.resolve(config.repoRoot, config.dockerfile);
|
|
73
123
|
if (!fs.existsSync(customPath)) {
|
|
@@ -76,26 +126,29 @@ export function prepareDockerfile(config: DockerfileConfig): { path: string; sig
|
|
|
76
126
|
|
|
77
127
|
return {
|
|
78
128
|
path: customPath,
|
|
79
|
-
signature: dockerfileSignature(config),
|
|
129
|
+
signature: dockerfileSignature(config, image),
|
|
80
130
|
cleanup() {}
|
|
81
131
|
};
|
|
82
132
|
}
|
|
83
133
|
|
|
84
134
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `${config.project}-sandbox-`));
|
|
85
135
|
const tempPath = path.join(tempDir, 'Dockerfile');
|
|
86
|
-
fs.writeFileSync(tempPath, dockerfileContent(config), 'utf8');
|
|
136
|
+
fs.writeFileSync(tempPath, dockerfileContent(config, image), 'utf8');
|
|
87
137
|
|
|
88
138
|
return {
|
|
89
139
|
path: tempPath,
|
|
90
|
-
signature: dockerfileSignature(config),
|
|
140
|
+
signature: dockerfileSignature(config, image),
|
|
91
141
|
cleanup() {
|
|
92
142
|
removeDirRecursive(tempDir);
|
|
93
143
|
}
|
|
94
144
|
};
|
|
95
145
|
}
|
|
96
146
|
|
|
97
|
-
export function composeDockerfile(
|
|
98
|
-
|
|
147
|
+
export function composeDockerfile(
|
|
148
|
+
config: DockerfileConfig,
|
|
149
|
+
image: DockerfileImageContribution = EMPTY_IMAGE_CONTRIBUTION
|
|
150
|
+
): string {
|
|
151
|
+
const content = dockerfileContent(config, image);
|
|
99
152
|
|
|
100
153
|
const tempPath = path.join(os.tmpdir(), `${config.project}-sandbox.Dockerfile`);
|
|
101
154
|
fs.writeFileSync(tempPath, content, 'utf8');
|
package/lib/sandbox/mounts.ts
CHANGED
|
@@ -19,9 +19,16 @@ export function sandboxCoreBindMounts(
|
|
|
19
19
|
'repoRoot' | 'worktreeBase' | 'shareBase' | 'shellConfigBase'
|
|
20
20
|
>,
|
|
21
21
|
branch: string,
|
|
22
|
-
overrides: {
|
|
22
|
+
overrides: {
|
|
23
|
+
worktree?: string;
|
|
24
|
+
shellConfigHostDir?: string;
|
|
25
|
+
workspaceViewRoot: string;
|
|
26
|
+
controlDir: string;
|
|
27
|
+
taskSource?: string;
|
|
28
|
+
taskId?: string;
|
|
29
|
+
}
|
|
23
30
|
): SandboxBindMountDeclaration[] {
|
|
24
|
-
|
|
31
|
+
const mounts: SandboxBindMountDeclaration[] = [
|
|
25
32
|
{
|
|
26
33
|
hostPaths: overrides.worktree
|
|
27
34
|
? [overrides.worktree]
|
|
@@ -30,9 +37,9 @@ export function sandboxCoreBindMounts(
|
|
|
30
37
|
readOnly: false
|
|
31
38
|
},
|
|
32
39
|
{
|
|
33
|
-
hostPaths: [
|
|
40
|
+
hostPaths: [overrides.workspaceViewRoot],
|
|
34
41
|
containerPath: '/workspace/.agents/workspace',
|
|
35
|
-
readOnly:
|
|
42
|
+
readOnly: true
|
|
36
43
|
},
|
|
37
44
|
{
|
|
38
45
|
hostPaths: [shareCommonDir(config)],
|
|
@@ -52,4 +59,17 @@ export function sandboxCoreBindMounts(
|
|
|
52
59
|
readOnly: true
|
|
53
60
|
}
|
|
54
61
|
];
|
|
62
|
+
if (overrides.taskSource && overrides.taskId) {
|
|
63
|
+
mounts.splice(2, 0, {
|
|
64
|
+
hostPaths: [overrides.taskSource],
|
|
65
|
+
containerPath: path.posix.join('/workspace/.agents/workspace/active', overrides.taskId),
|
|
66
|
+
readOnly: false
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
mounts.push({
|
|
70
|
+
hostPaths: [overrides.controlDir],
|
|
71
|
+
containerPath: '/run/agent-infra/control',
|
|
72
|
+
readOnly: false
|
|
73
|
+
});
|
|
74
|
+
return mounts;
|
|
55
75
|
}
|
package/lib/sandbox/recovery.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import {
|
|
4
6
|
containerNameCandidates,
|
|
5
7
|
sandboxBranchLabel,
|
|
6
8
|
sandboxLabel,
|
|
7
|
-
sandboxRuntimeCapabilityLabel
|
|
9
|
+
sandboxRuntimeCapabilityLabel,
|
|
10
|
+
sandboxTaskIdLabel,
|
|
11
|
+
sandboxWorkspaceModeLabel
|
|
8
12
|
} from './constants.ts';
|
|
9
13
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
10
14
|
import {
|
|
@@ -17,6 +21,16 @@ import type {
|
|
|
17
21
|
import type { SandboxConfig } from './config.ts';
|
|
18
22
|
import { toEnginePath } from './engines/wsl2-paths.ts';
|
|
19
23
|
import { sandboxCoreBindMounts } from './mounts.ts';
|
|
24
|
+
import {
|
|
25
|
+
assertSandboxTaskSource,
|
|
26
|
+
sandboxControlPaths,
|
|
27
|
+
sandboxWorkspaceViewPaths
|
|
28
|
+
} from './workspace-view.ts';
|
|
29
|
+
import {
|
|
30
|
+
parseSandboxWorkspaceIdentity,
|
|
31
|
+
sameSandboxWorkspaceIdentity,
|
|
32
|
+
type SandboxWorkspaceIdentity
|
|
33
|
+
} from './workspace-identity.ts';
|
|
20
34
|
import { runEngine, runOkEngine, runVerboseEngine } from './shell.ts';
|
|
21
35
|
import {
|
|
22
36
|
declaredTmpfsSeedEntries,
|
|
@@ -31,6 +45,7 @@ import {
|
|
|
31
45
|
startSandboxContainer,
|
|
32
46
|
type SandboxRow
|
|
33
47
|
} from './commands/list-running.ts';
|
|
48
|
+
import { processIdentityMatches } from '../server/process-state.ts';
|
|
34
49
|
|
|
35
50
|
export type SandboxRecoveryFinding = {
|
|
36
51
|
repairKind: 'permissions' | 'missing-seed' | 'builtin-link' | 'hard-failure';
|
|
@@ -109,6 +124,7 @@ type EnsureSandboxReadyParams = {
|
|
|
109
124
|
config: SandboxConfig;
|
|
110
125
|
engine: string;
|
|
111
126
|
branch: string;
|
|
127
|
+
workspace?: SandboxWorkspaceIdentity;
|
|
112
128
|
row: SandboxRow;
|
|
113
129
|
allowRecreate?: boolean;
|
|
114
130
|
recreate?: (branch: string) => Promise<void>;
|
|
@@ -123,6 +139,54 @@ type ExpectedMount = {
|
|
|
123
139
|
expectedRW: boolean;
|
|
124
140
|
};
|
|
125
141
|
|
|
142
|
+
function ensureSandboxControlBroker(params: {
|
|
143
|
+
config: SandboxConfig;
|
|
144
|
+
container: string;
|
|
145
|
+
workspace: SandboxWorkspaceIdentity;
|
|
146
|
+
}): void {
|
|
147
|
+
const control = sandboxControlPaths({
|
|
148
|
+
base: params.config.controlBase ?? path.join(params.config.home, '.agent-infra', 'sandbox-control'),
|
|
149
|
+
project: params.config.project,
|
|
150
|
+
container: params.container,
|
|
151
|
+
identity: params.workspace
|
|
152
|
+
});
|
|
153
|
+
if (!fs.existsSync(control.manifestPath)) return;
|
|
154
|
+
const manifest = JSON.parse(fs.readFileSync(control.manifestPath, 'utf8')) as { token?: unknown };
|
|
155
|
+
const brokerPath = path.join(control.root, 'broker.json');
|
|
156
|
+
try {
|
|
157
|
+
const broker = JSON.parse(fs.readFileSync(brokerPath, 'utf8')) as {
|
|
158
|
+
version?: unknown;
|
|
159
|
+
pid?: unknown;
|
|
160
|
+
startTime?: unknown;
|
|
161
|
+
token?: unknown;
|
|
162
|
+
};
|
|
163
|
+
if (
|
|
164
|
+
broker.version === 1
|
|
165
|
+
&& typeof broker.pid === 'number'
|
|
166
|
+
&& typeof broker.startTime === 'string'
|
|
167
|
+
&& broker.token === manifest.token
|
|
168
|
+
&& processIdentityMatches({ version: 1, pid: broker.pid, startTime: broker.startTime })
|
|
169
|
+
) return;
|
|
170
|
+
} catch {
|
|
171
|
+
// A missing, stale, or malformed broker record is replaced below.
|
|
172
|
+
}
|
|
173
|
+
fs.rmSync(brokerPath, { force: true });
|
|
174
|
+
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
175
|
+
const extension = path.extname(fileURLToPath(import.meta.url));
|
|
176
|
+
const internalCli = path.resolve(directory, '..', '..', 'bin', `internal-cli${extension}`);
|
|
177
|
+
const child = spawn(
|
|
178
|
+
process.execPath,
|
|
179
|
+
[internalCli, 'sandbox-control', 'serve', '--manifest', control.manifestPath],
|
|
180
|
+
{ cwd: params.config.repoRoot, detached: true, stdio: 'ignore' }
|
|
181
|
+
);
|
|
182
|
+
child.once('error', () => {
|
|
183
|
+
// Readiness remains fail-closed; a later control request reports the
|
|
184
|
+
// unavailable broker without turning this detached spawn into an
|
|
185
|
+
// unhandled process error.
|
|
186
|
+
});
|
|
187
|
+
child.unref();
|
|
188
|
+
}
|
|
189
|
+
|
|
126
190
|
function findingKey(finding: SandboxRecoveryFinding): string {
|
|
127
191
|
return `${finding.repairKind}:${finding.path ?? finding.seed?.targetPath ?? finding.message}`;
|
|
128
192
|
}
|
|
@@ -297,11 +361,34 @@ function hostSourceAccessible(hostPath: string, writable: boolean): boolean {
|
|
|
297
361
|
function expectedMounts(params: {
|
|
298
362
|
config: SandboxConfig;
|
|
299
363
|
branch: string;
|
|
364
|
+
container: string;
|
|
365
|
+
workspace: SandboxWorkspaceIdentity;
|
|
300
366
|
tools: readonly SandboxTool[];
|
|
301
367
|
actualMounts: Map<string, DockerMount>;
|
|
302
368
|
}): ExpectedMount[] {
|
|
303
369
|
const { config, branch, tools, actualMounts } = params;
|
|
304
|
-
const
|
|
370
|
+
const view = sandboxWorkspaceViewPaths({
|
|
371
|
+
base: config.workspaceViewBase ?? path.join(config.home, '.agent-infra', 'workspace-views'),
|
|
372
|
+
project: config.project,
|
|
373
|
+
container: params.container,
|
|
374
|
+
identity: params.workspace
|
|
375
|
+
});
|
|
376
|
+
const control = sandboxControlPaths({
|
|
377
|
+
base: config.controlBase ?? path.join(config.home, '.agent-infra', 'sandbox-control'),
|
|
378
|
+
project: config.project,
|
|
379
|
+
container: params.container,
|
|
380
|
+
identity: params.workspace
|
|
381
|
+
});
|
|
382
|
+
const core = sandboxCoreBindMounts(config, branch, {
|
|
383
|
+
workspaceViewRoot: view.root,
|
|
384
|
+
controlDir: control.channelDir,
|
|
385
|
+
...(params.workspace.mode === 'task-bound'
|
|
386
|
+
? {
|
|
387
|
+
taskSource: assertSandboxTaskSource(config.repoRoot, params.workspace.taskId),
|
|
388
|
+
taskId: params.workspace.taskId
|
|
389
|
+
}
|
|
390
|
+
: {})
|
|
391
|
+
}).map((mount) => ({
|
|
305
392
|
path: mount.containerPath,
|
|
306
393
|
expectedType: 'bind' as const,
|
|
307
394
|
hostPaths: mount.hostPaths,
|
|
@@ -399,6 +486,7 @@ export function collectSandboxRecoverySnapshot(params: {
|
|
|
399
486
|
config: SandboxConfig;
|
|
400
487
|
engine: string;
|
|
401
488
|
branch: string;
|
|
489
|
+
workspace?: SandboxWorkspaceIdentity;
|
|
402
490
|
container: string;
|
|
403
491
|
deps?: RecoveryCommandDeps;
|
|
404
492
|
}): SandboxRecoverySnapshot {
|
|
@@ -427,6 +515,7 @@ export function collectSandboxRecoverySnapshot(params: {
|
|
|
427
515
|
}
|
|
428
516
|
}
|
|
429
517
|
const capabilityPlan = createSandboxCapabilityPlan(params.config);
|
|
518
|
+
const workspace = params.workspace ?? { mode: 'branch-only' as const };
|
|
430
519
|
const tools = [...capabilityPlan.tools];
|
|
431
520
|
const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
|
|
432
521
|
const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
|
|
@@ -460,6 +549,10 @@ export function collectSandboxRecoverySnapshot(params: {
|
|
|
460
549
|
}));
|
|
461
550
|
const labels = inspection.Config?.Labels ?? {};
|
|
462
551
|
const branchLabel = labels[sandboxBranchLabel(params.config)];
|
|
552
|
+
const containerWorkspace = parseSandboxWorkspaceIdentity(labels, {
|
|
553
|
+
mode: sandboxWorkspaceModeLabel(params.config),
|
|
554
|
+
taskId: sandboxTaskIdLabel(params.config)
|
|
555
|
+
});
|
|
463
556
|
const selectedToolIds = new Set(tools.map((tool) => tool.id));
|
|
464
557
|
const enforceCanonicalPlan = params.config.agentClientSource === 'canonical';
|
|
465
558
|
const unexpectedCapabilityMounts = enforceCanonicalPlan
|
|
@@ -471,6 +564,8 @@ export function collectSandboxRecoverySnapshot(params: {
|
|
|
471
564
|
const mountSnapshots = expectedMounts({
|
|
472
565
|
config: params.config,
|
|
473
566
|
branch: params.branch,
|
|
567
|
+
container: params.container,
|
|
568
|
+
workspace,
|
|
474
569
|
tools,
|
|
475
570
|
actualMounts: mountsByDestination
|
|
476
571
|
}).map((expected) => {
|
|
@@ -510,7 +605,8 @@ export function collectSandboxRecoverySnapshot(params: {
|
|
|
510
605
|
return {
|
|
511
606
|
identityOk: typeof inspection.Id === 'string'
|
|
512
607
|
&& inspection.Id.length > 0
|
|
513
|
-
&& branchLabel === params.branch
|
|
608
|
+
&& branchLabel === params.branch
|
|
609
|
+
&& sameSandboxWorkspaceIdentity(containerWorkspace, workspace),
|
|
514
610
|
runtimeCapabilityOk: enforceCanonicalPlan
|
|
515
611
|
? labels[sandboxRuntimeCapabilityLabel(params.config)] === capabilityPlan.runtimeSignature
|
|
516
612
|
: undefined,
|
|
@@ -673,6 +769,7 @@ function assess(params: {
|
|
|
673
769
|
config: SandboxConfig;
|
|
674
770
|
engine: string;
|
|
675
771
|
branch: string;
|
|
772
|
+
workspace?: SandboxWorkspaceIdentity;
|
|
676
773
|
container: string;
|
|
677
774
|
deps?: RecoveryCommandDeps;
|
|
678
775
|
}): { snapshot: SandboxRecoverySnapshot; findings: SandboxRecoveryFinding[] } {
|
|
@@ -680,21 +777,64 @@ function assess(params: {
|
|
|
680
777
|
return { snapshot, findings: classifySandboxRecovery(snapshot) };
|
|
681
778
|
}
|
|
682
779
|
|
|
683
|
-
|
|
780
|
+
function canRetryFreshReadiness(findings: SandboxRecoveryFinding[]): boolean {
|
|
781
|
+
return findings.length > 0 && findings.every((finding) =>
|
|
782
|
+
finding.repairKind === 'hard-failure'
|
|
783
|
+
&& finding.path !== undefined
|
|
784
|
+
&& (
|
|
785
|
+
finding.path.startsWith('/workspace/.agents/workspace')
|
|
786
|
+
|| finding.path === '/home/devuser/.host-shell-config'
|
|
787
|
+
|| finding.path === '/home/devuser/.bash_aliases'
|
|
788
|
+
)
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
export async function assertFreshSandboxReady(params: {
|
|
684
793
|
config: SandboxConfig;
|
|
685
794
|
engine: string;
|
|
686
795
|
branch: string;
|
|
796
|
+
workspace?: SandboxWorkspaceIdentity;
|
|
687
797
|
container: string;
|
|
688
798
|
copiedEntries: TmpfsSeedEntry[];
|
|
689
799
|
deps?: RecoveryCommandDeps;
|
|
690
|
-
}): void {
|
|
800
|
+
}): Promise<void> {
|
|
691
801
|
validateTmpfsSeedEntries({
|
|
692
802
|
engine: params.engine,
|
|
693
803
|
container: params.container,
|
|
694
804
|
entries: params.copiedEntries,
|
|
695
805
|
deps: params.deps
|
|
696
806
|
});
|
|
697
|
-
|
|
807
|
+
let { findings } = assess(params);
|
|
808
|
+
if (canRetryFreshReadiness(findings)) {
|
|
809
|
+
const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
|
|
810
|
+
runVerboseFn(params.engine, 'docker', ['restart', params.container]);
|
|
811
|
+
prepareTmpfsMounts({
|
|
812
|
+
engine: params.engine,
|
|
813
|
+
container: params.container,
|
|
814
|
+
mountPaths: resolveTools(params.config)
|
|
815
|
+
.filter((tool) => tool.tmpfs)
|
|
816
|
+
.map((tool) => tool.containerMount),
|
|
817
|
+
deps: params.deps
|
|
818
|
+
});
|
|
819
|
+
hydrateTmpfsSeedEntries({
|
|
820
|
+
engine: params.engine,
|
|
821
|
+
container: params.container,
|
|
822
|
+
entries: params.copiedEntries,
|
|
823
|
+
replace: true,
|
|
824
|
+
deps: params.deps
|
|
825
|
+
});
|
|
826
|
+
let retried = assess(params);
|
|
827
|
+
if (!retried.findings.some((finding) => finding.repairKind === 'hard-failure')) {
|
|
828
|
+
repairFindings({
|
|
829
|
+
engine: params.engine,
|
|
830
|
+
container: params.container,
|
|
831
|
+
findings: retried.findings,
|
|
832
|
+
deps: params.deps
|
|
833
|
+
});
|
|
834
|
+
retried = assess(params);
|
|
835
|
+
}
|
|
836
|
+
findings = retried.findings;
|
|
837
|
+
}
|
|
698
838
|
if (findings.length > 0) {
|
|
699
839
|
throw new Error(`Fresh sandbox readiness check failed: ${describeFindings(findings)}`);
|
|
700
840
|
}
|
|
@@ -705,6 +845,11 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
705
845
|
const startFn = deps?.start ?? startSandboxContainer;
|
|
706
846
|
const warnings: string[] = [];
|
|
707
847
|
let failure: Error | null = null;
|
|
848
|
+
ensureSandboxControlBroker({
|
|
849
|
+
config: params.config,
|
|
850
|
+
container: params.row.name,
|
|
851
|
+
workspace: params.workspace ?? { mode: 'branch-only' }
|
|
852
|
+
});
|
|
708
853
|
|
|
709
854
|
try {
|
|
710
855
|
if (!params.row.running) {
|
|
@@ -713,6 +858,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
713
858
|
config: params.config,
|
|
714
859
|
engine: params.engine,
|
|
715
860
|
branch: params.branch,
|
|
861
|
+
workspace: params.workspace,
|
|
716
862
|
container: params.row.name,
|
|
717
863
|
deps
|
|
718
864
|
});
|
|
@@ -739,6 +885,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
739
885
|
config: params.config,
|
|
740
886
|
engine: params.engine,
|
|
741
887
|
branch: params.branch,
|
|
888
|
+
workspace: params.workspace,
|
|
742
889
|
container: params.row.name,
|
|
743
890
|
deps
|
|
744
891
|
});
|
|
@@ -753,6 +900,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
753
900
|
config: params.config,
|
|
754
901
|
engine: params.engine,
|
|
755
902
|
branch: params.branch,
|
|
903
|
+
workspace: params.workspace,
|
|
756
904
|
container: params.row.name,
|
|
757
905
|
deps
|
|
758
906
|
});
|
|
@@ -767,6 +915,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
767
915
|
config: params.config,
|
|
768
916
|
engine: params.engine,
|
|
769
917
|
branch: params.branch,
|
|
918
|
+
workspace: params.workspace,
|
|
770
919
|
container: params.row.name,
|
|
771
920
|
deps
|
|
772
921
|
});
|
|
@@ -791,6 +940,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
791
940
|
config: params.config,
|
|
792
941
|
engine: params.engine,
|
|
793
942
|
branch: params.branch,
|
|
943
|
+
workspace: params.workspace,
|
|
794
944
|
container: params.row.name,
|
|
795
945
|
deps
|
|
796
946
|
});
|
|
@@ -811,6 +961,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
811
961
|
config: params.config,
|
|
812
962
|
engine: params.engine,
|
|
813
963
|
branch: params.branch,
|
|
964
|
+
workspace: params.workspace,
|
|
814
965
|
container: params.row.name,
|
|
815
966
|
deps
|
|
816
967
|
});
|
|
@@ -831,6 +982,11 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
831
982
|
const warning = `Sandbox recovery failed in place. Replacing only the container. ${dataBoundary}`;
|
|
832
983
|
warnings.push(warning);
|
|
833
984
|
(params.writeWarning ?? ((message) => process.stderr.write(`${message}\n`)))(warning);
|
|
985
|
+
const runVerboseFn = deps?.runVerbose ?? runVerboseEngine;
|
|
986
|
+
if (params.row.running) {
|
|
987
|
+
runVerboseFn(params.engine, 'docker', ['stop', params.row.name]);
|
|
988
|
+
}
|
|
989
|
+
runVerboseFn(params.engine, 'docker', ['rm', params.row.name]);
|
|
834
990
|
await params.recreate(params.branch);
|
|
835
991
|
|
|
836
992
|
const rows = (deps?.fetchRows ?? fetchSandboxRows)(
|
|
@@ -849,6 +1005,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
849
1005
|
config: params.config,
|
|
850
1006
|
engine: params.engine,
|
|
851
1007
|
branch: params.branch,
|
|
1008
|
+
workspace: params.workspace,
|
|
852
1009
|
container: replacement.name,
|
|
853
1010
|
deps
|
|
854
1011
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const SENSITIVE_OUTPUT_PATTERNS = [
|
|
2
|
+
{ pattern: /\{[^{}]*"claudeAiOauth"[\s\S]*?\}\s*\}/g, replacement: '[REDACTED credentials blob]' },
|
|
3
|
+
{ pattern: /sk-ant-[A-Za-z0-9_-]{20,}/g, replacement: '[REDACTED claude token]' },
|
|
4
|
+
{ pattern: /gh[psoru]_[A-Za-z0-9]{30,}/g, replacement: '[REDACTED github token]' },
|
|
5
|
+
{ pattern: /Bearer\s+[A-Za-z0-9._~+/=-]{20,}/gi, replacement: 'Bearer [REDACTED]' }
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
export function redactCommandError(text: unknown): string {
|
|
9
|
+
if (!text || typeof text !== 'string') return '';
|
|
10
|
+
return SENSITIVE_OUTPUT_PATTERNS.reduce(
|
|
11
|
+
(result, { pattern, replacement }) => result.replace(pattern, replacement),
|
|
12
|
+
text
|
|
13
|
+
);
|
|
14
|
+
}
|