@kky42/pievo-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +17 -0
- package/.output/public/assets/CodeField-dOzyFQNH.js +34 -0
- package/.output/public/assets/LoopView-B4H9QibC.js +55 -0
- package/.output/public/assets/Modal-D33gNSgx.js +1 -0
- package/.output/public/assets/SignIn-SHuRRyOv.js +1 -0
- package/.output/public/assets/actionUi-ChGGALzg.js +1 -0
- package/.output/public/assets/app-DElSo_jy.css +2 -0
- package/.output/public/assets/artifactView-DFuubXLJ.js +66 -0
- package/.output/public/assets/index-DfpTWRSz.js +12 -0
- package/.output/public/assets/invite._token-DJtd-XE2.js +1 -0
- package/.output/public/assets/invite._token-T8oo4Qtw.js +1 -0
- package/.output/public/assets/jsx-runtime-BseJUIpC.js +1 -0
- package/.output/public/assets/lifecycleUi-DKkChdnU.js +1 -0
- package/.output/public/assets/loopApi-BM2nj71x.js +3 -0
- package/.output/public/assets/loops._loopId-B19Jw7jC.js +5 -0
- package/.output/public/assets/loops._loopId_.runs._runId-_CflHV38.js +3 -0
- package/.output/public/assets/notifyFns-DCubjpRy.js +1 -0
- package/.output/public/assets/preload-helper-CKUiEq3b.js +3 -0
- package/.output/public/assets/routes-B9tgli5f.js +1 -0
- package/.output/public/assets/routes-CTtlM_qt.js +1 -0
- package/.output/public/assets/t._teamId-Bm21mngW.js +1 -0
- package/.output/public/assets/t._teamId-gR7BVKkR.js +1 -0
- package/.output/public/assets/ui-Dp88qUWh.js +1 -0
- package/.output/public/assets/useRouter-DsjH8G0p.js +1 -0
- package/.output/public/favicon.svg +12 -0
- package/.output/public/fonts/GeistPixel-Square.woff2 +0 -0
- package/.output/server/_chunks/ssr-renderer.mjs +26 -0
- package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +419 -0
- package/.output/server/_libs/@aws-sdk/checksums+[...].mjs +10732 -0
- package/.output/server/_libs/@aws-sdk/client-s3+[...].mjs +9475 -0
- package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +137 -0
- package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +440 -0
- package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +62 -0
- package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +818 -0
- package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +59 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +11125 -0
- package/.output/server/_libs/@codemirror/autocomplete+[...].mjs +19713 -0
- package/.output/server/_libs/@codemirror/lang-css+[...].mjs +2636 -0
- package/.output/server/_libs/@codemirror/lang-html+[...].mjs +2247 -0
- package/.output/server/_libs/@codemirror/lang-json+[...].mjs +79 -0
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1736 -0
- package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +342 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +14300 -0
- package/.output/server/_libs/@uiw/codemirror-extensions-basic-setup+[...].mjs +76 -0
- package/.output/server/_libs/atomic-sleep.mjs +27 -0
- package/.output/server/_libs/aws-crypto__crc32c.mjs +314 -0
- package/.output/server/_libs/aws-sdk__nested-clients.mjs +2671 -0
- package/.output/server/_libs/babel__runtime.mjs +30 -0
- package/.output/server/_libs/clsx.mjs +16 -0
- package/.output/server/_libs/codemirror__commands.mjs +1725 -0
- package/.output/server/_libs/codemirror__lint.mjs +565 -0
- package/.output/server/_libs/codemirror__search.mjs +1138 -0
- package/.output/server/_libs/codemirror__theme-one-dark.mjs +161 -0
- package/.output/server/_libs/colorette.mjs +129 -0
- package/.output/server/_libs/croner.mjs +735 -0
- package/.output/server/_libs/d3-array.mjs +231 -0
- package/.output/server/_libs/d3-color.mjs +330 -0
- package/.output/server/_libs/d3-format.mjs +252 -0
- package/.output/server/_libs/d3-interpolate.mjs +163 -0
- package/.output/server/_libs/d3-path.mjs +77 -0
- package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
- package/.output/server/_libs/d3-shape.mjs +689 -0
- package/.output/server/_libs/dateformat.mjs +341 -0
- package/.output/server/_libs/decimal.js-light.mjs +884 -0
- package/.output/server/_libs/diff.mjs +732 -0
- package/.output/server/_libs/dom-serializer+[...].mjs +708 -0
- package/.output/server/_libs/domhandler.mjs +461 -0
- package/.output/server/_libs/dompurify.mjs +2111 -0
- package/.output/server/_libs/domutils.mjs +868 -0
- package/.output/server/_libs/drizzle-orm+postgres.mjs +7689 -0
- package/.output/server/_libs/electric-sql__pglite.mjs +14674 -0
- package/.output/server/_libs/end-of-stream+once+wrappy.mjs +145 -0
- package/.output/server/_libs/es-toolkit.mjs +1737 -0
- package/.output/server/_libs/eventemitter3.mjs +236 -0
- package/.output/server/_libs/fast-copy.mjs +285 -0
- package/.output/server/_libs/fast-safe-stringify.mjs +172 -0
- package/.output/server/_libs/h3+rou3+srvx.mjs +1201 -0
- package/.output/server/_libs/html-dom-parser+htmlparser2.mjs +1560 -0
- package/.output/server/_libs/html-react-parser+[...].mjs +1360 -0
- package/.output/server/_libs/initdb.wasm +0 -0
- package/.output/server/_libs/marked.mjs +1548 -0
- package/.output/server/_libs/on-exit-leak-free.mjs +80 -0
- package/.output/server/_libs/pglite.data +37667 -8
- package/.output/server/_libs/pglite.wasm +0 -0
- package/.output/server/_libs/pino+[...].mjs +3196 -0
- package/.output/server/_libs/pino-abstract-transport+split2.mjs +194 -0
- package/.output/server/_libs/pino-pretty+[...].mjs +1623 -0
- package/.output/server/_libs/pinojs__redact.mjs +280 -0
- package/.output/server/_libs/react-redux+[...].mjs +205 -0
- package/.output/server/_libs/recharts+victory-vendor.mjs +15875 -0
- package/.output/server/_libs/tanstack__history.mjs +342 -0
- package/.output/server/_libs/ufo.mjs +64 -0
- package/.output/server/_libs/uiw__codemirror-themes.mjs +32 -0
- package/.output/server/_libs/uiw__react-codemirror.mjs +354 -0
- package/.output/server/_runtime.mjs +37 -0
- package/.output/server/_ssr/CodeField-tm4rr6G3.mjs +143 -0
- package/.output/server/_ssr/DashboardView-CC7htv28.mjs +2795 -0
- package/.output/server/_ssr/LoopPlaybook-BXH8mhiX.mjs +2105 -0
- package/.output/server/_ssr/LoopView-DCugEbHx.mjs +1106 -0
- package/.output/server/_ssr/Modal-DpKZHLZK.mjs +49 -0
- package/.output/server/_ssr/SignIn-dTndU6Hw.mjs +67 -0
- package/.output/server/_ssr/actionUi-CZbEixbf.mjs +119 -0
- package/.output/server/_ssr/adapters-BS8XosjF.mjs +130 -0
- package/.output/server/_ssr/artifactFiles-Cagtj7J0.mjs +61 -0
- package/.output/server/_ssr/artifactKind-BW0isV8L.mjs +37 -0
- package/.output/server/_ssr/artifactView-DS4O22wl.mjs +304 -0
- package/.output/server/_ssr/artifacts-DngKyW2u.mjs +2 -0
- package/.output/server/_ssr/auth-DY1DuYZd.mjs +38429 -0
- package/.output/server/_ssr/boot-BbtG0ImW.mjs +9010 -0
- package/.output/server/_ssr/boot-CMHTxQpJ.mjs +2 -0
- package/.output/server/_ssr/bun-sqlite-dialect-BW9W1_Ps-CU1Spd6z.mjs +162 -0
- package/.output/server/_ssr/core-RrmWoKne.mjs +5 -0
- package/.output/server/_ssr/createServerRpc-BD8Xn_0_.mjs +12 -0
- package/.output/server/_ssr/d1-sqlite-dialect-BLC8LXE6-DfG7tB8s.mjs +112 -0
- package/.output/server/_ssr/db-DWAuZq1o.mjs +2 -0
- package/.output/server/_ssr/dist-BIxFFVZg.mjs +342 -0
- package/.output/server/_ssr/empty-plugin-adapters-DKUlnwwl.mjs +5 -0
- package/.output/server/_ssr/factory-Blq0DYfT.mjs +6903 -0
- package/.output/server/_ssr/invite._token-BRCo9LGB.mjs +36 -0
- package/.output/server/_ssr/invite._token-DJxGLILJ.mjs +28 -0
- package/.output/server/_ssr/invite._token-NewFt27g.mjs +92 -0
- package/.output/server/_ssr/kysely-adapter-D3XTe64J.mjs +2 -0
- package/.output/server/_ssr/kysely-migration-tables-JkVUjPF_-bPAX9LR_.mjs +19 -0
- package/.output/server/_ssr/lifecycleUi-C9lg1r3r.mjs +38 -0
- package/.output/server/_ssr/logger-Dw82jNjH.mjs +2 -0
- package/.output/server/_ssr/loopApi-BtfkZQYK.mjs +532 -0
- package/.output/server/_ssr/loopApi-Cxe8WyOB.mjs +105 -0
- package/.output/server/_ssr/loops._loopId-7NQJPB9W.mjs +30 -0
- package/.output/server/_ssr/loops._loopId-DhnArGdL.mjs +1635 -0
- package/.output/server/_ssr/loops._loopId_.runs._runId-Bvuo2nki.mjs +29 -0
- package/.output/server/_ssr/loops._loopId_.runs._runId-Dip5Xk-q.mjs +674 -0
- package/.output/server/_ssr/machineFns-Dvr5ICUy.mjs +266 -0
- package/.output/server/_ssr/node-sqlite-dialect-DhyOpJTX.mjs +162 -0
- package/.output/server/_ssr/notifyFns-BcMRY5kP.mjs +118 -0
- package/.output/server/_ssr/notifyFns-Q458pZmE.mjs +580 -0
- package/.output/server/_ssr/router-J8KZ30xw.mjs +678 -0
- package/.output/server/_ssr/routes-Bcs4T1ox.mjs +28 -0
- package/.output/server/_ssr/routes-BoGrcf-b.mjs +43 -0
- package/.output/server/_ssr/routes-D4am3Zff.mjs +28 -0
- package/.output/server/_ssr/runDiff-CpEFqo7G.mjs +113 -0
- package/.output/server/_ssr/ssr.mjs +5837 -0
- package/.output/server/_ssr/start-4ifArtKx.mjs +4 -0
- package/.output/server/_ssr/store-b3JcQ08F.mjs +2 -0
- package/.output/server/_ssr/t._teamId-CeUhzgzT.mjs +32 -0
- package/.output/server/_ssr/t._teamId-DR-hQK7o.mjs +47 -0
- package/.output/server/_ssr/t._teamId-Dy_McbRt.mjs +31 -0
- package/.output/server/_ssr/teamCookie-B7hLOIie.mjs +10 -0
- package/.output/server/_ssr/teamFns-BIDnsg2A.mjs +473 -0
- package/.output/server/_ssr/teamFns-DA4ovI5g.mjs +33 -0
- package/.output/server/_ssr/tokens-ytd60FUt.mjs +2 -0
- package/.output/server/_ssr/ui-Dej2qjuS.mjs +267 -0
- package/.output/server/_tanstack-start-manifest_v-CVS6c7re.mjs +95 -0
- package/.output/server/drizzle/0000_round_marauders.sql +193 -0
- package/.output/server/drizzle/0001_naive_silver_fox.sql +25 -0
- package/.output/server/drizzle/0002_funny_sage.sql +12 -0
- package/.output/server/drizzle/0003_optimal_slapstick.sql +102 -0
- package/.output/server/drizzle/0004_remove_grok_agent.sql +5 -0
- package/.output/server/drizzle/0005_heavy_gauntlet.sql +8 -0
- package/.output/server/drizzle/0006_tranquil_sharon_carter.sql +1 -0
- package/.output/server/drizzle/0007_faulty_starbolt.sql +30 -0
- package/.output/server/drizzle/0008_durable_lifecycle_evidence.sql +15 -0
- package/.output/server/drizzle/meta/0000_snapshot.json +1333 -0
- package/.output/server/drizzle/meta/0001_snapshot.json +1498 -0
- package/.output/server/drizzle/meta/0002_snapshot.json +1576 -0
- package/.output/server/drizzle/meta/0003_snapshot.json +1666 -0
- package/.output/server/drizzle/meta/0005_snapshot.json +1666 -0
- package/.output/server/drizzle/meta/0006_snapshot.json +1672 -0
- package/.output/server/drizzle/meta/0007_snapshot.json +1787 -0
- package/.output/server/drizzle/meta/_journal.json +69 -0
- package/.output/server/index.mjs +464 -0
- package/.output/server/node_modules/tslib/modules/index.js +70 -0
- package/.output/server/node_modules/tslib/modules/package.json +3 -0
- package/.output/server/node_modules/tslib/package.json +47 -0
- package/.output/server/node_modules/tslib/tslib.js +484 -0
- package/.output/server/package.json +9 -0
- package/LICENSE +21 -0
- package/drizzle/0000_round_marauders.sql +193 -0
- package/drizzle/0001_naive_silver_fox.sql +25 -0
- package/drizzle/0002_funny_sage.sql +12 -0
- package/drizzle/0003_optimal_slapstick.sql +102 -0
- package/drizzle/0004_remove_grok_agent.sql +5 -0
- package/drizzle/0005_heavy_gauntlet.sql +8 -0
- package/drizzle/0006_tranquil_sharon_carter.sql +1 -0
- package/drizzle/0007_faulty_starbolt.sql +30 -0
- package/drizzle/0008_durable_lifecycle_evidence.sql +15 -0
- package/drizzle/meta/0000_snapshot.json +1333 -0
- package/drizzle/meta/0001_snapshot.json +1498 -0
- package/drizzle/meta/0002_snapshot.json +1576 -0
- package/drizzle/meta/0003_snapshot.json +1666 -0
- package/drizzle/meta/0005_snapshot.json +1666 -0
- package/drizzle/meta/0006_snapshot.json +1672 -0
- package/drizzle/meta/0007_snapshot.json +1787 -0
- package/drizzle/meta/_journal.json +69 -0
- package/package.json +84 -0
- package/scripts/pievo-server.mjs +390 -0
- package/scripts/prestart.mjs +66 -0
- package/scripts/server-cli-lib.mjs +451 -0
|
@@ -0,0 +1,1635 @@
|
|
|
1
|
+
import { o as __toESM } from "../_runtime.mjs";
|
|
2
|
+
import { C as require_jsx_runtime, S as require_react, a as MenuPortal, i as MenuPositioner, n as MenuTrigger, o as MenuPopup, r as MenuRoot, s as MenuItem, t as Separator } from "../_libs/@base-ui/react+[...].mjs";
|
|
3
|
+
import { c as CODING_AGENTS, ft as isTaskEntry, h as buildFileEntries } from "./boot-BbtG0ImW.mjs";
|
|
4
|
+
import { A as selectCls, C as labelCls, E as rel, M as until, O as runPulseStyle, S as isCompleted, _ as fmt, b as inputCls, c as btnDanger, d as btnQuiet, g as dur, h as dotLabel, i as Pre, j as tsShort, k as sectionHeadCls, l as btnPrimary, m as dotColor, n as Loading, o as btn, p as cronText, r as Pill, s as btnCost, t as ErrorBanner, x as isClosed, y as humanBytes } from "./ui-Dej2qjuS.mjs";
|
|
5
|
+
import { d as useNavigate, u as Link } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
6
|
+
import { C as startJob, S as runJob, _ as loadOlderRuns, a as evolveJob, b as pauseJob, c as getArtifacts, f as getJobDetail, i as deleteJob, o as forceDeleteJob, w as stopJob, x as requestEdit, y as patchJob } from "./loopApi-Cxe8WyOB.mjs";
|
|
7
|
+
import { n as TaskFileView, r as ViewerHead, t as ArtifactBody } from "./artifactView-DS4O22wl.mjs";
|
|
8
|
+
import { i as useFlash, n as FlashLine, r as LoadErrorCard, t as ConfirmBar } from "./actionUi-CZbEixbf.mjs";
|
|
9
|
+
import { s as useSession } from "./LoopPlaybook-BXH8mhiX.mjs";
|
|
10
|
+
import { t as setActiveTeamCookie } from "./teamCookie-B7hLOIie.mjs";
|
|
11
|
+
import { t as SignIn } from "./SignIn-dTndU6Hw.mjs";
|
|
12
|
+
import { i as lifecycleDisplay, n as daemonStopSupport, r as deriveLoopLifecycle, t as DAEMON_UPGRADE_REQUIRED } from "./lifecycleUi-C9lg1r3r.mjs";
|
|
13
|
+
import { a as listChannels, n as Timeline, s as mergeRuns, t as MachinesModal } from "./notifyFns-Q458pZmE.mjs";
|
|
14
|
+
import { t as Route } from "./loops._loopId-7NQJPB9W.mjs";
|
|
15
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/loops._loopId-DhnArGdL.js
|
|
16
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
17
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
18
|
+
/**
|
|
19
|
+
* Copy-prompt path for the loop-detail Edit composer.
|
|
20
|
+
*
|
|
21
|
+
* The dispatch path (`requestEdit`) runs ONE agent pass on the owner's machine -
|
|
22
|
+
* spends credits, no conversation. This is the non-dispatch alternative: a
|
|
23
|
+
* self-contained prompt the owner pastes into their OWN local coding-agent
|
|
24
|
+
* session (in the loop's directory) so they can iterate/converse and adjust the
|
|
25
|
+
* loop themselves. Pure + agent-neutral so it's unit-testable and never inlines
|
|
26
|
+
* user text unsafely into JSX.
|
|
27
|
+
*/
|
|
28
|
+
/** The loop's on-disk folder — the containing directory of its task file
|
|
29
|
+
* (e.g. `.../pievo/<loop>/`). Returns null when the task file path is absent
|
|
30
|
+
* or has no parent, so callers degrade to a generic instruction rather than
|
|
31
|
+
* fabricating a path. Handles both POSIX and Windows separators. */
|
|
32
|
+
function loopDir(taskFile) {
|
|
33
|
+
if (!taskFile) return null;
|
|
34
|
+
const norm = taskFile.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
35
|
+
const i = norm.lastIndexOf("/");
|
|
36
|
+
return i > 0 ? norm.slice(0, i) : null;
|
|
37
|
+
}
|
|
38
|
+
/** Build the ready-to-paste prompt for a fresh coding-agent session: it states
|
|
39
|
+
* that they're adjusting a named Pievo loop, what the owner wants (their typed
|
|
40
|
+
* instruction, or a clear placeholder asking them to describe it), and that the
|
|
41
|
+
* agent should drive the installed pievo CLI/skill (`pievo loops`,
|
|
42
|
+
* `pievo edit <id> ...`) to apply and confirm the change. Concise, agent-neutral. */
|
|
43
|
+
function buildEditPrompt({ loopId, loopName, instruction }) {
|
|
44
|
+
const want = instruction?.trim() || "Describe the change you want to make to this loop.";
|
|
45
|
+
return [
|
|
46
|
+
`I want to adjust my Pievo loop "${loopName}" (loop id: ${loopId}).`,
|
|
47
|
+
"",
|
|
48
|
+
"The change I want:",
|
|
49
|
+
want,
|
|
50
|
+
"",
|
|
51
|
+
`Use the installed pievo CLI/skill to apply it: run \`pievo loops\` to find the loop, then \`pievo edit ${loopId} ...\` to make the change, and confirm the loop now reflects it. Ask me first if anything is unclear.`
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The unified "Files" surface of the loop detail page — the loop's spec (its task
|
|
56
|
+
* file) shown ALONGSIDE every live-synced artifact in ONE master-detail panel,
|
|
57
|
+
* not two separate boxes. A file list (task file pinned first, then artifacts
|
|
58
|
+
* path-sorted) drives a content viewer on the right; the task file is selected by
|
|
59
|
+
* default. Text files render inline (markdown → formatted, else mono); binary /
|
|
60
|
+
* oversize files offer the download route. The artifact list is fetched lazily by
|
|
61
|
+
* loopId and self-polls so files appear as the loop writes them (Phase 1-2 reuse).
|
|
62
|
+
*/
|
|
63
|
+
var basename = (path) => path.split("/").pop() || path;
|
|
64
|
+
/** Faint type tag shown after a file's size. */
|
|
65
|
+
function typeTag(path) {
|
|
66
|
+
const m = path.match(/\.([a-z0-9]+)$/i);
|
|
67
|
+
return m ? m[1].toLowerCase() : "file";
|
|
68
|
+
}
|
|
69
|
+
function LoopFilesPanel({ loopId, taskFile, taskFileContent, taskFileSyncedAt, running }) {
|
|
70
|
+
const [artifacts, setArtifacts] = (0, import_react.useState)(null);
|
|
71
|
+
const [selected, setSelected] = (0, import_react.useState)(null);
|
|
72
|
+
const seq = (0, import_react.useRef)(0);
|
|
73
|
+
const refresh = (0, import_react.useCallback)(async () => {
|
|
74
|
+
const mine = ++seq.current;
|
|
75
|
+
try {
|
|
76
|
+
const list = await getArtifacts({ data: { loopId } });
|
|
77
|
+
if (mine === seq.current) setArtifacts(list);
|
|
78
|
+
} catch {
|
|
79
|
+
if (mine === seq.current) setArtifacts((prev) => prev ?? []);
|
|
80
|
+
}
|
|
81
|
+
}, [loopId]);
|
|
82
|
+
(0, import_react.useEffect)(() => {
|
|
83
|
+
setArtifacts(null);
|
|
84
|
+
setSelected(null);
|
|
85
|
+
refresh();
|
|
86
|
+
}, [loopId, refresh]);
|
|
87
|
+
(0, import_react.useEffect)(() => {
|
|
88
|
+
const t = setInterval(() => void refresh(), running ? 4e3 : 12e3);
|
|
89
|
+
return () => clearInterval(t);
|
|
90
|
+
}, [running, refresh]);
|
|
91
|
+
const entries = (0, import_react.useMemo)(() => buildFileEntries(taskFile, artifacts ?? []), [taskFile, artifacts]);
|
|
92
|
+
const activePath = selected && entries.some((e) => e.path === selected) ? selected : entries[0]?.path ?? null;
|
|
93
|
+
const active = entries.find((e) => e.path === activePath) ?? null;
|
|
94
|
+
const activeIsTask = isTaskEntry(active);
|
|
95
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
96
|
+
id: "files",
|
|
97
|
+
className: "min-w-0",
|
|
98
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
99
|
+
className: "mb-2.5 flex items-end justify-between gap-3 border-b border-hairline pb-1.5",
|
|
100
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
|
|
101
|
+
className: "text-label font-semibold text-secondary",
|
|
102
|
+
children: ["Files", artifacts ? ` (${entries.length})` : ""]
|
|
103
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
104
|
+
className: "text-caption font-medium text-disabled",
|
|
105
|
+
children: "Spec + synced artifacts"
|
|
106
|
+
})]
|
|
107
|
+
}), entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
108
|
+
className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled shadow-card",
|
|
109
|
+
children: artifacts == null ? "Loading…" : "No files yet - the task file and synced artifacts appear here."
|
|
110
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
111
|
+
className: "grid h-[min(600px,68vh)] grid-cols-1 overflow-hidden rounded-card border border-hairline bg-surface shadow-card sm:grid-cols-[210px_1fr]",
|
|
112
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
|
|
113
|
+
className: "max-h-44 overflow-y-auto border-b border-hairline sm:max-h-none sm:border-b-0 sm:border-r",
|
|
114
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
|
|
115
|
+
className: "py-1.5",
|
|
116
|
+
children: entries.map((e) => {
|
|
117
|
+
const on = e.path === activePath;
|
|
118
|
+
const isTask = isTaskEntry(e);
|
|
119
|
+
const meta = !isTask && e.kind === "artifact" ? e.file.meta : null;
|
|
120
|
+
const title = meta?.title?.trim() || "";
|
|
121
|
+
const display = title && title !== basename(e.path) ? title : basename(e.path);
|
|
122
|
+
const showPath = e.path.includes("/") || display !== basename(e.path);
|
|
123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
124
|
+
type: "button",
|
|
125
|
+
onClick: () => setSelected(e.path),
|
|
126
|
+
className: `flex w-full items-center gap-2 border-l-2 px-3 py-2 text-left transition-colors ${on ? "border-display bg-raised" : "border-transparent hover:bg-raised/60"}`,
|
|
127
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
128
|
+
className: "min-w-0 flex-1",
|
|
129
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
130
|
+
className: `block truncate font-mono text-label ${on ? "text-display" : "text-primary"}`,
|
|
131
|
+
title: e.path,
|
|
132
|
+
children: display
|
|
133
|
+
}), showPath && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
134
|
+
className: "block truncate font-mono text-micro text-disabled",
|
|
135
|
+
title: e.path,
|
|
136
|
+
children: e.path
|
|
137
|
+
})]
|
|
138
|
+
}), isTask ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
139
|
+
className: "shrink-0 rounded-full bg-interactive-soft px-2 text-caption font-medium text-interactive",
|
|
140
|
+
children: "Task"
|
|
141
|
+
}) : meta?.type ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
142
|
+
className: "shrink-0 truncate rounded-full bg-raised px-2 text-caption font-medium text-secondary",
|
|
143
|
+
title: `type: ${meta.type}`,
|
|
144
|
+
children: meta.type
|
|
145
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
146
|
+
className: "shrink-0 font-mono text-micro text-disabled",
|
|
147
|
+
children: typeTag(e.path)
|
|
148
|
+
})]
|
|
149
|
+
}) }, e.path);
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153
|
+
className: "min-w-0 overflow-y-auto",
|
|
154
|
+
children: activeIsTask && active ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskEntryView, {
|
|
155
|
+
path: active.path,
|
|
156
|
+
content: taskFileContent,
|
|
157
|
+
syncedAt: taskFileSyncedAt
|
|
158
|
+
}) : active?.kind === "artifact" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactEntryView, {
|
|
159
|
+
loopId,
|
|
160
|
+
file: active.file
|
|
161
|
+
}) : null
|
|
162
|
+
})]
|
|
163
|
+
})]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** The task file pane — the loop's spec, rendered as formatted markdown. */
|
|
167
|
+
function TaskEntryView({ path, content, syncedAt }) {
|
|
168
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, {
|
|
169
|
+
path,
|
|
170
|
+
meta: syncedAt ? `Task file · synced ${fmt(syncedAt)}` : "Task file"
|
|
171
|
+
}), content == null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
172
|
+
className: "px-5 py-6 text-body text-disabled",
|
|
173
|
+
children: "(syncs from the machine on the next run)"
|
|
174
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskFileView, { content })] });
|
|
175
|
+
}
|
|
176
|
+
/** The artifact pane — the shared caption strip over the shared content body. */
|
|
177
|
+
function ArtifactEntryView({ loopId, file }) {
|
|
178
|
+
const meta = [file.size != null ? humanBytes(file.size) : "", `synced ${fmt(file.updatedAt)}`].filter(Boolean).join(" · ");
|
|
179
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, {
|
|
180
|
+
path: file.path,
|
|
181
|
+
meta
|
|
182
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactBody, {
|
|
183
|
+
loopId,
|
|
184
|
+
file
|
|
185
|
+
})] });
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Build the exec slice of a manual form save.
|
|
189
|
+
*
|
|
190
|
+
* Always emits so model / allowControl ride with Save even when workdir is
|
|
191
|
+
* empty (common for taskFile/workflow-only loops). Empty strings stay defined
|
|
192
|
+
* so patchJob can clear stored model/workdir via `trim() || null` - do not
|
|
193
|
+
* coerce cleared fields to `undefined` or gate the whole object on workdir.
|
|
194
|
+
*/
|
|
195
|
+
function buildFormExec(f) {
|
|
196
|
+
return {
|
|
197
|
+
executor: "claude",
|
|
198
|
+
workdir: f.workdir.trim(),
|
|
199
|
+
model: f.model.trim(),
|
|
200
|
+
allowControl: f.allowControl
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
var CodeField = (0, import_react.lazy)(() => import("./CodeField-tm4rr6G3.mjs"));
|
|
204
|
+
function initState(initial) {
|
|
205
|
+
const e = initial?.exec;
|
|
206
|
+
return {
|
|
207
|
+
name: initial?.name ?? "",
|
|
208
|
+
cron: initial?.cron ?? "0 */3 * * *",
|
|
209
|
+
scheduleMode: initial?.scheduleMode ?? "cron",
|
|
210
|
+
continuousDelayMinutes: String(initial?.continuousDelayMinutes ?? 1),
|
|
211
|
+
taskFile: initial?.taskFile ?? "",
|
|
212
|
+
notify: initial?.notify ?? "auto",
|
|
213
|
+
channelId: initial?.channelId ?? "",
|
|
214
|
+
workflow: initial?.workflow ?? "",
|
|
215
|
+
stateSchema: initial?.stateSchema ? JSON.stringify(initial.stateSchema) : "",
|
|
216
|
+
ui: initial?.ui ?? "",
|
|
217
|
+
agent: initial?.agent ?? "claude-code",
|
|
218
|
+
workdir: e?.workdir ?? "",
|
|
219
|
+
model: e?.model ?? "",
|
|
220
|
+
allowControl: !!e?.allowControl
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/** Quiet helper line under a field - guidance, not chrome. */
|
|
224
|
+
var hintCls = "mt-1 text-caption leading-snug text-disabled";
|
|
225
|
+
/** Display labels for the known agents; an unknown/widened value falls back to its
|
|
226
|
+
* raw enum token so a newly-added agent still renders. */
|
|
227
|
+
var AGENT_LABEL$1 = {
|
|
228
|
+
"claude-code": "Claude Code",
|
|
229
|
+
codex: "Codex"
|
|
230
|
+
};
|
|
231
|
+
function TextField({ label, value, onChange, mono, ph, hint }) {
|
|
232
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
233
|
+
className: "min-w-0",
|
|
234
|
+
children: [
|
|
235
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
236
|
+
className: labelCls,
|
|
237
|
+
children: label
|
|
238
|
+
}),
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
240
|
+
type: "text",
|
|
241
|
+
className: mono ? `${inputCls} font-mono` : inputCls,
|
|
242
|
+
value,
|
|
243
|
+
placeholder: ph,
|
|
244
|
+
onChange: (e) => onChange(e.target.value)
|
|
245
|
+
}),
|
|
246
|
+
hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
247
|
+
className: hintCls,
|
|
248
|
+
children: hint
|
|
249
|
+
})
|
|
250
|
+
]
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/** Section divider - same recipe as ModalSection, local so the form owns its rhythm. */
|
|
254
|
+
function Section({ title, hint }) {
|
|
255
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
256
|
+
className: "mb-1 mt-7 border-b border-hairline pb-1.5",
|
|
257
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
|
|
258
|
+
className: sectionHeadCls,
|
|
259
|
+
children: title
|
|
260
|
+
}), hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
261
|
+
className: "mt-0.5 text-caption text-disabled",
|
|
262
|
+
children: hint
|
|
263
|
+
})]
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/** Chunk-load placeholder that holds the editor's footprint (no layout jump). */
|
|
267
|
+
function EditorFallback({ minHeight }) {
|
|
268
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
269
|
+
className: "rounded-control border border-wire bg-raised",
|
|
270
|
+
style: { minHeight }
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
var LoopForm = (0, import_react.forwardRef)(function LoopForm({ initial, channels: channelsProp }, ref) {
|
|
274
|
+
const [f, setF] = (0, import_react.useState)(() => initState(initial));
|
|
275
|
+
const set = (k, v) => setF((s) => ({
|
|
276
|
+
...s,
|
|
277
|
+
[k]: v
|
|
278
|
+
}));
|
|
279
|
+
const [schemaErr, setSchemaErr] = (0, import_react.useState)(null);
|
|
280
|
+
const [fetched, setFetched] = (0, import_react.useState)([]);
|
|
281
|
+
const channels = channelsProp ?? fetched;
|
|
282
|
+
(0, import_react.useEffect)(() => {
|
|
283
|
+
if (channelsProp) return;
|
|
284
|
+
listChannels().then(setFetched).catch(() => {});
|
|
285
|
+
}, [channelsProp]);
|
|
286
|
+
(0, import_react.useImperativeHandle)(ref, () => ({ read() {
|
|
287
|
+
let stateSchema;
|
|
288
|
+
const ss = f.stateSchema.trim();
|
|
289
|
+
if (ss) try {
|
|
290
|
+
stateSchema = JSON.parse(ss);
|
|
291
|
+
} catch {
|
|
292
|
+
setSchemaErr("Not valid JSON - expected an array like [{\"key\":\"mrr\",\"label\":\"MRR\",\"unit\":\"$\"}]");
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
name: f.name.trim(),
|
|
297
|
+
cron: f.cron.trim(),
|
|
298
|
+
scheduleMode: f.scheduleMode,
|
|
299
|
+
continuousDelayMinutes: Math.max(1, Number.parseInt(f.continuousDelayMinutes, 10) || 1),
|
|
300
|
+
taskFile: f.taskFile.trim(),
|
|
301
|
+
notify: f.notify,
|
|
302
|
+
channelId: f.channelId || null,
|
|
303
|
+
workflow: f.workflow.trim(),
|
|
304
|
+
ui: f.ui.trim() || void 0,
|
|
305
|
+
agent: f.agent,
|
|
306
|
+
exec: buildFormExec(f),
|
|
307
|
+
stateSchema
|
|
308
|
+
};
|
|
309
|
+
} }));
|
|
310
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
311
|
+
className: "grid gap-x-10 lg:grid-cols-[minmax(300px,380px)_minmax(0,1fr)]",
|
|
312
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
313
|
+
className: "min-w-0",
|
|
314
|
+
children: [
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: "Basics" }),
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
|
|
317
|
+
label: "Name",
|
|
318
|
+
value: f.name,
|
|
319
|
+
onChange: (v) => set("name", v)
|
|
320
|
+
}),
|
|
321
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
322
|
+
className: "min-w-0",
|
|
323
|
+
children: [
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
325
|
+
className: labelCls,
|
|
326
|
+
children: "Schedule mode"
|
|
327
|
+
}),
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
329
|
+
"aria-label": "Schedule mode",
|
|
330
|
+
className: selectCls,
|
|
331
|
+
value: f.scheduleMode,
|
|
332
|
+
onChange: (e) => set("scheduleMode", e.target.value),
|
|
333
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
334
|
+
value: "cron",
|
|
335
|
+
children: "Cron"
|
|
336
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
337
|
+
value: "continuous",
|
|
338
|
+
children: "Continuous"
|
|
339
|
+
})]
|
|
340
|
+
}),
|
|
341
|
+
f.scheduleMode === "cron" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
342
|
+
className: `${inputCls} flex cursor-default select-none items-center justify-between gap-2 bg-raised`,
|
|
343
|
+
title: f.cron,
|
|
344
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
345
|
+
className: "truncate text-primary",
|
|
346
|
+
children: cronText(f.cron)
|
|
347
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
348
|
+
className: "shrink-0 font-mono text-caption text-secondary",
|
|
349
|
+
children: f.cron
|
|
350
|
+
})]
|
|
351
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
|
|
352
|
+
label: "Delay after each exec (minutes)",
|
|
353
|
+
value: f.continuousDelayMinutes,
|
|
354
|
+
onChange: (v) => set("continuousDelayMinutes", v),
|
|
355
|
+
mono: true,
|
|
356
|
+
hint: "At least 1 minute. Done and error continue; canceled runs do not."
|
|
357
|
+
}),
|
|
358
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
359
|
+
className: hintCls,
|
|
360
|
+
children: "Cron is retained while continuous mode is active, so switching back restores it."
|
|
361
|
+
})
|
|
362
|
+
]
|
|
363
|
+
}),
|
|
364
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
|
|
365
|
+
label: "Task file",
|
|
366
|
+
value: f.taskFile,
|
|
367
|
+
onChange: (v) => set("taskFile", v),
|
|
368
|
+
mono: true,
|
|
369
|
+
hint: "Path of the markdown file this loop tracks/maintains on the machine (optional)."
|
|
370
|
+
}),
|
|
371
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: "Notifications" }),
|
|
372
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
373
|
+
className: "min-w-0",
|
|
374
|
+
children: [
|
|
375
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
376
|
+
className: labelCls,
|
|
377
|
+
children: "Notify"
|
|
378
|
+
}),
|
|
379
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
|
|
380
|
+
"aria-label": "Notify",
|
|
381
|
+
className: selectCls,
|
|
382
|
+
value: f.notify,
|
|
383
|
+
onChange: (e) => set("notify", e.target.value),
|
|
384
|
+
children: [
|
|
385
|
+
"auto",
|
|
386
|
+
"always",
|
|
387
|
+
"never"
|
|
388
|
+
].map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { children: n }, n))
|
|
389
|
+
}),
|
|
390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
391
|
+
className: hintCls,
|
|
392
|
+
children: "auto pushes only when a run found something; never silences everything."
|
|
393
|
+
})
|
|
394
|
+
]
|
|
395
|
+
}),
|
|
396
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
397
|
+
className: "min-w-0",
|
|
398
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
399
|
+
className: labelCls,
|
|
400
|
+
children: "Push channel"
|
|
401
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
402
|
+
"aria-label": "Push channel",
|
|
403
|
+
className: selectCls,
|
|
404
|
+
value: f.channelId,
|
|
405
|
+
onChange: (e) => set("channelId", e.target.value),
|
|
406
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
407
|
+
value: "",
|
|
408
|
+
children: "none (dashboard only)"
|
|
409
|
+
}), channels.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
410
|
+
value: c.id,
|
|
411
|
+
children: c.name
|
|
412
|
+
}, c.id))]
|
|
413
|
+
})]
|
|
414
|
+
}),
|
|
415
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
|
|
416
|
+
title: "Execution",
|
|
417
|
+
hint: "Which coding agent on the machine runs this loop."
|
|
418
|
+
}),
|
|
419
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
420
|
+
className: "min-w-0",
|
|
421
|
+
children: [
|
|
422
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
423
|
+
className: labelCls,
|
|
424
|
+
children: "Coding agent"
|
|
425
|
+
}),
|
|
426
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
|
|
427
|
+
"aria-label": "Coding agent",
|
|
428
|
+
className: selectCls,
|
|
429
|
+
value: f.agent,
|
|
430
|
+
onChange: (e) => set("agent", e.target.value),
|
|
431
|
+
children: CODING_AGENTS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
432
|
+
value: a,
|
|
433
|
+
children: AGENT_LABEL$1[a] ?? a
|
|
434
|
+
}, a))
|
|
435
|
+
}),
|
|
436
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
437
|
+
className: hintCls,
|
|
438
|
+
children: "Which coding agent executes this loop on the bound machine."
|
|
439
|
+
})
|
|
440
|
+
]
|
|
441
|
+
}),
|
|
442
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
|
|
443
|
+
label: "Working directory",
|
|
444
|
+
value: f.workdir,
|
|
445
|
+
onChange: (v) => set("workdir", v),
|
|
446
|
+
mono: true,
|
|
447
|
+
ph: "/Users/you/Workspace/project",
|
|
448
|
+
hint: "Project root on the machine - empty means workflow-only / agent() escalation."
|
|
449
|
+
}),
|
|
450
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextField, {
|
|
451
|
+
label: "Model",
|
|
452
|
+
value: f.model,
|
|
453
|
+
onChange: (v) => set("model", v),
|
|
454
|
+
hint: "Optional - the daemon default when empty."
|
|
455
|
+
}),
|
|
456
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
|
|
457
|
+
className: "mt-4 flex cursor-pointer items-center gap-2 text-body text-primary",
|
|
458
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
459
|
+
type: "checkbox",
|
|
460
|
+
className: "accent-[color:var(--color-display)]",
|
|
461
|
+
checked: f.allowControl,
|
|
462
|
+
onChange: (e) => set("allowControl", e.target.checked)
|
|
463
|
+
}), "Allow self-rescheduling"]
|
|
464
|
+
})
|
|
465
|
+
]
|
|
466
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
467
|
+
className: "min-w-0",
|
|
468
|
+
children: [
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
|
|
470
|
+
title: "Agent-authored content",
|
|
471
|
+
hint: "Workflow, metrics and dashboard - usually written by evolve passes; edit by hand here when you know exactly what you want."
|
|
472
|
+
}),
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
474
|
+
className: labelCls,
|
|
475
|
+
children: "Workflow · JavaScript function body"
|
|
476
|
+
}),
|
|
477
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
|
|
478
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditorFallback, { minHeight: "180px" }),
|
|
479
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeField, {
|
|
480
|
+
lang: "js",
|
|
481
|
+
value: f.workflow,
|
|
482
|
+
onChange: (v) => set("workflow", v),
|
|
483
|
+
minHeight: "180px",
|
|
484
|
+
placeholder: "const res = await tools.call(\"server.tool\", { ... })\nreturn { message: \"...\", state: { ... } }"
|
|
485
|
+
})
|
|
486
|
+
}),
|
|
487
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
488
|
+
className: hintCls,
|
|
489
|
+
children: [
|
|
490
|
+
"Deterministic zero-LLM pre-stage; top-level await and return ",
|
|
491
|
+
"{message, state}",
|
|
492
|
+
" are allowed (optional)."
|
|
493
|
+
]
|
|
494
|
+
}),
|
|
495
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
496
|
+
className: labelCls,
|
|
497
|
+
children: "Metrics schema · JSON array"
|
|
498
|
+
}),
|
|
499
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
|
|
500
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditorFallback, { minHeight: "90px" }),
|
|
501
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeField, {
|
|
502
|
+
lang: "json",
|
|
503
|
+
value: f.stateSchema,
|
|
504
|
+
onChange: (v) => {
|
|
505
|
+
set("stateSchema", v);
|
|
506
|
+
setSchemaErr(null);
|
|
507
|
+
},
|
|
508
|
+
minHeight: "90px",
|
|
509
|
+
invalid: !!schemaErr,
|
|
510
|
+
placeholder: "[{\"key\":\"mrr\",\"label\":\"MRR\",\"unit\":\"$\"},{\"key\":\"paid\"}]"
|
|
511
|
+
})
|
|
512
|
+
}),
|
|
513
|
+
schemaErr ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
514
|
+
className: "mt-1 text-caption leading-snug text-accent",
|
|
515
|
+
children: schemaErr
|
|
516
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
517
|
+
className: hintCls,
|
|
518
|
+
children: "Per-run metrics the agent reports; they feed the dashboard charts."
|
|
519
|
+
}),
|
|
520
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
521
|
+
className: labelCls,
|
|
522
|
+
children: "Dashboard template · HTML"
|
|
523
|
+
}),
|
|
524
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
|
|
525
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditorFallback, { minHeight: "240px" }),
|
|
526
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeField, {
|
|
527
|
+
lang: "html",
|
|
528
|
+
value: f.ui,
|
|
529
|
+
onChange: (v) => set("ui", v),
|
|
530
|
+
minHeight: "240px",
|
|
531
|
+
placeholder: "<h3>{{latest.mrr}}$</h3>\n<loop-chart series=\"mrr:MRR:$\"></loop-chart>"
|
|
532
|
+
})
|
|
533
|
+
}),
|
|
534
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
535
|
+
className: hintCls,
|
|
536
|
+
children: [
|
|
537
|
+
"Agent-authored HTML with ",
|
|
538
|
+
"{{bindings}}",
|
|
539
|
+
" and loop-chart / loop-embed / loop-calendar / loop-kanban elements."
|
|
540
|
+
]
|
|
541
|
+
})
|
|
542
|
+
]
|
|
543
|
+
})]
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
var AGENT_LABEL = {
|
|
547
|
+
"claude-code": "Claude Code",
|
|
548
|
+
codex: "Codex"
|
|
549
|
+
};
|
|
550
|
+
/** Composer starters - one per editable dimension, so a blank box never stalls
|
|
551
|
+
* the owner. Clicking seeds the instruction; the agent handles the rest. */
|
|
552
|
+
var EDIT_SEEDS = [
|
|
553
|
+
{
|
|
554
|
+
label: "Change the schedule",
|
|
555
|
+
seed: "Change the schedule: "
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
label: "Adjust what it does",
|
|
559
|
+
seed: "Change what this loop does: "
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
label: "Improve the dashboard",
|
|
563
|
+
seed: "Improve the dashboard: "
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
label: "Set a finish line",
|
|
567
|
+
seed: "Give this loop a goal, and finish it once the goal is met: "
|
|
568
|
+
}
|
|
569
|
+
];
|
|
570
|
+
var LoopView = (0, import_react.lazy)(() => import("./LoopView-DCugEbHx.mjs").then((m) => ({ default: m.LoopView })));
|
|
571
|
+
/**
|
|
572
|
+
* Loop detail PAGE body (`/loops/$loopId`) — the redesign of the former modal.
|
|
573
|
+
* One scrolling page: a loop header (name / status / schedule / agent / machine +
|
|
574
|
+
* the action toolbar), an optional agent-authored dashboard, then a two-column
|
|
575
|
+
* main with the UNIFIED Files panel (the task file alongside synced artifacts) and
|
|
576
|
+
* the Runs timeline (a strip + a clickable list, each run linking to its own
|
|
577
|
+
* detail route). Self-polls while open (fast while a run is live).
|
|
578
|
+
*
|
|
579
|
+
* Editing (2026-07 redesign): the primary path is an INLINE composer in the
|
|
580
|
+
* header footer (same slot as the Push settings) - the page stays visible, so
|
|
581
|
+
* the owner describes the change while looking at the spec/dashboard it applies
|
|
582
|
+
* to. Dispatch swaps the composer for a live status card under the header
|
|
583
|
+
* (queued → applying → settled with report + files + a link to
|
|
584
|
+
* the edit run); the page keeps polling, so the applied change surfaces around
|
|
585
|
+
* the card. Only the manual field form (the raw-settings fallback) remains a
|
|
586
|
+
* full-page takeover.
|
|
587
|
+
*/
|
|
588
|
+
function LoopDetailView({ id }) {
|
|
589
|
+
const navigate = useNavigate();
|
|
590
|
+
const [detail, setDetail] = (0, import_react.useState)(null);
|
|
591
|
+
const [channels, setChannels] = (0, import_react.useState)([]);
|
|
592
|
+
const [err, setErr] = (0, import_react.useState)(null);
|
|
593
|
+
const [actionErr, setActionErr] = (0, import_react.useState)(null);
|
|
594
|
+
const [editing, setEditing] = (0, import_react.useState)(false);
|
|
595
|
+
const [editVia, setEditVia] = (0, import_react.useState)(false);
|
|
596
|
+
const [editInstruction, setEditInstruction] = (0, import_react.useState)("");
|
|
597
|
+
const [promptCopied, setPromptCopied] = (0, import_react.useState)(false);
|
|
598
|
+
const [editDispatched, setEditDispatched] = (0, import_react.useState)(false);
|
|
599
|
+
const [editRunId, setEditRunId] = (0, import_react.useState)(null);
|
|
600
|
+
const editBoxRef = (0, import_react.useRef)(null);
|
|
601
|
+
const [pushOpen, setPushOpen] = (0, import_react.useState)(false);
|
|
602
|
+
const [pushSaved, setPushSaved] = (0, import_react.useState)(false);
|
|
603
|
+
const [machinesOpen, setMachinesOpen] = (0, import_react.useState)(false);
|
|
604
|
+
const [pending, setPending] = (0, import_react.useState)(null);
|
|
605
|
+
const [confirming, setConfirming] = (0, import_react.useState)(null);
|
|
606
|
+
const [flash, setFlash] = useFlash();
|
|
607
|
+
const formRef = (0, import_react.useRef)(null);
|
|
608
|
+
const deletingRef = (0, import_react.useRef)(false);
|
|
609
|
+
const [older, setOlder] = (0, import_react.useState)([]);
|
|
610
|
+
const load = (0, import_react.useCallback)(async (silent = false) => {
|
|
611
|
+
try {
|
|
612
|
+
const next = await getJobDetail({ data: id });
|
|
613
|
+
setDetail(next);
|
|
614
|
+
deletingRef.current = next.summary.deleteRequestedAt != null;
|
|
615
|
+
setErr(null);
|
|
616
|
+
} catch (e) {
|
|
617
|
+
if (deletingRef.current) navigate({ to: "/" });
|
|
618
|
+
else if (!silent) setErr(String(e));
|
|
619
|
+
}
|
|
620
|
+
}, [id]);
|
|
621
|
+
(0, import_react.useEffect)(() => {
|
|
622
|
+
setDetail(null);
|
|
623
|
+
setEditing(false);
|
|
624
|
+
setEditVia(false);
|
|
625
|
+
setEditInstruction("");
|
|
626
|
+
setPromptCopied(false);
|
|
627
|
+
setEditDispatched(false);
|
|
628
|
+
setEditRunId(null);
|
|
629
|
+
setPushOpen(false);
|
|
630
|
+
setOlder([]);
|
|
631
|
+
load();
|
|
632
|
+
listChannels().then(setChannels).catch(() => {});
|
|
633
|
+
}, [id, load]);
|
|
634
|
+
const running = !!detail?.summary.running;
|
|
635
|
+
(0, import_react.useEffect)(() => {
|
|
636
|
+
if (editing) return;
|
|
637
|
+
const t = setInterval(() => void load(true), running || deletingRef.current ? 3e3 : 8e3);
|
|
638
|
+
return () => clearInterval(t);
|
|
639
|
+
}, [
|
|
640
|
+
editing,
|
|
641
|
+
running,
|
|
642
|
+
load
|
|
643
|
+
]);
|
|
644
|
+
(0, import_react.useEffect)(() => {
|
|
645
|
+
if (!pushSaved) return;
|
|
646
|
+
const t = setTimeout(() => setPushSaved(false), 1800);
|
|
647
|
+
return () => clearTimeout(t);
|
|
648
|
+
}, [pushSaved]);
|
|
649
|
+
async function refreshAll() {
|
|
650
|
+
await load();
|
|
651
|
+
}
|
|
652
|
+
function onRun() {
|
|
653
|
+
if (detail?.job.exec) setConfirming("run");
|
|
654
|
+
else doRun();
|
|
655
|
+
}
|
|
656
|
+
async function doRun() {
|
|
657
|
+
setActionErr(null);
|
|
658
|
+
setPending("run");
|
|
659
|
+
try {
|
|
660
|
+
const r = await runJob({ data: id });
|
|
661
|
+
if (r?.error) return setActionErr(`Run failed: ${r.error}`);
|
|
662
|
+
setConfirming(null);
|
|
663
|
+
setFlash({
|
|
664
|
+
label: r.coalesced ? "Already queued" : "Queued",
|
|
665
|
+
hold: 4e3
|
|
666
|
+
});
|
|
667
|
+
await refreshAll();
|
|
668
|
+
} finally {
|
|
669
|
+
setPending(null);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async function doEvolve() {
|
|
673
|
+
setActionErr(null);
|
|
674
|
+
setPending("evolve");
|
|
675
|
+
try {
|
|
676
|
+
const r = await evolveJob({ data: id });
|
|
677
|
+
if (r?.error) return setActionErr(`Evolve failed: ${r.error}`);
|
|
678
|
+
setConfirming(null);
|
|
679
|
+
setFlash({
|
|
680
|
+
label: r.coalesced ? "Evolve already queued" : "Evolve queued",
|
|
681
|
+
hold: 4e3
|
|
682
|
+
});
|
|
683
|
+
await refreshAll();
|
|
684
|
+
} finally {
|
|
685
|
+
setPending(null);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
async function doLifecycle(action) {
|
|
689
|
+
setActionErr(null);
|
|
690
|
+
setPending("lifecycle");
|
|
691
|
+
try {
|
|
692
|
+
const r = action === "pause" ? await pauseJob({ data: id }) : action === "start" ? await startJob({ data: id }) : action === "stop" ? await stopJob({ data: id }) : action === "delete" ? await deleteJob({ data: id }) : await forceDeleteJob({ data: {
|
|
693
|
+
id,
|
|
694
|
+
confirmation: "delete-server-data-anyway"
|
|
695
|
+
} });
|
|
696
|
+
if (r.error) return setActionErr(`${action === "force-delete" ? "Force delete" : action[0].toUpperCase() + action.slice(1)} failed: ${r.error}`);
|
|
697
|
+
setConfirming(null);
|
|
698
|
+
if (r.deleted) {
|
|
699
|
+
deletingRef.current = false;
|
|
700
|
+
navigate({ to: "/" });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (action === "delete") deletingRef.current = true;
|
|
704
|
+
await refreshAll();
|
|
705
|
+
setFlash({ label: action === "start" ? "Started" : action === "pause" ? "Paused" : action === "stop" ? "Stop requested" : "Deleting" });
|
|
706
|
+
} finally {
|
|
707
|
+
setPending(null);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function setPush(patch) {
|
|
711
|
+
setActionErr(null);
|
|
712
|
+
const r = await patchJob({ data: {
|
|
713
|
+
id,
|
|
714
|
+
patch
|
|
715
|
+
} });
|
|
716
|
+
if (r.error) return setActionErr(`Save failed: ${r.error}`);
|
|
717
|
+
await refreshAll();
|
|
718
|
+
setPushSaved(true);
|
|
719
|
+
}
|
|
720
|
+
async function onSave() {
|
|
721
|
+
const payload = formRef.current?.read();
|
|
722
|
+
if (!payload) return;
|
|
723
|
+
setActionErr(null);
|
|
724
|
+
setPending("save");
|
|
725
|
+
try {
|
|
726
|
+
const r = await patchJob({ data: {
|
|
727
|
+
id,
|
|
728
|
+
patch: payload
|
|
729
|
+
} });
|
|
730
|
+
if (r.error) return setActionErr(`Save failed: ${r.error}`);
|
|
731
|
+
setEditing(false);
|
|
732
|
+
await refreshAll();
|
|
733
|
+
setFlash({ label: "Saved" });
|
|
734
|
+
} finally {
|
|
735
|
+
setPending(null);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async function onRequestEdit() {
|
|
739
|
+
const instruction = editInstruction.trim();
|
|
740
|
+
if (!instruction) return;
|
|
741
|
+
setActionErr(null);
|
|
742
|
+
setPending("edit");
|
|
743
|
+
try {
|
|
744
|
+
const r = await requestEdit({ data: {
|
|
745
|
+
id,
|
|
746
|
+
instruction
|
|
747
|
+
} });
|
|
748
|
+
if (r.error) return setActionErr(`Couldn't queue the edit: ${r.error}`);
|
|
749
|
+
setEditRunId(r.runId ?? null);
|
|
750
|
+
setEditDispatched(true);
|
|
751
|
+
setEditVia(false);
|
|
752
|
+
setEditInstruction("");
|
|
753
|
+
await refreshAll();
|
|
754
|
+
} finally {
|
|
755
|
+
setPending(null);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function onConfirm() {
|
|
759
|
+
if (confirming === "run") doRun();
|
|
760
|
+
else if (confirming === "evolve") doEvolve();
|
|
761
|
+
else if (confirming === "pause") doLifecycle("pause");
|
|
762
|
+
else if (confirming === "stop") doLifecycle("stop");
|
|
763
|
+
else if (confirming === "delete") doLifecycle("delete");
|
|
764
|
+
else if (confirming === "force-delete") doLifecycle("force-delete");
|
|
765
|
+
}
|
|
766
|
+
const backLink = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
|
|
767
|
+
to: "/",
|
|
768
|
+
className: "inline-flex items-center gap-1.5 text-meta font-medium text-secondary transition-colors hover:text-display",
|
|
769
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
770
|
+
"aria-hidden": true,
|
|
771
|
+
children: "←"
|
|
772
|
+
}), " Loops"]
|
|
773
|
+
});
|
|
774
|
+
if (err) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, {
|
|
775
|
+
back: backLink,
|
|
776
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadErrorCard, {
|
|
777
|
+
title: "Couldn't load this loop.",
|
|
778
|
+
detail: err,
|
|
779
|
+
onRetry: () => void load()
|
|
780
|
+
})
|
|
781
|
+
});
|
|
782
|
+
if (!detail) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, {
|
|
783
|
+
back: backLink,
|
|
784
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loading, {})
|
|
785
|
+
});
|
|
786
|
+
const { job, summary: s, runs } = detail;
|
|
787
|
+
const hasUi = !!job.ui;
|
|
788
|
+
const busy = !!pending;
|
|
789
|
+
const online = detail.machine.online;
|
|
790
|
+
const asleep = detail.machine.presence === "asleep";
|
|
791
|
+
const offlineHint = !online ? asleep ? "Machine asleep - requests will stay queued until it wakes" : "Machine offline - requests will stay queued until it reconnects" : void 0;
|
|
792
|
+
const completed = isCompleted(s);
|
|
793
|
+
const lifecycle = deriveLoopLifecycle(s);
|
|
794
|
+
const lifecycleText = lifecycleDisplay(detail);
|
|
795
|
+
const protocolSupport = daemonStopSupport(detail.machine.daemonProtocol);
|
|
796
|
+
const closedActive = isClosed(s) && !completed;
|
|
797
|
+
const onMachine = detail.machine.name ? `“${detail.machine.name}”` : "the bound machine";
|
|
798
|
+
const editRun = editDispatched && editRunId ? runs.find((r) => r.id === editRunId) : void 0;
|
|
799
|
+
const editSettled = !!editRun && !editRun.queued && !editRun.running;
|
|
800
|
+
const dismissEdit = () => {
|
|
801
|
+
setEditDispatched(false);
|
|
802
|
+
setEditRunId(null);
|
|
803
|
+
};
|
|
804
|
+
const editDir = loopDir(job.taskFile);
|
|
805
|
+
const copyEditPrompt = async () => {
|
|
806
|
+
try {
|
|
807
|
+
await navigator.clipboard.writeText(buildEditPrompt({
|
|
808
|
+
loopId: id,
|
|
809
|
+
loopName: s.name,
|
|
810
|
+
instruction: editInstruction
|
|
811
|
+
}));
|
|
812
|
+
setPromptCopied(true);
|
|
813
|
+
setTimeout(() => setPromptCopied(false), 4e3);
|
|
814
|
+
} catch {
|
|
815
|
+
setActionErr("Could not copy the prompt - try again or copy it manually.");
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
const CONFIRM = {
|
|
819
|
+
run: {
|
|
820
|
+
q: "Run one real cycle now?",
|
|
821
|
+
note: `Spawns the coding agent (${AGENT_LABEL[job.agent ?? "claude-code"] ?? job.agent ?? "Claude Code"}).`,
|
|
822
|
+
cta: "Run once",
|
|
823
|
+
danger: false
|
|
824
|
+
},
|
|
825
|
+
evolve: {
|
|
826
|
+
q: "Trigger an evolution pass now?",
|
|
827
|
+
note: "Re-authors the dashboard / tightens the gate from real run data.",
|
|
828
|
+
cta: "Evolve",
|
|
829
|
+
danger: false
|
|
830
|
+
},
|
|
831
|
+
pause: {
|
|
832
|
+
q: "Pause future runs? The current run will continue.",
|
|
833
|
+
cta: "Pause",
|
|
834
|
+
danger: false
|
|
835
|
+
},
|
|
836
|
+
stop: {
|
|
837
|
+
q: "Pause this loop, cancel queued work, and stop the current run if it is still running?",
|
|
838
|
+
cta: "Stop",
|
|
839
|
+
danger: true
|
|
840
|
+
},
|
|
841
|
+
delete: {
|
|
842
|
+
q: "Stop this loop and delete its Pievo history and synced artifacts? Local project files are not deleted.",
|
|
843
|
+
cta: "Delete",
|
|
844
|
+
danger: true
|
|
845
|
+
},
|
|
846
|
+
"force-delete": {
|
|
847
|
+
q: "Delete server data anyway",
|
|
848
|
+
note: "The machine is unreachable. Its local process may still be running. This removes Pievo authority and server data only.",
|
|
849
|
+
cta: "Delete server data anyway",
|
|
850
|
+
danger: true
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
const flashLine = flash && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
854
|
+
className: "mb-2.5",
|
|
855
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlashLine, {
|
|
856
|
+
label: flash.label,
|
|
857
|
+
tone: flash.tone,
|
|
858
|
+
onUndo: flash.undo
|
|
859
|
+
})
|
|
860
|
+
});
|
|
861
|
+
const c = confirming && CONFIRM[confirming];
|
|
862
|
+
const confirmLocked = busy;
|
|
863
|
+
const pushSelectCls = "lp-select cursor-pointer rounded-control border border-wire bg-surface py-1.5 pl-2.5 text-meta text-primary outline-none transition-colors hover:border-display focus:border-display disabled:cursor-default disabled:opacity-40";
|
|
864
|
+
const menuItem = "flex w-full cursor-pointer select-none items-center px-3.5 py-2 text-body text-primary outline-none transition-colors data-[highlighted]:bg-raised data-[disabled]:cursor-default data-[disabled]:opacity-40";
|
|
865
|
+
const menuItemDanger = "flex w-full cursor-pointer select-none items-center px-3.5 py-2 text-body text-accent outline-none transition-colors data-[highlighted]:bg-accent-soft data-[disabled]:cursor-default data-[disabled]:opacity-40";
|
|
866
|
+
const actionBar = c ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfirmBar, {
|
|
867
|
+
prompt: c.q,
|
|
868
|
+
note: "note" in c ? c.note : void 0,
|
|
869
|
+
cta: c.cta,
|
|
870
|
+
danger: c.danger,
|
|
871
|
+
busy: confirmLocked,
|
|
872
|
+
onConfirm,
|
|
873
|
+
onCancel: () => setConfirming(null)
|
|
874
|
+
}, confirming) : lifecycle === "deleting" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
875
|
+
className: "flex flex-wrap items-center gap-3 rounded-md border border-wire bg-raised px-4 py-3",
|
|
876
|
+
children: [
|
|
877
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
878
|
+
className: "text-body font-medium text-display",
|
|
879
|
+
children: lifecycleText
|
|
880
|
+
}),
|
|
881
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
882
|
+
className: "text-meta text-secondary",
|
|
883
|
+
children: "Server deletion is waiting for execution authority to end."
|
|
884
|
+
}),
|
|
885
|
+
!online && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
886
|
+
type: "button",
|
|
887
|
+
className: btnDanger,
|
|
888
|
+
onClick: () => setConfirming("force-delete"),
|
|
889
|
+
disabled: busy,
|
|
890
|
+
children: "Delete server data anyway"
|
|
891
|
+
})
|
|
892
|
+
]
|
|
893
|
+
}) : editVia ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
894
|
+
onKeyDown: (e) => {
|
|
895
|
+
if (e.key === "Escape" && pending !== "edit") {
|
|
896
|
+
e.stopPropagation();
|
|
897
|
+
setEditVia(false);
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
children: [
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
902
|
+
className: "flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1",
|
|
903
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
904
|
+
className: "text-body font-medium text-display",
|
|
905
|
+
children: "Edit with your coding agent"
|
|
906
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
907
|
+
className: "text-meta text-secondary",
|
|
908
|
+
children: [
|
|
909
|
+
"One agent pass on ",
|
|
910
|
+
onMachine,
|
|
911
|
+
" · spends credits"
|
|
912
|
+
]
|
|
913
|
+
})]
|
|
914
|
+
}),
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
|
|
916
|
+
ref: editBoxRef,
|
|
917
|
+
autoFocus: true,
|
|
918
|
+
value: editInstruction,
|
|
919
|
+
onChange: (e) => setEditInstruction(e.target.value),
|
|
920
|
+
onKeyDown: (e) => {
|
|
921
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && editInstruction.trim() && pending !== "edit") onRequestEdit();
|
|
922
|
+
},
|
|
923
|
+
rows: 3,
|
|
924
|
+
placeholder: "e.g. run at 9am on weekdays instead, and also check coffee stock",
|
|
925
|
+
className: "mt-3 w-full resize-y rounded-control border border-wire bg-raised p-3 font-mono text-label leading-relaxed text-primary outline-none transition-shadow placeholder:text-disabled focus:border-transparent focus:shadow-focus"
|
|
926
|
+
}),
|
|
927
|
+
!editInstruction.trim() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
928
|
+
className: "mt-2 flex flex-wrap gap-1.5",
|
|
929
|
+
children: EDIT_SEEDS.map((sd) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
930
|
+
type: "button",
|
|
931
|
+
onClick: () => {
|
|
932
|
+
setEditInstruction(sd.seed);
|
|
933
|
+
editBoxRef.current?.focus();
|
|
934
|
+
},
|
|
935
|
+
className: "cursor-pointer rounded-full border border-wire bg-surface px-3 py-1 text-label font-medium text-secondary transition-colors hover:bg-raised hover:text-display",
|
|
936
|
+
children: sd.label
|
|
937
|
+
}, sd.label))
|
|
938
|
+
}),
|
|
939
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
940
|
+
className: "mt-3.5 flex flex-wrap items-center gap-x-2.5 gap-y-2",
|
|
941
|
+
children: [
|
|
942
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
943
|
+
className: btnCost,
|
|
944
|
+
disabled: pending === "edit" || !editInstruction.trim(),
|
|
945
|
+
title: offlineHint,
|
|
946
|
+
onClick: () => void onRequestEdit(),
|
|
947
|
+
children: pending === "edit" ? "Dispatching…" : "Dispatch to your coding agent"
|
|
948
|
+
}),
|
|
949
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
950
|
+
type: "button",
|
|
951
|
+
className: btn,
|
|
952
|
+
disabled: pending === "edit",
|
|
953
|
+
onClick: () => void copyEditPrompt(),
|
|
954
|
+
children: promptCopied ? "✓ Prompt copied" : "Copy prompt"
|
|
955
|
+
}),
|
|
956
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
957
|
+
className: btn,
|
|
958
|
+
disabled: pending === "edit",
|
|
959
|
+
onClick: () => setEditVia(false),
|
|
960
|
+
children: "Cancel"
|
|
961
|
+
}),
|
|
962
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
963
|
+
className: "hidden text-caption text-disabled sm:inline",
|
|
964
|
+
children: "⌘↩ dispatch · esc cancel"
|
|
965
|
+
}),
|
|
966
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
967
|
+
type: "button",
|
|
968
|
+
onClick: () => {
|
|
969
|
+
setEditVia(false);
|
|
970
|
+
setEditing(true);
|
|
971
|
+
},
|
|
972
|
+
className: "ml-auto cursor-pointer border-none bg-transparent p-0 text-label text-secondary underline underline-offset-2 transition-colors hover:text-display",
|
|
973
|
+
children: "Manual settings →"
|
|
974
|
+
})
|
|
975
|
+
]
|
|
976
|
+
}),
|
|
977
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
978
|
+
className: "mt-2 text-caption leading-snug text-disabled",
|
|
979
|
+
children: promptCopied ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
980
|
+
className: "text-secondary",
|
|
981
|
+
children: [
|
|
982
|
+
"✓ Copied · run your coding agent",
|
|
983
|
+
" ",
|
|
984
|
+
editDir ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: ["in ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
|
|
985
|
+
className: "break-all font-mono text-primary",
|
|
986
|
+
children: editDir
|
|
987
|
+
})] }) : "in the loop’s local directory on this machine",
|
|
988
|
+
" ",
|
|
989
|
+
"and paste the prompt to adjust the loop yourself."
|
|
990
|
+
]
|
|
991
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: "Prefer to adjust it yourself? Copy prompt drops a ready-to-paste prompt for your own coding agent - no dispatch, no credits." })
|
|
992
|
+
})
|
|
993
|
+
]
|
|
994
|
+
}) : pushOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
995
|
+
className: "flex flex-wrap items-center gap-2.5",
|
|
996
|
+
children: [
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
998
|
+
className: "text-label font-medium text-secondary",
|
|
999
|
+
children: "Push"
|
|
1000
|
+
}),
|
|
1001
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
|
|
1002
|
+
"aria-label": "Notify",
|
|
1003
|
+
className: pushSelectCls,
|
|
1004
|
+
value: job.notify,
|
|
1005
|
+
disabled: busy,
|
|
1006
|
+
onChange: (e) => void setPush({ notify: e.target.value }),
|
|
1007
|
+
children: [
|
|
1008
|
+
"auto",
|
|
1009
|
+
"always",
|
|
1010
|
+
"never"
|
|
1011
|
+
].map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
1012
|
+
value: n,
|
|
1013
|
+
children: n
|
|
1014
|
+
}, n))
|
|
1015
|
+
}),
|
|
1016
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1017
|
+
"aria-hidden": true,
|
|
1018
|
+
className: "text-secondary",
|
|
1019
|
+
children: "→"
|
|
1020
|
+
}),
|
|
1021
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
1022
|
+
"aria-label": "Push channel",
|
|
1023
|
+
className: pushSelectCls,
|
|
1024
|
+
value: job.channelId ?? "",
|
|
1025
|
+
disabled: busy,
|
|
1026
|
+
onChange: (e) => void setPush({ channelId: e.target.value }),
|
|
1027
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
1028
|
+
value: "",
|
|
1029
|
+
children: "none (dashboard only)"
|
|
1030
|
+
}), channels.map((ch) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
1031
|
+
value: ch.id,
|
|
1032
|
+
children: ch.name
|
|
1033
|
+
}, ch.id))]
|
|
1034
|
+
}),
|
|
1035
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1036
|
+
className: "ml-auto flex items-center gap-2.5",
|
|
1037
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1038
|
+
"aria-hidden": true,
|
|
1039
|
+
className: `text-[14px] leading-none text-success transition-opacity duration-200 ${pushSaved ? "opacity-100" : "opacity-0"}`,
|
|
1040
|
+
children: "✓"
|
|
1041
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1042
|
+
type: "button",
|
|
1043
|
+
onClick: () => setPushOpen(false),
|
|
1044
|
+
className: btnQuiet,
|
|
1045
|
+
children: "Done"
|
|
1046
|
+
})]
|
|
1047
|
+
})
|
|
1048
|
+
]
|
|
1049
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1050
|
+
className: "flex flex-wrap items-center gap-2",
|
|
1051
|
+
children: [
|
|
1052
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1053
|
+
className: btnPrimary,
|
|
1054
|
+
disabled: busy || completed,
|
|
1055
|
+
onClick: onRun,
|
|
1056
|
+
title: completed ? "Loop completed - only Edit is available until it is reopened" : s.running || s.queued ? "Queues or coalesces one exec follow-up" : offlineHint ?? (job.exec ? "Spends credits" : void 0),
|
|
1057
|
+
"aria-label": job.exec ? "Run once - spends credits" : "Run once",
|
|
1058
|
+
children: pending === "run" ? "Queuing…" : s.running || s.queued ? "Queue run" : "Run once"
|
|
1059
|
+
}),
|
|
1060
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1061
|
+
className: btn,
|
|
1062
|
+
disabled: busy,
|
|
1063
|
+
title: "Queues an edit; a queued edit is updated with the latest instruction",
|
|
1064
|
+
onClick: () => setEditVia(true),
|
|
1065
|
+
children: "Edit"
|
|
1066
|
+
}),
|
|
1067
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MenuRoot, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuTrigger, {
|
|
1068
|
+
className: `${btn} px-2.5`,
|
|
1069
|
+
disabled: busy,
|
|
1070
|
+
"aria-label": "More actions",
|
|
1071
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
|
|
1072
|
+
"aria-hidden": true,
|
|
1073
|
+
width: "16",
|
|
1074
|
+
height: "16",
|
|
1075
|
+
viewBox: "0 0 16 16",
|
|
1076
|
+
fill: "currentColor",
|
|
1077
|
+
children: [
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
|
|
1079
|
+
cx: "3",
|
|
1080
|
+
cy: "8",
|
|
1081
|
+
r: "1.4"
|
|
1082
|
+
}),
|
|
1083
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
|
|
1084
|
+
cx: "8",
|
|
1085
|
+
cy: "8",
|
|
1086
|
+
r: "1.4"
|
|
1087
|
+
}),
|
|
1088
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
|
|
1089
|
+
cx: "13",
|
|
1090
|
+
cy: "8",
|
|
1091
|
+
r: "1.4"
|
|
1092
|
+
})
|
|
1093
|
+
]
|
|
1094
|
+
})
|
|
1095
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPositioner, {
|
|
1096
|
+
side: "bottom",
|
|
1097
|
+
align: "end",
|
|
1098
|
+
sideOffset: 6,
|
|
1099
|
+
className: "z-[950]",
|
|
1100
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MenuPopup, {
|
|
1101
|
+
className: "glass-strong min-w-[176px] origin-[var(--transform-origin)] rounded-control py-1.5 outline-none transition-[opacity,transform] duration-150 data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
|
|
1102
|
+
children: [
|
|
1103
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
1104
|
+
className: menuItem,
|
|
1105
|
+
disabled: busy || completed,
|
|
1106
|
+
onClick: () => setConfirming("evolve"),
|
|
1107
|
+
title: completed ? "Completed loops only accept manual edits" : offlineHint ?? "Spends credits",
|
|
1108
|
+
children: pending === "evolve" ? "Evolving…" : "Evolve now"
|
|
1109
|
+
}),
|
|
1110
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
1111
|
+
className: menuItem,
|
|
1112
|
+
onClick: () => setPushOpen(true),
|
|
1113
|
+
children: "Push…"
|
|
1114
|
+
}),
|
|
1115
|
+
completed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
1116
|
+
className: menuItem,
|
|
1117
|
+
onClick: () => void patchJob({ data: {
|
|
1118
|
+
id,
|
|
1119
|
+
patch: { enabled: true }
|
|
1120
|
+
} }).then(refreshAll),
|
|
1121
|
+
children: "Reopen"
|
|
1122
|
+
}) : lifecycle === "paused" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
1123
|
+
className: menuItem,
|
|
1124
|
+
onClick: () => void doLifecycle("start"),
|
|
1125
|
+
children: "Start"
|
|
1126
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
1127
|
+
className: menuItem,
|
|
1128
|
+
onClick: () => setConfirming("pause"),
|
|
1129
|
+
children: "Pause"
|
|
1130
|
+
}),
|
|
1131
|
+
!completed && lifecycle !== "stopping" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
1132
|
+
className: menuItemDanger,
|
|
1133
|
+
disabled: !!s.running && !protocolSupport.supported,
|
|
1134
|
+
title: s.running && !protocolSupport.supported ? "Daemon upgrade required to stop a running process. Run `npm install -g @kky42/pievo@latest`, then `pievo daemon restart`." : void 0,
|
|
1135
|
+
onClick: () => setConfirming("stop"),
|
|
1136
|
+
children: "Stop"
|
|
1137
|
+
}),
|
|
1138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator, { className: "my-1.5 h-px bg-hairline" }),
|
|
1139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
1140
|
+
className: menuItemDanger,
|
|
1141
|
+
disabled: !!s.running && !protocolSupport.supported,
|
|
1142
|
+
title: s.running && !protocolSupport.supported ? DAEMON_UPGRADE_REQUIRED : void 0,
|
|
1143
|
+
onClick: () => setConfirming("delete"),
|
|
1144
|
+
children: "Delete"
|
|
1145
|
+
})
|
|
1146
|
+
]
|
|
1147
|
+
})
|
|
1148
|
+
}) })] })
|
|
1149
|
+
]
|
|
1150
|
+
}) });
|
|
1151
|
+
const actionErrEl = actionErr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, {
|
|
1152
|
+
message: actionErr,
|
|
1153
|
+
onDismiss: () => setActionErr(null),
|
|
1154
|
+
className: "mb-2.5"
|
|
1155
|
+
});
|
|
1156
|
+
const crossTeam = detail.team && !detail.team.isActive ? detail.team : null;
|
|
1157
|
+
const switchTeam = (teamId) => {
|
|
1158
|
+
setActiveTeamCookie(teamId);
|
|
1159
|
+
navigate({
|
|
1160
|
+
to: "/t/$teamId",
|
|
1161
|
+
params: { teamId }
|
|
1162
|
+
});
|
|
1163
|
+
};
|
|
1164
|
+
const crossTeamEl = crossTeam && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1165
|
+
className: "mb-2.5 flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-control border border-hairline bg-raised px-4 py-2.5",
|
|
1166
|
+
children: [
|
|
1167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1168
|
+
className: "inline-flex items-center gap-2 text-meta font-medium text-secondary",
|
|
1169
|
+
children: [
|
|
1170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1171
|
+
"aria-hidden": true,
|
|
1172
|
+
className: "size-2 rounded-full bg-interactive"
|
|
1173
|
+
}),
|
|
1174
|
+
"Viewing a loop in ",
|
|
1175
|
+
crossTeam.name
|
|
1176
|
+
]
|
|
1177
|
+
}),
|
|
1178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1179
|
+
className: "text-meta text-secondary",
|
|
1180
|
+
children: "- not your active team."
|
|
1181
|
+
}),
|
|
1182
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1183
|
+
type: "button",
|
|
1184
|
+
onClick: () => switchTeam(crossTeam.id),
|
|
1185
|
+
className: "ml-auto cursor-pointer text-meta font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
|
|
1186
|
+
children: "Switch to this team"
|
|
1187
|
+
})
|
|
1188
|
+
]
|
|
1189
|
+
});
|
|
1190
|
+
const offlineEl = !online && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1191
|
+
className: "mb-2.5 flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-control border border-hairline bg-raised px-4 py-2.5",
|
|
1192
|
+
children: [
|
|
1193
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1194
|
+
className: "inline-flex items-center gap-2 text-meta font-medium text-secondary",
|
|
1195
|
+
children: [
|
|
1196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1197
|
+
"aria-hidden": true,
|
|
1198
|
+
className: `size-2 rounded-full ${asleep ? "bg-rubik-yellow" : "bg-disabled"}`
|
|
1199
|
+
}),
|
|
1200
|
+
"Machine ",
|
|
1201
|
+
detail.machine.name ? `“${detail.machine.name}” ` : "",
|
|
1202
|
+
asleep ? "seems to be asleep or offline" : "offline"
|
|
1203
|
+
]
|
|
1204
|
+
}),
|
|
1205
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1206
|
+
className: "text-meta text-secondary",
|
|
1207
|
+
children: [asleep ? "- queued work starts automatically when it reconnects." : "- manual run, evolve, and edit requests remain safely queued.", detail.machine.lastSeen ? ` Last seen ${rel(detail.machine.lastSeen)}.` : ""]
|
|
1208
|
+
}),
|
|
1209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1210
|
+
type: "button",
|
|
1211
|
+
onClick: () => setMachinesOpen(true),
|
|
1212
|
+
className: "ml-auto cursor-pointer text-meta font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
|
|
1213
|
+
children: "Reconnect"
|
|
1214
|
+
})
|
|
1215
|
+
]
|
|
1216
|
+
});
|
|
1217
|
+
if (editing) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, {
|
|
1218
|
+
back: backLink,
|
|
1219
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditHead, {
|
|
1220
|
+
name: s.name,
|
|
1221
|
+
onBack: () => setEditing(false)
|
|
1222
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1223
|
+
className: "mt-5 rounded-card border border-hairline bg-surface px-6 pb-6 pt-2 shadow-card",
|
|
1224
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopForm, {
|
|
1225
|
+
ref: formRef,
|
|
1226
|
+
initial: job,
|
|
1227
|
+
channels
|
|
1228
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1229
|
+
className: "mt-7 border-t border-hairline pt-4",
|
|
1230
|
+
children: [actionErrEl, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1231
|
+
className: "flex flex-wrap gap-2.5",
|
|
1232
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1233
|
+
className: btnPrimary,
|
|
1234
|
+
disabled: pending === "save",
|
|
1235
|
+
onClick: onSave,
|
|
1236
|
+
children: pending === "save" ? "Saving…" : "Save"
|
|
1237
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1238
|
+
className: btn,
|
|
1239
|
+
disabled: pending === "save",
|
|
1240
|
+
onClick: () => setEditing(false),
|
|
1241
|
+
children: "Cancel"
|
|
1242
|
+
})]
|
|
1243
|
+
})]
|
|
1244
|
+
})]
|
|
1245
|
+
})]
|
|
1246
|
+
});
|
|
1247
|
+
const agentLabel = AGENT_LABEL[job.agent ?? "claude-code"] ?? job.agent ?? "Claude Code";
|
|
1248
|
+
const metaDot = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1249
|
+
className: "text-wire",
|
|
1250
|
+
children: "·"
|
|
1251
|
+
});
|
|
1252
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, {
|
|
1253
|
+
back: backLink,
|
|
1254
|
+
children: [
|
|
1255
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
|
|
1256
|
+
className: "rounded-card border border-hairline bg-surface px-6 pb-5 pt-[22px] shadow-card",
|
|
1257
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1258
|
+
className: "flex flex-wrap items-start justify-between gap-x-6 gap-y-4",
|
|
1259
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1260
|
+
className: "min-w-0",
|
|
1261
|
+
children: [
|
|
1262
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1263
|
+
className: "flex flex-wrap items-center gap-3",
|
|
1264
|
+
children: [
|
|
1265
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
|
|
1266
|
+
className: "text-[28px] font-semibold leading-tight tracking-[-0.015em] text-display",
|
|
1267
|
+
children: s.name
|
|
1268
|
+
}),
|
|
1269
|
+
completed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
1270
|
+
tone: "success",
|
|
1271
|
+
dot: "green",
|
|
1272
|
+
children: "Completed"
|
|
1273
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
1274
|
+
tone: lifecycle === "stopping" ? "running" : void 0,
|
|
1275
|
+
dot: lifecycle === "stopping" ? "pulse" : void 0,
|
|
1276
|
+
children: lifecycleText
|
|
1277
|
+
}),
|
|
1278
|
+
s.queued && lifecycle === "active" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
1279
|
+
tone: "outline",
|
|
1280
|
+
children: "Queued"
|
|
1281
|
+
}) : null,
|
|
1282
|
+
closedActive && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
1283
|
+
tone: "success",
|
|
1284
|
+
title: s.goal ?? void 0,
|
|
1285
|
+
children: "Goal"
|
|
1286
|
+
}),
|
|
1287
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
1288
|
+
tone: "outline",
|
|
1289
|
+
title: "Recorded coding agent",
|
|
1290
|
+
children: agentLabel
|
|
1291
|
+
}),
|
|
1292
|
+
crossTeam && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
1293
|
+
tone: "outline",
|
|
1294
|
+
title: "This loop belongs to another of your teams",
|
|
1295
|
+
children: crossTeam.name
|
|
1296
|
+
})
|
|
1297
|
+
]
|
|
1298
|
+
}),
|
|
1299
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1300
|
+
className: "mt-2.5 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-meta text-secondary",
|
|
1301
|
+
children: [
|
|
1302
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1303
|
+
className: "text-primary",
|
|
1304
|
+
title: job.cron,
|
|
1305
|
+
children: job.scheduleMode === "continuous" ? `continuous · ${job.continuousDelayMinutes}m delay` : cronText(job.cron)
|
|
1306
|
+
}),
|
|
1307
|
+
metaDot,
|
|
1308
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["next ", fmt(s.nextRun)] }),
|
|
1309
|
+
s.nextRun && s.enabled && !completed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1310
|
+
className: "text-disabled",
|
|
1311
|
+
children: [
|
|
1312
|
+
"(",
|
|
1313
|
+
until(s.nextRun),
|
|
1314
|
+
")"
|
|
1315
|
+
]
|
|
1316
|
+
}),
|
|
1317
|
+
metaDot,
|
|
1318
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1319
|
+
className: "inline-flex items-center gap-1.5",
|
|
1320
|
+
title: online ? "Machine online" : asleep ? "Machine asleep" : "Machine offline",
|
|
1321
|
+
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"]
|
|
1322
|
+
}),
|
|
1323
|
+
metaDot,
|
|
1324
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1325
|
+
title: "Breaking daemon/server lifecycle protocol",
|
|
1326
|
+
children: protocolSupport.label
|
|
1327
|
+
}),
|
|
1328
|
+
metaDot,
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
|
|
1330
|
+
className: "font-mono text-label text-disabled",
|
|
1331
|
+
children: s.id
|
|
1332
|
+
})
|
|
1333
|
+
]
|
|
1334
|
+
}),
|
|
1335
|
+
closedActive && s.goal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1336
|
+
className: "mt-2 text-body leading-snug text-secondary",
|
|
1337
|
+
children: ["Working toward: ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1338
|
+
className: "text-primary",
|
|
1339
|
+
children: s.goal
|
|
1340
|
+
})]
|
|
1341
|
+
}),
|
|
1342
|
+
completed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1343
|
+
className: "mt-2 text-body leading-snug text-success",
|
|
1344
|
+
children: [
|
|
1345
|
+
"Completed",
|
|
1346
|
+
s.completedAt ? ` · ${fmt(s.completedAt)}` : "",
|
|
1347
|
+
s.completionReason && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1348
|
+
className: "text-secondary",
|
|
1349
|
+
children: [" - ", s.completionReason]
|
|
1350
|
+
})
|
|
1351
|
+
]
|
|
1352
|
+
})
|
|
1353
|
+
]
|
|
1354
|
+
})
|
|
1355
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1356
|
+
className: "mt-5 border-t border-hairline pt-4",
|
|
1357
|
+
children: [
|
|
1358
|
+
crossTeamEl,
|
|
1359
|
+
offlineEl,
|
|
1360
|
+
actionErrEl,
|
|
1361
|
+
flashLine,
|
|
1362
|
+
actionBar
|
|
1363
|
+
]
|
|
1364
|
+
})]
|
|
1365
|
+
}),
|
|
1366
|
+
editDispatched && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
1367
|
+
className: "mt-6 rounded-card border border-hairline bg-surface px-6 py-5 shadow-card",
|
|
1368
|
+
style: { animation: "fadeIn 0.2s ease-out" },
|
|
1369
|
+
"aria-live": "polite",
|
|
1370
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1371
|
+
className: "flex flex-wrap items-center gap-x-4 gap-y-2",
|
|
1372
|
+
children: [!editRun || editRun.queued ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1373
|
+
className: "inline-flex items-center gap-2.5 text-body text-secondary",
|
|
1374
|
+
children: [
|
|
1375
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1376
|
+
"aria-hidden": true,
|
|
1377
|
+
className: "size-1.5 shrink-0 rounded-full bg-disabled"
|
|
1378
|
+
}),
|
|
1379
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1380
|
+
className: "font-medium text-primary",
|
|
1381
|
+
children: "Edit queued"
|
|
1382
|
+
}),
|
|
1383
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
1384
|
+
"waiting for ",
|
|
1385
|
+
onMachine,
|
|
1386
|
+
" and higher-priority work…"
|
|
1387
|
+
] })
|
|
1388
|
+
]
|
|
1389
|
+
}) : editRun.running ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1390
|
+
className: "inline-flex min-w-0 items-center gap-2.5 text-body text-secondary",
|
|
1391
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1392
|
+
"aria-hidden": true,
|
|
1393
|
+
className: "size-1.5 shrink-0 rounded-full",
|
|
1394
|
+
style: runPulseStyle
|
|
1395
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1396
|
+
className: "shrink-0 font-medium text-primary",
|
|
1397
|
+
children: "Applying your edit"
|
|
1398
|
+
})]
|
|
1399
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1400
|
+
className: "inline-flex min-w-0 items-center gap-2 text-body",
|
|
1401
|
+
children: [
|
|
1402
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1403
|
+
"aria-hidden": true,
|
|
1404
|
+
className: "size-2.5 shrink-0 rounded-[2px]",
|
|
1405
|
+
style: { background: dotColor(editRun) }
|
|
1406
|
+
}),
|
|
1407
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1408
|
+
className: "font-medium",
|
|
1409
|
+
style: { color: dotColor(editRun) },
|
|
1410
|
+
children: editRun.canceled ? "Edit canceled" : editRun.outcome === "error" ? "Edit failed" : "Edit applied"
|
|
1411
|
+
}),
|
|
1412
|
+
editRun.error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1413
|
+
className: "truncate text-secondary",
|
|
1414
|
+
children: ["· ", editRun.error]
|
|
1415
|
+
})
|
|
1416
|
+
]
|
|
1417
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1418
|
+
className: "ml-auto flex items-center gap-3.5",
|
|
1419
|
+
children: [editRun && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
|
|
1420
|
+
to: "/loops/$loopId/runs/$runId",
|
|
1421
|
+
params: {
|
|
1422
|
+
loopId: id,
|
|
1423
|
+
runId: editRun.id
|
|
1424
|
+
},
|
|
1425
|
+
className: "text-label font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
|
|
1426
|
+
children: "View run →"
|
|
1427
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1428
|
+
type: "button",
|
|
1429
|
+
onClick: dismissEdit,
|
|
1430
|
+
className: "inline-flex cursor-pointer items-center gap-1.5 border-none bg-transparent p-0 text-meta font-medium text-secondary transition-colors hover:text-display",
|
|
1431
|
+
children: editSettled ? "Done" : "Hide"
|
|
1432
|
+
})]
|
|
1433
|
+
})]
|
|
1434
|
+
}), editSettled && editRun.message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1435
|
+
className: "mt-3.5",
|
|
1436
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pre, { children: editRun.message })
|
|
1437
|
+
})]
|
|
1438
|
+
}),
|
|
1439
|
+
hasUi && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
1440
|
+
className: "mt-6 min-w-0 rounded-card border border-hairline bg-surface px-6 py-5 shadow-card",
|
|
1441
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1442
|
+
className: `mb-3.5 border-b border-hairline pb-1.5 text-label font-semibold text-secondary`,
|
|
1443
|
+
children: "Dashboard"
|
|
1444
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1445
|
+
className: "min-w-0 overflow-x-auto",
|
|
1446
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
|
|
1447
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loading, { className: "py-4" }),
|
|
1448
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopView, {
|
|
1449
|
+
html: job.ui,
|
|
1450
|
+
runs,
|
|
1451
|
+
loopId: id,
|
|
1452
|
+
taskFile: job.taskFile
|
|
1453
|
+
})
|
|
1454
|
+
})
|
|
1455
|
+
})]
|
|
1456
|
+
}),
|
|
1457
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1458
|
+
className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(300px,360px)]",
|
|
1459
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopFilesPanel, {
|
|
1460
|
+
loopId: id,
|
|
1461
|
+
taskFile: job.taskFile,
|
|
1462
|
+
taskFileContent: detail.taskFileContent,
|
|
1463
|
+
taskFileSyncedAt: detail.taskFileSyncedAt,
|
|
1464
|
+
running
|
|
1465
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunsSection, {
|
|
1466
|
+
loopId: id,
|
|
1467
|
+
summary: s,
|
|
1468
|
+
runs,
|
|
1469
|
+
older,
|
|
1470
|
+
onMore: async () => {
|
|
1471
|
+
const oldest = (older.length ? mergeRuns(s.runs ?? [], older) : s.runs ?? [])[0];
|
|
1472
|
+
if (!oldest) return 0;
|
|
1473
|
+
const more = await loadOlderRuns({ data: {
|
|
1474
|
+
loopId: id,
|
|
1475
|
+
beforeTs: oldest.ts,
|
|
1476
|
+
limit: 16
|
|
1477
|
+
} });
|
|
1478
|
+
if (more.length) setOlder((prev) => mergeRuns(prev, more));
|
|
1479
|
+
return more.length;
|
|
1480
|
+
},
|
|
1481
|
+
onPickRun: (run) => navigate({
|
|
1482
|
+
to: "/loops/$loopId/runs/$runId",
|
|
1483
|
+
params: {
|
|
1484
|
+
loopId: id,
|
|
1485
|
+
runId: run.id
|
|
1486
|
+
}
|
|
1487
|
+
})
|
|
1488
|
+
})]
|
|
1489
|
+
}),
|
|
1490
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MachinesModal, {
|
|
1491
|
+
open: machinesOpen,
|
|
1492
|
+
onClose: () => setMachinesOpen(false)
|
|
1493
|
+
})
|
|
1494
|
+
]
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Manual-settings page heading. The field form is an in-page mode takeover (NOT
|
|
1499
|
+
* a modal), so this is a plain heading - NOT `ModalHead`, whose Base UI
|
|
1500
|
+
* `Dialog.Title`/`Dialog.Close` require a `Dialog.Root` ancestor and throw
|
|
1501
|
+
* ("Cannot destructure property 'store' of 'useDialogRootContext(...)'") when
|
|
1502
|
+
* rendered on a bare page.
|
|
1503
|
+
*/
|
|
1504
|
+
function EditHead({ name, onBack }) {
|
|
1505
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1506
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
1507
|
+
type: "button",
|
|
1508
|
+
onClick: onBack,
|
|
1509
|
+
className: btnQuiet,
|
|
1510
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1511
|
+
"aria-hidden": true,
|
|
1512
|
+
children: "←"
|
|
1513
|
+
}), " Back to loop"]
|
|
1514
|
+
}),
|
|
1515
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", {
|
|
1516
|
+
className: "mt-2.5 text-[24px] font-semibold leading-tight tracking-[-0.015em] text-display",
|
|
1517
|
+
children: ["Manual settings · ", name]
|
|
1518
|
+
}),
|
|
1519
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1520
|
+
className: "mt-1.5 max-w-[640px] text-meta leading-snug text-secondary",
|
|
1521
|
+
children: "The raw fields, saved directly - no agent pass, no credits. For schedule or task changes in plain words, use Edit with your coding agent instead."
|
|
1522
|
+
})
|
|
1523
|
+
] });
|
|
1524
|
+
}
|
|
1525
|
+
/** The page shell — centered column, a back affordance, consistent padding. */
|
|
1526
|
+
function Shell({ back, children }) {
|
|
1527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
|
|
1528
|
+
className: "mx-auto max-w-[1360px] px-8 pb-24 pt-10",
|
|
1529
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1530
|
+
className: "mb-5",
|
|
1531
|
+
children: back
|
|
1532
|
+
}), children]
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
/** The runs panel — the signature timeline strip (paged) over a scannable list
|
|
1536
|
+
* of recent runs, each row linking to its own run-detail route. */
|
|
1537
|
+
function RunsSection({ loopId, summary, runs, older, onMore, onPickRun }) {
|
|
1538
|
+
const stripRuns = (0, import_react.useMemo)(() => older.length ? mergeRuns(summary.runs ?? [], older) : summary.runs ?? [], [summary.runs, older]);
|
|
1539
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
1540
|
+
className: "min-w-0",
|
|
1541
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1542
|
+
className: "mb-2.5 flex items-end justify-between gap-3 border-b border-hairline pb-1.5",
|
|
1543
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
|
|
1544
|
+
className: sectionHeadCls,
|
|
1545
|
+
children: [
|
|
1546
|
+
"Runs (",
|
|
1547
|
+
summary.runCount,
|
|
1548
|
+
")"
|
|
1549
|
+
]
|
|
1550
|
+
})
|
|
1551
|
+
}), summary.runCount === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1552
|
+
className: "rounded-card border border-hairline bg-surface px-5 py-10 text-center text-body text-disabled",
|
|
1553
|
+
children: "Never run"
|
|
1554
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1555
|
+
className: "rounded-card border border-hairline bg-surface px-5 pb-4 pt-5 shadow-card",
|
|
1556
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Timeline, {
|
|
1557
|
+
job: summary,
|
|
1558
|
+
runs: stripRuns,
|
|
1559
|
+
total: summary.runCount,
|
|
1560
|
+
onLoadMore: onMore,
|
|
1561
|
+
onPickRun
|
|
1562
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
|
|
1563
|
+
className: "mt-5 max-h-[clamp(280px,46vh,520px)] divide-y divide-hairline overflow-y-auto border-t border-hairline",
|
|
1564
|
+
children: runs.map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
|
|
1565
|
+
to: "/loops/$loopId/runs/$runId",
|
|
1566
|
+
params: {
|
|
1567
|
+
loopId,
|
|
1568
|
+
runId: x.id
|
|
1569
|
+
},
|
|
1570
|
+
className: "flex items-start gap-2.5 py-2.5 transition-colors hover:bg-raised",
|
|
1571
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1572
|
+
className: "mt-1 inline-block size-2.5 shrink-0 rounded-[2px]",
|
|
1573
|
+
style: { background: dotColor(x) },
|
|
1574
|
+
title: dotLabel(x),
|
|
1575
|
+
"aria-label": dotLabel(x)
|
|
1576
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1577
|
+
className: "min-w-0 flex-1",
|
|
1578
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1579
|
+
className: "flex items-baseline justify-between gap-2",
|
|
1580
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1581
|
+
className: "font-mono text-label text-secondary",
|
|
1582
|
+
children: tsShort(x.ts)
|
|
1583
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1584
|
+
className: "shrink-0 font-mono text-caption text-disabled",
|
|
1585
|
+
children: dur(x.durationMs)
|
|
1586
|
+
})]
|
|
1587
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1588
|
+
className: "mt-0.5 block",
|
|
1589
|
+
children: x.queued ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1590
|
+
className: "inline-flex items-center gap-2 text-meta text-secondary",
|
|
1591
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1592
|
+
"aria-hidden": true,
|
|
1593
|
+
className: "size-1.5 rounded-full bg-disabled"
|
|
1594
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Queued" })]
|
|
1595
|
+
}) : x.running ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1596
|
+
className: "inline-flex items-center gap-2 text-meta text-secondary",
|
|
1597
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1598
|
+
"aria-hidden": true,
|
|
1599
|
+
className: "size-1.5 rounded-full",
|
|
1600
|
+
style: runPulseStyle
|
|
1601
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Running" })]
|
|
1602
|
+
}) : x.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1603
|
+
className: "line-clamp-2 text-meta text-secondary",
|
|
1604
|
+
children: x.error
|
|
1605
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1606
|
+
className: "line-clamp-2 text-meta text-primary",
|
|
1607
|
+
children: x.message || dotLabel(x)
|
|
1608
|
+
})
|
|
1609
|
+
})]
|
|
1610
|
+
})]
|
|
1611
|
+
}) }, x.id))
|
|
1612
|
+
})]
|
|
1613
|
+
})]
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Loop detail PAGE — `/loops/$loopId`. The dedicated page that replaced the old
|
|
1618
|
+
* dashboard modal: a loop header + action toolbar, the unified Files panel (task
|
|
1619
|
+
* file alongside synced artifacts), and the runs timeline. The view owns its own
|
|
1620
|
+
* data + self-poll (ssr:false so the session cookie rides along with its fetches,
|
|
1621
|
+
* like the dashboard loader). Run rows link on to `/loops/$loopId/runs/$runId`.
|
|
1622
|
+
*
|
|
1623
|
+
* Auth-gated exactly like the dashboard (`/`): the loader checks getAuthState +
|
|
1624
|
+
* the session so a logged-out/expired DEEP LINK shows the sign-in CTA instead of
|
|
1625
|
+
* a raw `loop not found` error from the ownership-blocked data fetch.
|
|
1626
|
+
*/
|
|
1627
|
+
function LoopDetailPage() {
|
|
1628
|
+
const { loopId } = Route.useParams();
|
|
1629
|
+
const { auth } = Route.useLoaderData() ?? { auth: { enabled: false } };
|
|
1630
|
+
const { data: session, isPending } = useSession();
|
|
1631
|
+
if (auth?.enabled && !isPending && !session) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SignIn, { callbackURL: `/loops/${loopId}` });
|
|
1632
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopDetailView, { id: loopId });
|
|
1633
|
+
}
|
|
1634
|
+
//#endregion
|
|
1635
|
+
export { LoopDetailPage as component };
|