@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,286 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
-
4
- import {
5
- sanitizeSessionTitle,
6
- isIdLikeSessionTitle,
7
- isFallbackSessionTitle,
8
- titleFromFirstSentence,
9
- preferSessionTitle,
10
- } from './session-title.js';
11
-
12
- describe('sanitizeSessionTitle', () => {
13
- it('returns undefined for null', () => {
14
- assert.equal(sanitizeSessionTitle(null), undefined);
15
- });
16
-
17
- it('returns undefined for undefined', () => {
18
- assert.equal(sanitizeSessionTitle(undefined), undefined);
19
- });
20
-
21
- it('returns undefined for non-string values', () => {
22
- assert.equal(sanitizeSessionTitle(42 as any), undefined);
23
- assert.equal(sanitizeSessionTitle(true as any), undefined);
24
- assert.equal(sanitizeSessionTitle({} as any), undefined);
25
- });
26
-
27
- it('returns undefined for empty string', () => {
28
- assert.equal(sanitizeSessionTitle(''), undefined);
29
- });
30
-
31
- it('returns undefined for whitespace-only string', () => {
32
- assert.equal(sanitizeSessionTitle(' '), undefined);
33
- assert.equal(sanitizeSessionTitle('\t\n'), undefined);
34
- });
35
-
36
- it('collapses internal whitespace', () => {
37
- assert.equal(sanitizeSessionTitle('hello world'), 'hello world');
38
- assert.equal(sanitizeSessionTitle('a\n\nb\tc'), 'a b c');
39
- });
40
-
41
- it('trims leading and trailing whitespace', () => {
42
- assert.equal(sanitizeSessionTitle(' hello '), 'hello');
43
- });
44
-
45
- it('returns normalized string for valid input', () => {
46
- assert.equal(sanitizeSessionTitle('Fix the bug'), 'Fix the bug');
47
- });
48
- });
49
-
50
- describe('isIdLikeSessionTitle', () => {
51
- it('returns false for null/undefined', () => {
52
- assert.equal(isIdLikeSessionTitle(null), false);
53
- assert.equal(isIdLikeSessionTitle(undefined), false);
54
- });
55
-
56
- it('returns false for empty string', () => {
57
- assert.equal(isIdLikeSessionTitle(''), false);
58
- });
59
-
60
- it('detects long hex strings (8+ chars)', () => {
61
- assert.equal(isIdLikeSessionTitle('abcdef01'), true);
62
- assert.equal(isIdLikeSessionTitle('0123456789abcdef'), true);
63
- assert.equal(isIdLikeSessionTitle('ABCDEF01'), true);
64
- });
65
-
66
- it('does not match short hex strings (<8 chars)', () => {
67
- assert.equal(isIdLikeSessionTitle('abcdef0'), false);
68
- });
69
-
70
- it('detects UUID format', () => {
71
- assert.equal(isIdLikeSessionTitle('550e8400-e29b-41d4-a716-446655440000'), true);
72
- assert.equal(isIdLikeSessionTitle('ABCDEF00-1234-5678-9ABC-DEF012345678'), true);
73
- });
74
-
75
- it('detects agent-prefixed IDs', () => {
76
- assert.equal(isIdLikeSessionTitle('agent-abc123'), true);
77
- assert.equal(isIdLikeSessionTitle('agent-some_long-id'), true);
78
- });
79
-
80
- it('rejects agent prefix with short suffix', () => {
81
- assert.equal(isIdLikeSessionTitle('agent-abc'), false);
82
- });
83
-
84
- it('returns false for normal titles', () => {
85
- assert.equal(isIdLikeSessionTitle('Fix the login bug'), false);
86
- assert.equal(isIdLikeSessionTitle('Refactor utils'), false);
87
- });
88
- });
89
-
90
- describe('isFallbackSessionTitle', () => {
91
- it('returns true for null/undefined/empty', () => {
92
- assert.equal(isFallbackSessionTitle(null), true);
93
- assert.equal(isFallbackSessionTitle(undefined), true);
94
- assert.equal(isFallbackSessionTitle(''), true);
95
- assert.equal(isFallbackSessionTitle(' '), true);
96
- });
97
-
98
- it('returns true for generic titles', () => {
99
- assert.equal(isFallbackSessionTitle('New session'), true);
100
- assert.equal(isFallbackSessionTitle('Resumed session'), true);
101
- assert.equal(isFallbackSessionTitle('Untitled session'), true);
102
- });
103
-
104
- it('returns true for UUID-like titles', () => {
105
- assert.equal(isFallbackSessionTitle('550e8400-e29b-41d4-a716-446655440000'), true);
106
- assert.equal(isFallbackSessionTitle('abcdef0123456789'), true);
107
- assert.equal(isFallbackSessionTitle('agent-abc123def'), true);
108
- });
109
-
110
- it('returns false for real titles', () => {
111
- assert.equal(isFallbackSessionTitle('Fix the login bug'), false);
112
- assert.equal(isFallbackSessionTitle('Add dark mode support'), false);
113
- });
114
- });
115
-
116
- describe('titleFromFirstSentence', () => {
117
- it('returns undefined for null/undefined', () => {
118
- assert.equal(titleFromFirstSentence(null), undefined);
119
- assert.equal(titleFromFirstSentence(undefined), undefined);
120
- });
121
-
122
- it('returns undefined for empty/whitespace string', () => {
123
- assert.equal(titleFromFirstSentence(''), undefined);
124
- assert.equal(titleFromFirstSentence(' '), undefined);
125
- });
126
-
127
- it('extracts first sentence ending with period', () => {
128
- assert.equal(titleFromFirstSentence('Fix the bug. Then deploy.'), 'Fix the bug');
129
- });
130
-
131
- it('extracts first sentence ending with exclamation mark', () => {
132
- assert.equal(titleFromFirstSentence('Great feature! It works.'), 'Great feature');
133
- });
134
-
135
- it('extracts first sentence ending with question mark', () => {
136
- assert.equal(titleFromFirstSentence('Does this work? I think so.'), 'Does this work');
137
- });
138
-
139
- it('extracts first sentence ending with semicolon', () => {
140
- assert.equal(titleFromFirstSentence('First part; second part'), 'First part');
141
- });
142
-
143
- it('handles Chinese sentence markers', () => {
144
- assert.equal(titleFromFirstSentence('修复了登录问题。然后部署。'), '修复了登录问题');
145
- assert.equal(titleFromFirstSentence('这是什么?我不知道。'), '这是什么');
146
- assert.equal(titleFromFirstSentence('太好了!继续。'), '太好了');
147
- assert.equal(titleFromFirstSentence('第一部分;第二部分'), '第一部分');
148
- });
149
-
150
- it('does not split on period without trailing space (e.g. file.ts)', () => {
151
- assert.equal(titleFromFirstSentence('Update file.ts for production'), 'Update file.ts for production');
152
- });
153
-
154
- it('does split on period at end of string', () => {
155
- assert.equal(titleFromFirstSentence('Fix the bug.'), 'Fix the bug');
156
- });
157
-
158
- it('returns the whole line if no sentence boundary found', () => {
159
- assert.equal(titleFromFirstSentence('Fix the bug'), 'Fix the bug');
160
- });
161
-
162
- it('skips meta noise lines', () => {
163
- assert.equal(
164
- titleFromFirstSentence('<environment_context>some context\nActual title here'),
165
- 'Actual title here',
166
- );
167
- assert.equal(
168
- titleFromFirstSentence('<command-name>run\nReal title'),
169
- 'Real title',
170
- );
171
- assert.equal(
172
- titleFromFirstSentence('<command-message>msg\nReal title'),
173
- 'Real title',
174
- );
175
- assert.equal(
176
- titleFromFirstSentence('<command-args>args\nReal title'),
177
- 'Real title',
178
- );
179
- assert.equal(
180
- titleFromFirstSentence('<local-command-caveat>caveat\nReal title'),
181
- 'Real title',
182
- );
183
- assert.equal(
184
- titleFromFirstSentence('<local-command-stdout>out\nReal title'),
185
- 'Real title',
186
- );
187
- assert.equal(
188
- titleFromFirstSentence('<local-command-stderr>err\nReal title'),
189
- 'Real title',
190
- );
191
- assert.equal(
192
- titleFromFirstSentence('# AGENTS.md instructions for something\nReal title'),
193
- 'Real title',
194
- );
195
- });
196
-
197
- it('skips path-like lines', () => {
198
- assert.equal(titleFromFirstSentence('/usr/bin/node\nActual title'), 'Actual title');
199
- assert.equal(titleFromFirstSentence('~/projects/foo\nActual title'), 'Actual title');
200
- assert.equal(titleFromFirstSentence('./src/index.ts\nActual title'), 'Actual title');
201
- assert.equal(titleFromFirstSentence('../parent/file\nActual title'), 'Actual title');
202
- });
203
-
204
- it('skips Windows path-like lines', () => {
205
- assert.equal(titleFromFirstSentence('C:\\Users\\foo\nActual title'), 'Actual title');
206
- });
207
-
208
- it('skips quoted path-like lines', () => {
209
- assert.equal(titleFromFirstSentence('"/usr/bin/node"\nActual title'), 'Actual title');
210
- assert.equal(titleFromFirstSentence("'~/foo'\nActual title"), 'Actual title');
211
- });
212
-
213
- it('truncates long titles with ellipsis', () => {
214
- const longText = 'This is a very long title that keeps going and going and should be truncated at some reasonable length by the function';
215
- const result = titleFromFirstSentence(longText);
216
- assert.ok(result!.length <= 80);
217
- assert.ok(result!.endsWith('...'));
218
- });
219
-
220
- it('respects custom maxLength', () => {
221
- const result = titleFromFirstSentence('This is a somewhat long title for testing', 20);
222
- assert.ok(result!.length <= 20);
223
- assert.ok(result!.endsWith('...'));
224
- });
225
-
226
- it('does not truncate titles within maxLength', () => {
227
- assert.equal(titleFromFirstSentence('Short title', 80), 'Short title');
228
- });
229
-
230
- it('returns undefined if extracted title is ID-like', () => {
231
- assert.equal(titleFromFirstSentence('550e8400-e29b-41d4-a716-446655440000'), undefined);
232
- assert.equal(titleFromFirstSentence('abcdef0123456789'), undefined);
233
- });
234
-
235
- it('skips empty lines to find the first meaningful line', () => {
236
- assert.equal(titleFromFirstSentence('\n\n\nActual title'), 'Actual title');
237
- });
238
-
239
- it('handles \\r\\n line endings', () => {
240
- assert.equal(titleFromFirstSentence('\r\n\r\nActual title'), 'Actual title');
241
- });
242
- });
243
-
244
- describe('preferSessionTitle', () => {
245
- it('returns first non-fallback candidate', () => {
246
- assert.equal(preferSessionTitle(['Fix the bug', 'Other title']), 'Fix the bug');
247
- });
248
-
249
- it('skips null and undefined candidates', () => {
250
- assert.equal(preferSessionTitle([null, undefined, 'Fix the bug']), 'Fix the bug');
251
- });
252
-
253
- it('skips generic fallback titles', () => {
254
- assert.equal(
255
- preferSessionTitle(['New session', 'Resumed session', 'Real title']),
256
- 'Real title',
257
- );
258
- });
259
-
260
- it('skips ID-like candidates', () => {
261
- assert.equal(
262
- preferSessionTitle(['550e8400-e29b-41d4-a716-446655440000', 'Real title']),
263
- 'Real title',
264
- );
265
- });
266
-
267
- it('falls back to titleFromFirstSentence when all candidates are fallback', () => {
268
- assert.equal(
269
- preferSessionTitle(['New session', null], 'Extract this title from text'),
270
- 'Extract this title from text',
271
- );
272
- });
273
-
274
- it('returns undefined when no candidates and no fallback', () => {
275
- assert.equal(preferSessionTitle([null, undefined]), undefined);
276
- });
277
-
278
- it('returns undefined when all candidates are fallback and fallback text is empty', () => {
279
- assert.equal(preferSessionTitle(['New session'], ''), undefined);
280
- assert.equal(preferSessionTitle(['New session'], null), undefined);
281
- });
282
-
283
- it('returns undefined for empty candidates array with no fallback', () => {
284
- assert.equal(preferSessionTitle([]), undefined);
285
- });
286
- });
@@ -1,108 +0,0 @@
1
- const GENERIC_SESSION_TITLES = new Set([
2
- 'New session',
3
- 'Resumed session',
4
- 'Untitled session',
5
- ]);
6
-
7
- const KNOWN_META_PREFIXES = [
8
- '<environment_context>',
9
- '<local-command-caveat>',
10
- '<local-command-stdout>',
11
- '<local-command-stderr>',
12
- '<command-name>',
13
- '<command-message>',
14
- '<command-args>',
15
- '# AGENTS.md instructions for ',
16
- ];
17
-
18
- function collapseWhitespace(text: string): string {
19
- return text.replace(/\s+/g, ' ').trim();
20
- }
21
-
22
- export function sanitizeSessionTitle(title?: string | null): string | undefined {
23
- if (typeof title !== 'string') return undefined;
24
- const normalized = collapseWhitespace(title);
25
- return normalized || undefined;
26
- }
27
-
28
- export function isIdLikeSessionTitle(title?: string | null): boolean {
29
- const normalized = sanitizeSessionTitle(title);
30
- if (!normalized) return false;
31
- return (
32
- /^[0-9a-f]{8,}$/i.test(normalized) ||
33
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(normalized) ||
34
- /^agent-[a-z0-9_-]{6,}$/i.test(normalized)
35
- );
36
- }
37
-
38
- export function isFallbackSessionTitle(title?: string | null): boolean {
39
- const normalized = sanitizeSessionTitle(title);
40
- return !normalized || GENERIC_SESSION_TITLES.has(normalized) || isIdLikeSessionTitle(normalized);
41
- }
42
-
43
- function firstNonEmptyLine(text: string): string | undefined {
44
- for (const line of text.replace(/\r/g, '').split('\n')) {
45
- const normalized = sanitizeSessionTitle(line);
46
- if (!normalized) continue;
47
- if (isMetaTitleNoise(normalized)) continue;
48
- if (isPathLikeTitleLine(normalized)) continue;
49
- return normalized;
50
- }
51
- return undefined;
52
- }
53
-
54
- function isMetaTitleNoise(text: string): boolean {
55
- return KNOWN_META_PREFIXES.some(prefix => text.startsWith(prefix));
56
- }
57
-
58
- function isPathLikeTitleLine(text: string): boolean {
59
- const stripped = text
60
- .replace(/^['"`]+/, '')
61
- .replace(/['"`\\]+$/, '')
62
- .trim();
63
- if (!stripped) return true;
64
- if (/^(\/|~\/|\.\/|\.\.\/)/.test(stripped)) return true;
65
- if (/^[A-Za-z]:\\/.test(stripped)) return true;
66
- return false;
67
- }
68
-
69
- function findSentenceBoundary(text: string): number {
70
- for (let index = 0; index < text.length; index += 1) {
71
- const char = text[index];
72
- if (char === '。' || char === '!' || char === '?' || char === ';') {
73
- return index + 1;
74
- }
75
- if (char === '.' || char === '!' || char === '?' || char === ';') {
76
- const next = text[index + 1];
77
- if (!next || /\s/.test(next)) {
78
- return index + 1;
79
- }
80
- }
81
- }
82
- return -1;
83
- }
84
-
85
- export function titleFromFirstSentence(text?: string | null, maxLength = 80): string | undefined {
86
- if (typeof text !== 'string') return undefined;
87
- const line = firstNonEmptyLine(text);
88
- if (!line) return undefined;
89
-
90
- const boundary = findSentenceBoundary(line);
91
- const sentence = boundary > 0 ? line.slice(0, boundary) : line;
92
- const stripped = sentence.replace(/[。!?;.!?;]+$/gu, '').trim();
93
- if (!stripped || isIdLikeSessionTitle(stripped)) return undefined;
94
- if (stripped.length <= maxLength) return stripped;
95
- return `${stripped.slice(0, Math.max(1, maxLength - 3)).trimEnd()}...`;
96
- }
97
-
98
- export function preferSessionTitle(
99
- candidates: Array<string | null | undefined>,
100
- fallbackText?: string | null,
101
- ): string | undefined {
102
- for (const candidate of candidates) {
103
- const normalized = sanitizeSessionTitle(candidate);
104
- if (!normalized || isFallbackSessionTitle(normalized)) continue;
105
- return normalized;
106
- }
107
- return titleFromFirstSentence(fallbackText);
108
- }
@@ -1,95 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { spawn } from 'node:child_process';
4
- import { mkdtempSync, rmSync } from 'node:fs';
5
- import { tmpdir } from 'node:os';
6
- import { join, resolve } from 'node:path';
7
-
8
- // The bundled daemon entry is at the project root's dist/index.cjs
9
- const daemonEntry = resolve(import.meta.dirname, '..', '..', '..', 'dist', 'index.cjs');
10
-
11
- function waitForHealth(port: number, timeoutMs = 10000): Promise<boolean> {
12
- const deadline = Date.now() + timeoutMs;
13
- return new Promise((resolve) => {
14
- const poll = async () => {
15
- if (Date.now() > deadline) { resolve(false); return; }
16
- try {
17
- const res = await fetch(`http://127.0.0.1:${port}/health`);
18
- if (res.ok) { resolve(true); return; }
19
- } catch { /* retry */ }
20
- setTimeout(poll, 200);
21
- };
22
- poll();
23
- });
24
- }
25
-
26
- function waitForExit(child: ReturnType<typeof spawn>, timeoutMs = 5000): Promise<number | null> {
27
- return new Promise((resolve) => {
28
- const timer = setTimeout(() => {
29
- child.kill('SIGKILL');
30
- resolve(null);
31
- }, timeoutMs);
32
- child.on('exit', (code) => {
33
- clearTimeout(timer);
34
- resolve(code);
35
- });
36
- });
37
- }
38
-
39
- function spawnDaemon(port: number): { child: ReturnType<typeof spawn>; homeDir: string } {
40
- const homeDir = mkdtempSync(join(tmpdir(), 'vibelet-shutdown-test-'));
41
- const child = spawn(process.execPath, [daemonEntry], {
42
- env: {
43
- ...process.env,
44
- HOME: homeDir,
45
- VIBE_PORT: String(port),
46
- VIBE_TOKEN: 'test-token',
47
- CLAUDE_PATH: process.execPath,
48
- CODEX_PATH: process.execPath,
49
- },
50
- stdio: ['ignore', 'ignore', 'ignore'],
51
- });
52
- return { child, homeDir };
53
- }
54
-
55
- test('POST /shutdown returns stopping and daemon exits', async () => {
56
- const testPort = 29000 + Math.floor(Math.random() * 1000);
57
- const { child, homeDir } = spawnDaemon(testPort);
58
-
59
- try {
60
- const healthy = await waitForHealth(testPort);
61
- assert.ok(healthy, 'Daemon should become healthy');
62
-
63
- const res = await fetch(`http://127.0.0.1:${testPort}/shutdown`, { method: 'POST' });
64
- assert.equal(res.status, 200);
65
- const body = await res.json() as { status: string };
66
- assert.equal(body.status, 'stopping');
67
-
68
- const exitCode = await waitForExit(child);
69
- assert.notEqual(exitCode, null, 'Daemon should exit within timeout');
70
- } finally {
71
- child.kill('SIGKILL');
72
- rmSync(homeDir, { recursive: true, force: true });
73
- }
74
- });
75
-
76
- test('GET /shutdown does not trigger shutdown', async () => {
77
- const testPort = 29000 + Math.floor(Math.random() * 1000);
78
- const { child, homeDir } = spawnDaemon(testPort);
79
-
80
- try {
81
- const healthy = await waitForHealth(testPort);
82
- assert.ok(healthy, 'Daemon should become healthy');
83
-
84
- // GET /shutdown should not match the POST-only route
85
- const res = await fetch(`http://127.0.0.1:${testPort}/shutdown`);
86
- assert.notEqual(res.status, 200, 'GET /shutdown should not return 200');
87
-
88
- // Daemon should still be alive
89
- const stillHealthy = await waitForHealth(testPort, 1500);
90
- assert.ok(stillHealthy, 'Daemon should still be running');
91
- } finally {
92
- child.kill('SIGKILL');
93
- rmSync(homeDir, { recursive: true, force: true });
94
- }
95
- });
@@ -1,78 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { runStorageHousekeepingSync, trimFileTailSync } from './storage-housekeeping.js';
7
-
8
- test('trimFileTailSync leaves files under the cap unchanged', () => {
9
- const dir = mkdtempSync(join(tmpdir(), 'vibelet-storage-'));
10
- const filePath = join(dir, 'audit.jsonl');
11
- writeFileSync(filePath, 'one\ntwo\n', 'utf8');
12
-
13
- const result = trimFileTailSync(filePath, { maxBytes: 64 });
14
-
15
- assert.equal(result.trimmed, false);
16
- assert.equal(readFileSync(filePath, 'utf8'), 'one\ntwo\n');
17
- });
18
-
19
- test('trimFileTailSync keeps the newest tail aligned to a newline when trimming', () => {
20
- const dir = mkdtempSync(join(tmpdir(), 'vibelet-storage-'));
21
- const filePath = join(dir, 'daemon.stdout.log');
22
- writeFileSync(filePath, 'alpha\nbravo\ncharlie\ndelta\n', 'utf8');
23
-
24
- const result = trimFileTailSync(filePath, {
25
- maxBytes: 20,
26
- retainBytes: 20,
27
- });
28
-
29
- assert.equal(result.trimmed, true);
30
- assert.equal(readFileSync(filePath, 'utf8'), 'charlie\ndelta\n');
31
- assert.equal(result.afterBytes, Buffer.byteLength('charlie\ndelta\n'));
32
- });
33
-
34
- test('trimFileTailSync reserves space for an incoming append', () => {
35
- const dir = mkdtempSync(join(tmpdir(), 'vibelet-storage-'));
36
- const filePath = join(dir, 'audit.jsonl');
37
- writeFileSync(filePath, '1111\n2222\n3333\n4444\n', 'utf8');
38
-
39
- const result = trimFileTailSync(filePath, {
40
- maxBytes: 18,
41
- incomingBytes: 6,
42
- retainBytes: 18,
43
- });
44
-
45
- assert.equal(result.trimmed, true);
46
- assert.equal(readFileSync(filePath, 'utf8'), '3333\n4444\n');
47
- assert.ok(result.afterBytes + 6 <= 18);
48
- });
49
-
50
- test('runStorageHousekeepingSync trims only oversized audit and log files', () => {
51
- const dir = mkdtempSync(join(tmpdir(), 'vibelet-storage-'));
52
- const dataDir = join(dir, 'data');
53
- const logDir = join(dir, 'logs');
54
- mkdirSync(dataDir, { recursive: true });
55
- mkdirSync(logDir, { recursive: true });
56
-
57
- const auditPath = join(dataDir, 'audit.jsonl');
58
- const stdoutLogPath = join(logDir, 'daemon.stdout.log');
59
- const stderrLogPath = join(logDir, 'daemon.stderr.log');
60
-
61
- writeFileSync(auditPath, 'old-a\nold-b\nkeep-a\nkeep-b\n', 'utf8');
62
- writeFileSync(stdoutLogPath, 'stdout-old\nstdout-keep\n', 'utf8');
63
- writeFileSync(stderrLogPath, 'stderr-ok\n', 'utf8');
64
-
65
- const result = runStorageHousekeepingSync({
66
- auditPath,
67
- stdoutLogPath,
68
- stderrLogPath,
69
- auditMaxBytes: 20,
70
- logMaxBytes: 20,
71
- });
72
-
73
- assert.equal(result.trimmedFiles, 2);
74
- assert.ok(result.reclaimedBytes > 0);
75
- assert.equal(readFileSync(auditPath, 'utf8'), 'keep-a\nkeep-b\n');
76
- assert.equal(readFileSync(stdoutLogPath, 'utf8'), 'stdout-keep\n');
77
- assert.equal(readFileSync(stderrLogPath, 'utf8'), 'stderr-ok\n');
78
- });
@@ -1,111 +0,0 @@
1
- import { closeSync, existsSync, ftruncateSync, openSync, readSync, statSync, writeSync } from 'fs';
2
-
3
- export interface TrimFileOptions {
4
- maxBytes: number;
5
- incomingBytes?: number;
6
- retainBytes?: number;
7
- }
8
-
9
- export interface TrimFileResult {
10
- path: string;
11
- existed: boolean;
12
- trimmed: boolean;
13
- beforeBytes: number;
14
- afterBytes: number;
15
- reclaimedBytes: number;
16
- }
17
-
18
- export interface StorageHousekeepingOptions {
19
- auditPath: string;
20
- stdoutLogPath: string;
21
- stderrLogPath: string;
22
- auditMaxBytes: number;
23
- logMaxBytes: number;
24
- }
25
-
26
- export interface StorageHousekeepingResult {
27
- files: TrimFileResult[];
28
- trimmedFiles: number;
29
- reclaimedBytes: number;
30
- }
31
-
32
- function clampRetainBytes(beforeBytes: number, maxBytes: number, incomingBytes: number, retainBytes?: number): number {
33
- const appendBudget = Math.max(0, maxBytes - incomingBytes);
34
- const defaultRetain = Math.floor(maxBytes * 0.75);
35
- return Math.max(0, Math.min(beforeBytes, appendBudget, retainBytes ?? defaultRetain));
36
- }
37
-
38
- export function trimFileTailSync(filePath: string, options: TrimFileOptions): TrimFileResult {
39
- const existed = existsSync(filePath);
40
- if (!existed) {
41
- return {
42
- path: filePath,
43
- existed: false,
44
- trimmed: false,
45
- beforeBytes: 0,
46
- afterBytes: 0,
47
- reclaimedBytes: 0,
48
- };
49
- }
50
-
51
- const beforeBytes = statSync(filePath).size;
52
- const incomingBytes = Math.max(0, options.incomingBytes ?? 0);
53
- if (options.maxBytes <= 0 || beforeBytes + incomingBytes <= options.maxBytes) {
54
- return {
55
- path: filePath,
56
- existed: true,
57
- trimmed: false,
58
- beforeBytes,
59
- afterBytes: beforeBytes,
60
- reclaimedBytes: 0,
61
- };
62
- }
63
-
64
- const keepBytes = clampRetainBytes(beforeBytes, options.maxBytes, incomingBytes, options.retainBytes);
65
- const fd = openSync(filePath, 'r+');
66
- try {
67
- let tail = Buffer.alloc(0);
68
- if (keepBytes > 0) {
69
- const buffer = Buffer.alloc(keepBytes);
70
- readSync(fd, buffer, 0, keepBytes, beforeBytes - keepBytes);
71
- let start = 0;
72
- if (beforeBytes > keepBytes) {
73
- const newlineIndex = buffer.indexOf(0x0a);
74
- if (newlineIndex >= 0 && newlineIndex + 1 < buffer.length) {
75
- start = newlineIndex + 1;
76
- }
77
- }
78
- tail = buffer.subarray(start);
79
- }
80
-
81
- ftruncateSync(fd, 0);
82
- if (tail.length > 0) {
83
- writeSync(fd, tail, 0, tail.length, 0);
84
- }
85
-
86
- return {
87
- path: filePath,
88
- existed: true,
89
- trimmed: true,
90
- beforeBytes,
91
- afterBytes: tail.length,
92
- reclaimedBytes: beforeBytes - tail.length,
93
- };
94
- } finally {
95
- closeSync(fd);
96
- }
97
- }
98
-
99
- export function runStorageHousekeepingSync(options: StorageHousekeepingOptions): StorageHousekeepingResult {
100
- const files = [
101
- trimFileTailSync(options.auditPath, { maxBytes: options.auditMaxBytes }),
102
- trimFileTailSync(options.stdoutLogPath, { maxBytes: options.logMaxBytes }),
103
- trimFileTailSync(options.stderrLogPath, { maxBytes: options.logMaxBytes }),
104
- ];
105
-
106
- return {
107
- files,
108
- trimmedFiles: files.filter(file => file.trimmed).length,
109
- reclaimedBytes: files.reduce((sum, file) => sum + file.reclaimedBytes, 0),
110
- };
111
- }