lattice-orchestrator 0.7.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 (440) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +58 -0
  3. package/config/logrotate.conf +15 -0
  4. package/dist/cli-parser.d.ts +11 -0
  5. package/dist/cli-parser.d.ts.map +1 -0
  6. package/dist/cli-parser.js +48 -0
  7. package/dist/cli-parser.js.map +1 -0
  8. package/dist/lattice-server.d.ts +70 -0
  9. package/dist/lattice-server.d.ts.map +1 -0
  10. package/dist/lattice-server.js +969 -0
  11. package/dist/lattice-server.js.map +1 -0
  12. package/dist/mcp-server/index.d.ts +3 -0
  13. package/dist/mcp-server/index.d.ts.map +1 -0
  14. package/dist/mcp-server/index.js +190 -0
  15. package/dist/mcp-server/index.js.map +1 -0
  16. package/dist/mcp-server/lattice-tools.d.ts +15 -0
  17. package/dist/mcp-server/lattice-tools.d.ts.map +1 -0
  18. package/dist/mcp-server/lattice-tools.js +366 -0
  19. package/dist/mcp-server/lattice-tools.js.map +1 -0
  20. package/dist/middleware/cors-setup.d.ts +7 -0
  21. package/dist/middleware/cors-setup.d.ts.map +1 -0
  22. package/dist/middleware/cors-setup.js +8 -0
  23. package/dist/middleware/cors-setup.js.map +1 -0
  24. package/dist/middleware/error-handler.d.ts +4 -0
  25. package/dist/middleware/error-handler.d.ts.map +1 -0
  26. package/dist/middleware/error-handler.js +27 -0
  27. package/dist/middleware/error-handler.js.map +1 -0
  28. package/dist/middleware/query-parser.d.ts +11 -0
  29. package/dist/middleware/query-parser.d.ts.map +1 -0
  30. package/dist/middleware/query-parser.js +68 -0
  31. package/dist/middleware/query-parser.js.map +1 -0
  32. package/dist/middleware/request-logger.d.ts +4 -0
  33. package/dist/middleware/request-logger.d.ts.map +1 -0
  34. package/dist/middleware/request-logger.js +6 -0
  35. package/dist/middleware/request-logger.js.map +1 -0
  36. package/dist/process-daemon/index.d.ts +14 -0
  37. package/dist/process-daemon/index.d.ts.map +1 -0
  38. package/dist/process-daemon/index.js +51 -0
  39. package/dist/process-daemon/index.js.map +1 -0
  40. package/dist/process-daemon/process-daemon.d.ts +101 -0
  41. package/dist/process-daemon/process-daemon.d.ts.map +1 -0
  42. package/dist/process-daemon/process-daemon.js +846 -0
  43. package/dist/process-daemon/process-daemon.js.map +1 -0
  44. package/dist/process-daemon/process-manager-client.d.ts +123 -0
  45. package/dist/process-daemon/process-manager-client.d.ts.map +1 -0
  46. package/dist/process-daemon/process-manager-client.js +329 -0
  47. package/dist/process-daemon/process-manager-client.js.map +1 -0
  48. package/dist/process-daemon/process-manager-interface.d.ts +61 -0
  49. package/dist/process-daemon/process-manager-interface.d.ts.map +1 -0
  50. package/dist/process-daemon/process-manager-interface.js +8 -0
  51. package/dist/process-daemon/process-manager-interface.js.map +1 -0
  52. package/dist/process-daemon/test-daemon.d.ts +12 -0
  53. package/dist/process-daemon/test-daemon.d.ts.map +1 -0
  54. package/dist/process-daemon/test-daemon.js +81 -0
  55. package/dist/process-daemon/test-daemon.js.map +1 -0
  56. package/dist/process-daemon/types.d.ts +97 -0
  57. package/dist/process-daemon/types.d.ts.map +1 -0
  58. package/dist/process-daemon/types.js +8 -0
  59. package/dist/process-daemon/types.js.map +1 -0
  60. package/dist/routes/analysis.routes.d.ts +13 -0
  61. package/dist/routes/analysis.routes.d.ts.map +1 -0
  62. package/dist/routes/analysis.routes.js +520 -0
  63. package/dist/routes/analysis.routes.js.map +1 -0
  64. package/dist/routes/config.routes.d.ts +4 -0
  65. package/dist/routes/config.routes.d.ts.map +1 -0
  66. package/dist/routes/config.routes.js +27 -0
  67. package/dist/routes/config.routes.js.map +1 -0
  68. package/dist/routes/conversation.routes.d.ts +43 -0
  69. package/dist/routes/conversation.routes.d.ts.map +1 -0
  70. package/dist/routes/conversation.routes.js +79 -0
  71. package/dist/routes/conversation.routes.js.map +1 -0
  72. package/dist/routes/filesystem.routes.d.ts +4 -0
  73. package/dist/routes/filesystem.routes.d.ts.map +1 -0
  74. package/dist/routes/filesystem.routes.js +86 -0
  75. package/dist/routes/filesystem.routes.js.map +1 -0
  76. package/dist/routes/insights.routes.d.ts +17 -0
  77. package/dist/routes/insights.routes.d.ts.map +1 -0
  78. package/dist/routes/insights.routes.js +633 -0
  79. package/dist/routes/insights.routes.js.map +1 -0
  80. package/dist/routes/lattice.routes.d.ts +10 -0
  81. package/dist/routes/lattice.routes.d.ts.map +1 -0
  82. package/dist/routes/lattice.routes.js +123 -0
  83. package/dist/routes/lattice.routes.js.map +1 -0
  84. package/dist/routes/license.routes.d.ts +3 -0
  85. package/dist/routes/license.routes.d.ts.map +1 -0
  86. package/dist/routes/license.routes.js +95 -0
  87. package/dist/routes/license.routes.js.map +1 -0
  88. package/dist/routes/log.routes.d.ts +3 -0
  89. package/dist/routes/log.routes.d.ts.map +1 -0
  90. package/dist/routes/log.routes.js +184 -0
  91. package/dist/routes/log.routes.js.map +1 -0
  92. package/dist/routes/pending-question.routes.d.ts +9 -0
  93. package/dist/routes/pending-question.routes.d.ts.map +1 -0
  94. package/dist/routes/pending-question.routes.js +162 -0
  95. package/dist/routes/pending-question.routes.js.map +1 -0
  96. package/dist/routes/permission.routes.d.ts +18 -0
  97. package/dist/routes/permission.routes.d.ts.map +1 -0
  98. package/dist/routes/permission.routes.js +370 -0
  99. package/dist/routes/permission.routes.js.map +1 -0
  100. package/dist/routes/process-events.routes.d.ts +9 -0
  101. package/dist/routes/process-events.routes.d.ts.map +1 -0
  102. package/dist/routes/process-events.routes.js +141 -0
  103. package/dist/routes/process-events.routes.js.map +1 -0
  104. package/dist/routes/prototype.routes.d.ts +9 -0
  105. package/dist/routes/prototype.routes.d.ts.map +1 -0
  106. package/dist/routes/prototype.routes.js +757 -0
  107. package/dist/routes/prototype.routes.js.map +1 -0
  108. package/dist/routes/question.routes.d.ts +8 -0
  109. package/dist/routes/question.routes.d.ts.map +1 -0
  110. package/dist/routes/question.routes.js +83 -0
  111. package/dist/routes/question.routes.js.map +1 -0
  112. package/dist/routes/session-control.routes.d.ts +29 -0
  113. package/dist/routes/session-control.routes.d.ts.map +1 -0
  114. package/dist/routes/session-control.routes.js +455 -0
  115. package/dist/routes/session-control.routes.js.map +1 -0
  116. package/dist/routes/session-lifecycle.routes.d.ts +21 -0
  117. package/dist/routes/session-lifecycle.routes.d.ts.map +1 -0
  118. package/dist/routes/session-lifecycle.routes.js +256 -0
  119. package/dist/routes/session-lifecycle.routes.js.map +1 -0
  120. package/dist/routes/session-query.routes.d.ts +25 -0
  121. package/dist/routes/session-query.routes.d.ts.map +1 -0
  122. package/dist/routes/session-query.routes.js +363 -0
  123. package/dist/routes/session-query.routes.js.map +1 -0
  124. package/dist/routes/session-stream.routes.d.ts +21 -0
  125. package/dist/routes/session-stream.routes.d.ts.map +1 -0
  126. package/dist/routes/session-stream.routes.js +235 -0
  127. package/dist/routes/session-stream.routes.js.map +1 -0
  128. package/dist/routes/streaming.routes.d.ts +4 -0
  129. package/dist/routes/streaming.routes.d.ts.map +1 -0
  130. package/dist/routes/streaming.routes.js +33 -0
  131. package/dist/routes/streaming.routes.js.map +1 -0
  132. package/dist/routes/system.routes.d.ts +7 -0
  133. package/dist/routes/system.routes.d.ts.map +1 -0
  134. package/dist/routes/system.routes.js +214 -0
  135. package/dist/routes/system.routes.js.map +1 -0
  136. package/dist/routes/walkthrough.routes.d.ts +19 -0
  137. package/dist/routes/walkthrough.routes.d.ts.map +1 -0
  138. package/dist/routes/walkthrough.routes.js +688 -0
  139. package/dist/routes/walkthrough.routes.js.map +1 -0
  140. package/dist/routes/working-directories.routes.d.ts +4 -0
  141. package/dist/routes/working-directories.routes.d.ts.map +1 -0
  142. package/dist/routes/working-directories.routes.js +25 -0
  143. package/dist/routes/working-directories.routes.js.map +1 -0
  144. package/dist/server.d.ts +3 -0
  145. package/dist/server.d.ts.map +1 -0
  146. package/dist/server.js +34 -0
  147. package/dist/server.js.map +1 -0
  148. package/dist/services/ToolMetricsService.d.ts +53 -0
  149. package/dist/services/ToolMetricsService.d.ts.map +1 -0
  150. package/dist/services/ToolMetricsService.js +230 -0
  151. package/dist/services/ToolMetricsService.js.map +1 -0
  152. package/dist/services/claude-router-service.d.ts +19 -0
  153. package/dist/services/claude-router-service.d.ts.map +1 -0
  154. package/dist/services/claude-router-service.js +160 -0
  155. package/dist/services/claude-router-service.js.map +1 -0
  156. package/dist/services/commands-service.d.ts +20 -0
  157. package/dist/services/commands-service.d.ts.map +1 -0
  158. package/dist/services/commands-service.js +115 -0
  159. package/dist/services/commands-service.js.map +1 -0
  160. package/dist/services/connection-debug-logger.d.ts +85 -0
  161. package/dist/services/connection-debug-logger.d.ts.map +1 -0
  162. package/dist/services/connection-debug-logger.js +221 -0
  163. package/dist/services/connection-debug-logger.js.map +1 -0
  164. package/dist/services/debug-log.d.ts +6 -0
  165. package/dist/services/debug-log.d.ts.map +1 -0
  166. package/dist/services/debug-log.js +27 -0
  167. package/dist/services/debug-log.js.map +1 -0
  168. package/dist/services/gemini-service.d.ts +35 -0
  169. package/dist/services/gemini-service.d.ts.map +1 -0
  170. package/dist/services/gemini-service.js +256 -0
  171. package/dist/services/gemini-service.js.map +1 -0
  172. package/dist/services/infrastructure/config-service.d.ts +79 -0
  173. package/dist/services/infrastructure/config-service.d.ts.map +1 -0
  174. package/dist/services/infrastructure/config-service.js +431 -0
  175. package/dist/services/infrastructure/config-service.js.map +1 -0
  176. package/dist/services/infrastructure/cost-tracker.d.ts +112 -0
  177. package/dist/services/infrastructure/cost-tracker.d.ts.map +1 -0
  178. package/dist/services/infrastructure/cost-tracker.js +423 -0
  179. package/dist/services/infrastructure/cost-tracker.js.map +1 -0
  180. package/dist/services/infrastructure/file-system-service.d.ts +61 -0
  181. package/dist/services/infrastructure/file-system-service.d.ts.map +1 -0
  182. package/dist/services/infrastructure/file-system-service.js +348 -0
  183. package/dist/services/infrastructure/file-system-service.js.map +1 -0
  184. package/dist/services/infrastructure/log-formatter.d.ts +5 -0
  185. package/dist/services/infrastructure/log-formatter.d.ts.map +1 -0
  186. package/dist/services/infrastructure/log-formatter.js +77 -0
  187. package/dist/services/infrastructure/log-formatter.js.map +1 -0
  188. package/dist/services/infrastructure/log-stream-buffer.d.ts +11 -0
  189. package/dist/services/infrastructure/log-stream-buffer.d.ts.map +1 -0
  190. package/dist/services/infrastructure/log-stream-buffer.js +36 -0
  191. package/dist/services/infrastructure/log-stream-buffer.js.map +1 -0
  192. package/dist/services/infrastructure/logger.d.ts +71 -0
  193. package/dist/services/infrastructure/logger.d.ts.map +1 -0
  194. package/dist/services/infrastructure/logger.js +215 -0
  195. package/dist/services/infrastructure/logger.js.map +1 -0
  196. package/dist/services/infrastructure/service-registry.d.ts +86 -0
  197. package/dist/services/infrastructure/service-registry.d.ts.map +1 -0
  198. package/dist/services/infrastructure/service-registry.js +162 -0
  199. package/dist/services/infrastructure/service-registry.js.map +1 -0
  200. package/dist/services/infrastructure/stream-manager.d.ts +87 -0
  201. package/dist/services/infrastructure/stream-manager.d.ts.map +1 -0
  202. package/dist/services/infrastructure/stream-manager.js +436 -0
  203. package/dist/services/infrastructure/stream-manager.js.map +1 -0
  204. package/dist/services/infrastructure/timing.d.ts +72 -0
  205. package/dist/services/infrastructure/timing.d.ts.map +1 -0
  206. package/dist/services/infrastructure/timing.js +115 -0
  207. package/dist/services/infrastructure/timing.js.map +1 -0
  208. package/dist/services/insights/anthropic-service.d.ts +224 -0
  209. package/dist/services/insights/anthropic-service.d.ts.map +1 -0
  210. package/dist/services/insights/anthropic-service.js +1062 -0
  211. package/dist/services/insights/anthropic-service.js.map +1 -0
  212. package/dist/services/insights/insight-audit-repository.d.ts +119 -0
  213. package/dist/services/insights/insight-audit-repository.d.ts.map +1 -0
  214. package/dist/services/insights/insight-audit-repository.js +242 -0
  215. package/dist/services/insights/insight-audit-repository.js.map +1 -0
  216. package/dist/services/insights/insight-queue.d.ts +99 -0
  217. package/dist/services/insights/insight-queue.d.ts.map +1 -0
  218. package/dist/services/insights/insight-queue.js +277 -0
  219. package/dist/services/insights/insight-queue.js.map +1 -0
  220. package/dist/services/insights/insights-computer.d.ts +132 -0
  221. package/dist/services/insights/insights-computer.d.ts.map +1 -0
  222. package/dist/services/insights/insights-computer.js +936 -0
  223. package/dist/services/insights/insights-computer.js.map +1 -0
  224. package/dist/services/insights/insights-coordinator.d.ts +165 -0
  225. package/dist/services/insights/insights-coordinator.d.ts.map +1 -0
  226. package/dist/services/insights/insights-coordinator.js +1678 -0
  227. package/dist/services/insights/insights-coordinator.js.map +1 -0
  228. package/dist/services/insights/insights-event-log.d.ts +196 -0
  229. package/dist/services/insights/insights-event-log.d.ts.map +1 -0
  230. package/dist/services/insights/insights-event-log.js +319 -0
  231. package/dist/services/insights/insights-event-log.js.map +1 -0
  232. package/dist/services/lattice-service.d.ts +77 -0
  233. package/dist/services/lattice-service.d.ts.map +1 -0
  234. package/dist/services/lattice-service.js +195 -0
  235. package/dist/services/lattice-service.js.map +1 -0
  236. package/dist/services/license-service.d.ts +69 -0
  237. package/dist/services/license-service.d.ts.map +1 -0
  238. package/dist/services/license-service.js +330 -0
  239. package/dist/services/license-service.js.map +1 -0
  240. package/dist/services/mcp-config-generator.d.ts +32 -0
  241. package/dist/services/mcp-config-generator.d.ts.map +1 -0
  242. package/dist/services/mcp-config-generator.js +126 -0
  243. package/dist/services/mcp-config-generator.js.map +1 -0
  244. package/dist/services/message-filter.d.ts +22 -0
  245. package/dist/services/message-filter.d.ts.map +1 -0
  246. package/dist/services/message-filter.js +57 -0
  247. package/dist/services/message-filter.js.map +1 -0
  248. package/dist/services/notification-service.d.ts +45 -0
  249. package/dist/services/notification-service.d.ts.map +1 -0
  250. package/dist/services/notification-service.js +184 -0
  251. package/dist/services/notification-service.js.map +1 -0
  252. package/dist/services/pending-question-service.d.ts +97 -0
  253. package/dist/services/pending-question-service.d.ts.map +1 -0
  254. package/dist/services/pending-question-service.js +223 -0
  255. package/dist/services/pending-question-service.js.map +1 -0
  256. package/dist/services/permission-event-log.d.ts +136 -0
  257. package/dist/services/permission-event-log.d.ts.map +1 -0
  258. package/dist/services/permission-event-log.js +252 -0
  259. package/dist/services/permission-event-log.js.map +1 -0
  260. package/dist/services/permission-pattern-matcher.d.ts +82 -0
  261. package/dist/services/permission-pattern-matcher.d.ts.map +1 -0
  262. package/dist/services/permission-pattern-matcher.js +294 -0
  263. package/dist/services/permission-pattern-matcher.js.map +1 -0
  264. package/dist/services/permission-tracker.d.ts +67 -0
  265. package/dist/services/permission-tracker.d.ts.map +1 -0
  266. package/dist/services/permission-tracker.js +162 -0
  267. package/dist/services/permission-tracker.js.map +1 -0
  268. package/dist/services/process/claude-process-manager.d.ts +142 -0
  269. package/dist/services/process/claude-process-manager.d.ts.map +1 -0
  270. package/dist/services/process/claude-process-manager.js +1218 -0
  271. package/dist/services/process/claude-process-manager.js.map +1 -0
  272. package/dist/services/process/conversation-status-manager.d.ts +110 -0
  273. package/dist/services/process/conversation-status-manager.d.ts.map +1 -0
  274. package/dist/services/process/conversation-status-manager.js +349 -0
  275. package/dist/services/process/conversation-status-manager.js.map +1 -0
  276. package/dist/services/process/json-lines-parser.d.ts +19 -0
  277. package/dist/services/process/json-lines-parser.d.ts.map +1 -0
  278. package/dist/services/process/json-lines-parser.js +59 -0
  279. package/dist/services/process/json-lines-parser.js.map +1 -0
  280. package/dist/services/process/process-event-log.d.ts +263 -0
  281. package/dist/services/process/process-event-log.d.ts.map +1 -0
  282. package/dist/services/process/process-event-log.js +509 -0
  283. package/dist/services/process/process-event-log.js.map +1 -0
  284. package/dist/services/process/process-manager-factory.d.ts +109 -0
  285. package/dist/services/process/process-manager-factory.d.ts.map +1 -0
  286. package/dist/services/process/process-manager-factory.js +338 -0
  287. package/dist/services/process/process-manager-factory.js.map +1 -0
  288. package/dist/services/question-tracker.d.ts +51 -0
  289. package/dist/services/question-tracker.d.ts.map +1 -0
  290. package/dist/services/question-tracker.js +111 -0
  291. package/dist/services/question-tracker.js.map +1 -0
  292. package/dist/services/sessions/claude-history-reader.d.ts +139 -0
  293. package/dist/services/sessions/claude-history-reader.d.ts.map +1 -0
  294. package/dist/services/sessions/claude-history-reader.js +864 -0
  295. package/dist/services/sessions/claude-history-reader.js.map +1 -0
  296. package/dist/services/sessions/conversation-cache.d.ts +98 -0
  297. package/dist/services/sessions/conversation-cache.d.ts.map +1 -0
  298. package/dist/services/sessions/conversation-cache.js +329 -0
  299. package/dist/services/sessions/conversation-cache.js.map +1 -0
  300. package/dist/services/sessions/session-activity-watcher.d.ts +67 -0
  301. package/dist/services/sessions/session-activity-watcher.d.ts.map +1 -0
  302. package/dist/services/sessions/session-activity-watcher.js +236 -0
  303. package/dist/services/sessions/session-activity-watcher.js.map +1 -0
  304. package/dist/services/sessions/session-analysis-service.d.ts +72 -0
  305. package/dist/services/sessions/session-analysis-service.d.ts.map +1 -0
  306. package/dist/services/sessions/session-analysis-service.js +373 -0
  307. package/dist/services/sessions/session-analysis-service.js.map +1 -0
  308. package/dist/services/sessions/session-branch-service.d.ts +76 -0
  309. package/dist/services/sessions/session-branch-service.d.ts.map +1 -0
  310. package/dist/services/sessions/session-branch-service.js +355 -0
  311. package/dist/services/sessions/session-branch-service.js.map +1 -0
  312. package/dist/services/sessions/session-info-service.d.ts +455 -0
  313. package/dist/services/sessions/session-info-service.d.ts.map +1 -0
  314. package/dist/services/sessions/session-info-service.js +1640 -0
  315. package/dist/services/sessions/session-info-service.js.map +1 -0
  316. package/dist/services/sessions/session-marks-repository.d.ts +78 -0
  317. package/dist/services/sessions/session-marks-repository.d.ts.map +1 -0
  318. package/dist/services/sessions/session-marks-repository.js +263 -0
  319. package/dist/services/sessions/session-marks-repository.js.map +1 -0
  320. package/dist/services/sessions/session-marks-service.d.ts +137 -0
  321. package/dist/services/sessions/session-marks-service.d.ts.map +1 -0
  322. package/dist/services/sessions/session-marks-service.js +562 -0
  323. package/dist/services/sessions/session-marks-service.js.map +1 -0
  324. package/dist/services/sessions/session-review-service.d.ts +98 -0
  325. package/dist/services/sessions/session-review-service.d.ts.map +1 -0
  326. package/dist/services/sessions/session-review-service.js +629 -0
  327. package/dist/services/sessions/session-review-service.js.map +1 -0
  328. package/dist/services/sessions/turn-capture-service.d.ts +83 -0
  329. package/dist/services/sessions/turn-capture-service.d.ts.map +1 -0
  330. package/dist/services/sessions/turn-capture-service.js +477 -0
  331. package/dist/services/sessions/turn-capture-service.js.map +1 -0
  332. package/dist/services/sessions/turn-repository.d.ts +48 -0
  333. package/dist/services/sessions/turn-repository.d.ts.map +1 -0
  334. package/dist/services/sessions/turn-repository.js +103 -0
  335. package/dist/services/sessions/turn-repository.js.map +1 -0
  336. package/dist/services/walkthrough-service.d.ts +226 -0
  337. package/dist/services/walkthrough-service.d.ts.map +1 -0
  338. package/dist/services/walkthrough-service.js +1112 -0
  339. package/dist/services/walkthrough-service.js.map +1 -0
  340. package/dist/services/walkthrough-skill-prompt.d.ts +34 -0
  341. package/dist/services/walkthrough-skill-prompt.d.ts.map +1 -0
  342. package/dist/services/walkthrough-skill-prompt.js +313 -0
  343. package/dist/services/walkthrough-skill-prompt.js.map +1 -0
  344. package/dist/services/web-push-service.d.ts +48 -0
  345. package/dist/services/web-push-service.d.ts.map +1 -0
  346. package/dist/services/web-push-service.js +186 -0
  347. package/dist/services/web-push-service.js.map +1 -0
  348. package/dist/services/working-directories-service.d.ts +19 -0
  349. package/dist/services/working-directories-service.d.ts.map +1 -0
  350. package/dist/services/working-directories-service.js +103 -0
  351. package/dist/services/working-directories-service.js.map +1 -0
  352. package/dist/types/config.d.ts +122 -0
  353. package/dist/types/config.d.ts.map +1 -0
  354. package/dist/types/config.js +21 -0
  355. package/dist/types/config.js.map +1 -0
  356. package/dist/types/express.d.ts +5 -0
  357. package/dist/types/express.d.ts.map +1 -0
  358. package/dist/types/express.js +2 -0
  359. package/dist/types/express.js.map +1 -0
  360. package/dist/types/index.d.ts +400 -0
  361. package/dist/types/index.d.ts.map +1 -0
  362. package/dist/types/index.js +41 -0
  363. package/dist/types/index.js.map +1 -0
  364. package/dist/types/insights.d.ts +176 -0
  365. package/dist/types/insights.d.ts.map +1 -0
  366. package/dist/types/insights.js +23 -0
  367. package/dist/types/insights.js.map +1 -0
  368. package/dist/types/license.d.ts +70 -0
  369. package/dist/types/license.d.ts.map +1 -0
  370. package/dist/types/license.js +5 -0
  371. package/dist/types/license.js.map +1 -0
  372. package/dist/types/router-config.d.ts +13 -0
  373. package/dist/types/router-config.d.ts.map +1 -0
  374. package/dist/types/router-config.js +2 -0
  375. package/dist/types/router-config.js.map +1 -0
  376. package/dist/utils/constants.d.ts +26 -0
  377. package/dist/utils/constants.d.ts.map +1 -0
  378. package/dist/utils/constants.js +28 -0
  379. package/dist/utils/constants.js.map +1 -0
  380. package/dist/utils/machine-id.d.ts +7 -0
  381. package/dist/utils/machine-id.d.ts.map +1 -0
  382. package/dist/utils/machine-id.js +76 -0
  383. package/dist/utils/machine-id.js.map +1 -0
  384. package/dist/utils/server-startup.d.ts +11 -0
  385. package/dist/utils/server-startup.d.ts.map +1 -0
  386. package/dist/utils/server-startup.js +9 -0
  387. package/dist/utils/server-startup.js.map +1 -0
  388. package/dist/utils/update-check.d.ts +13 -0
  389. package/dist/utils/update-check.d.ts.map +1 -0
  390. package/dist/utils/update-check.js +90 -0
  391. package/dist/utils/update-check.js.map +1 -0
  392. package/dist/web/assets/ArchivedCardPrototype-S9ifiasa.js +5 -0
  393. package/dist/web/assets/BannerGallery-B__rJV6F.js +1 -0
  394. package/dist/web/assets/BannerPrototype-DBKP9Uiu.js +52 -0
  395. package/dist/web/assets/CodeHikeExperiment-B8jjWAFy.js +15 -0
  396. package/dist/web/assets/ContextTooltipVariations-DzklAFam.js +1 -0
  397. package/dist/web/assets/FontShowcasePrototype-KIMEWeP2.js +13 -0
  398. package/dist/web/assets/GeometricGallery-DddlWhHK.js +1 -0
  399. package/dist/web/assets/HistoryWalkthroughPrototype-DeniRRdw.js +18 -0
  400. package/dist/web/assets/InlineWalkthroughPrototype-Csd5r_Hk.js +1 -0
  401. package/dist/web/assets/MarkButtonPrototype-CxhxE0RP.js +1 -0
  402. package/dist/web/assets/MenuStylesPrototype-D7neA6YM.js +1 -0
  403. package/dist/web/assets/MomentCardVariations-2GT7GyFn.js +1 -0
  404. package/dist/web/assets/MomentHeaderVariations-DhGEw4XC.js +1 -0
  405. package/dist/web/assets/NarrativeWalkthroughDemo-B5C566fu.js +389 -0
  406. package/dist/web/assets/OutcomeVariations-BrZfsVcs.js +1 -0
  407. package/dist/web/assets/PermissionPatternPickerPrototype-CBOhe2Me.js +1 -0
  408. package/dist/web/assets/PermissionPrototype-BcF-a5an.js +1 -0
  409. package/dist/web/assets/PipelineGallery-BJhyM0rx.js +1 -0
  410. package/dist/web/assets/ScopeHeaderPrototype-GD1HNfaO.js +1 -0
  411. package/dist/web/assets/ScopeHeaderStylesPrototype-aa4uNJJ1.js +1 -0
  412. package/dist/web/assets/ScrollycodingPrototype-CKW1bf72.js +70 -0
  413. package/dist/web/assets/SectionHeaderVariations-DM8vUwfj.js +1 -0
  414. package/dist/web/assets/SemanticGallery-CtQEo7St.js +1 -0
  415. package/dist/web/assets/SessionCardPrototype-CgHCIMHe.js +1 -0
  416. package/dist/web/assets/SessionSidebarVariations-DMQL3Azj.js +3 -0
  417. package/dist/web/assets/SessionStartPrototype-Cwsv01Rr.js +1 -0
  418. package/dist/web/assets/SmartMenuPrototype-Br37Qbs_.js +1 -0
  419. package/dist/web/assets/StyleGallery-rZgrploB.js +1 -0
  420. package/dist/web/assets/TimelineCardPrototype-lzPc5mhe.js +19 -0
  421. package/dist/web/assets/ToolbarPrototype-Dm4BNZra.js +1 -0
  422. package/dist/web/assets/TooltipExperiment-Dy8QzTIP.js +13 -0
  423. package/dist/web/assets/WalkthroughCTAPrototype-uHoovujd.js +1 -0
  424. package/dist/web/assets/WalkthroughHeaderVariations-Do7Di1g1.js +1 -0
  425. package/dist/web/assets/WalkthroughShowcase-sGmRoPoM.js +112 -0
  426. package/dist/web/assets/arrow-right-D46Nx1mC.js +1 -0
  427. package/dist/web/assets/brain-BXIZKtOZ.js +1 -0
  428. package/dist/web/assets/grid-3x3-Cb81B62m.js +1 -0
  429. package/dist/web/assets/main-B1fyog77.js +321 -0
  430. package/dist/web/assets/main-C2PK2Klg.css +1 -0
  431. package/dist/web/assets/semantic-variations-Bd-W7ea2.js +1 -0
  432. package/dist/web/assets/target-Cf92wDTW.js +1 -0
  433. package/dist/web/favicon.png +0 -0
  434. package/dist/web/favicon.svg +22 -0
  435. package/dist/web/icon-192x192.png +0 -0
  436. package/dist/web/icon-512x512.png +0 -0
  437. package/dist/web/index.html +45 -0
  438. package/dist/web/manifest.json +61 -0
  439. package/package.json +192 -0
  440. package/scripts/postinstall.js +60 -0
