@rozek/nanoclaw 0.0.1
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/.claude/settings.json +1 -0
- package/.claude/skills/add-compact/SKILL.md +135 -0
- package/.claude/skills/add-discord/SKILL.md +203 -0
- package/.claude/skills/add-gmail/SKILL.md +220 -0
- package/.claude/skills/add-image-vision/SKILL.md +94 -0
- package/.claude/skills/add-ollama-tool/SKILL.md +153 -0
- package/.claude/skills/add-parallel/SKILL.md +290 -0
- package/.claude/skills/add-pdf-reader/SKILL.md +104 -0
- package/.claude/skills/add-reactions/SKILL.md +117 -0
- package/.claude/skills/add-slack/SKILL.md +207 -0
- package/.claude/skills/add-telegram/SKILL.md +222 -0
- package/.claude/skills/add-telegram-swarm/SKILL.md +384 -0
- package/.claude/skills/add-voice-transcription/SKILL.md +148 -0
- package/.claude/skills/add-whatsapp/SKILL.md +372 -0
- package/.claude/skills/convert-to-apple-container/SKILL.md +175 -0
- package/.claude/skills/customize/SKILL.md +110 -0
- package/.claude/skills/debug/SKILL.md +349 -0
- package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
- package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
- package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
- package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
- package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
- package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
- package/.claude/skills/setup/SKILL.md +218 -0
- package/.claude/skills/update-nanoclaw/SKILL.md +235 -0
- package/.claude/skills/update-skills/SKILL.md +130 -0
- package/.claude/skills/use-local-whisper/SKILL.md +152 -0
- package/.claude/skills/x-integration/SKILL.md +417 -0
- package/.claude/skills/x-integration/agent.ts +243 -0
- package/.claude/skills/x-integration/host.ts +159 -0
- package/.claude/skills/x-integration/lib/browser.ts +148 -0
- package/.claude/skills/x-integration/lib/config.ts +62 -0
- package/.claude/skills/x-integration/scripts/like.ts +56 -0
- package/.claude/skills/x-integration/scripts/post.ts +66 -0
- package/.claude/skills/x-integration/scripts/quote.ts +80 -0
- package/.claude/skills/x-integration/scripts/reply.ts +74 -0
- package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
- package/.claude/skills/x-integration/scripts/setup.ts +87 -0
- package/.env.example +1 -0
- package/.github/CODEOWNERS +10 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/.github/workflows/bump-version.yml +32 -0
- package/.github/workflows/ci.yml +25 -0
- package/.github/workflows/merge-forward-skills.yml +160 -0
- package/.github/workflows/update-tokens.yml +42 -0
- package/.husky/pre-commit +1 -0
- package/.mcp.json +3 -0
- package/.nvmrc +1 -0
- package/.prettierrc +3 -0
- package/CHANGELOG.md +8 -0
- package/CLAUDE.md +64 -0
- package/CONTRIBUTING.md +23 -0
- package/CONTRIBUTORS.md +15 -0
- package/LICENSE +21 -0
- package/NanoClaw_with_Web-Support.md +325 -0
- package/README.md +261 -0
- package/README_zh.md +200 -0
- package/assets/nanoclaw-favicon.png +0 -0
- package/assets/nanoclaw-icon.png +0 -0
- package/assets/nanoclaw-logo-dark.png +0 -0
- package/assets/nanoclaw-logo.png +0 -0
- package/assets/nanoclaw-profile.jpeg +0 -0
- package/assets/nanoclaw-sales.png +0 -0
- package/assets/social-preview.jpg +0 -0
- package/config-examples/mount-allowlist.json +25 -0
- package/container/Dockerfile +70 -0
- package/container/agent-runner/package.json +21 -0
- package/container/agent-runner/src/index.ts +774 -0
- package/container/agent-runner/src/ipc-mcp-stdio.ts +338 -0
- package/container/agent-runner/tsconfig.json +15 -0
- package/container/build.sh +23 -0
- package/container/skills/agent-browser/SKILL.md +159 -0
- package/container/skills/capabilities/SKILL.md +100 -0
- package/container/skills/cwd/SKILL.md +32 -0
- package/container/skills/pwd/SKILL.md +19 -0
- package/container/skills/status/SKILL.md +104 -0
- package/dist/channels/index.d.ts +2 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +10 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/registry.d.ts +13 -0
- package/dist/channels/registry.d.ts.map +1 -0
- package/dist/channels/registry.js +11 -0
- package/dist/channels/registry.js.map +1 -0
- package/dist/channels/registry.test.d.ts +2 -0
- package/dist/channels/registry.test.d.ts.map +1 -0
- package/dist/channels/registry.test.js +32 -0
- package/dist/channels/registry.test.js.map +1 -0
- package/dist/channels/web.d.ts +2 -0
- package/dist/channels/web.d.ts.map +1 -0
- package/dist/channels/web.js +1843 -0
- package/dist/channels/web.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +182 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/container-runner.d.ts +44 -0
- package/dist/container-runner.d.ts.map +1 -0
- package/dist/container-runner.js +511 -0
- package/dist/container-runner.js.map +1 -0
- package/dist/container-runner.test.d.ts +2 -0
- package/dist/container-runner.test.d.ts.map +1 -0
- package/dist/container-runner.test.js +150 -0
- package/dist/container-runner.test.js.map +1 -0
- package/dist/container-runtime.d.ts +22 -0
- package/dist/container-runtime.d.ts.map +1 -0
- package/dist/container-runtime.js +96 -0
- package/dist/container-runtime.js.map +1 -0
- package/dist/container-runtime.test.d.ts +2 -0
- package/dist/container-runtime.test.d.ts.map +1 -0
- package/dist/container-runtime.test.js +93 -0
- package/dist/container-runtime.test.js.map +1 -0
- package/dist/credential-proxy.d.ts +21 -0
- package/dist/credential-proxy.d.ts.map +1 -0
- package/dist/credential-proxy.js +95 -0
- package/dist/credential-proxy.js.map +1 -0
- package/dist/credential-proxy.test.d.ts +2 -0
- package/dist/credential-proxy.test.d.ts.map +1 -0
- package/dist/credential-proxy.test.js +134 -0
- package/dist/credential-proxy.test.js.map +1 -0
- package/dist/db.d.ts +115 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +549 -0
- package/dist/db.js.map +1 -0
- package/dist/db.test.d.ts +2 -0
- package/dist/db.test.d.ts.map +1 -0
- package/dist/db.test.js +360 -0
- package/dist/db.test.js.map +1 -0
- package/dist/env.d.ts +8 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +42 -0
- package/dist/env.js.map +1 -0
- package/dist/formatting.test.d.ts +2 -0
- package/dist/formatting.test.d.ts.map +1 -0
- package/dist/formatting.test.js +183 -0
- package/dist/formatting.test.js.map +1 -0
- package/dist/group-folder.d.ts +5 -0
- package/dist/group-folder.d.ts.map +1 -0
- package/dist/group-folder.js +44 -0
- package/dist/group-folder.js.map +1 -0
- package/dist/group-folder.test.d.ts +2 -0
- package/dist/group-folder.test.d.ts.map +1 -0
- package/dist/group-folder.test.js +29 -0
- package/dist/group-folder.test.js.map +1 -0
- package/dist/group-queue.d.ts +40 -0
- package/dist/group-queue.d.ts.map +1 -0
- package/dist/group-queue.js +276 -0
- package/dist/group-queue.js.map +1 -0
- package/dist/group-queue.test.d.ts +2 -0
- package/dist/group-queue.test.d.ts.map +1 -0
- package/dist/group-queue.test.js +341 -0
- package/dist/group-queue.test.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +592 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc-auth.test.d.ts +2 -0
- package/dist/ipc-auth.test.d.ts.map +1 -0
- package/dist/ipc-auth.test.js +434 -0
- package/dist/ipc-auth.test.js.map +1 -0
- package/dist/ipc.d.ts +32 -0
- package/dist/ipc.d.ts.map +1 -0
- package/dist/ipc.js +311 -0
- package/dist/ipc.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/mount-security.d.ts +34 -0
- package/dist/mount-security.d.ts.map +1 -0
- package/dist/mount-security.js +325 -0
- package/dist/mount-security.js.map +1 -0
- package/dist/remote-control.d.ts +32 -0
- package/dist/remote-control.d.ts.map +1 -0
- package/dist/remote-control.js +185 -0
- package/dist/remote-control.js.map +1 -0
- package/dist/remote-control.test.d.ts +2 -0
- package/dist/remote-control.test.d.ts.map +1 -0
- package/dist/remote-control.test.js +321 -0
- package/dist/remote-control.test.js.map +1 -0
- package/dist/router.d.ts +8 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +37 -0
- package/dist/router.js.map +1 -0
- package/dist/routing.test.d.ts +2 -0
- package/dist/routing.test.d.ts.map +1 -0
- package/dist/routing.test.js +81 -0
- package/dist/routing.test.js.map +1 -0
- package/dist/sender-allowlist.d.ts +14 -0
- package/dist/sender-allowlist.d.ts.map +1 -0
- package/dist/sender-allowlist.js +79 -0
- package/dist/sender-allowlist.js.map +1 -0
- package/dist/sender-allowlist.test.d.ts +2 -0
- package/dist/sender-allowlist.test.d.ts.map +1 -0
- package/dist/sender-allowlist.test.js +186 -0
- package/dist/sender-allowlist.test.js.map +1 -0
- package/dist/session-commands.d.ts +47 -0
- package/dist/session-commands.d.ts.map +1 -0
- package/dist/session-commands.js +104 -0
- package/dist/session-commands.js.map +1 -0
- package/dist/session-commands.test.d.ts +2 -0
- package/dist/session-commands.test.d.ts.map +1 -0
- package/dist/session-commands.test.js +194 -0
- package/dist/session-commands.test.js.map +1 -0
- package/dist/task-scheduler.d.ts +22 -0
- package/dist/task-scheduler.d.ts.map +1 -0
- package/dist/task-scheduler.js +241 -0
- package/dist/task-scheduler.js.map +1 -0
- package/dist/task-scheduler.test.d.ts +2 -0
- package/dist/task-scheduler.test.d.ts.map +1 -0
- package/dist/task-scheduler.test.js +107 -0
- package/dist/task-scheduler.test.js.map +1 -0
- package/dist/timezone.d.ts +6 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +17 -0
- package/dist/timezone.js.map +1 -0
- package/dist/timezone.test.d.ts +2 -0
- package/dist/timezone.test.d.ts.map +1 -0
- package/dist/timezone.test.js +23 -0
- package/dist/timezone.test.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
- package/docs/DEBUG_CHECKLIST.md +143 -0
- package/docs/REQUIREMENTS.md +196 -0
- package/docs/SDK_DEEP_DIVE.md +643 -0
- package/docs/SECURITY.md +122 -0
- package/docs/SPEC.md +785 -0
- package/docs/docker-sandboxes.md +359 -0
- package/docs/nanoclaw-architecture-final.md +1063 -0
- package/docs/nanorepo-architecture.md +168 -0
- package/docs/skills-as-branches.md +662 -0
- package/groups/global/CLAUDE.md +58 -0
- package/groups/main/CLAUDE.md +246 -0
- package/launchd/com.nanoclaw.plist +32 -0
- package/package.json +45 -0
- package/repo-tokens/README.md +113 -0
- package/repo-tokens/action.yml +186 -0
- package/repo-tokens/badge.svg +23 -0
- package/repo-tokens/examples/green.svg +14 -0
- package/repo-tokens/examples/red.svg +14 -0
- package/repo-tokens/examples/yellow-green.svg +14 -0
- package/repo-tokens/examples/yellow.svg +14 -0
- package/scripts/run-migrations.ts +105 -0
- package/setup/container.ts +144 -0
- package/setup/environment.test.ts +121 -0
- package/setup/environment.ts +94 -0
- package/setup/groups.ts +229 -0
- package/setup/index.ts +58 -0
- package/setup/mounts.ts +115 -0
- package/setup/platform.test.ts +120 -0
- package/setup/platform.ts +132 -0
- package/setup/register.test.ts +257 -0
- package/setup/register.ts +177 -0
- package/setup/service.test.ts +187 -0
- package/setup/service.ts +362 -0
- package/setup/status.ts +16 -0
- package/setup/verify.ts +192 -0
- package/setup.sh +161 -0
- package/src/channels/index.ts +15 -0
- package/src/channels/registry.test.ts +42 -0
- package/src/channels/registry.ts +32 -0
- package/src/channels/web.ts +1931 -0
- package/src/cli.ts +209 -0
- package/src/config.ts +73 -0
- package/src/container-runner.test.ts +210 -0
- package/src/container-runner.ts +768 -0
- package/src/container-runtime.test.ts +149 -0
- package/src/container-runtime.ts +127 -0
- package/src/credential-proxy.test.ts +192 -0
- package/src/credential-proxy.ts +125 -0
- package/src/db.test.ts +484 -0
- package/src/db.ts +803 -0
- package/src/env.ts +42 -0
- package/src/formatting.test.ts +256 -0
- package/src/group-folder.test.ts +43 -0
- package/src/group-folder.ts +44 -0
- package/src/group-queue.test.ts +484 -0
- package/src/group-queue.ts +379 -0
- package/src/index.ts +832 -0
- package/src/ipc-auth.test.ts +679 -0
- package/src/ipc.ts +461 -0
- package/src/logger.ts +16 -0
- package/src/mount-security.ts +419 -0
- package/src/remote-control.test.ts +397 -0
- package/src/remote-control.ts +224 -0
- package/src/router.ts +52 -0
- package/src/routing.test.ts +170 -0
- package/src/sender-allowlist.test.ts +216 -0
- package/src/sender-allowlist.ts +128 -0
- package/src/session-commands.test.ts +247 -0
- package/src/session-commands.ts +163 -0
- package/src/task-scheduler.test.ts +129 -0
- package/src/task-scheduler.ts +328 -0
- package/src/timezone.test.ts +29 -0
- package/src/timezone.ts +16 -0
- package/src/types.ts +109 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +7 -0
- package/vitest.skills.config.ts +7 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { PassThrough } from 'stream';
|
|
4
|
+
// Sentinel markers must match container-runner.ts
|
|
5
|
+
const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
|
|
6
|
+
const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
|
|
7
|
+
// Mock config
|
|
8
|
+
vi.mock('./config.js', () => ({
|
|
9
|
+
CONTAINER_IMAGE: 'nanoclaw-agent:latest',
|
|
10
|
+
CONTAINER_MAX_OUTPUT_SIZE: 10485760,
|
|
11
|
+
CONTAINER_TIMEOUT: 1800000, // 30min
|
|
12
|
+
CREDENTIAL_PROXY_PORT: 3001,
|
|
13
|
+
DATA_DIR: '/tmp/nanoclaw-test-data',
|
|
14
|
+
GROUPS_DIR: '/tmp/nanoclaw-test-groups',
|
|
15
|
+
IDLE_TIMEOUT: 1800000, // 30min
|
|
16
|
+
TIMEZONE: 'America/Los_Angeles',
|
|
17
|
+
}));
|
|
18
|
+
// Mock logger
|
|
19
|
+
vi.mock('./logger.js', () => ({
|
|
20
|
+
logger: {
|
|
21
|
+
debug: vi.fn(),
|
|
22
|
+
info: vi.fn(),
|
|
23
|
+
warn: vi.fn(),
|
|
24
|
+
error: vi.fn(),
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
// Mock fs
|
|
28
|
+
vi.mock('fs', async () => {
|
|
29
|
+
const actual = await vi.importActual('fs');
|
|
30
|
+
return {
|
|
31
|
+
...actual,
|
|
32
|
+
default: {
|
|
33
|
+
...actual,
|
|
34
|
+
existsSync: vi.fn(() => false),
|
|
35
|
+
mkdirSync: vi.fn(),
|
|
36
|
+
writeFileSync: vi.fn(),
|
|
37
|
+
readFileSync: vi.fn(() => ''),
|
|
38
|
+
readdirSync: vi.fn(() => []),
|
|
39
|
+
statSync: vi.fn(() => ({ isDirectory: () => false })),
|
|
40
|
+
copyFileSync: vi.fn(),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
// Mock mount-security
|
|
45
|
+
vi.mock('./mount-security.js', () => ({
|
|
46
|
+
validateAdditionalMounts: vi.fn(() => []),
|
|
47
|
+
}));
|
|
48
|
+
// Create a controllable fake ChildProcess
|
|
49
|
+
function createFakeProcess() {
|
|
50
|
+
const proc = new EventEmitter();
|
|
51
|
+
proc.stdin = new PassThrough();
|
|
52
|
+
proc.stdout = new PassThrough();
|
|
53
|
+
proc.stderr = new PassThrough();
|
|
54
|
+
proc.kill = vi.fn();
|
|
55
|
+
proc.pid = 12345;
|
|
56
|
+
return proc;
|
|
57
|
+
}
|
|
58
|
+
let fakeProc;
|
|
59
|
+
// Mock child_process.spawn
|
|
60
|
+
vi.mock('child_process', async () => {
|
|
61
|
+
const actual = await vi.importActual('child_process');
|
|
62
|
+
return {
|
|
63
|
+
...actual,
|
|
64
|
+
spawn: vi.fn(() => fakeProc),
|
|
65
|
+
exec: vi.fn((_cmd, _opts, cb) => {
|
|
66
|
+
if (cb)
|
|
67
|
+
cb(null);
|
|
68
|
+
return new EventEmitter();
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
import { runContainerAgent } from './container-runner.js';
|
|
73
|
+
const testGroup = {
|
|
74
|
+
name: 'Test Group',
|
|
75
|
+
folder: 'test-group',
|
|
76
|
+
trigger: '@Andy',
|
|
77
|
+
added_at: new Date().toISOString(),
|
|
78
|
+
};
|
|
79
|
+
const testInput = {
|
|
80
|
+
prompt: 'Hello',
|
|
81
|
+
groupFolder: 'test-group',
|
|
82
|
+
chatJid: 'test@g.us',
|
|
83
|
+
isMain: false,
|
|
84
|
+
};
|
|
85
|
+
function emitOutputMarker(proc, output) {
|
|
86
|
+
const json = JSON.stringify(output);
|
|
87
|
+
proc.stdout.push(`${OUTPUT_START_MARKER}\n${json}\n${OUTPUT_END_MARKER}\n`);
|
|
88
|
+
}
|
|
89
|
+
describe('container-runner timeout behavior', () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
vi.useFakeTimers();
|
|
92
|
+
fakeProc = createFakeProcess();
|
|
93
|
+
});
|
|
94
|
+
afterEach(() => {
|
|
95
|
+
vi.useRealTimers();
|
|
96
|
+
});
|
|
97
|
+
it('timeout after output resolves as success', async () => {
|
|
98
|
+
const onOutput = vi.fn(async () => { });
|
|
99
|
+
const resultPromise = runContainerAgent(testGroup, testInput, () => { }, onOutput);
|
|
100
|
+
// Emit output with a result
|
|
101
|
+
emitOutputMarker(fakeProc, {
|
|
102
|
+
status: 'success',
|
|
103
|
+
result: 'Here is my response',
|
|
104
|
+
newSessionId: 'session-123',
|
|
105
|
+
});
|
|
106
|
+
// Let output processing settle
|
|
107
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
108
|
+
// Fire the hard timeout (IDLE_TIMEOUT + 30s = 1830000ms)
|
|
109
|
+
await vi.advanceTimersByTimeAsync(1830000);
|
|
110
|
+
// Emit close event (as if container was stopped by the timeout)
|
|
111
|
+
fakeProc.emit('close', 137);
|
|
112
|
+
// Let the promise resolve
|
|
113
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
114
|
+
const result = await resultPromise;
|
|
115
|
+
expect(result.status).toBe('success');
|
|
116
|
+
expect(result.newSessionId).toBe('session-123');
|
|
117
|
+
expect(onOutput).toHaveBeenCalledWith(expect.objectContaining({ result: 'Here is my response' }));
|
|
118
|
+
});
|
|
119
|
+
it('timeout with no output resolves as error', async () => {
|
|
120
|
+
const onOutput = vi.fn(async () => { });
|
|
121
|
+
const resultPromise = runContainerAgent(testGroup, testInput, () => { }, onOutput);
|
|
122
|
+
// No output emitted — fire the hard timeout
|
|
123
|
+
await vi.advanceTimersByTimeAsync(1830000);
|
|
124
|
+
// Emit close event
|
|
125
|
+
fakeProc.emit('close', 137);
|
|
126
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
127
|
+
const result = await resultPromise;
|
|
128
|
+
expect(result.status).toBe('error');
|
|
129
|
+
expect(result.error).toContain('timed out');
|
|
130
|
+
expect(onOutput).not.toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
it('normal exit after output resolves as success', async () => {
|
|
133
|
+
const onOutput = vi.fn(async () => { });
|
|
134
|
+
const resultPromise = runContainerAgent(testGroup, testInput, () => { }, onOutput);
|
|
135
|
+
// Emit output
|
|
136
|
+
emitOutputMarker(fakeProc, {
|
|
137
|
+
status: 'success',
|
|
138
|
+
result: 'Done',
|
|
139
|
+
newSessionId: 'session-456',
|
|
140
|
+
});
|
|
141
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
142
|
+
// Normal exit (no timeout)
|
|
143
|
+
fakeProc.emit('close', 0);
|
|
144
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
145
|
+
const result = await resultPromise;
|
|
146
|
+
expect(result.status).toBe('success');
|
|
147
|
+
expect(result.newSessionId).toBe('session-456');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=container-runner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-runner.test.js","sourceRoot":"","sources":["../src/container-runner.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The container runtime binary name. */
|
|
2
|
+
export declare const CONTAINER_RUNTIME_BIN = "docker";
|
|
3
|
+
/** Hostname containers use to reach the host machine. */
|
|
4
|
+
export declare const CONTAINER_HOST_GATEWAY = "host.docker.internal";
|
|
5
|
+
/**
|
|
6
|
+
* Address the credential proxy binds to.
|
|
7
|
+
* Docker Desktop (macOS): 127.0.0.1 — the VM routes host.docker.internal to loopback.
|
|
8
|
+
* Docker (Linux): bind to the docker0 bridge IP so only containers can reach it,
|
|
9
|
+
* falling back to 0.0.0.0 if the interface isn't found.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PROXY_BIND_HOST: string;
|
|
12
|
+
/** CLI args needed for the container to resolve the host gateway. */
|
|
13
|
+
export declare function hostGatewayArgs(): string[];
|
|
14
|
+
/** Returns CLI args for a readonly bind mount. */
|
|
15
|
+
export declare function readonlyMountArgs(hostPath: string, containerPath: string): string[];
|
|
16
|
+
/** Returns the shell command to stop a container by name. */
|
|
17
|
+
export declare function stopContainer(name: string): string;
|
|
18
|
+
/** Ensure the container runtime is running, starting it if needed. */
|
|
19
|
+
export declare function ensureContainerRuntimeRunning(): void;
|
|
20
|
+
/** Kill orphaned NanoClaw containers from previous runs. */
|
|
21
|
+
export declare function cleanupOrphans(): void;
|
|
22
|
+
//# sourceMappingURL=container-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-runtime.d.ts","sourceRoot":"","sources":["../src/container-runtime.ts"],"names":[],"mappings":"AAUA,yCAAyC;AACzC,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QACgC,CAAC;AAmB7D,qEAAqE;AACrE,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAM1C;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,MAAM,EAAE,CAEV;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,sEAAsE;AACtE,wBAAgB,6BAA6B,IAAI,IAAI,CAmCpD;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,IAAI,IAAI,CAuBrC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container runtime abstraction for NanoClaw.
|
|
3
|
+
* All runtime-specific logic lives here so swapping runtimes means changing one file.
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
/** The container runtime binary name. */
|
|
10
|
+
export const CONTAINER_RUNTIME_BIN = 'docker';
|
|
11
|
+
/** Hostname containers use to reach the host machine. */
|
|
12
|
+
export const CONTAINER_HOST_GATEWAY = 'host.docker.internal';
|
|
13
|
+
/**
|
|
14
|
+
* Address the credential proxy binds to.
|
|
15
|
+
* Docker Desktop (macOS): 127.0.0.1 — the VM routes host.docker.internal to loopback.
|
|
16
|
+
* Docker (Linux): bind to the docker0 bridge IP so only containers can reach it,
|
|
17
|
+
* falling back to 0.0.0.0 if the interface isn't found.
|
|
18
|
+
*/
|
|
19
|
+
export const PROXY_BIND_HOST = process.env.CREDENTIAL_PROXY_HOST || detectProxyBindHost();
|
|
20
|
+
function detectProxyBindHost() {
|
|
21
|
+
if (os.platform() === 'darwin')
|
|
22
|
+
return '127.0.0.1';
|
|
23
|
+
// WSL uses Docker Desktop (same VM routing as macOS) — loopback is correct.
|
|
24
|
+
// Check /proc filesystem, not env vars — WSL_DISTRO_NAME isn't set under systemd.
|
|
25
|
+
if (fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop'))
|
|
26
|
+
return '127.0.0.1';
|
|
27
|
+
// Bare-metal Linux: bind to the docker0 bridge IP instead of 0.0.0.0
|
|
28
|
+
const ifaces = os.networkInterfaces();
|
|
29
|
+
const docker0 = ifaces['docker0'];
|
|
30
|
+
if (docker0) {
|
|
31
|
+
const ipv4 = docker0.find((a) => a.family === 'IPv4');
|
|
32
|
+
if (ipv4)
|
|
33
|
+
return ipv4.address;
|
|
34
|
+
}
|
|
35
|
+
return '0.0.0.0';
|
|
36
|
+
}
|
|
37
|
+
/** CLI args needed for the container to resolve the host gateway. */
|
|
38
|
+
export function hostGatewayArgs() {
|
|
39
|
+
// On Linux, host.docker.internal isn't built-in — add it explicitly
|
|
40
|
+
if (os.platform() === 'linux') {
|
|
41
|
+
return ['--add-host=host.docker.internal:host-gateway'];
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
/** Returns CLI args for a readonly bind mount. */
|
|
46
|
+
export function readonlyMountArgs(hostPath, containerPath) {
|
|
47
|
+
return ['-v', `${hostPath}:${containerPath}:ro`];
|
|
48
|
+
}
|
|
49
|
+
/** Returns the shell command to stop a container by name. */
|
|
50
|
+
export function stopContainer(name) {
|
|
51
|
+
return `${CONTAINER_RUNTIME_BIN} stop ${name}`;
|
|
52
|
+
}
|
|
53
|
+
/** Ensure the container runtime is running, starting it if needed. */
|
|
54
|
+
export function ensureContainerRuntimeRunning() {
|
|
55
|
+
try {
|
|
56
|
+
execSync(`${CONTAINER_RUNTIME_BIN} info`, {
|
|
57
|
+
stdio: 'pipe',
|
|
58
|
+
timeout: 10000,
|
|
59
|
+
});
|
|
60
|
+
logger.debug('Container runtime already running');
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
logger.error({ err }, 'Failed to reach container runtime');
|
|
64
|
+
console.error('\n╔════════════════════════════════════════════════════════════════╗');
|
|
65
|
+
console.error('║ FATAL: Container runtime failed to start ║');
|
|
66
|
+
console.error('║ ║');
|
|
67
|
+
console.error('║ Agents cannot run without a container runtime. To fix: ║');
|
|
68
|
+
console.error('║ 1. Ensure Docker is installed and running ║');
|
|
69
|
+
console.error('║ 2. Run: docker info ║');
|
|
70
|
+
console.error('║ 3. Restart NanoClaw ║');
|
|
71
|
+
console.error('╚════════════════════════════════════════════════════════════════╝\n');
|
|
72
|
+
throw new Error('Container runtime is required but failed to start');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Kill orphaned NanoClaw containers from previous runs. */
|
|
76
|
+
export function cleanupOrphans() {
|
|
77
|
+
try {
|
|
78
|
+
const output = execSync(`${CONTAINER_RUNTIME_BIN} ps --filter name=nanoclaw- --format '{{.Names}}'`, { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8' });
|
|
79
|
+
const orphans = output.trim().split('\n').filter(Boolean);
|
|
80
|
+
for (const name of orphans) {
|
|
81
|
+
try {
|
|
82
|
+
execSync(stopContainer(name), { stdio: 'pipe' });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* already stopped */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (orphans.length > 0) {
|
|
89
|
+
logger.info({ count: orphans.length, names: orphans }, 'Stopped orphaned containers');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
logger.warn({ err }, 'Failed to clean up orphaned containers');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=container-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-runtime.js","sourceRoot":"","sources":["../src/container-runtime.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,yCAAyC;AACzC,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AAE9C,yDAAyD;AACzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,mBAAmB,EAAE,CAAC;AAE7D,SAAS,mBAAmB;IAC1B,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC;IAEnD,4EAA4E;IAC5E,kFAAkF;IAClF,IAAI,EAAE,CAAC,UAAU,CAAC,qCAAqC,CAAC;QAAE,OAAO,WAAW,CAAC;IAE7E,qEAAqE;IACrE,MAAM,MAAM,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QACtD,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;IAChC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,eAAe;IAC7B,oEAAoE;IACpE,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC9B,OAAO,CAAC,8CAA8C,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,iBAAiB,CAC/B,QAAgB,EAChB,aAAqB;IAErB,OAAO,CAAC,IAAI,EAAE,GAAG,QAAQ,IAAI,aAAa,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,GAAG,qBAAqB,SAAS,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,6BAA6B;IAC3C,IAAI,CAAC;QACH,QAAQ,CAAC,GAAG,qBAAqB,OAAO,EAAE;YACxC,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,mCAAmC,CAAC,CAAC;QAC3D,OAAO,CAAC,KAAK,CACX,sEAAsE,CACvE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,oEAAoE,CACrE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,oEAAoE,CACrE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,oEAAoE,CACrE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,oEAAoE,CACrE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,oEAAoE,CACrE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,oEAAoE,CACrE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,sEAAsE,CACvE,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CACrB,GAAG,qBAAqB,mDAAmD,EAC3E,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CACvD,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1D,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,qBAAqB;YACvB,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EACzC,6BAA6B,CAC9B,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,wCAAwC,CAAC,CAAC;IACjE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-runtime.test.d.ts","sourceRoot":"","sources":["../src/container-runtime.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
// Mock logger
|
|
3
|
+
vi.mock('./logger.js', () => ({
|
|
4
|
+
logger: {
|
|
5
|
+
debug: vi.fn(),
|
|
6
|
+
info: vi.fn(),
|
|
7
|
+
warn: vi.fn(),
|
|
8
|
+
error: vi.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
// Mock child_process — store the mock fn so tests can configure it
|
|
12
|
+
const mockExecSync = vi.fn();
|
|
13
|
+
vi.mock('child_process', () => ({
|
|
14
|
+
execSync: (...args) => mockExecSync(...args),
|
|
15
|
+
}));
|
|
16
|
+
import { CONTAINER_RUNTIME_BIN, readonlyMountArgs, stopContainer, ensureContainerRuntimeRunning, cleanupOrphans, } from './container-runtime.js';
|
|
17
|
+
import { logger } from './logger.js';
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
// --- Pure functions ---
|
|
22
|
+
describe('readonlyMountArgs', () => {
|
|
23
|
+
it('returns -v flag with :ro suffix', () => {
|
|
24
|
+
const args = readonlyMountArgs('/host/path', '/container/path');
|
|
25
|
+
expect(args).toEqual(['-v', '/host/path:/container/path:ro']);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('stopContainer', () => {
|
|
29
|
+
it('returns stop command using CONTAINER_RUNTIME_BIN', () => {
|
|
30
|
+
expect(stopContainer('nanoclaw-test-123')).toBe(`${CONTAINER_RUNTIME_BIN} stop nanoclaw-test-123`);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
// --- ensureContainerRuntimeRunning ---
|
|
34
|
+
describe('ensureContainerRuntimeRunning', () => {
|
|
35
|
+
it('does nothing when runtime is already running', () => {
|
|
36
|
+
mockExecSync.mockReturnValueOnce('');
|
|
37
|
+
ensureContainerRuntimeRunning();
|
|
38
|
+
expect(mockExecSync).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(mockExecSync).toHaveBeenCalledWith(`${CONTAINER_RUNTIME_BIN} info`, {
|
|
40
|
+
stdio: 'pipe',
|
|
41
|
+
timeout: 10000,
|
|
42
|
+
});
|
|
43
|
+
expect(logger.debug).toHaveBeenCalledWith('Container runtime already running');
|
|
44
|
+
});
|
|
45
|
+
it('throws when docker info fails', () => {
|
|
46
|
+
mockExecSync.mockImplementationOnce(() => {
|
|
47
|
+
throw new Error('Cannot connect to the Docker daemon');
|
|
48
|
+
});
|
|
49
|
+
expect(() => ensureContainerRuntimeRunning()).toThrow('Container runtime is required but failed to start');
|
|
50
|
+
expect(logger.error).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
// --- cleanupOrphans ---
|
|
54
|
+
describe('cleanupOrphans', () => {
|
|
55
|
+
it('stops orphaned nanoclaw containers', () => {
|
|
56
|
+
// docker ps returns container names, one per line
|
|
57
|
+
mockExecSync.mockReturnValueOnce('nanoclaw-group1-111\nnanoclaw-group2-222\n');
|
|
58
|
+
// stop calls succeed
|
|
59
|
+
mockExecSync.mockReturnValue('');
|
|
60
|
+
cleanupOrphans();
|
|
61
|
+
// ps + 2 stop calls
|
|
62
|
+
expect(mockExecSync).toHaveBeenCalledTimes(3);
|
|
63
|
+
expect(mockExecSync).toHaveBeenNthCalledWith(2, `${CONTAINER_RUNTIME_BIN} stop nanoclaw-group1-111`, { stdio: 'pipe' });
|
|
64
|
+
expect(mockExecSync).toHaveBeenNthCalledWith(3, `${CONTAINER_RUNTIME_BIN} stop nanoclaw-group2-222`, { stdio: 'pipe' });
|
|
65
|
+
expect(logger.info).toHaveBeenCalledWith({ count: 2, names: ['nanoclaw-group1-111', 'nanoclaw-group2-222'] }, 'Stopped orphaned containers');
|
|
66
|
+
});
|
|
67
|
+
it('does nothing when no orphans exist', () => {
|
|
68
|
+
mockExecSync.mockReturnValueOnce('');
|
|
69
|
+
cleanupOrphans();
|
|
70
|
+
expect(mockExecSync).toHaveBeenCalledTimes(1);
|
|
71
|
+
expect(logger.info).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
it('warns and continues when ps fails', () => {
|
|
74
|
+
mockExecSync.mockImplementationOnce(() => {
|
|
75
|
+
throw new Error('docker not available');
|
|
76
|
+
});
|
|
77
|
+
cleanupOrphans(); // should not throw
|
|
78
|
+
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ err: expect.any(Error) }), 'Failed to clean up orphaned containers');
|
|
79
|
+
});
|
|
80
|
+
it('continues stopping remaining containers when one stop fails', () => {
|
|
81
|
+
mockExecSync.mockReturnValueOnce('nanoclaw-a-1\nnanoclaw-b-2\n');
|
|
82
|
+
// First stop fails
|
|
83
|
+
mockExecSync.mockImplementationOnce(() => {
|
|
84
|
+
throw new Error('already stopped');
|
|
85
|
+
});
|
|
86
|
+
// Second stop succeeds
|
|
87
|
+
mockExecSync.mockReturnValueOnce('');
|
|
88
|
+
cleanupOrphans(); // should not throw
|
|
89
|
+
expect(mockExecSync).toHaveBeenCalledTimes(3);
|
|
90
|
+
expect(logger.info).toHaveBeenCalledWith({ count: 2, names: ['nanoclaw-a-1', 'nanoclaw-b-2'] }, 'Stopped orphaned containers');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=container-runtime.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-runtime.test.js","sourceRoot":"","sources":["../src/container-runtime.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE9D,cAAc;AACd,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5B,MAAM,EAAE;QACN,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;QACd,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;KACf;CACF,CAAC,CAAC,CAAC;AAEJ,mEAAmE;AACnE,MAAM,YAAY,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAC7B,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,QAAQ,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;CACxD,CAAC,CAAC,CAAC;AAEJ,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,6BAA6B,EAC7B,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,UAAU,CAAC,GAAG,EAAE;IACd,EAAE,CAAC,aAAa,EAAE,CAAC;AACrB,CAAC,CAAC,CAAC;AAEH,yBAAyB;AAEzB,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,IAAI,GAAG,iBAAiB,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,+BAA+B,CAAC,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAC7C,GAAG,qBAAqB,yBAAyB,CAClD,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wCAAwC;AAExC,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,YAAY,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAErC,6BAA6B,EAAE,CAAC;QAEhC,MAAM,CAAC,YAAY,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,YAAY,CAAC,CAAC,oBAAoB,CAAC,GAAG,qBAAqB,OAAO,EAAE;YACzE,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,oBAAoB,CACvC,mCAAmC,CACpC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,YAAY,CAAC,sBAAsB,CAAC,GAAG,EAAE;YACvC,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,GAAG,EAAE,CAAC,6BAA6B,EAAE,CAAC,CAAC,OAAO,CACnD,mDAAmD,CACpD,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,gBAAgB,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,yBAAyB;AAEzB,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,kDAAkD;QAClD,YAAY,CAAC,mBAAmB,CAC9B,4CAA4C,CAC7C,CAAC;QACF,qBAAqB;QACrB,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAEjC,cAAc,EAAE,CAAC;QAEjB,oBAAoB;QACpB,MAAM,CAAC,YAAY,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,YAAY,CAAC,CAAC,uBAAuB,CAC1C,CAAC,EACD,GAAG,qBAAqB,2BAA2B,EACnD,EAAE,KAAK,EAAE,MAAM,EAAE,CAClB,CAAC;QACF,MAAM,CAAC,YAAY,CAAC,CAAC,uBAAuB,CAC1C,CAAC,EACD,GAAG,qBAAqB,2BAA2B,EACnD,EAAE,KAAK,EAAE,MAAM,EAAE,CAClB,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,oBAAoB,CACtC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,EAAE,EACnE,6BAA6B,CAC9B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,YAAY,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAErC,cAAc,EAAE,CAAC;QAEjB,MAAM,CAAC,YAAY,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,YAAY,CAAC,sBAAsB,CAAC,GAAG,EAAE;YACvC,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,cAAc,EAAE,CAAC,CAAC,mBAAmB;QAErC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,oBAAoB,CACtC,MAAM,CAAC,gBAAgB,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EACnD,wCAAwC,CACzC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,YAAY,CAAC,mBAAmB,CAAC,8BAA8B,CAAC,CAAC;QACjE,mBAAmB;QACnB,YAAY,CAAC,sBAAsB,CAAC,GAAG,EAAE;YACvC,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,uBAAuB;QACvB,YAAY,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAErC,cAAc,EAAE,CAAC,CAAC,mBAAmB;QAErC,MAAM,CAAC,YAAY,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,oBAAoB,CACtC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC,EAAE,EACrD,6BAA6B,CAC9B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential proxy for container isolation.
|
|
3
|
+
* Containers connect here instead of directly to the Anthropic API.
|
|
4
|
+
* The proxy injects real credentials so containers never see them.
|
|
5
|
+
*
|
|
6
|
+
* Two auth modes:
|
|
7
|
+
* API key: Proxy injects x-api-key on every request.
|
|
8
|
+
* OAuth: Container CLI exchanges its placeholder token for a temp
|
|
9
|
+
* API key via /api/oauth/claude_cli/create_api_key.
|
|
10
|
+
* Proxy injects real OAuth token on that exchange request;
|
|
11
|
+
* subsequent requests carry the temp key which is valid as-is.
|
|
12
|
+
*/
|
|
13
|
+
import { Server } from 'http';
|
|
14
|
+
export type AuthMode = 'api-key' | 'oauth';
|
|
15
|
+
export interface ProxyConfig {
|
|
16
|
+
authMode: AuthMode;
|
|
17
|
+
}
|
|
18
|
+
export declare function startCredentialProxy(port: number, host?: string): Promise<Server>;
|
|
19
|
+
/** Detect which auth mode the host is configured for. */
|
|
20
|
+
export declare function detectAuthMode(): AuthMode;
|
|
21
|
+
//# sourceMappingURL=credential-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-proxy.d.ts","sourceRoot":"","sources":["../src/credential-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAgB,MAAM,EAAE,MAAM,MAAM,CAAC;AAO5C,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAE3C,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,SAAc,GACjB,OAAO,CAAC,MAAM,CAAC,CA0FjB;AAED,yDAAyD;AACzD,wBAAgB,cAAc,IAAI,QAAQ,CAGzC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential proxy for container isolation.
|
|
3
|
+
* Containers connect here instead of directly to the Anthropic API.
|
|
4
|
+
* The proxy injects real credentials so containers never see them.
|
|
5
|
+
*
|
|
6
|
+
* Two auth modes:
|
|
7
|
+
* API key: Proxy injects x-api-key on every request.
|
|
8
|
+
* OAuth: Container CLI exchanges its placeholder token for a temp
|
|
9
|
+
* API key via /api/oauth/claude_cli/create_api_key.
|
|
10
|
+
* Proxy injects real OAuth token on that exchange request;
|
|
11
|
+
* subsequent requests carry the temp key which is valid as-is.
|
|
12
|
+
*/
|
|
13
|
+
import { createServer } from 'http';
|
|
14
|
+
import { request as httpsRequest } from 'https';
|
|
15
|
+
import { request as httpRequest } from 'http';
|
|
16
|
+
import { readEnvFile } from './env.js';
|
|
17
|
+
import { logger } from './logger.js';
|
|
18
|
+
export function startCredentialProxy(port, host = '127.0.0.1') {
|
|
19
|
+
const secrets = readEnvFile([
|
|
20
|
+
'ANTHROPIC_API_KEY',
|
|
21
|
+
'CLAUDE_CODE_OAUTH_TOKEN',
|
|
22
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
23
|
+
'ANTHROPIC_BASE_URL',
|
|
24
|
+
]);
|
|
25
|
+
const authMode = secrets.ANTHROPIC_API_KEY ? 'api-key' : 'oauth';
|
|
26
|
+
const oauthToken = secrets.CLAUDE_CODE_OAUTH_TOKEN || secrets.ANTHROPIC_AUTH_TOKEN;
|
|
27
|
+
const upstreamUrl = new URL(secrets.ANTHROPIC_BASE_URL || 'https://api.anthropic.com');
|
|
28
|
+
const isHttps = upstreamUrl.protocol === 'https:';
|
|
29
|
+
const makeRequest = isHttps ? httpsRequest : httpRequest;
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const server = createServer((req, res) => {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
req.on('data', (c) => chunks.push(c));
|
|
34
|
+
req.on('end', () => {
|
|
35
|
+
const body = Buffer.concat(chunks);
|
|
36
|
+
const headers = {
|
|
37
|
+
...req.headers,
|
|
38
|
+
host: upstreamUrl.host,
|
|
39
|
+
'content-length': body.length,
|
|
40
|
+
};
|
|
41
|
+
// Strip hop-by-hop headers that must not be forwarded by proxies
|
|
42
|
+
delete headers['connection'];
|
|
43
|
+
delete headers['keep-alive'];
|
|
44
|
+
delete headers['transfer-encoding'];
|
|
45
|
+
if (authMode === 'api-key') {
|
|
46
|
+
// API key mode: inject x-api-key on every request
|
|
47
|
+
delete headers['x-api-key'];
|
|
48
|
+
headers['x-api-key'] = secrets.ANTHROPIC_API_KEY;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// OAuth mode: replace placeholder Bearer token with the real one
|
|
52
|
+
// only when the container actually sends an Authorization header
|
|
53
|
+
// (exchange request + auth probes). Post-exchange requests use
|
|
54
|
+
// x-api-key only, so they pass through without token injection.
|
|
55
|
+
if (headers['authorization']) {
|
|
56
|
+
delete headers['authorization'];
|
|
57
|
+
if (oauthToken) {
|
|
58
|
+
headers['authorization'] = `Bearer ${oauthToken}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const upstream = makeRequest({
|
|
63
|
+
hostname: upstreamUrl.hostname,
|
|
64
|
+
port: upstreamUrl.port || (isHttps ? 443 : 80),
|
|
65
|
+
path: req.url,
|
|
66
|
+
method: req.method,
|
|
67
|
+
headers,
|
|
68
|
+
}, (upRes) => {
|
|
69
|
+
res.writeHead(upRes.statusCode, upRes.headers);
|
|
70
|
+
upRes.pipe(res);
|
|
71
|
+
});
|
|
72
|
+
upstream.on('error', (err) => {
|
|
73
|
+
logger.error({ err, url: req.url }, 'Credential proxy upstream error');
|
|
74
|
+
if (!res.headersSent) {
|
|
75
|
+
res.writeHead(502);
|
|
76
|
+
res.end('Bad Gateway');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
upstream.write(body);
|
|
80
|
+
upstream.end();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
server.listen(port, host, () => {
|
|
84
|
+
logger.info({ port, host, authMode }, 'Credential proxy started');
|
|
85
|
+
resolve(server);
|
|
86
|
+
});
|
|
87
|
+
server.on('error', reject);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** Detect which auth mode the host is configured for. */
|
|
91
|
+
export function detectAuthMode() {
|
|
92
|
+
const secrets = readEnvFile(['ANTHROPIC_API_KEY']);
|
|
93
|
+
return secrets.ANTHROPIC_API_KEY ? 'api-key' : 'oauth';
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=credential-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-proxy.js","sourceRoot":"","sources":["../src/credential-proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-proxy.test.d.ts","sourceRoot":"","sources":["../src/credential-proxy.test.ts"],"names":[],"mappings":""}
|