@vibelet/cli 0.1.37 → 1.0.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.
Files changed (323) hide show
  1. package/README.md +80 -0
  2. package/bin/cloudflared-quick-tunnel.mjs +11 -0
  3. package/bin/cloudflared-resolver.mjs +171 -0
  4. package/bin/vibelet-runtime-policy.mjs +36 -0
  5. package/bin/vibelet.cjs +12 -0
  6. package/bin/vibelet.mjs +1062 -0
  7. package/dist/index.cjs +126 -0
  8. package/package.json +25 -24
  9. package/app.json +0 -5
  10. package/dist/advertised-hosts.d.ts +0 -34
  11. package/dist/advertised-hosts.d.ts.map +0 -1
  12. package/dist/advertised-hosts.js +0 -176
  13. package/dist/advertised-hosts.js.map +0 -1
  14. package/dist/advertised-hosts.test.d.ts +0 -2
  15. package/dist/advertised-hosts.test.d.ts.map +0 -1
  16. package/dist/advertised-hosts.test.js +0 -96
  17. package/dist/advertised-hosts.test.js.map +0 -1
  18. package/dist/audit.d.ts +0 -30
  19. package/dist/audit.d.ts.map +0 -1
  20. package/dist/audit.js +0 -73
  21. package/dist/audit.js.map +0 -1
  22. package/dist/audit.test.d.ts +0 -2
  23. package/dist/audit.test.d.ts.map +0 -1
  24. package/dist/audit.test.js +0 -33
  25. package/dist/audit.test.js.map +0 -1
  26. package/dist/auth.d.ts +0 -6
  27. package/dist/auth.d.ts.map +0 -1
  28. package/dist/auth.js +0 -27
  29. package/dist/auth.js.map +0 -1
  30. package/dist/claude-hooks.d.ts +0 -58
  31. package/dist/claude-hooks.d.ts.map +0 -1
  32. package/dist/claude-hooks.js +0 -129
  33. package/dist/claude-hooks.js.map +0 -1
  34. package/dist/cli-version.d.ts +0 -3
  35. package/dist/cli-version.d.ts.map +0 -1
  36. package/dist/cli-version.js +0 -35
  37. package/dist/cli-version.js.map +0 -1
  38. package/dist/cli-version.test.d.ts +0 -2
  39. package/dist/cli-version.test.d.ts.map +0 -1
  40. package/dist/cli-version.test.js +0 -38
  41. package/dist/cli-version.test.js.map +0 -1
  42. package/dist/config.d.ts +0 -30
  43. package/dist/config.d.ts.map +0 -1
  44. package/dist/config.js +0 -327
  45. package/dist/config.js.map +0 -1
  46. package/dist/config.test.d.ts +0 -2
  47. package/dist/config.test.d.ts.map +0 -1
  48. package/dist/config.test.js +0 -184
  49. package/dist/config.test.js.map +0 -1
  50. package/dist/dev-auth.test.d.ts +0 -2
  51. package/dist/dev-auth.test.d.ts.map +0 -1
  52. package/dist/dev-auth.test.js +0 -154
  53. package/dist/dev-auth.test.js.map +0 -1
  54. package/dist/dev-script.test.d.ts +0 -2
  55. package/dist/dev-script.test.d.ts.map +0 -1
  56. package/dist/dev-script.test.js +0 -412
  57. package/dist/dev-script.test.js.map +0 -1
  58. package/dist/drivers/claude.d.ts +0 -34
  59. package/dist/drivers/claude.d.ts.map +0 -1
  60. package/dist/drivers/claude.js +0 -413
  61. package/dist/drivers/claude.js.map +0 -1
  62. package/dist/drivers/claude.test.d.ts +0 -2
  63. package/dist/drivers/claude.test.d.ts.map +0 -1
  64. package/dist/drivers/claude.test.js +0 -951
  65. package/dist/drivers/claude.test.js.map +0 -1
  66. package/dist/drivers/codex.d.ts +0 -38
  67. package/dist/drivers/codex.d.ts.map +0 -1
  68. package/dist/drivers/codex.js +0 -771
  69. package/dist/drivers/codex.js.map +0 -1
  70. package/dist/drivers/codex.test.d.ts +0 -2
  71. package/dist/drivers/codex.test.d.ts.map +0 -1
  72. package/dist/drivers/codex.test.js +0 -939
  73. package/dist/drivers/codex.test.js.map +0 -1
  74. package/dist/drivers/types.d.ts +0 -14
  75. package/dist/drivers/types.d.ts.map +0 -1
  76. package/dist/drivers/types.js +0 -2
  77. package/dist/drivers/types.js.map +0 -1
  78. package/dist/e2e.test.d.ts +0 -2
  79. package/dist/e2e.test.d.ts.map +0 -1
  80. package/dist/e2e.test.js +0 -111
  81. package/dist/e2e.test.js.map +0 -1
  82. package/dist/identity.d.ts +0 -10
  83. package/dist/identity.d.ts.map +0 -1
  84. package/dist/identity.js +0 -66
  85. package/dist/identity.js.map +0 -1
  86. package/dist/identity.test.d.ts +0 -2
  87. package/dist/identity.test.d.ts.map +0 -1
  88. package/dist/identity.test.js +0 -25
  89. package/dist/identity.test.js.map +0 -1
  90. package/dist/index-entry.test.d.ts +0 -2
  91. package/dist/index-entry.test.d.ts.map +0 -1
  92. package/dist/index-entry.test.js +0 -272
  93. package/dist/index-entry.test.js.map +0 -1
  94. package/dist/index.d.ts +0 -2
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/index.js +0 -707
  97. package/dist/index.js.map +0 -1
  98. package/dist/logger.d.ts +0 -31
  99. package/dist/logger.d.ts.map +0 -1
  100. package/dist/logger.js +0 -75
  101. package/dist/logger.js.map +0 -1
  102. package/dist/metrics.d.ts +0 -52
  103. package/dist/metrics.d.ts.map +0 -1
  104. package/dist/metrics.js +0 -89
  105. package/dist/metrics.js.map +0 -1
  106. package/dist/pairing-store.d.ts +0 -29
  107. package/dist/pairing-store.d.ts.map +0 -1
  108. package/dist/pairing-store.js +0 -131
  109. package/dist/pairing-store.js.map +0 -1
  110. package/dist/pairing-store.test.d.ts +0 -2
  111. package/dist/pairing-store.test.d.ts.map +0 -1
  112. package/dist/pairing-store.test.js +0 -47
  113. package/dist/pairing-store.test.js.map +0 -1
  114. package/dist/paths.d.ts +0 -16
  115. package/dist/paths.d.ts.map +0 -1
  116. package/dist/paths.js +0 -18
  117. package/dist/paths.js.map +0 -1
  118. package/dist/perf-compare.d.ts +0 -13
  119. package/dist/perf-compare.d.ts.map +0 -1
  120. package/dist/perf-compare.js +0 -125
  121. package/dist/perf-compare.js.map +0 -1
  122. package/dist/port-conflict.d.ts +0 -9
  123. package/dist/port-conflict.d.ts.map +0 -1
  124. package/dist/port-conflict.js +0 -33
  125. package/dist/port-conflict.js.map +0 -1
  126. package/dist/port-conflict.test.d.ts +0 -2
  127. package/dist/port-conflict.test.d.ts.map +0 -1
  128. package/dist/port-conflict.test.js +0 -38
  129. package/dist/port-conflict.test.js.map +0 -1
  130. package/dist/process-scanner.d.ts +0 -43
  131. package/dist/process-scanner.d.ts.map +0 -1
  132. package/dist/process-scanner.js +0 -453
  133. package/dist/process-scanner.js.map +0 -1
  134. package/dist/process-scanner.perf.test.d.ts +0 -2
  135. package/dist/process-scanner.perf.test.d.ts.map +0 -1
  136. package/dist/process-scanner.perf.test.js +0 -186
  137. package/dist/process-scanner.perf.test.js.map +0 -1
  138. package/dist/process-scanner.test.d.ts +0 -2
  139. package/dist/process-scanner.test.d.ts.map +0 -1
  140. package/dist/process-scanner.test.js +0 -399
  141. package/dist/process-scanner.test.js.map +0 -1
  142. package/dist/push-protocol.d.ts +0 -15
  143. package/dist/push-protocol.d.ts.map +0 -1
  144. package/dist/push-protocol.js +0 -23
  145. package/dist/push-protocol.js.map +0 -1
  146. package/dist/push-protocol.test.d.ts +0 -2
  147. package/dist/push-protocol.test.d.ts.map +0 -1
  148. package/dist/push-protocol.test.js +0 -57
  149. package/dist/push-protocol.test.js.map +0 -1
  150. package/dist/push-store.d.ts +0 -22
  151. package/dist/push-store.d.ts.map +0 -1
  152. package/dist/push-store.js +0 -103
  153. package/dist/push-store.js.map +0 -1
  154. package/dist/push-store.test.d.ts +0 -2
  155. package/dist/push-store.test.d.ts.map +0 -1
  156. package/dist/push-store.test.js +0 -79
  157. package/dist/push-store.test.js.map +0 -1
  158. package/dist/push.d.ts +0 -65
  159. package/dist/push.d.ts.map +0 -1
  160. package/dist/push.js +0 -202
  161. package/dist/push.js.map +0 -1
  162. package/dist/push.test.d.ts +0 -2
  163. package/dist/push.test.d.ts.map +0 -1
  164. package/dist/push.test.js +0 -199
  165. package/dist/push.test.js.map +0 -1
  166. package/dist/safe-stdio.d.ts +0 -3
  167. package/dist/safe-stdio.d.ts.map +0 -1
  168. package/dist/safe-stdio.js +0 -46
  169. package/dist/safe-stdio.js.map +0 -1
  170. package/dist/scanner.d.ts +0 -30
  171. package/dist/scanner.d.ts.map +0 -1
  172. package/dist/scanner.js +0 -859
  173. package/dist/scanner.js.map +0 -1
  174. package/dist/scanner.perf.test.d.ts +0 -2
  175. package/dist/scanner.perf.test.d.ts.map +0 -1
  176. package/dist/scanner.perf.test.js +0 -320
  177. package/dist/scanner.perf.test.js.map +0 -1
  178. package/dist/scanner.test.d.ts +0 -2
  179. package/dist/scanner.test.d.ts.map +0 -1
  180. package/dist/scanner.test.js +0 -948
  181. package/dist/scanner.test.js.map +0 -1
  182. package/dist/session-inventory.d.ts +0 -63
  183. package/dist/session-inventory.d.ts.map +0 -1
  184. package/dist/session-inventory.js +0 -525
  185. package/dist/session-inventory.js.map +0 -1
  186. package/dist/session-inventory.perf.test.d.ts +0 -2
  187. package/dist/session-inventory.perf.test.d.ts.map +0 -1
  188. package/dist/session-inventory.perf.test.js +0 -220
  189. package/dist/session-inventory.perf.test.js.map +0 -1
  190. package/dist/session-inventory.test.d.ts +0 -2
  191. package/dist/session-inventory.test.d.ts.map +0 -1
  192. package/dist/session-inventory.test.js +0 -712
  193. package/dist/session-inventory.test.js.map +0 -1
  194. package/dist/session-manager.d.ts +0 -75
  195. package/dist/session-manager.d.ts.map +0 -1
  196. package/dist/session-manager.js +0 -1515
  197. package/dist/session-manager.js.map +0 -1
  198. package/dist/session-manager.test.d.ts +0 -2
  199. package/dist/session-manager.test.d.ts.map +0 -1
  200. package/dist/session-manager.test.js +0 -2861
  201. package/dist/session-manager.test.js.map +0 -1
  202. package/dist/session-store.d.ts +0 -42
  203. package/dist/session-store.d.ts.map +0 -1
  204. package/dist/session-store.js +0 -163
  205. package/dist/session-store.js.map +0 -1
  206. package/dist/session-store.test.d.ts +0 -2
  207. package/dist/session-store.test.d.ts.map +0 -1
  208. package/dist/session-store.test.js +0 -236
  209. package/dist/session-store.test.js.map +0 -1
  210. package/dist/session-title.d.ts +0 -6
  211. package/dist/session-title.d.ts.map +0 -1
  212. package/dist/session-title.js +0 -105
  213. package/dist/session-title.js.map +0 -1
  214. package/dist/session-title.perf.test.d.ts +0 -2
  215. package/dist/session-title.perf.test.d.ts.map +0 -1
  216. package/dist/session-title.perf.test.js +0 -99
  217. package/dist/session-title.perf.test.js.map +0 -1
  218. package/dist/session-title.test.d.ts +0 -2
  219. package/dist/session-title.test.d.ts.map +0 -1
  220. package/dist/session-title.test.js +0 -199
  221. package/dist/session-title.test.js.map +0 -1
  222. package/dist/shutdown-endpoint.test.d.ts +0 -2
  223. package/dist/shutdown-endpoint.test.d.ts.map +0 -1
  224. package/dist/shutdown-endpoint.test.js +0 -93
  225. package/dist/shutdown-endpoint.test.js.map +0 -1
  226. package/dist/storage-housekeeping.d.ts +0 -28
  227. package/dist/storage-housekeeping.d.ts.map +0 -1
  228. package/dist/storage-housekeeping.js +0 -76
  229. package/dist/storage-housekeeping.js.map +0 -1
  230. package/dist/storage-housekeeping.test.d.ts +0 -2
  231. package/dist/storage-housekeeping.test.d.ts.map +0 -1
  232. package/dist/storage-housekeeping.test.js +0 -65
  233. package/dist/storage-housekeeping.test.js.map +0 -1
  234. package/dist/test-daemon-harness.d.ts +0 -31
  235. package/dist/test-daemon-harness.d.ts.map +0 -1
  236. package/dist/test-daemon-harness.js +0 -337
  237. package/dist/test-daemon-harness.js.map +0 -1
  238. package/dist/token-auth.test.d.ts +0 -2
  239. package/dist/token-auth.test.d.ts.map +0 -1
  240. package/dist/token-auth.test.js +0 -52
  241. package/dist/token-auth.test.js.map +0 -1
  242. package/dist/utils.d.ts +0 -4
  243. package/dist/utils.d.ts.map +0 -1
  244. package/dist/utils.js +0 -40
  245. package/dist/utils.js.map +0 -1
  246. package/dist/utils.test.d.ts +0 -2
  247. package/dist/utils.test.d.ts.map +0 -1
  248. package/dist/utils.test.js +0 -54
  249. package/dist/utils.test.js.map +0 -1
  250. package/dist/ws-data.d.ts +0 -4
  251. package/dist/ws-data.d.ts.map +0 -1
  252. package/dist/ws-data.js +0 -20
  253. package/dist/ws-data.js.map +0 -1
  254. package/dist/ws-data.test.d.ts +0 -2
  255. package/dist/ws-data.test.d.ts.map +0 -1
  256. package/dist/ws-data.test.js +0 -17
  257. package/dist/ws-data.test.js.map +0 -1
  258. package/perf-reporter.mjs +0 -138
  259. package/scripts/build-release.mjs +0 -41
  260. package/scripts/dev.mjs +0 -537
  261. package/src/advertised-hosts.test.ts +0 -125
  262. package/src/advertised-hosts.ts +0 -225
  263. package/src/audit.test.ts +0 -38
  264. package/src/audit.ts +0 -117
  265. package/src/auth.ts +0 -31
  266. package/src/claude-hooks.ts +0 -195
  267. package/src/cli-version.test.ts +0 -36
  268. package/src/cli-version.ts +0 -46
  269. package/src/config.test.ts +0 -254
  270. package/src/config.ts +0 -324
  271. package/src/dev-auth.test.ts +0 -183
  272. package/src/dev-script.test.ts +0 -511
  273. package/src/drivers/claude.test.ts +0 -1186
  274. package/src/drivers/claude.ts +0 -443
  275. package/src/drivers/codex.test.ts +0 -1096
  276. package/src/drivers/codex.ts +0 -879
  277. package/src/drivers/types.ts +0 -15
  278. package/src/e2e.test.ts +0 -139
  279. package/src/identity.test.ts +0 -26
  280. package/src/identity.ts +0 -82
  281. package/src/index-entry.test.ts +0 -336
  282. package/src/index.ts +0 -781
  283. package/src/logger.ts +0 -112
  284. package/src/metrics.ts +0 -117
  285. package/src/pairing-store.test.ts +0 -53
  286. package/src/pairing-store.ts +0 -154
  287. package/src/paths.ts +0 -19
  288. package/src/perf-compare.ts +0 -164
  289. package/src/port-conflict.test.ts +0 -45
  290. package/src/port-conflict.ts +0 -44
  291. package/src/process-scanner.perf.test.ts +0 -222
  292. package/src/process-scanner.test.ts +0 -575
  293. package/src/process-scanner.ts +0 -514
  294. package/src/push-protocol.test.ts +0 -74
  295. package/src/push-protocol.ts +0 -36
  296. package/src/push-store.test.ts +0 -89
  297. package/src/push-store.ts +0 -126
  298. package/src/push.test.ts +0 -234
  299. package/src/push.ts +0 -318
  300. package/src/safe-stdio.ts +0 -51
  301. package/src/scanner.perf.test.ts +0 -359
  302. package/src/scanner.test.ts +0 -1045
  303. package/src/scanner.ts +0 -924
  304. package/src/session-inventory.perf.test.ts +0 -250
  305. package/src/session-inventory.test.ts +0 -1002
  306. package/src/session-inventory.ts +0 -721
  307. package/src/session-manager.test.ts +0 -3430
  308. package/src/session-manager.ts +0 -1775
  309. package/src/session-store.test.ts +0 -276
  310. package/src/session-store.ts +0 -202
  311. package/src/session-title.perf.test.ts +0 -118
  312. package/src/session-title.test.ts +0 -286
  313. package/src/session-title.ts +0 -108
  314. package/src/shutdown-endpoint.test.ts +0 -95
  315. package/src/storage-housekeeping.test.ts +0 -78
  316. package/src/storage-housekeeping.ts +0 -111
  317. package/src/test-daemon-harness.ts +0 -410
  318. package/src/token-auth.test.ts +0 -67
  319. package/src/utils.test.ts +0 -65
  320. package/src/utils.ts +0 -47
  321. package/src/ws-data.test.ts +0 -20
  322. package/src/ws-data.ts +0 -26
  323. package/tsconfig.json +0 -12
