codesesh 0.10.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-ZQMIB7QO.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-GZSUB4NY.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-ZQMIB7QO.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 () => {
@@ -1049,6 +1061,8 @@ function resolveAgentWatchTargets(agentName) {
1049
1061
  { root: roots.opencodeRoot, path: join2(roots.opencodeRoot, "opencode.db") },
1050
1062
  { root: "data/opencode", path: "data/opencode/opencode.db" }
1051
1063
  ];
1064
+ case "zcode":
1065
+ return roots.zcodeRoot ? [{ root: roots.zcodeRoot, path: join2(roots.zcodeRoot, "cli", "db", "db.sqlite") }] : [];
1052
1066
  default:
1053
1067
  return [];
1054
1068
  }
@@ -1270,8 +1284,11 @@ var SessionWatcher = class {
1270
1284
  var REFRESH_DEBOUNCE_MS = 200;
1271
1285
  var EMPTY_AGENT_REFRESH_DEBOUNCE_MS = 3e4;
1272
1286
  var PENDING_REFRESH_DELAY_MS = 100;
1287
+ var MAX_ADAPTIVE_REFRESH_DELAY_MS = 3e4;
1288
+ var ADAPTIVE_REFRESH_DELAY_MULTIPLIER = 4;
1273
1289
  var NEW_SESSION_EVENT_WINDOW_MS = 250;
1274
1290
  var SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD = 100;
1291
+ var BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1275
1292
  function buildRefreshDiff(agentName, previousSessions, nextSessions, candidateChangedIds = []) {
1276
1293
  const { changes, removedSessionIds, counts } = computeSessionDiff(
1277
1294
  previousSessions,
@@ -1355,13 +1372,12 @@ var LiveScanStore = class {
1355
1372
  completedAgents: [],
1356
1373
  agentStatuses: {},
1357
1374
  totalAgents: 0,
1358
- updatedAt: Date.now()
1375
+ updatedAt: Date.now(),
1376
+ backfill: { active: false, pendingAgents: [], completedAgents: [] }
1359
1377
  };
1360
- refreshTimers = /* @__PURE__ */ new Map();
1361
- refreshTimestamps = /* @__PURE__ */ new Map();
1362
- refreshInFlight = /* @__PURE__ */ new Set();
1363
- pendingRefreshes = /* @__PURE__ */ new Set();
1364
- pendingRefreshPathCounts = /* @__PURE__ */ new Map();
1378
+ backfillQueue = [];
1379
+ backfillRunning = false;
1380
+ refreshStates = /* @__PURE__ */ new Map();
1365
1381
  watcher = null;
1366
1382
  pendingEvent = null;
1367
1383
  pendingEventTimer = null;
@@ -1369,6 +1385,24 @@ var LiveScanStore = class {
1369
1385
  searchIndexWorker = null;
1370
1386
  pendingSearchIndexJobs = [];
1371
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
+ }
1372
1406
  async initialize() {
1373
1407
  const startedAt = performance.now();
1374
1408
  const deferInitialRefresh = this.storeOptions.deferInitialRefresh === true;
@@ -1425,10 +1459,7 @@ var LiveScanStore = class {
1425
1459
  this.watcher = new SessionWatcher();
1426
1460
  this.watcher.onAgentsChanged((agentNames) => {
1427
1461
  for (const agentName of agentNames) {
1428
- this.pendingRefreshPathCounts.set(
1429
- agentName,
1430
- (this.pendingRefreshPathCounts.get(agentName) ?? 0) + 1
1431
- );
1462
+ this.getRefreshState(agentName).pendingPathCount += 1;
1432
1463
  const delayMs = (this.byAgent[agentName]?.length ?? 0) === 0 ? EMPTY_AGENT_REFRESH_DEBOUNCE_MS : REFRESH_DEBOUNCE_MS;
1433
1464
  this.scheduleRefresh(agentName, delayMs);
1434
1465
  }
@@ -1450,6 +1481,11 @@ var LiveScanStore = class {
1450
1481
  if (agentNames.length === 0) {
1451
1482
  this.finishScanBatch();
1452
1483
  }
1484
+ for (const agent of this.agents) {
1485
+ if (this.needsBackfill(agent)) {
1486
+ this.enqueueBackfill(agent.name);
1487
+ }
1488
+ }
1453
1489
  }, 0);
1454
1490
  }
1455
1491
  getSnapshot() {
@@ -1471,7 +1507,12 @@ var LiveScanStore = class {
1471
1507
  agentName,
1472
1508
  { ...status }
1473
1509
  ])
1474
- )
1510
+ ),
1511
+ backfill: {
1512
+ ...this.scanStatus.backfill,
1513
+ pendingAgents: [...this.scanStatus.backfill.pendingAgents],
1514
+ completedAgents: [...this.scanStatus.backfill.completedAgents]
1515
+ }
1475
1516
  };
1476
1517
  }
1477
1518
  subscribe(listener) {
@@ -1488,11 +1529,13 @@ var LiveScanStore = class {
1488
1529
  }
1489
1530
  async shutdown() {
1490
1531
  this.shuttingDown = true;
1491
- for (const timer of this.refreshTimers.values()) {
1492
- 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
+ }
1493
1538
  }
1494
- this.refreshTimers.clear();
1495
- this.pendingRefreshPathCounts.clear();
1496
1539
  if (this.pendingEventTimer) {
1497
1540
  clearTimeout(this.pendingEventTimer);
1498
1541
  this.pendingEventTimer = null;
@@ -1553,6 +1596,7 @@ var LiveScanStore = class {
1553
1596
  ])
1554
1597
  );
1555
1598
  this.updateScanStatus({
1599
+ ...this.scanStatus,
1556
1600
  active: uniqueAgentNames.length > 0,
1557
1601
  phase: uniqueAgentNames.length > 0 ? phase : "idle",
1558
1602
  pendingAgents: uniqueAgentNames,
@@ -1678,6 +1722,108 @@ var LiveScanStore = class {
1678
1722
  completedAt: now
1679
1723
  });
1680
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
+ }
1681
1827
  queueEvent(event) {
1682
1828
  this.pendingEvent = this.pendingEvent ? mergeEvents(this.pendingEvent, event) : event;
1683
1829
  if (this.pendingEventTimer) {
@@ -1811,7 +1957,8 @@ var LiveScanStore = class {
1811
1957
  jobs: batch.jobs,
1812
1958
  agentNames: [],
1813
1959
  sessionsByAgent: {},
1814
- metaByAgent: {}
1960
+ metaByAgent: {},
1961
+ skipFtsIntegrityCheck: this.ftsIntegrityChecked
1815
1962
  }
1816
1963
  });
1817
1964
  worker.unref();
@@ -1825,6 +1972,7 @@ var LiveScanStore = class {
1825
1972
  sessions: message.sessions
1826
1973
  });
