@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.
Files changed (171) hide show
  1. package/dist/__tests__/attachments.test.d.ts +2 -0
  2. package/dist/__tests__/attachments.test.d.ts.map +1 -0
  3. package/dist/__tests__/attachments.test.js +57 -0
  4. package/dist/__tests__/attachments.test.js.map +1 -0
  5. package/dist/__tests__/bundle.test.d.ts +2 -0
  6. package/dist/__tests__/bundle.test.d.ts.map +1 -0
  7. package/dist/__tests__/bundle.test.js +55 -0
  8. package/dist/__tests__/bundle.test.js.map +1 -0
  9. package/dist/__tests__/coordinator.test.d.ts +2 -0
  10. package/dist/__tests__/coordinator.test.d.ts.map +1 -0
  11. package/dist/__tests__/coordinator.test.js +283 -0
  12. package/dist/__tests__/coordinator.test.js.map +1 -0
  13. package/dist/__tests__/crypto.test.d.ts +2 -0
  14. package/dist/__tests__/crypto.test.d.ts.map +1 -0
  15. package/dist/__tests__/crypto.test.js +45 -0
  16. package/dist/__tests__/crypto.test.js.map +1 -0
  17. package/dist/__tests__/file-store.test.d.ts +2 -0
  18. package/dist/__tests__/file-store.test.d.ts.map +1 -0
  19. package/dist/__tests__/file-store.test.js +105 -0
  20. package/dist/__tests__/file-store.test.js.map +1 -0
  21. package/dist/__tests__/files.test.js +3 -3
  22. package/dist/__tests__/files.test.js.map +1 -1
  23. package/dist/__tests__/openapi.test.js +6 -3
  24. package/dist/__tests__/openapi.test.js.map +1 -1
  25. package/dist/__tests__/queue.test.d.ts +2 -0
  26. package/dist/__tests__/queue.test.d.ts.map +1 -0
  27. package/dist/__tests__/queue.test.js +151 -0
  28. package/dist/__tests__/queue.test.js.map +1 -0
  29. package/dist/__tests__/usage.test.d.ts +2 -0
  30. package/dist/__tests__/usage.test.d.ts.map +1 -0
  31. package/dist/__tests__/usage.test.js +74 -0
  32. package/dist/__tests__/usage.test.js.map +1 -0
  33. package/dist/crypto.d.ts +8 -0
  34. package/dist/crypto.d.ts.map +1 -0
  35. package/dist/crypto.js +29 -0
  36. package/dist/crypto.js.map +1 -0
  37. package/dist/db/drizzle-db.d.ts +128 -0
  38. package/dist/db/drizzle-db.d.ts.map +1 -0
  39. package/dist/db/drizzle-db.js +789 -0
  40. package/dist/db/drizzle-db.js.map +1 -0
  41. package/dist/db/index.d.ts +161 -3
  42. package/dist/db/index.d.ts.map +1 -1
  43. package/dist/db/index.js +164 -8
  44. package/dist/db/index.js.map +1 -1
  45. package/dist/db/schema.pg.d.ts +1625 -0
  46. package/dist/db/schema.pg.d.ts.map +1 -0
  47. package/dist/db/schema.pg.js +150 -0
  48. package/dist/db/schema.pg.js.map +1 -0
  49. package/dist/db/schema.sqlite.d.ts +1781 -0
  50. package/dist/db/schema.sqlite.d.ts.map +1 -0
  51. package/dist/db/schema.sqlite.js +150 -0
  52. package/dist/db/schema.sqlite.js.map +1 -0
  53. package/dist/index.js +18 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/queue/processor.d.ts +51 -0
  56. package/dist/queue/processor.d.ts.map +1 -0
  57. package/dist/queue/processor.js +98 -0
  58. package/dist/queue/processor.js.map +1 -0
  59. package/dist/routes/attachments.d.ts +3 -0
  60. package/dist/routes/attachments.d.ts.map +1 -0
  61. package/dist/routes/attachments.js +168 -0
  62. package/dist/routes/attachments.js.map +1 -0
  63. package/dist/routes/credentials.d.ts +11 -0
  64. package/dist/routes/credentials.d.ts.map +1 -0
  65. package/dist/routes/credentials.js +120 -0
  66. package/dist/routes/credentials.js.map +1 -0
  67. package/dist/routes/files.js +5 -5
  68. package/dist/routes/files.js.map +1 -1
  69. package/dist/routes/health.d.ts.map +1 -1
  70. package/dist/routes/health.js +9 -1
  71. package/dist/routes/health.js.map +1 -1
  72. package/dist/routes/queue.d.ts +3 -0
  73. package/dist/routes/queue.d.ts.map +1 -0
  74. package/dist/routes/queue.js +144 -0
  75. package/dist/routes/queue.js.map +1 -0
  76. package/dist/routes/runners.d.ts +5 -0
  77. package/dist/routes/runners.d.ts.map +1 -1
  78. package/dist/routes/runners.js +42 -5
  79. package/dist/routes/runners.js.map +1 -1
  80. package/dist/routes/sessions.d.ts +2 -1
  81. package/dist/routes/sessions.d.ts.map +1 -1
  82. package/dist/routes/sessions.js +236 -11
  83. package/dist/routes/sessions.js.map +1 -1
  84. package/dist/routes/usage.d.ts +3 -0
  85. package/dist/routes/usage.d.ts.map +1 -0
  86. package/dist/routes/usage.js +64 -0
  87. package/dist/routes/usage.js.map +1 -0
  88. package/dist/routes/workspace.d.ts +4 -0
  89. package/dist/routes/workspace.d.ts.map +1 -0
  90. package/dist/routes/workspace.js +123 -0
  91. package/dist/routes/workspace.js.map +1 -0
  92. package/dist/runner/coordinator.d.ts +77 -9
  93. package/dist/runner/coordinator.d.ts.map +1 -1
  94. package/dist/runner/coordinator.js +163 -89
  95. package/dist/runner/coordinator.js.map +1 -1
  96. package/dist/runner/local-backend.d.ts +1 -0
  97. package/dist/runner/local-backend.d.ts.map +1 -1
  98. package/dist/runner/local-backend.js +7 -0
  99. package/dist/runner/local-backend.js.map +1 -1
  100. package/dist/runner/remote-backend.d.ts +2 -0
  101. package/dist/runner/remote-backend.d.ts.map +1 -1
  102. package/dist/runner/remote-backend.js +7 -0
  103. package/dist/runner/remote-backend.js.map +1 -1
  104. package/dist/runner/runner-client.d.ts +4 -0
  105. package/dist/runner/runner-client.d.ts.map +1 -1
  106. package/dist/runner/runner-client.js +12 -0
  107. package/dist/runner/runner-client.js.map +1 -1
  108. package/dist/runner/types.d.ts +4 -0
  109. package/dist/runner/types.d.ts.map +1 -1
  110. package/dist/schemas.d.ts.map +1 -1
  111. package/dist/schemas.js +115 -1
  112. package/dist/schemas.js.map +1 -1
  113. package/dist/telemetry/exporter.d.ts +16 -0
  114. package/dist/telemetry/exporter.d.ts.map +1 -0
  115. package/dist/telemetry/exporter.js +89 -0
  116. package/dist/telemetry/exporter.js.map +1 -0
  117. package/dist/usage/extractor.d.ts +18 -0
  118. package/dist/usage/extractor.d.ts.map +1 -0
  119. package/dist/usage/extractor.js +48 -0
  120. package/dist/usage/extractor.js.map +1 -0
  121. package/drizzle/pg/0000_thick_loners.sql +75 -0
  122. package/drizzle/pg/0001_rare_lester.sql +13 -0
  123. package/drizzle/pg/0002_short_shinko_yamashiro.sql +1 -0
  124. package/drizzle/pg/0003_remarkable_mastermind.sql +14 -0
  125. package/drizzle/pg/0004_warm_reaper.sql +18 -0
  126. package/drizzle/pg/0005_overconfident_mole_man.sql +14 -0
  127. package/drizzle/pg/0006_third_shiva.sql +13 -0
  128. package/drizzle/pg/0007_keen_shockwave.sql +2 -0
  129. package/drizzle/pg/meta/0000_snapshot.json +648 -0
  130. package/drizzle/pg/meta/0001_snapshot.json +743 -0
  131. package/drizzle/pg/meta/0002_snapshot.json +749 -0
  132. package/drizzle/pg/meta/0003_snapshot.json +841 -0
  133. package/drizzle/pg/meta/0004_snapshot.json +974 -0
  134. package/drizzle/pg/meta/0005_snapshot.json +1079 -0
  135. package/drizzle/pg/meta/0006_snapshot.json +1193 -0
  136. package/drizzle/pg/meta/0007_snapshot.json +1199 -0
  137. package/drizzle/pg/meta/_journal.json +62 -0
  138. package/drizzle/sqlite/0000_massive_kinsey_walden.sql +75 -0
  139. package/drizzle/sqlite/0001_quiet_phantom_reporter.sql +13 -0
  140. package/drizzle/sqlite/0002_broad_sheva_callister.sql +1 -0
  141. package/drizzle/sqlite/0003_thankful_agent_brand.sql +14 -0
  142. package/drizzle/sqlite/0004_productive_wolverine.sql +18 -0
  143. package/drizzle/sqlite/0005_chilly_carlie_cooper.sql +14 -0
  144. package/drizzle/sqlite/0006_workable_starfox.sql +13 -0
  145. package/drizzle/sqlite/0007_quick_hemingway.sql +19 -0
  146. package/drizzle/sqlite/meta/0000_snapshot.json +503 -0
  147. package/drizzle/sqlite/meta/0001_snapshot.json +587 -0
  148. package/drizzle/sqlite/meta/0002_snapshot.json +594 -0
  149. package/drizzle/sqlite/meta/0003_snapshot.json +685 -0
  150. package/drizzle/sqlite/meta/0004_snapshot.json +807 -0
  151. package/drizzle/sqlite/meta/0005_snapshot.json +897 -0
  152. package/drizzle/sqlite/meta/0006_snapshot.json +981 -0
  153. package/drizzle/sqlite/meta/0007_snapshot.json +988 -0
  154. package/drizzle/sqlite/meta/_journal.json +62 -0
  155. package/package.json +10 -5
  156. package/dist/__tests__/schema.test.d.ts +0 -2
  157. package/dist/__tests__/schema.test.d.ts.map +0 -1
  158. package/dist/__tests__/schema.test.js +0 -31
  159. package/dist/__tests__/schema.test.js.map +0 -1
  160. package/dist/db/dump-schema.d.ts +0 -10
  161. package/dist/db/dump-schema.d.ts.map +0 -1
  162. package/dist/db/dump-schema.js +0 -64
  163. package/dist/db/dump-schema.js.map +0 -1
  164. package/dist/db/pg.d.ts +0 -35
  165. package/dist/db/pg.d.ts.map +0 -1
  166. package/dist/db/pg.js +0 -272
  167. package/dist/db/pg.js.map +0 -1
  168. package/dist/db/sqlite.d.ts +0 -34
  169. package/dist/db/sqlite.d.ts.map +0 -1
  170. package/dist/db/sqlite.js +0 -296
  171. package/dist/db/sqlite.js.map +0 -1
