maqcli 0.7.0 → 0.9.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.
@@ -1,462 +1,633 @@
1
1
  /**
2
- * webui.ts — the self-contained "god-level" control UI the daemon serves at
3
- * `/app` (and `/`). Zero build step, zero dependencies: one HTML document with
4
- * inline CSS + vanilla JS. It renders the SAME normalized event stream the
2
+ * webui.ts — the self-contained "Megalodon" control surface the daemon serves
3
+ * at `/app` (and `/`). Zero build step, zero dependencies: one HTML document
4
+ * with inline CSS + vanilla JS. It renders the SAME normalized event stream the
5
5
  * mobile app consumes, so there is no second source of truth.
6
6
  *
7
- * Layout (Cursor-style, megalodon theme deep black + electric blue + white):
8
- * left ────────────┬ center ───────────────────────┬ right (collapsible)
9
- * sessions/agents/ goal input + mode toggle │ preview: session │
10
- * history │ (parallel|loop|safe) + send │ detail / result │
11
- * │ │ phase chips + live SSE console │ JSON │
12
- * └──────────────────┴────────────────────────────────┴──────────────────────┘
13
- * A feature switcher (top bar) runs detect / doctor / connectivity / models.
7
+ * This is a from-spec rebuild (maq_cli_ui_spec Phases 4-6). The recommended
8
+ * SPA stack (React + Monaco + Framer + react-resizable-panels) is deliberately
9
+ * NOT used: the daemon ships one static asset with no build pipeline, so the
10
+ * same UX is delivered dependency-free
11
+ * - real drag-resizable 3-panel shell (custom pointer-drag splitters)
12
+ * - universal preview: JSON tree / code+line-numbers / markdown / sandboxed
13
+ * HTML / images (replaces Monaco + react-markdown + react-json-view)
14
+ * - send button with a toggle-up mode picker popover (Single/Parallel/Loop/
15
+ * Safe), mode icon on the button face, selection persisted
16
+ * - orchestration visualizations derived from the event stream (parallel
17
+ * round dashboard / loop progress / safe split→merge→validate rail)
18
+ * - custom animated cursor follower + micro-interactions (CSS, not Framer)
14
19
  *
15
- * Auth: the daemon requires a Bearer token for /v1/* . The page reads it from
16
- * ?token=… or a prompt, keeps it in memory only, and streams SSE via fetch +
17
- * ReadableStream (so the token rides in the Authorization header, never the URL).
20
+ * Theme (Megalodon): deep matte black base, controlled blue-white accents (not
21
+ * neon), sparse red for status/danger, white primary text, no gradients except
22
+ * intentional depth cues. Everything references CSS custom-property tokens.
23
+ *
24
+ * Auth: the daemon requires a Bearer token for /v1/*. The page reads it from
25
+ * ?key= / ?token= or a prompt, keeps it in memory only, and streams SSE via
26
+ * fetch + ReadableStream (token in the Authorization header, never the URL).
18
27
  */
