specmem-hardwicksoftware 3.5.99

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 (1473) hide show
  1. package/CHANGELOG.md +299 -0
  2. package/LICENSE.md +6406 -0
  3. package/README.md +539 -0
  4. package/bin/AegisTheme.cjs +1022 -0
  5. package/bin/AnsiRenderer.cjs +1055 -0
  6. package/bin/BoxRenderer.cjs +605 -0
  7. package/bin/ClaudeLiveScreen.cjs +1299 -0
  8. package/bin/DashboardModules.cjs +733 -0
  9. package/bin/LiveScreenCapture.cjs +1012 -0
  10. package/bin/MemoryBrowserScreen.cjs +1595 -0
  11. package/bin/TabManager.cjs +1414 -0
  12. package/bin/checkAgentStatus-fix.patch +30 -0
  13. package/bin/mcp-socket-client.cjs +462 -0
  14. package/bin/screen-utils.cjs +106 -0
  15. package/bin/specmem-autoclaude.cjs +663 -0
  16. package/bin/specmem-cleanup.cjs +421 -0
  17. package/bin/specmem-cli.cjs +794 -0
  18. package/bin/specmem-console-teamcomms-class.cjs +428 -0
  19. package/bin/specmem-console.cjs +8104 -0
  20. package/bin/specmem-statusbar.cjs +530 -0
  21. package/bootstrap.cjs +5065 -0
  22. package/claude-hooks/agent-chooser-hook.js +179 -0
  23. package/claude-hooks/agent-chooser-inject.js +121 -0
  24. package/claude-hooks/agent-loading-hook.js +990 -0
  25. package/claude-hooks/agent-output-fader.cjs +542 -0
  26. package/claude-hooks/agent-output-interceptor.js +193 -0
  27. package/claude-hooks/agent-type-matcher.js +419 -0
  28. package/claude-hooks/auto-bypass.py +74 -0
  29. package/claude-hooks/background-completion-silencer.js +134 -0
  30. package/claude-hooks/bash-auto-background.js +182 -0
  31. package/claude-hooks/build-cedict-dictionary.mjs +167 -0
  32. package/claude-hooks/bullshit-radar.cjs +323 -0
  33. package/claude-hooks/cedict-codes.json +270 -0
  34. package/claude-hooks/cedict-extracted.json +22632 -0
  35. package/claude-hooks/claude-watchdog.sh +401 -0
  36. package/claude-hooks/context-dedup.cjs +144 -0
  37. package/claude-hooks/context-yeeter.cjs +244 -0
  38. package/claude-hooks/debug-suffix.cjs +15 -0
  39. package/claude-hooks/debug2.cjs +7 -0
  40. package/claude-hooks/drilldown-enforcer.js +242 -0
  41. package/claude-hooks/english-morphology-standalone.cjs +149 -0
  42. package/claude-hooks/english-morphology.cjs +152 -0
  43. package/claude-hooks/extract-translations.mjs +193 -0
  44. package/claude-hooks/file-claim-enforcer.cjs +293 -0
  45. package/claude-hooks/file-claim-enforcer.js +293 -0
  46. package/claude-hooks/find-collisions.cjs +39 -0
  47. package/claude-hooks/fix-abbreviations.cjs +60 -0
  48. package/claude-hooks/fix-collisions.cjs +60 -0
  49. package/claude-hooks/fix-decompressor.cjs +79 -0
  50. package/claude-hooks/fix-suffixes.cjs +66 -0
  51. package/claude-hooks/grammar-engine.cjs +159 -0
  52. package/claude-hooks/input-aware-improver.js +231 -0
  53. package/claude-hooks/is-agent.cjs +64 -0
  54. package/claude-hooks/mega-test.cjs +213 -0
  55. package/claude-hooks/merge-dictionaries.mjs +207 -0
  56. package/claude-hooks/merged-codes.cjs +22675 -0
  57. package/claude-hooks/merged-codes.json +22676 -0
  58. package/claude-hooks/output-cleaner.cjs +388 -0
  59. package/claude-hooks/post-write-memory-hook.cjs +430 -0
  60. package/claude-hooks/quick-test.cjs +24 -0
  61. package/claude-hooks/quick-test2.cjs +24 -0
  62. package/claude-hooks/remove-bad-codes.cjs +23 -0
  63. package/claude-hooks/search-reminder-hook.js +90 -0
  64. package/claude-hooks/semantic-test.cjs +93 -0
  65. package/claude-hooks/settings.json +445 -0
  66. package/claude-hooks/smart-context-hook.cjs +547 -0
  67. package/claude-hooks/smart-context-hook.js +539 -0
  68. package/claude-hooks/smart-search-interceptor.js +364 -0
  69. package/claude-hooks/socket-connect-helper.cjs +235 -0
  70. package/claude-hooks/specmem/sockets/session-start.lock +1 -0
  71. package/claude-hooks/specmem-context-hook.cjs +357 -0
  72. package/claude-hooks/specmem-context-hook.js +357 -0
  73. package/claude-hooks/specmem-drilldown-hook.cjs +480 -0
  74. package/claude-hooks/specmem-drilldown-hook.js +480 -0
  75. package/claude-hooks/specmem-drilldown-setter.js +210 -0
  76. package/claude-hooks/specmem-paths.cjs +213 -0
  77. package/claude-hooks/specmem-precompact.js +183 -0
  78. package/claude-hooks/specmem-session-init.sh +33 -0
  79. package/claude-hooks/specmem-session-start.cjs +498 -0
  80. package/claude-hooks/specmem-stop-hook.cjs +73 -0
  81. package/claude-hooks/specmem-stop-hook.js +5 -0
  82. package/claude-hooks/specmem-team-comms.cjs +434 -0
  83. package/claude-hooks/specmem-team-member-inject.js +271 -0
  84. package/claude-hooks/specmem-unified-hook.py +670 -0
  85. package/claude-hooks/subagent-loading-hook.js +194 -0
  86. package/claude-hooks/sysprompt-squisher.cjs +167 -0
  87. package/claude-hooks/task-progress-hook.js +204 -0
  88. package/claude-hooks/team-comms-enforcer.cjs +585 -0
  89. package/claude-hooks/test-accuracy.cjs +27 -0
  90. package/claude-hooks/test-big.cjs +28 -0
  91. package/claude-hooks/test-inflectors.cjs +39 -0
  92. package/claude-hooks/test-pluralize.cjs +37 -0
  93. package/claude-hooks/test-quick.cjs +8 -0
  94. package/claude-hooks/test-wink.cjs +20 -0
  95. package/claude-hooks/token-compressor.cjs +940 -0
  96. package/claude-hooks/use-code-pointers.cjs +279 -0
  97. package/commands/COMMAND_TOOL_MAP.md +299 -0
  98. package/commands/specmem-agents.md +412 -0
  99. package/commands/specmem-autoclaude.md +295 -0
  100. package/commands/specmem-changes.md +247 -0
  101. package/commands/specmem-code.md +103 -0
  102. package/commands/specmem-configteammembercomms.md +322 -0
  103. package/commands/specmem-drilldown.md +208 -0
  104. package/commands/specmem-find.md +195 -0
  105. package/commands/specmem-getdashboard.md +243 -0
  106. package/commands/specmem-hooks.md +219 -0
  107. package/commands/specmem-pointers.md +149 -0
  108. package/commands/specmem-progress.md +287 -0
  109. package/commands/specmem-remember.md +123 -0
  110. package/commands/specmem-service.md +349 -0
  111. package/commands/specmem-stats.md +189 -0
  112. package/commands/specmem-team-member.md +409 -0
  113. package/commands/specmem-webdev.md +583 -0
  114. package/commands/specmem.md +363 -0
  115. package/dist/autoStart/index.d.ts +214 -0
  116. package/dist/autoStart/index.d.ts.map +1 -0
  117. package/dist/autoStart/index.js +883 -0
  118. package/dist/autoStart/index.js.map +1 -0
  119. package/dist/claude-sessions/contextRestorationParser.d.ts +74 -0
  120. package/dist/claude-sessions/contextRestorationParser.d.ts.map +1 -0
  121. package/dist/claude-sessions/contextRestorationParser.js +570 -0
  122. package/dist/claude-sessions/contextRestorationParser.js.map +1 -0
  123. package/dist/claude-sessions/index.d.ts +13 -0
  124. package/dist/claude-sessions/index.d.ts.map +1 -0
  125. package/dist/claude-sessions/index.js +11 -0
  126. package/dist/claude-sessions/index.js.map +1 -0
  127. package/dist/claude-sessions/sessionIntegration.d.ts +48 -0
  128. package/dist/claude-sessions/sessionIntegration.d.ts.map +1 -0
  129. package/dist/claude-sessions/sessionIntegration.js +146 -0
  130. package/dist/claude-sessions/sessionIntegration.js.map +1 -0
  131. package/dist/claude-sessions/sessionParser.d.ts +293 -0
  132. package/dist/claude-sessions/sessionParser.d.ts.map +1 -0
  133. package/dist/claude-sessions/sessionParser.js +1028 -0
  134. package/dist/claude-sessions/sessionParser.js.map +1 -0
  135. package/dist/claude-sessions/sessionWatcher.d.ts +139 -0
  136. package/dist/claude-sessions/sessionWatcher.d.ts.map +1 -0
  137. package/dist/claude-sessions/sessionWatcher.js +722 -0
  138. package/dist/claude-sessions/sessionWatcher.js.map +1 -0
  139. package/dist/cli/deploy-to-claude.d.ts +56 -0
  140. package/dist/cli/deploy-to-claude.d.ts.map +1 -0
  141. package/dist/cli/deploy-to-claude.js +576 -0
  142. package/dist/cli/deploy-to-claude.js.map +1 -0
  143. package/dist/code-explanations/explainCode.d.ts +86 -0
  144. package/dist/code-explanations/explainCode.d.ts.map +1 -0
  145. package/dist/code-explanations/explainCode.js +286 -0
  146. package/dist/code-explanations/explainCode.js.map +1 -0
  147. package/dist/code-explanations/feedback.d.ts +87 -0
  148. package/dist/code-explanations/feedback.d.ts.map +1 -0
  149. package/dist/code-explanations/feedback.js +212 -0
  150. package/dist/code-explanations/feedback.js.map +1 -0
  151. package/dist/code-explanations/getRelatedCode.d.ts +80 -0
  152. package/dist/code-explanations/getRelatedCode.d.ts.map +1 -0
  153. package/dist/code-explanations/getRelatedCode.js +262 -0
  154. package/dist/code-explanations/getRelatedCode.js.map +1 -0
  155. package/dist/code-explanations/index.d.ts +284 -0
  156. package/dist/code-explanations/index.d.ts.map +1 -0
  157. package/dist/code-explanations/index.js +249 -0
  158. package/dist/code-explanations/index.js.map +1 -0
  159. package/dist/code-explanations/linkCodeToPrompt.d.ts +79 -0
  160. package/dist/code-explanations/linkCodeToPrompt.d.ts.map +1 -0
  161. package/dist/code-explanations/linkCodeToPrompt.js +213 -0
  162. package/dist/code-explanations/linkCodeToPrompt.js.map +1 -0
  163. package/dist/code-explanations/recallExplanation.d.ts +88 -0
  164. package/dist/code-explanations/recallExplanation.d.ts.map +1 -0
  165. package/dist/code-explanations/recallExplanation.js +218 -0
  166. package/dist/code-explanations/recallExplanation.js.map +1 -0
  167. package/dist/code-explanations/schema.d.ts +32 -0
  168. package/dist/code-explanations/schema.d.ts.map +1 -0
  169. package/dist/code-explanations/schema.js +221 -0
  170. package/dist/code-explanations/schema.js.map +1 -0
  171. package/dist/code-explanations/semanticSearch.d.ts +75 -0
  172. package/dist/code-explanations/semanticSearch.d.ts.map +1 -0
  173. package/dist/code-explanations/semanticSearch.js +203 -0
  174. package/dist/code-explanations/semanticSearch.js.map +1 -0
  175. package/dist/code-explanations/types.d.ts +328 -0
  176. package/dist/code-explanations/types.d.ts.map +1 -0
  177. package/dist/code-explanations/types.js +122 -0
  178. package/dist/code-explanations/types.js.map +1 -0
  179. package/dist/codebase/codeAnalyzer.d.ts +272 -0
  180. package/dist/codebase/codeAnalyzer.d.ts.map +1 -0
  181. package/dist/codebase/codeAnalyzer.js +1353 -0
  182. package/dist/codebase/codeAnalyzer.js.map +1 -0
  183. package/dist/codebase/codebaseIndexer.d.ts +360 -0
  184. package/dist/codebase/codebaseIndexer.d.ts.map +1 -0
  185. package/dist/codebase/codebaseIndexer.js +1735 -0
  186. package/dist/codebase/codebaseIndexer.js.map +1 -0
  187. package/dist/codebase/codebaseTools.d.ts +853 -0
  188. package/dist/codebase/codebaseTools.d.ts.map +1 -0
  189. package/dist/codebase/codebaseTools.js +1279 -0
  190. package/dist/codebase/codebaseTools.js.map +1 -0
  191. package/dist/codebase/exclusions.d.ts +111 -0
  192. package/dist/codebase/exclusions.d.ts.map +1 -0
  193. package/dist/codebase/exclusions.js +771 -0
  194. package/dist/codebase/exclusions.js.map +1 -0
  195. package/dist/codebase/fileWatcher.d.ts +135 -0
  196. package/dist/codebase/fileWatcher.d.ts.map +1 -0
  197. package/dist/codebase/fileWatcher.js +309 -0
  198. package/dist/codebase/fileWatcher.js.map +1 -0
  199. package/dist/codebase/index.d.ts +33 -0
  200. package/dist/codebase/index.d.ts.map +1 -0
  201. package/dist/codebase/index.js +77 -0
  202. package/dist/codebase/index.js.map +1 -0
  203. package/dist/codebase/ingestion.d.ts +177 -0
  204. package/dist/codebase/ingestion.d.ts.map +1 -0
  205. package/dist/codebase/ingestion.js +690 -0
  206. package/dist/codebase/ingestion.js.map +1 -0
  207. package/dist/codebase/languageDetection.d.ts +75 -0
  208. package/dist/codebase/languageDetection.d.ts.map +1 -0
  209. package/dist/codebase/languageDetection.js +768 -0
  210. package/dist/codebase/languageDetection.js.map +1 -0
  211. package/dist/commands/codebaseCommands.d.ts +101 -0
  212. package/dist/commands/codebaseCommands.d.ts.map +1 -0
  213. package/dist/commands/codebaseCommands.js +911 -0
  214. package/dist/commands/codebaseCommands.js.map +1 -0
  215. package/dist/commands/commandHandler.d.ts +126 -0
  216. package/dist/commands/commandHandler.d.ts.map +1 -0
  217. package/dist/commands/commandHandler.js +296 -0
  218. package/dist/commands/commandHandler.js.map +1 -0
  219. package/dist/commands/commandLoader.d.ts +103 -0
  220. package/dist/commands/commandLoader.d.ts.map +1 -0
  221. package/dist/commands/commandLoader.js +223 -0
  222. package/dist/commands/commandLoader.js.map +1 -0
  223. package/dist/commands/contextCommands.d.ts +83 -0
  224. package/dist/commands/contextCommands.d.ts.map +1 -0
  225. package/dist/commands/contextCommands.js +512 -0
  226. package/dist/commands/contextCommands.js.map +1 -0
  227. package/dist/commands/index.d.ts +24 -0
  228. package/dist/commands/index.d.ts.map +1 -0
  229. package/dist/commands/index.js +28 -0
  230. package/dist/commands/index.js.map +1 -0
  231. package/dist/commands/mcpResources.d.ts +50 -0
  232. package/dist/commands/mcpResources.d.ts.map +1 -0
  233. package/dist/commands/mcpResources.js +372 -0
  234. package/dist/commands/mcpResources.js.map +1 -0
  235. package/dist/commands/memoryCommands.d.ts +74 -0
  236. package/dist/commands/memoryCommands.d.ts.map +1 -0
  237. package/dist/commands/memoryCommands.js +609 -0
  238. package/dist/commands/memoryCommands.js.map +1 -0
  239. package/dist/commands/promptCommands.d.ts +91 -0
  240. package/dist/commands/promptCommands.d.ts.map +1 -0
  241. package/dist/commands/promptCommands.js +801 -0
  242. package/dist/commands/promptCommands.js.map +1 -0
  243. package/dist/commands/teamMemberCommands.d.ts +21 -0
  244. package/dist/commands/teamMemberCommands.d.ts.map +1 -0
  245. package/dist/commands/teamMemberCommands.js +137 -0
  246. package/dist/commands/teamMemberCommands.js.map +1 -0
  247. package/dist/comms/fileCommsTransport.d.ts +91 -0
  248. package/dist/comms/fileCommsTransport.d.ts.map +1 -0
  249. package/dist/comms/fileCommsTransport.js +244 -0
  250. package/dist/comms/fileCommsTransport.js.map +1 -0
  251. package/dist/comms/index.d.ts +7 -0
  252. package/dist/comms/index.d.ts.map +1 -0
  253. package/dist/comms/index.js +7 -0
  254. package/dist/comms/index.js.map +1 -0
  255. package/dist/config/apiKeyDetection.d.ts +41 -0
  256. package/dist/config/apiKeyDetection.d.ts.map +1 -0
  257. package/dist/config/apiKeyDetection.js +211 -0
  258. package/dist/config/apiKeyDetection.js.map +1 -0
  259. package/dist/config/autoConfig.d.ts +188 -0
  260. package/dist/config/autoConfig.d.ts.map +1 -0
  261. package/dist/config/autoConfig.js +850 -0
  262. package/dist/config/autoConfig.js.map +1 -0
  263. package/dist/config/configSync.d.ts +119 -0
  264. package/dist/config/configSync.d.ts.map +1 -0
  265. package/dist/config/configSync.js +878 -0
  266. package/dist/config/configSync.js.map +1 -0
  267. package/dist/config/embeddingTimeouts.d.ts +145 -0
  268. package/dist/config/embeddingTimeouts.d.ts.map +1 -0
  269. package/dist/config/embeddingTimeouts.js +255 -0
  270. package/dist/config/embeddingTimeouts.js.map +1 -0
  271. package/dist/config/index.d.ts +5 -0
  272. package/dist/config/index.d.ts.map +1 -0
  273. package/dist/config/index.js +7 -0
  274. package/dist/config/index.js.map +1 -0
  275. package/dist/config/languageConfig.d.ts +68 -0
  276. package/dist/config/languageConfig.d.ts.map +1 -0
  277. package/dist/config/languageConfig.js +473 -0
  278. package/dist/config/languageConfig.js.map +1 -0
  279. package/dist/config/password.d.ts +145 -0
  280. package/dist/config/password.d.ts.map +1 -0
  281. package/dist/config/password.js +428 -0
  282. package/dist/config/password.js.map +1 -0
  283. package/dist/config.d.ts +338 -0
  284. package/dist/config.d.ts.map +1 -0
  285. package/dist/config.js +1177 -0
  286. package/dist/config.js.map +1 -0
  287. package/dist/consolidation.d.ts +44 -0
  288. package/dist/consolidation.d.ts.map +1 -0
  289. package/dist/consolidation.js +447 -0
  290. package/dist/consolidation.js.map +1 -0
  291. package/dist/constants.d.ts +371 -0
  292. package/dist/constants.d.ts.map +1 -0
  293. package/dist/constants.js +552 -0
  294. package/dist/constants.js.map +1 -0
  295. package/dist/coordination/TeamMemberRegistry.d.ts +192 -0
  296. package/dist/coordination/TeamMemberRegistry.d.ts.map +1 -0
  297. package/dist/coordination/TeamMemberRegistry.js +415 -0
  298. package/dist/coordination/TeamMemberRegistry.js.map +1 -0
  299. package/dist/coordination/events.d.ts +369 -0
  300. package/dist/coordination/events.d.ts.map +1 -0
  301. package/dist/coordination/events.js +232 -0
  302. package/dist/coordination/events.js.map +1 -0
  303. package/dist/coordination/handlers.d.ts +116 -0
  304. package/dist/coordination/handlers.d.ts.map +1 -0
  305. package/dist/coordination/handlers.js +400 -0
  306. package/dist/coordination/handlers.js.map +1 -0
  307. package/dist/coordination/index.d.ts +14 -0
  308. package/dist/coordination/index.d.ts.map +1 -0
  309. package/dist/coordination/index.js +31 -0
  310. package/dist/coordination/index.js.map +1 -0
  311. package/dist/coordination/integration.d.ts +260 -0
  312. package/dist/coordination/integration.d.ts.map +1 -0
  313. package/dist/coordination/integration.js +472 -0
  314. package/dist/coordination/integration.js.map +1 -0
  315. package/dist/coordination/server.d.ts +266 -0
  316. package/dist/coordination/server.d.ts.map +1 -0
  317. package/dist/coordination/server.js +995 -0
  318. package/dist/coordination/server.js.map +1 -0
  319. package/dist/coordination/serviceProvider.d.ts +70 -0
  320. package/dist/coordination/serviceProvider.d.ts.map +1 -0
  321. package/dist/coordination/serviceProvider.js +273 -0
  322. package/dist/coordination/serviceProvider.js.map +1 -0
  323. package/dist/dashboard/api/claudeControl.d.ts +44 -0
  324. package/dist/dashboard/api/claudeControl.d.ts.map +1 -0
  325. package/dist/dashboard/api/claudeControl.js +650 -0
  326. package/dist/dashboard/api/claudeControl.js.map +1 -0
  327. package/dist/dashboard/api/claudeHistory.d.ts +4 -0
  328. package/dist/dashboard/api/claudeHistory.d.ts.map +1 -0
  329. package/dist/dashboard/api/claudeHistory.js +319 -0
  330. package/dist/dashboard/api/claudeHistory.js.map +1 -0
  331. package/dist/dashboard/api/dataExport.d.ts +23 -0
  332. package/dist/dashboard/api/dataExport.d.ts.map +1 -0
  333. package/dist/dashboard/api/dataExport.js +509 -0
  334. package/dist/dashboard/api/dataExport.js.map +1 -0
  335. package/dist/dashboard/api/fileManager.d.ts +39 -0
  336. package/dist/dashboard/api/fileManager.d.ts.map +1 -0
  337. package/dist/dashboard/api/fileManager.js +814 -0
  338. package/dist/dashboard/api/fileManager.js.map +1 -0
  339. package/dist/dashboard/api/hooks.d.ts +16 -0
  340. package/dist/dashboard/api/hooks.d.ts.map +1 -0
  341. package/dist/dashboard/api/hooks.js +342 -0
  342. package/dist/dashboard/api/hooks.js.map +1 -0
  343. package/dist/dashboard/api/hotReload.d.ts +14 -0
  344. package/dist/dashboard/api/hotReload.d.ts.map +1 -0
  345. package/dist/dashboard/api/hotReload.js +219 -0
  346. package/dist/dashboard/api/hotReload.js.map +1 -0
  347. package/dist/dashboard/api/liveSessionStream.d.ts +19 -0
  348. package/dist/dashboard/api/liveSessionStream.d.ts.map +1 -0
  349. package/dist/dashboard/api/liveSessionStream.js +430 -0
  350. package/dist/dashboard/api/liveSessionStream.js.map +1 -0
  351. package/dist/dashboard/api/memoryRecall.d.ts +20 -0
  352. package/dist/dashboard/api/memoryRecall.d.ts.map +1 -0
  353. package/dist/dashboard/api/memoryRecall.js +524 -0
  354. package/dist/dashboard/api/memoryRecall.js.map +1 -0
  355. package/dist/dashboard/api/promptSend.d.ts +33 -0
  356. package/dist/dashboard/api/promptSend.d.ts.map +1 -0
  357. package/dist/dashboard/api/promptSend.js +544 -0
  358. package/dist/dashboard/api/promptSend.js.map +1 -0
  359. package/dist/dashboard/api/settings.d.ts +10 -0
  360. package/dist/dashboard/api/settings.d.ts.map +1 -0
  361. package/dist/dashboard/api/settings.js +656 -0
  362. package/dist/dashboard/api/settings.js.map +1 -0
  363. package/dist/dashboard/api/setup.d.ts +21 -0
  364. package/dist/dashboard/api/setup.d.ts.map +1 -0
  365. package/dist/dashboard/api/setup.js +663 -0
  366. package/dist/dashboard/api/setup.js.map +1 -0
  367. package/dist/dashboard/api/specmemTools.d.ts +14 -0
  368. package/dist/dashboard/api/specmemTools.d.ts.map +1 -0
  369. package/dist/dashboard/api/specmemTools.js +1059 -0
  370. package/dist/dashboard/api/specmemTools.js.map +1 -0
  371. package/dist/dashboard/api/taskTeamMembers.d.ts +8 -0
  372. package/dist/dashboard/api/taskTeamMembers.d.ts.map +1 -0
  373. package/dist/dashboard/api/taskTeamMembers.js +136 -0
  374. package/dist/dashboard/api/taskTeamMembers.js.map +1 -0
  375. package/dist/dashboard/api/teamMemberDeploy.d.ts +15 -0
  376. package/dist/dashboard/api/teamMemberDeploy.d.ts.map +1 -0
  377. package/dist/dashboard/api/teamMemberDeploy.js +421 -0
  378. package/dist/dashboard/api/teamMemberDeploy.js.map +1 -0
  379. package/dist/dashboard/api/teamMemberHistory.d.ts +38 -0
  380. package/dist/dashboard/api/teamMemberHistory.d.ts.map +1 -0
  381. package/dist/dashboard/api/teamMemberHistory.js +583 -0
  382. package/dist/dashboard/api/teamMemberHistory.js.map +1 -0
  383. package/dist/dashboard/api/terminal.d.ts +12 -0
  384. package/dist/dashboard/api/terminal.d.ts.map +1 -0
  385. package/dist/dashboard/api/terminal.js +344 -0
  386. package/dist/dashboard/api/terminal.js.map +1 -0
  387. package/dist/dashboard/api/terminalInject.d.ts +17 -0
  388. package/dist/dashboard/api/terminalInject.d.ts.map +1 -0
  389. package/dist/dashboard/api/terminalInject.js +322 -0
  390. package/dist/dashboard/api/terminalInject.js.map +1 -0
  391. package/dist/dashboard/api/terminalStream.d.ts +12 -0
  392. package/dist/dashboard/api/terminalStream.d.ts.map +1 -0
  393. package/dist/dashboard/api/terminalStream.js +482 -0
  394. package/dist/dashboard/api/terminalStream.js.map +1 -0
  395. package/dist/dashboard/index.d.ts +7 -0
  396. package/dist/dashboard/index.d.ts.map +1 -0
  397. package/dist/dashboard/index.js +7 -0
  398. package/dist/dashboard/index.js.map +1 -0
  399. package/dist/dashboard/ptyStreamer.d.ts +173 -0
  400. package/dist/dashboard/ptyStreamer.d.ts.map +1 -0
  401. package/dist/dashboard/ptyStreamer.js +661 -0
  402. package/dist/dashboard/ptyStreamer.js.map +1 -0
  403. package/dist/dashboard/public/DASHBOARD-README.md +378 -0
  404. package/dist/dashboard/public/INTEGRATION-GUIDE.md +395 -0
  405. package/dist/dashboard/public/codebase-config.html +1247 -0
  406. package/dist/dashboard/public/dashboard-v2.html +1942 -0
  407. package/dist/dashboard/public/data-export.html +819 -0
  408. package/dist/dashboard/public/example-page.html +164 -0
  409. package/dist/dashboard/public/file-explorer.html +1023 -0
  410. package/dist/dashboard/public/hooks.html +1103 -0
  411. package/dist/dashboard/public/index-improvements.css +499 -0
  412. package/dist/dashboard/public/index.html +5534 -0
  413. package/dist/dashboard/public/memory-controls.html +1959 -0
  414. package/dist/dashboard/public/memory-recall.html +1495 -0
  415. package/dist/dashboard/public/previews/skeleton-memory-graph.html +361 -0
  416. package/dist/dashboard/public/previews/skeleton-memory-list.html +366 -0
  417. package/dist/dashboard/public/previews/skeleton-search-results.html +609 -0
  418. package/dist/dashboard/public/previews/skeleton-stats-dashboard.html +556 -0
  419. package/dist/dashboard/public/prompt-console.html +2763 -0
  420. package/dist/dashboard/public/react-dist/assets/index-CkjobT5B.js +871 -0
  421. package/dist/dashboard/public/react-dist/assets/index-iRclxMst.css +1 -0
  422. package/dist/dashboard/public/react-dist/index.html +16 -0
  423. package/dist/dashboard/public/shared-header.js +325 -0
  424. package/dist/dashboard/public/shared-language-selector.js +626 -0
  425. package/dist/dashboard/public/shared-logger.js +66 -0
  426. package/dist/dashboard/public/shared-nav.js +325 -0
  427. package/dist/dashboard/public/shared-theme-blue.css +331 -0
  428. package/dist/dashboard/public/shared-theme.css +813 -0
  429. package/dist/dashboard/public/shared-toast.js +415 -0
  430. package/dist/dashboard/public/team-member-history.html +1291 -0
  431. package/dist/dashboard/public/team-member-spy.html +1199 -0
  432. package/dist/dashboard/public/team-members.html +3756 -0
  433. package/dist/dashboard/public/terminal-output.html +1013 -0
  434. package/dist/dashboard/public/terminal.html +372 -0
  435. package/dist/dashboard/sessionStore.d.ts +86 -0
  436. package/dist/dashboard/sessionStore.d.ts.map +1 -0
  437. package/dist/dashboard/sessionStore.js +262 -0
  438. package/dist/dashboard/sessionStore.js.map +1 -0
  439. package/dist/dashboard/standalone.d.ts +27 -0
  440. package/dist/dashboard/standalone.d.ts.map +1 -0
  441. package/dist/dashboard/standalone.js +380 -0
  442. package/dist/dashboard/standalone.js.map +1 -0
  443. package/dist/dashboard/webServer.d.ts +390 -0
  444. package/dist/dashboard/webServer.d.ts.map +1 -0
  445. package/dist/dashboard/webServer.js +4297 -0
  446. package/dist/dashboard/webServer.js.map +1 -0
  447. package/dist/dashboard/websocket/teamMemberStream.d.ts +87 -0
  448. package/dist/dashboard/websocket/teamMemberStream.d.ts.map +1 -0
  449. package/dist/dashboard/websocket/teamMemberStream.js +366 -0
  450. package/dist/dashboard/websocket/teamMemberStream.js.map +1 -0
  451. package/dist/dashboard/websocket/terminalStream.d.ts +130 -0
  452. package/dist/dashboard/websocket/terminalStream.d.ts.map +1 -0
  453. package/dist/dashboard/websocket/terminalStream.js +456 -0
  454. package/dist/dashboard/websocket/terminalStream.js.map +1 -0
  455. package/dist/database/embeddedPostgres.d.ts +187 -0
  456. package/dist/database/embeddedPostgres.d.ts.map +1 -0
  457. package/dist/database/embeddedPostgres.js +763 -0
  458. package/dist/database/embeddedPostgres.js.map +1 -0
  459. package/dist/database/index.d.ts +12 -0
  460. package/dist/database/index.d.ts.map +1 -0
  461. package/dist/database/index.js +20 -0
  462. package/dist/database/index.js.map +1 -0
  463. package/dist/database/initEmbeddedPostgres.d.ts +124 -0
  464. package/dist/database/initEmbeddedPostgres.d.ts.map +1 -0
  465. package/dist/database/initEmbeddedPostgres.js +855 -0
  466. package/dist/database/initEmbeddedPostgres.js.map +1 -0
  467. package/dist/database.d.ts +256 -0
  468. package/dist/database.d.ts.map +1 -0
  469. package/dist/database.js +1209 -0
  470. package/dist/database.js.map +1 -0
  471. package/dist/db/apiDataManager.d.ts +334 -0
  472. package/dist/db/apiDataManager.d.ts.map +1 -0
  473. package/dist/db/apiDataManager.js +631 -0
  474. package/dist/db/apiDataManager.js.map +1 -0
  475. package/dist/db/batchOperations.d.ts +154 -0
  476. package/dist/db/batchOperations.d.ts.map +1 -0
  477. package/dist/db/batchOperations.js +564 -0
  478. package/dist/db/batchOperations.js.map +1 -0
  479. package/dist/db/bigBrainMigrations.d.ts +48 -0
  480. package/dist/db/bigBrainMigrations.d.ts.map +1 -0
  481. package/dist/db/bigBrainMigrations.js +4266 -0
  482. package/dist/db/bigBrainMigrations.js.map +1 -0
  483. package/dist/db/connectionPoolGoBrrr.d.ts +94 -0
  484. package/dist/db/connectionPoolGoBrrr.d.ts.map +1 -0
  485. package/dist/db/connectionPoolGoBrrr.js +548 -0
  486. package/dist/db/connectionPoolGoBrrr.js.map +1 -0
  487. package/dist/db/dashboardQueries.d.ts +182 -0
  488. package/dist/db/dashboardQueries.d.ts.map +1 -0
  489. package/dist/db/dashboardQueries.js +821 -0
  490. package/dist/db/dashboardQueries.js.map +1 -0
  491. package/dist/db/deploymentBootstrap.d.ts +43 -0
  492. package/dist/db/deploymentBootstrap.d.ts.map +1 -0
  493. package/dist/db/deploymentBootstrap.js +329 -0
  494. package/dist/db/deploymentBootstrap.js.map +1 -0
  495. package/dist/db/dimensionService.d.ts +140 -0
  496. package/dist/db/dimensionService.d.ts.map +1 -0
  497. package/dist/db/dimensionService.js +261 -0
  498. package/dist/db/dimensionService.js.map +1 -0
  499. package/dist/db/embeddingOverflow.d.ts +69 -0
  500. package/dist/db/embeddingOverflow.d.ts.map +1 -0
  501. package/dist/db/embeddingOverflow.js +332 -0
  502. package/dist/db/embeddingOverflow.js.map +1 -0
  503. package/dist/db/embeddingOverflow.sql +221 -0
  504. package/dist/db/findThatShit.d.ts +145 -0
  505. package/dist/db/findThatShit.d.ts.map +1 -0
  506. package/dist/db/findThatShit.js +782 -0
  507. package/dist/db/findThatShit.js.map +1 -0
  508. package/dist/db/hotPathManager.d.ts +187 -0
  509. package/dist/db/hotPathManager.d.ts.map +1 -0
  510. package/dist/db/hotPathManager.js +504 -0
  511. package/dist/db/hotPathManager.js.map +1 -0
  512. package/dist/db/index.d.ts +85 -0
  513. package/dist/db/index.d.ts.map +1 -0
  514. package/dist/db/index.js +219 -0
  515. package/dist/db/index.js.map +1 -0
  516. package/dist/db/memoryDrilldown.sql +99 -0
  517. package/dist/db/migrate.d.ts +3 -0
  518. package/dist/db/migrate.d.ts.map +1 -0
  519. package/dist/db/migrate.js +97 -0
  520. package/dist/db/migrate.js.map +1 -0
  521. package/dist/db/migrateJsonToPostgres.d.ts +43 -0
  522. package/dist/db/migrateJsonToPostgres.d.ts.map +1 -0
  523. package/dist/db/migrateJsonToPostgres.js +465 -0
  524. package/dist/db/migrateJsonToPostgres.js.map +1 -0
  525. package/dist/db/nukeFromOrbit.d.ts +63 -0
  526. package/dist/db/nukeFromOrbit.d.ts.map +1 -0
  527. package/dist/db/nukeFromOrbit.js +499 -0
  528. package/dist/db/nukeFromOrbit.js.map +1 -0
  529. package/dist/db/processedTraining.sql +60 -0
  530. package/dist/db/projectNamespacing.d.ts +258 -0
  531. package/dist/db/projectNamespacing.d.ts.map +1 -0
  532. package/dist/db/projectNamespacing.js +920 -0
  533. package/dist/db/projectNamespacing.js.map +1 -0
  534. package/dist/db/projectNamespacing.sql +374 -0
  535. package/dist/db/projectSchemaInit.sql +271 -0
  536. package/dist/db/spatialMemory.d.ts +296 -0
  537. package/dist/db/spatialMemory.d.ts.map +1 -0
  538. package/dist/db/spatialMemory.js +818 -0
  539. package/dist/db/spatialMemory.js.map +1 -0
  540. package/dist/db/streamingQuery.d.ts +143 -0
  541. package/dist/db/streamingQuery.d.ts.map +1 -0
  542. package/dist/db/streamingQuery.js +350 -0
  543. package/dist/db/streamingQuery.js.map +1 -0
  544. package/dist/db/teamComms.sql +224 -0
  545. package/dist/db/yeetStuffInDb.d.ts +72 -0
  546. package/dist/db/yeetStuffInDb.d.ts.map +1 -0
  547. package/dist/db/yeetStuffInDb.js +473 -0
  548. package/dist/db/yeetStuffInDb.js.map +1 -0
  549. package/dist/embedding-providers/index.d.ts +10 -0
  550. package/dist/embedding-providers/index.d.ts.map +1 -0
  551. package/dist/embedding-providers/index.js +12 -0
  552. package/dist/embedding-providers/index.js.map +1 -0
  553. package/dist/embeddings/projectionLayer.d.ts +114 -0
  554. package/dist/embeddings/projectionLayer.d.ts.map +1 -0
  555. package/dist/embeddings/projectionLayer.js +345 -0
  556. package/dist/embeddings/projectionLayer.js.map +1 -0
  557. package/dist/events/Publisher.d.ts +193 -0
  558. package/dist/events/Publisher.d.ts.map +1 -0
  559. package/dist/events/Publisher.js +439 -0
  560. package/dist/events/Publisher.js.map +1 -0
  561. package/dist/events/config.d.ts +139 -0
  562. package/dist/events/config.d.ts.map +1 -0
  563. package/dist/events/config.js +266 -0
  564. package/dist/events/config.js.map +1 -0
  565. package/dist/events/index.d.ts +19 -0
  566. package/dist/events/index.d.ts.map +1 -0
  567. package/dist/events/index.js +31 -0
  568. package/dist/events/index.js.map +1 -0
  569. package/dist/events/integration.d.ts +206 -0
  570. package/dist/events/integration.d.ts.map +1 -0
  571. package/dist/events/integration.js +348 -0
  572. package/dist/events/integration.js.map +1 -0
  573. package/dist/events/metrics.d.ts +147 -0
  574. package/dist/events/metrics.d.ts.map +1 -0
  575. package/dist/events/metrics.js +343 -0
  576. package/dist/events/metrics.js.map +1 -0
  577. package/dist/hooks/cli.d.ts +28 -0
  578. package/dist/hooks/cli.d.ts.map +1 -0
  579. package/dist/hooks/cli.js +118 -0
  580. package/dist/hooks/cli.js.map +1 -0
  581. package/dist/hooks/contextInjectionHook.d.ts +60 -0
  582. package/dist/hooks/contextInjectionHook.d.ts.map +1 -0
  583. package/dist/hooks/contextInjectionHook.js +294 -0
  584. package/dist/hooks/contextInjectionHook.js.map +1 -0
  585. package/dist/hooks/drilldownHook.d.ts +125 -0
  586. package/dist/hooks/drilldownHook.d.ts.map +1 -0
  587. package/dist/hooks/drilldownHook.js +181 -0
  588. package/dist/hooks/drilldownHook.js.map +1 -0
  589. package/dist/hooks/hookManager.d.ts +180 -0
  590. package/dist/hooks/hookManager.d.ts.map +1 -0
  591. package/dist/hooks/hookManager.js +782 -0
  592. package/dist/hooks/hookManager.js.map +1 -0
  593. package/dist/hooks/index.d.ts +62 -0
  594. package/dist/hooks/index.d.ts.map +1 -0
  595. package/dist/hooks/index.js +66 -0
  596. package/dist/hooks/index.js.map +1 -0
  597. package/dist/hooks/lowContextHook.d.ts +71 -0
  598. package/dist/hooks/lowContextHook.d.ts.map +1 -0
  599. package/dist/hooks/lowContextHook.js +258 -0
  600. package/dist/hooks/lowContextHook.js.map +1 -0
  601. package/dist/hooks/simpleContextHook.d.ts +65 -0
  602. package/dist/hooks/simpleContextHook.d.ts.map +1 -0
  603. package/dist/hooks/simpleContextHook.js +194 -0
  604. package/dist/hooks/simpleContextHook.js.map +1 -0
  605. package/dist/hooks/teamFramingCli.d.ts +56 -0
  606. package/dist/hooks/teamFramingCli.d.ts.map +1 -0
  607. package/dist/hooks/teamFramingCli.js +264 -0
  608. package/dist/hooks/teamFramingCli.js.map +1 -0
  609. package/dist/hooks/teamMemberPrepromptHook.d.ts +150 -0
  610. package/dist/hooks/teamMemberPrepromptHook.d.ts.map +1 -0
  611. package/dist/hooks/teamMemberPrepromptHook.js +308 -0
  612. package/dist/hooks/teamMemberPrepromptHook.js.map +1 -0
  613. package/dist/index.d.ts +42 -0
  614. package/dist/index.d.ts.map +1 -0
  615. package/dist/index.js +4433 -0
  616. package/dist/index.js.map +1 -0
  617. package/dist/init/claudeConfigInjector.d.ts +116 -0
  618. package/dist/init/claudeConfigInjector.d.ts.map +1 -0
  619. package/dist/init/claudeConfigInjector.js +1154 -0
  620. package/dist/init/claudeConfigInjector.js.map +1 -0
  621. package/dist/installer/autoInstall.d.ts +72 -0
  622. package/dist/installer/autoInstall.d.ts.map +1 -0
  623. package/dist/installer/autoInstall.js +617 -0
  624. package/dist/installer/autoInstall.js.map +1 -0
  625. package/dist/installer/dbSetup.d.ts +84 -0
  626. package/dist/installer/dbSetup.d.ts.map +1 -0
  627. package/dist/installer/dbSetup.js +350 -0
  628. package/dist/installer/dbSetup.js.map +1 -0
  629. package/dist/installer/firstRun.d.ts +49 -0
  630. package/dist/installer/firstRun.d.ts.map +1 -0
  631. package/dist/installer/firstRun.js +207 -0
  632. package/dist/installer/firstRun.js.map +1 -0
  633. package/dist/installer/index.d.ts +10 -0
  634. package/dist/installer/index.d.ts.map +1 -0
  635. package/dist/installer/index.js +10 -0
  636. package/dist/installer/index.js.map +1 -0
  637. package/dist/installer/silentAutoInstall.d.ts +99 -0
  638. package/dist/installer/silentAutoInstall.d.ts.map +1 -0
  639. package/dist/installer/silentAutoInstall.js +491 -0
  640. package/dist/installer/silentAutoInstall.js.map +1 -0
  641. package/dist/installer/systemDeps.d.ts +54 -0
  642. package/dist/installer/systemDeps.d.ts.map +1 -0
  643. package/dist/installer/systemDeps.js +322 -0
  644. package/dist/installer/systemDeps.js.map +1 -0
  645. package/dist/mcp/cliNotifications.d.ts +133 -0
  646. package/dist/mcp/cliNotifications.d.ts.map +1 -0
  647. package/dist/mcp/cliNotifications.js +289 -0
  648. package/dist/mcp/cliNotifications.js.map +1 -0
  649. package/dist/mcp/embeddingServerManager.d.ts +307 -0
  650. package/dist/mcp/embeddingServerManager.d.ts.map +1 -0
  651. package/dist/mcp/embeddingServerManager.js +2081 -0
  652. package/dist/mcp/embeddingServerManager.js.map +1 -0
  653. package/dist/mcp/healthMonitor.d.ts +196 -0
  654. package/dist/mcp/healthMonitor.d.ts.map +1 -0
  655. package/dist/mcp/healthMonitor.js +685 -0
  656. package/dist/mcp/healthMonitor.js.map +1 -0
  657. package/dist/mcp/hotReloadManager.d.ts +101 -0
  658. package/dist/mcp/hotReloadManager.d.ts.map +1 -0
  659. package/dist/mcp/hotReloadManager.js +251 -0
  660. package/dist/mcp/hotReloadManager.js.map +1 -0
  661. package/dist/mcp/index.d.ts +16 -0
  662. package/dist/mcp/index.d.ts.map +1 -0
  663. package/dist/mcp/index.js +22 -0
  664. package/dist/mcp/index.js.map +1 -0
  665. package/dist/mcp/mcpProtocolHandler.d.ts +64 -0
  666. package/dist/mcp/mcpProtocolHandler.d.ts.map +1 -0
  667. package/dist/mcp/mcpProtocolHandler.js +253 -0
  668. package/dist/mcp/mcpProtocolHandler.js.map +1 -0
  669. package/dist/mcp/miniCOTServerManager.d.ts +336 -0
  670. package/dist/mcp/miniCOTServerManager.d.ts.map +1 -0
  671. package/dist/mcp/miniCOTServerManager.js +1384 -0
  672. package/dist/mcp/miniCOTServerManager.js.map +1 -0
  673. package/dist/mcp/promptExecutor.d.ts +188 -0
  674. package/dist/mcp/promptExecutor.d.ts.map +1 -0
  675. package/dist/mcp/promptExecutor.js +469 -0
  676. package/dist/mcp/promptExecutor.js.map +1 -0
  677. package/dist/mcp/reloadBroadcast.d.ts +127 -0
  678. package/dist/mcp/reloadBroadcast.d.ts.map +1 -0
  679. package/dist/mcp/reloadBroadcast.js +275 -0
  680. package/dist/mcp/reloadBroadcast.js.map +1 -0
  681. package/dist/mcp/resilientTransport.d.ts +249 -0
  682. package/dist/mcp/resilientTransport.d.ts.map +1 -0
  683. package/dist/mcp/resilientTransport.js +931 -0
  684. package/dist/mcp/resilientTransport.js.map +1 -0
  685. package/dist/mcp/samplingHandler.d.ts +129 -0
  686. package/dist/mcp/samplingHandler.d.ts.map +1 -0
  687. package/dist/mcp/samplingHandler.js +276 -0
  688. package/dist/mcp/samplingHandler.js.map +1 -0
  689. package/dist/mcp/specMemServer.d.ts +305 -0
  690. package/dist/mcp/specMemServer.d.ts.map +1 -0
  691. package/dist/mcp/specMemServer.js +2048 -0
  692. package/dist/mcp/specMemServer.js.map +1 -0
  693. package/dist/mcp/toolRegistry.d.ts +122 -0
  694. package/dist/mcp/toolRegistry.d.ts.map +1 -0
  695. package/dist/mcp/toolRegistry.js +609 -0
  696. package/dist/mcp/toolRegistry.js.map +1 -0
  697. package/dist/mcp/tools/embeddingControl.d.ts +114 -0
  698. package/dist/mcp/tools/embeddingControl.d.ts.map +1 -0
  699. package/dist/mcp/tools/embeddingControl.js +222 -0
  700. package/dist/mcp/tools/embeddingControl.js.map +1 -0
  701. package/dist/mcp/tools/index.d.ts +10 -0
  702. package/dist/mcp/tools/index.d.ts.map +1 -0
  703. package/dist/mcp/tools/index.js +17 -0
  704. package/dist/mcp/tools/index.js.map +1 -0
  705. package/dist/mcp/tools/teamComms.d.ts +444 -0
  706. package/dist/mcp/tools/teamComms.d.ts.map +1 -0
  707. package/dist/mcp/tools/teamComms.js +1953 -0
  708. package/dist/mcp/tools/teamComms.js.map +1 -0
  709. package/dist/mcp/triggerSystem.d.ts +129 -0
  710. package/dist/mcp/triggerSystem.d.ts.map +1 -0
  711. package/dist/mcp/triggerSystem.js +363 -0
  712. package/dist/mcp/triggerSystem.js.map +1 -0
  713. package/dist/mcp/watcherIntegration.d.ts +77 -0
  714. package/dist/mcp/watcherIntegration.d.ts.map +1 -0
  715. package/dist/mcp/watcherIntegration.js +428 -0
  716. package/dist/mcp/watcherIntegration.js.map +1 -0
  717. package/dist/mcp/watcherToolWrappers.d.ts +89 -0
  718. package/dist/mcp/watcherToolWrappers.d.ts.map +1 -0
  719. package/dist/mcp/watcherToolWrappers.js +91 -0
  720. package/dist/mcp/watcherToolWrappers.js.map +1 -0
  721. package/dist/memorization/claudeCodeMigration.d.ts +34 -0
  722. package/dist/memorization/claudeCodeMigration.d.ts.map +1 -0
  723. package/dist/memorization/claudeCodeMigration.js +210 -0
  724. package/dist/memorization/claudeCodeMigration.js.map +1 -0
  725. package/dist/memorization/claudeCodeTracker.d.ts +147 -0
  726. package/dist/memorization/claudeCodeTracker.d.ts.map +1 -0
  727. package/dist/memorization/claudeCodeTracker.js +424 -0
  728. package/dist/memorization/claudeCodeTracker.js.map +1 -0
  729. package/dist/memorization/codeMemorizer.d.ts +158 -0
  730. package/dist/memorization/codeMemorizer.d.ts.map +1 -0
  731. package/dist/memorization/codeMemorizer.js +357 -0
  732. package/dist/memorization/codeMemorizer.js.map +1 -0
  733. package/dist/memorization/codeRecall.d.ts +156 -0
  734. package/dist/memorization/codeRecall.d.ts.map +1 -0
  735. package/dist/memorization/codeRecall.js +499 -0
  736. package/dist/memorization/codeRecall.js.map +1 -0
  737. package/dist/memorization/index.d.ts +55 -0
  738. package/dist/memorization/index.d.ts.map +1 -0
  739. package/dist/memorization/index.js +64 -0
  740. package/dist/memorization/index.js.map +1 -0
  741. package/dist/memorization/memorizationTools.d.ts +413 -0
  742. package/dist/memorization/memorizationTools.d.ts.map +1 -0
  743. package/dist/memorization/memorizationTools.js +513 -0
  744. package/dist/memorization/memorizationTools.js.map +1 -0
  745. package/dist/memorization/watcherIntegration.d.ts +100 -0
  746. package/dist/memorization/watcherIntegration.d.ts.map +1 -0
  747. package/dist/memorization/watcherIntegration.js +196 -0
  748. package/dist/memorization/watcherIntegration.js.map +1 -0
  749. package/dist/memory/humanLikeMemory.d.ts +206 -0
  750. package/dist/memory/humanLikeMemory.d.ts.map +1 -0
  751. package/dist/memory/humanLikeMemory.js +603 -0
  752. package/dist/memory/humanLikeMemory.js.map +1 -0
  753. package/dist/memory/index.d.ts +22 -0
  754. package/dist/memory/index.d.ts.map +1 -0
  755. package/dist/memory/index.js +24 -0
  756. package/dist/memory/index.js.map +1 -0
  757. package/dist/memory/memoryEvolutionMigration.d.ts +36 -0
  758. package/dist/memory/memoryEvolutionMigration.d.ts.map +1 -0
  759. package/dist/memory/memoryEvolutionMigration.js +371 -0
  760. package/dist/memory/memoryEvolutionMigration.js.map +1 -0
  761. package/dist/memory/quadrantSearch.d.ts +221 -0
  762. package/dist/memory/quadrantSearch.d.ts.map +1 -0
  763. package/dist/memory/quadrantSearch.js +897 -0
  764. package/dist/memory/quadrantSearch.js.map +1 -0
  765. package/dist/middleware/apiVersioning.d.ts +83 -0
  766. package/dist/middleware/apiVersioning.d.ts.map +1 -0
  767. package/dist/middleware/apiVersioning.js +152 -0
  768. package/dist/middleware/apiVersioning.js.map +1 -0
  769. package/dist/middleware/compression.d.ts +48 -0
  770. package/dist/middleware/compression.d.ts.map +1 -0
  771. package/dist/middleware/compression.js +240 -0
  772. package/dist/middleware/compression.js.map +1 -0
  773. package/dist/middleware/csrf.d.ts +118 -0
  774. package/dist/middleware/csrf.d.ts.map +1 -0
  775. package/dist/middleware/csrf.js +300 -0
  776. package/dist/middleware/csrf.js.map +1 -0
  777. package/dist/middleware/index.d.ts +13 -0
  778. package/dist/middleware/index.d.ts.map +1 -0
  779. package/dist/middleware/index.js +17 -0
  780. package/dist/middleware/index.js.map +1 -0
  781. package/dist/middleware/wsRateLimiter.d.ts +129 -0
  782. package/dist/middleware/wsRateLimiter.d.ts.map +1 -0
  783. package/dist/middleware/wsRateLimiter.js +279 -0
  784. package/dist/middleware/wsRateLimiter.js.map +1 -0
  785. package/dist/migrations/run.d.ts +2 -0
  786. package/dist/migrations/run.d.ts.map +1 -0
  787. package/dist/migrations/run.js +25 -0
  788. package/dist/migrations/run.js.map +1 -0
  789. package/dist/migrations/syncDimensions.d.ts +24 -0
  790. package/dist/migrations/syncDimensions.d.ts.map +1 -0
  791. package/dist/migrations/syncDimensions.js +140 -0
  792. package/dist/migrations/syncDimensions.js.map +1 -0
  793. package/dist/migrations/teamComms.d.ts +16 -0
  794. package/dist/migrations/teamComms.d.ts.map +1 -0
  795. package/dist/migrations/teamComms.js +210 -0
  796. package/dist/migrations/teamComms.js.map +1 -0
  797. package/dist/openapi/index.d.ts +10 -0
  798. package/dist/openapi/index.d.ts.map +1 -0
  799. package/dist/openapi/index.js +10 -0
  800. package/dist/openapi/index.js.map +1 -0
  801. package/dist/openapi/spec.d.ts +902 -0
  802. package/dist/openapi/spec.d.ts.map +1 -0
  803. package/dist/openapi/spec.js +733 -0
  804. package/dist/openapi/spec.js.map +1 -0
  805. package/dist/packages/dependencyHistory.d.ts +113 -0
  806. package/dist/packages/dependencyHistory.d.ts.map +1 -0
  807. package/dist/packages/dependencyHistory.js +360 -0
  808. package/dist/packages/dependencyHistory.js.map +1 -0
  809. package/dist/packages/index.d.ts +30 -0
  810. package/dist/packages/index.d.ts.map +1 -0
  811. package/dist/packages/index.js +65 -0
  812. package/dist/packages/index.js.map +1 -0
  813. package/dist/packages/packageTools.d.ts +255 -0
  814. package/dist/packages/packageTools.d.ts.map +1 -0
  815. package/dist/packages/packageTools.js +242 -0
  816. package/dist/packages/packageTools.js.map +1 -0
  817. package/dist/packages/packageTracker.d.ts +98 -0
  818. package/dist/packages/packageTracker.d.ts.map +1 -0
  819. package/dist/packages/packageTracker.js +268 -0
  820. package/dist/packages/packageTracker.js.map +1 -0
  821. package/dist/packages/packageWatcher.d.ts +62 -0
  822. package/dist/packages/packageWatcher.d.ts.map +1 -0
  823. package/dist/packages/packageWatcher.js +146 -0
  824. package/dist/packages/packageWatcher.js.map +1 -0
  825. package/dist/providers/MiniCOTProvider.d.ts +48 -0
  826. package/dist/providers/MiniCOTProvider.d.ts.map +1 -0
  827. package/dist/providers/MiniCOTProvider.js +98 -0
  828. package/dist/providers/MiniCOTProvider.js.map +1 -0
  829. package/dist/reminders/index.d.ts +5 -0
  830. package/dist/reminders/index.d.ts.map +1 -0
  831. package/dist/reminders/index.js +5 -0
  832. package/dist/reminders/index.js.map +1 -0
  833. package/dist/reminders/skillReminder.d.ts +131 -0
  834. package/dist/reminders/skillReminder.d.ts.map +1 -0
  835. package/dist/reminders/skillReminder.js +386 -0
  836. package/dist/reminders/skillReminder.js.map +1 -0
  837. package/dist/search.d.ts +35 -0
  838. package/dist/search.d.ts.map +1 -0
  839. package/dist/search.js +574 -0
  840. package/dist/search.js.map +1 -0
  841. package/dist/security/localhostOnly.d.ts +36 -0
  842. package/dist/security/localhostOnly.d.ts.map +1 -0
  843. package/dist/security/localhostOnly.js +101 -0
  844. package/dist/security/localhostOnly.js.map +1 -0
  845. package/dist/services/CameraZoomSearch.d.ts +206 -0
  846. package/dist/services/CameraZoomSearch.d.ts.map +1 -0
  847. package/dist/services/CameraZoomSearch.js +669 -0
  848. package/dist/services/CameraZoomSearch.js.map +1 -0
  849. package/dist/services/DataFlowPipeline.d.ts +111 -0
  850. package/dist/services/DataFlowPipeline.d.ts.map +1 -0
  851. package/dist/services/DataFlowPipeline.js +379 -0
  852. package/dist/services/DataFlowPipeline.js.map +1 -0
  853. package/dist/services/DimensionAdapter.d.ts +194 -0
  854. package/dist/services/DimensionAdapter.d.ts.map +1 -0
  855. package/dist/services/DimensionAdapter.js +566 -0
  856. package/dist/services/DimensionAdapter.js.map +1 -0
  857. package/dist/services/DimensionService.d.ts +252 -0
  858. package/dist/services/DimensionService.d.ts.map +1 -0
  859. package/dist/services/DimensionService.js +564 -0
  860. package/dist/services/DimensionService.js.map +1 -0
  861. package/dist/services/EmbeddingQueue.d.ts +71 -0
  862. package/dist/services/EmbeddingQueue.d.ts.map +1 -0
  863. package/dist/services/EmbeddingQueue.js +258 -0
  864. package/dist/services/EmbeddingQueue.js.map +1 -0
  865. package/dist/services/MemoryDrilldown.d.ts +226 -0
  866. package/dist/services/MemoryDrilldown.d.ts.map +1 -0
  867. package/dist/services/MemoryDrilldown.js +479 -0
  868. package/dist/services/MemoryDrilldown.js.map +1 -0
  869. package/dist/services/MiniCOTScorer.d.ts +140 -0
  870. package/dist/services/MiniCOTScorer.d.ts.map +1 -0
  871. package/dist/services/MiniCOTScorer.js +292 -0
  872. package/dist/services/MiniCOTScorer.js.map +1 -0
  873. package/dist/services/ProjectContext.d.ts +342 -0
  874. package/dist/services/ProjectContext.d.ts.map +1 -0
  875. package/dist/services/ProjectContext.js +667 -0
  876. package/dist/services/ProjectContext.js.map +1 -0
  877. package/dist/services/ResponseCompactor.d.ts +135 -0
  878. package/dist/services/ResponseCompactor.d.ts.map +1 -0
  879. package/dist/services/ResponseCompactor.js +501 -0
  880. package/dist/services/ResponseCompactor.js.map +1 -0
  881. package/dist/services/TeamCommsDbService.d.ts +202 -0
  882. package/dist/services/TeamCommsDbService.d.ts.map +1 -0
  883. package/dist/services/TeamCommsDbService.js +526 -0
  884. package/dist/services/TeamCommsDbService.js.map +1 -0
  885. package/dist/services/UnifiedPasswordService.d.ts +166 -0
  886. package/dist/services/UnifiedPasswordService.d.ts.map +1 -0
  887. package/dist/services/UnifiedPasswordService.js +587 -0
  888. package/dist/services/UnifiedPasswordService.js.map +1 -0
  889. package/dist/services/adaptiveSearchConfig.d.ts +64 -0
  890. package/dist/services/adaptiveSearchConfig.d.ts.map +1 -0
  891. package/dist/services/adaptiveSearchConfig.js +187 -0
  892. package/dist/services/adaptiveSearchConfig.js.map +1 -0
  893. package/dist/skills/index.d.ts +8 -0
  894. package/dist/skills/index.d.ts.map +1 -0
  895. package/dist/skills/index.js +8 -0
  896. package/dist/skills/index.js.map +1 -0
  897. package/dist/skills/skillScanner.d.ts +203 -0
  898. package/dist/skills/skillScanner.d.ts.map +1 -0
  899. package/dist/skills/skillScanner.js +559 -0
  900. package/dist/skills/skillScanner.js.map +1 -0
  901. package/dist/skills/skillsResource.d.ts +69 -0
  902. package/dist/skills/skillsResource.d.ts.map +1 -0
  903. package/dist/skills/skillsResource.js +257 -0
  904. package/dist/skills/skillsResource.js.map +1 -0
  905. package/dist/startup/index.d.ts +9 -0
  906. package/dist/startup/index.d.ts.map +1 -0
  907. package/dist/startup/index.js +12 -0
  908. package/dist/startup/index.js.map +1 -0
  909. package/dist/startup/startupIndexing.d.ts +80 -0
  910. package/dist/startup/startupIndexing.d.ts.map +1 -0
  911. package/dist/startup/startupIndexing.js +463 -0
  912. package/dist/startup/startupIndexing.js.map +1 -0
  913. package/dist/startup/validation.d.ts +89 -0
  914. package/dist/startup/validation.d.ts.map +1 -0
  915. package/dist/startup/validation.js +590 -0
  916. package/dist/startup/validation.js.map +1 -0
  917. package/dist/storage/index.d.ts +4 -0
  918. package/dist/storage/index.d.ts.map +1 -0
  919. package/dist/storage/index.js +4 -0
  920. package/dist/storage/index.js.map +1 -0
  921. package/dist/storage/overflowManager.d.ts +80 -0
  922. package/dist/storage/overflowManager.d.ts.map +1 -0
  923. package/dist/storage/overflowManager.js +317 -0
  924. package/dist/storage/overflowManager.js.map +1 -0
  925. package/dist/storage/overflowStorage.d.ts +69 -0
  926. package/dist/storage/overflowStorage.d.ts.map +1 -0
  927. package/dist/storage/overflowStorage.js +379 -0
  928. package/dist/storage/overflowStorage.js.map +1 -0
  929. package/dist/storage/toonFormat.d.ts +50 -0
  930. package/dist/storage/toonFormat.d.ts.map +1 -0
  931. package/dist/storage/toonFormat.js +224 -0
  932. package/dist/storage/toonFormat.js.map +1 -0
  933. package/dist/team-members/communication.d.ts +237 -0
  934. package/dist/team-members/communication.d.ts.map +1 -0
  935. package/dist/team-members/communication.js +650 -0
  936. package/dist/team-members/communication.js.map +1 -0
  937. package/dist/team-members/index.d.ts +14 -0
  938. package/dist/team-members/index.d.ts.map +1 -0
  939. package/dist/team-members/index.js +22 -0
  940. package/dist/team-members/index.js.map +1 -0
  941. package/dist/team-members/taskOrchestrator.d.ts +224 -0
  942. package/dist/team-members/taskOrchestrator.d.ts.map +1 -0
  943. package/dist/team-members/taskOrchestrator.js +574 -0
  944. package/dist/team-members/taskOrchestrator.js.map +1 -0
  945. package/dist/team-members/taskTeamMemberLogger.d.ts +157 -0
  946. package/dist/team-members/taskTeamMemberLogger.d.ts.map +1 -0
  947. package/dist/team-members/taskTeamMemberLogger.js +478 -0
  948. package/dist/team-members/taskTeamMemberLogger.js.map +1 -0
  949. package/dist/team-members/teamCommsService.d.ts +221 -0
  950. package/dist/team-members/teamCommsService.d.ts.map +1 -0
  951. package/dist/team-members/teamCommsService.js +628 -0
  952. package/dist/team-members/teamCommsService.js.map +1 -0
  953. package/dist/team-members/teamMemberChannels.d.ts +217 -0
  954. package/dist/team-members/teamMemberChannels.d.ts.map +1 -0
  955. package/dist/team-members/teamMemberChannels.js +687 -0
  956. package/dist/team-members/teamMemberChannels.js.map +1 -0
  957. package/dist/team-members/teamMemberDashboard.d.ts +222 -0
  958. package/dist/team-members/teamMemberDashboard.d.ts.map +1 -0
  959. package/dist/team-members/teamMemberDashboard.js +610 -0
  960. package/dist/team-members/teamMemberDashboard.js.map +1 -0
  961. package/dist/team-members/teamMemberDeployment.d.ts +60 -0
  962. package/dist/team-members/teamMemberDeployment.d.ts.map +1 -0
  963. package/dist/team-members/teamMemberDeployment.js +429 -0
  964. package/dist/team-members/teamMemberDeployment.js.map +1 -0
  965. package/dist/team-members/teamMemberDiscovery.d.ts +178 -0
  966. package/dist/team-members/teamMemberDiscovery.d.ts.map +1 -0
  967. package/dist/team-members/teamMemberDiscovery.js +446 -0
  968. package/dist/team-members/teamMemberDiscovery.js.map +1 -0
  969. package/dist/team-members/teamMemberHistory.d.ts +80 -0
  970. package/dist/team-members/teamMemberHistory.d.ts.map +1 -0
  971. package/dist/team-members/teamMemberHistory.js +426 -0
  972. package/dist/team-members/teamMemberHistory.js.map +1 -0
  973. package/dist/team-members/teamMemberLimits.d.ts +66 -0
  974. package/dist/team-members/teamMemberLimits.d.ts.map +1 -0
  975. package/dist/team-members/teamMemberLimits.js +259 -0
  976. package/dist/team-members/teamMemberLimits.js.map +1 -0
  977. package/dist/team-members/teamMemberRegistry.d.ts +199 -0
  978. package/dist/team-members/teamMemberRegistry.d.ts.map +1 -0
  979. package/dist/team-members/teamMemberRegistry.js +572 -0
  980. package/dist/team-members/teamMemberRegistry.js.map +1 -0
  981. package/dist/team-members/teamMemberTracker.d.ts +148 -0
  982. package/dist/team-members/teamMemberTracker.d.ts.map +1 -0
  983. package/dist/team-members/teamMemberTracker.js +828 -0
  984. package/dist/team-members/teamMemberTracker.js.map +1 -0
  985. package/dist/team-members/workers/aiWorker.d.ts +53 -0
  986. package/dist/team-members/workers/aiWorker.d.ts.map +1 -0
  987. package/dist/team-members/workers/aiWorker.js +322 -0
  988. package/dist/team-members/workers/aiWorker.js.map +1 -0
  989. package/dist/team-members/workers/baseWorker.d.ts +101 -0
  990. package/dist/team-members/workers/baseWorker.d.ts.map +1 -0
  991. package/dist/team-members/workers/baseWorker.js +179 -0
  992. package/dist/team-members/workers/baseWorker.js.map +1 -0
  993. package/dist/team-members/workers/codeReviewWorker.d.ts +3 -0
  994. package/dist/team-members/workers/codeReviewWorker.d.ts.map +1 -0
  995. package/dist/team-members/workers/codeReviewWorker.js +144 -0
  996. package/dist/team-members/workers/codeReviewWorker.js.map +1 -0
  997. package/dist/team-members/workers/index.d.ts +7 -0
  998. package/dist/team-members/workers/index.d.ts.map +1 -0
  999. package/dist/team-members/workers/index.js +7 -0
  1000. package/dist/team-members/workers/index.js.map +1 -0
  1001. package/dist/team-members/workers/repairWorker.d.ts +9 -0
  1002. package/dist/team-members/workers/repairWorker.d.ts.map +1 -0
  1003. package/dist/team-members/workers/repairWorker.js +102 -0
  1004. package/dist/team-members/workers/repairWorker.js.map +1 -0
  1005. package/dist/team-members/workers/sendToTeamMemberB.d.ts +9 -0
  1006. package/dist/team-members/workers/sendToTeamMemberB.d.ts.map +1 -0
  1007. package/dist/team-members/workers/sendToTeamMemberB.js +105 -0
  1008. package/dist/team-members/workers/sendToTeamMemberB.js.map +1 -0
  1009. package/dist/team-members/workers/specmemClient.d.ts +179 -0
  1010. package/dist/team-members/workers/specmemClient.d.ts.map +1 -0
  1011. package/dist/team-members/workers/specmemClient.js +421 -0
  1012. package/dist/team-members/workers/specmemClient.js.map +1 -0
  1013. package/dist/team-members/workers/testCommunication.d.ts +8 -0
  1014. package/dist/team-members/workers/testCommunication.d.ts.map +1 -0
  1015. package/dist/team-members/workers/testCommunication.js +136 -0
  1016. package/dist/team-members/workers/testCommunication.js.map +1 -0
  1017. package/dist/team-members/workers/testCommunicationSuite.d.ts +26 -0
  1018. package/dist/team-members/workers/testCommunicationSuite.d.ts.map +1 -0
  1019. package/dist/team-members/workers/testCommunicationSuite.js +415 -0
  1020. package/dist/team-members/workers/testCommunicationSuite.js.map +1 -0
  1021. package/dist/team-members/workers/testWorker.d.ts +9 -0
  1022. package/dist/team-members/workers/testWorker.d.ts.map +1 -0
  1023. package/dist/team-members/workers/testWorker.js +107 -0
  1024. package/dist/team-members/workers/testWorker.js.map +1 -0
  1025. package/dist/tools/agentDefinitions.d.ts +30 -0
  1026. package/dist/tools/agentDefinitions.d.ts.map +1 -0
  1027. package/dist/tools/agentDefinitions.js +166 -0
  1028. package/dist/tools/agentDefinitions.js.map +1 -0
  1029. package/dist/tools/goofy/checkSyncStatus.d.ts +68 -0
  1030. package/dist/tools/goofy/checkSyncStatus.d.ts.map +1 -0
  1031. package/dist/tools/goofy/checkSyncStatus.js +112 -0
  1032. package/dist/tools/goofy/checkSyncStatus.js.map +1 -0
  1033. package/dist/tools/goofy/codeMemoryLink.d.ts +82 -0
  1034. package/dist/tools/goofy/codeMemoryLink.d.ts.map +1 -0
  1035. package/dist/tools/goofy/codeMemoryLink.js +212 -0
  1036. package/dist/tools/goofy/codeMemoryLink.js.map +1 -0
  1037. package/dist/tools/goofy/compareInstanceMemory.d.ts +97 -0
  1038. package/dist/tools/goofy/compareInstanceMemory.d.ts.map +1 -0
  1039. package/dist/tools/goofy/compareInstanceMemory.js +218 -0
  1040. package/dist/tools/goofy/compareInstanceMemory.js.map +1 -0
  1041. package/dist/tools/goofy/createReasoningChain.d.ts +135 -0
  1042. package/dist/tools/goofy/createReasoningChain.d.ts.map +1 -0
  1043. package/dist/tools/goofy/createReasoningChain.js +257 -0
  1044. package/dist/tools/goofy/createReasoningChain.js.map +1 -0
  1045. package/dist/tools/goofy/deployTeamMember.d.ts +63 -0
  1046. package/dist/tools/goofy/deployTeamMember.d.ts.map +1 -0
  1047. package/dist/tools/goofy/deployTeamMember.js +103 -0
  1048. package/dist/tools/goofy/deployTeamMember.js.map +1 -0
  1049. package/dist/tools/goofy/drillDown.d.ts +143 -0
  1050. package/dist/tools/goofy/drillDown.d.ts.map +1 -0
  1051. package/dist/tools/goofy/drillDown.js +288 -0
  1052. package/dist/tools/goofy/drillDown.js.map +1 -0
  1053. package/dist/tools/goofy/extractClaudeSessions.d.ts +90 -0
  1054. package/dist/tools/goofy/extractClaudeSessions.d.ts.map +1 -0
  1055. package/dist/tools/goofy/extractClaudeSessions.js +277 -0
  1056. package/dist/tools/goofy/extractClaudeSessions.js.map +1 -0
  1057. package/dist/tools/goofy/extractContextRestorations.d.ts +70 -0
  1058. package/dist/tools/goofy/extractContextRestorations.d.ts.map +1 -0
  1059. package/dist/tools/goofy/extractContextRestorations.js +100 -0
  1060. package/dist/tools/goofy/extractContextRestorations.js.map +1 -0
  1061. package/dist/tools/goofy/findCodePointers.d.ts +364 -0
  1062. package/dist/tools/goofy/findCodePointers.d.ts.map +1 -0
  1063. package/dist/tools/goofy/findCodePointers.js +1764 -0
  1064. package/dist/tools/goofy/findCodePointers.js.map +1 -0
  1065. package/dist/tools/goofy/findMemoryGallery.d.ts +40 -0
  1066. package/dist/tools/goofy/findMemoryGallery.d.ts.map +1 -0
  1067. package/dist/tools/goofy/findMemoryGallery.js +66 -0
  1068. package/dist/tools/goofy/findMemoryGallery.js.map +1 -0
  1069. package/dist/tools/goofy/findWhatISaid.d.ts +300 -0
  1070. package/dist/tools/goofy/findWhatISaid.d.ts.map +1 -0
  1071. package/dist/tools/goofy/findWhatISaid.js +2547 -0
  1072. package/dist/tools/goofy/findWhatISaid.js.map +1 -0
  1073. package/dist/tools/goofy/forceResync.d.ts +57 -0
  1074. package/dist/tools/goofy/forceResync.d.ts.map +1 -0
  1075. package/dist/tools/goofy/forceResync.js +100 -0
  1076. package/dist/tools/goofy/forceResync.js.map +1 -0
  1077. package/dist/tools/goofy/getActiveTeamMembers.d.ts +48 -0
  1078. package/dist/tools/goofy/getActiveTeamMembers.d.ts.map +1 -0
  1079. package/dist/tools/goofy/getActiveTeamMembers.js +136 -0
  1080. package/dist/tools/goofy/getActiveTeamMembers.js.map +1 -0
  1081. package/dist/tools/goofy/getMemoryFull.d.ts +34 -0
  1082. package/dist/tools/goofy/getMemoryFull.d.ts.map +1 -0
  1083. package/dist/tools/goofy/getMemoryFull.js +58 -0
  1084. package/dist/tools/goofy/getMemoryFull.js.map +1 -0
  1085. package/dist/tools/goofy/getSessionWatcherStatus.d.ts +43 -0
  1086. package/dist/tools/goofy/getSessionWatcherStatus.d.ts.map +1 -0
  1087. package/dist/tools/goofy/getSessionWatcherStatus.js +92 -0
  1088. package/dist/tools/goofy/getSessionWatcherStatus.js.map +1 -0
  1089. package/dist/tools/goofy/getTeamMemberOutput.d.ts +35 -0
  1090. package/dist/tools/goofy/getTeamMemberOutput.d.ts.map +1 -0
  1091. package/dist/tools/goofy/getTeamMemberOutput.js +62 -0
  1092. package/dist/tools/goofy/getTeamMemberOutput.js.map +1 -0
  1093. package/dist/tools/goofy/getTeamMemberScreen.d.ts +28 -0
  1094. package/dist/tools/goofy/getTeamMemberScreen.d.ts.map +1 -0
  1095. package/dist/tools/goofy/getTeamMemberScreen.js +59 -0
  1096. package/dist/tools/goofy/getTeamMemberScreen.js.map +1 -0
  1097. package/dist/tools/goofy/getTeamMemberStatus.d.ts +33 -0
  1098. package/dist/tools/goofy/getTeamMemberStatus.d.ts.map +1 -0
  1099. package/dist/tools/goofy/getTeamMemberStatus.js +56 -0
  1100. package/dist/tools/goofy/getTeamMemberStatus.js.map +1 -0
  1101. package/dist/tools/goofy/index.d.ts +39 -0
  1102. package/dist/tools/goofy/index.d.ts.map +1 -0
  1103. package/dist/tools/goofy/index.js +51 -0
  1104. package/dist/tools/goofy/index.js.map +1 -0
  1105. package/dist/tools/goofy/interveneTeamMember.d.ts +33 -0
  1106. package/dist/tools/goofy/interveneTeamMember.d.ts.map +1 -0
  1107. package/dist/tools/goofy/interveneTeamMember.js +69 -0
  1108. package/dist/tools/goofy/interveneTeamMember.js.map +1 -0
  1109. package/dist/tools/goofy/killDeployedTeamMember.d.ts +29 -0
  1110. package/dist/tools/goofy/killDeployedTeamMember.d.ts.map +1 -0
  1111. package/dist/tools/goofy/killDeployedTeamMember.js +56 -0
  1112. package/dist/tools/goofy/killDeployedTeamMember.js.map +1 -0
  1113. package/dist/tools/goofy/linkTheVibes.d.ts +125 -0
  1114. package/dist/tools/goofy/linkTheVibes.d.ts.map +1 -0
  1115. package/dist/tools/goofy/linkTheVibes.js +354 -0
  1116. package/dist/tools/goofy/linkTheVibes.js.map +1 -0
  1117. package/dist/tools/goofy/listDeployedTeamMembers.d.ts +26 -0
  1118. package/dist/tools/goofy/listDeployedTeamMembers.d.ts.map +1 -0
  1119. package/dist/tools/goofy/listDeployedTeamMembers.js +52 -0
  1120. package/dist/tools/goofy/listDeployedTeamMembers.js.map +1 -0
  1121. package/dist/tools/goofy/listenForMessages.d.ts +56 -0
  1122. package/dist/tools/goofy/listenForMessages.d.ts.map +1 -0
  1123. package/dist/tools/goofy/listenForMessages.js +122 -0
  1124. package/dist/tools/goofy/listenForMessages.js.map +1 -0
  1125. package/dist/tools/goofy/memoryHealthCheck.d.ts +159 -0
  1126. package/dist/tools/goofy/memoryHealthCheck.d.ts.map +1 -0
  1127. package/dist/tools/goofy/memoryHealthCheck.js +443 -0
  1128. package/dist/tools/goofy/memoryHealthCheck.js.map +1 -0
  1129. package/dist/tools/goofy/rememberThisShit.d.ts +103 -0
  1130. package/dist/tools/goofy/rememberThisShit.d.ts.map +1 -0
  1131. package/dist/tools/goofy/rememberThisShit.js +291 -0
  1132. package/dist/tools/goofy/rememberThisShit.js.map +1 -0
  1133. package/dist/tools/goofy/sayToTeamMember.d.ts +55 -0
  1134. package/dist/tools/goofy/sayToTeamMember.d.ts.map +1 -0
  1135. package/dist/tools/goofy/sayToTeamMember.js +116 -0
  1136. package/dist/tools/goofy/sayToTeamMember.js.map +1 -0
  1137. package/dist/tools/goofy/selfMessage.d.ts +54 -0
  1138. package/dist/tools/goofy/selfMessage.d.ts.map +1 -0
  1139. package/dist/tools/goofy/selfMessage.js +111 -0
  1140. package/dist/tools/goofy/selfMessage.js.map +1 -0
  1141. package/dist/tools/goofy/sendHeartbeat.d.ts +53 -0
  1142. package/dist/tools/goofy/sendHeartbeat.d.ts.map +1 -0
  1143. package/dist/tools/goofy/sendHeartbeat.js +119 -0
  1144. package/dist/tools/goofy/sendHeartbeat.js.map +1 -0
  1145. package/dist/tools/goofy/showMeTheStats.d.ts +216 -0
  1146. package/dist/tools/goofy/showMeTheStats.d.ts.map +1 -0
  1147. package/dist/tools/goofy/showMeTheStats.js +535 -0
  1148. package/dist/tools/goofy/showMeTheStats.js.map +1 -0
  1149. package/dist/tools/goofy/smartRecall.d.ts +136 -0
  1150. package/dist/tools/goofy/smartRecall.d.ts.map +1 -0
  1151. package/dist/tools/goofy/smartRecall.js +286 -0
  1152. package/dist/tools/goofy/smartRecall.js.map +1 -0
  1153. package/dist/tools/goofy/smartSearch.d.ts +64 -0
  1154. package/dist/tools/goofy/smartSearch.d.ts.map +1 -0
  1155. package/dist/tools/goofy/smartSearch.js +89 -0
  1156. package/dist/tools/goofy/smartSearch.js.map +1 -0
  1157. package/dist/tools/goofy/smushMemoriesTogether.d.ts +128 -0
  1158. package/dist/tools/goofy/smushMemoriesTogether.d.ts.map +1 -0
  1159. package/dist/tools/goofy/smushMemoriesTogether.js +536 -0
  1160. package/dist/tools/goofy/smushMemoriesTogether.js.map +1 -0
  1161. package/dist/tools/goofy/spatialSearch.d.ts +198 -0
  1162. package/dist/tools/goofy/spatialSearch.d.ts.map +1 -0
  1163. package/dist/tools/goofy/spatialSearch.js +551 -0
  1164. package/dist/tools/goofy/spatialSearch.js.map +1 -0
  1165. package/dist/tools/goofy/spawnResearchTeamMember.d.ts +104 -0
  1166. package/dist/tools/goofy/spawnResearchTeamMember.d.ts.map +1 -0
  1167. package/dist/tools/goofy/spawnResearchTeamMember.js +290 -0
  1168. package/dist/tools/goofy/spawnResearchTeamMember.js.map +1 -0
  1169. package/dist/tools/goofy/spawnResearchTeamMemberTool.d.ts +121 -0
  1170. package/dist/tools/goofy/spawnResearchTeamMemberTool.d.ts.map +1 -0
  1171. package/dist/tools/goofy/spawnResearchTeamMemberTool.js +215 -0
  1172. package/dist/tools/goofy/spawnResearchTeamMemberTool.js.map +1 -0
  1173. package/dist/tools/goofy/startWatchingTheFiles.d.ts +81 -0
  1174. package/dist/tools/goofy/startWatchingTheFiles.d.ts.map +1 -0
  1175. package/dist/tools/goofy/startWatchingTheFiles.js +161 -0
  1176. package/dist/tools/goofy/startWatchingTheFiles.js.map +1 -0
  1177. package/dist/tools/goofy/stopWatchingTheFiles.d.ts +50 -0
  1178. package/dist/tools/goofy/stopWatchingTheFiles.d.ts.map +1 -0
  1179. package/dist/tools/goofy/stopWatchingTheFiles.js +81 -0
  1180. package/dist/tools/goofy/stopWatchingTheFiles.js.map +1 -0
  1181. package/dist/tools/goofy/whatDidIMean.d.ts +113 -0
  1182. package/dist/tools/goofy/whatDidIMean.d.ts.map +1 -0
  1183. package/dist/tools/goofy/whatDidIMean.js +401 -0
  1184. package/dist/tools/goofy/whatDidIMean.js.map +1 -0
  1185. package/dist/tools/goofy/yeahNahDeleteThat.d.ts +109 -0
  1186. package/dist/tools/goofy/yeahNahDeleteThat.d.ts.map +1 -0
  1187. package/dist/tools/goofy/yeahNahDeleteThat.js +319 -0
  1188. package/dist/tools/goofy/yeahNahDeleteThat.js.map +1 -0
  1189. package/dist/tools/index.d.ts +9 -0
  1190. package/dist/tools/index.d.ts.map +1 -0
  1191. package/dist/tools/index.js +9 -0
  1192. package/dist/tools/index.js.map +1 -0
  1193. package/dist/tools/teamMemberDeployer.d.ts +117 -0
  1194. package/dist/tools/teamMemberDeployer.d.ts.map +1 -0
  1195. package/dist/tools/teamMemberDeployer.js +613 -0
  1196. package/dist/tools/teamMemberDeployer.js.map +1 -0
  1197. package/dist/trace/index.d.ts +14 -0
  1198. package/dist/trace/index.d.ts.map +1 -0
  1199. package/dist/trace/index.js +16 -0
  1200. package/dist/trace/index.js.map +1 -0
  1201. package/dist/trace/tools/analyzeImpact.d.ts +90 -0
  1202. package/dist/trace/tools/analyzeImpact.d.ts.map +1 -0
  1203. package/dist/trace/tools/analyzeImpact.js +240 -0
  1204. package/dist/trace/tools/analyzeImpact.js.map +1 -0
  1205. package/dist/trace/tools/exploreDependencies.d.ts +81 -0
  1206. package/dist/trace/tools/exploreDependencies.d.ts.map +1 -0
  1207. package/dist/trace/tools/exploreDependencies.js +161 -0
  1208. package/dist/trace/tools/exploreDependencies.js.map +1 -0
  1209. package/dist/trace/tools/findSimilarBugs.d.ts +112 -0
  1210. package/dist/trace/tools/findSimilarBugs.d.ts.map +1 -0
  1211. package/dist/trace/tools/findSimilarBugs.js +216 -0
  1212. package/dist/trace/tools/findSimilarBugs.js.map +1 -0
  1213. package/dist/trace/tools/index.d.ts +22 -0
  1214. package/dist/trace/tools/index.d.ts.map +1 -0
  1215. package/dist/trace/tools/index.js +39 -0
  1216. package/dist/trace/tools/index.js.map +1 -0
  1217. package/dist/trace/tools/smartExplore.d.ts +126 -0
  1218. package/dist/trace/tools/smartExplore.d.ts.map +1 -0
  1219. package/dist/trace/tools/smartExplore.js +303 -0
  1220. package/dist/trace/tools/smartExplore.js.map +1 -0
  1221. package/dist/trace/tools/traceError.d.ts +101 -0
  1222. package/dist/trace/tools/traceError.d.ts.map +1 -0
  1223. package/dist/trace/tools/traceError.js +175 -0
  1224. package/dist/trace/tools/traceError.js.map +1 -0
  1225. package/dist/trace/traceExploreSystem.d.ts +271 -0
  1226. package/dist/trace/traceExploreSystem.d.ts.map +1 -0
  1227. package/dist/trace/traceExploreSystem.js +789 -0
  1228. package/dist/trace/traceExploreSystem.js.map +1 -0
  1229. package/dist/types/index.d.ts +421 -0
  1230. package/dist/types/index.d.ts.map +1 -0
  1231. package/dist/types/index.js +118 -0
  1232. package/dist/types/index.js.map +1 -0
  1233. package/dist/utils/circuitBreaker.d.ts +195 -0
  1234. package/dist/utils/circuitBreaker.d.ts.map +1 -0
  1235. package/dist/utils/circuitBreaker.js +374 -0
  1236. package/dist/utils/circuitBreaker.js.map +1 -0
  1237. package/dist/utils/cleanupHandler.d.ts +108 -0
  1238. package/dist/utils/cleanupHandler.d.ts.map +1 -0
  1239. package/dist/utils/cleanupHandler.js +203 -0
  1240. package/dist/utils/cleanupHandler.js.map +1 -0
  1241. package/dist/utils/compactXmlResponse.d.ts +60 -0
  1242. package/dist/utils/compactXmlResponse.d.ts.map +1 -0
  1243. package/dist/utils/compactXmlResponse.js +209 -0
  1244. package/dist/utils/compactXmlResponse.js.map +1 -0
  1245. package/dist/utils/cotBroadcast.d.ts +56 -0
  1246. package/dist/utils/cotBroadcast.d.ts.map +1 -0
  1247. package/dist/utils/cotBroadcast.js +157 -0
  1248. package/dist/utils/cotBroadcast.js.map +1 -0
  1249. package/dist/utils/debugLogger.d.ts +95 -0
  1250. package/dist/utils/debugLogger.d.ts.map +1 -0
  1251. package/dist/utils/debugLogger.js +610 -0
  1252. package/dist/utils/debugLogger.js.map +1 -0
  1253. package/dist/utils/fileProcessingQueue.d.ts +259 -0
  1254. package/dist/utils/fileProcessingQueue.d.ts.map +1 -0
  1255. package/dist/utils/fileProcessingQueue.js +714 -0
  1256. package/dist/utils/fileProcessingQueue.js.map +1 -0
  1257. package/dist/utils/humanReadableOutput.d.ts +124 -0
  1258. package/dist/utils/humanReadableOutput.d.ts.map +1 -0
  1259. package/dist/utils/humanReadableOutput.js +340 -0
  1260. package/dist/utils/humanReadableOutput.js.map +1 -0
  1261. package/dist/utils/index.d.ts +32 -0
  1262. package/dist/utils/index.d.ts.map +1 -0
  1263. package/dist/utils/index.js +71 -0
  1264. package/dist/utils/index.js.map +1 -0
  1265. package/dist/utils/instanceManager.d.ts +530 -0
  1266. package/dist/utils/instanceManager.d.ts.map +1 -0
  1267. package/dist/utils/instanceManager.js +1784 -0
  1268. package/dist/utils/instanceManager.js.map +1 -0
  1269. package/dist/utils/logger.d.ts +6 -0
  1270. package/dist/utils/logger.d.ts.map +1 -0
  1271. package/dist/utils/logger.js +49 -0
  1272. package/dist/utils/logger.js.map +1 -0
  1273. package/dist/utils/mapCleanup.d.ts +58 -0
  1274. package/dist/utils/mapCleanup.d.ts.map +1 -0
  1275. package/dist/utils/mapCleanup.js +150 -0
  1276. package/dist/utils/mapCleanup.js.map +1 -0
  1277. package/dist/utils/memoryManager.d.ts +349 -0
  1278. package/dist/utils/memoryManager.d.ts.map +1 -0
  1279. package/dist/utils/memoryManager.js +799 -0
  1280. package/dist/utils/memoryManager.js.map +1 -0
  1281. package/dist/utils/metrics.d.ts +160 -0
  1282. package/dist/utils/metrics.d.ts.map +1 -0
  1283. package/dist/utils/metrics.js +558 -0
  1284. package/dist/utils/metrics.js.map +1 -0
  1285. package/dist/utils/pathValidator.d.ts +96 -0
  1286. package/dist/utils/pathValidator.d.ts.map +1 -0
  1287. package/dist/utils/pathValidator.js +320 -0
  1288. package/dist/utils/pathValidator.js.map +1 -0
  1289. package/dist/utils/portAllocator.d.ts +296 -0
  1290. package/dist/utils/portAllocator.d.ts.map +1 -0
  1291. package/dist/utils/portAllocator.js +768 -0
  1292. package/dist/utils/portAllocator.js.map +1 -0
  1293. package/dist/utils/portUtils.d.ts +97 -0
  1294. package/dist/utils/portUtils.d.ts.map +1 -0
  1295. package/dist/utils/portUtils.js +285 -0
  1296. package/dist/utils/portUtils.js.map +1 -0
  1297. package/dist/utils/postgresAutoSetup.d.ts +55 -0
  1298. package/dist/utils/postgresAutoSetup.d.ts.map +1 -0
  1299. package/dist/utils/postgresAutoSetup.js +406 -0
  1300. package/dist/utils/postgresAutoSetup.js.map +1 -0
  1301. package/dist/utils/processHealthCheck.d.ts +61 -0
  1302. package/dist/utils/processHealthCheck.d.ts.map +1 -0
  1303. package/dist/utils/processHealthCheck.js +313 -0
  1304. package/dist/utils/processHealthCheck.js.map +1 -0
  1305. package/dist/utils/progressReporter.d.ts +151 -0
  1306. package/dist/utils/progressReporter.d.ts.map +1 -0
  1307. package/dist/utils/progressReporter.js +345 -0
  1308. package/dist/utils/progressReporter.js.map +1 -0
  1309. package/dist/utils/projectEnv.d.ts +73 -0
  1310. package/dist/utils/projectEnv.d.ts.map +1 -0
  1311. package/dist/utils/projectEnv.js +137 -0
  1312. package/dist/utils/projectEnv.js.map +1 -0
  1313. package/dist/utils/qoms.d.ts +122 -0
  1314. package/dist/utils/qoms.d.ts.map +1 -0
  1315. package/dist/utils/qoms.js +650 -0
  1316. package/dist/utils/qoms.js.map +1 -0
  1317. package/dist/utils/retryHelper.d.ts +122 -0
  1318. package/dist/utils/retryHelper.d.ts.map +1 -0
  1319. package/dist/utils/retryHelper.js +272 -0
  1320. package/dist/utils/retryHelper.js.map +1 -0
  1321. package/dist/utils/safeProcessTermination.d.ts +206 -0
  1322. package/dist/utils/safeProcessTermination.d.ts.map +1 -0
  1323. package/dist/utils/safeProcessTermination.js +552 -0
  1324. package/dist/utils/safeProcessTermination.js.map +1 -0
  1325. package/dist/utils/sessionInjector.d.ts +68 -0
  1326. package/dist/utils/sessionInjector.d.ts.map +1 -0
  1327. package/dist/utils/sessionInjector.js +189 -0
  1328. package/dist/utils/sessionInjector.js.map +1 -0
  1329. package/dist/utils/statsCache.d.ts +134 -0
  1330. package/dist/utils/statsCache.d.ts.map +1 -0
  1331. package/dist/utils/statsCache.js +285 -0
  1332. package/dist/utils/statsCache.js.map +1 -0
  1333. package/dist/utils/timeoutMiddleware.d.ts +81 -0
  1334. package/dist/utils/timeoutMiddleware.d.ts.map +1 -0
  1335. package/dist/utils/timeoutMiddleware.js +155 -0
  1336. package/dist/utils/timeoutMiddleware.js.map +1 -0
  1337. package/dist/utils/timerRegistry.d.ts +91 -0
  1338. package/dist/utils/timerRegistry.d.ts.map +1 -0
  1339. package/dist/utils/timerRegistry.js +187 -0
  1340. package/dist/utils/timerRegistry.js.map +1 -0
  1341. package/dist/utils/tokenCompressor.d.ts +332 -0
  1342. package/dist/utils/tokenCompressor.d.ts.map +1 -0
  1343. package/dist/utils/tokenCompressor.js +1306 -0
  1344. package/dist/utils/tokenCompressor.js.map +1 -0
  1345. package/dist/utils/tracing.d.ts +236 -0
  1346. package/dist/utils/tracing.d.ts.map +1 -0
  1347. package/dist/utils/tracing.js +378 -0
  1348. package/dist/utils/tracing.js.map +1 -0
  1349. package/dist/watcher/changeHandler.d.ts +123 -0
  1350. package/dist/watcher/changeHandler.d.ts.map +1 -0
  1351. package/dist/watcher/changeHandler.js +623 -0
  1352. package/dist/watcher/changeHandler.js.map +1 -0
  1353. package/dist/watcher/changeQueue.d.ts +133 -0
  1354. package/dist/watcher/changeQueue.d.ts.map +1 -0
  1355. package/dist/watcher/changeQueue.js +355 -0
  1356. package/dist/watcher/changeQueue.js.map +1 -0
  1357. package/dist/watcher/fileWatcher.d.ts +121 -0
  1358. package/dist/watcher/fileWatcher.d.ts.map +1 -0
  1359. package/dist/watcher/fileWatcher.js +531 -0
  1360. package/dist/watcher/fileWatcher.js.map +1 -0
  1361. package/dist/watcher/index.d.ts +94 -0
  1362. package/dist/watcher/index.d.ts.map +1 -0
  1363. package/dist/watcher/index.js +235 -0
  1364. package/dist/watcher/index.js.map +1 -0
  1365. package/dist/watcher/syncChecker.d.ts +93 -0
  1366. package/dist/watcher/syncChecker.d.ts.map +1 -0
  1367. package/dist/watcher/syncChecker.js +401 -0
  1368. package/dist/watcher/syncChecker.js.map +1 -0
  1369. package/dist/watcher/tsCompiler.d.ts +88 -0
  1370. package/dist/watcher/tsCompiler.d.ts.map +1 -0
  1371. package/dist/watcher/tsCompiler.js +212 -0
  1372. package/dist/watcher/tsCompiler.js.map +1 -0
  1373. package/embedding-sandbox/Dockerfile +77 -0
  1374. package/embedding-sandbox/Dockerfile.frankenstein +91 -0
  1375. package/embedding-sandbox/README.md +193 -0
  1376. package/embedding-sandbox/__pycache__/frankenstein-embeddings.cpython-312.pyc +0 -0
  1377. package/embedding-sandbox/__pycache__/frankenstein-embeddings.cpython-313.pyc +0 -0
  1378. package/embedding-sandbox/__pycache__/qqms_v2.cpython-312.pyc +0 -0
  1379. package/embedding-sandbox/__pycache__/qqms_v2.cpython-313.pyc +0 -0
  1380. package/embedding-sandbox/add_js_docs.py +684 -0
  1381. package/embedding-sandbox/build_docs_db.py +239 -0
  1382. package/embedding-sandbox/client.cjs +376 -0
  1383. package/embedding-sandbox/client.ts +913 -0
  1384. package/embedding-sandbox/deploy-frankenstein.sh +240 -0
  1385. package/embedding-sandbox/docker-compose.yml +60 -0
  1386. package/embedding-sandbox/docker-manager.py +325 -0
  1387. package/embedding-sandbox/docs/python_docs.db +0 -0
  1388. package/embedding-sandbox/download-model.mjs +79 -0
  1389. package/embedding-sandbox/download-model.py +28 -0
  1390. package/embedding-sandbox/embedding-supervisor.sh +164 -0
  1391. package/embedding-sandbox/frankenstein-embeddings.py +3940 -0
  1392. package/embedding-sandbox/manage-services.sh +354 -0
  1393. package/embedding-sandbox/overflow_queue.py +345 -0
  1394. package/embedding-sandbox/package.json +17 -0
  1395. package/embedding-sandbox/project_isolation.py +292 -0
  1396. package/embedding-sandbox/qqms_v2.py +967 -0
  1397. package/embedding-sandbox/ram-manager.sh +311 -0
  1398. package/embedding-sandbox/requirements-frankenstein.txt +7 -0
  1399. package/embedding-sandbox/run_js_docs.py +59 -0
  1400. package/embedding-sandbox/seed_docs.py +885 -0
  1401. package/embedding-sandbox/server-batch.mjs +228 -0
  1402. package/embedding-sandbox/server.mjs +389 -0
  1403. package/embedding-sandbox/specmem/sockets/claude-input-state.json +1 -0
  1404. package/embedding-sandbox/specmem/sockets/embedding-death-reason.txt +3 -0
  1405. package/embedding-sandbox/specmem/sockets/seen-sessions.json +1 -0
  1406. package/embedding-sandbox/specmem/sockets/session-start.lock +1 -0
  1407. package/embedding-sandbox/specmem/sockets/session-stops.log +7 -0
  1408. package/embedding-sandbox/start-frankenstein-throttled.sh +98 -0
  1409. package/embedding-sandbox/start-on-demand.sh +116 -0
  1410. package/embedding-sandbox/start-sandbox.sh +237 -0
  1411. package/embedding-sandbox/start-supervised.sh +11 -0
  1412. package/embedding-sandbox/stop-sandbox.sh +51 -0
  1413. package/embedding-sandbox/test-socket.mjs +61 -0
  1414. package/embedding-sandbox/warm-start.sh +353 -0
  1415. package/embedding-sandbox/warm_start_feeder.py +660 -0
  1416. package/legal/README.md +31 -0
  1417. package/legal/anthropic-privacy-center-screenshot-2026-01-30.png +0 -0
  1418. package/legal/anthropic-tos-screenshot-2026-01-30.png +0 -0
  1419. package/lib/codebase-bridge.cjs +308 -0
  1420. package/package.json +136 -0
  1421. package/plugins/specmem-agents/agents/bug-hunter.md +79 -0
  1422. package/plugins/specmem-agents/agents/memory-explorer.md +57 -0
  1423. package/plugins/specmem-agents/agents/team-coordinator.md +82 -0
  1424. package/scripts/auto-updater.cjs +399 -0
  1425. package/scripts/backfill-code-definition-embeddings.ts +440 -0
  1426. package/scripts/backfill-code-embeddings.ts +206 -0
  1427. package/scripts/capture-tos-screenshots.cjs +94 -0
  1428. package/scripts/check-global-install.cjs +67 -0
  1429. package/scripts/cleanup-embedding-servers.sh +25 -0
  1430. package/scripts/dashboard-standalone.sh +369 -0
  1431. package/scripts/deploy-hooks.cjs +1451 -0
  1432. package/scripts/deploy.sh +106 -0
  1433. package/scripts/docker-project-down.sh +83 -0
  1434. package/scripts/docker-project-list.sh +40 -0
  1435. package/scripts/docker-project-up.sh +79 -0
  1436. package/scripts/fast-backfill-embeddings.ts +173 -0
  1437. package/scripts/fast-batch-embedder.cjs +334 -0
  1438. package/scripts/first-run-model-setup.cjs +849 -0
  1439. package/scripts/global-postinstall.cjs +1957 -0
  1440. package/scripts/index-codebase.js +72 -0
  1441. package/scripts/migrate-fix-embeddings.py +110 -0
  1442. package/scripts/migrate-to-project-schemas.ts +525 -0
  1443. package/scripts/optimize-embedding-model.py +324 -0
  1444. package/scripts/optimize-instructions.cjs +530 -0
  1445. package/scripts/pack-docker-images.sh +68 -0
  1446. package/scripts/pack-for-testing.sh +130 -0
  1447. package/scripts/postinstall.cjs +54 -0
  1448. package/scripts/project-env.sh +51 -0
  1449. package/scripts/reset-db.sh +30 -0
  1450. package/scripts/run-indexer.ts +69 -0
  1451. package/scripts/run-migrations.js +47 -0
  1452. package/scripts/setup-db.sh +34 -0
  1453. package/scripts/setup-minimal-schema.sql +143 -0
  1454. package/scripts/skills/code-review.md +44 -0
  1455. package/scripts/skills/debugging.md +56 -0
  1456. package/scripts/skills/specmem-deployteam.md +239 -0
  1457. package/scripts/skills/teammemberskills/EFFICIENT_GREP.md +171 -0
  1458. package/scripts/skills/teammemberskills/task-planning.md +67 -0
  1459. package/scripts/specmem/sockets/session-start.lock +1 -0
  1460. package/scripts/specmem/sockets/session-stops.log +1 -0
  1461. package/scripts/specmem-health.sh +382 -0
  1462. package/scripts/specmem-init.cjs +6935 -0
  1463. package/scripts/strip-debug-logs.cjs +43 -0
  1464. package/scripts/test-mcp-standalone.sh +365 -0
  1465. package/scripts/test-optimized-models.py +166 -0
  1466. package/scripts/verify-embedding-fix.sh +148 -0
  1467. package/skills/code-review.md +44 -0
  1468. package/skills/debugging.md +56 -0
  1469. package/skills/specmem-deployteam.md +239 -0
  1470. package/skills/teammemberskills/EFFICIENT_GREP.md +171 -0
  1471. package/skills/teammemberskills/task-planning.md +67 -0
  1472. package/specmem-health.cjs +522 -0
  1473. package/specmem.env +216 -0
