@shield-agent/kya 0.31.0 → 0.32.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/README.md +21 -8
- 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/receipt/live-server.d.ts.map +1 -1
- package/dist/receipt/live-server.js +36 -11
- 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 +12 -3
- 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 +273 -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
|
@@ -1320,7 +1320,7 @@ function kpiTile(label, value, cls = "") {
|
|
|
1320
1320
|
*/
|
|
1321
1321
|
function heroCertifyPanel(certify) {
|
|
1322
1322
|
if (!certify) {
|
|
1323
|
-
return `<section class="panel hero-certify none" aria-label="Certify">
|
|
1323
|
+
return `<section class="panel hero-certify none" id="frag-hero-certify" aria-label="Certify">
|
|
1324
1324
|
<h2>Certify - Agent Trust Baseline</h2>
|
|
1325
1325
|
<div class="orrline">
|
|
1326
1326
|
<span class="pill">not evaluated</span>
|
|
@@ -1373,7 +1373,7 @@ ${certify.topGaps
|
|
|
1373
1373
|
<span class="mute small">or run <code>kya certify --open</code> for the signed bundle</span>
|
|
1374
1374
|
</div>`;
|
|
1375
1375
|
// Pill tones reuse the ORR palette: pass is green, gap is amber.
|
|
1376
|
-
return `<section class="panel hero-certify ${state}" aria-label="Certify">
|
|
1376
|
+
return `<section class="panel hero-certify ${state}" id="frag-hero-certify" aria-label="Certify">
|
|
1377
1377
|
<h2>Certify - Agent Trust Baseline</h2>
|
|
1378
1378
|
<div class="orrline">
|
|
1379
1379
|
<span class="pill orr-${state === "pass" ? "green" : "amber"}">${esc(certify.result)}</span>
|
|
@@ -1852,6 +1852,63 @@ function paginationBar(total, page, pages) {
|
|
|
1852
1852
|
}
|
|
1853
1853
|
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
1854
|
}
|
|
1855
|
+
/** Zones never render as bare whitespace: with no panels they get a hint. */
|
|
1856
|
+
const zoneBody = (parts, empty) => parts.some((p) => p.length > 0)
|
|
1857
|
+
? parts.filter(Boolean).join("\n ")
|
|
1858
|
+
: `<p class="mute small zone-empty">${empty}</p>`;
|
|
1859
|
+
/** Inner markup of the Activity counts bar (verdict buttons + mode/plane chips). */
|
|
1860
|
+
function statCountsInner(c, chips) {
|
|
1861
|
+
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>
|
|
1862
|
+
<button type="button" class="stat bad" data-fgroup="verdict" data-fvalue="DENY" aria-pressed="false" title="Filter: Deny">Deny<b>${c.deny}</b></button>
|
|
1863
|
+
<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>
|
|
1864
|
+
<button type="button" class="stat" data-fgroup="never" data-fvalue="1" aria-pressed="false" title="Filter: Never">Never<b>${c.never}</b></button>
|
|
1865
|
+
${chips.modes}${chips.planes}
|
|
1866
|
+
<button type="button" class="stat clear-filters" id="clear-filters" hidden>Clear filters ×</button>`;
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Live-refresh fragments: the containers POST /fragments re-renders so the
|
|
1870
|
+
* report updates in place instead of reloading the whole page.
|
|
1871
|
+
* renderReceiptHtml builds the initial page through the same helpers, so
|
|
1872
|
+
* first paint and refreshes cannot drift apart.
|
|
1873
|
+
*/
|
|
1874
|
+
export function buildLiveFragments(model, livePaged) {
|
|
1875
|
+
const nowMs = Date.now();
|
|
1876
|
+
const events = normalizeTrailEvents(model.events);
|
|
1877
|
+
const c = countVerdicts(events);
|
|
1878
|
+
const agg = aggregateEvents(events);
|
|
1879
|
+
const chips = statChips(agg);
|
|
1880
|
+
const dashboard = computeDashboard(events);
|
|
1881
|
+
const changes = buildChangesModel(events);
|
|
1882
|
+
return {
|
|
1883
|
+
fragments: {
|
|
1884
|
+
statCounts: statCountsInner(c, chips),
|
|
1885
|
+
chipProducts: chips.products,
|
|
1886
|
+
chipProjects: chips.projects,
|
|
1887
|
+
chipServers: chips.servers,
|
|
1888
|
+
heroCertify: heroCertifyPanel(model.certify),
|
|
1889
|
+
heroTiles: [
|
|
1890
|
+
verdictsHeroCard(c, events.length),
|
|
1891
|
+
activityHeroCard(agg, dashboard, events.length),
|
|
1892
|
+
gatewayHeroCard(model.gate, nowMs),
|
|
1893
|
+
importHeroCard(model.imports, nowMs, livePaged),
|
|
1894
|
+
discoveryHeroCard(model.discovery, nowMs, livePaged),
|
|
1895
|
+
investigateHeroCard(model.investigate, nowMs, livePaged),
|
|
1896
|
+
showbackHeroCard(model.showback, events),
|
|
1897
|
+
].join("\n "),
|
|
1898
|
+
overviewPanels: zoneBody([dashboardPanel(dashboard), sessionsPanel(agg, nowMs, changes), reasonsPanel(agg)], "No activity in this window yet - analytics appear once events land."),
|
|
1899
|
+
certifyBody: zoneBody([model.certify ? certifyDetailPanel(model.certify.requirements) : ""], "No live evaluation yet - wrap tool calls and run kya certify to build the baseline."),
|
|
1900
|
+
orrPanel: orrPanel(model.orr, nowMs, livePaged),
|
|
1901
|
+
changesList: changesPanel(changes, nowMs),
|
|
1902
|
+
},
|
|
1903
|
+
nav: {
|
|
1904
|
+
overview: events.length,
|
|
1905
|
+
activity: events.length,
|
|
1906
|
+
changes: changes.fileCount,
|
|
1907
|
+
certify: model.certify?.gap ?? null,
|
|
1908
|
+
orr: model.orr ? model.orr.scorecards.fail : null,
|
|
1909
|
+
},
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1855
1912
|
export function renderReceiptHtml(model) {
|
|
1856
1913
|
const nowMs = Date.now();
|
|
1857
1914
|
const events = normalizeTrailEvents(model.events);
|
|
@@ -1871,11 +1928,12 @@ export function renderReceiptHtml(model) {
|
|
|
1871
1928
|
page: 1,
|
|
1872
1929
|
pages: 1,
|
|
1873
1930
|
};
|
|
1874
|
-
const c = countVerdicts(events);
|
|
1875
1931
|
const agg = aggregateEvents(events);
|
|
1876
1932
|
const chips = statChips(agg);
|
|
1877
|
-
const dashboard = computeDashboard(events);
|
|
1878
1933
|
const changes = buildChangesModel(events);
|
|
1934
|
+
// Live-refresh fragments render the initial page AND the POST /fragments
|
|
1935
|
+
// refresh, so the two can never drift apart.
|
|
1936
|
+
const fr = buildLiveFragments(model, livePaged);
|
|
1879
1937
|
const blocked = events.filter((e) => e.neverEvent || e.reasonCode === "NEVER_EVENT");
|
|
1880
1938
|
const tools = model.mcpSeen && model.mcpSeen.length > 0
|
|
1881
1939
|
? model.mcpSeen.map((m) => m.toolId)
|
|
@@ -1894,19 +1952,8 @@ export function renderReceiptHtml(model) {
|
|
|
1894
1952
|
</footer>`;
|
|
1895
1953
|
// liveToken is minted base64url by the live server - safe in a JS string
|
|
1896
1954
|
// 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
|
-
: "";
|
|
1955
|
+
// Live updates ride the filter script's EventSource + /fragments refresh;
|
|
1956
|
+
// there is deliberately no page-level reload-on-change script.
|
|
1910
1957
|
// Client-side chip filters, pagination, and search: only emitted for live
|
|
1911
1958
|
// reports (liveToken present). Static receipts render a separate BM25-only
|
|
1912
1959
|
// search script below. State maps are null-prototype: attacker-controlled
|
|
@@ -1932,8 +1979,8 @@ export function renderReceiptHtml(model) {
|
|
|
1932
1979
|
var page = 1;
|
|
1933
1980
|
var q = '';
|
|
1934
1981
|
var LIVE = !!(document.querySelectorAll && document.querySelectorAll('main[data-live-token]').length);
|
|
1935
|
-
var
|
|
1936
|
-
var
|
|
1982
|
+
var stopped = false;
|
|
1983
|
+
var pill = null;
|
|
1937
1984
|
function readToken(){
|
|
1938
1985
|
var s = location.search;
|
|
1939
1986
|
if (s.indexOf('?') !== 0) return '';
|
|
@@ -1945,6 +1992,24 @@ export function renderReceiptHtml(model) {
|
|
|
1945
1992
|
}
|
|
1946
1993
|
return '';
|
|
1947
1994
|
}
|
|
1995
|
+
// 401 on any live request means the report server restarted and this page's
|
|
1996
|
+
// token is stale. Stop refreshing and tell the user how to get a fresh link
|
|
1997
|
+
// instead of looping on a dead token.
|
|
1998
|
+
function showRestartBanner(){
|
|
1999
|
+
if (stopped) return;
|
|
2000
|
+
stopped = true;
|
|
2001
|
+
if (pill) { pill.textContent = 'Offline'; pill.classList.add('off'); }
|
|
2002
|
+
try {
|
|
2003
|
+
var main = document.querySelector('main');
|
|
2004
|
+
if (!main || document.getElementById('kya-restart-banner') || !document.createElement) return;
|
|
2005
|
+
var div = document.createElement('div');
|
|
2006
|
+
div.id = 'kya-restart-banner';
|
|
2007
|
+
div.className = 'alert';
|
|
2008
|
+
div.setAttribute('role', 'status');
|
|
2009
|
+
div.textContent = 'Report server restarted. Re-run kya receipt --open (or kya start) for a fresh link.';
|
|
2010
|
+
main.insertBefore(div, main.firstChild);
|
|
2011
|
+
} catch (e) { /* banner is best-effort */ }
|
|
2012
|
+
}
|
|
1948
2013
|
function parse(){
|
|
1949
2014
|
state = Object.create(null);
|
|
1950
2015
|
page = 1;
|
|
@@ -2047,11 +2112,12 @@ export function renderReceiptHtml(model) {
|
|
|
2047
2112
|
group.classList.toggle('filtered-out', on && !vis);
|
|
2048
2113
|
});
|
|
2049
2114
|
}
|
|
2050
|
-
|
|
2115
|
+
document.querySelectorAll('[data-fgroup]').forEach(function(chip){
|
|
2051
2116
|
var pressed = !!(state[chip.dataset.fgroup] && state[chip.dataset.fgroup][chip.dataset.fvalue]);
|
|
2052
2117
|
chip.setAttribute('aria-pressed', pressed ? 'true' : 'false');
|
|
2053
2118
|
});
|
|
2054
|
-
|
|
2119
|
+
var cb = document.getElementById('clear-filters');
|
|
2120
|
+
if (cb) cb.hidden = !active();
|
|
2055
2121
|
}
|
|
2056
2122
|
function updateMeta(total){
|
|
2057
2123
|
if (!searchMeta) return;
|
|
@@ -2074,7 +2140,10 @@ export function renderReceiptHtml(model) {
|
|
|
2074
2140
|
headers: { 'Content-Type': 'application/json' },
|
|
2075
2141
|
body: JSON.stringify({ filters: serverFilters, page: page, q: q })
|
|
2076
2142
|
})
|
|
2077
|
-
.then(function(r){
|
|
2143
|
+
.then(function(r){
|
|
2144
|
+
if (r.status === 401) { showRestartBanner(); throw new Error('unauthorized'); }
|
|
2145
|
+
return r.json();
|
|
2146
|
+
})
|
|
2078
2147
|
.then(function(data){
|
|
2079
2148
|
if (feed && data.feedHtml) feed.innerHTML = data.feedHtml;
|
|
2080
2149
|
if (bar && data.paginationHtml) bar.innerHTML = data.paginationHtml;
|
|
@@ -2082,19 +2151,31 @@ export function renderReceiptHtml(model) {
|
|
|
2082
2151
|
})
|
|
2083
2152
|
.catch(function(err){ console.error('KYA feed update failed', err); });
|
|
2084
2153
|
}
|
|
2085
|
-
chips
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
page = 1;
|
|
2154
|
+
// Filter chips are delegated on document: live refresh swaps chip containers
|
|
2155
|
+
// innerHTML, so per-element bindings would die on the first update. The
|
|
2156
|
+
// pressed state is reapplied by apply() from the query-param state.
|
|
2157
|
+
document.addEventListener('click', function(e){
|
|
2158
|
+
var t = e.target;
|
|
2159
|
+
if (!t || !t.closest) return;
|
|
2160
|
+
if (t.closest('#clear-filters')) {
|
|
2161
|
+
state = Object.create(null); page = 1; q = '';
|
|
2162
|
+
if (searchInput) searchInput.value = '';
|
|
2095
2163
|
save(); apply();
|
|
2096
2164
|
if (LIVE) feedUpdate();
|
|
2097
|
-
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
var chip = t.closest('[data-fgroup]');
|
|
2168
|
+
if (!chip) return;
|
|
2169
|
+
var g = chip.dataset.fgroup, v = chip.dataset.fvalue;
|
|
2170
|
+
if (state[g] && state[g][v]) {
|
|
2171
|
+
delete state[g][v];
|
|
2172
|
+
if (!Object.keys(state[g]).length) delete state[g];
|
|
2173
|
+
} else {
|
|
2174
|
+
(state[g] || (state[g] = Object.create(null)))[v] = true;
|
|
2175
|
+
}
|
|
2176
|
+
page = 1;
|
|
2177
|
+
save(); apply();
|
|
2178
|
+
if (LIVE) feedUpdate();
|
|
2098
2179
|
});
|
|
2099
2180
|
if (LIVE && bar) {
|
|
2100
2181
|
bar.addEventListener('click', function(e){
|
|
@@ -2121,10 +2202,123 @@ export function renderReceiptHtml(model) {
|
|
|
2121
2202
|
if (LIVE) debounce = setTimeout(feedUpdate, 120);
|
|
2122
2203
|
});
|
|
2123
2204
|
}
|
|
2124
|
-
if (clearBtn) clearBtn.addEventListener('click', function(){ state = Object.create(null); page = 1; q = ''; if (searchInput) searchInput.value = ''; save(); apply(); if (LIVE) feedUpdate(); });
|
|
2125
2205
|
parse(); apply();
|
|
2126
2206
|
if (searchInput) searchInput.value = q;
|
|
2127
2207
|
if (LIVE && (active() || page !== 1)) feedUpdate();
|
|
2208
|
+
|
|
2209
|
+
// Live refresh: SSE says what changed, then only the affected containers
|
|
2210
|
+
// re-render in place. No full-page reloads, so scroll, focus, and filter
|
|
2211
|
+
// state survive and new rows stream into the feed on page 1.
|
|
2212
|
+
if (LIVE) {
|
|
2213
|
+
pill = document.querySelector('.live');
|
|
2214
|
+
var dirty = false;
|
|
2215
|
+
var lastFrag = 0;
|
|
2216
|
+
var fragTimer = 0;
|
|
2217
|
+
var sseDebounce = 0;
|
|
2218
|
+
function flash(el){
|
|
2219
|
+
el.classList.remove('frag-flash');
|
|
2220
|
+
void el.offsetWidth;
|
|
2221
|
+
el.classList.add('frag-flash');
|
|
2222
|
+
}
|
|
2223
|
+
function updateNavCounts(nav){
|
|
2224
|
+
Object.keys(nav).forEach(function(k){
|
|
2225
|
+
var link = document.getElementById('tab-' + k);
|
|
2226
|
+
if (!link) return;
|
|
2227
|
+
var span = link.querySelector('.nav-count');
|
|
2228
|
+
if (nav[k] === null || nav[k] === undefined) {
|
|
2229
|
+
if (span) span.parentNode.removeChild(span);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
if (!span) {
|
|
2233
|
+
span = document.createElement('span');
|
|
2234
|
+
span.className = 'nav-count';
|
|
2235
|
+
link.appendChild(span);
|
|
2236
|
+
}
|
|
2237
|
+
span.textContent = String(nav[k]);
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
var FRAG_TARGETS = {
|
|
2241
|
+
statCounts: 'stat-counts',
|
|
2242
|
+
chipProducts: 'chip-products',
|
|
2243
|
+
chipProjects: 'chip-projects',
|
|
2244
|
+
chipServers: 'chip-servers',
|
|
2245
|
+
heroCertify: 'frag-hero-certify',
|
|
2246
|
+
heroTiles: 'frag-hero-tiles',
|
|
2247
|
+
overviewPanels: 'frag-overview-panels',
|
|
2248
|
+
certifyBody: 'frag-certify',
|
|
2249
|
+
orrPanel: 'frag-orr',
|
|
2250
|
+
changesList: 'changes-list'
|
|
2251
|
+
};
|
|
2252
|
+
function loadFragments(){
|
|
2253
|
+
lastFrag = Date.now();
|
|
2254
|
+
fetch('/fragments?t=' + encodeURIComponent(readToken()), {
|
|
2255
|
+
method: 'POST',
|
|
2256
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2257
|
+
body: '{}'
|
|
2258
|
+
})
|
|
2259
|
+
.then(function(r){
|
|
2260
|
+
if (r.status === 401) { showRestartBanner(); throw new Error('unauthorized'); }
|
|
2261
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
2262
|
+
return r.json();
|
|
2263
|
+
})
|
|
2264
|
+
.then(function(data){
|
|
2265
|
+
if (!data || !data.ok || !data.fragments) return;
|
|
2266
|
+
Object.keys(FRAG_TARGETS).forEach(function(k){
|
|
2267
|
+
var el = document.getElementById(FRAG_TARGETS[k]);
|
|
2268
|
+
if (!el || typeof data.fragments[k] !== 'string') return;
|
|
2269
|
+
if (el.innerHTML !== data.fragments[k]) {
|
|
2270
|
+
el.innerHTML = data.fragments[k];
|
|
2271
|
+
flash(el);
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
// Fresh chip HTML arrives unpressed: reapply from the query-param state.
|
|
2275
|
+
apply();
|
|
2276
|
+
if (data.nav) updateNavCounts(data.nav);
|
|
2277
|
+
})
|
|
2278
|
+
.catch(function(err){ if (!stopped) console.error('KYA fragments update failed', err); });
|
|
2279
|
+
}
|
|
2280
|
+
function scheduleFragments(){
|
|
2281
|
+
if (fragTimer || stopped) return;
|
|
2282
|
+
var wait = Math.max(0, 2000 - (Date.now() - lastFrag));
|
|
2283
|
+
fragTimer = setTimeout(function(){
|
|
2284
|
+
fragTimer = 0;
|
|
2285
|
+
if (!dirty || document.hidden) return;
|
|
2286
|
+
dirty = false;
|
|
2287
|
+
loadFragments();
|
|
2288
|
+
}, wait);
|
|
2289
|
+
}
|
|
2290
|
+
function refresh(){
|
|
2291
|
+
if (stopped) return;
|
|
2292
|
+
if (document.hidden) { dirty = true; return; }
|
|
2293
|
+
dirty = false;
|
|
2294
|
+
feedUpdate();
|
|
2295
|
+
scheduleFragments();
|
|
2296
|
+
}
|
|
2297
|
+
// EventSource is absent in DOM shims and very old engines; the page still
|
|
2298
|
+
// works, it just does not stream updates.
|
|
2299
|
+
if (typeof EventSource === 'function') {
|
|
2300
|
+
var es = new EventSource('/events?t=' + encodeURIComponent(readToken()));
|
|
2301
|
+
es.onmessage = function(){
|
|
2302
|
+
if (stopped) return;
|
|
2303
|
+
dirty = true;
|
|
2304
|
+
clearTimeout(sseDebounce);
|
|
2305
|
+
sseDebounce = setTimeout(refresh, 300);
|
|
2306
|
+
};
|
|
2307
|
+
es.onerror = function(){
|
|
2308
|
+
// EventSource retries on its own; the pill reflects the outage and a
|
|
2309
|
+
// 401 on the next real request surfaces the restart banner.
|
|
2310
|
+
if (pill) { pill.textContent = 'Offline'; pill.classList.add('off'); }
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
if (document.addEventListener) {
|
|
2314
|
+
document.addEventListener('visibilitychange', function(){
|
|
2315
|
+
if (!document.hidden && dirty && !stopped) {
|
|
2316
|
+
clearTimeout(sseDebounce);
|
|
2317
|
+
sseDebounce = setTimeout(refresh, 100);
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2128
2322
|
})();
|
|
2129
2323
|
</script>`
|
|
2130
2324
|
: "";
|
|
@@ -2300,7 +2494,15 @@ export function renderReceiptHtml(model) {
|
|
|
2300
2494
|
body: JSON.stringify(body)
|
|
2301
2495
|
});
|
|
2302
2496
|
}
|
|
2303
|
-
function parseJson(res){
|
|
2497
|
+
function parseJson(res){
|
|
2498
|
+
if (!res.ok) {
|
|
2499
|
+
var msg = res.status === 401
|
|
2500
|
+
? 'Session expired or unauthorized (401). Reload the report from kya start, then try again.'
|
|
2501
|
+
: 'Request failed with HTTP ' + res.status;
|
|
2502
|
+
throw new Error(msg);
|
|
2503
|
+
}
|
|
2504
|
+
return res.json();
|
|
2505
|
+
}
|
|
2304
2506
|
|
|
2305
2507
|
var form = document.getElementById('gate-play-form');
|
|
2306
2508
|
if (form) {
|
|
@@ -2359,6 +2561,23 @@ export function renderReceiptHtml(model) {
|
|
|
2359
2561
|
});
|
|
2360
2562
|
});
|
|
2361
2563
|
|
|
2564
|
+
// Success feedback must survive the confirmation reload: the result div is
|
|
2565
|
+
// server-rendered empty on the fresh page, so without this the user sees a
|
|
2566
|
+
// blip for 800 ms and then an apparently unchanged page.
|
|
2567
|
+
function persistResult(action, msg){
|
|
2568
|
+
try { sessionStorage.setItem('kya:action-result', JSON.stringify({ a: action, m: msg })); } catch (e) {}
|
|
2569
|
+
}
|
|
2570
|
+
(function showPersistedResult(){
|
|
2571
|
+
var raw;
|
|
2572
|
+
try { raw = sessionStorage.getItem('kya:action-result'); sessionStorage.removeItem('kya:action-result'); } catch (e) { return; }
|
|
2573
|
+
if (!raw) return;
|
|
2574
|
+
var data;
|
|
2575
|
+
try { data = JSON.parse(raw); } catch (e) { return; }
|
|
2576
|
+
var el = document.getElementById('report-action-result-' + data.a)
|
|
2577
|
+
|| (data.a === 'import' ? document.getElementById('import-result') : null);
|
|
2578
|
+
if (el && typeof data.m === 'string') setResult(el, '<p>' + esc(data.m) + '</p>', false);
|
|
2579
|
+
})();
|
|
2580
|
+
|
|
2362
2581
|
document.querySelectorAll('[data-report-action]').forEach(function(btn){
|
|
2363
2582
|
btn.addEventListener('click', function(){
|
|
2364
2583
|
var action = btn.getAttribute('data-report-action');
|
|
@@ -2380,7 +2599,8 @@ export function renderReceiptHtml(model) {
|
|
|
2380
2599
|
if (action === 'otel-test') {
|
|
2381
2600
|
btn.disabled = false;
|
|
2382
2601
|
} else {
|
|
2383
|
-
|
|
2602
|
+
persistResult(action, msg);
|
|
2603
|
+
setTimeout(function(){ location.reload(); }, 1500);
|
|
2384
2604
|
}
|
|
2385
2605
|
} else {
|
|
2386
2606
|
setResult(resultEl, '<p>Error: ' + esc(data && (data.error || data.detail) ? (data.error || data.detail) : 'unknown') + '</p>', true);
|
|
@@ -2409,8 +2629,10 @@ export function renderReceiptHtml(model) {
|
|
|
2409
2629
|
.then(function(data){
|
|
2410
2630
|
if (data && data.ok) {
|
|
2411
2631
|
var errs = data.errors && data.errors.length ? ' · ' + data.errors.length + ' error(s)' : '';
|
|
2412
|
-
|
|
2413
|
-
|
|
2632
|
+
var importMsg = 'Imported ' + data.imported + ' event(s)' + errs;
|
|
2633
|
+
setResult(resultEl, '<p>' + esc(importMsg) + ' - reloading...</p>', false);
|
|
2634
|
+
persistResult('import', importMsg);
|
|
2635
|
+
setTimeout(function(){ location.reload(); }, 1500);
|
|
2414
2636
|
} else {
|
|
2415
2637
|
setResult(resultEl, '<p>Error: ' + esc(data && data.error ? data.error : 'unknown') + '</p>', true);
|
|
2416
2638
|
if (btn) btn.disabled = false;
|
|
@@ -2450,12 +2672,8 @@ export function renderReceiptHtml(model) {
|
|
|
2450
2672
|
})();
|
|
2451
2673
|
</script>`
|
|
2452
2674
|
: "";
|
|
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.");
|
|
2675
|
+
// (zoneBody lives at module level; buildLiveFragments above produces the
|
|
2676
|
+
// overview and certify bodies through the same helper.)
|
|
2459
2677
|
const systemBody = zoneBody([
|
|
2460
2678
|
capabilitiesPanel(model),
|
|
2461
2679
|
gatePanel(model.gate, nowMs),
|
|
@@ -2515,18 +2733,12 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
|
2515
2733
|
${blockedBanner}
|
|
2516
2734
|
<section class="zone" id="overview" aria-labelledby="tab-overview">
|
|
2517
2735
|
<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)}
|
|
2736
|
+
${fr.fragments.heroCertify}
|
|
2737
|
+
<div class="hero-tiles" id="frag-hero-tiles">
|
|
2738
|
+
${fr.fragments.heroTiles}
|
|
2527
2739
|
</div>
|
|
2528
2740
|
</div>
|
|
2529
|
-
|
|
2741
|
+
<div id="frag-overview-panels">${fr.fragments.overviewPanels}</div>
|
|
2530
2742
|
</section>
|
|
2531
2743
|
<section class="zone" id="activity" aria-labelledby="tab-activity">
|
|
2532
2744
|
<div class="feed-search">
|
|
@@ -2535,17 +2747,10 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
|
2535
2747
|
</div>
|
|
2536
2748
|
${!model.liveToken && model.searchQuery ? '<p class="feed-search-note">Semantic ranking requires the live report server (<code>kya start</code>).</p>' : ""}
|
|
2537
2749
|
<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}
|
|
2750
|
+
<div class="stats" aria-label="Counts" id="stat-counts">${fr.fragments.statCounts}</div>
|
|
2751
|
+
<div id="chip-products">${chips.products}</div>
|
|
2752
|
+
<div id="chip-projects">${chips.projects}</div>
|
|
2753
|
+
<div id="chip-servers">${chips.servers}</div>
|
|
2549
2754
|
</div>
|
|
2550
2755
|
<section class="feed" id="feed" aria-label="Activity feed">
|
|
2551
2756
|
${initialFeed.feedHtml}
|
|
@@ -2556,14 +2761,14 @@ ${initialFeed.paginationHtml}
|
|
|
2556
2761
|
</section>
|
|
2557
2762
|
<section class="zone" id="changes" aria-labelledby="tab-changes">
|
|
2558
2763
|
<section class="feed chg" id="changes-list" aria-label="Changes by session and file">
|
|
2559
|
-
${
|
|
2764
|
+
${fr.fragments.changesList}
|
|
2560
2765
|
</section>
|
|
2561
2766
|
</section>
|
|
2562
2767
|
<section class="zone" id="certify" aria-labelledby="tab-certify">
|
|
2563
|
-
|
|
2768
|
+
<div id="frag-certify">${fr.fragments.certifyBody}</div>
|
|
2564
2769
|
</section>
|
|
2565
2770
|
<section class="zone" id="orr" aria-labelledby="tab-orr">
|
|
2566
|
-
|
|
2771
|
+
<div id="frag-orr">${fr.fragments.orrPanel}</div>
|
|
2567
2772
|
</section>
|
|
2568
2773
|
${gatePagePanel(model.gate, nowMs)}
|
|
2569
2774
|
<section class="zone" id="system" aria-labelledby="tab-system">
|
|
@@ -2574,7 +2779,6 @@ ${filterScript}
|
|
|
2574
2779
|
${staticSearchScript}
|
|
2575
2780
|
${themeScript}
|
|
2576
2781
|
${gateScript}
|
|
2577
|
-
${liveScript}
|
|
2578
2782
|
</body>
|
|
2579
2783
|
</html>`;
|
|
2580
2784
|
// Defense-in-depth: block known secret shapes before serving HTML.
|