@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.19.1-fork.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/dist/src/agent-execution.js +114 -87
  3. package/dist/src/background-widget.d.ts +61 -0
  4. package/dist/src/background-widget.js +23 -16
  5. package/dist/src/decoders.js +28 -1
  6. package/dist/src/execution.d.ts +4 -2
  7. package/dist/src/execution.js +43 -2
  8. package/dist/src/host-delivery.js +1 -0
  9. package/dist/src/host-navigator.d.ts +18 -0
  10. package/dist/src/host-navigator.js +71 -13
  11. package/dist/src/host-recovery.d.ts +5 -2
  12. package/dist/src/host-recovery.js +3 -3
  13. package/dist/src/host-view.d.ts +21 -1
  14. package/dist/src/host-view.js +138 -47
  15. package/dist/src/host.d.ts +2 -2
  16. package/dist/src/host.js +246 -60
  17. package/dist/src/index.d.ts +3 -1
  18. package/dist/src/index.js +1346 -622
  19. package/dist/src/index.js.map +3 -3
  20. package/dist/src/paths.d.ts +2 -0
  21. package/dist/src/paths.js +2 -0
  22. package/dist/src/registry.js +3 -2
  23. package/dist/src/roles.d.ts +70 -0
  24. package/dist/src/roles.js +326 -0
  25. package/dist/src/script-tools.d.ts +27 -0
  26. package/dist/src/script-tools.js +87 -0
  27. package/dist/src/settings.d.ts +26 -0
  28. package/dist/src/settings.js +277 -0
  29. package/dist/src/trajectory.d.ts +39 -4
  30. package/dist/src/trajectory.js +82 -5
  31. package/dist/src/types.d.ts +30 -3
  32. package/dist/src/types.js +1 -1
  33. package/dist/src/utils.d.ts +21 -1
  34. package/dist/src/utils.js +40 -0
  35. package/dist/src/validation.d.ts +10 -17
  36. package/dist/src/validation.js +29 -458
  37. package/dist/starter/index.js +0 -2
  38. package/dist/starter/index.js.map +1 -1
  39. package/dist/starter/review-loop.d.ts +0 -13
  40. package/dist/starter/review-loop.js +0 -2
  41. package/dist/subagents/index.js +1344 -798
  42. package/dist/subagents/index.js.map +3 -3
  43. package/dist/subagents/src/contracts.d.ts +49 -29
  44. package/dist/subagents/src/contracts.js +18 -7
  45. package/dist/subagents/src/index.d.ts +1 -1
  46. package/dist/subagents/src/index.js +15 -7
  47. package/dist/subagents/src/manager.js +32 -13
  48. package/dist/subagents/src/navigator.js +396 -247
  49. package/dist/subagents/src/view.d.ts +24 -2
  50. package/dist/subagents/src/view.js +107 -80
  51. package/dist/trajectory/assets/index.html +370 -232
  52. package/dist/trajectory/assets/prism.min.js +13 -0
  53. package/dist/trajectory/index.js +445 -277
  54. package/dist/trajectory/index.js.map +3 -3
  55. package/dist/trajectory/src/assets/index.html +370 -232
  56. package/dist/trajectory/src/assets/prism.min.js +13 -0
  57. package/dist/trajectory/src/export.js +4 -2
  58. package/dist/trajectory/src/index.d.ts +2 -0
  59. package/dist/trajectory/src/index.js +151 -60
  60. package/dist/trajectory/src/server.js +138 -11
  61. package/dist/trajectory/src/server.js.map +2 -2
  62. package/package.json +7 -6
  63. package/skills/pi-extensible-workflows/SKILL.md +2 -0
  64. package/src/agent-execution.ts +110 -80
  65. package/src/background-widget.ts +26 -19
  66. package/src/decoders.ts +22 -1
  67. package/src/execution.ts +41 -4
  68. package/src/host-delivery.ts +1 -0
  69. package/src/host-navigator.ts +58 -14
  70. package/src/host-recovery.ts +8 -7
  71. package/src/host-view.ts +116 -42
  72. package/src/host.ts +223 -55
  73. package/src/index.ts +3 -1
  74. package/src/paths.ts +3 -0
  75. package/src/registry.ts +3 -2
  76. package/src/roles.ts +327 -0
  77. package/src/script-tools.ts +76 -0
  78. package/src/settings.ts +224 -0
  79. package/src/trajectory.ts +97 -9
  80. package/src/types.ts +12 -5
  81. package/src/utils.ts +36 -1
  82. package/src/validation.ts +30 -356
  83. package/starter/review-loop.ts +0 -2
  84. package/subagents/README.md +3 -3
  85. package/subagents/src/contracts.ts +22 -7
  86. package/subagents/src/index.ts +18 -7
  87. package/subagents/src/manager.ts +31 -13
  88. package/subagents/src/navigator.ts +336 -180
  89. package/subagents/src/view.ts +108 -76
  90. package/trajectory/src/assets/index.html +370 -232
  91. package/trajectory/src/assets/prism.min.js +13 -0
  92. package/trajectory/src/export.ts +4 -2
  93. package/trajectory/src/index.ts +124 -47
  94. package/trajectory/src/server.ts +114 -14
@@ -5,112 +5,103 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Trajectory</title>
7
7
  <link rel="icon" type="image/png" href="./favicon.png" />
8
+ <script>try { const theme = localStorage.getItem("traj-theme"); if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme; } catch { /* Preferences are optional. */ }</script>
8
9
  <style>
