@vibelet/cli 0.1.35 → 0.1.36

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/app.json +5 -0
  2. package/dist/advertised-hosts.d.ts +34 -0
  3. package/dist/advertised-hosts.d.ts.map +1 -0
  4. package/dist/advertised-hosts.js +176 -0
  5. package/dist/advertised-hosts.js.map +1 -0
  6. package/dist/advertised-hosts.test.d.ts +2 -0
  7. package/dist/advertised-hosts.test.d.ts.map +1 -0
  8. package/dist/advertised-hosts.test.js +96 -0
  9. package/dist/advertised-hosts.test.js.map +1 -0
  10. package/dist/audit.d.ts +30 -0
  11. package/dist/audit.d.ts.map +1 -0
  12. package/dist/audit.js +73 -0
  13. package/dist/audit.js.map +1 -0
  14. package/dist/audit.test.d.ts +2 -0
  15. package/dist/audit.test.d.ts.map +1 -0
  16. package/dist/audit.test.js +33 -0
  17. package/dist/audit.test.js.map +1 -0
  18. package/dist/auth.d.ts +6 -0
  19. package/dist/auth.d.ts.map +1 -0
  20. package/dist/auth.js +27 -0
  21. package/dist/auth.js.map +1 -0
  22. package/dist/claude-hooks.d.ts +58 -0
  23. package/dist/claude-hooks.d.ts.map +1 -0
  24. package/dist/claude-hooks.js +129 -0
  25. package/dist/claude-hooks.js.map +1 -0
  26. package/dist/cli-version.d.ts +3 -0
  27. package/dist/cli-version.d.ts.map +1 -0
  28. package/dist/cli-version.js +35 -0
  29. package/dist/cli-version.js.map +1 -0
  30. package/dist/cli-version.test.d.ts +2 -0
  31. package/dist/cli-version.test.d.ts.map +1 -0
  32. package/dist/cli-version.test.js +38 -0
  33. package/dist/cli-version.test.js.map +1 -0
  34. package/dist/config.d.ts +30 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +327 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/config.test.d.ts +2 -0
  39. package/dist/config.test.d.ts.map +1 -0
  40. package/dist/config.test.js +184 -0
  41. package/dist/config.test.js.map +1 -0
  42. package/dist/dev-auth.test.d.ts +2 -0
  43. package/dist/dev-auth.test.d.ts.map +1 -0
  44. package/dist/dev-auth.test.js +154 -0
  45. package/dist/dev-auth.test.js.map +1 -0
  46. package/dist/dev-script.test.d.ts +2 -0
  47. package/dist/dev-script.test.d.ts.map +1 -0
  48. package/dist/dev-script.test.js +412 -0
  49. package/dist/dev-script.test.js.map +1 -0
  50. package/dist/drivers/claude.d.ts +34 -0
  51. package/dist/drivers/claude.d.ts.map +1 -0
  52. package/dist/drivers/claude.js +413 -0
  53. package/dist/drivers/claude.js.map +1 -0
  54. package/dist/drivers/claude.test.d.ts +2 -0
  55. package/dist/drivers/claude.test.d.ts.map +1 -0
  56. package/dist/drivers/claude.test.js +951 -0
  57. package/dist/drivers/claude.test.js.map +1 -0
  58. package/dist/drivers/codex.d.ts +38 -0
  59. package/dist/drivers/codex.d.ts.map +1 -0
  60. package/dist/drivers/codex.js +771 -0
  61. package/dist/drivers/codex.js.map +1 -0
  62. package/dist/drivers/codex.test.d.ts +2 -0
  63. package/dist/drivers/codex.test.d.ts.map +1 -0
  64. package/dist/drivers/codex.test.js +939 -0
  65. package/dist/drivers/codex.test.js.map +1 -0
  66. package/dist/drivers/types.d.ts +14 -0
  67. package/dist/drivers/types.d.ts.map +1 -0
  68. package/dist/drivers/types.js +2 -0
  69. package/dist/drivers/types.js.map +1 -0
  70. package/dist/e2e.test.d.ts +2 -0
  71. package/dist/e2e.test.d.ts.map +1 -0
  72. package/dist/e2e.test.js +111 -0
  73. package/dist/e2e.test.js.map +1 -0
  74. package/dist/identity.d.ts +10 -0
  75. package/dist/identity.d.ts.map +1 -0
  76. package/dist/identity.js +66 -0
  77. package/dist/identity.js.map +1 -0
  78. package/dist/identity.test.d.ts +2 -0
  79. package/dist/identity.test.d.ts.map +1 -0
  80. package/dist/identity.test.js +25 -0
  81. package/dist/identity.test.js.map +1 -0
  82. package/dist/index-entry.test.d.ts +2 -0
  83. package/dist/index-entry.test.d.ts.map +1 -0
  84. package/dist/index-entry.test.js +272 -0
  85. package/dist/index-entry.test.js.map +1 -0
  86. package/dist/index.d.ts +2 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +707 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/logger.d.ts +31 -0
  91. package/dist/logger.d.ts.map +1 -0
  92. package/dist/logger.js +75 -0
  93. package/dist/logger.js.map +1 -0
  94. package/dist/metrics.d.ts +52 -0
  95. package/dist/metrics.d.ts.map +1 -0
  96. package/dist/metrics.js +89 -0
  97. package/dist/metrics.js.map +1 -0
  98. package/dist/pairing-store.d.ts +29 -0
  99. package/dist/pairing-store.d.ts.map +1 -0
  100. package/dist/pairing-store.js +131 -0
  101. package/dist/pairing-store.js.map +1 -0
  102. package/dist/pairing-store.test.d.ts +2 -0
  103. package/dist/pairing-store.test.d.ts.map +1 -0
  104. package/dist/pairing-store.test.js +47 -0
  105. package/dist/pairing-store.test.js.map +1 -0
  106. package/dist/paths.d.ts +16 -0
  107. package/dist/paths.d.ts.map +1 -0
  108. package/dist/paths.js +18 -0
  109. package/dist/paths.js.map +1 -0
  110. package/dist/perf-compare.d.ts +13 -0
  111. package/dist/perf-compare.d.ts.map +1 -0
  112. package/dist/perf-compare.js +125 -0
  113. package/dist/perf-compare.js.map +1 -0
  114. package/dist/port-conflict.d.ts +9 -0
  115. package/dist/port-conflict.d.ts.map +1 -0
  116. package/dist/port-conflict.js +33 -0
  117. package/dist/port-conflict.js.map +1 -0
  118. package/dist/port-conflict.test.d.ts +2 -0
  119. package/dist/port-conflict.test.d.ts.map +1 -0
  120. package/dist/port-conflict.test.js +38 -0
  121. package/dist/port-conflict.test.js.map +1 -0
  122. package/dist/process-scanner.d.ts +43 -0
  123. package/dist/process-scanner.d.ts.map +1 -0
  124. package/dist/process-scanner.js +453 -0
  125. package/dist/process-scanner.js.map +1 -0
  126. package/dist/process-scanner.perf.test.d.ts +2 -0
  127. package/dist/process-scanner.perf.test.d.ts.map +1 -0
  128. package/dist/process-scanner.perf.test.js +186 -0
  129. package/dist/process-scanner.perf.test.js.map +1 -0
  130. package/dist/process-scanner.test.d.ts +2 -0
  131. package/dist/process-scanner.test.d.ts.map +1 -0
  132. package/dist/process-scanner.test.js +399 -0
  133. package/dist/process-scanner.test.js.map +1 -0
  134. package/dist/push-protocol.d.ts +15 -0
  135. package/dist/push-protocol.d.ts.map +1 -0
  136. package/dist/push-protocol.js +23 -0
  137. package/dist/push-protocol.js.map +1 -0
  138. package/dist/push-protocol.test.d.ts +2 -0
  139. package/dist/push-protocol.test.d.ts.map +1 -0
  140. package/dist/push-protocol.test.js +57 -0
  141. package/dist/push-protocol.test.js.map +1 -0
  142. package/dist/push-store.d.ts +22 -0
  143. package/dist/push-store.d.ts.map +1 -0
  144. package/dist/push-store.js +103 -0
  145. package/dist/push-store.js.map +1 -0
  146. package/dist/push-store.test.d.ts +2 -0
  147. package/dist/push-store.test.d.ts.map +1 -0
  148. package/dist/push-store.test.js +79 -0
  149. package/dist/push-store.test.js.map +1 -0
  150. package/dist/push.d.ts +65 -0
  151. package/dist/push.d.ts.map +1 -0
  152. package/dist/push.js +202 -0
  153. package/dist/push.js.map +1 -0
  154. package/dist/push.test.d.ts +2 -0
  155. package/dist/push.test.d.ts.map +1 -0
  156. package/dist/push.test.js +199 -0
  157. package/dist/push.test.js.map +1 -0
  158. package/dist/safe-stdio.d.ts +3 -0
  159. package/dist/safe-stdio.d.ts.map +1 -0
  160. package/dist/safe-stdio.js +46 -0
  161. package/dist/safe-stdio.js.map +1 -0
  162. package/dist/scanner.d.ts +30 -0
  163. package/dist/scanner.d.ts.map +1 -0
  164. package/dist/scanner.js +859 -0
  165. package/dist/scanner.js.map +1 -0
  166. package/dist/scanner.perf.test.d.ts +2 -0
  167. package/dist/scanner.perf.test.d.ts.map +1 -0
  168. package/dist/scanner.perf.test.js +320 -0
  169. package/dist/scanner.perf.test.js.map +1 -0
  170. package/dist/scanner.test.d.ts +2 -0
  171. package/dist/scanner.test.d.ts.map +1 -0
  172. package/dist/scanner.test.js +948 -0
  173. package/dist/scanner.test.js.map +1 -0
  174. package/dist/session-inventory.d.ts +63 -0
  175. package/dist/session-inventory.d.ts.map +1 -0
  176. package/dist/session-inventory.js +525 -0
  177. package/dist/session-inventory.js.map +1 -0
  178. package/dist/session-inventory.perf.test.d.ts +2 -0
  179. package/dist/session-inventory.perf.test.d.ts.map +1 -0
  180. package/dist/session-inventory.perf.test.js +220 -0
  181. package/dist/session-inventory.perf.test.js.map +1 -0
  182. package/dist/session-inventory.test.d.ts +2 -0
  183. package/dist/session-inventory.test.d.ts.map +1 -0
  184. package/dist/session-inventory.test.js +712 -0
  185. package/dist/session-inventory.test.js.map +1 -0
  186. package/dist/session-manager.d.ts +75 -0
  187. package/dist/session-manager.d.ts.map +1 -0
  188. package/dist/session-manager.js +1515 -0
  189. package/dist/session-manager.js.map +1 -0
  190. package/dist/session-manager.test.d.ts +2 -0
  191. package/dist/session-manager.test.d.ts.map +1 -0
  192. package/dist/session-manager.test.js +2861 -0
  193. package/dist/session-manager.test.js.map +1 -0
  194. package/dist/session-store.d.ts +42 -0
  195. package/dist/session-store.d.ts.map +1 -0
  196. package/dist/session-store.js +163 -0
  197. package/dist/session-store.js.map +1 -0
  198. package/dist/session-store.test.d.ts +2 -0
  199. package/dist/session-store.test.d.ts.map +1 -0
  200. package/dist/session-store.test.js +236 -0
  201. package/dist/session-store.test.js.map +1 -0
  202. package/dist/session-title.d.ts +6 -0
  203. package/dist/session-title.d.ts.map +1 -0
  204. package/dist/session-title.js +105 -0
  205. package/dist/session-title.js.map +1 -0
  206. package/dist/session-title.perf.test.d.ts +2 -0
  207. package/dist/session-title.perf.test.d.ts.map +1 -0
  208. package/dist/session-title.perf.test.js +99 -0
  209. package/dist/session-title.perf.test.js.map +1 -0
  210. package/dist/session-title.test.d.ts +2 -0
  211. package/dist/session-title.test.d.ts.map +1 -0
  212. package/dist/session-title.test.js +199 -0
  213. package/dist/session-title.test.js.map +1 -0
  214. package/dist/shutdown-endpoint.test.d.ts +2 -0
  215. package/dist/shutdown-endpoint.test.d.ts.map +1 -0
  216. package/dist/shutdown-endpoint.test.js +93 -0
  217. package/dist/shutdown-endpoint.test.js.map +1 -0
  218. package/dist/storage-housekeeping.d.ts +28 -0
  219. package/dist/storage-housekeeping.d.ts.map +1 -0
  220. package/dist/storage-housekeeping.js +76 -0
  221. package/dist/storage-housekeeping.js.map +1 -0
  222. package/dist/storage-housekeeping.test.d.ts +2 -0
  223. package/dist/storage-housekeeping.test.d.ts.map +1 -0
  224. package/dist/storage-housekeeping.test.js +65 -0
  225. package/dist/storage-housekeeping.test.js.map +1 -0
  226. package/dist/test-daemon-harness.d.ts +31 -0
  227. package/dist/test-daemon-harness.d.ts.map +1 -0
  228. package/dist/test-daemon-harness.js +337 -0
  229. package/dist/test-daemon-harness.js.map +1 -0
  230. package/dist/token-auth.test.d.ts +2 -0
  231. package/dist/token-auth.test.d.ts.map +1 -0
  232. package/dist/token-auth.test.js +52 -0
  233. package/dist/token-auth.test.js.map +1 -0
  234. package/dist/utils.d.ts +4 -0
  235. package/dist/utils.d.ts.map +1 -0
  236. package/dist/utils.js +40 -0
  237. package/dist/utils.js.map +1 -0
  238. package/dist/utils.test.d.ts +2 -0
  239. package/dist/utils.test.d.ts.map +1 -0
  240. package/dist/utils.test.js +54 -0
  241. package/dist/utils.test.js.map +1 -0
  242. package/dist/ws-data.d.ts +4 -0
  243. package/dist/ws-data.d.ts.map +1 -0
  244. package/dist/ws-data.js +20 -0
  245. package/dist/ws-data.js.map +1 -0
  246. package/dist/ws-data.test.d.ts +2 -0
  247. package/dist/ws-data.test.d.ts.map +1 -0
  248. package/dist/ws-data.test.js +17 -0
  249. package/dist/ws-data.test.js.map +1 -0
  250. package/package.json +24 -27
  251. package/perf-reporter.mjs +138 -0
  252. package/scripts/build-release.mjs +41 -0
  253. package/scripts/dev.mjs +537 -0
  254. package/src/advertised-hosts.test.ts +125 -0
  255. package/src/advertised-hosts.ts +225 -0
  256. package/src/audit.test.ts +38 -0
  257. package/src/audit.ts +117 -0
  258. package/src/auth.ts +31 -0
  259. package/src/claude-hooks.ts +195 -0
  260. package/src/cli-version.test.ts +36 -0
  261. package/src/cli-version.ts +46 -0
  262. package/src/config.test.ts +254 -0
  263. package/src/config.ts +324 -0
  264. package/src/dev-auth.test.ts +183 -0
  265. package/src/dev-script.test.ts +511 -0
  266. package/src/drivers/claude.test.ts +1186 -0
  267. package/src/drivers/claude.ts +443 -0
  268. package/src/drivers/codex.test.ts +1096 -0
  269. package/src/drivers/codex.ts +879 -0
  270. package/src/drivers/types.ts +15 -0
  271. package/src/e2e.test.ts +139 -0
  272. package/src/identity.test.ts +26 -0
  273. package/src/identity.ts +82 -0
  274. package/src/index-entry.test.ts +336 -0
  275. package/src/index.ts +781 -0
  276. package/src/logger.ts +112 -0
  277. package/src/metrics.ts +117 -0
  278. package/src/pairing-store.test.ts +53 -0
  279. package/src/pairing-store.ts +154 -0
  280. package/src/paths.ts +19 -0
  281. package/src/perf-compare.ts +164 -0
  282. package/src/port-conflict.test.ts +45 -0
  283. package/src/port-conflict.ts +44 -0
  284. package/src/process-scanner.perf.test.ts +222 -0
  285. package/src/process-scanner.test.ts +575 -0
  286. package/src/process-scanner.ts +514 -0
  287. package/src/push-protocol.test.ts +74 -0
  288. package/src/push-protocol.ts +36 -0
  289. package/src/push-store.test.ts +89 -0
  290. package/src/push-store.ts +126 -0
  291. package/src/push.test.ts +234 -0
  292. package/src/push.ts +318 -0
  293. package/src/safe-stdio.ts +51 -0
  294. package/src/scanner.perf.test.ts +359 -0
  295. package/src/scanner.test.ts +1045 -0
  296. package/src/scanner.ts +924 -0
  297. package/src/session-inventory.perf.test.ts +250 -0
  298. package/src/session-inventory.test.ts +1002 -0
  299. package/src/session-inventory.ts +721 -0
  300. package/src/session-manager.test.ts +3430 -0
  301. package/src/session-manager.ts +1775 -0
  302. package/src/session-store.test.ts +276 -0
  303. package/src/session-store.ts +202 -0
  304. package/src/session-title.perf.test.ts +118 -0
  305. package/src/session-title.test.ts +286 -0
  306. package/src/session-title.ts +108 -0
  307. package/src/shutdown-endpoint.test.ts +95 -0
  308. package/src/storage-housekeeping.test.ts +78 -0
  309. package/src/storage-housekeeping.ts +111 -0
  310. package/src/test-daemon-harness.ts +410 -0
  311. package/src/token-auth.test.ts +67 -0
  312. package/src/utils.test.ts +65 -0
  313. package/src/utils.ts +47 -0
  314. package/src/ws-data.test.ts +20 -0
  315. package/src/ws-data.ts +26 -0
  316. package/tsconfig.json +12 -0
  317. package/README.md +0 -80
  318. package/bin/cloudflared-quick-tunnel.mjs +0 -11
  319. package/bin/cloudflared-resolver.mjs +0 -68
  320. package/bin/vibelet-runtime-policy.mjs +0 -36
  321. package/bin/vibelet.cjs +0 -12
  322. package/bin/vibelet.mjs +0 -1035
  323. package/dist/index.cjs +0 -125
