@kky42/pievo-server 4.1.0 → 5.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 (43) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{MarkdownView-BuFKr9Jg.js → MarkdownView-T28pBEgh.js} +1 -1
  3. package/.output/public/assets/{actionUi-BZrLpgHT.js → actionUi-CY207nNM.js} +1 -1
  4. package/.output/public/assets/app-Cye1TgwS.css +2 -0
  5. package/.output/public/assets/{index-D0FIKiiW.js → index-BxXrF3gW.js} +3 -3
  6. package/.output/public/assets/loops._loopId-DuSEcu38.js +2 -0
  7. package/.output/public/assets/loops._loopId_.runs._runId-vjQaPXEe.js +3 -0
  8. package/.output/public/assets/{preload-helper-BSxGu4d0.js → preload-helper-B7p1iHUP.js} +2 -2
  9. package/.output/public/assets/routes-BUMEAWuS.js +1 -0
  10. package/.output/public/assets/{routes-BIOJZsig.js → routes-BrqOpUEi.js} +1 -1
  11. package/.output/server/_ssr/{MachinesModal-CLBZxZkq.mjs → MachinesModal-CB8rpDG2.mjs} +60 -29
  12. package/.output/server/_ssr/{MarkdownView-D1J7ImYF.mjs → MarkdownView-BOe75tli.mjs} +3 -6
  13. package/.output/server/_ssr/{actionUi-DE-Pb2tv.mjs → actionUi-CPYgZIr7.mjs} +57 -61
  14. package/.output/server/_ssr/artifactFiles-DLfSRhrV.mjs +67 -0
  15. package/.output/server/_ssr/{artifactKind-a2oxT2wQ.mjs → artifactKind-C0oXLA23.mjs} +2 -7
  16. package/.output/server/_ssr/{boot-DmpKtb0A.mjs → boot-jtVCf_pN.mjs} +239 -289
  17. package/.output/server/_ssr/{lifecycleUi-DEjM8Etm.mjs → lifecycleUi-Bd7_sthp.mjs} +8 -15
  18. package/.output/server/_ssr/{loopApi-DK1lQPrx.mjs → loopApi-0nR35Kh2.mjs} +12 -18
  19. package/.output/server/_ssr/{loopProjection-DdFivf7-.mjs → loopProjection-ZNfSmA5e.mjs} +3 -2
  20. package/.output/server/_ssr/{loops._loopId-DVCvGSEc.mjs → loops._loopId-CcJ3rxCU.mjs} +18 -89
  21. package/.output/server/_ssr/loops._loopId_.runs._runId-DFlrV3dJ.mjs +11 -0
  22. package/.output/server/_ssr/{loops._loopId_.runs._runId-BXbTfLlC.mjs → loops._loopId_.runs._runId-XkGF7HKo.mjs} +28 -19
  23. package/.output/server/_ssr/{machineFns-DLRHgDdS.mjs → machineFns-D-97a_hz.mjs} +6 -20
  24. package/.output/server/_ssr/{router-BhHsTGqu.mjs → router-CZ9CFWS2.mjs} +22 -31
  25. package/.output/server/_ssr/{routes-CCallyCT.mjs → routes-BBmuVs6n.mjs} +2 -2
  26. package/.output/server/_ssr/{routes-ngpX7h8D.mjs → routes-JxTh5mbj.mjs} +2 -2
  27. package/.output/server/_ssr/{routes-CL2Kef-Q.mjs → routes-fHRtSpkH.mjs} +6 -6
  28. package/.output/server/_ssr/ssr.mjs +22 -22
  29. package/.output/server/{_tanstack-start-manifest_v-BPiJ4yWT.mjs → _tanstack-start-manifest_v-C9JsxlwC.mjs} +8 -8
  30. package/.output/server/drizzle/0002_run_attention.sql +1 -0
  31. package/.output/server/drizzle/0003_run_status_ok.sql +13 -0
  32. package/.output/server/drizzle/meta/_journal.json +14 -0
  33. package/.output/server/index.mjs +49 -49
  34. package/drizzle/0002_run_attention.sql +1 -0
  35. package/drizzle/0003_run_status_ok.sql +13 -0
  36. package/drizzle/meta/_journal.json +14 -0
  37. package/package.json +1 -1
  38. package/.output/public/assets/app-x1CeeY7N.css +0 -2
  39. package/.output/public/assets/loops._loopId-DTkIz4C0.js +0 -2
  40. package/.output/public/assets/loops._loopId_.runs._runId-CsV2Ih4Z.js +0 -3
  41. package/.output/public/assets/routes-m4Z90DTR.js +0 -1
  42. package/.output/server/_ssr/artifactFiles-B0NPRDOR.mjs +0 -52
  43. package/.output/server/_ssr/loops._loopId_.runs._runId-Dr6E-FA5.mjs +0 -16
@@ -1,5 +1,5 @@
1
1
  import { i as getServerFnById, r as createServerFn, t as TSS_SERVER_FUNCTION } from "./ssr.mjs";
