zilmate 1.10.1 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/coding.agent.d.ts +8 -0
- package/dist/agents/coding.agent.d.ts.map +1 -1
- package/dist/agents/developer-helper.agent.d.ts +8 -0
- package/dist/agents/developer-helper.agent.d.ts.map +1 -1
- package/dist/agents/docs-research.agent.d.ts +8 -0
- package/dist/agents/docs-research.agent.d.ts.map +1 -1
- package/dist/agents/finance.agent.d.ts +8 -0
- package/dist/agents/finance.agent.d.ts.map +1 -1
- package/dist/agents/manager.d.ts +8 -0
- package/dist/agents/manager.d.ts.map +1 -1
- package/dist/agents/manager.js +20 -0
- package/dist/agents/manager.js.map +1 -1
- package/dist/agents/security.agent.d.ts +8 -0
- package/dist/agents/security.agent.d.ts.map +1 -1
- package/dist/cli/swarm.d.ts +1 -0
- package/dist/cli/swarm.d.ts.map +1 -1
- package/dist/cli/swarm.js +47 -8
- package/dist/cli/swarm.js.map +1 -1
- package/dist/cli/theme.d.ts +0 -24
- package/dist/cli/theme.d.ts.map +1 -1
- package/dist/cli/theme.js +6 -2
- package/dist/cli/theme.js.map +1 -1
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +28 -17
- package/dist/config/env.js.map +1 -1
- package/dist/index.js +84 -5
- package/dist/index.js.map +1 -1
- package/dist/memory/scratchpad.d.ts +2 -0
- package/dist/memory/scratchpad.d.ts.map +1 -1
- package/dist/memory/scratchpad.js +27 -0
- package/dist/memory/scratchpad.js.map +1 -1
- package/dist/observability/traces.d.ts +72 -0
- package/dist/observability/traces.d.ts.map +1 -0
- package/dist/observability/traces.js +1134 -0
- package/dist/observability/traces.js.map +1 -0
- package/dist/runtime/swarm.d.ts +1 -0
- package/dist/runtime/swarm.d.ts.map +1 -1
- package/dist/runtime/swarm.js +28 -10
- package/dist/runtime/swarm.js.map +1 -1
- package/dist/tools/corporate-wiki.tool.d.ts.map +1 -1
- package/dist/tools/corporate-wiki.tool.js +4 -0
- package/dist/tools/corporate-wiki.tool.js.map +1 -1
- package/dist/tools/sandbox-dev.tool.d.ts.map +1 -1
- package/dist/tools/sandbox-dev.tool.js +7 -0
- package/dist/tools/sandbox-dev.tool.js.map +1 -1
- package/dist/tools/scratchpad.tool.d.ts +8 -0
- package/dist/tools/scratchpad.tool.d.ts.map +1 -1
- package/dist/tools/scratchpad.tool.js +40 -1
- package/dist/tools/scratchpad.tool.js.map +1 -1
- package/dist/tools/swarm-ops.tool.d.ts.map +1 -1
- package/dist/tools/swarm-ops.tool.js +3 -0
- package/dist/tools/swarm-ops.tool.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { appendFile, readFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { exec } from 'node:child_process';
|
|
6
|
+
import { workspaceLayout } from '../workspace/paths.js';
|
|
7
|
+
import { theme, getDeptTheme, termWidth } from '../cli/theme.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
export class SwarmTraceTracker {
|
|
10
|
+
static instance;
|
|
11
|
+
storage = new AsyncLocalStorage();
|
|
12
|
+
activeSpans = new Map();
|
|
13
|
+
constructor() { }
|
|
14
|
+
static getInstance() {
|
|
15
|
+
if (!SwarmTraceTracker.instance) {
|
|
16
|
+
SwarmTraceTracker.instance = new SwarmTraceTracker();
|
|
17
|
+
}
|
|
18
|
+
return SwarmTraceTracker.instance;
|
|
19
|
+
}
|
|
20
|
+
getActiveSpanId() {
|
|
21
|
+
return this.storage.getStore();
|
|
22
|
+
}
|
|
23
|
+
async startSpan(config) {
|
|
24
|
+
const spanId = crypto.randomUUID();
|
|
25
|
+
const parentId = this.storage.getStore();
|
|
26
|
+
const span = {
|
|
27
|
+
id: spanId,
|
|
28
|
+
...(parentId !== undefined ? { parentId } : {}),
|
|
29
|
+
sessionId: config.sessionId,
|
|
30
|
+
agentKey: config.agentKey,
|
|
31
|
+
agentName: config.agentName,
|
|
32
|
+
department: config.department,
|
|
33
|
+
task: config.task,
|
|
34
|
+
status: 'running',
|
|
35
|
+
startedAt: Date.now(),
|
|
36
|
+
events: [],
|
|
37
|
+
};
|
|
38
|
+
this.activeSpans.set(spanId, span);
|
|
39
|
+
await this.writeLogLine(span);
|
|
40
|
+
return span;
|
|
41
|
+
}
|
|
42
|
+
async runWithSpan(span, callback) {
|
|
43
|
+
return this.storage.run(span.id, async () => {
|
|
44
|
+
try {
|
|
45
|
+
const result = await callback();
|
|
46
|
+
await this.endSpan(span.id, 'completed');
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
await this.endSpan(span.id, 'failed', error?.message || String(error));
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async endSpan(id, status, error) {
|
|
56
|
+
const span = this.activeSpans.get(id);
|
|
57
|
+
if (span) {
|
|
58
|
+
span.status = status;
|
|
59
|
+
span.endedAt = Date.now();
|
|
60
|
+
span.durationMs = span.endedAt - span.startedAt;
|
|
61
|
+
if (error) {
|
|
62
|
+
span.error = error;
|
|
63
|
+
}
|
|
64
|
+
await this.writeLogLine(span);
|
|
65
|
+
this.activeSpans.delete(id);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async recordEvent(type, label, detail) {
|
|
69
|
+
const activeId = this.storage.getStore();
|
|
70
|
+
if (activeId) {
|
|
71
|
+
const span = this.activeSpans.get(activeId);
|
|
72
|
+
if (span) {
|
|
73
|
+
const event = {
|
|
74
|
+
id: crypto.randomUUID(),
|
|
75
|
+
type,
|
|
76
|
+
label,
|
|
77
|
+
detail,
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
};
|
|
80
|
+
span.events.push(event);
|
|
81
|
+
await this.writeLogLine(span);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async writeLogLine(span) {
|
|
86
|
+
try {
|
|
87
|
+
const logsDir = workspaceLayout().logs;
|
|
88
|
+
await mkdir(logsDir, { recursive: true });
|
|
89
|
+
const file = path.join(logsDir, 'swarm-traces.jsonl');
|
|
90
|
+
// Clone span to avoid any race conditions during async serialization
|
|
91
|
+
const clone = { ...span, events: [...span.events] };
|
|
92
|
+
await appendFile(file, JSON.stringify(clone) + '\n', 'utf8');
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
// Don't crash the run if trace logging fails
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function loadSessionSpans(sessionId) {
|
|
100
|
+
const file = path.join(workspaceLayout().logs, 'swarm-traces.jsonl');
|
|
101
|
+
const spansMap = new Map();
|
|
102
|
+
try {
|
|
103
|
+
const content = await readFile(file, 'utf8');
|
|
104
|
+
const lines = content.split('\n');
|
|
105
|
+
for (const line of lines) {
|
|
106
|
+
if (!line.trim())
|
|
107
|
+
continue;
|
|
108
|
+
try {
|
|
109
|
+
const span = JSON.parse(line);
|
|
110
|
+
if (span.sessionId === sessionId) {
|
|
111
|
+
spansMap.set(span.id, span);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Skip corrupted lines
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Return empty if file doesn't exist
|
|
121
|
+
}
|
|
122
|
+
return Array.from(spansMap.values());
|
|
123
|
+
}
|
|
124
|
+
export function renderTraceTree(spans) {
|
|
125
|
+
const idToSpan = new Map();
|
|
126
|
+
for (const span of spans) {
|
|
127
|
+
idToSpan.set(span.id, span);
|
|
128
|
+
}
|
|
129
|
+
const parentToChildren = new Map();
|
|
130
|
+
const roots = [];
|
|
131
|
+
for (const span of spans) {
|
|
132
|
+
if (span.parentId && idToSpan.has(span.parentId)) {
|
|
133
|
+
if (!parentToChildren.has(span.parentId)) {
|
|
134
|
+
parentToChildren.set(span.parentId, []);
|
|
135
|
+
}
|
|
136
|
+
parentToChildren.get(span.parentId).push(span);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
roots.push(span);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Sort children and roots by start time to maintain chronological order
|
|
143
|
+
roots.sort((a, b) => a.startedAt - b.startedAt);
|
|
144
|
+
for (const children of parentToChildren.values()) {
|
|
145
|
+
children.sort((a, b) => a.startedAt - b.startedAt);
|
|
146
|
+
}
|
|
147
|
+
const lines = [];
|
|
148
|
+
const width = termWidth(120);
|
|
149
|
+
function renderSpanNode(span, prefix, isLast) {
|
|
150
|
+
const deptTheme = getDeptTheme(span.department);
|
|
151
|
+
const color = deptTheme.color;
|
|
152
|
+
const icon = deptTheme.icon;
|
|
153
|
+
const branch = isLast ? '└── ' : '├── ';
|
|
154
|
+
const duration = span.durationMs !== undefined ? ` (${span.durationMs}ms)` : '';
|
|
155
|
+
const statusStr = span.status === 'failed' ? ' [FAILED]' : span.status === 'running' ? ' [RUNNING]' : '';
|
|
156
|
+
const errStr = span.error ? ` - Error: ${span.error}` : '';
|
|
157
|
+
// Calculate maximum available width for text content before ANSI colors
|
|
158
|
+
const visualPrefix = prefix.length + branch.length;
|
|
159
|
+
const rightPart = `${statusStr}${duration}${errStr}`;
|
|
160
|
+
const availableForLabel = width - visualPrefix - rightPart.length - 2; // safety padding
|
|
161
|
+
let labelText = `${icon} ${span.agentName} (${span.department})`;
|
|
162
|
+
if (labelText.length > availableForLabel) {
|
|
163
|
+
labelText = labelText.substring(0, Math.max(10, availableForLabel - 3)) + '...';
|
|
164
|
+
}
|
|
165
|
+
const coloredLabel = color(labelText);
|
|
166
|
+
const coloredStatus = span.status === 'failed'
|
|
167
|
+
? chalk.red(statusStr)
|
|
168
|
+
: span.status === 'running'
|
|
169
|
+
? theme.thinking(statusStr)
|
|
170
|
+
: '';
|
|
171
|
+
const coloredError = span.error ? chalk.red(errStr) : '';
|
|
172
|
+
const headerLine = `${prefix}${branch}${coloredLabel}${coloredStatus}${duration}${coloredError}`;
|
|
173
|
+
lines.push(headerLine);
|
|
174
|
+
// Render task text
|
|
175
|
+
const padding = prefix + (isLast ? ' ' : '│ ');
|
|
176
|
+
const availableForTask = width - padding.length - 6; // "Task: ".length is 5
|
|
177
|
+
let taskText = span.task;
|
|
178
|
+
if (taskText.length > availableForTask) {
|
|
179
|
+
taskText = taskText.substring(0, Math.max(10, availableForTask - 3)) + '...';
|
|
180
|
+
}
|
|
181
|
+
const taskLine = `${padding}${chalk.gray('Task:')} ${taskText}`;
|
|
182
|
+
lines.push(taskLine);
|
|
183
|
+
// Render events
|
|
184
|
+
const nextPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
185
|
+
const totalEvents = span.events.length;
|
|
186
|
+
span.events.forEach((event) => {
|
|
187
|
+
let eventIcon = '⚙️';
|
|
188
|
+
if (event.type === 'wiki_query')
|
|
189
|
+
eventIcon = '🔍';
|
|
190
|
+
if (event.type === 'wiki_publish')
|
|
191
|
+
eventIcon = '📤';
|
|
192
|
+
if (event.type === 'tool_call')
|
|
193
|
+
eventIcon = '⚙️';
|
|
194
|
+
if (event.type === 'collaboration')
|
|
195
|
+
eventIcon = '🤝';
|
|
196
|
+
const labelPart = `✦ [${event.label}] `;
|
|
197
|
+
const availableForDetail = width - nextPrefix.length - labelPart.length - 4; // eventIcon + spaces
|
|
198
|
+
let detailText = event.detail;
|
|
199
|
+
if (detailText.length > availableForDetail) {
|
|
200
|
+
detailText = detailText.substring(0, Math.max(10, availableForDetail - 3)) + '...';
|
|
201
|
+
}
|
|
202
|
+
const eventLine = `${nextPrefix}${chalk.dim(`✦ [${event.label}]`)} ${eventIcon} ${chalk.gray(detailText)}`;
|
|
203
|
+
lines.push(eventLine);
|
|
204
|
+
});
|
|
205
|
+
// Render children
|
|
206
|
+
const children = parentToChildren.get(span.id) || [];
|
|
207
|
+
const totalChildren = children.length;
|
|
208
|
+
children.forEach((child, idx) => {
|
|
209
|
+
const isLastChild = idx === totalChildren - 1;
|
|
210
|
+
renderSpanNode(child, nextPrefix, isLastChild);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
// Draw roots
|
|
214
|
+
roots.forEach((root, idx) => {
|
|
215
|
+
const isLast = idx === roots.length - 1;
|
|
216
|
+
renderSpanNode(root, '', isLast);
|
|
217
|
+
});
|
|
218
|
+
return lines.join('\n');
|
|
219
|
+
}
|
|
220
|
+
export function filterSpans(spans, filters) {
|
|
221
|
+
if (!filters.dept && !filters.agent && !filters.status) {
|
|
222
|
+
return spans;
|
|
223
|
+
}
|
|
224
|
+
const matchedIds = new Set();
|
|
225
|
+
for (const span of spans) {
|
|
226
|
+
let matches = true;
|
|
227
|
+
if (filters.dept && span.department.toLowerCase() !== filters.dept.toLowerCase()) {
|
|
228
|
+
matches = false;
|
|
229
|
+
}
|
|
230
|
+
if (filters.agent && span.agentKey.toLowerCase() !== filters.agent.toLowerCase()) {
|
|
231
|
+
matches = false;
|
|
232
|
+
}
|
|
233
|
+
if (filters.status && span.status.toLowerCase() !== filters.status.toLowerCase()) {
|
|
234
|
+
matches = false;
|
|
235
|
+
}
|
|
236
|
+
if (matches) {
|
|
237
|
+
matchedIds.add(span.id);
|
|
238
|
+
// Retain ancestors to keep tree connectivity
|
|
239
|
+
let curr = span;
|
|
240
|
+
while (curr.parentId) {
|
|
241
|
+
const parent = spans.find((s) => s.id === curr.parentId);
|
|
242
|
+
if (parent) {
|
|
243
|
+
matchedIds.add(parent.id);
|
|
244
|
+
curr = parent;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return spans.filter((s) => matchedIds.has(s.id));
|
|
253
|
+
}
|
|
254
|
+
export function computeSessionStats(spans, sessionId) {
|
|
255
|
+
let minStart = Infinity;
|
|
256
|
+
let maxEnd = -Infinity;
|
|
257
|
+
let totalTools = 0;
|
|
258
|
+
let totalCollabs = 0;
|
|
259
|
+
let totalWikiQueries = 0;
|
|
260
|
+
let totalWikiPublishes = 0;
|
|
261
|
+
const deptMap = new Map();
|
|
262
|
+
for (const span of spans) {
|
|
263
|
+
if (span.startedAt < minStart)
|
|
264
|
+
minStart = span.startedAt;
|
|
265
|
+
const end = span.endedAt || span.startedAt;
|
|
266
|
+
if (end > maxEnd)
|
|
267
|
+
maxEnd = end;
|
|
268
|
+
let tools = 0;
|
|
269
|
+
let collabs = 0;
|
|
270
|
+
let queries = 0;
|
|
271
|
+
let publishes = 0;
|
|
272
|
+
for (const ev of span.events) {
|
|
273
|
+
if (ev.type === 'tool_call') {
|
|
274
|
+
tools++;
|
|
275
|
+
totalTools++;
|
|
276
|
+
}
|
|
277
|
+
else if (ev.type === 'collaboration') {
|
|
278
|
+
collabs++;
|
|
279
|
+
totalCollabs++;
|
|
280
|
+
}
|
|
281
|
+
else if (ev.type === 'wiki_query') {
|
|
282
|
+
queries++;
|
|
283
|
+
totalWikiQueries++;
|
|
284
|
+
}
|
|
285
|
+
else if (ev.type === 'wiki_publish') {
|
|
286
|
+
publishes++;
|
|
287
|
+
totalWikiPublishes++;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const dept = span.department;
|
|
291
|
+
const dur = span.durationMs || (span.endedAt ? span.endedAt - span.startedAt : 0);
|
|
292
|
+
if (!deptMap.has(dept)) {
|
|
293
|
+
deptMap.set(dept, {
|
|
294
|
+
dept,
|
|
295
|
+
spans: 0,
|
|
296
|
+
durationMs: 0,
|
|
297
|
+
tools: 0,
|
|
298
|
+
collabs: 0,
|
|
299
|
+
wikiQueries: 0,
|
|
300
|
+
wikiPublishes: 0,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
const d = deptMap.get(dept);
|
|
304
|
+
d.spans++;
|
|
305
|
+
d.durationMs += dur;
|
|
306
|
+
d.tools += tools;
|
|
307
|
+
d.collabs += collabs;
|
|
308
|
+
d.wikiQueries += queries;
|
|
309
|
+
d.wikiPublishes += publishes;
|
|
310
|
+
}
|
|
311
|
+
const depts = Array.from(deptMap.values()).sort((a, b) => b.durationMs - a.durationMs);
|
|
312
|
+
const totalDurationMs = minStart !== Infinity && maxEnd !== -Infinity ? (maxEnd - minStart) : 0;
|
|
313
|
+
return {
|
|
314
|
+
sessionId,
|
|
315
|
+
totalSpans: spans.length,
|
|
316
|
+
totalDurationMs,
|
|
317
|
+
totalTools,
|
|
318
|
+
totalCollabs,
|
|
319
|
+
totalWikiQueries,
|
|
320
|
+
totalWikiPublishes,
|
|
321
|
+
depts,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
export function printStatsTable(stats) {
|
|
325
|
+
console.log('\n' + chalk.cyan.bold(' 🌌 ZILMATE SWARM SESSION PERFORMANCE PROFILER ') + chalk.gray(`[Session: ${stats.sessionId}]`));
|
|
326
|
+
console.log(chalk.gray('─'.repeat(90)));
|
|
327
|
+
// Header row
|
|
328
|
+
console.log(chalk.bold('Department').padEnd(20) + ' │ ' +
|
|
329
|
+
chalk.bold('Spans').padEnd(8) + ' │ ' +
|
|
330
|
+
chalk.bold('Duration').padEnd(12) + ' │ ' +
|
|
331
|
+
chalk.bold('Tools').padEnd(8) + ' │ ' +
|
|
332
|
+
chalk.bold('Collabs').padEnd(10) + ' │ ' +
|
|
333
|
+
chalk.bold('Wiki Q/P').padEnd(10));
|
|
334
|
+
console.log(chalk.gray('─'.repeat(90)));
|
|
335
|
+
for (const dept of stats.depts) {
|
|
336
|
+
const deptTheme = getDeptTheme(dept.dept);
|
|
337
|
+
const deptColor = deptTheme.color;
|
|
338
|
+
const deptIcon = deptTheme.icon;
|
|
339
|
+
const deptLabel = `${deptIcon} ${dept.dept}`;
|
|
340
|
+
console.log(deptColor(deptLabel.padEnd(20)) + ' │ ' +
|
|
341
|
+
chalk.white(String(dept.spans).padEnd(8)) + ' │ ' +
|
|
342
|
+
chalk.yellow(`${dept.durationMs}ms`.padEnd(12)) + ' │ ' +
|
|
343
|
+
chalk.white(String(dept.tools).padEnd(8)) + ' │ ' +
|
|
344
|
+
chalk.white(String(dept.collabs).padEnd(10)) + ' │ ' +
|
|
345
|
+
chalk.white(`${dept.wikiQueries}/${dept.wikiPublishes}`.padEnd(10)));
|
|
346
|
+
}
|
|
347
|
+
console.log(chalk.gray('─'.repeat(90)));
|
|
348
|
+
// Totals row
|
|
349
|
+
console.log(chalk.bold('TOTAL').padEnd(20) + ' │ ' +
|
|
350
|
+
chalk.bold(String(stats.totalSpans).padEnd(8)) + ' │ ' +
|
|
351
|
+
chalk.bold.yellow(`${stats.totalDurationMs}ms`.padEnd(12)) + ' │ ' +
|
|
352
|
+
chalk.bold(String(stats.totalTools).padEnd(8)) + ' │ ' +
|
|
353
|
+
chalk.bold(String(stats.totalCollabs).padEnd(10)) + ' │ ' +
|
|
354
|
+
chalk.bold(`${stats.totalWikiQueries}/${stats.totalWikiPublishes}`.padEnd(10)));
|
|
355
|
+
console.log(chalk.gray('─'.repeat(90)) + '\n');
|
|
356
|
+
}
|
|
357
|
+
export function openBrowser(urlOrPath) {
|
|
358
|
+
try {
|
|
359
|
+
const cmd = process.platform === 'win32'
|
|
360
|
+
? 'start'
|
|
361
|
+
: process.platform === 'darwin'
|
|
362
|
+
? 'open'
|
|
363
|
+
: 'xdg-open';
|
|
364
|
+
exec(`${cmd} "${urlOrPath}"`);
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
// Fail silently
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export function generateHtmlDashboard(spans, sessionId) {
|
|
371
|
+
const stats = computeSessionStats(spans, sessionId);
|
|
372
|
+
const nowStr = new Date().toLocaleString();
|
|
373
|
+
const totalFailures = spans.filter(s => s.status === 'failed').length;
|
|
374
|
+
const integrityScore = spans.length > 0 ? Math.round(((spans.length - totalFailures) / spans.length) * 100) : 100;
|
|
375
|
+
const integrityColor = integrityScore < 100 ? 'text-rose-400' : 'text-pink-400';
|
|
376
|
+
// Custom HSL glows and SVG icons for each department (lowercase keys for case-insensitive lookup)
|
|
377
|
+
const deptConfigs = {
|
|
378
|
+
strategy: { color: '#0ea5e9', border: 'border-sky-500/20', glow: 'shadow-[0_0_15px_rgba(14,165,233,0.15)]', bg: 'bg-sky-500/10', text: 'text-sky-400', icon: '🎯' },
|
|
379
|
+
engineering: { color: '#f43f5e', border: 'border-rose-500/20', glow: 'shadow-[0_0_15px_rgba(244,63,94,0.15)]', bg: 'bg-rose-500/10', text: 'text-rose-400', icon: '🛠️' },
|
|
380
|
+
development: { color: '#06b6d4', border: 'border-cyan-500/20', glow: 'shadow-[0_0_15px_rgba(6,182,212,0.15)]', bg: 'bg-cyan-500/10', text: 'text-cyan-400', icon: '💻' },
|
|
381
|
+
growth: { color: '#10b981', border: 'border-emerald-500/20', glow: 'shadow-[0_0_15px_rgba(16,185,129,0.15)]', bg: 'bg-emerald-500/10', text: 'text-emerald-400', icon: '📈' },
|
|
382
|
+
operations: { color: '#f59e0b', border: 'border-amber-500/20', glow: 'shadow-[0_0_15px_rgba(245,158,11,0.15)]', bg: 'bg-amber-500/10', text: 'text-amber-400', icon: '⚙️' },
|
|
383
|
+
data: { color: '#8b5cf6', border: 'border-violet-500/20', glow: 'shadow-[0_0_15px_rgba(139,92,246,0.15)]', bg: 'bg-violet-500/10', text: 'text-violet-400', icon: '📊' },
|
|
384
|
+
security: { color: '#14b8a6', border: 'border-teal-500/20', glow: 'shadow-[0_0_15px_rgba(20,184,166,0.15)]', bg: 'bg-teal-500/10', text: 'text-teal-400', icon: '🛡️' },
|
|
385
|
+
revenue: { color: '#ec4899', border: 'border-pink-500/20', glow: 'shadow-[0_0_15px_rgba(236,72,153,0.15)]', bg: 'bg-pink-500/10', text: 'text-pink-400', icon: '💰' },
|
|
386
|
+
general: { color: '#94a3b8', border: 'border-slate-500/20', glow: 'shadow-[0_0_15px_rgba(148,163,184,0.15)]', bg: 'bg-slate-500/10', text: 'text-slate-400', icon: '🌐' }
|
|
387
|
+
};
|
|
388
|
+
return `<!DOCTYPE html>
|
|
389
|
+
<html lang="en">
|
|
390
|
+
<head>
|
|
391
|
+
<meta charset="UTF-8">
|
|
392
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
393
|
+
<title>🌌 ZilMate Swarm Intelligence Dashboard [Session: ${sessionId}]</title>
|
|
394
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
395
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
396
|
+
<script>
|
|
397
|
+
tailwind.config = {
|
|
398
|
+
theme: {
|
|
399
|
+
extend: {
|
|
400
|
+
fontFamily: {
|
|
401
|
+
sans: ['"Plus Jakarta Sans"', 'Inter', 'sans-serif'],
|
|
402
|
+
mono: ['"JetBrains Mono"', 'monospace']
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
</script>
|
|
408
|
+
<style>
|
|
409
|
+
body {
|
|
410
|
+
background: radial-gradient(circle at 50% 0%, #0c0f24 0%, #030712 100%);
|
|
411
|
+
color: #f1f5f9;
|
|
412
|
+
}
|
|
413
|
+
.glass {
|
|
414
|
+
background: rgba(15, 23, 42, 0.45);
|
|
415
|
+
backdrop-filter: blur(12px);
|
|
416
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
417
|
+
}
|
|
418
|
+
.glass-input {
|
|
419
|
+
background: rgba(15, 23, 42, 0.6);
|
|
420
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
421
|
+
}
|
|
422
|
+
.glass-input:focus {
|
|
423
|
+
outline: none;
|
|
424
|
+
border-color: rgba(99, 102, 241, 0.4);
|
|
425
|
+
box-shadow: 0 0 15px rgba(99, 102, 241, 0.15);
|
|
426
|
+
}
|
|
427
|
+
.glow-indigo {
|
|
428
|
+
box-shadow: 0 0 25px rgba(99, 102, 241, 0.15);
|
|
429
|
+
}
|
|
430
|
+
/* Scrollbars */
|
|
431
|
+
::-webkit-scrollbar {
|
|
432
|
+
width: 6px;
|
|
433
|
+
height: 6px;
|
|
434
|
+
}
|
|
435
|
+
::-webkit-scrollbar-track {
|
|
436
|
+
background: rgba(2, 6, 23, 0.3);
|
|
437
|
+
}
|
|
438
|
+
::-webkit-scrollbar-thumb {
|
|
439
|
+
background: rgba(255, 255, 255, 0.1);
|
|
440
|
+
border-radius: 4px;
|
|
441
|
+
}
|
|
442
|
+
::-webkit-scrollbar-thumb:hover {
|
|
443
|
+
background: rgba(255, 255, 255, 0.2);
|
|
444
|
+
}
|
|
445
|
+
.timeline-bar {
|
|
446
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
447
|
+
}
|
|
448
|
+
.timeline-bar:hover {
|
|
449
|
+
filter: brightness(1.2) contrast(1.1);
|
|
450
|
+
}
|
|
451
|
+
</style>
|
|
452
|
+
</head>
|
|
453
|
+
<body class="min-h-screen flex flex-col font-sans">
|
|
454
|
+
<!-- Top Navigation -->
|
|
455
|
+
<header class="glass border-b border-white/5 px-6 py-4 flex items-center justify-between z-10">
|
|
456
|
+
<div class="flex items-center space-x-3">
|
|
457
|
+
<span class="text-2xl">🌌</span>
|
|
458
|
+
<div>
|
|
459
|
+
<h1 class="text-lg font-bold bg-gradient-to-r from-cyan-400 via-indigo-400 to-pink-400 bg-clip-text text-transparent">
|
|
460
|
+
ZilMate Swarm Intelligence Terminal
|
|
461
|
+
</h1>
|
|
462
|
+
<p class="text-xs text-slate-400 font-mono">Session Protocol: <span class="text-indigo-400">${sessionId}</span></p>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
<div class="flex items-center space-x-4">
|
|
466
|
+
<div class="text-right">
|
|
467
|
+
<p class="text-xs text-slate-400 font-mono">Rendered: ${nowStr}</p>
|
|
468
|
+
<p class="text-xs text-emerald-400 flex items-center justify-end font-mono">
|
|
469
|
+
<span class="inline-block w-2 h-2 rounded-full bg-emerald-400 animate-pulse mr-1.5"></span>
|
|
470
|
+
Decentralized Runtime OK
|
|
471
|
+
</p>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
</header>
|
|
475
|
+
|
|
476
|
+
<!-- Metrics Bar (Interactive KPI Filters) -->
|
|
477
|
+
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 px-6 pt-5 pb-1 z-10 shrink-0">
|
|
478
|
+
<!-- Card 1 (Duration - Read-only) -->
|
|
479
|
+
<div class="glass rounded-xl p-4 flex flex-col justify-between glow-indigo border-white/5 transition-all">
|
|
480
|
+
<span class="text-xs text-slate-400 font-semibold tracking-wider uppercase">Session Duration</span>
|
|
481
|
+
<span class="text-2xl font-bold font-mono text-amber-400 mt-2">${(stats.totalDurationMs / 1000).toFixed(2)}s</span>
|
|
482
|
+
</div>
|
|
483
|
+
<!-- Card 2 (Active Specialists - Reset Filter) -->
|
|
484
|
+
<div id="metric-all" onclick="filterByMetric(null)" class="glass rounded-xl p-4 flex flex-col justify-between glow-indigo cursor-pointer border-indigo-500/80 bg-indigo-950/20 shadow-[0_0_20px_rgba(99,102,241,0.25)] transition-all hover:bg-white/5">
|
|
485
|
+
<span class="text-xs text-slate-400 font-semibold tracking-wider uppercase flex justify-between items-center">
|
|
486
|
+
<span>Active Specialists</span>
|
|
487
|
+
<span class="text-[9px] text-slate-500 font-mono uppercase tracking-normal">All Spans</span>
|
|
488
|
+
</span>
|
|
489
|
+
<span class="text-2xl font-bold font-mono text-cyan-400 mt-2">${stats.totalSpans}</span>
|
|
490
|
+
</div>
|
|
491
|
+
<!-- Card 3 (Joint War Rooms - Collab Filter) -->
|
|
492
|
+
<div id="metric-collab" onclick="filterByMetric('collab')" class="glass rounded-xl p-4 flex flex-col justify-between glow-indigo cursor-pointer border-white/5 transition-all hover:bg-white/5">
|
|
493
|
+
<span class="text-xs text-slate-400 font-semibold tracking-wider uppercase flex justify-between items-center">
|
|
494
|
+
<span>P2P Joint War Rooms</span>
|
|
495
|
+
<span class="text-[9px] text-slate-500 font-mono uppercase tracking-normal">Filter Collab</span>
|
|
496
|
+
</span>
|
|
497
|
+
<span class="text-2xl font-bold font-mono text-violet-400 mt-2">${stats.totalCollabs}</span>
|
|
498
|
+
</div>
|
|
499
|
+
<!-- Card 4 (Wiki Actions - Wiki Filter) -->
|
|
500
|
+
<div id="metric-wiki" onclick="filterByMetric('wiki')" class="glass rounded-xl p-4 flex flex-col justify-between glow-indigo cursor-pointer border-white/5 transition-all hover:bg-white/5">
|
|
501
|
+
<span class="text-xs text-slate-400 font-semibold tracking-wider uppercase flex justify-between items-center">
|
|
502
|
+
<span>Wiki Activities</span>
|
|
503
|
+
<span class="text-[9px] text-slate-500 font-mono uppercase tracking-normal">Filter Wiki</span>
|
|
504
|
+
</span>
|
|
505
|
+
<span class="text-2xl font-bold font-mono text-emerald-400 mt-2">${stats.totalWikiQueries} <span class="text-xs text-slate-500">/</span> ${stats.totalWikiPublishes}</span>
|
|
506
|
+
</div>
|
|
507
|
+
<!-- Card 5 (Integrity Score - Failure Filter) -->
|
|
508
|
+
<div id="metric-failed" onclick="filterByMetric('failed')" class="glass rounded-xl p-4 flex flex-col justify-between glow-indigo col-span-2 md:col-span-1 cursor-pointer border-white/5 transition-all hover:bg-white/5">
|
|
509
|
+
<span class="text-xs text-slate-400 font-semibold tracking-wider uppercase flex justify-between items-center">
|
|
510
|
+
<span>System Integrity</span>
|
|
511
|
+
<span class="text-[9px] text-slate-500 font-mono uppercase tracking-normal">Filter Failures</span>
|
|
512
|
+
</span>
|
|
513
|
+
<span class="text-2xl font-bold font-mono ${integrityColor} mt-2">${integrityScore}% <span class="text-xs text-slate-500 font-semibold">(${totalFailures} fail)</span></span>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<!-- Primary Tab Switcher -->
|
|
518
|
+
<div class="px-6 mt-4 flex space-x-1 shrink-0">
|
|
519
|
+
<button onclick="switchTab('traces')" id="tab-traces" class="px-4 py-2 text-sm font-semibold rounded-t-lg border-t border-x border-white/10 bg-slate-900/80 text-white transition-all">
|
|
520
|
+
📊 Swarm Execution Traces & Timeline
|
|
521
|
+
</button>
|
|
522
|
+
<button onclick="switchTab('intelligence')" id="tab-intelligence" class="px-4 py-2 text-sm font-semibold rounded-t-lg border border-white/5 text-slate-400 hover:text-white transition-all">
|
|
523
|
+
📚 Corporate Knowledge & Volatile Scratchpad
|
|
524
|
+
</button>
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
<!-- Main View Area -->
|
|
528
|
+
<main class="flex-grow px-6 pb-6 flex flex-col">
|
|
529
|
+
<!-- TAB 1: TRACES & TIMELINE -->
|
|
530
|
+
<div id="view-traces" class="flex flex-col space-y-4">
|
|
531
|
+
<!-- Chronological Gantt Timeline -->
|
|
532
|
+
<div class="glass rounded-xl p-4 flex flex-col">
|
|
533
|
+
<h3 class="text-xs font-bold uppercase tracking-wider text-slate-400 mb-2 flex items-center">
|
|
534
|
+
🕒 Chronological Execution Waterfall & grid
|
|
535
|
+
</h3>
|
|
536
|
+
<div class="flex-1 pr-1 space-y-1.5" id="gantt-chart-container">
|
|
537
|
+
<!-- Populated by JS -->
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<!-- Split Tree and Details panel -->
|
|
542
|
+
<div class="grid grid-cols-1 lg:grid-cols-12 gap-4">
|
|
543
|
+
<!-- Left: Trace Tree Node Directory -->
|
|
544
|
+
<div class="lg:col-span-5 glass rounded-xl p-4 flex flex-col">
|
|
545
|
+
<div class="mb-3 shrink-0 flex items-center space-x-2">
|
|
546
|
+
<input type="text" id="tree-search" oninput="filterTree()" placeholder="Search specialists, tasks, or tools..." class="flex-1 glass-input px-3 py-1.5 rounded-lg text-sm text-slate-200">
|
|
547
|
+
<button onclick="clearSearch()" class="text-xs text-slate-400 hover:text-white font-semibold">Reset</button>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="text-sm" id="tree-container">
|
|
550
|
+
<!-- Populated by JS -->
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
<!-- Right: Telemetry Inspector Card -->
|
|
555
|
+
<div class="lg:col-span-7 glass rounded-xl p-6 flex flex-col glow-indigo">
|
|
556
|
+
<div id="inspector-placeholder" class="flex-grow flex flex-col items-center justify-center text-slate-500 py-12">
|
|
557
|
+
<span class="text-4xl mb-4">🕵️♂️</span>
|
|
558
|
+
<p class="font-medium">No specialist span selected</p>
|
|
559
|
+
<p class="text-xs mt-1">Select an agent or execution span from the directory tree to inspect its core telemetry</p>
|
|
560
|
+
</div>
|
|
561
|
+
<div id="inspector-panel" class="hidden flex-col">
|
|
562
|
+
<!-- Header -->
|
|
563
|
+
<div class="border-b border-white/5 pb-4 shrink-0 flex justify-between items-start">
|
|
564
|
+
<div>
|
|
565
|
+
<div class="flex items-center space-x-2">
|
|
566
|
+
<span id="inspect-dept-badge" class="px-2 py-0.5 rounded text-xs font-mono font-bold bg-white/5"></span>
|
|
567
|
+
<span id="inspect-status-badge" class="px-2 py-0.5 rounded text-xs font-mono font-bold bg-white/5"></span>
|
|
568
|
+
</div>
|
|
569
|
+
<h2 id="inspect-agent-name" class="text-xl font-bold mt-1.5"></h2>
|
|
570
|
+
</div>
|
|
571
|
+
<div class="text-right font-mono text-sm">
|
|
572
|
+
<p id="inspect-duration" class="text-amber-400 font-bold"></p>
|
|
573
|
+
<p id="inspect-timings" class="text-xs text-slate-500 mt-1"></p>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<!-- Scrollable Content -->
|
|
578
|
+
<div class="py-4 space-y-5">
|
|
579
|
+
<!-- Task Section -->
|
|
580
|
+
<div>
|
|
581
|
+
<h4 class="text-xs font-bold uppercase tracking-wider text-slate-400 mb-2">🎯 Objective / Task Description</h4>
|
|
582
|
+
<div id="inspect-task" class="p-3 bg-slate-950/60 rounded-lg text-sm text-slate-300 font-mono border border-white/5 whitespace-pre-wrap"></div>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<!-- Events Section -->
|
|
586
|
+
<div>
|
|
587
|
+
<h4 class="text-xs font-bold uppercase tracking-wider text-slate-400 mb-2">📋 Intercepted Telemetry Feed</h4>
|
|
588
|
+
<div id="inspect-events" class="space-y-3">
|
|
589
|
+
<!-- Populated by JS -->
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<!-- Raw Payload Drawer Section -->
|
|
594
|
+
<div class="border-t border-white/5 pt-4">
|
|
595
|
+
<div class="flex justify-between items-center mb-2">
|
|
596
|
+
<h4 class="text-xs font-bold uppercase tracking-wider text-slate-400">🕵️♂️ Raw Telemetry JSON Payload</h4>
|
|
597
|
+
<button onclick="copyRawTelemetry()" class="px-2.5 py-1 text-xs font-semibold bg-white/5 text-slate-300 hover:text-white hover:bg-white/10 border border-white/10 rounded transition-all flex items-center space-x-1.5">
|
|
598
|
+
<span id="copy-btn-text">📋 Copy JSON</span>
|
|
599
|
+
</button>
|
|
600
|
+
</div>
|
|
601
|
+
<pre id="inspect-raw-json" class="p-3 bg-slate-950/80 rounded-lg text-xs text-indigo-300 font-mono border border-white/5 overflow-x-auto"></pre>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
<!-- TAB 2: CORPORATE KNOWLEDGE & SCRATCHPAD -->
|
|
610
|
+
<div id="view-intelligence" class="hidden grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
611
|
+
<!-- Blackboard (Corporate Wiki) facts -->
|
|
612
|
+
<div class="glass rounded-xl p-5 flex flex-col">
|
|
613
|
+
<h3 class="text-sm font-bold uppercase tracking-wider text-slate-400 mb-3 flex items-center">
|
|
614
|
+
📚 Shared Corporate Blackboard (Wiki Activity Logs)
|
|
615
|
+
</h3>
|
|
616
|
+
<div class="space-y-3" id="wiki-logs-container">
|
|
617
|
+
<!-- Populated by JS -->
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
|
|
621
|
+
<!-- Shared Multi-Agent Scratchpad variables -->
|
|
622
|
+
<div class="glass rounded-xl p-5 flex flex-col">
|
|
623
|
+
<h3 class="text-sm font-bold uppercase tracking-wider text-slate-400 mb-3 flex items-center">
|
|
624
|
+
💾 Shared Multi-Agent Scratchpad (Active Variable Registry)
|
|
625
|
+
</h3>
|
|
626
|
+
<div class="space-y-3" id="scratchpad-vars-container">
|
|
627
|
+
<!-- Populated by JS -->
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
630
|
+
</div>
|
|
631
|
+
</main>
|
|
632
|
+
|
|
633
|
+
<!-- Serialized Data Injected by Compiler -->
|
|
634
|
+
<script>
|
|
635
|
+
const spans = ${JSON.stringify(spans)};
|
|
636
|
+
const deptConfigs = ${JSON.stringify(deptConfigs)};
|
|
637
|
+
let activeSpanId = null;
|
|
638
|
+
let activeMetricFilter = null; // null, 'collab', 'wiki', 'failed'
|
|
639
|
+
const collapsedNodes = new Set();
|
|
640
|
+
|
|
641
|
+
// Build hierarchical tree structures
|
|
642
|
+
const idToSpan = new Map();
|
|
643
|
+
for (const span of spans) {
|
|
644
|
+
idToSpan.set(span.id, span);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const parentToChildren = new Map();
|
|
648
|
+
const roots = [];
|
|
649
|
+
|
|
650
|
+
for (const span of spans) {
|
|
651
|
+
if (span.parentId && idToSpan.has(span.parentId)) {
|
|
652
|
+
if (!parentToChildren.has(span.parentId)) {
|
|
653
|
+
parentToChildren.set(span.parentId, []);
|
|
654
|
+
}
|
|
655
|
+
parentToChildren.get(span.parentId).push(span);
|
|
656
|
+
} else {
|
|
657
|
+
roots.push(span);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
roots.sort((a, b) => a.startedAt - b.startedAt);
|
|
662
|
+
for (const children of parentToChildren.values()) {
|
|
663
|
+
children.sort((a, b) => a.startedAt - b.startedAt);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Tab switcher
|
|
667
|
+
function switchTab(tab) {
|
|
668
|
+
const isTraces = tab === 'traces';
|
|
669
|
+
document.getElementById('view-traces').classList.toggle('hidden', !isTraces);
|
|
670
|
+
document.getElementById('view-intelligence').classList.toggle('hidden', isTraces);
|
|
671
|
+
|
|
672
|
+
const tabTraces = document.getElementById('tab-traces');
|
|
673
|
+
const tabIntel = document.getElementById('tab-intelligence');
|
|
674
|
+
|
|
675
|
+
if (isTraces) {
|
|
676
|
+
tabTraces.className = "px-4 py-2 text-sm font-semibold rounded-t-lg border-t border-x border-white/10 bg-slate-900/80 text-white transition-all";
|
|
677
|
+
tabIntel.className = "px-4 py-2 text-sm font-semibold rounded-t-lg border border-white/5 text-slate-400 hover:text-white transition-all";
|
|
678
|
+
} else {
|
|
679
|
+
tabIntel.className = "px-4 py-2 text-sm font-semibold rounded-t-lg border-t border-x border-white/10 bg-slate-900/80 text-white transition-all";
|
|
680
|
+
tabTraces.className = "px-4 py-2 text-sm font-semibold rounded-t-lg border border-white/5 text-slate-400 hover:text-white transition-all";
|
|
681
|
+
renderIntelLogs();
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Build chronological timeline waterfall with Ruler and Guides
|
|
686
|
+
function renderGanttTimeline() {
|
|
687
|
+
const container = document.getElementById('gantt-chart-container');
|
|
688
|
+
if (spans.length === 0) {
|
|
689
|
+
container.innerHTML = '<p class="text-xs text-slate-500 py-4">No spans recorded.</p>';
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
let minStart = Infinity;
|
|
694
|
+
let maxEnd = -Infinity;
|
|
695
|
+
for (const s of spans) {
|
|
696
|
+
if (s.startedAt < minStart) minStart = s.startedAt;
|
|
697
|
+
const end = s.endedAt || s.startedAt;
|
|
698
|
+
if (end > maxEnd) maxEnd = end;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const totalSpan = maxEnd - minStart || 1;
|
|
702
|
+
let html = '';
|
|
703
|
+
|
|
704
|
+
// Render the Ruler ticks (0.00s to totals)
|
|
705
|
+
html += \`
|
|
706
|
+
<div class="flex items-center space-x-3 text-xs mb-2 border-b border-white/5 pb-1 shrink-0 relative">
|
|
707
|
+
<span class="w-40 shrink-0 text-slate-400 font-semibold uppercase tracking-wider text-[10px] font-mono">Specialist Timeline</span>
|
|
708
|
+
<div class="flex-grow relative h-4 flex justify-between text-[10px] text-slate-500 font-mono">
|
|
709
|
+
<span class="absolute left-0">0.00s</span>
|
|
710
|
+
<span class="absolute" style="left: 25%; transform: translateX(-50%);">\${(totalSpan * 0.25 / 1000).toFixed(2)}s</span>
|
|
711
|
+
<span class="absolute" style="left: 50%; transform: translateX(-50%);">\${(totalSpan * 0.50 / 1000).toFixed(2)}s</span>
|
|
712
|
+
<span class="absolute" style="left: 75%; transform: translateX(-50%);">\${(totalSpan * 0.75 / 1000).toFixed(2)}s</span>
|
|
713
|
+
<span class="absolute right-0">\${(totalSpan / 1000).toFixed(2)}s</span>
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
716
|
+
\`;
|
|
717
|
+
|
|
718
|
+
for (const s of spans) {
|
|
719
|
+
const config = deptConfigs[s.department.toLowerCase()] || deptConfigs.general;
|
|
720
|
+
const offsetPct = ((s.startedAt - minStart) / totalSpan * 100).toFixed(2);
|
|
721
|
+
const dur = s.durationMs || ((s.endedAt || s.startedAt) - s.startedAt);
|
|
722
|
+
const widthPct = Math.max(0.8, (dur / totalSpan * 100)).toFixed(2);
|
|
723
|
+
|
|
724
|
+
const pulseAnim = s.status === 'running' ? 'animate-pulse' : '';
|
|
725
|
+
|
|
726
|
+
html += \`
|
|
727
|
+
<div class="flex items-center space-x-3 text-xs relative">
|
|
728
|
+
<span class="w-40 truncate text-slate-300 font-mono flex items-center space-x-1 shrink-0">
|
|
729
|
+
<span>\${config.icon}</span>
|
|
730
|
+
<span class="font-bold cursor-pointer hover:text-white" onclick="selectSpan('\${s.id}')">\${s.agentName}</span>
|
|
731
|
+
</span>
|
|
732
|
+
<div class="flex-grow bg-white/5 h-4.5 rounded-lg overflow-hidden relative">
|
|
733
|
+
<!-- Grid guides -->
|
|
734
|
+
<div class="absolute inset-y-0 border-l border-dashed border-white/[0.04] pointer-events-none" style="left: 25%;"></div>
|
|
735
|
+
<div class="absolute inset-y-0 border-l border-dashed border-white/[0.04] pointer-events-none" style="left: 50%;"></div>
|
|
736
|
+
<div class="absolute inset-y-0 border-l border-dashed border-white/[0.04] pointer-events-none" style="left: 75%;"></div>
|
|
737
|
+
|
|
738
|
+
<!-- Bar -->
|
|
739
|
+
<div onclick="selectSpan('\${s.id}')" class="timeline-bar absolute h-full rounded \${config.bg} \${pulseAnim} flex items-center px-1.5 cursor-pointer z-10"
|
|
740
|
+
style="left: \${offsetPct}%; width: \${widthPct}%; border-left: 2px solid \${config.color};">
|
|
741
|
+
<span class="text-[9px] font-bold font-mono truncate text-white">\${dur}ms</span>
|
|
742
|
+
</div>
|
|
743
|
+
</div>
|
|
744
|
+
</div>
|
|
745
|
+
\`;
|
|
746
|
+
}
|
|
747
|
+
container.innerHTML = html;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Toggle collapsible tree node
|
|
751
|
+
function toggleNode(spanId, event) {
|
|
752
|
+
if (event) {
|
|
753
|
+
event.stopPropagation();
|
|
754
|
+
}
|
|
755
|
+
if (collapsedNodes.has(spanId)) {
|
|
756
|
+
collapsedNodes.delete(spanId);
|
|
757
|
+
} else {
|
|
758
|
+
collapsedNodes.add(spanId);
|
|
759
|
+
}
|
|
760
|
+
renderTree(document.getElementById('tree-search').value);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// KPI Metric Filtering logic
|
|
764
|
+
function filterByMetric(type) {
|
|
765
|
+
activeMetricFilter = type;
|
|
766
|
+
|
|
767
|
+
const cards = {
|
|
768
|
+
all: document.getElementById('metric-all'),
|
|
769
|
+
collab: document.getElementById('metric-collab'),
|
|
770
|
+
wiki: document.getElementById('metric-wiki'),
|
|
771
|
+
failed: document.getElementById('metric-failed')
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
Object.keys(cards).forEach(key => {
|
|
775
|
+
const card = cards[key];
|
|
776
|
+
if (!card) return;
|
|
777
|
+
if ((type === null && key === 'all') || (type === key)) {
|
|
778
|
+
card.classList.add('border-indigo-500/80', 'bg-indigo-950/20', 'shadow-[0_0_20px_rgba(99,102,241,0.25)]');
|
|
779
|
+
card.classList.remove('border-white/5');
|
|
780
|
+
} else {
|
|
781
|
+
card.classList.remove('border-indigo-500/80', 'bg-indigo-950/20', 'shadow-[0_0_20px_rgba(99,102,241,0.25)]');
|
|
782
|
+
card.classList.add('border-white/5');
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
renderTree(document.getElementById('tree-search').value);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Build hierarchical tree UI
|
|
790
|
+
function renderTree(searchQuery = '') {
|
|
791
|
+
const container = document.getElementById('tree-container');
|
|
792
|
+
const query = searchQuery.trim().toLowerCase();
|
|
793
|
+
|
|
794
|
+
function drawNode(span, depth = 0) {
|
|
795
|
+
const config = deptConfigs[span.department.toLowerCase()] || deptConfigs.general;
|
|
796
|
+
|
|
797
|
+
let queryMatches = !query ||
|
|
798
|
+
span.agentName.toLowerCase().includes(query) ||
|
|
799
|
+
span.task.toLowerCase().includes(query) ||
|
|
800
|
+
span.events.some(e => e.label.toLowerCase().includes(query) || e.detail.toLowerCase().includes(query));
|
|
801
|
+
|
|
802
|
+
let metricMatches = true;
|
|
803
|
+
if (activeMetricFilter === 'collab') {
|
|
804
|
+
metricMatches = span.events.some(e => e.type === 'collaboration');
|
|
805
|
+
} else if (activeMetricFilter === 'wiki') {
|
|
806
|
+
metricMatches = span.events.some(e => e.type === 'wiki_query' || e.type === 'wiki_publish');
|
|
807
|
+
} else if (activeMetricFilter === 'failed') {
|
|
808
|
+
metricMatches = span.status === 'failed';
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const isMatched = queryMatches && metricMatches;
|
|
812
|
+
|
|
813
|
+
// Skip branch if none of the descendents or parents match
|
|
814
|
+
if ((query || activeMetricFilter) && !isMatched) {
|
|
815
|
+
const children = parentToChildren.get(span.id) || [];
|
|
816
|
+
const childMatches = children.some(c => hasMatchingDescendent(c, query, activeMetricFilter));
|
|
817
|
+
if (!childMatches) return '';
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const indentPx = depth * 16;
|
|
821
|
+
const isSelected = span.id === activeSpanId;
|
|
822
|
+
const selectedClass = isSelected ? 'bg-indigo-600/20 border-indigo-500 shadow-[0_0_15px_rgba(99,102,241,0.15)]' : 'border-white/5 hover:bg-white/5';
|
|
823
|
+
|
|
824
|
+
const statusIcon = span.status === 'completed'
|
|
825
|
+
? '<span class="text-emerald-400">●</span>'
|
|
826
|
+
: span.status === 'failed'
|
|
827
|
+
? '<span class="text-rose-500">●</span>'
|
|
828
|
+
: '<span class="text-amber-400 animate-ping">●</span>';
|
|
829
|
+
|
|
830
|
+
const children = parentToChildren.get(span.id) || [];
|
|
831
|
+
const hasChildren = children.length > 0;
|
|
832
|
+
const isCollapsed = collapsedNodes.has(span.id);
|
|
833
|
+
|
|
834
|
+
let chevron = '';
|
|
835
|
+
if (hasChildren) {
|
|
836
|
+
const rotationClass = isCollapsed ? 'rotate-[-90deg]' : '';
|
|
837
|
+
chevron = \`
|
|
838
|
+
<span onclick="toggleNode('\${span.id}', event)" class="mr-1.5 text-[10px] text-slate-500 hover:text-white transition-transform duration-200 cursor-pointer inline-block \${rotationClass}">
|
|
839
|
+
▼
|
|
840
|
+
</span>
|
|
841
|
+
\`;
|
|
842
|
+
} else {
|
|
843
|
+
chevron = '<span class="w-4 inline-block"></span>';
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
let html = \`
|
|
847
|
+
<div onclick="selectSpan('\${span.id}')" class="flex items-center justify-between p-2.5 rounded-lg border \${selectedClass} mb-1.5 cursor-pointer transition-all shrink-0" style="margin-left: \${indentPx}px;">
|
|
848
|
+
<div class="flex items-center space-x-2.5 truncate">
|
|
849
|
+
\${chevron}
|
|
850
|
+
<span>\${config.icon}</span>
|
|
851
|
+
<div class="truncate">
|
|
852
|
+
<p class="font-semibold text-white flex items-center">
|
|
853
|
+
\${span.agentName}
|
|
854
|
+
<span class="text-[10px] font-semibold text-slate-400 ml-1.5 font-mono opacity-80">\${span.department}</span>
|
|
855
|
+
</p>
|
|
856
|
+
<p class="text-xs text-slate-400 truncate mt-0.5 font-mono">\${span.task}</p>
|
|
857
|
+
</div>
|
|
858
|
+
</div>
|
|
859
|
+
<div class="flex items-center space-x-2 shrink-0 font-mono text-xs">
|
|
860
|
+
<span class="text-slate-400">\${span.durationMs !== undefined ? span.durationMs + 'ms' : 'active'}</span>
|
|
861
|
+
\${statusIcon}
|
|
862
|
+
</div>
|
|
863
|
+
</div>
|
|
864
|
+
\`;
|
|
865
|
+
|
|
866
|
+
// Auto expand if there is search query or metric filter active
|
|
867
|
+
const showChildren = !isCollapsed || !!query || !!activeMetricFilter;
|
|
868
|
+
const childrenClass = showChildren ? '' : 'hidden';
|
|
869
|
+
|
|
870
|
+
if (hasChildren) {
|
|
871
|
+
html += \`<div class="\${childrenClass}">\`;
|
|
872
|
+
for (const child of children) {
|
|
873
|
+
html += drawNode(child, depth + 1);
|
|
874
|
+
}
|
|
875
|
+
html += \`</div>\`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return html;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function hasMatchingDescendent(span, query, metricFilter) {
|
|
882
|
+
let qMatches = !query ||
|
|
883
|
+
span.agentName.toLowerCase().includes(query) ||
|
|
884
|
+
span.task.toLowerCase().includes(query) ||
|
|
885
|
+
span.events.some(e => e.label.toLowerCase().includes(query) || e.detail.toLowerCase().includes(query));
|
|
886
|
+
|
|
887
|
+
let mMatches = true;
|
|
888
|
+
if (metricFilter === 'collab') {
|
|
889
|
+
mMatches = span.events.some(e => e.type === 'collaboration');
|
|
890
|
+
} else if (metricFilter === 'wiki') {
|
|
891
|
+
mMatches = span.events.some(e => e.type === 'wiki_query' || e.type === 'wiki_publish');
|
|
892
|
+
} else if (metricFilter === 'failed') {
|
|
893
|
+
mMatches = span.status === 'failed';
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (qMatches && mMatches) {
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
const children = parentToChildren.get(span.id) || [];
|
|
900
|
+
return children.some(c => hasMatchingDescendent(c, query, metricFilter));
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
let treeHtml = '';
|
|
904
|
+
for (const root of roots) {
|
|
905
|
+
treeHtml += drawNode(root, 0);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (!treeHtml) {
|
|
909
|
+
treeHtml = '<div class="text-center text-slate-500 py-12">No matching specialists found.</div>';
|
|
910
|
+
}
|
|
911
|
+
container.innerHTML = treeHtml;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function selectSpan(id) {
|
|
915
|
+
activeSpanId = id;
|
|
916
|
+
renderTree(document.getElementById('tree-search').value);
|
|
917
|
+
|
|
918
|
+
const span = idToSpan.get(id);
|
|
919
|
+
if (!span) return;
|
|
920
|
+
|
|
921
|
+
document.getElementById('inspector-placeholder').classList.add('hidden');
|
|
922
|
+
const panel = document.getElementById('inspector-panel');
|
|
923
|
+
panel.classList.remove('hidden');
|
|
924
|
+
panel.classList.add('flex');
|
|
925
|
+
|
|
926
|
+
// Department & Status badges
|
|
927
|
+
const config = deptConfigs[span.department.toLowerCase()] || deptConfigs.general;
|
|
928
|
+
|
|
929
|
+
const deptBadge = document.getElementById('inspect-dept-badge');
|
|
930
|
+
deptBadge.className = \`px-2.5 py-0.5 rounded text-xs font-mono font-bold \${config.bg} \${config.text}\`;
|
|
931
|
+
deptBadge.innerHTML = \`\${config.icon} \${span.department.toUpperCase()}\`;
|
|
932
|
+
|
|
933
|
+
const statusBadge = document.getElementById('inspect-status-badge');
|
|
934
|
+
if (span.status === 'completed') {
|
|
935
|
+
statusBadge.className = "px-2.5 py-0.5 rounded text-xs font-mono font-bold bg-emerald-500/10 text-emerald-400";
|
|
936
|
+
statusBadge.innerHTML = "COMPLETED";
|
|
937
|
+
} else if (span.status === 'failed') {
|
|
938
|
+
statusBadge.className = "px-2.5 py-0.5 rounded text-xs font-mono font-bold bg-rose-500/10 text-rose-400";
|
|
939
|
+
statusBadge.innerHTML = "FAILED";
|
|
940
|
+
} else {
|
|
941
|
+
statusBadge.className = "px-2.5 py-0.5 rounded text-xs font-mono font-bold bg-amber-500/10 text-amber-400 animate-pulse";
|
|
942
|
+
statusBadge.innerHTML = "RUNNING";
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Title & Timings
|
|
946
|
+
document.getElementById('inspect-agent-name').innerHTML = span.agentName;
|
|
947
|
+
document.getElementById('inspect-duration').innerHTML = span.durationMs !== undefined ? span.durationMs + 'ms' : 'Executing...';
|
|
948
|
+
|
|
949
|
+
const startStr = new Date(span.startedAt).toLocaleTimeString();
|
|
950
|
+
document.getElementById('inspect-timings').innerHTML = \`Started: \${startStr}\`;
|
|
951
|
+
|
|
952
|
+
// Task
|
|
953
|
+
document.getElementById('inspect-task').innerHTML = escapeHtml(span.task);
|
|
954
|
+
|
|
955
|
+
// Raw JSON Payload Block
|
|
956
|
+
const rawPre = document.getElementById('inspect-raw-json');
|
|
957
|
+
if (rawPre) {
|
|
958
|
+
rawPre.textContent = JSON.stringify(span, null, 2);
|
|
959
|
+
}
|
|
960
|
+
const copyBtnText = document.getElementById('copy-btn-text');
|
|
961
|
+
if (copyBtnText) {
|
|
962
|
+
copyBtnText.innerHTML = "📋 Copy JSON";
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Events feed
|
|
966
|
+
const eventsContainer = document.getElementById('inspect-events');
|
|
967
|
+
if (span.events.length === 0) {
|
|
968
|
+
eventsContainer.innerHTML = '<p class="text-xs text-slate-500 font-mono py-2">No trace events recorded inside this execution span.</p>';
|
|
969
|
+
} else {
|
|
970
|
+
let eventsHtml = '';
|
|
971
|
+
span.events.forEach(ev => {
|
|
972
|
+
let eventIcon = '⚙️';
|
|
973
|
+
let borderClass = 'border-white/5';
|
|
974
|
+
if (ev.type === 'wiki_query') { eventIcon = '🔍'; borderClass = 'border-cyan-500/20'; }
|
|
975
|
+
if (ev.type === 'wiki_publish') { eventIcon = '📤'; borderClass = 'border-emerald-500/20'; }
|
|
976
|
+
if (ev.type === 'tool_call') { eventIcon = '⚙️'; borderClass = 'border-violet-500/20'; }
|
|
977
|
+
if (ev.type === 'collaboration') { eventIcon = '🤝'; borderClass = 'border-pink-500/20'; }
|
|
978
|
+
|
|
979
|
+
const timeStr = new Date(ev.timestamp).toLocaleTimeString();
|
|
980
|
+
|
|
981
|
+
let detailsSection = '';
|
|
982
|
+
if (ev.type === 'collaboration') {
|
|
983
|
+
try {
|
|
984
|
+
const data = JSON.parse(ev.detail);
|
|
985
|
+
if (data && data.responseReport) {
|
|
986
|
+
detailsSection = \`
|
|
987
|
+
<div class="mt-2.5 p-3 bg-slate-900 rounded border border-white/5 font-mono text-xs">
|
|
988
|
+
<p class="text-slate-400 font-bold mb-1">Collaboration Request Detail:</p>
|
|
989
|
+
<p class="text-white mb-3 bg-black/30 p-2 rounded whitespace-pre-wrap">\${escapeHtml(data.task)}</p>
|
|
990
|
+
<p class="text-slate-400 font-bold mb-1">Delivered Resolution Report:</p>
|
|
991
|
+
<p class="text-slate-300 bg-black/30 p-2 rounded whitespace-pre-wrap">\${escapeHtml(data.responseReport)}</p>
|
|
992
|
+
</div>
|
|
993
|
+
\`;
|
|
994
|
+
}
|
|
995
|
+
} catch {
|
|
996
|
+
detailsSection = \`<div class="mt-2 text-xs text-slate-400 font-mono bg-black/25 p-2 rounded whitespace-pre-wrap">\${escapeHtml(ev.detail)}</div>\`;
|
|
997
|
+
}
|
|
998
|
+
} else {
|
|
999
|
+
detailsSection = \`<div class="mt-2 text-xs text-slate-400 font-mono bg-black/25 p-2 rounded whitespace-pre-wrap">\${escapeHtml(ev.detail)}</div>\`;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
eventsHtml += \`
|
|
1003
|
+
<div class="p-3 bg-slate-900/40 rounded-lg border \${borderClass} flex flex-col transition-all">
|
|
1004
|
+
<div class="flex justify-between items-center text-xs">
|
|
1005
|
+
<span class="font-bold flex items-center space-x-1.5">
|
|
1006
|
+
<span>\${eventIcon}</span>
|
|
1007
|
+
<span class="text-slate-300 font-mono">\${ev.label}</span>
|
|
1008
|
+
</span>
|
|
1009
|
+
<span class="text-slate-500 font-mono">\${timeStr}</span>
|
|
1010
|
+
</div>
|
|
1011
|
+
\${detailsSection}
|
|
1012
|
+
</div>
|
|
1013
|
+
\`;
|
|
1014
|
+
});
|
|
1015
|
+
eventsContainer.innerHTML = eventsHtml;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Clipboard copy action
|
|
1020
|
+
function copyRawTelemetry() {
|
|
1021
|
+
if (!activeSpanId) return;
|
|
1022
|
+
const span = idToSpan.get(activeSpanId);
|
|
1023
|
+
if (!span) return;
|
|
1024
|
+
|
|
1025
|
+
const jsonStr = JSON.stringify(span, null, 2);
|
|
1026
|
+
navigator.clipboard.writeText(jsonStr).then(() => {
|
|
1027
|
+
const copyBtnText = document.getElementById('copy-btn-text');
|
|
1028
|
+
if (copyBtnText) {
|
|
1029
|
+
copyBtnText.innerHTML = "✨ Copied!";
|
|
1030
|
+
setTimeout(() => {
|
|
1031
|
+
copyBtnText.innerHTML = "📋 Copy JSON";
|
|
1032
|
+
}, 2000);
|
|
1033
|
+
}
|
|
1034
|
+
}).catch(err => {
|
|
1035
|
+
alert("Copy failed: " + err);
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Tab 2 content renderers
|
|
1040
|
+
function renderIntelLogs() {
|
|
1041
|
+
const wikiContainer = document.getElementById('wiki-logs-container');
|
|
1042
|
+
const scratchContainer = document.getElementById('scratchpad-vars-container');
|
|
1043
|
+
|
|
1044
|
+
let wikiHtml = '';
|
|
1045
|
+
let scratchHtml = '';
|
|
1046
|
+
|
|
1047
|
+
const allWikiPublishes = [];
|
|
1048
|
+
const scratchVariables = new Map();
|
|
1049
|
+
|
|
1050
|
+
for (const s of spans) {
|
|
1051
|
+
for (const ev of s.events) {
|
|
1052
|
+
if (ev.type === 'wiki_publish') {
|
|
1053
|
+
allWikiPublishes.push({ agentName: s.agentName, fact: ev.detail, topic: ev.label, timestamp: ev.timestamp });
|
|
1054
|
+
} else if (ev.type === 'tool_call' && ev.label === 'setSharedValue') {
|
|
1055
|
+
try {
|
|
1056
|
+
const match = ev.detail.match(/Setting shared value:\\s+(\\S+)/i) || ev.detail.match(/key:\\s*['"]?(\\w+)/);
|
|
1057
|
+
if (match && match[1]) {
|
|
1058
|
+
scratchVariables.set(match[1], { agentName: s.agentName, value: ev.detail, timestamp: ev.timestamp });
|
|
1059
|
+
} else {
|
|
1060
|
+
scratchVariables.set('var_' + ev.id.slice(0,6), { agentName: s.agentName, value: ev.detail, timestamp: ev.timestamp });
|
|
1061
|
+
}
|
|
1062
|
+
} catch {
|
|
1063
|
+
scratchVariables.set('var_' + ev.id.slice(0,6), { agentName: s.agentName, value: ev.detail, timestamp: ev.timestamp });
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Wiki facts
|
|
1070
|
+
if (allWikiPublishes.length === 0) {
|
|
1071
|
+
wikiHtml = '<p class="text-xs text-slate-500 py-6 text-center">No knowledge facts published to the Wiki blackboard during this session.</p>';
|
|
1072
|
+
} else {
|
|
1073
|
+
allWikiPublishes.forEach(wp => {
|
|
1074
|
+
wikiHtml += \`
|
|
1075
|
+
<div class="p-4 bg-slate-900/60 rounded-xl border border-emerald-500/10">
|
|
1076
|
+
<div class="flex justify-between items-center text-xs text-emerald-400 border-b border-white/5 pb-2 mb-2 font-mono">
|
|
1077
|
+
<span class="font-bold">Topic: \${wp.topic}</span>
|
|
1078
|
+
<span>By: \${wp.agentName}</span>
|
|
1079
|
+
</div>
|
|
1080
|
+
<p class="text-xs text-slate-300 font-mono whitespace-pre-wrap">\${escapeHtml(wp.fact)}</p>
|
|
1081
|
+
</div>
|
|
1082
|
+
\`;
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
wikiContainer.innerHTML = wikiHtml;
|
|
1086
|
+
|
|
1087
|
+
// Scratchpad Variables
|
|
1088
|
+
if (scratchVariables.size === 0) {
|
|
1089
|
+
scratchHtml = '<p class="text-xs text-slate-500 py-6 text-center">No shared scratchpad keys set during this session.</p>';
|
|
1090
|
+
} else {
|
|
1091
|
+
for (const [key, details] of scratchVariables.entries()) {
|
|
1092
|
+
scratchHtml += \`
|
|
1093
|
+
<div class="p-4 bg-slate-900/60 rounded-xl border border-indigo-500/10">
|
|
1094
|
+
<div class="flex justify-between items-center text-xs text-indigo-400 border-b border-white/5 pb-2 mb-2 font-mono">
|
|
1095
|
+
<span class="font-bold">Key: \${key}</span>
|
|
1096
|
+
<span>Source: \${details.agentName}</span>
|
|
1097
|
+
</div>
|
|
1098
|
+
<p class="text-xs text-slate-300 font-mono whitespace-pre-wrap">\${escapeHtml(details.value)}</p>
|
|
1099
|
+
</div>
|
|
1100
|
+
\`;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
scratchContainer.innerHTML = scratchHtml;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Utilities
|
|
1107
|
+
function filterTree() {
|
|
1108
|
+
const q = document.getElementById('tree-search').value;
|
|
1109
|
+
renderTree(q);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function clearSearch() {
|
|
1113
|
+
document.getElementById('tree-search').value = '';
|
|
1114
|
+
renderTree('');
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function escapeHtml(str) {
|
|
1118
|
+
if (!str) return '';
|
|
1119
|
+
return str
|
|
1120
|
+
.replace(/&/g, "&")
|
|
1121
|
+
.replace(/</g, "<")
|
|
1122
|
+
.replace(/>/g, ">")
|
|
1123
|
+
.replace(/"/g, """)
|
|
1124
|
+
.replace(/'/g, "'");
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Initialize Page
|
|
1128
|
+
renderGanttTimeline();
|
|
1129
|
+
renderTree();
|
|
1130
|
+
</script>
|
|
1131
|
+
</body>
|
|
1132
|
+
</html>`;
|
|
1133
|
+
}
|
|
1134
|
+
//# sourceMappingURL=traces.js.map
|