codesesh 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,7 @@ import {
25
25
  createProjectScopeMatcher,
26
26
  createRegisteredAgents,
27
27
  deleteBookmark,
28
+ ensureSessionTagsSync,
28
29
  estimateCostForTokens,
29
30
  estimateTokenCost,
30
31
  extractFileActivityOccurrences,
@@ -39,7 +40,9 @@ import {
39
40
  firstVisibleLine,
40
41
  getAgentByName,
41
42
  getAgentInfoMap,
43
+ getAgentLastFullSyncAt,
42
44
  getCacheInfo,
45
+ getCachePath2,
43
46
  getCursorDataPath,
44
47
  getParsedSession,
45
48
  getPricingRegistry,
@@ -59,7 +62,9 @@ import {
59
62
  loadCachedSessionData,
60
63
  loadCachedSessions,
61
64
  markAgentCacheInitialized,
65
+ markAgentFullSyncCompleted,
62
66
  matchesProjectScope,
67
+ matchesScanWindow,
63
68
  normalizeGitRemote,
64
69
  normalizeTitleText,
65
70
  openDb,
@@ -83,6 +88,7 @@ import {
83
88
  searchFileActivitySessions,
84
89
  searchSessions,
85
90
  sessionSignature,
91
+ setFtsIntegrityCheckedPath,
86
92
  skipped,
87
93
  skippedSession,
88
94
  sortSessions,
@@ -93,7 +99,7 @@ import {
93
99
  toLocalDateKey,
94
100
  upsertBookmark,
95
101
  withEstimatedSessionCost
96
- } from "./chunk-BIXOP5QX.js";
102
+ } from "./chunk-GCOAE7KI.js";
97
103
  export {
98
104
  BaseAgent,
99
105
  BookmarkStorageUnavailableError,
@@ -120,6 +126,7 @@ export {
120
126
  createProjectScopeMatcher,
121
127
  createRegisteredAgents,
122
128
  deleteBookmark,
129
+ ensureSessionTagsSync,
123
130
  estimateCostForTokens,
124
131
  estimateTokenCost,
125
132
  extractFileActivityOccurrences,
@@ -134,7 +141,9 @@ export {
134
141
  firstVisibleLine,
135
142
  getAgentByName,
136
143
  getAgentInfoMap,
144
+ getAgentLastFullSyncAt,
137
145
  getCacheInfo,
146
+ getCachePath2 as getCachePath,
138
147
  getCursorDataPath,
139
148
  getParsedSession,
140
149
  getPricingRegistry,
@@ -154,7 +163,9 @@ export {
154
163
  loadCachedSessionData,
155
164
  loadCachedSessions,
156
165
  markAgentCacheInitialized,
166
+ markAgentFullSyncCompleted,
157
167
  matchesProjectScope,
168
+ matchesScanWindow,
158
169
  normalizeGitRemote,
159
170
  normalizeTitleText,
160
171
  openDb,
@@ -178,6 +189,7 @@ export {
178
189
  searchFileActivitySessions,
179
190
  searchSessions,
180
191
  sessionSignature,
192
+ setFtsIntegrityCheckedPath,
181
193
  skipped,
182
194
  skippedSession,
183
195
  sortSessions,
@@ -189,4 +201,4 @@ export {
189
201
  upsertBookmark,
190
202
  withEstimatedSessionCost
191
203
  };
192
- //# sourceMappingURL=dist-T737I76S.js.map
204
+ //# sourceMappingURL=dist-TZTL6VP4.js.map
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  extractSessionFileActivity,
15
15
  filterSessions,
16
16
  getAgentInfoMap,
17
+ getAgentLastFullSyncAt,
17
18
  getCursorDataPath,
18
19
  getSessionActivityTime,
19
20
  getSessionAgentName,
@@ -27,6 +28,7 @@ import {
27
28
  listSessionFileActivity,
28
29
  loadCachedSessionData,
29
30
  loadCachedSessions,
31
+ markAgentFullSyncCompleted,
30
32
  matchesProjectScope,
31
33
  parseSearchQuery,
32
34
  perf,
@@ -40,7 +42,7 @@ import {
40
42
  sortSessions,
41
43
  startOfLocalDay,
42
44
  upsertBookmark
43
- } from "./chunk-BIXOP5QX.js";
45
+ } from "./chunk-GCOAE7KI.js";
44
46
 
45
47
  // src/index.ts
46
48
  import { defineCommand, runMain } from "citty";
@@ -899,11 +901,12 @@ async function createServer(port, store, options = {}) {
899
901
  app.get("/*", serveStatic({ root: webDistPath, path: "index.html" }));
900
902
  }
901
903
  const attempts = Math.max(1, options.portFallbackAttempts ?? 1);
904
+ const hostname = options.hostname ?? "127.0.0.1";
902
905
  let server = null;
903
906
  let actualPort = port;
904
907
  for (let offset = 0; offset < attempts; offset += 1) {
905
908
  const candidatePort = port + offset;
906
- server = serve({ fetch: app.fetch, port: candidatePort });
909
+ server = serve({ fetch: app.fetch, port: candidatePort, hostname });
907
910
  try {
908
911
  await waitForListening(server);
909
912
  actualPort = getListeningPort(server, candidatePort);
@@ -925,8 +928,17 @@ async function createServer(port, store, options = {}) {
925
928
  throw new Error(getServerStartupErrorMessage(error, candidatePort));
926
929
  }
927
930
  }
928
- const url = `http://localhost:${actualPort}`;
929
- appLogger.info("server.listen", { port: actualPort, requested_port: port, url });
931
+ const isLoopback = hostname === "127.0.0.1" || hostname === "localhost";
932
+ const url = isLoopback ? `http://localhost:${actualPort}` : `http://${hostname}:${actualPort}`;
933
+ appLogger.info("server.listen", { port: actualPort, requested_port: port, hostname, url });
934
+ if (!isLoopback) {
935
+ appLogger.warn("server.listen.exposed", { hostname, port: actualPort });
936
+ console.warn(
937
+ `
938
+ \u26A0 \u670D\u52A1\u76D1\u542C ${hostname}\uFF0C\u5C40\u57DF\u7F51\u5185\u5176\u4ED6\u8BBE\u5907\u53EF\u8BFB\u53D6\u4F60\u7684\u5168\u90E8 AI \u4F1A\u8BDD\u8BB0\u5F55\uFF08\u65E0\u9274\u6743\uFF09\u3002
939
+ `
940
+ );
941
+ }
930
942
  return {
931
943
  url,
932
944
  shutdown: async () => {
@@ -1272,8 +1284,11 @@ var SessionWatcher = class {
1272
1284
  var REFRESH_DEBOUNCE_MS = 200;
1273
1285
  var EMPTY_AGENT_REFRESH_DEBOUNCE_MS = 3e4;
1274
1286
  var PENDING_REFRESH_DELAY_MS = 100;
1287
+ var MAX_ADAPTIVE_REFRESH_DELAY_MS = 3e4;
1288
+ var ADAPTIVE_REFRESH_DELAY_MULTIPLIER = 4;
1275
1289
  var NEW_SESSION_EVENT_WINDOW_MS = 250;
1276
1290
  var SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD = 100;
1291
+ var BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1277
1292
  function buildRefreshDiff(agentName, previousSessions, nextSessions, candidateChangedIds = []) {
1278
1293
  const { changes, removedSessionIds, counts } = computeSessionDiff(
1279
1294
  previousSessions,
@@ -1357,13 +1372,12 @@ var LiveScanStore = class {
1357
1372
  completedAgents: [],
1358
1373
  agentStatuses: {},
1359
1374
  totalAgents: 0,
1360
- updatedAt: Date.now()
1375
+ updatedAt: Date.now(),
1376
+ backfill: { active: false, pendingAgents: [], completedAgents: [] }
1361
1377
  };
1362
- refreshTimers = /* @__PURE__ */ new Map();
1363
- refreshTimestamps = /* @__PURE__ */ new Map();
1364
- refreshInFlight = /* @__PURE__ */ new Set();
1365
- pendingRefreshes = /* @__PURE__ */ new Set();
1366
- pendingRefreshPathCounts = /* @__PURE__ */ new Map();
1378
+ backfillQueue = [];
1379
+ backfillRunning = false;
1380
+ refreshStates = /* @__PURE__ */ new Map();
1367
1381
  watcher = null;
1368
1382
  pendingEvent = null;
1369
1383
  pendingEventTimer = null;
@@ -1371,6 +1385,24 @@ var LiveScanStore = class {
1371
1385
  searchIndexWorker = null;
1372
1386
  pendingSearchIndexJobs = [];
1373
1387
  shuttingDown = false;
1388
+ /** Set once a search-index worker in this process has completed the FTS integrity check. */
1389
+ ftsIntegrityChecked = false;
1390
+ getRefreshState(agentName) {
1391
+ let state = this.refreshStates.get(agentName);
1392
+ if (!state) {
1393
+ state = {
1394
+ timer: null,
1395
+ timerDeadline: 0,
1396
+ inFlight: false,
1397
+ pendingRerun: false,
1398
+ lastRefreshAt: 0,
1399
+ lastRefreshDurationMs: 0,
1400
+ pendingPathCount: 0
1401
+ };
1402
+ this.refreshStates.set(agentName, state);
1403
+ }
1404
+ return state;
1405
+ }
1374
1406
  async initialize() {
1375
1407
  const startedAt = performance.now();
1376
1408
  const deferInitialRefresh = this.storeOptions.deferInitialRefresh === true;
@@ -1427,10 +1459,7 @@ var LiveScanStore = class {
1427
1459
  this.watcher = new SessionWatcher();
1428
1460
  this.watcher.onAgentsChanged((agentNames) => {
1429
1461
  for (const agentName of agentNames) {
1430
- this.pendingRefreshPathCounts.set(
1431
- agentName,
1432
- (this.pendingRefreshPathCounts.get(agentName) ?? 0) + 1
1433
- );
1462
+ this.getRefreshState(agentName).pendingPathCount += 1;
1434
1463
  const delayMs = (this.byAgent[agentName]?.length ?? 0) === 0 ? EMPTY_AGENT_REFRESH_DEBOUNCE_MS : REFRESH_DEBOUNCE_MS;
1435
1464
  this.scheduleRefresh(agentName, delayMs);
1436
1465
  }
@@ -1452,6 +1481,11 @@ var LiveScanStore = class {
1452
1481
  if (agentNames.length === 0) {
1453
1482
  this.finishScanBatch();
1454
1483
  }
1484
+ for (const agent of this.agents) {
1485
+ if (this.needsBackfill(agent)) {
1486
+ this.enqueueBackfill(agent.name);
1487
+ }
1488
+ }
1455
1489
  }, 0);
1456
1490
  }
1457
1491
  getSnapshot() {
@@ -1473,7 +1507,12 @@ var LiveScanStore = class {
1473
1507
  agentName,
1474
1508
  { ...status }
1475
1509
  ])
1476
- )
1510
+ ),
1511
+ backfill: {
1512
+ ...this.scanStatus.backfill,
1513
+ pendingAgents: [...this.scanStatus.backfill.pendingAgents],
1514
+ completedAgents: [...this.scanStatus.backfill.completedAgents]
1515
+ }
1477
1516
  };
1478
1517
  }
1479
1518
  subscribe(listener) {
@@ -1490,11 +1529,13 @@ var LiveScanStore = class {
1490
1529
  }
1491
1530
  async shutdown() {
1492
1531
  this.shuttingDown = true;
1493
- for (const timer of this.refreshTimers.values()) {
1494
- clearTimeout(timer);
1532
+ for (const state of this.refreshStates.values()) {
1533
+ if (state.timer) {
1534
+ clearTimeout(state.timer);
1535
+ state.timer = null;
1536
+ state.timerDeadline = 0;
1537
+ }
1495
1538
  }
1496
- this.refreshTimers.clear();
1497
- this.pendingRefreshPathCounts.clear();
1498
1539
  if (this.pendingEventTimer) {
1499
1540
  clearTimeout(this.pendingEventTimer);
1500
1541
  this.pendingEventTimer = null;
@@ -1555,6 +1596,7 @@ var LiveScanStore = class {
1555
1596
  ])
1556
1597
  );
1557
1598
  this.updateScanStatus({
1599
+ ...this.scanStatus,
1558
1600
  active: uniqueAgentNames.length > 0,
1559
1601
  phase: uniqueAgentNames.length > 0 ? phase : "idle",
1560
1602
  pendingAgents: uniqueAgentNames,
@@ -1680,6 +1722,108 @@ var LiveScanStore = class {
1680
1722
  completedAt: now
1681
1723
  });
1682
1724
  }
1725
+ updateBackfillStatus(patch) {
1726
+ this.updateScanStatus({
1727
+ ...this.scanStatus,
1728
+ backfill: { ...this.scanStatus.backfill, ...patch },
1729
+ updatedAt: Date.now()
1730
+ });
1731
+ }
1732
+ /**
1733
+ * Only FileSystemSessionSource agents pay the O(history) enumeration cost this
1734
+ * guards against: database agents already do a cheap single-file mtime check.
1735
+ * With no startup window configured, the regular refresh path already walks
1736
+ * full history, so backfill would be redundant.
1737
+ */
1738
+ needsBackfill(agent) {
1739
+ if (this.startupScanOptions.from == null && this.startupScanOptions.to == null) return false;
1740
+ if (!(agent instanceof FileSystemSessionSource)) return false;
1741
+ if (!agent.isAvailable()) return false;
1742
+ const lastSyncAt = getAgentLastFullSyncAt(agent.name);
1743
+ return lastSyncAt == null || Date.now() - lastSyncAt > BACKFILL_INTERVAL_MS;
1744
+ }
1745
+ enqueueBackfill(agentName) {
1746
+ if (this.backfillQueue.includes(agentName) || this.scanStatus.backfill.currentAgent === agentName) {
1747
+ return;
1748
+ }
1749
+ this.backfillQueue.push(agentName);
1750
+ this.updateBackfillStatus({ active: true, pendingAgents: [...this.backfillQueue] });
1751
+ this.pumpBackfillQueue();
1752
+ }
1753
+ pumpBackfillQueue() {
1754
+ if (this.backfillRunning) return;
1755
+ const agentName = this.backfillQueue.shift();
1756
+ if (!agentName) return;
1757
+ this.backfillRunning = true;
1758
+ this.updateBackfillStatus({ currentAgent: agentName, pendingAgents: [...this.backfillQueue] });
1759
+ void this.runBackfill(agentName).finally(() => {
1760
+ this.backfillRunning = false;
1761
+ this.updateBackfillStatus({
1762
+ currentAgent: void 0,
1763
+ completedAgents: [.../* @__PURE__ */ new Set([...this.scanStatus.backfill.completedAgents, agentName])],
1764
+ active: this.backfillQueue.length > 0
1765
+ });
1766
+ this.pumpBackfillQueue();
1767
+ });
1768
+ }
1769
+ /** Unbounded per-source sync to reconcile the full session history, not just the display window. */
1770
+ async runBackfill(agentName) {
1771
+ const startedAt = performance.now();
1772
+ const agent = this.agents.find((item) => item.name === agentName);
1773
+ if (!agent || !(agent instanceof FileSystemSessionSource) || !agent.isAvailable()) {
1774
+ return;
1775
+ }
1776
+ const cached = loadCachedSessions(agentName);
1777
+ const baseline = cached?.sessions ?? this.byAgent[agentName] ?? [];
1778
+ const meta = cached?.meta ?? buildAgentCacheMeta(agent);
1779
+ if (cached) {
1780
+ restoreAgentCacheMeta(agent, cached.meta);
1781
+ }
1782
+ try {
1783
+ const result = await this.scanAgentInWorker(
1784
+ agent,
1785
+ baseline,
1786
+ null,
1787
+ {},
1788
+ { sourceSync: true, meta }
1789
+ );
1790
+ agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
1791
+ const fullSessions = attachMissingProjectIdentities(result.sessions);
1792
+ const filtered = this.applyFilters(fullSessions);
1793
+ const diff = buildRefreshDiff(
1794
+ agentName,
1795
+ this.byAgent[agentName] ?? [],
1796
+ filtered,
1797
+ result.changedIds ?? []
1798
+ );
1799
+ this.byAgent[agentName] = sortSessions(filtered);
1800
+ this.rebuildSessions();
1801
+ await this.enqueueSearchIndexJobs("scan.backfill", [
1802
+ {
1803
+ kind: "full",
1804
+ context: "scan.backfill",
1805
+ agentName,
1806
+ sessions: fullSessions,
1807
+ meta: buildAgentCacheMeta(agent),
1808
+ saveCache: true
1809
+ }
1810
+ ]);
1811
+ markAgentFullSyncCompleted(agentName);
1812
+ if (diff.event) {
1813
+ diff.event.totalSessions = this.sessions.length;
1814
+ this.emit(diff.event);
1815
+ }
1816
+ appLogger.info("scan.backfill.done", {
1817
+ agent: agentName,
1818
+ duration_ms: Math.round(performance.now() - startedAt),
1819
+ sessions: fullSessions.length,
1820
+ changed: result.changedIds?.length ?? 0
1821
+ });
1822
+ } catch (error) {
1823
+ appLogger.error("scan.backfill.error", { agent: agentName, error });
1824
+ console.error(`[${agentName}] Backfill failed:`, error);
1825
+ }
1826
+ }
1683
1827
  queueEvent(event) {
1684
1828
  this.pendingEvent = this.pendingEvent ? mergeEvents(this.pendingEvent, event) : event;
1685
1829
  if (this.pendingEventTimer) {
@@ -1813,7 +1957,8 @@ var LiveScanStore = class {
1813
1957
  jobs: batch.jobs,
1814
1958
  agentNames: [],
1815
1959
  sessionsByAgent: {},
1816
- metaByAgent: {}
1960
+ metaByAgent: {},
1961
+ skipFtsIntegrityCheck: this.ftsIntegrityChecked
1817
1962
  }
1818
1963
  });
1819
1964
  worker.unref();
@@ -1827,6 +1972,7 @@ var LiveScanStore = class {
1827
1972
  sessions: message.sessions
1828
1973
  });
1829
1974
  settled = true;
1975
+ this.ftsIntegrityChecked = true;
1830
1976
  batch.resolve();
1831
1977
  }
1832
1978
  });
@@ -1873,7 +2019,7 @@ var LiveScanStore = class {
1873
2019
  this.byAgent = {};
1874
2020
  for (const agent of this.agents) {
1875
2021
  this.byAgent[agent.name] = sortSessions(result.byAgent[agent.name] ?? []);
1876
- this.refreshTimestamps.set(agent.name, result.cacheTimestamps?.[agent.name] ?? Date.now());
2022
+ this.getRefreshState(agent.name).lastRefreshAt = result.cacheTimestamps?.[agent.name] ?? Date.now();
1877
2023
  }
1878
2024
  this.rebuildSessions();
1879
2025
  }
@@ -1903,25 +2049,41 @@ var LiveScanStore = class {
1903
2049
  console.error("[search] Background index sync failed:", error);
1904
2050
  }
1905
2051
  }
2052
+ /**
2053
+ * Throttles rather than debounces: a pending timer only gets replaced by a
2054
+ * request with an earlier deadline. Plain debounce (reset on every call)
2055
+ * would let a steady stream of events — each arriving before the adaptive
2056
+ * backoff elapses — push the deadline out forever and starve the refresh.
2057
+ */
1906
2058
  scheduleRefresh(agentName, delayMs = REFRESH_DEBOUNCE_MS) {
1907
- appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: delayMs });
1908
- const existing = this.refreshTimers.get(agentName);
1909
- if (existing) {
1910
- clearTimeout(existing);
2059
+ const state = this.getRefreshState(agentName);
2060
+ const adaptiveDelayMs = Math.min(
2061
+ state.lastRefreshDurationMs * ADAPTIVE_REFRESH_DELAY_MULTIPLIER,
2062
+ MAX_ADAPTIVE_REFRESH_DELAY_MS
2063
+ );
2064
+ const effectiveDelayMs = Math.max(delayMs, adaptiveDelayMs);
2065
+ const deadline = Date.now() + effectiveDelayMs;
2066
+ if (state.timer !== null) {
2067
+ if (deadline >= state.timerDeadline) {
2068
+ return;
2069
+ }
2070
+ clearTimeout(state.timer);
1911
2071
  }
1912
- const timer = setTimeout(() => {
1913
- this.refreshTimers.delete(agentName);
2072
+ appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: effectiveDelayMs });
2073
+ state.timerDeadline = deadline;
2074
+ state.timer = setTimeout(() => {
2075
+ state.timer = null;
1914
2076
  void this.refreshAgent(agentName);
1915
- }, delayMs);
1916
- this.refreshTimers.set(agentName, timer);
2077
+ }, effectiveDelayMs);
1917
2078
  }
1918
2079
  async refreshAgent(agentName) {
1919
- if (this.refreshInFlight.has(agentName)) {
2080
+ const state = this.getRefreshState(agentName);
2081
+ if (state.inFlight) {
1920
2082
  appLogger.debug("scan.refresh.pending", { agent: agentName });
1921
- this.pendingRefreshes.add(agentName);
2083
+ state.pendingRerun = true;
1922
2084
  return;
1923
2085
  }
1924
- this.refreshInFlight.add(agentName);
2086
+ state.inFlight = true;
1925
2087
  this.beginAgentScan(agentName);
1926
2088
  try {
1927
2089
  await this.runRefresh(agentName);
@@ -1929,17 +2091,19 @@ var LiveScanStore = class {
1929
2091
  appLogger.error("scan.refresh.error", { agent: agentName, error });
1930
2092
  console.error(`[${agentName}] Session refresh failed:`, error);
1931
2093
  } finally {
1932
- this.refreshInFlight.delete(agentName);
2094
+ state.inFlight = false;
1933
2095
  this.finishAgentScan(agentName);
1934
- if (this.pendingRefreshes.delete(agentName)) {
2096
+ if (state.pendingRerun) {
2097
+ state.pendingRerun = false;
1935
2098
  this.scheduleRefresh(agentName, PENDING_REFRESH_DELAY_MS);
1936
2099
  }
1937
2100
  }
1938
2101
  }
1939
2102
  async runRefresh(agentName) {
1940
2103
  const startedAt = performance.now();
1941
- const pendingPathCount = this.pendingRefreshPathCounts.get(agentName) ?? 0;
1942
- this.pendingRefreshPathCounts.delete(agentName);
2104
+ const state = this.getRefreshState(agentName);
2105
+ const pendingPathCount = state.pendingPathCount;
2106
+ state.pendingPathCount = 0;
1943
2107
  const agent = this.agents.find((item) => item.name === agentName);
1944
2108
  if (!agent) {
1945
2109
  appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
@@ -1948,7 +2112,7 @@ var LiveScanStore = class {
1948
2112
  const previousSessions = this.byAgent[agentName] ?? [];
1949
2113
  const cached = loadCachedSessions(agentName);
1950
2114
  const refreshBaseline = cached?.sessions ?? previousSessions;
1951
- const cacheTimestamp = cached?.timestamp ?? this.refreshTimestamps.get(agentName) ?? 0;
2115
+ const cacheTimestamp = cached?.timestamp ?? state.lastRefreshAt;
1952
2116
  if (cached) {
1953
2117
  restoreAgentCacheMeta(agent, cached.meta);
1954
2118
  }
@@ -1971,24 +2135,29 @@ var LiveScanStore = class {
1971
2135
  availabilityDuration = performance.now() - availabilityStartedAt;
1972
2136
  if (!isAvailable) {
1973
2137
  nextSessions = [];
1974
- this.refreshTimestamps.set(agentName, Date.now());
2138
+ state.lastRefreshAt = Date.now();
1975
2139
  } else if (!isInitialized) {
1976
2140
  this.setScanPhase("initializing");
1977
2141
  const scanStartedAt = performance.now();
1978
- const result = await this.scanAgentInWorker(agent, previousSessions, null, {});
2142
+ const result = await this.scanAgentInWorker(
2143
+ agent,
2144
+ previousSessions,
2145
+ null,
2146
+ this.startupScanOptions
2147
+ );
1979
2148
  nextSessions = result.sessions;
1980
2149
  agent.setSessionMetaMap?.(new Map(Object.entries(result.meta)));
1981
2150
  fullScanSessions = attachMissingProjectIdentities(nextSessions);
1982
2151
  nextSessions = fullScanSessions;
1983
2152
  scanDuration = performance.now() - scanStartedAt;
1984
- this.refreshTimestamps.set(agentName, Date.now());
2153
+ state.lastRefreshAt = Date.now();
1985
2154
  } else if (cached && agent instanceof FileSystemSessionSource) {
1986
2155
  const scanStartedAt = performance.now();
1987
2156
  const result = await this.scanAgentInWorker(
1988
2157
  agent,
1989
2158
  cached.sessions,
1990
2159
  null,
1991
- {},
2160
+ this.startupScanOptions,
1992
2161
  {
1993
2162
  sourceSync: true,
1994
2163
  meta: cached.meta
@@ -2005,7 +2174,7 @@ var LiveScanStore = class {
2005
2174
  preciseChangedIds
2006
2175
  );
2007
2176
  scanDuration = performance.now() - scanStartedAt;
2008
- this.refreshTimestamps.set(agentName, Date.now());
2177
+ state.lastRefreshAt = Date.now();
2009
2178
  if (preciseChangedIds.length === 0) {
2010
2179
  appLogger.debug("scan.refresh.unchanged", {
2011
2180
  agent: agentName,
@@ -2018,7 +2187,7 @@ var LiveScanStore = class {
2018
2187
  agent.checkForChanges(cacheTimestamp, refreshBaseline)
2019
2188
  );
2020
2189
  checkDuration = performance.now() - checkStartedAt;
2021
- this.refreshTimestamps.set(agentName, checkResult.timestamp);
2190
+ state.lastRefreshAt = checkResult.timestamp;
2022
2191
  if (!checkResult.hasChanges) {
2023
2192
  appLogger.debug("scan.refresh.unchanged", {
2024
2193
  agent: agentName,
@@ -2049,7 +2218,7 @@ var LiveScanStore = class {
2049
2218
  fullScanSessions = attachMissingProjectIdentities(nextSessions);
2050
2219
  nextSessions = fullScanSessions;
2051
2220
  scanDuration = performance.now() - scanStartedAt;
2052
- this.refreshTimestamps.set(agentName, Date.now());
2221
+ state.lastRefreshAt = Date.now();
2053
2222
  }
2054
2223
  nextSessions = attachMissingProjectIdentities(nextSessions);
2055
2224
  const filterStartedAt = performance.now();
@@ -2110,9 +2279,11 @@ var LiveScanStore = class {
2110
2279
  event.totalSessions = this.sessions.length;
2111
2280
  this.emit(event);
2112
2281
  }
2282
+ const totalDurationMs = performance.now() - startedAt;
2283
+ state.lastRefreshDurationMs = totalDurationMs;
2113
2284
  appLogger.info("scan.refresh.done", {
2114
2285
  agent: agentName,
2115
- duration_ms: Math.round(performance.now() - startedAt),
2286
+ duration_ms: Math.round(totalDurationMs),
2116
2287
  sessions: nextSessions.length,
2117
2288
  new_sessions: event?.newSessions ?? 0,
2118
2289
  updated_sessions: event?.updatedSessions ?? 0,
@@ -2201,6 +2372,11 @@ var main = defineCommand({
2201
2372
  description: "HTTP server port",
2202
2373
  default: String(DEFAULT_PORT)
2203
2374
  },
2375
+ host: {
2376
+ type: "string",
2377
+ description: "HTTP server bind address (default 127.0.0.1, local access only)",
2378
+ default: "127.0.0.1"
2379
+ },
2204
2380
  agent: {
2205
2381
  type: "string",
2206
2382
  alias: "a",
@@ -2278,7 +2454,7 @@ var main = defineCommand({
2278
2454
  log_path: appLogger.getLogPath()
2279
2455
  });
2280
2456
  if (clearCache) {
2281
- const { clearCache: clear } = await import("./dist-T737I76S.js");
2457
+ const { clearCache: clear } = await import("./dist-TZTL6VP4.js");
2282
2458
  clear();
2283
2459
  appLogger.info("cache.clear");
2284
2460
  console.log("Cache cleared.");
@@ -2367,7 +2543,8 @@ var main = defineCommand({
2367
2543
  defaultSessionFrom: listDefaultFrom,
2368
2544
  defaultSessionTo: listDefaultTo,
2369
2545
  defaultSessionDays: listDefaultDays,
2370
- portFallbackAttempts: explicitPort ? 1 : DEFAULT_PORT_FALLBACK_ATTEMPTS
2546
+ portFallbackAttempts: explicitPort ? 1 : DEFAULT_PORT_FALLBACK_ATTEMPTS,
2547
+ hostname: args.host
2371
2548
  });
2372
2549
  } catch (error) {
2373
2550
  console.error(getServerStartupErrorMessage(error, port));