@kky42/pievo-server 2.4.13 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{actionUi-D8m1FDHO.js → actionUi-BZrLpgHT.js} +1 -1
  3. package/.output/public/assets/app-DXLuQNb9.css +2 -0
  4. package/.output/public/assets/index-Di_wpAtL.js +10 -0
  5. package/.output/public/assets/loops._loopId-X3ClNPMY.js +67 -0
  6. package/.output/public/assets/loops._loopId_.runs._runId-ByU2BSyF.js +3 -0
  7. package/.output/public/assets/preload-helper-hRTYB_ju.js +3 -0
  8. package/.output/public/assets/{routes-79D939J4.js → routes-BIOJZsig.js} +1 -1
  9. package/.output/public/assets/routes-K8D5uBiV.js +1 -0
  10. package/.output/server/_chunks/ssr-renderer.mjs +1 -1
  11. package/.output/server/_libs/{drizzle-orm+postgres.mjs → drizzle-orm.mjs} +93 -2218
  12. package/.output/server/_ssr/{MachinesModal-DBIP648Z.mjs → MachinesModal-CmVMec6B.mjs} +52 -46
  13. package/.output/server/_ssr/{actionUi-C_WswNb1.mjs → actionUi-DE-Pb2tv.mjs} +4 -5
  14. package/.output/server/_ssr/{artifactFiles-BvVmixrs.mjs → artifactFiles-Di1xDKvX.mjs} +5 -7
  15. package/.output/server/_ssr/{artifactKind-DRCbYHZU.mjs → artifactKind-DqpmtxDU.mjs} +3 -9
  16. package/.output/server/_ssr/{boot-U-CFI65B.mjs → boot-CYRi-wez.mjs} +531 -1976
  17. package/.output/server/_ssr/lifecycleUi-BsbUsi5K.mjs +106 -0
  18. package/.output/server/_ssr/{loopApi-C4nN0msL.mjs → loopApi-Bez1rHV0.mjs} +50 -65
  19. package/.output/server/_ssr/{loopProjection-DMSNHux6.mjs → loopProjection-esCjyZJg.mjs} +4 -20
  20. package/.output/server/_ssr/{loops._loopId-Q4pRYPip.mjs → loops._loopId-KUezNY69.mjs} +133 -24
  21. package/.output/server/_ssr/{loops._loopId_.runs._runId-DNXVmCt5.mjs → loops._loopId_.runs._runId-BFP9oMrp.mjs} +2 -7
  22. package/.output/server/_ssr/{loops._loopId_.runs._runId-ty3AhkOi.mjs → loops._loopId_.runs._runId-DOkUEesG.mjs} +18 -33
  23. package/.output/server/_ssr/{machineFns-CkNms3zA.mjs → machineFns-CniGcY8s.mjs} +13 -49
  24. package/.output/server/_ssr/networkAccess-CIlK3ION.mjs +30 -0
  25. package/.output/server/_ssr/router-BFIH2UAE.mjs +362 -0
  26. package/.output/server/_ssr/routes-BSVBANI3.mjs +12 -0
  27. package/.output/server/_ssr/{routes-BaanDO41.mjs → routes-DnE7XDe_.mjs} +242 -77
  28. package/.output/server/_ssr/{routes-B3fulW5I.mjs → routes-ngpX7h8D.mjs} +2 -4
  29. package/.output/server/_ssr/ssr.mjs +25 -34
  30. package/.output/server/_ssr/start-B5_9Z5W-.mjs +35 -0
  31. package/.output/server/{_tanstack-start-manifest_v-DkmxsHDK.mjs → _tanstack-start-manifest_v-DnwAupbZ.mjs} +8 -13
  32. package/.output/server/drizzle/0000_baseline.sql +6 -97
  33. package/.output/server/drizzle/meta/_journal.json +2 -16
  34. package/.output/server/index.mjs +43 -50
  35. package/drizzle/0000_baseline.sql +6 -97
  36. package/drizzle/meta/_journal.json +2 -16
  37. package/package.json +4 -8
  38. package/scripts/pievo-server.mjs +49 -47
  39. package/scripts/server-cli-lib.mjs +21 -12
  40. package/scripts/server-network.mjs +139 -0
  41. package/.output/public/assets/SignIn-CDCdDD1a.js +0 -1
  42. package/.output/public/assets/app-PPhgN9iN.css +0 -2
  43. package/.output/public/assets/index-c0XPWXuz.js +0 -10
  44. package/.output/public/assets/loops._loopId-D7i_BGiE.js +0 -67
  45. package/.output/public/assets/loops._loopId_.runs._runId-CksSrEY7.js +0 -3
  46. package/.output/public/assets/preload-helper-Zcmr31XG.js +0 -5
  47. package/.output/public/assets/routes-BYgl3Tmv.js +0 -1
  48. package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +0 -419
  49. package/.output/server/_libs/@aws-sdk/checksums+[...].mjs +0 -10732
  50. package/.output/server/_libs/@aws-sdk/client-s3+[...].mjs +0 -9475
  51. package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +0 -137
  52. package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +0 -440
  53. package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +0 -62
  54. package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +0 -818
  55. package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +0 -59
  56. package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +0 -342
  57. package/.output/server/_libs/aws-crypto__crc32c.mjs +0 -314
  58. package/.output/server/_libs/aws-sdk__nested-clients.mjs +0 -2671
  59. package/.output/server/_ssr/SignIn-Bp3uWaCQ.mjs +0 -53
  60. package/.output/server/_ssr/artifacts-CzDJLaa6.mjs +0 -2
  61. package/.output/server/_ssr/auth-BV0MxVdo.mjs +0 -38355
  62. package/.output/server/_ssr/auth-client-RTpMpT4L.mjs +0 -1070
  63. package/.output/server/_ssr/boot-CsLXrLEa.mjs +0 -2
  64. package/.output/server/_ssr/bun-sqlite-dialect-BW9W1_Ps-D9KUBJ-L.mjs +0 -162
  65. package/.output/server/_ssr/core-RrmWoKne.mjs +0 -5
  66. package/.output/server/_ssr/d1-sqlite-dialect-BLC8LXE6-CVHRzH1n.mjs +0 -112
  67. package/.output/server/_ssr/db-BTX-xORB.mjs +0 -2
  68. package/.output/server/_ssr/dist-D7UDVZ5G.mjs +0 -342
  69. package/.output/server/_ssr/factory-DYFx1fg-.mjs +0 -6903
  70. package/.output/server/_ssr/kysely-adapter-CwdB7yBh.mjs +0 -2
  71. package/.output/server/_ssr/kysely-migration-tables-JkVUjPF_-bPAX9LR_.mjs +0 -19
  72. package/.output/server/_ssr/logger-CwiKSMp6.mjs +0 -2
  73. package/.output/server/_ssr/node-sqlite-dialect-B_g92oFK.mjs +0 -162
  74. package/.output/server/_ssr/router-DCNZ2xLm.mjs +0 -543
  75. package/.output/server/_ssr/routes-8JSF7bX_.mjs +0 -27
  76. package/.output/server/_ssr/runDiff-D7CxAgYL.mjs +0 -2
  77. package/.output/server/_ssr/start-4ifArtKx.mjs +0 -4
  78. package/.output/server/_ssr/store-DSNbXxqz.mjs +0 -2
  79. package/.output/server/_ssr/tokens-DzUv7CCp.mjs +0 -2
  80. package/.output/server/drizzle/0001_add_pi_agent.sql +0 -9
  81. package/.output/server/drizzle/0002_remove_teams.sql +0 -15
  82. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  83. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  84. package/.output/server/node_modules/tslib/package.json +0 -47
  85. package/.output/server/node_modules/tslib/tslib.js +0 -484
  86. package/.output/server/package.json +0 -9
  87. package/drizzle/0001_add_pi_agent.sql +0 -9
  88. package/drizzle/0002_remove_teams.sql +0 -15
  89. package/scripts/prestart.mjs +0 -66