@@ -0,0 +1,712 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { __resetExternalInventoryStateForTests, __waitForExternalInventoryRefreshForTests, buildSessionInventoryPage, getExternalInventoryHealth, listSessionPage, onExternalInventoryBackfill, } from './session-inventory.js';
4
+ import { metrics } from './metrics.js';
5
+ function createDeferred() {
6
+ let resolve;
7
+ let reject;
8
+ const promise = new Promise((res, rej) => {
9
+ resolve = res;
10
+ reject = rej;
11
+ });
12
+ return { promise, resolve, reject };
13
+ }
14
+ function idleSession(overrides) {
15
+ return {
16
+ sessionId: 'session-1',
17
+ agent: 'codex',
18
+ cwd: '/repo',
19
+ title: 'Session',
20
+ createdAt: '2026-03-01T00:00:00.000Z',
21
+ lastActivityAt: '2026-03-20T00:00:00.000Z',
22
+ sources: ['scanner'],
23
+ runtime: {
24
+ state: 'idle',
25
+ confidence: 'high',
26
+ resumeMode: 'resumeSession',
27
+ },
28
+ ...overrides,
29
+ };
30
+ }
31
+ function sources(overrides) {
32
+ return {
33
+ activeSessions: [],
34
+ sessionRecords: [],
35
+ scannedSessions: [],
36
+ runningSessions: [],
37
+ scannedAt: '2026-03-20T12:00:00.000Z',
38
+ deletedSessionIds: new Set(),
39
+ ...overrides,
40
+ };
41
+ }
42
+ test.beforeEach(() => {
43
+ __resetExternalInventoryStateForTests();
44
+ });
45
+ test('returns active sessions first and paginates only idle sessions', () => {
46
+ const page = buildSessionInventoryPage(sources({
47
+ activeSessions: [
48
+ {
49
+ sessionId: 'active-1',
50
+ agent: 'codex',
51
+ cwd: '/repo',
52
+ title: 'Active',
53
+ createdAt: '2026-03-20T09:00:00.000Z',
54
+ lastActivityAt: '2026-03-20T12:00:00.000Z',
55
+ },
56
+ ],
57
+ scannedSessions: [
58
+ idleSession({
59
+ sessionId: 'idle-new',
60
+ title: 'Idle New',
61
+ lastActivityAt: '2026-03-20T11:00:00.000Z',
62
+ }),
63
+ idleSession({
64
+ sessionId: 'idle-old',
65
+ title: 'Idle Old',
66
+ lastActivityAt: '2026-03-19T11:00:00.000Z',
67
+ }),
68
+ ],
69
+ }), 1);
70
+ assert.equal(page.sessions.length, 2);
71
+ assert.equal(page.sessions[0].sessionId, 'active-1');
72
+ assert.equal(page.sessions[0].runtime.state, 'daemonActive');
73
+ assert.equal(page.sessions[1].sessionId, 'idle-new');
74
+ assert.deepEqual(page.nextCursor, {
75
+ lastActivityAt: '2026-03-20T11:00:00.000Z',
76
+ sessionId: 'idle-new',
77
+ });
78
+ });
79
+ test('listSessionPage returns fast-path records before external backfill completes', async () => {
80
+ const scanned = createDeferred();
81
+ const running = createDeferred();
82
+ const page = await listSessionPage({
83
+ limit: 50,
84
+ activeSessions: [],
85
+ sessionRecords: [
86
+ {
87
+ sessionId: 'record-only',
88
+ agent: 'codex',
89
+ cwd: '/repo',
90
+ approvalMode: 'normal',
91
+ title: 'Record Only',
92
+ createdAt: '2026-03-01T00:00:00.000Z',
93
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
94
+ },
95
+ ],
96
+ loaders: {
97
+ listScannedSessions: async () => scanned.promise,
98
+ scanRunningSessions: async () => running.promise,
99
+ },
100
+ });
101
+ assert.equal(page.sessions.length, 1);
102
+ assert.equal(page.sessions[0].sessionId, 'record-only');
103
+ assert.equal(page.sessions[0].runtime.state, 'idle');
104
+ assert.deepEqual(page.sessions[0].sources, ['record']);
105
+ assert.equal(getExternalInventoryHealth().backfillInFlight, true);
106
+ scanned.resolve([
107
+ idleSession({
108
+ sessionId: 'external-idle',
109
+ title: 'External Idle',
110
+ lastActivityAt: '2026-03-20T11:00:00.000Z',
111
+ }),
112
+ ]);
113
+ running.resolve([]);
114
+ await __waitForExternalInventoryRefreshForTests();
115
+ const health = getExternalInventoryHealth();
116
+ const metricsSnapshot = metrics.snapshot();
117
+ assert.equal(health.backfillInFlight, false);
118
+ assert.equal(health.cachedSessions, 1);
119
+ assert.equal(health.runningSessions, 0);
120
+ assert.equal(typeof health.lastBackfillDurationMs, 'number');
121
+ assert.equal(typeof health.lastBackfillCompletedAt, 'string');
122
+ assert.equal(typeof health.lastBackfillAppliedAt, 'string');
123
+ assert.ok(metricsSnapshot.timers['session.inventory.fast_path_latency_ms']);
124
+ assert.ok(metricsSnapshot.timers['session.inventory.backfill_latency_ms']);
125
+ assert.ok(metricsSnapshot.counters['session.inventory.backfill_applied']);
126
+ const refreshed = await listSessionPage({
127
+ limit: 50,
128
+ activeSessions: [],
129
+ sessionRecords: [
130
+ {
131
+ sessionId: 'record-only',
132
+ agent: 'codex',
133
+ cwd: '/repo',
134
+ approvalMode: 'normal',
135
+ title: 'Record Only',
136
+ createdAt: '2026-03-01T00:00:00.000Z',
137
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
138
+ },
139
+ ],
140
+ });
141
+ assert.deepEqual(refreshed.sessions.map((session) => session.sessionId), ['external-idle', 'record-only']);
142
+ });
143
+ test('listSessionPage notifies listeners when external backfill changes inventory', async () => {
144
+ const scanned = createDeferred();
145
+ const running = createDeferred();
146
+ let notifications = 0;
147
+ const unsubscribe = onExternalInventoryBackfill(() => {
148
+ notifications += 1;
149
+ });
150
+ await listSessionPage({
151
+ limit: 50,
152
+ activeSessions: [],
153
+ sessionRecords: [],
154
+ loaders: {
155
+ listScannedSessions: async () => scanned.promise,
156
+ scanRunningSessions: async () => running.promise,
157
+ },
158
+ });
159
+ scanned.resolve([
160
+ idleSession({
161
+ sessionId: 'external-idle',
162
+ title: 'External Idle',
163
+ lastActivityAt: '2026-03-20T11:00:00.000Z',
164
+ }),
165
+ ]);
166
+ running.resolve([]);
167
+ await __waitForExternalInventoryRefreshForTests();
168
+ unsubscribe();
169
+ assert.equal(notifications, 1);
170
+ });
171
+ test('uses max lastActivityAt across sources and keeps later pages idle-only', () => {
172
+ const firstPage = buildSessionInventoryPage(sources({
173
+ sessionRecords: [
174
+ {
175
+ sessionId: 'shared',
176
+ agent: 'codex',
177
+ cwd: '/repo',
178
+ approvalMode: 'normal',
179
+ title: 'Shared Record',
180
+ createdAt: '2026-03-01T00:00:00.000Z',
181
+ lastActivityAt: '2026-03-02T00:00:00.000Z',
182
+ },
183
+ ],
184
+ scannedSessions: [
185
+ idleSession({
186
+ sessionId: 'shared',
187
+ title: 'Shared Scanner',
188
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
189
+ }),
190
+ idleSession({
191
+ sessionId: 'later-idle',
192
+ title: 'Later Idle',
193
+ lastActivityAt: '2026-03-19T10:00:00.000Z',
194
+ }),
195
+ ],
196
+ runningSessions: [
197
+ {
198
+ agent: 'claude',
199
+ pid: 9001,
200
+ cwd: '/repo',
201
+ command: 'claude --continue',
202
+ sessionId: 'claude-live',
203
+ confidence: 'medium',
204
+ isResponding: true,
205
+ },
206
+ ],
207
+ }), 1);
208
+ assert.equal(firstPage.sessions[0].sessionId, 'claude-live');
209
+ assert.equal(firstPage.sessions[0].runtime.state, 'externalRunning');
210
+ assert.equal(firstPage.sessions[0].runtime.isResponding, true);
211
+ assert.equal(firstPage.sessions[1].sessionId, 'shared');
212
+ assert.equal(firstPage.sessions[1].lastActivityAt, '2026-03-20T10:00:00.000Z');
213
+ assert.deepEqual(firstPage.sessions[1].sources.sort(), ['record', 'scanner']);
214
+ const secondPage = buildSessionInventoryPage(sources({
215
+ sessionRecords: [
216
+ {
217
+ sessionId: 'shared',
218
+ agent: 'codex',
219
+ cwd: '/repo',
220
+ approvalMode: 'normal',
221
+ title: 'Shared Record',
222
+ createdAt: '2026-03-01T00:00:00.000Z',
223
+ lastActivityAt: '2026-03-02T00:00:00.000Z',
224
+ },
225
+ ],
226
+ scannedSessions: [
227
+ idleSession({
228
+ sessionId: 'shared',
229
+ title: 'Shared Scanner',
230
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
231
+ }),
232
+ idleSession({
233
+ sessionId: 'later-idle',
234
+ title: 'Later Idle',
235
+ lastActivityAt: '2026-03-19T10:00:00.000Z',
236
+ }),
237
+ ],
238
+ runningSessions: [
239
+ {
240
+ agent: 'claude',
241
+ pid: 9001,
242
+ cwd: '/repo',
243
+ command: 'claude --continue',
244
+ sessionId: 'claude-live',
245
+ confidence: 'medium',
246
+ },
247
+ ],
248
+ }), 1, firstPage.nextCursor);
249
+ assert.equal(secondPage.sessions.length, 1);
250
+ assert.equal(secondPage.sessions[0].sessionId, 'later-idle');
251
+ assert.equal(secondPage.sessions[0].runtime.state, 'idle');
252
+ });
253
+ test('externalRunning sessions preserve parsed approval mode', () => {
254
+ const page = buildSessionInventoryPage(sources({
255
+ runningSessions: [
256
+ {
257
+ agent: 'claude',
258
+ pid: 9001,
259
+ cwd: '/repo',
260
+ command: 'claude --continue',
261
+ sessionId: 'claude-live',
262
+ confidence: 'medium',
263
+ approvalMode: 'acceptEdits',
264
+ },
265
+ ],
266
+ }), 50);
267
+ assert.equal(page.sessions[0].runtime.state, 'externalRunning');
268
+ assert.equal(page.sessions[0].approvalMode, 'acceptEdits');
269
+ });
270
+ test('listSessionPage keeps search requests on the blocking path', async () => {
271
+ const scanned = createDeferred();
272
+ const contentMatches = createDeferred();
273
+ let resolved = false;
274
+ const pending = listSessionPage({
275
+ limit: 50,
276
+ search: 'keyword',
277
+ activeSessions: [],
278
+ sessionRecords: [],
279
+ loaders: {
280
+ listScannedSessions: async () => scanned.promise,
281
+ scanRunningSessions: async () => [],
282
+ searchSessionContent: async () => contentMatches.promise,
283
+ },
284
+ }).then((page) => {
285
+ resolved = true;
286
+ return page;
287
+ });
288
+ await Promise.resolve();
289
+ assert.equal(resolved, false);
290
+ scanned.resolve([
291
+ idleSession({
292
+ sessionId: 'matched-search',
293
+ title: 'No metadata match here',
294
+ }),
295
+ ]);
296
+ contentMatches.resolve(new Set(['matched-search']));
297
+ const page = await pending;
298
+ assert.equal(resolved, true);
299
+ assert.equal(page.sessions.length, 1);
300
+ assert.equal(page.sessions[0].sessionId, 'matched-search');
301
+ });
302
+ // ===== Filtering =====
303
+ test('filters by agent type', () => {
304
+ const page = buildSessionInventoryPage(sources({
305
+ scannedSessions: [
306
+ idleSession({ sessionId: 'codex-1', agent: 'codex', title: 'Codex' }),
307
+ idleSession({ sessionId: 'claude-1', agent: 'claude', title: 'Claude' }),
308
+ ],
309
+ }), 10, undefined, 'claude');
310
+ assert.equal(page.sessions.length, 1);
311
+ assert.equal(page.sessions[0].agent, 'claude');
312
+ });
313
+ test('filters by cwd', () => {
314
+ const page = buildSessionInventoryPage(sources({
315
+ scannedSessions: [
316
+ idleSession({ sessionId: 's1', cwd: '/repo-a', title: 'A' }),
317
+ idleSession({ sessionId: 's2', cwd: '/repo-b', title: 'B' }),
318
+ ],
319
+ }), 10, undefined, undefined, '/repo-b');
320
+ assert.equal(page.sessions.length, 1);
321
+ assert.equal(page.sessions[0].cwd, '/repo-b');
322
+ });
323
+ test('filters by search term matching title', () => {
324
+ const page = buildSessionInventoryPage(sources({
325
+ scannedSessions: [
326
+ idleSession({ sessionId: 's1', title: 'Fix login bug' }),
327
+ idleSession({ sessionId: 's2', title: 'Add dashboard' }),
328
+ ],
329
+ }), 10, undefined, undefined, undefined, 'login');
330
+ assert.equal(page.sessions.length, 1);
331
+ assert.equal(page.sessions[0].sessionId, 's1');
332
+ });
333
+ test('filters by search term matching cwd', () => {
334
+ const page = buildSessionInventoryPage(sources({
335
+ scannedSessions: [
336
+ idleSession({ sessionId: 's1', cwd: '/home/user/frontend', title: 'A' }),
337
+ idleSession({ sessionId: 's2', cwd: '/home/user/backend', title: 'B' }),
338
+ ],
339
+ }), 10, undefined, undefined, undefined, 'frontend');
340
+ assert.equal(page.sessions.length, 1);
341
+ assert.equal(page.sessions[0].sessionId, 's1');
342
+ });
343
+ test('search with contentMatchIds includes sessions matching content', () => {
344
+ const page = buildSessionInventoryPage(sources({
345
+ scannedSessions: [
346
+ idleSession({ sessionId: 's1', title: 'No match' }),
347
+ idleSession({ sessionId: 's2', title: 'Also no match' }),
348
+ ],
349
+ }), 10, undefined, undefined, undefined, 'special-query', new Set(['s2']));
350
+ assert.equal(page.sessions.length, 1);
351
+ assert.equal(page.sessions[0].sessionId, 's2');
352
+ });
353
+ test('search disables pagination — returns all matching sessions', () => {
354
+ const sessions = Array.from({ length: 80 }, (_, i) => idleSession({
355
+ sessionId: `s${i}`,
356
+ title: `Session ${i} with keyword`,
357
+ lastActivityAt: `2026-03-${String(20 - Math.floor(i / 4)).padStart(2, '0')}T${String(i % 24).padStart(2, '0')}:00:00.000Z`,
358
+ }));
359
+ const page = buildSessionInventoryPage(sources({ scannedSessions: sessions }), 50, undefined, undefined, undefined, 'keyword');
360
+ assert.equal(page.sessions.length, 80);
361
+ assert.equal(page.nextCursor, undefined);
362
+ });
363
+ test('without search, pagination limits results to pageSize', () => {
364
+ const sessions = Array.from({ length: 80 }, (_, i) => idleSession({
365
+ sessionId: `s${i}`,
366
+ title: `Session ${i}`,
367
+ lastActivityAt: `2026-03-${String(20 - Math.floor(i / 4)).padStart(2, '0')}T${String(i % 24).padStart(2, '0')}:00:00.000Z`,
368
+ }));
369
+ const page = buildSessionInventoryPage(sources({ scannedSessions: sessions }), 50);
370
+ assert.equal(page.sessions.length, 50);
371
+ assert.ok(page.nextCursor);
372
+ });
373
+ test('search matches title OR content — union of both', () => {
374
+ const page = buildSessionInventoryPage(sources({
375
+ scannedSessions: [
376
+ idleSession({ sessionId: 'title-match', title: 'Fix login bug' }),
377
+ idleSession({ sessionId: 'content-match', title: 'Unrelated title' }),
378
+ idleSession({ sessionId: 'no-match', title: 'Something else' }),
379
+ ],
380
+ }), 50, undefined, undefined, undefined, 'login', new Set(['content-match']));
381
+ assert.equal(page.sessions.length, 2);
382
+ const ids = page.sessions.map(s => s.sessionId).sort();
383
+ assert.deepEqual(ids, ['content-match', 'title-match']);
384
+ });
385
+ test('search is case insensitive for title and cwd', () => {
386
+ const page = buildSessionInventoryPage(sources({
387
+ scannedSessions: [
388
+ idleSession({ sessionId: 's1', title: 'Fix Login Bug', cwd: '/home/User/Project' }),
389
+ idleSession({ sessionId: 's2', title: 'other', cwd: '/tmp' }),
390
+ ],
391
+ }), 50, undefined, undefined, undefined, 'LOGIN');
392
+ assert.equal(page.sessions.length, 1);
393
+ assert.equal(page.sessions[0].sessionId, 's1');
394
+ });
395
+ test('search matches Chinese characters in title', () => {
396
+ const page = buildSessionInventoryPage(sources({
397
+ scannedSessions: [
398
+ idleSession({ sessionId: 's1', title: '修复登录问题' }),
399
+ idleSession({ sessionId: 's2', title: '添加仪表盘功能' }),
400
+ ],
401
+ }), 50, undefined, undefined, undefined, '登录');
402
+ assert.equal(page.sessions.length, 1);
403
+ assert.equal(page.sessions[0].sessionId, 's1');
404
+ });
405
+ test('search with no matches returns empty page', () => {
406
+ const page = buildSessionInventoryPage(sources({
407
+ scannedSessions: [
408
+ idleSession({ sessionId: 's1', title: 'Fix bug' }),
409
+ idleSession({ sessionId: 's2', title: 'Add feature' }),
410
+ ],
411
+ }), 50, undefined, undefined, undefined, 'nonexistent-term', new Set());
412
+ assert.equal(page.sessions.length, 0);
413
+ assert.equal(page.nextCursor, undefined);
414
+ });
415
+ test('search with contentMatchIds includes sessions not matched by meta', () => {
416
+ const contentIds = new Set(['s1', 's3']);
417
+ const page = buildSessionInventoryPage(sources({
418
+ scannedSessions: [
419
+ idleSession({ sessionId: 's1', title: 'No meta match', cwd: '/a' }),
420
+ idleSession({ sessionId: 's2', title: 'No meta match', cwd: '/b' }),
421
+ idleSession({ sessionId: 's3', title: 'No meta match', cwd: '/c' }),
422
+ ],
423
+ }), 50, undefined, undefined, undefined, 'xyz', contentIds);
424
+ assert.equal(page.sessions.length, 2);
425
+ const ids = page.sessions.map(s => s.sessionId).sort();
426
+ assert.deepEqual(ids, ['s1', 's3']);
427
+ });
428
+ test('search combined with agent filter narrows results', () => {
429
+ const page = buildSessionInventoryPage(sources({
430
+ scannedSessions: [
431
+ idleSession({ sessionId: 's1', agent: 'claude', title: 'Fix login' }),
432
+ idleSession({ sessionId: 's2', agent: 'codex', title: 'Fix login' }),
433
+ ],
434
+ }), 50, undefined, 'claude', undefined, 'login');
435
+ assert.equal(page.sessions.length, 1);
436
+ assert.equal(page.sessions[0].agent, 'claude');
437
+ });
438
+ // ===== Additional coverage tests =====
439
+ test('active sessions are sorted by runtime state rank then by lastActivityAt', () => {
440
+ const page = buildSessionInventoryPage(sources({
441
+ runningSessions: [
442
+ {
443
+ agent: 'codex', pid: 1, cwd: '/repo', command: 'codex',
444
+ sessionId: 'running-1', confidence: 'medium',
445
+ },
446
+ ],
447
+ activeSessions: [
448
+ {
449
+ sessionId: 'daemon-1', agent: 'codex', cwd: '/repo', title: 'Daemon',
450
+ createdAt: '2026-03-20T09:00:00.000Z', lastActivityAt: '2026-03-20T10:00:00.000Z',
451
+ },
452
+ ],
453
+ }), 10);
454
+ // daemonActive (rank 3) should come before externalRunning (rank 2)
455
+ assert.equal(page.sessions[0].runtime.state, 'daemonActive');
456
+ assert.equal(page.sessions[1].runtime.state, 'externalRunning');
457
+ });
458
+ test('cursor that does not match any session resets to beginning', () => {
459
+ const page = buildSessionInventoryPage(sources({
460
+ scannedSessions: [
461
+ idleSession({ sessionId: 's1', lastActivityAt: '2026-03-20T10:00:00.000Z' }),
462
+ idleSession({ sessionId: 's2', lastActivityAt: '2026-03-19T10:00:00.000Z' }),
463
+ ],
464
+ }), 10, { lastActivityAt: 'nonexistent', sessionId: 'nonexistent' });
465
+ // Should fall back to beginning
466
+ assert.equal(page.sessions.length, 2);
467
+ assert.equal(page.sessions[0].sessionId, 's1');
468
+ });
469
+ test('idle sessions are sorted by lastActivityAt descending', () => {
470
+ const page = buildSessionInventoryPage(sources({
471
+ scannedSessions: [
472
+ idleSession({ sessionId: 'old', lastActivityAt: '2026-03-01T00:00:00.000Z' }),
473
+ idleSession({ sessionId: 'new', lastActivityAt: '2026-03-20T00:00:00.000Z' }),
474
+ idleSession({ sessionId: 'mid', lastActivityAt: '2026-03-10T00:00:00.000Z' }),
475
+ ],
476
+ }), 10);
477
+ assert.equal(page.sessions[0].sessionId, 'new');
478
+ assert.equal(page.sessions[1].sessionId, 'mid');
479
+ assert.equal(page.sessions[2].sessionId, 'old');
480
+ });
481
+ test('runtime state: externalRunning beats idle in sort order', () => {
482
+ const page = buildSessionInventoryPage(sources({
483
+ scannedSessions: [
484
+ idleSession({ sessionId: 'idle-1', lastActivityAt: '2026-03-20T12:00:00.000Z' }),
485
+ ],
486
+ runningSessions: [
487
+ {
488
+ agent: 'codex', pid: 1, cwd: '/repo', command: 'codex',
489
+ sessionId: 'running-1', confidence: 'low',
490
+ },
491
+ ],
492
+ }), 10);
493
+ assert.equal(page.sessions[0].sessionId, 'running-1');
494
+ assert.equal(page.sessions[0].runtime.state, 'externalRunning');
495
+ assert.equal(page.sessions[1].sessionId, 'idle-1');
496
+ });
497
+ test('deleted sessions are filtered even when scanner finds them again', () => {
498
+ const page = buildSessionInventoryPage(sources({
499
+ sessionRecords: [
500
+ {
501
+ sessionId: 'deleted-1',
502
+ agent: 'codex',
503
+ cwd: '/repo',
504
+ approvalMode: 'normal',
505
+ title: 'Deleted record',
506
+ createdAt: '2026-03-01T00:00:00.000Z',
507
+ lastActivityAt: '2026-03-02T00:00:00.000Z',
508
+ managed: true,
509
+ },
510
+ ],
511
+ scannedSessions: [
512
+ idleSession({
513
+ sessionId: 'deleted-1',
514
+ title: 'Deleted scanner',
515
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
516
+ }),
517
+ idleSession({
518
+ sessionId: 'kept-1',
519
+ title: 'Kept session',
520
+ lastActivityAt: '2026-03-19T10:00:00.000Z',
521
+ }),
522
+ ],
523
+ deletedSessionIds: new Set(['deleted-1']),
524
+ }), 10);
525
+ assert.equal(page.sessions.length, 1);
526
+ assert.equal(page.sessions[0].sessionId, 'kept-1');
527
+ });
528
+ test('pickRuntime chooses higher confidence when same state', () => {
529
+ const page = buildSessionInventoryPage(sources({
530
+ runningSessions: [
531
+ {
532
+ agent: 'codex', pid: 1, cwd: '/repo', command: 'codex',
533
+ sessionId: 'shared', confidence: 'low',
534
+ },
535
+ {
536
+ agent: 'codex', pid: 2, cwd: '/repo', command: 'codex resume shared',
537
+ sessionId: 'shared', confidence: 'high',
538
+ },
539
+ ],
540
+ }), 10);
541
+ assert.equal(page.sessions.length, 1);
542
+ assert.equal(page.sessions[0].runtime.confidence, 'high');
543
+ });
544
+ // ===== Merge edge cases =====
545
+ test('merges same session from record and scanner, picks higher runtime state', () => {
546
+ const page = buildSessionInventoryPage(sources({
547
+ sessionRecords: [
548
+ {
549
+ sessionId: 'shared',
550
+ agent: 'codex',
551
+ cwd: '/repo',
552
+ approvalMode: 'normal',
553
+ title: 'Record title',
554
+ createdAt: '2026-03-01T00:00:00.000Z',
555
+ lastActivityAt: '2026-03-02T00:00:00.000Z',
556
+ },
557
+ ],
558
+ activeSessions: [
559
+ {
560
+ sessionId: 'shared',
561
+ agent: 'codex',
562
+ cwd: '/repo',
563
+ title: 'Active title',
564
+ createdAt: '2026-03-01T00:00:00.000Z',
565
+ lastActivityAt: '2026-03-20T12:00:00.000Z',
566
+ },
567
+ ],
568
+ }), 10);
569
+ assert.equal(page.sessions.length, 1);
570
+ assert.equal(page.sessions[0].runtime.state, 'daemonActive');
571
+ assert.equal(page.sessions[0].title, 'Active title');
572
+ });
573
+ test('returns empty page when no sessions match filters', () => {
574
+ const page = buildSessionInventoryPage(sources({
575
+ scannedSessions: [
576
+ idleSession({ sessionId: 's1', agent: 'codex' }),
577
+ ],
578
+ }), 10, undefined, 'claude');
579
+ assert.equal(page.sessions.length, 0);
580
+ assert.equal(page.nextCursor, undefined);
581
+ });
582
+ test('uses min createdAt across merged sources', () => {
583
+ const page = buildSessionInventoryPage(sources({
584
+ sessionRecords: [
585
+ {
586
+ sessionId: 'shared',
587
+ agent: 'codex',
588
+ cwd: '/repo',
589
+ title: 'Title',
590
+ createdAt: '2026-03-01T00:00:00.000Z',
591
+ lastActivityAt: '2026-03-10T00:00:00.000Z',
592
+ },
593
+ ],
594
+ scannedSessions: [
595
+ idleSession({
596
+ sessionId: 'shared',
597
+ createdAt: '2026-03-05T00:00:00.000Z',
598
+ lastActivityAt: '2026-03-20T00:00:00.000Z',
599
+ }),
600
+ ],
601
+ }), 10);
602
+ assert.equal(page.sessions[0].createdAt, '2026-03-01T00:00:00.000Z');
603
+ assert.equal(page.sessions[0].lastActivityAt, '2026-03-20T00:00:00.000Z');
604
+ });
605
+ // ===== Title merging =====
606
+ test('mergeInto does not let process scanner override daemonActive lastActivityAt', () => {
607
+ const fiveMinsAgo = '2026-03-20T11:55:00.000Z';
608
+ const scannedAt = '2026-03-20T12:00:00.000Z'; // now
609
+ const page = buildSessionInventoryPage(sources({
610
+ activeSessions: [
611
+ {
612
+ sessionId: 'shared-session',
613
+ agent: 'codex',
614
+ cwd: '/repo',
615
+ title: 'Daemon session',
616
+ createdAt: '2026-03-01T00:00:00.000Z',
617
+ lastActivityAt: fiveMinsAgo,
618
+ },
619
+ ],
620
+ runningSessions: [
621
+ {
622
+ agent: 'codex',
623
+ pid: 42,
624
+ cwd: '/repo',
625
+ command: 'codex --continue',
626
+ sessionId: 'shared-session',
627
+ confidence: 'medium',
628
+ },
629
+ ],
630
+ scannedAt,
631
+ }), 10);
632
+ assert.equal(page.sessions.length, 1);
633
+ assert.equal(page.sessions[0].sessionId, 'shared-session');
634
+ // The daemon's lastActivityAt (5 mins ago) should be preserved,
635
+ // NOT overwritten by the process scanner's scannedAt (now).
636
+ assert.equal(page.sessions[0].lastActivityAt, fiveMinsAgo, 'lastActivityAt should be the daemon timestamp, not the scanner scannedAt');
637
+ });
638
+ test('managed idle sessions are sorted with active sessions, not idle', () => {
639
+ const page = buildSessionInventoryPage(sources({
640
+ sessionRecords: [
641
+ {
642
+ sessionId: 'managed-idle',
643
+ agent: 'claude',
644
+ cwd: '/repo',
645
+ title: 'Managed Session',
646
+ createdAt: '2026-03-01T00:00:00.000Z',
647
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
648
+ managed: true,
649
+ },
650
+ ],
651
+ scannedSessions: [
652
+ idleSession({
653
+ sessionId: 'scanned-idle',
654
+ title: 'Scanned Session',
655
+ lastActivityAt: '2026-03-20T11:00:00.000Z',
656
+ }),
657
+ ],
658
+ }), 1);
659
+ // managed idle should appear first (in active section), scanned idle in idle section
660
+ assert.equal(page.sessions[0].sessionId, 'managed-idle');
661
+ assert.equal(page.sessions[0].managed, true);
662
+ assert.equal(page.sessions[1].sessionId, 'scanned-idle');
663
+ });
664
+ test('managed flag is preserved when merging record and scanner sources', () => {
665
+ const page = buildSessionInventoryPage(sources({
666
+ sessionRecords: [
667
+ {
668
+ sessionId: 'shared',
669
+ agent: 'codex',
670
+ cwd: '/repo',
671
+ title: 'Record',
672
+ createdAt: '2026-03-01T00:00:00.000Z',
673
+ lastActivityAt: '2026-03-02T00:00:00.000Z',
674
+ managed: true,
675
+ },
676
+ ],
677
+ scannedSessions: [
678
+ idleSession({
679
+ sessionId: 'shared',
680
+ title: 'Scanned',
681
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
682
+ }),
683
+ ],
684
+ }), 10);
685
+ assert.equal(page.sessions.length, 1);
686
+ assert.equal(page.sessions[0].managed, true);
687
+ });
688
+ test('replaces id-like fallback titles with a real scanned title', () => {
689
+ const sessionId = '019d03e3-2672-7011-9c6d-5ba083b71111';
690
+ const page = buildSessionInventoryPage(sources({
691
+ sessionRecords: [
692
+ {
693
+ sessionId,
694
+ agent: 'codex',
695
+ cwd: '/repo',
696
+ approvalMode: 'normal',
697
+ title: sessionId,
698
+ createdAt: '2026-03-01T00:00:00.000Z',
699
+ lastActivityAt: '2026-03-02T00:00:00.000Z',
700
+ },
701
+ ],
702
+ scannedSessions: [
703
+ idleSession({
704
+ sessionId,
705
+ title: 'Recovered title',
706
+ lastActivityAt: '2026-03-20T10:00:00.000Z',
707
+ }),
708
+ ],
709
+ }), 10);
710
+ assert.equal(page.sessions[0].title, 'Recovered title');
711
+ });
712
+ //# sourceMappingURL=session-inventory.test.js.map