@crownpeak/dqm-react-component-dev-mcp 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +138 -0
  2. package/data/.env.example +22 -0
  3. package/data/.gitattributes +47 -0
  4. package/data/.glfrc.json +7 -0
  5. package/data/.husky/pre-commit +5 -0
  6. package/data/.nvmrc +1 -0
  7. package/data/CHANGELOG.md +75 -0
  8. package/data/CODE_OF_CONDUCT.md +129 -0
  9. package/data/CONTRIBUTING.md +203 -0
  10. package/data/DOCS-STRUCTURE.md +307 -0
  11. package/data/I18N.md +292 -0
  12. package/data/LICENSE +22 -0
  13. package/data/README.md +315 -0
  14. package/data/SECURITY.md +125 -0
  15. package/data/WIKI-DEPLOYMENT.md +348 -0
  16. package/data/docs/AI-FEATURES.md +610 -0
  17. package/data/docs/API-REFERENCE.md +1022 -0
  18. package/data/docs/AUTHENTICATION.md +301 -0
  19. package/data/docs/BACKEND-API.md +468 -0
  20. package/data/docs/DEVELOPMENT.md +375 -0
  21. package/data/docs/EXAMPLES.md +622 -0
  22. package/data/docs/MCP-SERVER.md +307 -0
  23. package/data/docs/MIGRATION-GUIDE.md +367 -0
  24. package/data/docs/NPM-PUBLISH.md +193 -0
  25. package/data/docs/QUICKSTART.md +206 -0
  26. package/data/docs/REDIS-SETUP.md +162 -0
  27. package/data/docs/SERVER.md +228 -0
  28. package/data/docs/TROUBLESHOOTING.md +657 -0
  29. package/data/docs/WIDGET-GUIDE.md +638 -0
  30. package/data/docs/WIKI-HOME.md +58 -0
  31. package/data/docs/WIKI-SIDEBAR.md +39 -0
  32. package/data/package.json +171 -0
  33. package/data/playwright.config.ts +64 -0
  34. package/data/probe/.cargo/config.toml +10 -0
  35. package/data/probe/.claude/commands/performance-review.md +15 -0
  36. package/data/probe/.clinerules +288 -0
  37. package/data/probe/.dockerignore +57 -0
  38. package/data/probe/.githooks/post-commit +11 -0
  39. package/data/probe/.githooks/pre-commit +99 -0
  40. package/data/probe/.githooks/pre-commit-vow +9 -0
  41. package/data/probe/.prompts/engineer.md +41 -0
  42. package/data/probe/.roomodes +28 -0
  43. package/data/probe/.windsurfrules +0 -0
  44. package/data/probe/BASH_TOOL_SUMMARY.md +148 -0
  45. package/data/probe/BENCHMARKING.md +256 -0
  46. package/data/probe/CLAUDE.md +226 -0
  47. package/data/probe/CODE_OF_CONDUCT.md +128 -0
  48. package/data/probe/CONTRIBUTING.md +193 -0
  49. package/data/probe/Cargo.toml +120 -0
  50. package/data/probe/Cross.toml +10 -0
  51. package/data/probe/DOCKER-README.md +224 -0
  52. package/data/probe/Dockerfile +32 -0
  53. package/data/probe/ENHANCED_DEBUG_TELEMETRY.md +188 -0
  54. package/data/probe/LICENSE +201 -0
  55. package/data/probe/Makefile +210 -0
  56. package/data/probe/README.md +824 -0
  57. package/data/probe/SECURITY.md +67 -0
  58. package/data/probe/WINDOWS-GUIDE.md +294 -0
  59. package/data/probe/benches/parsing_benchmarks.rs +370 -0
  60. package/data/probe/benches/search_benchmarks.rs +599 -0
  61. package/data/probe/benches/simd_benchmarks.rs +372 -0
  62. package/data/probe/benches/timing_benchmarks.rs +287 -0
  63. package/data/probe/build-windows.bat +229 -0
  64. package/data/probe/codex-config/config.toml +6 -0
  65. package/data/probe/docs/PERFORMANCE_OPTIMIZATION.md +161 -0
  66. package/data/probe/examples/cache_demo.rs +46 -0
  67. package/data/probe/examples/chat/.dockerignore +37 -0
  68. package/data/probe/examples/chat/ChatSessionManager.js +295 -0
  69. package/data/probe/examples/chat/Dockerfile +98 -0
  70. package/data/probe/examples/chat/LICENSE +201 -0
  71. package/data/probe/examples/chat/LOCAL_IMAGE_SUPPORT.md +195 -0
  72. package/data/probe/examples/chat/MCP_INTEGRATION.md +400 -0
  73. package/data/probe/examples/chat/README.md +338 -0
  74. package/data/probe/examples/chat/TRACING.md +226 -0
  75. package/data/probe/examples/chat/appTracer.js +968 -0
  76. package/data/probe/examples/chat/auth.js +76 -0
  77. package/data/probe/examples/chat/bin/probe-chat.js +13 -0
  78. package/data/probe/examples/chat/build.js +104 -0
  79. package/data/probe/examples/chat/cancelRequest.js +84 -0
  80. package/data/probe/examples/chat/demo-agentic-image-flow.js +88 -0
  81. package/data/probe/examples/chat/demo-local-images.js +128 -0
  82. package/data/probe/examples/chat/fileSpanExporter.js +181 -0
  83. package/data/probe/examples/chat/implement/README.md +228 -0
  84. package/data/probe/examples/chat/implement/backends/AiderBackend.js +750 -0
  85. package/data/probe/examples/chat/implement/backends/BaseBackend.js +276 -0
  86. package/data/probe/examples/chat/implement/backends/ClaudeCodeBackend.js +767 -0
  87. package/data/probe/examples/chat/implement/backends/MockBackend.js +237 -0
  88. package/data/probe/examples/chat/implement/backends/registry.js +85 -0
  89. package/data/probe/examples/chat/implement/core/BackendManager.js +567 -0
  90. package/data/probe/examples/chat/implement/core/ImplementTool.js +354 -0
  91. package/data/probe/examples/chat/implement/core/config.js +428 -0
  92. package/data/probe/examples/chat/implement/core/timeouts.js +58 -0
  93. package/data/probe/examples/chat/implement/core/utils.js +496 -0
  94. package/data/probe/examples/chat/implement/types/BackendTypes.js +126 -0
  95. package/data/probe/examples/chat/index.js +669 -0
  96. package/data/probe/examples/chat/mcpServer.js +341 -0
  97. package/data/probe/examples/chat/npm/LICENSE +15 -0
  98. package/data/probe/examples/chat/npm/README.md +168 -0
  99. package/data/probe/examples/chat/npm/bin/probe-chat.js +156 -0
  100. package/data/probe/examples/chat/npm/index.js +259 -0
  101. package/data/probe/examples/chat/npm/package.json +54 -0
  102. package/data/probe/examples/chat/package.json +102 -0
  103. package/data/probe/examples/chat/probeChat.js +456 -0
  104. package/data/probe/examples/chat/probeTool.js +491 -0
  105. package/data/probe/examples/chat/storage/JsonChatStorage.js +476 -0
  106. package/data/probe/examples/chat/telemetry.js +281 -0
  107. package/data/probe/examples/chat/test/integration/chatFlows.test.js +320 -0
  108. package/data/probe/examples/chat/test/integration/toolCalling.test.js +471 -0
  109. package/data/probe/examples/chat/test/mocks/mockLLMProvider.js +269 -0
  110. package/data/probe/examples/chat/test/test-backends.js +90 -0
  111. package/data/probe/examples/chat/test/testUtils.js +530 -0
  112. package/data/probe/examples/chat/test/unit/backendTimeout.test.js +161 -0
  113. package/data/probe/examples/chat/test/unit/packageFiles.test.js +120 -0
  114. package/data/probe/examples/chat/test/verify-tests.js +118 -0
  115. package/data/probe/examples/chat/test-agentic-image-loading.js +294 -0
  116. package/data/probe/examples/chat/test-ai-sdk-telemetry.js +204 -0
  117. package/data/probe/examples/chat/test-chat-tracing.js +38 -0
  118. package/data/probe/examples/chat/test-direct-function.js +49 -0
  119. package/data/probe/examples/chat/test-file-size-validation.js +103 -0
  120. package/data/probe/examples/chat/test-full-mcp-integration.js +258 -0
  121. package/data/probe/examples/chat/test-github-context.txt +12 -0
  122. package/data/probe/examples/chat/test-hierarchy.js +203 -0
  123. package/data/probe/examples/chat/test-image-spans.js +37 -0
  124. package/data/probe/examples/chat/test-local-image-reading.js +176 -0
  125. package/data/probe/examples/chat/test-mcp-integration.js +136 -0
  126. package/data/probe/examples/chat/test-mcp-probe-server.js +161 -0
  127. package/data/probe/examples/chat/test-mcp-with-ai.js +279 -0
  128. package/data/probe/examples/chat/test-multiple-allowed-dirs.js +111 -0
  129. package/data/probe/examples/chat/test-probe-mcp-server.js +110 -0
  130. package/data/probe/examples/chat/test-security-validation.js +145 -0
  131. package/data/probe/examples/chat/test-simple-tracing.js +32 -0
  132. package/data/probe/examples/chat/test-trace-verification.js +235 -0
  133. package/data/probe/examples/chat/test-tracing.js +114 -0
  134. package/data/probe/examples/chat/tokenCounter.js +419 -0
  135. package/data/probe/examples/chat/tokenUsageDisplay.js +134 -0
  136. package/data/probe/examples/chat/webServer.js +1103 -0
  137. package/data/probe/examples/reranker/Cargo.toml +33 -0
  138. package/data/probe/examples/reranker/DEBUG_OUTPUT_ANALYSIS.md +71 -0
  139. package/data/probe/examples/reranker/MODELS.md +66 -0
  140. package/data/probe/examples/reranker/MODEL_COMPARISON.md +60 -0
  141. package/data/probe/examples/reranker/MULTI_MODEL_ANALYSIS.md +176 -0
  142. package/data/probe/examples/reranker/PERFORMANCE_SUMMARY.md +156 -0
  143. package/data/probe/examples/reranker/README.md +347 -0
  144. package/data/probe/examples/reranker/RUST_BERT_COMPARISON.md +82 -0
  145. package/data/probe/examples/reranker/TOKENIZATION_GUIDE.md +120 -0
  146. package/data/probe/examples/reranker/check_rust_tokenizer.py +108 -0
  147. package/data/probe/examples/reranker/convert_to_torchscript.py +109 -0
  148. package/data/probe/examples/reranker/debug_scoring.py +189 -0
  149. package/data/probe/examples/reranker/debug_tokenization.py +154 -0
  150. package/data/probe/examples/reranker/download_models.sh +73 -0
  151. package/data/probe/examples/reranker/requirements.txt +13 -0
  152. package/data/probe/examples/reranker/run_comprehensive_benchmark.sh +83 -0
  153. package/data/probe/examples/reranker/rust_bert_test/Cargo.toml +12 -0
  154. package/data/probe/examples/reranker/rust_bert_test/README.md +54 -0
  155. package/data/probe/examples/reranker/simple_test.py +50 -0
  156. package/data/probe/examples/reranker/test_all_models.sh +63 -0
  157. package/data/probe/examples/reranker/test_bert_results.sh +44 -0
  158. package/data/probe/examples/reranker/test_cross_encoder.py +334 -0
  159. package/data/probe/examples/reranker/test_cross_encoder.sh +80 -0
  160. package/data/probe/examples/reranker/test_exact_comparison.py +151 -0
  161. package/data/probe/examples/reranker/test_parallel_performance.sh +56 -0
  162. package/data/probe/examples/reranker/test_scores.py +132 -0
  163. package/data/probe/install.ps1 +508 -0
  164. package/data/probe/install.sh +460 -0
  165. package/data/probe/npm/CLONE_METHOD_EXAMPLES.md +596 -0
  166. package/data/probe/npm/CONTEXT_COMPACTION.md +303 -0
  167. package/data/probe/npm/DELEGATE_TOOL_README.md +166 -0
  168. package/data/probe/npm/MAID_INTEGRATION.md +313 -0
  169. package/data/probe/npm/MCP_INTEGRATION_SUMMARY.md +241 -0
  170. package/data/probe/npm/README.md +824 -0
  171. package/data/probe/npm/bin/.gitignore +7 -0
  172. package/data/probe/npm/bin/.gitkeep +0 -0
  173. package/data/probe/npm/bin/README.md +12 -0
  174. package/data/probe/npm/bin/probe +167 -0
  175. package/data/probe/npm/docs/CLAUDE_CODE_INTEGRATION.md +414 -0
  176. package/data/probe/npm/docs/CODEX_INTEGRATION.md +502 -0
  177. package/data/probe/npm/docs/EDIT_CREATE_TOOLS.md +233 -0
  178. package/data/probe/npm/docs/RETRY_AND_FALLBACK.md +674 -0
  179. package/data/probe/npm/example-usage.js +335 -0
  180. package/data/probe/npm/examples/multi-engine-demo.js +117 -0
  181. package/data/probe/npm/examples/probe-agent-cli.js +113 -0
  182. package/data/probe/npm/examples/test-agent-edit.js +114 -0
  183. package/data/probe/npm/examples/test-edit-create.js +120 -0
  184. package/data/probe/npm/examples/test-edit-direct.js +114 -0
  185. package/data/probe/npm/index.d.ts +744 -0
  186. package/data/probe/npm/jest.config.js +52 -0
  187. package/data/probe/npm/package.json +117 -0
  188. package/data/probe/npm/scripts/build-agent.cjs +75 -0
  189. package/data/probe/npm/scripts/build-cjs.js +124 -0
  190. package/data/probe/npm/scripts/build-mcp.cjs +36 -0
  191. package/data/probe/npm/scripts/postinstall.js +216 -0
  192. package/data/probe/npm/test-codex-e2e.js +78 -0
  193. package/data/probe/npm/test-download-lock.js +109 -0
  194. package/data/probe/npm/test-grep-security.js +94 -0
  195. package/data/probe/npm/test-grep-simplified.js +63 -0
  196. package/data/probe/npm/test-grep.js +51 -0
  197. package/data/probe/npm/tests/README.md +96 -0
  198. package/data/probe/npm/tests/agent-compact-history.test.js +174 -0
  199. package/data/probe/npm/tests/allow-tests-default.test.js +151 -0
  200. package/data/probe/npm/tests/contextCompactor.test.js +498 -0
  201. package/data/probe/npm/tests/delegate-config.test.js +353 -0
  202. package/data/probe/npm/tests/delegate-integration.test.js +348 -0
  203. package/data/probe/npm/tests/extractor-integration.test.js +162 -0
  204. package/data/probe/npm/tests/extractor.test.js +317 -0
  205. package/data/probe/npm/tests/fixtures/sampleDiagrams.js +267 -0
  206. package/data/probe/npm/tests/integration/claude-code-auto-fallback.spec.js +148 -0
  207. package/data/probe/npm/tests/integration/claude-code-multi-step.spec.js +127 -0
  208. package/data/probe/npm/tests/integration/claude-code-tool-events.spec.js +163 -0
  209. package/data/probe/npm/tests/integration/codex-auto-fallback.spec.js +191 -0
  210. package/data/probe/npm/tests/integration/codex-tool-events.spec.js +147 -0
  211. package/data/probe/npm/tests/integration/examplesChatMcp.test.js +402 -0
  212. package/data/probe/npm/tests/integration/mcpDotenvSupport.test.js +174 -0
  213. package/data/probe/npm/tests/integration/mcpErrorHandling.test.js +566 -0
  214. package/data/probe/npm/tests/integration/mcpRobustness.test.js +564 -0
  215. package/data/probe/npm/tests/integration/mcpStdoutPurity.test.js +355 -0
  216. package/data/probe/npm/tests/integration/probeAgentMcp.test.js +398 -0
  217. package/data/probe/npm/tests/integration/retryFallback.test.js +368 -0
  218. package/data/probe/npm/tests/integration/schema-in-initial-message.test.js +318 -0
  219. package/data/probe/npm/tests/integration/schema-validation-loop-prevention.test.js +244 -0
  220. package/data/probe/npm/tests/integration/schemaRetryLogic.test.js +94 -0
  221. package/data/probe/npm/tests/integration/validationFlow.test.js +329 -0
  222. package/data/probe/npm/tests/manual/test-codex-basic.js +110 -0
  223. package/data/probe/npm/tests/mcp/mcpClientManager.test.js +614 -0
  224. package/data/probe/npm/tests/mcp/mcpConfig.test.js +359 -0
  225. package/data/probe/npm/tests/mcp/mcpXmlBridge.test.js +436 -0
  226. package/data/probe/npm/tests/mcp/mockMcpServer.js +510 -0
  227. package/data/probe/npm/tests/mcp-strict-syntax.test.js +319 -0
  228. package/data/probe/npm/tests/mermaidQuoteEscaping.test.js +214 -0
  229. package/data/probe/npm/tests/nestedQuoteFix.test.js +40 -0
  230. package/data/probe/npm/tests/setup.js +46 -0
  231. package/data/probe/npm/tests/unit/allowed-tools.test.js +513 -0
  232. package/data/probe/npm/tests/unit/attempt-completion-closing-tag-in-content.test.js +188 -0
  233. package/data/probe/npm/tests/unit/attemptCompletionJsonFix.test.js +238 -0
  234. package/data/probe/npm/tests/unit/attemptCompletionJsonIssue.test.js +128 -0
  235. package/data/probe/npm/tests/unit/backtickAutoFix.test.js +35 -0
  236. package/data/probe/npm/tests/unit/bash-probe-agent-integration.test.js +389 -0
  237. package/data/probe/npm/tests/unit/bash-simple-commands.test.js +324 -0
  238. package/data/probe/npm/tests/unit/bash-tool-comprehensive.test.js +371 -0
  239. package/data/probe/npm/tests/unit/bash-tool-integration.test.js +310 -0
  240. package/data/probe/npm/tests/unit/bash-tool.test.js +341 -0
  241. package/data/probe/npm/tests/unit/completion-prompt.test.js +379 -0
  242. package/data/probe/npm/tests/unit/cwd-path-options.test.js +287 -0
  243. package/data/probe/npm/tests/unit/delegate-limits.test.js +422 -0
  244. package/data/probe/npm/tests/unit/direct-content-attempt-completion.test.js +235 -0
  245. package/data/probe/npm/tests/unit/edit-create-tools.test.js +609 -0
  246. package/data/probe/npm/tests/unit/enhancedMermaidValidation.test.js +577 -0
  247. package/data/probe/npm/tests/unit/extract-content.test.js +83 -0
  248. package/data/probe/npm/tests/unit/extract-multiple-targets.test.js +89 -0
  249. package/data/probe/npm/tests/unit/fallbackManager.test.js +442 -0
  250. package/data/probe/npm/tests/unit/githubCompatibilityValidation.test.js +258 -0
  251. package/data/probe/npm/tests/unit/imageConfig.test.js +149 -0
  252. package/data/probe/npm/tests/unit/imagePathResolution.test.js +345 -0
  253. package/data/probe/npm/tests/unit/json-fixing-agent.test.js +238 -0
  254. package/data/probe/npm/tests/unit/json-validation-enhanced-errors.test.js +199 -0
  255. package/data/probe/npm/tests/unit/jsonValidationInfiniteLoopFix.test.js +228 -0
  256. package/data/probe/npm/tests/unit/maidIntegration.test.js +139 -0
  257. package/data/probe/npm/tests/unit/maxIterationsWarning.test.js +195 -0
  258. package/data/probe/npm/tests/unit/mermaidEdgeLabelFix.test.js +161 -0
  259. package/data/probe/npm/tests/unit/mermaidHtmlEntities.test.js +76 -0
  260. package/data/probe/npm/tests/unit/mermaidInfiniteLoopFix.test.js +64 -0
  261. package/data/probe/npm/tests/unit/mermaidValidation.test.js +723 -0
  262. package/data/probe/npm/tests/unit/mermaidValidationVisorExample.test.js +309 -0
  263. package/data/probe/npm/tests/unit/probe-agent-clone-realistic.test.js +643 -0
  264. package/data/probe/npm/tests/unit/probe-agent-clone.test.js +476 -0
  265. package/data/probe/npm/tests/unit/probe-agent-delegate.test.js +400 -0
  266. package/data/probe/npm/tests/unit/probe-agent-model-option.test.js +118 -0
  267. package/data/probe/npm/tests/unit/probeTool-security.test.js +283 -0
  268. package/data/probe/npm/tests/unit/readImageTool.test.js +418 -0
  269. package/data/probe/npm/tests/unit/retryManager.test.js +317 -0
  270. package/data/probe/npm/tests/unit/schema-aware-reminders.test.js +288 -0
  271. package/data/probe/npm/tests/unit/schemaDefinitionDetection.test.js +115 -0
  272. package/data/probe/npm/tests/unit/schemaUtils.test.js +1268 -0
  273. package/data/probe/npm/tests/unit/simpleTelemetry.test.js +282 -0
  274. package/data/probe/npm/tests/unit/simplified-attempt-completion.test.js +274 -0
  275. package/data/probe/npm/tests/unit/single-quote-json-bug.test.js +231 -0
  276. package/data/probe/npm/tests/unit/subgraphAutoFix.test.js +110 -0
  277. package/data/probe/npm/tests/unit/system-prompt.test.js +32 -0
  278. package/data/probe/npm/tests/unit/types-probe-agent-options.test.js +42 -0
  279. package/data/probe/npm/tests/unit/xmlParsing.test.js +720 -0
  280. package/data/probe/npm/tsconfig.json +21 -0
  281. package/data/probe/result1.txt +19 -0
  282. package/data/probe/result2.txt +26 -0
  283. package/data/probe/scripts/benchmark.sh +270 -0
  284. package/data/probe/scripts/cache_memory_analysis.rs +844 -0
  285. package/data/probe/scripts/claude-hook-wrapper.sh +56 -0
  286. package/data/probe/site/.env.example +10 -0
  287. package/data/probe/site/DEPLOYMENT.md +86 -0
  288. package/data/probe/site/README.md +183 -0
  289. package/data/probe/site/adding-languages.md +135 -0
  290. package/data/probe/site/ai-chat.md +427 -0
  291. package/data/probe/site/ai-integration.md +1488 -0
  292. package/data/probe/site/blog/agentic-flow-custom-xml-protocol.md +407 -0
  293. package/data/probe/site/blog/index.md +118 -0
  294. package/data/probe/site/blog/v0.6.0-release.md +426 -0
  295. package/data/probe/site/blog.md +8 -0
  296. package/data/probe/site/changelog.md +200 -0
  297. package/data/probe/site/cli-mode.md +437 -0
  298. package/data/probe/site/code-extraction.md +436 -0
  299. package/data/probe/site/contributing/README.md +9 -0
  300. package/data/probe/site/contributing/documentation-cross-references.md +215 -0
  301. package/data/probe/site/contributing/documentation-maintenance.md +275 -0
  302. package/data/probe/site/contributing/documentation-structure.md +75 -0
  303. package/data/probe/site/documentation-cross-references.md +215 -0
  304. package/data/probe/site/documentation-guide.md +132 -0
  305. package/data/probe/site/documentation-maintenance.md +275 -0
  306. package/data/probe/site/features.md +147 -0
  307. package/data/probe/site/how-it-works.md +118 -0
  308. package/data/probe/site/index.md +175 -0
  309. package/data/probe/site/index.md.bak +133 -0
  310. package/data/probe/site/installation.md +235 -0
  311. package/data/probe/site/integrations/docker.md +248 -0
  312. package/data/probe/site/integrations/github-actions.md +413 -0
  313. package/data/probe/site/language-support-overview.md +168 -0
  314. package/data/probe/site/mcp-integration.md +587 -0
  315. package/data/probe/site/mcp-server.md +304 -0
  316. package/data/probe/site/navigation-structure.md +76 -0
  317. package/data/probe/site/nodejs-sdk.md +798 -0
  318. package/data/probe/site/output-formats.md +625 -0
  319. package/data/probe/site/package.json +21 -0
  320. package/data/probe/site/public/_headers +28 -0
  321. package/data/probe/site/public/_redirects +11 -0
  322. package/data/probe/site/quick-start.md +289 -0
  323. package/data/probe/site/search-functionality.md +291 -0
  324. package/data/probe/site/search-reference.md +291 -0
  325. package/data/probe/site/supported-languages.md +215 -0
  326. package/data/probe/site/use-cases/README.md +8 -0
  327. package/data/probe/site/use-cases/advanced-cli.md +253 -0
  328. package/data/probe/site/use-cases/ai-code-editors.md +239 -0
  329. package/data/probe/site/use-cases/building-ai-tools.md +529 -0
  330. package/data/probe/site/use-cases/cli-ai-workflows.md +285 -0
  331. package/data/probe/site/use-cases/deploying-probe-web-interface.md +255 -0
  332. package/data/probe/site/use-cases/integrating-probe-into-ai-code-editors.md +161 -0
  333. package/data/probe/site/use-cases/nodejs-sdk.md +596 -0
  334. package/data/probe/site/use-cases/team-chat.md +350 -0
  335. package/data/probe/site/web-interface.md +434 -0
  336. package/data/probe/site/wrangler.toml +9 -0
  337. package/data/probe/test-api-key.sh +1 -0
  338. package/data/probe/test-probe-implementation/hello.js +7 -0
  339. package/data/probe/test_cases/demonstrate_early_termination_issues.sh +176 -0
  340. package/data/probe/test_cases/early_termination_issues.rs +533 -0
  341. package/data/probe/test_data/test_nested_struct.go +26 -0
  342. package/data/probe/tests/README.md +286 -0
  343. package/data/probe/tests/README_search_determinism_tests.md +116 -0
  344. package/data/probe/tests/adjacent_comment_test.rs +152 -0
  345. package/data/probe/tests/apostrophe_handling_tests.rs +132 -0
  346. package/data/probe/tests/block_filtering_with_ast_tests.rs +669 -0
  347. package/data/probe/tests/block_merging_tests.rs +396 -0
  348. package/data/probe/tests/c_outline_format_tests.rs +2179 -0
  349. package/data/probe/tests/cache_invalidation_issues.rs.disabled +682 -0
  350. package/data/probe/tests/cache_order_tests.rs +147 -0
  351. package/data/probe/tests/cache_query_scoping_tests.rs +221 -0
  352. package/data/probe/tests/cli_tests.rs +680 -0
  353. package/data/probe/tests/comment_context_integration_test.rs +240 -0
  354. package/data/probe/tests/common.rs +33 -0
  355. package/data/probe/tests/complex_block_merging_tests.rs +599 -0
  356. package/data/probe/tests/complex_query_block_filtering_tests.rs +422 -0
  357. package/data/probe/tests/control_flow_closing_braces_test.rs +91 -0
  358. package/data/probe/tests/cpp_outline_format_tests.rs +1507 -0
  359. package/data/probe/tests/csharp_outline_format_tests.rs +941 -0
  360. package/data/probe/tests/elastic_query_integration_tests.rs +922 -0
  361. package/data/probe/tests/extract_command_tests.rs +1848 -0
  362. package/data/probe/tests/extract_deduplication_tests.rs +146 -0
  363. package/data/probe/tests/extract_input_file_tests.rs +84 -0
  364. package/data/probe/tests/extract_prompt_tests.rs +102 -0
  365. package/data/probe/tests/filename_search_tests.rs +96 -0
  366. package/data/probe/tests/fixtures/user/AssemblyInfo.cs +3 -0
  367. package/data/probe/tests/github_extract_tests.rs +234 -0
  368. package/data/probe/tests/go_comment_test.rs +253 -0
  369. package/data/probe/tests/go_outline_format_tests.rs +2587 -0
  370. package/data/probe/tests/go_path_resolver_tests.rs +96 -0
  371. package/data/probe/tests/html_outline_format_tests.rs +637 -0
  372. package/data/probe/tests/integration_tests.rs +837 -0
  373. package/data/probe/tests/ip_whitelist_test.rs +148 -0
  374. package/data/probe/tests/java_outline_format_tests.rs +1611 -0
  375. package/data/probe/tests/javascript_extract_tests.rs +315 -0
  376. package/data/probe/tests/javascript_outline_format_tests.rs +1464 -0
  377. package/data/probe/tests/json_format_tests.rs +436 -0
  378. package/data/probe/tests/json_schema_validation_tests.rs +450 -0
  379. package/data/probe/tests/lib_usage.rs +60 -0
  380. package/data/probe/tests/line_comment_context_extension_test.rs +459 -0
  381. package/data/probe/tests/line_map_cache_tests.rs +114 -0
  382. package/data/probe/tests/markdown_integration_tests.rs +190 -0
  383. package/data/probe/tests/mocks/test_ip_whitelist.go +11 -0
  384. package/data/probe/tests/mocks/test_object.js +27 -0
  385. package/data/probe/tests/mocks/test_struct.go +50 -0
  386. package/data/probe/tests/multi_keyword_pattern_tests.rs +464 -0
  387. package/data/probe/tests/multi_language_syntax_integration_tests.rs +218 -0
  388. package/data/probe/tests/multiple_capture_groups_tests.rs +169 -0
  389. package/data/probe/tests/negative_compound_word_tests.rs +246 -0
  390. package/data/probe/tests/nested_symbol_extraction_tests.rs +99 -0
  391. package/data/probe/tests/outline_cross_file_interference_test.rs +335 -0
  392. package/data/probe/tests/outline_keyword_preservation_test.rs +67 -0
  393. package/data/probe/tests/output_format_edge_cases_tests.rs +693 -0
  394. package/data/probe/tests/parallel_extraction_tests.rs +178 -0
  395. package/data/probe/tests/parallel_search_tests.rs +355 -0
  396. package/data/probe/tests/path_resolver_tests.rs +698 -0
  397. package/data/probe/tests/php_outline_format_extended_tests.rs +928 -0
  398. package/data/probe/tests/php_outline_format_tests.rs +768 -0
  399. package/data/probe/tests/property_tests.proptest-regressions +9 -0
  400. package/data/probe/tests/property_tests.rs +118 -0
  401. package/data/probe/tests/python_outline_format_tests.rs +1538 -0
  402. package/data/probe/tests/query_command_json_tests.rs +438 -0
  403. package/data/probe/tests/query_command_tests.rs +232 -0
  404. package/data/probe/tests/query_command_xml_tests.rs +569 -0
  405. package/data/probe/tests/quoted_term_with_negative_keyword_tests.rs +216 -0
  406. package/data/probe/tests/required_terms_filename_tests.rs +116 -0
  407. package/data/probe/tests/ruby_outline_format_tests.rs +1011 -0
  408. package/data/probe/tests/rust_line_comment_context_test.rs +151 -0
  409. package/data/probe/tests/rust_outline_format_enhanced_tests.rs +725 -0
  410. package/data/probe/tests/rust_outline_format_tests.rs +843 -0
  411. package/data/probe/tests/schemas/xml_output_schema.xsd +38 -0
  412. package/data/probe/tests/search_determinism_tests.rs +451 -0
  413. package/data/probe/tests/search_hints_tests.rs +253 -0
  414. package/data/probe/tests/special_character_escaping_tests.rs +417 -0
  415. package/data/probe/tests/stemming_compound_word_filtering_tests.rs +535 -0
  416. package/data/probe/tests/strict_elastic_syntax_tests.rs +404 -0
  417. package/data/probe/tests/swift_outline_format_tests.rs +3319 -0
  418. package/data/probe/tests/symbols_tests.rs +166 -0
  419. package/data/probe/tests/test_file.rs +45 -0
  420. package/data/probe/tests/test_tokenize.rs +28 -0
  421. package/data/probe/tests/timeout_tests.rs +82 -0
  422. package/data/probe/tests/tokenization_tests.rs +195 -0
  423. package/data/probe/tests/tokenized_block_filtering_tests.rs +174 -0
  424. package/data/probe/tests/typescript_extract_tests.rs +214 -0
  425. package/data/probe/tests/typescript_outline_format_tests.rs +2188 -0
  426. package/data/probe/tests/xml_format_tests.rs +568 -0
  427. package/data/probe/tests/xml_schema_validation_tests.rs +497 -0
  428. package/data/scripts/postinstall.mjs +9 -0
  429. package/data/scripts/set-version.js +0 -0
  430. package/data/scripts/wiki-build.sh +111 -0
  431. package/data/scripts/wiki-deploy.sh +73 -0
  432. package/data/serve.json +12 -0
  433. package/data/test/demo-dynamic.html +134 -0
  434. package/data/test/demo-esm.html +105 -0
  435. package/data/test/demo-iife.html +78 -0
  436. package/data/tsconfig.json +7 -0
  437. package/data/vite.server.ts +483 -0
  438. package/data/vitest.config.ts +40 -0
  439. package/data/wiki/Home.md +58 -0
  440. package/data/wiki/_Sidebar.md +39 -0
  441. package/docs-mcp.config.json +20 -0
  442. package/package.json +56 -0
  443. package/src/config.js +111 -0
  444. package/src/index.js +395 -0