@@ -0,0 +1,123 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync, rmSync } from 'node:fs';
3
+ import { getSession } from '../db/index.js';
4
+ import { createBundle, extractBundle, hasPersistedState, restoreSessionState } from '@ash-ai/sandbox';
5
+ /** Max body size for workspace uploads: ~134MB base64 ≈ 100MB binary. */
6
+ const WORKSPACE_BODY_LIMIT = 134 * 1024 * 1024;
7
+ export function workspaceRoutes(app, coordinator, dataDir) {
8
+ // Download workspace as tar.gz bundle
9
+ app.get('/api/sessions/:id/workspace', {
10
+ schema: {
11
+ tags: ['sessions'],
12
+ params: {
13
+ type: 'object',
14
+ properties: { id: { type: 'string', format: 'uuid' } },
15
+ required: ['id'],
16
+ },
17
+ response: {
18
+ 404: { $ref: 'ApiError#' },
19
+ },
20
+ },
21
+ }, async (req, reply) => {
22
+ const session = await getSession(req.params.id);
23
+ if (!session || session.tenantId !== req.tenantId) {
24
+ return reply.status(404).send({ error: 'Session not found', statusCode: 404 });
25
+ }
26
+ // Try live sandbox workspace first
27
+ let workspaceDir = null;
28
+ let tempDir = null;
29
+ try {
30
+ const backend = await coordinator.getBackendForRunnerAsync(session.runnerId);
31
+ const sandbox = backend.getSandbox(session.sandboxId);
32
+ if (sandbox) {
33
+ workspaceDir = sandbox.workspaceDir;
34
+ }
35
+ }
36
+ catch { /* runner may be gone */ }
37
+ // Fall back to persisted snapshot
38
+ if (!workspaceDir || !existsSync(workspaceDir)) {
39
+ const snapshotDir = join(dataDir, 'sessions', session.id, 'workspace');
40
+ if (existsSync(snapshotDir)) {
41
+ workspaceDir = snapshotDir;
42
+ }
43
+ else if (hasPersistedState(dataDir, session.id, session.tenantId)) {
44
+ tempDir = join(dataDir, 'tmp', `bundle-${session.id}-${Date.now()}`);
45
+ restoreSessionState(dataDir, session.id, tempDir, session.tenantId);
46
+ workspaceDir = tempDir;
47
+ }
48
+ }
49
+ if (!workspaceDir || !existsSync(workspaceDir)) {
50
+ return reply.status(404).send({ error: 'No workspace available for this session', statusCode: 404 });
51
+ }
52
+ try {
53
+ const bundle = createBundle(workspaceDir);
54
+ return reply
55
+ .header('Content-Type', 'application/gzip')
56
+ .header('Content-Disposition', `attachment; filename="${session.id}.tar.gz"`)
57
+ .send(bundle);
58
+ }
59
+ finally {
60
+ // Clean up temp directory if we created one
61
+ if (tempDir) {
62
+ try {
63
+ rmSync(tempDir, { recursive: true, force: true });
64
+ }
65
+ catch { /* best effort */ }
66
+ }
67
+ }
68
+ });
69
+ // Upload/restore workspace from tar.gz bundle
70
+ app.post('/api/sessions/:id/workspace', {
71
+ bodyLimit: WORKSPACE_BODY_LIMIT,
72
+ schema: {
73
+ tags: ['sessions'],
74
+ params: {
75
+ type: 'object',
76
+ properties: { id: { type: 'string', format: 'uuid' } },
77
+ required: ['id'],
78
+ },
79
+ body: {
80
+ type: 'object',
81
+ properties: {
82
+ bundle: { type: 'string', description: 'Base64-encoded tar.gz bundle' },
83
+ },
84
+ required: ['bundle'],
85
+ },
86
+ response: {
87
+ 200: {
88
+ type: 'object',
89
+ properties: {
90
+ message: { type: 'string' },
91
+ },
92
+ },
93
+ 400: { $ref: 'ApiError#' },
94
+ 404: { $ref: 'ApiError#' },
95
+ },
96
+ },
97
+ }, async (req, reply) => {
98
+ const session = await getSession(req.params.id);
99
+ if (!session || session.tenantId !== req.tenantId) {
100
+ return reply.status(404).send({ error: 'Session not found', statusCode: 404 });
101
+ }
102
+ const { bundle: b64 } = req.body;
103
+ const bundleBuffer = Buffer.from(b64, 'base64');
104
+ if (bundleBuffer.length === 0) {
105
+ return reply.status(400).send({ error: 'Empty bundle', statusCode: 400 });
106
+ }
107
+ // Extract into live sandbox workspace if available
108
+ try {
109
+ const backend = await coordinator.getBackendForRunnerAsync(session.runnerId);
110
+ const sandbox = backend.getSandbox(session.sandboxId);
111
+ if (sandbox) {
112
+ extractBundle(bundleBuffer, sandbox.workspaceDir);
113
+ return reply.send({ message: 'Workspace restored to live sandbox' });
114
+ }
115
+ }
116
+ catch { /* runner may be gone */ }
117
+ // Fall back to persisting as a snapshot
118
+ const snapshotDir = join(dataDir, 'sessions', session.id, 'workspace');
119
+ extractBundle(bundleBuffer, snapshotDir);
120
+ return reply.send({ message: 'Workspace saved as snapshot' });
121
+ });
122
+ }
123
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/routes/workspace.ts"],"names":[],"mappings":"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"}
@@ -3,50 +3,118 @@ import type { RunnerBackend } from './types.js';
3
3
  /**
4
4
  * Coordinates multiple runners. Routes session creation to the least-loaded runner.
5
5
  * Detects dead runners and marks their sessions as paused.
6
+ *
7
+ * In multi-coordinator mode, the runner registry lives in the shared database
8
+ * (Postgres/CRDB). Any coordinator can discover all healthy runners by querying
9
+ * the DB. The in-memory `backends` map is a local connection cache — each
10
+ * coordinator creates RemoteRunnerBackend instances on demand when it first
11
+ * needs to talk to a runner it hasn't seen before.
12
+ *
13
+ * Design principles:
14
+ * - DB is the source of truth for runner discovery (multi-coordinator safe)
15
+ * - Local Map is a connection cache only (avoids creating new HTTP clients per request)
16
+ * - All write operations (register, heartbeat, delete) go through DB
17
+ * - All read operations for routing go through DB (selectBestRunner)
18
+ * - Liveness sweep is idempotent (safe to run on multiple coordinators)
6
19
  */
