@kky42/pievo-server 2.4.9 → 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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{MachinesModal-wxHeRJsP.js → MachinesModal-ByHejech.js} +1 -1
- package/.output/public/assets/app-IrlFuBXa.css +2 -0
- package/.output/public/assets/{index-DXJGEV9d.js → index-ia26HsP1.js} +3 -3
- package/.output/public/assets/{invite._token-DgbAZ8tR.js → invite._token-tDfe3p0Z.js} +1 -1
- package/.output/public/assets/{loops._loopId-hqovx5nj.js → loops._loopId-CuYq_XgT.js} +3 -3
- package/.output/public/assets/{loops._loopId_.runs._runId-B233CSov.js → loops._loopId_.runs._runId-mOaF51Qu.js} +1 -1
- package/.output/public/assets/{routes-BcmIcHul.js → routes-Dbl_3lt_.js} +1 -1
- package/.output/public/assets/{t._teamId-CJOyBon9.js → t._teamId-BVsl0EGm.js} +1 -1
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_ssr/{DashboardView-Dj-FImnr.mjs → DashboardView-BMHMrOfC.mjs} +6 -6
- package/.output/server/_ssr/{MachinesModal-C8UdXFwP.mjs → MachinesModal-BhRw7oOx.mjs} +8 -15
- package/.output/server/_ssr/{SignIn-kgyyAjxa.mjs → SignIn-BhoZIOUp.mjs} +3 -3
- package/.output/server/_ssr/{SocialLinks-ZYIMrv4W.mjs → SocialLinks-DVCLmql0.mjs} +2 -11
- package/.output/server/_ssr/{actionUi-DCNmrAjw.mjs → actionUi-Cf6DuEC2.mjs} +2 -2
- package/.output/server/_ssr/{artifactFiles-Dg7Csxw-.mjs → artifactFiles-ChuJ4PzC.mjs} +3 -4
- package/.output/server/_ssr/{artifactKind-Bex7n005.mjs → artifactKind-DXumOv7c.mjs} +3 -7
- package/.output/server/_ssr/{artifacts-CGJQi12P.mjs → artifacts-DOLHJHcd.mjs} +1 -1
- package/.output/server/_ssr/{auth-pONvN2Mo.mjs → auth-LwPp_iim.mjs} +9 -13
- package/.output/server/_ssr/{boot-DoRoAQuI.mjs → boot-BIxtZHU-.mjs} +61 -209
- package/.output/server/_ssr/{boot-DF8p07PC.mjs → boot-D8cj4uZj.mjs} +1 -1
- package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-fxdbbtvy.mjs → bun-sqlite-dialect-BW9W1_Ps-CHMDkKqA.mjs} +2 -2
- package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-CcOEyONf.mjs → d1-sqlite-dialect-BLC8LXE6-DYum6qky.mjs} +2 -2
- package/.output/server/_ssr/db-BphSJs66.mjs +2 -0
- package/.output/server/_ssr/{dist-ghpS-jzd.mjs → dist-bWqs5sTy.mjs} +2 -2
- package/.output/server/_ssr/{factory-D7WCUKnW.mjs → factory-pBwrjVTI.mjs} +2 -2
- package/.output/server/_ssr/{invite._token-hkOJXnJU.mjs → invite._token-CPSvdDox.mjs} +4 -4
- package/.output/server/_ssr/{invite._token-YSls7Te8.mjs → invite._token-CRBsTSTZ.mjs} +6 -6
- package/.output/server/_ssr/{invite._token-CGzJGIfm.mjs → invite._token-CaTLt92a.mjs} +2 -2
- package/.output/server/_ssr/{kysely-adapter-DD_FuS-x.mjs → kysely-adapter-DMf5pFTf.mjs} +1 -1
- package/.output/server/_ssr/{lifecycleUi-DOr6Af13.mjs → lifecycleUi-DauKavgx.mjs} +1 -2
- package/.output/server/_ssr/logger-EinA2J6y.mjs +2 -0
- package/.output/server/_ssr/{loopApi-Vb7RrshX.mjs → loopApi-Im6JSK0p.mjs} +10 -11
- package/.output/server/_ssr/{loopProjection-DfpYkHDU.mjs → loopProjection-DIpGR6rp.mjs} +2 -4
- package/.output/server/_ssr/{loops._loopId-BKUT85ip.mjs → loops._loopId-B5iPseDG.mjs} +13 -27
- package/.output/server/_ssr/{loops._loopId_.runs._runId-CBXFBNrN.mjs → loops._loopId_.runs._runId-BA-P6sGA.mjs} +6 -44
- package/.output/server/_ssr/{loops._loopId_.runs._runId-Bha29x1u.mjs → loops._loopId_.runs._runId-DVtG0xYb.mjs} +3 -3
- package/.output/server/_ssr/{machineFns-BFBPpfxd.mjs → machineFns-Bb9KNXun.mjs} +3 -5
- package/.output/server/_ssr/{node-sqlite-dialect-BDYK0mnk.mjs → node-sqlite-dialect-Dho9YjXb.mjs} +2 -2
- package/.output/server/_ssr/{router-DfegU72d.mjs → router-DPz3uoUr.mjs} +22 -30
- package/.output/server/_ssr/{routes-BwTvON37.mjs → routes-B9zD2h4Y.mjs} +5 -7
- package/.output/server/_ssr/{routes-Bx3C3K_H.mjs → routes-BuILEmHs.mjs} +5 -5
- package/.output/server/_ssr/{routes-D5GHah06.mjs → routes-FO0MFfYK.mjs} +2 -2
- package/.output/server/_ssr/runDiff-BLdot9mi.mjs +2 -0
- package/.output/server/_ssr/ssr.mjs +39 -39
- package/.output/server/_ssr/store-CFCp_ggC.mjs +2 -0
- package/.output/server/_ssr/{t._teamId-CpPl9_XU.mjs → t._teamId-1t5wOXrV.mjs} +5 -5
- package/.output/server/_ssr/{t._teamId-BFs1MfBR.mjs → t._teamId-A37pQ8Vp.mjs} +5 -5
- package/.output/server/_ssr/{t._teamId-CrZ7EkUL.mjs → t._teamId-D1BTwhWf.mjs} +2 -2
- package/.output/server/_ssr/{teamFns-BVjiPvv8.mjs → teamFns-DfqtuBW2.mjs} +3 -7
- package/.output/server/_ssr/{teamFns-3lItAtfc.mjs → teamFns-cI_tsiCv.mjs} +2 -3
- package/.output/server/_ssr/{tokens-DUsrVE9g.mjs → tokens-CUz4LDLm.mjs} +1 -1
- package/.output/server/_ssr/{ui-BEPYdOmJ.mjs → ui-DGz-q9MT.mjs} +1 -22
- package/.output/server/{_tanstack-start-manifest_v-qnHwyto-.mjs → _tanstack-start-manifest_v-CojB4Veh.mjs} +11 -11
- package/.output/server/drizzle/0001_add_pi_agent.sql +9 -0
- package/.output/server/drizzle/meta/_journal.json +7 -0
- package/.output/server/index.mjs +65 -65
- package/drizzle/0001_add_pi_agent.sql +9 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/.output/public/assets/app-CuZ0I3Mb.css +0 -2
- package/.output/server/_ssr/db-DS0gckHd.mjs +0 -2
- package/.output/server/_ssr/logger-BuIBg8Jv.mjs +0 -2
- package/.output/server/_ssr/runDiff-CcG2PgCn.mjs +0 -2
- package/.output/server/_ssr/store-yOJEt4It.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-
|
|
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 = [
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|
}
|
|
@@ -2914,7 +2851,6 @@ async function setMachineOnline(id, online) {
|
|
|
2914
2851
|
lastSeen: nowIso$1()
|
|
2915
2852
|
}).where(eq(machines.id, id));
|
|
2916
2853
|
}
|
|
2917
|
-
/** Deterministic personal-team id for a user (open mode ⇒ the shared "team-shared"). */
|
|
2918
2854
|
function teamIdForUser(userId) {
|
|
2919
2855
|
return `team-${userId ?? "shared"}`;
|
|
2920
2856
|
}
|
|
@@ -2948,7 +2884,6 @@ async function ensureTeam(id, name, ownerUserId) {
|
|
|
2948
2884
|
function newTeamId() {
|
|
2949
2885
|
return `team-${randomUUID().slice(0, 12)}`;
|
|
2950
2886
|
}
|
|
2951
|
-
/** Whether this is the user's renamable but undeletable personal team. */
|
|
2952
2887
|
function isPersonalTeam(team) {
|
|
2953
2888
|
return !!team.ownerUserId && team.id === teamIdForUser(team.ownerUserId);
|
|
2954
2889
|
}
|
|
@@ -2974,16 +2909,12 @@ async function createTeam(name, ownerUserId) {
|
|
|
2974
2909
|
return team;
|
|
2975
2910
|
});
|
|
2976
2911
|
}
|
|
2977
|
-
/** Rename a team (no name-sync fights this — see ensureTeam). */
|
|
2978
2912
|
async function renameTeam(teamId, name) {
|
|
2979
2913
|
await db.update(teams).set({ name }).where(eq(teams.id, teamId));
|
|
2980
2914
|
}
|
|
2981
|
-
/** A user's membership row in a team (undefined ⇒ not a member). */
|
|
2982
2915
|
async function getTeamMember(teamId, userId) {
|
|
2983
2916
|
return (await db.select().from(teamMembers).where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId))))[0];
|
|
2984
2917
|
}
|
|
2985
|
-
/** A team's members joined with their user email/name (owners first, then by
|
|
2986
|
-
* join order). The member-list surface in the team settings UI. */
|
|
2987
2918
|
async function listTeamMembers(teamId) {
|
|
2988
2919
|
return (await db.select({
|
|
2989
2920
|
id: teamMembers.id,
|
|
@@ -2999,7 +2930,6 @@ async function listTeamMembers(teamId) {
|
|
|
2999
2930
|
displayName: r.displayName ?? null
|
|
3000
2931
|
})).sort((a, b) => a.role === b.role ? a.createdAt < b.createdAt ? -1 : 1 : a.role === "owner" ? -1 : 1);
|
|
3001
2932
|
}
|
|
3002
|
-
/** Resolve a user by email for direct membership; undefined means no account yet. */
|
|
3003
2933
|
async function userByEmail(email) {
|
|
3004
2934
|
const r = (await db.select({
|
|
3005
2935
|
id: user.id,
|
|
@@ -3078,7 +3008,6 @@ async function deleteTeamCascade(teamId) {
|
|
|
3078
3008
|
if (outcome === "ok") ensuredTeams.delete(teamId);
|
|
3079
3009
|
return outcome;
|
|
3080
3010
|
}
|
|
3081
|
-
/** Mint an invite for a team. `token` is the caller-supplied wire token. */
|
|
3082
3011
|
async function createInvite(input) {
|
|
3083
3012
|
return (await db.insert(teamInvites).values({
|
|
3084
3013
|
...input,
|
|
@@ -3088,7 +3017,6 @@ async function createInvite(input) {
|
|
|
3088
3017
|
async function getInvite(token) {
|
|
3089
3018
|
return (await db.select().from(teamInvites).where(eq(teamInvites.token, token)))[0];
|
|
3090
3019
|
}
|
|
3091
|
-
/** A team's still-live invites (unredeemed, unexpired), newest first. */
|
|
3092
3020
|
async function listPendingInvites(teamId) {
|
|
3093
3021
|
const now = nowIso$1();
|
|
3094
3022
|
return db.select().from(teamInvites).where(and(eq(teamInvites.teamId, teamId), isNull(teamInvites.redeemedAt), gt(teamInvites.expiresAt, now))).orderBy(desc(teamInvites.createdAt));
|
|
@@ -3120,23 +3048,18 @@ async function redeemInviteAtomic(token, userId, grant) {
|
|
|
3120
3048
|
return true;
|
|
3121
3049
|
});
|
|
3122
3050
|
}
|
|
3123
|
-
/** Revoke a pending invite (owner action). */
|
|
3124
3051
|
async function deleteInvite(token) {
|
|
3125
3052
|
await db.delete(teamInvites).where(eq(teamInvites.token, token));
|
|
3126
3053
|
}
|
|
3127
3054
|
async function getTeam(id) {
|
|
3128
3055
|
return (await db.select().from(teams).where(eq(teams.id, id)))[0];
|
|
3129
3056
|
}
|
|
3130
|
-
/** Teams the user belongs to (membership join), newest first. Drives the team
|
|
3131
|
-
* switcher — a regular user has just their personal team (no dropdown). */
|
|
3132
3057
|
async function listTeamsForUser(userId) {
|
|
3133
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);
|
|
3134
3059
|
}
|
|
3135
|
-
/** Whether the user is a member of the team (authorizes a team-switch request). */
|
|
3136
3060
|
async function isTeamMember(teamId, userId) {
|
|
3137
3061
|
return !!(await db.select({ id: teamMembers.id }).from(teamMembers).where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId))))[0];
|
|
3138
3062
|
}
|
|
3139
|
-
/** Does the server already have metadata for this blob hash? (drives needHashes). */
|
|
3140
3063
|
async function blobExists(hash) {
|
|
3141
3064
|
return !!(await db.select({ hash: blobs.hash }).from(blobs).where(eq(blobs.hash, hash)))[0];
|
|
3142
3065
|
}
|
|
@@ -3150,7 +3073,6 @@ async function blobSizes(hashes) {
|
|
|
3150
3073
|
}).from(blobs).where(inArray(blobs.hash, [...new Set(hashes)]));
|
|
3151
3074
|
return new Map(rows.map((row) => [row.hash, row.size]));
|
|
3152
3075
|
}
|
|
3153
|
-
/** Record verified blob metadata idempotently. */
|
|
3154
3076
|
async function recordBlob(hash, size) {
|
|
3155
3077
|
await db.insert(blobs).values({
|
|
3156
3078
|
hash,
|
|
@@ -3219,16 +3141,12 @@ async function reconcileConfiguredArtifacts(loopId, configuredAtStart, keepPaths
|
|
|
3219
3141
|
return stale.length;
|
|
3220
3142
|
});
|
|
3221
3143
|
}
|
|
3222
|
-
/** The loop's current (non-deleted) file set, path-sorted. */
|
|
3223
3144
|
async function listArtifacts(loopId) {
|
|
3224
3145
|
return db.select().from(artifactFiles).where(and(eq(artifactFiles.loopId, loopId), eq(artifactFiles.deleted, false))).orderBy(artifactFiles.path);
|
|
3225
3146
|
}
|
|
3226
|
-
/** One file row by loop + path (live or tombstoned). */
|
|
3227
3147
|
async function getArtifactFile(loopId, path) {
|
|
3228
3148
|
return (await db.select().from(artifactFiles).where(and(eq(artifactFiles.loopId, loopId), eq(artifactFiles.path, path))))[0];
|
|
3229
3149
|
}
|
|
3230
|
-
/** The loop's CURRENT live file set as a snapshot manifest (path → metadata) —
|
|
3231
|
-
* what report() captures as the terminal run's artifact state. */
|
|
3232
3150
|
async function buildLoopManifest(loopId) {
|
|
3233
3151
|
const manifest = {};
|
|
3234
3152
|
for (const f of await listArtifacts(loopId)) manifest[f.path] = {
|
|
@@ -3256,7 +3174,6 @@ async function putRunSnapshot(runId, loopId, manifest) {
|
|
|
3256
3174
|
}
|
|
3257
3175
|
});
|
|
3258
3176
|
}
|
|
3259
|
-
/** A run's captured snapshot, or undefined when the run predates the feature. */
|
|
3260
3177
|
async function getRunSnapshot(runId) {
|
|
3261
3178
|
return (await db.select().from(runSnapshots).where(eq(runSnapshots.runId, runId)))[0];
|
|
3262
3179
|
}
|
|
@@ -3277,7 +3194,6 @@ async function pruneRunSnapshots(loopId, keep) {
|
|
|
3277
3194
|
const pred = survivors.length ? and(eq(runSnapshots.loopId, loopId), notInArray(runSnapshots.runId, survivors)) : eq(runSnapshots.loopId, loopId);
|
|
3278
3195
|
return (await db.delete(runSnapshots).where(pred).returning({ id: runSnapshots.runId })).length;
|
|
3279
3196
|
}
|
|
3280
|
-
/** Distinct loop ids that currently have at least one run snapshot. */
|
|
3281
3197
|
async function loopIdsWithSnapshots() {
|
|
3282
3198
|
return (await db.selectDistinct({ loopId: runSnapshots.loopId }).from(runSnapshots)).map((r) => r.loopId);
|
|
3283
3199
|
}
|
|
@@ -3328,7 +3244,6 @@ async function countRunSnapshots() {
|
|
|
3328
3244
|
const r = (await db.select({ n: sql`count(*)` }).from(runSnapshots))[0];
|
|
3329
3245
|
return Number(r?.n ?? 0);
|
|
3330
3246
|
}
|
|
3331
|
-
/** Server-side artifact-sync helpers: path safety, byte caps, and hashing. */
|
|
3332
3247
|
/** Per-file byte cap. At or under ⇒ bytes sync; over ⇒ metadata-only (no bytes). */
|
|
3333
3248
|
var BLOB_CAP = 10 * 1024 * 1024;
|
|
3334
3249
|
/** Hard ceiling on the exact-manifest sync POST body. */
|
|
@@ -3352,7 +3267,6 @@ function exactRecord(value, fields) {
|
|
|
3352
3267
|
function sha256Buf(bytes) {
|
|
3353
3268
|
return createHash("sha256").update(bytes).digest("hex");
|
|
3354
3269
|
}
|
|
3355
|
-
/** A 64-char lowercase hex sha256. */
|
|
3356
3270
|
function isValidHash(hash) {
|
|
3357
3271
|
return typeof hash === "string" && /^[0-9a-f]{64}$/.test(hash);
|
|
3358
3272
|
}
|
|
@@ -3423,11 +3337,9 @@ function parseArtifactSyncBody(value) {
|
|
|
3423
3337
|
function sha256(s) {
|
|
3424
3338
|
return createHash("sha256").update(s).digest("hex");
|
|
3425
3339
|
}
|
|
3426
|
-
/** Mint a fresh device token (`dk_…`) — the one wire format `machineIdFromToken` consumes. */
|
|
3427
3340
|
function mintDeviceToken() {
|
|
3428
3341
|
return `dk_${randomBytes(15).toString("hex")}`;
|
|
3429
3342
|
}
|
|
3430
|
-
/** Derive the stable machine id from its device token. */
|
|
3431
3343
|
function machineIdFromToken(token) {
|
|
3432
3344
|
return `m-${sha256(token).slice(0, 16)}`;
|
|
3433
3345
|
}
|
|
@@ -3460,7 +3372,6 @@ async function rememberConnectKey(connectKey, intent) {
|
|
|
3460
3372
|
set: row
|
|
3461
3373
|
});
|
|
3462
3374
|
}
|
|
3463
|
-
/** Peek (NON-evicting) the team/minter a connect-key was minted under, if still live. */
|
|
3464
3375
|
async function readClaimIntent(connectKey, now = Date.now()) {
|
|
3465
3376
|
if (!connectKey) return void 0;
|
|
3466
3377
|
const row = (await db.select().from(connectKeys).where(eq(connectKeys.machineId, machineIdFromToken(connectKey))))[0];
|
|
@@ -3470,7 +3381,6 @@ async function readClaimIntent(connectKey, now = Date.now()) {
|
|
|
3470
3381
|
teamId: row.teamId
|
|
3471
3382
|
};
|
|
3472
3383
|
}
|
|
3473
|
-
/** The remembered owner of a self-registering machine, if any (still-live key). */
|
|
3474
3384
|
async function getDeviceOwner(machineId, now = Date.now()) {
|
|
3475
3385
|
const row = (await db.select().from(connectKeys).where(eq(connectKeys.machineId, machineId)))[0];
|
|
3476
3386
|
if (!row || !connectKeyFresh(row.mintedAt, now)) return void 0;
|
|
@@ -3533,7 +3443,6 @@ var newIdempotency = /* @__PURE__ */ new Map();
|
|
|
3533
3443
|
function pruneNewIdempotency(now) {
|
|
3534
3444
|
for (const [key, rec] of newIdempotency) if (now - rec.at > 9e5) newIdempotency.delete(key);
|
|
3535
3445
|
}
|
|
3536
|
-
/** Remember that `key` (from THIS machine) created `loopId`. Pruned on write. */
|
|
3537
3446
|
function recordNewIdempotency(key, machineId, loopId, now = Date.now()) {
|
|
3538
3447
|
pruneNewIdempotency(now);
|
|
3539
3448
|
newIdempotency.set(key, {
|
|
@@ -3595,8 +3504,6 @@ function validIdentifiers(value, rejectNumericLeadingZero) {
|
|
|
3595
3504
|
return !rejectNumericLeadingZero || !/^\d+$/.test(identifier) || identifier === "0" || !identifier.startsWith("0");
|
|
3596
3505
|
});
|
|
3597
3506
|
}
|
|
3598
|
-
/** Parse the leading numeric core `x.y.z` of a version, ignoring any pre-release
|
|
3599
|
-
* / build suffix. Returns null when it isn't a recognizable version. */
|
|
3600
3507
|
function core(v) {
|
|
3601
3508
|
const m = /^\s*v?(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
3602
3509
|
if (!m) return null;
|
|
@@ -3606,7 +3513,6 @@ function core(v) {
|
|
|
3606
3513
|
Number(m[3])
|
|
3607
3514
|
];
|
|
3608
3515
|
}
|
|
3609
|
-
/** Does `v` carry a pre-release suffix (e.g. "1.2.3-rc.1")? */
|
|
3610
3516
|
function isPre(v) {
|
|
3611
3517
|
return /^\s*v?\d+\.\d+\.\d+-/.test(v);
|
|
3612
3518
|
}
|
|
@@ -3706,7 +3612,6 @@ async function readJsonBody(request, maxBytes) {
|
|
|
3706
3612
|
return { kind: "invalid" };
|
|
3707
3613
|
}
|
|
3708
3614
|
}
|
|
3709
|
-
/** Shared clipping cap for large prompt and terminal telemetry strings. */
|
|
3710
3615
|
var WIRE_TEXT_CAP = 512 * 1024;
|
|
3711
3616
|
/** Validate an optional poll string without coercion, clipping, or NUL removal. */
|
|
3712
3617
|
function validOptionalPollString(value, cap) {
|
|
@@ -3723,7 +3628,6 @@ function nowIso() {
|
|
|
3723
3628
|
function stripNul(s) {
|
|
3724
3629
|
return s.replace(/\u0000/g, "");
|
|
3725
3630
|
}
|
|
3726
|
-
/** Clip a free-text wire field and strip NUL before persistence. */
|
|
3727
3631
|
function clipText(s, cap) {
|
|
3728
3632
|
return stripNul(s.slice(0, cap));
|
|
3729
3633
|
}
|
|
@@ -3880,7 +3784,6 @@ var LocalBlobStore = class {
|
|
|
3880
3784
|
await this.syncDir(path.dirname(target));
|
|
3881
3785
|
}
|
|
3882
3786
|
};
|
|
3883
|
-
/** In-memory blob store — injectable test adapter. */
|
|
3884
3787
|
var MemoryBlobStore = class {
|
|
3885
3788
|
map = /* @__PURE__ */ new Map();
|
|
3886
3789
|
async has(hash) {
|
|
@@ -3896,11 +3799,9 @@ var MemoryBlobStore = class {
|
|
|
3896
3799
|
this.map.delete(hash);
|
|
3897
3800
|
}
|
|
3898
3801
|
};
|
|
3899
|
-
/** Object key for a blob hash. Flat namespace under a prefix — hashes are unique. */
|
|
3900
3802
|
function blobKey(hash) {
|
|
3901
3803
|
return `blobs/${hash}`;
|
|
3902
3804
|
}
|
|
3903
|
-
/** Cloudflare R2 blob store over the S3-compatible API (AWS SDK v3). */
|
|
3904
3805
|
var R2BlobStore = class {
|
|
3905
3806
|
cfg;
|
|
3906
3807
|
client;
|
|
@@ -3967,7 +3868,6 @@ var R2BlobStore = class {
|
|
|
3967
3868
|
}));
|
|
3968
3869
|
}
|
|
3969
3870
|
};
|
|
3970
|
-
/** S3 "object absent" maps to a 404 / NoSuchKey / NotFound error shape. */
|
|
3971
3871
|
function isNotFound(err) {
|
|
3972
3872
|
const e = err;
|
|
3973
3873
|
return e?.name === "NotFound" || e?.name === "NoSuchKey" || e?.$metadata?.httpStatusCode === 404;
|
|
@@ -4158,35 +4058,21 @@ async function authenticateDeviceToken(deviceToken, options = {}) {
|
|
|
4158
4058
|
machine
|
|
4159
4059
|
};
|
|
4160
4060
|
}
|
|
4161
|
-
/** Whether a token needs quoting inside a key/value line or comma-delimited row. */
|
|
4162
4061
|
function needsQuote(s) {
|
|
4163
4062
|
return s === "" || /[\s,:"]/.test(s);
|
|
4164
4063
|
}
|
|
4165
|
-
/** Quote and escape a string so it remains on one line. */
|
|
4166
4064
|
function quote(s) {
|
|
4167
4065
|
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\r?\n/g, "\\n")}"`;
|
|
4168
4066
|
}
|
|
4169
|
-
/** Render one scalar the TOON way: a finite number / boolean bare, null|undefined as
|
|
4170
|
-
* the em-dash placeholder, a string bare unless it needs quoting. */
|
|
4171
4067
|
function scalar(v) {
|
|
4172
4068
|
if (v === null || v === void 0) return "—";
|
|
4173
4069
|
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "—";
|
|
4174
4070
|
if (typeof v === "boolean") return v ? "true" : "false";
|
|
4175
4071
|
return needsQuote(v) ? quote(v) : v;
|
|
4176
4072
|
}
|
|
4177
|
-
/** A single top-level `key: value` line (value scalar-rendered). */
|
|
4178
4073
|
function kvLine(key, v) {
|
|
4179
4074
|
return `${key}: ${scalar(v)}`;
|
|
4180
4075
|
}
|
|
4181
|
-
/**
|
|
4182
|
-
* A detail block: a top key, then each field indented two spaces as `key: value`.
|
|
4183
|
-
* The value may be a pre-rendered raw string (pass a `{ raw }`) so composite values
|
|
4184
|
-
* (e.g. a size hint or an already-quoted body) render verbatim.
|
|
4185
|
-
*
|
|
4186
|
-
* topKey:
|
|
4187
|
-
* name: "Docs Sweep"
|
|
4188
|
-
* cron: "0 6 * * 1"
|
|
4189
|
-
*/
|
|
4190
4076
|
function detailBlock(topKey, rows) {
|
|
4191
4077
|
const lines = [`${topKey}:`];
|
|
4192
4078
|
for (const [k, v] of rows) {
|
|
@@ -4195,44 +4081,25 @@ function detailBlock(topKey, rows) {
|
|
|
4195
4081
|
}
|
|
4196
4082
|
return lines.join("\n");
|
|
4197
4083
|
}
|
|
4198
|
-
/** Render a count, optionally with total or displayed-window context. */
|
|
4199
4084
|
function countLine(count, opts = {}) {
|
|
4200
4085
|
if (opts.total !== void 0) return `count: ${count} of ${opts.total} total`;
|
|
4201
4086
|
if (opts.showing !== void 0) return `count: ${count} (showing first ${opts.showing})`;
|
|
4202
4087
|
return `count: ${count}`;
|
|
4203
4088
|
}
|
|
4204
|
-
/**
|
|
4205
|
-
* A typed list block: the header row `name[N]{f1,f2}:` then each record as an
|
|
4206
|
-
* indented comma row (cells scalar-rendered, quoted only when needed). Does NOT emit
|
|
4207
|
-
* the `count:` line — compose it with `countLine` so the caller controls the
|
|
4208
|
-
* aggregate flavor.
|
|
4209
|
-
*
|
|
4210
|
-
* loops[2]{id,name,cron,enabled}:
|
|
4211
|
-
* loop-abc,"Docs Sweep","0 6 * * 1",on
|
|
4212
|
-
*/
|
|
4213
4089
|
function listBlock(name, fields, rows) {
|
|
4214
4090
|
const lines = [`${name}[${rows.length}]{${fields.join(",")}}:`];
|
|
4215
4091
|
for (const row of rows) lines.push(` ${row.map(scalar).join(",")}`);
|
|
4216
4092
|
return lines.join("\n");
|
|
4217
4093
|
}
|
|
4218
|
-
/** Render an empty named collection. */
|
|
4219
4094
|
function emptyList(name) {
|
|
4220
4095
|
return `${name}: []`;
|
|
4221
4096
|
}
|
|
4222
|
-
/** Render command templates as an indented help block. */
|
|
4223
4097
|
function helpBlock(lines) {
|
|
4224
4098
|
return [`help[${lines.length}]:`, ...lines.map((l) => ` ${l}`)].join("\n");
|
|
4225
4099
|
}
|
|
4226
|
-
/**
|
|
4227
|
-
* Render a quoted error plus a machine-readable code.
|
|
4228
|
-
*
|
|
4229
|
-
* error: "status must be keep|no-change|block (got \"wibble\")"
|
|
4230
|
-
* code: VALIDATION_ERROR
|
|
4231
|
-
*/
|
|
4232
4100
|
function errorBlock(message, code) {
|
|
4233
4101
|
return `error: ${quote(message)}\ncode: ${code}`;
|
|
4234
4102
|
}
|
|
4235
|
-
/** Map an HTTP status to its CLI error code. */
|
|
4236
4103
|
function codeForStatus(status) {
|
|
4237
4104
|
switch (status) {
|
|
4238
4105
|
case 400: return "VALIDATION_ERROR";
|
|
@@ -4244,7 +4111,6 @@ function codeForStatus(status) {
|
|
|
4244
4111
|
default: return "ERROR";
|
|
4245
4112
|
}
|
|
4246
4113
|
}
|
|
4247
|
-
/** Clip content at `cap` and append its original size plus recovery hint. */
|
|
4248
4114
|
function truncate(text, cap, tail = "use --full to see complete body") {
|
|
4249
4115
|
if (text.length <= cap) return {
|
|
4250
4116
|
value: text,
|
|
@@ -4255,8 +4121,6 @@ function truncate(text, cap, tail = "use --full to see complete body") {
|
|
|
4255
4121
|
truncated: true
|
|
4256
4122
|
};
|
|
4257
4123
|
}
|
|
4258
|
-
/** Join document sections with a single newline, dropping empty/blank sections so a
|
|
4259
|
-
* missing optional block (e.g. no rejections) leaves no stray blank line. */
|
|
4260
4124
|
function doc(...sections) {
|
|
4261
4125
|
return sections.filter((s) => typeof s === "string" && s.length > 0).join("\n");
|
|
4262
4126
|
}
|
|
@@ -4323,6 +4187,24 @@ function optionalProvider(value, field) {
|
|
|
4323
4187
|
value: normalizeProviderSetting(value)
|
|
4324
4188
|
};
|
|
4325
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
|
+
}
|
|
4326
4208
|
function validTimezone(timezone) {
|
|
4327
4209
|
try {
|
|
4328
4210
|
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
@@ -4534,7 +4416,6 @@ function statusDefinitionsFromLoop(loop) {
|
|
|
4534
4416
|
block: loop.statusBlock
|
|
4535
4417
|
};
|
|
4536
4418
|
}
|
|
4537
|
-
/** The single owner-facing structured loop shape used by show/list JSON output. */
|
|
4538
4419
|
function canonicalLoopEnvelope(loop) {
|
|
4539
4420
|
return {
|
|
4540
4421
|
id: loop.id,
|
|
@@ -4575,13 +4456,13 @@ function validateLoopCreate(value) {
|
|
|
4575
4456
|
if (!artifacts.ok) return artifacts;
|
|
4576
4457
|
const model = optionalProvider(raw.model, "model");
|
|
4577
4458
|
if (!model.ok) return model;
|
|
4578
|
-
const reasoningEffort = optionalProvider(raw.reasoningEffort, "reasoningEffort");
|
|
4579
|
-
if (!reasoningEffort.ok) return reasoningEffort;
|
|
4580
4459
|
const agent = coerceCodingAgent(raw.agent);
|
|
4581
4460
|
if (!agent) return {
|
|
4582
4461
|
ok: false,
|
|
4583
4462
|
detail: `agent is required and must be one of ${CODING_AGENTS.join(", ")}`
|
|
4584
4463
|
};
|
|
4464
|
+
const reasoningEffort = validateReasoningEffort(raw.reasoningEffort, agent);
|
|
4465
|
+
if (!reasoningEffort.ok) return reasoningEffort;
|
|
4585
4466
|
if (raw.enabled !== void 0 && typeof raw.enabled !== "boolean") return {
|
|
4586
4467
|
ok: false,
|
|
4587
4468
|
detail: "enabled must be boolean"
|
|
@@ -4668,19 +4549,21 @@ function validateLoopEdit(loop, value) {
|
|
|
4668
4549
|
if (!result.ok) return result;
|
|
4669
4550
|
update.model = result.value;
|
|
4670
4551
|
}
|
|
4671
|
-
|
|
4672
|
-
const result = optionalProvider(raw.reasoningEffort, "reasoningEffort");
|
|
4673
|
-
if (!result.ok) return result;
|
|
4674
|
-
update.reasoningEffort = result.value;
|
|
4675
|
-
}
|
|
4552
|
+
let effectiveAgent = loop.agent;
|
|
4676
4553
|
if (raw.agent !== void 0) {
|
|
4677
4554
|
const agent = coerceCodingAgent(raw.agent);
|
|
4678
4555
|
if (!agent) return {
|
|
4679
4556
|
ok: false,
|
|
4680
4557
|
detail: `agent must be one of ${CODING_AGENTS.join(", ")}`
|
|
4681
4558
|
};
|
|
4559
|
+
effectiveAgent = agent;
|
|
4682
4560
|
update.agent = agent;
|
|
4683
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
|
+
}
|
|
4684
4567
|
if (raw.enabled !== void 0) {
|
|
4685
4568
|
if (typeof raw.enabled !== "boolean") return {
|
|
4686
4569
|
ok: false,
|
|
@@ -4747,7 +4630,6 @@ var ONLINE_TTL_MS = 3e4;
|
|
|
4747
4630
|
/** Auto-pause after this many consecutive errors. Canceled rows are transparent;
|
|
4748
4631
|
* zero disables the breaker. */
|
|
4749
4632
|
var AUTOPAUSE_STREAK = Math.max(0, Number(process.env.PIEVO_FAILURE_AUTOPAUSE_STREAK ?? 3));
|
|
4750
|
-
/** How long pending system work on an offline machine stays claimable. */
|
|
4751
4633
|
var DEFERRED_MAX_MS = 7 * 864e5;
|
|
4752
4634
|
/** A claimed run that never reports within this window is reclaimed as timed out. */
|
|
4753
4635
|
var configuredRunTimeoutMs = Number(process.env.PIEVO_RUN_TIMEOUT_MS || 20 * 6e4);
|
|
@@ -4762,9 +4644,9 @@ var EDITABLE_LOOP_FIELDS = LOOP_EDIT_FIELDS;
|
|
|
4762
4644
|
* it never satisfies the successful-run reporting protocol. Run errors share
|
|
4763
4645
|
* this cap. Exported for `cli.ts` so the report verb uses the same budget. */
|
|
4764
4646
|
var MESSAGE_CAP = 2e3;
|
|
4765
|
-
/**
|
|
4647
|
+
/** Provider session IDs are opaque short tokens; this rejects malformed payloads
|
|
4648
|
+
* without constraining known provider formats. */
|
|
4766
4649
|
var SESSION_ID_CAP = 200;
|
|
4767
|
-
/** Keep heartbeat throttling safely inside custom short timeout windows. */
|
|
4768
4650
|
function heartbeatRefreshMs(runTimeoutMs) {
|
|
4769
4651
|
if (!Number.isFinite(runTimeoutMs) || runTimeoutMs <= 0) return 1;
|
|
4770
4652
|
return Math.max(1, Math.min(6e4, runTimeoutMs / 3));
|
|
@@ -4970,7 +4852,6 @@ var MachineGateway = class {
|
|
|
4970
4852
|
* process-local; unlike durable run leases, a deploy drops them and the daemon
|
|
4971
4853
|
* simply re-polls. */
|
|
4972
4854
|
pollWaiters = /* @__PURE__ */ new Map();
|
|
4973
|
-
/** Resolve (and disarm) a machine's parked long-poll waiter, if any. */
|
|
4974
4855
|
wakeMachine(machineId) {
|
|
4975
4856
|
this.pollWaiters.get(machineId)?.(true);
|
|
4976
4857
|
}
|
|
@@ -5138,7 +5019,6 @@ var MachineGateway = class {
|
|
|
5138
5019
|
body: { delivery }
|
|
5139
5020
|
};
|
|
5140
5021
|
}
|
|
5141
|
-
/** Protocol-v4 poll: plural local state, one new delivery per poll. */
|
|
5142
5022
|
async pollV4(deviceToken, request) {
|
|
5143
5023
|
const rawRequest = request;
|
|
5144
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 {
|
|
@@ -5208,7 +5088,6 @@ var MachineGateway = class {
|
|
|
5208
5088
|
}
|
|
5209
5089
|
};
|
|
5210
5090
|
}
|
|
5211
|
-
/** Idle v4 polls long-poll; active/reporting polls return immediately. */
|
|
5212
5091
|
async pollV4Wait(deviceToken, request, waitMs = LONG_POLL_WAIT_MS) {
|
|
5213
5092
|
if (request.currentRuns?.length || request.protocolVersion !== 4) return this.pollV4(deviceToken, request);
|
|
5214
5093
|
const auth = await authenticateDeviceToken(deviceToken, { allowUnknown: true });
|
|
@@ -5267,8 +5146,6 @@ var MachineGateway = class {
|
|
|
5267
5146
|
}
|
|
5268
5147
|
};
|
|
5269
5148
|
}
|
|
5270
|
-
/** Create a validated loop through device authority, bind it to that machine,
|
|
5271
|
-
* and schedule it immediately. */
|
|
5272
5149
|
async createLoop(deviceToken, body) {
|
|
5273
5150
|
const auth = await authenticateDeviceToken(deviceToken);
|
|
5274
5151
|
if (!auth.ok) return auth.response;
|
|
@@ -5361,9 +5238,6 @@ var MachineGateway = class {
|
|
|
5361
5238
|
};
|
|
5362
5239
|
}
|
|
5363
5240
|
}
|
|
5364
|
-
/** List loops bound to this machine. `--fields` extends the minimal default
|
|
5365
|
-
* columns and rejects unknown names. `--json` returns full structured records
|
|
5366
|
-
* (first byte `[`), mirroring `show --json` — the daemon prints `text` either way. */
|
|
5367
5241
|
async listLoops(deviceToken, fieldsFlag, json) {
|
|
5368
5242
|
const auth = await authenticateDeviceToken(deviceToken);
|
|
5369
5243
|
if (!auth.ok) return auth.response;
|
|
@@ -5409,7 +5283,6 @@ var MachineGateway = class {
|
|
|
5409
5283
|
}
|
|
5410
5284
|
};
|
|
5411
5285
|
}
|
|
5412
|
-
/** Edit a loop through owner authority, scoped to loops bound to this machine. */
|
|
5413
5286
|
async editLoop(deviceToken, id, patch, dryRun = false) {
|
|
5414
5287
|
const auth = await authenticateDeviceToken(deviceToken);
|
|
5415
5288
|
if (!auth.ok) return auth.response;
|
|
@@ -5431,20 +5304,30 @@ var MachineGateway = class {
|
|
|
5431
5304
|
};
|
|
5432
5305
|
const update = validated.value;
|
|
5433
5306
|
const applied = Object.keys(patch ?? {});
|
|
5434
|
-
if (dryRun)
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
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 = {
|
|
5439
5315
|
id: loop.id,
|
|
5440
5316
|
applied,
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
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
|
+
}
|
|
5448
5331
|
if (Object.keys(update).length === 0) return {
|
|
5449
5332
|
status: 200,
|
|
5450
5333
|
body: {
|
|
@@ -5850,7 +5733,6 @@ function fmtTimeZoned(iso, timezone, opts = {}) {
|
|
|
5850
5733
|
return fmtTime(iso);
|
|
5851
5734
|
}
|
|
5852
5735
|
}
|
|
5853
|
-
/** Minimal default columns for `pievo loops`. */
|
|
5854
5736
|
var LIST_DEFAULT_FIELDS = [
|
|
5855
5737
|
"id",
|
|
5856
5738
|
"name",
|
|
@@ -5858,8 +5740,6 @@ var LIST_DEFAULT_FIELDS = [
|
|
|
5858
5740
|
"enabled",
|
|
5859
5741
|
"nextFire"
|
|
5860
5742
|
];
|
|
5861
|
-
/** The optional columns `--fields` may add (the "available" set an unknown field is
|
|
5862
|
-
* measured against). `runs`/`lastResult` are derived per loop. */
|
|
5863
5743
|
var LIST_OPTIONAL_FIELDS = [
|
|
5864
5744
|
"timezone",
|
|
5865
5745
|
"model",
|
|
@@ -5867,7 +5747,6 @@ var LIST_OPTIONAL_FIELDS = [
|
|
|
5867
5747
|
"runs",
|
|
5868
5748
|
"lastResult"
|
|
5869
5749
|
];
|
|
5870
|
-
/** One `loops` cell for a named column (scalar-rendered by `listBlock`). */
|
|
5871
5750
|
function loopCell(rec, field) {
|
|
5872
5751
|
switch (field) {
|
|
5873
5752
|
case "id": return rec.id;
|
|
@@ -5883,13 +5762,10 @@ function loopCell(rec, field) {
|
|
|
5883
5762
|
default: return null;
|
|
5884
5763
|
}
|
|
5885
5764
|
}
|
|
5886
|
-
/** Render the typed loop list. Columns are the default set
|
|
5887
|
-
* plus any `--fields` extras (validated + resolved by `listLoops`). */
|
|
5888
5765
|
function renderLoopsText(loops, fields) {
|
|
5889
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"]));
|
|
5890
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"]));
|
|
5891
5768
|
}
|
|
5892
|
-
/** One run's result cell, derived from phase + status. */
|
|
5893
5769
|
function runResultToken(r) {
|
|
5894
5770
|
if (r.status === "block") return `block/${r.phase}`;
|
|
5895
5771
|
if (r.phase === "canceled") return "canceled";
|
|
@@ -6069,7 +5945,6 @@ var ArtifactSync = class {
|
|
|
6069
5945
|
body: { ok: true }
|
|
6070
5946
|
};
|
|
6071
5947
|
}
|
|
6072
|
-
/** Read stored content-addressed bytes, or null when absent. */
|
|
6073
5948
|
readBlob(hash) {
|
|
6074
5949
|
if (!isValidHash(hash)) return Promise.resolve(null);
|
|
6075
5950
|
return this.blobStore.get(hash);
|
|
@@ -6090,20 +5965,16 @@ var ArtifactSync = class {
|
|
|
6090
5965
|
* a changed text file degrades to the size-delta marker, like binary/oversize.
|
|
6091
5966
|
*/
|
|
6092
5967
|
var MAX_DIFF_BYTES = 512 * 1024;
|
|
6093
|
-
/** A file is text-diffable only when it has stored bytes and isn't binary/oversize. */
|
|
6094
5968
|
function isText(e) {
|
|
6095
5969
|
return !!e && !e.binary && !e.oversize && !!e.hash;
|
|
6096
5970
|
}
|
|
6097
|
-
/** Within the inline-diff size cap (unknown size ⇒ allowed). */
|
|
6098
5971
|
function withinDiffCap(e) {
|
|
6099
5972
|
return !!e && (e.size == null || e.size <= MAX_DIFF_BYTES);
|
|
6100
5973
|
}
|
|
6101
|
-
/** Load one snapshot blob, or null when recorded bytes are absent. */
|
|
6102
5974
|
async function bytesOf(e) {
|
|
6103
5975
|
if (!e.hash) return null;
|
|
6104
5976
|
return await (await getArtifactSync()).readBlob(e.hash) ?? null;
|
|
6105
5977
|
}
|
|
6106
|
-
/** Unified diff between two versions of a path (empty string ⇒ added/removed side). */
|
|
6107
5978
|
function unified(path, oldText, newText, maxEditLength) {
|
|
6108
5979
|
return maxEditLength === void 0 ? createTwoFilesPatch(path, path, oldText, newText, "previous run", "this run") : createTwoFilesPatch(path, path, oldText, newText, "previous run", "this run", { maxEditLength });
|
|
6109
5980
|
}
|
|
@@ -6606,7 +6477,6 @@ var CliGateway = class {
|
|
|
6606
6477
|
async cli(token, argv) {
|
|
6607
6478
|
return finalizeCli(token.startsWith("dk_") ? await this.deviceCli(token, argv) : await this.runCli(token, argv));
|
|
6608
6479
|
}
|
|
6609
|
-
/** DEVICE-credential branch of the unified CLI. */
|
|
6610
6480
|
async deviceCli(deviceToken, argv) {
|
|
6611
6481
|
const auth = await authenticateDeviceToken(deviceToken, { allowUnknown: true });
|
|
6612
6482
|
if (!auth.ok) return auth.response;
|
|
@@ -6865,7 +6735,6 @@ var CliGateway = class {
|
|
|
6865
6735
|
body: { text: `run already finished: ${runResultToken(stopped)}` }
|
|
6866
6736
|
};
|
|
6867
6737
|
}
|
|
6868
|
-
/** RUN-credential branch of the unified CLI: report and its help only. */
|
|
6869
6738
|
async runCli(runToken, argv) {
|
|
6870
6739
|
const lease = await resolveLease(runToken);
|
|
6871
6740
|
if (!lease) return {
|
|
@@ -6949,7 +6818,6 @@ var CliGateway = class {
|
|
|
6949
6818
|
text: renderReportedText(status, true)
|
|
6950
6819
|
} : derr(applied.status ?? 409, applied.detail ?? "this run is no longer active", applied.code);
|
|
6951
6820
|
}
|
|
6952
|
-
/** The complete run-token command surface: report plus help for report. */
|
|
6953
6821
|
helpText(_lease) {
|
|
6954
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"]));
|
|
6955
6823
|
}
|
|
@@ -7026,7 +6894,6 @@ var CliGateway = class {
|
|
|
7026
6894
|
})), scoped.elsewhere, await recentMachineRuns(loops, 3));
|
|
7027
6895
|
}
|
|
7028
6896
|
};
|
|
7029
|
-
/** Shared refusal for terminal-report-only grace after reclaim. */
|
|
7030
6897
|
var TERMINAL_GRACE_MSG = "this run is terminal and no longer accepts commands; its final result is delivered via the terminal report";
|
|
7031
6898
|
/** Verbs that require device authority. `report` is run-only and rejected there. */
|
|
7032
6899
|
var DEVICE_ONLY_VERBS = new Set([
|
|
@@ -7111,21 +6978,18 @@ function finalizeCli(res) {
|
|
|
7111
6978
|
}
|
|
7112
6979
|
return res;
|
|
7113
6980
|
}
|
|
7114
|
-
/** Render the compact run-status confirmation. */
|
|
7115
6981
|
function renderReportedText(status, hasMessage) {
|
|
7116
6982
|
const parts = [];
|
|
7117
6983
|
if (status) parts.push(`status=${status}`);
|
|
7118
6984
|
if (hasMessage) parts.push("message recorded");
|
|
7119
6985
|
return `reported: ${parts.length ? parts.join(" · ") : "recorded"}`;
|
|
7120
6986
|
}
|
|
7121
|
-
/** RUN-credential verb help (in-run `rk_` lease). */
|
|
7122
6987
|
var RUN_VERB_HELP = { report: {
|
|
7123
6988
|
syntax: "report --status keep|no-change|block --message <text>",
|
|
7124
6989
|
summary: "record this run's required status and non-empty summary",
|
|
7125
6990
|
avail: () => "always available",
|
|
7126
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"]
|
|
7127
6992
|
} };
|
|
7128
|
-
/** DEVICE-credential verb help (owner `dk_` device token). */
|
|
7129
6993
|
var DEVICE_VERB_HELP = {
|
|
7130
6994
|
new: {
|
|
7131
6995
|
syntax: "new --json '<config>' [--dry-run] [--connect-key <dk_…>] [--server-url <url>]",
|
|
@@ -7178,25 +7042,19 @@ var DEVICE_VERB_HELP = {
|
|
|
7178
7042
|
help: ["Run `pievo log <id> --json` for structured history", "Run `pievo log <id> --run 12 --diff` for bounded run detail and artifact diff"]
|
|
7179
7043
|
}
|
|
7180
7044
|
};
|
|
7181
|
-
/** Render per-verb help. A run lease selects the report-only help;
|
|
7182
|
-
* no lease means the device (owner) surface. Returns undefined for a verb
|
|
7183
|
-
* with no help spec, so the caller falls back to its unknown-command handling. */
|
|
7184
7045
|
function verbHelpText(verb, lease) {
|
|
7185
7046
|
const spec = lease ? RUN_VERB_HELP[verb] : DEVICE_VERB_HELP[verb];
|
|
7186
7047
|
if (!spec) return void 0;
|
|
7187
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));
|
|
7188
7049
|
}
|
|
7189
|
-
/** The canonical editable config emitted by `show --json`. */
|
|
7190
7050
|
function loopEnvelope(loop) {
|
|
7191
7051
|
return { ...canonicalLoopEnvelope(loop) };
|
|
7192
7052
|
}
|
|
7193
|
-
/** Render a large prompt in full or as a compact presence hint. */
|
|
7194
7053
|
function contentField(value, full) {
|
|
7195
7054
|
if (value == null) return "absent";
|
|
7196
7055
|
if (full) return value;
|
|
7197
7056
|
return { raw: `present, ${value.length} bytes — use --full to see` };
|
|
7198
7057
|
}
|
|
7199
|
-
/** The next cadence fire, formatted in the loop's timezone. */
|
|
7200
7058
|
function nextFireDisplay(loop) {
|
|
7201
7059
|
const schedule = scheduleFromLoop(loop);
|
|
7202
7060
|
if (schedule.mode === "continuous") return `after exec terminal + ${schedule.delayMinutes}m`;
|
|
@@ -7204,7 +7062,6 @@ function nextFireDisplay(loop) {
|
|
|
7204
7062
|
if (!iso) return "(never)";
|
|
7205
7063
|
return fmtTimeZoned(iso, schedule.timezone, { seconds: true });
|
|
7206
7064
|
}
|
|
7207
|
-
/** `pievo show` renders the editable envelope followed by derived read-only state. */
|
|
7208
7065
|
function renderShowText(loop, env, totalRuns, lastRun, opts = {}) {
|
|
7209
7066
|
const full = opts.full === true;
|
|
7210
7067
|
const block = detailBlock("loop", [
|
|
@@ -7228,14 +7085,12 @@ function renderShowText(loop, env, totalRuns, lastRun, opts = {}) {
|
|
|
7228
7085
|
];
|
|
7229
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));
|
|
7230
7087
|
}
|
|
7231
|
-
/** The static one-line description in the home header. */
|
|
7232
7088
|
var HOME_DESCRIPTION = "Run your scheduled Pievo agent loops on this machine with your own coding agent.";
|
|
7233
7089
|
/** Expand a leading `~/` against the daemon-supplied home dir (the SERVER's own home
|
|
7234
7090
|
* is irrelevant — a loop's paths are the daemon machine's). Absent home ⇒ unchanged. */
|
|
7235
7091
|
function expandHome(p, home) {
|
|
7236
7092
|
return home && p.startsWith("~/") ? path.join(home, p.slice(2)) : p;
|
|
7237
7093
|
}
|
|
7238
|
-
/** A loop's folder on the daemon machine is its configured workdir. */
|
|
7239
7094
|
function scopeLoopDir(workdir, home) {
|
|
7240
7095
|
return path.resolve(expandHome(workdir, home));
|
|
7241
7096
|
}
|
|
@@ -7264,8 +7119,6 @@ function scopeLoopsByCwd(loops, cwd, home) {
|
|
|
7264
7119
|
elsewhere: loops.length - matched.length
|
|
7265
7120
|
};
|
|
7266
7121
|
}
|
|
7267
|
-
/** The most recent runs across ALL of a machine's loops, newest-first, for the home
|
|
7268
|
-
* `recent[]` block. Merges each loop's newest few then globally sorts by ts. */
|
|
7269
7122
|
async function recentMachineRuns(loops, n) {
|
|
7270
7123
|
const rows = [];
|
|
7271
7124
|
for (const l of loops) for (const r of await listRuns(l.id, n)) rows.push({
|
|
@@ -7396,7 +7249,6 @@ var Scheduler = class {
|
|
|
7396
7249
|
enqueueInitialExec(id) {
|
|
7397
7250
|
return this.enqueue(id, { requestedBy: "system" });
|
|
7398
7251
|
}
|
|
7399
|
-
/** Poll fallback and timer callback converge here. */
|
|
7400
7252
|
async advanceDueSchedules(machineId, loopId) {
|
|
7401
7253
|
const advanced = await advanceDueSchedules((/* @__PURE__ */ new Date()).toISOString(), {
|
|
7402
7254
|
machineId,
|