@kky42/pievo-server 2.3.0 → 2.4.2

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 (199) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/MachinesModal-CSbnrUER.js +1 -0
  3. package/.output/public/assets/SignIn-0Tjqs-Ma.js +1 -0
  4. package/.output/public/assets/actionUi-DpwGIGpw.js +1 -0
  5. package/.output/public/assets/app-CaZpuwOS.css +2 -0
  6. package/.output/public/assets/index-CaS_SmDq.js +10 -0
  7. package/.output/public/assets/invite._token-Btg_0aLp.js +1 -0
  8. package/.output/public/assets/invite._token-KMD2JAgE.js +1 -0
  9. package/.output/public/assets/lifecycleUi-C7dwHtbw.js +1 -0
  10. package/.output/public/assets/loops._loopId-D5usF3DO.js +67 -0
  11. package/.output/public/assets/loops._loopId_.runs._runId-DRCgXaCq.js +3 -0
  12. package/.output/public/assets/preload-helper-DsxECZIS.js +5 -0
  13. package/.output/public/assets/routes-CtFgwdgW.js +1 -0
  14. package/.output/public/assets/routes-LDbnnQ3f.js +1 -0
  15. package/.output/public/assets/t._teamId-D41xcbsF.js +1 -0
  16. package/.output/public/assets/t._teamId-DUena1pV.js +1 -0
  17. package/.output/public/assets/ui-Do4gqMyP.js +1 -0
  18. package/.output/server/_libs/@base-ui/react+[...].mjs +4130 -4130
  19. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  20. package/.output/server/_libs/drizzle-orm+postgres.mjs +28 -1
  21. package/.output/server/_ssr/DashboardView-C2LdV6J0.mjs +914 -0
  22. package/.output/server/_ssr/{notifyFns-DuPR1zd7.mjs → MachinesModal-BjkcVjjx.mjs} +55 -35
  23. package/.output/server/_ssr/{SignIn-RmpWm9uy.mjs → SignIn-DEGrvJvb.mjs} +10 -24
  24. package/.output/server/_ssr/SocialLinks-Cm4H8pUJ.mjs +1044 -0
  25. package/.output/server/_ssr/{actionUi-BN55TNYE.mjs → actionUi-DJBWuNwr.mjs} +3 -3
  26. package/.output/server/_ssr/{artifactFiles-BCaIIrrT.mjs → artifactFiles-DtpRidFZ.mjs} +7 -8
  27. package/.output/server/_ssr/{artifactKind-BW0isV8L.mjs → artifactKind-FBYbgSuf.mjs} +27 -2
  28. package/.output/server/_ssr/artifacts-BR3jj0NC.mjs +2 -0
  29. package/.output/server/_ssr/{auth-30Z4lV1w.mjs → auth-C0zoZCGe.mjs} +16 -19
  30. package/.output/server/_ssr/boot-CdOO4xOo.mjs +2 -0
  31. package/.output/server/_ssr/{boot-3BNKujyp.mjs → boot-sNlIkoLe.mjs} +1331 -3927
  32. package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-_7tfuNRi.mjs → bun-sqlite-dialect-BW9W1_Ps-DmlZWqO7.mjs} +2 -2
  33. package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-ucDUbeBk.mjs → d1-sqlite-dialect-BLC8LXE6-CCNzqZwZ.mjs} +2 -2
  34. package/.output/server/_ssr/db-CwLD-5w1.mjs +2 -0
  35. package/.output/server/_ssr/{dist-Cd6nlLlO.mjs → dist-B3SxDOpW.mjs} +2 -2
  36. package/.output/server/_ssr/{factory-CWWwV7XZ.mjs → factory-Dv3Zopis.mjs} +2 -2
  37. package/.output/server/_ssr/invite._token-CJ6-H-s3.mjs +20 -0
  38. package/.output/server/_ssr/{invite._token-SdWXemIc.mjs → invite._token-dLKDJKVs.mjs} +9 -17
  39. package/.output/server/_ssr/{invite._token-BKlz0R2f.mjs → invite._token-l1SjouDz.mjs} +6 -15
  40. package/.output/server/_ssr/{kysely-adapter-C8iS51DB.mjs → kysely-adapter-nL7WKSgw.mjs} +1 -1
  41. package/.output/server/_ssr/{lifecycleUi-DgBQAYzp.mjs → lifecycleUi-Dad3hvhm.mjs} +11 -11
  42. package/.output/server/_ssr/logger-B8m8F1Qu.mjs +2 -0
  43. package/.output/server/_ssr/loopApi-BKyFB4KM.mjs +342 -0
  44. package/.output/server/_ssr/{adapters-BoLkOf1T.mjs → loopProjection-B8a829ij.mjs} +25 -55
  45. package/.output/server/_ssr/loops._loopId-DS16Phmq.mjs +1271 -0
  46. package/.output/server/_ssr/{loops._loopId_.runs._runId-PUGBrD5q.mjs → loops._loopId_.runs._runId-BEJI6iK5.mjs} +23 -74
  47. package/.output/server/_ssr/{loops._loopId_.runs._runId-Ymy4lwee.mjs → loops._loopId_.runs._runId-CduETLqH.mjs} +4 -5
  48. package/.output/server/_ssr/{machineFns-BZ9puxDt.mjs → machineFns-ByMsgG12.mjs} +3 -5
  49. package/.output/server/_ssr/{node-sqlite-dialect-D_3JmkMo.mjs → node-sqlite-dialect-CjNineno.mjs} +2 -2
  50. package/.output/server/_ssr/router-CXyrnvx-.mjs +565 -0
  51. package/.output/server/_ssr/{routes-ClZEdsnB.mjs → routes-BulNEUEE.mjs} +6 -10
  52. package/.output/server/_ssr/{routes-ComqHvCL.mjs → routes-DD94baie.mjs} +3 -3
  53. package/.output/server/_ssr/{routes-D5jJJ-30.mjs → routes-Wjexi35W.mjs} +6 -6
  54. package/.output/server/_ssr/runDiff-Ba8Lg1vg.mjs +2 -0
  55. package/.output/server/_ssr/ssr.mjs +60 -100
  56. package/.output/server/_ssr/store-Cujtg2QE.mjs +2 -0
  57. package/.output/server/_ssr/{t._teamId-BgHV_KFP.mjs → t._teamId-1CreIlPV.mjs} +7 -11
  58. package/.output/server/_ssr/{t._teamId-DDERNgfn.mjs → t._teamId-Bd6-zZsy.mjs} +4 -4
  59. package/.output/server/_ssr/{t._teamId-DRJvKHZn.mjs → t._teamId-COuMKQ4r.mjs} +7 -7
  60. package/.output/server/_ssr/{teamFns-Bp81UO-x.mjs → teamFns-H438WnZC.mjs} +12 -30
  61. package/.output/server/_ssr/{teamFns-bm82twd5.mjs → teamFns-j2JKjorZ.mjs} +2 -2
  62. package/.output/server/_ssr/tokens-DgG_Syq3.mjs +2 -0
  63. package/.output/server/_ssr/{ui-Ww2tTM2u.mjs → ui-D5KWstlb.mjs} +26 -48
  64. package/.output/server/{_tanstack-start-manifest_v-DwKENYCN.mjs → _tanstack-start-manifest_v-3H7yBKLz.mjs} +25 -35
  65. package/{drizzle/0000_round_marauders.sql → .output/server/drizzle/0000_baseline.sql} +102 -44
  66. package/.output/server/drizzle/meta/_journal.json +3 -115
  67. package/.output/server/index.mjs +83 -132
  68. package/{.output/server/drizzle/0000_round_marauders.sql → drizzle/0000_baseline.sql} +102 -44
  69. package/drizzle/meta/_journal.json +3 -115
  70. package/package.json +2 -11
  71. package/scripts/pievo-server.mjs +3 -1
  72. package/scripts/server-cli-lib.mjs +23 -16
  73. package/.output/public/assets/CodeField-dOzyFQNH.js +0 -34
  74. package/.output/public/assets/LoopView-DjVHUyrR.js +0 -59
  75. package/.output/public/assets/Modal-CCRvx959.js +0 -1
  76. package/.output/public/assets/SignIn-Qxw5zDFq.js +0 -1
  77. package/.output/public/assets/actionUi-CXxhzYe2.js +0 -1
  78. package/.output/public/assets/app-D1cij_NJ.css +0 -2
  79. package/.output/public/assets/artifactView-Cd5wWPil.js +0 -66
  80. package/.output/public/assets/index-BRN_CQ8U.js +0 -12
  81. package/.output/public/assets/invite._token-BIxr8DlP.js +0 -1
  82. package/.output/public/assets/invite._token-j2BTCeG1.js +0 -1
  83. package/.output/public/assets/jsx-runtime-BseJUIpC.js +0 -1
  84. package/.output/public/assets/lifecycleUi-Qy_8beQK.js +0 -1
  85. package/.output/public/assets/loopApi-CT1mMhlb.js +0 -3
  86. package/.output/public/assets/loops._loopId-BRBQzioy.js +0 -4
  87. package/.output/public/assets/loops._loopId_.runs._runId-CMnOJkem.js +0 -3
  88. package/.output/public/assets/notifyFns-DYD4EKaG.js +0 -1
  89. package/.output/public/assets/preload-helper-DFkkpP6W.js +0 -3
  90. package/.output/public/assets/routes-3nuEJGdo.js +0 -1
  91. package/.output/public/assets/routes-DORLim-C.js +0 -1
  92. package/.output/public/assets/t._teamId-F5f4K8kd.js +0 -1
  93. package/.output/public/assets/t._teamId-XfE_S1Fo.js +0 -1
  94. package/.output/public/assets/ui-Dhsx6SW0.js +0 -1
  95. package/.output/public/assets/useRouter-DsjH8G0p.js +0 -1
  96. package/.output/server/_libs/@codemirror/autocomplete+[...].mjs +0 -19713
  97. package/.output/server/_libs/@codemirror/lang-css+[...].mjs +0 -2636
  98. package/.output/server/_libs/@codemirror/lang-html+[...].mjs +0 -2247
  99. package/.output/server/_libs/@codemirror/lang-json+[...].mjs +0 -79
  100. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1736
  101. package/.output/server/_libs/@uiw/codemirror-extensions-basic-setup+[...].mjs +0 -76
  102. package/.output/server/_libs/babel__runtime.mjs +0 -30
  103. package/.output/server/_libs/clsx.mjs +0 -16
  104. package/.output/server/_libs/codemirror__commands.mjs +0 -1725
  105. package/.output/server/_libs/codemirror__lint.mjs +0 -565
  106. package/.output/server/_libs/codemirror__search.mjs +0 -1138
  107. package/.output/server/_libs/codemirror__theme-one-dark.mjs +0 -161
  108. package/.output/server/_libs/d3-array.mjs +0 -231
  109. package/.output/server/_libs/d3-color.mjs +0 -330
  110. package/.output/server/_libs/d3-format.mjs +0 -252
  111. package/.output/server/_libs/d3-interpolate.mjs +0 -163
  112. package/.output/server/_libs/d3-path.mjs +0 -77
  113. package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
  114. package/.output/server/_libs/d3-shape.mjs +0 -818
  115. package/.output/server/_libs/decimal.js-light.mjs +0 -884
  116. package/.output/server/_libs/dom-serializer+[...].mjs +0 -708
  117. package/.output/server/_libs/domhandler.mjs +0 -461
  118. package/.output/server/_libs/domutils.mjs +0 -868
  119. package/.output/server/_libs/es-toolkit.mjs +0 -1737
  120. package/.output/server/_libs/eventemitter3.mjs +0 -236
  121. package/.output/server/_libs/html-dom-parser+htmlparser2.mjs +0 -1560
  122. package/.output/server/_libs/html-react-parser+[...].mjs +0 -1360
  123. package/.output/server/_libs/react-is.mjs +0 -48
  124. package/.output/server/_libs/react-redux+[...].mjs +0 -205
  125. package/.output/server/_libs/recharts+victory-vendor.mjs +0 -17303
  126. package/.output/server/_libs/uiw__codemirror-themes.mjs +0 -32
  127. package/.output/server/_libs/uiw__react-codemirror.mjs +0 -354
  128. package/.output/server/_ssr/CodeField-B7oOmmwH.mjs +0 -143
  129. package/.output/server/_ssr/DashboardView-BF7PciP7.mjs +0 -2736
  130. package/.output/server/_ssr/LoopPlaybook-DC7QVvPY.mjs +0 -2105
  131. package/.output/server/_ssr/LoopView-DwSrm9Ja.mjs +0 -1300
  132. package/.output/server/_ssr/Modal-MaCG52Pv.mjs +0 -49
  133. package/.output/server/_ssr/artifactView-iuPsp5aa.mjs +0 -304
  134. package/.output/server/_ssr/artifacts-jQ-bfdhI.mjs +0 -2
  135. package/.output/server/_ssr/boot-CsEFT53K.mjs +0 -2
  136. package/.output/server/_ssr/db-CXownd1H.mjs +0 -2
  137. package/.output/server/_ssr/invite._token-CWl6kipD.mjs +0 -28
  138. package/.output/server/_ssr/logger-Gy-47z3k.mjs +0 -2
  139. package/.output/server/_ssr/loopApi-8_KgBapr.mjs +0 -109
  140. package/.output/server/_ssr/loopApi-ABi9SgWS.mjs +0 -564
  141. package/.output/server/_ssr/loops._loopId-SON_t43g.mjs +0 -30
  142. package/.output/server/_ssr/loops._loopId-p1KWsvMM.mjs +0 -1477
  143. package/.output/server/_ssr/notifyFns-BUCePuF3.mjs +0 -118
  144. package/.output/server/_ssr/router-Czxvra6l.mjs +0 -685
  145. package/.output/server/_ssr/runDiff--qLtU4FI.mjs +0 -2
  146. package/.output/server/_ssr/store-40eVzW1S.mjs +0 -2
  147. package/.output/server/_ssr/tokens-CbfoRC_s.mjs +0 -2
  148. package/.output/server/drizzle/0001_naive_silver_fox.sql +0 -25
  149. package/.output/server/drizzle/0002_funny_sage.sql +0 -12
  150. package/.output/server/drizzle/0003_optimal_slapstick.sql +0 -102
  151. package/.output/server/drizzle/0004_remove_grok_agent.sql +0 -5
  152. package/.output/server/drizzle/0005_heavy_gauntlet.sql +0 -8
  153. package/.output/server/drizzle/0006_tranquil_sharon_carter.sql +0 -1
  154. package/.output/server/drizzle/0007_faulty_starbolt.sql +0 -30
  155. package/.output/server/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
  156. package/.output/server/drizzle/0009_ordinary_electro.sql +0 -1
  157. package/.output/server/drizzle/0010_workable_rage.sql +0 -14
  158. package/.output/server/drizzle/0011_steep_marvel_zombies.sql +0 -3
  159. package/.output/server/drizzle/0012_blocked_run_status.sql +0 -12
  160. package/.output/server/drizzle/0013_drop_run_outcome.sql +0 -2
  161. package/.output/server/drizzle/0014_luxuriant_scrambler.sql +0 -5
  162. package/.output/server/drizzle/0015_run_history_foundation.sql +0 -43
  163. package/.output/server/drizzle/0016_plural_machine_runs.sql +0 -4
  164. package/.output/server/drizzle/meta/0000_snapshot.json +0 -1333
  165. package/.output/server/drizzle/meta/0001_snapshot.json +0 -1498
  166. package/.output/server/drizzle/meta/0002_snapshot.json +0 -1576
  167. package/.output/server/drizzle/meta/0003_snapshot.json +0 -1666
  168. package/.output/server/drizzle/meta/0005_snapshot.json +0 -1666
  169. package/.output/server/drizzle/meta/0006_snapshot.json +0 -1672
  170. package/.output/server/drizzle/meta/0007_snapshot.json +0 -1787
  171. package/.output/server/drizzle/meta/0009_snapshot.json +0 -1793
  172. package/.output/server/drizzle/meta/0010_snapshot.json +0 -1891
  173. package/.output/server/drizzle/meta/0011_snapshot.json +0 -1872
  174. package/drizzle/0001_naive_silver_fox.sql +0 -25
  175. package/drizzle/0002_funny_sage.sql +0 -12
  176. package/drizzle/0003_optimal_slapstick.sql +0 -102
  177. package/drizzle/0004_remove_grok_agent.sql +0 -5
  178. package/drizzle/0005_heavy_gauntlet.sql +0 -8
  179. package/drizzle/0006_tranquil_sharon_carter.sql +0 -1
  180. package/drizzle/0007_faulty_starbolt.sql +0 -30
  181. package/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
  182. package/drizzle/0009_ordinary_electro.sql +0 -1
  183. package/drizzle/0010_workable_rage.sql +0 -14
  184. package/drizzle/0011_steep_marvel_zombies.sql +0 -3
  185. package/drizzle/0012_blocked_run_status.sql +0 -12
  186. package/drizzle/0013_drop_run_outcome.sql +0 -2
  187. package/drizzle/0014_luxuriant_scrambler.sql +0 -5
  188. package/drizzle/0015_run_history_foundation.sql +0 -43
  189. package/drizzle/0016_plural_machine_runs.sql +0 -4
  190. package/drizzle/meta/0000_snapshot.json +0 -1333
  191. package/drizzle/meta/0001_snapshot.json +0 -1498
  192. package/drizzle/meta/0002_snapshot.json +0 -1576
  193. package/drizzle/meta/0003_snapshot.json +0 -1666
  194. package/drizzle/meta/0005_snapshot.json +0 -1666
  195. package/drizzle/meta/0006_snapshot.json +0 -1672
  196. package/drizzle/meta/0007_snapshot.json +0 -1787
  197. package/drizzle/meta/0009_snapshot.json +0 -1793
  198. package/drizzle/meta/0010_snapshot.json +0 -1891
  199. package/drizzle/meta/0011_snapshot.json +0 -1872