10
+ /* Paper sheets on a desk. Colours follow the system scheme unless html[data-theme] pins light or dark. */
9
11
  :root {
10
- --font: "Segoe UI", "SF Pro Text", "Helvetica Neue", sans-serif;
11
- --bg: #141414; --bg-2: #1b1b1b; --bg-3: #222; --bg-hover: #2a2a2a; --bg-sel: #2a3140;
12
- --line: #2c2c2c; --text: #e8e8e8; --dim: #8d8d8d; --mute: #5c5c5c; --accent: #6ea0ff;
13
- --ok: #3ecf8e; --warn: #e6c35c; --err: #e25d5d; --left: 300px; --right: 360px; --swim: 220px;
14
- --side: #101010; --insp: #161616;
12
+ color-scheme: light dark;
13
+ --serif: "Iowan Old Style", Charter, "Bitstream Charter", "Palatino Linotype", Georgia, serif;
14
+ --font: Inter, system-ui, -apple-system, "Segoe UI", "Helvetica Neue", sans-serif;
15
+ --bg: light-dark(#ece5d6, #12110f); --bg-2: light-dark(#fbf8f1, #1c1a17); --bg-3: light-dark(#f3eee3, #26231f);
16
+ --bg-hover: light-dark(#f2ecdf, #25221e); --bg-sel: light-dark(#f7e6b5, #3a3222); --line: light-dark(#e4dbc9, #312d27);
17
+ --text: light-dark(#2b2723, #ebe4d6); --dim: light-dark(#6f665a, #a89e8e); --mute: light-dark(#a19682, #6f675b);
18
+ --accent: light-dark(#2f5d8a, #8db4de); --ok: light-dark(#4d7c3a, #9cc47f); --warn: light-dark(#b7791f, #e2b45c); --err: light-dark(#b3402e, #ea8b76);
19
+ --side: var(--bg-2); --insp: var(--bg-2); --left: 300px; --right: 360px; --swim: 220px;
20
+ --rule: light-dark(#ece4d3, #26231f); --margin: light-dark(#e7b9a9, #4a302a);
21
+ --sheet: 0 1px 2px light-dark(#5a47261f, #0000004d), 0 12px 28px -16px light-dark(#5a472640, #00000080);
22
+ --code-bg: light-dark(#f5f0e4, #161412); --code-text: light-dark(#3b352d, #ddd5c6);
23
+ --note-warn: light-dark(#fff5d9, #2d2617); --note-err: light-dark(#fbe8e2, #321f1a);
24
+ --attn-bg: light-dark(#f6e3b4, #3d321c); --attn-text: light-dark(#7a5310, #e9c476);
25
+ --bar-ok: light-dark(#b9cfa6, #3d5534); --bar-spin: light-dark(#a9c1dc, #34506e); --bar-fail: light-dark(#e3a595, #6b3a30);
26
+ --bar-wait: light-dark(#ecd29b, #5d4b25); --bar-paused: light-dark(#d6cfc2, #3a3630); --model: light-dark(#6c56b8, #b39ce8);
27
+ --grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 .35 0 0 0 0 .28 0 0 0 0 .18 0 0 0 .07 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
15
28
  }
16
- html[data-theme="tty"] {
17
- --font: ui-monospace, "IBM Plex Mono", "SF Mono", Menlo, Consolas, monospace;
18
- --bg: #1e1e2e; --bg-2: #181825; --bg-3: #313244; --bg-hover: #313244; --bg-sel: #45475a;
19
- --line: #313244; --text: #cdd6f4; --dim: #a6adc8; --mute: #6c7086; --accent: #89b4fa;
20
- --ok: #a6e3a1; --warn: #f9e2af; --err: #f38ba8; --side: #181825; --insp: #181825;
21
- }
22
- html[data-theme="paper"] {
23
- --font: "Segoe UI", "SF Pro Text", "Helvetica Neue", sans-serif;
24
- --bg: #f6f7f8; --bg-2: #eef0f2; --bg-3: #e4e7eb; --bg-hover: #e8eaed; --bg-sel: #dbe7ff;
25
- --line: #d8dce2; --text: #121417; --dim: #5b6370; --mute: #8b929c; --accent: #2563eb;
26
- --ok: #17803d; --warn: #b45309; --err: #c41c1c; --side: #eef0f2; --insp: #f3f4f6;
27
- }
28
- * { box-sizing: border-box; scrollbar-width: thin; scrollbar-color: var(--mute) var(--bg-2); }
29
+ html[data-theme="light"] { color-scheme: light; }
30
+ html[data-theme="dark"] { color-scheme: dark; }
31
+ * { box-sizing: border-box; scrollbar-width: thin; scrollbar-color: var(--mute) transparent; }
29
32
  *::-webkit-scrollbar { width: 10px; height: 10px; }
30
- *::-webkit-scrollbar-track { background: var(--bg-2); }
33
+ *::-webkit-scrollbar-track { background: transparent; }
31
34
  *::-webkit-scrollbar-thumb { background: var(--mute); border: 2px solid var(--bg-2); border-radius: 6px; }
32
35
  *::-webkit-scrollbar-thumb:hover { background: var(--dim); }
33
- :root, html[data-theme="tty"], html[data-theme="harness"] { color-scheme: dark; }
34
- html[data-theme="paper"] { color-scheme: light; }
35
- html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font: 13px/1.4 var(--font); }
36
- html[data-theme="tty"] body { letter-spacing: -0.02em; }
37
- html[data-theme="tty"] * { border-radius: 0 !important; }
36
+ html, body { height: 100%; margin: 0; background: var(--grain), var(--bg); color: var(--text); font: 13.5px/1.5 var(--font); -webkit-font-smoothing: antialiased; }
38
37
  button, input { font: inherit; color: inherit; }
39
38
  button { cursor: pointer; }
40
- .app { height: 100%; display: flex; min-width: 0; }
41
- .sidebar { width: var(--left); flex: none; background: var(--side); border-right: 1px solid var(--line); display: flex; flex-direction: column; padding: 12px 10px; overflow: auto; min-width: 180px; }
42
- .splitbar { width: 5px; flex: none; cursor: col-resize; background: var(--line); }
43
- .splitbar.row { width: auto; height: 5px; padding: 0; cursor: row-resize; }
44
- .splitbar:hover, .splitbar.drag, .splitbar.row:hover, .splitbar.row.drag { background: var(--accent); }
45
- .brand { font-size: 11px; letter-spacing: .12em; color: var(--dim); font-weight: 600; padding: 4px 8px 12px; display: flex; align-items: center; gap: 6px; }
46
- .brand .tab { border: 1px solid var(--line); background: var(--bg-3); color: var(--dim); padding: 2px 5px; font-size: 9px; }
47
- .brand .tab.on { color: var(--accent); border-color: var(--accent); }
39
+ .app { height: 100%; display: flex; min-width: 0; padding: 12px; }
40
+ .sidebar, .stage, .inspector { background: var(--bg-2); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--sheet); overflow: hidden; }
41
+ .sidebar { width: var(--left); flex: none; display: flex; flex-direction: column; padding: 16px 12px; overflow: auto; min-width: 180px; }
42
+ .splitbar { width: 12px; flex: none; cursor: col-resize; background: transparent; }
43
+ .splitbar.row { width: auto; height: 10px; padding: 0; cursor: row-resize; }
44
+ .splitbar:hover, .splitbar.drag { background: linear-gradient(var(--accent), var(--accent)) center / 2px 40% no-repeat; }
45
+ .splitbar.row:hover, .splitbar.row.drag { background: linear-gradient(var(--accent), var(--accent)) center / 40% 2px no-repeat; }
46
+ .brand { padding: 2px 4px 16px 8px; display: flex; align-items: center; gap: 6px; }
47
+ .brand-name { flex: 1; font: italic 600 21px/1 var(--serif); color: var(--text); }
48
+ .theme-toggle { width: 26px; height: 26px; flex: none; border: 1px solid var(--line); border-radius: 50%; background: var(--bg-3); color: var(--dim); font-size: 13px; line-height: 1; padding: 0; }
49
+ .theme-toggle:hover { color: var(--text); border-color: var(--mute); }
48
50
  .group { margin-bottom: 6px; }
49
- .folder { display: flex; align-items: center; gap: 6px; width: 100%; text-align: left; border: 0; background: none; color: var(--text); padding: 4px 6px; border-radius: 6px; }
51
+ .folder { display: flex; align-items: center; gap: 6px; width: 100%; text-align: left; border: 0; background: none; color: var(--text); padding: 4px 6px; border-radius: 8px; }
50
52
  .folder:hover { background: var(--bg-hover); }
51
53
  .folder .marker { width: 10px; color: var(--dim); }
52
54
  .folder .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
53
55
  .folder .count { color: var(--dim); font: 11px ui-monospace, Menlo, Consolas, monospace; }
54
56
  .group-children { margin-left: 8px; }
55
- .pub { border-radius: 8px; padding: 4px 6px; }
56
- .pub.on { background: #1c1c1c; }
57
+ .pub { border-radius: 10px; padding: 4px 6px; }
58
+ .pub.on { background: light-dark(#f4eee1, #24211d); }
57
59
  .pub .row { display: flex; align-items: center; gap: 8px; }
58
60
  .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--mute); }
59
- .dot.live { background: var(--ok); box-shadow: 0 0 0 3px #3ecf8e22; }
61
+ .dot.live { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent); }
60
62
  .pub .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
61
- .attn { margin-left: auto; font-size: 10px; color: var(--warn); font-weight: 600; }
62
- .children { margin: 0 0 0 8px; padding: 2px 0 0 8px; border-left: 1px solid #333; }
63
- .run-item { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; border: 0; background: none; color: var(--dim); padding: 4px 6px; border-radius: 6px; font-size: 12px; }
64
- .run-item.on { background: #1c2433; color: var(--text); }
63
+ .attn { margin-left: auto; font-size: 10px; font-weight: 600; background: var(--attn-bg); color: var(--attn-text); border-radius: 999px; padding: 1px 7px; }
64
+ .children { margin: 0 0 0 8px; padding: 2px 0 0 8px; border-left: 1px solid var(--line); }
65
+ .run-item { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; border: 0; background: none; color: var(--dim); padding: 4px 6px; border-radius: 8px; font-size: 12px; }
66
+ .run-item:hover { background: var(--bg-hover); }
67
+ .run-item.on { background: var(--bg-sel); color: var(--text); box-shadow: inset 3px 0 0 var(--warn); }
68
+ .more-items.section-toggle { margin: 2px 0; padding: 4px 6px; color: var(--mute); font-size: 11px; border-radius: 8px; }
69
+ .more-items.section-toggle:hover { color: var(--text); background: var(--bg-hover); }
65
70
  .subagents-head { margin: 4px 0 2px; padding: 3px 6px; font-size: 10px; letter-spacing: .08em; color: var(--mute); }
66
- .steer-input { min-width: 180px; flex: 1; background: var(--bg-3); border: 1px solid var(--line); border-radius: 6px; padding: 6px 8px; }
71
+ .steer-input { min-width: 180px; flex: 1; background: var(--bg-3); border: 1px solid var(--line); border-radius: 8px; padding: 6px 8px; }
67
72
  .g-run { width: 14px; flex: none; display: inline-block; text-align: center; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; line-height: 1; }
68
73
  .g-run.ok { color: var(--ok); }
69
74
  .g-run.wait { color: var(--warn); }
70
75
  .g-run.fail { color: var(--err); }
71
76
  .g-run.queued, .g-run.paused { color: var(--mute); }
72
77
  .g-run.spin { color: var(--accent); }
73
- .g-run.spin::before { content: "⠋"; animation: braille .8s linear infinite; }
78
+ /* Rotating a transform only repaints the glyph; animating `content` relaid out the whole document each step. */
79
+ .g-run.spin::before { content: "◜"; display: inline-block; animation: spin-glyph .8s linear infinite; will-change: transform; }
74
80
  .g-run.budget { color: var(--warn); }
75
- @keyframes braille {
76
- 0% { content: "⠋"; } 12% { content: "⠙"; } 25% { content: "⠹"; } 37% { content: "⠸"; }
77
- 50% { content: "⠼"; } 62% { content: "⠴"; } 75% { content: "⠦"; } 87% { content: "⠧"; } 100% { content: "⠇"; }
78
- }
81
+ @keyframes spin-glyph { to { transform: rotate(360deg); } }
79
82
  .run-item .n { min-width: 0; flex: 1; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
80
- .run-item .await { flex: none; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 3px; background: #3a3214; color: var(--warn); }
83
+ .run-item .await { flex: none; font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 999px; background: var(--attn-bg); color: var(--attn-text); }
81
84
  .run-item .tel { flex: none; margin-left: auto; font-size: 10px; color: var(--mute); font-family: ui-monospace, Menlo, Consolas, monospace; white-space: nowrap; }
82
85
  .stage { flex: 1; min-width: 0; display: flex; flex-direction: column; }
83
- .top { display: flex; align-items: center; gap: 14px; padding: 8px 14px; border-bottom: 1px solid var(--line); }
84
- .crumb { color: var(--dim); font-size: 12px; }
86
+ .top { display: flex; align-items: center; gap: 14px; padding: 14px 20px 12px; border-bottom: 1px solid var(--line); }
87
+ .crumb { font: 16px/1.2 var(--serif); color: var(--dim); }
88
+ .crumb #canvas-run, .crumb #agent-crumb { color: var(--text); font-weight: 600; }
85
89
  .crumb a { color: var(--accent); text-decoration: none; cursor: pointer; }
86
90
  .run-stats { margin-left: auto; color: var(--dim); font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11px; display: flex; align-items: center; gap: 8px; }
87
91
  .hidden { display: none !important; }
88
- .toolbar { display: flex; align-items: center; gap: 6px; padding: 8px 14px; border-bottom: 1px solid var(--line); }
89
- .seg button { background: none; border: 0; color: var(--dim); padding: 4px 8px; border-radius: 4px; }
92
+ .toolbar { display: flex; align-items: center; gap: 6px; padding: 10px 20px; border-bottom: 1px solid var(--line); }
93
+ .seg button { background: none; border: 0; color: var(--dim); padding: 4px 8px; border-radius: 6px; }
90
94
  .seg button.on { background: var(--bg-3); color: var(--text); }
91
- .search { margin-left: auto; background: var(--bg-3); border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; width: 180px; }
95
+ .search { margin-left: auto; background: var(--bg-3); border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px; width: 180px; }
92
96
  .body { flex: 1; min-height: 0; display: flex; }
93
- .inspector { width: var(--right); flex: none; border-left: 1px solid var(--line); background: var(--insp); overflow: hidden; min-width: 220px; display: flex; flex-direction: column; }
97
+ .inspector { width: var(--right); flex: none; min-width: 220px; display: flex; flex-direction: column; }
94
98
  .inspector .ins-head, .inspector .itabs { flex: none; }
95
99
  .inspector .ins-body { flex: 1; min-height: 0; overflow: auto; }
96
100
  #tool-pane, #sys-pane { overflow: visible; }
97
101
  .inspector .code:not(.script) { flex: 1; max-height: none; min-height: 0; }
98
- @media (max-width: 900px) {
99
- .app { flex-direction: column; overflow: auto; }
100
- .sidebar, .inspector { width: 100% !important; min-width: 0; max-height: 28vh; }
101
- .splitbar { display: none; }
102
- .splitbar.row { display: block; }
103
- .stage { min-height: 48vh; }
104
- .top, .toolbar { flex-wrap: wrap; }
105
- .run-stats { margin-left: 0; width: 100%; }
106
- .search { width: 100%; margin: 6px 0 0; }
107
- .modal { inset: 8px; }
108
- .swim .lane { grid-template-columns: 16px 56px 1fr; }
109
- .swim .lane.child { grid-template-columns: 16px 44px 1fr; }
110
- .swim .axis { grid-template-columns: 80px 1fr; }
111
- }
112
- .swim { height: var(--swim); flex: 0 0 var(--swim); min-height: 0; overflow-y: auto; padding: 8px 14px 12px; border-bottom: 1px solid var(--line); background: linear-gradient(#1d1d1d 1px, transparent 1px) 0 0 / 100% 18px, linear-gradient(90deg, #1d1d1d 1px, transparent 1px) 0 0 / 24px 100%, #151515; position: relative; user-select: none; }
113
- .brush { position: absolute; top: 22px; bottom: 8px; background: #6ea0ff22; border-left: 1px solid #6ea0ff99; border-right: 1px solid #6ea0ff99; pointer-events: none; z-index: 3; }
102
+ .mobile-tabs { display: none; }
103
+ .swim { height: var(--swim); flex: 0 0 var(--swim); min-height: 0; overflow-y: auto; padding: 8px 14px 12px; border-bottom: 1px solid var(--line); background: linear-gradient(90deg, transparent 117px, var(--margin) 117px 118px, transparent 118px), repeating-linear-gradient(transparent 0 23px, var(--rule) 23px 24px), var(--bg-2); position: relative; user-select: none; }
104
+ .brush { position: absolute; top: 22px; bottom: 8px; background: color-mix(in srgb, var(--accent) 14%, transparent); border-left: 1px solid var(--accent); border-right: 1px solid var(--accent); pointer-events: none; z-index: 3; }
114
105
  .range-edge { position: absolute; top: -17px; color: var(--accent); background: var(--bg-2); padding: 1px 3px; font: 10px ui-monospace, Menlo, Consolas, monospace; white-space: nowrap; text-shadow: 0 1px var(--bg); z-index: 1; }
115
106
  .range-edge.start { left: 3px; }
116
107
  .range-edge.end { right: 3px; text-align: right; }
@@ -120,39 +111,42 @@
120
111
  .swim .axis { position: sticky; top: 0; z-index: 4; background: var(--bg-2); }
121
112
  .axis .ticks { display: flex; justify-content: space-between; position: relative; }
122
113
  .timeline .axis { grid-template-columns: 44px 1fr; gap: 8px; margin-bottom: 2px; }
123
- .now { position: absolute; width: 1px; background: #e25d5d; z-index: 2; pointer-events: none; }
114
+ .now { position: absolute; width: 1px; background: var(--err); z-index: 2; pointer-events: none; }
124
115
  .swim > .now { top: 22px; bottom: 8px; z-index: 5; }
125
116
  .timeline > .now { top: 18px; bottom: 2px; }
126
- .now span { position: absolute; top: -14px; left: 4px; color: #e25d5d; background: var(--bg-2); padding: 1px 3px; white-space: nowrap; font: 10px ui-monospace, Menlo, Consolas, monospace; }
127
- .lane { display: grid; grid-template-columns: 16px 76px 1fr; gap: 8px; align-items: center; height: 18px; margin: 3px 0; cursor: pointer; border-radius: 3px; }
128
- .lane:hover { background: #ffffff08; }
117
+ .now span { position: absolute; top: -14px; left: 4px; color: var(--err); background: var(--bg-2); padding: 1px 3px; white-space: nowrap; font: 10px ui-monospace, Menlo, Consolas, monospace; }
118
+ .lane { display: grid; grid-template-columns: 16px 76px 1fr; gap: 8px; align-items: center; height: 20px; margin: 2px 0; cursor: pointer; border-radius: 6px; }
119
+ .lane:hover { background: color-mix(in srgb, var(--text) 5%, transparent); }
129
120
  .lane.child { padding-left: 12px; grid-template-columns: 16px 64px 1fr; }
130
121
  .lane.crumb { cursor: default; opacity: .85; }
131
122
  .lane.crumb:hover { background: none; }
132
- .lane.crumb .lab { text-align: left; color: #9aa; font-style: italic; }
133
- .bar.crumb { background: #ffffff18; height: 4px; top: 3px; }
134
- .crumbband { font-size: 11px; color: #9aa; padding: 4px 12px 2px 28px; font-family: ui-monospace, Menlo, Consolas, monospace; font-style: italic; }
135
- .lane .lab { color: var(--mute); font-size: 11px; text-align: right; overflow: hidden; white-space: nowrap; }
136
- .track { position: relative; height: 10px; }
137
- .bar { position: absolute; top: 0; height: 10px; border-radius: 2px; }
138
- .bar.tool { top: 5px; height: 5px; opacity: .9; }
139
- .bar.ok { background: #2f6f55; }
140
- .bar.spin { background: #6ea0ff; }
141
- .bar.fail { background: var(--err); }
142
- .bar.wait { background: var(--warn); }
143
- .bar.paused { background: #555; }
123
+ .lane.crumb .lab { text-align: left; color: var(--mute); font-style: italic; }
124
+ .bar.crumb { background: var(--line); height: 4px; top: 3px; }
125
+ .crumbband { font-size: 11px; color: var(--mute); padding: 4px 12px 2px 28px; font-family: ui-monospace, Menlo, Consolas, monospace; font-style: italic; }
126
+ .lane .lab { color: var(--dim); font-size: 11px; text-align: right; overflow: hidden; white-space: nowrap; }
127
+ .track { position: relative; height: 12px; }
128
+ .bar { position: absolute; top: 0; height: 12px; border-radius: 999px; }
129
+ .bar.tool { top: 3px; height: 6px; border-radius: 2px; }
130
+ .bar.ok { background: var(--bar-ok); }
131
+ .bar.spin { background: var(--bar-spin); }
132
+ .bar.fail { background: var(--bar-fail); }
133
+ .bar.wait { background: var(--bar-wait); }
134
+ .bar.paused { background: var(--bar-paused); }
135
+ .bar.tool.ok { background: var(--ok); }
136
+ .bar.tool.fail { background: var(--err); }
144
137
  .split { flex: 1; min-height: 0; display: flex; }
145
138
  .list { flex: 1; overflow: auto; padding: 8px 0; }
146
139
  .workflow-card { border-bottom: 1px solid var(--line); }
147
- .workflow-head { display: grid; grid-template-columns: 16px minmax(0, 1fr) auto; gap: 8px; width: 100%; align-items: center; padding: 8px 12px; border: 0; background: none; color: inherit; text-align: left; }
148
- .workflow-head:hover, .workflow-head.on { background: var(--bg-sel); }
149
- .workflow-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
140
+ .workflow-head { display: grid; grid-template-columns: 16px minmax(0, 1fr) auto; gap: 8px; width: 100%; align-items: center; padding: 12px 20px; border: 0; background: none; color: inherit; text-align: left; }
141
+ .workflow-head:hover, .workflow-head.on { background: var(--bg-hover); }
142
+ .workflow-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 600 16px/1.2 var(--serif); }
150
143
  .workflow-meta { color: var(--dim); font: 11px ui-monospace, Menlo, Consolas, monospace; white-space: nowrap; }
151
144
  .agent-grid { min-width: 900px; padding: 0 12px 8px 28px; }
152
145
  .agent-grid-head, .agent-grid-row { display: grid; grid-template-columns: minmax(220px, 2fr) minmax(140px, 1.2fr) 80px minmax(150px, 1.5fr) 80px 72px 64px; gap: 8px; align-items: center; }
153
146
  .agent-grid-head { padding: 4px 8px; color: var(--mute); font-size: 10px; letter-spacing: .05em; }
154
- .agent-grid-row { min-height: 24px; padding: 3px 8px; cursor: pointer; font-size: 11px; }
155
- .agent-grid-row:hover, .agent-grid-row.on { background: var(--bg-hover); }
147
+ .agent-grid-row { min-height: 24px; padding: 3px 8px; cursor: pointer; font-size: 11px; border-radius: 8px; }
148
+ .agent-grid-row:hover { background: var(--bg-hover); }
149
+ .agent-grid-row.on { background: var(--bg-sel); }
156
150
  .agent-grid-row .agent-cell { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
157
151
  .agent-grid-row .agent-cell:first-child { color: var(--text); font-family: ui-monospace, Menlo, Consolas, monospace; }
158
152
  .agent-grid-scope { padding: 6px 8px 2px; color: var(--dim); font: 11px ui-monospace, Menlo, Consolas, monospace; font-style: italic; }
@@ -163,6 +157,14 @@
163
157
  .log-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: 10px; padding: 5px 8px; border-top: 1px solid var(--line); font: 11px ui-monospace, Menlo, Consolas, monospace; }
164
158
  .log-time { color: var(--mute); white-space: nowrap; }
165
159
  .log-message { min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
160
+ .calls-list { max-height: 480px; overflow: auto; }
161
+ .call-row { display: grid; grid-template-columns: 16px 7.5rem minmax(80px, 160px) minmax(0, 1fr) 64px; gap: 10px; align-items: center; padding: 5px 8px; border-top: 1px solid var(--line); font: 11px ui-monospace, Menlo, Consolas, monospace; cursor: pointer; list-style: none; }
162
+ .call-row::-webkit-details-marker { display: none; }
163
+ .call-row:hover { background: var(--bg-hover); }
164
+ .call-row b, .call-preview { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
165
+ .call-preview { color: var(--dim); }
166
+ .call-row .num { color: var(--dim); text-align: right; }
167
+ .call-body { padding: 4px 8px 12px 34px; }
166
168
  .row { display: grid; grid-template-columns: 16px 1fr; gap: 8px; align-items: center; padding: 3px 12px; cursor: pointer; }
167
169
  .row.child { padding-left: 28px; }
168
170
  .row:hover { background: var(--bg-hover); }
@@ -170,33 +172,33 @@
170
172
  .tree { color: var(--dim); font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }
171
173
  .tree .nm { color: var(--text); }
172
174
  .note { color: var(--err); font-size: 11px; }
173
- .phaseband { font-size: 11px; color: #5ec8d6; padding: 8px 12px 2px; letter-spacing: .04em; }
174
- .timeline { padding: 6px 12px 8px; border-bottom: 1px solid var(--line); background: linear-gradient(#1d1d1d 1px, transparent 1px) 0 0 / 100% 16px, linear-gradient(90deg, #1d1d1d 1px, transparent 1px) 0 0 / 18px 100%, #151515; position: relative; user-select: none; }
175
+ .phaseband { color: var(--accent); padding: 8px 12px 2px; font: italic 13px/1.3 var(--serif); }
176
+ .timeline { padding: 6px 12px 8px; border-bottom: 1px solid var(--line); background: repeating-linear-gradient(transparent 0 15px, var(--rule) 15px 16px), var(--bg-2); position: relative; user-select: none; }
175
177
  .timeline > .brush { top: 18px; bottom: 2px; }
176
178
  .tl-lane { display: grid; grid-template-columns: 44px 1fr; gap: 8px; align-items: center; height: 16px; }
177
179
  .tl-lane .lab { font-size: 10px; color: var(--mute); }
178
180
  .ticks { position: relative; height: 12px; }
179
- .tick { position: absolute; top: 2px; width: 8px; height: 8px; border-radius: 1px; cursor: pointer; }
180
- .tick.in { background: #3ecf8e; }
181
- .tick.model { background: #7b6ad8; }
182
- .tick.tl { background: #c47a20; }
181
+ .tick { position: absolute; top: 2px; width: 8px; height: 8px; border-radius: 2px; cursor: pointer; }
182
+ .tick.in { background: var(--ok); }
183
+ .tick.model { background: var(--model); }
184
+ .tick.tl { background: var(--warn); }
183
185
  .tick.err { background: var(--err); }
184
186
  .events { flex: 1; overflow: auto; }
185
- .evt { display: grid; grid-template-columns: 13.5rem 86px minmax(0, 1fr); gap: 14px; align-items: center; padding: 5px 12px; cursor: pointer; border-left: 2px solid transparent; }
187
+ .evt { display: grid; grid-template-columns: 13.5rem 86px minmax(0, 1fr); gap: 14px; align-items: center; padding: 5px 12px; cursor: pointer; border-left: 2px solid transparent; border-bottom: 1px solid color-mix(in srgb, var(--line) 60%, transparent); }
186
188
  .evt > .pill { justify-self: end; }
187
189
  .evt:hover { background: var(--bg-hover); }
188
- .evt.on { background: #243044; border-left-color: var(--accent); }
190
+ .evt.on { background: var(--bg-sel); border-left-color: var(--warn); }
189
191
  @keyframes timeline-event-highlight { from { background: var(--accent); } to { background: transparent; } }
190
192
  .evt.timeline-highlight { animation: timeline-event-highlight 1.2s ease-out; }
191
193
  .evt .gutter { color: var(--mute); font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
192
- .pill { display: inline-block; width: max-content; min-width: 0; font-size: 10px; font-weight: 700; letter-spacing: .04em; padding: 2px 6px; border-radius: 3px; text-align: center; }
193
- .pill.system { background: #3a3a3a; color: #ddd; }
194
- .pill.user { background: #2a4a7a; color: #cfe0ff; }
195
- .pill.context { background: #1d5a3a; color: #b8f5d4; }
196
- .pill.assistant { background: #3d2f6a; color: #ddd0ff; }
197
- .pill.tool { background: #5a3a10; color: #ffd08a; }
198
- .pill.skill { background: #164e63; color: #a5f3fc; }
199
- .pill.issue { background: #5a1818; color: #ffb0b0; }
194
+ .pill { display: inline-block; width: max-content; min-width: 0; font-size: 10px; font-weight: 600; letter-spacing: .06em; padding: 2px 7px; border-radius: 999px; text-align: center; }
195
+ .pill.system { background: light-dark(#ebe6db, #2c2925); color: light-dark(#4a443b, #cfc7b8); }
196
+ .pill.user { background: light-dark(#dde7f3, #22324a); color: light-dark(#264a73, #a9c6ea); }
197
+ .pill.context { background: light-dark(#e0ecd6, #23351f); color: light-dark(#3d6a2c, #a9d08f); }
198
+ .pill.assistant { background: light-dark(#ebe2f2, #33273f); color: light-dark(#5a3a78, #d2b9ea); }
199
+ .pill.tool { background: light-dark(#f8e3cc, #3d2a18); color: light-dark(#8a4b12, #f0bf87); }
200
+ .pill.skill { background: light-dark(#d9eeee, #1d3536); color: light-dark(#1f5f63, #9fd8da); }
201
+ .pill.issue { background: light-dark(#f7ddd6, #3f211b); color: light-dark(#9b2f1f, #f1a492); }
200
202
  .preview { color: var(--dim); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }
201
203
  .preview .pill.tool { margin-right: 4px; }
202
204
  .preview .preview-tool { font-size: 9px; padding: 1px 4px; }
@@ -204,47 +206,59 @@
204
206
  .preview .tool-key, .preview .tool-gap, .preview .tool-timing { color: var(--dim); }
205
207
  .preview .tool-value { font-weight: 400; }
206
208
  .preview b { color: var(--text); font-weight: 400; }
207
- .ins-head { padding: 10px 12px 6px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--line); }
209
+ .ins-head { padding: 16px 16px 10px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--line); font: 600 16px/1.2 var(--serif); }
210
+ .run-stats .agent-details { margin-left: 12px; }
208
211
  .ins-sub { color: var(--dim); font-size: 12px; }
209
212
  .tool-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }
210
- .itabs { display: flex; gap: 12px; padding: 8px 12px 0; border-bottom: 1px solid var(--line); }
213
+ .itabs { display: flex; gap: 12px; padding: 8px 16px 0; border-bottom: 1px solid var(--line); }
211
214
  .itabs span { color: var(--dim); padding-bottom: 8px; border-bottom: 2px solid transparent; cursor: pointer; font-size: 12px; }
212
215
  .itabs span.on { color: var(--accent); border-bottom-color: var(--accent); }
213
- .ins-body { padding: 12px; font-size: 12px; }
216
+ .ins-body { padding: 14px 16px; font-size: 12px; }
214
217
  .md { line-height: 1.5; }
215
218
  .md h1, .md h2, .md h3 { margin: .7em 0 .3em; font-size: 13px; }
216
219
  .md p { margin: 0 0 .7em; }
217
220
  .md ul { margin: 0 0 .7em; padding-left: 1.2em; }
218
- .md code { font-family: ui-monospace, Menlo, Consolas, monospace; background: var(--bg-3); padding: 0 4px; }
221
+ .md code { font-family: ui-monospace, Menlo, Consolas, monospace; background: var(--bg-3); padding: 0 4px; border-radius: 4px; }
219
222
  .md a { color: var(--accent); }
220
- .md pre.code { max-height: none; white-space: pre-wrap; }
223
+ .md pre { background: var(--code-bg); border: 1px solid var(--line); border-radius: 8px; padding: 8px; overflow: auto; font: 11px/1.45 ui-monospace, Menlo, Consolas, monospace; white-space: pre-wrap; color: var(--code-text); }
224
+ .md pre code { background: none; padding: 0; }
221
225
  .kv { display: grid; grid-template-columns: 90px 1fr; gap: 6px 10px; margin-bottom: 12px; }
222
226
  .kv .k { color: var(--dim); }
223
- .code { background: #101010; border: 1px solid var(--line); border-radius: 6px; padding: 8px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11px; white-space: pre-wrap; color: #c0caf5; overflow: auto; max-height: 240px; }
227
+ .code { background: var(--code-bg); border: 1px solid var(--line); border-radius: 8px; padding: 8px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11px; white-space: pre-wrap; color: var(--code-text); overflow: auto; max-height: 240px; }
224
228
  .assistant-tools { display: grid; gap: 8px; margin-top: 12px; }
225
229
  .assistant-tool { display: grid; gap: 4px; }
226
- .code .kw { color: #7aa2f7; }
227
- .code .str { color: #9ece6a; }
230
+ .token.comment, .token.prolog { color: var(--mute); font-style: italic; }
231
+ .token.string, .token.template-string, .token.char, .token.inserted { color: var(--ok); }
232
+ .token.keyword, .token.boolean, .token.builtin, .token.important { color: var(--accent); }
233
+ .token.number, .token.constant, .token.regex { color: var(--warn); }
234
+ .token.property, .token.attr-name, .token.parameter { color: light-dark(#8a3d7a, #d7a1cf); }
235
+ .token.function, .token.class-name { color: light-dark(#2f6f6f, #8fd0cf); }
236
+ .token.operator, .token.punctuation, .token.interpolation-punctuation { color: var(--dim); }
237
+ .token.deleted { color: var(--err); }
228
238
  .code.script { max-height: 4.8em; cursor: pointer; }
229
- .code.script:hover { border-color: #556; }
239
+ .code.script:hover { border-color: var(--mute); }
240
+ .inspector .code.args { max-height: min(40vh, 420px); overflow: auto; }
241
+ .workflow-args { margin: 14px 0 12px; }
242
+ .workflow-args summary.sec { margin: 0 0 8px; }
243
+ .workflow-args .code.args { margin: 0; }
244
+ .workflow-args-warning { color: var(--warn); margin-top: 8px; }
230
245
  .ln { display: grid; grid-template-columns: 44px 1fr; }
231
246
  .ln .no { color: var(--mute); text-align: right; padding-right: 12px; user-select: none; }
232
- .modal-bg { position: fixed; inset: 0; background: #000c; z-index: 40; }
233
- .modal { position: fixed; inset: 32px; background: #0e0e0e; border: 1px solid #333; border-radius: 8px; z-index: 41; display: flex; flex-direction: column; }
247
+ .modal-bg { position: fixed; inset: 0; background: light-dark(#2b27234d, #000000a6); backdrop-filter: blur(3px); z-index: 40; }
248
+ .modal { position: fixed; inset: 32px; background: var(--bg-2); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--sheet); z-index: 41; display: flex; flex-direction: column; overflow: hidden; }
234
249
  .modal-bar { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--line); }
235
250
  .modal-bar .sp { flex: 1; }
236
- .modal-bar button { background: var(--bg-3); border: 0; border-radius: 6px; padding: 4px 10px; }
251
+ .modal-bar button { background: var(--bg-3); border: 1px solid var(--line); border-radius: 8px; padding: 4px 10px; }
237
252
  .modal .code { flex: 1; max-height: none; border: 0; border-radius: 0; }
238
253
  .tool-item { padding: 7px 8px; border-bottom: 1px solid var(--line); }
239
254
  .tool-item b { font-family: ui-monospace, Menlo, Consolas, monospace; }
240
255
  .tool-item span { color: var(--dim); display: block; }
241
256
  .actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
242
- .actions button { border: 0; border-radius: 6px; padding: 6px 10px; }
257
+ .actions button { background: var(--bg-2); color: var(--text); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 1px 0 color-mix(in srgb, var(--text) 6%, transparent); padding: 6px 12px; }
258
+ .actions button:hover { border-color: var(--mute); }
243
259
  .actions button:disabled { opacity: .45; cursor: not-allowed; }
244
- .btn-ok { background: #1d5a3a; color: #b8f5d4; }
245
- .btn-no { background: #5a1818; color: #ffb0b0; }
246
- .btn-alt { background: #2a4a7a; color: #cfe0ff; }
247
- .toast { position: fixed; right: 16px; bottom: 16px; background: #111; border: 1px solid #e6c35c; color: #e6c35c; padding: 8px 12px; border-radius: 6px; display: none; z-index: 20; }
260
+ .btn-alt, .actions .btn-alt { background: var(--bg-2); color: var(--accent); border: 1px solid var(--line); border-radius: 8px; padding: 6px 12px; }
261
+ .toast { position: fixed; right: 16px; bottom: 16px; background: var(--bg-2); border: 1px solid var(--warn); color: var(--text); padding: 8px 12px; border-radius: 10px; box-shadow: var(--sheet); display: none; z-index: 20; }
248
262
  .hint { color: var(--mute); font-size: 11px; padding: 0 12px 8px; }
249
263
  .acct { width: 100%; border-collapse: collapse; font: 11px ui-monospace, Menlo, Consolas, monospace; }
250
264
  .acct th { text-align: left; color: var(--mute); font-weight: 500; padding: 0 6px 6px; }
@@ -252,59 +266,47 @@
252
266
  .acct .num { text-align: right; color: var(--dim); }
253
267
  .acct tr { cursor: pointer; }
254
268
  .acct tr:hover td { background: var(--bg-hover); }
255
- .pending { border: 1px solid #5a4b14; background: #1a1608; border-radius: 6px; padding: 8px; margin-bottom: 10px; }
256
- .pending.issue { border-color: #5a1818; background: #1a1010; }
269
+ .pending { background: var(--note-warn); border: 0; border-left: 3px solid var(--warn); border-radius: 4px 10px 10px 4px; padding: 10px 12px; margin-bottom: 10px; box-shadow: 0 1px 2px color-mix(in srgb, var(--text) 8%, transparent); }
270
+ .pending.issue { background: var(--note-err); border-left-color: var(--err); }
257
271
  .pending .ttl { font-weight: 600; margin-bottom: 4px; display: flex; gap: 6px; align-items: center; }
258
- .sec { color: var(--mute); font-size: 10px; letter-spacing: .08em; margin: 14px 0 8px; }
272
+ .sec { color: var(--mute); font-size: 10.5px; font-weight: 600; letter-spacing: .14em; margin: 14px 0 8px; }
259
273
  .section-toggle { display: block; width: 100%; padding: 0; border: 0; background: none; font: inherit; text-align: left; cursor: pointer; }
260
- #toggle-gantt { padding: 0 14px; }
274
+ #toggle-gantt { padding: 14px 20px 6px; }
261
275
  .offline { color: var(--warn); font-size: 11px; margin-top: 8px; }
262
- .share-url { display: flex; gap: 6px; margin-top: 8px; } .share-url input { flex: 1; min-width: 0; background: var(--bg-2); color: var(--text); border: 1px solid var(--line); border-radius: 3px; padding: 3px 6px; font: inherit; font-size: 11px; }
263
- html[data-theme="paper"] .code { background: #1b1f24; color: #e6edf3; }
264
- html[data-theme="paper"] .swim, html[data-theme="paper"] .timeline {
265
- background: linear-gradient(#e6e8eb 1px, transparent 1px) 0 0 / 100% 18px,
266
- linear-gradient(90deg, #e6e8eb 1px, transparent 1px) 0 0 / 24px 100%, #f4f5f7;
267
- }
268
- html[data-theme="paper"] .pub.on { background: #e2e8f4; }
269
- html[data-theme="paper"] .run-item.on { background: #dbe7ff; }
270
- html[data-theme="paper"] .evt.on { background: #e8eef9; }
271
- html[data-theme="paper"] .pending { background: #fff7e6; border-color: #f0d9a8; }
272
- html[data-theme="paper"] .pending.issue { background: #fdecec; border-color: #f0b4b4; }
273
- html[data-theme="paper"] .pill.system { background: #e8eaed; color: #3c424a; }
274
- html[data-theme="paper"] .pill.user { background: #dbe7ff; color: #1e3a8a; }
275
- html[data-theme="paper"] .pill.context { background: #d9f0e3; color: #166534; }
276
- html[data-theme="paper"] .pill.assistant { background: #ece4ff; color: #4c1d95; }
277
- html[data-theme="paper"] .pill.tool { background: #ffedd5; color: #9a3412; }
278
- html[data-theme="paper"] .pill.skill { background: #cffafe; color: #155e75; }
279
- html[data-theme="paper"] .pill.issue { background: #fee2e2; color: #991b1b; }
280
- html[data-theme="paper"] .btn-ok { background: #d9f0e3; color: #166534; }
281
- html[data-theme="paper"] .btn-no { background: #fee2e2; color: #991b1b; }
282
- html[data-theme="paper"] .btn-alt { background: #dbe7ff; color: #1e3a8a; }
283
- html[data-theme="paper"] .toast { background: #fff; color: #1c1917; }
284
- html[data-theme="tty"] .pub.on, html[data-theme="tty"] .run-item.on { background: #313244; }
285
- html[data-theme="tty"] .evt.on { background: #313244; }
286
- html[data-theme="tty"] .pending { background: #181825; border-color: #f9e2af; }
287
- html[data-theme="tty"] .pending.issue { background: #181825; border-color: #f38ba8; }
288
- html[data-theme="tty"] .pill.system { background: #313244; color: #cdd6f4; }
289
- html[data-theme="tty"] .pill.user { background: #313244; color: #89b4fa; }
290
- html[data-theme="tty"] .pill.context { background: #313244; color: #a6e3a1; }
291
- html[data-theme="tty"] .pill.assistant { background: #313244; color: #cba6f7; }
292
- html[data-theme="tty"] .pill.tool { background: #313244; color: #fab387; }
293
- html[data-theme="tty"] .pill.skill { background: #313244; color: #89dceb; }
294
- html[data-theme="tty"] .pill.issue { background: #313244; color: #f38ba8; }
295
- html[data-theme="tty"] .btn-ok { background: #313244; color: #a6e3a1; }
296
- html[data-theme="tty"] .btn-no { background: #313244; color: #f38ba8; }
297
- html[data-theme="tty"] .btn-alt { background: #313244; color: #89b4fa; }
298
- html[data-theme="tty"] .code { background: #11111b; }
299
- html[data-theme="tty"] .code .kw { color: #cba6f7; }
300
- html[data-theme="tty"] .code .str { color: #a6e3a1; }
301
- html[data-theme="tty"] .bar.ok { background: #a6e3a1; }
302
- html[data-theme="tty"] .bar.spin { background: #89b4fa; }
303
- html[data-theme="tty"] .bar.fail { background: #f38ba8; }
304
- html[data-theme="tty"] .bar.wait { background: #f9e2af; }
305
- html[data-theme="tty"] .swim, html[data-theme="tty"] .timeline {
306
- background: linear-gradient(#313244 1px, transparent 1px) 0 0 / 100% 18px,
307
- linear-gradient(90deg, #313244 1px, transparent 1px) 0 0 / 24px 100%, #1e1e2e;
276
+ .share-url { display: flex; gap: 6px; margin-top: 8px; } .share-url input { flex: 1; min-width: 0; background: var(--bg-3); color: var(--text); border: 1px solid var(--line); border-radius: 6px; padding: 3px 6px; font: inherit; font-size: 11px; }
277
+ @media (max-width: 900px) {
278
+ .app { flex-direction: column; overflow: hidden; height: 100dvh; padding: 8px 8px 76px; }
279
+ .sidebar, .inspector, .stage { flex: 1; width: 100% !important; min-width: 0; min-height: 0; overflow: auto; }
280
+ html:not([data-mobile-pane="runs"]) .sidebar, html:not([data-mobile-pane="insp"]) .inspector, html[data-mobile-pane]:not([data-mobile-pane="stage"]) .stage { display: none; }
281
+ .splitbar, .splitbar.row { display: none; }
282
+ .mobile-tabs { display: grid; grid-template-columns: repeat(3, 1fr); position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 30; padding: 6px; gap: 6px; background: var(--bg-2); border: 1px solid var(--line); border-radius: 18px; box-shadow: var(--sheet); }
283
+ .mobile-tabs button { border: 0; background: none; color: var(--dim); padding: 8px 4px; border-radius: 12px; font-weight: 600; font-size: 12px; line-height: 1.1; display: grid; gap: 3px; justify-items: center; }
284
+ .mobile-tabs button b { font: 18px/1 var(--serif); }
285
+ .mobile-tabs button.on { background: var(--bg-sel); color: var(--text); }
286
+ .top, .toolbar { flex-wrap: wrap; }
287
+ .run-stats { margin-left: 0; width: 100%; flex-wrap: wrap; }
288
+ .search { width: 100%; margin: 6px 0 0; }
289
+ .search, .steer-input { font-size: 16px; }
290
+ .modal { inset: 8px; }
291
+ .swim { height: auto; flex: none; max-height: 44vh; background: repeating-linear-gradient(transparent 0 23px, var(--rule) 23px 24px), var(--bg-2); }
292
+ .swim .lane { grid-template-columns: 16px 56px 1fr; }
293
+ .swim .lane.child { grid-template-columns: 16px 44px 1fr; }
294
+ .swim .axis { grid-template-columns: 80px 1fr; }
295
+ .run-item { padding: 9px 8px; }
296
+ .agent-grid { min-width: 0; padding: 0 8px 8px; }
297
+ .agent-grid-head { display: none; }
298
+ .agent-grid-row { grid-template-columns: minmax(0, 1fr) auto auto auto; min-height: 40px; }
299
+ .agent-grid-row > :nth-child(2), .agent-grid-row > :nth-child(4), .agent-grid-row > :nth-child(6) { display: none; }
300
+ .agent-grid-row > :nth-child(3) { color: var(--dim); }
301
+ .evt { grid-template-columns: auto minmax(0, 1fr); gap: 4px 10px; padding: 10px 14px; }
302
+ .evt > .pill { grid-row: 1; grid-column: 1; justify-self: start; }
303
+ .evt .gutter { grid-row: 1; grid-column: 2; justify-self: end; }
304
+ .evt .preview { grid-column: 1 / -1; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
305
+ .log-row { grid-template-columns: 5.5rem minmax(0, 1fr); }
306
+ .logs { padding-left: 8px; }
307
+ .itabs { overflow-x: auto; scrollbar-width: none; gap: 16px; }
308
+ .itabs span { flex: none; white-space: nowrap; }
309
+ .ins-head { flex-wrap: wrap; row-gap: 4px; }
308
310
  }
309
311
  </style>
310
312
  </head>
@@ -336,6 +338,7 @@
336
338
  <div class="splitbar" id="split-right"></div>
337
339
  <aside class="inspector" id="insp"></aside>
338
340
  </div>
341
+ <nav class="mobile-tabs" id="mobile-tabs"><button type="button" data-mobile-pane="runs"><b>&#9776;</b>Runs</button><button type="button" data-mobile-pane="stage" class="on"><b>&#8766;</b>Trace</button><button type="button" data-mobile-pane="insp"><b>&#9998;</b>Details</button></nav>
339
342
  <div class="toast" id="toast"></div>
340
343
  <div class="modal-bg hidden" id="script-bg"></div>
341
344
  <div class="modal hidden" id="script-modal">
@@ -344,8 +347,9 @@
344
347
  </div>
345
348
  <script src="./marked.min.js"></script>
346
349
  <script src="./morphdom.min.js"></script>
350
+ <script src="./prism.min.js"></script>
347
351
  <script>
348
- const defaultRunLayout = () => ({ swimHeight: 220, ganttCollapsed: false, agentsCollapsed: false, logsCollapsed: false });
352
+ const defaultRunLayout = () => ({ swimHeight: 220, ganttCollapsed: false, agentsCollapsed: false, callsCollapsed: false, logsCollapsed: false });
349
353
  const maxSwimHeight = () => Math.max(72, window.innerHeight * 0.7);
350
354
  function loadRunLayout() {
351
355
  const fallback = defaultRunLayout();
@@ -358,6 +362,7 @@
358
362
  swimHeight: Number.isFinite(value.swimHeight) && value.swimHeight >= 72 ? value.swimHeight : fallback.swimHeight,
359
363
  ganttCollapsed: typeof value.ganttCollapsed === "boolean" ? value.ganttCollapsed : fallback.ganttCollapsed,
360
364
  agentsCollapsed: typeof value.agentsCollapsed === "boolean" ? value.agentsCollapsed : fallback.agentsCollapsed,
365
+ callsCollapsed: typeof value.callsCollapsed === "boolean" ? value.callsCollapsed : fallback.callsCollapsed,
361
366
  logsCollapsed: typeof value.logsCollapsed === "boolean" ? value.logsCollapsed : fallback.logsCollapsed,
362
367
  };
363
368
  } catch {
@@ -376,7 +381,7 @@
376
381
  }
377
382
  }
378
383
  function saveSidebarCollapsed() { try { localStorage.setItem("traj-sidebar-collapsed", JSON.stringify([...state.sidebarCollapsed])); } catch { /* Preferences are optional. */ } }
379
- const state = { publishers: [], currentPub: null, currentRun: null, currentTarget: null, currentAgent: null, pendingTarget: null, agentRange: null, agentRangeAgent: null, selectedEvent: null, timelineHighlight: null, eventsSig: null, toolPane: "summary", sysPane: "prompt", inspSig: null, socket: null, transcripts: {}, transcriptPending: {}, transcriptSources: {}, transcriptRefresh: {}, transcriptRevision: {}, transcriptStatus: {}, transcriptCacheOrder: [], actionTargets: new Map(), shareUrls: {}, runLayout: loadRunLayout(), sidebarCollapsed: loadSidebarCollapsed() };
384
+ const state = { publishers: [], currentPub: null, currentRun: null, currentTarget: null, currentAgent: null, pendingTarget: null, agentRange: null, agentRangeAgent: null, selectedEvent: null, timelineHighlight: null, eventsSig: null, toolPane: "summary", sysPane: "prompt", inspMode: "event", inspSig: null, socket: null, transcripts: {}, transcriptPending: {}, transcriptSources: {}, transcriptRefresh: {}, transcriptRevision: {}, transcriptStatus: {}, transcriptCacheOrder: [], actionTargets: new Map(), shareUrls: {}, runLayout: loadRunLayout(), sidebarCollapsed: loadSidebarCollapsed(), workflowArgumentsOpen: new Set(), callsOpen: new Set() };
380
385
  let hasAcceptedState = false;
381
386
  let socketGeneration = 0;
382
387
  let socketReconnect;
@@ -399,7 +404,8 @@
399
404
  const transcriptKey = (publisherId, targetId, agentId) => agentId === undefined ? `${publisherId}\tsubagent\t${targetId}` : `${publisherId}\t${targetId}\t${agentId}`;
400
405
  const sameValue = (left, right) => { try { return JSON.stringify(left) === JSON.stringify(right); } catch { return false; } };
401
406
  function shareArray(previous, incoming, key) { if (!Array.isArray(incoming)) return incoming; const old = Array.isArray(previous) ? previous : []; const byId = new Map(old.map((item) => [item?.[key], item])); const shared = incoming.map((item) => { const prior = byId.get(item?.[key]); return prior !== undefined && sameValue(prior, item) ? prior : item; }); return old.length === shared.length && shared.every((item, index) => item === old[index]) ? old : shared; }
402
- function shareTranscriptMap(previous, incoming) { if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) return incoming; const old = previous && typeof previous === "object" && !Array.isArray(previous) ? previous : {}; const shared = { ...incoming }; for (const [key, entries] of Object.entries(incoming)) if (Array.isArray(old[key]) && Array.isArray(entries) && sameValue(old[key], entries)) shared[key] = old[key]; const keys = Object.keys(shared); return keys.length === Object.keys(old).length && keys.every((key) => shared[key] === old[key]) ? old : shared; }
407
+ // The server omits timing it already delivered at this revision, so the cached entries are carried forward.
408
+ function shareTranscriptMap(previous, incoming) { if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) return incoming; const old = previous && typeof previous === "object" && !Array.isArray(previous) ? previous : {}; const shared = { ...incoming }; for (const [key, entries] of Object.entries(incoming)) { if (Array.isArray(old[key]) && Array.isArray(entries) && sameValue(old[key], entries)) { shared[key] = old[key]; continue; } if (!entries || typeof entries !== "object" || Array.isArray(entries) || entries.timing !== undefined) continue; const prior = old[key]; if (!prior || typeof prior !== "object" || Array.isArray(prior)) { shared[key] = { ...entries, timing: [] }; continue; } shared[key] = prior.revision === entries.revision && prior.status === entries.status ? prior : { ...entries, timing: prior.timing || [] }; } const keys = Object.keys(shared); return keys.length === Object.keys(old).length && keys.every((key) => shared[key] === old[key]) ? old : shared; }
403
409
  function shareRunRecord(previous, incoming) { const run = incoming?.run; const oldRun = previous?.run; const agents = shareArray(oldRun?.agents, run?.agents, "id"); const sharedRun = run && typeof run === "object" ? { ...run, agents } : run; const transcripts = shareTranscriptMap(previous?.transcripts, incoming?.transcripts); const shared = { ...incoming, run: sharedRun, transcripts }; return previous !== undefined && sameValue(previous, shared) ? previous : shared; }
404
410
  function shareSubagent(previous, incoming) { const transcript = Array.isArray(incoming?.transcript) && Array.isArray(previous?.transcript) && sameValue(previous.transcript, incoming.transcript) ? previous.transcript : incoming?.transcript; const shared = { ...incoming, transcript }; return previous !== undefined && sameValue(previous, shared) ? previous : shared; }
405
411
  function sharePublisher(previous, incoming) { const sharedRuns = Array.isArray(incoming?.runs) ? incoming.runs.map((record) => shareRunRecord(previous?.runs?.find((candidate) => candidate?.run?.id === record?.run?.id), record)) : incoming?.runs; const sharedSubagents = Array.isArray(incoming?.subagents) ? incoming.subagents.map((subagent) => { const prior = previous?.subagents?.find((candidate) => candidate?.id === subagent?.id); return shareSubagent(prior, subagent); }) : incoming?.subagents; const shared = { ...incoming, runs: sharedRuns, subagents: sharedSubagents }; return previous !== undefined && sameValue(previous, shared) ? previous : shared; }
@@ -431,12 +437,18 @@
431
437
  const runTokens = (record) => record.run.usage?.tokens ?? record.run.agents.reduce((sum, agent) => sum + accounting(agent).input + accounting(agent).output, 0);
432
438
  const startOf = (record) => { const times = record.run.agents.map((agent) => agent.startedAt).filter((time) => Number.isFinite(time)); const created = Date.parse(record.createdAt || ""); return new Date(Math.min(...times, Number.isFinite(created) ? created : Date.now())); };
433
439
  const endOf = (record) => { const start = startOf(record).getTime(); const ends = record.run.agents.map((agent) => agent.startedAt && agent.durationMs ? agent.startedAt + agent.durationMs : 0); return Math.max(start + 1, ...ends, record.run.state === "running" || record.run.state === "paused" || record.run.state === "awaiting_input" ? Date.now() : start + (record.run.usage?.durationMs || 0)); };
440
+ // Newest completion first; runs still going have no end yet, so they stay on top, newest start first.
441
+ const isActiveRun = (record) => ["running", "paused", "awaiting_input"].includes(record.run.state);
442
+ const runsByCompletion = (publisher) => { if (!Array.isArray(publisher.runs)) return publisher; const keyed = publisher.runs.map((record) => ({ record, end: isActiveRun(record) ? Infinity : endOf(record), start: startOf(record).getTime() })); keyed.sort((left, right) => right.end - left.end || right.start - left.start); return { ...publisher, runs: keyed.map(({ record }) => record) }; };
434
443
  const attention = (record) => (record.awaiting || []).length + record.run.agents.filter((agent) => agent.state === "failed").length;
435
444
  const duration = (record) => Math.max(0, endOf(record) - startOf(record).getTime());
436
445
  const age = (stamp) => { const minutes = Math.floor((Date.now() - stamp) / 60000); return minutes < 60 ? `${minutes}m ago` : `${Math.floor(minutes / 60)}h ago`; };
437
- function tickClocks() { const now = Date.now(); document.querySelectorAll("[data-clock-kind]").forEach((element) => { const start = Number(element.dataset.clockStart); if (!Number.isFinite(start)) return; const end = element.dataset.clockEnd === "live" ? now : Number(element.dataset.clockEnd); const value = element.dataset.clockKind === "age" ? age(start) : fmtRuntime((Number.isFinite(end) ? end : now) - start); const prefix = element.dataset.clockPrefix; const text = prefix ? `${prefix} · ${value}` : value; if (element.firstChild) element.firstChild.nodeValue = text; else element.append(text); if (element.dataset.clockTitlePrefix) element.title = `${element.dataset.clockTitlePrefix} · ${value}`; }); const swim = $("swim"); if (swim?.dataset.timelineRunning !== "true") return; const start = Number(swim.dataset.timelineStart); if (!Number.isFinite(start)) return; const span = Math.max(1, now - start); swim.dataset.timelineSpan = String(span); swim.querySelectorAll("[data-clock-bar-start]").forEach((bar) => { const barStart = Number(bar.dataset.clockBarStart); const minimum = Number(bar.dataset.clockBarMin); if (Number.isFinite(barStart)) bar.style.width = `${Math.max(Number.isFinite(minimum) ? minimum : 0, (now - barStart) / span * 100)}%`; }); const axisEnd = swim.querySelector('[data-clock-axis="end"]'); if (axisEnd?.firstChild) axisEnd.firstChild.nodeValue = fmtClock(now); }
438
- const highlight = (source) => esc(source).replace(/(&quot;.*?&quot;)/g, '<span class="str">$1</span>').replace(/\b(await|return|const|async|agent|parallel|pipeline|phase|checkpoint)\b/g, '<span class="kw">$1</span>');
439
- const numbered = (source) => String(source || "").split("\n").map((line, index) => `<div class="ln"><span class="no">${index + 1}</span><span>${highlight(line)}</span></div>`).join("");
446
+ function tickClocks() { const now = Date.now(); document.querySelectorAll("[data-clock-kind]").forEach((element) => { const start = Number(element.dataset.clockStart); if (!Number.isFinite(start)) return; const end = element.dataset.clockEnd === "live" ? now : Number(element.dataset.clockEnd); const value = element.dataset.clockKind === "age" ? age(start) : fmtRuntime((Number.isFinite(end) ? end : now) - start); const prefix = element.dataset.clockPrefix; const text = prefix ? `${prefix} · ${value}` : value; if (element.firstChild) element.firstChild.nodeValue = text; else element.append(text); if (element.dataset.clockTitlePrefix) element.title = `${element.dataset.clockTitlePrefix} · ${value}`; }); const swim = $("swim"); if (swim?.dataset.timelineRunning !== "true") return; const start = Number(swim.dataset.timelineStart); if (!Number.isFinite(start)) return; const span = quantizeSpan(Math.max(1, now - start)); swim.dataset.timelineSpan = String(span); swim.querySelectorAll("[data-clock-bar-start]").forEach((bar) => { const barStart = Number(bar.dataset.clockBarStart); const minimum = Number(bar.dataset.clockBarMin); if (Number.isFinite(barStart)) bar.style.width = `${Math.max(Number.isFinite(minimum) ? minimum : 0, (now - barStart) / span * 100)}%`; }); setAxisLabel(swim.querySelector('[data-clock-axis="mid"]'), `+${fmtRuntime(span / 2)}`); setAxisLabel(swim.querySelector('[data-clock-axis="end"]'), fmtClock(now)); }
447
+ function setAxisLabel(element, text) { if (!element) return; if (element.firstChild) element.firstChild.nodeValue = text; else element.append(text); }
448
+ // Prism tokenises whole sources, so multi-line strings and comments keep their colour; unknown languages stay plain text.
449
+ const highlightCode = (source, language) => { const grammar = globalThis.Prism?.languages[language]; return grammar ? Prism.highlight(String(source ?? ""), grammar, language) : esc(source); };
450
+ // Token spans can cross lines, so each numbered line reopens the spans still open from the line before.
451
+ const numbered = (source) => { const open = []; return highlightCode(source, "javascript").split("\n").map((line, index) => { const prefix = open.join(""); for (const [tag] of line.matchAll(/<span[^>]*>|<\/span>/g)) { if (tag === "</span>") open.pop(); else open.push(tag); } return `<div class="ln"><span class="no">${index + 1}</span><span>${prefix}${line}${"</span>".repeat(open.length)}</span></div>`; }).join(""); };
440
452
  const contentText = (value) => Array.isArray(value) ? value.map((part) => typeof part === "string" ? part : part?.text || part?.thinking || part?.name || "").join(" ") : typeof value === "string" ? value : value ? json(value) : "";
441
453
  const assistantBodyText = (value) => Array.isArray(value) ? value.filter((part) => typeof part === "string" || part?.type === "text" || part?.type === "thinking").map((part) => typeof part === "string" ? part : part.type === "text" ? part.text || "" : part.thinking || "").join(" ") : contentText(value);
442
454
  const toolCallsOf = (entry) => { const content = entry?.message?.content; return Array.isArray(content) ? content.filter((part) => part && part.type === "toolCall") : []; };
@@ -464,6 +476,9 @@
464
476
  const safeClass = name !== "class" || (tag === "code" && /^(?:language|lang)-[a-z0-9_-]+(?:\s+(?:language|lang)-[a-z0-9_-]+)*$/i.test(value));
465
477
  if (name.startsWith("on") || !markdownAllowedAttributes.has(name) || name === "style" || !safeClass || ((name === "href" || name === "src") && /^(?:javascript|data):/i.test(value.replace(/[\u0000-\u0020]/g, "")))) element.removeAttribute(attribute.name);
466
478
  }
479
+ // Fenced code keeps its language class through sanitising; Prism then rebuilds the block from its text.
480
+ const language = tag === "code" && globalThis.Prism ? /(?:language|lang)-([a-z0-9_-]+)/i.exec(element.getAttribute("class") || "")?.[1].toLowerCase() : undefined;
481
+ if (language && Prism.languages[language]) element.innerHTML = highlightCode(element.textContent, language);
467
482
  }
468
483
  return parsed.body.innerHTML;
469
484
  }
@@ -479,7 +494,7 @@
479
494
  const transcriptEmptyMessage = (key) => ({ empty: "No transcript events recorded.", missing: "Transcript not found.", disconnected: "Publisher disconnected.", failed: "Transcript unavailable.", oversized: "Transcript is too large." }[state.transcriptStatus[key]] || "Transcript unavailable.");
480
495
  const toolArgsOf = (entry, entries) => { const id = toolCallIdOf(entry); if (id) { for (const candidate of entries) { const content = candidate.message?.content; if (!Array.isArray(content)) continue; const call = content.find((part) => part && part.type === "toolCall" && part.id === id); if (call && call.arguments !== undefined) return call.arguments; } } return entry.message?.arguments !== undefined ? entry.message.arguments : entry.arguments !== undefined ? entry.arguments : {}; };
481
496
  const toolDescriptionOf = (agent, name) => (agent.toolDefinitions || []).find((tool) => tool.name === name)?.description || "";
482
- const sessionResources = (agent) => { const snapshot = selected()?.record.snapshot; const resources = agent?.attemptDetails?.at(-1)?.setup?.resourceSelectors; return { skillFilters: resources?.selectors?.skills?.length ? resources.selectors.skills : snapshot?.settings?.skills || [], skillResolved: resources?.skills || [], extensionFilters: resources?.selectors?.extensions?.length ? resources.selectors.extensions : snapshot?.settings?.extensions || [], extensionResolved: resources?.extensions || [] }; };
497
+ const sessionResources = (agent) => { const snapshot = selected()?.record.snapshot; const resources = agent?.attemptDetails?.at(-1)?.setup?.resourceSelectors; return { skillFilters: resources?.selectors?.skills?.length ? resources.selectors.skills : agent?.skills?.length ? agent.skills : snapshot?.settings?.skills || [], skillResolved: resources?.skills || [], extensionFilters: resources?.selectors?.extensions?.length ? resources.selectors.extensions : agent?.extensions?.length ? agent.extensions : snapshot?.settings?.extensions || [], extensionResolved: resources?.extensions || [] }; };
483
498
  const resourceTitle = (value) => { const parts = String(value).replaceAll("\\", "/").split("/").filter(Boolean); const file = parts.at(-1) || String(value); if (/^(skill\.md|index\.(ts|js|mjs))$/i.test(file)) return parts.at(-2) || file; return file.replace(/\.(ts|js|mjs)$/i, ""); };
484
499
  const nameList = (items) => items.length ? items.map((item) => `<div class="tool-item"><b>${esc(item)}</b></div>`).join("") : '<div class="hint">None recorded.</div>';
485
500
  const resourceList = (items) => items.length ? items.map((item) => { const title = resourceTitle(item); return `<div class="tool-item"><b>${esc(title)}</b>${title !== item ? `<span>${esc(item)}</span>` : ""}</div>`; }).join("") : '<div class="hint">None resolved.</div>';
@@ -576,21 +591,15 @@
576
591
  const eventSearchText = (entry, entries = []) => { const toolCalls = toolCallsOf(entry); if (toolCalls.length) return toolCalls.map((call) => `${call.name} ${JSON.stringify(toolArgsOf(call, entries))}`).join(" "); return eventKind(entry) === "tool" ? `${toolNameOf(entry)} ${JSON.stringify(toolArgsOf(entry, entries))}` : ""; };
577
592
  const renderAssistantPreview = (preview) => { const text = preview.text ? `<span>${esc(preview.text)}</span>` : ""; const separator = text && preview.names.length ? '<span class="preview-sep"> | </span>' : ""; const names = preview.names.slice(0, 3).map((name) => `<span class="pill tool preview-tool">${esc(name)}</span>`).join(""); const overflow = preview.overflow ? `<span class="preview-overflow">+${preview.overflow} others</span>` : ""; return `${text}${separator}${names}${overflow}`; };
578
593
  const eventLabel = (kind) => kind === "toolResult" ? "TOOL" : kind === "context" ? "RESULT" : kind === "issue" ? "FAILURE" : String(kind || "EVENT").toUpperCase();
579
- function renderThemeButtons() {
580
- const enabled = state.publishers.some((publisher) => publisher.themes === true);
581
- const themes = enabled ? ["harness", "tty", "paper"] : [];
582
- if (enabled && !["harness", "tty", "paper"].includes(document.documentElement.dataset.theme || "")) {
583
- try { document.documentElement.dataset.theme = localStorage.getItem("traj-theme") || "tty"; } catch { document.documentElement.dataset.theme = "tty"; }
584
- }
585
- patch($("theme-buttons"), themes.map((theme) => `<button class="tab theme" data-theme="${theme}">${theme.toUpperCase()}</button>`).join(""));
586
- $("theme-buttons").classList.toggle("hidden", !enabled);
587
- $("theme-buttons").querySelectorAll("[data-theme]").forEach((button) => button.addEventListener("click", () => {
588
- document.documentElement.dataset.theme = button.dataset.theme;
589
- try { localStorage.setItem("traj-theme", button.dataset.theme); } catch { /* Preferences are optional. */ }
590
- renderThemeButtons();
591
- }));
592
- const current = document.documentElement.dataset.theme || "tty";
593
- $("theme-buttons").querySelectorAll("[data-theme]").forEach((button) => button.classList.toggle("on", button.dataset.theme === current));
594
+ // Without a pinned choice the page follows the system scheme; the toggle cycles system, light, and dark.
595
+ const themeModes = { auto: { next: "light", icon: "\u25D0", label: "Theme: system" }, light: { next: "dark", icon: "\u2600", label: "Theme: light" }, dark: { next: "auto", icon: "\u263E", label: "Theme: dark" } };
596
+ const currentTheme = () => ["light", "dark"].includes(document.documentElement.dataset.theme) ? document.documentElement.dataset.theme : "auto";
597
+ const themeToggleHtml = () => { const mode = themeModes[currentTheme()]; return `<button type="button" class="theme-toggle" data-theme-toggle title="${mode.label}" aria-label="${mode.label}">${mode.icon}</button>`; };
598
+ function toggleTheme() {
599
+ const next = themeModes[currentTheme()].next;
600
+ if (next === "auto") delete document.documentElement.dataset.theme; else document.documentElement.dataset.theme = next;
601
+ try { localStorage.setItem("traj-theme", next); } catch { /* Preferences are optional. */ }
602
+ renderSidebar();
594
603
  }
595
604
  const subagentAccounting = (subagent) => subagent.progress?.accounting || subagent.attempt?.accounting || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
596
605
  const subagentCost = (subagent) => subagentAccounting(subagent).cost;
@@ -601,6 +610,15 @@
601
610
  const subagentAttention = (subagent) => subagent.state === "failed" ? 1 : 0;
602
611
  const subagentModel = (subagent) => subagent.model ? `${subagent.model.provider || "?"}/${subagent.model.model || "?"}` : "—";
603
612
  const subagentLabel = (subagent) => subagent.label || subagent.id.slice(0, 8);
613
+ // A session shows every active entry and at least three in total; the rest wait behind a "more" toggle, and the selected entry always stays visible.
614
+ // The toggle reuses the persisted sidebar section set, where membership of this key means expanded.
615
+ function sidebarWindow(items, key, active, selected) {
616
+ const limit = Math.max(3, items.filter(active).length);
617
+ if (items.length <= limit) return { visible: items, more: "" };
618
+ const expanded = state.sidebarCollapsed.has(key);
619
+ const visible = expanded ? items : items.filter((item, index) => index < limit || selected(item));
620
+ return { visible, more: `<button type="button" class="more-items section-toggle" data-sidebar-section="${esc(key)}" aria-expanded="${expanded}">${expanded ? "show less" : `+ ${items.length - visible.length} more`}</button>` };
621
+ }
604
622
  function renderSidebar() {
605
623
  const groups = sidebarGroups(livePublishers());
606
624
  const html = groups.map((group) => {
@@ -608,38 +626,89 @@
608
626
  const collapsed = state.sidebarCollapsed.has(group.key);
609
627
  const sessions = group.publishers.map((publisher) => {
610
628
  const publisherAttention = (publisher.runs || []).reduce((total, record) => total + attention(record), 0) + (publisher.subagents || []).reduce((total, subagent) => total + subagentAttention(subagent), 0);
611
- const runs = (publisher.runs || []).map((record) => {
629
+ const selectedKey = targetKey(currentTarget() || {});
630
+ const runWindow = sidebarWindow(publisher.runs || [], `more:runs:${publisher.id}`, isActiveRun, (record) => selectedKey === `run:${runKey(publisher, record)}`);
631
+ const runs = runWindow.visible.map((record) => {
612
632
  const ref = runKey(publisher, record);
613
633
  const pending = record.awaiting?.length || attention(record);
614
634
  return `<button class="run-item${targetKey(currentTarget() || {}) === `run:${ref}` ? " on" : ""}" data-run="${esc(ref)}" data-publisher="${esc(publisher.id)}">${glyph(runState(record))}<span class="n">${esc(record.run.workflowName)}</span>${pending ? `<span class="await">${pending} attn</span>` : ""}<span class="tel" data-clock-kind="${record.run.state === "running" ? "runtime" : "age"}" data-clock-start="${startOf(record).getTime()}" data-clock-prefix="${esc(fmtCost(runCost(record)))}" data-clock-title-prefix="${esc(`${runState(record)} · ${fmtCost(runCost(record))}`)}" title="${esc(`${runState(record)} · ${fmtCost(runCost(record))} · ${fmtRuntime(duration(record))}`)}">${fmtCost(runCost(record))} · ${record.run.state === "running" ? fmtRuntime(Date.now() - startOf(record).getTime()) : age(startOf(record).getTime())}</span></button>`;
615
- }).join("");
635
+ }).join("") + runWindow.more;
616
636
  const subagentSectionKey = `subagents:${publisher.id}`;
617
637
  const subagentsCollapsed = state.sidebarCollapsed.has(subagentSectionKey);
618
- const subagents = (publisher.subagents || []).map((subagent) => {
638
+ const subagentWindow = sidebarWindow(publisher.subagents || [], `more:subagents:${publisher.id}`, (subagent) => subagent.state === "running", (subagent) => selectedKey === targetKey({ kind: "subagent", publisherId: publisher.id, id: subagent.id }));
639
+ const subagents = subagentWindow.visible.map((subagent) => {
619
640
  const ref = subagentKey(publisher, subagent);
620
641
  const target = { kind: "subagent", publisherId: publisher.id, id: subagent.id };
621
642
  const pending = subagentAttention(subagent);
622
643
  const telemetry = `${subagent.state} · ${fmtCost(subagentCost(subagent))} · ${fmtRuntime(subagentRuntime(subagent))}`;
623
644
  return `<button class="run-item subagent${targetKey(currentTarget() || {}) === targetKey(target) ? " on" : ""}" data-subagent="${esc(ref)}" data-publisher="${esc(publisher.id)}">${glyph(subagent.state)}<span class="n" title="${esc(subagent.id)}">${esc(subagentLabel(subagent))}</span>${pending ? `<span class="await">${pending} attn</span>` : ""}<span class="tel" data-clock-kind="${subagent.state === "running" ? "runtime" : "age"}" data-clock-start="${subagentStartOf(subagent)}" data-clock-prefix="${esc(fmtCost(subagentCost(subagent)))}" data-clock-title-prefix="${esc(`${subagent.state} · ${fmtCost(subagentCost(subagent))}`)}" title="${esc(telemetry)}">${fmtCost(subagentCost(subagent))} · ${subagent.state === "running" ? fmtRuntime(subagentRuntime(subagent)) : age(subagentStartOf(subagent))}</span></button>`;
624
- }).join("");
645
+ }).join("") + subagentWindow.more;
625
646
  return `<div class="pub${publisher.id === state.currentPub ? " on" : ""}"><div class="row"><span class="dot live"></span><span class="name">${esc(publisher.title || publisher.id)}</span>${publisherAttention ? `<span class="attn">${publisherAttention}</span>` : ""}</div><div class="children">${runs || '<div class="hint">no runs</div>'}<button type="button" class="subagents-head section-toggle" data-sidebar-section="${esc(subagentSectionKey)}" aria-expanded="${!subagentsCollapsed}">${subagentsCollapsed ? "▸" : "▾"} SUBAGENTS <span class="count">${(publisher.subagents || []).length}</span></button>${subagentsCollapsed ? "" : subagents || '<div class="hint">no subagents</div>'}</div></div>`;
626
647
  }).join("");
627
648
  return `<div class="group"><button type="button" class="folder" data-sidebar-group="${esc(group.key)}" title="${esc(group.key)}" aria-expanded="${!collapsed}"><span class="marker">${collapsed ? "▸" : "▾"}</span><span class="name">${esc(group.label)}</span><span class="count">${group.publishers.length}</span>${groupAttention ? `<span class="attn">${groupAttention}</span>` : ""}</button>${collapsed ? "" : `<div class="group-children">${sessions}</div>`}</div>`;
628
649
  }).join("");
629
- patch($("sidebar"), `<div class="brand">TRAJECTORY <span id="theme-buttons"></span></div>${html}`);
630
- renderThemeButtons();
650
+ patch($("sidebar"), `<div class="brand"><span class="brand-name">Trajectory</span>${themeToggleHtml()}</div>${html}`);
651
+ }
652
+ // A live timeline grows in ~10% steps so bar geometry stays byte-identical between frames instead of shifting every second.
653
+ const quantizeSpan = (ms) => Math.max(1, Math.round(Math.pow(1.1, Math.ceil(Math.log(Math.max(1, ms)) / Math.log(1.1)))));
654
+ const timelineSpan = (record) => { const span = Math.max(1, endOf(record) - startOf(record).getTime()); return record.run.state === "running" ? quantizeSpan(span) : span; };
655
+ const barStyle = (offsetMs, lengthMs, minWidth, span) => `left:${Math.max(0, offsetMs / span * 100)}%;width:${Math.max(minWidth, lengthMs / span * 100)}%`;
656
+ // Tool calls less than a pixel apart on the track overlap on screen, so they ship as one bar instead of thousands.
657
+ const TOOL_BAR_MERGE_RATIO = 0.001;
658
+ function mergedToolBars(timings, span) {
659
+ const gap = span * TOOL_BAR_MERGE_RATIO;
660
+ const groups = [];
661
+ for (const { name, timing } of [...timings].sort((left, right) => left.timing.startedAt - right.timing.startedAt)) {
662
+ const last = groups[groups.length - 1];
663
+ if (last && timing.startedAt - last.end <= gap) {
664
+ last.end = Math.max(last.end, timing.completedAt);
665
+ last.count += 1;
666
+ last.error = last.error || timing.isError === true;
667
+ if (last.names.length < 4 && !last.names.includes(name)) last.names.push(name);
668
+ continue;
669
+ }
670
+ groups.push({ start: timing.startedAt, end: timing.completedAt, count: 1, error: timing.isError === true, names: [name], timing });
671
+ }
672
+ return groups;
631
673
  }
632
- function renderGantt(record, timingsByAgent) {
633
- const agents = record.run.agents; const start = startOf(record).getTime(), end = endOf(record), span = Math.max(1, end - start); const lanes = []; let previous = "";
674
+ const toolBarTitle = (group) => group.count === 1 ? `${group.names[0]} \u00b7 ${formatToolTiming(group.timing)}` : `${group.count} tool calls \u00b7 ${group.names.join(", ")}${group.count > group.names.length ? ", …" : ""} \u00b7 ${fmtRuntime(group.end - group.start)}`;
675
+ function renderGantt(record, timingsByAgent) { return ganttLanes(record, timingsByAgent).map(({ html }) => html).join(""); }
676
+ function ganttLanes(record, timingsByAgent) {
677
+ const agents = record.run.agents; const start = startOf(record).getTime(); const live = record.run.state === "running"; const span = timelineSpan(record); const lanes = []; let previous = "";
634
678
  for (const agent of agents) {
635
679
  const crumb = agent.parentBreadcrumb || "";
636
- if (crumb && crumb !== previous) { const group = agents.filter((candidate) => (candidate.parentBreadcrumb || "") === crumb); const left = Math.min(...group.map((candidate) => ((candidate.startedAt || start) - start) / span * 100)); const right = Math.max(...group.map((candidate) => ((candidate.startedAt || start) + (candidate.durationMs || 0) - start) / span * 100)); lanes.push(`<div class="lane crumb"><span></span><div class="lab">${esc(crumb)}</div><div class="track"><i class="bar crumb" style="left:${Math.max(0, left)}%;width:${Math.max(1, right - left)}%"></i></div></div>`); }
680
+ if (crumb && crumb !== previous) { const group = agents.filter((candidate) => (candidate.parentBreadcrumb || "") === crumb); const left = Math.min(...group.map((candidate) => (candidate.startedAt || start) - start)); const right = Math.max(...group.map((candidate) => (candidate.startedAt || start) + (candidate.durationMs || 0) - start)); lanes.push({ key: `crumb:${crumb}`, html: `<div class="lane crumb"><span></span><div class="lab">${esc(crumb)}</div><div class="track"><i class="bar crumb" style="${barStyle(Math.max(0, left), Math.max(0, right - left), 1, span)}"></i></div></div>` }); }
637
681
  previous = crumb;
638
- const timings = timingsByAgent.get(agent.id) || []; const left = agent.startedAt ? (agent.startedAt - start) / span * 100 : 0; const width = agent.durationMs ? agent.durationMs / span * 100 : agent.state === "running" ? Math.max(2, (Date.now() - (agent.startedAt || start)) / span * 100) : 0;
639
- const toolBars = timings.map(({ name, timing }) => { const toolLeft = Math.max(0, (timing.startedAt - start) / span * 100); const toolWidth = Math.max(0.5, (timing.completedAt - timing.startedAt) / span * 100); return `<i class="bar tool ${timing.isError ? "fail" : "ok"}" style="left:${toolLeft}%;width:${toolWidth}%" title="${esc(`${name} · ${formatToolTiming(timing)}`)}"></i>`; }).join("");
640
- lanes.push(`<div class="lane${agent.parentId ? " child" : ""}" data-agent="${esc(agent.id)}">${glyph(agent.state)}<div class="lab">${esc(agent.label || agent.name)}</div><div class="track"><i class="bar ${stateClass(agent.state)}" style="left:${Math.max(0, left)}%;width:${Math.max(0, width)}%"${agent.state === "running" ? ` data-clock-bar-start="${agent.startedAt || start}" data-clock-bar-min="2"` : ""}></i>${toolBars}</div></div>`);
682
+ const timings = timingsByAgent.get(agent.id) || []; const left = agent.startedAt ? agent.startedAt - start : 0; const running = agent.state === "running";
683
+ const toolBars = mergedToolBars(timings, span).map((group) => `<i class="bar tool ${group.error ? "fail" : "ok"}" style="${barStyle(group.start - start, group.end - group.start, 0.5, span)}" title="${esc(toolBarTitle(group))}"></i>`).join("");
684
+ // A running agent's width is time-dependent, so tickClocks owns it and the markup stays stable between frames.
685
+ const style = running ? `left:${Math.max(0, left / span * 100)}%;width:0%` : barStyle(Math.max(0, left), agent.durationMs || 0, 0, span);
686
+ lanes.push({ key: `agent:${agent.id}`, html: `<div class="lane${agent.parentId ? " child" : ""}" data-agent="${esc(agent.id)}">${glyph(agent.state)}<div class="lab">${esc(agent.label || agent.name)}</div><div class="track"><i class="bar ${stateClass(agent.state)}" style="${style}"${running ? ` data-clock-bar-start="${agent.startedAt || start}" data-clock-bar-min="2"` : ""}></i>${toolBars}</div></div>` });
641
687
  }
642
- return `<div class="axis"><div></div><div class="ticks"><span>${fmtClock(start)}</span><span>+${fmtRuntime(span / 2)}</span><span data-clock-axis="end">${record.run.state === "running" ? fmtClock(Date.now()) : "end"}</span></div></div>${lanes.join("")}`;
688
+ const calls = scriptCalls(record);
689
+ if (calls.length) lanes.push({ key: "calls", html: `<div class="lane crumb"><span></span><div class="lab">script</div><div class="track">${calls.map((call) => { const status = callState(record, call); const running = status === "running"; const title = `${callLabel(call)} · ${status}${running ? "" : ` · ${callDuration(call)}`}`; return `<i class="bar tool ${stateClass(status)}" style="${running ? `left:${Math.max(0, (call.startedAt - start) / span * 100)}%;width:0.5%` : barStyle(call.startedAt - start, (call.finishedAt ?? call.startedAt) - call.startedAt, 0.5, span)}"${running ? ` data-clock-bar-start="${call.startedAt}" data-clock-bar-min="0.5"` : ""} title="${esc(title)}"></i>`; }).join("")}</div></div>` });
690
+ return [{ key: "axis", html: `<div class="axis"><div></div><div class="ticks"><span>${fmtClock(start)}</span><span data-clock-axis="mid">${live ? "" : `+${fmtRuntime(span / 2)}`}</span><span data-clock-axis="end">${live ? "" : "end"}</span></div></div>` }, ...lanes];
691
+ }
692
+ // Only the lanes whose markup changed are re-parsed; the quantised span keeps the others byte-identical between frames.
693
+ let ganttOrder = "";
694
+ const ganttLaneHtml = new Map();
695
+ function patchGantt(record, timingsByAgent) {
696
+ const root = $("swim-content");
697
+ const lanes = ganttLanes(record, timingsByAgent);
698
+ const order = `${state.currentRun}\n${lanes.map(({ key }) => key).join("\n")}`;
699
+ if (order !== ganttOrder || root.children.length !== lanes.length) {
700
+ ganttOrder = order;
701
+ ganttLaneHtml.clear();
702
+ patch(root, lanes.map(({ html }) => html).join(""));
703
+ for (const { key, html } of lanes) ganttLaneHtml.set(key, html);
704
+ return;
705
+ }
706
+ lanes.forEach(({ key, html }, index) => {
707
+ if (ganttLaneHtml.get(key) === html) return;
708
+ ganttLaneHtml.set(key, html);
709
+ const child = root.children[index];
710
+ if (child) child.outerHTML = html;
711
+ });
643
712
  }
644
713
  function renderSubagentGantt(publisher) {
645
714
  const subagents = publisher.subagents || []; if (!subagents.length) return '<div class="hint">No subagents recorded.</div>';
@@ -668,27 +737,70 @@
668
737
  return `<div class="agent-grid"><div class="agent-grid-head"><span>AGENT</span><span>MODEL</span><span>ROLE</span><span>TOOL TIMING</span><span>DURATION</span><span>TOKENS</span><span>COST</span></div>${body}</div>`;
669
738
  }
670
739
  function renderLogs(record) { const events = record.run.events || []; const logs = events.filter((event) => event.type === "log"); const omitted = events.find((event) => event.type === "trajectory:truncated"); if (!logs.length && !omitted) return ""; const collapsed = state.runLayout.logsCollapsed; return `<section class="logs"><button type="button" class="sec section-toggle" data-toggle="logs" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} LOGS</button>${collapsed ? "" : `<div class="logs-list">${logs.map((event) => `<div class="log-row"><span class="log-time">${esc(fmtClock(event.timestamp))}</span><span class="log-message">${esc(event.message)}</span></div>`).join("")}${omitted ? `<div class="log-row"><span class="log-time">—</span><span class="log-message">${esc(omitted.omitted)} events omitted from live state</span></div>` : ""}</div>`}</section>`; }
740
+ const scriptCalls = (record) => (record.run.scriptCalls || []).filter((call) => call?.type !== "trajectory:truncated");
741
+ // The journal proves completion even if a crash prevented the finish metadata write.
742
+ const callState = (record, call) => call.output?.status === "completed" || call.output?.status === "available" || call.output?.status === "truncated" && call.output.kind === "result" ? "completed" : call.error ? (call.error.code === "CANCELLED" ? "stopped" : "failed") : call.finishedAt !== undefined ? "completed" : record.run.state === "running" || record.run.state === "pausing" ? "running" : "interrupted";
743
+ const callDuration = (call) => { if (call.finishedAt === undefined) return "—"; const ms = Math.max(0, call.finishedAt - call.startedAt); return ms < 1000 ? `${ms}ms` : fmtRuntime(ms); };
744
+ const callLabel = (call) => call.kind === "shell" ? "shell" : `tools.${call.name}`;
745
+ const callPreview = (call) => call.kind === "tool" ? call.error?.code || "" : typeof call.input === "string" ? call.input : JSON.stringify(call.input);
746
+ function renderCalls(record) {
747
+ const calls = scriptCalls(record); const omitted = (record.run.scriptCalls || []).find((call) => call?.type === "trajectory:truncated"); if (!calls.length && !omitted) return ""; const collapsed = state.runLayout.callsCollapsed;
748
+ const rows = calls.map((call) => { const key = `${state.currentRun}\t${call.path}`; const status = callState(record, call); const duration = status === "running" ? `<span data-clock-kind="runtime" data-clock-start="${call.startedAt}" data-clock-end="live"></span>` : call.finishedAt === undefined ? "—" : esc(callDuration(call)); const input = call.kind !== "shell" ? "" : call.inputBytes !== undefined ? `<div class="hint">Input truncated (${esc(call.inputBytes)} bytes).</div><div class="code">${esc(call.input)}</div>` : `<div class="code">${highlightCode(json(call.input), "json")}</div>`; return `<details class="call"${state.callsOpen.has(key) ? " open" : ""} data-call="${esc(key)}"><summary class="call-row">${glyph(status)}<span class="log-time">${esc(fmtClock(call.startedAt))}</span><b>${esc(callLabel(call))}</b><span class="call-preview">${esc(callPreview(call))}</span><span class="num">${duration}</span></summary><div class="call-body">${call.phase ? `<div class="kv"><div class="k">Phase</div><div>${esc(call.phase)}</div></div>` : ""}${call.kind === "shell" ? `<div class="sec">INPUT</div>${input}<div class="sec">OUTPUT</div>${renderOutputPane(call)}` : `<div class="kv"><div class="k">Status</div><div>${esc(status)}</div>${call.error ? `<div class="k">Error</div><div>${esc(call.error.code)}</div>` : ""}</div>`}</div></details>`; }).join("");
749
+ return `<section class="logs"><button type="button" class="sec section-toggle" data-toggle="calls" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} CALLS</button>${collapsed ? "" : `<div class="calls-list">${rows}${omitted ? `<div class="log-row"><span class="log-time">—</span><span class="log-message">${esc(omitted.omitted)} calls omitted from live state</span></div>` : ""}</div>`}</section>`;
750
+ }
671
751
  function renderAgentSection(record, timingsByAgent) { const collapsed = state.runLayout.agentsCollapsed; return `<button type="button" class="sec section-toggle" data-toggle="agents" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} AGENTS</button>${collapsed ? "" : renderAgentGrid(record, timingsByAgent)}`; }
672
- function renderList(record, timingsByAgent) { const run = record.run; const completed = run.agents.filter((agent) => agent.state === "completed").length; return `<section class="workflow-card"><button type="button" class="workflow-head on" data-run="${esc(state.currentRun)}">${glyph(run.state)}<span class="workflow-name">${esc(run.workflowName)}</span><span class="workflow-meta">${completed}/${run.agents.length} agents · ${fmtCost(runCost(record))} · <span data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}">${fmtRuntime(duration(record))}</span></span></button>${renderAgentSection(record, timingsByAgent)}${renderLogs(record)}</section>`; }
752
+ function renderList(record, timingsByAgent) { const run = record.run; const completed = run.agents.filter((agent) => agent.state === "completed").length; return `<section class="workflow-card"><button type="button" class="workflow-head on" data-run="${esc(state.currentRun)}">${glyph(run.state)}<span class="workflow-name">${esc(run.workflowName)}</span><span class="workflow-meta">${completed}/${run.agents.length} agents · ${fmtCost(runCost(record))} · <span data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}"></span></span></button>${renderAgentSection(record, timingsByAgent)}${renderCalls(record)}${renderLogs(record)}</section>`; }
673
753
  function actionButton(label, action, disabled) { return `<button class="btn-alt" data-act="${action}"${disabled ? " disabled" : ""}>${label}</button>`; }
674
- function subagentAgent(subagent) { return { id: subagent.id, name: subagentLabel(subagent), label: subagentLabel(subagent), role: subagent.role || "subagent", state: subagent.state, model: subagent.model, tools: subagent.tools || [], toolDefinitions: subagent.toolDefinitions || [], prompt: subagent.request.prompt, systemPrompt: subagent.attempt?.setup?.systemPrompt || subagent.request.prompt, attempts: subagent.attempts || 0, mode: subagent.mode, worktree: subagent.worktree, outcome: subagent.result !== undefined ? { kind: "result", value: subagent.result } : subagent.failure !== undefined ? { kind: "failure", value: subagent.failure } : subagent.attempt?.error !== undefined ? { kind: "failure", value: subagent.attempt.error } : undefined, finishedAt: subagent.finishedAt, attemptDetails: subagent.attempt ? [subagent.attempt] : [] }; }
754
+ function subagentAgent(subagent) { return { id: subagent.id, name: subagentLabel(subagent), label: subagentLabel(subagent), role: subagent.role || "subagent", state: subagent.state, model: subagent.model, requestedModel: subagent.request.model, tools: subagent.tools || [], toolDefinitions: subagent.toolDefinitions || [], skills: subagent.request.skills || [], extensions: subagent.request.extensions || [], contextFiles: subagent.request.contextFiles || [], prompt: subagent.request.prompt, systemPrompt: subagent.attempt?.setup?.systemPrompt || subagent.request.prompt, attempts: subagent.attempts || 0, mode: subagent.mode, cwd: subagent.cwd, worktree: subagent.worktree, output: subagent.output || subagentOutputFallback(subagent), outcome: subagent.result !== undefined ? { kind: "result", value: subagent.result } : subagent.failure !== undefined ? { kind: "failure", value: subagent.failure } : subagent.attempt?.error !== undefined ? { kind: "failure", value: subagent.attempt.error } : undefined, finishedAt: subagent.finishedAt, attemptDetails: subagent.attempt ? [subagent.attempt] : [] }; }
755
+ function subagentOutputFallback(subagent) { if (subagent.result !== undefined) return { status: "available", value: subagent.result, bytes: 0 }; if (subagent.failure !== undefined) return { status: "failed", code: subagent.failure.code || "AGENT_FAILED", message: subagent.failure.message || "Subagent failed" }; if (subagent.state === "running") return { status: "pending" }; if (subagent.state === "completed") return { status: "unavailable" }; if (subagent.state === "stopped") return { status: "cancelled", code: subagent.error?.code || "CANCELLED", message: subagent.error?.message || "Subagent stopped" }; if (subagent.state === "failed") return { status: "failed", code: subagent.error?.code || "AGENT_FAILED", message: subagent.error?.message || "Subagent failed" }; return { status: "unavailable" }; }
675
756
  function renderAgentStats(found, agent, isSubagent) {
676
757
  const offline = !found.publisher.connected; const stateText = agent.state || "—"; const value = isSubagent ? subagentAccounting(found.record) : accounting(agent);
677
758
  const startedAt = isSubagent ? subagentStartOf(found.record) : agent.startedAt; const endedAt = stateText === "running" ? undefined : isSubagent ? subagentEndOf(found.record) : Number.isFinite(agent.durationMs) && agent.startedAt ? agent.startedAt + agent.durationMs : undefined;
678
759
  const clock = Number.isFinite(startedAt) ? `<span data-clock-kind="runtime" data-clock-start="${startedAt}" data-clock-end="${stateText === "running" ? "live" : Number.isFinite(endedAt) ? endedAt : startedAt}">${esc(fmtRuntime(Math.max(0, (Number.isFinite(endedAt) ? endedAt : Date.now()) - startedAt)))}</span>` : "—";
679
760
  const controls = isSubagent ? stateText === "running" ? `${actionButton("Stop", "stop", offline)}<input class="steer-input" id="subagent-steer" placeholder="Send guidance"${offline ? " disabled" : ""} /><button class="btn-alt" data-act="steer"${offline ? " disabled" : ""}>Send</button>` : stateText === "failed" || stateText === "stopped" ? actionButton("Retry", "retry", offline) : "" : "";
680
- return `${glyph(stateText)} ${fmtCost(value.cost)} · ${fmtTokens(value.input + value.output)} · runtime ${clock}${controls ? `<span class="actions">${controls}</span>` : ""}${isSubagent && offline ? '<span class="offline">publisher offline — actions disabled</span>' : ""}`;
761
+ return `${glyph(stateText)} <button type="button" class="btn-alt agent-details" data-agent-details="1">Agent details</button> ${fmtCost(value.cost)} · ${fmtTokens(value.input + value.output)} · runtime ${clock}${controls ? `<span class="actions">${controls}</span>` : ""}${isSubagent && offline ? '<span class="offline">publisher offline — actions disabled</span>' : ""}`;
681
762
  }
682
- function renderDossier(publisher, record) { const offline = !publisher.connected; const issues = record.run.agents.filter((agent) => agent.state === "failed").map((agent) => `<div class="pending issue"><div class="ttl">${glyph(agent.state)} ${esc(agent.label || agent.name)} needs retry</div><div class="ins-sub">${esc(agent.attemptDetails?.at(-1)?.error?.message || record.run.error?.message || "agent failed")}</div><div class="actions">${actionButton("Retry", "retry", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const pending = issues + (record.awaiting || []).map((item) => `<div class="pending" data-name="${esc(item.name)}"><div class="ttl">${glyph("awaiting_input")} checkpoint ${esc(item.name)}</div><div class="ins-sub">${esc(item.prompt)}</div><div class="actions">${actionButton("Approve", "checkpoint-approve", offline)}${actionButton("Reject", "checkpoint-reject", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const run = record.run; const controls = (run.state === "running" || run.state === "awaiting_input" ? `${actionButton("Pause", "pause", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "pausing" ? `${actionButton("Cancel pause", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "paused" || run.state === "interrupted" || run.state === "budget_exhausted" ? `${actionButton("Resume", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : "") + (window.__PIEWF_STATIC__ ? "" : actionButton("Share", "share", offline)); const rows = run.agents.map((agent) => `<tr data-agent="${esc(agent.id)}"><td>${glyph(agent.state)}</td><td><b>${esc(agent.label || agent.name)}</b><div class="ins-sub">${esc(agent.role || "agent")}</div></td><td class="num">${fmtTokens(accounting(agent).input + accounting(agent).output)}</td><td class="num">${fmtCost(accounting(agent).cost)}</td></tr>`).join(""); const script = record.snapshot?.script ? `<div class="sec">SCRIPT <span class="ins-sub">click to expand</span></div><div class="code script" data-script="1">${highlight(record.snapshot.script)}</div>` : ""; return `<div class="ins-head" data-run-panel="1">${glyph(run.state)}<b>${esc(run.workflowName)}</b><span class="ins-sub">${esc(run.state)}</span></div><div class="ins-body">${pending ? '<div class="sec">NEEDS ATTENTION</div>' + pending : ""}<div class="sec">STATE</div><div class="kv"><div class="k">Phase</div><div>${esc(run.phase || "—")}</div><div class="k">Active</div><div>${run.agents.filter((agent) => agent.state === "running").length} running · ${run.agents.filter((agent) => agent.state === "queued").length} queued · ${run.agents.filter((agent) => agent.state === "failed").length} failed</div><div class="k">Outcome</div><div>${esc(run.error ? `${run.error.code}: ${run.error.message}` : run.state === "completed" ? "completed" : attention(record) ? `${attention(record)} agent(s) need a human` : "—")}</div><div class="k">Cost</div><div>${fmtCost(runCost(record))}${run.budget?.costUsd?.hard !== undefined ? ` / ${fmtCost(run.budget.costUsd.hard)} hard` : ""}</div><div class="k">Tokens</div><div>${fmtTokens(runTokens(record))}${run.budget?.tokens?.hard !== undefined ? ` / ${fmtTokens(run.budget.tokens.hard)} hard` : ""}</div><div class="k">Started</div><div>${fmtClock(startOf(record))}</div><div class="k">Runtime</div><div data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}">${fmtRuntime(duration(record))}</div></div><div class="actions">${controls}</div>${state.shareUrls[run.id] ? `<div class="share-url"><input readonly value="${esc(state.shareUrls[run.id])}" /><button class="btn-alt" data-copy-share="${esc(state.shareUrls[run.id])}">Copy</button></div><div class="ins-sub">secret gist: anyone with the link can read it</div>` : ""}${script}<div class="sec">AGENTS</div><table class="acct"><thead><tr><th></th><th>agent</th><th class="num">tok</th><th class="num">cost</th></tr></thead><tbody>${rows}</tbody></table></div>`; }
763
+ function renderWorkflowArguments(record, open = false, key = "") { const args = record.snapshot?.args; const incomplete = record.snapshotArgsTruncated === true; if (args === null || args === undefined && !incomplete) return ""; const status = incomplete ? ' <span class="ins-sub">incomplete</span>' : ""; const body = args === undefined ? '<div class="hint">Arguments unavailable in the live transport.</div>' : `<div class="code args">${highlightCode(json(args), "json")}</div>`; const warning = incomplete ? '<div class="workflow-args-warning">Arguments were truncated by the live transport; this value is incomplete.</div>' : ""; return `<details class="workflow-args"${open ? " open" : ""} data-workflow-args="${esc(key)}"><summary class="sec">ARGUMENTS${status}</summary>${body}${warning}</details>`; }
764
+ function renderDossier(publisher, record) { const offline = !publisher.connected; const issues = record.run.agents.filter((agent) => agent.state === "failed").map((agent) => `<div class="pending issue"><div class="ttl">${glyph(agent.state)} ${esc(agent.label || agent.name)} needs retry</div><div class="ins-sub">${esc(agent.attemptDetails?.at(-1)?.error?.message || record.run.error?.message || "agent failed")}</div><div class="actions">${actionButton("Retry", "retry", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const pending = issues + (record.awaiting || []).map((item) => `<div class="pending" data-name="${esc(item.name)}"><div class="ttl">${glyph("awaiting_input")} checkpoint ${esc(item.name)}</div><div class="ins-sub">${esc(item.prompt)}</div><div class="actions">${actionButton("Approve", "checkpoint-approve", offline)}${actionButton("Reject", "checkpoint-reject", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const run = record.run; const controls = (run.state === "running" || run.state === "awaiting_input" ? `${actionButton("Pause", "pause", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "pausing" ? `${actionButton("Cancel pause", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "paused" || run.state === "interrupted" || run.state === "budget_exhausted" ? `${actionButton("Resume", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : "") + (window.__PIEWF_STATIC__ ? "" : actionButton("Share", "share", offline)); const rows = run.agents.map((agent) => `<tr data-agent="${esc(agent.id)}"><td>${glyph(agent.state)}</td><td><b>${esc(agent.label || agent.name)}</b><div class="ins-sub">${esc(agent.role || "agent")}</div></td><td class="num">${fmtTokens(accounting(agent).input + accounting(agent).output)}</td><td class="num">${fmtCost(accounting(agent).cost)}</td></tr>`).join(""); const script = record.snapshot?.script ? `<div class="sec">SCRIPT <span class="ins-sub">click to expand</span></div><div class="code script" data-script="1">${highlightCode(record.snapshot.script, "javascript")}</div>` : ""; const argumentsSection = renderWorkflowArguments(record, state.workflowArgumentsOpen.has(state.currentRun), state.currentRun); return `<div class="ins-head" data-run-panel="1">${glyph(run.state)}<b>${esc(run.workflowName)}</b><span class="ins-sub">${esc(run.state)}</span></div><div class="ins-body">${pending ? '<div class="sec">NEEDS ATTENTION</div>' + pending : ""}<div class="sec">STATE</div><div class="kv"><div class="k">Phase</div><div>${esc(run.phase || "—")}</div><div class="k">Active</div><div>${run.agents.filter((agent) => agent.state === "running").length} running · ${run.agents.filter((agent) => agent.state === "queued").length} queued · ${run.agents.filter((agent) => agent.state === "failed").length} failed</div><div class="k">Outcome</div><div>${esc(run.error ? `${run.error.code}: ${run.error.message}` : run.state === "completed" ? "completed" : attention(record) ? `${attention(record)} agent(s) need a human` : "—")}</div><div class="k">Cost</div><div>${fmtCost(runCost(record))}${run.budget?.costUsd?.hard !== undefined ? ` / ${fmtCost(run.budget.costUsd.hard)} hard` : ""}</div><div class="k">Tokens</div><div>${fmtTokens(runTokens(record))}${run.budget?.tokens?.hard !== undefined ? ` / ${fmtTokens(run.budget.tokens.hard)} hard` : ""}</div><div class="k">Started</div><div>${fmtClock(startOf(record))}</div><div class="k">Runtime</div><div data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}"></div></div><div class="actions">${controls}</div>${state.shareUrls[run.id] ? `<div class="share-url"><input readonly value="${esc(state.shareUrls[run.id])}" /><button class="btn-alt" data-copy-share="${esc(state.shareUrls[run.id])}">Copy</button></div><div class="ins-sub">secret gist: anyone with the link can read it</div>` : ""}${script}${argumentsSection}<div class="sec">AGENTS</div><table class="acct"><thead><tr><th></th><th>agent</th><th class="num">tok</th><th class="num">cost</th></tr></thead><tbody>${rows}</tbody></table></div>`; }
683
765
  function renderRunLayout() { const ganttCollapsed = state.runLayout.ganttCollapsed; const splitSwim = $("split-swim"); $("swim").classList.toggle("hidden", ganttCollapsed); splitSwim.classList.toggle("hidden", ganttCollapsed); const heading = $("toggle-gantt"); heading.textContent = `${ganttCollapsed ? "▸" : "▾"} GANTT`; heading.setAttribute("aria-expanded", String(!ganttCollapsed)); const swimHeight = Math.min(state.runLayout.swimHeight, maxSwimHeight()); document.documentElement.style.setProperty("--swim", `${swimHeight}px`); }
684
766
  function clampSwimHeight() { renderRunLayout(); }
685
- function renderHome() { const homePublisher = livePublishers().find((publisher) => publisher.id === state.currentPub); const hasSubagents = Boolean(homePublisher?.subagents?.length); $("canvas-pub").textContent = homePublisher ? homePublisher.id.slice(0, 8) : "—"; $("canvas-run").textContent = hasSubagents ? "SUBAGENTS" : "—"; patch($("swim-content"), hasSubagents ? renderSubagentGantt(homePublisher) : ""); $("swim").dataset.timelineHasTime = hasSubagents ? "true" : "false"; $("swim").dataset.timelineRunning = String(Boolean(hasSubagents && homePublisher.subagents.some((subagent) => subagent.state === "running"))); if (hasSubagents) { const subagents = homePublisher.subagents || []; const start = Math.min(...subagents.map((subagent) => subagentStartOf(subagent))); const end = Math.max(start + 1, ...subagents.map((subagent) => subagentEndOf(subagent))); $("swim").dataset.timelineStart = String(start); $("swim").dataset.timelineSpan = String(Math.max(1, end - start)); $("swim").querySelector(":scope > .now")?.classList.remove("hidden"); } else { $("swim").querySelector(":scope > .now")?.classList.add("hidden"); } patch($("list"), hasSubagents ? '<div class="hint">Select a subagent to inspect its transcript.</div>' : ""); patch($("run-stats"), hasSubagents ? `${homePublisher.subagents.length} subagents` : ""); $("run-crumb").textContent = "—"; $("agent-crumb").textContent = "—"; patch($("agent-ticks"), ""); $("agent-timeline").dataset.timelineHasTime = "false"; $("agent-timeline").querySelector(":scope > .now")?.classList.add("hidden"); $("agent-timeline").querySelector(":scope > .brush")?.classList.add("hidden"); patch($("events"), ""); $("q").value = ""; $("reset-agent-range").classList.add("hidden"); patch($("insp"), hasSubagents ? '<div class="ins-body hint">Select a subagent.</div>' : '<div class="ins-body hint">Select a run.</div>'); state.inspSig = null; renderRunLayout(); }
686
- function renderRun() { const found = selected(); if (!found || found.target.kind !== "run") { renderHome(); return; } const dossierBody = $("insp").querySelector(".ins-body"); const dossierScroll = dossierBody ? dossierBody.scrollTop : 0; const { publisher, record } = found; const timingsByAgent = agentTimingMap(record); $("canvas-pub").textContent = publisher.id.slice(0, 8); $("canvas-run").textContent = record.run.workflowName; patch($("swim-content"), renderGantt(record, timingsByAgent)); $("swim").dataset.timelineStart = String(startOf(record).getTime()); $("swim").dataset.timelineSpan = String(Math.max(1, endOf(record) - startOf(record).getTime())); $("swim").dataset.timelineHasTime = "true"; $("swim").dataset.timelineRunning = String(record.run.state === "running"); patch($("list"), renderList(record, timingsByAgent)); renderRunLayout(); patch($("insp"), renderDossier(publisher, record)); const nextDossierBody = $("insp").querySelector(".ins-body"); if (nextDossierBody && dossierScroll) nextDossierBody.scrollTop = dossierScroll; patch($("run-stats"), `${glyph(record.run.state)} ${fmtCost(runCost(record))} · ${fmtTokens(runTokens(record))} · started ${fmtClock(startOf(record))} · runtime <span data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${record.run.state === "running" ? "live" : endOf(record)}">${fmtRuntime(duration(record))}</span>`); }
767
+ function renderHome() { const homePublisher = livePublishers().find((publisher) => publisher.id === state.currentPub); const hasSubagents = Boolean(homePublisher?.subagents?.length); $("canvas-pub").textContent = homePublisher ? homePublisher.id.slice(0, 8) : "—"; $("canvas-run").textContent = hasSubagents ? "SUBAGENTS" : "—"; renderedRunHtml.clear(); ganttOrder = ""; patch($("swim-content"), hasSubagents ? renderSubagentGantt(homePublisher) : ""); $("swim").dataset.timelineHasTime = hasSubagents ? "true" : "false"; $("swim").dataset.timelineRunning = String(Boolean(hasSubagents && homePublisher.subagents.some((subagent) => subagent.state === "running"))); if (hasSubagents) { const subagents = homePublisher.subagents || []; const start = Math.min(...subagents.map((subagent) => subagentStartOf(subagent))); const end = Math.max(start + 1, ...subagents.map((subagent) => subagentEndOf(subagent))); $("swim").dataset.timelineStart = String(start); $("swim").dataset.timelineSpan = String(Math.max(1, end - start)); $("swim").querySelector(":scope > .now")?.classList.remove("hidden"); } else { $("swim").querySelector(":scope > .now")?.classList.add("hidden"); } patch($("list"), hasSubagents ? '<div class="hint">Select a subagent to inspect its transcript.</div>' : ""); patch($("run-stats"), hasSubagents ? `${homePublisher.subagents.length} subagents` : ""); $("run-crumb").textContent = "—"; $("agent-crumb").textContent = "—"; patch($("agent-ticks"), ""); $("agent-timeline").dataset.timelineHasTime = "false"; $("agent-timeline").querySelector(":scope > .now")?.classList.add("hidden"); $("agent-timeline").querySelector(":scope > .brush")?.classList.add("hidden"); patch($("events"), ""); $("q").value = ""; $("reset-agent-range").classList.add("hidden"); patch($("insp"), hasSubagents ? '<div class="ins-body hint">Select a subagent.</div>' : '<div class="ins-body hint">Select a run.</div>'); state.inspSig = null; renderRunLayout(); }
768
+ // Live updates repeat identical markup for everything but the clocks, so each run panel is patched only when its HTML actually changes.
769
+ const renderedRunHtml = new Map();
770
+ function patchRun(id, html) { const key = `${document.body.dataset.view}\n${state.currentRun}\n${html}`; if (renderedRunHtml.get(id) === key) return; renderedRunHtml.set(id, key); patch($(id), html); }
771
+ function renderRun() { const found = selected(); if (!found || found.target.kind !== "run") { renderHome(); return; } const dossierBody = $("insp").querySelector(".ins-body"); const dossierScroll = dossierBody ? dossierBody.scrollTop : 0; const { publisher, record } = found; const timingsByAgent = agentTimingMap(record); $("canvas-pub").textContent = publisher.id.slice(0, 8); $("canvas-run").textContent = record.run.workflowName; patchGantt(record, timingsByAgent); $("swim").dataset.timelineStart = String(startOf(record).getTime()); $("swim").dataset.timelineSpan = String(timelineSpan(record)); $("swim").dataset.timelineHasTime = "true"; $("swim").dataset.timelineRunning = String(record.run.state === "running"); patchRun("list", renderList(record, timingsByAgent)); renderRunLayout(); patchRun("insp", renderDossier(publisher, record)); const nextDossierBody = $("insp").querySelector(".ins-body"); if (nextDossierBody && dossierScroll) nextDossierBody.scrollTop = dossierScroll; patchRun("run-stats", `${glyph(record.run.state)} ${fmtCost(runCost(record))} · ${fmtTokens(runTokens(record))} · started ${fmtClock(startOf(record))} · runtime <span data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${record.run.state === "running" ? "live" : endOf(record)}"></span>`); tickClocks(); }
687
772
  function entryDetails(entry, agent, entries = []) { if (compactSkillReadPreview(entry, entries) !== undefined) return { kind: "skill", entry, agent }; const kind = eventKind(entry); if (kind === "tool" || entry.type === "tool_result") return { kind: "tool", entry, agent }; if (kind === "system") return { kind: "system", entry, agent }; if (kind === "context" || kind === "issue") return { kind, entry, agent }; return { kind: kind === "user" ? "user" : "assistant", entry, agent }; }
688
- function renderToolPane(detail, entries) { const entry = detail.entry; const message = entry.message || entry; const args = toolArgsOf(entry, entries); const description = toolDescriptionOf(detail.agent, toolNameOf(entry)); const timing = toolTimingOf(entry); const panes = { summary: () => `<div class="kv"><div class="k">Tool</div><div>${esc(toolNameOf(entry))}</div>${description ? `<div class="k">Description</div><div>${esc(description)}</div>` : ""}<div class="k">Hierarchy</div><div>${esc(entry.type)}</div><div class="k">Status</div><div>${toolResultIsError(entry) ? "Failed" : "Completed"}</div></div>`, payload: () => `<div class="code">${highlight(json(args))}</div>`, result: () => `<div class="code">${esc(contentText(entry.result ?? entry.content ?? message.content))}</div>`, schema: () => `<div class="code">${esc(toolNameOf(entry))}(${esc(json(args))})</div>`, timing: () => timing ? `<div class="kv"><div class="k">Started</div><div>${esc(fmtClock(timing.startedAt))}</div><div class="k">Completed</div><div>${esc(fmtClock(timing.completedAt))}</div><div class="k">Duration</div><div>${esc(formatToolTiming(timing))}</div><div class="k">Status</div><div>${timing.isError ? "Failed" : "Completed"}</div><div class="k">Source</div><div>${timingEntryType}</div></div>` : `<div class="kv"><div class="k">Duration</div><div>timing unavailable</div><div class="k">Source</div><div>${timingEntryType}</div></div>` }; patch($("tool-pane"), (panes[state.toolPane] || panes.summary)()); $("tool-tabs").querySelectorAll("span").forEach((tab) => tab.classList.toggle("on", tab.dataset.pane === state.toolPane)); }
689
- function renderSystemPane(agent) { const prompt = agent.systemPrompt || agent.prompt || ""; const { skillFilters, skillResolved, extensionFilters, extensionResolved } = sessionResources(agent); const panes = { prompt: () => `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="md">${prompt ? sanitizeMarkdown(marked.parse(prompt, { mangle: false, headerIds: false })) : "System prompt unavailable"}</div>`, tools: () => (agent.tools || []).map((tool) => { const description = toolDescriptionOf(agent, tool); return `<div class="tool-item"><b>${esc(tool)}</b>${description ? `<span>${esc(description)}</span>` : ""}</div>`; }).join("") || '<div class="hint">No tools recorded.</div>', skills: () => `<div class="sec">FILTERS</div>${nameList(skillFilters)}<div class="sec">RESOLVED</div>${resourceList(skillResolved)}`, extensions: () => `<div class="sec">FILTERS</div>${nameList(extensionFilters)}<div class="sec">RESOLVED</div>${resourceList(extensionResolved)}`, env: () => `<div class="code">${esc(json({ role: agent.role, state: agent.state, mode: agent.mode, model: agent.model, requestedModel: agent.requestedModel, attempts: agent.attempts, worktree: agent.worktree, structuralPath: agent.structuralPath, worktreeOwner: agent.worktreeOwner }))}</div>` }; patch($("sys-pane"), (panes[state.sysPane] || panes.prompt)()); $("sys-tabs").querySelectorAll("span").forEach((tab) => tab.classList.toggle("on", tab.dataset.pane === state.sysPane)); }
690
- function renderAssistantToolCalls(entry, entries) { const toolCalls = toolCallsOf(entry); return toolCalls.length ? `<div class="assistant-tools">${toolCalls.map((call) => `<div class="assistant-tool"><span class="pill tool">${esc(call.name)}</span><div class="code">${highlight(json(toolArgsOf(call, entries)))}</div></div>`).join("")}</div>` : ""; }
691
- function renderInspector(detail, agent, entries, root = $("insp")) { if (!detail) { patch(root, '<div class="ins-body hint">Select an event.</div>'); return; } if (detail.kind === "tool" || detail.kind === "skill") { patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span><b class="tool-name">${esc(toolNameOf(detail.entry))}</b><span class="ins-sub">${esc(detail.entry.timestamp || "")}</span></div><div class="itabs" id="tool-tabs"><span data-pane="summary">Summary</span><span data-pane="payload">Input</span><span data-pane="result">Output</span><span data-pane="schema">Schema</span><span data-pane="timing">Timing</span></div><div class="ins-body"><div id="tool-pane"></div></div>`); renderToolPane(detail, entries); return; } if (detail.kind === "system") { patch(root, `<div class="ins-head"><span class="pill system">SYSTEM</span><span class="ins-sub">est. ${estTokens(agent.systemPrompt || agent.prompt || detail.entry.prompt || "")} tok</span></div><div class="itabs" id="sys-tabs"><span data-pane="prompt">Prompt</span><span data-pane="tools">Tools</span><span data-pane="skills">Skills</span><span data-pane="extensions">Extensions</span><span data-pane="env">Environment</span></div><div class="ins-body"><div id="sys-pane"></div></div>`); renderSystemPane(agent); return; } const message = detail.entry.message || detail.entry; const body = detail.kind === "assistant" ? assistantBodyText(message.content ?? detail.entry.content) : contentText(message.content ?? detail.entry.content); const tokenRows = messageTokenRows(detail.kind, message, body); const bodyHtml = detail.kind === "assistant" ? sanitizeMarkdown(marked.parse(body)) : esc(body); const tokenHtml = tokenRows ? `<div class="kv">${tokenRows}</div>` : ""; const assistantTools = detail.kind === "assistant" ? renderAssistantToolCalls(detail.entry, entries) : ""; patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span></div><div class="ins-body">${tokenHtml}<div class="${detail.kind === "assistant" ? "md" : "code"}">${bodyHtml}</div>${assistantTools}</div>`); }
773
+ function renderToolPane(detail, entries) { const entry = detail.entry; const message = entry.message || entry; const args = toolArgsOf(entry, entries); const description = toolDescriptionOf(detail.agent, toolNameOf(entry)); const timing = toolTimingOf(entry); const panes = { summary: () => `<div class="kv"><div class="k">Tool</div><div>${esc(toolNameOf(entry))}</div>${description ? `<div class="k">Description</div><div>${esc(description)}</div>` : ""}<div class="k">Hierarchy</div><div>${esc(entry.type)}</div><div class="k">Status</div><div>${toolResultIsError(entry) ? "Failed" : "Completed"}</div></div>`, payload: () => `<div class="code">${highlightCode(json(args), "json")}</div>`, result: () => `<div class="code">${esc(contentText(entry.result ?? entry.content ?? message.content))}</div>`, schema: () => `<div class="code">${esc(toolNameOf(entry))}(${esc(json(args))})</div>`, timing: () => timing ? `<div class="kv"><div class="k">Started</div><div>${esc(fmtClock(timing.startedAt))}</div><div class="k">Completed</div><div>${esc(fmtClock(timing.completedAt))}</div><div class="k">Duration</div><div>${esc(formatToolTiming(timing))}</div><div class="k">Status</div><div>${timing.isError ? "Failed" : "Completed"}</div><div class="k">Source</div><div>${timingEntryType}</div></div>` : `<div class="kv"><div class="k">Duration</div><div>timing unavailable</div><div class="k">Source</div><div>${timingEntryType}</div></div>` }; patch($("tool-pane"), (panes[state.toolPane] || panes.summary)()); $("tool-tabs").querySelectorAll("span").forEach((tab) => tab.classList.toggle("on", tab.dataset.pane === state.toolPane)); }
774
+ function outputForInspector(agent) { return agent.output || { status: "unavailable" }; }
775
+ function renderOutputPane(agent) {
776
+ const output = outputForInspector(agent);
777
+ if (output.status === "available") return typeof output.value === "string" ? `<div class="code">${esc(output.value)}</div>` : `<div class="code">${highlightCode(json(output.value), "json")}</div>`;
778
+ if (output.status === "pending") return '<div class="hint">Output not yet available.</div>';
779
+ if (output.status === "unavailable") return '<div class="hint">Output unavailable.</div>';
780
+ if (output.status === "truncated") return `<div class="hint">${output.kind === "failure" ? "Failure" : "Output"} is truncated (${esc(output.bytes)} bytes); the complete value is not available.</div>`;
781
+ return `<div class="code">${esc(`${output.status === "cancelled" ? "Cancelled" : "Failed"}: ${output.code}: ${output.message}`)}</div>`;
782
+ }
783
+ function renderSystemPane(agent) {
784
+ const promptAvailable = typeof agent.systemPrompt === "string" || typeof agent.prompt === "string";
785
+ const prompt = typeof agent.systemPrompt === "string" ? agent.systemPrompt : typeof agent.prompt === "string" ? agent.prompt : "";
786
+ const { skillFilters, skillResolved, extensionFilters, extensionResolved } = sessionResources(agent);
787
+ const attempt = agent.attemptDetails?.at(-1);
788
+ const attempts = (agent.attemptDetails || []).map((detail) => ({ attempt: detail.attempt, transport: detail.transport, cwd: detail.setup?.cwd, model: detail.setup?.model, tools: detail.setup?.tools, ...(detail.error ? { error: detail.error } : {}) }));
789
+ const environment = { role: agent.role, requestedModel: agent.requestedModel, model: agent.model, cwd: agent.cwd || attempt?.setup?.cwd || selected()?.record?.run?.cwd, mode: agent.mode, contextFiles: agent.contextFiles, worktree: agent.worktree || (agent.worktreeOwner ? { owner: agent.worktreeOwner } : undefined), structuralPath: agent.structuralPath, parentId: agent.parentId, parentBreadcrumb: agent.parentBreadcrumb, handle: agent.handle, turn: agent.turn, continuity: agent.continuity, state: agent.state, attempts: agent.attempts, attempt: attempt?.attempt, attemptDetails: attempts };
790
+ const panes = {
791
+ prompt: () => `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="md">${promptAvailable ? sanitizeMarkdown(marked.parse(prompt, { mangle: false, headerIds: false })) : "Prompt unavailable"}</div>`,
792
+ tools: () => (agent.tools || []).map((tool) => { const description = toolDescriptionOf(agent, tool); return `<div class="tool-item"><b>${esc(tool)}</b>${description ? `<span>${esc(description)}</span>` : ""}</div>`; }).join("") || '<div class="hint">No tools recorded.</div>',
793
+ skills: () => `<div class="sec">FILTERS</div>${nameList(skillFilters)}<div class="sec">RESOLVED</div>${resourceList(skillResolved)}`,
794
+ extensions: () => `<div class="sec">FILTERS</div>${nameList(extensionFilters)}<div class="sec">RESOLVED</div>${resourceList(extensionResolved)}`,
795
+ env: () => `<div class="code">${highlightCode(json(environment), "json")}</div>`,
796
+ output: () => renderOutputPane(agent),
797
+ };
798
+ patch($("sys-pane"), (panes[state.sysPane] || panes.prompt)()); $("sys-tabs").querySelectorAll("span").forEach((tab) => tab.classList.toggle("on", tab.dataset.pane === state.sysPane));
799
+ }
800
+ function renderSystemPromptPane(agent, entry) { const prompt = typeof entry?.prompt === "string" ? entry.prompt : typeof agent.systemPrompt === "string" ? agent.systemPrompt : ""; const available = typeof entry?.prompt === "string" || typeof agent.systemPrompt === "string"; patch($("sys-pane"), `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="md">${available ? sanitizeMarkdown(marked.parse(prompt, { mangle: false, headerIds: false })) : "System prompt unavailable"}</div>`); }
801
+ function renderAssistantToolCalls(entry, entries) { const toolCalls = toolCallsOf(entry); return toolCalls.length ? `<div class="assistant-tools">${toolCalls.map((call) => `<div class="assistant-tool"><span class="pill tool">${esc(call.name)}</span><div class="code">${highlightCode(json(toolArgsOf(call, entries)), "json")}</div></div>`).join("")}</div>` : ""; }
802
+ function renderAgentDetails(agent, root = $("insp")) { patch(root, `<div class="ins-head"><span class="pill context">AGENT</span><b class="tool-name">${esc(agent.label || agent.name)}</b><span class="ins-sub">${esc(agent.state || "")}</span></div><div class="itabs" id="sys-tabs"><span data-pane="prompt">Prompt</span><span data-pane="tools">Tools</span><span data-pane="skills">Skills</span><span data-pane="extensions">Extensions</span><span data-pane="env">Environment</span><span data-pane="output">Output</span></div><div class="ins-body"><div id="sys-pane"></div></div>`); renderSystemPane(agent); }
803
+ function renderInspector(detail, agent, entries, root = $("insp")) { if (!detail) { patch(root, '<div class="ins-body hint">Select an event.</div>'); return; } if (detail.kind === "tool" || detail.kind === "skill") { patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span><b class="tool-name">${esc(toolNameOf(detail.entry))}</b><span class="ins-sub">${esc(detail.entry.timestamp || "")}</span></div><div class="itabs" id="tool-tabs"><span data-pane="summary">Summary</span><span data-pane="payload">Input</span><span data-pane="result">Output</span><span data-pane="schema">Schema</span><span data-pane="timing">Timing</span></div><div class="ins-body"><div id="tool-pane"></div></div>`); renderToolPane(detail, entries); return; } if (detail.kind === "system") { const prompt = typeof detail.entry.prompt === "string" ? detail.entry.prompt : agent.systemPrompt; patch(root, `<div class="ins-head"><span class="pill system">SYSTEM</span><span class="ins-sub">est. ${estTokens(prompt || "")} tok</span></div><div class="ins-body"><div id="sys-pane"></div></div>`); renderSystemPromptPane(agent, detail.entry); return; } const message = detail.entry.message || detail.entry; const body = detail.kind === "assistant" ? assistantBodyText(message.content ?? detail.entry.content) : contentText(message.content ?? detail.entry.content); const tokenRows = messageTokenRows(detail.kind, message, body); const bodyHtml = detail.kind === "assistant" ? sanitizeMarkdown(marked.parse(body)) : esc(body); const tokenHtml = tokenRows ? `<div class="kv">${tokenRows}</div>` : ""; const assistantTools = detail.kind === "assistant" ? renderAssistantToolCalls(detail.entry, entries) : ""; patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span></div><div class="ins-body">${tokenHtml}<div class="${detail.kind === "assistant" ? "md" : "code"}">${bodyHtml}</div>${assistantTools}</div>`); }
692
804
  function requestTranscript(found, agentId) {
693
805
  if (!found || !state.socket || state.socket.readyState !== 1) return;
694
806
  const isSubagent = found.target.kind === "subagent"; if (!agentId && !isSubagent) return; const key = isSubagent ? transcriptKey(found.publisher.id, found.record.id) : transcriptKey(found.publisher.id, found.record.run.id, agentId);
@@ -697,8 +809,8 @@
697
809
  if (metadata && (metadata.status === "empty" || metadata.status === "missing" || metadata.status === "failed" || metadata.status === "oversized" || metadata.status === "disconnected")) { cacheTranscript(key, metadata.timing || [], found.record, metadata.revision, metadata.status); return; }
698
810
  if (state.transcriptPending[key]) return;
699
811
  if (!sourceChanged && hasCache && !state.transcriptRefresh[key]) return;
700
- const requestId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`; state.transcriptPending[key] = { source: sourceToken, requestId };
701
812
  const revision = metadata && typeof metadata.revision === "number" ? metadata.revision : undefined;
813
+ const requestId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`; state.transcriptPending[key] = { source: sourceToken, requestId, revision };
702
814
  state.socket.send(isSubagent ? JSON.stringify({ type: "ui:transcript", requestId, publisherId: found.publisher.id, subagentId: found.record.id, ...(revision === undefined ? {} : { revision }) }) : JSON.stringify({ type: "ui:transcript", requestId, publisherId: found.publisher.id, runId: found.record.run.id, agentId, ...(revision === undefined ? {} : { revision }) }));
703
815
  }
704
816
  function renderAgent() {
@@ -710,20 +822,32 @@
710
822
  $("run-crumb").textContent = isSubagent ? "SUBAGENTS" : found.record.run.workflowName; $("agent-crumb").textContent = agent.label || agent.name;
711
823
  const entries = agentEntries(found.record, agent); const { times, start, span, hasTime } = timelineScale(entries); if (!hasTime) state.agentRange = null; const timeline = $("agent-timeline"); timeline.dataset.timelineStart = String(start); timeline.dataset.timelineSpan = String(span); timeline.dataset.timelineHasTime = String(hasTime); patch($("agent-ticks"), renderAgentTimeline(entries));
712
824
  const transcriptRevisionKey = isSubagent ? transcriptKey(found.publisher.id, found.record.id) : transcriptKey(found.publisher.id, found.record.run.id, agent.id); const eventsSig = `${targetKey(found.target)}:${agent.id}:${state.transcriptRevision[transcriptRevisionKey] || 0}:${entries.length}:${entries.at(-1)?.timestamp || entries.at(-1)?.type || ""}:${state.selectedEvent ?? ""}:${state.timelineHighlight ?? ""}`; if (state.eventsSig !== eventsSig) { patch(events, entries.length ? entries.map((entry, index) => { const detail = entryDetails(entry, agent, entries); const t = (times[index] - start) / span * 100; const compact = compactSkillReadPreview(entry, entries); const preview = compact !== undefined ? esc(compact) : eventKind(entry) === "tool" ? toolPreviewHtml(entry, entries) : eventKind(entry) === "assistant" ? renderAssistantPreview(eventPreviewParts(entry, entries)) : esc(eventPreview(entry, entries)); return `<div class="evt${index === state.selectedEvent ? " on" : ""}${index === state.timelineHighlight ? " timeline-highlight" : ""}" data-event="${index}" data-t="${t}" data-search="${esc(eventSearchText(entry, entries))}"><div class="gutter">${esc(entry.timestamp || `#${index + 1}`)}</div><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span><div class="preview">${preview}</div></div>`; }).join("") : `<div class="hint">${esc(transcriptEmptyMessage(transcriptRevisionKey))}</div>`); events.scrollTop = eventsScroll; state.eventsSig = eventsSig; }
713
- const detail = entries[state.selectedEvent || 0] ? entryDetails(entries[state.selectedEvent || 0], agent, entries) : undefined; const sig = `${targetKey(found.target)}:${agent.id}:${state.selectedEvent ?? 0}:${state.sysPane}:${state.toolPane}:${detail?.kind || ""}:${detail ? json(detail.entry) : ""}`;
714
- patch($("agent-stats"), renderAgentStats(found, agent, isSubagent)); if (state.inspSig !== sig) { renderInspector(detail, agent, entries); state.inspSig = sig; }
825
+ const detail = entries[state.selectedEvent || 0] ? entryDetails(entries[state.selectedEvent || 0], agent, entries) : undefined; const sig = `${targetKey(found.target)}:${agent.id}:${agent.state}:${agent.attempts ?? 0}:${json(agent.model)}:${agent.requestedModel || ""}:${state.inspMode}:${state.selectedEvent ?? 0}:${state.sysPane}:${state.toolPane}:${detail?.kind || ""}:${detail ? json(detail.entry) : ""}:${json(agent.output)}`;
826
+ patch($("agent-stats"), renderAgentStats(found, agent, isSubagent)); if (state.inspSig !== sig) { if (state.inspMode === "agent") renderAgentDetails(agent); else renderInspector(detail, agent, entries); state.inspSig = sig; }
715
827
  if (scroller && $("insp").contains(scroller)) scroller.scrollTop = scroll; paintBrush(timeline, ".axis .ticks", state.agentRange); $("reset-agent-range").classList.toggle("hidden", !state.agentRange); applyEventSearch();
716
828
  }
717
829
  function clearEventHighlight() { if (eventHighlightTimer !== undefined) clearTimeout(eventHighlightTimer); eventHighlightTimer = undefined; state.timelineHighlight = null; $("events")?.querySelectorAll(".timeline-highlight").forEach((candidate) => candidate.classList.remove("timeline-highlight")); }
718
830
  function highlightEvent(index) { const events = $("events"); if (!Number.isInteger(index) || index < 0 || !events) return; const item = [...events.querySelectorAll("[data-event]")].find((candidate) => Number(candidate.dataset.event) === index); if (!item) return; clearEventHighlight(); state.timelineHighlight = index; applyEventSearch(); events.querySelectorAll(".timeline-highlight").forEach((candidate) => candidate.classList.remove("timeline-highlight")); void item.offsetWidth; item.classList.add("timeline-highlight"); item.scrollIntoView({ block: "nearest" }); const timer = setTimeout(() => { if (eventHighlightTimer !== timer) return; eventHighlightTimer = undefined; if (state.timelineHighlight === index) state.timelineHighlight = null; events.querySelectorAll(".timeline-highlight").forEach((candidate) => candidate.classList.remove("timeline-highlight")); applyEventSearch(); }, 1200); eventHighlightTimer = timer; }
719
831
  function nearestTimelineEvent(root, clientX) { let nearest; for (const candidate of root.querySelectorAll("[data-event]")) { const box = candidate.getBoundingClientRect(); const distance = Math.abs(clientX - (box.left + box.width / 2)); if (!nearest || distance < nearest.distance) nearest = { candidate, distance }; } return nearest?.candidate; }
720
- function scheduleRender() { if (renderFrame) return; renderFrame = requestAnimationFrame(() => { renderFrame = 0; renderSidebar(); if (document.body.dataset.view === "run") renderRun(); else renderAgent(); }); }
832
+ function scheduleRender() { if (renderFrame) return; renderFrame = requestAnimationFrame(() => { renderFrame = 0; sendFocus(); if (document.hidden) return; renderSidebar(); if (document.body.dataset.view === "run") renderRun(); else renderAgent(); }); }
721
833
  function applyEventSearch() { const query = ($("q")?.value || "").toLowerCase(); const range = state.agentRange; document.querySelectorAll("#events .evt").forEach((event) => { const t = Number(event.dataset.t); const inRange = !range || (Number.isFinite(t) && t >= range.from && t <= range.to); const searchText = `${event.textContent} ${event.dataset.search || ""}`.toLowerCase(); const inQuery = !query || searchText.includes(query); const highlighted = Number(event.dataset.event) === state.timelineHighlight; event.style.display = highlighted || inRange && inQuery ? "" : "none"; }); }
722
834
  function setView(view, mode = "push") { document.body.dataset.view = view; clearEventHighlight(); $("view-run").classList.toggle("hidden", view !== "run"); $("view-agent").classList.toggle("hidden", view !== "agent" && view !== "subagent"); if (view === "run") renderRun(); else renderAgent(); syncUrl(mode); }
723
- function syncUrl(mode) { const url = new URL(location.href); const target = currentTarget(); url.searchParams.set("view", document.body.dataset.view); if (target?.kind === "run") url.searchParams.set("run", state.currentRun); else url.searchParams.delete("run"); if (target?.kind === "subagent") url.searchParams.set("subagent", state.currentRun); else url.searchParams.delete("subagent"); if (document.body.dataset.view === "agent" && target?.kind === "run" && state.currentAgent) url.searchParams.set("agent", state.currentAgent); else url.searchParams.delete("agent"); const next = url.pathname + url.search; if (mode !== "pop" && next !== location.pathname + location.search) try { (mode === "replace" ? history.replaceState : history.pushState).call(history, { view: document.body.dataset.view, run: target?.kind === "run" ? state.currentRun : null, subagent: target?.kind === "subagent" ? state.currentRun : null, agent: target?.kind === "run" ? state.currentAgent : null, target }, "", next); } catch { /* Static exports run from file: or sandboxed iframes where history writes can throw; navigation state stays in memory. */ } }
724
- function selectHome(mode = "replace") { const homePublisher = state.currentPub || currentTarget()?.publisherId || null; state.currentRun = null; state.currentTarget = null; state.pendingTarget = null; state.currentPub = homePublisher; state.currentAgent = null; state.agentRange = null; state.agentRangeAgent = null; state.selectedEvent = null; state.eventsSig = null; closeScript(); renderSidebar(); setView("run", mode); }
725
- function selectRun(ref, mode = "push") { const target = targetFromRef("run", ref); if (!target) return; setTarget(target); state.pendingTarget = null; state.currentPub = target.publisherId; state.currentAgent = selected()?.record.run.agents[0]?.id || null; state.selectedEvent = null; state.eventsSig = null; renderSidebar(); setView("run", mode); }
726
- function selectSubagent(ref, mode = "push") { const target = targetFromRef("subagent", ref.includes(":") ? ref : `${state.currentPub}:${ref}`); if (!target) return; setTarget(target); state.currentPub = target.publisherId; state.currentAgent = target.id; state.selectedEvent = null; state.agentRange = null; state.eventsSig = null; renderSidebar(); setView("subagent", mode); }
835
+ let lastFocusSent;
836
+ // The server ships tool-timing only for the focused run, so it has to learn which run this tab shows.
837
+ function sendFocus(force = false) {
838
+ const socket = state.socket;
839
+ if (!socket || socket.readyState !== 1) return;
840
+ const target = document.hidden ? undefined : currentTarget();
841
+ const focus = target?.kind === "run" ? { publisherId: target.publisherId, runId: target.id } : { publisherId: null, runId: null };
842
+ const key = `${focus.publisherId}\t${focus.runId}`;
843
+ if (!force && key === lastFocusSent) return;
844
+ lastFocusSent = key;
845
+ socket.send(JSON.stringify({ type: "ui:focus", ...focus }));
846
+ }
847
+ function syncUrl(mode) { sendFocus(); const url = new URL(location.href); const target = currentTarget(); url.searchParams.set("view", document.body.dataset.view); if (target?.kind === "run") url.searchParams.set("run", state.currentRun); else url.searchParams.delete("run"); if (target?.kind === "subagent") url.searchParams.set("subagent", state.currentRun); else url.searchParams.delete("subagent"); if (document.body.dataset.view === "agent" && target?.kind === "run" && state.currentAgent) url.searchParams.set("agent", state.currentAgent); else url.searchParams.delete("agent"); const next = url.pathname + url.search; if (mode !== "pop" && next !== location.pathname + location.search) try { (mode === "replace" ? history.replaceState : history.pushState).call(history, { view: document.body.dataset.view, run: target?.kind === "run" ? state.currentRun : null, subagent: target?.kind === "subagent" ? state.currentRun : null, agent: target?.kind === "run" ? state.currentAgent : null, target }, "", next); } catch { /* Static exports run from file: or sandboxed iframes where history writes can throw; navigation state stays in memory. */ } }
848
+ function selectHome(mode = "replace") { const homePublisher = state.currentPub || currentTarget()?.publisherId || null; state.currentRun = null; state.currentTarget = null; state.pendingTarget = null; state.currentPub = homePublisher; state.currentAgent = null; state.agentRange = null; state.agentRangeAgent = null; state.selectedEvent = null; state.inspMode = "event"; state.eventsSig = null; closeScript(); renderSidebar(); setView("run", mode); }
849
+ function selectRun(ref, mode = "push") { const target = targetFromRef("run", ref); if (!target) return; setTarget(target); state.pendingTarget = null; state.currentPub = target.publisherId; state.currentAgent = selected()?.record.run.agents[0]?.id || null; state.selectedEvent = null; state.inspMode = "event"; state.eventsSig = null; renderSidebar(); setView("run", mode); }
850
+ function selectSubagent(ref, mode = "push") { const target = targetFromRef("subagent", ref.includes(":") ? ref : `${state.currentPub}:${ref}`); if (!target) return; setTarget(target); state.currentPub = target.publisherId; state.currentAgent = target.id; state.selectedEvent = null; state.inspMode = "event"; state.agentRange = null; state.eventsSig = null; renderSidebar(); setView("subagent", mode); }
727
851
  function sendAction(action, extra = {}) { const found = selected(); if (!found || !state.socket || state.socket.readyState !== 1 || !found.publisher.connected) { showToast("publisher offline"); return; } const requestId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`; state.actionTargets.set(requestId, found.target); state.socket.send(JSON.stringify({ type: "ui:action", requestId, publisherId: found.publisher.id, target: found.target, action, ...extra })); showToast(`sent ${action}`); }
728
852
  function showToast(text) { $("toast").textContent = text; $("toast").style.display = "block"; setTimeout(() => { $("toast").style.display = "none"; }, 1800); }
729
853
  function paintBrush(root, selector, range) { let brush = root.querySelector(":scope > .brush"); if (!range) { brush?.classList.add("hidden"); return; } if (!brush) { brush = document.createElement("div"); brush.className = "brush"; patch(brush, '<span class="range-edge start"></span><span class="range-edge end"></span>'); root.appendChild(brush); } const track = root.querySelector(selector); if (!track) return; const box = track.getBoundingClientRect(), rootBox = root.getBoundingClientRect(); if (!box.width) return; const from = Math.min(range.from, range.to), to = Math.max(range.from, range.to); brush.classList.remove("hidden"); brush.style.left = `${box.left - rootBox.left + from / 100 * box.width}px`; brush.style.width = `${(to - from) / 100 * box.width}px`; const start = Number(root.dataset.timelineStart), span = Number(root.dataset.timelineSpan); const clock = (position) => root.dataset.timelineHasTime === "true" && Number.isFinite(start) && Number.isFinite(span) ? fmtClock(start + span * position / 100) : "—"; const startEdge = brush.querySelector(".range-edge.start"), endEdge = brush.querySelector(".range-edge.end"); startEdge?.replaceChildren(clock(from)); endEdge?.replaceChildren(clock(to)); if (startEdge && endEdge) endEdge.classList.toggle("stacked", brush.clientWidth < startEdge.offsetWidth + endEdge.offsetWidth + 6); }
@@ -733,40 +857,54 @@
733
857
  function openScript() { const record = selected()?.record; if (!record?.snapshot?.script) return; patch($("script-full"), numbered(record.snapshot.script)); $("script-title").textContent = `${record.run.workflowName} script`; $("script-bg").classList.remove("hidden"); $("script-modal").classList.remove("hidden"); }
734
858
  function closeScript() { $("script-bg").classList.add("hidden"); $("script-modal").classList.add("hidden"); }
735
859
  function discardOfflineTranscripts() { const liveIds = new Set(state.publishers.map((publisher) => publisher.id)); for (const cache of [state.transcripts, state.transcriptPending, state.transcriptSources, state.transcriptRefresh, state.transcriptRevision, state.transcriptStatus]) for (const key of Object.keys(cache)) if (!liveIds.has(key.split("\t")[0])) delete cache[key]; state.transcriptCacheOrder = state.transcriptCacheOrder.filter((key) => liveIds.has(key.split("\t")[0])); }
736
- function acceptState(value) { if (!value || !Array.isArray(value.publishers)) return; if (value.initial === true && hasAcceptedState) return; if (value.truncated === true && value.publishers.length === 0 && state.publishers.length > 0) return; const shouldAutoSelect = !hasAcceptedState && !state.currentRun && !new URLSearchParams(location.search).has("view"); state.publishers = sharePublishers(state.publishers, value.publishers.filter((publisher) => publisher.connected === true)); discardOfflineTranscripts(); const current = selected(); if (state.currentRun && current) { state.currentPub = current.publisher.id; if (state.pendingTarget && targetKey(state.pendingTarget) === targetKey(current.target)) state.pendingTarget = null; } const keepPendingTarget = state.pendingTarget && currentTarget() && targetKey(state.pendingTarget) === targetKey(currentTarget()); if (state.currentRun && !current && !keepPendingTarget) { selectHome("replace"); hasAcceptedState = true; } if (shouldAutoSelect && allRuns()[0]) { const first = allRuns()[0]; setTarget(first.target); state.currentPub = first.publisher.id; state.currentAgent = first.record.run.agents[0]?.id || null; hasAcceptedState = true; } else if (shouldAutoSelect && allSubagents()[0]) { const first = allSubagents()[0]; setTarget(first.target); state.currentPub = first.publisher.id; state.currentAgent = first.record.id; document.body.dataset.view = "subagent"; $("view-run").classList.add("hidden"); $("view-agent").classList.remove("hidden"); hasAcceptedState = true; } if (state.currentRun) hasAcceptedState = true; scheduleRender(); }
860
+ function acceptState(value) { if (!value || !Array.isArray(value.publishers)) return; if (value.initial === true && hasAcceptedState) return; if (value.truncated === true && value.publishers.length === 0 && state.publishers.length > 0) return; const shouldAutoSelect = !hasAcceptedState && !state.currentRun && !new URLSearchParams(location.search).has("view"); state.publishers = sharePublishers(state.publishers, value.publishers.filter((publisher) => publisher.connected === true).map(runsByCompletion)); discardOfflineTranscripts(); const current = selected(); if (state.currentRun && current) { state.currentPub = current.publisher.id; if (state.pendingTarget && targetKey(state.pendingTarget) === targetKey(current.target)) state.pendingTarget = null; } const keepPendingTarget = state.pendingTarget && currentTarget() && targetKey(state.pendingTarget) === targetKey(currentTarget()); if (state.currentRun && !current && !keepPendingTarget) { selectHome("replace"); hasAcceptedState = true; } if (shouldAutoSelect && allRuns()[0]) { const first = allRuns()[0]; setTarget(first.target); state.currentPub = first.publisher.id; state.currentAgent = first.record.run.agents[0]?.id || null; hasAcceptedState = true; } else if (shouldAutoSelect && allSubagents()[0]) { const first = allSubagents()[0]; setTarget(first.target); state.currentPub = first.publisher.id; state.currentAgent = first.record.id; document.body.dataset.view = "subagent"; $("view-run").classList.add("hidden"); $("view-agent").classList.remove("hidden"); hasAcceptedState = true; } if (state.currentRun) hasAcceptedState = true; scheduleRender(); }
737
861
  function connect() {
738
862
  const generation = ++socketGeneration;
739
863
  const Socket = window.WebSocket;
740
864
  const socket = new Socket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/ws`);
741
865
  state.socket = socket;
742
- socket.addEventListener("open", () => { if (state.socket === socket && generation === socketGeneration) socket.send(JSON.stringify({ type: "ui:attach" })); });
866
+ socket.addEventListener("open", () => { if (state.socket !== socket || generation !== socketGeneration) return; socket.send(JSON.stringify({ type: "ui:attach" })); sendFocus(true); });
743
867
  socket.addEventListener("message", (event) => {
744
868
  if (state.socket !== socket || generation !== socketGeneration) return;
745
869
  try {
746
870
  const message = JSON.parse(event.data);
747
871
  if (message.type === "state") acceptState(message);
748
- if (message.type === "transcript") { const isSubagent = typeof message.subagentId === "string"; const key = isSubagent ? transcriptKey(message.publisherId, message.subagentId) : transcriptKey(message.publisherId, message.runId, message.agentId); const pending = state.transcriptPending[key]; if (!pending || pending.requestId !== message.requestId) return; delete state.transcriptPending[key]; if (pending.source !== state.transcriptSources[key]) { if (document.body.dataset.view === "agent" || document.body.dataset.view === "subagent") scheduleRender(); return; } if (typeof message.publisherId === "string" && (isSubagent || typeof message.runId === "string" && typeof message.agentId === "string")) { const current = selected(); const currentKey = current ? current.target.kind === "subagent" ? transcriptKey(current.publisher.id, current.record.id) : transcriptKey(current.publisher.id, current.record.run.id, state.currentAgent) : ""; const status = message.ok === true ? message.status === "empty" ? "empty" : "available" : ["missing", "failed", "oversized", "disconnected"].includes(message.status) ? message.status : "failed"; if (message.ok === true || message.ok === false || message.status === "empty" || message.status === "missing" || message.status === "failed" || message.status === "oversized" || message.status === "disconnected") cacheTranscript(key, message.ok === true && Array.isArray(message.entries) ? message.entries : [], currentKey === key ? current.record : undefined, message.revision, status); } if (message.ok === false) showToast(message.error || "transcript failed"); if (document.body.dataset.view === "agent" || document.body.dataset.view === "subagent") scheduleRender(); }
872
+ // A stale reply means the transcript moved on after this request; the cached entries stay until the newer revision is fetched.
873
+ if (message.type === "transcript") { const isSubagent = typeof message.subagentId === "string"; const key = isSubagent ? transcriptKey(message.publisherId, message.subagentId) : transcriptKey(message.publisherId, message.runId, message.agentId); const pending = state.transcriptPending[key]; if (!pending || pending.requestId !== message.requestId) return; delete state.transcriptPending[key]; if (message.ok === false && pending.revision !== undefined && message.revision !== pending.revision) return; if (pending.source !== state.transcriptSources[key]) { if (document.body.dataset.view === "agent" || document.body.dataset.view === "subagent") scheduleRender(); return; } if (typeof message.publisherId === "string" && (isSubagent || typeof message.runId === "string" && typeof message.agentId === "string")) { const current = selected(); const currentKey = current ? current.target.kind === "subagent" ? transcriptKey(current.publisher.id, current.record.id) : transcriptKey(current.publisher.id, current.record.run.id, state.currentAgent) : ""; const status = message.ok === true ? message.status === "empty" ? "empty" : "available" : ["missing", "failed", "oversized", "disconnected"].includes(message.status) ? message.status : "failed"; if (message.ok === true || message.ok === false || message.status === "empty" || message.status === "missing" || message.status === "failed" || message.status === "oversized" || message.status === "disconnected") cacheTranscript(key, message.ok === true && Array.isArray(message.entries) ? message.entries : [], currentKey === key ? current.record : undefined, message.revision, status); } if (message.ok === false) showToast(message.error || "transcript failed"); if (document.body.dataset.view === "agent" || document.body.dataset.view === "subagent") scheduleRender(); }
749
874
  if (message.type === "publisher:action-result" || message.type === "action-result") { const pending = typeof message.requestId === "string" ? state.actionTargets.get(message.requestId) : undefined; if (typeof message.requestId === "string") state.actionTargets.delete(message.requestId); const result = message.result; if (message.ok && result && typeof result.shareUrl === "string") { if (pending?.kind === "run") { state.shareUrls[pending.id] = result.shareUrl; scheduleRender(); } showToast("share link ready"); return; } if (message.ok && pending?.kind === "subagent" && result && typeof result.id === "string") { const target = { kind: "subagent", publisherId: pending.publisherId, id: result.id }; state.pendingTarget = target; setTarget(target); state.currentPub = pending.publisherId; state.currentAgent = result.id; state.selectedEvent = null; state.eventsSig = null; renderSidebar(); setView("subagent", "replace"); } showToast(message.ok ? "action accepted" : `action failed: ${message.error}`); }
750
875
  } catch { showToast("invalid Trajectory update"); }
751
876
  });
752
877
  socket.addEventListener("close", () => {
753
878
  if (state.socket !== socket || generation !== socketGeneration) return;
754
879
  state.socket = null;
880
+ lastFocusSent = undefined;
755
881
  state.transcriptPending = {};
756
882
  state.actionTargets.clear();
757
883
  socketReconnect = setTimeout(() => { socketReconnect = undefined; if (generation === socketGeneration) connect(); }, 1000);
758
884
  });
759
885
  }
760
886
  $("script-close").addEventListener("click", closeScript); $("script-bg").addEventListener("click", closeScript); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closeScript(); setAgentRange(null); if (document.body.dataset.view === "run") renderRun(); else renderAgent(); } });
761
- document.querySelector(".app").addEventListener("click", (event) => { const target = event.target.closest("[data-sidebar-group], [data-sidebar-section], [data-copy-share], [data-act], [data-run], [data-subagent], [data-agent], [data-script], [data-pane], [data-toggle]"); if (!target) return; if (target.dataset.copyShare) { const input = target.previousElementSibling; navigator.clipboard?.writeText(target.dataset.copyShare).then(() => showToast("copied"), () => { input?.select(); showToast("press Ctrl+C to copy"); }); return; } if (target.dataset.sidebarGroup !== undefined || target.dataset.sidebarSection !== undefined) { const key = target.dataset.sidebarGroup ?? target.dataset.sidebarSection; if (state.sidebarCollapsed.has(key)) state.sidebarCollapsed.delete(key); else state.sidebarCollapsed.add(key); saveSidebarCollapsed(); renderSidebar(); return; } if (target.dataset.toggle) { if (document.body.dataset.view !== "run") return; const key = target.dataset.toggle === "gantt" ? "ganttCollapsed" : target.dataset.toggle === "agents" ? "agentsCollapsed" : target.dataset.toggle === "logs" ? "logsCollapsed" : ""; if (!key) return; state.runLayout[key] = !state.runLayout[key]; saveRunLayout(); renderRun(); return; } if (target.dataset.act) { if (target.dataset.act === "steer") { const input = $("subagent-steer"); const message = input?.value.trim(); if (!message) return; sendAction("steer", { payload: { message } }); if (input) input.value = ""; return; } const name = target.closest("[data-name]")?.dataset.name; sendAction(target.dataset.act, name ? { name } : {}); return; } if (target.dataset.script) { openScript(); return; } if (target.dataset.pane) { const found = selected(); const agent = found ? found.target.kind === "subagent" ? subagentAgent(found.record) : found.record.run.agents.find((candidate) => candidate.id === state.currentAgent) : undefined; const entries = found && agent ? agentEntries(found.record, agent) : []; if (target.closest("#tool-tabs")) { state.toolPane = target.dataset.pane; renderInspector(entries[state.selectedEvent || 0] ? entryDetails(entries[state.selectedEvent || 0], agent, entries) : undefined, agent, entries); } else { state.sysPane = target.dataset.pane; if (agent) renderSystemPane(agent); } return; } if (target.dataset.run) { selectRun(target.dataset.run); return; } if (target.dataset.subagent) { selectSubagent(target.dataset.subagent); return; } if (target.dataset.agent && document.body.dataset.view === "run") { state.currentAgent = target.dataset.agent; state.selectedEvent = null; setView("agent"); } });
887
+ document.addEventListener("toggle", (event) => { const target = event.target; if (!(target instanceof HTMLDetailsElement)) return; const key = target.dataset.workflowArgs ?? target.dataset.call; const open = target.dataset.workflowArgs !== undefined ? state.workflowArgumentsOpen : target.dataset.call !== undefined ? state.callsOpen : undefined; if (!open) return; if (target.open) open.add(key); else open.delete(key); }, true);
888
+ document.querySelector(".app").addEventListener("click", (event) => { const target = event.target.closest("[data-sidebar-group], [data-sidebar-section], [data-copy-share], [data-act], [data-run], [data-subagent], [data-agent], [data-agent-details], [data-script], [data-pane], [data-toggle]"); if (!target) return; if (target.dataset.copyShare) { const input = target.previousElementSibling; navigator.clipboard?.writeText(target.dataset.copyShare).then(() => showToast("copied"), () => { input?.select(); showToast("press Ctrl+C to copy"); }); return; } if (target.dataset.sidebarGroup !== undefined || target.dataset.sidebarSection !== undefined) { const key = target.dataset.sidebarGroup ?? target.dataset.sidebarSection; if (state.sidebarCollapsed.has(key)) state.sidebarCollapsed.delete(key); else state.sidebarCollapsed.add(key); saveSidebarCollapsed(); renderSidebar(); return; } if (target.dataset.toggle) { if (document.body.dataset.view !== "run") return; const key = target.dataset.toggle === "gantt" ? "ganttCollapsed" : target.dataset.toggle === "agents" ? "agentsCollapsed" : target.dataset.toggle === "calls" ? "callsCollapsed" : target.dataset.toggle === "logs" ? "logsCollapsed" : ""; if (!key) return; state.runLayout[key] = !state.runLayout[key]; saveRunLayout(); renderRun(); return; } if (target.dataset.act) { if (target.dataset.act === "steer") { const input = $("subagent-steer"); const message = input?.value.trim(); if (!message) return; sendAction("steer", { payload: { message } }); if (input) input.value = ""; return; } const name = target.closest("[data-name]")?.dataset.name; sendAction(target.dataset.act, name ? { name } : {}); return; } if (target.dataset.agentDetails !== undefined) { state.inspMode = "agent"; state.inspSig = null; renderAgent(); return; } if (target.dataset.script) { openScript(); return; } if (target.dataset.pane) { const found = selected(); const agent = found ? found.target.kind === "subagent" ? subagentAgent(found.record) : found.record.run.agents.find((candidate) => candidate.id === state.currentAgent) : undefined; const entries = found && agent ? agentEntries(found.record, agent) : []; if (target.closest("#tool-tabs")) { state.toolPane = target.dataset.pane; state.inspMode = "event"; renderInspector(entries[state.selectedEvent || 0] ? entryDetails(entries[state.selectedEvent || 0], agent, entries) : undefined, agent, entries); } else { state.sysPane = target.dataset.pane; state.inspMode = "agent"; if (agent) renderSystemPane(agent); } return; } if (target.dataset.run) { selectRun(target.dataset.run); return; } if (target.dataset.subagent) { selectSubagent(target.dataset.subagent); return; } if (target.dataset.agent && document.body.dataset.view === "run") { state.currentAgent = target.dataset.agent; state.selectedEvent = null; state.inspMode = "event"; setView("agent"); } });
889
+ document.addEventListener("visibilitychange", () => { sendFocus(); if (!document.hidden) scheduleRender(); });
890
+ // Phones show one sheet at a time, so picking something moves to the sheet that shows its result.
891
+ const phone = matchMedia("(max-width: 900px)");
892
+ function showMobilePane(pane) { document.documentElement.dataset.mobilePane = pane; $("mobile-tabs").querySelectorAll("[data-mobile-pane]").forEach((button) => button.classList.toggle("on", button.dataset.mobilePane === pane)); }
893
+ $("mobile-tabs").addEventListener("click", (event) => { const button = event.target.closest("[data-mobile-pane]"); if (button) showMobilePane(button.dataset.mobilePane); });
894
+ document.addEventListener("click", (event) => {
895
+ if (event.target.closest("[data-theme-toggle]")) { toggleTheme(); return; }
896
+ if (!phone.matches) return;
897
+ if (event.target.closest("#sidebar [data-run], #sidebar [data-subagent], #insp [data-agent], #insp [data-run]")) showMobilePane("stage");
898
+ else if (event.target.closest("#events .evt, [data-agent-details]")) showMobilePane("insp");
899
+ });
762
900
  $("q").addEventListener("input", applyEventSearch); $("run-crumb").addEventListener("click", () => document.body.dataset.view === "subagent" ? selectHome("push") : setView("run"));
763
- $("events").addEventListener("click", (event) => { const item = event.target.closest("[data-event]"); if (!item) return; state.selectedEvent = Number(item.dataset.event); renderAgent(); });
901
+ $("events").addEventListener("click", (event) => { const item = event.target.closest("[data-event]"); if (!item) return; state.selectedEvent = Number(item.dataset.event); state.inspMode = "event"; renderAgent(); });
764
902
  $("agent-timeline").addEventListener("click", (event) => { const timeline = $("agent-timeline"); if (timeline.dataset.brushMoved === "true") { timeline.dataset.brushMoved = "false"; return; } const item = event.target.closest("[data-event]") || (event.target.closest(".ticks") ? nearestTimelineEvent(timeline, event.clientX) : undefined); if (!item) return; highlightEvent(Number(item.dataset.event)); });
765
903
  window.addEventListener("popstate", (event) => { const next = event.state || Object.fromEntries(new URL(location.href).searchParams); const view = next.view || "run"; const ref = view === "subagent" ? next.subagent || next.run : next.run; state.currentRun = ref || null; state.currentAgent = next.agent || null; state.currentTarget = next.target || targetFromRef(view === "subagent" ? "subagent" : "run", ref) || null; state.pendingTarget = null; state.eventsSig = null; setView(view, "pop"); });
766
904
  $("reset-agent-range").addEventListener("click", () => setAgentRange(null)); bindTimeCursor($("swim"), ".axis .ticks"); bindTimeCursor($("agent-timeline"), ".axis .ticks", $("events")); bindBrush($("agent-timeline"), ".axis .ticks", setAgentRange); bindSplit($("split-left"), "--left", 180, 520, { direction: "left" }); bindSplit($("split-right"), "--right", 240, 640, { direction: "right" }); bindSplit($("split-swim"), "--swim", 72, maxSwimHeight, { axis: "y", onResize: (height) => { state.runLayout.swimHeight = height; }, onEnd: saveRunLayout });
767
905
  function bindSplit(element, property, min, max, options = {}) { element.addEventListener("mousedown", (event) => { if (event.button !== 0) return; event.preventDefault(); element.classList.add("drag"); const axis = options.axis || "x"; const start = axis === "y" ? event.clientY : event.clientX; const size = parseFloat(getComputedStyle(document.documentElement).getPropertyValue(property)); const move = (next) => { const coordinate = axis === "y" ? next.clientY : next.clientX; const delta = axis === "y" ? coordinate - start : options.direction === "left" ? coordinate - start : start - coordinate; const limit = typeof max === "function" ? max() : max; const value = Math.round(Math.max(min, Math.min(limit, size + delta))); document.documentElement.style.setProperty(property, `${value}px`); options.onResize?.(value); }; const up = () => { window.removeEventListener("mousemove", move); window.removeEventListener("mouseup", up); element.classList.remove("drag"); options.onEnd?.(); }; window.addEventListener("mousemove", move); window.addEventListener("mouseup", up); }); }
768
906
  window.addEventListener("resize", clampSwimHeight);
769
- document.documentElement.dataset.theme = "tty"; renderRunLayout(); const query = new URLSearchParams(location.search); const initialView = query.get("view") || "run"; const initialRef = initialView === "subagent" ? query.get("subagent") || query.get("run") : query.get("run"); state.currentRun = initialRef; state.currentAgent = query.get("agent"); document.body.dataset.view = initialView; state.currentTarget = targetFromRef(initialView === "subagent" ? "subagent" : "run", initialRef) || null; if (initialView === "subagent" && !state.currentAgent) state.currentAgent = state.currentTarget?.id || null; $("view-run").classList.toggle("hidden", document.body.dataset.view !== "run"); $("view-agent").classList.toggle("hidden", document.body.dataset.view === "run"); $("view-agent").classList.toggle("hidden", document.body.dataset.view !== "agent" && document.body.dataset.view !== "subagent"); if (window.__PIEWF_STATIC__) { acceptState(window.__PIEWF_STATIC__); } else { connect(); setInterval(tickClocks, 1000); }
907
+ renderRunLayout(); const query = new URLSearchParams(location.search); const initialView = query.get("view") || "run"; const initialRef = initialView === "subagent" ? query.get("subagent") || query.get("run") : query.get("run"); state.currentRun = initialRef; state.currentAgent = query.get("agent"); document.body.dataset.view = initialView; state.currentTarget = targetFromRef(initialView === "subagent" ? "subagent" : "run", initialRef) || null; if (initialView === "subagent" && !state.currentAgent) state.currentAgent = state.currentTarget?.id || null; $("view-run").classList.toggle("hidden", document.body.dataset.view !== "run"); $("view-agent").classList.toggle("hidden", document.body.dataset.view === "run"); $("view-agent").classList.toggle("hidden", document.body.dataset.view !== "agent" && document.body.dataset.view !== "subagent"); if (window.__PIEWF_STATIC__) { acceptState(window.__PIEWF_STATIC__); } else { connect(); setInterval(tickClocks, 1000); }
770
908
  </script>
771
909
  </body>
772
910
  </html>