@kky42/pievo-server 3.0.0 → 4.2.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 (45) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/MarkdownView-C9-AzIkl.js +66 -0
  3. package/.output/public/assets/{actionUi-BZrLpgHT.js → actionUi-D47jwMfh.js} +1 -1
  4. package/.output/public/assets/app-C5xfpeJe.css +2 -0
  5. package/.output/public/assets/index-BgNjFXWE.js +10 -0
  6. package/.output/public/assets/loops._loopId-Bo63FXPQ.js +2 -0
  7. package/.output/public/assets/loops._loopId_.runs._runId-DW2HPjtj.js +3 -0
  8. package/.output/public/assets/{preload-helper-hRTYB_ju.js → preload-helper-CbYqBpY9.js} +2 -2
  9. package/.output/public/assets/routes-C88oSApK.js +1 -0
  10. package/.output/public/assets/routes-CTCh8xYm.js +1 -0
  11. package/.output/server/_ssr/{MachinesModal-CmVMec6B.mjs → MachinesModal-DwHy2cis.mjs} +44 -25
  12. package/.output/server/_ssr/MarkdownView-BOe75tli.mjs +67 -0
  13. package/.output/server/_ssr/{actionUi-DE-Pb2tv.mjs → actionUi-CszQXDSN.mjs} +20 -2
  14. package/.output/server/_ssr/artifactFiles-DJiK99sF.mjs +67 -0
  15. package/.output/server/_ssr/{artifactKind-DqpmtxDU.mjs → artifactKind-DYMU0q3N.mjs} +2 -7
  16. package/.output/server/_ssr/{boot-CYRi-wez.mjs → boot-CecCDRoH.mjs} +950 -968
  17. package/.output/server/_ssr/{lifecycleUi-BsbUsi5K.mjs → lifecycleUi-Bd7_sthp.mjs} +12 -19
  18. package/.output/server/_ssr/{loopApi-Bez1rHV0.mjs → loopApi-uEiBQ5YX.mjs} +17 -23
  19. package/.output/server/_ssr/{loopProjection-esCjyZJg.mjs → loopProjection-Bo54jjGd.mjs} +5 -3
  20. package/.output/server/_ssr/{loops._loopId-KUezNY69.mjs → loops._loopId-c1oRP7cr.mjs} +37 -117
  21. package/.output/server/_ssr/{loops._loopId_.runs._runId-DOkUEesG.mjs → loops._loopId_.runs._runId-Dl0SwOlC.mjs} +43 -23
  22. package/.output/server/_ssr/loops._loopId_.runs._runId-czMVvNsk.mjs +11 -0
  23. package/.output/server/_ssr/{machineFns-CniGcY8s.mjs → machineFns-aw9xe5oz.mjs} +6 -20
  24. package/.output/server/_ssr/{router-BFIH2UAE.mjs → router-3OQ849sN.mjs} +96 -112
  25. package/.output/server/_ssr/{routes-BSVBANI3.mjs → routes-CL4y6AEv.mjs} +2 -2
  26. package/.output/server/_ssr/{routes-DnE7XDe_.mjs → routes-CT5IdoiP.mjs} +6 -6
  27. package/.output/server/_ssr/{routes-ngpX7h8D.mjs → routes-nU2PDK3W.mjs} +2 -2
  28. package/.output/server/_ssr/ssr.mjs +22 -22
  29. package/.output/server/{_tanstack-start-manifest_v-DnwAupbZ.mjs → _tanstack-start-manifest_v-_nMS2ozp.mjs} +9 -10
  30. package/.output/server/drizzle/0001_loop_files.sql +9 -0
  31. package/.output/server/drizzle/0002_run_attention.sql +1 -0
  32. package/.output/server/drizzle/meta/_journal.json +15 -1
  33. package/.output/server/index.mjs +52 -45
  34. package/drizzle/0001_loop_files.sql +9 -0
  35. package/drizzle/0002_run_attention.sql +1 -0
  36. package/drizzle/meta/_journal.json +15 -1
  37. package/package.json +1 -1
  38. package/.output/public/assets/app-DXLuQNb9.css +0 -2
  39. package/.output/public/assets/index-Di_wpAtL.js +0 -10
  40. package/.output/public/assets/loops._loopId-X3ClNPMY.js +0 -67
  41. package/.output/public/assets/loops._loopId_.runs._runId-ByU2BSyF.js +0 -3
  42. package/.output/public/assets/routes-BIOJZsig.js +0 -1
  43. package/.output/public/assets/routes-K8D5uBiV.js +0 -1
  44. package/.output/server/_ssr/artifactFiles-Di1xDKvX.mjs +0 -57
  45. package/.output/server/_ssr/loops._loopId_.runs._runId-BFP9oMrp.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-BsbUsi5K.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 };