7
20
  export declare class RunnerCoordinator {
8
- private runners;
21
+ /** Local connection cache: runnerId -> RemoteRunnerBackend. Lazily populated from DB. */
22
+ private backends;
9
23
  private localBackend;
10
24
  private localRunnerId;
11
25
  private livenessSweepTimer;
12
26
  constructor(opts: {
13
27
  localBackend?: RunnerBackend;
14
28
  });
29
+ /**
30
+ * Register or re-register a runner. Persists to DB so all coordinators see it.
31
+ */
15
32
  registerRunner(info: {
16
33
  runnerId: string;
17
34
  host: string;
18
35
  port: number;
19
36
  maxSandboxes: number;
20
- }): void;
21
- heartbeat(runnerId: string, stats: PoolStats): void;
37
+ }): Promise<void>;
38
+ /**
39
+ * Process a heartbeat. Updates DB so all coordinators see fresh capacity stats.
40
+ */
41
+ heartbeat(runnerId: string, stats: PoolStats): Promise<void>;
42
+ /**
43
+ * Graceful deregistration. Called when a runner shuts down cleanly.
44
+ * Immediately pauses its sessions and removes it from the registry,
45
+ * rather than waiting 30s for the liveness sweep to notice.
46
+ */
47
+ deregisterRunner(runnerId: string): Promise<void>;
22
48
  /**
23
49
  * Select the best backend for a new session.
24
- * Returns the least-loaded runner, or local backend if no runners available.
50
+ * Reads from DB to discover all healthy runners (multi-coordinator safe).
51
+ * Falls back to local backend if no remote runners available.
25
52
  */
