@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.
- package/CHANGELOG.md +135 -0
- package/dist/src/agent-execution.js +114 -87
- package/dist/src/background-widget.d.ts +61 -0
- package/dist/src/background-widget.js +23 -16
- package/dist/src/decoders.js +28 -1
- package/dist/src/execution.d.ts +4 -2
- package/dist/src/execution.js +43 -2
- package/dist/src/host-delivery.js +1 -0
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-recovery.d.ts +5 -2
- package/dist/src/host-recovery.js +3 -3
- package/dist/src/host-view.d.ts +21 -1
- package/dist/src/host-view.js +138 -47
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -60
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +1346 -622
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +326 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/settings.d.ts +26 -0
- package/dist/src/settings.js +277 -0
- package/dist/src/trajectory.d.ts +39 -4
- package/dist/src/trajectory.js +82 -5
- package/dist/src/types.d.ts +30 -3
- package/dist/src/types.js +1 -1
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +40 -0
- package/dist/src/validation.d.ts +10 -17
- package/dist/src/validation.js +29 -458
- package/dist/starter/index.js +0 -2
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +0 -2
- package/dist/subagents/index.js +1344 -798
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +49 -29
- package/dist/subagents/src/contracts.js +18 -7
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +15 -7
- package/dist/subagents/src/manager.js +32 -13
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +24 -2
- package/dist/subagents/src/view.js +107 -80
- package/dist/trajectory/assets/index.html +370 -232
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +445 -277
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +370 -232
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +151 -60
- package/dist/trajectory/src/server.js +138 -11
- package/dist/trajectory/src/server.js.map +2 -2
- package/package.json +7 -6
- package/skills/pi-extensible-workflows/SKILL.md +2 -0
- package/src/agent-execution.ts +110 -80
- package/src/background-widget.ts +26 -19
- package/src/decoders.ts +22 -1
- package/src/execution.ts +41 -4
- package/src/host-delivery.ts +1 -0
- package/src/host-navigator.ts +58 -14
- package/src/host-recovery.ts +8 -7
- package/src/host-view.ts +116 -42
- package/src/host.ts +223 -55
- package/src/index.ts +3 -1
- package/src/paths.ts +3 -0
- package/src/registry.ts +3 -2
- package/src/roles.ts +327 -0
- package/src/script-tools.ts +76 -0
- package/src/settings.ts +224 -0
- package/src/trajectory.ts +97 -9
- package/src/types.ts +12 -5
- package/src/utils.ts +36 -1
- package/src/validation.ts +30 -356
- package/starter/review-loop.ts +0 -2
- package/subagents/README.md +3 -3
- package/subagents/src/contracts.ts +22 -7
- package/subagents/src/index.ts +18 -7
- package/subagents/src/manager.ts +31 -13
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +108 -76
- package/trajectory/src/assets/index.html +370 -232
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +124 -47
- 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
|
-
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
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="
|
|
17
|
-
|
|
18
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
42
|
-
.
|
|
43
|
-
.splitbar
|
|
44
|
-
.splitbar:
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
.brand
|
|
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:
|
|
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:
|
|
56
|
-
.pub.on { background: #
|
|
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
|
|
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(--
|
|
62
|
-
.children { margin: 0 0 0 8px; padding: 2px 0 0 8px; border-left: 1px solid
|
|
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:
|
|
64
|
-
.run-item
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
84
|
-
.crumb {
|
|
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:
|
|
89
|
-
.seg button { background: none; border: 0; color: var(--dim); padding: 4px 8px; border-radius:
|
|
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:
|
|
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;
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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:
|
|
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:
|
|
127
|
-
.lane { display: grid; grid-template-columns: 16px 76px 1fr; gap: 8px; align-items: center; height:
|
|
128
|
-
.lane:hover { background:
|
|
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:
|
|
133
|
-
.bar.crumb { background:
|
|
134
|
-
.crumbband { font-size: 11px; color:
|
|
135
|
-
.lane .lab { color: var(--
|
|
136
|
-
.track { position: relative; height:
|
|
137
|
-
.bar { position: absolute; top: 0; height:
|
|
138
|
-
.bar.tool { top:
|
|
139
|
-
.bar.ok { background:
|
|
140
|
-
.bar.spin { background:
|
|
141
|
-
.bar.fail { background: var(--
|
|
142
|
-
.bar.wait { background: var(--
|
|
143
|
-
.bar.paused { background:
|
|
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:
|
|
148
|
-
.workflow-head:hover, .workflow-head.on { background: var(--bg-
|
|
149
|
-
.workflow-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font
|
|
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
|
|
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 {
|
|
174
|
-
.timeline { padding: 6px 12px 8px; border-bottom: 1px solid var(--line); background: linear-gradient(
|
|
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:
|
|
180
|
-
.tick.in { background:
|
|
181
|
-
.tick.model { background:
|
|
182
|
-
.tick.tl { background:
|
|
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:
|
|
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:
|
|
193
|
-
.pill.system { background: #
|
|
194
|
-
.pill.user { background: #
|
|
195
|
-
.pill.context { background: #
|
|
196
|
-
.pill.assistant { background: #
|
|
197
|
-
.pill.tool { background: #
|
|
198
|
-
.pill.skill { background: #
|
|
199
|
-
.pill.issue { background: #
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
.
|
|
227
|
-
.
|
|
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:
|
|
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: #
|
|
233
|
-
.modal { position: fixed; inset: 32px; background:
|
|
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:
|
|
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:
|
|
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-
|
|
245
|
-
.
|
|
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 {
|
|
256
|
-
.pending.issue {
|
|
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:
|
|
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:
|
|
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-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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>☰</b>Runs</button><button type="button" data-mobile-pane="stage" class="on"><b>∾</b>Trace</button><button type="button" data-mobile-pane="insp"><b>✎</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
|
-
|
|
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)}%`; });
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
|
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
|
|
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"
|
|
630
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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) => (
|
|
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 ?
|
|
639
|
-
const toolBars = timings
|
|
640
|
-
|
|
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
|
-
|
|
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)}"
|
|
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
|
|
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
|
-
|
|
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">${
|
|
689
|
-
function
|
|
690
|
-
function
|
|
691
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
function
|
|
726
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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>
|