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