@@ -1,721 +0,0 @@
1
- import type {
2
- AgentType,
3
- SessionConfidence,
4
- SessionInfo,
5
- SessionListCursor,
6
- SessionListPage,
7
- SessionResumeMode,
8
- SessionRuntimeInfo,
9
- SessionRuntimeState,
10
- } from '@vibelet/shared';
11
- import { scanRunningSessions, type RunningSessionCandidate } from './process-scanner.js';
12
- import { listSessions as listScannedSessions, searchSessionContent } from './scanner.js';
13
- import { isFallbackSessionTitle } from './session-title.js';
14
- import type { SessionRecord } from './session-store.js';
15
- import { logger as rootLogger } from './logger.js';
16
- import { metrics } from './metrics.js';
17
-
18
- const log = rootLogger.child({ module: 'inventory' });
19
- const DEFAULT_INVENTORY_SOURCE_TIMEOUT_MS = 6_000;
20
- const EXTERNAL_INVENTORY_BACKFILL_TTL_MS = 10_000;
21
-
22
- export type { SessionRecord } from './session-store.js';
23
-
24
- export interface ActiveSessionSnapshot {
25
- sessionId: string;
26
- agent: AgentType;
27
- cwd: string;
28
- approvalMode?: import('@vibelet/shared').ApprovalMode;
29
- title: string;
30
- createdAt: string;
31
- lastActivityAt: string;
32
- needsAttention?: boolean;
33
- isResponding?: boolean;
34
- managed?: boolean;
35
- }
36
-
37
- export interface InventorySources {
38
- activeSessions: ActiveSessionSnapshot[];
39
- sessionRecords: SessionRecord[];
40
- scannedSessions: SessionInfo[];
41
- runningSessions: RunningSessionCandidate[];
42
- scannedAt: string;
43
- deletedSessionIds?: ReadonlySet<string>;
44
- }
45
-
46
- export interface ListSessionPageOptions {
47
- agent?: AgentType;
48
- cwd?: string;
49
- search?: string;
50
- limit: number;
51
- cursor?: SessionListCursor;
52
- activeSessions: ActiveSessionSnapshot[];
53
- sessionRecords: SessionRecord[];
54
- deletedSessionIds?: ReadonlySet<string>;
55
- /** @internal override inventory loaders in tests */
56
- loaders?: Partial<InventoryLoaders>;
57
- /** @internal per-source timeout for slow inventory probes */
58
- sourceTimeoutMs?: number;
59
- }
60
-
61
- export interface InventoryLoaders {
62
- listScannedSessions: (agent?: AgentType, cwd?: string, partial?: SessionInfo[]) => Promise<SessionInfo[]>;
63
- scanRunningSessions: typeof scanRunningSessions;
64
- searchSessionContent: typeof searchSessionContent;
65
- }
66
-
67
- const defaultInventoryLoaders: InventoryLoaders = {
68
- listScannedSessions,
69
- scanRunningSessions,
70
- searchSessionContent,
71
- };
72
-
73
- interface ExternalInventorySnapshot {
74
- scannedSessions: SessionInfo[];
75
- runningSessions: RunningSessionCandidate[];
76
- scannedAt: string;
77
- refreshedAt: number;
78
- }
79
-
80
- interface ExternalInventoryHealthSnapshot {
81
- cachedSessions: number;
82
- runningSessions: number;
83
- cacheAgeMs?: number;
84
- backfillInFlight: boolean;
85
- lastBackfillDurationMs?: number;
86
- lastBackfillCompletedAt?: string;
87
- lastBackfillAppliedAt?: string;
88
- }
89
-
90
- type ExternalInventoryBackfillListener = () => void;
91
-
92
- let externalInventorySnapshot: ExternalInventorySnapshot = createEmptyExternalInventorySnapshot();
93
- let externalInventoryRefreshInflight: Promise<void> | null = null;
94
- let externalInventoryGeneration = 0;
95
- let externalInventoryLastBackfillCompletedAt = 0;
96
- let externalInventoryLastBackfillDurationMs = 0;
97
- let externalInventoryLastBackfillAppliedAt = 0;
98
- const externalInventoryBackfillListeners = new Set<ExternalInventoryBackfillListener>();
99
-
100
- function createEmptyExternalInventorySnapshot(): ExternalInventorySnapshot {
101
- return {
102
- scannedSessions: [],
103
- runningSessions: [],
104
- scannedAt: '',
105
- refreshedAt: 0,
106
- };
107
- }
108
-
109
- function serializeSessionInfo(session: SessionInfo): string {
110
- return JSON.stringify({
111
- sessionId: session.sessionId,
112
- agent: session.agent,
113
- cwd: session.cwd,
114
- title: session.title,
115
- createdAt: session.createdAt,
116
- lastActivityAt: session.lastActivityAt,
117
- sources: [...session.sources].sort(),
118
- runtime: {
119
- state: session.runtime.state,
120
- confidence: session.runtime.confidence,
121
- resumeMode: session.runtime.resumeMode,
122
- pid: session.runtime.pid,
123
- command: session.runtime.command,
124
- isResponding: session.runtime.isResponding,
125
- needsAttention: session.runtime.needsAttention,
126
- },
127
- managed: session.managed,
128
- approvalMode: session.approvalMode,
129
- observedApprovalMode: session.observedApprovalMode,
130
- });
131
- }
132
-
133
- function serializeRunningSession(candidate: RunningSessionCandidate): string {
134
- return JSON.stringify({
135
- sessionId: candidate.sessionId,
136
- agent: candidate.agent,
137
- cwd: candidate.cwd,
138
- title: candidate.title,
139
- pid: candidate.pid,
140
- command: candidate.command,
141
- confidence: candidate.confidence,
142
- isResponding: candidate.isResponding,
143
- approvalMode: candidate.approvalMode,
144
- });
145
- }
146
-
147
- function externalInventorySignature(snapshot: ExternalInventorySnapshot): string {
148
- return JSON.stringify({
149
- scannedSessions: snapshot.scannedSessions.map(serializeSessionInfo).sort(),
150
- runningSessions: snapshot.runningSessions.map(serializeRunningSession).sort(),
151
- });
152
- }
153
-
154
- function notifyExternalInventoryBackfillListeners(): void {
155
- for (const listener of externalInventoryBackfillListeners) {
156
- try {
157
- listener();
158
- } catch (error) {
159
- log.warn({ error: String(error) }, 'session inventory backfill listener failed');
160
- }
161
- }
162
- }
163
-
164
- function updateExternalInventoryGauges(): void {
165
- metrics.gauge('session.inventory.external_cached_sessions', externalInventorySnapshot.scannedSessions.length);
166
- metrics.gauge('session.inventory.external_running_sessions', externalInventorySnapshot.runningSessions.length);
167
- metrics.gauge('session.inventory.backfill_inflight', externalInventoryRefreshInflight ? 1 : 0);
168
- }
169
-
170
- export function onExternalInventoryBackfill(listener: ExternalInventoryBackfillListener): () => void {
171
- externalInventoryBackfillListeners.add(listener);
172
- return () => {
173
- externalInventoryBackfillListeners.delete(listener);
174
- };
175
- }
176
-
177
- export function getExternalInventoryHealth(): ExternalInventoryHealthSnapshot {
178
- return {
179
- cachedSessions: externalInventorySnapshot.scannedSessions.length,
180
- runningSessions: externalInventorySnapshot.runningSessions.length,
181
- cacheAgeMs: externalInventorySnapshot.refreshedAt
182
- ? Math.max(0, Date.now() - externalInventorySnapshot.refreshedAt)
183
- : undefined,
184
- backfillInFlight: Boolean(externalInventoryRefreshInflight),
185
- lastBackfillDurationMs: externalInventoryLastBackfillDurationMs || undefined,
186
- lastBackfillCompletedAt: externalInventoryLastBackfillCompletedAt
187
- ? new Date(externalInventoryLastBackfillCompletedAt).toISOString()
188
- : undefined,
189
- lastBackfillAppliedAt: externalInventoryLastBackfillAppliedAt
190
- ? new Date(externalInventoryLastBackfillAppliedAt).toISOString()
191
- : undefined,
192
- };
193
- }
194
-
195
- updateExternalInventoryGauges();
196
-
197
- const runtimeStateRank: Record<SessionRuntimeState, number> = {
198
- daemonActive: 3,
199
- externalRunning: 2,
200
- idle: 1,
201
- };
202
-
203
- const confidenceRank: Record<SessionConfidence, number> = {
204
- high: 3,
205
- medium: 2,
206
- low: 1,
207
- };
208
-
209
- function compareSessions(a: SessionInfo, b: SessionInfo): number {
210
- return (
211
- b.lastActivityAt.localeCompare(a.lastActivityAt) ||
212
- a.sessionId.localeCompare(b.sessionId)
213
- );
214
- }
215
-
216
- function compareActiveSessions(a: SessionInfo, b: SessionInfo): number {
217
- // Sessions needing attention always come first
218
- const aAttention = a.runtime.needsAttention ? 1 : 0;
219
- const bAttention = b.runtime.needsAttention ? 1 : 0;
220
- // Responding sessions stay pinned near top so the list doesn't shift mid-reply
221
- const aResponding = a.runtime.isResponding ? 1 : 0;
222
- const bResponding = b.runtime.isResponding ? 1 : 0;
223
- return (
224
- bAttention - aAttention ||
225
- bResponding - aResponding ||
226
- runtimeStateRank[b.runtime.state] - runtimeStateRank[a.runtime.state] ||
227
- compareSessions(a, b)
228
- );
229
- }
230
-
231
- function pickRuntime(current: SessionRuntimeInfo, next: SessionRuntimeInfo): SessionRuntimeInfo {
232
- if (runtimeStateRank[next.state] > runtimeStateRank[current.state]) return next;
233
- if (runtimeStateRank[next.state] < runtimeStateRank[current.state]) return current;
234
- if (confidenceRank[next.confidence] > confidenceRank[current.confidence]) return next;
235
- return current;
236
- }
237
-
238
- function minIso(a: string, b: string): string {
239
- if (!a) return b;
240
- if (!b) return a;
241
- return a.localeCompare(b) <= 0 ? a : b;
242
- }
243
-
244
- function maxIso(a: string, b: string): string {
245
- if (!a) return b;
246
- if (!b) return a;
247
- return a.localeCompare(b) >= 0 ? a : b;
248
- }
249
-
250
- function shouldOverwriteTitle(current: string | undefined, next: string | undefined): boolean {
251
- if (!next) return false;
252
- if (!current) return true;
253
- if (isFallbackSessionTitle(next) && !isFallbackSessionTitle(current)) return false;
254
- return true;
255
- }
256
-
257
- export function createIdleRuntime(confidence: SessionConfidence = 'high', resumeMode: SessionResumeMode = 'resumeSession'): SessionRuntimeInfo {
258
- return {
259
- state: 'idle',
260
- confidence,
261
- resumeMode,
262
- };
263
- }
264
-
265
- function toRecordSession(record: SessionRecord): SessionInfo {
266
- return {
267
- sessionId: record.sessionId,
268
- agent: record.agent,
269
- cwd: record.cwd,
270
- title: record.title,
271
- createdAt: record.createdAt,
272
- lastActivityAt: record.lastActivityAt,
273
- sources: ['record'],
274
- runtime: {
275
- ...createIdleRuntime(),
276
- // Note: record.isResponding is intentionally NOT restored here.
277
- // If a session only exists in the store (no active driver), it
278
- // cannot still be responding — the daemon was restarted mid-reply.
279
- },
280
- managed: record.managed,
281
- approvalMode: record.approvalMode,
282
- };
283
- }
284
-
285
- function toRunningSession(candidate: RunningSessionCandidate, scannedAt: string): SessionInfo {
286
- return {
287
- sessionId: candidate.sessionId,
288
- agent: candidate.agent,
289
- cwd: candidate.cwd,
290
- title: candidate.title,
291
- createdAt: scannedAt,
292
- lastActivityAt: scannedAt,
293
- sources: ['process'],
294
- runtime: {
295
- state: 'externalRunning',
296
- confidence: candidate.confidence,
297
- resumeMode: 'resumeSession',
298
- pid: candidate.pid,
299
- command: candidate.command,
300
- isResponding: candidate.isResponding,
301
- },
302
- observedApprovalMode: candidate.approvalMode,
303
- };
304
- }
305
-
306
- function toActiveSession(session: ActiveSessionSnapshot): SessionInfo {
307
- return {
308
- sessionId: session.sessionId,
309
- agent: session.agent,
310
- cwd: session.cwd,
311
- title: session.title,
312
- createdAt: session.createdAt,
313
- lastActivityAt: session.lastActivityAt,
314
- sources: ['daemon'],
315
- runtime: {
316
- state: 'daemonActive',
317
- confidence: 'high',
318
- resumeMode: 'reuseDriver',
319
- needsAttention: session.needsAttention || undefined,
320
- isResponding: session.isResponding || undefined,
321
- },
322
- managed: session.managed,
323
- approvalMode: session.approvalMode,
324
- };
325
- }
326
-
327
- function mergeInto(merged: Map<string, SessionInfo>, sessions: SessionInfo[]): void {
328
- for (const session of sessions) {
329
- const key = `${session.agent}:${session.sessionId}`;
330
- const current = merged.get(key);
331
- if (!current) {
332
- merged.set(key, {
333
- ...session,
334
- sources: [...session.sources],
335
- runtime: { ...session.runtime },
336
- });
337
- continue;
338
- }
339
-
340
- const nextTitle = shouldOverwriteTitle(current.title, session.title) ? session.title : current.title;
341
- const nextCwd = session.cwd || current.cwd;
342
- const nextRuntime = pickRuntime(current.runtime, session.runtime);
343
-
344
- // Deduplicate sources without Set (at most 4 elements)
345
- const nextSources = current.sources.slice();
346
- for (const s of session.sources) {
347
- if (!nextSources.includes(s)) nextSources.push(s);
348
- }
349
-
350
- // Process-scanner uses scannedAt (current time) as lastActivityAt which is inaccurate.
351
- // Only use it if we have no better timestamp.
352
- const incomingIsProcessScan = session.sources.includes('process');
353
- const currentIsProcessScan = current.sources.includes('process') && !current.sources.includes('daemon');
354
- const nextLastActivityAt =
355
- incomingIsProcessScan ? current.lastActivityAt
356
- : currentIsProcessScan ? session.lastActivityAt
357
- : maxIso(current.lastActivityAt, session.lastActivityAt);
358
-
359
- merged.set(key, {
360
- ...current,
361
- cwd: nextCwd,
362
- title: nextTitle,
363
- createdAt: minIso(current.createdAt, session.createdAt),
364
- lastActivityAt: nextLastActivityAt,
365
- sources: nextSources,
366
- runtime: nextRuntime,
367
- managed: current.managed || session.managed,
368
- approvalMode: session.approvalMode ?? current.approvalMode,
369
- observedApprovalMode: session.observedApprovalMode ?? current.observedApprovalMode,
370
- });
371
- }
372
- }
373
-
374
- function mergeAllSessions(...lists: SessionInfo[][]): SessionInfo[] {
375
- const merged = new Map<string, SessionInfo>();
376
- for (const list of lists) mergeInto(merged, list);
377
- return [...merged.values()];
378
- }
379
-
380
- function applyFilters(
381
- sessions: SessionInfo[],
382
- agent?: AgentType,
383
- cwd?: string,
384
- search?: string,
385
- contentMatchIds?: Set<string>,
386
- ): SessionInfo[] {
387
- const q = search?.toLowerCase();
388
- const result = sessions.filter((session) => {
389
- if (agent && session.agent !== agent) return false;
390
- if (cwd && session.cwd !== cwd) return false;
391
- if (q) {
392
- const title = (session.title ?? '').toLowerCase();
393
- const sessionCwd = (session.cwd ?? '').toLowerCase();
394
- const matchesMeta = title.includes(q) || sessionCwd.includes(q);
395
- const matchesContent = contentMatchIds?.has(session.sessionId) ?? false;
396
- if (!matchesMeta && !matchesContent) return false;
397
- }
398
- return true;
399
- });
400
- if (search) {
401
- log.info({ total: sessions.length, filtered: result.length, search, contentMatchIds: contentMatchIds?.size ?? 0 }, 'filter applied');
402
- }
403
- return result;
404
- }
405
-
406
- function cursorIndex(sessions: SessionInfo[], cursor?: SessionListCursor): number {
407
- if (!cursor) return 0;
408
-
409
- // Binary search on the sorted (desc lastActivityAt, asc sessionId) array
410
- let lo = 0;
411
- let hi = sessions.length;
412
- while (lo < hi) {
413
- const mid = (lo + hi) >> 1;
414
- const s = sessions[mid];
415
- const cmp = cursor.lastActivityAt.localeCompare(s.lastActivityAt)
416
- || s.sessionId.localeCompare(cursor.sessionId);
417
- if (cmp > 0) hi = mid;
418
- else lo = mid + 1;
419
- }
420
-
421
- // Verify exact match at lo-1
422
- if (lo > 0) {
423
- const prev = sessions[lo - 1];
424
- if (prev.lastActivityAt === cursor.lastActivityAt && prev.sessionId === cursor.sessionId) {
425
- return lo;
426
- }
427
- }
428
- return 0;
429
- }
430
-
431
- export function buildSessionInventoryPage(
432
- sources: InventorySources,
433
- limit: number,
434
- cursor?: SessionListCursor,
435
- agent?: AgentType,
436
- cwd?: string,
437
- search?: string,
438
- contentMatchIds?: Set<string>,
439
- ): SessionListPage {
440
- const pageSize = search ? Infinity : Math.max(1, limit || 50);
441
- const merged = mergeAllSessions(
442
- sources.sessionRecords.map(toRecordSession),
443
- sources.scannedSessions,
444
- sources.runningSessions.map((session) => toRunningSession(session, sources.scannedAt)),
445
- sources.activeSessions.map(toActiveSession),
446
- );
447
-
448
- const visible = sources.deletedSessionIds?.size
449
- ? merged.filter((session) => !sources.deletedSessionIds?.has(session.sessionId))
450
- : merged;
451
- const filtered = applyFilters(visible, agent, cwd, search, contentMatchIds);
452
- const activeSessions = filtered
453
- .filter((session) => session.runtime.state !== 'idle' || session.managed)
454
- .sort(compareActiveSessions);
455
- const idleSessions = filtered
456
- .filter((session) => session.runtime.state === 'idle' && !session.managed)
457
- .sort(compareSessions);
458
-
459
- const idleStart = cursorIndex(idleSessions, cursor);
460
- const idleSlice = idleSessions.slice(idleStart, idleStart + pageSize);
461
- const hasMoreIdle = idleStart + idleSlice.length < idleSessions.length;
462
- const nextCursor = hasMoreIdle && idleSlice.length > 0
463
- ? {
464
- lastActivityAt: idleSlice[idleSlice.length - 1].lastActivityAt,
465
- sessionId: idleSlice[idleSlice.length - 1].sessionId,
466
- }
467
- : undefined;
468
-
469
- return {
470
- sessions: cursor ? idleSlice : [...activeSessions, ...idleSlice],
471
- nextCursor,
472
- };
473
- }
474
-
475
- function loadInventorySource<T>(
476
- source: string,
477
- load: () => Promise<T>,
478
- fallback: T,
479
- timeoutMs: number,
480
- /** Shared array populated incrementally by the loader — used as partial result on timeout */
481
- partial?: T,
482
- ): Promise<T> {
483
- return new Promise((resolve) => {
484
- const startedAt = Date.now();
485
- let settled = false;
486
- let timer: ReturnType<typeof setTimeout> | null = null;
487
-
488
- const finish = (value: T) => {
489
- if (settled) return;
490
- settled = true;
491
- if (timer) {
492
- clearTimeout(timer);
493
- timer = null;
494
- }
495
- resolve(value);
496
- };
497
-
498
- timer = setTimeout(() => {
499
- if (settled) return;
500
- // Use partial results if available, otherwise fall back to empty
501
- const hasPartial = Array.isArray(partial) && partial.length > 0;
502
- log.warn(
503
- { source, timeoutMs, durationMs: Date.now() - startedAt, partial: hasPartial ? (partial as unknown[]).length : 0 },
504
- hasPartial
505
- ? 'session inventory source timed out, using partial results'
506
- : 'session inventory source timed out, using fallback',
507
- );
508
- finish(hasPartial ? partial : fallback);
509
- }, timeoutMs);
510
- if (typeof timer === 'object' && 'unref' in timer) {
511
- timer.unref();
512
- }
513
-
514
- load()
515
- .then((value) => {
516
- finish(value);
517
- })
518
- .catch((error) => {
519
- if (!settled) {
520
- log.warn({ source, error: String(error) }, 'session inventory source failed, using fallback');
521
- }
522
- finish(fallback);
523
- });
524
- });
525
- }
526
-
527
- function shouldRefreshExternalInventory(now = Date.now()): boolean {
528
- return (
529
- externalInventorySnapshot.refreshedAt === 0
530
- || now - externalInventorySnapshot.refreshedAt >= EXTERNAL_INVENTORY_BACKFILL_TTL_MS
531
- );
532
- }
533
-
534
- async function loadExternalInventorySnapshot(
535
- loaders: InventoryLoaders,
536
- timeoutMs: number,
537
- ): Promise<ExternalInventorySnapshot> {
538
- const scannedAt = new Date().toISOString();
539
- const partialScanned: SessionInfo[] = [];
540
- const [scannedSessions, runningSessions] = await Promise.all([
541
- loadInventorySource(
542
- 'scanner.sessions',
543
- () => loaders.listScannedSessions(undefined, undefined, partialScanned),
544
- [] as SessionInfo[],
545
- timeoutMs,
546
- partialScanned,
547
- ),
548
- loadInventorySource(
549
- 'process.running_sessions',
550
- () => loaders.scanRunningSessions(),
551
- [],
552
- timeoutMs,
553
- ),
554
- ]);
555
-
556
- return {
557
- scannedSessions,
558
- runningSessions,
559
- scannedAt,
560
- refreshedAt: Date.now(),
561
- };
562
- }
563
-
564
- function scheduleExternalInventoryRefresh(loaders: InventoryLoaders, timeoutMs: number): void {
565
- if (externalInventoryRefreshInflight) return;
566
-
567
- const generation = externalInventoryGeneration;
568
- const endTimer = metrics.startTimer('session.inventory.backfill_latency_ms');
569
- externalInventoryRefreshInflight = loadExternalInventorySnapshot(loaders, timeoutMs)
570
- .then((nextSnapshot) => {
571
- if (generation !== externalInventoryGeneration) return;
572
-
573
- const previousSignature = externalInventorySignature(externalInventorySnapshot);
574
- externalInventorySnapshot = nextSnapshot;
575
- const nextSignature = externalInventorySignature(nextSnapshot);
576
- const durationMs = endTimer();
577
- externalInventoryLastBackfillDurationMs = durationMs;
578
- externalInventoryLastBackfillCompletedAt = Date.now();
579
- updateExternalInventoryGauges();
580
- if (previousSignature === nextSignature) return;
581
-
582
- externalInventoryLastBackfillAppliedAt = Date.now();
583
- metrics.increment('session.inventory.backfill_applied');
584
- log.info(
585
- {
586
- durationMs,
587
- scanned: nextSnapshot.scannedSessions.length,
588
- running: nextSnapshot.runningSessions.length,
589
- },
590
- 'session inventory external backfill applied',
591
- );
592
- notifyExternalInventoryBackfillListeners();
593
- })
594
- .catch((error) => {
595
- if (generation !== externalInventoryGeneration) return;
596
- externalInventoryLastBackfillDurationMs = endTimer();
597
- externalInventoryLastBackfillCompletedAt = Date.now();
598
- updateExternalInventoryGauges();
599
- log.warn(
600
- {
601
- error: String(error),
602
- durationMs: externalInventoryLastBackfillDurationMs,
603
- },
604
- 'session inventory external backfill failed',
605
- );
606
- })
607
- .finally(() => {
608
- if (generation === externalInventoryGeneration) {
609
- externalInventoryRefreshInflight = null;
610
- updateExternalInventoryGauges();
611
- }
612
- });
613
- updateExternalInventoryGauges();
614
- }
615
-
616
- export async function __waitForExternalInventoryRefreshForTests(): Promise<void> {
617
- await externalInventoryRefreshInflight;
618
- }
619
-
620
- export function __emitExternalInventoryBackfillForTests(): void {
621
- notifyExternalInventoryBackfillListeners();
622
- }
623
-
624
- export function __resetExternalInventoryStateForTests(): void {
625
- externalInventoryGeneration += 1;
626
- externalInventorySnapshot = createEmptyExternalInventorySnapshot();
627
- externalInventoryRefreshInflight = null;
628
- externalInventoryLastBackfillCompletedAt = 0;
629
- externalInventoryLastBackfillDurationMs = 0;
630
- externalInventoryLastBackfillAppliedAt = 0;
631
- externalInventoryBackfillListeners.clear();
632
- updateExternalInventoryGauges();
633
- }
634
-
635
- export async function listSessionPage(options: ListSessionPageOptions): Promise<SessionListPage> {
636
- const loaders: InventoryLoaders = {
637
- ...defaultInventoryLoaders,
638
- ...(options.loaders ?? {}),
639
- };
640
- const timeoutMs = Math.max(250, options.sourceTimeoutMs ?? DEFAULT_INVENTORY_SOURCE_TIMEOUT_MS);
641
- if (!options.search) {
642
- const endFastPath = metrics.startTimer('session.inventory.fast_path_latency_ms');
643
- if (shouldRefreshExternalInventory()) {
644
- scheduleExternalInventoryRefresh(loaders, timeoutMs);
645
- }
646
-
647
- const snapshot = externalInventorySnapshot;
648
- const page = buildSessionInventoryPage(
649
- {
650
- activeSessions: options.activeSessions,
651
- sessionRecords: options.sessionRecords,
652
- scannedSessions: snapshot.scannedSessions,
653
- runningSessions: snapshot.runningSessions,
654
- scannedAt: snapshot.scannedAt || new Date().toISOString(),
655
- deletedSessionIds: options.deletedSessionIds,
656
- },
657
- options.limit,
658
- options.cursor,
659
- options.agent,
660
- options.cwd,
661
- );
662
- endFastPath();
663
- return page;
664
- }
665
-
666
- // Shared array that the scanner populates incrementally (mtime-sorted, recent first).
667
- // On timeout, loadInventorySource returns whatever has been collected so far
668
- // instead of discarding all results.
669
- const partialScanned: SessionInfo[] = [];
670
- const scannedAt = new Date().toISOString();
671
- const search = options.search;
672
- const [scannedSessions, runningSessions, contentMatchIds] = await Promise.all([
673
- loadInventorySource(
674
- 'scanner.sessions',
675
- () => loaders.listScannedSessions(options.agent, options.cwd, partialScanned),
676
- [] as SessionInfo[],
677
- timeoutMs,
678
- partialScanned,
679
- ),
680
- loadInventorySource(
681
- 'process.running_sessions',
682
- () => loaders.scanRunningSessions(),
683
- [],
684
- timeoutMs,
685
- ),
686
- loadInventorySource(
687
- 'scanner.search_content',
688
- () => loaders.searchSessionContent(search, options.agent),
689
- undefined,
690
- timeoutMs,
691
- ),
692
- ]);
693
-
694
- log.info(
695
- {
696
- search: options.search,
697
- contentMatches: contentMatchIds?.size ?? 0,
698
- scanned: scannedSessions.length,
699
- records: options.sessionRecords.length,
700
- active: options.activeSessions.length,
701
- },
702
- 'search results',
703
- );
704
-
705
- return buildSessionInventoryPage(
706
- {
707
- activeSessions: options.activeSessions,
708
- sessionRecords: options.sessionRecords,
709
- scannedSessions,
710
- runningSessions,
711
- scannedAt,
712
- deletedSessionIds: options.deletedSessionIds,
713
- },
714
- options.limit,
715
- options.cursor,
716
- options.agent,
717
- options.cwd,
718
- search,
719
- contentMatchIds,
720
- );
721
- }