26
- selectBackend(): {
53
+ selectBackend(): Promise<{
27
54
  backend: RunnerBackend;
28
55
  runnerId: string;
29
- };
56
+ }>;
30
57
  /**
31
58
  * Get the backend for a specific runner. Used for routing messages to existing sessions.
59
+ *
60
+ * Synchronous fast path: checks local cache first. If not found, falls back to local
61
+ * backend (standalone mode) or throws. For multi-coordinator mode where a runner was
62
+ * registered by a different coordinator, use getBackendForRunnerAsync().
32
63
  */
33
64
  getBackendForRunner(runnerId: string | null | undefined): RunnerBackend;
65
+ /**
66
+ * Async version: looks up runner from DB if not in local cache.
67
+ * Required in multi-coordinator mode where a different coordinator may have
68
+ * registered the runner. Creates a RemoteRunnerBackend on the fly from the
69
+ * DB record and caches it locally.
70
+ */
71
+ getBackendForRunnerAsync(runnerId: string | null | undefined): Promise<RunnerBackend>;
72
+ /**
73
+ * Get or lazily create a RemoteRunnerBackend from a DB record.
74
+ * The backends map is a local connection cache — avoids creating
75
+ * new HTTP clients on every request.
76
+ */
77
+ private getOrCreateBackend;
34
78
  /**
35
79
  * Start periodic liveness checks for remote runners.
80
+ * Safe to run on multiple coordinators — all operations are idempotent.
81
+ * Each coordinator runs independently; no leader election needed.
82
+ *
83
+ * Uses random jitter (0-5s) on each interval to prevent thundering herd
84
+ * when multiple coordinators run the same sweep at the same time.
36
85
  */