2
- //#region node_modules/.nitro/vite/services/ssr/assets/lifecycleUi-DEjM8Etm.js
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/lifecycleUi-Bd7_sthp.js
3
3
  var createSsrRpc = (functionId) => {
4
4
  const url = "/_serverFn/" + functionId;
5
5
  const serverFnMeta = { id: functionId };
@@ -19,24 +19,17 @@ var createSsrRpc = (functionId) => {
19
19
  * The machine-facing poll, CLI, report, and run-file endpoints are sibling server
20
20
  * routes in this same process, so one process owns the single scheduler.
21
21
  */
22
- /**
23
- * Client config. `pievoCli` is the CLI invocation prefix the skill + connect
24
- * dialog use for every verb (`daemon start`, `new`, …) — defaults to the globally
25
- * installed `pievo`. Set PIEVO_CLI locally to a runnable command that points at the in-repo
26
- * daemon, e.g. `tsx /abs/packages/daemon/src/cli.ts` or
27
- * `node /abs/packages/daemon/dist/cli.js`, so loops created from THIS server tell
28
- * Claude Code to run your local code instead of the registry build.
29
- */
22
+ /** `pievoCli` prefixes every command the dashboard shows. PIEVO_CLI points it at an
23
+ * in-repo daemon (e.g. `node /abs/packages/daemon/dist/cli.js`) so loops created
24
+ * from this server run local code instead of the registry build. */
30
25
  var getConfig = createServerFn({ method: "GET" }).handler(createSsrRpc("cbb6a1b09b309459bbba3dc3851ae85fc468658b3cda77e22b6c6f3e28b65af4"));
31
- /** GET — visible loops as compact summaries, most recently run first. */
32
26
  var listLoops = createServerFn({ method: "GET" }).handler(createSsrRpc("23e290b2b7483ceea6fe669024c35b12c2c4ddd76649f6fab21c958ceda02940"));
33
27
  var getLoopDetail = createServerFn({ method: "GET" }).validator((id) => id).handler(createSsrRpc("b050455aacc84dd2a2c9a45517826ad10f91d8f1545e87cc7015784cc7116a97"));
34
28
  var loadOlderRuns = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("85fdf73d4fee43aed762f93c5d47d33200fbff39e2d082647dc921f610d32698"));
35
29
  var getArtifacts = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("b18bbec3d06dfc77714e1e527dfb9172fd7575a64a6c0d6e73ba6dc658dccf0d"));
36
30
  var getArtifact = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("ed10d5fe79208e5e0e4e18e4a17b18d7f7db4dd16204c00c3ac90c4644668841"));
37
- /** GET — a run's per-file diff vs the previous run. Lazy by runId like
38
- * computed on the server at read time (no stored diffs). Old runs
39
- * with no snapshot return `hasSnapshot: false` for the degrade copy. */
31
+ /** A run's per-file diff against the previous run, computed at read time. Runs
32
+ * without a snapshot return `hasSnapshot: false`. */
40
33
  var getRunDiff = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("08c28b892712a1b491b120b74c7dcb81761988d31ccea60216d1807b73c1182c"));
41
34
  var patchLoop = createServerFn({ method: "POST" }).validator((d) => d).handler(createSsrRpc("9befe6b8d67ccbbd7aa9b538bde2a161a9894761c8936761a9503fda1a88c0fc"));