@@ -1,1477 +0,0 @@
1
- import { o as __toESM } from "../_runtime.mjs";
2
- import { g as require_jsx_runtime, h as require_react } from "../_libs/@base-ui/react+[...].mjs";
3
- import { _ as buildFileEntries, gt as isTaskEntry, l as CODING_AGENTS } from "./boot-3BNKujyp.mjs";
4
- import { A as tsShort, D as runPulseStyle, O as sectionHeadCls, S as labelCls, T as rel, _ as dur, b as humanBytes, c as btnDanger, d as btnQuiet, h as dotLabel, i as Pre, j as until, k as selectCls, l as btnPrimary, m as dotColor, n as Loading, o as btn, p as cronText, r as Pill, s as btnCost, t as ErrorBanner, v as fmt, x as inputCls } from "./ui-Ww2tTM2u.mjs";
5
- import { d as useNavigate, u as Link } from "../_libs/@tanstack/react-router+[...].mjs";
6
- import { C as startJob, S as runJob, _ as loadOlderRuns, a as evolveJob, b as pauseJob, f as getJobDetail, i as deleteJob, s as getArtifacts, w as stopJob, x as requestSteer, y as patchJob } from "./loopApi-8_KgBapr.mjs";
7
- import { n as TaskFileView, r as ViewerHead, t as ArtifactBody } from "./artifactView-iuPsp5aa.mjs";
8
- import { i as useFlash, n as FlashLine, r as LoadErrorCard, t as ConfirmBar } from "./actionUi-BN55TNYE.mjs";
9
- import { s as useSession } from "./LoopPlaybook-DC7QVvPY.mjs";
10
- import { t as setActiveTeamCookie } from "./teamCookie-B7hLOIie.mjs";
11
- import { t as SignIn } from "./SignIn-RmpWm9uy.mjs";
12
- import { i as lifecycleDisplay, n as daemonStopSupport, r as deriveLoopLifecycle, t as DAEMON_UPGRADE_REQUIRED } from "./lifecycleUi-DgBQAYzp.mjs";
13
- import { a as listChannels, n as Timeline, s as mergeRuns, t as MachinesModal } from "./notifyFns-DuPR1zd7.mjs";
14
- import { t as Route } from "./loops._loopId-SON_t43g.mjs";
15
- //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-p1KWsvMM.js
16
- var import_react = /* @__PURE__ */ __toESM(require_react());
17
- var import_jsx_runtime = require_jsx_runtime();
18
- /**
19
- * Copy-prompt path for the loop-detail Edit composer.
20
- *
21
- * The dispatch path (`requestSteer`) runs ONE agent pass on the owner's machine -
22
- * spends credits, no conversation. This is the non-dispatch alternative: a
23
- * self-contained prompt the owner pastes into their OWN local coding-agent
24
- * session (in the loop's directory) so they can iterate/converse and adjust the
25
- * loop themselves. Pure + agent-neutral so it's unit-testable and never inlines
26
- * user text unsafely into JSX.
27
- */
28
- /** The loop's on-disk folder — the containing directory of its task file
29
- * (e.g. `.../pievo/<loop>/`). Returns null when the task file path is absent
30
- * or has no parent, so callers degrade to a generic instruction rather than
31
- * fabricating a path. Handles both POSIX and Windows separators. */
32
- function loopDir(taskFile) {
33
- if (!taskFile) return null;
34
- const norm = taskFile.replace(/\\/g, "/").replace(/\/+$/, "");
35
- const i = norm.lastIndexOf("/");
36
- return i > 0 ? norm.slice(0, i) : null;
37
- }
38
- /** Build the ready-to-paste prompt for a fresh coding-agent session: it states
39
- * that they're adjusting a named Pievo loop, what the owner wants (their typed
40
- * instruction, or a clear placeholder asking them to describe it), and that the
41
- * agent should drive the installed pievo CLI/skill (`pievo loops`,
42
- * `pievo edit <id> ...`) to apply and confirm the change. Concise, agent-neutral. */
43
- function buildSteerPrompt({ loopId, loopName, instruction }) {
44
- const want = instruction?.trim() || "Describe the change you want to make to this loop.";
45
- return [
46
- `I want to adjust my Pievo loop "${loopName}" (loop id: ${loopId}).`,
47
- "",
48
- "The change I want:",
49
- want,
50
- "",
51
- `Use the installed pievo CLI/skill to apply it: run \`pievo loops\` to find the loop, then \`pievo edit ${loopId} ...\` to make the change, and confirm the loop now reflects it. Ask me first if anything is unclear.`
52
- ].join("\n");
53
- }
54
- /**
55
- * The unified "Files" surface of the loop detail page — the loop's spec (its task
56
- * file) shown ALONGSIDE every live-synced artifact in ONE master-detail panel,
57
- * not two separate boxes. A file list (task file pinned first, then artifacts
58
- * path-sorted) drives a content viewer on the right; the task file is selected by
59
- * default. Text files render inline (markdown → formatted, else mono); binary /
60
- * oversize files offer the download route. The artifact list is fetched lazily by
61
- * loopId and self-polls so files appear as the loop writes them (Phase 1-2 reuse).
62
- */
63
- var basename = (path) => path.split("/").pop() || path;
64
- /** Faint type tag shown after a file's size. */
65
- function typeTag(path) {
66
- const m = path.match(/\.([a-z0-9]+)$/i);
67
- return m ? m[1].toLowerCase() : "file";
68
- }
69
- function LoopFilesPanel({ loopId, taskFile, taskFileContent, taskFileSyncedAt, running }) {
70
- const [artifacts, setArtifacts] = (0, import_react.useState)(null);
71
- const [selected, setSelected] = (0, import_react.useState)(null);
72
- const seq = (0, import_react.useRef)(0);
73
- const refresh = (0, import_react.useCallback)(async () => {
74
- const mine = ++seq.current;
75
- try {
76
- const list = await getArtifacts({ data: { loopId } });
77
- if (mine === seq.current) setArtifacts(list);
78
- } catch {
79
- if (mine === seq.current) setArtifacts((prev) => prev ?? []);
80
- }
81
- }, [loopId]);
82
- (0, import_react.useEffect)(() => {
83
- setArtifacts(null);
84
- setSelected(null);
85
- refresh();
86
- }, [loopId, refresh]);
87
- (0, import_react.useEffect)(() => {
88
- const t = setInterval(() => void refresh(), running ? 4e3 : 12e3);
89
- return () => clearInterval(t);
90
- }, [running, refresh]);
91
- const entries = (0, import_react.useMemo)(() => buildFileEntries(taskFile, artifacts ?? []), [taskFile, artifacts]);
92
- const activePath = selected && entries.some((e) => e.path === selected) ? selected : entries[0]?.path ?? null;
93
- const active = entries.find((e) => e.path === activePath) ?? null;
94
- const activeIsTask = isTaskEntry(active);
95
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
96
- id: "files",
97
- className: "min-w-0",
98
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
99
- className: "mb-2.5 flex items-end justify-between gap-3 border-b border-hairline pb-1.5",
100
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
101
- className: "text-label font-semibold text-secondary",
102
- children: ["Files", artifacts ? ` (${entries.length})` : ""]
103
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
104
- className: "text-caption font-medium text-disabled",
105
- children: "Spec + synced artifacts"
106
- })]
107
- }), entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
108
- className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled shadow-card",
109
- children: artifacts == null ? "Loading…" : "No files yet - the task file and synced artifacts appear here."
110
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
111
- className: "grid h-[min(600px,68vh)] grid-cols-1 overflow-hidden rounded-card border border-hairline bg-surface shadow-card sm:grid-cols-[210px_1fr]",
112
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
113
- className: "max-h-44 overflow-y-auto border-b border-hairline sm:max-h-none sm:border-b-0 sm:border-r",
114
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
115
- className: "py-1.5",
116
- children: entries.map((e) => {
117
- const on = e.path === activePath;
118
- const isTask = isTaskEntry(e);
119
- const meta = !isTask && e.kind === "artifact" ? e.file.meta : null;
120
- const title = meta?.title?.trim() || "";
121
- const display = title && title !== basename(e.path) ? title : basename(e.path);
122
- const showPath = e.path.includes("/") || display !== basename(e.path);
123
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
124
- type: "button",
125
- onClick: () => setSelected(e.path),
126
- className: `flex w-full items-center gap-2 border-l-2 px-3 py-2 text-left transition-colors ${on ? "border-display bg-raised" : "border-transparent hover:bg-raised/60"}`,
127
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
128
- className: "min-w-0 flex-1",
129
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
130
- className: `block truncate font-mono text-label ${on ? "text-display" : "text-primary"}`,
131
- title: e.path,
132
- children: display
133
- }), showPath && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
134
- className: "block truncate font-mono text-micro text-disabled",
135
- title: e.path,
136
- children: e.path
137
- })]
138
- }), isTask ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
139
- className: "shrink-0 rounded-full bg-interactive-soft px-2 text-caption font-medium text-interactive",
140
- children: "Task"
141
- }) : meta?.type ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
142
- className: "shrink-0 truncate rounded-full bg-raised px-2 text-caption font-medium text-secondary",
143
- title: `type: ${meta.type}`,
144
- children: meta.type
145
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
146
- className: "shrink-0 font-mono text-micro text-disabled",
147
- children: typeTag(e.path)
148
- })]
149
- }) }, e.path);
150
- })
151
- })
152
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
153
- className: "min-w-0 overflow-y-auto",
154
- children: activeIsTask && active ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskEntryView, {
155
- path: active.path,
156
- content: taskFileContent,
157
- syncedAt: taskFileSyncedAt
158
- }) : active?.kind === "artifact" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactEntryView, {
159
- loopId,
160
- file: active.file
161
- }) : null
162
- })]
163
- })]
164
- });
165
- }
166
- /** The task file pane — the loop's spec, rendered as formatted markdown. */
167
- function TaskEntryView({ path, content, syncedAt }) {
168
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, {
169
- path,
170
- meta: syncedAt ? `Task file · synced ${fmt(syncedAt)}` : "Task file"
171
- }), content == null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
172
- className: "px-5 py-6 text-body text-disabled",
173
- children: "(syncs from the machine on the next run)"
174
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskFileView, { content })] });
175
- }
176
- /** The artifact pane — the shared caption strip over the shared content body. */
177
- function ArtifactEntryView({ loopId, file }) {
178
- const meta = [file.size != null ? humanBytes(file.size) : "", `synced ${fmt(file.updatedAt)}`].filter(Boolean).join(" · ");
179
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, {
180
- path: file.path,
181
- meta
182
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactBody, {
183
- loopId,
184
- file
185
- })] });
186
- }
187
- /**
188
- * Build the exec slice of a manual form save.
189
- *
190
- * Always emits so model / reasoningEffort / allowControl ride with Save even
191
- * when workdir is empty. Empty strings
192
- * stay defined so patchJob can clear stored execution settings via `trim() || null` -
193
- * do not coerce cleared fields to `undefined` or gate the whole object on workdir.
194
- */
195
- function buildFormExec(f) {
196
- return {
197
- executor: "claude",
198
- workdir: f.workdir.trim(),
199
- model: f.model.trim(),
200
- reasoningEffort: f.reasoningEffort.trim(),
201
- allowControl: f.allowControl
202
- };
203
- }
204
- var CodeField = (0, import_react.lazy)(() => import("./CodeField-B7oOmmwH.mjs"));
205
- function initState(initial) {
206
- const e = initial?.exec;
207
- return {
208
- name: initial?.name ?? "",
209
- cron: initial?.cron ?? "0 */3 * * *",
210
- scheduleMode: initial?.scheduleMode ?? "cron",
211
- continuousDelayMinutes: String(initial?.continuousDelayMinutes ?? 1),
212
- taskFile: initial?.taskFile ?? "",
213
- notify: initial?.notify ?? "auto",
214
- channelId: initial?.channelId ?? "",
215
- metricSchema: initial?.metricSchema ? JSON.stringify(initial.metricSchema) : "",
216
- ui: initial?.ui ?? "",
217
- agent: initial?.agent ?? "claude-code",
218
- workdir: e?.workdir ?? "",
219
- model: e?.model ?? "",
220
- reasoningEffort: e?.reasoningEffort ?? "",
221
- allowControl: !!e?.allowControl
222
- };
223
- }
224
- /** Quiet helper line under a field - guidance, not chrome. */
225
- var hintCls = "mt-1 text-caption leading-snug text-disabled";
226
- /** Display labels for the known agents; an unknown/widened value falls back to its
227
- * raw enum token so a newly-added agent still renders. */
228
- var AGENT_LABEL$1 = {
229
- "claude-code": "Claude Code",
230
- codex: "Codex"
231
- };
232
- function TextField({ label, value, onChange, mono, ph, hint }) {
233
- const id = (0, import_react.useId)();
234
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
235
- className: "min-w-0",
236
- children: [
237
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
238
- htmlFor: id,
239
- className: labelCls,
240
- children: label
241
- }),
242
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
243
- id,
244
- type: "text",
245
- className: mono ? `${inputCls} font-mono` : inputCls,
246
- value,
247
- placeholder: ph,
248
- onChange: (e) => onChange(e.target.value)
249
- }),
250
- hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
251
- className: hintCls,
252
- children: hint
253
- })
254
- ]
255
- });
256
- }
257
- /** Section divider - same recipe as ModalSection, local so the form owns its rhythm. */
258
- function Section({ title, hint }) {
259
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
260
- className: "mb-1 mt-7 border-b border-hairline pb-1.5",
261
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
262
- className: sectionHeadCls,
263
- children: title
264
- }), hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
265
- className: "mt-0.5 text-caption text-disabled",
266
- children: hint
267
- })]
268
- });
269
- }
270
- /** Chunk-load placeholder that holds the editor's footprint (no layout jump). */
271
- function EditorFallback({ minHeight }) {
272
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
273
- className: "rounded-control border border-wire bg-raised",
274
- style: { minHeight }
275
- });
276
- }
277
- var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial, channels: channelsProp }, ref) {
278
- const [f, setF] = (0, import_react.useState)(() => initState(initial));
279
- const set = (k, v) => setF((s) => ({
280
- ...s,
281
- [k]: v
282
- }));
283
- const [schemaErr, setSchemaErr] = (0, import_react.useState)(null);
284
- const [fetched, setFetched] = (0, import_react.useState)([]);
285
- const channels = channelsProp ?? fetched;
286
- (0, import_react.useEffect)(() => {
287
- if (channelsProp) return;
288
- listChannels().then(setFetched).catch(() => {});
289
- }, [channelsProp]);
290
- (0, import_react.useImperativeHandle)(ref, () => ({ read() {
291
- let metricSchema;
292
- const ss = f.metricSchema.trim();
293
- if (ss) try {
294
- metricSchema = JSON.parse(ss);
295
- } catch {
296
- setSchemaErr("Not valid JSON - expected an array like [{\"key\":\"mrr\",\"label\":\"MRR\",\"unit\":\"$\"}]");
297
- return null;
298
- }
299
- return {
300
- name: f.name.trim(),
301
- cron: f.cron.trim(),
302
- scheduleMode: f.scheduleMode,
303
- continuousDelayMinutes: Math.max(1, Number.parseInt(f.continuousDelayMinutes, 10) || 1),
304
- taskFile: f.taskFile.trim(),
305
- notify: f.notify,
306
- channelId: f.channelId || null,
307
- ui: f.ui.trim() || void 0,
308
- agent: f.agent,
309
- exec: buildFormExec(f),
310
- metricSchema
311
- };
312
- } }));
313
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
314
- className: "grid gap-x-10 lg:grid-cols-[minmax(300px,380px)_minmax(0,1fr)]",
315
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
316
- className: "min-w-0",
317
- children: [
318
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: "Basics" }),
319
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
320
- label: "Name",
321
- value: f.name,
322
- onChange: (v) => set("name", v)
323
- }),
324
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
325
- className: "min-w-0",
326
- children: [
327
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
328
- className: labelCls,
329
- children: "Schedule mode"
330
- }),
331
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
332
- "aria-label": "Schedule mode",
333
- className: selectCls,
334
- value: f.scheduleMode,
335
- onChange: (e) => set("scheduleMode", e.target.value),
336
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
337
- value: "cron",
338
- children: "Cron"
339
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
340
- value: "continuous",
341
- children: "Continuous"
342
- })]
343
- }),
344
- f.scheduleMode === "cron" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
345
- className: `${inputCls} flex cursor-default select-none items-center justify-between gap-2 bg-raised`,
346
- title: f.cron,
347
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
348
- className: "truncate text-primary",
349
- children: cronText(f.cron)
350
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
351
- className: "shrink-0 font-mono text-caption text-secondary",
352
- children: f.cron
353
- })]
354
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
355
- label: "Delay after each exec (minutes)",
356
- value: f.continuousDelayMinutes,
357
- onChange: (v) => set("continuousDelayMinutes", v),
358
- mono: true,
359
- hint: "At least 1 minute. Done and error continue; canceled runs do not."
360
- }),
361
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
362
- className: hintCls,
363
- children: "Cron is retained while continuous mode is active, so switching back restores it."
364
- })
365
- ]
366
- }),
367
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
368
- label: "Task file",
369
- value: f.taskFile,
370
- onChange: (v) => set("taskFile", v),
371
- mono: true,
372
- hint: "Path of the markdown file this loop tracks/maintains on the machine (optional)."
373
- }),
374
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: "Notifications" }),
375
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
376
- className: "min-w-0",
377
- children: [
378
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
379
- className: labelCls,
380
- children: "Notify"
381
- }),
382
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
383
- "aria-label": "Notify",
384
- className: selectCls,
385
- value: f.notify,
386
- onChange: (e) => set("notify", e.target.value),
387
- children: [
388
- "auto",
389
- "always",
390
- "never"
391
- ].map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { children: n }, n))
392
- }),
393
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
394
- className: hintCls,
395
- children: "auto pushes only when a run found something; never silences everything."
396
- })
397
- ]
398
- }),
399
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
400
- className: "min-w-0",
401
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
402
- className: labelCls,
403
- children: "Push channel"
404
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
405
- "aria-label": "Push channel",
406
- className: selectCls,
407
- value: f.channelId,
408
- onChange: (e) => set("channelId", e.target.value),
409
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
410
- value: "",
411
- children: "none (dashboard only)"
412
- }), channels.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
413
- value: c.id,
414
- children: c.name
415
- }, c.id))]
416
- })]
417
- }),
418
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
419
- title: "Execution",
420
- hint: "Which coding agent on the machine runs this loop."
421
- }),
422
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
423
- className: "min-w-0",
424
- children: [
425
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
426
- className: labelCls,
427
- children: "Coding agent"
428
- }),
429
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
430
- "aria-label": "Coding agent",
431
- className: selectCls,
432
- value: f.agent,
433
- onChange: (e) => set("agent", e.target.value),
434
- children: CODING_AGENTS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
435
- value: a,
436
- children: AGENT_LABEL$1[a] ?? a
437
- }, a))
438
- }),
439
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
440
- className: hintCls,
441
- children: "Which coding agent executes this loop on the bound machine."
442
- })
443
- ]
444
- }),
445
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
446
- label: "Working directory",
447
- value: f.workdir,
448
- onChange: (v) => set("workdir", v),
449
- mono: true,
450
- ph: "/Users/you/Workspace/project",
451
- hint: "Project root on the machine. Empty lets the daemon use a scratch directory."
452
- }),
453
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
454
- label: "Model",
455
- value: f.model,
456
- onChange: (v) => set("model", v),
457
- ph: "default",
458
- hint: "Optional arbitrary model id. Empty uses the coding-agent CLI default."
459
- }),
460
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
461
- label: "Reasoning effort",
462
- value: f.reasoningEffort,
463
- onChange: (v) => set("reasoningEffort", v),
464
- ph: "default",
465
- hint: "Optional arbitrary effort value. Empty uses the coding-agent CLI default."
466
- }),
467
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
468
- className: "mt-4 flex cursor-pointer items-center gap-2 text-body text-primary",
469
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
470
- type: "checkbox",
471
- className: "accent-[color:var(--color-display)]",
472
- checked: f.allowControl,
473
- onChange: (e) => set("allowControl", e.target.checked)
474
- }), "Allow self-rescheduling"]
475
- })
476
- ]
477
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
478
- className: "min-w-0",
479
- children: [
480
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
481
- title: "Agent-authored content",
482
- hint: "Metrics and dashboard - usually written by evolve passes; edit by hand here when you know exactly what you want."
483
- }),
484
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
485
- className: labelCls,
486
- children: "Metrics schema · JSON array"
487
- }),
488
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
489
- fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditorFallback, { minHeight: "90px" }),
490
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeField, {
491
- lang: "json",
492
- value: f.metricSchema,
493
- onChange: (v) => {
494
- set("metricSchema", v);
495
- setSchemaErr(null);
496
- },
497
- minHeight: "90px",
498
- invalid: !!schemaErr,
499
- placeholder: "[{\"key\":\"mrr\",\"label\":\"MRR\",\"unit\":\"$\"},{\"key\":\"paid\"}]"
500
- })
501
- }),
502
- schemaErr ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
503
- className: "mt-1 text-caption leading-snug text-accent",
504
- children: schemaErr
505
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
506
- className: hintCls,
507
- children: "Per-run metrics the agent reports; they feed the dashboard charts."
508
- }),
509
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
510
- className: labelCls,
511
- children: "Dashboard template · HTML"
512
- }),
513
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
514
- fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditorFallback, { minHeight: "240px" }),
515
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeField, {
516
- lang: "html",
517
- value: f.ui,
518
- onChange: (v) => set("ui", v),
519
- minHeight: "240px",
520
- placeholder: "<h3>{{latest.mrr}}$</h3>\n<loop-chart type=\"line\" x=\"runIndex\" series=\"mrr:MRR:$\"></loop-chart>"
521
- })
522
- }),
523
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
524
- className: hintCls,
525
- children: [
526
- "Agent-authored HTML with ",
527
- "{{bindings}}",
528
- " and loop-chart / loop-embed / loop-calendar / loop-kanban elements."
529
- ]
530
- })
531
- ]
532
- })]
533
- });
534
- });
535
- var AGENT_LABEL = {
536
- "claude-code": "Claude Code",
537
- codex: "Codex"
538
- };
539
- /** Composer starters - one per editable dimension, so a blank box never stalls
540
- * the owner. Clicking seeds the instruction; the agent handles the rest. */
541
- var STEER_SEEDS = [
542
- {
543
- label: "Change the schedule",
544
- seed: "Change the schedule: "
545
- },
546
- {
547
- label: "Adjust what it does",
548
- seed: "Change what this loop does: "
549
- },
550
- {
551
- label: "Improve the dashboard",
552
- seed: "Improve the dashboard: "
553
- },
554
- {
555
- label: "Set an objective",
556
- seed: "Give this loop a standing objective to optimize toward: "
557
- }
558
- ];
559
- var LoopView = (0, import_react.lazy)(() => import("./LoopView-DwSrm9Ja.mjs").then((m) => ({ default: m.LoopView })));
560
- /**
561
- * Loop detail PAGE body (`/loops/$loopId`) — the redesign of the former modal.
562
- * One scrolling page: a loop header (name / status / schedule / agent / machine +
563
- * the action toolbar), an optional agent-authored dashboard, then a two-column
564
- * main with the UNIFIED Files panel (the task file alongside synced artifacts) and
565
- * the Runs timeline (a strip + a clickable list, each run linking to its own
566
- * detail route). Self-polls while open (fast while a run is live).
567
- *
568
- * Editing (2026-07 redesign): the primary path is an INLINE composer below the
569
- * always-visible action toolbar - the page stays visible, so
570
- * the owner describes the change while looking at the spec/dashboard it applies
571
- * to. Dispatch swaps the composer for a live status card under the header
572
- * (queued → applying → settled with report + files + a link to
573
- * the steer run); the page keeps polling, so the applied change surfaces around
574
- * the card. Settings remains a full-page field-form takeover.
575
- */
576
- function LoopDetailView({ id }) {
577
- const navigate = useNavigate();
578
- const [detail, setDetail] = (0, import_react.useState)(null);
579
- const [channels, setChannels] = (0, import_react.useState)([]);
580
- const [err, setErr] = (0, import_react.useState)(null);
581
- const [actionErr, setActionErr] = (0, import_react.useState)(null);
582
- const [editing, setEditing] = (0, import_react.useState)(false);
583
- const [steerVia, setSteerVia] = (0, import_react.useState)(false);
584
- const [steerInstruction, setSteerInstruction] = (0, import_react.useState)("");
585
- const [promptCopied, setPromptCopied] = (0, import_react.useState)(false);
586
- const [steerDispatched, setSteerDispatched] = (0, import_react.useState)(false);
587
- const [steerRunId, setSteerRunId] = (0, import_react.useState)(null);
588
- const steerBoxRef = (0, import_react.useRef)(null);
589
- const [machinesOpen, setMachinesOpen] = (0, import_react.useState)(false);
590
- const [pending, setPending] = (0, import_react.useState)(null);
591
- const [confirmingDelete, setConfirmingDelete] = (0, import_react.useState)(false);
592
- const [flash, setFlash] = useFlash();
593
- const formRef = (0, import_react.useRef)(null);
594
- const deletingRef = (0, import_react.useRef)(false);
595
- const [older, setOlder] = (0, import_react.useState)([]);
596
- const load = (0, import_react.useCallback)(async (silent = false) => {
597
- try {
598
- const next = await getJobDetail({ data: id });
599
- setDetail(next);
600
- deletingRef.current = next.summary.deleteRequestedAt != null;
601
- setErr(null);
602
- } catch (e) {
603
- if (deletingRef.current) navigate({ to: "/" });
604
- else if (!silent) setErr(String(e));
605
- }
606
- }, [id]);
607
- (0, import_react.useEffect)(() => {
608
- setDetail(null);
609
- setEditing(false);
610
- setSteerVia(false);
611
- setSteerInstruction("");
612
- setPromptCopied(false);
613
- setSteerDispatched(false);
614
- setSteerRunId(null);
615
- setConfirmingDelete(false);
616
- setOlder([]);
617
- load();
618
- listChannels().then(setChannels).catch(() => {});
619
- }, [id, load]);
620
- const running = !!detail?.summary.running;
621
- (0, import_react.useEffect)(() => {
622
- if (editing) return;
623
- const t = setInterval(() => void load(true), running || deletingRef.current ? 3e3 : 8e3);
624
- return () => clearInterval(t);
625
- }, [
626
- editing,
627
- running,
628
- load
629
- ]);
630
- async function refreshAll() {
631
- await load();
632
- }
633
- async function doRun() {
634
- setActionErr(null);
635
- setPending("run");
636
- try {
637
- const r = await runJob({ data: id });
638
- if (r?.error) return setActionErr(`Run failed: ${r.error}`);
639
- setFlash({
640
- label: r.coalesced ? "Already queued" : "Queued",
641
- hold: 4e3
642
- });
643
- await refreshAll();
644
- } finally {
645
- setPending(null);
646
- }
647
- }
648
- async function doEvolve() {
649
- setActionErr(null);
650
- setPending("evolve");
651
- try {
652
- const r = await evolveJob({ data: id });
653
- if (r?.error) return setActionErr(`Evolve failed: ${r.error}`);
654
- setFlash({
655
- label: r.coalesced ? "Evolve already queued" : "Evolve queued",
656
- hold: 4e3
657
- });
658
- await refreshAll();
659
- } finally {
660
- setPending(null);
661
- }
662
- }
663
- async function doLifecycle(action) {
664
- setActionErr(null);
665
- setPending("lifecycle");
666
- try {
667
- const r = action === "pause" ? await pauseJob({ data: id }) : action === "start" ? await startJob({ data: id }) : action === "stop" ? await stopJob({ data: id }) : await deleteJob({ data: id });
668
- if (r.error) return setActionErr(`${action[0].toUpperCase() + action.slice(1)} failed: ${r.error}`);
669
- setConfirmingDelete(false);
670
- if (r.deleted) {
671
- deletingRef.current = false;
672
- navigate({ to: "/" });
673
- return;
674
- }
675
- if (action === "delete") deletingRef.current = true;
676
- await refreshAll();
677
- setFlash({ label: action === "start" ? "Started" : action === "pause" ? "Paused" : action === "stop" ? "Stop requested" : "Deleting" });
678
- } finally {
679
- setPending(null);
680
- }
681
- }
682
- async function onSave() {
683
- const payload = formRef.current?.read();
684
- if (!payload) return;
685
- setActionErr(null);
686
- setPending("save");
687
- try {
688
- const r = await patchJob({ data: {
689
- id,
690
- patch: payload
691
- } });
692
- if (r.error) return setActionErr(`Save failed: ${r.error}`);
693
- setEditing(false);
694
- await refreshAll();
695
- setFlash({ label: "Saved" });
696
- } finally {
697
- setPending(null);
698
- }
699
- }
700
- async function onRequestSteer() {
701
- const instruction = steerInstruction.trim();
702
- if (!instruction) return;
703
- setActionErr(null);
704
- setPending("steer");
705
- try {
706
- const r = await requestSteer({ data: {
707
- id,
708
- instruction
709
- } });
710
- if (r.error) return setActionErr(`Couldn't queue the steer pass: ${r.error}`);
711
- setSteerRunId(r.runId ?? null);
712
- setSteerDispatched(true);
713
- setSteerVia(false);
714
- setSteerInstruction("");
715
- await refreshAll();
716
- } finally {
717
- setPending(null);
718
- }
719
- }
720
- const backLink = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
721
- to: "/",
722
- className: "inline-flex items-center gap-1.5 text-meta font-medium text-secondary transition-colors hover:text-display",
723
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
724
- "aria-hidden": true,
725
- children: "←"
726
- }), " Loops"]
727
- });
728
- if (err) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, {
729
- back: backLink,
730
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadErrorCard, {
731
- title: "Couldn't load this loop.",
732
- detail: err,
733
- onRetry: () => void load()
734
- })
735
- });
736
- if (!detail) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, {
737
- back: backLink,
738
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loading, {})
739
- });
740
- const { job, summary: s, runs } = detail;
741
- const hasUi = !!job.ui;
742
- const busy = !!pending;
743
- const online = detail.machine.online;
744
- const asleep = detail.machine.presence === "asleep";
745
- const offlineHint = !online ? asleep ? "Machine asleep - requests will stay queued until it wakes" : "Machine offline - requests will stay queued until it reconnects" : void 0;
746
- const lifecycle = deriveLoopLifecycle(s);
747
- const lifecycleText = lifecycleDisplay(detail);
748
- const protocolSupport = daemonStopSupport(detail.machine.daemonProtocol);
749
- const onMachine = detail.machine.name ? `“${detail.machine.name}”` : "the bound machine";
750
- const steerRun = steerDispatched && steerRunId ? runs.find((r) => r.id === steerRunId) : void 0;
751
- const steerSettled = !!steerRun && !steerRun.queued && !steerRun.running;
752
- const dismissSteer = () => {
753
- setSteerDispatched(false);
754
- setSteerRunId(null);
755
- };
756
- const steerDir = loopDir(job.taskFile);
757
- const copySteerPrompt = async () => {
758
- try {
759
- await navigator.clipboard.writeText(buildSteerPrompt({
760
- loopId: id,
761
- loopName: s.name,
762
- instruction: steerInstruction
763
- }));
764
- setPromptCopied(true);
765
- setTimeout(() => setPromptCopied(false), 4e3);
766
- } catch {
767
- setActionErr("Could not copy the prompt - try again or copy it manually.");
768
- }
769
- };
770
- const flashLine = flash && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
771
- className: "mb-2.5",
772
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlashLine, {
773
- label: flash.label,
774
- tone: flash.tone,
775
- onUndo: flash.undo
776
- })
777
- });
778
- const deleting = lifecycle === "deleting";
779
- const paused = !s.enabled && !deleting;
780
- const active = s.enabled && !deleting;
781
- const actionDisabled = busy || deleting;
782
- const canRunWork = active || paused;
783
- const canStop = canRunWork && !!s.running && protocolSupport.supported;
784
- const stopTitle = s.running && !protocolSupport.supported ? DAEMON_UPGRADE_REQUIRED : !s.running ? "No run is currently running" : void 0;
785
- const actionBar = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
786
- className: "min-w-0",
787
- children: [
788
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
789
- className: "flex min-w-0 flex-wrap items-center gap-2",
790
- children: [
791
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
792
- className: btnPrimary,
793
- disabled: actionDisabled || !canRunWork,
794
- onClick: () => void doRun(),
795
- title: !canRunWork ? "Unavailable for a deleting loop" : offlineHint ?? (job.exec ? "Spends credits" : void 0),
796
- "aria-label": job.exec ? "Run once - spends credits" : "Run once",
797
- children: pending === "run" ? "Queuing…" : "Run once"
798
- }),
799
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
800
- className: btn,
801
- disabled: actionDisabled || !canRunWork,
802
- title: "Dispatch or copy a prompt for an owner-requested steer",
803
- onClick: () => setSteerVia(true),
804
- children: "Steer"
805
- }),
806
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
807
- className: btn,
808
- disabled: actionDisabled || !canRunWork,
809
- title: !canRunWork ? "Unavailable for a deleting loop" : offlineHint ?? "Spends credits",
810
- onClick: () => void doEvolve(),
811
- children: pending === "evolve" ? "Evolving…" : "Evolve once"
812
- }),
813
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
814
- className: btn,
815
- disabled: actionDisabled,
816
- onClick: () => setEditing(true),
817
- children: "Settings"
818
- }),
819
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
820
- className: btn,
821
- disabled: actionDisabled || !paused,
822
- onClick: () => void doLifecycle("start"),
823
- children: "Start"
824
- }),
825
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
826
- className: btn,
827
- disabled: actionDisabled || !active,
828
- onClick: () => void doLifecycle("pause"),
829
- children: "Pause"
830
- }),
831
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
832
- className: btnDanger,
833
- disabled: actionDisabled || !canStop,
834
- title: stopTitle,
835
- onClick: () => void doLifecycle("stop"),
836
- children: "Stop"
837
- }),
838
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
839
- className: btnDanger,
840
- disabled: actionDisabled,
841
- onClick: () => setConfirmingDelete(true),
842
- children: deleting ? "Deleting…" : "Delete"
843
- })
844
- ]
845
- }),
846
- confirmingDelete && !deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
847
- className: "mt-3",
848
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfirmBar, {
849
- prompt: "Stop this loop and delete its server data?",
850
- note: "This stops the loop and deletes server history and synced artifacts. Local files are not deleted. If the machine is unreachable, server data is still deleted and its local process may continue running.",
851
- cta: "Delete",
852
- danger: true,
853
- busy,
854
- onConfirm: () => void doLifecycle("delete"),
855
- onCancel: () => setConfirmingDelete(false)
856
- })
857
- }),
858
- deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
859
- className: "mt-3 rounded-md border border-wire bg-raised px-4 py-3 text-meta text-secondary",
860
- children: "Deleting… graceful stop is waiting for execution authority to end."
861
- }),
862
- steerVia && !deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
863
- className: "mt-4 border-t border-hairline pt-4",
864
- onKeyDown: (e) => {
865
- if (e.key === "Escape" && pending !== "steer") {
866
- e.stopPropagation();
867
- setSteerVia(false);
868
- }
869
- },
870
- children: [
871
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
872
- className: "flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1",
873
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
874
- className: "text-body font-medium text-display",
875
- children: "Steer with your coding agent"
876
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
877
- className: "text-meta text-secondary",
878
- children: [
879
- "One agent pass on ",
880
- onMachine,
881
- " · spends credits"
882
- ]
883
- })]
884
- }),
885
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
886
- ref: steerBoxRef,
887
- autoFocus: true,
888
- value: steerInstruction,
889
- onChange: (e) => setSteerInstruction(e.target.value),
890
- onKeyDown: (e) => {
891
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && steerInstruction.trim() && pending !== "steer") onRequestSteer();
892
- },
893
- rows: 3,
894
- placeholder: "e.g. run at 9am on weekdays instead, and also check coffee stock",
895
- className: "mt-3 w-full resize-y rounded-control border border-wire bg-raised p-3 font-mono text-label leading-relaxed text-primary outline-none transition-shadow placeholder:text-disabled focus:border-transparent focus:shadow-focus"
896
- }),
897
- !steerInstruction.trim() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
898
- className: "mt-2 flex flex-wrap gap-1.5",
899
- children: STEER_SEEDS.map((sd) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
900
- type: "button",
901
- onClick: () => {
902
- setSteerInstruction(sd.seed);
903
- steerBoxRef.current?.focus();
904
- },
905
- className: "cursor-pointer rounded-full border border-wire bg-surface px-3 py-1 text-label font-medium text-secondary transition-colors hover:bg-raised hover:text-display",
906
- children: sd.label
907
- }, sd.label))
908
- }),
909
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
910
- className: "mt-3.5 flex flex-wrap items-center gap-x-2.5 gap-y-2",
911
- children: [
912
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
913
- className: btnCost,
914
- disabled: pending === "steer" || !steerInstruction.trim(),
915
- title: offlineHint,
916
- onClick: () => void onRequestSteer(),
917
- children: pending === "steer" ? "Dispatching…" : "Dispatch to your coding agent"
918
- }),
919
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
920
- type: "button",
921
- className: btn,
922
- disabled: pending === "steer",
923
- onClick: () => void copySteerPrompt(),
924
- children: promptCopied ? "✓ Prompt copied" : "Copy prompt"
925
- }),
926
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
927
- className: btn,
928
- disabled: pending === "steer",
929
- onClick: () => setSteerVia(false),
930
- children: "Cancel"
931
- }),
932
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
933
- className: "hidden text-caption text-disabled sm:inline",
934
- children: "⌘↩ dispatch · esc cancel"
935
- })
936
- ]
937
- }),
938
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
939
- className: "mt-2 text-caption leading-snug text-disabled",
940
- children: promptCopied ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
941
- className: "text-secondary",
942
- children: [
943
- "✓ Copied · run your coding agent",
944
- " ",
945
- steerDir ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: ["in ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
946
- className: "break-all font-mono text-primary",
947
- children: steerDir
948
- })] }) : "in the loop’s local directory on this machine",
949
- " ",
950
- "and paste the prompt to adjust the loop yourself."
951
- ]
952
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: "Prefer to adjust it yourself? Copy prompt drops a ready-to-paste prompt for your own coding agent - no dispatch, no credits." })
953
- })
954
- ]
955
- })
956
- ]
957
- });
958
- const actionErrEl = actionErr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, {
959
- message: actionErr,
960
- onDismiss: () => setActionErr(null),
961
- className: "mb-2.5"
962
- });
963
- const crossTeam = detail.team && !detail.team.isActive ? detail.team : null;
964
- const switchTeam = (teamId) => {
965
- setActiveTeamCookie(teamId);
966
- navigate({
967
- to: "/t/$teamId",
968
- params: { teamId }
969
- });
970
- };
971
- const crossTeamEl = crossTeam && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
972
- className: "mb-2.5 flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-control border border-hairline bg-raised px-4 py-2.5",
973
- children: [
974
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
975
- className: "inline-flex items-center gap-2 text-meta font-medium text-secondary",
976
- children: [
977
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
978
- "aria-hidden": true,
979
- className: "size-2 rounded-full bg-interactive"
980
- }),
981
- "Viewing a loop in ",
982
- crossTeam.name
983
- ]
984
- }),
985
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
986
- className: "text-meta text-secondary",
987
- children: "- not your active team."
988
- }),
989
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
990
- type: "button",
991
- onClick: () => switchTeam(crossTeam.id),
992
- className: "ml-auto cursor-pointer text-meta font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
993
- children: "Switch to this team"
994
- })
995
- ]
996
- });
997
- const needsUpdateEl = detail.machine.needsUpdate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
998
- className: "mb-2.5 flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-control border border-warn/40 bg-warn/10 px-4 py-2.5",
999
- children: [
1000
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1001
- className: "inline-flex items-center gap-2 text-meta font-medium text-primary",
1002
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1003
- "aria-hidden": true,
1004
- className: "size-2 rounded-full bg-warn"
1005
- }), "Daemon update required"]
1006
- }),
1007
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1008
- className: "text-meta text-secondary",
1009
- children: [
1010
- "This server requires v",
1011
- detail.machine.requiredDaemonVersion,
1012
- "; ",
1013
- detail.machine.name ? `“${detail.machine.name}”` : "the bound machine",
1014
- " reports ",
1015
- detail.machine.daemonVersion ? `v${detail.machine.daemonVersion}` : "an unknown version",
1016
- ". Queued work will wait."
1017
- ]
1018
- }),
1019
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1020
- type: "button",
1021
- onClick: () => setMachinesOpen(true),
1022
- className: "ml-auto cursor-pointer text-meta font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
1023
- children: "Update daemon"
1024
- })
1025
- ]
1026
- });
1027
- const offlineEl = !online && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1028
- className: "mb-2.5 flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-control border border-hairline bg-raised px-4 py-2.5",
1029
- children: [
1030
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1031
- className: "inline-flex items-center gap-2 text-meta font-medium text-secondary",
1032
- children: [
1033
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1034
- "aria-hidden": true,
1035
- className: `size-2 rounded-full ${asleep ? "bg-rubik-yellow" : "bg-disabled"}`
1036
- }),
1037
- "Machine ",
1038
- detail.machine.name ? `“${detail.machine.name}” ` : "",
1039
- asleep ? "seems to be asleep or offline" : "offline"
1040
- ]
1041
- }),
1042
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1043
- className: "text-meta text-secondary",
1044
- children: [asleep ? "- queued work starts automatically when it reconnects." : "- manual run, evolve, and steer requests remain safely queued.", detail.machine.lastSeen ? ` Last seen ${rel(detail.machine.lastSeen)}.` : ""]
1045
- }),
1046
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1047
- type: "button",
1048
- onClick: () => setMachinesOpen(true),
1049
- className: "ml-auto cursor-pointer text-meta font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
1050
- children: "Reconnect"
1051
- })
1052
- ]
1053
- });
1054
- if (editing) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, {
1055
- back: backLink,
1056
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditHead, {
1057
- name: s.name,
1058
- onBack: () => setEditing(false)
1059
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1060
- className: "mt-5 rounded-card border border-hairline bg-surface px-6 pb-6 pt-2 shadow-card",
1061
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopForm, {
1062
- ref: formRef,
1063
- initial: job,
1064
- channels
1065
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1066
- className: "mt-7 border-t border-hairline pt-4",
1067
- children: [actionErrEl, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1068
- className: "flex flex-wrap gap-2.5",
1069
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1070
- className: btnPrimary,
1071
- disabled: pending === "save",
1072
- onClick: onSave,
1073
- children: pending === "save" ? "Saving…" : "Save"
1074
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1075
- className: btn,
1076
- disabled: pending === "save",
1077
- onClick: () => setEditing(false),
1078
- children: "Cancel"
1079
- })]
1080
- })]
1081
- })]
1082
- })]
1083
- });
1084
- const agentLabel = AGENT_LABEL[job.agent ?? "claude-code"] ?? job.agent ?? "Claude Code";
1085
- const modelLabel = job.exec?.model?.trim() || "default";
1086
- const reasoningEffortLabel = job.exec?.reasoningEffort?.trim() || "default";
1087
- const newestRun = runs[0];
1088
- const latestIncidentRun = newestRun?.reportIncident ? newestRun : void 0;
1089
- const metaDot = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1090
- className: "text-wire",
1091
- children: "·"
1092
- });
1093
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, {
1094
- back: backLink,
1095
- children: [
1096
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
1097
- className: "rounded-card border border-hairline bg-surface px-6 pb-5 pt-[22px] shadow-card",
1098
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1099
- className: "flex flex-wrap items-start justify-between gap-x-6 gap-y-4",
1100
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1101
- className: "min-w-0",
1102
- children: [
1103
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1104
- className: "flex flex-wrap items-center gap-3",
1105
- children: [
1106
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
1107
- className: "text-[28px] font-semibold leading-tight tracking-[-0.015em] text-display",
1108
- children: s.name
1109
- }),
1110
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
1111
- tone: lifecycle === "stopping" ? "running" : void 0,
1112
- dot: lifecycle === "stopping" ? "pulse" : void 0,
1113
- children: lifecycleText
1114
- }),
1115
- s.queued && lifecycle === "active" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
1116
- tone: "outline",
1117
- children: "Queued"
1118
- }) : null,
1119
- s.goal && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
1120
- tone: "success",
1121
- title: s.goal ?? void 0,
1122
- children: "Objective"
1123
- }),
1124
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
1125
- tone: "outline",
1126
- title: "Recorded coding agent",
1127
- children: agentLabel
1128
- }),
1129
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
1130
- tone: "outline",
1131
- title: `Model: ${modelLabel}`,
1132
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1133
- className: "max-w-72 truncate",
1134
- children: ["Model: ", modelLabel]
1135
- })
1136
- }),
1137
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
1138
- tone: "outline",
1139
- title: `Reasoning: ${reasoningEffortLabel}`,
1140
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1141
- className: "max-w-72 truncate",
1142
- children: ["Reasoning: ", reasoningEffortLabel]
1143
- })
1144
- }),
1145
- crossTeam && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
1146
- tone: "outline",
1147
- title: "This loop belongs to another of your teams",
1148
- children: crossTeam.name
1149
- })
1150
- ]
1151
- }),
1152
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1153
- className: "mt-2.5 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-meta text-secondary",
1154
- children: [
1155
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1156
- className: "text-primary",
1157
- title: job.cron,
1158
- children: job.scheduleMode === "continuous" ? `continuous · ${job.continuousDelayMinutes}m delay` : cronText(job.cron)
1159
- }),
1160
- metaDot,
1161
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["next ", fmt(s.nextRun)] }),
1162
- s.nextRun && s.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1163
- className: "text-disabled",
1164
- children: [
1165
- "(",
1166
- until(s.nextRun),
1167
- ")"
1168
- ]
1169
- }),
1170
- metaDot,
1171
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1172
- className: "inline-flex items-center gap-1.5",
1173
- title: online ? "Machine online" : asleep ? "Machine asleep" : "Machine offline",
1174
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `size-1.5 rounded-full ${online ? "bg-rubik-green" : asleep ? "bg-rubik-yellow" : "bg-disabled"}` }), detail.machine.name || "machine"]
1175
- }),
1176
- metaDot,
1177
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
1178
- className: "font-mono text-label text-disabled",
1179
- children: s.id
1180
- })
1181
- ]
1182
- }),
1183
- s.goal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1184
- className: "mt-2 text-body leading-snug text-secondary",
1185
- children: ["Working toward: ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1186
- className: "text-primary",
1187
- children: s.goal
1188
- })]
1189
- }),
1190
- latestIncidentRun?.reportIncident && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1191
- className: "mt-2 text-body leading-snug text-secondary",
1192
- children: latestIncidentRun.phase === "error" ? "Last run failed · Terminal report rejected" : "Last run telemetry warning · Terminal report rejected"
1193
- })
1194
- ]
1195
- })
1196
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1197
- className: "mt-5 border-t border-hairline pt-4",
1198
- children: [
1199
- crossTeamEl,
1200
- needsUpdateEl,
1201
- offlineEl,
1202
- actionErrEl,
1203
- flashLine,
1204
- actionBar
1205
- ]
1206
- })]
1207
- }),
1208
- steerDispatched && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1209
- className: "mt-6 rounded-card border border-hairline bg-surface px-6 py-5 shadow-card",
1210
- style: { animation: "fadeIn 0.2s ease-out" },
1211
- "aria-live": "polite",
1212
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1213
- className: "flex flex-wrap items-center gap-x-4 gap-y-2",
1214
- children: [!steerRun || steerRun.queued ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1215
- className: "inline-flex items-center gap-2.5 text-body text-secondary",
1216
- children: [
1217
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1218
- "aria-hidden": true,
1219
- className: "size-1.5 shrink-0 rounded-full bg-disabled"
1220
- }),
1221
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1222
- className: "font-medium text-primary",
1223
- children: "Steer queued"
1224
- }),
1225
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1226
- "waiting for ",
1227
- onMachine,
1228
- " and higher-priority work…"
1229
- ] })
1230
- ]
1231
- }) : steerRun.running ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1232
- className: "inline-flex min-w-0 items-center gap-2.5 text-body text-secondary",
1233
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1234
- "aria-hidden": true,
1235
- className: "size-1.5 shrink-0 rounded-full",
1236
- style: runPulseStyle
1237
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1238
- className: "shrink-0 font-medium text-primary",
1239
- children: "Applying your steer"
1240
- })]
1241
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1242
- className: "inline-flex min-w-0 items-center gap-2 text-body",
1243
- children: [
1244
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1245
- "aria-hidden": true,
1246
- className: "size-2.5 shrink-0 rounded-[2px]",
1247
- style: { background: dotColor(steerRun) }
1248
- }),
1249
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1250
- className: "font-medium",
1251
- style: { color: dotColor(steerRun) },
1252
- children: steerRun.canceled ? "Steer canceled" : steerRun.phase === "error" ? "Steer failed" : "Steer applied"
1253
- }),
1254
- steerRun.error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1255
- className: "truncate text-secondary",
1256
- children: ["· ", steerRun.error]
1257
- })
1258
- ]
1259
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1260
- className: "ml-auto flex items-center gap-3.5",
1261
- children: [steerRun && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
1262
- to: "/loops/$loopId/runs/$runId",
1263
- params: {
1264
- loopId: id,
1265
- runId: steerRun.id
1266
- },
1267
- className: "text-label font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
1268
- children: "View run →"
1269
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1270
- type: "button",
1271
- onClick: dismissSteer,
1272
- className: "inline-flex cursor-pointer items-center gap-1.5 border-none bg-transparent p-0 text-meta font-medium text-secondary transition-colors hover:text-display",
1273
- children: steerSettled ? "Done" : "Hide"
1274
- })]
1275
- })]
1276
- }), steerSettled && steerRun.message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1277
- className: "mt-3.5",
1278
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pre, { children: steerRun.message })
1279
- })]
1280
- }),
1281
- hasUi && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1282
- className: "mt-6 min-w-0 rounded-card border border-hairline bg-surface px-6 py-5 shadow-card",
1283
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1284
- className: `mb-3.5 border-b border-hairline pb-1.5 text-label font-semibold text-secondary`,
1285
- children: "Dashboard"
1286
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1287
- className: "min-w-0 overflow-x-auto",
1288
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
1289
- fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loading, { className: "py-4" }),
1290
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopView, {
1291
- html: job.ui,
1292
- runs,
1293
- loopId: id,
1294
- taskFile: job.taskFile
1295
- })
1296
- })
1297
- })]
1298
- }),
1299
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1300
- className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(300px,360px)]",
1301
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopFilesPanel, {
1302
- loopId: id,
1303
- taskFile: job.taskFile,
1304
- taskFileContent: detail.taskFileContent,
1305
- taskFileSyncedAt: detail.taskFileSyncedAt,
1306
- running
1307
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunsSection, {
1308
- loopId: id,
1309
- summary: s,
1310
- runs,
1311
- older,
1312
- onMore: async () => {
1313
- const oldest = (older.length ? mergeRuns(s.runs ?? [], older) : s.runs ?? [])[0];
1314
- if (!oldest) return 0;
1315
- const more = await loadOlderRuns({ data: {
1316
- loopId: id,
1317
- beforeTs: oldest.ts,
1318
- limit: 16
1319
- } });
1320
- if (more.length) setOlder((prev) => mergeRuns(prev, more));
1321
- return more.length;
1322
- },
1323
- onPickRun: (run) => navigate({
1324
- to: "/loops/$loopId/runs/$runId",
1325
- params: {
1326
- loopId: id,
1327
- runId: run.id
1328
- }
1329
- })
1330
- })]
1331
- }),
1332
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MachinesModal, {
1333
- open: machinesOpen,
1334
- onClose: () => setMachinesOpen(false)
1335
- })
1336
- ]
1337
- });
1338
- }
1339
- /**
1340
- * Settings page heading. The field form is an in-page mode takeover (NOT
1341
- * a modal), so this is a plain heading - NOT `ModalHead`, whose Base UI
1342
- * `Dialog.Title`/`Dialog.Close` require a `Dialog.Root` ancestor and throw
1343
- * ("Cannot destructure property 'store' of 'useDialogRootContext(...)'") when
1344
- * rendered on a bare page.
1345
- */
1346
- function EditHead({ name, onBack }) {
1347
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1348
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
1349
- type: "button",
1350
- onClick: onBack,
1351
- className: btnQuiet,
1352
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1353
- "aria-hidden": true,
1354
- children: "←"
1355
- }), " Back to loop"]
1356
- }),
1357
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", {
1358
- className: "mt-2.5 text-[24px] font-semibold leading-tight tracking-[-0.015em] text-display",
1359
- children: ["Settings · ", name]
1360
- }),
1361
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1362
- className: "mt-1.5 max-w-[640px] text-meta leading-snug text-secondary",
1363
- children: "Loop configuration, notifications, and push channel, saved directly - no agent pass or credits. For changes in plain words, use Steer instead."
1364
- })
1365
- ] });
1366
- }
1367
- /** The page shell — centered column, a back affordance, consistent padding. */
1368
- function Shell({ back, children }) {
1369
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
1370
- className: "mx-auto max-w-[1360px] px-8 pb-24 pt-10",
1371
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1372
- className: "mb-5",
1373
- children: back
1374
- }), children]
1375
- });
1376
- }
1377
- /** The runs panel — the signature timeline strip (paged) over a scannable list
1378
- * of recent runs, each row linking to its own run-detail route. */
1379
- function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1380
- const stripRuns = (0, import_react.useMemo)(() => older.length ? mergeRuns(summary.runs ?? [], older) : summary.runs ?? [], [summary.runs, older]);
1381
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1382
- className: "min-w-0",
1383
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1384
- className: "mb-2.5 flex items-end justify-between gap-3 border-b border-hairline pb-1.5",
1385
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
1386
- className: sectionHeadCls,
1387
- children: [
1388
- "Runs (",
1389
- summary.runCount,
1390
- ")"
1391
- ]
1392
- })
1393
- }), summary.runCount === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1394
- className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled",
1395
- children: "Never run"
1396
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1397
- className: "rounded-card border border-hairline bg-surface px-5 pb-4 pt-5 shadow-card",
1398
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Timeline, {
1399
- job: summary,
1400
- runs: stripRuns,
1401
- total: summary.runCount,
1402
- onLoadMore: onMore,
1403
- onPickRun
1404
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
1405
- className: "mt-5 max-h-[clamp(280px,46vh,520px)] divide-y divide-hairline overflow-y-auto border-t border-hairline",
1406
- children: runs.map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
1407
- to: "/loops/$loopId/runs/$runId",
1408
- params: {
1409
- loopId,
1410
- runId: x.id
1411
- },
1412
- className: "flex items-start gap-2.5 py-2.5 transition-colors hover:bg-raised",
1413
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1414
- className: "mt-1 inline-block size-2.5 shrink-0 rounded-[2px]",
1415
- style: { background: dotColor(x) },
1416
- title: dotLabel(x),
1417
- "aria-label": dotLabel(x)
1418
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1419
- className: "min-w-0 flex-1",
1420
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1421
- className: "flex items-baseline justify-between gap-2",
1422
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1423
- className: "font-mono text-label text-secondary",
1424
- children: tsShort(x.ts)
1425
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1426
- className: "shrink-0 font-mono text-caption text-disabled",
1427
- children: dur(x.durationMs)
1428
- })]
1429
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1430
- className: "mt-0.5 block",
1431
- children: x.queued ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1432
- className: "inline-flex items-center gap-2 text-meta text-secondary",
1433
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1434
- "aria-hidden": true,
1435
- className: "size-1.5 rounded-full bg-disabled"
1436
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Queued" })]
1437
- }) : x.running ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1438
- className: "inline-flex items-center gap-2 text-meta text-secondary",
1439
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1440
- "aria-hidden": true,
1441
- className: "size-1.5 rounded-full",
1442
- style: runPulseStyle
1443
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Running" })]
1444
- }) : x.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1445
- className: "line-clamp-2 text-meta text-secondary",
1446
- children: x.error
1447
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1448
- className: "line-clamp-2 text-meta text-primary",
1449
- children: x.message || dotLabel(x)
1450
- })
1451
- })]
1452
- })]
1453
- }) }, x.id))
1454
- })]
1455
- })]
1456
- });
1457
- }
1458
- /**
1459
- * Loop detail PAGE — `/loops/$loopId`. The dedicated page that replaced the old
1460
- * dashboard modal: a loop header + action toolbar, the unified Files panel (task
1461
- * file alongside synced artifacts), and the runs timeline. The view owns its own
1462
- * data + self-poll (ssr:false so the session cookie rides along with its fetches,
1463
- * like the dashboard loader). Run rows link on to `/loops/$loopId/runs/$runId`.
1464
- *
1465
- * Auth-gated exactly like the dashboard (`/`): the loader checks getAuthState +
1466
- * the session so a logged-out/expired DEEP LINK shows the sign-in CTA instead of
1467
- * a raw `loop not found` error from the ownership-blocked data fetch.
1468
- */
1469
- function LoopDetailPage() {
1470
- const { loopId } = Route.useParams();
1471
- const { auth } = Route.useLoaderData() ?? { auth: { enabled: false } };
1472
- const { data: session, isPending } = useSession();
1473
- if (auth?.enabled && !isPending && !session) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SignIn, { callbackURL: `/loops/${loopId}` });
1474
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopDetailView, { id: loopId });
1475
- }
1476
- //#endregion
1477
- export { LoopDetailPage as component };