@shield-agent/kya 0.19.0 → 0.20.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 +15 -4
- 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/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 +24 -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 +192 -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/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 +3 -2
- 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,
|
|
@@ -309,17 +312,17 @@ function projectMeta(project) {
|
|
|
309
312
|
<span class="dot">-</span>
|
|
310
313
|
<span class="project">${esc(clip(p, 40))}</span>`;
|
|
311
314
|
}
|
|
312
|
-
function renderFeed(events, nowMs, live) {
|
|
315
|
+
function renderFeed(events, nowMs, live, preserveOrder) {
|
|
313
316
|
if (events.length === 0) {
|
|
314
317
|
const hint = live
|
|
315
318
|
? "No events yet — evaluate a tool call with <code>kya wrap</code> and this page will refresh."
|
|
316
319
|
: "No events yet — evaluate a tool call with <code>kya wrap</code>, then regenerate this receipt.";
|
|
317
320
|
return `<p class="empty">${hint}</p>`;
|
|
318
321
|
}
|
|
319
|
-
const
|
|
322
|
+
const ordered = preserveOrder ? [...events] : [...events].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
320
323
|
const parts = [];
|
|
321
324
|
let lastDay = "";
|
|
322
|
-
for (const e of
|
|
325
|
+
for (const e of ordered) {
|
|
323
326
|
const key = localDayKey(e.ts);
|
|
324
327
|
if (key !== lastDay) {
|
|
325
328
|
lastDay = key;
|
|
@@ -1273,52 +1276,32 @@ function normalizeTrailEvents(events) {
|
|
|
1273
1276
|
targetPath: e.targetPath ? clip(e.targetPath, 80) : undefined,
|
|
1274
1277
|
}));
|
|
1275
1278
|
}
|
|
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;
|
|
1279
|
+
export function renderFeedPage(events, filters, page, nowMs, live, query, embeddings, queryEmbedding) {
|
|
1280
|
+
const q = query?.trim();
|
|
1281
|
+
let ordered;
|
|
1282
|
+
let total;
|
|
1283
|
+
if (q) {
|
|
1284
|
+
const ranked = rankEvents(events, {
|
|
1285
|
+
query: q,
|
|
1286
|
+
filters,
|
|
1287
|
+
embeddings,
|
|
1288
|
+
queryEmbedding,
|
|
1289
|
+
facetBoost: 0.05,
|
|
1290
|
+
});
|
|
1291
|
+
ordered = ranked.map((r) => r.event);
|
|
1292
|
+
total = ranked.length;
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
const sorted = [...events].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
1296
|
+
ordered = applyChipFilters(sorted, filters);
|
|
1297
|
+
total = ordered.length;
|
|
1302
1298
|
}
|
|
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
1299
|
const pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
1317
1300
|
const p = clampPage(page, pages);
|
|
1318
1301
|
const start = (p - 1) * PAGE_SIZE;
|
|
1319
|
-
const pageEvents =
|
|
1302
|
+
const pageEvents = ordered.slice(start, start + PAGE_SIZE);
|
|
1320
1303
|
return {
|
|
1321
|
-
feedHtml: renderFeed(pageEvents, nowMs, live),
|
|
1304
|
+
feedHtml: renderFeed(pageEvents, nowMs, live, !!q),
|
|
1322
1305
|
paginationHtml: paginationBar(total, p, pages),
|
|
1323
1306
|
total,
|
|
1324
1307
|
page: p,
|
|
@@ -1358,7 +1341,7 @@ export function renderReceiptHtml(model) {
|
|
|
1358
1341
|
? clampPage(model.page ?? 1, Math.max(1, Math.ceil(events.length / PAGE_SIZE)))
|
|
1359
1342
|
: 1;
|
|
1360
1343
|
const initialFeed = livePaged
|
|
1361
|
-
? renderFeedPage(events, {}, currentPage, nowMs, model.live ?? false)
|
|
1344
|
+
? renderFeedPage(events, {}, currentPage, nowMs, model.live ?? false, model.searchQuery)
|
|
1362
1345
|
: {
|
|
1363
1346
|
feedHtml: renderFeed(events, nowMs, model.live ?? false),
|
|
1364
1347
|
paginationHtml: `<div class="pagination-bar"><span class="mute">${events.length} event${events.length === 1 ? "" : "s"}</span></div>`,
|
|
@@ -1402,18 +1385,17 @@ export function renderReceiptHtml(model) {
|
|
|
1402
1385
|
})();
|
|
1403
1386
|
</script>`
|
|
1404
1387
|
: "";
|
|
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.
|
|
1388
|
+
// Client-side chip filters, pagination, and search: only emitted for live
|
|
1389
|
+
// reports (liveToken present). Static receipts render a separate BM25-only
|
|
1390
|
+
// search script below. State maps are null-prototype: attacker-controlled
|
|
1391
|
+
// values (project names) must never resolve via Object.prototype. The id lets
|
|
1392
|
+
// tests extract just this script. KNOWN is space-delimited so
|
|
1393
|
+
// `indexOf(' '+g+' ')` doubles as a prototype-safe whitelist ('constructor'
|
|
1394
|
+
// etc. never match). Filter state persists in the ?f= QUERY param, not the
|
|
1395
|
+
// hash: the hash is owned by the pure-CSS :target tabs, so a hash-based
|
|
1396
|
+
// filter would hide the Activity zone on every chip toggle. Legacy #f= hashes
|
|
1397
|
+
// are still parsed on load (read-only); the next save writes the ?f= form and
|
|
1398
|
+
// drops the legacy fragment.
|
|
1417
1399
|
const filterScript = model.liveToken
|
|
1418
1400
|
? `<script id="kya-filters">
|
|
1419
1401
|
(function(){
|
|
@@ -1421,9 +1403,12 @@ export function renderReceiptHtml(model) {
|
|
|
1421
1403
|
if (!feed) return;
|
|
1422
1404
|
var chg = document.getElementById('changes-list');
|
|
1423
1405
|
var bar = document.getElementById('pagination-bar');
|
|
1406
|
+
var searchInput = document.getElementById('feed-search');
|
|
1407
|
+
var searchMeta = document.querySelector('.feed-search-meta');
|
|
1424
1408
|
var KNOWN = ' verdict never mode plane product project tool session server ';
|
|
1425
1409
|
var state = Object.create(null);
|
|
1426
1410
|
var page = 1;
|
|
1411
|
+
var q = '';
|
|
1427
1412
|
var LIVE = !!(document.querySelectorAll && document.querySelectorAll('main[data-live-token]').length);
|
|
1428
1413
|
var clearBtn = document.getElementById('clear-filters');
|
|
1429
1414
|
var chips = document.querySelectorAll('[data-fgroup]');
|
|
@@ -1441,12 +1426,16 @@ export function renderReceiptHtml(model) {
|
|
|
1441
1426
|
function parse(){
|
|
1442
1427
|
state = Object.create(null);
|
|
1443
1428
|
page = 1;
|
|
1429
|
+
q = '';
|
|
1444
1430
|
var raw = null;
|
|
1445
1431
|
var s = location.search;
|
|
1446
1432
|
if (s.indexOf('?') === 0) {
|
|
1447
1433
|
var params = s.slice(1).split('&');
|
|
1448
1434
|
for (var i = 0; i < params.length; i++) {
|
|
1449
1435
|
if (raw === null && params[i].indexOf('f=') === 0) { raw = params[i].slice(2); }
|
|
1436
|
+
else if (params[i].indexOf('q=') === 0) {
|
|
1437
|
+
try { q = decodeURIComponent(params[i].slice(2).replace(/\\+/g, ' ')); } catch(e){ q = params[i].slice(2); }
|
|
1438
|
+
}
|
|
1450
1439
|
else if (params[i].indexOf('page=') === 0) {
|
|
1451
1440
|
var p = parseInt(params[i].slice(5), 10);
|
|
1452
1441
|
if (p > 0) page = p;
|
|
@@ -1481,19 +1470,18 @@ export function renderReceiptHtml(model) {
|
|
|
1481
1470
|
if (s.indexOf('?') === 0) {
|
|
1482
1471
|
var params = s.slice(1).split('&');
|
|
1483
1472
|
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]);
|
|
1473
|
+
if (params[i] && params[i].indexOf('f=') !== 0 && params[i].indexOf('q=') !== 0 && params[i].indexOf('page=') !== 0) kept.push(params[i]);
|
|
1485
1474
|
}
|
|
1486
1475
|
}
|
|
1487
1476
|
if (parts.length) kept.push('f=' + parts.join(','));
|
|
1477
|
+
if (q) kept.push('q=' + encodeURIComponent(q));
|
|
1488
1478
|
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.
|
|
1479
|
+
var qq = kept.length ? '?' + kept.join('&') : '';
|
|
1492
1480
|
var h = location.hash.indexOf('#f=') === 0 ? '' : location.hash;
|
|
1493
|
-
history.replaceState(null, '', location.pathname +
|
|
1481
|
+
history.replaceState(null, '', location.pathname + qq + h);
|
|
1494
1482
|
}
|
|
1495
1483
|
function active(){
|
|
1496
|
-
return Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; });
|
|
1484
|
+
return Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; }) || !!q;
|
|
1497
1485
|
}
|
|
1498
1486
|
// AND across groups, OR within a group; an unstamped facet never matches.
|
|
1499
1487
|
function rowVisible(row, on){
|
|
@@ -1505,7 +1493,7 @@ export function renderReceiptHtml(model) {
|
|
|
1505
1493
|
return true;
|
|
1506
1494
|
}
|
|
1507
1495
|
function apply(){
|
|
1508
|
-
var on =
|
|
1496
|
+
var on = Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; });
|
|
1509
1497
|
// Guard: the script is only emitted for live reports, so the feed is
|
|
1510
1498
|
// always server-rendered; changes entries still filter client-side.
|
|
1511
1499
|
if (!LIVE) {
|
|
@@ -1536,7 +1524,13 @@ export function renderReceiptHtml(model) {
|
|
|
1536
1524
|
var pressed = !!(state[chip.dataset.fgroup] && state[chip.dataset.fgroup][chip.dataset.fvalue]);
|
|
1537
1525
|
chip.setAttribute('aria-pressed', pressed ? 'true' : 'false');
|
|
1538
1526
|
});
|
|
1539
|
-
if (clearBtn) clearBtn.hidden = !
|
|
1527
|
+
if (clearBtn) clearBtn.hidden = !active();
|
|
1528
|
+
}
|
|
1529
|
+
function updateMeta(total){
|
|
1530
|
+
if (!searchMeta) return;
|
|
1531
|
+
if (!q) { searchMeta.style.display = 'none'; return; }
|
|
1532
|
+
searchMeta.textContent = (total || 0) + ' matches · ranked by relevance';
|
|
1533
|
+
searchMeta.style.display = '';
|
|
1540
1534
|
}
|
|
1541
1535
|
function feedUpdate(){
|
|
1542
1536
|
var T = readToken();
|
|
@@ -1544,12 +1538,13 @@ export function renderReceiptHtml(model) {
|
|
|
1544
1538
|
fetch('/feed?t=' + encodeURIComponent(T), {
|
|
1545
1539
|
method: 'POST',
|
|
1546
1540
|
headers: { 'Content-Type': 'application/json' },
|
|
1547
|
-
body: JSON.stringify({ filters: state, page: page })
|
|
1541
|
+
body: JSON.stringify({ filters: state, page: page, q: q })
|
|
1548
1542
|
})
|
|
1549
1543
|
.then(function(r){ return r.json(); })
|
|
1550
1544
|
.then(function(data){
|
|
1551
1545
|
if (feed && data.feedHtml) feed.innerHTML = data.feedHtml;
|
|
1552
1546
|
if (bar && data.paginationHtml) bar.innerHTML = data.paginationHtml;
|
|
1547
|
+
updateMeta(data.total);
|
|
1553
1548
|
})
|
|
1554
1549
|
.catch(function(err){ console.error('KYA feed update failed', err); });
|
|
1555
1550
|
}
|
|
@@ -1582,10 +1577,134 @@ export function renderReceiptHtml(model) {
|
|
|
1582
1577
|
feedUpdate();
|
|
1583
1578
|
});
|
|
1584
1579
|
}
|
|
1585
|
-
if (
|
|
1580
|
+
if (searchInput) {
|
|
1581
|
+
var debounce = 0;
|
|
1582
|
+
searchInput.addEventListener('input', function(){
|
|
1583
|
+
q = (searchInput.value || '').trim();
|
|
1584
|
+
page = 1;
|
|
1585
|
+
save(); apply();
|
|
1586
|
+
clearTimeout(debounce);
|
|
1587
|
+
if (LIVE) debounce = setTimeout(feedUpdate, 120);
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
if (clearBtn) clearBtn.addEventListener('click', function(){ state = Object.create(null); page = 1; q = ''; if (searchInput) searchInput.value = ''; save(); apply(); if (LIVE) feedUpdate(); });
|
|
1586
1591
|
parse(); apply();
|
|
1592
|
+
if (searchInput) searchInput.value = q;
|
|
1587
1593
|
if (LIVE && (active() || page !== 1)) feedUpdate();
|
|
1588
1594
|
})();
|
|
1595
|
+
</script>`
|
|
1596
|
+
: "";
|
|
1597
|
+
// Static receipts: a small client-side BM25-only scorer that filters the
|
|
1598
|
+
// rendered feed rows. No network round-trips and no semantic embeddings.
|
|
1599
|
+
const staticSearchScript = !model.liveToken
|
|
1600
|
+
? `<script id="kya-static-search">
|
|
1601
|
+
(function(){
|
|
1602
|
+
var feed = document.getElementById('feed');
|
|
1603
|
+
var input = document.getElementById('feed-search');
|
|
1604
|
+
var meta = document.querySelector('.feed-search-meta');
|
|
1605
|
+
var clearBtn = document.getElementById('clear-filters');
|
|
1606
|
+
if (!feed || !input) return;
|
|
1607
|
+
var STOP = Object.create(null);
|
|
1608
|
+
"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; });
|
|
1609
|
+
function stem(t){
|
|
1610
|
+
if (t.length <= 3) return t;
|
|
1611
|
+
if (t.slice(-3) === 'ing') return t.slice(0,-3) || t;
|
|
1612
|
+
if (t.slice(-2) === 'ed') return t.slice(0,-2) || t;
|
|
1613
|
+
if (t.slice(-1) === 's' && t.slice(-2) !== 'ss') return t.slice(0,-1) || t;
|
|
1614
|
+
return t;
|
|
1615
|
+
}
|
|
1616
|
+
function tokenize(text){
|
|
1617
|
+
var out = [];
|
|
1618
|
+
var parts = String(text).toLowerCase().split(/[^a-z0-9]+/);
|
|
1619
|
+
for (var i = 0; i < parts.length; i++) {
|
|
1620
|
+
var p = parts[i];
|
|
1621
|
+
if (!p) continue;
|
|
1622
|
+
var split = p.replace(/([A-Z])/g, ' $1').replace(/[_\-.]/g, ' ').toLowerCase().split(/[^a-z0-9]+/);
|
|
1623
|
+
for (var j = 0; j < split.length; j++) {
|
|
1624
|
+
var s = stem(split[j]);
|
|
1625
|
+
if (s && !STOP[s]) out.push(s);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return out;
|
|
1629
|
+
}
|
|
1630
|
+
var rows = Array.prototype.slice.call(feed.querySelectorAll('.ev'));
|
|
1631
|
+
var docs = rows.map(function(row){
|
|
1632
|
+
var text = row.textContent || '';
|
|
1633
|
+
var tokens = tokenize(text);
|
|
1634
|
+
var freq = Object.create(null);
|
|
1635
|
+
for (var i = 0; i < tokens.length; i++) freq[tokens[i]] = (freq[tokens[i]] || 0) + 1;
|
|
1636
|
+
return { row: row, tokens: tokens, freq: freq, text: text };
|
|
1637
|
+
});
|
|
1638
|
+
var N = docs.length;
|
|
1639
|
+
var avgLen = N ? docs.reduce(function(a,d){ return a + d.tokens.length; }, 0) / N : 0;
|
|
1640
|
+
var q = '';
|
|
1641
|
+
function scoreDoc(doc, qTokens){
|
|
1642
|
+
if (!qTokens.length) return 1;
|
|
1643
|
+
var score = 0, k1 = 1.5, b = 0.75;
|
|
1644
|
+
for (var i = 0; i < qTokens.length; i++) {
|
|
1645
|
+
var t = qTokens[i];
|
|
1646
|
+
var df = 0;
|
|
1647
|
+
for (var j = 0; j < docs.length; j++) if (docs[j].freq[t]) df++;
|
|
1648
|
+
if (!df) continue;
|
|
1649
|
+
var tf = doc.freq[t] || 0;
|
|
1650
|
+
if (!tf) continue;
|
|
1651
|
+
var idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
|
|
1652
|
+
var denom = tf + k1 * (1 - b + b * doc.tokens.length / Math.max(avgLen, 1));
|
|
1653
|
+
score += idf * (tf * (k1 + 1) / denom);
|
|
1654
|
+
}
|
|
1655
|
+
return score;
|
|
1656
|
+
}
|
|
1657
|
+
function apply(){
|
|
1658
|
+
var qTokens = tokenize(q);
|
|
1659
|
+
var visible = 0;
|
|
1660
|
+
for (var i = 0; i < docs.length; i++) {
|
|
1661
|
+
var s = scoreDoc(docs[i], qTokens);
|
|
1662
|
+
var hide = q && s <= 0;
|
|
1663
|
+
docs[i].row.classList.toggle('filtered-out', hide);
|
|
1664
|
+
if (!hide) visible++;
|
|
1665
|
+
}
|
|
1666
|
+
var day = null, dayHas = false;
|
|
1667
|
+
function flush(){ if (day) day.classList.toggle('filtered-out', q && !dayHas); }
|
|
1668
|
+
for (var k = 0; k < feed.children.length; k++) {
|
|
1669
|
+
var el = feed.children[k];
|
|
1670
|
+
if (el.classList.contains('day')) { flush(); day = el; dayHas = false; }
|
|
1671
|
+
else if (el.classList.contains('ev') && !el.classList.contains('filtered-out')) dayHas = true;
|
|
1672
|
+
}
|
|
1673
|
+
flush();
|
|
1674
|
+
if (meta) {
|
|
1675
|
+
if (q) { meta.textContent = visible + ' matches · filtered by relevance'; meta.style.display = ''; }
|
|
1676
|
+
else meta.style.display = 'none';
|
|
1677
|
+
}
|
|
1678
|
+
if (clearBtn) clearBtn.hidden = !q;
|
|
1679
|
+
}
|
|
1680
|
+
function save(){
|
|
1681
|
+
var kept = [];
|
|
1682
|
+
var s = location.search;
|
|
1683
|
+
if (s.indexOf('?') === 0) {
|
|
1684
|
+
var params = s.slice(1).split('&');
|
|
1685
|
+
for (var i = 0; i < params.length; i++) {
|
|
1686
|
+
if (params[i] && params[i].indexOf('q=') !== 0) kept.push(params[i]);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
if (q) kept.push('q=' + encodeURIComponent(q));
|
|
1690
|
+
var qq = kept.length ? '?' + kept.join('&') : '';
|
|
1691
|
+
var h = location.hash;
|
|
1692
|
+
history.replaceState(null, '', location.pathname + qq + h);
|
|
1693
|
+
}
|
|
1694
|
+
input.addEventListener('input', function(){ q = (input.value || '').trim(); apply(); save(); });
|
|
1695
|
+
if (clearBtn) clearBtn.addEventListener('click', function(){ q = ''; input.value = ''; apply(); save(); });
|
|
1696
|
+
var s = location.search;
|
|
1697
|
+
if (s.indexOf('?') === 0) {
|
|
1698
|
+
var params = s.slice(1).split('&');
|
|
1699
|
+
for (var i = 0; i < params.length; i++) {
|
|
1700
|
+
if (params[i].indexOf('q=') === 0) {
|
|
1701
|
+
try { q = decodeURIComponent(params[i].slice(2).replace(/\\+/g, ' ')); } catch(e){ q = params[i].slice(2); }
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
input.value = q;
|
|
1706
|
+
apply();
|
|
1707
|
+
})();
|
|
1589
1708
|
</script>`
|
|
1590
1709
|
: "";
|
|
1591
1710
|
const livePill = model.live ? `<span class="live" title="Watching trail.jsonl">Live</span>` : "";
|
|
@@ -1799,6 +1918,11 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
|
|
|
1799
1918
|
${overviewBody}
|
|
1800
1919
|
</section>
|
|
1801
1920
|
<section class="zone" id="activity" aria-labelledby="tab-activity">
|
|
1921
|
+
<div class="feed-search">
|
|
1922
|
+
<input id="feed-search" class="feed-search-input" type="search" placeholder="Search activity in plain language…" value="${esc(model.searchQuery ?? "")}" autocomplete="off" />
|
|
1923
|
+
${model.searchQuery ? `<div class="feed-search-meta">${initialFeed.total} matches · ${livePaged ? "ranked by relevance" : "filtered by relevance"}</div>` : ""}
|
|
1924
|
+
</div>
|
|
1925
|
+
${!model.liveToken && model.searchQuery ? '<p class="feed-search-note">Semantic ranking requires the live report server (<code>kya start</code>).</p>' : ""}
|
|
1802
1926
|
<div class="feed-head">
|
|
1803
1927
|
<div class="stats" aria-label="Counts">
|
|
1804
1928
|
<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 +1957,7 @@ ${changesPanel(changes, nowMs)}
|
|
|
1833
1957
|
</section>
|
|
1834
1958
|
</main>
|
|
1835
1959
|
${filterScript}
|
|
1960
|
+
${staticSearchScript}
|
|
1836
1961
|
${themeScript}
|
|
1837
1962
|
${gateScript}
|
|
1838
1963
|
${liveScript}
|