codesesh 1.0.5 → 1.0.7

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.
Files changed (54) hide show
  1. package/README.md +1 -0
  2. package/dist/{chunk-PAP2Q3WV.js → chunk-DE6UJMOY.js} +2 -2
  3. package/dist/{chunk-YQFEGFWD.js → chunk-EYZ73CYP.js} +2 -2
  4. package/dist/{chunk-NBJJGH5P.js → chunk-QHWSZFRM.js} +118 -74
  5. package/dist/chunk-QHWSZFRM.js.map +1 -0
  6. package/dist/{chunk-WUYSZAYR.js → chunk-SII3M66U.js} +13 -6
  7. package/dist/chunk-SII3M66U.js.map +1 -0
  8. package/dist/{chunk-TS3IWH5U.js → chunk-VVCSQU5B.js} +120 -20
  9. package/dist/chunk-VVCSQU5B.js.map +1 -0
  10. package/dist/{chunk-FCYCBGEL.js → chunk-WIWK7BXS.js} +3 -3
  11. package/dist/{chunk-XKZSX5BM.js → chunk-YEQTRALH.js} +3 -3
  12. package/dist/{discovery-D23RV7FY.js → discovery-4WAEYSTR.js} +7 -5
  13. package/dist/index.js +217 -150
  14. package/dist/index.js.map +1 -1
  15. package/dist/project-identity-worker.js +4 -4
  16. package/dist/scan-refresh-worker.js +6 -6
  17. package/dist/search-index-worker.js +6 -6
  18. package/dist/session-detail-worker.js +6 -6
  19. package/dist/smart-tag-worker.js +3 -3
  20. package/dist/web/assets/CustomTimeWindowDialog-CVURkiVO.js +1 -0
  21. package/dist/web/assets/InteractiveReceipt-DGC-xV7U.js +1 -0
  22. package/dist/web/assets/OverviewScreen-DoXrZ5qp.js +1 -0
  23. package/dist/web/assets/{PrismHighlighter-LWITDffT.js → PrismHighlighter-02695Lmx.js} +1 -1
  24. package/dist/web/assets/Projects-DZiYh6hp.js +1 -0
  25. package/dist/web/assets/SearchResultsPanel-CAQe8Di0.js +1 -0
  26. package/dist/web/assets/SessionDetail-Da8EBygt.js +53 -0
  27. package/dist/web/assets/api-ToidoNW6.js +1 -0
  28. package/dist/web/assets/index-Bl8pbRFj.js +1423 -0
  29. package/dist/web/assets/index-CRI1m3xN.css +1 -0
  30. package/dist/web/assets/session-indexes-D5gps7K8.js +4 -0
  31. package/dist/web/assets/translate-BDFu3ZiK.js +2 -0
  32. package/dist/web/assets/utils-BIZi2up3.js +1 -0
  33. package/dist/web/index.html +7 -7
  34. package/package.json +3 -3
  35. package/dist/chunk-NBJJGH5P.js.map +0 -1
  36. package/dist/chunk-TS3IWH5U.js.map +0 -1
  37. package/dist/chunk-WUYSZAYR.js.map +0 -1
  38. package/dist/web/assets/CustomTimeWindowDialog-C9XTZRm0.js +0 -1
  39. package/dist/web/assets/InteractiveReceipt-Sca1smpv.js +0 -1
  40. package/dist/web/assets/OverviewScreen-Vq6BQR9_.js +0 -1
  41. package/dist/web/assets/Projects-CwicV3Dk.js +0 -1
  42. package/dist/web/assets/SearchResultsPanel-DCO0Ak0c.js +0 -1
  43. package/dist/web/assets/SessionDetail-mdmTQgo1.js +0 -53
  44. package/dist/web/assets/api-CXVT9XcZ.js +0 -1
  45. package/dist/web/assets/contract-BS78VBCV.js +0 -2
  46. package/dist/web/assets/index-C5cyw58A.css +0 -1
  47. package/dist/web/assets/index-H2cT-6c6.js +0 -1423
  48. package/dist/web/assets/session-indexes-yyyWpKLf.js +0 -4
  49. package/dist/web/assets/utils-C7BqyxxV.js +0 -1
  50. /package/dist/{chunk-PAP2Q3WV.js.map → chunk-DE6UJMOY.js.map} +0 -0
  51. /package/dist/{chunk-YQFEGFWD.js.map → chunk-EYZ73CYP.js.map} +0 -0
  52. /package/dist/{chunk-FCYCBGEL.js.map → chunk-WIWK7BXS.js.map} +0 -0
  53. /package/dist/{chunk-XKZSX5BM.js.map → chunk-YEQTRALH.js.map} +0 -0
  54. /package/dist/{discovery-D23RV7FY.js.map → discovery-4WAEYSTR.js.map} +0 -0
