ats-daemon 1.0.0
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/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/connection.d.ts +37 -0
- package/dist/connection.js +153 -0
- package/dist/connection.js.map +1 -0
- package/dist/docker/checker.d.ts +50 -0
- package/dist/docker/checker.js +131 -0
- package/dist/docker/checker.js.map +1 -0
- package/dist/docker/index.d.ts +7 -0
- package/dist/docker/index.js +6 -0
- package/dist/docker/index.js.map +1 -0
- package/dist/docker/manager.d.ts +138 -0
- package/dist/docker/manager.js +564 -0
- package/dist/docker/manager.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +337 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/protocol.d.ts +217 -0
- package/dist/shared/protocol.js +31 -0
- package/dist/shared/protocol.js.map +1 -0
- package/dist/shared/types.d.ts +11 -0
- package/dist/shared/types.js +9 -0
- package/dist/shared/types.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 MFS-code
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# ats-daemon
|
|
2
|
+
|
|
3
|
+
Companion daemon for [Agent Testing Suite](https://github.com/MFS-code/ralph-testing-suite). Runs on your local machine, connects to the cloud server, manages Docker containers for agent test runs, and streams results back in real time.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Node.js 20+**
|
|
8
|
+
- **Docker** installed and running (Docker Desktop or Docker Engine)
|
|
9
|
+
- The `agent-runner:latest` Docker image built locally (see [setup instructions](https://github.com/MFS-code/ralph-testing-suite#quick-start))
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g ats-daemon
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
1. Open the Agent Testing Suite web UI
|
|
20
|
+
2. Create a test run and copy the pairing token
|
|
21
|
+
3. Run the daemon:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ats-daemon --token <YOUR_PAIRING_TOKEN>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The daemon will:
|
|
28
|
+
- Verify Docker is installed and running
|
|
29
|
+
- Connect to the server via WebSocket
|
|
30
|
+
- Wait for test commands
|
|
31
|
+
- Spin up Docker containers for each agent setup
|
|
32
|
+
- Stream container output back to the server for live metrics
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
USAGE:
|
|
38
|
+
ats-daemon --token <TOKEN> [OPTIONS]
|
|
39
|
+
|
|
40
|
+
REQUIRED:
|
|
41
|
+
--token <TOKEN> Pairing token from the web UI
|
|
42
|
+
|
|
43
|
+
OPTIONS:
|
|
44
|
+
--server <URL> Server URL (default: ws://localhost:3001)
|
|
45
|
+
--help, -h Show this help message
|
|
46
|
+
--version, -v Show version
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API Keys
|
|
50
|
+
|
|
51
|
+
API keys can be provided in two ways:
|
|
52
|
+
|
|
53
|
+
1. **Environment variables** on the machine running the daemon:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
57
|
+
export OPENAI_API_KEY=sk-...
|
|
58
|
+
export GOOGLE_API_KEY=AI...
|
|
59
|
+
export CURSOR_API_KEY=...
|
|
60
|
+
ats-daemon --token abc123
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. **Via the web UI** -- keys entered in the browser are forwarded to the daemon over the WebSocket connection and injected into containers. They are never persisted on the server.
|
|
64
|
+
|
|
65
|
+
Keys from the web UI take precedence over local environment variables.
|
|
66
|
+
|
|
67
|
+
## What It Does
|
|
68
|
+
|
|
69
|
+
The daemon is a thin orchestration layer:
|
|
70
|
+
|
|
71
|
+
- Receives test configurations (setup files, model choice, instance count) from the server
|
|
72
|
+
- Creates isolated Docker containers using the `agent-runner` image
|
|
73
|
+
- Mounts setup files into each container
|
|
74
|
+
- Streams container stdout/stderr back to the server line-by-line
|
|
75
|
+
- Reports container lifecycle events (started, stopped, crashed, OOM)
|
|
76
|
+
- Enforces resource limits (4GB memory, 2 CPUs, 100 PIDs per container)
|
|
77
|
+
- Handles graceful shutdown on SIGINT/SIGTERM
|
|
78
|
+
|
|
79
|
+
## Security
|
|
80
|
+
|
|
81
|
+
- Containers run with enforced resource limits (memory, CPU, PIDs)
|
|
82
|
+
- Setup files are written to a temp directory with path traversal protection
|
|
83
|
+
- API keys are only held in memory, never written to disk by the daemon
|
|
84
|
+
- Containers use an isolated Docker network (`agent-network`)
|
|
85
|
+
- The daemon connects outbound to the server (no inbound ports needed)
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Client - connects the daemon to the cloud server
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Outbound connection to server (user's machine -> cloud)
|
|
6
|
+
* - Automatic reconnection with exponential backoff
|
|
7
|
+
* - Sending daemon events (container output, status changes)
|
|
8
|
+
* - Receiving server commands (start test, stop instance, etc.)
|
|
9
|
+
*/
|
|
10
|
+
import type { ServerToDaemonMessage, DaemonToServerMessage } from './shared/protocol.js';
|
|
11
|
+
export interface ConnectionConfig {
|
|
12
|
+
/** Server URL (e.g., wss://my-server.example.com or ws://localhost:3001) */
|
|
13
|
+
serverUrl: string;
|
|
14
|
+
/** Pairing token from the web UI */
|
|
15
|
+
token: string;
|
|
16
|
+
/** Which API keys are available locally */
|
|
17
|
+
localApiKeys: {
|
|
18
|
+
anthropic: boolean;
|
|
19
|
+
openai: boolean;
|
|
20
|
+
google: boolean;
|
|
21
|
+
cursor: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
25
|
+
export interface ServerConnection {
|
|
26
|
+
/** Current connection state */
|
|
27
|
+
state: ConnectionState;
|
|
28
|
+
/** Send a message to the server */
|
|
29
|
+
send: (message: DaemonToServerMessage) => void;
|
|
30
|
+
/** Register a handler for incoming server commands */
|
|
31
|
+
onCommand: (handler: (message: ServerToDaemonMessage) => void) => void;
|
|
32
|
+
/** Register a handler for connection state changes */
|
|
33
|
+
onStateChange: (handler: (state: ConnectionState) => void) => void;
|
|
34
|
+
/** Gracefully close the connection */
|
|
35
|
+
close: () => void;
|
|
36
|
+
}
|
|
37
|
+
export declare function connectToServer(config: ConnectionConfig): ServerConnection;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Client - connects the daemon to the cloud server
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Outbound connection to server (user's machine -> cloud)
|
|
6
|
+
* - Automatic reconnection with exponential backoff
|
|
7
|
+
* - Sending daemon events (container output, status changes)
|
|
8
|
+
* - Receiving server commands (start test, stop instance, etc.)
|
|
9
|
+
*/
|
|
10
|
+
import WebSocket from 'ws';
|
|
11
|
+
import { DAEMON_WS_PATH, PROTOCOL_VERSION } from './shared/protocol.js';
|
|
12
|
+
import { getDockerStatus } from './docker/checker.js';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Connection implementation
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const RECONNECT_BASE_MS = 1000;
|
|
18
|
+
const RECONNECT_MAX_MS = 30000;
|
|
19
|
+
const HEARTBEAT_INTERVAL_MS = 15000;
|
|
20
|
+
export function connectToServer(config) {
|
|
21
|
+
let ws = null;
|
|
22
|
+
let state = 'disconnected';
|
|
23
|
+
let reconnectAttempt = 0;
|
|
24
|
+
let reconnectTimer = null;
|
|
25
|
+
let heartbeatTimer = null;
|
|
26
|
+
let intentionalClose = false;
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
const commandHandlers = [];
|
|
29
|
+
const stateHandlers = [];
|
|
30
|
+
function setState(newState) {
|
|
31
|
+
if (state === newState)
|
|
32
|
+
return;
|
|
33
|
+
state = newState;
|
|
34
|
+
for (const handler of stateHandlers) {
|
|
35
|
+
try {
|
|
36
|
+
handler(newState);
|
|
37
|
+
}
|
|
38
|
+
catch { /* ignore handler errors */ }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function send(message) {
|
|
42
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
43
|
+
ws.send(JSON.stringify(message));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function buildHello() {
|
|
47
|
+
const dockerStatus = await getDockerStatus();
|
|
48
|
+
return {
|
|
49
|
+
type: 'daemon_hello',
|
|
50
|
+
daemonVersion: PROTOCOL_VERSION,
|
|
51
|
+
docker: {
|
|
52
|
+
installed: dockerStatus.installed,
|
|
53
|
+
running: dockerStatus.running,
|
|
54
|
+
version: dockerStatus.version,
|
|
55
|
+
},
|
|
56
|
+
localApiKeys: config.localApiKeys,
|
|
57
|
+
platform: os.platform(),
|
|
58
|
+
arch: os.arch(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function startHeartbeat() {
|
|
62
|
+
stopHeartbeat();
|
|
63
|
+
heartbeatTimer = setInterval(() => {
|
|
64
|
+
const pong = {
|
|
65
|
+
type: 'daemon_pong',
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
uptimeMs: Date.now() - startTime,
|
|
68
|
+
activeContainers: 0, // Will be updated by the caller
|
|
69
|
+
};
|
|
70
|
+
send(pong);
|
|
71
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
72
|
+
}
|
|
73
|
+
function stopHeartbeat() {
|
|
74
|
+
if (heartbeatTimer) {
|
|
75
|
+
clearInterval(heartbeatTimer);
|
|
76
|
+
heartbeatTimer = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function connect() {
|
|
80
|
+
if (intentionalClose)
|
|
81
|
+
return;
|
|
82
|
+
setState('connecting');
|
|
83
|
+
const url = `${config.serverUrl}${DAEMON_WS_PATH}?token=${encodeURIComponent(config.token)}`;
|
|
84
|
+
ws = new WebSocket(url);
|
|
85
|
+
ws.on('open', async () => {
|
|
86
|
+
console.log('[DAEMON] Connected to server');
|
|
87
|
+
reconnectAttempt = 0;
|
|
88
|
+
setState('connected');
|
|
89
|
+
// Send hello
|
|
90
|
+
const hello = await buildHello();
|
|
91
|
+
send(hello);
|
|
92
|
+
startHeartbeat();
|
|
93
|
+
});
|
|
94
|
+
ws.on('message', (data) => {
|
|
95
|
+
try {
|
|
96
|
+
const message = JSON.parse(data.toString());
|
|
97
|
+
for (const handler of commandHandlers) {
|
|
98
|
+
try {
|
|
99
|
+
handler(message);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error('[DAEMON] Command handler error:', err);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
console.error('[DAEMON] Failed to parse server message');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
ws.on('close', (code, reason) => {
|
|
111
|
+
stopHeartbeat();
|
|
112
|
+
if (intentionalClose) {
|
|
113
|
+
setState('disconnected');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(`[DAEMON] Disconnected (code=${code}, reason=${reason?.toString() || 'none'}). Reconnecting...`);
|
|
117
|
+
setState('disconnected');
|
|
118
|
+
scheduleReconnect();
|
|
119
|
+
});
|
|
120
|
+
ws.on('error', (err) => {
|
|
121
|
+
console.error('[DAEMON] WebSocket error:', err.message);
|
|
122
|
+
setState('error');
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function scheduleReconnect() {
|
|
126
|
+
if (intentionalClose)
|
|
127
|
+
return;
|
|
128
|
+
const delay = Math.min(RECONNECT_BASE_MS * Math.pow(2, reconnectAttempt), RECONNECT_MAX_MS);
|
|
129
|
+
reconnectAttempt++;
|
|
130
|
+
console.log(`[DAEMON] Reconnecting in ${delay}ms (attempt ${reconnectAttempt})...`);
|
|
131
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
132
|
+
}
|
|
133
|
+
// Start initial connection
|
|
134
|
+
connect();
|
|
135
|
+
return {
|
|
136
|
+
get state() { return state; },
|
|
137
|
+
send,
|
|
138
|
+
onCommand(handler) { commandHandlers.push(handler); },
|
|
139
|
+
onStateChange(handler) { stateHandlers.push(handler); },
|
|
140
|
+
close() {
|
|
141
|
+
intentionalClose = true;
|
|
142
|
+
stopHeartbeat();
|
|
143
|
+
if (reconnectTimer)
|
|
144
|
+
clearTimeout(reconnectTimer);
|
|
145
|
+
if (ws) {
|
|
146
|
+
ws.close(1000, 'Daemon shutting down');
|
|
147
|
+
ws = null;
|
|
148
|
+
}
|
|
149
|
+
setState('disconnected');
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Availability Checker (Daemon version)
|
|
3
|
+
*
|
|
4
|
+
* Checks Docker availability on the USER's local machine.
|
|
5
|
+
* Unlike the server version, this connects to the default Docker socket
|
|
6
|
+
* (/var/run/docker.sock) since the daemon runs on the user's machine
|
|
7
|
+
* where Docker Desktop / Docker Engine is installed.
|
|
8
|
+
*
|
|
9
|
+
* No DOCKER_HOST gating here -- the user's local Docker IS the target.
|
|
10
|
+
*/
|
|
11
|
+
import Docker from 'dockerode';
|
|
12
|
+
export interface DockerStatus {
|
|
13
|
+
installed: boolean;
|
|
14
|
+
running: boolean;
|
|
15
|
+
version: string | null;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a dockerode instance using default connection.
|
|
20
|
+
* On Linux: /var/run/docker.sock
|
|
21
|
+
* On macOS: /var/run/docker.sock (Docker Desktop)
|
|
22
|
+
* On Windows: //./pipe/docker_engine (Docker Desktop)
|
|
23
|
+
*
|
|
24
|
+
* dockerode auto-detects the right socket for the platform.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createDockerClient(): Docker;
|
|
27
|
+
/**
|
|
28
|
+
* Check if Docker CLI is installed
|
|
29
|
+
*/
|
|
30
|
+
export declare function isDockerInstalled(): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if Docker daemon is running
|
|
33
|
+
*/
|
|
34
|
+
export declare function isDockerRunning(): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Get Docker version string
|
|
37
|
+
*/
|
|
38
|
+
export declare function getDockerVersion(): Promise<string | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Get comprehensive Docker status on the local machine
|
|
41
|
+
*/
|
|
42
|
+
export declare function getDockerStatus(): Promise<DockerStatus>;
|
|
43
|
+
/**
|
|
44
|
+
* Verify Docker is available, throw if not
|
|
45
|
+
*/
|
|
46
|
+
export declare function verifyDockerAvailable(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Check if the agent-runner image exists locally
|
|
49
|
+
*/
|
|
50
|
+
export declare function isAgentRunnerImageAvailable(): Promise<boolean>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Availability Checker (Daemon version)
|
|
3
|
+
*
|
|
4
|
+
* Checks Docker availability on the USER's local machine.
|
|
5
|
+
* Unlike the server version, this connects to the default Docker socket
|
|
6
|
+
* (/var/run/docker.sock) since the daemon runs on the user's machine
|
|
7
|
+
* where Docker Desktop / Docker Engine is installed.
|
|
8
|
+
*
|
|
9
|
+
* No DOCKER_HOST gating here -- the user's local Docker IS the target.
|
|
10
|
+
*/
|
|
11
|
+
import { exec } from 'child_process';
|
|
12
|
+
import { promisify } from 'util';
|
|
13
|
+
import Docker from 'dockerode';
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
const COMMAND_TIMEOUT_MS = 5000;
|
|
16
|
+
const DOCKER_PING_TIMEOUT_MS = 3000;
|
|
17
|
+
/**
|
|
18
|
+
* Create a dockerode instance using default connection.
|
|
19
|
+
* On Linux: /var/run/docker.sock
|
|
20
|
+
* On macOS: /var/run/docker.sock (Docker Desktop)
|
|
21
|
+
* On Windows: //./pipe/docker_engine (Docker Desktop)
|
|
22
|
+
*
|
|
23
|
+
* dockerode auto-detects the right socket for the platform.
|
|
24
|
+
*/
|
|
25
|
+
export function createDockerClient() {
|
|
26
|
+
return new Docker();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Ping the local Docker daemon
|
|
30
|
+
*/
|
|
31
|
+
async function pingDockerDaemon() {
|
|
32
|
+
const docker = createDockerClient();
|
|
33
|
+
const timeout = new Promise((_, reject) => {
|
|
34
|
+
setTimeout(() => reject(new Error('Docker ping timeout')), DOCKER_PING_TIMEOUT_MS);
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
await Promise.race([docker.ping(), timeout]);
|
|
38
|
+
return { running: true };
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
running: false,
|
|
43
|
+
error: error instanceof Error ? error.message : String(error),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if Docker CLI is installed
|
|
49
|
+
*/
|
|
50
|
+
export async function isDockerInstalled() {
|
|
51
|
+
try {
|
|
52
|
+
await execAsync('docker --version', { timeout: COMMAND_TIMEOUT_MS });
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if Docker daemon is running
|
|
61
|
+
*/
|
|
62
|
+
export async function isDockerRunning() {
|
|
63
|
+
const status = await pingDockerDaemon();
|
|
64
|
+
return status.running;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get Docker version string
|
|
68
|
+
*/
|
|
69
|
+
export async function getDockerVersion() {
|
|
70
|
+
try {
|
|
71
|
+
const { stdout } = await execAsync('docker --version', { timeout: COMMAND_TIMEOUT_MS });
|
|
72
|
+
const match = stdout.match(/Docker version ([^\s,]+)/);
|
|
73
|
+
if (match && match[1]) {
|
|
74
|
+
return match[1];
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get comprehensive Docker status on the local machine
|
|
84
|
+
*/
|
|
85
|
+
export async function getDockerStatus() {
|
|
86
|
+
const status = {
|
|
87
|
+
installed: false,
|
|
88
|
+
running: false,
|
|
89
|
+
version: null,
|
|
90
|
+
};
|
|
91
|
+
status.installed = await isDockerInstalled();
|
|
92
|
+
if (!status.installed) {
|
|
93
|
+
status.error = 'Docker is not installed. Install Docker Desktop from https://docker.com/get-started';
|
|
94
|
+
return status;
|
|
95
|
+
}
|
|
96
|
+
status.version = await getDockerVersion();
|
|
97
|
+
const daemonStatus = await pingDockerDaemon();
|
|
98
|
+
status.running = daemonStatus.running;
|
|
99
|
+
if (!status.running) {
|
|
100
|
+
const detail = daemonStatus.error ? ` (${daemonStatus.error})` : '';
|
|
101
|
+
status.error = `Docker daemon is not running. Start Docker Desktop and try again.${detail}`;
|
|
102
|
+
return status;
|
|
103
|
+
}
|
|
104
|
+
return status;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Verify Docker is available, throw if not
|
|
108
|
+
*/
|
|
109
|
+
export async function verifyDockerAvailable() {
|
|
110
|
+
const status = await getDockerStatus();
|
|
111
|
+
if (!status.installed) {
|
|
112
|
+
throw new Error(status.error || 'Docker is not installed.');
|
|
113
|
+
}
|
|
114
|
+
if (!status.running) {
|
|
115
|
+
throw new Error(status.error || 'Docker daemon is not running.');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if the agent-runner image exists locally
|
|
120
|
+
*/
|
|
121
|
+
export async function isAgentRunnerImageAvailable() {
|
|
122
|
+
const docker = createDockerClient();
|
|
123
|
+
try {
|
|
124
|
+
await docker.getImage('agent-runner:latest').inspect();
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=checker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checker.js","sourceRoot":"","sources":["../../src/docker/checker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Docker module exports
|
|
3
|
+
*/
|
|
4
|
+
export { createDockerClient, isDockerInstalled, isDockerRunning, getDockerVersion, getDockerStatus, verifyDockerAvailable, isAgentRunnerImageAvailable, } from './checker.js';
|
|
5
|
+
export type { DockerStatus } from './checker.js';
|
|
6
|
+
export { ContainerManager, DockerUnavailableError } from './manager.js';
|
|
7
|
+
export type { ContainerConfig, ContainerInfo, ContainerEvent, ContainerEventType, ContainerHealthStatus, } from './manager.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Docker module exports
|
|
3
|
+
*/
|
|
4
|
+
export { createDockerClient, isDockerInstalled, isDockerRunning, getDockerVersion, getDockerStatus, verifyDockerAvailable, isAgentRunnerImageAvailable, } from './checker.js';
|
|
5
|
+
export { ContainerManager, DockerUnavailableError } from './manager.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/docker/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container Manager (Daemon version)
|
|
3
|
+
*
|
|
4
|
+
* Manages Docker container lifecycle on the user's local machine.
|
|
5
|
+
* - Connects to local Docker (default socket)
|
|
6
|
+
* - Streams output back to the cloud server via a callback
|
|
7
|
+
* - Receives commands from the server, not directly from the UI
|
|
8
|
+
* - Subscribes to Docker events for OOM / crash detection
|
|
9
|
+
*/
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import type { ModelId, InstanceStatus, SetupId, ProviderMode } from '../shared/types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Error thrown when Docker is not available
|
|
14
|
+
*/
|
|
15
|
+
export declare class DockerUnavailableError extends Error {
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
export interface ContainerConfig {
|
|
19
|
+
instanceId: string;
|
|
20
|
+
setupId: SetupId;
|
|
21
|
+
setupName: string;
|
|
22
|
+
model: ModelId;
|
|
23
|
+
providerMode: ProviderMode;
|
|
24
|
+
/** Absolute path to entrypoint / wrapper scripts */
|
|
25
|
+
scriptsPath?: string;
|
|
26
|
+
/** Absolute path to the directory containing setup files */
|
|
27
|
+
filesPath: string;
|
|
28
|
+
/** Absolute path for container output */
|
|
29
|
+
outputPath: string;
|
|
30
|
+
apiKeys: {
|
|
31
|
+
anthropic?: string;
|
|
32
|
+
openai?: string;
|
|
33
|
+
google?: string;
|
|
34
|
+
cursor?: string;
|
|
35
|
+
};
|
|
36
|
+
gitRemoteUrl?: string;
|
|
37
|
+
networkName?: string;
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface ContainerInfo {
|
|
41
|
+
instanceId: string;
|
|
42
|
+
setupId: SetupId;
|
|
43
|
+
containerId: string;
|
|
44
|
+
status: InstanceStatus;
|
|
45
|
+
startTime?: number;
|
|
46
|
+
endTime?: number;
|
|
47
|
+
exitCode?: number;
|
|
48
|
+
exitType?: 'complete' | 'timeout' | 'crash' | 'stopped';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Container health status with resource usage
|
|
52
|
+
* Used for periodic health check logging
|
|
53
|
+
*/
|
|
54
|
+
export interface ContainerHealthStatus {
|
|
55
|
+
instanceId: string;
|
|
56
|
+
containerId: string;
|
|
57
|
+
status: InstanceStatus;
|
|
58
|
+
uptimeMs: number;
|
|
59
|
+
memoryUsageBytes?: number;
|
|
60
|
+
memoryLimitBytes?: number;
|
|
61
|
+
memoryPercent?: number;
|
|
62
|
+
cpuPercent?: number;
|
|
63
|
+
}
|
|
64
|
+
export type ContainerEventType = 'created' | 'started' | 'output' | 'commit' | 'status_change' | 'stopped' | 'error' | 'cleanup';
|
|
65
|
+
export interface ContainerEvent {
|
|
66
|
+
type: ContainerEventType;
|
|
67
|
+
instanceId: string;
|
|
68
|
+
setupId: SetupId;
|
|
69
|
+
containerId: string;
|
|
70
|
+
timestamp: number;
|
|
71
|
+
data?: unknown;
|
|
72
|
+
}
|
|
73
|
+
export declare class ContainerManager extends EventEmitter {
|
|
74
|
+
private docker;
|
|
75
|
+
private containers;
|
|
76
|
+
private containerInfo;
|
|
77
|
+
private timeouts;
|
|
78
|
+
private isListeningToEvents;
|
|
79
|
+
private constructor();
|
|
80
|
+
/**
|
|
81
|
+
* Factory – verifies Docker is available before returning a manager
|
|
82
|
+
*/
|
|
83
|
+
static create(): Promise<ContainerManager>;
|
|
84
|
+
/**
|
|
85
|
+
* Ping Docker daemon
|
|
86
|
+
*/
|
|
87
|
+
ping(): Promise<boolean>;
|
|
88
|
+
/**
|
|
89
|
+
* Create and start a container, streaming output via onOutput callback
|
|
90
|
+
*/
|
|
91
|
+
runContainer(config: ContainerConfig, onOutput: (line: string) => void): Promise<ContainerInfo>;
|
|
92
|
+
stopContainer(instanceId: string): Promise<void>;
|
|
93
|
+
pauseContainer(instanceId: string): Promise<boolean>;
|
|
94
|
+
resumeContainer(instanceId: string): Promise<boolean>;
|
|
95
|
+
cleanupContainer(instanceId: string): Promise<void>;
|
|
96
|
+
cleanupAll(): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Get resource usage stats for a running container
|
|
99
|
+
*/
|
|
100
|
+
getResourceUsage(instanceId: string): Promise<{
|
|
101
|
+
memoryUsageBytes: number;
|
|
102
|
+
memoryLimitBytes: number;
|
|
103
|
+
memoryPercent: number;
|
|
104
|
+
cpuPercent: number;
|
|
105
|
+
} | null>;
|
|
106
|
+
getContainerInfo(instanceId: string): ContainerInfo | undefined;
|
|
107
|
+
getAllContainerInfo(): ContainerInfo[];
|
|
108
|
+
getActiveCount(): number;
|
|
109
|
+
/**
|
|
110
|
+
* Check if a container is paused via Docker inspect
|
|
111
|
+
*/
|
|
112
|
+
isContainerPaused(instanceId: string): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Subscribe to Docker events for OOM / crash detection
|
|
115
|
+
*/
|
|
116
|
+
subscribeToDockerEvents(): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Get health status for all containers including resource usage
|
|
119
|
+
*/
|
|
120
|
+
getContainerHealthStatus(): Promise<ContainerHealthStatus[]>;
|
|
121
|
+
/**
|
|
122
|
+
* Parse commit line from git hook output
|
|
123
|
+
* Format: AGENT_COMMIT:hash:message:insertions deletions
|
|
124
|
+
*/
|
|
125
|
+
static parseCommitLine(line: string): {
|
|
126
|
+
hash: string;
|
|
127
|
+
message: string;
|
|
128
|
+
stats: string;
|
|
129
|
+
};
|
|
130
|
+
/** Format uptime in human-readable format */
|
|
131
|
+
static formatUptime(uptimeMs: number): string;
|
|
132
|
+
/** Format bytes in human-readable format */
|
|
133
|
+
static formatBytes(bytes: number): string;
|
|
134
|
+
private monitorCompletion;
|
|
135
|
+
private handleTimeout;
|
|
136
|
+
private clearTimeout;
|
|
137
|
+
private emitEvent;
|
|
138
|
+
}
|