codesesh 1.0.2 → 1.0.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/dist/{chunk-VAUC2W7I.js → chunk-PSHZZITB.js} +1919 -731
- package/dist/chunk-PSHZZITB.js.map +1 -0
- package/dist/chunk-RF3EEWPH.js +17 -0
- package/dist/chunk-RF3EEWPH.js.map +1 -0
- package/dist/{chunk-PROMURPM.js → chunk-WAKW5OPS.js} +2 -2
- package/dist/{dist-6QTGTYZT.js → dist-RHDY2EZX.js} +18 -20
- package/dist/index.js +1291 -672
- package/dist/index.js.map +1 -1
- package/dist/project-identity-worker.js +27 -0
- package/dist/project-identity-worker.js.map +1 -0
- package/dist/scan-refresh-worker.js +15 -6
- package/dist/scan-refresh-worker.js.map +1 -1
- package/dist/search-index-worker.js +16 -6
- package/dist/search-index-worker.js.map +1 -1
- package/dist/smart-tag-worker.js +2 -2
- package/dist/web/assets/{ErrorBoundary-D11tWzfn.js → ErrorBoundary-DIwwrdlw.js} +1 -1
- package/dist/web/assets/InteractiveReceipt-DmNBDVms.js +1 -0
- package/dist/web/assets/{OverviewScreen-DriPmCam.js → OverviewScreen-B5JekBLC.js} +1 -1
- package/dist/web/assets/Projects-Bj6ma2k2.js +1 -0
- package/dist/web/assets/{SearchFilterBar-Bx73B5e9.js → SearchFilterBar-B9VrUbym.js} +1 -1
- package/dist/web/assets/{SearchResultsPanel-A6_3TPhW.js → SearchResultsPanel-C1cuuOKq.js} +1 -1
- package/dist/web/assets/{SessionDetail-BnpvDwtH.js → SessionDetail-Dc2CQ3hH.js} +5 -5
- package/dist/web/assets/contract-DXvltqJG.js +1 -0
- package/dist/web/assets/index-3UfOiTBO.css +1 -0
- package/dist/web/assets/index-DBMiEd61.js +1410 -0
- package/dist/web/assets/panel-CLnL4kMN.js +1 -0
- package/dist/web/assets/{session-indexes-0oT5HSS5.js → session-indexes-BPhf1X2P.js} +2 -2
- package/dist/web/assets/{utils-DgUgu15E.js → utils-BKqufGdv.js} +1 -1
- package/dist/web/index.html +8 -8
- package/package.json +1 -1
- package/dist/chunk-VAUC2W7I.js.map +0 -1
- package/dist/web/assets/InteractiveReceipt-DYGyHeZd.js +0 -1
- package/dist/web/assets/Projects-BPDIcUBK.js +0 -1
- package/dist/web/assets/contract-V6QuD0UY.js +0 -1
- package/dist/web/assets/index-99ZxpV2y.js +0 -1410
- package/dist/web/assets/index-B2RqUgZ5.css +0 -1
- package/dist/web/assets/panel-C-gc79Gz.js +0 -1
- /package/dist/{chunk-PROMURPM.js.map → chunk-WAKW5OPS.js.map} +0 -0
- /package/dist/{dist-6QTGTYZT.js.map → dist-RHDY2EZX.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AGENT_UNAVAILABLE_DURING_SCAN_ERROR_CODE,
|
|
4
|
+
AgentUnavailableDuringScanError
|
|
5
|
+
} from "./chunk-RF3EEWPH.js";
|
|
2
6
|
import {
|
|
3
7
|
appLogger,
|
|
4
8
|
logSearchIndexSync
|
|
5
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WAKW5OPS.js";
|
|
6
10
|
import {
|
|
7
11
|
FileSystemSessionSource,
|
|
12
|
+
PROJECT_IDENTITY_RESOLVER_REVISION,
|
|
8
13
|
SessionAliasValidationError,
|
|
9
14
|
StateStorageUnavailableError,
|
|
10
15
|
addCalendarDays,
|
|
@@ -16,7 +21,7 @@ import {
|
|
|
16
21
|
closeCacheStorage,
|
|
17
22
|
computeSessionDiff,
|
|
18
23
|
countCalendarDays,
|
|
19
|
-
|
|
24
|
+
createProjectScopeMatcherFromIdentity,
|
|
20
25
|
createRegisteredAgents,
|
|
21
26
|
createSessionIndex,
|
|
22
27
|
createSessionProjectionContext,
|
|
@@ -28,16 +33,17 @@ import {
|
|
|
28
33
|
formatSessionReference,
|
|
29
34
|
getAgentFullSyncCursor,
|
|
30
35
|
getAgentInfoMap,
|
|
36
|
+
getAnalyticsRevision,
|
|
31
37
|
getPricingGeneration,
|
|
38
|
+
getSearchProjectDirectory,
|
|
32
39
|
getSessionActivityTime,
|
|
33
40
|
getSessionAgentKey,
|
|
34
|
-
getSessionRouteKey,
|
|
35
41
|
importBookmarks,
|
|
36
42
|
isProjectIdentityKind,
|
|
37
43
|
listBookmarks,
|
|
38
44
|
listCachedProjectGroups,
|
|
45
|
+
listDashboardCostFacts,
|
|
39
46
|
listFileActivity,
|
|
40
|
-
listModelCostDistribution,
|
|
41
47
|
listSessionAliases,
|
|
42
48
|
loadCachedSessionHeads,
|
|
43
49
|
loadCachedSessions,
|
|
@@ -51,11 +57,13 @@ import {
|
|
|
51
57
|
mergeSearchQueryOptions,
|
|
52
58
|
mergeSessionsUpdatedEvents,
|
|
53
59
|
mergeSortedSessions,
|
|
60
|
+
normalizeProjectDirectory,
|
|
54
61
|
normalizeSessionReference,
|
|
55
62
|
perf,
|
|
56
63
|
publishPendingPricing,
|
|
57
64
|
readAgentCacheInitialization,
|
|
58
65
|
readAgentLastFullSyncAt,
|
|
66
|
+
readCachedSessions,
|
|
59
67
|
readPendingSearchIndexMaintenance,
|
|
60
68
|
refreshPricingCache,
|
|
61
69
|
scanSessions,
|
|
@@ -65,7 +73,7 @@ import {
|
|
|
65
73
|
startOfCalendarDay,
|
|
66
74
|
upsertBookmark,
|
|
67
75
|
upsertSessionAlias
|
|
68
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-PSHZZITB.js";
|
|
69
77
|
|
|
70
78
|
// src/index.ts
|
|
71
79
|
import { defineCommand, runMain } from "citty";
|
|
@@ -317,136 +325,92 @@ function paginateSessionSnapshot(items, request) {
|
|
|
317
325
|
};
|
|
318
326
|
}
|
|
319
327
|
|
|
320
|
-
// src/api/
|
|
321
|
-
function aliasKey(reference) {
|
|
322
|
-
return `${reference.agentName.toLowerCase()}\0${reference.sessionId}`;
|
|
323
|
-
}
|
|
324
|
-
function loadAliasMap() {
|
|
325
|
-
try {
|
|
326
|
-
return new Map(listSessionAliases().map((alias) => [aliasKey(alias.reference), alias]));
|
|
327
|
-
} catch (error) {
|
|
328
|
-
if (!(error instanceof StateStorageUnavailableError)) {
|
|
329
|
-
appLogger.warn("api.session_aliases.load_failed", {
|
|
330
|
-
error: error instanceof Error ? error.message : String(error)
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
return /* @__PURE__ */ new Map();
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
function buildAliasView() {
|
|
337
|
-
const aliases = loadAliasMap();
|
|
338
|
-
return {
|
|
339
|
-
size: aliases.size,
|
|
340
|
-
get: (reference) => aliases.get(aliasKey(reference))?.alias,
|
|
341
|
-
decorate(record, reference) {
|
|
342
|
-
const alias = aliases.get(aliasKey(reference))?.alias;
|
|
343
|
-
return alias ? { ...record, display_title: alias } : record;
|
|
344
|
-
},
|
|
345
|
-
entries: () => aliases.values()
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
var cachedView = null;
|
|
349
|
-
function loadAliasView() {
|
|
350
|
-
return cachedView ??= buildAliasView();
|
|
351
|
-
}
|
|
352
|
-
function invalidateAliasView() {
|
|
353
|
-
cachedView = null;
|
|
354
|
-
}
|
|
355
|
-
function decorateBookmark(bookmark, aliases) {
|
|
356
|
-
if (bookmark.availability === "available") {
|
|
357
|
-
return {
|
|
358
|
-
...bookmark,
|
|
359
|
-
session: aliases.decorate(bookmark.session, bookmark.reference)
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
const displayTitle = aliases.get(bookmark.reference);
|
|
363
|
-
return displayTitle ? { ...bookmark, display_title: displayTitle } : bookmark;
|
|
364
|
-
}
|
|
365
|
-
function decorateFileActivity(activity, aliases) {
|
|
366
|
-
return {
|
|
367
|
-
...activity,
|
|
368
|
-
session: aliases.decorate(activity.session, activity.reference)
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
function findAliasSearchResults(query, options, scanResult, aliases) {
|
|
372
|
-
const search = mergeSearchQueryOptions(query, options);
|
|
373
|
-
const needle = search.text.trim().toLowerCase();
|
|
374
|
-
if (!needle || aliases.size === 0) return [];
|
|
375
|
-
const sessionsByAgent = /* @__PURE__ */ new Map();
|
|
376
|
-
const lookupSession = (agentName, sessionId) => {
|
|
377
|
-
let byId = sessionsByAgent.get(agentName);
|
|
378
|
-
if (!byId) {
|
|
379
|
-
byId = new Map((scanResult.byAgent[agentName] ?? []).map((item) => [item.id, item]));
|
|
380
|
-
sessionsByAgent.set(agentName, byId);
|
|
381
|
-
}
|
|
382
|
-
return byId.get(sessionId);
|
|
383
|
-
};
|
|
384
|
-
const results = [];
|
|
385
|
-
for (const alias of aliases.entries()) {
|
|
386
|
-
if (!alias.alias.toLowerCase().includes(needle)) continue;
|
|
387
|
-
const { agentName, sessionId } = alias.reference;
|
|
388
|
-
const session = lookupSession(agentName, sessionId);
|
|
389
|
-
if (!session) continue;
|
|
390
|
-
results.push({
|
|
391
|
-
reference: alias.reference,
|
|
392
|
-
session: aliases.decorate(session, alias.reference),
|
|
393
|
-
snippet: `Alias \xB7 ${session.directory}`,
|
|
394
|
-
snippetHighlights: [],
|
|
395
|
-
matchType: "title"
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
return filterSessionSearchCandidates(results, search.options, scanResult.sessions).sort(
|
|
399
|
-
(a, b) => getSessionActivityTime(b.session) - getSessionActivityTime(a.session)
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src/api/handlers.ts
|
|
404
|
-
var KNOWN_AGENT_NAMES = getAgentInfoMap({}).map((agent) => agent.name);
|
|
405
|
-
var KNOWN_AGENT_NAME_SET = new Set(KNOWN_AGENT_NAMES);
|
|
406
|
-
function reportInvalidQueryParameter(endpoint, parameter, validationOutcome) {
|
|
407
|
-
appLogger.warn("api.query_parameter.invalid", {
|
|
408
|
-
endpoint,
|
|
409
|
-
parameter,
|
|
410
|
-
validation_outcome: validationOutcome
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
function parseDateWindowRequest(c, endpoint, defaults) {
|
|
414
|
-
const outcome = parseDateWindow(searchParams(c), defaults);
|
|
415
|
-
if (outcome.kind === "valid") return outcome;
|
|
416
|
-
reportInvalidQueryParameter(endpoint, outcome.parameter, "rejected");
|
|
417
|
-
return {
|
|
418
|
-
kind: "rejected",
|
|
419
|
-
response: c.json({ error: `${outcome.parameter} ${outcome.error}` }, 400)
|
|
420
|
-
};
|
|
421
|
-
}
|
|
328
|
+
// src/api/snapshot-aggregation.ts
|
|
422
329
|
var SNAPSHOT_AGGREGATION_CACHE_LIMIT = 64;
|
|
423
330
|
var snapshotAggregationCaches = /* @__PURE__ */ new WeakMap();
|
|
424
|
-
function
|
|
331
|
+
function getSnapshotAggregationCache(source, sessions) {
|
|
425
332
|
let cache = snapshotAggregationCaches.get(source);
|
|
426
333
|
if (!cache || cache.sessions !== sessions) {
|
|
427
334
|
cache = { sessions, values: /* @__PURE__ */ new Map() };
|
|
428
335
|
snapshotAggregationCaches.set(source, cache);
|
|
429
336
|
}
|
|
337
|
+
return cache;
|
|
338
|
+
}
|
|
339
|
+
function getSnapshotAggregation(source, sessions, key, build, onCacheState) {
|
|
340
|
+
const cache = getSnapshotAggregationCache(source, sessions);
|
|
430
341
|
const cacheKey = JSON.stringify(key);
|
|
431
|
-
if (cache.values.has(cacheKey))
|
|
342
|
+
if (cache.values.has(cacheKey)) {
|
|
343
|
+
const cached = cache.values.get(cacheKey);
|
|
344
|
+
cache.values.delete(cacheKey);
|
|
345
|
+
cache.values.set(cacheKey, cached);
|
|
346
|
+
onCacheState?.("hit");
|
|
347
|
+
return cached;
|
|
348
|
+
}
|
|
432
349
|
const value = build();
|
|
433
350
|
if (cache.values.size >= SNAPSHOT_AGGREGATION_CACHE_LIMIT) {
|
|
434
351
|
const oldestKey = cache.values.keys().next().value;
|
|
435
352
|
if (oldestKey != null) cache.values.delete(oldestKey);
|
|
436
353
|
}
|
|
437
354
|
cache.values.set(cacheKey, value);
|
|
355
|
+
onCacheState?.("miss");
|
|
438
356
|
return value;
|
|
439
357
|
}
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
358
|
+
function getSnapshotSessionTree(source, sessions) {
|
|
359
|
+
const cache = getSnapshotAggregationCache(source, sessions);
|
|
360
|
+
return cache.sessionTree ??= buildSessionTree(sessions);
|
|
361
|
+
}
|
|
362
|
+
function getDashboardCostFacts(source, sessions, from, to, cacheTo, analyticsRevision) {
|
|
363
|
+
const build = () => listDashboardCostFacts({ from, to });
|
|
364
|
+
if (analyticsRevision === null) return build();
|
|
365
|
+
return getSnapshotAggregation(
|
|
366
|
+
source,
|
|
367
|
+
sessions,
|
|
368
|
+
["dashboard-cost-facts", from, cacheTo, analyticsRevision],
|
|
369
|
+
build
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
function getDashboardStorageAggregation(source, sessions, scope, from, to, cacheTo, analyticsRevision) {
|
|
373
|
+
const build = () => ({
|
|
374
|
+
recentFileActivities: listFileActivity({
|
|
375
|
+
agent: scope.agent,
|
|
376
|
+
projectKind: scope.projectKind,
|
|
377
|
+
projectKey: scope.projectKey,
|
|
378
|
+
from,
|
|
379
|
+
to,
|
|
380
|
+
limit: 12
|
|
381
|
+
})
|
|
382
|
+
});
|
|
383
|
+
const startedAt = performance.now();
|
|
384
|
+
const log = (cache) => {
|
|
385
|
+
appLogger.info("api.dashboard.storage_aggregation", {
|
|
386
|
+
cache,
|
|
387
|
+
...analyticsRevision === null ? {} : { analytics_revision: analyticsRevision },
|
|
388
|
+
duration_ms: Math.round(performance.now() - startedAt)
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
if (analyticsRevision === null) {
|
|
392
|
+
const value = build();
|
|
393
|
+
log("unavailable");
|
|
394
|
+
return value;
|
|
448
395
|
}
|
|
396
|
+
return getSnapshotAggregation(
|
|
397
|
+
source,
|
|
398
|
+
sessions,
|
|
399
|
+
[
|
|
400
|
+
"dashboard-storage",
|
|
401
|
+
scope.agent,
|
|
402
|
+
scope.projectKind,
|
|
403
|
+
scope.projectKey,
|
|
404
|
+
from,
|
|
405
|
+
cacheTo,
|
|
406
|
+
analyticsRevision
|
|
407
|
+
],
|
|
408
|
+
build,
|
|
409
|
+
log
|
|
410
|
+
);
|
|
449
411
|
}
|
|
412
|
+
|
|
413
|
+
// src/api/request-payloads.ts
|
|
450
414
|
function isRecord(value) {
|
|
451
415
|
return typeof value === "object" && value !== null;
|
|
452
416
|
}
|
|
@@ -496,23 +460,8 @@ function sanitizeClientLogData(value) {
|
|
|
496
460
|
})
|
|
497
461
|
);
|
|
498
462
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
model_usage: _modelUsage,
|
|
502
|
-
project_identity_resolver_revision: _resolverRevision,
|
|
503
|
-
project_identity_input_signature: _identityInputSignature,
|
|
504
|
-
smart_tags_source_updated_at: _smartTagsSourceUpdatedAt,
|
|
505
|
-
smart_tags_classifier_revision: _smartTagsClassifierRevision,
|
|
506
|
-
...item
|
|
507
|
-
} = session;
|
|
508
|
-
return item;
|
|
509
|
-
}
|
|
510
|
-
function getSessionHeadReference(session) {
|
|
511
|
-
return {
|
|
512
|
-
agentName: getSessionAgentKey(session),
|
|
513
|
-
sessionId: session.id
|
|
514
|
-
};
|
|
515
|
-
}
|
|
463
|
+
|
|
464
|
+
// src/api/session-detail-stream.ts
|
|
516
465
|
var SESSION_DETAIL_STREAM_BATCH_CHARS = 64 * 1024;
|
|
517
466
|
function createSessionDetailJsonResponse(data, messages) {
|
|
518
467
|
const encoder = new TextEncoder();
|
|
@@ -565,6 +514,193 @@ function createSessionDetailJsonResponse(data, messages) {
|
|
|
565
514
|
{ headers: { "Content-Type": "application/json; charset=UTF-8" } }
|
|
566
515
|
);
|
|
567
516
|
}
|
|
517
|
+
|
|
518
|
+
// src/api/search-result-merge.ts
|
|
519
|
+
function withParentContext(results, getSessionTree, aliases) {
|
|
520
|
+
if (!results.some((result) => result.session.parent_reference)) return results;
|
|
521
|
+
const byRouteKey = getSessionTree().byRouteKey;
|
|
522
|
+
return results.map((result) => {
|
|
523
|
+
const parentReference = result.session.parent_reference;
|
|
524
|
+
if (!parentReference) return result;
|
|
525
|
+
const parent = byRouteKey.get(formatSessionReference(parentReference))?.session;
|
|
526
|
+
if (!parent) return result;
|
|
527
|
+
const reference = normalizeSessionReference(parentReference);
|
|
528
|
+
return { ...result, parent: { reference, title: aliases.get(reference) ?? parent.title } };
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
var ALIAS_SEARCH_RESULT_SHARE = 0.25;
|
|
532
|
+
function searchResultKey(result) {
|
|
533
|
+
return `${result.reference.agentName}\0${result.reference.sessionId}`;
|
|
534
|
+
}
|
|
535
|
+
function uniqueSearchResults(results) {
|
|
536
|
+
const seen = /* @__PURE__ */ new Set();
|
|
537
|
+
return results.filter((result) => {
|
|
538
|
+
const key = searchResultKey(result);
|
|
539
|
+
if (seen.has(key)) return false;
|
|
540
|
+
seen.add(key);
|
|
541
|
+
return true;
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
function mergeAliasSearchResults(rankedResults, aliasResults, limit) {
|
|
545
|
+
if (limit <= 0) return [];
|
|
546
|
+
const ranked = uniqueSearchResults(rankedResults);
|
|
547
|
+
const rankedKeys = new Set(ranked.map(searchResultKey));
|
|
548
|
+
const aliases = uniqueSearchResults(aliasResults).filter(
|
|
549
|
+
(result) => !rankedKeys.has(searchResultKey(result))
|
|
550
|
+
);
|
|
551
|
+
if (ranked.length === 0) return aliases.slice(0, limit);
|
|
552
|
+
if (aliases.length === 0) return ranked.slice(0, limit);
|
|
553
|
+
const aliasQuota = Math.min(
|
|
554
|
+
limit - 1,
|
|
555
|
+
Math.max(1, Math.floor(limit * ALIAS_SEARCH_RESULT_SHARE))
|
|
556
|
+
);
|
|
557
|
+
const rankedQuota = limit - aliasQuota;
|
|
558
|
+
return [
|
|
559
|
+
...ranked.slice(0, rankedQuota),
|
|
560
|
+
...aliases.slice(0, aliasQuota),
|
|
561
|
+
...ranked.slice(rankedQuota),
|
|
562
|
+
...aliases.slice(aliasQuota)
|
|
563
|
+
].slice(0, limit);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/api/session-aliases-view.ts
|
|
567
|
+
function aliasKey(reference) {
|
|
568
|
+
return `${reference.agentName.toLowerCase()}\0${reference.sessionId}`;
|
|
569
|
+
}
|
|
570
|
+
function loadAliasMap() {
|
|
571
|
+
try {
|
|
572
|
+
return new Map(listSessionAliases().map((alias) => [aliasKey(alias.reference), alias]));
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (!(error instanceof StateStorageUnavailableError)) {
|
|
575
|
+
appLogger.warn("api.session_aliases.load_failed", {
|
|
576
|
+
error: error instanceof Error ? error.message : String(error)
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return /* @__PURE__ */ new Map();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function buildAliasView() {
|
|
583
|
+
const aliases = loadAliasMap();
|
|
584
|
+
return {
|
|
585
|
+
size: aliases.size,
|
|
586
|
+
get: (reference) => aliases.get(aliasKey(reference))?.alias,
|
|
587
|
+
decorate(record, reference) {
|
|
588
|
+
const alias = aliases.get(aliasKey(reference))?.alias;
|
|
589
|
+
return alias ? { ...record, display_title: alias } : record;
|
|
590
|
+
},
|
|
591
|
+
entries: () => aliases.values()
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
var cachedView = null;
|
|
595
|
+
function loadAliasView() {
|
|
596
|
+
return cachedView ??= buildAliasView();
|
|
597
|
+
}
|
|
598
|
+
function invalidateAliasView() {
|
|
599
|
+
cachedView = null;
|
|
600
|
+
}
|
|
601
|
+
function decorateBookmark(bookmark, aliases) {
|
|
602
|
+
if (bookmark.availability === "available") {
|
|
603
|
+
return {
|
|
604
|
+
...bookmark,
|
|
605
|
+
session: aliases.decorate(bookmark.session, bookmark.reference)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const displayTitle = aliases.get(bookmark.reference);
|
|
609
|
+
return displayTitle ? { ...bookmark, display_title: displayTitle } : bookmark;
|
|
610
|
+
}
|
|
611
|
+
function decorateFileActivity(activity, aliases) {
|
|
612
|
+
return {
|
|
613
|
+
...activity,
|
|
614
|
+
session: aliases.decorate(activity.session, activity.reference)
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function findAliasSearchResults(query, options, scanResult, aliases, context = {}) {
|
|
618
|
+
const search = mergeSearchQueryOptions(query, options);
|
|
619
|
+
const needle = search.text.trim().toLowerCase();
|
|
620
|
+
if (!needle || aliases.size === 0) return [];
|
|
621
|
+
const sessionsByAgent = /* @__PURE__ */ new Map();
|
|
622
|
+
const lookupSession = (agentName, sessionId) => {
|
|
623
|
+
let byId = sessionsByAgent.get(agentName);
|
|
624
|
+
if (!byId) {
|
|
625
|
+
byId = new Map((scanResult.byAgent[agentName] ?? []).map((item) => [item.id, item]));
|
|
626
|
+
sessionsByAgent.set(agentName, byId);
|
|
627
|
+
}
|
|
628
|
+
return byId.get(sessionId);
|
|
629
|
+
};
|
|
630
|
+
const results = [];
|
|
631
|
+
for (const alias of aliases.entries()) {
|
|
632
|
+
if (!alias.alias.toLowerCase().includes(needle)) continue;
|
|
633
|
+
const { agentName, sessionId } = alias.reference;
|
|
634
|
+
const session = lookupSession(agentName, sessionId);
|
|
635
|
+
if (!session) continue;
|
|
636
|
+
results.push({
|
|
637
|
+
reference: alias.reference,
|
|
638
|
+
session: aliases.decorate(session, alias.reference),
|
|
639
|
+
snippet: `Alias \xB7 ${session.directory}`,
|
|
640
|
+
snippetHighlights: [],
|
|
641
|
+
matchType: "title"
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return filterSessionSearchCandidates(results, search.options, {
|
|
645
|
+
sessionSnapshot: scanResult.sessions,
|
|
646
|
+
sessionTree: context.sessionTree
|
|
647
|
+
}).sort((a, b) => getSessionActivityTime(b.session) - getSessionActivityTime(a.session));
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/api/handlers.ts
|
|
651
|
+
var KNOWN_AGENT_NAMES = getAgentInfoMap({}).map((agent) => agent.name);
|
|
652
|
+
var KNOWN_AGENT_NAME_SET = new Set(KNOWN_AGENT_NAMES);
|
|
653
|
+
async function resolveProjectScope(cwd, sessions, resolver) {
|
|
654
|
+
const normalizedCwd = normalizeProjectDirectory(cwd);
|
|
655
|
+
const matchingSession = sessions.find(
|
|
656
|
+
(session) => normalizeProjectDirectory(session.directory) === normalizedCwd && session.project_identity != null && session.project_identity_resolver_revision === PROJECT_IDENTITY_RESOLVER_REVISION && Boolean(session.project_identity_input_signature)
|
|
657
|
+
);
|
|
658
|
+
if (matchingSession?.project_identity) {
|
|
659
|
+
return createProjectScopeMatcherFromIdentity(cwd, matchingSession.project_identity);
|
|
660
|
+
}
|
|
661
|
+
if (!resolver) throw new Error("Project identity resolver is unavailable");
|
|
662
|
+
const projection = await resolver.resolve(cwd);
|
|
663
|
+
return createProjectScopeMatcherFromIdentity(cwd, projection.identity);
|
|
664
|
+
}
|
|
665
|
+
function reportProjectScopeResolutionFailure(endpoint, error) {
|
|
666
|
+
appLogger.warn("api.project_scope.unavailable", {
|
|
667
|
+
endpoint,
|
|
668
|
+
error: error instanceof Error ? error.message : String(error)
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
function reportInvalidQueryParameter(endpoint, parameter, validationOutcome) {
|
|
672
|
+
appLogger.warn("api.query_parameter.invalid", {
|
|
673
|
+
endpoint,
|
|
674
|
+
parameter,
|
|
675
|
+
validation_outcome: validationOutcome
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function parseDateWindowRequest(c, endpoint, defaults) {
|
|
679
|
+
const outcome = parseDateWindow(searchParams(c), defaults);
|
|
680
|
+
if (outcome.kind === "valid") return outcome;
|
|
681
|
+
reportInvalidQueryParameter(endpoint, outcome.parameter, "rejected");
|
|
682
|
+
return {
|
|
683
|
+
kind: "rejected",
|
|
684
|
+
response: c.json({ error: `${outcome.parameter} ${outcome.error}` }, 400)
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function toSessionListItem(session) {
|
|
688
|
+
const {
|
|
689
|
+
model_usage: _modelUsage,
|
|
690
|
+
project_identity_resolver_revision: _resolverRevision,
|
|
691
|
+
project_identity_input_signature: _identityInputSignature,
|
|
692
|
+
smart_tags_source_updated_at: _smartTagsSourceUpdatedAt,
|
|
693
|
+
smart_tags_classifier_revision: _smartTagsClassifierRevision,
|
|
694
|
+
...item
|
|
695
|
+
} = session;
|
|
696
|
+
return item;
|
|
697
|
+
}
|
|
698
|
+
function getSessionHeadReference(session) {
|
|
699
|
+
return {
|
|
700
|
+
agentName: getSessionAgentKey(session),
|
|
701
|
+
sessionId: session.id
|
|
702
|
+
};
|
|
703
|
+
}
|
|
568
704
|
function handleGetConfig(c, defaults) {
|
|
569
705
|
const payload = {
|
|
570
706
|
window: {
|
|
@@ -604,21 +740,30 @@ function handleGetProjects(c, scanSource, defaults = {}) {
|
|
|
604
740
|
const window = parseDateWindowRequest(c, "projects", defaults);
|
|
605
741
|
if (window.kind === "rejected") return window.response;
|
|
606
742
|
const { from, to } = window;
|
|
743
|
+
const analyticsRevision = getAnalyticsRevision();
|
|
607
744
|
const projects = getSnapshotAggregation(
|
|
608
745
|
scanSource,
|
|
609
746
|
scanResult.sessions,
|
|
610
|
-
["projects", from, to],
|
|
747
|
+
["projects", from, to, analyticsRevision],
|
|
611
748
|
() => {
|
|
612
|
-
const tree =
|
|
613
|
-
const
|
|
749
|
+
const tree = getSnapshotSessionTree(scanSource, scanResult.sessions);
|
|
750
|
+
const costFacts = listDashboardCostFacts({ from, to, includeModelCosts: false });
|
|
614
751
|
return {
|
|
615
|
-
projects: attachProjectMetricsFromTree(
|
|
752
|
+
projects: attachProjectMetricsFromTree(
|
|
753
|
+
listCachedProjectGroups(scanResult.sessions),
|
|
754
|
+
tree,
|
|
755
|
+
from,
|
|
756
|
+
to,
|
|
757
|
+
costFacts
|
|
758
|
+
).filter(
|
|
759
|
+
(project) => project.sessionCount > 0 || project.messages > 0 || project.tokens > 0 || project.cost > 0
|
|
760
|
+
)
|
|
616
761
|
};
|
|
617
762
|
}
|
|
618
763
|
);
|
|
619
764
|
return c.json(projects);
|
|
620
765
|
}
|
|
621
|
-
function handleGetSessions(c, scanSource, defaults = {}) {
|
|
766
|
+
async function handleGetSessions(c, scanSource, defaults = {}, resolver) {
|
|
622
767
|
const scanResult = scanSource.getSnapshot();
|
|
623
768
|
const params = searchParams(c);
|
|
624
769
|
const paginationRequested = params.has("limit") || params.has("cursor");
|
|
@@ -628,7 +773,7 @@ function handleGetSessions(c, scanSource, defaults = {}) {
|
|
|
628
773
|
return c.json({ error: sessionQuery.limit.error }, 400);
|
|
629
774
|
}
|
|
630
775
|
const q = c.req.query("q")?.toLowerCase();
|
|
631
|
-
const cwd = c.req.query("cwd");
|
|
776
|
+
const cwd = optionalQueryValue(c.req.query("cwd"));
|
|
632
777
|
const projectIdentity = parseProjectIdentityFilter(
|
|
633
778
|
c.req.query("projectKind"),
|
|
634
779
|
c.req.query("projectKey")
|
|
@@ -640,6 +785,15 @@ function handleGetSessions(c, scanSource, defaults = {}) {
|
|
|
640
785
|
const window = parseDateWindowRequest(c, "sessions", defaults);
|
|
641
786
|
if (window.kind === "rejected") return window.response;
|
|
642
787
|
const { from, to } = window;
|
|
788
|
+
let projectScope;
|
|
789
|
+
if (cwd && !projectIdentity) {
|
|
790
|
+
try {
|
|
791
|
+
projectScope = await resolveProjectScope(cwd, scanResult.sessions, resolver);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
reportProjectScopeResolutionFailure("sessions", error);
|
|
794
|
+
return c.json({ error: "Project scope unavailable" }, 503);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
643
797
|
if (sessionQuery.agent.kind === "unknown") {
|
|
644
798
|
reportInvalidQueryParameter("sessions", "agent", "empty_result");
|
|
645
799
|
}
|
|
@@ -647,15 +801,25 @@ function handleGetSessions(c, scanSource, defaults = {}) {
|
|
|
647
801
|
let sessions = getSnapshotAggregation(
|
|
648
802
|
scanSource,
|
|
649
803
|
scanResult.sessions,
|
|
650
|
-
[
|
|
804
|
+
[
|
|
805
|
+
"sessions",
|
|
806
|
+
agentFilter,
|
|
807
|
+
projectIdentity?.kind,
|
|
808
|
+
projectIdentity?.key,
|
|
809
|
+
projectScope?.identity.kind,
|
|
810
|
+
projectScope?.identity.key,
|
|
811
|
+
projectScope?.path,
|
|
812
|
+
tag,
|
|
813
|
+
from,
|
|
814
|
+
to
|
|
815
|
+
],
|
|
651
816
|
() => {
|
|
652
817
|
let filtered = sessionQuery.agent.kind === "all" ? scanResult.sessions : sessionQuery.agent.kind === "known" ? scanResult.byAgent[sessionQuery.agent.agentName] ?? [] : [];
|
|
653
818
|
if (projectIdentity) {
|
|
654
819
|
filtered = filtered.filter(
|
|
655
820
|
(session) => matchesProjectIdentity(session.project_identity, projectIdentity)
|
|
656
821
|
);
|
|
657
|
-
} else if (
|
|
658
|
-
const projectScope = createProjectScopeMatcher(cwd);
|
|
822
|
+
} else if (projectScope) {
|
|
659
823
|
filtered = filtered.filter((session) => matchesProjectScope(session, projectScope));
|
|
660
824
|
}
|
|
661
825
|
filtered = filterSessionsByActivityWindow(filtered, from, to);
|
|
@@ -697,58 +861,7 @@ function handleGetSessions(c, scanSource, defaults = {}) {
|
|
|
697
861
|
)
|
|
698
862
|
});
|
|
699
863
|
}
|
|
700
|
-
function
|
|
701
|
-
if (!results.some((result) => result.session.parent_reference)) return results;
|
|
702
|
-
const byRouteKey = new Map(
|
|
703
|
-
sessions.map((session) => [
|
|
704
|
-
getSessionRouteKey(getSessionAgentKey(session), session.id),
|
|
705
|
-
session
|
|
706
|
-
])
|
|
707
|
-
);
|
|
708
|
-
return results.map((result) => {
|
|
709
|
-
const parentReference = result.session.parent_reference;
|
|
710
|
-
if (!parentReference) return result;
|
|
711
|
-
const parent = byRouteKey.get(formatSessionReference(parentReference));
|
|
712
|
-
if (!parent) return result;
|
|
713
|
-
const reference = normalizeSessionReference(parentReference);
|
|
714
|
-
return { ...result, parent: { reference, title: aliases.get(reference) ?? parent.title } };
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
var ALIAS_SEARCH_RESULT_SHARE = 0.25;
|
|
718
|
-
function searchResultKey(result) {
|
|
719
|
-
return `${result.reference.agentName}\0${result.reference.sessionId}`;
|
|
720
|
-
}
|
|
721
|
-
function uniqueSearchResults(results) {
|
|
722
|
-
const seen = /* @__PURE__ */ new Set();
|
|
723
|
-
return results.filter((result) => {
|
|
724
|
-
const key = searchResultKey(result);
|
|
725
|
-
if (seen.has(key)) return false;
|
|
726
|
-
seen.add(key);
|
|
727
|
-
return true;
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
function mergeAliasSearchResults(rankedResults, aliasResults, limit) {
|
|
731
|
-
if (limit <= 0) return [];
|
|
732
|
-
const ranked = uniqueSearchResults(rankedResults);
|
|
733
|
-
const rankedKeys = new Set(ranked.map(searchResultKey));
|
|
734
|
-
const aliases = uniqueSearchResults(aliasResults).filter(
|
|
735
|
-
(result) => !rankedKeys.has(searchResultKey(result))
|
|
736
|
-
);
|
|
737
|
-
if (ranked.length === 0) return aliases.slice(0, limit);
|
|
738
|
-
if (aliases.length === 0) return ranked.slice(0, limit);
|
|
739
|
-
const aliasQuota = Math.min(
|
|
740
|
-
limit - 1,
|
|
741
|
-
Math.max(1, Math.floor(limit * ALIAS_SEARCH_RESULT_SHARE))
|
|
742
|
-
);
|
|
743
|
-
const rankedQuota = limit - aliasQuota;
|
|
744
|
-
return [
|
|
745
|
-
...ranked.slice(0, rankedQuota),
|
|
746
|
-
...aliases.slice(0, aliasQuota),
|
|
747
|
-
...ranked.slice(rankedQuota),
|
|
748
|
-
...aliases.slice(aliasQuota)
|
|
749
|
-
].slice(0, limit);
|
|
750
|
-
}
|
|
751
|
-
function handleSearchSessions(c, scanSource, defaults = {}) {
|
|
864
|
+
async function handleSearchSessions(c, scanSource, defaults = {}, resolver) {
|
|
752
865
|
const query = c.req.query("q")?.trim() ?? "";
|
|
753
866
|
const scanResult = scanSource.getSnapshot();
|
|
754
867
|
const sessionQuery = parseSessionQuery(searchParams(c), KNOWN_AGENT_NAMES, SEARCH_LIMIT_POLICY);
|
|
@@ -769,7 +882,7 @@ function handleSearchSessions(c, scanSource, defaults = {}) {
|
|
|
769
882
|
reportInvalidQueryParameter("search", "agent", "empty_result");
|
|
770
883
|
return c.json({ results: [] });
|
|
771
884
|
}
|
|
772
|
-
const
|
|
885
|
+
const searchRequestOptions = parseSearchOptions(
|
|
773
886
|
c,
|
|
774
887
|
window,
|
|
775
888
|
{
|
|
@@ -778,18 +891,45 @@ function handleSearchSessions(c, scanSource, defaults = {}) {
|
|
|
778
891
|
},
|
|
779
892
|
projectIdentity
|
|
780
893
|
);
|
|
894
|
+
const cwd = getSearchProjectDirectory(query, searchRequestOptions);
|
|
895
|
+
let projectScope;
|
|
896
|
+
if (cwd) {
|
|
897
|
+
try {
|
|
898
|
+
projectScope = await resolveProjectScope(cwd, scanResult.sessions, resolver);
|
|
899
|
+
} catch (error) {
|
|
900
|
+
reportProjectScopeResolutionFailure("search", error);
|
|
901
|
+
return c.json({ error: "Project scope unavailable" }, 503);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const { cwd: _cwd, ...searchOptions } = searchRequestOptions;
|
|
905
|
+
const resolvedSearchOptions = projectScope ? { ...searchOptions, projectScope } : searchOptions;
|
|
781
906
|
const aliases = loadAliasView();
|
|
782
|
-
|
|
907
|
+
let sessionTree;
|
|
908
|
+
const getSessionTree = () => sessionTree ??= getSnapshotSessionTree(scanSource, scanResult.sessions);
|
|
909
|
+
const effectiveSearchOptions = mergeSearchQueryOptions(query, resolvedSearchOptions).options;
|
|
910
|
+
const searchContext = effectiveSearchOptions.costMin != null || effectiveSearchOptions.costMax != null ? { sessionTree: getSessionTree() } : void 0;
|
|
911
|
+
const searchResults = searchContext ? executeSessionSearch(query, resolvedSearchOptions, scanResult, searchContext) : executeSessionSearch(query, resolvedSearchOptions, scanResult);
|
|
912
|
+
const results = searchResults.map((result) => ({
|
|
783
913
|
...result,
|
|
784
914
|
session: aliases.decorate(result.session, result.reference)
|
|
785
915
|
}));
|
|
786
|
-
const aliasResults = findAliasSearchResults(
|
|
787
|
-
|
|
916
|
+
const aliasResults = findAliasSearchResults(
|
|
917
|
+
query,
|
|
918
|
+
resolvedSearchOptions,
|
|
919
|
+
scanResult,
|
|
920
|
+
aliases,
|
|
921
|
+
searchContext
|
|
922
|
+
);
|
|
923
|
+
const mergedResults = mergeAliasSearchResults(
|
|
924
|
+
results,
|
|
925
|
+
aliasResults,
|
|
926
|
+
resolvedSearchOptions.limit ?? 50
|
|
927
|
+
);
|
|
788
928
|
return c.json({
|
|
789
|
-
results: withParentContext(mergedResults,
|
|
929
|
+
results: withParentContext(mergedResults, getSessionTree, aliases)
|
|
790
930
|
});
|
|
791
931
|
}
|
|
792
|
-
function handleGetFileActivity(c, defaults = {}) {
|
|
932
|
+
async function handleGetFileActivity(c, defaults = {}, resolver) {
|
|
793
933
|
const sessionQuery = parseSessionQuery(
|
|
794
934
|
searchParams(c),
|
|
795
935
|
KNOWN_AGENT_NAMES,
|
|
@@ -812,6 +952,16 @@ function handleGetFileActivity(c, defaults = {}) {
|
|
|
812
952
|
reportInvalidQueryParameter("file-activity", "agent", "empty_result");
|
|
813
953
|
return c.json({ activity: [] });
|
|
814
954
|
}
|
|
955
|
+
const cwd = optionalQueryValue(c.req.query("cwd"));
|
|
956
|
+
let projectScope;
|
|
957
|
+
if (cwd) {
|
|
958
|
+
try {
|
|
959
|
+
projectScope = await resolveProjectScope(cwd, [], resolver);
|
|
960
|
+
} catch (error) {
|
|
961
|
+
reportProjectScopeResolutionFailure("file-activity", error);
|
|
962
|
+
return c.json({ error: "Project scope unavailable" }, 503);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
815
965
|
const aliases = loadAliasView();
|
|
816
966
|
return c.json({
|
|
817
967
|
activity: listFileActivity({
|
|
@@ -820,7 +970,7 @@ function handleGetFileActivity(c, defaults = {}) {
|
|
|
820
970
|
projectKind: projectIdentity?.kind,
|
|
821
971
|
projectKey: projectIdentity?.key,
|
|
822
972
|
project: optionalQueryValue(c.req.query("project")),
|
|
823
|
-
|
|
973
|
+
projectScope,
|
|
824
974
|
path: optionalQueryValue(c.req.query("path")),
|
|
825
975
|
kind: parseFileActivityKind(optionalQueryValue(c.req.query("kind"))),
|
|
826
976
|
from: window.from,
|
|
@@ -829,7 +979,7 @@ function handleGetFileActivity(c, defaults = {}) {
|
|
|
829
979
|
}).map((activity) => decorateFileActivity(activity, aliases))
|
|
830
980
|
});
|
|
831
981
|
}
|
|
832
|
-
async function handleGetSessionData(c, scanSource) {
|
|
982
|
+
async function handleGetSessionData(c, scanSource, resolver) {
|
|
833
983
|
const startedAt = performance.now();
|
|
834
984
|
const agentName = c.req.param("agent");
|
|
835
985
|
const sessionId = c.req.param("id");
|
|
@@ -845,7 +995,18 @@ async function handleGetSessionData(c, scanSource) {
|
|
|
845
995
|
sessionId
|
|
846
996
|
};
|
|
847
997
|
const messageCursor = optionalQueryValue(c.req.query("messageCursor"));
|
|
848
|
-
const
|
|
998
|
+
const materialize = (fallback) => materializeSessionDetailResponse(scanSource.getSnapshot(), reference, {
|
|
999
|
+
...messageCursor ? { messageCursor } : {},
|
|
1000
|
+
...fallback ? { projectIdentityFallback: fallback } : {}
|
|
1001
|
+
});
|
|
1002
|
+
let result = materialize();
|
|
1003
|
+
if (result.status === "needs-identity") {
|
|
1004
|
+
if (!resolver) throw new Error("Project identity resolver is unavailable");
|
|
1005
|
+
result = materialize((await resolver.resolve(result.directory)).identity);
|
|
1006
|
+
}
|
|
1007
|
+
if (result.status === "needs-identity") {
|
|
1008
|
+
throw new Error("Project identity fallback was not applied");
|
|
1009
|
+
}
|
|
849
1010
|
if (result.status === "unknown-agent") {
|
|
850
1011
|
return c.json({ error: `Unknown agent: ${agentName}` }, 404);
|
|
851
1012
|
}
|
|
@@ -884,15 +1045,118 @@ async function handleGetSessionData(c, scanSource) {
|
|
|
884
1045
|
return c.json({ error: "Failed to load session" }, 500);
|
|
885
1046
|
}
|
|
886
1047
|
}
|
|
887
|
-
async function handlePostClientLog(c) {
|
|
888
|
-
const payload = await c.req.json().catch(() => null);
|
|
889
|
-
const rawEvent = payload?.event;
|
|
890
|
-
if (typeof rawEvent !== "string" || !rawEvent.trim()) {
|
|
891
|
-
return c.json({ ok: false }, 400);
|
|
1048
|
+
async function handlePostClientLog(c) {
|
|
1049
|
+
const payload = await c.req.json().catch(() => null);
|
|
1050
|
+
const rawEvent = payload?.event;
|
|
1051
|
+
if (typeof rawEvent !== "string" || !rawEvent.trim()) {
|
|
1052
|
+
return c.json({ ok: false }, 400);
|
|
1053
|
+
}
|
|
1054
|
+
const event = rawEvent.trim().replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 120);
|
|
1055
|
+
appLogger.info(`client.${event}`, sanitizeClientLogData(payload?.data));
|
|
1056
|
+
return c.json({ ok: true });
|
|
1057
|
+
}
|
|
1058
|
+
function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
1059
|
+
const scanResult = scanSource.getSnapshot();
|
|
1060
|
+
const projectIdentity = parseProjectIdentityFilter(
|
|
1061
|
+
c.req.query("projectKind"),
|
|
1062
|
+
c.req.query("projectKey")
|
|
1063
|
+
);
|
|
1064
|
+
if (projectIdentity === null) {
|
|
1065
|
+
return c.json({ error: "projectKind and projectKey must form a valid project identity" }, 400);
|
|
1066
|
+
}
|
|
1067
|
+
const dateWindow = parseDateWindowRequest(c, "dashboard", defaults);
|
|
1068
|
+
if (dateWindow.kind === "rejected") return dateWindow.response;
|
|
1069
|
+
const { from, to, days } = resolveTimeWindow({
|
|
1070
|
+
mode: "dashboard",
|
|
1071
|
+
query: {
|
|
1072
|
+
days: c.req.query("days"),
|
|
1073
|
+
from: c.req.query("from"),
|
|
1074
|
+
to: c.req.query("to")
|
|
1075
|
+
},
|
|
1076
|
+
defaults
|
|
1077
|
+
});
|
|
1078
|
+
const scope = {
|
|
1079
|
+
agent: optionalQueryValue(c.req.query("agent"))?.toLowerCase(),
|
|
1080
|
+
projectKind: projectIdentity?.kind,
|
|
1081
|
+
projectKey: projectIdentity?.key
|
|
1082
|
+
};
|
|
1083
|
+
const compare = from == null ? void 0 : { from: addCalendarDays(from, -(days ?? countCalendarDays(from, to))), to: from - 1 };
|
|
1084
|
+
const fixedTo = dateWindow.to;
|
|
1085
|
+
const cacheTo = fixedTo ?? startOfCalendarDay(to);
|
|
1086
|
+
const analyticsRevision = getAnalyticsRevision();
|
|
1087
|
+
const costFacts = getDashboardCostFacts(
|
|
1088
|
+
scanSource,
|
|
1089
|
+
scanResult.sessions,
|
|
1090
|
+
compare?.from ?? from,
|
|
1091
|
+
to,
|
|
1092
|
+
cacheTo,
|
|
1093
|
+
analyticsRevision
|
|
1094
|
+
);
|
|
1095
|
+
const aggregate = getSnapshotAggregation(
|
|
1096
|
+
scanSource,
|
|
1097
|
+
scanResult.sessions,
|
|
1098
|
+
[
|
|
1099
|
+
"dashboard",
|
|
1100
|
+
scope.agent,
|
|
1101
|
+
scope.projectKind,
|
|
1102
|
+
scope.projectKey,
|
|
1103
|
+
from,
|
|
1104
|
+
cacheTo,
|
|
1105
|
+
compare?.from,
|
|
1106
|
+
compare?.to,
|
|
1107
|
+
analyticsRevision
|
|
1108
|
+
],
|
|
1109
|
+
() => {
|
|
1110
|
+
const agentInfo = getAgentInfoMap({});
|
|
1111
|
+
const agentInfoMap = new Map(agentInfo.map((agent) => [agent.name, agent]));
|
|
1112
|
+
return buildDashboard(scanResult.sessions, {
|
|
1113
|
+
byAgentNames: Object.keys(scanResult.byAgent),
|
|
1114
|
+
scope,
|
|
1115
|
+
from,
|
|
1116
|
+
to,
|
|
1117
|
+
agentInfoMap,
|
|
1118
|
+
compare,
|
|
1119
|
+
costFacts
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1123
|
+
const storageAggregation = getDashboardStorageAggregation(
|
|
1124
|
+
scanSource,
|
|
1125
|
+
scanResult.sessions,
|
|
1126
|
+
scope,
|
|
1127
|
+
from,
|
|
1128
|
+
to,
|
|
1129
|
+
cacheTo,
|
|
1130
|
+
analyticsRevision
|
|
1131
|
+
);
|
|
1132
|
+
const data = {
|
|
1133
|
+
...aggregate,
|
|
1134
|
+
...storageAggregation,
|
|
1135
|
+
window: { from, to, days, compareFrom: compare?.from, compareTo: compare?.to }
|
|
1136
|
+
};
|
|
1137
|
+
const aliases = loadAliasView();
|
|
1138
|
+
return c.json({
|
|
1139
|
+
...data,
|
|
1140
|
+
recentSessions: data.recentSessions.map((item) => ({
|
|
1141
|
+
...item,
|
|
1142
|
+
session: aliases.decorate(item.session, item.reference)
|
|
1143
|
+
})),
|
|
1144
|
+
recentFileActivities: data.recentFileActivities.map(
|
|
1145
|
+
(activity) => decorateFileActivity(activity, aliases)
|
|
1146
|
+
)
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/api/bookmark-handlers.ts
|
|
1151
|
+
function withStorageErrors(handler, onUnavailable) {
|
|
1152
|
+
try {
|
|
1153
|
+
return handler();
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
if (error instanceof StateStorageUnavailableError) {
|
|
1156
|
+
return onUnavailable();
|
|
1157
|
+
}
|
|
1158
|
+
throw error;
|
|
892
1159
|
}
|
|
893
|
-
const event = rawEvent.trim().replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 120);
|
|
894
|
-
appLogger.info(`client.${event}`, sanitizeClientLogData(payload?.data));
|
|
895
|
-
return c.json({ ok: true });
|
|
896
1160
|
}
|
|
897
1161
|
function materializeStoredBookmarks(scanSource, bookmarks, aliases) {
|
|
898
1162
|
const snapshot = scanSource.getSnapshot();
|
|
@@ -921,11 +1185,17 @@ function handleGetBookmarks(c, scanSource) {
|
|
|
921
1185
|
() => c.json({ bookmarks: [], storageAvailable: false })
|
|
922
1186
|
);
|
|
923
1187
|
}
|
|
1188
|
+
function isKnownAgentKey(agentKey) {
|
|
1189
|
+
return KNOWN_AGENT_NAME_SET.has(agentKey.trim().toLowerCase());
|
|
1190
|
+
}
|
|
924
1191
|
async function handlePutBookmark(c) {
|
|
925
1192
|
const payload = parseBookmarkReference(await c.req.json().catch(() => null));
|
|
926
1193
|
if (!payload) {
|
|
927
1194
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
928
1195
|
}
|
|
1196
|
+
if (!isKnownAgentKey(payload.agentName)) {
|
|
1197
|
+
return c.json({ error: `Unknown agent: ${payload.agentName}` }, 400);
|
|
1198
|
+
}
|
|
929
1199
|
return withStorageErrors(
|
|
930
1200
|
() => c.json({ bookmark: upsertBookmark(payload), storageAvailable: true }),
|
|
931
1201
|
() => c.json({ error: "Bookmark storage is unavailable" }, 503)
|
|
@@ -936,15 +1206,21 @@ async function handleImportBookmarks(c, scanSource) {
|
|
|
936
1206
|
if (!Array.isArray(payload)) {
|
|
937
1207
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
938
1208
|
}
|
|
939
|
-
const
|
|
940
|
-
if (
|
|
1209
|
+
const parsed = payload.map((entry) => parseBookmarkImport(entry)).filter((entry) => entry !== null);
|
|
1210
|
+
if (parsed.length !== payload.length) {
|
|
941
1211
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
942
1212
|
}
|
|
1213
|
+
const bookmarks = parsed.filter((entry) => isKnownAgentKey(entry.reference.agentName));
|
|
1214
|
+
const skippedUnknownAgents = parsed.length - bookmarks.length;
|
|
943
1215
|
return withStorageErrors(
|
|
944
1216
|
() => {
|
|
945
1217
|
const imported = importBookmarks(bookmarks);
|
|
946
1218
|
const views = materializeStoredBookmarks(scanSource, imported, loadAliasView());
|
|
947
|
-
return c.json({
|
|
1219
|
+
return c.json({
|
|
1220
|
+
bookmarks: views,
|
|
1221
|
+
storageAvailable: true,
|
|
1222
|
+
...skippedUnknownAgents > 0 ? { skippedUnknownAgents } : {}
|
|
1223
|
+
});
|
|
948
1224
|
},
|
|
949
1225
|
() => c.json({ error: "Bookmark storage is unavailable" }, 503)
|
|
950
1226
|
);
|
|
@@ -955,6 +1231,9 @@ function handleDeleteBookmark(c) {
|
|
|
955
1231
|
if (!agentKey || !sessionId) {
|
|
956
1232
|
return c.json({ error: "Missing bookmark identifier" }, 400);
|
|
957
1233
|
}
|
|
1234
|
+
if (!isKnownAgentKey(agentKey)) {
|
|
1235
|
+
return c.json({ error: `Unknown agent: ${agentKey}` }, 400);
|
|
1236
|
+
}
|
|
958
1237
|
return withStorageErrors(
|
|
959
1238
|
() => {
|
|
960
1239
|
deleteBookmark({ agentName: agentKey, sessionId });
|
|
@@ -971,6 +1250,9 @@ async function handlePutSessionAlias(c) {
|
|
|
971
1250
|
if (!agentKey || !sessionId || typeof aliasValue !== "string") {
|
|
972
1251
|
return c.json({ error: "Invalid session alias payload" }, 400);
|
|
973
1252
|
}
|
|
1253
|
+
if (!isKnownAgentKey(agentKey)) {
|
|
1254
|
+
return c.json({ error: `Unknown agent: ${agentKey}` }, 400);
|
|
1255
|
+
}
|
|
974
1256
|
try {
|
|
975
1257
|
return withStorageErrors(
|
|
976
1258
|
() => {
|
|
@@ -993,6 +1275,9 @@ function handleDeleteSessionAlias(c) {
|
|
|
993
1275
|
if (!agentKey || !sessionId) {
|
|
994
1276
|
return c.json({ error: "Missing session alias identifier" }, 400);
|
|
995
1277
|
}
|
|
1278
|
+
if (!isKnownAgentKey(agentKey)) {
|
|
1279
|
+
return c.json({ error: `Unknown agent: ${agentKey}` }, 400);
|
|
1280
|
+
}
|
|
996
1281
|
return withStorageErrors(
|
|
997
1282
|
() => {
|
|
998
1283
|
deleteSessionAlias({ agentName: agentKey, sessionId });
|
|
@@ -1002,95 +1287,24 @@ function handleDeleteSessionAlias(c) {
|
|
|
1002
1287
|
() => c.json({ error: "Session alias storage is unavailable" }, 503)
|
|
1003
1288
|
);
|
|
1004
1289
|
}
|
|
1005
|
-
function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
1006
|
-
const scanResult = scanSource.getSnapshot();
|
|
1007
|
-
const projectIdentity = parseProjectIdentityFilter(
|
|
1008
|
-
c.req.query("projectKind"),
|
|
1009
|
-
c.req.query("projectKey")
|
|
1010
|
-
);
|
|
1011
|
-
if (projectIdentity === null) {
|
|
1012
|
-
return c.json({ error: "projectKind and projectKey must form a valid project identity" }, 400);
|
|
1013
|
-
}
|
|
1014
|
-
const dateWindow = parseDateWindowRequest(c, "dashboard", defaults);
|
|
1015
|
-
if (dateWindow.kind === "rejected") return dateWindow.response;
|
|
1016
|
-
const { from, to, days } = resolveTimeWindow({
|
|
1017
|
-
mode: "dashboard",
|
|
1018
|
-
query: {
|
|
1019
|
-
days: c.req.query("days"),
|
|
1020
|
-
from: c.req.query("from"),
|
|
1021
|
-
to: c.req.query("to")
|
|
1022
|
-
},
|
|
1023
|
-
defaults
|
|
1024
|
-
});
|
|
1025
|
-
const scope = {
|
|
1026
|
-
agent: optionalQueryValue(c.req.query("agent"))?.toLowerCase(),
|
|
1027
|
-
projectKind: projectIdentity?.kind,
|
|
1028
|
-
projectKey: projectIdentity?.key
|
|
1029
|
-
};
|
|
1030
|
-
const compare = from == null ? void 0 : { from: addCalendarDays(from, -(days ?? countCalendarDays(from, to))), to: from - 1 };
|
|
1031
|
-
const fixedTo = dateWindow.to;
|
|
1032
|
-
const aggregate = getSnapshotAggregation(
|
|
1033
|
-
scanSource,
|
|
1034
|
-
scanResult.sessions,
|
|
1035
|
-
[
|
|
1036
|
-
"dashboard",
|
|
1037
|
-
scope.agent,
|
|
1038
|
-
scope.projectKind,
|
|
1039
|
-
scope.projectKey,
|
|
1040
|
-
from,
|
|
1041
|
-
fixedTo ?? startOfCalendarDay(to),
|
|
1042
|
-
compare?.from,
|
|
1043
|
-
compare?.to
|
|
1044
|
-
],
|
|
1045
|
-
() => {
|
|
1046
|
-
const agentInfo = getAgentInfoMap({});
|
|
1047
|
-
const agentInfoMap = new Map(agentInfo.map((agent) => [agent.name, agent]));
|
|
1048
|
-
return buildDashboard(scanResult.sessions, {
|
|
1049
|
-
byAgentNames: Object.keys(scanResult.byAgent),
|
|
1050
|
-
scope,
|
|
1051
|
-
from,
|
|
1052
|
-
to,
|
|
1053
|
-
agentInfoMap,
|
|
1054
|
-
compare
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
);
|
|
1058
|
-
const data = {
|
|
1059
|
-
...aggregate,
|
|
1060
|
-
recentFileActivities: listFileActivity({
|
|
1061
|
-
agent: scope.agent,
|
|
1062
|
-
projectKind: scope.projectKind,
|
|
1063
|
-
projectKey: scope.projectKey,
|
|
1064
|
-
from,
|
|
1065
|
-
to,
|
|
1066
|
-
limit: 12
|
|
1067
|
-
}),
|
|
1068
|
-
modelCost: listModelCostDistribution({
|
|
1069
|
-
agent: scope.agent,
|
|
1070
|
-
projectKind: scope.projectKind,
|
|
1071
|
-
projectKey: scope.projectKey,
|
|
1072
|
-
from,
|
|
1073
|
-
to
|
|
1074
|
-
}),
|
|
1075
|
-
window: { from, to, days, compareFrom: compare?.from, compareTo: compare?.to }
|
|
1076
|
-
};
|
|
1077
|
-
const aliases = loadAliasView();
|
|
1078
|
-
return c.json({
|
|
1079
|
-
...data,
|
|
1080
|
-
recentSessions: data.recentSessions.map((item) => ({
|
|
1081
|
-
...item,
|
|
1082
|
-
session: aliases.decorate(item.session, item.reference)
|
|
1083
|
-
})),
|
|
1084
|
-
recentFileActivities: data.recentFileActivities.map(
|
|
1085
|
-
(activity) => decorateFileActivity(activity, aliases)
|
|
1086
|
-
)
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
1290
|
|
|
1090
1291
|
// src/api/sse-event-buffer.ts
|
|
1091
1292
|
var MAX_PENDING_CRITICAL_SSE_FRAMES = 64;
|
|
1092
1293
|
function scanStatusMilestone(status) {
|
|
1093
|
-
const agentStates = Object.entries(status.agentStatuses).sort(([left], [right]) => left.localeCompare(right)).map(([agentName, agentStatus]) => [
|
|
1294
|
+
const agentStates = Object.entries(status.agentStatuses).sort(([left], [right]) => left.localeCompare(right)).map(([agentName, agentStatus]) => [
|
|
1295
|
+
agentName,
|
|
1296
|
+
agentStatus.status,
|
|
1297
|
+
agentStatus.error ?? null,
|
|
1298
|
+
agentStatus.completeness ?? null,
|
|
1299
|
+
agentStatus.sourceFailureCount ?? null,
|
|
1300
|
+
agentStatus.sourceFailureSummary ?? null
|
|
1301
|
+
]);
|
|
1302
|
+
const partialBackfills = Object.entries(status.backfill.partialAgents ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([agentName, completion]) => [
|
|
1303
|
+
agentName,
|
|
1304
|
+
completion.completeness,
|
|
1305
|
+
completion.sourceFailureCount ?? null,
|
|
1306
|
+
completion.sourceFailureSummary ?? null
|
|
1307
|
+
]);
|
|
1094
1308
|
return JSON.stringify([
|
|
1095
1309
|
status.active,
|
|
1096
1310
|
status.phase,
|
|
@@ -1105,6 +1319,7 @@ function scanStatusMilestone(status) {
|
|
|
1105
1319
|
status.backfill.completedAgents,
|
|
1106
1320
|
status.backfill.failedAgents,
|
|
1107
1321
|
status.backfill.progress?.phase ?? null,
|
|
1322
|
+
partialBackfills,
|
|
1108
1323
|
status.searchIndexMaintenance?.active ?? false,
|
|
1109
1324
|
status.searchIndexMaintenance?.currentAgent ?? null,
|
|
1110
1325
|
status.searchIndexMaintenance?.pendingAgents ?? [],
|
|
@@ -1281,10 +1496,22 @@ function createApiRoutes(scanSource, eventSource, options = {}) {
|
|
|
1281
1496
|
}
|
|
1282
1497
|
api.get("/agents", (c) => handleGetAgents(c, scanSource, listDefaults));
|
|
1283
1498
|
api.get("/projects", (c) => handleGetProjects(c, scanSource, listDefaults));
|
|
1284
|
-
api.get(
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1499
|
+
api.get(
|
|
1500
|
+
"/sessions",
|
|
1501
|
+
(c) => handleGetSessions(c, scanSource, listDefaults, options.projectIdentityResolver)
|
|
1502
|
+
);
|
|
1503
|
+
api.get(
|
|
1504
|
+
"/search",
|
|
1505
|
+
(c) => handleSearchSessions(c, scanSource, listDefaults, options.projectIdentityResolver)
|
|
1506
|
+
);
|
|
1507
|
+
api.get(
|
|
1508
|
+
"/file-activity",
|
|
1509
|
+
(c) => handleGetFileActivity(c, listDefaults, options.projectIdentityResolver)
|
|
1510
|
+
);
|
|
1511
|
+
api.get(
|
|
1512
|
+
"/sessions/:agent/:id",
|
|
1513
|
+
(c) => handleGetSessionData(c, scanSource, options.projectIdentityResolver)
|
|
1514
|
+
);
|
|
1288
1515
|
api.get("/dashboard", (c) => handleGetDashboard(c, scanSource, listDefaults));
|
|
1289
1516
|
api.get("/bookmarks", (c) => handleGetBookmarks(c, scanSource));
|
|
1290
1517
|
api.put("/bookmarks", (c) => handlePutBookmark(c));
|
|
@@ -1302,6 +1529,124 @@ function createApiRoutes(scanSource, eventSource, options = {}) {
|
|
|
1302
1529
|
return api;
|
|
1303
1530
|
}
|
|
1304
1531
|
|
|
1532
|
+
// src/project-identity-resolver.ts
|
|
1533
|
+
import { Worker } from "worker_threads";
|
|
1534
|
+
var PROJECT_IDENTITY_RESOLVER_MAX_WORKERS = 2;
|
|
1535
|
+
var SHUTDOWN_ERROR_MESSAGE = "Project identity resolver shut down";
|
|
1536
|
+
function toError(error) {
|
|
1537
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
1538
|
+
}
|
|
1539
|
+
function isWorkerMessage(message) {
|
|
1540
|
+
return typeof message === "object" && message !== null && "type" in message && (message.type === "resolved" && "requestId" in message && "projection" in message || message.type === "failed" && "requestId" in message && "error" in message);
|
|
1541
|
+
}
|
|
1542
|
+
var ThreadProjectIdentityResolver = class {
|
|
1543
|
+
constructor(workerUrl, maxWorkers = PROJECT_IDENTITY_RESOLVER_MAX_WORKERS) {
|
|
1544
|
+
this.workerUrl = workerUrl;
|
|
1545
|
+
this.maxWorkers = maxWorkers;
|
|
1546
|
+
}
|
|
1547
|
+
workerUrl;
|
|
1548
|
+
maxWorkers;
|
|
1549
|
+
workers = /* @__PURE__ */ new Set();
|
|
1550
|
+
queue = [];
|
|
1551
|
+
nextRequestId = 1;
|
|
1552
|
+
isShuttingDown = false;
|
|
1553
|
+
resolve(cwd) {
|
|
1554
|
+
if (this.isShuttingDown) return Promise.reject(new Error(SHUTDOWN_ERROR_MESSAGE));
|
|
1555
|
+
return new Promise((resolve4, reject) => {
|
|
1556
|
+
this.queue.push({ requestId: this.nextRequestId++, cwd, resolve: resolve4, reject });
|
|
1557
|
+
this.pump();
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
async shutdown() {
|
|
1561
|
+
if (this.isShuttingDown) return;
|
|
1562
|
+
this.isShuttingDown = true;
|
|
1563
|
+
const error = new Error(SHUTDOWN_ERROR_MESSAGE);
|
|
1564
|
+
for (const request of this.queue.splice(0)) request.reject(error);
|
|
1565
|
+
const workers = [...this.workers];
|
|
1566
|
+
this.workers.clear();
|
|
1567
|
+
for (const slot of workers) {
|
|
1568
|
+
slot.closed = true;
|
|
1569
|
+
slot.pending?.reject(error);
|
|
1570
|
+
slot.pending = null;
|
|
1571
|
+
}
|
|
1572
|
+
await Promise.allSettled(workers.map(({ worker }) => worker.terminate()));
|
|
1573
|
+
}
|
|
1574
|
+
pump() {
|
|
1575
|
+
while (!this.isShuttingDown && this.queue.length > 0) {
|
|
1576
|
+
const slot = this.idleWorker() ?? this.createWorker();
|
|
1577
|
+
if (!slot) return;
|
|
1578
|
+
const request = this.queue.shift();
|
|
1579
|
+
if (!request) return;
|
|
1580
|
+
slot.pending = request;
|
|
1581
|
+
try {
|
|
1582
|
+
slot.worker.postMessage({
|
|
1583
|
+
type: "resolve",
|
|
1584
|
+
requestId: request.requestId,
|
|
1585
|
+
cwd: request.cwd
|
|
1586
|
+
});
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
this.closeWorker(slot, toError(error));
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
idleWorker() {
|
|
1593
|
+
for (const slot of this.workers) {
|
|
1594
|
+
if (!slot.closed && slot.pending == null) return slot;
|
|
1595
|
+
}
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1598
|
+
createWorker() {
|
|
1599
|
+
if (this.workers.size >= this.maxWorkers) return null;
|
|
1600
|
+
let worker;
|
|
1601
|
+
try {
|
|
1602
|
+
worker = new Worker(this.workerUrl);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
this.rejectQueued(toError(error));
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
const slot = { worker, pending: null, closed: false };
|
|
1608
|
+
this.workers.add(slot);
|
|
1609
|
+
worker.unref();
|
|
1610
|
+
worker.on("message", (message) => {
|
|
1611
|
+
if (appLogger.consumeWorkerMessage(message)) return;
|
|
1612
|
+
this.handleWorkerMessage(slot, message);
|
|
1613
|
+
});
|
|
1614
|
+
worker.on("error", (error) => this.closeWorker(slot, toError(error)));
|
|
1615
|
+
worker.on("exit", (code) => {
|
|
1616
|
+
if (!slot.closed) {
|
|
1617
|
+
this.closeWorker(slot, new Error(`Project identity worker exited with code ${code}`));
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
return slot;
|
|
1621
|
+
}
|
|
1622
|
+
handleWorkerMessage(slot, message) {
|
|
1623
|
+
if (!isWorkerMessage(message) || slot.closed) return;
|
|
1624
|
+
const request = slot.pending;
|
|
1625
|
+
if (!request || message.requestId !== request.requestId) return;
|
|
1626
|
+
slot.pending = null;
|
|
1627
|
+
if (message.type === "resolved") request.resolve(message.projection);
|
|
1628
|
+
else request.reject(new Error(message.error));
|
|
1629
|
+
this.pump();
|
|
1630
|
+
}
|
|
1631
|
+
closeWorker(slot, error) {
|
|
1632
|
+
if (slot.closed) return;
|
|
1633
|
+
slot.closed = true;
|
|
1634
|
+
this.workers.delete(slot);
|
|
1635
|
+
const request = slot.pending;
|
|
1636
|
+
slot.pending = null;
|
|
1637
|
+
request?.reject(error);
|
|
1638
|
+
if (!this.isShuttingDown) this.pump();
|
|
1639
|
+
}
|
|
1640
|
+
rejectQueued(error) {
|
|
1641
|
+
for (const request of this.queue.splice(0)) request.reject(error);
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
function createProjectIdentityResolver() {
|
|
1645
|
+
return new ThreadProjectIdentityResolver(
|
|
1646
|
+
new URL("./project-identity-worker.js", import.meta.url)
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1305
1650
|
// src/loopback-authority.ts
|
|
1306
1651
|
import { isIP } from "net";
|
|
1307
1652
|
var LOOPBACK_AUTHORITY_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
@@ -1366,7 +1711,6 @@ function validateLoopbackAuthority(rawHeaders, listenerHostname, listenerPort) {
|
|
|
1366
1711
|
}
|
|
1367
1712
|
|
|
1368
1713
|
// src/loopback-write-guard.ts
|
|
1369
|
-
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
1370
1714
|
var JSON_BODY_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
1371
1715
|
var ALLOWED_FETCH_SITES = /* @__PURE__ */ new Set(["same-origin", "none"]);
|
|
1372
1716
|
function mediaType(contentType) {
|
|
@@ -1381,7 +1725,6 @@ function isSameOrigin(origin, requestOrigin) {
|
|
|
1381
1725
|
}
|
|
1382
1726
|
function evaluateLoopbackWriteRequest(request) {
|
|
1383
1727
|
const method = request.method.toUpperCase();
|
|
1384
|
-
if (SAFE_METHODS.has(method)) return { allowed: true };
|
|
1385
1728
|
if (JSON_BODY_METHODS.has(method) && mediaType(request.contentType) !== "application/json") {
|
|
1386
1729
|
return { allowed: false, reason: "content-type", status: 415 };
|
|
1387
1730
|
}
|
|
@@ -1404,12 +1747,12 @@ function loopbackWriteGuard() {
|
|
|
1404
1747
|
requestOrigin: url.origin
|
|
1405
1748
|
});
|
|
1406
1749
|
if (!decision.allowed) {
|
|
1407
|
-
appLogger.warn("http.
|
|
1750
|
+
appLogger.warn("http.loopback_request.rejected", {
|
|
1408
1751
|
method: c.req.method,
|
|
1409
1752
|
path: url.pathname,
|
|
1410
1753
|
reason: decision.reason
|
|
1411
1754
|
});
|
|
1412
|
-
const error = decision.reason === "content-type" ? "Write requests require application/json" : "Cross-origin
|
|
1755
|
+
const error = decision.reason === "content-type" ? "Write requests require application/json" : "Cross-origin API request rejected";
|
|
1413
1756
|
return c.json({ error }, decision.status);
|
|
1414
1757
|
}
|
|
1415
1758
|
await next();
|
|
@@ -1572,6 +1915,7 @@ async function createServer(port, store, options = {}) {
|
|
|
1572
1915
|
const remoteAccessToken = accessPolicy.authenticationRequired ? options.remoteAccessToken ?? (options.remoteAccess ? createRemoteAccessToken() : null) : null;
|
|
1573
1916
|
const loopbackAuthorityEnabled = !accessPolicy.authenticationRequired;
|
|
1574
1917
|
const shutdownController = new AbortController();
|
|
1918
|
+
const projectIdentityResolver = options.projectIdentityResolver ?? createProjectIdentityResolver();
|
|
1575
1919
|
let actualPort = null;
|
|
1576
1920
|
appLogger.info("server.access_policy", {
|
|
1577
1921
|
bind_category: accessPolicy.bindCategory,
|
|
@@ -1667,7 +2011,8 @@ async function createServer(port, store, options = {}) {
|
|
|
1667
2011
|
defaultSessionFrom: options.defaultSessionFrom,
|
|
1668
2012
|
defaultSessionTo: options.defaultSessionTo,
|
|
1669
2013
|
defaultSessionDays: options.defaultSessionDays,
|
|
1670
|
-
shutdownSignal: shutdownController.signal
|
|
2014
|
+
shutdownSignal: shutdownController.signal,
|
|
2015
|
+
projectIdentityResolver
|
|
1671
2016
|
};
|
|
1672
2017
|
app.route("/api", createApiRoutes(store, store, routeOptions));
|
|
1673
2018
|
const webDistPath = options.webDistPath ?? findWebDistPath();
|
|
@@ -1752,8 +2097,12 @@ async function createServer(port, store, options = {}) {
|
|
|
1752
2097
|
appLogger.info("server.shutdown.phase", { phase: "http-closing", port: actualPort });
|
|
1753
2098
|
await closeHttpServer(server, actualPort);
|
|
1754
2099
|
} finally {
|
|
1755
|
-
|
|
1756
|
-
|
|
2100
|
+
try {
|
|
2101
|
+
await projectIdentityResolver.shutdown();
|
|
2102
|
+
} finally {
|
|
2103
|
+
appLogger.info("server.shutdown.phase", { phase: "store-closing", port: actualPort });
|
|
2104
|
+
await store.shutdown();
|
|
2105
|
+
}
|
|
1757
2106
|
}
|
|
1758
2107
|
appLogger.info("server.shutdown.phase", { phase: "complete", port: actualPort });
|
|
1759
2108
|
}
|
|
@@ -1800,7 +2149,9 @@ var AgentOperationScheduler = class {
|
|
|
1800
2149
|
state.timer = setTimeout(() => {
|
|
1801
2150
|
state.timer = null;
|
|
1802
2151
|
state.timerDeadline = 0;
|
|
1803
|
-
|
|
2152
|
+
this.refresh(agentName).catch((error) => {
|
|
2153
|
+
appLogger.error("scan.refresh.uncaught", { agent: agentName, error });
|
|
2154
|
+
});
|
|
1804
2155
|
}, effectiveDelayMs);
|
|
1805
2156
|
}
|
|
1806
2157
|
async refresh(agentName) {
|
|
@@ -1946,9 +2297,16 @@ var BackfillLifecycle = class {
|
|
|
1946
2297
|
});
|
|
1947
2298
|
return true;
|
|
1948
2299
|
}
|
|
2300
|
+
recordCompletion(attempt, completion) {
|
|
2301
|
+
const current = this.matchingRunningAttempt(attempt);
|
|
2302
|
+
if (!current) return false;
|
|
2303
|
+
this.attempts.set(attempt.agentName, { ...current, completion: { ...completion } });
|
|
2304
|
+
return true;
|
|
2305
|
+
}
|
|
1949
2306
|
complete(attempt, status) {
|
|
1950
|
-
|
|
1951
|
-
|
|
2307
|
+
const current = this.matchingRunningAttempt(attempt);
|
|
2308
|
+
if (!current) return false;
|
|
2309
|
+
this.attempts.set(attempt.agentName, { ...current, status });
|
|
1952
2310
|
return true;
|
|
1953
2311
|
}
|
|
1954
2312
|
cancelAll() {
|
|
@@ -1965,17 +2323,25 @@ var BackfillLifecycle = class {
|
|
|
1965
2323
|
stateFor(agentName) {
|
|
1966
2324
|
const state = this.attempts.get(agentName);
|
|
1967
2325
|
if (!state) return void 0;
|
|
1968
|
-
|
|
2326
|
+
const completion = state.completion ? { completion: { ...state.completion } } : {};
|
|
2327
|
+
return state.status === "running" && state.progress ? { ...state, progress: { ...state.progress }, ...completion } : { ...state, ...completion };
|
|
1969
2328
|
}
|
|
1970
2329
|
status() {
|
|
1971
2330
|
const states = [...this.attempts.values()];
|
|
1972
2331
|
const queued = states.filter((attempt) => attempt.status === "queued").sort((left, right) => left.attemptId - right.attemptId);
|
|
1973
2332
|
const running = states.find((attempt) => attempt.status === "running");
|
|
2333
|
+
const partialAgents = {};
|
|
2334
|
+
for (const attempt of states) {
|
|
2335
|
+
if (attempt.status === "committed" && attempt.completion?.completeness === "partial") {
|
|
2336
|
+
partialAgents[attempt.agentName] = { ...attempt.completion };
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
1974
2339
|
const status = {
|
|
1975
2340
|
active: running != null || queued.length > 0,
|
|
1976
2341
|
pendingAgents: queued.map((attempt) => attempt.agentName),
|
|
1977
2342
|
completedAgents: states.filter((attempt) => attempt.status === "committed").map((attempt) => attempt.agentName),
|
|
1978
|
-
failedAgents: states.filter((attempt) => attempt.status === "failed").map((attempt) => attempt.agentName)
|
|
2343
|
+
failedAgents: states.filter((attempt) => attempt.status === "failed").map((attempt) => attempt.agentName),
|
|
2344
|
+
...Object.keys(partialAgents).length > 0 ? { partialAgents } : {}
|
|
1979
2345
|
};
|
|
1980
2346
|
if (running) {
|
|
1981
2347
|
status.currentAgent = running.agentName;
|
|
@@ -1995,6 +2361,7 @@ var LiveSessionIndex = class {
|
|
|
1995
2361
|
agentsByName = /* @__PURE__ */ new Map();
|
|
1996
2362
|
byAgent = {};
|
|
1997
2363
|
sessions = [];
|
|
2364
|
+
cacheFailures = {};
|
|
1998
2365
|
scanFailures = {};
|
|
1999
2366
|
signatureCaches = /* @__PURE__ */ new Map();
|
|
2000
2367
|
initialize(snapshot, options = {}) {
|
|
@@ -2007,6 +2374,7 @@ var LiveSessionIndex = class {
|
|
|
2007
2374
|
(agent) => !options.allowedAgents || options.allowedAgents.has(agent.name.toLowerCase())
|
|
2008
2375
|
);
|
|
2009
2376
|
this.agentsByName = new Map(this.agents.map((agent) => [agent.name, agent]));
|
|
2377
|
+
this.cacheFailures = { ...snapshot.cacheFailures };
|
|
2010
2378
|
this.scanFailures = { ...snapshot.scanFailures };
|
|
2011
2379
|
this.byAgent = Object.fromEntries(
|
|
2012
2380
|
this.agents.flatMap((agent) => {
|
|
@@ -2022,6 +2390,7 @@ var LiveSessionIndex = class {
|
|
|
2022
2390
|
agents: this.agents,
|
|
2023
2391
|
byAgent: this.byAgent,
|
|
2024
2392
|
sessions: this.sessions,
|
|
2393
|
+
...Object.keys(this.cacheFailures).length > 0 ? { cacheFailures: this.cacheFailures } : {},
|
|
2025
2394
|
...Object.keys(this.scanFailures).length > 0 ? { scanFailures: this.scanFailures } : {}
|
|
2026
2395
|
};
|
|
2027
2396
|
}
|
|
@@ -2029,6 +2398,7 @@ var LiveSessionIndex = class {
|
|
|
2029
2398
|
return this.agentsByName.get(agentName);
|
|
2030
2399
|
}
|
|
2031
2400
|
commitAgentSessions(agentName, nextSessions, candidateChangedIds = []) {
|
|
2401
|
+
delete this.cacheFailures[agentName];
|
|
2032
2402
|
delete this.scanFailures[agentName];
|
|
2033
2403
|
const previousSessions = this.byAgent[agentName] ?? [];
|
|
2034
2404
|
const previousGlobalSessions = this.sessions;
|
|
@@ -2083,76 +2453,13 @@ var LiveSessionIndex = class {
|
|
|
2083
2453
|
}
|
|
2084
2454
|
};
|
|
2085
2455
|
|
|
2086
|
-
// src/latest-value-throttle.ts
|
|
2087
|
-
var LatestValueThrottle = class {
|
|
2088
|
-
constructor(intervalMs, emit) {
|
|
2089
|
-
this.intervalMs = intervalMs;
|
|
2090
|
-
this.emit = emit;
|
|
2091
|
-
}
|
|
2092
|
-
intervalMs;
|
|
2093
|
-
emit;
|
|
2094
|
-
timer = null;
|
|
2095
|
-
phase = null;
|
|
2096
|
-
pending;
|
|
2097
|
-
hasPending = false;
|
|
2098
|
-
push(value, phase) {
|
|
2099
|
-
if (this.phase !== phase) {
|
|
2100
|
-
this.flush();
|
|
2101
|
-
this.phase = phase;
|
|
2102
|
-
this.emit(value);
|
|
2103
|
-
this.schedule();
|
|
2104
|
-
return;
|
|
2105
|
-
}
|
|
2106
|
-
if (this.timer) {
|
|
2107
|
-
this.pending = value;
|
|
2108
|
-
this.hasPending = true;
|
|
2109
|
-
return;
|
|
2110
|
-
}
|
|
2111
|
-
this.emit(value);
|
|
2112
|
-
this.schedule();
|
|
2113
|
-
}
|
|
2114
|
-
flush() {
|
|
2115
|
-
this.clearTimer();
|
|
2116
|
-
if (!this.hasPending) return;
|
|
2117
|
-
const pending = this.pending;
|
|
2118
|
-
this.pending = void 0;
|
|
2119
|
-
this.hasPending = false;
|
|
2120
|
-
this.emit(pending);
|
|
2121
|
-
}
|
|
2122
|
-
cancel() {
|
|
2123
|
-
this.dropPending();
|
|
2124
|
-
this.phase = null;
|
|
2125
|
-
}
|
|
2126
|
-
schedule() {
|
|
2127
|
-
this.timer = setTimeout(() => {
|
|
2128
|
-
this.timer = null;
|
|
2129
|
-
if (!this.hasPending) return;
|
|
2130
|
-
const pending = this.pending;
|
|
2131
|
-
this.pending = void 0;
|
|
2132
|
-
this.hasPending = false;
|
|
2133
|
-
this.emit(pending);
|
|
2134
|
-
this.schedule();
|
|
2135
|
-
}, this.intervalMs);
|
|
2136
|
-
}
|
|
2137
|
-
clearTimer() {
|
|
2138
|
-
if (!this.timer) return;
|
|
2139
|
-
clearTimeout(this.timer);
|
|
2140
|
-
this.timer = null;
|
|
2141
|
-
}
|
|
2142
|
-
dropPending() {
|
|
2143
|
-
this.clearTimer();
|
|
2144
|
-
this.pending = void 0;
|
|
2145
|
-
this.hasPending = false;
|
|
2146
|
-
}
|
|
2147
|
-
};
|
|
2148
|
-
|
|
2149
2456
|
// src/search-index-job-runner.ts
|
|
2150
2457
|
import { existsSync as existsSync2 } from "fs";
|
|
2151
2458
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2152
|
-
import { Worker } from "worker_threads";
|
|
2459
|
+
import { Worker as Worker2 } from "worker_threads";
|
|
2153
2460
|
|
|
2154
2461
|
// src/errors.ts
|
|
2155
|
-
function
|
|
2462
|
+
function toError2(value) {
|
|
2156
2463
|
return value instanceof Error ? value : new Error(String(value));
|
|
2157
2464
|
}
|
|
2158
2465
|
|
|
@@ -2322,7 +2629,7 @@ function changesJobFromPending(pending) {
|
|
|
2322
2629
|
}
|
|
2323
2630
|
|
|
2324
2631
|
// src/search-index-job-runner.ts
|
|
2325
|
-
var
|
|
2632
|
+
var SHUTDOWN_ERROR_MESSAGE2 = "Live scan store shut down";
|
|
2326
2633
|
var SearchIndexJobRunner = class {
|
|
2327
2634
|
worker = null;
|
|
2328
2635
|
activeBatch = null;
|
|
@@ -2338,7 +2645,7 @@ var SearchIndexJobRunner = class {
|
|
|
2338
2645
|
}
|
|
2339
2646
|
enqueueIn(queue, context, jobs, onStarted) {
|
|
2340
2647
|
if (jobs.length === 0) return Promise.resolve();
|
|
2341
|
-
if (this.isShuttingDown) return Promise.reject(new Error(
|
|
2648
|
+
if (this.isShuttingDown) return Promise.reject(new Error(SHUTDOWN_ERROR_MESSAGE2));
|
|
2342
2649
|
const batchId = this.nextBatchId++;
|
|
2343
2650
|
const completion = queue.enqueue(batchId, context, jobs, onStarted);
|
|
2344
2651
|
if (this.activeBatch) {
|
|
@@ -2372,7 +2679,7 @@ var SearchIndexJobRunner = class {
|
|
|
2372
2679
|
const worker = this.worker;
|
|
2373
2680
|
this.activeBatch = null;
|
|
2374
2681
|
this.worker = null;
|
|
2375
|
-
const shutdownError = new Error(
|
|
2682
|
+
const shutdownError = new Error(SHUTDOWN_ERROR_MESSAGE2);
|
|
2376
2683
|
if (activeBatch) this.settle(activeBatch, shutdownError);
|
|
2377
2684
|
this.pendingJobs.rejectAll(shutdownError);
|
|
2378
2685
|
this.pendingMaintenanceJobs.rejectAll(shutdownError);
|
|
@@ -2391,14 +2698,14 @@ var SearchIndexJobRunner = class {
|
|
|
2391
2698
|
appLogger.error("search_index.worker_start_listener_failed", {
|
|
2392
2699
|
batch_id: batch.id,
|
|
2393
2700
|
context: batch.context,
|
|
2394
|
-
error:
|
|
2701
|
+
error: toError2(error)
|
|
2395
2702
|
});
|
|
2396
2703
|
});
|
|
2397
2704
|
this.startBatch(batch);
|
|
2398
2705
|
}
|
|
2399
2706
|
startBatch(batch) {
|
|
2400
2707
|
if (this.isShuttingDown) {
|
|
2401
|
-
this.settle(batch, new Error(
|
|
2708
|
+
this.settle(batch, new Error(SHUTDOWN_ERROR_MESSAGE2));
|
|
2402
2709
|
return;
|
|
2403
2710
|
}
|
|
2404
2711
|
const request = {
|
|
@@ -2413,7 +2720,7 @@ var SearchIndexJobRunner = class {
|
|
|
2413
2720
|
try {
|
|
2414
2721
|
existingWorker.postMessage(request);
|
|
2415
2722
|
} catch (error) {
|
|
2416
|
-
this.invalidateWorker(existingWorker,
|
|
2723
|
+
this.invalidateWorker(existingWorker, toError2(error));
|
|
2417
2724
|
}
|
|
2418
2725
|
return;
|
|
2419
2726
|
}
|
|
@@ -2431,9 +2738,9 @@ var SearchIndexJobRunner = class {
|
|
|
2431
2738
|
});
|
|
2432
2739
|
let worker;
|
|
2433
2740
|
try {
|
|
2434
|
-
worker = new
|
|
2741
|
+
worker = new Worker2(workerUrl, { workerData: request });
|
|
2435
2742
|
} catch (error) {
|
|
2436
|
-
this.settle(batch,
|
|
2743
|
+
this.settle(batch, toError2(error));
|
|
2437
2744
|
this.startNextBatch();
|
|
2438
2745
|
return;
|
|
2439
2746
|
}
|
|
@@ -2458,15 +2765,24 @@ var SearchIndexJobRunner = class {
|
|
|
2458
2765
|
agent: message.agentName,
|
|
2459
2766
|
sessions: message.sessions
|
|
2460
2767
|
});
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2768
|
+
if (message.fatal) {
|
|
2769
|
+
this.finishBatch(
|
|
2770
|
+
activeBatch,
|
|
2771
|
+
new Error(
|
|
2772
|
+
`Search index worker failed to persist ${message.stage} for ${message.agentName}`
|
|
2773
|
+
)
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2467
2776
|
return;
|
|
2468
2777
|
}
|
|
2469
2778
|
if (message.type !== "done") return;
|
|
2779
|
+
if (message.failedAgents.length > 0) {
|
|
2780
|
+
appLogger.warn("search_index.batch_partial", {
|
|
2781
|
+
batch_id: activeBatch.id,
|
|
2782
|
+
context: message.context,
|
|
2783
|
+
failed_agents: message.failedAgents
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2470
2786
|
appLogger.info(`${message.context}.done`, {
|
|
2471
2787
|
duration_ms: Math.round(message.durationMs),
|
|
2472
2788
|
sessions: message.sessions
|
|
@@ -2478,7 +2794,7 @@ var SearchIndexJobRunner = class {
|
|
|
2478
2794
|
context: this.activeBatch?.context ?? batch.context,
|
|
2479
2795
|
error
|
|
2480
2796
|
});
|
|
2481
|
-
this.invalidateWorker(worker,
|
|
2797
|
+
this.invalidateWorker(worker, toError2(error));
|
|
2482
2798
|
});
|
|
2483
2799
|
worker.on("exit", (code) => this.finishWorker(worker, code));
|
|
2484
2800
|
}
|
|
@@ -2588,7 +2904,7 @@ var SearchIndexMaintenanceScheduler = class {
|
|
|
2588
2904
|
this.failedAgents.add(agentName);
|
|
2589
2905
|
appLogger.error("search_index.maintenance_failed", {
|
|
2590
2906
|
agent: agentName,
|
|
2591
|
-
error:
|
|
2907
|
+
error: toError2(error)
|
|
2592
2908
|
});
|
|
2593
2909
|
}
|
|
2594
2910
|
} finally {
|
|
@@ -2671,6 +2987,69 @@ var SearchIndexMaintenanceScheduler = class {
|
|
|
2671
2987
|
}
|
|
2672
2988
|
};
|
|
2673
2989
|
|
|
2990
|
+
// src/latest-value-throttle.ts
|
|
2991
|
+
var LatestValueThrottle = class {
|
|
2992
|
+
constructor(intervalMs, emit) {
|
|
2993
|
+
this.intervalMs = intervalMs;
|
|
2994
|
+
this.emit = emit;
|
|
2995
|
+
}
|
|
2996
|
+
intervalMs;
|
|
2997
|
+
emit;
|
|
2998
|
+
timer = null;
|
|
2999
|
+
phase = null;
|
|
3000
|
+
pending;
|
|
3001
|
+
hasPending = false;
|
|
3002
|
+
push(value, phase) {
|
|
3003
|
+
if (this.phase !== phase) {
|
|
3004
|
+
this.flush();
|
|
3005
|
+
this.phase = phase;
|
|
3006
|
+
this.emit(value);
|
|
3007
|
+
this.schedule();
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
if (this.timer) {
|
|
3011
|
+
this.pending = value;
|
|
3012
|
+
this.hasPending = true;
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
this.emit(value);
|
|
3016
|
+
this.schedule();
|
|
3017
|
+
}
|
|
3018
|
+
flush() {
|
|
3019
|
+
this.clearTimer();
|
|
3020
|
+
if (!this.hasPending) return;
|
|
3021
|
+
const pending = this.pending;
|
|
3022
|
+
this.pending = void 0;
|
|
3023
|
+
this.hasPending = false;
|
|
3024
|
+
this.emit(pending);
|
|
3025
|
+
}
|
|
3026
|
+
cancel() {
|
|
3027
|
+
this.dropPending();
|
|
3028
|
+
this.phase = null;
|
|
3029
|
+
}
|
|
3030
|
+
schedule() {
|
|
3031
|
+
this.timer = setTimeout(() => {
|
|
3032
|
+
this.timer = null;
|
|
3033
|
+
if (!this.hasPending) return;
|
|
3034
|
+
const pending = this.pending;
|
|
3035
|
+
this.pending = void 0;
|
|
3036
|
+
this.hasPending = false;
|
|
3037
|
+
this.emit(pending);
|
|
3038
|
+
this.schedule();
|
|
3039
|
+
}, this.intervalMs);
|
|
3040
|
+
}
|
|
3041
|
+
clearTimer() {
|
|
3042
|
+
if (!this.timer) return;
|
|
3043
|
+
clearTimeout(this.timer);
|
|
3044
|
+
this.timer = null;
|
|
3045
|
+
}
|
|
3046
|
+
dropPending() {
|
|
3047
|
+
this.clearTimer();
|
|
3048
|
+
this.pending = void 0;
|
|
3049
|
+
this.hasPending = false;
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
|
|
2674
3053
|
// src/scan-status-model.ts
|
|
2675
3054
|
var ScanStatusModel = class {
|
|
2676
3055
|
status = {
|
|
@@ -2702,6 +3081,16 @@ var ScanStatusModel = class {
|
|
|
2702
3081
|
} else {
|
|
2703
3082
|
delete backfill.progress;
|
|
2704
3083
|
}
|
|
3084
|
+
if (this.status.backfill.partialAgents) {
|
|
3085
|
+
backfill.partialAgents = Object.fromEntries(
|
|
3086
|
+
Object.entries(this.status.backfill.partialAgents).map(([agentName, completion]) => [
|
|
3087
|
+
agentName,
|
|
3088
|
+
{ ...completion }
|
|
3089
|
+
])
|
|
3090
|
+
);
|
|
3091
|
+
} else {
|
|
3092
|
+
delete backfill.partialAgents;
|
|
3093
|
+
}
|
|
2705
3094
|
return {
|
|
2706
3095
|
type: "scan-status",
|
|
2707
3096
|
...this.status,
|
|
@@ -2847,7 +3236,7 @@ var ScanStatusModel = class {
|
|
|
2847
3236
|
updatedAt: now
|
|
2848
3237
|
});
|
|
2849
3238
|
}
|
|
2850
|
-
finishAgent(agentName, sessionCount) {
|
|
3239
|
+
finishAgent(agentName, sessionCount, completion = { completeness: "complete" }) {
|
|
2851
3240
|
const pendingAgents = this.status.pendingAgents.filter((agent) => agent !== agentName);
|
|
2852
3241
|
const scanningAgents = this.status.scanningAgents.filter((agent) => agent !== agentName);
|
|
2853
3242
|
const completedAgents = [.../* @__PURE__ */ new Set([...this.status.completedAgents, agentName])];
|
|
@@ -2860,6 +3249,7 @@ var ScanStatusModel = class {
|
|
|
2860
3249
|
[agentName]: {
|
|
2861
3250
|
agentName,
|
|
2862
3251
|
status: "complete",
|
|
3252
|
+
...completion,
|
|
2863
3253
|
total,
|
|
2864
3254
|
processed: total,
|
|
2865
3255
|
sessions: sessionCount ?? previousStatus?.sessions ?? 0,
|
|
@@ -2926,6 +3316,7 @@ var ScanStatusModel = class {
|
|
|
2926
3316
|
status.status === "failed" ? { ...status, completedAt: status.completedAt ?? now, updatedAt: now } : {
|
|
2927
3317
|
...status,
|
|
2928
3318
|
status: "complete",
|
|
3319
|
+
completeness: status.completeness ?? "complete",
|
|
2929
3320
|
completedAt: status.completedAt ?? now,
|
|
2930
3321
|
updatedAt: now
|
|
2931
3322
|
}
|
|
@@ -2943,7 +3334,15 @@ var ScanStatusModel = class {
|
|
|
2943
3334
|
pendingAgents: [...backfill.pendingAgents],
|
|
2944
3335
|
completedAgents: [...backfill.completedAgents],
|
|
2945
3336
|
failedAgents: [...backfill.failedAgents],
|
|
2946
|
-
progress: backfill.progress ? { ...backfill.progress } : void 0
|
|
3337
|
+
progress: backfill.progress ? { ...backfill.progress } : void 0,
|
|
3338
|
+
...backfill.partialAgents ? {
|
|
3339
|
+
partialAgents: Object.fromEntries(
|
|
3340
|
+
Object.entries(backfill.partialAgents).map(([agentName, completion]) => [
|
|
3341
|
+
agentName,
|
|
3342
|
+
{ ...completion }
|
|
3343
|
+
])
|
|
3344
|
+
)
|
|
3345
|
+
} : {}
|
|
2947
3346
|
},
|
|
2948
3347
|
updatedAt: Date.now()
|
|
2949
3348
|
});
|
|
@@ -2967,11 +3366,236 @@ var ScanStatusModel = class {
|
|
|
2967
3366
|
})) {
|
|
2968
3367
|
return "publishing";
|
|
2969
3368
|
}
|
|
2970
|
-
return this.status.phase === "initializing" ? "initializing" : "scanning";
|
|
2971
|
-
}
|
|
2972
|
-
set(status) {
|
|
2973
|
-
this.status = status;
|
|
2974
|
-
return this.snapshot();
|
|
3369
|
+
return this.status.phase === "initializing" ? "initializing" : "scanning";
|
|
3370
|
+
}
|
|
3371
|
+
set(status) {
|
|
3372
|
+
this.status = status;
|
|
3373
|
+
return this.snapshot();
|
|
3374
|
+
}
|
|
3375
|
+
};
|
|
3376
|
+
|
|
3377
|
+
// src/scan-status-reporter.ts
|
|
3378
|
+
var STATUS_PROGRESS_INTERVAL_MS = 100;
|
|
3379
|
+
var ScanStatusReporter = class {
|
|
3380
|
+
constructor(options) {
|
|
3381
|
+
this.options = options;
|
|
3382
|
+
}
|
|
3383
|
+
options;
|
|
3384
|
+
scanStatus = new ScanStatusModel();
|
|
3385
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3386
|
+
progressThrottles = /* @__PURE__ */ new Map();
|
|
3387
|
+
shuttingDown = false;
|
|
3388
|
+
subscribe(listener) {
|
|
3389
|
+
this.listeners.add(listener);
|
|
3390
|
+
return () => this.listeners.delete(listener);
|
|
3391
|
+
}
|
|
3392
|
+
status() {
|
|
3393
|
+
return this.scanStatus.snapshot();
|
|
3394
|
+
}
|
|
3395
|
+
markShuttingDown() {
|
|
3396
|
+
this.shuttingDown = true;
|
|
3397
|
+
this.cancelProgressStatuses();
|
|
3398
|
+
}
|
|
3399
|
+
/** Record a failure without publishing (initialization-time bookkeeping). */
|
|
3400
|
+
recordAgentFailure(agentName, message, sessionCount) {
|
|
3401
|
+
this.scanStatus.failAgent(agentName, message, sessionCount);
|
|
3402
|
+
}
|
|
3403
|
+
failAgent(agentName, message) {
|
|
3404
|
+
this.flushProgressStatus(`scan:${agentName}`);
|
|
3405
|
+
this.publishStatus(this.scanStatus.failAgent(agentName, message));
|
|
3406
|
+
}
|
|
3407
|
+
updateSearchIndexMaintenance(status) {
|
|
3408
|
+
this.publishStatus(this.scanStatus.updateSearchIndexMaintenance(status));
|
|
3409
|
+
}
|
|
3410
|
+
startScanBatch(agentNames, phase) {
|
|
3411
|
+
const sessionCounts = Object.fromEntries(
|
|
3412
|
+
agentNames.map((agentName) => [agentName, this.options.sessionCount(agentName) ?? 0])
|
|
3413
|
+
);
|
|
3414
|
+
this.publishStatus(this.scanStatus.startBatch(agentNames, phase, sessionCounts));
|
|
3415
|
+
}
|
|
3416
|
+
setScanPhase(phase) {
|
|
3417
|
+
this.publishStatus(this.scanStatus.setPhase(phase));
|
|
3418
|
+
}
|
|
3419
|
+
beginAgentScan(agentName) {
|
|
3420
|
+
this.cancelProgressStatus(`scan:${agentName}`);
|
|
3421
|
+
if (!this.scanStatus.snapshot().active) this.startScanBatch([agentName], "scanning");
|
|
3422
|
+
this.publishStatus(
|
|
3423
|
+
this.scanStatus.beginAgent(agentName, this.options.sessionCount(agentName) ?? 0)
|
|
3424
|
+
);
|
|
3425
|
+
}
|
|
3426
|
+
updateAgentScanProgress(agentName, progress, backfillAttempt) {
|
|
3427
|
+
const backfills = this.options.backfills;
|
|
3428
|
+
if (backfillAttempt) {
|
|
3429
|
+
const key2 = `backfill:${agentName}`;
|
|
3430
|
+
const current = backfills.stateFor(agentName);
|
|
3431
|
+
if (current?.status !== "running" || current.attemptId !== backfillAttempt.attemptId) {
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
const currentPhase2 = current.progress?.phase ?? "scanning";
|
|
3435
|
+
const nextPhase2 = progress.phase ?? "scanning";
|
|
3436
|
+
if (currentPhase2 && currentPhase2 !== nextPhase2) this.flushProgressStatus(key2);
|
|
3437
|
+
if (backfills.updateProgress(backfillAttempt, {
|
|
3438
|
+
phase: progress.phase,
|
|
3439
|
+
total: progress.total,
|
|
3440
|
+
processed: progress.processed,
|
|
3441
|
+
sessions: progress.sessions
|
|
3442
|
+
})) {
|
|
3443
|
+
this.publishProgressStatus(
|
|
3444
|
+
key2,
|
|
3445
|
+
nextPhase2,
|
|
3446
|
+
this.scanStatus.updateBackfill(backfills.status())
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
const key = `scan:${agentName}`;
|
|
3452
|
+
const currentPhase = this.scanStatus.snapshot().agentStatuses[agentName]?.status;
|
|
3453
|
+
const nextPhase = progress.phase === "finalizing" ? "finalizing" : "scanning";
|
|
3454
|
+
if (currentPhase && currentPhase !== nextPhase) this.flushProgressStatus(key);
|
|
3455
|
+
const status = this.scanStatus.updateAgent(agentName, progress);
|
|
3456
|
+
this.publishProgressStatus(key, status?.agentStatuses[agentName]?.status ?? nextPhase, status);
|
|
3457
|
+
}
|
|
3458
|
+
beginAgentPublishing(agentName) {
|
|
3459
|
+
this.flushProgressStatus(`scan:${agentName}`);
|
|
3460
|
+
this.publishStatus(this.scanStatus.publishAgent(agentName));
|
|
3461
|
+
}
|
|
3462
|
+
queueAgentPublication(agentName) {
|
|
3463
|
+
this.flushProgressStatus(`scan:${agentName}`);
|
|
3464
|
+
this.publishStatus(this.scanStatus.queueAgentPublication(agentName));
|
|
3465
|
+
}
|
|
3466
|
+
finishAgentScan(agentName, completion) {
|
|
3467
|
+
this.flushProgressStatus(`scan:${agentName}`);
|
|
3468
|
+
this.publishStatus(
|
|
3469
|
+
this.scanStatus.finishAgent(agentName, this.options.sessionCount(agentName), completion)
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
finishScanBatch() {
|
|
3473
|
+
this.flushProgressStatuses("scan:");
|
|
3474
|
+
this.publishStatus(this.scanStatus.finishBatch());
|
|
3475
|
+
}
|
|
3476
|
+
publishBackfillStatus() {
|
|
3477
|
+
this.publishStatus(this.scanStatus.updateBackfill(this.options.backfills.status()));
|
|
3478
|
+
}
|
|
3479
|
+
publishBackfillProgress(key, phase) {
|
|
3480
|
+
this.publishProgressStatus(
|
|
3481
|
+
key,
|
|
3482
|
+
phase,
|
|
3483
|
+
this.scanStatus.updateBackfill(this.options.backfills.status())
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
flushProgressStatus(key) {
|
|
3487
|
+
const throttle = this.progressThrottles.get(key);
|
|
3488
|
+
if (!throttle) return;
|
|
3489
|
+
throttle.flush();
|
|
3490
|
+
throttle.cancel();
|
|
3491
|
+
this.progressThrottles.delete(key);
|
|
3492
|
+
}
|
|
3493
|
+
cancelProgressStatus(key) {
|
|
3494
|
+
const throttle = this.progressThrottles.get(key);
|
|
3495
|
+
if (!throttle) return;
|
|
3496
|
+
throttle.cancel();
|
|
3497
|
+
this.progressThrottles.delete(key);
|
|
3498
|
+
}
|
|
3499
|
+
flushProgressStatuses(prefix) {
|
|
3500
|
+
for (const key of this.progressThrottles.keys()) {
|
|
3501
|
+
if (key.startsWith(prefix)) this.flushProgressStatus(key);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
cancelProgressStatuses() {
|
|
3505
|
+
for (const throttle of this.progressThrottles.values()) throttle.cancel();
|
|
3506
|
+
this.progressThrottles.clear();
|
|
3507
|
+
}
|
|
3508
|
+
publishStatus(event) {
|
|
3509
|
+
if (!event || this.shuttingDown) return;
|
|
3510
|
+
for (const listener of this.listeners) {
|
|
3511
|
+
try {
|
|
3512
|
+
listener(event);
|
|
3513
|
+
} catch (error) {
|
|
3514
|
+
appLogger.error("scan.status_listener.error", { error });
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
publishProgressStatus(key, phase, event) {
|
|
3519
|
+
if (!event) return;
|
|
3520
|
+
let throttle = this.progressThrottles.get(key);
|
|
3521
|
+
if (!throttle) {
|
|
3522
|
+
throttle = new LatestValueThrottle(STATUS_PROGRESS_INTERVAL_MS, () => {
|
|
3523
|
+
this.publishStatus(this.scanStatus.snapshot());
|
|
3524
|
+
});
|
|
3525
|
+
this.progressThrottles.set(key, throttle);
|
|
3526
|
+
}
|
|
3527
|
+
throttle.push(void 0, phase);
|
|
3528
|
+
}
|
|
3529
|
+
};
|
|
3530
|
+
|
|
3531
|
+
// src/search-index-publisher.ts
|
|
3532
|
+
var SearchIndexPublisher = class {
|
|
3533
|
+
constructor(options) {
|
|
3534
|
+
this.options = options;
|
|
3535
|
+
}
|
|
3536
|
+
options;
|
|
3537
|
+
nextPublicationId = 1;
|
|
3538
|
+
buildFullSearchIndexJobs(context) {
|
|
3539
|
+
const snapshot = this.options.snapshot();
|
|
3540
|
+
return snapshot.agents.flatMap((agent) => {
|
|
3541
|
+
if (!(agent.name in snapshot.byAgent)) return [];
|
|
3542
|
+
const cached = this.options.readCachedSessions(agent.name);
|
|
3543
|
+
return [
|
|
3544
|
+
cached ? {
|
|
3545
|
+
kind: "full",
|
|
3546
|
+
context,
|
|
3547
|
+
agentName: agent.name,
|
|
3548
|
+
sessions: cached.sessions,
|
|
3549
|
+
meta: cached.meta,
|
|
3550
|
+
completeness: "partial",
|
|
3551
|
+
removedSessionIds: [],
|
|
3552
|
+
searchIndexOptions: { includePendingReindex: false }
|
|
3553
|
+
} : {
|
|
3554
|
+
kind: "full",
|
|
3555
|
+
context,
|
|
3556
|
+
agentName: agent.name,
|
|
3557
|
+
sessions: snapshot.byAgent[agent.name] ?? [],
|
|
3558
|
+
meta: buildAgentCacheMeta(agent),
|
|
3559
|
+
completeness: "partial",
|
|
3560
|
+
removedSessionIds: [],
|
|
3561
|
+
searchIndexOptions: { includePendingReindex: false }
|
|
3562
|
+
}
|
|
3563
|
+
];
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
publicationId(context, agentName) {
|
|
3567
|
+
const id = this.nextPublicationId++;
|
|
3568
|
+
return agentName ? `${context}:${agentName}:${id}` : `${context}:${id}`;
|
|
3569
|
+
}
|
|
3570
|
+
async commitSearchIndex(context, jobs, details) {
|
|
3571
|
+
appLogger.info("session.publication.prepared", {
|
|
3572
|
+
publication_id: details.publicationId,
|
|
3573
|
+
context,
|
|
3574
|
+
agent: details.agent,
|
|
3575
|
+
agents: details.agents,
|
|
3576
|
+
jobs: jobs.length
|
|
3577
|
+
});
|
|
3578
|
+
try {
|
|
3579
|
+
const publicationJobs = jobs.map((job) => ({
|
|
3580
|
+
...job,
|
|
3581
|
+
publicationId: details.publicationId
|
|
3582
|
+
}));
|
|
3583
|
+
await (details.onStarted ? this.options.jobs.enqueue(context, publicationJobs, details.onStarted) : this.options.jobs.enqueue(context, publicationJobs));
|
|
3584
|
+
} catch (error) {
|
|
3585
|
+
appLogger.error("session.publication.failed", {
|
|
3586
|
+
publication_id: details.publicationId,
|
|
3587
|
+
context,
|
|
3588
|
+
agent: details.agent,
|
|
3589
|
+
stage: "search_index",
|
|
3590
|
+
error
|
|
3591
|
+
});
|
|
3592
|
+
throw error;
|
|
3593
|
+
}
|
|
3594
|
+
appLogger.info("session.publication.index_committed", {
|
|
3595
|
+
publication_id: details.publicationId,
|
|
3596
|
+
context,
|
|
3597
|
+
agent: details.agent
|
|
3598
|
+
});
|
|
2975
3599
|
}
|
|
2976
3600
|
};
|
|
2977
3601
|
|
|
@@ -2981,7 +3605,7 @@ var EMPTY_AGENT_REFRESH_DEBOUNCE_MS = 3e4;
|
|
|
2981
3605
|
var SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD = 100;
|
|
2982
3606
|
var BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
2983
3607
|
var CACHE_TRUNCATION_COVERAGE = 0.5;
|
|
2984
|
-
var
|
|
3608
|
+
var SOURCE_FAILURE_SUMMARY_MAX_LENGTH = 160;
|
|
2985
3609
|
function buildPersistenceDiff(previousSessions, nextSessions, candidateChangedIds = [], completeness = "complete", explicitRemovedSessionIds = []) {
|
|
2986
3610
|
const { changes, removedSessionIds } = computeSessionDiff(
|
|
2987
3611
|
previousSessions,
|
|
@@ -2998,9 +3622,15 @@ function buildPersistenceDiff(previousSessions, nextSessions, candidateChangedId
|
|
|
2998
3622
|
removedSessionIds: removedSessionIds.filter((sessionId) => explicitRemovals.has(sessionId))
|
|
2999
3623
|
};
|
|
3000
3624
|
}
|
|
3001
|
-
function
|
|
3002
|
-
|
|
3003
|
-
|
|
3625
|
+
function buildScanCompletion(completeness, sourceFailures) {
|
|
3626
|
+
if (sourceFailures.length === 0) return { completeness };
|
|
3627
|
+
const firstFailure = sourceFailures[0];
|
|
3628
|
+
const summary = `${firstFailure.errorClass}: ${firstFailure.message}`;
|
|
3629
|
+
return {
|
|
3630
|
+
completeness: "partial",
|
|
3631
|
+
sourceFailureCount: sourceFailures.length,
|
|
3632
|
+
sourceFailureSummary: summary.length > SOURCE_FAILURE_SUMMARY_MAX_LENGTH ? `${summary.slice(0, SOURCE_FAILURE_SUMMARY_MAX_LENGTH - 1)}\u2026` : summary
|
|
3633
|
+
};
|
|
3004
3634
|
}
|
|
3005
3635
|
function restoreAgentCacheMeta(agent, cached) {
|
|
3006
3636
|
agent.setSessionMetaMap(new Map(Object.entries(cached.meta)));
|
|
@@ -3009,10 +3639,19 @@ var AgentSyncEngine = class {
|
|
|
3009
3639
|
constructor(options) {
|
|
3010
3640
|
this.options = options;
|
|
3011
3641
|
this.scheduler = new AgentOperationScheduler((agentName) => this.performRefresh(agentName));
|
|
3642
|
+
this.statusReporter = new ScanStatusReporter({
|
|
3643
|
+
sessionCount: (agentName) => this.sessionIndex.snapshot().byAgent[agentName]?.length,
|
|
3644
|
+
backfills: this.backfills
|
|
3645
|
+
});
|
|
3012
3646
|
this.searchIndexJobs = new SearchIndexJobRunner();
|
|
3647
|
+
this.indexPublisher = new SearchIndexPublisher({
|
|
3648
|
+
jobs: this.searchIndexJobs,
|
|
3649
|
+
snapshot: () => this.sessionIndex.snapshot(),
|
|
3650
|
+
readCachedSessions: (agentName) => this.readCachedSessionsOrWarn("search.index", agentName)
|
|
3651
|
+
});
|
|
3013
3652
|
this.searchIndexMaintenance = new SearchIndexMaintenanceScheduler(
|
|
3014
3653
|
this.searchIndexJobs,
|
|
3015
|
-
(status) => this.
|
|
3654
|
+
(status) => this.statusReporter.updateSearchIndexMaintenance(status)
|
|
3016
3655
|
);
|
|
3017
3656
|
}
|
|
3018
3657
|
options;
|
|
@@ -3022,12 +3661,10 @@ var AgentSyncEngine = class {
|
|
|
3022
3661
|
backfills = new BackfillLifecycle();
|
|
3023
3662
|
cacheIntegrityValidUntilByAgent = /* @__PURE__ */ new Map();
|
|
3024
3663
|
sessionsChangedListeners = /* @__PURE__ */ new Set();
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
scanStatus = new ScanStatusModel();
|
|
3664
|
+
statusReporter;
|
|
3665
|
+
indexPublisher;
|
|
3028
3666
|
searchIndexJobs;
|
|
3029
3667
|
searchIndexMaintenance;
|
|
3030
|
-
nextPublicationId = 1;
|
|
3031
3668
|
backgroundRefreshTimer = null;
|
|
3032
3669
|
isShuttingDown = false;
|
|
3033
3670
|
initialize(snapshot, options = {}) {
|
|
@@ -3040,7 +3677,7 @@ var AgentSyncEngine = class {
|
|
|
3040
3677
|
);
|
|
3041
3678
|
}
|
|
3042
3679
|
for (const [agentName, failure] of Object.entries(snapshot.scanFailures ?? {})) {
|
|
3043
|
-
this.
|
|
3680
|
+
this.statusReporter.recordAgentFailure(
|
|
3044
3681
|
agentName,
|
|
3045
3682
|
`${failure.stage}: ${failure.message}`,
|
|
3046
3683
|
snapshot.byAgent[agentName]?.length ?? 0
|
|
@@ -3051,20 +3688,19 @@ var AgentSyncEngine = class {
|
|
|
3051
3688
|
return this.sessionIndex.snapshot();
|
|
3052
3689
|
}
|
|
3053
3690
|
status() {
|
|
3054
|
-
return this.
|
|
3691
|
+
return this.statusReporter.status();
|
|
3055
3692
|
}
|
|
3056
3693
|
subscribeSessionsChanged(listener) {
|
|
3057
3694
|
this.sessionsChangedListeners.add(listener);
|
|
3058
3695
|
return () => this.sessionsChangedListeners.delete(listener);
|
|
3059
3696
|
}
|
|
3060
3697
|
subscribeStatusChanged(listener) {
|
|
3061
|
-
this.
|
|
3062
|
-
return () => this.statusChangedListeners.delete(listener);
|
|
3698
|
+
return this.statusReporter.subscribe(listener);
|
|
3063
3699
|
}
|
|
3064
3700
|
async syncInitialIndex() {
|
|
3065
|
-
const jobs = this.buildFullSearchIndexJobs("scan.initial");
|
|
3066
|
-
await this.commitSearchIndex("scan.initial", jobs, {
|
|
3067
|
-
publicationId: this.publicationId("scan.initial"),
|
|
3701
|
+
const jobs = this.indexPublisher.buildFullSearchIndexJobs("scan.initial");
|
|
3702
|
+
await this.indexPublisher.commitSearchIndex("scan.initial", jobs, {
|
|
3703
|
+
publicationId: this.indexPublisher.publicationId("scan.initial"),
|
|
3068
3704
|
agents: jobs.map((job) => job.agentName)
|
|
3069
3705
|
});
|
|
3070
3706
|
for (const job of jobs) this.searchIndexMaintenance.enqueue(job.agentName);
|
|
@@ -3079,11 +3715,11 @@ var AgentSyncEngine = class {
|
|
|
3079
3715
|
startBackgroundRefresh() {
|
|
3080
3716
|
if (this.backgroundRefreshTimer) return;
|
|
3081
3717
|
const agentNames = this.sessionIndex.snapshot().agents.map((agent) => agent.name);
|
|
3082
|
-
this.startScanBatch(agentNames, "scanning");
|
|
3718
|
+
this.statusReporter.startScanBatch(agentNames, "scanning");
|
|
3083
3719
|
this.backgroundRefreshTimer = setTimeout(() => {
|
|
3084
3720
|
this.backgroundRefreshTimer = null;
|
|
3085
3721
|
for (const agentName of agentNames) this.scheduler.schedule(agentName, 0);
|
|
3086
|
-
if (agentNames.length === 0) this.finishScanBatch();
|
|
3722
|
+
if (agentNames.length === 0) this.statusReporter.finishScanBatch();
|
|
3087
3723
|
}, 0);
|
|
3088
3724
|
}
|
|
3089
3725
|
async refresh(agentName) {
|
|
@@ -3108,7 +3744,7 @@ var AgentSyncEngine = class {
|
|
|
3108
3744
|
}
|
|
3109
3745
|
this.backfills.cancelAll();
|
|
3110
3746
|
this.searchIndexMaintenance.stop();
|
|
3111
|
-
this.
|
|
3747
|
+
this.statusReporter.markShuttingDown();
|
|
3112
3748
|
this.cacheIntegrityValidUntilByAgent.clear();
|
|
3113
3749
|
const searchIndexSnapshot = this.searchIndexJobs.snapshot();
|
|
3114
3750
|
appLogger.info("search_index.shutdown.started", {
|
|
@@ -3125,156 +3761,95 @@ var AgentSyncEngine = class {
|
|
|
3125
3761
|
pending_batches: stoppedSearchIndexSnapshot.pendingBatches
|
|
3126
3762
|
});
|
|
3127
3763
|
}
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
const
|
|
3131
|
-
agentNames.map((agentName) => [agentName, snapshot.byAgent[agentName]?.length ?? 0])
|
|
3132
|
-
);
|
|
3133
|
-
this.publishStatus(this.scanStatus.startBatch(agentNames, phase, sessionCounts));
|
|
3134
|
-
}
|
|
3135
|
-
setScanPhase(phase) {
|
|
3136
|
-
this.publishStatus(this.scanStatus.setPhase(phase));
|
|
3137
|
-
}
|
|
3138
|
-
beginAgentScan(agentName) {
|
|
3139
|
-
this.cancelProgressStatus(`scan:${agentName}`);
|
|
3140
|
-
const snapshot = this.sessionIndex.snapshot();
|
|
3141
|
-
if (!this.scanStatus.snapshot().active) this.startScanBatch([agentName], "scanning");
|
|
3142
|
-
this.publishStatus(
|
|
3143
|
-
this.scanStatus.beginAgent(agentName, snapshot.byAgent[agentName]?.length ?? 0)
|
|
3144
|
-
);
|
|
3145
|
-
}
|
|
3146
|
-
updateAgentScanProgress(agentName, progress, backfillAttempt) {
|
|
3147
|
-
if (backfillAttempt) {
|
|
3148
|
-
const key2 = `backfill:${agentName}`;
|
|
3149
|
-
const current = this.backfills.stateFor(agentName);
|
|
3150
|
-
if (current?.status !== "running" || current.attemptId !== backfillAttempt.attemptId) {
|
|
3151
|
-
return;
|
|
3152
|
-
}
|
|
3153
|
-
const currentPhase2 = current.progress?.phase ?? "scanning";
|
|
3154
|
-
const nextPhase2 = progress.phase ?? "scanning";
|
|
3155
|
-
if (currentPhase2 && currentPhase2 !== nextPhase2) this.flushProgressStatus(key2);
|
|
3156
|
-
if (this.backfills.updateProgress(backfillAttempt, {
|
|
3157
|
-
phase: progress.phase,
|
|
3158
|
-
total: progress.total,
|
|
3159
|
-
processed: progress.processed,
|
|
3160
|
-
sessions: progress.sessions
|
|
3161
|
-
})) {
|
|
3162
|
-
this.publishProgressStatus(
|
|
3163
|
-
key2,
|
|
3164
|
-
nextPhase2,
|
|
3165
|
-
this.scanStatus.updateBackfill(this.backfills.status())
|
|
3166
|
-
);
|
|
3167
|
-
}
|
|
3168
|
-
return;
|
|
3169
|
-
}
|
|
3170
|
-
const key = `scan:${agentName}`;
|
|
3171
|
-
const currentPhase = this.scanStatus.snapshot().agentStatuses[agentName]?.status;
|
|
3172
|
-
const nextPhase = progress.phase === "finalizing" ? "finalizing" : "scanning";
|
|
3173
|
-
if (currentPhase && currentPhase !== nextPhase) this.flushProgressStatus(key);
|
|
3174
|
-
const status = this.scanStatus.updateAgent(agentName, progress);
|
|
3175
|
-
this.publishProgressStatus(key, status?.agentStatuses[agentName]?.status ?? nextPhase, status);
|
|
3176
|
-
}
|
|
3177
|
-
beginAgentPublishing(agentName) {
|
|
3178
|
-
this.flushProgressStatus(`scan:${agentName}`);
|
|
3179
|
-
this.publishStatus(this.scanStatus.publishAgent(agentName));
|
|
3180
|
-
}
|
|
3181
|
-
queueAgentPublication(agentName) {
|
|
3182
|
-
this.flushProgressStatus(`scan:${agentName}`);
|
|
3183
|
-
this.publishStatus(this.scanStatus.queueAgentPublication(agentName));
|
|
3184
|
-
}
|
|
3185
|
-
finishAgentScan(agentName) {
|
|
3186
|
-
this.flushProgressStatus(`scan:${agentName}`);
|
|
3187
|
-
const count = this.sessionIndex.snapshot().byAgent[agentName]?.length;
|
|
3188
|
-
this.publishStatus(this.scanStatus.finishAgent(agentName, count));
|
|
3189
|
-
}
|
|
3190
|
-
finishScanBatch() {
|
|
3191
|
-
this.flushProgressStatuses("scan:");
|
|
3192
|
-
this.publishStatus(this.scanStatus.finishBatch());
|
|
3193
|
-
}
|
|
3194
|
-
publishBackfillStatus() {
|
|
3195
|
-
this.publishStatus(this.scanStatus.updateBackfill(this.backfills.status()));
|
|
3196
|
-
}
|
|
3197
|
-
publishStatus(event) {
|
|
3198
|
-
if (!event || this.isShuttingDown) return;
|
|
3199
|
-
for (const listener of this.statusChangedListeners) {
|
|
3764
|
+
emitSessionsChanged(change) {
|
|
3765
|
+
if (this.isShuttingDown) return;
|
|
3766
|
+
for (const listener of this.sessionsChangedListeners) {
|
|
3200
3767
|
try {
|
|
3201
|
-
listener(
|
|
3768
|
+
listener(change);
|
|
3202
3769
|
} catch (error) {
|
|
3203
|
-
|
|
3770
|
+
this.reportPostCommitError("session.publication", change.agentName, error);
|
|
3204
3771
|
}
|
|
3205
3772
|
}
|
|
3206
3773
|
}
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
let throttle = this.statusProgressThrottles.get(key);
|
|
3210
|
-
if (!throttle) {
|
|
3211
|
-
throttle = new LatestValueThrottle(STATUS_PROGRESS_INTERVAL_MS, () => {
|
|
3212
|
-
this.publishStatus(this.scanStatus.snapshot());
|
|
3213
|
-
});
|
|
3214
|
-
this.statusProgressThrottles.set(key, throttle);
|
|
3215
|
-
}
|
|
3216
|
-
throttle.push(void 0, phase);
|
|
3217
|
-
}
|
|
3218
|
-
flushProgressStatus(key) {
|
|
3219
|
-
const throttle = this.statusProgressThrottles.get(key);
|
|
3220
|
-
if (!throttle) return;
|
|
3221
|
-
throttle.flush();
|
|
3222
|
-
throttle.cancel();
|
|
3223
|
-
this.statusProgressThrottles.delete(key);
|
|
3224
|
-
}
|
|
3225
|
-
cancelProgressStatus(key) {
|
|
3226
|
-
const throttle = this.statusProgressThrottles.get(key);
|
|
3227
|
-
if (!throttle) return;
|
|
3228
|
-
throttle.cancel();
|
|
3229
|
-
this.statusProgressThrottles.delete(key);
|
|
3774
|
+
reportPostCommitError(operation, agentName, error) {
|
|
3775
|
+
appLogger.error(`${operation}.post_commit_error`, { agent: agentName, error });
|
|
3230
3776
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3777
|
+
finishCommittedAgentScan(agentName, completion) {
|
|
3778
|
+
try {
|
|
3779
|
+
this.statusReporter.finishAgentScan(agentName, completion);
|
|
3780
|
+
} catch (error) {
|
|
3781
|
+
this.reportPostCommitError("scan.refresh", agentName, error);
|
|
3782
|
+
try {
|
|
3783
|
+
this.statusReporter.finishAgentScan(agentName, completion);
|
|
3784
|
+
} catch (recoveryError) {
|
|
3785
|
+
this.reportPostCommitError("scan.refresh", agentName, recoveryError);
|
|
3786
|
+
}
|
|
3234
3787
|
}
|
|
3235
3788
|
}
|
|
3236
|
-
cancelProgressStatuses() {
|
|
3237
|
-
for (const throttle of this.statusProgressThrottles.values()) throttle.cancel();
|
|
3238
|
-
this.statusProgressThrottles.clear();
|
|
3239
|
-
}
|
|
3240
|
-
emitSessionsChanged(change) {
|
|
3241
|
-
if (this.isShuttingDown) return;
|
|
3242
|
-
for (const listener of this.sessionsChangedListeners) listener(change);
|
|
3243
|
-
}
|
|
3244
3789
|
async performRefresh(agentName) {
|
|
3245
|
-
this.beginAgentScan(agentName);
|
|
3790
|
+
this.statusReporter.beginAgentScan(agentName);
|
|
3246
3791
|
const startedAt = performance.now();
|
|
3247
3792
|
let failed = false;
|
|
3793
|
+
let durableCommitted = false;
|
|
3248
3794
|
let cached = null;
|
|
3249
|
-
let result =
|
|
3795
|
+
let result = "failed";
|
|
3796
|
+
let completion = { completeness: "complete" };
|
|
3250
3797
|
try {
|
|
3251
|
-
if (this.findAgent(agentName))
|
|
3252
|
-
|
|
3253
|
-
|
|
3798
|
+
if (this.findAgent(agentName))
|
|
3799
|
+
cached = this.readCachedSessionsOrWarn("scan.refresh", agentName);
|
|
3800
|
+
const refresh = await this.runRefresh(agentName, cached, startedAt, (committedCompletion) => {
|
|
3801
|
+
durableCommitted = true;
|
|
3802
|
+
completion = committedCompletion;
|
|
3803
|
+
result = "committed";
|
|
3804
|
+
});
|
|
3805
|
+
result = refresh.result;
|
|
3806
|
+
completion = refresh.completion;
|
|
3254
3807
|
} catch (error) {
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3808
|
+
if (durableCommitted) {
|
|
3809
|
+
this.reportPostCommitError("scan.refresh", agentName, error);
|
|
3810
|
+
result = "committed";
|
|
3811
|
+
} else {
|
|
3812
|
+
this.options.workerRunner.discard?.(agentName);
|
|
3813
|
+
failed = true;
|
|
3814
|
+
const failure = toError2(error);
|
|
3815
|
+
if (failure instanceof AgentUnavailableDuringScanError) {
|
|
3816
|
+
appLogger.warn("scan.refresh.worker_agent_unavailable", {
|
|
3817
|
+
agent: agentName,
|
|
3818
|
+
error: failure.message
|
|
3819
|
+
});
|
|
3820
|
+
} else {
|
|
3821
|
+
appLogger.error("scan.refresh.error", { agent: agentName, error });
|
|
3822
|
+
}
|
|
3823
|
+
console.error(`[${agentName}] Session refresh failed:`, error);
|
|
3824
|
+
this.statusReporter.failAgent(agentName, failure.message);
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
try {
|
|
3828
|
+
if (!failed) {
|
|
3829
|
+
if (durableCommitted) this.finishCommittedAgentScan(agentName, completion);
|
|
3830
|
+
else this.statusReporter.finishAgentScan(agentName, completion);
|
|
3831
|
+
}
|
|
3832
|
+
} catch (error) {
|
|
3833
|
+
if (!durableCommitted) throw error;
|
|
3834
|
+
this.reportPostCommitError("scan.refresh", agentName, error);
|
|
3835
|
+
}
|
|
3836
|
+
try {
|
|
3265
3837
|
const agent = this.findAgent(agentName);
|
|
3266
3838
|
if (agent && this.needsBackfill(agent, cached, failed || result === "committed")) {
|
|
3267
3839
|
this.enqueueBackfill(agentName);
|
|
3268
3840
|
}
|
|
3269
3841
|
if (agent) this.searchIndexMaintenance.enqueue(agentName);
|
|
3842
|
+
} catch (error) {
|
|
3843
|
+
appLogger.warn("scan.refresh.backfill_probe_failed", { agent: agentName, error });
|
|
3270
3844
|
}
|
|
3845
|
+
return result;
|
|
3271
3846
|
}
|
|
3272
|
-
async runRefresh(agentName, cached, startedAt) {
|
|
3847
|
+
async runRefresh(agentName, cached, startedAt, onDurableCommit) {
|
|
3273
3848
|
const pendingPathCount = this.scheduler.takePendingSignalCount(agentName);
|
|
3274
3849
|
const agent = this.findAgent(agentName);
|
|
3275
3850
|
if (!agent) {
|
|
3276
3851
|
appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
|
|
3277
|
-
return "skipped";
|
|
3852
|
+
return { result: "skipped", completion: { completeness: "complete" } };
|
|
3278
3853
|
}
|
|
3279
3854
|
const previousSessions = this.sessionIndex.snapshot().byAgent[agentName] ?? [];
|
|
3280
3855
|
const refreshBaseline = cached?.sessions ?? previousSessions;
|
|
@@ -3288,7 +3863,7 @@ var AgentSyncEngine = class {
|
|
|
3288
3863
|
agent: agentName,
|
|
3289
3864
|
state: "initialization"
|
|
3290
3865
|
});
|
|
3291
|
-
return "unchanged";
|
|
3866
|
+
return { result: "unchanged", completion: { completeness: "complete" } };
|
|
3292
3867
|
}
|
|
3293
3868
|
const isInitialized = initialization.value;
|
|
3294
3869
|
const availabilityStartedAt = performance.now();
|
|
@@ -3313,7 +3888,10 @@ var AgentSyncEngine = class {
|
|
|
3313
3888
|
}
|
|
3314
3889
|
if (strategyResult.status === "unchanged") {
|
|
3315
3890
|
this.options.workerRunner.commit?.(agentName);
|
|
3316
|
-
return
|
|
3891
|
+
return {
|
|
3892
|
+
result: "unchanged",
|
|
3893
|
+
completion: buildScanCompletion(strategyResult.completeness, strategyResult.sourceFailures)
|
|
3894
|
+
};
|
|
3317
3895
|
}
|
|
3318
3896
|
const nextSessions = attachMissingProjectIdentities(strategyResult.nextSessions);
|
|
3319
3897
|
const searchIndexOptions = pendingPathCount >= SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD ? { isBulk: true } : void 0;
|
|
@@ -3355,7 +3933,12 @@ var AgentSyncEngine = class {
|
|
|
3355
3933
|
saveCache: true,
|
|
3356
3934
|
...searchIndexOptions ? { searchIndexOptions } : {}
|
|
3357
3935
|
};
|
|
3358
|
-
|
|
3936
|
+
const completion = buildScanCompletion(
|
|
3937
|
+
strategyResult.completeness,
|
|
3938
|
+
strategyResult.sourceFailures
|
|
3939
|
+
);
|
|
3940
|
+
this.statusReporter.queueAgentPublication(agentName);
|
|
3941
|
+
let publicationCommitted = false;
|
|
3359
3942
|
let publication;
|
|
3360
3943
|
try {
|
|
3361
3944
|
publication = await this.commitSessionPublication({
|
|
@@ -3364,12 +3947,18 @@ var AgentSyncEngine = class {
|
|
|
3364
3947
|
sessions: nextSessions,
|
|
3365
3948
|
candidateChangedIds: strategyResult.preciseChangedIds ?? [],
|
|
3366
3949
|
indexJob: persistentJob,
|
|
3367
|
-
onPublishing: () => this.beginAgentPublishing(agentName)
|
|
3950
|
+
onPublishing: () => this.statusReporter.beginAgentPublishing(agentName),
|
|
3951
|
+
onCommitted: () => {
|
|
3952
|
+
publicationCommitted = true;
|
|
3953
|
+
onDurableCommit(completion);
|
|
3954
|
+
}
|
|
3368
3955
|
});
|
|
3369
3956
|
} catch (error) {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3957
|
+
if (!publicationCommitted) {
|
|
3958
|
+
agent.setSessionMetaMap(durableMeta);
|
|
3959
|
+
if (durableLastRefreshAt == null) this.lastRefreshAtByAgent.delete(agentName);
|
|
3960
|
+
else this.lastRefreshAtByAgent.set(agentName, durableLastRefreshAt);
|
|
3961
|
+
}
|
|
3373
3962
|
throw error;
|
|
3374
3963
|
}
|
|
3375
3964
|
this.options.workerRunner.commit?.(agentName);
|
|
@@ -3402,9 +3991,8 @@ var AgentSyncEngine = class {
|
|
|
3402
3991
|
agent: agentName,
|
|
3403
3992
|
failures: strategyResult.sourceFailures
|
|
3404
3993
|
});
|
|
3405
|
-
throw sourceFailureError(strategyResult.sourceFailures);
|
|
3406
3994
|
}
|
|
3407
|
-
return "committed";
|
|
3995
|
+
return { result: "committed", completion };
|
|
3408
3996
|
}
|
|
3409
3997
|
/**
|
|
3410
3998
|
* An agent that cannot be reached was never scanned, so there is nothing to
|
|
@@ -3425,7 +4013,7 @@ var AgentSyncEngine = class {
|
|
|
3425
4013
|
return this.refreshStrategyResult([], "partial", {});
|
|
3426
4014
|
}
|
|
3427
4015
|
async initializeAgent(agent, previousSessions) {
|
|
3428
|
-
this.setScanPhase("initializing");
|
|
4016
|
+
this.statusReporter.setScanPhase("initializing");
|
|
3429
4017
|
const scanStartedAt = performance.now();
|
|
3430
4018
|
const scope = this.startupScanOptions();
|
|
3431
4019
|
const result = await this.runWorker(agent, previousSessions, { kind: "full-scan" }, scope);
|
|
@@ -3483,12 +4071,13 @@ var AgentSyncEngine = class {
|
|
|
3483
4071
|
{ status: "unchanged", checkDuration }
|
|
3484
4072
|
);
|
|
3485
4073
|
}
|
|
3486
|
-
this.lastRefreshAtByAgent.set(agent.name, checkResult.timestamp);
|
|
3487
4074
|
if (!checkResult.hasChanges) {
|
|
3488
4075
|
const scanStartedAt2 = performance.now();
|
|
3489
4076
|
const result = await this.runWorker(agent, baseline, { kind: "recompute-derived" }, {});
|
|
3490
4077
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
3491
4078
|
const sessions2 = attachMissingProjectIdentities(result.sessions);
|
|
4079
|
+
agent.commitChangeCheck();
|
|
4080
|
+
this.lastRefreshAtByAgent.set(agent.name, checkResult.timestamp);
|
|
3492
4081
|
const persistenceDiff = buildPersistenceDiff(baseline, sessions2);
|
|
3493
4082
|
if (persistenceDiff.changedSessions.length === 0 && persistenceDiff.removedSessionIds.length === 0) {
|
|
3494
4083
|
this.logUnchangedRefresh(agent.name, refreshStartedAt);
|
|
@@ -3521,6 +4110,8 @@ var AgentSyncEngine = class {
|
|
|
3521
4110
|
const result = await this.runWorker(agent, baseline, { kind: "full-scan" }, scope);
|
|
3522
4111
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
3523
4112
|
const sessions2 = attachMissingProjectIdentities(result.sessions);
|
|
4113
|
+
agent.commitChangeCheck();
|
|
4114
|
+
this.lastRefreshAtByAgent.set(agent.name, checkResult.timestamp);
|
|
3524
4115
|
return this.refreshStrategyResult(sessions2, result.completeness, scope, {
|
|
3525
4116
|
// Meta-only changes (e.g. a pricing capture epoch bump) leave the head
|
|
3526
4117
|
// signature intact; without the worker-reported ids they would never
|
|
@@ -3543,6 +4134,8 @@ var AgentSyncEngine = class {
|
|
|
3543
4134
|
agent.incrementalScan(baseline, preciseChangedIds, checkResult.refs, scope)
|
|
3544
4135
|
)
|
|
3545
4136
|
);
|
|
4137
|
+
agent.commitChangeCheck();
|
|
4138
|
+
this.lastRefreshAtByAgent.set(agent.name, checkResult.timestamp);
|
|
3546
4139
|
const sourceFailures = checkResult.sourceFailures ?? [];
|
|
3547
4140
|
const completeness = scope.from == null && scope.to == null && sourceFailures.length === 0 ? "complete" : "partial";
|
|
3548
4141
|
return this.refreshStrategyResult(sessions, completeness, scope, {
|
|
@@ -3595,19 +4188,40 @@ var AgentSyncEngine = class {
|
|
|
3595
4188
|
operation,
|
|
3596
4189
|
scanOptions,
|
|
3597
4190
|
meta: runOptions.meta ?? buildAgentCacheMeta(agent),
|
|
3598
|
-
onProgress: (progress) => this.updateAgentScanProgress(
|
|
4191
|
+
onProgress: (progress) => this.statusReporter.updateAgentScanProgress(
|
|
4192
|
+
agent.name,
|
|
4193
|
+
progress,
|
|
4194
|
+
runOptions.backfillAttempt
|
|
4195
|
+
),
|
|
3599
4196
|
onCheckpoint: runOptions.onCheckpoint
|
|
3600
4197
|
});
|
|
3601
4198
|
}
|
|
3602
4199
|
handleBackfillCheckpoint(agentName, checkpoint) {
|
|
3603
4200
|
if (checkpoint.stage !== "finalizing" || !checkpoint.backfillCursor) return;
|
|
3604
|
-
markAgentFullSyncProgress(agentName, checkpoint.backfillCursor)
|
|
4201
|
+
if (!markAgentFullSyncProgress(agentName, checkpoint.backfillCursor)) {
|
|
4202
|
+
appLogger.warn("scan.backfill.checkpoint_not_durable", {
|
|
4203
|
+
agent: agentName,
|
|
4204
|
+
cursor: checkpoint.backfillCursor
|
|
4205
|
+
});
|
|
4206
|
+
return;
|
|
4207
|
+
}
|
|
3605
4208
|
appLogger.debug("scan.backfill.checkpoint", {
|
|
3606
4209
|
agent: agentName,
|
|
3607
4210
|
cursor: checkpoint.backfillCursor,
|
|
3608
4211
|
sessions: checkpoint.changes.length
|
|
3609
4212
|
});
|
|
3610
4213
|
}
|
|
4214
|
+
readCachedSessionsOrWarn(scope, agentName) {
|
|
4215
|
+
const outcome = readCachedSessions(agentName);
|
|
4216
|
+
if (outcome.status === "failed") {
|
|
4217
|
+
appLogger.warn(`${scope}.cache_state_unavailable`, {
|
|
4218
|
+
agent: agentName,
|
|
4219
|
+
state: "cached_sessions"
|
|
4220
|
+
});
|
|
4221
|
+
return null;
|
|
4222
|
+
}
|
|
4223
|
+
return outcome.value;
|
|
4224
|
+
}
|
|
3611
4225
|
needsBackfill(agent, cached, reloadCached = false) {
|
|
3612
4226
|
const startupScanOptions = this.startupScanOptions();
|
|
3613
4227
|
if (startupScanOptions.from == null && startupScanOptions.to == null) return false;
|
|
@@ -3627,7 +4241,18 @@ var AgentSyncEngine = class {
|
|
|
3627
4241
|
}
|
|
3628
4242
|
if (!(agent instanceof FileSystemSessionSource)) return false;
|
|
3629
4243
|
if (!agent.isAvailable()) return false;
|
|
3630
|
-
|
|
4244
|
+
let cachedSessions = cached;
|
|
4245
|
+
if (reloadCached || cached === void 0) {
|
|
4246
|
+
const outcome = readCachedSessions(agent.name);
|
|
4247
|
+
if (outcome.status === "failed") {
|
|
4248
|
+
appLogger.warn("scan.backfill.cache_state_unavailable", {
|
|
4249
|
+
agent: agent.name,
|
|
4250
|
+
state: "cached_sessions"
|
|
4251
|
+
});
|
|
4252
|
+
return false;
|
|
4253
|
+
}
|
|
4254
|
+
cachedSessions = outcome.value;
|
|
4255
|
+
}
|
|
3631
4256
|
const sourceCount = agent.listSessionSources().length;
|
|
3632
4257
|
const cachedCount = cachedSessions?.sessions.length ?? 0;
|
|
3633
4258
|
this.cacheIntegrityValidUntilByAgent.set(agent.name, lastSyncAt + BACKFILL_INTERVAL_MS);
|
|
@@ -3644,21 +4269,21 @@ var AgentSyncEngine = class {
|
|
|
3644
4269
|
}
|
|
3645
4270
|
enqueueBackfill(agentName) {
|
|
3646
4271
|
if (this.isShuttingDown || !this.backfills.enqueue(agentName)) return;
|
|
3647
|
-
this.publishBackfillStatus();
|
|
4272
|
+
this.statusReporter.publishBackfillStatus();
|
|
3648
4273
|
this.pumpBackfillQueue();
|
|
3649
4274
|
}
|
|
3650
4275
|
pumpBackfillQueue() {
|
|
3651
4276
|
if (this.isShuttingDown) return;
|
|
3652
4277
|
const attempt = this.backfills.startNext();
|
|
3653
4278
|
if (!attempt) return;
|
|
3654
|
-
this.publishBackfillStatus();
|
|
4279
|
+
this.statusReporter.publishBackfillStatus();
|
|
3655
4280
|
void this.runBackfill(attempt).then((result) => this.completeBackfillAttempt(attempt, result)).catch((error) => this.rejectBackfillAttempt(attempt, error));
|
|
3656
4281
|
}
|
|
3657
4282
|
completeBackfillAttempt(attempt, result) {
|
|
3658
4283
|
if (this.isShuttingDown) return;
|
|
3659
4284
|
const current = this.backfills.stateFor(attempt.agentName);
|
|
3660
4285
|
if (current?.status === "running" && current.attemptId === attempt.attemptId) {
|
|
3661
|
-
this.flushProgressStatus(`backfill:${attempt.agentName}`);
|
|
4286
|
+
this.statusReporter.flushProgressStatus(`backfill:${attempt.agentName}`);
|
|
3662
4287
|
if (this.backfills.complete(attempt, result)) {
|
|
3663
4288
|
if (result === "failed") {
|
|
3664
4289
|
this.cacheIntegrityValidUntilByAgent.delete(attempt.agentName);
|
|
@@ -3668,7 +4293,7 @@ var AgentSyncEngine = class {
|
|
|
3668
4293
|
Date.now() + BACKFILL_INTERVAL_MS
|
|
3669
4294
|
);
|
|
3670
4295
|
}
|
|
3671
|
-
this.publishBackfillStatus();
|
|
4296
|
+
this.statusReporter.publishBackfillStatus();
|
|
3672
4297
|
}
|
|
3673
4298
|
}
|
|
3674
4299
|
this.pumpBackfillQueue();
|
|
@@ -3686,14 +4311,19 @@ var AgentSyncEngine = class {
|
|
|
3686
4311
|
const agent = this.findAgent(agentName);
|
|
3687
4312
|
if (!agent || !agent.isAvailable()) return "skipped";
|
|
3688
4313
|
const snapshot = this.sessionIndex.snapshot();
|
|
3689
|
-
const cached =
|
|
4314
|
+
const cached = this.readCachedSessionsOrWarn("scan.backfill", agentName);
|
|
3690
4315
|
const baseline = cached?.sessions ?? snapshot.byAgent[agentName] ?? [];
|
|
3691
4316
|
const meta = cached?.meta ?? buildAgentCacheMeta(agent);
|
|
3692
4317
|
const backfillCursor = getAgentFullSyncCursor(agentName);
|
|
3693
4318
|
if (cached) restoreAgentCacheMeta(agent, cached);
|
|
3694
4319
|
let durableCommitted = false;
|
|
3695
4320
|
try {
|
|
3696
|
-
markAgentFullSyncStarted(agentName)
|
|
4321
|
+
if (!markAgentFullSyncStarted(agentName)) {
|
|
4322
|
+
appLogger.warn("scan.backfill.cache_state_unavailable", {
|
|
4323
|
+
agent: agentName,
|
|
4324
|
+
state: "full_sync_started"
|
|
4325
|
+
});
|
|
4326
|
+
}
|
|
3697
4327
|
const operation = agent instanceof FileSystemSessionSource ? { kind: "source-backfill", cursor: backfillCursor, checkpoint: "durable" } : { kind: "full-backfill", cursor: backfillCursor, checkpoint: "durable" };
|
|
3698
4328
|
appLogger.info("scan.backfill.started", {
|
|
3699
4329
|
agent: agentName,
|
|
@@ -3713,21 +4343,18 @@ var AgentSyncEngine = class {
|
|
|
3713
4343
|
);
|
|
3714
4344
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
3715
4345
|
const fullSessions = attachMissingProjectIdentities(result.sessions);
|
|
3716
|
-
|
|
4346
|
+
const completion = buildScanCompletion(result.completeness, result.sourceFailures ?? []);
|
|
4347
|
+
this.statusReporter.flushProgressStatus(`backfill:${agentName}`);
|
|
3717
4348
|
const updatePublicationPhase = (phase) => {
|
|
3718
4349
|
if (this.backfills.updateProgress(attempt, {
|
|
3719
4350
|
phase,
|
|
3720
4351
|
sessions: fullSessions.length
|
|
3721
4352
|
})) {
|
|
3722
|
-
this.
|
|
3723
|
-
`backfill:${agentName}`,
|
|
3724
|
-
phase,
|
|
3725
|
-
this.scanStatus.updateBackfill(this.backfills.status())
|
|
3726
|
-
);
|
|
4353
|
+
this.statusReporter.publishBackfillProgress(`backfill:${agentName}`, phase);
|
|
3727
4354
|
}
|
|
3728
4355
|
};
|
|
3729
4356
|
updatePublicationPhase("publish-queued");
|
|
3730
|
-
await this.commitSessionPublication({
|
|
4357
|
+
const publication = await this.commitSessionPublication({
|
|
3731
4358
|
context: "scan.backfill",
|
|
3732
4359
|
agentName,
|
|
3733
4360
|
sessions: fullSessions,
|
|
@@ -3742,92 +4369,54 @@ var AgentSyncEngine = class {
|
|
|
3742
4369
|
removedSessionIds: result.explicitRemovedSessionIds,
|
|
3743
4370
|
saveCache: true
|
|
3744
4371
|
},
|
|
3745
|
-
onPublishing: () => updatePublicationPhase("publishing")
|
|
4372
|
+
onPublishing: () => updatePublicationPhase("publishing"),
|
|
4373
|
+
onCommitted: () => {
|
|
4374
|
+
durableCommitted = true;
|
|
4375
|
+
if (completion.completeness === "partial") {
|
|
4376
|
+
this.backfills.recordCompletion(attempt, completion);
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
3746
4379
|
});
|
|
3747
|
-
durableCommitted =
|
|
4380
|
+
durableCommitted = publication.durableCommitted;
|
|
3748
4381
|
this.options.workerRunner.commit?.(agentName);
|
|
3749
|
-
if (
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
4382
|
+
if (!markAgentFullSyncCompleted(agentName)) {
|
|
4383
|
+
appLogger.warn("scan.backfill.completion_not_durable", { agent: agentName });
|
|
4384
|
+
}
|
|
4385
|
+
appLogger.info(
|
|
4386
|
+
result.sourceFailures?.length ? "scan.backfill.partial" : "scan.backfill.done",
|
|
4387
|
+
{
|
|
4388
|
+
agent: agentName,
|
|
4389
|
+
duration_ms: Math.round(performance.now() - startedAt),
|
|
4390
|
+
sessions: fullSessions.length,
|
|
4391
|
+
changed: result.changedIds?.length ?? 0,
|
|
4392
|
+
failed_sources: result.sourceFailures?.length ?? 0
|
|
4393
|
+
}
|
|
4394
|
+
);
|
|
4395
|
+
if (result.sourceFailures?.length) {
|
|
4396
|
+
appLogger.warn("scan.backfill.source_failures", {
|
|
4397
|
+
agent: agentName,
|
|
4398
|
+
failures: result.sourceFailures
|
|
4399
|
+
});
|
|
4400
|
+
}
|
|
3757
4401
|
return "committed";
|
|
3758
4402
|
} catch (error) {
|
|
3759
|
-
if (
|
|
4403
|
+
if (durableCommitted) {
|
|
4404
|
+
this.reportPostCommitError("scan.backfill", agentName, error);
|
|
4405
|
+
return "committed";
|
|
4406
|
+
}
|
|
4407
|
+
agent.setSessionMetaMap(new Map(Object.entries(meta)));
|
|
3760
4408
|
this.options.workerRunner.discard?.(agentName);
|
|
3761
4409
|
appLogger.error("scan.backfill.error", { agent: agentName, error });
|
|
3762
4410
|
console.error(`[${agentName}] Backfill failed:`, error);
|
|
3763
4411
|
return "failed";
|
|
3764
4412
|
}
|
|
3765
4413
|
}
|
|
3766
|
-
buildFullSearchIndexJobs(context) {
|
|
3767
|
-
const snapshot = this.sessionIndex.snapshot();
|
|
3768
|
-
return snapshot.agents.flatMap((agent) => {
|
|
3769
|
-
if (!(agent.name in snapshot.byAgent)) return [];
|
|
3770
|
-
const cached = loadCachedSessions(agent.name);
|
|
3771
|
-
return [
|
|
3772
|
-
cached ? {
|
|
3773
|
-
kind: "full",
|
|
3774
|
-
context,
|
|
3775
|
-
agentName: agent.name,
|
|
3776
|
-
sessions: cached.sessions,
|
|
3777
|
-
meta: cached.meta,
|
|
3778
|
-
completeness: "partial",
|
|
3779
|
-
removedSessionIds: [],
|
|
3780
|
-
searchIndexOptions: { includePendingReindex: false }
|
|
3781
|
-
} : {
|
|
3782
|
-
kind: "full",
|
|
3783
|
-
context,
|
|
3784
|
-
agentName: agent.name,
|
|
3785
|
-
sessions: snapshot.byAgent[agent.name] ?? [],
|
|
3786
|
-
meta: buildAgentCacheMeta(agent),
|
|
3787
|
-
completeness: "partial",
|
|
3788
|
-
removedSessionIds: [],
|
|
3789
|
-
searchIndexOptions: { includePendingReindex: false }
|
|
3790
|
-
}
|
|
3791
|
-
];
|
|
3792
|
-
});
|
|
3793
|
-
}
|
|
3794
|
-
publicationId(context, agentName) {
|
|
3795
|
-
const id = this.nextPublicationId++;
|
|
3796
|
-
return agentName ? `${context}:${agentName}:${id}` : `${context}:${id}`;
|
|
3797
|
-
}
|
|
3798
|
-
async commitSearchIndex(context, jobs, details) {
|
|
3799
|
-
appLogger.info("session.publication.prepared", {
|
|
3800
|
-
publication_id: details.publicationId,
|
|
3801
|
-
context,
|
|
3802
|
-
agent: details.agent,
|
|
3803
|
-
agents: details.agents,
|
|
3804
|
-
jobs: jobs.length
|
|
3805
|
-
});
|
|
3806
|
-
try {
|
|
3807
|
-
const publicationJobs = jobs.map((job) => ({
|
|
3808
|
-
...job,
|
|
3809
|
-
publicationId: details.publicationId
|
|
3810
|
-
}));
|
|
3811
|
-
await (details.onStarted ? this.searchIndexJobs.enqueue(context, publicationJobs, details.onStarted) : this.searchIndexJobs.enqueue(context, publicationJobs));
|
|
3812
|
-
} catch (error) {
|
|
3813
|
-
appLogger.error("session.publication.failed", {
|
|
3814
|
-
publication_id: details.publicationId,
|
|
3815
|
-
context,
|
|
3816
|
-
agent: details.agent,
|
|
3817
|
-
stage: "search_index",
|
|
3818
|
-
error
|
|
3819
|
-
});
|
|
3820
|
-
throw error;
|
|
3821
|
-
}
|
|
3822
|
-
appLogger.info("session.publication.index_committed", {
|
|
3823
|
-
publication_id: details.publicationId,
|
|
3824
|
-
context,
|
|
3825
|
-
agent: details.agent
|
|
3826
|
-
});
|
|
3827
|
-
}
|
|
3828
4414
|
async commitSessionPublication(publication) {
|
|
3829
|
-
const publicationId = this.publicationId(
|
|
3830
|
-
|
|
4415
|
+
const publicationId = this.indexPublisher.publicationId(
|
|
4416
|
+
publication.context,
|
|
4417
|
+
publication.agentName
|
|
4418
|
+
);
|
|
4419
|
+
await this.indexPublisher.commitSearchIndex(publication.context, [publication.indexJob], {
|
|
3831
4420
|
publicationId,
|
|
3832
4421
|
agent: publication.agentName,
|
|
3833
4422
|
...publication.onPublishing ? { onStarted: publication.onPublishing } : {}
|
|
@@ -3839,6 +4428,8 @@ var AgentSyncEngine = class {
|
|
|
3839
4428
|
publication.candidateChangedIds
|
|
3840
4429
|
);
|
|
3841
4430
|
const diffDuration = performance.now() - diffStartedAt;
|
|
4431
|
+
const result = { durableCommitted: true, event, diffDuration };
|
|
4432
|
+
publication.onCommitted?.(result);
|
|
3842
4433
|
this.emitSessionsChanged({
|
|
3843
4434
|
agentName: publication.agentName,
|
|
3844
4435
|
sessions: this.sessionIndex.snapshot().byAgent[publication.agentName] ?? [],
|
|
@@ -3851,7 +4442,7 @@ var AgentSyncEngine = class {
|
|
|
3851
4442
|
sessions: publication.sessions.length,
|
|
3852
4443
|
has_event: event != null
|
|
3853
4444
|
});
|
|
3854
|
-
return
|
|
4445
|
+
return result;
|
|
3855
4446
|
}
|
|
3856
4447
|
findAgent(agentName) {
|
|
3857
4448
|
return this.sessionIndex.findAgent(agentName);
|
|
@@ -4158,7 +4749,11 @@ var SessionWatcher = class {
|
|
|
4158
4749
|
emitAgentsChanged(agentNames) {
|
|
4159
4750
|
if (agentNames.size === 0) return;
|
|
4160
4751
|
for (const listener of this.listeners) {
|
|
4161
|
-
|
|
4752
|
+
try {
|
|
4753
|
+
listener(new Set(agentNames));
|
|
4754
|
+
} catch (error) {
|
|
4755
|
+
appLogger.error("watch.agents_listener.error", { error });
|
|
4756
|
+
}
|
|
4162
4757
|
}
|
|
4163
4758
|
}
|
|
4164
4759
|
reportWatchError(event, data) {
|
|
@@ -4168,8 +4763,8 @@ var SessionWatcher = class {
|
|
|
4168
4763
|
};
|
|
4169
4764
|
|
|
4170
4765
|
// src/worker-runner.ts
|
|
4171
|
-
import { Worker as
|
|
4172
|
-
var
|
|
4766
|
+
import { Worker as Worker3 } from "worker_threads";
|
|
4767
|
+
var SHUTDOWN_ERROR_MESSAGE3 = "Scan refresh worker shut down";
|
|
4173
4768
|
function applySessionChanges2(previousSessions, changes, removedSessionIds) {
|
|
4174
4769
|
if (changes.length === 0 && removedSessionIds.length === 0) return previousSessions;
|
|
4175
4770
|
const replacedIds = new Set(removedSessionIds);
|
|
@@ -4216,7 +4811,7 @@ var ThreadWorkerRunner = class {
|
|
|
4216
4811
|
return count;
|
|
4217
4812
|
}
|
|
4218
4813
|
run(agentName, payload) {
|
|
4219
|
-
if (this.isShuttingDown) return Promise.reject(new Error(
|
|
4814
|
+
if (this.isShuttingDown) return Promise.reject(new Error(SHUTDOWN_ERROR_MESSAGE3));
|
|
4220
4815
|
const existingSlot = this.workers.get(agentName);
|
|
4221
4816
|
if (existingSlot && (existingSlot.pending.size > 0 || existingSlot.awaitingCommit)) {
|
|
4222
4817
|
return Promise.reject(new Error(`Scan refresh worker for ${agentName} is busy`));
|
|
@@ -4239,7 +4834,7 @@ var ThreadWorkerRunner = class {
|
|
|
4239
4834
|
try {
|
|
4240
4835
|
slot = this.createWorker(agentName, request);
|
|
4241
4836
|
} catch (error) {
|
|
4242
|
-
reject(
|
|
4837
|
+
reject(toError2(error));
|
|
4243
4838
|
return;
|
|
4244
4839
|
}
|
|
4245
4840
|
}
|
|
@@ -4256,7 +4851,7 @@ var ThreadWorkerRunner = class {
|
|
|
4256
4851
|
slot.worker.postMessage(request);
|
|
4257
4852
|
} catch (error) {
|
|
4258
4853
|
slot.pending.delete(request.requestId);
|
|
4259
|
-
reject(
|
|
4854
|
+
reject(toError2(error));
|
|
4260
4855
|
this.invalidateWorker(agentName, slot);
|
|
4261
4856
|
}
|
|
4262
4857
|
}
|
|
@@ -4287,7 +4882,7 @@ var ThreadWorkerRunner = class {
|
|
|
4287
4882
|
this.isShuttingDown = true;
|
|
4288
4883
|
const slots = [...this.workers.values()];
|
|
4289
4884
|
this.workers.clear();
|
|
4290
|
-
const shutdownError = new Error(
|
|
4885
|
+
const shutdownError = new Error(SHUTDOWN_ERROR_MESSAGE3);
|
|
4291
4886
|
for (const slot of slots) {
|
|
4292
4887
|
slot.closed = true;
|
|
4293
4888
|
for (const pending of slot.pending.values()) pending.reject(shutdownError);
|
|
@@ -4296,7 +4891,7 @@ var ThreadWorkerRunner = class {
|
|
|
4296
4891
|
await Promise.allSettled(slots.map((slot) => slot.worker.terminate()));
|
|
4297
4892
|
}
|
|
4298
4893
|
createWorker(agentName, request) {
|
|
4299
|
-
const worker = new
|
|
4894
|
+
const worker = new Worker3(this.workerUrl, { workerData: request });
|
|
4300
4895
|
const slot = {
|
|
4301
4896
|
agentName,
|
|
4302
4897
|
worker,
|
|
@@ -4312,7 +4907,7 @@ var ThreadWorkerRunner = class {
|
|
|
4312
4907
|
this.handleMessage(slot, message);
|
|
4313
4908
|
});
|
|
4314
4909
|
worker.on("error", (error) => {
|
|
4315
|
-
this.closeWorker(agentName, slot,
|
|
4910
|
+
this.closeWorker(agentName, slot, toError2(error));
|
|
4316
4911
|
});
|
|
4317
4912
|
worker.on("exit", (code) => {
|
|
4318
4913
|
if (slot.closed) return;
|
|
@@ -4345,14 +4940,16 @@ var ThreadWorkerRunner = class {
|
|
|
4345
4940
|
pending.onCheckpoint?.(message.checkpoint);
|
|
4346
4941
|
} catch (error) {
|
|
4347
4942
|
slot.pending.delete(message.requestId);
|
|
4348
|
-
pending.reject(
|
|
4943
|
+
pending.reject(toError2(error));
|
|
4349
4944
|
this.invalidateWorker(slot.agentName, slot);
|
|
4350
4945
|
}
|
|
4351
4946
|
return;
|
|
4352
4947
|
}
|
|
4353
4948
|
slot.pending.delete(message.requestId);
|
|
4354
4949
|
if (message.type === "error") {
|
|
4355
|
-
pending.reject(
|
|
4950
|
+
pending.reject(
|
|
4951
|
+
message.errorCode === AGENT_UNAVAILABLE_DURING_SCAN_ERROR_CODE ? new AgentUnavailableDuringScanError(slot.agentName) : new Error(message.error)
|
|
4952
|
+
);
|
|
4356
4953
|
this.invalidateWorker(slot.agentName, slot);
|
|
4357
4954
|
return;
|
|
4358
4955
|
}
|
|
@@ -4378,7 +4975,7 @@ var ThreadWorkerRunner = class {
|
|
|
4378
4975
|
};
|
|
4379
4976
|
pending.resolve(result);
|
|
4380
4977
|
} catch (error) {
|
|
4381
|
-
pending.reject(
|
|
4978
|
+
pending.reject(toError2(error));
|
|
4382
4979
|
this.invalidateWorker(slot.agentName, slot);
|
|
4383
4980
|
}
|
|
4384
4981
|
}
|
|
@@ -4530,7 +5127,13 @@ var LiveScanStore = class {
|
|
|
4530
5127
|
this.emitNow(event);
|
|
4531
5128
|
}
|
|
4532
5129
|
emitNow(event) {
|
|
4533
|
-
for (const listener of this.listeners)
|
|
5130
|
+
for (const listener of this.listeners) {
|
|
5131
|
+
try {
|
|
5132
|
+
listener(event);
|
|
5133
|
+
} catch (error) {
|
|
5134
|
+
appLogger.error("scan.sessions_listener.error", { error });
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
4534
5137
|
}
|
|
4535
5138
|
queueEvent(event) {
|
|
4536
5139
|
this.pendingEvent = this.pendingEvent ? mergeSessionsUpdatedEvents(this.pendingEvent, event) : event;
|
|
@@ -4590,6 +5193,11 @@ function formatScanFailureDiagnostics(snapshot) {
|
|
|
4590
5193
|
return `[${failure.agentName}] Scan failed during ${failure.stage}${source} (${failure.errorClass}): ${failure.message}`;
|
|
4591
5194
|
});
|
|
4592
5195
|
}
|
|
5196
|
+
function formatCacheFailureDiagnostics(snapshot) {
|
|
5197
|
+
return Object.values(snapshot.cacheFailures ?? {}).map(
|
|
5198
|
+
(failure) => `[${failure.agentName}] Cache persistence failed; serving in-memory results without advancing the durable baseline`
|
|
5199
|
+
);
|
|
5200
|
+
}
|
|
4593
5201
|
function buildSessionIndexOutput(snapshot, window = {}) {
|
|
4594
5202
|
const sessions = filterSessionTreeByActivityWindow(snapshot.sessions, window.from, window.to);
|
|
4595
5203
|
const agents = getAgentInfoMap(
|
|
@@ -4683,6 +5291,10 @@ function startPricingRefresh(timeoutMs = REFRESH_TIMEOUT_MS) {
|
|
|
4683
5291
|
}
|
|
4684
5292
|
|
|
4685
5293
|
// src/index.ts
|
|
5294
|
+
process.on("unhandledRejection", (reason) => {
|
|
5295
|
+
appLogger.error("process.unhandled_rejection", { error: reason });
|
|
5296
|
+
throw reason;
|
|
5297
|
+
});
|
|
4686
5298
|
var main = defineCommand({
|
|
4687
5299
|
meta: {
|
|
4688
5300
|
name: "codesesh",
|
|
@@ -4808,15 +5420,19 @@ var main = defineCommand({
|
|
|
4808
5420
|
}
|
|
4809
5421
|
appLogger.info("cli.start", {
|
|
4810
5422
|
version: VERSION,
|
|
4811
|
-
argv: process.argv.slice(2),
|
|
4812
5423
|
port,
|
|
5424
|
+
host: hostname,
|
|
5425
|
+
transport: transport.kind,
|
|
5426
|
+
remote_access: remoteAccess,
|
|
4813
5427
|
json: jsonOnly,
|
|
4814
5428
|
no_open: noOpen,
|
|
4815
5429
|
cache: useCache,
|
|
5430
|
+
clear_cache: clearCache,
|
|
5431
|
+
trace,
|
|
4816
5432
|
log_path: appLogger.getLogPath()
|
|
4817
5433
|
});
|
|
4818
5434
|
if (clearCache) {
|
|
4819
|
-
const { clearCache: clear } = await import("./dist-
|
|
5435
|
+
const { clearCache: clear } = await import("./dist-RHDY2EZX.js");
|
|
4820
5436
|
clear();
|
|
4821
5437
|
appLogger.info("cache.clear");
|
|
4822
5438
|
console.log("Cache cleared.");
|
|
@@ -4873,6 +5489,7 @@ var main = defineCommand({
|
|
|
4873
5489
|
process.exitCode = 1;
|
|
4874
5490
|
return;
|
|
4875
5491
|
}
|
|
5492
|
+
for (const diagnostic of formatCacheFailureDiagnostics(result)) console.error(diagnostic);
|
|
4876
5493
|
const output = buildSessionIndexOutput(result, { from: listDefaultFrom, to: listDefaultTo });
|
|
4877
5494
|
appLogger.info("cli.json_output", {
|
|
4878
5495
|
sessions: output.sessions.length,
|
|
@@ -4912,12 +5529,14 @@ var main = defineCommand({
|
|
|
4912
5529
|
await app.shutdown();
|
|
4913
5530
|
process.exit(0);
|
|
4914
5531
|
};
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
}
|
|
5532
|
+
const shutdownOnSignal = (signal) => {
|
|
5533
|
+
shutdown(signal).catch((error) => {
|
|
5534
|
+
appLogger.error("cli.shutdown_failed", { signal, error });
|
|
5535
|
+
process.exit(1);
|
|
5536
|
+
});
|
|
5537
|
+
};
|
|
5538
|
+
process.once("SIGINT", shutdownOnSignal);
|
|
5539
|
+
process.once("SIGTERM", shutdownOnSignal);
|
|
4921
5540
|
console.log(` ${url}`);
|
|
4922
5541
|
console.log("");
|
|
4923
5542
|
appLogger.info("cli.ready", {
|