@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/MachinesModal-CEddj7la.js +1 -0
- package/.output/public/assets/SignIn-BdkKWOSs.js +1 -0
- package/.output/public/assets/actionUi-BYBeYy7U.js +1 -0
- package/.output/public/assets/app-BAgHQVvr.css +2 -0
- package/.output/public/assets/index-BZLqD3JI.js +12 -0
- package/.output/public/assets/invite._token-BCLgmTO7.js +1 -0
- package/.output/public/assets/invite._token-ZnVQqqq4.js +1 -0
- package/.output/public/assets/lifecycleUi-C7dwHtbw.js +1 -0
- package/.output/public/assets/loops._loopId-X8PSlgdB.js +67 -0
- package/.output/public/assets/loops._loopId_.runs._runId-CA0-q1y5.js +3 -0
- package/.output/public/assets/preload-helper-CUIO87nJ.js +5 -0
- package/.output/public/assets/routes-BjKcDy6D.js +1 -0
- package/.output/public/assets/routes-bfMcArTT.js +1 -0
- package/.output/public/assets/t._teamId-B75J8h0x.js +1 -0
- package/.output/public/assets/t._teamId-D7WVPMDr.js +1 -0
- package/.output/public/assets/ui-DGOT8AoZ.js +1 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +4130 -4130
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_libs/drizzle-orm+postgres.mjs +28 -1
- package/.output/server/_ssr/DashboardView-D_hKsCsv.mjs +1098 -0
- package/.output/server/_ssr/{notifyFns-DXMbgYu2.mjs → MachinesModal-DZ_9ErAU.mjs} +57 -35
- package/.output/server/_ssr/{SignIn-C4fSJl_j.mjs → SignIn-DjSrg0nO.mjs} +10 -24
- package/.output/server/_ssr/SocialLinks-BTjsnVOm.mjs +1051 -0
- package/.output/server/_ssr/{actionUi-BKF6xI3d.mjs → actionUi-D15R8L7R.mjs} +3 -3
- package/.output/server/_ssr/{artifactFiles-C8PQKN6V.mjs → artifactFiles-CV3OXxRD.mjs} +7 -8
- package/.output/server/_ssr/{artifactKind-BW0isV8L.mjs → artifactKind-ByzVa7fg.mjs} +27 -2
- package/.output/server/_ssr/artifacts-BhQvaGjF.mjs +2 -0
- package/.output/server/_ssr/{auth-Sie6sQHF.mjs → auth-CPCDf6Wr.mjs} +16 -19
- package/.output/server/_ssr/{boot-akwC2sQg.mjs → boot-BbWxaF-z.mjs} +1359 -3843
- package/.output/server/_ssr/boot-COHf8Gv3.mjs +2 -0
- package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-CLY8cORt.mjs → bun-sqlite-dialect-BW9W1_Ps-8MIh9jN1.mjs} +2 -2
- package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-CRsXQNRe.mjs → d1-sqlite-dialect-BLC8LXE6-CcMQ01_d.mjs} +2 -2
- package/.output/server/_ssr/db-dw61xIt1.mjs +2 -0
- package/.output/server/_ssr/{dist-Cc_x6bBb.mjs → dist-AxCsRawm.mjs} +2 -2
- package/.output/server/_ssr/{factory-CDwHL_zY.mjs → factory-S7MaMVC2.mjs} +2 -2
- package/.output/server/_ssr/{invite._token-BlbVjz-o.mjs → invite._token-B1UgMbNQ.mjs} +3 -3
- package/.output/server/_ssr/{invite._token-BE_QUAVb.mjs → invite._token-BliJIGk4.mjs} +4 -5
- package/.output/server/_ssr/{invite._token-C7GjrJxr.mjs → invite._token-D296YVGd.mjs} +7 -7
- package/.output/server/_ssr/{kysely-adapter-zflsyYBA.mjs → kysely-adapter-DUdpqcCJ.mjs} +1 -1
- package/.output/server/_ssr/{lifecycleUi-Djb44Aj3.mjs → lifecycleUi-Dad3hvhm.mjs} +12 -11
- package/.output/server/_ssr/logger-DdUWuMdh.mjs +2 -0
- package/.output/server/_ssr/loopApi-Dy8ztANM.mjs +389 -0
- package/.output/server/_ssr/loopProjection-BZ1_IYCr.mjs +110 -0
- package/.output/server/_ssr/loops._loopId-CKsAnxMl.mjs +1255 -0
- package/.output/server/_ssr/{loops._loopId_.runs._runId-rBHW41dx.mjs → loops._loopId_.runs._runId-DG405pQs.mjs} +29 -74
- package/.output/server/_ssr/{loops._loopId_.runs._runId-Dk1QY49p.mjs → loops._loopId_.runs._runId-a_SyY8rX.mjs} +4 -5
- package/.output/server/_ssr/{machineFns-CoqLRLRh.mjs → machineFns-BSaE7K88.mjs} +3 -5
- package/.output/server/_ssr/{node-sqlite-dialect-dmrazfbM.mjs → node-sqlite-dialect-rWwVyt2J.mjs} +2 -2
- package/.output/server/_ssr/router-lMkobh5r.mjs +618 -0
- package/.output/server/_ssr/{routes-DebVUmCS.mjs → routes-CRHc9cLG.mjs} +6 -10
- package/.output/server/_ssr/{routes-Bx0bRxt9.mjs → routes-DNCZrs8z.mjs} +3 -3
- package/.output/server/_ssr/{routes-dQCBLTxY.mjs → routes-ufPbrGHs.mjs} +6 -6
- package/.output/server/_ssr/runDiff-CrZzjokj.mjs +2 -0
- package/.output/server/_ssr/ssr.mjs +65 -97
- package/.output/server/_ssr/store-CirPHuib.mjs +2 -0
- package/.output/server/_ssr/{t._teamId-BqOUF-0J.mjs → t._teamId-BoBoYEHL.mjs} +7 -7
- package/.output/server/_ssr/{t._teamId-D9nCEa2h.mjs → t._teamId-DcWOYjPf.mjs} +4 -4
- package/.output/server/_ssr/{t._teamId-BzWy4Ft2.mjs → t._teamId-QD2nN0wn.mjs} +7 -11
- package/.output/server/_ssr/{teamFns-BcnGMC-g.mjs → teamFns-BsmconJ1.mjs} +4 -4
- package/.output/server/_ssr/{teamFns-bm82twd5.mjs → teamFns-DcoVuWyH.mjs} +2 -2
- package/.output/server/_ssr/tokens-XngoyCU8.mjs +2 -0
- package/.output/server/_ssr/{ui-B6mE5-5v.mjs → ui-DmFKW2H_.mjs} +33 -47
- package/.output/server/{_tanstack-start-manifest_v-COr27bxK.mjs → _tanstack-start-manifest_v-Xd5bMpJe.mjs} +25 -33
- package/.output/server/drizzle/{0000_round_marauders.sql → 0000_baseline.sql} +102 -44
- package/.output/server/drizzle/meta/_journal.json +3 -115
- package/.output/server/index.mjs +82 -131
- package/drizzle/{0000_round_marauders.sql → 0000_baseline.sql} +102 -44
- package/drizzle/meta/_journal.json +3 -115
- package/package.json +2 -10
- package/scripts/pievo-server.mjs +3 -1
- package/scripts/server-cli-lib.mjs +23 -16
- package/.output/public/assets/CodeField-dOzyFQNH.js +0 -34
- package/.output/public/assets/LoopView-BTXoqfzM.js +0 -59
- package/.output/public/assets/Modal-Ba36NAkv.js +0 -1
- package/.output/public/assets/SignIn-Cc1zxGnA.js +0 -1
- package/.output/public/assets/actionUi-DDDfD0WX.js +0 -1
- package/.output/public/assets/app-D1cij_NJ.css +0 -2
- package/.output/public/assets/artifactView-Cd5wWPil.js +0 -66
- package/.output/public/assets/index-BGM1XGY0.js +0 -12
- package/.output/public/assets/invite._token-C3t4JfAY.js +0 -1
- package/.output/public/assets/invite._token-CUCNcN89.js +0 -1
- package/.output/public/assets/jsx-runtime-BseJUIpC.js +0 -1
- package/.output/public/assets/lifecycleUi-CMFw5Nlj.js +0 -1
- package/.output/public/assets/loopApi-CT1mMhlb.js +0 -3
- package/.output/public/assets/loops._loopId-CrSSvSVl.js +0 -4
- package/.output/public/assets/loops._loopId_.runs._runId-B5Qacloe.js +0 -3
- package/.output/public/assets/notifyFns-CYiZT7CK.js +0 -1
- package/.output/public/assets/preload-helper-DFkkpP6W.js +0 -3
- package/.output/public/assets/routes-B6Kg4mFC.js +0 -1
- package/.output/public/assets/routes-DsdcURfk.js +0 -1
- package/.output/public/assets/t._teamId-C9uEyAh_.js +0 -1
- package/.output/public/assets/t._teamId-Cmm4xdKj.js +0 -1
- package/.output/public/assets/ui-Gb1413-F.js +0 -1
- package/.output/public/assets/useRouter-DsjH8G0p.js +0 -1
- package/.output/server/_libs/@codemirror/autocomplete+[...].mjs +0 -19713
- package/.output/server/_libs/@codemirror/lang-css+[...].mjs +0 -2636
- package/.output/server/_libs/@codemirror/lang-html+[...].mjs +0 -2247
- package/.output/server/_libs/@codemirror/lang-json+[...].mjs +0 -79
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1736
- package/.output/server/_libs/@uiw/codemirror-extensions-basic-setup+[...].mjs +0 -76
- package/.output/server/_libs/babel__runtime.mjs +0 -30
- package/.output/server/_libs/clsx.mjs +0 -16
- package/.output/server/_libs/codemirror__commands.mjs +0 -1725
- package/.output/server/_libs/codemirror__lint.mjs +0 -565
- package/.output/server/_libs/codemirror__search.mjs +0 -1138
- package/.output/server/_libs/codemirror__theme-one-dark.mjs +0 -161
- package/.output/server/_libs/d3-array.mjs +0 -231
- package/.output/server/_libs/d3-color.mjs +0 -330
- package/.output/server/_libs/d3-format.mjs +0 -252
- package/.output/server/_libs/d3-interpolate.mjs +0 -163
- package/.output/server/_libs/d3-path.mjs +0 -77
- package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
- package/.output/server/_libs/d3-shape.mjs +0 -818
- package/.output/server/_libs/decimal.js-light.mjs +0 -884
- package/.output/server/_libs/dom-serializer+[...].mjs +0 -708
- package/.output/server/_libs/domhandler.mjs +0 -461
- package/.output/server/_libs/domutils.mjs +0 -868
- package/.output/server/_libs/es-toolkit.mjs +0 -1737
- package/.output/server/_libs/eventemitter3.mjs +0 -236
- package/.output/server/_libs/html-dom-parser+htmlparser2.mjs +0 -1560
- package/.output/server/_libs/html-react-parser+[...].mjs +0 -1360
- package/.output/server/_libs/react-is.mjs +0 -48
- package/.output/server/_libs/react-redux+[...].mjs +0 -205
- package/.output/server/_libs/recharts+victory-vendor.mjs +0 -17303
- package/.output/server/_libs/uiw__codemirror-themes.mjs +0 -32
- package/.output/server/_libs/uiw__react-codemirror.mjs +0 -354
- package/.output/server/_ssr/CodeField-B7oOmmwH.mjs +0 -143
- package/.output/server/_ssr/DashboardView-0yoKh8Em.mjs +0 -2733
- package/.output/server/_ssr/LoopPlaybook-uq_sFfgU.mjs +0 -2105
- package/.output/server/_ssr/LoopView-BlOY-jfJ.mjs +0 -1300
- package/.output/server/_ssr/Modal-Bc_5TYGr.mjs +0 -49
- package/.output/server/_ssr/adapters-RReIIwAx.mjs +0 -134
- package/.output/server/_ssr/artifactView-iuPsp5aa.mjs +0 -304
- package/.output/server/_ssr/artifacts-B_F6vSih.mjs +0 -2
- package/.output/server/_ssr/boot-pVS0wjd0.mjs +0 -2
- package/.output/server/_ssr/db-Bdm9PFZF.mjs +0 -2
- package/.output/server/_ssr/logger-tfiliKTi.mjs +0 -2
- package/.output/server/_ssr/loopApi-8_KgBapr.mjs +0 -109
- package/.output/server/_ssr/loopApi-JzmqWENY.mjs +0 -564
- package/.output/server/_ssr/loops._loopId-D-aCCtrX.mjs +0 -1477
- package/.output/server/_ssr/loops._loopId-DZ-FjscA.mjs +0 -30
- package/.output/server/_ssr/notifyFns-CH7-bcmd.mjs +0 -118
- package/.output/server/_ssr/router-BFPzSTFx.mjs +0 -679
- package/.output/server/_ssr/runDiff-qA1C8R_L.mjs +0 -2
- package/.output/server/_ssr/store-DWUdkIq2.mjs +0 -2
- package/.output/server/_ssr/tokens-DG8z1GIg.mjs +0 -2
- package/.output/server/drizzle/0001_naive_silver_fox.sql +0 -25
- package/.output/server/drizzle/0002_funny_sage.sql +0 -12
- package/.output/server/drizzle/0003_optimal_slapstick.sql +0 -102
- package/.output/server/drizzle/0004_remove_grok_agent.sql +0 -5
- package/.output/server/drizzle/0005_heavy_gauntlet.sql +0 -8
- package/.output/server/drizzle/0006_tranquil_sharon_carter.sql +0 -1
- package/.output/server/drizzle/0007_faulty_starbolt.sql +0 -30
- package/.output/server/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
- package/.output/server/drizzle/0009_ordinary_electro.sql +0 -1
- package/.output/server/drizzle/0010_workable_rage.sql +0 -14
- package/.output/server/drizzle/0011_steep_marvel_zombies.sql +0 -3
- package/.output/server/drizzle/0012_blocked_run_status.sql +0 -12
- package/.output/server/drizzle/0013_drop_run_outcome.sql +0 -2
- package/.output/server/drizzle/0014_luxuriant_scrambler.sql +0 -5
- package/.output/server/drizzle/0015_run_history_foundation.sql +0 -43
- package/.output/server/drizzle/0016_plural_machine_runs.sql +0 -4
- package/.output/server/drizzle/meta/0000_snapshot.json +0 -1333
- package/.output/server/drizzle/meta/0001_snapshot.json +0 -1498
- package/.output/server/drizzle/meta/0002_snapshot.json +0 -1576
- package/.output/server/drizzle/meta/0003_snapshot.json +0 -1666
- package/.output/server/drizzle/meta/0005_snapshot.json +0 -1666
- package/.output/server/drizzle/meta/0006_snapshot.json +0 -1672
- package/.output/server/drizzle/meta/0007_snapshot.json +0 -1787
- package/.output/server/drizzle/meta/0009_snapshot.json +0 -1793
- package/.output/server/drizzle/meta/0010_snapshot.json +0 -1891
- package/.output/server/drizzle/meta/0011_snapshot.json +0 -1872
- package/drizzle/0001_naive_silver_fox.sql +0 -25
- package/drizzle/0002_funny_sage.sql +0 -12
- package/drizzle/0003_optimal_slapstick.sql +0 -102
- package/drizzle/0004_remove_grok_agent.sql +0 -5
- package/drizzle/0005_heavy_gauntlet.sql +0 -8
- package/drizzle/0006_tranquil_sharon_carter.sql +0 -1
- package/drizzle/0007_faulty_starbolt.sql +0 -30
- package/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
- package/drizzle/0009_ordinary_electro.sql +0 -1
- package/drizzle/0010_workable_rage.sql +0 -14
- package/drizzle/0011_steep_marvel_zombies.sql +0 -3
- package/drizzle/0012_blocked_run_status.sql +0 -12
- package/drizzle/0013_drop_run_outcome.sql +0 -2
- package/drizzle/0014_luxuriant_scrambler.sql +0 -5
- package/drizzle/0015_run_history_foundation.sql +0 -43
- package/drizzle/0016_plural_machine_runs.sql +0 -4
- package/drizzle/meta/0000_snapshot.json +0 -1333
- package/drizzle/meta/0001_snapshot.json +0 -1498
- package/drizzle/meta/0002_snapshot.json +0 -1576
- package/drizzle/meta/0003_snapshot.json +0 -1666
- package/drizzle/meta/0005_snapshot.json +0 -1666
- package/drizzle/meta/0006_snapshot.json +0 -1672
- package/drizzle/meta/0007_snapshot.json +0 -1787
- package/drizzle/meta/0009_snapshot.json +0 -1793
- package/drizzle/meta/0010_snapshot.json +0 -1891
- 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 };
|