42
35
  /** Dedicated lifecycle operations. Dashboard callers never synthesize these
@@ -78,11 +71,11 @@ function lifecyclePresentation(loop) {
78
71
  };
79
72
  if (loop.pauseCause?.kind === "blocked") return {
80
73
  label: "Paused · blocked",
81
- tone: "attention"
74
+ tone: "accent"
82
75
  };
83
76
  if (loop.pauseCause?.kind === "failure-streak") return {
84
77
  label: "Paused · failure streak",
85
- tone: "attention"
78
+ tone: "accent"
86
79
  };
87
80
  if (loop.pauseCause?.kind === "owner") return {
88
81
  label: "Paused · owner",
@@ -1,8 +1,8 @@
1
- import { B as requestDeleteLoop, C as getRun, F as machinePresence, G as tryDeleteLoop, K as updateLoop, L as pauseLoop$1, O as listLoops$1, S as getMachine, T as hasRunningRun, U as startLoop$1, V as requestRunCancel, W as stopLoop$1, j as listRunsBefore, k as listMachines, m as validateLoopEdit, s as ensureServer, v as forceDeleteLoop, x as getLoop } from "./boot-DmpKtb0A.mjs";
2
- import { a as toRunSummaries, i as toLoopSummaries, r as toLoopDetail, t as sortLoopSummariesByRecentRun } from "./loopProjection-DdFivf7-.mjs";
1
+ import { C as getRun, G as stopLoop$1, H as requestRunCancel, I as machinePresence, K as tryDeleteLoop, O as listLoops$1, R as pauseLoop$1, S as getMachine, T as hasRunningRun, V as requestDeleteLoop, W as startLoop$1, j as listRunsBefore, k as listMachines, m as validateLoopEdit, q as updateLoop, s as ensureServer, v as forceDeleteLoop, x as getLoop } from "./boot-jtVCf_pN.mjs";
2
+ import { a as toRunSummaries, i as toLoopSummaries, r as toLoopDetail, t as sortLoopSummariesByRecentRun } from "./loopProjection-ZNfSmA5e.mjs";
3
3
  import { r as createServerFn } from "./ssr.mjs";
4
4
  import { t as createServerRpc } from "./createServerRpc-BD8Xn_0_.mjs";
5
- //#region node_modules/.nitro/vite/services/ssr/assets/loopApi-DK1lQPrx.js
5
+ //#region node_modules/.nitro/vite/services/ssr/assets/loopApi-0nR35Kh2.js
6
6
  /**
7
7
  * Server functions backing the dashboard. They run server-side (createServerFn
8
8
  * → RPC) and call the IN-PROCESS scheduler + store directly — the scheduler
@@ -17,14 +17,9 @@ async function findLoop(id) {
17
17
  const loop = await getLoop(id);
18
18
  return loop ? { loop } : void 0;
19
19
  }
20
- /**
21
- * Client config. `pievoCli` is the CLI invocation prefix the skill + connect
22
- * dialog use for every verb (`daemon start`, `new`, …) — defaults to the globally
23
- * installed `pievo`. Set PIEVO_CLI locally to a runnable command that points at the in-repo
24
- * daemon, e.g. `tsx /abs/packages/daemon/src/cli.ts` or
25
- * `node /abs/packages/daemon/dist/cli.js`, so loops created from THIS server tell
26
- * Claude Code to run your local code instead of the registry build.
27
- */
20
+ /** `pievoCli` prefixes every command the dashboard shows. PIEVO_CLI points it at an
21
+ * in-repo daemon (e.g. `node /abs/packages/daemon/dist/cli.js`) so loops created
22
+ * from this server run local code instead of the registry build. */
28
23
  var getConfig_createServerFn_handler = createServerRpc({
29
24
  id: "cbb6a1b09b309459bbba3dc3851ae85fc468658b3cda77e22b6c6f3e28b65af4",
30
25
  name: "getConfig",
@@ -34,8 +29,7 @@ var getConfig = createServerFn({ method: "GET" }).handler(getConfig_createServer
34
29
  const custom = process.env.PIEVO_CLI?.trim();
35
30
  return {
36
31
  pievoCli: custom || "pievo",
37
- /** True when a non-default (dev) CLI is configured — the New-loop paste then
38
- * carries an explicit `pievo-cli:` line so Claude Code uses it verbatim. */
32
+ /** A custom CLI is shown verbatim, without the global npm install step. */
39
33
  customCli: !!custom,
40
34
  /** Best URL for other devices, published by the launcher (HTTPS, Tailscale, then LAN). */
41
35
  publicUrl: process.env.PIEVO_PUBLIC_URL?.trim() || null
@@ -81,7 +75,7 @@ var getArtifacts_createServerFn_handler = createServerRpc({
81
75
  var getArtifacts = createServerFn({ method: "GET" }).validator((d) => d).handler(getArtifacts_createServerFn_handler, async ({ data }) => {
82
76
  await backend();
83
77
  if (!await findLoop(data.loopId)) return [];
84
- const { listLoopArtifacts } = await import("./artifactFiles-B0NPRDOR.mjs");
78
+ const { listLoopArtifacts } = await import("./artifactFiles-DLfSRhrV.mjs");
85
79
  return listLoopArtifacts(data.loopId);
86
80
  });
87
81
  var getArtifact_createServerFn_handler = createServerRpc({
@@ -92,7 +86,7 @@ var getArtifact_createServerFn_handler = createServerRpc({
92
86
  var getArtifact = createServerFn({ method: "GET" }).validator((d) => d).handler(getArtifact_createServerFn_handler, async ({ data }) => {
93
87
  await backend();
94
88
  if (!await findLoop(data.loopId)) return { error: "file not found" };
95
- const { readLoopArtifact } = await import("./artifactFiles-B0NPRDOR.mjs");
89
+ const { readLoopArtifact } = await import("./artifactFiles-DLfSRhrV.mjs");
96
90
  return readLoopArtifact(data.loopId, data.path);
97
91
  });
98
92
  var getRunDiff_createServerFn_handler = createServerRpc({
@@ -111,7 +105,7 @@ var getRunDiff = createServerFn({ method: "GET" }).validator((d) => d).handler(g
111
105
  hasSnapshot: false,
112
106
  files: []
113
107
  };
114
- const { computeRunDiff } = await import("./boot-DmpKtb0A.mjs").then((n) => n.r).then((n) => n.i);
108
+ const { computeRunDiff } = await import("./boot-jtVCf_pN.mjs").then((n) => n.r).then((n) => n.i);
115
109
  return computeRunDiff(data.runId);
116
110
  });
117
111
  var patchLoop_createServerFn_handler = createServerRpc({
@@ -197,7 +191,7 @@ var deleteLoop = createServerFn({ method: "POST" }).validator((id) => id).handle
197
191
  if (!await requestDeleteLoop(id)) return { error: "not found" };
198
192
  if (unreachable) {
199
193
  if (!await forceDeleteLoop(id)) return { error: "delete failed; server data was not deleted" };
200
- const { logger } = await import("./boot-DmpKtb0A.mjs").then((n) => n.M).then((n) => n.Qt);
194
+ const { logger } = await import("./boot-jtVCf_pN.mjs").then((n) => n.N).then((n) => n.$t);
201
195
  logger.child({ mod: "loop-lifecycle" }).warn({
202
196
  action: "unreachable-delete",
203
197
  loopId: id,
@@ -256,7 +250,7 @@ var mintConnectKey_createServerFn_handler = createServerRpc({
256
250
  }, (opts) => mintConnectKey.__executeServer(opts));
257
251
  var mintConnectKey = createServerFn({ method: "POST" }).handler(mintConnectKey_createServerFn_handler, async () => {
258
252
  await backend();
259
- const { mintDeviceToken, rememberConnectKey } = await import("./boot-DmpKtb0A.mjs").then((n) => n.M).then((n) => n.g);
253
+ const { mintDeviceToken, rememberConnectKey } = await import("./boot-jtVCf_pN.mjs").then((n) => n.N).then((n) => n.g);
260
254
  const token = mintDeviceToken();
261
255
  await rememberConnectKey(token);
262
256
  return { token };
@@ -1,5 +1,5 @@
1
- import { A as listRuns, F as machinePresence, R as recentLoopUsage, S as getMachine, T as hasRunningRun, a as canonicalLoopSummary, g as countRuns, i as canonicalLoopEnvelope, o as daemonNeedsUpdate, t as MIN_DAEMON_VERSION, w as hasPendingRun, z as reconciliationStatesForRuns } from "./boot-DmpKtb0A.mjs";
2
- //#region node_modules/.nitro/vite/services/ssr/assets/loopProjection-DdFivf7-.js
1
+ import { A as listRuns, B as reconciliationStatesForRuns, I as machinePresence, S as getMachine, T as hasRunningRun, a as canonicalLoopSummary, g as countRuns, i as canonicalLoopEnvelope, o as daemonNeedsUpdate, t as MIN_DAEMON_VERSION, w as hasPendingRun, z as recentLoopUsage } from "./boot-jtVCf_pN.mjs";
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/loopProjection-ZNfSmA5e.js
3
3
  var SUMMARY_RUNS = 18;
4
4
  var RECENT_USAGE_WINDOW_MS = 10080 * 60 * 1e3;
5
5
  /** Dashboard order: loops with runs first, most recent run first. The incoming
@@ -28,6 +28,7 @@ function toRunSummary(r, reconciliation) {
28
28
  agent: r.agent ?? null,
29
29
  status: r.status ?? null,
30
30
  message: r.message ?? null,
31
+ attention: r.attention ?? null,
31
32
  durationMs: r.durationMs ?? null,
32
33
  exitCode: r.exitCode ?? null,
33
34
  finalText: r.finalText ?? null,
@@ -1,13 +1,13 @@
1
1
  import { o as __toESM } from "../_runtime.mjs";
2
- import { J as validateLoopTags, h as CODING_AGENTS } from "./boot-DmpKtb0A.mjs";
2
+ import { Y as validateLoopTags, h as CODING_AGENTS } from "./boot-jtVCf_pN.mjs";
3
3
  import { g as require_react, h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
4
4
  import { d as useNavigate, u as Link } from "../_libs/@tanstack/react-router+[...].mjs";
5
- import { _ as runLoop, a as deriveLoopLifecycle, g as pauseLoop, h as patchLoop, i as deleteLoop, l as getLoopDetail, o as getArtifact, p as loadOlderRuns, r as daemonStopSupport, s as getArtifacts, t as DAEMON_UPGRADE_REQUIRED, v as startLoop, y as stopLoop } from "./lifecycleUi-DEjM8Etm.mjs";
6
- import { A as tsShort, E as runPulseStyle, M as useFlash, O as sectionHeadCls, S as labelCls, _ as fmt, a as Loading, b as humanBytes, c as btn, d as btnQuiet, g as dur, i as LoadErrorCard, k as selectCls, l as btnDanger, m as dotLabel, n as ErrorBanner, p as dotColor, r as FlashLine, t as ConfirmBar, u as btnPrimary, w as rel, x as inputCls, y as focusRing } from "./actionUi-DE-Pb2tv.mjs";
7
- import { a as Timeline, l as mergeRuns, n as MachinesModal, t as LoopOverview } from "./MachinesModal-CLBZxZkq.mjs";
8
- import { n as artifactKind, t as Route } from "./artifactKind-a2oxT2wQ.mjs";
9
- import { t as MarkdownView } from "./MarkdownView-D1J7ImYF.mjs";
10
- //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-DVCvGSEc.js
5
+ import { _ as runLoop, a as deriveLoopLifecycle, g as pauseLoop, h as patchLoop, i as deleteLoop, l as getLoopDetail, o as getArtifact, p as loadOlderRuns, r as daemonStopSupport, s as getArtifacts, t as DAEMON_UPGRADE_REQUIRED, v as startLoop, y as stopLoop } from "./lifecycleUi-Bd7_sthp.mjs";
6
+ import { A as tsShort, E as runSwatch, M as useFlash, O as sectionHeadCls, T as runPulseStyle, a as LoadErrorCard, b as inputCls, d as btnPrimary, f as btnQuiet, g as fmt, h as dur, i as FlashLine, k as selectCls, l as btn, m as dotLabel, n as ConfirmBar, o as Loading, r as ErrorBanner, u as btnDanger, v as focusRing, w as rel, x as labelCls, y as humanBytes } from "./actionUi-CPYgZIr7.mjs";
7
+ import { a as Timeline, l as mergeRuns, n as MachinesModal, t as LoopOverview } from "./MachinesModal-CB8rpDG2.mjs";
8
+ import { n as artifactKind, t as Route } from "./artifactKind-C0oXLA23.mjs";
9
+ import { t as MarkdownView } from "./MarkdownView-BOe75tli.mjs";
10
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-CcJ3rxCU.js
11
11
  var import_react = /* @__PURE__ */ __toESM(require_react());
12
12
  var import_jsx_runtime = require_jsx_runtime();
13
13
  /** Download URL for one loop file. Each path segment is encoded independently so
@@ -20,22 +20,9 @@ function inlineHref(loopId, path) {
20
20
  return `${downloadHref(loopId, path)}?view=inline`;
21
21
  }
22
22
  /**
23
- * Shared pieces of the artifact content viewer. The Files panel uses this one
24
- * dispatch so binary/oversize copy and type-appropriate rendering cannot drift.
25
- *
26
- * Display types (see `lib/artifactKind`):
27
- * - HTML → a STRICT sandboxed iframe (`sandbox="allow-scripts"`, deliberately
28
- * NO `allow-same-origin`): user-machine-synced markup renders richly
29
- * but in an opaque origin, so its scripts can't read the app's
30
- * cookies/session or reach `parent` - stored-XSS is contained by
31
- * design. A Preview/Source toggle exposes the raw markup too.
32
- * - image → an <img> pointed at the hardened inline route (`inlineHref`); the
33
- * bytes are NEVER inlined into the app DOM (SVG is scriptable, so it
34
- * rides as an image, which does not execute embedded scripts).
35
- * - markdown → the shared markdown pipeline (Preview) with a raw Source toggle.
36
- * - text → monospace source.
37
- * Everything keeps a Download fallback; a legacy oversize/binary entry has no
38
- * renderable bytes, so it says so rather than showing an empty pane.
23
+ * One viewer dispatch for every loop file, so binary/oversize copy and rendering
24
+ * cannot drift between surfaces. Images, including scriptable SVG, always load
25
+ * through the hardened inline route and are never inlined into the app DOM.
39
26
  */
40
27
  function ViewerHead({ path, meta, action }) {
41
28
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -258,7 +245,7 @@ function LoopFilesPanel({ loopId, configuredPaths, running }) {
258
245
  })]
259
246
  }), files.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
260
247
  className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled shadow-card",
261
- children: artifacts == null ? "Loading…" : "No loop file has been written yet. Runs create MEMORY.md and any configured artifacts."
248
+ children: artifacts == null ? "Loading…" : "No files are available for this loop."
262
249
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
263
250
  className: "grid h-[min(600px,68vh)] grid-cols-1 overflow-hidden rounded-card border border-hairline bg-surface shadow-card sm:grid-cols-[210px_1fr]",
264
251
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
@@ -314,9 +301,6 @@ function initState(initial) {
314
301
  model: initial?.model ?? "",
315
302
  reasoningEffort: initial?.reasoningEffort ?? "",
316
303
  prompt: initial?.prompt ?? "",
317
- keep: initial?.statusDefinitions?.keep ?? "",
318
- noChange: initial?.statusDefinitions?.noChange ?? "",
319
- block: initial?.statusDefinitions?.block ?? "",
320
304
  artifacts: (initial?.artifacts ?? []).join("\n")
321
305
  };
322
306
  }
@@ -418,10 +402,7 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
418
402
  const missing = [
419
403
  ["Name", form.name],
420
404
  ["Working directory", form.workdir],
421
- ["Prompt", form.prompt],
422
- ["Keep definition", form.keep],
423
- ["No-change definition", form.noChange],
424
- ["Block definition", form.block]
405
+ ["Prompt", form.prompt]
425
406
  ].find(([, value]) => !value.trim());
426
407
  if (missing) {
427
408
  setError(`${missing[0]} is required.`);
@@ -458,11 +439,6 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
458
439
  model: form.model.trim() || null,
459
440
  reasoningEffort: form.reasoningEffort.trim() || null,
460
441
  prompt: form.prompt,
461
- statusDefinitions: {
462
- keep: form.keep,
463
- noChange: form.noChange,
464
- block: form.block
465
- },
466
442
  artifacts: form.artifacts.split(/\r?\n/).filter((name) => name.length > 0)
467
443
  };
468
444
  } }));
@@ -667,31 +643,9 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
667
643
  onChange: (value) => set("prompt", value),
668
644
  rows: 10
669
645
  }),
670
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
671
- title: "Status definitions",
672
- hint: "All three definitions are required and are appended to every run."
673
- }),
674
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
675
- label: "keep",
676
- value: form.keep,
677
- onChange: (value) => set("keep", value),
678
- rows: 2
679
- }),
680
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
681
- label: "no-change",
682
- value: form.noChange,
683
- onChange: (value) => set("noChange", value),
684
- rows: 2
685
- }),
686
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
687
- label: "block",
688
- value: form.block,
689
- onChange: (value) => set("block", value),
690
- rows: 2
691
- }),
692
646
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
693
647
  title: "Artifacts",
