@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,276 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtemp, rm } from 'fs/promises';
4
- import { readFileSync, mkdirSync, writeFileSync } from 'fs';
5
- import { join, dirname } from 'path';
6
- import { tmpdir } from 'os';
7
- import type { SessionRecord } from './session-store.js';
8
-
9
- // We need to override SESSION_STORE_PATH before importing SessionStore.
10
- // Use a dynamic approach: create the store, then replace its internals.
11
-
12
- async function withTempStore(fn: (storePath: string) => Promise<void>): Promise<void> {
13
- const dir = await mkdtemp(join(tmpdir(), 'ss-test-'));
14
- const storePath = join(dir, 'sessions.json');
15
- try {
16
- await fn(storePath);
17
- } finally {
18
- await rm(dir, { recursive: true, force: true });
19
- }
20
- }
21
-
22
- function makeRecord(overrides: Partial<SessionRecord> & { sessionId: string }): SessionRecord {
23
- return {
24
- agent: 'claude',
25
- cwd: '/test',
26
- title: 'Test',
27
- createdAt: '2026-01-01T00:00:00.000Z',
28
- lastActivityAt: '2026-01-01T00:00:00.000Z',
29
- ...overrides,
30
- };
31
- }
32
-
33
- // Import SessionStore and override its disk path for testing
34
- async function createTestStore(storePath: string, initialRecords?: SessionRecord[]) {
35
- if (initialRecords) {
36
- mkdirSync(dirname(storePath), { recursive: true });
37
- writeFileSync(storePath, JSON.stringify({
38
- records: initialRecords,
39
- deletedSessionIds: [],
40
- }));
41
- }
42
- const { SessionStore } = await import('./session-store.js');
43
- const store = new SessionStore();
44
- // Override path and records for isolated testing
45
- (store as any).records = initialRecords ?? [];
46
- (store as any).deletedSessionIds = new Set<string>();
47
- // Make save synchronous and write to our temp path
48
- (store as any).saveToDisk = () => {
49
- mkdirSync(dirname(storePath), { recursive: true });
50
- writeFileSync(storePath, JSON.stringify({
51
- records: (store as any).records,
52
- deletedSessionIds: [...(store as any).deletedSessionIds],
53
- }, null, 2));
54
- };
55
- (store as any).scheduleSave = () => (store as any).saveToDisk();
56
- return store;
57
- }
58
-
59
- test('getAll: returns empty array initially', async () => {
60
- await withTempStore(async (storePath) => {
61
- const store = await createTestStore(storePath);
62
- assert.deepEqual(store.getAll(), []);
63
- });
64
- });
65
-
66
- test('upsert: inserts new record at beginning', async () => {
67
- await withTempStore(async (storePath) => {
68
- const store = await createTestStore(storePath);
69
- const r1 = makeRecord({ sessionId: 's1' });
70
- const r2 = makeRecord({ sessionId: 's2' });
71
-
72
- store.upsert(r1);
73
- store.upsert(r2);
74
-
75
- const all = store.getAll();
76
- assert.equal(all.length, 2);
77
- assert.equal(all[0].sessionId, 's2');
78
- assert.equal(all[1].sessionId, 's1');
79
- });
80
- });
81
-
82
- test('upsert: updates existing record in place', async () => {
83
- await withTempStore(async (storePath) => {
84
- const store = await createTestStore(storePath);
85
- store.upsert(makeRecord({ sessionId: 's1', title: 'Original' }));
86
- store.upsert(makeRecord({ sessionId: 's1', title: 'Updated' }));
87
-
88
- const all = store.getAll();
89
- assert.equal(all.length, 1);
90
- assert.equal(all[0].title, 'Updated');
91
- });
92
- });
93
-
94
- test('find: returns matching record', async () => {
95
- await withTempStore(async (storePath) => {
96
- const store = await createTestStore(storePath);
97
- store.upsert(makeRecord({ sessionId: 's1', title: 'Found' }));
98
- store.upsert(makeRecord({ sessionId: 's2', title: 'Other' }));
99
-
100
- const found = store.find('s1');
101
- assert.ok(found);
102
- assert.equal(found.title, 'Found');
103
- });
104
- });
105
-
106
- test('find: returns undefined for missing record', async () => {
107
- await withTempStore(async (storePath) => {
108
- const store = await createTestStore(storePath);
109
- assert.equal(store.find('nonexistent'), undefined);
110
- });
111
- });
112
-
113
- test('remove: removes matching record', async () => {
114
- await withTempStore(async (storePath) => {
115
- const store = await createTestStore(storePath);
116
- store.upsert(makeRecord({ sessionId: 's1' }));
117
- store.upsert(makeRecord({ sessionId: 's2' }));
118
-
119
- store.remove('s1');
120
-
121
- assert.equal(store.getAll().length, 1);
122
- assert.equal(store.find('s1'), undefined);
123
- assert.equal(store.isDeleted('s1'), true);
124
- assert.ok(store.find('s2'));
125
- });
126
- });
127
-
128
- test('remove: no-op for missing record', async () => {
129
- await withTempStore(async (storePath) => {
130
- const store = await createTestStore(storePath);
131
- store.upsert(makeRecord({ sessionId: 's1' }));
132
-
133
- store.remove('nonexistent');
134
-
135
- assert.equal(store.getAll().length, 1);
136
- });
137
- });
138
-
139
- test('flushSync: persists to disk', async () => {
140
- await withTempStore(async (storePath) => {
141
- const store = await createTestStore(storePath);
142
- store.upsert(makeRecord({ sessionId: 's1', title: 'Persisted' }));
143
- store.flushSync();
144
-
145
- const data = JSON.parse(readFileSync(storePath, 'utf-8'));
146
- assert.equal(data.records.length, 1);
147
- assert.equal(data.records[0].sessionId, 's1');
148
- assert.equal(data.records[0].title, 'Persisted');
149
- assert.deepEqual(data.deletedSessionIds, []);
150
- });
151
- });
152
-
153
- test('upsert: triggers save to disk', async () => {
154
- await withTempStore(async (storePath) => {
155
- const store = await createTestStore(storePath);
156
- store.upsert(makeRecord({ sessionId: 's1' }));
157
-
158
- // scheduleSave is synchronous in our test setup
159
- const data = JSON.parse(readFileSync(storePath, 'utf-8'));
160
- assert.equal(data.records.length, 1);
161
- assert.deepEqual(data.deletedSessionIds, []);
162
- });
163
- });
164
-
165
- test('remove: triggers save to disk', async () => {
166
- await withTempStore(async (storePath) => {
167
- const store = await createTestStore(storePath);
168
- store.upsert(makeRecord({ sessionId: 's1' }));
169
- store.upsert(makeRecord({ sessionId: 's2' }));
170
- store.remove('s1');
171
-
172
- const data = JSON.parse(readFileSync(storePath, 'utf-8'));
173
- assert.equal(data.records.length, 1);
174
- assert.equal(data.records[0].sessionId, 's2');
175
- assert.deepEqual(data.deletedSessionIds, ['s1']);
176
- });
177
- });
178
-
179
- test('upsert: clears tombstone for a recreated session', async () => {
180
- await withTempStore(async (storePath) => {
181
- const store = await createTestStore(storePath);
182
- store.upsert(makeRecord({ sessionId: 's1' }));
183
- store.remove('s1');
184
-
185
- assert.equal(store.isDeleted('s1'), true);
186
-
187
- store.upsert(makeRecord({ sessionId: 's1', title: 'Recreated' }));
188
-
189
- assert.equal(store.isDeleted('s1'), false);
190
- assert.equal(store.find('s1')?.title, 'Recreated');
191
- });
192
- });
193
-
194
- test('loadFromDisk: preserves accepted client message ids and trims oversized arrays', async () => {
195
- await withTempStore(async (storePath) => {
196
- const initial = makeRecord({
197
- sessionId: 's1',
198
- acceptedClientMessageIds: Array.from({ length: 205 }, (_, index) => `cm_${index}`),
199
- });
200
- const { normalizeSessionRecord } = await import('./session-store.js');
201
- const record = normalizeSessionRecord(initial);
202
-
203
- assert.ok(record);
204
- assert.equal(record.acceptedClientMessageIds?.length, 200);
205
- assert.equal(record.acceptedClientMessageIds?.[0], 'cm_5');
206
- assert.equal(record.acceptedClientMessageIds?.[199], 'cm_204');
207
- });
208
- });
209
-
210
- test('loadFromDisk: backfills missing accepted client message ids for legacy records', async () => {
211
- await withTempStore(async (storePath) => {
212
- const legacyRecord = makeRecord({ sessionId: 'legacy' });
213
- delete (legacyRecord as SessionRecord & { acceptedClientMessageIds?: string[] }).acceptedClientMessageIds;
214
- const { normalizeSessionRecord } = await import('./session-store.js');
215
- const record = normalizeSessionRecord(legacyRecord);
216
-
217
- assert.ok(record);
218
- assert.deepEqual(record.acceptedClientMessageIds, []);
219
- });
220
- });
221
-
222
- test('normalizeSessionRecord preserves a valid pending approval payload', async () => {
223
- await withTempStore(async () => {
224
- const { normalizeSessionRecord } = await import('./session-store.js');
225
- const record = normalizeSessionRecord(makeRecord({
226
- sessionId: 'pending-approval',
227
- pendingApproval: {
228
- requestId: 'req-1',
229
- toolName: 'Bash',
230
- input: { command: 'pwd' },
231
- description: 'Run pwd',
232
- approvalContext: {
233
- provider: 'codex',
234
- kind: 'command-execution',
235
- rpcId: 42,
236
- },
237
- },
238
- }));
239
-
240
- assert.deepEqual(record.pendingApproval, {
241
- requestId: 'req-1',
242
- toolName: 'Bash',
243
- input: { command: 'pwd' },
244
- description: 'Run pwd',
245
- approvalContext: {
246
- provider: 'codex',
247
- kind: 'command-execution',
248
- rpcId: 42,
249
- },
250
- });
251
- });
252
- });
253
-
254
- test('normalizeSessionStoreDiskState: supports legacy array-only store files', async () => {
255
- const { normalizeSessionStoreDiskState } = await import('./session-store.js');
256
- const legacyRecord = makeRecord({ sessionId: 'legacy-array' });
257
- delete (legacyRecord as SessionRecord & { acceptedClientMessageIds?: string[] }).acceptedClientMessageIds;
258
-
259
- const state = normalizeSessionStoreDiskState([legacyRecord]);
260
-
261
- assert.equal(state.records.length, 1);
262
- assert.equal(state.records[0].sessionId, 'legacy-array');
263
- assert.deepEqual(state.records[0].acceptedClientMessageIds, []);
264
- assert.deepEqual(state.deletedSessionIds, []);
265
- });
266
-
267
- test('normalizeSessionStoreDiskState: sanitizes deletedSessionIds entries', async () => {
268
- const { normalizeSessionStoreDiskState } = await import('./session-store.js');
269
-
270
- const state = normalizeSessionStoreDiskState({
271
- records: [makeRecord({ sessionId: 'kept' })],
272
- deletedSessionIds: ['dup', '', 'dup', null, 123, 'ok'],
273
- });
274
-
275
- assert.deepEqual(state.deletedSessionIds, ['dup', 'ok']);
276
- });
@@ -1,202 +0,0 @@
1
- import { readFileSync, writeFileSync, mkdirSync } from 'fs';
2
- import { dirname } from 'path';
3
- import type { AgentType, ApprovalMode, ApprovalRequestPayload } from '@vibelet/shared';
4
- import { SESSION_STORE_PATH } from './paths.js';
5
-
6
- export interface SessionRecord {
7
- sessionId: string;
8
- agent: AgentType;
9
- cwd: string;
10
- approvalMode?: ApprovalMode;
11
- acceptedClientMessageIds?: string[];
12
- pendingApproval?: ApprovalRequestPayload;
13
- title: string;
14
- createdAt: string;
15
- lastActivityAt: string;
16
- /** True for sessions created from the Vibelet app. */
17
- managed?: boolean;
18
- /** True if the session was responding when last persisted. */
19
- isResponding?: boolean;
20
- }
21
-
22
- interface SessionStoreDiskState {
23
- records: SessionRecord[];
24
- deletedSessionIds: string[];
25
- }
26
-
27
- const DEBOUNCE_MS = 500;
28
- const MAX_ACCEPTED_CLIENT_MESSAGE_IDS = 200;
29
-
30
- function sanitizeAcceptedClientMessageIds(ids: unknown): string[] {
31
- if (!Array.isArray(ids)) return [];
32
- const sanitized = ids.filter((value): value is string => typeof value === 'string' && value.length > 0);
33
- if (sanitized.length <= MAX_ACCEPTED_CLIENT_MESSAGE_IDS) {
34
- return sanitized;
35
- }
36
- return sanitized.slice(-MAX_ACCEPTED_CLIENT_MESSAGE_IDS);
37
- }
38
-
39
- function sanitizeDeletedSessionIds(ids: unknown): string[] {
40
- if (!Array.isArray(ids)) return [];
41
- const result: string[] = [];
42
- for (const value of ids) {
43
- if (typeof value !== 'string' || value.length === 0 || result.includes(value)) continue;
44
- result.push(value);
45
- }
46
- return result;
47
- }
48
-
49
- function sanitizeApprovalRequest(value: unknown): ApprovalRequestPayload | undefined {
50
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
51
- return undefined;
52
- }
53
-
54
- const candidate = value as Partial<ApprovalRequestPayload>;
55
- const requestId = typeof candidate.requestId === 'string' ? candidate.requestId : '';
56
- const toolName = typeof candidate.toolName === 'string' ? candidate.toolName : '';
57
- const description = typeof candidate.description === 'string' ? candidate.description : '';
58
- const input = candidate.input && typeof candidate.input === 'object' && !Array.isArray(candidate.input)
59
- ? candidate.input as Record<string, unknown>
60
- : {};
61
-
62
- if (!requestId || !toolName || !description) {
63
- return undefined;
64
- }
65
-
66
- const approvalContext = candidate.approvalContext;
67
- if (
68
- approvalContext
69
- && typeof approvalContext === 'object'
70
- && !Array.isArray(approvalContext)
71
- && approvalContext.provider === 'codex'
72
- && typeof approvalContext.kind === 'string'
73
- && typeof approvalContext.rpcId === 'number'
74
- ) {
75
- return {
76
- requestId,
77
- toolName,
78
- input,
79
- description,
80
- approvalContext: {
81
- provider: 'codex',
82
- kind: approvalContext.kind,
83
- rpcId: approvalContext.rpcId,
84
- ...(typeof approvalContext.questionId === 'string' ? { questionId: approvalContext.questionId } : {}),
85
- ...(typeof approvalContext.approveLabel === 'string' ? { approveLabel: approvalContext.approveLabel } : {}),
86
- ...(typeof approvalContext.denyLabel === 'string' ? { denyLabel: approvalContext.denyLabel } : {}),
87
- },
88
- };
89
- }
90
-
91
- return {
92
- requestId,
93
- toolName,
94
- input,
95
- description,
96
- };
97
- }
98
-
99
- export function normalizeSessionRecord(record: SessionRecord & { lastActivityAt?: string }): SessionRecord {
100
- return {
101
- ...record,
102
- lastActivityAt: record.lastActivityAt ?? record.createdAt,
103
- acceptedClientMessageIds: sanitizeAcceptedClientMessageIds(record.acceptedClientMessageIds),
104
- pendingApproval: sanitizeApprovalRequest(record.pendingApproval),
105
- };
106
- }
107
-
108
- export function normalizeSessionStoreDiskState(parsed: unknown): SessionStoreDiskState {
109
- if (Array.isArray(parsed)) {
110
- return {
111
- records: parsed.map(normalizeSessionRecord),
112
- deletedSessionIds: [],
113
- };
114
- }
115
- if (parsed && typeof parsed === 'object') {
116
- const state = parsed as Partial<SessionStoreDiskState>;
117
- return {
118
- records: Array.isArray(state.records) ? state.records.map(normalizeSessionRecord) : [],
119
- deletedSessionIds: sanitizeDeletedSessionIds(state.deletedSessionIds),
120
- };
121
- }
122
- return { records: [], deletedSessionIds: [] };
123
- }
124
-
125
- export class SessionStore {
126
- private records: SessionRecord[];
127
- private deletedSessionIds: Set<string>;
128
- private debounceTimer: ReturnType<typeof setTimeout> | null = null;
129
-
130
- constructor() {
131
- const state = this.loadFromDisk();
132
- this.records = state.records;
133
- this.deletedSessionIds = new Set(state.deletedSessionIds);
134
- }
135
-
136
- getAll(): SessionRecord[] {
137
- return this.records;
138
- }
139
-
140
- getDeletedSessionIds(): string[] {
141
- return [...this.deletedSessionIds];
142
- }
143
-
144
- find(sessionId: string): SessionRecord | undefined {
145
- return this.records.find(r => r.sessionId === sessionId);
146
- }
147
-
148
- isDeleted(sessionId: string): boolean {
149
- return this.deletedSessionIds.has(sessionId);
150
- }
151
-
152
- upsert(record: SessionRecord): void {
153
- this.deletedSessionIds.delete(record.sessionId);
154
- const index = this.records.findIndex(r => r.sessionId === record.sessionId);
155
- if (index >= 0) {
156
- this.records[index] = record;
157
- } else {
158
- this.records.unshift(record);
159
- }
160
- this.scheduleSave();
161
- }
162
-
163
- remove(sessionId: string): void {
164
- this.records = this.records.filter(r => r.sessionId !== sessionId);
165
- this.deletedSessionIds.add(sessionId);
166
- this.scheduleSave();
167
- }
168
-
169
- flushSync(): void {
170
- if (this.debounceTimer) {
171
- clearTimeout(this.debounceTimer);
172
- this.debounceTimer = null;
173
- }
174
- this.saveToDisk();
175
- }
176
-
177
- private scheduleSave(): void {
178
- if (this.debounceTimer) clearTimeout(this.debounceTimer);
179
- this.debounceTimer = setTimeout(() => {
180
- this.debounceTimer = null;
181
- this.saveToDisk();
182
- }, DEBOUNCE_MS);
183
- }
184
-
185
- private loadFromDisk(): SessionStoreDiskState {
186
- try {
187
- const data = readFileSync(SESSION_STORE_PATH, 'utf-8');
188
- return normalizeSessionStoreDiskState(JSON.parse(data) as unknown);
189
- } catch {
190
- // Ignore invalid or missing store files and start from an empty state.
191
- }
192
- return { records: [], deletedSessionIds: [] };
193
- }
194
-
195
- private saveToDisk(): void {
196
- mkdirSync(dirname(SESSION_STORE_PATH), { recursive: true });
197
- writeFileSync(SESSION_STORE_PATH, JSON.stringify({
198
- records: this.records,
199
- deletedSessionIds: [...this.deletedSessionIds],
200
- }, null, 2));
201
- }
202
- }
@@ -1,118 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- sanitizeSessionTitle,
5
- isIdLikeSessionTitle,
6
- isFallbackSessionTitle,
7
- titleFromFirstSentence,
8
- preferSessionTitle,
9
- } from './session-title.js';
10
-
11
- function measure(fn: () => void, iterations: number): { totalMs: number; avgMs: number } {
12
- const start = performance.now();
13
- for (let i = 0; i < iterations; i++) fn();
14
- const totalMs = performance.now() - start;
15
- return { totalMs, avgMs: totalMs / iterations };
16
- }
17
-
18
- const ITERATIONS = 10_000;
19
-
20
- describe('session-title performance', () => {
21
- const longText = 'a'.repeat(500);
22
- const multiLine = Array.from({ length: 50 }, (_, i) => `Line ${i}: some content here`).join('\n');
23
- const uuid = '019d03e3-2672-7011-9c6d-5ba083b71111';
24
- const hexId = 'abcdef1234567890abcdef';
25
-
26
- it(`sanitizeSessionTitle × ${ITERATIONS} short strings < 50ms`, () => {
27
- const { totalMs } = measure(() => sanitizeSessionTitle(' hello world '), ITERATIONS);
28
- console.log(` sanitizeSessionTitle (short): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
29
- assert.ok(totalMs < 50, `took ${totalMs.toFixed(2)}ms, expected < 50ms`);
30
- });
31
-
32
- it(`sanitizeSessionTitle × ${ITERATIONS} long strings < 100ms`, () => {
33
- const { totalMs } = measure(() => sanitizeSessionTitle(longText), ITERATIONS);
34
- console.log(` sanitizeSessionTitle (long): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
35
- assert.ok(totalMs < 100, `took ${totalMs.toFixed(2)}ms, expected < 100ms`);
36
- });
37
-
38
- it(`isIdLikeSessionTitle × ${ITERATIONS} UUID checks < 50ms`, () => {
39
- const { totalMs } = measure(() => isIdLikeSessionTitle(uuid), ITERATIONS);
40
- console.log(` isIdLikeSessionTitle (uuid): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
41
- assert.ok(totalMs < 50, `took ${totalMs.toFixed(2)}ms, expected < 50ms`);
42
- });
43
-
44
- it(`isIdLikeSessionTitle × ${ITERATIONS} hex checks < 50ms`, () => {
45
- const { totalMs } = measure(() => isIdLikeSessionTitle(hexId), ITERATIONS);
46
- console.log(` isIdLikeSessionTitle (hex): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
47
- assert.ok(totalMs < 50, `took ${totalMs.toFixed(2)}ms, expected < 50ms`);
48
- });
49
-
50
- it(`isIdLikeSessionTitle × ${ITERATIONS} normal title < 50ms`, () => {
51
- const { totalMs } = measure(() => isIdLikeSessionTitle('Fix the login bug'), ITERATIONS);
52
- console.log(` isIdLikeSessionTitle (normal): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
53
- assert.ok(totalMs < 50, `took ${totalMs.toFixed(2)}ms, expected < 50ms`);
54
- });
55
-
56
- it(`isFallbackSessionTitle × ${ITERATIONS} mixed checks < 100ms`, () => {
57
- const titles = ['New session', 'Resumed session', uuid, 'Fix bug', hexId, 'Untitled session'];
58
- const { totalMs } = measure(() => {
59
- for (const title of titles) isFallbackSessionTitle(title);
60
- }, ITERATIONS);
61
- console.log(` isFallbackSessionTitle (mixed×6): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
62
- assert.ok(totalMs < 100, `took ${totalMs.toFixed(2)}ms, expected < 100ms`);
63
- });
64
-
65
- it(`titleFromFirstSentence × ${ITERATIONS} single line < 50ms`, () => {
66
- const { totalMs } = measure(() => titleFromFirstSentence('Fix the login bug. Then deploy.'), ITERATIONS);
67
- console.log(` titleFromFirstSentence (single): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
68
- assert.ok(totalMs < 50, `took ${totalMs.toFixed(2)}ms, expected < 50ms`);
69
- });
70
-
71
- it(`titleFromFirstSentence × ${ITERATIONS} multi-line < 200ms`, () => {
72
- const { totalMs } = measure(() => titleFromFirstSentence(multiLine), ITERATIONS);
73
- console.log(` titleFromFirstSentence (multi): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
74
- assert.ok(totalMs < 200, `took ${totalMs.toFixed(2)}ms, expected < 200ms`);
75
- });
76
-
77
- it(`titleFromFirstSentence × ${ITERATIONS} with meta prefix noise < 200ms`, () => {
78
- const text = '<environment_context>setup\n# AGENTS.md instructions for project\nActual title here. More text.';
79
- const { totalMs } = measure(() => titleFromFirstSentence(text), ITERATIONS);
80
- console.log(` titleFromFirstSentence (meta noise): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
81
- assert.ok(totalMs < 200, `took ${totalMs.toFixed(2)}ms, expected < 200ms`);
82
- });
83
-
84
- it(`titleFromFirstSentence × ${ITERATIONS} long truncation < 100ms`, () => {
85
- const sentence = 'A'.repeat(200) + '. Done.';
86
- const { totalMs } = measure(() => titleFromFirstSentence(sentence, 80), ITERATIONS);
87
- console.log(` titleFromFirstSentence (truncation): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
88
- assert.ok(totalMs < 100, `took ${totalMs.toFixed(2)}ms, expected < 100ms`);
89
- });
90
-
91
- it(`preferSessionTitle × ${ITERATIONS} candidate chain < 100ms`, () => {
92
- const candidates: Array<string | null | undefined> = [
93
- null,
94
- undefined,
95
- 'New session',
96
- uuid,
97
- 'Actual good title',
98
- ];
99
- const { totalMs } = measure(() => preferSessionTitle(candidates, 'fallback text here'), ITERATIONS);
100
- console.log(` preferSessionTitle (chain): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
101
- assert.ok(totalMs < 100, `took ${totalMs.toFixed(2)}ms, expected < 100ms`);
102
- });
103
-
104
- it(`preferSessionTitle × ${ITERATIONS} fallback to text < 200ms`, () => {
105
- const candidates: Array<string | null | undefined> = [null, 'New session', uuid];
106
- const { totalMs } = measure(() => preferSessionTitle(candidates, multiLine), ITERATIONS);
107
- console.log(` preferSessionTitle (fallback): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
108
- assert.ok(totalMs < 200, `took ${totalMs.toFixed(2)}ms, expected < 200ms`);
109
- });
110
-
111
- // CJK sentence boundary test
112
- it(`titleFromFirstSentence × ${ITERATIONS} CJK text < 100ms`, () => {
113
- const text = '修复登录页面的样式问题。然后部署到生产环境。';
114
- const { totalMs } = measure(() => titleFromFirstSentence(text), ITERATIONS);
115
- console.log(` titleFromFirstSentence (CJK): ${totalMs.toFixed(2)}ms for ${ITERATIONS} calls`);
116
- assert.ok(totalMs < 100, `took ${totalMs.toFixed(2)}ms, expected < 100ms`);
117
- });
118
- });