@shield-agent/kya 0.1.39 → 0.1.41
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/.mcp.json +1 -1
- package/README.md +3 -3
- package/amp/settings.example.json +1 -1
- package/claude/claude_desktop_config.example.json +1 -1
- package/cline/cline_mcp_settings.example.json +1 -1
- package/copilot/mcp-config.example.json +1 -1
- package/dist/commands/connect.d.ts +15 -0
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +4 -4
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/orr.d.ts.map +1 -1
- package/dist/commands/orr.js +3 -3
- package/dist/commands/orr.js.map +1 -1
- package/dist/receipt/enrich.d.ts +45 -0
- package/dist/receipt/enrich.d.ts.map +1 -0
- package/dist/receipt/enrich.js +205 -0
- package/dist/receipt/enrich.js.map +1 -0
- package/dist/receipt/render-receipt.d.ts +47 -0
- package/dist/receipt/render-receipt.d.ts.map +1 -1
- package/dist/receipt/render-receipt.js +433 -14
- package/dist/receipt/render-receipt.js.map +1 -1
- package/dist/showback/usage-file.d.ts +12 -0
- package/dist/showback/usage-file.d.ts.map +1 -0
- package/dist/showback/usage-file.js +14 -0
- package/dist/showback/usage-file.js.map +1 -0
- package/droid/README.md +1 -1
- package/droid/mcp.example.json +1 -1
- package/gemini/settings.example.json +1 -1
- package/grok/README.md +1 -1
- package/kilo/kilo.example.json +1 -1
- package/kimi/mcp.example.json +1 -1
- package/kiro/mcp.example.json +1 -1
- package/manifest.json +1 -1
- package/mastracode/mcp.example.json +1 -1
- package/mcp.json +1 -1
- package/openai/codex.config.example.toml +1 -1
- package/opencode/opencode.example.json +1 -1
- package/package.json +1 -1
- package/qwen/settings.example.json +1 -1
- package/server.json +2 -2
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type TrailEvent } from "../trail.js";
|
|
2
|
+
import type { KyaFileConfig } from "../config.js";
|
|
3
|
+
import { type OrrCard, type SandboxCard, type WiredHostRow } from "./enrich.js";
|
|
4
|
+
import { type ShowbackReport } from "../showback/cost-per-task.js";
|
|
2
5
|
export interface ReceiptModel {
|
|
3
6
|
readonly title: string;
|
|
4
7
|
readonly rangeLabel: string;
|
|
@@ -14,7 +17,51 @@ export interface ReceiptModel {
|
|
|
14
17
|
}[];
|
|
15
18
|
/** When true, page connects to /events SSE for live refresh. */
|
|
16
19
|
readonly live?: boolean;
|
|
20
|
+
/** Local identity from .kya/config.json (inert text; never a link). */
|
|
21
|
+
readonly identity?: KyaFileConfig;
|
|
22
|
+
/** Sandbox state + configured KYA_SANDBOX backend. */
|
|
23
|
+
readonly sandboxes?: SandboxCard;
|
|
24
|
+
/** Per-host wiring status across the connect registry. */
|
|
25
|
+
readonly wiredHosts?: readonly WiredHostRow[];
|
|
26
|
+
/** Slim ORR card from orr-report/report.json. */
|
|
27
|
+
readonly orr?: OrrCard;
|
|
28
|
+
/** Observe-only showback from .kya/usage.json. */
|
|
29
|
+
readonly showback?: ShowbackReport;
|
|
17
30
|
}
|
|
31
|
+
export type WorstVerdict = "never" | "deny" | "hold" | "allow";
|
|
32
|
+
export interface SessionAggregate {
|
|
33
|
+
readonly sessionId: string;
|
|
34
|
+
readonly events: number;
|
|
35
|
+
readonly deny: number;
|
|
36
|
+
readonly hold: number;
|
|
37
|
+
readonly never: number;
|
|
38
|
+
readonly lastTs: string;
|
|
39
|
+
/** never > deny > hold > allow. */
|
|
40
|
+
readonly worst: WorstVerdict;
|
|
41
|
+
}
|
|
42
|
+
export interface CountRow {
|
|
43
|
+
readonly label: string;
|
|
44
|
+
readonly count: number;
|
|
45
|
+
}
|
|
46
|
+
export interface EventAggregates {
|
|
47
|
+
readonly modes: {
|
|
48
|
+
readonly observe: number;
|
|
49
|
+
readonly hold: number;
|
|
50
|
+
readonly offline: number;
|
|
51
|
+
};
|
|
52
|
+
readonly planes: {
|
|
53
|
+
readonly ide: number;
|
|
54
|
+
readonly runtime: number;
|
|
55
|
+
};
|
|
56
|
+
/** Top 8 sessions by recency. */
|
|
57
|
+
readonly sessions: readonly SessionAggregate[];
|
|
58
|
+
/** Top 8 reason codes by count. */
|
|
59
|
+
readonly reasons: readonly CountRow[];
|
|
60
|
+
/** Counts by productLabel, sorted desc. */
|
|
61
|
+
readonly products: readonly CountRow[];
|
|
62
|
+
}
|
|
63
|
+
/** Pure rollup of trail events for the report's stat chips and sections. */
|
|
64
|
+
export declare function aggregateEvents(events: readonly TrailEvent[]): EventAggregates;
|
|
18
65
|
export declare function buildReceiptModel(sessionId: string, events: readonly TrailEvent[], extras?: Partial<Omit<ReceiptModel, "title" | "events" | "generatedAt" | "rangeLabel">>): ReceiptModel;
|
|
19
66
|
export declare function buildWindowReceiptModel(events: readonly TrailEvent[], days: number, extras?: Partial<Omit<ReceiptModel, "title" | "events" | "generatedAt" | "rangeLabel">>): ReceiptModel;
|
|
20
67
|
/** Shared loader for static artifacts and the live loopback server. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-receipt.d.ts","sourceRoot":"","sources":["../../src/receipt/render-receipt.ts"],"names":[],"mappings":"AAKA,OAAO,EAA2C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"render-receipt.d.ts","sourceRoot":"","sources":["../../src/receipt/render-receipt.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { assertNoSecrets, clip } from "../dash/render.js";
|
|
5
5
|
import { clipMultiline, DIFF_MAX_TOTAL_CHARS } from "../diff-preview.js";
|
|
6
6
|
import { productLabel, readTrail, readTrailSince } from "../trail.js";
|
|
7
|
+
import { loadIdentity, loadOrrCard, loadSandboxes, loadShowbackCard, loadWiredHosts, } from "./enrich.js";
|
|
8
|
+
import { SHOWBACK_DISCLAIMER, } from "../showback/cost-per-task.js";
|
|
7
9
|
function esc(s) {
|
|
8
10
|
return s
|
|
9
11
|
.replace(/&/g, "&")
|
|
@@ -84,6 +86,67 @@ function verdictWord(verdict) {
|
|
|
84
86
|
return "HOLD";
|
|
85
87
|
return v;
|
|
86
88
|
}
|
|
89
|
+
/** Pure rollup of trail events for the report's stat chips and sections. */
|
|
90
|
+
export function aggregateEvents(events) {
|
|
91
|
+
const modes = { observe: 0, hold: 0, offline: 0 };
|
|
92
|
+
const planes = { ide: 0, runtime: 0 };
|
|
93
|
+
const reasonCounts = new Map();
|
|
94
|
+
const productCounts = new Map();
|
|
95
|
+
const bySession = new Map();
|
|
96
|
+
for (const e of events) {
|
|
97
|
+
if (e.mode === "observe")
|
|
98
|
+
modes.observe++;
|
|
99
|
+
else if (e.mode === "hold")
|
|
100
|
+
modes.hold++;
|
|
101
|
+
else if (e.mode === "offline")
|
|
102
|
+
modes.offline++;
|
|
103
|
+
if (e.host === "ide")
|
|
104
|
+
planes.ide++;
|
|
105
|
+
else if (e.host === "runtime")
|
|
106
|
+
planes.runtime++;
|
|
107
|
+
reasonCounts.set(e.reasonCode, (reasonCounts.get(e.reasonCode) ?? 0) + 1);
|
|
108
|
+
const pl = productLabel(e.product);
|
|
109
|
+
productCounts.set(pl, (productCounts.get(pl) ?? 0) + 1);
|
|
110
|
+
const sid = e.sessionId || "unknown";
|
|
111
|
+
let s = bySession.get(sid);
|
|
112
|
+
if (!s) {
|
|
113
|
+
s = { events: 0, deny: 0, hold: 0, never: 0, lastTs: e.ts };
|
|
114
|
+
bySession.set(sid, s);
|
|
115
|
+
}
|
|
116
|
+
s.events++;
|
|
117
|
+
if (e.neverEvent || e.reasonCode === "NEVER_EVENT")
|
|
118
|
+
s.never++;
|
|
119
|
+
const v = e.verdict.toUpperCase();
|
|
120
|
+
if (v === "DENY")
|
|
121
|
+
s.deny++;
|
|
122
|
+
else if (v === "REQUIRE_APPROVE")
|
|
123
|
+
s.hold++;
|
|
124
|
+
if (e.ts > s.lastTs)
|
|
125
|
+
s.lastTs = e.ts;
|
|
126
|
+
}
|
|
127
|
+
const sessions = [...bySession.entries()]
|
|
128
|
+
.map(([sessionId, s]) => ({
|
|
129
|
+
sessionId,
|
|
130
|
+
...s,
|
|
131
|
+
worst: (s.never > 0
|
|
132
|
+
? "never"
|
|
133
|
+
: s.deny > 0
|
|
134
|
+
? "deny"
|
|
135
|
+
: s.hold > 0
|
|
136
|
+
? "hold"
|
|
137
|
+
: "allow"),
|
|
138
|
+
}))
|
|
139
|
+
.sort((a, b) => b.lastTs.localeCompare(a.lastTs))
|
|
140
|
+
.slice(0, 8);
|
|
141
|
+
const reasons = [...reasonCounts.entries()]
|
|
142
|
+
.map(([label, count]) => ({ label, count }))
|
|
143
|
+
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label))
|
|
144
|
+
.slice(0, 8);
|
|
145
|
+
const products = [...productCounts.entries()]
|
|
146
|
+
.map(([label, count]) => ({ label, count }))
|
|
147
|
+
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
|
|
148
|
+
return { modes, planes, sessions, reasons, products };
|
|
149
|
+
}
|
|
87
150
|
export function buildReceiptModel(sessionId, events, extras) {
|
|
88
151
|
const filtered = events.filter((e) => e.sessionId === sessionId);
|
|
89
152
|
return {
|
|
@@ -94,6 +157,11 @@ export function buildReceiptModel(sessionId, events, extras) {
|
|
|
94
157
|
spend: extras?.spend,
|
|
95
158
|
mcpSeen: extras?.mcpSeen,
|
|
96
159
|
live: extras?.live,
|
|
160
|
+
identity: extras?.identity,
|
|
161
|
+
sandboxes: extras?.sandboxes,
|
|
162
|
+
wiredHosts: extras?.wiredHosts,
|
|
163
|
+
orr: extras?.orr,
|
|
164
|
+
showback: extras?.showback,
|
|
97
165
|
};
|
|
98
166
|
}
|
|
99
167
|
export function buildWindowReceiptModel(events, days, extras) {
|
|
@@ -106,16 +174,42 @@ export function buildWindowReceiptModel(events, days, extras) {
|
|
|
106
174
|
spend: extras?.spend,
|
|
107
175
|
mcpSeen: extras?.mcpSeen,
|
|
108
176
|
live: extras?.live,
|
|
177
|
+
identity: extras?.identity,
|
|
178
|
+
sandboxes: extras?.sandboxes,
|
|
179
|
+
wiredHosts: extras?.wiredHosts,
|
|
180
|
+
orr: extras?.orr,
|
|
181
|
+
showback: extras?.showback,
|
|
109
182
|
};
|
|
110
183
|
}
|
|
111
184
|
/** Shared loader for static artifacts and the live loopback server. */
|
|
112
185
|
export function loadReceiptModel(input) {
|
|
113
186
|
const days = input.days > 0 ? Math.floor(input.days) : 3;
|
|
187
|
+
const showback = loadShowbackCard(input.cwd);
|
|
188
|
+
const extras = {
|
|
189
|
+
live: input.live,
|
|
190
|
+
identity: loadIdentity(input.cwd),
|
|
191
|
+
sandboxes: loadSandboxes(input.cwd),
|
|
192
|
+
wiredHosts: loadWiredHosts(input.cwd),
|
|
193
|
+
orr: loadOrrCard(input.cwd),
|
|
194
|
+
showback,
|
|
195
|
+
spend: showback
|
|
196
|
+
? {
|
|
197
|
+
tokens: showback.totalTokensIn + showback.totalTokensOut,
|
|
198
|
+
usdEstimate: showback.estimatedUsd ?? undefined,
|
|
199
|
+
}
|
|
200
|
+
: undefined,
|
|
201
|
+
};
|
|
114
202
|
if (input.sessionId) {
|
|
115
|
-
return buildReceiptModel(input.sessionId, readTrail(input.cwd),
|
|
203
|
+
return buildReceiptModel(input.sessionId, readTrail(input.cwd), extras);
|
|
116
204
|
}
|
|
117
205
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
118
|
-
return buildWindowReceiptModel(readTrailSince(input.cwd, since), days,
|
|
206
|
+
return buildWindowReceiptModel(readTrailSince(input.cwd, since), days, extras);
|
|
207
|
+
}
|
|
208
|
+
function modeBadge(mode) {
|
|
209
|
+
const letter = mode === "observe" ? "O" : mode === "hold" ? "H" : mode === "offline" ? "X" : "";
|
|
210
|
+
if (!letter)
|
|
211
|
+
return "";
|
|
212
|
+
return `<span class="modeb" title="${letter === "O" ? "observe" : letter === "H" ? "hold" : "offline"} mode">${letter}</span>`;
|
|
119
213
|
}
|
|
120
214
|
function renderFeed(events, nowMs, live) {
|
|
121
215
|
if (events.length === 0) {
|
|
@@ -145,7 +239,7 @@ function renderFeed(events, nowMs, live) {
|
|
|
145
239
|
<div class="rail" aria-hidden="true"><span class="tick"></span></div>
|
|
146
240
|
<div class="main">
|
|
147
241
|
<div class="top">
|
|
148
|
-
<span class="verdict">${esc(verdictWord(e.verdict))}</span
|
|
242
|
+
<span class="verdict">${esc(verdictWord(e.verdict))}</span>${modeBadge(e.mode)}
|
|
149
243
|
<code class="tool">${esc(e.toolId)}</code>
|
|
150
244
|
<time class="when" datetime="${esc(e.ts)}" title="${esc(e.ts)}">${esc(clock(e.ts))} <span class="rel">${esc(relativeTime(e.ts, nowMs))}</span></time>
|
|
151
245
|
</div>
|
|
@@ -161,6 +255,167 @@ function renderFeed(events, nowMs, live) {
|
|
|
161
255
|
}
|
|
162
256
|
return parts.join("\n");
|
|
163
257
|
}
|
|
258
|
+
function identityLine(identity) {
|
|
259
|
+
if (!identity)
|
|
260
|
+
return "";
|
|
261
|
+
const nameId = identity.agentName && identity.agentId
|
|
262
|
+
? `${identity.agentName}/${identity.agentId}`
|
|
263
|
+
: (identity.agentName ?? identity.agentId);
|
|
264
|
+
const bits = [nameId, identity.host, identity.baseUrl].filter((b) => typeof b === "string" && b.trim().length > 0);
|
|
265
|
+
if (bits.length === 0)
|
|
266
|
+
return "";
|
|
267
|
+
// baseUrl is inert text here — never an anchor.
|
|
268
|
+
return `<div class="identity">${esc(clip(bits.join(" · "), 160))}</div>`;
|
|
269
|
+
}
|
|
270
|
+
function statChips(agg) {
|
|
271
|
+
const chip = (label, n, cls = "") => n > 0 ? `<span class="stat${cls ? ` ${cls}` : ""}">${label}<b>${n}</b></span>` : "";
|
|
272
|
+
return {
|
|
273
|
+
modes: chip("Mode: observe", agg.modes.observe) +
|
|
274
|
+
chip("Mode: hold", agg.modes.hold, "warn") +
|
|
275
|
+
chip("Mode: offline", agg.modes.offline),
|
|
276
|
+
planes: chip("IDE", agg.planes.ide) + chip("Runtime", agg.planes.runtime),
|
|
277
|
+
products: agg.products.length >= 2
|
|
278
|
+
? `<div class="stats" aria-label="Products">${agg.products
|
|
279
|
+
.map((p) => `<span class="stat">${esc(clip(p.label, 40))}<b>${p.count}</b></span>`)
|
|
280
|
+
.join("")}</div>`
|
|
281
|
+
: "",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function sessionsPanel(agg, nowMs) {
|
|
285
|
+
if (agg.sessions.length === 0)
|
|
286
|
+
return "";
|
|
287
|
+
const rows = agg.sessions
|
|
288
|
+
.map((s) => `<li>
|
|
289
|
+
<span class="wdot ${s.worst}" title="worst verdict: ${s.worst}"></span>
|
|
290
|
+
<code class="sid" title="${esc(s.sessionId)}">${esc(clip(s.sessionId, 24))}</code>
|
|
291
|
+
<span class="cnt">${s.events} event${s.events === 1 ? "" : "s"}</span>
|
|
292
|
+
<span class="rel">${esc(relativeTime(s.lastTs, nowMs))}</span>
|
|
293
|
+
</li>`)
|
|
294
|
+
.join("\n");
|
|
295
|
+
return `<section class="panel" aria-label="Sessions">
|
|
296
|
+
<h2>Sessions</h2>
|
|
297
|
+
<ul class="rows">
|
|
298
|
+
${rows}
|
|
299
|
+
</ul>
|
|
300
|
+
</section>`;
|
|
301
|
+
}
|
|
302
|
+
function reasonsPanel(agg) {
|
|
303
|
+
if (agg.reasons.length === 0)
|
|
304
|
+
return "";
|
|
305
|
+
const chips = agg.reasons
|
|
306
|
+
.map((r) => `<span class="chip"><code>${esc(clip(r.label, 60))}</code> × ${r.count}</span>`)
|
|
307
|
+
.join("\n ");
|
|
308
|
+
return `<section class="panel" aria-label="Reasons">
|
|
309
|
+
<h2>Reasons</h2>
|
|
310
|
+
<div class="chiprow">
|
|
311
|
+
${chips}
|
|
312
|
+
</div>
|
|
313
|
+
</section>`;
|
|
314
|
+
}
|
|
315
|
+
function wiredHostsPanel(hosts) {
|
|
316
|
+
if (!hosts || hosts.length === 0)
|
|
317
|
+
return "";
|
|
318
|
+
if (!hosts.some((h) => h.wired !== "none"))
|
|
319
|
+
return "";
|
|
320
|
+
const rank = (h) => h.wired !== "none" ? 0 : h.recipeOnly ? 1 : 2;
|
|
321
|
+
const rows = [...hosts]
|
|
322
|
+
.sort((a, b) => rank(a) - rank(b))
|
|
323
|
+
.map((h) => {
|
|
324
|
+
if (h.recipeOnly) {
|
|
325
|
+
return `<li class="mute">${esc(h.label)} — manual setup <span class="hint" title="${esc(h.recipeOnly)}">docs recipe</span></li>`;
|
|
326
|
+
}
|
|
327
|
+
if (h.wired === "none") {
|
|
328
|
+
return `<li class="mute">${esc(h.label)} — not wired</li>`;
|
|
329
|
+
}
|
|
330
|
+
const reload = h.reload ? ` · ${esc(h.reload.reload)}` : "";
|
|
331
|
+
const running = h.running ? "running" : "not running";
|
|
332
|
+
return `<li>${esc(h.label)} — wired (${h.wired})${reload} · ${running}</li>`;
|
|
333
|
+
})
|
|
334
|
+
.join("\n ");
|
|
335
|
+
return `<section class="panel" aria-label="Wired hosts">
|
|
336
|
+
<h2>Wired hosts</h2>
|
|
337
|
+
<ul class="rows">
|
|
338
|
+
${rows}
|
|
339
|
+
</ul>
|
|
340
|
+
</section>`;
|
|
341
|
+
}
|
|
342
|
+
function sandboxesPanel(card, nowMs) {
|
|
343
|
+
if (!card)
|
|
344
|
+
return "";
|
|
345
|
+
if (card.sandboxes.length === 0 && !card.backend)
|
|
346
|
+
return "";
|
|
347
|
+
const backend = card.backend
|
|
348
|
+
? `<p class="sub">Configured backend: <code>${esc(clip(card.backend, 40))}</code></p>`
|
|
349
|
+
: "";
|
|
350
|
+
const tbl = card.sandboxes.length === 0
|
|
351
|
+
? ""
|
|
352
|
+
: `<table class="tbl">
|
|
353
|
+
<thead><tr><th>id</th><th>backend</th><th>status</th><th>created</th></tr></thead>
|
|
354
|
+
<tbody>
|
|
355
|
+
${card.sandboxes
|
|
356
|
+
.map((s) => ` <tr>
|
|
357
|
+
<td><code title="${esc(s.sandboxId)}">${esc(clip(s.sandboxId, 20))}</code></td>
|
|
358
|
+
<td>${esc(s.backend)}</td>
|
|
359
|
+
<td><span class="pill ${s.status === "running" ? "ok" : "mute"}">${esc(s.status)}</span></td>
|
|
360
|
+
<td>${esc(relativeTime(s.createdAt, nowMs))}</td>
|
|
361
|
+
</tr>`)
|
|
362
|
+
.join("\n")}
|
|
363
|
+
</tbody>
|
|
364
|
+
</table>`;
|
|
365
|
+
return `<section class="panel" aria-label="Sandboxes">
|
|
366
|
+
<h2>Sandboxes</h2>
|
|
367
|
+
${backend}
|
|
368
|
+
${tbl}
|
|
369
|
+
</section>`;
|
|
370
|
+
}
|
|
371
|
+
function orrPanel(orr, nowMs) {
|
|
372
|
+
if (!orr)
|
|
373
|
+
return "";
|
|
374
|
+
const target = orr.targetName
|
|
375
|
+
? `<span class="mute">target ${esc(clip(orr.targetName, 60))}</span>`
|
|
376
|
+
: "";
|
|
377
|
+
const failure = orr.primaryFailureMode
|
|
378
|
+
? `<p class="line">Primary failure mode: ${esc(clip(orr.primaryFailureMode, 200))}</p>`
|
|
379
|
+
: "";
|
|
380
|
+
const fix = orr.mostUrgentFix
|
|
381
|
+
? `<p class="line">Most urgent fix: ${esc(clip(orr.mostUrgentFix, 200))}</p>`
|
|
382
|
+
: "";
|
|
383
|
+
const when = orr.generatedAt ? ` · ${esc(relativeTime(orr.generatedAt, nowMs))}` : "";
|
|
384
|
+
return `<section class="panel" aria-label="ORR">
|
|
385
|
+
<h2>Operational readiness</h2>
|
|
386
|
+
<div class="orrline">
|
|
387
|
+
<span class="pill orr-${esc(orr.overall)}">${esc(orr.overall)}</span>
|
|
388
|
+
<span class="disp">${esc(orr.disposition.replace(/_/g, " "))}</span>
|
|
389
|
+
${target}
|
|
390
|
+
</div>
|
|
391
|
+
${failure}
|
|
392
|
+
${fix}
|
|
393
|
+
<p class="mute small">scorecards: ${orr.scorecards.pass} pass · ${orr.scorecards.fail} fail · ${orr.scorecards.partial} partial · ${orr.scorecards.notEvaluated} n/e${when}</p>
|
|
394
|
+
</section>`;
|
|
395
|
+
}
|
|
396
|
+
function usdText(usd) {
|
|
397
|
+
return usd === null ? "USD n/a" : `~$${usd.toFixed(2)}`;
|
|
398
|
+
}
|
|
399
|
+
function showbackPanel(report) {
|
|
400
|
+
if (!report)
|
|
401
|
+
return "";
|
|
402
|
+
const topRuns = [...report.perRun]
|
|
403
|
+
.sort((a, b) => (b.estimatedUsd ?? -1) - (a.estimatedUsd ?? -1))
|
|
404
|
+
.slice(0, 5);
|
|
405
|
+
const runs = topRuns.length === 0
|
|
406
|
+
? ""
|
|
407
|
+
: `<ul class="rows">
|
|
408
|
+
${topRuns
|
|
409
|
+
.map((r) => ` <li><code title="${esc(r.runId)}">${esc(clip(r.runId, 32))}</code> — ${r.steps} step${r.steps === 1 ? "" : "s"} · ${esc(usdText(r.estimatedUsd))}</li>`)
|
|
410
|
+
.join("\n")}
|
|
411
|
+
</ul>`;
|
|
412
|
+
return `<section class="panel" aria-label="Showback">
|
|
413
|
+
<h2>Showback (observe only)</h2>
|
|
414
|
+
<p class="line">${report.totalTokensIn} tokens in · ${report.totalTokensOut} tokens out · ${esc(usdText(report.estimatedUsd))}</p>
|
|
415
|
+
${runs}
|
|
416
|
+
<p class="mute small">${esc(SHOWBACK_DISCLAIMER)}</p>
|
|
417
|
+
</section>`;
|
|
418
|
+
}
|
|
164
419
|
export function renderReceiptHtml(model) {
|
|
165
420
|
const nowMs = Date.now();
|
|
166
421
|
const events = [...model.events].map((e) => ({
|
|
@@ -172,6 +427,8 @@ export function renderReceiptHtml(model) {
|
|
|
172
427
|
diffPreview: e.diffPreview ? clipMultiline(e.diffPreview, DIFF_MAX_TOTAL_CHARS) : undefined,
|
|
173
428
|
}));
|
|
174
429
|
const c = countVerdicts(events);
|
|
430
|
+
const agg = aggregateEvents(events);
|
|
431
|
+
const chips = statChips(agg);
|
|
175
432
|
const blocked = events.filter((e) => e.neverEvent || e.reasonCode === "NEVER_EVENT");
|
|
176
433
|
const tools = model.mcpSeen && model.mcpSeen.length > 0
|
|
177
434
|
? model.mcpSeen.map((m) => m.toolId)
|
|
@@ -296,6 +553,81 @@ export function renderReceiptHtml(model) {
|
|
|
296
553
|
.stat.bad b { color: var(--bad); }
|
|
297
554
|
.stat.warn b { color: var(--warn); }
|
|
298
555
|
.stat.ok b { color: var(--ok); }
|
|
556
|
+
.identity {
|
|
557
|
+
margin-top: 0.45rem; color: var(--mute); font-size: 0.78rem;
|
|
558
|
+
word-break: break-word;
|
|
559
|
+
}
|
|
560
|
+
.modeb {
|
|
561
|
+
display: inline-block; font-size: 0.6rem; font-weight: 700;
|
|
562
|
+
color: var(--mute); border: 1px solid var(--line); border-radius: 4px;
|
|
563
|
+
padding: 0 0.28rem; line-height: 1.1rem; vertical-align: baseline;
|
|
564
|
+
}
|
|
565
|
+
.panel {
|
|
566
|
+
background: var(--card); border: 1px solid var(--line);
|
|
567
|
+
border-radius: 12px; padding: 0.7rem 0.9rem 0.8rem; margin-bottom: 1rem;
|
|
568
|
+
}
|
|
569
|
+
.panel h2 {
|
|
570
|
+
margin: 0 0 0.5rem; font-size: 0.72rem; font-weight: 700;
|
|
571
|
+
letter-spacing: 0.07em; text-transform: uppercase; color: var(--day);
|
|
572
|
+
}
|
|
573
|
+
.panel .sub { margin: 0 0 0.45rem; color: var(--mute); font-size: 0.8rem; }
|
|
574
|
+
.panel .line { margin: 0.25rem 0; font-size: 0.84rem; word-break: break-word; }
|
|
575
|
+
.panel .mute { color: var(--mute); }
|
|
576
|
+
.panel .small { font-size: 0.74rem; }
|
|
577
|
+
.rows {
|
|
578
|
+
list-style: none; margin: 0; padding: 0;
|
|
579
|
+
display: flex; flex-direction: column; gap: 0.3rem;
|
|
580
|
+
font-size: 0.82rem;
|
|
581
|
+
}
|
|
582
|
+
.rows li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.45rem; min-width: 0; }
|
|
583
|
+
.rows li.mute { color: var(--mute); }
|
|
584
|
+
.rows .sid, .rows code {
|
|
585
|
+
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
586
|
+
font-size: 0.78rem; word-break: break-all;
|
|
587
|
+
}
|
|
588
|
+
.rows .cnt { color: var(--mute); font-size: 0.76rem; }
|
|
589
|
+
.rows .rel { margin-left: auto; color: var(--mute); font-size: 0.74rem; white-space: nowrap; }
|
|
590
|
+
.rows .hint {
|
|
591
|
+
font-size: 0.72rem; border-bottom: 1px dotted var(--mute); cursor: help;
|
|
592
|
+
}
|
|
593
|
+
.wdot {
|
|
594
|
+
width: 0.5rem; height: 0.5rem; border-radius: 50%; flex: none;
|
|
595
|
+
align-self: center; background: var(--mute);
|
|
596
|
+
}
|
|
597
|
+
.wdot.allow { background: var(--ok); }
|
|
598
|
+
.wdot.hold { background: var(--warn); }
|
|
599
|
+
.wdot.deny, .wdot.never { background: var(--bad); }
|
|
600
|
+
.chiprow { display: flex; flex-wrap: wrap; gap: 0.35rem; }
|
|
601
|
+
.chip {
|
|
602
|
+
font-size: 0.76rem; color: var(--mute);
|
|
603
|
+
background: color-mix(in srgb, var(--bg) 55%, var(--card));
|
|
604
|
+
border: 1px solid var(--line); border-radius: 999px; padding: 0.15rem 0.55rem;
|
|
605
|
+
}
|
|
606
|
+
.chip code {
|
|
607
|
+
font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
|
|
608
|
+
color: var(--fg); font-size: 0.74rem;
|
|
609
|
+
}
|
|
610
|
+
.tbl { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
|
|
611
|
+
.tbl th {
|
|
612
|
+
text-align: left; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.06em;
|
|
613
|
+
text-transform: uppercase; color: var(--mute); padding: 0.15rem 0.5rem 0.3rem 0;
|
|
614
|
+
}
|
|
615
|
+
.tbl td { padding: 0.2rem 0.5rem 0.2rem 0; border-top: 1px solid var(--line); }
|
|
616
|
+
.tbl code {
|
|
617
|
+
font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace; font-size: 0.76rem;
|
|
618
|
+
}
|
|
619
|
+
.pill {
|
|
620
|
+
display: inline-block; font-size: 0.7rem; font-weight: 700;
|
|
621
|
+
letter-spacing: 0.04em; text-transform: uppercase;
|
|
622
|
+
border-radius: 999px; padding: 0.1rem 0.55rem;
|
|
623
|
+
border: 1px solid var(--line); color: var(--mute);
|
|
624
|
+
}
|
|
625
|
+
.pill.ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
|
|
626
|
+
.pill.orr-green { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
|
|
627
|
+
.pill.orr-amber { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
|
|
628
|
+
.pill.orr-red { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
|
|
629
|
+
.orrline { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.55rem; }
|
|
630
|
+
.orrline .disp { font-size: 0.84rem; font-weight: 600; }
|
|
299
631
|
.alert {
|
|
300
632
|
display: flex; flex-wrap: wrap; gap: 0.45rem 0.75rem; align-items: baseline;
|
|
301
633
|
background: color-mix(in srgb, var(--bad) 12%, var(--card));
|
|
@@ -320,7 +652,6 @@ export function renderReceiptHtml(model) {
|
|
|
320
652
|
text-transform: uppercase; color: var(--day);
|
|
321
653
|
background: color-mix(in srgb, var(--bg) 55%, var(--card));
|
|
322
654
|
border-bottom: 1px solid var(--line);
|
|
323
|
-
position: sticky; top: 4.2rem; z-index: 1;
|
|
324
655
|
}
|
|
325
656
|
.ev {
|
|
326
657
|
display: grid; grid-template-columns: 1.1rem 1fr; gap: 0;
|
|
@@ -431,17 +762,26 @@ export function renderReceiptHtml(model) {
|
|
|
431
762
|
<h1>${esc(model.title)}</h1>
|
|
432
763
|
<div class="range">${esc(model.rangeLabel)}${livePill}</div>
|
|
433
764
|
</div>
|
|
765
|
+
${identityLine(model.identity)}
|
|
434
766
|
<div class="stats" aria-label="Counts">
|
|
435
767
|
<span class="stat ok">Allow<b>${c.allow}</b></span>
|
|
436
768
|
<span class="stat bad">Deny<b>${c.deny}</b></span>
|
|
437
769
|
<span class="stat warn">Hold<b>${c.require}</b></span>
|
|
438
770
|
<span class="stat">Never<b>${c.never}</b></span>
|
|
771
|
+
${chips.modes}${chips.planes}
|
|
439
772
|
</div>
|
|
773
|
+
${chips.products}
|
|
440
774
|
</header>
|
|
441
775
|
${blockedBanner}
|
|
442
776
|
<section class="feed" id="feed" aria-label="Activity feed">
|
|
443
777
|
${renderFeed(events, nowMs, model.live)}
|
|
444
778
|
</section>
|
|
779
|
+
${sessionsPanel(agg, nowMs)}
|
|
780
|
+
${reasonsPanel(agg)}
|
|
781
|
+
${wiredHostsPanel(model.wiredHosts)}
|
|
782
|
+
${sandboxesPanel(model.sandboxes, nowMs)}
|
|
783
|
+
${orrPanel(model.orr, nowMs)}
|
|
784
|
+
${showbackPanel(model.showback)}
|
|
445
785
|
${toolsHtml}
|
|
446
786
|
</main>
|
|
447
787
|
${liveScript}
|
|
@@ -453,6 +793,8 @@ ${liveScript}
|
|
|
453
793
|
}
|
|
454
794
|
export function renderReceiptMarkdown(model) {
|
|
455
795
|
const c = countVerdicts(model.events);
|
|
796
|
+
const agg = aggregateEvents(model.events);
|
|
797
|
+
const nowMs = Date.now();
|
|
456
798
|
const lines = [
|
|
457
799
|
`# ${model.title}`,
|
|
458
800
|
"",
|
|
@@ -460,17 +802,94 @@ export function renderReceiptMarkdown(model) {
|
|
|
460
802
|
"",
|
|
461
803
|
`Allow ${c.allow} | Deny ${c.deny} | Hold ${c.require} | Never ${c.never}`,
|
|
462
804
|
"",
|
|
463
|
-
"## Feed",
|
|
464
|
-
...[...model.events]
|
|
465
|
-
.sort((a, b) => b.ts.localeCompare(a.ts))
|
|
466
|
-
.flatMap((e) => {
|
|
467
|
-
const sum = e.summary?.trim() ? ` - ${e.summary.trim()}` : "";
|
|
468
|
-
const head = `- **${verdictWord(e.verdict)}** \`${e.toolId}\`${sum} - ${productLabel(e.product)} - ${e.reasonCode}`;
|
|
469
|
-
if (!e.diffPreview?.trim())
|
|
470
|
-
return [head];
|
|
471
|
-
return [head, "", "```", e.diffPreview.trim(), "```", ""];
|
|
472
|
-
}),
|
|
473
805
|
];
|
|
806
|
+
if (model.identity) {
|
|
807
|
+
const id = model.identity;
|
|
808
|
+
const nameId = id.agentName && id.agentId
|
|
809
|
+
? `${id.agentName}/${id.agentId}`
|
|
810
|
+
: (id.agentName ?? id.agentId);
|
|
811
|
+
const bits = [nameId, id.host, id.baseUrl].filter((b) => typeof b === "string" && b.trim().length > 0);
|
|
812
|
+
if (bits.length > 0) {
|
|
813
|
+
lines.push(bits.join(" · "), "");
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const modeBits = [
|
|
817
|
+
agg.modes.observe > 0 ? `observe ${agg.modes.observe}` : "",
|
|
818
|
+
agg.modes.hold > 0 ? `hold ${agg.modes.hold}` : "",
|
|
819
|
+
agg.modes.offline > 0 ? `offline ${agg.modes.offline}` : "",
|
|
820
|
+
].filter(Boolean);
|
|
821
|
+
const planeBits = [
|
|
822
|
+
agg.planes.ide > 0 ? `IDE ${agg.planes.ide}` : "",
|
|
823
|
+
agg.planes.runtime > 0 ? `Runtime ${agg.planes.runtime}` : "",
|
|
824
|
+
].filter(Boolean);
|
|
825
|
+
if (modeBits.length > 0 || planeBits.length > 0) {
|
|
826
|
+
lines.push(`Modes: ${modeBits.join(" | ") || "-"} · Planes: ${planeBits.join(" | ") || "-"}`, "");
|
|
827
|
+
}
|
|
828
|
+
if (agg.products.length >= 2) {
|
|
829
|
+
lines.push("## Products", ...agg.products.map((p) => `- ${p.label} × ${p.count}`), "");
|
|
830
|
+
}
|
|
831
|
+
lines.push("## Feed", ...[...model.events]
|
|
832
|
+
.sort((a, b) => b.ts.localeCompare(a.ts))
|
|
833
|
+
.flatMap((e) => {
|
|
834
|
+
const sum = e.summary?.trim() ? ` - ${e.summary.trim()}` : "";
|
|
835
|
+
const head = `- **${verdictWord(e.verdict)}** \`${e.toolId}\`${sum} - ${productLabel(e.product)} - ${e.reasonCode}`;
|
|
836
|
+
if (!e.diffPreview?.trim())
|
|
837
|
+
return [head];
|
|
838
|
+
return [head, "", "```", e.diffPreview.trim(), "```", ""];
|
|
839
|
+
}), "");
|
|
840
|
+
if (agg.sessions.length > 0) {
|
|
841
|
+
lines.push("## Sessions", ...agg.sessions.map((s) => `- \`${s.sessionId}\` — ${s.events} event${s.events === 1 ? "" : "s"}, worst: ${s.worst}, last ${relativeTime(s.lastTs, nowMs)}`), "");
|
|
842
|
+
}
|
|
843
|
+
if (agg.reasons.length > 0) {
|
|
844
|
+
lines.push("## Reasons", ...agg.reasons.map((r) => `- ${r.label} × ${r.count}`), "");
|
|
845
|
+
}
|
|
846
|
+
const hosts = model.wiredHosts ?? [];
|
|
847
|
+
if (hosts.some((h) => h.wired !== "none")) {
|
|
848
|
+
lines.push("## Wired hosts");
|
|
849
|
+
for (const h of hosts) {
|
|
850
|
+
if (h.recipeOnly) {
|
|
851
|
+
lines.push(`- ${h.label} — manual setup (docs recipe)`);
|
|
852
|
+
}
|
|
853
|
+
else if (h.wired === "none") {
|
|
854
|
+
lines.push(`- ${h.label} — not wired`);
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
const reload = h.reload ? ` · ${h.reload.reload}` : "";
|
|
858
|
+
lines.push(`- ${h.label} — wired (${h.wired})${reload} · ${h.running ? "running" : "not running"}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
lines.push("");
|
|
862
|
+
}
|
|
863
|
+
if (model.sandboxes && (model.sandboxes.sandboxes.length > 0 || model.sandboxes.backend)) {
|
|
864
|
+
const sb = model.sandboxes;
|
|
865
|
+
lines.push("## Sandboxes");
|
|
866
|
+
if (sb.backend)
|
|
867
|
+
lines.push(`Configured backend: ${sb.backend}`);
|
|
868
|
+
for (const s of sb.sandboxes) {
|
|
869
|
+
lines.push(`- \`${s.sandboxId}\` — ${s.backend} · ${s.status} · created ${relativeTime(s.createdAt, nowMs)}`);
|
|
870
|
+
}
|
|
871
|
+
lines.push("");
|
|
872
|
+
}
|
|
873
|
+
if (model.orr) {
|
|
874
|
+
const orr = model.orr;
|
|
875
|
+
lines.push("## Operational readiness", `overall: ${orr.overall} · disposition: ${orr.disposition.replace(/_/g, " ")}${orr.targetName ? ` · target ${orr.targetName}` : ""}`);
|
|
876
|
+
if (orr.primaryFailureMode)
|
|
877
|
+
lines.push(`Primary failure mode: ${orr.primaryFailureMode}`);
|
|
878
|
+
if (orr.mostUrgentFix)
|
|
879
|
+
lines.push(`Most urgent fix: ${orr.mostUrgentFix}`);
|
|
880
|
+
lines.push(`scorecards: ${orr.scorecards.pass} pass · ${orr.scorecards.fail} fail · ${orr.scorecards.partial} partial · ${orr.scorecards.notEvaluated} n/e${orr.generatedAt ? ` · ${relativeTime(orr.generatedAt, nowMs)}` : ""}`, "");
|
|
881
|
+
}
|
|
882
|
+
if (model.showback) {
|
|
883
|
+
const sb = model.showback;
|
|
884
|
+
lines.push("## Showback (observe only)", `${sb.totalTokensIn} tokens in · ${sb.totalTokensOut} tokens out · ${usdText(sb.estimatedUsd)}`);
|
|
885
|
+
const topRuns = [...sb.perRun]
|
|
886
|
+
.sort((a, b) => (b.estimatedUsd ?? -1) - (a.estimatedUsd ?? -1))
|
|
887
|
+
.slice(0, 5);
|
|
888
|
+
for (const r of topRuns) {
|
|
889
|
+
lines.push(`- \`${r.runId}\` — ${r.steps} step${r.steps === 1 ? "" : "s"} · ${usdText(r.estimatedUsd)}`);
|
|
890
|
+
}
|
|
891
|
+
lines.push(SHOWBACK_DISCLAIMER, "");
|
|
892
|
+
}
|
|
474
893
|
const md = `${lines.join("\n")}\n`;
|
|
475
894
|
assertNoSecrets(md);
|
|
476
895
|
return md;
|