694
- hint: "Optional .md or .html file names (up to 4, 2 MB each) that runs update and the dashboard renders. One name per line. Every loop also has MEMORY.md."
648
+ hint: "Optional text/code file names such as .md, .html, .sh, .py, .json, or .csv (up to 4, 256 KB each) that runs update and the dashboard renders. One name per line. Every loop also has MEMORY.md."
695
649
  }),
696
650
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
697
651
  label: "Artifact files",
@@ -1055,29 +1009,6 @@ function LoopDetailView({ id }) {
1055
1009
  children: loop.prompt
1056
1010
  })
1057
1011
  }),
1058
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1059
- label: "Status definitions",
1060
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", {
1061
- className: "space-y-1",
1062
- children: [
1063
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
1064
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "keep:" }),
1065
- " ",
1066
- loop.statusDefinitions.keep
1067
- ] }),
1068
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
1069
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "no-change:" }),
1070
- " ",
1071
- loop.statusDefinitions.noChange
1072
- ] }),
1073
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
1074
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "block:" }),
1075
- " ",
1076
- loop.statusDefinitions.block
1077
- ] })
1078
- ]
1079
- })
1080
- }),
1081
1012
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1082
1013
  label: "Artifacts",
1083
1014
  children: loop.artifacts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
@@ -1140,7 +1071,7 @@ function EditHead({ name, onBack }) {
1140
1071
  }),