1827
1974
  settled = true;
1975
+ this.ftsIntegrityChecked = true;
1828
1976
  batch.resolve();
1829
1977
  }
1830
1978
  });
@@ -1871,7 +2019,7 @@ var LiveScanStore = class {
1871
2019
  this.byAgent = {};
1872
2020
  for (const agent of this.agents) {
1873
2021
  this.byAgent[agent.name] = sortSessions(result.byAgent[agent.name] ?? []);
1874
- this.refreshTimestamps.set(agent.name, result.cacheTimestamps?.[agent.name] ?? Date.now());
2022
+ this.getRefreshState(agent.name).lastRefreshAt = result.cacheTimestamps?.[agent.name] ?? Date.now();
1875
2023
  }
1876
2024
  this.rebuildSessions();
1877
2025
  }
@@ -1901,25 +2049,41 @@ var LiveScanStore = class {
1901
2049
  console.error("[search] Background index sync failed:", error);
1902
2050
  }
1903
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
+ */
1904
2058
  scheduleRefresh(agentName, delayMs = REFRESH_DEBOUNCE_MS) {
1905
- appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: delayMs });
1906
- const existing = this.refreshTimers.get(agentName);
1907
- if (existing) {
1908
- 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);
1909
2071
  }
1910
- const timer = setTimeout(() => {
1911
- 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;
1912
2076
  void this.refreshAgent(agentName);
1913
- }, delayMs);
1914
- this.refreshTimers.set(agentName, timer);
2077
+ }, effectiveDelayMs);
1915
2078
  }
