@katyella/legio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +422 -0
- package/LICENSE +21 -0
- package/README.md +555 -0
- package/agents/builder.md +141 -0
- package/agents/coordinator.md +351 -0
- package/agents/cto.md +196 -0
- package/agents/gateway.md +276 -0
- package/agents/lead.md +281 -0
- package/agents/merger.md +156 -0
- package/agents/monitor.md +212 -0
- package/agents/reviewer.md +142 -0
- package/agents/scout.md +131 -0
- package/agents/supervisor.md +416 -0
- package/bin/legio.mjs +38 -0
- package/package.json +77 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +102 -0
- package/src/agents/hooks-deployer.test.ts +1820 -0
- package/src/agents/hooks-deployer.ts +574 -0
- package/src/agents/identity.test.ts +614 -0
- package/src/agents/identity.ts +385 -0
- package/src/agents/lifecycle.test.ts +202 -0
- package/src/agents/lifecycle.ts +184 -0
- package/src/agents/manifest.test.ts +558 -0
- package/src/agents/manifest.ts +297 -0
- package/src/agents/overlay.test.ts +592 -0
- package/src/agents/overlay.ts +316 -0
- package/src/beads/client.test.ts +210 -0
- package/src/beads/client.ts +227 -0
- package/src/beads/molecules.test.ts +320 -0
- package/src/beads/molecules.ts +209 -0
- package/src/commands/agents.test.ts +325 -0
- package/src/commands/agents.ts +286 -0
- package/src/commands/clean.test.ts +730 -0
- package/src/commands/clean.ts +653 -0
- package/src/commands/completions.test.ts +346 -0
- package/src/commands/completions.ts +950 -0
- package/src/commands/coordinator.test.ts +1524 -0
- package/src/commands/coordinator.ts +880 -0
- package/src/commands/costs.test.ts +1015 -0
- package/src/commands/costs.ts +473 -0
- package/src/commands/dashboard.test.ts +94 -0
- package/src/commands/dashboard.ts +607 -0
- package/src/commands/doctor.test.ts +295 -0
- package/src/commands/doctor.ts +213 -0
- package/src/commands/down.test.ts +308 -0
- package/src/commands/down.ts +124 -0
- package/src/commands/errors.test.ts +648 -0
- package/src/commands/errors.ts +255 -0
- package/src/commands/feed.test.ts +579 -0
- package/src/commands/feed.ts +368 -0
- package/src/commands/gateway.test.ts +698 -0
- package/src/commands/gateway.ts +419 -0
- package/src/commands/group.test.ts +262 -0
- package/src/commands/group.ts +539 -0
- package/src/commands/hooks.test.ts +292 -0
- package/src/commands/hooks.ts +210 -0
- package/src/commands/init.test.ts +211 -0
- package/src/commands/init.ts +622 -0
- package/src/commands/inspect.test.ts +670 -0
- package/src/commands/inspect.ts +455 -0
- package/src/commands/log.test.ts +1556 -0
- package/src/commands/log.ts +752 -0
- package/src/commands/logs.test.ts +379 -0
- package/src/commands/logs.ts +544 -0
- package/src/commands/mail.test.ts +1726 -0
- package/src/commands/mail.ts +926 -0
- package/src/commands/merge.test.ts +676 -0
- package/src/commands/merge.ts +374 -0
- package/src/commands/metrics.test.ts +444 -0
- package/src/commands/metrics.ts +150 -0
- package/src/commands/monitor.test.ts +151 -0
- package/src/commands/monitor.ts +394 -0
- package/src/commands/nudge.test.ts +230 -0
- package/src/commands/nudge.ts +373 -0
- package/src/commands/prime.test.ts +467 -0
- package/src/commands/prime.ts +386 -0
- package/src/commands/replay.test.ts +742 -0
- package/src/commands/replay.ts +367 -0
- package/src/commands/run.test.ts +443 -0
- package/src/commands/run.ts +365 -0
- package/src/commands/server.test.ts +626 -0
- package/src/commands/server.ts +298 -0
- package/src/commands/sling.test.ts +810 -0
- package/src/commands/sling.ts +700 -0
- package/src/commands/spec.test.ts +206 -0
- package/src/commands/spec.ts +171 -0
- package/src/commands/status.test.ts +276 -0
- package/src/commands/status.ts +339 -0
- package/src/commands/stop.test.ts +357 -0
- package/src/commands/stop.ts +119 -0
- package/src/commands/supervisor.test.ts +186 -0
- package/src/commands/supervisor.ts +544 -0
- package/src/commands/trace.test.ts +746 -0
- package/src/commands/trace.ts +332 -0
- package/src/commands/up.test.ts +597 -0
- package/src/commands/up.ts +275 -0
- package/src/commands/watch.test.ts +152 -0
- package/src/commands/watch.ts +238 -0
- package/src/commands/worktree.test.ts +648 -0
- package/src/commands/worktree.ts +266 -0
- package/src/config.test.ts +496 -0
- package/src/config.ts +616 -0
- package/src/doctor/agents.test.ts +448 -0
- package/src/doctor/agents.ts +396 -0
- package/src/doctor/config-check.test.ts +184 -0
- package/src/doctor/config-check.ts +185 -0
- package/src/doctor/consistency.test.ts +645 -0
- package/src/doctor/consistency.ts +294 -0
- package/src/doctor/databases.test.ts +284 -0
- package/src/doctor/databases.ts +211 -0
- package/src/doctor/dependencies.test.ts +150 -0
- package/src/doctor/dependencies.ts +179 -0
- package/src/doctor/logs.test.ts +244 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +210 -0
- package/src/doctor/merge-queue.ts +144 -0
- package/src/doctor/structure.test.ts +285 -0
- package/src/doctor/structure.ts +195 -0
- package/src/doctor/types.ts +37 -0
- package/src/doctor/version.test.ts +130 -0
- package/src/doctor/version.ts +131 -0
- package/src/e2e/chat-flow.test.ts +346 -0
- package/src/e2e/init-sling-lifecycle.test.ts +288 -0
- package/src/errors.test.ts +21 -0
- package/src/errors.ts +246 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +344 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/global-setup.ts +14 -0
- package/src/index.ts +339 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/logging/color.test.ts +118 -0
- package/src/logging/color.ts +71 -0
- package/src/logging/logger.test.ts +812 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +258 -0
- package/src/logging/reporter.ts +109 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/mail/broadcast.test.ts +203 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +873 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +815 -0
- package/src/mail/store.ts +402 -0
- package/src/merge/queue.test.ts +449 -0
- package/src/merge/queue.ts +262 -0
- package/src/merge/resolver.test.ts +1453 -0
- package/src/merge/resolver.ts +759 -0
- package/src/metrics/store.test.ts +1167 -0
- package/src/metrics/store.ts +511 -0
- package/src/metrics/summary.test.ts +397 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +643 -0
- package/src/metrics/transcript.ts +351 -0
- package/src/mulch/client.test.ts +547 -0
- package/src/mulch/client.ts +416 -0
- package/src/server/audit-store.test.ts +384 -0
- package/src/server/audit-store.ts +257 -0
- package/src/server/headless.test.ts +180 -0
- package/src/server/headless.ts +151 -0
- package/src/server/index.test.ts +241 -0
- package/src/server/index.ts +317 -0
- package/src/server/public/app.js +187 -0
- package/src/server/public/apple-touch-icon.png +0 -0
- package/src/server/public/components/agent-badge.js +37 -0
- package/src/server/public/components/data-table.js +114 -0
- package/src/server/public/components/gateway-chat.js +256 -0
- package/src/server/public/components/issue-card.js +96 -0
- package/src/server/public/components/layout.js +88 -0
- package/src/server/public/components/message-bubble.js +120 -0
- package/src/server/public/components/stat-card.js +26 -0
- package/src/server/public/components/terminal-panel.js +140 -0
- package/src/server/public/favicon-16.png +0 -0
- package/src/server/public/favicon-32.png +0 -0
- package/src/server/public/favicon.ico +0 -0
- package/src/server/public/favicon.png +0 -0
- package/src/server/public/index.html +64 -0
- package/src/server/public/lib/api.js +35 -0
- package/src/server/public/lib/markdown.js +8 -0
- package/src/server/public/lib/preact-setup.js +8 -0
- package/src/server/public/lib/state.js +99 -0
- package/src/server/public/lib/utils.js +309 -0
- package/src/server/public/lib/ws.js +79 -0
- package/src/server/public/views/chat.js +983 -0
- package/src/server/public/views/costs.js +692 -0
- package/src/server/public/views/dashboard.js +781 -0
- package/src/server/public/views/gateway-chat.js +622 -0
- package/src/server/public/views/inspect.js +399 -0
- package/src/server/public/views/issues.js +470 -0
- package/src/server/public/views/setup.js +94 -0
- package/src/server/public/views/task-detail.js +422 -0
- package/src/server/routes.test.ts +3816 -0
- package/src/server/routes.ts +1964 -0
- package/src/server/websocket.test.ts +288 -0
- package/src/server/websocket.ts +196 -0
- package/src/sessions/compat.test.ts +109 -0
- package/src/sessions/compat.ts +17 -0
- package/src/sessions/store.test.ts +969 -0
- package/src/sessions/store.ts +480 -0
- package/src/test-helpers.test.ts +97 -0
- package/src/test-helpers.ts +143 -0
- package/src/types.ts +708 -0
- package/src/watchdog/daemon.test.ts +1233 -0
- package/src/watchdog/daemon.ts +533 -0
- package/src/watchdog/health.test.ts +371 -0
- package/src/watchdog/health.ts +248 -0
- package/src/watchdog/triage.test.ts +162 -0
- package/src/watchdog/triage.ts +193 -0
- package/src/worktree/manager.test.ts +444 -0
- package/src/worktree/manager.ts +224 -0
- package/src/worktree/tmux.test.ts +1238 -0
- package/src/worktree/tmux.ts +644 -0
- package/templates/CLAUDE.md.tmpl +89 -0
- package/templates/hooks.json.tmpl +132 -0
- package/templates/overlay.md.tmpl +79 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// views/inspect.js — Per-agent deep inspection view
|
|
2
|
+
// Exports InspectView (Preact component)
|
|
3
|
+
|
|
4
|
+
import { html, useCallback, useEffect, useRef, useState } from "../lib/preact-setup.js";
|
|
5
|
+
|
|
6
|
+
// ── Utility functions ──────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
function formatDuration(ms) {
|
|
9
|
+
if (ms < 1000) return "< 1s";
|
|
10
|
+
const s = Math.floor(ms / 1000);
|
|
11
|
+
const m = Math.floor(s / 60);
|
|
12
|
+
const hh = Math.floor(m / 60);
|
|
13
|
+
if (hh > 0) return `${hh}h ${m % 60}m`;
|
|
14
|
+
if (m > 0) return `${m}m ${s % 60}s`;
|
|
15
|
+
return `${s}s`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function _timeAgo(isoString) {
|
|
19
|
+
if (!isoString) return "";
|
|
20
|
+
const diff = Date.now() - new Date(isoString).getTime();
|
|
21
|
+
if (diff < 0) return "just now";
|
|
22
|
+
const s = Math.floor(diff / 1000);
|
|
23
|
+
if (s < 60) return `${s}s ago`;
|
|
24
|
+
const m = Math.floor(s / 60);
|
|
25
|
+
if (m < 60) return `${m}m ago`;
|
|
26
|
+
const hh = Math.floor(m / 60);
|
|
27
|
+
if (hh < 24) return `${hh}h ago`;
|
|
28
|
+
return `${Math.floor(hh / 24)}d ago`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatNumber(n) {
|
|
32
|
+
if (n == null) return "\u2014";
|
|
33
|
+
return Number(n).toLocaleString();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function truncate(str, maxLen) {
|
|
37
|
+
if (!str) return "";
|
|
38
|
+
return str.length <= maxLen ? str : `${str.slice(0, maxLen - 3)}...`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatTimestamp(iso) {
|
|
42
|
+
if (!iso) return "\u2014";
|
|
43
|
+
const d = new Date(iso);
|
|
44
|
+
return (
|
|
45
|
+
String(d.getHours()).padStart(2, "0") +
|
|
46
|
+
":" +
|
|
47
|
+
String(d.getMinutes()).padStart(2, "0") +
|
|
48
|
+
":" +
|
|
49
|
+
String(d.getSeconds()).padStart(2, "0")
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Strip ANSI escape sequences from terminal output before display
|
|
54
|
+
function stripAnsi(str) {
|
|
55
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: stripping ANSI escape sequences requires matching control chars
|
|
56
|
+
return str.replace(/\x1b\[[0-9;]*[mGKHF]/g, "");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── State badge config ─────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
const stateBadgeClasses = {
|
|
62
|
+
working: "text-green-500 bg-green-500/10",
|
|
63
|
+
booting: "text-yellow-500 bg-yellow-500/10",
|
|
64
|
+
stalled: "text-red-500 bg-red-500/10",
|
|
65
|
+
zombie: "text-gray-500 bg-gray-500/10",
|
|
66
|
+
completed: "text-blue-500 bg-blue-500/10",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ── Preact component ───────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
export function InspectView({ agentName }) {
|
|
72
|
+
// Agent data state
|
|
73
|
+
const [data, setData] = useState(null);
|
|
74
|
+
const [loading, setLoading] = useState(false);
|
|
75
|
+
const [error, setError] = useState(null);
|
|
76
|
+
|
|
77
|
+
// Terminal state
|
|
78
|
+
const [termOutput, setTermOutput] = useState("");
|
|
79
|
+
const [termInput, setTermInput] = useState("");
|
|
80
|
+
const [termSending, setTermSending] = useState(false);
|
|
81
|
+
const [termError, setTermError] = useState("");
|
|
82
|
+
const [termConnected, setTermConnected] = useState(false);
|
|
83
|
+
const termOutputRef = useRef(null);
|
|
84
|
+
const isNearBottomRef = useRef(true);
|
|
85
|
+
const termIntervalRef = useRef(null);
|
|
86
|
+
|
|
87
|
+
// Fetch agent inspect data on mount / agent change
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!agentName) return;
|
|
90
|
+
setLoading(true);
|
|
91
|
+
setError(null);
|
|
92
|
+
setData(null);
|
|
93
|
+
fetch(`/api/agents/${encodeURIComponent(agentName)}/inspect`)
|
|
94
|
+
.then((r) => {
|
|
95
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
96
|
+
return r.json();
|
|
97
|
+
})
|
|
98
|
+
.then((d) => {
|
|
99
|
+
setData(d);
|
|
100
|
+
setLoading(false);
|
|
101
|
+
})
|
|
102
|
+
.catch((err) => {
|
|
103
|
+
setError(String(err));
|
|
104
|
+
setLoading(false);
|
|
105
|
+
});
|
|
106
|
+
}, [agentName]);
|
|
107
|
+
|
|
108
|
+
// Smart scroll: only scroll to bottom when user is near the bottom
|
|
109
|
+
const scrollToBottomIfNear = useCallback(() => {
|
|
110
|
+
const el = termOutputRef.current;
|
|
111
|
+
if (el && isNearBottomRef.current) {
|
|
112
|
+
el.scrollTop = el.scrollHeight;
|
|
113
|
+
}
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
const handleOutputScroll = useCallback(() => {
|
|
117
|
+
const el = termOutputRef.current;
|
|
118
|
+
if (!el) return;
|
|
119
|
+
isNearBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 50;
|
|
120
|
+
}, []);
|
|
121
|
+
|
|
122
|
+
// Fetch terminal capture for this agent
|
|
123
|
+
const fetchTermCapture = useCallback(async () => {
|
|
124
|
+
if (!agentName) return;
|
|
125
|
+
try {
|
|
126
|
+
const res = await fetch(
|
|
127
|
+
`/api/terminal/capture?agent=${encodeURIComponent(agentName)}&lines=80`,
|
|
128
|
+
);
|
|
129
|
+
if (!res.ok) {
|
|
130
|
+
setTermConnected(false);
|
|
131
|
+
setTermError(`Capture failed: HTTP ${res.status}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const d = await res.json();
|
|
135
|
+
setTermConnected(true);
|
|
136
|
+
setTermError("");
|
|
137
|
+
setTermOutput(stripAnsi(d.output || ""));
|
|
138
|
+
requestAnimationFrame(scrollToBottomIfNear);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
setTermConnected(false);
|
|
141
|
+
setTermError(e.message || "Failed to reach server");
|
|
142
|
+
}
|
|
143
|
+
}, [agentName, scrollToBottomIfNear]);
|
|
144
|
+
|
|
145
|
+
// Terminal polling every 3 seconds
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!agentName) return;
|
|
148
|
+
fetchTermCapture();
|
|
149
|
+
termIntervalRef.current = setInterval(fetchTermCapture, 3000);
|
|
150
|
+
return () => {
|
|
151
|
+
if (termIntervalRef.current) clearInterval(termIntervalRef.current);
|
|
152
|
+
};
|
|
153
|
+
}, [fetchTermCapture, agentName]);
|
|
154
|
+
|
|
155
|
+
// Send text to agent's tmux session
|
|
156
|
+
const handleTermSend = useCallback(async () => {
|
|
157
|
+
const text = termInput.trim();
|
|
158
|
+
if (!text || !agentName) return;
|
|
159
|
+
setTermSending(true);
|
|
160
|
+
setTermError("");
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch("/api/terminal/send", {
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: { "Content-Type": "application/json" },
|
|
165
|
+
body: JSON.stringify({ text, agent: agentName }),
|
|
166
|
+
});
|
|
167
|
+
if (!res.ok) {
|
|
168
|
+
const err = await res.json().catch(() => ({}));
|
|
169
|
+
throw new Error(err.error || `Send failed: HTTP ${res.status}`);
|
|
170
|
+
}
|
|
171
|
+
setTermInput("");
|
|
172
|
+
setTimeout(fetchTermCapture, 400);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
setTermError(e.message || "Send failed");
|
|
175
|
+
} finally {
|
|
176
|
+
setTermSending(false);
|
|
177
|
+
}
|
|
178
|
+
}, [termInput, agentName, fetchTermCapture]);
|
|
179
|
+
|
|
180
|
+
const handleTermKeyDown = useCallback(
|
|
181
|
+
(e) => {
|
|
182
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
handleTermSend();
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
[handleTermSend],
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const handleTermClear = useCallback(() => setTermOutput(""), []);
|
|
191
|
+
|
|
192
|
+
if (!agentName) {
|
|
193
|
+
return html`
|
|
194
|
+
<div class="flex items-center justify-center h-64 text-[#999]">
|
|
195
|
+
Select an agent to inspect
|
|
196
|
+
</div>
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (loading) {
|
|
201
|
+
return html`
|
|
202
|
+
<div class="flex items-center justify-center h-64 text-[#999]">
|
|
203
|
+
Loading ${agentName}…
|
|
204
|
+
</div>
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (error) {
|
|
209
|
+
return html`
|
|
210
|
+
<div class="flex items-center justify-center h-64 text-red-500">
|
|
211
|
+
Failed to load agent data: ${error}
|
|
212
|
+
</div>
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!data) return null;
|
|
217
|
+
|
|
218
|
+
const agent = data.session || {};
|
|
219
|
+
const dur = agent.startedAt ? Date.now() - new Date(agent.startedAt).getTime() : 0;
|
|
220
|
+
const token = data.tokenUsage || {};
|
|
221
|
+
const badgeClass = stateBadgeClasses[agent.state] || "text-gray-500 bg-gray-500/10";
|
|
222
|
+
|
|
223
|
+
const statCards = [
|
|
224
|
+
{ label: "Input Tokens", value: formatNumber(token.inputTokens) },
|
|
225
|
+
{ label: "Output Tokens", value: formatNumber(token.outputTokens) },
|
|
226
|
+
{ label: "Cache Read", value: formatNumber(token.cacheReadTokens) },
|
|
227
|
+
{ label: "Cache Created", value: formatNumber(token.cacheCreationTokens) },
|
|
228
|
+
{
|
|
229
|
+
label: "Est. Cost",
|
|
230
|
+
value: token.estimatedCostUsd != null ? `$${token.estimatedCostUsd.toFixed(4)}` : "\u2014",
|
|
231
|
+
},
|
|
232
|
+
{ label: "Model", value: token.modelUsed || "\u2014" },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
const toolStats = [...(data.toolStats || [])].sort((a, b) => (b.count || 0) - (a.count || 0));
|
|
236
|
+
const recentToolCalls = data.recentToolCalls || [];
|
|
237
|
+
|
|
238
|
+
const inputCls =
|
|
239
|
+
"bg-[#1a1a1a] border border-[#2a2a2a] rounded px-2 py-1 text-sm text-[#e5e5e5] placeholder-[#666] outline-none focus:border-[#E64415]";
|
|
240
|
+
|
|
241
|
+
return html`
|
|
242
|
+
<div class="p-4 text-[#e5e5e5]">
|
|
243
|
+
<!-- Header -->
|
|
244
|
+
<div class="flex items-center gap-3 mb-1">
|
|
245
|
+
<h2 class="text-xl font-semibold">${agent.agentName || agentName}</h2>
|
|
246
|
+
<span class=${`text-sm px-2 py-0.5 rounded-sm ${badgeClass}`}>
|
|
247
|
+
${agent.state || ""}
|
|
248
|
+
</span>
|
|
249
|
+
<span class="text-[#999] text-sm">${agent.capability || ""}</span>
|
|
250
|
+
<span class="font-mono text-[#999] text-sm">${agent.beadId || ""}</span>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<!-- Subheader -->
|
|
254
|
+
<div class="text-[#999] text-sm mb-4">
|
|
255
|
+
Branch: ${agent.branchName || "\u2014"} |
|
|
256
|
+
Parent: ${agent.parentAgent || "orchestrator"} |
|
|
257
|
+
Duration: ${formatDuration(dur)}
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<!-- Token stat cards -->
|
|
261
|
+
<div class="grid grid-cols-3 gap-2 mb-6">
|
|
262
|
+
${statCards.map(
|
|
263
|
+
({ label, value }) => html`
|
|
264
|
+
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm p-3">
|
|
265
|
+
<div class="text-[#999] text-xs mb-1">${label}</div>
|
|
266
|
+
<div class="text-[#e5e5e5] font-mono text-sm">${value}</div>
|
|
267
|
+
</div>
|
|
268
|
+
`,
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<!-- Terminal section -->
|
|
273
|
+
<div class="flex items-center justify-between mb-2">
|
|
274
|
+
<h3 class="text-sm font-semibold text-[#999] uppercase tracking-wide">Terminal</h3>
|
|
275
|
+
<div class="flex items-center gap-3">
|
|
276
|
+
<div class="flex items-center gap-1.5">
|
|
277
|
+
<span
|
|
278
|
+
class=${`w-2 h-2 rounded-full ${termConnected ? "bg-green-500" : "bg-[#555]"}`}
|
|
279
|
+
></span>
|
|
280
|
+
<span class=${`text-xs font-mono ${termConnected ? "text-green-400" : "text-[#555]"}`}>
|
|
281
|
+
${termConnected ? "connected" : "disconnected"}
|
|
282
|
+
</span>
|
|
283
|
+
</div>
|
|
284
|
+
<button
|
|
285
|
+
onClick=${handleTermClear}
|
|
286
|
+
class="text-xs text-[#666] hover:text-[#999] bg-transparent border-none cursor-pointer font-mono"
|
|
287
|
+
>
|
|
288
|
+
clear
|
|
289
|
+
</button>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm mb-6">
|
|
293
|
+
<div
|
|
294
|
+
ref=${termOutputRef}
|
|
295
|
+
onScroll=${handleOutputScroll}
|
|
296
|
+
class="max-h-[40vh] overflow-y-auto p-3"
|
|
297
|
+
>
|
|
298
|
+
${
|
|
299
|
+
termOutput
|
|
300
|
+
? html`<pre
|
|
301
|
+
class="text-[#e5e5e5] text-xs leading-relaxed whitespace-pre-wrap break-words m-0 font-mono"
|
|
302
|
+
>${termOutput}</pre>`
|
|
303
|
+
: html`<div class="text-[#444] text-sm font-mono text-center py-4">
|
|
304
|
+
No output
|
|
305
|
+
</div>`
|
|
306
|
+
}
|
|
307
|
+
</div>
|
|
308
|
+
${
|
|
309
|
+
termError
|
|
310
|
+
? html`<div class="px-3 py-1.5 bg-[#1a0a0a] border-t border-red-900">
|
|
311
|
+
<span class="text-xs text-red-400 font-mono">${termError}</span>
|
|
312
|
+
</div>`
|
|
313
|
+
: null
|
|
314
|
+
}
|
|
315
|
+
<div class="border-t border-[#2a2a2a] p-3">
|
|
316
|
+
<div class="flex items-center gap-2">
|
|
317
|
+
<span class="text-[#E64415] font-mono text-sm shrink-0 select-none">$</span>
|
|
318
|
+
<input
|
|
319
|
+
type="text"
|
|
320
|
+
placeholder="Type a command or prompt..."
|
|
321
|
+
value=${termInput}
|
|
322
|
+
onInput=${(e) => setTermInput(e.target.value)}
|
|
323
|
+
onKeyDown=${handleTermKeyDown}
|
|
324
|
+
disabled=${termSending}
|
|
325
|
+
class=${`flex-1 ${inputCls} font-mono disabled:opacity-50`}
|
|
326
|
+
/>
|
|
327
|
+
<button
|
|
328
|
+
onClick=${handleTermSend}
|
|
329
|
+
disabled=${termSending || !termInput.trim()}
|
|
330
|
+
class="bg-[#E64415] hover:bg-[#cc3d12] disabled:opacity-50 text-white text-sm px-3 py-1 rounded cursor-pointer border-none font-mono shrink-0"
|
|
331
|
+
>
|
|
332
|
+
${termSending ? "…" : "Send"}
|
|
333
|
+
</button>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<!-- Tool Stats -->
|
|
339
|
+
<h3 class="text-sm font-semibold text-[#999] uppercase tracking-wide mb-2">Tool Stats</h3>
|
|
340
|
+
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm mb-6 overflow-x-auto">
|
|
341
|
+
<table class="w-full text-sm">
|
|
342
|
+
<thead>
|
|
343
|
+
<tr class="border-b border-[#2a2a2a]">
|
|
344
|
+
<th class="text-left text-[#999] text-xs px-3 py-2 font-medium">Tool</th>
|
|
345
|
+
<th class="text-right text-[#999] text-xs px-3 py-2 font-medium">Calls</th>
|
|
346
|
+
<th class="text-right text-[#999] text-xs px-3 py-2 font-medium">Avg</th>
|
|
347
|
+
<th class="text-right text-[#999] text-xs px-3 py-2 font-medium">Max</th>
|
|
348
|
+
</tr>
|
|
349
|
+
</thead>
|
|
350
|
+
<tbody>
|
|
351
|
+
${
|
|
352
|
+
toolStats.length === 0
|
|
353
|
+
? html`<tr><td colspan="4" class="text-center text-[#999] text-sm px-3 py-4">No tool stats</td></tr>`
|
|
354
|
+
: toolStats.map(
|
|
355
|
+
(ts) => html`
|
|
356
|
+
<tr class="border-b border-[#2a2a2a] last:border-0">
|
|
357
|
+
<td class="px-3 py-2">${ts.toolName || ""}</td>
|
|
358
|
+
<td class="px-3 py-2 text-right font-mono">${ts.count || 0}</td>
|
|
359
|
+
<td class="px-3 py-2 text-right font-mono text-[#999]">
|
|
360
|
+
${ts.avgDurationMs != null ? formatDuration(ts.avgDurationMs) : "\u2014"}
|
|
361
|
+
</td>
|
|
362
|
+
<td class="px-3 py-2 text-right font-mono text-[#999]">
|
|
363
|
+
${ts.maxDurationMs != null ? formatDuration(ts.maxDurationMs) : "\u2014"}
|
|
364
|
+
</td>
|
|
365
|
+
</tr>
|
|
366
|
+
`,
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
</tbody>
|
|
370
|
+
</table>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<!-- Recent Tool Calls timeline -->
|
|
374
|
+
<h3 class="text-sm font-semibold text-[#999] uppercase tracking-wide mb-2">Recent Tool Calls</h3>
|
|
375
|
+
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm mb-6">
|
|
376
|
+
${
|
|
377
|
+
recentToolCalls.length === 0
|
|
378
|
+
? html`<div class="text-center text-[#999] text-sm px-3 py-4">No recent tool calls</div>`
|
|
379
|
+
: recentToolCalls.map(
|
|
380
|
+
(tc) => html`
|
|
381
|
+
<div class="flex items-center gap-3 px-3 py-2 border-b border-[#2a2a2a] last:border-0 text-sm">
|
|
382
|
+
<span class="font-mono text-[#999] text-xs w-20 shrink-0">
|
|
383
|
+
${formatTimestamp(tc.timestamp)}
|
|
384
|
+
</span>
|
|
385
|
+
<span class="font-semibold">${tc.toolName || ""}</span>
|
|
386
|
+
<span class="text-[#999] truncate flex-1">
|
|
387
|
+
${truncate(tc.args || "", 80)}
|
|
388
|
+
</span>
|
|
389
|
+
<span class="font-mono text-[#999] text-xs shrink-0">
|
|
390
|
+
${tc.durationMs != null ? formatDuration(tc.durationMs) : "\u2014"}
|
|
391
|
+
</span>
|
|
392
|
+
</div>
|
|
393
|
+
`,
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
`;
|
|
399
|
+
}
|