@oscharko-dev/keiko-server 0.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 (509) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/assistant-response.d.ts +6 -0
  3. package/dist/assistant-response.d.ts.map +1 -0
  4. package/dist/assistant-response.js +12 -0
  5. package/dist/browser.d.ts +11 -0
  6. package/dist/browser.d.ts.map +1 -0
  7. package/dist/browser.js +245 -0
  8. package/dist/chat-handlers.d.ts +48 -0
  9. package/dist/chat-handlers.d.ts.map +1 -0
  10. package/dist/chat-handlers.js +821 -0
  11. package/dist/chat-stream-handlers.d.ts +4 -0
  12. package/dist/chat-stream-handlers.d.ts.map +1 -0
  13. package/dist/chat-stream-handlers.js +136 -0
  14. package/dist/conversation-prompt.d.ts +8 -0
  15. package/dist/conversation-prompt.d.ts.map +1 -0
  16. package/dist/conversation-prompt.js +36 -0
  17. package/dist/conversation-validation.d.ts +26 -0
  18. package/dist/conversation-validation.d.ts.map +1 -0
  19. package/dist/conversation-validation.js +125 -0
  20. package/dist/credentialPersistence.d.ts +23 -0
  21. package/dist/credentialPersistence.d.ts.map +1 -0
  22. package/dist/credentialPersistence.js +93 -0
  23. package/dist/credentialVault.d.ts +30 -0
  24. package/dist/credentialVault.d.ts.map +1 -0
  25. package/dist/credentialVault.js +206 -0
  26. package/dist/csp.d.ts +3 -0
  27. package/dist/csp.d.ts.map +1 -0
  28. package/dist/csp.js +75 -0
  29. package/dist/deps.d.ts +78 -0
  30. package/dist/deps.d.ts.map +1 -0
  31. package/dist/deps.js +457 -0
  32. package/dist/editor/agentRoutes.d.ts +7 -0
  33. package/dist/editor/agentRoutes.d.ts.map +1 -0
  34. package/dist/editor/agentRoutes.js +197 -0
  35. package/dist/editor/assuredGateRunner.d.ts +36 -0
  36. package/dist/editor/assuredGateRunner.d.ts.map +1 -0
  37. package/dist/editor/assuredGateRunner.js +100 -0
  38. package/dist/editor/assuredPreFilter.d.ts +34 -0
  39. package/dist/editor/assuredPreFilter.d.ts.map +1 -0
  40. package/dist/editor/assuredPreFilter.js +134 -0
  41. package/dist/editor/assuredPreFilterRunner.d.ts +31 -0
  42. package/dist/editor/assuredPreFilterRunner.d.ts.map +1 -0
  43. package/dist/editor/assuredPreFilterRunner.js +312 -0
  44. package/dist/editor/builtinLanguageProviders.d.ts +6 -0
  45. package/dist/editor/builtinLanguageProviders.d.ts.map +1 -0
  46. package/dist/editor/builtinLanguageProviders.js +221 -0
  47. package/dist/editor/codingContext.d.ts +12 -0
  48. package/dist/editor/codingContext.d.ts.map +1 -0
  49. package/dist/editor/codingContext.js +121 -0
  50. package/dist/editor/codingContextEvidence.d.ts +7 -0
  51. package/dist/editor/codingContextEvidence.d.ts.map +1 -0
  52. package/dist/editor/codingContextEvidence.js +52 -0
  53. package/dist/editor/codingContextProviders.d.ts +36 -0
  54. package/dist/editor/codingContextProviders.d.ts.map +1 -0
  55. package/dist/editor/codingContextProviders.js +348 -0
  56. package/dist/editor/completionModelEvidence.d.ts +16 -0
  57. package/dist/editor/completionModelEvidence.d.ts.map +1 -0
  58. package/dist/editor/completionModelEvidence.js +50 -0
  59. package/dist/editor/completionRoutes.d.ts +37 -0
  60. package/dist/editor/completionRoutes.d.ts.map +1 -0
  61. package/dist/editor/completionRoutes.js +411 -0
  62. package/dist/editor/contextRoutes.d.ts +6 -0
  63. package/dist/editor/contextRoutes.d.ts.map +1 -0
  64. package/dist/editor/contextRoutes.js +411 -0
  65. package/dist/editor/disposableAssuredExecution.d.ts +22 -0
  66. package/dist/editor/disposableAssuredExecution.d.ts.map +1 -0
  67. package/dist/editor/disposableAssuredExecution.js +57 -0
  68. package/dist/editor/editorCompletionModel.d.ts +47 -0
  69. package/dist/editor/editorCompletionModel.d.ts.map +1 -0
  70. package/dist/editor/editorCompletionModel.js +156 -0
  71. package/dist/editor/editorInlineCompletionModel.d.ts +34 -0
  72. package/dist/editor/editorInlineCompletionModel.d.ts.map +1 -0
  73. package/dist/editor/editorInlineCompletionModel.js +112 -0
  74. package/dist/editor/editorModelTokenBudget.d.ts +46 -0
  75. package/dist/editor/editorModelTokenBudget.d.ts.map +1 -0
  76. package/dist/editor/editorModelTokenBudget.js +121 -0
  77. package/dist/editor/inlineCompletionRateLimiter.d.ts +19 -0
  78. package/dist/editor/inlineCompletionRateLimiter.d.ts.map +1 -0
  79. package/dist/editor/inlineCompletionRateLimiter.js +46 -0
  80. package/dist/editor/inlineCompletionRoutes.d.ts +26 -0
  81. package/dist/editor/inlineCompletionRoutes.d.ts.map +1 -0
  82. package/dist/editor/inlineCompletionRoutes.js +404 -0
  83. package/dist/editor/inlineCompletionTelemetryEvidence.d.ts +5 -0
  84. package/dist/editor/inlineCompletionTelemetryEvidence.d.ts.map +1 -0
  85. package/dist/editor/inlineCompletionTelemetryEvidence.js +42 -0
  86. package/dist/editor/languageCancellation.d.ts +19 -0
  87. package/dist/editor/languageCancellation.d.ts.map +1 -0
  88. package/dist/editor/languageCancellation.js +48 -0
  89. package/dist/editor/languageProvider.d.ts +39 -0
  90. package/dist/editor/languageProvider.d.ts.map +1 -0
  91. package/dist/editor/languageProvider.js +11 -0
  92. package/dist/editor/languageRoutes.d.ts +15 -0
  93. package/dist/editor/languageRoutes.d.ts.map +1 -0
  94. package/dist/editor/languageRoutes.js +106 -0
  95. package/dist/editor/languageSanitize.d.ts +8 -0
  96. package/dist/editor/languageSanitize.d.ts.map +1 -0
  97. package/dist/editor/languageSanitize.js +101 -0
  98. package/dist/editor/languageService.d.ts +36 -0
  99. package/dist/editor/languageService.d.ts.map +1 -0
  100. package/dist/editor/languageService.js +93 -0
  101. package/dist/editor/languageServiceHost.d.ts +14 -0
  102. package/dist/editor/languageServiceHost.d.ts.map +1 -0
  103. package/dist/editor/languageServiceHost.js +242 -0
  104. package/dist/editor/localKnowledgeRetrieval.d.ts +21 -0
  105. package/dist/editor/localKnowledgeRetrieval.d.ts.map +1 -0
  106. package/dist/editor/localKnowledgeRetrieval.js +44 -0
  107. package/dist/editor/patchApplyEvidence.d.ts +21 -0
  108. package/dist/editor/patchApplyEvidence.d.ts.map +1 -0
  109. package/dist/editor/patchApplyEvidence.js +87 -0
  110. package/dist/editor/patchApplyRoutes.d.ts +16 -0
  111. package/dist/editor/patchApplyRoutes.d.ts.map +1 -0
  112. package/dist/editor/patchApplyRoutes.js +307 -0
  113. package/dist/editor/postApplyVerification.d.ts +42 -0
  114. package/dist/editor/postApplyVerification.d.ts.map +1 -0
  115. package/dist/editor/postApplyVerification.js +177 -0
  116. package/dist/editor/testGenerationEvidence.d.ts +6 -0
  117. package/dist/editor/testGenerationEvidence.d.ts.map +1 -0
  118. package/dist/editor/testGenerationEvidence.js +72 -0
  119. package/dist/editor/testGenerationPatch.d.ts +10 -0
  120. package/dist/editor/testGenerationPatch.d.ts.map +1 -0
  121. package/dist/editor/testGenerationPatch.js +66 -0
  122. package/dist/editor/testGenerationRoutes.d.ts +21 -0
  123. package/dist/editor/testGenerationRoutes.d.ts.map +1 -0
  124. package/dist/editor/testGenerationRoutes.js +254 -0
  125. package/dist/editor/testGenerationRunner.d.ts +23 -0
  126. package/dist/editor/testGenerationRunner.d.ts.map +1 -0
  127. package/dist/editor/testGenerationRunner.js +120 -0
  128. package/dist/editor/textOffsets.d.ts +6 -0
  129. package/dist/editor/textOffsets.d.ts.map +1 -0
  130. package/dist/editor/textOffsets.js +82 -0
  131. package/dist/editor/typescriptLanguageProvider.d.ts +3 -0
  132. package/dist/editor/typescriptLanguageProvider.d.ts.map +1 -0
  133. package/dist/editor/typescriptLanguageProvider.js +217 -0
  134. package/dist/evidence.d.ts +28 -0
  135. package/dist/evidence.d.ts.map +1 -0
  136. package/dist/evidence.js +145 -0
  137. package/dist/files-deny.d.ts +3 -0
  138. package/dist/files-deny.d.ts.map +1 -0
  139. package/dist/files-deny.js +12 -0
  140. package/dist/files.d.ts +97 -0
  141. package/dist/files.d.ts.map +1 -0
  142. package/dist/files.js +733 -0
  143. package/dist/gateway-setup.d.ts +10 -0
  144. package/dist/gateway-setup.d.ts.map +1 -0
  145. package/dist/gateway-setup.js +896 -0
  146. package/dist/governed-workflow.d.ts +17 -0
  147. package/dist/governed-workflow.d.ts.map +1 -0
  148. package/dist/governed-workflow.js +147 -0
  149. package/dist/grounded-answer.d.ts +12 -0
  150. package/dist/grounded-answer.d.ts.map +1 -0
  151. package/dist/grounded-answer.js +69 -0
  152. package/dist/grounded-context-index.d.ts +25 -0
  153. package/dist/grounded-context-index.d.ts.map +1 -0
  154. package/dist/grounded-context-index.js +169 -0
  155. package/dist/grounded-document-evidence.d.ts +28 -0
  156. package/dist/grounded-document-evidence.d.ts.map +1 -0
  157. package/dist/grounded-document-evidence.js +430 -0
  158. package/dist/grounded-handoff.d.ts +4 -0
  159. package/dist/grounded-handoff.d.ts.map +1 -0
  160. package/dist/grounded-handoff.js +445 -0
  161. package/dist/grounded-orchestrator.d.ts +43 -0
  162. package/dist/grounded-orchestrator.d.ts.map +1 -0
  163. package/dist/grounded-orchestrator.js +1445 -0
  164. package/dist/grounded-prompt.d.ts +2 -0
  165. package/dist/grounded-prompt.d.ts.map +1 -0
  166. package/dist/grounded-prompt.js +17 -0
  167. package/dist/grounded-qa-hybrid.d.ts +36 -0
  168. package/dist/grounded-qa-hybrid.d.ts.map +1 -0
  169. package/dist/grounded-qa-hybrid.js +762 -0
  170. package/dist/grounded-qa-multi-source.d.ts +38 -0
  171. package/dist/grounded-qa-multi-source.d.ts.map +1 -0
  172. package/dist/grounded-qa-multi-source.js +461 -0
  173. package/dist/grounded-qa.d.ts +45 -0
  174. package/dist/grounded-qa.d.ts.map +1 -0
  175. package/dist/grounded-qa.js +877 -0
  176. package/dist/grounded-rerank.d.ts +26 -0
  177. package/dist/grounded-rerank.d.ts.map +1 -0
  178. package/dist/grounded-rerank.js +72 -0
  179. package/dist/grounded-turn-registry.d.ts +23 -0
  180. package/dist/grounded-turn-registry.d.ts.map +1 -0
  181. package/dist/grounded-turn-registry.js +102 -0
  182. package/dist/headers.d.ts +3 -0
  183. package/dist/headers.d.ts.map +1 -0
  184. package/dist/headers.js +22 -0
  185. package/dist/host-check.d.ts +3 -0
  186. package/dist/host-check.d.ts.map +1 -0
  187. package/dist/host-check.js +58 -0
  188. package/dist/index.d.ts +26 -0
  189. package/dist/index.d.ts.map +1 -0
  190. package/dist/index.js +33 -0
  191. package/dist/load-csp.d.ts +3 -0
  192. package/dist/load-csp.d.ts.map +1 -0
  193. package/dist/load-csp.js +100 -0
  194. package/dist/local-knowledge-grounded-qa.d.ts +42 -0
  195. package/dist/local-knowledge-grounded-qa.d.ts.map +1 -0
  196. package/dist/local-knowledge-grounded-qa.js +678 -0
  197. package/dist/local-knowledge-handlers.d.ts +24 -0
  198. package/dist/local-knowledge-handlers.d.ts.map +1 -0
  199. package/dist/local-knowledge-handlers.js +1285 -0
  200. package/dist/local-knowledge-indexing-registry.d.ts +13 -0
  201. package/dist/local-knowledge-indexing-registry.d.ts.map +1 -0
  202. package/dist/local-knowledge-indexing-registry.js +53 -0
  203. package/dist/localKnowledgeKeyProvider.d.ts +11 -0
  204. package/dist/localKnowledgeKeyProvider.d.ts.map +1 -0
  205. package/dist/localKnowledgeKeyProvider.js +48 -0
  206. package/dist/memory-audit-event-builders.d.ts +21 -0
  207. package/dist/memory-audit-event-builders.d.ts.map +1 -0
  208. package/dist/memory-audit-event-builders.js +187 -0
  209. package/dist/memory-audit-handler.d.ts +23 -0
  210. package/dist/memory-audit-handler.d.ts.map +1 -0
  211. package/dist/memory-audit-handler.js +191 -0
  212. package/dist/memory-capture-policy.d.ts +10 -0
  213. package/dist/memory-capture-policy.d.ts.map +1 -0
  214. package/dist/memory-capture-policy.js +44 -0
  215. package/dist/memory-consolidation-handlers.d.ts +6 -0
  216. package/dist/memory-consolidation-handlers.d.ts.map +1 -0
  217. package/dist/memory-consolidation-handlers.js +491 -0
  218. package/dist/memory-consolidation-registry.d.ts +47 -0
  219. package/dist/memory-consolidation-registry.d.ts.map +1 -0
  220. package/dist/memory-consolidation-registry.js +106 -0
  221. package/dist/memory-conv-handlers.d.ts +8 -0
  222. package/dist/memory-conv-handlers.d.ts.map +1 -0
  223. package/dist/memory-conv-handlers.js +369 -0
  224. package/dist/memory-conversation-context.d.ts +13 -0
  225. package/dist/memory-conversation-context.d.ts.map +1 -0
  226. package/dist/memory-conversation-context.js +22 -0
  227. package/dist/memory-diagnostics.d.ts +29 -0
  228. package/dist/memory-diagnostics.d.ts.map +1 -0
  229. package/dist/memory-diagnostics.js +122 -0
  230. package/dist/memory-embedding.d.ts +21 -0
  231. package/dist/memory-embedding.d.ts.map +1 -0
  232. package/dist/memory-embedding.js +264 -0
  233. package/dist/memory-handlers.d.ts +19 -0
  234. package/dist/memory-handlers.d.ts.map +1 -0
  235. package/dist/memory-handlers.js +1204 -0
  236. package/dist/memory-maintenance-handlers.d.ts +35 -0
  237. package/dist/memory-maintenance-handlers.d.ts.map +1 -0
  238. package/dist/memory-maintenance-handlers.js +219 -0
  239. package/dist/memory-record-builders.d.ts +4 -0
  240. package/dist/memory-record-builders.d.ts.map +1 -0
  241. package/dist/memory-record-builders.js +19 -0
  242. package/dist/memory-retention.d.ts +31 -0
  243. package/dist/memory-retention.d.ts.map +1 -0
  244. package/dist/memory-retention.js +151 -0
  245. package/dist/memory-retrieval-signals.d.ts +12 -0
  246. package/dist/memory-retrieval-signals.d.ts.map +1 -0
  247. package/dist/memory-retrieval-signals.js +100 -0
  248. package/dist/memory-salience.d.ts +12 -0
  249. package/dist/memory-salience.d.ts.map +1 -0
  250. package/dist/memory-salience.js +154 -0
  251. package/dist/memory-scope-sanitizer.d.ts +6 -0
  252. package/dist/memory-scope-sanitizer.d.ts.map +1 -0
  253. package/dist/memory-scope-sanitizer.js +106 -0
  254. package/dist/memory-target-resolver.d.ts +4 -0
  255. package/dist/memory-target-resolver.d.ts.map +1 -0
  256. package/dist/memory-target-resolver.js +73 -0
  257. package/dist/memory-workflow-port.d.ts +14 -0
  258. package/dist/memory-workflow-port.d.ts.map +1 -0
  259. package/dist/memory-workflow-port.js +186 -0
  260. package/dist/private-json.d.ts +3 -0
  261. package/dist/private-json.d.ts.map +1 -0
  262. package/dist/private-json.js +62 -0
  263. package/dist/promptEnhancer/index.d.ts +3 -0
  264. package/dist/promptEnhancer/index.d.ts.map +1 -0
  265. package/dist/promptEnhancer/index.js +5 -0
  266. package/dist/promptEnhancer/orchestrate.d.ts +2 -0
  267. package/dist/promptEnhancer/orchestrate.d.ts.map +1 -0
  268. package/dist/promptEnhancer/orchestrate.js +5 -0
  269. package/dist/promptEnhancer/routes.d.ts +9 -0
  270. package/dist/promptEnhancer/routes.d.ts.map +1 -0
  271. package/dist/promptEnhancer/routes.js +205 -0
  272. package/dist/qualityIntelligence/capsuleAdapter.d.ts +27 -0
  273. package/dist/qualityIntelligence/capsuleAdapter.d.ts.map +1 -0
  274. package/dist/qualityIntelligence/capsuleAdapter.js +57 -0
  275. package/dist/qualityIntelligence/connectorAuthorization.d.ts +22 -0
  276. package/dist/qualityIntelligence/connectorAuthorization.d.ts.map +1 -0
  277. package/dist/qualityIntelligence/connectorAuthorization.js +35 -0
  278. package/dist/qualityIntelligence/connectorErrors.d.ts +16 -0
  279. package/dist/qualityIntelligence/connectorErrors.d.ts.map +1 -0
  280. package/dist/qualityIntelligence/connectorErrors.js +56 -0
  281. package/dist/qualityIntelligence/connectorRoutes.d.ts +7 -0
  282. package/dist/qualityIntelligence/connectorRoutes.d.ts.map +1 -0
  283. package/dist/qualityIntelligence/connectorRoutes.js +167 -0
  284. package/dist/qualityIntelligence/editRoutes.d.ts +5 -0
  285. package/dist/qualityIntelligence/editRoutes.d.ts.map +1 -0
  286. package/dist/qualityIntelligence/editRoutes.js +293 -0
  287. package/dist/qualityIntelligence/exportAssembly.d.ts +22 -0
  288. package/dist/qualityIntelligence/exportAssembly.d.ts.map +1 -0
  289. package/dist/qualityIntelligence/exportAssembly.js +352 -0
  290. package/dist/qualityIntelligence/exportRoutes.d.ts +5 -0
  291. package/dist/qualityIntelligence/exportRoutes.d.ts.map +1 -0
  292. package/dist/qualityIntelligence/exportRoutes.js +320 -0
  293. package/dist/qualityIntelligence/figma/figmaConcurrency.d.ts +8 -0
  294. package/dist/qualityIntelligence/figma/figmaConcurrency.d.ts.map +1 -0
  295. package/dist/qualityIntelligence/figma/figmaConcurrency.js +34 -0
  296. package/dist/qualityIntelligence/figma/figmaConnector.d.ts +65 -0
  297. package/dist/qualityIntelligence/figma/figmaConnector.d.ts.map +1 -0
  298. package/dist/qualityIntelligence/figma/figmaConnector.js +184 -0
  299. package/dist/qualityIntelligence/figma/figmaConnectorAudit.d.ts +52 -0
  300. package/dist/qualityIntelligence/figma/figmaConnectorAudit.d.ts.map +1 -0
  301. package/dist/qualityIntelligence/figma/figmaConnectorAudit.js +63 -0
  302. package/dist/qualityIntelligence/figma/figmaConnectorErrors.d.ts +31 -0
  303. package/dist/qualityIntelligence/figma/figmaConnectorErrors.d.ts.map +1 -0
  304. package/dist/qualityIntelligence/figma/figmaConnectorErrors.js +220 -0
  305. package/dist/qualityIntelligence/figma/figmaConnectorMetrics.d.ts +44 -0
  306. package/dist/qualityIntelligence/figma/figmaConnectorMetrics.d.ts.map +1 -0
  307. package/dist/qualityIntelligence/figma/figmaConnectorMetrics.js +49 -0
  308. package/dist/qualityIntelligence/figma/figmaConsent.d.ts +39 -0
  309. package/dist/qualityIntelligence/figma/figmaConsent.d.ts.map +1 -0
  310. package/dist/qualityIntelligence/figma/figmaConsent.js +62 -0
  311. package/dist/qualityIntelligence/figma/figmaHttpPort.d.ts +28 -0
  312. package/dist/qualityIntelligence/figma/figmaHttpPort.d.ts.map +1 -0
  313. package/dist/qualityIntelligence/figma/figmaHttpPort.js +70 -0
  314. package/dist/qualityIntelligence/figma/figmaObservedActions.d.ts +49 -0
  315. package/dist/qualityIntelligence/figma/figmaObservedActions.d.ts.map +1 -0
  316. package/dist/qualityIntelligence/figma/figmaObservedActions.js +89 -0
  317. package/dist/qualityIntelligence/figma/figmaReadiness.d.ts +32 -0
  318. package/dist/qualityIntelligence/figma/figmaReadiness.d.ts.map +1 -0
  319. package/dist/qualityIntelligence/figma/figmaReadiness.js +67 -0
  320. package/dist/qualityIntelligence/figma/figmaRenderPort.d.ts +29 -0
  321. package/dist/qualityIntelligence/figma/figmaRenderPort.d.ts.map +1 -0
  322. package/dist/qualityIntelligence/figma/figmaRenderPort.js +93 -0
  323. package/dist/qualityIntelligence/figma/figmaResnapshot.d.ts +28 -0
  324. package/dist/qualityIntelligence/figma/figmaResnapshot.d.ts.map +1 -0
  325. package/dist/qualityIntelligence/figma/figmaResnapshot.js +38 -0
  326. package/dist/qualityIntelligence/figma/figmaRetry.d.ts +31 -0
  327. package/dist/qualityIntelligence/figma/figmaRetry.d.ts.map +1 -0
  328. package/dist/qualityIntelligence/figma/figmaRetry.js +62 -0
  329. package/dist/qualityIntelligence/figma/figmaScopeRef.d.ts +9 -0
  330. package/dist/qualityIntelligence/figma/figmaScopeRef.d.ts.map +1 -0
  331. package/dist/qualityIntelligence/figma/figmaScopeRef.js +18 -0
  332. package/dist/qualityIntelligence/figma/figmaScopedPagination.d.ts +86 -0
  333. package/dist/qualityIntelligence/figma/figmaScopedPagination.d.ts.map +1 -0
  334. package/dist/qualityIntelligence/figma/figmaScopedPagination.js +308 -0
  335. package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.d.ts +31 -0
  336. package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.d.ts.map +1 -0
  337. package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.js +314 -0
  338. package/dist/qualityIntelligence/figma/figmaSnapshotHash.d.ts +18 -0
  339. package/dist/qualityIntelligence/figma/figmaSnapshotHash.d.ts.map +1 -0
  340. package/dist/qualityIntelligence/figma/figmaSnapshotHash.js +63 -0
  341. package/dist/qualityIntelligence/figma/figmaSnapshotTypes.d.ts +65 -0
  342. package/dist/qualityIntelligence/figma/figmaSnapshotTypes.d.ts.map +1 -0
  343. package/dist/qualityIntelligence/figma/figmaSnapshotTypes.js +13 -0
  344. package/dist/qualityIntelligence/figma/figmaTokenSource.d.ts +9 -0
  345. package/dist/qualityIntelligence/figma/figmaTokenSource.d.ts.map +1 -0
  346. package/dist/qualityIntelligence/figma/figmaTokenSource.js +61 -0
  347. package/dist/qualityIntelligence/figma/figmaTokenStore.d.ts +19 -0
  348. package/dist/qualityIntelligence/figma/figmaTokenStore.d.ts.map +1 -0
  349. package/dist/qualityIntelligence/figma/figmaTokenStore.js +156 -0
  350. package/dist/qualityIntelligence/figma/figmaUrl.d.ts +6 -0
  351. package/dist/qualityIntelligence/figma/figmaUrl.d.ts.map +1 -0
  352. package/dist/qualityIntelligence/figma/figmaUrl.js +36 -0
  353. package/dist/qualityIntelligence/figma/index.d.ts +20 -0
  354. package/dist/qualityIntelligence/figma/index.d.ts.map +1 -0
  355. package/dist/qualityIntelligence/figma/index.js +26 -0
  356. package/dist/qualityIntelligence/figmaCodegenRoutes.d.ts +28 -0
  357. package/dist/qualityIntelligence/figmaCodegenRoutes.d.ts.map +1 -0
  358. package/dist/qualityIntelligence/figmaCodegenRoutes.js +165 -0
  359. package/dist/qualityIntelligence/figmaSnapshotAdapter.d.ts +55 -0
  360. package/dist/qualityIntelligence/figmaSnapshotAdapter.d.ts.map +1 -0
  361. package/dist/qualityIntelligence/figmaSnapshotAdapter.js +219 -0
  362. package/dist/qualityIntelligence/figmaSnapshotOrchestration.d.ts +64 -0
  363. package/dist/qualityIntelligence/figmaSnapshotOrchestration.d.ts.map +1 -0
  364. package/dist/qualityIntelligence/figmaSnapshotOrchestration.js +203 -0
  365. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +112 -0
  366. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -0
  367. package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1063 -0
  368. package/dist/qualityIntelligence/figmaSnapshotScreenIds.d.ts +19 -0
  369. package/dist/qualityIntelligence/figmaSnapshotScreenIds.d.ts.map +1 -0
  370. package/dist/qualityIntelligence/figmaSnapshotScreenIds.js +75 -0
  371. package/dist/qualityIntelligence/generationPort.d.ts +15 -0
  372. package/dist/qualityIntelligence/generationPort.d.ts.map +1 -0
  373. package/dist/qualityIntelligence/generationPort.js +185 -0
  374. package/dist/qualityIntelligence/handoffErrors.d.ts +9 -0
  375. package/dist/qualityIntelligence/handoffErrors.d.ts.map +1 -0
  376. package/dist/qualityIntelligence/handoffErrors.js +21 -0
  377. package/dist/qualityIntelligence/handoffRoutes.d.ts +15 -0
  378. package/dist/qualityIntelligence/handoffRoutes.d.ts.map +1 -0
  379. package/dist/qualityIntelligence/handoffRoutes.js +341 -0
  380. package/dist/qualityIntelligence/index.d.ts +17 -0
  381. package/dist/qualityIntelligence/index.d.ts.map +1 -0
  382. package/dist/qualityIntelligence/index.js +36 -0
  383. package/dist/qualityIntelligence/judgePort.d.ts +30 -0
  384. package/dist/qualityIntelligence/judgePort.d.ts.map +1 -0
  385. package/dist/qualityIntelligence/judgePort.js +326 -0
  386. package/dist/qualityIntelligence/modelSelection.d.ts +58 -0
  387. package/dist/qualityIntelligence/modelSelection.d.ts.map +1 -0
  388. package/dist/qualityIntelligence/modelSelection.js +148 -0
  389. package/dist/qualityIntelligence/reCheckRoutes.d.ts +6 -0
  390. package/dist/qualityIntelligence/reCheckRoutes.d.ts.map +1 -0
  391. package/dist/qualityIntelligence/reCheckRoutes.js +1157 -0
  392. package/dist/qualityIntelligence/retentionEnforcement.d.ts +13 -0
  393. package/dist/qualityIntelligence/retentionEnforcement.d.ts.map +1 -0
  394. package/dist/qualityIntelligence/retentionEnforcement.js +47 -0
  395. package/dist/qualityIntelligence/retentionRoutes.d.ts +8 -0
  396. package/dist/qualityIntelligence/retentionRoutes.d.ts.map +1 -0
  397. package/dist/qualityIntelligence/retentionRoutes.js +74 -0
  398. package/dist/qualityIntelligence/reviewRoutes.d.ts +5 -0
  399. package/dist/qualityIntelligence/reviewRoutes.d.ts.map +1 -0
  400. package/dist/qualityIntelligence/reviewRoutes.js +145 -0
  401. package/dist/qualityIntelligence/reviewStore.d.ts +75 -0
  402. package/dist/qualityIntelligence/reviewStore.d.ts.map +1 -0
  403. package/dist/qualityIntelligence/reviewStore.js +170 -0
  404. package/dist/qualityIntelligence/runExecution.d.ts +36 -0
  405. package/dist/qualityIntelligence/runExecution.d.ts.map +1 -0
  406. package/dist/qualityIntelligence/runExecution.js +180 -0
  407. package/dist/qualityIntelligence/runIngestion.d.ts +70 -0
  408. package/dist/qualityIntelligence/runIngestion.d.ts.map +1 -0
  409. package/dist/qualityIntelligence/runIngestion.js +1235 -0
  410. package/dist/qualityIntelligence/runRegistry.d.ts +31 -0
  411. package/dist/qualityIntelligence/runRegistry.d.ts.map +1 -0
  412. package/dist/qualityIntelligence/runRegistry.js +66 -0
  413. package/dist/qualityIntelligence/runRoutes.d.ts +16 -0
  414. package/dist/qualityIntelligence/runRoutes.d.ts.map +1 -0
  415. package/dist/qualityIntelligence/runRoutes.js +357 -0
  416. package/dist/qualityIntelligence/traceabilityRoutes.d.ts +5 -0
  417. package/dist/qualityIntelligence/traceabilityRoutes.d.ts.map +1 -0
  418. package/dist/qualityIntelligence/traceabilityRoutes.js +173 -0
  419. package/dist/qualityIntelligence/uiRoutes.d.ts +7 -0
  420. package/dist/qualityIntelligence/uiRoutes.d.ts.map +1 -0
  421. package/dist/qualityIntelligence/uiRoutes.js +336 -0
  422. package/dist/read-handlers.d.ts +9 -0
  423. package/dist/read-handlers.d.ts.map +1 -0
  424. package/dist/read-handlers.js +265 -0
  425. package/dist/relationship-handlers.d.ts +191 -0
  426. package/dist/relationship-handlers.d.ts.map +1 -0
  427. package/dist/relationship-handlers.js +0 -0
  428. package/dist/routes.d.ts +37 -0
  429. package/dist/routes.d.ts.map +1 -0
  430. package/dist/routes.js +507 -0
  431. package/dist/run-engine.d.ts +25 -0
  432. package/dist/run-engine.d.ts.map +1 -0
  433. package/dist/run-engine.js +385 -0
  434. package/dist/run-handlers.d.ts +9 -0
  435. package/dist/run-handlers.d.ts.map +1 -0
  436. package/dist/run-handlers.js +465 -0
  437. package/dist/run-request.d.ts +17 -0
  438. package/dist/run-request.d.ts.map +1 -0
  439. package/dist/run-request.js +219 -0
  440. package/dist/runs.d.ts +47 -0
  441. package/dist/runs.d.ts.map +1 -0
  442. package/dist/runs.js +100 -0
  443. package/dist/server.d.ts +13 -0
  444. package/dist/server.d.ts.map +1 -0
  445. package/dist/server.js +152 -0
  446. package/dist/sink.d.ts +28 -0
  447. package/dist/sink.d.ts.map +1 -0
  448. package/dist/sink.js +80 -0
  449. package/dist/sse-write.d.ts +9 -0
  450. package/dist/sse-write.d.ts.map +1 -0
  451. package/dist/sse-write.js +26 -0
  452. package/dist/sse.d.ts +8 -0
  453. package/dist/sse.d.ts.map +1 -0
  454. package/dist/sse.js +27 -0
  455. package/dist/static.d.ts +5 -0
  456. package/dist/static.d.ts.map +1 -0
  457. package/dist/static.js +76 -0
  458. package/dist/store/chats.d.ts +17 -0
  459. package/dist/store/chats.d.ts.map +1 -0
  460. package/dist/store/chats.js +624 -0
  461. package/dist/store/db.d.ts +11 -0
  462. package/dist/store/db.d.ts.map +1 -0
  463. package/dist/store/db.js +203 -0
  464. package/dist/store/errors.d.ts +13 -0
  465. package/dist/store/errors.d.ts.map +1 -0
  466. package/dist/store/errors.js +30 -0
  467. package/dist/store/index.d.ts +7 -0
  468. package/dist/store/index.d.ts.map +1 -0
  469. package/dist/store/index.js +6 -0
  470. package/dist/store/messages.d.ts +8 -0
  471. package/dist/store/messages.d.ts.map +1 -0
  472. package/dist/store/messages.js +149 -0
  473. package/dist/store/paths.d.ts +5 -0
  474. package/dist/store/paths.d.ts.map +1 -0
  475. package/dist/store/paths.js +84 -0
  476. package/dist/store/projects.d.ts +8 -0
  477. package/dist/store/projects.d.ts.map +1 -0
  478. package/dist/store/projects.js +59 -0
  479. package/dist/store/relationship-audit.d.ts +42 -0
  480. package/dist/store/relationship-audit.d.ts.map +1 -0
  481. package/dist/store/relationship-audit.js +155 -0
  482. package/dist/store/relationships.d.ts +191 -0
  483. package/dist/store/relationships.d.ts.map +1 -0
  484. package/dist/store/relationships.js +724 -0
  485. package/dist/store/schema.d.ts +4 -0
  486. package/dist/store/schema.d.ts.map +1 -0
  487. package/dist/store/schema.js +220 -0
  488. package/dist/store/types.d.ts +29 -0
  489. package/dist/store/types.d.ts.map +1 -0
  490. package/dist/store/types.js +8 -0
  491. package/dist/store/validation.d.ts +7 -0
  492. package/dist/store/validation.d.ts.map +1 -0
  493. package/dist/store/validation.js +117 -0
  494. package/dist/store-handlers.d.ts +17 -0
  495. package/dist/store-handlers.d.ts.map +1 -0
  496. package/dist/store-handlers.js +872 -0
  497. package/dist/terminal-errors.d.ts +22 -0
  498. package/dist/terminal-errors.d.ts.map +1 -0
  499. package/dist/terminal-errors.js +45 -0
  500. package/dist/terminal-evidence.d.ts +21 -0
  501. package/dist/terminal-evidence.d.ts.map +1 -0
  502. package/dist/terminal-evidence.js +65 -0
  503. package/dist/terminal-routes.d.ts +10 -0
  504. package/dist/terminal-routes.d.ts.map +1 -0
  505. package/dist/terminal-routes.js +219 -0
  506. package/dist/terminal.d.ts +68 -0
  507. package/dist/terminal.d.ts.map +1 -0
  508. package/dist/terminal.js +855 -0
  509. package/package.json +52 -0
