@shield-agent/kya 0.32.0 → 0.32.4
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/.cursor-plugin/plugin.json +1 -1
- 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/orr.d.ts.map +1 -1
- package/dist/commands/orr.js +57 -3
- package/dist/commands/orr.js.map +1 -1
- package/dist/receipt/enrich.d.ts +8 -0
- package/dist/receipt/enrich.d.ts.map +1 -1
- package/dist/receipt/enrich.js +33 -0
- package/dist/receipt/enrich.js.map +1 -1
- package/dist/receipt/live-server.d.ts +7 -0
- package/dist/receipt/live-server.d.ts.map +1 -1
- package/dist/receipt/live-server.js +75 -18
- package/dist/receipt/live-server.js.map +1 -1
- package/dist/receipt/receipt-css.d.ts.map +1 -1
- package/dist/receipt/receipt-css.js +9 -0
- package/dist/receipt/receipt-css.js.map +1 -1
- package/dist/receipt/render-receipt.d.ts +10 -0
- package/dist/receipt/render-receipt.d.ts.map +1 -1
- package/dist/receipt/render-receipt.js +294 -69
- package/dist/receipt/render-receipt.js.map +1 -1
- 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
|
@@ -1289,6 +1289,19 @@ function orrPanel(orr, nowMs, live) {
|
|
|
1289
1289
|
? `<p class="line">Most urgent fix: ${esc(clip(orr.mostUrgentFix, 200))}</p>`
|
|
1290
1290
|
: "";
|
|
1291
1291
|
const when = orr.generatedAt ? ` · ${esc(relativeTime(orr.generatedAt, nowMs))}` : "";
|
|
1292
|
+
const findings = orr.findings.length
|
|
1293
|
+
? `<ul class="orrfindings">${orr.findings
|
|
1294
|
+
.map((f) => {
|
|
1295
|
+
const evidence = f.evidence
|
|
1296
|
+
? ` <span class="mute">- ${esc(clip(f.evidence, 160))}</span>`
|
|
1297
|
+
: "";
|
|
1298
|
+
const detail = f.detail
|
|
1299
|
+
? `<br><span class="mute small">${esc(clip(f.detail, 200))}</span>`
|
|
1300
|
+
: "";
|
|
1301
|
+
return `<li><span class="pill ${esc(severityCls(f.severity))}">${esc(f.severity)}</span> ${esc(clip(f.title, 120))}${evidence}${detail}</li>`;
|
|
1302
|
+
})
|
|
1303
|
+
.join("")}</ul>`
|
|
1304
|
+
: "";
|
|
1292
1305
|
return `<section class="panel" aria-label="ORR">
|
|
1293
1306
|
<h2>Operational readiness</h2>
|
|
1294
1307
|
<div class="orrline">
|
|
@@ -1298,10 +1311,18 @@ function orrPanel(orr, nowMs, live) {
|
|
|
1298
1311
|
</div>
|
|
1299
1312
|
${failure}
|
|
1300
1313
|
${fix}
|
|
1314
|
+
${findings}
|
|
1301
1315
|
<p class="mute small">scorecards: ${orr.scorecards.pass} pass · ${orr.scorecards.fail} fail · ${orr.scorecards.partial} partial · ${orr.scorecards.notEvaluated} n/e${when} · auto-refreshes every 30 min while the report runs</p>
|
|
1302
1316
|
${run}
|
|
1303
1317
|
</section>`;
|
|
1304
1318
|
}
|
|
1319
|
+
function severityCls(severity) {
|
|
1320
|
+
if (severity === "critical" || severity === "high")
|
|
1321
|
+
return "orr-red";
|
|
1322
|
+
if (severity === "medium")
|
|
1323
|
+
return "orr-amber";
|
|
1324
|
+
return "";
|
|
1325
|
+
}
|
|
1305
1326
|
function usdText(usd) {
|
|
1306
1327
|
return usd === null ? "USD n/a" : `~$${usd.toFixed(2)}`;
|
|
1307
1328
|
}
|
|
@@ -1320,7 +1341,7 @@ function kpiTile(label, value, cls = "") {
|
|
|
1320
1341
|
*/
|
|
1321
1342
|
function heroCertifyPanel(certify) {
|
|
1322
1343
|
if (!certify) {
|
|
1323
|
-
return `<section class="panel hero-certify none" aria-label="Certify">
|
|
1344
|
+
return `<section class="panel hero-certify none" id="frag-hero-certify" aria-label="Certify">
|
|
1324
1345
|
<h2>Certify - Agent Trust Baseline</h2>
|
|
1325
1346
|
<div class="orrline">
|
|
1326
1347
|
<span class="pill">not evaluated</span>
|
|
@@ -1373,7 +1394,7 @@ ${certify.topGaps
|
|
|
1373
1394
|
<span class="mute small">or run <code>kya certify --open</code> for the signed bundle</span>
|
|
1374
1395
|
</div>`;
|
|
1375
1396
|
// Pill tones reuse the ORR palette: pass is green, gap is amber.
|
|
1376
|
-
return `<section class="panel hero-certify ${state}" aria-label="Certify">
|
|
1397
|
+
return `<section class="panel hero-certify ${state}" id="frag-hero-certify" aria-label="Certify">
|
|
1377
1398
|
<h2>Certify - Agent Trust Baseline</h2>
|
|
1378
1399
|
<div class="orrline">
|
|
1379
1400
|
<span class="pill orr-${state === "pass" ? "green" : "amber"}">${esc(certify.result)}</span>
|
|
@@ -1852,6 +1873,63 @@ function paginationBar(total, page, pages) {
|
|
|
1852
1873
|
}
|
|
1853
1874
|
return `<div class="pagination-bar">\n ${prev}\n ${numbers.join("\n ")}\n ${next}\n <span class="mute">Page ${page} of ${pages} · ${total} event${total === 1 ? "" : "s"}</span>\n</div>`;
|
|
1854
1875
|
}
|
|
1876
|
+
/** Zones never render as bare whitespace: with no panels they get a hint. */
|
|
1877
|
+
const zoneBody = (parts, empty) => parts.some((p) => p.length > 0)
|
|
1878
|
+
? parts.filter(Boolean).join("\n ")
|
|
1879
|
+
: `<p class="mute small zone-empty">${empty}</p>`;
|
|
1880
|
+
/** Inner markup of the Activity counts bar (verdict buttons + mode/plane chips). */
|
|
1881
|
+
function statCountsInner(c, chips) {
|
|
1882
|
+
return `<button type="button" class="stat ok" data-fgroup="verdict" data-fvalue="ALLOW" aria-pressed="false" title="Filter: Allow">Allow<b>${c.allow}</b></button>
|
|
1883
|
+
<button type="button" class="stat bad" data-fgroup="verdict" data-fvalue="DENY" aria-pressed="false" title="Filter: Deny">Deny<b>${c.deny}</b></button>
|
|
1884
|
+
<button type="button" class="stat warn" data-fgroup="verdict" data-fvalue="REQUIRE_APPROVE" aria-pressed="false" title="Filter: Review">Review<b>${c.require}</b></button>
|
|
1885
|
+
<button type="button" class="stat" data-fgroup="never" data-fvalue="1" aria-pressed="false" title="Filter: Never">Never<b>${c.never}</b></button>
|
|
1886
|
+
${chips.modes}${chips.planes}
|
|
1887
|
+
<button type="button" class="stat clear-filters" id="clear-filters" hidden>Clear filters ×</button>`;
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Live-refresh fragments: the containers POST /fragments re-renders so the
|
|
1891
|
+
* report updates in place instead of reloading the whole page.
|
|
1892
|
+
* renderReceiptHtml builds the initial page through the same helpers, so
|
|
1893
|
+
* first paint and refreshes cannot drift apart.
|
|
1894
|
+
*/
|
|
1895
|
+
export function buildLiveFragments(model, livePaged) {
|
|
1896
|
+
const nowMs = Date.now();
|
|
1897
|
+
const events = normalizeTrailEvents(model.events);
|
|
1898
|
+
const c = countVerdicts(events);
|
|
1899
|
+
const agg = aggregateEvents(events);
|
|
1900
|
+
const chips = statChips(agg);
|
|
1901
|
+
const dashboard = computeDashboard(events);
|
|
1902
|
+
const changes = buildChangesModel(events);
|
|
1903
|
+
return {
|
|
1904
|
+
fragments: {
|
|
1905
|
+
statCounts: statCountsInner(c, chips),
|
|
1906
|
+
chipProducts: chips.products,
|
|
1907
|
+
chipProjects: chips.projects,
|
|
1908
|
+
chipServers: chips.servers,
|
|
1909
|
+
heroCertify: heroCertifyPanel(model.certify),
|
|
1910
|
+
heroTiles: [
|
|
1911
|
+
verdictsHeroCard(c, events.length),
|
|
1912
|
+
activityHeroCard(agg, dashboard, events.length),
|
|
1913
|
+
gatewayHeroCard(model.gate, nowMs),
|
|
1914
|
+
importHeroCard(model.imports, nowMs, livePaged),
|
|
1915
|
+
discoveryHeroCard(model.discovery, nowMs, livePaged),
|
|
1916
|
+
investigateHeroCard(model.investigate, nowMs, livePaged),
|
|
1917
|
+
showbackHeroCard(model.showback, events),
|
|
1918
|
+
].join("\n "),
|
|
1919
|
+
overviewPanels: zoneBody([dashboardPanel(dashboard), sessionsPanel(agg, nowMs, changes), reasonsPanel(agg)], "No activity in this window yet - analytics appear once events land."),
|
|
1920
|
+
certifyBody: zoneBody([model.certify ? certifyDetailPanel(model.certify.requirements) : ""], "No live evaluation yet - wrap tool calls and run kya certify to build the baseline."),
|
|
1921
|
+
orrPanel: orrPanel(model.orr, nowMs, livePaged),
|
|
1922
|
+
changesList: changesPanel(changes, nowMs),
|
|
1923
|
+
},
|
|
1924
|
+
nav: {
|
|
1925
|
+
overview: events.length,
|
|
1926
|
+
activity: events.length,
|
|
1927
|
+
changes: changes.fileCount,
|
|
1928
|
+
certify: model.certify?.gap ?? null,
|
|
1929
|
+
orr: model.orr ? model.orr.scorecards.fail : null,
|
|
1930
|
+
},
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1855
1933
|
export function renderReceiptHtml(model) {
|
|
1856
1934
|
const nowMs = Date.now();
|
|
1857
1935
|
const events = normalizeTrailEvents(model.events);
|
|
@@ -1871,11 +1949,12 @@ export function renderReceiptHtml(model) {
|
|
|
1871
1949
|
page: 1,
|
|
1872
1950
|
pages: 1,
|
|
1873
1951
|
};
|
|
1874
|
-
const c = countVerdicts(events);
|
|
1875
1952
|
const agg = aggregateEvents(events);
|
|
1876
1953
|
const chips = statChips(agg);
|
|
1877
|
-
const dashboard = computeDashboard(events);
|
|
1878
1954
|
const changes = buildChangesModel(events);
|
|
1955
|
+
// Live-refresh fragments render the initial page AND the POST /fragments
|
|
1956
|
+
// refresh, so the two can never drift apart.
|
|
1957
|
+
const fr = buildLiveFragments(model, livePaged);
|
|
1879
1958
|
const blocked = events.filter((e) => e.neverEvent || e.reasonCode === "NEVER_EVENT");
|
|
1880
1959
|
const tools = model.mcpSeen && model.mcpSeen.length > 0
|
|
1881
1960
|
? model.mcpSeen.map((m) => m.toolId)
|
|
@@ -1894,19 +1973,8 @@ export function renderReceiptHtml(model) {
|
|
|
1894
1973
|
</footer>`;
|
|
1895
1974
|
// liveToken is minted base64url by the live server - safe in a JS string
|
|
1896
1975
|
// literal; static renders omit it. No post-hoc html.replace (spoofable).
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
? `<script>
|
|
1900
|
-
(function(){
|
|
1901
|
-
var pill = document.querySelector('.live');
|
|
1902
|
-
var es = new EventSource('${eventsUrl}');
|
|
1903
|
-
es.onmessage = function(){ location.reload(); };
|
|
1904
|
-
es.onerror = function(){
|
|
1905
|
-
if (pill) { pill.textContent = 'Offline'; pill.classList.add('off'); }
|
|
1906
|
-
};
|
|
1907
|
-
})();
|
|
1908
|
-
</script>`
|
|
1909
|
-
: "";
|
|
1976
|
+
// Live updates ride the filter script's EventSource + /fragments refresh;
|
|
1977
|
+
// there is deliberately no page-level reload-on-change script.
|
|
1910
1978
|
// Client-side chip filters, pagination, and search: only emitted for live
|
|
1911
1979
|
// reports (liveToken present). Static receipts render a separate BM25-only
|
|
1912
1980
|
// search script below. State maps are null-prototype: attacker-controlled
|
|
@@ -1932,8 +2000,8 @@ export function renderReceiptHtml(model) {
|
|
|
1932
2000
|
var page = 1;
|
|
1933
2001
|
var q = '';
|
|
1934
2002
|
var LIVE = !!(document.querySelectorAll && document.querySelectorAll('main[data-live-token]').length);
|
|
1935
|
-
var
|
|
1936
|
-
var
|
|
2003
|
+
var stopped = false;
|
|
2004
|
+
var pill = null;
|
|
1937
2005
|
function readToken(){
|
|
1938
2006
|
var s = location.search;
|
|
1939
2007
|
if (s.indexOf('?') !== 0) return '';
|
|
@@ -1945,6 +2013,24 @@ export function renderReceiptHtml(model) {
|
|
|
1945
2013
|
}
|
|
1946
2014
|
return '';
|
|
1947
2015
|
}
|
|
2016
|
+
// 401 on any live request means the report server restarted and this page's
|
|
2017
|
+
// token is stale. Stop refreshing and tell the user how to get a fresh link
|
|
2018
|
+
// instead of looping on a dead token.
|
|
2019
|
+
function showRestartBanner(){
|
|
2020
|
+
if (stopped) return;
|
|
2021
|
+
stopped = true;
|
|
2022
|
+
if (pill) { pill.textContent = 'Offline'; pill.classList.add('off'); }
|
|
2023
|
+
try {
|
|
2024
|
+
var main = document.querySelector('main');
|
|
2025
|
+
if (!main || document.getElementById('kya-restart-banner') || !document.createElement) return;
|
|
2026
|
+
var div = document.createElement('div');
|
|
2027
|
+
div.id = 'kya-restart-banner';
|
|
2028
|
+
div.className = 'alert';
|
|
2029
|
+
div.setAttribute('role', 'status');
|
|
2030
|
+
div.textContent = 'Report server restarted. Re-run kya receipt --open (or kya start) for a fresh link.';
|
|
2031
|
+
main.insertBefore(div, main.firstChild);
|
|
2032
|
+
} catch (e) { /* banner is best-effort */ }
|
|
2033
|
+
}
|
|
1948
2034
|
function parse(){
|
|
1949
2035
|
state = Object.create(null);
|
|
1950
2036
|
page = 1;
|
|
@@ -2047,11 +2133,12 @@ export function renderReceiptHtml(model) {
|
|
|
2047
2133
|
group.classList.toggle('filtered-out', on && !vis);
|
|
2048
2134
|
});
|
|
2049
2135
|
}
|
|
2050
|
-
|
|
2136
|
+
document.querySelectorAll('[data-fgroup]').forEach(function(chip){
|
|
2051
2137
|
var pressed = !!(state[chip.dataset.fgroup] && state[chip.dataset.fgroup][chip.dataset.fvalue]);
|
|
2052
2138
|
chip.setAttribute('aria-pressed', pressed ? 'true' : 'false');
|
|
2053
2139
|
});
|
|
2054
|
-
|
|
2140
|
+
var cb = document.getElementById('clear-filters');
|
|
2141
|
+
if (cb) cb.hidden = !active();
|
|
2055
2142
|
}
|
|
2056
2143
|
function updateMeta(total){
|
|
2057
2144
|
if (!searchMeta) return;
|
|
@@ -2074,7 +2161,10 @@ export function renderReceiptHtml(model) {
|
|
|
2074
2161
|
headers: { 'Content-Type': 'application/json' },
|
|
2075
2162
|
body: JSON.stringify({ filters: serverFilters, page: page, q: q })
|
|
2076
2163
|
})
|
|
2077
|
-
.then(function(r){
|
|
2164
|
+
.then(function(r){
|
|
2165
|
+
if (r.status === 401) { showRestartBanner(); throw new Error('unauthorized'); }
|
|
2166
|
+
return r.json();
|
|
2167
|
+
})
|
|
2078
2168
|
.then(function(data){
|
|
2079
2169
|
if (feed && data.feedHtml) feed.innerHTML = data.feedHtml;
|
|
2080
2170
|
if (bar && data.paginationHtml) bar.innerHTML = data.paginationHtml;
|
|
@@ -2082,19 +2172,31 @@ export function renderReceiptHtml(model) {
|
|
|
2082
2172
|
})
|
|
2083
2173
|
.catch(function(err){ console.error('KYA feed update failed', err); });
|
|
2084
2174
|
}
|
|
2085
|
-
chips
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
page = 1;
|
|
2175
|
+
// Filter chips are delegated on document: live refresh swaps chip containers
|
|
2176
|
+
// innerHTML, so per-element bindings would die on the first update. The
|
|
2177
|
+
// pressed state is reapplied by apply() from the query-param state.
|
|
2178
|
+
document.addEventListener('click', function(e){
|
|
2179
|
+
var t = e.target;
|
|
2180
|
+
if (!t || !t.closest) return;
|
|
2181
|
+
if (t.closest('#clear-filters')) {
|
|
2182
|
+
state = Object.create(null); page = 1; q = '';
|
|
2183
|
+
if (searchInput) searchInput.value = '';
|
|
2095
2184
|
save(); apply();
|
|
2096
2185
|
if (LIVE) feedUpdate();
|
|
2097
|
-
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
var chip = t.closest('[data-fgroup]');
|
|
2189
|
+
if (!chip) return;
|
|
2190
|
+
var g = chip.dataset.fgroup, v = chip.dataset.fvalue;
|
|
2191
|
+
if (state[g] && state[g][v]) {
|
|
2192
|
+
delete state[g][v];
|
|
2193
|
+
if (!Object.keys(state[g]).length) delete state[g];
|
|
2194
|
+
} else {
|
|
2195
|
+
(state[g] || (state[g] = Object.create(null)))[v] = true;
|
|
2196
|
+
}
|
|
2197
|
+
page = 1;
|
|
2198
|
+
save(); apply();
|
|
2199
|
+
if (LIVE) feedUpdate();
|
|
2098
2200
|
});
|
|
2099
2201
|
if (LIVE && bar) {
|
|
2100
2202
|
bar.addEventListener('click', function(e){
|
|
@@ -2121,10 +2223,123 @@ export function renderReceiptHtml(model) {
|
|
|
2121
2223
|
if (LIVE) debounce = setTimeout(feedUpdate, 120);
|
|
2122
2224
|
});
|
|
2123
2225
|
}
|
|
2124
|
-
if (clearBtn) clearBtn.addEventListener('click', function(){ state = Object.create(null); page = 1; q = ''; if (searchInput) searchInput.value = ''; save(); apply(); if (LIVE) feedUpdate(); });
|
|
2125
2226
|
parse(); apply();
|
|
2126
2227
|
if (searchInput) searchInput.value = q;
|
|
2127
2228
|
if (LIVE && (active() || page !== 1)) feedUpdate();
|
|
2229
|
+
|
|
2230
|
+
// Live refresh: SSE says what changed, then only the affected containers
|
|
2231
|
+
// re-render in place. No full-page reloads, so scroll, focus, and filter
|
|
2232
|
+
// state survive and new rows stream into the feed on page 1.
|
|
2233
|
+
if (LIVE) {
|
|
2234
|
+
pill = document.querySelector('.live');
|
|
2235
|
+
var dirty = false;
|
|
2236
|
+
var lastFrag = 0;
|
|
2237
|
+
var fragTimer = 0;
|
|
2238
|
+
var sseDebounce = 0;
|
|
2239
|
+
function flash(el){
|
|
2240
|
+
el.classList.remove('frag-flash');
|
|
2241
|
+
void el.offsetWidth;
|
|
2242
|
+
el.classList.add('frag-flash');
|
|
2243
|
+
}
|
|
2244
|
+
function updateNavCounts(nav){
|
|
2245
|
+
Object.keys(nav).forEach(function(k){
|
|
2246
|
+
var link = document.getElementById('tab-' + k);
|
|
2247
|
+
if (!link) return;
|
|
2248
|
+
var span = link.querySelector('.nav-count');
|
|
2249
|
+
if (nav[k] === null || nav[k] === undefined) {
|
|
2250
|
+
if (span) span.parentNode.removeChild(span);
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
if (!span) {
|
|
2254
|
+
span = document.createElement('span');
|
|
2255
|
+
span.className = 'nav-count';
|
|
2256
|
+
link.appendChild(span);
|
|
2257
|
+
}
|
|
2258
|
+
span.textContent = String(nav[k]);
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
var FRAG_TARGETS = {
|
|
2262
|
+
statCounts: 'stat-counts',
|
|
2263
|
+
chipProducts: 'chip-products',
|
|
2264
|
+
chipProjects: 'chip-projects',
|
|
2265
|
+
chipServers: 'chip-servers',
|
|
2266
|
+
heroCertify: 'frag-hero-certify',
|
|
2267
|
+
heroTiles: 'frag-hero-tiles',
|
|
2268
|
+
overviewPanels: 'frag-overview-panels',
|
|
2269
|
+
certifyBody: 'frag-certify',
|
|
2270
|
+
orrPanel: 'frag-orr',
|
|
2271
|
+
changesList: 'changes-list'
|
|
2272
|
+
};
|
|
2273
|
+
function loadFragments(){
|
|
2274
|
+
lastFrag = Date.now();
|
|
2275
|
+
fetch('/fragments?t=' + encodeURIComponent(readToken()), {
|
|
2276
|
+
method: 'POST',
|
|
2277
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2278
|
+
body: '{}'
|
|
2279
|
+
})
|
|
2280
|
+
.then(function(r){
|
|
2281
|
+
if (r.status === 401) { showRestartBanner(); throw new Error('unauthorized'); }
|
|
2282
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
2283
|
+
return r.json();
|
|
2284
|
+
})
|
|
2285
|
+
.then(function(data){
|
|
2286
|
+
if (!data || !data.ok || !data.fragments) return;
|
|
2287
|
+
Object.keys(FRAG_TARGETS).forEach(function(k){
|
|
2288
|
+
var el = document.getElementById(FRAG_TARGETS[k]);
|
|
2289
|
+
if (!el || typeof data.fragments[k] !== 'string') return;
|
|
2290
|
+
if (el.innerHTML !== data.fragments[k]) {
|
|
2291
|
+
el.innerHTML = data.fragments[k];
|
|
2292
|
+
flash(el);
|
|
2293
|
+
}
|
|
2294
|
+
});
|
|
2295
|
+
// Fresh chip HTML arrives unpressed: reapply from the query-param state.
|
|
2296
|
+
apply();
|
|
2297
|
+
if (data.nav) updateNavCounts(data.nav);
|
|
2298
|
+
})
|
|
2299
|
+
.catch(function(err){ if (!stopped) console.error('KYA fragments update failed', err); });
|
|
2300
|
+
}
|
|
2301
|
+
function scheduleFragments(){
|
|
2302
|
+
if (fragTimer || stopped) return;
|
|
2303
|
+
var wait = Math.max(0, 2000 - (Date.now() - lastFrag));
|
|
2304
|
+
fragTimer = setTimeout(function(){
|
|
2305
|
+
fragTimer = 0;
|
|
2306
|
+
if (!dirty || document.hidden) return;
|
|
2307
|
+
dirty = false;
|
|
2308
|
+
loadFragments();
|
|
2309
|
+
}, wait);
|
|
2310
|
+
}
|
|
2311
|
+
function refresh(){
|
|
2312
|
+
if (stopped) return;
|
|
2313
|
+
if (document.hidden) { dirty = true; return; }
|
|
2314
|
+
dirty = false;
|
|
2315
|
+
feedUpdate();
|
|
2316
|
+
scheduleFragments();
|
|
2317
|
+
}
|
|
2318
|
+
// EventSource is absent in DOM shims and very old engines; the page still
|
|
2319
|
+
// works, it just does not stream updates.
|
|
2320
|
+
if (typeof EventSource === 'function') {
|
|
2321
|
+
var es = new EventSource('/events?t=' + encodeURIComponent(readToken()));
|
|
2322
|
+
es.onmessage = function(){
|
|
2323
|
+
if (stopped) return;
|
|
2324
|
+
dirty = true;
|
|
2325
|
+
clearTimeout(sseDebounce);
|
|
2326
|
+
sseDebounce = setTimeout(refresh, 300);
|
|
2327
|
+
};
|
|
2328
|
+
es.onerror = function(){
|
|
2329
|
+
// EventSource retries on its own; the pill reflects the outage and a
|
|
2330
|
+
// 401 on the next real request surfaces the restart banner.
|
|
2331
|
+
if (pill) { pill.textContent = 'Offline'; pill.classList.add('off'); }
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
if (document.addEventListener) {
|
|
2335
|
+
document.addEventListener('visibilitychange', function(){
|
|
2336
|
+
if (!document.hidden && dirty && !stopped) {
|
|
2337
|
+
clearTimeout(sseDebounce);
|
|
2338
|
+
sseDebounce = setTimeout(refresh, 100);
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2128
2343
|
})();
|
|
2129
2344
|
</script>`
|
|
2130
2345
|
: "";
|
|
@@ -2300,7 +2515,15 @@ export function renderReceiptHtml(model) {
|
|
|
2300
2515
|
body: JSON.stringify(body)
|
|
2301
2516
|
});
|
|
2302
2517
|
}
|
|
2303
|
-
function parseJson(res){
|
|
2518
|
+
function parseJson(res){
|
|
2519
|
+
if (!res.ok) {
|
|
2520
|
+
var msg = res.status === 401
|
|
2521
|
+
? 'Session expired or unauthorized (401). Reload the report from kya start, then try again.'
|
|
2522
|
+
: 'Request failed with HTTP ' + res.status;
|
|
2523
|
+
throw new Error(msg);
|
|
2524
|
+
}
|
|
2525
|
+
return res.json();
|
|
2526
|
+
}
|
|
2304
2527
|
|
|
2305
2528
|
var form = document.getElementById('gate-play-form');
|
|
2306
2529
|
if (form) {
|
|
@@ -2359,6 +2582,23 @@ export function renderReceiptHtml(model) {
|
|
|
2359
2582
|
});
|
|
2360
2583
|
});
|
|
2361
2584
|
|
|
2585
|
+
// Success feedback must survive the confirmation reload: the result div is
|
|
2586
|
+
// server-rendered empty on the fresh page, so without this the user sees a
|
|
2587
|
+
// blip for 800 ms and then an apparently unchanged page.
|
|
2588
|
+
function persistResult(action, msg){
|
|
2589
|
+
try { sessionStorage.setItem('kya:action-result', JSON.stringify({ a: action, m: msg })); } catch (e) {}
|
|
2590
|
+
}
|
|
2591
|
+
(function showPersistedResult(){
|
|
2592
|
+
var raw;
|
|
2593
|
+
try { raw = sessionStorage.getItem('kya:action-result'); sessionStorage.removeItem('kya:action-result'); } catch (e) { return; }
|
|
2594
|
+
if (!raw) return;
|
|
2595
|
+
var data;
|
|
2596
|
+
try { data = JSON.parse(raw); } catch (e) { return; }
|
|
2597
|
+
var el = document.getElementById('report-action-result-' + data.a)
|
|
2598
|
+
|| (data.a === 'import' ? document.getElementById('import-result') : null);
|
|
2599
|
+
if (el && typeof data.m === 'string') setResult(el, '<p>' + esc(data.m) + '</p>', false);
|
|
2600
|
+
})();
|
|
2601
|
+
|
|
2362
2602
|
document.querySelectorAll('[data-report-action]').forEach(function(btn){
|
|
2363
2603
|
btn.addEventListener('click', function(){
|
|
2364
2604
|
var action = btn.getAttribute('data-report-action');
|
|
@@ -2380,7 +2620,8 @@ export function renderReceiptHtml(model) {
|
|
|
2380
2620
|
if (action === 'otel-test') {
|
|
2381
2621
|
btn.disabled = false;
|
|
2382
2622
|
} else {
|
|
2383
|
-
|
|
2623
|
+
persistResult(action, msg);
|
|
2624
|
+
setTimeout(function(){ location.reload(); }, 1500);
|
|
2384
2625
|
}
|
|
2385
2626
|
} else {
|
|
2386
2627
|
setResult(resultEl, '<p>Error: ' + esc(data && (data.error || data.detail) ? (data.error || data.detail) : 'unknown') + '</p>', true);
|
|
@@ -2409,8 +2650,10 @@ export function renderReceiptHtml(model) {
|
|
|
2409
2650
|
.then(function(data){
|
|
2410
2651
|
if (data && data.ok) {
|
|
2411
2652
|
var errs = data.errors && data.errors.length ? ' · ' + data.errors.length + ' error(s)' : '';
|
|
2412
|
-
|
|
2413
|
-
|
|
2653
|
+
var importMsg = 'Imported ' + data.imported + ' event(s)' + errs;
|
|
2654
|
+
setResult(resultEl, '<p>' + esc(importMsg) + ' - reloading...</p>', false);
|
|
2655
|
+
persistResult('import', importMsg);
|
|
2656
|
+
setTimeout(function(){ location.reload(); }, 1500);
|
|
2414
2657
|
} else {
|
|
2415
2658
|
setResult(resultEl, '<p>Error: ' + esc(data && data.error ? data.error : 'unknown') + '</p>', true);
|
|
2416
2659
|
if (btn) btn.disabled = false;
|
|
@@ -2450,12 +2693,8 @@ export function renderReceiptHtml(model) {
|
|
|
2450
2693
|
})();
|
|
2451
2694
|
</script>`
|
|
2452
2695
|
: "";
|
|
2453
|
-
//
|
|
2454
|
-
|
|
2455
|
-
? parts.filter(Boolean).join("\n ")
|
|
2456
|
-
: `<p class="mute small zone-empty">${empty}</p>`;
|
|
2457
|
-
const overviewBody = zoneBody([dashboardPanel(dashboard), sessionsPanel(agg, nowMs, changes), reasonsPanel(agg)], "No activity in this window yet - analytics appear once events land.");
|
|
2458
|
-
const certifyBody = zoneBody([model.certify ? certifyDetailPanel(model.certify.requirements) : ""], "No live evaluation yet - wrap tool calls and run kya certify to build the baseline.");
|
|
2696
|
+
// (zoneBody lives at module level; buildLiveFragments above produces the
|
|
2697
|
+
// overview and certify bodies through the same helper.)
|
|
2459
2698
|
const systemBody = zoneBody([
|
|
2460
2699
|
capabilitiesPanel(model),
|
|
2461
2700
|
gatePanel(model.gate, nowMs),
|
|
@@ -2515,18 +2754,12 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
|
2515
2754
|
${blockedBanner}
|
|
2516
2755
|
<section class="zone" id="overview" aria-labelledby="tab-overview">
|
|
2517
2756
|
<div class="hero">
|
|
2518
|
-
${
|
|
2519
|
-
<div class="hero-tiles">
|
|
2520
|
-
${
|
|
2521
|
-
${activityHeroCard(agg, dashboard, events.length)}
|
|
2522
|
-
${gatewayHeroCard(model.gate, nowMs)}
|
|
2523
|
-
${importHeroCard(model.imports, nowMs, livePaged)}
|
|
2524
|
-
${discoveryHeroCard(model.discovery, nowMs, livePaged)}
|
|
2525
|
-
${investigateHeroCard(model.investigate, nowMs, livePaged)}
|
|
2526
|
-
${showbackHeroCard(model.showback, model.events)}
|
|
2757
|
+
${fr.fragments.heroCertify}
|
|
2758
|
+
<div class="hero-tiles" id="frag-hero-tiles">
|
|
2759
|
+
${fr.fragments.heroTiles}
|
|
2527
2760
|
</div>
|
|
2528
2761
|
</div>
|
|
2529
|
-
|
|
2762
|
+
<div id="frag-overview-panels">${fr.fragments.overviewPanels}</div>
|
|
2530
2763
|
</section>
|
|
2531
2764
|
<section class="zone" id="activity" aria-labelledby="tab-activity">
|
|
2532
2765
|
<div class="feed-search">
|
|
@@ -2535,17 +2768,10 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
|
2535
2768
|
</div>
|
|
2536
2769
|
${!model.liveToken && model.searchQuery ? '<p class="feed-search-note">Semantic ranking requires the live report server (<code>kya start</code>).</p>' : ""}
|
|
2537
2770
|
<div class="feed-head">
|
|
2538
|
-
<div class="stats" aria-label="Counts">
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
<button type="button" class="stat" data-fgroup="never" data-fvalue="1" aria-pressed="false" title="Filter: Never">Never<b>${c.never}</b></button>
|
|
2543
|
-
${chips.modes}${chips.planes}
|
|
2544
|
-
<button type="button" class="stat clear-filters" id="clear-filters" hidden>Clear filters ×</button>
|
|
2545
|
-
</div>
|
|
2546
|
-
${chips.products}
|
|
2547
|
-
${chips.projects}
|
|
2548
|
-
${chips.servers}
|
|
2771
|
+
<div class="stats" aria-label="Counts" id="stat-counts">${fr.fragments.statCounts}</div>
|
|
2772
|
+
<div id="chip-products">${chips.products}</div>
|
|
2773
|
+
<div id="chip-projects">${chips.projects}</div>
|
|
2774
|
+
<div id="chip-servers">${chips.servers}</div>
|
|
2549
2775
|
</div>
|
|
2550
2776
|
<section class="feed" id="feed" aria-label="Activity feed">
|
|
2551
2777
|
${initialFeed.feedHtml}
|
|
@@ -2556,14 +2782,14 @@ ${initialFeed.paginationHtml}
|
|
|
2556
2782
|
</section>
|
|
2557
2783
|
<section class="zone" id="changes" aria-labelledby="tab-changes">
|
|
2558
2784
|
<section class="feed chg" id="changes-list" aria-label="Changes by session and file">
|
|
2559
|
-
${
|
|
2785
|
+
${fr.fragments.changesList}
|
|
2560
2786
|
</section>
|
|
2561
2787
|
</section>
|
|
2562
2788
|
<section class="zone" id="certify" aria-labelledby="tab-certify">
|
|
2563
|
-
|
|
2789
|
+
<div id="frag-certify">${fr.fragments.certifyBody}</div>
|
|
2564
2790
|
</section>
|
|
2565
2791
|
<section class="zone" id="orr" aria-labelledby="tab-orr">
|
|
2566
|
-
|
|
2792
|
+
<div id="frag-orr">${fr.fragments.orrPanel}</div>
|
|
2567
2793
|
</section>
|
|
2568
2794
|
${gatePagePanel(model.gate, nowMs)}
|
|
2569
2795
|
<section class="zone" id="system" aria-labelledby="tab-system">
|
|
@@ -2574,7 +2800,6 @@ ${filterScript}
|
|
|
2574
2800
|
${staticSearchScript}
|
|
2575
2801
|
${themeScript}
|
|
2576
2802
|
${gateScript}
|
|
2577
|
-
${liveScript}
|
|
2578
2803
|
</body>
|
|
2579
2804
|
</html>`;
|
|
2580
2805
|
// Defense-in-depth: block known secret shapes before serving HTML.
|