@@ -0,0 +1,864 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { LatticeError } from '../../types/index.js';
5
+ import { createLogger } from '../infrastructure/logger.js';
6
+ import { SessionInfoService } from '../sessions/session-info-service.js';
7
+ import { ConversationCache } from '../sessions/conversation-cache.js';
8
+ import { ToolMetricsService } from '../ToolMetricsService.js';
9
+ import { MessageFilter } from '../message-filter.js';
10
+ /**
11
+ * Reads conversation history from Claude's local storage
12
+ */
13
+ export class ClaudeHistoryReader {
14
+ claudeHomePath;
15
+ logger;
16
+ sessionInfoService;
17
+ conversationCache;
18
+ toolMetricsService;
19
+ messageFilter;
20
+ constructor(sessionInfoService) {
21
+ this.claudeHomePath = path.join(os.homedir(), '.claude');
22
+ this.logger = createLogger('ClaudeHistoryReader');
23
+ this.sessionInfoService = sessionInfoService || SessionInfoService.getInstance();
24
+ this.conversationCache = new ConversationCache();
25
+ this.toolMetricsService = new ToolMetricsService();
26
+ this.messageFilter = new MessageFilter();
27
+ }
28
+ get homePath() {
29
+ return this.claudeHomePath;
30
+ }
31
+ /**
32
+ * Clear the conversation cache to force a refresh on next read
33
+ */
34
+ clearCache() {
35
+ this.conversationCache.clear();
36
+ }
37
+ /**
38
+ * List all conversations with optional filtering
39
+ */
40
+ async listConversations(filter) {
41
+ const timings = {};
42
+ const startTime = Date.now();
43
+ try {
44
+ // FAST PATH: For archived=true queries, use DB-only path (avoids parsing JSONL files)
45
+ // This is critical for performance - archived sessions can number in the thousands
46
+ if (filter?.archived === true && filter?.pinned === undefined) {
47
+ return this.listArchivedConversationsFast(filter, timings, startTime);
48
+ }
49
+ // SLOW PATH: Parse JSONL files for non-archived or mixed queries
50
+ // Fetch ALL session info first - needed to filter before expensive parsing
51
+ const sessionInfoStart = Date.now();
52
+ const allSessionInfo = await this.sessionInfoService.getAllSessionInfo();
53
+ timings.getAllSessionInfo = Date.now() - sessionInfoStart;
54
+ // Fetch ALL insights - needed for cached tool metrics (Phase 1 optimization)
55
+ const insightsStart = Date.now();
56
+ const allInsights = await this.sessionInfoService.getAllInsights();
57
+ timings.getAllInsights = Date.now() - insightsStart;
58
+ // Pre-filter session IDs based on archived/pinned filters to avoid parsing unwanted files
59
+ let allowedSessionIds = null;
60
+ if (filter?.archived !== undefined || filter?.pinned !== undefined) {
61
+ allowedSessionIds = new Set();
62
+ for (const [sessionId, info] of Object.entries(allSessionInfo)) {
63
+ // Apply archive filter
64
+ if (filter.archived !== undefined && info.archived !== filter.archived) {
65
+ continue;
66
+ }
67
+ // Apply pinned filter
68
+ if (filter.pinned !== undefined && info.pinned !== filter.pinned) {
69
+ continue;
70
+ }
71
+ allowedSessionIds.add(sessionId);
72
+ }
73
+ }
74
+ // Parse conversations (optionally filtered by sessionId)
75
+ const parseStart = Date.now();
76
+ const conversationChains = await this.parseAllConversations(allowedSessionIds);
77
+ timings.parseAllConversations = Date.now() - parseStart;
78
+ // Track sessions that need metrics backfilled (fire-and-forget after response)
79
+ const metricsToBackfill = [];
80
+ // Convert to ConversationSummary format and enhance with custom names
81
+ const allConversations = conversationChains.map((chain) => {
82
+ // Use bulk-fetched session info (O(1) lookup instead of async query)
83
+ // Default to archived: true to match syncMissingSessions behavior -
84
+ // sessions discovered outside Claudia start archived
85
+ const sessionInfo = allSessionInfo[chain.sessionId] || {
86
+ custom_name: '',
87
+ created_at: new Date().toISOString(),
88
+ updated_at: new Date().toISOString(),
89
+ version: 4,
90
+ pinned: false,
91
+ archived: true,
92
+ continuation_session_id: '',
93
+ initial_commit_head: '',
94
+ permission_mode: 'default'
95
+ };
96
+ // Use cached tool metrics if available (Phase 1 optimization)
97
+ const cachedInsights = allInsights.get(chain.sessionId);
98
+ let toolMetrics;
99
+ if (cachedInsights?.metrics_updated_at) {
100
+ // Use cached metrics - fast path
101
+ toolMetrics = {
102
+ linesAdded: cachedInsights.lines_added || 0,
103
+ linesRemoved: cachedInsights.lines_removed || 0,
104
+ editCount: cachedInsights.edit_count || 0,
105
+ writeCount: cachedInsights.write_count || 0
106
+ };
107
+ }
108
+ else {
109
+ // Calculate metrics and queue for backfill - slow path (only on first access)
110
+ toolMetrics = this.toolMetricsService.calculateMetricsFromMessages(chain.messages);
111
+ metricsToBackfill.push({ sessionId: chain.sessionId, metrics: toolMetrics, messageCount: chain.messages.length });
112
+ }
113
+ return {
114
+ sessionId: chain.sessionId,
115
+ projectPath: chain.projectPath,
116
+ summary: chain.summary,
117
+ sessionInfo: sessionInfo,
118
+ createdAt: chain.createdAt,
119
+ updatedAt: chain.updatedAt,
120
+ messageCount: chain.messages.length,
121
+ totalDuration: chain.totalDuration,
122
+ model: chain.model,
123
+ status: 'completed', // Default status, will be updated by server
124
+ toolMetrics: toolMetrics
125
+ };
126
+ });
127
+ // Backfill metrics in background (don't block response)
128
+ if (metricsToBackfill.length > 0) {
129
+ this.logger.info('Backfilling tool metrics for sessions without cache', {
130
+ count: metricsToBackfill.length
131
+ });
132
+ // Fire and forget - update DB in background
133
+ Promise.all(metricsToBackfill.map(({ sessionId, metrics, messageCount }) => this.sessionInfoService.updateToolMetrics(sessionId, metrics, messageCount).catch(() => { }))).catch(() => { });
134
+ }
135
+ // Apply filters and pagination
136
+ const filterStart = Date.now();
137
+ const filtered = this.applyFilters(allConversations, filter);
138
+ const paginated = this.applyPagination(filtered, filter);
139
+ timings.filterAndPaginate = Date.now() - filterStart;
140
+ timings.total = Date.now() - startTime;
141
+ this.logger.debug('listConversations breakdown', {
142
+ timingsMs: timings,
143
+ sessionInfoCount: Object.keys(allSessionInfo).length,
144
+ insightsCount: allInsights.size,
145
+ conversationChainsCount: conversationChains.length,
146
+ filteredCount: filtered.length,
147
+ paginatedCount: paginated.length,
148
+ allowedSessionIds: allowedSessionIds?.size ?? 'all',
149
+ });
150
+ return {
151
+ conversations: paginated,
152
+ total: filtered.length
153
+ };
154
+ }
155
+ catch (error) {
156
+ throw new LatticeError('HISTORY_READ_FAILED', `Failed to read conversation history: ${error}`, 500);
157
+ }
158
+ }
159
+ /**
160
+ * Fast path for listing archived sessions - queries DB directly without parsing JSONL files.
161
+ * This is ~100x faster than the file-parsing path for large archives.
162
+ */
163
+ async listArchivedConversationsFast(filter, timings, startTime) {
164
+ const dbStart = Date.now();
165
+ // Get total count for pagination
166
+ const total = this.sessionInfoService.getArchivedSessionCount();
167
+ // Get paginated archived sessions with insights from DB
168
+ const limit = filter.limit || 20;
169
+ const offset = filter.offset || 0;
170
+ const dbResults = this.sessionInfoService.getArchivedSessionsWithInsights(limit, offset);
171
+ timings.dbQuery = Date.now() - dbStart;
172
+ // Convert to ConversationSummary format
173
+ const conversations = dbResults.map(({ sessionId, sessionInfo, insights }) => {
174
+ // Build tool metrics from insights if available
175
+ const toolMetrics = insights?.metrics_updated_at ? {
176
+ linesAdded: insights.lines_added || 0,
177
+ linesRemoved: insights.lines_removed || 0,
178
+ editCount: insights.edit_count || 0,
179
+ writeCount: insights.write_count || 0
180
+ } : {
181
+ linesAdded: 0,
182
+ linesRemoved: 0,
183
+ editCount: 0,
184
+ writeCount: 0
185
+ };
186
+ return {
187
+ sessionId,
188
+ projectPath: '', // Not available without parsing JSONL - acceptable for archive view
189
+ summary: insights?.purpose || sessionInfo.custom_name || 'Archived session',
190
+ sessionInfo,
191
+ createdAt: sessionInfo.created_at,
192
+ updatedAt: sessionInfo.updated_at,
193
+ messageCount: insights?.message_count || 0,
194
+ totalDuration: 0, // Not available without parsing JSONL
195
+ model: 'Unknown', // Not available without parsing JSONL
196
+ status: 'completed',
197
+ toolMetrics
198
+ };
199
+ });
200
+ timings.total = Date.now() - startTime;
201
+ this.logger.debug('listArchivedConversationsFast', {
202
+ timingsMs: timings,
203
+ total,
204
+ returnedCount: conversations.length,
205
+ limit,
206
+ offset
207
+ });
208
+ return { conversations, total };
209
+ }
210
+ /**
211
+ * Get the file path for a session's JSONL file.
212
+ * Public wrapper around findSessionFile.
213
+ */
214
+ async getSessionFilePath(sessionId) {
215
+ return this.findSessionFile(sessionId);
216
+ }
217
+ /**
218
+ * Find the JSONL file for a session by checking each project folder
219
+ * Returns the file path if found, null otherwise
220
+ */
221
+ async findSessionFile(sessionId) {
222
+ const projectsPath = path.join(this.claudeHomePath, 'projects');
223
+ try {
224
+ const projects = await this.readDirectory(projectsPath);
225
+ for (const project of projects) {
226
+ const projectPath = path.join(projectsPath, project);
227
+ const stats = await fs.stat(projectPath);
228
+ if (!stats.isDirectory())
229
+ continue;
230
+ const sessionFile = path.join(projectPath, `${sessionId}.jsonl`);
231
+ try {
232
+ await fs.access(sessionFile);
233
+ return sessionFile;
234
+ }
235
+ catch {
236
+ // File doesn't exist in this project, continue searching
237
+ }
238
+ }
239
+ return null;
240
+ }
241
+ catch (error) {
242
+ this.logger.error('Error searching for session file', error, { sessionId });
243
+ return null;
244
+ }
245
+ }
246
+ /**
247
+ * Get file modification times for multiple sessions
248
+ * Used for lightweight activity detection without reading file contents
249
+ */
250
+ async getSessionFileMtimes(sessionIds) {
251
+ const results = new Map();
252
+ await Promise.all(sessionIds.map(async (sessionId) => {
253
+ const filePath = await this.findSessionFile(sessionId);
254
+ if (filePath) {
255
+ try {
256
+ const stats = await fs.stat(filePath);
257
+ results.set(sessionId, stats.mtimeMs);
258
+ }
259
+ catch {
260
+ // File not accessible, skip
261
+ }
262
+ }
263
+ }));
264
+ return results;
265
+ }
266
+ /**
267
+ * Get sessions that are "recently active" based on file modification time.
268
+ * A session is considered active if its JSONL file was modified within the threshold.
269
+ * This is more reliable than fuser since Claude writes frequently during activity.
270
+ *
271
+ * @param sessionIds - Session IDs to check
272
+ * @param thresholdMs - How recent the mtime must be (default: 60 seconds)
273
+ * @returns Set of session IDs that are recently active
274
+ */
275
+ async getRecentlyActiveSessionsByMtime(sessionIds, thresholdMs = 60000) {
276
+ const activeSet = new Set();
277
+ const now = Date.now();
278
+ const projectsPath = path.join(this.claudeHomePath, 'projects');
279
+ // Build a Set for O(1) lookup
280
+ const sessionIdSet = new Set(sessionIds);
281
+ try {
282
+ // Get all project directories
283
+ const projects = await this.readDirectory(projectsPath);
284
+ // Check all JSONL files in parallel across all projects
285
+ await Promise.all(projects.map(async (project) => {
286
+ const projectDir = path.join(projectsPath, project);
287
+ try {
288
+ const stats = await fs.stat(projectDir);
289
+ if (!stats.isDirectory())
290
+ return;
291
+ const files = await this.readDirectory(projectDir);
292
+ await Promise.all(files
293
+ .filter(f => f.endsWith('.jsonl'))
294
+ .map(async (file) => {
295
+ const sessionId = file.replace('.jsonl', '');
296
+ if (!sessionIdSet.has(sessionId))
297
+ return;
298
+ try {
299
+ const filePath = path.join(projectDir, file);
300
+ const fileStats = await fs.stat(filePath);
301
+ const ageMs = now - fileStats.mtimeMs;
302
+ if (ageMs < thresholdMs) {
303
+ activeSet.add(sessionId);
304
+ this.logger.debug('Session detected as active by mtime', {
305
+ sessionId: sessionId.slice(0, 8),
306
+ ageSeconds: Math.round(ageMs / 1000)
307
+ });
308
+ }
309
+ }
310
+ catch {
311
+ // File not accessible, skip
312
+ }
313
+ }));
314
+ }
315
+ catch {
316
+ // Project dir not accessible, skip
317
+ }
318
+ }));
319
+ return activeSet;
320
+ }
321
+ catch (error) {
322
+ this.logger.error('Error checking session mtimes', error);
323
+ return activeSet;
324
+ }
325
+ }
326
+ /**
327
+ * Fetch conversation with metadata in a single fast operation
328
+ * Uses direct file lookup instead of parsing all conversations
329
+ */
330
+ async fetchConversationDirect(sessionId) {
331
+ const startTime = Date.now();
332
+ try {
333
+ // Find the session file directly
334
+ const filePath = await this.findSessionFile(sessionId);
335
+ if (!filePath) {
336
+ throw new LatticeError('CONVERSATION_NOT_FOUND', `Conversation ${sessionId} not found`, 404);
337
+ }
338
+ // Parse just this one file
339
+ const entries = await this.parseJsonlFile(filePath);
340
+ // Extract project path from file path
341
+ const projectsPath = path.join(this.claudeHomePath, 'projects');
342
+ const relativePath = filePath.replace(projectsPath + path.sep, '');
343
+ const projectName = relativePath.split(path.sep)[0];
344
+ const projectPath = this.decodeProjectName(projectName);
345
+ // Get summary from global summaries.json
346
+ const summary = await this.getSummaryForSession(sessionId);
347
+ // Build conversation chain from entries
348
+ const summaryMap = new Map();
349
+ if (summary)
350
+ summaryMap.set(sessionId, summary);
351
+ const chain = this.buildConversationChain(sessionId, entries.map(e => ({ ...e, sourceProject: projectPath })), summaryMap);
352
+ if (!chain) {
353
+ throw new LatticeError('CONVERSATION_NOT_FOUND', `Failed to build conversation ${sessionId}`, 404);
354
+ }
355
+ const elapsed = Date.now() - startTime;
356
+ this.logger.debug('Direct conversation fetch completed', {
357
+ sessionId,
358
+ elapsedMs: elapsed,
359
+ messageCount: chain.messages.length
360
+ });
361
+ return {
362
+ messages: this.messageFilter.filterMessages(chain.messages),
363
+ metadata: {
364
+ summary: chain.summary,
365
+ projectPath: chain.projectPath,
366
+ model: chain.model,
367
+ totalDuration: chain.totalDuration
368
+ }
369
+ };
370
+ }
371
+ catch (error) {
372
+ if (error instanceof LatticeError)
373
+ throw error;
374
+ throw new LatticeError('CONVERSATION_READ_FAILED', `Failed to read conversation: ${error}`, 500);
375
+ }
376
+ }
377
+ /**
378
+ * Get summary for a session from summaries.json
379
+ */
380
+ async getSummaryForSession(sessionId) {
381
+ try {
382
+ const summariesPath = path.join(this.claudeHomePath, 'summaries.json');
383
+ const content = await fs.readFile(summariesPath, 'utf-8');
384
+ const summaries = JSON.parse(content);
385
+ return summaries[sessionId] || null;
386
+ }
387
+ catch {
388
+ return null;
389
+ }
390
+ }
391
+ /**
392
+ * Decode project folder name to actual path
393
+ */
394
+ decodeProjectName(projectName) {
395
+ // Project folders use dashes instead of slashes
396
+ // e.g., "-home-jason-cui-custom" -> "/home/jason/cui-custom"
397
+ return projectName.replace(/^-/, '/').replace(/-/g, '/');
398
+ }
399
+ /**
400
+ * Get the working directory for a specific conversation session
401
+ * Reads the cwd from the session's JSONL file to get the accurate path
402
+ * (folder name decoding is lossy for paths containing hyphens)
403
+ */
404
+ async getConversationWorkingDirectory(sessionId) {
405
+ const startTime = Date.now();
406
+ try {
407
+ const filePath = await this.findSessionFile(sessionId);
408
+ if (!filePath) {
409
+ this.logger.warn('Session file not found', { sessionId });
410
+ return null;
411
+ }
412
+ // Read the first entry from the JSONL file to get the actual cwd
413
+ // This is more reliable than decoding the folder name, which is lossy
414
+ // (e.g., "slingshot-ai" folder becomes "slingshot/ai" when decoded)
415
+ const entries = await this.parseJsonlFile(filePath);
416
+ // Find the first entry with a cwd field
417
+ const entryWithCwd = entries.find(e => e.cwd);
418
+ if (entryWithCwd?.cwd) {
419
+ this.logger.debug('Found working directory from JSONL cwd field', {
420
+ sessionId,
421
+ workingDirectory: entryWithCwd.cwd,
422
+ elapsedMs: Date.now() - startTime
423
+ });
424
+ return entryWithCwd.cwd;
425
+ }
426
+ // Fallback to folder name decoding if no cwd field found
427
+ // (older sessions may not have cwd in the JSONL)
428
+ const projectsPath = path.join(this.claudeHomePath, 'projects');
429
+ const relativePath = filePath.replace(projectsPath + path.sep, '');
430
+ const projectName = relativePath.split(path.sep)[0];
431
+ const projectPath = this.decodeProjectName(projectName);
432
+ this.logger.debug('Found working directory from folder name (fallback)', {
433
+ sessionId,
434
+ workingDirectory: projectPath,
435
+ elapsedMs: Date.now() - startTime
436
+ });
437
+ return projectPath;
438
+ }
439
+ catch (error) {
440
+ this.logger.error('Error getting working directory for conversation', error, { sessionId });
441
+ return null;
442
+ }
443
+ }
444
+ /**
445
+ * Get file modification times for JSONL files
446
+ * @param allowedSessionIds Optional filter - if provided, only stat files for these sessions
447
+ */
448
+ async getFileModificationTimes(allowedSessionIds) {
449
+ const modTimes = new Map();
450
+ const projectsPath = path.join(this.claudeHomePath, 'projects');
451
+ this.logger.debug('Getting file modification times', {
452
+ projectsPath,
453
+ filtered: !!allowedSessionIds,
454
+ allowedCount: allowedSessionIds?.size
455
+ });
456
+ try {
457
+ const projects = await this.readDirectory(projectsPath);
458
+ this.logger.debug('Found projects', { projectCount: projects.length });
459
+ for (const project of projects) {
460
+ const projectPath = path.join(projectsPath, project);
461
+ const stats = await fs.stat(projectPath);
462
+ if (!stats.isDirectory())
463
+ continue;
464
+ const files = await this.readDirectory(projectPath);
465
+ const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
466
+ for (const file of jsonlFiles) {
467
+ // Skip files not in allowed list (optimization: avoid stat calls)
468
+ if (allowedSessionIds) {
469
+ const sessionId = file.replace('.jsonl', '');
470
+ if (!allowedSessionIds.has(sessionId))
471
+ continue;
472
+ }
473
+ const filePath = path.join(projectPath, file);
474
+ try {
475
+ const fileStats = await fs.stat(filePath);
476
+ modTimes.set(filePath, fileStats.mtimeMs);
477
+ }
478
+ catch (error) {
479
+ this.logger.warn('Failed to stat file', { filePath, error });
480
+ }
481
+ }
482
+ }
483
+ this.logger.debug('File modification times collection complete', {
484
+ totalFiles: modTimes.size,
485
+ projects: projects.length
486
+ });
487
+ }
488
+ catch (error) {
489
+ this.logger.error('Error getting file modification times', error);
490
+ }
491
+ return modTimes;
492
+ }
493
+ /**
494
+ * Extract source project name from file path
495
+ */
496
+ extractSourceProject(filePath) {
497
+ const projectsPath = path.join(this.claudeHomePath, 'projects');
498
+ const relativePath = path.relative(projectsPath, filePath);
499
+ const segments = relativePath.split(path.sep);
500
+ return segments[0]; // First segment is the project directory name
501
+ }
502
+ /**
503
+ * Process all entries into conversation chains (the cheap in-memory operations)
504
+ */
505
+ processAllEntries(allEntries) {
506
+ const startTime = Date.now();
507
+ this.logger.debug('Processing all entries into conversations', {
508
+ totalEntries: allEntries.length
509
+ });
510
+ // Group entries by sessionId
511
+ const sessionGroups = this.groupEntriesBySession(allEntries);
512
+ this.logger.debug('Entries grouped by session', {
513
+ sessionCount: sessionGroups.size,
514
+ totalEntries: allEntries.length
515
+ });
516
+ // Process summaries
517
+ const summaries = this.processSummaries(allEntries);
518
+ this.logger.debug('Summaries processed', {
519
+ summaryCount: summaries.size
520
+ });
521
+ // Build conversation chains
522
+ const conversationChains = [];
523
+ for (const [sessionId, entries] of sessionGroups) {
524
+ const chain = this.buildConversationChain(sessionId, entries, summaries);
525
+ if (chain) {
526
+ conversationChains.push(chain);
527
+ }
528
+ }
529
+ const totalElapsed = Date.now() - startTime;
530
+ this.logger.debug('Entry processing complete', {
531
+ conversationCount: conversationChains.length,
532
+ totalElapsedMs: totalElapsed,
533
+ avgTimePerConversation: conversationChains.length > 0 ? totalElapsed / conversationChains.length : 0
534
+ });
535
+ return conversationChains;
536
+ }
537
+ /**
538
+ * Parse all conversations from all JSONL files with file-level caching and concurrency protection
539
+ * @param allowedSessionIds Optional set of session IDs to parse. If provided, only these sessions will be parsed.
540
+ */
541
+ async parseAllConversations(allowedSessionIds) {
542
+ const startTime = Date.now();
543
+ const timings = {};
544
+ this.logger.debug('Starting parseAllConversations with file-level caching', {
545
+ filtered: allowedSessionIds !== null && allowedSessionIds !== undefined,
546
+ allowedCount: allowedSessionIds?.size
547
+ });
548
+ // Get current file modification times (filtered if allowedSessionIds provided)
549
+ const modTimeStart = Date.now();
550
+ const filteredModTimes = await this.getFileModificationTimes(allowedSessionIds);
551
+ timings.getFileModificationTimes = Date.now() - modTimeStart;
552
+ // Use the new file-level cache interface
553
+ const cacheStart = Date.now();
554
+ const conversations = await this.conversationCache.getOrParseConversations(filteredModTimes, (filePath) => this.parseJsonlFile(filePath), // Parse single file
555
+ (filePath) => this.extractSourceProject(filePath), // Get source project
556
+ (allEntries) => this.processAllEntries(allEntries) // Process entries
557
+ );
558
+ timings.getOrParseConversations = Date.now() - cacheStart;
559
+ timings.total = Date.now() - startTime;
560
+ this.logger.debug('parseAllConversations breakdown', {
561
+ timingsMs: timings,
562
+ filteredModTimesCount: filteredModTimes.size,
563
+ conversationCount: conversations.length,
564
+ });
565
+ return conversations;
566
+ }
567
+ /**
568
+ * Parse a single JSONL file and return all valid entries
569
+ */
570
+ async parseJsonlFile(filePath) {
571
+ try {
572
+ const content = await fs.readFile(filePath, 'utf-8');
573
+ const lines = content.split('\n').filter(line => line.trim());
574
+ const entries = [];
575
+ for (const line of lines) {
576
+ try {
577
+ const entry = JSON.parse(line);
578
+ entries.push(entry);
579
+ }
580
+ catch (parseError) {
581
+ this.logger.warn('Failed to parse line from JSONL file', {
582
+ error: parseError,
583
+ filePath,
584
+ line: line.substring(0, 100)
585
+ });
586
+ }
587
+ }
588
+ return entries;
589
+ }
590
+ catch (error) {
591
+ this.logger.error('Failed to read JSONL file', error, { filePath });
592
+ return [];
593
+ }
594
+ }
595
+ /**
596
+ * Group entries by sessionId
597
+ */
598
+ groupEntriesBySession(entries) {
599
+ const sessionGroups = new Map();
600
+ for (const entry of entries) {
601
+ // Only group user and assistant messages
602
+ if ((entry.type === 'user' || entry.type === 'assistant') && entry.sessionId) {
603
+ if (!sessionGroups.has(entry.sessionId)) {
604
+ sessionGroups.set(entry.sessionId, []);
605
+ }
606
+ sessionGroups.get(entry.sessionId).push(entry);
607
+ }
608
+ }
609
+ return sessionGroups;
610
+ }
611
+ /**
612
+ * Process summary entries and create leafUuid mapping
613
+ */
614
+ processSummaries(entries) {
615
+ const summaries = new Map();
616
+ for (const entry of entries) {
617
+ if (entry.type === 'summary' && entry.leafUuid && entry.summary) {
618
+ summaries.set(entry.leafUuid, entry.summary);
619
+ }
620
+ }
621
+ return summaries;
622
+ }
623
+ async readDirectory(dirPath) {
624
+ try {
625
+ return await fs.readdir(dirPath);
626
+ }
627
+ catch (error) {
628
+ if (error.code === 'ENOENT') {
629
+ return [];
630
+ }
631
+ throw error;
632
+ }
633
+ }
634
+ /**
635
+ * Build a conversation chain from session entries
636
+ */
637
+ buildConversationChain(sessionId, entries, summaries) {
638
+ try {
639
+ // Convert entries to ConversationMessage format
640
+ const messages = entries.map(entry => this.parseMessage(entry));
641
+ // Build message chain using parentUuid/uuid relationships
642
+ const orderedMessages = this.buildMessageChain(messages);
643
+ if (orderedMessages.length === 0) {
644
+ return null;
645
+ }
646
+ // Apply message filter
647
+ const filteredMessages = this.messageFilter.filterMessages(orderedMessages);
648
+ // Check if we have any messages left after filtering
649
+ if (filteredMessages.length === 0) {
650
+ return null;
651
+ }
652
+ // Filter out warmup/sidechain-only sessions
653
+ // These are subagent sessions that exist only to prime the context cache
654
+ const firstUserMessage = filteredMessages.find(msg => msg.type === 'user');
655
+ let isWarmupSession = false;
656
+ if (firstUserMessage?.message) {
657
+ const msgContent = 'content' in firstUserMessage.message
658
+ ? firstUserMessage.message.content
659
+ : null;
660
+ if (msgContent) {
661
+ isWarmupSession = typeof msgContent === 'string'
662
+ ? msgContent.trim() === 'Warmup'
663
+ : Array.isArray(msgContent) &&
664
+ msgContent.length === 1 &&
665
+ msgContent[0].type === 'text' &&
666
+ 'text' in msgContent[0] &&
667
+ msgContent[0].text?.trim() === 'Warmup';
668
+ }
669
+ }
670
+ // Skip sessions that are pure warmup sessions (sidechain with just "Warmup" prompt)
671
+ if (isWarmupSession && orderedMessages[0]?.isSidechain) {
672
+ return null;
673
+ }
674
+ // Determine project path - use original first message for cwd before filtering
675
+ const firstMessage = orderedMessages[0];
676
+ let projectPath = '';
677
+ if (firstMessage.cwd) {
678
+ projectPath = firstMessage.cwd;
679
+ }
680
+ else {
681
+ // Fallback to decoding directory name from source project
682
+ const sourceProject = entries[0].sourceProject;
683
+ projectPath = this.decodeProjectPath(sourceProject);
684
+ }
685
+ // Determine conversation summary
686
+ const summary = this.determineConversationSummary(filteredMessages, summaries);
687
+ // Calculate metadata from filtered messages
688
+ const totalDuration = filteredMessages.reduce((sum, msg) => sum + (msg.durationMs || 0), 0);
689
+ const model = this.extractModel(filteredMessages);
690
+ // Get timestamps from filtered messages
691
+ const timestamps = filteredMessages
692
+ .map(msg => msg.timestamp)
693
+ .filter(ts => ts)
694
+ .sort();
695
+ const createdAt = timestamps[0] || new Date().toISOString();
696
+ const updatedAt = timestamps[timestamps.length - 1] || createdAt;
697
+ return {
698
+ sessionId,
699
+ messages: filteredMessages,
700
+ projectPath,
701
+ summary,
702
+ createdAt,
703
+ updatedAt,
704
+ totalDuration,
705
+ model
706
+ };
707
+ }
708
+ catch (error) {
709
+ this.logger.error('Error building conversation chain', error, { sessionId });
710
+ return null;
711
+ }
712
+ }
713
+ /**
714
+ * Build ordered message chain using parentUuid relationships
715
+ */
716
+ buildMessageChain(messages) {
717
+ // Create uuid to message mapping
718
+ const messageMap = new Map();
719
+ messages.forEach(msg => messageMap.set(msg.uuid, msg));
720
+ // Find head message (parentUuid is null)
721
+ // Prefer non-sidechain roots - sidechain entries (subagent tasks) have their own
722
+ // separate chains but share the same sessionId
723
+ const rootMessages = messages.filter(msg => !msg.parentUuid);
724
+ const mainRoot = rootMessages.find(msg => !msg.isSidechain);
725
+ const headMessage = mainRoot || rootMessages[0];
726
+ if (!headMessage) {
727
+ // If no head found, return messages sorted by timestamp
728
+ return messages.sort((a, b) => new Date(a.timestamp || '').getTime() - new Date(b.timestamp || '').getTime());
729
+ }
730
+ // Build chain from head
731
+ const orderedMessages = [];
732
+ const visited = new Set();
733
+ const traverse = (currentMessage) => {
734
+ if (visited.has(currentMessage.uuid)) {
735
+ return; // Avoid cycles
736
+ }
737
+ visited.add(currentMessage.uuid);
738
+ orderedMessages.push(currentMessage);
739
+ // Find children (messages with this message as parent)
740
+ const children = messages.filter(msg => msg.parentUuid === currentMessage.uuid);
741
+ // Sort children by timestamp to maintain order
742
+ children.sort((a, b) => new Date(a.timestamp || '').getTime() - new Date(b.timestamp || '').getTime());
743
+ children.forEach(child => traverse(child));
744
+ };
745
+ traverse(headMessage);
746
+ // Add any orphaned messages at the end
747
+ const orphanedMessages = messages.filter(msg => !visited.has(msg.uuid));
748
+ orderedMessages.push(...orphanedMessages.sort((a, b) => new Date(a.timestamp || '').getTime() - new Date(b.timestamp || '').getTime()));
749
+ return orderedMessages;
750
+ }
751
+ /**
752
+ * Determine conversation summary from messages and summary map
753
+ */
754
+ determineConversationSummary(messages, summaries) {
755
+ // Walk through messages from latest to earliest to find last available summary
756
+ for (let i = messages.length - 1; i >= 0; i--) {
757
+ const message = messages[i];
758
+ if (summaries.has(message.uuid)) {
759
+ return summaries.get(message.uuid);
760
+ }
761
+ }
762
+ // Fallback to first user message content
763
+ const firstUserMessage = messages.find(msg => msg.type === 'user');
764
+ if (firstUserMessage && firstUserMessage.message) {
765
+ const content = this.extractMessageContent(firstUserMessage.message);
766
+ return content.length > 100 ? content.substring(0, 100) + '...' : content;
767
+ }
768
+ return 'No summary available';
769
+ }
770
+ /**
771
+ * Extract text content from message object
772
+ */
773
+ extractMessageContent(message) {
774
+ if (typeof message === 'string') {
775
+ return message;
776
+ }
777
+ if (message.content) {
778
+ if (typeof message.content === 'string') {
779
+ return message.content;
780
+ }
781
+ if (Array.isArray(message.content)) {
782
+ // Find first text content block
783
+ const textBlock = message.content.find((block) => block.type === 'text');
784
+ return textBlock && 'text' in textBlock ? textBlock.text : '';
785
+ }
786
+ }
787
+ return 'No content available';
788
+ }
789
+ /**
790
+ * Extract model information from messages
791
+ */
792
+ extractModel(messages) {
793
+ for (const message of messages) {
794
+ if (message.message && typeof message.message === 'object') {
795
+ const messageObj = message.message;
796
+ if (messageObj.model) {
797
+ return messageObj.model;
798
+ }
799
+ }
800
+ }
801
+ return 'Unknown';
802
+ }
803
+ parseMessage(entry) {
804
+ return {
805
+ uuid: entry.uuid || '',
806
+ type: entry.type,
807
+ message: entry.message, // Non-null assertion since ConversationMessage requires it
808
+ timestamp: entry.timestamp || '',
809
+ sessionId: entry.sessionId || '',
810
+ parentUuid: entry.parentUuid,
811
+ isSidechain: entry.isSidechain,
812
+ userType: entry.userType,
813
+ cwd: entry.cwd,
814
+ version: entry.version,
815
+ durationMs: entry.durationMs
816
+ };
817
+ }
818
+ applyFilters(conversations, filter) {
819
+ if (!filter)
820
+ return conversations;
821
+ let filtered = [...conversations];
822
+ // Filter by project path
823
+ if (filter.projectPath) {
824
+ filtered = filtered.filter(c => c.projectPath === filter.projectPath);
825
+ }
826
+ // Filter by continuation session
827
+ if (filter.hasContinuation !== undefined) {
828
+ filtered = filtered.filter(c => {
829
+ const hasContinuation = c.sessionInfo.continuation_session_id !== '';
830
+ return filter.hasContinuation ? hasContinuation : !hasContinuation;
831
+ });
832
+ }
833
+ // Filter by archived status
834
+ if (filter.archived !== undefined) {
835
+ filtered = filtered.filter(c => c.sessionInfo.archived === filter.archived);
836
+ }
837
+ // Filter by pinned status
838
+ if (filter.pinned !== undefined) {
839
+ filtered = filtered.filter(c => c.sessionInfo.pinned === filter.pinned);
840
+ }
841
+ // Sort
842
+ if (filter.sortBy) {
843
+ filtered.sort((a, b) => {
844
+ const field = filter.sortBy === 'created' ? 'createdAt' : 'updatedAt';
845
+ const aVal = new Date(a[field]).getTime();
846
+ const bVal = new Date(b[field]).getTime();
847
+ return filter.order === 'desc' ? bVal - aVal : aVal - bVal;
848
+ });
849
+ }
850
+ return filtered;
851
+ }
852
+ applyPagination(conversations, filter) {
853
+ if (!filter)
854
+ return conversations;
855
+ const limit = filter.limit || 20;
856
+ const offset = filter.offset || 0;
857
+ return conversations.slice(offset, offset + limit);
858
+ }
859
+ decodeProjectPath(encoded) {
860
+ // Claude encodes directory paths by replacing '/' with '-'
861
+ return encoded.replace(/-/g, '/');
862
+ }
863
+ }
864
+ //# sourceMappingURL=claude-history-reader.js.map