agentix-cli 0.16.0 → 0.18.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/dist/agent-RDVGL2TW.js +2 -0
- package/dist/audit-Z47AQFFD.js +3 -0
- package/dist/audit-Z47AQFFD.js.map +1 -0
- package/dist/board-dashboard-KX2CNOSY.js +7282 -0
- package/dist/board-dashboard-KX2CNOSY.js.map +1 -0
- package/dist/chunk-34NG7UDX.js +11 -0
- package/dist/chunk-34NG7UDX.js.map +1 -0
- package/dist/{chunk-6MKAXEE5.js → chunk-4ITQP3XL.js} +2 -2
- package/dist/{chunk-6MKAXEE5.js.map → chunk-4ITQP3XL.js.map} +1 -1
- package/dist/chunk-53NRX2NA.js +2 -0
- package/dist/chunk-53NRX2NA.js.map +1 -0
- package/dist/chunk-6PPIFDOQ.js +1742 -0
- package/dist/chunk-6PPIFDOQ.js.map +1 -0
- package/dist/chunk-AFRHKA7B.js +2 -0
- package/dist/chunk-AFRHKA7B.js.map +1 -0
- package/dist/chunk-ASSJ4Z6Z.js +164 -0
- package/dist/chunk-ASSJ4Z6Z.js.map +1 -0
- package/dist/chunk-BLUXL6HR.js +854 -0
- package/dist/chunk-BLUXL6HR.js.map +1 -0
- package/dist/chunk-BTAEYL3P.js +72 -0
- package/dist/chunk-BTAEYL3P.js.map +1 -0
- package/dist/chunk-CDNWCYF7.js +2 -0
- package/dist/chunk-CDNWCYF7.js.map +1 -0
- package/dist/chunk-DJEZ2HVU.js +5 -0
- package/dist/chunk-DJEZ2HVU.js.map +1 -0
- package/dist/{chunk-FLOGK2U7.js → chunk-DSCMCVTV.js} +3 -3
- package/dist/chunk-DSCMCVTV.js.map +1 -0
- package/dist/chunk-EW27SCCC.js +2 -0
- package/dist/chunk-EW27SCCC.js.map +1 -0
- package/dist/chunk-HPDHNIA3.js +2 -0
- package/dist/chunk-IM376SD6.js +13 -0
- package/dist/chunk-IM376SD6.js.map +1 -0
- package/dist/chunk-J4WKMKTP.js +5 -0
- package/dist/{chunk-YI2T5QYB.js.map → chunk-J4WKMKTP.js.map} +1 -1
- package/dist/chunk-JGXE2YNV.js +46 -0
- package/dist/chunk-JGXE2YNV.js.map +1 -0
- package/dist/chunk-JXEPJ3YD.js +118 -0
- package/dist/chunk-JXEPJ3YD.js.map +1 -0
- package/dist/chunk-LY7JGDV7.js +6 -0
- package/dist/chunk-LY7JGDV7.js.map +1 -0
- package/dist/chunk-N67JZAXU.js +2 -0
- package/dist/chunk-N67JZAXU.js.map +1 -0
- package/dist/chunk-SR3X25TN.js +3 -0
- package/dist/chunk-SR3X25TN.js.map +1 -0
- package/dist/{chunk-7MBTMCKY.js → chunk-TRQSP67X.js} +2 -2
- package/dist/chunk-U364GNQX.js +719 -0
- package/dist/chunk-U364GNQX.js.map +1 -0
- package/dist/chunk-VJUTQAPJ.js +2 -0
- package/dist/chunk-VJUTQAPJ.js.map +1 -0
- package/dist/{chunk-HI24KAEY.js → chunk-WJXU5F4Z.js} +3 -4
- package/dist/{chunk-HI24KAEY.js.map → chunk-WJXU5F4Z.js.map} +1 -1
- package/dist/{chunk-QZP65I2H.js → chunk-ZE2YFX4K.js} +2 -2
- package/dist/cli.js +1 -320
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-JBZZ7I7Y.js → compaction-S5H6DTOR.js} +6 -6
- package/dist/compaction-S5H6DTOR.js.map +1 -0
- package/dist/config-UOHEG4BW.js +2 -0
- package/dist/config-mutator-KSEBWKJH.js +2 -0
- package/dist/context-planner-2RGHJHSO.js +17 -0
- package/dist/context-planner-2RGHJHSO.js.map +1 -0
- package/dist/debug-TGAP7E7W.js +2 -0
- package/dist/discover-OATU7SHI.js +4 -0
- package/dist/discover-OATU7SHI.js.map +1 -0
- package/dist/editor-chat-FUBQLMAQ.js +137 -0
- package/dist/editor-chat-FUBQLMAQ.js.map +1 -0
- package/dist/heal-YP7QI7D3.js +2 -0
- package/dist/inbox-O5UB3YQN.js +315 -0
- package/dist/inbox-O5UB3YQN.js.map +1 -0
- package/dist/index.d.ts +3390 -1814
- package/dist/index.js +23 -53
- package/dist/index.js.map +1 -1
- package/dist/ingest-whatsapp-C3O5UPBX.js +6 -0
- package/dist/ingest-whatsapp-C3O5UPBX.js.map +1 -0
- package/dist/loader-BGWA7IRY.js +2 -0
- package/dist/loader-LBHKSXBK.js +2 -0
- package/dist/memory-extract-KKPKAOPH.js +2 -0
- package/dist/processes-7W27G3J3.js +256 -0
- package/dist/processes-7W27G3J3.js.map +1 -0
- package/dist/program-2MDELQLX.js +2 -0
- package/dist/providers-WCQSKTNP.js +2 -0
- package/dist/query-FZAL7AQD.js +2 -0
- package/dist/recipes-LZ56TA3X.js +2 -0
- package/dist/registry-ONJSILH4.js +2 -0
- package/dist/scheduler-XRM2RJIV.js +2 -0
- package/dist/slack-D27BE37Q.js +3 -0
- package/dist/slack-D27BE37Q.js.map +1 -0
- package/dist/store-4UCO56K2.js +2 -0
- package/dist/store-4UCO56K2.js.map +1 -0
- package/dist/{subagent-2SJHSSJS.js → subagent-EPOT3QDM.js} +2 -2
- package/dist/task-store-3NUWLYRY.js +2 -0
- package/dist/task-store-3NUWLYRY.js.map +1 -0
- package/dist/telegram-UWV67KWG.js +3 -0
- package/dist/telegram-UWV67KWG.js.map +1 -0
- package/dist/timers-QB4KYNAE.js +2 -0
- package/dist/timers-QB4KYNAE.js.map +1 -0
- package/dist/types-ECMEC5BZ.js +2 -0
- package/dist/types-ECMEC5BZ.js.map +1 -0
- package/dist/types-TTB6DJUE.js +2 -0
- package/dist/types-TTB6DJUE.js.map +1 -0
- package/dist/usage-dashboard-FQGPPABD.js +803 -0
- package/dist/usage-dashboard-FQGPPABD.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1450 -0
- package/dist/web/workflow-editor.global.js.map +1 -0
- package/dist/whatsapp-KJ2NRKKQ.js +3 -0
- package/dist/whatsapp-KJ2NRKKQ.js.map +1 -0
- package/dist/{whatsapp-state-6D6FHHD2.js → whatsapp-state-3ZVQ6MQD.js} +2 -2
- package/dist/{wiki-UDPYFJBL.js → wiki-OUYKM7MB.js} +2 -2
- package/dist/wiki-OUYKM7MB.js.map +1 -0
- package/dist/workspace-setup-6GDBEORT.js +2 -0
- package/dist/workspace-setup-6GDBEORT.js.map +1 -0
- package/package.json +19 -2
- package/dist/agent-BCDPSPKU.js +0 -2
- package/dist/board-dashboard-LA7WW56M.js +0 -5668
- package/dist/board-dashboard-LA7WW56M.js.map +0 -1
- package/dist/chunk-6U2XOQHU.js +0 -7
- package/dist/chunk-6U2XOQHU.js.map +0 -1
- package/dist/chunk-7FJQGD6U.js +0 -6
- package/dist/chunk-7FJQGD6U.js.map +0 -1
- package/dist/chunk-ABDP7TDL.js +0 -71
- package/dist/chunk-ABDP7TDL.js.map +0 -1
- package/dist/chunk-CXY2RYNI.js +0 -11
- package/dist/chunk-CXY2RYNI.js.map +0 -1
- package/dist/chunk-FIOB3MEX.js +0 -2
- package/dist/chunk-FLOGK2U7.js.map +0 -1
- package/dist/chunk-HVC6FJNP.js +0 -108
- package/dist/chunk-HVC6FJNP.js.map +0 -1
- package/dist/chunk-K7BE4E35.js +0 -138
- package/dist/chunk-K7BE4E35.js.map +0 -1
- package/dist/chunk-KXJSLLSQ.js +0 -199
- package/dist/chunk-KXJSLLSQ.js.map +0 -1
- package/dist/chunk-KXZMYLHQ.js +0 -46
- package/dist/chunk-KXZMYLHQ.js.map +0 -1
- package/dist/chunk-M7HKBG3V.js +0 -2
- package/dist/chunk-M7HKBG3V.js.map +0 -1
- package/dist/chunk-MCJUAE6V.js +0 -6
- package/dist/chunk-MCJUAE6V.js.map +0 -1
- package/dist/chunk-RB7265LO.js +0 -12
- package/dist/chunk-RB7265LO.js.map +0 -1
- package/dist/chunk-YI2T5QYB.js +0 -5
- package/dist/compaction-JBZZ7I7Y.js.map +0 -1
- package/dist/config-LN6JEHMF.js +0 -2
- package/dist/config-mutator-24TH7PHM.js +0 -2
- package/dist/debug-3MEUO2P2.js +0 -2
- package/dist/heal-WKY5I4KB.js +0 -2
- package/dist/loader-PTCXLJEX.js +0 -2
- package/dist/memory-extract-AD7KMRPC.js +0 -2
- package/dist/providers-VUIWO3ZK.js +0 -2
- package/dist/query-PKGYE5HR.js +0 -2
- package/dist/registry-ASC2QS5K.js +0 -2
- package/dist/scheduler-A3TQOCKT.js +0 -2
- package/dist/types-OIFU3KSQ.js +0 -2
- package/dist/usage-dashboard-QX3CF6KK.js +0 -404
- package/dist/usage-dashboard-QX3CF6KK.js.map +0 -1
- package/dist/workspace-setup-ZWTT42XT.js +0 -2
- /package/dist/{agent-BCDPSPKU.js.map → agent-RDVGL2TW.js.map} +0 -0
- /package/dist/{chunk-FIOB3MEX.js.map → chunk-HPDHNIA3.js.map} +0 -0
- /package/dist/{chunk-7MBTMCKY.js.map → chunk-TRQSP67X.js.map} +0 -0
- /package/dist/{chunk-QZP65I2H.js.map → chunk-ZE2YFX4K.js.map} +0 -0
- /package/dist/{config-LN6JEHMF.js.map → config-UOHEG4BW.js.map} +0 -0
- /package/dist/{config-mutator-24TH7PHM.js.map → config-mutator-KSEBWKJH.js.map} +0 -0
- /package/dist/{debug-3MEUO2P2.js.map → debug-TGAP7E7W.js.map} +0 -0
- /package/dist/{heal-WKY5I4KB.js.map → heal-YP7QI7D3.js.map} +0 -0
- /package/dist/{loader-PTCXLJEX.js.map → loader-BGWA7IRY.js.map} +0 -0
- /package/dist/{memory-extract-AD7KMRPC.js.map → loader-LBHKSXBK.js.map} +0 -0
- /package/dist/{providers-VUIWO3ZK.js.map → memory-extract-KKPKAOPH.js.map} +0 -0
- /package/dist/{query-PKGYE5HR.js.map → program-2MDELQLX.js.map} +0 -0
- /package/dist/{registry-ASC2QS5K.js.map → providers-WCQSKTNP.js.map} +0 -0
- /package/dist/{scheduler-A3TQOCKT.js.map → query-FZAL7AQD.js.map} +0 -0
- /package/dist/{types-OIFU3KSQ.js.map → recipes-LZ56TA3X.js.map} +0 -0
- /package/dist/{wiki-UDPYFJBL.js.map → registry-ONJSILH4.js.map} +0 -0
- /package/dist/{workspace-setup-ZWTT42XT.js.map → scheduler-XRM2RJIV.js.map} +0 -0
- /package/dist/{subagent-2SJHSSJS.js.map → subagent-EPOT3QDM.js.map} +0 -0
- /package/dist/{whatsapp-state-6D6FHHD2.js.map → whatsapp-state-3ZVQ6MQD.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/ui/tokens.ts","../src/daemon/ui/components.css.ts","../src/daemon/topbar.ts","../src/daemon/ui/shell.ts","../src/daemon/ui/util.ts","../src/daemon/ui/components.ts"],"sourcesContent":["// --- Design tokens for every dashboard surface ---\n//\n// SINGLE source of truth. Previously each page file (live, boards, admin,\n// graph, agent, setup) carried its own copy of :root + [data-theme=light] +\n// [data-theme=crt] — ~60 lines of CSS duplicated 6 times. Now they import\n// AX_TOKENS_CSS and drop it in their <style> block once.\n//\n// Guideline for adding tokens: every token must be page-neutral. Page-\n// specific padding/gaps belong in the page's own CSS file, not here.\n\nexport const AX_TOKENS_CSS = `:root {\n --ax-bg: oklch(0.16 0.010 265);\n --ax-bg-elev: oklch(0.19 0.012 265);\n --ax-surface: oklch(0.21 0.012 265);\n --ax-surface-2: oklch(0.24 0.014 265);\n --ax-surface-3: oklch(0.27 0.016 265);\n --ax-border: oklch(0.29 0.014 265);\n --ax-border-2: oklch(0.35 0.016 265);\n --ax-text: oklch(0.95 0.005 265);\n --ax-text-2: oklch(0.80 0.008 265);\n --ax-muted: oklch(0.60 0.010 265);\n --ax-accent: oklch(0.78 0.13 165);\n --ax-accent-2: oklch(0.55 0.11 165);\n --ax-warn: oklch(0.80 0.14 75);\n --ax-err: oklch(0.68 0.19 25);\n --ax-info: oklch(0.78 0.10 220);\n --ax-radius: 6px;\n --ax-radius-lg: 8px;\n --ax-radius-sm: 5px;\n --ax-pad: 16px;\n --ax-pad-sm: 10px;\n --ax-gap: 12px;\n --ax-font: \"IBM Plex Sans\", -apple-system, \"Segoe UI\", sans-serif;\n --ax-mono: \"IBM Plex Mono\", ui-monospace, \"SF Mono\", Consolas, monospace;\n --ax-fs: 13px;\n --ax-fs-sm: 12px;\n --ax-fs-xs: 11px;\n /* Legacy aliases so older CSS blocks keep rendering until they're ported. */\n --bg: var(--ax-bg);\n --card: var(--ax-surface);\n --node: var(--ax-bg-elev);\n --border: var(--ax-border);\n --text: var(--ax-text);\n --muted: var(--ax-muted);\n --accent: var(--ax-accent);\n --green: var(--ax-accent);\n --yellow: var(--ax-warn);\n --red: var(--ax-err);\n --blue: var(--ax-info);\n --gray: var(--ax-muted);\n color-scheme: dark;\n}\n[data-theme=\"light\"] {\n --ax-bg: oklch(0.98 0.002 265);\n --ax-bg-elev: oklch(0.96 0.003 265);\n --ax-surface: oklch(0.99 0.002 265);\n --ax-surface-2: oklch(0.955 0.003 265);\n --ax-surface-3: oklch(0.92 0.004 265);\n --ax-border: oklch(0.88 0.006 265);\n --ax-border-2: oklch(0.78 0.008 265);\n --ax-text: oklch(0.22 0.010 265);\n --ax-text-2: oklch(0.36 0.010 265);\n --ax-muted: oklch(0.54 0.010 265);\n --ax-accent: oklch(0.55 0.14 165);\n color-scheme: light;\n}\n[data-theme=\"crt\"] {\n --ax-bg: #05140a;\n --ax-bg-elev: #061a0d;\n --ax-surface: #08201f;\n --ax-surface-2: #0b2922;\n --ax-surface-3: #0e3329;\n --ax-border: #164a30;\n --ax-border-2: #1f6a44;\n --ax-text: #b7ffcc;\n --ax-text-2: #83e3a8;\n --ax-muted: #4f9a73;\n --ax-accent: #6dff9e;\n --ax-font: \"IBM Plex Mono\", ui-monospace, monospace;\n}\n\n/* Base element resets shared across every page. */\n* { box-sizing: border-box; }\nhtml, body {\n margin: 0; min-height: 100vh;\n background: var(--ax-bg); color: var(--ax-text);\n font-family: var(--ax-font); font-size: var(--ax-fs);\n -webkit-font-smoothing: antialiased; font-feature-settings: \"ss01\", \"cv01\";\n}\npre, code, .ax-mono {\n font-family: var(--ax-mono); font-variant-numeric: tabular-nums;\n letter-spacing: -0.01em;\n}\na { color: var(--ax-accent); text-decoration: none; }\na:hover { text-decoration: underline; }\n.ax-muted { color: var(--ax-muted); }\n.ax-accent { color: var(--ax-accent); }`\n","// --- Shared component CSS ---\n//\n// Styles for the primitives in components.ts. Kept separate from tokens so a\n// page can opt-in to only what it uses (setup-wizard doesn't need stat-strip\n// styles, for example). Most pages just emit both blocks together.\n//\n// All class names are prefixed `ax-` — they won't collide with host-app or\n// per-page styles.\n\nexport const AX_COMPONENTS_CSS = `\n/* --- Dots --- */\n.ax-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--ax-muted); }\n.ax-dot--ok, .ax-dot--live { background: var(--ax-accent); }\n.ax-dot--warn { background: var(--ax-warn); }\n.ax-dot--err { background: var(--ax-err); }\n.ax-dot--off { background: var(--ax-border-2); }\n.ax-dot--pulse {\n box-shadow: 0 0 0 0 currentColor;\n animation: ax-pulse 1.8s infinite;\n color: var(--ax-accent);\n}\n@keyframes ax-pulse {\n 0% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--ax-accent) 70%, transparent); }\n 70% { box-shadow: 0 0 0 6px color-mix(in oklch, var(--ax-accent) 0%, transparent); }\n 100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--ax-accent) 0%, transparent); }\n}\n\n/* --- Badges --- */\n.ax-badge {\n display: inline-flex; align-items: center; gap: 4px;\n padding: 1px 7px; border-radius: 3px; font-size: var(--ax-fs-xs); line-height: 16px;\n border: 1px solid var(--ax-border-2); color: var(--ax-text-2); background: transparent;\n}\n.ax-badge--mono {\n font-family: var(--ax-mono); letter-spacing: 0.02em;\n text-transform: uppercase; font-size: 10px;\n}\n.ax-badge--accent {\n color: var(--ax-accent);\n border-color: color-mix(in oklch, var(--ax-accent) 50%, transparent);\n}\n.ax-badge--live {\n color: var(--ax-accent);\n background: color-mix(in oklch, var(--ax-accent) 14%, transparent);\n border-color: color-mix(in oklch, var(--ax-accent) 60%, transparent);\n font-weight: 600; letter-spacing: 0.06em;\n}\n.ax-badge--warn {\n color: var(--ax-warn);\n border-color: color-mix(in oklch, var(--ax-warn) 50%, transparent);\n}\n.ax-badge--err {\n color: var(--ax-err);\n border-color: color-mix(in oklch, var(--ax-err) 50%, transparent);\n}\n.ax-badge--ghost {\n color: var(--ax-muted); border-color: var(--ax-border); background: var(--ax-surface-2);\n}\n\n/* --- Buttons --- */\n.ax-btn {\n background: transparent; color: var(--ax-text-2);\n border: 1px solid var(--ax-border-2); border-radius: var(--ax-radius);\n padding: 7px 14px; font: inherit; font-size: var(--ax-fs); cursor: pointer;\n transition: border-color 0.1s, color 0.1s, background 0.1s;\n line-height: 1.4;\n}\n.ax-btn:hover:not(:disabled) { color: var(--ax-text); border-color: var(--ax-accent); }\n.ax-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n\n.ax-btn--primary {\n background: color-mix(in oklch, var(--ax-accent) 15%, var(--ax-surface));\n color: var(--ax-accent);\n border-color: color-mix(in oklch, var(--ax-accent) 50%, var(--ax-border-2));\n font-weight: 600;\n}\n.ax-btn--primary:hover:not(:disabled) {\n background: color-mix(in oklch, var(--ax-accent) 25%, var(--ax-surface));\n border-color: var(--ax-accent);\n}\n\n.ax-btn--ghost {\n background: transparent; color: var(--ax-muted);\n border-color: var(--ax-border);\n}\n\n.ax-btn--danger {\n color: var(--ax-err);\n border-color: color-mix(in oklch, var(--ax-err) 50%, var(--ax-border-2));\n}\n.ax-btn--danger:hover:not(:disabled) {\n background: color-mix(in oklch, var(--ax-err) 12%, var(--ax-surface));\n border-color: var(--ax-err);\n}\n\n/* --- Chips (read-only labels used in kanban filters, etc.) --- */\n.ax-chip {\n display: inline-block; padding: 0 6px; font-family: var(--ax-mono);\n font-size: 10px; line-height: 16px; border-radius: 3px; color: var(--ax-muted);\n border: 1px solid var(--ax-border); background: var(--ax-surface);\n}\n\n/* --- Cards --- */\n.ax-card {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius); padding: var(--ax-pad);\n}\n\n/* Step cards used by the setup wizard / onboarding-style flows. */\n.ax-step {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: 8px; padding: 20px 22px; margin: 14px 0;\n}\n.ax-step__head {\n font-size: var(--ax-fs-xs); font-weight: 600; text-transform: uppercase;\n letter-spacing: 0.08em; color: var(--ax-muted);\n margin: 0 0 14px; display: flex; align-items: center; gap: 10px;\n}\n.ax-step__num {\n width: 22px; height: 22px; border-radius: 4px;\n background: color-mix(in oklch, var(--ax-accent) 15%, var(--ax-surface));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 45%, var(--ax-border-2));\n color: var(--ax-accent);\n display: inline-flex; align-items: center; justify-content: center;\n font-family: var(--ax-mono); font-size: 11px; font-weight: 700;\n letter-spacing: 0; text-transform: none;\n}\n\n/* --- Stat strip (the \"COUNT / label\" cards across the top of Live) --- */\n.ax-statstrip {\n display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));\n gap: var(--ax-gap); padding: var(--ax-pad) var(--ax-pad) 0;\n}\n.ax-stat {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius); padding: 14px 16px;\n}\n.ax-stat__label {\n font-size: var(--ax-fs-xs); text-transform: uppercase; letter-spacing: 0.06em;\n color: var(--ax-muted); display: flex; align-items: center; gap: 6px;\n}\n.ax-stat__value {\n font-size: 24px; font-weight: 600; margin-top: 4px; letter-spacing: -0.02em;\n font-family: var(--ax-mono); font-variant-numeric: tabular-nums;\n}\n.ax-stat__sub {\n font-size: var(--ax-fs-xs); color: var(--ax-muted); margin-top: 2px;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.ax-stat--live .ax-stat__value { color: var(--ax-accent); }\n.ax-stat--warn .ax-stat__value { color: var(--ax-warn); }\n.ax-stat--err .ax-stat__value { color: var(--ax-err); }\n\n/* --- Forms --- */\n.ax-field { margin: 10px 0 0; }\n.ax-field:first-child { margin-top: 0; }\n.ax-field label {\n display: block; margin-bottom: 5px;\n font-size: var(--ax-fs-xs); color: var(--ax-muted);\n text-transform: uppercase; letter-spacing: 0.06em; font-weight: 500;\n}\n.ax-field label .ax-hint {\n text-transform: none; letter-spacing: 0; color: var(--ax-muted);\n font-weight: 400; font-size: var(--ax-fs-xs); margin-left: 6px;\n}\n.ax-field input, .ax-field textarea, .ax-field select {\n width: 100%;\n background: var(--ax-bg); color: var(--ax-text);\n border: 1px solid var(--ax-border-2); border-radius: var(--ax-radius);\n padding: 8px 10px; font: inherit; font-size: var(--ax-fs);\n transition: border-color 0.1s ease;\n}\n.ax-field input:focus, .ax-field textarea:focus, .ax-field select:focus {\n outline: none; border-color: var(--ax-accent);\n box-shadow: 0 0 0 2px color-mix(in oklch, var(--ax-accent) 18%, transparent);\n}\n.ax-field textarea { resize: vertical; min-height: 72px; font-family: var(--ax-font); }\n.ax-field input::placeholder, .ax-field textarea::placeholder {\n color: var(--ax-muted); opacity: 0.7;\n}\n\n.ax-row { display: flex; gap: 12px; }\n.ax-row > * { flex: 1; }\n\n/* --- Section titles shared across pages --- */\n.ax-section-title {\n display: flex; justify-content: space-between; align-items: center;\n margin-bottom: 12px;\n}\n.ax-section-title__label {\n font-size: var(--ax-fs-xs); text-transform: uppercase;\n letter-spacing: 0.08em; color: var(--ax-muted);\n}\n.ax-section-title__right {\n display: flex; align-items: center; gap: 8px;\n font-size: var(--ax-fs-xs); color: var(--ax-muted);\n}\n\n/* ============================================================\n * Settings-page design system (also reusable on other pages).\n * Ported from the Stitch redesign mockup so the primitives can\n * be consumed by any page — not just /admin. Sections:\n * page-head — kicker + h1 + lead paragraph\n * health-strip — 4-up status cards with coloured dot\n * wit — dismissable \"what is this?\" info banner\n * section-head — icon chip + title + lead + right-aligned CTA\n * row-card — list row with avatar + info + actions + expand\n * avatar — coloured-initial square (teal/coral/amber/blue)\n * pill — small status pill with coloured dot (chip-flavor)\n * connectors — 2-up grid of connector cards\n * tab-count — mono count bubble on a tab\n * toast — bottom-right ephemeral notification\n * modal — simple centred dialog + backdrop\n * sec-label — uppercase mini-heading with optional CTA\n * ============================================================ */\n\n/* --- Page head --- */\n.ax-page-head {\n max-width: 1040px; margin: 0 auto; padding: 28px 24px 12px;\n}\n.ax-page-head .ax-kicker {\n display: inline-block; font-size: 10px; letter-spacing: 0.1em;\n color: var(--ax-muted); text-transform: uppercase; margin-bottom: 6px;\n font-family: var(--ax-mono);\n}\n.ax-page-head h1 {\n font-size: 22px; font-weight: 600; letter-spacing: -0.015em;\n margin: 0 0 4px; display: flex; align-items: center; gap: 10px;\n}\n.ax-page-head .ax-lead {\n color: var(--ax-text-2); margin: 0; font-size: 13px;\n max-width: 640px; line-height: 1.6;\n}\n.ax-page-head .ax-lead code {\n font-family: var(--ax-mono); font-size: 11.5px;\n background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px;\n}\n\n/* --- Health strip --- */\n.ax-health-strip {\n max-width: 1040px; margin: 18px auto 22px; padding: 0 24px;\n display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;\n}\n.ax-health-card {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-sm); padding: 12px 14px;\n display: flex; align-items: center; gap: 11px;\n transition: border-color 120ms;\n}\n.ax-health-card:hover { border-color: var(--ax-border-2); }\n.ax-health-card .ax-hc-dot {\n width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;\n}\n.ax-health-card .ax-hc-dot--ok {\n background: var(--ax-accent);\n box-shadow: 0 0 8px color-mix(in oklch, var(--ax-accent) 50%, transparent);\n}\n.ax-health-card .ax-hc-dot--warn { background: var(--ax-warn); }\n.ax-health-card .ax-hc-dot--off { background: var(--ax-border-2); }\n.ax-health-card .ax-hc-txt { flex: 1; min-width: 0; }\n.ax-health-card .ax-hc-num {\n font-size: 17px; font-weight: 600; letter-spacing: -0.015em;\n}\n.ax-health-card .ax-hc-lbl {\n font-size: 11px; color: var(--ax-muted); margin-top: 1px;\n}\n\n/* --- \"What is this?\" info banner (dismissable) --- */\n.ax-wit {\n background: color-mix(in oklch, var(--ax-info) 6%, var(--ax-bg-elev));\n border: 1px solid color-mix(in oklch, var(--ax-info) 22%, var(--ax-border));\n border-radius: var(--ax-radius-sm); padding: 12px 14px; margin-bottom: 18px;\n font-size: 12.5px; color: var(--ax-text-2); line-height: 1.6;\n display: flex; gap: 10px; align-items: flex-start;\n}\n.ax-wit__icon {\n width: 18px; height: 18px; border-radius: 50%;\n background: color-mix(in oklch, var(--ax-info) 18%, transparent);\n color: var(--ax-info);\n display: grid; place-items: center;\n font-size: 11px; font-weight: 600; flex-shrink: 0; margin-top: 1px;\n}\n.ax-wit b { color: var(--ax-text); font-weight: 600; }\n.ax-wit code {\n font-family: var(--ax-mono); font-size: 11.5px;\n background: var(--ax-bg); padding: 1px 5px; border-radius: 3px;\n border: 1px solid var(--ax-border);\n}\n.ax-wit .ax-wit__dismiss {\n background: transparent; border: 0; color: var(--ax-muted);\n font-size: 16px; cursor: pointer; margin-left: auto;\n padding: 0 4px; line-height: 1;\n}\n.ax-wit .ax-wit__dismiss:hover { color: var(--ax-text); }\n\n/* --- Section head (icon + title + lead + right CTA) --- */\n.ax-section-head {\n display: flex; align-items: flex-start; gap: 16px; margin-bottom: 18px;\n}\n.ax-section-head__icon {\n width: 44px; height: 44px; border-radius: 10px;\n background: color-mix(in oklch, var(--ax-accent) 10%, var(--ax-surface));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 25%, var(--ax-border));\n display: grid; place-items: center; flex-shrink: 0;\n color: var(--ax-accent);\n}\n.ax-section-head__icon svg { width: 22px; height: 22px; }\n.ax-section-head__text { flex: 1; }\n.ax-section-head h2 {\n font-size: 18px; margin: 0 0 3px;\n font-weight: 600; letter-spacing: -0.01em;\n}\n.ax-section-head .ax-lead {\n color: var(--ax-text-2); margin: 0; font-size: 13px; line-height: 1.55;\n max-width: 640px;\n}\n\n/* --- Row cards (collapsible list items) --- */\n.ax-stack { display: flex; flex-direction: column; gap: 10px; }\n.ax-row-card {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); padding: 14px 16px;\n transition: border-color 160ms, transform 160ms;\n}\n.ax-row-card:hover { border-color: var(--ax-border-2); }\n.ax-row-card__top { display: flex; align-items: center; gap: 12px; }\n.ax-row-card__info { flex: 1; min-width: 0; }\n.ax-row-card__info .ax-name {\n font-size: 14px; font-weight: 600; letter-spacing: -0.005em;\n display: flex; align-items: center; gap: 8px;\n}\n.ax-row-card__info .ax-slug {\n font-family: var(--ax-mono); font-size: 11px;\n color: var(--ax-muted); margin-left: 4px;\n}\n.ax-row-card__info .ax-sub {\n font-size: 12px; color: var(--ax-text-2); margin-top: 3px;\n line-height: 1.5; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;\n}\n.ax-row-card__actions { display: flex; gap: 6px; align-items: center; }\n.ax-row-card__details {\n display: none; margin-top: 14px; padding-top: 14px;\n border-top: 1px dashed var(--ax-border);\n font-size: 12.5px; color: var(--ax-text-2); line-height: 1.6;\n}\n.ax-row-card.is-open .ax-row-card__details { display: block; }\n.ax-row-card.is-open .ax-chev { transform: rotate(180deg); }\n.ax-chev { transition: transform 160ms; }\n\n.ax-detail-grid {\n display: grid; grid-template-columns: repeat(2, 1fr);\n gap: 12px 20px; margin-bottom: 12px;\n}\n.ax-detail-grid dt {\n font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;\n color: var(--ax-muted); margin-bottom: 3px; font-family: var(--ax-mono);\n}\n.ax-detail-grid dd {\n margin: 0; font-size: 12.5px; color: var(--ax-text);\n}\n.ax-triggers { display: flex; gap: 4px; flex-wrap: wrap; }\n.ax-trigger-pill {\n font-family: var(--ax-mono); font-size: 11px;\n padding: 2px 8px; background: var(--ax-bg);\n border: 1px solid var(--ax-border); border-radius: 4px;\n color: var(--ax-text);\n}\n\n/* --- Avatars (coloured initial square) --- */\n.ax-avatar {\n width: 36px; height: 36px; border-radius: 9px; flex-shrink: 0;\n background: var(--ax-surface-2); border: 1px solid var(--ax-border-2);\n display: grid; place-items: center;\n font-weight: 600; font-size: 14px; letter-spacing: -0.01em;\n color: var(--ax-text);\n}\n.ax-avatar--teal {\n background: color-mix(in oklch, var(--ax-accent) 18%, var(--ax-surface));\n border-color: color-mix(in oklch, var(--ax-accent) 35%, var(--ax-border));\n color: var(--ax-accent);\n}\n.ax-avatar--coral {\n background: color-mix(in oklch, var(--ax-err) 14%, var(--ax-surface));\n border-color: color-mix(in oklch, var(--ax-err) 30%, var(--ax-border));\n color: var(--ax-err);\n}\n.ax-avatar--amber {\n background: color-mix(in oklch, var(--ax-warn) 14%, var(--ax-surface));\n border-color: color-mix(in oklch, var(--ax-warn) 30%, var(--ax-border));\n color: var(--ax-warn);\n}\n.ax-avatar--blue {\n background: color-mix(in oklch, var(--ax-info) 14%, var(--ax-surface));\n border-color: color-mix(in oklch, var(--ax-info) 30%, var(--ax-border));\n color: var(--ax-info);\n}\n\n/* --- Pills (status indicator with coloured dot) ---\n *\n * Different from .ax-badge: pills have a rounded (10px) shape, a small\n * leading coloured dot, and stronger semantics (ok/warn/off/info). Use\n * for \"live\", \"idle\", \"receiving\", \"stale\" states next to a name.\n */\n.ax-pill {\n display: inline-flex; align-items: center; gap: 5px;\n font-size: 11px; padding: 2px 8px; line-height: 16px;\n border-radius: 10px; background: var(--ax-surface-2);\n color: var(--ax-text-2); border: 1px solid var(--ax-border);\n white-space: nowrap;\n}\n.ax-pill .ax-pill__dot {\n width: 6px; height: 6px; border-radius: 50%; background: currentColor;\n}\n.ax-pill--ok {\n background: color-mix(in oklch, var(--ax-accent) 14%, transparent);\n color: var(--ax-accent);\n border-color: color-mix(in oklch, var(--ax-accent) 30%, transparent);\n}\n.ax-pill--warn {\n background: color-mix(in oklch, var(--ax-warn) 14%, transparent);\n color: var(--ax-warn);\n border-color: color-mix(in oklch, var(--ax-warn) 30%, transparent);\n}\n.ax-pill--off { color: var(--ax-muted); }\n.ax-pill--info {\n background: color-mix(in oklch, var(--ax-info) 14%, transparent);\n color: var(--ax-info);\n border-color: color-mix(in oklch, var(--ax-info) 30%, transparent);\n}\n\n/* --- Connectors grid (Channels tab) --- */\n.ax-connectors {\n display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;\n}\n.ax-connector {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); padding: 18px 18px 16px;\n cursor: pointer; position: relative;\n transition: border-color 160ms, transform 160ms, background 160ms;\n}\n.ax-connector:hover { border-color: var(--ax-border-2); background: var(--ax-surface-2); }\n.ax-connector.is-on {\n border-color: color-mix(in oklch, var(--ax-accent) 40%, var(--ax-border));\n}\n.ax-connector.is-active {\n border-color: var(--ax-accent);\n background: color-mix(in oklch, var(--ax-accent) 8%, var(--ax-surface));\n}\n.ax-connector__top {\n display: flex; align-items: center; gap: 12px; margin-bottom: 10px;\n}\n.ax-connector__logo {\n width: 40px; height: 40px; border-radius: 10px;\n display: grid; place-items: center;\n font-weight: 700; font-size: 15px; letter-spacing: -0.02em; flex-shrink: 0;\n background: var(--ax-surface-2); border: 1px solid var(--ax-border-2);\n color: var(--ax-text); font-family: var(--ax-mono);\n}\n.ax-connector__meta { flex: 1; }\n.ax-connector__name {\n font-weight: 600; font-size: 14px; letter-spacing: -0.005em;\n}\n.ax-connector__sub {\n font-size: 11.5px; color: var(--ax-muted); margin-top: 2px;\n}\n.ax-connector__status { margin-left: auto; }\n.ax-connector__desc {\n font-size: 12.5px; color: var(--ax-text-2); line-height: 1.5; min-height: 36px;\n}\n.ax-connector__foot {\n margin-top: 12px; display: flex; align-items: center; justify-content: space-between;\n font-size: 11.5px; color: var(--ax-muted);\n}\n.ax-connector__cta {\n color: var(--ax-accent); font-weight: 500;\n display: inline-flex; align-items: center; gap: 4px;\n}\n.ax-connector__cta svg { width: 12px; height: 12px; }\n\n/* --- Tab counts + warn indicator --- */\n.ax-tab-count {\n font-family: var(--ax-mono); font-size: 10px;\n padding: 1px 6px; background: var(--ax-surface-2);\n color: var(--ax-muted); border-radius: 9px; line-height: 14px;\n margin-left: 6px;\n}\n.is-active .ax-tab-count {\n background: color-mix(in oklch, var(--ax-accent) 18%, transparent);\n color: var(--ax-accent);\n}\n.ax-tab-warn {\n width: 6px; height: 6px; border-radius: 50%; background: var(--ax-warn);\n display: inline-block; margin-left: 6px;\n}\n\n/* --- Section mini-labels --- */\n.ax-sec-label {\n display: flex; align-items: center; justify-content: space-between;\n margin: 22px 0 10px;\n}\n.ax-sec-label h3 {\n font-size: 12px; font-weight: 600; letter-spacing: 0.06em;\n text-transform: uppercase; color: var(--ax-muted);\n margin: 0; font-family: var(--ax-mono);\n}\n\n/* --- Toast --- */\n.ax-toast {\n position: fixed; bottom: 24px; right: 24px;\n background: var(--ax-surface-2);\n border: 1px solid var(--ax-accent); color: var(--ax-text);\n padding: 10px 16px; border-radius: 6px; font-size: 12.5px;\n display: flex; align-items: center; gap: 10px;\n box-shadow: 0 10px 30px rgba(0,0,0,0.3);\n opacity: 0; transform: translateY(10px); pointer-events: none;\n transition: opacity 200ms, transform 200ms; z-index: 100;\n}\n.ax-toast.is-show { opacity: 1; transform: none; }\n.ax-toast__icon { color: var(--ax-accent); }\n\n/* --- Modal (centred dialog + backdrop) --- */\n.ax-modal-bd {\n position: fixed; inset: 0;\n background: color-mix(in oklch, var(--ax-bg) 60%, black);\n display: none; align-items: center; justify-content: center;\n z-index: 200; padding: 24px;\n}\n.ax-modal-bd.is-show { display: flex; }\n.ax-modal {\n width: min(560px, 94vw); max-height: 86vh; overflow: auto;\n background: var(--ax-surface); border: 1px solid var(--ax-border-2);\n border-radius: 12px; padding: 0;\n box-shadow: 0 20px 60px rgba(0,0,0,0.5);\n}\n.ax-modal > header {\n padding: 18px 22px 10px; border-bottom: 1px solid var(--ax-border);\n display: flex; align-items: center; gap: 10px;\n}\n.ax-modal > header h3 {\n margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -0.01em; flex: 1;\n}\n.ax-modal > header .ax-modal__close {\n background: transparent; border: 0; color: var(--ax-muted);\n font-size: 22px; cursor: pointer; line-height: 1; padding: 0 6px;\n}\n.ax-modal__body { padding: 18px 22px; }\n.ax-modal__foot {\n padding: 12px 22px 18px;\n display: flex; justify-content: flex-end; gap: 8px;\n border-top: 1px solid var(--ax-border);\n}\n\n/* --- Mesh hero (enable toggle + SVG network viz) --- */\n.ax-mesh-hero {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); padding: 20px;\n display: grid; grid-template-columns: 1fr 240px; gap: 24px;\n align-items: center; margin-bottom: 18px;\n}\n.ax-mesh-hero h3 { margin: 0 0 4px; font-size: 15px; font-weight: 600; }\n.ax-mesh-hero p { margin: 0 0 12px; font-size: 12.5px; color: var(--ax-text-2); line-height: 1.5; }\n.ax-mesh-viz { position: relative; width: 240px; height: 140px; }\n.ax-mesh-dot {\n position: absolute; width: 44px; height: 44px; border-radius: 50%;\n background: var(--ax-surface-2); border: 2px solid var(--ax-border-2);\n display: grid; place-items: center;\n font-family: var(--ax-mono); font-size: 10px; font-weight: 600;\n color: var(--ax-text-2);\n}\n.ax-mesh-dot.self {\n background: color-mix(in oklch, var(--ax-accent) 18%, var(--ax-surface));\n border-color: var(--ax-accent); color: var(--ax-accent);\n}\n.ax-mesh-dot.empty {\n border-style: dashed; color: var(--ax-muted); font-size: 14px;\n}\n.ax-mesh-wires { position: absolute; inset: 0; }\n.ax-mesh-wires line { stroke: var(--ax-border-2); stroke-width: 1.5; stroke-dasharray: 3 3; }\n.ax-mesh-wires line.live { stroke: var(--ax-accent); stroke-dasharray: 0; opacity: 0.6; }\n\n/* Pill-shaped toggle (used for the mesh enable switch + similar binary actions). */\n.ax-mesh-toggle {\n display: inline-flex; align-items: center; gap: 10px;\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: 20px; padding: 4px 14px 4px 5px;\n cursor: pointer; user-select: none; font-size: 12px;\n}\n.ax-mesh-switch {\n width: 32px; height: 18px; background: var(--ax-surface-3);\n border-radius: 9px; position: relative; transition: background 160ms;\n}\n.ax-mesh-switch::after {\n content: \"\"; position: absolute; top: 2px; left: 2px;\n width: 14px; height: 14px; border-radius: 50%;\n background: var(--ax-text-2); transition: all 160ms;\n}\n.ax-mesh-toggle.is-on .ax-mesh-switch {\n background: color-mix(in oklch, var(--ax-accent) 80%, transparent);\n}\n.ax-mesh-toggle.is-on .ax-mesh-switch::after {\n left: 16px; background: var(--ax-bg);\n}\n\n/* --- Schedule builder: sentence-builder + pill picks + day picker --- */\n.ax-builder {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); padding: 20px;\n}\n.ax-builder h3 {\n margin: 0 0 4px; font-size: 15px; font-weight: 600; letter-spacing: -0.005em;\n}\n.ax-builder__hint { margin: 0 0 16px; font-size: 12.5px; color: var(--ax-muted); }\n.ax-builder__lbl {\n display: block; font-size: 11px; color: var(--ax-muted);\n letter-spacing: 0.03em; text-transform: uppercase; margin: 12px 0 5px;\n font-weight: 500;\n}\n.ax-builder__lbl .opt {\n text-transform: none; letter-spacing: 0; font-weight: 400;\n opacity: 0.75; margin-left: 6px;\n}\n.ax-builder__inp {\n width: 100%; background: var(--ax-bg); color: var(--ax-text);\n border: 1px solid var(--ax-border); border-radius: 5px;\n padding: 8px 11px; font: inherit; font-size: 13px;\n transition: border-color 120ms, box-shadow 120ms;\n}\n.ax-builder__inp:focus {\n outline: none; border-color: var(--ax-accent);\n box-shadow: 0 0 0 3px color-mix(in oklch, var(--ax-accent) 18%, transparent);\n}\ntextarea.ax-builder__inp { min-height: 80px; line-height: 1.5; resize: vertical; }\n\n.ax-mode-switch {\n display: inline-flex; background: var(--ax-surface-2);\n border: 1px solid var(--ax-border); border-radius: 6px; padding: 3px;\n margin-bottom: 14px;\n}\n.ax-mode-switch button {\n background: transparent; border: 0; color: var(--ax-muted);\n padding: 6px 14px; font: inherit; font-size: 12px; cursor: pointer;\n border-radius: 4px; font-weight: 500;\n}\n.ax-mode-switch button.is-active {\n background: var(--ax-surface); color: var(--ax-text);\n}\n\n.ax-sentence {\n display: flex; align-items: center; gap: 8px;\n font-size: 16px; line-height: 1.9; flex-wrap: wrap;\n margin-bottom: 14px; padding: 14px 16px;\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-sm);\n}\n.ax-sentence > span { color: var(--ax-text-2); }\n.ax-pill-pick {\n background: var(--ax-surface); border: 1px solid var(--ax-border-2);\n color: var(--ax-accent); padding: 3px 10px; border-radius: 6px;\n font-size: 14px; font-family: inherit; cursor: pointer; font-weight: 500;\n}\n.ax-pill-pick:hover { background: var(--ax-surface-2); border-color: var(--ax-accent); }\n.ax-pill-pick--sm { font-size: 13px; padding: 3px 8px; }\n\n.ax-day-picker { display: flex; gap: 4px; }\n.ax-day-picker button {\n width: 30px; height: 30px; border-radius: 6px;\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n color: var(--ax-muted); font: inherit; font-size: 11px; font-weight: 500;\n cursor: pointer;\n}\n.ax-day-picker button:hover { border-color: var(--ax-border-2); color: var(--ax-text); }\n.ax-day-picker button.is-active {\n background: color-mix(in oklch, var(--ax-accent) 20%, var(--ax-surface));\n border-color: var(--ax-accent); color: var(--ax-accent);\n}\n\n.ax-preview-box {\n background: color-mix(in oklch, var(--ax-accent) 5%, var(--ax-bg-elev));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 20%, var(--ax-border));\n border-radius: var(--ax-radius-sm); padding: 12px 14px; margin-top: 12px;\n font-size: 12.5px; color: var(--ax-text-2);\n display: flex; align-items: center; gap: 10px;\n}\n.ax-preview-box svg { width: 15px; height: 15px; color: var(--ax-accent); flex-shrink: 0; }\n.ax-preview-box b { color: var(--ax-text); font-weight: 600; }\n\n.ax-cron-hints {\n display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px 18px;\n margin-top: 10px; font-size: 11.5px; color: var(--ax-muted);\n}\n.ax-cron-hints code {\n font-family: var(--ax-mono); color: var(--ax-text);\n background: var(--ax-bg); border: 1px solid var(--ax-border);\n padding: 1px 6px; border-radius: 3px; margin-right: 6px;\n}\n\n/* --- JSON tree viewer (Advanced tab) --- */\n.ax-jv-toolbar {\n display: flex; align-items: center; gap: 8px;\n padding: 8px 12px; background: var(--ax-bg-elev);\n border: 1px solid var(--ax-border); border-bottom: 0;\n border-radius: var(--ax-radius) var(--ax-radius) 0 0;\n}\n.ax-jv-toolbar .ax-jv-spacer { flex: 1; }\n.ax-jv-toolbar .ax-jv-seg {\n display: inline-flex; background: var(--ax-surface);\n border: 1px solid var(--ax-border); border-radius: 5px; padding: 2px;\n}\n.ax-jv-toolbar .ax-jv-seg button {\n background: transparent; border: 0; color: var(--ax-muted);\n padding: 4px 10px; font: inherit; font-size: 11px;\n cursor: pointer; border-radius: 4px;\n}\n.ax-jv-toolbar .ax-jv-seg button.is-active { background: var(--ax-surface-2); color: var(--ax-text); }\n.ax-jv-search {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: 5px; padding: 4px 9px; font: inherit; font-size: 12px;\n color: var(--ax-text); width: 180px;\n}\n.ax-jv-search:focus { outline: none; border-color: var(--ax-accent); }\n\n.ax-jv-viewer {\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-top: 0; border-radius: 0 0 var(--ax-radius) var(--ax-radius);\n font-family: var(--ax-mono); font-size: 12.5px; line-height: 1.75;\n padding: 14px 18px; max-height: 620px; overflow: auto;\n}\n.ax-jv-line {\n display: flex; align-items: flex-start; padding: 1px 0; gap: 0;\n position: relative; border-radius: 3px; white-space: nowrap;\n}\n.ax-jv-line:hover { background: color-mix(in oklch, var(--ax-accent) 5%, transparent); }\n.ax-jv-line.is-match { background: color-mix(in oklch, var(--ax-warn) 16%, transparent); }\n.ax-jv-toggle {\n width: 12px; display: inline-block; color: var(--ax-muted);\n cursor: pointer; user-select: none; text-align: center;\n font-size: 9px; line-height: 1.75;\n}\n.ax-jv-toggle.empty { cursor: default; }\n.ax-jv-key { color: var(--ax-info); margin-right: 0; }\n.ax-jv-punc { color: var(--ax-muted); }\n.ax-jv-string { color: var(--ax-accent); }\n.ax-jv-number { color: var(--ax-warn); }\n.ax-jv-bool { color: var(--ax-info); font-weight: 500; }\n.ax-jv-null { color: var(--ax-muted); font-style: italic; }\n.ax-jv-comment {\n color: var(--ax-muted); font-family: var(--ax-font);\n font-style: italic; font-size: 11.5px; margin-left: 8px; opacity: 0.8;\n}\n.ax-jv-children {\n padding-left: 18px;\n border-left: 1px dashed color-mix(in oklch, var(--ax-border) 80%, transparent);\n margin-left: 4px;\n}\n.ax-jv-children.is-collapsed { display: none; }\n.ax-jv-collapsed-note {\n color: var(--ax-muted); font-size: 11px; font-style: italic; margin-left: 4px;\n}\n.ax-jv-empty-brace { color: var(--ax-muted); }\n\n/* ============================================================\n * Detail-page primitives (Agent manage, and any future\n * detail-view). Built around a 3-col layout:\n *\n * [rail 200px] [main] [test-drive 360px]\n *\n * Scales down: test-drive hides at 1180px, rail hides at 780px.\n * ============================================================ */\n\n/* Crumb */\n.ax-crumb {\n max-width: 1400px; margin: 0 auto; padding: 16px 24px 0;\n font-size: 12px; color: var(--ax-muted);\n display: flex; align-items: center; gap: 6px;\n}\n.ax-crumb a { color: var(--ax-muted); }\n.ax-crumb a:hover { color: var(--ax-accent); text-decoration: none; }\n.ax-crumb .ax-crumb__sep { color: var(--ax-border-2); }\n.ax-crumb .ax-crumb__cur { color: var(--ax-text); }\n\n/* Hero — large avatar + editable name + chip row + actions */\n.ax-hero {\n max-width: 1400px; margin: 0 auto; padding: 14px 24px 18px;\n display: flex; align-items: center; gap: 18px; flex-wrap: wrap;\n}\n.ax-hero__ava {\n width: 64px; height: 64px; border-radius: 14px;\n background: color-mix(in oklch, var(--ax-accent) 20%, var(--ax-surface));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 40%, var(--ax-border));\n display: grid; place-items: center;\n font-weight: 700; font-size: 22px; letter-spacing: -0.02em;\n color: var(--ax-accent); flex-shrink: 0; position: relative;\n}\n.ax-hero__on {\n position: absolute; bottom: -2px; right: -2px;\n width: 16px; height: 16px; border-radius: 50%;\n background: var(--ax-accent); border: 3px solid var(--ax-bg);\n}\n.ax-hero__title { flex: 1; min-width: 260px; }\n.ax-hero h1 {\n margin: 0 0 3px; font-size: 22px; font-weight: 600;\n letter-spacing: -0.02em; display: flex; align-items: center; gap: 10px;\n}\n.ax-hero h1 input {\n background: transparent; border: 0; color: var(--ax-text);\n font: inherit; padding: 0; border-bottom: 1px dashed transparent;\n transition: border-color 120ms; max-width: 260px;\n}\n.ax-hero h1 input:hover { border-color: var(--ax-border-2); }\n.ax-hero h1 input:focus { outline: none; border-color: var(--ax-accent); }\n.ax-hero__sub {\n color: var(--ax-text-2); font-size: 13px; margin-top: 2px;\n display: flex; align-items: center; gap: 12px; flex-wrap: wrap;\n}\n.ax-hero__sub .ax-hero__dot {\n width: 3px; height: 3px; background: var(--ax-border-2); border-radius: 50%;\n}\n.ax-hero__actions { display: flex; gap: 8px; align-items: center; }\n\n/* Inline stats strip (smaller than the settings health strip — fits in\n * the detail page hero below the hero title). */\n.ax-stats-inline {\n max-width: 1400px; margin: 0 auto; padding: 0 24px;\n display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;\n}\n.ax-stat-inline {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-sm); padding: 10px 12px;\n display: flex; align-items: center; gap: 11px;\n}\n.ax-stat-inline__icon {\n width: 26px; height: 26px; border-radius: 6px;\n background: color-mix(in oklch, var(--ax-accent) 12%, var(--ax-surface-2));\n display: grid; place-items: center;\n color: var(--ax-accent); flex-shrink: 0;\n}\n.ax-stat-inline__icon svg { width: 14px; height: 14px; }\n.ax-stat-inline__v {\n font-size: 15px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.1;\n}\n.ax-stat-inline__l {\n font-size: 11px; color: var(--ax-muted); margin-top: 1px;\n}\n\n/* Detail layout — 2-col rail + main. Overlays (test-drive, history, etc.)\n * dock separately via fixed positioning. */\n.ax-layout {\n max-width: 1400px; margin: 22px auto 0; padding: 0 24px 60px;\n display: grid; grid-template-columns: 200px 1fr;\n gap: 22px; align-items: flex-start;\n}\n\n/* Left rail — sticky nav list with grouped items */\n.ax-rail {\n position: sticky; top: 72px;\n display: flex; flex-direction: column; gap: 1px;\n}\n.ax-rail a {\n display: flex; align-items: center; gap: 10px;\n padding: 9px 11px; border-radius: 6px;\n color: var(--ax-text-2); font-size: 13px; font-weight: 500;\n cursor: pointer; position: relative;\n transition: background 120ms, color 120ms;\n}\n.ax-rail a svg {\n width: 15px; height: 15px; flex-shrink: 0; color: var(--ax-muted);\n}\n.ax-rail a:hover {\n background: var(--ax-surface); color: var(--ax-text); text-decoration: none;\n}\n.ax-rail a:hover svg { color: var(--ax-text); }\n.ax-rail a.is-active {\n background: color-mix(in oklch, var(--ax-accent) 12%, var(--ax-surface));\n color: var(--ax-accent);\n}\n.ax-rail a.is-active svg { color: var(--ax-accent); }\n.ax-rail a.is-active::before {\n content: \"\"; position: absolute; left: -3px; top: 7px; bottom: 7px;\n width: 2px; border-radius: 2px; background: var(--ax-accent);\n}\n.ax-rail__count {\n margin-left: auto; font-family: var(--ax-mono);\n font-size: 10px; padding: 1px 6px; background: var(--ax-surface-2);\n color: var(--ax-muted); border-radius: 9px; line-height: 14px;\n}\n.ax-rail a.is-active .ax-rail__count {\n background: color-mix(in oklch, var(--ax-accent) 22%, transparent);\n color: var(--ax-accent);\n}\n.ax-rail__sep { height: 1px; background: var(--ax-border); margin: 8px 4px; }\n.ax-rail__label {\n font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;\n color: var(--ax-muted); padding: 6px 11px 4px; font-family: var(--ax-mono);\n}\n\n/* Main panel containers (one per section) */\n.ax-panel {\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); padding: 22px;\n}\n.ax-panel + .ax-panel { margin-top: 12px; }\n.ax-panel h2 {\n margin: 0 0 4px; font-size: 16px; font-weight: 600; letter-spacing: -0.01em;\n}\n.ax-panel .ax-lead {\n margin: 0 0 16px; font-size: 12.5px; color: var(--ax-text-2); line-height: 1.55;\n}\n.ax-panel__head {\n display: flex; align-items: flex-start; justify-content: space-between;\n gap: 12px; margin-bottom: 14px;\n}\n.ax-panel__head h2 { margin: 0 0 3px; }\n.ax-panel__tab { display: none; animation: ax-fade 180ms ease; }\n.ax-panel__tab.is-active { display: block; }\n@keyframes ax-fade {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n/* Grids for form rows */\n.ax-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }\n.ax-grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }\n\n/* Slider-style setting row — label + hint + segmented options */\n.ax-slider-row {\n display: grid; grid-template-columns: 140px 1fr auto;\n gap: 12px; align-items: center;\n padding: 10px 0; border-bottom: 1px dashed var(--ax-border);\n}\n.ax-slider-row:last-child { border-bottom: 0; }\n.ax-slider-row label { font-size: 12.5px; color: var(--ax-text); margin: 0; }\n.ax-slider-row .ax-hint {\n margin: 0; color: var(--ax-muted); font-size: 11.5px;\n}\n.ax-slider-row .ax-opts {\n display: flex; gap: 4px; background: var(--ax-bg);\n border: 1px solid var(--ax-border); border-radius: 5px; padding: 2px;\n}\n.ax-slider-row .ax-opts button {\n background: transparent; border: 0; color: var(--ax-muted);\n padding: 5px 11px; font: inherit; font-size: 11.5px;\n cursor: pointer; border-radius: 3px; white-space: nowrap;\n}\n.ax-slider-row .ax-opts button.is-active {\n background: var(--ax-surface-2); color: var(--ax-text);\n}\n\n/* Sub-tabs (Guided/Freeform, CLAUDE.md/SOUL.md/IDENTITY.md) */\n.ax-subtabs {\n display: inline-flex; background: var(--ax-bg);\n border: 1px solid var(--ax-border); border-radius: 6px;\n padding: 3px; margin-bottom: 16px;\n}\n.ax-subtabs button {\n background: transparent; border: 0; color: var(--ax-muted);\n padding: 6px 14px; font: inherit; font-size: 12px;\n cursor: pointer; border-radius: 4px; font-weight: 500;\n}\n.ax-subtabs button.is-active {\n background: var(--ax-surface-2); color: var(--ax-text);\n}\n\n/* Trigger-word chips with inline input (for Wake words field) */\n.ax-triggers-edit {\n display: flex; flex-wrap: wrap; gap: 6px;\n background: var(--ax-bg); border: 1px solid var(--ax-border);\n padding: 7px; border-radius: 5px; min-height: 38px;\n align-items: center; cursor: text;\n}\n.ax-triggers-edit:focus-within {\n border-color: var(--ax-accent);\n box-shadow: 0 0 0 3px color-mix(in oklch, var(--ax-accent) 18%, transparent);\n}\n.ax-trig-pill {\n display: inline-flex; align-items: center; gap: 6px;\n font-family: var(--ax-mono); font-size: 11px;\n padding: 3px 4px 3px 9px;\n background: color-mix(in oklch, var(--ax-accent) 16%, var(--ax-surface));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 30%, var(--ax-border));\n border-radius: 4px; color: var(--ax-accent);\n}\n.ax-trig-pill button {\n background: transparent; border: 0; color: currentColor;\n opacity: 0.6; cursor: pointer; font-size: 13px; line-height: 1;\n padding: 0 2px; border-radius: 3px;\n}\n.ax-trig-pill button:hover {\n opacity: 1;\n background: color-mix(in oklch, var(--ax-accent) 20%, transparent);\n}\n.ax-triggers-edit input {\n flex: 1; min-width: 120px;\n background: transparent; border: 0; color: var(--ax-text);\n font: inherit; font-size: 12.5px;\n padding: 4px 6px; font-family: var(--ax-mono);\n}\n.ax-triggers-edit input:focus { outline: none; }\n\n/* Prompt editor (freeform CLAUDE.md) */\n.ax-prompt-edit { position: relative; }\n.ax-prompt-edit textarea { min-height: 220px; line-height: 1.6; }\n.ax-prompt-badges {\n position: absolute; top: 8px; right: 8px; display: flex; gap: 6px;\n}\n.ax-prompt-badges .ax-pbadge {\n font-size: 10px; padding: 3px 7px; border-radius: 3px;\n background: var(--ax-surface-2); color: var(--ax-muted);\n font-family: var(--ax-mono); letter-spacing: 0.03em;\n}\n\n/* Skill gallery */\n.ax-skill-toolbar {\n display: flex; gap: 10px; align-items: center;\n margin-bottom: 14px; flex-wrap: wrap;\n}\n.ax-search {\n position: relative; flex: 1; min-width: 220px;\n}\n.ax-search svg {\n position: absolute; left: 10px; top: 50%; transform: translateY(-50%);\n width: 13px; height: 13px; color: var(--ax-muted); pointer-events: none;\n}\n.ax-search input { width: 100%; padding-left: 30px; }\n.ax-skill-filter {\n display: flex; gap: 4px; background: var(--ax-bg);\n border: 1px solid var(--ax-border); border-radius: 5px; padding: 2px;\n}\n.ax-skill-filter button {\n background: transparent; border: 0; color: var(--ax-muted);\n padding: 5px 10px; font: inherit; font-size: 11.5px;\n cursor: pointer; border-radius: 3px;\n}\n.ax-skill-filter button.is-active {\n background: var(--ax-surface-2); color: var(--ax-text);\n}\n.ax-skill-grid {\n display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: 10px;\n}\n.ax-skill-card {\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); padding: 14px;\n display: flex; flex-direction: column; gap: 8px;\n cursor: pointer; position: relative; overflow: hidden;\n transition: border-color 160ms, transform 160ms, background 160ms;\n}\n.ax-skill-card:hover { border-color: var(--ax-border-2); transform: translateY(-1px); }\n.ax-skill-card.is-on {\n border-color: color-mix(in oklch, var(--ax-accent) 40%, var(--ax-border));\n background: color-mix(in oklch, var(--ax-accent) 5%, var(--ax-bg-elev));\n}\n.ax-skill-card.is-on::after {\n content: \"\"; position: absolute; top: 0; right: 0;\n width: 0; height: 0;\n border-left: 26px solid transparent;\n border-top: 26px solid var(--ax-accent);\n}\n.ax-skill-card.is-on::before {\n content: \"\"; position: absolute; top: 5px; right: 3px;\n width: 7px; height: 4px;\n border-left: 2px solid var(--ax-bg);\n border-bottom: 2px solid var(--ax-bg);\n transform: rotate(-45deg); z-index: 2;\n}\n.ax-skill-card__top { display: flex; align-items: center; gap: 10px; }\n.ax-skill-card__icon {\n width: 32px; height: 32px; border-radius: 7px;\n background: var(--ax-surface-2);\n display: grid; place-items: center; flex-shrink: 0;\n color: var(--ax-accent);\n}\n.ax-skill-card__icon svg { width: 16px; height: 16px; }\n.ax-skill-card__name {\n font-weight: 600; font-size: 13px; letter-spacing: -0.005em;\n}\n.ax-skill-card__slug {\n font-family: var(--ax-mono); font-size: 10.5px;\n color: var(--ax-muted); margin-top: 1px;\n}\n.ax-skill-card__desc {\n font-size: 12px; color: var(--ax-text-2); line-height: 1.5;\n flex: 1; min-height: 32px;\n}\n.ax-skill-card__foot {\n display: flex; align-items: center; justify-content: space-between;\n font-size: 11px; color: var(--ax-muted);\n}\n\n/* Info callout (similar to wit but inline inside panels) */\n.ax-callout {\n background: color-mix(in oklch, var(--ax-info) 5%, var(--ax-bg-elev));\n border: 1px solid color-mix(in oklch, var(--ax-info) 20%, var(--ax-border));\n border-radius: var(--ax-radius-sm); padding: 12px 14px;\n font-size: 12.5px; color: var(--ax-text-2); line-height: 1.6;\n display: flex; gap: 10px; align-items: flex-start; margin-bottom: 16px;\n}\n.ax-callout__icon {\n width: 18px; height: 18px; border-radius: 50%;\n background: color-mix(in oklch, var(--ax-info) 18%, transparent);\n color: var(--ax-info); display: grid; place-items: center;\n font-size: 11px; font-weight: 600; flex-shrink: 0; margin-top: 1px;\n}\n.ax-callout b { color: var(--ax-text); }\n.ax-callout code {\n font-family: var(--ax-mono); font-size: 11.5px;\n background: var(--ax-bg); padding: 1px 5px; border-radius: 3px;\n border: 1px solid var(--ax-border);\n}\n\n/* Channel-binding rows (agent-specific view) */\n.ax-chan-row {\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); padding: 12px 14px;\n display: flex; align-items: center; gap: 12px; margin-bottom: 8px;\n}\n.ax-chan-row__logo {\n width: 32px; height: 32px; border-radius: 7px;\n display: grid; place-items: center;\n font-weight: 700; font-size: 13px; color: white; flex-shrink: 0;\n font-family: var(--ax-mono);\n}\n.ax-chan-row__info { flex: 1; }\n.ax-chan-row__name { font-size: 13px; font-weight: 600; }\n.ax-chan-row__desc {\n font-size: 12px; color: var(--ax-muted);\n margin-top: 2px; font-family: var(--ax-mono);\n}\n\n/* Handover viz (from-agent → to-agent arrow) */\n.ax-handover-viz {\n display: flex; align-items: center; justify-content: space-between;\n gap: 14px; padding: 16px 18px;\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-lg); margin-bottom: 14px;\n}\n.ax-ho-side {\n display: flex; flex-direction: column; align-items: center;\n gap: 6px; flex: 0 0 130px;\n}\n.ax-ho-side__av {\n width: 42px; height: 42px; border-radius: 10px;\n display: grid; place-items: center;\n font-weight: 600; font-size: 15px;\n}\n.ax-ho-side--from .ax-ho-side__av {\n background: color-mix(in oklch, var(--ax-accent) 18%, var(--ax-surface));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 35%, var(--ax-border));\n color: var(--ax-accent);\n}\n.ax-ho-side--to .ax-ho-side__av {\n background: var(--ax-surface-2); border: 1px solid var(--ax-border-2);\n color: var(--ax-text);\n}\n.ax-ho-side__nm { font-size: 12px; font-weight: 600; }\n.ax-ho-side__rl {\n font-size: 10px; color: var(--ax-muted);\n text-transform: uppercase; letter-spacing: 0.06em;\n font-family: var(--ax-mono);\n}\n.ax-ho-wire {\n flex: 1; height: 2px; position: relative;\n background: repeating-linear-gradient(90deg,\n var(--ax-border-2) 0, var(--ax-border-2) 4px,\n transparent 4px, transparent 8px);\n}\n.ax-ho-wire svg {\n position: absolute; right: -6px; top: 50%; transform: translateY(-50%);\n width: 16px; height: 16px; color: var(--ax-accent);\n}\n.ax-ho-list-row {\n display: flex; align-items: center; gap: 10px;\n padding: 10px 12px;\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-sm); margin-bottom: 6px;\n font-size: 12.5px;\n}\n.ax-ho-list-row .ax-who { flex: 1; color: var(--ax-text-2); }\n.ax-ho-list-row .ax-who b {\n color: var(--ax-text); font-family: var(--ax-mono); font-weight: 500;\n}\n.ax-ho-list-row .ax-when { color: var(--ax-muted); font-size: 11px; }\n\n/* Activity cells + spark + session rows */\n.ax-activity-grid {\n display: grid; grid-template-columns: repeat(4, 1fr);\n gap: 10px; margin-bottom: 18px;\n}\n.ax-activity-cell {\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-sm); padding: 14px;\n}\n.ax-activity-cell .ax-v {\n font-size: 20px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.2;\n}\n.ax-activity-cell .ax-l {\n font-size: 11px; color: var(--ax-muted); margin-top: 2px;\n}\n.ax-activity-cell .ax-delta {\n font-size: 11px; margin-top: 6px;\n display: inline-flex; align-items: center; gap: 3px;\n}\n.ax-activity-cell .ax-delta--up { color: var(--ax-accent); }\n.ax-activity-cell .ax-delta--down { color: var(--ax-muted); }\n\n.ax-spark { width: 100%; height: 100px; margin: 10px 0 14px; }\n\n.ax-session {\n display: flex; align-items: center; gap: 10px;\n padding: 10px 12px;\n background: var(--ax-bg-elev); border: 1px solid var(--ax-border);\n border-radius: var(--ax-radius-sm); margin-bottom: 6px;\n font-size: 12.5px;\n}\n.ax-session__ch {\n width: 24px; height: 24px; border-radius: 5px;\n display: grid; place-items: center;\n font-weight: 700; font-size: 10px; color: white;\n flex-shrink: 0; font-family: var(--ax-mono);\n}\n.ax-session__body { flex: 1; min-width: 0; }\n.ax-session__txt {\n color: var(--ax-text); overflow: hidden; text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ax-session__meta {\n font-size: 11px; color: var(--ax-muted);\n margin-top: 1px; font-family: var(--ax-mono);\n}\n\n/* Log row (Conversations tab) */\n.ax-log-head {\n display: grid; grid-template-columns: 24px 90px 160px 1fr auto;\n gap: 12px; padding: 8px 10px;\n border-bottom: 1px solid var(--ax-border-2);\n font-size: 10px; color: var(--ax-muted);\n text-transform: uppercase; letter-spacing: 0.06em; font-family: var(--ax-mono);\n}\n.ax-log-row {\n display: grid; grid-template-columns: 24px 90px 160px 1fr auto;\n gap: 12px; align-items: center; padding: 9px 10px;\n border-bottom: 1px solid var(--ax-border); font-size: 12px;\n}\n.ax-log-row:hover { background: color-mix(in oklch, var(--ax-accent) 4%, transparent); }\n.ax-log-row .ax-sev {\n width: 8px; height: 8px; border-radius: 50%; margin-left: 8px;\n}\n.ax-log-row .ax-sev--ok { background: var(--ax-accent); }\n.ax-log-row .ax-sev--warn { background: var(--ax-warn); }\n.ax-log-row .ax-sev--err { background: var(--ax-err); }\n.ax-log-row .ax-t {\n font-family: var(--ax-mono); color: var(--ax-muted); font-size: 11px;\n}\n.ax-log-row .ax-who {\n font-family: var(--ax-mono); font-size: 11px; color: var(--ax-text-2);\n}\n.ax-log-row .ax-msg {\n color: var(--ax-text); overflow: hidden;\n text-overflow: ellipsis; white-space: nowrap;\n}\n.ax-log-row .ax-ms {\n color: var(--ax-muted); font-family: var(--ax-mono); font-size: 11px;\n}\n\n/* Danger zone */\n.ax-panel--danger {\n border-color: color-mix(in oklch, var(--ax-err) 30%, var(--ax-border));\n}\n.ax-danger-item {\n display: flex; align-items: center; gap: 14px;\n padding: 14px 0; border-bottom: 1px dashed var(--ax-border);\n}\n.ax-danger-item:last-child { border-bottom: 0; padding-bottom: 0; }\n.ax-danger-item:first-child { padding-top: 4px; }\n.ax-danger-item__info { flex: 1; }\n.ax-danger-item__name { font-size: 13px; font-weight: 600; margin-bottom: 3px; }\n.ax-danger-item__desc { font-size: 12px; color: var(--ax-muted); line-height: 1.5; }\n\n/* Test-drive panel — docked to the right edge, slides in from outside\n * the viewport when toggled. Similar to the Live page history-panel. */\n.ax-td-panel {\n position: fixed; top: 0; right: 0; bottom: 0; width: 420px;\n background: var(--ax-bg-elev); border-left: 1px solid var(--ax-border);\n z-index: 900;\n display: flex; flex-direction: column;\n box-shadow: -8px 0 24px rgba(0,0,0,0.4);\n transform: translateX(100%); transition: transform 0.22s ease;\n}\n.ax-td-panel.is-open { transform: translateX(0); }\n@media (max-width: 520px) {\n .ax-td-panel { width: 100vw; }\n}\n.ax-td-head {\n padding: 12px 14px; border-bottom: 1px solid var(--ax-border);\n background: var(--ax-bg-elev);\n display: flex; align-items: center; gap: 10px;\n}\n.ax-td-head__dot {\n width: 8px; height: 8px; border-radius: 50%;\n background: var(--ax-accent);\n box-shadow: 0 0 8px color-mix(in oklch, var(--ax-accent) 60%, transparent);\n animation: ax-pulse 2.2s infinite;\n}\n.ax-td-head__t { flex: 1; font-size: 12px; font-weight: 600; }\n.ax-td-head__sub {\n font-size: 11px; color: var(--ax-muted); font-family: var(--ax-mono);\n}\n.ax-td-head__seg {\n display: inline-flex; background: var(--ax-surface);\n border: 1px solid var(--ax-border); border-radius: 5px; padding: 2px;\n}\n.ax-td-head__seg button {\n background: transparent; border: 0; color: var(--ax-muted);\n padding: 3px 8px; font: inherit; font-size: 10.5px;\n cursor: pointer; border-radius: 3px; font-family: var(--ax-mono);\n}\n.ax-td-head__seg button.is-active {\n background: var(--ax-surface-2); color: var(--ax-text);\n}\n\n.ax-td-scenarios {\n padding: 10px 14px; background: var(--ax-bg-elev);\n border-bottom: 1px solid var(--ax-border);\n display: flex; gap: 5px; flex-wrap: wrap;\n}\n.ax-td-scenarios__lbl {\n font-size: 10px; color: var(--ax-muted);\n text-transform: uppercase; letter-spacing: 0.06em;\n font-family: var(--ax-mono); width: 100%; margin-bottom: 3px;\n}\n.ax-td-scenario {\n font-size: 11px; padding: 3px 8px;\n background: var(--ax-surface); border: 1px solid var(--ax-border);\n border-radius: 10px; color: var(--ax-text-2);\n cursor: pointer; transition: all 120ms;\n}\n.ax-td-scenario:hover { border-color: var(--ax-accent); color: var(--ax-accent); }\n\n.ax-td-chat {\n flex: 1; overflow-y: auto; padding: 14px;\n display: flex; flex-direction: column; gap: 8px;\n background: var(--ax-bg);\n}\n.ax-td-chat__msg-wrap {\n display: flex; flex-direction: column; gap: 2px; max-width: 86%;\n}\n.ax-td-chat__msg-wrap.is-user { align-self: flex-end; align-items: flex-end; }\n.ax-td-chat__msg-wrap.is-bot { align-self: flex-start; }\n.ax-td-chat__bubble {\n padding: 8px 12px; border-radius: 12px;\n font-size: 12.5px; line-height: 1.55; word-wrap: break-word;\n}\n.ax-td-chat__bubble.is-user {\n background: var(--ax-accent); color: var(--ax-bg);\n border-bottom-right-radius: 3px; font-weight: 500;\n}\n.ax-td-chat__bubble.is-bot {\n background: var(--ax-surface-2); color: var(--ax-text);\n border-bottom-left-radius: 3px; border: 1px solid var(--ax-border);\n}\n.ax-td-chat__meta {\n font-size: 9.5px; color: var(--ax-muted);\n font-family: var(--ax-mono); padding: 0 4px;\n}\n.ax-td-chat__typing {\n align-self: flex-start; padding: 10px 14px;\n background: var(--ax-surface-2); border: 1px solid var(--ax-border);\n border-radius: 12px; border-bottom-left-radius: 3px;\n display: inline-flex; gap: 4px;\n}\n.ax-td-chat__typing span {\n width: 6px; height: 6px; background: var(--ax-muted);\n border-radius: 50%; animation: axTypingBounce 1.2s infinite;\n}\n.ax-td-chat__typing span:nth-child(2) { animation-delay: 0.15s; }\n.ax-td-chat__typing span:nth-child(3) { animation-delay: 0.3s; }\n@keyframes axTypingBounce {\n 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }\n 30% { transform: translateY(-5px); opacity: 1; }\n}\n.ax-td-input {\n padding: 10px; border-top: 1px solid var(--ax-border);\n background: var(--ax-bg-elev); display: flex; gap: 6px;\n}\n.ax-td-input input {\n flex: 1; background: var(--ax-bg); border: 1px solid var(--ax-border);\n border-radius: 6px; padding: 8px 11px;\n color: var(--ax-text); font: inherit; font-size: 12.5px;\n}\n.ax-td-input input:focus { outline: none; border-color: var(--ax-accent); }\n.ax-td-input button {\n background: var(--ax-accent); color: var(--ax-bg); border: 0;\n border-radius: 6px; padding: 0 12px;\n cursor: pointer; font-weight: 600;\n}\n.ax-td-input button:hover {\n background: color-mix(in oklch, var(--ax-accent) 88%, white);\n}\n\n/* Section mini-label used throughout detail pages */\n.ax-sec-label-d {\n display: flex; align-items: center; justify-content: space-between;\n margin: 20px 0 10px;\n}\n.ax-sec-label-d h3 {\n font-size: 11px; font-weight: 600; letter-spacing: 0.06em;\n text-transform: uppercase; color: var(--ax-muted);\n margin: 0; font-family: var(--ax-mono);\n}\n\n/* --- Responsive collapses for the settings primitives --- */\n@media (max-width: 1180px) {\n .ax-layout { grid-template-columns: 180px 1fr; }\n}\n@media (max-width: 760px) {\n .ax-health-strip { grid-template-columns: repeat(2, 1fr); }\n .ax-connectors { grid-template-columns: 1fr; }\n .ax-detail-grid { grid-template-columns: 1fr; }\n .ax-mesh-hero { grid-template-columns: 1fr; }\n .ax-mesh-viz { margin: 0 auto; }\n .ax-cron-hints { grid-template-columns: 1fr; }\n .ax-layout { grid-template-columns: 1fr; }\n .ax-rail { display: none; }\n .ax-stats-inline { grid-template-columns: repeat(2, 1fr); }\n .ax-activity-grid { grid-template-columns: repeat(2, 1fr); }\n .ax-log-head, .ax-log-row { grid-template-columns: 8px 80px 1fr auto; }\n .ax-log-head span:nth-child(3), .ax-log-row .ax-who { display: none; }\n .ax-grid-2, .ax-grid-3 { grid-template-columns: 1fr; }\n}`\n","// --- Shared top-chrome for every dashboard surface ---\n//\n// Four pages render in different files (live + kanban + glossary in\n// board-dashboard.ts, settings in admin-panel.ts) but must share: design\n// tokens, fonts, theme switcher, brand, primary tab nav, and a mesh-node\n// selector. This module exports the CSS + HTML snippets so each page\n// composes exactly the same chrome and we never drift them again.\n//\n// Nothing here is page-specific — callers pass in { activeTab, subtitle,\n// subheader? } and compose their own <main> below.\n\nexport type TopbarTab = \"live\" | \"boards\" | \"admin\" | \"graph\" | \"glossary\" | \"workflows\"\n\nexport interface TopbarPeer {\n /** Stable id: primary node id, or URL for configured daemons */\n id: string\n name: string\n /** Absolute URL of the peer's dashboard (http://host:port) — selector\n * navigates to <url>/admin when switched. */\n dashboardUrl: string\n /** True for the node hosting this dashboard (locks the default). */\n primary?: boolean\n /** Optional; shown in the dropdown as \"managing via token\" hint. */\n tokenScope?: string\n}\n\nexport interface TopbarOpts {\n activeTab: TopbarTab\n /** Text after the brand, e.g. \"Live\" / \"Settings\" / \"Boards\". */\n subtitle: string\n /** Optional HTML inserted as a second row under the topbar. */\n subheader?: string\n /** Peers to list in the mesh selector. */\n peers?: TopbarPeer[]\n /** id of the peer currently being managed — defaults to the primary. */\n currentPeerId?: string\n /** Optional right-side HTML added before the theme switcher (clocks,\n * connection dots, etc.). */\n rightExtras?: string\n}\n\n/**\n * Font link tags + tiny inline bootstrap script. Callers drop this in <head>.\n */\nexport const TOPBAR_HEAD = `<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n<script>(function(){try{var t=localStorage.getItem('ax-theme')||'dark';document.documentElement.setAttribute('data-theme',t)}catch(e){}})();</script>`\n\n/**\n * CSS for topbar + subheader + theme switch + mesh selector. No token\n * definitions — those are per-page because each page's CSS block owns its\n * own :root overrides.\n */\nexport const TOPBAR_CSS = `\n.ax-topbar{display:flex;align-items:center;justify-content:space-between;\n padding:8px 18px;border-bottom:1px solid var(--ax-border);\n background:var(--ax-bg-elev);position:sticky;top:0;z-index:20;gap:10px;flex-wrap:nowrap}\n.ax-topbar__left{display:flex;align-items:center;gap:18px;min-width:0}\n.ax-brand{display:flex;align-items:center;gap:10px;font-weight:600;letter-spacing:-0.01em;flex-shrink:0}\n.ax-brand__mark{font-family:var(--ax-mono);font-size:12px;padding:2px 8px;\n border:1px solid var(--ax-border-2);color:var(--ax-accent);border-radius:4px}\n.ax-brand__name{font-size:14px}\n.ax-brand__subtitle{font-size:11px;color:var(--ax-muted);\n border-left:1px solid var(--ax-border);padding-left:12px;margin-left:4px}\n.ax-topbar__tabs{display:flex;gap:2px;min-width:0;overflow-x:auto}\n.ax-topbar__tab{background:transparent;border:none;color:var(--ax-text-2);\n padding:8px 14px;font:inherit;cursor:pointer;font-size:12px;text-decoration:none;\n border-bottom:2px solid transparent;letter-spacing:-0.005em;white-space:nowrap}\n.ax-topbar__tab:hover{color:var(--ax-text)}\n.ax-topbar__tab.is-active{color:var(--ax-text);border-bottom-color:var(--ax-accent)}\n.ax-topbar__right{display:flex;align-items:center;gap:10px;font-size:11px;\n color:var(--ax-muted);flex-shrink:0}\n.ax-topbar__right .ax-mono{color:var(--ax-text-2)}\n\n/* Mesh selector */\n.ax-mesh-sel{position:relative;display:inline-flex;align-items:center;gap:6px;\n padding:4px 10px;border:1px solid var(--ax-border-2);border-radius:4px;\n background:var(--ax-surface);cursor:pointer;font:inherit;font-size:11px;\n color:var(--ax-text-2);white-space:nowrap}\n.ax-mesh-sel:hover{border-color:var(--ax-accent);color:var(--ax-text)}\n.ax-mesh-sel.is-proxy{background:color-mix(in oklch,var(--ax-accent) 14%,var(--ax-surface));\n border-color:color-mix(in oklch,var(--ax-accent) 45%,var(--ax-border-2));color:var(--ax-accent)}\n.ax-mesh-sel .lbl{font-size:9px;letter-spacing:0.06em;text-transform:uppercase;color:var(--ax-muted);margin-right:2px}\n.ax-mesh-sel .name{font-weight:500;color:var(--ax-text)}\n.ax-mesh-sel .chev{font-size:9px;opacity:0.6;margin-left:2px}\n.ax-mesh-menu{position:absolute;top:calc(100% + 4px);right:0;min-width:240px;\n background:var(--ax-surface);border:1px solid var(--ax-border-2);\n border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,0.4);padding:4px;z-index:30;display:none}\n.ax-mesh-sel.is-open .ax-mesh-menu{display:block}\n.ax-mesh-menu a{display:block;padding:7px 10px;border-radius:4px;text-decoration:none;\n color:var(--ax-text-2);font-size:12px}\n.ax-mesh-menu a:hover{background:var(--ax-surface-2);color:var(--ax-text)}\n.ax-mesh-menu a.is-current{background:color-mix(in oklch,var(--ax-accent) 12%,var(--ax-surface));\n color:var(--ax-accent);border:1px solid color-mix(in oklch,var(--ax-accent) 30%,var(--ax-border-2))}\n.ax-mesh-menu a .row{display:flex;align-items:center;gap:8px}\n.ax-mesh-menu a .row .dot{width:6px;height:6px;border-radius:50%;background:var(--ax-accent)}\n.ax-mesh-menu a.is-offline .row .dot{background:var(--ax-border-2)}\n.ax-mesh-menu a .url{font-family:var(--ax-mono);font-size:10px;color:var(--ax-muted);margin-top:2px}\n\n/* Theme switcher segmented control */\n.ax-theme-switch{display:inline-flex;border:1px solid var(--ax-border-2);\n border-radius:4px;overflow:hidden}\n.ax-theme-switch button{background:transparent;border:none;color:var(--ax-muted);\n padding:3px 9px;font:inherit;font-size:10px;cursor:pointer;letter-spacing:0.04em;\n text-transform:uppercase;font-family:var(--ax-mono);border-right:1px solid var(--ax-border-2)}\n.ax-theme-switch button:last-child{border-right:none}\n.ax-theme-switch button:hover{color:var(--ax-text);background:var(--ax-surface-2,var(--ax-surface))}\n.ax-theme-switch button.is-active{color:var(--ax-accent);\n background:color-mix(in oklch,var(--ax-accent) 12%,var(--ax-surface))}\n\n/* Subheader — page-specific toolbar below the main topbar. Kanban uses it\n * for the board picker + search + filters; others can use it for context\n * bars if they want. */\n.ax-subheader{display:flex;align-items:center;gap:10px;padding:10px 18px;\n background:var(--ax-bg-elev);border-bottom:1px solid var(--ax-border);\n flex-wrap:wrap}\n.ax-subheader > * {flex-shrink:0}\n.ax-subheader .spacer{flex:1}`\n\n/**\n * Shared click wiring for the theme switcher + mesh selector. Safe to call\n * multiple times — only binds once per element.\n */\nexport const TOPBAR_SCRIPT = `<script>\n(function(){\n function wireTheme(){\n var sw = document.querySelectorAll('[data-theme-opt]');\n if (!sw.length) return;\n var current = document.documentElement.getAttribute('data-theme') || 'dark';\n sw.forEach(function(b){\n if (b.dataset.wired) return; b.dataset.wired = '1';\n if (b.getAttribute('data-theme-opt') === current) b.classList.add('is-active');\n b.addEventListener('click', function(){\n var t = b.getAttribute('data-theme-opt');\n document.documentElement.setAttribute('data-theme', t);\n try { localStorage.setItem('ax-theme', t); } catch (e) {}\n sw.forEach(function(x){ x.classList.toggle('is-active', x === b); });\n });\n });\n }\n function currentPeer(){\n try { return localStorage.getItem('ax-peer') || 'primary'; } catch { return 'primary'; }\n }\n function setCurrentPeer(id){\n try {\n if (id && id !== 'primary') localStorage.setItem('ax-peer', id);\n else localStorage.removeItem('ax-peer');\n } catch {}\n }\n function reflectPeerPill(){\n var sel = document.querySelector('.ax-mesh-sel');\n if (!sel) return;\n var cur = currentPeer();\n sel.classList.toggle('is-proxy', cur !== 'primary');\n // If a proxy is active, update the visible pill label + highlight.\n var picked = null;\n sel.querySelectorAll('.ax-mesh-menu a').forEach(function(a){\n a.classList.toggle('is-current', a.getAttribute('data-peer-id') === cur || (cur === 'primary' && a.getAttribute('data-peer-id') === 'primary'));\n if (a.getAttribute('data-peer-id') === cur) picked = a;\n });\n if (picked) {\n var name = picked.querySelector('span'); // first <span> inside .row\n var visibleName = sel.querySelector('.name');\n if (name && visibleName) visibleName.textContent = name.textContent.replace(/ · primary/, '');\n }\n }\n function wireMesh(){\n var sel = document.querySelector('.ax-mesh-sel');\n if (!sel || sel.dataset.wired) return; sel.dataset.wired = '1';\n sel.addEventListener('click', function(e){\n var link = e.target.closest('.ax-mesh-menu a[data-peer-action=\"proxy\"]');\n if (link) {\n e.preventDefault();\n setCurrentPeer(link.getAttribute('data-peer-id'));\n reflectPeerPill();\n sel.classList.remove('is-open');\n // Reload so the admin panel re-reads state from the selected peer.\n location.reload();\n return;\n }\n if (e.target.closest('.ax-mesh-menu a')) return; // plain navigate anchor\n sel.classList.toggle('is-open');\n });\n document.addEventListener('click', function(e){\n if (!sel.contains(e.target)) sel.classList.remove('is-open');\n });\n reflectPeerPill();\n }\n function wireHostRewrite(){\n try {\n var h = location.hostname || 'localhost';\n document.querySelectorAll('.ax-topbar__tab[href*=\"__HOST__\"]').forEach(function(a){\n a.href = a.getAttribute('href').replace(/__HOST__/g, h);\n });\n } catch (e) {}\n }\n function wire(){ wireHostRewrite(); wireTheme(); wireMesh(); }\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', wire);\n } else { wire(); }\n})();\n</script>`\n\n/**\n * Render the topbar element. Caller places this at the start of <body>.\n * Emits a matching .ax-subheader row only when `subheader` HTML is provided.\n */\nexport function renderTopbar(opts: TopbarOpts): string {\n const peers = opts.peers && opts.peers.length > 0 ? opts.peers : [{ id: \"local\", name: \"this daemon\", dashboardUrl: \"\", primary: true }]\n const currentId = opts.currentPeerId || peers.find((p) => p.primary)?.id || peers[0].id\n const current = peers.find((p) => p.id === currentId) || peers[0]\n\n const meshMenu = peers.map((p) => {\n const isCurrent = p.id === currentId\n const primaryMark = p.primary ? ' <span style=\"color:var(--ax-muted);font-size:10px\">· primary</span>' : \"\"\n const proxyable = !!p.tokenScope && !p.primary\n // Proxyable peers: use data-peer-id so the click handler switches locally\n // via localStorage + reload. Un-proxyable (no token): legacy navigate.\n const href = proxyable\n ? \"#\"\n : p.dashboardUrl\n ? `${p.dashboardUrl.replace(/\\/+$/, \"\")}/admin`\n : \"#\"\n const dataAttr = proxyable ? ` data-peer-id=\"${esc(p.id)}\" data-peer-action=\"proxy\"` : p.primary ? ` data-peer-id=\"primary\" data-peer-action=\"proxy\"` : \"\"\n const hint = proxyable\n ? '<div class=\"url\" style=\"color:var(--ax-accent)\">manage from this dashboard ✓</div>'\n : p.tokenScope\n ? `<div class=\"url\">${esc(p.tokenScope)}</div>`\n : p.dashboardUrl\n ? '<div class=\"url\" style=\"color:var(--ax-muted)\">(open peer dashboard ↗)</div>'\n : \"\"\n return `<a href=\"${esc(href)}\" class=\"${isCurrent ? \"is-current\" : \"\"}\"${dataAttr}>\n <div class=\"row\"><span class=\"dot\"></span><span>${esc(p.name)}${primaryMark}</span></div>\n ${p.dashboardUrl ? `<div class=\"url\">${esc(p.dashboardUrl)}</div>` : \"\"}\n ${hint}\n </a>`\n }).join(\"\")\n\n const meshSelector = `<div class=\"ax-mesh-sel\" aria-haspopup=\"menu\">\n <span class=\"lbl\">managing</span>\n <span class=\"name\">${esc(current.name)}</span>\n <span class=\"chev\">▾</span>\n <div class=\"ax-mesh-menu\" role=\"menu\">${meshMenu}</div>\n </div>`\n\n // Graph tab is intentionally not listed — the page is still reachable at\n // /admin/graph for debugging the classifier, but we don't promote it in the\n // main nav until it earns its keep (wiki absorb writing graphPath, or\n // graph-based routing).\n // Usage + Wiki run on their own ports. Emit a __HOST__ sentinel the\n // client-side TOPBAR_SCRIPT rewrites to location.hostname, so the link\n // works on localhost and Tailscale/LAN hostnames alike. Override with\n // AGENTX_USAGE_URL / AGENTX_WIKI_URL when those surfaces live on a\n // different host.\n const usageUrl = process.env.AGENTX_USAGE_URL || `http://__HOST__:4201`\n const wikiUrl = process.env.AGENTX_WIKI_URL || `http://__HOST__:4200`\n\n const tabs = [\n { id: \"live\", label: \"Live\", href: \"/live\", external: false },\n { id: \"boards\", label: \"Boards\", href: \"/\", external: false },\n { id: \"workflows\", label: \"Workflows\", href: \"/workflows\", external: false },\n { id: \"admin\", label: \"Settings\", href: \"/admin\", external: false },\n { id: \"wiki\", label: \"Wiki\", href: wikiUrl, external: true },\n { id: \"usage\", label: \"Usage\", href: usageUrl, external: true },\n { id: \"glossary\", label: \"Glossary\", href: \"/glossary\", external: false },\n ].map((t) => {\n const cls = t.id === opts.activeTab ? \"ax-topbar__tab is-active\" : \"ax-topbar__tab\"\n const extAttrs = t.external ? ` target=\"_blank\" rel=\"noopener\"` : \"\"\n const extGlyph = t.external ? ' <span style=\"font-size:9px;opacity:0.6\">↗</span>' : \"\"\n return `<a href=\"${t.href}\" class=\"${cls}\"${extAttrs}>${t.label}${extGlyph}</a>`\n }).join(\"\")\n\n const header = `<header class=\"ax-topbar\">\n <div class=\"ax-topbar__left\">\n <div class=\"ax-brand\">\n <span class=\"ax-brand__mark\">AX</span>\n <span class=\"ax-brand__name\">AgentX</span>\n <span class=\"ax-brand__subtitle\">${esc(opts.subtitle)}</span>\n </div>\n <nav class=\"ax-topbar__tabs\">${tabs}</nav>\n </div>\n <div class=\"ax-topbar__right\">\n ${opts.rightExtras || \"\"}\n ${meshSelector}\n <div class=\"ax-theme-switch\" role=\"tablist\" aria-label=\"Theme\">\n <button data-theme-opt=\"dark\">Dark</button>\n <button data-theme-opt=\"light\">Light</button>\n <button data-theme-opt=\"crt\">CRT</button>\n </div>\n </div>\n</header>${opts.subheader ? `<div class=\"ax-subheader\">${opts.subheader}</div>` : \"\"}`\n return header\n}\n\nfunction esc(s: string): string {\n return String(s || \"\").replace(/[&<>\"']/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" }[c] as string))\n}\n","// --- Page shell renderer ---\n//\n// Every dashboard page used to hand-roll its own <html><head><body> wrapper:\n// meta tags, font links, theme bootstrap script, style tags, topbar markup,\n// closing tags. That's ~60 lines of boilerplate per page, identical across\n// ~8 pages. This module centralises it.\n//\n// A page calls `renderShell({ title, activeTab, subtitle, body, css, ... })`\n// and gets back a complete HTML document with:\n// - Correct <meta>, <title>, Plex font link, theme-bootstrap script\n// - AX_TOKENS_CSS (design tokens) + AX_COMPONENTS_CSS (badge/stat/card/...)\n// - TOPBAR_CSS (chrome) always included\n// - Page-specific CSS appended after\n// - renderTopbar() at the top\n// - <main> with the page body\n// - TOPBAR_SCRIPT (theme toggle + mesh selector) at the end\n// - Page-specific <script> appended after\n//\n// Pages that want a slimmer header (e.g. /setup, which has its own simplified\n// topbar) can pass `renderHeader: \"custom\"` and provide their own <header>.\n\nimport { AX_TOKENS_CSS } from \"./tokens\"\nimport { AX_COMPONENTS_CSS } from \"./components.css\"\nimport {\n TOPBAR_HEAD,\n TOPBAR_CSS,\n TOPBAR_SCRIPT,\n renderTopbar,\n type TopbarOpts,\n type TopbarPeer,\n} from \"../topbar\"\n\nexport interface ShellOpts {\n /** Browser tab title — \"AgentX · Live\", \"AgentX · Settings\", etc. */\n title: string\n /**\n * The topbar tab to highlight, OR \"custom\" to skip rendering the default\n * topbar so the page can provide its own header.\n */\n activeTab: TopbarOpts[\"activeTab\"] | \"custom\"\n /** Subtitle after the brand — \"Live\", \"Settings\", \"Setup\", \"Intent Graph\". */\n subtitle: string\n /** Page body HTML — everything inside <main>. */\n body: string\n /** Optional HTML inserted as a second row under the topbar. */\n subheader?: string\n /**\n * Page-specific CSS appended after the shared blocks. Use this for styles\n * that aren't worth promoting to components.css (e.g. kanban column\n * layout, graph tree indentation).\n */\n css?: string\n /** Page-specific <script> blocks (full tags, not raw JS). */\n scripts?: string\n /** Mesh peers to populate the selector. */\n peers?: TopbarPeer[]\n currentPeerId?: string\n /** If provided, replaces the default topbar entirely. */\n customHeader?: string\n /**\n * When true, omit the <main> wrapper — caller renders their own. Useful\n * for pages (e.g. boards kanban) that need a full-viewport layout without\n * the default padding.\n */\n noMain?: boolean\n /** Extra content to inject into <head> (rare — e.g. preload hints). */\n headExtras?: string\n}\n\nexport function renderShell(opts: ShellOpts): string {\n const css = `${AX_TOKENS_CSS}\\n${AX_COMPONENTS_CSS}\\n${TOPBAR_CSS}${opts.css ? \"\\n\" + opts.css : \"\"}`\n\n const header = opts.customHeader\n ?? (opts.activeTab === \"custom\"\n ? \"\"\n : renderTopbar({\n activeTab: opts.activeTab,\n subtitle: opts.subtitle,\n subheader: opts.subheader,\n peers: opts.peers,\n currentPeerId: opts.currentPeerId,\n }))\n\n const main = opts.noMain ? opts.body : `<main>${opts.body}</main>`\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeTitle(opts.title)}</title>\n${TOPBAR_HEAD}\n${opts.headExtras || \"\"}\n<style>${css}</style>\n</head>\n<body>\n${header}\n${main}\n${TOPBAR_SCRIPT}\n${opts.scripts || \"\"}\n</body>\n</html>`\n}\n\nfunction escapeTitle(s: string): string {\n return String(s || \"\").replace(/[<>&]/g, (c) => ({ \"<\": \"<\", \">\": \">\", \"&\": \"&\" }[c] as string))\n}\n","// Shared small utilities for UI renderers.\n\n/**\n * HTML-escape a string so it's safe to interpolate inside attributes or text\n * content. Every page used to ship its own `esc` / `escapeHtml` / `safe` —\n * this is the canonical one.\n */\nexport function esc(s: unknown): string {\n return String(s ?? \"\").replace(/[&<>\"']/g, (c) => ({\n \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\",\n }[c] as string))\n}\n\n/** Join class names, dropping falsy values. */\nexport function cx(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(\" \")\n}\n","// --- Shared UI components ---\n//\n// Small helpers that return HTML strings. Used by every page to produce\n// consistent badges, cards, form rows, buttons without copy-pasting.\n//\n// ### Philosophy\n//\n// - Pure functions: `string` in, `string` out. No classes, no DSL, no JSX.\n// - Caller composes: `cardHtml(children)` not `Card({children})`.\n// - Null/undefined opts render an empty string, so conditional composition\n// just inlines: `${badge(...) ?? \"\"}` without wrapper ifs.\n// - Variants are flat classnames, not object props: `badge(\"LIVE\", \"live\")`\n// not `badge({label: \"LIVE\", variant: \"live\"})`. Cheaper to read, easier\n// to grep for.\n\nimport { esc } from \"./util\"\n\n// --- Dots (status indicators) ---\n\nexport type DotKind = \"ok\" | \"live\" | \"warn\" | \"err\" | \"off\" | undefined\n\nexport function dot(kind?: DotKind, pulse = false): string {\n const classes = [\"ax-dot\", kind ? `ax-dot--${kind}` : \"\", pulse ? \"ax-dot--pulse\" : \"\"]\n .filter(Boolean).join(\" \")\n return `<span class=\"${classes}\"></span>`\n}\n\n// --- Badges (small pill labels) ---\n\nexport type BadgeKind = \"accent\" | \"live\" | \"warn\" | \"err\" | \"ghost\" | \"mono\" | undefined\n\nexport function badge(label: string, kind?: BadgeKind, extraClass = \"\"): string {\n const classes = [\"ax-badge\", kind ? `ax-badge--${kind}` : \"\", extraClass]\n .filter(Boolean).join(\" \")\n return `<span class=\"${classes}\">${esc(label)}</span>`\n}\n\n// --- Buttons ---\n\nexport interface BtnOpts {\n type?: \"button\" | \"submit\"\n primary?: boolean\n ghost?: boolean\n danger?: boolean\n id?: string\n disabled?: boolean\n extraClass?: string\n onclick?: string\n}\n\nexport function btn(label: string, opts: BtnOpts = {}): string {\n const classes = [\"ax-btn\",\n opts.primary ? \"ax-btn--primary\" : \"\",\n opts.ghost ? \"ax-btn--ghost\" : \"\",\n opts.danger ? \"ax-btn--danger\" : \"\",\n opts.extraClass || \"\",\n ].filter(Boolean).join(\" \")\n const attrs = [\n `type=\"${opts.type || \"button\"}\"`,\n `class=\"${classes}\"`,\n opts.id ? `id=\"${esc(opts.id)}\"` : \"\",\n opts.disabled ? \"disabled\" : \"\",\n opts.onclick ? `onclick=\"${esc(opts.onclick)}\"` : \"\",\n ].filter(Boolean).join(\" \")\n return `<button ${attrs}>${esc(label)}</button>`\n}\n\n// --- Stat cards (the \"NUMBER + label\" strip) ---\n\nexport interface StatOpts {\n label: string\n value: string\n sub?: string\n kind?: \"live\" | \"warn\" | \"err\"\n}\n\nexport function stat(opts: StatOpts): string {\n const cls = opts.kind ? `ax-stat ax-stat--${opts.kind}` : \"ax-stat\"\n return `<div class=\"${cls}\">\n <div class=\"ax-stat__label\">${esc(opts.label)}</div>\n <div class=\"ax-stat__value\">${esc(opts.value)}</div>\n ${opts.sub ? `<div class=\"ax-stat__sub\">${esc(opts.sub)}</div>` : \"\"}\n </div>`\n}\n\nexport function statStrip(stats: StatOpts[]): string {\n return `<div class=\"ax-statstrip\">${stats.map(stat).join(\"\")}</div>`\n}\n\n// --- Form field (label + input/textarea/select) ---\n\nexport interface FieldOpts {\n name: string\n label: string\n hint?: string\n type?: \"text\" | \"password\" | \"email\" | \"url\" | \"number\" | \"textarea\" | \"select\"\n value?: string\n placeholder?: string\n pattern?: string\n required?: boolean\n readonly?: boolean\n rows?: number\n options?: Array<{ value: string; label: string; selected?: boolean }>\n extraAttrs?: string\n}\n\nexport function field(opts: FieldOpts): string {\n const labelHtml = `<label>${esc(opts.label)}${\n opts.hint ? ` <span class=\"ax-hint\">${opts.hint}</span>` : \"\"\n }</label>`\n let controlHtml = \"\"\n const common = [\n `name=\"${esc(opts.name)}\"`,\n opts.required ? \"required\" : \"\",\n opts.readonly ? \"readonly\" : \"\",\n opts.placeholder ? `placeholder=\"${esc(opts.placeholder)}\"` : \"\",\n opts.extraAttrs || \"\",\n ].filter(Boolean).join(\" \")\n\n if (opts.type === \"textarea\") {\n controlHtml = `<textarea ${common} rows=\"${opts.rows || 3}\">${esc(opts.value || \"\")}</textarea>`\n } else if (opts.type === \"select\" && opts.options) {\n const options = opts.options.map(o =>\n `<option value=\"${esc(o.value)}\"${o.selected ? \" selected\" : \"\"}>${esc(o.label)}</option>`\n ).join(\"\")\n controlHtml = `<select ${common}>${options}</select>`\n } else {\n const type = opts.type || \"text\"\n controlHtml = `<input type=\"${type}\" ${common}`\n + (opts.value !== undefined ? ` value=\"${esc(opts.value)}\"` : \"\")\n + (opts.pattern ? ` pattern=\"${esc(opts.pattern)}\"` : \"\")\n + \" />\"\n }\n return `<div class=\"ax-field\">${labelHtml}${controlHtml}</div>`\n}\n\nexport function row(children: string[]): string {\n return `<div class=\"ax-row\">${children.join(\"\")}</div>`\n}\n\n// --- Cards ---\n\nexport function card(content: string, extraClass = \"\"): string {\n return `<div class=\"ax-card${extraClass ? \" \" + extraClass : \"\"}\">${content}</div>`\n}\n\nexport function stepCard(num: number | string, title: string, body: string): string {\n return `<section class=\"ax-step\">\n <div class=\"ax-step__head\"><span class=\"ax-step__num\">${esc(String(num))}</span>${esc(title)}</div>\n ${body}\n </section>`\n}\n\n// --- Small layout helpers ---\n\nexport function spacer(height = 12): string {\n return `<div style=\"height:${height}px\"></div>`\n}\n\n// ============================================================================\n// Settings-page primitives (from the Stitch redesign)\n// ============================================================================\n\n/** Page head — kicker + h1 + lead. Lives outside <main> so it can span wider. */\nexport interface PageHeadOpts {\n kicker?: string\n title: string\n lead?: string\n}\nexport function pageHead(opts: PageHeadOpts): string {\n return `<div class=\"ax-page-head\">\n ${opts.kicker ? `<div class=\"ax-kicker\">${esc(opts.kicker)}</div>` : \"\"}\n <h1>${esc(opts.title)}</h1>\n ${opts.lead ? `<p class=\"ax-lead\">${opts.lead}</p>` : \"\"}\n </div>`\n}\n\n/** 4-up health strip: colour-coded status cards across the top of a page. */\nexport interface HealthCardOpts {\n kind?: \"ok\" | \"warn\" | \"off\"\n num: string\n label: string\n}\nexport function healthCard(opts: HealthCardOpts): string {\n const kind = opts.kind || \"off\"\n return `<div class=\"ax-health-card\">\n <div class=\"ax-hc-dot ax-hc-dot--${kind}\"></div>\n <div class=\"ax-hc-txt\">\n <div class=\"ax-hc-num\">${esc(opts.num)}</div>\n <div class=\"ax-hc-lbl\">${esc(opts.label)}</div>\n </div>\n </div>`\n}\nexport function healthStrip(cards: HealthCardOpts[]): string {\n return `<div class=\"ax-health-strip\">${cards.map(healthCard).join(\"\")}</div>`\n}\n\n/**\n * \"What is this?\" banner. Renders a dismissable explanatory box for users\n * meeting a tab for the first time. Pass raw HTML for body — short runs\n * with <b> and <code> are the intended style.\n */\nexport interface WitOpts {\n bodyHtml: string\n /** Persist the dismissal under this key in localStorage. Optional. */\n persistKey?: string\n icon?: string\n}\nexport function witBanner(opts: WitOpts): string {\n const attr = opts.persistKey ? ` data-wit=\"${esc(opts.persistKey)}\"` : \"\"\n return `<div class=\"ax-wit\"${attr}>\n <span class=\"ax-wit__icon\">${esc(opts.icon || \"?\")}</span>\n <div>${opts.bodyHtml}</div>\n <button class=\"ax-wit__dismiss\" onclick=\"this.parentElement.style.display='none'\">×</button>\n </div>`\n}\n\n/**\n * Section header with icon chip + title + lead + optional right-aligned\n * action. Icon is raw SVG so the caller picks the glyph; feed a 24x24\n * stroked Feather-style icon for best fit.\n */\nexport interface SectionHeadOpts {\n icon: string\n title: string\n lead?: string\n actionHtml?: string\n}\nexport function sectionHead(opts: SectionHeadOpts): string {\n return `<div class=\"ax-section-head\">\n <div class=\"ax-section-head__icon\">${opts.icon}</div>\n <div class=\"ax-section-head__text\">\n <h2>${esc(opts.title)}</h2>\n ${opts.lead ? `<p class=\"ax-lead\">${esc(opts.lead)}</p>` : \"\"}\n </div>\n ${opts.actionHtml || \"\"}\n </div>`\n}\n\n/** Coloured-initial avatar — pass 1-2 letters; variant sets the tint. */\nexport type AvatarVariant = \"plain\" | \"teal\" | \"coral\" | \"amber\" | \"blue\"\nexport function avatar(initials: string, variant: AvatarVariant = \"plain\"): string {\n const cls = variant === \"plain\" ? \"ax-avatar\" : `ax-avatar ax-avatar--${variant}`\n return `<div class=\"${cls}\">${esc(initials)}</div>`\n}\n\n/**\n * Status pill with coloured dot — \"live\", \"idle\", \"receiving\", \"stale\".\n * Different from {@link badge}: pill has a rounded shape (10px radius) and\n * a leading dot. Prefer for attached-to-name status labels.\n */\nexport type PillKind = \"ok\" | \"warn\" | \"off\" | \"info\" | \"plain\"\nexport function pill(label: string, kind: PillKind = \"plain\", withDot = true): string {\n const cls = kind === \"plain\" ? \"ax-pill\" : `ax-pill ax-pill--${kind}`\n const dot = withDot && kind !== \"plain\" && kind !== \"off\" ? `<span class=\"ax-pill__dot\"></span>` : \"\"\n return `<span class=\"${cls}\">${dot}${esc(label)}</span>`\n}\n\n/**\n * Row card with colored avatar + info + actions + optional expandable\n * details section. Details are hidden behind `.is-open` on the card;\n * toggleCard() on the client flips it.\n */\nexport interface RowCardOpts {\n avatarHtml?: string\n /** Left block: title + pills + sub-line. Raw HTML. */\n infoHtml: string\n /** Right-aligned action cluster. Raw HTML (usually btns). */\n actionsHtml?: string\n /** Collapsible details. When provided, the card is collapsible. */\n detailsHtml?: string\n /** Initial open state. Default false. */\n open?: boolean\n /** Extra classes on the card wrapper. */\n extraClass?: string\n}\nexport function rowCard(opts: RowCardOpts): string {\n const cls = [\"ax-row-card\", opts.open ? \"is-open\" : \"\", opts.extraClass || \"\"]\n .filter(Boolean).join(\" \")\n return `<div class=\"${cls}\">\n <div class=\"ax-row-card__top\">\n ${opts.avatarHtml || \"\"}\n <div class=\"ax-row-card__info\">${opts.infoHtml}</div>\n ${opts.actionsHtml ? `<div class=\"ax-row-card__actions\">${opts.actionsHtml}</div>` : \"\"}\n </div>\n ${opts.detailsHtml ? `<div class=\"ax-row-card__details\">${opts.detailsHtml}</div>` : \"\"}\n </div>`\n}\n\n/**\n * Channel/webhook-style connector card — big logo, name, status pill,\n * description, foot row. Clickable (caller attaches onclick / wraps\n * in <a>).\n */\nexport interface ConnectorOpts {\n logoHtml: string\n name: string\n sub: string\n statusHtml?: string\n desc: string\n footLeft?: string\n footCta?: string\n isOn?: boolean\n onclick?: string\n extraStyle?: string\n}\nexport function connector(opts: ConnectorOpts): string {\n const cls = opts.isOn ? \"ax-connector is-on\" : \"ax-connector\"\n const onclick = opts.onclick ? ` onclick=\"${esc(opts.onclick)}\"` : \"\"\n const style = opts.extraStyle ? ` style=\"${esc(opts.extraStyle)}\"` : \"\"\n return `<div class=\"${cls}\"${onclick}${style}>\n <div class=\"ax-connector__top\">\n ${opts.logoHtml}\n <div class=\"ax-connector__meta\">\n <div class=\"ax-connector__name\">${esc(opts.name)}</div>\n <div class=\"ax-connector__sub\">${esc(opts.sub)}</div>\n </div>\n ${opts.statusHtml ? `<div class=\"ax-connector__status\">${opts.statusHtml}</div>` : \"\"}\n </div>\n <div class=\"ax-connector__desc\">${opts.desc}</div>\n <div class=\"ax-connector__foot\">\n <span>${esc(opts.footLeft || \"\")}</span>\n ${opts.footCta\n ? `<span class=\"ax-connector__cta\">${esc(opts.footCta)} <svg viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M4 2l4 4-4 4\"/></svg></span>`\n : \"\"}\n </div>\n </div>`\n}\n\n/** Empty state used when a tab has nothing yet. */\nexport interface EmptyOpts {\n title: string\n desc?: string\n actionHtml?: string\n}\nexport function emptyState(opts: EmptyOpts): string {\n return `<div class=\"ax-empty-card\" style=\"text-align:center;padding:42px 22px;background:var(--ax-surface);border:1px dashed var(--ax-border-2);border-radius:var(--ax-radius-lg);color:var(--ax-muted)\">\n <h3 style=\"margin:0 0 4px;color:var(--ax-text);font-size:15px;font-weight:600\">${esc(opts.title)}</h3>\n ${opts.desc ? `<p style=\"margin:0 0 16px;font-size:13px;line-height:1.55;max-width:380px;margin-left:auto;margin-right:auto\">${esc(opts.desc)}</p>` : \"\"}\n ${opts.actionHtml || \"\"}\n </div>`\n}\n\n/** Mini-label used to divide sections inside a tab. */\nexport interface SecLabelOpts {\n label: string\n rightHtml?: string\n}\nexport function secLabel(opts: SecLabelOpts): string {\n return `<div class=\"ax-sec-label\"><h3>${esc(opts.label)}</h3>${opts.rightHtml || \"\"}</div>`\n}\n\n/** Toast element + helper script. Include once per page that uses it. */\nexport const TOAST_HTML = `<div class=\"ax-toast\" id=\"ax-toast\">\n <svg class=\"ax-toast__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 6L9 17l-5-5\"/></svg>\n <span id=\"ax-toast-msg\">Copied</span>\n</div>`\nexport const TOAST_SCRIPT = `\n let __axToastTimer;\n window.showToast = function(msg) {\n const el = document.getElementById('ax-toast');\n const m = document.getElementById('ax-toast-msg');\n if (!el || !m) return;\n m.textContent = msg;\n el.classList.add('is-show');\n clearTimeout(__axToastTimer);\n __axToastTimer = setTimeout(function(){ el.classList.remove('is-show'); }, 1800);\n };\n window.copyText = function(_btn, text) {\n try { navigator.clipboard.writeText(text); } catch (e) {}\n window.showToast('Copied to clipboard');\n };`\n\n/** Client script that wires `.ax-row-card` expand/collapse on any button\n * that calls `toggleCard(this)`. Include once per page. */\nexport const ROW_CARD_SCRIPT = `\n window.toggleCard = function(btn) {\n const card = btn.closest('.ax-row-card');\n if (card) card.classList.toggle('is-open');\n };`\n"],"mappings":"AAUO,IAAMA,EAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;yCCDtB,IAAMC,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["AX_TOKENS_CSS","AX_COMPONENTS_CSS","TOPBAR_HEAD","TOPBAR_CSS","TOPBAR_SCRIPT","renderTopbar","opts","peers","currentId","p","current","meshMenu","isCurrent","primaryMark","proxyable","href","dataAttr","esc","hint","meshSelector","usageUrl","wikiUrl","tabs","t","cls","extAttrs","extGlyph","s","c","renderShell","opts","css","AX_TOKENS_CSS","AX_COMPONENTS_CSS","TOPBAR_CSS","header","renderTopbar","main","escapeTitle","TOPBAR_HEAD","TOPBAR_SCRIPT","s","c","esc","s","c","btn","label","opts","classes","esc","field","opts","labelHtml","esc","controlHtml","common","options","o","row","children","stepCard","num","title","body","esc","pageHead","opts","esc","healthCard","healthStrip","cards","witBanner","sectionHead","secLabel","opts","esc","TOAST_HTML","TOAST_SCRIPT","ROW_CARD_SCRIPT"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{existsSync as a,mkdirSync as d,readdirSync as l,readFileSync as h,unlinkSync as o,writeFileSync as m}from"fs";import{resolve as s}from"path";import{randomUUID as f}from"crypto";import{z as n}from"zod";var u=n.object({id:n.string(),runId:n.string(),workflowId:n.string(),nodeId:n.string(),fireAt:n.string(),cancelKey:n.string().optional(),createdAt:n.string()}),c=class{baseDir;tickIntervalMs;log;timer;callback;firedIds=new Set;constructor(e={}){let t=e.baseDir??s(process.cwd(),".agentx/workflows");this.baseDir=s(t,"_timers"),d(this.baseDir,{recursive:!0}),this.tickIntervalMs=e.tickIntervalMs??1e3,this.log=e.log??(()=>{})}onFire(e){this.callback=e}start(){this.timer||(this.log(`[timers] ticking every ${this.tickIntervalMs}ms \u2014 scanning ${this.baseDir}`),this.tick(),this.timer=setInterval(()=>this.tick(),this.tickIntervalMs),typeof this.timer.unref=="function"&&this.timer.unref())}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0)}schedule(e){let t={id:f(),runId:e.runId,workflowId:e.workflowId,nodeId:e.nodeId,fireAt:e.fireAt,cancelKey:e.cancelKey,createdAt:new Date().toISOString()};return m(this.pathFor(t.id),JSON.stringify(t,null,2)),t}cancel(e){let t=0;for(let i of this.list())if(!(e.runId&&i.runId!==e.runId)&&!(e.nodeId&&i.nodeId!==e.nodeId)&&!(e.cancelKey&&i.cancelKey!==e.cancelKey))try{o(this.pathFor(i.id)),t++}catch{}return t}list(){if(!a(this.baseDir))return[];let e=[];for(let t of l(this.baseDir,{withFileTypes:!0}))if(!(!t.isFile()||!t.name.endsWith(".json")))try{let i=JSON.parse(h(s(this.baseDir,t.name),"utf-8")),r=u.safeParse(i);r.success&&e.push(r.data)}catch{}return e}pathFor(e){return s(this.baseDir,`${e}.json`)}tick(){if(!this.callback)return;let e=Date.now();for(let t of this.list()){if(this.firedIds.has(t.id))continue;let i=Date.parse(t.fireAt);if(!(Number.isNaN(i)||i>e)){this.firedIds.add(t.id);try{o(this.pathFor(t.id))}catch{}try{Promise.resolve(this.callback(t)).catch(r=>this.log(`[timers] fire callback threw: ${r?.message??r}`))}catch(r){this.log(`[timers] fire dispatch failed: ${r?.message??r}`)}}}}};export{u as a,c as b};
|
|
2
|
+
//# sourceMappingURL=chunk-AFRHKA7B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflows/timers.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\n\n// --- Durable timer service ---\n//\n// Persists pending timers under .agentx/workflows/_timers/<id>.json so a\n// daemon restart doesn't forget them. A 1s tick loop fires due timers by\n// invoking a registered callback. The workflow dispatcher registers its\n// resume-on-timer callback at boot.\n//\n// Timer model — v1:\n// - Each timer is keyed to a (runId, nodeId) pair so `timer.boundary`\n// nodes can be cancelled when the attached node completes before the\n// timer elapses.\n// - Fire-once semantics. No repeating timers at this layer (use\n// trigger.cron at the workflow level for recurrence).\n// - Best-effort delivery: a missed tick (daemon crash, clock skew) is\n// honoured on next startup by scanning persisted timers with fireAt\n// in the past.\n\nexport const timerRecordSchema = z.object({\n id: z.string(),\n runId: z.string(),\n workflowId: z.string(),\n nodeId: z.string(),\n /** ISO-8601 instant when the timer should fire. */\n fireAt: z.string(),\n /** Optional key to cancel matching timers (e.g., when the attached node\n * completes normally and the escalation timer should be cancelled). */\n cancelKey: z.string().optional(),\n createdAt: z.string(),\n})\nexport type TimerRecord = z.infer<typeof timerRecordSchema>\n\nexport interface TimerCallback {\n (t: TimerRecord): void | Promise<void>\n}\n\nexport interface TimerServiceOptions {\n baseDir?: string\n /** Tick interval, in ms. Default 1000. */\n tickIntervalMs?: number\n log?: (msg: string) => void\n}\n\nexport class TimerService {\n readonly baseDir: string\n private readonly tickIntervalMs: number\n private readonly log: (msg: string) => void\n private timer?: ReturnType<typeof setInterval>\n private callback?: TimerCallback\n private firedIds: Set<string> = new Set()\n\n constructor(opts: TimerServiceOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx/workflows\")\n this.baseDir = resolve(root, \"_timers\")\n mkdirSync(this.baseDir, { recursive: true })\n this.tickIntervalMs = opts.tickIntervalMs ?? 1000\n this.log = opts.log ?? (() => {})\n }\n\n /** Register the fire callback. Overwrites any prior callback. Typical\n * caller is the dispatcher's \"resume on timer\" path. */\n onFire(cb: TimerCallback): void {\n this.callback = cb\n }\n\n start(): void {\n if (this.timer) return\n this.log(`[timers] ticking every ${this.tickIntervalMs}ms — scanning ${this.baseDir}`)\n // Honour missed ticks on startup: scan all persisted timers once.\n this.tick()\n this.timer = setInterval(() => this.tick(), this.tickIntervalMs)\n // Don't hold the event loop open on daemon shutdown.\n if (typeof this.timer.unref === \"function\") this.timer.unref()\n }\n\n stop(): void {\n if (!this.timer) return\n clearInterval(this.timer)\n this.timer = undefined\n }\n\n schedule(args: {\n runId: string\n workflowId: string\n nodeId: string\n fireAt: string\n cancelKey?: string\n }): TimerRecord {\n const record: TimerRecord = {\n id: randomUUID(),\n runId: args.runId,\n workflowId: args.workflowId,\n nodeId: args.nodeId,\n fireAt: args.fireAt,\n cancelKey: args.cancelKey,\n createdAt: new Date().toISOString(),\n }\n writeFileSync(this.pathFor(record.id), JSON.stringify(record, null, 2))\n return record\n }\n\n /** Cancel any timers matching the filter. Returns how many were removed. */\n cancel(filter: { runId?: string; nodeId?: string; cancelKey?: string }): number {\n let removed = 0\n for (const t of this.list()) {\n if (filter.runId && t.runId !== filter.runId) continue\n if (filter.nodeId && t.nodeId !== filter.nodeId) continue\n if (filter.cancelKey && t.cancelKey !== filter.cancelKey) continue\n try { unlinkSync(this.pathFor(t.id)); removed++ } catch { /* already gone */ }\n }\n return removed\n }\n\n list(): TimerRecord[] {\n if (!existsSync(this.baseDir)) return []\n const out: TimerRecord[] = []\n for (const entry of readdirSync(this.baseDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.baseDir, entry.name), \"utf-8\"))\n const parsed = timerRecordSchema.safeParse(raw)\n if (parsed.success) out.push(parsed.data)\n } catch { /* skip malformed */ }\n }\n return out\n }\n\n private pathFor(id: string): string {\n return resolve(this.baseDir, `${id}.json`)\n }\n\n private tick(): void {\n if (!this.callback) return\n const now = Date.now()\n for (const t of this.list()) {\n if (this.firedIds.has(t.id)) continue\n const fireAtMs = Date.parse(t.fireAt)\n if (Number.isNaN(fireAtMs) || fireAtMs > now) continue\n // Mark fired BEFORE calling the callback so a callback that loops\n // back into the walk (and re-schedules siblings) doesn't re-trigger.\n this.firedIds.add(t.id)\n try { unlinkSync(this.pathFor(t.id)) } catch { /* may race with cancel */ }\n try {\n void Promise.resolve(this.callback(t)).catch((e: any) => this.log(`[timers] fire callback threw: ${e?.message ?? e}`))\n } catch (e: any) {\n this.log(`[timers] fire dispatch failed: ${e?.message ?? e}`)\n }\n }\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","randomUUID","z","timerRecordSchema","TimerService","opts","root","cb","args","record","filter","removed","t","out","entry","raw","parsed","id","now","fireAtMs","e"]}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import{a as k,b as A,c as f,d,e as b,f as p}from"./chunk-6PPIFDOQ.js";import{e as w}from"./chunk-LY7JGDV7.js";import{existsSync as h,readFileSync as I,writeFileSync as P,appendFileSync as H,mkdirSync as M}from"fs";import{resolve as u}from"path";import{hostname as J}from"os";import{existsSync as T,readFileSync as S,writeFileSync as v,copyFileSync as j}from"fs";import{resolve as _}from"path";function C(e,t={}){let n=t.configPath||_(process.cwd(),"agentx.json");if(!T(n))throw new Error(`agentx.json not found at ${n}`);let a=JSON.parse(S(n,"utf-8")),r=e(a),o=typeof r=="string"?{summary:r}:r,i=JSON.stringify(a,null,2)+`
|
|
2
|
+
`,c=n+".tmp";v(c,i,"utf-8");try{JSON.parse(S(c,"utf-8"))}catch(g){throw new Error(`config did not round-trip cleanly: ${g.message}`)}let l;return t.skipBackup||(l=`${n}.bak.${Date.now()}`,j(n,l)),v(n,i,"utf-8"),t.skipReload||U().catch(()=>{}),{summary:o.summary,payload:o.payload,backupPath:l}}function E(e,t={}){let n=t.configPath||_(process.cwd(),"agentx.json");if(T(n)&&!t.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return v(n,JSON.stringify(e,null,2)+`
|
|
3
|
+
`,"utf-8"),{file:n}}async function U(){try{let t=w().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${t}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function z(e,t){let n=e.configExists?e.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",a=`<header class="ax-topbar">
|
|
4
|
+
<div class="ax-topbar__left">
|
|
5
|
+
<div class="ax-brand">
|
|
6
|
+
<span class="ax-brand__mark">AX</span>
|
|
7
|
+
<span class="ax-brand__name">AgentX</span>
|
|
8
|
+
<span class="ax-brand__subtitle">Setup</span>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="ax-topbar__right">
|
|
12
|
+
<a href="/glossary" class="ax-skip">? Glossary</a>
|
|
13
|
+
<a href="/live" class="ax-skip">Skip \u2014 open dashboard</a>
|
|
14
|
+
<div class="ax-theme-switch" role="tablist" aria-label="Theme">
|
|
15
|
+
<button data-theme-opt="dark">Dark</button>
|
|
16
|
+
<button data-theme-opt="light">Light</button>
|
|
17
|
+
<button data-theme-opt="crt">CRT</button>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</header>`,r=p(1,"Team basics",d({name:"nodeName",label:"Team name",hint:"(what you'd call this AgentX install)",value:t,placeholder:"My Team",required:!0})),o=p(2,"First agent",b([d({name:"agentName",label:"Agent name",value:"Assistant",placeholder:"Support Bot",required:!0}),d({name:"agentId",label:"Agent id",hint:"(lowercase, no spaces)",value:"assistant",pattern:"[a-z0-9][a-z0-9_-]*",required:!0})])+d({name:"triggerWords",label:"Trigger words",hint:"(comma or space separated \u2014 e.g. <code>@support, support</code>)",value:"@assistant, assistant",required:!0})+b([d({name:"tier",label:"AI engine",type:"select",options:[{value:"claude-code",label:"Claude Code (recommended)",selected:!0},{value:"sdk",label:"Anthropic API (BYO key)"}]}),d({name:"model",label:"Model",hint:"(optional)",value:"claude-sonnet-4-6",placeholder:"claude-sonnet-4-6"})])+d({name:"personality",label:"Personality / instructions",hint:"(optional \u2014 what this agent does, in plain English)",type:"textarea",placeholder:"You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short."})),i=p(3,"First channel",`
|
|
21
|
+
<label class="ax-toggle"><input type="checkbox" id="enableTelegram" /> Connect Telegram now</label>
|
|
22
|
+
<div class="ax-optional" id="telegramFields">
|
|
23
|
+
${d({name:"telegramAccountId",label:"Account id",hint:"(a label you choose \u2014 e.g. <code>support</code>)",value:"default"})}
|
|
24
|
+
${d({name:"telegramBotToken",label:"Bot token",hint:'(from <a href="https://t.me/BotFather" target="_blank">@BotFather</a>)',placeholder:"123456:ABCdef..."})}
|
|
25
|
+
${d({name:"telegramBotUsername",label:"Bot username",hint:"(optional \u2014 e.g. <code>my_support_bot</code>)",placeholder:"my_support_bot"})}
|
|
26
|
+
</div>
|
|
27
|
+
`),c=p(4,"Anthropic API key",d({name:"anthropicApiKey",label:"API key",hint:"(optional \u2014 skip if you're using Claude Code)",type:"password",placeholder:"sk-ant-\u2026",extraAttrs:'autocomplete="off"'})+`<div class="ax-hint-block">
|
|
28
|
+
The key is written to <code>.env</code> as <code>ANTHROPIC_API_KEY</code>. AgentX never transmits it \u2014 everything stays on this machine.
|
|
29
|
+
</div>`),l=e.configExists?`<div class="ax-banner">Existing install: <b>${e.agentCount}</b> agent(s), <b>${e.channelCount}</b> channel(s) already configured. This wizard will add to it.</div>`:"",g=`<div class="ax-wizard">
|
|
30
|
+
<h1 class="ax-wizard__h1">${A(n)}</h1>
|
|
31
|
+
<p class="ax-wizard__lead">Fill in the basics below. We'll write <code>agentx.json</code>, set up a folder for your first agent, and point you at the next step.</p>
|
|
32
|
+
|
|
33
|
+
${l}
|
|
34
|
+
|
|
35
|
+
<div id="msg" class="ax-msg"></div>
|
|
36
|
+
|
|
37
|
+
<form id="wizard" autocomplete="off">
|
|
38
|
+
${r}
|
|
39
|
+
${o}
|
|
40
|
+
${i}
|
|
41
|
+
${c}
|
|
42
|
+
<footer class="ax-actions">
|
|
43
|
+
${f("Save and continue",{type:"submit",primary:!0,id:"submitBtn"})}
|
|
44
|
+
${f("Skip, I'll do it later",{onclick:"window.location.href='/live'"})}
|
|
45
|
+
</footer>
|
|
46
|
+
</form>
|
|
47
|
+
</div>`;return k({title:"AgentX \u2014 Setup",activeTab:"custom",subtitle:"Setup",customHeader:a,noMain:!0,body:g,css:D,scripts:K})}var D=`
|
|
48
|
+
/* Pre-dashboard topbar variant \u2014 no tab nav, the pills are the only chrome. */
|
|
49
|
+
.ax-topbar__right a.ax-skip {
|
|
50
|
+
padding: 4px 10px; border: 1px solid var(--ax-border-2); border-radius: 4px;
|
|
51
|
+
color: var(--ax-text-2); font-size: var(--ax-fs-xs); text-decoration: none;
|
|
52
|
+
}
|
|
53
|
+
.ax-topbar__right a.ax-skip:hover { color: var(--ax-accent); border-color: var(--ax-accent); }
|
|
54
|
+
|
|
55
|
+
/* Wizard frame */
|
|
56
|
+
.ax-wizard { max-width: 680px; margin: 0 auto; padding: 36px 22px 80px; }
|
|
57
|
+
.ax-wizard__h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; letter-spacing: -0.015em; }
|
|
58
|
+
.ax-wizard__lead { color: var(--ax-muted); margin: 0 0 24px; font-size: var(--ax-fs); line-height: 1.55; }
|
|
59
|
+
.ax-wizard__lead code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-size: var(--ax-fs-sm); }
|
|
60
|
+
|
|
61
|
+
/* Info banner ("You already have N agents\u2026") */
|
|
62
|
+
.ax-banner {
|
|
63
|
+
background: color-mix(in oklch, var(--ax-accent) 10%, var(--ax-bg-elev));
|
|
64
|
+
border: 1px solid color-mix(in oklch, var(--ax-accent) 30%, var(--ax-border-2));
|
|
65
|
+
border-radius: var(--ax-radius);
|
|
66
|
+
padding: 10px 14px; margin-bottom: 18px;
|
|
67
|
+
color: var(--ax-text-2); font-size: var(--ax-fs-sm);
|
|
68
|
+
}
|
|
69
|
+
.ax-banner b { color: var(--ax-text); font-weight: 600; }
|
|
70
|
+
|
|
71
|
+
/* "Connect Telegram now" toggle + collapsible fields */
|
|
72
|
+
.ax-toggle { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; font-size: var(--ax-fs); color: var(--ax-text); cursor: pointer; user-select: none; }
|
|
73
|
+
.ax-toggle input { width: auto; accent-color: var(--ax-accent); }
|
|
74
|
+
.ax-optional { border-left: 2px solid var(--ax-border-2); padding-left: 14px; margin-top: 10px; display: none; }
|
|
75
|
+
.ax-optional.is-show { display: block; }
|
|
76
|
+
.ax-hint-block { font-size: var(--ax-fs-xs); color: var(--ax-muted); margin-top: 8px; line-height: 1.55; }
|
|
77
|
+
.ax-hint-block code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-xs); }
|
|
78
|
+
|
|
79
|
+
/* Form footer */
|
|
80
|
+
.ax-actions {
|
|
81
|
+
display: flex; gap: 10px; padding: 20px 0 0; align-items: center;
|
|
82
|
+
border-top: 1px solid var(--ax-border); margin-top: 20px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Server-response messaging (success / error blocks) */
|
|
86
|
+
.ax-msg { margin: 14px 0; padding: 12px 16px; border-radius: var(--ax-radius); font-size: var(--ax-fs); display: none; }
|
|
87
|
+
.ax-msg.is-ok {
|
|
88
|
+
display: block;
|
|
89
|
+
background: color-mix(in oklch, var(--ax-accent) 12%, transparent);
|
|
90
|
+
color: var(--ax-accent);
|
|
91
|
+
border: 1px solid color-mix(in oklch, var(--ax-accent) 40%, transparent);
|
|
92
|
+
}
|
|
93
|
+
.ax-msg.is-err {
|
|
94
|
+
display: block;
|
|
95
|
+
background: color-mix(in oklch, var(--ax-err) 12%, transparent);
|
|
96
|
+
color: var(--ax-err);
|
|
97
|
+
border: 1px solid color-mix(in oklch, var(--ax-err) 40%, transparent);
|
|
98
|
+
}
|
|
99
|
+
.ax-next-steps { margin-top: 12px; padding-left: 20px; color: var(--ax-text-2); }
|
|
100
|
+
.ax-next-steps li { margin: 4px 0; }
|
|
101
|
+
.ax-next-steps code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-sm); }`,K=`<script>
|
|
102
|
+
document.getElementById('enableTelegram').addEventListener('change', (e) => {
|
|
103
|
+
document.getElementById('telegramFields').classList.toggle('is-show', e.target.checked);
|
|
104
|
+
});
|
|
105
|
+
document.getElementById('wizard').addEventListener('submit', async (e) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
const f = e.target;
|
|
108
|
+
const msg = document.getElementById('msg');
|
|
109
|
+
const btn = document.getElementById('submitBtn');
|
|
110
|
+
msg.className = 'ax-msg'; msg.style.display = 'none';
|
|
111
|
+
btn.disabled = true; btn.textContent = 'Saving\u2026';
|
|
112
|
+
const telegramEnabled = document.getElementById('enableTelegram').checked;
|
|
113
|
+
const payload = {
|
|
114
|
+
nodeName: f.nodeName.value.trim(),
|
|
115
|
+
agent: {
|
|
116
|
+
id: f.agentId.value.trim(),
|
|
117
|
+
name: f.agentName.value.trim(),
|
|
118
|
+
triggerWords: f.triggerWords.value.trim(),
|
|
119
|
+
tier: f.tier.value,
|
|
120
|
+
model: f.model.value.trim() || undefined,
|
|
121
|
+
personality: f.personality.value.trim() || undefined,
|
|
122
|
+
},
|
|
123
|
+
anthropicApiKey: f.anthropicApiKey.value.trim() || undefined,
|
|
124
|
+
};
|
|
125
|
+
if (telegramEnabled) {
|
|
126
|
+
payload.telegram = {
|
|
127
|
+
accountId: f.telegramAccountId.value.trim() || 'default',
|
|
128
|
+
botToken: f.telegramBotToken.value.trim(),
|
|
129
|
+
botUsername: f.telegramBotUsername.value.trim() || undefined,
|
|
130
|
+
};
|
|
131
|
+
if (!payload.telegram.botToken) {
|
|
132
|
+
msg.className = 'ax-msg is-err'; msg.textContent = 'Bot token is required when Telegram is enabled.';
|
|
133
|
+
btn.disabled = false; btn.textContent = 'Save and continue'; return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const r = await fetch('/api/setup', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'agentx-wizard' },
|
|
140
|
+
body: JSON.stringify(payload),
|
|
141
|
+
});
|
|
142
|
+
const data = await r.json();
|
|
143
|
+
if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));
|
|
144
|
+
const steps = data.nextSteps.map((s) => '<li>' + s.replace(/\\\`([^\\\`]+)\\\`/g, '<code>$1</code>') + '</li>').join('');
|
|
145
|
+
msg.className = 'ax-msg is-ok';
|
|
146
|
+
msg.innerHTML = '<b>' + data.summary + '</b><ol class="ax-next-steps">' + steps + '</ol>';
|
|
147
|
+
btn.textContent = 'Done';
|
|
148
|
+
btn.disabled = true;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
msg.className = 'ax-msg is-err'; msg.textContent = err.message;
|
|
151
|
+
btn.disabled = false; btn.textContent = 'Save and continue';
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
</script>`;function N(e=process.cwd()){let t=u(e,"agentx.json");if(!h(t))return{configExists:!1,agentCount:0,channelCount:0};try{let n=JSON.parse(I(t,"utf-8")),a=n.agents?Object.keys(n.agents).length:0,r=n.channels?Object.values(n.channels).filter(o=>o&&o.enabled).length:0;return{configExists:!0,agentCount:a,channelCount:r,nodeName:n.node?.name}}catch{return{configExists:!1,agentCount:0,channelCount:0}}}function q(e,t=process.cwd()){if(!e.nodeName?.trim())throw new Error("Team name is required.");if(!e.agent?.id?.trim())throw new Error("Agent id is required.");if(!/^[a-z0-9][a-z0-9_-]*$/.test(e.agent.id))throw new Error("Agent id must be lowercase (letters, digits, -, _) and start with a letter or digit.");if(!e.agent.name?.trim())throw new Error("Agent name is required.");let n=L(e.agent.triggerWords);if(n.length===0)throw new Error("At least one trigger word is required.");if(e.agent.tier!=="claude-code"&&e.agent.tier!=="sdk")throw new Error("AI engine must be claude-code or sdk.");let a=u(t,"agentx.json"),r=u(t,".env"),o=u(t,"agents",e.agent.id),i=N(t);M(o,{recursive:!0});let c=u(o,"CLAUDE.md");if(!h(c)){let x=e.agent.personality?.trim()||`You are ${e.agent.name}, an assistant on the ${e.nodeName} team.`;P(c,`# ${e.agent.name}
|
|
155
|
+
|
|
156
|
+
${x}
|
|
157
|
+
`)}let l=e.telegram?`TG_${e.agent.id.toUpperCase()}_BOT_TOKEN`:void 0,g;if(i.configExists)g=C(s=>{if(s.node=s.node||{},s.node.name||(s.node.name=e.nodeName),s.node.id||(s.node.id=$(e.nodeName)||"team"),s.node.bind||(s.node.bind="127.0.0.1:18800"),s.node.defaultAgent=s.node.defaultAgent||e.agent.id,s.providers=s.providers||{},s.providers.claude||(s.providers.claude={apiKey:"${ANTHROPIC_API_KEY}"}),s.agents=s.agents||{},s.agents[e.agent.id])throw new Error(`Agent "${e.agent.id}" already exists.`);return s.agents[e.agent.id]=O(e,o,n),e.telegram&&(s.channels=s.channels||{},s.channels.telegram=s.channels.telegram||{enabled:!0,accounts:{},policy:{dm:"pair",group:"mention-required"}},s.channels.telegram.enabled=!0,s.channels.telegram.accounts=s.channels.telegram.accounts||{},s.channels.telegram.accounts[e.telegram.accountId]=R(e,l)),{summary:`added agent "${e.agent.id}"`+(e.telegram?" + telegram account":"")}},{configPath:a}).backupPath;else{let x=Y(e,o,n,l);E(x,{configPath:a})}X(r,e,l);let m=[];return(e.anthropicApiKey||!h(u(t,".env")))&&m.push("Double-check the API keys in .env"),e.agent.tier==="claude-code"&&m.push("Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)"),m.push("Start the daemon: agentx daemon start"),m.push("Open the dashboard: http://127.0.0.1:4202"),{summary:i.configExists?`Added agent "${e.agent.id}" to your team.`:`Initialized your team with first agent "${e.agent.id}".`,nextSteps:m,backupPath:g}}function L(e){return(e||"").split(/[,\s]+/).map(t=>t.trim()).filter(Boolean).filter((t,n,a)=>a.indexOf(t)===n)}function $(e){return(e||"").toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"")}function Y(e,t,n,a){let o={node:{id:$(e.nodeName)||"team",name:e.nodeName,bind:"127.0.0.1:18800",defaultAgent:e.agent.id},providers:{claude:{apiKey:"${ANTHROPIC_API_KEY}"}},agents:{[e.agent.id]:O(e,t,n)},channels:{},crons:{},mesh:{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},dashboard:{enabled:!0,port:4202,bind:"127.0.0.1",daemonUrl:"http://localhost:18800"},boards:[]};return e.telegram&&a?o.channels.telegram={enabled:!0,accounts:{[e.telegram.accountId]:R(e,a)},policy:{dm:"pair",group:"mention-required"}}:o.channels.telegram={enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},o}function O(e,t,n){let a={name:e.agent.name,workspace:t,tier:e.agent.tier,mentions:n,maxConcurrent:2,permissionMode:"default"};return e.agent.model&&(a.model=e.agent.model),e.agent.personality?.trim()&&(a.systemPrompt=e.agent.personality.trim().slice(0,400)),a}function R(e,t){return{botToken:"${"+t+"}",botUsername:e.telegram?.botUsername||"",agentBinding:e.agent.id}}function X(e,t,n){let a=h(e)?I(e,"utf-8"):"",r=[];if(t.anthropicApiKey&&!a.includes("ANTHROPIC_API_KEY=")&&r.push(`ANTHROPIC_API_KEY=${t.anthropicApiKey}`),t.telegram&&n&&!a.includes(`${n}=`)&&r.push(`${n}=${t.telegram.botToken}`),!h(e)){P(e,[...["# AgentX environment variables","# (Never commit this file \u2014 it contains secrets.)",""],...r,""].join(`
|
|
158
|
+
`));return}if(r.length>0){let o=a.endsWith(`
|
|
159
|
+
`)?"":`
|
|
160
|
+
`;H(e,o+r.join(`
|
|
161
|
+
`)+`
|
|
162
|
+
`)}}function xe(e,t){let n=N(),a=n.nodeName||J().replace(/\.local$/,""),r=z(n,a);t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r)}async function fe(e,t){let n="";await new Promise(r=>{e.on("data",o=>{n+=o}),e.on("end",()=>r())});let a;try{a=JSON.parse(n)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid JSON"}));return}try{let r=q(a);t.writeHead(200,{"Content-Type":"application/json"}),t.end(JSON.stringify(r))}catch(r){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:r.message||"wizard failed"}))}}import{createHash as G,randomBytes as B}from"crypto";import{existsSync as V,mkdirSync as Q,readFileSync as Z,writeFileSync as ee}from"fs";import{resolve as te,dirname as ne}from"path";var y="agx_live_",re=".agentx/tokens.json",W=class{file;constructor(t=process.cwd(),n=re){this.file=te(t,n)}load(){if(!V(this.file))return[];try{return JSON.parse(Z(this.file,"utf-8"))}catch{return[]}}save(t){Q(ne(this.file),{recursive:!0}),ee(this.file,JSON.stringify(t,null,2)+`
|
|
163
|
+
`,"utf-8")}create(t){if(!t.name?.trim())throw new Error("Token name is required.");if(!Array.isArray(t.scopes)||t.scopes.length===0)throw new Error("At least one scope is required.");for(let i of t.scopes)if(!ae(i))throw new Error(`Invalid scope: ${i}`);let n=B(32).toString("hex"),a=y+n,r={id:"tok_"+B(4).toString("hex"),name:t.name.trim(),prefix:y+n.slice(0,4),hash:F(a),scopes:[...t.scopes],createdAt:new Date().toISOString(),expiresAt:t.expiresInDays?new Date(Date.now()+t.expiresInDays*86400*1e3).toISOString():void 0},o=this.load();return o.push(r),this.save(o),{token:a,record:r}}list(){return this.load()}revoke(t){let n=this.load(),a=n.find(r=>r.id===t);return a?(a.revokedAt=new Date().toISOString(),this.save(n),a):null}verify(t){if(!t||!t.startsWith(y))return null;let n=F(t),a=this.load(),r=a.find(c=>c.hash===n);if(!r||r.revokedAt||r.expiresAt&&Date.parse(r.expiresAt)<Date.now())return null;let o=Date.now(),i=r.lastUsedAt?Date.parse(r.lastUsedAt):0;if(o-i>6e4){r.lastUsedAt=new Date(o).toISOString();try{this.save(a)}catch{}}return r}};function ke(e,t){return!!(e.scopes.includes(t)||t.startsWith("agent:")&&e.scopes.includes("agent:*")||t==="dashboard:read"&&e.scopes.includes("dashboard:write"))}function ae(e){return e==="dashboard:read"||e==="dashboard:write"||e==="agent:*"||/^agent:[a-z0-9][a-z0-9_-]*$/.test(e)||e==="mesh:peer"}function F(e){return"sha256:"+G("sha256").update(e).digest("hex")}function Ae(e){let t=e.headers.authorization,n=Array.isArray(t)?t[0]:t;if(n&&n.toLowerCase().startsWith("bearer "))return n.slice(7).trim();if(e.url)try{let r=new URL(e.url,"http://localhost").searchParams.get("token");if(r)return r}catch{}return null}export{C as a,N as b,xe as c,fe as d,W as e,ke as f,Ae as g};
|
|
164
|
+
//# sourceMappingURL=chunk-ASSJ4Z6Z.js.map
|