@cat-factory/node-server 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/LICENSE +21 -0
  2. package/dist/config.d.ts +3 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +297 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/container.d.ts +88 -0
  7. package/dist/container.d.ts.map +1 -0
  8. package/dist/container.js +937 -0
  9. package/dist/container.js.map +1 -0
  10. package/dist/db/client.d.ts +13 -0
  11. package/dist/db/client.d.ts.map +1 -0
  12. package/dist/db/client.js +21 -0
  13. package/dist/db/client.js.map +1 -0
  14. package/dist/db/migrate.d.ts +12 -0
  15. package/dist/db/migrate.d.ts.map +1 -0
  16. package/dist/db/migrate.js +40 -0
  17. package/dist/db/migrate.js.map +1 -0
  18. package/dist/db/schema.d.ts +7858 -0
  19. package/dist/db/schema.d.ts.map +1 -0
  20. package/dist/db/schema.js +928 -0
  21. package/dist/db/schema.js.map +1 -0
  22. package/dist/environments.d.ts +11 -0
  23. package/dist/environments.d.ts.map +1 -0
  24. package/dist/environments.js +31 -0
  25. package/dist/environments.js.map +1 -0
  26. package/dist/execution/bootstrapRunner.d.ts +27 -0
  27. package/dist/execution/bootstrapRunner.d.ts.map +1 -0
  28. package/dist/execution/bootstrapRunner.js +79 -0
  29. package/dist/execution/bootstrapRunner.js.map +1 -0
  30. package/dist/execution/config.d.ts +37 -0
  31. package/dist/execution/config.d.ts.map +1 -0
  32. package/dist/execution/config.js +86 -0
  33. package/dist/execution/config.js.map +1 -0
  34. package/dist/execution/drive.d.ts +6 -0
  35. package/dist/execution/drive.d.ts.map +1 -0
  36. package/dist/execution/drive.js +13 -0
  37. package/dist/execution/drive.js.map +1 -0
  38. package/dist/execution/pgBossRunner.d.ts +82 -0
  39. package/dist/execution/pgBossRunner.d.ts.map +1 -0
  40. package/dist/execution/pgBossRunner.js +163 -0
  41. package/dist/execution/pgBossRunner.js.map +1 -0
  42. package/dist/gateways.d.ts +4 -0
  43. package/dist/gateways.d.ts.map +1 -0
  44. package/dist/gateways.js +91 -0
  45. package/dist/gateways.js.map +1 -0
  46. package/dist/index.d.ts +13 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +22 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/main.d.ts +2 -0
  51. package/dist/main.d.ts.map +1 -0
  52. package/dist/main.js +9 -0
  53. package/dist/main.js.map +1 -0
  54. package/dist/modelProvider.d.ts +6 -0
  55. package/dist/modelProvider.d.ts.map +1 -0
  56. package/dist/modelProvider.js +72 -0
  57. package/dist/modelProvider.js.map +1 -0
  58. package/dist/realtime.d.ts +62 -0
  59. package/dist/realtime.d.ts.map +1 -0
  60. package/dist/realtime.js +171 -0
  61. package/dist/realtime.js.map +1 -0
  62. package/dist/recurring.d.ts +11 -0
  63. package/dist/recurring.d.ts.map +1 -0
  64. package/dist/recurring.js +33 -0
  65. package/dist/recurring.js.map +1 -0
  66. package/dist/repositories/bootstrap.d.ts +25 -0
  67. package/dist/repositories/bootstrap.d.ts.map +1 -0
  68. package/dist/repositories/bootstrap.js +280 -0
  69. package/dist/repositories/bootstrap.js.map +1 -0
  70. package/dist/repositories/containerExecution.d.ts +33 -0
  71. package/dist/repositories/containerExecution.d.ts.map +1 -0
  72. package/dist/repositories/containerExecution.js +199 -0
  73. package/dist/repositories/containerExecution.js.map +1 -0
  74. package/dist/repositories/documents.d.ts +31 -0
  75. package/dist/repositories/documents.d.ts.map +1 -0
  76. package/dist/repositories/documents.js +176 -0
  77. package/dist/repositories/documents.js.map +1 -0
  78. package/dist/repositories/drizzle.d.ts +105 -0
  79. package/dist/repositories/drizzle.d.ts.map +1 -0
  80. package/dist/repositories/drizzle.js +1872 -0
  81. package/dist/repositories/drizzle.js.map +1 -0
  82. package/dist/repositories/environments.d.ts +23 -0
  83. package/dist/repositories/environments.d.ts.map +1 -0
  84. package/dist/repositories/environments.js +162 -0
  85. package/dist/repositories/environments.js.map +1 -0
  86. package/dist/repositories/fragments.d.ts +23 -0
  87. package/dist/repositories/fragments.d.ts.map +1 -0
  88. package/dist/repositories/fragments.js +190 -0
  89. package/dist/repositories/fragments.js.map +1 -0
  90. package/dist/repositories/github.d.ts +53 -0
  91. package/dist/repositories/github.d.ts.map +1 -0
  92. package/dist/repositories/github.js +441 -0
  93. package/dist/repositories/github.js.map +1 -0
  94. package/dist/repositories/localModelEndpoint.d.ts +12 -0
  95. package/dist/repositories/localModelEndpoint.d.ts.map +1 -0
  96. package/dist/repositories/localModelEndpoint.js +75 -0
  97. package/dist/repositories/localModelEndpoint.js.map +1 -0
  98. package/dist/repositories/notifications.d.ts +11 -0
  99. package/dist/repositories/notifications.d.ts.map +1 -0
  100. package/dist/repositories/notifications.js +88 -0
  101. package/dist/repositories/notifications.js.map +1 -0
  102. package/dist/repositories/personalSubscription.d.ts +22 -0
  103. package/dist/repositories/personalSubscription.d.ts.map +1 -0
  104. package/dist/repositories/personalSubscription.js +159 -0
  105. package/dist/repositories/personalSubscription.js.map +1 -0
  106. package/dist/repositories/providerApiKey.d.ts +18 -0
  107. package/dist/repositories/providerApiKey.d.ts.map +1 -0
  108. package/dist/repositories/providerApiKey.js +111 -0
  109. package/dist/repositories/providerApiKey.js.map +1 -0
  110. package/dist/repositories/providerSubscription.d.ts +16 -0
  111. package/dist/repositories/providerSubscription.d.ts.map +1 -0
  112. package/dist/repositories/providerSubscription.js +88 -0
  113. package/dist/repositories/providerSubscription.js.map +1 -0
  114. package/dist/repositories/slack.d.ts +23 -0
  115. package/dist/repositories/slack.d.ts.map +1 -0
  116. package/dist/repositories/slack.js +150 -0
  117. package/dist/repositories/slack.js.map +1 -0
  118. package/dist/repositories/tasks.d.ts +24 -0
  119. package/dist/repositories/tasks.d.ts.map +1 -0
  120. package/dist/repositories/tasks.js +194 -0
  121. package/dist/repositories/tasks.js.map +1 -0
  122. package/dist/retention.d.ts +38 -0
  123. package/dist/retention.d.ts.map +1 -0
  124. package/dist/retention.js +53 -0
  125. package/dist/retention.js.map +1 -0
  126. package/dist/runtime.d.ts +10 -0
  127. package/dist/runtime.d.ts.map +1 -0
  128. package/dist/runtime.js +13 -0
  129. package/dist/runtime.js.map +1 -0
  130. package/dist/server.d.ts +41 -0
  131. package/dist/server.d.ts.map +1 -0
  132. package/dist/server.js +138 -0
  133. package/dist/server.js.map +1 -0
  134. package/dist/tasks/JiraProvider.d.ts +27 -0
  135. package/dist/tasks/JiraProvider.d.ts.map +1 -0
  136. package/dist/tasks/JiraProvider.js +79 -0
  137. package/dist/tasks/JiraProvider.js.map +1 -0
  138. package/drizzle/20260622175812_flashy_maginty/migration.sql +689 -0
  139. package/drizzle/20260622175812_flashy_maginty/snapshot.json +8318 -0
  140. package/drizzle/20260623172634_loud_wallop/migration.sql +11 -0
  141. package/drizzle/20260623172634_loud_wallop/snapshot.json +8439 -0
  142. package/drizzle/20260623174706_acoustic_zemo/migration.sql +16 -0
  143. package/drizzle/20260623174706_acoustic_zemo/snapshot.json +8506 -0
  144. package/drizzle/20260623184400_silent_cardiac/migration.sql +24 -0
  145. package/drizzle/20260623184400_silent_cardiac/snapshot.json +8639 -0
  146. package/drizzle/20260623205323_quick_arclight/migration.sql +1 -0
  147. package/drizzle/20260623205323_quick_arclight/snapshot.json +8963 -0
  148. package/drizzle/20260623221910_black_zombie/migration.sql +22 -0
  149. package/drizzle/20260623221910_black_zombie/snapshot.json +9189 -0
  150. package/drizzle/20260624131343_far_lily_hollister/migration.sql +3 -0
  151. package/drizzle/20260624131343_far_lily_hollister/snapshot.json +9228 -0
  152. package/drizzle/20260624135452_tiny_norman_osborn/migration.sql +11 -0
  153. package/drizzle/20260624135452_tiny_norman_osborn/snapshot.json +9126 -0
  154. package/drizzle/20260624140138_wandering_avengers/migration.sql +1 -0
  155. package/drizzle/20260624140138_wandering_avengers/snapshot.json +9045 -0
  156. package/package.json +62 -0
