@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
|
@@ -0,0 +1,144 @@
|
|
|
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.js';
|
|
5
|
+
import { createTask } from '../../task/create-service.js';
|
|
6
|
+
import { assertGitWorktreeBinding } from '../../git/worktree-identity.js';
|
|
7
|
+
import { bindSandboxControlTask, validateSandboxControlRequest } from './protocol.js';
|
|
8
|
+
function readManifest(manifestPath) {
|
|
9
|
+
const stat = fs.lstatSync(manifestPath);
|
|
10
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
11
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
12
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
13
|
+
if (manifest.version !== 2) {
|
|
14
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_VERSION_INVALID: expected version 2; recreate the sandbox');
|
|
15
|
+
}
|
|
16
|
+
if (typeof manifest.repoRoot !== 'string'
|
|
17
|
+
|| typeof manifest.worktreeRoot !== 'string'
|
|
18
|
+
|| typeof manifest.channelDir !== 'string'
|
|
19
|
+
|| typeof manifest.token !== 'string')
|
|
20
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
21
|
+
if (path.resolve(manifest.channelDir) !== path.join(path.dirname(path.resolve(manifestPath)), 'channel')) {
|
|
22
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
23
|
+
}
|
|
24
|
+
return manifest;
|
|
25
|
+
}
|
|
26
|
+
function assertRealDirectory(directory, parent) {
|
|
27
|
+
const stat = fs.lstatSync(directory);
|
|
28
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
29
|
+
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
30
|
+
if (parent && path.dirname(fs.realpathSync.native(directory)) !== fs.realpathSync.native(parent)) {
|
|
31
|
+
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function writeResponse(directory, response) {
|
|
35
|
+
const temporary = path.join(directory, `.${response.id}.tmp`);
|
|
36
|
+
fs.writeFileSync(temporary, `${JSON.stringify(response)}\n`, { mode: 0o600, flag: 'wx' });
|
|
37
|
+
fs.renameSync(temporary, path.join(directory, `${response.id}.json`));
|
|
38
|
+
}
|
|
39
|
+
export function sandboxControlSafeEnv(env = process.env) {
|
|
40
|
+
return Object.fromEntries(Object.entries(env).filter(([key]) => !key.toUpperCase().startsWith('AGENT_INFRA_CONTROL_')));
|
|
41
|
+
}
|
|
42
|
+
function consumeRequest(directory, id) {
|
|
43
|
+
try {
|
|
44
|
+
fs.writeFileSync(path.join(directory, id), '', { flag: 'wx', mode: 0o600 });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error.code === 'EEXIST') {
|
|
48
|
+
throw new Error('SANDBOX_CONTROL_REQUEST_REPLAYED');
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function serveSandboxControl(manifestPath, signal = new AbortController().signal) {
|
|
54
|
+
const manifest = readManifest(manifestPath);
|
|
55
|
+
assertGitWorktreeBinding(manifest.repoRoot, manifest.worktreeRoot, manifest.branch);
|
|
56
|
+
const brokerPath = path.join(path.dirname(manifestPath), 'broker.json');
|
|
57
|
+
const startTime = getProcessStartTime(process.pid);
|
|
58
|
+
if (!startTime)
|
|
59
|
+
throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
60
|
+
fs.writeFileSync(brokerPath, `${JSON.stringify({
|
|
61
|
+
version: 1,
|
|
62
|
+
pid: process.pid,
|
|
63
|
+
startTime,
|
|
64
|
+
token: manifest.token
|
|
65
|
+
})}\n`, { mode: 0o600, flag: 'wx' });
|
|
66
|
+
const requestsDir = path.join(manifest.channelDir, 'requests');
|
|
67
|
+
const responsesDir = path.join(manifest.channelDir, 'responses');
|
|
68
|
+
const consumedDir = path.join(path.dirname(manifestPath), 'consumed');
|
|
69
|
+
assertRealDirectory(manifest.channelDir);
|
|
70
|
+
fs.mkdirSync(requestsDir, { recursive: true, mode: 0o700 });
|
|
71
|
+
fs.mkdirSync(responsesDir, { recursive: true, mode: 0o700 });
|
|
72
|
+
fs.mkdirSync(consumedDir, { recursive: true, mode: 0o700 });
|
|
73
|
+
assertRealDirectory(requestsDir, manifest.channelDir);
|
|
74
|
+
assertRealDirectory(responsesDir, manifest.channelDir);
|
|
75
|
+
assertRealDirectory(consumedDir, path.dirname(manifestPath));
|
|
76
|
+
while (!signal.aborted) {
|
|
77
|
+
const current = readManifest(manifestPath);
|
|
78
|
+
if (current.token !== manifest.token)
|
|
79
|
+
return;
|
|
80
|
+
assertGitWorktreeBinding(current.repoRoot, current.worktreeRoot, current.branch);
|
|
81
|
+
assertRealDirectory(requestsDir, manifest.channelDir);
|
|
82
|
+
assertRealDirectory(responsesDir, manifest.channelDir);
|
|
83
|
+
for (const name of fs.readdirSync(requestsDir).sort()) {
|
|
84
|
+
if (!/^[a-f0-9-]{16,64}\.json$/.test(name))
|
|
85
|
+
continue;
|
|
86
|
+
const requestPath = path.join(requestsDir, name);
|
|
87
|
+
let id = name.slice(0, -5);
|
|
88
|
+
let response;
|
|
89
|
+
try {
|
|
90
|
+
const stat = fs.lstatSync(requestPath);
|
|
91
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
92
|
+
throw new Error('SANDBOX_CONTROL_REQUEST_INVALID');
|
|
93
|
+
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(requestPath, 'utf8')), manifest);
|
|
94
|
+
id = request.id;
|
|
95
|
+
consumeRequest(consumedDir, id);
|
|
96
|
+
if (request.family === 'task-create') {
|
|
97
|
+
const result = createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
98
|
+
response = {
|
|
99
|
+
version: 1, id,
|
|
100
|
+
exitCode: result.status === 'blocked' ? 2 : result.status === 'failed' ? 1 : 0,
|
|
101
|
+
stdout: `${JSON.stringify(result)}\n`, stderr: ''
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
const boundArgs = bindSandboxControlTask(request, manifest.taskId);
|
|
106
|
+
if (request.family === 'task-orchestration') {
|
|
107
|
+
boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
|
|
108
|
+
}
|
|
109
|
+
const result = spawnSync(process.execPath, ['--experimental-strip-types', '--no-warnings', process.argv[1], request.family, ...boundArgs], {
|
|
110
|
+
cwd: manifest.repoRoot,
|
|
111
|
+
encoding: 'utf8',
|
|
112
|
+
env: sandboxControlSafeEnv()
|
|
113
|
+
});
|
|
114
|
+
response = {
|
|
115
|
+
version: 1,
|
|
116
|
+
id,
|
|
117
|
+
exitCode: result.status ?? 1,
|
|
118
|
+
stdout: result.stdout ?? '',
|
|
119
|
+
stderr: result.stderr ?? ''
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
response = {
|
|
125
|
+
version: 1,
|
|
126
|
+
id,
|
|
127
|
+
exitCode: 1,
|
|
128
|
+
stdout: '',
|
|
129
|
+
stderr: `${error instanceof Error ? error.message : String(error)}\n`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
fs.unlinkSync(requestPath);
|
|
134
|
+
writeResponse(responsesDir, response);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error.code !== 'ENOENT')
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -5,6 +5,11 @@ import { createHash } from 'node:crypto';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { removeDirRecursive } from '../remove-dir.js';
|
|
7
7
|
const RUNTIMES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), 'runtimes');
|
|
8
|
+
const AGENT_CLIENT_RUNTIMES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'agent-clients', 'adapters', 'runtimes');
|
|
9
|
+
const EMPTY_IMAGE_CONTRIBUTION = Object.freeze({
|
|
10
|
+
dockerfileFragments: Object.freeze([]),
|
|
11
|
+
dotfilesExclusions: Object.freeze([])
|
|
12
|
+
});
|
|
8
13
|
function listRuntimeFragments() {
|
|
9
14
|
return fs.readdirSync(RUNTIMES_DIR)
|
|
10
15
|
.filter((file) => file.endsWith('.dockerfile'))
|
|
@@ -15,7 +20,24 @@ export function availableRuntimes() {
|
|
|
15
20
|
.filter((name) => name !== 'base' && name !== 'ai-tools')
|
|
16
21
|
.sort();
|
|
17
22
|
}
|
|
18
|
-
function
|
|
23
|
+
function exclusionsFragment(exclusions) {
|
|
24
|
+
return [
|
|
25
|
+
"RUN mkdir -p /etc/agent-infra && cat > /etc/agent-infra/dotfiles-exclusions <<'EXCLUSIONS'",
|
|
26
|
+
...exclusions,
|
|
27
|
+
'EXCLUSIONS'
|
|
28
|
+
].join('\n');
|
|
29
|
+
}
|
|
30
|
+
function adapterFragmentPath(fragment) {
|
|
31
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(fragment)) {
|
|
32
|
+
throw new Error(`Invalid Agent Client Dockerfile fragment: ${fragment}`);
|
|
33
|
+
}
|
|
34
|
+
const fragmentPath = path.join(AGENT_CLIENT_RUNTIMES_DIR, `${fragment}.dockerfile`);
|
|
35
|
+
if (!fs.existsSync(fragmentPath)) {
|
|
36
|
+
throw new Error(`Unknown Agent Client Dockerfile fragment: ${fragment}`);
|
|
37
|
+
}
|
|
38
|
+
return fragmentPath;
|
|
39
|
+
}
|
|
40
|
+
function dockerfileContent(config, image = EMPTY_IMAGE_CONTRIBUTION) {
|
|
19
41
|
if (config.dockerfile) {
|
|
20
42
|
const customPath = path.resolve(config.repoRoot, config.dockerfile);
|
|
21
43
|
if (!fs.existsSync(customPath)) {
|
|
@@ -30,22 +52,23 @@ function dockerfileContent(config) {
|
|
|
30
52
|
}
|
|
31
53
|
}
|
|
32
54
|
const fragments = [
|
|
33
|
-
'base.dockerfile',
|
|
34
|
-
|
|
35
|
-
'
|
|
55
|
+
fs.readFileSync(path.join(RUNTIMES_DIR, 'base.dockerfile'), 'utf8').trimEnd(),
|
|
56
|
+
exclusionsFragment(image.dotfilesExclusions),
|
|
57
|
+
...image.dockerfileFragments.map((fragment) => fs.readFileSync(adapterFragmentPath(fragment), 'utf8').trimEnd()),
|
|
58
|
+
...config.runtimes.map((runtime) => fs.readFileSync(path.join(RUNTIMES_DIR, `${runtime}.dockerfile`), 'utf8').trimEnd()),
|
|
59
|
+
fs.readFileSync(path.join(RUNTIMES_DIR, 'ai-tools.dockerfile'), 'utf8').trimEnd()
|
|
36
60
|
];
|
|
37
61
|
const content = fragments
|
|
38
|
-
.map((fragment) => fs.readFileSync(path.join(RUNTIMES_DIR, fragment), 'utf8').trimEnd())
|
|
39
62
|
.join('\n\n');
|
|
40
63
|
return `${content}\n`;
|
|
41
64
|
}
|
|
42
|
-
export function dockerfileSignature(config) {
|
|
65
|
+
export function dockerfileSignature(config, image = EMPTY_IMAGE_CONTRIBUTION) {
|
|
43
66
|
return createHash('sha256')
|
|
44
|
-
.update(dockerfileContent(config))
|
|
67
|
+
.update(dockerfileContent(config, image))
|
|
45
68
|
.digest('hex')
|
|
46
69
|
.slice(0, 12);
|
|
47
70
|
}
|
|
48
|
-
export function prepareDockerfile(config) {
|
|
71
|
+
export function prepareDockerfile(config, image = EMPTY_IMAGE_CONTRIBUTION) {
|
|
49
72
|
if (config.dockerfile) {
|
|
50
73
|
const customPath = path.resolve(config.repoRoot, config.dockerfile);
|
|
51
74
|
if (!fs.existsSync(customPath)) {
|
|
@@ -53,23 +76,23 @@ export function prepareDockerfile(config) {
|
|
|
53
76
|
}
|
|
54
77
|
return {
|
|
55
78
|
path: customPath,
|
|
56
|
-
signature: dockerfileSignature(config),
|
|
79
|
+
signature: dockerfileSignature(config, image),
|
|
57
80
|
cleanup() { }
|
|
58
81
|
};
|
|
59
82
|
}
|
|
60
83
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `${config.project}-sandbox-`));
|
|
61
84
|
const tempPath = path.join(tempDir, 'Dockerfile');
|
|
62
|
-
fs.writeFileSync(tempPath, dockerfileContent(config), 'utf8');
|
|
85
|
+
fs.writeFileSync(tempPath, dockerfileContent(config, image), 'utf8');
|
|
63
86
|
return {
|
|
64
87
|
path: tempPath,
|
|
65
|
-
signature: dockerfileSignature(config),
|
|
88
|
+
signature: dockerfileSignature(config, image),
|
|
66
89
|
cleanup() {
|
|
67
90
|
removeDirRecursive(tempDir);
|
|
68
91
|
}
|
|
69
92
|
};
|
|
70
93
|
}
|
|
71
|
-
export function composeDockerfile(config) {
|
|
72
|
-
const content = dockerfileContent(config);
|
|
94
|
+
export function composeDockerfile(config, image = EMPTY_IMAGE_CONTRIBUTION) {
|
|
95
|
+
const content = dockerfileContent(config, image);
|
|
73
96
|
const tempPath = path.join(os.tmpdir(), `${config.project}-sandbox.Dockerfile`);
|
|
74
97
|
fs.writeFileSync(tempPath, content, 'utf8');
|
|
75
98
|
return tempPath;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { shareBranchDir, shareCommonDir, shellConfigDirCandidates, worktreeDirCandidates } from './constants.js';
|
|
3
|
-
export function sandboxCoreBindMounts(config, branch, overrides
|
|
4
|
-
|
|
3
|
+
export function sandboxCoreBindMounts(config, branch, overrides) {
|
|
4
|
+
const mounts = [
|
|
5
5
|
{
|
|
6
6
|
hostPaths: overrides.worktree
|
|
7
7
|
? [overrides.worktree]
|
|
@@ -10,9 +10,9 @@ export function sandboxCoreBindMounts(config, branch, overrides = {}) {
|
|
|
10
10
|
readOnly: false
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
|
-
hostPaths: [
|
|
13
|
+
hostPaths: [overrides.workspaceViewRoot],
|
|
14
14
|
containerPath: '/workspace/.agents/workspace',
|
|
15
|
-
readOnly:
|
|
15
|
+
readOnly: true
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
hostPaths: [shareCommonDir(config)],
|
|
@@ -32,5 +32,18 @@ export function sandboxCoreBindMounts(config, branch, overrides = {}) {
|
|
|
32
32
|
readOnly: true
|
|
33
33
|
}
|
|
34
34
|
];
|
|
35
|
+
if (overrides.taskSource && overrides.taskId) {
|
|
36
|
+
mounts.splice(2, 0, {
|
|
37
|
+
hostPaths: [overrides.taskSource],
|
|
38
|
+
containerPath: path.posix.join('/workspace/.agents/workspace/active', overrides.taskId),
|
|
39
|
+
readOnly: false
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
mounts.push({
|
|
43
|
+
hostPaths: [overrides.controlDir],
|
|
44
|
+
containerPath: '/run/agent-infra/control',
|
|
45
|
+
readOnly: false
|
|
46
|
+
});
|
|
47
|
+
return mounts;
|
|
35
48
|
}
|
|
36
49
|
//# sourceMappingURL=mounts.js.map
|
|
@@ -1,13 +1,53 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxRuntimeCapabilityLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel } from './constants.js';
|
|
4
6
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
5
7
|
import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
|
|
6
8
|
import { toEnginePath } from './engines/wsl2-paths.js';
|
|
7
9
|
import { sandboxCoreBindMounts } from './mounts.js';
|
|
10
|
+
import { assertSandboxTaskSource, sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
|
|
11
|
+
import { parseSandboxWorkspaceIdentity, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
|
|
8
12
|
import { runEngine, runOkEngine, runVerboseEngine } from './shell.js';
|
|
9
13
|
import { declaredTmpfsSeedEntries, resolveTools, toolConfigDirCandidates } from './tools.js';
|
|
10
14
|
import { fetchSandboxRows, selectSandboxContainer, startSandboxContainer } from './commands/list-running.js';
|
|
15
|
+
import { processIdentityMatches } from '../server/process-state.js';
|
|
16
|
+
function ensureSandboxControlBroker(params) {
|
|
17
|
+
const control = sandboxControlPaths({
|
|
18
|
+
base: params.config.controlBase ?? path.join(params.config.home, '.agent-infra', 'sandbox-control'),
|
|
19
|
+
project: params.config.project,
|
|
20
|
+
container: params.container,
|
|
21
|
+
identity: params.workspace
|
|
22
|
+
});
|
|
23
|
+
if (!fs.existsSync(control.manifestPath))
|
|
24
|
+
return;
|
|
25
|
+
const manifest = JSON.parse(fs.readFileSync(control.manifestPath, 'utf8'));
|
|
26
|
+
const brokerPath = path.join(control.root, 'broker.json');
|
|
27
|
+
try {
|
|
28
|
+
const broker = JSON.parse(fs.readFileSync(brokerPath, 'utf8'));
|
|
29
|
+
if (broker.version === 1
|
|
30
|
+
&& typeof broker.pid === 'number'
|
|
31
|
+
&& typeof broker.startTime === 'string'
|
|
32
|
+
&& broker.token === manifest.token
|
|
33
|
+
&& processIdentityMatches({ version: 1, pid: broker.pid, startTime: broker.startTime }))
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// A missing, stale, or malformed broker record is replaced below.
|
|
38
|
+
}
|
|
39
|
+
fs.rmSync(brokerPath, { force: true });
|
|
40
|
+
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const extension = path.extname(fileURLToPath(import.meta.url));
|
|
42
|
+
const internalCli = path.resolve(directory, '..', '..', 'bin', `internal-cli${extension}`);
|
|
43
|
+
const child = spawn(process.execPath, [internalCli, 'sandbox-control', 'serve', '--manifest', control.manifestPath], { cwd: params.config.repoRoot, detached: true, stdio: 'ignore' });
|
|
44
|
+
child.once('error', () => {
|
|
45
|
+
// Readiness remains fail-closed; a later control request reports the
|
|
46
|
+
// unavailable broker without turning this detached spawn into an
|
|
47
|
+
// unhandled process error.
|
|
48
|
+
});
|
|
49
|
+
child.unref();
|
|
50
|
+
}
|
|
11
51
|
function findingKey(finding) {
|
|
12
52
|
return `${finding.repairKind}:${finding.path ?? finding.seed?.targetPath ?? finding.message}`;
|
|
13
53
|
}
|
|
@@ -156,7 +196,28 @@ function hostSourceAccessible(hostPath, writable) {
|
|
|
156
196
|
}
|
|
157
197
|
function expectedMounts(params) {
|
|
158
198
|
const { config, branch, tools, actualMounts } = params;
|
|
159
|
-
const
|
|
199
|
+
const view = sandboxWorkspaceViewPaths({
|
|
200
|
+
base: config.workspaceViewBase ?? path.join(config.home, '.agent-infra', 'workspace-views'),
|
|
201
|
+
project: config.project,
|
|
202
|
+
container: params.container,
|
|
203
|
+
identity: params.workspace
|
|
204
|
+
});
|
|
205
|
+
const control = sandboxControlPaths({
|
|
206
|
+
base: config.controlBase ?? path.join(config.home, '.agent-infra', 'sandbox-control'),
|
|
207
|
+
project: config.project,
|
|
208
|
+
container: params.container,
|
|
209
|
+
identity: params.workspace
|
|
210
|
+
});
|
|
211
|
+
const core = sandboxCoreBindMounts(config, branch, {
|
|
212
|
+
workspaceViewRoot: view.root,
|
|
213
|
+
controlDir: control.channelDir,
|
|
214
|
+
...(params.workspace.mode === 'task-bound'
|
|
215
|
+
? {
|
|
216
|
+
taskSource: assertSandboxTaskSource(config.repoRoot, params.workspace.taskId),
|
|
217
|
+
taskId: params.workspace.taskId
|
|
218
|
+
}
|
|
219
|
+
: {})
|
|
220
|
+
}).map((mount) => ({
|
|
160
221
|
path: mount.containerPath,
|
|
161
222
|
expectedType: 'bind',
|
|
162
223
|
hostPaths: mount.hostPaths,
|
|
@@ -248,6 +309,7 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
248
309
|
}
|
|
249
310
|
}
|
|
250
311
|
const capabilityPlan = createSandboxCapabilityPlan(params.config);
|
|
312
|
+
const workspace = params.workspace ?? { mode: 'branch-only' };
|
|
251
313
|
const tools = [...capabilityPlan.tools];
|
|
252
314
|
const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
|
|
253
315
|
const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
|
|
@@ -269,6 +331,10 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
269
331
|
}));
|
|
270
332
|
const labels = inspection.Config?.Labels ?? {};
|
|
271
333
|
const branchLabel = labels[sandboxBranchLabel(params.config)];
|
|
334
|
+
const containerWorkspace = parseSandboxWorkspaceIdentity(labels, {
|
|
335
|
+
mode: sandboxWorkspaceModeLabel(params.config),
|
|
336
|
+
taskId: sandboxTaskIdLabel(params.config)
|
|
337
|
+
});
|
|
272
338
|
const selectedToolIds = new Set(tools.map((tool) => tool.id));
|
|
273
339
|
const enforceCanonicalPlan = params.config.agentClientSource === 'canonical';
|
|
274
340
|
const unexpectedCapabilityMounts = enforceCanonicalPlan
|
|
@@ -280,6 +346,8 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
280
346
|
const mountSnapshots = expectedMounts({
|
|
281
347
|
config: params.config,
|
|
282
348
|
branch: params.branch,
|
|
349
|
+
container: params.container,
|
|
350
|
+
workspace,
|
|
283
351
|
tools,
|
|
284
352
|
actualMounts: mountsByDestination
|
|
285
353
|
}).map((expected) => {
|
|
@@ -313,7 +381,8 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
313
381
|
return {
|
|
314
382
|
identityOk: typeof inspection.Id === 'string'
|
|
315
383
|
&& inspection.Id.length > 0
|
|
316
|
-
&& branchLabel === params.branch
|
|
384
|
+
&& branchLabel === params.branch
|
|
385
|
+
&& sameSandboxWorkspaceIdentity(containerWorkspace, workspace),
|
|
317
386
|
runtimeCapabilityOk: enforceCanonicalPlan
|
|
318
387
|
? labels[sandboxRuntimeCapabilityLabel(params.config)] === capabilityPlan.runtimeSignature
|
|
319
388
|
: undefined,
|
|
@@ -424,14 +493,51 @@ function assess(params) {
|
|
|
424
493
|
const snapshot = collectSandboxRecoverySnapshot(params);
|
|
425
494
|
return { snapshot, findings: classifySandboxRecovery(snapshot) };
|
|
426
495
|
}
|
|
427
|
-
|
|
496
|
+
function canRetryFreshReadiness(findings) {
|
|
497
|
+
return findings.length > 0 && findings.every((finding) => finding.repairKind === 'hard-failure'
|
|
498
|
+
&& finding.path !== undefined
|
|
499
|
+
&& (finding.path.startsWith('/workspace/.agents/workspace')
|
|
500
|
+
|| finding.path === '/home/devuser/.host-shell-config'
|
|
501
|
+
|| finding.path === '/home/devuser/.bash_aliases'));
|
|
502
|
+
}
|
|
503
|
+
export async function assertFreshSandboxReady(params) {
|
|
428
504
|
validateTmpfsSeedEntries({
|
|
429
505
|
engine: params.engine,
|
|
430
506
|
container: params.container,
|
|
431
507
|
entries: params.copiedEntries,
|
|
432
508
|
deps: params.deps
|
|
433
509
|
});
|
|
434
|
-
|
|
510
|
+
let { findings } = assess(params);
|
|
511
|
+
if (canRetryFreshReadiness(findings)) {
|
|
512
|
+
const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
|
|
513
|
+
runVerboseFn(params.engine, 'docker', ['restart', params.container]);
|
|
514
|
+
prepareTmpfsMounts({
|
|
515
|
+
engine: params.engine,
|
|
516
|
+
container: params.container,
|
|
517
|
+
mountPaths: resolveTools(params.config)
|
|
518
|
+
.filter((tool) => tool.tmpfs)
|
|
519
|
+
.map((tool) => tool.containerMount),
|
|
520
|
+
deps: params.deps
|
|
521
|
+
});
|
|
522
|
+
hydrateTmpfsSeedEntries({
|
|
523
|
+
engine: params.engine,
|
|
524
|
+
container: params.container,
|
|
525
|
+
entries: params.copiedEntries,
|
|
526
|
+
replace: true,
|
|
527
|
+
deps: params.deps
|
|
528
|
+
});
|
|
529
|
+
let retried = assess(params);
|
|
530
|
+
if (!retried.findings.some((finding) => finding.repairKind === 'hard-failure')) {
|
|
531
|
+
repairFindings({
|
|
532
|
+
engine: params.engine,
|
|
533
|
+
container: params.container,
|
|
534
|
+
findings: retried.findings,
|
|
535
|
+
deps: params.deps
|
|
536
|
+
});
|
|
537
|
+
retried = assess(params);
|
|
538
|
+
}
|
|
539
|
+
findings = retried.findings;
|
|
540
|
+
}
|
|
435
541
|
if (findings.length > 0) {
|
|
436
542
|
throw new Error(`Fresh sandbox readiness check failed: ${describeFindings(findings)}`);
|
|
437
543
|
}
|
|
@@ -441,6 +547,11 @@ export async function ensureSandboxReady(params) {
|
|
|
441
547
|
const startFn = deps?.start ?? startSandboxContainer;
|
|
442
548
|
const warnings = [];
|
|
443
549
|
let failure = null;
|
|
550
|
+
ensureSandboxControlBroker({
|
|
551
|
+
config: params.config,
|
|
552
|
+
container: params.row.name,
|
|
553
|
+
workspace: params.workspace ?? { mode: 'branch-only' }
|
|
554
|
+
});
|
|
444
555
|
try {
|
|
445
556
|
if (!params.row.running) {
|
|
446
557
|
startFn(params.engine, params.row.name);
|
|
@@ -448,6 +559,7 @@ export async function ensureSandboxReady(params) {
|
|
|
448
559
|
config: params.config,
|
|
449
560
|
engine: params.engine,
|
|
450
561
|
branch: params.branch,
|
|
562
|
+
workspace: params.workspace,
|
|
451
563
|
container: params.row.name,
|
|
452
564
|
deps
|
|
453
565
|
});
|
|
@@ -472,6 +584,7 @@ export async function ensureSandboxReady(params) {
|
|
|
472
584
|
config: params.config,
|
|
473
585
|
engine: params.engine,
|
|
474
586
|
branch: params.branch,
|
|
587
|
+
workspace: params.workspace,
|
|
475
588
|
container: params.row.name,
|
|
476
589
|
deps
|
|
477
590
|
});
|
|
@@ -486,6 +599,7 @@ export async function ensureSandboxReady(params) {
|
|
|
486
599
|
config: params.config,
|
|
487
600
|
engine: params.engine,
|
|
488
601
|
branch: params.branch,
|
|
602
|
+
workspace: params.workspace,
|
|
489
603
|
container: params.row.name,
|
|
490
604
|
deps
|
|
491
605
|
});
|
|
@@ -499,6 +613,7 @@ export async function ensureSandboxReady(params) {
|
|
|
499
613
|
config: params.config,
|
|
500
614
|
engine: params.engine,
|
|
501
615
|
branch: params.branch,
|
|
616
|
+
workspace: params.workspace,
|
|
502
617
|
container: params.row.name,
|
|
503
618
|
deps
|
|
504
619
|
});
|
|
@@ -521,6 +636,7 @@ export async function ensureSandboxReady(params) {
|
|
|
521
636
|
config: params.config,
|
|
522
637
|
engine: params.engine,
|
|
523
638
|
branch: params.branch,
|
|
639
|
+
workspace: params.workspace,
|
|
524
640
|
container: params.row.name,
|
|
525
641
|
deps
|
|
526
642
|
});
|
|
@@ -539,6 +655,7 @@ export async function ensureSandboxReady(params) {
|
|
|
539
655
|
config: params.config,
|
|
540
656
|
engine: params.engine,
|
|
541
657
|
branch: params.branch,
|
|
658
|
+
workspace: params.workspace,
|
|
542
659
|
container: params.row.name,
|
|
543
660
|
deps
|
|
544
661
|
});
|
|
@@ -557,6 +674,11 @@ export async function ensureSandboxReady(params) {
|
|
|
557
674
|
const warning = `Sandbox recovery failed in place. Replacing only the container. ${dataBoundary}`;
|
|
558
675
|
warnings.push(warning);
|
|
559
676
|
(params.writeWarning ?? ((message) => process.stderr.write(`${message}\n`)))(warning);
|
|
677
|
+
const runVerboseFn = deps?.runVerbose ?? runVerboseEngine;
|
|
678
|
+
if (params.row.running) {
|
|
679
|
+
runVerboseFn(params.engine, 'docker', ['stop', params.row.name]);
|
|
680
|
+
}
|
|
681
|
+
runVerboseFn(params.engine, 'docker', ['rm', params.row.name]);
|
|
560
682
|
await params.recreate(params.branch);
|
|
561
683
|
const rows = (deps?.fetchRows ?? fetchSandboxRows)(params.engine, sandboxLabel(params.config), sandboxBranchLabel(params.config));
|
|
562
684
|
const replacement = selectSandboxContainer([...rows.running, ...rows.nonRunning], containerNameCandidates(params.config, params.branch));
|
|
@@ -567,6 +689,7 @@ export async function ensureSandboxReady(params) {
|
|
|
567
689
|
config: params.config,
|
|
568
690
|
engine: params.engine,
|
|
569
691
|
branch: params.branch,
|
|
692
|
+
workspace: params.workspace,
|
|
570
693
|
container: replacement.name,
|
|
571
694
|
deps
|
|
572
695
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
export function redactCommandError(text) {
|
|
8
|
+
if (!text || typeof text !== 'string')
|
|
9
|
+
return '';
|
|
10
|
+
return SENSITIVE_OUTPUT_PATTERNS.reduce((result, { pattern, replacement }) => result.replace(pattern, replacement), text);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -64,69 +64,9 @@ RUN printf '%s\n' \
|
|
|
64
64
|
'set -g mouse on' \
|
|
65
65
|
'set -g status-interval 1' \
|
|
66
66
|
'set -g status-right-length 80' \
|
|
67
|
-
"set -g status-right '
|
|
67
|
+
"set -g status-right '%H:%M'" \
|
|
68
68
|
> /etc/tmux.conf
|
|
69
69
|
|
|
70
|
-
RUN cat > /usr/local/bin/cc-token-status <<'SCRIPT' && chmod +x /usr/local/bin/cc-token-status
|
|
71
|
-
#!/bin/sh
|
|
72
|
-
set -eu
|
|
73
|
-
|
|
74
|
-
SETTINGS_FILE="/home/devuser/.claude/settings.json"
|
|
75
|
-
if [ -r "$SETTINGS_FILE" ] && jq -e '
|
|
76
|
-
def has_nonempty($v):
|
|
77
|
-
if ($v | type) == "string" then ($v | gsub("^\\s+|\\s+$"; "") | length) > 0 else false end;
|
|
78
|
-
(if type == "object" then . else {} end) as $settings
|
|
79
|
-
| (if ($settings.env | type) == "object" then $settings.env else {} end) as $env
|
|
80
|
-
| has_nonempty($env.ANTHROPIC_AUTH_TOKEN)
|
|
81
|
-
or has_nonempty($env.ANTHROPIC_API_KEY)
|
|
82
|
-
or has_nonempty($settings.apiKeyHelper)
|
|
83
|
-
' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
84
|
-
exit 0
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
CRED_FILE="/home/devuser/.claude/.credentials.json"
|
|
88
|
-
[ -r "$CRED_FILE" ] || exit 0
|
|
89
|
-
|
|
90
|
-
EXPIRES_MS=$(jq -r '(.claudeAiOauth.expiresAt // .expiresAt) // empty' "$CRED_FILE" 2>/dev/null || true)
|
|
91
|
-
case "$EXPIRES_MS" in
|
|
92
|
-
''|*[!0-9]*) exit 0 ;;
|
|
93
|
-
esac
|
|
94
|
-
|
|
95
|
-
NOW_MS=$(($(date +%s) * 1000))
|
|
96
|
-
DIFF_MS=$((EXPIRES_MS - NOW_MS))
|
|
97
|
-
DIFF_S=$((DIFF_MS / 1000))
|
|
98
|
-
|
|
99
|
-
DIM='#[fg=colour245]'
|
|
100
|
-
YELLOW='#[fg=yellow]'
|
|
101
|
-
YELLOW_BOLD='#[fg=yellow,bold]'
|
|
102
|
-
RED_BOLD='#[fg=red,bold]'
|
|
103
|
-
RED_REV='#[fg=red,reverse]'
|
|
104
|
-
RESET='#[default]'
|
|
105
|
-
|
|
106
|
-
if [ "$DIFF_S" -le 0 ]; then
|
|
107
|
-
ELAPSED=$(( -DIFF_S ))
|
|
108
|
-
M=$((ELAPSED / 60))
|
|
109
|
-
printf '%sClaude Code auth EXPIRED %dm ago%s' "$RED_REV" "$M" "$RESET"
|
|
110
|
-
elif [ "$DIFF_S" -lt 60 ]; then
|
|
111
|
-
printf '%sClaude Code auth expires in %ds%s' "$RED_BOLD" "$DIFF_S" "$RESET"
|
|
112
|
-
elif [ "$DIFF_S" -lt 300 ]; then
|
|
113
|
-
M=$((DIFF_S / 60))
|
|
114
|
-
S=$((DIFF_S % 60))
|
|
115
|
-
printf '%sClaude Code auth expires in %dm %ds%s' "$RED_BOLD" "$M" "$S" "$RESET"
|
|
116
|
-
elif [ "$DIFF_S" -lt 1800 ]; then
|
|
117
|
-
M=$((DIFF_S / 60))
|
|
118
|
-
printf '%sClaude Code auth expires in %dm%s' "$YELLOW_BOLD" "$M" "$RESET"
|
|
119
|
-
elif [ "$DIFF_S" -lt 3600 ]; then
|
|
120
|
-
M=$((DIFF_S / 60))
|
|
121
|
-
printf '%sClaude Code auth expires in %dm%s' "$YELLOW" "$M" "$RESET"
|
|
122
|
-
else
|
|
123
|
-
TOTAL_M=$((DIFF_S / 60))
|
|
124
|
-
H=$((TOTAL_M / 60))
|
|
125
|
-
M=$((TOTAL_M % 60))
|
|
126
|
-
printf '%sClaude Code auth expires in %dh %dm%s' "$DIM" "$H" "$M" "$RESET"
|
|
127
|
-
fi
|
|
128
|
-
SCRIPT
|
|
129
|
-
|
|
130
70
|
RUN cat > /usr/local/bin/sandbox-dotfiles-link <<'SCRIPT' && chmod +x /usr/local/bin/sandbox-dotfiles-link
|
|
131
71
|
#!/bin/sh
|
|
132
72
|
# Mirror /dotfiles/ tree as symlinks under $HOME/, overwriting any image-baked
|
|
@@ -136,23 +76,34 @@ set -eu
|
|
|
136
76
|
DOTFILES_SRC=/dotfiles
|
|
137
77
|
[ -d "$DOTFILES_SRC" ] || exit 0
|
|
138
78
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
target="$HOME/$rel"
|
|
143
|
-
case "$rel" in
|
|
79
|
+
is_dotfiles_excluded() {
|
|
80
|
+
candidate=$1
|
|
81
|
+
case "$candidate" in
|
|
144
82
|
.ssh|.ssh/*|\
|
|
145
83
|
.gnupg|.gnupg/*|\
|
|
146
|
-
.claude|.claude/*|\
|
|
147
|
-
.codex|.codex/*|\
|
|
148
|
-
.gemini|.gemini/*|\
|
|
149
|
-
.config/opencode|.config/opencode/*|\
|
|
150
|
-
.local/share/opencode|.local/share/opencode/*|\
|
|
151
84
|
.host-shell-config|.host-shell-config/*|\
|
|
152
85
|
.gitconfig|.gitignore_global|.stCommitMsg|.bash_aliases|README.md)
|
|
153
|
-
|
|
86
|
+
return 0 ;;
|
|
154
87
|
esac
|
|
155
88
|
|
|
89
|
+
[ -r /etc/agent-infra/dotfiles-exclusions ] || return 1
|
|
90
|
+
while IFS= read -r prefix; do
|
|
91
|
+
[ -n "$prefix" ] || continue
|
|
92
|
+
case "$candidate" in
|
|
93
|
+
"$prefix"|"$prefix"/*) return 0 ;;
|
|
94
|
+
esac
|
|
95
|
+
done < /etc/agent-infra/dotfiles-exclusions
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
cd "$DOTFILES_SRC"
|
|
100
|
+
find . -type f -print | while IFS= read -r rel; do
|
|
101
|
+
rel=${rel#./}
|
|
102
|
+
target="$HOME/$rel"
|
|
103
|
+
if is_dotfiles_excluded "$rel"; then
|
|
104
|
+
continue
|
|
105
|
+
fi
|
|
106
|
+
|
|
156
107
|
mkdir -p "$(dirname "$target")"
|
|
157
108
|
if [ -d "$target" ] && [ ! -L "$target" ]; then
|
|
158
109
|
printf 'sandbox-dotfiles-link: skipping %s (existing directory; use nested path like %s/<file> instead)\n' "$target" "$rel" >&2
|
|
@@ -198,8 +149,7 @@ fi
|
|
|
198
149
|
exec tmux new-session -s "$SESSION"
|
|
199
150
|
SCRIPT
|
|
200
151
|
|
|
201
|
-
RUN test -s /usr/local/bin/
|
|
202
|
-
test -s /usr/local/bin/sandbox-dotfiles-link && test -x /usr/local/bin/sandbox-dotfiles-link && \
|
|
152
|
+
RUN test -s /usr/local/bin/sandbox-dotfiles-link && test -x /usr/local/bin/sandbox-dotfiles-link && \
|
|
203
153
|
test -s /usr/local/bin/sandbox-tmux-entry && test -x /usr/local/bin/sandbox-tmux-entry
|
|
204
154
|
|
|
205
155
|
ENV LANG=en_US.UTF-8
|