@@ -0,0 +1,106 @@
1
+ import { i as getServerFnById, r as createServerFn, t as TSS_SERVER_FUNCTION } from "./ssr.mjs";
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/lifecycleUi-BsbUsi5K.js
3
+ var createSsrRpc = (functionId) => {
4
+ const url = "/_serverFn/" + functionId;
5
+ const serverFnMeta = { id: functionId };
6
+ const fn = async (...args) => {
7
+ return (await getServerFnById(functionId, { origin: "server" }))(...args);
8
+ };
9
+ return Object.assign(fn, {
10
+ url,
11
+ serverFnMeta,
12
+ [TSS_SERVER_FUNCTION]: true
13
+ });
14
+ };
15
+ /**
16
+ * Server functions backing the dashboard. They run server-side (createServerFn
17
+ * → RPC) and call the IN-PROCESS scheduler + store directly — the scheduler
18
+ * lives in this same TanStack process (booted on first call via ensureServer).
19
+ * The machine-facing poll, CLI, report, and artifact-sync endpoints are sibling
20
+ * server routes in this same process, so one process owns the single scheduler.
21
+ */
22
+ /**
23
+ * Client config. `pievoCli` is the CLI invocation prefix the skill + connect
24
+ * dialog use for every verb (`daemon start`, `new`, …) — defaults to the globally
25
+ * installed `pievo`. Set PIEVO_CLI locally to a runnable command that points at the in-repo
26
+ * daemon, e.g. `tsx /abs/packages/daemon/src/cli.ts` or
27
+ * `node /abs/packages/daemon/dist/cli.js`, so loops created from THIS server tell
28
+ * Claude Code to run your local code instead of the registry build.
29
+ */
30
+ var getConfig = createServerFn({ method: "GET" }).handler(createSsrRpc("cbb6a1b09b309459bbba3dc3851ae85fc468658b3cda77e22b6c6f3e28b65af4"));
31
+ /** GET — visible loops as compact summaries, most recently run first. */
32
+ var listLoops = createServerFn({ method: "GET" }).handler(createSsrRpc("23e290b2b7483ceea6fe669024c35b12c2c4ddd76649f6fab21c958ceda02940"));
33
+ var getLoopDetail = createServerFn({ method: "GET" }).validator((id) => id).handler(createSsrRpc("b050455aacc84dd2a2c9a45517826ad10f91d8f1545e87cc7015784cc7116a97"));
34
+ var loadOlderRuns = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("85fdf73d4fee43aed762f93c5d47d33200fbff39e2d082647dc921f610d32698"));
35
+ var getArtifacts = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("b18bbec3d06dfc77714e1e527dfb9172fd7575a64a6c0d6e73ba6dc658dccf0d"));
36
+ var getArtifact = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("ed10d5fe79208e5e0e4e18e4a17b18d7f7db4dd16204c00c3ac90c4644668841"));
37
+ /** GET — a run's per-file diff vs the previous run. Lazy by runId like
38
+ * computed on the server at read time (no stored diffs). Old runs
39
+ * with no snapshot return `hasSnapshot: false` for the degrade copy. */
40
+ var getRunDiff = createServerFn({ method: "GET" }).validator((d) => d).handler(createSsrRpc("08c28b892712a1b491b120b74c7dcb81761988d31ccea60216d1807b73c1182c"));
41
+ var patchLoop = createServerFn({ method: "POST" }).validator((d) => d).handler(createSsrRpc("9befe6b8d67ccbbd7aa9b538bde2a161a9894761c8936761a9503fda1a88c0fc"));
42
+ /** Dedicated lifecycle operations. Dashboard callers never synthesize these
43
+ * transitions with patchLoop: each delegates to the loop-locked store operation. */
44
+ var pauseLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(createSsrRpc("95b7483b333d7649e70c848319417e83c2af6a0327bbcda2d0e331f626dffe05"));
45
+ var startLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(createSsrRpc("5660ea82ea5ff2549e0ce2f8f070c54f3e9e24fee84d547601ef5d6ef49a4016"));
46
+ var stopLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(createSsrRpc("e233a461e4526dec3cc4cbaa891a7c3621d5967ce7f32b4fb17e9fb645857b9a"));
47
+ /** Delete is one confirmed operation. Reachable machines get the graceful
48
+ * Stop-and-wait path; a machine already offline at click time cannot acknowledge
49
+ * Stop, so retire its execution authority and remove server data immediately.
50
+ * Local files are never touched by either path. */
51
+ var deleteLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(createSsrRpc("5e0d6b55f5629faac2f55a68661eeb82668b2be752192b16264ffb5cd1e6fabe"));
52
+ var runLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(createSsrRpc("08565e35f6a3dd06ef25401de0343976c47f7ae319d3baed6320b56dd3718311"));
53
+ /** Dedicated Stop-run operation. Pending work cancels immediately; running work
54
+ * records intent and stays Running until the daemon reports its actual result. */
55
+ var stopRun = createServerFn({ method: "POST" }).validator((id) => id).handler(createSsrRpc("6850ba403da9dec5aff45794eb52636ca540ddec2d9e59f5c75ccbb852bfeacb"));
56
+ /** Mint the key shown in the New-loop daemon connection command. */
57
+ var mintConnectKey = createServerFn({ method: "POST" }).handler(createSsrRpc("ce43dcb9366e7c8f7338a6ad28e4290b75a5ee3b13d0d2c9cb585020008f12c6"));
58
+ var DAEMON_UPGRADE_REQUIRED = "Daemon upgrade required to stop a running process. Run `npm install -g @kky42/pievo@latest`, then `pievo daemon restart`.";
59
+ function deriveLoopLifecycle(loop) {
60
+ if (loop.deleteRequestedAt != null) return "deleting";
61
+ const running = loop.runs.find((run) => run.phase === "running");
62
+ if (!loop.enabled && running?.cancelRequested) return "stopping";
63
+ if (!loop.enabled && (loop.running || running)) return "paused-finishing";
64
+ if (!loop.enabled) return "paused";
65
+ return "active";
66
+ }
67
+ /** Stable lifecycle badge shared by dashboard cards and loop detail. It shows
68
+ * only scheduling state and its durable pause cause; execution transients
69
+ * (queued/running/stopping) stay in the runs timeline and never replace it. */
70
+ function lifecyclePresentation(loop) {
71
+ if (loop.deleteRequestedAt != null) return {
72
+ label: "Deleting",
73
+ tone: "accent"
74
+ };
75
+ if (loop.enabled) return {
76
+ label: "Active",
77
+ tone: "success"
78
+ };
79
+ if (loop.pauseCause?.kind === "blocked") return {
80
+ label: "Paused · blocked",
81
+ tone: "attention"
82
+ };
83
+ if (loop.pauseCause?.kind === "failure-streak") return {
84
+ label: "Paused · failure streak",
85
+ tone: "attention"
86
+ };
87
+ if (loop.pauseCause?.kind === "owner") return {
88
+ label: "Paused · owner",
89
+ tone: "neutral"
90
+ };
91
+ return {
92
+ label: "Paused",
93
+ tone: "neutral"
94
+ };
95
+ }
96
+ function daemonStopSupport(protocol) {
97
+ return protocol === 4 ? {
98
+ supported: true,
99
+ label: `Daemon protocol 4 · Stop supported`
100
+ } : {
101
+ supported: false,
102
+ label: `Daemon protocol ${protocol ?? "unknown"} · upgrade required`
103
+ };
104
+ }
105
+ //#endregion
106
+ export { runLoop as _, deriveLoopLifecycle as a, stopRun as b, getConfig as c, lifecyclePresentation as d, listLoops as f, pauseLoop as g, patchLoop as h, deleteLoop as i, getLoopDetail as l, mintConnectKey as m, createSsrRpc as n, getArtifact as o, loadOlderRuns as p, daemonStopSupport as r, getArtifacts as s, DAEMON_UPGRADE_REQUIRED as t, getRunDiff as u, startLoop as v, stopLoop as y };
@@ -1,43 +1,30 @@
1
- import { $ as listLoopsForUser, B as getRun, E as forceDeleteLoop, Et as stopLoop$1, H as hasRunningRun, I as getLoop, L as getMachine, Ot as tryDeleteLoop, Pt as validateLoopEdit, Q as listLoops$1, ct as machinePresence, et as listMachines, ft as pauseLoop$1, kt as updateLoop, rt as listRunsBefore, tt as listMachinesForUser, vt as requestDeleteLoop, w as ensureServer, wt as startLoop$1, yt as requestRunCancel } from "./boot-U-CFI65B.mjs";
2
- import { a as toRunSummaries, i as toLoopSummaries, r as toLoopDetail, t as sortLoopSummariesByRecentRun } from "./loopProjection-DMSNHux6.mjs";
3
- import { canAccessLoop, requestScope } from "./auth-BV0MxVdo.mjs";
4
- import { n as createServerFn } from "./ssr.mjs";
1
+ import { A as listMachines, C as getMachine, E as hasRunningRun, G as startLoop$1, H as requestRunCancel, I as machinePresence, J as updateLoop, K as stopLoop$1, M as listRunsBefore, R as pauseLoop$1, S as getLoop, V as requestDeleteLoop, c as ensureServer, h as validateLoopEdit, k as listLoops$1, q as tryDeleteLoop, w as getRun, y as forceDeleteLoop } from "./boot-CYRi-wez.mjs";
2
+ import { a as toRunSummaries, i as toLoopSummaries, r as toLoopDetail, t as sortLoopSummariesByRecentRun } from "./loopProjection-esCjyZJg.mjs";
3
+ import { r as createServerFn } from "./ssr.mjs";
5
4
  import { t as createServerRpc } from "./createServerRpc-BD8Xn_0_.mjs";