1141
1072
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1142
1073
  className: "mt-1.5 text-meta text-secondary",
1143
- children: "Edit tags, schedule, execution settings, prompt, status definitions, and artifact files."
1074
+ children: "Edit tags, schedule, execution settings, prompt, and artifact files."
1144
1075
  })
1145
1076
  ] });
1146
1077
  }
@@ -1189,7 +1120,7 @@ function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1189
1120
  className: "flex items-start gap-2.5 py-2.5 hover:bg-raised",
1190
1121
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1191
1122
  className: "mt-1 inline-block size-2.5 shrink-0 rounded-[2px]",
1192
- style: { background: dotColor(run) },
1123
+ style: runSwatch(run),
1193
1124
  title: dotLabel(run)
1194
1125
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1195
1126
  className: "min-w-0 flex-1",
@@ -1210,6 +1141,9 @@ function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1210
1141
  className: "size-1.5 rounded-full",
1211
1142
  style: runPulseStyle
1212
1143
  }), "Running"]
1144
+ }) : run.attention ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1145
+ className: "font-medium text-attention",
1146
+ children: ["! ", run.attention]
1213
1147
  }) : run.error || run.message || dotLabel(run)
1214
1148
  })]
1215
1149
  })]
@@ -1218,11 +1152,6 @@ function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1218
1152
  })]
1219
1153
  });
1220
1154
  }
1221
- /**
1222
- * Loop detail page — `/loops/$loopId`: a loop header and action toolbar, the
1223
- * loop Files panel, and the runs timeline. The view owns its own data + self-poll.
1224
- * Run rows link on to `/loops/$loopId/runs/$runId`.
1225
- */
1226
1155
  function LoopDetailPage() {
1227
1156
  const { loopId } = Route.useParams();
1228
1157
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopDetailView, { id: loopId });
@@ -0,0 +1,11 @@
1
+ import { c as createFileRoute, s as lazyRouteComponent } from "../_libs/@tanstack/react-router+[...].mjs";
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId_.runs._runId-DFlrV3dJ.js
3
+ var $$splitComponentImporter = () => import("./loops._loopId_.runs._runId-XkGF7HKo.mjs");
4
+ /** The trailing `_` on `$loopId` opts the run page out of the loop page's component,
5
+ * so a run gets its own deep-linkable, back-button-friendly page. */
6
+ var Route = createFileRoute("/loops/$loopId_/runs/$runId")({
7
+ ssr: false,
8
+ component: lazyRouteComponent($$splitComponentImporter, "component")
9
+ });
10
+ //#endregion
11
+ export { Route as t };
@@ -1,11 +1,11 @@
1
1
  import { o as __toESM } from "../_runtime.mjs";