@@ -0,0 +1,1103 @@
1
+ import 'dotenv/config';
2
+ import { createServer } from 'http';
3
+ // import { streamText } from 'ai'; // streamText might not be suitable for the loop logic directly
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { resolve, dirname, join } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { randomUUID } from 'crypto';
8
+ import { ChatSessionManager } from './ChatSessionManager.js';
9
+ import { TokenUsageDisplay } from './tokenUsageDisplay.js';
10
+ import { authMiddleware, withAuth } from './auth.js';
11
+ import {
12
+ // probeTool, // This is the compatibility layer, less critical now
13
+ searchToolInstance, // Keep direct instances for API endpoints
14
+ queryToolInstance,
15
+ extractToolInstance,
16
+ implementToolInstance,
17
+ toolCallEmitter,
18
+ cancelToolExecutions,
19
+ clearToolExecutionData,
20
+ isSessionCancelled
21
+ } from './probeTool.js';
22
+ import { registerRequest, cancelRequest, clearRequest, isRequestActive } from './cancelRequest.js';
23
+ import { JsonChatStorage } from './storage/JsonChatStorage.js';
24
+
25
+ // Get the directory name of the current module
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+
28
+ // Global storage instance - will be initialized when web server starts
29
+ let globalStorage = null;
30
+
31
+ // Map to store chat instances by session ID
32
+ const chatSessions = new Map();
33
+
34
+ /**
35
+ * Retrieve or create a ChatSessionManager instance keyed by sessionId.
36
+ */
37
+ function getOrCreateChat(sessionId, apiCredentials = null) {
38
+ if (!sessionId) {
39
+ // Safety fallback: generate a random ID if missing
40
+ sessionId = randomUUID(); // Use crypto.randomUUID() if available/preferred
41
+ console.warn(`[WARN] Missing sessionId, generated fallback: ${sessionId}`);
42
+ }
43
+
44
+ // Check in-memory cache first
45
+ if (chatSessions.has(sessionId)) {
46
+ const existingChat = chatSessions.get(sessionId);
47
+ // Update activity timestamp in persistent storage
48
+ if (globalStorage) {
49
+ globalStorage.updateSessionActivity(sessionId).catch(err => {
50
+ console.error('Failed to update session activity:', err);
51
+ });
52
+ }
53
+ return existingChat;
54
+ }
55
+
56
+ // Create options object with sessionId and API credentials if provided
57
+ const options = {
58
+ sessionId,
59
+ storage: globalStorage,
60
+ debug: process.env.DEBUG_CHAT === '1'
61
+ };
62
+
63
+ if (apiCredentials) {
64
+ options.apiProvider = apiCredentials.apiProvider;
65
+ options.apiKey = apiCredentials.apiKey;
66
+ options.apiUrl = apiCredentials.apiUrl;
67
+ }
68
+
69
+ const newChat = new ChatSessionManager(options);
70
+
71
+ // Store in memory cache
72
+ chatSessions.set(sessionId, newChat);
73
+
74
+ // Save session metadata to persistent storage
75
+ if (globalStorage) {
76
+ globalStorage.saveSession({
77
+ id: sessionId,
78
+ createdAt: newChat.createdAt,
79
+ lastActivity: newChat.lastActivity,
80
+ firstMessagePreview: null, // Will be updated when first message is sent
81
+ metadata: {
82
+ apiProvider: apiCredentials?.apiProvider || null
83
+ }
84
+ }).catch(err => {
85
+ console.error('Failed to save session to persistent storage:', err);
86
+ });
87
+ }
88
+
89
+ if (process.env.DEBUG_CHAT === '1') {
90
+ console.log(`[DEBUG] Created and stored new ChatSessionManager instance for session: ${sessionId}. Total sessions: ${chatSessions.size}`);
91
+ if (apiCredentials && apiCredentials.apiKey) {
92
+ console.log(`[DEBUG] Chat instance created with client-provided API credentials (provider: ${apiCredentials.apiProvider})`);
93
+ }
94
+ }
95
+ return newChat;
96
+ }
97
+
98
+ /**
99
+ * Start the web server
100
+ * @param {string} version - The version of the application
101
+ * @param {boolean} hasApiKeys - Whether any API keys are configured
102
+ * @param {Object} options - Additional options
103
+ * @param {boolean} options.allowEdit - Whether to allow editing files via the implement tool
104
+ */
105
+ export async function startWebServer(version, hasApiKeys = true, options = {}) {
106
+ const allowEdit = options?.allowEdit || false;
107
+
108
+ if (allowEdit) {
109
+ console.log('Edit mode enabled: implement tool is available');
110
+ }
111
+ // Authentication configuration
112
+ const AUTH_ENABLED = process.env.AUTH_ENABLED === '1';
113
+ const AUTH_USERNAME = process.env.AUTH_USERNAME || 'admin';
114
+ const AUTH_PASSWORD = process.env.AUTH_PASSWORD || 'password';
115
+
116
+ if (AUTH_ENABLED) {
117
+ console.log(`Authentication enabled (username: ${AUTH_USERNAME})`);
118
+ } else {
119
+ console.log('Authentication disabled');
120
+ }
121
+
122
+ // Initialize persistent storage for web mode
123
+ globalStorage = new JsonChatStorage({
124
+ webMode: true,
125
+ verbose: process.env.DEBUG_CHAT === '1'
126
+ });
127
+
128
+ // Initialize storage synchronously before server starts
129
+ try {
130
+ await globalStorage.initialize();
131
+ const stats = await globalStorage.getStats();
132
+ console.log(`Chat history storage: ${stats.storage_type} (${stats.session_count} sessions, ${stats.visible_message_count} messages)`);
133
+ } catch (error) {
134
+ console.warn('Failed to initialize chat history storage:', error.message);
135
+ }
136
+
137
+ // Map to store SSE clients by session ID
138
+ const sseClients = new Map();
139
+
140
+ // Initialize a default ProbeChat instance for /folders endpoint? Or make folders static?
141
+ // Let's make /folders rely on environment variables directly or a static config
142
+ // to avoid needing a default chat instance just for that.
143
+ const staticAllowedFolders = process.env.ALLOWED_FOLDERS
144
+ ? process.env.ALLOWED_FOLDERS.split(',').map(folder => folder.trim()).filter(Boolean)
145
+ : [];
146
+
147
+
148
+ let noApiKeysMode = !hasApiKeys;
149
+ if (noApiKeysMode) {
150
+ console.log('Running in No API Keys mode - will show setup instructions to users');
151
+ } else {
152
+ console.log('API keys detected. Chat functionality enabled.');
153
+ }
154
+
155
+
156
+ // Define the tools available for direct API calls (bypassing LLM loop)
157
+ // Note: probeTool is the backward compatibility wrapper
158
+ const directApiTools = {
159
+ search: searchToolInstance,
160
+ query: queryToolInstance,
161
+ extract: extractToolInstance
162
+ };
163
+
164
+ // Add implement tool if edit mode is enabled
165
+ if (allowEdit) {
166
+ directApiTools.implement = implementToolInstance;
167
+ }
168
+
169
+
170
+ // Helper function to send SSE data
171
+ function sendSSEData(res, data, eventType = 'message') {
172
+ const DEBUG = process.env.DEBUG_CHAT === '1';
173
+ try {
174
+ // Check if the response stream is still writable
175
+ if (!res.writable || res.writableEnded) {
176
+ if (DEBUG) console.log(`[DEBUG] SSE stream closed for event type ${eventType}, cannot send.`);
177
+ return;
178
+ }
179
+ if (DEBUG) {
180
+ // console.log(`[DEBUG] Sending SSE data, event type: ${eventType}`); // Can be noisy
181
+ }
182
+ res.write(`event: ${eventType}\n`);
183
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
184
+ if (DEBUG) {
185
+ // console.log(`[DEBUG] SSE data sent successfully for event: ${eventType}`);
186
+ // const preview = JSON.stringify(data).substring(0, 100);
187
+ // console.log(`[DEBUG] SSE data content preview: ${preview}...`);
188
+ }
189
+ } catch (error) {
190
+ console.error(`[ERROR] Error sending SSE data:`, error);
191
+ // Attempt to close the connection gracefully on error?
192
+ try {
193
+ if (res.writable && !res.writableEnded) res.end();
194
+ } catch (closeError) {
195
+ console.error(`[ERROR] Error closing SSE stream after send error:`, closeError);
196
+ }
197
+ }
198
+ }
199
+
200
+ // Map to store active chat instances by session ID for cancellation purposes
201
+ const activeChatInstances = new Map();
202
+
203
+ const server = createServer(async (req, res) => {
204
+ // Apply authentication middleware to all requests first
205
+ const processRequest = (routeHandler) => {
206
+ // First apply authentication middleware
207
+ authMiddleware(req, res, () => {
208
+ // Then process the route if authentication passes
209
+ routeHandler(req, res);
210
+ });
211
+ };
212
+
213
+ // Define route handlers
214
+ const routes = {
215
+ // Handle OPTIONS requests for CORS preflight (Common)
216
+ 'OPTIONS /api/token-usage': (req, res) => handleOptions(res),
217
+ 'OPTIONS /api/sessions': (req, res) => handleOptions(res),
218
+ 'OPTIONS /chat': (req, res) => handleOptions(res),
219
+ 'OPTIONS /api/search': (req, res) => handleOptions(res),
220
+ 'OPTIONS /api/query': (req, res) => handleOptions(res),
221
+ 'OPTIONS /api/extract': (req, res) => handleOptions(res),
222
+ 'OPTIONS /api/implement': (req, res) => handleOptions(res),
223
+ 'OPTIONS /cancel-request': (req, res) => handleOptions(res),
224
+ 'OPTIONS /folders': (req, res) => handleOptions(res), // Added for /folders
225
+
226
+
227
+ // Token usage API endpoint
228
+ 'GET /api/token-usage': (req, res) => {
229
+ const sessionId = getSessionIdFromUrl(req);
230
+ if (!sessionId) return sendError(res, 400, 'Missing sessionId parameter');
231
+
232
+ const chatInstance = chatSessions.get(sessionId);
233
+ if (!chatInstance) return sendError(res, 404, 'Session not found');
234
+
235
+ const DEBUG = process.env.DEBUG_CHAT === '1';
236
+
237
+ // Update the tokenCounter's history with the chat history
238
+ if (chatInstance.tokenCounter && typeof chatInstance.tokenCounter.updateHistory === 'function' &&
239
+ chatInstance.history) {
240
+ chatInstance.tokenCounter.updateHistory(chatInstance.history);
241
+ if (DEBUG) {
242
+ console.log(`[DEBUG] Updated tokenCounter history with ${chatInstance.history.length} messages for token usage request`);
243
+ }
244
+ }
245
+
246
+ // Get raw token usage data from the chat instance
247
+ const tokenUsage = chatInstance.getTokenUsage();
248
+
249
+ if (DEBUG) {
250
+ console.log(`[DEBUG] Token usage request - Context window size: ${tokenUsage.contextWindow}`);
251
+ console.log(`[DEBUG] Token usage request - Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
252
+ }
253
+
254
+ // Send the raw token usage data to the client
255
+ // The client-side JavaScript will handle formatting
256
+ sendJson(res, 200, tokenUsage);
257
+ },
258
+
259
+ // Session history API endpoint for URL-based session restoration
260
+ 'GET /api/session/:sessionId/history': async (req, res) => {
261
+ const sessionId = extractSessionIdFromHistoryPath(req.url);
262
+ if (!sessionId) return sendError(res, 400, 'Missing sessionId in URL path');
263
+
264
+ const DEBUG = process.env.DEBUG_CHAT === '1';
265
+ if (DEBUG) {
266
+ console.log(`[DEBUG] Fetching history for session: ${sessionId}`);
267
+ }
268
+
269
+ try {
270
+ // First check if session is in memory cache
271
+ const chatInstance = chatSessions.get(sessionId);
272
+ let history = [];
273
+ let tokenUsage = null;
274
+ let exists = false;
275
+
276
+ if (chatInstance) {
277
+ // Session is active - use in-memory display history for consistency
278
+ history = chatInstance.displayHistory || [];
279
+ tokenUsage = chatInstance.getTokenUsage();
280
+ exists = true;
281
+ } else if (globalStorage) {
282
+ // Session not in memory - try loading from persistent storage
283
+ const persistentHistory = await globalStorage.getSessionHistory(sessionId);
284
+ if (persistentHistory && persistentHistory.length > 0) {
285
+ // Convert stored messages to display format
286
+ history = persistentHistory.map(msg => ({
287
+ role: msg.role,
288
+ content: msg.content,
289
+ timestamp: new Date(msg.timestamp).toISOString(),
290
+ displayType: msg.display_type,
291
+ visible: msg.visible,
292
+ images: msg.images || []
293
+ }));
294
+ exists = true;
295
+ }
296
+ }
297
+
298
+ sendJson(res, 200, {
299
+ history: history,
300
+ tokenUsage: tokenUsage,
301
+ sessionId: sessionId,
302
+ exists: exists,
303
+ timestamp: new Date().toISOString()
304
+ });
305
+ } catch (error) {
306
+ console.error('Error fetching session history:', error);
307
+ sendJson(res, 200, {
308
+ history: [],
309
+ tokenUsage: null,
310
+ sessionId: sessionId,
311
+ exists: false,
312
+ timestamp: new Date().toISOString()
313
+ });
314
+ }
315
+ },
316
+
317
+ // Sessions list endpoint for history dropdown
318
+ 'GET /api/sessions': async (req, res) => {
319
+ const DEBUG = process.env.DEBUG_CHAT === '1';
320
+ if (DEBUG) {
321
+ console.log(`[DEBUG] Fetching sessions list`);
322
+ }
323
+
324
+ try {
325
+ const sessions = [];
326
+ const now = Date.now();
327
+ const maxAge = 2 * 60 * 60 * 1000; // 2 hours in milliseconds
328
+
329
+ if (globalStorage) {
330
+ // Get sessions from persistent storage
331
+ const storedSessions = await globalStorage.listSessions(50);
332
+
333
+ for (const session of storedSessions) {
334
+ // Skip inactive sessions older than 2 hours
335
+ if (now - session.last_activity > maxAge) {
336
+ continue;
337
+ }
338
+
339
+ // Use stored preview or generate from session metadata
340
+ let preview = session.first_message_preview;
341
+ if (!preview) {
342
+ // If no preview stored, try to get first message from history
343
+ const history = await globalStorage.getSessionHistory(session.id, 1);
344
+ if (history.length > 0 && history[0].role === 'user') {
345
+ const cleanContent = extractContentFromMessage(history[0].content);
346
+ preview = cleanContent.length > 100
347
+ ? cleanContent.substring(0, 100) + '...'
348
+ : cleanContent;
349
+ }
350
+ }
351
+
352
+ if (preview) {
353
+ sessions.push({
354
+ sessionId: session.id,
355
+ preview: preview,
356
+ messageCount: 0, // We could calculate this but it's not critical
357
+ createdAt: new Date(session.created_at).toISOString(),
358
+ lastActivity: new Date(session.last_activity).toISOString(),
359
+ relativeTime: getRelativeTime(session.last_activity)
360
+ });
361
+ }
362
+ }
363
+ } else {
364
+ // Fallback to in-memory sessions
365
+ for (const [sessionId, chatInstance] of chatSessions.entries()) {
366
+ // Skip sessions without any history
367
+ if (!chatInstance.history || chatInstance.history.length === 0) {
368
+ continue;
369
+ }
370
+
371
+ // Get session metadata
372
+ const createdAt = chatInstance.createdAt || now;
373
+ const lastActivity = chatInstance.lastActivity || createdAt;
374
+
375
+ // Skip inactive sessions older than 2 hours
376
+ if (now - lastActivity > maxAge) {
377
+ continue;
378
+ }
379
+
380
+ // Find the first user message for preview
381
+ const firstUserMessage = chatInstance.history.find(msg => msg.role === 'user');
382
+ if (!firstUserMessage) {
383
+ continue;
384
+ }
385
+
386
+ // Extract clean content and create preview
387
+ const cleanContent = extractContentFromMessage(firstUserMessage.content);
388
+ const preview = cleanContent.length > 100
389
+ ? cleanContent.substring(0, 100) + '...'
390
+ : cleanContent;
391
+
392
+ sessions.push({
393
+ sessionId: sessionId,
394
+ preview: preview,
395
+ messageCount: chatInstance.history.length,
396
+ createdAt: new Date(createdAt).toISOString(),
397
+ lastActivity: new Date(lastActivity).toISOString(),
398
+ relativeTime: getRelativeTime(lastActivity)
399
+ });
400
+ }
401
+ }
402
+
403
+ if (DEBUG) {
404
+ console.log(`[DEBUG] Returning ${sessions.length} sessions`);
405
+ }
406
+
407
+ sendJson(res, 200, {
408
+ sessions: sessions,
409
+ total: sessions.length,
410
+ timestamp: new Date().toISOString()
411
+ });
412
+ } catch (error) {
413
+ console.error('Error fetching sessions list:', error);
414
+ sendJson(res, 500, { error: 'Failed to fetch sessions' });
415
+ }
416
+ },
417
+ // Static file routes
418
+ 'GET /logo.png': (req, res) => serveStatic(res, join(__dirname, 'logo.png'), 'image/png'),
419
+ // UI Routes
420
+ 'GET /': (req, res) => {
421
+ const htmlPath = join(__dirname, 'index.html');
422
+ serveHtml(res, htmlPath, { 'data-no-api-keys': noApiKeysMode ? 'true' : 'false' });
423
+ },
424
+
425
+ // Chat session route - serves HTML with injected session ID
426
+ 'GET /chat/:sessionId': (req, res) => {
427
+ const sessionId = extractSessionIdFromPath(req.url);
428
+ if (!sessionId) {
429
+ return sendError(res, 400, 'Invalid session ID in URL');
430
+ }
431
+
432
+ // Validate that session exists or at least has a valid UUID format
433
+ if (!isValidUUID(sessionId)) {
434
+ return sendError(res, 400, 'Invalid session ID format');
435
+ }
436
+
437
+ const htmlPath = join(__dirname, 'index.html');
438
+ serveHtml(res, htmlPath, {
439
+ 'data-no-api-keys': noApiKeysMode ? 'true' : 'false',
440
+ 'data-session-id': sessionId
441
+ });
442
+ },
443
+
444
+ 'GET /folders': (req, res) => {
445
+ const currentWorkingDir = process.cwd();
446
+ // Use static config or environment variables directly
447
+ const folders = staticAllowedFolders.length > 0 ? staticAllowedFolders : [currentWorkingDir];
448
+ // Use first allowed folder as currentDir, or fall back to process.cwd()
449
+ const currentDir = staticAllowedFolders.length > 0 ? staticAllowedFolders[0] : currentWorkingDir;
450
+
451
+ sendJson(res, 200, {
452
+ folders: folders,
453
+ currentDir: currentDir,
454
+ noApiKeysMode: noApiKeysMode
455
+ });
456
+ },
457
+
458
+ 'GET /openapi.yaml': (req, res) => serveStatic(res, join(__dirname, 'openapi.yaml'), 'text/yaml'),
459
+
460
+
461
+ // SSE endpoint for tool calls - NO AUTH for easier client implementation
462
+ 'GET /api/tool-events': (req, res) => {
463
+ const DEBUG = process.env.DEBUG_CHAT === '1';
464
+ const sessionId = getSessionIdFromUrl(req);
465
+ if (!sessionId) {
466
+ if (DEBUG) console.error(`[DEBUG] SSE: No sessionId found in URL: ${req.url}`);
467
+ return sendError(res, 400, 'Missing sessionId parameter');
468
+ }
469
+
470
+ if (DEBUG) console.log(`[DEBUG] SSE: Setting up connection for session: ${sessionId}`);
471
+
472
+ // Set headers for SSE
473
+ res.writeHead(200, {
474
+ 'Content-Type': 'text/event-stream',
475
+ 'Cache-Control': 'no-cache',
476
+ 'Connection': 'keep-alive',
477
+ 'Access-Control-Allow-Origin': '*' // Allow all origins for SSE
478
+ });
479
+ if (DEBUG) console.log(`[DEBUG] SSE: Headers set for session: ${sessionId}`);
480
+
481
+ // Send initial connection established event
482
+ const connectionData = { type: 'connection', message: 'SSE Connection Established', sessionId, timestamp: new Date().toISOString() };
483
+ sendSSEData(res, connectionData, 'connection');
484
+ if (DEBUG) console.log(`[DEBUG] SSE: Sent connection event for session: ${sessionId}`);
485
+
486
+ // Send a test event (optional, for debugging)
487
+ // setTimeout(() => sendSSEData(res, { type: 'test', message: 'Test event', sessionId }, 'test'), 1000);
488
+
489
+ // Function to handle tool call events for this session
490
+ const handleToolCall = (toolCall) => {
491
+ if (DEBUG) {
492
+ // console.log(`[DEBUG] SSE: Handling tool call event for session ${sessionId}: ${toolCall.name}`); // Noisy
493
+ }
494
+
495
+ // Store tool call in chat session's display history
496
+ const chatInstance = chatSessions.get(sessionId);
497
+ if (chatInstance && toolCall.status === 'completed') {
498
+ // Only store completed tool calls that users see
499
+ const displayToolCall = {
500
+ role: 'toolCall',
501
+ name: toolCall.name,
502
+ args: toolCall.args || {},
503
+ timestamp: toolCall.timestamp || new Date().toISOString(),
504
+ visible: true,
505
+ displayType: 'toolCall'
506
+ };
507
+
508
+ if (!chatInstance.displayHistory) {
509
+ chatInstance.displayHistory = [];
510
+ }
511
+ chatInstance.displayHistory.push(displayToolCall);
512
+
513
+ // Also save to persistent storage
514
+ if (globalStorage) {
515
+ globalStorage.saveMessage(sessionId, {
516
+ role: 'toolCall',
517
+ content: `Tool: ${toolCall.name}\nArgs: ${JSON.stringify(toolCall.args || {}, null, 2)}`,
518
+ timestamp: toolCall.timestamp ? new Date(toolCall.timestamp).getTime() : Date.now(),
519
+ displayType: 'toolCall',
520
+ visible: 1,
521
+ metadata: {
522
+ name: toolCall.name,
523
+ args: toolCall.args || {}
524
+ }
525
+ }).catch(err => {
526
+ console.error('Failed to save tool call to persistent storage:', err);
527
+ });
528
+ }
529
+
530
+ if (DEBUG) {
531
+ console.log(`[DEBUG] Stored tool call in display history: ${toolCall.name}`);
532
+ }
533
+ }
534
+
535
+ // Ensure data is serializable and add timestamp if missing
536
+ const serializableCall = {
537
+ ...toolCall,
538
+ timestamp: toolCall.timestamp || new Date().toISOString(),
539
+ _sse_sent_at: new Date().toISOString()
540
+ };
541
+ sendSSEData(res, serializableCall, 'toolCall'); // Event type 'toolCall'
542
+ };
543
+
544
+ // Register event listener for this specific session
545
+ const eventName = `toolCall:${sessionId}`;
546
+ // Remove previous listener for this exact session ID if any (safety measure)
547
+ const existingHandler = sseClients.get(sessionId)?.handler;
548
+ if (existingHandler) {
549
+ toolCallEmitter.removeListener(eventName, existingHandler);
550
+ }
551
+
552
+ toolCallEmitter.on(eventName, handleToolCall);
553
+ if (DEBUG) console.log(`[DEBUG] SSE: Registered listener for ${eventName}`);
554
+
555
+ // Store client and handler for cleanup
556
+ sseClients.set(sessionId, { res, handler: handleToolCall });
557
+ if (DEBUG) console.log(`[DEBUG] SSE: Client added for session ${sessionId}. Total clients: ${sseClients.size}`);
558
+
559
+ // Handle client disconnect
560
+ req.on('close', () => {
561
+ if (DEBUG) console.log(`[DEBUG] SSE: Client disconnecting: ${sessionId}`);
562
+ toolCallEmitter.removeListener(eventName, handleToolCall);
563
+ sseClients.delete(sessionId);
564
+ if (DEBUG) console.log(`[DEBUG] SSE: Client removed for session ${sessionId}. Remaining clients: ${sseClients.size}`);
565
+ });
566
+ },
567
+
568
+ // Cancellation endpoint
569
+ 'POST /cancel-request': async (req, res) => {
570
+ handlePostRequest(req, res, async (body) => {
571
+ const { sessionId } = body;
572
+ if (!sessionId) return sendError(res, 400, 'Missing required parameter: sessionId');
573
+
574
+ const DEBUG = process.env.DEBUG_CHAT === '1';
575
+ if (DEBUG) console.log(`\n[DEBUG] ===== Cancel Request for Session: ${sessionId} =====`);
576
+
577
+ // 1. Cancel Tool Executions (via probeTool.js)
578
+ const toolExecutionsCancelled = cancelToolExecutions(sessionId);
579
+
580
+ // 2. Cancel Active Chat Request (via probeChat instance)
581
+ const chatInstance = activeChatInstances.get(sessionId);
582
+ let chatInstanceAborted = false;
583
+ if (chatInstance && typeof chatInstance.abort === 'function') {
584
+ try {
585
+ chatInstance.abort(); // This sets chatInstance.cancelled = true and aborts controller
586
+ chatInstanceAborted = true;
587
+ if (DEBUG) console.log(`[DEBUG] Aborted chat instance processing for session: ${sessionId}`);
588
+ } catch (error) {
589
+ console.error(`Error aborting chat instance for session ${sessionId}:`, error);
590
+ }
591
+ } else {
592
+ if (DEBUG) console.log(`[DEBUG] No active chat instance found in map for session ${sessionId} to abort.`);
593
+ }
594
+
595
+ // 3. Cancel the request tracking entry (via cancelRequest.js - might be redundant if chatInstance.abort works)
596
+ const requestCancelled = cancelRequest(sessionId); // This calls the registered abort function
597
+
598
+ // Clean up map entry (might be done in finally block of chat endpoint too)
599
+ activeChatInstances.delete(sessionId);
600
+
601
+
602
+ console.log(`Cancellation processed for session ${sessionId}: Tools=${toolExecutionsCancelled}, Chat=${chatInstanceAborted}, RequestTracking=${requestCancelled}`);
603
+
604
+ sendJson(res, 200, {
605
+ success: true,
606
+ message: 'Cancellation request processed',
607
+ details: { toolExecutionsCancelled, chatInstanceAborted, requestCancelled },
608
+ timestamp: new Date().toISOString()
609
+ });
610
+ });
611
+ },
612
+
613
+ // --- Direct API Tool Endpoints (Bypass LLM Loop) ---
614
+ 'POST /api/search': async (req, res) => {
615
+ handlePostRequest(req, res, async (body) => {
616
+ const { query, path, allow_tests, maxResults, maxTokens, sessionId: reqSessionId } = body; // Renamed params
617
+ if (!query) return sendError(res, 400, 'Missing required parameter: query');
618
+
619
+ const sessionId = reqSessionId || randomUUID(); // Use provided or generate new for direct call
620
+ const toolParams = { query, path, allow_tests, maxResults, maxTokens, sessionId };
621
+
622
+ await executeDirectTool(res, directApiTools.search, 'search', toolParams, sessionId);
623
+ });
624
+ },
625
+ 'POST /api/query': async (req, res) => {
626
+ handlePostRequest(req, res, async (body) => {
627
+ const { pattern, path, language, allow_tests, sessionId: reqSessionId } = body;
628
+ if (!pattern) return sendError(res, 400, 'Missing required parameter: pattern');
629
+
630
+ const sessionId = reqSessionId || randomUUID();
631
+ const toolParams = { pattern, path, language, allow_tests, sessionId };
632
+
633
+ await executeDirectTool(res, directApiTools.query, 'query', toolParams, sessionId);
634
+ });
635
+ },
636
+ 'POST /api/extract': async (req, res) => {
637
+ handlePostRequest(req, res, async (body) => {
638
+ const { file_path, line, end_line, allow_tests, context_lines, format, input_content, sessionId: reqSessionId } = body;
639
+ // file_path or input_content is required by the underlying tool implementation usually
640
+ if (!file_path && !input_content) return sendError(res, 400, 'Missing required parameter: file_path or input_content');
641
+
642
+ const sessionId = reqSessionId || randomUUID();
643
+ const toolParams = { file_path, line, end_line, allow_tests, context_lines, format, input_content, sessionId };
644
+
645
+ await executeDirectTool(res, directApiTools.extract, 'extract', toolParams, sessionId);
646
+ });
647
+ },
648
+
649
+ // Implement tool endpoint (only available if allowEdit is true)
650
+ 'POST /api/implement': async (req, res) => {
651
+ // Check if edit mode is enabled
652
+ if (!directApiTools.implement) {
653
+ return sendError(res, 403, 'Implement tool is not enabled. Start server with --allow-edit to enable.');
654
+ }
655
+
656
+ handlePostRequest(req, res, async (body) => {
657
+ const { task, sessionId: reqSessionId } = body;
658
+ if (!task) return sendError(res, 400, 'Missing required parameter: task');
659
+
660
+ const sessionId = reqSessionId || randomUUID();
661
+ const toolParams = { task, sessionId };
662
+
663
+ await executeDirectTool(res, directApiTools.implement, 'implement', toolParams, sessionId);
664
+ });
665
+ },
666
+
667
+ // --- Main Chat Endpoint (Handles the Loop) ---
668
+ 'POST /chat': (req, res) => { // This is the route used by the frontend UI
669
+ handlePostRequest(req, res, async (requestData) => {
670
+ const {
671
+ message,
672
+ images = [], // Array of base64 image URLs
673
+ sessionId: reqSessionId,
674
+ clearHistory,
675
+ apiProvider,
676
+ apiKey,
677
+ apiUrl
678
+ } = requestData;
679
+ const DEBUG = process.env.DEBUG_CHAT === '1';
680
+
681
+ if (DEBUG) {
682
+ console.log(`\n[DEBUG] ===== UI Chat Request =====`);
683
+ console.log(`[DEBUG] Request Data:`, { ...requestData, apiKey: requestData.apiKey ? '******' : undefined });
684
+ }
685
+
686
+ // --- Session and Instance Management ---
687
+ const chatSessionId = reqSessionId || randomUUID(); // Ensure we always have a session ID
688
+ if (!reqSessionId && DEBUG) console.log(`[DEBUG] No session ID from UI, generated: ${chatSessionId}`);
689
+ else if (DEBUG) console.log(`[DEBUG] Using session ID from UI: ${chatSessionId}`);
690
+
691
+ // Get or create the chat instance *without* API key overrides here.
692
+ // API keys from request are ignored for existing sessions to preserve history consistency.
693
+ // If a *new* session is created AND keys are provided, the ProbeChat constructor *should* handle them.
694
+ // Extract API credentials from request if available
695
+ const apiCredentials = apiKey ? { apiProvider, apiKey, apiUrl } : null;
696
+
697
+ // Get or create chat instance with API credentials
698
+ const chatInstance = getOrCreateChat(chatSessionId, apiCredentials);
699
+
700
+ // Update last activity timestamp
701
+ chatInstance.lastActivity = Date.now();
702
+
703
+ // Check if API keys are needed but missing
704
+ if (chatInstance.noApiKeysMode) {
705
+ console.warn(`[WARN] Chat request for session ${chatSessionId} cannot proceed: No API keys configured.`);
706
+ return sendError(res, 503, 'Chat service unavailable: API key not configured on server.');
707
+ }
708
+
709
+ // Register this request as active for cancellation
710
+ registerRequest(chatSessionId, { abort: () => chatInstance.abort() });
711
+ if (DEBUG) console.log(`[DEBUG] Registered cancellable request for session: ${chatSessionId}`);
712
+ activeChatInstances.set(chatSessionId, chatInstance); // Store for direct access during cancellation
713
+
714
+ // --- Handle Clear History ---
715
+ if (message === '__clear_history__' || clearHistory) {
716
+ console.log(`Clearing chat history for session: ${chatSessionId}`);
717
+ const newSessionId = chatInstance.clearHistory(); // clearHistory now returns the *new* session ID
718
+ // Remove old session state
719
+ clearRequest(chatSessionId);
720
+ activeChatInstances.delete(chatSessionId);
721
+ clearToolExecutionData(chatSessionId);
722
+ chatSessions.delete(chatSessionId); // Remove old instance from map
723
+ // We don't create the *new* instance here, it will be created on the *next* message request
724
+
725
+ // Create a new empty token usage object for the cleared history
726
+ const emptyTokenUsage = {
727
+ contextWindow: 0,
728
+ current: {
729
+ request: 0,
730
+ response: 0,
731
+ total: 0,
732
+ cacheRead: 0,
733
+ cacheWrite: 0,
734
+ cacheTotal: 0
735
+ },
736
+ total: {
737
+ request: 0,
738
+ response: 0,
739
+ total: 0,
740
+ cacheRead: 0,
741
+ cacheWrite: 0,
742
+ cacheTotal: 0
743
+ }
744
+ };
745
+
746
+ sendJson(res, 200, {
747
+ response: 'Chat history cleared',
748
+ tokenUsage: emptyTokenUsage, // Include empty token usage data
749
+ newSessionId: newSessionId, // Inform UI about the new ID
750
+ timestamp: new Date().toISOString()
751
+ });
752
+ return; // Stop processing
753
+ }
754
+
755
+ // --- Execute Chat Loop (Non-Streaming Response) ---
756
+ // The loop is inside chatInstance.chat now.
757
+ // We expect the *final* result string back.
758
+ try {
759
+ // ChatSessionManager handles session ID and API credentials internally
760
+ // Only pass the message and images
761
+ const result = await chatInstance.chat(message, images);
762
+
763
+ // Check if cancelled *during* the chat call (ProbeChat throws error)
764
+ // Error handled in catch block
765
+
766
+ // Handle the new structured response format
767
+ let responseText;
768
+ let tokenUsage;
769
+
770
+ if (result && typeof result === 'object' && 'response' in result) {
771
+ // New format: { response: string, tokenUsage: object }
772
+ responseText = result.response;
773
+ tokenUsage = result.tokenUsage;
774
+
775
+ if (process.env.DEBUG_CHAT === '1') {
776
+ console.log(`[DEBUG] Received structured response with token usage data`);
777
+ console.log(`[DEBUG] Context window size: ${tokenUsage.contextWindow}`);
778
+ console.log(`[DEBUG] Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
779
+ }
780
+ } else {
781
+ // Legacy format: string response
782
+ responseText = result;
783
+ tokenUsage = chatInstance.getTokenUsage(); // Get token usage separately
784
+
785
+ if (process.env.DEBUG_CHAT === '1') {
786
+ console.log(`[DEBUG] Received legacy response format, fetched token usage separately`);
787
+ }
788
+ }
789
+
790
+ // Create the response object with the response text and token usage data
791
+ const responseObject = {
792
+ response: responseText,
793
+ tokenUsage: tokenUsage,
794
+ sessionId: chatSessionId,
795
+ timestamp: new Date().toISOString()
796
+ };
797
+
798
+ // Send the response with token usage in both the body and header
799
+ sendJson(res, 200, responseObject, { 'X-Token-Usage': JSON.stringify(tokenUsage) });
800
+
801
+ console.log(`Finished chat request for session: ${chatSessionId}`);
802
+
803
+ } catch (error) {
804
+ // Check if the error is actually a structured response with token usage
805
+ let errorResponse = error;
806
+ let tokenUsage;
807
+
808
+ if (error && typeof error === 'object' && error.response && error.tokenUsage) {
809
+ // This is a structured error response from probeChat
810
+ errorResponse = error.response;
811
+ tokenUsage = error.tokenUsage;
812
+
813
+ if (process.env.DEBUG_CHAT === '1') {
814
+ console.log(`[DEBUG] Received structured error response with token usage data`);
815
+ console.log(`[DEBUG] Context window size: ${tokenUsage.contextWindow}`);
816
+ console.log(`[DEBUG] Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
817
+ }
818
+ } else {
819
+ // Get token usage separately for regular errors
820
+
821
+ // First update the tokenCounter's history with the chat history
822
+ if (chatInstance.tokenCounter && typeof chatInstance.tokenCounter.updateHistory === 'function' &&
823
+ chatInstance.history) {
824
+ chatInstance.tokenCounter.updateHistory(chatInstance.history);
825
+ if (DEBUG) {
826
+ console.log(`[DEBUG] Updated tokenCounter history with ${chatInstance.history.length} messages for error case`);
827
+ }
828
+ }
829
+
830
+ // Force recalculation of context window size
831
+ if (chatInstance.tokenCounter && typeof chatInstance.tokenCounter.calculateContextSize === 'function') {
832
+ chatInstance.tokenCounter.calculateContextSize(chatInstance.history);
833
+ if (DEBUG) {
834
+ console.log(`[DEBUG] Forced recalculation of context window size for error case`);
835
+ }
836
+ }
837
+
838
+ // Get updated token usage after history update and recalculation
839
+ tokenUsage = chatInstance.getTokenUsage();
840
+
841
+ if (DEBUG) {
842
+ console.log(`[DEBUG] Error case - Final context window size: ${tokenUsage.contextWindow}`);
843
+ console.log(`[DEBUG] Error case - Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
844
+ }
845
+ }
846
+
847
+ // Handle errors, including cancellation
848
+ if (errorResponse.message && errorResponse.message.includes('cancelled') ||
849
+ (typeof errorResponse === 'string' && errorResponse.includes('cancelled'))) {
850
+ console.log(`Chat request processing was cancelled for session: ${chatSessionId}`);
851
+ // Send structured error response with token usage
852
+ sendJson(res, 499, {
853
+ error: 'Request cancelled by user',
854
+ tokenUsage: tokenUsage,
855
+ sessionId: chatSessionId,
856
+ timestamp: new Date().toISOString()
857
+ }); // 499 Client Closed Request
858
+ } else {
859
+ console.error(`Error processing chat for session ${chatSessionId}:`, error);
860
+ // Send structured error response with token usage
861
+ sendJson(res, 500, {
862
+ error: `Chat processing error: ${typeof errorResponse === 'string' ? errorResponse : errorResponse.message || 'Unknown error'}`,
863
+ tokenUsage: tokenUsage,
864
+ sessionId: chatSessionId,
865
+ timestamp: new Date().toISOString()
866
+ });
867
+ }
868
+ } finally {
869
+ // Cleanup regardless of success, error, or cancellation
870
+ clearRequest(chatSessionId);
871
+ activeChatInstances.delete(chatSessionId);
872
+ // Don't clear tool execution data here, it might be needed if user retries
873
+ // clearToolExecutionData(chatSessionId);
874
+ if (DEBUG) console.log(`[DEBUG] Cleaned up active request tracking for session: ${chatSessionId}`);
875
+ }
876
+ }); // End handlePostRequest for /chat
877
+ } // End /chat route
878
+ }; // End routes object
879
+
880
+ // --- Request Routing ---
881
+ const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
882
+ const routeKey = `${req.method} ${parsedUrl.pathname}`;
883
+ let handler = routes[routeKey];
884
+
885
+ // Handle dynamic routes if no exact match found
886
+ if (!handler) {
887
+ // Check for /chat/:sessionId pattern
888
+ if (req.method === 'GET' && parsedUrl.pathname.match(/^\/chat\/[^/?]+$/)) {
889
+ handler = routes['GET /chat/:sessionId'];
890
+ }
891
+ // Check for /api/session/:sessionId/history pattern
892
+ else if (req.method === 'GET' && parsedUrl.pathname.match(/^\/api\/session\/[^/?]+\/history$/)) {
893
+ handler = routes['GET /api/session/:sessionId/history'];
894
+ }
895
+ }
896
+
897
+ if (handler) {
898
+ // Skip auth for specific public routes
899
+ const publicRoutes = ['GET /openapi.yaml', 'GET /api/tool-events', 'GET /logo.png', 'GET /', 'GET /folders', 'OPTIONS']; // Add OPTIONS
900
+ const isPublicRoute = publicRoutes.includes(routeKey) || req.method === 'OPTIONS' ||
901
+ parsedUrl.pathname.match(/^\/chat\/[^/?]+$/) || // Chat sessions are public
902
+ parsedUrl.pathname.match(/^\/api\/session\/[^/?]+\/history$/); // History API is public
903
+
904
+ if (isPublicRoute) {
905
+ handler(req, res);
906
+ } else {
907
+ processRequest(handler); // Apply auth middleware
908
+ }
909
+ } else {
910
+ // No route match, return 404
911
+ sendError(res, 404, 'Not Found');
912
+ }
913
+ }); // End createServer
914
+
915
+ // Start the server
916
+ const PORT = process.env.PORT || 8080;
917
+ server.listen(PORT, () => {
918
+ console.log(`Probe Web Interface v${version}`);
919
+ console.log(`Server running on http://localhost:${PORT}`);
920
+ console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
921
+ if (noApiKeysMode) {
922
+ console.log('*** Running in NO API KEYS mode. Chat functionality disabled. ***');
923
+ }
924
+ });
925
+ }
926
+
927
+
928
+ // --- Helper Functions ---
929
+
930
+ function handleOptions(res) {
931
+ res.writeHead(200, {
932
+ 'Access-Control-Allow-Origin': '*', // Or specific origin
933
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
934
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Session-ID', // Add any custom headers needed
935
+ 'Access-Control-Max-Age': '86400' // 24 hours
936
+ });
937
+ res.end();
938
+ }
939
+
940
+ function sendJson(res, statusCode, data, headers = {}) {
941
+ if (res.headersSent) return;
942
+ res.writeHead(statusCode, {
943
+ 'Content-Type': 'application/json',
944
+ 'Access-Control-Allow-Origin': '*', // Adjust as needed
945
+ 'Access-Control-Expose-Headers': 'X-Token-Usage', // Expose custom headers
946
+ ...headers
947
+ });
948
+ res.end(JSON.stringify(data));
949
+ }
950
+
951
+ function sendError(res, statusCode, message) {
952
+ if (res.headersSent) return;
953
+ console.error(`Sending error (${statusCode}): ${message}`);
954
+ res.writeHead(statusCode, {
955
+ 'Content-Type': 'application/json',
956
+ 'Access-Control-Allow-Origin': '*'
957
+ });
958
+ res.end(JSON.stringify({ error: message, status: statusCode }));
959
+ }
960
+
961
+ function serveStatic(res, filePath, contentType) {
962
+ if (res.headersSent) return;
963
+ if (existsSync(filePath)) {
964
+ res.writeHead(200, { 'Content-Type': contentType });
965
+ const fileData = readFileSync(filePath);
966
+ res.end(fileData);
967
+ } else {
968
+ sendError(res, 404, `${contentType} not found`);
969
+ }
970
+ }
971
+
972
+ function serveHtml(res, filePath, bodyAttributes = {}) {
973
+ if (res.headersSent) return;
974
+ if (existsSync(filePath)) {
975
+ res.writeHead(200, { 'Content-Type': 'text/html' });
976
+ let html = readFileSync(filePath, 'utf8');
977
+ // Inject attributes into body tag
978
+ const attributesString = Object.entries(bodyAttributes)
979
+ .map(([key, value]) => `${key}="${String(value).replace(/"/g, '"')}"`)
980
+ .join(' ');
981
+ if (attributesString) {
982
+ html = html.replace('<body', `<body ${attributesString}`);
983
+ }
984
+ res.end(html);
985
+ } else {
986
+ sendError(res, 404, 'HTML file not found');
987
+ }
988
+ }
989
+
990
+
991
+ function getSessionIdFromUrl(req) {
992
+ try {
993
+ const url = new URL(req.url, `http://${req.headers.host}`);
994
+ return url.searchParams.get('sessionId');
995
+ } catch (error) {
996
+ console.error(`Error parsing URL for sessionId: ${error.message}`);
997
+ // Fallback: manual parsing (less reliable)
998
+ const match = req.url.match(/[?&]sessionId=([^&]+)/);
999
+ return match ? match[1] : null;
1000
+ }
1001
+ }
1002
+
1003
+ async function handlePostRequest(req, res, callback) {
1004
+ let body = '';
1005
+ req.on('data', chunk => body += chunk);
1006
+ req.on('end', async () => {
1007
+ try {
1008
+ const parsedBody = JSON.parse(body);
1009
+ await callback(parsedBody);
1010
+ } catch (error) {
1011
+ if (error instanceof SyntaxError) {
1012
+ sendError(res, 400, 'Invalid JSON in request body');
1013
+ } else {
1014
+ console.error('Error handling POST request:', error);
1015
+ sendError(res, 500, `Internal Server Error: ${error.message}`);
1016
+ }
1017
+ }
1018
+ });
1019
+ req.on('error', (err) => {
1020
+ console.error('Request error:', err);
1021
+ sendError(res, 500, 'Request error');
1022
+ });
1023
+ }
1024
+
1025
+ async function executeDirectTool(res, toolInstance, toolName, toolParams, sessionId) {
1026
+ const DEBUG = process.env.DEBUG_CHAT === '1';
1027
+ if (DEBUG) {
1028
+ console.log(`\n[DEBUG] ===== Direct API Tool Call: ${toolName} =====`);
1029
+ console.log(`[DEBUG] Session ID: ${sessionId}`);
1030
+ console.log(`[DEBUG] Params:`, toolParams);
1031
+ }
1032
+ try {
1033
+ // Execute the tool instance directly (it handles events/cancellation)
1034
+ const result = await toolInstance.execute(toolParams);
1035
+ sendJson(res, 200, { results: result, timestamp: new Date().toISOString() });
1036
+ } catch (error) {
1037
+ console.error(`Error executing direct tool ${toolName}:`, error);
1038
+ let statusCode = 500;
1039
+ let errorMessage = `Error executing ${toolName}`;
1040
+ if (error.message.includes('cancelled')) {
1041
+ statusCode = 499; // Client Closed Request
1042
+ errorMessage = 'Operation cancelled';
1043
+ } else if (error.code === 'ENOENT') {
1044
+ statusCode = 404; errorMessage = 'File or path not found';
1045
+ } else if (error.code === 'EACCES') {
1046
+ statusCode = 403; errorMessage = 'Permission denied';
1047
+ }
1048
+ // Add more specific error handling if needed
1049
+ sendError(res, statusCode, `${errorMessage}: ${error.message}`);
1050
+ }
1051
+ }
1052
+
1053
+ function extractSessionIdFromPath(url) {
1054
+ // Extract session ID from URLs like /chat/session-id
1055
+ const match = url.match(/^\/chat\/([^/?]+)/);
1056
+ return match ? match[1] : null;
1057
+ }
1058
+
1059
+ function isValidUUID(str) {
1060
+ // Basic UUID validation (any version, with or without hyphens)
1061
+ const uuidRegex = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;
1062
+ return uuidRegex.test(str);
1063
+ }
1064
+
1065
+ function extractSessionIdFromHistoryPath(url) {
1066
+ // Extract session ID from URLs like /api/session/session-id/history
1067
+ const match = url.match(/^\/api\/session\/([^/?]+)\/history/);
1068
+ return match ? match[1] : null;
1069
+ }
1070
+
1071
+ function extractContentFromMessage(content) {
1072
+ // Handle different XML patterns used by the assistant and clean user messages
1073
+ const patterns = [
1074
+ /<task>([\s\S]*?)<\/task>/,
1075
+ /<attempt_completion>\s*<result>([\s\S]*?)<\/result>\s*<\/attempt_completion>/,
1076
+ /<result>([\s\S]*?)<\/result>/
1077
+ ];
1078
+
1079
+ for (const pattern of patterns) {
1080
+ const match = content.match(pattern);
1081
+ if (match) {
1082
+ return match[1].trim();
1083
+ }
1084
+ }
1085
+
1086
+ // If no XML pattern matches, return the content as-is
1087
+ return content.trim();
1088
+ }
1089
+
1090
+ function getRelativeTime(timestamp) {
1091
+ const now = Date.now();
1092
+ const diff = now - timestamp;
1093
+
1094
+ const seconds = Math.floor(diff / 1000);
1095
+ const minutes = Math.floor(seconds / 60);
1096
+ const hours = Math.floor(minutes / 60);
1097
+ const days = Math.floor(hours / 24);
1098
+
1099
+ if (days > 0) return `${days}d ago`;
1100
+ if (hours > 0) return `${hours}h ago`;
1101
+ if (minutes > 0) return `${minutes}m ago`;
1102
+ return 'just now';
1103
+ }