@@ -16,27 +16,20 @@ var createSsrRpc = (functionId) => {
16
16
  * Server functions backing the dashboard. They run server-side (createServerFn
17
17
  * → RPC) and call the IN-PROCESS scheduler + store directly — the scheduler
18
18
  * lives in this same TanStack process (booted on first call via ensureServer).
19
- * The machine-facing poll, CLI, report, and artifact-sync endpoints are sibling
20
- * server routes in this same process, so one process owns the single scheduler.
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.
19
+ * The machine-facing poll, CLI, report, and run-file endpoints are sibling server
20
+ * routes in this same process, so one process owns the single scheduler.
29
21
  */
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",
@@ -94,9 +87,9 @@ function lifecyclePresentation(loop) {
94
87
  };
95
88
  }
96
89
  function daemonStopSupport(protocol) {
97
- return protocol === 4 ? {
90
+ return protocol === 5 ? {
98
91
  supported: true,
99
- label: `Daemon protocol 4 · Stop supported`
92
+ label: `Daemon protocol 5 · Stop supported`
100
93
  } : {
101
94
  supported: false,
102
95
  label: `Daemon protocol ${protocol ?? "unknown"} · upgrade required`
@@ -1,14 +1,14 @@
1
- import { A as listMachines, C as getMachine, E as hasRunningRun, G as startLoop$1, H as requestRunCancel, I as machinePresence, J as updateLoop, K as stopLoop$1, M as listRunsBefore, R as pauseLoop$1, S as getLoop, V as requestDeleteLoop, c as ensureServer, h as validateLoopEdit, k as listLoops$1, q as tryDeleteLoop, w as getRun, y as forceDeleteLoop } from "./boot-CYRi-wez.mjs";
2
- import { a as toRunSummaries, i as toLoopSummaries, r as toLoopDetail, t as sortLoopSummariesByRecentRun } from "./loopProjection-esCjyZJg.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-CecCDRoH.mjs";
2
+ import { a as toRunSummaries, i as toLoopSummaries, r as toLoopDetail, t as sortLoopSummariesByRecentRun } from "./loopProjection-Bo54jjGd.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-Bez1rHV0.js
5
+ //#region node_modules/.nitro/vite/services/ssr/assets/loopApi-uEiBQ5YX.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
9
9
  * lives in this same TanStack process (booted on first call via ensureServer).
10
- * The machine-facing poll, CLI, report, and artifact-sync endpoints are sibling
11
- * server routes in this same process, so one process owns the single scheduler.
10
+ * The machine-facing poll, CLI, report, and run-file endpoints are sibling server
11
+ * routes in this same process, so one process owns the single scheduler.
12
12
  */
13
13
  function backend() {
14
14
  return ensureServer();
@@ -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-Di1xDKvX.mjs");
78
+ const { listLoopArtifacts } = await import("./artifactFiles-DJiK99sF.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-Di1xDKvX.mjs");
89
+ const { readLoopArtifact } = await import("./artifactFiles-DJiK99sF.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-CYRi-wez.mjs").then((n) => n.i).then((n) => n.i);
108
+ const { computeRunDiff } = await import("./boot-CecCDRoH.mjs").then((n) => n.r).then((n) => n.i);
115
109
  return computeRunDiff(data.runId);
116
110
  });
117
111
  var patchLoop_createServerFn_handler = createServerRpc({
@@ -171,7 +165,7 @@ var stopLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(
171
165
  const found = await findLoop(id);
172
166
  if (!found) return { error: "not found" };
173
167
  if (await hasRunningRun(id)) {
174
- if ((await getMachine(found.loop.machineId))?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
168
+ if ((await getMachine(found.loop.machineId))?.daemonProtocol !== 5) return { error: "Daemon upgrade required to stop a running process" };
175
169
  }
176
170
  const stopped = await stopLoop$1(id);
177
171
  if (!stopped) return { error: "not found" };
@@ -192,12 +186,12 @@ var deleteLoop = createServerFn({ method: "POST" }).validator((id) => id).handle
192
186
  if (!found) return { error: "not found" };
193
187
  const machine = await getMachine(found.loop.machineId);
194
188
  const unreachable = machinePresence(machine?.online, machine?.lastSeen) !== "online";
195
- if (!unreachable && await hasRunningRun(id) && machine?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
189
+ if (!unreachable && await hasRunningRun(id) && machine?.daemonProtocol !== 5) return { error: "Daemon upgrade required to stop a running process" };
196
190
  scheduler.removeLoop(id);
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-CYRi-wez.mjs").then((n) => n.N).then((n) => n.Jt);
194
+ const { logger } = await import("./boot-CecCDRoH.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,
@@ -242,7 +236,7 @@ var stopRun = createServerFn({ method: "POST" }).validator((id) => id).handler(s
242
236
  const run = await getRun(id);
243
237
  const found = run ? await findLoop(run.loopId) : void 0;
244
238
  if (!run || !found) return { error: "run not found" };
245
- if (run.phase === "running" && (await getMachine(found.loop.machineId))?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
239
+ if (run.phase === "running" && (await getMachine(found.loop.machineId))?.daemonProtocol !== 5) return { error: "Daemon upgrade required to stop a running process" };
246
240
  const result = await requestRunCancel(run.loopId, id);
247
241
  return result ? {
248
242
  ok: true,
@@ -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-CYRi-wez.mjs").then((n) => n.N).then((n) => n.g);
253
+ const { mintDeviceToken, rememberConnectKey } = await import("./boot-CecCDRoH.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 { B as reconciliationStatesForRuns, C as getMachine, E as hasRunningRun, I as machinePresence, T as hasPendingRun, _ as countRuns, a as canonicalLoopEnvelope, j as listRuns, n as MIN_DAEMON_VERSION, o as canonicalLoopSummary, s as daemonNeedsUpdate, z as recentLoopUsage } from "./boot-CYRi-wez.mjs";
2
- //#region node_modules/.nitro/vite/services/ssr/assets/loopProjection-esCjyZJg.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-CecCDRoH.mjs";
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/loopProjection-Bo54jjGd.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,
@@ -39,7 +40,8 @@ function toRunSummary(r, reconciliation) {
39
40
  } : null,
40
41
  error: r.error ?? null,
41
42
  sessionId: r.sessionId ?? null,
42
- reportIncident: r.reportIncident ?? null
43
+ reportIncident: r.reportIncident ?? null,
44
+ fileNotices: r.fileNotices ?? null
43
45
  };
44
46
  }
45
47
  async function toRunSummaries(loopId, rows) {
@@ -1,18 +1,17 @@
1
1
  import { o as __toESM } from "../_runtime.mjs";
2
- import { X as validateLoopTags, g as CODING_AGENTS } from "./boot-CYRi-wez.mjs";
2
+ import { Y as validateLoopTags, h as CODING_AGENTS } from "./boot-CecCDRoH.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-BsbUsi5K.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-CmVMec6B.mjs";
8
- import { n as artifactKind, t as Route } from "./artifactKind-DqpmtxDU.mjs";
9
- import { t as purify } from "../_libs/dompurify.mjs";
10
- import { t as g } from "../_libs/marked.mjs";
11
- //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-KUezNY69.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 sectionHeadCls, C as labelCls, E as rel, M as tsShort, O as runPulseStyle, P as useFlash, S as inputCls, _ as dur, a as LoadErrorCard, b as focusRing, d as btnPrimary, f as btnQuiet, h as dotLabel, i as FlashLine, j as selectCls, l as btn, m as dotColor, n as ConfirmBar, o as Loading, r as ErrorBanner, u as btnDanger, v as fmt, x as humanBytes } from "./actionUi-CszQXDSN.mjs";
7
+ import { a as Timeline, l as mergeRuns, n as MachinesModal, t as LoopOverview } from "./MachinesModal-DwHy2cis.mjs";
8
+ import { n as artifactKind, t as Route } from "./artifactKind-DYMU0q3N.mjs";
9
+ import { t as MarkdownView } from "./MarkdownView-BOe75tli.mjs";
10
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-c1oRP7cr.js
12
11
  var import_react = /* @__PURE__ */ __toESM(require_react());
13
12
  var import_jsx_runtime = require_jsx_runtime();
14
- /** Download URL for one artifact. Each path segment is encoded independently so
15
- * nested artifact paths continue to match the catch-all route. */
13
+ /** Download URL for one loop file. Each path segment is encoded independently so
14
+ * the name continues to match the catch-all route. */
16
15
  function downloadHref(loopId, path) {
17
16
  const encodedPath = path.split("/").map(encodeURIComponent).join("/");
18
17
  return `/api/artifact/${encodeURIComponent(loopId)}/${encodedPath}`;
@@ -21,83 +20,9 @@ function inlineHref(loopId, path) {
21
20
  return `${downloadHref(loopId, path)}?view=inline`;
22
21
  }
23
22
  /**
24
- * Shared markdown → sanitized HTML pipeline: marked (GFM) → DOMPurify (an
25
- * allowlisted prose subset, NO script/handlers). ONE sanitizer config for every
26
- * artifact markdown surface so the allowlist cannot drift between viewers.
27
- * Render the output under
28
- * the `.artifact-markdown` styles.
29
- */
30
- var MD_SANITIZE = {
31
- ALLOWED_TAGS: [
32
- "h1",
33
- "h2",
34
- "h3",
35
- "h4",
36
- "h5",
37
- "h6",
38
- "p",
39
- "br",
40
- "hr",
41
- "strong",
42
- "b",
43
- "em",
44
- "i",
45
- "del",
46
- "s",
47
- "code",
48
- "pre",
49
- "blockquote",
50
- "ul",
51
- "ol",
52
- "li",
53
- "a",
54
- "span",
55
- "table",
56
- "thead",
57
- "tbody",
58
- "tr",
59
- "th",
60
- "td",
61
- "img"
62
- ],
63
- ALLOWED_ATTR: [
64
- "href",
65
- "title",
66
- "target",
67
- "rel",
68
- "src",
69
- "alt"
70
- ]
71
- };
72
- function renderMarkdown(content) {
73
- return purify.sanitize(g.parse(content, {
74
- async: false,
75
- gfm: true
76
- }), MD_SANITIZE);
77
- }
78
- function MarkdownView({ content }) {
79
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
80
- className: "artifact-markdown px-5 py-4",
81
- dangerouslySetInnerHTML: { __html: (0, import_react.useMemo)(() => renderMarkdown(content), [content]) }
82
- });
83
- }
84
- /**
85
- * Shared pieces of the artifact content viewer. The Files panel uses this one
86
- * dispatch so binary/oversize copy and type-appropriate rendering cannot drift.
87
- *
88
- * Display types (see `lib/artifactKind`):
89
- * - HTML → a STRICT sandboxed iframe (`sandbox="allow-scripts"`, deliberately
90
- * NO `allow-same-origin`): user-machine-synced markup renders richly
91
- * but in an opaque origin, so its scripts can't read the app's
92
- * cookies/session or reach `parent` - stored-XSS is contained by
93
- * design. A Preview/Source toggle exposes the raw markup too.
94
- * - image → an <img> pointed at the hardened inline route (`inlineHref`); the
95
- * bytes are NEVER inlined into the app DOM (SVG is scriptable, so it
96
- * rides as an image, which does not execute embedded scripts).
97
- * - markdown → the shared markdown pipeline (Preview) with a raw Source toggle.
98
- * - text → monospace source.
99
- * Everything keeps a Download fallback; an oversize (metadata-only) artifact has
100
- * no synced bytes to render, 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.
101
26
  */
102
27
  function ViewerHead({ path, meta, action }) {
103
28
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -276,7 +201,6 @@ function ArtifactBody({ loopId, file }) {
276
201
  kind
277
202
  }, file.path);
278
203
  }
279
- var basename = (path) => path.split("/").pop() || path;
280
204
  function LoopFilesPanel({ loopId, configuredPaths, running }) {
281
205
  const [artifacts, setArtifacts] = (0, import_react.useState)(null);
282
206
  const [selected, setSelected] = (0, import_react.useState)(null);
@@ -309,14 +233,19 @@ function LoopFilesPanel({ loopId, configuredPaths, running }) {
309
233
  className: "mb-2.5 flex items-end justify-between gap-3 border-b border-hairline pb-1.5",
310
234
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
311
235
  className: "text-label font-semibold text-secondary",
312
- children: ["Artifacts", artifacts ? ` (${files.length})` : ""]
236
+ children: ["Files", artifacts ? ` (${files.length})` : ""]
313
237
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
314
238
  className: "text-caption font-medium text-disabled",
315
- children: ["Configured exact paths: ", configuredPaths.length]
239
+ children: [
240
+ "MEMORY.md + ",
241
+ configuredPaths.length,
242
+ " artifact",
243
+ configuredPaths.length === 1 ? "" : "s"
244
+ ]
316
245
  })]
317
246
  }), files.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
318
247
  className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled shadow-card",
319
- children: artifacts == null ? "Loading…" : configuredPaths.length ? "No configured artifact has been collected yet." : "No artifact paths configured."
248
+ children: artifacts == null ? "Loading…" : "No files are available for this loop."
320
249
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
321
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]",
322
251
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
@@ -329,17 +258,10 @@ function LoopFilesPanel({ loopId, configuredPaths, running }) {
329
258
  type: "button",
330
259
  onClick: () => setSelected(file.path),
331
260
  className: `flex w-full items-center border-l-2 px-3 py-2 text-left transition-colors ${activeRow ? "border-display bg-raised" : "border-transparent hover:bg-raised/60"}`,
332
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
333
- className: "min-w-0 flex-1",
334
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
335
- className: `block truncate font-mono text-label ${activeRow ? "text-display" : "text-primary"}`,
336
- title: file.path,
337
- children: basename(file.path)
338
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
339
- className: "block truncate font-mono text-micro text-disabled",
340
- title: file.path,
341
- children: file.path
342
- })]
261
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
262
+ className: `block min-w-0 flex-1 truncate font-mono text-label ${activeRow ? "text-display" : "text-primary"}`,
263
+ title: file.path,
264
+ children: file.path
343
265
  })
344
266
  }) }, file.path);