@@ -0,0 +1,4297 @@
1
+ /**
2
+ * webServer.ts - CSGO-Themed SpecMem Web Dashboard
3
+ *
4
+ * A badass web dashboard with CS:GO vibes for managing the SpecMem MCP Server.
5
+ * Yellow (#FFD700) and Black (#000000) color scheme with modal-based UI.
6
+ *
7
+ * Features:
8
+ * - Login system with password protection
9
+ * - Memory management (view/search/delete)
10
+ * - Session management ( sessions)
11
+ * - Codebase browser
12
+ * - Skills manager
13
+ * - Team member coordination viewer
14
+ * - Statistics dashboard
15
+ * - Configuration panel
16
+ *
17
+ * @author hardwicksoftwareservices
18
+ */
19
+ // @ts-ignore - express types not installed
20
+ import express from 'express';
21
+ // @ts-ignore - express-session types not installed
22
+ import session from 'express-session';
23
+ import rateLimit from 'express-rate-limit';
24
+ import { createServer } from 'http';
25
+ import { WebSocket, WebSocketServer } from 'ws';
26
+ import path from 'path';
27
+ import { fileURLToPath } from 'url';
28
+ import crypto from 'crypto';
29
+ import * as fs from 'fs/promises';
30
+ import { z } from 'zod';
31
+ import { logger } from '../utils/logger.js';
32
+ import { isPortAvailable, sleep } from '../utils/portUtils.js';
33
+ import { getDatabase } from '../database.js';
34
+ import { createSessionStore } from './sessionStore.js';
35
+ import { requestTimeout, setServerTimeouts } from '../utils/timeoutMiddleware.js';
36
+ import { getSkillScanner } from '../skills/skillScanner.js';
37
+ import { getCodebaseIndexer } from '../codebase/codebaseIndexer.js';
38
+ import { getMemoryManager } from '../utils/memoryManager.js';
39
+ import { getTeamMemberTracker } from '../team-members/teamMemberTracker.js';
40
+ import { getTeamMemberDeployment } from '../team-members/teamMemberDeployment.js';
41
+ import { getTeamMemberHistoryManager } from '../team-members/teamMemberHistory.js';
42
+ import { createTeamMemberDiscovery } from '../team-members/teamMemberDiscovery.js';
43
+ import { createTeamMemberCommunicator } from '../team-members/communication.js';
44
+ import { createMemoryRecallRouter } from './api/memoryRecall.js';
45
+ import { createTeamMemberHistoryRouter } from './api/teamMemberHistory.js';
46
+ import { createTeamMemberDeployRouter } from './api/teamMemberDeploy.js';
47
+ import { initializeTeamMemberStream, shutdownTeamMemberStream } from './websocket/teamMemberStream.js';
48
+ // Phase 4-6 imports for Direct Prompting, Terminal Streaming, and Control
49
+ import { createPromptSendRouter } from './api/promptSend.js';
50
+ import { createTerminalRouter } from './api/terminal.js';
51
+ import { createControlRouter } from './api/claudeControl.js';
52
+ import { createSpecmemToolsRouter } from './api/specmemTools.js';
53
+ import { createTerminalInjectRouter } from './api/terminalInject.js';
54
+ import { createTerminalStreamRouter, handleTerminalWebSocket } from './api/terminalStream.js';
55
+ // Live Session Streaming - Team Member 2's LIVE Code session viewer!
56
+ import { createLiveSessionRouter } from './api/liveSessionStream.js';
57
+ // Task team member logging
58
+ import { createTaskTeamMembersRouter } from './api/taskTeamMembers.js';
59
+ import { initializeTaskTeamMemberLogger } from '../team-members/taskTeamMemberLogger.js';
60
+ // File Manager - FTP-style file browsing for codebase management
61
+ import { createFileManagerRouter } from './api/fileManager.js';
62
+ // Settings API - Password management and dashboard configuration
63
+ import { createSettingsRouter } from './api/settings.js';
64
+ // Setup API - Dashboard mode switching and initial setup
65
+ import { createSetupRouter } from './api/setup.js';
66
+ // Data Export API - Export PostgreSQL tables to JSON
67
+ import { createDataExportRouter } from './api/dataExport.js';
68
+ // Hot Reload API - Dashboard control for hot reload system
69
+ import { createHotReloadRouter } from './api/hotReload.js';
70
+ // Camera Roll Search - zoom-based memory exploration
71
+ import { ZOOM_CONFIGS, formatAsCameraRollItem, formatAsCameraRollResponse } from '../services/CameraZoomSearch.js';
72
+ // Hooks Management API - User-manageable custom hooks
73
+ import { hooksRouter } from './api/hooks.js';
74
+ // Centralized password management
75
+ import { getPassword, checkPassword, isUsingDefaultPassword, changePasswordWithTeamMemberNotification } from '../config/password.js';
76
+ // Port allocation for unique per-instance ports
77
+ import { getInstancePortsSync, getDashboardPort, getCoordinationPort, getPortAllocationSummary, PORT_CONFIG } from '../utils/portAllocator.js';
78
+ // Project path for database isolation
79
+ import { getProjectPathForInsert } from '../services/ProjectContext.js';
80
+ // ============================================================================
81
+ // Zod Validation Schemas for Dashboard API
82
+ // ============================================================================
83
+ const MemoriesQuerySchema = z.object({
84
+ search: z.string().max(1000).optional(),
85
+ limit: z.coerce.number().int().min(1).max(500).default(50),
86
+ offset: z.coerce.number().int().min(0).default(0)
87
+ });
88
+ const BulkDeleteMemoriesSchema = z.object({
89
+ ids: z.array(z.string().uuid()).optional(),
90
+ olderThan: z.string().datetime().optional(),
91
+ tags: z.array(z.string()).optional(),
92
+ expiredOnly: z.boolean().optional()
93
+ }).refine(data => data.ids || data.olderThan || data.tags || data.expiredOnly, { message: 'At least one deletion criterion required (ids, olderThan, tags, or expiredOnly)' });
94
+ // Get __dirname equivalent for ES modules
95
+ const __filename = fileURLToPath(import.meta.url);
96
+ const __dirname = path.dirname(__filename);
97
+ // ============================================================================
98
+ // Default Configuration
99
+ // ============================================================================
100
+ /**
101
+ * Get or create a persistent session secret
102
+ * - First checks SPECMEM_SESSION_SECRET env var
103
+ * - Then checks SPECMEM_SESSION_SECRET_FILE for file-based secret
104
+ * - Falls back to generating one (logged as warning since it won't persist)
105
+ */
106
+ function getSessionSecret() {
107
+ // Option 1: Environment variable
108
+ const envSecret = process.env['SPECMEM_SESSION_SECRET'];
109
+ if (envSecret && envSecret.length >= 32) {
110
+ logger.debug('using session secret from SPECMEM_SESSION_SECRET env var');
111
+ return envSecret;
112
+ }
113
+ // Option 2: File-based secret
114
+ const secretFile = process.env['SPECMEM_SESSION_SECRET_FILE'];
115
+ if (secretFile) {
116
+ try {
117
+ const fsSync = require('fs');
118
+ if (fsSync.existsSync(secretFile)) {
119
+ const fileSecret = fsSync.readFileSync(secretFile, 'utf-8').trim();
120
+ if (fileSecret.length >= 32) {
121
+ logger.debug({ secretFile }, 'using session secret from file');
122
+ return fileSecret;
123
+ }
124
+ }
125
+ }
126
+ catch (e) {
127
+ logger.warn({ secretFile, error: e }, 'couldnt read session secret file');
128
+ }
129
+ }
130
+ // Option 3: Generate new (sessions won't persist across restarts)
131
+ logger.warn('generating random session secret - sessions will not persist across restarts bruh');
132
+ logger.warn('set SPECMEM_SESSION_SECRET or SPECMEM_SESSION_SECRET_FILE env var for persistent sessions');
133
+ return crypto.randomBytes(32).toString('hex');
134
+ }
135
+ /**
136
+ * Get dashboard mode from environment
137
+ * - 'private' (default): Localhost only, more secure
138
+ * - 'public': Network accessible, requires strong password
139
+ */
140
+ function getDashboardMode() {
141
+ const mode = process.env['SPECMEM_DASHBOARD_MODE'];
142
+ if (mode === 'public')
143
+ return 'public';
144
+ return 'private';
145
+ }
146
+ /**
147
+ * Get dashboard host based on mode
148
+ * Private mode: Always 127.0.0.1 (localhost only)
149
+ * Public mode: Use SPECMEM_DASHBOARD_HOST or 0.0.0.0 (all interfaces)
150
+ */
151
+ function getDashboardHost() {
152
+ const mode = getDashboardMode();
153
+ if (mode === 'private') {
154
+ return '127.0.0.1';
155
+ }
156
+ return process.env['SPECMEM_DASHBOARD_HOST'] || '0.0.0.0';
157
+ }
158
+ const DEFAULT_CONFIG = {
159
+ // Use dynamic port from portAllocator (project-hash derived)
160
+ port: parseInt(process.env['SPECMEM_DASHBOARD_PORT'] || '', 10) || getDashboardPort(),
161
+ host: getDashboardHost(),
162
+ mode: getDashboardMode(),
163
+ sessionSecret: getSessionSecret(),
164
+ password: '', // Must be set via SPECMEM_DASHBOARD_PASSWORD env var
165
+ // Use dynamic coordination port from portAllocator (project-hash derived)
166
+ coordinationPort: parseInt(process.env['SPECMEM_COORDINATION_PORT'] || '', 10) || getCoordinationPort(),
167
+ maxPortAttempts: 10,
168
+ maxStartupRetries: 3,
169
+ retryDelayMs: 1000
170
+ };
171
+ // ============================================================================
172
+ // Dashboard Web Server
173
+ // ============================================================================
174
+ export class DashboardWebServer {
175
+ config;
176
+ app;
177
+ server;
178
+ wss;
179
+ isRunning = false;
180
+ startTime = 0;
181
+ actualPort = 0; // The port we actually bound to
182
+ db = null;
183
+ sessionStore = null;
184
+ skillScanner = null;
185
+ codebaseIndexer = null;
186
+ embeddingProvider = null;
187
+ memoryManager = null;
188
+ embeddingOverflowHandler = null;
189
+ connectedClients = new Set();
190
+ envFilePath = null;
191
+ teamMemberTracker = null;
192
+ teamMemberDeployment = null;
193
+ teamMemberHistoryManager = null;
194
+ terminalStreamManager = null;
195
+ teamMemberStreamManager = null;
196
+ dashboardCommunicator = null;
197
+ dashboardDiscovery = null; // TeamMemberDiscovery instance for querying team members
198
+ constructor(config) {
199
+ this.config = { ...DEFAULT_CONFIG, ...config };
200
+ this.app = express();
201
+ this.server = createServer(this.app);
202
+ // CRITICAL: Set HTTP server keepalive to prevent connection drops
203
+ this.server.keepAliveTimeout = 120000; // 120 seconds
204
+ this.server.headersTimeout = 125000; // Must be > keepAliveTimeout
205
+ // CRITICAL FIX: Use noServer mode to prevent conflicts with other WebSocket handlers
206
+ // When multiple WebSocketServers are attached to the same HTTP server, they ALL
207
+ // receive the 'upgrade' event, causing the RSV1/1006 close bug where one WSS
208
+ // sends a 400 error after another has already completed the upgrade.
209
+ this.wss = new WebSocketServer({
210
+ noServer: true,
211
+ perMessageDeflate: false, // Disable compression to prevent issues
212
+ clientTracking: true,
213
+ maxPayload: 100 * 1024 * 1024 // 100MB max message size
214
+ });
215
+ // Centralized upgrade handling - route to appropriate WebSocket server
216
+ this.server.on('upgrade', (request, socket, head) => {
217
+ const url = new URL(request.url || '/', `http://${request.headers.host}`);
218
+ const pathname = url.pathname;
219
+ // Skip paths handled by other WebSocket managers (TeamMemberStreamManager handles /ws/team-members/live)
220
+ if (pathname === '/ws/team-members/live') {
221
+ // Let TeamMemberStreamManager handle this
222
+ return;
223
+ }
224
+ // Handle all other WebSocket paths
225
+ this.wss.handleUpgrade(request, socket, head, (ws) => {
226
+ this.wss.emit('connection', ws, request);
227
+ });
228
+ });
229
+ this.setupMiddleware();
230
+ this.setupRoutes();
231
+ this.setupWebSocket();
232
+ }
233
+ /**
234
+ * Setup Express middleware
235
+ */
236
+ setupMiddleware() {
237
+ // Parse JSON bodies
238
+ this.app.use(express.json());
239
+ this.app.use(express.urlencoded({ extended: true }));
240
+ // Request timeout middleware - prevents long-running requests from hanging
241
+ // Configurable via SPECMEM_REQUEST_TIMEOUT env var (default: 30 seconds)
242
+ this.app.use(requestTimeout({
243
+ timeout: parseInt(process.env['SPECMEM_REQUEST_TIMEOUT'] || '30000', 10),
244
+ message: 'Request timeout - try again or reduce the scope of your request',
245
+ log: true
246
+ }));
247
+ // Rate limiting for API endpoints
248
+ const apiLimiter = rateLimit({
249
+ windowMs: 1 * 60 * 1000, // 1 minute window (faster reset)
250
+ max: 1000, // 1000 requests per minute (teamMembers need this!)
251
+ message: { error: 'Too many requests, please try again later' },
252
+ standardHeaders: true,
253
+ legacyHeaders: false,
254
+ skip: (req) => {
255
+ // Skip rate limiting for localhost (teamMembers running on same machine)
256
+ const ip = req.ip || req.socket.remoteAddress || '';
257
+ return ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
258
+ }
259
+ });
260
+ // Much more relaxed auth limiting for team member communication
261
+ const authLimiter = rateLimit({
262
+ windowMs: 1 * 60 * 1000, // 1 minute window
263
+ max: 500, // 500 logins per minute (teamMembers retry a lot!)
264
+ message: { error: 'Too many login attempts, please try again later' },
265
+ standardHeaders: true,
266
+ legacyHeaders: false,
267
+ skip: (req) => {
268
+ // Skip rate limiting for localhost
269
+ const ip = req.ip || req.socket.remoteAddress || '';
270
+ return ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
271
+ }
272
+ });
273
+ // Apply rate limiting to API routes (but localhost is exempt!)
274
+ this.app.use('/api/', apiLimiter);
275
+ this.app.use('/api/login', authLimiter);
276
+ // Session management
277
+ // Cookie secure flag configurable via SPECMEM_COOKIE_SECURE env var (default: false)
278
+ const cookieSecure = process.env.SPECMEM_COOKIE_SECURE === 'true';
279
+ // Session options - store will be set in start() if database is available
280
+ const sessionOptions = {
281
+ secret: this.config.sessionSecret,
282
+ resave: false,
283
+ saveUninitialized: false,
284
+ cookie: {
285
+ secure: cookieSecure,
286
+ httpOnly: true,
287
+ maxAge: 24 * 60 * 60 * 1000 // 24 hours
288
+ }
289
+ };
290
+ // Note: We configure the session store in start() after database is available
291
+ // For now, use the default memory store (will be replaced in start())
292
+ this.app.use(session(sessionOptions));
293
+ // Serve static files - React app takes priority
294
+ this.app.use(express.static(path.join(__dirname, 'public', 'react-dist')));
295
+ this.app.use(express.static(path.join(__dirname, 'public')));
296
+ // CORS headers for API
297
+ // Private mode: restricted to localhost origins only
298
+ // Public mode: allows configured origins or same-origin requests
299
+ // Use dynamic ports for per-project isolation
300
+ const dynamicDashboardPort = this.config.port;
301
+ const dynamicCoordinationPort = this.config.coordinationPort;
302
+ const allowedOrigins = [
303
+ `http://localhost:${dynamicDashboardPort}`,
304
+ `http://127.0.0.1:${dynamicDashboardPort}`,
305
+ `http://localhost:${dynamicCoordinationPort}`,
306
+ `http://127.0.0.1:${dynamicCoordinationPort}`
307
+ ];
308
+ // In public mode, add the actual host binding to allowed origins
309
+ if (this.config.mode === 'public') {
310
+ // Allow requests from any host the server is bound to
311
+ allowedOrigins.push(`http://${this.config.host}:${this.config.port}`);
312
+ // Also allow requests from the local machine's actual IP/hostname
313
+ // The origin will be validated against what the browser sends
314
+ }
315
+ this.app.use((req, res, next) => {
316
+ const origin = req.headers.origin;
317
+ if (this.config.mode === 'public') {
318
+ // In public mode, use whitelist for CORS instead of reflecting any origin
319
+ // This prevents CSRF attacks while still allowing legitimate cross-origin requests
320
+ const publicModeWhitelist = [
321
+ ...allowedOrigins,
322
+ `http://${this.config.host}:${this.config.port}`,
323
+ // Allow common local development origins
324
+ 'http://localhost:3000',
325
+ 'http://localhost:5173',
326
+ 'http://127.0.0.1:3000',
327
+ 'http://127.0.0.1:5173'
328
+ ];
329
+ if (origin && publicModeWhitelist.includes(origin)) {
330
+ res.header('Access-Control-Allow-Origin', origin);
331
+ res.header('Access-Control-Allow-Credentials', 'true');
332
+ }
333
+ }
334
+ else {
335
+ // Private mode: strict origin checking
336
+ if (origin && allowedOrigins.includes(origin)) {
337
+ res.header('Access-Control-Allow-Origin', origin);
338
+ }
339
+ }
340
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
341
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
342
+ if (req.method === 'OPTIONS') {
343
+ res.sendStatus(204);
344
+ return;
345
+ }
346
+ next();
347
+ });
348
+ }
349
+ /**
350
+ * Authentication middleware
351
+ */
352
+ requireAuth(req, res, next) {
353
+ const session = req.session;
354
+ if (session?.authenticated) {
355
+ next();
356
+ }
357
+ else {
358
+ res.status(401).json({ error: 'Authentication required' });
359
+ }
360
+ }
361
+ /**
362
+ * Setup Express routes
363
+ */
364
+ setupRoutes() {
365
+ // ==================== PUBLIC ROUTES ====================
366
+ // Serve React app index.html
367
+ this.app.get('/', (req, res) => {
368
+ res.sendFile(path.join(__dirname, 'public', 'react-dist', 'index.html'));
369
+ });
370
+ // Health check
371
+ this.app.get('/health', (req, res) => {
372
+ res.json({
373
+ status: 'healthy',
374
+ uptime: this.isRunning ? Date.now() - this.startTime : 0,
375
+ service: 'specmem-dashboard'
376
+ });
377
+ });
378
+ // Login endpoint - uses centralized password management
379
+ this.app.post('/api/login', (req, res) => {
380
+ const { password } = req.body;
381
+ // Use centralized password check (supports runtime updates)
382
+ if (checkPassword(password)) {
383
+ const sess = req.session;
384
+ sess.authenticated = true;
385
+ sess.loginTime = Date.now();
386
+ logger.info('Dashboard login successful');
387
+ // Warn if using default password
388
+ if (isUsingDefaultPassword()) {
389
+ logger.warn('Login with DEFAULT password - consider changing for security');
390
+ }
391
+ res.json({ success: true, message: 'Login successful' });
392
+ }
393
+ else {
394
+ logger.warn('Dashboard login failed - incorrect password');
395
+ res.status(401).json({ error: 'Invalid password' });
396
+ }
397
+ });
398
+ // Logout endpoint
399
+ this.app.post('/api/logout', (req, res) => {
400
+ req.session.destroy((err) => {
401
+ if (err) {
402
+ logger.error({ err }, 'Error destroying session');
403
+ res.status(500).json({ error: 'Logout failed' });
404
+ }
405
+ else {
406
+ res.json({ success: true, message: 'Logged out' });
407
+ }
408
+ });
409
+ });
410
+ // Check auth status
411
+ this.app.get('/api/auth/status', (req, res) => {
412
+ const sess = req.session;
413
+ res.json({
414
+ authenticated: !!sess?.authenticated,
415
+ loginTime: sess?.loginTime || null
416
+ });
417
+ });
418
+ // Client-side logging endpoint - logs browser errors/messages to server
419
+ // This allows dashboard pages to send errors to the server for proper logging
420
+ this.app.post('/api/log', (req, res) => {
421
+ const { level, message, page, data } = req.body;
422
+ // Validate level
423
+ const validLevels = ['info', 'warn', 'error', 'debug'];
424
+ const logLevel = validLevels.includes(level) ? level : 'info';
425
+ // Construct log context
426
+ const logContext = {
427
+ source: 'dashboard-client',
428
+ page: page || 'unknown',
429
+ userAgent: req.headers['user-agent'],
430
+ ...(data && typeof data === 'object' ? data : { extra: data })
431
+ };
432
+ // Log using the appropriate level
433
+ switch (logLevel) {
434
+ case 'error':
435
+ logger.error(logContext, `[Dashboard] ${message}`);
436
+ break;
437
+ case 'warn':
438
+ logger.warn(logContext, `[Dashboard] ${message}`);
439
+ break;
440
+ case 'debug':
441
+ logger.debug(logContext, `[Dashboard] ${message}`);
442
+ break;
443
+ default:
444
+ logger.info(logContext, `[Dashboard] ${message}`);
445
+ }
446
+ res.json({ success: true });
447
+ });
448
+ // ==================== PROTECTED ROUTES ====================
449
+ // Port allocation status endpoint - shows allocated ports for this instance
450
+ this.app.get('/api/ports', this.requireAuth.bind(this), async (req, res) => {
451
+ try {
452
+ const allocatedPorts = getInstancePortsSync();
453
+ if (allocatedPorts) {
454
+ const summary = getPortAllocationSummary(allocatedPorts);
455
+ res.json({
456
+ success: true,
457
+ allocated: true,
458
+ ports: {
459
+ dashboard: summary.dashboard,
460
+ coordination: summary.coordination
461
+ },
462
+ projectPath: summary.projectPath,
463
+ verified: summary.verified,
464
+ config: {
465
+ minPort: PORT_CONFIG.MIN_PORT,
466
+ maxPort: PORT_CONFIG.MAX_PORT,
467
+ defaults: PORT_CONFIG.DEFAULTS
468
+ }
469
+ });
470
+ }
471
+ else {
472
+ // Fallback to current configuration
473
+ res.json({
474
+ success: true,
475
+ allocated: false,
476
+ ports: {
477
+ dashboard: {
478
+ port: getDashboardPort(),
479
+ url: `http://localhost:${getDashboardPort()}`
480
+ },
481
+ coordination: {
482
+ port: getCoordinationPort(),
483
+ wsUrl: `ws://localhost:${getCoordinationPort()}/teamMembers`
484
+ }
485
+ },
486
+ projectPath: process.cwd(),
487
+ config: {
488
+ minPort: PORT_CONFIG.MIN_PORT,
489
+ maxPort: PORT_CONFIG.MAX_PORT,
490
+ defaults: PORT_CONFIG.DEFAULTS
491
+ }
492
+ });
493
+ }
494
+ }
495
+ catch (error) {
496
+ logger.error({ error }, 'Error fetching port allocation');
497
+ res.status(500).json({ error: 'Port info not available' });
498
+ }
499
+ });
500
+ // Database metrics endpoint (#31)
501
+ this.app.get('/api/metrics/database', this.requireAuth.bind(this), async (req, res) => {
502
+ try {
503
+ if (!this.db) {
504
+ res.status(503).json({ error: 'Database not connected bruh' });
505
+ return;
506
+ }
507
+ const metrics = await this.db.getDetailedMetrics();
508
+ res.json(metrics);
509
+ }
510
+ catch (error) {
511
+ logger.error({ error }, 'Error fetching database metrics');
512
+ res.status(500).json({ error: 'Database metrics said nah fr fr' });
513
+ }
514
+ });
515
+ // Stats dashboard
516
+ this.app.get('/api/stats', this.requireAuth.bind(this), async (req, res) => {
517
+ try {
518
+ const stats = await this.getStats();
519
+ res.json(stats);
520
+ }
521
+ catch (error) {
522
+ logger.error({ error }, 'Error fetching stats');
523
+ res.status(500).json({ error: 'Stats ain\'t loading bruh' });
524
+ }
525
+ });
526
+ // Memory management routes
527
+ this.app.get('/api/memories', this.requireAuth.bind(this), async (req, res) => {
528
+ try {
529
+ const parseResult = MemoriesQuerySchema.safeParse(req.query);
530
+ if (!parseResult.success) {
531
+ res.status(400).json({
532
+ error: 'Invalid query parameters',
533
+ details: parseResult.error.issues.map(i => ({ path: i.path.join('.'), message: i.message }))
534
+ });
535
+ return;
536
+ }
537
+ const { search, limit, offset } = parseResult.data;
538
+ const memories = await this.getMemories(search, limit, offset);
539
+ res.json(memories);
540
+ }
541
+ catch (error) {
542
+ logger.error({ error }, 'Error fetching memories');
543
+ res.status(500).json({ error: 'Memories not loading lmao' });
544
+ }
545
+ });
546
+ // Search memories - MUST be before :id route to avoid matching "search" as an ID
547
+ // Supports camera roll mode for zoom-based exploration with drilldown IDs
548
+ this.app.get('/api/memories/search', this.requireAuth.bind(this), async (req, res) => {
549
+ try {
550
+ const q = req.query.q || req.query.query || '';
551
+ const limit = parseInt(req.query.limit) || 50;
552
+ const offset = parseInt(req.query.offset) || 0;
553
+ // Camera roll mode parameters
554
+ const cameraRollMode = req.query.cameraRollMode === 'true' || req.query.cameraRollMode === '1';
555
+ const zoomLevelParam = req.query.zoomLevel;
556
+ const validZoomLevels = ['ultra-wide', 'wide', 'normal', 'close', 'macro'];
557
+ const zoomLevel = validZoomLevels.includes(zoomLevelParam)
558
+ ? zoomLevelParam
559
+ : 'normal';
560
+ // Standard search mode (backward compatible)
561
+ if (!cameraRollMode) {
562
+ const memories = await this.getMemories(q, limit, offset);
563
+ res.json({ success: true, query: q, memories: memories.memories, total: memories.total });
564
+ return;
565
+ }
566
+ // Camera roll mode - use zoom-based search with drilldown IDs
567
+ const zoomConfig = ZOOM_CONFIGS[zoomLevel];
568
+ const effectiveLimit = Math.min(limit, zoomConfig.limit);
569
+ // Perform the search with zoom-appropriate threshold
570
+ const searchResult = await this.getCameraRollMemories(q, zoomConfig.threshold, effectiveLimit, offset);
571
+ // Format results as CameraRollItems with drilldown IDs
572
+ const items = searchResult.memories.map((memory) => {
573
+ return formatAsCameraRollItem({
574
+ id: memory.id,
575
+ content: memory.content,
576
+ similarity: memory.similarity || 0.5,
577
+ metadata: memory.metadata,
578
+ tags: memory.tags,
579
+ createdAt: memory.created_at
580
+ }, zoomConfig, {
581
+ claudeResponse: memory.metadata?.claudeResponse,
582
+ relatedCount: memory.metadata?.relatedCount,
583
+ codePointers: memory.metadata?.codePointers
584
+ });
585
+ });
586
+ // Format as CameraRollResponse - now returns compact XML string
587
+ const xmlResponse = formatAsCameraRollResponse(items, q, zoomLevel, searchResult.searchType === 'hybrid' ? 'hybrid' : 'memory', searchResult.total);
588
+ // Extract drilldownIDs for easy access
589
+ const drilldownIDs = items.map(item => item.drilldownID);
590
+ // Return XML directly with metadata
591
+ res.json({
592
+ success: true,
593
+ cameraRollMode: true,
594
+ drilldownIDs,
595
+ response: xmlResponse
596
+ });
597
+ }
598
+ catch (error) {
599
+ logger.error({ error }, 'Error searching memories');
600
+ res.status(500).json({ error: 'Memory search failed fr' });
601
+ }
602
+ });
603
+ // Get single memory by ID
604
+ this.app.get('/api/memories/:id', this.requireAuth.bind(this), async (req, res) => {
605
+ try {
606
+ const { id } = req.params;
607
+ const memory = await this.getMemoryById(id);
608
+ if (!memory) {
609
+ res.status(404).json({ error: 'Memory not found' });
610
+ return;
611
+ }
612
+ res.json(memory);
613
+ }
614
+ catch (error) {
615
+ logger.error({ error }, 'Error fetching memory');
616
+ res.status(500).json({ error: 'Memory fetch broke fr' });
617
+ }
618
+ });
619
+ this.app.delete('/api/memories/:id', this.requireAuth.bind(this), async (req, res) => {
620
+ try {
621
+ const { id } = req.params;
622
+ await this.deleteMemory(id);
623
+ res.json({ success: true, message: 'Memory deleted' });
624
+ }
625
+ catch (error) {
626
+ logger.error({ error }, 'Error deleting memory');
627
+ res.status(500).json({ error: 'Couldn\'t yeet that memory' });
628
+ }
629
+ });
630
+ // Bulk delete memories
631
+ this.app.post('/api/memories/bulk-delete', this.requireAuth.bind(this), async (req, res) => {
632
+ try {
633
+ const parseResult = BulkDeleteMemoriesSchema.safeParse(req.body);
634
+ if (!parseResult.success) {
635
+ res.status(400).json({
636
+ error: 'Invalid request body',
637
+ details: parseResult.error.issues.map(i => ({ path: i.path.join('.'), message: i.message }))
638
+ });
639
+ return;
640
+ }
641
+ const { ids, olderThan, tags, expiredOnly } = parseResult.data;
642
+ const result = await this.bulkDeleteMemories({ ids, olderThan, tags, expiredOnly });
643
+ res.json({ success: true, deleted: result.deleted, message: `${result.deleted} memories deleted` });
644
+ }
645
+ catch (error) {
646
+ logger.error({ error }, 'Error bulk deleting memories');
647
+ res.status(500).json({ error: 'Bulk delete didn\'t work rip' });
648
+ }
649
+ });
650
+ // Session management routes
651
+ this.app.get('/api/sessions', this.requireAuth.bind(this), async (req, res) => {
652
+ try {
653
+ const sessions = await this.getSessions();
654
+ res.json(sessions);
655
+ }
656
+ catch (error) {
657
+ logger.error({ error }, 'Error fetching sessions');
658
+ res.status(500).json({ error: 'Sessions not loading yo' });
659
+ }
660
+ });
661
+ // Get session details by ID
662
+ this.app.get('/api/sessions/:id', this.requireAuth.bind(this), async (req, res) => {
663
+ try {
664
+ const sessionId = req.params.id;
665
+ // Validate session ID format - must be alphanumeric with dashes/underscores (UUID-like or custom format)
666
+ // Prevents SQL injection and other attacks via malformed session IDs
667
+ const sessionIdRegex = /^[a-zA-Z0-9_-]{1,128}$/;
668
+ if (!sessionIdRegex.test(sessionId)) {
669
+ res.status(400).json({ error: 'Invalid session ID format' });
670
+ return;
671
+ }
672
+ if (!this.db) {
673
+ res.status(503).json({ error: 'Database not connected' });
674
+ return;
675
+ }
676
+ // Get session details from memories - use sessionId (camelCase) from metadata
677
+ const sessionQuery = `
678
+ WITH session_tags AS (
679
+ SELECT DISTINCT unnest(tags) as tag
680
+ FROM memories
681
+ WHERE COALESCE(metadata->>'sessionId', metadata->>'session_id') = $1
682
+ )
683
+ SELECT
684
+ COALESCE(metadata->>'sessionId', metadata->>'session_id') as session_id,
685
+ MIN(created_at) as started_at,
686
+ MAX(updated_at) as last_activity,
687
+ COUNT(*) as memory_count,
688
+ COUNT(DISTINCT memory_type) as memory_types_used,
689
+ array_agg(DISTINCT memory_type) as memory_types,
690
+ array_agg(DISTINCT importance) as importance_levels,
691
+ (SELECT array_agg(tag) FROM session_tags) as all_tags,
692
+ MAX(metadata->>'project') as project,
693
+ MAX(metadata->>'workingDirectory') as working_directory
694
+ FROM memories
695
+ WHERE COALESCE(metadata->>'sessionId', metadata->>'session_id') = $1
696
+ GROUP BY COALESCE(metadata->>'sessionId', metadata->>'session_id')
697
+ `;
698
+ const result = await this.db.query(sessionQuery, [sessionId]);
699
+ if (result.rows.length === 0) {
700
+ res.status(404).json({ error: 'Session not found' });
701
+ return;
702
+ }
703
+ const session = result.rows[0];
704
+ // Get memories for this session - use sessionId (camelCase) from metadata
705
+ const memoriesQuery = `
706
+ SELECT id, content, memory_type, importance, tags, created_at, updated_at, metadata
707
+ FROM memories
708
+ WHERE COALESCE(metadata->>'sessionId', metadata->>'session_id') = $1
709
+ ORDER BY created_at DESC
710
+ LIMIT 100
711
+ `;
712
+ const memories = await this.db.query(memoriesQuery, [sessionId]);
713
+ res.json({
714
+ ...session,
715
+ memories: memories.rows,
716
+ duration_minutes: session.last_activity && session.started_at
717
+ ? (new Date(session.last_activity).getTime() - new Date(session.started_at).getTime()) / 60000
718
+ : 0
719
+ });
720
+ }
721
+ catch (error) {
722
+ logger.error({ error }, 'Error fetching session details');
723
+ res.status(500).json({ error: 'Session details broke' });
724
+ }
725
+ });
726
+ // Get messages for a session
727
+ this.app.get('/api/sessions/:id/messages', this.requireAuth.bind(this), async (req, res) => {
728
+ try {
729
+ const sessionId = req.params.id;
730
+ // Validate session ID format - must be alphanumeric with dashes/underscores
731
+ const sessionIdRegex = /^[a-zA-Z0-9_-]{1,128}$/;
732
+ if (!sessionIdRegex.test(sessionId)) {
733
+ res.status(400).json({ error: 'Invalid session ID format' });
734
+ return;
735
+ }
736
+ if (!this.db) {
737
+ res.status(503).json({ error: 'Database not connected' });
738
+ return;
739
+ }
740
+ // Get memories for this session (these are the "messages") - use sessionId (camelCase)
741
+ const query = `
742
+ SELECT
743
+ id,
744
+ content,
745
+ memory_type,
746
+ importance,
747
+ tags,
748
+ created_at,
749
+ updated_at,
750
+ metadata
751
+ FROM memories
752
+ WHERE COALESCE(metadata->>'sessionId', metadata->>'session_id') = $1
753
+ ORDER BY created_at ASC
754
+ `;
755
+ const result = await this.db.query(query, [sessionId]);
756
+ res.json(result.rows);
757
+ }
758
+ catch (error) {
759
+ logger.error({ error }, 'Error fetching session messages');
760
+ res.status(500).json({ error: 'Session messages ain\'t showing' });
761
+ }
762
+ });
763
+ // Codebase browser routes
764
+ this.app.get('/api/codebase', this.requireAuth.bind(this), async (req, res) => {
765
+ try {
766
+ const { path: filePath, search } = req.query;
767
+ const files = await this.getCodebaseFiles(filePath, search);
768
+ res.json(files);
769
+ }
770
+ catch (error) {
771
+ logger.error({ error }, 'Error fetching codebase');
772
+ res.status(500).json({ error: 'Codebase fetch broke lmao' });
773
+ }
774
+ });
775
+ // Get file content from codebase
776
+ this.app.get('/api/codebase/file', this.requireAuth.bind(this), async (req, res) => {
777
+ try {
778
+ const { path: filePath } = req.query;
779
+ if (!filePath || typeof filePath !== 'string') {
780
+ res.status(400).json({ error: 'File path required' });
781
+ return;
782
+ }
783
+ const fileContent = await this.getFileContent(filePath);
784
+ if (!fileContent) {
785
+ res.status(404).json({ error: 'File not found' });
786
+ return;
787
+ }
788
+ res.json(fileContent);
789
+ }
790
+ catch (error) {
791
+ logger.error({ error }, 'Error fetching file content');
792
+ res.status(500).json({ error: 'File content not loading' });
793
+ }
794
+ });
795
+ // Skills manager routes
796
+ this.app.get('/api/skills', this.requireAuth.bind(this), async (req, res) => {
797
+ try {
798
+ const skills = await this.getSkills();
799
+ res.json(skills);
800
+ }
801
+ catch (error) {
802
+ logger.error({ error }, 'Error fetching skills');
803
+ res.status(500).json({ error: 'Skills ain\'t loading' });
804
+ }
805
+ });
806
+ this.app.post('/api/skills/reload', this.requireAuth.bind(this), async (req, res) => {
807
+ try {
808
+ await this.reloadSkills();
809
+ res.json({ success: true, message: 'Skills reloaded' });
810
+ }
811
+ catch (error) {
812
+ logger.error({ error }, 'Error reloading skills');
813
+ res.status(500).json({ error: 'Skills reload broke' });
814
+ }
815
+ });
816
+ // Get individual skill content by name
817
+ this.app.get('/api/skills/:name', this.requireAuth.bind(this), async (req, res) => {
818
+ try {
819
+ if (!this.skillScanner) {
820
+ res.status(503).json({ error: 'Skills system not initialized' });
821
+ return;
822
+ }
823
+ const skillName = decodeURIComponent(req.params.name);
824
+ const skills = this.skillScanner.getAllSkills();
825
+ const skill = skills.find(s => s.name === skillName || s.id === skillName);
826
+ if (!skill) {
827
+ res.status(404).json({ error: 'Skill not found' });
828
+ return;
829
+ }
830
+ res.json({
831
+ id: skill.id,
832
+ name: skill.name,
833
+ category: skill.category,
834
+ description: skill.description,
835
+ content: skill.content,
836
+ path: skill.filePath
837
+ });
838
+ }
839
+ catch (error) {
840
+ logger.error({ error }, 'Error fetching skill content');
841
+ res.status(500).json({ error: 'Skill content not loading' });
842
+ }
843
+ });
844
+ // Update skill content
845
+ this.app.put('/api/skills/:name', this.requireAuth.bind(this), async (req, res) => {
846
+ try {
847
+ if (!this.skillScanner) {
848
+ res.status(503).json({ error: 'Skills system not initialized' });
849
+ return;
850
+ }
851
+ const skillName = decodeURIComponent(req.params.name);
852
+ const { content } = req.body;
853
+ if (!content) {
854
+ res.status(400).json({ error: 'Content is required' });
855
+ return;
856
+ }
857
+ const skills = this.skillScanner.getAllSkills();
858
+ const skill = skills.find(s => s.name === skillName || s.id === skillName);
859
+ if (!skill) {
860
+ res.status(404).json({ error: 'Skill not found' });
861
+ return;
862
+ }
863
+ // Write content to file
864
+ const fs = await import('fs/promises');
865
+ await fs.writeFile(skill.filePath, content, 'utf-8');
866
+ // Reload skills to reflect changes
867
+ await this.skillScanner.scan();
868
+ res.json({ success: true, message: 'Skill updated, no cap' });
869
+ }
870
+ catch (error) {
871
+ logger.error({ error }, 'Error updating skill');
872
+ res.status(500).json({ error: 'Skill update didn\'t work' });
873
+ }
874
+ });
875
+ // Team member coordination routes
876
+ this.app.get('/api/teamMembers', this.requireAuth.bind(this), async (req, res) => {
877
+ try {
878
+ const teamMembers = await this.getTeamMembers();
879
+ res.json(teamMembers);
880
+ }
881
+ catch (error) {
882
+ logger.error({ error }, 'Error fetching team members');
883
+ res.status(500).json({ error: 'Failed to fetch team members' });
884
+ }
885
+ });
886
+ // ==================== TEAM_MEMBER COMMUNICATION DASHBOARD ROUTES ====================
887
+ // GET /api/team-members/active - list all currently active team member sessions
888
+ this.app.get('/api/team-members/active', this.requireAuth.bind(this), async (req, res) => {
889
+ try {
890
+ const activeTeamMembers = await this.getActiveTeamMemberSessions();
891
+ res.json(activeTeamMembers);
892
+ }
893
+ catch (error) {
894
+ logger.error({ error }, 'Error fetching active team members');
895
+ res.status(500).json({ error: 'Failed to fetch active team members' });
896
+ }
897
+ });
898
+ // GET /api/team-members/history - list past team member sessions with pagination
899
+ this.app.get('/api/team-members/history', this.requireAuth.bind(this), async (req, res) => {
900
+ try {
901
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
902
+ const offset = parseInt(req.query.offset) || 0;
903
+ const teamMemberType = req.query.type;
904
+ const status = req.query.status;
905
+ const history = await this.getTeamMemberSessionHistory(limit, offset, teamMemberType, status);
906
+ res.json(history);
907
+ }
908
+ catch (error) {
909
+ logger.error({ error }, 'Error fetching team member history');
910
+ res.status(500).json({ error: 'Failed to fetch team member history' });
911
+ }
912
+ });
913
+ // GET /api/team-members/session/:id - get detailed session info with messages
914
+ this.app.get('/api/team-members/session/:id', this.requireAuth.bind(this), async (req, res) => {
915
+ try {
916
+ const { id } = req.params;
917
+ const includeMessages = req.query.messages !== 'false';
918
+ const messageLimit = Math.min(parseInt(req.query.messageLimit) || 100, 500);
919
+ const session = await this.getTeamMemberSessionDetails(id, includeMessages, messageLimit);
920
+ if (!session) {
921
+ res.status(404).json({ error: 'Session not found' });
922
+ return;
923
+ }
924
+ res.json(session);
925
+ }
926
+ catch (error) {
927
+ logger.error({ error }, 'Error fetching session details');
928
+ res.status(500).json({ error: 'Session details broke' });
929
+ }
930
+ });
931
+ // GET /api/team-members/session/:id/messages - get messages for a session with pagination
932
+ this.app.get('/api/team-members/session/:id/messages', this.requireAuth.bind(this), async (req, res) => {
933
+ try {
934
+ const { id } = req.params;
935
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
936
+ const offset = parseInt(req.query.offset) || 0;
937
+ const messageType = req.query.type;
938
+ const messages = await this.getTeamMemberSessionMessages(id, limit, offset, messageType);
939
+ res.json(messages);
940
+ }
941
+ catch (error) {
942
+ logger.error({ error }, 'Error fetching session messages');
943
+ res.status(500).json({ error: 'Session messages ain\'t showing' });
944
+ }
945
+ });
946
+ // GET /api/team-members/deployments - list team member deployments
947
+ this.app.get('/api/team-members/deployments', this.requireAuth.bind(this), async (req, res) => {
948
+ try {
949
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
950
+ const offset = parseInt(req.query.offset) || 0;
951
+ const status = req.query.status;
952
+ const environment = req.query.environment;
953
+ const deployments = await this.getTeamMemberDeployments(limit, offset, status, environment);
954
+ res.json(deployments);
955
+ }
956
+ catch (error) {
957
+ logger.error({ error }, 'Error fetching deployments');
958
+ res.status(500).json({ error: 'Failed to fetch deployments' });
959
+ }
960
+ });
961
+ // GET /api/team-members/stats - get aggregate team member statistics
962
+ this.app.get('/api/team-members/stats', this.requireAuth.bind(this), async (req, res) => {
963
+ try {
964
+ const stats = await this.getTeamMemberStats();
965
+ res.json(stats);
966
+ }
967
+ catch (error) {
968
+ logger.error({ error }, 'Error fetching team member stats');
969
+ res.status(500).json({ error: 'Failed to fetch team member stats' });
970
+ }
971
+ });
972
+ // GET /api/team-members/collaboration/stats - get team member collaboration statistics
973
+ this.app.get('/api/team-members/collaboration/stats', this.requireAuth.bind(this), async (req, res) => {
974
+ try {
975
+ if (!this.db) {
976
+ res.status(503).json({ error: 'Database not connected' });
977
+ return;
978
+ }
979
+ // Get collaboration statistics from team member sessions and messages
980
+ const collaborationQuery = `
981
+ SELECT
982
+ COUNT(DISTINCT s.id) as total_collaborations,
983
+ COUNT(DISTINCT s.team_member_id) as unique_teamMembers,
984
+ COUNT(m.id) as total_messages,
985
+ AVG(EXTRACT(EPOCH FROM (s.ended_at - s.started_at))) as avg_duration_seconds,
986
+ COUNT(DISTINCT DATE(s.started_at)) as active_days,
987
+ MAX(s.started_at) as last_collaboration
988
+ FROM team_member_sessions s
989
+ LEFT JOIN team_member_messages m ON m.session_id = s.id
990
+ WHERE s.started_at >= NOW() - INTERVAL '30 days'
991
+ `;
992
+ const result = await this.db.query(collaborationQuery);
993
+ const stats = result.rows[0];
994
+ res.json({
995
+ total_collaborations: parseInt(stats.total_collaborations) || 0,
996
+ unique_teamMembers: parseInt(stats.unique_teamMembers) || 0,
997
+ total_messages: parseInt(stats.total_messages) || 0,
998
+ avg_duration_seconds: parseFloat(stats.avg_duration_seconds) || 0,
999
+ active_days: parseInt(stats.active_days) || 0,
1000
+ last_collaboration: stats.last_collaboration || null,
1001
+ period: '30_days'
1002
+ });
1003
+ }
1004
+ catch (error) {
1005
+ logger.error({ error }, 'Error fetching collaboration stats');
1006
+ res.status(500).json({ error: 'Failed to fetch collaboration stats' });
1007
+ }
1008
+ });
1009
+ // Team Member Deployment & Tracking Routes
1010
+ // BUG FIX (Team Member 2): Added discovery-based team members to the list
1011
+ this.app.get('/api/team-members/list', this.requireAuth.bind(this), async (req, res) => {
1012
+ try {
1013
+ // Get team members from deployment tracker
1014
+ const deployedTeamMembers = await this.teamMemberTracker?.getAllTeamMembers() || [];
1015
+ const deployedIds = new Set(deployedTeamMembers.map(a => a.id));
1016
+ // Also get team members from discovery service (SpecMem heartbeat-based)
1017
+ let discoveredTeamMembers = [];
1018
+ try {
1019
+ if (this.dashboardDiscovery) {
1020
+ const discovered = await this.dashboardDiscovery.getActiveTeamMembers(120000); // 2 min expiry
1021
+ // Convert discovered team members to the same format, excluding already-deployed ones
1022
+ discoveredTeamMembers = discovered
1023
+ .filter((d) => !deployedIds.has(d.teamMemberId))
1024
+ .map((d) => ({
1025
+ id: d.teamMemberId,
1026
+ name: d.teamMemberName || d.teamMemberId.substring(0, 8),
1027
+ type: d.teamMemberType || 'worker',
1028
+ status: d.status === 'active' || d.status === 'busy' ? 'running' : d.status === 'idle' ? 'pending' : 'stopped',
1029
+ tokensUsed: d.metadata?.tokensUsed || 0,
1030
+ tokensLimit: d.metadata?.tokensLimit || 20000,
1031
+ createdAt: d.registeredAt || d.lastHeartbeat,
1032
+ lastHeartbeat: d.lastHeartbeat,
1033
+ currentTask: d.metadata?.currentTask,
1034
+ metadata: { ...d.metadata, source: 'discovery' }
1035
+ }));
1036
+ }
1037
+ }
1038
+ catch (discErr) {
1039
+ logger.debug({ error: discErr }, 'Discovery service not available');
1040
+ }
1041
+ // ALSO get Task team members from team_member_sessions table
1042
+ let taskTeamMembers = [];
1043
+ if (this.db) {
1044
+ try {
1045
+ const result = await this.db.query(`
1046
+ SELECT
1047
+ team_member_id as id,
1048
+ team_member_name as name,
1049
+ team_member_type as type,
1050
+ status,
1051
+ current_task,
1052
+ started_at as created_at,
1053
+ tokens_used,
1054
+ metadata,
1055
+ last_heartbeat
1056
+ FROM team_member_sessions
1057
+ ORDER BY started_at DESC
1058
+ LIMIT 100
1059
+ `);
1060
+ taskTeamMembers = result.rows.map((row) => ({
1061
+ id: row.id,
1062
+ name: row.name || row.id.substring(0, 8),
1063
+ type: row.type || 'worker',
1064
+ status: row.status === 'terminated' ? 'completed' : row.status === 'error' ? 'failed' : row.status,
1065
+ tokensUsed: row.tokens_used || 0,
1066
+ tokensLimit: row.metadata?.tokensLimit || 128000,
1067
+ createdAt: row.created_at,
1068
+ lastHeartbeat: row.last_heartbeat,
1069
+ currentTask: row.current_task ? { name: row.current_task, progress: 0 } : undefined,
1070
+ metadata: { ...row.metadata, source: 'task-team-member' }
1071
+ }));
1072
+ }
1073
+ catch (taskErr) {
1074
+ logger.debug({ error: taskErr }, 'Failed to get Task team members');
1075
+ }
1076
+ }
1077
+ const allTeamMembers = [...deployedTeamMembers, ...discoveredTeamMembers, ...taskTeamMembers];
1078
+ res.json({ success: true, teamMembers: allTeamMembers });
1079
+ }
1080
+ catch (error) {
1081
+ logger.error({ error }, 'Error fetching team member list');
1082
+ res.status(500).json({ success: false, error: 'Failed to fetch team members' });
1083
+ }
1084
+ });
1085
+ // REMOVED: Duplicate route handlers - now using teamMemberDeployRouter instead
1086
+ this.app.post('/api/team-members/:id/restart', this.requireAuth.bind(this), async (req, res) => {
1087
+ try {
1088
+ const { id } = req.params;
1089
+ const result = await this.teamMemberDeployment?.restart(id);
1090
+ res.json({ success: result });
1091
+ }
1092
+ catch (error) {
1093
+ logger.error({ error }, 'Error restarting team member');
1094
+ res.status(500).json({ success: false, error: 'Failed to restart team member' });
1095
+ }
1096
+ });
1097
+ this.app.get('/api/team-members/:id/logs', this.requireAuth.bind(this), async (req, res) => {
1098
+ try {
1099
+ const { id } = req.params;
1100
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
1101
+ const offset = parseInt(req.query.offset) || 0;
1102
+ // Check if this is a Task team member (from team_member_sessions table)
1103
+ let isTaskTeamMember = false;
1104
+ let sessionDbId = null;
1105
+ if (this.db) {
1106
+ try {
1107
+ const sessionCheck = await this.db.query(`
1108
+ SELECT id, metadata FROM team_member_sessions WHERE team_member_id = $1 LIMIT 1
1109
+ `, [id]);
1110
+ if (sessionCheck.rows.length > 0) {
1111
+ isTaskTeamMember = true;
1112
+ sessionDbId = sessionCheck.rows[0].id;
1113
+ }
1114
+ }
1115
+ catch (checkErr) {
1116
+ logger.debug({ error: checkErr }, 'Could not check for Task team member session');
1117
+ }
1118
+ }
1119
+ if (isTaskTeamMember && sessionDbId && this.db) {
1120
+ // Fetch logs for Task team member from team_member_logs table using session's DB id
1121
+ try {
1122
+ const result = await this.db.query(`
1123
+ SELECT
1124
+ al.id,
1125
+ al.team_member_id,
1126
+ al.level,
1127
+ al.message,
1128
+ al.metadata,
1129
+ al.created_at as timestamp
1130
+ FROM team_member_logs al
1131
+ WHERE al.team_member_id = $1
1132
+ ORDER BY al.created_at DESC
1133
+ LIMIT $2 OFFSET $3
1134
+ `, [sessionDbId, limit, offset]);
1135
+ const logs = result.rows.map((row) => ({
1136
+ id: row.id,
1137
+ teamMemberId: id,
1138
+ level: row.level || 'info',
1139
+ message: row.message,
1140
+ metadata: row.metadata,
1141
+ timestamp: row.timestamp
1142
+ }));
1143
+ res.json({ success: true, logs, isTaskTeamMember: true });
1144
+ return;
1145
+ }
1146
+ catch (logErr) {
1147
+ logger.debug({ error: logErr }, 'Could not fetch Task team member logs, falling back');
1148
+ }
1149
+ }
1150
+ // Fall back to native team member logs from teamMemberTracker
1151
+ const logs = await this.teamMemberTracker?.getLogs(id, limit, offset) || [];
1152
+ res.json({ success: true, logs });
1153
+ }
1154
+ catch (error) {
1155
+ logger.error({ error }, 'Error fetching team member logs');
1156
+ res.status(500).json({ success: false, error: 'Failed to fetch logs' });
1157
+ }
1158
+ });
1159
+ this.app.get('/api/team-members/:id/stats', this.requireAuth.bind(this), async (req, res) => {
1160
+ try {
1161
+ const { id } = req.params;
1162
+ const teamMember = await this.teamMemberTracker?.getTeamMember(id);
1163
+ if (!teamMember) {
1164
+ res.status(404).json({ success: false, error: 'Team Member not found' });
1165
+ return;
1166
+ }
1167
+ res.json({ success: true, teamMember });
1168
+ }
1169
+ catch (error) {
1170
+ logger.error({ error }, 'Error fetching team member stats');
1171
+ res.status(500).json({ success: false, error: 'Failed to fetch team member stats' });
1172
+ }
1173
+ });
1174
+ // TeamMember limits endpoint
1175
+ this.app.get('/api/team-members/:id/limits', this.requireAuth.bind(this), async (req, res) => {
1176
+ try {
1177
+ const { id } = req.params;
1178
+ const limits = this.teamMemberDeployment?.getTeamMemberLimits(id);
1179
+ const status = this.teamMemberDeployment?.getTeamMemberLimitStatus(id);
1180
+ if (!limits) {
1181
+ res.status(404).json({ success: false, error: 'Team Member limits not found' });
1182
+ return;
1183
+ }
1184
+ res.json({ success: true, limits, status });
1185
+ }
1186
+ catch (error) {
1187
+ logger.error({ error }, 'Error fetching team member limits');
1188
+ res.status(500).json({ success: false, error: 'Failed to fetch team member limits' });
1189
+ }
1190
+ });
1191
+ // ==================== TEAM_MEMBER COLLABORATION ROUTES ====================
1192
+ this.app.post('/api/team-members/:id/share-code', this.requireAuth.bind(this), async (req, res) => {
1193
+ try {
1194
+ const { id } = req.params;
1195
+ const { title, description, code, filePath, language, tags } = req.body;
1196
+ if (!title || !code) {
1197
+ res.status(400).json({ success: false, error: 'Title and code required' });
1198
+ return;
1199
+ }
1200
+ const shared = await this.teamMemberTracker?.shareCode(id, { title, description, code, filePath, language, tags });
1201
+ this.broadcastUpdate('code_shared', shared);
1202
+ res.json({ success: true, sharedCode: shared });
1203
+ }
1204
+ catch (error) {
1205
+ logger.error({ error }, 'Error sharing code');
1206
+ res.status(500).json({ success: false, error: 'Failed to share code' });
1207
+ }
1208
+ });
1209
+ this.app.get('/api/team-members/shared-code', this.requireAuth.bind(this), async (req, res) => {
1210
+ try {
1211
+ const limit = Math.min(parseInt(req.query.limit) || 50, 100);
1212
+ const offset = parseInt(req.query.offset) || 0;
1213
+ const sharedCode = await this.teamMemberTracker?.getAllSharedCode(limit, offset) || [];
1214
+ res.json({ success: true, sharedCode });
1215
+ }
1216
+ catch (error) {
1217
+ logger.error({ error }, 'Error fetching shared code');
1218
+ res.status(500).json({ success: false, error: 'Failed to fetch shared code' });
1219
+ }
1220
+ });
1221
+ this.app.get('/api/team-members/shared-code/:codeId', this.requireAuth.bind(this), async (req, res) => {
1222
+ try {
1223
+ const { codeId } = req.params;
1224
+ const code = await this.teamMemberTracker?.getSharedCode(codeId);
1225
+ if (!code) {
1226
+ res.status(404).json({ success: false, error: 'Shared code not found' });
1227
+ return;
1228
+ }
1229
+ res.json({ success: true, sharedCode: code });
1230
+ }
1231
+ catch (error) {
1232
+ logger.error({ error }, 'Error fetching shared code');
1233
+ res.status(500).json({ success: false, error: 'Failed to fetch shared code' });
1234
+ }
1235
+ });
1236
+ this.app.get('/api/team-members/:id/shared-code', this.requireAuth.bind(this), async (req, res) => {
1237
+ try {
1238
+ const { id } = req.params;
1239
+ const limit = Math.min(parseInt(req.query.limit) || 50, 100);
1240
+ const sharedCode = await this.teamMemberTracker?.getSharedCodeByTeamMember(id, limit) || [];
1241
+ res.json({ success: true, sharedCode });
1242
+ }
1243
+ catch (error) {
1244
+ logger.error({ error }, 'Error fetching team member shared code');
1245
+ res.status(500).json({ success: false, error: 'Failed to fetch shared code' });
1246
+ }
1247
+ });
1248
+ this.app.get('/api/team-members/shared-code/:codeId/chunk/:index', this.requireAuth.bind(this), async (req, res) => {
1249
+ try {
1250
+ const { codeId, index } = req.params;
1251
+ const chunkIndex = parseInt(index, 10);
1252
+ if (isNaN(chunkIndex) || chunkIndex < 0) {
1253
+ res.status(400).json({ success: false, error: 'Invalid chunk index' });
1254
+ return;
1255
+ }
1256
+ const chunk = await this.teamMemberTracker?.getCodeChunk(codeId, chunkIndex);
1257
+ if (!chunk) {
1258
+ res.status(404).json({ success: false, error: 'Chunk not found' });
1259
+ return;
1260
+ }
1261
+ res.json({ success: true, chunk });
1262
+ }
1263
+ catch (error) {
1264
+ logger.error({ error }, 'Error fetching code chunk');
1265
+ res.status(500).json({ success: false, error: 'Failed to fetch code chunk' });
1266
+ }
1267
+ });
1268
+ this.app.get('/api/team-members/shared-code/:codeId/download', this.requireAuth.bind(this), async (req, res) => {
1269
+ try {
1270
+ const { codeId } = req.params;
1271
+ const code = await this.teamMemberTracker?.getSharedCode(codeId);
1272
+ if (!code) {
1273
+ res.status(404).json({ success: false, error: 'Shared code not found' });
1274
+ return;
1275
+ }
1276
+ const fullCode = await this.teamMemberTracker?.getFullCode(codeId);
1277
+ if (!fullCode) {
1278
+ res.status(404).json({ success: false, error: 'Code content not found' });
1279
+ return;
1280
+ }
1281
+ const ext = this.getFileExtension(code.language);
1282
+ const filename = code.filePath ? code.filePath.split('/').pop() : `${code.title.replace(/[^a-z0-9]/gi, '_')}.${ext}`;
1283
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
1284
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
1285
+ res.setHeader('Content-Length', Buffer.byteLength(fullCode, 'utf-8'));
1286
+ res.send(fullCode);
1287
+ }
1288
+ catch (error) {
1289
+ logger.error({ error }, 'Error downloading code');
1290
+ res.status(500).json({ success: false, error: 'Failed to download code' });
1291
+ }
1292
+ });
1293
+ this.app.post('/api/team-members/shared-code/:codeId/feedback', this.requireAuth.bind(this), async (req, res) => {
1294
+ try {
1295
+ const { codeId } = req.params;
1296
+ const { fromTeamMemberId, feedbackType, message } = req.body;
1297
+ if (!fromTeamMemberId || !feedbackType || !message) {
1298
+ res.status(400).json({ success: false, error: 'fromTeamMemberId, feedbackType, and message required' });
1299
+ return;
1300
+ }
1301
+ if (!['positive', 'negative', 'question', 'critique'].includes(feedbackType)) {
1302
+ res.status(400).json({ success: false, error: 'Invalid feedback type' });
1303
+ return;
1304
+ }
1305
+ const feedback = await this.teamMemberTracker?.giveFeedback(fromTeamMemberId, codeId, feedbackType, message);
1306
+ this.broadcastUpdate('feedback_given', feedback);
1307
+ res.json({ success: true, feedback });
1308
+ }
1309
+ catch (error) {
1310
+ logger.error({ error }, 'Error giving feedback');
1311
+ res.status(500).json({ success: false, error: 'Failed to give feedback' });
1312
+ }
1313
+ });
1314
+ this.app.get('/api/team-members/shared-code/:codeId/feedback', this.requireAuth.bind(this), async (req, res) => {
1315
+ try {
1316
+ const { codeId } = req.params;
1317
+ const limit = Math.min(parseInt(req.query.limit) || 50, 100);
1318
+ const feedback = await this.teamMemberTracker?.getFeedbackForCode(codeId, limit) || [];
1319
+ res.json({ success: true, feedback });
1320
+ }
1321
+ catch (error) {
1322
+ logger.error({ error }, 'Error fetching feedback');
1323
+ res.status(500).json({ success: false, error: 'Failed to fetch feedback' });
1324
+ }
1325
+ });
1326
+ this.app.post('/api/team-members/:id/message', this.requireAuth.bind(this), async (req, res) => {
1327
+ try {
1328
+ const { id } = req.params;
1329
+ const { toTeamMemberId, message, metadata } = req.body;
1330
+ if (!toTeamMemberId || !message) {
1331
+ res.status(400).json({ success: false, error: 'toTeamMemberId and message required' });
1332
+ return;
1333
+ }
1334
+ const msg = await this.teamMemberTracker?.sendMessage(id, toTeamMemberId, message, metadata);
1335
+ this.broadcastUpdate('message_sent', msg);
1336
+ res.json({ success: true, message: msg });
1337
+ }
1338
+ catch (error) {
1339
+ logger.error({ error }, 'Error sending message');
1340
+ res.status(500).json({ success: false, error: 'Failed to send message' });
1341
+ }
1342
+ });
1343
+ this.app.get('/api/team-members/:id/messages', this.requireAuth.bind(this), async (req, res) => {
1344
+ try {
1345
+ const { id } = req.params;
1346
+ const limit = Math.min(parseInt(req.query.limit) || 50, 100);
1347
+ const unreadOnly = req.query.unreadOnly === 'true';
1348
+ const messages = await this.teamMemberTracker?.getMessagesForTeamMember(id, limit, unreadOnly) || [];
1349
+ res.json({ success: true, messages });
1350
+ }
1351
+ catch (error) {
1352
+ logger.error({ error }, 'Error fetching messages');
1353
+ res.status(500).json({ success: false, error: 'Failed to fetch messages' });
1354
+ }
1355
+ });
1356
+ this.app.post('/api/team-members/messages/:messageId/read', this.requireAuth.bind(this), async (req, res) => {
1357
+ try {
1358
+ const { messageId } = req.params;
1359
+ await this.teamMemberTracker?.markMessageRead(messageId);
1360
+ res.json({ success: true });
1361
+ }
1362
+ catch (error) {
1363
+ logger.error({ error }, 'Error marking message read');
1364
+ res.status(500).json({ success: false, error: 'Failed to mark message read' });
1365
+ }
1366
+ });
1367
+ this.app.get('/api/team-members/:id/unread-count', this.requireAuth.bind(this), async (req, res) => {
1368
+ try {
1369
+ const { id } = req.params;
1370
+ const count = await this.teamMemberTracker?.getUnreadMessageCount(id) || 0;
1371
+ res.json({ success: true, count });
1372
+ }
1373
+ catch (error) {
1374
+ logger.error({ error }, 'Error fetching unread count');
1375
+ res.status(500).json({ success: false, error: 'Failed to fetch unread count' });
1376
+ }
1377
+ });
1378
+ this.app.get('/api/team-members/:id/pending-reviews', this.requireAuth.bind(this), async (req, res) => {
1379
+ try {
1380
+ const { id } = req.params;
1381
+ const pendingReviews = await this.teamMemberTracker?.getPendingReviewsForTeamMember(id) || [];
1382
+ res.json({ success: true, pendingReviews });
1383
+ }
1384
+ catch (error) {
1385
+ logger.error({ error }, 'Error fetching pending reviews');
1386
+ res.status(500).json({ success: false, error: 'Failed to fetch pending reviews' });
1387
+ }
1388
+ });
1389
+ this.app.get('/api/team-members/collaboration/stats', this.requireAuth.bind(this), async (req, res) => {
1390
+ try {
1391
+ const stats = this.teamMemberTracker?.getCollaborationStats() || {
1392
+ totalSharedCode: 0, totalFeedback: 0, totalMessages: 0, positiveRatio: 0
1393
+ };
1394
+ res.json({ success: true, stats });
1395
+ }
1396
+ catch (error) {
1397
+ logger.error({ error }, 'Error fetching collaboration stats');
1398
+ res.status(500).json({ success: false, error: 'Failed to fetch collaboration stats' });
1399
+ }
1400
+ });
1401
+ // ==================== TEAM_MEMBER COMMAND ROUTE ====================
1402
+ // POST /api/team-members/:id/command - Send command to team member stdin OR via SpecMem
1403
+ // BUG FIX (Team Member 2): Added fallback to SpecMem-based communication for discovered team members
1404
+ this.app.post('/api/team-members/:id/command', this.requireAuth.bind(this), async (req, res) => {
1405
+ try {
1406
+ const { id } = req.params;
1407
+ const { command } = req.body;
1408
+ if (!command || typeof command !== 'object') {
1409
+ res.status(400).json({ success: false, error: 'Command must be a valid object' });
1410
+ return;
1411
+ }
1412
+ // Validate command has a type
1413
+ if (!command.type || typeof command.type !== 'string') {
1414
+ res.status(400).json({ success: false, error: 'Command must have a type field' });
1415
+ return;
1416
+ }
1417
+ // First try sending via deployment manager (for spawned processes)
1418
+ let result = await this.teamMemberDeployment?.sendCommand(id, command);
1419
+ // If deployment manager failed, try SpecMem-based communication
1420
+ if (!result && this.dashboardCommunicator) {
1421
+ try {
1422
+ // Check if team member exists in discovery
1423
+ const teamMemberOnline = this.dashboardDiscovery ?
1424
+ await this.dashboardDiscovery.isTeamMemberOnline(id) : false;
1425
+ if (teamMemberOnline) {
1426
+ // Send message via SpecMem
1427
+ const messageContent = JSON.stringify({
1428
+ type: 'command',
1429
+ command: command,
1430
+ from: 'dashboard',
1431
+ timestamp: new Date().toISOString()
1432
+ });
1433
+ const sent = await this.dashboardCommunicator.say(messageContent, id, { priority: 'high' });
1434
+ if (sent) {
1435
+ result = {
1436
+ success: true,
1437
+ response: { status: 'sent', via: 'specmem' },
1438
+ queued: true
1439
+ };
1440
+ logger.info({ teamMemberId: id }, 'Command sent via SpecMem communicator');
1441
+ }
1442
+ }
1443
+ }
1444
+ catch (specMemErr) {
1445
+ logger.debug({ error: specMemErr }, 'SpecMem communication failed');
1446
+ }
1447
+ }
1448
+ if (!result) {
1449
+ res.status(404).json({ success: false, error: 'Team Member not found or not running' });
1450
+ return;
1451
+ }
1452
+ // Log the command (truncate for safety)
1453
+ const commandStr = JSON.stringify(command);
1454
+ const truncatedCmd = commandStr.length > 200 ? commandStr.substring(0, 200) + '...' : commandStr;
1455
+ await this.teamMemberTracker?.addLog(id, 'info', `Command sent: ${truncatedCmd}`);
1456
+ // Broadcast command event via WebSocket
1457
+ this.broadcastUpdate('teamMember_command', {
1458
+ teamMemberId: id,
1459
+ command,
1460
+ timestamp: new Date().toISOString()
1461
+ });
1462
+ res.json({ success: true, response: result.response, queued: result.queued });
1463
+ }
1464
+ catch (error) {
1465
+ logger.error({ error }, 'Error sending command to team member');
1466
+ res.status(500).json({ success: false, error: 'Failed to send command to team member' });
1467
+ }
1468
+ });
1469
+ // ==================== TEAM_MEMBER HISTORY ROUTES ====================
1470
+ // BUG FIX (Team Member 2): Also include discovered team members with active sessions
1471
+ this.app.get('/api/team-members/history/teamMembers', this.requireAuth.bind(this), async (req, res) => {
1472
+ try {
1473
+ // Get historical team members from session database
1474
+ const historicalTeamMembers = await this.teamMemberHistoryManager?.getTeamMembersWithSessionCounts() || [];
1475
+ const historicalIds = new Set(historicalTeamMembers.map(a => a.id));
1476
+ // Also include currently active discovered team members (they may have no DB sessions yet)
1477
+ let activeTeamMembers = [];
1478
+ try {
1479
+ if (this.dashboardDiscovery) {
1480
+ const discovered = await this.dashboardDiscovery.getActiveTeamMembers(300000); // 5 min window
1481
+ activeTeamMembers = discovered
1482
+ .filter((d) => !historicalIds.has(d.teamMemberId))
1483
+ .map((d) => ({
1484
+ id: d.teamMemberId,
1485
+ name: d.teamMemberName || d.teamMemberId.substring(0, 8),
1486
+ type: d.teamMemberType || 'worker',
1487
+ sessionCount: 1, // Current session counts as 1
1488
+ lastSessionDate: d.lastHeartbeat,
1489
+ totalTokensUsed: d.metadata?.tokensUsed || 0
1490
+ }));
1491
+ }
1492
+ }
1493
+ catch (discErr) {
1494
+ logger.debug({ error: discErr }, 'Discovery not available for history');
1495
+ }
1496
+ const allTeamMembers = [...historicalTeamMembers, ...activeTeamMembers];
1497
+ res.json({ success: true, teamMembers: allTeamMembers });
1498
+ }
1499
+ catch (error) {
1500
+ logger.error({ error }, 'Error fetching team members with session counts');
1501
+ res.status(500).json({ success: false, error: 'Failed to fetch team member history list' });
1502
+ }
1503
+ });
1504
+ // BUG FIX (Team Member 2): Also return synthetic session for currently active discovered team members
1505
+ this.app.get('/api/team-members/:id/sessions', this.requireAuth.bind(this), async (req, res) => {
1506
+ try {
1507
+ const { id } = req.params;
1508
+ const limit = Math.min(parseInt(req.query.limit) || 10, 50);
1509
+ const offset = parseInt(req.query.offset) || 0;
1510
+ // Get sessions from history manager
1511
+ let sessions = await this.teamMemberHistoryManager?.getSessionsForTeamMember(id, limit, offset) || [];
1512
+ // If no sessions found, check if this is a currently active discovered teamMember
1513
+ if (sessions.length === 0 && this.dashboardDiscovery) {
1514
+ try {
1515
+ const teamMemberInfo = await this.dashboardDiscovery.getTeamMemberInfo(id);
1516
+ if (teamMemberInfo) {
1517
+ // Create a synthetic "current session" for this active teamMember
1518
+ sessions = [{
1519
+ id: `live-${id}`,
1520
+ teamMemberId: id,
1521
+ teamMemberName: teamMemberInfo.teamMemberName || id.substring(0, 8),
1522
+ teamMemberType: teamMemberInfo.teamMemberType || 'worker',
1523
+ sessionStart: teamMemberInfo.registeredAt || teamMemberInfo.lastHeartbeat,
1524
+ sessionEnd: null,
1525
+ taskCount: teamMemberInfo.metadata?.currentTask ? 1 : 0,
1526
+ codeCount: 0,
1527
+ feedbackCount: 0,
1528
+ messageCount: 0,
1529
+ tokensUsed: teamMemberInfo.metadata?.tokensUsed || 0,
1530
+ status: teamMemberInfo.status === 'active' || teamMemberInfo.status === 'busy' ? 'running' : 'completed',
1531
+ summary: `Active session - ${teamMemberInfo.status}`
1532
+ }];
1533
+ }
1534
+ }
1535
+ catch (discErr) {
1536
+ logger.debug({ error: discErr }, 'Discovery not available for sessions');
1537
+ }
1538
+ }
1539
+ res.json({ success: true, sessions });
1540
+ }
1541
+ catch (error) {
1542
+ logger.error({ error }, 'Error fetching sessions for team member');
1543
+ res.status(500).json({ success: false, error: 'Failed to fetch sessions' });
1544
+ }
1545
+ });
1546
+ // BUG FIX (Team Member 2): Handle live sessions for discovered team members
1547
+ this.app.get('/api/team-members/sessions/:sessionId', this.requireAuth.bind(this), async (req, res) => {
1548
+ try {
1549
+ const { sessionId } = req.params;
1550
+ // Check for live session (synthetic session for discovered team members)
1551
+ if (sessionId.startsWith('live-') && this.dashboardDiscovery) {
1552
+ const teamMemberId = sessionId.substring(5); // Remove 'live-' prefix
1553
+ const teamMemberInfo = await this.dashboardDiscovery.getTeamMemberInfo(teamMemberId);
1554
+ if (teamMemberInfo) {
1555
+ const liveSession = {
1556
+ id: sessionId,
1557
+ teamMemberId: teamMemberId,
1558
+ teamMemberName: teamMemberInfo.teamMemberName || teamMemberId.substring(0, 8),
1559
+ teamMemberType: teamMemberInfo.teamMemberType || 'worker',
1560
+ sessionStart: teamMemberInfo.registeredAt || teamMemberInfo.lastHeartbeat,
1561
+ sessionEnd: null,
1562
+ tasksCompleted: teamMemberInfo.metadata?.currentTask ? [{
1563
+ id: 'current',
1564
+ name: teamMemberInfo.metadata.currentTask,
1565
+ status: 'running',
1566
+ startedAt: teamMemberInfo.lastHeartbeat
1567
+ }] : [],
1568
+ codeSharedIds: [],
1569
+ feedbackGivenIds: [],
1570
+ messagesSentIds: [],
1571
+ tokensUsed: teamMemberInfo.metadata?.tokensUsed || 0,
1572
+ status: 'running',
1573
+ summary: `Live session for ${teamMemberInfo.teamMemberName || teamMemberId}`
1574
+ };
1575
+ res.json({ success: true, session: liveSession });
1576
+ return;
1577
+ }
1578
+ }
1579
+ // Fall back to database session
1580
+ const session = await this.teamMemberHistoryManager?.getSessionDetails(sessionId);
1581
+ if (!session) {
1582
+ res.status(404).json({ success: false, error: 'Session not found' });
1583
+ return;
1584
+ }
1585
+ res.json({ success: true, session });
1586
+ }
1587
+ catch (error) {
1588
+ logger.error({ error }, 'Error fetching session details');
1589
+ res.status(500).json({ success: false, error: 'Failed to fetch session details' });
1590
+ }
1591
+ });
1592
+ // BUG FIX (Team Member 2): Handle live session logs from SpecMem messages
1593
+ this.app.get('/api/team-members/sessions/:sessionId/logs', this.requireAuth.bind(this), async (req, res) => {
1594
+ try {
1595
+ const { sessionId } = req.params;
1596
+ const limit = Math.min(parseInt(req.query.limit) || 100, 500);
1597
+ const offset = parseInt(req.query.offset) || 0;
1598
+ // Check for live session logs
1599
+ if (sessionId.startsWith('live-') && this.dashboardCommunicator) {
1600
+ const teamMemberId = sessionId.substring(5);
1601
+ try {
1602
+ // Try to get recent messages from/to this team member as "logs"
1603
+ const messages = await this.dashboardCommunicator.getMessages(new Date(Date.now() - 3600000) // Last hour
1604
+ );
1605
+ const teamMemberLogs = messages
1606
+ .filter(m => m.from === teamMemberId || m.to === teamMemberId)
1607
+ .map((m, i) => ({
1608
+ id: m.messageId || `msg-${i}`,
1609
+ teamMemberId: m.from,
1610
+ timestamp: m.timestamp,
1611
+ level: m.messageType === 'status' ? 'info' : 'debug',
1612
+ message: m.content
1613
+ }))
1614
+ .slice(offset, offset + limit);
1615
+ res.json({
1616
+ success: true,
1617
+ logs: teamMemberLogs,
1618
+ totalCount: teamMemberLogs.length,
1619
+ limit,
1620
+ offset
1621
+ });
1622
+ return;
1623
+ }
1624
+ catch (msgErr) {
1625
+ logger.debug({ error: msgErr }, 'Could not get messages for live session');
1626
+ }
1627
+ }
1628
+ // Fall back to database logs
1629
+ const logs = await this.teamMemberHistoryManager?.getSessionLogs(sessionId, limit, offset) || [];
1630
+ const totalCount = await this.teamMemberHistoryManager?.getSessionLogCount(sessionId) || 0;
1631
+ res.json({ success: true, logs, totalCount, limit, offset });
1632
+ }
1633
+ catch (error) {
1634
+ logger.error({ error }, 'Error fetching session logs');
1635
+ res.status(500).json({ success: false, error: 'Failed to fetch session logs' });
1636
+ }
1637
+ });
1638
+ // Configuration routes
1639
+ this.app.get('/api/config', this.requireAuth.bind(this), (req, res) => {
1640
+ res.json({
1641
+ coordinationPort: this.config.coordinationPort,
1642
+ dashboardPort: this.config.port
1643
+ });
1644
+ });
1645
+ this.app.post('/api/config/password', this.requireAuth.bind(this), async (req, res) => {
1646
+ const { currentPassword, newPassword } = req.body;
1647
+ // Use centralized password change with team member notification
1648
+ // This handles: validation, runtime update, env persistence, hook update, and team member notification
1649
+ const result = await changePasswordWithTeamMemberNotification(currentPassword, newPassword, true);
1650
+ if (!result.success) {
1651
+ // Determine appropriate status code based on error
1652
+ const statusCode = result.message.includes('incorrect') ? 401 : 400;
1653
+ res.status(statusCode).json({ error: result.message });
1654
+ return;
1655
+ }
1656
+ // Also update this.config.password for backwards compatibility with any code
1657
+ // that still reads from config directly
1658
+ this.config.password = newPassword;
1659
+ logger.info({
1660
+ persisted: result.persisted,
1661
+ hookUpdated: result.hookUpdated,
1662
+ teamMembersNotified: result.teamMembersNotified
1663
+ }, 'Dashboard password changed via centralized system');
1664
+ res.json({
1665
+ success: true,
1666
+ message: result.message,
1667
+ persisted: result.persisted,
1668
+ hookUpdated: result.hookUpdated,
1669
+ teamMembersNotified: result.teamMembersNotified
1670
+ });
1671
+ });
1672
+ // ==================== MEMORY MANAGEMENT ROUTES ====================
1673
+ // Get memory configuration
1674
+ this.app.get('/api/memory/config', this.requireAuth.bind(this), async (req, res) => {
1675
+ try {
1676
+ const memoryConfig = await this.getMemoryConfig();
1677
+ res.json(memoryConfig);
1678
+ }
1679
+ catch (error) {
1680
+ logger.error({ error }, 'Error fetching memory config');
1681
+ res.status(500).json({ error: 'Failed to fetch memory configuration' });
1682
+ }
1683
+ });
1684
+ // Update memory configuration
1685
+ this.app.post('/api/memory/config', this.requireAuth.bind(this), async (req, res) => {
1686
+ try {
1687
+ const { memoryLimit, overflowTime, cacheSize } = req.body;
1688
+ // Validate inputs
1689
+ if (memoryLimit !== undefined && (memoryLimit < 50 || memoryLimit > 200)) {
1690
+ res.status(400).json({ error: 'Memory limit must be between 50 and 200 MB' });
1691
+ return;
1692
+ }
1693
+ if (overflowTime !== undefined && (overflowTime < 0 || overflowTime > 72)) {
1694
+ res.status(400).json({ error: 'Overflow time must be between 0 and 72 hours' });
1695
+ return;
1696
+ }
1697
+ if (cacheSize !== undefined && (cacheSize < 100 || cacheSize > 1000)) {
1698
+ res.status(400).json({ error: 'Cache size must be between 100 and 1000 entries' });
1699
+ return;
1700
+ }
1701
+ const persisted = await this.persistMemoryConfig({ memoryLimit, overflowTime, cacheSize });
1702
+ logger.info({ memoryLimit, overflowTime, cacheSize, persisted }, 'Memory configuration updated');
1703
+ res.json({
1704
+ success: true,
1705
+ message: persisted ? 'Memory configuration saved to specmem.env' : 'Memory configuration updated (in-memory only)',
1706
+ config: { memoryLimit, overflowTime, cacheSize }
1707
+ });
1708
+ }
1709
+ catch (error) {
1710
+ logger.error({ error }, 'Error updating memory config');
1711
+ res.status(500).json({ error: 'Failed to update memory configuration' });
1712
+ }
1713
+ });
1714
+ // Get memory statistics
1715
+ this.app.get('/api/memory/stats', this.requireAuth.bind(this), async (req, res) => {
1716
+ try {
1717
+ const stats = await this.getMemoryStats();
1718
+ res.json(stats);
1719
+ }
1720
+ catch (error) {
1721
+ logger.error({ error }, 'Error fetching memory stats');
1722
+ res.status(500).json({ error: 'Failed to fetch memory statistics' });
1723
+ }
1724
+ });
1725
+ // Trigger overflow cleanup
1726
+ this.app.post('/api/memory/overflow', this.requireAuth.bind(this), async (req, res) => {
1727
+ try {
1728
+ const result = await this.triggerOverflowCleanup();
1729
+ this.broadcastUpdate('overflow_triggered', result);
1730
+ res.json(result);
1731
+ }
1732
+ catch (error) {
1733
+ logger.error({ error }, 'Error triggering overflow cleanup');
1734
+ res.status(500).json({ error: 'Failed to trigger overflow cleanup' });
1735
+ }
1736
+ });
1737
+ // Emergency memory purge
1738
+ this.app.post('/api/memory/purge', this.requireAuth.bind(this), async (req, res) => {
1739
+ try {
1740
+ const result = await this.emergencyMemoryPurge();
1741
+ this.broadcastUpdate('emergency_purge', result);
1742
+ logger.warn({ result }, 'Emergency memory purge executed');
1743
+ res.json(result);
1744
+ }
1745
+ catch (error) {
1746
+ logger.error({ error }, 'Error executing emergency purge');
1747
+ res.status(500).json({ error: 'Failed to execute emergency purge' });
1748
+ }
1749
+ });
1750
+ // Clear specific cache
1751
+ this.app.delete('/api/memory/cache/:type', this.requireAuth.bind(this), async (req, res) => {
1752
+ try {
1753
+ const { type } = req.params;
1754
+ if (!['query', 'embedding'].includes(type)) {
1755
+ res.status(400).json({ error: 'Invalid cache type. Must be "query" or "embedding"' });
1756
+ return;
1757
+ }
1758
+ const result = await this.clearCache(type);
1759
+ this.broadcastUpdate('cache_cleared', { type, ...result });
1760
+ res.json(result);
1761
+ }
1762
+ catch (error) {
1763
+ logger.error({ error }, 'Error clearing cache');
1764
+ res.status(500).json({ error: 'Failed to clear cache' });
1765
+ }
1766
+ });
1767
+ // ==================== PHASE 4-6 ROUTES ====================
1768
+ // Phase 4: Direct Prompting API
1769
+ const promptSendRouter = createPromptSendRouter(this.db, this.requireAuth.bind(this));
1770
+ this.app.use('/api/prompt', promptSendRouter);
1771
+ // Phase 5: Terminal Output API
1772
+ const terminalRouter = createTerminalRouter(this.requireAuth.bind(this));
1773
+ this.app.use('/api/terminal', terminalRouter);
1774
+ // Terminal Injection API - Direct prompt injection into Code terminal!
1775
+ const terminalInjectRouter = createTerminalInjectRouter(this.requireAuth.bind(this));
1776
+ this.app.use('/api/terminal-inject', terminalInjectRouter);
1777
+ // Terminal Streaming API - PTY streaming with full ANSI support!
1778
+ const terminalStreamRouter = createTerminalStreamRouter(this.requireAuth.bind(this));
1779
+ this.app.use('/api/terminal-stream', terminalStreamRouter);
1780
+ // Phase 6: Control API
1781
+ const claudeControlRouter = createControlRouter(this.db, this.requireAuth.bind(this), this.broadcastUpdate.bind(this));
1782
+ this.app.use('/api/claude', claudeControlRouter);
1783
+ // Specmem Tools API - Expose MCP tools to team members via HTTP
1784
+ // Pass embedding provider GETTER so HTTP endpoints use REAL MCP tool semantic search!
1785
+ // NOTE: Using getter function because embeddingProvider is set AFTER server starts
1786
+ const specmemToolsRouter = createSpecmemToolsRouter(() => this.db, this.requireAuth.bind(this), () => this.embeddingProvider);
1787
+ // SECURITY: Localhost-only access for SpecMem API (internal bridge only)
1788
+ // This ensures SpecMem API is NEVER exposed to public internet
1789
+ this.app.use('/api/specmem', (req, res, next) => {
1790
+ const clientIP = req.ip || req.socket?.remoteAddress || '';
1791
+ const isLocalhost = ['127.0.0.1', '::1', '::ffff:127.0.0.1', 'localhost'].some(ip => clientIP.includes(ip) || clientIP === ip);
1792
+ if (!isLocalhost) {
1793
+ console.warn(`[SPECMEM-API] BLOCKED non-localhost access attempt from: ${clientIP}`);
1794
+ return res.status(403).json({ error: 'Access denied - localhost only' });
1795
+ }
1796
+ next();
1797
+ });
1798
+ // SECURITY: Encrypted payload middleware for SpecMem API
1799
+ // Decrypts Serpent-32 encrypted payloads from SpecMemSecurityBridge
1800
+ this.app.use('/api/specmem', (req, res, next) => {
1801
+ // Check for encrypted payload header
1802
+ if (req.headers['x-specmem-encrypted'] === 'serpent-32' && req.body?._encrypted) {
1803
+ try {
1804
+ const { _payload, _timestamp, _nonce } = req.body;
1805
+ // Validate timestamp (prevent replay attacks - max 5 min old)
1806
+ const MAX_AGE_MS = 5 * 60 * 1000;
1807
+ if (Date.now() - _timestamp > MAX_AGE_MS) {
1808
+ console.warn('[SPECMEM-API] Rejected stale encrypted request (replay attack?)');
1809
+ return res.status(400).json({ error: 'Request expired' });
1810
+ }
1811
+ // Decrypt using shared secret + nonce
1812
+ const EncryptedDataCommunication = require('/server/serverModules/security/EncryptedDataCommunication');
1813
+ const apiSecret = process.env.SPECMEM_API_SECRET || 'specmem_serpent_key_2025_security';
1814
+ const decryptor = new EncryptedDataCommunication({ encryptionKey: apiSecret });
1815
+ // Reconstruct the key from shared secret + nonce (same as encryptData does)
1816
+ const decrypted = decryptor.decryptData({
1817
+ encrypted: _payload,
1818
+ nonce: _nonce
1819
+ }, 'specmem-bridge');
1820
+ req.body = decrypted;
1821
+ console.log('[SPECMEM-API] Decrypted incoming encrypted payload');
1822
+ }
1823
+ catch (err) {
1824
+ console.error('[SPECMEM-API] Decryption failed:', err.message);
1825
+ return res.status(400).json({ error: 'Decryption failed' });
1826
+ }
1827
+ }
1828
+ next();
1829
+ });
1830
+ this.app.use('/api/specmem', specmemToolsRouter);
1831
+ // Task Team Members API - Track Code Task tool deployments
1832
+ const taskTeamMembersRouter = createTaskTeamMembersRouter();
1833
+ this.app.use('/api/task-team-members', taskTeamMembersRouter);
1834
+ // LIVE Session Streaming API - Team Member 2's real-time Code viewer!
1835
+ const liveSessionRouter = createLiveSessionRouter(this.requireAuth.bind(this));
1836
+ this.app.use('/api/live', liveSessionRouter);
1837
+ // File Manager API - FTP-style file browser for codebase management
1838
+ const fileManagerRouter = createFileManagerRouter(() => this.db, this.requireAuth.bind(this));
1839
+ this.app.use('/api/file-manager', fileManagerRouter);
1840
+ // Settings API - Password management and dashboard configuration
1841
+ const settingsRouter = createSettingsRouter(this.requireAuth.bind(this));
1842
+ this.app.use('/api/settings', settingsRouter);
1843
+ // Setup API - Dashboard mode switching and initial setup wizard
1844
+ // Note: Some endpoints are public (status), some require auth (mode switch to public)
1845
+ const setupRouter = createSetupRouter(this.requireAuth.bind(this));
1846
+ this.app.use('/api/setup', setupRouter);
1847
+ // Data Export API - Export PostgreSQL tables to JSON
1848
+ const dataExportRouter = createDataExportRouter(this.requireAuth.bind(this), this.db);
1849
+ this.app.use('/api/admin/export', dataExportRouter);
1850
+ // Hot Reload API - Dashboard control for hot reload system
1851
+ const hotReloadRouter = createHotReloadRouter(this.requireAuth.bind(this));
1852
+ this.app.use('/api/reload', hotReloadRouter);
1853
+ // Hooks Management API - User-manageable custom hooks
1854
+ this.app.use('/api/hooks', this.requireAuth.bind(this), hooksRouter);
1855
+ // Also alias the thinking stream to /api/stream/thinking for backwards compat
1856
+ this.app.get('/api/stream/thinking', this.requireAuth.bind(this), (req, res) => {
1857
+ // Redirect to the live session thinking endpoint
1858
+ res.redirect('/api/live/thinking');
1859
+ });
1860
+ // Serve prompt console page
1861
+ this.app.get('/prompt', (req, res) => {
1862
+ res.sendFile(path.join(__dirname, 'public', 'prompt-console.html'));
1863
+ });
1864
+ // Serve terminal output page (legacy)
1865
+ this.app.get('/terminal-output', (req, res) => {
1866
+ res.sendFile(path.join(__dirname, 'public', 'terminal-output.html'));
1867
+ });
1868
+ // Serve new terminal emulator page with full ANSI support
1869
+ this.app.get('/terminal', (req, res) => {
1870
+ res.sendFile(path.join(__dirname, 'public', 'terminal.html'));
1871
+ });
1872
+ // Serve data export page
1873
+ this.app.get('/data-export', (req, res) => {
1874
+ res.sendFile(path.join(__dirname, 'public', 'data-export.html'));
1875
+ });
1876
+ // SPA catch-all route: serve React app for all non-API routes (React Router handles routing)
1877
+ this.app.get('*', (req, res) => {
1878
+ // Don't catch API routes, WebSocket, or specific pages
1879
+ if (req.path.startsWith('/api/') || req.path.startsWith('/ws/') ||
1880
+ req.path === '/prompt' || req.path === '/terminal' ||
1881
+ req.path === '/terminal-output' || req.path === '/health' ||
1882
+ req.path === '/data-export') {
1883
+ return res.status(404).json({ error: 'Not found' });
1884
+ }
1885
+ res.sendFile(path.join(__dirname, 'public', 'react-dist', 'index.html'));
1886
+ });
1887
+ logger.info('Phase 4-6 routes initialized: /api/prompt, /api/terminal, /api/claude');
1888
+ }
1889
+ /**
1890
+ * Setup WebSocket for real-time updates
1891
+ */
1892
+ setupWebSocket() {
1893
+ this.wss.on('connection', (ws, req) => {
1894
+ // Check if this is a team member-specific WebSocket connection
1895
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
1896
+ const isTeamMemberWs = url.pathname === '/ws/team-members';
1897
+ const isTerminalWs = url.pathname === '/ws/terminal';
1898
+ logger.info({
1899
+ pathname: url.pathname,
1900
+ isTerminalWs,
1901
+ isTeamMemberWs,
1902
+ readyState: ws.readyState,
1903
+ readyStateLabel: ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][ws.readyState] || 'UNKNOWN',
1904
+ remoteAddress: req.socket?.remoteAddress,
1905
+ headers: {
1906
+ host: req.headers.host,
1907
+ origin: req.headers.origin,
1908
+ upgrade: req.headers.upgrade
1909
+ }
1910
+ }, '[WEBSERVER-WS-DEBUG] WebSocket connection established');
1911
+ if (isTeamMemberWs) {
1912
+ logger.info('[WEBSERVER-WS-DEBUG] Routing to setupTeamMemberWebSocket');
1913
+ this.setupTeamMemberWebSocket(ws);
1914
+ return;
1915
+ }
1916
+ // Phase 5: Terminal WebSocket
1917
+ if (isTerminalWs) {
1918
+ logger.info('[WEBSERVER-WS-DEBUG] Routing to setupTerminalWebSocket');
1919
+ this.setupTerminalWebSocket(ws);
1920
+ return;
1921
+ }
1922
+ logger.info('Dashboard WebSocket client connected');
1923
+ this.connectedClients.add(ws);
1924
+ ws.on('close', () => {
1925
+ logger.info('Dashboard WebSocket client disconnected');
1926
+ this.connectedClients.delete(ws);
1927
+ });
1928
+ ws.on('error', (error) => {
1929
+ logger.error({ error }, 'Dashboard WebSocket error');
1930
+ this.connectedClients.delete(ws);
1931
+ });
1932
+ // Send initial stats AFTER a delay to let mobile proxies fully establish the connection
1933
+ // Mobile carriers often have transparent proxies that kill WebSocket connections if data is sent too quickly
1934
+ setTimeout(() => {
1935
+ if (ws.readyState === WebSocket.OPEN) {
1936
+ this.getStats().then(stats => {
1937
+ ws.send(JSON.stringify({ type: 'stats', data: stats }));
1938
+ }).catch(err => {
1939
+ logger.error({ err }, 'Error sending initial stats');
1940
+ });
1941
+ }
1942
+ }, 1000);
1943
+ });
1944
+ }
1945
+ /**
1946
+ * TeamMember-specific WebSocket clients for live message streaming
1947
+ */
1948
+ teamMemberWsClients = new Set();
1949
+ teamMemberMessageSubscriptions = new Map();
1950
+ setupTeamMemberEventForwarding() {
1951
+ if (!this.teamMemberTracker)
1952
+ return;
1953
+ const events = ['teamMember:registered', 'teamMember:status', 'teamMember:log', 'teamMember:tokens', 'teamMember:task'];
1954
+ for (const event of events) {
1955
+ this.teamMemberTracker.on(event, (data) => {
1956
+ this.broadcastUpdate(event.replace(':', '_'), data);
1957
+ for (const client of this.connectedClients) {
1958
+ if (client.readyState === WebSocket.OPEN) {
1959
+ client.send(JSON.stringify({ type: event, ...data }));
1960
+ }
1961
+ }
1962
+ });
1963
+ }
1964
+ const collabEvents = ['code:shared', 'feedback:given', 'message:sent'];
1965
+ for (const event of collabEvents) {
1966
+ this.teamMemberTracker.on(event, (data) => {
1967
+ this.broadcastUpdate(event.replace(':', '_'), data);
1968
+ for (const client of this.connectedClients) {
1969
+ if (client.readyState === WebSocket.OPEN) {
1970
+ client.send(JSON.stringify({ type: event, data }));
1971
+ }
1972
+ }
1973
+ });
1974
+ }
1975
+ // Forward limit warnings from TeamMemberDeployment
1976
+ if (this.teamMemberDeployment) {
1977
+ this.teamMemberDeployment.on('teamMember:limit_warning', (data) => {
1978
+ this.broadcastUpdate('teamMember_limit_warning', data);
1979
+ for (const client of this.connectedClients) {
1980
+ if (client.readyState === WebSocket.OPEN) {
1981
+ client.send(JSON.stringify({ type: 'teamMember:limit_warning', teamMemberId: data.teamMemberId, warning: data.warning }));
1982
+ }
1983
+ }
1984
+ });
1985
+ this.teamMemberDeployment.on('teamMember:limit_acknowledged', (data) => {
1986
+ this.broadcastUpdate('teamMember_limit_acknowledged', data);
1987
+ for (const client of this.connectedClients) {
1988
+ if (client.readyState === WebSocket.OPEN) {
1989
+ client.send(JSON.stringify({ type: 'teamMember:limit_acknowledged', teamMemberId: data.teamMemberId, limitType: data.type, action: data.action }));
1990
+ }
1991
+ }
1992
+ });
1993
+ // Forward team member responses to connected clients
1994
+ this.teamMemberDeployment.on('teamMember:response', (data) => {
1995
+ for (const client of this.connectedClients) {
1996
+ if (client.readyState === WebSocket.OPEN) {
1997
+ client.send(JSON.stringify({ type: 'teamMember:response', teamMemberId: data.teamMemberId, response: data.response }));
1998
+ }
1999
+ }
2000
+ });
2001
+ // Forward team member command events
2002
+ this.teamMemberDeployment.on('teamMember:command_sent', (data) => {
2003
+ for (const client of this.connectedClients) {
2004
+ if (client.readyState === WebSocket.OPEN) {
2005
+ client.send(JSON.stringify({ type: 'teamMember:command', teamMemberId: data.teamMemberId, command: data.command }));
2006
+ }
2007
+ }
2008
+ });
2009
+ }
2010
+ }
2011
+ /**
2012
+ * Setup WebSocket connection for team member message streaming
2013
+ */
2014
+ setupTeamMemberWebSocket(ws) {
2015
+ logger.info('Team Member WebSocket client connected');
2016
+ this.teamMemberWsClients.add(ws);
2017
+ this.teamMemberMessageSubscriptions.set(ws, new Set());
2018
+ ws.on('message', (data) => {
2019
+ try {
2020
+ const message = JSON.parse(data.toString());
2021
+ this.handleTeamMemberWsMessage(ws, message);
2022
+ }
2023
+ catch (error) {
2024
+ logger.error({ error }, 'Error parsing team member WebSocket message');
2025
+ ws.send(JSON.stringify({ type: 'error', message: 'Invalid JSON message' }));
2026
+ }
2027
+ });
2028
+ ws.on('close', () => {
2029
+ logger.info('Team Member WebSocket client disconnected');
2030
+ this.teamMemberWsClients.delete(ws);
2031
+ this.teamMemberMessageSubscriptions.delete(ws);
2032
+ });
2033
+ ws.on('error', (error) => {
2034
+ logger.error({ error }, 'Team Member WebSocket error');
2035
+ this.teamMemberWsClients.delete(ws);
2036
+ this.teamMemberMessageSubscriptions.delete(ws);
2037
+ });
2038
+ // Send initial active team members list AFTER a delay to let mobile proxies fully establish the connection
2039
+ // Mobile carriers often have transparent proxies that kill WebSocket connections if data is sent too quickly
2040
+ setTimeout(() => {
2041
+ if (ws.readyState === WebSocket.OPEN) {
2042
+ this.getActiveTeamMemberSessions().then(teamMembers => {
2043
+ ws.send(JSON.stringify({ type: 'active_teamMembers', data: teamMembers }));
2044
+ }).catch(err => {
2045
+ logger.error({ err }, 'Error sending initial team members list');
2046
+ });
2047
+ }
2048
+ }, 1000);
2049
+ }
2050
+ /**
2051
+ * Setup WebSocket connection for terminal output streaming (Phase 5)
2052
+ * Uses PTY streaming with full ANSI support for colors, formatting, etc.
2053
+ */
2054
+ setupTerminalWebSocket(ws) {
2055
+ logger.info({
2056
+ readyState: ws.readyState,
2057
+ readyStateLabel: ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][ws.readyState] || 'UNKNOWN'
2058
+ }, '[WEBSERVER-WS-DEBUG] setupTerminalWebSocket called');
2059
+ try {
2060
+ // Use the new PTY streaming system with full ANSI support
2061
+ logger.info('[WEBSERVER-WS-DEBUG] About to call handleTerminalWebSocket...');
2062
+ handleTerminalWebSocket(ws, {});
2063
+ logger.info('[WEBSERVER-WS-DEBUG] handleTerminalWebSocket returned successfully');
2064
+ }
2065
+ catch (error) {
2066
+ logger.error({
2067
+ error,
2068
+ stack: error?.stack,
2069
+ message: error?.message
2070
+ }, '[WEBSERVER-WS-DEBUG] Error in handleTerminalWebSocket call');
2071
+ // Try to send error to client
2072
+ try {
2073
+ if (ws.readyState === WebSocket.OPEN) {
2074
+ ws.send(JSON.stringify({
2075
+ type: 'error',
2076
+ message: 'Server error initializing terminal stream'
2077
+ }));
2078
+ ws.close(1011, 'Internal server error');
2079
+ }
2080
+ }
2081
+ catch (sendError) {
2082
+ logger.error({ error: sendError }, '[WEBSERVER-WS-DEBUG] Failed to send error or close ws');
2083
+ }
2084
+ }
2085
+ }
2086
+ /**
2087
+ * Handle incoming WebSocket messages for team member streaming
2088
+ */
2089
+ handleTeamMemberWsMessage(ws, message) {
2090
+ switch (message.type) {
2091
+ case 'subscribe':
2092
+ // Subscribe to messages from a specific session
2093
+ if (message.sessionId) {
2094
+ const subs = this.teamMemberMessageSubscriptions.get(ws);
2095
+ if (subs) {
2096
+ subs.add(message.sessionId);
2097
+ ws.send(JSON.stringify({ type: 'subscribed', sessionId: message.sessionId }));
2098
+ logger.debug({ sessionId: message.sessionId }, 'Client subscribed to session');
2099
+ }
2100
+ }
2101
+ break;
2102
+ case 'unsubscribe':
2103
+ // Unsubscribe from a specific session
2104
+ if (message.sessionId) {
2105
+ const subs = this.teamMemberMessageSubscriptions.get(ws);
2106
+ if (subs) {
2107
+ subs.delete(message.sessionId);
2108
+ ws.send(JSON.stringify({ type: 'unsubscribed', sessionId: message.sessionId }));
2109
+ }
2110
+ }
2111
+ break;
2112
+ case 'subscribe_all':
2113
+ // Subscribe to all team member messages
2114
+ const allSubs = this.teamMemberMessageSubscriptions.get(ws);
2115
+ if (allSubs) {
2116
+ allSubs.add('*');
2117
+ ws.send(JSON.stringify({ type: 'subscribed_all' }));
2118
+ }
2119
+ break;
2120
+ case 'get_active':
2121
+ // Request current active team members
2122
+ this.getActiveTeamMemberSessions().then(teamMembers => {
2123
+ ws.send(JSON.stringify({ type: 'active_teamMembers', data: teamMembers }));
2124
+ }).catch(err => {
2125
+ logger.error({ err }, 'Error fetching active team members');
2126
+ });
2127
+ break;
2128
+ case 'get_session_messages':
2129
+ // Request recent messages for a session
2130
+ if (message.sessionId) {
2131
+ this.getTeamMemberSessionMessages(message.sessionId, 50, 0).then(messages => {
2132
+ ws.send(JSON.stringify({
2133
+ type: 'session_messages',
2134
+ sessionId: message.sessionId,
2135
+ data: messages
2136
+ }));
2137
+ }).catch(err => {
2138
+ logger.error({ err }, 'Error fetching session messages');
2139
+ });
2140
+ }
2141
+ break;
2142
+ default:
2143
+ ws.send(JSON.stringify({ type: 'error', message: `Unknown message type: ${message.type}` }));
2144
+ }
2145
+ }
2146
+ /**
2147
+ * Broadcast team member message to subscribed WebSocket clients
2148
+ */
2149
+ broadcastTeamMemberMessage(sessionId, message) {
2150
+ const payload = JSON.stringify({
2151
+ type: 'team_member_message',
2152
+ sessionId,
2153
+ data: message,
2154
+ timestamp: new Date().toISOString()
2155
+ });
2156
+ for (const [ws, subs] of this.teamMemberMessageSubscriptions) {
2157
+ if (ws.readyState === WebSocket.OPEN && (subs.has(sessionId) || subs.has('*'))) {
2158
+ ws.send(payload);
2159
+ }
2160
+ }
2161
+ }
2162
+ /**
2163
+ * Broadcast team member status update to all team member WebSocket clients
2164
+ */
2165
+ broadcastTeamMemberStatusUpdate(sessionId, status, teamMember) {
2166
+ const payload = JSON.stringify({
2167
+ type: 'teamMember_status',
2168
+ sessionId,
2169
+ status,
2170
+ data: teamMember,
2171
+ timestamp: new Date().toISOString()
2172
+ });
2173
+ for (const ws of this.teamMemberWsClients) {
2174
+ if (ws.readyState === WebSocket.OPEN) {
2175
+ ws.send(payload);
2176
+ }
2177
+ }
2178
+ }
2179
+ /**
2180
+ * Broadcast message to all connected WebSocket clients
2181
+ */
2182
+ broadcastUpdate(type, data) {
2183
+ const message = JSON.stringify({ type, data });
2184
+ for (const client of this.connectedClients) {
2185
+ if (client.readyState === WebSocket.OPEN) {
2186
+ client.send(message);
2187
+ }
2188
+ }
2189
+ }
2190
+ /**
2191
+ * Get dashboard statistics
2192
+ */
2193
+ async getStats() {
2194
+ let totalMemories = 0;
2195
+ let totalSessions = 0;
2196
+ let totalFiles = 0;
2197
+ let totalSkills = 0;
2198
+ let activeTeamMembers = 0;
2199
+ try {
2200
+ if (this.db) {
2201
+ // PROJECT ISOLATION: Only count memories from current project
2202
+ const projectPath = getProjectPathForInsert();
2203
+ // Get memory count
2204
+ const memoryResult = await this.db.query('SELECT COUNT(*) as count FROM memories WHERE project_path = $1', [projectPath]);
2205
+ totalMemories = parseInt(memoryResult.rows[0]?.count || '0', 10);
2206
+ // Get session count (from memories with session tags)
2207
+ const sessionResult = await this.db.query("SELECT COUNT(DISTINCT COALESCE(metadata->>'sessionId', metadata->>'session_id')) as count FROM memories WHERE project_path = $1 AND (metadata->>'sessionId' IS NOT NULL OR metadata->>'session_id' IS NOT NULL)", [projectPath]);
2208
+ totalSessions = parseInt(sessionResult.rows[0]?.count || '0', 10);
2209
+ }
2210
+ }
2211
+ catch (error) {
2212
+ logger.debug({ error }, 'Error fetching database stats');
2213
+ }
2214
+ try {
2215
+ if (this.codebaseIndexer) {
2216
+ const stats = this.codebaseIndexer.getStats();
2217
+ totalFiles = stats.totalFiles;
2218
+ }
2219
+ }
2220
+ catch (error) {
2221
+ logger.debug({ error }, 'Error fetching codebase stats');
2222
+ }
2223
+ try {
2224
+ if (this.skillScanner) {
2225
+ const skills = this.skillScanner.getAllSkills();
2226
+ totalSkills = skills.length;
2227
+ }
2228
+ }
2229
+ catch (error) {
2230
+ logger.debug({ error }, 'Error fetching skills stats');
2231
+ }
2232
+ // Try to get team member count from coordination server
2233
+ try {
2234
+ const response = await fetch(`http://localhost:${this.config.coordinationPort}/teamMembers`);
2235
+ if (response.ok) {
2236
+ const data = await response.json();
2237
+ activeTeamMembers = data.teamMembers?.length || 0;
2238
+ }
2239
+ }
2240
+ catch (error) {
2241
+ logger.debug({ error }, 'Error fetching team member stats');
2242
+ }
2243
+ // Get memory manager stats if available
2244
+ let memoryStats;
2245
+ try {
2246
+ if (this.memoryManager) {
2247
+ const memStats = this.memoryManager.getStats();
2248
+ memoryStats = {
2249
+ heapUsedMB: Math.round(memStats.heapUsed / 1024 / 1024 * 100) / 100,
2250
+ heapTotalMB: Math.round(memStats.heapTotal / 1024 / 1024 * 100) / 100,
2251
+ maxHeapMB: Math.round(memStats.maxHeap / 1024 / 1024 * 100) / 100,
2252
+ usagePercent: Math.round(memStats.usagePercent * 10000) / 100,
2253
+ pressureLevel: memStats.pressureLevel,
2254
+ embeddingCacheSize: memStats.embeddingCacheSize,
2255
+ totalEvictions: memStats.totalEvictions,
2256
+ totalOverflowed: memStats.totalOverflowed
2257
+ };
2258
+ }
2259
+ }
2260
+ catch (error) {
2261
+ logger.debug({ error }, 'Error fetching memory manager stats');
2262
+ }
2263
+ return {
2264
+ totalMemories,
2265
+ totalSessions,
2266
+ totalFiles,
2267
+ totalSkills,
2268
+ activeTeamMembers,
2269
+ uptime: this.isRunning ? Date.now() - this.startTime : 0,
2270
+ memory: memoryStats
2271
+ };
2272
+ }
2273
+ /**
2274
+ * Persist password change to environment file
2275
+ */
2276
+ async persistPasswordToEnv(newPassword) {
2277
+ // Try common env file locations
2278
+ const envPaths = [
2279
+ this.envFilePath,
2280
+ path.join(process.cwd(), 'specmem.env'),
2281
+ path.join(process.cwd(), '.env'),
2282
+ path.join(__dirname, '../../specmem.env'),
2283
+ path.join(__dirname, '../../.env')
2284
+ ].filter(Boolean);
2285
+ for (const envPath of envPaths) {
2286
+ try {
2287
+ const content = await fs.readFile(envPath, 'utf-8');
2288
+ // Check if this file has the dashboard password setting
2289
+ if (content.includes('SPECMEM_DASHBOARD_PASSWORD')) {
2290
+ const updatedContent = content.replace(/SPECMEM_DASHBOARD_PASSWORD=.*/, `SPECMEM_DASHBOARD_PASSWORD=${newPassword}`);
2291
+ await fs.writeFile(envPath, updatedContent, 'utf-8');
2292
+ logger.info({ envPath }, 'Password persisted to env file');
2293
+ return true;
2294
+ }
2295
+ }
2296
+ catch (error) {
2297
+ // File doesn't exist or not readable, try next
2298
+ logger.debug({ error, envPath }, 'Could not update env file');
2299
+ }
2300
+ }
2301
+ return false;
2302
+ }
2303
+ /**
2304
+ * Memory configuration state
2305
+ */
2306
+ memoryConfig = {
2307
+ memoryLimit: 100, // MB
2308
+ overflowTime: 24, // hours
2309
+ cacheSize: 500 // entries
2310
+ };
2311
+ /**
2312
+ * Cache statistics tracking
2313
+ */
2314
+ cacheStats = {
2315
+ queryCacheSize: 0,
2316
+ queryCacheHits: 0,
2317
+ queryCacheMisses: 0,
2318
+ embeddingCacheSize: 0,
2319
+ embeddingCacheHits: 0,
2320
+ embeddingCacheMisses: 0,
2321
+ lastOverflow: null
2322
+ };
2323
+ /**
2324
+ * Get memory configuration
2325
+ */
2326
+ async getMemoryConfig() {
2327
+ // Try to load from env file first
2328
+ const envPaths = [
2329
+ this.envFilePath,
2330
+ path.join(process.cwd(), 'specmem.env'),
2331
+ path.join(__dirname, '../../specmem.env')
2332
+ ].filter(Boolean);
2333
+ for (const envPath of envPaths) {
2334
+ try {
2335
+ const content = await fs.readFile(envPath, 'utf-8');
2336
+ const memoryLimitMatch = content.match(/SPECMEM_MEMORY_LIMIT=(\d+)/);
2337
+ const overflowTimeMatch = content.match(/SPECMEM_OVERFLOW_TIME=(\d+)/);
2338
+ const cacheSizeMatch = content.match(/SPECMEM_CACHE_SIZE=(\d+)/);
2339
+ if (memoryLimitMatch) {
2340
+ this.memoryConfig.memoryLimit = parseInt(memoryLimitMatch[1], 10);
2341
+ }
2342
+ if (overflowTimeMatch) {
2343
+ this.memoryConfig.overflowTime = parseInt(overflowTimeMatch[1], 10);
2344
+ }
2345
+ if (cacheSizeMatch) {
2346
+ this.memoryConfig.cacheSize = parseInt(cacheSizeMatch[1], 10);
2347
+ }
2348
+ break;
2349
+ }
2350
+ catch (e) {
2351
+ // Continue to next path - this is expected when file doesn't exist
2352
+ logger.debug({ envPath, error: e }, 'env file not found at this path, trying next');
2353
+ }
2354
+ }
2355
+ return this.memoryConfig;
2356
+ }
2357
+ /**
2358
+ * Persist memory configuration to env file
2359
+ */
2360
+ async persistMemoryConfig(config) {
2361
+ // Update in-memory config
2362
+ if (config.memoryLimit !== undefined)
2363
+ this.memoryConfig.memoryLimit = config.memoryLimit;
2364
+ if (config.overflowTime !== undefined)
2365
+ this.memoryConfig.overflowTime = config.overflowTime;
2366
+ if (config.cacheSize !== undefined)
2367
+ this.memoryConfig.cacheSize = config.cacheSize;
2368
+ const envPaths = [
2369
+ this.envFilePath,
2370
+ path.join(process.cwd(), 'specmem.env'),
2371
+ path.join(__dirname, '../../specmem.env')
2372
+ ].filter(Boolean);
2373
+ for (const envPath of envPaths) {
2374
+ try {
2375
+ let content = await fs.readFile(envPath, 'utf-8');
2376
+ // Update or add memory limit
2377
+ if (content.includes('SPECMEM_MEMORY_LIMIT=')) {
2378
+ content = content.replace(/SPECMEM_MEMORY_LIMIT=\d+/, `SPECMEM_MEMORY_LIMIT=${this.memoryConfig.memoryLimit}`);
2379
+ }
2380
+ else {
2381
+ content += `\nSPECMEM_MEMORY_LIMIT=${this.memoryConfig.memoryLimit}`;
2382
+ }
2383
+ // Update or add overflow time
2384
+ if (content.includes('SPECMEM_OVERFLOW_TIME=')) {
2385
+ content = content.replace(/SPECMEM_OVERFLOW_TIME=\d+/, `SPECMEM_OVERFLOW_TIME=${this.memoryConfig.overflowTime}`);
2386
+ }
2387
+ else {
2388
+ content += `\nSPECMEM_OVERFLOW_TIME=${this.memoryConfig.overflowTime}`;
2389
+ }
2390
+ // Update or add cache size
2391
+ if (content.includes('SPECMEM_CACHE_SIZE=')) {
2392
+ content = content.replace(/SPECMEM_CACHE_SIZE=\d+/, `SPECMEM_CACHE_SIZE=${this.memoryConfig.cacheSize}`);
2393
+ }
2394
+ else {
2395
+ content += `\nSPECMEM_CACHE_SIZE=${this.memoryConfig.cacheSize}`;
2396
+ }
2397
+ await fs.writeFile(envPath, content, 'utf-8');
2398
+ logger.info({ envPath, config: this.memoryConfig }, 'Memory configuration persisted');
2399
+ return true;
2400
+ }
2401
+ catch (e) {
2402
+ // File not found or not readable, try next path
2403
+ logger.debug({ envPath, error: e }, 'couldnt save to env file, trying next');
2404
+ continue;
2405
+ }
2406
+ }
2407
+ return false;
2408
+ }
2409
+ /**
2410
+ * Get memory statistics for the dashboard
2411
+ * Integrates with the MemoryManager for real heap stats
2412
+ */
2413
+ async getMemoryStats() {
2414
+ let totalMemories = 0;
2415
+ let estimatedUsageMB = 0;
2416
+ try {
2417
+ if (this.db) {
2418
+ // PROJECT ISOLATION: Only count memories from current project
2419
+ const projectPath = getProjectPathForInsert();
2420
+ // Get memory count
2421
+ const countResult = await this.db.query('SELECT COUNT(*) as count FROM memories WHERE project_path = $1', [projectPath]);
2422
+ totalMemories = parseInt(countResult.rows[0]?.count || '0', 10);
2423
+ // Estimate memory usage based on content size
2424
+ const sizeResult = await this.db.query(`
2425
+ SELECT COALESCE(SUM(LENGTH(content)), 0) as total_size
2426
+ FROM memories WHERE project_path = $1
2427
+ `, [projectPath]);
2428
+ const totalBytes = parseInt(sizeResult.rows[0]?.total_size || '0', 10);
2429
+ estimatedUsageMB = Math.round((totalBytes / 1024 / 1024) * 100) / 100;
2430
+ }
2431
+ }
2432
+ catch (error) {
2433
+ logger.debug({ error }, 'Error calculating memory stats');
2434
+ }
2435
+ // Get real heap stats from memory manager
2436
+ let heapStats = {
2437
+ usedMB: 0,
2438
+ totalMB: 0,
2439
+ maxMB: 100,
2440
+ usagePercent: 0,
2441
+ pressureLevel: 'normal',
2442
+ rssMB: 0,
2443
+ externalMB: 0
2444
+ };
2445
+ let overflowStats = {
2446
+ totalEvictions: 0,
2447
+ totalOverflowed: 0,
2448
+ lastGC: null
2449
+ };
2450
+ let embeddingCacheSize = this.cacheStats.embeddingCacheSize;
2451
+ try {
2452
+ if (this.memoryManager) {
2453
+ const memStats = this.memoryManager.getStats();
2454
+ heapStats = {
2455
+ usedMB: Math.round(memStats.heapUsed / 1024 / 1024 * 100) / 100,
2456
+ totalMB: Math.round(memStats.heapTotal / 1024 / 1024 * 100) / 100,
2457
+ maxMB: Math.round(memStats.maxHeap / 1024 / 1024 * 100) / 100,
2458
+ usagePercent: Math.round(memStats.usagePercent * 10000) / 100,
2459
+ pressureLevel: memStats.pressureLevel,
2460
+ rssMB: Math.round(memStats.rss / 1024 / 1024 * 100) / 100,
2461
+ externalMB: Math.round(memStats.external / 1024 / 1024 * 100) / 100
2462
+ };
2463
+ overflowStats = {
2464
+ totalEvictions: memStats.totalEvictions,
2465
+ totalOverflowed: memStats.totalOverflowed,
2466
+ lastGC: memStats.lastGC?.toISOString() || null
2467
+ };
2468
+ embeddingCacheSize = memStats.embeddingCacheSize;
2469
+ }
2470
+ else {
2471
+ // Fallback to process.memoryUsage if no memory manager
2472
+ const mem = process.memoryUsage();
2473
+ heapStats = {
2474
+ usedMB: Math.round(mem.heapUsed / 1024 / 1024 * 100) / 100,
2475
+ totalMB: Math.round(mem.heapTotal / 1024 / 1024 * 100) / 100,
2476
+ maxMB: 100, // Default limit
2477
+ usagePercent: Math.round((mem.heapUsed / (100 * 1024 * 1024)) * 10000) / 100,
2478
+ pressureLevel: 'unknown',
2479
+ rssMB: Math.round(mem.rss / 1024 / 1024 * 100) / 100,
2480
+ externalMB: Math.round(mem.external / 1024 / 1024 * 100) / 100
2481
+ };
2482
+ }
2483
+ }
2484
+ catch (error) {
2485
+ logger.debug({ error }, 'Error getting heap stats');
2486
+ }
2487
+ // Calculate cache hit rate
2488
+ const totalHits = this.cacheStats.queryCacheHits + this.cacheStats.embeddingCacheHits;
2489
+ const totalMisses = this.cacheStats.queryCacheMisses + this.cacheStats.embeddingCacheMisses;
2490
+ const cacheHitRate = totalHits + totalMisses > 0
2491
+ ? Math.round((totalHits / (totalHits + totalMisses)) * 100)
2492
+ : 0;
2493
+ // Determine trend based on pressure level
2494
+ let trend = 'STABLE';
2495
+ if (heapStats.pressureLevel === 'emergency') {
2496
+ trend = 'CRITICAL';
2497
+ }
2498
+ else if (heapStats.pressureLevel === 'critical') {
2499
+ trend = 'UP';
2500
+ }
2501
+ else if (heapStats.pressureLevel === 'warning') {
2502
+ trend = 'RISING';
2503
+ }
2504
+ return {
2505
+ currentUsage: estimatedUsageMB,
2506
+ totalMemories,
2507
+ cacheHitRate,
2508
+ lastOverflow: this.cacheStats.lastOverflow?.toISOString() || overflowStats.lastGC,
2509
+ peakUsage: heapStats.usedMB, // Real peak from actual usage
2510
+ avgUsage: Math.round(heapStats.usedMB * 0.8 * 100) / 100,
2511
+ trend,
2512
+ queryCacheSize: this.cacheStats.queryCacheSize,
2513
+ embeddingCacheSize: embeddingCacheSize,
2514
+ heap: heapStats,
2515
+ overflow: overflowStats
2516
+ };
2517
+ }
2518
+ /**
2519
+ * Trigger overflow cleanup based on configuration
2520
+ * PROJECT ISOLATED: Only cleans up current project's memories
2521
+ */
2522
+ async triggerOverflowCleanup() {
2523
+ if (!this.db) {
2524
+ return { deleted: 0, message: 'Database not available' };
2525
+ }
2526
+ try {
2527
+ const cutoffTime = new Date();
2528
+ cutoffTime.setHours(cutoffTime.getHours() - this.memoryConfig.overflowTime);
2529
+ const projectPath = getProjectPathForInsert();
2530
+ // Delete memories older than the overflow time that haven't been accessed recently
2531
+ // PROJECT ISOLATED: Only delete from current project
2532
+ const result = await this.db.query(`
2533
+ DELETE FROM memories
2534
+ WHERE updated_at < $1
2535
+ AND importance NOT IN ('critical', 'high')
2536
+ AND access_count < 5
2537
+ AND project_path = $2
2538
+ RETURNING id
2539
+ `, [cutoffTime.toISOString(), projectPath]);
2540
+ const deletedCount = result.rowCount ?? 0;
2541
+ this.cacheStats.lastOverflow = new Date();
2542
+ logger.info({ deletedCount, cutoffTime, projectPath }, 'Overflow cleanup completed');
2543
+ return {
2544
+ deleted: deletedCount,
2545
+ message: `Cleaned up ${deletedCount} memories older than ${this.memoryConfig.overflowTime} hours`
2546
+ };
2547
+ }
2548
+ catch (error) {
2549
+ logger.error({ error }, 'Overflow cleanup failed');
2550
+ throw error;
2551
+ }
2552
+ }
2553
+ /**
2554
+ * Emergency memory purge - deletes ALL memories
2555
+ * PROJECT ISOLATED: Only purges current project's memories
2556
+ */
2557
+ async emergencyMemoryPurge() {
2558
+ if (!this.db) {
2559
+ return { deleted: 0, message: 'Database not available' };
2560
+ }
2561
+ try {
2562
+ const projectPath = getProjectPathForInsert();
2563
+ // Get count before deletion - only for current project
2564
+ const countResult = await this.db.query('SELECT COUNT(*) as count FROM memories WHERE project_path = $1', [projectPath]);
2565
+ const totalBefore = parseInt(countResult.rows[0]?.count || '0', 10);
2566
+ // Delete all memories for current project only
2567
+ await this.db.query('DELETE FROM memories WHERE project_path = $1', [projectPath]);
2568
+ // Reset cache stats
2569
+ this.cacheStats.queryCacheSize = 0;
2570
+ this.cacheStats.embeddingCacheSize = 0;
2571
+ this.cacheStats.lastOverflow = new Date();
2572
+ logger.warn({ deletedCount: totalBefore, projectPath }, 'Emergency memory purge executed for project');
2573
+ return {
2574
+ deleted: totalBefore,
2575
+ message: `Emergency purge complete. ${totalBefore} memories permanently deleted from current project.`
2576
+ };
2577
+ }
2578
+ catch (error) {
2579
+ logger.error({ error }, 'Emergency purge failed');
2580
+ throw error;
2581
+ }
2582
+ }
2583
+ /**
2584
+ * Clear specific cache type
2585
+ */
2586
+ async clearCache(type) {
2587
+ try {
2588
+ if (type === 'query') {
2589
+ this.cacheStats.queryCacheSize = 0;
2590
+ this.cacheStats.queryCacheHits = 0;
2591
+ this.cacheStats.queryCacheMisses = 0;
2592
+ logger.info('Query cache cleared');
2593
+ return { cleared: true, message: 'Query cache yeeted, clean slate fr' };
2594
+ }
2595
+ else if (type === 'embedding') {
2596
+ this.cacheStats.embeddingCacheSize = 0;
2597
+ this.cacheStats.embeddingCacheHits = 0;
2598
+ this.cacheStats.embeddingCacheMisses = 0;
2599
+ logger.info('Embedding cache cleared');
2600
+ return { cleared: true, message: 'Embedding cache wiped clean, let\'s go' };
2601
+ }
2602
+ return { cleared: false, message: 'Unknown cache type' };
2603
+ }
2604
+ catch (error) {
2605
+ logger.error({ error, type }, 'Failed to clear cache');
2606
+ throw error;
2607
+ }
2608
+ }
2609
+ getFileExtension(language) {
2610
+ const extMap = {
2611
+ typescript: 'ts', javascript: 'js', python: 'py', rust: 'rs',
2612
+ go: 'go', java: 'java', cpp: 'cpp', c: 'c', ruby: 'rb',
2613
+ php: 'php', sql: 'sql', bash: 'sh', yaml: 'yaml', json: 'json',
2614
+ markdown: 'md', html: 'html', css: 'css', text: 'txt'
2615
+ };
2616
+ return extMap[language] || 'txt';
2617
+ }
2618
+ /**
2619
+ * Get memories with optional search (supports text and semantic search)
2620
+ */
2621
+ async getMemories(search, limit = 50, offset = 0) {
2622
+ if (!this.db) {
2623
+ return { memories: [], total: 0 };
2624
+ }
2625
+ // PROJECT ISOLATION: Filter by project_path
2626
+ const projectPath = getProjectPathForInsert();
2627
+ // If no search, return paginated results
2628
+ if (!search) {
2629
+ const query = `
2630
+ SELECT id, content, tags, metadata, importance, memory_type, created_at, updated_at, access_count
2631
+ FROM memories
2632
+ WHERE project_path = $1
2633
+ ORDER BY created_at DESC
2634
+ LIMIT $2 OFFSET $3
2635
+ `;
2636
+ const countQuery = `SELECT COUNT(*) as count FROM memories WHERE project_path = $1`;
2637
+ const [memoriesResult, countResult] = await Promise.all([
2638
+ this.db.query(query, [projectPath, limit, offset]),
2639
+ this.db.query(countQuery, [projectPath])
2640
+ ]);
2641
+ return {
2642
+ memories: memoriesResult.rows,
2643
+ total: parseInt(countResult.rows[0]?.count || '0', 10)
2644
+ };
2645
+ }
2646
+ // Try semantic search first if embedding provider is available
2647
+ if (this.embeddingProvider) {
2648
+ try {
2649
+ const embedding = await this.embeddingProvider.generateEmbedding(search);
2650
+ const embeddingStr = `[${embedding.join(',')}]`;
2651
+ // Hybrid search: combine vector similarity with text matching
2652
+ // PROJECT ISOLATION: Filter by project_path
2653
+ const query = `
2654
+ WITH vector_matches AS (
2655
+ SELECT
2656
+ id, content, tags, metadata, importance, memory_type,
2657
+ created_at, updated_at, access_count,
2658
+ 1 - (embedding <=> $1::vector) AS similarity
2659
+ FROM memories
2660
+ WHERE project_path = $6
2661
+ AND embedding IS NOT NULL
2662
+ AND (embedding <=> $1::vector) < 0.5
2663
+ ORDER BY embedding <=> $1::vector
2664
+ LIMIT $2
2665
+ ),
2666
+ text_matches AS (
2667
+ SELECT
2668
+ id, content, tags, metadata, importance, memory_type,
2669
+ created_at, updated_at, access_count,
2670
+ ts_rank(content_tsv, plainto_tsquery('english', $3)) AS similarity
2671
+ FROM memories
2672
+ WHERE project_path = $6
2673
+ AND (content_tsv @@ plainto_tsquery('english', $3)
2674
+ OR content ILIKE $4
2675
+ OR $3 = ANY(tags))
2676
+ ORDER BY similarity DESC
2677
+ LIMIT $2
2678
+ ),
2679
+ combined AS (
2680
+ SELECT * FROM vector_matches
2681
+ UNION
2682
+ SELECT * FROM text_matches
2683
+ )
2684
+ SELECT DISTINCT ON (id) *
2685
+ FROM combined
2686
+ ORDER BY id, similarity DESC
2687
+ LIMIT $2 OFFSET $5
2688
+ `;
2689
+ const countQuery = `
2690
+ SELECT COUNT(DISTINCT id) as count FROM (
2691
+ SELECT id FROM memories
2692
+ WHERE project_path = $4 AND embedding IS NOT NULL AND (embedding <=> $1::vector) < 0.5
2693
+ UNION
2694
+ SELECT id FROM memories
2695
+ WHERE project_path = $4
2696
+ AND (content_tsv @@ plainto_tsquery('english', $2)
2697
+ OR content ILIKE $3
2698
+ OR $2 = ANY(tags))
2699
+ ) combined
2700
+ `;
2701
+ const [memoriesResult, countResult] = await Promise.all([
2702
+ this.db.query(query, [embeddingStr, limit, search, `%${search}%`, offset, projectPath]),
2703
+ this.db.query(countQuery, [embeddingStr, search, `%${search}%`, projectPath])
2704
+ ]);
2705
+ return {
2706
+ memories: memoriesResult.rows,
2707
+ total: parseInt(countResult.rows[0]?.count || '0', 10),
2708
+ searchType: 'hybrid'
2709
+ };
2710
+ }
2711
+ catch (error) {
2712
+ logger.warn({ error }, 'Semantic search failed, falling back to text search');
2713
+ }
2714
+ }
2715
+ // Fallback to text search with full-text search support
2716
+ // PROJECT ISOLATION: Filter by project_path
2717
+ const query = `
2718
+ SELECT id, content, tags, metadata, importance, memory_type, created_at, updated_at, access_count,
2719
+ CASE
2720
+ WHEN content_tsv @@ plainto_tsquery('english', $1) THEN ts_rank(content_tsv, plainto_tsquery('english', $1))
2721
+ ELSE 0
2722
+ END AS rank
2723
+ FROM memories
2724
+ WHERE project_path = $5
2725
+ AND (content_tsv @@ plainto_tsquery('english', $1)
2726
+ OR content ILIKE $2
2727
+ OR $1 = ANY(tags))
2728
+ ORDER BY rank DESC, created_at DESC
2729
+ LIMIT $3 OFFSET $4
2730
+ `;
2731
+ const countQuery = `
2732
+ SELECT COUNT(*) as count FROM memories
2733
+ WHERE project_path = $3
2734
+ AND (content_tsv @@ plainto_tsquery('english', $1)
2735
+ OR content ILIKE $2
2736
+ OR $1 = ANY(tags))
2737
+ `;
2738
+ const [memoriesResult, countResult] = await Promise.all([
2739
+ this.db.query(query, [search, `%${search}%`, limit, offset, projectPath]),
2740
+ this.db.query(countQuery, [search, `%${search}%`, projectPath])
2741
+ ]);
2742
+ return {
2743
+ memories: memoriesResult.rows,
2744
+ total: parseInt(countResult.rows[0]?.count || '0', 10),
2745
+ searchType: 'text'
2746
+ };
2747
+ }
2748
+ /**
2749
+ * Get memories for Camera Roll mode with zoom-based similarity threshold
2750
+ * Returns results with similarity scores for drilldown functionality
2751
+ */
2752
+ async getCameraRollMemories(search, similarityThreshold, limit, offset = 0) {
2753
+ if (!this.db) {
2754
+ return { memories: [], total: 0, searchType: 'none' };
2755
+ }
2756
+ // PROJECT ISOLATION: Filter by project_path
2757
+ const projectPath = getProjectPathForInsert();
2758
+ // If no search query, return recent memories with default similarity
2759
+ if (!search) {
2760
+ const query = `
2761
+ SELECT id, content, tags, metadata, importance, memory_type,
2762
+ created_at, updated_at, access_count,
2763
+ 0.5 as similarity
2764
+ FROM memories
2765
+ WHERE project_path = $1
2766
+ ORDER BY created_at DESC
2767
+ LIMIT $2 OFFSET $3
2768
+ `;
2769
+ const countQuery = `SELECT COUNT(*) as count FROM memories WHERE project_path = $1`;
2770
+ const [memoriesResult, countResult] = await Promise.all([
2771
+ this.db.query(query, [projectPath, limit, offset]),
2772
+ this.db.query(countQuery, [projectPath])
2773
+ ]);
2774
+ return {
2775
+ memories: memoriesResult.rows,
2776
+ total: parseInt(countResult.rows[0]?.count || '0', 10),
2777
+ searchType: 'recent'
2778
+ };
2779
+ }
2780
+ // Try semantic search with zoom-appropriate threshold
2781
+ if (this.embeddingProvider) {
2782
+ try {
2783
+ const embedding = await this.embeddingProvider.generateEmbedding(search);
2784
+ const embeddingStr = `[${embedding.join(',')}]`;
2785
+ // Calculate distance threshold from similarity threshold
2786
+ // similarity = 1 - distance, so distance = 1 - similarity
2787
+ const distanceThreshold = 1 - similarityThreshold;
2788
+ // Vector search with zoom-based threshold
2789
+ // PROJECT ISOLATION: Filter by project_path
2790
+ const query = `
2791
+ SELECT
2792
+ id, content, tags, metadata, importance, memory_type,
2793
+ created_at, updated_at, access_count,
2794
+ 1 - (embedding <=> $1::vector) AS similarity
2795
+ FROM memories
2796
+ WHERE project_path = $5
2797
+ AND embedding IS NOT NULL
2798
+ AND (embedding <=> $1::vector) < $2
2799
+ ORDER BY embedding <=> $1::vector
2800
+ LIMIT $3 OFFSET $4
2801
+ `;
2802
+ const countQuery = `
2803
+ SELECT COUNT(*) as count
2804
+ FROM memories
2805
+ WHERE project_path = $3
2806
+ AND embedding IS NOT NULL
2807
+ AND (embedding <=> $1::vector) < $2
2808
+ `;
2809
+ const [memoriesResult, countResult] = await Promise.all([
2810
+ this.db.query(query, [embeddingStr, distanceThreshold, limit, offset, projectPath]),
2811
+ this.db.query(countQuery, [embeddingStr, distanceThreshold, projectPath])
2812
+ ]);
2813
+ return {
2814
+ memories: memoriesResult.rows,
2815
+ total: parseInt(countResult.rows[0]?.count || '0', 10),
2816
+ searchType: 'hybrid'
2817
+ };
2818
+ }
2819
+ catch (error) {
2820
+ logger.warn({ error }, 'Camera roll semantic search failed, falling back to text search');
2821
+ }
2822
+ }
2823
+ // Fallback to text search
2824
+ const query = `
2825
+ SELECT id, content, tags, metadata, importance, memory_type,
2826
+ created_at, updated_at, access_count,
2827
+ CASE
2828
+ WHEN content_tsv @@ plainto_tsquery('english', $1)
2829
+ THEN ts_rank(content_tsv, plainto_tsquery('english', $1))
2830
+ ELSE 0.3
2831
+ END AS similarity
2832
+ FROM memories
2833
+ WHERE content_tsv @@ plainto_tsquery('english', $1)
2834
+ OR content ILIKE $2
2835
+ OR $1 = ANY(tags)
2836
+ ORDER BY similarity DESC, created_at DESC
2837
+ LIMIT $3 OFFSET $4
2838
+ `;
2839
+ const countQuery = `
2840
+ SELECT COUNT(*) as count FROM memories
2841
+ WHERE content_tsv @@ plainto_tsquery('english', $1)
2842
+ OR content ILIKE $2
2843
+ OR $1 = ANY(tags)
2844
+ `;
2845
+ const [memoriesResult, countResult] = await Promise.all([
2846
+ this.db.query(query, [search, `%${search}%`, limit, offset]),
2847
+ this.db.query(countQuery, [search, `%${search}%`])
2848
+ ]);
2849
+ return {
2850
+ memories: memoriesResult.rows,
2851
+ total: parseInt(countResult.rows[0]?.count || '0', 10),
2852
+ searchType: 'text'
2853
+ };
2854
+ }
2855
+ /**
2856
+ * Get a single memory by ID with full details
2857
+ */
2858
+ async getMemoryById(id) {
2859
+ if (!this.db) {
2860
+ return null;
2861
+ }
2862
+ const result = await this.db.query(`
2863
+ SELECT
2864
+ id,
2865
+ content,
2866
+ tags,
2867
+ metadata,
2868
+ importance,
2869
+ memory_type,
2870
+ created_at,
2871
+ updated_at,
2872
+ access_count,
2873
+ expires_at,
2874
+ embedding[1:5] as embedding_preview
2875
+ FROM memories
2876
+ WHERE id = $1
2877
+ `, [id]);
2878
+ if (result.rowCount === 0) {
2879
+ return null;
2880
+ }
2881
+ const row = result.rows[0];
2882
+ // Update access count for this memory
2883
+ await this.db.query(`
2884
+ UPDATE memories
2885
+ SET access_count = access_count + 1, updated_at = NOW()
2886
+ WHERE id = $1
2887
+ `, [id]);
2888
+ return {
2889
+ id: row.id,
2890
+ content: row.content,
2891
+ tags: row.tags || [],
2892
+ metadata: row.metadata || {},
2893
+ importance: row.importance,
2894
+ memory_type: row.memory_type,
2895
+ created_at: row.created_at,
2896
+ updated_at: row.updated_at,
2897
+ access_count: row.access_count + 1,
2898
+ expires_at: row.expires_at,
2899
+ embedding_preview: row.embedding_preview
2900
+ };
2901
+ }
2902
+ /**
2903
+ * Delete a memory by ID
2904
+ * PROJECT ISOLATED: Only deletes from current project
2905
+ */
2906
+ async deleteMemory(id) {
2907
+ if (!this.db) {
2908
+ throw new Error('Database not available');
2909
+ }
2910
+ const projectPath = getProjectPathForInsert();
2911
+ await this.db.query('DELETE FROM memories WHERE id = $1 AND project_path = $2', [id, projectPath]);
2912
+ this.broadcastUpdate('memory_deleted', { id });
2913
+ }
2914
+ /**
2915
+ * Bulk delete memories based on criteria
2916
+ * PROJECT ISOLATED: Only deletes from current project
2917
+ */
2918
+ async bulkDeleteMemories(criteria) {
2919
+ if (!this.db) {
2920
+ throw new Error('Database not available');
2921
+ }
2922
+ const conditions = [];
2923
+ const values = [];
2924
+ let paramIndex = 1;
2925
+ // PROJECT ISOLATION: Always filter by project_path first
2926
+ const projectPath = getProjectPathForInsert();
2927
+ conditions.push(`project_path = $${paramIndex}`);
2928
+ values.push(projectPath);
2929
+ paramIndex++;
2930
+ // Delete by IDs
2931
+ if (criteria.ids && criteria.ids.length > 0) {
2932
+ conditions.push(`id = ANY($${paramIndex})`);
2933
+ values.push(criteria.ids);
2934
+ paramIndex++;
2935
+ }
2936
+ // Delete older than date
2937
+ if (criteria.olderThan) {
2938
+ conditions.push(`created_at < $${paramIndex}`);
2939
+ values.push(criteria.olderThan);
2940
+ paramIndex++;
2941
+ }
2942
+ // Delete by tags (memories having any of these tags)
2943
+ if (criteria.tags && criteria.tags.length > 0) {
2944
+ conditions.push(`tags && $${paramIndex}`);
2945
+ values.push(criteria.tags);
2946
+ paramIndex++;
2947
+ }
2948
+ // Delete only expired memories
2949
+ if (criteria.expiredOnly) {
2950
+ conditions.push('expires_at IS NOT NULL AND expires_at < NOW()');
2951
+ }
2952
+ // We always have project_path, but need at least one other criterion
2953
+ if (conditions.length === 1) {
2954
+ throw new Error('At least one deletion criterion required (besides project filter)');
2955
+ }
2956
+ const query = `DELETE FROM memories WHERE ${conditions.join(' AND ')} RETURNING id`;
2957
+ const result = await this.db.query(query, values);
2958
+ const deletedCount = result.rowCount ?? 0;
2959
+ if (deletedCount > 0) {
2960
+ this.broadcastUpdate('memories_bulk_deleted', {
2961
+ count: deletedCount,
2962
+ criteria
2963
+ });
2964
+ }
2965
+ logger.info({ deletedCount, criteria, projectPath }, 'Bulk delete completed');
2966
+ return { deleted: deletedCount };
2967
+ }
2968
+ /**
2969
+ * Get sessions with detailed information
2970
+ */
2971
+ async getSessions() {
2972
+ if (!this.db) {
2973
+ return [];
2974
+ }
2975
+ // Get detailed session information including memory types and importance distribution
2976
+ // Get unique sessions with deduplication
2977
+ // The DISTINCT ON ensures we get unique session_ids and avoid any edge cases
2978
+ const result = await this.db.query(`
2979
+ WITH session_memories AS (
2980
+ SELECT
2981
+ COALESCE(metadata->>'sessionId', metadata->>'session_id') as session_id,
2982
+ id,
2983
+ memory_type,
2984
+ importance,
2985
+ tags,
2986
+ created_at,
2987
+ content,
2988
+ metadata
2989
+ FROM memories
2990
+ WHERE (metadata->>'sessionId' IS NOT NULL AND metadata->>'sessionId' != '')
2991
+ OR (metadata->>'session_id' IS NOT NULL AND metadata->>'session_id' != '')
2992
+ ),
2993
+ session_aggregates AS (
2994
+ SELECT
2995
+ session_id,
2996
+ MIN(created_at) as started_at,
2997
+ MAX(created_at) as last_activity,
2998
+ COUNT(*) as memory_count,
2999
+ COUNT(*) as message_count,
3000
+ COUNT(DISTINCT memory_type) as memory_types_used,
3001
+ ARRAY_AGG(DISTINCT memory_type) as memory_types,
3002
+ ARRAY_AGG(DISTINCT importance) as importance_levels,
3003
+ MAX(metadata->>'project') as project,
3004
+ MAX(metadata->>'workingDirectory') as working_directory,
3005
+ EXTRACT(EPOCH FROM (MAX(created_at) - MIN(created_at)))/60 as duration_minutes
3006
+ FROM session_memories
3007
+ WHERE session_id IS NOT NULL AND session_id != ''
3008
+ GROUP BY session_id
3009
+ )
3010
+ SELECT
3011
+ session_id,
3012
+ started_at,
3013
+ last_activity,
3014
+ memory_count,
3015
+ message_count,
3016
+ memory_types_used,
3017
+ memory_types,
3018
+ importance_levels,
3019
+ project,
3020
+ working_directory,
3021
+ duration_minutes
3022
+ FROM session_aggregates
3023
+ ORDER BY last_activity DESC
3024
+ LIMIT 100
3025
+ `);
3026
+ return result.rows;
3027
+ }
3028
+ /**
3029
+ * Get codebase files with content search support
3030
+ */
3031
+ async getCodebaseFiles(filePath, search) {
3032
+ if (!this.codebaseIndexer) {
3033
+ return { files: [], stats: {} };
3034
+ }
3035
+ const stats = this.codebaseIndexer.getStats();
3036
+ // Get all files
3037
+ const allFiles = this.codebaseIndexer.getAllFiles();
3038
+ // Filter by search term or path
3039
+ let files = allFiles;
3040
+ let searchType;
3041
+ if (search) {
3042
+ const searchLower = search.toLowerCase();
3043
+ // Check if search should include file content
3044
+ const includeContent = search.startsWith('content:') || search.startsWith('code:');
3045
+ const searchTerm = includeContent ? search.replace(/^(content:|code:)/, '').trim() : search;
3046
+ const searchTermLower = searchTerm.toLowerCase();
3047
+ if (includeContent) {
3048
+ // Content search - search within file contents
3049
+ searchType = 'content';
3050
+ files = allFiles.filter(f => f.content.toLowerCase().includes(searchTermLower)).slice(0, 50);
3051
+ // Extract matching lines for context
3052
+ const filesWithMatches = files.map(f => {
3053
+ const lines = f.content.split('\n');
3054
+ const matchingLines = [];
3055
+ lines.forEach((line, index) => {
3056
+ if (line.toLowerCase().includes(searchTermLower)) {
3057
+ matchingLines.push({
3058
+ lineNumber: index + 1,
3059
+ content: line.trim().slice(0, 200)
3060
+ });
3061
+ }
3062
+ });
3063
+ return {
3064
+ path: f.filePath,
3065
+ name: f.fileName,
3066
+ language: f.language,
3067
+ lines: f.lineCount,
3068
+ size: f.sizeBytes,
3069
+ lastModified: f.lastModified,
3070
+ matches: matchingLines.slice(0, 5), // Top 5 matches
3071
+ matchCount: matchingLines.length
3072
+ };
3073
+ });
3074
+ return {
3075
+ files: filesWithMatches,
3076
+ stats,
3077
+ searchType
3078
+ };
3079
+ }
3080
+ else {
3081
+ // Path/name search
3082
+ searchType = 'path';
3083
+ files = allFiles.filter(f => f.filePath.toLowerCase().includes(searchLower) ||
3084
+ f.fileName.toLowerCase().includes(searchLower) ||
3085
+ f.language.toLowerCase().includes(searchLower)).slice(0, 100);
3086
+ }
3087
+ }
3088
+ else if (filePath) {
3089
+ // Filter by path prefix
3090
+ files = allFiles.filter(f => f.filePath.startsWith(filePath));
3091
+ }
3092
+ else {
3093
+ // Return most recently modified files
3094
+ files = [...allFiles]
3095
+ .sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime())
3096
+ .slice(0, 100);
3097
+ }
3098
+ // Map to simpler format for frontend
3099
+ const mappedFiles = files.map(f => ({
3100
+ path: f.filePath,
3101
+ name: f.fileName,
3102
+ language: f.language,
3103
+ lines: f.lineCount,
3104
+ size: f.sizeBytes,
3105
+ lastModified: f.lastModified
3106
+ }));
3107
+ return { files: mappedFiles, stats, searchType };
3108
+ }
3109
+ /**
3110
+ * Get file content from the codebase indexer
3111
+ */
3112
+ async getFileContent(filePath) {
3113
+ if (!this.codebaseIndexer) {
3114
+ return null;
3115
+ }
3116
+ const file = this.codebaseIndexer.getFile(filePath);
3117
+ if (!file) {
3118
+ return null;
3119
+ }
3120
+ return {
3121
+ path: file.filePath,
3122
+ name: file.fileName,
3123
+ language: file.language,
3124
+ content: file.content,
3125
+ lines: file.lineCount,
3126
+ size: file.sizeBytes
3127
+ };
3128
+ }
3129
+ /**
3130
+ * Get skills
3131
+ */
3132
+ async getSkills() {
3133
+ if (!this.skillScanner) {
3134
+ return { skills: [], categories: [] };
3135
+ }
3136
+ const skills = this.skillScanner.getAllSkills();
3137
+ const categories = this.skillScanner.getCategories();
3138
+ return {
3139
+ skills: skills.map(s => ({
3140
+ id: s.id,
3141
+ name: s.name,
3142
+ category: s.category,
3143
+ description: s.description,
3144
+ path: s.filePath,
3145
+ size: s.content.length,
3146
+ content: s.content // Include content so frontend can display/edit it
3147
+ })),
3148
+ categories
3149
+ };
3150
+ }
3151
+ /**
3152
+ * Reload skills
3153
+ */
3154
+ async reloadSkills() {
3155
+ if (!this.skillScanner) {
3156
+ throw new Error('Skill scanner not available');
3157
+ }
3158
+ await this.skillScanner.scan();
3159
+ this.broadcastUpdate('skills_reloaded', await this.getSkills());
3160
+ }
3161
+ /**
3162
+ * Get active team members from discovery service (SpecMem-based)
3163
+ */
3164
+ async getTeamMembers() {
3165
+ try {
3166
+ // Get team members from discovery service (SpecMem heartbeat-based)
3167
+ if (this.dashboardDiscovery) {
3168
+ const discovered = await this.dashboardDiscovery.getActiveTeamMembers(120000); // 2 min expiry
3169
+ return discovered.map((d) => ({
3170
+ id: d.teamMemberId,
3171
+ name: d.teamMemberId,
3172
+ type: d.teamMemberType,
3173
+ connected: true,
3174
+ lastSeen: d.lastHeartbeat
3175
+ }));
3176
+ }
3177
+ // Fallback to coordination server if discovery not available
3178
+ const response = await fetch(`http://localhost:${this.config.coordinationPort}/teamMembers`);
3179
+ if (response.ok) {
3180
+ const data = await response.json();
3181
+ return data.teamMembers || [];
3182
+ }
3183
+ }
3184
+ catch (error) {
3185
+ logger.debug({ error }, 'Error fetching team members');
3186
+ }
3187
+ return [];
3188
+ }
3189
+ // ==================== TEAM_MEMBER COMMUNICATION DASHBOARD METHODS ====================
3190
+ /**
3191
+ * Get all currently active team member sessions from database
3192
+ */
3193
+ async getActiveTeamMemberSessions() {
3194
+ if (!this.db) {
3195
+ return { sessions: [], count: 0 };
3196
+ }
3197
+ try {
3198
+ const result = await this.db.query(`
3199
+ SELECT
3200
+ id,
3201
+ team_member_id,
3202
+ team_member_name,
3203
+ team_member_type,
3204
+ status,
3205
+ started_at,
3206
+ last_heartbeat,
3207
+ current_task,
3208
+ working_directory,
3209
+ project_name,
3210
+ message_count,
3211
+ tool_calls,
3212
+ errors_count,
3213
+ tokens_used,
3214
+ metadata,
3215
+ capabilities
3216
+ FROM team_member_sessions
3217
+ WHERE status IN ('active', 'idle', 'busy')
3218
+ AND last_heartbeat > NOW() - INTERVAL '5 minutes'
3219
+ ORDER BY last_heartbeat DESC
3220
+ `);
3221
+ return {
3222
+ sessions: result.rows,
3223
+ count: result.rowCount ?? 0
3224
+ };
3225
+ }
3226
+ catch (error) {
3227
+ logger.debug({ error }, 'Error fetching active team member sessions');
3228
+ return { sessions: [], count: 0 };
3229
+ }
3230
+ }
3231
+ /**
3232
+ * Get team member session history with pagination and filtering
3233
+ */
3234
+ async getTeamMemberSessionHistory(limit, offset, teamMemberType, status) {
3235
+ if (!this.db) {
3236
+ return { sessions: [], total: 0, limit, offset };
3237
+ }
3238
+ try {
3239
+ const conditions = [];
3240
+ const values = [];
3241
+ let paramIndex = 1;
3242
+ if (teamMemberType) {
3243
+ conditions.push(`team_member_type = $${paramIndex}`);
3244
+ values.push(teamMemberType);
3245
+ paramIndex++;
3246
+ }
3247
+ if (status) {
3248
+ conditions.push(`status = $${paramIndex}`);
3249
+ values.push(status);
3250
+ paramIndex++;
3251
+ }
3252
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
3253
+ // Get total count
3254
+ const countResult = await this.db.query(`SELECT COUNT(*) as count FROM team_member_sessions ${whereClause}`, values);
3255
+ const total = parseInt(countResult.rows[0]?.count || '0', 10);
3256
+ // Get paginated results
3257
+ values.push(limit, offset);
3258
+ const result = await this.db.query(`
3259
+ SELECT
3260
+ id,
3261
+ team_member_id,
3262
+ team_member_name,
3263
+ team_member_type,
3264
+ status,
3265
+ started_at,
3266
+ ended_at,
3267
+ last_heartbeat,
3268
+ current_task,
3269
+ project_name,
3270
+ message_count,
3271
+ tool_calls,
3272
+ errors_count,
3273
+ tokens_used,
3274
+ EXTRACT(EPOCH FROM (COALESCE(ended_at, NOW()) - started_at)) as duration_seconds
3275
+ FROM team_member_sessions
3276
+ ${whereClause}
3277
+ ORDER BY started_at DESC
3278
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
3279
+ `, values);
3280
+ return {
3281
+ sessions: result.rows,
3282
+ total,
3283
+ limit,
3284
+ offset
3285
+ };
3286
+ }
3287
+ catch (error) {
3288
+ logger.debug({ error }, 'Error fetching team member session history');
3289
+ return { sessions: [], total: 0, limit, offset };
3290
+ }
3291
+ }
3292
+ /**
3293
+ * Get detailed information about a specific team member session
3294
+ */
3295
+ async getTeamMemberSessionDetails(sessionId, includeMessages = true, messageLimit = 100) {
3296
+ if (!this.db) {
3297
+ return null;
3298
+ }
3299
+ try {
3300
+ // Get session details
3301
+ const sessionResult = await this.db.query(`
3302
+ SELECT
3303
+ s.*,
3304
+ EXTRACT(EPOCH FROM (COALESCE(ended_at, NOW()) - started_at)) as duration_seconds,
3305
+ (SELECT COUNT(*) FROM team_member_messages WHERE session_id = s.id) as total_messages,
3306
+ (SELECT COUNT(*) FROM team_member_messages WHERE session_id = s.id AND message_type = 'tool_call') as total_tool_calls,
3307
+ (SELECT COUNT(*) FROM team_member_messages WHERE session_id = s.id AND is_error = true) as total_errors
3308
+ FROM team_member_sessions s
3309
+ WHERE s.id = $1
3310
+ `, [sessionId]);
3311
+ if (sessionResult.rowCount === 0) {
3312
+ return null;
3313
+ }
3314
+ const session = sessionResult.rows[0];
3315
+ const response = { session };
3316
+ // Get messages if requested
3317
+ if (includeMessages) {
3318
+ const messagesResult = await this.db.query(`
3319
+ SELECT
3320
+ id,
3321
+ message_type,
3322
+ direction,
3323
+ sequence_number,
3324
+ content_preview as content,
3325
+ tool_name,
3326
+ tool_duration_ms,
3327
+ role,
3328
+ importance,
3329
+ input_tokens,
3330
+ output_tokens,
3331
+ is_error,
3332
+ error_message,
3333
+ timestamp
3334
+ FROM team_member_messages
3335
+ WHERE session_id = $1
3336
+ ORDER BY sequence_number DESC
3337
+ LIMIT $2
3338
+ `, [sessionId, messageLimit]);
3339
+ response.messages = messagesResult.rows;
3340
+ response.messageCount = messagesResult.rowCount ?? 0;
3341
+ }
3342
+ return response;
3343
+ }
3344
+ catch (error) {
3345
+ logger.debug({ error }, 'Error fetching session details');
3346
+ return null;
3347
+ }
3348
+ }
3349
+ /**
3350
+ * Get messages for a specific session with pagination
3351
+ */
3352
+ async getTeamMemberSessionMessages(sessionId, limit = 50, offset = 0, messageType) {
3353
+ if (!this.db) {
3354
+ return { messages: [], total: 0, limit, offset };
3355
+ }
3356
+ try {
3357
+ const conditions = ['session_id = $1'];
3358
+ const values = [sessionId];
3359
+ let paramIndex = 2;
3360
+ if (messageType) {
3361
+ conditions.push(`message_type = $${paramIndex}`);
3362
+ values.push(messageType);
3363
+ paramIndex++;
3364
+ }
3365
+ const whereClause = `WHERE ${conditions.join(' AND ')}`;
3366
+ // Get total count
3367
+ const countResult = await this.db.query(`SELECT COUNT(*) as count FROM team_member_messages ${whereClause}`, values);
3368
+ const total = parseInt(countResult.rows[0]?.count || '0', 10);
3369
+ // Get paginated results
3370
+ values.push(limit, offset);
3371
+ const result = await this.db.query(`
3372
+ SELECT
3373
+ id,
3374
+ message_type,
3375
+ direction,
3376
+ sequence_number,
3377
+ content,
3378
+ tool_name,
3379
+ tool_input,
3380
+ tool_output,
3381
+ tool_error,
3382
+ tool_duration_ms,
3383
+ role,
3384
+ importance,
3385
+ input_tokens,
3386
+ output_tokens,
3387
+ estimated_cost_cents,
3388
+ is_error,
3389
+ error_code,
3390
+ error_message,
3391
+ timestamp
3392
+ FROM team_member_messages
3393
+ ${whereClause}
3394
+ ORDER BY sequence_number DESC
3395
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
3396
+ `, values);
3397
+ return {
3398
+ messages: result.rows,
3399
+ total,
3400
+ limit,
3401
+ offset
3402
+ };
3403
+ }
3404
+ catch (error) {
3405
+ logger.debug({ error }, 'Error fetching session messages');
3406
+ return { messages: [], total: 0, limit, offset };
3407
+ }
3408
+ }
3409
+ /**
3410
+ * Get team member deployments with pagination and filtering
3411
+ */
3412
+ async getTeamMemberDeployments(limit, offset, status, environment) {
3413
+ if (!this.db) {
3414
+ return { deployments: [], total: 0, limit, offset };
3415
+ }
3416
+ try {
3417
+ const conditions = [];
3418
+ const values = [];
3419
+ let paramIndex = 1;
3420
+ if (status) {
3421
+ conditions.push(`status = $${paramIndex}`);
3422
+ values.push(status);
3423
+ paramIndex++;
3424
+ }
3425
+ if (environment) {
3426
+ conditions.push(`environment = $${paramIndex}`);
3427
+ values.push(environment);
3428
+ paramIndex++;
3429
+ }
3430
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
3431
+ // Get total count
3432
+ const countResult = await this.db.query(`SELECT COUNT(*) as count FROM team_member_deployments ${whereClause}`, values);
3433
+ const total = parseInt(countResult.rows[0]?.count || '0', 10);
3434
+ // Get paginated results
3435
+ values.push(limit, offset);
3436
+ const result = await this.db.query(`
3437
+ SELECT
3438
+ id,
3439
+ deployment_name,
3440
+ deployment_type,
3441
+ environment,
3442
+ team_member_count,
3443
+ status,
3444
+ health,
3445
+ started_at,
3446
+ completed_at,
3447
+ task_description,
3448
+ success,
3449
+ result_summary,
3450
+ actual_tokens_used,
3451
+ actual_cost_cents,
3452
+ tags,
3453
+ EXTRACT(EPOCH FROM (COALESCE(completed_at, NOW()) - started_at)) as duration_seconds
3454
+ FROM team_member_deployments
3455
+ ${whereClause}
3456
+ ORDER BY created_at DESC
3457
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
3458
+ `, values);
3459
+ return {
3460
+ deployments: result.rows,
3461
+ total,
3462
+ limit,
3463
+ offset
3464
+ };
3465
+ }
3466
+ catch (error) {
3467
+ logger.debug({ error }, 'Error fetching deployments');
3468
+ return { deployments: [], total: 0, limit, offset };
3469
+ }
3470
+ }
3471
+ /**
3472
+ * Get aggregate statistics for team members
3473
+ */
3474
+ async getTeamMemberStats() {
3475
+ if (!this.db) {
3476
+ return {
3477
+ activeSessions: 0,
3478
+ totalSessions: 0,
3479
+ totalMessages: 0,
3480
+ totalToolCalls: 0,
3481
+ totalTokens: 0,
3482
+ avgSessionDuration: 0,
3483
+ errorRate: 0,
3484
+ topTeamMemberTypes: [],
3485
+ recentActivity: []
3486
+ };
3487
+ }
3488
+ try {
3489
+ // Get basic counts
3490
+ const countsResult = await this.db.query(`
3491
+ SELECT
3492
+ (SELECT COUNT(*) FROM team_member_sessions WHERE status IN ('active', 'idle', 'busy')) as active_sessions,
3493
+ (SELECT COUNT(*) FROM team_member_sessions) as total_sessions,
3494
+ (SELECT COUNT(*) FROM team_member_messages) as total_messages,
3495
+ (SELECT COUNT(*) FROM team_member_messages WHERE message_type = 'tool_call') as total_tool_calls,
3496
+ (SELECT COALESCE(SUM(tokens_used), 0) FROM team_member_sessions) as total_tokens,
3497
+ (SELECT AVG(EXTRACT(EPOCH FROM (COALESCE(ended_at, NOW()) - started_at)))
3498
+ FROM team_member_sessions WHERE started_at IS NOT NULL) as avg_duration,
3499
+ (SELECT COUNT(*)::FLOAT / NULLIF(COUNT(*) FILTER (WHERE NOT is_error), 0) * 100
3500
+ FROM team_member_messages) as error_rate
3501
+ `);
3502
+ const counts = countsResult.rows[0] || {};
3503
+ // Get top team member types
3504
+ const typesResult = await this.db.query(`
3505
+ SELECT team_member_type as type, COUNT(*) as count
3506
+ FROM team_member_sessions
3507
+ GROUP BY team_member_type
3508
+ ORDER BY count DESC
3509
+ LIMIT 5
3510
+ `);
3511
+ // Get recent activity (last 24 hours by hour)
3512
+ const activityResult = await this.db.query(`
3513
+ SELECT
3514
+ DATE_TRUNC('hour', s.started_at) as hour,
3515
+ COUNT(DISTINCT s.id) as sessions,
3516
+ COUNT(m.id) as messages
3517
+ FROM team_member_sessions s
3518
+ LEFT JOIN team_member_messages m ON m.session_id = s.id
3519
+ AND m.timestamp >= NOW() - INTERVAL '24 hours'
3520
+ WHERE s.started_at >= NOW() - INTERVAL '24 hours'
3521
+ GROUP BY DATE_TRUNC('hour', s.started_at)
3522
+ ORDER BY hour DESC
3523
+ LIMIT 24
3524
+ `);
3525
+ return {
3526
+ activeSessions: parseInt(counts.active_sessions || '0', 10),
3527
+ totalSessions: parseInt(counts.total_sessions || '0', 10),
3528
+ totalMessages: parseInt(counts.total_messages || '0', 10),
3529
+ totalToolCalls: parseInt(counts.total_tool_calls || '0', 10),
3530
+ totalTokens: parseInt(counts.total_tokens || '0', 10),
3531
+ avgSessionDuration: parseFloat(counts.avg_duration || '0'),
3532
+ errorRate: parseFloat(counts.error_rate || '0'),
3533
+ topTeamMemberTypes: typesResult.rows.map((r) => ({
3534
+ type: r.type,
3535
+ count: parseInt(r.count, 10)
3536
+ })),
3537
+ recentActivity: activityResult.rows.map((r) => ({
3538
+ hour: r.hour,
3539
+ sessions: parseInt(r.sessions, 10),
3540
+ messages: parseInt(r.messages, 10)
3541
+ }))
3542
+ };
3543
+ }
3544
+ catch (error) {
3545
+ logger.debug({ error }, 'Error fetching team member stats');
3546
+ return {
3547
+ activeSessions: 0,
3548
+ totalSessions: 0,
3549
+ totalMessages: 0,
3550
+ totalToolCalls: 0,
3551
+ totalTokens: 0,
3552
+ avgSessionDuration: 0,
3553
+ errorRate: 0,
3554
+ topTeamMemberTypes: [],
3555
+ recentActivity: []
3556
+ };
3557
+ }
3558
+ }
3559
+ /**
3560
+ * Set database manager
3561
+ */
3562
+ setDatabase(db) {
3563
+ this.db = db;
3564
+ }
3565
+ /**
3566
+ * Set skill scanner
3567
+ */
3568
+ setSkillScanner(scanner) {
3569
+ this.skillScanner = scanner;
3570
+ }
3571
+ /**
3572
+ * Set codebase indexer
3573
+ */
3574
+ setCodebaseIndexer(indexer) {
3575
+ this.codebaseIndexer = indexer;
3576
+ }
3577
+ /**
3578
+ * Set embedding provider for semantic search
3579
+ */
3580
+ setEmbeddingProvider(provider) {
3581
+ this.embeddingProvider = provider;
3582
+ }
3583
+ /**
3584
+ * Set memory manager for heap monitoring
3585
+ */
3586
+ setMemoryManager(manager) {
3587
+ this.memoryManager = manager;
3588
+ }
3589
+ /**
3590
+ * Set embedding overflow handler
3591
+ */
3592
+ setEmbeddingOverflowHandler(handler) {
3593
+ this.embeddingOverflowHandler = handler;
3594
+ }
3595
+ /**
3596
+ * Set path to env file for password persistence
3597
+ */
3598
+ setEnvFilePath(envPath) {
3599
+ this.envFilePath = envPath;
3600
+ }
3601
+ /**
3602
+ * Start the server with port retry logic
3603
+ * Will try multiple ports if the base port is in use
3604
+ */
3605
+ async start() {
3606
+ if (this.isRunning) {
3607
+ logger.warn('Dashboard server already running');
3608
+ return;
3609
+ }
3610
+ // Try to get database
3611
+ try {
3612
+ // Build database config from environment variables
3613
+ const dbConfig = {
3614
+ host: process.env['SPECMEM_DB_HOST'] || 'localhost',
3615
+ port: parseInt(process.env['SPECMEM_DB_PORT'] || '5432', 10),
3616
+ database: process.env['SPECMEM_DB_NAME'] || 'specmem_westayunprofessional',
3617
+ user: process.env['SPECMEM_DB_USER'] || 'specmem_westayunprofessional',
3618
+ password: process.env['SPECMEM_DB_PASSWORD'] || '',
3619
+ maxConnections: parseInt(process.env['SPECMEM_DB_MAX_CONNECTIONS'] || '20', 10),
3620
+ idleTimeout: 30000,
3621
+ connectionTimeout: 5000
3622
+ };
3623
+ this.db = getDatabase(dbConfig);
3624
+ if (this.db) {
3625
+ await this.db.initialize(); // sets up schema isolation
3626
+ }
3627
+ }
3628
+ catch (error) {
3629
+ logger.warn('Database not available for dashboard');
3630
+ }
3631
+ // Initialize PostgreSQL session store if database is available
3632
+ if (this.db) {
3633
+ try {
3634
+ const store = await createSessionStore(this.db, {
3635
+ tableName: 'dashboard_sessions',
3636
+ cleanupIntervalMs: 15 * 60 * 1000, // 15 minutes
3637
+ pruneOnStart: true
3638
+ });
3639
+ if (store) {
3640
+ this.sessionStore = store;
3641
+ // Reconfigure session middleware with PostgreSQL store
3642
+ const cookieSecure = process.env.SPECMEM_COOKIE_SECURE === 'true';
3643
+ this.app.use(session({
3644
+ store: this.sessionStore,
3645
+ secret: this.config.sessionSecret,
3646
+ resave: false,
3647
+ saveUninitialized: false,
3648
+ cookie: {
3649
+ secure: cookieSecure,
3650
+ httpOnly: true,
3651
+ maxAge: 24 * 60 * 60 * 1000 // 24 hours
3652
+ }
3653
+ }));
3654
+ logger.info('PostgreSQL session store initialized - no more memory leaks!');
3655
+ }
3656
+ else {
3657
+ logger.warn('Using in-memory session store - sessions will not persist across restarts');
3658
+ }
3659
+ }
3660
+ catch (error) {
3661
+ logger.warn({ error }, 'Failed to initialize PostgreSQL session store - using in-memory store');
3662
+ }
3663
+ }
3664
+ else {
3665
+ logger.warn('Using in-memory session store - database not available');
3666
+ }
3667
+ // Try to get skill scanner
3668
+ try {
3669
+ this.skillScanner = getSkillScanner();
3670
+ }
3671
+ catch (error) {
3672
+ logger.warn('Skill scanner not available for dashboard');
3673
+ }
3674
+ // Try to get codebase indexer
3675
+ try {
3676
+ this.codebaseIndexer = getCodebaseIndexer();
3677
+ }
3678
+ catch (error) {
3679
+ logger.warn('Codebase indexer not available for dashboard');
3680
+ }
3681
+ // Try to get memory manager
3682
+ try {
3683
+ this.memoryManager = getMemoryManager();
3684
+ logger.info('Memory manager connected to dashboard');
3685
+ }
3686
+ catch (error) {
3687
+ logger.warn('Memory manager not available for dashboard');
3688
+ }
3689
+ // Initialize team member tracker, deployment, and history manager
3690
+ try {
3691
+ this.teamMemberTracker = getTeamMemberTracker();
3692
+ if (this.db) {
3693
+ this.teamMemberTracker.setDatabase(this.db.pool);
3694
+ }
3695
+ this.teamMemberDeployment = getTeamMemberDeployment();
3696
+ this.teamMemberHistoryManager = getTeamMemberHistoryManager();
3697
+ // Initialize Task team member logger
3698
+ if (this.db) {
3699
+ initializeTaskTeamMemberLogger(this.db);
3700
+ logger.info('Task team member logger initialized - Code team members will be tracked');
3701
+ }
3702
+ if (this.db) {
3703
+ this.teamMemberHistoryManager.setDatabase(this.db.pool);
3704
+ }
3705
+ this.setupTeamMemberEventForwarding();
3706
+ logger.info('Team Member tracker, deployment, and history manager initialized');
3707
+ }
3708
+ catch (error) {
3709
+ logger.warn({ error }, 'Team Member tracker/deployment/history not available');
3710
+ }
3711
+ // BUG FIX (Team Member 2): Initialize dashboard communicator and discovery for SpecMem-based team members
3712
+ try {
3713
+ const dashboardTeamMemberId = `dashboard-${crypto.randomUUID().substring(0, 8)}`;
3714
+ this.dashboardCommunicator = createTeamMemberCommunicator(dashboardTeamMemberId);
3715
+ this.dashboardDiscovery = createTeamMemberDiscovery(dashboardTeamMemberId, 'dashboard', 'overseer', {
3716
+ heartbeatIntervalMs: 60000, // Dashboard heartbeats less frequently
3717
+ teamMemberExpiryMs: 120000 // 2 minute expiry for discovered team members
3718
+ });
3719
+ // Start discovery but don't block on it
3720
+ this.dashboardDiscovery.start().catch((err) => {
3721
+ logger.debug({ error: err }, 'Dashboard discovery start warning (non-critical)');
3722
+ });
3723
+ logger.info({ teamMemberId: dashboardTeamMemberId }, 'Dashboard communicator and discovery initialized');
3724
+ }
3725
+ catch (error) {
3726
+ logger.debug({ error }, 'Dashboard communicator/discovery not available (SpecMem may not be running)');
3727
+ }
3728
+ // Initialize Memory Recall API routes
3729
+ if (this.db) {
3730
+ try {
3731
+ const memoryRecallRouter = createMemoryRecallRouter(this.db);
3732
+ this.app.use('/api/memory', (req, res, next) => {
3733
+ // Apply auth middleware to memory recall routes
3734
+ const sess = req.session;
3735
+ if (sess?.authenticated) {
3736
+ next();
3737
+ }
3738
+ else {
3739
+ res.status(401).json({ error: 'Authentication required' });
3740
+ }
3741
+ }, memoryRecallRouter);
3742
+ logger.info('Memory Recall API routes initialized');
3743
+ }
3744
+ catch (error) {
3745
+ logger.warn({ error }, 'Failed to initialize Memory Recall API');
3746
+ }
3747
+ // Initialize Team Member History API routes
3748
+ try {
3749
+ const teamMemberHistoryRouter = createTeamMemberHistoryRouter(this.db);
3750
+ this.app.use('/api/teamMembers', (req, res, next) => {
3751
+ // Apply auth middleware to team member history routes
3752
+ const sess = req.session;
3753
+ if (sess?.authenticated) {
3754
+ next();
3755
+ }
3756
+ else {
3757
+ res.status(401).json({ error: 'Authentication required' });
3758
+ }
3759
+ }, teamMemberHistoryRouter);
3760
+ logger.info('Team Member History API routes initialized');
3761
+ }
3762
+ catch (error) {
3763
+ logger.warn({ error }, 'Failed to initialize Team Member History API');
3764
+ }
3765
+ // Initialize TeamMember Deploy API routes
3766
+ try {
3767
+ const teamMemberDeployRouter = createTeamMemberDeployRouter();
3768
+ this.app.use('/api/teamMembers', (req, res, next) => {
3769
+ // Apply auth middleware to team member deploy routes
3770
+ const sess = req.session;
3771
+ if (sess?.authenticated) {
3772
+ next();
3773
+ }
3774
+ else {
3775
+ res.status(401).json({ error: 'Authentication required' });
3776
+ }
3777
+ }, teamMemberDeployRouter);
3778
+ logger.info('Team Member Deploy API routes initialized');
3779
+ }
3780
+ catch (error) {
3781
+ logger.warn({ error }, 'Failed to initialize TeamMember Deploy API');
3782
+ }
3783
+ }
3784
+ // Initialize TeamMember Stream WebSocket
3785
+ try {
3786
+ this.teamMemberStreamManager = initializeTeamMemberStream(this.server, '/ws/team-members/live');
3787
+ logger.info('Team Member Stream WebSocket initialized');
3788
+ }
3789
+ catch (error) {
3790
+ logger.warn({ error }, 'Failed to initialize TeamMember Stream WebSocket');
3791
+ }
3792
+ // Auto-detect env file path for password persistence
3793
+ if (!this.envFilePath) {
3794
+ const envPaths = [
3795
+ path.join(process.cwd(), 'specmem.env'),
3796
+ path.join(process.cwd(), '.env'),
3797
+ path.join(__dirname, '../../specmem.env'),
3798
+ path.join(__dirname, '../../.env')
3799
+ ];
3800
+ for (const envPath of envPaths) {
3801
+ try {
3802
+ await fs.access(envPath);
3803
+ this.envFilePath = envPath;
3804
+ logger.debug({ envPath }, 'Found env file for password persistence');
3805
+ break;
3806
+ }
3807
+ catch (e) {
3808
+ // File doesn't exist, try next - expected behavior
3809
+ logger.debug({ envPath, error: e }, 'env file not found at this path, checking next');
3810
+ }
3811
+ }
3812
+ }
3813
+ const { maxPortAttempts, maxStartupRetries, retryDelayMs } = this.config;
3814
+ for (let portOffset = 0; portOffset < maxPortAttempts; portOffset++) {
3815
+ const port = this.config.port + portOffset;
3816
+ // Check port availability first
3817
+ const available = await isPortAvailable(port, this.config.host);
3818
+ if (!available) {
3819
+ logger.debug({ port, host: this.config.host }, 'Dashboard port already in use, trying next');
3820
+ continue;
3821
+ }
3822
+ // Try to start the server with retries
3823
+ for (let retry = 0; retry < maxStartupRetries; retry++) {
3824
+ try {
3825
+ await this.startOnPort(port);
3826
+ this.actualPort = port;
3827
+ return; // Success!
3828
+ }
3829
+ catch (err) {
3830
+ const error = err instanceof Error ? err : new Error(String(err));
3831
+ // Check if it's a port-in-use error (race condition)
3832
+ if (error.message.includes('EADDRINUSE') || error.code === 'EADDRINUSE') {
3833
+ logger.warn({ port }, 'Dashboard port became unavailable during startup, trying next');
3834
+ break; // Try next port
3835
+ }
3836
+ logger.warn({
3837
+ port,
3838
+ retry: retry + 1,
3839
+ maxRetries: maxStartupRetries,
3840
+ error: error.message
3841
+ }, 'Dashboard server startup failed, retrying');
3842
+ // Wait before retry with exponential backoff
3843
+ if (retry < maxStartupRetries - 1) {
3844
+ await sleep(retryDelayMs * Math.pow(2, retry));
3845
+ }
3846
+ }
3847
+ }
3848
+ }
3849
+ // All attempts failed
3850
+ const errorMsg = `Failed to start dashboard server on any port in range ${this.config.port}-${this.config.port + maxPortAttempts - 1}`;
3851
+ logger.error({ basePort: this.config.port, maxPortAttempts }, errorMsg);
3852
+ throw new Error(errorMsg);
3853
+ }
3854
+ /**
3855
+ * Internal method to start server on a specific port
3856
+ */
3857
+ startOnPort(port) {
3858
+ return new Promise((resolve, reject) => {
3859
+ // Set up error handler before listening to catch EADDRINUSE
3860
+ const errorHandler = (error) => {
3861
+ // Remove error handler to prevent memory leak
3862
+ this.server.removeListener('error', errorHandler);
3863
+ logger.error({ error, port }, 'Dashboard server error during startup');
3864
+ reject(error);
3865
+ };
3866
+ this.server.once('error', errorHandler);
3867
+ this.server.listen(port, this.config.host, () => {
3868
+ // Remove error handler on success
3869
+ this.server.removeListener('error', errorHandler);
3870
+ this.isRunning = true;
3871
+ this.startTime = Date.now();
3872
+ this.actualPort = port;
3873
+ // Set up server timeouts
3874
+ setServerTimeouts(this.server, {
3875
+ keepAliveTimeout: parseInt(process.env['SPECMEM_KEEP_ALIVE_TIMEOUT'] || '5000', 10),
3876
+ headersTimeout: parseInt(process.env['SPECMEM_HEADERS_TIMEOUT'] || '60000', 10),
3877
+ requestTimeout: parseInt(process.env['SPECMEM_SERVER_REQUEST_TIMEOUT'] || '120000', 10)
3878
+ });
3879
+ // Set up persistent error handler for runtime errors
3880
+ this.server.on('error', (error) => {
3881
+ logger.error({ error, port: this.actualPort }, 'Dashboard server runtime error');
3882
+ // Don't crash - just log
3883
+ });
3884
+ logger.info({
3885
+ port,
3886
+ configuredPort: this.config.port,
3887
+ host: this.config.host,
3888
+ mode: this.config.mode,
3889
+ url: `http://${this.config.host}:${port}`,
3890
+ envFilePath: this.envFilePath || 'none'
3891
+ }, 'Dashboard server started - CSGO VIBES ACTIVATED');
3892
+ // Security warnings for public mode
3893
+ if (this.config.mode === 'public') {
3894
+ logger.warn('========================================');
3895
+ logger.warn(' SECURITY WARNING: PUBLIC MODE ACTIVE ');
3896
+ logger.warn('========================================');
3897
+ logger.warn(`Dashboard is accessible on the network at ${this.config.host}:${port}`);
3898
+ logger.warn('Ensure SPECMEM_DASHBOARD_PASSWORD is set to a strong password!');
3899
+ logger.warn('Consider using a reverse proxy (nginx/caddy) with HTTPS for production.');
3900
+ // Check for weak/default password
3901
+ if (isUsingDefaultPassword()) {
3902
+ logger.error('========================================');
3903
+ logger.error(' CRITICAL: DEFAULT PASSWORD IN USE! ');
3904
+ logger.error('========================================');
3905
+ logger.error('You are running in PUBLIC mode with the default password.');
3906
+ logger.error('This is a MAJOR security risk! Anyone on your network can access the dashboard.');
3907
+ logger.error('Set SPECMEM_DASHBOARD_PASSWORD to a strong, unique password immediately.');
3908
+ }
3909
+ }
3910
+ else {
3911
+ logger.info({ mode: 'private' }, 'Dashboard running in private mode (localhost only)');
3912
+ }
3913
+ resolve();
3914
+ });
3915
+ });
3916
+ }
3917
+ /**
3918
+ * Stop the server
3919
+ */
3920
+ async stop() {
3921
+ if (!this.isRunning) {
3922
+ return;
3923
+ }
3924
+ // Close all WebSocket connections
3925
+ for (const client of this.connectedClients) {
3926
+ client.close(1001, 'Server shutting down');
3927
+ }
3928
+ this.connectedClients.clear();
3929
+ // Shutdown session store
3930
+ if (this.sessionStore) {
3931
+ await this.sessionStore.shutdown();
3932
+ this.sessionStore = null;
3933
+ }
3934
+ // Shutdown terminal stream manager
3935
+ if (this.terminalStreamManager) {
3936
+ this.terminalStreamManager.stop();
3937
+ this.terminalStreamManager = null;
3938
+ }
3939
+ // Shutdown team member stream manager and reset global singleton
3940
+ await shutdownTeamMemberStream();
3941
+ return new Promise((resolve) => {
3942
+ this.server.close(() => {
3943
+ this.isRunning = false;
3944
+ logger.info('Dashboard server stopped');
3945
+ resolve();
3946
+ });
3947
+ });
3948
+ }
3949
+ /**
3950
+ * Get server status
3951
+ */
3952
+ getStatus() {
3953
+ return {
3954
+ running: this.isRunning,
3955
+ port: this.actualPort || this.config.port,
3956
+ configuredPort: this.config.port,
3957
+ uptime: this.isRunning ? Date.now() - this.startTime : 0
3958
+ };
3959
+ }
3960
+ /**
3961
+ * Get the actual port the server is bound to
3962
+ */
3963
+ getActualPort() {
3964
+ return this.actualPort || this.config.port;
3965
+ }
3966
+ /**
3967
+ * Get current dashboard mode
3968
+ */
3969
+ getMode() {
3970
+ return this.config.mode;
3971
+ }
3972
+ /**
3973
+ * Get current host binding
3974
+ */
3975
+ getHost() {
3976
+ return this.config.host;
3977
+ }
3978
+ // ============================================================================
3979
+ // Config Persistence and Hot Reload
3980
+ // ============================================================================
3981
+ /**
3982
+ * RebindResult - Result of a server rebind operation
3983
+ */
3984
+ pendingRebind = false;
3985
+ /**
3986
+ * updateConfig - Update server configuration with optional rebind
3987
+ *
3988
+ * Some changes (mode, host, port) require rebinding the server.
3989
+ * Password changes can be applied without restart (hot reload).
3990
+ *
3991
+ * @param newConfig - Partial config to update
3992
+ * @returns Object indicating success and whether restart is needed
3993
+ */
3994
+ async updateConfig(newConfig) {
3995
+ const appliedChanges = [];
3996
+ let requiresRebind = false;
3997
+ // Detect changes that require rebind
3998
+ if (newConfig.mode !== undefined && newConfig.mode !== this.config.mode) {
3999
+ requiresRebind = true;
4000
+ appliedChanges.push(`mode: ${this.config.mode} -> ${newConfig.mode}`);
4001
+ }
4002
+ if (newConfig.host !== undefined && newConfig.host !== this.config.host) {
4003
+ requiresRebind = true;
4004
+ appliedChanges.push(`host: ${this.config.host} -> ${newConfig.host}`);
4005
+ }
4006
+ if (newConfig.port !== undefined && newConfig.port !== this.config.port) {
4007
+ requiresRebind = true;
4008
+ appliedChanges.push(`port: ${this.config.port} -> ${newConfig.port}`);
4009
+ }
4010
+ // Password can be hot-reloaded (it's checked on each login)
4011
+ if (newConfig.password !== undefined && newConfig.password !== this.config.password) {
4012
+ this.config.password = newConfig.password;
4013
+ appliedChanges.push('password: (updated)');
4014
+ logger.info('Dashboard password updated via hot reload');
4015
+ }
4016
+ // Session secret can be hot-reloaded but existing sessions will be invalidated
4017
+ if (newConfig.sessionSecret !== undefined && newConfig.sessionSecret !== this.config.sessionSecret) {
4018
+ this.config.sessionSecret = newConfig.sessionSecret;
4019
+ appliedChanges.push('sessionSecret: (updated - existing sessions invalidated)');
4020
+ logger.warn('Session secret changed - all existing sessions will be invalidated on next request');
4021
+ }
4022
+ if (!requiresRebind) {
4023
+ return {
4024
+ success: true,
4025
+ message: 'Configuration updated (hot reload)',
4026
+ requiresRebind: false,
4027
+ appliedChanges
4028
+ };
4029
+ }
4030
+ // Apply config changes that will take effect on rebind
4031
+ if (newConfig.mode !== undefined)
4032
+ this.config.mode = newConfig.mode;
4033
+ if (newConfig.host !== undefined)
4034
+ this.config.host = newConfig.host;
4035
+ if (newConfig.port !== undefined)
4036
+ this.config.port = newConfig.port;
4037
+ return {
4038
+ success: true,
4039
+ message: 'Configuration updated. Server rebind required to apply binding changes.',
4040
+ requiresRebind: true,
4041
+ appliedChanges
4042
+ };
4043
+ }
4044
+ /**
4045
+ * rebind - Gracefully rebind the server to a new host/port
4046
+ *
4047
+ * This performs a graceful restart:
4048
+ * 1. Mark server as stopping
4049
+ * 2. Stop accepting new connections
4050
+ * 3. Close existing WebSocket connections with notice
4051
+ * 4. Close HTTP server
4052
+ * 5. Restart with new configuration
4053
+ *
4054
+ * @param notifyClients - Whether to notify WebSocket clients before restart
4055
+ * @returns Promise<boolean> - true if rebind successful
4056
+ */
4057
+ async rebind(notifyClients = true) {
4058
+ if (this.pendingRebind) {
4059
+ return {
4060
+ success: false,
4061
+ message: 'Rebind already in progress',
4062
+ oldBinding: { host: this.config.host, port: this.actualPort },
4063
+ newBinding: { host: this.config.host, port: this.config.port }
4064
+ };
4065
+ }
4066
+ this.pendingRebind = true;
4067
+ const oldBinding = { host: this.config.host, port: this.actualPort };
4068
+ try {
4069
+ logger.info({
4070
+ oldHost: oldBinding.host,
4071
+ oldPort: oldBinding.port,
4072
+ newHost: this.config.host,
4073
+ newPort: this.config.port,
4074
+ mode: this.config.mode
4075
+ }, 'initiating graceful server rebind');
4076
+ // Notify WebSocket clients about impending restart
4077
+ if (notifyClients && this.connectedClients.size > 0) {
4078
+ const restartNotice = JSON.stringify({
4079
+ type: 'server_restart',
4080
+ message: 'Server is restarting to apply configuration changes',
4081
+ reconnectIn: 3000
4082
+ });
4083
+ for (const client of this.connectedClients) {
4084
+ try {
4085
+ if (client.readyState === WebSocket.OPEN) {
4086
+ client.send(restartNotice);
4087
+ }
4088
+ }
4089
+ catch (err) {
4090
+ // Ignore individual client errors
4091
+ }
4092
+ }
4093
+ // Give clients time to receive the message
4094
+ await new Promise(resolve => setTimeout(resolve, 500));
4095
+ }
4096
+ // Stop the server (closes connections)
4097
+ await this.stop();
4098
+ // Re-apply host based on mode
4099
+ if (this.config.mode === 'private') {
4100
+ this.config.host = '127.0.0.1';
4101
+ }
4102
+ else if (!this.config.host || this.config.host === '127.0.0.1') {
4103
+ this.config.host = '0.0.0.0';
4104
+ }
4105
+ // Create new HTTP server instance
4106
+ this.server = createServer(this.app);
4107
+ // Re-setup WebSocket server
4108
+ this.wss = new WebSocketServer({
4109
+ noServer: true,
4110
+ perMessageDeflate: false,
4111
+ clientTracking: true,
4112
+ maxPayload: 100 * 1024 * 1024
4113
+ });
4114
+ // Re-setup upgrade handler
4115
+ this.server.on('upgrade', (request, socket, head) => {
4116
+ const url = new URL(request.url || '/', `http://${request.headers.host}`);
4117
+ const pathname = url.pathname;
4118
+ if (pathname === '/ws/team-members/live') {
4119
+ return;
4120
+ }
4121
+ this.wss.handleUpgrade(request, socket, head, (ws) => {
4122
+ this.wss.emit('connection', ws, request);
4123
+ });
4124
+ });
4125
+ // Start on new binding
4126
+ await this.start();
4127
+ const newBinding = { host: this.config.host, port: this.actualPort };
4128
+ logger.info({
4129
+ oldBinding,
4130
+ newBinding,
4131
+ mode: this.config.mode
4132
+ }, 'server rebind completed successfully');
4133
+ return {
4134
+ success: true,
4135
+ message: `Server rebound from ${oldBinding.host}:${oldBinding.port} to ${newBinding.host}:${newBinding.port}`,
4136
+ oldBinding,
4137
+ newBinding
4138
+ };
4139
+ }
4140
+ catch (err) {
4141
+ const error = err instanceof Error ? err : new Error(String(err));
4142
+ logger.error({ error, oldBinding }, 'server rebind failed - attempting rollback');
4143
+ // ROLLBACK: Try to restore server on old binding
4144
+ try {
4145
+ // Restore config to old values
4146
+ this.config.host = oldBinding.host;
4147
+ this.config.port = oldBinding.port;
4148
+ // Recreate server if needed
4149
+ if (!this.isRunning) {
4150
+ this.server = createServer(this.app);
4151
+ this.wss = new WebSocketServer({
4152
+ noServer: true,
4153
+ perMessageDeflate: false,
4154
+ clientTracking: true,
4155
+ maxPayload: 100 * 1024 * 1024
4156
+ });
4157
+ this.server.on('upgrade', (request, socket, head) => {
4158
+ const url = new URL(request.url || '/', `http://${request.headers.host}`);
4159
+ const pathname = url.pathname;
4160
+ if (pathname === '/ws/team-members/live')
4161
+ return;
4162
+ this.wss.handleUpgrade(request, socket, head, (ws) => {
4163
+ this.wss.emit('connection', ws, request);
4164
+ });
4165
+ });
4166
+ await this.start();
4167
+ logger.info({ oldBinding }, 'ROLLBACK SUCCESS: Server restored to previous binding');
4168
+ return {
4169
+ success: false,
4170
+ message: `Rebind failed: ${error.message}. Server rolled back to ${oldBinding.host}:${oldBinding.port}`,
4171
+ oldBinding,
4172
+ newBinding: oldBinding,
4173
+ rolledBack: true
4174
+ };
4175
+ }
4176
+ }
4177
+ catch (rollbackErr) {
4178
+ const rollbackError = rollbackErr instanceof Error ? rollbackErr : new Error(String(rollbackErr));
4179
+ logger.error({ error: rollbackError, oldBinding }, 'ROLLBACK FAILED: Server may be in inconsistent state');
4180
+ return {
4181
+ success: false,
4182
+ message: `Rebind failed: ${error.message}. Rollback also failed: ${rollbackError.message}. Manual restart required.`,
4183
+ oldBinding,
4184
+ newBinding: { host: this.config.host, port: this.config.port },
4185
+ rollbackFailed: true
4186
+ };
4187
+ }
4188
+ return {
4189
+ success: false,
4190
+ message: `Rebind failed: ${error.message}`,
4191
+ oldBinding,
4192
+ newBinding: { host: this.config.host, port: this.config.port }
4193
+ };
4194
+ }
4195
+ finally {
4196
+ this.pendingRebind = false;
4197
+ }
4198
+ }
4199
+ /**
4200
+ * scheduleRebind - Schedule a rebind after a delay
4201
+ *
4202
+ * Useful for giving clients time to prepare for restart
4203
+ *
4204
+ * @param delayMs - Delay before rebind in milliseconds
4205
+ * @returns Promise resolving when rebind is complete
4206
+ */
4207
+ async scheduleRebind(delayMs = 2000) {
4208
+ logger.info({ delayMs }, 'scheduling server rebind');
4209
+ // Notify connected clients
4210
+ const scheduleNotice = JSON.stringify({
4211
+ type: 'server_restart_scheduled',
4212
+ message: `Server will restart in ${delayMs}ms to apply configuration changes`,
4213
+ restartTime: Date.now() + delayMs
4214
+ });
4215
+ for (const client of this.connectedClients) {
4216
+ try {
4217
+ if (client.readyState === WebSocket.OPEN) {
4218
+ client.send(scheduleNotice);
4219
+ }
4220
+ }
4221
+ catch {
4222
+ // Ignore
4223
+ }
4224
+ }
4225
+ // Wait for delay
4226
+ await new Promise(resolve => setTimeout(resolve, delayMs));
4227
+ // Perform rebind
4228
+ const result = await this.rebind(false); // Already notified
4229
+ return {
4230
+ success: result.success,
4231
+ message: result.message
4232
+ };
4233
+ }
4234
+ /**
4235
+ * reloadConfig - Reload configuration from environment/files
4236
+ *
4237
+ * Hot-reloads what can be reloaded without restart,
4238
+ * flags what requires restart
4239
+ */
4240
+ async reloadConfig() {
4241
+ const hotReloaded = [];
4242
+ const requiresRestart = [];
4243
+ // Password can be hot-reloaded (centralized password module handles this)
4244
+ const newPassword = getPassword();
4245
+ if (newPassword !== this.config.password) {
4246
+ this.config.password = newPassword;
4247
+ hotReloaded.push('password');
4248
+ }
4249
+ // Mode changes require restart
4250
+ const newMode = getDashboardMode();
4251
+ if (newMode !== this.config.mode) {
4252
+ requiresRestart.push(`mode: ${this.config.mode} -> ${newMode}`);
4253
+ }
4254
+ // Host changes require restart
4255
+ const newHost = getDashboardHost();
4256
+ if (newHost !== this.config.host) {
4257
+ requiresRestart.push(`host: ${this.config.host} -> ${newHost}`);
4258
+ }
4259
+ // Port changes require restart
4260
+ const newPort = parseInt(process.env['SPECMEM_DASHBOARD_PORT'] || '8585', 10);
4261
+ if (newPort !== this.config.port) {
4262
+ requiresRestart.push(`port: ${this.config.port} -> ${newPort}`);
4263
+ }
4264
+ logger.info({
4265
+ hotReloaded,
4266
+ requiresRestart
4267
+ }, 'config reload check completed');
4268
+ return {
4269
+ success: true,
4270
+ hotReloaded,
4271
+ requiresRestart
4272
+ };
4273
+ }
4274
+ }
4275
+ // ============================================================================
4276
+ // Singleton Instance
4277
+ // ============================================================================
4278
+ let globalDashboard = null;
4279
+ /**
4280
+ * Get the global dashboard server
4281
+ */
4282
+ export function getDashboardServer(config) {
4283
+ if (!globalDashboard) {
4284
+ globalDashboard = new DashboardWebServer(config);
4285
+ }
4286
+ return globalDashboard;
4287
+ }
4288
+ /**
4289
+ * Reset the global dashboard server (for testing)
4290
+ */
4291
+ export async function resetDashboardServer() {
4292
+ if (globalDashboard) {
4293
+ await globalDashboard.stop();
4294
+ globalDashboard = null;
4295
+ }
4296
+ }
4297
+ //# sourceMappingURL=webServer.js.map