@kky42/pievo-server 2.4.8 → 2.4.10

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 (65) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{MachinesModal-ClNm1IAU.js → MachinesModal-ByHejech.js} +1 -1
  3. package/.output/public/assets/app-IrlFuBXa.css +2 -0
  4. package/.output/public/assets/{index-DM5pYU2E.js → index-ia26HsP1.js} +3 -3
  5. package/.output/public/assets/{invite._token-CjcTZgvV.js → invite._token-tDfe3p0Z.js} +1 -1
  6. package/.output/public/assets/{loops._loopId-1WUV1NQv.js → loops._loopId-CuYq_XgT.js} +3 -3
  7. package/.output/public/assets/{loops._loopId_.runs._runId-BgUc4GIy.js → loops._loopId_.runs._runId-mOaF51Qu.js} +1 -1
  8. package/.output/public/assets/{routes-DTmJ-YqP.js → routes-Dbl_3lt_.js} +1 -1
  9. package/.output/public/assets/{t._teamId-BmUhbnOU.js → t._teamId-BVsl0EGm.js} +1 -1
  10. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  11. package/.output/server/_ssr/{DashboardView-DTfEhixr.mjs → DashboardView-BMHMrOfC.mjs} +15 -8
  12. package/.output/server/_ssr/{MachinesModal-D54Uza6A.mjs → MachinesModal-BhRw7oOx.mjs} +22 -17
  13. package/.output/server/_ssr/{SignIn-C2JkrSfV.mjs → SignIn-BhoZIOUp.mjs} +3 -3
  14. package/.output/server/_ssr/{SocialLinks-dPCmZWvC.mjs → SocialLinks-DVCLmql0.mjs} +2 -11
  15. package/.output/server/_ssr/{actionUi-DCNmrAjw.mjs → actionUi-Cf6DuEC2.mjs} +2 -2
  16. package/.output/server/_ssr/{artifactFiles-DBu2yk9a.mjs → artifactFiles-ChuJ4PzC.mjs} +3 -4
  17. package/.output/server/_ssr/{artifactKind-CJMjNtSC.mjs → artifactKind-DXumOv7c.mjs} +3 -7
  18. package/.output/server/_ssr/{artifacts-8JYBE5zx.mjs → artifacts-DOLHJHcd.mjs} +1 -1
  19. package/.output/server/_ssr/{auth-b1o4lrGO.mjs → auth-LwPp_iim.mjs} +9 -13
  20. package/.output/server/_ssr/{boot-CEPRQU5M.mjs → boot-BIxtZHU-.mjs} +67 -212
  21. package/.output/server/_ssr/{boot-dZdGfkZm.mjs → boot-D8cj4uZj.mjs} +1 -1
  22. package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-D2QuIuwp.mjs → bun-sqlite-dialect-BW9W1_Ps-CHMDkKqA.mjs} +2 -2
  23. package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-CdT8qARs.mjs → d1-sqlite-dialect-BLC8LXE6-DYum6qky.mjs} +2 -2
  24. package/.output/server/_ssr/db-BphSJs66.mjs +2 -0
  25. package/.output/server/_ssr/{dist-E-fSrrAE.mjs → dist-bWqs5sTy.mjs} +2 -2
  26. package/.output/server/_ssr/{factory-CzRRgvct.mjs → factory-pBwrjVTI.mjs} +2 -2
  27. package/.output/server/_ssr/{invite._token-DDDd3vNf.mjs → invite._token-CPSvdDox.mjs} +4 -4
  28. package/.output/server/_ssr/{invite._token-ZthOtmu9.mjs → invite._token-CRBsTSTZ.mjs} +6 -6
  29. package/.output/server/_ssr/{invite._token-CGzJGIfm.mjs → invite._token-CaTLt92a.mjs} +2 -2
  30. package/.output/server/_ssr/{kysely-adapter-cB-XpVsU.mjs → kysely-adapter-DMf5pFTf.mjs} +1 -1
  31. package/.output/server/_ssr/{lifecycleUi-DOr6Af13.mjs → lifecycleUi-DauKavgx.mjs} +1 -2
  32. package/.output/server/_ssr/logger-EinA2J6y.mjs +2 -0
  33. package/.output/server/_ssr/{loopApi-DxDYnPI7.mjs → loopApi-Im6JSK0p.mjs} +10 -11
  34. package/.output/server/_ssr/{loopProjection-CeH1HELf.mjs → loopProjection-DIpGR6rp.mjs} +2 -4
  35. package/.output/server/_ssr/{loops._loopId-BTAKWGvT.mjs → loops._loopId-B5iPseDG.mjs} +13 -27
  36. package/.output/server/_ssr/{loops._loopId_.runs._runId-BGl_4tUK.mjs → loops._loopId_.runs._runId-BA-P6sGA.mjs} +6 -44
  37. package/.output/server/_ssr/{loops._loopId_.runs._runId-BRzoEUe1.mjs → loops._loopId_.runs._runId-DVtG0xYb.mjs} +3 -3
  38. package/.output/server/_ssr/{machineFns-Bmh_4hnx.mjs → machineFns-Bb9KNXun.mjs} +3 -5
  39. package/.output/server/_ssr/{node-sqlite-dialect-C8O4zOiM.mjs → node-sqlite-dialect-Dho9YjXb.mjs} +2 -2
  40. package/.output/server/_ssr/{router-Vpcd2SDx.mjs → router-DPz3uoUr.mjs} +22 -30
  41. package/.output/server/_ssr/{routes-BczmJLuq.mjs → routes-B9zD2h4Y.mjs} +5 -7
  42. package/.output/server/_ssr/{routes-DBCYbmLW.mjs → routes-BuILEmHs.mjs} +5 -5
  43. package/.output/server/_ssr/{routes-D5GHah06.mjs → routes-FO0MFfYK.mjs} +2 -2
  44. package/.output/server/_ssr/runDiff-BLdot9mi.mjs +2 -0
  45. package/.output/server/_ssr/ssr.mjs +39 -39
  46. package/.output/server/_ssr/store-CFCp_ggC.mjs +2 -0
  47. package/.output/server/_ssr/{t._teamId-COKCNxP7.mjs → t._teamId-1t5wOXrV.mjs} +5 -5
  48. package/.output/server/_ssr/{t._teamId-D0Y3DqcE.mjs → t._teamId-A37pQ8Vp.mjs} +5 -5
  49. package/.output/server/_ssr/{t._teamId-CrZ7EkUL.mjs → t._teamId-D1BTwhWf.mjs} +2 -2
  50. package/.output/server/_ssr/{teamFns-DctV7gTn.mjs → teamFns-DfqtuBW2.mjs} +3 -7
  51. package/.output/server/_ssr/{teamFns-BMR5TRJw.mjs → teamFns-cI_tsiCv.mjs} +2 -3
  52. package/.output/server/_ssr/{tokens-BP8ltV6J.mjs → tokens-CUz4LDLm.mjs} +1 -1
  53. package/.output/server/_ssr/{ui-BEPYdOmJ.mjs → ui-DGz-q9MT.mjs} +1 -22
  54. package/.output/server/{_tanstack-start-manifest_v-CevOnXtc.mjs → _tanstack-start-manifest_v-CojB4Veh.mjs} +9 -9
  55. package/.output/server/drizzle/0001_add_pi_agent.sql +9 -0
  56. package/.output/server/drizzle/meta/_journal.json +7 -0
  57. package/.output/server/index.mjs +61 -61
  58. package/drizzle/0001_add_pi_agent.sql +9 -0
  59. package/drizzle/meta/_journal.json +7 -0
  60. package/package.json +1 -1
  61. package/.output/public/assets/app-CuZ0I3Mb.css +0 -2
  62. package/.output/server/_ssr/db-CJUmGnnY.mjs +0 -2
  63. package/.output/server/_ssr/logger-Co0_q7fy.mjs +0 -2
  64. package/.output/server/_ssr/runDiff-Ce1DhI7z.mjs +0 -2
  65. package/.output/server/_ssr/store-BpbKfvFY.mjs +0 -2
@@ -11,7 +11,7 @@ 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/boot-CEPRQU5M.js
14
+ //#region node_modules/.nitro/vite/services/ssr/assets/boot-BIxtZHU-.js
15
15
  var import_pino = /* @__PURE__ */ __toESM(require_pino());
16
16
  var import_pino_pretty = /* @__PURE__ */ __toESM(require_pino_pretty());