345
267
  })
@@ -348,7 +270,7 @@ function LoopFilesPanel({ loopId, configuredPaths, running }) {
348
270
  className: "min-w-0 overflow-y-auto",
349
271
  children: active && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, {
350
272
  path: active.path,
351
- meta: [active.size != null ? humanBytes(active.size) : "", `collected ${fmt(active.updatedAt)}`].filter(Boolean).join(" · ")
273
+ meta: [active.size != null ? humanBytes(active.size) : "", `updated ${fmt(active.updatedAt)}`].filter(Boolean).join(" · ")
352
274
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactBody, {
353
275
  loopId,
354
276
  file: active
@@ -528,7 +450,7 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
528
450
  noChange: form.noChange,
529
451
  block: form.block
530
452
  },
531
- artifacts: form.artifacts.split(/\r?\n/).filter((path) => path.length > 0)
453
+ artifacts: form.artifacts.split(/\r?\n/).filter((name) => name.length > 0)
532
454
  };
533
455
  } }));
534
456
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -756,10 +678,10 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
756
678
  }),
757
679
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
758
680
  title: "Artifacts",
759
- hint: "Optional exact paths relative to the working directory. One path per line; globs are not supported."
681
+ 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."
760
682
  }),
761
683
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
762
- label: "Artifact paths",
684
+ label: "Artifact files",
763
685
  value: form.artifacts,