37
86
  startLivenessSweep(): void;
38
87
  stopLivenessSweep(): void;
88
+ /**
89
+ * Single-query dead runner detection. Queries directly for runners past
90
+ * the heartbeat cutoff instead of listing ALL runners then filtering in JS.
91
+ *
92
+ * Cache cleanup: stale backend cache entries are cleaned up in handleDeadRunner()
93
+ * (for this coordinator's kills) and lazily on next use via getOrCreateBackend()
94
+ * (for kills by other coordinators). No need to list all runners every sweep.
95
+ */
39
96
  private checkLiveness;
97
+ /**
98
+ * Handle a dead runner: pause its sessions and remove from registry.
99
+ * Idempotent — safe to call from multiple coordinators concurrently.
100
+ * Uses a single bulk UPDATE instead of per-session queries.
101
+ */
40
102
  handleDeadRunner(runnerId: string): Promise<void>;
41
103
  get runnerCount(): number;
42
104
  get hasLocalBackend(): boolean;
43
- getRunnerInfo(): Array<{
105
+ /**
106
+ * Get runner info from DB (not just local cache).
107
+ * Any coordinator gets the same view. Use this for monitoring/admin.
108
+ */
109
+ getRunnerInfoFromDb(): Promise<Array<{
44
110
  runnerId: string;
45
111
  host: string;
46
112
  port: number;
47
113
  active: number;
48
114
  max: number;
49
- lastHeartbeat: number;
50
- }>;
115
+ lastHeartbeat: string;
116
+ }>>;
117
+ /** Number of locally cached backend connections. */
118
+ get cachedBackendCount(): number;
51
119
  }