1916
2079
  async refreshAgent(agentName) {
1917
- if (this.refreshInFlight.has(agentName)) {
2080
+ const state = this.getRefreshState(agentName);
2081
+ if (state.inFlight) {
1918
2082
  appLogger.debug("scan.refresh.pending", { agent: agentName });
1919
- this.pendingRefreshes.add(agentName);
2083
+ state.pendingRerun = true;
1920
2084
  return;
1921
2085
  }
1922
- this.refreshInFlight.add(agentName);
2086
+ state.inFlight = true;
1923
2087
  this.beginAgentScan(agentName);
1924
2088
  try {
1925
2089
  await this.runRefresh(agentName);
@@ -1927,17 +2091,19 @@ var LiveScanStore = class {
1927
2091
  appLogger.error("scan.refresh.error", { agent: agentName, error });
1928
2092
  console.error(`[${agentName}] Session refresh failed:`, error);
1929
2093
  } finally {
1930
- this.refreshInFlight.delete(agentName);
2094
+ state.inFlight = false;
1931
2095
  this.finishAgentScan(agentName);
1932
- if (this.pendingRefreshes.delete(agentName)) {
2096
+ if (state.pendingRerun) {
2097
+ state.pendingRerun = false;
1933
2098
  this.scheduleRefresh(agentName, PENDING_REFRESH_DELAY_MS);
1934
2099
  }
1935
2100
  }
1936
2101
  }
1937
2102
  async runRefresh(agentName) {
1938
2103
  const startedAt = performance.now();
1939
- const pendingPathCount = this.pendingRefreshPathCounts.get(agentName) ?? 0;
1940
- this.pendingRefreshPathCounts.delete(agentName);
2104
+ const state = this.getRefreshState(agentName);
2105
+ const pendingPathCount = state.pendingPathCount;
2106
+ state.pendingPathCount = 0;
1941
2107
  const agent = this.agents.find((item) => item.name === agentName);
1942
2108
  if (!agent) {
1943
2109
  appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
@@ -1946,7 +2112,7 @@ var LiveScanStore = class {
1946
2112
  const previousSessions = this.byAgent[agentName] ?? [];
1947
2113
  const cached = loadCachedSessions(agentName);
1948
2114
  const refreshBaseline = cached?.sessions ?? previousSessions;
1949
- const cacheTimestamp = cached?.timestamp ?? this.refreshTimestamps.get(agentName) ?? 0;
2115
+ const cacheTimestamp = cached?.timestamp ?? state.lastRefreshAt;
1950
2116
  if (cached) {
1951
2117
  restoreAgentCacheMeta(agent, cached.meta);
1952
2118
  }
@@ -1969,24 +2135,29 @@ var LiveScanStore = class {
1969
2135
  availabilityDuration = performance.now() - availabilityStartedAt;
1970
2136
  if (!isAvailable) {
1971
2137
  nextSessions = [];
1972
- this.refreshTimestamps.set(agentName, Date.now());
2138
+ state.lastRefreshAt = Date.now();
1973
2139
  } else if (!isInitialized) {
1974
2140
  this.setScanPhase("initializing");
1975
2141
  const scanStartedAt = performance.now();
1976
- const result = await this.scanAgentInWorker(agent, previousSessions, null, {});
2142
+ const result = await this.scanAgentInWorker(
2143
+ agent,
2144
+ previousSessions,
2145
+ null,
2146
+ this.startupScanOptions
2147
+ );
1977
2148
  nextSessions = result.sessions;
1978
2149
  agent.setSessionMetaMap?.(new Map(Object.entries(result.meta)));
1979
2150
  fullScanSessions = attachMissingProjectIdentities(nextSessions);
1980
2151
  nextSessions = fullScanSessions;
1981
2152
  scanDuration = performance.now() - scanStartedAt;
1982
- this.refreshTimestamps.set(agentName, Date.now());
2153
+ state.lastRefreshAt = Date.now();
1983
2154
  } else if (cached && agent instanceof FileSystemSessionSource) {
1984
2155
  const scanStartedAt = performance.now();
1985
2156
  const result = await this.scanAgentInWorker(
1986
2157
  agent,
1987
2158
  cached.sessions,
1988
2159
  null,
1989
- {},
2160
+ this.startupScanOptions,
1990
2161
  {
1991
2162
  sourceSync: true,
1992
2163
  meta: cached.meta
@@ -2003,7 +2174,7 @@ var LiveScanStore = class {
2003
2174
  preciseChangedIds
2004
2175
  );
2005
2176
  scanDuration = performance.now() - scanStartedAt;
2006
- this.refreshTimestamps.set(agentName, Date.now());
2177
+ state.lastRefreshAt = Date.now();
2007
2178
  if (preciseChangedIds.length === 0) {
2008
2179
  appLogger.debug("scan.refresh.unchanged", {
2009
2180
  agent: agentName,
@@ -2016,7 +2187,7 @@ var LiveScanStore = class {
2016
2187
  agent.checkForChanges(cacheTimestamp, refreshBaseline)
2017
2188
  );
2018
2189
  checkDuration = performance.now() - checkStartedAt;
2019
- this.refreshTimestamps.set(agentName, checkResult.timestamp);
2190
+ state.lastRefreshAt = checkResult.timestamp;
2020
2191
  if (!checkResult.hasChanges) {
2021
2192
  appLogger.debug("scan.refresh.unchanged", {
2022
2193
  agent: agentName,
@@ -2047,7 +2218,7 @@ var LiveScanStore = class {
2047
2218
  fullScanSessions = attachMissingProjectIdentities(nextSessions);
2048
2219
  nextSessions = fullScanSessions;
2049
2220
  scanDuration = performance.now() - scanStartedAt;
2050
- this.refreshTimestamps.set(agentName, Date.now());
2221
+ state.lastRefreshAt = Date.now();
2051
2222
  }
2052
2223
  nextSessions = attachMissingProjectIdentities(nextSessions);
2053
2224
  const filterStartedAt = performance.now();
@@ -2108,9 +2279,11 @@ var LiveScanStore = class {
2108
2279
  event.totalSessions = this.sessions.length;
2109
2280
  this.emit(event);
2110
2281
  }
2282
+ const totalDurationMs = performance.now() - startedAt;
2283
+ state.lastRefreshDurationMs = totalDurationMs;
2111
2284
  appLogger.info("scan.refresh.done", {
2112
2285
  agent: agentName,
2113
- duration_ms: Math.round(performance.now() - startedAt),
2286
+ duration_ms: Math.round(totalDurationMs),
2114
2287
  sessions: nextSessions.length,
2115
2288
  new_sessions: event?.newSessions ?? 0,
2116
2289
  updated_sessions: event?.updatedSessions ?? 0,
@@ -2199,6 +2372,11 @@ var main = defineCommand({
2199
2372
  description: "HTTP server port",
2200
2373
  default: String(DEFAULT_PORT)
2201
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
+ },
2202
2380
  agent: {
2203
2381
  type: "string",
2204
2382
  alias: "a",
@@ -2276,7 +2454,7 @@ var main = defineCommand({
2276
2454
  log_path: appLogger.getLogPath()
2277
2455
  });
2278
2456
  if (clearCache) {
2279
- const { clearCache: clear } = await import("./dist-GZSUB4NY.js");
2457
+ const { clearCache: clear } = await import("./dist-TZTL6VP4.js");
2280
2458
  clear();
2281
2459
  appLogger.info("cache.clear");
2282
2460
  console.log("Cache cleared.");
@@ -2365,7 +2543,8 @@ var main = defineCommand({
2365
2543
  defaultSessionFrom: listDefaultFrom,
2366
2544
  defaultSessionTo: listDefaultTo,
2367
2545
  defaultSessionDays: listDefaultDays,
2368
- portFallbackAttempts: explicitPort ? 1 : DEFAULT_PORT_FALLBACK_ATTEMPTS
2546
+ portFallbackAttempts: explicitPort ? 1 : DEFAULT_PORT_FALLBACK_ATTEMPTS,
2547
+ hostname: args.host
2369
2548
  });
2370
2549
  } catch (error) {
2371
2550
  console.error(getServerStartupErrorMessage(error, port));