764
686
  onChange: (value) => set("artifacts", value),
765
687
  rows: 6,
@@ -1033,7 +955,7 @@ function LoopDetailView({ id }) {
1033
955
  className: "mt-3",
1034
956
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfirmBar, {
1035
957
  prompt: "Stop this loop and delete its server data?",
1036
- note: "This stops the loop and deletes server history and collected artifacts. Files in the working directory are not deleted. If the machine is unreachable, its local process may continue running.",
958
+ note: "This stops the loop and deletes server history, the loop memory, and artifacts. Files in the working directory are not deleted. If the machine is unreachable, its local process may continue running.",
1037
959
  cta: "Delete",
1038
960
  danger: true,
1039
961
  busy,
@@ -1144,13 +1066,13 @@ function LoopDetailView({ id }) {
1144
1066
  })
1145
1067
  }),
1146
1068
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1147
- label: "Artifact paths",
1069
+ label: "Artifacts",
1148
1070
  children: loop.artifacts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
1149
1071
  className: "space-y-1",
1150
- children: loop.artifacts.map((path) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
1072
+ children: loop.artifacts.map((name) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
1151
1073
  className: "break-all font-mono",
1152
- children: path
1153
- }) }, path))
1074
+ children: name
1075
+ }) }, name))
1154
1076
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1155
1077
  className: "text-disabled",
