@kky42/pievo-server 2.4.13 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{actionUi-D8m1FDHO.js → actionUi-BZrLpgHT.js} +1 -1
  3. package/.output/public/assets/app-DXLuQNb9.css +2 -0
  4. package/.output/public/assets/index-Di_wpAtL.js +10 -0
  5. package/.output/public/assets/loops._loopId-X3ClNPMY.js +67 -0
  6. package/.output/public/assets/loops._loopId_.runs._runId-ByU2BSyF.js +3 -0
  7. package/.output/public/assets/preload-helper-hRTYB_ju.js +3 -0
  8. package/.output/public/assets/{routes-79D939J4.js → routes-BIOJZsig.js} +1 -1
  9. package/.output/public/assets/routes-K8D5uBiV.js +1 -0
  10. package/.output/server/_chunks/ssr-renderer.mjs +1 -1
  11. package/.output/server/_libs/{drizzle-orm+postgres.mjs → drizzle-orm.mjs} +93 -2218
  12. package/.output/server/_ssr/{MachinesModal-DBIP648Z.mjs → MachinesModal-CmVMec6B.mjs} +52 -46
  13. package/.output/server/_ssr/{actionUi-C_WswNb1.mjs → actionUi-DE-Pb2tv.mjs} +4 -5
  14. package/.output/server/_ssr/{artifactFiles-BvVmixrs.mjs → artifactFiles-Di1xDKvX.mjs} +5 -7
  15. package/.output/server/_ssr/{artifactKind-DRCbYHZU.mjs → artifactKind-DqpmtxDU.mjs} +3 -9
  16. package/.output/server/_ssr/{boot-U-CFI65B.mjs → boot-CYRi-wez.mjs} +531 -1976
  17. package/.output/server/_ssr/lifecycleUi-BsbUsi5K.mjs +106 -0
  18. package/.output/server/_ssr/{loopApi-C4nN0msL.mjs → loopApi-Bez1rHV0.mjs} +50 -65
  19. package/.output/server/_ssr/{loopProjection-DMSNHux6.mjs → loopProjection-esCjyZJg.mjs} +4 -20
  20. package/.output/server/_ssr/{loops._loopId-Q4pRYPip.mjs → loops._loopId-KUezNY69.mjs} +133 -24
  21. package/.output/server/_ssr/{loops._loopId_.runs._runId-DNXVmCt5.mjs → loops._loopId_.runs._runId-BFP9oMrp.mjs} +2 -7
  22. package/.output/server/_ssr/{loops._loopId_.runs._runId-ty3AhkOi.mjs → loops._loopId_.runs._runId-DOkUEesG.mjs} +18 -33
  23. package/.output/server/_ssr/{machineFns-CkNms3zA.mjs → machineFns-CniGcY8s.mjs} +13 -49
  24. package/.output/server/_ssr/networkAccess-CIlK3ION.mjs +30 -0
  25. package/.output/server/_ssr/router-BFIH2UAE.mjs +362 -0
  26. package/.output/server/_ssr/routes-BSVBANI3.mjs +12 -0
  27. package/.output/server/_ssr/{routes-BaanDO41.mjs → routes-DnE7XDe_.mjs} +242 -77
  28. package/.output/server/_ssr/{routes-B3fulW5I.mjs → routes-ngpX7h8D.mjs} +2 -4
  29. package/.output/server/_ssr/ssr.mjs +25 -34
  30. package/.output/server/_ssr/start-B5_9Z5W-.mjs +35 -0
  31. package/.output/server/{_tanstack-start-manifest_v-DkmxsHDK.mjs → _tanstack-start-manifest_v-DnwAupbZ.mjs} +8 -13
  32. package/.output/server/drizzle/0000_baseline.sql +6 -97
  33. package/.output/server/drizzle/meta/_journal.json +2 -16
  34. package/.output/server/index.mjs +43 -50
  35. package/drizzle/0000_baseline.sql +6 -97
  36. package/drizzle/meta/_journal.json +2 -16
  37. package/package.json +4 -8
  38. package/scripts/pievo-server.mjs +49 -47
  39. package/scripts/server-cli-lib.mjs +21 -12
  40. package/scripts/server-network.mjs +139 -0
  41. package/.output/public/assets/SignIn-CDCdDD1a.js +0 -1
  42. package/.output/public/assets/app-PPhgN9iN.css +0 -2
  43. package/.output/public/assets/index-c0XPWXuz.js +0 -10
  44. package/.output/public/assets/loops._loopId-D7i_BGiE.js +0 -67
  45. package/.output/public/assets/loops._loopId_.runs._runId-CksSrEY7.js +0 -3
  46. package/.output/public/assets/preload-helper-Zcmr31XG.js +0 -5
  47. package/.output/public/assets/routes-BYgl3Tmv.js +0 -1
  48. package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +0 -419
  49. package/.output/server/_libs/@aws-sdk/checksums+[...].mjs +0 -10732
  50. package/.output/server/_libs/@aws-sdk/client-s3+[...].mjs +0 -9475
  51. package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +0 -137
  52. package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +0 -440
  53. package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +0 -62
  54. package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +0 -818
  55. package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +0 -59
  56. package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +0 -342
  57. package/.output/server/_libs/aws-crypto__crc32c.mjs +0 -314
  58. package/.output/server/_libs/aws-sdk__nested-clients.mjs +0 -2671
  59. package/.output/server/_ssr/SignIn-Bp3uWaCQ.mjs +0 -53
  60. package/.output/server/_ssr/artifacts-CzDJLaa6.mjs +0 -2
  61. package/.output/server/_ssr/auth-BV0MxVdo.mjs +0 -38355
  62. package/.output/server/_ssr/auth-client-RTpMpT4L.mjs +0 -1070
  63. package/.output/server/_ssr/boot-CsLXrLEa.mjs +0 -2
  64. package/.output/server/_ssr/bun-sqlite-dialect-BW9W1_Ps-D9KUBJ-L.mjs +0 -162
  65. package/.output/server/_ssr/core-RrmWoKne.mjs +0 -5
  66. package/.output/server/_ssr/d1-sqlite-dialect-BLC8LXE6-CVHRzH1n.mjs +0 -112
  67. package/.output/server/_ssr/db-BTX-xORB.mjs +0 -2
  68. package/.output/server/_ssr/dist-D7UDVZ5G.mjs +0 -342
  69. package/.output/server/_ssr/factory-DYFx1fg-.mjs +0 -6903
  70. package/.output/server/_ssr/kysely-adapter-CwdB7yBh.mjs +0 -2
  71. package/.output/server/_ssr/kysely-migration-tables-JkVUjPF_-bPAX9LR_.mjs +0 -19
  72. package/.output/server/_ssr/logger-CwiKSMp6.mjs +0 -2
  73. package/.output/server/_ssr/node-sqlite-dialect-B_g92oFK.mjs +0 -162
  74. package/.output/server/_ssr/router-DCNZ2xLm.mjs +0 -543
  75. package/.output/server/_ssr/routes-8JSF7bX_.mjs +0 -27
  76. package/.output/server/_ssr/runDiff-D7CxAgYL.mjs +0 -2
  77. package/.output/server/_ssr/start-4ifArtKx.mjs +0 -4
  78. package/.output/server/_ssr/store-DSNbXxqz.mjs +0 -2
  79. package/.output/server/_ssr/tokens-DzUv7CCp.mjs +0 -2
  80. package/.output/server/drizzle/0001_add_pi_agent.sql +0 -9
  81. package/.output/server/drizzle/0002_remove_teams.sql +0 -15
  82. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  83. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  84. package/.output/server/node_modules/tslib/package.json +0 -47
  85. package/.output/server/node_modules/tslib/tslib.js +0 -484
  86. package/.output/server/package.json +0 -9
  87. package/drizzle/0001_add_pi_agent.sql +0 -9
  88. package/drizzle/0002_remove_teams.sql +0 -15
  89. package/scripts/prestart.mjs +0 -66