19
28
  export function webuiHtml(version) {
20
- // NB: the client script uses only single/double quotes and string
21
- // concatenation — no backticks / ${} — so it lives safely inside this
22
- // template literal.
29
+ // NB: client script uses only single/double quotes + string concatenation —
30
+ // no backticks / ${} — so it lives safely inside this template literal.
23
31
  return `<!doctype html>
24
32
  <html lang="en"><head>
25
33
  <meta charset="utf-8">
26
34
  <meta name="viewport" content="width=device-width, initial-scale=1">
27
- <title>MAQ · megalodon</title>
35
+ <title>MAQ · Megalodon</title>
28
36
  <style>
37
+ @media (prefers-reduced-motion: no-preference){ :root{ --dur-1:140ms; --dur-2:240ms; --ease:cubic-bezier(.2,.8,.2,1) } }
38
+ @media (prefers-reduced-motion: reduce){ :root{ --dur-1:0ms; --dur-2:0ms; --ease:linear } *{animation-duration:0ms !important;transition-duration:0ms !important} }
29
39
  :root{
30
- --bg:#04060c; --bg2:#080c16; --panel:#0b1120; --panel2:#0e1526; --edge:#182339;
31
- --ink:#eaf1ff; --mut:#8695b3; --blue:#2f81f7; --blue2:#5aa2ff; --white:#ffffff;
32
- --red:#ff4d5e; --ok:#3ad29f; --warn:#f5b44a;
40
+ /* Megalodon: deep matte black + blue-white + sparse red */
41
+ --bg:#04070d; --surface:#090d16; --surface-2:#0e131f; --surface-3:#141a29;
42
+ --border:#1a2233; --border-strong:#28324a; --edge-soft:#131926;
43
+ --ink:#eef2fb; --ink-dim:#8a94ab; --ink-faint:#565f76;
44
+ --accent:#4d8dff; --accent-2:#93b8ff; --accent-ink:#04122e;
45
+ --accent-soft:rgba(77,141,255,.13); --accent-line:rgba(77,141,255,.42); --accent-glow:rgba(77,141,255,.28);
46
+ --danger:#ff4d5e; --danger-soft:rgba(255,77,94,.13);
47
+ --ok:#37d9a0; --ok-soft:rgba(55,217,160,.13);
48
+ --warn:#f5b544; --warn-soft:rgba(245,181,68,.13);
49
+ --fs-11:.6875rem; --fs-12:.75rem; --fs-13:.8125rem; --fs-15:.9375rem; --fs-19:1.1875rem; --fs-24:1.5rem;
50
+ --font-display:'Space Grotesk',ui-sans-serif,system-ui,sans-serif;
51
+ --font-body:'Inter',ui-sans-serif,-apple-system,'Segoe UI',Roboto,sans-serif;
52
+ --font-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
53
+ --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px;
54
+ --r:10px; --r-sm:7px; --r-lg:14px;
55
+ --wl:266px; --wr:0px; --shadow:0 16px 48px rgba(0,0,0,.5);
33
56
  }
34
57
  *{box-sizing:border-box} html,body{height:100%}
35
- body{margin:0;background:
36
- radial-gradient(1200px 500px at 70% -10%, #0b1c38 0%, transparent 60%),
37
- radial-gradient(900px 500px at 0% 110%, #0a1730 0%, transparent 55%), var(--bg);
38
- color:var(--ink); font:14px/1.5 ui-sans-serif,-apple-system,Segoe UI,Roboto,sans-serif}
39
- .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
40
- header{display:flex;align-items:center;gap:14px;padding:10px 16px;border-bottom:1px solid var(--edge);
41
- background:linear-gradient(180deg,#0a1224,#070b15)}
42
- .brand{font-weight:800;letter-spacing:3px;font-size:16px}
43
- .brand .fin{color:var(--red)} .brand .m{color:var(--blue2)}
44
- .dot{width:9px;height:9px;border-radius:50%;background:var(--mut);box-shadow:0 0 0 3px #ffffff10}
45
- .dot.on{background:var(--ok);box-shadow:0 0 10px var(--ok)}
58
+ body{margin:0;background:var(--bg);color:var(--ink);font:var(--fs-13)/1.55 var(--font-body);-webkit-font-smoothing:antialiased}
59
+ @media (pointer:fine) and (prefers-reduced-motion: no-preference){ body{cursor:none} a,button,[role=button],input,textarea,select,.resize{cursor:none} }
60
+ h1,h2,h3,h4{font-family:var(--font-display);font-weight:600;margin:0;letter-spacing:-.01em}
61
+ a{color:var(--accent-2)}
62
+ button,input,select,textarea{font-family:inherit;color:inherit}
63
+ :focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:5px}
64
+ .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
65
+ .eyebrow{font-size:var(--fs-11);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-faint);font-weight:600}
66
+ ::-webkit-scrollbar{width:9px;height:9px} ::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:9px} ::-webkit-scrollbar-track{background:transparent}
67
+
68
+ /* custom cursor */
69
+ #cur,#curDot{position:fixed;top:0;left:0;pointer-events:none;z-index:9999;border-radius:50%;translate:-50% -50%}
70
+ #cur{width:26px;height:26px;border:1.5px solid var(--accent-line);transition:width var(--dur-1) var(--ease),height var(--dur-1) var(--ease),background var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
71
+ #cur.hot{width:40px;height:40px;background:var(--accent-soft);border-color:var(--accent)}
72
+ #cur.press{width:20px;height:20px}
73
+ #curDot{width:5px;height:5px;background:var(--accent)}
74
+ @media (pointer:coarse),(prefers-reduced-motion: reduce){ #cur,#curDot{display:none !important} }
75
+
76
+ /* app shell */
77
+ #app{display:grid;height:100vh;grid-template-rows:54px 1fr;
78
+ grid-template-columns:var(--wl) 5px 1fr 5px var(--wr);
79
+ grid-template-areas:"top top top top top" "side rl main rr prev"}
80
+ #app.no-prev{grid-template-columns:var(--wl) 5px 1fr 0px 0px}
81
+ @media (max-width:900px){
82
+ #app{grid-template-columns:1fr;grid-template-areas:"top" "main"}
83
+ #side{position:fixed;inset:54px auto 0 0;width:284px;z-index:60;transform:translateX(-100%);transition:transform var(--dur-2) var(--ease)}
84
+ #side.open{transform:none} #rl,#rr,#prev{display:none}
85
+ #scrim{position:fixed;inset:54px 0 0 0;background:#000b;z-index:55;display:none} #scrim.on{display:block}
86
+ #app.prev-sheet #prev{display:flex;position:fixed;inset:54px 0 0 0;z-index:58;width:100%}
87
+ }
88
+
89
+ /* topbar */
90
+ #top{grid-area:top;display:flex;align-items:center;gap:var(--sp-3);padding:0 var(--sp-4);
91
+ border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--surface-2),var(--surface))}
92
+ .menu-b{display:none} @media(max-width:900px){.menu-b{display:inline-flex}}
93
+ .brand{display:flex;align-items:center;gap:9px}
94
+ .fin{width:26px;height:26px;color:var(--accent)} .fin svg{width:100%;height:100%}
95
+ .brand h1{font-size:var(--fs-15);letter-spacing:.14em}
96
+ .brand .q{color:var(--danger)}
97
+ .live{display:flex;align-items:center;gap:6px;font-size:var(--fs-11);color:var(--ink-faint)}
98
+ .dot{width:7px;height:7px;border-radius:50%;background:var(--ink-faint)}
99
+ .dot.on{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}
46
100
  .grow{flex:1}
47
- .switch{display:flex;gap:6px;flex-wrap:wrap}
48
- .switch button, .ghost{background:var(--panel2);color:var(--mut);border:1px solid var(--edge);
49
- border-radius:8px;padding:6px 10px;cursor:pointer;font-size:12px}
50
- .switch button:hover,.ghost:hover{color:var(--ink);border-color:var(--blue)}
51
- main{display:grid;grid-template-columns:250px 1fr 0px;height:calc(100vh - 53px);transition:grid-template-columns .18s}
52
- main.preview{grid-template-columns:250px 1fr 360px}
53
- .col{overflow:auto;min-width:0}
54
- .left{border-right:1px solid var(--edge);background:var(--bg2)}
55
- .right{border-left:1px solid var(--edge);background:var(--bg2)}
56
- .sec{padding:10px 12px;border-bottom:1px solid var(--edge);color:var(--mut);
57
- text-transform:uppercase;font-size:10px;letter-spacing:1.5px}
58
- .item{padding:9px 12px;border-bottom:1px solid #0f1626;cursor:pointer}
59
- .item:hover{background:#0f1830}
60
- .item.active{background:#101c38;box-shadow:inset 3px 0 0 var(--blue)}
61
- .item .t{color:var(--ink);font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
62
- .item .s{color:var(--mut);font-size:11px;margin-top:2px}
63
- .badge{display:inline-block;padding:1px 7px;border-radius:999px;border:1px solid var(--edge);font-size:10px}
64
- .b-running{color:var(--blue2);border-color:var(--blue)} .b-done{color:var(--ok);border-color:var(--ok)}
65
- .b-error,.b-cancelled{color:var(--red);border-color:var(--red)} .b-paused{color:var(--warn);border-color:var(--warn)}
66
- .center{display:flex;flex-direction:column}
67
- .composer{padding:14px 16px;border-bottom:1px solid var(--edge)}
68
- .modes{display:flex;gap:8px;margin-bottom:10px}
69
- .mode{flex:1;background:var(--panel);border:1px solid var(--edge);border-radius:10px;padding:9px 10px;cursor:pointer}
70
- .mode.sel{border-color:var(--blue);background:#0f1c3a;box-shadow:0 0 0 1px var(--blue) inset}
71
- .mode h4{margin:0;font-size:13px;color:var(--ink)} .mode p{margin:2px 0 0;font-size:11px;color:var(--mut)}
72
- .composerRow{display:flex;gap:8px;align-items:flex-end}
73
- textarea{flex:1;resize:vertical;min-height:44px;max-height:160px;background:var(--panel2);color:var(--ink);
74
- border:1px solid var(--edge);border-radius:10px;padding:10px 12px;font:14px/1.4 inherit}
75
- textarea:focus{outline:none;border-color:var(--blue)}
76
- .send{background:linear-gradient(180deg,var(--blue2),var(--blue));color:#00122e;font-weight:700;border:none;
77
- border-radius:10px;padding:11px 16px;cursor:pointer}
78
- .send:disabled{opacity:.5;cursor:not-allowed}
79
- .opts{display:flex;gap:14px;align-items:center;margin-top:8px;color:var(--mut);font-size:12px}
80
- .opts select,.opts input{background:var(--panel2);color:var(--ink);border:1px solid var(--edge);border-radius:7px;padding:4px 7px}
81
- .chips{display:flex;gap:8px;padding:10px 16px;flex-wrap:wrap;border-bottom:1px solid var(--edge)}
82
- .chip{display:flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;border:1px solid var(--edge);
83
- color:var(--mut);font-size:12px}
84
- .chip.on{color:var(--blue2);border-color:var(--blue)} .chip.done{color:var(--ok);border-color:var(--ok)}
85
- .console{flex:1;overflow:auto;padding:12px 16px}
86
- .ev{display:flex;gap:9px;padding:2px 0;font-size:12.5px}
87
- .ev .ic{width:14px;text-align:center} .ev .tx{white-space:pre-wrap;word-break:break-word}
88
- .e-blue{color:var(--blue2)} .e-mut{color:var(--mut)} .e-ok{color:var(--ok)} .e-red{color:var(--red)} .e-warn{color:var(--warn)}
89
- .empty{color:var(--mut);text-align:center;padding:40px 20px}
90
- pre{white-space:pre-wrap;word-break:break-word;font-size:12px;color:var(--ink)}
91
- .rhead{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--edge)}
92
- a{color:var(--blue2)}
93
- .overlay{position:fixed;inset:0;background:var(--bg);z-index:60;display:none;flex-direction:column}
94
- .overlay.show{display:flex}
95
- .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px;padding:16px;overflow:auto;align-content:start}
96
- .fcard{background:var(--panel);border:1px solid var(--edge);border-radius:12px;padding:14px;cursor:pointer}
97
- .fcard:hover{border-color:var(--blue);box-shadow:0 0 0 1px var(--blue) inset}
98
- .fcard h4{margin:0 0 4px;color:var(--ink);font-size:14px}
99
- .fcard .cat{font-size:10px;color:var(--blue2);text-transform:uppercase;letter-spacing:1px}
100
- .fcard p{margin:6px 0 0;font-size:12px;color:var(--mut)}
101
- .req{padding:9px 12px;border-bottom:1px solid #0f1626}
102
- .req .a{font-size:12.5px;color:var(--ink)} .req .r{font-size:11px;color:var(--mut);margin:2px 0 6px}
103
- .req.destructive{box-shadow:inset 3px 0 0 var(--red)} .req.major{box-shadow:inset 3px 0 0 var(--warn)}
104
- .req .btns{display:flex;gap:6px}
105
- .req button{border:none;border-radius:6px;padding:4px 10px;cursor:pointer;font-size:11px}
106
- .ok-btn{background:var(--ok);color:#00231a} .no-btn{background:var(--red);color:#2a0007}
107
- .sechead{display:flex;align-items:center;gap:8px;margin:18px 0 8px}
108
- .sechead:first-child{margin-top:0}
109
- .sechead .n{background:var(--panel2);border:1px solid var(--edge);border-radius:999px;padding:2px 9px;font-size:11px;color:var(--mut)}
110
- .seclist{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px}
111
- .secitem{background:var(--panel);border:1px solid var(--edge);border-radius:8px;padding:8px 10px;font-family:ui-monospace,monospace;font-size:12px;color:var(--ink);word-break:break-all}
112
- .secnote{color:var(--mut);font-size:12px;margin:4px 0 14px}
113
- .pill2{display:inline-flex;align-items:center;gap:6px;background:var(--panel2);border:1px solid var(--edge);border-radius:999px;padding:5px 12px;font-size:12px;color:var(--ink);margin-right:8px}
114
- .pill2.on{color:var(--ok);border-color:var(--ok)}
101
+ .tnav{display:flex;gap:3px;overflow-x:auto;scrollbar-width:none} .tnav::-webkit-scrollbar{display:none}
102
+ .tb{background:transparent;color:var(--ink-dim);border:1px solid transparent;border-radius:var(--r-sm);
103
+ padding:6px 11px;font-size:var(--fs-12);font-weight:500;white-space:nowrap;display:inline-flex;align-items:center;gap:6px;
104
+ transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
105
+ .tb:hover{background:var(--surface-3);color:var(--ink)} .tb:active{transform:scale(.96)}
106
+ .tb.accent{color:var(--accent-2)} .tb svg{width:14px;height:14px;flex:none}
107
+ .kbd{border:1px solid var(--border-strong);border-radius:4px;padding:0 5px;font:10px var(--font-mono);color:var(--ink-faint)}
108
+
109
+ /* resize handles */
110
+ .resize{background:transparent;position:relative} .resize::after{content:'';position:absolute;inset:0 2px;border-radius:3px;transition:background var(--dur-1) var(--ease)}
111
+ .resize:hover::after,.resize.drag::after{background:var(--accent-line)}
112
+ @media(max-width:900px){.resize{display:none}}
113
+
114
+ /* sidebar */
115
+ #side{grid-area:side;background:var(--surface);border-right:1px solid var(--border);overflow-y:auto;display:flex;flex-direction:column}
116
+ .s-sec{padding:var(--sp-3) var(--sp-4) var(--sp-2)} .s-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
117
+ .cbadge{background:var(--surface-3);border:1px solid var(--border);border-radius:999px;color:var(--ink-dim);font-size:10px;padding:1px 7px;font-weight:600}
118
+ .cbadge.alert{background:var(--warn-soft);border-color:var(--warn);color:var(--warn)}
119
+ .grp-label{font-size:10px;color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em;padding:8px var(--sp-4) 3px}
120
+ .row{padding:9px var(--sp-4);border-bottom:1px solid var(--edge-soft);display:flex;flex-direction:column;gap:3px;
121
+ transition:background var(--dur-1) var(--ease)}
122
+ .row[tabindex]{cursor:pointer} .row:hover{background:var(--surface-2)} .row.active{background:var(--surface-3);box-shadow:inset 3px 0 0 var(--accent)}
123
+ .row-t{font-size:var(--fs-13);color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
124
+ .row-m{font-size:var(--fs-11);color:var(--ink-faint);display:flex;align-items:center;gap:7px;flex-wrap:wrap}
125
+ .tag{border:1px solid var(--border);border-radius:999px;padding:0 7px;font-size:10px;font-weight:600}
126
+ .tag.running{color:var(--accent-2);border-color:var(--accent-line)} .tag.done{color:var(--ok);border-color:var(--ok)}
127
+ .tag.error,.tag.cancelled{color:var(--danger);border-color:var(--danger)} .tag.paused{color:var(--warn);border-color:var(--warn)}
128
+ .empty{padding:var(--sp-6) var(--sp-4);text-align:center;color:var(--ink-faint);font-size:var(--fs-12)}
129
+ .empty svg{width:26px;height:26px;opacity:.5;margin-bottom:6px}
130
+ .skel{background:linear-gradient(90deg,var(--surface-2) 25%,var(--surface-3) 37%,var(--surface-2) 63%);background-size:400% 100%;
131
+ animation:sh 1.4s ease infinite;border-radius:6px;height:12px;margin:6px var(--sp-4)}
132
+ @keyframes sh{0%{background-position:100% 0}100%{background-position:0 0}}
133
+ @media(prefers-reduced-motion:reduce){.skel{animation:none;opacity:.5}}
134
+
135
+ /* main */
136
+ #main{grid-area:main;display:flex;flex-direction:column;min-width:0;background:var(--bg)}
137
+ #composer{padding:var(--sp-4) var(--sp-5);border-bottom:1px solid var(--border)}
138
+ .comp-row{display:flex;gap:var(--sp-2);align-items:flex-end}
139
+ #goal{flex:1;resize:none;height:48px;max-height:190px;background:var(--surface);color:var(--ink);border:1px solid var(--border);
140
+ border-radius:var(--r);padding:12px 14px;font:var(--fs-13)/1.5 var(--font-body);transition:border-color var(--dur-1) var(--ease)}
141
+ #goal::placeholder{color:var(--ink-faint)} #goal:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
142
+ /* split send button + toggle-up mode picker */
143
+ .send-wrap{position:relative;display:flex;flex:none}
144
+ .send{background:var(--accent);color:var(--accent-ink);font-weight:700;border:none;border-radius:var(--r) 0 0 var(--r);
145
+ padding:12px 15px 12px 16px;font-size:var(--fs-13);display:inline-flex;align-items:center;gap:8px;
146
+ transition:filter var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
147
+ .send:hover{filter:brightness(1.09)} .send:active{transform:scale(.97)} .send:disabled{opacity:.45;filter:none}
148
+ .send .mi{width:15px;height:15px}
149
+ .mode-toggle{background:var(--accent);color:var(--accent-ink);border:none;border-left:1px solid rgba(4,18,46,.25);
150
+ border-radius:0 var(--r) var(--r) 0;padding:0 9px;display:inline-flex;align-items:center;transition:filter var(--dur-1) var(--ease)}
151
+ .mode-toggle:hover{filter:brightness(1.09)} .mode-toggle svg{width:14px;height:14px;transition:transform var(--dur-1) var(--ease)}
152
+ .mode-toggle.open svg{transform:rotate(180deg)}
153
+ .mode-pop{position:absolute;bottom:calc(100% + 8px);right:0;width:288px;background:var(--surface-2);border:1px solid var(--border-strong);
154
+ border-radius:var(--r-lg);box-shadow:var(--shadow);padding:6px;display:none;z-index:40;animation:pop var(--dur-2) var(--ease)}
155
+ .mode-pop.open{display:block}
156
+ @keyframes pop{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
157
+ .mopt{display:flex;gap:10px;padding:9px 10px;border-radius:9px;cursor:pointer;align-items:flex-start}
158
+ .mopt:hover,.mopt.sel{background:var(--surface-3)} .mopt.sel{box-shadow:inset 0 0 0 1px var(--accent-line)}
159
+ .mopt .ic{width:17px;height:17px;color:var(--ink-dim);flex:none;margin-top:1px} .mopt.sel .ic{color:var(--accent)}
160
+ .mopt h4{font-size:var(--fs-12);font-family:var(--font-body);color:var(--ink)} .mopt p{margin:2px 0 0;font-size:var(--fs-11);color:var(--ink-faint);line-height:1.4}
161
+ .opts{display:flex;gap:var(--sp-4);align-items:center;margin-top:var(--sp-3);flex-wrap:wrap;color:var(--ink-dim);font-size:var(--fs-12)}
162
+ .opts label{display:flex;align-items:center;gap:6px}
163
+ .opts select,.opts input[type=number]{background:var(--surface);color:var(--ink);border:1px solid var(--border);border-radius:6px;padding:3px 7px;font-size:var(--fs-12)}
164
+ .opts input[type=checkbox]{accent-color:var(--accent);width:14px;height:14px}
165
+ .hint{margin-left:auto;color:var(--ink-faint);font-size:var(--fs-11)}
166
+
167
+ /* timeline (signature) + orchestration viz */
168
+ #timeline{padding:var(--sp-3) var(--sp-5);border-bottom:1px solid var(--border);overflow-x:auto}
169
+ .track{display:flex;align-items:center;min-height:32px}
170
+ .node{display:flex;align-items:center;flex:none}
171
+ .node-dot{width:20px;height:20px;border-radius:50%;border:2px solid var(--border-strong);background:var(--surface);display:flex;align-items:center;justify-content:center;flex:none;
172
+ transition:border-color var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
173
+ .node-dot svg{width:11px;height:11px;opacity:0} .node.done .node-dot{border-color:var(--ok);background:var(--ok-soft)} .node.done .node-dot svg{opacity:1;color:var(--ok)}
174
+ .node.active .node-dot{border-color:var(--accent);background:var(--accent-soft);animation:pulse 1.6s ease-in-out infinite}
175
+ @keyframes pulse{0%,100%{box-shadow:0 0 0 3px var(--accent-soft)}50%{box-shadow:0 0 0 8px transparent}}
176
+ .node-label{font-size:var(--fs-12);color:var(--ink-faint);margin-left:7px;white-space:nowrap}
177
+ .node.done .node-label{color:var(--ok)} .node.active .node-label{color:var(--accent-2);font-weight:600}
178
+ .node-line{width:26px;height:1px;background:var(--border-strong);margin:0 4px} .node.done + .node-line{background:var(--ok)}
179
+ .sub-rail{display:flex;align-items:center;gap:6px;margin-top:8px;flex-wrap:wrap}
180
+ .sub-chip{font-size:10px;color:var(--accent-2);background:var(--accent-soft);border:1px solid var(--accent-line);border-radius:999px;padding:2px 9px;font-weight:600}
181
+ .sub-chip.merge{color:var(--warn);background:var(--warn-soft);border-color:var(--warn)}
182
+
183
+ /* console */
184
+ #console{flex:1;overflow-y:auto;padding:var(--sp-3) var(--sp-5);display:flex;flex-direction:column;gap:5px}
185
+ .ev{display:flex;gap:9px;padding:6px 9px;border-radius:8px;border-left:2px solid transparent;animation:fade var(--dur-2) var(--ease)}
186
+ @keyframes fade{from{opacity:0;transform:translateY(2px)}to{opacity:1;transform:none}}
187
+ @media(prefers-reduced-motion:reduce){.ev{animation:none}}
188
+ .ev .ic{width:15px;height:15px;flex:none;margin-top:1px} .ev .ic svg{width:100%;height:100%}
189
+ .ev .tx{white-space:pre-wrap;word-break:break-word;font:11.5px/1.55 var(--font-mono);flex:1}
190
+ .ev .t{color:var(--ink-faint);font-size:10px;margin-left:7px}
191
+ .c-accent{color:var(--accent-2)} .c-dim{color:var(--ink-dim)} .c-ok{color:var(--ok)} .c-danger{color:var(--danger)} .c-warn{color:var(--warn)}
192
+ .ev.bg-danger{background:var(--danger-soft);border-left-color:var(--danger)} .ev.bg-warn{background:var(--warn-soft);border-left-color:var(--warn)}
193
+
194
+ /* preview */
195
+ #prev{grid-area:prev;background:var(--surface);border-left:1px solid var(--border);overflow:hidden;min-width:0;display:flex;flex-direction:column}
196
+ .p-head{display:flex;align-items:center;gap:8px;padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--border)}
197
+ .p-head h3{font-size:var(--fs-13);flex:1} .p-type{font-size:10px;color:var(--ink-faint);text-transform:uppercase;letter-spacing:.06em}
198
+ .ibtn{background:transparent;border:1px solid var(--border);border-radius:7px;color:var(--ink-dim);padding:5px 9px;font-size:var(--fs-12);display:inline-flex;align-items:center;gap:5px;transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
199
+ .ibtn:hover{color:var(--ink);border-color:var(--accent-line)}
200
+ #prevBody{flex:1;overflow:auto}
201
+ pre.code{margin:0;padding:0;font:11.5px/1.6 var(--font-mono);display:flex}
202
+ .gutter{color:var(--ink-faint);text-align:right;padding:var(--sp-3) 8px;user-select:none;border-right:1px solid var(--border);background:var(--surface-2)}
203
+ .codelines{padding:var(--sp-3) var(--sp-4);white-space:pre;overflow-x:auto;color:var(--ink);flex:1}
204
+ .md{padding:var(--sp-4);line-height:1.65} .md h1,.md h2,.md h3{margin:.8em 0 .3em} .md code{background:var(--surface-3);padding:1px 5px;border-radius:4px;font:12px var(--font-mono)}
205
+ .md pre{background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:10px;overflow-x:auto;font:12px var(--font-mono)}
206
+ .md ul{padding-left:1.2em} .md a{color:var(--accent-2)}
207
+ .jt{padding:var(--sp-4);font:12px/1.6 var(--font-mono)} .jt .k{color:var(--accent-2)} .jt .s{color:var(--ok)} .jt .n{color:var(--warn)} .jt .b{color:var(--danger)}
208
+ .jt .tog{cursor:pointer;color:var(--ink-faint);user-select:none} .jt .nest{padding-left:16px;border-left:1px solid var(--edge-soft)}
209
+ .jt .col{display:none} .jt.open>.col{display:block}
210
+ iframe.htmlprev{width:100%;height:100%;border:none;background:#fff}
211
+
212
+ /* overlays */
213
+ .ov{position:fixed;inset:0;background:var(--bg);z-index:100;display:none;flex-direction:column}
214
+ .ov.show{display:flex}
215
+ .ov-head{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-5);border-bottom:1px solid var(--border);background:var(--surface)}
216
+ .back{background:var(--surface-3);border:1px solid var(--border);color:var(--ink);border-radius:var(--r-sm);padding:7px 12px;font-size:var(--fs-12);font-weight:500;display:inline-flex;align-items:center;gap:6px;transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
217
+ .back:hover{border-color:var(--accent);color:var(--accent-2)}
218
+ .ov-body{flex:1;overflow-y:auto;padding:var(--sp-5)}
219
+ .fgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--sp-3)}
220
+ .fcard{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:var(--sp-4);text-align:left;
221
+ transition:border-color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
222
+ .fcard:hover{border-color:var(--accent-line);transform:translateY(-2px)} .fcard:active{transform:none}
223
+ .fcard .eyebrow{color:var(--accent-2)} .fcard h4{font-size:var(--fs-13);margin:5px 0 4px;font-family:var(--font-body)} .fcard p{margin:0;font-size:var(--fs-12);color:var(--ink-dim);line-height:1.5}
224
+ .req{padding:10px var(--sp-4);border-bottom:1px solid var(--edge-soft);border-left:3px solid transparent}
225
+ .req.destructive{border-left-color:var(--danger);background:var(--danger-soft)} .req.major{border-left-color:var(--warn);background:var(--warn-soft)}
226
+ .req .a{font-size:var(--fs-12);color:var(--ink)} .req .r{font-size:var(--fs-11);color:var(--ink-faint);margin:3px 0 8px}
227
+ .req .btns{display:flex;gap:6px} .bs{border:none;border-radius:6px;padding:5px 12px;font-size:11px;font-weight:600} .ok-b{background:var(--ok);color:#04231a} .no-b{background:var(--danger);color:#2e0507}
228
+ .sec-blk{margin-bottom:var(--sp-6)} .sec-h{display:flex;align-items:center;gap:8px;margin-bottom:8px} .sec-h h4{font-size:var(--fs-13);font-family:var(--font-body)}
229
+ .sec-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px;margin-bottom:8px}
230
+ .sec-i{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:7px 10px;font:11.5px var(--font-mono);color:var(--ink);word-break:break-all}
231
+ .sec-note{color:var(--ink-faint);font-size:var(--fs-11);line-height:1.5}
232
+ .posture{display:flex;gap:var(--sp-2);margin-bottom:var(--sp-5);flex-wrap:wrap}
233
+ .pill{display:inline-flex;align-items:center;gap:6px;background:var(--ok-soft);border:1px solid var(--ok);color:var(--ok);border-radius:999px;padding:5px 12px;font-size:var(--fs-12);font-weight:600}
234
+ .pill svg{width:12px;height:12px}
235
+ /* command palette */
236
+ .cmdk-bd{position:fixed;inset:0;background:rgba(2,4,9,.62);z-index:200;display:none;justify-content:center;padding-top:12vh}
237
+ .cmdk-bd.show{display:flex}
238
+ .cmdk{width:min(560px,92vw);height:max-content;background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--r-lg);box-shadow:var(--shadow);overflow:hidden;animation:pop var(--dur-2) var(--ease)}
239
+ .cmdk-in{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--border)} .cmdk-in svg{width:16px;height:16px;color:var(--ink-faint)}
240
+ #cmdkInput{flex:1;background:transparent;border:none;outline:none;color:var(--ink);font-size:var(--fs-15)} #cmdkInput::placeholder{color:var(--ink-faint)}
241
+ .cmdk-list{max-height:52vh;overflow-y:auto;padding:6px}
242
+ .cmdk-it{display:flex;align-items:center;gap:11px;padding:9px 10px;border-radius:8px;cursor:pointer} .cmdk-it.sel,.cmdk-it:hover{background:var(--surface-3)}
243
+ .cmdk-it .ic{width:15px;height:15px;color:var(--ink-faint);flex:none} .cmdk-it.sel .ic{color:var(--accent)}
244
+ .cmdk-it .m{flex:1;min-width:0} .cmdk-it .tt{font-size:var(--fs-13);color:var(--ink)} .cmdk-it .ss{font-size:var(--fs-11);color:var(--ink-faint)}
245
+ .cmdk-it .cat{font-size:10px;color:var(--ink-faint);text-transform:uppercase}
115
246
  </style></head>
116
247
  <body>
117
- <header>
118
- <span class="dot" id="dot"></span>
119
- <span class="brand"><span class="m">M A</span> <span class="fin">Q</span> &#9650;</span>
120
- <span class="e-mut" style="font-size:12px">megalodon · v${version}</span>
121
- <span class="grow"></span>
122
- <div class="switch">
123
- <button onclick="openFeatures()">&#9776; Features</button>
124
- <button onclick="feat('agents')">Agents</button>
125
- <button onclick="feat('connectivity')">Connectivity</button>
126
- <button onclick="feat('doctor')">Doctor</button>
127
- <button onclick="feat('models')">Models</button>
128
- <button onclick="openSecurity()">&#128274; Security</button>
129
- <button class="ghost" onclick="togglePreview()">Preview</button>
130
- </div>
131
- </header>
132
- <main id="main">
133
- <div class="col left">
134
- <div class="sec">Request box <span id="reqCount" class="badge"></span></div>
135
- <div id="requests"><div class="empty">No pending requests.</div></div>
136
- <div class="sec">Sessions &amp; history</div>
137
- <div id="sessions"><div class="empty">No sessions yet.</div></div>
138
- <div class="sec">Agents</div>
139
- <div id="agents"><div class="empty">—</div></div>
140
- </div>
141
- <div class="col center">
142
- <div class="composer">
143
- <div class="modes" id="modes"></div>
144
- <div class="composerRow">
145
- <textarea id="goal" placeholder="Describe a goal — e.g. add pagination to the users endpoint and add tests"></textarea>
146
- <button class="send" id="send" onclick="startTask()">Send &#9654;</button>
248
+ <div id="cur"></div><div id="curDot"></div>
249
+ <div id="app">
250
+ <header id="top">
251
+ <button class="menu-b ibtn" aria-label="Toggle sidebar" onclick="toggleSide()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M3 6h18M3 12h18M3 18h18"/></svg></button>
252
+ <div class="brand">
253
+ <span class="fin" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M2 15c5 1 8-1 11-6 1 3 1 5 0 7 2 0 4-1 5-3 0 4-3 7-8 7-4 0-8-2-8-5zm11-7c-3 2-6 3-9 2 2-3 6-4 9-2z"/></svg></span>
254
+ <h1>MAQ<span class="q"> ▴</span></h1>
255
+ <span class="sr-only">version ${version}</span>
256
+ <span class="live"><span class="dot" id="dot"></span><span id="liveTxt">connecting</span></span>
257
+ </div>
258
+ <span class="grow"></span>
259
+ <nav class="tnav" aria-label="Tools">
260
+ <button class="tb" onclick="openPalette()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>Search<span class="kbd">⌘K</span></button>
261
+ <button class="tb accent" onclick="openFeatures()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>Features</button>
262
+ <button class="tb" onclick="openSecurity()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2 4 5v6c0 5 3.5 8.5 8 11 4.5-2.5 8-6 8-11V5l-8-3z"/></svg>Security</button>
263
+ <button class="tb" id="prevToggle" onclick="togglePreview()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/></svg>Preview</button>
264
+ </nav>
265
+ </header>
266
+ <div id="scrim" onclick="toggleSide()"></div>
267
+
268
+ <aside id="side" aria-label="History">
269
+ <div class="s-sec"><div class="s-head"><span class="eyebrow">Request box</span><span class="cbadge" id="reqCount">0</span></div><div id="requests" role="region" aria-label="Pending requests"></div></div>
270
+ <div class="s-sec"><div class="s-head"><span class="eyebrow">History</span></div></div>
271
+ <div id="sessions" role="region" aria-label="Session history"></div>
272
+ <div class="s-sec"><div class="s-head"><span class="eyebrow">Agents</span></div><div id="agents" role="region" aria-label="Agents"></div></div>
273
+ </aside>
274
+
275
+ <div class="resize" id="rl" role="separator" aria-label="Resize sidebar" aria-orientation="vertical"></div>
276
+
277
+ <main id="main">
278
+ <div id="composer">
279
+ <div class="comp-row">
280
+ <label class="sr-only" for="goal">Goal</label>
281
+ <textarea id="goal" placeholder="Describe a goal — the Headroom master decomposes and dispatches it"></textarea>
282
+ <div class="send-wrap">
283
+ <button class="send" id="send" onclick="startTask()"><span class="mi" id="sendIcon"></span><span id="sendLabel">Send</span></button>
284
+ <button class="mode-toggle" id="modeToggle" aria-haspopup="menu" aria-expanded="false" aria-label="Choose execution mode" onclick="togglePop(event)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M6 15l6-6 6 6"/></svg></button>
285
+ <div class="mode-pop" id="modePop" role="menu" aria-label="Execution mode"></div>
286
+ </div>
147
287
  </div>
148
288
  <div class="opts">
149
- <label>target
150
- <select id="target">
151
- <option value="none">none (raw)</option><option value="auto">auto</option>
152
- <option value="claude-code">claude-code</option><option value="codex">codex</option>
153
- <option value="gemini">gemini</option>
154
- </select>
155
- </label>
289
+ <label>target <select id="target"><option value="none">none (raw)</option><option value="auto">auto</option><option value="claude-code">claude-code</option><option value="codex">codex</option><option value="gemini">gemini</option></select></label>
156
290
  <label><input type="checkbox" id="dry"> dry-run</label>
157
291
  <label>concurrency <input type="number" id="conc" value="4" min="1" max="16" style="width:52px"></label>
292
+ <span class="hint"><span class="kbd">⌘↵</span> to send</span>
158
293
  </div>
159
294
  </div>
160
- <div class="chips" id="chips"></div>
161
- <div class="console" id="console"><div class="empty">Pick a mode, type a goal, and press Send. The live Scout&#8594;Plan&#8594;Execute&#8594;Verify stream shows here.</div></div>
162
- </div>
163
- <div class="col right">
164
- <div class="rhead"><b>Preview</b><button class="ghost" onclick="togglePreview()">Close</button></div>
165
- <div id="preview" style="padding:12px"><div class="empty">Select a session to inspect its result.</div></div>
166
- </div>
167
- </main>
168
- <div class="overlay" id="features">
169
- <div class="rhead" style="padding:14px 16px">
170
- <button class="ghost" onclick="closeFeatures()">&#8592; Back to home</button>
171
- <b style="margin-left:12px">All features</b>
172
- <span class="grow"></span>
173
- <span class="e-mut" style="font-size:12px">everything MAQ can do — click to run</span>
174
- </div>
175
- <div class="grid" id="featureGrid"><div class="empty">loading…</div></div>
295
+ <div id="timeline" aria-label="Progress"><div class="track" id="track"></div><div id="subrail"></div></div>
296
+ <div id="console" role="log" aria-live="polite" aria-label="Live activity"></div>
297
+ </main>
298
+
299
+ <div class="resize" id="rr" role="separator" aria-label="Resize preview" aria-orientation="vertical"></div>
300
+
301
+ <aside id="prev" aria-label="Preview">
302
+ <div class="p-head"><h3>Preview</h3><span class="p-type" id="prevType"></span><button class="ibtn" onclick="togglePreview()">Close</button></div>
303
+ <div id="prevBody"></div>
304
+ </aside>
176
305
  </div>
177
- <div class="overlay" id="security">
178
- <div class="rhead" style="padding:14px 16px">
179
- <button class="ghost" onclick="closeSecurity()">&#8592; Back to home</button>
180
- <b style="margin-left:12px">Security rules</b>
181
- <span class="grow"></span>
182
- <span class="e-mut" style="font-size:12px">enforced at the code level — same rules the terminal ('maq security') shows and enforces</span>
183
- </div>
184
- <div id="securityBody" style="overflow:auto;padding:16px"><div class="empty">loading…</div></div>
306
+
307
+ <div class="ov" id="features" role="dialog" aria-label="All features">
308
+ <div class="ov-head"><button class="back" onclick="closeFeatures()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>Back to home</button><h3 style="margin-left:4px">All features</h3><span class="grow"></span><span class="eyebrow" style="text-transform:none;letter-spacing:0">everything MAQ can do — click to run</span></div>
309
+ <div class="ov-body"><div class="fgrid" id="featureGrid"></div></div>
185
310
  </div>
186
- <script>
311
+ <div class="ov" id="security" role="dialog" aria-label="Security rules">
312
+ <div class="ov-head"><button class="back" onclick="closeSecurity()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>Back to home</button><h3 style="margin-left:4px">Security rules</h3><span class="grow"></span><span class="eyebrow" style="text-transform:none;letter-spacing:0">enforced at the code level — same rules the terminal ('maq security') shows</span></div>
313
+ <div class="ov-body" id="securityBody"></div>
314
+ </div>
315
+ <div class="cmdk-bd" id="cmdk" role="dialog" aria-label="Command palette">
316
+ <div class="cmdk"><div class="cmdk-in"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg><input id="cmdkInput" placeholder="Search features and sessions…" autocomplete="off"><span class="kbd">esc</span></div><div class="cmdk-list" id="cmdkList"></div></div>
317
+ </div>
318
+ <script>${CLIENT_JS}</script>
319
+ </body></html>`;
320
+ }
321
+ const CLIENT_JS = String.raw `
187
322
  "use strict";
188
- var base = location.origin;
189
- var token = new URLSearchParams(location.search).get('token') || sessionStorage.getItem('maqToken') || '';
190
- if(!token){ token = prompt('Daemon auth token (printed by: maq serve — or the launcher):') || ''; }
191
- sessionStorage.setItem('maqToken', token);
192
-
193
- var MODES = [
194
- {id:'', h:'Single', p:'one Scout&#8594;Plan&#8594;Execute&#8594;Verify pass'},
195
- {id:'parallel',h:'Parallel', p:'split, run at once, join &amp; repeat'},
196
- {id:'loop', h:'Loop', p:'refine &amp; retry until it verifies'},
197
- {id:'safe', h:'Safe', p:'light split &#8594; merge &#8594; validate'}
323
+ var base=location.origin;
324
+ var qp=new URLSearchParams(location.search);
325
+ var token=qp.get('key')||qp.get('token')||sessionStorage.getItem('maqToken')||'';
326
+ if(!token){ token=prompt('Session auth key (the 9-digit key printed by: maq serve):')||''; }
327
+ sessionStorage.setItem('maqToken',token);
328
+
329
+ var I={
330
+ play:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>',
331
+ single:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>',
332
+ parallel:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h6M4 12h6M4 17h6M14 12h6M10 7c4 0 0 5 4 5M10 17c4 0 0-5 4-5"/></svg>',
333
+ loop:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12a8 8 0 0 1 14-5M20 12a8 8 0 0 1-14 5"/><path d="M18 3v4h-4M6 21v-4h4"/></svg>',
334
+ safe:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v4M6 21v-6M18 21v-6M12 15v-4M6 15a3 3 0 0 1 0-6M18 15a3 3 0 0 0 0-6M6 9h12"/></svg>',
335
+ chev:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>',
336
+ dot:'<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="4"/></svg>',
337
+ check:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg>',
338
+ warn:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4M12 17h.01M10.3 3.9L2.7 17a2 2 0 0 0 1.7 3h15.2a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>',
339
+ x:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>',
340
+ tool:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.8-3.8a6 6 0 0 1-7.9 7.9l-6.9 6.9a2.1 2.1 0 0 1-3-3l6.9-6.9a6 6 0 0 1 7.9-7.9z"/></svg>',
341
+ flag:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 22V4M4 4h11l-1.5 4L15 12H4"/></svg>',
342
+ verified:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l3 1.5 3.3-.3 1 3.1 2.7 2-1.3 3 1.3 3-2.7 2-1 3.1-3.3-.3L12 22l-3-1.5-3.3.3-1-3.1-2.7-2 1.3-3-1.3-3 2.7-2 1-3.1 3.3.3z"/><path d="M9 12l2 2 4-4"/></svg>',
343
+ circle:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/></svg>',
344
+ box:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M3 7l3-4h12l3 4"/></svg>'
345
+ };
346
+ var MODES=[
347
+ {id:'',h:'Single',ic:I.single,p:'one Scout→Plan→Execute→Verify pass'},
348
+ {id:'parallel',h:'Parallel',ic:I.parallel,p:'split, run at once, join by summary, repeat'},
349
+ {id:'loop',h:'Loop',ic:I.loop,p:'refine & retry until it verifies'},
350
+ {id:'safe',h:'Safe',ic:I.safe,p:'light split → merge → validate, isolated'}
198
351
  ];
199
- var mode = 'parallel';
200
- var activeId = null;
352
+ var mode=sessionStorage.getItem('maqMode')||'parallel';
353
+ var activeId=null, connected=false, evList=[];
354
+
355
+ function h(t,c,html){var e=document.createElement(t);if(c)e.className=c;if(html!=null)e.innerHTML=html;return e;}
356
+ function esc(s){var d=document.createElement('div');d.textContent=String(s==null?'':s);return d.innerHTML;}
357
+ function hd(){return {authorization:'Bearer '+token,'content-type':'application/json'};}
358
+ function api(p,o){o=o||{};o.headers=hd();return fetch(base+p,o);}
359
+ function setLive(v){connected=v;document.getElementById('dot').className='dot'+(v?' on':'');document.getElementById('liveTxt').textContent=v?'connected':'offline';}
360
+ function empty(ic,t,hint){return '<div class="empty">'+(I[ic]||I.circle)+'<div>'+t+'</div>'+(hint?'<div style="margin-top:2px;opacity:.8">'+hint+'</div>':'')+'</div>';}
361
+ function skel(n){var s='';for(var i=0;i<n;i++)s+='<div class="skel" style="width:'+(58+((i*19)%36))+'%"></div>';return s;}
362
+ function ago(ts){try{var d=(Date.now()-new Date(ts).getTime())/1000;if(d<60)return Math.max(1,d|0)+'s ago';if(d<3600)return (d/60|0)+'m ago';if(d<86400)return (d/3600|0)+'h ago';return (d/86400|0)+'d ago';}catch(e){return '';}}
363
+ function fmtT(ts){try{return new Date(ts).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'});}catch(e){return '';}}
364
+ function toggleSide(){document.getElementById('side').classList.toggle('open');document.getElementById('scrim').classList.toggle('on');}
365
+
366
+ /* ---- custom cursor follower ---- */
367
+ (function(){
368
+ var cur=document.getElementById('cur'),dot=document.getElementById('curDot');
369
+ if(!cur)return; var tx=0,ty=0,cx=0,cy=0;
370
+ addEventListener('mousemove',function(e){tx=e.clientX;ty=e.clientY;dot.style.transform='translate('+tx+'px,'+ty+'px) translate(-50%,-50%)';
371
+ var t=e.target; cur.classList.toggle('hot', !!(t.closest && t.closest('button,a,[role=button],.row[tabindex],.fcard,.mopt,.cmdk-it')));});
372
+ addEventListener('mousedown',function(){cur.classList.add('press');});
373
+ addEventListener('mouseup',function(){cur.classList.remove('press');});
374
+ (function loop(){cx+=(tx-cx)*.18;cy+=(ty-cy)*.18;cur.style.transform='translate('+cx+'px,'+cy+'px) translate(-50%,-50%)';requestAnimationFrame(loop);})();
375
+ })();
201
376
 
202
- function h(tag,cls,txt){ var e=document.createElement(tag); if(cls)e.className=cls; if(txt!=null)e.textContent=txt; return e; }
203
- function headers(){ return {authorization:'Bearer '+token,'content-type':'application/json'}; }
204
- function api(path,opts){ opts=opts||{}; opts.headers=headers(); return fetch(base+path,opts); }
377
+ /* ---- resizable panels ---- */
378
+ (function(){
379
+ var app=document.getElementById('app');
380
+ var wl=sessionStorage.getItem('maqWL'); if(wl)app.style.setProperty('--wl',wl);
381
+ function drag(handle,varName,fromLeft){
382
+ var el=document.getElementById(handle); if(!el)return;
383
+ el.addEventListener('pointerdown',function(e){
384
+ e.preventDefault();el.classList.add('drag');el.setPointerCapture(e.pointerId);
385
+ function mv(ev){
386
+ var v = fromLeft ? ev.clientX : (innerWidth-ev.clientX);
387
+ v=Math.max(180,Math.min(520,v));
388
+ app.style.setProperty(varName, v+'px');
389
+ sessionStorage.setItem(varName==='--wl'?'maqWL':'maqWR', v+'px');
390
+ }
391
+ function up(){el.classList.remove('drag');removeEventListener('pointermove',mv);removeEventListener('pointerup',up);}
392
+ addEventListener('pointermove',mv);addEventListener('pointerup',up);
393
+ });
394
+ el.addEventListener('dblclick',function(){app.style.removeProperty(varName);sessionStorage.removeItem(varName==='--wl'?'maqWL':'maqWR');});
395
+ }
396
+ drag('rl','--wl',true); drag('rr','--wr',false);
397
+ })();
205
398
 
206
- function renderModes(){
207
- var c=document.getElementById('modes'); c.innerHTML='';
399
+ /* ---- mode picker (toggle-up popover) ---- */
400
+ function renderModePop(){
401
+ var pop=document.getElementById('modePop');pop.innerHTML='';
208
402
  MODES.forEach(function(m){
209
- var d=h('div','mode'+(m.id===mode?' sel':''));
210
- d.innerHTML='<h4>'+m.h+'</h4><p>'+m.p+'</p>';
211
- d.onclick=function(){ mode=m.id; renderModes(); };
212
- c.appendChild(d);
403
+ var d=h('div','mopt'+(m.id===mode?' sel':''),'<span class="ic">'+m.ic+'</span><div><h4>'+m.h+'</h4><p>'+m.p+'</p></div>');
404
+ d.setAttribute('role','menuitemradio');d.setAttribute('aria-checked',m.id===mode?'true':'false');
405
+ d.onclick=function(){mode=m.id;sessionStorage.setItem('maqMode',mode);renderModePop();syncSendFace();closePop();};
406
+ pop.appendChild(d);
213
407
  });
214
408
  }
409
+ function syncSendFace(){
410
+ var m=MODES.filter(function(x){return x.id===mode;})[0]||MODES[0];
411
+ document.getElementById('sendIcon').innerHTML=m.ic;
412
+ document.getElementById('sendLabel').textContent='Send · '+m.h;
413
+ }
414
+ function togglePop(e){e.stopPropagation();var p=document.getElementById('modePop'),t=document.getElementById('modeToggle');
415
+ var open=p.classList.toggle('open');t.classList.toggle('open',open);t.setAttribute('aria-expanded',open?'true':'false');}
416
+ function closePop(){var p=document.getElementById('modePop'),t=document.getElementById('modeToggle');p.classList.remove('open');t.classList.remove('open');t.setAttribute('aria-expanded','false');}
417
+ document.addEventListener('click',function(e){ if(!e.target.closest('.send-wrap'))closePop(); });
215
418
 
216
- var PHASES=['scout','plan','execute','verify'];
217
- function renderChips(events){
218
- var done={}, active=null, orch=[];
419
+ /* ---- timeline + orchestration viz ---- */
420
+ var PH=[{id:'scout',l:'Scout'},{id:'plan',l:'Plan'},{id:'execute',l:'Execute'},{id:'verify',l:'Verify'}];
421
+ function renderTimeline(events){
422
+ var done={},active=null,orchRounds=[],parallel=0,loopN=0,safe={};
219
423
  events.forEach(function(e){
220
- var p=e.data&&e.data.phase;
221
- if(!p)return;
222
- if(/^(parallel-round|loop-iteration|safe-)/.test(p)){ if(e.type==='phase.started')orch.push(p); return; }
223
- if(e.type==='phase.started')active=p;
224
- if(e.type==='phase.done'){done[p]=true; if(active===p)active=null;}
424
+ var p=e.data&&e.data.phase; if(!p)return;
425
+ if(/^parallel-round/.test(p)){ if(e.type==='phase.started'){parallel++;if(orchRounds.indexOf(p)<0)orchRounds.push(p);} return; }
426
+ if(/^loop-iteration/.test(p)){ if(e.type==='phase.started'){loopN++;if(orchRounds.indexOf(p)<0)orchRounds.push(p);} return; }
427
+ if(/^safe-/.test(p)){ if(e.type==='phase.done')safe[p.replace('safe-','')]=true; if(orchRounds.indexOf(p)<0)orchRounds.push(p); return; }
428
+ if(e.type==='phase.started')active=p; if(e.type==='phase.done'){done[p]=true;if(active===p)active=null;}
225
429
  });
226
- var c=document.getElementById('chips'); c.innerHTML='';
227
- PHASES.forEach(function(p){
228
- var cls='chip'+(done[p]?' done':(active===p?' on':''));
229
- var chip=h('div',cls); chip.innerHTML=(done[p]?'&#10003; ':(active===p?'&#9679; ':'&#9675; '))+p; c.appendChild(chip);
230
- });
231
- var last=orch[orch.length-1];
232
- if(last){ var oc=h('div','chip on'); oc.textContent='&#9670; '+last; oc.innerHTML='&#9670; '+last; c.appendChild(oc); }
430
+ var tr=document.getElementById('track');tr.innerHTML='';
431
+ PH.forEach(function(ph,i){var st=done[ph.id]?'done':(active===ph.id?'active':'');var n=h('div','node'+(st?' '+st:''),'<span class="node-dot">'+I.check+'</span><span class="node-label">'+ph.l+'</span>');tr.appendChild(n);if(i<PH.length-1)tr.appendChild(h('div','node-line'));});
432
+ var sr=document.getElementById('subrail');
433
+ if(mode==='safe'&&orchRounds.length){
434
+ var stages=['split','merge','validate'];sr.innerHTML='<div class="sub-rail">'+stages.map(function(s){return '<span class="sub-chip'+(s==='merge'?' merge':'')+'">'+(safe[s]?'✓ ':'')+s+'</span>';}).join('')+'</div>';
435
+ } else if(parallel&&mode==='parallel'){ sr.innerHTML='<div class="sub-rail"><span class="sub-chip">parallel · round '+parallel+'</span></div>'; }
436
+ else if(loopN&&mode==='loop'){ sr.innerHTML='<div class="sub-rail"><span class="sub-chip">loop · iteration '+loopN+'</span></div>'; }
437
+ else if(orchRounds.length){ sr.innerHTML='<div class="sub-rail">'+orchRounds.slice(-4).map(function(r){return '<span class="sub-chip">'+r+'</span>';}).join('')+'</div>'; }
438
+ else sr.innerHTML='';
233
439
  }
234
440
 
235
- function iconFor(e){
441
+ function evView(e){
236
442
  switch(e.type){
237
- case 'task.started': return ['&#9654;','e-blue', (e.data.goal||e.data.task||'')+(e.data.mode?' ['+e.data.mode+']':'')];
238
- case 'phase.started': return ['&#8250;','e-blue', (e.data.phase||'')+' …'];
239
- case 'phase.done': return ['&#8226;','e-mut', (e.data.phase||'')+(e.data.reason?' '+e.data.reason:'')];
240
- case 'agent.event': return ['&#183;','e-mut', e.data.note||JSON.stringify(e.data)];
241
- case 'agent.stdout': return ['|','e-mut', e.data.text||''];
242
- case 'agent.stderr': return ['!','e-warn', e.data.text||''];
243
- case 'tool.call': return ['&#9881;','e-blue','tool: '+(e.data.tool||JSON.stringify(e.data.command||e.data.raw||{}))];
244
- case 'task.done': return [e.data.verified?'&#10003;':'&#9873;', e.data.verified?'e-ok':'e-warn', 'done · verified='+e.data.verified+(e.data.mode?' · '+e.data.mode:'')+(e.data.summary?' · '+e.data.summary:'')];
245
- case 'task.error': return ['&#10007;','e-red','error: '+(e.data.message||'')];
246
- case 'task.cancelled': return ['&#10007;','e-red','cancelled'];
247
- default: return ['&#9675;','e-mut', e.type];
443
+ case 'task.started':return [I.play,'c-accent',(e.data.goal||e.data.task||'')+(e.data.mode?' ['+e.data.mode+']':''),0];
444
+ case 'phase.started':return [I.chev,'c-accent',(e.data.phase||'')+' …',0];
445
+ case 'phase.done':return [I.dot,'c-dim',(e.data.phase||'')+(e.data.reason?' '+e.data.reason:''),0];
446
+ case 'agent.event':return [I.dot,'c-dim',e.data.note||JSON.stringify(e.data),0];
447
+ case 'agent.stdout':return [I.dot,'c-dim',e.data.text||'',0];
448
+ case 'agent.stderr':return [I.warn,'c-warn',e.data.text||'',1];
449
+ case 'tool.call':return [I.tool,'c-accent','tool: '+(e.data.tool||JSON.stringify(e.data.command||e.data.raw||{})),0];
450
+ case 'task.done':return [e.data.verified?I.verified:I.flag,e.data.verified?'c-ok':'c-warn','done · verified='+e.data.verified+(e.data.mode?' · '+e.data.mode:'')+(e.data.summary?' · '+e.data.summary:''),e.data.verified?0:1];
451
+ case 'task.error':return [I.x,'c-danger','error: '+(e.data.message||''),2];
452
+ case 'task.cancelled':return [I.x,'c-danger','cancelled',2];
453
+ default:return [I.circle,'c-dim',e.type,0];
248
454
  }
249
455
  }
250
456
  function pushEvent(e){
251
- var c=document.getElementById('console');
252
- if(c.firstChild&&c.firstChild.className==='empty')c.innerHTML='';
253
- var parts=iconFor(e);
254
- var row=h('div','ev');
255
- var ic=h('span','ic '+parts[1]); ic.innerHTML=parts[0];
256
- var tx=h('span','tx '+parts[1]); tx.textContent=parts[2];
257
- row.appendChild(ic); row.appendChild(tx); c.appendChild(row);
258
- c.scrollTop=c.scrollHeight;
457
+ var c=document.getElementById('console');var f=c.firstElementChild;if(f&&f.classList&&f.classList.contains('emptywrap'))c.innerHTML='';
458
+ var p=evView(e);var card=h('div','ev'+(p[3]===2?' bg-danger':(p[3]===1?' bg-warn':'')));
459
+ card.innerHTML='<span class="ic '+p[1]+'">'+p[0]+'</span><span class="tx '+p[1]+'"></span><span class="t">'+fmtT(e.ts)+'</span>';
460
+ card.querySelector('.tx').textContent=p[2];c.appendChild(card);c.scrollTop=c.scrollHeight;
259
461
  }
462
+ function clearConsole(){document.getElementById('console').innerHTML='<div class="emptywrap">'+empty('box','Pick a mode, describe a goal, and Send.','The live Scout→Plan→Execute→Verify stream renders here.')+'</div>';}
260
463
 
261
- var evList=[];
262
464
  async function streamEvents(id){
263
- evList=[];
264
- document.getElementById('console').innerHTML='';
465
+ evList=[];document.getElementById('console').innerHTML='';renderTimeline([]);
265
466
  var res=await fetch(base+'/v1/sessions/'+id+'/events',{headers:{authorization:'Bearer '+token}});
266
- if(!res.ok||!res.body){ pushEvent({type:'task.error',data:{message:'stream failed '+res.status}}); return; }
267
- var reader=res.body.getReader(), dec=new TextDecoder(), buf='';
268
- while(true){
269
- var r=await reader.read(); if(r.done)break;
270
- buf+=dec.decode(r.value,{stream:true});
271
- var blocks=buf.split('\\n\\n'); buf=blocks.pop();
272
- for(var i=0;i<blocks.length;i++){
273
- var lines=blocks[i].split('\\n');
274
- for(var j=0;j<lines.length;j++){
275
- var ln=lines[j];
276
- if(ln.indexOf('data:')===0){
277
- var payload=ln.slice(5).trim(); if(!payload)continue;
278
- try{ var ev=JSON.parse(payload); if(ev.type==='stream.end'){continue;} evList.push(ev); pushEvent(ev); renderChips(evList); }catch(err){}
279
- }
280
- }
281
- }
282
- if(id!==activeId)break;
283
- }
467
+ if(!res.ok||!res.body){pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:'stream '+res.status}});return;}
468
+ var rd=res.body.getReader(),dec=new TextDecoder(),buf='';
469
+ while(true){var r=await rd.read();if(r.done)break;buf+=dec.decode(r.value,{stream:true});
470
+ var bl=buf.split('\n\n');buf=bl.pop();
471
+ for(var i=0;i<bl.length;i++){var ls=bl[i].split('\n');for(var j=0;j<ls.length;j++){var ln=ls[j];
472
+ if(ln.indexOf('data:')===0){var pl=ln.slice(5).trim();if(!pl)continue;try{var ev=JSON.parse(pl);if(ev.type==='stream.end')continue;evList.push(ev);pushEvent(ev);renderTimeline(evList);}catch(x){}}}}
473
+ if(id!==activeId)break;}
284
474
  refreshSessions();
285
475
  }
286
476
 
287
477
  async function startTask(){
288
- var goal=document.getElementById('goal').value.trim(); if(!goal)return;
289
- var btn=document.getElementById('send'); btn.disabled=true;
290
- var body={task:goal, target:document.getElementById('target').value, dryRun:document.getElementById('dry').checked,
291
- maxConcurrency:Number(document.getElementById('conc').value)||4};
478
+ var g=document.getElementById('goal');var goal=g.value.trim();if(!goal)return;
479
+ var btn=document.getElementById('send');btn.disabled=true;
480
+ var body={task:goal,target:document.getElementById('target').value,dryRun:document.getElementById('dry').checked,maxConcurrency:Number(document.getElementById('conc').value)||4};
292
481
  if(mode)body.mode=mode;
293
- try{
294
- var res=await api('/v1/sessions',{method:'POST',body:JSON.stringify(body)});
295
- var s=await res.json();
296
- if(!res.ok){ pushEvent({type:'task.error',data:{message:s.error||('HTTP '+res.status)}}); return; }
297
- activeId=s.id; document.getElementById('goal').value='';
298
- await refreshSessions();
299
- streamEvents(s.id);
300
- }catch(e){ pushEvent({type:'task.error',data:{message:String(e)}}); }
301
- finally{ btn.disabled=false; }
482
+ try{var res=await api('/v1/sessions',{method:'POST',body:JSON.stringify(body)});var s=await res.json();
483
+ if(!res.ok){pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:s.error||('HTTP '+res.status)}});return;}
484
+ activeId=s.id;g.value='';await refreshSessions();streamEvents(s.id);
485
+ }catch(e){pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:String(e)}});}
486
+ finally{btn.disabled=false;}
302
487
  }
303
488
 
304
- async function refreshSessions(){
305
- try{
306
- var res=await api('/v1/sessions'); var d=await res.json();
307
- var box=document.getElementById('sessions'); box.innerHTML='';
308
- var list=(d.sessions||[]);
309
- document.getElementById('dot').className='dot on';
310
- if(!list.length){ box.innerHTML='<div class="empty">No sessions yet.</div>'; return; }
311
- list.slice().reverse().forEach(function(s){
312
- var it=h('div','item'+(s.id===activeId?' active':''));
313
- var t=h('div','t'); t.textContent=s.task;
314
- var meta=h('div','s');
315
- meta.innerHTML='<span class="badge b-'+s.status+'">'+s.status+'</span> '+
316
- (s.mode?('· '+s.mode+' '):'')+'· '+(s.target||'auto')+' · '+s.eventCount+' ev'+
317
- (s.verified===true?' · &#10003;':(s.verified===false?' · &#10007;':''));
318
- it.appendChild(t); it.appendChild(meta);
319
- it.onclick=function(){ activeId=s.id; refreshSessions(); openPreview(s.id); streamEvents(s.id); };
320
- box.appendChild(it);
321
- });
322
- }catch(e){ document.getElementById('dot').className='dot'; }
489
+ /* ---- sessions (history grouped by day, timestamps, click-to-restore) ---- */
490
+ var sLoaded=false;
491
+ function groupKey(ts){try{var d=new Date(ts);var t=new Date();if(d.toDateString()===t.toDateString())return 'Today';var y=new Date(t-864e5);if(d.toDateString()===y.toDateString())return 'Yesterday';return d.toLocaleDateString([], {month:'short',day:'numeric'});}catch(e){return 'Earlier';}}
492
+ function sessionRow(s){
493
+ var it=h('div','row'+(s.id===activeId?' active':''));it.setAttribute('tabindex','0');it.setAttribute('role','button');it.setAttribute('aria-label','Open session '+s.task);
494
+ var vm=s.verified===true?' · ✓':(s.verified===false?' · ✗':'');
495
+ it.innerHTML='<div class="row-t">'+esc(s.task)+'</div><div class="row-m"><span class="tag '+s.status+'">'+s.status+'</span>'+(s.mode?'<span>'+s.mode+'</span>':'')+'<span>'+(s.target||'auto')+'</span><span>'+ago(s.updatedAt||s.createdAt)+'</span>'+vm+'</div>';
496
+ it.onclick=function(){activeId=s.id;refreshSessions();openPreview(s.id);streamEvents(s.id);if(innerWidth<=900)toggleSide();};
497
+ it.onkeydown=function(ev){if(ev.key==='Enter')it.click();};
498
+ return it;
323
499
  }
324
-
325
- async function openPreview(id){
326
- document.getElementById('main').classList.add('preview');
327
- var box=document.getElementById('preview'); box.innerHTML='loading…';
328
- try{
329
- var res=await api('/v1/sessions/'+id); var d=await res.json();
330
- box.innerHTML='';
331
- var pre=h('pre'); pre.textContent=JSON.stringify({id:d.id,task:d.task,status:d.status,mode:d.mode,verified:d.verified,error:d.error},null,2);
332
- box.appendChild(pre);
333
- }catch(e){ box.textContent=String(e); }
500
+ async function refreshSessions(){
501
+ var box=document.getElementById('sessions');if(!sLoaded)box.innerHTML=skel(3);
502
+ try{var d=await (await api('/v1/sessions')).json();setLive(true);var list=(d.sessions||[]);sLoaded=true;box.innerHTML='';
503
+ if(!list.length){box.innerHTML=empty('box','No sessions yet','Send a goal to start one.');return;}
504
+ var groups={},order=[];list.slice().reverse().forEach(function(s){var k=groupKey(s.updatedAt||s.createdAt);if(!groups[k]){groups[k]=[];order.push(k);}groups[k].push(s);});
505
+ order.forEach(function(k){box.appendChild(h('div','grp-label',k));groups[k].forEach(function(s){box.appendChild(sessionRow(s));});});
506
+ }catch(e){setLive(false);if(!sLoaded)box.innerHTML=empty('warn','Can\'t reach the daemon','');}
334
507
  }
335
- function togglePreview(){ document.getElementById('main').classList.toggle('preview'); }
336
508
 
337
- async function feat(kind){
338
- document.getElementById('main').classList.add('preview');
339
- var box=document.getElementById('preview'); box.innerHTML='running '+kind+'…';
340
- try{
341
- var out;
342
- if(kind==='agents'){ out=await (await api('/v1/agents')).json(); renderAgents(out.agents||[]); }
343
- else if(kind==='connectivity'){ out=await (await api('/v1/connectivity')).json(); }
344
- else { out=await (await api('/v1/exec',{method:'POST',body:JSON.stringify({argv:[kind]})})).json(); }
345
- var pre=h('pre'); pre.textContent=(out.stdout!=null?out.stdout:'')+(out.stderr?('\\n'+out.stderr):'')||JSON.stringify(out,null,2);
346
- box.innerHTML=''; box.appendChild(pre);
347
- }catch(e){ box.textContent=String(e); }
509
+ /* ---- universal preview ---- */
510
+ function detectType(text){var t=(text||'').trim();if(!t)return 'empty';if((t[0]==='{'||t[0]==='[')){try{JSON.parse(t);return 'json';}catch(e){}}if(/^<!doctype|^<html|^<div|^<body/i.test(t))return 'html';if(/^#\s|\n#\s|\*\*|^- |\n- /.test(t))return 'markdown';return 'code';}
511
+ function jsonTree(v,key){
512
+ var wrap=h('div','jt open');var isObj=v&&typeof v==='object';
513
+ if(isObj){var isArr=Array.isArray(v);var keys=isArr?v.map(function(_,i){return i;}):Object.keys(v);
514
+ var head=h('div',null,'<span class="tog">▾</span> '+(key!=null?'<span class="k">'+esc(key)+'</span>: ':'')+(isArr?'[':'{')+'<span class="c-dim"> '+keys.length+' </span>'+(isArr?']':'}'));
515
+ head.querySelector('.tog').onclick=function(){wrap.classList.toggle('open');this.textContent=wrap.classList.contains('open')?'▾':'▸';};
516
+ wrap.appendChild(head);var col=h('div','col nest');keys.forEach(function(k){col.appendChild(jsonTree(v[k],k));});wrap.appendChild(col);
517
+ } else {
518
+ var cls=typeof v==='number'?'n':(typeof v==='boolean'?'b':'s');var val=typeof v==='string'?'"'+v+'"':String(v);
519
+ wrap.innerHTML=(key!=null?'<span class="k">'+esc(key)+'</span>: ':'')+'<span class="'+cls+'">'+esc(val)+'</span>';wrap.className='jt';
520
+ }
521
+ return wrap;
348
522
  }
349
- function renderAgents(agents){
350
- var box=document.getElementById('agents'); box.innerHTML='';
351
- if(!agents.length){ box.innerHTML='<div class="empty">none detected</div>'; return; }
352
- agents.forEach(function(a){
353
- var it=h('div','item'); var t=h('div','t'); t.textContent=a.name;
354
- var s=h('div','s'); s.textContent=a.installed?(a.authenticated?'authenticated':'installed'):'not found';
355
- it.appendChild(t); it.appendChild(s); box.appendChild(it);
356
- });
523
+ function mdToHtml(t){
524
+ var FE=String.fromCharCode(96,96,96);var parts=t.split(FE);var out='';
525
+ for(var i=0;i<parts.length;i++){ if(i%2){out+='<pre>'+esc(parts[i].replace(/^\w*\n/,''))+'</pre>';continue;}
526
+ var s=esc(parts[i]);
527
+ s=s.replace(/^### (.*)$/gm,'<h3>$1</h3>').replace(/^## (.*)$/gm,'<h2>$1</h2>').replace(/^# (.*)$/gm,'<h1>$1</h1>');
528
+ s=s.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener">$1</a>');
529
+ s=s.replace(/^- (.*)$/gm,'<li>$1</li>').replace(/(<li>[\s\S]*?<\/li>)/g,'<ul>$1</ul>');
530
+ var C=String.fromCharCode(96);s=s.replace(new RegExp(C+'([^'+C+']+)'+C,'g'),'<code>$1</code>');
531
+ s=s.replace(/\n{2,}/g,'</p><p>');out+='<p>'+s+'</p>';
532
+ }
533
+ return out;
357
534
  }
358
-
359
- async function openFeatures(){
360
- document.getElementById('features').classList.add('show');
361
- var grid=document.getElementById('featureGrid'); grid.innerHTML='loading…';
362
- try{
363
- var cat=await (await api('/v1/commands')).json();
364
- var cmds=(cat.maq||[]); grid.innerHTML='';
365
- cmds.forEach(function(c){
366
- var card=h('div','fcard');
367
- card.innerHTML='<div class="cat">'+c.category+'</div><h4>'+c.name+'</h4><p>'+c.summary+'</p>';
368
- card.onclick=function(){ runFeature(c); };
369
- grid.appendChild(card);
370
- });
371
- }catch(e){ grid.innerHTML='<div class="empty">'+e+'</div>'; }
535
+ function renderPreview(content,forceType){
536
+ var box=document.getElementById('prevBody');var ty=forceType||detectType(content);document.getElementById('prevType').textContent=ty;
537
+ box.innerHTML='';
538
+ if(ty==='json'){try{var v=JSON.parse(content);box.appendChild(jsonTree(v));return;}catch(e){ty='code';}}
539
+ if(ty==='markdown'){var m=h('div','md');m.innerHTML=mdToHtml(content);box.appendChild(m);return;}
540
+ if(ty==='html'){var f=document.createElement('iframe');f.className='htmlprev';f.setAttribute('sandbox','');f.srcdoc=content;box.appendChild(f);return;}
541
+ if(ty==='image'){var img=document.createElement('img');img.src=content;img.style.maxWidth='100%';box.appendChild(img);return;}
542
+ if(ty==='empty'){box.innerHTML=empty('box','Nothing to preview','Select a session or run a feature.');return;}
543
+ var lines=String(content).split('\n');var pre=h('pre','code');var g=h('div','gutter');var cl=h('div','codelines');
544
+ g.textContent=lines.map(function(_,i){return i+1;}).join('\n');cl.textContent=content;pre.appendChild(g);pre.appendChild(cl);box.appendChild(pre);
372
545
  }
373
- function closeFeatures(){ document.getElementById('features').classList.remove('show'); }
374
-
375
- async function openSecurity(){
376
- document.getElementById('security').classList.add('show');
377
- var box=document.getElementById('securityBody'); box.innerHTML='loading…';
378
- try{
379
- var d=await (await api('/v1/security')).json();
380
- var r=d.rules, evs=(d.events||[]);
381
- var html='';
382
- html+='<div style="margin-bottom:14px">';
383
- html+='<span class="pill2 on">permission posture: '+r.permissionMode+'</span>';
384
- html+='<span class="pill2 on">secret env scrubbing: on</span>';
385
- html+='<span class="pill2 on">prompt-injection scanning: on</span>';
386
- html+='</div>';
387
-
388
- html+=sec('Protected paths (never writable — cannot be overridden by config)', r.protectedPaths.length);
389
- html+='<div class="seclist">'+r.protectedPaths.map(function(p){return '<div class="secitem">'+esc(p)+'</div>';}).join('')+'</div>';
390
- html+='<div class="secnote">Enforced in sandbox.ts checkWrite(), unconditionally, before any tier logic — a compromised agent cannot argue past this with a clever prompt.</div>';
391
-
392
- html+=sec('Protected file patterns (matched by name, anywhere in the project)', r.protectedNamePatterns.length);
393
- html+='<div class="seclist">'+r.protectedNamePatterns.map(function(p){return '<div class="secitem">'+esc(p)+'</div>';}).join('')+'</div>';
394
-
395
- if(r.extraProtectedPaths && r.extraProtectedPaths.length){
396
- html+=sec('Project-added protected paths', r.extraProtectedPaths.length);
397
- html+='<div class="seclist">'+r.extraProtectedPaths.map(function(p){return '<div class="secitem">'+esc(p)+'</div>';}).join('')+'</div>';
398
- }
399
-
400
- html+=sec('Network egress allowlist (default-deny everything else)', r.netAllowlist.length);
401
- html+='<div class="seclist">'+r.netAllowlist.map(function(p){return '<div class="secitem">'+esc(p)+'</div>';}).join('')+'</div>';
402
- html+='<div class="secnote">Enforced in tools.ts http_get and available to any outbound call site via security.ts checkEgress(). Not just a suggestion in a prompt.</div>';
403
-
404
- html+=sec('Prompt-injection scanning', r.promptInjectionScanning.length);
405
- html+='<div class="seclist">'+r.promptInjectionScanning.map(function(p){return '<div class="secitem">'+esc(p)+'</div>';}).join('')+'</div>';
406
- html+='<div class="secnote">README, manifest, git commits, AGENTS.md and skill files are scanned for override/exfiltration/jailbreak patterns before they reach a model. Findings are surfaced loudly, never silently dropped.</div>';
407
-
408
- html+=sec('Recent findings (this session)', evs.length);
409
- if(!evs.length){ html+='<div class="secnote">None recorded yet — findings appear here as tasks run.</div>'; }
410
- else {
411
- html+='<div>'+evs.slice().reverse().map(function(e){
412
- var cls=e.severity==='high'?'e-red':'e-warn';
413
- return '<div class="ev"><span class="ic '+cls+'">&#9888;</span><span class="tx '+cls+'">'+esc(e.kind)+': '+esc(e.detail)+'</span></div>';
414
- }).join('')+'</div>';
415
- }
416
- }catch(e){ box.innerHTML='<div class="empty">'+e+'</div>'; return; }
417
- box.innerHTML = html;
546
+ function openPreview(id){
547
+ document.getElementById('app').classList.remove('no-prev');document.getElementById('app').classList.add('prev-sheet');
548
+ var box=document.getElementById('prevBody');box.innerHTML=skel(5);document.getElementById('prevType').textContent='';
549
+ api('/v1/sessions/'+id).then(function(r){return r.json();}).then(function(d){
550
+ renderPreview(JSON.stringify({id:d.id,task:d.task,status:d.status,mode:d.mode,verified:d.verified,error:d.error,events:(d.events||[]).length},null,2),'json');
551
+ }).catch(function(e){box.innerHTML=empty('warn','Could not load session',String(e));});
418
552
  }
419
- function sec(title,count){ return '<div class="sechead"><b>'+title+'</b><span class="n">'+count+'</span></div>'; }
420
- function esc(s){ var d=document.createElement('div'); d.textContent=String(s); return d.innerHTML; }
421
- function closeSecurity(){ document.getElementById('security').classList.remove('show'); }
422
-
423
- function runFeature(c){
424
- closeFeatures();
425
- if(c.needsInput==='task'||c.needsInput==='query'){
426
- var g=document.getElementById('goal'); g.focus();
427
- if(c.name==='orchestrate'){ mode='parallel'; renderModes(); }
428
- g.placeholder='['+c.name+'] '+c.summary;
429
- } else {
430
- feat(c.name);
431
- }
553
+ function togglePreview(){var a=document.getElementById('app');a.classList.toggle('no-prev');a.classList.toggle('prev-sheet');
554
+ if(!a.classList.contains('no-prev')&&!document.getElementById('prevBody').innerHTML.trim())renderPreview('','empty');}
555
+
556
+ /* ---- feature explorer / exec ---- */
557
+ async function feat(kind){
558
+ document.getElementById('app').classList.remove('no-prev');document.getElementById('app').classList.add('prev-sheet');
559
+ var box=document.getElementById('prevBody');box.innerHTML=skel(5);document.getElementById('prevType').textContent='';
560
+ try{var out;
561
+ if(kind==='agents'){out=await (await api('/v1/agents')).json();renderAgents(out.agents||[]);renderPreview(JSON.stringify(out,null,2),'json');return;}
562
+ if(kind==='connectivity'){out=await (await api('/v1/connectivity')).json();renderPreview(JSON.stringify(out,null,2),'json');return;}
563
+ out=await (await api('/v1/exec',{method:'POST',body:JSON.stringify({argv:[kind]})})).json();
564
+ var txt=(out.stdout!=null?out.stdout:'')+(out.stderr?('\n'+out.stderr):'');renderPreview(txt||JSON.stringify(out,null,2));
565
+ }catch(e){box.innerHTML=empty('warn','Could not run '+kind,String(e));}
432
566
  }
567
+ function renderAgents(agents){var box=document.getElementById('agents');if(!agents.length){box.innerHTML=empty('box','No agents','Log in to an AI CLI.');return;}box.innerHTML='';
568
+ agents.forEach(function(a){var st=a.installed?(a.authenticated?'authenticated':'logged out'):'not found';var tg=a.installed?(a.authenticated?'done':'paused'):'cancelled';
569
+ box.appendChild(h('div','row','<div class="row-t">'+esc(a.name)+'</div><div class="row-m"><span class="tag '+tg+'">'+st+'</span></div>'));});}
433
570
 
571
+ async function openFeatures(){document.getElementById('features').classList.add('show');var g=document.getElementById('featureGrid');g.innerHTML=skel(6);
572
+ try{var cat=await (await api('/v1/commands')).json();var cmds=(cat.maq||[]);g.innerHTML='';
573
+ cmds.forEach(function(c){var card=h('button','fcard','<div class="eyebrow">'+esc(c.category)+'</div><h4>'+esc(c.name)+'</h4><p>'+esc(c.summary)+'</p>');card.onclick=function(){runFeature(c);};g.appendChild(card);});
574
+ }catch(e){g.innerHTML=empty('warn','Could not load features',String(e));}}
575
+ function closeFeatures(){document.getElementById('features').classList.remove('show');}
576
+ function runFeature(c){closeFeatures();if(c.needsInput==='task'||c.needsInput==='query'){var g=document.getElementById('goal');g.focus();if(c.name==='orchestrate'){mode='parallel';renderModePop();syncSendFace();}g.placeholder='['+c.name+'] '+c.summary;}else feat(c.name);}
577
+
578
+ async function openSecurity(){document.getElementById('security').classList.add('show');var box=document.getElementById('securityBody');box.innerHTML=skel(8);
579
+ try{var d=await (await api('/v1/security')).json();var r=d.rules,evs=(d.events||[]);var html='';
580
+ html+='<div class="posture"><span class="pill">'+I.check+' posture: '+r.permissionMode+'</span><span class="pill">'+I.check+' secret scrubbing: on</span><span class="pill">'+I.check+' injection scanning: on</span></div>';
581
+ html+=secBlk('Protected paths','never writable — cannot be overridden by config',r.protectedPaths,'Enforced in sandbox.ts checkWrite(), unconditionally, before any tier logic.');
582
+ html+=secBlk('Protected file patterns','matched by name anywhere',r.protectedNamePatterns,'');
583
+ if(r.extraProtectedPaths&&r.extraProtectedPaths.length)html+=secBlk('Project-added protected paths','',r.extraProtectedPaths,'');
584
+ html+=secBlk('Network egress allowlist','default-deny everything else',r.netAllowlist,'Enforced in tools.ts http_get via security.ts checkEgress().');
585
+ html+=secBlk('Prompt-injection scanning','',r.promptInjectionScanning,'README, manifest, git commits, AGENTS.md and skill files scanned before reaching a model.');
586
+ html+='<div class="sec-blk"><div class="sec-h"><h4>Recent findings</h4><span class="cbadge'+(evs.length?' alert':'')+'">'+evs.length+'</span></div>';
587
+ html+=evs.length?('<div>'+evs.slice().reverse().map(function(e){var cl=e.severity==='high'?'c-danger':'c-warn';return '<div class="ev"><span class="ic '+cl+'">'+I.warn+'</span><span class="tx '+cl+'">'+esc(e.kind)+': '+esc(e.detail)+'</span></div>';}).join('')+'</div>'):'<div class="sec-note">None recorded yet.</div>';
588
+ html+='</div>';box.innerHTML=html;
589
+ }catch(e){box.innerHTML=empty('warn','Could not load security rules',String(e));}}
590
+ function secBlk(t,note,items,foot){var html='<div class="sec-blk"><div class="sec-h"><h4>'+t+'</h4><span class="cbadge">'+(items?items.length:0)+'</span>'+(note?'<span class="sec-note">'+note+'</span>':'')+'</div>';
591
+ html+='<div class="sec-list">'+(items||[]).map(function(p){return '<div class="sec-i">'+esc(p)+'</div>';}).join('')+'</div>';if(foot)html+='<div class="sec-note">'+foot+'</div>';return html+'</div>';}
592
+ function closeSecurity(){document.getElementById('security').classList.remove('show');}
593
+
594
+ /* ---- request box ---- */
434
595
  async function loadRequests(){
435
- try{
436
- var d=await (await api('/v1/requests')).json();
437
- var box=document.getElementById('requests'); var pend=(d.pending||[]);
438
- document.getElementById('reqCount').textContent=pend.length?String(pend.length):'';
439
- if(!pend.length){ box.innerHTML='<div class="empty">No pending requests.</div>'; return; }
440
- box.innerHTML='';
441
- pend.forEach(function(r){
442
- var it=h('div','req '+r.risk);
443
- var a=h('div','a'); a.textContent=r.action+' — '+(r.detail||'');
444
- var reason=h('div','r'); reason.textContent=r.risk+' · '+(r.reason||'');
445
- var btns=h('div','btns');
446
- var ok=h('button','ok-btn','Approve'); ok.onclick=function(){ decideReq(r.id,'approve'); };
447
- var no=h('button','no-btn','Deny'); no.onclick=function(){ decideReq(r.id,'deny'); };
448
- btns.appendChild(ok); btns.appendChild(no);
449
- it.appendChild(a); it.appendChild(reason); it.appendChild(btns); box.appendChild(it);
450
- });
596
+ try{var d=await (await api('/v1/requests')).json();var box=document.getElementById('requests');var pend=(d.pending||[]);
597
+ var b=document.getElementById('reqCount');b.textContent=String(pend.length);b.className='cbadge'+(pend.length?' alert':'');
598
+ if(!pend.length){box.innerHTML=empty('box','No pending requests','');return;}box.innerHTML='';
599
+ pend.forEach(function(r){var it=h('div','req '+r.risk,'<div class="a">'+esc(r.action)+' — '+esc(r.detail||'')+'</div><div class="r">'+esc(r.risk)+' · '+esc(r.reason||'')+'</div><div class="btns"><button class="bs ok-b">Approve</button><button class="bs no-b">Deny</button></div>');
600
+ it.querySelector('.ok-b').onclick=function(){decideReq(r.id,'approve');};it.querySelector('.no-b').onclick=function(){decideReq(r.id,'deny');};box.appendChild(it);});
451
601
  }catch(e){}
452
602
  }
453
- async function decideReq(id,action){
454
- try{ await api('/v1/requests/'+id,{method:'POST',body:JSON.stringify({action:action})}); loadRequests(); }catch(e){}
455
- }
603
+ async function decideReq(id,a){try{await api('/v1/requests/'+id,{method:'POST',body:JSON.stringify({action:a})});loadRequests();}catch(e){}}
456
604
 
457
- renderModes(); refreshSessions(); feat('agents'); loadRequests();
458
- setInterval(refreshSessions, 5000); setInterval(loadRequests, 3000);
459
- document.getElementById('goal').addEventListener('keydown', function(e){ if((e.metaKey||e.ctrlKey)&&e.key==='Enter')startTask(); });
460
- </script>
461
- </body></html>`;
462
- }
605
+ /* ---- command palette ---- */
606
+ var cmdkItems=[],cmdkSel=0;
607
+ async function openPalette(){var bd=document.getElementById('cmdk');bd.classList.add('show');var inp=document.getElementById('cmdkInput');inp.value='';inp.focus();
608
+ cmdkItems=[];try{var cat=await (await api('/v1/commands')).json();(cat.maq||[]).forEach(function(c){cmdkItems.push({t:c.name,s:c.summary,cat:c.category,run:function(){closePalette();runFeature(c);}});});}catch(e){}
609
+ try{var d=await (await api('/v1/sessions')).json();(d.sessions||[]).slice().reverse().slice(0,8).forEach(function(s){cmdkItems.push({t:s.task,s:s.status+' · '+(s.mode||'single'),cat:'session',run:function(){closePalette();activeId=s.id;refreshSessions();openPreview(s.id);streamEvents(s.id);}});});}catch(e){}
610
+ paletteFilter('');}
611
+ function closePalette(){document.getElementById('cmdk').classList.remove('show');}
612
+ function paletteFilter(q){q=q.toLowerCase();var list=document.getElementById('cmdkList');var m=cmdkItems.filter(function(it){return (it.t+' '+it.s+' '+it.cat).toLowerCase().indexOf(q)>=0;});cmdkSel=0;
613
+ if(!m.length){list.innerHTML='<div class="empty" style="padding:26px">No matches</div>';return;}list.innerHTML='';
614
+ m.forEach(function(it,i){var el=h('div','cmdk-it'+(i===0?' sel':''),'<span class="ic">'+I.chev+'</span><div class="m"><div class="tt">'+esc(it.t)+'</div><div class="ss">'+esc(it.s)+'</div></div><span class="cat">'+esc(it.cat)+'</span>');el.onclick=it.run;el.dataset.i=i;list.appendChild(el);});
615
+ list._m=m;}
616
+ document.getElementById('cmdkInput').addEventListener('input',function(e){paletteFilter(e.target.value);});
617
+ document.getElementById('cmdkInput').addEventListener('keydown',function(e){var list=document.getElementById('cmdkList');var m=list._m||[];
618
+ if(e.key==='ArrowDown'){e.preventDefault();cmdkSel=Math.min(m.length-1,cmdkSel+1);}
619
+ else if(e.key==='ArrowUp'){e.preventDefault();cmdkSel=Math.max(0,cmdkSel-1);}
620
+ else if(e.key==='Enter'){if(m[cmdkSel])m[cmdkSel].run();return;}
621
+ else return;
622
+ [].forEach.call(list.children,function(c,i){c.classList.toggle('sel',i===cmdkSel);});});
623
+
624
+ /* ---- init ---- */
625
+ clearConsole();renderModePop();syncSendFace();renderTimeline([]);refreshSessions();feat('agents');loadRequests();
626
+ document.getElementById('app').classList.add('no-prev');
627
+ setInterval(refreshSessions,5000);setInterval(loadRequests,3000);
628
+ document.getElementById('goal').addEventListener('keydown',function(e){if((e.metaKey||e.ctrlKey)&&e.key==='Enter')startTask();});
629
+ document.addEventListener('keydown',function(e){
630
+ if((e.metaKey||e.ctrlKey)&&e.key.toLowerCase()==='k'){e.preventDefault();openPalette();}
631
+ if(e.key==='Escape'){closeFeatures();closeSecurity();closePalette();closePop();}
632
+ });
633
+ `;