@raquezha/notrace 0.0.4 → 0.0.5
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 +7 -0
- package/README.md +59 -6
- package/dist/notrace.js +466 -337
- package/extensions/notrace.ts +500 -337
- package/package.json +1 -1
package/extensions/notrace.ts
CHANGED
|
@@ -20,7 +20,31 @@ function getCaptureMode(): CaptureMode {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function isSensitiveKey(key: string): boolean {
|
|
23
|
-
|
|
23
|
+
const normalized = key.replace(/[^a-z0-9]/gi, "");
|
|
24
|
+
if (/^(inputtokens|outputtokens|totaltokens|prompttokens|completiontokens|reasoningtokens|cachedtokens|cachecreationinputtokens|cachereadinputtokens|cost|total|input|output|prompt|completion|reasoning|read|write)$/i.test(normalized)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return SENSITIVE_KEY_RE.test(normalized);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function sanitizeUsageValue(usage: unknown): unknown {
|
|
31
|
+
if (getCaptureMode() === "full") return usage;
|
|
32
|
+
if (!usage || typeof usage !== "object") return sanitizeTraceValue(usage);
|
|
33
|
+
|
|
34
|
+
const source = usage as Record<string, unknown>;
|
|
35
|
+
const output: Record<string, unknown> = {};
|
|
36
|
+
|
|
37
|
+
for (const [key, value] of Object.entries(source)) {
|
|
38
|
+
if (typeof value === "number" || typeof value === "boolean" || value == null) {
|
|
39
|
+
output[key] = value;
|
|
40
|
+
} else if (typeof value === "object" && value) {
|
|
41
|
+
output[key] = sanitizeUsageValue(value);
|
|
42
|
+
} else {
|
|
43
|
+
output[key] = sanitizeTraceValue(value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return output;
|
|
24
48
|
}
|
|
25
49
|
|
|
26
50
|
function redactString(value: string): string {
|
|
@@ -75,6 +99,205 @@ function escapeHtml(value: unknown): string {
|
|
|
75
99
|
});
|
|
76
100
|
}
|
|
77
101
|
|
|
102
|
+
type NotraceLocation = {
|
|
103
|
+
workflowDir: string;
|
|
104
|
+
notraceDir: string;
|
|
105
|
+
outputDir: string;
|
|
106
|
+
taskDir: string | null;
|
|
107
|
+
taskId: string | null;
|
|
108
|
+
taskPath: string | null;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type NotraceMetrics = {
|
|
112
|
+
totalTokens: number;
|
|
113
|
+
inputTokens: number;
|
|
114
|
+
outputTokens: number;
|
|
115
|
+
totalCost: number;
|
|
116
|
+
toolCallCount: number;
|
|
117
|
+
toolErrorCount: number;
|
|
118
|
+
llmCallCount: number;
|
|
119
|
+
turnCount: number;
|
|
120
|
+
models: string[];
|
|
121
|
+
providers: string[];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function ensureWorkflowDir(workflowDir: string): void {
|
|
125
|
+
if (existsSync(workflowDir)) return;
|
|
126
|
+
try {
|
|
127
|
+
mkdirSync(workflowDir, { recursive: true, mode: 0o700 });
|
|
128
|
+
} catch {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function safePathSegment(value: string): string {
|
|
132
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 160) || `session-${Date.now()}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getNotraceLocation(cwd: string, sessionId: string): NotraceLocation {
|
|
136
|
+
const workflowDir = path.resolve(cwd, ".workflow");
|
|
137
|
+
const notraceDir = path.resolve(cwd, ".notrace");
|
|
138
|
+
const outputDir = path.join(notraceDir, "sessions", safePathSegment(sessionId));
|
|
139
|
+
ensureWorkflowDir(workflowDir);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const activeTaskJsonPath = path.join(workflowDir, "active_task.json");
|
|
143
|
+
if (existsSync(activeTaskJsonPath)) {
|
|
144
|
+
const content = JSON.parse(readFileSync(activeTaskJsonPath, "utf-8"));
|
|
145
|
+
const candidate = typeof content.taskPath === "string"
|
|
146
|
+
? safeResolveUnder(cwd, content.taskPath)
|
|
147
|
+
: typeof content.active_task === "string"
|
|
148
|
+
? safeResolveUnder(workflowDir, path.join("tasks", content.active_task))
|
|
149
|
+
: null;
|
|
150
|
+
|
|
151
|
+
if (candidate && safeResolveUnder(workflowDir, path.relative(workflowDir, candidate))) {
|
|
152
|
+
return {
|
|
153
|
+
workflowDir,
|
|
154
|
+
notraceDir,
|
|
155
|
+
outputDir,
|
|
156
|
+
taskDir: candidate,
|
|
157
|
+
taskId: typeof content.active_task === "string" ? content.active_task : path.basename(candidate),
|
|
158
|
+
taskPath: path.relative(cwd, candidate)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// fallback
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
workflowDir,
|
|
168
|
+
notraceDir,
|
|
169
|
+
outputDir,
|
|
170
|
+
taskDir: null,
|
|
171
|
+
taskId: null,
|
|
172
|
+
taskPath: null
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function updateNotraceIndex(cwd: string, location: NotraceLocation, entry: Record<string, unknown>): void {
|
|
177
|
+
const indexPath = path.join(location.notraceDir, "index.json");
|
|
178
|
+
const base = {
|
|
179
|
+
schemaVersion: 1,
|
|
180
|
+
kind: "notrace-index",
|
|
181
|
+
workdir: ".",
|
|
182
|
+
sessions: [] as Record<string, unknown>[]
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
mkdirSync(location.notraceDir, { recursive: true, mode: 0o700 });
|
|
187
|
+
const existing = existsSync(indexPath)
|
|
188
|
+
? JSON.parse(readFileSync(indexPath, "utf-8"))
|
|
189
|
+
: base;
|
|
190
|
+
const sessions = Array.isArray(existing.sessions) ? existing.sessions : [];
|
|
191
|
+
const sessionId = entry.sessionId;
|
|
192
|
+
const nextSessions = sessions.filter((item: any) => item?.sessionId !== sessionId);
|
|
193
|
+
nextSessions.push(entry);
|
|
194
|
+
const next = {
|
|
195
|
+
...base,
|
|
196
|
+
...existing,
|
|
197
|
+
schemaVersion: 1,
|
|
198
|
+
kind: "notrace-index",
|
|
199
|
+
workdir: ".",
|
|
200
|
+
sessions: nextSessions
|
|
201
|
+
};
|
|
202
|
+
writeFileSync(indexPath, `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
203
|
+
} catch {
|
|
204
|
+
// keep notrace non-fatal if index update fails
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function appendWorkLogEntry(taskDir: string, message: string): void {
|
|
209
|
+
const workMd = path.join(taskDir, "WORK.md");
|
|
210
|
+
if (!existsSync(workMd)) return;
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const text = readFileSync(workMd, "utf-8");
|
|
214
|
+
const entry = `- ${new Date().toISOString()}: ${message}`;
|
|
215
|
+
|
|
216
|
+
if (!/^(## )?\[LOG\]\s*$/m.test(text)) {
|
|
217
|
+
writeFileSync(workMd, `${text.trimEnd()}\n\n## [LOG]\n${entry}\n`, { encoding: "utf-8" });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const lines = text.split("\n");
|
|
222
|
+
const logIndex = lines.findIndex((line) => /^(## )?\[LOG\]\s*$/.test(line));
|
|
223
|
+
if (logIndex === -1) return;
|
|
224
|
+
|
|
225
|
+
let nextSectionIndex = lines.length;
|
|
226
|
+
for (let i = logIndex + 1; i < lines.length; i++) {
|
|
227
|
+
if (/^(## )?\[[A-Z0-9_-]+\]\s*$/.test(lines[i])) {
|
|
228
|
+
nextSectionIndex = i;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const before = lines.slice(0, nextSectionIndex);
|
|
234
|
+
const after = lines.slice(nextSectionIndex);
|
|
235
|
+
while (before.length > logIndex + 1 && before[before.length - 1]?.trim() === "") {
|
|
236
|
+
before.pop();
|
|
237
|
+
}
|
|
238
|
+
before.push(entry);
|
|
239
|
+
|
|
240
|
+
writeFileSync(workMd, `${[...before, ...after].join("\n").replace(/\n*$/, "\n")}`, { encoding: "utf-8" });
|
|
241
|
+
} catch {
|
|
242
|
+
// keep notrace non-fatal if WORK.md append fails
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function collectMetrics(events: any[]): NotraceMetrics {
|
|
247
|
+
let totalTokens = 0;
|
|
248
|
+
let inputTokens = 0;
|
|
249
|
+
let outputTokens = 0;
|
|
250
|
+
let totalCost = 0;
|
|
251
|
+
let toolCallCount = 0;
|
|
252
|
+
let toolErrorCount = 0;
|
|
253
|
+
let llmCallCount = 0;
|
|
254
|
+
let turnCount = 0;
|
|
255
|
+
const models = new Set<string>();
|
|
256
|
+
const providers = new Set<string>();
|
|
257
|
+
|
|
258
|
+
for (const event of events) {
|
|
259
|
+
if (event.type === "turn_start") {
|
|
260
|
+
turnCount++;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (event.type === "tool_start") {
|
|
265
|
+
toolCallCount++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (event.type === "tool_end") {
|
|
270
|
+
if (event.isError) toolErrorCount++;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (event.type === "llm_completion") {
|
|
275
|
+
llmCallCount++;
|
|
276
|
+
if (typeof event.model === "string" && event.model) models.add(event.model);
|
|
277
|
+
if (typeof event.provider === "string" && event.provider) providers.add(event.provider);
|
|
278
|
+
if (event.usage) {
|
|
279
|
+
inputTokens += event.usage.input || 0;
|
|
280
|
+
outputTokens += event.usage.output || 0;
|
|
281
|
+
totalTokens += event.usage.totalTokens || 0;
|
|
282
|
+
totalCost += event.usage.cost?.total || 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
totalTokens,
|
|
289
|
+
inputTokens,
|
|
290
|
+
outputTokens,
|
|
291
|
+
totalCost,
|
|
292
|
+
toolCallCount,
|
|
293
|
+
toolErrorCount,
|
|
294
|
+
llmCallCount,
|
|
295
|
+
turnCount,
|
|
296
|
+
models: [...models],
|
|
297
|
+
providers: [...providers]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
78
301
|
/**
|
|
79
302
|
* html-observability extension
|
|
80
303
|
*
|
|
@@ -90,33 +313,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
313
|
let llmStartTime = 0;
|
|
91
314
|
const activeToolTimes: Record<string, number> = {};
|
|
92
315
|
|
|
93
|
-
// Helper to extract active task path from .workflow/active_task.json.
|
|
94
|
-
// Reports are constrained to cwd/.workflow to avoid task metadata causing writes elsewhere.
|
|
95
|
-
function getActiveTaskDir(cwd: string): string {
|
|
96
|
-
const workflowDir = path.resolve(cwd, ".workflow");
|
|
97
|
-
try {
|
|
98
|
-
const activeTaskJsonPath = path.join(workflowDir, "active_task.json");
|
|
99
|
-
if (existsSync(activeTaskJsonPath)) {
|
|
100
|
-
const content = JSON.parse(readFileSync(activeTaskJsonPath, "utf-8"));
|
|
101
|
-
const candidate = typeof content.taskPath === "string"
|
|
102
|
-
? safeResolveUnder(cwd, content.taskPath)
|
|
103
|
-
: typeof content.active_task === "string"
|
|
104
|
-
? safeResolveUnder(workflowDir, path.join("tasks", content.active_task))
|
|
105
|
-
: null;
|
|
106
|
-
|
|
107
|
-
if (candidate && safeResolveUnder(workflowDir, path.relative(workflowDir, candidate))) {
|
|
108
|
-
return candidate;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
// fallback
|
|
113
|
-
}
|
|
114
|
-
if (!existsSync(workflowDir)) {
|
|
115
|
-
try { mkdirSync(workflowDir, { recursive: true, mode: 0o700 }); } catch {}
|
|
116
|
-
}
|
|
117
|
-
return workflowDir;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
316
|
// 1. Session start
|
|
121
317
|
pi.on("session_start", async (event, ctx) => {
|
|
122
318
|
traceId = ctx.sessionManager.getSessionId() || `session-${Date.now()}`;
|
|
@@ -194,7 +390,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
194
390
|
provider: message.provider || "unknown",
|
|
195
391
|
inputPayload: activeLlmPayload,
|
|
196
392
|
outputContent: getCaptureMode() === "metadata" ? "[metadata-only capture]" : sanitizeTraceValue(message.content),
|
|
197
|
-
usage:
|
|
393
|
+
usage: sanitizeUsageValue(message.usage),
|
|
198
394
|
durationMs,
|
|
199
395
|
timestamp: Date.now()
|
|
200
396
|
});
|
|
@@ -207,57 +403,103 @@ export default function (pi: ExtensionAPI) {
|
|
|
207
403
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
208
404
|
const sessionEndTime = Date.now();
|
|
209
405
|
const totalDurationMs = sessionEndTime - sessionStartTime;
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
let totalCost = 0;
|
|
219
|
-
let toolCallCount = 0;
|
|
220
|
-
let llmCallCount = 0;
|
|
221
|
-
|
|
222
|
-
events.forEach((e) => {
|
|
223
|
-
if (e.type === "llm_completion") {
|
|
224
|
-
llmCallCount++;
|
|
225
|
-
if (e.usage) {
|
|
226
|
-
inputTokens += e.usage.input || 0;
|
|
227
|
-
outputTokens += e.usage.output || 0;
|
|
228
|
-
totalTokens += e.usage.totalTokens || 0;
|
|
229
|
-
totalCost += e.usage.cost?.total || 0;
|
|
230
|
-
}
|
|
231
|
-
} else if (e.type === "tool_start") {
|
|
232
|
-
toolCallCount++;
|
|
233
|
-
}
|
|
234
|
-
});
|
|
406
|
+
const projectName = process.env.PHOENIX_PROJECT_NAME || "pi-coding-agent";
|
|
407
|
+
const captureMode = getCaptureMode();
|
|
408
|
+
const location = getNotraceLocation(ctx.cwd, traceId);
|
|
409
|
+
const reportPath = path.join(location.outputDir, "notrace.html");
|
|
410
|
+
const recordPath = path.join(location.outputDir, "notrace.json");
|
|
411
|
+
const reviewPath = path.join(location.outputDir, "notrace.review.json");
|
|
412
|
+
const metrics = collectMetrics(events);
|
|
413
|
+
const sessionFile = ctx.sessionManager.getSessionFile() || null;
|
|
235
414
|
|
|
236
415
|
const htmlContent = generateHtmlReport({
|
|
237
416
|
traceId,
|
|
238
|
-
projectName
|
|
417
|
+
projectName,
|
|
239
418
|
startTime: new Date(sessionStartTime).toISOString(),
|
|
240
419
|
endTime: new Date(sessionEndTime).toISOString(),
|
|
241
420
|
durationMs: totalDurationMs,
|
|
242
421
|
metrics: {
|
|
243
|
-
totalTokens,
|
|
244
|
-
inputTokens,
|
|
245
|
-
outputTokens,
|
|
246
|
-
totalCost: totalCost.toFixed(5),
|
|
247
|
-
toolCallCount,
|
|
248
|
-
llmCallCount
|
|
422
|
+
totalTokens: metrics.totalTokens,
|
|
423
|
+
inputTokens: metrics.inputTokens,
|
|
424
|
+
outputTokens: metrics.outputTokens,
|
|
425
|
+
totalCost: metrics.totalCost.toFixed(5),
|
|
426
|
+
toolCallCount: metrics.toolCallCount,
|
|
427
|
+
llmCallCount: metrics.llmCallCount
|
|
249
428
|
},
|
|
250
429
|
events
|
|
251
430
|
});
|
|
252
431
|
|
|
432
|
+
const runRecord = {
|
|
433
|
+
schemaVersion: 1,
|
|
434
|
+
kind: "notrace-run",
|
|
435
|
+
traceId,
|
|
436
|
+
runtime: "pi",
|
|
437
|
+
projectName,
|
|
438
|
+
captureMode,
|
|
439
|
+
session: {
|
|
440
|
+
id: traceId,
|
|
441
|
+
cwd: ctx.cwd,
|
|
442
|
+
file: sessionFile
|
|
443
|
+
},
|
|
444
|
+
task: location.taskId || location.taskPath
|
|
445
|
+
? {
|
|
446
|
+
id: location.taskId,
|
|
447
|
+
path: location.taskPath
|
|
448
|
+
}
|
|
449
|
+
: null,
|
|
450
|
+
conditions: {
|
|
451
|
+
models: metrics.models,
|
|
452
|
+
providers: metrics.providers
|
|
453
|
+
},
|
|
454
|
+
activity: {
|
|
455
|
+
startTime: new Date(sessionStartTime).toISOString(),
|
|
456
|
+
endTime: new Date(sessionEndTime).toISOString(),
|
|
457
|
+
durationMs: totalDurationMs,
|
|
458
|
+
turnCount: metrics.turnCount,
|
|
459
|
+
llmCallCount: metrics.llmCallCount,
|
|
460
|
+
toolCallCount: metrics.toolCallCount,
|
|
461
|
+
toolErrorCount: metrics.toolErrorCount,
|
|
462
|
+
totals: {
|
|
463
|
+
totalTokens: metrics.totalTokens,
|
|
464
|
+
inputTokens: metrics.inputTokens,
|
|
465
|
+
outputTokens: metrics.outputTokens,
|
|
466
|
+
totalCostUsd: Number(metrics.totalCost.toFixed(5))
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
artifacts: {
|
|
470
|
+
htmlReportPath: path.relative(ctx.cwd, reportPath),
|
|
471
|
+
recordPath: path.relative(ctx.cwd, recordPath),
|
|
472
|
+
reviewPath: path.relative(ctx.cwd, reviewPath)
|
|
473
|
+
},
|
|
474
|
+
evidence: {
|
|
475
|
+
events
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
253
479
|
try {
|
|
254
|
-
mkdirSync(
|
|
480
|
+
mkdirSync(location.outputDir, { recursive: true, mode: 0o700 });
|
|
255
481
|
writeFileSync(reportPath, htmlContent, { encoding: "utf-8", mode: 0o600 });
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
482
|
+
writeFileSync(recordPath, `${JSON.stringify(runRecord, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
483
|
+
updateNotraceIndex(ctx.cwd, location, {
|
|
484
|
+
sessionId: traceId,
|
|
485
|
+
startedAt: new Date(sessionStartTime).toISOString(),
|
|
486
|
+
endedAt: new Date(sessionEndTime).toISOString(),
|
|
487
|
+
taskId: location.taskId,
|
|
488
|
+
taskPath: location.taskPath,
|
|
489
|
+
artifacts: {
|
|
490
|
+
record: path.relative(ctx.cwd, recordPath),
|
|
491
|
+
html: path.relative(ctx.cwd, reportPath),
|
|
492
|
+
review: path.relative(ctx.cwd, reviewPath)
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
if (location.taskDir && (location.taskId || location.taskPath)) {
|
|
496
|
+
appendWorkLogEntry(location.taskDir, `notrace captured artifacts: ${path.relative(location.taskDir, reportPath)}, ${path.relative(location.taskDir, recordPath)}`);
|
|
497
|
+
}
|
|
498
|
+
console.log(`\n📊 [notrace] Observability artifacts generated:`);
|
|
499
|
+
console.log(`👉 \x1b[36mfile://${reportPath}\x1b[0m`);
|
|
500
|
+
console.log(`🧾 \x1b[36mfile://${recordPath}\x1b[0m\n`);
|
|
259
501
|
} catch (err: any) {
|
|
260
|
-
console.warn(`[notrace] Failed to write
|
|
502
|
+
console.warn(`[notrace] Failed to write notrace artifacts: ${err?.message || err}`);
|
|
261
503
|
}
|
|
262
504
|
});
|
|
263
505
|
}
|
|
@@ -290,301 +532,222 @@ function generateHtmlReport(data: any): string {
|
|
|
290
532
|
<title>notrace - ${escapedTraceId}</title>
|
|
291
533
|
<style>
|
|
292
534
|
:root {
|
|
293
|
-
--bg: #
|
|
294
|
-
--bg-
|
|
295
|
-
--
|
|
296
|
-
--
|
|
297
|
-
--
|
|
298
|
-
--
|
|
299
|
-
--text
|
|
300
|
-
--
|
|
301
|
-
--
|
|
302
|
-
--
|
|
303
|
-
--
|
|
304
|
-
--
|
|
305
|
-
--
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
535
|
+
--bg: #191613;
|
|
536
|
+
--bg-glow: #2a221c;
|
|
537
|
+
--panel: rgba(244, 237, 228, 0.055);
|
|
538
|
+
--panel-strong: rgba(244, 237, 228, 0.075);
|
|
539
|
+
--paper: #f6efe7;
|
|
540
|
+
--paper-soft: #e7dbce;
|
|
541
|
+
--text: #f5eee7;
|
|
542
|
+
--text-soft: #ddd0c2;
|
|
543
|
+
--text-muted: #b9ab9d;
|
|
544
|
+
--border: rgba(255, 255, 255, 0.09);
|
|
545
|
+
--border-strong: rgba(255, 255, 255, 0.16);
|
|
546
|
+
--accent: #d88462;
|
|
547
|
+
--accent-soft: rgba(216, 132, 98, 0.14);
|
|
548
|
+
--session: #8ab7ff;
|
|
549
|
+
--turn: #e0b46b;
|
|
550
|
+
--tool: #86cca4;
|
|
551
|
+
--error: #f08e8e;
|
|
552
|
+
--shadow: 0 24px 80px rgba(0, 0, 0, 0.34);
|
|
553
|
+
--card-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
313
557
|
|
|
314
558
|
body {
|
|
315
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
316
|
-
background
|
|
559
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
560
|
+
background:
|
|
561
|
+
radial-gradient(900px 420px at 50% -10%, rgba(216, 132, 98, 0.16), transparent 60%),
|
|
562
|
+
radial-gradient(680px 360px at 10% 0%, rgba(255, 255, 255, 0.03), transparent 60%),
|
|
563
|
+
linear-gradient(180deg, #171411 0%, #1b1714 100%);
|
|
317
564
|
color: var(--text);
|
|
318
|
-
line-height: 1.
|
|
319
|
-
padding:
|
|
565
|
+
line-height: 1.65;
|
|
566
|
+
padding: 14px 12px 40px;
|
|
320
567
|
min-height: 100vh;
|
|
321
|
-
|
|
322
|
-
radial-gradient(circle at 10% 20%, rgba(139, 92, 246, 0.08) 0%, transparent 40%),
|
|
323
|
-
radial-gradient(circle at 90% 80%, rgba(59, 130, 246, 0.06) 0%, transparent 40%);
|
|
324
|
-
background-attachment: fixed;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.container {
|
|
328
|
-
max-width: 1100px;
|
|
329
|
-
margin: 0 auto;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
header {
|
|
333
|
-
display: flex;
|
|
334
|
-
justify-content: space-between;
|
|
335
|
-
align-items: center;
|
|
336
|
-
margin-bottom: 2rem;
|
|
337
|
-
padding-bottom: 1.5rem;
|
|
338
|
-
border-bottom: 1px solid var(--border);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
.brand {
|
|
342
|
-
display: flex;
|
|
343
|
-
align-items: center;
|
|
344
|
-
gap: 0.75rem;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
.brand h1 {
|
|
348
|
-
font-size: 1.75rem;
|
|
349
|
-
font-weight: 700;
|
|
350
|
-
background: var(--accent-gradient);
|
|
351
|
-
-webkit-background-clip: text;
|
|
352
|
-
-webkit-text-fill-color: transparent;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.brand-tag {
|
|
356
|
-
font-size: 0.75rem;
|
|
357
|
-
text-transform: uppercase;
|
|
358
|
-
letter-spacing: 0.05em;
|
|
359
|
-
background: rgba(139, 92, 246, 0.15);
|
|
360
|
-
color: #c084fc;
|
|
361
|
-
padding: 0.2rem 0.5rem;
|
|
362
|
-
border-radius: 4px;
|
|
363
|
-
border: 1px solid rgba(167, 139, 250, 0.3);
|
|
568
|
+
letter-spacing: 0.01em;
|
|
364
569
|
}
|
|
365
570
|
|
|
366
|
-
.
|
|
367
|
-
|
|
368
|
-
color: var(--text-muted);
|
|
369
|
-
text-align: right;
|
|
370
|
-
}
|
|
571
|
+
.container { max-width: 920px; margin: 0 auto; }
|
|
572
|
+
header { margin-bottom: 24px; }
|
|
371
573
|
|
|
372
|
-
.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
574
|
+
.hero {
|
|
575
|
+
position: relative;
|
|
576
|
+
overflow: hidden;
|
|
577
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0.025));
|
|
578
|
+
border: 1px solid var(--border);
|
|
579
|
+
border-radius: 22px;
|
|
580
|
+
padding: 18px 16px 16px;
|
|
581
|
+
box-shadow: var(--shadow);
|
|
582
|
+
backdrop-filter: blur(10px);
|
|
377
583
|
}
|
|
378
584
|
|
|
585
|
+
.hero::after {
|
|
586
|
+
content: "";
|
|
587
|
+
position: absolute;
|
|
588
|
+
inset: auto -10% -30% auto;
|
|
589
|
+
width: 260px;
|
|
590
|
+
height: 260px;
|
|
591
|
+
background: radial-gradient(circle, rgba(216,132,98,0.14), transparent 70%);
|
|
592
|
+
pointer-events: none;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.brand { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; position: relative; z-index: 1; }
|
|
596
|
+
.brand h1 { font-size: 2rem; line-height: 1; font-weight: 680; color: var(--text); letter-spacing: -0.03em; }
|
|
597
|
+
.brand-tag { font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; color: #f4ccb9; background: var(--accent-soft); border: 1px solid rgba(216, 132, 98, 0.24); padding: 0.35rem 0.6rem; border-radius: 999px; }
|
|
598
|
+
.hero-subtitle { position: relative; z-index: 1; color: var(--text-soft); margin-bottom: 20px; max-width: 720px; font-size: 1rem; }
|
|
599
|
+
.hero-meta { position: relative; z-index: 1; display: flex; flex-wrap: wrap; gap: 10px; }
|
|
600
|
+
.meta-pill { display: inline-flex; align-items: center; gap: 6px; background: rgba(255,255,255,0.04); border: 1px solid var(--border); border-radius: 999px; padding: 0.48rem 0.82rem; font-size: 0.86rem; color: var(--text-muted); }
|
|
601
|
+
.meta-pill strong { color: var(--text); font-weight: 570; }
|
|
602
|
+
|
|
603
|
+
.metrics-grid { display: grid; grid-template-columns: 1fr; gap: 10px; margin: 16px 0 24px; }
|
|
379
604
|
.metric-card {
|
|
380
|
-
background:
|
|
605
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0.028));
|
|
381
606
|
border: 1px solid var(--border);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
transition:
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.metric-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
607
|
+
border-radius: 20px;
|
|
608
|
+
padding: 18px 16px;
|
|
609
|
+
box-shadow: var(--card-shadow);
|
|
610
|
+
transition: border-color 0.18s ease, transform 0.18s ease, background 0.18s ease;
|
|
611
|
+
}
|
|
612
|
+
.metric-card:hover { border-color: var(--border-strong); transform: translateY(-1px); background: linear-gradient(180deg, rgba(255,255,255,0.055), rgba(255,255,255,0.03)); }
|
|
613
|
+
.metric-label { font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: 7px; }
|
|
614
|
+
.metric-value { font-size: 1.42rem; font-weight: 640; color: var(--text); letter-spacing: -0.03em; }
|
|
615
|
+
|
|
616
|
+
.section-title {
|
|
617
|
+
font-size: 0.95rem;
|
|
618
|
+
font-weight: 620;
|
|
619
|
+
color: var(--paper-soft);
|
|
620
|
+
margin-bottom: 16px;
|
|
621
|
+
padding-left: 2px;
|
|
622
|
+
letter-spacing: 0.04em;
|
|
396
623
|
text-transform: uppercase;
|
|
397
|
-
letter-spacing: 0.05em;
|
|
398
|
-
color: var(--text-muted);
|
|
399
|
-
margin-bottom: 0.5rem;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.metric-value {
|
|
403
|
-
font-size: 1.5rem;
|
|
404
|
-
font-weight: 600;
|
|
405
|
-
color: #fff;
|
|
406
624
|
}
|
|
407
625
|
|
|
408
|
-
.timeline {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
margin-left: 1rem;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
.timeline-event {
|
|
416
|
-
position: relative;
|
|
417
|
-
margin-bottom: 2rem;
|
|
418
|
-
animation: slideIn 0.4s ease-out;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
@keyframes slideIn {
|
|
422
|
-
from { opacity: 0; transform: translateX(-10px); }
|
|
423
|
-
to { opacity: 1; transform: translateX(0); }
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
.timeline-dot {
|
|
427
|
-
position: absolute;
|
|
428
|
-
left: calc(-2rem - 6px);
|
|
429
|
-
top: 1.25rem;
|
|
430
|
-
width: 10px;
|
|
431
|
-
height: 10px;
|
|
432
|
-
border-radius: 50%;
|
|
433
|
-
background: var(--text-muted);
|
|
434
|
-
box-shadow: 0 0 0 4px var(--bg);
|
|
435
|
-
transition: all 0.3s ease;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/* Event color mappings */
|
|
439
|
-
.timeline-event.session-start .timeline-dot { background: var(--info); box-shadow: 0 0 0 4px var(--bg), 0 0 8px var(--info); }
|
|
440
|
-
.timeline-event.turn-start .timeline-dot { background: var(--accent); box-shadow: 0 0 0 4px var(--bg), 0 0 8px var(--accent); }
|
|
441
|
-
.timeline-event.tool-call .timeline-dot { background: var(--warning); box-shadow: 0 0 0 4px var(--bg); }
|
|
442
|
-
.timeline-event.tool-call.error .timeline-dot { background: var(--error); box-shadow: 0 0 0 4px var(--bg), 0 0 8px var(--error); }
|
|
443
|
-
.timeline-event.llm-call .timeline-dot { background: var(--success); box-shadow: 0 0 0 4px var(--bg), 0 0 8px var(--success); }
|
|
626
|
+
.timeline { display: flex; flex-direction: column; gap: 16px; }
|
|
627
|
+
.timeline-event { position: relative; animation: slideIn 0.28s ease-out; }
|
|
628
|
+
@keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
|
629
|
+
.timeline-dot { display: none; }
|
|
444
630
|
|
|
445
631
|
.card {
|
|
446
|
-
|
|
632
|
+
position: relative;
|
|
633
|
+
overflow: hidden;
|
|
634
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0.024));
|
|
447
635
|
border: 1px solid var(--border);
|
|
448
|
-
border-radius:
|
|
449
|
-
padding:
|
|
450
|
-
|
|
451
|
-
transition: border-color 0.
|
|
636
|
+
border-radius: 18px;
|
|
637
|
+
padding: 14px 14px 13px;
|
|
638
|
+
box-shadow: var(--card-shadow);
|
|
639
|
+
transition: border-color 0.18s ease, background 0.18s ease, transform 0.18s ease;
|
|
452
640
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
.
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
.
|
|
481
|
-
.
|
|
482
|
-
.
|
|
483
|
-
|
|
484
|
-
.
|
|
485
|
-
|
|
486
|
-
.card-time {
|
|
487
|
-
font-size: 0.8rem;
|
|
488
|
-
color: var(--text-muted);
|
|
489
|
-
display: flex;
|
|
490
|
-
align-items: center;
|
|
491
|
-
gap: 0.5rem;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.arrow-icon {
|
|
495
|
-
width: 16px;
|
|
496
|
-
height: 16px;
|
|
497
|
-
fill: none;
|
|
498
|
-
stroke: currentColor;
|
|
499
|
-
stroke-width: 2;
|
|
500
|
-
stroke-linecap: round;
|
|
501
|
-
stroke-linejoin: round;
|
|
502
|
-
transition: transform 0.2s;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
.expanded .arrow-icon {
|
|
506
|
-
transform: rotate(90deg);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
.card-body {
|
|
510
|
-
margin-top: 1rem;
|
|
511
|
-
padding-top: 1rem;
|
|
512
|
-
border-top: 1px solid var(--border);
|
|
513
|
-
display: none;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
.expanded .card-body {
|
|
517
|
-
display: block;
|
|
518
|
-
}
|
|
519
|
-
|
|
641
|
+
.card::before {
|
|
642
|
+
content: "";
|
|
643
|
+
position: absolute;
|
|
644
|
+
left: 0;
|
|
645
|
+
top: 0;
|
|
646
|
+
bottom: 0;
|
|
647
|
+
width: 3px;
|
|
648
|
+
background: rgba(255,255,255,0.1);
|
|
649
|
+
}
|
|
650
|
+
.timeline-event.session-start .card::before { background: var(--session); }
|
|
651
|
+
.timeline-event.turn-start .card::before { background: var(--turn); }
|
|
652
|
+
.timeline-event.tool-start .card::before { background: var(--tool); }
|
|
653
|
+
.timeline-event.tool-start.error .card::before { background: var(--error); }
|
|
654
|
+
.timeline-event.llm-start .card::before { background: var(--accent); }
|
|
655
|
+
.card:hover { border-color: var(--border-strong); background: linear-gradient(180deg, rgba(255,255,255,0.055), rgba(255,255,255,0.028)); transform: translateY(-1px); }
|
|
656
|
+
|
|
657
|
+
.card-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; cursor: pointer; user-select: none; }
|
|
658
|
+
.card-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-weight: 600; color: var(--text); }
|
|
659
|
+
.card-title span:last-child { font-size: 1rem; letter-spacing: -0.01em; }
|
|
660
|
+
.card-badge { font-size: 0.69rem; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.34rem 0.62rem; border-radius: 999px; border: 1px solid transparent; }
|
|
661
|
+
.badge-session { background: rgba(138, 183, 255, 0.14); color: var(--session); border-color: rgba(138, 183, 255, 0.24); }
|
|
662
|
+
.badge-turn { background: rgba(224, 180, 107, 0.14); color: var(--turn); border-color: rgba(224, 180, 107, 0.22); }
|
|
663
|
+
.badge-tool { background: rgba(134, 204, 164, 0.14); color: var(--tool); border-color: rgba(134, 204, 164, 0.22); }
|
|
664
|
+
.badge-tool.error { background: rgba(240, 142, 142, 0.15); color: var(--error); border-color: rgba(240, 142, 142, 0.24); }
|
|
665
|
+
.badge-llm { background: rgba(216, 132, 98, 0.14); color: #f2c2ae; border-color: rgba(216, 132, 98, 0.22); }
|
|
666
|
+
.card-time { flex-shrink: 0; font-size: 0.82rem; color: var(--text-muted); display: flex; align-items: center; gap: 8px; padding-top: 3px; }
|
|
667
|
+
.arrow-icon { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.2s; }
|
|
668
|
+
.expanded .arrow-icon { transform: rotate(90deg); }
|
|
669
|
+
.card-body { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); display: none; }
|
|
670
|
+
.expanded .card-body { display: block; }
|
|
671
|
+
|
|
672
|
+
.detail-label { font-size: 0.76rem; font-weight: 650; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; margin: 14px 0 8px; display: block; }
|
|
520
673
|
.code-block {
|
|
521
674
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
522
|
-
font-size: 0.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
675
|
+
font-size: 0.84rem;
|
|
676
|
+
line-height: 1.62;
|
|
677
|
+
background: rgba(16, 14, 12, 0.64);
|
|
678
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
679
|
+
padding: 14px;
|
|
680
|
+
border-radius: 16px;
|
|
527
681
|
overflow-x: auto;
|
|
528
|
-
margin-top: 0.
|
|
529
|
-
color: #
|
|
682
|
+
margin-top: 0.35rem;
|
|
683
|
+
color: #f1e9df;
|
|
530
684
|
white-space: pre-wrap;
|
|
531
685
|
}
|
|
532
686
|
|
|
533
|
-
.messages-container {
|
|
534
|
-
display: flex;
|
|
535
|
-
flex-direction: column;
|
|
536
|
-
gap: 0.75rem;
|
|
537
|
-
}
|
|
538
|
-
|
|
687
|
+
.messages-container { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
|
|
539
688
|
.msg-row {
|
|
540
689
|
display: flex;
|
|
541
690
|
flex-direction: column;
|
|
542
|
-
gap:
|
|
543
|
-
padding:
|
|
544
|
-
background: rgba(255,
|
|
545
|
-
border
|
|
546
|
-
border-
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
.msg-row.
|
|
550
|
-
.msg-row.
|
|
551
|
-
.msg-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
letter-spacing: 0.05em;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
.msg-row.user .msg-role { color: #60a5fa; }
|
|
561
|
-
.msg-row.assistant .msg-role { color: #34d399; }
|
|
562
|
-
.msg-row.system .msg-role { color: #c084fc; }
|
|
563
|
-
|
|
564
|
-
.msg-text {
|
|
565
|
-
font-size: 0.9rem;
|
|
566
|
-
white-space: pre-wrap;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
.duration-pill {
|
|
570
|
-
font-size: 0.75rem;
|
|
571
|
-
background: rgba(255, 255, 255, 0.06);
|
|
572
|
-
padding: 0.1rem 0.4rem;
|
|
573
|
-
border-radius: 4px;
|
|
691
|
+
gap: 4px;
|
|
692
|
+
padding: 12px 14px;
|
|
693
|
+
background: rgba(255,255,255,0.038);
|
|
694
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
695
|
+
border-radius: 18px;
|
|
696
|
+
}
|
|
697
|
+
.msg-row.user { background: rgba(138, 183, 255, 0.08); }
|
|
698
|
+
.msg-row.assistant { background: rgba(216, 132, 98, 0.08); }
|
|
699
|
+
.msg-row.system { background: rgba(224, 180, 107, 0.08); }
|
|
700
|
+
.msg-role { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }
|
|
701
|
+
.msg-text { font-size: 0.93rem; color: var(--text-soft); white-space: pre-wrap; }
|
|
702
|
+
|
|
703
|
+
.usage-row {
|
|
704
|
+
margin-top: 12px;
|
|
705
|
+
font-size: 0.84rem;
|
|
574
706
|
color: var(--text-muted);
|
|
707
|
+
display: flex;
|
|
708
|
+
flex-wrap: wrap;
|
|
709
|
+
gap: 12px;
|
|
710
|
+
padding: 10px 12px;
|
|
711
|
+
border-radius: 14px;
|
|
712
|
+
background: rgba(255,255,255,0.03);
|
|
713
|
+
border: 1px solid rgba(255,255,255,0.05);
|
|
714
|
+
}
|
|
715
|
+
.usage-row strong { color: var(--text); font-weight: 620; }
|
|
716
|
+
.duration-pill { font-size: 0.74rem; background: rgba(255,255,255,0.06); padding: 0.2rem 0.46rem; border-radius: 999px; color: var(--text-muted); border: 1px solid rgba(255,255,255,0.06); }
|
|
717
|
+
|
|
718
|
+
@media (min-width: 640px) {
|
|
719
|
+
body { padding: 28px 18px 56px; }
|
|
720
|
+
.hero { border-radius: 28px; padding: 28px 28px 24px; }
|
|
721
|
+
.metrics-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 18px 0 30px; }
|
|
722
|
+
.card { border-radius: 22px; padding: 18px 18px 16px; }
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
@media (max-width: 720px) {
|
|
726
|
+
.hero, .card, .metric-card { border-radius: 18px; }
|
|
727
|
+
.card-header { flex-direction: column; }
|
|
728
|
+
.card-time { width: 100%; justify-content: space-between; }
|
|
729
|
+
.brand { align-items: flex-start; flex-direction: column; gap: 8px; }
|
|
730
|
+
.brand h1 { font-size: 1.8rem; }
|
|
731
|
+
.hero-meta { flex-direction: column; align-items: stretch; }
|
|
732
|
+
.meta-pill { width: 100%; justify-content: space-between; }
|
|
733
|
+
.usage-row { flex-direction: column; gap: 6px; }
|
|
734
|
+
.code-block { font-size: 0.8rem; padding: 12px; }
|
|
575
735
|
}
|
|
576
736
|
</style>
|
|
577
737
|
</head>
|
|
578
738
|
<body>
|
|
579
739
|
<div class="container">
|
|
580
740
|
<header>
|
|
581
|
-
<div class="
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
<
|
|
587
|
-
<div
|
|
741
|
+
<div class="hero">
|
|
742
|
+
<div class="brand">
|
|
743
|
+
<h1>notrace</h1>
|
|
744
|
+
<span class="brand-tag">retrospective</span>
|
|
745
|
+
</div>
|
|
746
|
+
<p class="hero-subtitle">A local evidence view of the run: messages, tools, model calls, timing, and token cost.</p>
|
|
747
|
+
<div class="hero-meta">
|
|
748
|
+
<span class="meta-pill">Session <strong id="sess-id"></strong></span>
|
|
749
|
+
<span class="meta-pill">Started <strong id="sess-time"></strong></span>
|
|
750
|
+
</div>
|
|
588
751
|
</div>
|
|
589
752
|
</header>
|
|
590
753
|
|
|
@@ -611,7 +774,7 @@ function generateHtmlReport(data: any): string {
|
|
|
611
774
|
</div>
|
|
612
775
|
</div>
|
|
613
776
|
|
|
614
|
-
<h2
|
|
777
|
+
<h2 class="section-title">Activity flow</h2>
|
|
615
778
|
<div class="timeline" id="timeline-container">
|
|
616
779
|
<!-- Injected by JS -->
|
|
617
780
|
</div>
|
|
@@ -731,9 +894,9 @@ function generateHtmlReport(data: any): string {
|
|
|
731
894
|
cardHtml += \`<div class="code-block">\${escapeHtml(ev.body)}</div>\`;
|
|
732
895
|
} else if (ev.type === "tool") {
|
|
733
896
|
cardHtml += \`
|
|
734
|
-
<
|
|
897
|
+
<span class="detail-label">Arguments</span>
|
|
735
898
|
<div class="code-block">\${jsonText(ev.args)}</div>
|
|
736
|
-
<
|
|
899
|
+
<span class="detail-label">Result (\${ev.isError ? "Error" : "Success"})</span>
|
|
737
900
|
<div class="code-block">\${jsonText(ev.result)}</div>
|
|
738
901
|
\`;
|
|
739
902
|
} else if (ev.type === "llm") {
|
|
@@ -767,16 +930,16 @@ function generateHtmlReport(data: any): string {
|
|
|
767
930
|
messagesHtml += '</div>';
|
|
768
931
|
|
|
769
932
|
cardHtml += \`
|
|
770
|
-
<
|
|
933
|
+
<span class="detail-label">Context messages</span>
|
|
771
934
|
\${messagesHtml}
|
|
772
|
-
<
|
|
935
|
+
<span class="detail-label">Generated response</span>
|
|
773
936
|
<div class="code-block">\${jsonText(ev.output)}</div>
|
|
774
937
|
\${ev.usage ? \`
|
|
775
|
-
<div
|
|
776
|
-
<span>Input
|
|
777
|
-
<span>Output
|
|
778
|
-
<span>Total
|
|
779
|
-
<span>Cost
|
|
938
|
+
<div class="usage-row">
|
|
939
|
+
<span>Input tokens <strong>\${escapeHtml(ev.usage.input ?? 0)}</strong></span>
|
|
940
|
+
<span>Output tokens <strong>\${escapeHtml(ev.usage.output ?? 0)}</strong></span>
|
|
941
|
+
<span>Total tokens <strong>\${escapeHtml(ev.usage.totalTokens ?? 0)}</strong></span>
|
|
942
|
+
<span>Cost <strong>\$\${escapeHtml(ev.usage.cost?.total?.toFixed?.(5) || "0.00")}</strong></span>
|
|
780
943
|
</div>
|
|
781
944
|
\` : ""}
|
|
782
945
|
\`;
|