52
120
  //# sourceMappingURL=coordinator.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"coordinator.d.ts","sourceRoot":"","sources":["../../src/runner/coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,gBAAgB,CAAC;AAE9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAiB;IAC5B,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAA0C;IAC1D,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;;OAEG;IACG,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,OAAO,CAAC,IAAI,CAAC;IAajH;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE;;;;OAIG;IACG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAmB5E;;;;;;OAMG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa;IAevE;;;;;OAKG;IACG,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC;IAqB3F;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;;;;OAOG;IACH,kBAAkB,IAAI,IAAI;IAe1B,iBAAiB,IAAI,IAAI;IAOzB;;;;;;;OAOG;YACW,aAAa;IAS3B;;;;OAIG;IACG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvD,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED;;;OAGG;IACG,mBAAmB,IAAI,OAAO,CAAC,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,CAAC;IAYjJ,oDAAoD;IACpD,IAAI,kBAAkB,IAAI,MAAM,CAE/B;CACF"}
@@ -1,73 +1,76 @@
1
1
  import { RUNNER_LIVENESS_TIMEOUT_MS } from '@ash-ai/shared';
2
2
  import { RemoteRunnerBackend } from './remote-backend.js';
3
- import { updateSessionStatus, listSessionsByRunner } from '../db/index.js';
3
+ import { bulkPauseSessionsByRunner, upsertRunner, heartbeatRunner, selectBestRunner, deleteRunner, listAllRunners, listDeadRunners, getRunner } from '../db/index.js';
4
4
  /**
5
5
  * Coordinates multiple runners. Routes session creation to the least-loaded runner.
6
6
  * Detects dead runners and marks their sessions as paused.
7
+ *
8
+ * In multi-coordinator mode, the runner registry lives in the shared database
9
+ * (Postgres/CRDB). Any coordinator can discover all healthy runners by querying
10
+ * the DB. The in-memory `backends` map is a local connection cache — each
11
+ * coordinator creates RemoteRunnerBackend instances on demand when it first
12
+ * needs to talk to a runner it hasn't seen before.
13
+ *
14
+ * Design principles:
15
+ * - DB is the source of truth for runner discovery (multi-coordinator safe)
16
+ * - Local Map is a connection cache only (avoids creating new HTTP clients per request)
17
+ * - All write operations (register, heartbeat, delete) go through DB
18
+ * - All read operations for routing go through DB (selectBestRunner)
19
+ * - Liveness sweep is idempotent (safe to run on multiple coordinators)
7
20
  */
8
21
  export class RunnerCoordinator {
9
- runners = new Map();
22
+ /** Local connection cache: runnerId -> RemoteRunnerBackend. Lazily populated from DB. */
23
+ backends = new Map();
10
24
  localBackend;
11
25
  localRunnerId = '__local__';
12
26
  livenessSweepTimer = null;
13
27
  constructor(opts) {
14
28
  this.localBackend = opts.localBackend ?? null;
15
29
  }
16
- registerRunner(info) {
17
- const existing = this.runners.get(info.runnerId);
30
+ /**
31
+ * Register or re-register a runner. Persists to DB so all coordinators see it.
32
+ */
33
+ async registerRunner(info) {
34
+ // Persist to DB (upsert — idempotent, safe for concurrent coordinators)
35
+ await upsertRunner(info.runnerId, info.host, info.port, info.maxSandboxes);
36
+ // Update local backend cache
37
+ const existing = this.backends.get(info.runnerId);
18
38
  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;
39
+ existing.close();
26
40
  }
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
- });
41
+ this.backends.set(info.runnerId, new RemoteRunnerBackend({ host: info.host, port: info.port }));
34
42
  console.log(`[coordinator] Runner ${info.runnerId} registered at ${info.host}:${info.port} (max ${info.maxSandboxes})`);
35
43
  }
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
+ * Process a heartbeat. Updates DB so all coordinators see fresh capacity stats.
46
+ */
47
+ async heartbeat(runnerId, stats) {
48
+ await heartbeatRunner(runnerId, stats.running ?? 0, stats.warming ?? 0);
49
+ }
50
+ /**
51
+ * Graceful deregistration. Called when a runner shuts down cleanly.
52
+ * Immediately pauses its sessions and removes it from the registry,
53
+ * rather than waiting 30s for the liveness sweep to notice.
54
+ */
55
+ async deregisterRunner(runnerId) {
56
+ console.log(`[coordinator] Runner ${runnerId} deregistering gracefully`);
57
+ await this.handleDeadRunner(runnerId);
44
58
  }
