@shield-agent/kya 0.22.0 → 0.23.1
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 +72 -30
- 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/certify/attest.js +5 -5
- package/dist/certify/catalog.d.ts +1 -1
- package/dist/certify/catalog.js +3 -3
- package/dist/certify/context.js +1 -1
- package/dist/certify/evaluate.d.ts +2 -2
- package/dist/certify/evaluate.js +12 -12
- package/dist/certify/live.js +2 -2
- package/dist/certify/render.d.ts +1 -1
- package/dist/certify/render.js +10 -10
- package/dist/cli.d.ts +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +49 -16
- package/dist/cli.js.map +1 -1
- package/dist/commands/certify.d.ts +1 -1
- package/dist/commands/certify.js +3 -3
- package/dist/commands/connect.d.ts +2 -2
- package/dist/commands/connect.js +22 -22
- package/dist/commands/dash.js +4 -4
- package/dist/commands/gate-serve.js +3 -3
- package/dist/commands/gate.d.ts +1 -1
- package/dist/commands/gate.js +16 -16
- package/dist/commands/hook.d.ts +1 -1
- package/dist/commands/hook.d.ts.map +1 -1
- package/dist/commands/hook.js +55 -18
- package/dist/commands/hook.js.map +1 -1
- package/dist/commands/import-traces.d.ts +16 -0
- package/dist/commands/import-traces.d.ts.map +1 -0
- package/dist/commands/import-traces.js +18 -0
- package/dist/commands/import-traces.js.map +1 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/investigate.d.ts +13 -0
- package/dist/commands/investigate.d.ts.map +1 -0
- package/dist/commands/investigate.js +22 -0
- package/dist/commands/investigate.js.map +1 -0
- package/dist/commands/notify-flush.d.ts +2 -0
- package/dist/commands/notify-flush.d.ts.map +1 -0
- package/dist/commands/notify-flush.js +47 -0
- package/dist/commands/notify-flush.js.map +1 -0
- package/dist/commands/orr.d.ts +3 -3
- package/dist/commands/orr.js +5 -5
- package/dist/commands/receipt-serve.d.ts +1 -1
- package/dist/commands/receipt-serve.js +1 -1
- package/dist/commands/serve-mcp.js +1 -1
- package/dist/commands/start.d.ts +2 -2
- package/dist/commands/start.js +20 -20
- package/dist/commands/stop.d.ts +1 -1
- package/dist/commands/stop.js +6 -6
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +96 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/wire-hooks.d.ts +1 -1
- package/dist/commands/wire-hooks.js +14 -14
- package/dist/commands/wrap.d.ts.map +1 -1
- package/dist/commands/wrap.js +12 -7
- package/dist/commands/wrap.js.map +1 -1
- package/dist/config.d.ts +4 -4
- package/dist/config.js +5 -5
- package/dist/dash/dash.d.ts +1 -1
- package/dist/dash/dash.js +3 -3
- package/dist/dash/entitlement.d.ts +1 -1
- package/dist/dash/entitlement.js +1 -1
- package/dist/dash/render.js +2 -2
- package/dist/dash/screens/enterprise.js +9 -9
- package/dist/dash/screens/free.js +5 -5
- package/dist/diff-preview.d.ts +1 -1
- package/dist/diff-preview.js +2 -2
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/fs-atomic.d.ts +1 -1
- package/dist/fs-atomic.js +2 -2
- package/dist/gate/binary.js +4 -4
- package/dist/gate/config-gen.d.ts +1 -1
- package/dist/gate/config-gen.js +7 -7
- package/dist/gate/config.d.ts +2 -2
- package/dist/gate/config.js +6 -6
- package/dist/gate/daemon.js +1 -1
- package/dist/gate/discover.d.ts +2 -2
- package/dist/gate/discover.js +7 -7
- package/dist/gate/hosted-sync.js +1 -1
- package/dist/gate/otlp-decode.d.ts +2 -2
- package/dist/gate/otlp-decode.js +2 -2
- package/dist/gate/otlp-receiver.d.ts +1 -1
- package/dist/gate/otlp-receiver.js +3 -3
- package/dist/host-reload.js +11 -11
- package/dist/host-tools.d.ts +1 -1
- package/dist/host-tools.js +1 -1
- package/dist/importers/formats.d.ts +26 -0
- package/dist/importers/formats.d.ts.map +1 -0
- package/dist/importers/formats.js +281 -0
- package/dist/importers/formats.js.map +1 -0
- package/dist/importers/index.d.ts +26 -0
- package/dist/importers/index.d.ts.map +1 -0
- package/dist/importers/index.js +122 -0
- package/dist/importers/index.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/investigate/detectors.d.ts +35 -0
- package/dist/investigate/detectors.d.ts.map +1 -0
- package/dist/investigate/detectors.js +277 -0
- package/dist/investigate/detectors.js.map +1 -0
- package/dist/investigate/fix-brief.d.ts +3 -0
- package/dist/investigate/fix-brief.d.ts.map +1 -0
- package/dist/investigate/fix-brief.js +88 -0
- package/dist/investigate/fix-brief.js.map +1 -0
- package/dist/investigate/incidents.d.ts +19 -0
- package/dist/investigate/incidents.d.ts.map +1 -0
- package/dist/investigate/incidents.js +84 -0
- package/dist/investigate/incidents.js.map +1 -0
- package/dist/investigate/index.d.ts +25 -0
- package/dist/investigate/index.d.ts.map +1 -0
- package/dist/investigate/index.js +46 -0
- package/dist/investigate/index.js.map +1 -0
- package/dist/mcp/protocol.d.ts +2 -2
- package/dist/mcp/protocol.d.ts.map +1 -1
- package/dist/mcp/protocol.js +39 -8
- package/dist/mcp/protocol.js.map +1 -1
- package/dist/mcp/stdio.d.ts +1 -1
- package/dist/mcp/stdio.js +2 -2
- package/dist/mcp-servers.d.ts +2 -2
- package/dist/mcp-servers.js +2 -2
- package/dist/node-upgrade.d.ts +2 -2
- package/dist/node-upgrade.js +9 -9
- package/dist/notify/dispatch.d.ts +20 -0
- package/dist/notify/dispatch.d.ts.map +1 -0
- package/dist/notify/dispatch.js +52 -0
- package/dist/notify/dispatch.js.map +1 -0
- package/dist/notify/index.d.ts +39 -0
- package/dist/notify/index.d.ts.map +1 -0
- package/dist/notify/index.js +119 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/notify/sender.d.ts +56 -0
- package/dist/notify/sender.d.ts.map +1 -0
- package/dist/notify/sender.js +139 -0
- package/dist/notify/sender.js.map +1 -0
- package/dist/notify/templates.d.ts +33 -0
- package/dist/notify/templates.d.ts.map +1 -0
- package/dist/notify/templates.js +119 -0
- package/dist/notify/templates.js.map +1 -0
- package/dist/offline-evaluate.d.ts +2 -2
- package/dist/offline-evaluate.js +3 -3
- package/dist/orr/agentshield.d.ts +2 -2
- package/dist/orr/agentshield.js +4 -4
- package/dist/orr/scorecard.d.ts +1 -1
- package/dist/orr/scorecard.js +3 -3
- package/dist/otel/exporter.d.ts +64 -0
- package/dist/otel/exporter.d.ts.map +1 -0
- package/dist/otel/exporter.js +247 -0
- package/dist/otel/exporter.js.map +1 -0
- package/dist/parse-args.d.ts +1 -1
- package/dist/parse-args.js +1 -1
- package/dist/receipt/auto-open.d.ts +1 -1
- package/dist/receipt/auto-open.js +1 -1
- package/dist/receipt/daemon.d.ts +6 -6
- package/dist/receipt/daemon.js +15 -15
- package/dist/receipt/dashboard.d.ts +1 -1
- package/dist/receipt/dashboard.js +2 -2
- package/dist/receipt/embeddings.d.ts +15 -4
- package/dist/receipt/embeddings.d.ts.map +1 -1
- package/dist/receipt/embeddings.js +116 -35
- package/dist/receipt/embeddings.js.map +1 -1
- package/dist/receipt/enrich.d.ts +2 -2
- package/dist/receipt/enrich.js +5 -5
- package/dist/receipt/gate-card.d.ts +2 -2
- package/dist/receipt/gate-card.d.ts.map +1 -1
- package/dist/receipt/gate-card.js +3 -3
- package/dist/receipt/live-server.d.ts.map +1 -1
- package/dist/receipt/live-server.js +22 -8
- package/dist/receipt/live-server.js.map +1 -1
- package/dist/receipt/receipt-css.d.ts +1 -1
- package/dist/receipt/receipt-css.d.ts.map +1 -1
- package/dist/receipt/receipt-css.js +72 -11
- package/dist/receipt/receipt-css.js.map +1 -1
- package/dist/receipt/render-receipt.d.ts +1 -1
- package/dist/receipt/render-receipt.d.ts.map +1 -1
- package/dist/receipt/render-receipt.js +138 -87
- package/dist/receipt/render-receipt.js.map +1 -1
- package/dist/receipt/search.d.ts +11 -0
- package/dist/receipt/search.d.ts.map +1 -1
- package/dist/receipt/search.js +70 -3
- package/dist/receipt/search.js.map +1 -1
- package/dist/receipt/share.d.ts +2 -2
- package/dist/receipt/share.js +5 -5
- package/dist/sample-tools.d.ts +1 -1
- package/dist/sample-tools.js +1 -1
- package/dist/sandbox/mock-driver.d.ts +1 -1
- package/dist/sandbox/mock-driver.js +1 -1
- package/dist/sdk/a2a.d.ts +2 -2
- package/dist/sdk/a2a.js +1 -1
- package/dist/sdk/adapters/claude-agent.d.ts +1 -1
- package/dist/sdk/adapters/claude-agent.js +1 -1
- package/dist/sdk/adapters/langgraph.d.ts +1 -1
- package/dist/sdk/adapters/langgraph.js +1 -1
- package/dist/sdk/adapters/mastra.d.ts +1 -1
- package/dist/sdk/adapters/mastra.js +1 -1
- package/dist/sdk/adapters/openai-agents.d.ts +1 -1
- package/dist/sdk/adapters/openai-agents.js +1 -1
- package/dist/sdk/adapters/vercel-ai.d.ts +2 -2
- package/dist/sdk/adapters/vercel-ai.js +1 -1
- package/dist/sdk/govern.d.ts +2 -2
- package/dist/sdk/govern.js +3 -3
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/showback/trail-usage.d.ts +30 -0
- package/dist/showback/trail-usage.d.ts.map +1 -0
- package/dist/showback/trail-usage.js +50 -0
- package/dist/showback/trail-usage.js.map +1 -0
- package/dist/sign/evidence-bundle.d.ts +6 -6
- package/dist/sign/evidence-bundle.js +5 -5
- package/dist/star-cta.d.ts +1 -1
- package/dist/star-cta.d.ts.map +1 -1
- package/dist/star-cta.js +3 -3
- package/dist/trail-summary.d.ts +1 -1
- package/dist/trail-summary.js +3 -3
- package/dist/trail.d.ts +8 -2
- package/dist/trail.d.ts.map +1 -1
- package/dist/trail.js +18 -5
- package/dist/trail.js.map +1 -1
- package/dist/update-check.d.ts +17 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +134 -0
- package/dist/update-check.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 +5 -3
- package/qwen/settings.example.json +1 -1
- package/server.json +2 -2
|
@@ -8,6 +8,7 @@ import { productLabel, readTrail, readTrailSince } from "../trail.js";
|
|
|
8
8
|
import { mcpServerLabel, parseMcpToolId } from "../mcp-servers.js";
|
|
9
9
|
import { loadCertifyCard, loadIdentity, loadOrrCard, loadSandboxes, loadShowbackCard, loadWiredHosts, } from "./enrich.js";
|
|
10
10
|
import { SHOWBACK_DISCLAIMER, } from "../showback/cost-per-task.js";
|
|
11
|
+
import { summarizeTrailUsage } from "../showback/trail-usage.js";
|
|
11
12
|
import { computeDashboard } from "./dashboard.js";
|
|
12
13
|
import { buildChangesModel } from "./changes.js";
|
|
13
14
|
import { GATE_NOT_SETUP_QUICKSTART, loadGatePage, toolNameFromToolId, toolServerPrefix, } from "./gate-page.js";
|
|
@@ -304,7 +305,7 @@ function modeBadge(mode) {
|
|
|
304
305
|
return "";
|
|
305
306
|
return `<span class="modeb" title="${letter === "O" ? "observe" : letter === "H" ? "hold" : "offline"} mode">${letter}</span>`;
|
|
306
307
|
}
|
|
307
|
-
// project is attacker-controllable (trail files are user-editable)
|
|
308
|
+
// project is attacker-controllable (trail files are user-editable) - always escaped.
|
|
308
309
|
function projectMeta(project) {
|
|
309
310
|
const p = project?.trim();
|
|
310
311
|
if (!p)
|
|
@@ -316,8 +317,8 @@ function projectMeta(project) {
|
|
|
316
317
|
function renderFeed(events, nowMs, live, preserveOrder) {
|
|
317
318
|
if (events.length === 0) {
|
|
318
319
|
const hint = live
|
|
319
|
-
? "No events yet
|
|
320
|
-
: "No events yet
|
|
320
|
+
? "No events yet - evaluate a tool call with <code>kya wrap</code> and this page will refresh."
|
|
321
|
+
: "No events yet - evaluate a tool call with <code>kya wrap</code>, then regenerate this receipt.";
|
|
321
322
|
return `<p class="empty">${hint}</p>`;
|
|
322
323
|
}
|
|
323
324
|
const ordered = preserveOrder ? [...events] : [...events].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
@@ -377,7 +378,7 @@ function changeWorstPill(worst) {
|
|
|
377
378
|
*/
|
|
378
379
|
function changesPanel(changes, nowMs) {
|
|
379
380
|
if (changes.sessions.length === 0) {
|
|
380
|
-
return `<p class="empty">No recorded changes yet
|
|
381
|
+
return `<p class="empty">No recorded changes yet - write/edit tool calls will show up here.</p>`;
|
|
381
382
|
}
|
|
382
383
|
return changes.sessions
|
|
383
384
|
.map((s) => {
|
|
@@ -427,7 +428,7 @@ function identityLine(identity) {
|
|
|
427
428
|
const bits = [nameId, identity.host, identity.baseUrl].filter((b) => typeof b === "string" && b.trim().length > 0);
|
|
428
429
|
if (bits.length === 0)
|
|
429
430
|
return "";
|
|
430
|
-
// baseUrl is inert text here
|
|
431
|
+
// baseUrl is inert text here - never an anchor.
|
|
431
432
|
return `<div class="identity">${esc(clip(bits.join(" · "), 160))}</div>`;
|
|
432
433
|
}
|
|
433
434
|
function navItem(href, label, count, runningDot) {
|
|
@@ -526,7 +527,7 @@ function worstTone(worst) {
|
|
|
526
527
|
/**
|
|
527
528
|
* Analytics dashboard: four cards in a 2×2 grid. Every data row except the
|
|
528
529
|
* timeline is a filter toggle on the same data-fgroup/data-fvalue contract as
|
|
529
|
-
* the header chips
|
|
530
|
+
* the header chips - the engine picks them up via the [data-fgroup] query.
|
|
530
531
|
*/
|
|
531
532
|
function dashboardPanel(db) {
|
|
532
533
|
if (db.verdictMix.total === 0)
|
|
@@ -554,7 +555,7 @@ function dashboardPanel(db) {
|
|
|
554
555
|
const tlCols = buckets
|
|
555
556
|
.map((b) => {
|
|
556
557
|
const h = b.count === 0 ? 0 : Math.max(6, Math.round((b.count / maxCount) * 100));
|
|
557
|
-
return `<div class="col" title="${esc(b.label)}
|
|
558
|
+
return `<div class="col" title="${esc(b.label)} - ${b.count} event${b.count === 1 ? "" : "s"}"><span class="db-vbar" style="height:${h}%"></span><span class="lab">${esc(b.label)}</span></div>`;
|
|
558
559
|
})
|
|
559
560
|
.join("\n ");
|
|
560
561
|
const timelineCard = `<div class="db-card">
|
|
@@ -608,14 +609,14 @@ function wiredHostsPanel(hosts) {
|
|
|
608
609
|
.sort((a, b) => rank(a) - rank(b))
|
|
609
610
|
.map((h) => {
|
|
610
611
|
if (h.recipeOnly) {
|
|
611
|
-
return `<li class="mute">${esc(h.label)}
|
|
612
|
+
return `<li class="mute">${esc(h.label)} - manual setup <span class="hint" title="${esc(h.recipeOnly)}">docs recipe</span></li>`;
|
|
612
613
|
}
|
|
613
614
|
if (h.wired === "none") {
|
|
614
|
-
return `<li class="mute">${esc(h.label)}
|
|
615
|
+
return `<li class="mute">${esc(h.label)} - not wired</li>`;
|
|
615
616
|
}
|
|
616
617
|
const reload = h.reload ? ` · ${esc(h.reload.reload)}` : "";
|
|
617
618
|
const running = h.running ? "running" : "not running";
|
|
618
|
-
return `<li>${esc(h.label)}
|
|
619
|
+
return `<li>${esc(h.label)} - wired (${h.wired})${reload} · ${running}</li>`;
|
|
619
620
|
})
|
|
620
621
|
.join("\n ");
|
|
621
622
|
return `<section class="panel" aria-label="Wired hosts">
|
|
@@ -644,7 +645,7 @@ function gatePanel(card, nowMs) {
|
|
|
644
645
|
}
|
|
645
646
|
const binary = card.binaryPresent
|
|
646
647
|
? `binary ${card.binaryVersion ? esc(clip(card.binaryVersion, 40)) : "installed"}`
|
|
647
|
-
: "binary missing
|
|
648
|
+
: "binary missing - kya gate setup";
|
|
648
649
|
const uptime = card.startedAt ? ` · up since ${esc(relativeTime(card.startedAt, nowMs))}` : "";
|
|
649
650
|
const pill = card.state === "running"
|
|
650
651
|
? `<span class="pill ok">running</span>`
|
|
@@ -752,7 +753,7 @@ function gateHomePage(page, nowMs) {
|
|
|
752
753
|
if (page.state === "not-set-up") {
|
|
753
754
|
return `<section class="zone gate-zone" id="gateway-home" aria-labelledby="tab-gateway-home">
|
|
754
755
|
${gateStatusHeader(page, nowMs)}
|
|
755
|
-
<p class="page-sub">Local MCP gate
|
|
756
|
+
<p class="page-sub">Local MCP gate - every proxied tool call is evaluated and audited.</p>
|
|
756
757
|
<div class="card">
|
|
757
758
|
<span class="pill">not set up</span>
|
|
758
759
|
<p class="line">${esc(GATE_NOT_SETUP_QUICKSTART)}</p>
|
|
@@ -762,7 +763,7 @@ function gateHomePage(page, nowMs) {
|
|
|
762
763
|
}
|
|
763
764
|
return `<section class="zone gate-zone" id="gateway-home" aria-labelledby="tab-gateway-home">
|
|
764
765
|
${gateStatusHeader(page, nowMs)}
|
|
765
|
-
<p class="page-sub">Local MCP gate
|
|
766
|
+
<p class="page-sub">Local MCP gate - every proxied tool call is evaluated and audited.</p>
|
|
766
767
|
${gateStatCards(page)}
|
|
767
768
|
<div class="card-grid">
|
|
768
769
|
<div class="card">
|
|
@@ -770,7 +771,7 @@ function gateHomePage(page, nowMs) {
|
|
|
770
771
|
<p class="line">${page.url ? `<code>${esc(clip(page.url, 80))}</code>` : `Listener port ${page.port ?? 3930}`} · ${page.state}</p>
|
|
771
772
|
<p class="line">OTLP receiver port ${page.otlpPort}</p>
|
|
772
773
|
<p class="line">Failure mode <span class="pill">${esc(page.failureMode)}</span></p>
|
|
773
|
-
${page.binaryPresent ? `<p class="line">Binary <code>${page.binaryVersion ? esc(clip(page.binaryVersion, 40)) : "installed"}</code></p>` : `<p class="line">Binary missing
|
|
774
|
+
${page.binaryPresent ? `<p class="line">Binary <code>${page.binaryVersion ? esc(clip(page.binaryVersion, 40)) : "installed"}</code></p>` : `<p class="line">Binary missing - <code>kya gate setup</code></p>`}
|
|
774
775
|
</div>
|
|
775
776
|
<div class="card">
|
|
776
777
|
<h3>Quick actions</h3>
|
|
@@ -787,7 +788,7 @@ function gateListenersPage(page) {
|
|
|
787
788
|
<td>${esc(l.name)}</td>
|
|
788
789
|
<td>${esc(l.protocol)}</td>
|
|
789
790
|
<td><code>${esc(l.address)}</code></td>
|
|
790
|
-
<td>${l.uptime ? `<time datetime="${esc(l.uptime)}">${esc(relativeTime(l.uptime, Date.now()))}</time>` : '<span class="mute"
|
|
791
|
+
<td>${l.uptime ? `<time datetime="${esc(l.uptime)}">${esc(relativeTime(l.uptime, Date.now()))}</time>` : '<span class="mute">-</span>'}</td>
|
|
791
792
|
<td>${esc(l.detail ?? "")}</td>
|
|
792
793
|
</tr>`)
|
|
793
794
|
.join("\n");
|
|
@@ -810,7 +811,7 @@ function gateRoutesPage(page) {
|
|
|
810
811
|
<h2 class="page-title">Routes</h2>
|
|
811
812
|
<p class="page-sub">One route per configured backend server.</p>
|
|
812
813
|
<div class="card">
|
|
813
|
-
<p class="empty small">No servers configured
|
|
814
|
+
<p class="empty small">No servers configured - add one to .kya/gateways.json or run <code>kya gate init</code>.</p>
|
|
814
815
|
</div>
|
|
815
816
|
</section>`;
|
|
816
817
|
}
|
|
@@ -818,7 +819,7 @@ function gateRoutesPage(page) {
|
|
|
818
819
|
.map((r) => {
|
|
819
820
|
const dot = r.worst === "none" ? "" : `<span class="wdot ${r.worst}"></span>`;
|
|
820
821
|
const denied = r.deniedTools.length === 0
|
|
821
|
-
? '<span class="mute"
|
|
822
|
+
? '<span class="mute">-</span>'
|
|
822
823
|
: r.deniedTools.map((t) => `<span class="chip bad">${esc(t)}</span>`).join("");
|
|
823
824
|
return ` <tr data-server="${esc(r.backendLabel)}">
|
|
824
825
|
<td><code>${esc(r.pattern)}</code></td>
|
|
@@ -849,7 +850,7 @@ function gateBackendsPage(page) {
|
|
|
849
850
|
<h2 class="page-title">Backends</h2>
|
|
850
851
|
<p class="page-sub">Configured upstream MCP servers.</p>
|
|
851
852
|
<div class="card">
|
|
852
|
-
<p class="empty small">No servers configured
|
|
853
|
+
<p class="empty small">No servers configured - add one to .kya/gateways.json or run <code>kya gate init</code>.</p>
|
|
853
854
|
</div>
|
|
854
855
|
</section>`;
|
|
855
856
|
}
|
|
@@ -861,7 +862,7 @@ function gateBackendsPage(page) {
|
|
|
861
862
|
const provenance = s.importedFrom.map((h) => `<span class="chip">${esc(h)}</span>`).join("");
|
|
862
863
|
const dot = s.worst === "none" ? "" : `<span class="wdot ${s.worst}"></span>`;
|
|
863
864
|
const denied = s.deniedTools.length === 0
|
|
864
|
-
? '<span class="mute"
|
|
865
|
+
? '<span class="mute">-</span>'
|
|
865
866
|
: s.deniedTools.map((t) => `<span class="chip bad">${esc(t)}</span>`).join("");
|
|
866
867
|
return ` <tr data-server="${esc(s.serverFacet)}">
|
|
867
868
|
<td><span class="wdot ${s.worst === "none" ? "mute" : s.worst}"></span> <code>${esc(s.id)}</code></td>
|
|
@@ -910,7 +911,7 @@ function gatePoliciesPage(page) {
|
|
|
910
911
|
: page.servers
|
|
911
912
|
.map((s) => `<div class="card">
|
|
912
913
|
<h3><code>${esc(s.id)}</code> · ${s.policy.denyPatterns.length} pattern${s.policy.denyPatterns.length === 1 ? "" : "s"}</h3>
|
|
913
|
-
${s.policy.denyPatterns.length === 0 ? '<p class="mute small">No deny patterns
|
|
914
|
+
${s.policy.denyPatterns.length === 0 ? '<p class="mute small">No deny patterns - all tools observed.</p>' : `<pre><code>${esc(s.policy.denyPatterns.join("\n"))}</code></pre>`}
|
|
914
915
|
</div>`)
|
|
915
916
|
.join("\n");
|
|
916
917
|
return `<section class="zone gate-zone" id="gateway-policies" aria-labelledby="tab-gateway-policies">
|
|
@@ -938,7 +939,7 @@ function gatePlaygroundPage(page) {
|
|
|
938
939
|
: `<p class="mute small">Recent tools: ${page.playgroundSamples.map((t) => `<code>${esc(t)}</code>`).join(" ")}</p>`;
|
|
939
940
|
return `<section class="zone gate-zone" id="gateway-playground" aria-labelledby="tab-gateway-playground">
|
|
940
941
|
<h2 class="page-title">Playground</h2>
|
|
941
|
-
<p class="page-sub">Dry-run a tool call against the generated policy
|
|
942
|
+
<p class="page-sub">Dry-run a tool call against the generated policy - no tool is executed.</p>
|
|
942
943
|
<div class="card">
|
|
943
944
|
<form class="playground-form" id="gate-play-form">
|
|
944
945
|
<label class="sr-only" for="gate-play-server">Server</label>
|
|
@@ -1028,7 +1029,7 @@ function usdText(usd) {
|
|
|
1028
1029
|
return usd === null ? "USD n/a" : `~$${usd.toFixed(2)}`;
|
|
1029
1030
|
}
|
|
1030
1031
|
/** KPI tile: big tabular number over a small caps label. The tone class is
|
|
1031
|
-
* only applied above zero
|
|
1032
|
+
* only applied above zero - a zero count never reads as a signal. */
|
|
1032
1033
|
function kpiTile(label, value, cls = "") {
|
|
1033
1034
|
const v = typeof value === "number" ? value : esc(value);
|
|
1034
1035
|
const toned = typeof value === "number" && value > 0 && cls ? ` ${cls}` : "";
|
|
@@ -1043,17 +1044,21 @@ function kpiTile(label, value, cls = "") {
|
|
|
1043
1044
|
function heroCertifyPanel(certify) {
|
|
1044
1045
|
if (!certify) {
|
|
1045
1046
|
return `<section class="panel hero-certify none" aria-label="Certify">
|
|
1046
|
-
<h2>Certify
|
|
1047
|
+
<h2>Certify - Agent Trust Baseline</h2>
|
|
1047
1048
|
<div class="orrline">
|
|
1048
1049
|
<span class="pill">not evaluated</span>
|
|
1049
1050
|
</div>
|
|
1050
|
-
<p class="mute small">No live evaluation available
|
|
1051
|
+
<p class="mute small">No live evaluation available - wrap tool calls and run kya certify to build the baseline.</p>
|
|
1051
1052
|
</section>`;
|
|
1052
1053
|
}
|
|
1053
1054
|
const state = certify.result === "pass" ? "pass" : "gap";
|
|
1054
|
-
// Status legend
|
|
1055
|
-
//
|
|
1056
|
-
const legend = `<
|
|
1055
|
+
// Status legend as a definition list so each counter is explained on first
|
|
1056
|
+
// glance, not hidden in a dense sentence.
|
|
1057
|
+
const legend = `<dl class="cert-legend">
|
|
1058
|
+
<div><dt>Gap</dt><dd>requirement is failing - this is your work plan</dd></div>
|
|
1059
|
+
<div><dt>Insufficient</dt><dd>not enough local evidence to evaluate (never counts as a pass)</dd></div>
|
|
1060
|
+
<div><dt>Attested</dt><dd>your signed statement, unverified until audited</dd></div>
|
|
1061
|
+
</dl>`;
|
|
1057
1062
|
// Attested summary: the hero's gap rows are gap-status only, so attestations
|
|
1058
1063
|
// surface as one line naming the first attested requirement (+N more) with
|
|
1059
1064
|
// its attestation text.
|
|
@@ -1061,22 +1066,38 @@ function heroCertifyPanel(certify) {
|
|
|
1061
1066
|
const attestedLine = certify.attested === 0
|
|
1062
1067
|
? ""
|
|
1063
1068
|
: firstAttested
|
|
1064
|
-
? `<
|
|
1065
|
-
: `<p class="att-line">attested: ${certify.attested}
|
|
1069
|
+
? `<div class="att-line">attested: <code>${esc(firstAttested.id)}</code>${certify.attested > 1 ? ` (+${certify.attested - 1} more)` : ""} - "${expandableText("att-quote", firstAttested.attestation?.text ?? firstAttested.evidence, 100)}"</div>`
|
|
1070
|
+
: `<p class="att-line">attested: ${certify.attested} - your signed statement, unverified</p>`;
|
|
1066
1071
|
// topGaps arrives severity-ordered from the loader; cap defensively at 5.
|
|
1067
|
-
// Each
|
|
1068
|
-
//
|
|
1072
|
+
// Each gap is an expandable <details> row: summary stays compact, the body
|
|
1073
|
+
// reveals the full requirement title, what the status means, the unclipped
|
|
1074
|
+
// evidence, and the exact CLI command to close the gap.
|
|
1069
1075
|
const gaps = certify.topGaps.length === 0
|
|
1070
1076
|
? ""
|
|
1071
|
-
: `<
|
|
1077
|
+
: `<div class="cert-rows">
|
|
1072
1078
|
${certify.topGaps
|
|
1073
1079
|
.slice(0, 5)
|
|
1074
|
-
.map((g) =>
|
|
1080
|
+
.map((g) => {
|
|
1081
|
+
const cmd = `kya certify --attest ${g.id} --text "…"`;
|
|
1082
|
+
return ` <details class="req-row">
|
|
1083
|
+
<summary><code>${esc(g.id)}</code> - ${esc(g.title)} <span class="sev ${esc(g.severity)}">${esc(g.severity)}</span></summary>
|
|
1084
|
+
<div class="req-body">
|
|
1085
|
+
<p class="req-mean">Gap means this requirement is currently failing against the local evidence in the ${certify.windowDays}-day window.</p>
|
|
1086
|
+
${g.evidence ? `<p class="req-evi"><strong>Evidence:</strong> ${esc(g.evidence)}</p>` : ""}
|
|
1087
|
+
<p class="req-cta"><strong>Close it:</strong> <code>${esc(cmd)}</code> <span class="req-hint">(replace "…" with your accountable statement or fix the underlying control)</span></p>
|
|
1088
|
+
</div>
|
|
1089
|
+
</details>`;
|
|
1090
|
+
})
|
|
1075
1091
|
.join("\n")}
|
|
1076
|
-
</
|
|
1092
|
+
</div>`;
|
|
1093
|
+
// Call-to-action: one primary command and one secondary link to the detail tab.
|
|
1094
|
+
const cta = `<div class="cert-cta">
|
|
1095
|
+
<a class="btn" href="#certify">Open full requirement table</a>
|
|
1096
|
+
<span class="mute small">or run <code>kya certify --open</code> for the signed bundle</span>
|
|
1097
|
+
</div>`;
|
|
1077
1098
|
// Pill tones reuse the ORR palette: pass is green, gap is amber.
|
|
1078
1099
|
return `<section class="panel hero-certify ${state}" aria-label="Certify">
|
|
1079
|
-
<h2>Certify
|
|
1100
|
+
<h2>Certify - Agent Trust Baseline</h2>
|
|
1080
1101
|
<div class="orrline">
|
|
1081
1102
|
<span class="pill orr-${state === "pass" ? "green" : "amber"}">${esc(certify.result)}</span>
|
|
1082
1103
|
</div>
|
|
@@ -1089,7 +1110,8 @@ ${certify.topGaps
|
|
|
1089
1110
|
${legend}
|
|
1090
1111
|
${attestedLine}
|
|
1091
1112
|
${gaps}
|
|
1092
|
-
|
|
1113
|
+
${cta}
|
|
1114
|
+
<p class="mute small">live evaluation - window ${certify.windowDays}d, ${certify.trailEvents} trail events · the Certify tab has the complete gap report · <code>kya certify</code> writes the signed evidence bundle</p>
|
|
1093
1115
|
</section>`;
|
|
1094
1116
|
}
|
|
1095
1117
|
/** Detail-table pill tones, reusing the existing pill/tone system: pass is
|
|
@@ -1101,14 +1123,24 @@ const DETAIL_PILL_TONE = {
|
|
|
1101
1123
|
insufficient_evidence: "",
|
|
1102
1124
|
attested: "att",
|
|
1103
1125
|
};
|
|
1104
|
-
const CERTIFY_DETAIL_TITLE_CLIP = 120;
|
|
1105
1126
|
const CERTIFY_DETAIL_CLIP = 160;
|
|
1106
1127
|
/**
|
|
1107
|
-
*
|
|
1128
|
+
* Long evidence/attestation text renders in full behind a Read more toggle;
|
|
1129
|
+
* at or under the preview length it stays a plain inline span. The preview
|
|
1130
|
+
* keeps the old clip length, so collapsed rows look exactly as before.
|
|
1131
|
+
*/
|
|
1132
|
+
function expandableText(cls, text, previewLen = CERTIFY_DETAIL_CLIP) {
|
|
1133
|
+
const clean = stripEscapes(text).replace(/\r?\n/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
1134
|
+
if (clean.length <= previewLen)
|
|
1135
|
+
return `<span class="${cls}">${esc(clean)}</span>`;
|
|
1136
|
+
return `<details class="req-more ${cls}"><summary>${esc(clip(clean, previewLen))}</summary><div class="full">${esc(clean)}</div></details>`;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Certify tab body: the full live requirement table - every evaluated
|
|
1108
1140
|
* requirement grouped by domain, domains in catalog order (the card's
|
|
1109
1141
|
* requirements arrive in catalog order, so first-seen order IS the catalog
|
|
1110
1142
|
* order). Each domain gets a heading row with mini-counts, then one row per
|
|
1111
|
-
* requirement: status pill, id, title, and a muted evidence line beneath
|
|
1143
|
+
* requirement: status pill, id, title, and a muted evidence line beneath -
|
|
1112
1144
|
* or the attestation text for attested rows. Empty input yields "" so the
|
|
1113
1145
|
* caller falls back to the zone-empty hint (fail-closed: no table without
|
|
1114
1146
|
* data).
|
|
@@ -1135,11 +1167,11 @@ function certifyDetailPanel(requirements) {
|
|
|
1135
1167
|
const tone = DETAIL_PILL_TONE[r.status];
|
|
1136
1168
|
const pill = `<span class="pill${tone ? ` ${tone}` : ""}">${esc(r.status.replace(/_/g, " "))}</span>`;
|
|
1137
1169
|
const detail = r.attestation
|
|
1138
|
-
?
|
|
1170
|
+
? expandableText("att", `attested ${r.attestation.at} - "${r.attestation.text}"`)
|
|
1139
1171
|
: r.evidence
|
|
1140
|
-
?
|
|
1172
|
+
? expandableText("evi", r.evidence)
|
|
1141
1173
|
: "";
|
|
1142
|
-
return ` <li>${pill} <code>${esc(r.id)}</code>
|
|
1174
|
+
return ` <li>${pill} <code>${esc(r.id)}</code> - ${esc(r.title)}${detail}</li>`;
|
|
1143
1175
|
})
|
|
1144
1176
|
.join("\n");
|
|
1145
1177
|
const label = DOMAIN_LABELS[domain] ?? domain;
|
|
@@ -1152,7 +1184,7 @@ ${items}
|
|
|
1152
1184
|
})
|
|
1153
1185
|
.join("\n");
|
|
1154
1186
|
return `<section class="panel certify-detail" aria-label="Certify detail">
|
|
1155
|
-
<h2>Every requirement
|
|
1187
|
+
<h2>Every requirement - live evaluation</h2>
|
|
1156
1188
|
${sections}
|
|
1157
1189
|
</section>`;
|
|
1158
1190
|
}
|
|
@@ -1161,7 +1193,7 @@ ${sections}
|
|
|
1161
1193
|
function verdictsHeroCard(c, total) {
|
|
1162
1194
|
// Base 100% = total events, matching dashboard.ts's verdictMix contract:
|
|
1163
1195
|
// verdict is a free-form string from untrusted JSONL, so an event may land
|
|
1164
|
-
// in no bucket (bogus verdict) while still counting in `never`
|
|
1196
|
+
// in no bucket (bogus verdict) while still counting in `never` - only the
|
|
1165
1197
|
// raw event count keeps every bar inside the base the Overview card uses.
|
|
1166
1198
|
const pct = (n) => (total > 0 ? Math.round((n / total) * 100) : 0);
|
|
1167
1199
|
const mixRow = (label, n, fill) => `<div class="mix-row"><span class="mix-lab">${label}</span><span class="mix-bar"><span class="mix-fill ${fill}" style="width:${pct(n)}%"></span></span><span class="mix-num">${n}</span></div>`;
|
|
@@ -1180,7 +1212,7 @@ const SPARK_H = 24;
|
|
|
1180
1212
|
/**
|
|
1181
1213
|
* Inline 7-bar sparkline (no JS): the last 7 activity buckets, left-padded
|
|
1182
1214
|
* with zeros, heights normalized so the max bucket is full height. Empty
|
|
1183
|
-
* input renders a flat baseline
|
|
1215
|
+
* input renders a flat baseline - heights are guarded, never NaN/Infinity.
|
|
1184
1216
|
*/
|
|
1185
1217
|
function activitySparkline(activity) {
|
|
1186
1218
|
const counts = activity.buckets.slice(-SPARK_BARS).map((b) => b.count);
|
|
@@ -1207,10 +1239,16 @@ function activityHeroCard(agg, db, total) {
|
|
|
1207
1239
|
</section>`;
|
|
1208
1240
|
}
|
|
1209
1241
|
/** Hero-styled showback: same fields as the old panel (tokens in/out, est.
|
|
1210
|
-
* USD, top runs, disclaimer), omitted entirely when no report exists.
|
|
1211
|
-
|
|
1242
|
+
* USD, top runs, disclaimer), omitted entirely when no report exists. When
|
|
1243
|
+
* window events carry host-reported token usage, a line states how much of
|
|
1244
|
+
* the total is real vs the static per-event estimate. */
|
|
1245
|
+
function showbackHeroCard(report, events = []) {
|
|
1212
1246
|
if (!report)
|
|
1213
1247
|
return "";
|
|
1248
|
+
const usage = summarizeTrailUsage(events);
|
|
1249
|
+
const realLine = usage.realUsageEvents === 0
|
|
1250
|
+
? ""
|
|
1251
|
+
: `<p class="mute small">real host-reported usage on ${usage.realUsageEvents} of ${usage.events} window events (${usage.realTokensIn} in / ${usage.realTokensOut} out tokens); the rest is priced at the static estimate</p>`;
|
|
1214
1252
|
const topRuns = [...report.perRun]
|
|
1215
1253
|
.sort((a, b) => (b.estimatedUsd ?? -1) - (a.estimatedUsd ?? -1))
|
|
1216
1254
|
.slice(0, 5);
|
|
@@ -1218,7 +1256,7 @@ function showbackHeroCard(report) {
|
|
|
1218
1256
|
? ""
|
|
1219
1257
|
: `<ul class="rows">
|
|
1220
1258
|
${topRuns
|
|
1221
|
-
.map((r) => ` <li><code title="${esc(r.runId)}">${esc(clip(r.runId, 32))}</code>
|
|
1259
|
+
.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>`)
|
|
1222
1260
|
.join("\n")}
|
|
1223
1261
|
</ul>`;
|
|
1224
1262
|
return `<section class="panel" aria-label="Showback">
|
|
@@ -1229,6 +1267,7 @@ ${topRuns
|
|
|
1229
1267
|
${kpiTile("Est. USD", usdText(report.estimatedUsd))}
|
|
1230
1268
|
</div>
|
|
1231
1269
|
${runs}
|
|
1270
|
+
${realLine}
|
|
1232
1271
|
<p class="mute small">${esc(SHOWBACK_DISCLAIMER)}</p>
|
|
1233
1272
|
</section>`;
|
|
1234
1273
|
}
|
|
@@ -1242,7 +1281,7 @@ export function gatewayHeroCard(page, _nowMs) {
|
|
|
1242
1281
|
: `<span class="pill">stopped</span>`;
|
|
1243
1282
|
const binary = page.binaryPresent
|
|
1244
1283
|
? `binary ${page.binaryVersion ? esc(clip(page.binaryVersion, 40)) : "installed"}`
|
|
1245
|
-
: "binary missing
|
|
1284
|
+
: "binary missing - kya gate setup";
|
|
1246
1285
|
const detail = page.state === "running"
|
|
1247
1286
|
? `${page.url ? `<code>${esc(clip(page.url, 60))}</code>` : `port ${page.port ?? 3930}`} · ${page.listeners.length} listener${page.listeners.length === 1 ? "" : "s"} · ${page.servers.length} backend${page.servers.length === 1 ? "" : "s"}`
|
|
1248
1287
|
: `${page.servers.length} backend${page.servers.length === 1 ? "" : "s"} configured · ${binary}`;
|
|
@@ -1371,7 +1410,7 @@ export function renderReceiptHtml(model) {
|
|
|
1371
1410
|
<span class="label">Tools</span>
|
|
1372
1411
|
${tools.map((t) => `<code>${esc(t)}</code>`).join("")}
|
|
1373
1412
|
</footer>`;
|
|
1374
|
-
// liveToken is minted base64url by the live server
|
|
1413
|
+
// liveToken is minted base64url by the live server - safe in a JS string
|
|
1375
1414
|
// literal; static renders omit it. No post-hoc html.replace (spoofable).
|
|
1376
1415
|
const eventsUrl = model.liveToken ? `/events?t=${encodeURIComponent(model.liveToken)}` : "/events";
|
|
1377
1416
|
const liveScript = model.live
|
|
@@ -1495,21 +1534,26 @@ export function renderReceiptHtml(model) {
|
|
|
1495
1534
|
}
|
|
1496
1535
|
function apply(){
|
|
1497
1536
|
var on = Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; });
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1537
|
+
// Feed rows: filter client-side immediately so the UI reacts without
|
|
1538
|
+
// waiting for the server round-trip. For live/paginated reports the server
|
|
1539
|
+
// still returns the authoritative page via feedUpdate(), but this keeps
|
|
1540
|
+
// the Activity tab responsive and survives SSE reloads that would otherwise
|
|
1541
|
+
// overwrite the server response before the user sees it.
|
|
1542
|
+
var toggled = 0;
|
|
1543
|
+
feed.querySelectorAll('.ev').forEach(function(row){
|
|
1544
|
+
var visible = rowVisible(row, on);
|
|
1545
|
+
if (!visible) toggled++;
|
|
1546
|
+
row.classList.toggle('filtered-out', !visible);
|
|
1547
|
+
});
|
|
1548
|
+
try { window.__kyaFilterDebug = { toggled: toggled, feedRows: feed.querySelectorAll('.ev').length }; } catch(e){}
|
|
1549
|
+
var day = null, dayHasRows = false;
|
|
1550
|
+
function flush(){ if (day) day.classList.toggle('filtered-out', on && !dayHasRows); }
|
|
1551
|
+
for (var i = 0; i < feed.children.length; i++) {
|
|
1552
|
+
var el = feed.children[i];
|
|
1553
|
+
if (el.classList.contains('day')) { flush(); day = el; dayHasRows = false; }
|
|
1554
|
+
else if (el.classList.contains('ev') && !el.classList.contains('filtered-out')) dayHasRows = true;
|
|
1512
1555
|
}
|
|
1556
|
+
flush();
|
|
1513
1557
|
// Changes tab: entries filter individually; file/session groups collapse
|
|
1514
1558
|
// when no entry inside survives.
|
|
1515
1559
|
if (chg) {
|
|
@@ -1536,10 +1580,17 @@ export function renderReceiptHtml(model) {
|
|
|
1536
1580
|
function feedUpdate(){
|
|
1537
1581
|
var T = readToken();
|
|
1538
1582
|
if (!T) return;
|
|
1583
|
+
// Server expects { group: [value, ...] }, but client state stores
|
|
1584
|
+
// { group: { value: true } } for fast lookup. Convert before sending.
|
|
1585
|
+
var serverFilters = {};
|
|
1586
|
+
for (var g in state) {
|
|
1587
|
+
var vals = Object.keys(state[g]);
|
|
1588
|
+
if (vals.length) serverFilters[g] = vals;
|
|
1589
|
+
}
|
|
1539
1590
|
fetch('/feed?t=' + encodeURIComponent(T), {
|
|
1540
1591
|
method: 'POST',
|
|
1541
1592
|
headers: { 'Content-Type': 'application/json' },
|
|
1542
|
-
body: JSON.stringify({ filters:
|
|
1593
|
+
body: JSON.stringify({ filters: serverFilters, page: page, q: q })
|
|
1543
1594
|
})
|
|
1544
1595
|
.then(function(r){ return r.json(); })
|
|
1545
1596
|
.then(function(data){
|
|
@@ -1857,15 +1908,15 @@ export function renderReceiptHtml(model) {
|
|
|
1857
1908
|
const zoneBody = (parts, empty) => parts.some((p) => p.length > 0)
|
|
1858
1909
|
? parts.filter(Boolean).join("\n ")
|
|
1859
1910
|
: `<p class="mute small zone-empty">${empty}</p>`;
|
|
1860
|
-
const overviewBody = zoneBody([dashboardPanel(dashboard), sessionsPanel(agg, nowMs, changes), reasonsPanel(agg)], "No activity in this window yet
|
|
1861
|
-
const certifyBody = zoneBody([model.certify ? certifyDetailPanel(model.certify.requirements) : ""], "No live evaluation yet
|
|
1911
|
+
const overviewBody = zoneBody([dashboardPanel(dashboard), sessionsPanel(agg, nowMs, changes), reasonsPanel(agg)], "No activity in this window yet - analytics appear once events land.");
|
|
1912
|
+
const certifyBody = zoneBody([model.certify ? certifyDetailPanel(model.certify.requirements) : ""], "No live evaluation yet - wrap tool calls and run kya certify to build the baseline.");
|
|
1862
1913
|
const systemBody = zoneBody([
|
|
1863
1914
|
gatePanel(model.gate, nowMs),
|
|
1864
1915
|
wiredHostsPanel(model.wiredHosts),
|
|
1865
1916
|
sandboxesPanel(model.sandboxes, nowMs),
|
|
1866
1917
|
orrPanel(model.orr, nowMs),
|
|
1867
1918
|
toolsHtml,
|
|
1868
|
-
], "No system state yet
|
|
1919
|
+
], "No system state yet - wire a host, configure a sandbox, or run an ORR.");
|
|
1869
1920
|
const html = `<!DOCTYPE html>
|
|
1870
1921
|
<html lang="en">
|
|
1871
1922
|
<head>
|
|
@@ -1919,7 +1970,7 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
|
1919
1970
|
${verdictsHeroCard(c, events.length)}
|
|
1920
1971
|
${activityHeroCard(agg, dashboard, events.length)}
|
|
1921
1972
|
${gatewayHeroCard(model.gate, nowMs)}
|
|
1922
|
-
${showbackHeroCard(model.showback)}
|
|
1973
|
+
${showbackHeroCard(model.showback, model.events)}
|
|
1923
1974
|
</div>
|
|
1924
1975
|
</div>
|
|
1925
1976
|
${overviewBody}
|
|
@@ -2020,10 +2071,10 @@ export function renderReceiptMarkdown(model) {
|
|
|
2020
2071
|
if (c.topGaps.length > 0) {
|
|
2021
2072
|
lines.push("Top gaps:");
|
|
2022
2073
|
for (const g of c.topGaps) {
|
|
2023
|
-
lines.push(`- \`${mdInline(g.id)}\`
|
|
2074
|
+
lines.push(`- \`${mdInline(g.id)}\` - ${mdText(g.title)} (${mdText(g.severity)})`);
|
|
2024
2075
|
}
|
|
2025
2076
|
}
|
|
2026
|
-
lines.push(`live evaluation
|
|
2077
|
+
lines.push(`live evaluation - window ${c.windowDays}d, ${c.trailEvents} trail events · the Certify tab has the full live requirement table · kya certify for the gap report + signed bundle`, "");
|
|
2027
2078
|
}
|
|
2028
2079
|
const db = computeDashboard(model.events);
|
|
2029
2080
|
if (db.verdictMix.total > 0) {
|
|
@@ -2065,7 +2116,7 @@ export function renderReceiptMarkdown(model) {
|
|
|
2065
2116
|
const head = `- **${mdText(verdictWord(e.verdict))}** \`${mdInline(e.toolId)}\`${sum} - ${productLabel(e.product)} - ${mdText(e.reasonCode)}`;
|
|
2066
2117
|
if (!e.diffPreview?.trim())
|
|
2067
2118
|
return [head];
|
|
2068
|
-
// Fenced content is verbatim (stripEscapes only
|
|
2119
|
+
// Fenced content is verbatim (stripEscapes only - backslash escapes
|
|
2069
2120
|
// are literal in code fences); the dynamically sized tilde fence
|
|
2070
2121
|
// prevents breakout and backticks cannot close a tilde fence.
|
|
2071
2122
|
const diff = stripEscapes(e.diffPreview.trim());
|
|
@@ -2078,21 +2129,21 @@ export function renderReceiptMarkdown(model) {
|
|
|
2078
2129
|
if (changes.sessions.length > 0) {
|
|
2079
2130
|
lines.push("## Changes", "");
|
|
2080
2131
|
for (const s of changes.sessions) {
|
|
2081
|
-
lines.push(`### \`${mdInline(s.sessionId)}\`
|
|
2132
|
+
lines.push(`### \`${mdInline(s.sessionId)}\` - ${s.fileCount} file${s.fileCount === 1 ? "" : "s"}, ${s.changeCount} change${s.changeCount === 1 ? "" : "s"}, last ${relativeTime(s.lastTs, nowMs)}`, "");
|
|
2082
2133
|
for (const f of s.files) {
|
|
2083
|
-
lines.push(`#### \`${mdInline(f.path)}\`
|
|
2134
|
+
lines.push(`#### \`${mdInline(f.path)}\` - ${f.writes} write${f.writes === 1 ? "" : "s"}, worst: ${f.worst}`, "");
|
|
2084
2135
|
for (const e of f.entries) {
|
|
2085
2136
|
// Same fencing contract as the md feed: verbatim content, tilde
|
|
2086
2137
|
// fence one longer than the longest run inside.
|
|
2087
2138
|
const diff = stripEscapes(e.preview);
|
|
2088
2139
|
const fence = mdFence(diff);
|
|
2089
|
-
lines.push(`- **${mdText(verdictWord(e.verdict))}** \`${mdInline(e.toolId)}\`
|
|
2140
|
+
lines.push(`- **${mdText(verdictWord(e.verdict))}** \`${mdInline(e.toolId)}\` - ${mdText(e.ts)}`, "", fence, diff, fence, "");
|
|
2090
2141
|
}
|
|
2091
2142
|
}
|
|
2092
2143
|
}
|
|
2093
2144
|
}
|
|
2094
2145
|
if (agg.sessions.length > 0) {
|
|
2095
|
-
lines.push("## Sessions", ...agg.sessions.map((s) => `- \`${mdInline(s.sessionId)}\`
|
|
2146
|
+
lines.push("## Sessions", ...agg.sessions.map((s) => `- \`${mdInline(s.sessionId)}\` - ${s.events} event${s.events === 1 ? "" : "s"}, worst: ${s.worst}, last ${relativeTime(s.lastTs, nowMs)}`), "");
|
|
2096
2147
|
}
|
|
2097
2148
|
if (agg.reasons.length > 0) {
|
|
2098
2149
|
lines.push("## Reasons", ...agg.reasons.map((r) => `- ${mdText(r.label)} × ${r.count}`), "");
|
|
@@ -2101,20 +2152,20 @@ export function renderReceiptMarkdown(model) {
|
|
|
2101
2152
|
const g = model.gate;
|
|
2102
2153
|
lines.push("## Gateway");
|
|
2103
2154
|
if (g.state === "not-set-up") {
|
|
2104
|
-
lines.push(`not set up
|
|
2155
|
+
lines.push(`not set up - ${GATE_NOT_SETUP_QUICKSTART}`);
|
|
2105
2156
|
}
|
|
2106
2157
|
else {
|
|
2107
2158
|
const binary = g.binaryPresent
|
|
2108
2159
|
? `binary ${g.binaryVersion ? mdInline(clip(g.binaryVersion, 40)) : "installed"}`
|
|
2109
|
-
: "binary missing
|
|
2160
|
+
: "binary missing - kya gate setup";
|
|
2110
2161
|
lines.push(g.state === "running"
|
|
2111
|
-
? `running
|
|
2112
|
-
: `stopped
|
|
2162
|
+
? `running - \`${mdInline(clip(g.url ?? "", 60))}\` · ${binary} · ${g.events} event${g.events === 1 ? "" : "s"} in window`
|
|
2163
|
+
: `stopped - \`kya gate run\` · ${binary}`);
|
|
2113
2164
|
if (g.state === "running" && g.servers.length === 0) {
|
|
2114
|
-
lines.push("running, no servers configured
|
|
2165
|
+
lines.push("running, no servers configured - add servers to .kya/gateways.json and restart");
|
|
2115
2166
|
}
|
|
2116
2167
|
for (const s of g.servers) {
|
|
2117
|
-
lines.push(`- \`${mdInline(s.id)}\`
|
|
2168
|
+
lines.push(`- \`${mdInline(s.id)}\` - ${mdText(s.transport)} · ${s.events} event${s.events === 1 ? "" : "s"}${s.worst === "none" ? "" : `, worst: ${s.worst}`}`);
|
|
2118
2169
|
}
|
|
2119
2170
|
}
|
|
2120
2171
|
lines.push("");
|
|
@@ -2124,14 +2175,14 @@ export function renderReceiptMarkdown(model) {
|
|
|
2124
2175
|
lines.push("## Wired hosts");
|
|
2125
2176
|
for (const h of hosts) {
|
|
2126
2177
|
if (h.recipeOnly) {
|
|
2127
|
-
lines.push(`- ${mdText(h.label)}
|
|
2178
|
+
lines.push(`- ${mdText(h.label)} - manual setup (docs recipe)`);
|
|
2128
2179
|
}
|
|
2129
2180
|
else if (h.wired === "none") {
|
|
2130
|
-
lines.push(`- ${mdText(h.label)}
|
|
2181
|
+
lines.push(`- ${mdText(h.label)} - not wired`);
|
|
2131
2182
|
}
|
|
2132
2183
|
else {
|
|
2133
2184
|
const reload = h.reload ? ` · ${mdText(h.reload.reload)}` : "";
|
|
2134
|
-
lines.push(`- ${mdText(h.label)}
|
|
2185
|
+
lines.push(`- ${mdText(h.label)} - wired (${h.wired})${reload} · ${h.running ? "running" : "not running"}`);
|
|
2135
2186
|
}
|
|
2136
2187
|
}
|
|
2137
2188
|
lines.push("");
|
|
@@ -2142,7 +2193,7 @@ export function renderReceiptMarkdown(model) {
|
|
|
2142
2193
|
if (sb.backend)
|
|
2143
2194
|
lines.push(`Configured backend: ${mdText(sb.backend)}`);
|
|
2144
2195
|
for (const s of sb.sandboxes) {
|
|
2145
|
-
lines.push(`- \`${mdInline(s.sandboxId)}\`
|
|
2196
|
+
lines.push(`- \`${mdInline(s.sandboxId)}\` - ${mdText(s.backend)} · ${mdText(s.status)} · created ${relativeTime(s.createdAt, nowMs)}`);
|
|
2146
2197
|
}
|
|
2147
2198
|
lines.push("");
|
|
2148
2199
|
}
|
|
@@ -2162,7 +2213,7 @@ export function renderReceiptMarkdown(model) {
|
|
|
2162
2213
|
.sort((a, b) => (b.estimatedUsd ?? -1) - (a.estimatedUsd ?? -1))
|
|
2163
2214
|
.slice(0, 5);
|
|
2164
2215
|
for (const r of topRuns) {
|
|
2165
|
-
lines.push(`- \`${mdInline(r.runId)}\`
|
|
2216
|
+
lines.push(`- \`${mdInline(r.runId)}\` - ${r.steps} step${r.steps === 1 ? "" : "s"} · ${usdText(r.estimatedUsd)}`);
|
|
2166
2217
|
}
|
|
2167
2218
|
lines.push(SHOWBACK_DISCLAIMER, "");
|
|
2168
2219
|
}
|