@@ -0,0 +1,872 @@
1
+ // ADR-0013 D7 — Route handlers for UI-local store routes. All inputs are validated;
2
+ // every error path uses the redacted `{ error: { code, message } }` envelope; SECURITY_HEADERS are
3
+ // applied uniformly by the server layer. JSON body reading is bounded by MAX_STORE_BODY_BYTES.
4
+ import { realpathSync, statSync } from "node:fs";
5
+ import { isAbsolute, relative, resolve } from "node:path";
6
+ import { errorBody } from "./routes.js";
7
+ import { currentGatewayConfig, currentGroundingLimits } from "./deps.js";
8
+ import { findCapability, findConfiguredCapability } from "@oscharko-dev/keiko-model-gateway";
9
+ import { UiStoreError, assertUiDbOutsideProject, isProjectAvailable, validateProjectPath, } from "./store/index.js";
10
+ import { pathIsDenied } from "./files-deny.js";
11
+ import { clearGroundedContextIndexesForConversation, clearGroundedContextIndexesForWorkspace, } from "./grounded-context-index.js";
12
+ import { clearGroundedTurnsForConversation, clearGroundedTurnsForWorkspace, } from "./grounded-turn-registry.js";
13
+ import { isLegacyEmptyAssistantPlaceholder } from "./assistant-response.js";
14
+ // Issue #184 — workspace-relative path gate. isValidScopePath is the canonical validator from
15
+ // @oscharko-dev/keiko-contracts/connected-context (issue #178). Reusing it here keeps the BFF
16
+ // boundary aligned with the rest of the connected-repo surface and avoids regex drift.
17
+ import { SELECTED_SCOPE_KINDS, isValidScopePath, } from "@oscharko-dev/keiko-contracts/connected-context";
18
+ const MAX_STORE_BODY_BYTES = 256_000;
19
+ const SELECTED_SCOPE_KIND_SET = new Set(SELECTED_SCOPE_KINDS);
20
+ const DEFAULT_CHAT_LIST_LIMIT = 100;
21
+ const MAX_CHAT_LIST_LIMIT = 200;
22
+ const DEFAULT_MESSAGE_LIST_LIMIT = 200;
23
+ const MAX_MESSAGE_LIST_LIMIT = 500;
24
+ class BodyTooLargeError extends Error {
25
+ constructor() {
26
+ super("body too large");
27
+ this.name = "BodyTooLargeError";
28
+ }
29
+ }
30
+ // ──────────────────────────────────────────────────────────────────────────
31
+ // Body parsing helpers
32
+ // ──────────────────────────────────────────────────────────────────────────
33
+ function readBody(req) {
34
+ return new Promise((resolve, reject) => {
35
+ const chunks = [];
36
+ let total = 0;
37
+ let capped = false;
38
+ req.on("data", (chunk) => {
39
+ total += chunk.length;
40
+ if (total > MAX_STORE_BODY_BYTES) {
41
+ if (!capped) {
42
+ capped = true;
43
+ chunks.length = 0;
44
+ reject(new BodyTooLargeError());
45
+ req.resume();
46
+ }
47
+ return;
48
+ }
49
+ chunks.push(chunk);
50
+ });
51
+ req.on("end", () => {
52
+ if (!capped)
53
+ resolve(Buffer.concat(chunks).toString("utf8"));
54
+ });
55
+ req.on("error", reject);
56
+ });
57
+ }
58
+ async function readJsonObject(req) {
59
+ let raw;
60
+ try {
61
+ raw = await readBody(req);
62
+ }
63
+ catch (error) {
64
+ if (error instanceof BodyTooLargeError)
65
+ throw error;
66
+ throw new InvalidRequest("Failed to read request body.");
67
+ }
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(raw);
71
+ }
72
+ catch {
73
+ throw new InvalidRequest("Request body is not valid JSON.");
74
+ }
75
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
76
+ throw new InvalidRequest("Request body must be a JSON object.");
77
+ }
78
+ return parsed;
79
+ }
80
+ class InvalidRequest extends Error {
81
+ constructor(message) {
82
+ super(message);
83
+ this.name = "InvalidRequest";
84
+ }
85
+ }
86
+ // ──────────────────────────────────────────────────────────────────────────
87
+ // Error mapping
88
+ // ──────────────────────────────────────────────────────────────────────────
89
+ function uiStoreErrorResult(error) {
90
+ return { status: error.status, body: errorBody(error.code, error.message) };
91
+ }
92
+ function badRequest(code, message) {
93
+ return { status: 400, body: errorBody(code, message) };
94
+ }
95
+ function notFoundResult(message) {
96
+ return { status: 404, body: errorBody("not_found", message) };
97
+ }
98
+ function forbiddenResult(code, message) {
99
+ return { status: 403, body: errorBody(code, message) };
100
+ }
101
+ function payloadTooLarge() {
102
+ return {
103
+ status: 413,
104
+ body: errorBody("payload_too_large", "Request body exceeds the size limit."),
105
+ };
106
+ }
107
+ async function runHandler(worker) {
108
+ try {
109
+ return await worker();
110
+ }
111
+ catch (error) {
112
+ if (error instanceof BodyTooLargeError)
113
+ return payloadTooLarge();
114
+ if (error instanceof InvalidRequest)
115
+ return badRequest("invalid_request", error.message);
116
+ if (error instanceof UiStoreError)
117
+ return uiStoreErrorResult(error);
118
+ throw error;
119
+ }
120
+ }
121
+ // ──────────────────────────────────────────────────────────────────────────
122
+ // Field validators (typed narrowing from JSON)
123
+ // ──────────────────────────────────────────────────────────────────────────
124
+ function requireString(body, name) {
125
+ const v = body[name];
126
+ if (typeof v !== "string" || v.length === 0) {
127
+ throw new InvalidRequest(`Field "${name}" is required.`);
128
+ }
129
+ return v;
130
+ }
131
+ function optionalString(body, name) {
132
+ const v = body[name];
133
+ if (v === undefined)
134
+ return undefined;
135
+ if (typeof v !== "string")
136
+ throw new InvalidRequest(`Field "${name}" must be a string.`);
137
+ return v;
138
+ }
139
+ function assertChatModelId(deps, modelId) {
140
+ const config = currentGatewayConfig(deps);
141
+ const capability = config === undefined ? findCapability(modelId) : findConfiguredCapability(config, modelId);
142
+ if (capability?.kind !== "chat") {
143
+ throw new InvalidRequest('Field "selectedModel" must be a configured chat model id.');
144
+ }
145
+ }
146
+ function requireChatModelId(deps, body, name) {
147
+ const modelId = requireString(body, name);
148
+ assertChatModelId(deps, modelId);
149
+ return modelId;
150
+ }
151
+ function optionalChatModelId(deps, body, name) {
152
+ const modelId = optionalString(body, name);
153
+ if (modelId !== undefined)
154
+ assertChatModelId(deps, modelId);
155
+ return modelId;
156
+ }
157
+ function optionalBoolean(body, name) {
158
+ const v = body[name];
159
+ if (v === undefined)
160
+ return undefined;
161
+ if (typeof v !== "boolean")
162
+ throw new InvalidRequest(`Field "${name}" must be a boolean.`);
163
+ return v;
164
+ }
165
+ function requireNumber(body, name) {
166
+ const v = body[name];
167
+ if (typeof v !== "number" || !Number.isFinite(v)) {
168
+ throw new InvalidRequest(`Field "${name}" must be a finite number.`);
169
+ }
170
+ return v;
171
+ }
172
+ function requireObject(body, name) {
173
+ const v = body[name];
174
+ if (typeof v !== "object" || v === null || Array.isArray(v)) {
175
+ throw new InvalidRequest(`Field "${name}" must be a JSON object.`);
176
+ }
177
+ return v;
178
+ }
179
+ const ROLES = new Set(["user", "assistant", "system"]);
180
+ const WORKFLOW_STATUSES = new Set([
181
+ "pending",
182
+ "running",
183
+ "completed",
184
+ "failed",
185
+ "cancelled",
186
+ ]);
187
+ // Issue #66 — taskType labels non-workflow harness runs (verify, explain-plan) so the chat can
188
+ // render a non-ambiguous label. Constrained to a-z/0-9/`-` so the value stays URL-safe and
189
+ // matches the BFF descriptor taskType identifiers (verify, explain-plan).
190
+ const MAX_TASK_TYPE = 64;
191
+ const TASK_TYPE_RE = /^[a-z][a-z0-9-]*$/;
192
+ function optionalTaskType(body) {
193
+ const v = body.taskType;
194
+ if (v === undefined)
195
+ return undefined;
196
+ if (typeof v !== "string" ||
197
+ v.length === 0 ||
198
+ v.length > MAX_TASK_TYPE ||
199
+ !TASK_TYPE_RE.test(v)) {
200
+ throw new InvalidRequest('Field "taskType" must match [a-z][a-z0-9-]* (≤ 64 chars).');
201
+ }
202
+ return v;
203
+ }
204
+ function requireRole(body) {
205
+ const v = body.role;
206
+ if (typeof v !== "string" || !ROLES.has(v)) {
207
+ throw new InvalidRequest('Field "role" must be one of user, assistant, system.');
208
+ }
209
+ return v;
210
+ }
211
+ function optionalWorkflowStatus(body) {
212
+ const v = body.workflowStatus;
213
+ if (v === undefined)
214
+ return undefined;
215
+ if (typeof v !== "string" || !WORKFLOW_STATUSES.has(v)) {
216
+ throw new InvalidRequest('Field "workflowStatus" is not a recognized value.');
217
+ }
218
+ return v;
219
+ }
220
+ function requireQuery(ctx, name) {
221
+ const v = ctx.url.searchParams.get(name);
222
+ if (v === null || v.length === 0)
223
+ throw new InvalidRequest(`Query "${name}" is required.`);
224
+ return v;
225
+ }
226
+ function optionalBoundedQueryInteger(ctx, name, fallback, max) {
227
+ const raw = ctx.url.searchParams.get(name);
228
+ if (raw === null || raw.length === 0)
229
+ return fallback;
230
+ if (!/^\d+$/.test(raw)) {
231
+ throw new InvalidRequest(`Query "${name}" must be a positive integer.`);
232
+ }
233
+ const value = Number(raw);
234
+ if (!Number.isSafeInteger(value) || value <= 0 || value > max) {
235
+ throw new InvalidRequest(`Query "${name}" must be between 1 and ${String(max)}.`);
236
+ }
237
+ return value;
238
+ }
239
+ function projectWithAvailability(p) {
240
+ return { ...p, available: isProjectAvailable(p) };
241
+ }
242
+ function putPreferredProjectFirst(projects, preferredProjectPath) {
243
+ if (preferredProjectPath === undefined)
244
+ return projects;
245
+ const preferred = projects.find((project) => project.path === preferredProjectPath);
246
+ if (preferred === undefined)
247
+ return projects;
248
+ return [preferred, ...projects.filter((project) => project.path !== preferredProjectPath)];
249
+ }
250
+ function chatBelongsToProject(deps, projectPath, chatId) {
251
+ return deps.store.findChatById(chatId)?.projectPath === projectPath;
252
+ }
253
+ // Epic #177 audit: the chat PATCH path scanned every project's chat list per request
254
+ // (O(projects × chats)). The chat id is unique across projects, so `UiStore.findChatById` is a
255
+ // single-row SELECT. Helper preserved so callers stay decoupled from the store interface.
256
+ function findChatById(deps, chatId) {
257
+ return deps.store.findChatById(chatId);
258
+ }
259
+ function messageBelongsToChat(deps, chatId, messageId) {
260
+ return deps.store.findMessageById(messageId)?.chatId === chatId;
261
+ }
262
+ // ──────────────────────────────────────────────────────────────────────────
263
+ // Route 13 — GET /api/projects
264
+ // ──────────────────────────────────────────────────────────────────────────
265
+ export function handleListProjects(_ctx, deps) {
266
+ const projects = putPreferredProjectFirst(deps.store.listProjects().map(projectWithAvailability), deps.preferredProjectPath);
267
+ return { status: 200, body: { projects } };
268
+ }
269
+ // ──────────────────────────────────────────────────────────────────────────
270
+ // Route 14 — POST /api/projects
271
+ // ──────────────────────────────────────────────────────────────────────────
272
+ export async function handleCreateProject(ctx, deps) {
273
+ return runHandler(async () => {
274
+ const body = await readJsonObject(ctx.req);
275
+ const path = requireString(body, "path");
276
+ const name = optionalString(body, "name");
277
+ const normalizedPath = validateProjectPath(path, { mustExist: true });
278
+ if (pathIsDenied(normalizedPath)) {
279
+ return forbiddenResult("DENIED", "The project path is excluded from Keiko's safe read surface.");
280
+ }
281
+ assertUiDbOutsideProject(deps.uiDbPath, normalizedPath);
282
+ const project = deps.store.createProject(normalizedPath, name);
283
+ return { status: 201, body: { project: projectWithAvailability(project) } };
284
+ });
285
+ }
286
+ // ──────────────────────────────────────────────────────────────────────────
287
+ // Route 15 — PATCH /api/projects?path=...
288
+ // ──────────────────────────────────────────────────────────────────────────
289
+ function buildProjectPatch(body) {
290
+ const name = optionalString(body, "name");
291
+ const favorite = optionalBoolean(body, "favorite");
292
+ return {
293
+ ...(name !== undefined ? { name } : {}),
294
+ ...(favorite !== undefined ? { favorite } : {}),
295
+ };
296
+ }
297
+ export async function handleUpdateProject(ctx, deps) {
298
+ return runHandler(async () => {
299
+ const targetPath = requireQuery(ctx, "path");
300
+ const body = await readJsonObject(ctx.req);
301
+ const patch = buildProjectPatch(body);
302
+ const project = deps.store.updateProject(targetPath, patch);
303
+ return { status: 200, body: { project: projectWithAvailability(project) } };
304
+ });
305
+ }
306
+ // ──────────────────────────────────────────────────────────────────────────
307
+ // Route 16 — DELETE /api/projects?path=...
308
+ // ──────────────────────────────────────────────────────────────────────────
309
+ export function handleDeleteProject(ctx, deps) {
310
+ return runHandlerSync(() => {
311
+ const targetPath = requireQuery(ctx, "path");
312
+ const normalizedPath = validateProjectPath(targetPath, { mustExist: false });
313
+ const deletedChats = deps.store.listChats(normalizedPath);
314
+ deps.store.deleteProject(normalizedPath);
315
+ for (const chat of deletedChats) {
316
+ clearGroundedContextIndexesForConversation(chat.id);
317
+ clearGroundedTurnsForConversation(chat.id);
318
+ }
319
+ clearGroundedContextIndexesForWorkspace(normalizedPath);
320
+ clearGroundedTurnsForWorkspace(normalizedPath);
321
+ return { status: 204, body: null };
322
+ });
323
+ }
324
+ function runHandlerSync(worker) {
325
+ try {
326
+ return worker();
327
+ }
328
+ catch (error) {
329
+ if (error instanceof InvalidRequest)
330
+ return badRequest("invalid_request", error.message);
331
+ if (error instanceof UiStoreError)
332
+ return uiStoreErrorResult(error);
333
+ throw error;
334
+ }
335
+ }
336
+ // ──────────────────────────────────────────────────────────────────────────
337
+ // Route 17 — GET /api/chats?projectPath=...
338
+ // ──────────────────────────────────────────────────────────────────────────
339
+ export function handleListChats(ctx, deps) {
340
+ return runHandlerSync(() => {
341
+ const projectPath = requireQuery(ctx, "projectPath");
342
+ const limit = optionalBoundedQueryInteger(ctx, "limit", DEFAULT_CHAT_LIST_LIMIT, MAX_CHAT_LIST_LIMIT);
343
+ const chats = deps.store.listChats(projectPath, limit);
344
+ return { status: 200, body: { chats } };
345
+ });
346
+ }
347
+ // ──────────────────────────────────────────────────────────────────────────
348
+ // Route 18 — POST /api/chats
349
+ // ──────────────────────────────────────────────────────────────────────────
350
+ export async function handleCreateChat(ctx, deps) {
351
+ return runHandler(async () => {
352
+ const body = await readJsonObject(ctx.req);
353
+ const projectPath = requireString(body, "projectPath");
354
+ const title = requireString(body, "title");
355
+ const selectedModel = requireChatModelId(deps, body, "selectedModel");
356
+ const branchLabel = optionalString(body, "branchLabel");
357
+ const chat = deps.store.createChat(projectPath, title, selectedModel, branchLabel === undefined ? undefined : { branchLabel });
358
+ return { status: 201, body: { chat } };
359
+ });
360
+ }
361
+ // ──────────────────────────────────────────────────────────────────────────
362
+ // Route 19 — PATCH /api/chats?id=...
363
+ // ──────────────────────────────────────────────────────────────────────────
364
+ // Issue #184 — bound the number of paths the BFF will accept on one binding. Higher than the
365
+ // realistic ad-hoc selection size (Files window selection caps at a handful) but low enough to
366
+ // prevent JSON-blob inflation in connected_scope_paths. The store enforces the same cap as a
367
+ // defense-in-depth subset of this gate.
368
+ const MAX_CONNECTED_SCOPE_PATHS = 50;
369
+ function isSelectedScopeKind(value) {
370
+ return typeof value === "string" && SELECTED_SCOPE_KIND_SET.has(value);
371
+ }
372
+ function validateScopeKind(value) {
373
+ if (!isSelectedScopeKind(value)) {
374
+ throw new InvalidRequest('Field "connectedScope.kind" must be a recognized scope kind.');
375
+ }
376
+ return value;
377
+ }
378
+ function assertScopePathCount(kind, count) {
379
+ if (kind === "workspace-root") {
380
+ if (count !== 0) {
381
+ throw new InvalidRequest('Field "connectedScope.relativePaths" must be empty for repository scope.');
382
+ }
383
+ return;
384
+ }
385
+ if (kind === "directory") {
386
+ if (count !== 1) {
387
+ throw new InvalidRequest('Field "connectedScope.relativePaths" must contain exactly one folder path.');
388
+ }
389
+ return;
390
+ }
391
+ if (count === 0) {
392
+ throw new InvalidRequest('Field "connectedScope.relativePaths" must not be empty.');
393
+ }
394
+ if (count > MAX_CONNECTED_SCOPE_PATHS) {
395
+ throw new InvalidRequest(`Field "connectedScope.relativePaths" must have at most ${String(MAX_CONNECTED_SCOPE_PATHS)} entries.`);
396
+ }
397
+ }
398
+ // Issue #184 — validates the relativePaths sub-array. Pulled out of optionalConnectedScope so
399
+ // the outer function's cyclomatic complexity stays within the project's ≤10 bound.
400
+ function validateScopeRelativePaths(kind, paths) {
401
+ if (!Array.isArray(paths)) {
402
+ throw new InvalidRequest('Field "connectedScope.relativePaths" must be an array.');
403
+ }
404
+ assertScopePathCount(kind, paths.length);
405
+ const validated = [];
406
+ for (const entry of paths) {
407
+ if (typeof entry !== "string") {
408
+ throw new InvalidRequest('Field "connectedScope.relativePaths" entries must be strings.');
409
+ }
410
+ if (!isValidScopePath(entry, { mustBeRelative: true })) {
411
+ throw new InvalidRequest('Field "connectedScope.relativePaths" entry is not a valid workspace-relative path.');
412
+ }
413
+ validated.push(entry);
414
+ }
415
+ return validated;
416
+ }
417
+ function isContainedPath(root, target) {
418
+ const rootCmp = process.platform === "win32" ? root.toLowerCase() : root;
419
+ const targetCmp = process.platform === "win32" ? target.toLowerCase() : target;
420
+ const rel = relative(rootCmp, targetCmp);
421
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
422
+ }
423
+ function scopeTargetPath(realProjectRoot, relativePath) {
424
+ if (relativePath.length === 0)
425
+ return realProjectRoot;
426
+ return resolve(realProjectRoot, ...relativePath.split("/").filter((part) => part.length > 0));
427
+ }
428
+ function scopeRelativePath(realProjectRoot, absolutePath) {
429
+ return relative(realProjectRoot, absolutePath).split("\\").join("/");
430
+ }
431
+ function assertScopePathMetadataSafe(deps, relativePath) {
432
+ if (pathIsDenied(relativePath)) {
433
+ throw new InvalidRequest("Connected scope is excluded from Keiko's safe read surface.");
434
+ }
435
+ const redacted = deps.redactor(relativePath);
436
+ if (typeof redacted === "string" && redacted !== relativePath) {
437
+ throw new InvalidRequest("Connected scope path contains credential-shaped metadata.");
438
+ }
439
+ }
440
+ function validateScopePathAccess(deps, realProjectRoot, kind, entry) {
441
+ assertScopePathMetadataSafe(deps, entry);
442
+ const candidate = scopeTargetPath(realProjectRoot, entry);
443
+ let targetReal;
444
+ try {
445
+ targetReal = realpathSync(candidate);
446
+ }
447
+ catch {
448
+ throw new InvalidRequest("Connected scope path is not accessible from the selected project.");
449
+ }
450
+ if (!isContainedPath(realProjectRoot, targetReal)) {
451
+ throw new InvalidRequest("Connected scope path must stay inside the selected project.");
452
+ }
453
+ if (pathIsDenied(scopeRelativePath(realProjectRoot, targetReal))) {
454
+ throw new InvalidRequest("Connected scope is excluded from Keiko's safe read surface.");
455
+ }
456
+ let info;
457
+ try {
458
+ info = statSync(targetReal);
459
+ }
460
+ catch {
461
+ throw new InvalidRequest("Connected scope path is not accessible from the selected project.");
462
+ }
463
+ if (kind === "directory" && !info.isDirectory()) {
464
+ throw new InvalidRequest("Connected folder scope must reference a folder.");
465
+ }
466
+ if (!info.isDirectory() && !info.isFile()) {
467
+ throw new InvalidRequest("Connected scope path must reference a file or folder.");
468
+ }
469
+ }
470
+ function resolveRealRoot(rootInput, notAccessibleMessage) {
471
+ const root = validateProjectPath(rootInput, { mustExist: true });
472
+ try {
473
+ return { root, realRoot: realpathSync(root) };
474
+ }
475
+ catch {
476
+ throw new InvalidRequest(notAccessibleMessage);
477
+ }
478
+ }
479
+ // Epic #532 — a connected scope may carry its OWN absolute root pointing anywhere on the machine
480
+ // (a folder outside the chat's project, so non-developers can connect any folder). Validate it like
481
+ // a project root, then refuse credential/secret locations (deny-list) and credential-shaped path
482
+ // metadata so home-directory browsing can never bind a secret folder as a grounded scope.
483
+ function validateAccessibleRoot(deps, rootInput, notAccessibleMessage, deniedMessage) {
484
+ const { root, realRoot } = resolveRealRoot(rootInput, notAccessibleMessage);
485
+ if (pathIsDenied(root) || pathIsDenied(realRoot)) {
486
+ throw new InvalidRequest(deniedMessage);
487
+ }
488
+ const redacted = deps.redactor(realRoot);
489
+ if (typeof redacted === "string" && redacted !== realRoot) {
490
+ throw new InvalidRequest("Connected scope root contains credential-shaped metadata.");
491
+ }
492
+ return realRoot;
493
+ }
494
+ function validateConnectedScopeRoot(deps, rootInput) {
495
+ return validateAccessibleRoot(deps, rootInput, "Connected scope root is not accessible.", "Connected scope root is excluded from Keiko's safe read surface.");
496
+ }
497
+ function validateFallbackProjectRoot(deps, projectPath) {
498
+ return validateAccessibleRoot(deps, projectPath, "Selected project is not accessible.", "Selected project is excluded from Keiko's safe read surface.");
499
+ }
500
+ function validateConnectedScopeAccess(deps, chat, scope) {
501
+ const realRoot = scope.root !== undefined
502
+ ? validateConnectedScopeRoot(deps, scope.root)
503
+ : validateFallbackProjectRoot(deps, chat.projectPath);
504
+ if (scope.kind === "workspace-root") {
505
+ return scope.root === undefined ? scope : { ...scope, root: realRoot };
506
+ }
507
+ for (const entry of scope.relativePaths) {
508
+ validateScopePathAccess(deps, realRoot, scope.kind, entry);
509
+ }
510
+ return scope.root === undefined ? scope : { ...scope, root: realRoot };
511
+ }
512
+ function validateScopeConnectedAtMs(value) {
513
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
514
+ throw new InvalidRequest('Field "connectedScope.connectedAtMs" must be a finite non-negative integer.');
515
+ }
516
+ return value;
517
+ }
518
+ // Epic #532 — shape check for the optional connected-scope root. Deep validation (existence,
519
+ // deny-list, realpath containment) runs in validateConnectedScopeAccess against the live filesystem.
520
+ function validateOptionalScopeRoot(value) {
521
+ if (value === undefined)
522
+ return undefined;
523
+ if (typeof value !== "string" || value.trim().length === 0) {
524
+ throw new InvalidRequest('Field "connectedScope.root" must be a non-empty string when provided.');
525
+ }
526
+ return value.trim();
527
+ }
528
+ function parseCapsuleLocalKnowledgeScope(scope, connectedAtMs) {
529
+ if (typeof scope.capsuleId !== "string" || scope.capsuleId.trim().length === 0) {
530
+ throw new InvalidRequest('Field "localKnowledgeScope.capsuleId" must be a non-empty string.');
531
+ }
532
+ return {
533
+ kind: "capsule",
534
+ capsuleId: scope.capsuleId.trim(),
535
+ connectedAtMs,
536
+ };
537
+ }
538
+ function parseCapsuleSetLocalKnowledgeScope(scope, connectedAtMs) {
539
+ if (typeof scope.capsuleSetId !== "string" || scope.capsuleSetId.trim().length === 0) {
540
+ throw new InvalidRequest('Field "localKnowledgeScope.capsuleSetId" must be a non-empty string.');
541
+ }
542
+ return {
543
+ kind: "capsule-set",
544
+ capsuleSetId: scope.capsuleSetId.trim(),
545
+ connectedAtMs,
546
+ };
547
+ }
548
+ // Shared per-connector shape validator. Used by both the single localKnowledgeScope field and each
549
+ // entry of the Epic #189 localKnowledgeScopes list, so the two surfaces never drift.
550
+ function parseLocalKnowledgeScopeObject(raw) {
551
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
552
+ throw new InvalidRequest('Field "localKnowledgeScope" must be an object or null.');
553
+ }
554
+ const scope = raw;
555
+ const connectedAtMs = validateScopeConnectedAtMs(scope.connectedAtMs);
556
+ if (scope.kind === "capsule") {
557
+ return parseCapsuleLocalKnowledgeScope(scope, connectedAtMs);
558
+ }
559
+ if (scope.kind === "capsule-set") {
560
+ return parseCapsuleSetLocalKnowledgeScope(scope, connectedAtMs);
561
+ }
562
+ throw new InvalidRequest('Field "localKnowledgeScope.kind" must be "capsule" or "capsule-set".');
563
+ }
564
+ function optionalLocalKnowledgeScope(body) {
565
+ if (!("localKnowledgeScope" in body))
566
+ return undefined;
567
+ const raw = body.localKnowledgeScope;
568
+ if (raw === null)
569
+ return null;
570
+ return parseLocalKnowledgeScopeObject(raw);
571
+ }
572
+ // Epic #189 — parse the multi-source connector list (capsules/capsule-sets). undefined → absent;
573
+ // null → clear all; array → fully validated list bounded by the runtime maxLocalKnowledgeSources
574
+ // cap. Each entry runs the same shape validator as the single field. Capsule existence is checked
575
+ // in the grounded path, not here (shape-only, like optionalConnectedScopes).
576
+ function optionalLocalKnowledgeScopes(body, maxSources) {
577
+ if (!("localKnowledgeScopes" in body))
578
+ return undefined;
579
+ const raw = body.localKnowledgeScopes;
580
+ if (raw === null)
581
+ return null;
582
+ if (!Array.isArray(raw)) {
583
+ throw new InvalidRequest('Field "localKnowledgeScopes" must be an array or null.');
584
+ }
585
+ if (raw.length > maxSources) {
586
+ throw new InvalidRequest(`Field "localKnowledgeScopes" must contain at most ${String(maxSources)} sources.`);
587
+ }
588
+ return raw.map((entry) => parseLocalKnowledgeScopeObject(entry));
589
+ }
590
+ // Issue #184 — three return states: undefined → field absent (leave unchanged); null →
591
+ // explicit clear (forward through to the store); ChatConnectedScope → fully validated value.
592
+ // All input has crossed the wire and is `unknown` until proven otherwise.
593
+ // Shared per-scope shape validator. Used by both the single connectedScope field and each entry
594
+ // of the Epic #532 connectedScopes list, so the two surfaces never drift.
595
+ function parseConnectedScopeObject(raw) {
596
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
597
+ throw new InvalidRequest('Field "connectedScope" must be an object or null.');
598
+ }
599
+ const scope = raw;
600
+ const kind = validateScopeKind(scope.kind);
601
+ const relativePaths = validateScopeRelativePaths(kind, scope.relativePaths);
602
+ const connectedAtMs = validateScopeConnectedAtMs(scope.connectedAtMs);
603
+ const root = validateOptionalScopeRoot(scope.root);
604
+ return { kind, relativePaths, connectedAtMs, ...(root !== undefined ? { root } : {}) };
605
+ }
606
+ function optionalConnectedScope(body) {
607
+ if (!("connectedScope" in body))
608
+ return undefined;
609
+ const raw = body.connectedScope;
610
+ if (raw === null)
611
+ return null;
612
+ return parseConnectedScopeObject(raw);
613
+ }
614
+ // Epic #532 — parse the multi-source connectedScopes list. undefined → field absent; null →
615
+ // clear all; array → fully validated list. Each entry runs the same shape validators (incl. the
616
+ // optional root) as the single field; the list is bounded by the runtime maxConnectedSources cap.
617
+ // Live-filesystem access (realpath + deny-list + redaction) for each scope runs later in
618
+ // handleUpdateChat.
619
+ function optionalConnectedScopes(body, maxSources) {
620
+ if (!("connectedScopes" in body))
621
+ return undefined;
622
+ const raw = body.connectedScopes;
623
+ if (raw === null)
624
+ return null;
625
+ if (!Array.isArray(raw)) {
626
+ throw new InvalidRequest('Field "connectedScopes" must be an array or null.');
627
+ }
628
+ if (raw.length > maxSources) {
629
+ throw new InvalidRequest(`Field "connectedScopes" must contain at most ${String(maxSources)} sources.`);
630
+ }
631
+ return raw.map((entry) => parseConnectedScopeObject(entry));
632
+ }
633
+ // Epic #189/#532 — the four grounding-source patch fields (connected folders + local-knowledge
634
+ // connectors, each single + plural). Extracted so buildChatPatch stays under the complexity gate.
635
+ // Receives deps so runtime-resolved grounding limits are used for the source-count caps.
636
+ function groundingScopePatchFields(body, deps) {
637
+ const limits = currentGroundingLimits(deps);
638
+ const connectedScope = optionalConnectedScope(body);
639
+ const connectedScopes = optionalConnectedScopes(body, limits.maxConnectedSources);
640
+ const localKnowledgeScope = optionalLocalKnowledgeScope(body);
641
+ const localKnowledgeScopes = optionalLocalKnowledgeScopes(body, limits.maxLocalKnowledgeSources);
642
+ return {
643
+ ...(connectedScope !== undefined ? { connectedScope } : {}),
644
+ ...(connectedScopes !== undefined ? { connectedScopes } : {}),
645
+ ...(localKnowledgeScope !== undefined ? { localKnowledgeScope } : {}),
646
+ ...(localKnowledgeScopes !== undefined ? { localKnowledgeScopes } : {}),
647
+ };
648
+ }
649
+ function buildChatPatch(deps, body) {
650
+ const title = optionalString(body, "title");
651
+ const selectedModel = optionalChatModelId(deps, body, "selectedModel");
652
+ const branchLabel = optionalString(body, "branchLabel");
653
+ const statusRaw = body.status;
654
+ const patch = {
655
+ ...(title !== undefined ? { title } : {}),
656
+ ...(selectedModel !== undefined ? { selectedModel } : {}),
657
+ ...(branchLabel !== undefined ? { branchLabel } : {}),
658
+ ...groundingScopePatchFields(body, deps),
659
+ };
660
+ if (statusRaw === undefined)
661
+ return patch;
662
+ if (statusRaw !== "open" && statusRaw !== "closed") {
663
+ throw new InvalidRequest('Field "status" must be "open" or "closed".');
664
+ }
665
+ return { ...patch, status: statusRaw };
666
+ }
667
+ // Epic #532 — the connectedScopes list SUPERSEDES the single connectedScope. The effective set of
668
+ // sources to access-validate is the list when present (non-null), else the single field. Returns
669
+ // an empty list when the patch only clears or omits the binding (no filesystem checks needed).
670
+ function scopesRequiringAccessValidation(patch) {
671
+ if (patch.connectedScopes !== undefined) {
672
+ return patch.connectedScopes ?? [];
673
+ }
674
+ if (patch.connectedScope !== undefined && patch.connectedScope !== null) {
675
+ return [patch.connectedScope];
676
+ }
677
+ return [];
678
+ }
679
+ function canonicalizeConnectedScopePatch(deps, chat, patch) {
680
+ if (patch.connectedScopes !== undefined) {
681
+ return {
682
+ ...patch,
683
+ connectedScopes: patch.connectedScopes === null
684
+ ? null
685
+ : patch.connectedScopes.map((scope) => validateConnectedScopeAccess(deps, chat, scope)),
686
+ };
687
+ }
688
+ if (patch.connectedScope !== undefined && patch.connectedScope !== null) {
689
+ return {
690
+ ...patch,
691
+ connectedScope: validateConnectedScopeAccess(deps, chat, patch.connectedScope),
692
+ };
693
+ }
694
+ return patch;
695
+ }
696
+ // Epic #189 — the grounded index is invalidated when ANY grounding source changes: a connected
697
+ // folder scope (#532) OR a local-knowledge connector scope. The hybrid path reads both.
698
+ function patchTouchesGroundingScope(patch) {
699
+ return (patch.connectedScopes !== undefined ||
700
+ patch.connectedScope !== undefined ||
701
+ patch.localKnowledgeScopes !== undefined ||
702
+ patch.localKnowledgeScope !== undefined);
703
+ }
704
+ export async function handleUpdateChat(ctx, deps) {
705
+ return runHandler(async () => {
706
+ const id = requireQuery(ctx, "id");
707
+ const body = await readJsonObject(ctx.req);
708
+ const patch = buildChatPatch(deps, body);
709
+ const scopesToCheck = scopesRequiringAccessValidation(patch);
710
+ let safePatch = patch;
711
+ if (scopesToCheck.length > 0) {
712
+ const existing = findChatById(deps, id);
713
+ if (existing === undefined)
714
+ return notFoundResult("Chat not found.");
715
+ safePatch = canonicalizeConnectedScopePatch(deps, existing, patch);
716
+ }
717
+ const limits = currentGroundingLimits(deps);
718
+ const chat = deps.store.updateChat(id, safePatch, {
719
+ maxConnectedSources: limits.maxConnectedSources,
720
+ maxLocalKnowledgeSources: limits.maxLocalKnowledgeSources,
721
+ });
722
+ if (patchTouchesGroundingScope(safePatch) || safePatch.status === "closed") {
723
+ clearGroundedContextIndexesForConversation(id);
724
+ clearGroundedTurnsForConversation(id);
725
+ }
726
+ return { status: 200, body: { chat } };
727
+ });
728
+ }
729
+ // ──────────────────────────────────────────────────────────────────────────
730
+ // Route 20 — DELETE /api/chats?id=...
731
+ // ──────────────────────────────────────────────────────────────────────────
732
+ export function handleDeleteChat(ctx, deps) {
733
+ return runHandlerSync(() => {
734
+ const id = requireQuery(ctx, "id");
735
+ deps.store.deleteChat(id);
736
+ clearGroundedContextIndexesForConversation(id);
737
+ clearGroundedTurnsForConversation(id);
738
+ return { status: 204, body: null };
739
+ });
740
+ }
741
+ // ──────────────────────────────────────────────────────────────────────────
742
+ // Route 21 — GET /api/chats/messages?chatId=...
743
+ // ──────────────────────────────────────────────────────────────────────────
744
+ export function handleListMessages(ctx, deps) {
745
+ return runHandlerSync(() => {
746
+ const chatId = requireQuery(ctx, "chatId");
747
+ const projectPath = requireQuery(ctx, "projectPath");
748
+ const limit = optionalBoundedQueryInteger(ctx, "limit", DEFAULT_MESSAGE_LIST_LIMIT, MAX_MESSAGE_LIST_LIMIT);
749
+ if (!chatBelongsToProject(deps, projectPath, chatId)) {
750
+ return notFoundResult("Chat not found.");
751
+ }
752
+ const messages = deps.store
753
+ .listMessages(chatId, limit)
754
+ .filter((message) => !isLegacyEmptyAssistantPlaceholder(message));
755
+ return { status: 200, body: { messages } };
756
+ });
757
+ }
758
+ // ──────────────────────────────────────────────────────────────────────────
759
+ // Route 22 — POST /api/chats/messages
760
+ // ──────────────────────────────────────────────────────────────────────────
761
+ export async function handleCreateMessage(ctx, deps) {
762
+ return runHandler(async () => {
763
+ const body = await readJsonObject(ctx.req);
764
+ const chatId = requireString(body, "chatId");
765
+ const projectPath = requireString(body, "projectPath");
766
+ if (!chatBelongsToProject(deps, projectPath, chatId)) {
767
+ return notFoundResult("Chat not found.");
768
+ }
769
+ const role = requireRole(body);
770
+ const content = requireString(body, "content");
771
+ const timestamp = requireNumber(body, "timestamp");
772
+ const message = deps.store.createMessage({
773
+ chatId,
774
+ role,
775
+ content,
776
+ timestamp,
777
+ runId: optionalString(body, "runId"),
778
+ workflowId: optionalString(body, "workflowId"),
779
+ workflowStatus: optionalWorkflowStatus(body),
780
+ shortResult: optionalString(body, "shortResult"),
781
+ taskType: optionalTaskType(body),
782
+ });
783
+ return { status: 201, body: { message } };
784
+ });
785
+ }
786
+ // ──────────────────────────────────────────────────────────────────────────
787
+ // Route 23 — POST /api/chats/messages/run-summary-pair (issue #66)
788
+ // ──────────────────────────────────────────────────────────────────────────
789
+ function buildRunSummaryPair(body) {
790
+ const chatId = requireString(body, "chatId");
791
+ const user = requireObject(body, "user");
792
+ const summary = requireObject(body, "summary");
793
+ const workflowId = optionalString(summary, "workflowId");
794
+ const taskType = optionalTaskType(summary);
795
+ if ((workflowId === undefined) === (taskType === undefined)) {
796
+ throw new InvalidRequest('Run summary requires exactly one of "workflowId" or "taskType".');
797
+ }
798
+ const userMessage = {
799
+ chatId,
800
+ role: "user",
801
+ content: requireString(user, "content"),
802
+ timestamp: requireNumber(user, "timestamp"),
803
+ runId: undefined,
804
+ workflowId: undefined,
805
+ workflowStatus: undefined,
806
+ shortResult: undefined,
807
+ taskType: undefined,
808
+ };
809
+ const summaryMessage = {
810
+ chatId,
811
+ role: "system",
812
+ content: requireString(summary, "content"),
813
+ timestamp: requireNumber(summary, "timestamp"),
814
+ runId: requireString(summary, "runId"),
815
+ workflowId,
816
+ workflowStatus: optionalWorkflowStatus(summary),
817
+ shortResult: optionalString(summary, "shortResult"),
818
+ taskType,
819
+ };
820
+ if (summaryMessage.workflowStatus === undefined) {
821
+ throw new InvalidRequest('Field "summary.workflowStatus" is required.');
822
+ }
823
+ return [userMessage, summaryMessage];
824
+ }
825
+ export async function handleCreateRunSummaryPair(ctx, deps) {
826
+ return runHandler(async () => {
827
+ const body = await readJsonObject(ctx.req);
828
+ const chatId = requireString(body, "chatId");
829
+ const projectPath = requireString(body, "projectPath");
830
+ if (!chatBelongsToProject(deps, projectPath, chatId)) {
831
+ return notFoundResult("Chat not found.");
832
+ }
833
+ const messages = deps.store.createMessages(buildRunSummaryPair(body));
834
+ return { status: 201, body: { messages } };
835
+ });
836
+ }
837
+ // ──────────────────────────────────────────────────────────────────────────
838
+ // Route 24 — PATCH /api/chats/messages?id=... (issue #66)
839
+ // ──────────────────────────────────────────────────────────────────────────
840
+ // Builds a typed UpdateChatMessagePatch from the JSON body. At least one updatable field must
841
+ // appear; the store layer also fails-closed on this, but throwing here surfaces the friendlier
842
+ // INVALID_REQUEST envelope without spending a SQL prepare.
843
+ function buildMessagePatch(body) {
844
+ const workflowStatus = optionalWorkflowStatus(body);
845
+ const shortResult = optionalString(body, "shortResult");
846
+ const taskType = optionalTaskType(body);
847
+ if (workflowStatus === undefined && shortResult === undefined && taskType === undefined) {
848
+ throw new InvalidRequest("PATCH body must include at least one updatable field.");
849
+ }
850
+ return {
851
+ ...(workflowStatus !== undefined ? { workflowStatus } : {}),
852
+ ...(shortResult !== undefined ? { shortResult } : {}),
853
+ ...(taskType !== undefined ? { taskType } : {}),
854
+ };
855
+ }
856
+ export async function handleUpdateMessage(ctx, deps) {
857
+ return runHandler(async () => {
858
+ const id = requireQuery(ctx, "id");
859
+ const chatId = requireQuery(ctx, "chatId");
860
+ const projectPath = requireQuery(ctx, "projectPath");
861
+ if (!chatBelongsToProject(deps, projectPath, chatId) ||
862
+ !messageBelongsToChat(deps, chatId, id)) {
863
+ return notFoundResult("Message not found.");
864
+ }
865
+ const body = await readJsonObject(ctx.req);
866
+ const patch = buildMessagePatch(body);
867
+ const message = deps.store.updateMessage(id, patch);
868
+ return { status: 200, body: { message } };
869
+ });
870
+ }
871
+ // barrel-level NOT_FOUND helper used by future delete-missing paths
872
+ export { notFoundResult };