@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
@@ -11,14 +11,14 @@ import { fileURLToPath } from "node:url";
11
11
  import path from "node:path";
12
12
  import os from "node:os";
13
13
  import fs$1 from "node:fs/promises";
14
- //#region node_modules/.nitro/vite/services/ssr/assets/loopTags-CnVLi70h.js
15
- var loopTags_CnVLi70h_exports = /* @__PURE__ */ __exportAll$1({
14
+ //#region node_modules/.nitro/vite/services/ssr/assets/loopTags-CI_aMcqh.js
15
+ var loopTags_CI_aMcqh_exports = /* @__PURE__ */ __exportAll$1({
16
16
  $: () => loopIdsWithSnapshots,
17
- $t: () => BLOB_GC_GRACE_MS,
17
+ $t: () => logger_exports,
18
18
  A: () => enqueueRun,
19
19
  At: () => updateMachine,
20
20
  B: () => getRun,
21
- Bt: () => loopFileNames,
21
+ Bt: () => loopFileIssue,
22
22
  C: () => buildLoopManifest,
23
23
  Ct: () => setMachineOnline,
24
24
  D: () => createLoop,
@@ -26,47 +26,47 @@ var loopTags_CnVLi70h_exports = /* @__PURE__ */ __exportAll$1({
26
26
  E: () => countRuns,
27
27
  Et: () => stopLoop,
28
28
  F: () => getArtifactFile,
29
- Ft: () => MEMORY_CHAR_CAP,
29
+ Ft: () => LoopFiles,
30
30
  G: () => initializeCronCadence,
31
- Gt: () => closeClient,
31
+ Gt: () => createBlobStore,
32
32
  H: () => getTerminalReportIncidents,
33
- Ht: () => sha256Buf,
33
+ Ht: () => loopFiles_exports,
34
34
  I: () => getExactTerminalReportIncident,
35
- It: () => MEMORY_FILE,
35
+ It: () => MEMORY_CHAR_CAP,
36
36
  J: () => listLoops,
37
- Jt: () => runs,
37
+ Jt: () => runMigrations,
38
38
  K: () => lastRun,
39
- Kt: () => db,
39
+ Kt: () => closeClient,
40
40
  L: () => getLoop,
41
- Lt: () => fileLimitLabel,
41
+ Lt: () => MEMORY_FILE,
42
42
  M: () => finalizeRunningRun,
43
- Mt: () => EMPTY_SHA256,
43
+ Mt: () => ARTIFACT_EXTENSIONS,
44
44
  N: () => forceDeleteLoop,
45
- Nt: () => LOOP_FILE_BYTE_CAP,
45
+ Nt: () => EMPTY_SHA256,
46
46
  O: () => createMachine,
47
47
  Ot: () => tryDeleteLoop,
48
48
  P: () => forceDeleteMachine,
49
- Pt: () => LoopFiles,
49
+ Pt: () => LOOP_FILE_BYTE_CAP,
50
50
  Q: () => liveBlobRefs,
51
- Qt: () => logger_exports,
51
+ Qt: () => logger,
52
52
  R: () => getMachine,
53
- Rt: () => isValidHash,
53
+ Rt: () => fileLimitLabel,
54
54
  S: () => blobSizes,
55
55
  St: () => runningRunsForMachine,
56
56
  T: () => countRunSnapshots,
57
57
  Tt: () => startLoop,
58
58
  U: () => hasPendingRun,
59
- Ut: () => validArtifactName,
59
+ Ut: () => sha256Buf,
60
60
  V: () => getRunSnapshot,
61
- Vt: () => loopFiles_exports,
61
+ Vt: () => loopFileNames,
62
62
  W: () => hasRunningRun,
63
- Wt: () => createBlobStore,
63
+ Wt: () => validArtifactName,
64
64
  X: () => listRuns,
65
- Xt: () => coerceCodingAgent,
65
+ Xt: () => CODING_AGENTS,
66
66
  Y: () => listMachines,
67
- Yt: () => CODING_AGENTS,
67
+ Yt: () => runs,
68
68
  Z: () => listRunsBefore,
69
- Zt: () => logger,
69
+ Zt: () => coerceCodingAgent,
70
70
  _: () => acknowledgeRetiredReport,
71
71
  _t: () => releaseAbsentReconciliations,
72
72
  a: () => TERMINAL_GRACE_MS,
@@ -77,7 +77,7 @@ var loopTags_CnVLi70h_exports = /* @__PURE__ */ __exportAll$1({
77
77
  ct: () => putRunSnapshot,
78
78
  d: () => readNewIdempotency,
79
79
  dt: () => reclaimRun,
80
- en: () => GC_INTERVAL_MS,
80
+ en: () => BLOB_GC_GRACE_MS,
81
81
  et: () => loopsForMachine,
82
82
  f: () => recordNewIdempotency,
83
83
  ft: () => reconcileReclaimedRun,
@@ -96,19 +96,20 @@ var loopTags_CnVLi70h_exports = /* @__PURE__ */ __exportAll$1({
96
96
  m: () => retireLease,
97
97
  mt: () => recordBlob,
98
98
  n: () => isOutdated,
99
+ nn: () => __exportAll,
99
100
  nt: () => pauseLoop,
100
101
  o: () => connectKeyValid,
101
102
  ot: () => pruneRunSnapshots,
102
103
  p: () => resolveLease,
103
104
  pt: () => reconciliationStatesForRuns,
104
105
  q: () => listArtifacts,
105
- qt: () => runMigrations,
106
+ qt: () => db,
106
107
  r: () => isValidSemver,
107
108
  rt: () => pauseLoopState,
108
109
  s: () => isDeviceTokenShape,
109
110
  st: () => putReportReceiptIfAbsent,
110
111
  t: () => validateLoopTags,
111
- tn: () => __exportAll,
112
+ tn: () => GC_INTERVAL_MS,
112
113
  tt: () => openRuns,
113
114
  u: () => pruneExpiredLeases,
114
115
  ut: () => recentLoopUsage,
@@ -121,7 +122,7 @@ var loopTags_CnVLi70h_exports = /* @__PURE__ */ __exportAll$1({
121
122
  y: () => advanceDueSchedules,
122
123
  yt: () => requestDeleteLoop,
123
124
  z: () => getReportReceipt,
124
- zt: () => loopFileIssue
125
+ zt: () => isValidHash
125
126
  });
126
127
  var import_pino = /* @__PURE__ */ __toESM(require_pino());
127
128
  var import_pino_pretty = /* @__PURE__ */ __toESM(require_pino_pretty());
@@ -153,9 +154,9 @@ var BLOB_GC_GRACE_MS = 3600 * 1e3;
153
154
  /** Storage-maintenance cadence (prune snapshots, then GC blobs). 15 minutes. */
154
155
  var GC_INTERVAL_MS = 900 * 1e3;
155
156
  /**
156
- * Uses pino-pretty as a *synchronous* destination stream (not a worker-thread
157
- * transport) so logs flush before process.exit() in the one-shot CLIs. Writes to
158
- * stderr (fd 2) to keep stdout clean for QR codes and human CLI output.
157
+ * pino-pretty as a synchronous destination stream (not a worker-thread transport)
158
+ * so logs flush before process.exit() in the one-shot CLIs. Writes to stderr so
159
+ * stdout carries only CLI output.
159
160
  */
160
161
  var logger_exports = /* @__PURE__ */ __exportAll({ logger: () => logger });
161
162
  var level = (process.env.PIEVO_LOG_LEVEL || "info").toLowerCase();
@@ -166,17 +167,9 @@ var stream = (0, import_pino_pretty.default)({
166
167
  ignore: "pid,hostname"
167
168
  });
168
169
  var logger = (0, import_pino.default)({ level }, stream);
169
- /** The coding agent a loop is bound to AND executed with (BYOA on the owner's
170
- * machine): `claude-code` → Claude Code, `codex` → `codex exec`, `pi` → Pi.
171
- * Non-Claude agents may still have thinner daemon telemetry until stream
172
- * integration lands; execution itself is real for every value.
173
- *
174
- * Runtime SINGLE SOURCE (anti-drift): every server consumer DERIVES from this
175
- * array — the `CodingAgent` type here, the `db/schema.ts` `CodingAgent` type AND
176
- * the `loops.agent` column enum, the edit validator (`coerceCodingAgent`), and the
177
- * web agent `<select>` (LoopForm). So widening the set is a one-line edit HERE with
178
- * no other server change (the daemon's own enum in `packages/daemon/src/create.ts`
179
- * is a separate package, widened alongside). */
170
+ /** Single source for coding agents: server types, the `loops.agent` column enum,
171
+ * validators, and the dashboard select all derive from this array. The daemon
172
+ * keeps its own copy in `packages/daemon/src/create.ts`; widen both together. */
180
173
  var CODING_AGENTS = [
181
174
  "claude-code",
182
175
  "codex",
@@ -196,10 +189,6 @@ var RESERVED_LOOP_TAGS = [
196
189
  * Pievo is single-user: no row carries an owner. Timestamp values are ISO
197
190
  * strings; JSON columns use `jsonb().$type<>()`.
198
191
  */
199
- /** `skipped`: a pending run retired without executing (pause/retention cleanup,
200
- * queue coalescing, or the offline catch-up window elapsed).
201
- * Neither success nor failure — excluded from the failure streak (it rides
202
- * phase `canceled`, and the streak counts only phase `error`). */
203
192
  var machines = pgTable("machines", {
204
193
  id: text("id").primaryKey(),
205
194
  name: text("name").notNull(),
@@ -228,11 +217,8 @@ var loops = pgTable("loops", {
228
217
  name: text("name").notNull(),
229
218
  tags: text("tags").array().notNull().default(sql`ARRAY[]::text[]`),
230
219
  prompt: text("prompt").notNull(),
231
- statusKeep: text("status_keep").notNull(),
232
- statusNoChange: text("status_no_change").notNull(),
233
- statusBlock: text("status_block").notNull(),
234
- /** Configured artifact file names (`.md`/`.html`), in addition to the
235
- * always-present MEMORY.md. See `gateway/loopFiles.ts`. */
220
+ /** Configured artifact names, in addition to the always-present MEMORY.md.
221
+ * See `gateway/loopFiles.ts`. */
236
222
  artifacts: jsonb("artifacts").$type().notNull().default([]),
237
223
  cron: text("cron").notNull(),
238
224
  scheduleMode: text("schedule_mode", { enum: ["cron", "continuous"] }).notNull(),
@@ -249,11 +235,8 @@ var loops = pgTable("loops", {
249
235
  pauseCause: jsonb("pause_cause").$type(),
250
236
  model: text("model"),
251
237
  reasoningEffort: text("reasoning_effort"),
252
- /** Coding agent this loop is BOUND TO and EXECUTED with: the daemon's
253
- * `buildAgentSpawn` branches on this value (`claude-code` → claude,
254
- * `codex` → `codex exec`, `pi` → Pi print mode). Measured from the creating CLI's env when
255
- * detectable, else the declared/selected value. The text column carries
256
- * both a TypeScript enum and a database CHECK constraint. */
238
+ /** The daemon launches this agent for every run (`buildAgentSpawn`). Enforced by
239
+ * both the TypeScript enum and a database CHECK constraint. */
257
240
  agent: text("agent", { enum: CODING_AGENTS }).notNull(),
258
241
  enabled: boolean("enabled").notNull().default(true),
259
242
  nextRunAt: text("next_run_at"),
@@ -281,6 +264,9 @@ var runs = pgTable("runs", {
281
264
  model: text("model"),
282
265
  reasoningEffort: text("reasoning_effort"),
283
266
  runIndex: integer("run_index"),
267
+ /** A pending run retired without executing (pause, retention cleanup, queue
268
+ * coalescing, or an elapsed offline catch-up window) is `canceled`, not `error`,
269
+ * so it never counts toward the failure streak. */
284
270
  phase: text("phase", { enum: [
285
271
  "pending",
286
272
  "running",
@@ -294,12 +280,11 @@ var runs = pgTable("runs", {
294
280
  ts: text("ts").notNull(),
295
281
  createdAt: text("created_at").notNull().default(sql`CURRENT_TIMESTAMP::text`),
296
282
  updatedAt: text("updated_at").notNull().default(sql`CURRENT_TIMESTAMP::text`),
297
- status: text("status", { enum: [
298
- "keep",
299
- "no-change",
300
- "block"
301
- ] }),
283
+ status: text("status", { enum: ["ok", "block"] }),
302
284
  message: text("message"),
285
+ /** Owner-facing reason from the final response's ATTENTION line. Independent
286
+ * of status: it never pauses the loop. */
287
+ attention: text("attention"),
303
288
  durationMs: integer("duration_ms"),
304
289
  exitCode: integer("exit_code"),
305
290
  finalText: text("final_text"),
@@ -329,7 +314,8 @@ var runs = pgTable("runs", {
329
314
  check("runs_agent_check", sql`${t.agent} IS NULL OR ${t.agent} IN ('claude-code', 'codex', 'pi')`),
330
315
  check("runs_phase_check", sql`${t.phase} IN ('pending', 'running', 'done', 'error', 'canceled')`),
331
316
  check("runs_requested_by_check", sql`${t.requestedBy} IN ('owner', 'system')`),
332
- check("runs_status_check", sql`${t.status} IS NULL OR ${t.status} IN ('keep', 'no-change', 'block')`)
317
+ check("runs_status_check", sql`${t.status} IS NULL OR ${t.status} IN ('ok', 'block')`),
318
+ check("runs_done_status_check", sql`(${t.phase} = 'done') = (${t.status} IS NOT NULL)`)
333
319
  ]);
334
320
  var runLeases = pgTable("run_leases", {
335
321
  tokenHash: text("token_hash").primaryKey(),
@@ -403,7 +389,6 @@ var blobs = pgTable("blobs", {
403
389
  var artifactFiles = pgTable("artifact_files", {
404
390
  id: text("id").primaryKey(),
405
391
  loopId: text("loop_id").notNull(),
406
- /** Loop file name: MEMORY.md or a configured artifact name. */
407
392
  path: text("path").notNull(),
408
393
  hash: text("hash"),
409
394
  size: integer("size"),
@@ -486,7 +471,6 @@ function migrationsDir() {
486
471
  if (fs.existsSync(builtDir)) return builtDir;
487
472
  return devDir;
488
473
  }
489
- /** Apply generated migrations in-process (idempotent). */
490
474
  async function runMigrations() {
491
475
  const dir = migrationsDir();
492
476
  if (!fs.existsSync(dir)) return;
@@ -637,7 +621,7 @@ function createBlobStore() {
637
621
  * Loop files: a loop's server-owned memory and display artifacts.
638
622
  *
639
623
  * Every loop has the required `MEMORY.md` plus up to MAX_ARTIFACTS configured
640
- * `.md`/`.html` artifact names. The server is authoritative: bytes live in the
624
+ * text/code artifact names. The server is authoritative: bytes live in the
641
625
  * content-addressed BlobStore and the current version of each name is an
642
626
  * `artifact_files` row. For each run the daemon downloads the current files into
643
627
  * a private run directory, and the terminal report carries the changed versions
@@ -651,6 +635,7 @@ function createBlobStore() {
651
635
  */
652
636
  var loopFiles_exports = /* @__PURE__ */ __exportAll({
653
637
  ARTIFACT_BYTE_CAP: () => ARTIFACT_BYTE_CAP,
638
+ ARTIFACT_EXTENSIONS: () => ARTIFACT_EXTENSIONS,
654
639
  EMPTY_SHA256: () => EMPTY_SHA256,
655
640
  LOOP_FILE_BYTE_CAP: () => LOOP_FILE_BYTE_CAP,
656
641
  LoopFiles: () => LoopFiles,
@@ -668,26 +653,107 @@ var MEMORY_FILE = "MEMORY.md";
668
653
  /** Memory is re-read by every run, so it is bounded in characters (Unicode code
669
654
  * points), roughly 2k tokens of prompt context per run. */
670
655
  var MEMORY_CHAR_CAP = 8e3;
671
- /** HTML reports are bulky; 2 MB keeps a run's upload and the dashboard iframe cheap. */
672
- var ARTIFACT_BYTE_CAP = 2 * 1024 * 1024;
656
+ /** 256 KB bounds a run's upload and keeps dashboard rendering smooth. */
657
+ var ARTIFACT_BYTE_CAP = 256 * 1024;
658
+ var ARTIFACT_LIMIT_LABEL = `${ARTIFACT_BYTE_CAP / 1024} KB`;
673
659
  /** sha256 of zero bytes: the version of a loop file that has never been written. */
674
660
  var EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
675
- var ARTIFACT_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}\.(md|html)$/;
661
+ /** Text/code formats accepted as artifacts. Loop files are NUL-free text, so
662
+ * binary formats (images, PDFs, archives) are deliberately absent; `.svg` is
663
+ * text and renders through the hardened inline image route. */
664
+ var ARTIFACT_EXTENSIONS = [
665
+ "md",
666
+ "markdown",
667
+ "mdx",
668
+ "html",
669
+ "htm",
670
+ "txt",
671
+ "text",
672
+ "rst",
673
+ "adoc",
674
+ "org",
675
+ "sh",
676
+ "bash",
677
+ "zsh",
678
+ "fish",
679
+ "ps1",
680
+ "bat",
681
+ "cmd",
682
+ "py",
683
+ "rb",
684
+ "pl",
685
+ "lua",
686
+ "r",
687
+ "js",
688
+ "mjs",
689
+ "cjs",
690
+ "ts",
691
+ "tsx",
692
+ "jsx",
693
+ "go",
694
+ "rs",
695
+ "java",
696
+ "kt",
697
+ "kts",
698
+ "scala",
699
+ "c",
700
+ "h",
701
+ "cc",
702
+ "cpp",
703
+ "hpp",
704
+ "cs",
705
+ "php",
706
+ "swift",
707
+ "dart",
708
+ "ex",
709
+ "exs",
710
+ "erl",
711
+ "hs",
712
+ "clj",
713
+ "cljs",
714
+ "json",
715
+ "jsonl",
716
+ "yaml",
717
+ "yml",
718
+ "toml",
719
+ "xml",
720
+ "csv",
721
+ "tsv",
722
+ "svg",
723
+ "css",
724
+ "scss",
725
+ "sass",
726
+ "less",
727
+ "sql",
728
+ "ini",
729
+ "conf",
730
+ "cfg",
731
+ "env",
732
+ "properties",
733
+ "diff",
734
+ "patch",
735
+ "tex",
736
+ "log"
737
+ ];
738
+ var ARTIFACT_EXTENSION_SET = new Set(ARTIFACT_EXTENSIONS);
739
+ var ARTIFACT_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}\.([A-Za-z0-9]{1,16})$/;
676
740
  function sha256Buf(bytes) {
677
741
  return createHash("sha256").update(bytes).digest("hex");
678
742
  }
679
743
  function isValidHash(hash) {
680
744
  return typeof hash === "string" && /^[0-9a-f]{64}$/.test(hash);
681
745
  }
682
- /** A plain file name (no directories) ending in .md or .html, other than MEMORY.md. */
746
+ /** A plain text/code file name (no directories) other than MEMORY.md. */
683
747
  function validArtifactName(name) {
684
- return typeof name === "string" && ARTIFACT_NAME_RE.test(name) && name.toLowerCase() !== "MEMORY.md".toLowerCase();
748
+ if (typeof name !== "string" || name.toLowerCase() === "MEMORY.md".toLowerCase()) return false;
749
+ const match = ARTIFACT_NAME_RE.exec(name);
750
+ return match !== null && ARTIFACT_EXTENSION_SET.has(match[1].toLowerCase());
685
751
  }
686
752
  function loopFileNames(loop) {
687
753
  return [MEMORY_FILE, ...loop.artifacts];
688
754
  }
689
755
  function fileLimitLabel(name) {
690
- return name === "MEMORY.md" ? `${MEMORY_CHAR_CAP} characters` : "2 MB";
756
+ return name === "MEMORY.md" ? `${MEMORY_CHAR_CAP} characters` : ARTIFACT_LIMIT_LABEL;
691
757
  }
692
758
  /** Why `bytes` cannot become the next version of `name`, or undefined if it can. */
693
759
  function loopFileIssue(name, bytes) {
@@ -697,7 +763,7 @@ function loopFileIssue(name, bytes) {
697
763
  if (chars > 8e3) return `${name}: ${chars} characters exceeds the ${MEMORY_CHAR_CAP}-character limit`;
698
764
  return;
699
765
  }
700
- if (bytes.length > 2097152) return `${name}: ${(bytes.length / 1024 / 1024).toFixed(1)} MB exceeds the 2 MB limit`;
766
+ if (bytes.length > 262144) return `${name}: ${(bytes.length / 1024).toFixed(1)} KB exceeds the ${ARTIFACT_LIMIT_LABEL} limit`;
701
767
  }
702
768
  /** The largest byte size any loop file can have (a code point is at most 4 bytes). */
703
769
  var LOOP_FILE_BYTE_CAP = Math.max(ARTIFACT_BYTE_CAP, MEMORY_CHAR_CAP * 4);
@@ -721,10 +787,7 @@ var LoopFiles = class {
721
787
  return hash === "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" || this.blobStore.has(hash);
722
788
  }
723
789
  };
724
- /**
725
- * Data-access layer over Drizzle. Loop-locked state transitions live here; every
726
- * DB-touching function is async, while the pure helpers stay sync.
727
- */
790
+ /** Data-access layer over Drizzle. Loop-locked state transitions live here. */
728
791
  var store_exports = /* @__PURE__ */ __exportAll({
729
792
  TERMINAL_REPORT_GRACE_MS: () => TERMINAL_REPORT_GRACE_MS,
730
793
  acknowledgeRetiredReport: () => acknowledgeRetiredReport,
@@ -1928,19 +1991,15 @@ async function countRunSnapshots() {
1928
1991
  return Number(r?.n ?? 0);
1929
1992
  }
1930
1993
  /**
1931
- * Machine + run credential helpers. Device tokens (`dk_…`) identify a machine
1932
- * (its id is derived from the token: `m-sha256(token)[:16]`). A run
1933
- * LEASE (`rk_…`) is minted per delivery, bound to one run, and carries the
1934
- * run's least-privilege caps — the CLI dispatch authorizes the `pievo` shim
1935
- * against it. Its lifecycle is a small state machine (`active` →
1936
- * `terminal-grace` → `reconciliation-only`/`retired`), not a mint→revoke pair;
1937
- * see `RunLease` below.
1994
+ * Machine and run credentials. A device token (`dk_…`) identifies a machine whose
1995
+ * id is derived from it (`m-sha256(token)[:16]`). A run lease (`rk_…`) is minted per
1996
+ * delivery and authorizes only that run's loop-file transfer and terminal report;
1997
+ * its lifecycle is the state machine on `RunLease`.
1938
1998
  *
1939
- * Leases and connect-key bindings are DURABLE (run_leases / connect_keys
1940
- * tables): they must survive a deploy, or every restart 401s the in-flight
1941
- * runs' callbacks/finalize and prevents a freshly connected machine from
1942
- * enrolling. The 15-minute `new` idempotency window stays in-process;
1943
- * losing it only degrades retry deduplication, never persisted data.
1999
+ * Leases and connect-key bindings are durable so a restart neither rejects
2000
+ * in-flight reports nor blocks a just-connected machine from enrolling. The
2001
+ * 15-minute `new` idempotency window stays in memory; losing it on restart only
2002
+ * weakens retry deduplication.
1944
2003
  */
1945
2004
  var tokens_exports = /* @__PURE__ */ __exportAll({
1946
2005
  CONNECT_KEY_TTL_MS: () => CONNECT_KEY_TTL_MS,
@@ -2203,8 +2262,8 @@ function validateLoopTags(value) {
2203
2262
  };
2204
2263
  }
2205
2264
  //#endregion
2206
- //#region node_modules/.nitro/vite/services/ssr/assets/boot-DmpKtb0A.js
2207
- var boot_DmpKtb0A_exports = /* @__PURE__ */ __exportAll$1({
2265
+ //#region node_modules/.nitro/vite/services/ssr/assets/boot-jtVCf_pN.js
2266
+ var boot_jtVCf_pN_exports = /* @__PURE__ */ __exportAll$1({
2208
2267
  _: () => validOptionalPollString,
2209
2268
  a: () => POLL_HTTP_BODY_FIELDS,
2210
2269
  c: () => MIN_DAEMON_VERSION,
@@ -2296,11 +2355,8 @@ function validOptionalPollString(value, cap) {
2296
2355
  function nowIso() {
2297
2356
  return (/* @__PURE__ */ new Date()).toISOString();
2298
2357
  }
2299
- /** Strip NUL (U+0000) from a wire string: Postgres text/jsonb columns REJECT the
2300
- * NUL byte (SQLite tolerated it), so a daemon-supplied string carrying one would
2301
- * throw mid-finalize on the DB write. The single sanitizing primitive behind
2302
- * `clipText` - and used directly by `cli.ts`
2303
- * (parseFlags/validateState, the same one-chokepoint discipline). */
2358
+ /** Postgres text/jsonb columns reject NUL, so a daemon-supplied string carrying
2359
+ * one would fail mid-finalize. The single sanitizer behind `clipText` and `cli.ts`. */
2304
2360
  function stripNul(s) {
2305
2361
  return s.replace(/\u0000/g, "");
2306
2362
  }
@@ -2310,33 +2366,35 @@ function clipText(s, cap) {
2310
2366
  /** Literal placeholder the daemon replaces with the run's absolute loop-file
2311
2367
  * directory before launch (and exports as an env var of the same name). */
2312
2368
  var LOOP_DIR_PLACEHOLDER = "$PIEVO_LOOP_DIR";
2313
- var STATUS_LINE_RE = /^STATUS:\s*(keep|no-change|block)\.?$/i;
2369
+ var STATUS_LINE_RE = /^STATUS:\s*(ok|block)\.?$/i;
2370
+ var ATTENTION_LINE_RE = /^ATTENTION:\s*(.*)$/i;
2314
2371
  /**
2315
- * Deliver the user's prompt unchanged, followed by the status definitions, the
2316
- * loop-file contract, and the final-line status contract. The run index
2372
+ * Deliver the user's prompt unchanged, followed by the loop-file contract and the
2373
+ * final-line status contract. The run index
2317
2374
  * intentionally remains an internal history fact.
2318
2375
  */
2319
2376
  function buildRunTask(loop, previousNotices = []) {
2320
- const files = [`- ${MEMORY_FILE}: this loop's memory, kept across runs (max ${MEMORY_CHAR_CAP} characters). Read it before you start. Before finishing, update it with anything that would help future runs avoid mistakes or work faster. Keep it concise: merge similar notes, delete stale or wrong ones, no run logs.`, ...loop.artifacts.map((name) => `- ${name}: artifact shown on the Pievo dashboard (max 2 MB). Update it only as the task above asks.`)];
2377
+ const files = [`- ${MEMORY_FILE}: this loop's long-term experience (max ${MEMORY_CHAR_CAP} characters). Read it first and follow it. Before finishing, record only lessons this run learned the hard way that make later runs faster or safer: a pitfall hit, a workaround found, an environment fact discovered. One line each; correct or delete lessons that proved wrong. Never restate the task or these instructions, and never store state, results, baselines, trends, or run logs (those go in an artifact). Most runs add nothing.`, ...loop.artifacts.map((name) => `- ${name}: artifact shown on the Pievo dashboard (max ${fileLimitLabel(name)}). Update it only as the task above asks.`)];
2321
2378
  const notices = previousNotices.length ? `\n\nNotices from the previous run:\n${previousNotices.map((notice) => `- ${notice}`).join("\n")}` : "";
2322
2379
  return `${loop.prompt}
2323
2380
 
2324
- Status definitions:
2325
- - keep: ${loop.statusKeep}
2326
- - no-change: ${loop.statusNoChange}
2327
- - block: ${loop.statusBlock}
2328
-
2329
2381
  Loop files in ${LOOP_DIR_PLACEHOLDER}:
2330
2382
  ${files.join("\n")}
2331
2383
  A file over its limit is rejected and its previous version kept. Other files in that directory are discarded.${notices}
2332
2384
 
2333
- End your final response with a short summary, then exactly one last line:
2334
- STATUS: keep|no-change|block`;
2385
+ Be concise in everything you write: lead with what changed or needs action; prefer tables and short lists; no filler or restated instructions.
2386
+
2387
+ Your final response is only: a summary of at most five short lines (no headings, no repeated tables or file contents); if the owner should look at something, one line \`ATTENTION: <reason>\`; then exactly one last line:
2388
+ STATUS: ok|block
2389
+ - ok: the loop keeps running on schedule. Findings and alerts are ok; flag them with ATTENTION.
2390
+ - block: the loop must stop until the owner acts, because the task says to stop or the next run cannot proceed safely (missing access, broken environment, unsafe next step). Block pauses the loop.`;
2335
2391
  }
2392
+ var bareLine = (line) => line.trim().replace(/^[*_`>\s]+|[*_`\s]+$/g, "");
2336
2393
  /**
2337
- * Split a provider's final response into the reported status and summary. The
2338
- * final non-empty line must be the status line; markdown emphasis or code ticks
2339
- * around it are tolerated because models add them unprompted.
2394
+ * Split a provider's final response into the reported status, an optional
2395
+ * attention reason from the ATTENTION lines just above it, and the summary. The final
2396
+ * non-empty line must be the status line; markdown emphasis or code ticks around
2397
+ * either line are tolerated because models add them unprompted.
2340
2398
  */
2341
2399
  function parseFinalStatus(finalText) {
2342
2400
  if (!finalText) return void 0;
@@ -2344,12 +2402,26 @@ function parseFinalStatus(finalText) {
2344
2402
  let index = lines.length - 1;
2345
2403
  while (index >= 0 && lines[index].trim() === "") index--;
2346
2404
  if (index < 0) return void 0;
2347
- const bare = lines[index].trim().replace(/^[*_`>\s]+|[*_`\s]+$/g, "");
2348
- const match = STATUS_LINE_RE.exec(bare);
2405
+ const match = STATUS_LINE_RE.exec(bareLine(lines[index]));
2349
2406
  if (!match) return void 0;
2407
+ const status = match[1].toLowerCase();
2408
+ const reasons = [];
2409
+ let end = index;
2410
+ for (;;) {
2411
+ let previous = end;
2412
+ while (previous > 0 && lines[previous - 1].trim() === "") previous--;
2413
+ const attentionMatch = previous > 0 ? ATTENTION_LINE_RE.exec(bareLine(lines[previous - 1])) : null;
2414
+ if (!attentionMatch) break;
2415
+ end = previous - 1;
2416
+ const reason = attentionMatch[1].trim();
2417
+ if (reason && !/^(none|n\/a|-)$/i.test(reason)) reasons.unshift(reason);
2418
+ }
2419
+ const joined = reasons.join("; ");
2420
+ const attention = joined ? joined.length > 200 ? `${joined.slice(0, 199)}…` : joined : null;
2350
2421
  return {
2351
- status: match[1].toLowerCase(),
2352
- message: lines.slice(0, index).join("\n").trim()
2422
+ status,
2423
+ message: lines.slice(0, end).join("\n").trim(),
2424
+ attention
2353
2425
  };
2354
2426
  }
2355
2427
  async function buildDelivery(loop, queuedRun, runToken, roots) {
@@ -2373,31 +2445,11 @@ async function buildDelivery(loop, queuedRun, runToken, roots) {
2373
2445
  };
2374
2446
  }
2375
2447
  /**
2376
- * Loop-file storage retention + garbage collection — the policy that keeps the
2377
- * local byte store from growing monotonically.
2378
- *
2379
- * The loop-file pipeline is content-addressed: blob BYTES are keyed by sha256 and
2380
- * deduped across every loop/run. When a file is deleted or its content changes,
2381
- * the OLD blob is no longer pointed at by the current file row — but nothing was
2382
- * reclaiming it, so the byte store grew forever. This module closes that gap with two pieces:
2383
- *
2384
- * 1. pruneSnapshots() — bound the per-loop run-snapshot history to a window, so
2385
- * old snapshots stop pinning the blobs they referenced for diffs.
2386
- * 2. gcBlobs() — delete blob bytes no LIVE row needs: a blob is collected
2387
- * only when NO artifact_files row AND NO retained run_snapshot references its
2388
- * hash. CORRECTNESS over aggressiveness — a still-referenced blob is never
2389
- * deleted; a leaked blob is only a cost bug the next pass reclaims.
2390
- *
2391
- * Concurrency: gcBlobs is safe alongside owner writes and run uploads. Three guards
2392
- * combine — a grace window (a blob whose metadata row is younger than the window is
2393
- * never collected, so a blob a run just uploaded is untouchable), a point re-check
2394
- * of each candidate immediately before its bytes are deleted (so a blob
2395
- * re-referenced before we touch it keeps both its bytes and metadata), and a
2396
- * bytes-before-metadata delete ordering: each candidate's bytes are reclaimed
2397
- * FIRST, then its metadata row is dropped unconditionally — so blobExists() goes
2398
- * false and any writer that re-references the hash (even one that raced the byte
2399
- * delete) re-uploads the bytes (self-healing). We never leave a live blobs row
2400
- * pointing at deleted bytes.
2448
+ * Loop-file storage retention. Blob bytes are content-addressed and shared, so
2449
+ * replacing a file never frees its old blob by itself. pruneSnapshots() bounds
2450
+ * per-loop snapshot history; gcBlobs() then deletes bytes no file row and no
2451
+ * retained snapshot references. Correctness wins over aggressiveness: a leaked
2452
+ * blob is a storage cost the next pass reclaims, a deleted live blob is data loss.
2401
2453
  */
2402
2454
  var log$2 = logger.child({ mod: "retention" });
2403
2455
  /**
@@ -2411,28 +2463,14 @@ async function pruneSnapshots(keep = 20) {
2411
2463
  return pruned;
2412
2464
  }
2413
2465
  /**
2414
- * Reclaim unreferenced blob bytes. Returns the number of blobs collected.
2415
- *
2416
- * Algorithm:
2417
- * 1. Compute the live keep-set (all hashes any artifact_files row / retained
2418
- * snapshot references) and the grace-windowed candidate list — both reads are
2419
- * synchronous (interleave-free under the single-threaded event loop).
2420
- * 2. Garbage = candidates not in the keep-set.
2421
- * 3. For each garbage hash, in order:
2422
- * a. Pre-delete guard: if a sync re-referenced it since the keep-set was
2423
- * computed, skip it — leaving both its bytes AND metadata row intact
2424
- * (a consistent live blob, nothing to heal). The guard is two O(1) checks:
2425
- * an indexed artifact_files point query (always fresh) plus membership in a
2426
- * snapshot-hash Set deserialized ONCE per pass and rebuilt only when the
2427
- * snapshot row count changes (a report() raced us) — never the old
2428
- * per-candidate full snapshot scan (which was O(garbage × snapshots), since
2429
- * genuine garbage always misses the file check and fell through to it).
2430
- * b. Otherwise delete the BYTES (the await), then drop the metadata row
2431
- * UNCONDITIONALLY. Bytes-before-metadata is the data-loss fix: if a sync
2432
- * raced the byte delete (re-referencing the hash + recreating the blobs
2433
- * row mid-await), dropping the metadata still leaves blobExists()=false,
2434
- * so that file re-uploads its bytes on the next sync. We never leave a
2435
- * live blobs row pointing at deleted bytes.
2466
+ * Reclaim unreferenced blob bytes; returns the number collected. Safe alongside
2467
+ * owner writes and run uploads because of three guards:
2468
+ * - grace window: blobs whose metadata row is younger than `graceMs` are skipped;
2469
+ * - per-candidate re-check right before deletion: an indexed file-row lookup plus
2470
+ * a snapshot-hash set rebuilt only when the snapshot count changes (a concurrent
2471
+ * report), instead of an O(garbage x snapshots) scan;
2472
+ * - bytes before metadata: the metadata row is dropped after the bytes even if a
2473
+ * writer raced the delete, so blobExists() is false and that writer re-uploads.
2436
2474
  */
2437
2475
  async function gcBlobs(blobStore, graceMs = BLOB_GC_GRACE_MS) {
2438
2476
  const refs = await liveBlobRefs();
@@ -2592,7 +2630,6 @@ var LOOP_CONFIG_FIELDS = [
2592
2630
  "model",
2593
2631
  "reasoningEffort",
2594
2632
  "prompt",
2595
- "statusDefinitions",
2596
2633
  "artifacts",
2597
2634
  "enabled"
2598
2635
  ];
@@ -2768,36 +2805,6 @@ function validateSchedule(value) {
2768
2805
  detail: "schedule.mode must be cron|continuous"
2769
2806
  };
2770
2807
  }
2771
- function validateStatusDefinitions(value) {
2772
- const raw = object(value);
2773
- if (!raw) return {
2774
- ok: false,
2775
- detail: "statusDefinitions is required"
2776
- };
2777
- const unknown = Object.keys(raw).filter((key) => ![
2778
- "keep",
2779
- "noChange",
2780
- "block"
2781
- ].includes(key));
2782
- if (unknown.length) return {
2783
- ok: false,
2784
- detail: `statusDefinitions has unknown field(s): ${unknown.join(", ")}`
2785
- };
2786
- const keep = requiredText(raw.keep, "statusDefinitions.keep", true);
2787
- if (!keep.ok) return keep;
2788
- const noChange = requiredText(raw.noChange, "statusDefinitions.noChange", true);
2789
- if (!noChange.ok) return noChange;
2790
- const block = requiredText(raw.block, "statusDefinitions.block", true);
2791
- if (!block.ok) return block;
2792
- return {
2793
- ok: true,
2794
- value: {
2795
- keep: keep.value,
2796
- noChange: noChange.value,
2797
- block: block.value
2798
- }
2799
- };
2800
- }
2801
2808
  function validateArtifactNames(value) {
2802
2809
  if (value === void 0 || value === null) return {
2803
2810
  ok: true,
@@ -2816,7 +2823,7 @@ function validateArtifactNames(value) {
2816
2823
  for (const entry of value) {
2817
2824
  if (!validArtifactName(entry)) return {
2818
2825
  ok: false,
2819
- detail: `artifact must be a plain .md or .html file name other than ${MEMORY_FILE} (letters, digits, . _ -; at most 64 characters before the extension): ${JSON.stringify(entry)}`
2826
+ detail: `artifact must be a plain text/code file name other than ${MEMORY_FILE} (supported extensions: ${ARTIFACT_EXTENSIONS.join(", ")}; letters, digits, . _ -; at most 64 characters before the extension): ${JSON.stringify(entry)}`
2820
2827
  };
2821
2828
  const key = entry.toLowerCase();
2822
2829
  if (seen.has(key)) return {
@@ -2862,18 +2869,10 @@ function scheduleFromLoop(loop) {
2862
2869
  }
2863
2870
  throw new Error(`invariant: loop ${loop.id} has unknown schedule mode: ${String(loop.scheduleMode)}`);
2864
2871
  }
2865
- function statusDefinitionsFromLoop(loop) {
2866
- return {
2867
- keep: loop.statusKeep,
2868
- noChange: loop.statusNoChange,
2869
- block: loop.statusBlock
2870
- };
2871
- }
2872
2872
  function canonicalLoopEnvelope(loop) {
2873
2873
  return {
2874
2874
  ...canonicalLoopSummary(loop),
2875
2875
  prompt: loop.prompt,
2876
- statusDefinitions: statusDefinitionsFromLoop(loop),
2877
2876
  artifacts: loop.artifacts,
2878
2877
  enabled: loop.enabled
2879
2878
  };
@@ -2911,8 +2910,6 @@ function validateLoopCreate(value) {
2911
2910
  if (!prompt.ok) return prompt;
2912
2911
  const schedule = validateSchedule(raw.schedule);
2913
2912
  if (!schedule.ok) return schedule;
2914
- const statusDefinitions = validateStatusDefinitions(raw.statusDefinitions);
2915
- if (!statusDefinitions.ok) return statusDefinitions;
2916
2913
  const artifacts = validateArtifactNames(raw.artifacts);
2917
2914
  if (!artifacts.ok) return artifacts;
2918
2915
  const model = optionalProvider(raw.model, "model");
@@ -2937,7 +2934,6 @@ function validateLoopCreate(value) {
2937
2934
  model: model.value,
2938
2935
  reasoningEffort: reasoningEffort.value,
2939
2936
  prompt: prompt.value,
2940
- statusDefinitions: statusDefinitions.value,
2941
2937
  artifacts: artifacts.value,
2942
2938
  enabled: raw.enabled !== false
2943
2939
  };
@@ -2954,9 +2950,6 @@ function validateLoopCreate(value) {
2954
2950
  model: config.model,
2955
2951
  reasoningEffort: config.reasoningEffort,
2956
2952
  prompt: config.prompt,
2957
- statusKeep: config.statusDefinitions.keep,
2958
- statusNoChange: config.statusDefinitions.noChange,
2959
- statusBlock: config.statusDefinitions.block,
2960
2953
  artifacts: config.artifacts,
2961
2954
  enabled: config.enabled
2962
2955
  }
@@ -3000,13 +2993,6 @@ function validateLoopEdit(loop, value) {
3000
2993
  if (!result.ok) return result;
3001
2994
  Object.assign(update, rowSchedule(result.value, loop.cron));
3002
2995
  }
3003
- if (raw.statusDefinitions !== void 0) {
3004
- const result = validateStatusDefinitions(raw.statusDefinitions);
3005
- if (!result.ok) return result;
3006
- update.statusKeep = result.value.keep;
3007
- update.statusNoChange = result.value.noChange;
3008
- update.statusBlock = result.value.block;
3009
- }
3010
2996
  if (raw.artifacts !== void 0) {
3011
2997
  const result = validateArtifactNames(raw.artifacts);
3012
2998
  if (!result.ok) return result;
@@ -3077,22 +3063,16 @@ var validPollWireId = (value) => typeof value === "string" && value.length <= 20
3077
3063
  var validPollCurrentRun = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 2 && Object.prototype.hasOwnProperty.call(value, "runId") && Object.prototype.hasOwnProperty.call(value, "stage") && validPollWireId(value.runId) && (value.stage === "executing" || value.stage === "reporting");
3078
3064
  var validPollInfo = (value) => value === void 0 || value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every((key) => POLL_INFO_FIELDS.has(key)) && validOptionalPollString(value.host, 524288) && validOptionalPollString(value.platform, 524288) && validOptionalPollString(value.arch, 524288) && validOptionalPollString(value.version, 64);
3079
3065
  /**
3080
- * Machine gateway - the run-lifecycle core of the HTTP surface the daemon talks
3081
- * to (poll transport: short-poll while a run is in flight, opt-in server-held
3082
- * long-poll while idle). Framework-agnostic like the rest of the gateway
3083
- * (return `{ status, body }` so the methods can be mounted on a plain http
3084
- * server or TanStack server routes):
3066
+ * Machine gateway: the run-lifecycle core the daemon talks to. Methods return
3067
+ * transport-free `{ status, body }` results for the thin route shells:
3085
3068
  *
3086
- * POST /api/machine/poll (Bearer device token) → claim pending runs, deliver
3087
- * POST /machine/report (Bearer run token) → finalize a run
3088
- * GET|PUT /api/machine/run-files/:name (Bearer run token) → loop-file transfer
3069
+ * POST /api/machine/poll (device token) claim and deliver runs
3070
+ * POST /machine/report (run token) finalize a run
3071
+ * GET|PUT /api/machine/run-files/:name (run token) loop-file transfer
3089
3072
  *
3090
- * plus owner create/list/edit methods and retention.
3091
- * Also exposes `dispatcher` (a `Dispatcher` for the Scheduler: "is the machine
3092
- * online?") and `sweep()` (mark stale machines offline, reclaim stuck runs).
3093
- * CLI verb dispatch at `/api/machine/cli` lives in `gateway/cli.ts`
3094
- * (`CliGateway`), which reuses this class's owner methods;
3095
- * canonical loop configuration validation lives in `gateway/loopConfig.ts`.
3073
+ * Idle polls may be held open (long-poll) until the scheduler's dispatcher wakes
3074
+ * them. `sweep()` marks stale machines offline and reclaims silent runs. CLI verbs
3075
+ * live in `cli.ts`; loop config validation lives in `loopConfig.ts`.
3096
3076
  */
3097
3077
  var log$1 = logger.child({ mod: "gateway" });
3098
3078
  var ONLINE_TTL_MS = 3e4;
@@ -3242,7 +3222,7 @@ function incidentDiagnosis(code, issues, reportId, payloadDigest) {
3242
3222
  recommendedAction
3243
3223
  };
3244
3224
  }
3245
- var MISSING_STATUS_ERROR = "run protocol incomplete: the final response has no \"STATUS: keep|no-change|block\" line";
3225
+ var MISSING_STATUS_ERROR = "run protocol incomplete: the final response has no \"STATUS: ok|block\" line";
3246
3226
  /** The run outcome a terminal report establishes. Only a successful provider
3247
3227
  * exit is read for a status; a successful exit without one is a protocol error. */
3248
3228
  function reportOutcome(body) {
@@ -3269,6 +3249,7 @@ function reportOutcome(body) {
3269
3249
  patch: {
3270
3250
  status: parsed.status,
3271
3251
  message: clipText(parsed.message, 2e3) || null,
3252
+ attention: parsed.attention,
3272
3253
  error: null
3273
3254
  }
3274
3255
  };
@@ -3352,7 +3333,6 @@ var MachineGateway = class {
3352
3333
  this.blobStore = blobStore;
3353
3334
  this.loopFiles = new LoopFiles(blobStore);
3354
3335
  }
3355
- /** Loop-file bytes over the same blob store that maintenance garbage-collects. */
3356
3336
  loopFiles;
3357
3337
  /** In-flight latch: the maintenance pass is sequential and the first post-deploy
3358
3338
  * backlog reclamation can overrun the interval, so a fresh tick skips rather than
@@ -3430,20 +3410,12 @@ var MachineGateway = class {
3430
3410
  await pruneExpiredLeases(now);
3431
3411
  for (const loop of await listLoops()) if (loop.deleteRequestedAt) await tryDeleteLoop(loop.id);
3432
3412
  }
3433
- /** Finalize one stuck run as an error (the sweep's reclaim path): persist the
3434
- * failure, TERMINALIZE its run lease (flip it to `terminal-grace` rather than
3435
- * retiring it outright), while preserving a bounded reconciliation window.
3436
- *
3437
- * Why terminalize, not retire: the usual cause is a laptop that merely fell
3438
- * ASLEEP mid-run. When it wakes, claude finishes and the daemon delivers the real
3439
- * (often SUCCESSFUL) result. Retiring the lease here would 401 that late report
3440
- * and strand the run as a permanent false failure with its message lost (the
3441
- * investigated bug). So the lease survives a bounded grace window
3442
- * (`TERMINAL_GRACE_MS`) during which exactly ONE late wake-report may reconcile
3443
- * the run — see `report()`'s terminal-grace branch. The credential is still
3444
- * bounded: CLI mutations are refused while terminal-grace, and the
3445
- * reconciliation retires the lease single-shot. Pending rows are durable inbox
3446
- * entries and are never reclaimed by this path. */
3413
+ /** Reclaim one silent running run as an error, but terminalize its lease instead
3414
+ * of retiring it. The usual cause is a laptop that fell asleep mid-run; on wake
3415
+ * the daemon delivers the real (often successful) result, and a retired lease
3416
+ * would 401 it and leave a false failure. During TERMINAL_GRACE_MS exactly one
3417
+ * late report may reconcile the run (see `report()`); CLI mutations stay refused.
3418
+ * Pending rows are durable queue entries and never reach this path. */
3447
3419
  async reclaimRun(run, reason) {
3448
3420
  if (run.phase !== "running") return;
3449
3421
  const at = nowIso();
@@ -3629,11 +3601,8 @@ var MachineGateway = class {
3629
3601
  waiter.cancel();
3630
3602
  }
3631
3603
  }
3632
- /**
3633
- * Whether this machine (by device token) currently has a live daemon — so
3634
- * Claude Code can avoid starting a duplicate. `online` is fresh-checked against
3635
- * the poll TTL, not just the stored flag.
3636
- */
3604
+ /** Whether this machine has a live daemon. `online` is fresh-checked against the
3605
+ * poll TTL, not just the stored flag. */
3637
3606
  async status(deviceToken) {
3638
3607
  const auth = await authenticateDeviceToken(deviceToken, { allowUnknown: true });
3639
3608
  if (!auth.ok) return auth.response;
@@ -4325,10 +4294,8 @@ function renderLoopsText(loops, fields) {
4325
4294
  return doc(countLine(loops.length), listBlock("loops", fields, loops.map((l) => fields.map((f) => loopCell(l, f)))), helpBlock(["Run `pievo show <id>` to see a loop's full config", "Run `pievo log <id>` to see a loop's recent runs"]));
4326
4295
  }
4327
4296
  function runResultToken(r) {
4328
- if (r.status === "block") return `block/${r.phase}`;
4329
- if (r.phase === "canceled") return "canceled";
4330
- const base = r.phase === "error" ? "failed" : r.phase === "done" ? "ok" : r.phase;
4331
- return r.status ? `${base}/${r.status}` : `${base}/missing-status`;
4297
+ if (r.phase === "error") return "failed";
4298
+ return r.status ?? r.phase;
4332
4299
  }
4333
4300
  /** Stable JSON with recursively sorted object keys — so two structurally-equal values
4334
4301
  * serialize identically regardless of key ordering (pg `jsonb` normalizes key order). */
@@ -4558,11 +4525,7 @@ var TERMINAL_PHASES = [
4558
4525
  "error",
4559
4526
  "canceled"
4560
4527
  ];
4561
- var STATUSES = [
4562
- "keep",
4563
- "no-change",
4564
- "block"
4565
- ];
4528
+ var STATUSES = ["ok", "block"];
4566
4529
  var LOG_FLAGS = new Set([
4567
4530
  "_",
4568
4531
  "loop",
@@ -4620,7 +4583,7 @@ function parseHistoryFlags(flags) {
4620
4583
  const status = flags.status;
4621
4584
  if (status !== void 0 && (typeof status !== "string" || !STATUSES.includes(status))) return {
4622
4585
  ok: false,
4623
- error: "--status must be keep|no-change|block"
4586
+ error: "--status must be ok|block"
4624
4587
  };
4625
4588
  const phase = flags.phase;
4626
4589
  if (phase !== void 0 && (typeof phase !== "string" || !TERMINAL_PHASES.includes(phase))) return {
@@ -4689,15 +4652,19 @@ function boundedText(value, cap = HISTORY_DETAIL_TEXT_CAP) {
4689
4652
  }
4690
4653
  function normalizedListRun(run) {
4691
4654
  const message = boundedText(run.message ?? null, 500);
4655
+ const error = boundedText(run.error ?? null, 500);
4692
4656
  return {
4693
4657
  runIndex: run.runIndex,
4694
4658
  terminalAt: run.ts,
4695
4659
  phase: run.phase,
4696
4660
  status: run.status ?? null,
4661
+ attention: run.attention ?? null,
4697
4662
  durationMs: run.durationMs ?? null,
4698
4663
  tokenUsage: totalTokenUsage(run.usage ?? null),
4699
4664
  message: message.value,
4700
4665
  messageTruncated: message.truncated,
4666
+ error: error.value,
4667
+ errorTruncated: error.truncated,
4701
4668
  finalTextAvailable: !!run.finalText?.trim()
4702
4669
  };
4703
4670
  }
@@ -4748,18 +4715,22 @@ async function listHistory(loop, q) {
4748
4715
  "terminal",
4749
4716
  "phase",
4750
4717
  "status",
4718
+ "attention",
4751
4719
  "durationMs",
4752
4720
  "tokenUsage",
4753
4721
  "message",
4722
+ "error",
4754
4723
  "finalTextAvailable"
4755
4724
  ], rows.map((run) => [
4756
4725
  run.runIndex,
4757
4726
  fmt(run.ts),
4758
4727
  run.phase,
4759
4728
  run.status ?? null,
4729
+ run.attention ?? null,
4760
4730
  run.durationMs ?? null,
4761
4731
  totalTokenUsage(run.usage ?? null),
4762
4732
  run.message ? truncate(run.message, 500, "use --run for detail").value : null,
4733
+ run.error ? truncate(run.error, 500, "use --run for detail").value : null,
4763
4734
  !!run.finalText?.trim()
4764
4735
  ])) : emptyList("runs");
4765
4736
  return response(doc(`loop: ${scalar(loop.name)} (${loop.id})`, `count: ${rows.length} of ${Number(counted[0]?.n ?? 0)} matching`, table, helpBlock(["Use `pievo log --run <index>` for one run"])));
@@ -4804,6 +4775,7 @@ async function detailHistory(loop, q) {
4804
4775
  terminalAt: TERMINAL_PHASES.includes(run.phase) ? run.ts : null,
4805
4776
  phase: run.phase,
4806
4777
  status: run.status ?? null,
4778
+ attention: run.attention ?? null,
4807
4779
  durationMs: run.durationMs ?? null,
4808
4780
  tokenUsage: totalTokenUsage(run.usage ?? null),
4809
4781
  message: message.value,
@@ -4830,6 +4802,7 @@ async function detailHistory(loop, q) {
4830
4802
  ["terminalAt", detail.terminalAt],
4831
4803
  ["phase", detail.phase],
4832
4804
  ["status", detail.status],
4805
+ ["attention", detail.attention],
4833
4806
  ["durationMs", detail.durationMs],
4834
4807
  ["tokenUsage", detail.tokenUsage],
4835
4808
  ["message", detail.message],
@@ -4853,18 +4826,10 @@ async function readLoopHistory(loop, flags) {
4853
4826
  return parsed.value.mode === "detail" ? detailHistory(loop, parsed.value) : listHistory(loop, parsed.value);
4854
4827
  }
4855
4828
  /**
4856
- * CLI dispatch - the credential-keyed verb router half of the machine gateway,
4857
- * split out of `MachineGateway` (which keeps poll/report/sweep/owner verbs).
4858
- * Same wire surface, framework-agnostic like the rest of the gateway
4859
- * (`{ status, body }` results):
4860
- *
4861
- * POST /api/machine/cli (Bearer device OR run credential) → unified dispatch
4862
- *
4863
- * The verb router keys authority on credential type first (`dk_` device prefix
4864
- * vs `rk_` run lease) and reuses the core gateway
4865
- * methods (`createLoop`/`editLoop`) through the injected `MachineGateway`,
4866
- * plus the credential-neutral history module. Run credentials authorize no CLI
4867
- * verb: a run reports through its final response, not a callback.
4829
+ * `/api/machine/cli` verb router. Authority keys on credential type: a device
4830
+ * (`dk_`) token gets owner verbs over loops bound to its machine, reusing
4831
+ * `MachineGateway`'s owner methods; a run (`rk_`) token gets no verb, because a
4832
+ * run reports only through its final response.
4868
4833
  */
4869
4834
  var cliLog = logger.child({ mod: "cli" });
4870
4835
  var CliGateway = class {
@@ -4878,14 +4843,6 @@ var CliGateway = class {
4878
4843
  this.forceDeleteLoop = deps.forceDeleteLoop ?? forceDeleteLoop;
4879
4844
  this.destructiveLog = deps.destructiveLog ?? ((event) => cliLog.warn(event, "force-delete: destructive server authority removal"));
4880
4845
  }
4881
- /**
4882
- * The unified CLI endpoint. It is a ROUTER in front of the gateway logic that
4883
- * already exists — never a rewrite — that keys authority on the credential type:
4884
- * · DEVICE credential (`dk_`-prefixed) → owner authority over any loop bound to
4885
- * the machine: `new`→createLoop, `loops`→listLoops, `edit`→editLoop,
4886
- * `log`→readLoopHistory, `show`→describe, `file`→loop files.
4887
- * · RUN credential (`rk_` run lease) → rejected; runs have no CLI.
4888
- */
4889
4846
  async cli(token, argv) {
4890
4847
  return finalizeCli(token.startsWith("dk_") ? await this.deviceCli(token, argv) : await this.runCli(token, argv));
4891
4848
  }
@@ -5266,7 +5223,7 @@ var CliGateway = class {
5266
5223
  })), scoped.elsewhere, await recentMachineRuns(loops, 3));
5267
5224
  }
5268
5225
  };
5269
- var RUN_HAS_NO_CLI = "pievo commands are not available inside a run; end your final response with a summary and a last line `STATUS: keep|no-change|block`";
5226
+ var RUN_HAS_NO_CLI = "pievo commands are not available inside a run; end your final response with a summary and a last line `STATUS: ok|block`";
5270
5227
  var FILE_USAGE = "pievo file list <loop> | file get <loop> <name> | file set <loop> <name> --from <path>";
5271
5228
  var PAUSED_FINISHING = "loop paused; current run is finishing";
5272
5229
  var STOP_UPGRADE_REQUIRED = "Daemon upgrade required to stop a running process. Run `npm install -g @kky42/pievo@latest`, then `pievo daemon restart`.";
@@ -5336,7 +5293,7 @@ var DEVICE_VERB_HELP = {
5336
5293
  new: {
5337
5294
  syntax: "new --json '<config>' [--dry-run]",
5338
5295
  summary: `create a loop from the canonical envelope (keys: ${[...EDITABLE_LOOP_FIELDS].join(", ")}; agent required)`,
5339
- help: ["Run `pievo new --json '{\"name\":\"Daily check\",\"schedule\":{\"mode\":\"cron\",\"cron\":\"0 8 * * *\",\"timezone\":\"UTC\",\"overlap\":\"skip\"},\"workdir\":\"<path>\",\"agent\":\"claude-code\",\"prompt\":\"Check the project.\",\"statusDefinitions\":{\"keep\":\"result retained\",\"noChange\":\"nothing changed\",\"block\":\"owner input required\"}}'` to create a loop", "Run `pievo new --json '{...}' --dry-run` to validate without creating"]
5296
+ help: ["Run `pievo new --json '{\"name\":\"Daily check\",\"schedule\":{\"mode\":\"cron\",\"cron\":\"0 8 * * *\",\"timezone\":\"UTC\",\"overlap\":\"skip\"},\"workdir\":\"<path>\",\"agent\":\"claude-code\",\"prompt\":\"Check the project.\"}'` to create a loop", "Run `pievo new --json '{...}' --dry-run` to validate without creating"]
5340
5297
  },
5341
5298
  loops: {
5342
5299
  syntax: "loops [--fields a,b] [--json]",
@@ -5384,7 +5341,7 @@ var DEVICE_VERB_HELP = {
5384
5341
  help: ["Run `pievo file get <id> MEMORY.md` to read the loop's memory", "An edit made while a run is executing wins over that run's version of the file"]
5385
5342
  },
5386
5343
  log: {
5387
- syntax: "log [<id>] [--limit 1..20] [--since ISO] [--until ISO] [--status keep|no-change|block] [--phase done|error|canceled] [--run <index|UUID> [--diff]] [--json]",
5344
+ syntax: "log [<id>] [--limit 1..20] [--since ISO] [--until ISO] [--status ok|block] [--phase done|error|canceled] [--run <index|UUID> [--diff]] [--json]",
5388
5345
  summary: "bounded terminal history or one detailed run",
5389
5346
  help: ["Run `pievo log <id> --json` for structured history", "Run `pievo log <id> --run 12 --diff` for bounded run detail and artifact diff"]
5390
5347
  }
@@ -5421,7 +5378,6 @@ function renderShowText(loop, env, totalRuns, lastRun, opts = {}) {
5421
5378
  ["model", env.model == null ? { raw: "default" } : env.model],
5422
5379
  ["reasoningEffort", env.reasoningEffort == null ? { raw: "default" } : env.reasoningEffort],
5423
5380
  ["prompt", contentField(loop.prompt, full)],
5424
- ["statusDefinitions", { raw: JSON.stringify(env.statusDefinitions) }],
5425
5381
  ["artifacts", { raw: JSON.stringify(env.artifacts) }],
5426
5382
  ["enabled", env.enabled]
5427
5383
  ]);
@@ -5648,14 +5604,8 @@ var Scheduler = class {
5648
5604
  function msg(err) {
5649
5605
  return err instanceof Error ? err.message : String(err);
5650
5606
  }
5651
- /**
5652
- * Server boot — idempotent one-time init of the in-process backend: apply
5653
- * migrations, wire the machine gateway's dispatcher into the Scheduler, start
5654
- * the Scheduler + an offline-sweep interval. Guarded on `globalThis` so it
5655
- * survives dev HMR and runs at most once per process. Call `ensureServer()`
5656
- * from any server-side entry (the standalone machine server / TanStack server
5657
- * fns); the first call boots, the rest share the same instance.
5658
- */
5607
+ /** One-time, idempotent in-process backend boot (migrations, scheduler, sweep and
5608
+ * storage-maintenance intervals). Guarded on `globalThis` to survive dev HMR. */
5659
5609
  var boot_exports = /* @__PURE__ */ __exportAll({
5660
5610
  ensureServer: () => ensureServer,
5661
5611
  getCliGateway: () => getCliGateway,
@@ -5713,4 +5663,4 @@ async function getCliGateway() {
5713
5663
  return (await ensureServer()).cliGateway;
5714
5664
  }
5715
5665
  //#endregion
5716
- export { listRuns as A, requestDeleteLoop as B, getRun as C, listArtifacts as D, isOutdated as E, machinePresence as F, tryDeleteLoop as G, sha256 as H, mintDeviceToken as I, validateLoopTags as J, updateLoop as K, pauseLoop as L, loopTags_CnVLi70h_exports as M, loopsForMachine as N, listLoops as O, machineIdFromToken as P, recentLoopUsage as R, getMachine as S, hasRunningRun as T, startLoop as U, requestRunCancel as V, stopLoop as W, createMachine as _, canonicalLoopSummary as a, getArtifactFile as b, getLoopFiles as c, validOptionalPollString as d, validPollCurrentRun as f, countRuns as g, CODING_AGENTS as h, canonicalLoopEnvelope as i, listRunsBefore as j, listMachines as k, readByteBody as l, validateLoopEdit as m, POLL_HTTP_BODY_FIELDS as n, daemonNeedsUpdate as o, validPollWireId as p, updateMachine as q, boot_DmpKtb0A_exports as r, ensureServer as s, MIN_DAEMON_VERSION as t, readJsonBody as u, forceDeleteLoop as v, hasPendingRun as w, getLoop as x, forceDeleteMachine as y, reconciliationStatesForRuns as z };
5666
+ export { listRuns as A, reconciliationStatesForRuns as B, getRun as C, listArtifacts as D, isOutdated as E, machineIdFromToken as F, stopLoop as G, requestRunCancel as H, machinePresence as I, updateMachine as J, tryDeleteLoop as K, mintDeviceToken as L, loopFileNames as M, loopTags_CI_aMcqh_exports as N, listLoops as O, loopsForMachine as P, pauseLoop as R, getMachine as S, hasRunningRun as T, sha256 as U, requestDeleteLoop as V, startLoop as W, validateLoopTags as Y, createMachine as _, canonicalLoopSummary as a, getArtifactFile as b, getLoopFiles as c, validOptionalPollString as d, validPollCurrentRun as f, countRuns as g, CODING_AGENTS as h, canonicalLoopEnvelope as i, listRunsBefore as j, listMachines as k, readByteBody as l, validateLoopEdit as m, POLL_HTTP_BODY_FIELDS as n, daemonNeedsUpdate as o, validPollWireId as p, updateLoop as q, boot_jtVCf_pN_exports as r, ensureServer as s, MIN_DAEMON_VERSION as t, readJsonBody as u, forceDeleteLoop as v, hasPendingRun as w, getLoop as x, forceDeleteMachine as y, recentLoopUsage as z };