@ash-ai/server 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/attachments.test.d.ts +2 -0
- package/dist/__tests__/attachments.test.d.ts.map +1 -0
- package/dist/__tests__/attachments.test.js +57 -0
- package/dist/__tests__/attachments.test.js.map +1 -0
- package/dist/__tests__/bundle.test.d.ts +2 -0
- package/dist/__tests__/bundle.test.d.ts.map +1 -0
- package/dist/__tests__/bundle.test.js +55 -0
- package/dist/__tests__/bundle.test.js.map +1 -0
- package/dist/__tests__/coordinator.test.d.ts +2 -0
- package/dist/__tests__/coordinator.test.d.ts.map +1 -0
- package/dist/__tests__/coordinator.test.js +283 -0
- package/dist/__tests__/coordinator.test.js.map +1 -0
- package/dist/__tests__/crypto.test.d.ts +2 -0
- package/dist/__tests__/crypto.test.d.ts.map +1 -0
- package/dist/__tests__/crypto.test.js +45 -0
- package/dist/__tests__/crypto.test.js.map +1 -0
- package/dist/__tests__/file-store.test.d.ts +2 -0
- package/dist/__tests__/file-store.test.d.ts.map +1 -0
- package/dist/__tests__/file-store.test.js +105 -0
- package/dist/__tests__/file-store.test.js.map +1 -0
- package/dist/__tests__/files.test.js +3 -3
- package/dist/__tests__/files.test.js.map +1 -1
- package/dist/__tests__/openapi.test.js +6 -3
- package/dist/__tests__/openapi.test.js.map +1 -1
- package/dist/__tests__/queue.test.d.ts +2 -0
- package/dist/__tests__/queue.test.d.ts.map +1 -0
- package/dist/__tests__/queue.test.js +151 -0
- package/dist/__tests__/queue.test.js.map +1 -0
- package/dist/__tests__/usage.test.d.ts +2 -0
- package/dist/__tests__/usage.test.d.ts.map +1 -0
- package/dist/__tests__/usage.test.js +74 -0
- package/dist/__tests__/usage.test.js.map +1 -0
- package/dist/crypto.d.ts +8 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +29 -0
- package/dist/crypto.js.map +1 -0
- package/dist/db/drizzle-db.d.ts +128 -0
- package/dist/db/drizzle-db.d.ts.map +1 -0
- package/dist/db/drizzle-db.js +789 -0
- package/dist/db/drizzle-db.js.map +1 -0
- package/dist/db/index.d.ts +161 -3
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +164 -8
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema.pg.d.ts +1625 -0
- package/dist/db/schema.pg.d.ts.map +1 -0
- package/dist/db/schema.pg.js +150 -0
- package/dist/db/schema.pg.js.map +1 -0
- package/dist/db/schema.sqlite.d.ts +1781 -0
- package/dist/db/schema.sqlite.d.ts.map +1 -0
- package/dist/db/schema.sqlite.js +150 -0
- package/dist/db/schema.sqlite.js.map +1 -0
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/queue/processor.d.ts +51 -0
- package/dist/queue/processor.d.ts.map +1 -0
- package/dist/queue/processor.js +98 -0
- package/dist/queue/processor.js.map +1 -0
- package/dist/routes/attachments.d.ts +3 -0
- package/dist/routes/attachments.d.ts.map +1 -0
- package/dist/routes/attachments.js +168 -0
- package/dist/routes/attachments.js.map +1 -0
- package/dist/routes/credentials.d.ts +11 -0
- package/dist/routes/credentials.d.ts.map +1 -0
- package/dist/routes/credentials.js +120 -0
- package/dist/routes/credentials.js.map +1 -0
- package/dist/routes/files.js +5 -5
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +9 -1
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/queue.d.ts +3 -0
- package/dist/routes/queue.d.ts.map +1 -0
- package/dist/routes/queue.js +144 -0
- package/dist/routes/queue.js.map +1 -0
- package/dist/routes/runners.d.ts +5 -0
- package/dist/routes/runners.d.ts.map +1 -1
- package/dist/routes/runners.js +42 -5
- package/dist/routes/runners.js.map +1 -1
- package/dist/routes/sessions.d.ts +2 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +236 -11
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/usage.d.ts +3 -0
- package/dist/routes/usage.d.ts.map +1 -0
- package/dist/routes/usage.js +64 -0
- package/dist/routes/usage.js.map +1 -0
- package/dist/routes/workspace.d.ts +4 -0
- package/dist/routes/workspace.d.ts.map +1 -0
- package/dist/routes/workspace.js +123 -0
- package/dist/routes/workspace.js.map +1 -0
- package/dist/runner/coordinator.d.ts +77 -9
- package/dist/runner/coordinator.d.ts.map +1 -1
- package/dist/runner/coordinator.js +163 -89
- package/dist/runner/coordinator.js.map +1 -1
- package/dist/runner/local-backend.d.ts +1 -0
- package/dist/runner/local-backend.d.ts.map +1 -1
- package/dist/runner/local-backend.js +7 -0
- package/dist/runner/local-backend.js.map +1 -1
- package/dist/runner/remote-backend.d.ts +2 -0
- package/dist/runner/remote-backend.d.ts.map +1 -1
- package/dist/runner/remote-backend.js +7 -0
- package/dist/runner/remote-backend.js.map +1 -1
- package/dist/runner/runner-client.d.ts +4 -0
- package/dist/runner/runner-client.d.ts.map +1 -1
- package/dist/runner/runner-client.js +12 -0
- package/dist/runner/runner-client.js.map +1 -1
- package/dist/runner/types.d.ts +4 -0
- package/dist/runner/types.d.ts.map +1 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +115 -1
- package/dist/schemas.js.map +1 -1
- package/dist/telemetry/exporter.d.ts +16 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +89 -0
- package/dist/telemetry/exporter.js.map +1 -0
- package/dist/usage/extractor.d.ts +18 -0
- package/dist/usage/extractor.d.ts.map +1 -0
- package/dist/usage/extractor.js +48 -0
- package/dist/usage/extractor.js.map +1 -0
- package/drizzle/pg/0000_thick_loners.sql +75 -0
- package/drizzle/pg/0001_rare_lester.sql +13 -0
- package/drizzle/pg/0002_short_shinko_yamashiro.sql +1 -0
- package/drizzle/pg/0003_remarkable_mastermind.sql +14 -0
- package/drizzle/pg/0004_warm_reaper.sql +18 -0
- package/drizzle/pg/0005_overconfident_mole_man.sql +14 -0
- package/drizzle/pg/0006_third_shiva.sql +13 -0
- package/drizzle/pg/0007_keen_shockwave.sql +2 -0
- package/drizzle/pg/meta/0000_snapshot.json +648 -0
- package/drizzle/pg/meta/0001_snapshot.json +743 -0
- package/drizzle/pg/meta/0002_snapshot.json +749 -0
- package/drizzle/pg/meta/0003_snapshot.json +841 -0
- package/drizzle/pg/meta/0004_snapshot.json +974 -0
- package/drizzle/pg/meta/0005_snapshot.json +1079 -0
- package/drizzle/pg/meta/0006_snapshot.json +1193 -0
- package/drizzle/pg/meta/0007_snapshot.json +1199 -0
- package/drizzle/pg/meta/_journal.json +62 -0
- package/drizzle/sqlite/0000_massive_kinsey_walden.sql +75 -0
- package/drizzle/sqlite/0001_quiet_phantom_reporter.sql +13 -0
- package/drizzle/sqlite/0002_broad_sheva_callister.sql +1 -0
- package/drizzle/sqlite/0003_thankful_agent_brand.sql +14 -0
- package/drizzle/sqlite/0004_productive_wolverine.sql +18 -0
- package/drizzle/sqlite/0005_chilly_carlie_cooper.sql +14 -0
- package/drizzle/sqlite/0006_workable_starfox.sql +13 -0
- package/drizzle/sqlite/0007_quick_hemingway.sql +19 -0
- package/drizzle/sqlite/meta/0000_snapshot.json +503 -0
- package/drizzle/sqlite/meta/0001_snapshot.json +587 -0
- package/drizzle/sqlite/meta/0002_snapshot.json +594 -0
- package/drizzle/sqlite/meta/0003_snapshot.json +685 -0
- package/drizzle/sqlite/meta/0004_snapshot.json +807 -0
- package/drizzle/sqlite/meta/0005_snapshot.json +897 -0
- package/drizzle/sqlite/meta/0006_snapshot.json +981 -0
- package/drizzle/sqlite/meta/0007_snapshot.json +988 -0
- package/drizzle/sqlite/meta/_journal.json +62 -0
- package/package.json +10 -5
- package/dist/__tests__/schema.test.d.ts +0 -2
- package/dist/__tests__/schema.test.d.ts.map +0 -1
- package/dist/__tests__/schema.test.js +0 -31
- package/dist/__tests__/schema.test.js.map +0 -1
- package/dist/db/dump-schema.d.ts +0 -10
- package/dist/db/dump-schema.d.ts.map +0 -1
- package/dist/db/dump-schema.js +0 -64
- package/dist/db/dump-schema.js.map +0 -1
- package/dist/db/pg.d.ts +0 -35
- package/dist/db/pg.d.ts.map +0 -1
- package/dist/db/pg.js +0 -272
- package/dist/db/pg.js.map +0 -1
- package/dist/db/sqlite.d.ts +0 -34
- package/dist/db/sqlite.d.ts.map +0 -1
- package/dist/db/sqlite.js +0 -296
- package/dist/db/sqlite.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/attachments.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { mkdtempSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { initDb, closeDb, insertAttachment, getAttachment, listAttachmentsByMessage, listAttachmentsBySession, deleteAttachment } from '../db/index.js';
|
|
6
|
+
const tenant = 'attach-test';
|
|
7
|
+
let n = 0;
|
|
8
|
+
const uid = () => `a-${Date.now()}-${++n}`;
|
|
9
|
+
describe('attachments', () => {
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
const dir = mkdtempSync(join(tmpdir(), 'ash-attach-'));
|
|
12
|
+
await initDb({ dataDir: dir });
|
|
13
|
+
});
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await closeDb();
|
|
16
|
+
});
|
|
17
|
+
it('inserts and retrieves an attachment', async () => {
|
|
18
|
+
const id = uid();
|
|
19
|
+
const att = await insertAttachment(id, tenant, 'msg-1', 'sess-1', 'test.txt', 'text/plain', 42, '/path/to/test.txt');
|
|
20
|
+
expect(att.id).toBe(id);
|
|
21
|
+
expect(att.filename).toBe('test.txt');
|
|
22
|
+
expect(att.mimeType).toBe('text/plain');
|
|
23
|
+
expect(att.size).toBe(42);
|
|
24
|
+
const got = await getAttachment(id);
|
|
25
|
+
expect(got).not.toBeNull();
|
|
26
|
+
expect(got.id).toBe(id);
|
|
27
|
+
});
|
|
28
|
+
it('returns null for nonexistent attachment', async () => {
|
|
29
|
+
const got = await getAttachment('nope');
|
|
30
|
+
expect(got).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
it('lists attachments by message', async () => {
|
|
33
|
+
const a = uid(), b = uid();
|
|
34
|
+
await insertAttachment(a, tenant, 'msg-1', 'sess-1', 'file1.txt', 'text/plain', 10, '/p1');
|
|
35
|
+
await insertAttachment(b, tenant, 'msg-2', 'sess-1', 'file2.txt', 'text/plain', 20, '/p2');
|
|
36
|
+
const forMsg1 = await listAttachmentsByMessage('msg-1', tenant);
|
|
37
|
+
expect(forMsg1.length).toBe(1);
|
|
38
|
+
expect(forMsg1[0].id).toBe(a);
|
|
39
|
+
});
|
|
40
|
+
it('lists attachments by session', async () => {
|
|
41
|
+
const a = uid(), b = uid(), c = uid();
|
|
42
|
+
await insertAttachment(a, tenant, 'msg-1', 'sess-1', 'f1.txt', 'text/plain', 10, '/p1');
|
|
43
|
+
await insertAttachment(b, tenant, 'msg-2', 'sess-1', 'f2.txt', 'text/plain', 20, '/p2');
|
|
44
|
+
await insertAttachment(c, tenant, 'msg-3', 'sess-2', 'f3.txt', 'text/plain', 30, '/p3');
|
|
45
|
+
const forSess1 = await listAttachmentsBySession('sess-1', tenant);
|
|
46
|
+
expect(forSess1.length).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
it('deletes an attachment', async () => {
|
|
49
|
+
const id = uid();
|
|
50
|
+
await insertAttachment(id, tenant, 'msg-1', 'sess-1', 'del.txt', 'text/plain', 5, '/del');
|
|
51
|
+
const deleted = await deleteAttachment(id);
|
|
52
|
+
expect(deleted).toBe(true);
|
|
53
|
+
const got = await getAttachment(id);
|
|
54
|
+
expect(got).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=attachments.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.test.js","sourceRoot":"","sources":["../../src/__tests__/attachments.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bundle.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { createBundle, extractBundle } from '@ash-ai/sandbox';
|
|
6
|
+
describe('workspace bundles', () => {
|
|
7
|
+
const dirs = [];
|
|
8
|
+
function tmpDir() {
|
|
9
|
+
const d = mkdtempSync(join(tmpdir(), 'ash-bundle-'));
|
|
10
|
+
dirs.push(d);
|
|
11
|
+
return d;
|
|
12
|
+
}
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const d of dirs) {
|
|
15
|
+
rmSync(d, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
dirs.length = 0;
|
|
18
|
+
});
|
|
19
|
+
it('round-trip: createBundle → extractBundle preserves files', () => {
|
|
20
|
+
const src = tmpDir();
|
|
21
|
+
writeFileSync(join(src, 'hello.txt'), 'world');
|
|
22
|
+
mkdirSync(join(src, 'sub'));
|
|
23
|
+
writeFileSync(join(src, 'sub', 'nested.txt'), 'deep');
|
|
24
|
+
const bundle = createBundle(src);
|
|
25
|
+
expect(bundle.length).toBeGreaterThan(0);
|
|
26
|
+
const dest = tmpDir();
|
|
27
|
+
extractBundle(bundle, dest);
|
|
28
|
+
expect(readFileSync(join(dest, 'hello.txt'), 'utf-8')).toBe('world');
|
|
29
|
+
expect(readFileSync(join(dest, 'sub', 'nested.txt'), 'utf-8')).toBe('deep');
|
|
30
|
+
});
|
|
31
|
+
it('createBundle excludes node_modules', () => {
|
|
32
|
+
const src = tmpDir();
|
|
33
|
+
writeFileSync(join(src, 'index.js'), 'console.log("hi")');
|
|
34
|
+
mkdirSync(join(src, 'node_modules', 'dep'), { recursive: true });
|
|
35
|
+
writeFileSync(join(src, 'node_modules', 'dep', 'index.js'), 'big module');
|
|
36
|
+
const bundle = createBundle(src);
|
|
37
|
+
const dest = tmpDir();
|
|
38
|
+
extractBundle(bundle, dest);
|
|
39
|
+
expect(readFileSync(join(dest, 'index.js'), 'utf-8')).toBe('console.log("hi")');
|
|
40
|
+
// node_modules should be excluded
|
|
41
|
+
expect(existsSync(join(dest, 'node_modules'))).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
it('throws for nonexistent workspace', () => {
|
|
44
|
+
expect(() => createBundle('/nonexistent/path')).toThrow('does not exist');
|
|
45
|
+
});
|
|
46
|
+
it('extractBundle creates target dir if needed', () => {
|
|
47
|
+
const src = tmpDir();
|
|
48
|
+
writeFileSync(join(src, 'file.txt'), 'data');
|
|
49
|
+
const bundle = createBundle(src);
|
|
50
|
+
const dest = join(tmpDir(), 'new', 'nested', 'dir');
|
|
51
|
+
extractBundle(bundle, dest);
|
|
52
|
+
expect(readFileSync(join(dest, 'file.txt'), 'utf-8')).toBe('data');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=bundle.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.test.js","sourceRoot":"","sources":["../../src/__tests__/bundle.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordinator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/coordinator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { initDb, closeDb, insertSession, updateSessionStatus, updateSessionRunner } from '../db/index.js';
|
|
6
|
+
import { RunnerCoordinator } from '../runner/coordinator.js';
|
|
7
|
+
const TEST_DIR = join(import.meta.dirname ?? '.', '..', '..', '.test-coordinator-' + process.pid);
|
|
8
|
+
describe('RunnerCoordinator (DB-backed)', () => {
|
|
9
|
+
let coordinator;
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
12
|
+
await initDb({ dataDir: TEST_DIR });
|
|
13
|
+
coordinator = new RunnerCoordinator({});
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
coordinator.stopLivenessSweep();
|
|
17
|
+
await closeDb();
|
|
18
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
describe('registerRunner', () => {
|
|
21
|
+
it('registers a new runner and persists to DB', async () => {
|
|
22
|
+
await coordinator.registerRunner({
|
|
23
|
+
runnerId: 'runner-1',
|
|
24
|
+
host: 'host-1',
|
|
25
|
+
port: 4200,
|
|
26
|
+
maxSandboxes: 50,
|
|
27
|
+
});
|
|
28
|
+
const info = await coordinator.getRunnerInfoFromDb();
|
|
29
|
+
expect(info).toHaveLength(1);
|
|
30
|
+
expect(info[0].runnerId).toBe('runner-1');
|
|
31
|
+
expect(info[0].host).toBe('host-1');
|
|
32
|
+
expect(info[0].port).toBe(4200);
|
|
33
|
+
expect(info[0].max).toBe(50);
|
|
34
|
+
});
|
|
35
|
+
it('re-registers an existing runner with updated connection info', async () => {
|
|
36
|
+
await coordinator.registerRunner({
|
|
37
|
+
runnerId: 'runner-1',
|
|
38
|
+
host: 'old-host',
|
|
39
|
+
port: 4200,
|
|
40
|
+
maxSandboxes: 50,
|
|
41
|
+
});
|
|
42
|
+
await coordinator.registerRunner({
|
|
43
|
+
runnerId: 'runner-1',
|
|
44
|
+
host: 'new-host',
|
|
45
|
+
port: 4201,
|
|
46
|
+
maxSandboxes: 100,
|
|
47
|
+
});
|
|
48
|
+
const info = await coordinator.getRunnerInfoFromDb();
|
|
49
|
+
expect(info).toHaveLength(1);
|
|
50
|
+
expect(info[0].host).toBe('new-host');
|
|
51
|
+
expect(info[0].port).toBe(4201);
|
|
52
|
+
expect(info[0].max).toBe(100);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('heartbeat', () => {
|
|
56
|
+
it('updates runner stats in DB', async () => {
|
|
57
|
+
await coordinator.registerRunner({
|
|
58
|
+
runnerId: 'runner-1',
|
|
59
|
+
host: 'host-1',
|
|
60
|
+
port: 4200,
|
|
61
|
+
maxSandboxes: 50,
|
|
62
|
+
});
|
|
63
|
+
await coordinator.heartbeat('runner-1', {
|
|
64
|
+
total: 10, cold: 0, warming: 2, warm: 3, waiting: 3, running: 5,
|
|
65
|
+
maxCapacity: 50, resumeWarmHits: 0, resumeColdHits: 0,
|
|
66
|
+
});
|
|
67
|
+
const info = await coordinator.getRunnerInfoFromDb();
|
|
68
|
+
expect(info[0].active).toBe(5); // running count
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('selectBackend', () => {
|
|
72
|
+
it('throws when no runners available and no local backend', async () => {
|
|
73
|
+
await expect(coordinator.selectBackend()).rejects.toThrow('No runners available');
|
|
74
|
+
});
|
|
75
|
+
it('falls back to local backend when no remote runners', async () => {
|
|
76
|
+
const localBackend = {
|
|
77
|
+
createSandbox: vi.fn(),
|
|
78
|
+
destroySandbox: vi.fn(),
|
|
79
|
+
destroyAll: vi.fn(),
|
|
80
|
+
sendCommand: vi.fn(),
|
|
81
|
+
interrupt: vi.fn(),
|
|
82
|
+
getSandbox: vi.fn(),
|
|
83
|
+
isSandboxAlive: vi.fn(),
|
|
84
|
+
markRunning: vi.fn(),
|
|
85
|
+
markWaiting: vi.fn(),
|
|
86
|
+
recordWarmHit: vi.fn(),
|
|
87
|
+
recordColdHit: vi.fn(),
|
|
88
|
+
persistState: vi.fn(),
|
|
89
|
+
getStats: vi.fn(),
|
|
90
|
+
activeCount: 0,
|
|
91
|
+
};
|
|
92
|
+
const coordWithLocal = new RunnerCoordinator({ localBackend });
|
|
93
|
+
const result = await coordWithLocal.selectBackend();
|
|
94
|
+
expect(result.runnerId).toBe('__local__');
|
|
95
|
+
expect(result.backend).toBe(localBackend);
|
|
96
|
+
});
|
|
97
|
+
it('selects runner with most available capacity', async () => {
|
|
98
|
+
await coordinator.registerRunner({
|
|
99
|
+
runnerId: 'runner-1',
|
|
100
|
+
host: 'host-1',
|
|
101
|
+
port: 4200,
|
|
102
|
+
maxSandboxes: 50,
|
|
103
|
+
});
|
|
104
|
+
await coordinator.registerRunner({
|
|
105
|
+
runnerId: 'runner-2',
|
|
106
|
+
host: 'host-2',
|
|
107
|
+
port: 4201,
|
|
108
|
+
maxSandboxes: 100,
|
|
109
|
+
});
|
|
110
|
+
// runner-1 has 40 active sandboxes (10 available)
|
|
111
|
+
await coordinator.heartbeat('runner-1', {
|
|
112
|
+
total: 50, cold: 0, warming: 5, warm: 0, waiting: 5, running: 40,
|
|
113
|
+
maxCapacity: 50, resumeWarmHits: 0, resumeColdHits: 0,
|
|
114
|
+
});
|
|
115
|
+
// runner-2 has 10 active sandboxes (90 available)
|
|
116
|
+
await coordinator.heartbeat('runner-2', {
|
|
117
|
+
total: 100, cold: 0, warming: 0, warm: 0, waiting: 0, running: 10,
|
|
118
|
+
maxCapacity: 100, resumeWarmHits: 0, resumeColdHits: 0,
|
|
119
|
+
});
|
|
120
|
+
const result = await coordinator.selectBackend();
|
|
121
|
+
expect(result.runnerId).toBe('runner-2');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('handleDeadRunner', () => {
|
|
125
|
+
it('marks active sessions on dead runner as paused and removes runner', async () => {
|
|
126
|
+
await coordinator.registerRunner({
|
|
127
|
+
runnerId: 'runner-dead',
|
|
128
|
+
host: 'host-1',
|
|
129
|
+
port: 4200,
|
|
130
|
+
maxSandboxes: 50,
|
|
131
|
+
});
|
|
132
|
+
// Create a session on this runner
|
|
133
|
+
const sessionId = randomUUID();
|
|
134
|
+
await insertSession(sessionId, 'test-agent', sessionId);
|
|
135
|
+
await updateSessionRunner(sessionId, 'runner-dead');
|
|
136
|
+
await updateSessionStatus(sessionId, 'active');
|
|
137
|
+
await coordinator.handleDeadRunner('runner-dead');
|
|
138
|
+
// Session should be paused
|
|
139
|
+
const { getSession } = await import('../db/index.js');
|
|
140
|
+
const session = await getSession(sessionId);
|
|
141
|
+
expect(session?.status).toBe('paused');
|
|
142
|
+
// Runner should be removed from DB
|
|
143
|
+
const info = await coordinator.getRunnerInfoFromDb();
|
|
144
|
+
expect(info).toHaveLength(0);
|
|
145
|
+
});
|
|
146
|
+
it('does not re-pause already paused sessions', async () => {
|
|
147
|
+
await coordinator.registerRunner({
|
|
148
|
+
runnerId: 'runner-dead',
|
|
149
|
+
host: 'host-1',
|
|
150
|
+
port: 4200,
|
|
151
|
+
maxSandboxes: 50,
|
|
152
|
+
});
|
|
153
|
+
const sessionId = randomUUID();
|
|
154
|
+
await insertSession(sessionId, 'test-agent', sessionId);
|
|
155
|
+
await updateSessionRunner(sessionId, 'runner-dead');
|
|
156
|
+
await updateSessionStatus(sessionId, 'paused');
|
|
157
|
+
// Should not throw
|
|
158
|
+
await coordinator.handleDeadRunner('runner-dead');
|
|
159
|
+
const { getSession } = await import('../db/index.js');
|
|
160
|
+
const session = await getSession(sessionId);
|
|
161
|
+
expect(session?.status).toBe('paused');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('getRunnerInfoFromDb', () => {
|
|
165
|
+
it('returns all runners visible to any coordinator', async () => {
|
|
166
|
+
await coordinator.registerRunner({
|
|
167
|
+
runnerId: 'runner-1',
|
|
168
|
+
host: 'host-1',
|
|
169
|
+
port: 4200,
|
|
170
|
+
maxSandboxes: 50,
|
|
171
|
+
});
|
|
172
|
+
await coordinator.registerRunner({
|
|
173
|
+
runnerId: 'runner-2',
|
|
174
|
+
host: 'host-2',
|
|
175
|
+
port: 4201,
|
|
176
|
+
maxSandboxes: 100,
|
|
177
|
+
});
|
|
178
|
+
// A second coordinator can see both runners (same DB)
|
|
179
|
+
const coordinator2 = new RunnerCoordinator({});
|
|
180
|
+
const info = await coordinator2.getRunnerInfoFromDb();
|
|
181
|
+
expect(info).toHaveLength(2);
|
|
182
|
+
const runnerIds = info.map((r) => r.runnerId).sort();
|
|
183
|
+
expect(runnerIds).toEqual(['runner-1', 'runner-2']);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe('deregisterRunner', () => {
|
|
187
|
+
it('immediately pauses sessions and removes runner from DB', async () => {
|
|
188
|
+
await coordinator.registerRunner({
|
|
189
|
+
runnerId: 'runner-shutting-down',
|
|
190
|
+
host: 'host-1',
|
|
191
|
+
port: 4200,
|
|
192
|
+
maxSandboxes: 50,
|
|
193
|
+
});
|
|
194
|
+
// Create two sessions: one active, one ended
|
|
195
|
+
const activeId = randomUUID();
|
|
196
|
+
const endedId = randomUUID();
|
|
197
|
+
await insertSession(activeId, 'test-agent', activeId);
|
|
198
|
+
await updateSessionRunner(activeId, 'runner-shutting-down');
|
|
199
|
+
await updateSessionStatus(activeId, 'active');
|
|
200
|
+
await insertSession(endedId, 'test-agent', endedId);
|
|
201
|
+
await updateSessionRunner(endedId, 'runner-shutting-down');
|
|
202
|
+
await updateSessionStatus(endedId, 'ended');
|
|
203
|
+
await coordinator.deregisterRunner('runner-shutting-down');
|
|
204
|
+
const { getSession } = await import('../db/index.js');
|
|
205
|
+
// Active session should be paused
|
|
206
|
+
const active = await getSession(activeId);
|
|
207
|
+
expect(active?.status).toBe('paused');
|
|
208
|
+
// Ended session should remain ended (not re-paused)
|
|
209
|
+
const ended = await getSession(endedId);
|
|
210
|
+
expect(ended?.status).toBe('ended');
|
|
211
|
+
// Runner should be gone
|
|
212
|
+
const info = await coordinator.getRunnerInfoFromDb();
|
|
213
|
+
expect(info).toHaveLength(0);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('bulkPauseSessionsByRunner', () => {
|
|
217
|
+
it('pauses multiple active sessions in a single operation', async () => {
|
|
218
|
+
await coordinator.registerRunner({
|
|
219
|
+
runnerId: 'runner-bulk',
|
|
220
|
+
host: 'host-1',
|
|
221
|
+
port: 4200,
|
|
222
|
+
maxSandboxes: 50,
|
|
223
|
+
});
|
|
224
|
+
// Create 5 sessions: 3 active, 1 paused, 1 ended
|
|
225
|
+
const ids = Array.from({ length: 5 }, () => randomUUID());
|
|
226
|
+
for (const id of ids) {
|
|
227
|
+
await insertSession(id, 'test-agent', id);
|
|
228
|
+
await updateSessionRunner(id, 'runner-bulk');
|
|
229
|
+
}
|
|
230
|
+
await updateSessionStatus(ids[0], 'active');
|
|
231
|
+
await updateSessionStatus(ids[1], 'active');
|
|
232
|
+
await updateSessionStatus(ids[2], 'active');
|
|
233
|
+
await updateSessionStatus(ids[3], 'paused');
|
|
234
|
+
await updateSessionStatus(ids[4], 'ended');
|
|
235
|
+
await coordinator.handleDeadRunner('runner-bulk');
|
|
236
|
+
const { getSession } = await import('../db/index.js');
|
|
237
|
+
// All 3 active sessions should be paused
|
|
238
|
+
for (const id of ids.slice(0, 3)) {
|
|
239
|
+
const session = await getSession(id);
|
|
240
|
+
expect(session?.status).toBe('paused');
|
|
241
|
+
}
|
|
242
|
+
// Already paused stays paused
|
|
243
|
+
expect((await getSession(ids[3]))?.status).toBe('paused');
|
|
244
|
+
// Ended stays ended
|
|
245
|
+
expect((await getSession(ids[4]))?.status).toBe('ended');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
describe('multi-coordinator consistency', () => {
|
|
249
|
+
it('second coordinator can select runner registered by first', async () => {
|
|
250
|
+
// First coordinator registers a runner
|
|
251
|
+
await coordinator.registerRunner({
|
|
252
|
+
runnerId: 'runner-shared',
|
|
253
|
+
host: 'shared-host',
|
|
254
|
+
port: 4200,
|
|
255
|
+
maxSandboxes: 100,
|
|
256
|
+
});
|
|
257
|
+
// Second coordinator should be able to select it
|
|
258
|
+
const coordinator2 = new RunnerCoordinator({});
|
|
259
|
+
const result = await coordinator2.selectBackend();
|
|
260
|
+
expect(result.runnerId).toBe('runner-shared');
|
|
261
|
+
});
|
|
262
|
+
it('dead runner handling is idempotent across coordinators', async () => {
|
|
263
|
+
await coordinator.registerRunner({
|
|
264
|
+
runnerId: 'runner-dying',
|
|
265
|
+
host: 'host-1',
|
|
266
|
+
port: 4200,
|
|
267
|
+
maxSandboxes: 50,
|
|
268
|
+
});
|
|
269
|
+
const sessionId = randomUUID();
|
|
270
|
+
await insertSession(sessionId, 'test-agent', sessionId);
|
|
271
|
+
await updateSessionRunner(sessionId, 'runner-dying');
|
|
272
|
+
await updateSessionStatus(sessionId, 'active');
|
|
273
|
+
const coordinator2 = new RunnerCoordinator({});
|
|
274
|
+
// Both coordinators handle the dead runner — should be idempotent
|
|
275
|
+
await coordinator.handleDeadRunner('runner-dying');
|
|
276
|
+
await coordinator2.handleDeadRunner('runner-dying');
|
|
277
|
+
const { getSession } = await import('../db/index.js');
|
|
278
|
+
const session = await getSession(sessionId);
|
|
279
|
+
expect(session?.status).toBe('paused');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
//# sourceMappingURL=coordinator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordinator.test.js","sourceRoot":"","sources":["../../src/__tests__/coordinator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crypto.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { encrypt, decrypt } from '../crypto.js';
|
|
3
|
+
describe('credential encryption', () => {
|
|
4
|
+
const masterKey = 'test-master-key-for-unit-tests';
|
|
5
|
+
it('round-trips a plaintext key', () => {
|
|
6
|
+
const plain = 'sk-ant-abc123xyz';
|
|
7
|
+
const { encrypted, iv, authTag } = encrypt(plain, masterKey);
|
|
8
|
+
const decrypted = decrypt(encrypted, iv, authTag, masterKey);
|
|
9
|
+
expect(decrypted).toBe(plain);
|
|
10
|
+
});
|
|
11
|
+
it('produces different ciphertext for same plaintext (unique IV)', () => {
|
|
12
|
+
const plain = 'sk-ant-abc123xyz';
|
|
13
|
+
const a = encrypt(plain, masterKey);
|
|
14
|
+
const b = encrypt(plain, masterKey);
|
|
15
|
+
expect(a.encrypted).not.toBe(b.encrypted);
|
|
16
|
+
expect(a.iv).not.toBe(b.iv);
|
|
17
|
+
});
|
|
18
|
+
it('fails to decrypt with wrong master key', () => {
|
|
19
|
+
const plain = 'sk-ant-abc123xyz';
|
|
20
|
+
const { encrypted, iv, authTag } = encrypt(plain, masterKey);
|
|
21
|
+
expect(() => decrypt(encrypted, iv, authTag, 'wrong-key')).toThrow();
|
|
22
|
+
});
|
|
23
|
+
it('fails to decrypt with tampered authTag', () => {
|
|
24
|
+
const plain = 'sk-ant-abc123xyz';
|
|
25
|
+
const { encrypted, iv, authTag } = encrypt(plain, masterKey);
|
|
26
|
+
// Flip bits in the auth tag — guaranteed to fail GCM authentication
|
|
27
|
+
const buf = Buffer.from(authTag, 'base64');
|
|
28
|
+
buf[0] ^= 0xff;
|
|
29
|
+
const tampered = buf.toString('base64');
|
|
30
|
+
expect(() => decrypt(encrypted, iv, tampered, masterKey)).toThrow();
|
|
31
|
+
});
|
|
32
|
+
it('handles empty string', () => {
|
|
33
|
+
const plain = '';
|
|
34
|
+
const { encrypted, iv, authTag } = encrypt(plain, masterKey);
|
|
35
|
+
const decrypted = decrypt(encrypted, iv, authTag, masterKey);
|
|
36
|
+
expect(decrypted).toBe('');
|
|
37
|
+
});
|
|
38
|
+
it('handles long keys', () => {
|
|
39
|
+
const plain = 'sk-' + 'a'.repeat(1000);
|
|
40
|
+
const { encrypted, iv, authTag } = encrypt(plain, masterKey);
|
|
41
|
+
const decrypted = decrypt(encrypted, iv, authTag, masterKey);
|
|
42
|
+
expect(decrypted).toBe(plain);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=crypto.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.test.js","sourceRoot":"","sources":["../../src/__tests__/crypto.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/file-store.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { LocalFileStore } from '../../../sandbox/src/file-store-local.js';
|
|
6
|
+
import { createFileStore } from '../../../sandbox/src/file-store.js';
|
|
7
|
+
describe('LocalFileStore', () => {
|
|
8
|
+
let dir;
|
|
9
|
+
let store;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
dir = mkdtempSync(join(tmpdir(), 'ash-filestore-'));
|
|
12
|
+
store = new LocalFileStore(dir);
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
rmSync(dir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
it('put and get round-trip', async () => {
|
|
18
|
+
const content = Buffer.from('hello world');
|
|
19
|
+
await store.put('test.txt', content);
|
|
20
|
+
const got = await store.get('test.txt');
|
|
21
|
+
expect(got).not.toBeNull();
|
|
22
|
+
expect(got.toString()).toBe('hello world');
|
|
23
|
+
});
|
|
24
|
+
it('get returns null for missing key', async () => {
|
|
25
|
+
const got = await store.get('nonexistent.txt');
|
|
26
|
+
expect(got).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
it('exists returns true for existing file', async () => {
|
|
29
|
+
await store.put('exists.txt', Buffer.from('data'));
|
|
30
|
+
expect(await store.exists('exists.txt')).toBe(true);
|
|
31
|
+
expect(await store.exists('nope.txt')).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('delete removes a file', async () => {
|
|
34
|
+
await store.put('to-delete.txt', Buffer.from('data'));
|
|
35
|
+
expect(await store.exists('to-delete.txt')).toBe(true);
|
|
36
|
+
await store.delete('to-delete.txt');
|
|
37
|
+
expect(await store.exists('to-delete.txt')).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
it('delete is a no-op for missing file', async () => {
|
|
40
|
+
// Should not throw
|
|
41
|
+
await store.delete('nonexistent.txt');
|
|
42
|
+
});
|
|
43
|
+
it('put creates nested directories', async () => {
|
|
44
|
+
await store.put('a/b/c/deep.txt', Buffer.from('deep'));
|
|
45
|
+
const got = await store.get('a/b/c/deep.txt');
|
|
46
|
+
expect(got.toString()).toBe('deep');
|
|
47
|
+
});
|
|
48
|
+
it('list returns all files', async () => {
|
|
49
|
+
await store.put('file1.txt', Buffer.from('1'));
|
|
50
|
+
await store.put('dir/file2.txt', Buffer.from('2'));
|
|
51
|
+
await store.put('dir/sub/file3.txt', Buffer.from('3'));
|
|
52
|
+
const files = await store.list();
|
|
53
|
+
const keys = files.map(f => f.key).sort();
|
|
54
|
+
expect(keys).toEqual(['dir/file2.txt', 'dir/sub/file3.txt', 'file1.txt']);
|
|
55
|
+
});
|
|
56
|
+
it('list with prefix filters to subdirectory', async () => {
|
|
57
|
+
await store.put('a/1.txt', Buffer.from('1'));
|
|
58
|
+
await store.put('a/2.txt', Buffer.from('2'));
|
|
59
|
+
await store.put('b/3.txt', Buffer.from('3'));
|
|
60
|
+
const files = await store.list('a');
|
|
61
|
+
const keys = files.map(f => f.key).sort();
|
|
62
|
+
expect(keys).toEqual(['a/1.txt', 'a/2.txt']);
|
|
63
|
+
});
|
|
64
|
+
it('list returns empty array for missing prefix', async () => {
|
|
65
|
+
const files = await store.list('nonexistent');
|
|
66
|
+
expect(files).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
it('rejects path traversal', async () => {
|
|
69
|
+
await expect(store.put('../escape.txt', Buffer.from('bad'))).rejects.toThrow('path traversal');
|
|
70
|
+
});
|
|
71
|
+
it('list includes file metadata', async () => {
|
|
72
|
+
await store.put('meta.txt', Buffer.from('some content'));
|
|
73
|
+
const files = await store.list();
|
|
74
|
+
expect(files).toHaveLength(1);
|
|
75
|
+
expect(files[0].key).toBe('meta.txt');
|
|
76
|
+
expect(files[0].size).toBe(12); // 'some content'.length
|
|
77
|
+
expect(files[0].lastModified).toBeTruthy();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('createFileStore', () => {
|
|
81
|
+
it('creates LocalFileStore for file:// URL', async () => {
|
|
82
|
+
const dir = mkdtempSync(join(tmpdir(), 'ash-fs-factory-'));
|
|
83
|
+
const store = await createFileStore(`file://${dir}`);
|
|
84
|
+
await store.put('hello.txt', Buffer.from('world'));
|
|
85
|
+
const got = await store.get('hello.txt');
|
|
86
|
+
expect(got.toString()).toBe('world');
|
|
87
|
+
rmSync(dir, { recursive: true, force: true });
|
|
88
|
+
});
|
|
89
|
+
it('throws for unsupported scheme', async () => {
|
|
90
|
+
await expect(createFileStore('ftp://bad')).rejects.toThrow('Unsupported file store URL scheme');
|
|
91
|
+
});
|
|
92
|
+
it('throws for s3:// without AWS SDK installed', async () => {
|
|
93
|
+
// S3FileStore requires @aws-sdk/client-s3 — this test verifies error handling
|
|
94
|
+
// In CI without the SDK, it should give a helpful error
|
|
95
|
+
// (If SDK IS installed, this would succeed, so we just verify it doesn't crash unexpectedly)
|
|
96
|
+
try {
|
|
97
|
+
await createFileStore('s3://test-bucket/prefix/');
|
|
98
|
+
// If it doesn't throw, the SDK is available — that's fine
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
expect(err.message).toMatch(/aws-sdk|S3/i);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=file-store.test.js.map
|