1156
1078
  children: "None"
@@ -1205,7 +1127,7 @@ function EditHead({ name, onBack }) {
1205
1127
  }),
1206
1128
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1207
1129
  className: "mt-1.5 text-meta text-secondary",
1208
- children: "Edit tags, schedule, execution settings, prompt, status definitions, and exact artifact paths."
1130
+ children: "Edit tags, schedule, execution settings, prompt, status definitions, and artifact files."
1209
1131
  })
1210
1132
  ] });
1211
1133
  }
@@ -1275,6 +1197,9 @@ function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1275
1197
  className: "size-1.5 rounded-full",
1276
1198
  style: runPulseStyle
1277
1199
  }), "Running"]
1200
+ }) : run.attention ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1201
+ className: "font-medium text-running",
1202
+ children: ["! ", run.attention]
1278
1203
  }) : run.error || run.message || dotLabel(run)
1279
1204
  })]
1280
1205
  })]
@@ -1283,11 +1208,6 @@ function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1283
1208
  })]
1284
1209
  });
1285
1210
  }
1286
- /**
1287
- * Loop detail page — `/loops/$loopId`: a loop header and action toolbar, the
1288
- * exact artifact Files panel, and the runs timeline. The view owns its own
1289
- * data + self-poll. Run rows link on to `/loops/$loopId/runs/$runId`.
1290
- */
1291
1211
  function LoopDetailPage() {
1292
1212
  const { loopId } = Route.useParams();
1293
1213
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopDetailView, { id: loopId });
@@ -1,10 +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-BsbUsi5K.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 Route } from "./loops._loopId_.runs._runId-BFP9oMrp.mjs";
7
- //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId_.runs._runId-DOkUEesG.js
4
+ import { b as stopRun, l as getLoopDetail, p as loadOlderRuns, u as getRunDiff } from "./lifecycleUi-Bd7_sthp.mjs";
5
+ import { A as sectionHeadCls, O as runPulseStyle, _ as dur, a as LoadErrorCard, c as StatusPill, l as btn, o as Loading, s as Pill, u as btnDanger, v as fmt, x as humanBytes, y as fnum } from "./actionUi-CszQXDSN.mjs";
6
+ import { t as MarkdownView } from "./MarkdownView-BOe75tli.mjs";
7
+ import { t as Route } from "./loops._loopId_.runs._runId-czMVvNsk.mjs";
8
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId_.runs._runId-Dl0SwOlC.js
8
9
  var import_react = /* @__PURE__ */ __toESM(require_react());
