@kky42/pievo-server 2.2.0 → 2.4.1

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-CEddj7la.js +1 -0
  3. package/.output/public/assets/SignIn-BdkKWOSs.js +1 -0
  4. package/.output/public/assets/actionUi-BYBeYy7U.js +1 -0
  5. package/.output/public/assets/app-BAgHQVvr.css +2 -0
  6. package/.output/public/assets/index-BZLqD3JI.js +12 -0
  7. package/.output/public/assets/invite._token-BCLgmTO7.js +1 -0
  8. package/.output/public/assets/invite._token-ZnVQqqq4.js +1 -0
  9. package/.output/public/assets/lifecycleUi-C7dwHtbw.js +1 -0
  10. package/.output/public/assets/loops._loopId-X8PSlgdB.js +67 -0
  11. package/.output/public/assets/loops._loopId_.runs._runId-CA0-q1y5.js +3 -0
  12. package/.output/public/assets/preload-helper-CUIO87nJ.js +5 -0
  13. package/.output/public/assets/routes-BjKcDy6D.js +1 -0
  14. package/.output/public/assets/routes-bfMcArTT.js +1 -0
  15. package/.output/public/assets/t._teamId-B75J8h0x.js +1 -0
  16. package/.output/public/assets/t._teamId-D7WVPMDr.js +1 -0
  17. package/.output/public/assets/ui-DGOT8AoZ.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-D_hKsCsv.mjs +1098 -0
  22. package/.output/server/_ssr/{notifyFns-DXMbgYu2.mjs → MachinesModal-DZ_9ErAU.mjs} +57 -35
  23. package/.output/server/_ssr/{SignIn-C4fSJl_j.mjs → SignIn-DjSrg0nO.mjs} +10 -24
  24. package/.output/server/_ssr/SocialLinks-BTjsnVOm.mjs +1051 -0
  25. package/.output/server/_ssr/{actionUi-BKF6xI3d.mjs → actionUi-D15R8L7R.mjs} +3 -3
  26. package/.output/server/_ssr/{artifactFiles-C8PQKN6V.mjs → artifactFiles-CV3OXxRD.mjs} +7 -8
  27. package/.output/server/_ssr/{artifactKind-BW0isV8L.mjs → artifactKind-ByzVa7fg.mjs} +27 -2
  28. package/.output/server/_ssr/artifacts-BhQvaGjF.mjs +2 -0
  29. package/.output/server/_ssr/{auth-Sie6sQHF.mjs → auth-CPCDf6Wr.mjs} +16 -19
  30. package/.output/server/_ssr/{boot-akwC2sQg.mjs → boot-BbWxaF-z.mjs} +1359 -3843
  31. package/.output/server/_ssr/boot-COHf8Gv3.mjs +2 -0
  32. package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-CLY8cORt.mjs → bun-sqlite-dialect-BW9W1_Ps-8MIh9jN1.mjs} +2 -2
  33. package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-CRsXQNRe.mjs → d1-sqlite-dialect-BLC8LXE6-CcMQ01_d.mjs} +2 -2
  34. package/.output/server/_ssr/db-dw61xIt1.mjs +2 -0
  35. package/.output/server/_ssr/{dist-Cc_x6bBb.mjs → dist-AxCsRawm.mjs} +2 -2
  36. package/.output/server/_ssr/{factory-CDwHL_zY.mjs → factory-S7MaMVC2.mjs} +2 -2
  37. package/.output/server/_ssr/{invite._token-BlbVjz-o.mjs → invite._token-B1UgMbNQ.mjs} +3 -3
  38. package/.output/server/_ssr/{invite._token-BE_QUAVb.mjs → invite._token-BliJIGk4.mjs} +4 -5
  39. package/.output/server/_ssr/{invite._token-C7GjrJxr.mjs → invite._token-D296YVGd.mjs} +7 -7
  40. package/.output/server/_ssr/{kysely-adapter-zflsyYBA.mjs → kysely-adapter-DUdpqcCJ.mjs} +1 -1
  41. package/.output/server/_ssr/{lifecycleUi-Djb44Aj3.mjs → lifecycleUi-Dad3hvhm.mjs} +12 -11
  42. package/.output/server/_ssr/logger-DdUWuMdh.mjs +2 -0
  43. package/.output/server/_ssr/loopApi-Dy8ztANM.mjs +389 -0
  44. package/.output/server/_ssr/loopProjection-BZ1_IYCr.mjs +110 -0
  45. package/.output/server/_ssr/loops._loopId-CKsAnxMl.mjs +1255 -0
  46. package/.output/server/_ssr/{loops._loopId_.runs._runId-rBHW41dx.mjs → loops._loopId_.runs._runId-DG405pQs.mjs} +29 -74
  47. package/.output/server/_ssr/{loops._loopId_.runs._runId-Dk1QY49p.mjs → loops._loopId_.runs._runId-a_SyY8rX.mjs} +4 -5
  48. package/.output/server/_ssr/{machineFns-CoqLRLRh.mjs → machineFns-BSaE7K88.mjs} +3 -5
  49. package/.output/server/_ssr/{node-sqlite-dialect-dmrazfbM.mjs → node-sqlite-dialect-rWwVyt2J.mjs} +2 -2
  50. package/.output/server/_ssr/router-lMkobh5r.mjs +618 -0
  51. package/.output/server/_ssr/{routes-DebVUmCS.mjs → routes-CRHc9cLG.mjs} +6 -10
  52. package/.output/server/_ssr/{routes-Bx0bRxt9.mjs → routes-DNCZrs8z.mjs} +3 -3
  53. package/.output/server/_ssr/{routes-dQCBLTxY.mjs → routes-ufPbrGHs.mjs} +6 -6
  54. package/.output/server/_ssr/runDiff-CrZzjokj.mjs +2 -0
  55. package/.output/server/_ssr/ssr.mjs +65 -97
  56. package/.output/server/_ssr/store-CirPHuib.mjs +2 -0
  57. package/.output/server/_ssr/{t._teamId-BqOUF-0J.mjs → t._teamId-BoBoYEHL.mjs} +7 -7
  58. package/.output/server/_ssr/{t._teamId-D9nCEa2h.mjs → t._teamId-DcWOYjPf.mjs} +4 -4
  59. package/.output/server/_ssr/{t._teamId-BzWy4Ft2.mjs → t._teamId-QD2nN0wn.mjs} +7 -11
  60. package/.output/server/_ssr/{teamFns-BcnGMC-g.mjs → teamFns-BsmconJ1.mjs} +4 -4
  61. package/.output/server/_ssr/{teamFns-bm82twd5.mjs → teamFns-DcoVuWyH.mjs} +2 -2
  62. package/.output/server/_ssr/tokens-XngoyCU8.mjs +2 -0
  63. package/.output/server/_ssr/{ui-B6mE5-5v.mjs → ui-DmFKW2H_.mjs} +33 -47
  64. package/.output/server/{_tanstack-start-manifest_v-COr27bxK.mjs → _tanstack-start-manifest_v-Xd5bMpJe.mjs} +25 -33
  65. package/.output/server/drizzle/{0000_round_marauders.sql → 0000_baseline.sql} +102 -44
  66. package/.output/server/drizzle/meta/_journal.json +3 -115
  67. package/.output/server/index.mjs +82 -131
  68. package/drizzle/{0000_round_marauders.sql → 0000_baseline.sql} +102 -44
  69. package/drizzle/meta/_journal.json +3 -115
  70. package/package.json +2 -10
  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-BTXoqfzM.js +0 -59
  75. package/.output/public/assets/Modal-Ba36NAkv.js +0 -1
  76. package/.output/public/assets/SignIn-Cc1zxGnA.js +0 -1
  77. package/.output/public/assets/actionUi-DDDfD0WX.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-BGM1XGY0.js +0 -12
  81. package/.output/public/assets/invite._token-C3t4JfAY.js +0 -1
  82. package/.output/public/assets/invite._token-CUCNcN89.js +0 -1
  83. package/.output/public/assets/jsx-runtime-BseJUIpC.js +0 -1
  84. package/.output/public/assets/lifecycleUi-CMFw5Nlj.js +0 -1
  85. package/.output/public/assets/loopApi-CT1mMhlb.js +0 -3
  86. package/.output/public/assets/loops._loopId-CrSSvSVl.js +0 -4
  87. package/.output/public/assets/loops._loopId_.runs._runId-B5Qacloe.js +0 -3
  88. package/.output/public/assets/notifyFns-CYiZT7CK.js +0 -1
  89. package/.output/public/assets/preload-helper-DFkkpP6W.js +0 -3
  90. package/.output/public/assets/routes-B6Kg4mFC.js +0 -1
  91. package/.output/public/assets/routes-DsdcURfk.js +0 -1
  92. package/.output/public/assets/t._teamId-C9uEyAh_.js +0 -1
  93. package/.output/public/assets/t._teamId-Cmm4xdKj.js +0 -1
  94. package/.output/public/assets/ui-Gb1413-F.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-0yoKh8Em.mjs +0 -2733
  130. package/.output/server/_ssr/LoopPlaybook-uq_sFfgU.mjs +0 -2105
  131. package/.output/server/_ssr/LoopView-BlOY-jfJ.mjs +0 -1300
  132. package/.output/server/_ssr/Modal-Bc_5TYGr.mjs +0 -49
  133. package/.output/server/_ssr/adapters-RReIIwAx.mjs +0 -134
  134. package/.output/server/_ssr/artifactView-iuPsp5aa.mjs +0 -304
  135. package/.output/server/_ssr/artifacts-B_F6vSih.mjs +0 -2
  136. package/.output/server/_ssr/boot-pVS0wjd0.mjs +0 -2
  137. package/.output/server/_ssr/db-Bdm9PFZF.mjs +0 -2
  138. package/.output/server/_ssr/logger-tfiliKTi.mjs +0 -2
  139. package/.output/server/_ssr/loopApi-8_KgBapr.mjs +0 -109
  140. package/.output/server/_ssr/loopApi-JzmqWENY.mjs +0 -564
  141. package/.output/server/_ssr/loops._loopId-D-aCCtrX.mjs +0 -1477
  142. package/.output/server/_ssr/loops._loopId-DZ-FjscA.mjs +0 -30
  143. package/.output/server/_ssr/notifyFns-CH7-bcmd.mjs +0 -118
  144. package/.output/server/_ssr/router-BFPzSTFx.mjs +0 -679
  145. package/.output/server/_ssr/runDiff-qA1C8R_L.mjs +0 -2
  146. package/.output/server/_ssr/store-DWUdkIq2.mjs +0 -2
  147. package/.output/server/_ssr/tokens-DG8z1GIg.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