45
59
  /**
46
60
  * Select the best backend for a new session.
47
- * Returns the least-loaded runner, or local backend if no runners available.
61
+ * Reads from DB to discover all healthy runners (multi-coordinator safe).
62
+ * Falls back to local backend if no remote runners available.
48
63
  */
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
- }
64
+ async selectBackend() {
65
+ const cutoff = new Date(Date.now() - RUNNER_LIVENESS_TIMEOUT_MS).toISOString();
66
+ const bestRunner = await selectBestRunner(cutoff);
67
+ if (bestRunner) {
68
+ // selectBestRunner() already orders by available capacity DESC and only
69
+ // returns healthy runners. Trust the query — no redundant capacity check.
70
+ const backend = this.getOrCreateBackend(bestRunner);
71
+ return { backend, runnerId: bestRunner.id };
69
72
  }
70
- // Fall back to local backend
73
+ // Fall back to local backend (standalone mode)
71
74
  if (this.localBackend) {
72
75
  return { backend: this.localBackend, runnerId: this.localRunnerId };
73
76
  }
@@ -75,6 +78,10 @@ export class RunnerCoordinator {
75
78
  }
76
79
  /**
77
80
  * Get the backend for a specific runner. Used for routing messages to existing sessions.
81
+ *
82
+ * Synchronous fast path: checks local cache first. If not found, falls back to local
83
+ * backend (standalone mode) or throws. For multi-coordinator mode where a runner was
84
+ * registered by a different coordinator, use getBackendForRunnerAsync().
78
85
  */
79
86
  getBackendForRunner(runnerId) {
80
87
  if (!runnerId || runnerId === this.localRunnerId) {
@@ -82,25 +89,76 @@ export class RunnerCoordinator {
82
89
  return this.localBackend;
83
90
  throw new Error('No local backend configured');
84
91
  }
85
- const runner = this.runners.get(runnerId);
86
- if (!runner) {
87
- // Runner gone — fall back to local if available
92
+ const cached = this.backends.get(runnerId);
93
+ if (cached && !cached.closed)
94
+ return cached;
95
+ // Not in cache. In standalone mode, fall back to local.
96
+ // In coordinator mode, caller should use getBackendForRunnerAsync().
97
+ if (this.localBackend)
98
+ return this.localBackend;
99
+ throw new Error(`Runner ${runnerId} not found in local cache — use getBackendForRunnerAsync() in multi-coordinator mode`);
100
+ }
101
+ /**
102
+ * Async version: looks up runner from DB if not in local cache.
103
+ * Required in multi-coordinator mode where a different coordinator may have
104
+ * registered the runner. Creates a RemoteRunnerBackend on the fly from the
105
+ * DB record and caches it locally.
106
+ */
107
+ async getBackendForRunnerAsync(runnerId) {
108
+ if (!runnerId || runnerId === this.localRunnerId) {
88
109
  if (this.localBackend)
89
110
  return this.localBackend;
90
- throw new Error(`Runner ${runnerId} not found`);
111
+ throw new Error('No local backend configured');
112
+ }
113
+ // Fast path: already cached
114
+ const cached = this.backends.get(runnerId);
115
+ if (cached && !cached.closed)
116
+ return cached;
117
+ // Slow path: look up from shared DB
118
+ const record = await getRunner(runnerId);
119
+ if (record) {
120
+ return this.getOrCreateBackend(record);
91
121
  }
92
- return runner.backend;
122
+ // Runner gone — fall back to local if available
123
+ if (this.localBackend)
124
+ return this.localBackend;
125
+ throw new Error(`Runner ${runnerId} not found`);
126
+ }
127
+ /**
128
+ * Get or lazily create a RemoteRunnerBackend from a DB record.
129
+ * The backends map is a local connection cache — avoids creating
130
+ * new HTTP clients on every request.
131
+ */
132
+ getOrCreateBackend(record) {
133
+ let backend = this.backends.get(record.id);
134
+ if (backend && !backend.closed)
135
+ return backend;
136
+ backend = new RemoteRunnerBackend({ host: record.host, port: record.port });
137
+ this.backends.set(record.id, backend);
138
+ return backend;
93
139
  }
94
140
  /**
95
141
  * Start periodic liveness checks for remote runners.
142
+ * Safe to run on multiple coordinators — all operations are idempotent.
143
+ * Each coordinator runs independently; no leader election needed.
144
+ *
145
+ * Uses random jitter (0-5s) on each interval to prevent thundering herd
146
+ * when multiple coordinators run the same sweep at the same time.
96
147
  */
97
148
  startLivenessSweep() {
98
149
  if (this.livenessSweepTimer)
99
150
  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();
151
+ const scheduleNext = () => {
152
+ // Add 0-5s random jitter to prevent thundering herd across coordinators
153
+ const jitter = Math.floor(Math.random() * 5000);
154
+ this.livenessSweepTimer = setTimeout(() => {
155
+ this.checkLiveness()
156
+ .catch((err) => console.error('[coordinator] Liveness sweep error:', err))
157
+ .finally(() => scheduleNext());
158
+ }, RUNNER_LIVENESS_TIMEOUT_MS + jitter);
159
+ this.livenessSweepTimer.unref();
160
+ };
161
+ scheduleNext();
104
162
  }
105
163
  stopLivenessSweep() {
106
164
  if (this.livenessSweepTimer) {
@@ -108,50 +166,66 @@ export class RunnerCoordinator {
108
166
  this.livenessSweepTimer = null;
109
167
  }
110
168
  }
169
+ /**
170
+ * Single-query dead runner detection. Queries directly for runners past
171
+ * the heartbeat cutoff instead of listing ALL runners then filtering in JS.
172
+ *
173
+ * Cache cleanup: stale backend cache entries are cleaned up in handleDeadRunner()
174
+ * (for this coordinator's kills) and lazily on next use via getOrCreateBackend()
175
+ * (for kills by other coordinators). No need to list all runners every sweep.
176
+ */
111
177
  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
- }
178
+ const cutoff = new Date(Date.now() - RUNNER_LIVENESS_TIMEOUT_MS).toISOString();
179
+ const deadRunners = await listDeadRunners(cutoff);
180
+ for (const runner of deadRunners) {
181
+ console.warn(`[coordinator] Runner ${runner.id} missed heartbeat — marking sessions paused`);
182
+ await this.handleDeadRunner(runner.id);
118
183
  }
119
184
  }
185
+ /**
186
+ * Handle a dead runner: pause its sessions and remove from registry.
187
+ * Idempotent — safe to call from multiple coordinators concurrently.
188
+ * Uses a single bulk UPDATE instead of per-session queries.
189
+ */
120
190
  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
- }
191
+ // Single bulk UPDATE no N+1
192
+ const pausedCount = await bulkPauseSessionsByRunner(runnerId);
193
+ if (pausedCount > 0) {
194
+ console.log(`[coordinator] Paused ${pausedCount} session(s) on runner ${runnerId}`);
128
195
  }
129
- // Remove the runner
130
- const runner = this.runners.get(runnerId);
131
- if (runner) {
132
- runner.backend.close();
133
- this.runners.delete(runnerId);
196
+ // Remove from DB (all coordinators will see this)
197
+ await deleteRunner(runnerId);
198
+ // Clean up local cache
199
+ const backend = this.backends.get(runnerId);
200
+ if (backend) {
201
+ backend.close();
202
+ this.backends.delete(runnerId);
134
203
  }
135
204
  }
136
205
  get runnerCount() {
137
- return this.runners.size;
206
+ return this.backends.size;
138
207
  }
139
208
  get hasLocalBackend() {
140
209
  return this.localBackend !== null;
141
210
  }
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;
211
+ /**
212
+ * Get runner info from DB (not just local cache).
213
+ * Any coordinator gets the same view. Use this for monitoring/admin.
214
+ */
215
+ async getRunnerInfoFromDb() {
216
+ const allRunners = await listAllRunners();
217
+ return allRunners.map((r) => ({
218
+ runnerId: r.id,
219
+ host: r.host,
220
+ port: r.port,
221
+ active: r.activeCount,
222
+ max: r.maxSandboxes,
223
+ lastHeartbeat: r.lastHeartbeatAt,
224
+ }));
225
+ }
226
+ /** Number of locally cached backend connections. */
227
+ get cachedBackendCount() {
228
+ return this.backends.size;
155
229
  }
156
230
  }
157
231
  //# sourceMappingURL=coordinator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"coordinator.js","sourceRoot":"","sources":["../../src/runner/coordinator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"coordinator.js","sourceRoot":"","sources":["../../src/runner/coordinator.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ export declare class LocalRunnerBackend implements RunnerBackend {
13
13
  destroySandbox(sandboxId: string): Promise<void>;
14
14
  destroyAll(): Promise<void>;
15
15
  sendCommand(sandboxId: string, cmd: BridgeCommand): AsyncGenerator<BridgeEvent>;
16
+ interrupt(sandboxId: string): void;
16
17
  getSandbox(sandboxId: string): SandboxHandle | undefined;
17
18
  isSandboxAlive(sandboxId: string): boolean;
18
19
  markRunning(sandboxId: string): void;