9
10
  var import_jsx_runtime = require_jsx_runtime();
10
11
  /**
@@ -422,13 +423,20 @@ function RunDetailView({ loopId, runId }) {
422
423
  className: "min-w-0",
423
424
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
424
425
  className: "flex flex-wrap items-center gap-3",
425
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", {
426
- className: "text-[26px] font-medium leading-tight tracking-tight text-display",
427
- children: ["Run · ", loopName]
428
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusPill, {
429
- run,
430
- colorText: true
431
- })]
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
+ ]
432
440
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
433
441
  className: "mt-2.5 flex flex-wrap items-center gap-x-2.5 gap-y-1.5 text-meta text-secondary",
434
442
  children: [
@@ -462,8 +470,8 @@ function RunDetailView({ loopId, runId }) {
462
470
  type: "button",
463
471
  onClick: onStop,
464
472
  className: btnDanger,
465
- disabled: run.phase === "running" && detail.machine.daemonProtocol !== 4,
466
- title: run.phase === "running" && detail.machine.daemonProtocol !== 4 ? "Daemon upgrade required to stop a running process. Run `npm install -g @kky42/pievo@latest`, then `pievo daemon restart`." : void 0,
473
+ disabled: run.phase === "running" && detail.machine.daemonProtocol !== 5,
474
+ title: run.phase === "running" && detail.machine.daemonProtocol !== 5 ? "Daemon upgrade required to stop a running process. Run `npm install -g @kky42/pievo@latest`, then `pievo daemon restart`." : void 0,
467
475
  children: "Stop run"
468
476
  })]
469
477
  })]
@@ -485,6 +493,13 @@ function RunDetailView({ loopId, runId }) {
485
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."
486
494
  })
487
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-running",
500
+ children: run.attention
501
+ })
502
+ }),
488
503
  run.reportIncident && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
489
504
  label: "Terminal report rejected",
490
505
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -527,15 +542,25 @@ function RunDetailView({ loopId, runId }) {
527
542
  ]
528
543
  })
529
544
  }),
545
+ run.fileNotices && run.fileNotices.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
546
+ label: "Loop files not kept",
547
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
548
+ className: "list-disc space-y-1.5 pl-5 text-body text-primary",
549
+ children: run.fileNotices.map((notice) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", {
550
+ className: "break-words",
551
+ children: notice
552
+ }, notice))
553
+ })
554
+ }),
530
555
  run.message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
531
556
  label: "Report",
532
557
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
533
- className: "whitespace-pre-wrap break-words rounded-control border border-hairline bg-raised px-4 py-3.5 text-body leading-relaxed text-primary",
534
- children: run.message
558
+ className: "break-words rounded-control border border-hairline bg-raised text-body leading-relaxed text-primary",
559
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownView, { content: run.message })
535
560
  })
536
561
  }),
537
562
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Changes, { run }),
538
- run.finalText && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
563
+ run.finalText && !run.message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
539
564
  label: "Final output",
540
565
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
541
566
  className: "whitespace-pre-wrap break-words text-body text-primary",
@@ -587,13 +612,8 @@ function RunDetailView({ loopId, runId }) {
587
612
  })]
588
613
  })] });
589
614
  }
590
- /**
591
- * Run detail PAGE — `/loops/$loopId/runs/$runId`. A standalone page (the trailing
592
- * `_` on the `$loopId` segment opts it out of the loop page's component so the run
593
- * gets its own full surface, deep-linkable + browser-back friendly) rather than a
594
- * modal or an inline panel. It resolves the run from the loop's detail payload
595
- * (reusing getLoopDetail) and the existing artifact diff.
596
- */
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. */
597
617
  function RunDetailPage() {
598
618
  const { loopId, runId } = Route.useParams();
599
619
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
@@ -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-czMVvNsk.js
3
+ var $$splitComponentImporter = () => import("./loops._loopId_.runs._runId-Dl0SwOlC.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,30 +1,16 @@
1
- import { A as listMachines$1, C as getMachine, F as machineIdFromToken, L as mintDeviceToken, P as loopsForMachine, W as sha256, Y as updateMachine, b as forceDeleteMachine, c as ensureServer, n as MIN_DAEMON_VERSION, s as daemonNeedsUpdate, v as createMachine$1 } from "./boot-CYRi-wez.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-CecCDRoH.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-CniGcY8s.js
4
+ //#region node_modules/.nitro/vite/services/ssr/assets/machineFns-aw9xe5oz.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);