6
- //#region node_modules/.nitro/vite/services/ssr/assets/loopApi-C4nN0msL.js
5
+ //#region node_modules/.nitro/vite/services/ssr/assets/loopApi-Bez1rHV0.js
7
6
  /**
8
7
  * Server functions backing the dashboard. They run server-side (createServerFn
9
8
  * → RPC) and call the IN-PROCESS scheduler + store directly — the scheduler
10
9
  * lives in this same TanStack process (booted on first call via ensureServer).
11
10
  * The machine-facing poll, CLI, report, and artifact-sync endpoints are sibling
12
11
  * server routes in this same process, so one process owns the single scheduler.
13
- *
14
- * Reads and writes are scoped through requestScope/ownedLoop so auth-mode users
15
- * can access only their own loops. Open mode intentionally shares all loops.
16
12
  */
17
13
  function backend() {
18
14
  return ensureServer();
19
15
  }
20
- /**
21
- * Resolve a loop and authorize the request against its owner. Returns the loop,
22
- * or undefined when it's missing OR (gate on) owned by a different user — callers
23
- * treat both as "not found" so existence never leaks across users.
24
- */
25
- async function ownedLoop(id) {
16
+ async function findLoop(id) {
26
17
  const loop = await getLoop(id);
27
- if (!loop) return void 0;
28
- const scope = await requestScope();
29
- if (!canAccessLoop(loop.userId, scope)) return void 0;
30
- return { loop };
18
+ return loop ? { loop } : void 0;
31
19
  }
32
- var getAuthState_createServerFn_handler = createServerRpc({
33
- id: "b8119d5fe6ca1ed9f609702aa2133b5ac5337030c0bdcf92dc7eba889d6bd2e6",
34
- name: "getAuthState",
35
- filename: "src/server/loopApi.ts"
36
- }, (opts) => getAuthState.__executeServer(opts));
37
- var getAuthState = createServerFn({ method: "GET" }).handler(getAuthState_createServerFn_handler, async () => {
38
- const { authEnabled } = await import("./auth-BV0MxVdo.mjs");
39
- return { enabled: authEnabled };
40
- });
20
+ /**
21
+ * Client config. `pievoCli` is the CLI invocation prefix the skill + connect
22
+ * dialog use for every verb (`daemon start`, `new`, …) — defaults to the globally
23
+ * installed `pievo`. Set PIEVO_CLI locally to a runnable command that points at the in-repo
24
+ * daemon, e.g. `tsx /abs/packages/daemon/src/cli.ts` or
25
+ * `node /abs/packages/daemon/dist/cli.js`, so loops created from THIS server tell
26
+ * Claude Code to run your local code instead of the registry build.
27
+ */
41
28
  var getConfig_createServerFn_handler = createServerRpc({
42
29
  id: "cbb6a1b09b309459bbba3dc3851ae85fc468658b3cda77e22b6c6f3e28b65af4",
43
30
  name: "getConfig",
@@ -49,7 +36,9 @@ var getConfig = createServerFn({ method: "GET" }).handler(getConfig_createServer
49
36
  pievoCli: custom || "pievo",
50
37
  /** True when a non-default (dev) CLI is configured — the New-loop paste then
51
38
  * carries an explicit `pievo-cli:` line so Claude Code uses it verbatim. */
52
- customCli: !!custom
39
+ customCli: !!custom,
40
+ /** Best URL for other devices, published by the launcher (HTTPS, Tailscale, then LAN). */
41
+ publicUrl: process.env.PIEVO_PUBLIC_URL?.trim() || null
53
42
  };
54
43
  });
55
44
  var listLoops_createServerFn_handler = createServerRpc({
@@ -59,9 +48,7 @@ var listLoops_createServerFn_handler = createServerRpc({
59
48
  }, (opts) => listLoops.__executeServer(opts));
60
49
  var listLoops = createServerFn({ method: "GET" }).handler(listLoops_createServerFn_handler, async () => {
61
50
  await backend();
62
- const scope = await requestScope();
63
- if (scope.enforce && !scope.userId) return [];
64
- const [loopRows, machines] = await Promise.all([scope.enforce ? listLoopsForUser(scope.userId) : listLoops$1(), scope.enforce ? listMachinesForUser(scope.userId) : listMachines()]);
51
+ const [loopRows, machines] = await Promise.all([listLoops$1(), listMachines()]);
65
52
  return sortLoopSummariesByRecentRun(await toLoopSummaries(loopRows.sort((a, b) => a.createdAt < b.createdAt ? 1 : -1), machines));
66
53
  });
67
54
  var getLoopDetail_createServerFn_handler = createServerRpc({
@@ -71,9 +58,9 @@ var getLoopDetail_createServerFn_handler = createServerRpc({
71
58
  }, (opts) => getLoopDetail.__executeServer(opts));
72
59
  var getLoopDetail = createServerFn({ method: "GET" }).validator((id) => id).handler(getLoopDetail_createServerFn_handler, async ({ data: id }) => {
73
60
  await backend();
74
- const owned = await ownedLoop(id);
75
- if (!owned) throw new Error("This loop does not exist, or you do not have access to it.");
76
- return toLoopDetail(owned.loop);
61
+ const found = await findLoop(id);
62
+ if (!found) throw new Error("This loop does not exist.");
63
+ return toLoopDetail(found.loop);
77
64
  });
78
65
  var loadOlderRuns_createServerFn_handler = createServerRpc({
79
66
  id: "85fdf73d4fee43aed762f93c5d47d33200fbff39e2d082647dc921f610d32698",
@@ -82,7 +69,7 @@ var loadOlderRuns_createServerFn_handler = createServerRpc({
82
69
  }, (opts) => loadOlderRuns.__executeServer(opts));
83
70
  var loadOlderRuns = createServerFn({ method: "GET" }).validator((d) => d).handler(loadOlderRuns_createServerFn_handler, async ({ data }) => {
84
71
  await backend();
85
- if (!await ownedLoop(data.loopId)) return [];
72
+ if (!await findLoop(data.loopId)) return [];
86
73
  const limit = Math.min(Math.max(data.limit ?? 16, 1), 100);
87
74
  return toRunSummaries(data.loopId, await listRunsBefore(data.loopId, data.beforeTs, limit));
88
75
  });
@@ -93,8 +80,8 @@ var getArtifacts_createServerFn_handler = createServerRpc({
93
80
  }, (opts) => getArtifacts.__executeServer(opts));
94
81
  var getArtifacts = createServerFn({ method: "GET" }).validator((d) => d).handler(getArtifacts_createServerFn_handler, async ({ data }) => {
95
82
  await backend();
96
- if (!await ownedLoop(data.loopId)) return [];
97
- const { listLoopArtifacts } = await import("./artifactFiles-BvVmixrs.mjs");
83
+ if (!await findLoop(data.loopId)) return [];
84
+ const { listLoopArtifacts } = await import("./artifactFiles-Di1xDKvX.mjs");
98
85
  return listLoopArtifacts(data.loopId);
99
86
  });
100
87
  var getArtifact_createServerFn_handler = createServerRpc({
@@ -104,8 +91,8 @@ var getArtifact_createServerFn_handler = createServerRpc({
104
91
  }, (opts) => getArtifact.__executeServer(opts));
105
92
  var getArtifact = createServerFn({ method: "GET" }).validator((d) => d).handler(getArtifact_createServerFn_handler, async ({ data }) => {
106
93
  await backend();
107
- if (!await ownedLoop(data.loopId)) return { error: "file not found" };
108
- const { readLoopArtifact } = await import("./artifactFiles-BvVmixrs.mjs");
94
+ if (!await findLoop(data.loopId)) return { error: "file not found" };
95
+ const { readLoopArtifact } = await import("./artifactFiles-Di1xDKvX.mjs");
109
96
  return readLoopArtifact(data.loopId, data.path);
110
97
  });
111
98
  var getRunDiff_createServerFn_handler = createServerRpc({
@@ -120,11 +107,11 @@ var getRunDiff = createServerFn({ method: "GET" }).validator((d) => d).handler(g
120
107
  hasSnapshot: false,
121
108
  files: []
122
109
  };
123
- if (!await ownedLoop(run.loopId)) return {
110
+ if (!await findLoop(run.loopId)) return {
124
111
  hasSnapshot: false,
125
112
  files: []
126
113
  };
127
- const { computeRunDiff } = await import("./runDiff-D7CxAgYL.mjs");
114
+ const { computeRunDiff } = await import("./boot-CYRi-wez.mjs").then((n) => n.i).then((n) => n.i);
128
115
  return computeRunDiff(data.runId);
129
116
  });
130
117
  var patchLoop_createServerFn_handler = createServerRpc({
@@ -134,11 +121,11 @@ var patchLoop_createServerFn_handler = createServerRpc({
134
121
  }, (opts) => patchLoop.__executeServer(opts));
135
122
  var patchLoop = createServerFn({ method: "POST" }).validator((d) => d).handler(patchLoop_createServerFn_handler, async ({ data }) => {
136
123
  const { scheduler } = await backend();
137
- const owned = await ownedLoop(data.id);
138
- if (!owned) return { error: "not found" };
124
+ const found = await findLoop(data.id);
125
+ if (!found) return { error: "not found" };
139
126
  const p = data.patch;
140
127
  {
141
- const validated = validateLoopEdit(owned.loop, p);
128
+ const validated = validateLoopEdit(found.loop, p);
142
129
  if (!validated.ok) return { error: validated.detail };
143
130
  const loop = await updateLoop(data.id, validated.value);
144
131
  if (!loop) return { error: "not found" };
@@ -154,7 +141,7 @@ var pauseLoop_createServerFn_handler = createServerRpc({
154
141
  }, (opts) => pauseLoop.__executeServer(opts));
155
142
  var pauseLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(pauseLoop_createServerFn_handler, async ({ data: id }) => {
156
143
  const { scheduler } = await backend();
157
- if (!await ownedLoop(id)) return { error: "not found" };
144
+ if (!await findLoop(id)) return { error: "not found" };
158
145
  if (!await pauseLoop$1(id)) return { error: "not found" };
159
146
  scheduler.removeLoop(id);
160
147
  return { ok: true };
@@ -166,9 +153,9 @@ var startLoop_createServerFn_handler = createServerRpc({
166
153
  }, (opts) => startLoop.__executeServer(opts));
167
154
  var startLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(startLoop_createServerFn_handler, async ({ data: id }) => {
168
155
  const { scheduler } = await backend();
169
- const owned = await ownedLoop(id);
170
- if (!owned) return { error: "not found" };
171
- if (owned.loop.deleteRequestedAt) return { error: "loop is being deleted" };
156
+ const found = await findLoop(id);
157
+ if (!found) return { error: "not found" };
158
+ if (found.loop.deleteRequestedAt) return { error: "loop is being deleted" };
172
159
  const loop = await startLoop$1(id);
173
160
  if (!loop) return { error: "not found" };
174
161
  scheduler.addLoop(loop);
@@ -181,10 +168,10 @@ var stopLoop_createServerFn_handler = createServerRpc({
181
168
  }, (opts) => stopLoop.__executeServer(opts));
182
169
  var stopLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(stopLoop_createServerFn_handler, async ({ data: id }) => {
183
170
  const { scheduler } = await backend();
184
- const owned = await ownedLoop(id);
185
- if (!owned) return { error: "not found" };
171
+ const found = await findLoop(id);
172
+ if (!found) return { error: "not found" };
186
173
  if (await hasRunningRun(id)) {
187
- if ((await getMachine(owned.loop.machineId))?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
174
+ if ((await getMachine(found.loop.machineId))?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
188
175
  }
189
176
  const stopped = await stopLoop$1(id);
190
177
  if (!stopped) return { error: "not found" };
@@ -201,20 +188,20 @@ var deleteLoop_createServerFn_handler = createServerRpc({
201
188
  }, (opts) => deleteLoop.__executeServer(opts));
202
189
  var deleteLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(deleteLoop_createServerFn_handler, async ({ data: id }) => {
203
190
  const { scheduler } = await backend();
204
- const owned = await ownedLoop(id);
205
- if (!owned) return { error: "not found" };
206
- const machine = await getMachine(owned.loop.machineId);
191
+ const found = await findLoop(id);
192
+ if (!found) return { error: "not found" };
193
+ const machine = await getMachine(found.loop.machineId);
207
194
  const unreachable = machinePresence(machine?.online, machine?.lastSeen) !== "online";
208
195
  if (!unreachable && await hasRunningRun(id) && machine?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
209
196
  scheduler.removeLoop(id);
210
197
  if (!await requestDeleteLoop(id)) return { error: "not found" };
211
198
  if (unreachable) {
212
199
  if (!await forceDeleteLoop(id)) return { error: "delete failed; server data was not deleted" };
213
- const { logger } = await import("./logger-CwiKSMp6.mjs");
200
+ const { logger } = await import("./boot-CYRi-wez.mjs").then((n) => n.N).then((n) => n.Jt);
214
201
  logger.child({ mod: "loop-lifecycle" }).warn({
215
202
  action: "unreachable-delete",
216
203
  loopId: id,
217
- machineId: owned.loop.machineId
204
+ machineId: found.loop.machineId
218
205
  }, "unreachable-machine delete: retired execution authority and removed server data");
219
206
  return {
220
207
  ok: true,
@@ -235,7 +222,7 @@ var runLoop_createServerFn_handler = createServerRpc({
235
222
  }, (opts) => runLoop.__executeServer(opts));
236
223
  var runLoop = createServerFn({ method: "POST" }).validator((id) => id).handler(runLoop_createServerFn_handler, async ({ data: id }) => {
237
224
  const { scheduler } = await backend();
238
- if (!await ownedLoop(id)) return { error: "not found" };
225
+ if (!await findLoop(id)) return { error: "not found" };
239
226
  const queued = await scheduler.runNow(id);
240
227
  if (!("run" in queued)) return { error: queued.reason };
241
228
  return {
@@ -253,9 +240,9 @@ var stopRun_createServerFn_handler = createServerRpc({
253
240
  var stopRun = createServerFn({ method: "POST" }).validator((id) => id).handler(stopRun_createServerFn_handler, async ({ data: id }) => {
254
241
  await backend();
255
242
  const run = await getRun(id);
256
- const owned = run ? await ownedLoop(run.loopId) : void 0;
257
- if (!run || !owned) return { error: "run not found" };
258
- if (run.phase === "running" && (await getMachine(owned.loop.machineId))?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
243
+ const found = run ? await findLoop(run.loopId) : void 0;
244
+ if (!run || !found) return { error: "run not found" };
245
+ if (run.phase === "running" && (await getMachine(found.loop.machineId))?.daemonProtocol !== 4) return { error: "Daemon upgrade required to stop a running process" };
259
246
  const result = await requestRunCancel(run.loopId, id);
260
247
  return result ? {
261
248
  ok: true,
@@ -269,12 +256,10 @@ var mintConnectKey_createServerFn_handler = createServerRpc({
269
256
  }, (opts) => mintConnectKey.__executeServer(opts));
270
257
  var mintConnectKey = createServerFn({ method: "POST" }).handler(mintConnectKey_createServerFn_handler, async () => {
271
258
  await backend();
272
- const scope = await requestScope();
273
- if (scope.enforce && !scope.userId) return { error: "not signed in" };
274
- const { mintDeviceToken, rememberConnectKey } = await import("./tokens-DzUv7CCp.mjs");
259
+ const { mintDeviceToken, rememberConnectKey } = await import("./boot-CYRi-wez.mjs").then((n) => n.N).then((n) => n.g);
275
260
  const token = mintDeviceToken();
276
- await rememberConnectKey(token, { userId: scope.userId ?? "shared" });
261
+ await rememberConnectKey(token);
277
262
  return { token };
278
263
  });
279
264
  //#endregion
280
- export { deleteLoop_createServerFn_handler, getArtifact_createServerFn_handler, getArtifacts_createServerFn_handler, getAuthState_createServerFn_handler, getConfig_createServerFn_handler, getLoopDetail_createServerFn_handler, getRunDiff_createServerFn_handler, listLoops_createServerFn_handler, loadOlderRuns_createServerFn_handler, mintConnectKey_createServerFn_handler, patchLoop_createServerFn_handler, pauseLoop_createServerFn_handler, runLoop_createServerFn_handler, startLoop_createServerFn_handler, stopLoop_createServerFn_handler, stopRun_createServerFn_handler };
265
+ export { deleteLoop_createServerFn_handler, getArtifact_createServerFn_handler, getArtifacts_createServerFn_handler, getConfig_createServerFn_handler, getLoopDetail_createServerFn_handler, getRunDiff_createServerFn_handler, listLoops_createServerFn_handler, loadOlderRuns_createServerFn_handler, mintConnectKey_createServerFn_handler, patchLoop_createServerFn_handler, pauseLoop_createServerFn_handler, runLoop_createServerFn_handler, startLoop_createServerFn_handler, stopLoop_createServerFn_handler, stopRun_createServerFn_handler };
@@ -1,5 +1,5 @@
1
- import { H as hasRunningRun, L as getMachine, St as scheduleFromLoop, Tt as statusDefinitionsFromLoop, V as hasPendingRun, _ as countRuns, ct as machinePresence, gt as reconciliationStatesForRuns, ht as recentLoopUsage, nt as listRuns, u as MIN_DAEMON_VERSION, x as daemonNeedsUpdate } from "./boot-U-CFI65B.mjs";
2
- //#region node_modules/.nitro/vite/services/ssr/assets/loopProjection-DMSNHux6.js
1
+ import { B as reconciliationStatesForRuns, C as getMachine, E as hasRunningRun, I as machinePresence, T as hasPendingRun, _ as countRuns, a as canonicalLoopEnvelope, j as listRuns, n as MIN_DAEMON_VERSION, o as canonicalLoopSummary, s as daemonNeedsUpdate, z as recentLoopUsage } from "./boot-CYRi-wez.mjs";
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/loopProjection-esCjyZJg.js
3
3
  var SUMMARY_RUNS = 18;
4
4
  var RECENT_USAGE_WINDOW_MS = 10080 * 60 * 1e3;
5
5
  /** Dashboard order: loops with runs first, most recent run first. The incoming
@@ -60,13 +60,7 @@ async function toLoopSummaryWithMachine(loop, machine) {
60
60
  const recentUsage = await recentLoopUsage(loop.id, (/* @__PURE__ */ new Date(Date.now() - RECENT_USAGE_WINDOW_MS)).toISOString());
61
61
  const presence = machinePresence(machine?.online, machine?.lastSeen);
62
62
  return {
63
- id: loop.id,
64
- name: loop.name,
65
- schedule: scheduleFromLoop(loop),
66
- workdir: loop.workdir,
67
- agent: loop.agent,
68
- model: loop.model ?? null,
69
- reasoningEffort: loop.reasoningEffort ?? null,
63
+ ...canonicalLoopSummary(loop),
70
64
  machine: {
71
65
  id: loop.machineId,
72
66
  name: machine?.name || "",
@@ -95,17 +89,7 @@ async function toLoopSummaries(loops, machines) {
95
89
  }
96
90
  function toLoopFull(loop) {
97
91
  return {
98
- id: loop.id,
99
- name: loop.name,
100
- schedule: scheduleFromLoop(loop),
101
- workdir: loop.workdir,
102
- agent: loop.agent,
103
- model: loop.model ?? null,
104
- reasoningEffort: loop.reasoningEffort ?? null,
105
- prompt: loop.prompt,
106
- statusDefinitions: statusDefinitionsFromLoop(loop),
107
- artifacts: loop.artifacts,
108
- enabled: loop.enabled,
92
+ ...canonicalLoopEnvelope(loop),
109
93
  pauseCause: loop.pauseCause ?? null,
110
94
  createdAt: loop.createdAt,
111
95
  updatedAt: loop.updatedAt
@@ -1,15 +1,14 @@
1
1
  import { o as __toESM } from "../_runtime.mjs";
2
- import { s as CODING_AGENTS } from "./boot-U-CFI65B.mjs";
2
+ import { X as validateLoopTags, g as CODING_AGENTS } from "./boot-CYRi-wez.mjs";
3
3
  import { g as require_react, h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
4
4
  import { d as useNavigate, u as Link } from "../_libs/@tanstack/react-router+[...].mjs";
5
- import { E as stopLoop, O as useSession, S as runLoop, T as startLoop, b as patchLoop, c as deleteLoop, d as getArtifacts, i as OpenModeWarning, l as deriveLoopLifecycle, m as getLoopDetail, s as daemonStopSupport, t as DAEMON_UPGRADE_REQUIRED, u as getArtifact, v as loadOlderRuns, x as pauseLoop } from "./auth-client-RTpMpT4L.mjs";
6
- import { C as rel, D as sectionHeadCls, O as selectCls, T as runPulseStyle, _ as fmt, a as Loading, b as inputCls, c as btn, d as btnQuiet, g as dur, i as LoadErrorCard, j as useFlash, k as tsShort, l as btnDanger, m as dotLabel, n as ErrorBanner, p as dotColor, r as FlashLine, t as ConfirmBar, u as btnPrimary, x as labelCls, y as humanBytes } from "./actionUi-C_WswNb1.mjs";
7
- import { a as Timeline, c as mergeRuns, n as MachinesModal, t as LoopOverview } from "./MachinesModal-DBIP648Z.mjs";
8
- import { n as artifactKind, t as Route } from "./artifactKind-DRCbYHZU.mjs";
9
- import { t as SignIn } from "./SignIn-Bp3uWaCQ.mjs";
5
+ import { _ as runLoop, a as deriveLoopLifecycle, g as pauseLoop, h as patchLoop, i as deleteLoop, l as getLoopDetail, o as getArtifact, p as loadOlderRuns, r as daemonStopSupport, s as getArtifacts, t as DAEMON_UPGRADE_REQUIRED, v as startLoop, y as stopLoop } from "./lifecycleUi-BsbUsi5K.mjs";
6
+ import { A as tsShort, E as runPulseStyle, M as useFlash, O as sectionHeadCls, S as labelCls, _ as fmt, a as Loading, b as humanBytes, c as btn, d as btnQuiet, g as dur, i as LoadErrorCard, k as selectCls, l as btnDanger, m as dotLabel, n as ErrorBanner, p as dotColor, r as FlashLine, t as ConfirmBar, u as btnPrimary, w as rel, x as inputCls, y as focusRing } from "./actionUi-DE-Pb2tv.mjs";
7
+ import { a as Timeline, l as mergeRuns, n as MachinesModal, t as LoopOverview } from "./MachinesModal-CmVMec6B.mjs";
8
+ import { n as artifactKind, t as Route } from "./artifactKind-DqpmtxDU.mjs";
10
9
  import { t as purify } from "../_libs/dompurify.mjs";
11
10
  import { t as g } from "../_libs/marked.mjs";
12
- //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-Q4pRYPip.js
11
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-KUezNY69.js
13
12
  var import_react = /* @__PURE__ */ __toESM(require_react());
14
13
  var import_jsx_runtime = require_jsx_runtime();
15
14
  /** Download URL for one artifact. Each path segment is encoded independently so
@@ -368,6 +367,8 @@ function initState(initial) {
368
367
  const schedule = initial?.schedule;
369
368
  return {
370
369
  name: initial?.name ?? "",
370
+ tags: initial?.tags ?? [],
371
+ tagDraft: "",
371
372
  mode: schedule?.mode ?? "cron",
372
373
  cron: schedule?.mode === "cron" ? schedule.cron : "0 */3 * * *",
373
374
  timezone: schedule?.mode === "cron" ? schedule.timezone : Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
@@ -447,10 +448,37 @@ function TextArea({ label, value, onChange, rows = 4, mono, hint }) {
447
448
  var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref) {
448
449
  const [form, setForm] = (0, import_react.useState)(() => initState(initial));
449
450
  const [error, setError] = (0, import_react.useState)(null);
451
+ const [tagError, setTagError] = (0, import_react.useState)(null);
452
+ const tagsLabelId = (0, import_react.useId)();
453
+ const tagsHintId = (0, import_react.useId)();
454
+ const tagsErrorId = (0, import_react.useId)();
455
+ const tagGroupRef = (0, import_react.useRef)(null);
456
+ const tagInputRef = (0, import_react.useRef)(null);
450
457
  const set = (key, value) => setForm((current) => ({
451
458
  ...current,
452
459
  [key]: value
453
460
  }));
461
+ const commitTagDraft = () => {
462
+ const additions = form.tagDraft.trim() ? [form.tagDraft] : [];
463
+ if (!additions.length) {
464
+ set("tagDraft", "");
465
+ return;
466
+ }
467
+ const result = validateLoopTags([...form.tags, ...additions]);
468
+ if (!result.ok) {
469
+ setTagError(result.detail);
470
+ return;
471
+ }
472
+ setForm((current) => ({
473
+ ...current,
474
+ tags: result.value,
475
+ tagDraft: ""
476
+ }));
477
+ setTagError(null);
478
+ if (result.value.length === 4) requestAnimationFrame(() => {
479
+ if (document.activeElement === document.body) tagGroupRef.current?.focus();
480
+ });
481
+ };
454
482
  (0, import_react.useImperativeHandle)(ref, () => ({ read() {
455
483
  const missing = [
456
484
  ["Name", form.name],
@@ -464,6 +492,12 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
464
492
  setError(`${missing[0]} is required.`);
465
493
  return null;
466
494
  }
495
+ const tagResult = validateLoopTags([...form.tags, ...form.tagDraft.trim() ? [form.tagDraft] : []]);
496
+ if (!tagResult.ok) {
497
+ setTagError(tagResult.detail);
498
+ setError(null);
499
+ return null;
500
+ }
467
501
  const delayMinutes = Number(form.delayMinutes);
468
502
  if (form.mode === "continuous" && (!Number.isInteger(delayMinutes) || delayMinutes < 1)) {
469
503
  setError("Continuous delay must be an integer of at least 1 minute.");
@@ -479,8 +513,10 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
479
513
  delayMinutes
480
514
  };
481
515
  setError(null);
516
+ setTagError(null);
482
517
  return {
483
518
  name: form.name.trim(),
519
+ tags: tagResult.value,
484
520
  schedule,
485
521
  workdir: form.workdir.trim(),
486
522
  agent: form.agent,
@@ -506,6 +542,79 @@ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref)
506
542
  value: form.name,
507
543
  onChange: (value) => set("name", value)
508
544
  }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
546
+ className: "min-w-0",
547
+ children: [
548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
549
+ id: tagsLabelId,
550
+ className: labelCls,
551
+ children: "Tags"
552
+ }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
554
+ ref: tagGroupRef,
555
+ role: "group",
556
+ tabIndex: -1,
557
+ "aria-labelledby": tagsLabelId,
558
+ "aria-describedby": `${tagsHintId}${tagError ? ` ${tagsErrorId}` : ""}`,
559
+ "aria-invalid": tagError ? "true" : void 0,
560
+ className: "flex min-h-10 flex-wrap items-center gap-1.5 rounded-control border border-wire bg-surface px-2.5 py-1.5 outline-none transition-shadow focus-within:border-transparent focus-within:shadow-focus",
561
+ children: [form.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
562
+ className: "inline-flex h-6 max-w-full items-center gap-1 rounded-full bg-raised px-2.5 text-caption font-medium text-secondary",
563
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
564
+ className: "max-w-52 truncate",
565
+ title: tag,
566
+ children: tag
567
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
568
+ type: "button",
569
+ "aria-label": `Remove tag ${tag}`,
570
+ onClick: () => {
571
+ set("tags", form.tags.filter((value) => value !== tag));
572
+ setTagError(null);
573
+ requestAnimationFrame(() => (tagInputRef.current ?? tagGroupRef.current)?.focus());
574
+ },
575
+ className: `-mr-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full text-disabled transition-colors hover:bg-surface hover:text-primary ${focusRing}`,
576
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
577
+ "aria-hidden": true,
578
+ children: "×"
579
+ })
580
+ })]
581
+ }, tag)), form.tags.length < 4 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
582
+ ref: tagInputRef,
583
+ "aria-label": "Add tag",
584
+ value: form.tagDraft,
585
+ placeholder: form.tags.length ? "Add another…" : "Add a tag…",
586
+ onChange: (event) => {
587
+ set("tagDraft", event.target.value);
588
+ setTagError(null);
589
+ },
590
+ onKeyDown: (event) => {
591
+ if (event.nativeEvent.isComposing) return;
592
+ if (event.key === "Enter") {
593
+ event.preventDefault();
594
+ commitTagDraft();
595
+ } else if (event.key === "Backspace" && !form.tagDraft && form.tags.length) set("tags", form.tags.slice(0, -1));
596
+ },
597
+ onBlur: commitTagDraft,
598
+ className: "h-6 min-w-28 flex-1 bg-transparent px-1 text-sm text-primary outline-none placeholder:text-disabled"
599
+ })]
600
+ }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
602
+ id: tagsHintId,
603
+ className: hintCls,
604
+ children: [
605
+ "Up to ",
606
+ 4,
607
+ ". Press Enter to add."
608
+ ]
609
+ }),
610
+ tagError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
611
+ id: tagsErrorId,
612
+ role: "alert",
613
+ className: "mt-1 text-caption text-accent",
614
+ children: tagError
615
+ })
616
+ ]
617
+ }),
509
618
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
510
619
  className: "min-w-0",
511
620
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
@@ -971,6 +1080,20 @@ function LoopDetailView({ id }) {
971
1080
  children: configurationScheduleText
972
1081
  })
973
1082
  }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1084
+ label: "Tags",
1085
+ children: loop.tags.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1086
+ className: "flex flex-wrap gap-1.5",
1087
+ children: loop.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1088
+ className: "inline-flex h-[22px] max-w-52 items-center truncate rounded-full bg-raised px-2.5 text-caption font-medium text-secondary",
1089
+ title: tag,
1090
+ children: tag
1091
+ }, tag))
1092
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1093
+ className: "text-disabled",
1094
+ children: "None"
1095
+ })
1096
+ }),
974
1097
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
975
1098
  label: "Working directory",
976
1099
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
@@ -1082,7 +1205,7 @@ function EditHead({ name, onBack }) {
1082
1205
  }),
1083
1206
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1084
1207
  className: "mt-1.5 text-meta text-secondary",
1085
- children: "Edit the stored schedule, execution settings, prompt, status definitions, and exact artifact paths."
1208
+ children: "Edit tags, schedule, execution settings, prompt, status definitions, and exact artifact paths."
1086
1209
  })
1087
1210
  ] });
1088
1211
  }
@@ -1163,25 +1286,11 @@ function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1163
1286
  /**
1164
1287
  * Loop detail page — `/loops/$loopId`: a loop header and action toolbar, the
1165
1288
  * exact artifact Files panel, and the runs timeline. The view owns its own
1166
- * data + self-poll (ssr:false so the session cookie rides along with its fetches,
1167
- * like the dashboard loader). Run rows link on to `/loops/$loopId/runs/$runId`.
1168
- *
1169
- * Auth-gated exactly like the dashboard (`/`): Better Auth's session hook keeps a
1170
- * logged-out or expired deep link from mounting the ownership-scoped data view.
1289
+ * data + self-poll. Run rows link on to `/loops/$loopId/runs/$runId`.
1171
1290
  */
1172
1291
  function LoopDetailPage() {
1173
1292
  const { loopId } = Route.useParams();
1174
- const { auth } = Route.useLoaderData() ?? { auth: { enabled: false } };
1175
- const { data: session, isPending } = useSession();
1176
- if (auth.enabled && isPending) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
1177
- className: "mx-auto max-w-[1360px] px-8 pt-10",
1178
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loading, {})
1179
- });
1180
- if (auth.enabled && !session) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SignIn, { callbackURL: `/loops/${loopId}` });
1181
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [!auth.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1182
- className: "mx-auto max-w-[1360px] px-8 pt-6",
1183
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OpenModeWarning, {})
1184
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopDetailView, { id: loopId })] });
1293
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopDetailView, { id: loopId });
1185
1294
  }
1186
1295
  //#endregion
1187
1296
  export { LoopDetailPage as component };