2
2
  import { g as require_react, h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
3
3
  import { u as Link } from "../_libs/@tanstack/react-router+[...].mjs";
4
- import { b as stopRun, l as getLoopDetail, p as loadOlderRuns, u as getRunDiff } from "./lifecycleUi-DEjM8Etm.mjs";
5
- import { E as runPulseStyle, O as sectionHeadCls, _ as fmt, a as Loading, b as humanBytes, c as btn, g as dur, i as LoadErrorCard, l as btnDanger, o as Pill, s as StatusPill, v as fnum } from "./actionUi-DE-Pb2tv.mjs";
6
- import { t as MarkdownView } from "./MarkdownView-D1J7ImYF.mjs";
7
- import { t as Route } from "./loops._loopId_.runs._runId-Dr6E-FA5.mjs";
8
- //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId_.runs._runId-BXbTfLlC.js
4
+ import { b as stopRun, l as getLoopDetail, p as loadOlderRuns, u as getRunDiff } from "./lifecycleUi-Bd7_sthp.mjs";
5
+ import { O as sectionHeadCls, T as runPulseStyle, _ as fnum, a as LoadErrorCard, c as StatusPill, g as fmt, h as dur, l as btn, o as Loading, s as Pill, u as btnDanger, y as humanBytes } from "./actionUi-CPYgZIr7.mjs";
6
+ import { t as MarkdownView } from "./MarkdownView-BOe75tli.mjs";
7
+ import { t as Route } from "./loops._loopId_.runs._runId-DFlrV3dJ.mjs";
8
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId_.runs._runId-XkGF7HKo.js
9
9
  var import_react = /* @__PURE__ */ __toESM(require_react());
10
10
  var import_jsx_runtime = require_jsx_runtime();
11
11
  /**
@@ -423,13 +423,20 @@ function RunDetailView({ loopId, runId }) {
423
423
  className: "min-w-0",
424
424
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
425
425
  className: "flex flex-wrap items-center gap-3",
426
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", {
427
- className: "text-[26px] font-medium leading-tight tracking-tight text-display",
428
- children: ["Run · ", loopName]
429
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusPill, {
430
- run,
431
- colorText: true
432
- })]
426
+ children: [
427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", {
428
+ className: "text-[26px] font-medium leading-tight tracking-tight text-display",
429
+ children: ["Run · ", loopName]
430
+ }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusPill, {
432
+ run,
433
+ colorText: true
434
+ }),
435
+ run.attention && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
436
+ tone: "attention",
437
+ children: "Needs attention"
438
+ })
439
+ ]
433
440
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
434
441
  className: "mt-2.5 flex flex-wrap items-center gap-x-2.5 gap-y-1.5 text-meta text-secondary",
435
442
  children: [
@@ -486,6 +493,13 @@ function RunDetailView({ loopId, runId }) {
486
493
  children: run.reconciliation === "blocking" ? detail.machine.presence === "online" ? "The machine is online and checking an interrupted run. Queued work starts automatically after recovery." : "Waiting for the machine to reconnect and check an interrupted run. Queued work starts automatically; no action is required." : "This interrupted run no longer blocks queued work. Pievo will still accept its saved report if it arrives."
487
494
  })
488
495
  }) : null,
496
+ run.attention && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
497
+ label: "Needs attention",
498
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
499
+ className: "break-words text-body font-medium text-attention",
500
+ children: run.attention
501
+ })
502
+ }),
489
503
  run.reportIncident && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
490
504
  label: "Terminal report rejected",
491
505
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -598,13 +612,8 @@ function RunDetailView({ loopId, runId }) {
598
612
  })]
599
613
  })] });
600
614
  }
601
- /**
602
- * Run detail PAGE — `/loops/$loopId/runs/$runId`. A standalone page (the trailing
603
- * `_` on the `$loopId` segment opts it out of the loop page's component so the run
604
- * gets its own full surface, deep-linkable + browser-back friendly) rather than a
605
- * modal or an inline panel. It resolves the run from the loop's detail payload
606
- * (reusing getLoopDetail) and the existing loop-file diff.
607
- */
615
+ /** The trailing `_` on `$loopId` opts the run page out of the loop page's component,
616
+ * so a run gets its own deep-linkable, back-button-friendly page. */
608
617
  function RunDetailPage() {
609
618
  const { loopId, runId } = Route.useParams();
610
619
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
@@ -1,30 +1,16 @@
1
- import { H as sha256, I as mintDeviceToken, N as loopsForMachine, P as machineIdFromToken, S as getMachine, _ as createMachine$1, k as listMachines$1, o as daemonNeedsUpdate, q as updateMachine, s as ensureServer, t as MIN_DAEMON_VERSION, y as forceDeleteMachine } from "./boot-DmpKtb0A.mjs";
1
+ import { F as machineIdFromToken, J as updateMachine, L as mintDeviceToken, P as loopsForMachine, S as getMachine, U as sha256, _ as createMachine$1, k as listMachines$1, o as daemonNeedsUpdate, s as ensureServer, t as MIN_DAEMON_VERSION, y as forceDeleteMachine } from "./boot-jtVCf_pN.mjs";
2
2
  import { r as createServerFn } from "./ssr.mjs";
3
3
  import { t as createServerRpc } from "./createServerRpc-BD8Xn_0_.mjs";
4
- //#region node_modules/.nitro/vite/services/ssr/assets/machineFns-DLRHgDdS.js
4
+ //#region node_modules/.nitro/vite/services/ssr/assets/machineFns-D-97a_hz.js
5
5
  /**
6
- * Latest published @kky42/pievo version — a cached, fail-silent lookup of the
7
- * npm dist-tag `latest`, so the web can tell a user their daemon is outdated and
8
- * show the exact update command.
9
- *
10
- * Zero-exec invariant: this is a plain HTTP GET against the public npm registry —
11
- * the server runs no LLM and executes no user code. It NEVER surfaces an error:
12
- * if npm is unreachable/slow/garbage, the cache simply stays as-is (undefined at
13
- * first) and the web shows no update hint. Bounded timeout + ~1h in-memory cache
14
- * so the machine-list hot path never blocks on the network.
15
- *
16
- * The fetch is injectable (mirroring the gateway's injectable notifier/blobStore)
17
- * so tests need no network: construct a `LatestDaemonVersion` with a fake fetch
18
- * and `now`, or call the pure `fetchNpmLatest` directly.
6
+ * Latest published @kky42/pievo version for the dashboard's update hint: a plain
7
+ * GET of the npm `latest` dist-tag. Fail-silent with a bounded timeout and a ~1h
8
+ * in-memory cache, so the machine list never blocks on or surfaces npm errors.
19
9
  */
20
10
  var NPM_URL = "https://registry.npmjs.org/@kky42/pievo";
21
11
  var DEFAULT_TTL_MS = 3600 * 1e3;
22
12
  var FETCH_TIMEOUT_MS = 4e3;
23
- /**
24
- * One npm `latest` read — bounded + fail-silent. Returns the version string or
25
- * null (unreachable/timeout/malformed). Pure w.r.t. the injected fetch, so it's
26
- * directly unit-testable with no network.
27
- */
13
+ /** One bounded npm `latest` read; null when unreachable, slow, or malformed. */
28
14
  async function fetchNpmLatest(fetchImpl, timeoutMs = FETCH_TIMEOUT_MS) {
29
15
  const ctrl = new AbortController();
30
16
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
@@ -1,12 +1,12 @@
1
- import { d as validOptionalPollString, f as validPollCurrentRun, l as readByteBody, n as POLL_HTTP_BODY_FIELDS, p as validPollWireId, s as ensureServer, u as readJsonBody } from "./boot-DmpKtb0A.mjs";
1
+ import { d as validOptionalPollString, f as validPollCurrentRun, l as readByteBody, n as POLL_HTTP_BODY_FIELDS, p as validPollWireId, s as ensureServer, u as readJsonBody } from "./boot-jtVCf_pN.mjs";
2
2
  import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
3
3
  import { c as createFileRoute, i as HeadContent, l as createRootRoute, o as createRouter, r as Scripts } from "../_libs/@tanstack/react-router+[...].mjs";
4
- import { r as imageMime, t as Route$9 } from "./artifactKind-a2oxT2wQ.mjs";
5
- import { t as Route$10 } from "./loops._loopId_.runs._runId-Dr6E-FA5.mjs";
6
- import { n as Route$11 } from "./routes-CL2Kef-Q.mjs";
7
- //#region node_modules/.nitro/vite/services/ssr/assets/router-BhHsTGqu.js
4
+ import { r as imageMime, t as Route$9 } from "./artifactKind-C0oXLA23.mjs";
5
+ import { t as Route$10 } from "./loops._loopId_.runs._runId-DFlrV3dJ.mjs";
6
+ import { n as Route$11 } from "./routes-fHRtSpkH.mjs";
7
+ //#region node_modules/.nitro/vite/services/ssr/assets/router-CZ9CFWS2.js
8
8
  var import_jsx_runtime = require_jsx_runtime();
9
- var app_default = "/assets/app-x1CeeY7N.css";
9
+ var app_default = "/assets/app-Cye1TgwS.css";
10
10
  var Route$8 = createRootRoute({
11
11
  head: () => ({
12
12
  meta: [
@@ -59,7 +59,7 @@ var Route$7 = createFileRoute("/machine/report")({ server: { handlers: { POST: a
59
59
  invalidBody: {},
60
60
  parseBody: (value) => value && typeof value === "object" ? value : {},
61
61
  handle: async (token, body) => {
62
- const { getGateway } = await import("./boot-DmpKtb0A.mjs").then((n) => n.r).then((n) => n.t);
62
+ const { getGateway } = await import("./boot-jtVCf_pN.mjs").then((n) => n.r).then((n) => n.t);
63
63
  const r = await (await getGateway()).report(token, body);
64
64
  return Response.json(r.body, { status: r.status });
65
65
  }
@@ -84,7 +84,7 @@ var Route$4 = createFileRoute("/api/machine/status")({ server: { handlers: { GET
84
84
  const auth = request.headers.get("authorization") ?? "";
85
85
  const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
86
86
  if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
87
- const { getGateway } = await import("./boot-DmpKtb0A.mjs").then((n) => n.r).then((n) => n.t);
87
+ const { getGateway } = await import("./boot-jtVCf_pN.mjs").then((n) => n.r).then((n) => n.t);
88
88
  const r = await (await getGateway()).status(token);
89
89
  return Response.json(r.body, { status: r.status });
90
90
  } } } });
@@ -122,7 +122,7 @@ var Route$3 = createFileRoute("/api/machine/poll")({ server: { handlers: { POST:
122
122
  invalidJsonError: "invalid JSON body",
123
123
  parseBody: parsePollBody,
124
124
  handle: async (token, body) => {
125
- const { getGateway } = await import("./boot-DmpKtb0A.mjs").then((n) => n.r).then((n) => n.t);
125
+ const { getGateway } = await import("./boot-jtVCf_pN.mjs").then((n) => n.r).then((n) => n.t);
126
126
  const r = await (await getGateway()).pollV4Wait(token, gatewayPollRequest(body));
127
127
  return Response.json(r.body, { status: r.status });
128
128
  }
@@ -139,7 +139,7 @@ function parseCliBody(value) {
139
139
  * POST /api/machine/cli — the ONE unified CLI dispatch (Bearer credential + `{argv}`).
140
140
  * `CliGateway.cli()` branches by credential type: a `dk_` device token takes owner
141
141
  * commands; an `rk_` run credential is rejected. The 4 MB body cap fits
142
- * `file set` carrying a 2 MB artifact as base64 (4/3 expansion) plus argv framing.
142
+ * `file set` carrying a 256 KB artifact as base64 (4/3 expansion) plus argv framing.
143
143
  */
144
144
  var CLI_BODY_CAP = 4 * 1024 * 1024;
145
145
  var Route$2 = createFileRoute("/api/machine/cli")({ server: { handlers: { POST: async ({ request }) => {
@@ -150,7 +150,7 @@ var Route$2 = createFileRoute("/api/machine/cli")({ server: { handlers: { POST:
150
150
  invalidBodyError: "body must be exactly {argv:string[]}",
151
151
  parseBody: parseCliBody,
152
152
  handle: async (token, body) => {
153
- const { getCliGateway } = await import("./boot-DmpKtb0A.mjs").then((n) => n.r).then((n) => n.t);
153
+ const { getCliGateway } = await import("./boot-jtVCf_pN.mjs").then((n) => n.r).then((n) => n.t);
154
154
  const r = await (await getCliGateway()).cli(token, body.argv);
155
155
  return Response.json(r.body, { status: r.status });
156
156
  }
@@ -192,7 +192,7 @@ var Route$1 = createFileRoute("/api/machine/run-files/$name")({ server: { handle
192
192
  if (!token) return Response.json({ error: "missing run token" }, { status: 401 });
193
193
  const name = fileName(request);
194
194
  if (name === null) return Response.json({ error: "bad file name" }, { status: 400 });
195
- const { getGateway } = await import("./boot-DmpKtb0A.mjs").then((n) => n.r).then((n) => n.t);
195
+ const { getGateway } = await import("./boot-jtVCf_pN.mjs").then((n) => n.r).then((n) => n.t);
196
196
  const r = await (await getGateway()).readRunFile(token, name);
197
197
  if (r.status !== 200 || !r.bytes || !r.hash) return Response.json(r.body, { status: r.status });
198
198
  return new Response(new Uint8Array(r.bytes), {
@@ -210,32 +210,23 @@ var Route$1 = createFileRoute("/api/machine/run-files/$name")({ server: { handle
210
210
  if (!token) return Response.json({ error: "missing run token" }, { status: 401 });
211
211
  const name = fileName(request);
212
212
  if (name === null) return Response.json({ error: "bad file name" }, { status: 400 });
213
- const { LOOP_FILE_BYTE_CAP } = await import("./boot-DmpKtb0A.mjs").then((n) => n.M).then((n) => n.Vt);
213
+ const { LOOP_FILE_BYTE_CAP } = await import("./boot-jtVCf_pN.mjs").then((n) => n.N).then((n) => n.Ht);
214
214
  const body = await readByteBody(request, LOOP_FILE_BYTE_CAP);
215
215
  if (body.kind === "too-large") return Response.json({ error: "file exceeds the loop-file size limit" }, { status: 413 });
216
216
  if (body.kind === "invalid") return Response.json({ error: "could not read file body" }, { status: 400 });
217
217
  const bytes = Buffer.from(body.bytes.buffer, body.bytes.byteOffset, body.bytes.byteLength);
218
- const { getGateway } = await import("./boot-DmpKtb0A.mjs").then((n) => n.r).then((n) => n.t);
218
+ const { getGateway } = await import("./boot-jtVCf_pN.mjs").then((n) => n.r).then((n) => n.t);
219
219
  const r = await (await getGateway()).storeRunFile(token, name, bytes, request.headers.get("x-pievo-sha256"));
220
220
  return Response.json(r.body, { status: r.status });
221
221
  }
222
222
  } } });
223
223
  /**
224
- * GET /api/artifact/:loopId/:path - stream one of a loop's file blobs.
225
- * Two dispositions:
226
- * - default: `attachment` (the download affordance for binary/oversize files;
227
- * text is shown inline via getArtifact).
228
- * - `?view=inline`: for a KNOWN image type only (the `imageMime` allowlist),
229
- * served `inline` with the real image content-type so an <img> can render it.
230
- * Hardened against a direct navigation turning the byte stream into a scripted
231
- * document on the app origin: `X-Content-Type-Options: nosniff` pins the type
232
- * and `Content-Security-Policy: sandbox` loads it in an opaque origin with no
233
- * scripts - so an inline SVG can't run script against the session even if the
234
- * URL is opened directly. Non-image inline requests fall back to attachment.
235
- *
236
- * Served to dashboard callers like the server functions. The path is matched
237
- * exactly against a loop's stored file rows, and blob keys are content hashes,
238
- * never the user path. Reads stream from the local BlobStore; the route never writes.
224
+ * GET /api/artifact/:loopId/:path streams one loop file. Default disposition is
225
+ * `attachment`. `?view=inline` is honored only for the `imageMime` allowlist, and
226
+ * is hardened against a direct navigation scripting the app origin:
227
+ * `nosniff` pins the type and `Content-Security-Policy: sandbox` gives the
228
+ * document an opaque origin, so an SVG opened directly cannot run script. The path
229
+ * must match a stored file row exactly; blob keys are content hashes.
239
230
  */
240
231
  var PREFIX = "/api/artifact/";
241
232
  var Route = createFileRoute("/api/artifact/$loopId/$")({ server: { handlers: { GET: async ({ request }) => {
@@ -249,8 +240,8 @@ var Route = createFileRoute("/api/artifact/$loopId/$")({ server: { handlers: { G
249
240
  const segments = after.slice(slash + 1).split("/").map((s) => safeDecode(s));
250
241
  if (segments.some((s) => s === null)) return Response.json({ error: "bad path" }, { status: 400 });
251
242
  const relPath = segments.join("/");
252
- if (!await (await import("./boot-DmpKtb0A.mjs").then((n) => n.M).then((n) => n.Dt)).getLoop(loopId)) return Response.json({ error: "not found" }, { status: 404 });
253
- const { readLoopArtifactBytes } = await import("./artifactFiles-B0NPRDOR.mjs");
243
+ if (!await (await import("./boot-jtVCf_pN.mjs").then((n) => n.N).then((n) => n.Dt)).getLoop(loopId)) return Response.json({ error: "not found" }, { status: 404 });
244
+ const { readLoopArtifactBytes } = await import("./artifactFiles-DLfSRhrV.mjs");
254
245
  const r = await readLoopArtifactBytes(loopId, relPath);
255
246
  if (r.status !== 200 || !r.bytes) return Response.json({ error: "not found" }, { status: r.status });
256
247
  const filename = (r.filename || "file").replace(/["\\\r\n]/g, "_");
@@ -1,6 +1,6 @@
1
1
  import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
- import { n as Route, t as DashboardView } from "./routes-CL2Kef-Q.mjs";
3
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-CCallyCT.js
2
+ import { n as Route, t as DashboardView } from "./routes-fHRtSpkH.mjs";
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-BBmuVs6n.js
4
4
  var import_jsx_runtime = require_jsx_runtime();
5
5
  /** First-load failure screen — a calm retry instead of the router's default
6
6
  * error dump. Only the initial loader can land here; the in-page poll is
@@ -1,7 +1,7 @@
1
1
  import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
2
  import { f as useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
3
- import { i as LoadErrorCard } from "./actionUi-DE-Pb2tv.mjs";
4
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-ngpX7h8D.js
3
+ import { a as LoadErrorCard } from "./actionUi-CPYgZIr7.mjs";
4
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-JxTh5mbj.js
5
5
  var import_jsx_runtime = require_jsx_runtime();
6
6
  /** First-load failure screen — a calm retry instead of the router's default
7
7
  * error dump. Only the initial loader can land here; the in-page poll is