@@ -0,0 +1,1255 @@
1
+ import { o as __toESM } from "../_runtime.mjs";
2
+ import { s as CODING_AGENTS } from "./boot-BbWxaF-z.mjs";
3
+ import { g as require_react, h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
4
+ import { D as tsShort, E as selectCls, O as until, S as rel, T as sectionHeadCls, a as btn, b as labelCls, f as dotColor, g as fmt, h as dur, l as btnQuiet, n as Loading, o as btnDanger, p as dotLabel, r as Pill, s as btnPrimary, t as ErrorBanner, v as humanBytes, w as runPulseStyle, y as inputCls } from "./ui-DmFKW2H_.mjs";
5
+ import { i as useFlash, n as FlashLine, r as LoadErrorCard, t as ConfirmBar } from "./actionUi-D15R8L7R.mjs";
6
+ import { d as useNavigate, u as Link } from "../_libs/@tanstack/react-router+[...].mjs";
7
+ import { D as useSession, S as runLoop, T as stopLoop, b as patchLoop, c as deleteLoop, l as getArtifact, m as getLoopDetail, u as getArtifacts, v as loadOlderRuns, w as startLoop, x as pauseLoop } from "./SocialLinks-BTjsnVOm.mjs";
8
+ import { t as setActiveTeamCookie } from "./teamCookie-B7hLOIie.mjs";
9
+ import { t as SignIn } from "./SignIn-DjSrg0nO.mjs";
10
+ import { i as lifecycleDisplay, n as daemonStopSupport, r as deriveLoopLifecycle, t as DAEMON_UPGRADE_REQUIRED } from "./lifecycleUi-Dad3hvhm.mjs";
11
+ import { a as Timeline, s as mergeRuns, t as MachinesModal } from "./MachinesModal-DZ_9ErAU.mjs";
12
+ import { n as artifactKind, t as Route } from "./artifactKind-ByzVa7fg.mjs";
13
+ import { t as purify } from "../_libs/dompurify.mjs";
14
+ import { t as g } from "../_libs/marked.mjs";
15
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-CKsAnxMl.js
16
+ var import_react = /* @__PURE__ */ __toESM(require_react());
17
+ var import_jsx_runtime = require_jsx_runtime();
18
+ /** Download URL for one artifact - the path is segment-encoded so a nested path
19
+ * (`data/raw.json`) round-trips and can't be mistaken for extra route segments. */
20
+ function downloadHref(loopId, path) {
21
+ const enc = path.split("/").map(encodeURIComponent).join("/");
22
+ return `/api/artifact/${encodeURIComponent(loopId)}/${enc}`;
23
+ }
24
+ /** Inline render URL for an image artifact - the same bytes as `downloadHref`
25
+ * but served with the real image content-type + `inline` disposition (see the
26
+ * route). Used as an <img> src; never for a document the browser navigates to. */
27
+ function inlineHref(loopId, path) {
28
+ return `${downloadHref(loopId, path)}?view=inline`;
29
+ }
30
+ /**
31
+ * Shared markdown → sanitized HTML pipeline: marked (GFM) → DOMPurify (an
32
+ * allowlisted prose subset, NO script/handlers). ONE sanitizer config for every
33
+ * artifact markdown surface so the allowlist cannot drift between viewers.
34
+ * Render the output under
35
+ * the `.artifact-markdown` styles.
36
+ */
37
+ var MD_SANITIZE = {
38
+ ALLOWED_TAGS: [
39
+ "h1",
40
+ "h2",
41
+ "h3",
42
+ "h4",
43
+ "h5",
44
+ "h6",
45
+ "p",
46
+ "br",
47
+ "hr",
48
+ "strong",
49
+ "b",
50
+ "em",
51
+ "i",
52
+ "del",
53
+ "s",
54
+ "code",
55
+ "pre",
56
+ "blockquote",
57
+ "ul",
58
+ "ol",
59
+ "li",
60
+ "a",
61
+ "span",
62
+ "table",
63
+ "thead",
64
+ "tbody",
65
+ "tr",
66
+ "th",
67
+ "td",
68
+ "img"
69
+ ],
70
+ ALLOWED_ATTR: [
71
+ "href",
72
+ "title",
73
+ "target",
74
+ "rel",
75
+ "src",
76
+ "alt"
77
+ ]
78
+ };
79
+ /** Markdown string → sanitized HTML string (safe for dangerouslySetInnerHTML). */
80
+ function renderMarkdown(content) {
81
+ return purify.sanitize(g.parse(content, {
82
+ async: false,
83
+ gfm: true
84
+ }), MD_SANITIZE);
85
+ }
86
+ /** Render a Markdown artifact through the shared sanitized GFM pipeline. */
87
+ function MarkdownView({ content }) {
88
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
89
+ className: "artifact-markdown px-5 py-4",
90
+ dangerouslySetInnerHTML: { __html: (0, import_react.useMemo)(() => renderMarkdown(content), [content]) }
91
+ });
92
+ }
93
+ /** A small monospace caption strip above a file's content (path · meta [· action]). */
94
+ function ViewerHead({ path, meta, action }) {
95
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
96
+ className: "sticky top-0 z-10 flex flex-wrap items-center gap-x-2 gap-y-1 border-b border-hairline bg-surface px-5 py-2.5",
97
+ children: [
98
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
99
+ className: "break-all font-mono text-label text-primary",
100
+ children: path
101
+ }),
102
+ meta && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
103
+ className: "font-mono text-caption text-disabled",
104
+ children: ["· ", meta]
105
+ }),
106
+ action && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
107
+ className: "ml-auto shrink-0",
108
+ children: action
109
+ })
110
+ ]
111
+ });
112
+ }
113
+ /** The binary / oversize body - download affordance or the metadata-only note. */
114
+ function BinaryNotice({ loopId, path, oversize }) {
115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
116
+ className: "px-5 py-8 text-body text-secondary",
117
+ children: oversize ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
118
+ className: "text-disabled",
119
+ children: "Too large to preview - stored as metadata only (no synced bytes)."
120
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
121
+ "Binary file - not previewable.",
122
+ " ",
123
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
124
+ href: downloadHref(loopId, path),
125
+ download: true,
126
+ className: "text-interactive underline underline-offset-2 transition-colors hover:text-display",
127
+ children: "Download"
128
+ })
129
+ ] })
130
+ });
131
+ }
132
+ /** The viewer's action strip: a Preview/Source toggle (renderable types only)
133
+ * plus an always-present Download. Sits just under the content head. */
134
+ function ViewerActions({ loopId, path, mode, onMode, showToggle }) {
135
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
136
+ className: "flex items-center gap-2 border-b border-hairline bg-surface px-5 py-1.5",
137
+ children: [showToggle && mode && onMode && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
138
+ className: "inline-flex overflow-hidden rounded-control border border-hairline",
139
+ children: ["preview", "source"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
140
+ type: "button",
141
+ onClick: () => onMode(m),
142
+ className: `cursor-pointer border-none px-2.5 py-0.5 text-caption font-medium transition-colors ${mode === m ? "bg-raised text-display" : "bg-transparent text-secondary hover:text-display"}`,
143
+ children: m === "preview" ? "Preview" : "Source"
144
+ }, m))
145
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
146
+ href: downloadHref(loopId, path),
147
+ download: true,
148
+ className: "ml-auto shrink-0 text-caption font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
149
+ children: "Download ↓"
150
+ })]
151
+ });
152
+ }
153
+ /**
154
+ * Rendered HTML in a STRICT sandbox. `sandbox="allow-scripts"` WITHOUT
155
+ * `allow-same-origin` is the load-bearing isolation: the frame gets an opaque
156
+ * origin, so document.cookie is empty, storage is inaccessible, and `parent`
157
+ * access throws a cross-origin SecurityError - synced markup can render (styles
158
+ * + its own scripts run) yet can't touch the app's session or DOM. `srcDoc`
159
+ * keeps the bytes off any navigable app-origin URL. Bounded height with internal
160
+ * scroll; a white backdrop matches what report HTML expects regardless of theme.
161
+ */
162
+ function HtmlPreview({ html }) {
163
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("iframe", {
164
+ title: "Rendered HTML artifact (sandboxed)",
165
+ srcDoc: html,
166
+ sandbox: "allow-scripts",
167
+ referrerPolicy: "no-referrer",
168
+ className: "h-[min(70vh,620px)] w-full border-0 bg-white"
169
+ });
170
+ }
171
+ /** An image artifact, rendered from the hardened inline route (never inlined
172
+ * into the app DOM). Falls back to a download prompt if the bytes won't load. */
173
+ function ImageView({ loopId, file }) {
174
+ const [failed, setFailed] = (0, import_react.useState)(false);
175
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
176
+ className: "min-w-0",
177
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerActions, {
178
+ loopId,
179
+ path: file.path,
180
+ showToggle: false
181
+ }), failed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
182
+ className: "px-5 py-8 text-body text-secondary",
183
+ children: [
184
+ "Couldn't load this image.",
185
+ " ",
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
187
+ href: downloadHref(loopId, file.path),
188
+ download: true,
189
+ className: "text-interactive underline underline-offset-2 transition-colors hover:text-display",
190
+ children: "Download"
191
+ }),
192
+ " ",
193
+ "instead."
194
+ ]
195
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
196
+ className: "flex justify-center overflow-auto px-5 py-6",
197
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
198
+ src: inlineHref(loopId, file.path),
199
+ alt: file.path,
200
+ onError: () => setFailed(true),
201
+ className: "max-w-full rounded-control border border-hairline bg-white object-contain"
202
+ })
203
+ })]
204
+ });
205
+ }
206
+ /** A text-bearing artifact (html / markdown / plain text): fetch the bytes, then
207
+ * render per its kind with a Preview/Source toggle (html + markdown) + download. */
208
+ function TextArtifactView({ loopId, file, kind }) {
209
+ const [loaded, setLoaded] = (0, import_react.useState)({ loading: true });
210
+ const [mode, setMode] = (0, import_react.useState)("preview");
211
+ (0, import_react.useEffect)(() => {
212
+ let alive = true;
213
+ setLoaded({ loading: true });
214
+ getArtifact({ data: {
215
+ loopId,
216
+ path: file.path
217
+ } }).then((c) => alive && setLoaded(c)).catch((e) => alive && setLoaded({ error: String(e) }));
218
+ return () => {
219
+ alive = false;
220
+ };
221
+ }, [
222
+ loopId,
223
+ file.path,
224
+ file.updatedAt
225
+ ]);
226
+ if ("loading" in loaded) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
227
+ className: "px-5 py-6 text-body text-disabled",
228
+ children: "Loading…"
229
+ });
230
+ if ("binary" in loaded) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BinaryNotice, {
231
+ loopId,
232
+ path: file.path,
233
+ oversize: loaded.oversize
234
+ });
235
+ if ("error" in loaded) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
236
+ className: "px-5 py-6 text-body text-accent",
237
+ children: ["Couldn't load this file - ", loaded.error]
238
+ });
239
+ const text = loaded.text || "(empty file)";
240
+ const renderable = kind === "html" || kind === "markdown";
241
+ const showSource = !renderable || mode === "source";
242
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
243
+ className: "min-w-0",
244
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerActions, {
245
+ loopId,
246
+ path: file.path,
247
+ mode,
248
+ onMode: setMode,
249
+ showToggle: renderable
250
+ }), kind === "html" && !showSource ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HtmlPreview, { html: loaded.text || "" }) : kind === "markdown" && !showSource ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownView, { content: text }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
251
+ className: "m-0 overflow-x-auto whitespace-pre-wrap px-5 py-4 font-mono text-label leading-relaxed text-secondary",
252
+ children: text
253
+ })]
254
+ });
255
+ }
256
+ /**
257
+ * One artifact's content body, dispatched by display kind (see `artifactKind`).
258
+ * Oversize → metadata-only note (no bytes); image → hardened <img>; other binary
259
+ * → download; text-bearing → the fetch-and-render viewer.
260
+ */
261
+ function ArtifactBody({ loopId, file }) {
262
+ const kind = artifactKind(file.path);
263
+ if (file.oversize) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BinaryNotice, {
264
+ loopId,
265
+ path: file.path,
266
+ oversize: true
267
+ });
268
+ if (kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImageView, {
269
+ loopId,
270
+ file
271
+ }, `${file.path}:${file.updatedAt ?? ""}`);
272
+ if (file.binary) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BinaryNotice, {
273
+ loopId,
274
+ path: file.path,
275
+ oversize: false
276
+ });
277
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArtifactView, {
278
+ loopId,
279
+ file,
280
+ kind
281
+ }, file.path);
282
+ }
283
+ var basename = (path) => path.split("/").pop() || path;
284
+ function LoopFilesPanel({ loopId, configuredPaths, running }) {
285
+ const [artifacts, setArtifacts] = (0, import_react.useState)(null);
286
+ const [selected, setSelected] = (0, import_react.useState)(null);
287
+ const seq = (0, import_react.useRef)(0);
288
+ const refresh = (0, import_react.useCallback)(async () => {
289
+ const mine = ++seq.current;
290
+ try {
291
+ const list = await getArtifacts({ data: { loopId } });
292
+ if (mine === seq.current) setArtifacts(list);
293
+ } catch {
294
+ if (mine === seq.current) setArtifacts((current) => current ?? []);
295
+ }
296
+ }, [loopId]);
297
+ (0, import_react.useEffect)(() => {
298
+ setArtifacts(null);
299
+ setSelected(null);
300
+ refresh();
301
+ }, [loopId, refresh]);
302
+ (0, import_react.useEffect)(() => {
303
+ const timer = setInterval(() => void refresh(), running ? 4e3 : 12e3);
304
+ return () => clearInterval(timer);
305
+ }, [running, refresh]);
306
+ const files = artifacts ?? [];
307
+ const activePath = selected && files.some((file) => file.path === selected) ? selected : files[0]?.path ?? null;
308
+ const active = files.find((file) => file.path === activePath) ?? null;
309
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
310
+ id: "files",
311
+ className: "min-w-0",
312
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
313
+ className: "mb-2.5 flex items-end justify-between gap-3 border-b border-hairline pb-1.5",
314
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
315
+ className: "text-label font-semibold text-secondary",
316
+ children: ["Artifacts", artifacts ? ` (${files.length})` : ""]
317
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
318
+ className: "text-caption font-medium text-disabled",
319
+ children: ["Configured exact paths: ", configuredPaths.length]
320
+ })]
321
+ }), files.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
322
+ className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled shadow-card",
323
+ children: artifacts == null ? "Loading…" : configuredPaths.length ? "No configured artifact has been collected yet." : "No artifact paths configured."
324
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
325
+ 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]",
326
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
327
+ className: "max-h-44 overflow-y-auto border-b border-hairline sm:max-h-none sm:border-b-0 sm:border-r",
328
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
329
+ className: "py-1.5",
330
+ children: files.map((file) => {
331
+ const activeRow = file.path === activePath;
332
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
333
+ type: "button",
334
+ onClick: () => setSelected(file.path),
335
+ className: `flex w-full items-center border-l-2 px-3 py-2 text-left transition-colors ${activeRow ? "border-display bg-raised" : "border-transparent hover:bg-raised/60"}`,
336
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
337
+ className: "min-w-0 flex-1",
338
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
339
+ className: `block truncate font-mono text-label ${activeRow ? "text-display" : "text-primary"}`,
340
+ title: file.path,
341
+ children: basename(file.path)
342
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
343
+ className: "block truncate font-mono text-micro text-disabled",
344
+ title: file.path,
345
+ children: file.path
346
+ })]
347
+ })
348
+ }) }, file.path);
349
+ })
350
+ })
351
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
352
+ className: "min-w-0 overflow-y-auto",
353
+ children: active && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, {
354
+ path: active.path,
355
+ meta: [active.size != null ? humanBytes(active.size) : "", `collected ${fmt(active.updatedAt)}`].filter(Boolean).join(" · ")
356
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactBody, {
357
+ loopId,
358
+ file: active
359
+ })] })
360
+ })]
361
+ })]
362
+ });
363
+ }
364
+ var AGENT_LABEL$1 = {
365
+ "claude-code": "Claude Code",
366
+ codex: "Codex"
367
+ };
368
+ var hintCls = "mt-1 text-caption leading-snug text-disabled";
369
+ function initState(initial) {
370
+ const schedule = initial?.schedule;
371
+ return {
372
+ name: initial?.name ?? "",
373
+ mode: schedule?.mode ?? "cron",
374
+ cron: schedule?.mode === "cron" ? schedule.cron : "0 */3 * * *",
375
+ timezone: schedule?.mode === "cron" ? schedule.timezone : Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
376
+ overlap: schedule?.mode === "cron" ? schedule.overlap : "queue-one",
377
+ delayMinutes: String(schedule?.mode === "continuous" ? schedule.delayMinutes : 1),
378
+ workdir: initial?.workdir ?? "",
379
+ agent: initial?.agent ?? "claude-code",
380
+ model: initial?.model ?? "",
381
+ reasoningEffort: initial?.reasoningEffort ?? "",
382
+ prompt: initial?.prompt ?? "",
383
+ keep: initial?.statusDefinitions?.keep ?? "",
384
+ noChange: initial?.statusDefinitions?.noChange ?? "",
385
+ block: initial?.statusDefinitions?.block ?? "",
386
+ artifacts: (initial?.artifacts ?? []).join("\n")
387
+ };
388
+ }
389
+ function Section({ title, hint }) {
390
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
391
+ className: "mb-3 mt-7 border-b border-hairline pb-1.5 first:mt-2",
392
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
393
+ className: sectionHeadCls,
394
+ children: title
395
+ }), hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
396
+ className: "mt-0.5 text-caption text-disabled",
397
+ children: hint
398
+ })]
399
+ });
400
+ }
401
+ function TextField({ label, value, onChange, mono, hint, placeholder }) {
402
+ const id = (0, import_react.useId)();
403
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
404
+ className: "min-w-0",
405
+ children: [
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
407
+ htmlFor: id,
408
+ className: labelCls,
409
+ children: label
410
+ }),
411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
412
+ id,
413
+ className: `${inputCls} ${mono ? "font-mono" : ""}`,
414
+ value,
415
+ placeholder,
416
+ onChange: (event) => onChange(event.target.value)
417
+ }),
418
+ hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
419
+ className: hintCls,
420
+ children: hint
421
+ })
422
+ ]
423
+ });
424
+ }
425
+ function TextArea({ label, value, onChange, rows = 4, mono, hint }) {
426
+ const id = (0, import_react.useId)();
427
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
428
+ className: "min-w-0",
429
+ children: [
430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
431
+ htmlFor: id,
432
+ className: labelCls,
433
+ children: label
434
+ }),
435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
436
+ id,
437
+ rows,
438
+ className: `${inputCls} h-auto resize-y ${mono ? "font-mono" : ""}`,
439
+ value,
440
+ onChange: (event) => onChange(event.target.value)
441
+ }),
442
+ hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
443
+ className: hintCls,
444
+ children: hint
445
+ })
446
+ ]
447
+ });
448
+ }
449
+ var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial }, ref) {
450
+ const [form, setForm] = (0, import_react.useState)(() => initState(initial));
451
+ const [error, setError] = (0, import_react.useState)(null);
452
+ const set = (key, value) => setForm((current) => ({
453
+ ...current,
454
+ [key]: value
455
+ }));
456
+ (0, import_react.useImperativeHandle)(ref, () => ({ read() {
457
+ const missing = [
458
+ ["Name", form.name],
459
+ ["Working directory", form.workdir],
460
+ ["Prompt", form.prompt],
461
+ ["Keep definition", form.keep],
462
+ ["No-change definition", form.noChange],
463
+ ["Block definition", form.block]
464
+ ].find(([, value]) => !value.trim());
465
+ if (missing) {
466
+ setError(`${missing[0]} is required.`);
467
+ return null;
468
+ }
469
+ const delayMinutes = Number(form.delayMinutes);
470
+ if (form.mode === "continuous" && (!Number.isInteger(delayMinutes) || delayMinutes < 1)) {
471
+ setError("Continuous delay must be an integer of at least 1 minute.");
472
+ return null;
473
+ }
474
+ const schedule = form.mode === "cron" ? {
475
+ mode: "cron",
476
+ cron: form.cron.trim(),
477
+ timezone: form.timezone.trim(),
478
+ overlap: form.overlap
479
+ } : {
480
+ mode: "continuous",
481
+ delayMinutes
482
+ };
483
+ setError(null);
484
+ return {
485
+ name: form.name.trim(),
486
+ schedule,
487
+ workdir: form.workdir.trim(),
488
+ agent: form.agent,
489
+ model: form.model.trim() || null,
490
+ reasoningEffort: form.reasoningEffort.trim() || null,
491
+ prompt: form.prompt,
492
+ statusDefinitions: {
493
+ keep: form.keep,
494
+ noChange: form.noChange,
495
+ block: form.block
496
+ },
497
+ artifacts: form.artifacts.split(/\r?\n/).filter((path) => path.length > 0)
498
+ };
499
+ } }));
500
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
501
+ className: "grid gap-x-10 lg:grid-cols-[minmax(300px,380px)_minmax(0,1fr)]",
502
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
503
+ className: "min-w-0",
504
+ children: [
505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: "Basics" }),
506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
507
+ label: "Name",
508
+ value: form.name,
509
+ onChange: (value) => set("name", value)
510
+ }),
511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
512
+ className: "min-w-0",
513
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
514
+ className: labelCls,
515
+ children: "Schedule"
516
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
517
+ "aria-label": "Schedule",
518
+ className: selectCls,
519
+ value: form.mode,
520
+ onChange: (event) => set("mode", event.target.value),
521
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
522
+ value: "cron",
523
+ children: "Cron"
524
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
525
+ value: "continuous",
526
+ children: "Continuous"
527
+ })]
528
+ })]
529
+ }),
530
+ form.mode === "cron" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
532
+ label: "Cron expression",
533
+ value: form.cron,
534
+ onChange: (value) => set("cron", value),
535
+ mono: true
536
+ }),
537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
538
+ label: "Timezone",
539
+ value: form.timezone,
540
+ onChange: (value) => set("timezone", value),
541
+ mono: true,
542
+ hint: "IANA timezone, for example America/Los_Angeles."
543
+ }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
545
+ className: "min-w-0",
546
+ children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
548
+ className: labelCls,
549
+ children: "Cron overlap"
550
+ }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
552
+ "aria-label": "Cron overlap",
553
+ className: selectCls,
554
+ value: form.overlap,
555
+ onChange: (event) => set("overlap", event.target.value),
556
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
557
+ value: "skip",
558
+ children: "Skip occurrence"
559
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
560
+ value: "queue-one",
561
+ children: "Queue one follow-up"
562
+ })]
563
+ }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
565
+ className: hintCls,
566
+ children: "Applies only when a cron occurrence is due while a run is still open."
567
+ })
568
+ ]
569
+ })
570
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
571
+ label: "Delay after terminal run (minutes)",
572
+ value: form.delayMinutes,
573
+ onChange: (value) => set("delayMinutes", value),
574
+ mono: true
575
+ }),
576
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: "Execution" }),
577
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
578
+ label: "Working directory",
579
+ value: form.workdir,
580
+ onChange: (value) => set("workdir", value),
581
+ mono: true,
582
+ placeholder: "/Users/you/project"
583
+ }),
584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
585
+ className: "min-w-0",
586
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
587
+ className: labelCls,
588
+ children: "Coding agent"
589
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
590
+ "aria-label": "Coding agent",
591
+ className: selectCls,
592
+ value: form.agent,
593
+ onChange: (event) => set("agent", event.target.value),
594
+ children: CODING_AGENTS.map((agent) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
595
+ value: agent,
596
+ children: AGENT_LABEL$1[agent] ?? agent
597
+ }, agent))
598
+ })]
599
+ }),
600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
601
+ label: "Model",
602
+ value: form.model,
603
+ onChange: (value) => set("model", value),
604
+ placeholder: "default",
605
+ hint: "Optional. Empty uses the agent default."
606
+ }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
608
+ label: "Reasoning effort",
609
+ value: form.reasoningEffort,
610
+ onChange: (value) => set("reasoningEffort", value),
611
+ placeholder: "default",
612
+ hint: "Optional. Empty uses the agent default."
613
+ })
614
+ ]
615
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
616
+ className: "min-w-0",
617
+ children: [
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
619
+ title: "Prompt",
620
+ hint: "Sent unchanged before Pievo appends the report contract."
621
+ }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
623
+ label: "User prompt",
624
+ value: form.prompt,
625
+ onChange: (value) => set("prompt", value),
626
+ rows: 10
627
+ }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
629
+ title: "Status definitions",
630
+ hint: "All three definitions are required and are appended to every run."
631
+ }),
632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
633
+ label: "keep",
634
+ value: form.keep,
635
+ onChange: (value) => set("keep", value),
636
+ rows: 2
637
+ }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
639
+ label: "no-change",
640
+ value: form.noChange,
641
+ onChange: (value) => set("noChange", value),
642
+ rows: 2
643
+ }),
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
645
+ label: "block",
646
+ value: form.block,
647
+ onChange: (value) => set("block", value),
648
+ rows: 2
649
+ }),
650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
651
+ title: "Artifacts",
652
+ hint: "Optional exact paths relative to the working directory. One path per line; globs are not supported."
653
+ }),
654
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextArea, {
655
+ label: "Artifact paths",
656
+ value: form.artifacts,
657
+ onChange: (value) => set("artifacts", value),
658
+ rows: 6,
659
+ mono: true
660
+ }),
661
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
662
+ className: "mt-4 text-body text-accent",
663
+ children: error
664
+ })
665
+ ]
666
+ })]
667
+ });
668
+ });
669
+ var AGENT_LABEL = {
670
+ "claude-code": "Claude Code",
671
+ codex: "Codex"
672
+ };
673
+ function LoopDetailView({ id }) {
674
+ const navigate = useNavigate();
675
+ const [detail, setDetail] = (0, import_react.useState)(null);
676
+ const [err, setErr] = (0, import_react.useState)(null);
677
+ const [actionErr, setActionErr] = (0, import_react.useState)(null);
678
+ const [editing, setEditing] = (0, import_react.useState)(false);
679
+ const [machinesOpen, setMachinesOpen] = (0, import_react.useState)(false);
680
+ const [pending, setPending] = (0, import_react.useState)(null);
681
+ const [confirmingDelete, setConfirmingDelete] = (0, import_react.useState)(false);
682
+ const [flash, setFlash] = useFlash();
683
+ const [older, setOlder] = (0, import_react.useState)([]);
684
+ const formRef = (0, import_react.useRef)(null);
685
+ const deletingRef = (0, import_react.useRef)(false);
686
+ const load = (0, import_react.useCallback)(async (silent = false) => {
687
+ try {
688
+ const next = await getLoopDetail({ data: id });
689
+ setDetail(next);
690
+ deletingRef.current = next.summary.deleteRequestedAt != null;
691
+ setErr(null);
692
+ } catch (error) {
693
+ if (deletingRef.current) navigate({ to: "/" });
694
+ else if (!silent) setErr(String(error));
695
+ }
696
+ }, [id]);
697
+ (0, import_react.useEffect)(() => {
698
+ setDetail(null);
699
+ setEditing(false);
700
+ setConfirmingDelete(false);
701
+ setOlder([]);
702
+ load();
703
+ }, [id, load]);
704
+ const running = !!detail?.summary.running;
705
+ (0, import_react.useEffect)(() => {
706
+ if (editing) return;
707
+ const timer = setInterval(() => void load(true), running || deletingRef.current ? 3e3 : 8e3);
708
+ return () => clearInterval(timer);
709
+ }, [
710
+ editing,
711
+ running,
712
+ load
713
+ ]);
714
+ async function doRun() {
715
+ setActionErr(null);
716
+ setPending("run");
717
+ try {
718
+ const result = await runLoop({ data: id });
719
+ if (result.error) return setActionErr(`Run failed: ${result.error}`);
720
+ setFlash({
721
+ label: result.coalesced ? "Already queued" : "Queued",
722
+ hold: 4e3
723
+ });
724
+ await load();
725
+ } finally {
726
+ setPending(null);
727
+ }
728
+ }
729
+ async function doLifecycle(action) {
730
+ setActionErr(null);
731
+ setPending("lifecycle");
732
+ try {
733
+ const result = action === "pause" ? await pauseLoop({ data: id }) : action === "start" ? await startLoop({ data: id }) : action === "stop" ? await stopLoop({ data: id }) : await deleteLoop({ data: id });
734
+ if (result.error) return setActionErr(`${action[0].toUpperCase() + action.slice(1)} failed: ${result.error}`);
735
+ setConfirmingDelete(false);
736
+ if (result.deleted) return navigate({ to: "/" });
737
+ if (action === "delete") deletingRef.current = true;
738
+ await load();
739
+ setFlash({ label: action === "start" ? "Started" : action === "pause" ? "Paused" : action === "stop" ? "Stop requested" : "Deleting" });
740
+ } finally {
741
+ setPending(null);
742
+ }
743
+ }
744
+ async function onSave() {
745
+ const patch = formRef.current?.read();
746
+ if (!patch) return;
747
+ setActionErr(null);
748
+ setPending("save");
749
+ try {
750
+ const result = await patchLoop({ data: {
751
+ id,
752
+ patch
753
+ } });
754
+ if (result.error) return setActionErr(`Save failed: ${result.error}`);
755
+ setEditing(false);
756
+ await load();
757
+ setFlash({ label: "Saved" });
758
+ } finally {
759
+ setPending(null);
760
+ }
761
+ }
762
+ const back = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
763
+ to: "/",
764
+ className: "inline-flex items-center gap-1.5 text-meta font-medium text-secondary hover:text-display",
765
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
766
+ "aria-hidden": true,
767
+ children: "←"
768
+ }), " Loops"]
769
+ });
770
+ if (err) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, {
771
+ back,
772
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadErrorCard, {
773
+ title: "Couldn't load this loop.",
774
+ detail: err,
775
+ onRetry: () => void load()
776
+ })
777
+ });
778
+ if (!detail) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, {
779
+ back,
780
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loading, {})
781
+ });
782
+ const { loop, summary, runs } = detail;
783
+ const lifecycle = deriveLoopLifecycle(summary);
784
+ const deleting = lifecycle === "deleting";
785
+ const active = summary.enabled && !deleting;
786
+ const paused = !summary.enabled && !deleting;
787
+ const busy = pending != null;
788
+ const stopSupport = daemonStopSupport(detail.machine.daemonProtocol);
789
+ const canStop = !deleting && !!summary.running && stopSupport.supported;
790
+ const online = detail.machine.online;
791
+ const asleep = detail.machine.presence === "asleep";
792
+ const crossTeam = detail.team && !detail.team.isActive ? detail.team : null;
793
+ if (editing) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, {
794
+ back,
795
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditHead, {
796
+ name: summary.name,
797
+ onBack: () => setEditing(false)
798
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
799
+ className: "mt-5 rounded-card border border-hairline bg-surface px-6 pb-6 pt-2 shadow-card",
800
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopForm, {
801
+ ref: formRef,
802
+ initial: loop
803
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
804
+ className: "mt-7 border-t border-hairline pt-4",
805
+ children: [actionErr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, {
806
+ message: actionErr,
807
+ onDismiss: () => setActionErr(null),
808
+ className: "mb-3"
809
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
810
+ className: "flex gap-2.5",
811
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
812
+ className: btnPrimary,
813
+ disabled: pending === "save",
814
+ onClick: () => void onSave(),
815
+ children: pending === "save" ? "Saving…" : "Save"
816
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
817
+ className: btn,
818
+ disabled: pending === "save",
819
+ onClick: () => setEditing(false),
820
+ children: "Cancel"
821
+ })]
822
+ })]
823
+ })]
824
+ })]
825
+ });
826
+ const scheduleText = loop.schedule.mode === "cron" ? `${loop.schedule.cron} · ${loop.schedule.timezone} · overlap ${loop.schedule.overlap}` : `continuous · ${loop.schedule.delayMinutes}m after terminal`;
827
+ const model = loop.model || "default";
828
+ const effort = loop.reasoningEffort || "default";
829
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, {
830
+ back,
831
+ children: [
832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
833
+ className: "rounded-card border border-hairline bg-surface px-6 pb-5 pt-[22px] shadow-card",
834
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
835
+ className: "flex flex-wrap items-start justify-between gap-x-6 gap-y-4",
836
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
837
+ className: "min-w-0",
838
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
839
+ className: "flex flex-wrap items-center gap-3",
840
+ children: [
841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
842
+ className: "text-[28px] font-semibold leading-tight tracking-[-0.015em] text-display",
843
+ children: summary.name
844
+ }),
845
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
846
+ tone: lifecycle === "stopping" ? "running" : void 0,
847
+ dot: lifecycle === "stopping" ? "pulse" : void 0,
848
+ children: lifecycleDisplay(detail)
849
+ }),
850
+ summary.queued && lifecycle === "active" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
851
+ tone: "outline",
852
+ children: "Queued"
853
+ }),
854
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
855
+ tone: "outline",
856
+ children: AGENT_LABEL[loop.agent]
857
+ }),
858
+ crossTeam && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
859
+ tone: "outline",
860
+ children: crossTeam.name
861
+ })
862
+ ]
863
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
864
+ className: "mt-2.5 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-meta text-secondary",
865
+ children: [
866
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
867
+ className: "font-mono text-primary",
868
+ children: scheduleText
869
+ }),
870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
871
+ className: "text-wire",
872
+ children: "·"
873
+ }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["next ", fmt(summary.nextRun)] }),
875
+ summary.nextRun && summary.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
876
+ className: "text-disabled",
877
+ children: [
878
+ "(",
879
+ until(summary.nextRun),
880
+ ")"
881
+ ]
882
+ }),
883
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
884
+ className: "text-wire",
885
+ children: "·"
886
+ }),
887
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
888
+ className: "inline-flex items-center gap-1.5",
889
+ 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"]
890
+ })
891
+ ]
892
+ })]
893
+ })
894
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
895
+ className: "mt-5 border-t border-hairline pt-4",
896
+ children: [
897
+ crossTeam && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TeamBanner, {
898
+ team: crossTeam,
899
+ onSwitch: () => {
900
+ setActiveTeamCookie(crossTeam.id);
901
+ navigate({
902
+ to: "/t/$teamId",
903
+ params: { teamId: crossTeam.id }
904
+ });
905
+ }
906
+ }),
907
+ detail.machine.needsUpdate && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MachineBanner, {
908
+ tone: "warn",
909
+ text: `Daemon update required · server requires v${detail.machine.requiredDaemonVersion}; machine reports ${detail.machine.daemonVersion ? `v${detail.machine.daemonVersion}` : "an unknown version"}.`,
910
+ action: "Update daemon",
911
+ onAction: () => setMachinesOpen(true)
912
+ }),
913
+ !online && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MachineBanner, {
914
+ text: `Machine ${asleep ? "seems asleep or offline" : "is offline"}; queued work starts when it reconnects.${detail.machine.lastSeen ? ` Last seen ${rel(detail.machine.lastSeen)}.` : ""}`,
915
+ action: "Reconnect",
916
+ onAction: () => setMachinesOpen(true)
917
+ }),
918
+ actionErr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, {
919
+ message: actionErr,
920
+ onDismiss: () => setActionErr(null),
921
+ className: "mb-2.5"
922
+ }),
923
+ flash && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
924
+ className: "mb-2.5",
925
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlashLine, {
926
+ label: flash.label,
927
+ tone: flash.tone,
928
+ onUndo: flash.undo
929
+ })
930
+ }),
931
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
932
+ className: "flex min-w-0 flex-wrap items-center gap-2",
933
+ children: [
934
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
935
+ className: btnPrimary,
936
+ disabled: busy || deleting,
937
+ onClick: () => void doRun(),
938
+ children: pending === "run" ? "Queuing…" : "Run once"
939
+ }),
940
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
941
+ className: btn,
942
+ disabled: busy || deleting,
943
+ onClick: () => setEditing(true),
944
+ children: "Settings"
945
+ }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
947
+ className: btn,
948
+ disabled: busy || !paused,
949
+ onClick: () => void doLifecycle("start"),
950
+ children: "Start"
951
+ }),
952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
953
+ className: btn,
954
+ disabled: busy || !active,
955
+ onClick: () => void doLifecycle("pause"),
956
+ children: "Pause"
957
+ }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
959
+ className: btnDanger,
960
+ disabled: busy || !canStop,
961
+ title: summary.running && !stopSupport.supported ? DAEMON_UPGRADE_REQUIRED : !summary.running ? "No run is currently running" : void 0,
962
+ onClick: () => void doLifecycle("stop"),
963
+ children: "Stop"
964
+ }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
966
+ className: btnDanger,
967
+ disabled: busy || deleting,
968
+ onClick: () => setConfirmingDelete(true),
969
+ children: deleting ? "Deleting…" : "Delete"
970
+ })
971
+ ]
972
+ }),
973
+ confirmingDelete && !deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
974
+ className: "mt-3",
975
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfirmBar, {
976
+ prompt: "Stop this loop and delete its server data?",
977
+ note: "This stops the loop and deletes server history and collected artifacts. Files in the working directory are not deleted. If the machine is unreachable, its local process may continue running.",
978
+ cta: "Delete",
979
+ danger: true,
980
+ busy,
981
+ onConfirm: () => void doLifecycle("delete"),
982
+ onCancel: () => setConfirmingDelete(false)
983
+ })
984
+ })
985
+ ]
986
+ })]
987
+ }),
988
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
989
+ className: "mt-6 min-w-0 rounded-card border border-hairline bg-surface px-6 py-5 shadow-card",
990
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
991
+ className: `mb-4 border-b border-hairline pb-1.5 ${sectionHeadCls}`,
992
+ children: "Configuration"
993
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
994
+ className: "grid min-w-0 gap-x-6 gap-y-4 md:grid-cols-[180px_minmax(0,1fr)]",
995
+ children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
997
+ label: "Schedule",
998
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
999
+ className: "break-all font-mono",
1000
+ children: scheduleText
1001
+ })
1002
+ }),
1003
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1004
+ label: "Working directory",
1005
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
1006
+ className: "break-all font-mono",
1007
+ children: loop.workdir
1008
+ })
1009
+ }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1011
+ label: "Agent",
1012
+ children: AGENT_LABEL[loop.agent]
1013
+ }),
1014
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Config, {
1015
+ label: "Model / effort",
1016
+ children: [
1017
+ model,
1018
+ " / ",
1019
+ effort
1020
+ ]
1021
+ }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1023
+ label: "Prompt",
1024
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
1025
+ className: "whitespace-pre-wrap break-words font-sans",
1026
+ children: loop.prompt
1027
+ })
1028
+ }),
1029
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1030
+ label: "Status definitions",
1031
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", {
1032
+ className: "space-y-1",
1033
+ children: [
1034
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
1035
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "keep:" }),
1036
+ " ",
1037
+ loop.statusDefinitions.keep
1038
+ ] }),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
1040
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "no-change:" }),
1041
+ " ",
1042
+ loop.statusDefinitions.noChange
1043
+ ] }),
1044
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
1045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "block:" }),
1046
+ " ",
1047
+ loop.statusDefinitions.block
1048
+ ] })
1049
+ ]
1050
+ })
1051
+ }),
1052
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Config, {
1053
+ label: "Artifact paths",
1054
+ children: loop.artifacts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
1055
+ className: "space-y-1",
1056
+ children: loop.artifacts.map((path) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
1057
+ className: "break-all font-mono",
1058
+ children: path
1059
+ }) }, path))
1060
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1061
+ className: "text-disabled",
1062
+ children: "None"
1063
+ })
1064
+ })
1065
+ ]
1066
+ })]
1067
+ }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1069
+ className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(300px,360px)]",
1070
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopFilesPanel, {
1071
+ loopId: id,
1072
+ configuredPaths: loop.artifacts,
1073
+ running
1074
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunsSection, {
1075
+ loopId: id,
1076
+ summary,
1077
+ runs,
1078
+ older,
1079
+ onPickRun: (run) => navigate({
1080
+ to: "/loops/$loopId/runs/$runId",
1081
+ params: {
1082
+ loopId: id,
1083
+ runId: run.id
1084
+ }
1085
+ }),
1086
+ onMore: async () => {
1087
+ const oldest = (older.length ? mergeRuns(summary.runs, older) : summary.runs)[0];
1088
+ if (!oldest) return 0;
1089
+ const more = await loadOlderRuns({ data: {
1090
+ loopId: id,
1091
+ beforeTs: oldest.ts,
1092
+ limit: 16
1093
+ } });
1094
+ if (more.length) setOlder((current) => mergeRuns(current, more));
1095
+ return more.length;
1096
+ }
1097
+ })]
1098
+ }),
1099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MachinesModal, {
1100
+ open: machinesOpen,
1101
+ onClose: () => setMachinesOpen(false)
1102
+ })
1103
+ ]
1104
+ });
1105
+ }
1106
+ function Config({ label, children }) {
1107
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
1108
+ className: "text-label font-medium text-secondary",
1109
+ children: label
1110
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1111
+ className: "min-w-0 text-body text-primary",
1112
+ children
1113
+ })] });
1114
+ }
1115
+ function TeamBanner({ team, onSwitch }) {
1116
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1117
+ className: "mb-2.5 flex flex-wrap items-center gap-3 rounded-control border border-hairline bg-raised px-4 py-2.5 text-meta text-secondary",
1118
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1119
+ "Viewing a loop in ",
1120
+ team.name,
1121
+ " - not your active team."
1122
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1123
+ onClick: onSwitch,
1124
+ className: "ml-auto font-medium text-interactive underline",
1125
+ children: "Switch to this team"
1126
+ })]
1127
+ });
1128
+ }
1129
+ function MachineBanner({ text, action, onAction, tone }) {
1130
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1131
+ className: `mb-2.5 flex flex-wrap items-center gap-3 rounded-control border px-4 py-2.5 text-meta ${tone === "warn" ? "border-warn/40 bg-warn/10" : "border-hairline bg-raised"}`,
1132
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1133
+ className: "text-secondary",
1134
+ children: text
1135
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1136
+ onClick: onAction,
1137
+ className: "ml-auto font-medium text-interactive underline",
1138
+ children: action
1139
+ })]
1140
+ });
1141
+ }
1142
+ function EditHead({ name, onBack }) {
1143
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
1145
+ type: "button",
1146
+ onClick: onBack,
1147
+ className: btnQuiet,
1148
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1149
+ "aria-hidden": true,
1150
+ children: "←"
1151
+ }), " Back to loop"]
1152
+ }),
1153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", {
1154
+ className: "mt-2.5 text-[24px] font-semibold text-display",
1155
+ children: ["Settings · ", name]
1156
+ }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1158
+ className: "mt-1.5 text-meta text-secondary",
1159
+ children: "Edit the stored schedule, execution settings, prompt, status definitions, and exact artifact paths."
1160
+ })
1161
+ ] });
1162
+ }
1163
+ function Shell({ back, children }) {
1164
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
1165
+ className: "mx-auto max-w-[1360px] px-8 pb-24 pt-10",
1166
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1167
+ className: "mb-5",
1168
+ children: back
1169
+ }), children]
1170
+ });
1171
+ }
1172
+ function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
1173
+ const stripRuns = (0, import_react.useMemo)(() => older.length ? mergeRuns(summary.runs, older) : summary.runs, [summary.runs, older]);
1174
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1175
+ className: "min-w-0",
1176
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1177
+ className: "mb-2.5 border-b border-hairline pb-1.5",
1178
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
1179
+ className: sectionHeadCls,
1180
+ children: [
1181
+ "Runs (",
1182
+ summary.runCount,
1183
+ ")"
1184
+ ]
1185
+ })
1186
+ }), summary.runCount === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1187
+ className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled",
1188
+ children: "Never run"
1189
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1190
+ className: "rounded-card border border-hairline bg-surface px-5 pb-4 pt-5 shadow-card",
1191
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Timeline, {
1192
+ loop: summary,
1193
+ runs: stripRuns,
1194
+ total: summary.runCount,
1195
+ onLoadMore: onMore,
1196
+ onPickRun
1197
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
1198
+ className: "mt-5 max-h-[clamp(280px,46vh,520px)] divide-y divide-hairline overflow-y-auto border-t border-hairline",
1199
+ children: runs.map((run) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
1200
+ to: "/loops/$loopId/runs/$runId",
1201
+ params: {
1202
+ loopId,
1203
+ runId: run.id
1204
+ },
1205
+ className: "flex items-start gap-2.5 py-2.5 hover:bg-raised",
1206
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1207
+ className: "mt-1 inline-block size-2.5 shrink-0 rounded-[2px]",
1208
+ style: { background: dotColor(run) },
1209
+ title: dotLabel(run)
1210
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1211
+ className: "min-w-0 flex-1",
1212
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1213
+ className: "flex justify-between gap-2",
1214
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1215
+ className: "font-mono text-label text-secondary",
1216
+ children: tsShort(run.ts)
1217
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1218
+ className: "font-mono text-caption text-disabled",
1219
+ children: dur(run.durationMs)
1220
+ })]
1221
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1222
+ className: "mt-0.5 block line-clamp-2 text-meta text-primary",
1223
+ children: run.phase === "pending" ? "Queued" : run.phase === "running" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1224
+ className: "inline-flex items-center gap-2",
1225
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1226
+ className: "size-1.5 rounded-full",
1227
+ style: runPulseStyle
1228
+ }), "Running"]
1229
+ }) : run.error || run.message || dotLabel(run)
1230
+ })]
1231
+ })]
1232
+ }) }, run.id))
1233
+ })]
1234
+ })]
1235
+ });
1236
+ }
1237
+ /**
1238
+ * Loop detail page — `/loops/$loopId`: a loop header and action toolbar, the
1239
+ * exact artifact Files panel, and the runs timeline. The view owns its own
1240
+ * data + self-poll (ssr:false so the session cookie rides along with its fetches,
1241
+ * like the dashboard loader). Run rows link on to `/loops/$loopId/runs/$runId`.
1242
+ *
1243
+ * Auth-gated exactly like the dashboard (`/`): the loader checks getAuthState +
1244
+ * the session so a logged-out/expired DEEP LINK shows the sign-in CTA instead of
1245
+ * a raw `loop not found` error from the ownership-blocked data fetch.
1246
+ */
1247
+ function LoopDetailPage() {
1248
+ const { loopId } = Route.useParams();
1249
+ const { auth } = Route.useLoaderData() ?? { auth: { enabled: false } };
1250
+ const { data: session, isPending } = useSession();
1251
+ if (auth?.enabled && !isPending && !session) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SignIn, { callbackURL: `/loops/${loopId}` });
1252
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopDetailView, { id: loopId });
1253
+ }
1254
+ //#endregion
1255
+ export { LoopDetailPage as component };