@lloyal-labs/dev-tools 0.1.1 → 0.3.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/react.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
- * The dev pane's web/desktop surface: a cog FAB that docks a full-width pane
4
- * over the harness's own view Timeline · Sources · Settings.
3
+ * The dev pane's web/desktop surface: the harness view's LAYOUT SHELL. The
4
+ * app renders inside the shell's scroll container and the pane docks BELOW
5
+ * it as its own flex region — Timeline · Sources · Settings. Opening the
6
+ * pane shrinks the app's viewport (the Chrome DevTools anchoring); it never
7
+ * covers the app.
5
8
  *
6
- * Mounted ONCE beside the app's view; it subscribes to the same bridge stream
9
+ * Wraps the app's view ONCE (the view is its children); it subscribes to the same bridge stream
7
10
  * through {@link createDevStore} (wire-gated: a production stream folds
8
11
  * nothing but `config:loaded`). Rendering is hand-rolled — no timeline
9
12
  * library: the design's invariants (fixed-span sliding window, stepped live
@@ -26,6 +29,10 @@ const C = {
26
29
  const HOST_CPU = '#00897b';
27
30
  const TOOL_PALETTE = ['#e8710a', '#8430ce', '#00897b', '#d01884', '#827717', '#0097a7'];
28
31
  const mono = 'ui-monospace, "SF Mono", Menlo, Consolas, monospace';
32
+ const runBtn = {
33
+ fontSize: 10.5, padding: '2px 10px', borderRadius: 11, cursor: 'pointer',
34
+ border: '1px solid #dadce0', background: '#fff', color: C.dim, fontWeight: 600,
35
+ };
29
36
  /** Per-tool color, assigned first-seen — stable within a run. */
30
37
  function useToolColors() {
31
38
  const map = useRef(new Map());
@@ -37,6 +44,18 @@ function useToolColors() {
37
44
  };
38
45
  }
39
46
  const letterOf = (name) => (name[0] || '?').toUpperCase();
47
+ /** Invisible companion that forces one re-render when a toast expires —
48
+ * the store stops repainting after the run ends, and a toast must never
49
+ * outlive its 8 seconds on a frozen clock. */
50
+ function ToastDismiss({ at }) {
51
+ const [, force] = useState(0);
52
+ useEffect(() => {
53
+ const left = Math.max(0, 8000 - (performance.now() - at)) + 50;
54
+ const t = setTimeout(() => force((n) => n + 1), left);
55
+ return () => clearTimeout(t);
56
+ }, [at]);
57
+ return null;
58
+ }
40
59
  /** Enter/Space activates a clickable — pairs with role="button" tabIndex={0}. */
41
60
  const keyActivate = (fn) => (e) => {
42
61
  if (e.key === 'Enter' || e.key === ' ') {
@@ -136,9 +155,13 @@ function highlightJson(src) {
136
155
  /** A tool result in full: pretty-printed and token-colored when it parses as
137
156
  * JSON, scrollable past ~14 lines, and the copy control carries EVERY byte —
138
157
  * the block never truncates. */
139
- function JsonBlock({ text }) {
158
+ function JsonBlock({ text, raw = false }) {
140
159
  const [copied, setCopied] = useState(false);
141
160
  const { pretty, body } = useMemo(() => {
161
+ // raw: byte-for-byte — a compiled prompt that HAPPENS to be valid JSON
162
+ // must never be reformatted; what you copy is what the model saw.
163
+ if (raw)
164
+ return { pretty: text, body: null };
142
165
  try {
143
166
  const p = JSON.stringify(JSON.parse(text), null, 2);
144
167
  return { pretty: p, body: highlightJson(p) };
@@ -146,7 +169,7 @@ function JsonBlock({ text }) {
146
169
  catch {
147
170
  return { pretty: text, body: null };
148
171
  }
149
- }, [text]);
172
+ }, [text, raw]);
150
173
  const copy = () => {
151
174
  navigator.clipboard.writeText(pretty).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1200); }, () => { });
152
175
  };
@@ -199,7 +222,7 @@ function runEndS(m) {
199
222
  return m.runStartAt === null ? 0 : Math.max(0, (end - m.runStartAt) / 1000);
200
223
  }
201
224
  // ═══════════════════════════════════════════════════════════════
202
- export function DevPane({ bridge, controls = [], title }) {
225
+ export function DevPane({ bridge, controls = [], title, runCommands = {}, children }) {
203
226
  // The store is a per-bridge SINGLETON: a remount reattaches to the running
204
227
  // fold (full history intact) instead of restarting it and desyncing. It is
205
228
  // deliberately NOT destroyed on unmount — it lives with the page, like the
@@ -208,64 +231,103 @@ export function DevPane({ bridge, controls = [], title }) {
208
231
  const rev = useStore(store, (s) => s.rev);
209
232
  const m = store.getState().model;
210
233
  const [open, setOpen] = useState(false);
211
- // The FAB renders only when the wire said dev production ships inert.
234
+ // While the pane is open, everything is seen live; the failure badge
235
+ // shows only failures newer than the last moment the pane was open.
236
+ const seenAtRef = useRef(0);
237
+ useEffect(() => { if (open)
238
+ seenAtRef.current = performance.now(); }, [open, rev]);
239
+ // The shell: app above, dev chrome below/over. Rendered in EVERY state —
240
+ // dev off, cog closed, pane open — so the app subtree keeps its position
241
+ // in the tree and never remounts when the dev flag or the pane toggles.
242
+ const shell = (content) => (_jsxs("div", { style: { height: '100dvh', display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [_jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'auto', position: 'relative' }, children: children }), content] }));
243
+ // The cog renders only when the wire said dev — production ships inert.
212
244
  if (!m.dev)
213
- return null;
245
+ return shell(null);
214
246
  if (!open) {
215
247
  // The closed cog still tells the story: amber ? = the planner is waiting
216
248
  // on the USER (the one state that blocks everything), red = an agent
217
249
  // failed this run, blue = agents live right now. Nothing = quiet.
218
- const liveCount = [...m.lanes.values()].filter((l) => l.doneAt === null).length;
219
- const failed = [...m.lanes.values()].some((l) => l.outcome === 'failed');
250
+ // A stop can halt lanes without closing them — an ended run counts none.
251
+ const liveCount = isLive(m)
252
+ ? [...m.lanes.values()].filter((l) => l.doneAt === null).length
253
+ : 0;
254
+ // A user cancel is a deliberate cull, not a failure — it never badges.
255
+ const failedLanes = [...m.lanes.values()].filter((l) => l.outcome === 'failed' && l.failReason !== 'user_cancel');
256
+ const failed = failedLanes.length > 0;
257
+ const lastFailAt = failedLanes.reduce((mx, l) => Math.max(mx, l.doneAt ?? 0), 0);
220
258
  // iOS-style badge: always the one red, meaning carried by the glyph —
221
- // ? = the planner waits on the user, ! = an agent failed, n = agents live.
259
+ // ? = the planner waits on the user, ! = an unseen failure, n = agents live.
222
260
  const badge = m.clarifying
223
261
  ? '?'
224
262
  : liveCount > 0 ? String(liveCount)
225
- : failed ? '!' : null;
263
+ : failed && lastFailAt > seenAtRef.current ? '!' : null;
226
264
  const fabTitle = m.clarifying
227
265
  ? 'the planner is waiting on your answer'
228
266
  : liveCount > 0
229
267
  ? `${liveCount} agent${liveCount === 1 ? '' : 's'} live${failed ? ' · one failed' : ''}`
230
268
  : failed ? 'an agent failed — open for the reason' : 'dev pane (LLOYAL_DEV)';
231
- return (_jsxs("button", { onClick: () => setOpen(true), "aria-label": "open the dev pane", title: fabTitle, style: {
232
- position: 'fixed', right: 20, bottom: 20, width: 44, height: 44,
233
- borderRadius: '50%', background: '#fff', border: `1px solid #dadce0`,
234
- display: 'grid', placeItems: 'center', color: C.dim, cursor: 'pointer',
235
- boxShadow: '0 2px 8px rgba(32,33,36,.14)', zIndex: 40, padding: 0,
236
- }, children: [badge && (_jsx("span", { style: {
237
- position: 'absolute', top: -6, right: -6, minWidth: 20, height: 20,
238
- borderRadius: 10, background: 'linear-gradient(180deg, #ff544a 0%, #ff2d1f 100%)',
239
- color: '#fff', fontSize: 12, fontWeight: 600,
240
- display: 'grid', placeItems: 'center', padding: '0 6px', boxSizing: 'border-box',
241
- lineHeight: 1, boxShadow: '0 1px 3px rgba(0,0,0,.35)',
242
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
243
- }, children: badge })), _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })] })] }));
269
+ // A park or failure in the last 8s surfaces as a one-line toast beside
270
+ // the cog. Age reads the REAL clock (paintedAt freezes when the run
271
+ // ends) and a timer forces the dismissal render — no immortal toasts.
272
+ const nowP = performance.now();
273
+ let toast = null;
274
+ let toastAt = 0;
275
+ for (const r of m.retrievals) {
276
+ if (r.retry && nowP - r.retry.at < 8000 && r.settledAt === null) {
277
+ toast = `${r.tool} rate-limited · retrying`;
278
+ toastAt = r.retry.at;
279
+ }
280
+ }
281
+ for (const l of failedLanes) {
282
+ if (l.doneAt !== null && nowP - l.doneAt < 8000) {
283
+ toast = `agent #${l.agentId} failed — open for the reason`;
284
+ toastAt = l.doneAt;
285
+ }
286
+ }
287
+ return shell(_jsxs(_Fragment, { children: [toast && (_jsxs("div", { role: "status", "aria-live": "polite", style: {
288
+ position: 'fixed', right: 74, bottom: 28, zIndex: 40,
289
+ background: '#fff', border: `1px solid ${C.warnBorder}`, borderRadius: 8,
290
+ padding: '6px 12px', fontSize: 11.5, color: C.warn, boxShadow: '0 2px 8px rgba(32,33,36,.14)',
291
+ }, children: [toast, _jsx(ToastDismiss, { at: toastAt })] })), _jsxs("button", { onClick: () => setOpen(true), "aria-label": "open the dev pane", title: fabTitle, style: {
292
+ position: 'fixed', right: 20, bottom: 20, width: 44, height: 44,
293
+ borderRadius: '50%', background: '#fff', border: `1px solid #dadce0`,
294
+ display: 'grid', placeItems: 'center', color: C.dim, cursor: 'pointer',
295
+ boxShadow: '0 2px 8px rgba(32,33,36,.14)', zIndex: 40, padding: 0,
296
+ }, children: [badge && (_jsx("span", { style: {
297
+ position: 'absolute', top: -6, right: -6, minWidth: 20, height: 20,
298
+ borderRadius: 10, background: 'linear-gradient(180deg, #ff544a 0%, #ff2d1f 100%)',
299
+ color: '#fff', fontSize: 12, fontWeight: 600,
300
+ display: 'grid', placeItems: 'center', padding: '0 6px', boxSizing: 'border-box',
301
+ lineHeight: 1, boxShadow: '0 1px 3px rgba(0,0,0,.35)',
302
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
303
+ }, children: badge })), _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })] })] })] }));
244
304
  }
