agent-trace 0.2.1 → 0.2.3
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/agent-trace.cjs +551 -398
- package/package.json +1 -1
package/agent-trace.cjs
CHANGED
|
@@ -24008,439 +24008,579 @@ var import_node_fs5 = __toESM(require("node:fs"));
|
|
|
24008
24008
|
var import_node_http = __toESM(require("node:http"));
|
|
24009
24009
|
|
|
24010
24010
|
// packages/dashboard/src/web-render.ts
|
|
24011
|
-
function escapeHtml(value) {
|
|
24012
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
24013
|
-
}
|
|
24014
24011
|
function renderDashboardHtml(options = {}) {
|
|
24015
24012
|
const title = options.title ?? "agent-trace dashboard";
|
|
24016
|
-
const safeTitle = escapeHtml(title);
|
|
24017
24013
|
return `<!doctype html>
|
|
24018
24014
|
<html lang="en">
|
|
24019
|
-
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
|
|
24033
|
-
|
|
24034
|
-
|
|
24035
|
-
|
|
24036
|
-
|
|
24037
|
-
|
|
24038
|
-
|
|
24039
|
-
|
|
24040
|
-
|
|
24041
|
-
|
|
24042
|
-
|
|
24043
|
-
|
|
24044
|
-
|
|
24045
|
-
|
|
24046
|
-
|
|
24047
|
-
|
|
24048
|
-
|
|
24049
|
-
|
|
24050
|
-
|
|
24051
|
-
|
|
24052
|
-
|
|
24053
|
-
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
|
|
24068
|
-
|
|
24069
|
-
|
|
24070
|
-
|
|
24071
|
-
|
|
24072
|
-
|
|
24073
|
-
|
|
24074
|
-
|
|
24075
|
-
|
|
24076
|
-
|
|
24015
|
+
<head>
|
|
24016
|
+
<meta charset="utf-8"/>
|
|
24017
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
24018
|
+
<title>${title}</title>
|
|
24019
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark-dimmed.min.css"/>
|
|
24020
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
24021
|
+
<style>
|
|
24022
|
+
:root{--bg:#000;--panel:#0a0a0a;--panel-border:#1a1a1a;--panel-hover:#111;--panel-muted:#060606;--text-primary:#d4d4d4;--text-muted:#666;--text-dim:#444;--line:#1a1a1a;--green:#4ade80;--green-dim:rgba(74,222,128,.12);--orange:#fb923c;--orange-dim:rgba(251,146,60,.12);--red:#f87171;--red-dim:rgba(248,113,113,.12);--purple:#c084fc;--purple-dim:rgba(192,132,252,.1);--cyan:#22d3ee;--cyan-dim:rgba(34,211,238,.08);--yellow:#facc15}
|
|
24023
|
+
*{box-sizing:border-box}
|
|
24024
|
+
html,body{margin:0;padding:0;min-height:100vh;background:var(--bg);color:var(--text-primary);font-family:"SF Mono","JetBrains Mono","Fira Code",ui-monospace,monospace;font-size:13px;-webkit-font-smoothing:antialiased}
|
|
24025
|
+
.shell{max-width:1400px;margin:0 auto;padding:20px 16px 40px}
|
|
24026
|
+
.hero{border:1px solid var(--panel-border);border-radius:8px;padding:16px;background:var(--panel)}
|
|
24027
|
+
.hero h1{margin:0;font-size:16px;font-weight:600;letter-spacing:-.02em}
|
|
24028
|
+
.hero p{margin:4px 0 0;font-size:12px;color:var(--text-muted)}
|
|
24029
|
+
.status-banner{margin-top:10px;padding:8px 10px;border-radius:6px;border:1px solid var(--line);background:var(--panel-muted);color:var(--text-muted);font-size:12px}
|
|
24030
|
+
.status-banner.warning{border-color:rgba(250,204,21,.4);color:var(--yellow)}
|
|
24031
|
+
.mg{margin-top:14px;display:grid;gap:8px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
|
24032
|
+
.mc{border:1px solid var(--panel-border);border-radius:8px;padding:10px 12px;background:var(--panel)}
|
|
24033
|
+
.mc .label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim)}
|
|
24034
|
+
.mc .val{margin-top:6px;font-size:18px;font-weight:700;font-variant-numeric:tabular-nums}
|
|
24035
|
+
.mc .det{font-size:10px;color:var(--text-dim);margin-top:2px}
|
|
24036
|
+
.green{color:var(--green)}.cyan{color:var(--cyan)}.orange{color:var(--orange)}.red{color:var(--red)}.purple{color:var(--purple)}.yellow{color:var(--yellow)}
|
|
24037
|
+
.sg{margin-top:14px;display:grid;gap:10px;grid-template-columns:1.2fr .8fr}
|
|
24038
|
+
.panel{border:1px solid var(--panel-border);border-radius:8px;background:var(--panel);overflow:hidden}
|
|
24039
|
+
.ph{padding:10px 12px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between}
|
|
24040
|
+
.ph h2{margin:0;font-size:13px;font-weight:600;letter-spacing:-.01em}
|
|
24041
|
+
.ph p{margin:2px 0 0;font-size:11px;color:var(--text-dim)}
|
|
24042
|
+
.pc{padding:8px}
|
|
24043
|
+
table{width:100%;border-collapse:collapse}
|
|
24044
|
+
th,td{border-bottom:1px solid var(--line);text-align:left;padding:7px 8px;font-size:12px}
|
|
24045
|
+
th{color:var(--text-dim);font-size:10px;text-transform:uppercase;letter-spacing:.08em;font-weight:500}
|
|
24046
|
+
.srow{cursor:pointer}.srow:hover{background:var(--panel-hover)}.srow.active{background:var(--green-dim)}
|
|
24047
|
+
.badge{display:inline-flex;align-items:center;border-radius:4px;padding:1px 6px;font-size:11px;border:1px solid var(--line);color:var(--text-muted);font-variant-numeric:tabular-nums}
|
|
24048
|
+
.badge.green{border-color:rgba(74,222,128,.3);color:var(--green)}
|
|
24049
|
+
.badge.orange{border-color:rgba(251,146,60,.3);color:var(--orange)}
|
|
24050
|
+
.badge.red{border-color:rgba(248,113,113,.3);color:var(--red)}
|
|
24051
|
+
.badge.purple{border-color:rgba(192,132,252,.3);color:var(--purple)}
|
|
24052
|
+
.badge.cyan{border-color:rgba(34,211,238,.3);color:var(--cyan)}
|
|
24053
|
+
.badge.dim{border-color:var(--line);color:var(--text-dim)}
|
|
24054
|
+
.badge.commit{border-color:rgba(250,204,21,.3);color:var(--yellow)}
|
|
24055
|
+
.ls{font-size:11px;font-variant-numeric:tabular-nums}
|
|
24056
|
+
.ls.green{color:var(--green);margin-right:4px}.ls.red{color:var(--red)}
|
|
24057
|
+
.repo-cell{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
24058
|
+
.tm{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}
|
|
24059
|
+
.tmi{border:1px solid var(--line);border-radius:6px;padding:4px 8px;font-size:11px;color:var(--text-muted)}
|
|
24060
|
+
.chart{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:6px;align-items:end;min-height:160px}
|
|
24061
|
+
.chart-col{display:flex;flex-direction:column;align-items:stretch;justify-content:end;gap:4px}
|
|
24062
|
+
.chart-bar{width:100%;border-radius:4px 4px 1px 1px;background:linear-gradient(180deg,var(--green),#166534);min-height:3px}
|
|
24063
|
+
.chart-label{font-size:10px;color:var(--text-dim);text-align:center}
|
|
24064
|
+
.chart-value{font-size:11px;color:var(--text-muted);text-align:center;font-variant-numeric:tabular-nums}
|
|
24065
|
+
.empty{padding:12px;color:var(--text-dim);font-size:12px}
|
|
24066
|
+
.pg{border:1px solid var(--panel-border);border-radius:8px;margin-bottom:8px;background:var(--panel-muted);overflow:hidden}
|
|
24067
|
+
.pg.expanded{border-color:#222}
|
|
24068
|
+
.pg-hd{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;cursor:pointer;user-select:none}
|
|
24069
|
+
.pg-hd:hover{background:var(--panel-hover)}
|
|
24070
|
+
.pg-idx{flex-shrink:0;width:28px;height:22px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;background:var(--green-dim);color:var(--green);border:1px solid rgba(74,222,128,.2)}
|
|
24071
|
+
.pg-txt{flex:1;min-width:0;font-size:13px;color:var(--text-primary);line-height:1.5;white-space:pre-wrap;word-break:break-word}
|
|
24072
|
+
.pg-txt.trunc{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
24073
|
+
.pg-stats{flex-shrink:0;display:flex;gap:6px;align-items:center;font-size:11px;color:var(--text-dim);font-variant-numeric:tabular-nums}
|
|
24074
|
+
.pg-arrow{flex-shrink:0;font-size:12px;color:var(--text-dim);transition:transform .15s}
|
|
24075
|
+
.pg-arrow.open{transform:rotate(90deg)}
|
|
24076
|
+
.pg-body{border-top:1px solid var(--line)}
|
|
24077
|
+
.pg-full{padding:10px 12px;border-bottom:1px solid var(--line);background:rgba(74,222,128,.04)}
|
|
24078
|
+
.pg-full-label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);margin-bottom:4px}
|
|
24079
|
+
.pg-full-content{font-size:12px;color:var(--text-primary);line-height:1.6;white-space:pre-wrap;word-break:break-word;max-height:200px;overflow-y:auto}
|
|
24080
|
+
.erow{display:grid;grid-template-columns:18px 1fr auto;gap:8px;align-items:start;padding:6px 12px;border-bottom:1px solid var(--line);font-size:12px;min-height:32px}
|
|
24081
|
+
.erow:last-child{border-bottom:none}
|
|
24082
|
+
.eicon{width:18px;height:18px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:10px;flex-shrink:0;margin-top:1px}
|
|
24083
|
+
.eicon.tool{background:var(--purple-dim);color:var(--purple);border:1px solid rgba(192,132,252,.2)}
|
|
24084
|
+
.eicon.api{background:var(--cyan-dim);color:var(--cyan);border:1px solid rgba(34,211,238,.15)}
|
|
24085
|
+
.eicon.error{background:var(--red-dim);color:var(--red);border:1px solid rgba(248,113,113,.2)}
|
|
24086
|
+
.econtent{min-width:0}
|
|
24087
|
+
.elabel{color:var(--text-primary);font-weight:500}
|
|
24088
|
+
.edetail{margin-top:2px;color:var(--text-muted);font-size:11px;line-height:1.5;white-space:pre-wrap;word-break:break-word}
|
|
24089
|
+
.emeta{display:flex;gap:6px;align-items:center;flex-shrink:0;font-size:11px;color:var(--text-dim);font-variant-numeric:tabular-nums}
|
|
24090
|
+
.rblock{padding:10px 12px;border-top:1px solid var(--line);background:rgba(74,222,128,.03)}
|
|
24091
|
+
.rblock-label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);margin-bottom:4px}
|
|
24092
|
+
.rblock-text{font-size:12px;color:var(--text-primary);line-height:1.6;white-space:pre-wrap;word-break:break-word;max-height:400px;overflow-y:auto}
|
|
24093
|
+
.tool-fp{display:inline-block;font-size:11px;color:var(--cyan);padding:1px 5px;border-radius:3px;background:var(--cyan-dim);margin-bottom:4px}
|
|
24094
|
+
.tool-pat{display:inline-block;font-size:11px;color:var(--purple);padding:1px 5px;border-radius:3px;background:var(--purple-dim);margin-right:4px}
|
|
24095
|
+
.cblock{border:1px solid var(--line);border-radius:6px;overflow:hidden;margin-top:4px;margin-bottom:4px;background:#050505}
|
|
24096
|
+
.cblock-hd{padding:3px 8px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);background:#0d0d0d;border-bottom:1px solid var(--line)}
|
|
24097
|
+
.cblock pre{margin:0;padding:8px 10px;font-size:11px;line-height:1.6;color:var(--text-primary);overflow-x:auto;max-height:300px;overflow-y:auto;white-space:pre;tab-size:2}
|
|
24098
|
+
.cblock pre code{font-family:inherit}
|
|
24099
|
+
.diff-block{border:1px solid var(--line);border-radius:6px;overflow:hidden;margin-top:4px;margin-bottom:4px;background:#050505}
|
|
24100
|
+
.diff-rm{display:flex;border-bottom:1px solid var(--line);background:rgba(248,113,113,.06)}
|
|
24101
|
+
.diff-add{display:flex;background:rgba(74,222,128,.06)}
|
|
24102
|
+
.diff-lbl{flex-shrink:0;width:24px;padding:6px 0;text-align:center;font-size:11px;font-weight:700;user-select:none}
|
|
24103
|
+
.diff-rm .diff-lbl{color:var(--red)}.diff-add .diff-lbl{color:var(--green)}
|
|
24104
|
+
.diff-rm pre,.diff-add pre{margin:0;padding:6px 8px;font-size:11px;line-height:1.5;color:var(--text-primary);overflow-x:auto;max-height:200px;overflow-y:auto;white-space:pre;tab-size:2;flex:1;min-width:0}
|
|
24105
|
+
.fsummary{padding:8px 12px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:4px}
|
|
24106
|
+
.fsg{display:flex;flex-wrap:wrap;align-items:center;gap:4px}
|
|
24107
|
+
.fsg-label{font-size:10px;text-transform:uppercase;letter-spacing:.06em;padding:1px 5px;border-radius:3px;font-weight:600}
|
|
24108
|
+
.fsg-label.written{color:var(--green);background:var(--green-dim)}
|
|
24109
|
+
.fsg-label.read{color:var(--text-muted);background:rgba(255,255,255,.04)}
|
|
24110
|
+
.fsg-path{font-size:11px;color:var(--text-muted);padding:1px 5px;border-radius:3px;background:rgba(255,255,255,.03)}
|
|
24111
|
+
.outcome{border:1px solid var(--panel-border);border-radius:6px;padding:10px 12px;margin-bottom:12px;background:var(--panel-muted)}
|
|
24112
|
+
.outcome-hd{font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}
|
|
24113
|
+
.outcome-row{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:8px}
|
|
24114
|
+
.outcome-item{display:flex;align-items:center;gap:4px}
|
|
24115
|
+
.outcome-lbl{font-size:11px;color:var(--text-dim)}
|
|
24116
|
+
.outcome-val{font-size:12px;color:var(--text-primary)}
|
|
24117
|
+
.outcome-commits{border-top:1px solid var(--panel-border);padding-top:6px;margin-top:4px}
|
|
24118
|
+
.outcome-cr{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px}
|
|
24119
|
+
.commit-sha{color:var(--yellow);font-weight:600;min-width:56px}
|
|
24120
|
+
.commit-msg{color:var(--text-primary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
24121
|
+
.commit-pl{font-size:10px;color:var(--text-dim)}
|
|
24122
|
+
.outcome-prs{border-top:1px solid var(--panel-border);padding-top:6px;margin-top:4px}
|
|
24123
|
+
.outcome-pr{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px}
|
|
24124
|
+
.pr-badge{font-size:10px;padding:1px 5px;border-radius:3px;font-weight:600;text-transform:uppercase;border:1px solid rgba(74,222,128,.3);color:var(--green)}
|
|
24125
|
+
.pr-label{color:var(--cyan);font-weight:600}
|
|
24126
|
+
.pr-repo{color:var(--text-muted)}
|
|
24127
|
+
.pr-link{color:var(--text-dim);text-decoration:none;font-size:11px}
|
|
24128
|
+
.pr-link:hover{color:var(--cyan);text-decoration:underline}
|
|
24129
|
+
.pcommits{border:1px solid rgba(250,204,21,.15);border-radius:4px;padding:6px 8px;margin-bottom:8px;background:rgba(250,204,21,.04)}
|
|
24130
|
+
.pcommit{display:flex;align-items:center;gap:8px;padding:2px 0;font-size:12px}
|
|
24131
|
+
.hljs{background:transparent!important;color:var(--text-primary)}
|
|
24132
|
+
.hljs-keyword,.hljs-selector-tag,.hljs-built_in,.hljs-name{color:#c084fc}
|
|
24133
|
+
.hljs-string,.hljs-attr{color:#4ade80}
|
|
24134
|
+
.hljs-number,.hljs-literal{color:#fb923c}
|
|
24135
|
+
.hljs-comment{color:#555;font-style:italic}
|
|
24136
|
+
.hljs-type,.hljs-class .hljs-title,.hljs-title.class_{color:#22d3ee}
|
|
24137
|
+
.hljs-function .hljs-title,.hljs-title.function_{color:#60a5fa}
|
|
24138
|
+
.hljs-variable,.hljs-template-variable{color:#f87171}
|
|
24139
|
+
.hljs-regexp{color:#fbbf24}
|
|
24140
|
+
.hljs-meta{color:#666}
|
|
24141
|
+
.hljs-punctuation{color:#888}
|
|
24142
|
+
.hljs-property{color:#93c5fd}
|
|
24143
|
+
.hljs-addition{color:#4ade80;background:rgba(74,222,128,.08)}
|
|
24144
|
+
.hljs-deletion{color:#f87171;background:rgba(248,113,113,.08)}
|
|
24145
|
+
@media(max-width:1200px){.mg{grid-template-columns:repeat(2,minmax(0,1fr))}.sg{grid-template-columns:1fr}}
|
|
24146
|
+
@media(max-width:760px){.shell{padding:12px 8px 24px}.mg{grid-template-columns:1fr}th:nth-child(5),td:nth-child(5),th:nth-child(7),td:nth-child(7){display:none}.erow{grid-template-columns:18px 1fr}.emeta{grid-column:2}.pg-stats{display:none}}
|
|
24147
|
+
</style>
|
|
24148
|
+
</head>
|
|
24149
|
+
<body>
|
|
24150
|
+
<main class="shell">
|
|
24151
|
+
<section class="hero">
|
|
24152
|
+
<h1>${title}</h1>
|
|
24153
|
+
<p>session observability for coding agents</p>
|
|
24154
|
+
<div id="status" class="status-banner">Connecting...</div>
|
|
24155
|
+
</section>
|
|
24156
|
+
<section class="mg">
|
|
24157
|
+
<article class="mc"><div class="label">Sessions</div><div class="val green" id="m-sessions">0</div></article>
|
|
24158
|
+
<article class="mc"><div class="label">Total Cost</div><div class="val orange" id="m-cost">$0.00</div></article>
|
|
24159
|
+
<article class="mc"><div class="label">Prompts</div><div class="val cyan" id="m-prompts">0</div></article>
|
|
24160
|
+
<article class="mc"><div class="label">Tool Calls</div><div class="val" id="m-tools">0</div></article>
|
|
24161
|
+
<article class="mc"><div class="label">Commits</div><div class="val green" id="m-commits">0</div><div class="det" id="m-commits-det"></div></article>
|
|
24162
|
+
</section>
|
|
24163
|
+
<section class="sg">
|
|
24164
|
+
<section class="panel">
|
|
24165
|
+
<header class="ph"><div><h2>Sessions</h2><p id="stream-label">...</p></div></header>
|
|
24166
|
+
<div class="pc"><div id="sessions-area" class="empty">Loading...</div></div>
|
|
24167
|
+
</section>
|
|
24168
|
+
<section class="panel">
|
|
24169
|
+
<header class="ph"><div><h2>Daily Cost</h2><p>7-day spend</p></div></header>
|
|
24170
|
+
<div class="pc"><div id="cost-chart" class="empty">Loading...</div></div>
|
|
24171
|
+
</section>
|
|
24172
|
+
</section>
|
|
24173
|
+
<section class="panel" style="margin-top:10px">
|
|
24174
|
+
<header class="ph"><div><h2>Session Replay</h2><p id="replay-label">select a session</p></div></header>
|
|
24175
|
+
<div class="pc" id="replay-area"><div class="empty">No session selected.</div></div>
|
|
24176
|
+
</section>
|
|
24177
|
+
</main>
|
|
24178
|
+
<script>
|
|
24179
|
+
(function(){
|
|
24180
|
+
var DQ = String.fromCharCode(34);
|
|
24181
|
+
var selectedId = null;
|
|
24182
|
+
var sessions = [];
|
|
24183
|
+
var costPoints = [];
|
|
24184
|
+
var replay = null;
|
|
24077
24185
|
|
|
24078
|
-
|
|
24079
|
-
|
|
24080
|
-
|
|
24081
|
-
|
|
24082
|
-
|
|
24186
|
+
function esc(s) {
|
|
24187
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(new RegExp(DQ,'g'),'"');
|
|
24188
|
+
}
|
|
24189
|
+
function fmt$(v) { return '$' + v.toFixed(2); }
|
|
24190
|
+
function fmt$4(v) { return '$' + v.toFixed(4); }
|
|
24191
|
+
function ensureUtc(v) {
|
|
24192
|
+
if (v.endsWith('Z') || /[+-]\\d{2}:\\d{2}$/.test(v)) return v;
|
|
24193
|
+
return v.includes('T') ? v + 'Z' : v.replace(' ','T') + 'Z';
|
|
24194
|
+
}
|
|
24195
|
+
function fmtDate(v) { try { return new Date(ensureUtc(v)).toLocaleString(); } catch(e) { return v; } }
|
|
24196
|
+
function fmtTime(v) { try { return new Date(ensureUtc(v)).toLocaleTimeString(); } catch(e) { return v; } }
|
|
24197
|
+
function fmtDur(ms) { return ms < 1000 ? ms + 'ms' : (ms/1000).toFixed(1) + 's'; }
|
|
24083
24198
|
|
|
24084
|
-
|
|
24085
|
-
|
|
24086
|
-
|
|
24087
|
-
|
|
24088
|
-
color: #52525b;
|
|
24089
|
-
}
|
|
24199
|
+
function readStr(r, k) { var v = r[k]; return typeof v === 'string' && v.length > 0 ? v : undefined; }
|
|
24200
|
+
function readNum(r, k) { var v = r[k]; return typeof v === 'number' && isFinite(v) ? v : undefined; }
|
|
24201
|
+
function readArr(r, k) { var v = r[k]; return Array.isArray(v) ? v : []; }
|
|
24202
|
+
function asRec(v) { return typeof v === 'object' && v !== null && !Array.isArray(v) ? v : undefined; }
|
|
24090
24203
|
|
|
24091
|
-
|
|
24092
|
-
|
|
24093
|
-
|
|
24094
|
-
|
|
24095
|
-
|
|
24204
|
+
var EXT_MAP = {ts:'typescript',tsx:'typescript',js:'javascript',jsx:'javascript',py:'python',rb:'ruby',go:'go',rs:'rust',java:'java',css:'css',html:'html',json:'json',yaml:'yaml',yml:'yaml',md:'markdown',sh:'bash',bash:'bash',sql:'sql',toml:'yaml'};
|
|
24205
|
+
function guessLang(fp) { var e = (fp.split('.').pop()||'').toLowerCase(); return EXT_MAP[e] || e; }
|
|
24206
|
+
function highlight(code, lang) {
|
|
24207
|
+
if (typeof hljs === 'undefined') return esc(code);
|
|
24208
|
+
try { var r = hljs.highlight(code, {language: lang || 'text', ignoreIllegals: true}); return r.value; } catch(e) { return esc(code); }
|
|
24209
|
+
}
|
|
24096
24210
|
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
border: 2px solid var(--ink);
|
|
24100
|
-
background: #fff;
|
|
24101
|
-
box-shadow: 8px 8px 0 var(--ink);
|
|
24102
|
-
overflow: hidden;
|
|
24103
|
-
}
|
|
24211
|
+
var READ_TOOLS = {Read:1,Glob:1,Grep:1,Search:1};
|
|
24212
|
+
var WRITE_TOOLS = {Write:1,Edit:1,NotebookEdit:1};
|
|
24104
24213
|
|
|
24105
|
-
|
|
24106
|
-
|
|
24107
|
-
|
|
24108
|
-
|
|
24109
|
-
|
|
24110
|
-
|
|
24214
|
+
function parseReplay(val) {
|
|
24215
|
+
var r = asRec(val); if (!r) return null;
|
|
24216
|
+
var sid = readStr(r,'sessionId'), sa = readStr(r,'startedAt'), m = asRec(r.metrics), tl = r.timeline;
|
|
24217
|
+
if (!sid || !sa || !m || !Array.isArray(tl)) return null;
|
|
24218
|
+
var envR = asRec(r.environment), gitR = asRec(r.git);
|
|
24219
|
+
var gitBranch = (envR ? readStr(envR,'gitBranch') : undefined) || readStr(r,'gitBranch');
|
|
24220
|
+
var cRaw = (gitR && Array.isArray(gitR.commits)) ? gitR.commits : Array.isArray(r.commits) ? r.commits : [];
|
|
24221
|
+
var commits = cRaw.map(function(e){var c=asRec(e);if(!c)return null;var sha=readStr(c,'sha');if(!sha||sha.indexOf('placeholder_')===0)return null;return{sha:sha,message:readStr(c,'message'),promptId:readStr(c,'promptId'),committedAt:readStr(c,'committedAt')};}).filter(Boolean);
|
|
24222
|
+
var pRaw = (gitR && Array.isArray(gitR.pullRequests)) ? gitR.pullRequests : Array.isArray(r.pullRequests) ? r.pullRequests : [];
|
|
24223
|
+
var prs = pRaw.map(function(e){var p=asRec(e);if(!p)return null;var repo=readStr(p,'repo'),n=readNum(p,'prNumber');if(!repo||n===undefined)return null;return{repo:repo,prNumber:n,state:readStr(p,'state')||'open',url:readStr(p,'url')};}).filter(Boolean);
|
|
24224
|
+
var timeline = tl.map(function(e){
|
|
24225
|
+
var ev=asRec(e);if(!ev)return null;
|
|
24226
|
+
var id=readStr(ev,'id'),type=readStr(ev,'type'),ts=readStr(ev,'timestamp');if(!id||!type||!ts)return null;
|
|
24227
|
+
var d=asRec(ev.details),tok=asRec(ev.tokens);
|
|
24228
|
+
return{id:id,type:type,timestamp:ts,promptId:readStr(ev,'promptId'),status:readStr(ev,'status'),costUsd:readNum(ev,'costUsd'),toolName:d?readStr(d,'toolName'):undefined,toolDurationMs:d?readNum(d,'toolDurationMs'):undefined,inputTokens:tok?readNum(tok,'input'):undefined,outputTokens:tok?readNum(tok,'output'):undefined,details:d};
|
|
24229
|
+
}).filter(Boolean);
|
|
24230
|
+
return{sessionId:sid,startedAt:sa,endedAt:readStr(r,'endedAt'),gitBranch:gitBranch,
|
|
24231
|
+
metrics:{promptCount:readNum(m,'promptCount')||0,toolCallCount:readNum(m,'toolCallCount')||0,totalCostUsd:readNum(m,'totalCostUsd')||0,totalInputTokens:readNum(m,'totalInputTokens')||0,totalOutputTokens:readNum(m,'totalOutputTokens')||0,linesAdded:readNum(m,'linesAdded')||0,linesRemoved:readNum(m,'linesRemoved')||0,modelsUsed:readArr(m,'modelsUsed').filter(function(x){return typeof x==='string'}),toolsUsed:readArr(m,'toolsUsed').filter(function(x){return typeof x==='string'}),filesTouched:readArr(m,'filesTouched').filter(function(x){return typeof x==='string'})},
|
|
24232
|
+
commits:commits,pullRequests:prs,timeline:timeline};
|
|
24233
|
+
}
|
|
24111
24234
|
|
|
24112
|
-
|
|
24113
|
-
|
|
24114
|
-
|
|
24115
|
-
|
|
24235
|
+
function extractToolDetail(ev) {
|
|
24236
|
+
var d = ev.details, tn = ev.toolName || ev.type;
|
|
24237
|
+
if (!d) return {toolName:tn};
|
|
24238
|
+
var rawTi = d.toolInput || d.tool_input, inp = asRec(rawTi), tiStr = typeof rawTi === 'string' ? rawTi : undefined;
|
|
24239
|
+
function rs(k) { return (d ? readStr(d,k) : undefined) || (inp ? readStr(inp,k) : undefined) || (tiStr ? extractFromTruncJson(tiStr,k) : undefined); }
|
|
24240
|
+
return {toolName:tn,filePath:rs('filePath')||rs('file_path'),command:rs('command')||rs('cmd'),pattern:rs('pattern'),oldString:(d?readStr(d,'oldString'):undefined)||(inp?readStr(inp,'old_string'):undefined),newString:(d?readStr(d,'newString'):undefined)||(inp?readStr(inp,'new_string'):undefined),writeContent:d?readStr(d,'writeContent'):undefined,description:rs('description')};
|
|
24241
|
+
}
|
|
24116
24242
|
|
|
24117
|
-
|
|
24118
|
-
|
|
24119
|
-
|
|
24120
|
-
|
|
24121
|
-
font-size: 0.95rem;
|
|
24122
|
-
}
|
|
24243
|
+
function extractFromTruncJson(raw, key) {
|
|
24244
|
+
var re = new RegExp(DQ + key + DQ + '\\\\s*:\\\\s*' + DQ + '([^' + DQ + ']*?)' + DQ);
|
|
24245
|
+
var m = re.exec(raw); return m && m[1] && m[1].length > 0 ? m[1] : undefined;
|
|
24246
|
+
}
|
|
24123
24247
|
|
|
24124
|
-
|
|
24125
|
-
|
|
24126
|
-
|
|
24127
|
-
|
|
24128
|
-
|
|
24129
|
-
|
|
24130
|
-
|
|
24248
|
+
function renderToolDetail(ev) {
|
|
24249
|
+
var td = extractToolDetail(ev);
|
|
24250
|
+
if (td.toolName === 'Bash' && td.command) return '<div class="cblock"><div class="cblock-hd">bash</div><pre><code class="language-bash">' + highlight(td.command,'bash') + '</code></pre></div>';
|
|
24251
|
+
if (td.toolName === 'Edit' && td.filePath) {
|
|
24252
|
+
var h = '<div class="tool-fp">' + esc(td.filePath) + '</div>';
|
|
24253
|
+
if (td.oldString) {
|
|
24254
|
+
h += '<div class="diff-block"><div class="diff-rm"><div class="diff-lbl">-</div><pre><code>' + esc(td.oldString) + '</code></pre></div>';
|
|
24255
|
+
if (td.newString) h += '<div class="diff-add"><div class="diff-lbl">+</div><pre><code>' + esc(td.newString) + '</code></pre></div>';
|
|
24256
|
+
h += '</div>';
|
|
24257
|
+
}
|
|
24258
|
+
return h;
|
|
24259
|
+
}
|
|
24260
|
+
if ((td.toolName === 'Grep' || td.toolName === 'Glob') && td.pattern) {
|
|
24261
|
+
var h2 = '<span class="tool-pat">' + esc(td.toolName === 'Grep' ? '/' + td.pattern + '/' : td.pattern) + '</span>';
|
|
24262
|
+
if (td.filePath) h2 += ' <span class="tool-fp">' + esc(td.filePath) + '</span>';
|
|
24263
|
+
return h2;
|
|
24264
|
+
}
|
|
24265
|
+
if (td.toolName === 'Task' && td.description) return '<span class="edetail">' + esc(td.description) + '</span>';
|
|
24266
|
+
if (td.toolName === 'Write' && td.filePath) {
|
|
24267
|
+
var lang = guessLang(td.filePath);
|
|
24268
|
+
var h3 = '<div class="tool-fp">' + esc(td.filePath) + '</div>';
|
|
24269
|
+
if (td.writeContent) h3 += '<div class="cblock"><div class="cblock-hd">' + esc(lang) + '</div><pre><code class="language-' + esc(lang) + '">' + highlight(td.writeContent,lang) + '</code></pre></div>';
|
|
24270
|
+
return h3;
|
|
24271
|
+
}
|
|
24272
|
+
if (td.filePath) return '<div class="tool-fp">' + esc(td.filePath) + '</div>';
|
|
24273
|
+
if (td.command) return '<div class="cblock"><div class="cblock-hd">shell</div><pre><code class="language-bash">' + highlight(td.command,'bash') + '</code></pre></div>';
|
|
24274
|
+
return '';
|
|
24275
|
+
}
|
|
24131
24276
|
|
|
24132
|
-
|
|
24133
|
-
|
|
24134
|
-
|
|
24135
|
-
|
|
24136
|
-
|
|
24137
|
-
|
|
24277
|
+
function renderEventRow(ev) {
|
|
24278
|
+
var iconCls = ev.status === 'error' ? 'eicon error' : ev.toolName ? 'eicon tool' : 'eicon api';
|
|
24279
|
+
var iconChr = ev.status === 'error' ? '!' : ev.toolName ? 'T' : 'E';
|
|
24280
|
+
var meta = '';
|
|
24281
|
+
if (ev.toolDurationMs !== undefined) meta += '<span class="badge">' + fmtDur(ev.toolDurationMs) + '</span>';
|
|
24282
|
+
if (ev.costUsd !== undefined && ev.costUsd > 0) meta += '<span class="badge orange">' + fmt$4(ev.costUsd) + '</span>';
|
|
24283
|
+
if (ev.status) {
|
|
24284
|
+
var sc = ev.status === 'error' ? 'red' : (ev.status === 'ok' || ev.status === 'success') ? 'green' : '';
|
|
24285
|
+
meta += '<span class="badge ' + sc + '">' + esc(ev.status) + '</span>';
|
|
24286
|
+
}
|
|
24287
|
+
meta += '<span style="color:var(--text-dim)">' + fmtTime(ev.timestamp) + '</span>';
|
|
24288
|
+
return '<div class="erow"><div class="' + iconCls + '">' + iconChr + '</div><div class="econtent"><div class="elabel">' + esc(ev.toolName || ev.type) + '</div>' + renderToolDetail(ev) + '</div><div class="emeta">' + meta + '</div></div>';
|
|
24289
|
+
}
|
|
24138
24290
|
|
|
24139
|
-
|
|
24140
|
-
|
|
24141
|
-
|
|
24142
|
-
|
|
24291
|
+
function buildPromptGroups(timeline, commits) {
|
|
24292
|
+
var byPrompt = {};
|
|
24293
|
+
commits.forEach(function(c){ if(c.promptId){if(!byPrompt[c.promptId])byPrompt[c.promptId]=[];byPrompt[c.promptId].push(c);} });
|
|
24294
|
+
var order = [], map = {};
|
|
24295
|
+
timeline.forEach(function(ev){
|
|
24296
|
+
if(!ev.promptId) return;
|
|
24297
|
+
if(!map[ev.promptId]){order.push(ev.promptId);map[ev.promptId]=[];}
|
|
24298
|
+
map[ev.promptId].push(ev);
|
|
24299
|
+
});
|
|
24300
|
+
return order.map(function(pid){
|
|
24301
|
+
var evts = map[pid] || [], promptText, responseText, cost=0,tools=0,inTok=0,outTok=0,dur=0;
|
|
24302
|
+
var filesR={},filesW={},toolEvts=[];
|
|
24303
|
+
evts.forEach(function(ev){
|
|
24304
|
+
var d = ev.details;
|
|
24305
|
+
if(!promptText && d){var pt=readStr(d,'promptText');if(pt)promptText=pt;}
|
|
24306
|
+
if(ev.type==='assistant_response'||ev.type==='api_call'||ev.type==='api_response'){
|
|
24307
|
+
if(d){var rt=readStr(d,'responseText')||readStr(d,'lastAssistantMessage');if(rt)responseText=rt;}
|
|
24308
|
+
}
|
|
24309
|
+
cost+=(ev.costUsd||0);inTok+=(ev.inputTokens||0);outTok+=(ev.outputTokens||0);
|
|
24310
|
+
if(ev.toolName||(ev.type==='tool_call'||ev.type==='tool_result')){
|
|
24311
|
+
toolEvts.push(ev);tools++;dur+=(ev.toolDurationMs||0);
|
|
24312
|
+
var dd=ev.details,rti=dd?(dd.toolInput||dd.tool_input):undefined,inp=asRec(rti),tiStr=typeof rti==='string'?rti:undefined;
|
|
24313
|
+
var fp=(dd?readStr(dd,'filePath'):undefined)||(inp?readStr(inp,'file_path'):undefined)||(tiStr?extractFromTruncJson(tiStr,'file_path'):undefined);
|
|
24314
|
+
if(fp){var tn=ev.toolName||'';if(WRITE_TOOLS[tn])filesW[fp]=1;else if(READ_TOOLS[tn])filesR[fp]=1;}
|
|
24143
24315
|
}
|
|
24316
|
+
});
|
|
24317
|
+
// deduplicate tool events
|
|
24318
|
+
var deduped = [];
|
|
24319
|
+
toolEvts.forEach(function(ev){
|
|
24320
|
+
if(deduped.length>0){
|
|
24321
|
+
var prev=deduped[deduped.length-1];
|
|
24322
|
+
if(prev.toolName===ev.toolName){
|
|
24323
|
+
var pfp=prev.details?(readStr(prev.details,'filePath')||readStr(asRec(prev.details.toolInput)||{},'file_path')):undefined;
|
|
24324
|
+
var cfp=ev.details?(readStr(ev.details,'filePath')||readStr(asRec(ev.details.toolInput)||{},'file_path')):undefined;
|
|
24325
|
+
if(pfp&&pfp===cfp)return;
|
|
24326
|
+
}
|
|
24327
|
+
}
|
|
24328
|
+
deduped.push(ev);
|
|
24329
|
+
});
|
|
24330
|
+
return{promptId:pid,promptText:promptText,responseText:responseText,toolEvents:deduped,commits:byPrompt[pid]||[],totalCostUsd:cost,totalToolCalls:tools,totalInputTokens:inTok,totalOutputTokens:outTok,totalDurationMs:dur,filesRead:Object.keys(filesR),filesWritten:Object.keys(filesW)};
|
|
24331
|
+
}).filter(function(g){return g.promptText||g.toolEvents.length>0||g.responseText;});
|
|
24332
|
+
}
|
|
24144
24333
|
|
|
24145
|
-
|
|
24146
|
-
|
|
24147
|
-
|
|
24148
|
-
|
|
24149
|
-
|
|
24150
|
-
|
|
24151
|
-
|
|
24152
|
-
|
|
24153
|
-
|
|
24154
|
-
|
|
24155
|
-
</style>
|
|
24156
|
-
</head>
|
|
24157
|
-
<body>
|
|
24158
|
-
<main class="shell">
|
|
24159
|
-
<section class="hero">
|
|
24160
|
-
<h1>${safeTitle}</h1>
|
|
24161
|
-
<p>Session-level observability for coding agents, running locally.</p>
|
|
24162
|
-
</section>
|
|
24163
|
-
|
|
24164
|
-
<section class="grid">
|
|
24165
|
-
<article class="metric">
|
|
24166
|
-
<div class="label">Sessions</div>
|
|
24167
|
-
<div class="value" id="metric-sessions">0</div>
|
|
24168
|
-
</article>
|
|
24169
|
-
<article class="metric">
|
|
24170
|
-
<div class="label">Total Cost (USD)</div>
|
|
24171
|
-
<div class="value" id="metric-cost">$0.00</div>
|
|
24172
|
-
</article>
|
|
24173
|
-
<article class="metric">
|
|
24174
|
-
<div class="label">Latest Start</div>
|
|
24175
|
-
<div class="value" id="metric-latest">-</div>
|
|
24176
|
-
</article>
|
|
24177
|
-
</section>
|
|
24178
|
-
|
|
24179
|
-
<section class="panel">
|
|
24180
|
-
<header>Recent Sessions</header>
|
|
24181
|
-
<table>
|
|
24182
|
-
<thead>
|
|
24183
|
-
<tr>
|
|
24184
|
-
<th>Session</th>
|
|
24185
|
-
<th>User</th>
|
|
24186
|
-
<th>Repo</th>
|
|
24187
|
-
<th>Started</th>
|
|
24188
|
-
<th>Cost</th>
|
|
24189
|
-
<th>Replay</th>
|
|
24190
|
-
</tr>
|
|
24191
|
-
</thead>
|
|
24192
|
-
<tbody id="sessions-body">
|
|
24193
|
-
<tr><td colspan="6">Loading sessions...</td></tr>
|
|
24194
|
-
</tbody>
|
|
24195
|
-
</table>
|
|
24196
|
-
</section>
|
|
24197
|
-
|
|
24198
|
-
<section class="panel">
|
|
24199
|
-
<header>Session Replay</header>
|
|
24200
|
-
<div id="replay-meta" class="status">Select a session to inspect timeline events.</div>
|
|
24201
|
-
<table>
|
|
24202
|
-
<thead>
|
|
24203
|
-
<tr>
|
|
24204
|
-
<th>Timestamp</th>
|
|
24205
|
-
<th>Type</th>
|
|
24206
|
-
<th>Status</th>
|
|
24207
|
-
<th>Cost</th>
|
|
24208
|
-
<th>Prompt</th>
|
|
24209
|
-
</tr>
|
|
24210
|
-
</thead>
|
|
24211
|
-
<tbody id="replay-body">
|
|
24212
|
-
<tr><td colspan="5">No session selected.</td></tr>
|
|
24213
|
-
</tbody>
|
|
24214
|
-
</table>
|
|
24215
|
-
</section>
|
|
24334
|
+
function parseTextSegments(text) {
|
|
24335
|
+
var segs = [], re = /\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g, last = 0, m;
|
|
24336
|
+
while ((m = re.exec(text)) !== null) {
|
|
24337
|
+
if (m.index > last) segs.push({type:'text',content:text.slice(last,m.index)});
|
|
24338
|
+
segs.push({type:'code',lang:m[1]||'text',content:m[2]||''});
|
|
24339
|
+
last = m.index + m[0].length;
|
|
24340
|
+
}
|
|
24341
|
+
if (last < text.length) segs.push({type:'text',content:text.slice(last)});
|
|
24342
|
+
return segs.length > 0 ? segs : [{type:'text',content:text}];
|
|
24343
|
+
}
|
|
24216
24344
|
|
|
24217
|
-
|
|
24218
|
-
|
|
24219
|
-
|
|
24220
|
-
|
|
24221
|
-
|
|
24222
|
-
|
|
24223
|
-
|
|
24224
|
-
const latestMetric = document.getElementById("metric-latest");
|
|
24225
|
-
const replayMeta = document.getElementById("replay-meta");
|
|
24226
|
-
const replayBody = document.getElementById("replay-body");
|
|
24227
|
-
let selectedSessionId = null;
|
|
24345
|
+
function renderFormattedText(text) {
|
|
24346
|
+
var segs = parseTextSegments(text);
|
|
24347
|
+
return segs.map(function(s){
|
|
24348
|
+
if(s.type==='code') return '<div class="cblock"><div class="cblock-hd">' + esc(s.lang) + '</div><pre><code class="language-' + esc(s.lang) + '">' + highlight(s.content,s.lang) + '</code></pre></div>';
|
|
24349
|
+
return '<span>' + esc(s.content) + '</span>';
|
|
24350
|
+
}).join('');
|
|
24351
|
+
}
|
|
24228
24352
|
|
|
24229
|
-
|
|
24230
|
-
|
|
24231
|
-
|
|
24353
|
+
function renderPromptCard(g, idx) {
|
|
24354
|
+
var stats = '';
|
|
24355
|
+
if(g.commits.length>0) stats += '<span class="badge commit">' + (g.commits.length===1?esc(g.commits[0].sha.slice(0,7)):g.commits.length+' commits') + '</span>';
|
|
24356
|
+
if(g.totalToolCalls>0) stats += '<span class="badge purple">' + g.totalToolCalls + ' tools</span>';
|
|
24357
|
+
if(g.filesWritten.length>0) stats += '<span class="badge green">' + g.filesWritten.length + ' written</span>';
|
|
24358
|
+
if(g.filesRead.length>0) stats += '<span class="badge">' + g.filesRead.length + ' read</span>';
|
|
24359
|
+
if(g.totalCostUsd>0) stats += '<span class="badge orange">' + fmt$4(g.totalCostUsd) + '</span>';
|
|
24232
24360
|
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
24361
|
+
var body = '';
|
|
24362
|
+
if(g.commits.length>0){
|
|
24363
|
+
body += '<div class="pcommits">';
|
|
24364
|
+
g.commits.forEach(function(c){body += '<div class="pcommit"><span class="commit-sha">' + esc(c.sha.slice(0,7)) + '</span><span class="commit-msg">' + esc(c.message||'no message') + '</span></div>';});
|
|
24365
|
+
body += '</div>';
|
|
24366
|
+
}
|
|
24367
|
+
g.toolEvents.forEach(function(ev){body += renderEventRow(ev);});
|
|
24368
|
+
if(g.filesWritten.length>0||g.filesRead.length>0){
|
|
24369
|
+
body += '<div class="fsummary">';
|
|
24370
|
+
if(g.filesWritten.length>0){body += '<div class="fsg"><span class="fsg-label written">written</span>';g.filesWritten.forEach(function(f){body += '<span class="fsg-path">' + esc(f) + '</span>';});body += '</div>';}
|
|
24371
|
+
if(g.filesRead.length>0){body += '<div class="fsg"><span class="fsg-label read">read</span>';g.filesRead.forEach(function(f){body += '<span class="fsg-path">' + esc(f) + '</span>';});body += '</div>';}
|
|
24372
|
+
body += '</div>';
|
|
24373
|
+
}
|
|
24374
|
+
if(g.responseText) body += '<div class="rblock"><div class="rblock-label">Response</div><div class="rblock-text">' + renderFormattedText(g.responseText) + '</div></div>';
|
|
24240
24375
|
|
|
24241
|
-
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
|
|
24245
|
-
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
replayBody.innerHTML = "<tr><td colspan=\\"5\\">" + escapeHtml(message) + "</td></tr>";
|
|
24252
|
-
}
|
|
24376
|
+
return '<div class="pg" id="pg-' + esc(g.promptId.slice(0,12)) + '">' +
|
|
24377
|
+
'<div class="pg-hd" onclick="togglePrompt(this)">' +
|
|
24378
|
+
'<div class="pg-idx">' + idx + '</div>' +
|
|
24379
|
+
'<div class="pg-txt trunc">' + esc(g.promptText || 'prompt ' + g.promptId.slice(0,8)) + '</div>' +
|
|
24380
|
+
'<div class="pg-stats">' + stats + '</div>' +
|
|
24381
|
+
'<div class="pg-arrow">></div>' +
|
|
24382
|
+
'</div>' +
|
|
24383
|
+
'<div class="pg-body" style="display:none">' + body + '</div>' +
|
|
24384
|
+
'</div>';
|
|
24385
|
+
}
|
|
24253
24386
|
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24387
|
+
window.togglePrompt = function(hd) {
|
|
24388
|
+
var pg = hd.parentElement;
|
|
24389
|
+
var body = pg.querySelector('.pg-body');
|
|
24390
|
+
var txt = pg.querySelector('.pg-txt');
|
|
24391
|
+
var arrow = pg.querySelector('.pg-arrow');
|
|
24392
|
+
var open = body.style.display !== 'none';
|
|
24393
|
+
body.style.display = open ? 'none' : '';
|
|
24394
|
+
pg.classList.toggle('expanded', !open);
|
|
24395
|
+
txt.classList.toggle('trunc', open);
|
|
24396
|
+
arrow.classList.toggle('open', !open);
|
|
24397
|
+
};
|
|
24261
24398
|
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24399
|
+
function renderSessions() {
|
|
24400
|
+
var area = document.getElementById('sessions-area');
|
|
24401
|
+
if (sessions.length === 0) { area.innerHTML = '<div class="empty">No sessions captured yet.</div>'; return; }
|
|
24402
|
+
var h = '<table><thead><tr><th>Session</th><th>Repo</th><th>Started</th><th>Prompts</th><th>Cost</th><th>Commits</th><th>Lines</th></tr></thead><tbody>';
|
|
24403
|
+
sessions.forEach(function(s){
|
|
24404
|
+
var active = s.sessionId === selectedId ? ' active' : '';
|
|
24405
|
+
var repo = s.gitRepo ? (s.gitBranch ? s.gitRepo + '/' + s.gitBranch : s.gitRepo) : '-';
|
|
24406
|
+
var commits = s.commitCount > 0 ? '<span class="badge green">' + s.commitCount + '</span>' : '<span class="badge dim">0</span>';
|
|
24407
|
+
var lines = (s.linesAdded > 0 || s.linesRemoved > 0) ? '<span class="ls green">+' + s.linesAdded + '</span><span class="ls red">-' + s.linesRemoved + '</span>' : '<span style="color:var(--text-dim)">-</span>';
|
|
24408
|
+
h += '<tr class="srow' + active + '" data-sid="' + esc(s.sessionId) + '" onclick="selectSession(this.dataset.sid)"><td>' + esc(s.sessionId.slice(0,10)) + '</td><td class="repo-cell">' + esc(repo) + '</td><td>' + fmtDate(s.startedAt) + '</td><td>' + s.promptCount + '</td><td>' + fmt$(s.totalCostUsd) + '</td><td>' + commits + '</td><td>' + lines + '</td></tr>';
|
|
24409
|
+
});
|
|
24410
|
+
h += '</tbody></table>';
|
|
24411
|
+
area.innerHTML = h;
|
|
24412
|
+
}
|
|
24269
24413
|
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
24273
|
-
|
|
24414
|
+
function renderMetrics() {
|
|
24415
|
+
document.getElementById('m-sessions').textContent = sessions.length;
|
|
24416
|
+
document.getElementById('m-cost').textContent = fmt$(sessions.reduce(function(s,x){return s+x.totalCostUsd;},0));
|
|
24417
|
+
document.getElementById('m-prompts').textContent = sessions.reduce(function(s,x){return s+x.promptCount;},0);
|
|
24418
|
+
document.getElementById('m-tools').textContent = sessions.reduce(function(s,x){return s+x.toolCallCount;},0);
|
|
24419
|
+
var tc = sessions.reduce(function(s,x){return s+x.commitCount;},0);
|
|
24420
|
+
var sc = sessions.filter(function(x){return x.commitCount>0;}).length;
|
|
24421
|
+
document.getElementById('m-commits').textContent = tc;
|
|
24422
|
+
document.getElementById('m-commits-det').textContent = sc + '/' + sessions.length + ' sessions produced commits';
|
|
24423
|
+
}
|
|
24274
24424
|
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
|
|
24285
|
-
|
|
24286
|
-
|
|
24287
|
-
|
|
24288
|
-
}).join("");
|
|
24289
|
-
replayBody.innerHTML = rows;
|
|
24290
|
-
}
|
|
24425
|
+
function renderCostChart() {
|
|
24426
|
+
var el = document.getElementById('cost-chart');
|
|
24427
|
+
if (costPoints.length === 0) { el.innerHTML = '<div class="empty">No cost data yet.</div>'; return; }
|
|
24428
|
+
var pts = costPoints.slice(-7);
|
|
24429
|
+
var max = Math.max(0.01, Math.max.apply(null, pts.map(function(p){return p.totalCostUsd;})));
|
|
24430
|
+
var h = '<div class="chart">';
|
|
24431
|
+
pts.forEach(function(p){
|
|
24432
|
+
var ht = Math.max(4, Math.round((p.totalCostUsd / max) * 140));
|
|
24433
|
+
h += '<div class="chart-col"><div class="chart-bar" style="height:' + ht + 'px"></div><div class="chart-value">' + fmt$(p.totalCostUsd) + '</div><div class="chart-label">' + esc(p.date.slice(5)) + '</div></div>';
|
|
24434
|
+
});
|
|
24435
|
+
h += '</div>';
|
|
24436
|
+
el.innerHTML = h;
|
|
24437
|
+
}
|
|
24291
24438
|
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
|
|
24295
|
-
|
|
24296
|
-
|
|
24297
|
-
|
|
24298
|
-
|
|
24299
|
-
|
|
24300
|
-
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
|
|
24304
|
-
|
|
24305
|
-
|
|
24306
|
-
|
|
24307
|
-
|
|
24308
|
-
|
|
24309
|
-
|
|
24310
|
-
|
|
24311
|
-
|
|
24312
|
-
|
|
24313
|
-
|
|
24314
|
-
|
|
24315
|
-
|
|
24439
|
+
function renderReplay() {
|
|
24440
|
+
var area = document.getElementById('replay-area');
|
|
24441
|
+
var label = document.getElementById('replay-label');
|
|
24442
|
+
if (!replay) {
|
|
24443
|
+
label.textContent = selectedId ? selectedId.slice(0,12) : 'select a session';
|
|
24444
|
+
area.innerHTML = '<div class="empty">No replay data.</div>';
|
|
24445
|
+
return;
|
|
24446
|
+
}
|
|
24447
|
+
label.textContent = replay.sessionId.slice(0,12) + ' \\u2014 ' + replay.metrics.promptCount + ' prompts, ' + fmt$(replay.metrics.totalCostUsd);
|
|
24448
|
+
var h = '';
|
|
24449
|
+
// meta
|
|
24450
|
+
h += '<div class="tm">';
|
|
24451
|
+
h += '<span class="tmi">Cost <span class="badge orange">' + fmt$4(replay.metrics.totalCostUsd) + '</span></span>';
|
|
24452
|
+
h += '<span class="tmi">Tokens <span class="badge cyan">' + replay.metrics.totalInputTokens + ' in / ' + replay.metrics.totalOutputTokens + ' out</span></span>';
|
|
24453
|
+
if (replay.metrics.linesAdded > 0 || replay.metrics.linesRemoved > 0) h += '<span class="tmi">Lines <span class="badge green">+' + replay.metrics.linesAdded + '</span> <span class="badge red">-' + replay.metrics.linesRemoved + '</span></span>';
|
|
24454
|
+
if (replay.metrics.modelsUsed.length > 0) h += '<span class="tmi">' + esc(replay.metrics.modelsUsed.join(', ')) + '</span>';
|
|
24455
|
+
if (replay.metrics.filesTouched.length > 0) h += '<span class="tmi">' + replay.metrics.filesTouched.length + ' files</span>';
|
|
24456
|
+
h += '</div>';
|
|
24457
|
+
// outcome
|
|
24458
|
+
if (replay.commits.length > 0 || replay.pullRequests.length > 0 || replay.gitBranch) {
|
|
24459
|
+
h += '<div class="outcome"><div class="outcome-hd">Outcome</div><div class="outcome-row">';
|
|
24460
|
+
if (replay.gitBranch) h += '<span class="outcome-item"><span class="outcome-lbl">branch</span><span class="outcome-val">' + esc(replay.gitBranch) + '</span></span>';
|
|
24461
|
+
if (replay.commits.length > 0) h += '<span class="outcome-item"><span class="outcome-lbl">' + (replay.commits.length===1?'commit':'commits') + '</span><span class="outcome-val">' + replay.commits.length + '</span></span>';
|
|
24462
|
+
if (replay.metrics.linesAdded > 0 || replay.metrics.linesRemoved > 0) h += '<span class="outcome-item"><span class="outcome-lbl">lines</span><span class="outcome-val"><span class="ls green">+' + replay.metrics.linesAdded + '</span><span class="ls red">-' + replay.metrics.linesRemoved + '</span></span></span>';
|
|
24463
|
+
if (replay.metrics.filesTouched.length > 0) h += '<span class="outcome-item"><span class="outcome-lbl">files</span><span class="outcome-val">' + replay.metrics.filesTouched.length + '</span></span>';
|
|
24464
|
+
h += '</div>';
|
|
24465
|
+
if (replay.commits.length > 0) {
|
|
24466
|
+
h += '<div class="outcome-commits">';
|
|
24467
|
+
replay.commits.forEach(function(c){ h += '<div class="outcome-cr"><span class="commit-sha">' + esc(c.sha.slice(0,7)) + '</span><span class="commit-msg">' + esc(c.message||'-') + '</span>' + (c.promptId ? '<span class="commit-pl">prompt ' + esc(c.promptId.slice(0,6)) + '</span>' : '') + '</div>'; });
|
|
24468
|
+
h += '</div>';
|
|
24469
|
+
}
|
|
24470
|
+
if (replay.pullRequests.length > 0) {
|
|
24471
|
+
h += '<div class="outcome-prs">';
|
|
24472
|
+
replay.pullRequests.forEach(function(pr){ h += '<div class="outcome-pr"><span class="pr-badge">' + esc(pr.state) + '</span><span class="pr-label">PR #' + pr.prNumber + '</span><span class="pr-repo">' + esc(pr.repo) + '</span>' + (pr.url ? '<a class="pr-link" href="' + esc(pr.url) + '" target="_blank" rel="noopener noreferrer">' + esc(pr.url) + '</a>' : '') + '</div>'; });
|
|
24473
|
+
h += '</div>';
|
|
24474
|
+
}
|
|
24475
|
+
h += '</div>';
|
|
24476
|
+
}
|
|
24477
|
+
// prompt groups
|
|
24478
|
+
var groups = buildPromptGroups(replay.timeline, replay.commits);
|
|
24479
|
+
if (groups.length === 0) {
|
|
24480
|
+
h += '<div class="empty">No prompts in this session.</div>';
|
|
24481
|
+
} else {
|
|
24482
|
+
groups.forEach(function(g, i) { h += renderPromptCard(g, i + 1); });
|
|
24483
|
+
}
|
|
24484
|
+
area.innerHTML = h;
|
|
24485
|
+
}
|
|
24316
24486
|
|
|
24317
|
-
|
|
24318
|
-
|
|
24319
|
-
|
|
24320
|
-
|
|
24321
|
-
|
|
24322
|
-
|
|
24323
|
-
return;
|
|
24324
|
-
}
|
|
24325
|
-
void loadSessionReplay(sessionId);
|
|
24326
|
-
});
|
|
24327
|
-
});
|
|
24328
|
-
}
|
|
24487
|
+
function parseSummary(v) {
|
|
24488
|
+
var r = asRec(v); if (!r) return null;
|
|
24489
|
+
var sid = readStr(r,'sessionId'), uid = readStr(r,'userId'), sa = readStr(r,'startedAt');
|
|
24490
|
+
if (!sid || !uid || !sa) return null;
|
|
24491
|
+
return {sessionId:sid,userId:uid,gitRepo:typeof r.gitRepo==='string'?r.gitRepo:null,gitBranch:typeof r.gitBranch==='string'?r.gitBranch:null,startedAt:sa,endedAt:typeof r.endedAt==='string'?r.endedAt:null,promptCount:readNum(r,'promptCount')||0,toolCallCount:readNum(r,'toolCallCount')||0,totalCostUsd:readNum(r,'totalCostUsd')||0,commitCount:readNum(r,'commitCount')||0,linesAdded:readNum(r,'linesAdded')||0,linesRemoved:readNum(r,'linesRemoved')||0};
|
|
24492
|
+
}
|
|
24329
24493
|
|
|
24330
|
-
|
|
24331
|
-
|
|
24332
|
-
|
|
24333
|
-
|
|
24334
|
-
|
|
24335
|
-
latestMetric.textContent = "-";
|
|
24336
|
-
replayMeta.classList.remove("error");
|
|
24337
|
-
replayMeta.textContent = "Select a session to inspect timeline events.";
|
|
24338
|
-
setReplayPlaceholder("No session selected.");
|
|
24339
|
-
return;
|
|
24340
|
-
}
|
|
24494
|
+
function parseCostPoint(v) {
|
|
24495
|
+
var r = asRec(v); if (!r) return null;
|
|
24496
|
+
var d = readStr(r,'date'); if (!d) return null;
|
|
24497
|
+
return {date:d,totalCostUsd:readNum(r,'totalCostUsd')||0,sessionCount:readNum(r,'sessionCount')||0,promptCount:readNum(r,'promptCount')||0,toolCallCount:readNum(r,'toolCallCount')||0};
|
|
24498
|
+
}
|
|
24341
24499
|
|
|
24342
|
-
|
|
24343
|
-
const repo = session.gitRepo ?? "-";
|
|
24344
|
-
const cost = typeof session.totalCostUsd === "number" ? session.totalCostUsd : 0;
|
|
24345
|
-
return "<tr>"
|
|
24346
|
-
+ "<td>" + escapeHtml(session.sessionId) + "</td>"
|
|
24347
|
-
+ "<td>" + escapeHtml(session.userId) + "</td>"
|
|
24348
|
-
+ "<td>" + escapeHtml(repo) + "</td>"
|
|
24349
|
-
+ "<td>" + escapeHtml(formatDate(session.startedAt)) + "</td>"
|
|
24350
|
-
+ "<td>" + escapeHtml(formatMoney(cost)) + "</td>"
|
|
24351
|
-
+ "<td><button type=\\"button\\" class=\\"replay-button\\" data-session-id=\\""
|
|
24352
|
-
+ escapeHtml(session.sessionId)
|
|
24353
|
-
+ "\\">View</button></td>"
|
|
24354
|
-
+ "</tr>";
|
|
24355
|
-
}).join("");
|
|
24356
|
-
sessionsBody.innerHTML = rows;
|
|
24357
|
-
bindReplayButtons();
|
|
24500
|
+
function sortLatest(arr) { return arr.slice().sort(function(a,b){ return Date.parse(ensureUtc(b.startedAt)) - Date.parse(ensureUtc(a.startedAt)); }); }
|
|
24358
24501
|
|
|
24359
|
-
|
|
24360
|
-
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
|
|
24364
|
-
|
|
24365
|
-
|
|
24366
|
-
|
|
24367
|
-
.at(-1) ?? "-";
|
|
24502
|
+
function setSessions(raw) {
|
|
24503
|
+
sessions = sortLatest(raw.map(parseSummary).filter(Boolean));
|
|
24504
|
+
renderMetrics();
|
|
24505
|
+
renderSessions();
|
|
24506
|
+
if (sessions.length > 0 && (!selectedId || !sessions.some(function(s){return s.sessionId===selectedId;}))) {
|
|
24507
|
+
selectSession(sessions[0].sessionId);
|
|
24508
|
+
}
|
|
24509
|
+
}
|
|
24368
24510
|
|
|
24369
|
-
|
|
24370
|
-
|
|
24371
|
-
|
|
24511
|
+
window.selectSession = function(sid) {
|
|
24512
|
+
selectedId = sid;
|
|
24513
|
+
renderSessions();
|
|
24514
|
+
loadReplay(sid);
|
|
24515
|
+
};
|
|
24372
24516
|
|
|
24373
|
-
|
|
24374
|
-
|
|
24375
|
-
|
|
24376
|
-
|
|
24377
|
-
|
|
24517
|
+
function loadReplay(sid) {
|
|
24518
|
+
replay = null;
|
|
24519
|
+
renderReplay();
|
|
24520
|
+
fetch('/api/session/' + encodeURIComponent(sid), {cache:'no-store'}).then(function(r){
|
|
24521
|
+
if (r.status === 404) return null;
|
|
24522
|
+
if (!r.ok) throw new Error('replay failed (' + r.status + ')');
|
|
24523
|
+
return r.json();
|
|
24524
|
+
}).then(function(payload){
|
|
24525
|
+
if (!payload) return;
|
|
24526
|
+
var p = asRec(payload);
|
|
24527
|
+
if (!p || readStr(p,'status') !== 'ok') return;
|
|
24528
|
+
replay = parseReplay(p.session);
|
|
24529
|
+
renderReplay();
|
|
24530
|
+
}).catch(function(e){ document.getElementById('replay-area').innerHTML = '<div class="empty" style="color:var(--red)">' + esc(String(e)) + '</div>'; });
|
|
24531
|
+
}
|
|
24378
24532
|
|
|
24379
|
-
|
|
24380
|
-
|
|
24381
|
-
|
|
24382
|
-
|
|
24383
|
-
|
|
24384
|
-
|
|
24385
|
-
|
|
24386
|
-
|
|
24387
|
-
|
|
24388
|
-
|
|
24389
|
-
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
24393
|
-
|
|
24394
|
-
|
|
24395
|
-
|
|
24396
|
-
|
|
24397
|
-
}
|
|
24533
|
+
function loadSnapshot() {
|
|
24534
|
+
return Promise.all([
|
|
24535
|
+
fetch('/api/sessions', {cache:'no-store'}),
|
|
24536
|
+
fetch('/api/analytics/cost/daily', {cache:'no-store'}).catch(function(){return null;})
|
|
24537
|
+
]).then(function(results){
|
|
24538
|
+
var sr = results[0], cr = results[1];
|
|
24539
|
+
if (!sr.ok) throw new Error('sessions failed (' + sr.status + ')');
|
|
24540
|
+
return Promise.all([sr.json(), cr && cr.ok ? cr.json() : {points:[]}]);
|
|
24541
|
+
}).then(function(data){
|
|
24542
|
+
var sp = asRec(data[0]), cp = asRec(data[1]);
|
|
24543
|
+
if (sp && Array.isArray(sp.sessions)) setSessions(sp.sessions);
|
|
24544
|
+
if (cp && Array.isArray(cp.points)) { costPoints = cp.points.map(parseCostPoint).filter(Boolean); renderCostChart(); }
|
|
24545
|
+
document.getElementById('status').className = 'status-banner';
|
|
24546
|
+
}).catch(function(e){
|
|
24547
|
+
document.getElementById('status').className = 'status-banner warning';
|
|
24548
|
+
document.getElementById('status').textContent = String(e);
|
|
24549
|
+
});
|
|
24550
|
+
}
|
|
24398
24551
|
|
|
24399
|
-
|
|
24400
|
-
|
|
24401
|
-
|
|
24402
|
-
|
|
24403
|
-
|
|
24552
|
+
function boot() {
|
|
24553
|
+
loadSnapshot().then(function(){
|
|
24554
|
+
if (typeof EventSource !== 'undefined') {
|
|
24555
|
+
var es = new EventSource('/api/sessions/stream');
|
|
24556
|
+
es.addEventListener('sessions', function(event) {
|
|
24557
|
+
var p = asRec(JSON.parse(event.data));
|
|
24558
|
+
if (p && Array.isArray(p.sessions)) {
|
|
24559
|
+
setSessions(p.sessions);
|
|
24560
|
+
document.getElementById('stream-label').textContent = 'live';
|
|
24561
|
+
document.getElementById('status').className = 'status-banner';
|
|
24562
|
+
document.getElementById('status').textContent = 'Live';
|
|
24404
24563
|
}
|
|
24564
|
+
});
|
|
24565
|
+
es.addEventListener('bridge_error', function(event) {
|
|
24566
|
+
document.getElementById('status').className = 'status-banner warning';
|
|
24567
|
+
document.getElementById('status').textContent = 'Bridge error: ' + event.data;
|
|
24568
|
+
});
|
|
24569
|
+
es.onerror = function() {
|
|
24570
|
+
document.getElementById('stream-label').textContent = 'polling';
|
|
24571
|
+
document.getElementById('status').textContent = 'Polling';
|
|
24572
|
+
};
|
|
24573
|
+
} else {
|
|
24574
|
+
document.getElementById('stream-label').textContent = 'polling';
|
|
24575
|
+
}
|
|
24576
|
+
setInterval(function(){ loadSnapshot(); }, 15000);
|
|
24577
|
+
});
|
|
24578
|
+
}
|
|
24405
24579
|
|
|
24406
|
-
|
|
24407
|
-
|
|
24408
|
-
|
|
24409
|
-
|
|
24410
|
-
stream.addEventListener("sessions", (event) => {
|
|
24411
|
-
try {
|
|
24412
|
-
const payload = JSON.parse(event.data);
|
|
24413
|
-
if (payload?.status !== "ok" || !Array.isArray(payload.sessions)) {
|
|
24414
|
-
throw new Error("unexpected stream payload");
|
|
24415
|
-
}
|
|
24416
|
-
renderSessions(payload.sessions);
|
|
24417
|
-
status.classList.remove("error");
|
|
24418
|
-
status.textContent = "Live session stream connected.";
|
|
24419
|
-
} catch (error) {
|
|
24420
|
-
status.classList.add("error");
|
|
24421
|
-
status.textContent = String(error);
|
|
24422
|
-
}
|
|
24423
|
-
});
|
|
24424
|
-
|
|
24425
|
-
stream.addEventListener("bridge_error", (event) => {
|
|
24426
|
-
status.classList.add("error");
|
|
24427
|
-
status.textContent = "Bridge error: " + event.data;
|
|
24428
|
-
});
|
|
24429
|
-
|
|
24430
|
-
stream.onerror = () => {
|
|
24431
|
-
status.classList.add("error");
|
|
24432
|
-
status.textContent = "Live stream disconnected. Retrying...";
|
|
24433
|
-
};
|
|
24434
|
-
}
|
|
24435
|
-
|
|
24436
|
-
async function boot() {
|
|
24437
|
-
await loadSessions();
|
|
24438
|
-
startLiveSessionsStream();
|
|
24439
|
-
}
|
|
24440
|
-
|
|
24441
|
-
void boot();
|
|
24442
|
-
</script>
|
|
24443
|
-
</body>
|
|
24580
|
+
boot();
|
|
24581
|
+
})();
|
|
24582
|
+
</script>
|
|
24583
|
+
</body>
|
|
24444
24584
|
</html>`;
|
|
24445
24585
|
}
|
|
24446
24586
|
|
|
@@ -24770,6 +24910,19 @@ async function startDashboardServer(options = {}) {
|
|
|
24770
24910
|
startSessionsSseBridge(req, res, sessionsProvider);
|
|
24771
24911
|
return;
|
|
24772
24912
|
}
|
|
24913
|
+
if (pathname === "/api/analytics/cost/daily") {
|
|
24914
|
+
void fetch(`${apiBaseUrl}/v1/analytics/cost/daily`).then(async (apiResponse) => {
|
|
24915
|
+
if (!apiResponse.ok) {
|
|
24916
|
+
sendJson(res, 502, { status: "error", message: `api returned status ${String(apiResponse.status)}` });
|
|
24917
|
+
return;
|
|
24918
|
+
}
|
|
24919
|
+
const payload = await apiResponse.json();
|
|
24920
|
+
sendJson(res, 200, payload);
|
|
24921
|
+
}).catch((error) => {
|
|
24922
|
+
sendJson(res, 502, { status: "error", message: `failed to fetch daily cost: ${String(error)}` });
|
|
24923
|
+
});
|
|
24924
|
+
return;
|
|
24925
|
+
}
|
|
24773
24926
|
if (segments.length === 3 && segments[0] === "api" && segments[1] === "session") {
|
|
24774
24927
|
const encodedSessionId = segments[2];
|
|
24775
24928
|
let sessionId = "";
|