@ash-ai/server 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/LICENSE +21 -0
- package/dist/__tests__/auth.test.d.ts +2 -0
- package/dist/__tests__/auth.test.d.ts.map +1 -0
- package/dist/__tests__/auth.test.js +111 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/backpressure.test.d.ts +2 -0
- package/dist/__tests__/backpressure.test.d.ts.map +1 -0
- package/dist/__tests__/backpressure.test.js +81 -0
- package/dist/__tests__/backpressure.test.js.map +1 -0
- package/dist/__tests__/db.test.d.ts +2 -0
- package/dist/__tests__/db.test.d.ts.map +1 -0
- package/dist/__tests__/db.test.js +111 -0
- package/dist/__tests__/db.test.js.map +1 -0
- package/dist/__tests__/files.test.d.ts +2 -0
- package/dist/__tests__/files.test.d.ts.map +1 -0
- package/dist/__tests__/files.test.js +171 -0
- package/dist/__tests__/files.test.js.map +1 -0
- package/dist/__tests__/openapi.test.d.ts +2 -0
- package/dist/__tests__/openapi.test.d.ts.map +1 -0
- package/dist/__tests__/openapi.test.js +88 -0
- package/dist/__tests__/openapi.test.js.map +1 -0
- package/dist/__tests__/pool.test.d.ts +2 -0
- package/dist/__tests__/pool.test.d.ts.map +1 -0
- package/dist/__tests__/pool.test.js +352 -0
- package/dist/__tests__/pool.test.js.map +1 -0
- package/dist/__tests__/resource-limits.test.d.ts +2 -0
- package/dist/__tests__/resource-limits.test.d.ts.map +1 -0
- package/dist/__tests__/resource-limits.test.js +119 -0
- package/dist/__tests__/resource-limits.test.js.map +1 -0
- package/dist/__tests__/sandbox-env.test.d.ts +2 -0
- package/dist/__tests__/sandbox-env.test.d.ts.map +1 -0
- package/dist/__tests__/sandbox-env.test.js +40 -0
- package/dist/__tests__/sandbox-env.test.js.map +1 -0
- package/dist/__tests__/snapshot-store.test.d.ts +2 -0
- package/dist/__tests__/snapshot-store.test.d.ts.map +1 -0
- package/dist/__tests__/snapshot-store.test.js +101 -0
- package/dist/__tests__/snapshot-store.test.js.map +1 -0
- package/dist/__tests__/state-persistence.test.d.ts +2 -0
- package/dist/__tests__/state-persistence.test.d.ts.map +1 -0
- package/dist/__tests__/state-persistence.test.js +116 -0
- package/dist/__tests__/state-persistence.test.js.map +1 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +30 -0
- package/dist/auth.js.map +1 -0
- package/dist/db/index.d.ts +54 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +91 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/pg.d.ts +31 -0
- package/dist/db/pg.d.ts.map +1 -0
- package/dist/db/pg.js +214 -0
- package/dist/db/pg.js.map +1 -0
- package/dist/db/sqlite.d.ts +30 -0
- package/dist/db/sqlite.d.ts.map +1 -0
- package/dist/db/sqlite.js +195 -0
- package/dist/db/sqlite.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +122 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/agents.d.ts +3 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +103 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +189 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/health.d.ts +5 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +72 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/runners.d.ts +8 -0
- package/dist/routes/runners.d.ts.map +1 -0
- package/dist/routes/runners.js +33 -0
- package/dist/routes/runners.js.map +1 -0
- package/dist/routes/sessions.d.ts +10 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +392 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/runner/coordinator.d.ts +52 -0
- package/dist/runner/coordinator.d.ts.map +1 -0
- package/dist/runner/coordinator.js +157 -0
- package/dist/runner/coordinator.js.map +1 -0
- package/dist/runner/local-backend.d.ts +26 -0
- package/dist/runner/local-backend.d.ts.map +1 -0
- package/dist/runner/local-backend.js +79 -0
- package/dist/runner/local-backend.js.map +1 -0
- package/dist/runner/remote-backend.d.ts +32 -0
- package/dist/runner/remote-backend.d.ts.map +1 -0
- package/dist/runner/remote-backend.js +81 -0
- package/dist/runner/remote-backend.js.map +1 -0
- package/dist/runner/runner-client.d.ts +53 -0
- package/dist/runner/runner-client.d.ts.map +1 -0
- package/dist/runner/runner-client.js +157 -0
- package/dist/runner/runner-client.js.map +1 -0
- package/dist/runner/types.d.ts +37 -0
- package/dist/runner/types.d.ts.map +1 -0
- package/dist/runner/types.js +2 -0
- package/dist/runner/types.js.map +1 -0
- package/dist/schemas.d.ts +3 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +73 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { SSE_WRITE_TIMEOUT_MS, timingEnabled, startTimer, logTiming } from '@ash-ai/shared';
|
|
5
|
+
import { getAgent, insertSession, getSession, listSessions, updateSessionStatus, updateSessionSandbox, touchSession, updateSessionRunner } from '../db/index.js';
|
|
6
|
+
import { restoreSessionState, hasPersistedState, restoreStateFromCloud } from '@ash-ai/sandbox';
|
|
7
|
+
/** Structured log line for every resume — always on, not gated by ASH_DEBUG_TIMING. */
|
|
8
|
+
function logResume(path, sessionId, agentName) {
|
|
9
|
+
process.stderr.write(JSON.stringify({
|
|
10
|
+
type: 'resume_hit',
|
|
11
|
+
path,
|
|
12
|
+
sessionId,
|
|
13
|
+
agentName,
|
|
14
|
+
ts: new Date().toISOString(),
|
|
15
|
+
}) + '\n');
|
|
16
|
+
}
|
|
17
|
+
const idParam = {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: { id: { type: 'string', format: 'uuid' } },
|
|
20
|
+
required: ['id'],
|
|
21
|
+
};
|
|
22
|
+
const sessionResponse = {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: { session: { $ref: 'Session#' } },
|
|
25
|
+
required: ['session'],
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Write an SSE frame with backpressure. If the kernel TCP send buffer is full,
|
|
29
|
+
* waits for `drain` up to SSE_WRITE_TIMEOUT_MS before giving up.
|
|
30
|
+
*/
|
|
31
|
+
export async function writeSSE(raw, frame) {
|
|
32
|
+
const canWrite = raw.write(frame);
|
|
33
|
+
if (!canWrite) {
|
|
34
|
+
let timer;
|
|
35
|
+
let onDrain;
|
|
36
|
+
const drained = await Promise.race([
|
|
37
|
+
new Promise((resolve) => {
|
|
38
|
+
onDrain = () => resolve(true);
|
|
39
|
+
raw.once('drain', onDrain);
|
|
40
|
+
}),
|
|
41
|
+
new Promise((resolve) => {
|
|
42
|
+
timer = setTimeout(() => resolve(false), SSE_WRITE_TIMEOUT_MS);
|
|
43
|
+
}),
|
|
44
|
+
]);
|
|
45
|
+
if (drained) {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
raw.removeListener('drain', onDrain);
|
|
50
|
+
throw new Error('Client write timeout — closing stream');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function sessionRoutes(app, coordinator, dataDir) {
|
|
55
|
+
// Create session — picks the best runner via coordinator
|
|
56
|
+
app.post('/api/sessions', {
|
|
57
|
+
schema: {
|
|
58
|
+
tags: ['sessions'],
|
|
59
|
+
body: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
agent: { type: 'string' },
|
|
63
|
+
},
|
|
64
|
+
required: ['agent'],
|
|
65
|
+
},
|
|
66
|
+
response: {
|
|
67
|
+
201: sessionResponse,
|
|
68
|
+
400: { $ref: 'ApiError#' },
|
|
69
|
+
404: { $ref: 'ApiError#' },
|
|
70
|
+
500: { $ref: 'ApiError#' },
|
|
71
|
+
503: { $ref: 'ApiError#' },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}, async (req, reply) => {
|
|
75
|
+
const { agent } = req.body;
|
|
76
|
+
const agentRecord = await getAgent(agent);
|
|
77
|
+
if (!agentRecord) {
|
|
78
|
+
return reply.status(404).send({ error: `Agent "${agent}" not found`, statusCode: 404 });
|
|
79
|
+
}
|
|
80
|
+
const sessionId = randomUUID();
|
|
81
|
+
try {
|
|
82
|
+
const { backend, runnerId } = coordinator.selectBackend();
|
|
83
|
+
const handle = await backend.createSandbox({
|
|
84
|
+
sessionId,
|
|
85
|
+
agentDir: agentRecord.path,
|
|
86
|
+
agentName: agent,
|
|
87
|
+
sandboxId: sessionId,
|
|
88
|
+
onOomKill: () => {
|
|
89
|
+
updateSessionStatus(sessionId, 'paused').catch((err) => console.error(`Failed to update session status on OOM: ${err}`));
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
const session = await insertSession(sessionId, agent, handle.sandboxId);
|
|
93
|
+
const effectiveRunnerId = runnerId === '__local__' ? null : runnerId;
|
|
94
|
+
await updateSessionRunner(sessionId, effectiveRunnerId);
|
|
95
|
+
await updateSessionStatus(sessionId, 'active');
|
|
96
|
+
return reply.status(201).send({ session: { ...session, status: 'active', runnerId: effectiveRunnerId } });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
100
|
+
if (msg.includes('capacity reached') || msg.includes('No runners available')) {
|
|
101
|
+
return reply.status(503).send({ error: msg, statusCode: 503 });
|
|
102
|
+
}
|
|
103
|
+
return reply.status(500).send({ error: `Failed to create session: ${msg}`, statusCode: 500 });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
// List sessions (optional ?agent=name filter)
|
|
107
|
+
app.get('/api/sessions', {
|
|
108
|
+
schema: {
|
|
109
|
+
tags: ['sessions'],
|
|
110
|
+
querystring: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
agent: { type: 'string' },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
response: {
|
|
117
|
+
200: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
sessions: { type: 'array', items: { $ref: 'Session#' } },
|
|
121
|
+
},
|
|
122
|
+
required: ['sessions'],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}, async (req, reply) => {
|
|
127
|
+
const { agent } = req.query;
|
|
128
|
+
return reply.send({ sessions: await listSessions(agent || undefined) });
|
|
129
|
+
});
|
|
130
|
+
// Get session
|
|
131
|
+
app.get('/api/sessions/:id', {
|
|
132
|
+
schema: {
|
|
133
|
+
tags: ['sessions'],
|
|
134
|
+
params: idParam,
|
|
135
|
+
response: {
|
|
136
|
+
200: sessionResponse,
|
|
137
|
+
404: { $ref: 'ApiError#' },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}, async (req, reply) => {
|
|
141
|
+
const session = await getSession(req.params.id);
|
|
142
|
+
if (!session) {
|
|
143
|
+
return reply.status(404).send({ error: 'Session not found', statusCode: 404 });
|
|
144
|
+
}
|
|
145
|
+
return reply.send({ session });
|
|
146
|
+
});
|
|
147
|
+
// Send message — routes to the correct runner for the session
|
|
148
|
+
app.post('/api/sessions/:id/messages', {
|
|
149
|
+
schema: {
|
|
150
|
+
tags: ['sessions'],
|
|
151
|
+
params: idParam,
|
|
152
|
+
body: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
content: { type: 'string' },
|
|
156
|
+
includePartialMessages: { type: 'boolean' },
|
|
157
|
+
},
|
|
158
|
+
required: ['content'],
|
|
159
|
+
},
|
|
160
|
+
response: {
|
|
161
|
+
200: {
|
|
162
|
+
type: 'string',
|
|
163
|
+
description: 'SSE stream. Events: `message` (SDK Message JSON), `error` ({error: string}), `done` ({sessionId: string})',
|
|
164
|
+
},
|
|
165
|
+
400: { $ref: 'ApiError#' },
|
|
166
|
+
404: { $ref: 'ApiError#' },
|
|
167
|
+
500: { $ref: 'ApiError#' },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
}, async (req, reply) => {
|
|
171
|
+
const timing = timingEnabled();
|
|
172
|
+
const elapsed = timing ? startTimer() : null;
|
|
173
|
+
const session = await getSession(req.params.id);
|
|
174
|
+
if (!session) {
|
|
175
|
+
return reply.status(404).send({ error: 'Session not found', statusCode: 404 });
|
|
176
|
+
}
|
|
177
|
+
if (session.status !== 'active') {
|
|
178
|
+
return reply.status(400).send({ error: `Session is ${session.status}`, statusCode: 400 });
|
|
179
|
+
}
|
|
180
|
+
const { content, includePartialMessages } = req.body;
|
|
181
|
+
let backend;
|
|
182
|
+
try {
|
|
183
|
+
backend = coordinator.getBackendForRunner(session.runnerId);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
await updateSessionStatus(session.id, 'error');
|
|
187
|
+
return reply.status(500).send({ error: 'Runner not available', statusCode: 500 });
|
|
188
|
+
}
|
|
189
|
+
const sandbox = backend.getSandbox(session.sandboxId);
|
|
190
|
+
if (!sandbox) {
|
|
191
|
+
await updateSessionStatus(session.id, 'error');
|
|
192
|
+
return reply.status(500).send({ error: 'Sandbox not found', statusCode: 500 });
|
|
193
|
+
}
|
|
194
|
+
const lookupMs = elapsed?.() ?? 0;
|
|
195
|
+
// Mark running BEFORE any async work — prevents eviction
|
|
196
|
+
backend.markRunning(session.sandboxId);
|
|
197
|
+
await touchSession(session.id);
|
|
198
|
+
// SSE response
|
|
199
|
+
reply.raw.writeHead(200, {
|
|
200
|
+
'Content-Type': 'text/event-stream',
|
|
201
|
+
'Cache-Control': 'no-cache',
|
|
202
|
+
'Connection': 'keep-alive',
|
|
203
|
+
});
|
|
204
|
+
let eventCount = 0;
|
|
205
|
+
let firstEventMs = 0;
|
|
206
|
+
try {
|
|
207
|
+
const events = backend.sendCommand(session.sandboxId, {
|
|
208
|
+
cmd: 'query',
|
|
209
|
+
prompt: content,
|
|
210
|
+
sessionId: session.id,
|
|
211
|
+
...(includePartialMessages && { includePartialMessages: true }),
|
|
212
|
+
});
|
|
213
|
+
for await (const event of events) {
|
|
214
|
+
eventCount++;
|
|
215
|
+
if (eventCount === 1 && elapsed) {
|
|
216
|
+
firstEventMs = elapsed();
|
|
217
|
+
}
|
|
218
|
+
if (event.ev === 'message') {
|
|
219
|
+
await writeSSE(reply.raw, `event: message\ndata: ${JSON.stringify(event.data)}\n\n`);
|
|
220
|
+
}
|
|
221
|
+
else if (event.ev === 'error') {
|
|
222
|
+
await writeSSE(reply.raw, `event: error\ndata: ${JSON.stringify({ error: event.error })}\n\n`);
|
|
223
|
+
}
|
|
224
|
+
else if (event.ev === 'done') {
|
|
225
|
+
await writeSSE(reply.raw, `event: done\ndata: ${JSON.stringify({ sessionId: event.sessionId })}\n\n`);
|
|
226
|
+
// Best-effort state persistence after each completed turn
|
|
227
|
+
backend.persistState(session.sandboxId, session.id, session.agentName);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
233
|
+
reply.raw.write(`event: error\ndata: ${JSON.stringify({ error: msg })}\n\n`);
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
// Mark waiting after message processing completes
|
|
237
|
+
backend.markWaiting(session.sandboxId);
|
|
238
|
+
}
|
|
239
|
+
if (elapsed) {
|
|
240
|
+
logTiming({
|
|
241
|
+
type: 'timing',
|
|
242
|
+
source: 'server',
|
|
243
|
+
sessionId: session.id,
|
|
244
|
+
sandboxId: session.sandboxId,
|
|
245
|
+
lookupMs: Math.round(lookupMs * 100) / 100,
|
|
246
|
+
firstEventMs: Math.round(firstEventMs * 100) / 100,
|
|
247
|
+
totalMs: Math.round(elapsed() * 100) / 100,
|
|
248
|
+
eventCount,
|
|
249
|
+
timestamp: new Date().toISOString(),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
reply.raw.end();
|
|
253
|
+
});
|
|
254
|
+
// Pause session
|
|
255
|
+
app.post('/api/sessions/:id/pause', {
|
|
256
|
+
schema: {
|
|
257
|
+
tags: ['sessions'],
|
|
258
|
+
params: idParam,
|
|
259
|
+
response: {
|
|
260
|
+
200: sessionResponse,
|
|
261
|
+
400: { $ref: 'ApiError#' },
|
|
262
|
+
404: { $ref: 'ApiError#' },
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
}, async (req, reply) => {
|
|
266
|
+
const session = await getSession(req.params.id);
|
|
267
|
+
if (!session) {
|
|
268
|
+
return reply.status(404).send({ error: 'Session not found', statusCode: 404 });
|
|
269
|
+
}
|
|
270
|
+
if (session.status !== 'active') {
|
|
271
|
+
return reply.status(400).send({ error: `Cannot pause session with status "${session.status}"`, statusCode: 400 });
|
|
272
|
+
}
|
|
273
|
+
// Best-effort persist state before pausing
|
|
274
|
+
try {
|
|
275
|
+
const backend = coordinator.getBackendForRunner(session.runnerId);
|
|
276
|
+
backend.persistState(session.sandboxId, session.id, session.agentName);
|
|
277
|
+
}
|
|
278
|
+
catch { /* runner may be gone */ }
|
|
279
|
+
await updateSessionStatus(session.id, 'paused');
|
|
280
|
+
return reply.send({ session: { ...session, status: 'paused' } });
|
|
281
|
+
});
|
|
282
|
+
// Resume session — may route to a different runner
|
|
283
|
+
app.post('/api/sessions/:id/resume', {
|
|
284
|
+
schema: {
|
|
285
|
+
tags: ['sessions'],
|
|
286
|
+
params: idParam,
|
|
287
|
+
response: {
|
|
288
|
+
200: sessionResponse,
|
|
289
|
+
404: { $ref: 'ApiError#' },
|
|
290
|
+
410: { $ref: 'ApiError#' },
|
|
291
|
+
500: { $ref: 'ApiError#' },
|
|
292
|
+
503: { $ref: 'ApiError#' },
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
}, async (req, reply) => {
|
|
296
|
+
const session = await getSession(req.params.id);
|
|
297
|
+
if (!session) {
|
|
298
|
+
return reply.status(404).send({ error: 'Session not found', statusCode: 404 });
|
|
299
|
+
}
|
|
300
|
+
if (session.status === 'ended') {
|
|
301
|
+
return reply.status(410).send({ error: 'Session has ended — create a new session', statusCode: 410 });
|
|
302
|
+
}
|
|
303
|
+
if (session.status === 'active') {
|
|
304
|
+
return reply.send({ session });
|
|
305
|
+
}
|
|
306
|
+
// Resumable statuses: 'paused', 'error', 'starting'
|
|
307
|
+
const agentRecord = await getAgent(session.agentName);
|
|
308
|
+
if (!agentRecord) {
|
|
309
|
+
return reply.status(404).send({ error: `Agent "${session.agentName}" not found`, statusCode: 404 });
|
|
310
|
+
}
|
|
311
|
+
// Fast path: try the same runner if sandbox is still alive
|
|
312
|
+
try {
|
|
313
|
+
const oldBackend = coordinator.getBackendForRunner(session.runnerId);
|
|
314
|
+
if (oldBackend.isSandboxAlive(session.sandboxId)) {
|
|
315
|
+
oldBackend.recordWarmHit();
|
|
316
|
+
logResume('warm', session.id, session.agentName);
|
|
317
|
+
await updateSessionStatus(session.id, 'active');
|
|
318
|
+
return reply.send({ session: { ...session, status: 'active' } });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch { /* runner gone — cold path */ }
|
|
322
|
+
// Cold path: pick any healthy runner
|
|
323
|
+
try {
|
|
324
|
+
const oldWorkspaceDir = join(dataDir, 'sandboxes', session.id, 'workspace');
|
|
325
|
+
const workspaceExists = existsSync(oldWorkspaceDir);
|
|
326
|
+
if (!workspaceExists) {
|
|
327
|
+
if (hasPersistedState(dataDir, session.id)) {
|
|
328
|
+
restoreSessionState(dataDir, session.id, oldWorkspaceDir);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// Fall back to cloud storage
|
|
332
|
+
const restored = await restoreStateFromCloud(dataDir, session.id);
|
|
333
|
+
if (restored) {
|
|
334
|
+
restoreSessionState(dataDir, session.id, oldWorkspaceDir);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const workspaceAvailable = existsSync(oldWorkspaceDir);
|
|
339
|
+
const { backend, runnerId } = coordinator.selectBackend();
|
|
340
|
+
const handle = await backend.createSandbox({
|
|
341
|
+
sessionId: session.id,
|
|
342
|
+
agentDir: agentRecord.path,
|
|
343
|
+
agentName: session.agentName,
|
|
344
|
+
sandboxId: session.id,
|
|
345
|
+
skipAgentCopy: workspaceAvailable,
|
|
346
|
+
onOomKill: () => {
|
|
347
|
+
updateSessionStatus(session.id, 'paused').catch((err) => console.error(`Failed to update session status on OOM: ${err}`));
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
backend.recordColdHit();
|
|
351
|
+
logResume('cold', session.id, session.agentName);
|
|
352
|
+
const effectiveRunnerId = runnerId === '__local__' ? null : runnerId;
|
|
353
|
+
await updateSessionSandbox(session.id, handle.sandboxId);
|
|
354
|
+
await updateSessionRunner(session.id, effectiveRunnerId);
|
|
355
|
+
await updateSessionStatus(session.id, 'active');
|
|
356
|
+
return reply.send({ session: { ...session, sandboxId: handle.sandboxId, status: 'active', runnerId: effectiveRunnerId } });
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
360
|
+
if (msg.includes('capacity reached') || msg.includes('No runners available')) {
|
|
361
|
+
return reply.status(503).send({ error: msg, statusCode: 503 });
|
|
362
|
+
}
|
|
363
|
+
return reply.status(500).send({ error: `Failed to resume session: ${msg}`, statusCode: 500 });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
// End session
|
|
367
|
+
app.delete('/api/sessions/:id', {
|
|
368
|
+
schema: {
|
|
369
|
+
tags: ['sessions'],
|
|
370
|
+
params: idParam,
|
|
371
|
+
response: {
|
|
372
|
+
200: sessionResponse,
|
|
373
|
+
404: { $ref: 'ApiError#' },
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
}, async (req, reply) => {
|
|
377
|
+
const session = await getSession(req.params.id);
|
|
378
|
+
if (!session) {
|
|
379
|
+
return reply.status(404).send({ error: 'Session not found', statusCode: 404 });
|
|
380
|
+
}
|
|
381
|
+
// Persist state and destroy sandbox (best-effort — runner may be gone)
|
|
382
|
+
try {
|
|
383
|
+
const backend = coordinator.getBackendForRunner(session.runnerId);
|
|
384
|
+
backend.persistState(session.sandboxId, session.id, session.agentName);
|
|
385
|
+
await backend.destroySandbox(session.sandboxId);
|
|
386
|
+
}
|
|
387
|
+
catch { /* runner may be gone */ }
|
|
388
|
+
await updateSessionStatus(session.id, 'ended');
|
|
389
|
+
return reply.send({ session: { ...session, status: 'ended' } });
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/routes/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PoolStats } from '@ash-ai/shared';
|
|
2
|
+
import type { RunnerBackend } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Coordinates multiple runners. Routes session creation to the least-loaded runner.
|
|
5
|
+
* Detects dead runners and marks their sessions as paused.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RunnerCoordinator {
|
|
8
|
+
private runners;
|
|
9
|
+
private localBackend;
|
|
10
|
+
private localRunnerId;
|
|
11
|
+
private livenessSweepTimer;
|
|
12
|
+
constructor(opts: {
|
|
13
|
+
localBackend?: RunnerBackend;
|
|
14
|
+
});
|
|
15
|
+
registerRunner(info: {
|
|
16
|
+
runnerId: string;
|
|
17
|
+
host: string;
|
|
18
|
+
port: number;
|
|
19
|
+
maxSandboxes: number;
|
|
20
|
+
}): void;
|
|
21
|
+
heartbeat(runnerId: string, stats: PoolStats): void;
|
|
22
|
+
/**
|
|
23
|
+
* Select the best backend for a new session.
|
|
24
|
+
* Returns the least-loaded runner, or local backend if no runners available.
|
|
25
|
+
*/
|
|
26
|
+
selectBackend(): {
|
|
27
|
+
backend: RunnerBackend;
|
|
28
|
+
runnerId: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Get the backend for a specific runner. Used for routing messages to existing sessions.
|
|
32
|
+
*/
|
|
33
|
+
getBackendForRunner(runnerId: string | null | undefined): RunnerBackend;
|
|
34
|
+
/**
|
|
35
|
+
* Start periodic liveness checks for remote runners.
|
|
36
|
+
*/
|
|
37
|
+
startLivenessSweep(): void;
|
|
38
|
+
stopLivenessSweep(): void;
|
|
39
|
+
private checkLiveness;
|
|
40
|
+
handleDeadRunner(runnerId: string): Promise<void>;
|
|
41
|
+
get runnerCount(): number;
|
|
42
|
+
get hasLocalBackend(): boolean;
|
|
43
|
+
getRunnerInfo(): Array<{
|
|
44
|
+
runnerId: string;
|
|
45
|
+
host: string;
|
|
46
|
+
port: number;
|
|
47
|
+
active: number;
|
|
48
|
+
max: number;
|
|
49
|
+
lastHeartbeat: number;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=coordinator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordinator.d.ts","sourceRoot":"","sources":["../../src/runner/coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAchD;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAA+B;gBAE7C,IAAI,EAAE;QAAE,YAAY,CAAC,EAAE,aAAa,CAAA;KAAE;IAIlD,cAAc,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAsBlG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAUnD;;;OAGG;IACH,aAAa,IAAI;QAAE,OAAO,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAiC7D;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa;IAgBvE;;OAEG;IACH,kBAAkB,IAAI,IAAI;IAU1B,iBAAiB,IAAI,IAAI;YAOX,aAAa;IAUrB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvD,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED,aAAa,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CAc7H"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { RUNNER_LIVENESS_TIMEOUT_MS } from '@ash-ai/shared';
|
|
2
|
+
import { RemoteRunnerBackend } from './remote-backend.js';
|
|
3
|
+
import { updateSessionStatus, listSessionsByRunner } from '../db/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Coordinates multiple runners. Routes session creation to the least-loaded runner.
|
|
6
|
+
* Detects dead runners and marks their sessions as paused.
|
|
7
|
+
*/
|
|
8
|
+
export class RunnerCoordinator {
|
|
9
|
+
runners = new Map();
|
|
10
|
+
localBackend;
|
|
11
|
+
localRunnerId = '__local__';
|
|
12
|
+
livenessSweepTimer = null;
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
this.localBackend = opts.localBackend ?? null;
|
|
15
|
+
}
|
|
16
|
+
registerRunner(info) {
|
|
17
|
+
const existing = this.runners.get(info.runnerId);
|
|
18
|
+
if (existing) {
|
|
19
|
+
// Re-registration — update connection info
|
|
20
|
+
existing.host = info.host;
|
|
21
|
+
existing.port = info.port;
|
|
22
|
+
existing.maxSandboxes = info.maxSandboxes;
|
|
23
|
+
existing.lastHeartbeat = Date.now();
|
|
24
|
+
console.log(`[coordinator] Runner ${info.runnerId} re-registered at ${info.host}:${info.port}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const backend = new RemoteRunnerBackend({ host: info.host, port: info.port });
|
|
28
|
+
this.runners.set(info.runnerId, {
|
|
29
|
+
...info,
|
|
30
|
+
backend,
|
|
31
|
+
lastHeartbeat: Date.now(),
|
|
32
|
+
stats: null,
|
|
33
|
+
});
|
|
34
|
+
console.log(`[coordinator] Runner ${info.runnerId} registered at ${info.host}:${info.port} (max ${info.maxSandboxes})`);
|
|
35
|
+
}
|
|
36
|
+
heartbeat(runnerId, stats) {
|
|
37
|
+
const runner = this.runners.get(runnerId);
|
|
38
|
+
if (!runner) {
|
|
39
|
+
console.warn(`[coordinator] Heartbeat from unknown runner ${runnerId}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
runner.lastHeartbeat = Date.now();
|
|
43
|
+
runner.stats = stats;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Select the best backend for a new session.
|
|
47
|
+
* Returns the least-loaded runner, or local backend if no runners available.
|
|
48
|
+
*/
|
|
49
|
+
selectBackend() {
|
|
50
|
+
// If we have remote runners, pick the one with most available capacity
|
|
51
|
+
if (this.runners.size > 0) {
|
|
52
|
+
let bestRunner = null;
|
|
53
|
+
let bestAvailable = -1;
|
|
54
|
+
for (const runner of this.runners.values()) {
|
|
55
|
+
// Skip dead runners
|
|
56
|
+
if (Date.now() - runner.lastHeartbeat > RUNNER_LIVENESS_TIMEOUT_MS)
|
|
57
|
+
continue;
|
|
58
|
+
const available = runner.stats
|
|
59
|
+
? runner.maxSandboxes - runner.stats.running - runner.stats.warming
|
|
60
|
+
: runner.maxSandboxes;
|
|
61
|
+
if (available > bestAvailable) {
|
|
62
|
+
bestAvailable = available;
|
|
63
|
+
bestRunner = runner;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (bestRunner && bestAvailable > 0) {
|
|
67
|
+
return { backend: bestRunner.backend, runnerId: bestRunner.runnerId };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Fall back to local backend
|
|
71
|
+
if (this.localBackend) {
|
|
72
|
+
return { backend: this.localBackend, runnerId: this.localRunnerId };
|
|
73
|
+
}
|
|
74
|
+
throw new Error('No runners available and no local backend configured');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get the backend for a specific runner. Used for routing messages to existing sessions.
|
|
78
|
+
*/
|
|
79
|
+
getBackendForRunner(runnerId) {
|
|
80
|
+
if (!runnerId || runnerId === this.localRunnerId) {
|
|
81
|
+
if (this.localBackend)
|
|
82
|
+
return this.localBackend;
|
|
83
|
+
throw new Error('No local backend configured');
|
|
84
|
+
}
|
|
85
|
+
const runner = this.runners.get(runnerId);
|
|
86
|
+
if (!runner) {
|
|
87
|
+
// Runner gone — fall back to local if available
|
|
88
|
+
if (this.localBackend)
|
|
89
|
+
return this.localBackend;
|
|
90
|
+
throw new Error(`Runner ${runnerId} not found`);
|
|
91
|
+
}
|
|
92
|
+
return runner.backend;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Start periodic liveness checks for remote runners.
|
|
96
|
+
*/
|
|
97
|
+
startLivenessSweep() {
|
|
98
|
+
if (this.livenessSweepTimer)
|
|
99
|
+
return;
|
|
100
|
+
this.livenessSweepTimer = setInterval(() => {
|
|
101
|
+
this.checkLiveness().catch((err) => console.error('[coordinator] Liveness sweep error:', err));
|
|
102
|
+
}, RUNNER_LIVENESS_TIMEOUT_MS);
|
|
103
|
+
this.livenessSweepTimer.unref();
|
|
104
|
+
}
|
|
105
|
+
stopLivenessSweep() {
|
|
106
|
+
if (this.livenessSweepTimer) {
|
|
107
|
+
clearInterval(this.livenessSweepTimer);
|
|
108
|
+
this.livenessSweepTimer = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async checkLiveness() {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
for (const [runnerId, runner] of this.runners) {
|
|
114
|
+
if (now - runner.lastHeartbeat > RUNNER_LIVENESS_TIMEOUT_MS) {
|
|
115
|
+
console.warn(`[coordinator] Runner ${runnerId} missed heartbeat — marking sessions paused`);
|
|
116
|
+
await this.handleDeadRunner(runnerId);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async handleDeadRunner(runnerId) {
|
|
121
|
+
// Mark all sessions on this runner as paused
|
|
122
|
+
const sessions = await listSessionsByRunner(runnerId);
|
|
123
|
+
for (const session of sessions) {
|
|
124
|
+
if (session.status === 'active' || session.status === 'starting') {
|
|
125
|
+
await updateSessionStatus(session.id, 'paused');
|
|
126
|
+
console.log(`[coordinator] Paused session ${session.id} (runner ${runnerId} dead)`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Remove the runner
|
|
130
|
+
const runner = this.runners.get(runnerId);
|
|
131
|
+
if (runner) {
|
|
132
|
+
runner.backend.close();
|
|
133
|
+
this.runners.delete(runnerId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
get runnerCount() {
|
|
137
|
+
return this.runners.size;
|
|
138
|
+
}
|
|
139
|
+
get hasLocalBackend() {
|
|
140
|
+
return this.localBackend !== null;
|
|
141
|
+
}
|
|
142
|
+
getRunnerInfo() {
|
|
143
|
+
const result = [];
|
|
144
|
+
for (const runner of this.runners.values()) {
|
|
145
|
+
result.push({
|
|
146
|
+
runnerId: runner.runnerId,
|
|
147
|
+
host: runner.host,
|
|
148
|
+
port: runner.port,
|
|
149
|
+
active: runner.stats?.running ?? 0,
|
|
150
|
+
max: runner.maxSandboxes,
|
|
151
|
+
lastHeartbeat: runner.lastHeartbeat,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordinator.js","sourceRoot":"","sources":["../../src/runner/coordinator.ts"],"names":[],"mappings":"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"}
|