@@ -0,0 +1,163 @@
1
+ import { reenqueueStaleBootstrap } from './bootstrapRunner.js';
2
+ import { driveExecution } from './drive.js';
3
+ // Durable execution on pg-boss: the analogue of the Worker's Cloudflare Workflows
4
+ // driver. `startRun` enqueues an advance job (deduped per run via singletonKey); a
5
+ // registered worker drives the run to a standstill via `driveExecution`. A resolved
6
+ // decision re-enqueues an advance to resume a parked run. State lives in Postgres,
7
+ // so a crash mid-run is recovered two ways: pg-boss retries an expired/failed advance
8
+ // job, and the stale-run sweeper re-enqueues runs still `running` in storage.
9
+ const QUEUE = 'execution.advance';
10
+ // A separate, delayed queue that fails a run still parked on a decision after the
11
+ // `decisionTimeout` window — the Node analogue of the Cloudflare driver's
12
+ // `waitForEvent(..., { timeout })`. Kept off the advance queue so the delay never holds
13
+ // up real drives, and `exclusive` so at most one pending timeout exists per (run, decision).
14
+ const DECISION_TIMEOUT_QUEUE = 'execution.decision-timeout';
15
+ // The queue MUST be created with the `exclusive` policy for the dedup below to hold.
16
+ // Under pg-boss's default `standard` policy, `singletonKey` alone enforces NO uniqueness
17
+ // (the singleton unique indexes are policy-gated, and the policy-independent one requires
18
+ // `singletonSeconds`, which we don't set). `exclusive` makes (name, singletonKey) unique
19
+ // across the `created`/`retry`/`active` states, so at most one advance job per run is
20
+ // alive at a time and a duplicate `send` is an `ON CONFLICT DO NOTHING` no-op.
21
+ const QUEUE_POLICY = 'exclusive';
22
+ function sendOptions(executionId, opts) {
23
+ return {
24
+ singletonKey: executionId,
25
+ expireInSeconds: opts.expireInSeconds,
26
+ heartbeatSeconds: opts.heartbeatSeconds,
27
+ retryLimit: opts.retryLimit,
28
+ retryDelay: opts.retryDelaySeconds,
29
+ retryBackoff: true,
30
+ };
31
+ }
32
+ export class PgBossWorkRunner {
33
+ boss;
34
+ queueOptions;
35
+ constructor(boss, queueOptions) {
36
+ this.boss = boss;
37
+ this.queueOptions = queueOptions;
38
+ }
39
+ async startRun(workspaceId, executionId) {
40
+ await this.boss.send(QUEUE, { workspaceId, executionId }, sendOptions(executionId, this.queueOptions));
41
+ }
42
+ async signalDecision(workspaceId, executionId, _decisionId, _choice) {
43
+ // The decision is already persisted by resolveDecision; re-enqueue an advance so
44
+ // the parked run resumes. The DB write is the source of truth either way.
45
+ await this.boss.send(QUEUE, { workspaceId, executionId }, sendOptions(executionId, this.queueOptions));
46
+ }
47
+ async cancelRun(_workspaceId, _executionId) {
48
+ // Best-effort: the run is finalized via ExecutionService.stopRun; any in-flight
49
+ // advance job is a no-op once the run is terminal (advanceInstance returns noop).
50
+ }
51
+ }
52
+ /**
53
+ * Create the execution queue and start the worker that drives runs.
54
+ *
55
+ * `concurrency` (pg-boss `localConcurrency`) spawns that many INDEPENDENT workers for
56
+ * the queue on this node: each polls, fetches one job (`batchSize` stays 1) and acks /
57
+ * retries it on its own, so up to `concurrency` runs drive in parallel. This is the key
58
+ * to throughput — a single drive parks for the whole of a step's poll budget (sleeping
59
+ * between polls), so without parallel workers one slow run would block every other run
60
+ * behind it. We deliberately keep `batchSize: 1` rather than raising it: a batch handler
61
+ * completes/fails all its jobs together, which would couple unrelated runs' retries;
62
+ * independent workers keep per-run retry semantics intact. The `exclusive` queue policy
63
+ * still prevents the SAME run being driven by two workers at once (one live advance job per
64
+ * run id; duplicate sends no-op). Scale `concurrency` with the DB pool
65
+ * (each active drive borrows a connection only for its brief reads/writes between sleeps).
66
+ */
67
+ export async function startExecutionWorker(boss, container, cfg, log, options = {}) {
68
+ const concurrency = options.concurrency ?? 10;
69
+ const decisionTimeoutSeconds = options.decisionTimeoutSeconds ?? 0;
70
+ await boss.createQueue(QUEUE, { policy: QUEUE_POLICY });
71
+ await boss.work(QUEUE, { localConcurrency: Math.max(1, concurrency) }, async (jobs) => {
72
+ for (const job of jobs) {
73
+ const { workspaceId, executionId } = job.data;
74
+ try {
75
+ const outcome = await driveExecution(container.executionService, workspaceId, executionId, cfg, { log });
76
+ // Arm a decision timeout when the run parked awaiting a human. There is no
77
+ // event to cancel it on resolution (unlike Cloudflare's waitForEvent), so the
78
+ // timeout job re-checks state and `expireDecision` no-ops if it was resolved.
79
+ if (outcome.parkedDecisionId && decisionTimeoutSeconds > 0) {
80
+ await boss.send(DECISION_TIMEOUT_QUEUE, { workspaceId, executionId, decisionId: outcome.parkedDecisionId }, {
81
+ startAfter: decisionTimeoutSeconds,
82
+ singletonKey: `${executionId}:${outcome.parkedDecisionId}`,
83
+ });
84
+ }
85
+ }
86
+ catch (error) {
87
+ log.error({
88
+ workspaceId,
89
+ executionId,
90
+ err: error instanceof Error ? error.message : String(error),
91
+ }, 'execution driver failed');
92
+ throw error;
93
+ }
94
+ }
95
+ });
96
+ }
97
+ /**
98
+ * Start the worker that expires overdue decisions. A delayed job (armed by
99
+ * {@link startExecutionWorker} when a run parks on a decision) fires after the
100
+ * `decisionTimeout`; `expireDecision` fails the run as `decision_timeout` ONLY if it is
101
+ * still parked on that exact decision, so a decision resolved meanwhile is a safe no-op
102
+ * (no driving — that stays on the advance queue). This is the Node analogue of the
103
+ * Cloudflare driver's `waitForEvent` timeout. Create the queue before the advance worker
104
+ * so the advance worker's `boss.send` to it always has a target.
105
+ */
106
+ export async function startDecisionTimeoutWorker(boss, container, log) {
107
+ await boss.createQueue(DECISION_TIMEOUT_QUEUE, { policy: QUEUE_POLICY });
108
+ await boss.work(DECISION_TIMEOUT_QUEUE, async (jobs) => {
109
+ for (const job of jobs) {
110
+ const { workspaceId, executionId, decisionId } = job.data;
111
+ try {
112
+ await container.executionService.expireDecision(workspaceId, executionId, decisionId);
113
+ }
114
+ catch (error) {
115
+ log.error({
116
+ workspaceId,
117
+ executionId,
118
+ decisionId,
119
+ err: error instanceof Error ? error.message : String(error),
120
+ }, 'decision-timeout check failed');
121
+ throw error;
122
+ }
123
+ }
124
+ });
125
+ }
126
+ /**
127
+ * Backstop for runs that are still `running` in storage but whose advance job is gone
128
+ * (the worker crashed/was evicted before the job retried). Mirrors the Worker's cron
129
+ * `sweepStuckRuns`: on each tick it re-enqueues every stale `running` execution run.
130
+ * The re-enqueue carries the run's `singletonKey` and the queue is `exclusive`, so a run
131
+ * that IS still being driven (its advance job active/retrying) is a silent no-op — only
132
+ * genuinely orphaned runs re-drive.
133
+ * Decision-parked (`blocked`) and spend-paused (`paused`) runs aren't `running`, so the
134
+ * sweeper leaves them alone. Returns a stop function (clears the interval).
135
+ */
136
+ export function startStaleRunSweeper(boss, container, cfg, queueOptions, log) {
137
+ const tick = async () => {
138
+ try {
139
+ const stale = await container.agentRunRepository.listStale(Date.now() - cfg.leaseMs);
140
+ for (const ref of stale) {
141
+ // Both durable kinds are re-driven: an orphaned execution back onto the advance
142
+ // queue, an orphaned bootstrap onto the bootstrap drive queue (parity with the
143
+ // Worker's sweepStuckRuns, which covers execution + bootstrap).
144
+ if (ref.kind === 'bootstrap') {
145
+ log.warn({ workspaceId: ref.workspaceId, jobId: ref.id }, 're-driving stale bootstrap');
146
+ await reenqueueStaleBootstrap(boss, ref.workspaceId, ref.id, queueOptions);
147
+ continue;
148
+ }
149
+ if (ref.kind !== 'execution')
150
+ continue;
151
+ log.warn({ workspaceId: ref.workspaceId, executionId: ref.id }, 're-driving stale run');
152
+ await boss.send(QUEUE, { workspaceId: ref.workspaceId, executionId: ref.id }, sendOptions(ref.id, queueOptions));
153
+ }
154
+ }
155
+ catch (error) {
156
+ log.error({ err: error instanceof Error ? error.message : String(error) }, 'stale-run sweep failed');
157
+ }
158
+ };
159
+ const timer = setInterval(() => void tick(), cfg.intervalMs);
160
+ timer.unref?.(); // never keep the process alive on the sweep timer alone
161
+ return () => clearInterval(timer);
162
+ }
163
+ //# sourceMappingURL=pgBossRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pgBossRunner.js","sourceRoot":"","sources":["../../src/execution/pgBossRunner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type RuntimeGateways } from '@cat-factory/server';
2
+ /** Build the Node runtime gateways from process env. */
3
+ export declare function createNodeGateways(env: NodeJS.ProcessEnv): RuntimeGateways;
4
+ //# sourceMappingURL=gateways.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateways.d.ts","sourceRoot":"","sources":["../src/gateways.ts"],"names":[],"mappings":"AAOA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAA;AAmF5B,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,eAAe,CAU1E"}
@@ -0,0 +1,91 @@
1
+ import { DEEPSEEK_BASE_URL, MOONSHOT_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, QWEN_BASE_URL, } from '@cat-factory/agents';
2
+ import { createWebSearchUpstreamFromEnv, } from '@cat-factory/server';
3
+ // Node implementations of the runtime gateway seams. Async GitHub ingest still falls
4
+ // back to the "inline / not enabled" paths the shared controllers handle, and the LLM
5
+ // proxy forwards to OpenAI-compatible providers over HTTP (no in-process binding).
6
+ //
7
+ // Real-time delivery, by contrast, IS implemented — but NOT through this gateway seam.
8
+ // The seam returns a Hono `Response` (the Cloudflare model: a 101 from the per-workspace
9
+ // Durable Object). `@hono/node-server` can't complete a WebSocket upgrade from a
10
+ // `Response`, so the Node facade intercepts the `/workspaces/:ws/events` upgrade on the
11
+ // HTTP server directly (see `attachRealtime` in `realtime.ts`) before it reaches this
12
+ // controller. This gateway therefore stays a no-op: it is never invoked for an actual
13
+ // upgrade on Node, and the shared events route only ever falls through to its 426/501.
14
+ //
15
+ // Production swap-in for a multi-replica deployment (follow-up): front the in-process
16
+ // `NodeRealtimeHub` with a shared bus (Postgres LISTEN/NOTIFY); single-process Node and
17
+ // local mode need nothing more. Async GitHub ingest: pg-boss `githubBackfill` / `githubWebhook`.
18
+ /**
19
+ * No-op: Node handles the WebSocket upgrade at the HTTP-server level (`attachRealtime`),
20
+ * not through this Response-returning seam — see the file header. Returning null keeps
21
+ * the shared controller's contract intact for the (unreachable on Node) delegation path.
22
+ */
23
+ class NodeRealtimeGateway {
24
+ upgrade() {
25
+ return Promise.resolve(null);
26
+ }
27
+ }
28
+ /** No async backfill scheduler yet: report "not scheduled" so the caller runs it inline. */
29
+ class InlineGitHubBackfillScheduler {
30
+ scheduleBackfill() {
31
+ return Promise.resolve(false);
32
+ }
33
+ }
34
+ /** No async queue yet: report "not queued" so the caller handles webhooks/resyncs inline. */
35
+ class InlineGitHubWebhookIngest {
36
+ enqueueWebhook() {
37
+ return Promise.resolve(false);
38
+ }
39
+ queueRepoResync() {
40
+ return Promise.resolve(false);
41
+ }
42
+ }
43
+ // `baseUrl` is the built-in default; LiteLLM has none (operator-hosted), so it relies
44
+ // purely on its env override and resolves to null until LITELLM_BASE_URL is set.
45
+ const OPENAI_COMPATIBLE = {
46
+ qwen: { baseUrl: QWEN_BASE_URL, baseUrlEnv: 'QWEN_BASE_URL' },
47
+ deepseek: { baseUrl: DEEPSEEK_BASE_URL, baseUrlEnv: 'DEEPSEEK_BASE_URL' },
48
+ moonshot: { baseUrl: MOONSHOT_BASE_URL, baseUrlEnv: 'MOONSHOT_BASE_URL' },
49
+ openai: { baseUrl: OPENAI_BASE_URL, baseUrlEnv: 'OPENAI_BASE_URL' },
50
+ openrouter: { baseUrl: OPENROUTER_BASE_URL, baseUrlEnv: 'OPENROUTER_BASE_URL' },
51
+ litellm: { baseUrlEnv: 'LITELLM_BASE_URL' },
52
+ };
53
+ /**
54
+ * Forwards the container LLM proxy to OpenAI-compatible providers over HTTP. Only the
55
+ * base URL is resolved here (overridable per provider via env); the API key is leased
56
+ * per call from the DB-backed pool by the proxy. There is no in-process path on Node,
57
+ * so `runInProcess` returns null (a `workers-ai`-pinned model is unavailable here; use
58
+ * a direct provider, or enable the Cloudflare REST flavour).
59
+ */
60
+ class HttpLlmUpstream {
61
+ env;
62
+ constructor(env) {
63
+ this.env = env;
64
+ }
65
+ resolveOpenAiCompatible(provider) {
66
+ const entry = OPENAI_COMPATIBLE[provider];
67
+ if (!entry)
68
+ return null;
69
+ // `||` not `??`: a set-but-blank base-URL env must fall back to the default, not
70
+ // collapse to an empty URL the SDK then chokes on. For a provider with no default
71
+ // (LiteLLM), an unset env yields null so the proxy reports "not available" cleanly.
72
+ const baseURL = this.env[entry.baseUrlEnv] || entry.baseUrl;
73
+ return baseURL ? { baseURL } : null;
74
+ }
75
+ runInProcess() {
76
+ return null;
77
+ }
78
+ }
79
+ /** Build the Node runtime gateways from process env. */
80
+ export function createNodeGateways(env) {
81
+ return {
82
+ realtime: new NodeRealtimeGateway(),
83
+ githubBackfill: new InlineGitHubBackfillScheduler(),
84
+ githubWebhook: new InlineGitHubWebhookIngest(),
85
+ llmUpstream: new HttpLlmUpstream(env),
86
+ // Container web-search proxy upstream (Brave / self-hosted SearXNG from env);
87
+ // absent ⇒ the `/v1/web-search` route 503s and container web search stays off.
88
+ webSearch: createWebSearchUpstreamFromEnv(env),
89
+ };
90
+ }
91
+ //# sourceMappingURL=gateways.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateways.js","sourceRoot":"","sources":["../src/gateways.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAOL,8BAA8B,GAC/B,MAAM,qBAAqB,CAAA;AAE5B,qFAAqF;AACrF,sFAAsF;AACtF,mFAAmF;AACnF,EAAE;AACF,uFAAuF;AACvF,yFAAyF;AACzF,iFAAiF;AACjF,wFAAwF;AACxF,sFAAsF;AACtF,sFAAsF;AACtF,uFAAuF;AACvF,EAAE;AACF,sFAAsF;AACtF,wFAAwF;AACxF,iGAAiG;AAEjG;;;;GAIG;AACH,MAAM,mBAAmB;IACvB,OAAO;QACL,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,6BAA6B;IACjC,gBAAgB;QACd,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;CACF;AAED,6FAA6F;AAC7F,MAAM,yBAAyB;IAC7B,cAAc;QACZ,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;IAED,eAAe;QACb,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;CACF;AAED,sFAAsF;AACtF,iFAAiF;AACjF,MAAM,iBAAiB,GAA6D;IAClF,IAAI,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE;IAC7D,QAAQ,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,mBAAmB,EAAE;IACzE,QAAQ,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,mBAAmB,EAAE;IACzE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,iBAAiB,EAAE;IACnE,UAAU,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,qBAAqB,EAAE;IAC/E,OAAO,EAAE,EAAE,UAAU,EAAE,kBAAkB,EAAE;CAC5C,CAAA;AAED;;;;;;GAMG;AACH,MAAM,eAAe;IACU,GAAG;IAAhC,YAA6B,GAAsB;mBAAtB,GAAG;IAAsB,CAAC;IAEvD,uBAAuB,CAAC,QAAgB;QACtC,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAA;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,iFAAiF;QACjF,kFAAkF;QAClF,oFAAoF;QACpF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,OAAO,CAAA;QAC3D,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IACrC,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAA;IACb,CAAC;CACF;AAED,wDAAwD;AACxD,MAAM,UAAU,kBAAkB,CAAC,GAAsB;IACvD,OAAO;QACL,QAAQ,EAAE,IAAI,mBAAmB,EAAE;QACnC,cAAc,EAAE,IAAI,6BAA6B,EAAE;QACnD,aAAa,EAAE,IAAI,yBAAyB,EAAE;QAC9C,WAAW,EAAE,IAAI,eAAe,CAAC,GAAG,CAAC;QACrC,8EAA8E;QAC9E,+EAA+E;QAC/E,SAAS,EAAE,8BAA8B,CAAC,GAAG,CAAC;KAC/C,CAAA;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ export { createApp, createServer, start, type CreateServerOptions } from './server.js';
2
+ export { buildNodeContainer, type NodeContainerOptions } from './container.js';
3
+ export { loadNodeConfig } from './config.js';
4
+ export { createNodeGateways } from './gateways.js';
5
+ export { createNodeModelProviderResolver } from './modelProvider.js';
6
+ export { registerAgentKind, registerAgentKinds, clearRegisteredAgentKinds, type AgentKindDefinition, } from '@cat-factory/agents';
7
+ export { registerPipeline, registerPipelines, clearRegisteredPipelines } from '@cat-factory/kernel';
8
+ export { createDbClient, type DbClient, type DrizzleDb } from './db/client.js';
9
+ export { migrate } from './db/migrate.js';
10
+ export { createDrizzleRepositories, type CoreRepositories } from './repositories/drizzle.js';
11
+ export { DrizzleGitHubInstallationRepository } from './repositories/containerExecution.js';
12
+ export * as schema from './db/schema.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACtF,OAAO,EAAE,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAA;AAMpE,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AACnG,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,yBAAyB,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC5F,OAAO,EAAE,mCAAmC,EAAE,MAAM,sCAAsC,CAAA;AAC1F,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ // @cat-factory/node-server — the Node.js runtime facade. Serves the shared
2
+ // @cat-factory/server Hono app via @hono/node-server, wiring Node implementations of
3
+ // the runtime ports over a Drizzle/Postgres persistence layer (the single store used
4
+ // in dev, test and prod). `start()` boots an HTTP server; `createServer()` returns the
5
+ // app (for embedding/tests); `buildNodeContainer()` is the composition root.
6
+ export { createApp, createServer, start } from './server.js';
7
+ export { buildNodeContainer } from './container.js';
8
+ export { loadNodeConfig } from './config.js';
9
+ export { createNodeGateways } from './gateways.js';
10
+ export { createNodeModelProviderResolver } from './modelProvider.js';
11
+ // Installation-level extension points (mirroring the Worker facade): a deployment —
12
+ // typically a proprietary org package — registers custom agent kinds and predefined
13
+ // pipelines at startup (before `start()`), and the shared prompt catalog / workspace
14
+ // seeding pick them up. Bedrock-style model providers mix in via createNodeModelProvider.
15
+ export { registerAgentKind, registerAgentKinds, clearRegisteredAgentKinds, } from '@cat-factory/agents';
16
+ export { registerPipeline, registerPipelines, clearRegisteredPipelines } from '@cat-factory/kernel';
17
+ export { createDbClient } from './db/client.js';
18
+ export { migrate } from './db/migrate.js';
19
+ export { createDrizzleRepositories } from './repositories/drizzle.js';
20
+ export { DrizzleGitHubInstallationRepository } from './repositories/containerExecution.js';
21
+ export * as schema from './db/schema.js';
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,qFAAqF;AACrF,qFAAqF;AACrF,uFAAuF;AACvF,6EAA6E;AAC7E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAA4B,MAAM,aAAa,CAAA;AACtF,OAAO,EAAE,kBAAkB,EAA6B,MAAM,gBAAgB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAA;AAEpE,oFAAoF;AACpF,oFAAoF;AACpF,qFAAqF;AACrF,0FAA0F;AAC1F,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,GAE1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AACnG,OAAO,EAAE,cAAc,EAAiC,MAAM,gBAAgB,CAAA;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,yBAAyB,EAAyB,MAAM,2BAA2B,CAAA;AAC5F,OAAO,EAAE,mCAAmC,EAAE,MAAM,sCAAsC,CAAA;AAC1F,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAA"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js ADDED
@@ -0,0 +1,9 @@
1
+ import { logger } from '@cat-factory/server';
2
+ import { start } from './server.js';
3
+ // Default entrypoint: `pnpm build` then `node dist/main.js`. Requires DATABASE_URL;
4
+ // set PORT to override the listen port.
5
+ start().catch((err) => {
6
+ logger.error({ err: err instanceof Error ? err.message : String(err) }, 'failed to start');
7
+ process.exit(1);
8
+ });
9
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC,oFAAoF;AACpF,wCAAwC;AACxC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC7B,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAA;IAC1F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { ApiKeyService, LocalModelEndpointService } from '@cat-factory/integrations';
2
+ import type { ModelProviderResolver } from '@cat-factory/kernel';
3
+ /** The base URL for a direct provider: env override (e.g. QWEN_BASE_URL), else default. */
4
+ export declare function baseUrlForNode(provider: string, env: NodeJS.ProcessEnv): string | undefined;
5
+ export declare function createNodeModelProviderResolver(env: NodeJS.ProcessEnv, apiKeys: ApiKeyService | undefined, localModelEndpoints?: LocalModelEndpointService): ModelProviderResolver;
6
+ //# sourceMappingURL=modelProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelProvider.d.ts","sourceRoot":"","sources":["../src/modelProvider.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAuBhE,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,mBAAmB,CAAC,EAAE,yBAAyB,GAC9C,qBAAqB,CAqDvB"}
@@ -0,0 +1,72 @@
1
+ import {} from '@cat-factory/agents';
2
+ import { DEEPSEEK_BASE_URL, MOONSHOT_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, QWEN_BASE_URL, } from '@cat-factory/agents';
3
+ import { bedrockRegistry } from '@cat-factory/provider-bedrock';
4
+ import { cloudflareRestRegistry } from '@cat-factory/provider-cloudflare';
5
+ import { createLangfuseSink } from '@cat-factory/observability-langfuse';
6
+ import { createScopedModelProviderResolver } from '@cat-factory/server';
7
+ // The Node deployment's ModelProvider RESOLVER: builds a per-scope provider from the
8
+ // DB-backed API-key pool (account/workspace/user), plus opt-in registries that need no
9
+ // per-scope key — AWS Bedrock (when AWS creds/region are set) and Cloudflare Workers AI
10
+ // over REST (when CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN are set). There is no
11
+ // Workers AI binding on Node, so `workers-ai` is served via the Cloudflare REST flavour.
12
+ // Inline calls are wrapped for Langfuse exactly like the proxied path when configured.
13
+ const NODE_BASE_URLS = {
14
+ openai: OPENAI_BASE_URL,
15
+ qwen: QWEN_BASE_URL,
16
+ deepseek: DEEPSEEK_BASE_URL,
17
+ moonshot: MOONSHOT_BASE_URL,
18
+ openrouter: OPENROUTER_BASE_URL,
19
+ // `litellm` has no default: its base URL is the operator's own gateway, supplied via
20
+ // LITELLM_BASE_URL (honored automatically by baseUrlForNode's `${PROVIDER}_BASE_URL`).
21
+ };
22
+ /** The base URL for a direct provider: env override (e.g. QWEN_BASE_URL), else default. */
23
+ export function baseUrlForNode(provider, env) {
24
+ // `||` not `??`: a set-but-blank override must fall back to the default.
25
+ return env[`${provider.toUpperCase()}_BASE_URL`] || NODE_BASE_URLS[provider];
26
+ }
27
+ export function createNodeModelProviderResolver(env, apiKeys, localModelEndpoints) {
28
+ const extraRegistries = [];
29
+ // Opt-in Cloudflare Workers AI over REST.
30
+ if (env.CLOUDFLARE_ACCOUNT_ID && env.CLOUDFLARE_API_TOKEN) {
31
+ extraRegistries.push(cloudflareRestRegistry({
32
+ accountId: env.CLOUDFLARE_ACCOUNT_ID,
33
+ apiToken: env.CLOUDFLARE_API_TOKEN,
34
+ gateway: env.CLOUDFLARE_AI_GATEWAY,
35
+ }));
36
+ }
37
+ // Opt-in Bedrock: registered only when a region is configured.
38
+ if (env.BEDROCK_REGION) {
39
+ const supportedModels = env.BEDROCK_MODELS?.split(',')
40
+ .map((m) => m.trim())
41
+ .filter(Boolean);
42
+ extraRegistries.push(bedrockRegistry({
43
+ region: env.BEDROCK_REGION,
44
+ accessKeyId: env.AWS_ACCESS_KEY_ID,
45
+ secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
46
+ sessionToken: env.AWS_SESSION_TOKEN,
47
+ supportedModels: supportedModels?.length ? supportedModels : undefined,
48
+ }));
49
+ }
50
+ const instrument = env.LANGFUSE_ENABLED?.trim() === 'true' &&
51
+ env.LANGFUSE_PUBLIC_KEY?.trim() &&
52
+ env.LANGFUSE_SECRET_KEY?.trim()
53
+ ? {
54
+ traceSink: createLangfuseSink({
55
+ publicKey: env.LANGFUSE_PUBLIC_KEY.trim(),
56
+ secretKey: env.LANGFUSE_SECRET_KEY.trim(),
57
+ baseUrl: env.LANGFUSE_BASE_URL?.trim() || undefined,
58
+ }),
59
+ recordPrompts: env.LLM_RECORD_PROMPTS?.trim() !== 'false',
60
+ }
61
+ : undefined;
62
+ return createScopedModelProviderResolver({
63
+ apiKeys,
64
+ baseUrlFor: (provider) => baseUrlForNode(provider, env),
65
+ extraRegistries,
66
+ localEndpointsFor: localModelEndpoints
67
+ ? (userId) => localModelEndpoints.listResolved(userId)
68
+ : undefined,
69
+ instrument,
70
+ });
71
+ }
72
+ //# sourceMappingURL=modelProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelProvider.js","sourceRoot":"","sources":["../src/modelProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import type { Duplex } from 'node:stream';
3
+ import type { Block, BootstrapJob, ConsensusSession, ClarityReview, ExecutionInstance, LlmCallActivity, Notification, RequirementReview } from '@cat-factory/contracts';
4
+ import type { ExecutionEventPublisher } from '@cat-factory/kernel';
5
+ import { type AuthConfig } from '@cat-factory/server';
6
+ import { WebSocket } from 'ws';
7
+ /** The minimal logger shape this module needs (a pino logger satisfies it). */
8
+ interface RealtimeLogger {
9
+ info(obj: object, msg?: string): void;
10
+ warn(obj: object, msg?: string): void;
11
+ }
12
+ /** The subset of a Node HTTP/HTTP2 server we attach the upgrade listener to. */
13
+ interface UpgradableServer {
14
+ on(event: 'upgrade', listener: (request: IncomingMessage, socket: Duplex, head: Buffer) => void): void;
15
+ }
16
+ /**
17
+ * Per-workspace subscriber registry. Every browser subscribed to a workspace's stream
18
+ * converges here, so a published event fans out to all of them. In-memory and
19
+ * single-process: the Node service runs as one process (unlike the Worker's globally
20
+ * addressed Durable Object), which is the right model for the self-hosted / local
21
+ * deployments this facade targets. A multi-replica deployment would need a shared bus
22
+ * (Postgres LISTEN/NOTIFY) in front of this — a follow-up, not needed for local mode.
23
+ */
24
+ export declare class NodeRealtimeHub {
25
+ private readonly rooms;
26
+ /** Add a socket to a workspace's room; it is reaped on close/error. */
27
+ subscribe(workspaceId: string, socket: WebSocket): void;
28
+ private unsubscribe;
29
+ /** Fan a pre-serialised JSON event out to every socket on a workspace's stream. */
30
+ broadcast(workspaceId: string, payload: string): void;
31
+ }
32
+ /**
33
+ * Pushes execution/board events to the {@link NodeRealtimeHub}, which fans them out to
34
+ * subscribed browsers. The event shapes are IDENTICAL to the Worker's
35
+ * `DurableObjectEventPublisher`, so the SPA's stream handling is runtime-agnostic.
36
+ * Best-effort: a publish failure must never break a state transition (the persisted
37
+ * row is the source of truth and clients reconcile on reconnect), so each publish
38
+ * swallows its own errors.
39
+ */
40
+ export declare class NodeEventPublisher implements ExecutionEventPublisher {
41
+ private readonly hub;
42
+ constructor(hub: NodeRealtimeHub);
43
+ executionChanged(workspaceId: string, instance: ExecutionInstance, block?: Block | null): Promise<void>;
44
+ boardChanged(workspaceId: string, reason: string, _blockId?: string | null): Promise<void>;
45
+ bootstrapChanged(workspaceId: string, job: BootstrapJob, block?: Block | null): Promise<void>;
46
+ notificationChanged(workspaceId: string, notification: Notification): Promise<void>;
47
+ llmCallObserved(workspaceId: string, activity: LlmCallActivity): Promise<void>;
48
+ requirementReviewChanged(workspaceId: string, review: RequirementReview): Promise<void>;
49
+ consensusSessionChanged(workspaceId: string, session: ConsensusSession): Promise<void>;
50
+ clarityReviewChanged(workspaceId: string, review: ClarityReview): Promise<void>;
51
+ private publish;
52
+ }
53
+ /**
54
+ * Attach the real-time WebSocket transport to a running Node HTTP server: accept
55
+ * `GET /workspaces/:ws/events` upgrades (authorising the `?ticket=` exactly like the
56
+ * shared EventsController), register each socket into the {@link NodeRealtimeHub}, and
57
+ * run a heartbeat that terminates dead connections. Returns a stop function that clears
58
+ * the heartbeat and closes the WS server (call it on graceful shutdown).
59
+ */
60
+ export declare function attachRealtime(server: UpgradableServer, hub: NodeRealtimeHub, auth: AuthConfig, log: RealtimeLogger): () => void;
61
+ export {};
62
+ //# sourceMappingURL=realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}