@@ -1,543 +0,0 @@
1
- import { j as sql } from "../_libs/drizzle-orm+postgres.mjs";
2
- import { Mt as validPollCurrentRun, Nt as validPollWireId, d as POLL_HTTP_BODY_FIELDS, jt as validOptionalPollString, l as MACHINE_BODY_CAP, mt as readJsonBody, pt as readByteBody, w as ensureServer } from "./boot-U-CFI65B.mjs";
3
- import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
4
- import { c as createFileRoute, i as HeadContent, l as createRootRoute, o as createRouter, r as Scripts } from "../_libs/@tanstack/react-router+[...].mjs";
5
- import { r as imageMime, t as Route$12 } from "./artifactKind-DRCbYHZU.mjs";
6
- import { t as Route$13 } from "./loops._loopId_.runs._runId-DNXVmCt5.mjs";
7
- import { n as Route$14 } from "./routes-BaanDO41.mjs";
8
- //#region node_modules/.nitro/vite/services/ssr/assets/router-DCNZ2xLm.js
9
- var import_jsx_runtime = require_jsx_runtime();
10
- var app_default = "/assets/app-PPhgN9iN.css";
11
- var Route$11 = createRootRoute({
12
- head: () => ({
13
- meta: [
14
- { charSet: "utf-8" },
15
- {
16
- name: "viewport",
17
- content: "width=device-width, initial-scale=1"
18
- },
19
- { title: "Pievo" }
20
- ],
21
- links: [{
22
- rel: "stylesheet",
23
- href: app_default
24
- }, {
25
- rel: "icon",
26
- type: "image/svg+xml",
27
- href: "/favicon.svg"
28
- }]
29
- }),
30
- shellComponent: RootDocument
31
- });
32
- function RootDocument({ children }) {
33
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", {
34
- lang: "en",
35
- suppressHydrationWarning: true,
36
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("head", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeadContent, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("body", {
37
- className: "dot-grid-subtle bg-paper font-sans text-primary antialiased",
38
- suppressHydrationWarning: true,
39
- children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scripts, {})]
40
- })]
41
- });
42
- }
43
- var TokenBucketLimiter = class {
44
- capacity;
45
- refillPerSec;
46
- maxKeys;
47
- buckets = /* @__PURE__ */ new Map();
48
- constructor(capacity, refillPerSec, maxKeys = 2e4) {
49
- this.capacity = capacity;
50
- this.refillPerSec = refillPerSec;
51
- this.maxKeys = maxKeys;
52
- }
53
- allow(key, now = Date.now()) {
54
- let b = this.buckets.get(key);
55
- if (!b) {
56
- if (this.buckets.size >= this.maxKeys) this.evictOldest();
57
- b = {
58
- tokens: this.capacity,
59
- last: now
60
- };
61
- this.buckets.set(key, b);
62
- }
63
- const elapsedSec = Math.max(0, (now - b.last) / 1e3);
64
- b.tokens = Math.min(this.capacity, b.tokens + elapsedSec * this.refillPerSec);
65
- b.last = now;
66
- if (b.tokens >= 1) {
67
- b.tokens -= 1;
68
- return true;
69
- }
70
- return false;
71
- }
72
- /** Drop the least-recently-touched key (bounded memory under a distinct-key flood). */
73
- evictOldest() {
74
- let oldestKey;
75
- let oldest = Number.POSITIVE_INFINITY;
76
- for (const [k, b] of this.buckets) if (b.last < oldest) {
77
- oldest = b.last;
78
- oldestKey = k;
79
- }
80
- if (oldestKey !== void 0) this.buckets.delete(oldestKey);
81
- }
82
- reset() {
83
- this.buckets.clear();
84
- }
85
- };
86
- function envNum(name, dflt) {
87
- const raw = process.env[name]?.trim();
88
- if (!raw) return dflt;
89
- const n = Number(raw);
90
- return Number.isFinite(n) && n > 0 ? n : dflt;
91
- }
92
- /**
93
- * Per-IP: default 240 burst + 8/sec sustained (≈480 req/min). One daemon does
94
- * ~20 polls/min plus sync bursts; this comfortably covers a dozen daemons behind a
95
- * shared NAT while still capping a single-IP flood.
96
- */
97
- var ipLimiter = new TokenBucketLimiter(envNum("PIEVO_RL_IP_BURST", 240), envNum("PIEVO_RL_IP_PER_SEC", 8));
98
- /**
99
- * Per-token (per-machine): default 120 burst + 4/sec sustained. A single daemon
100
- * never approaches this; it bounds one credential's share of the process.
101
- */
102
- var tokenLimiter = new TokenBucketLimiter(envNum("PIEVO_RL_TOKEN_BURST", 120), envNum("PIEVO_RL_TOKEN_PER_SEC", 4));
103
- /** Whether rate limiting is active. Off by default in tests unless opted in, so the
104
- * large existing suites don't trip it; ON in a real server process. */
105
- function rateLimitEnabled() {
106
- const flag = process.env.PIEVO_RATE_LIMIT?.trim().toLowerCase();
107
- if (flag === "0" || flag === "false" || flag === "off") return false;
108
- if (flag === "1" || flag === "true" || flag === "on") return true;
109
- return process.env.VITEST !== "true";
110
- }
111
- /**
112
- * Best-effort client IP from the request, honoring the usual proxy headers (Fly
113
- * sets `Fly-Client-IP`; a generic proxy sets `X-Forwarded-For`). Falls back to a
114
- * single shared key so a missing IP still shares ONE bucket (fail-closed: unknown
115
- * origins can't each get a private allowance). Only the FIRST `X-Forwarded-For` hop
116
- * is trusted (the client-facing edge appends left-to-right).
117
- */
118
- function clientIp(request) {
119
- const h = request.headers;
120
- const fly = h.get("fly-client-ip");
121
- if (fly) return fly.trim();
122
- const xff = h.get("x-forwarded-for");
123
- if (xff) {
124
- const first = xff.split(",")[0]?.trim();
125
- if (first) return first;
126
- }
127
- const real = h.get("x-real-ip");
128
- if (real) return real.trim();
129
- return "unknown";
130
- }
131
- /**
132
- * A 429 response with a Retry-After hint. Carries `text` + `exitCode` alongside
133
- * `error` so `/api/machine/cli` can print the response directly.
134
- */
135
- function tooMany() {
136
- return Response.json({
137
- error: "rate limited — slow down",
138
- text: "rate limited — slow down",
139
- exitCode: 1
140
- }, {
141
- status: 429,
142
- headers: { "retry-after": "1" }
143
- });
144
- }
145
- /**
146
- * Enforce the machine-route rate limits for one request. Call it at the top of a
147
- * machine route handler, right after reading the Bearer token (pass it so a valid
148
- * daemon keys off its own per-machine bucket). Returns a 429 `Response` to return
149
- * verbatim, or null to proceed. Both tiers apply: the per-IP flood guard always,
150
- * and the per-token fairness tier whenever a credential is present. (The byte-
151
- * ingress routes are exempt entirely and simply do not call this — see the module
152
- * doc.)
153
- */
154
- function machineRouteLimit(request, token, opts = {}) {
155
- if (!rateLimitEnabled()) return null;
156
- const now = opts.now ?? Date.now();
157
- if (!ipLimiter.allow(clientIp(request), now)) return tooMany();
158
- if (token && !tokenLimiter.allow(token, now)) return tooMany();
159
- return null;
160
- }
161
- var Route$10 = createFileRoute("/machine/report")({ server: { handlers: { POST: async ({ request }) => {
162
- const auth = request.headers.get("authorization") ?? "";
163
- const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
164
- const limited = machineRouteLimit(request, token || void 0);
165
- if (limited) return limited;
166
- if (!token) return Response.json({ error: "missing token" }, { status: 401 });
167
- const parsed = await readJsonBody(request, MACHINE_BODY_CAP);
168
- if (parsed.kind === "too-large") return Response.json({ error: "body too large" }, { status: 413 });
169
- const body = parsed.kind === "ok" && parsed.body && typeof parsed.body === "object" ? parsed.body : {};
170
- const { getGateway } = await import("./boot-CsLXrLEa.mjs");
171
- const r = await (await getGateway()).report(token, body);
172
- return Response.json(r.body, { status: r.status });
173
- } } } });
174
- /**
175
- * Deep readiness for process launchers. Unlike /api/health, this waits for the
176
- * one-time backend boot (migrations, blob-store selection, and scheduler start).
177
- * The per-launch nonce lets a launcher reject a different process already bound
178
- * to the requested port.
179
- */
180
- async function readyResponse(ensure = ensureServer, env = process.env) {
181
- await ensure();
182
- return Response.json({
183
- ok: true,
184
- nonce: env.PIEVO_SERVER_LAUNCH_NONCE ?? null
185
- });
186
- }
187
- var Route$9 = createFileRoute("/api/ready")({ server: { handlers: { GET: () => readyResponse() } } });
188
- var buildSha = () => process.env.GIT_SHA || "unknown";
189
- var buildAt = () => process.env.BUILT_AT || "unknown";
190
- var Route$8 = createFileRoute("/api/health")({ server: { handlers: { GET: async () => Response.json({
191
- ok: true,
192
- sha: buildSha(),
193
- builtAt: buildAt()
194
- }) } } });
195
- /**
196
- * POST /api/machine/sync — exact artifact sync (Bearer DEVICE token). The daemon
197
- * posts the complete configured-path manifest after a run; the server reconciles
198
- * artifact_files and replies with needHashes for raw blob PUTs.
199
- * The manifest route retains its dedicated 32 MB JSON-body limit.
200
- *
201
- * This byte-ingress route is deliberately NOT rate limited (same rationale as the
202
- * blob PUT): it requires a valid registered device token (unknown ⇒ 401) and is
203
- * already bounded by the exact-path/hash handshake and 32MB sync-body cap, so a
204
- * limiter would only throttle a legitimate large sync.
205
- */
206
- var Route$7 = createFileRoute("/api/machine/sync")({ server: { handlers: { POST: async ({ request }) => {
207
- const auth = request.headers.get("authorization") ?? "";
208
- const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
209
- if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
210
- const { SYNC_BODY_CAP, parseArtifactSyncBody } = await import("./artifacts-CzDJLaa6.mjs");
211
- const parsed = await readJsonBody(request, SYNC_BODY_CAP);
212
- if (parsed.kind === "too-large") return Response.json({ error: "sync body too large" }, { status: 413 });
213
- if (parsed.kind === "invalid") return Response.json({ error: "invalid JSON" }, { status: 400 });
214
- const body = parseArtifactSyncBody(parsed.body);
215
- if (!body) return Response.json({ error: "invalid sync body" }, { status: 400 });
216
- const { getArtifactSync } = await import("./boot-CsLXrLEa.mjs");
217
- const r = await (await getArtifactSync()).sync(token, body);
218
- return Response.json(r.body, { status: r.status });
219
- } } } });
220
- var Route$6 = createFileRoute("/api/machine/status")({ server: { handlers: { GET: async ({ request }) => {
221
- const auth = request.headers.get("authorization") ?? "";
222
- const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
223
- const limited = machineRouteLimit(request, token || void 0);
224
- if (limited) return limited;
225
- if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
226
- const { getGateway } = await import("./boot-CsLXrLEa.mjs");
227
- const r = await (await getGateway()).status(token);
228
- return Response.json(r.body, { status: r.status });
229
- } } } });
230
- /** Reject non-object bodies, additions, coercible types, over-cap strings, and
231
- * NUL-bearing text before the gateway is loaded or any state can mutate. */
232
- function parsePollBody(value) {
233
- if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
234
- if (Object.keys(value).some((key) => !POLL_HTTP_BODY_FIELDS.has(key))) return null;
235
- const body = value;
236
- if (!validOptionalPollString(body.host, 524288) || !validOptionalPollString(body.platform, 524288) || !validOptionalPollString(body.arch, 524288) || !validOptionalPollString(body.version, 64)) return null;
237
- if (body.protocolVersion !== void 0 && (typeof body.protocolVersion !== "number" || !Number.isInteger(body.protocolVersion))) return null;
238
- if (body.currentRuns !== void 0 && (!Array.isArray(body.currentRuns) || body.currentRuns.some((run) => !validPollCurrentRun(run)))) return null;
239
- if (body.daemonInstanceId !== void 0 && !validPollWireId(body.daemonInstanceId)) return null;
240
- if (body.recoveryComplete !== void 0 && typeof body.recoveryComplete !== "boolean") return null;
241
- return body;
242
- }
243
- function gatewayPollRequest(body) {
244
- return {
245
- protocolVersion: body.protocolVersion,
246
- currentRuns: body.currentRuns,
247
- daemonInstanceId: body.daemonInstanceId,
248
- recoveryComplete: body.recoveryComplete,
249
- info: {
250
- host: body.host,
251
- platform: body.platform,
252
- arch: body.arch,
253
- version: body.version
254
- }
255
- };
256
- }
257
- var Route$5 = createFileRoute("/api/machine/poll")({ server: { handlers: { POST: async ({ request }) => {
258
- const auth = request.headers.get("authorization") ?? "";
259
- const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
260
- const limited = machineRouteLimit(request, token || void 0);
261
- if (limited) return limited;
262
- if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
263
- const parsed = await readJsonBody(request, MACHINE_BODY_CAP);
264
- if (parsed.kind === "too-large") return Response.json({ error: "body too large" }, { status: 413 });
265
- if (parsed.kind !== "ok") return Response.json({ error: "invalid JSON body" }, { status: 400 });
266
- const body = parsePollBody(parsed.body);
267
- if (!body) return Response.json({ error: "unknown or invalid poll body fields" }, { status: 400 });
268
- const { getGateway } = await import("./boot-CsLXrLEa.mjs");
269
- const r = await (await getGateway()).pollV4Wait(token, gatewayPollRequest(body));
270
- return Response.json(r.body, { status: r.status });
271
- } } } });
272
- function parseCliBody(value) {
273
- if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
274
- const body = value;
275
- if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "argv")) return null;
276
- if (!Array.isArray(body.argv) || body.argv.some((entry) => typeof entry !== "string")) return null;
277
- return { argv: body.argv };
278
- }
279
- /**
280
- * POST /api/machine/cli — the ONE unified CLI dispatch (Bearer credential + `{argv}`).
281
- * `CliGateway.cli()` branches by credential type: a `dk_` device token takes owner
282
- * commands, while an `rk_` run credential can only report its run. Uses the standard
283
- * 2MB machine JSON body cap.
284
- */
285
- var Route$4 = createFileRoute("/api/machine/cli")({ server: { handlers: { POST: async ({ request }) => {
286
- const auth = request.headers.get("authorization") ?? "";
287
- const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
288
- const limited = machineRouteLimit(request, token || void 0);
289
- if (limited) return limited;
290
- if (!token) return Response.json({ error: "missing credential" }, { status: 401 });
291
- const parsed = await readJsonBody(request, MACHINE_BODY_CAP);
292
- if (parsed.kind === "too-large") return Response.json({ error: "body too large" }, { status: 413 });
293
- const body = parsed.kind === "ok" ? parseCliBody(parsed.body) : null;
294
- if (!body) return Response.json({ error: "body must be exactly {argv:string[]}" }, { status: 400 });
295
- const { getCliGateway } = await import("./boot-CsLXrLEa.mjs");
296
- const r = await (await getCliGateway()).cli(token, body.argv);
297
- return Response.json(r.body, { status: r.status });
298
- } } } });
299
- /**
300
- * DB-touching liveness probe — the ONE home for the wedged-pool incident rationale
301
- * (db/index.ts and the Fly checks point here). Unlike /api/health (which never
302
- * touches the DB and so reported `ok` right through a ~5.5h outage while a dead
303
- * postgres-js pool masked it), this runs a trivial `select 1` through the runtime
304
- * pool.
305
- *
306
- * This route SURFACES a wedged pool (Fly's check goes critical, which de-routes the
307
- * machine); it does NOT by itself recover it. Fly's failing `[http_service.checks]`
308
- * only pull the machine from load balancing — they never restart the VM (the
309
- * 2026-07-12 outage: pool wedged, check critical, box down ~9h with no auto-restart
310
- * until a manual `fly machine restart`). The ACTUAL auto-recovery is the in-process
311
- * DB watchdog (`server/dbWatchdog.ts`), which exits the process on a sustained wedge
312
- * so Fly's `restart.policy = "on-failure"` brings up a fresh pool.
313
- *
314
- * A wedged pool makes `select 1` HANG (queued behind stuck connections; the pool's
315
- * `statement_timeout` is unenforced under the Supabase transaction pooler). So the
316
- * probe races the query against a hard client-side deadline (`PROBE_TIMEOUT_MS`, <
317
- * Fly's 10s check timeout) and returns a fast 503 instead of eating the full timeout
318
- * "awaiting headers". 200 → DB reachable; 503 → the query failed or timed out.
319
- * Drizzle imported inside the handler so it stays out of the client bundle.
320
- */
321
- var PROBE_TIMEOUT_MS = 5e3;
322
- var Route$3 = createFileRoute("/api/health/db")({ server: { handlers: { GET: async () => {
323
- try {
324
- const { db } = await import("./db-BTX-xORB.mjs");
325
- const ping = db.execute(sql`select 1`);
326
- const timeout = new Promise((_, reject) => {
327
- setTimeout(() => reject(/* @__PURE__ */ new Error(`db ping timed out after ${PROBE_TIMEOUT_MS}ms`)), PROBE_TIMEOUT_MS).unref?.();
328
- });
329
- await Promise.race([ping, timeout]);
330
- return Response.json({
331
- ok: true,
332
- db: "up"
333
- });
334
- } catch (err) {
335
- return Response.json({
336
- ok: false,
337
- db: "down",
338
- error: err instanceof Error ? err.message : String(err)
339
- }, { status: 503 });
340
- }
341
- } } } });
342
- var Route$2 = createFileRoute("/api/auth/$")({ server: { handlers: {
343
- GET: async ({ request }) => (await import("./auth-BV0MxVdo.mjs")).auth.handler(request),
344
- POST: async ({ request }) => (await import("./auth-BV0MxVdo.mjs")).auth.handler(request)
345
- } } });
346
- /**
347
- * `decodeURIComponent` that returns null on malformed percent-encoding instead
348
- * of throwing a URIError (which would escape a route handler as a 500). Shared
349
- * by the routes that decode path segments; the policy is uniform — ANY
350
- * malformed segment is the caller's clean 400, never silently kept raw.
351
- */
352
- function safeDecode(s) {
353
- try {
354
- return decodeURIComponent(s);
355
- } catch {
356
- return null;
357
- }
358
- }
359
- /**
360
- * PUT /api/machine/blob/:hash — upload one content-addressed blob's raw bytes
361
- * (Bearer DEVICE token). The server recomputes sha256(body) and rejects a
362
- * mismatch before storing in the byte store. The hash is read from the URL (the daemon PUTs
363
- * exactly the hashes the sync handshake returned in needHashes).
364
- *
365
- * This byte-ingress route is deliberately NOT rate limited: it requires a valid
366
- * registered device token (unknown ⇒ 401, not an unauthenticated surface) and is
367
- * already bounded by the sync hash-handshake (the server only accepts hashes it
368
- * asked THIS machine for) plus the per-file 10MB byte cap. A
369
- * large first sync bursts many concurrent PUTs on one token, so any limiter would
370
- * only throttle legitimate uploads without adding real protection.
371
- */
372
- var Route$1 = createFileRoute("/api/machine/blob/$hash")({ server: { handlers: { PUT: async ({ request }) => {
373
- const auth = request.headers.get("authorization") ?? "";
374
- const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
375
- if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
376
- const hash = safeDecode(new URL(request.url).pathname.split("/").pop() ?? "");
377
- if (hash === null) return Response.json({ error: "bad hash" }, { status: 400 });
378
- const { BLOB_CAP } = await import("./artifacts-CzDJLaa6.mjs");
379
- const body = await readByteBody(request, BLOB_CAP);
380
- if (body.kind === "too-large") return Response.json({ error: "blob exceeds size cap" }, { status: 413 });
381
- if (body.kind === "invalid") return Response.json({ error: "could not read blob body" }, { status: 400 });
382
- const buf = Buffer.from(body.bytes.buffer, body.bytes.byteOffset, body.bytes.byteLength);
383
- const { getArtifactSync } = await import("./boot-CsLXrLEa.mjs");
384
- const r = await (await getArtifactSync()).putBlob(token, hash, buf);
385
- return Response.json(r.body, { status: r.status });
386
- } } } });
387
- /**
388
- * GET /api/artifact/:loopId/:path - stream one of a loop's live-synced artifact
389
- * blobs. Two dispositions:
390
- * - default: `attachment` (the download affordance for binary/oversize files;
391
- * text is shown inline via getArtifact).
392
- * - `?view=inline`: for a KNOWN image type only (the `imageMime` allowlist),
393
- * served `inline` with the real image content-type so an <img> can render it.
394
- * Hardened against a direct navigation turning the byte stream into a scripted
395
- * document on the app origin: `X-Content-Type-Options: nosniff` pins the type
396
- * and `Content-Security-Policy: sandbox` loads it in an opaque origin with no
397
- * scripts - so an inline SVG can't run script against the session even if the
398
- * URL is opened directly. Non-image inline requests fall back to attachment.
399
- *
400
- * Authed via the web session (not a machine token) and scoped to the loop owner,
401
- * mirroring the server functions' `ownedLoop` gate. Path-safe: the relative path is
402
- * normalized + traversal-rejected before it ever reaches a blob, and blob keys
403
- * are content hashes, never the user path. Reads stream from the BlobStore
404
- * (local by default, R2 when configured); the route never writes.
405
- */
406
- var PREFIX = "/api/artifact/";
407
- var Route = createFileRoute("/api/artifact/$loopId/$")({ server: { handlers: { GET: async ({ request }) => {
408
- const pathname = new URL(request.url).pathname;
409
- if (!pathname.startsWith(PREFIX)) return Response.json({ error: "not found" }, { status: 404 });
410
- const after = pathname.slice(14);
411
- const slash = after.indexOf("/");
412
- if (slash < 0) return Response.json({ error: "missing path" }, { status: 400 });
413
- const loopId = safeDecode(after.slice(0, slash));
414
- if (loopId === null) return Response.json({ error: "bad loop id" }, { status: 400 });
415
- const segments = after.slice(slash + 1).split("/").map((s) => safeDecode(s));
416
- if (segments.some((s) => s === null)) return Response.json({ error: "bad path" }, { status: 400 });
417
- const relPath = segments.join("/");
418
- const loop = await (await import("./store-DSNbXxqz.mjs")).getLoop(loopId);
419
- if (!loop) return Response.json({ error: "not found" }, { status: 404 });
420
- const { requestScope, canAccessLoop } = await import("./auth-BV0MxVdo.mjs");
421
- if (!canAccessLoop(loop.userId, await requestScope())) return Response.json({ error: "not found" }, { status: 404 });
422
- const { readLoopArtifactBytes } = await import("./artifactFiles-BvVmixrs.mjs");
423
- const r = await readLoopArtifactBytes(loopId, relPath);
424
- if (r.status !== 200 || !r.bytes) return Response.json({ error: "not found" }, { status: r.status });
425
- const filename = (r.filename || "file").replace(/["\\\r\n]/g, "_");
426
- const mime = new URL(request.url).searchParams.get("view") === "inline" ? imageMime(relPath) : null;
427
- if (mime) return new Response(new Uint8Array(r.bytes), {
428
- status: 200,
429
- headers: {
430
- "content-type": mime,
431
- "content-length": String(r.bytes.length),
432
- "content-disposition": `inline; filename="${filename}"`,
433
- "x-content-type-options": "nosniff",
434
- "content-security-policy": "sandbox",
435
- "cache-control": "private, no-store"
436
- }
437
- });
438
- return new Response(new Uint8Array(r.bytes), {
439
- status: 200,
440
- headers: {
441
- "content-type": r.binary ? "application/octet-stream" : "text/plain; charset=utf-8",
442
- "content-length": String(r.bytes.length),
443
- "content-disposition": `attachment; filename="${filename}"`,
444
- "cache-control": "private, no-store"
445
- }
446
- });
447
- } } } });
448
- var IndexRoute = Route$14.update({
449
- id: "/",
450
- path: "/",
451
- getParentRoute: () => Route$11
452
- });
453
- var MachineReportRoute = Route$10.update({
454
- id: "/machine/report",
455
- path: "/machine/report",
456
- getParentRoute: () => Route$11
457
- });
458
- var LoopsLoopIdRoute = Route$12.update({
459
- id: "/loops/$loopId",
460
- path: "/loops/$loopId",
461
- getParentRoute: () => Route$11
462
- });
463
- var ApiReadyRoute = Route$9.update({
464
- id: "/api/ready",
465
- path: "/api/ready",
466
- getParentRoute: () => Route$11
467
- });
468
- var ApiHealthRoute = Route$8.update({
469
- id: "/api/health",
470
- path: "/api/health",
471
- getParentRoute: () => Route$11
472
- });
473
- var ApiMachineSyncRoute = Route$7.update({
474
- id: "/api/machine/sync",
475
- path: "/api/machine/sync",
476
- getParentRoute: () => Route$11
477
- });
478
- var ApiMachineStatusRoute = Route$6.update({
479
- id: "/api/machine/status",
480
- path: "/api/machine/status",
481
- getParentRoute: () => Route$11
482
- });
483
- var ApiMachinePollRoute = Route$5.update({
484
- id: "/api/machine/poll",
485
- path: "/api/machine/poll",
486
- getParentRoute: () => Route$11
487
- });
488
- var ApiMachineCliRoute = Route$4.update({
489
- id: "/api/machine/cli",
490
- path: "/api/machine/cli",
491
- getParentRoute: () => Route$11
492
- });
493
- var ApiHealthDbRoute = Route$3.update({
494
- id: "/db",
495
- path: "/db",
496
- getParentRoute: () => ApiHealthRoute
497
- });
498
- var ApiAuthSplatRoute = Route$2.update({
499
- id: "/api/auth/$",
500
- path: "/api/auth/$",
501
- getParentRoute: () => Route$11
502
- });
503
- var LoopsLoopIdRunsRunIdRoute = Route$13.update({
504
- id: "/loops/$loopId_/runs/$runId",
505
- path: "/loops/$loopId/runs/$runId",
506
- getParentRoute: () => Route$11
507
- });
508
- var ApiMachineBlobHashRoute = Route$1.update({
509
- id: "/api/machine/blob/$hash",
510
- path: "/api/machine/blob/$hash",
511
- getParentRoute: () => Route$11
512
- });
513
- var ApiArtifactLoopIdSplatRoute = Route.update({
514
- id: "/api/artifact/$loopId/$",
515
- path: "/api/artifact/$loopId/$",
516
- getParentRoute: () => Route$11
517
- });
518
- var ApiHealthRouteChildren = { ApiHealthDbRoute };
519
- var rootRouteChildren = {
520
- IndexRoute,
521
- ApiHealthRoute: ApiHealthRoute._addFileChildren(ApiHealthRouteChildren),
522
- ApiReadyRoute,
523
- LoopsLoopIdRoute,
524
- MachineReportRoute,
525
- ApiAuthSplatRoute,
526
- ApiMachineCliRoute,
527
- ApiMachinePollRoute,
528
- ApiMachineStatusRoute,
529
- ApiMachineSyncRoute,
530
- ApiArtifactLoopIdSplatRoute,
531
- ApiMachineBlobHashRoute,
532
- LoopsLoopIdRunsRunIdRoute
533
- };
534
- var routeTree = Route$11._addFileChildren(rootRouteChildren)._addFileTypes();
535
- function getRouter() {
536
- return createRouter({
537
- routeTree,
538
- scrollRestoration: true,
539
- defaultPreload: "intent"
540
- });
541
- }
542
- //#endregion
543
- export { getRouter };
@@ -1,27 +0,0 @@
1
- import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
- import { O as useSession } from "./auth-client-RTpMpT4L.mjs";
3
- import { a as Loading } from "./actionUi-C_WswNb1.mjs";
4
- import { t as SignIn } from "./SignIn-Bp3uWaCQ.mjs";
5
- import { n as Route, t as DashboardView } from "./routes-BaanDO41.mjs";
6
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-8JSF7bX_.js
7
- var import_jsx_runtime = require_jsx_runtime();
8
- /** The dashboard route. Auth mode shows sign-in before loading owner-scoped data;
9
- * open mode renders the server's shared administrative workspace. */
10
- /** First-load failure screen — a calm retry instead of the router's default
11
- * error dump. Only the initial loader can land here; the in-page poll is
12
- * fetch-then-set and keeps stale data on a transient blip. */
13
- function Home() {
14
- const loaded = Route.useLoaderData();
15
- const { data: session, isPending } = useSession();
16
- if (loaded.auth.enabled && isPending) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
17
- className: "mx-auto max-w-[1180px] px-8 pt-12",
18
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loading, {})
19
- });
20
- if (loaded.auth.enabled && !session) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SignIn, {});
21
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DashboardView, {
22
- initial: loaded.initial,
23
- mode: loaded.auth.enabled ? "auth" : "open"
24
- });
25
- }
26
- //#endregion
27
- export { Home as component };
@@ -1,2 +0,0 @@
1
- import { g as computeRunDiff } from "./boot-U-CFI65B.mjs";
2
- export { computeRunDiff };
@@ -1,4 +0,0 @@
1
- //#region node_modules/.nitro/vite/services/ssr/assets/start-4ifArtKx.js
2
- var startInstance = void 0;
3
- //#endregion
4
- export { startInstance };
@@ -1,2 +0,0 @@
1
- import { I as getLoop } from "./boot-U-CFI65B.mjs";
2
- export { getLoop };
@@ -1,2 +0,0 @@
1
- import { _t as rememberConnectKey, lt as mintDeviceToken } from "./boot-U-CFI65B.mjs";
2
- export { mintDeviceToken, rememberConnectKey };
@@ -1,9 +0,0 @@
1
- ALTER TABLE "loops" DROP CONSTRAINT "loops_agent_check";
2
- --> statement-breakpoint
3
- ALTER TABLE "loops" ADD CONSTRAINT "loops_agent_check"
4
- CHECK ("agent" IN ('claude-code', 'codex', 'pi'));
5
- --> statement-breakpoint
6
- ALTER TABLE "runs" DROP CONSTRAINT "runs_agent_check";
7
- --> statement-breakpoint
8
- ALTER TABLE "runs" ADD CONSTRAINT "runs_agent_check"
9
- CHECK ("agent" IS NULL OR "agent" IN ('claude-code', 'codex', 'pi'));
@@ -1,15 +0,0 @@
1
- DROP INDEX "machines_team_idx";
2
- --> statement-breakpoint
3
- DROP INDEX "loops_team_idx";
4
- --> statement-breakpoint
5
- ALTER TABLE "machines" DROP COLUMN "team_id";
6
- --> statement-breakpoint
7
- ALTER TABLE "loops" DROP COLUMN "team_id";
8
- --> statement-breakpoint
9
- ALTER TABLE "connect_keys" DROP COLUMN "team_id";
10
- --> statement-breakpoint
11
- DROP TABLE "team_invites";
12
- --> statement-breakpoint
13
- DROP TABLE "team_members";
14
- --> statement-breakpoint
15
- DROP TABLE "teams";