@shield-agent/kya 0.19.0 → 0.21.0
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 +21 -5
- 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/cli.d.ts.map +1 -1
- package/dist/cli.js +23 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/receipt.d.ts +13 -0
- package/dist/commands/receipt.d.ts.map +1 -1
- package/dist/commands/receipt.js +20 -0
- package/dist/commands/receipt.js.map +1 -1
- package/dist/receipt/embeddings.d.ts +34 -0
- package/dist/receipt/embeddings.d.ts.map +1 -0
- package/dist/receipt/embeddings.js +140 -0
- package/dist/receipt/embeddings.js.map +1 -0
- package/dist/receipt/live-server.d.ts +3 -0
- package/dist/receipt/live-server.d.ts.map +1 -1
- package/dist/receipt/live-server.js +24 -3
- 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 +29 -0
- package/dist/receipt/receipt-css.js.map +1 -1
- package/dist/receipt/render-receipt.d.ts +4 -1
- package/dist/receipt/render-receipt.d.ts.map +1 -1
- package/dist/receipt/render-receipt.js +199 -67
- package/dist/receipt/render-receipt.js.map +1 -1
- package/dist/receipt/search.d.ts +79 -0
- package/dist/receipt/search.d.ts.map +1 -0
- package/dist/receipt/search.js +475 -0
- package/dist/receipt/search.js.map +1 -0
- package/dist/receipt/share.d.ts +65 -0
- package/dist/receipt/share.d.ts.map +1 -0
- package/dist/receipt/share.js +109 -0
- package/dist/receipt/share.js.map +1 -0
- package/dist/star-cta.d.ts +14 -0
- package/dist/star-cta.d.ts.map +1 -0
- package/dist/star-cta.js +51 -0
- package/dist/star-cta.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 +14 -3
- package/qwen/settings.example.json +1 -1
- package/server.json +2 -2
|
@@ -12,6 +12,7 @@ import { computeDashboard } from "./dashboard.js";
|
|
|
12
12
|
import { buildChangesModel } from "./changes.js";
|
|
13
13
|
import { GATE_NOT_SETUP_QUICKSTART, loadGatePage, toolNameFromToolId, toolServerPrefix, } from "./gate-page.js";
|
|
14
14
|
import { receiptCss } from "./receipt-css.js";
|
|
15
|
+
import { applyChipFilters, rankEvents } from "./search.js";
|
|
15
16
|
export const PAGE_SIZE = 50;
|
|
16
17
|
export function clampPage(page, total) {
|
|
17
18
|
if (total === 0)
|
|
@@ -215,6 +216,7 @@ export function buildReceiptModel(sessionId, events, extras) {
|
|
|
215
216
|
mcpSeen: extras?.mcpSeen,
|
|
216
217
|
live: extras?.live,
|
|
217
218
|
liveToken: extras?.liveToken,
|
|
219
|
+
searchQuery: extras?.searchQuery,
|
|
218
220
|
identity: extras?.identity,
|
|
219
221
|
sandboxes: extras?.sandboxes,
|
|
220
222
|
wiredHosts: extras?.wiredHosts,
|
|
@@ -236,6 +238,7 @@ export function buildWindowReceiptModel(events, days, extras) {
|
|
|
236
238
|
mcpSeen: extras?.mcpSeen,
|
|
237
239
|
live: extras?.live,
|
|
238
240
|
liveToken: extras?.liveToken,
|
|
241
|
+
searchQuery: extras?.searchQuery,
|
|
239
242
|
identity: extras?.identity,
|
|
240
243
|
sandboxes: extras?.sandboxes,
|
|
241
244
|
wiredHosts: extras?.wiredHosts,
|
|
@@ -256,6 +259,7 @@ export function loadReceiptModel(input) {
|
|
|
256
259
|
page: input.page ?? 1,
|
|
257
260
|
live: input.live,
|
|
258
261
|
liveToken: input.liveToken,
|
|
262
|
+
searchQuery: input.searchQuery,
|
|
259
263
|
identity: loadIdentity(input.cwd),
|
|
260
264
|
sandboxes: loadSandboxes(input.cwd),
|
|
261
265
|
wiredHosts: loadWiredHosts(input.cwd),
|
|
@@ -309,17 +313,17 @@ function projectMeta(project) {
|
|
|
309
313
|
<span class="dot">-</span>
|
|
310
314
|
<span class="project">${esc(clip(p, 40))}</span>`;
|
|
311
315
|
}
|
|
312
|
-
function renderFeed(events, nowMs, live) {
|
|
316
|
+
function renderFeed(events, nowMs, live, preserveOrder) {
|
|
313
317
|
if (events.length === 0) {
|
|
314
318
|
const hint = live
|
|
315
319
|
? "No events yet — evaluate a tool call with <code>kya wrap</code> and this page will refresh."
|
|
316
320
|
: "No events yet — evaluate a tool call with <code>kya wrap</code>, then regenerate this receipt.";
|
|
317
321
|
return `<p class="empty">${hint}</p>`;
|
|
318
322
|
}
|
|
319
|
-
const
|
|
323
|
+
const ordered = preserveOrder ? [...events] : [...events].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
320
324
|
const parts = [];
|
|
321
325
|
let lastDay = "";
|
|
322
|
-
for (const e of
|
|
326
|
+
for (const e of ordered) {
|
|
323
327
|
const key = localDayKey(e.ts);
|
|
324
328
|
if (key !== lastDay) {
|
|
325
329
|
lastDay = key;
|
|
@@ -1273,52 +1277,32 @@ function normalizeTrailEvents(events) {
|
|
|
1273
1277
|
targetPath: e.targetPath ? clip(e.targetPath, 80) : undefined,
|
|
1274
1278
|
}));
|
|
1275
1279
|
}
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
return (e.sessionId || "unknown") === value;
|
|
1296
|
-
case "server": {
|
|
1297
|
-
const parsed = parseMcpToolId(e.toolId);
|
|
1298
|
-
return parsed != null && mcpServerLabel(parsed.server) === value;
|
|
1299
|
-
}
|
|
1300
|
-
default:
|
|
1301
|
-
return false;
|
|
1280
|
+
export function renderFeedPage(events, filters, page, nowMs, live, query, embeddings, queryEmbedding) {
|
|
1281
|
+
const q = query?.trim();
|
|
1282
|
+
let ordered;
|
|
1283
|
+
let total;
|
|
1284
|
+
if (q) {
|
|
1285
|
+
const ranked = rankEvents(events, {
|
|
1286
|
+
query: q,
|
|
1287
|
+
filters,
|
|
1288
|
+
embeddings,
|
|
1289
|
+
queryEmbedding,
|
|
1290
|
+
facetBoost: 0.05,
|
|
1291
|
+
});
|
|
1292
|
+
ordered = ranked.map((r) => r.event);
|
|
1293
|
+
total = ranked.length;
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
const sorted = [...events].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
1297
|
+
ordered = applyChipFilters(sorted, filters);
|
|
1298
|
+
total = ordered.length;
|
|
1302
1299
|
}
|
|
1303
|
-
}
|
|
1304
|
-
function applyFeedFilters(events, filters) {
|
|
1305
|
-
const groups = Object.entries(filters)
|
|
1306
|
-
.map(([group, vals]) => [group, vals.filter((v) => v !== "")])
|
|
1307
|
-
.filter(([, vals]) => vals.length > 0);
|
|
1308
|
-
if (groups.length === 0)
|
|
1309
|
-
return [...events];
|
|
1310
|
-
return events.filter((e) => groups.every(([group, vals]) => vals.some((v) => eventMatchesFilter(e, group, v))));
|
|
1311
|
-
}
|
|
1312
|
-
export function renderFeedPage(events, filters, page, nowMs, live) {
|
|
1313
|
-
const sorted = [...events].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
1314
|
-
const filtered = applyFeedFilters(sorted, filters);
|
|
1315
|
-
const total = filtered.length;
|
|
1316
1300
|
const pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
1317
1301
|
const p = clampPage(page, pages);
|
|
1318
1302
|
const start = (p - 1) * PAGE_SIZE;
|
|
1319
|
-
const pageEvents =
|
|
1303
|
+
const pageEvents = ordered.slice(start, start + PAGE_SIZE);
|
|
1320
1304
|
return {
|
|
1321
|
-
feedHtml: renderFeed(pageEvents, nowMs, live),
|
|
1305
|
+
feedHtml: renderFeed(pageEvents, nowMs, live, !!q),
|
|
1322
1306
|
paginationHtml: paginationBar(total, p, pages),
|
|
1323
1307
|
total,
|
|
1324
1308
|
page: p,
|
|
@@ -1358,7 +1342,7 @@ export function renderReceiptHtml(model) {
|
|
|
1358
1342
|
? clampPage(model.page ?? 1, Math.max(1, Math.ceil(events.length / PAGE_SIZE)))
|
|
1359
1343
|
: 1;
|
|
1360
1344
|
const initialFeed = livePaged
|
|
1361
|
-
? renderFeedPage(events, {}, currentPage, nowMs, model.live ?? false)
|
|
1345
|
+
? renderFeedPage(events, {}, currentPage, nowMs, model.live ?? false, model.searchQuery)
|
|
1362
1346
|
: {
|
|
1363
1347
|
feedHtml: renderFeed(events, nowMs, model.live ?? false),
|
|
1364
1348
|
paginationHtml: `<div class="pagination-bar"><span class="mute">${events.length} event${events.length === 1 ? "" : "s"}</span></div>`,
|
|
@@ -1402,18 +1386,17 @@ export function renderReceiptHtml(model) {
|
|
|
1402
1386
|
})();
|
|
1403
1387
|
</script>`
|
|
1404
1388
|
: "";
|
|
1405
|
-
// Client-side chip filters and
|
|
1406
|
-
// (liveToken present). Static receipts render
|
|
1407
|
-
// State maps are null-prototype: attacker-controlled
|
|
1408
|
-
// must never resolve via Object.prototype. The id lets
|
|
1409
|
-
// this script. KNOWN is space-delimited so
|
|
1410
|
-
// prototype-safe whitelist ('constructor'
|
|
1411
|
-
// Filter state persists in the ?f= QUERY param, not the
|
|
1412
|
-
// owned by the pure-CSS :target tabs
|
|
1413
|
-
//
|
|
1414
|
-
//
|
|
1415
|
-
//
|
|
1416
|
-
// and drops the legacy fragment.
|
|
1389
|
+
// Client-side chip filters, pagination, and search: only emitted for live
|
|
1390
|
+
// reports (liveToken present). Static receipts render a separate BM25-only
|
|
1391
|
+
// search script below. State maps are null-prototype: attacker-controlled
|
|
1392
|
+
// values (project names) must never resolve via Object.prototype. The id lets
|
|
1393
|
+
// tests extract just this script. KNOWN is space-delimited so
|
|
1394
|
+
// `indexOf(' '+g+' ')` doubles as a prototype-safe whitelist ('constructor'
|
|
1395
|
+
// etc. never match). Filter state persists in the ?f= QUERY param, not the
|
|
1396
|
+
// hash: the hash is owned by the pure-CSS :target tabs, so a hash-based
|
|
1397
|
+
// filter would hide the Activity zone on every chip toggle. Legacy #f= hashes
|
|
1398
|
+
// are still parsed on load (read-only); the next save writes the ?f= form and
|
|
1399
|
+
// drops the legacy fragment.
|
|
1417
1400
|
const filterScript = model.liveToken
|
|
1418
1401
|
? `<script id="kya-filters">
|
|
1419
1402
|
(function(){
|
|
@@ -1421,9 +1404,12 @@ export function renderReceiptHtml(model) {
|
|
|
1421
1404
|
if (!feed) return;
|
|
1422
1405
|
var chg = document.getElementById('changes-list');
|
|
1423
1406
|
var bar = document.getElementById('pagination-bar');
|
|
1407
|
+
var searchInput = document.getElementById('feed-search');
|
|
1408
|
+
var searchMeta = document.querySelector('.feed-search-meta');
|
|
1424
1409
|
var KNOWN = ' verdict never mode plane product project tool session server ';
|
|
1425
1410
|
var state = Object.create(null);
|
|
1426
1411
|
var page = 1;
|
|
1412
|
+
var q = '';
|
|
1427
1413
|
var LIVE = !!(document.querySelectorAll && document.querySelectorAll('main[data-live-token]').length);
|
|
1428
1414
|
var clearBtn = document.getElementById('clear-filters');
|
|
1429
1415
|
var chips = document.querySelectorAll('[data-fgroup]');
|
|
@@ -1441,12 +1427,16 @@ export function renderReceiptHtml(model) {
|
|
|
1441
1427
|
function parse(){
|
|
1442
1428
|
state = Object.create(null);
|
|
1443
1429
|
page = 1;
|
|
1430
|
+
q = '';
|
|
1444
1431
|
var raw = null;
|
|
1445
1432
|
var s = location.search;
|
|
1446
1433
|
if (s.indexOf('?') === 0) {
|
|
1447
1434
|
var params = s.slice(1).split('&');
|
|
1448
1435
|
for (var i = 0; i < params.length; i++) {
|
|
1449
1436
|
if (raw === null && params[i].indexOf('f=') === 0) { raw = params[i].slice(2); }
|
|
1437
|
+
else if (params[i].indexOf('q=') === 0) {
|
|
1438
|
+
try { q = decodeURIComponent(params[i].slice(2).replace(/\\+/g, ' ')); } catch(e){ q = params[i].slice(2); }
|
|
1439
|
+
}
|
|
1450
1440
|
else if (params[i].indexOf('page=') === 0) {
|
|
1451
1441
|
var p = parseInt(params[i].slice(5), 10);
|
|
1452
1442
|
if (p > 0) page = p;
|
|
@@ -1481,19 +1471,18 @@ export function renderReceiptHtml(model) {
|
|
|
1481
1471
|
if (s.indexOf('?') === 0) {
|
|
1482
1472
|
var params = s.slice(1).split('&');
|
|
1483
1473
|
for (var i = 0; i < params.length; i++) {
|
|
1484
|
-
if (params[i] && params[i].indexOf('f=') !== 0 && params[i].indexOf('page=') !== 0) kept.push(params[i]);
|
|
1474
|
+
if (params[i] && params[i].indexOf('f=') !== 0 && params[i].indexOf('q=') !== 0 && params[i].indexOf('page=') !== 0) kept.push(params[i]);
|
|
1485
1475
|
}
|
|
1486
1476
|
}
|
|
1487
1477
|
if (parts.length) kept.push('f=' + parts.join(','));
|
|
1478
|
+
if (q) kept.push('q=' + encodeURIComponent(q));
|
|
1488
1479
|
if (page > 1) kept.push('page=' + page);
|
|
1489
|
-
var
|
|
1490
|
-
// The tab hash rides along untouched; a legacy #f= fragment is not a tab
|
|
1491
|
-
// hash and is dropped now that the state lives in the query.
|
|
1480
|
+
var qq = kept.length ? '?' + kept.join('&') : '';
|
|
1492
1481
|
var h = location.hash.indexOf('#f=') === 0 ? '' : location.hash;
|
|
1493
|
-
history.replaceState(null, '', location.pathname +
|
|
1482
|
+
history.replaceState(null, '', location.pathname + qq + h);
|
|
1494
1483
|
}
|
|
1495
1484
|
function active(){
|
|
1496
|
-
return Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; });
|
|
1485
|
+
return Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; }) || !!q;
|
|
1497
1486
|
}
|
|
1498
1487
|
// AND across groups, OR within a group; an unstamped facet never matches.
|
|
1499
1488
|
function rowVisible(row, on){
|
|
@@ -1505,7 +1494,7 @@ export function renderReceiptHtml(model) {
|
|
|
1505
1494
|
return true;
|
|
1506
1495
|
}
|
|
1507
1496
|
function apply(){
|
|
1508
|
-
var on =
|
|
1497
|
+
var on = Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; });
|
|
1509
1498
|
// Guard: the script is only emitted for live reports, so the feed is
|
|
1510
1499
|
// always server-rendered; changes entries still filter client-side.
|
|
1511
1500
|
if (!LIVE) {
|
|
@@ -1536,7 +1525,13 @@ export function renderReceiptHtml(model) {
|
|
|
1536
1525
|
var pressed = !!(state[chip.dataset.fgroup] && state[chip.dataset.fgroup][chip.dataset.fvalue]);
|
|
1537
1526
|
chip.setAttribute('aria-pressed', pressed ? 'true' : 'false');
|
|
1538
1527
|
});
|
|
1539
|
-
if (clearBtn) clearBtn.hidden = !
|
|
1528
|
+
if (clearBtn) clearBtn.hidden = !active();
|
|
1529
|
+
}
|
|
1530
|
+
function updateMeta(total){
|
|
1531
|
+
if (!searchMeta) return;
|
|
1532
|
+
if (!q) { searchMeta.style.display = 'none'; return; }
|
|
1533
|
+
searchMeta.textContent = (total || 0) + ' matches · ranked by relevance';
|
|
1534
|
+
searchMeta.style.display = '';
|
|
1540
1535
|
}
|
|
1541
1536
|
function feedUpdate(){
|
|
1542
1537
|
var T = readToken();
|
|
@@ -1544,12 +1539,13 @@ export function renderReceiptHtml(model) {
|
|
|
1544
1539
|
fetch('/feed?t=' + encodeURIComponent(T), {
|
|
1545
1540
|
method: 'POST',
|
|
1546
1541
|
headers: { 'Content-Type': 'application/json' },
|
|
1547
|
-
body: JSON.stringify({ filters: state, page: page })
|
|
1542
|
+
body: JSON.stringify({ filters: state, page: page, q: q })
|
|
1548
1543
|
})
|
|
1549
1544
|
.then(function(r){ return r.json(); })
|
|
1550
1545
|
.then(function(data){
|
|
1551
1546
|
if (feed && data.feedHtml) feed.innerHTML = data.feedHtml;
|
|
1552
1547
|
if (bar && data.paginationHtml) bar.innerHTML = data.paginationHtml;
|
|
1548
|
+
updateMeta(data.total);
|
|
1553
1549
|
})
|
|
1554
1550
|
.catch(function(err){ console.error('KYA feed update failed', err); });
|
|
1555
1551
|
}
|
|
@@ -1582,10 +1578,139 @@ export function renderReceiptHtml(model) {
|
|
|
1582
1578
|
feedUpdate();
|
|
1583
1579
|
});
|
|
1584
1580
|
}
|
|
1585
|
-
if (
|
|
1581
|
+
if (searchInput) {
|
|
1582
|
+
var debounce = 0;
|
|
1583
|
+
searchInput.addEventListener('input', function(){
|
|
1584
|
+
q = (searchInput.value || '').trim();
|
|
1585
|
+
page = 1;
|
|
1586
|
+
save(); apply();
|
|
1587
|
+
clearTimeout(debounce);
|
|
1588
|
+
if (LIVE) debounce = setTimeout(feedUpdate, 120);
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
if (clearBtn) clearBtn.addEventListener('click', function(){ state = Object.create(null); page = 1; q = ''; if (searchInput) searchInput.value = ''; save(); apply(); if (LIVE) feedUpdate(); });
|
|
1586
1592
|
parse(); apply();
|
|
1593
|
+
if (searchInput) searchInput.value = q;
|
|
1587
1594
|
if (LIVE && (active() || page !== 1)) feedUpdate();
|
|
1588
1595
|
})();
|
|
1596
|
+
</script>`
|
|
1597
|
+
: "";
|
|
1598
|
+
// Static receipts: a small client-side BM25-only scorer that filters the
|
|
1599
|
+
// rendered feed rows. No network round-trips and no semantic embeddings.
|
|
1600
|
+
const staticSearchScript = !model.liveToken
|
|
1601
|
+
? `<script id="kya-static-search">
|
|
1602
|
+
(function(){
|
|
1603
|
+
var feed = document.getElementById('feed');
|
|
1604
|
+
var input = document.getElementById('feed-search');
|
|
1605
|
+
var meta = document.querySelector('.feed-search-meta');
|
|
1606
|
+
var clearBtn = document.getElementById('clear-filters');
|
|
1607
|
+
if (!feed || !input) return;
|
|
1608
|
+
var STOP = Object.create(null);
|
|
1609
|
+
"a an the and or but is are was were be been being to of in on at by for with as this that it its from up about into through during before after above below between among within without against over under again further then once here there when where why how all any both each few more most other some such only own same so than too very can will just should now did does do has have had having get got gets make made makes use used uses using".split(" ").forEach(function(w){ STOP[w] = 1; });
|
|
1610
|
+
function stem(t){
|
|
1611
|
+
if (t.length <= 3) return t;
|
|
1612
|
+
if (t.slice(-3) === 'ing') return t.slice(0,-3) || t;
|
|
1613
|
+
if (t.slice(-2) === 'ed') return t.slice(0,-2) || t;
|
|
1614
|
+
if (t.slice(-1) === 's' && t.slice(-2) !== 'ss') return t.slice(0,-1) || t;
|
|
1615
|
+
return t;
|
|
1616
|
+
}
|
|
1617
|
+
function tokenize(text){
|
|
1618
|
+
var out = [];
|
|
1619
|
+
var parts = String(text).toLowerCase().split(/[^a-z0-9]+/);
|
|
1620
|
+
for (var i = 0; i < parts.length; i++) {
|
|
1621
|
+
var p = parts[i];
|
|
1622
|
+
if (!p) continue;
|
|
1623
|
+
var split = p.replace(/([A-Z])/g, ' $1').replace(/[_\\-.]/g, ' ').toLowerCase().split(/[^a-z0-9]+/);
|
|
1624
|
+
for (var j = 0; j < split.length; j++) {
|
|
1625
|
+
var s = stem(split[j]);
|
|
1626
|
+
if (s && !STOP[s]) out.push(s);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return out;
|
|
1630
|
+
}
|
|
1631
|
+
var rows = Array.prototype.slice.call(feed.querySelectorAll('.ev'));
|
|
1632
|
+
var docs = rows.map(function(row){
|
|
1633
|
+
var text = row.textContent || '';
|
|
1634
|
+
var tokens = tokenize(text);
|
|
1635
|
+
var freq = Object.create(null);
|
|
1636
|
+
for (var i = 0; i < tokens.length; i++) freq[tokens[i]] = (freq[tokens[i]] || 0) + 1;
|
|
1637
|
+
return { row: row, tokens: tokens, freq: freq, text: text };
|
|
1638
|
+
});
|
|
1639
|
+
var N = docs.length;
|
|
1640
|
+
var avgLen = N ? docs.reduce(function(a,d){ return a + d.tokens.length; }, 0) / N : 0;
|
|
1641
|
+
var q = '';
|
|
1642
|
+
function scoreDoc(doc, qTokens){
|
|
1643
|
+
if (!qTokens.length) return 1;
|
|
1644
|
+
var score = 0, k1 = 1.5, b = 0.75;
|
|
1645
|
+
for (var i = 0; i < qTokens.length; i++) {
|
|
1646
|
+
var t = qTokens[i];
|
|
1647
|
+
var df = 0;
|
|
1648
|
+
for (var j = 0; j < docs.length; j++) if (docs[j].freq[t]) df++;
|
|
1649
|
+
if (!df) continue;
|
|
1650
|
+
var tf = doc.freq[t] || 0;
|
|
1651
|
+
if (!tf) continue;
|
|
1652
|
+
var idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
|
|
1653
|
+
var denom = tf + k1 * (1 - b + b * doc.tokens.length / Math.max(avgLen, 1));
|
|
1654
|
+
score += idf * (tf * (k1 + 1) / denom);
|
|
1655
|
+
}
|
|
1656
|
+
return score;
|
|
1657
|
+
}
|
|
1658
|
+
function apply(){
|
|
1659
|
+
var qTokens = tokenize(q);
|
|
1660
|
+
var visible = 0;
|
|
1661
|
+
for (var i = 0; i < docs.length; i++) {
|
|
1662
|
+
var s = scoreDoc(docs[i], qTokens);
|
|
1663
|
+
var hide = q && s <= 0;
|
|
1664
|
+
docs[i].row.classList.toggle('filtered-out', hide);
|
|
1665
|
+
if (!hide) visible++;
|
|
1666
|
+
}
|
|
1667
|
+
var day = null, dayHas = false;
|
|
1668
|
+
function flush(){ if (day) day.classList.toggle('filtered-out', q && !dayHas); }
|
|
1669
|
+
for (var k = 0; k < feed.children.length; k++) {
|
|
1670
|
+
var el = feed.children[k];
|
|
1671
|
+
if (el.classList.contains('day')) { flush(); day = el; dayHas = false; }
|
|
1672
|
+
else if (el.classList.contains('ev') && !el.classList.contains('filtered-out')) dayHas = true;
|
|
1673
|
+
}
|
|
1674
|
+
flush();
|
|
1675
|
+
if (meta) {
|
|
1676
|
+
if (q) { meta.textContent = visible + ' matches · filtered by relevance'; meta.style.display = ''; }
|
|
1677
|
+
else meta.style.display = 'none';
|
|
1678
|
+
}
|
|
1679
|
+
if (clearBtn) clearBtn.hidden = !q;
|
|
1680
|
+
}
|
|
1681
|
+
function save(){
|
|
1682
|
+
var kept = [];
|
|
1683
|
+
var s = location.search;
|
|
1684
|
+
if (s.indexOf('?') === 0) {
|
|
1685
|
+
var params = s.slice(1).split('&');
|
|
1686
|
+
for (var i = 0; i < params.length; i++) {
|
|
1687
|
+
if (params[i] && params[i].indexOf('q=') !== 0) kept.push(params[i]);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
if (q) kept.push('q=' + encodeURIComponent(q));
|
|
1691
|
+
var qq = kept.length ? '?' + kept.join('&') : '';
|
|
1692
|
+
var h = location.hash;
|
|
1693
|
+
history.replaceState(null, '', location.pathname + qq + h);
|
|
1694
|
+
}
|
|
1695
|
+
input.addEventListener('input', function(){ q = (input.value || '').trim(); apply(); save(); });
|
|
1696
|
+
if (clearBtn) clearBtn.addEventListener('click', function(){ q = ''; input.value = ''; apply(); save(); });
|
|
1697
|
+
var s = location.search;
|
|
1698
|
+
var found = false;
|
|
1699
|
+
if (s.indexOf('?') === 0) {
|
|
1700
|
+
var params = s.slice(1).split('&');
|
|
1701
|
+
for (var i = 0; i < params.length; i++) {
|
|
1702
|
+
if (params[i].indexOf('q=') === 0) {
|
|
1703
|
+
try { q = decodeURIComponent(params[i].slice(2).replace(/\\+/g, ' ')); } catch(e){ q = params[i].slice(2); }
|
|
1704
|
+
found = true;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
// No ?q= in the URL: honor the server-prefilled input (static artifacts
|
|
1709
|
+
// generated with kya receipt --q) so they open pre-filtered.
|
|
1710
|
+
if (!found) q = (input.value || '').trim();
|
|
1711
|
+
input.value = q;
|
|
1712
|
+
apply();
|
|
1713
|
+
})();
|
|
1589
1714
|
</script>`
|
|
1590
1715
|
: "";
|
|
1591
1716
|
const livePill = model.live ? `<span class="live" title="Watching trail.jsonl">Live</span>` : "";
|
|
@@ -1775,6 +1900,7 @@ export function renderReceiptHtml(model) {
|
|
|
1775
1900
|
<button type="button" class="theme-toggle" id="theme-toggle" aria-label="Toggle theme">Theme</button>
|
|
1776
1901
|
<!-- Stop-report action is wired by the live-server POST handler. -->
|
|
1777
1902
|
<a href="#" id="stop-report">Stop report</a>
|
|
1903
|
+
${model.liveToken ? '<a href="https://github.com/The-Pixel-Boys/shield-agent" class="star-link" target="_blank" rel="noopener noreferrer">Star on GitHub ↗</a>' : ""}
|
|
1778
1904
|
</footer>
|
|
1779
1905
|
</aside>
|
|
1780
1906
|
${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
@@ -1799,6 +1925,11 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
|
1799
1925
|
${overviewBody}
|
|
1800
1926
|
</section>
|
|
1801
1927
|
<section class="zone" id="activity" aria-labelledby="tab-activity">
|
|
1928
|
+
<div class="feed-search">
|
|
1929
|
+
<input id="feed-search" class="feed-search-input" type="search" placeholder="Search activity in plain language…" value="${esc(model.searchQuery ?? "")}" autocomplete="off" />
|
|
1930
|
+
${model.searchQuery ? `<div class="feed-search-meta">${initialFeed.total} matches · ${livePaged ? "ranked by relevance" : "filtered by relevance"}</div>` : ""}
|
|
1931
|
+
</div>
|
|
1932
|
+
${!model.liveToken && model.searchQuery ? '<p class="feed-search-note">Semantic ranking requires the live report server (<code>kya start</code>).</p>' : ""}
|
|
1802
1933
|
<div class="feed-head">
|
|
1803
1934
|
<div class="stats" aria-label="Counts">
|
|
1804
1935
|
<button type="button" class="stat ok" data-fgroup="verdict" data-fvalue="ALLOW" aria-pressed="false" title="Filter: Allow">Allow<b>${c.allow}</b></button>
|
|
@@ -1833,6 +1964,7 @@ ${changesPanel(changes, nowMs)}
|
|
|
1833
1964
|
</section>
|
|
1834
1965
|
</main>
|
|
1835
1966
|
${filterScript}
|
|
1967
|
+
${staticSearchScript}
|
|
1836
1968
|
${themeScript}
|
|
1837
1969
|
${gateScript}
|
|
1838
1970
|
${liveScript}
|