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,1218 @@
1
+ import * as pty from 'node-pty';
2
+ import { spawn } from 'child_process';
3
+ import { LatticeError, asClaudeSessionId, asStreamingId } from '../../types/index.js';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { EventEmitter } from 'events';
6
+ import { existsSync, readFileSync } from 'fs';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname } from 'path';
9
+ import os from 'os';
10
+ import { JsonLinesParser } from './json-lines-parser.js';
11
+ import { createLogger } from '../infrastructure/logger.js';
12
+ import path from 'path';
13
+ import { getProcessEventLog } from './process-event-log.js';
14
+ /**
15
+ * Expand tilde (~) in paths to the user's home directory.
16
+ * Node.js spawn doesn't expand ~ like shells do, causing ENOENT errors.
17
+ */
18
+ function expandTilde(filePath) {
19
+ if (filePath === '~' || filePath.startsWith('~/')) {
20
+ return path.join(os.homedir(), filePath.slice(1));
21
+ }
22
+ return filePath;
23
+ }
24
+ // Get the directory of this module
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = dirname(__filename);
27
+ /**
28
+ * Manages Claude CLI processes and their lifecycle
29
+ */
30
+ export class ClaudeProcessManager extends EventEmitter {
31
+ processes = new Map();
32
+ outputBuffers = new Map();
33
+ timeouts = new Map();
34
+ conversationConfigs = new Map();
35
+ sessionIds = new Map(); // streamingId -> claude session_id
36
+ claudeExecutablePath;
37
+ logger;
38
+ envOverrides;
39
+ historyReader;
40
+ mcpConfigPath;
41
+ statusTracker;
42
+ conversationStatusManager;
43
+ toolMetricsService;
44
+ sessionInfoService;
45
+ fileSystemService;
46
+ notificationService;
47
+ routerService;
48
+ killedProcesses = new Set(); // Track which processes we've killed
49
+ processEventLog;
50
+ constructor(historyReader, statusTracker, claudeExecutablePath, envOverrides, toolMetricsService, sessionInfoService, fileSystemService) {
51
+ super();
52
+ this.historyReader = historyReader;
53
+ this.statusTracker = statusTracker;
54
+ this.claudeExecutablePath = claudeExecutablePath || this.findClaudeExecutable();
55
+ this.logger = createLogger('ClaudeProcessManager');
56
+ this.envOverrides = envOverrides || {};
57
+ this.toolMetricsService = toolMetricsService;
58
+ this.sessionInfoService = sessionInfoService;
59
+ this.fileSystemService = fileSystemService;
60
+ this.processEventLog = getProcessEventLog();
61
+ }
62
+ setRouterService(service) {
63
+ this.routerService = service;
64
+ }
65
+ /**
66
+ * Find the Claude executable - check user-local and system paths
67
+ */
68
+ findClaudeExecutable() {
69
+ // Check common installation paths including user-local installs
70
+ const userLocalPath = path.join(os.homedir(), '.local', 'bin', 'claude');
71
+ const candidatePaths = [
72
+ userLocalPath, // ~/.local/bin/claude (common for user installs)
73
+ '/usr/local/bin/claude',
74
+ '/usr/bin/claude',
75
+ ];
76
+ for (const candidate of candidatePaths) {
77
+ if (existsSync(candidate)) {
78
+ return candidate;
79
+ }
80
+ }
81
+ // Fallback: try PATH, excluding node_modules
82
+ const pathEnv = process.env.PATH || '';
83
+ const pathDirs = pathEnv.split(path.delimiter);
84
+ for (const dir of pathDirs) {
85
+ if (dir.includes('node_modules'))
86
+ continue; // Skip node_modules
87
+ const candidate = path.join(dir, 'claude');
88
+ if (existsSync(candidate)) {
89
+ return candidate;
90
+ }
91
+ }
92
+ throw new Error('Claude CLI not found.\n\n' +
93
+ 'Claudia Orchestrator requires Claude Code CLI to be installed.\n' +
94
+ 'Install it with: npm install -g @anthropic-ai/claude-code\n' +
95
+ 'Then authenticate with: claude\n\n' +
96
+ 'More info: https://docs.anthropic.com/en/docs/claude-code');
97
+ }
98
+ /**
99
+ * Set the MCP config path to be used for all conversations
100
+ */
101
+ setMCPConfigPath(path) {
102
+ this.mcpConfigPath = path;
103
+ this.logger.debug('MCP config path set', { path });
104
+ }
105
+ /**
106
+ * Set the optimistic conversation service
107
+ */
108
+ setConversationStatusManager(service) {
109
+ this.conversationStatusManager = service;
110
+ this.logger.debug('Conversation status manager set');
111
+ }
112
+ /**
113
+ * Set the notification service
114
+ */
115
+ setNotificationService(service) {
116
+ this.notificationService = service;
117
+ this.logger.debug('Notification service set');
118
+ }
119
+ /**
120
+ * Start a new Claude conversation (or resume if resumedSessionId is provided)
121
+ */
122
+ async startConversation(config) {
123
+ const isResume = !!config.resumedSessionId;
124
+ // Guard against duplicate spawns for the same session
125
+ if (isResume && config.resumedSessionId) {
126
+ const resumeSessionId = asClaudeSessionId(config.resumedSessionId);
127
+ if (this.statusTracker.isSessionActive(resumeSessionId)) {
128
+ this.logger.warn('Attempted to resume already-active session', {
129
+ sessionId: config.resumedSessionId
130
+ });
131
+ throw new LatticeError('SESSION_ALREADY_ACTIVE', `Cannot resume session ${config.resumedSessionId}: already has an active Claude process`, 409 // Conflict
132
+ );
133
+ }
134
+ }
135
+ this.logger.debug('Start conversation requested', {
136
+ hasInitialPrompt: !!config.initialPrompt,
137
+ promptLength: config.initialPrompt?.length,
138
+ workingDirectory: config.workingDirectory,
139
+ model: config.model,
140
+ allowedTools: config.allowedTools,
141
+ disallowedTools: config.disallowedTools,
142
+ hasSystemPrompt: !!config.systemPrompt,
143
+ claudePath: config.claudeExecutablePath || this.claudeExecutablePath,
144
+ isResume,
145
+ resumedSessionId: config.resumedSessionId,
146
+ previousMessageCount: config.previousMessages?.length || 0
147
+ });
148
+ // If resuming and no working directory provided, fetch from original session
149
+ let workingDirectory = config.workingDirectory;
150
+ if (isResume && !workingDirectory && config.resumedSessionId) {
151
+ const fetchedWorkingDirectory = await this.historyReader.getConversationWorkingDirectory(config.resumedSessionId);
152
+ if (!fetchedWorkingDirectory) {
153
+ throw new LatticeError('CONVERSATION_NOT_FOUND', `Could not find working directory for session ${config.resumedSessionId}`, 404);
154
+ }
155
+ workingDirectory = fetchedWorkingDirectory;
156
+ this.logger.debug('Found working directory for resume session', {
157
+ sessionId: config.resumedSessionId,
158
+ workingDirectory
159
+ });
160
+ }
161
+ const args = isResume && config.resumedSessionId
162
+ ? this.buildResumeArgs({
163
+ sessionId: config.resumedSessionId,
164
+ message: config.initialPrompt,
165
+ permissionMode: config.permissionMode,
166
+ hasMultimodalContent: !!(config.initialContent && config.initialContent.length > 0)
167
+ })
168
+ : this.buildStartArgs(config);
169
+ const spawnConfig = {
170
+ executablePath: config.claudeExecutablePath || this.claudeExecutablePath,
171
+ cwd: workingDirectory || config.workingDirectory || process.cwd(),
172
+ env: { ...process.env, ...this.envOverrides }
173
+ };
174
+ this.logger.debug('Spawn config prepared', {
175
+ executablePath: spawnConfig.executablePath,
176
+ cwd: spawnConfig.cwd,
177
+ hasEnvOverrides: Object.keys(this.envOverrides).length > 0,
178
+ envOverrideKeys: Object.keys(this.envOverrides),
179
+ isResume
180
+ });
181
+ return this.executeConversationFlow(isResume ? 'resuming' : 'starting', isResume && config.resumedSessionId ? { resumeSessionId: config.resumedSessionId } : {}, config, args, spawnConfig, isResume ? 'PROCESS_RESUME_FAILED' : 'PROCESS_START_FAILED', isResume ? 'Failed to resume Claude process' : 'Failed to start Claude process');
182
+ }
183
+ /**
184
+ * Stop a conversation gracefully (SIGTERM, then SIGKILL after 3s)
185
+ */
186
+ async stopConversation(streamingId) {
187
+ this.logger.debug('Stopping conversation', { streamingId });
188
+ const process = this.processes.get(streamingId);
189
+ if (!process) {
190
+ this.logger.warn('No process found for conversation', { streamingId });
191
+ return false;
192
+ }
193
+ try {
194
+ // Force kill if not already killed
195
+ if (!this.killedProcesses.has(streamingId)) {
196
+ this.logger.info('Sending SIGTERM to process', { streamingId, pid: process.pid });
197
+ // Log stop requested
198
+ this.processEventLog.stopRequested({
199
+ traceId: streamingId,
200
+ streamingId,
201
+ pid: process.pid,
202
+ });
203
+ process.kill('SIGTERM');
204
+ this.killedProcesses.add(streamingId);
205
+ // If SIGTERM doesn't work, use SIGKILL after 3 seconds
206
+ const killTimeout = setTimeout(() => {
207
+ try {
208
+ if (this.processes.has(streamingId)) {
209
+ this.logger.warn('Process not responding to SIGTERM, sending SIGKILL', { streamingId, pid: process.pid });
210
+ // Log force kill due to SIGTERM timeout
211
+ this.processEventLog.forceKill({
212
+ traceId: streamingId,
213
+ streamingId,
214
+ pid: process.pid,
215
+ reason: 'SIGTERM timeout',
216
+ });
217
+ process.kill('SIGKILL');
218
+ }
219
+ }
220
+ catch {
221
+ // Process may have already exited, ignore
222
+ }
223
+ }, 3000);
224
+ // Track timeout for cleanup on process exit
225
+ const sessionTimeouts = this.timeouts.get(streamingId) || [];
226
+ sessionTimeouts.push(killTimeout);
227
+ this.timeouts.set(streamingId, sessionTimeouts);
228
+ }
229
+ // Note: Cleanup happens in handleProcessClose when process actually exits
230
+ // Don't clean up here - the process may still be running
231
+ this.logger.info('Stop signal sent to process', { streamingId });
232
+ return true;
233
+ }
234
+ catch (error) {
235
+ this.logger.error('Error stopping conversation', error, { streamingId });
236
+ return false;
237
+ }
238
+ }
239
+ /**
240
+ * Interrupt a conversation (SIGINT - like Ctrl-C)
241
+ * This allows the process to handle the interrupt gracefully
242
+ */
243
+ interruptConversation(streamingId) {
244
+ this.logger.info('Interrupting conversation', { streamingId });
245
+ const process = this.processes.get(streamingId);
246
+ if (!process) {
247
+ this.logger.warn('No process found for interrupt', { streamingId });
248
+ return false;
249
+ }
250
+ try {
251
+ this.logger.info('Sending SIGINT to process', { streamingId, pid: process.pid });
252
+ process.kill('SIGINT');
253
+ return true;
254
+ }
255
+ catch (error) {
256
+ this.logger.error('Error interrupting conversation', error, { streamingId });
257
+ return false;
258
+ }
259
+ }
260
+ /**
261
+ * Force kill a conversation immediately (SIGKILL)
262
+ */
263
+ forceKillConversation(streamingId) {
264
+ this.logger.info('Force killing conversation', { streamingId });
265
+ const process = this.processes.get(streamingId);
266
+ if (!process) {
267
+ this.logger.warn('No process found for force kill', { streamingId });
268
+ return false;
269
+ }
270
+ try {
271
+ this.logger.warn('Sending SIGKILL to process', { streamingId, pid: process.pid });
272
+ // Log force kill
273
+ this.processEventLog.forceKill({
274
+ traceId: streamingId,
275
+ streamingId,
276
+ pid: process.pid,
277
+ reason: 'user requested',
278
+ });
279
+ process.kill('SIGKILL');
280
+ this.killedProcesses.add(streamingId);
281
+ return true;
282
+ }
283
+ catch (error) {
284
+ this.logger.error('Error force killing conversation', error, { streamingId });
285
+ return false;
286
+ }
287
+ }
288
+ /**
289
+ * Get active sessions with their session IDs
290
+ */
291
+ getActiveSessions() {
292
+ const sessions = Array.from(this.processes.keys())
293
+ .map(streamingId => {
294
+ const sessionId = this.sessionIds.get(streamingId);
295
+ return sessionId ? { streamingId, sessionId } : null;
296
+ })
297
+ .filter((s) => s !== null);
298
+ this.logger.debug('Getting active sessions', { sessionCount: sessions.length });
299
+ return sessions;
300
+ }
301
+ /**
302
+ * Check if a session is active
303
+ */
304
+ isSessionActive(streamingId) {
305
+ const active = this.processes.has(streamingId);
306
+ return active;
307
+ }
308
+ /**
309
+ * Get the Claude session ID for a streaming ID
310
+ * This mapping persists for the lifetime of the process
311
+ */
312
+ getClaudeSessionId(streamingId) {
313
+ return this.sessionIds.get(streamingId);
314
+ }
315
+ /**
316
+ * Wait for the system init message from Claude CLI
317
+ * This should always be the first message in the stream
318
+ */
319
+ async waitForSystemInit(streamingId) {
320
+ this.logger.debug('Waiting for system init message', { streamingId });
321
+ return new Promise((resolve, reject) => {
322
+ let isResolved = false;
323
+ let stderrOutput = '';
324
+ // Set up timeout (1 minute)
325
+ const timeout = setTimeout(() => {
326
+ if (!isResolved) {
327
+ isResolved = true;
328
+ cleanup();
329
+ this.logger.error('Timeout waiting for system init message', {
330
+ streamingId,
331
+ stderrOutput: stderrOutput || '(no stderr output)'
332
+ });
333
+ // Include stderr output in error message if available
334
+ let errorMessage = 'Timeout waiting for system initialization from Claude CLI';
335
+ if (stderrOutput) {
336
+ errorMessage += `. Error output: ${stderrOutput}`;
337
+ }
338
+ reject(new LatticeError('SYSTEM_INIT_TIMEOUT', errorMessage, 500));
339
+ }
340
+ }, 60000);
341
+ // Cleanup function to remove all listeners
342
+ const cleanup = () => {
343
+ clearTimeout(timeout);
344
+ this.removeListener('claude-message', messageHandler);
345
+ this.removeListener('process-closed', processClosedHandler);
346
+ this.removeListener('process-error', processErrorHandler);
347
+ };
348
+ // Register timeout for cleanup on process termination
349
+ const existingTimeouts = this.timeouts.get(streamingId) || [];
350
+ existingTimeouts.push(timeout);
351
+ this.timeouts.set(streamingId, existingTimeouts);
352
+ // Listen for process exit before system init is received
353
+ const processClosedHandler = ({ streamingId: closedStreamingId, code }) => {
354
+ if (closedStreamingId !== streamingId || isResolved) {
355
+ return; // Not our process or already resolved
356
+ }
357
+ isResolved = true;
358
+ cleanup();
359
+ this.logger.error('Claude process exited before system init message', {
360
+ streamingId,
361
+ exitCode: code,
362
+ stderrOutput: stderrOutput || '(no stderr output)'
363
+ });
364
+ // Create error message with Claude CLI output if available
365
+ let errorMessage = 'Claude CLI process exited before sending system initialization message';
366
+ if (stderrOutput) {
367
+ // Extract Claude CLI's actual output from parser errors
368
+ const claudeOutputMatch = stderrOutput.match(/Invalid JSON: (.+)/);
369
+ if (claudeOutputMatch) {
370
+ errorMessage += `. Claude CLI said: "${claudeOutputMatch[1]}"`;
371
+ }
372
+ else {
373
+ errorMessage += `. Error output: ${stderrOutput}`;
374
+ }
375
+ }
376
+ if (code !== null) {
377
+ errorMessage += `. Exit code: ${code}`;
378
+ }
379
+ reject(new LatticeError('CLAUDE_PROCESS_EXITED_EARLY', errorMessage, 500));
380
+ };
381
+ // Listen for process errors (including stderr output)
382
+ const processErrorHandler = ({ streamingId: errorStreamingId, error }) => {
383
+ if (errorStreamingId !== streamingId) {
384
+ return; // Not our process
385
+ }
386
+ // Capture stderr output for error context
387
+ stderrOutput += error;
388
+ this.logger.debug('Captured stderr output during system init wait', {
389
+ streamingId,
390
+ errorLength: error.length,
391
+ totalStderrLength: stderrOutput.length
392
+ });
393
+ };
394
+ // Listen for the first claude-message event for this streamingId
395
+ const messageHandler = ({ streamingId: msgStreamingId, message }) => {
396
+ if (msgStreamingId !== streamingId) {
397
+ return; // Not for our session
398
+ }
399
+ if (isResolved) {
400
+ return; // Already resolved
401
+ }
402
+ this.logger.debug('Received message from Claude CLI during init wait', {
403
+ streamingId,
404
+ messageType: message?.type,
405
+ messageSubtype: 'subtype' in message ? message.subtype : undefined,
406
+ hasSessionId: 'session_id' in message ? !!message.session_id : false
407
+ });
408
+ // Skip hook_response messages (Claude v2 sends these before init)
409
+ // Cast to unknown string to compare - StreamEvent type doesn't include all system subtypes
410
+ if (message?.type === 'system' && 'subtype' in message && message.subtype === 'hook_response') {
411
+ this.logger.debug('Skipping hook_response message, waiting for init', { streamingId });
412
+ return; // Keep waiting for init message
413
+ }
414
+ // Now we have a non-hook message, mark as resolved
415
+ isResolved = true;
416
+ cleanup();
417
+ // Validate that this message is a system init message
418
+ if (!message || message.type !== 'system' || !('subtype' in message) || message.subtype !== 'init') {
419
+ this.logger.error('Expected system init message', {
420
+ streamingId,
421
+ actualType: message?.type,
422
+ actualSubtype: 'subtype' in message ? message.subtype : undefined,
423
+ expectedType: 'system',
424
+ expectedSubtype: 'init'
425
+ });
426
+ reject(new LatticeError('INVALID_SYSTEM_INIT', `Expected system init message, but got: ${message?.type}/${'subtype' in message ? message.subtype : 'undefined'}`, 500));
427
+ return;
428
+ }
429
+ // At this point, TypeScript knows message is SystemInitMessage
430
+ const systemInitMessage = message;
431
+ // Validate required fields
432
+ const requiredFields = ['session_id', 'cwd', 'tools', 'mcp_servers', 'model', 'permissionMode', 'apiKeySource'];
433
+ const missingFields = requiredFields.filter(field => systemInitMessage[field] === undefined);
434
+ if (missingFields.length > 0) {
435
+ this.logger.error('System init message missing required fields', {
436
+ streamingId,
437
+ missingFields,
438
+ availableFields: Object.keys(systemInitMessage)
439
+ });
440
+ reject(new LatticeError('INCOMPLETE_SYSTEM_INIT', `System init message missing required fields: ${missingFields.join(', ')}`, 500));
441
+ return;
442
+ }
443
+ this.logger.debug('Successfully received valid system init message', {
444
+ streamingId,
445
+ sessionId: systemInitMessage.session_id,
446
+ cwd: systemInitMessage.cwd,
447
+ model: systemInitMessage.model,
448
+ toolCount: systemInitMessage.tools?.length || 0,
449
+ mcpServerCount: systemInitMessage.mcp_servers?.length || 0
450
+ });
451
+ // Log system init to process event log
452
+ this.processEventLog.systemInit({
453
+ traceId: streamingId,
454
+ streamingId,
455
+ sessionId: systemInitMessage.session_id,
456
+ model: systemInitMessage.model,
457
+ workingDirectory: systemInitMessage.cwd,
458
+ toolCount: systemInitMessage.tools?.length || 0,
459
+ mcpServerCount: systemInitMessage.mcp_servers?.length || 0,
460
+ });
461
+ // Register active session immediately when we have the session_id
462
+ // Include optimistic context if available
463
+ const config = this.conversationConfigs.get(streamingId);
464
+ if (this.conversationStatusManager && config) {
465
+ const optimisticContext = {
466
+ initialPrompt: config.initialPrompt || '',
467
+ workingDirectory: config.workingDirectory || process.cwd(),
468
+ model: config.model || 'default',
469
+ timestamp: new Date().toISOString(),
470
+ inheritedMessages: config.previousMessages
471
+ };
472
+ // Store in local sessionIds map (never cleaned up until process exits)
473
+ const claudeSessionId = asClaudeSessionId(systemInitMessage.session_id);
474
+ this.sessionIds.set(streamingId, claudeSessionId);
475
+ this.conversationStatusManager.registerActiveSession(streamingId, claudeSessionId, optimisticContext);
476
+ this.logger.debug('Registered conversation context', {
477
+ streamingId,
478
+ claudeSessionId,
479
+ inheritedMessageCount: config.previousMessages?.length || 0
480
+ });
481
+ }
482
+ else {
483
+ // Fallback to old behavior if service not set
484
+ const claudeSessionId = asClaudeSessionId(systemInitMessage.session_id);
485
+ this.sessionIds.set(streamingId, claudeSessionId);
486
+ this.statusTracker.registerActiveSession(streamingId, claudeSessionId);
487
+ this.logger.debug('Registered active session with status tracker (no optimistic service)', {
488
+ streamingId,
489
+ claudeSessionId
490
+ });
491
+ }
492
+ resolve(systemInitMessage);
493
+ };
494
+ // Set up all event listeners
495
+ this.on('claude-message', messageHandler);
496
+ this.on('process-closed', processClosedHandler);
497
+ this.on('process-error', processErrorHandler);
498
+ });
499
+ }
500
+ /**
501
+ * Execute common conversation flow for both start and resume operations
502
+ */
503
+ async executeConversationFlow(operation, loggerContext, config, args, spawnConfig, errorCode, errorPrefix) {
504
+ const streamingId = asStreamingId(uuidv4()); // CUI's internal streaming identifier
505
+ const traceId = streamingId; // Use streamingId as traceId for now
506
+ const isResume = operation === 'resuming';
507
+ // Store config for use in waitForSystemInit
508
+ this.conversationConfigs.set(streamingId, config);
509
+ // Log spawn start
510
+ this.processEventLog.spawnStart({
511
+ traceId,
512
+ streamingId,
513
+ isResume,
514
+ workingDirectory: spawnConfig.cwd,
515
+ model: config.model,
516
+ resumedSessionId: config.resumedSessionId,
517
+ });
518
+ try {
519
+ // Validate Claude executable before proceeding
520
+ if (this.fileSystemService) {
521
+ await this.fileSystemService.validateExecutable(spawnConfig.executablePath);
522
+ }
523
+ this.logger.debug(`${operation.charAt(0).toUpperCase() + operation.slice(1)} conversation`, {
524
+ streamingId,
525
+ operation,
526
+ configKeys: Object.keys(config),
527
+ argCount: args.length,
528
+ ...loggerContext
529
+ });
530
+ this.logger.debug(`Built Claude ${operation} args`, {
531
+ streamingId,
532
+ args,
533
+ argsString: args.join(' '),
534
+ ...loggerContext
535
+ });
536
+ // Set up system init promise before spawning process
537
+ const systemInitPromise = this.waitForSystemInit(streamingId);
538
+ // Add streamingId to environment for MCP server to use
539
+ // Filter out debugging-related environment variables that would cause
540
+ // the VSCode debugger to attach to the Claude CLI child process
541
+ // Also filter CLAUDECODE to prevent nested session detection issues
542
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
543
+ const { NODE_OPTIONS, VSCODE_INSPECTOR_OPTIONS, CLAUDECODE, CLAUDE_CODE_ENTRYPOINT, ...cleanEnv } = spawnConfig.env;
544
+ // Expand tilde to actual home directory path
545
+ const expandedCwd = expandTilde(spawnConfig.cwd);
546
+ const envWithStreamingId = {
547
+ ...cleanEnv,
548
+ CUI_STREAMING_ID: streamingId,
549
+ PWD: expandedCwd,
550
+ INIT_CWD: expandedCwd
551
+ };
552
+ // Use piped spawn for multimodal content (stdin works properly with pipes)
553
+ // Use PTY for text-only (better terminal experience)
554
+ const hasMultimodalContent = !!(config.initialContent && config.initialContent.length > 0);
555
+ const managedProcess = this.spawnProcess({ ...spawnConfig, env: envWithStreamingId }, args, streamingId, hasMultimodalContent // usePipe = true for multimodal
556
+ );
557
+ this.processes.set(streamingId, managedProcess);
558
+ this.setupProcessHandlers(streamingId, managedProcess);
559
+ // Emit process-spawned event so StreamManager can start buffering events
560
+ // This handles the race condition where events are broadcast before SSE client connects
561
+ this.emit('process-spawned', { streamingId });
562
+ // For multimodal content, send via stdin BEFORE waiting for system init
563
+ // The CLI will process this and emit the system init message
564
+ if (hasMultimodalContent && config.initialContent) {
565
+ const contentBlocks = [
566
+ ...config.initialContent,
567
+ ...(config.initialPrompt ? [{ type: 'text', text: config.initialPrompt }] : [])
568
+ ];
569
+ const stdinMessage = JSON.stringify({
570
+ type: 'user',
571
+ message: {
572
+ role: 'user',
573
+ content: contentBlocks
574
+ },
575
+ session_id: config.resumedSessionId || ''
576
+ });
577
+ this.logger.info('Sending multimodal content via stdin (piped mode)', {
578
+ streamingId,
579
+ contentBlockCount: contentBlocks.length,
580
+ messageLength: stdinMessage.length
581
+ });
582
+ managedProcess.write(stdinMessage + '\n');
583
+ // Close stdin to signal end of input - CLI will process and respond
584
+ if (managedProcess.type === 'pipe') {
585
+ managedProcess.endStdin();
586
+ }
587
+ }
588
+ // Log spawn success
589
+ if (managedProcess.pid) {
590
+ this.processEventLog.spawnSuccess({
591
+ traceId,
592
+ streamingId,
593
+ pid: managedProcess.pid,
594
+ });
595
+ }
596
+ // Handle spawn errors by listening for our custom event
597
+ const spawnErrorPromise = new Promise((_, reject) => {
598
+ this.once('spawn-error', (error) => {
599
+ this.processes.delete(streamingId);
600
+ reject(error);
601
+ });
602
+ });
603
+ // Wait a bit to see if spawn fails immediately
604
+ this.logger.debug('Waiting for spawn validation', { streamingId, ...loggerContext });
605
+ const delayPromise = new Promise(resolve => {
606
+ setTimeout(() => {
607
+ this.logger.debug('Spawn validation period passed, process appears stable', { streamingId, ...loggerContext });
608
+ this.removeAllListeners('spawn-error');
609
+ resolve(streamingId);
610
+ }, 100);
611
+ });
612
+ await Promise.race([spawnErrorPromise, delayPromise]);
613
+ // Now wait for the system init message
614
+ this.logger.debug('Process spawned successfully, waiting for system init message', { streamingId, ...loggerContext });
615
+ const systemInit = await systemInitPromise;
616
+ // Check if cwd is a git repository and set initial_commit_head for new session
617
+ if (this.sessionInfoService && this.fileSystemService) {
618
+ try {
619
+ if (await this.fileSystemService.isGitRepository(systemInit.cwd)) {
620
+ const gitHead = await this.fileSystemService.getCurrentGitHead(systemInit.cwd);
621
+ if (gitHead) {
622
+ await this.sessionInfoService.updateSessionInfo(systemInit.session_id, {
623
+ initial_commit_head: gitHead
624
+ });
625
+ this.logger.debug('Set initial commit head for new session', {
626
+ sessionId: systemInit.session_id,
627
+ gitHead
628
+ });
629
+ }
630
+ }
631
+ }
632
+ catch (error) {
633
+ this.logger.warn('Failed to set initial commit head for new session', {
634
+ sessionId: systemInit.session_id,
635
+ cwd: systemInit.cwd,
636
+ error: error instanceof Error ? error.message : String(error)
637
+ });
638
+ }
639
+ }
640
+ this.logger.debug(`${operation.charAt(0).toUpperCase() + operation.slice(1)} conversation successfully`, {
641
+ streamingId,
642
+ sessionId: systemInit.session_id,
643
+ model: systemInit.model,
644
+ cwd: systemInit.cwd,
645
+ processCount: this.processes.size,
646
+ ...loggerContext
647
+ });
648
+ return { streamingId, systemInit };
649
+ }
650
+ catch (error) {
651
+ this.logger.error(`Error ${operation} conversation`, error, {
652
+ streamingId,
653
+ errorName: error instanceof Error ? error.name : 'Unknown',
654
+ errorMessage: error instanceof Error ? error.message : String(error),
655
+ errorCode: error instanceof LatticeError ? error.code : undefined,
656
+ ...loggerContext
657
+ });
658
+ // Log spawn failure
659
+ this.processEventLog.spawnFailed({
660
+ traceId,
661
+ streamingId,
662
+ errorCode: error instanceof LatticeError ? error.code : errorCode,
663
+ errorMessage: error instanceof Error ? error.message : String(error),
664
+ });
665
+ // Clean up any resources if process fails
666
+ const timeouts = this.timeouts.get(streamingId);
667
+ if (timeouts) {
668
+ timeouts.forEach(timeout => clearTimeout(timeout));
669
+ this.timeouts.delete(streamingId);
670
+ }
671
+ this.processes.delete(streamingId);
672
+ this.outputBuffers.delete(streamingId);
673
+ this.conversationConfigs.delete(streamingId);
674
+ if (error instanceof LatticeError) {
675
+ throw error;
676
+ }
677
+ throw new LatticeError(errorCode, `${errorPrefix}: ${error}`, 500);
678
+ }
679
+ }
680
+ buildBaseArgs() {
681
+ return [
682
+ '-p', // Print mode - required for programmatic use
683
+ ];
684
+ }
685
+ buildResumeArgs(config) {
686
+ this.logger.debug('Building Claude resume args', {
687
+ sessionId: config.sessionId,
688
+ messagePreview: config.message.substring(0, 50) + (config.message.length > 50 ? '...' : ''),
689
+ permissionMode: config.permissionMode,
690
+ hasMultimodalContent: config.hasMultimodalContent
691
+ });
692
+ const args = this.buildBaseArgs();
693
+ // Add message as CLI arg - skip if multimodal content will be sent via stdin
694
+ // NOTE: For very long messages, this may hit CLI arg length limits
695
+ if (config.message && !config.hasMultimodalContent) {
696
+ args.push(config.message);
697
+ }
698
+ args.push('--resume', config.sessionId, '--output-format', 'stream-json', '--input-format', 'stream-json', // Enable stdin for AskUserQuestion responses
699
+ '--verbose');
700
+ // Add MCP config if available (same as start operations)
701
+ // This enables the permission prompt tool for ASK mode on resume
702
+ if (this.mcpConfigPath) {
703
+ args.push('--mcp-config', this.mcpConfigPath);
704
+ }
705
+ // Apply permission mode with MCP tool support enabled
706
+ this.applyPermissionMode(args, config.permissionMode);
707
+ this.logger.debug('Built Claude resume args', { args, hasMCPConfig: !!this.mcpConfigPath });
708
+ return args;
709
+ }
710
+ /**
711
+ * Apply permission mode flags to CLI args
712
+ * Modes:
713
+ * - "bypassPermissions" (default): Skip all permission prompts (--dangerously-skip-permissions)
714
+ * - "default": Use Claude's default permission behavior (prompts for approval via MCP)
715
+ * - "acceptEdits": Auto-accept file edits but prompt for other tools
716
+ * - "plan": Plan mode - research only, no edits
717
+ */
718
+ applyPermissionMode(args, permissionMode) {
719
+ switch (permissionMode) {
720
+ case 'default':
721
+ // Use MCP permission prompt tool to delegate approval to the dashboard
722
+ // The tool must be pre-allowed so Claude can use it without prompting
723
+ if (this.mcpConfigPath) {
724
+ args.push('--permission-prompt-tool', 'mcp__claudia-permissions__permission_prompt');
725
+ args.push('--allowedTools', 'mcp__claudia-permissions__permission_prompt');
726
+ this.logger.debug('Permission mode: default (using MCP permission prompt)');
727
+ }
728
+ else {
729
+ // No MCP config available - fall back to bypass permissions
730
+ args.push('--dangerously-skip-permissions');
731
+ this.logger.debug('Permission mode: default but no MCP config, falling back to bypass');
732
+ }
733
+ break;
734
+ case 'acceptEdits':
735
+ args.push('--permission-mode', 'acceptEdits');
736
+ this.logger.debug('Permission mode: acceptEdits');
737
+ break;
738
+ case 'plan':
739
+ args.push('--permission-mode', 'plan');
740
+ this.logger.debug('Permission mode: plan');
741
+ break;
742
+ case 'bypassPermissions':
743
+ default:
744
+ // Default to bypass for backwards compatibility
745
+ args.push('--dangerously-skip-permissions');
746
+ this.logger.debug('Permission mode: bypassPermissions (dangerously skip)');
747
+ break;
748
+ }
749
+ }
750
+ buildStartArgs(config) {
751
+ this.logger.debug('Building Claude start args', {
752
+ hasInitialPrompt: !!config.initialPrompt,
753
+ hasInitialContent: !!(config.initialContent && config.initialContent.length > 0),
754
+ promptPreview: config.initialPrompt ? config.initialPrompt.substring(0, 50) + (config.initialPrompt.length > 50 ? '...' : '') : null,
755
+ workingDirectory: config.workingDirectory,
756
+ model: config.model
757
+ });
758
+ const args = this.buildBaseArgs();
759
+ // Add initial prompt immediately after -p
760
+ // Skip if initialContent is provided - multimodal content will be sent via stdin
761
+ if (config.initialPrompt && !(config.initialContent && config.initialContent.length > 0)) {
762
+ args.push(config.initialPrompt);
763
+ }
764
+ args.push('--output-format', 'stream-json', // JSONL output format
765
+ '--input-format', 'stream-json', // Enable stdin for AskUserQuestion responses
766
+ '--verbose' // Required when using stream-json with print mode
767
+ );
768
+ // Add working directory access
769
+ // if (config.workingDirectory) {
770
+ // args.push('--add-dir', config.workingDirectory);
771
+ // }
772
+ // Add model specification
773
+ if (config.model) {
774
+ args.push('--model', config.model);
775
+ }
776
+ // Add allowed tools
777
+ if (config.allowedTools && config.allowedTools.length > 0) {
778
+ args.push('--allowedTools', config.allowedTools.join(','));
779
+ }
780
+ // Add disallowed tools
781
+ if (config.disallowedTools && config.disallowedTools.length > 0) {
782
+ args.push('--disallowedTools', config.disallowedTools.join(','));
783
+ }
784
+ // Add system prompt with Claudia environment info
785
+ const claudiaEnvInfo = `
786
+ ## Claudia Orchestrator Environment
787
+
788
+ You are running inside the Claudia orchestrator dashboard.
789
+
790
+ **Server logs** (for debugging issues):
791
+ - \`~/.claudia/logs/server.log\` - Express server logs
792
+ - \`~/.claudia/logs/daemon.log\` - Process daemon logs
793
+ `;
794
+ const systemPrompt = config.systemPrompt
795
+ ? `${config.systemPrompt}\n${claudiaEnvInfo}`
796
+ : claudiaEnvInfo;
797
+ args.push('--system-prompt', systemPrompt);
798
+ // Add MCP config if available
799
+ if (this.mcpConfigPath) {
800
+ args.push('--mcp-config', this.mcpConfigPath);
801
+ }
802
+ // Apply permission mode
803
+ this.applyPermissionMode(args, config.permissionMode);
804
+ this.logger.debug('Built Claude args', { args, hasMCPConfig: !!this.mcpConfigPath, permissionMode: config.permissionMode });
805
+ return args;
806
+ }
807
+ /**
808
+ * Consolidated method to spawn Claude processes for both start and resume operations
809
+ * Uses node-pty for text-only (terminal experience) or piped spawn for multimodal (stdin works properly)
810
+ */
811
+ spawnProcess(spawnConfig, args, streamingId, usePipe = false) {
812
+ const { executablePath } = spawnConfig;
813
+ // Expand tilde to actual home directory - Node.js spawn doesn't do shell expansion
814
+ const cwd = expandTilde(spawnConfig.cwd);
815
+ let { env } = spawnConfig;
816
+ // Inject router proxy if enabled
817
+ if (this.routerService?.isEnabled()) {
818
+ env = {
819
+ ...env,
820
+ ANTHROPIC_BASE_URL: this.routerService.getProxyUrl(),
821
+ ANTHROPIC_API_KEY: 'router-managed'
822
+ };
823
+ this.logger.info('Using router proxy', {
824
+ streamingId,
825
+ proxyUrl: this.routerService.getProxyUrl()
826
+ });
827
+ }
828
+ // Check if MCP config is in args and validate it
829
+ const mcpConfigIndex = args.indexOf('--mcp-config');
830
+ if (mcpConfigIndex !== -1 && mcpConfigIndex + 1 < args.length) {
831
+ const mcpConfigPath = args[mcpConfigIndex + 1];
832
+ this.logger.debug('MCP config specified', {
833
+ streamingId,
834
+ mcpConfigPath,
835
+ exists: existsSync(mcpConfigPath)
836
+ });
837
+ // Try to read and log the MCP config content
838
+ try {
839
+ const mcpConfigContent = readFileSync(mcpConfigPath, 'utf-8');
840
+ this.logger.debug('MCP config content', {
841
+ streamingId,
842
+ mcpConfig: JSON.parse(mcpConfigContent)
843
+ });
844
+ }
845
+ catch (error) {
846
+ this.logger.error('Failed to read MCP config', { streamingId, error });
847
+ }
848
+ }
849
+ const spawnMode = usePipe ? 'PIPE' : 'PTY';
850
+ this.logger.debug(`Spawning Claude process with ${spawnMode}`, {
851
+ streamingId,
852
+ executablePath,
853
+ args,
854
+ cwd,
855
+ PATH: env.PATH,
856
+ nodeVersion: process.version,
857
+ platform: process.platform
858
+ });
859
+ try {
860
+ // Log the exact command for debugging
861
+ const fullCommand = `${executablePath} ${args.join(' ')}`;
862
+ this.logger.info(`SPAWNING CLAUDE COMMAND (${spawnMode}): ` + fullCommand, {
863
+ streamingId,
864
+ fullCommand,
865
+ executablePath,
866
+ args,
867
+ cwd,
868
+ env: Object.entries(env).reduce((acc, [key, value]) => {
869
+ acc[key] = value;
870
+ return acc;
871
+ }, {})
872
+ });
873
+ if (usePipe) {
874
+ // Use piped spawn for multimodal - stdin will be a pipe, not a TTY
875
+ // This allows CLI to block waiting for stdin input
876
+ const childProcess = spawn(executablePath, args, {
877
+ cwd,
878
+ env: env,
879
+ stdio: ['pipe', 'pipe', 'pipe'],
880
+ });
881
+ if (!childProcess.pid) {
882
+ this.logger.error('Failed to spawn Claude process - no PID assigned', { streamingId });
883
+ throw new Error('Failed to spawn Claude process - no PID assigned');
884
+ }
885
+ this.logger.info('Claude process spawned successfully (PIPE)', {
886
+ streamingId,
887
+ pid: childProcess.pid
888
+ });
889
+ return {
890
+ type: 'pipe',
891
+ child: childProcess,
892
+ pid: childProcess.pid,
893
+ write: (data) => childProcess.stdin?.write(data),
894
+ kill: (signal) => childProcess.kill(signal),
895
+ endStdin: () => childProcess.stdin?.end(),
896
+ };
897
+ }
898
+ else {
899
+ // Use node-pty to spawn with a pseudo-terminal
900
+ // This is required because Claude CLI -p mode only outputs when connected to a TTY
901
+ const claudeProcess = pty.spawn(executablePath, args, {
902
+ name: 'xterm-256color',
903
+ cols: 200,
904
+ rows: 50,
905
+ cwd,
906
+ env: env
907
+ });
908
+ if (!claudeProcess.pid) {
909
+ this.logger.error('Failed to spawn Claude process - no PID assigned', { streamingId });
910
+ throw new Error('Failed to spawn Claude process - no PID assigned');
911
+ }
912
+ this.logger.info('Claude process spawned successfully (PTY)', {
913
+ streamingId,
914
+ pid: claudeProcess.pid
915
+ });
916
+ return {
917
+ type: 'pty',
918
+ pty: claudeProcess,
919
+ pid: claudeProcess.pid,
920
+ write: (data) => claudeProcess.write(data),
921
+ kill: (signal) => claudeProcess.kill(signal),
922
+ };
923
+ }
924
+ }
925
+ catch (error) {
926
+ this.logger.error('Error in spawnProcess', error, { streamingId });
927
+ if (error instanceof LatticeError) {
928
+ throw error;
929
+ }
930
+ const errorMessage = error instanceof Error ? error.message : String(error);
931
+ if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
932
+ throw new LatticeError('CLAUDE_NOT_FOUND', 'Claude CLI not found. Please ensure Claude is installed and in PATH.', 500);
933
+ }
934
+ throw new LatticeError('PROCESS_SPAWN_FAILED', `Failed to spawn Claude process: ${errorMessage}`, 500);
935
+ }
936
+ }
937
+ setupProcessHandlers(streamingId, managedProcess) {
938
+ this.logger.debug(`Setting up ${managedProcess.type.toUpperCase()} process handlers`, { streamingId, pid: managedProcess.pid });
939
+ // Create JSONL parser for Claude output
940
+ const parser = new JsonLinesParser();
941
+ // Initialize output buffer for this session
942
+ this.outputBuffers.set(streamingId, '');
943
+ // Handle parsed JSONL messages from Claude
944
+ parser.on('data', (message) => {
945
+ // Skip echoed stdin messages - PTY echoes our input back
946
+ // Piped processes don't echo, but we still filter non-tool-result user messages
947
+ // BUT: tool_result messages are also type: 'user' (Claude API protocol:
948
+ // user = input TO Claude, assistant = output FROM Claude)
949
+ // We need to let tool_result messages through so the UI can update tool status
950
+ if (message?.type === 'user') {
951
+ const content = message.message?.content;
952
+ const isToolResult = Array.isArray(content) &&
953
+ content.some((block) => block.type === 'tool_result');
954
+ if (!isToolResult) {
955
+ this.logger.debug('Skipping echoed stdin message', {
956
+ streamingId,
957
+ messageType: message?.type
958
+ });
959
+ return;
960
+ }
961
+ // Fall through to emit tool_result messages
962
+ this.logger.debug('Allowing tool_result message through', {
963
+ streamingId,
964
+ toolResultCount: content.filter((b) => b.type === 'tool_result').length
965
+ });
966
+ }
967
+ this.logger.debug('Received Claude message', {
968
+ streamingId,
969
+ messageType: message?.type,
970
+ hasContent: !!message?.content,
971
+ contentLength: message?.content?.length,
972
+ messageKeys: message ? Object.keys(message) : [],
973
+ timestamp: new Date().toISOString()
974
+ });
975
+ this.handleClaudeMessage(streamingId, message);
976
+ });
977
+ parser.on('error', (error) => {
978
+ this.logger.error('Parser error', error, {
979
+ streamingId,
980
+ errorType: error.name,
981
+ errorMessage: error.message,
982
+ bufferState: this.outputBuffers.get(streamingId)?.length || 0
983
+ });
984
+ this.handleProcessError(streamingId, error);
985
+ });
986
+ if (managedProcess.type === 'pty') {
987
+ // Handle PTY data output (combined stdout/stderr from the pseudo-terminal)
988
+ // PTY combines both streams, so we need to parse JSONL from the raw output
989
+ managedProcess.pty.onData((data) => {
990
+ // Log raw PTY data for debugging (debug level - very verbose)
991
+ this.logger.debug('RAW PTY DATA', {
992
+ streamingId,
993
+ dataLength: data.length,
994
+ dataPreview: data.substring(0, 200)
995
+ });
996
+ // Strip ANSI escape sequences that might interfere with JSON parsing
997
+ // Common PTY escape sequences: cursor control, colors, etc.
998
+ const cleanedData = data.replace(/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07/g, '');
999
+ // Feed cleaned data to the JSONL parser
1000
+ if (cleanedData.trim()) {
1001
+ parser.write(cleanedData);
1002
+ }
1003
+ });
1004
+ // Handle PTY process exit (node-pty uses onExit, not EventEmitter 'exit')
1005
+ managedProcess.pty.onExit(({ exitCode }) => {
1006
+ this.logger.debug('PTY process exited', {
1007
+ streamingId,
1008
+ exitCode,
1009
+ normalExit: exitCode === 0,
1010
+ timestamp: new Date().toISOString(),
1011
+ outputBuffer: this.outputBuffers.get(streamingId) || 'No output captured'
1012
+ });
1013
+ this.handleProcessClose(streamingId, exitCode);
1014
+ });
1015
+ }
1016
+ else {
1017
+ // Handle piped child process stdout
1018
+ managedProcess.child.stdout?.on('data', (data) => {
1019
+ const str = data.toString();
1020
+ this.logger.debug('RAW PIPE STDOUT', {
1021
+ streamingId,
1022
+ dataLength: str.length,
1023
+ dataPreview: str.substring(0, 200)
1024
+ });
1025
+ if (str.trim()) {
1026
+ parser.write(str);
1027
+ }
1028
+ });
1029
+ // Handle piped child process stderr
1030
+ managedProcess.child.stderr?.on('data', (data) => {
1031
+ this.logger.warn('Claude CLI stderr (piped)', { streamingId, stderr: data.toString() });
1032
+ });
1033
+ // Handle piped process close
1034
+ managedProcess.child.on('close', (code) => {
1035
+ this.logger.debug('Piped process closed', {
1036
+ streamingId,
1037
+ exitCode: code,
1038
+ normalExit: code === 0,
1039
+ timestamp: new Date().toISOString(),
1040
+ outputBuffer: this.outputBuffers.get(streamingId) || 'No output captured'
1041
+ });
1042
+ this.handleProcessClose(streamingId, code);
1043
+ });
1044
+ // Handle piped process error
1045
+ managedProcess.child.on('error', (error) => {
1046
+ this.handleProcessError(streamingId, error);
1047
+ });
1048
+ }
1049
+ }
1050
+ handleClaudeMessage(streamingId, message) {
1051
+ this.logger.debug('Handling Claude message', {
1052
+ streamingId,
1053
+ messageType: message?.type,
1054
+ isError: message?.type === 'error',
1055
+ isResult: message?.type === 'result'
1056
+ });
1057
+ // Log important message types to process event log
1058
+ const messageType = message?.type;
1059
+ const messageSubtype = 'subtype' in message ? message.subtype : undefined;
1060
+ this.processEventLog.message({
1061
+ traceId: streamingId,
1062
+ streamingId,
1063
+ messageType: messageType || 'unknown',
1064
+ messageSubtype,
1065
+ });
1066
+ // Log result messages specifically
1067
+ if (messageType === 'result') {
1068
+ this.processEventLog.result({
1069
+ traceId: streamingId,
1070
+ streamingId,
1071
+ resultSubtype: messageSubtype,
1072
+ });
1073
+ }
1074
+ // Log tool use from assistant content blocks
1075
+ if (messageType === 'assistant' && 'message' in message && message.message?.content) {
1076
+ const content = message.message.content;
1077
+ if (Array.isArray(content)) {
1078
+ for (const block of content) {
1079
+ if (block.type === 'tool_use' && block.name && block.id) {
1080
+ this.processEventLog.toolUse({
1081
+ traceId: streamingId,
1082
+ streamingId,
1083
+ toolName: block.name,
1084
+ toolUseId: block.id,
1085
+ });
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+ this.emit('claude-message', { streamingId, message });
1091
+ // When we receive a 'result' message, the conversation turn is complete.
1092
+ // For PTY, we don't need to close stdin - the process will exit naturally after result.
1093
+ // The PTY's onExit handler will clean up.
1094
+ if (message?.type === 'result') {
1095
+ this.logger.debug('Received result message, process will exit naturally', {
1096
+ streamingId,
1097
+ resultSubtype: 'subtype' in message ? message.subtype : undefined
1098
+ });
1099
+ }
1100
+ }
1101
+ handleProcessClose(streamingId, code) {
1102
+ // Log process exit
1103
+ this.processEventLog.processExit({
1104
+ traceId: streamingId,
1105
+ streamingId,
1106
+ exitCode: code,
1107
+ });
1108
+ // Clear any pending timeouts for this session
1109
+ const timeouts = this.timeouts.get(streamingId);
1110
+ if (timeouts) {
1111
+ timeouts.forEach(timeout => clearTimeout(timeout));
1112
+ this.timeouts.delete(streamingId);
1113
+ }
1114
+ this.processes.delete(streamingId);
1115
+ this.outputBuffers.delete(streamingId);
1116
+ const config = this.conversationConfigs.get(streamingId);
1117
+ this.conversationConfigs.delete(streamingId);
1118
+ // Send notification if service is available
1119
+ if (this.notificationService && config) {
1120
+ // Get session ID from conversation status or config
1121
+ const sessionId = this.statusTracker.getSessionId(streamingId) || 'unknown';
1122
+ // Try to get conversation metadata for summary
1123
+ this.historyReader.fetchConversationDirect(sessionId)
1124
+ .then(({ metadata }) => {
1125
+ if (this.notificationService && metadata) {
1126
+ return this.notificationService.sendConversationEndNotification(streamingId, sessionId, metadata.summary);
1127
+ }
1128
+ })
1129
+ .catch((error) => {
1130
+ this.logger.error('Failed to send conversation end notification', error);
1131
+ });
1132
+ }
1133
+ this.emit('process-closed', { streamingId, code });
1134
+ }
1135
+ handleProcessError(streamingId, error) {
1136
+ const errorMessage = error.toString();
1137
+ const isBuffer = Buffer.isBuffer(error);
1138
+ this.logger.error('Process error occurred', {
1139
+ streamingId,
1140
+ error: errorMessage,
1141
+ errorType: isBuffer ? 'stderr-output' : error.constructor.name,
1142
+ errorLength: errorMessage.length,
1143
+ processStillActive: this.processes.has(streamingId),
1144
+ timestamp: new Date().toISOString()
1145
+ });
1146
+ this.emit('process-error', { streamingId, error: errorMessage });
1147
+ }
1148
+ /**
1149
+ * Send an answer to an AskUserQuestion tool via stdin.
1150
+ * The Claude CLI with --input-format stream-json expects a tool_result message.
1151
+ *
1152
+ * Per the SDK docs, the answer format must include both the original questions
1153
+ * and an answers object mapping question text to selected option labels.
1154
+ */
1155
+ sendQuestionAnswer(streamingId, toolUseId, questions, answers) {
1156
+ const process = this.processes.get(streamingId);
1157
+ if (!process) {
1158
+ this.logger.warn('Cannot send question answer: process not found', { streamingId });
1159
+ return false;
1160
+ }
1161
+ const sessionId = this.sessionIds.get(streamingId);
1162
+ if (!sessionId) {
1163
+ this.logger.warn('Cannot send question answer: session ID not found', { streamingId });
1164
+ return false;
1165
+ }
1166
+ // Build the tool result content per SDK docs:
1167
+ // { questions: [...], answers: { "Question text?": "Selected label" } }
1168
+ const toolResultContent = JSON.stringify({
1169
+ questions,
1170
+ answers
1171
+ });
1172
+ // Format as a tool_result message per Claude CLI stream-json protocol
1173
+ const stdinMessage = JSON.stringify({
1174
+ type: 'user',
1175
+ message: {
1176
+ role: 'user',
1177
+ content: [{
1178
+ type: 'tool_result',
1179
+ tool_use_id: toolUseId,
1180
+ content: toolResultContent
1181
+ }]
1182
+ },
1183
+ session_id: sessionId
1184
+ });
1185
+ this.logger.info('Sending question answer via stdin', {
1186
+ streamingId,
1187
+ sessionId,
1188
+ toolUseId,
1189
+ answerCount: Object.keys(answers).length,
1190
+ stdinMessagePreview: stdinMessage.substring(0, 500),
1191
+ fullMessage: stdinMessage
1192
+ });
1193
+ // Write to PTY stdin with newline
1194
+ process.write(stdinMessage + '\n');
1195
+ this.logger.info('Question answer written to PTY stdin', { streamingId, toolUseId });
1196
+ return true;
1197
+ }
1198
+ /**
1199
+ * Send a raw stdin message to a Claude process.
1200
+ * Used by ClaudiaService for sending messages to Claudia's session.
1201
+ * The message should already be formatted as a JSON string.
1202
+ */
1203
+ sendStdinMessage(streamingId, message) {
1204
+ const process = this.processes.get(streamingId);
1205
+ if (!process) {
1206
+ this.logger.warn('Cannot send stdin message: process not found', { streamingId });
1207
+ return false;
1208
+ }
1209
+ this.logger.info('Sending stdin message', {
1210
+ streamingId,
1211
+ messageLength: message.length
1212
+ });
1213
+ // Write to PTY stdin with newline
1214
+ process.write(message + '\n');
1215
+ return true;
1216
+ }
1217
+ }
1218
+ //# sourceMappingURL=claude-process-manager.js.map