245
- return _jsx(Pane, { store: store, m: m, rev: rev, controls: controls, title: title, onClose: () => setOpen(false) });
305
+ return shell(_jsx(Pane, { store: store, m: m, rev: rev, controls: controls, title: title, runCommands: runCommands, onClose: () => setOpen(false) }));
246
306
  }
247
307
  // ═══ the docked pane ═══
248
- function Pane({ store, m, rev, controls, title, onClose }) {
308
+ function Pane({ store, m, rev, controls, title, runCommands, onClose }) {
249
309
  const [tab, setTab] = useState('timeline');
250
310
  const [selAgent, setSelAgent] = useState(null);
251
311
  const [feedW, setFeedW] = useState(feedWidthPref);
312
+ const [paneH, setPaneH] = useState(paneHeightPref);
252
313
  const toolColor = useToolColors();
253
314
  const paneRef = useRef(null);
254
- // The pane is fixed, so the document doesn't know it's there reserve its
255
- // height as body padding while open, so the app's bottom content can always
256
- // scroll clear of it. Restored on close/unmount.
315
+ // A dragged height outlives the viewport that fit it — re-clamp on window
316
+ // resize so the pane can never swallow the whole shell (the app area is
317
+ // minHeight: 0 and would collapse to nothing).
257
318
  useEffect(() => {
258
- const prev = document.body.style.paddingBottom;
259
- const apply = () => {
260
- if (paneRef.current)
261
- document.body.style.paddingBottom = `${paneRef.current.offsetHeight}px`;
262
- };
263
- apply();
264
- window.addEventListener('resize', apply);
265
- return () => {
266
- window.removeEventListener('resize', apply);
267
- document.body.style.paddingBottom = prev;
319
+ const onResize = () => {
320
+ setPaneH((h) => {
321
+ if (h === null)
322
+ return h;
323
+ const clamped = clampPaneH(h);
324
+ if (clamped !== h)
325
+ paneHeightPref = clamped;
326
+ return clamped;
327
+ });
268
328
  };
329
+ window.addEventListener('resize', onResize);
330
+ return () => window.removeEventListener('resize', onResize);
269
331
  }, []);
270
332
  useEffect(() => {
271
333
  const onKey = (e) => {
@@ -290,17 +352,30 @@ function Pane({ store, m, rev, controls, title, onClose }) {
290
352
  boxShadow: on ? `inset 0 2px 0 ${C.text}` : undefined,
291
353
  });
292
354
  return (_jsxs("div", { ref: paneRef, style: {
293
- position: 'fixed', left: 0, right: 0, bottom: 0, height: 'min(560px, 72vh)',
355
+ position: 'relative', flex: 'none',
356
+ height: paneH !== null ? paneH : 'min(560px, 72vh)',
294
357
  background: '#fff', borderTop: '1px solid #bdc1c6', display: 'flex',
295
- flexDirection: 'column', zIndex: 50, fontSize: 12, color: C.text,
358
+ flexDirection: 'column', fontSize: 12, color: C.text,
296
359
  fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
297
- }, children: [_jsxs("div", { role: "tablist", style: { height: 30, display: 'flex', alignItems: 'stretch', background: C.chromeBg, borderBottom: '1px solid #d9dce1', flex: 'none' }, children: [['timeline', 'sources', 'settings'].map((t) => {
360
+ }, children: [_jsx(PaneResizer, { height: paneH, paneRef: paneRef, onHeight: (h) => { paneHeightPref = h; setPaneH(h); } }), _jsxs("div", { role: "tablist", style: { height: 30, display: 'flex', alignItems: 'stretch', background: C.chromeBg, borderBottom: '1px solid #d9dce1', flex: 'none' }, children: [['timeline', 'sources', 'settings'].map((t) => {
298
361
  const settled = t === 'sources' ? m.retrievals.filter((r) => r.settledAt !== null).length : 0;
299
362
  return (_jsxs("div", { role: "tab", tabIndex: 0, "aria-selected": tab === t, style: tabStyle(tab === t), onClick: () => setTab(t), onKeyDown: (e) => { if (e.key === 'Enter' || e.key === ' ') {
300
363
  e.preventDefault();
301
364
  setTab(t);
302
365
  } }, children: [t[0].toUpperCase() + t.slice(1), settled > 0 && _jsx("span", { style: { marginLeft: 5, fontFamily: mono, fontSize: 9.5, color: C.faint }, children: settled })] }, t));
303
- }), _jsx("span", { style: { flex: 1 } }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '0 12px', fontSize: 10.5, color: C.dim }, children: [_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 5 }, children: [_jsx("span", { style: { width: 14, height: 5, borderRadius: 3, background: C.agent, display: 'inline-block' } }), " agent"] }), _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 5 }, children: [_jsx("span", { style: { width: 14, height: 5, display: 'inline-block', borderRadius: 3, background: 'repeating-linear-gradient(135deg, rgba(26,115,232,.45) 0 4px, rgba(26,115,232,.12) 4px 9px)' } }), " waiting"] }), toolsSeen.map((t) => (_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 5 }, children: [_jsx(Badge, { color: toolColor(t), letter: letterOf(t), size: 15 }), " ", t] }, t))), _jsxs("span", { style: { fontFamily: mono, display: 'inline-flex', alignItems: 'center', gap: 7 }, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: '50%', background: live ? C.ok : C.faint } }), live ? 'live' : m.runStartAt === null ? 'idle' : 'run complete'] }), _jsx("span", { style: { cursor: 'pointer', color: C.dim }, onClick: onClose, title: "collapse to the cog", children: "\u2715" })] })] }), tab === 'timeline' && (_jsxs("div", { style: { flex: 1, minHeight: 0, display: 'flex' }, children: [_jsx(Timeline, { m: m, rev: rev, store: store, selAgent: selAgent, onSelect: setSelAgent, toolColor: toolColor }), selAgent !== null && m.lanes.has(selAgent) && (_jsxs(_Fragment, { children: [_jsx(FeedResizer, { width: feedW, onWidth: (w) => { feedWidthPref = w; setFeedW(w); } }), _jsx(AgentFeed, { m: m, lane: m.lanes.get(selAgent), toolColor: toolColor, onClose: () => setSelAgent(null), onJump: setSelAgent, nowMs: store.getState().paintedAt, width: feedW })] }))] })), tab === 'sources' && _jsx(Sources, { m: m, toolColor: toolColor }), tab === 'settings' && _jsx(Settings, { m: m, controls: controls, send: (c) => store.send(c) }), _jsxs("div", { style: {
366
+ }), _jsx("span", { style: { flex: 1 } }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '0 12px', fontSize: 10.5, color: C.dim }, children: [_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 5 }, children: [_jsx("span", { style: { width: 14, height: 5, borderRadius: 3, background: C.agent, display: 'inline-block' } }), " agent"] }), _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 5 }, children: [_jsx("span", { style: { width: 14, height: 5, display: 'inline-block', borderRadius: 3, background: 'repeating-linear-gradient(135deg, rgba(26,115,232,.45) 0 4px, rgba(26,115,232,.12) 4px 9px)' } }), " waiting"] }), toolsSeen.map((t) => (_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 5 }, children: [_jsx(Badge, { color: toolColor(t), letter: letterOf(t), size: 15 }), " ", t] }, t))), live && (runCommands.pause || runCommands.wrapUp || runCommands.stop) && (_jsxs("span", { style: { display: 'inline-flex', gap: 6 }, children: [runCommands.pause && (_jsx("button", { onClick: () => store.send({ type: m.pausedAt !== null ? 'resume' : 'pause' }), disabled: m.windingDownAt !== null, title: m.windingDownAt !== null
367
+ ? 'finishing — the run is winding down'
368
+ : m.pausedAt !== null
369
+ ? 'play — the next tick proceeds from the settled state'
370
+ : 'pause — hold at the tick boundary; branches stay resident', style: m.windingDownAt !== null ? { ...runBtn, opacity: 0.4, cursor: 'default' }
371
+ : m.pausedAt !== null ? { ...runBtn, color: C.ok, borderColor: '#c9e7d4' } : runBtn, children: m.pausedAt !== null ? '▶ play' : '⏸ pause' })), runCommands.wrapUp && (_jsx("button", { onClick: () => store.send({ type: 'wrap_up' }), disabled: m.pausedAt !== null || m.windingDownAt !== null, title: m.windingDownAt !== null
372
+ ? 'agents are wrapping up and reporting'
373
+ : m.pausedAt !== null
374
+ ? 'paused — press play first'
375
+ : 'finish up — agents wrap up and report; the trajectory is kept', style: m.pausedAt !== null || m.windingDownAt !== null
376
+ ? { ...runBtn, opacity: 0.4, cursor: 'default' } : runBtn, children: m.windingDownAt !== null ? 'finishing…' : 'finish up' })), runCommands.stop && (_jsx("button", { onClick: () => store.send({ type: 'stop' }), title: "halt the run", style: { ...runBtn, color: C.fail, borderColor: '#f0c4c1' }, children: "stop" }))] })), _jsxs("span", { style: { fontFamily: mono, display: 'inline-flex', alignItems: 'center', gap: 7 }, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: '50%', background: live ? (m.pausedAt !== null ? C.warn : C.ok) : C.faint } }), live
377
+ ? (m.pausedAt !== null ? 'paused' : m.windingDownAt !== null ? 'finishing' : 'live')
378
+ : m.runStartAt === null ? 'idle' : 'run complete'] }), _jsx("span", { style: { cursor: 'pointer', color: C.dim }, onClick: onClose, title: "collapse to the cog", children: "\u2715" })] })] }), tab === 'timeline' && (_jsxs("div", { style: { flex: 1, minHeight: 0, display: 'flex' }, children: [_jsx(Timeline, { m: m, rev: rev, store: store, selAgent: selAgent, onSelect: setSelAgent, toolColor: toolColor }), selAgent !== null && m.lanes.has(selAgent) && (_jsxs(_Fragment, { children: [_jsx(FeedResizer, { width: feedW, onWidth: (w) => { feedWidthPref = w; setFeedW(w); } }), _jsx(AgentFeed, { m: m, lane: m.lanes.get(selAgent), toolColor: toolColor, onClose: () => setSelAgent(null), onJump: setSelAgent, nowMs: store.getState().paintedAt, width: feedW, send: (c) => store.send(c), canCancel: !!runCommands.cancelAgent })] }))] })), tab === 'sources' && _jsx(Sources, { m: m, toolColor: toolColor }), tab === 'settings' && _jsx(Settings, { m: m, controls: controls, send: (c) => store.send(c) }), _jsxs("div", { style: {
304
379
  height: 24, display: 'flex', alignItems: 'center', gap: 14, padding: '0 12px',
305
380
  borderTop: `1px solid ${C.border}`, background: '#f8f9fa', fontSize: 10.5, color: C.dim, flex: 'none',
306
381
  }, children: [_jsxs("span", { style: { fontFamily: mono }, children: [m.runStartAt === null
@@ -391,7 +466,7 @@ function Timeline({ m, rev, store, selAgent, onSelect, toolColor }) {
391
466
  };
392
467
  const onMouseUp = () => { drag.current = null; };
393
468
  return (_jsxs("div", { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', minHeight: 0 }, children: [_jsxs("div", { style: { height: 54, borderBottom: `1px solid ${C.border}`, display: 'flex', flex: 'none' }, children: [_jsx(MetricCell, { heading: "pressure", color: C.agent, values: strip.slice(-60).map((p) => p.pct), value: pct === null ? '—' : `${pct}% · ${m.pressure[m.pressure.length - 1]?.cellsUsed.toLocaleString()} / ${m.pressure[m.pressure.length - 1]?.nCtx.toLocaleString()}`, title: "kv cells used \u2014 context pressure on the model side" }), _jsx(MetricCell, { heading: "cpu", color: HOST_CPU, values: host.slice(-60).map((h) => h.cpu), value: lastHost ? `${lastHost.cpu}%` : '—', title: "this process's cpu, % of the whole machine" }), _jsx(MetricCell, { heading: "mem", color: "#9aa0a6", values: host.slice(-60).filter((h) => h.memUsedMb !== null).map((h) => h.memUsedMb), value: lastHost && lastHost.memUsedMb !== null && lastHost.memTotalMb > 0
394
- ? `${(lastHost.memUsedMb / 1024).toFixed(1)} / ${Math.round(lastHost.memTotalMb / 1024)}G` : '—', title: "machine memory in use, honestly counted (vm_stat / MemAvailable)" }), _jsx(MetricCell, { heading: "harness", color: "#5f6368", last: true, values: host.slice(-60).map((h) => h.rssMb), value: lastHost ? `${(lastHost.rssMb / 1024).toFixed(1)}G` : '—', title: "this process's resident memory \u2014 weights + KV + runtime" })] }), _jsxs("div", { style: { height: 20, display: 'flex', borderBottom: `1px solid ${C.border}`, flex: 'none' }, children: [_jsx("div", { style: { width: GUTTER, flex: 'none', padding: '4px 0 0 14px' }, children: _jsx("span", { style: label, children: lanes.length ? `${lanes.length} agents` : '' }) }), _jsx("div", { style: { flex: 1, position: 'relative', fontFamily: mono }, children: ticks.map((t) => (_jsx("span", { style: { position: 'absolute', left: px(t) - GUTTER, fontSize: 9, color: C.faint }, children: t >= 60 ? `${Math.floor(t / 60)}m${t % 60 ? String(t % 60).padStart(2, '0') : ''}` : `${t}s` }, t))) })] }), _jsxs("div", { ref: trackRef, style: { flex: 1, position: 'relative', overflowY: 'auto', overflowX: 'hidden', cursor: drag.current ? 'grabbing' : undefined }, onMouseDown: onMouseDown, onMouseMove: onMouseMove, onMouseUp: onMouseUp, onMouseLeave: onMouseUp, onDoubleClick: () => { setFollow(true); setPanWindow(null); }, children: [!follow && live && (_jsx("button", { onClick: (e) => { e.stopPropagation(); setFollow(true); setPanWindow(null); }, style: {
469
+ ? `${(lastHost.memUsedMb / 1024).toFixed(1)} / ${Math.round(lastHost.memTotalMb / 1024)}G` : '—', title: "machine memory in use, honestly counted (vm_stat / MemAvailable)" }), _jsx(MetricCell, { heading: "harness", color: "#5f6368", last: true, values: host.slice(-60).map((h) => h.rssMb), value: lastHost ? `${(lastHost.rssMb / 1024).toFixed(1)}G` : '—', title: "this process's resident memory \u2014 weights + KV + runtime" })] }), _jsxs("div", { style: { height: 20, display: 'flex', borderBottom: `1px solid ${C.border}`, flex: 'none' }, children: [_jsx("div", { style: { width: GUTTER, flex: 'none', padding: '4px 0 0 14px' }, children: _jsx("span", { style: label, children: lanes.length ? `${lanes.length} agents` : '' }) }), _jsx("div", { style: { flex: 1, position: 'relative', fontFamily: mono }, children: ticks.map((t) => (_jsx("span", { style: { position: 'absolute', left: px(t) - GUTTER, fontSize: 9, color: C.faint }, children: t >= 60 ? `${Math.floor(t / 60)}m${t % 60 ? String(t % 60).padStart(2, '0') : ''}` : `${t}s` }, t))) })] }), _jsxs("div", { ref: trackRef, style: { flex: 1, position: 'relative', overflowY: 'auto', overflowX: 'hidden', cursor: drag.current ? 'grabbing' : undefined }, onMouseDown: onMouseDown, onMouseMove: onMouseMove, onMouseUp: onMouseUp, onMouseLeave: onMouseUp, onDoubleClick: () => { setFollow(true); setPanWindow(null); }, children: [m.runStartAt === null && (_jsx("div", { style: { position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', color: C.faint, fontSize: 11.5, pointerEvents: 'none' }, children: "run a query \u2014 agents appear here live" })), !follow && live && (_jsx("button", { onClick: (e) => { e.stopPropagation(); setFollow(true); setPanWindow(null); }, style: {
395
470
  position: 'absolute', top: 6, right: 10, zIndex: 3, cursor: 'pointer',
396
471
  fontSize: 10.5, padding: '3px 10px', borderRadius: 12, border: `1px solid ${C.border}`,
397
472
  background: '#fff', color: C.agentDark, fontWeight: 600, boxShadow: '0 1px 4px rgba(32,33,36,.12)',
@@ -402,7 +477,11 @@ const label = {
402
477
  };
403
478
  function Lane({ m, l, px, on, secOf, nowS, live, selected, toolColor, onClick, gutter, windowEnd }) {
404
479
  const s = secOf(l.spawnedAt);
405
- const e = l.doneAt === null ? nowS : secOf(l.doneAt);
480
+ // A paused run's lanes freeze at the hold — the widening gap to the
481
+ // now-line IS the pause, drawn honestly (no decode happened there).
482
+ const e = l.doneAt === null
483
+ ? (m.pausedAt !== null ? Math.min(nowS, secOf(m.pausedAt)) : nowS)
484
+ : secOf(l.doneAt);
406
485
  const capL = px(s);
407
486
  const capR = px(Math.min(e, windowEnd));
408
487
  const running = l.doneAt === null;
@@ -421,16 +500,24 @@ function Lane({ m, l, px, on, secOf, nowS, live, selected, toolColor, onClick, g
421
500
  };
422
501
  const glyph = l.outcome === 'failed' ? '✗' : l.outcome === 'recovered' ? '↻' : '✓';
423
502
  const glyphBg = l.outcome === 'failed' ? C.fail : l.outcome === 'recovered' ? C.agent : C.ok;
503
+ // A user cancel is a deliberate cull, not a failure of the agent's own —
504
+ // the row wears it: faint red wash, red identity, a 'cancelled' pill.
505
+ const cancelled = l.outcome === 'failed' && l.failReason === 'user_cancel';
424
506
  const endLabel = l.role === 'planner' && m.plan
425
507
  ? `plan · ${m.plan.tasks.length} tasks · ${fmtS(e - s)}`
426
508
  : l.outcome === 'recovered' ? `recovered · ${fmtS(e - s)}`
427
- : l.outcome === 'failed' ? `${l.failReason ?? l.dropReason ?? 'failed'} · ${fmtS(e - s)}` : fmtS(e - s);
509
+ : cancelled ? fmtS(e - s)
510
+ : l.outcome === 'failed' ? `${l.failReason ?? l.dropReason ?? 'failed'} · ${fmtS(e - s)}` : fmtS(e - s);
428
511
  const endColor = l.outcome === 'failed' ? C.fail : l.outcome === 'recovered' ? C.agent : '#3c4043';
429
512
  return (_jsxs("div", { onClick: onClick, role: "button", tabIndex: 0, onKeyDown: keyActivate(onClick), "aria-label": `open agent ${l.agentId}`, style: {
430
513
  display: 'flex', height: 38, borderTop: `1px solid ${C.hair}`, position: 'relative', cursor: 'pointer',
431
- background: selected ? C.chromeBg : undefined,
514
+ background: selected ? C.chromeBg : cancelled ? 'rgba(179,38,30,.05)' : undefined,
432
515
  boxShadow: selected ? `inset 3px 0 0 ${C.text}` : undefined,
433
- }, children: [_jsxs("div", { style: { width: gutter, flex: 'none', display: 'flex', alignItems: 'baseline', gap: 6, padding: '12px 0 0 14px', fontSize: 11.5 }, children: [_jsx("span", { style: { fontWeight: 600 }, children: l.role ?? 'agent' }), _jsxs("span", { style: { color: C.dim, fontSize: 10.5, fontFamily: mono }, children: ["#", l.agentId] })] }), _jsxs("div", { style: { flex: 1, position: 'relative', overflow: 'hidden' }, children: [capR > gutter && capL < px(windowEnd) && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
516
+ }, children: [cancelled && (_jsx("span", { style: {
517
+ position: 'absolute', right: 10, top: 10, zIndex: 2,
518
+ fontSize: 9.5, fontWeight: 600, padding: '2px 9px', borderRadius: 9,
519
+ color: C.fail, background: 'rgba(179,38,30,.08)', border: '1px solid #f0c4c1',
520
+ }, children: "cancelled" })), _jsxs("div", { style: { width: gutter, flex: 'none', display: 'flex', alignItems: 'baseline', gap: 6, padding: '12px 0 0 14px', fontSize: 11.5 }, children: [_jsx("span", { style: { fontWeight: 600, color: cancelled ? C.fail : undefined }, children: l.role ?? 'agent' }), _jsxs("span", { style: { color: cancelled ? C.fail : C.dim, fontSize: 10.5, fontFamily: mono }, children: ["#", l.agentId] })] }), _jsxs("div", { style: { flex: 1, position: 'relative', overflow: 'hidden' }, children: [capR > gutter && capL < px(windowEnd) && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
434
521
  position: 'absolute', top: 12, height: 14,
435
522
  left: Math.max(capL, gutter) - gutter,
436
523
  width: Math.max(4, capR - Math.max(capL, gutter)),
@@ -490,21 +577,82 @@ function Lane({ m, l, px, on, secOf, nowS, live, selected, toolColor, onClick, g
490
577
  * own border stays the visual line. Width persists for the page session. */
491
578
  const FEED_W_DEFAULT = 420;
492
579
  let feedWidthPref = FEED_W_DEFAULT;
580
+ const feedMax = () => Math.max(480, window.innerWidth - 380);
581
+ const clampFeedW = (w) => Math.round(Math.min(Math.max(w, 320), feedMax()));
493
582
  function FeedResizer({ width, onWidth }) {
494
583
  const drag = useRef(null);
495
- return (_jsx("div", { style: { width: 7, margin: '0 -3.5px', flex: 'none', cursor: 'col-resize', zIndex: 3, position: 'relative', userSelect: 'none', touchAction: 'none' }, title: "drag to resize \\u00b7 double-click to reset", onDoubleClick: () => { feedWidthPref = FEED_W_DEFAULT; onWidth(FEED_W_DEFAULT); }, onPointerDown: (e) => {
584
+ return (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": "resize the agent feed", "aria-valuemin": 320, "aria-valuemax": feedMax(), "aria-valuenow": Math.round(width), tabIndex: 0, style: { width: 7, margin: '0 -3.5px', flex: 'none', cursor: 'col-resize', zIndex: 3, position: 'relative', userSelect: 'none', touchAction: 'none' }, title: "drag to resize \\u00b7 double-click to reset", onDoubleClick: () => { feedWidthPref = FEED_W_DEFAULT; onWidth(FEED_W_DEFAULT); }, onKeyDown: (e) => {
585
+ if (e.key === 'ArrowLeft')
586
+ onWidth(clampFeedW(width + 32));
587
+ else if (e.key === 'ArrowRight')
588
+ onWidth(clampFeedW(width - 32));
589
+ else if (e.key === 'Home')
590
+ onWidth(320);
591
+ else if (e.key === 'End')
592
+ onWidth(feedMax());
593
+ else if (e.key === 'Enter') {
594
+ feedWidthPref = FEED_W_DEFAULT;
595
+ onWidth(FEED_W_DEFAULT);
596
+ }
597
+ else
598
+ return;
599
+ e.preventDefault();
600
+ e.stopPropagation();
601
+ }, onPointerDown: (e) => {
496
602
  e.preventDefault();
497
603
  drag.current = { x: e.clientX, w: width };
498
604
  e.currentTarget.setPointerCapture(e.pointerId);
499
605
  }, onPointerMove: (e) => {
500
606
  if (!drag.current)
501
607
  return;
502
- const w = drag.current.w + (drag.current.x - e.clientX);
503
- onWidth(Math.round(Math.min(Math.max(w, 320), Math.max(480, window.innerWidth - 380))));
608
+ onWidth(clampFeedW(drag.current.w + (drag.current.x - e.clientX)));
609
+ }, onPointerUp: (e) => {
610
+ drag.current = null;
611
+ if (e.currentTarget.hasPointerCapture(e.pointerId))
612
+ e.currentTarget.releasePointerCapture(e.pointerId);
613
+ }, onPointerCancel: () => { drag.current = null; }, onLostPointerCapture: () => { drag.current = null; } }));
614
+ }
615
+ /** Drag handle on the pane's TOP edge — pull up for a taller pane, exactly
616
+ * the Chrome DevTools gesture; double-click (or Enter) restores the
617
+ * responsive default (min(560px, 72vh)). Same invisible-strip affordance as
618
+ * FeedResizer: the cursor is the hint, the pane's own border stays the
619
+ * line. Height persists for the page session. Keyboard: a focusable
620
+ * horizontal separator — arrows step, Home/End jump to the bounds. */
621
+ let paneHeightPref = null;
622
+ const PANE_MIN = 180;
623
+ const paneMax = () => Math.max(PANE_MIN, window.innerHeight - 48);
624
+ const clampPaneH = (h) => Math.round(Math.min(Math.max(h, PANE_MIN), paneMax()));
625
+ function PaneResizer({ height, paneRef, onHeight }) {
626
+ const drag = useRef(null);
627
+ const current = () => height ?? (paneRef.current?.offsetHeight ?? 560);
628
+ return (_jsx("div", { role: "separator", "aria-orientation": "horizontal", "aria-label": "resize the pane", "aria-valuemin": PANE_MIN, "aria-valuemax": paneMax(), "aria-valuenow": Math.round(current()), tabIndex: 0, style: { position: 'absolute', top: -3.5, left: 0, right: 0, height: 7, cursor: 'ns-resize', zIndex: 3, userSelect: 'none', touchAction: 'none' }, title: "drag to resize \\u00b7 double-click to reset", onDoubleClick: () => onHeight(null), onKeyDown: (e) => {
629
+ if (e.key === 'ArrowUp')
630
+ onHeight(clampPaneH(current() + 32));
631
+ else if (e.key === 'ArrowDown')
632
+ onHeight(clampPaneH(current() - 32));
633
+ else if (e.key === 'Home')
634
+ onHeight(PANE_MIN);
635
+ else if (e.key === 'End')
636
+ onHeight(paneMax());
637
+ else if (e.key === 'Enter')
638
+ onHeight(null);
639
+ else
640
+ return;
641
+ e.preventDefault();
642
+ e.stopPropagation();
643
+ }, onPointerDown: (e) => {
644
+ e.preventDefault();
645
+ drag.current = { y: e.clientY, h: current() };
646
+ e.currentTarget.setPointerCapture(e.pointerId);
647
+ }, onPointerMove: (e) => {
648
+ if (!drag.current)
649
+ return;
650
+ onHeight(clampPaneH(drag.current.h + (drag.current.y - e.clientY)));
504
651
  }, onPointerUp: (e) => {
505
652
  drag.current = null;
506
- e.currentTarget.releasePointerCapture(e.pointerId);
507
- } }));
653
+ if (e.currentTarget.hasPointerCapture(e.pointerId))
654
+ e.currentTarget.releasePointerCapture(e.pointerId);
655
+ }, onPointerCancel: () => { drag.current = null; }, onLostPointerCapture: () => { drag.current = null; } }));
508
656
  }
509
657
  /** The epistemics instrument: entropy (area) and surprisal (line) in nats
510
658
  * over the agent's WHOLE span — x is anchored time, so samples never slide,
@@ -563,10 +711,14 @@ function EpistemicsChart({ m, lane, nowMs }) {
563
711
  .filter((x) => x >= 0 && x <= B);
564
712
  const last = e[e.length - 1];
565
713
  const ppl = lanePpl(lane);
714
+ const tail = e.slice(-64);
715
+ const recentPpl = tail.length >= 16
716
+ ? Math.exp(tail.reduce((a, x) => a + x.s, 0) / tail.length)
717
+ : null;
566
718
  const chip = (color, label, value, title) => (_jsxs("span", { style: { display: 'inline-flex', alignItems: 'baseline', gap: 4, whiteSpace: 'nowrap' }, title: title, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: 2, background: color, alignSelf: 'center' } }), _jsx("span", { style: { color: C.dim }, children: label }), _jsx("span", { style: { fontFamily: mono, fontSize: 10, color: C.text }, children: value.toFixed(2) })] }));
567
- return (_jsxs("div", { style: { borderBottom: `1px solid ${C.border}` }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', gap: 10, padding: '6px 12px 4px' }, children: [_jsx("span", { style: { color: C.dim }, children: "epistemics" }), _jsx("span", { style: { flex: 1 } }), chip(C.agent, 'entropy', last.h, 'how open the model\u2019s next-token choice was \u2014 nats, over the full vocabulary'), chip(SURPRISAL_COLOR, 'surprisal', last.s, 'how unexpected the picked token was \u2014 \u2212ln p, in nats'), ppl !== null && (_jsxs("span", { style: { fontFamily: mono, fontSize: 10, color: C.faint, whiteSpace: 'nowrap' }, title: "perplexity \\u2014 exp of mean surprisal over this agent\\u2019s tokens; compare agents, lower reads more fluent", children: ["ppl ", ppl.toFixed(2)] }))] }), _jsxs("div", { style: { position: 'relative', padding: '0 12px 7px' }, children: [_jsxs("svg", { viewBox: `0 0 ${B} ${H}`, preserveAspectRatio: "none", style: { width: '100%', height: H, display: 'block' }, children: [_jsx("line", { x1: 0, y1: y(yMax / 2), x2: B, y2: y(yMax / 2), stroke: C.hair, strokeWidth: 1, vectorEffect: "non-scaling-stroke" }), _jsx("path", { d: entropyArea, fill: "rgba(26,115,232,.16)" }), _jsx("path", { d: entropyLine, fill: "none", stroke: C.agent, strokeWidth: 1.1, vectorEffect: "non-scaling-stroke" }), _jsx("path", { d: surprisalLine, fill: "none", stroke: SURPRISAL_COLOR, strokeWidth: 1, strokeOpacity: 0.75, vectorEffect: "non-scaling-stroke" }), ticks.map((x, i) => (_jsx("line", { x1: x, y1: H - 5, x2: x, y2: H, stroke: "#e8710a", strokeWidth: 2, vectorEffect: "non-scaling-stroke", children: _jsx("title", { children: "a tool result landed" }) }, i)))] }), _jsxs("span", { style: { position: 'absolute', top: 0, left: 14, fontFamily: mono, fontSize: 8.5, color: C.faint }, children: [yMax.toFixed(0), " nats"] })] })] }));
719
+ return (_jsxs("div", { style: { borderBottom: `1px solid ${C.border}` }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', gap: 10, padding: '6px 12px 4px' }, children: [_jsx("span", { style: { color: C.dim }, children: "epistemics" }), _jsx("span", { style: { flex: 1 } }), chip(C.agent, 'entropy', last.h, 'how open the model\u2019s next-token choice was \u2014 nats, over the full vocabulary'), chip(SURPRISAL_COLOR, 'surprisal', last.s, 'how unexpected the picked token was \u2014 \u2212ln p, in nats'), ppl !== null && (_jsxs("span", { style: { fontFamily: mono, fontSize: 10, color: C.faint, whiteSpace: 'nowrap' }, title: "ppl = exp of mean surprisal over ALL this agent's tokens (compare agents); recent = the same over the last ~64 \u2014 the live fluency signal, e.g. across quants", children: ["ppl ", ppl.toFixed(2), recentPpl !== null ? ` · recent ${recentPpl.toFixed(2)}` : ''] }))] }), _jsxs("div", { style: { position: 'relative', padding: '0 12px 7px' }, children: [_jsxs("svg", { viewBox: `0 0 ${B} ${H}`, preserveAspectRatio: "none", style: { width: '100%', height: H, display: 'block' }, children: [_jsx("line", { x1: 0, y1: y(yMax / 2), x2: B, y2: y(yMax / 2), stroke: C.hair, strokeWidth: 1, vectorEffect: "non-scaling-stroke" }), _jsx("path", { d: entropyArea, fill: "rgba(26,115,232,.16)" }), _jsx("path", { d: entropyLine, fill: "none", stroke: C.agent, strokeWidth: 1.1, vectorEffect: "non-scaling-stroke" }), _jsx("path", { d: surprisalLine, fill: "none", stroke: SURPRISAL_COLOR, strokeWidth: 1, strokeOpacity: 0.75, vectorEffect: "non-scaling-stroke" }), ticks.map((x, i) => (_jsx("line", { x1: x, y1: H - 5, x2: x, y2: H, stroke: "#e8710a", strokeWidth: 2, vectorEffect: "non-scaling-stroke", children: _jsx("title", { children: "a tool result landed" }) }, i)))] }), _jsxs("span", { style: { position: 'absolute', top: 0, left: 14, fontFamily: mono, fontSize: 8.5, color: C.faint }, children: [yMax.toFixed(0), " nats"] })] })] }));
568
720
  }
569
- function AgentFeed({ m, lane, toolColor, onClose, onJump, nowMs, width }) {
721
+ function AgentFeed({ m, lane, toolColor, onClose, onJump, nowMs, width, send, canCancel }) {
570
722
  const [expanded, setExpanded] = useState(() => new Set(['report']));
571
723
  const toggle = (id) => {
572
724
  setExpanded((prev) => {
@@ -628,7 +780,7 @@ function AgentFeed({ m, lane, toolColor, onClose, onJump, nowMs, width }) {
628
780
  }, children: [_jsxs("div", { style: {
629
781
  height: 30, flex: 'none', display: 'flex', alignItems: 'center', gap: 8, padding: '0 12px',
630
782
  borderBottom: `1px solid ${C.border}`, background: '#f8f9fa',
631
- }, children: [_jsxs("span", { style: { fontFamily: mono, fontWeight: 600, fontSize: 11 }, children: [lane.role ?? 'agent', " #", lane.agentId] }), lane.parentAgentId !== null && m.lanes.has(lane.parentAgentId) && (_jsxs("span", { style: chip, children: ["forked from #", lane.parentAgentId] })), _jsxs("span", { style: chip, children: [lane.outcome, lane.doneAt !== null && m.runStartAt !== null ? ` · ${fmtS((lane.doneAt - lane.spawnedAt) / 1000)}` : ''] }), _jsx("span", { style: { flex: 1 } }), _jsx("span", { style: { cursor: 'pointer', color: C.dim }, onClick: onClose, title: "close \u2014 the timeline returns to full width", children: "\u2715" })] }), _jsxs("div", { style: { overflowY: 'auto', flex: 1, fontSize: 11, paddingBottom: 8 }, children: [_jsx(EpistemicsChart, { m: m, lane: lane, nowMs: nowMs }), (lane.failReason || lane.dropReason) && (_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', padding: '6px 12px', gap: 8 }, children: [_jsx("span", { style: { color: C.dim, width: 92, flex: 'none' }, children: "pool said" }), _jsx("span", { style: { fontFamily: mono, fontSize: 11 }, children: lane.dropReason ?? lane.failReason })] })), items.map((it) => it.el), planView, lane.role !== 'planner' && (lane.report !== null || lane.outcome === 'failed') && (_jsxs("div", { style: feedItem, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 7, cursor: 'pointer' }, onClick: () => toggle('report'), children: [_jsx("span", { style: { color: C.faint, fontSize: 9, width: 9, flex: 'none' }, children: reportOpen ? '▾' : '▸' }), _jsx("b", { style: { fontSize: 11 }, children: "report" }), _jsx("span", { style: { color: C.faint }, children: lane.report === null ? 'not delivered' : lane.reportSource === 'recovery' ? 'extracted by recovery' : 'delivered' })] }), reportOpen && lane.report !== null && (_jsx("div", { style: { margin: '6px 0 2px 16px', lineHeight: 1.5, color: '#3c4043', whiteSpace: 'pre-wrap' }, children: lane.report }))] }))] })] }));
783
+ }, children: [_jsxs("span", { style: { fontFamily: mono, fontWeight: 600, fontSize: 11 }, children: [lane.role ?? 'agent', " #", lane.agentId] }), lane.parentAgentId !== null && m.lanes.has(lane.parentAgentId) && (_jsxs("span", { style: chip, children: ["forked from #", lane.parentAgentId] })), _jsxs("span", { style: chip, children: [lane.outcome, lane.doneAt !== null && m.runStartAt !== null ? ` · ${fmtS((lane.doneAt - lane.spawnedAt) / 1000)}` : ''] }), _jsx("span", { style: { flex: 1 } }), canCancel && lane.doneAt === null && (_jsx("button", { onClick: () => send({ type: 'cancel_agent', agentId: lane.agentId }), "aria-label": "cancel this agent", title: "cancel this agent \u2014 its branch is reclaimed; siblings continue. Works while paused: evaluate trajectories, cull, play", style: { ...runBtn, color: C.fail, borderColor: '#f0c4c1', padding: '2px 7px', display: 'inline-flex', alignItems: 'center' }, children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("polyline", { points: "3 6 5 6 21 6" }), _jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }), _jsx("line", { x1: "10", y1: "11", x2: "10", y2: "17" }), _jsx("line", { x1: "14", y1: "11", x2: "14", y2: "17" })] }) })), _jsx("span", { style: { cursor: 'pointer', color: C.dim }, onClick: onClose, title: "close \u2014 the timeline returns to full width", children: "\u2715" })] }), _jsxs("div", { style: { overflowY: 'auto', flex: 1, fontSize: 11, paddingBottom: 8 }, children: [_jsx(EpistemicsChart, { m: m, lane: lane, nowMs: nowMs }), lane.prompt && (_jsxs("div", { style: { borderBottom: `1px solid ${C.border}`, padding: '4px 12px 6px' }, children: [_jsxs("div", { role: "button", tabIndex: 0, onClick: () => toggle('prompt'), onKeyDown: keyActivate(() => toggle('prompt')), style: { display: 'flex', alignItems: 'baseline', gap: 7, cursor: 'pointer', padding: '2px 0' }, title: "the compiled prompt suffix that seeded this agent. In shared-spine mode the system + tool header lives on the spine PREFIX (inherited via fork) and is not repeated here; recursive agents inherit further parent context the same way", children: [_jsx("span", { style: { color: C.faint, fontSize: 9, width: 9, flex: 'none' }, children: expanded.has('prompt') ? '▾' : '▸' }), _jsx("span", { style: { color: C.dim }, children: "prompt" }), _jsxs("span", { style: { fontFamily: mono, fontSize: 10, color: C.faint }, children: [lane.prompt.tokenCount.toLocaleString(), " tok"] })] }), expanded.has('prompt') && _jsx(JsonBlock, { text: lane.prompt.text, raw: true })] })), (lane.failReason || lane.dropReason) && (_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', padding: '6px 12px', gap: 8 }, children: [_jsx("span", { style: { color: C.dim, width: 92, flex: 'none' }, children: "pool said" }), _jsx("span", { style: { fontFamily: mono, fontSize: 11 }, children: lane.dropReason ?? lane.failReason })] })), items.map((it) => it.el), planView, lane.role !== 'planner' && (lane.report !== null || lane.outcome === 'failed') && (_jsxs("div", { style: feedItem, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 7, cursor: 'pointer' }, onClick: () => toggle('report'), children: [_jsx("span", { style: { color: C.faint, fontSize: 9, width: 9, flex: 'none' }, children: reportOpen ? '▾' : '▸' }), _jsx("b", { style: { fontSize: 11 }, children: "report" }), _jsx("span", { style: { color: C.faint }, children: lane.report === null ? 'not delivered' : lane.reportSource === 'recovery' ? 'extracted by recovery' : 'delivered' })] }), reportOpen && lane.report !== null && (_jsx("div", { style: { margin: '6px 0 2px 16px', lineHeight: 1.5, color: '#3c4043', whiteSpace: 'pre-wrap' }, children: lane.report }))] }))] })] }));
632
784
  }
633
785
  const feedItem = { padding: '7px 12px', borderTop: '1px solid #eceef1' };
634
786
  const chip = {