package/dist/index.js CHANGED
@@ -4,19 +4,19 @@ import {
4
4
  buildDashboard,
5
5
  getSessionActivityTime,
6
6
  summarizeProjects
7
- } from "./chunk-FCYCBGEL.js";
7
+ } from "./chunk-WIWK7BXS.js";
8
8
  import {
9
9
  AGENT_UNAVAILABLE_DURING_SCAN_ERROR_CODE,
10
10
  AgentUnavailableDuringScanError
11
11
  } from "./chunk-RF3EEWPH.js";
12
12
  import {
13
13
  terminateWorkerAfterLogDrain
14
- } from "./chunk-YQFEGFWD.js";
14
+ } from "./chunk-EYZ73CYP.js";
15
15
  import "./chunk-QH2HOXJH.js";
16
16
  import {
17
17
  appLogger,
18
18
  logSearchIndexSync
19
- } from "./chunk-PAP2Q3WV.js";
19
+ } from "./chunk-DE6UJMOY.js";
20
20
  import {
21
21
  attachMissingProjectIdentities,
22
22
  beginAgentRefresh,
@@ -29,6 +29,7 @@ import {
29
29
  getAnalyticsRevision,
30
30
  getSearchProjectDirectory,
31
31
  listCachedProjectGroups,
32
+ listDashboardActiveHours,
32
33
  listDashboardCostFacts,
33
34
  listFileActivity,
34
35
  loadCachedSessionHeads,
@@ -50,18 +51,18 @@ import {
50
51
  sessionMatchesSearchCost,
51
52
  sessionSignature,
52
53
  sortSessions
53
- } from "./chunk-TS3IWH5U.js";
54
+ } from "./chunk-VVCSQU5B.js";
54
55
  import {
55
56
  PROJECT_IDENTITY_RESOLVER_REVISION,
56
57
  createProjectScopeMatcher,
57
58
  createProjectScopeMatcherFromIdentity,
58
59
  matchesProjectScope,
59
60
  normalizeProjectDirectory
60
- } from "./chunk-XKZSX5BM.js";
61
+ } from "./chunk-YEQTRALH.js";
61
62
  import {
62
63
  createRegisteredAgents,
63
64
  getAgentInfoMap
64
- } from "./chunk-WUYSZAYR.js";
65
+ } from "./chunk-SII3M66U.js";
65
66
  import {
66
67
  CODESESH_OPERATION_ID_HEADER,
67
68
  CODESESH_REQUEST_ID_HEADER,
@@ -96,7 +97,7 @@ import {
96
97
  tableExists,
97
98
  toPublicReferencedSessionHead,
98
99
  toPublicSessionHead
99
- } from "./chunk-NBJJGH5P.js";
100
+ } from "./chunk-QHWSZFRM.js";
100
101
 
101
102
  // src/index.ts
102
103
  import { defineCommand, runMain } from "citty";