17
17
  function defineErrorCodes(codes) {
@@ -1268,7 +1268,6 @@ function r2Config() {
1268
1268
  * a deploy can tune them without a rebuild. Each has a safe, generous default —
1269
1269
  * the bias is "keep storage bounded without ever surprising a healthy loop".
1270
1270
  */
1271
- /** A positive env integer, or `fallback` when unset / unparseable / non-positive. */
1272
1271
  function posIntEnv(name, fallback) {
1273
1272
  const raw = process.env[name]?.trim();
1274
1273
  if (!raw) return fallback;
@@ -1321,11 +1320,9 @@ function dbWatchdogEnabled() {
1321
1320
  if (process.env.VITEST || false) return false;
1322
1321
  return Boolean(databaseUrl());
1323
1322
  }
1324
- /** How often the DB watchdog pings `select 1`. Default 20s. */
1325
1323
  function dbWatchdogIntervalMs() {
1326
1324
  return posIntEnv("PIEVO_DB_WATCHDOG_INTERVAL_MS", 2e4);
1327
1325
  }
1328
- /** Hard per-ping deadline; a ping that exceeds it counts as a failure. Default 5s. */
1329
1326
  function dbWatchdogTimeoutMs() {
1330
1327
  return posIntEnv("PIEVO_DB_WATCHDOG_TIMEOUT_MS", 5e3);
1331
1328
  }
@@ -1338,8 +1335,6 @@ function dbWatchdogFailureThreshold() {
1338
1335
  return posIntEnv("PIEVO_DB_WATCHDOG_FAILURES", 3);
1339
1336
  }
1340
1337
  /**
1341
- * Central logger (pino + pino-pretty).
1342
- *
1343
1338
  * Uses pino-pretty as a *synchronous* destination stream (not a worker-thread
1344
1339
  * transport) so logs flush before process.exit() in the one-shot CLIs. Writes to
1345
1340
  * stderr (fd 2) to keep stdout clean for QR codes and human CLI output.
@@ -1405,7 +1400,7 @@ function poolOptionsFor(url, transactionPooler = isTransactionPooler(url)) {
1405
1400
  };
1406
1401
  }
1407
1402
  /** The coding agent a loop is bound to AND executed with (BYOA on the owner's
1408
- * machine): `claude-code` → Claude Code, `codex` → `codex exec`.
1403
+ * machine): `claude-code` → Claude Code, `codex` → `codex exec`, `pi` → Pi.
1409
1404
  * Non-Claude agents may still have thinner daemon telemetry until stream
1410
1405
  * integration lands; execution itself is real for every value.
1411
1406
  *
@@ -1415,9 +1410,11 @@ function poolOptionsFor(url, transactionPooler = isTransactionPooler(url)) {
1415
1410
  * web agent `<select>` (LoopForm). So widening the set is a one-line edit HERE with
1416
1411
  * no other server change (the daemon's own enum in `packages/daemon/src/create.ts`
1417
1412
  * is a separate package, widened alongside). */
1418
- var CODING_AGENTS = ["claude-code", "codex"];
1419
- /** Coerce an unknown value to a known `CodingAgent`, or null when unrecognized.
1420
- * Canonical loop validation and the web agent select share this enum source. */
1413
+ var CODING_AGENTS = [
1414
+ "claude-code",
1415
+ "codex",
1416
+ "pi"
1417
+ ];
1421
1418
  function coerceCodingAgent(value) {
1422
1419
  return typeof value === "string" && CODING_AGENTS.includes(value) ? value : null;
1423
1420
  }
@@ -1434,23 +1431,16 @@ function coerceCodingAgent(value) {
1434
1431
  * columns use `jsonb().$type<>()`; booleans use native `boolean()`.
1435
1432
  */
1436
1433
  var machines = pgTable("machines", {
1437
- /** m-sha256(deviceToken)[:16] */
1438
1434
  id: text("id").primaryKey(),
1439
- /** Owning user (Better Auth user.id) — creator attribution. */
1440
1435
  userId: text("user_id").notNull(),
1441
- /** Owning team — the scope machines and loops are listed by. */
1442
1436
  teamId: text("team_id").notNull(),
1443
- /** Friendly name (set AFTER the daemon connects; empty string = pending/unnamed). */
1444
1437
  name: text("name").notNull(),
1445
- /** Daemon-reported machine identity (captured on first connect). */
1446
1438
  hostname: text("hostname"),
1447
1439
  platform: text("platform"),
1448
1440
  arch: text("arch"),
1449
- /** Daemon package version reported on poll. Drives the web upgrade hint. */
1450
1441
  daemonVersion: text("daemon_version"),
1451
1442
  /** Breaking machine protocol last observed on poll. */
1452
1443
  daemonProtocol: integer("daemon_protocol"),
1453
- /** Hash of the device token (machine identity derives from the token). */
1454
1444
  tokenHash: text("token_hash").notNull(),
1455
1445
  /**
1456
1446
  * Plaintext device token. Stored so the UI can re-show the connect command
@@ -1458,67 +1448,51 @@ var machines = pgTable("machines", {
1458
1448
  * for a self-hosted team tool where the DB is already the trust root).
1459
1449
  */
1460
1450
  token: text("token"),
1461
- /** Workdir allowlist the daemon enforces as cwd jail; null/[] = unrestricted. */
1462
1451
  roots: jsonb("roots").$type(),
1463
- /** Last WS contact (ISO). */
1464
1452
  lastSeen: text("last_seen"),
1465
- /** Live WS connection state. */
1466
1453
  online: boolean("online").notNull().default(false),
1467
1454
  createdAt: text("created_at").notNull()
1468
1455
  }, (t) => [index("machines_user_idx").on(t.userId), index("machines_team_idx").on(t.teamId)]);
1469
1456
  var loops = pgTable("loops", {
1470
1457
  id: text("id").primaryKey(),
1471
- /** Owning user (creator attribution). */
1472
1458
  userId: text("user_id").notNull(),
1473
- /** Owning team — the scope loops are listed and authorized by. */
1474
1459
  teamId: text("team_id").notNull(),
1475
1460
  /** Execution machine (set at creation; no cross-machine fallback). */
1476
1461
  machineId: text("machine_id").notNull(),
1477
1462
  name: text("name").notNull(),
1478
- /** The user's complete runtime prompt. Canonical writes always provide it. */
1479
1463
  prompt: text("prompt").notNull(),
1480
- /** User-authored meanings appended to every delivered prompt. */
1481
1464
  statusKeep: text("status_keep").notNull(),
1482
1465
  statusNoChange: text("status_no_change").notNull(),
1483
1466
  statusBlock: text("status_block").notNull(),
1484
1467
  /** Exact workdir-relative artifact paths collected by the daemon after provider exit. */
1485
1468
  artifacts: jsonb("artifacts").$type().notNull().default([]),
1486
1469
  cron: text("cron").notNull(),
1487
- /** Cadence facts stay denormalized internally; external writes use LoopSchedule. */
1488
1470
  scheduleMode: text("schedule_mode", { enum: ["cron", "continuous"] }).notNull(),
1489
1471
  /** Cron-only overlap behavior. Continuous runs never overlap. */
1490
1472
  cronOverlap: text("cron_overlap", { enum: ["skip", "queue-one"] }).notNull(),
1491
- /** Delay used when a done/error terminal restores continuous cadence. */
1492
1473
  continuousDelayMinutes: integer("continuous_delay_minutes").notNull(),
1493
- /** IANA tz for cron rows; null for continuous rows. */
1494
1474
  timezone: text("timezone"),
1495
- /** Absolute project dir ON THE MACHINE the agent runs in (cwd). */
1496
1475
  workdir: text("workdir").notNull(),
1497
1476
  /** Durable Stop-before-delete marker. A deleting loop is never claimable. */
1498
1477
  deleteRequestedAt: timestamp("delete_requested_at", {
1499
1478
  withTimezone: true,
1500
1479
  mode: "string"
1501
1480
  }),
1502
- /** Diagnostic annotation for a paused loop. */
1503
1481
  pauseCause: jsonb("pause_cause").$type(),
1504
- /** Optional provider model id. Null delegates model selection to the coding-agent CLI. */
1505
1482
  model: text("model"),
1506
- /** Optional provider reasoning-effort value. Null delegates to the coding-agent CLI. */
1507
1483
  reasoningEffort: text("reasoning_effort"),
1508
1484
  /** Coding agent this loop is BOUND TO and EXECUTED with: the daemon's
1509
1485
  * `buildAgentSpawn` branches on this value (`claude-code` → claude,
1510
- * `codex` → `codex exec`). Measured from the creating CLI's env when
1486
+ * `codex` → `codex exec`, `pi` → Pi print mode). Measured from the creating CLI's env when
1511
1487
  * detectable, else the declared/selected value. The text column carries
1512
1488
  * both a TypeScript enum and a database CHECK constraint. */
1513
1489
  agent: text("agent", { enum: CODING_AGENTS }).notNull(),
1514
1490
  enabled: boolean("enabled").notNull().default(true),
1515
- /** One-shot override, independent of the recurring cadence (ISO). */
1516
1491
  nextRunAt: text("next_run_at"),
1517
1492
  /** The next recurring cadence occurrence not yet materialized as a run. Cron
1518
1493
  * advances this fact after each due occurrence; continuous clears it on due
1519
1494
  * and restores it from a successful/error terminal. */
1520
1495
  nextCadenceAt: text("next_cadence_at"),
1521
- /** Last history number assigned under this loop's row lock. */
1522
1496
  lastRunIndex: integer("last_run_index").notNull().default(0),
1523
1497
  createdAt: text("created_at").notNull(),
1524
1498
  updatedAt: text("updated_at").notNull()
@@ -1527,7 +1501,7 @@ var loops = pgTable("loops", {
1527
1501
  index("loops_team_idx").on(t.teamId),
1528
1502
  index("loops_machine_idx").on(t.machineId),
1529
1503
  index("delete_requested_loops").on(t.deleteRequestedAt).where(sql`${t.deleteRequestedAt} IS NOT NULL`),
1530
- check("loops_agent_check", sql`${t.agent} IN ('claude-code', 'codex')`),
1504
+ check("loops_agent_check", sql`${t.agent} IN ('claude-code', 'codex', 'pi')`),
1531
1505
  check("loops_schedule_mode_check", sql`${t.scheduleMode} IN ('cron', 'continuous')`),
1532
1506
  check("loops_cron_overlap_check", sql`${t.cronOverlap} IN ('skip', 'queue-one')`)
1533
1507
  ]);
@@ -1535,11 +1509,9 @@ var runs = pgTable("runs", {
1535
1509
  id: text("id").primaryKey(),
1536
1510
  loopId: text("loop_id").notNull(),
1537
1511
  machineId: text("machine_id").notNull(),
1538
- /** Execution profile captured atomically at claim. Pending/unclaimed rows are null. */
1539
1512
  agent: text("agent", { enum: CODING_AGENTS }),
1540
1513
  model: text("model"),
1541
1514
  reasoningEffort: text("reasoning_effort"),
1542
- /** 1-based execution/terminal order, assigned at claim or unclaimed terminalization. */
1543
1515
  runIndex: integer("run_index"),
1544
1516
  phase: text("phase", { enum: [
1545
1517
  "pending",
@@ -1551,12 +1523,9 @@ var runs = pgTable("runs", {
1551
1523
  /** Durable queue authority. It only promotes system→owner; diagnostic trigger
1552
1524
  * reasons, if added later, must never drive lifecycle state. */
1553
1525
  requestedBy: text("requested_by", { enum: ["owner", "system"] }).notNull().default("system"),
1554
- /** Current phase/event timestamp. */
1555
1526
  ts: text("ts").notNull(),
1556
- /** Immutable queue/history age anchor plus mutation timestamp. */
1557
1527
  createdAt: text("created_at").notNull().default(sql`CURRENT_TIMESTAMP::text`),
1558
1528
  updatedAt: text("updated_at").notNull().default(sql`CURRENT_TIMESTAMP::text`),
1559
- /** Canonical report status. */
1560
1529
  status: text("status", { enum: [
1561
1530
  "keep",
1562
1531
  "no-change",
@@ -1567,18 +1536,14 @@ var runs = pgTable("runs", {
1567
1536
  exitCode: integer("exit_code"),
1568
1537
  finalText: text("final_text"),
1569
1538
  error: text("error"),
1570
- /** Provider session id on the machine, retained for owner-side continuation. */
1571
1539
  sessionId: text("session_id"),
1572
- /** Provider-neutral token usage. Dollar cost is deliberately not stored. */
1573
1540
  usage: jsonb("usage").$type(),
1574
1541
  /** Monotonic cancellation intent. Running becomes canceled only on daemon report. */
1575
1542
  cancelRequestedAt: timestamp("cancel_requested_at", {
1576
1543
  withTimezone: true,
1577
1544
  mode: "string"
1578
1545
  }),
1579
- /** Last poll where this machine declared the run active. Timeout authority. */
1580
1546
  heartbeatAt: text("heartbeat_at"),
1581
- /** Latest rejected terminal-report diagnosis, if any. */
1582
1547
  reportIncident: jsonb("report_incident").$type()
1583
1548
  }, (t) => [
1584
1549
  index("runs_loop_idx").on(t.loopId),
@@ -1589,13 +1554,12 @@ var runs = pgTable("runs", {
1589
1554
  index("runs_loop_terminal_history_idx").on(t.loopId, t.runIndex).where(sql`${t.runIndex} IS NOT NULL AND ${t.phase} IN ('done', 'error', 'canceled')`),
1590
1555
  uniqueIndex("runs_loop_pending_idx").on(t.loopId).where(sql`${t.phase} = 'pending'`),
1591
1556
  uniqueIndex("one_running_run_per_loop").on(t.loopId).where(sql`${t.phase} = 'running'`),
1592
- check("runs_agent_check", sql`${t.agent} IS NULL OR ${t.agent} IN ('claude-code', 'codex')`),
1557
+ check("runs_agent_check", sql`${t.agent} IS NULL OR ${t.agent} IN ('claude-code', 'codex', 'pi')`),
1593
1558
  check("runs_phase_check", sql`${t.phase} IN ('pending', 'running', 'done', 'error', 'canceled')`),
1594
1559
  check("runs_requested_by_check", sql`${t.requestedBy} IN ('owner', 'system')`),
1595
1560
  check("runs_status_check", sql`${t.status} IS NULL OR ${t.status} IN ('keep', 'no-change', 'block')`)
1596
1561
  ]);
1597
1562
  var runLeases = pgTable("run_leases", {
1598
- /** sha256 hex of the full `rk_…` wire token. */
1599
1563
  tokenHash: text("token_hash").primaryKey(),
1600
1564
  runId: text("run_id").notNull(),
1601
1565
  loopId: text("loop_id").notNull(),
@@ -1645,11 +1609,8 @@ var terminalReportIncidents = pgTable("terminal_report_incidents", {
1645
1609
  check("terminal_report_incidents_disposition_check", sql`${t.disposition} IN ('run-error', 'telemetry-rejected')`)
1646
1610
  ]);
1647
1611
  var connectKeys = pgTable("connect_keys", {
1648
- /** m-sha256(connectKey)[:16] — the machine id this key self-registers as. */
1649
1612
  machineId: text("machine_id").primaryKey(),
1650
- /** The user who minted the key (the authenticated dashboard session). */
1651
1613
  userId: text("user_id").notNull(),
1652
- /** The validated active team the key was minted under. */
1653
1614
  teamId: text("team_id").notNull(),
1654
1615
  /** Mint time (ISO) — drives the TTL. */
1655
1616
  mintedAt: text("minted_at").notNull()
@@ -1657,7 +1618,6 @@ var connectKeys = pgTable("connect_keys", {
1657
1618
  var teams = pgTable("teams", {
1658
1619
  id: text("id").primaryKey(),
1659
1620
  name: text("name").notNull(),
1660
- /** The user whose personal team this is (null for the open-mode shared team). */
1661
1621
  ownerUserId: text("owner_user_id"),
1662
1622
  createdAt: text("created_at").notNull()
1663
1623
  });
@@ -1673,18 +1633,13 @@ var teamMembers = pgTable("team_members", {
1673
1633
  check("team_members_role_check", sql`${t.role} IN ('owner', 'member')`)
1674
1634
  ]);
1675
1635
  var teamInvites = pgTable("team_invites", {
1676
- /** The wire token the recipient presents (`/invite/<token>`). */
1677
1636
  token: text("token").primaryKey(),
1678
1637
  teamId: text("team_id").notNull(),
1679
- /** Role granted on redeem (capped at the inviter's role at mint time). */
1680
1638
  role: text("role", { enum: ["owner", "member"] }).notNull().default("member"),
1681
- /** The owner who minted the invite (attribution). */
1682
1639
  invitedByUserId: text("invited_by_user_id").notNull(),
1683
- /** When the invite lapses (ISO). A redeem past this is refused. */
1684
1640
  expiresAt: text("expires_at").notNull(),
1685
1641
  /** Single-use stamp: set on redeem so the same link can't be reused. */
1686
1642
  redeemedAt: text("redeemed_at"),
1687
- /** The user who redeemed it (attribution). */
1688
1643
  redeemedByUserId: text("redeemed_by_user_id"),
1689
1644
  createdAt: text("created_at").notNull()
1690
1645
  }, (t) => [index("team_invites_team_idx").on(t.teamId), check("team_invites_role_check", sql`${t.role} IN ('owner', 'member')`)]);
@@ -1693,7 +1648,6 @@ var teamInvites = pgTable("team_invites", {
1693
1648
  * the byte store under the hash; this row records verified byte presence and size.
1694
1649
  */
1695
1650
  var blobs = pgTable("blobs", {
1696
- /** sha256 hex of the bytes (the byte-store key). */
1697
1651
  hash: text("hash").primaryKey(),
1698
1652
  size: integer("size").notNull(),
1699
1653
  createdAt: text("created_at").notNull()
@@ -1709,14 +1663,12 @@ var artifactFiles = pgTable("artifact_files", {
1709
1663
  loopId: text("loop_id").notNull(),
1710
1664
  /** Normalized workdir-relative path (never absolute or escaping the workdir). */
1711
1665
  path: text("path").notNull(),
1712
- /** → blobs.hash. Null when deleted or oversize (no bytes stored). */
1713
1666
  hash: text("hash"),
1714
1667
  size: integer("size"),
1715
1668
  /** Bytes contain a NUL (set even for oversize metadata-only files). */
1716
1669
  binary: boolean("binary").notNull().default(false),
1717
1670
  /** File exceeds the per-file byte cap → metadata-only (path + size), no blob. */
1718
1671
  oversize: boolean("oversize").notNull().default(false),
1719
- /** Tombstone: the file vanished from the loop's manifest (kept for future diffs). */
1720
1672
  deleted: boolean("deleted").notNull().default(false),
1721
1673
  updatedAt: text("updated_at").notNull()
1722
1674
  }, (t) => [
@@ -1943,7 +1895,6 @@ function newLoopId() {
1943
1895
  function nowIso$1() {
1944
1896
  return (/* @__PURE__ */ new Date()).toISOString();
1945
1897
  }
1946
- /** First cron occurrence strictly after `after`, interpreted in the loop's zone. */
1947
1898
  function nextCronAt(cron, timezone, after) {
1948
1899
  if (!timezone) throw new Error("invariant: cron schedule has no timezone");
1949
1900
  const probe = new E(cron, {
@@ -1969,8 +1920,6 @@ async function listLoops(teamId) {
1969
1920
  const q = db.select().from(loops);
1970
1921
  return teamId ? await q.where(eq(loops.teamId, teamId)) : await q;
1971
1922
  }
1972
- /** Count a team's loops without materializing their (large) rows — the
1973
- * delete-block guard + settings-detail badge only need the tally. */
1974
1923
  async function countLoopsForTeam(teamId) {
1975
1924
  const r = (await db.select({ n: sql`count(*)` }).from(loops).where(eq(loops.teamId, teamId)))[0];
1976
1925
  return Number(r?.n ?? 0);
@@ -1978,7 +1927,6 @@ async function countLoopsForTeam(teamId) {
1978
1927
  async function getLoop(id) {
1979
1928
  return (await db.select().from(loops).where(eq(loops.id, id)))[0];
1980
1929
  }
1981
- /** Loops bound to a machine — used by machine cascade authorization and UI counts. */
1982
1930
  async function loopsForMachine(machineId) {
1983
1931
  return db.select().from(loops).where(eq(loops.machineId, machineId));
1984
1932
  }
@@ -2080,7 +2028,6 @@ async function updateLoopTx(tx, current, patch, at) {
2080
2028
  }
2081
2029
  return loop;
2082
2030
  }
2083
- /** Partial owner/system update under the loop lifecycle lock. */
2084
2031
  async function updateLoop(id, patch) {
2085
2032
  return db.transaction(async (tx) => {
2086
2033
  const current = (await tx.select().from(loops).where(eq(loops.id, id)).for("update"))[0];
@@ -2112,11 +2059,9 @@ async function pauseLoopState(id) {
2112
2059
  };
2113
2060
  });
2114
2061
  }
2115
- /** Pause a loop and return its updated row. */
2116
2062
  async function pauseLoop(id) {
2117
2063
  return (await pauseLoopState(id))?.loop;
2118
2064
  }
2119
- /** Start a paused loop unless deletion has begun. */
2120
2065
  async function startLoop(id) {
2121
2066
  return db.transaction(async (tx) => {
2122
2067
  const current = (await tx.select().from(loops).where(eq(loops.id, id)).for("update"))[0];
@@ -2146,14 +2091,12 @@ async function stopLoopTx(tx, current, at) {
2146
2091
  }).where(and(eq(runs.id, running.id), eq(runs.phase, "running"), isNull(runs.cancelRequestedAt))).returning())[0] ?? running
2147
2092
  };
2148
2093
  }
2149
- /** Stop is one loop-locked write: Pause + cancel all queue + mark current run. */
2150
2094
  async function stopLoop(id) {
2151
2095
  return db.transaction(async (tx) => {
2152
2096
  const current = (await tx.select().from(loops).where(eq(loops.id, id)).for("update"))[0];
2153
2097
  return current ? stopLoopTx(tx, current, nowIso$1()) : void 0;
2154
2098
  });
2155
2099
  }
2156
- /** Stop-run preserves loop lifecycle; running cancellation waits for daemon proof. */
2157
2100
  async function requestRunCancel(loopId, runId) {
2158
2101
  return db.transaction(async (tx) => {
2159
2102
  if (!(await tx.select({ id: loops.id }).from(loops).where(eq(loops.id, loopId)).for("update"))[0]) return void 0;
@@ -2338,7 +2281,6 @@ async function runningRunForLoop(loopId) {
2338
2281
  async function getReportReceipt(reportId) {
2339
2282
  return (await db.select().from(runReportReceipts).where(eq(runReportReceipts.reportId, reportId)))[0];
2340
2283
  }
2341
- /** Deterministic primary key for exact rejected-payload replay evidence. */
2342
2284
  function terminalIncidentReceiptId(reportId, payloadDigest) {
2343
2285
  return createHash("sha256").update(reportId + payloadDigest).digest("hex");
2344
2286
  }
@@ -2407,7 +2349,6 @@ async function refreshRunHeartbeats(machineId, runIds, at, staleBefore) {
2407
2349
  updatedAt: at
2408
2350
  }).where(and(eq(runs.machineId, machineId), eq(runs.phase, "running"), inArray(runs.id, runIds), or(isNull(runs.heartbeatAt), lt(runs.heartbeatAt, staleBefore)))).returning({ id: runs.id })).length;
2409
2351
  }
2410
- /** Validate the report-only authority for one currently running run. */
2411
2352
  async function activeRunForReportTx(tx, loopId, runId, leaseTokenHash) {
2412
2353
  const lease = (await tx.select().from(runLeases).where(and(eq(runLeases.tokenHash, leaseTokenHash), eq(runLeases.runId, runId), eq(runLeases.loopId, loopId), eq(runLeases.state, "active"))).limit(1).for("update"))[0];
2413
2354
  if (!lease) return { state: "invalid-lease" };
@@ -2792,11 +2733,9 @@ async function claimReadyRunForMachine(machineId, at = nowIso$1(), excludeRunIds
2792
2733
  };
2793
2734
  });
2794
2735
  }
2795
- /** Newest-last run history for a loop (chronological), capped. */
2796
2736
  async function listRuns(loopId, limit = 30) {
2797
2737
  return (await db.select().from(runs).where(eq(runs.loopId, loopId)).orderBy(desc(runs.ts)).limit(limit)).reverse();
2798
2738
  }
2799
- /** Live reconciliation projection for dashboard/run-detail reads. */
2800
2739
  async function reconciliationStatesForRuns(loopId, runIds, at = nowIso$1()) {
2801
2740
  if (!runIds.length) return /* @__PURE__ */ new Map();
2802
2741
  const rows = await db.select({
@@ -2818,7 +2757,6 @@ async function countRuns(loopId) {
2818
2757
  const r = (await db.select({ n: sql`count(*)` }).from(runs).where(eq(runs.loopId, loopId)))[0];
2819
2758
  return Number(r?.n ?? 0);
2820
2759
  }
2821
- /** Run count and reported input + output tokens in a bounded recent window. */
2822
2760
  async function recentLoopUsage(loopId, since) {
2823
2761
  const row = (await db.select({
2824
2762
  runCount: sql`count(*)`,
@@ -2832,7 +2770,6 @@ async function recentLoopUsage(loopId, since) {
2832
2770
  tokenCount: Number(row?.tokenCount ?? 0)
2833
2771
  };
2834
2772
  }
2835
- /** Open runs (pending/running) — used by the timeout-reclaim sweep. */
2836
2773
  async function openRuns() {
2837
2774
  return db.select().from(runs).where(inArray(runs.phase, ["pending", "running"]));
2838
2775
  }
@@ -2864,8 +2801,7 @@ async function expirePendingRun(runId, expected, at, maxLifetimeMs, message) {
2864
2801
  });
2865
2802
  }
2866
2803
  async function listMachines(teamId) {
2867
- const q = db.select().from(machines);
2868
- return teamId ? await q.where(eq(machines.teamId, teamId)) : await q;
2804
+ return teamId ? await db.select().from(machines).where(eq(machines.teamId, teamId)).orderBy(asc(machines.name), asc(machines.id)) : await db.select().from(machines).orderBy(asc(machines.name), asc(machines.id));
2869
2805
  }
2870
2806
  /**
2871
2807
  * Machines usable/visible in a team, MEMBERSHIP-scoped: every machine whose owner
@@ -2874,7 +2810,7 @@ async function listMachines(teamId) {
2874
2810
  * A user has at most one membership row per team, so no machine is duplicated.
2875
2811
  */
2876
2812
  async function listMachinesForTeam(teamId) {
2877
- return (await db.select({ m: machines }).from(machines).innerJoin(teamMembers, eq(machines.userId, teamMembers.userId)).where(eq(teamMembers.teamId, teamId))).map((r) => r.m);
2813
+ return (await db.select({ m: machines }).from(machines).innerJoin(teamMembers, eq(machines.userId, teamMembers.userId)).where(eq(teamMembers.teamId, teamId)).orderBy(asc(machines.name), asc(machines.id))).map((r) => r.m);
2878
2814
  }
2879
2815
  async function getMachine(id) {
2880
2816
  return (await db.select().from(machines).where(eq(machines.id, id)))[0];
@@ -2915,7 +2851,6 @@ async function setMachineOnline(id, online) {
2915
2851
  lastSeen: nowIso$1()
2916
2852
  }).where(eq(machines.id, id));
2917
2853
  }
2918
- /** Deterministic personal-team id for a user (open mode ⇒ the shared "team-shared"). */
2919
2854
  function teamIdForUser(userId) {
2920
2855
  return `team-${userId ?? "shared"}`;
2921
2856
  }
@@ -2949,7 +2884,6 @@ async function ensureTeam(id, name, ownerUserId) {
2949
2884
  function newTeamId() {
2950
2885
  return `team-${randomUUID().slice(0, 12)}`;
2951
2886
  }
2952
- /** Whether this is the user's renamable but undeletable personal team. */
2953
2887
  function isPersonalTeam(team) {
2954
2888
  return !!team.ownerUserId && team.id === teamIdForUser(team.ownerUserId);
2955
2889
  }
@@ -2975,16 +2909,12 @@ async function createTeam(name, ownerUserId) {
2975
2909
  return team;
2976
2910
  });
2977
2911
  }
2978
- /** Rename a team (no name-sync fights this — see ensureTeam). */
2979
2912
  async function renameTeam(teamId, name) {
2980
2913
  await db.update(teams).set({ name }).where(eq(teams.id, teamId));
2981
2914
  }
2982
- /** A user's membership row in a team (undefined ⇒ not a member). */
2983
2915
  async function getTeamMember(teamId, userId) {
2984
2916
  return (await db.select().from(teamMembers).where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId))))[0];
2985
2917
  }
2986
- /** A team's members joined with their user email/name (owners first, then by
2987
- * join order). The member-list surface in the team settings UI. */
2988
2918
  async function listTeamMembers(teamId) {
2989
2919
  return (await db.select({
2990
2920
  id: teamMembers.id,
@@ -3000,7 +2930,6 @@ async function listTeamMembers(teamId) {
3000
2930
  displayName: r.displayName ?? null
3001
2931
  })).sort((a, b) => a.role === b.role ? a.createdAt < b.createdAt ? -1 : 1 : a.role === "owner" ? -1 : 1);
3002
2932
  }
3003
- /** Resolve a user by email for direct membership; undefined means no account yet. */
3004
2933
  async function userByEmail(email) {
3005
2934
  const r = (await db.select({
3006
2935
  id: user.id,
@@ -3079,7 +3008,6 @@ async function deleteTeamCascade(teamId) {
3079
3008
  if (outcome === "ok") ensuredTeams.delete(teamId);
3080
3009
  return outcome;
3081
3010
  }
3082
- /** Mint an invite for a team. `token` is the caller-supplied wire token. */
3083
3011
  async function createInvite(input) {
3084
3012
  return (await db.insert(teamInvites).values({
3085
3013
  ...input,
@@ -3089,7 +3017,6 @@ async function createInvite(input) {
3089
3017
  async function getInvite(token) {
3090
3018
  return (await db.select().from(teamInvites).where(eq(teamInvites.token, token)))[0];
3091
3019
  }
3092
- /** A team's still-live invites (unredeemed, unexpired), newest first. */
3093
3020
  async function listPendingInvites(teamId) {
3094
3021
  const now = nowIso$1();
3095
3022
  return db.select().from(teamInvites).where(and(eq(teamInvites.teamId, teamId), isNull(teamInvites.redeemedAt), gt(teamInvites.expiresAt, now))).orderBy(desc(teamInvites.createdAt));
@@ -3121,23 +3048,18 @@ async function redeemInviteAtomic(token, userId, grant) {
3121
3048
  return true;
3122
3049
  });
3123
3050
  }
3124
- /** Revoke a pending invite (owner action). */
3125
3051
  async function deleteInvite(token) {
3126
3052
  await db.delete(teamInvites).where(eq(teamInvites.token, token));
3127
3053
  }
3128
3054
  async function getTeam(id) {
3129
3055
  return (await db.select().from(teams).where(eq(teams.id, id)))[0];
3130
3056
  }
3131
- /** Teams the user belongs to (membership join), newest first. Drives the team
3132
- * switcher — a regular user has just their personal team (no dropdown). */
3133
3057
  async function listTeamsForUser(userId) {
3134
3058
  return (await db.select({ t: teams }).from(teamMembers).innerJoin(teams, eq(teamMembers.teamId, teams.id)).where(eq(teamMembers.userId, userId)).orderBy(desc(teams.createdAt))).map((r) => r.t);
3135
3059
  }
3136
- /** Whether the user is a member of the team (authorizes a team-switch request). */
3137
3060
  async function isTeamMember(teamId, userId) {
3138
3061
  return !!(await db.select({ id: teamMembers.id }).from(teamMembers).where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId))))[0];
3139
3062
  }
3140
- /** Does the server already have metadata for this blob hash? (drives needHashes). */
3141
3063
  async function blobExists(hash) {
3142
3064
  return !!(await db.select({ hash: blobs.hash }).from(blobs).where(eq(blobs.hash, hash)))[0];
3143
3065
  }
@@ -3151,7 +3073,6 @@ async function blobSizes(hashes) {
3151
3073
  }).from(blobs).where(inArray(blobs.hash, [...new Set(hashes)]));
3152
3074
  return new Map(rows.map((row) => [row.hash, row.size]));
3153
3075
  }
3154
- /** Record verified blob metadata idempotently. */
3155
3076
  async function recordBlob(hash, size) {
3156
3077
  await db.insert(blobs).values({
3157
3078
  hash,
@@ -3220,16 +3141,12 @@ async function reconcileConfiguredArtifacts(loopId, configuredAtStart, keepPaths
3220
3141
  return stale.length;
3221
3142
  });
3222
3143
  }
3223
- /** The loop's current (non-deleted) file set, path-sorted. */
3224
3144
  async function listArtifacts(loopId) {
3225
3145
  return db.select().from(artifactFiles).where(and(eq(artifactFiles.loopId, loopId), eq(artifactFiles.deleted, false))).orderBy(artifactFiles.path);
3226
3146
  }
3227
- /** One file row by loop + path (live or tombstoned). */
3228
3147
  async function getArtifactFile(loopId, path) {
3229
3148
  return (await db.select().from(artifactFiles).where(and(eq(artifactFiles.loopId, loopId), eq(artifactFiles.path, path))))[0];
3230
3149
  }
3231
- /** The loop's CURRENT live file set as a snapshot manifest (path → metadata) —
3232
- * what report() captures as the terminal run's artifact state. */
3233
3150
  async function buildLoopManifest(loopId) {
3234
3151
  const manifest = {};
3235
3152
  for (const f of await listArtifacts(loopId)) manifest[f.path] = {
@@ -3257,7 +3174,6 @@ async function putRunSnapshot(runId, loopId, manifest) {
3257
3174
  }
3258
3175
  });
3259
3176
  }
3260
- /** A run's captured snapshot, or undefined when the run predates the feature. */
3261
3177
  async function getRunSnapshot(runId) {
3262
3178
  return (await db.select().from(runSnapshots).where(eq(runSnapshots.runId, runId)))[0];
3263
3179
  }
@@ -3278,7 +3194,6 @@ async function pruneRunSnapshots(loopId, keep) {
3278
3194
  const pred = survivors.length ? and(eq(runSnapshots.loopId, loopId), notInArray(runSnapshots.runId, survivors)) : eq(runSnapshots.loopId, loopId);
3279
3195
  return (await db.delete(runSnapshots).where(pred).returning({ id: runSnapshots.runId })).length;
3280
3196
  }
3281
- /** Distinct loop ids that currently have at least one run snapshot. */
3282
3197
  async function loopIdsWithSnapshots() {
3283
3198
  return (await db.selectDistinct({ loopId: runSnapshots.loopId }).from(runSnapshots)).map((r) => r.loopId);
3284
3199
  }
@@ -3329,7 +3244,6 @@ async function countRunSnapshots() {
3329
3244
  const r = (await db.select({ n: sql`count(*)` }).from(runSnapshots))[0];
3330
3245
  return Number(r?.n ?? 0);
3331
3246
  }
3332
- /** Server-side artifact-sync helpers: path safety, byte caps, and hashing. */
3333
3247
  /** Per-file byte cap. At or under ⇒ bytes sync; over ⇒ metadata-only (no bytes). */
3334
3248
  var BLOB_CAP = 10 * 1024 * 1024;
3335
3249
  /** Hard ceiling on the exact-manifest sync POST body. */
@@ -3353,7 +3267,6 @@ function exactRecord(value, fields) {
3353
3267
  function sha256Buf(bytes) {
3354
3268
  return createHash("sha256").update(bytes).digest("hex");
3355
3269
  }
3356
- /** A 64-char lowercase hex sha256. */
3357
3270
  function isValidHash(hash) {
3358
3271
  return typeof hash === "string" && /^[0-9a-f]{64}$/.test(hash);
3359
3272
  }
@@ -3424,11 +3337,9 @@ function parseArtifactSyncBody(value) {
3424
3337
  function sha256(s) {
3425
3338
  return createHash("sha256").update(s).digest("hex");
3426
3339
  }
3427
- /** Mint a fresh device token (`dk_…`) — the one wire format `machineIdFromToken` consumes. */
3428
3340
  function mintDeviceToken() {
3429
3341
  return `dk_${randomBytes(15).toString("hex")}`;
3430
3342
  }
3431
- /** Derive the stable machine id from its device token. */
3432
3343
  function machineIdFromToken(token) {
3433
3344
  return `m-${sha256(token).slice(0, 16)}`;
3434
3345
  }
@@ -3461,7 +3372,6 @@ async function rememberConnectKey(connectKey, intent) {
3461
3372
  set: row
3462
3373
  });
3463
3374
  }
3464
- /** Peek (NON-evicting) the team/minter a connect-key was minted under, if still live. */
3465
3375
  async function readClaimIntent(connectKey, now = Date.now()) {
3466
3376
  if (!connectKey) return void 0;
3467
3377
  const row = (await db.select().from(connectKeys).where(eq(connectKeys.machineId, machineIdFromToken(connectKey))))[0];
@@ -3471,7 +3381,6 @@ async function readClaimIntent(connectKey, now = Date.now()) {
3471
3381
  teamId: row.teamId
3472
3382
  };
3473
3383
  }
3474
- /** The remembered owner of a self-registering machine, if any (still-live key). */
3475
3384
  async function getDeviceOwner(machineId, now = Date.now()) {
3476
3385
  const row = (await db.select().from(connectKeys).where(eq(connectKeys.machineId, machineId)))[0];
3477
3386
  if (!row || !connectKeyFresh(row.mintedAt, now)) return void 0;
@@ -3534,7 +3443,6 @@ var newIdempotency = /* @__PURE__ */ new Map();
3534
3443
  function pruneNewIdempotency(now) {
3535
3444
  for (const [key, rec] of newIdempotency) if (now - rec.at > 9e5) newIdempotency.delete(key);
3536
3445
  }
3537
- /** Remember that `key` (from THIS machine) created `loopId`. Pruned on write. */
3538
3446
  function recordNewIdempotency(key, machineId, loopId, now = Date.now()) {
3539
3447
  pruneNewIdempotency(now);
3540
3448
  newIdempotency.set(key, {
@@ -3596,8 +3504,6 @@ function validIdentifiers(value, rejectNumericLeadingZero) {
3596
3504
  return !rejectNumericLeadingZero || !/^\d+$/.test(identifier) || identifier === "0" || !identifier.startsWith("0");
3597
3505
  });
3598
3506
  }
3599
- /** Parse the leading numeric core `x.y.z` of a version, ignoring any pre-release
3600
- * / build suffix. Returns null when it isn't a recognizable version. */
3601
3507
  function core(v) {
3602
3508
  const m = /^\s*v?(\d+)\.(\d+)\.(\d+)/.exec(v);
3603
3509
  if (!m) return null;
@@ -3607,7 +3513,6 @@ function core(v) {
3607
3513
  Number(m[3])
3608
3514
  ];
3609
3515
  }
3610
- /** Does `v` carry a pre-release suffix (e.g. "1.2.3-rc.1")? */
3611
3516
  function isPre(v) {
3612
3517
  return /^\s*v?\d+\.\d+\.\d+-/.test(v);
3613
3518
  }
@@ -3707,7 +3612,6 @@ async function readJsonBody(request, maxBytes) {
3707
3612
  return { kind: "invalid" };
3708
3613
  }
3709
3614
  }
3710
- /** Shared clipping cap for large prompt and terminal telemetry strings. */
3711
3615
  var WIRE_TEXT_CAP = 512 * 1024;
3712
3616
  /** Validate an optional poll string without coercion, clipping, or NUL removal. */
3713
3617
  function validOptionalPollString(value, cap) {
@@ -3724,7 +3628,6 @@ function nowIso() {
3724
3628
  function stripNul(s) {
3725
3629
  return s.replace(/\u0000/g, "");
3726
3630
  }
3727
- /** Clip a free-text wire field and strip NUL before persistence. */
3728
3631
  function clipText(s, cap) {
3729
3632
  return stripNul(s.slice(0, cap));
3730
3633
  }
@@ -3881,7 +3784,6 @@ var LocalBlobStore = class {
3881
3784
  await this.syncDir(path.dirname(target));
3882
3785
  }
3883
3786
  };
3884
- /** In-memory blob store — injectable test adapter. */
3885
3787
  var MemoryBlobStore = class {
3886
3788
  map = /* @__PURE__ */ new Map();
3887
3789
  async has(hash) {
@@ -3897,11 +3799,9 @@ var MemoryBlobStore = class {
3897
3799
  this.map.delete(hash);
3898
3800
  }
3899
3801
  };
3900
- /** Object key for a blob hash. Flat namespace under a prefix — hashes are unique. */
3901
3802
  function blobKey(hash) {
3902
3803
  return `blobs/${hash}`;
3903
3804
  }
3904
- /** Cloudflare R2 blob store over the S3-compatible API (AWS SDK v3). */
3905
3805
  var R2BlobStore = class {
3906
3806
  cfg;
3907
3807
  client;
@@ -3968,7 +3868,6 @@ var R2BlobStore = class {
3968
3868
  }));
3969
3869
  }
3970
3870
  };
3971
- /** S3 "object absent" maps to a 404 / NoSuchKey / NotFound error shape. */
3972
3871
  function isNotFound(err) {
3973
3872
  const e = err;
3974
3873
  return e?.name === "NotFound" || e?.name === "NoSuchKey" || e?.$metadata?.httpStatusCode === 404;
@@ -4159,35 +4058,21 @@ async function authenticateDeviceToken(deviceToken, options = {}) {
4159
4058
  machine
4160
4059
  };
4161
4060
  }
4162
- /** Whether a token needs quoting inside a key/value line or comma-delimited row. */
4163
4061
  function needsQuote(s) {
4164
4062
  return s === "" || /[\s,:"]/.test(s);
4165
4063
  }
4166
- /** Quote and escape a string so it remains on one line. */
4167
4064
  function quote(s) {
4168
4065
  return `"${s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\r?\n/g, "\\n")}"`;
4169
4066
  }
4170
- /** Render one scalar the TOON way: a finite number / boolean bare, null|undefined as
4171
- * the em-dash placeholder, a string bare unless it needs quoting. */
4172
4067
  function scalar(v) {
4173
4068
  if (v === null || v === void 0) return "—";
4174
4069
  if (typeof v === "number") return Number.isFinite(v) ? String(v) : "—";
4175
4070
  if (typeof v === "boolean") return v ? "true" : "false";
4176
4071
  return needsQuote(v) ? quote(v) : v;
4177
4072
  }
4178
- /** A single top-level `key: value` line (value scalar-rendered). */
4179
4073
  function kvLine(key, v) {
4180
4074
  return `${key}: ${scalar(v)}`;
4181
4075
  }
4182
- /**
4183
- * A detail block: a top key, then each field indented two spaces as `key: value`.
4184
- * The value may be a pre-rendered raw string (pass a `{ raw }`) so composite values
4185
- * (e.g. a size hint or an already-quoted body) render verbatim.
4186
- *
4187
- * topKey:
4188
- * name: "Docs Sweep"
4189
- * cron: "0 6 * * 1"
4190
- */
4191
4076
  function detailBlock(topKey, rows) {
4192
4077
  const lines = [`${topKey}:`];
4193
4078
  for (const [k, v] of rows) {
@@ -4196,44 +4081,25 @@ function detailBlock(topKey, rows) {
4196
4081
  }
4197
4082
  return lines.join("\n");
4198
4083
  }
4199
- /** Render a count, optionally with total or displayed-window context. */
4200
4084
  function countLine(count, opts = {}) {
4201
4085
  if (opts.total !== void 0) return `count: ${count} of ${opts.total} total`;
4202
4086
  if (opts.showing !== void 0) return `count: ${count} (showing first ${opts.showing})`;
4203
4087
  return `count: ${count}`;
4204
4088
  }
4205
- /**
4206
- * A typed list block: the header row `name[N]{f1,f2}:` then each record as an
4207
- * indented comma row (cells scalar-rendered, quoted only when needed). Does NOT emit
4208
- * the `count:` line — compose it with `countLine` so the caller controls the
4209
- * aggregate flavor.
4210
- *
4211
- * loops[2]{id,name,cron,enabled}:
4212
- * loop-abc,"Docs Sweep","0 6 * * 1",on
4213
- */
4214
4089
  function listBlock(name, fields, rows) {
4215
4090
  const lines = [`${name}[${rows.length}]{${fields.join(",")}}:`];
4216
4091
  for (const row of rows) lines.push(` ${row.map(scalar).join(",")}`);
4217
4092
  return lines.join("\n");
4218
4093
  }
4219
- /** Render an empty named collection. */
4220
4094
  function emptyList(name) {
4221
4095
  return `${name}: []`;
4222
4096
  }
4223
- /** Render command templates as an indented help block. */
4224
4097
  function helpBlock(lines) {
4225
4098
  return [`help[${lines.length}]:`, ...lines.map((l) => ` ${l}`)].join("\n");
4226
4099
  }
4227
- /**
4228
- * Render a quoted error plus a machine-readable code.
4229
- *
4230
- * error: "status must be keep|no-change|block (got \"wibble\")"
4231
- * code: VALIDATION_ERROR
4232
- */
4233
4100
  function errorBlock(message, code) {
4234
4101
  return `error: ${quote(message)}\ncode: ${code}`;
4235
4102
  }
4236
- /** Map an HTTP status to its CLI error code. */
4237
4103
  function codeForStatus(status) {
4238
4104
  switch (status) {
4239
4105
  case 400: return "VALIDATION_ERROR";
@@ -4245,7 +4111,6 @@ function codeForStatus(status) {
4245
4111
  default: return "ERROR";
4246
4112
  }
4247
4113
  }
4248
- /** Clip content at `cap` and append its original size plus recovery hint. */
4249
4114
  function truncate(text, cap, tail = "use --full to see complete body") {
4250
4115
  if (text.length <= cap) return {
4251
4116
  value: text,
@@ -4256,8 +4121,6 @@ function truncate(text, cap, tail = "use --full to see complete body") {
4256
4121
  truncated: true
4257
4122
  };
4258
4123
  }
4259
- /** Join document sections with a single newline, dropping empty/blank sections so a
4260
- * missing optional block (e.g. no rejections) leaves no stray blank line. */
4261
4124
  function doc(...sections) {
4262
4125
  return sections.filter((s) => typeof s === "string" && s.length > 0).join("\n");
4263
4126
  }
@@ -4324,6 +4187,24 @@ function optionalProvider(value, field) {
4324
4187
  value: normalizeProviderSetting(value)
4325
4188
  };
4326
4189
  }
4190
+ var PI_THINKING = new Set([
4191
+ "off",
4192
+ "minimal",
4193
+ "low",
4194
+ "medium",
4195
+ "high",
4196
+ "xhigh",
4197
+ "max"
4198
+ ]);
4199
+ function validateReasoningEffort(value, agent) {
4200
+ const result = optionalProvider(value, "reasoningEffort");
4201
+ if (!result.ok) return result;
4202
+ if (agent === "pi" && result.value !== null && !PI_THINKING.has(result.value)) return {
4203
+ ok: false,
4204
+ detail: "reasoningEffort for pi must be one of off, minimal, low, medium, high, xhigh, max, or null"
4205
+ };
4206
+ return result;
4207
+ }
4327
4208
  function validTimezone(timezone) {
4328
4209
  try {
4329
4210
  new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
@@ -4535,7 +4416,6 @@ function statusDefinitionsFromLoop(loop) {
4535
4416
  block: loop.statusBlock
4536
4417
  };
4537
4418
  }
4538
- /** The single owner-facing structured loop shape used by show/list JSON output. */
4539
4419
  function canonicalLoopEnvelope(loop) {
4540
4420
  return {
4541
4421
  id: loop.id,
@@ -4576,13 +4456,13 @@ function validateLoopCreate(value) {
4576
4456
  if (!artifacts.ok) return artifacts;
4577
4457
  const model = optionalProvider(raw.model, "model");
4578
4458
  if (!model.ok) return model;
4579
- const reasoningEffort = optionalProvider(raw.reasoningEffort, "reasoningEffort");
4580
- if (!reasoningEffort.ok) return reasoningEffort;
4581
4459
  const agent = coerceCodingAgent(raw.agent);
4582
4460
  if (!agent) return {
4583
4461
  ok: false,
4584
4462
  detail: `agent is required and must be one of ${CODING_AGENTS.join(", ")}`
4585
4463
  };
4464
+ const reasoningEffort = validateReasoningEffort(raw.reasoningEffort, agent);
4465
+ if (!reasoningEffort.ok) return reasoningEffort;
4586
4466
  if (raw.enabled !== void 0 && typeof raw.enabled !== "boolean") return {
4587
4467
  ok: false,
4588
4468
  detail: "enabled must be boolean"
@@ -4669,19 +4549,21 @@ function validateLoopEdit(loop, value) {
4669
4549
  if (!result.ok) return result;
4670
4550
  update.model = result.value;
4671
4551
  }
4672
- if (raw.reasoningEffort !== void 0) {
4673
- const result = optionalProvider(raw.reasoningEffort, "reasoningEffort");
4674
- if (!result.ok) return result;
4675
- update.reasoningEffort = result.value;
4676
- }
4552
+ let effectiveAgent = loop.agent;
4677
4553
  if (raw.agent !== void 0) {
4678
4554
  const agent = coerceCodingAgent(raw.agent);
4679
4555
  if (!agent) return {
4680
4556
  ok: false,
4681
4557
  detail: `agent must be one of ${CODING_AGENTS.join(", ")}`
4682
4558
  };
4559
+ effectiveAgent = agent;
4683
4560
  update.agent = agent;
4684
4561
  }
4562
+ if (raw.reasoningEffort !== void 0 || raw.agent !== void 0) {
4563
+ const result = validateReasoningEffort(raw.reasoningEffort !== void 0 ? raw.reasoningEffort : loop.reasoningEffort, effectiveAgent);
4564
+ if (!result.ok) return result;
4565
+ if (raw.reasoningEffort !== void 0) update.reasoningEffort = result.value;
4566
+ }
4685
4567
  if (raw.enabled !== void 0) {
4686
4568
  if (typeof raw.enabled !== "boolean") return {
4687
4569
  ok: false,
@@ -4748,7 +4630,6 @@ var ONLINE_TTL_MS = 3e4;
4748
4630
  /** Auto-pause after this many consecutive errors. Canceled rows are transparent;
4749
4631
  * zero disables the breaker. */
4750
4632
  var AUTOPAUSE_STREAK = Math.max(0, Number(process.env.PIEVO_FAILURE_AUTOPAUSE_STREAK ?? 3));
4751
- /** How long pending system work on an offline machine stays claimable. */
4752
4633
  var DEFERRED_MAX_MS = 7 * 864e5;
4753
4634
  /** A claimed run that never reports within this window is reclaimed as timed out. */
4754
4635
  var configuredRunTimeoutMs = Number(process.env.PIEVO_RUN_TIMEOUT_MS || 20 * 6e4);
@@ -4763,9 +4644,9 @@ var EDITABLE_LOOP_FIELDS = LOOP_EDIT_FIELDS;
4763
4644
  * it never satisfies the successful-run reporting protocol. Run errors share
4764
4645
  * this cap. Exported for `cli.ts` so the report verb uses the same budget. */
4765
4646
  var MESSAGE_CAP = 2e3;
4766
- /** A claude-code session id is a UUID-ish token — anything longer is garbage. */
4647
+ /** Provider session IDs are opaque short tokens; this rejects malformed payloads
4648
+ * without constraining known provider formats. */
4767
4649
  var SESSION_ID_CAP = 200;
4768
- /** Keep heartbeat throttling safely inside custom short timeout windows. */
4769
4650
  function heartbeatRefreshMs(runTimeoutMs) {
4770
4651
  if (!Number.isFinite(runTimeoutMs) || runTimeoutMs <= 0) return 1;
4771
4652
  return Math.max(1, Math.min(6e4, runTimeoutMs / 3));
@@ -4971,7 +4852,6 @@ var MachineGateway = class {
4971
4852
  * process-local; unlike durable run leases, a deploy drops them and the daemon
4972
4853
  * simply re-polls. */
4973
4854
  pollWaiters = /* @__PURE__ */ new Map();
4974
- /** Resolve (and disarm) a machine's parked long-poll waiter, if any. */
4975
4855
  wakeMachine(machineId) {
4976
4856
  this.pollWaiters.get(machineId)?.(true);
4977
4857
  }
@@ -5139,7 +5019,6 @@ var MachineGateway = class {
5139
5019
  body: { delivery }
5140
5020
  };
5141
5021
  }
5142
- /** Protocol-v4 poll: plural local state, one new delivery per poll. */
5143
5022
  async pollV4(deviceToken, request) {
5144
5023
  const rawRequest = request;
5145
5024
  if (request === null || typeof request !== "object" || Array.isArray(request) || Object.keys(rawRequest).some((key) => !POLL_V4_REQUEST_FIELDS.has(key)) || !validPollInfo(rawRequest?.info) || rawRequest.protocolVersion !== void 0 && (typeof rawRequest.protocolVersion !== "number" || !Number.isInteger(rawRequest.protocolVersion)) || rawRequest.currentRuns !== void 0 && (!Array.isArray(rawRequest.currentRuns) || rawRequest.currentRuns.some((run) => !validPollCurrentRun(run))) || rawRequest.daemonInstanceId !== void 0 && !validPollWireId(rawRequest.daemonInstanceId) || rawRequest.recoveryComplete !== void 0 && typeof rawRequest.recoveryComplete !== "boolean") return {
@@ -5209,7 +5088,6 @@ var MachineGateway = class {
5209
5088
  }
5210
5089
  };
5211
5090
  }
5212
- /** Idle v4 polls long-poll; active/reporting polls return immediately. */
5213
5091
  async pollV4Wait(deviceToken, request, waitMs = LONG_POLL_WAIT_MS) {
5214
5092
  if (request.currentRuns?.length || request.protocolVersion !== 4) return this.pollV4(deviceToken, request);
5215
5093
  const auth = await authenticateDeviceToken(deviceToken, { allowUnknown: true });
@@ -5268,8 +5146,6 @@ var MachineGateway = class {
5268
5146
  }
5269
5147
  };
5270
5148
  }
5271
- /** Create a validated loop through device authority, bind it to that machine,
5272
- * and schedule it immediately. */
5273
5149
  async createLoop(deviceToken, body) {
5274
5150
  const auth = await authenticateDeviceToken(deviceToken);
5275
5151
  if (!auth.ok) return auth.response;
@@ -5362,9 +5238,6 @@ var MachineGateway = class {
5362
5238
  };
5363
5239
  }
5364
5240
  }
5365
- /** List loops bound to this machine. `--fields` extends the minimal default
5366
- * columns and rejects unknown names. `--json` returns full structured records
5367
- * (first byte `[`), mirroring `show --json` — the daemon prints `text` either way. */
5368
5241
  async listLoops(deviceToken, fieldsFlag, json) {
5369
5242
  const auth = await authenticateDeviceToken(deviceToken);
5370
5243
  if (!auth.ok) return auth.response;
@@ -5410,7 +5283,6 @@ var MachineGateway = class {
5410
5283
  }
5411
5284
  };
5412
5285
  }
5413
- /** Edit a loop through owner authority, scoped to loops bound to this machine. */
5414
5286
  async editLoop(deviceToken, id, patch, dryRun = false) {
5415
5287
  const auth = await authenticateDeviceToken(deviceToken);
5416
5288
  if (!auth.ok) return auth.response;
@@ -5432,20 +5304,30 @@ var MachineGateway = class {
5432
5304
  };
5433
5305
  const update = validated.value;
5434
5306
  const applied = Object.keys(patch ?? {});
5435
- if (dryRun) return {
5436
- status: 200,
5437
- body: {
5438
- ok: true,
5439
- dryRun: true,
5307
+ if (dryRun) {
5308
+ const before = canonicalLoopEnvelope(loop);
5309
+ const after = canonicalLoopEnvelope({
5310
+ ...loop,
5311
+ ...update
5312
+ });
5313
+ const { id: _readOnlyId, ...config } = after;
5314
+ const preview = {
5440
5315
  id: loop.id,
5441
5316
  applied,
5442
- config: { ...patch },
5443
- text: JSON.stringify({
5444
- id: loop.id,
5445
- applied
5446
- }, null, 2)
5447
- }
5448
- };
5317
+ before,
5318
+ after
5319
+ };
5320
+ return {
5321
+ status: 200,
5322
+ body: {
5323
+ ok: true,
5324
+ dryRun: true,
5325
+ ...preview,
5326
+ config,
5327
+ text: JSON.stringify(preview, null, 2)
5328
+ }
5329
+ };
5330
+ }
5449
5331
  if (Object.keys(update).length === 0) return {
5450
5332
  status: 200,
5451
5333
  body: {
@@ -5851,7 +5733,6 @@ function fmtTimeZoned(iso, timezone, opts = {}) {
5851
5733
  return fmtTime(iso);
5852
5734
  }
5853
5735
  }
5854
- /** Minimal default columns for `pievo loops`. */
5855
5736
  var LIST_DEFAULT_FIELDS = [
5856
5737
  "id",
5857
5738
  "name",
@@ -5859,8 +5740,6 @@ var LIST_DEFAULT_FIELDS = [
5859
5740
  "enabled",
5860
5741
  "nextFire"
5861
5742
  ];
5862
- /** The optional columns `--fields` may add (the "available" set an unknown field is
5863
- * measured against). `runs`/`lastResult` are derived per loop. */
5864
5743
  var LIST_OPTIONAL_FIELDS = [
5865
5744
  "timezone",
5866
5745
  "model",
@@ -5868,7 +5747,6 @@ var LIST_OPTIONAL_FIELDS = [
5868
5747
  "runs",
5869
5748
  "lastResult"
5870
5749
  ];
5871
- /** One `loops` cell for a named column (scalar-rendered by `listBlock`). */
5872
5750
  function loopCell(rec, field) {
5873
5751
  switch (field) {
5874
5752
  case "id": return rec.id;
@@ -5884,13 +5762,10 @@ function loopCell(rec, field) {
5884
5762
  default: return null;
5885
5763
  }
5886
5764
  }
5887
- /** Render the typed loop list. Columns are the default set
5888
- * plus any `--fields` extras (validated + resolved by `listLoops`). */
5889
5765
  function renderLoopsText(loops, fields) {
5890
5766
  if (!loops.length) return doc(countLine(0), emptyList("loops"), helpBlock(["Run `pievo new --json '{...}'` to create your first loop", "Run `pievo daemon connect` if this machine isn't connected yet"]));
5891
5767
  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"]));
5892
5768
  }
5893
- /** One run's result cell, derived from phase + status. */
5894
5769
  function runResultToken(r) {
5895
5770
  if (r.status === "block") return `block/${r.phase}`;
5896
5771
  if (r.phase === "canceled") return "canceled";
@@ -6070,7 +5945,6 @@ var ArtifactSync = class {
6070
5945
  body: { ok: true }
6071
5946
  };
6072
5947
  }
6073
- /** Read stored content-addressed bytes, or null when absent. */
6074
5948
  readBlob(hash) {
6075
5949
  if (!isValidHash(hash)) return Promise.resolve(null);
6076
5950
  return this.blobStore.get(hash);
@@ -6091,20 +5965,16 @@ var ArtifactSync = class {
6091
5965
  * a changed text file degrades to the size-delta marker, like binary/oversize.
6092
5966
  */
6093
5967
  var MAX_DIFF_BYTES = 512 * 1024;
6094
- /** A file is text-diffable only when it has stored bytes and isn't binary/oversize. */
6095
5968
  function isText(e) {
6096
5969
  return !!e && !e.binary && !e.oversize && !!e.hash;
6097
5970
  }
6098
- /** Within the inline-diff size cap (unknown size ⇒ allowed). */
6099
5971
  function withinDiffCap(e) {
6100
5972
  return !!e && (e.size == null || e.size <= MAX_DIFF_BYTES);
6101
5973
  }
6102
- /** Load one snapshot blob, or null when recorded bytes are absent. */
6103
5974
  async function bytesOf(e) {
6104
5975
  if (!e.hash) return null;
6105
5976
  return await (await getArtifactSync()).readBlob(e.hash) ?? null;
6106
5977
  }
6107
- /** Unified diff between two versions of a path (empty string ⇒ added/removed side). */
6108
5978
  function unified(path, oldText, newText, maxEditLength) {
6109
5979
  return maxEditLength === void 0 ? createTwoFilesPatch(path, path, oldText, newText, "previous run", "this run") : createTwoFilesPatch(path, path, oldText, newText, "previous run", "this run", { maxEditLength });
6110
5980
  }
@@ -6607,7 +6477,6 @@ var CliGateway = class {
6607
6477
  async cli(token, argv) {
6608
6478
  return finalizeCli(token.startsWith("dk_") ? await this.deviceCli(token, argv) : await this.runCli(token, argv));
6609
6479
  }
6610
- /** DEVICE-credential branch of the unified CLI. */
6611
6480
  async deviceCli(deviceToken, argv) {
6612
6481
  const auth = await authenticateDeviceToken(deviceToken, { allowUnknown: true });
6613
6482
  if (!auth.ok) return auth.response;
@@ -6866,7 +6735,6 @@ var CliGateway = class {
6866
6735
  body: { text: `run already finished: ${runResultToken(stopped)}` }
6867
6736
  };
6868
6737
  }
6869
- /** RUN-credential branch of the unified CLI: report and its help only. */
6870
6738
  async runCli(runToken, argv) {
6871
6739
  const lease = await resolveLease(runToken);
6872
6740
  if (!lease) return {
@@ -6940,13 +6808,16 @@ var CliGateway = class {
6940
6808
  ];
6941
6809
  const unknown = Object.keys(flags).filter((key) => !acceptedFlags.includes(key));
6942
6810
  if (unknown.length) return derr(400, `report does not accept --${unknown[0]}`, "VALIDATION_ERROR");
6811
+ if (lease.state === "retired") return {
6812
+ code: 200,
6813
+ text: "reported: run retired · result discarded"
6814
+ };
6943
6815
  const applied = await this.applyReportMutation(lease, leaseTokenHash, status, message.slice(0, MESSAGE_CAP));
6944
6816
  return applied.ok ? {
6945
6817
  code: 200,
6946
6818
  text: renderReportedText(status, true)
6947
6819
  } : derr(applied.status ?? 409, applied.detail ?? "this run is no longer active", applied.code);
6948
6820
  }
6949
- /** The complete run-token command surface: report plus help for report. */
6950
6821
  helpText(_lease) {
6951
6822
  return doc(listBlock("verbs", ["verb", "syntax"], [["report", "--status keep|no-change|block --message <summary>"]]), helpBlock(["Call exactly one pievo report before finishing", "No other command is accepted from a run credential"]));
6952
6823
  }
@@ -7023,7 +6894,6 @@ var CliGateway = class {
7023
6894
  })), scoped.elsewhere, await recentMachineRuns(loops, 3));
7024
6895
  }
7025
6896
  };
7026
- /** Shared refusal for terminal-report-only grace after reclaim. */
7027
6897
  var TERMINAL_GRACE_MSG = "this run is terminal and no longer accepts commands; its final result is delivered via the terminal report";
7028
6898
  /** Verbs that require device authority. `report` is run-only and rejected there. */
7029
6899
  var DEVICE_ONLY_VERBS = new Set([
@@ -7108,21 +6978,18 @@ function finalizeCli(res) {
7108
6978
  }
7109
6979
  return res;
7110
6980
  }
7111
- /** Render the compact run-status confirmation. */
7112
6981
  function renderReportedText(status, hasMessage) {
7113
6982
  const parts = [];
7114
6983
  if (status) parts.push(`status=${status}`);
7115
6984
  if (hasMessage) parts.push("message recorded");
7116
6985
  return `reported: ${parts.length ? parts.join(" · ") : "recorded"}`;
7117
6986
  }
7118
- /** RUN-credential verb help (in-run `rk_` lease). */
7119
6987
  var RUN_VERB_HELP = { report: {
7120
6988
  syntax: "report --status keep|no-change|block --message <text>",
7121
6989
  summary: "record this run's required status and non-empty summary",
7122
6990
  avail: () => "always available",
7123
6991
  help: ["Run `pievo report --status no-change --message \"no actionable change\"` to close this run", "Run `pievo report --status keep --message \"completed the requested work\"` to retain a result"]
7124
6992
  } };
7125
- /** DEVICE-credential verb help (owner `dk_` device token). */
7126
6993
  var DEVICE_VERB_HELP = {
7127
6994
  new: {
7128
6995
  syntax: "new --json '<config>' [--dry-run] [--connect-key <dk_…>] [--server-url <url>]",
@@ -7175,25 +7042,19 @@ var DEVICE_VERB_HELP = {
7175
7042
  help: ["Run `pievo log <id> --json` for structured history", "Run `pievo log <id> --run 12 --diff` for bounded run detail and artifact diff"]
7176
7043
  }
7177
7044
  };
7178
- /** Render per-verb help. A run lease selects the report-only help;
7179
- * no lease means the device (owner) surface. Returns undefined for a verb
7180
- * with no help spec, so the caller falls back to its unknown-command handling. */
7181
7045
  function verbHelpText(verb, lease) {
7182
7046
  const spec = lease ? RUN_VERB_HELP[verb] : DEVICE_VERB_HELP[verb];
7183
7047
  if (!spec) return void 0;
7184
7048
  return doc(kvLine("verb", verb), kvLine("syntax", spec.syntax), kvLine("summary", spec.summary), lease && spec.avail ? kvLine("availability", spec.avail(lease)) : null, helpBlock(spec.help));
7185
7049
  }
7186
- /** The canonical editable config emitted by `show --json`. */
7187
7050
  function loopEnvelope(loop) {
7188
7051
  return { ...canonicalLoopEnvelope(loop) };
7189
7052
  }
7190
- /** Render a large prompt in full or as a compact presence hint. */
7191
7053
  function contentField(value, full) {
7192
7054
  if (value == null) return "absent";
7193
7055
  if (full) return value;
7194
7056
  return { raw: `present, ${value.length} bytes — use --full to see` };
7195
7057
  }
7196
- /** The next cadence fire, formatted in the loop's timezone. */
7197
7058
  function nextFireDisplay(loop) {
7198
7059
  const schedule = scheduleFromLoop(loop);
7199
7060
  if (schedule.mode === "continuous") return `after exec terminal + ${schedule.delayMinutes}m`;
@@ -7201,7 +7062,6 @@ function nextFireDisplay(loop) {
7201
7062
  if (!iso) return "(never)";
7202
7063
  return fmtTimeZoned(iso, schedule.timezone, { seconds: true });
7203
7064
  }
7204
- /** `pievo show` renders the editable envelope followed by derived read-only state. */
7205
7065
  function renderShowText(loop, env, totalRuns, lastRun, opts = {}) {
7206
7066
  const full = opts.full === true;
7207
7067
  const block = detailBlock("loop", [
@@ -7225,14 +7085,12 @@ function renderShowText(loop, env, totalRuns, lastRun, opts = {}) {
7225
7085
  ];
7226
7086
  return doc(block, kvLine("nextFire", nextFireDisplay(loop)), `lifecycle: ${loop.enabled ? "active" : "paused"}`, `runs: ${runsTally}`, !loop.enabled ? kvLine("pauseCause", loop.pauseCause?.kind === "failure-streak" ? `failure-streak (run ${loop.pauseCause.runId}, count ${loop.pauseCause.count})` : loop.pauseCause?.kind === "blocked" ? `blocked (run ${loop.pauseCause.runId})` : loop.pauseCause?.kind === "owner" ? "owner" : "unknown") : null, lastRun?.reportIncident ? kvLine("reportIncident", `${lastRun.reportIncident.code} · ${lastRun.reportIncident.faultDomain} · ${lastRun.reportIncident.reason}`) : null, helpBlock(help));
7227
7087
  }
7228
- /** The static one-line description in the home header. */
7229
7088
  var HOME_DESCRIPTION = "Run your scheduled Pievo agent loops on this machine with your own coding agent.";
7230
7089
  /** Expand a leading `~/` against the daemon-supplied home dir (the SERVER's own home
7231
7090
  * is irrelevant — a loop's paths are the daemon machine's). Absent home ⇒ unchanged. */
7232
7091
  function expandHome(p, home) {
7233
7092
  return home && p.startsWith("~/") ? path.join(home, p.slice(2)) : p;
7234
7093
  }
7235
- /** A loop's folder on the daemon machine is its configured workdir. */
7236
7094
  function scopeLoopDir(workdir, home) {
7237
7095
  return path.resolve(expandHome(workdir, home));
7238
7096
  }
@@ -7261,8 +7119,6 @@ function scopeLoopsByCwd(loops, cwd, home) {
7261
7119
  elsewhere: loops.length - matched.length
7262
7120
  };
7263
7121
  }
7264
- /** The most recent runs across ALL of a machine's loops, newest-first, for the home
7265
- * `recent[]` block. Merges each loop's newest few then globally sorts by ts. */
7266
7122
  async function recentMachineRuns(loops, n) {
7267
7123
  const rows = [];
7268
7124
  for (const l of loops) for (const r of await listRuns(l.id, n)) rows.push({
@@ -7393,7 +7249,6 @@ var Scheduler = class {
7393
7249
  enqueueInitialExec(id) {
7394
7250
  return this.enqueue(id, { requestedBy: "system" });
7395
7251
  }
7396
- /** Poll fallback and timer callback converge here. */
7397
7252
  async advanceDueSchedules(machineId, loopId) {
7398
7253
  const advanced = await advanceDueSchedules((/* @__PURE__ */ new Date()).toISOString(), {
7399
7254
  machineId,