@@ -574,14 +575,8 @@ function parseDateWindowRequest(c, endpoint, defaults) {
574
575
 
575
576
  // src/api/snapshot-pagination.ts
576
577
  import { createHash, randomUUID } from "crypto";
577
- var identityVersions = /* @__PURE__ */ new WeakMap();
578
- function identityVersion(identity) {
579
- const existing = identityVersions.get(identity);
580
- if (existing) return existing;
581
- const version = randomUUID();
582
- identityVersions.set(identity, version);
583
- return version;
584
- }
578
+ var SNAPSHOT_TTL_MS = 6e4;
579
+ var SNAPSHOT_LIMIT = 32;
585
580
  function queryFingerprint(params) {
586
581
  const canonical = new URLSearchParams(params);
587
582
  canonical.delete("cursor");
@@ -598,7 +593,7 @@ function decodeCursor(cursor) {
598
593
  const value = JSON.parse(
599
594
  Buffer.from(cursor, "base64url").toString("utf8")
600
595
  );
601
- if (value.version !== 1 || typeof value.snapshot !== "string" || typeof value.view !== "string" || typeof value.query !== "string" || !Number.isSafeInteger(value.offset) || value.offset <= 0) {
596
+ if (value.version !== 1 || typeof value.snapshot !== "string" || !Number.isSafeInteger(value.offset) || value.offset <= 0) {
602
597
  return null;
603
598
  }
604
599
  return value;
@@ -606,26 +601,47 @@ function decodeCursor(cursor) {
606
601
  return null;
607
602
  }
608
603
  }
609
- function paginateSnapshot(items, request) {
610
- const snapshot = identityVersion(request.snapshotIdentity);
611
- const view = identityVersion(request.viewIdentity);
612
- const query = queryFingerprint(request.query);
613
- let offset = 0;
614
- if (request.cursor) {
615
- const cursor = decodeCursor(request.cursor);
616
- if (!cursor || cursor.query !== query) return { kind: "invalid_cursor" };
617
- if (cursor.snapshot !== snapshot || cursor.view !== view) {
618
- return { kind: "stale_snapshot" };
604
+ function createSnapshotPaginator() {
605
+ const snapshotsBySource = /* @__PURE__ */ new WeakMap();
606
+ return function paginateSnapshot(source, request, load) {
607
+ const snapshots = snapshotsBySource.get(source) ?? /* @__PURE__ */ new Map();
608
+ const now = Date.now();
609
+ for (const [id, snapshot2] of snapshots) {
610
+ if (snapshot2.expiresAt <= now) snapshots.delete(id);
611
+ }
612
+ const query = queryFingerprint(request.query);
613
+ let snapshot;
614
+ let snapshotId;
615
+ let offset = 0;
616
+ if (request.cursor) {
617
+ const cursor = decodeCursor(request.cursor);
618
+ if (!cursor) return { kind: "invalid_cursor" };
619
+ const retained = snapshots.get(cursor.snapshot);
620
+ if (!retained) return { kind: "stale_snapshot" };
621
+ if (retained.query !== query || cursor.offset >= retained.items.length) {
622
+ return { kind: "invalid_cursor" };
623
+ }
624
+ snapshot = retained;
625
+ snapshotId = cursor.snapshot;
626
+ offset = cursor.offset;
627
+ } else {
628
+ const loaded = load();
629
+ snapshot = { ...loaded, items: [...loaded.items], query, expiresAt: now + SNAPSHOT_TTL_MS };
630
+ snapshotId = randomUUID();
619
631
  }
620
- if (cursor.offset >= items.length) return { kind: "invalid_cursor" };
621
- offset = cursor.offset;
622
- }
623
- const end = Math.min(offset + request.limit, items.length);
624
- const nextCursor = end < items.length ? encodeCursor({ version: 1, snapshot, view, query, offset: end }) : void 0;
625
- return {
626
- kind: "page",
627
- items: items.slice(offset, end),
628
- ...nextCursor ? { nextCursor } : {}
632
+ const end = Math.min(offset + request.limit, snapshot.items.length);
633
+ const nextCursor = end < snapshot.items.length ? encodeCursor({ version: 1, snapshot: snapshotId, offset: end }) : void 0;
634
+ if (!request.cursor && nextCursor) {
635
+ if (snapshots.size >= SNAPSHOT_LIMIT) snapshots.delete(snapshots.keys().next().value);
636
+ snapshots.set(snapshotId, snapshot);
637
+ snapshotsBySource.set(source, snapshots);
638
+ }
639
+ return {
640
+ kind: "page",
641
+ items: snapshot.items.slice(offset, end),
642
+ view: snapshot.view,
643
+ ...nextCursor ? { nextCursor } : {}
644
+ };
629
645
  };
630
646
  }
631
647
 
@@ -663,18 +679,20 @@ function getSnapshotSessionTree(source, sessions) {
663
679
  const cache = getSnapshotAggregationCache(source, sessions);
664
680
  return cache.sessionTree ??= buildSessionTree(sessions);
665
681
  }
666
- function getDashboardCostFacts(source, sessions, from, to, cacheTo, analyticsRevision) {
667
- const build = () => listDashboardCostFacts({ from, to });
682
+ function getSnapshotCostFacts(source, sessions, from, to, cacheTo, analyticsRevision) {
683
+ const costFrom = from === 0 ? void 0 : from;
684
+ const build = () => listDashboardCostFacts({ from: costFrom, to });
668
685
  if (analyticsRevision === null) return build();
669
686
  return getSnapshotAggregation(
670
687
  source,
671
688
  sessions,
672
- ["dashboard-cost-facts", from, cacheTo, analyticsRevision],
689
+ ["cost-facts", costFrom, cacheTo, analyticsRevision],
673
690
  build
674
691
  );
675
692
  }
676
- function getDashboardStorageAggregation(source, sessions, scope, from, to, cacheTo, analyticsRevision) {
693
+ function getDashboardStorageAggregation(source, sessions, scope, from, to, cacheTo, analyticsRevision, timeZone) {
677
694
  const build = () => ({
695
+ activeHours: listDashboardActiveHours({ ...scope, from, to, timeZone }, source.queryScope),
678
696
  recentFileActivities: listFileActivity(
679
697
  {
680
698
  agent: scope.agent,
@@ -705,6 +723,7 @@ function getDashboardStorageAggregation(source, sessions, scope, from, to, cache
705
723
  sessions,
706
724
  [
707
725
  "dashboard-storage",
726
+ timeZone,
708
727
  scope.agent,
709
728
  scope.projectKind,
710
729
  scope.projectKey,
@@ -718,6 +737,7 @@ function getDashboardStorageAggregation(source, sessions, scope, from, to, cache
718
737
  }
719
738
 
720
739
  // src/api/catalog-handlers.ts
740
+ var paginateProjects = createSnapshotPaginator();
721
741
  function handleGetConfig(c, defaults) {
722
742
  const payload = {
723
743
  window: {
@@ -770,46 +790,54 @@ function handleGetProjects(c, scanSource, defaults = {}) {
770
790
  const window = parseDateWindowRequest(c, "projects", defaults);
771
791
  if (window.kind === "rejected") return window.response;
772
792
  const { from, to } = window;
773
- const analyticsRevision = getAnalyticsRevision();
774
- const catalog = getSnapshotAggregation(
793
+ const loadSnapshot = () => {
794
+ const analyticsRevision = getAnalyticsRevision();
795
+ const catalog = getSnapshotAggregation(
796
+ scanSource,
797
+ scanResult.sessions,
798
+ ["projects", from, to, analyticsRevision],
799
+ () => {
800
+ const tree = getSnapshotSessionTree(scanSource, scanResult.sessions);
801
+ const costFacts = (from == null || from === 0) && to == null ? getSnapshotCostFacts(scanSource, scanResult.sessions, from, to, to, analyticsRevision) : listDashboardCostFacts({ from, to, includeModelCosts: false });
802
+ const projects2 = attachProjectMetricsFromTree(
803
+ listCachedProjectGroups(scanResult.sessions),
804
+ tree,
805
+ from,
806
+ to,
807
+ costFacts
808
+ ).filter(
809
+ (project) => project.sessionCount > 0 || project.messages > 0 || project.tokens > 0 || project.cost > 0
810
+ );
811
+ return { projects: projects2, summary: summarizeProjects(projects2) };
812
+ }
813
+ );
814
+ const projects = projectIdentity ? catalog.projects.filter(
815
+ (project) => project.identityKind === projectIdentity.kind && project.identityKey === projectIdentity.key
816
+ ) : catalog.projects;
817
+ return {
818
+ items: projects,
819
+ view: projectIdentity ? summarizeProjects(projects) : catalog.summary
820
+ };
821
+ };
822
+ const page = paginateProjects(
775
823
  scanSource,
776
- scanResult.sessions,
777
- ["projects", from, to, analyticsRevision],
778
- () => {
779
- const tree = getSnapshotSessionTree(scanSource, scanResult.sessions);
780
- const costFacts = listDashboardCostFacts({ from, to, includeModelCosts: false });
781
- const projects2 = attachProjectMetricsFromTree(
782
- listCachedProjectGroups(scanResult.sessions),
783
- tree,
784
- from,
785
- to,
786
- costFacts
787
- ).filter(
788
- (project) => project.sessionCount > 0 || project.messages > 0 || project.tokens > 0 || project.cost > 0
789
- );
790
- return { projects: projects2, summary: summarizeProjects(projects2) };
791
- }
824
+ {
825
+ cursor: params.get("cursor") ?? void 0,
826
+ limit: limit.value,
827
+ query: params
828
+ },
829
+ loadSnapshot
792
830
  );
793
- const projects = projectIdentity ? catalog.projects.filter(
794
- (project) => project.identityKind === projectIdentity.kind && project.identityKey === projectIdentity.key
795
- ) : catalog.projects;
796
- const page = paginateSnapshot(projects, {
797
- cursor: params.get("cursor") ?? void 0,
798
- limit: limit.value,
799
- query: params,
800
- snapshotIdentity: scanResult.sessions,
801
- viewIdentity: catalog.projects
802
- });
803
831
  if (page.kind === "invalid_cursor") {
804
832
  reportInvalidQueryParameter("projects", "cursor", "rejected");
805
833
  return c.json({ error: "cursor is invalid for this request" }, 400);
806
834
  }
807
835
  if (page.kind === "stale_snapshot") {
808
- return c.json({ error: "project snapshot changed; restart pagination" }, 409);
836
+ return c.json({ error: "project snapshot expired; restart pagination" }, 409);
809
837
  }
810
838
  return c.json({
811
839
  projects: page.items,
812
- summary: projectIdentity ? summarizeProjects(projects) : catalog.summary,
840
+ summary: page.view,
813
841
  ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
814
842
  });
815
843
  }
@@ -1453,12 +1481,13 @@ function readAliasView() {
1453
1481
  }
1454
1482
  }
1455
1483
  var EMPTY_ALIAS_VIEW = buildAliasView(/* @__PURE__ */ new Map());
1484
+ var ALIAS_VIEW_TTL_MS = 1e3;
1456
1485
  var cachedView = null;
1457
1486
  function loadAliasView() {
1458
- if (cachedView) return cachedView;
1487
+ if (cachedView && Date.now() < cachedView.expiresAt) return cachedView.view;
1459
1488
  const loaded = readAliasView();
1460
1489
  if (!loaded) return EMPTY_ALIAS_VIEW;
1461
- cachedView = loaded;
1490
+ cachedView = { view: loaded, expiresAt: Date.now() + ALIAS_VIEW_TTL_MS };
1462
1491
  return loaded;
1463
1492
  }
1464
1493
  function invalidateAliasView() {
@@ -1518,6 +1547,14 @@ function findAliasSearchResults(query, options, scanResult, aliases, context = {
1518
1547
 
1519
1548
  // src/api/dashboard-handler.ts
1520
1549
  function handleGetDashboard(c, scanSource, defaults = {}) {
1550
+ let timeZone;
1551
+ try {
1552
+ timeZone = new Intl.DateTimeFormat("en", {
1553
+ timeZone: optionalQueryValue(c.req.query("timeZone")) ?? Intl.DateTimeFormat().resolvedOptions().timeZone
1554
+ }).resolvedOptions().timeZone;
1555
+ } catch {
1556
+ return c.json({ error: "timeZone must be a valid IANA time zone" }, 400);
1557
+ }
1521
1558
  const scanResult = scanSource.getSnapshot();
1522
1559
  const projectIdentity = parseProjectIdentityFilter(
1523
1560
  c.req.query("projectKind"),
@@ -1543,12 +1580,12 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
1543
1580
  const fixedTo = dateWindow.to;
1544
1581
  const cacheTo = fixedTo ?? startOfCalendarDay(to);
1545
1582
  const analyticsRevision = getAnalyticsRevision();
1546
- const costFacts = getDashboardCostFacts(
1583
+ const costFacts = getSnapshotCostFacts(
1547
1584
  scanSource,
1548
1585
  scanResult.sessions,
1549
1586
  compare?.from ?? from,
1550
- to,
1551
- cacheTo,
1587
+ from == null && fixedTo == null ? void 0 : to,
1588
+ from == null && fixedTo == null ? void 0 : cacheTo,
1552
1589
  analyticsRevision
1553
1590
  );
1554
1591
  const aggregate = getSnapshotAggregation(
@@ -1586,7 +1623,8 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
1586
1623
  from,
1587
1624
  to,
1588
1625
  cacheTo,
1589
- analyticsRevision
1626
+ analyticsRevision,
1627
+ timeZone
1590
1628
  );
1591
1629
  const data = {
1592
1630
  ...aggregate,
@@ -1932,6 +1970,7 @@ function createSessionDetailJsonResponse(data, messages) {
1932
1970
  }
1933
1971
 
1934
1972
  // src/api/session-handlers.ts
1973
+ var paginateSessions = createSnapshotPaginator();
1935
1974
  function getSessionHeadReference(session) {
1936
1975
  return session.reference;
1937
1976
  }
@@ -1973,64 +2012,72 @@ async function handleGetSessions(c, scanSource, defaults = {}, resolver) {
1973
2012
  if (sessionQuery.agent.kind === "unknown") {
1974
2013
  reportInvalidQueryParameter("sessions", "agent", "empty_result");
1975
2014
  }
1976
- const agentFilter = sessionQuery.agent.kind === "known" ? sessionQuery.agent.agentName : sessionQuery.agent.kind;
1977
- let sessions = getSnapshotAggregation(
1978
- scanSource,
1979
- scanResult.sessions,
1980
- [
1981
- "sessions",
1982
- agentFilter,
1983
- projectIdentity?.kind,
1984
- projectIdentity?.key,
1985
- projectScope?.identity.kind,
1986
- projectScope?.identity.key,
1987
- projectScope?.path,
1988
- tag,
1989
- from,
1990
- to
1991
- ],
1992
- () => {
1993
- let filtered = sessionQuery.agent.kind === "all" ? scanResult.sessions : sessionQuery.agent.kind === "known" ? scanResult.byAgent[sessionQuery.agent.agentName] ?? [] : [];
1994
- if (projectIdentity) {
1995
- filtered = filtered.filter(
1996
- (session) => matchesProjectIdentity(session.project_identity, projectIdentity)
1997
- );
1998
- } else if (projectScope) {
1999
- filtered = filtered.filter((session) => matchesProjectScope(session, projectScope));
2015
+ const loadSnapshot = () => {
2016
+ const agentFilter = sessionQuery.agent.kind === "known" ? sessionQuery.agent.agentName : sessionQuery.agent.kind;
2017
+ let sessions2 = getSnapshotAggregation(
2018
+ scanSource,
2019
+ scanResult.sessions,
2020
+ [
2021
+ "sessions",
2022
+ agentFilter,
2023
+ projectIdentity?.kind,
2024
+ projectIdentity?.key,
2025
+ projectScope?.identity.kind,
2026
+ projectScope?.identity.key,
2027
+ projectScope?.path,
2028
+ tag,
2029
+ from,
2030
+ to
2031
+ ],
2032
+ () => {
2033
+ let filtered = sessionQuery.agent.kind === "all" ? scanResult.sessions : sessionQuery.agent.kind === "known" ? scanResult.byAgent[sessionQuery.agent.agentName] ?? [] : [];
2034
+ if (projectIdentity) {
2035
+ filtered = filtered.filter(
2036
+ (session) => matchesProjectIdentity(session.project_identity, projectIdentity)
2037
+ );
2038
+ } else if (projectScope) {
2039
+ filtered = filtered.filter(
2040
+ (session) => matchesProjectScope(session, projectScope)
2041
+ );
2042
+ }
2043
+ filtered = filterSessionsByActivityWindow(filtered, from, to);
2044
+ return tag ? filtered.filter((session) => session.smart_tags?.includes(tag)) : filtered;
2000
2045
  }
2001
- filtered = filterSessionsByActivityWindow(filtered, from, to);
2002
- return tag ? filtered.filter((session) => session.smart_tags?.includes(tag)) : filtered;
2046
+ );
2047
+ const aliases2 = loadAliasView();
2048
+ if (q) {
2049
+ sessions2 = sessions2.filter((session) => {
2050
+ const alias = aliases2.get(getSessionHeadReference(session));
2051
+ return session.title.toLowerCase().includes(q) || alias?.toLowerCase().includes(q);
2052
+ });
2003
2053
  }
2004
- );
2005
- const aliases = loadAliasView();
2006
- if (q) {
2007
- sessions = sessions.filter((session) => {
2008
- const alias = aliases.get(getSessionHeadReference(session));
2009
- return session.title.toLowerCase().includes(q) || alias?.toLowerCase().includes(q);
2010
- });
2011
- }
2054
+ return { items: sessions2, view: aliases2 };
2055
+ };
2012
2056
  if (paginationRequested) {
2013
- const page = paginateSnapshot(sessions, {
2014
- cursor: params.get("cursor") ?? void 0,
2015
- limit: sessionQuery.limit.value,
2016
- query: params,
2017
- snapshotIdentity: scanResult.sessions,
2018
- viewIdentity: aliases
2019
- });
2057
+ const page = paginateSessions(
2058
+ scanSource,
2059
+ {
2060
+ cursor: params.get("cursor") ?? void 0,
2061
+ limit: sessionQuery.limit.value,
2062
+ query: params
2063
+ },
2064
+ loadSnapshot
2065
+ );
2020
2066
  if (page.kind === "invalid_cursor") {
2021
2067
  reportInvalidQueryParameter("sessions", "cursor", "rejected");
2022
2068
  return c.json({ error: "cursor is invalid for this request" }, 400);
2023
2069
  }
2024
2070
  if (page.kind === "stale_snapshot") {
2025
- return c.json({ error: "session snapshot changed; restart pagination" }, 409);
2071
+ return c.json({ error: "session snapshot expired; restart pagination" }, 409);
2026
2072
  }
2027
2073
  return c.json({
2028
2074
  sessions: page.items.map(
2029
- (session) => toPublicSessionHead(aliases.decorate(session, getSessionHeadReference(session)))
2075
+ (session) => toPublicSessionHead(page.view.decorate(session, getSessionHeadReference(session)))
2030
2076
  ),
2031
2077
  ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
2032
2078
  });
2033
2079
  }
2080
+ const { items: sessions, view: aliases } = loadSnapshot();
2034
2081
  return c.json({
2035
2082
  sessions: sessions.map(
2036
2083
  (session) => toPublicSessionHead(aliases.decorate(session, getSessionHeadReference(session)))
@@ -2061,7 +2108,8 @@ async function handleGetSessionData(c, scanSource, loadDetail = materializeSessi
2061
2108
  session_id: sessionId,
2062
2109
  duration_ms: Math.round(performance.now() - startedAt)
2063
2110
  });
2064
- return c.json({ error: "Session cache not ready" }, 404);
2111
+ c.header("Retry-After", "1");
2112
+ return c.json({ error: "Session detail not ready; retry later" }, 503);
2065
2113
  }
2066
2114
  appLogger.info("api.session_data", {
2067
2115
  agent: agentName,
@@ -2248,7 +2296,7 @@ function handleGetBookmarks(c, scanSource) {
2248
2296
  storageAvailable: true
2249
2297
  });
2250
2298
  },
2251
- () => c.json({ bookmarks: [], storageAvailable: false })
2299
+ () => c.json({ error: "Bookmark storage is unavailable" }, 503)
2252
2300
  );
2253
2301
  }
2254
2302
  function isKnownAgentKey(agentKey) {
@@ -2548,7 +2596,19 @@ function createSseResponse(eventSource, requestSignal, shutdownSignal, closeConn
2548
2596
  buffer.enqueue("connected", { timestamp: Date.now() });
2549
2597
  buffer.enqueueScanStatus(eventSource.getScanStatus());
2550
2598
  unsubscribeSessions = eventSource.subscribe((event) => {
2551
- buffer?.enqueue(event.type, event);
2599
+ if (isClosed) return;
2600
+ const aliases = loadAliasView();
2601
+ buffer?.enqueue(event.type, {
2602
+ ...event,
2603
+ changedSessionHeads: event.changedSessionHeads.map((change) => ({
2604
+ ...change,
2605
+ session: aliases.decorate(change.session, change.reference)
2606
+ })),
2607
+ projectionRelatedSessionHeads: event.projectionRelatedSessionHeads?.map((change) => ({
2608
+ ...change,
2609
+ session: aliases.decorate(change.session, change.reference)
2610
+ }))
2611
+ });
2552
2612
  });
2553
2613
  unsubscribeScanStatus = eventSource.subscribeScanStatus((event) => {
2554
2614
  buffer?.enqueueScanStatus(event);
@@ -5588,31 +5648,31 @@ var AgentSyncEngine = class {
5588
5648
  try {
5589
5649
  const { result } = workerRun;
5590
5650
  const sessions2 = attachMissingProjectIdentities(result.sessions);
5651
+ const persistenceDiff = buildSessionPersistenceDiff(baseline, sessions2, {
5652
+ // Worker-reported IDs also preserve metadata-only pricing repairs.
5653
+ candidateChangedIds: result.changedIds ?? [],
5654
+ completeness: result.completeness,
5655
+ explicitRemovedSessionIds: result.explicitRemovedSessionIds
5656
+ });
5657
+ const strategy = this.refreshStrategyBase(sessions2, result.completeness, scope, {
5658
+ checkDuration,
5659
+ scanDuration: performance.now() - scanStartedAt,
5660
+ sourceFailures: result.sourceFailures ?? [],
5661
+ workerRun,
5662
+ pendingAgentState: {
5663
+ meta: result.meta,
5664
+ refreshedAt: checkResult.timestamp,
5665
+ refreshTransaction
5666
+ }
5667
+ });
5668
+ if (persistenceDiff.changedSessions.length === 0 && persistenceDiff.removedSessionIds.length === 0 && (result.sourceFailures?.length ?? 0) === 0) {
5669
+ this.logUnchangedRefresh(agent.name, refreshStartedAt);
5670
+ return { ...strategy, status: "unchanged" };
5671
+ }
5591
5672
  return {
5592
- ...this.refreshStrategyBase(sessions2, result.completeness, scope, {
5593
- checkDuration,
5594
- scanDuration: performance.now() - scanStartedAt,
5595
- sourceFailures: result.sourceFailures ?? [],
5596
- workerRun,
5597
- pendingAgentState: {
5598
- meta: result.meta,
5599
- refreshedAt: checkResult.timestamp,
5600
- refreshTransaction
5601
- }
5602
- }),
5673
+ ...strategy,
5603
5674
  status: "continue",
5604
- publication: {
5605
- kind: "changes",
5606
- // Meta-only changes (e.g. a pricing capture epoch bump) leave the head
5607
- // signature intact; without the worker-reported ids they would never
5608
- // persist and checkForChanges would rescan on every startup.
5609
- diff: buildSessionPersistenceDiff(baseline, sessions2, {
5610
- candidateChangedIds: result.changedIds ?? [],
5611
- completeness: result.completeness,
5612
- explicitRemovedSessionIds: result.explicitRemovedSessionIds
5613
- }),
5614
- candidateChangedIds: []
5615
- }
5675
+ publication: { kind: "changes", diff: persistenceDiff, candidateChangedIds: [] }
5616
5676
  };
5617
5677
  } catch (error) {
5618
5678
  workerRun.discard();
@@ -5835,7 +5895,7 @@ var AgentSyncEngine = class {
5835
5895
 
5836
5896
  // src/session-watcher.ts
5837
5897
  import { existsSync as existsSync3, readdirSync, statSync, watch } from "fs";
5838
- import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "path";
5898
+ import { basename, dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "path";
5839
5899
  var WRITE_STABILITY_THRESHOLD_MS = 250;
5840
5900
  var WRITE_STABILITY_POLL_MS = 100;
5841
5901
  function toAbsolutePath(path) {
@@ -5941,7 +6001,11 @@ var SessionWatcher = class {
5941
6001
  const targetPath = toAbsolutePath(target.path);
5942
6002
  const scopes = scopesByRoot.get(rootPath) ?? [];
5943
6003
  if (!scopes.some((scope) => scope.agentName === agent.name && scope.targetPath === targetPath)) {
5944
- scopes.push({ agentName: agent.name, targetPath });
6004
+ scopes.push({
6005
+ agentName: agent.name,
6006
+ targetPath,
6007
+ ignoredFileNames: target.ignoredFileNames
6008
+ });
5945
6009
  }
5946
6010
  scopesByRoot.set(rootPath, scopes);
5947
6011
  }
@@ -6060,7 +6124,9 @@ var SessionWatcher = class {
6060
6124
  handleWatchEvent(watchPath, scopes, eventType, filename) {
6061
6125
  const changedPath = resolveWatchEventPath(watchPath, filename);
6062
6126
  const agentNames = new Set(
6063
- scopes.filter((scope) => isRelatedPath(changedPath, scope.targetPath)).map((scope) => scope.agentName)
6127
+ scopes.filter(
6128
+ (scope) => isRelatedPath(changedPath, scope.targetPath) && !(scope.ignoredFileNames?.includes(basename(changedPath)) && changedPath !== scope.targetPath && isSameOrChildPath(scope.targetPath, changedPath))
6129
+ ).map((scope) => scope.agentName)
6064
6130
  );
6065
6131
  if (agentNames.size === 0) {
6066
6132
  return;
@@ -6712,7 +6778,8 @@ function startPricingRefresh(timeoutMs = REFRESH_TIMEOUT_MS) {
6712
6778
  }
6713
6779
  );
6714
6780
  return {
6715
- publish() {
6781
+ async ready() {
6782
+ await completion;
6716
6783
  if (publishPendingPricing()) appLogger.info("pricing.refresh.published", {});
6717
6784
  },
6718
6785
  async cancel() {
@@ -6886,7 +6953,7 @@ var main = defineCommand({
6886
6953
  log_path: appLogger.getLogPath()
6887
6954
  });
6888
6955
  if (clearCache) {
6889
- const { clearCache: clear } = await import("./discovery-D23RV7FY.js");
6956
+ const { clearCache: clear } = await import("./discovery-4WAEYSTR.js");
6890
6957
  clear();
6891
6958
  appLogger.info("cache.clear");
6892
6959
  console.log("Cache cleared.");
@@ -6915,6 +6982,7 @@ var main = defineCommand({
6915
6982
  { currentWorkingDirectory: process.cwd() }
6916
6983
  );
6917
6984
  const { from: listDefaultFrom, to: listDefaultTo, days: listDefaultDays } = listWindow;
6985
+ await pricingRefresh.ready();
6918
6986
  const store = new LiveScanStore({
6919
6987
  watchEnabled: !jsonOnly,
6920
6988
  scanOptions,
@@ -6978,7 +7046,6 @@ var main = defineCommand({
6978
7046
  }
6979
7047
  const { url } = app;
6980
7048
  if (!jsonOnly) {
6981
- pricingRefresh.publish();
6982
7049
  store.startBackgroundRefresh();
6983
7050
  }
6984
7051
  let shuttingDown = false;