@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,1285 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { statSync } from "node:fs";
3
+ import { basename, dirname } from "node:path";
4
+ import { addSourceToCapsule, checkpointToProgress, composeCapsules, CompositionError, createSqliteAuditSink, createDefaultParserRegistry, createCapsule, deleteCapsule, getCapsule, listCapsuleSets, listCapsuleSources, listCapsules, listExtractionCheckpoints, listResumableDocuments, openKnowledgeStore, removeSourceFromCapsule, resolveKnowledgeStorePath, runIndexingJob, updateCapsuleDetails, updateCapsuleState, } from "@oscharko-dev/keiko-local-knowledge";
5
+ import { KnowledgeNotFoundError, KnowledgeStoreError } from "@oscharko-dev/keiko-local-knowledge";
6
+ import { localKnowledgeProtectionOptions } from "./localKnowledgeKeyProvider.js";
7
+ import { CAPSULE_SET_MAX_MEMBERS, DEFAULT_EXTRACTION_CAPABILITY_AVAILABILITY, DEFAULT_LARGE_DOCUMENT_RESOURCE_POLICY, isSafeDisplaySummary, isSafeQualityWarning, validateCapsuleReindexRequest, validateKnowledgeSourceScope, } from "@oscharko-dev/keiko-contracts";
8
+ import { currentGatewayConfig } from "./deps.js";
9
+ import { errorBody } from "./routes.js";
10
+ import { findConfiguredCapability, requestOpenAIEmbedding, requestOpenAIEmbeddingBatch, verifyEmbeddingCapability, } from "@oscharko-dev/keiko-model-gateway";
11
+ import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
12
+ import { isDenied } from "@oscharko-dev/keiko-workspace";
13
+ import { localKnowledgeIndexingRegistry } from "./local-knowledge-indexing-registry.js";
14
+ const MAX_BODY_BYTES = 32_000;
15
+ // F4 (Epic #189): cap unbounded BFF response collections so a worst-case capsule with
16
+ // thousands of parser diagnostics / job rows cannot inflate a single JSON response.
17
+ const MAX_DIAGNOSTICS_PER_RESPONSE = 500;
18
+ const MAX_JOBS_PER_RESPONSE = 500;
19
+ const LOCAL_KNOWLEDGE_STORE_UNAVAILABLE_MESSAGE = "Local knowledge storage is unavailable. Check the local runtime state and try again.";
20
+ class InvalidRequest extends Error {
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = "InvalidRequest";
24
+ }
25
+ }
26
+ class BodyTooLargeError extends Error {
27
+ constructor() {
28
+ super("body too large");
29
+ this.name = "BodyTooLargeError";
30
+ }
31
+ }
32
+ function badRequest(code, message) {
33
+ return { status: 400, body: errorBody(code, message) };
34
+ }
35
+ function conflict(message) {
36
+ return { status: 409, body: errorBody("LOCAL_KNOWLEDGE_CONFLICT", message) };
37
+ }
38
+ // LK-001 / LK-003 (Epic #189): structured 409 conflict variants that surface the
39
+ // affected capsule + job so the UI can route the user back to the right run without
40
+ // re-fetching the capsule detail.
41
+ function indexingConflict(code, message, capsuleId, jobId) {
42
+ return {
43
+ status: 409,
44
+ body: { ...errorBody(code, message), capsuleId, jobId },
45
+ };
46
+ }
47
+ function serviceUnavailable(message) {
48
+ return { status: 503, body: errorBody("LOCAL_KNOWLEDGE_UNAVAILABLE", message) };
49
+ }
50
+ function notFound(message) {
51
+ return { status: 404, body: errorBody("NOT_FOUND", message) };
52
+ }
53
+ async function readBody(req) {
54
+ return new Promise((resolve, reject) => {
55
+ const chunks = [];
56
+ let total = 0;
57
+ let capped = false;
58
+ req.on("data", (chunk) => {
59
+ total += chunk.length;
60
+ if (total > MAX_BODY_BYTES) {
61
+ if (!capped) {
62
+ capped = true;
63
+ chunks.length = 0;
64
+ reject(new BodyTooLargeError());
65
+ req.resume();
66
+ }
67
+ return;
68
+ }
69
+ chunks.push(chunk);
70
+ });
71
+ req.on("end", () => {
72
+ if (!capped)
73
+ resolve(Buffer.concat(chunks).toString("utf8"));
74
+ });
75
+ req.on("error", reject);
76
+ });
77
+ }
78
+ async function readJsonObject(req) {
79
+ if (req.method === undefined || req.method === "GET" || req.method === "HEAD") {
80
+ return {};
81
+ }
82
+ const raw = await readBody(req);
83
+ if (raw.length === 0)
84
+ return {};
85
+ let parsed;
86
+ try {
87
+ parsed = JSON.parse(raw);
88
+ }
89
+ catch {
90
+ throw new InvalidRequest("Request body is not valid JSON.");
91
+ }
92
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
93
+ throw new InvalidRequest("Request body must be a JSON object.");
94
+ }
95
+ return parsed;
96
+ }
97
+ function parseScope(kind, json) {
98
+ let parsed;
99
+ try {
100
+ parsed = JSON.parse(json);
101
+ }
102
+ catch (cause) {
103
+ throw new KnowledgeStoreError(`Corrupt capsule_sources.scope_json (kind=${kind}): ${cause instanceof Error ? cause.message : String(cause)}`);
104
+ }
105
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
106
+ throw new KnowledgeStoreError(`Corrupt capsule_sources.scope_json (kind=${kind}).`);
107
+ }
108
+ return { kind, ...parsed };
109
+ }
110
+ function scopeToJson(scope) {
111
+ const copy = {};
112
+ for (const [key, value] of Object.entries(scope)) {
113
+ if (key === "kind")
114
+ continue;
115
+ copy[key] = value;
116
+ }
117
+ return JSON.stringify(copy);
118
+ }
119
+ function runtimeStateDir(deps) {
120
+ if (deps.uiDbPath === undefined || deps.uiDbPath.length === 0) {
121
+ return undefined;
122
+ }
123
+ return dirname(deps.uiDbPath);
124
+ }
125
+ function recoverAbandonedIndexingJobs(store) {
126
+ const rows = store._internal.db
127
+ .prepare([
128
+ "SELECT id, capsule_id, cancellation_requested",
129
+ "FROM indexing_jobs",
130
+ "WHERE status = 'running'",
131
+ "ORDER BY started_at ASC, id ASC",
132
+ ].join(" "))
133
+ .all();
134
+ if (rows.length === 0) {
135
+ return;
136
+ }
137
+ const finishedAt = store._internal.now();
138
+ for (const row of rows) {
139
+ if (localKnowledgeIndexingRegistry.isActiveCapsule(row.capsule_id) ||
140
+ localKnowledgeIndexingRegistry.isActiveJob(row.id)) {
141
+ continue;
142
+ }
143
+ const cancelled = row.cancellation_requested === 1;
144
+ store._internal.db
145
+ .prepare([
146
+ "UPDATE indexing_jobs SET",
147
+ " status = :status,",
148
+ " finished_at = :finished_at,",
149
+ " last_error_code = :error_code,",
150
+ " last_error_message = :error_message",
151
+ "WHERE id = :id AND status = 'running'",
152
+ ].join(" "))
153
+ .run({
154
+ status: cancelled ? "cancelled" : "failed",
155
+ finished_at: finishedAt,
156
+ error_code: cancelled ? "CANCELLED" : "INDEXING_INTERRUPTED",
157
+ error_message: cancelled
158
+ ? "Indexing was cancelled before the run could be finalized."
159
+ : "Indexing stopped unexpectedly before completion. Restart the run to finish indexing.",
160
+ id: row.id,
161
+ });
162
+ try {
163
+ updateCapsuleState(store, row.capsule_id, "error");
164
+ }
165
+ catch {
166
+ // informational only — the recovered job row is the durable source of truth
167
+ }
168
+ }
169
+ }
170
+ function openStoreForDeps(deps) {
171
+ const root = runtimeStateDir(deps);
172
+ if (root === undefined) {
173
+ throw new KnowledgeStoreError("UI runtime-state path is unavailable.");
174
+ }
175
+ const dbPath = resolveKnowledgeStorePath({ runtimeStateDir: root });
176
+ const protection = localKnowledgeProtectionOptions(deps.localKnowledgeKeyProvider);
177
+ const store = openKnowledgeStore(protection === undefined ? { dbPath } : { dbPath, protection });
178
+ recoverAbandonedIndexingJobs(store);
179
+ return {
180
+ store,
181
+ dbPath,
182
+ close: () => {
183
+ store.close();
184
+ },
185
+ };
186
+ }
187
+ function storageSizeBytes(dbPath) {
188
+ let total = 0;
189
+ for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
190
+ try {
191
+ total += statSync(path).size;
192
+ }
193
+ catch {
194
+ // Optional sidecars or unopened DB paths are fine.
195
+ }
196
+ }
197
+ return total;
198
+ }
199
+ function configuredCapabilityForModel(config, modelId) {
200
+ return findConfiguredCapability(config, modelId);
201
+ }
202
+ function isConfiguredEmbeddingModel(config, modelId) {
203
+ return configuredCapabilityForModel(config, modelId)?.kind === "embedding";
204
+ }
205
+ function configuredEmbeddingProvider(config, modelId) {
206
+ if (config === undefined)
207
+ return undefined;
208
+ const provider = config.providers.find((entry) => entry.modelId === modelId);
209
+ if (provider === undefined)
210
+ return undefined;
211
+ return isConfiguredEmbeddingModel(config, provider.modelId) ? provider : undefined;
212
+ }
213
+ function configuredProviderForCapsule(deps, capsule) {
214
+ const provider = configuredEmbeddingProvider(currentGatewayConfig(deps), capsule.embeddingModelIdentity.modelId);
215
+ if (provider === undefined)
216
+ return undefined;
217
+ return storedProviderMatchesConfiguredProvider(capsule.embeddingModelIdentity.provider, provider)
218
+ ? provider
219
+ : undefined;
220
+ }
221
+ function embeddingCompatibilityReason(config, capsule) {
222
+ if (config === undefined)
223
+ return undefined;
224
+ const modelId = capsule.embeddingModelIdentity.modelId;
225
+ if (!config.providers.some((entry) => entry.modelId === modelId)) {
226
+ return "The configured embedding model no longer matches this capsule.";
227
+ }
228
+ const provider = configuredEmbeddingProvider(config, modelId);
229
+ if (provider === undefined) {
230
+ return "The configured model for this capsule cannot serve embeddings.";
231
+ }
232
+ if (!storedProviderMatchesConfiguredProvider(capsule.embeddingModelIdentity.provider, provider)) {
233
+ return "The configured embedding gateway no longer matches this capsule.";
234
+ }
235
+ return undefined;
236
+ }
237
+ function vectorCompatibility(deps, capsule) {
238
+ const config = currentGatewayConfig(deps);
239
+ const reasons = [];
240
+ const provider = configuredProviderForCapsule(deps, capsule);
241
+ const embeddingReason = embeddingCompatibilityReason(config, capsule);
242
+ if (embeddingReason !== undefined)
243
+ reasons.push(embeddingReason);
244
+ if (capsule.lifecycleState === "stale") {
245
+ reasons.push("The capsule is marked stale and should be refreshed.");
246
+ }
247
+ if (capsule.lifecycleState === "error") {
248
+ reasons.push("The last indexing run ended with errors.");
249
+ }
250
+ return {
251
+ vectorCompatible: provider !== undefined || config === undefined,
252
+ staleReasons: reasons,
253
+ };
254
+ }
255
+ function loadSourceStats(store, capsuleId) {
256
+ const rows = store._internal.db
257
+ .prepare([
258
+ "SELECT s.id AS source_id, ks.display_name, ks.scope_kind, ks.scope_json,",
259
+ " SUM(CASE WHEN d.status = 'extracted' THEN 1 ELSE 0 END) AS indexed_count,",
260
+ " SUM(CASE WHEN d.status = 'failed' THEN 1 ELSE 0 END) AS failed_count,",
261
+ " SUM(CASE WHEN d.status IN ('skipped', 'unsupported') THEN 1 ELSE 0 END) AS skipped_count",
262
+ "FROM capsule_sources AS s",
263
+ "JOIN knowledge_sources AS ks ON ks.id = s.id",
264
+ "LEFT JOIN documents AS d ON d.capsule_id = s.capsule_id AND d.source_id = s.id",
265
+ "WHERE s.capsule_id = :c",
266
+ "GROUP BY s.id, ks.display_name, ks.scope_kind, ks.scope_json",
267
+ "ORDER BY s.created_at ASC, s.id ASC",
268
+ ].join(" "))
269
+ .all({ c: capsuleId });
270
+ return rows.map((row) => ({
271
+ sourceId: row.source_id,
272
+ displayName: row.display_name,
273
+ scope: parseScope(row.scope_kind, row.scope_json),
274
+ indexedCount: row.indexed_count,
275
+ failedCount: row.failed_count,
276
+ skippedCount: row.skipped_count,
277
+ }));
278
+ }
279
+ function loadParserDiagnostics(store, capsuleId) {
280
+ const rows = store._internal.db
281
+ .prepare([
282
+ "SELECT severity, code, message, document_id, page_number",
283
+ "FROM parser_diagnostics",
284
+ "WHERE capsule_id = :c",
285
+ "ORDER BY created_at DESC, id DESC",
286
+ "LIMIT :limit",
287
+ ].join(" "))
288
+ .all({
289
+ c: capsuleId,
290
+ limit: MAX_DIAGNOSTICS_PER_RESPONSE,
291
+ });
292
+ const totalRow = store._internal.db
293
+ .prepare("SELECT COUNT(*) AS n FROM parser_diagnostics WHERE capsule_id = :c")
294
+ .get({ c: capsuleId });
295
+ const items = rows.map((row) => ({
296
+ severity: row.severity,
297
+ code: row.code,
298
+ message: row.message,
299
+ ...(row.document_id !== null ? { documentId: row.document_id } : {}),
300
+ ...(row.page_number !== null ? { pageNumber: row.page_number } : {}),
301
+ }));
302
+ return { items, total: totalRow.n };
303
+ }
304
+ function unsupportedGuidanceFor(reason) {
305
+ if (reason === "pdf-no-text-layer" || reason === "pdf-not-implemented") {
306
+ return "Scanned PDFs need an OCR-capable extraction path. Configure a verified OCR or vision adapter, or provide a text-layer PDF.";
307
+ }
308
+ if (reason === "image-not-supported") {
309
+ return "Image-only documents need an OCR-capable extraction path before they can be indexed.";
310
+ }
311
+ if (reason.startsWith("ocr-failed:")) {
312
+ return "OCR extraction failed for at least one document. Review the OCR adapter configuration and retry indexing.";
313
+ }
314
+ return "Some documents are unsupported in this build. Review the health diagnostics for the affected formats and next steps.";
315
+ }
316
+ function loadUnsupportedGuidance(store, capsuleId) {
317
+ const rows = store._internal.db
318
+ .prepare([
319
+ "SELECT DISTINCT unsupported_reason",
320
+ "FROM parsed_units",
321
+ "WHERE capsule_id = :c AND kind = 'unsupported-media'",
322
+ "ORDER BY unsupported_reason ASC",
323
+ ].join(" "))
324
+ .all({ c: capsuleId });
325
+ const guidance = new Set();
326
+ for (const row of rows) {
327
+ if (typeof row.unsupported_reason !== "string" || row.unsupported_reason.length === 0)
328
+ continue;
329
+ guidance.add(unsupportedGuidanceFor(row.unsupported_reason));
330
+ }
331
+ return [...guidance];
332
+ }
333
+ function parseSourceIds(json) {
334
+ let parsed;
335
+ try {
336
+ parsed = JSON.parse(json);
337
+ }
338
+ catch (cause) {
339
+ throw new KnowledgeStoreError(`Corrupt indexing_jobs.source_ids_json: ${cause instanceof Error ? cause.message : String(cause)}`);
340
+ }
341
+ if (!Array.isArray(parsed))
342
+ return [];
343
+ return parsed
344
+ .filter((entry) => typeof entry === "string")
345
+ .map((entry) => entry);
346
+ }
347
+ function rowToIndexingJobRecord(row) {
348
+ return {
349
+ id: row.id,
350
+ capsuleId: row.capsule_id,
351
+ sourceIds: parseSourceIds(row.source_ids_json),
352
+ startedAt: row.started_at,
353
+ ...(row.finished_at !== null ? { finishedAt: row.finished_at } : {}),
354
+ status: row.status,
355
+ totalDocuments: row.total_documents,
356
+ processedDocuments: row.processed_documents,
357
+ failedDocuments: row.failed_documents,
358
+ skippedDocuments: row.skipped_documents,
359
+ ...(row.last_error_code !== null && row.last_error_message !== null
360
+ ? { lastError: { code: row.last_error_code, message: row.last_error_message } }
361
+ : {}),
362
+ };
363
+ }
364
+ function loadIndexingJobs(store, capsuleId) {
365
+ const rows = store._internal.db
366
+ .prepare([
367
+ "SELECT id, capsule_id, source_ids_json, started_at, finished_at, status,",
368
+ " total_documents, processed_documents, failed_documents, skipped_documents,",
369
+ " last_error_code, last_error_message, resume_token",
370
+ "FROM indexing_jobs",
371
+ "WHERE capsule_id = :c",
372
+ "ORDER BY started_at DESC, id DESC",
373
+ "LIMIT :limit",
374
+ ].join(" "))
375
+ .all({
376
+ c: capsuleId,
377
+ limit: MAX_JOBS_PER_RESPONSE,
378
+ });
379
+ const totalRow = store._internal.db
380
+ .prepare("SELECT COUNT(*) AS n FROM indexing_jobs WHERE capsule_id = :c")
381
+ .get({ c: capsuleId });
382
+ const items = rows.map((row) => rowToIndexingJobRecord(row));
383
+ return { items, total: totalRow.n };
384
+ }
385
+ function countForTable(store, table, capsuleId) {
386
+ const row = store._internal.db
387
+ .prepare(`SELECT COUNT(*) AS n FROM ${table} WHERE capsule_id = :c`)
388
+ .get({ c: capsuleId });
389
+ return row.n;
390
+ }
391
+ function countDocumentStatus(store, capsuleId, status) {
392
+ const row = status === "failed"
393
+ ? store._internal.db
394
+ .prepare("SELECT COUNT(*) AS n FROM documents WHERE capsule_id = :c AND status = 'failed'")
395
+ .get({ c: capsuleId })
396
+ : status === "unsupported"
397
+ ? store._internal.db
398
+ .prepare("SELECT COUNT(*) AS n FROM documents WHERE capsule_id = :c AND status = 'unsupported'")
399
+ .get({ c: capsuleId })
400
+ : store._internal.db
401
+ .prepare("SELECT COUNT(*) AS n FROM documents WHERE capsule_id = :c AND status IN ('skipped', 'unsupported')")
402
+ .get({ c: capsuleId });
403
+ return row.n;
404
+ }
405
+ function lastIndexedAt(store, capsuleId) {
406
+ const row = store._internal.db
407
+ .prepare("SELECT MAX(finished_at) AS finished_at FROM indexing_jobs WHERE capsule_id = :c AND finished_at IS NOT NULL")
408
+ .get({ c: capsuleId });
409
+ return row.finished_at ?? undefined;
410
+ }
411
+ // Bounded large-document ingestion (Epic #1160, Issue #1286). The active resource policy layers an
412
+ // optional operator-configured large-file threshold over the contract defaults.
413
+ function resolveLargeDocumentPolicy() {
414
+ const raw = process.env.KEIKO_LK_LARGE_DOC_THRESHOLD_BYTES;
415
+ const threshold = raw === undefined ? Number.NaN : Number.parseInt(raw, 10);
416
+ if (!Number.isFinite(threshold) || threshold <= 0) {
417
+ return DEFAULT_LARGE_DOCUMENT_RESOURCE_POLICY;
418
+ }
419
+ return {
420
+ ...DEFAULT_LARGE_DOCUMENT_RESOURCE_POLICY,
421
+ largeFileThresholdBytes: Math.min(threshold, DEFAULT_LARGE_DOCUMENT_RESOURCE_POLICY.maxRawFileBytes),
422
+ };
423
+ }
424
+ function countPartialCoverageDocuments(store, capsuleId) {
425
+ const row = store._internal.db
426
+ .prepare("SELECT COUNT(*) AS n FROM extraction_checkpoints WHERE capsule_id = :c AND coverage <> 'complete'")
427
+ .get({ c: capsuleId });
428
+ return row.n;
429
+ }
430
+ // Distinct, browser-safe warning-severity diagnostic messages. The SELECT carries no paths or
431
+ // secrets (parser diagnostics are redacted at write time) and is bounded.
432
+ function loadQualityWarnings(store, capsuleId) {
433
+ const rows = store._internal.db
434
+ .prepare("SELECT DISTINCT message FROM parser_diagnostics WHERE capsule_id = :c AND severity = 'warning' ORDER BY message ASC LIMIT 50")
435
+ .all({ c: capsuleId });
436
+ return rows.map((row) => row.message).filter((message) => isSafeQualityWarning(message));
437
+ }
438
+ function documentDisplayName(store, capsuleId, documentId) {
439
+ const row = store._internal.db
440
+ .prepare("SELECT safe_display_name AS n FROM documents WHERE capsule_id = :c AND id = :d")
441
+ .get({ c: capsuleId, d: String(documentId) });
442
+ return row?.n ?? String(documentId);
443
+ }
444
+ function buildLargeDocumentHealth(store, capsule, policy) {
445
+ const progress = listExtractionCheckpoints(store._internal.db, capsule.id).map((checkpoint) => checkpointToProgress(checkpoint, documentDisplayName(store, capsule.id, checkpoint.documentId)));
446
+ return {
447
+ resourcePolicy: policy,
448
+ capabilities: DEFAULT_EXTRACTION_CAPABILITY_AVAILABILITY,
449
+ progress,
450
+ resumableDocuments: listResumableDocuments(store, capsule.id),
451
+ partialCoverageDocuments: countPartialCoverageDocuments(store, capsule.id),
452
+ qualityWarnings: loadQualityWarnings(store, capsule.id),
453
+ };
454
+ }
455
+ function buildCapsuleHealth(deps, store, dbPath, capsule) {
456
+ const documentCount = countForTable(store, "documents", capsule.id);
457
+ const chunkCount = countForTable(store, "chunks", capsule.id);
458
+ const vectorCount = countForTable(store, "vectors", capsule.id);
459
+ const failedDocuments = countDocumentStatus(store, capsule.id, "failed");
460
+ const skippedDocuments = countDocumentStatus(store, capsule.id, "skipped");
461
+ const unsupportedDocuments = countDocumentStatus(store, capsule.id, "unsupported");
462
+ const compatibility = vectorCompatibility(deps, capsule);
463
+ const indexedAt = lastIndexedAt(store, capsule.id);
464
+ const unsupportedGuidance = unsupportedDocuments > 0 ? loadUnsupportedGuidance(store, capsule.id) : [];
465
+ return {
466
+ capsuleId: capsule.id,
467
+ sourceIds: capsule.sourceIds,
468
+ lifecycleState: capsule.lifecycleState,
469
+ storageSizeBytes: storageSizeBytes(dbPath),
470
+ documentCount,
471
+ chunkCount,
472
+ vectorCount,
473
+ ...(indexedAt !== undefined ? { lastIndexedAt: indexedAt } : {}),
474
+ embeddingIdentity: capsule.embeddingModelIdentity,
475
+ vectorCompatible: compatibility.vectorCompatible,
476
+ failedDocuments,
477
+ skippedDocuments,
478
+ unsupportedDocuments,
479
+ unsupportedGuidance: unsupportedDocuments > 0 && unsupportedGuidance.length === 0
480
+ ? [
481
+ "Some documents were skipped because this build cannot extract them yet. Review the health diagnostics for the affected formats and next steps.",
482
+ ]
483
+ : unsupportedGuidance,
484
+ staleReasons: compatibility.staleReasons,
485
+ partialCoverageDocuments: countPartialCoverageDocuments(store, capsule.id),
486
+ qualityWarnings: loadQualityWarnings(store, capsule.id),
487
+ resumableDocuments: listResumableDocuments(store, capsule.id).length,
488
+ };
489
+ }
490
+ function createEmbeddingAdapter(provider, requestImpl, batchImpl) {
491
+ const providerCreds = {
492
+ endpoint: provider.baseUrl,
493
+ apiKey: provider.apiKey,
494
+ ...(provider.apiKeyHeaderName !== undefined
495
+ ? { apiKeyHeaderName: provider.apiKeyHeaderName }
496
+ : {}),
497
+ ...(provider.egress !== undefined ? { egress: provider.egress } : {}),
498
+ };
499
+ return {
500
+ ...providerCreds,
501
+ request: (request) => requestImpl({ ...request, ...providerCreds }),
502
+ // #189 GRD-004: the indexing batcher prefers this array-batch port when present, turning
503
+ // up to batchSize per-chunk HTTPS round-trips into a single array call. Omitted when no
504
+ // batch impl is wired (scalar-stub tests) so those keep the one-request-per-chunk path.
505
+ ...(batchImpl !== undefined
506
+ ? {
507
+ requestBatch: (request) => batchImpl({ ...request, ...providerCreds }),
508
+ }
509
+ : {}),
510
+ };
511
+ }
512
+ function requestEmbeddingImpl(deps) {
513
+ return deps.localKnowledgeEmbeddingRequest ?? requestOpenAIEmbedding;
514
+ }
515
+ // #189 GRD-004: returns the array-batch impl, or undefined to fall back to per-chunk scalar
516
+ // embedding. A test that stubs only the scalar request (localKnowledgeEmbeddingRequest) gets
517
+ // no batch port unless it also stubs the batch request — keeping existing call-count tests valid.
518
+ function requestEmbeddingBatchImpl(deps) {
519
+ if (deps.localKnowledgeEmbeddingRequest !== undefined) {
520
+ return deps.localKnowledgeEmbeddingBatchRequest;
521
+ }
522
+ return requestOpenAIEmbeddingBatch;
523
+ }
524
+ function canonicalizeScopeRoot(scope) {
525
+ const safeRealPath = (path) => {
526
+ try {
527
+ return nodeWorkspaceFs.realPath(path);
528
+ }
529
+ catch {
530
+ return path;
531
+ }
532
+ };
533
+ if (scope.kind === "folder" || scope.kind === "files") {
534
+ return { ...scope, rootPath: safeRealPath(scope.rootPath) };
535
+ }
536
+ return { ...scope, repositoryRoot: safeRealPath(scope.repositoryRoot) };
537
+ }
538
+ function canonicalizeCapsuleSourceRoots(store, capsule) {
539
+ const now = store._internal.now();
540
+ for (const source of listCapsuleSources(store, capsule.id)) {
541
+ const canonicalScope = canonicalizeScopeRoot(source.scope);
542
+ // Re-validate the deny-list against the canonical (realpath-resolved) root at index time,
543
+ // not only at connect time. A folder that was safe when connected can later be moved or
544
+ // symlink-swapped so its realpath resolves into a denied location (e.g. ~/.ssh); refuse to
545
+ // index it rather than walking credential files inside it. Runs for every source, including
546
+ // already-canonical ones, so it must precede the no-op skip below.
547
+ if (isDenied(connectScopeRootPath(canonicalScope))) {
548
+ throw new InvalidRequest("Source path is in a denied location and cannot be indexed.");
549
+ }
550
+ if (JSON.stringify(canonicalScope) === JSON.stringify(source.scope)) {
551
+ continue;
552
+ }
553
+ store._internal.db
554
+ .prepare("UPDATE knowledge_sources SET scope_json = :scope_json, updated_at = :updated_at WHERE id = :id")
555
+ .run({
556
+ scope_json: scopeToJson(canonicalScope),
557
+ updated_at: now,
558
+ id: source.id,
559
+ });
560
+ store._internal.db
561
+ .prepare("UPDATE capsule_sources SET scope_json = :scope_json, updated_at = :updated_at WHERE capsule_id = :c AND id = :id")
562
+ .run({
563
+ scope_json: scopeToJson(canonicalScope),
564
+ updated_at: now,
565
+ c: capsule.id,
566
+ id: source.id,
567
+ });
568
+ }
569
+ }
570
+ function actionResponse(capsuleId) {
571
+ return { status: 200, body: { ok: true, capsuleId } };
572
+ }
573
+ // F4 (Epic #189): the response shape for both GET /capsules/:id and POST /capsules
574
+ // includes truncation metadata so a future UI can prompt the user to refine their
575
+ // view when the persisted history grew past the per-response cap.
576
+ function buildCapsuleResponseBody(deps, store, dbPath, capsule) {
577
+ const diagnostics = loadParserDiagnostics(store, capsule.id);
578
+ const jobs = loadIndexingJobs(store, capsule.id);
579
+ return {
580
+ capsule,
581
+ health: buildCapsuleHealth(deps, store, dbPath, capsule),
582
+ largeDocumentHealth: buildLargeDocumentHealth(store, capsule, resolveLargeDocumentPolicy()),
583
+ sources: loadSourceStats(store, capsule.id),
584
+ parserDiagnostics: diagnostics.items,
585
+ parserDiagnosticsTotal: diagnostics.total,
586
+ parserDiagnosticsTruncated: diagnostics.total > diagnostics.items.length,
587
+ indexingJobs: jobs.items,
588
+ indexingJobsTotal: jobs.total,
589
+ indexingJobsTruncated: jobs.total > jobs.items.length,
590
+ };
591
+ }
592
+ function deleteActionResponse(input) {
593
+ return {
594
+ status: 200,
595
+ body: {
596
+ ok: true,
597
+ capsuleId: input.capsuleId,
598
+ affectedCapsuleSetIds: input.affectedCapsuleSetIds,
599
+ cleanupVerified: input.cleanupVerified,
600
+ },
601
+ };
602
+ }
603
+ function parseCapsuleId(ctx) {
604
+ const capsuleId = ctx.params.capsuleId;
605
+ if (capsuleId === undefined) {
606
+ throw new InvalidRequest("Route parameter capsuleId is required.");
607
+ }
608
+ return capsuleId;
609
+ }
610
+ function requireSafeDisplayText(field, value) {
611
+ const trimmed = value.trim();
612
+ if (trimmed.length === 0 || !isSafeDisplaySummary(trimmed)) {
613
+ throw new InvalidRequest(`Field "${field}" must be a browser-safe non-empty string.`);
614
+ }
615
+ return trimmed;
616
+ }
617
+ function safeOptionalDisplayText(field, value) {
618
+ if (value === undefined) {
619
+ return undefined;
620
+ }
621
+ if (typeof value !== "string") {
622
+ throw new InvalidRequest(`Field "${field}" must be a string when provided.`);
623
+ }
624
+ const trimmed = value.trim();
625
+ if (trimmed.length === 0) {
626
+ return undefined;
627
+ }
628
+ if (!isSafeDisplaySummary(trimmed)) {
629
+ throw new InvalidRequest(`Field "${field}" must be browser-safe when provided.`);
630
+ }
631
+ return trimmed;
632
+ }
633
+ function parseCreateCapsuleInput(body) {
634
+ if (typeof body.displayName !== "string") {
635
+ throw new InvalidRequest('Field "displayName" must be a non-empty string.');
636
+ }
637
+ const displayName = requireSafeDisplayText("displayName", body.displayName);
638
+ const description = safeOptionalDisplayText("description", body.description);
639
+ return description === undefined ? { displayName } : { displayName, description };
640
+ }
641
+ function normalizedEndpointFingerprint(baseUrl) {
642
+ const normalized = baseUrl.trim().replace(/\/+$/, "");
643
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
644
+ }
645
+ function embeddingProviderIdentity(provider) {
646
+ return `openai-compatible:${normalizedEndpointFingerprint(provider.baseUrl)}`;
647
+ }
648
+ function storedProviderMatchesConfiguredProvider(storedProvider, provider) {
649
+ // Legacy capsules created before #192 audit fixes stored the generic "openai" label.
650
+ // Keep those usable; new capsules store a non-secret endpoint fingerprint so future
651
+ // gateway swaps mark them incompatible instead of mixing unrelated vector spaces.
652
+ if (!storedProvider.startsWith("openai-compatible:"))
653
+ return true;
654
+ return storedProvider === embeddingProviderIdentity(provider);
655
+ }
656
+ // Issue #621 / #677: select the first provider whose resolved capability is embedding-capable.
657
+ // Falling back to a chat model creates capsules that can never index successfully.
658
+ export function selectEmbeddingModelId(config) {
659
+ if (config === undefined || config === null || config.providers.length === 0)
660
+ return undefined;
661
+ return config.providers.find((provider) => isConfiguredEmbeddingModel(config, provider.modelId))
662
+ ?.modelId;
663
+ }
664
+ function createCapsuleStorageReference(capsuleId) {
665
+ return `capsules/${capsuleId}`;
666
+ }
667
+ async function verifiedNewCapsuleEmbeddingIdentity(deps, provider) {
668
+ const adapter = createEmbeddingAdapter(provider, requestEmbeddingImpl(deps), requestEmbeddingBatchImpl(deps));
669
+ try {
670
+ const result = await verifyEmbeddingCapability(adapter, {
671
+ modelId: provider.modelId,
672
+ provider: embeddingProviderIdentity(provider),
673
+ vectorMetric: "cosine",
674
+ timeoutMs: provider.timeoutMs,
675
+ });
676
+ if (result.ok) {
677
+ return { ok: true, identity: result.identity };
678
+ }
679
+ return { ok: false, result: conflict(result.safeMessage) };
680
+ }
681
+ catch {
682
+ return {
683
+ ok: false,
684
+ result: conflict("embedding capability preflight failed before capsule creation"),
685
+ };
686
+ }
687
+ }
688
+ async function resolveNewCapsuleEmbeddingIdentity(deps) {
689
+ const config = currentGatewayConfig(deps);
690
+ const configuredModelId = selectEmbeddingModelId(config);
691
+ if (configuredModelId === undefined) {
692
+ return {
693
+ ok: false,
694
+ result: conflict("No configured embedding-capable model is available for new capsules. Configure the Model Gateway first."),
695
+ };
696
+ }
697
+ const provider = configuredEmbeddingProvider(config, configuredModelId);
698
+ if (provider === undefined) {
699
+ return {
700
+ ok: false,
701
+ result: conflict("No configured embedding-capable model is available for new capsules. Configure the Model Gateway first."),
702
+ };
703
+ }
704
+ return verifiedNewCapsuleEmbeddingIdentity(deps, provider);
705
+ }
706
+ function latestRunningJobId(store, capsuleId) {
707
+ const row = store._internal.db
708
+ .prepare([
709
+ "SELECT id",
710
+ "FROM indexing_jobs",
711
+ "WHERE capsule_id = :c AND status = 'running'",
712
+ "ORDER BY started_at DESC, id DESC",
713
+ "LIMIT 1",
714
+ ].join(" "))
715
+ .get({ c: capsuleId });
716
+ return row?.id;
717
+ }
718
+ function requestRunningJobCancellation(store, capsuleId) {
719
+ const jobId = latestRunningJobId(store, capsuleId);
720
+ if (jobId === undefined) {
721
+ return false;
722
+ }
723
+ store._internal.db
724
+ .prepare("UPDATE indexing_jobs SET cancellation_requested = 1 WHERE id = :id AND capsule_id = :c")
725
+ .run({ id: jobId, c: capsuleId });
726
+ localKnowledgeIndexingRegistry.cancel(String(capsuleId));
727
+ return true;
728
+ }
729
+ function emptyCapsuleIndexingConflict() {
730
+ return conflict("Attach at least one source to this capsule before indexing it.");
731
+ }
732
+ function assertScopeShape(scope) {
733
+ const result = validateKnowledgeSourceScope(scope);
734
+ if (result.ok)
735
+ return;
736
+ throw new InvalidRequest(result.errors.join(" "));
737
+ }
738
+ function disconnectCapsuleSources(store, capsuleId) {
739
+ const auditSink = createSqliteAuditSink(store);
740
+ const sources = listCapsuleSources(store, capsuleId);
741
+ for (const source of sources) {
742
+ assertScopeShape(source.scope);
743
+ removeSourceFromCapsule(store, capsuleId, source.id, auditSink);
744
+ }
745
+ updateCapsuleState(store, capsuleId, "draft");
746
+ }
747
+ function resolveIndexingSourceSelection(store, capsule, mode) {
748
+ if (mode !== "repair-failed") {
749
+ return { shouldRun: true };
750
+ }
751
+ const sourceIds = failedSourceIds(store, capsule.id);
752
+ return sourceIds.length === 0 ? { shouldRun: false } : { shouldRun: true, sourceIds };
753
+ }
754
+ // LK-001 (Epic #189): both the start and refresh handlers map a terminal IndexingTerminal
755
+ // to the same 3-way response — cancelled → 409 cancelled, failed → 409 failed-message,
756
+ // any other (completed or absent) → 200 ok. Extracted so each handler stays under the
757
+ // 50-LOC per-function ceiling.
758
+ function indexingCompletionResponse(capsuleId, terminal, failedMessage) {
759
+ if (terminal?.kind === "job-cancelled") {
760
+ return indexingConflict("indexing-cancelled", "Indexing job was cancelled.", capsuleId, terminal.jobId);
761
+ }
762
+ if (terminal?.kind === "job-failed") {
763
+ return conflict(failedMessage);
764
+ }
765
+ return actionResponse(capsuleId);
766
+ }
767
+ function buildIndexingOptions(store, capsule, adapter, options, sourceSelection, signal) {
768
+ return {
769
+ capsuleId: capsule.id,
770
+ ...(sourceSelection.shouldRun && sourceSelection.sourceIds !== undefined
771
+ ? { sourceIds: sourceSelection.sourceIds }
772
+ : {}),
773
+ parserRegistry: createDefaultParserRegistry(),
774
+ workspaceFs: nodeWorkspaceFs,
775
+ embeddingAdapter: adapter,
776
+ auditSink: createSqliteAuditSink(store),
777
+ store,
778
+ force: options.force,
779
+ largeDocumentPolicy: resolveLargeDocumentPolicy(),
780
+ ...(options.mode === "resume" ? { resume: true } : {}),
781
+ signal,
782
+ };
783
+ }
784
+ async function runCapsuleIndexingJob(deps, store, capsule, options) {
785
+ const provider = configuredProviderForCapsule(deps, capsule);
786
+ if (provider === undefined) {
787
+ return { kind: "job-failed", jobId: "" };
788
+ }
789
+ canonicalizeCapsuleSourceRoots(store, capsule);
790
+ const adapter = createEmbeddingAdapter(provider, requestEmbeddingImpl(deps), requestEmbeddingBatchImpl(deps));
791
+ const sourceSelection = resolveIndexingSourceSelection(store, capsule, options.mode);
792
+ if (!sourceSelection.shouldRun) {
793
+ return undefined;
794
+ }
795
+ const controller = localKnowledgeIndexingRegistry.start(String(capsule.id));
796
+ let terminal;
797
+ try {
798
+ for await (const event of runIndexingJob(buildIndexingOptions(store, capsule, adapter, options, sourceSelection, controller.signal))) {
799
+ if (event.kind === "job-started") {
800
+ localKnowledgeIndexingRegistry.attachJobId(String(capsule.id), event.jobId);
801
+ }
802
+ if (event.kind === "job-completed" ||
803
+ event.kind === "job-failed" ||
804
+ event.kind === "job-cancelled") {
805
+ terminal = event;
806
+ }
807
+ }
808
+ }
809
+ finally {
810
+ localKnowledgeIndexingRegistry.complete(String(capsule.id));
811
+ }
812
+ return terminal;
813
+ }
814
+ function failedSourceIds(store, capsuleId) {
815
+ const rows = store._internal.db
816
+ .prepare([
817
+ "SELECT DISTINCT source_id",
818
+ "FROM documents",
819
+ "WHERE capsule_id = :c AND status = 'failed' AND source_id IS NOT NULL",
820
+ "ORDER BY source_id ASC",
821
+ ].join(" "))
822
+ .all({ c: capsuleId });
823
+ return rows.map((row) => row.source_id);
824
+ }
825
+ async function runHandler(worker) {
826
+ try {
827
+ return await worker();
828
+ }
829
+ catch (error) {
830
+ if (error instanceof BodyTooLargeError) {
831
+ return {
832
+ status: 413,
833
+ body: errorBody("PAYLOAD_TOO_LARGE", "Request body exceeds the size limit."),
834
+ };
835
+ }
836
+ if (error instanceof InvalidRequest) {
837
+ return badRequest("INVALID_REQUEST", error.message);
838
+ }
839
+ if (error instanceof KnowledgeNotFoundError) {
840
+ return notFound(error.message);
841
+ }
842
+ if (error instanceof KnowledgeStoreError) {
843
+ return serviceUnavailable(LOCAL_KNOWLEDGE_STORE_UNAVAILABLE_MESSAGE);
844
+ }
845
+ throw error;
846
+ }
847
+ }
848
+ export async function handleListLocalKnowledgeCapsules(_ctx, deps) {
849
+ return runHandler(() => {
850
+ const env = openStoreForDeps(deps);
851
+ try {
852
+ const capsules = listCapsules(env.store).map((capsule) => ({
853
+ id: capsule.id,
854
+ displayName: capsule.displayName,
855
+ lifecycleState: capsule.lifecycleState,
856
+ sourceCount: capsule.sourceIds.length,
857
+ updatedAt: capsule.updatedAt,
858
+ }));
859
+ return { status: 200, body: { capsules } };
860
+ }
861
+ finally {
862
+ env.close();
863
+ }
864
+ });
865
+ }
866
+ export async function handleListLocalKnowledgeCapsuleSets(_ctx, deps) {
867
+ return runHandler(() => {
868
+ const env = openStoreForDeps(deps);
869
+ try {
870
+ const capsuleSets = listCapsuleSets(env.store).map((capsuleSet) => ({
871
+ id: capsuleSet.id,
872
+ displayName: capsuleSet.displayName,
873
+ capsuleCount: capsuleSet.capsuleIds.length,
874
+ composedAt: capsuleSet.composedAt,
875
+ }));
876
+ return { status: 200, body: { capsuleSets } };
877
+ }
878
+ finally {
879
+ env.close();
880
+ }
881
+ });
882
+ }
883
+ // ─── Create a capsule set (Slice 4 / Issue #189) — non-destructive composition ──
884
+ function parseSetCapsuleIds(raw) {
885
+ if (!Array.isArray(raw) || raw.length === 0) {
886
+ throw new InvalidRequest('Field "capsuleIds" must be a non-empty array.');
887
+ }
888
+ if (raw.length > CAPSULE_SET_MAX_MEMBERS) {
889
+ throw new InvalidRequest(`A capsule set may reference at most ${String(CAPSULE_SET_MAX_MEMBERS)} capsules.`);
890
+ }
891
+ const seen = new Set();
892
+ const capsuleIds = [];
893
+ for (const id of raw) {
894
+ if (typeof id !== "string" || id.trim().length === 0) {
895
+ throw new InvalidRequest('Every "capsuleIds" entry must be a non-empty string.');
896
+ }
897
+ if (seen.has(id)) {
898
+ throw new InvalidRequest(`Duplicate capsule id in the set request: ${id}.`);
899
+ }
900
+ seen.add(id);
901
+ capsuleIds.push(id);
902
+ }
903
+ return capsuleIds;
904
+ }
905
+ function parseCreateCapsuleSetInput(body) {
906
+ if (typeof body.displayName !== "string") {
907
+ throw new InvalidRequest('Field "displayName" must be a non-empty string.');
908
+ }
909
+ const displayName = requireSafeDisplayText("displayName", body.displayName);
910
+ const capsuleIds = parseSetCapsuleIds(body.capsuleIds);
911
+ const description = safeOptionalDisplayText("description", body.description);
912
+ return description === undefined
913
+ ? { displayName, capsuleIds }
914
+ : { displayName, description, capsuleIds };
915
+ }
916
+ export async function handleCreateLocalKnowledgeCapsuleSet(ctx, deps) {
917
+ return runHandler(async () => {
918
+ const input = parseCreateCapsuleSetInput(await readJsonObject(ctx.req));
919
+ const env = openStoreForDeps(deps);
920
+ try {
921
+ const set = composeCapsules(env.store, {
922
+ displayName: input.displayName,
923
+ ...(input.description !== undefined ? { description: input.description } : {}),
924
+ capsuleIds: input.capsuleIds,
925
+ });
926
+ return {
927
+ status: 201,
928
+ body: {
929
+ capsuleSet: {
930
+ id: set.id,
931
+ displayName: set.displayName,
932
+ ...(set.description !== undefined ? { description: set.description } : {}),
933
+ capsuleIds: set.capsuleIds,
934
+ capsuleCount: set.capsuleIds.length,
935
+ composedAt: set.composedAt,
936
+ },
937
+ },
938
+ };
939
+ }
940
+ catch (error) {
941
+ if (error instanceof CompositionError) {
942
+ return badRequest("INVALID_REQUEST", error.message);
943
+ }
944
+ throw error;
945
+ }
946
+ finally {
947
+ env.close();
948
+ }
949
+ });
950
+ }
951
+ // ─── Update a capsule's display name / description (Slice 4 / Issue #189) ───────
952
+ // Metadata persistence requires a schema migration and is intentionally NOT supported here yet;
953
+ // a metadata-bearing patch is rejected with a clear 400 rather than silently dropped.
954
+ function parseUpdateCapsuleInput(body) {
955
+ if (body.metadata !== undefined) {
956
+ throw new InvalidRequest("Capsule metadata updates are not yet supported; update displayName or description.");
957
+ }
958
+ const patch = {};
959
+ if (body.displayName !== undefined) {
960
+ if (typeof body.displayName !== "string") {
961
+ throw new InvalidRequest('Field "displayName" must be a non-empty string when provided.');
962
+ }
963
+ patch.displayName = requireSafeDisplayText("displayName", body.displayName);
964
+ }
965
+ if (body.description !== undefined) {
966
+ if (typeof body.description !== "string") {
967
+ throw new InvalidRequest('Field "description" must be a string when provided.');
968
+ }
969
+ const trimmed = body.description.trim();
970
+ if (!isSafeDisplaySummary(trimmed)) {
971
+ throw new InvalidRequest('Field "description" must be browser-safe when provided.');
972
+ }
973
+ patch.description = trimmed;
974
+ }
975
+ if (patch.displayName === undefined && patch.description === undefined) {
976
+ throw new InvalidRequest("Patch must include displayName or description.");
977
+ }
978
+ return patch;
979
+ }
980
+ export async function handleUpdateLocalKnowledgeCapsule(ctx, deps) {
981
+ return runHandler(async () => {
982
+ const capsuleId = parseCapsuleId(ctx);
983
+ const patch = parseUpdateCapsuleInput(await readJsonObject(ctx.req));
984
+ const env = openStoreForDeps(deps);
985
+ try {
986
+ const capsule = updateCapsuleDetails(env.store, capsuleId, patch);
987
+ return {
988
+ status: 200,
989
+ body: buildCapsuleResponseBody(deps, env.store, env.dbPath, capsule),
990
+ };
991
+ }
992
+ finally {
993
+ env.close();
994
+ }
995
+ });
996
+ }
997
+ export async function handleCreateLocalKnowledgeCapsule(ctx, deps) {
998
+ return runHandler(async () => {
999
+ const input = parseCreateCapsuleInput(await readJsonObject(ctx.req));
1000
+ const env = openStoreForDeps(deps);
1001
+ try {
1002
+ const embeddingIdentity = await resolveNewCapsuleEmbeddingIdentity(deps);
1003
+ if (!embeddingIdentity.ok) {
1004
+ return embeddingIdentity.result;
1005
+ }
1006
+ const capsuleId = randomUUID();
1007
+ const capsule = createCapsule(env.store, {
1008
+ id: capsuleId,
1009
+ displayName: input.displayName,
1010
+ ...(input.description !== undefined ? { description: input.description } : {}),
1011
+ tags: [],
1012
+ retrievalEffort: "default",
1013
+ outputMode: "snippets",
1014
+ answerGroundingPolicy: "require-citations",
1015
+ embeddingModelIdentity: embeddingIdentity.identity,
1016
+ lifecycleState: "draft",
1017
+ storageReference: createCapsuleStorageReference(capsuleId),
1018
+ }, createSqliteAuditSink(env.store));
1019
+ return {
1020
+ status: 201,
1021
+ body: buildCapsuleResponseBody(deps, env.store, env.dbPath, capsule),
1022
+ };
1023
+ }
1024
+ finally {
1025
+ env.close();
1026
+ }
1027
+ });
1028
+ }
1029
+ export async function handleGetLocalKnowledgeCapsule(ctx, deps) {
1030
+ return runHandler(() => {
1031
+ const capsuleId = parseCapsuleId(ctx);
1032
+ const env = openStoreForDeps(deps);
1033
+ try {
1034
+ const capsule = getCapsule(env.store, capsuleId);
1035
+ if (capsule === undefined) {
1036
+ return notFound(`Capsule not found: ${capsuleId}`);
1037
+ }
1038
+ return {
1039
+ status: 200,
1040
+ body: buildCapsuleResponseBody(deps, env.store, env.dbPath, capsule),
1041
+ };
1042
+ }
1043
+ finally {
1044
+ env.close();
1045
+ }
1046
+ });
1047
+ }
1048
+ export async function handleStartLocalKnowledgeCapsuleIndexing(ctx, deps) {
1049
+ return runHandler(async () => {
1050
+ const capsuleId = parseCapsuleId(ctx);
1051
+ await readJsonObject(ctx.req);
1052
+ const env = openStoreForDeps(deps);
1053
+ try {
1054
+ const capsule = getCapsule(env.store, capsuleId);
1055
+ if (capsule === undefined) {
1056
+ return notFound(`Capsule not found: ${capsuleId}`);
1057
+ }
1058
+ if (capsule.sourceIds.length === 0) {
1059
+ return emptyCapsuleIndexingConflict();
1060
+ }
1061
+ if (configuredProviderForCapsule(deps, capsule) === undefined) {
1062
+ return conflict("No configured embedding-capable model matches this capsule. Update the Model Gateway configuration before indexing it.");
1063
+ }
1064
+ // LK-003 (Epic #189): refuse to start a second concurrent indexer for the same
1065
+ // capsule — the orchestrator persists running jobs, so a duplicate POST would
1066
+ // race the in-flight one and corrupt vector counts.
1067
+ const runningJobId = latestRunningJobId(env.store, capsule.id);
1068
+ if (runningJobId !== undefined) {
1069
+ return indexingConflict("indexing-already-running", "An indexing job is already running for this capsule.", capsule.id, runningJobId);
1070
+ }
1071
+ const terminal = await runCapsuleIndexingJob(deps, env.store, capsule, {
1072
+ mode: undefined,
1073
+ force: false,
1074
+ });
1075
+ return indexingCompletionResponse(capsule.id, terminal, "Capsule indexing failed. Review the capsule health diagnostics and job history for details.");
1076
+ }
1077
+ finally {
1078
+ env.close();
1079
+ }
1080
+ });
1081
+ }
1082
+ export async function handleCancelLocalKnowledgeCapsuleIndexing(ctx, deps) {
1083
+ return runHandler(async () => {
1084
+ const capsuleId = parseCapsuleId(ctx);
1085
+ await readJsonObject(ctx.req);
1086
+ const env = openStoreForDeps(deps);
1087
+ try {
1088
+ const capsule = getCapsule(env.store, capsuleId);
1089
+ if (capsule === undefined) {
1090
+ return notFound(`Capsule not found: ${capsuleId}`);
1091
+ }
1092
+ if (!requestRunningJobCancellation(env.store, capsule.id)) {
1093
+ return conflict("No running indexing job was found for this capsule.");
1094
+ }
1095
+ return actionResponse(capsule.id);
1096
+ }
1097
+ finally {
1098
+ env.close();
1099
+ }
1100
+ });
1101
+ }
1102
+ // ─── Connect a source folder to a capsule (Epic #189) ─────────────────────────
1103
+ // A connector's entry point: attach a host folder (or file set) as a knowledge source
1104
+ // so it can be indexed. Connectors deliberately reach OUTSIDE the workspace (the product
1105
+ // connects ANY machine folder of manuals), so containment is by realpath + the always-on
1106
+ // deny list (never index ~/.ssh, ~/.aws, .git, …) — not a workspace root. Per-file size
1107
+ // and format limits are enforced later by the indexing discovery walk.
1108
+ function connectScopeRootPath(scope) {
1109
+ return scope.kind === "folder" || scope.kind === "files" ? scope.rootPath : scope.repositoryRoot;
1110
+ }
1111
+ function parseConnectSourceInput(body) {
1112
+ const scopeRaw = body.scope;
1113
+ if (typeof scopeRaw !== "object" || scopeRaw === null || Array.isArray(scopeRaw)) {
1114
+ throw new InvalidRequest('Field "scope" must be a knowledge-source scope object.');
1115
+ }
1116
+ const scope = scopeRaw;
1117
+ assertScopeShape(scope);
1118
+ const displayNameRaw = body.displayName;
1119
+ const displayName = typeof displayNameRaw === "string" && displayNameRaw.trim().length > 0
1120
+ ? requireSafeDisplayText("displayName", displayNameRaw)
1121
+ : basename(connectScopeRootPath(scope));
1122
+ if (!isSafeDisplaySummary(displayName)) {
1123
+ throw new InvalidRequest('Field "displayName" must be browser-safe when provided.');
1124
+ }
1125
+ return { scope, displayName };
1126
+ }
1127
+ // Canonicalize (realpath) then refuse denied locations and non-directory roots BEFORE
1128
+ // touching the store. realpath resolves symlinks so a link into ~/.ssh cannot slip past.
1129
+ function guardConnectorSourcePath(scope) {
1130
+ const canonical = canonicalizeScopeRoot(scope);
1131
+ const root = connectScopeRootPath(canonical);
1132
+ if (isDenied(root)) {
1133
+ throw new InvalidRequest("Source path is in a denied location and cannot be indexed.");
1134
+ }
1135
+ let stats;
1136
+ try {
1137
+ stats = statSync(root);
1138
+ }
1139
+ catch {
1140
+ throw new InvalidRequest("Source path does not exist or is not accessible.");
1141
+ }
1142
+ if (!stats.isDirectory()) {
1143
+ throw new InvalidRequest("Source path must be an existing directory.");
1144
+ }
1145
+ return canonical;
1146
+ }
1147
+ // Stable identity for connect-time dedup. Field-by-field (not JSON.stringify of the raw
1148
+ // object) so the request body's key order cannot defeat the comparison, and built on the
1149
+ // canonicalized scope so trailing-slash and symlink-alias spellings of the same folder fold
1150
+ // together. Scopes that differ in globs, recursion, or file lists stay distinct sources.
1151
+ //
1152
+ // files scope: the array order has no logical meaning (unlike includeGlobs/excludeGlobs on
1153
+ // folder/repository scopes where first-match-wins makes order semantically significant), so
1154
+ // we sort before serializing. This ensures [a,b] and [b,a] map to the same identity and the
1155
+ // idempotency check fires correctly on permuted re-connects.
1156
+ function scopeIdentity(scope) {
1157
+ const canonical = canonicalizeScopeRoot(scope);
1158
+ if (canonical.kind === "folder") {
1159
+ return JSON.stringify([
1160
+ "folder",
1161
+ canonical.rootPath,
1162
+ canonical.recursive,
1163
+ canonical.includeGlobs ?? null,
1164
+ canonical.excludeGlobs ?? null,
1165
+ ]);
1166
+ }
1167
+ if (canonical.kind === "repository") {
1168
+ return JSON.stringify([
1169
+ "repository",
1170
+ canonical.repositoryRoot,
1171
+ canonical.includeGlobs ?? null,
1172
+ canonical.excludeGlobs ?? null,
1173
+ ]);
1174
+ }
1175
+ return JSON.stringify(["files", canonical.rootPath, [...canonical.files].sort()]);
1176
+ }
1177
+ export async function handleConnectLocalKnowledgeCapsule(ctx, deps) {
1178
+ return runHandler(async () => {
1179
+ const capsuleId = parseCapsuleId(ctx);
1180
+ const { scope, displayName } = parseConnectSourceInput(await readJsonObject(ctx.req));
1181
+ const guarded = guardConnectorSourcePath(scope);
1182
+ const env = openStoreForDeps(deps);
1183
+ try {
1184
+ const capsule = getCapsule(env.store, capsuleId);
1185
+ if (capsule === undefined) {
1186
+ return notFound(`Capsule not found: ${capsuleId}`);
1187
+ }
1188
+ // Idempotent connect: re-connecting an identical scope (double-click, trailing-slash or
1189
+ // symlink-alias spelling of the same folder) must not create a second source row — a
1190
+ // duplicate would double-index every document and double every grounded citation.
1191
+ const targetIdentity = scopeIdentity(guarded);
1192
+ const alreadyConnected = listCapsuleSources(env.store, capsule.id).some((source) => scopeIdentity(source.scope) === targetIdentity);
1193
+ if (alreadyConnected) {
1194
+ return {
1195
+ status: 200,
1196
+ body: buildCapsuleResponseBody(deps, env.store, env.dbPath, capsule),
1197
+ };
1198
+ }
1199
+ addSourceToCapsule(env.store, capsule.id, {
1200
+ id: randomUUID(),
1201
+ displayName,
1202
+ tags: [],
1203
+ scope: guarded,
1204
+ }, createSqliteAuditSink(env.store));
1205
+ const updated = getCapsule(env.store, capsule.id) ?? capsule;
1206
+ return {
1207
+ status: 201,
1208
+ body: buildCapsuleResponseBody(deps, env.store, env.dbPath, updated),
1209
+ };
1210
+ }
1211
+ finally {
1212
+ env.close();
1213
+ }
1214
+ });
1215
+ }
1216
+ export async function handleDisconnectLocalKnowledgeCapsule(ctx, deps) {
1217
+ return runHandler(async () => {
1218
+ const capsuleId = parseCapsuleId(ctx);
1219
+ await readJsonObject(ctx.req);
1220
+ const env = openStoreForDeps(deps);
1221
+ try {
1222
+ const capsule = getCapsule(env.store, capsuleId);
1223
+ if (capsule === undefined) {
1224
+ return notFound(`Capsule not found: ${capsuleId}`);
1225
+ }
1226
+ disconnectCapsuleSources(env.store, capsule.id);
1227
+ return actionResponse(capsule.id);
1228
+ }
1229
+ finally {
1230
+ env.close();
1231
+ }
1232
+ });
1233
+ }
1234
+ export async function handleDeleteLocalKnowledgeCapsule(ctx, deps) {
1235
+ return runHandler(async () => {
1236
+ const capsuleId = parseCapsuleId(ctx);
1237
+ await readJsonObject(ctx.req);
1238
+ const env = openStoreForDeps(deps);
1239
+ try {
1240
+ const result = deleteCapsule(env.store, capsuleId, createSqliteAuditSink(env.store));
1241
+ return deleteActionResponse(result);
1242
+ }
1243
+ finally {
1244
+ env.close();
1245
+ }
1246
+ });
1247
+ }
1248
+ export async function handleReindexLocalKnowledgeCapsule(ctx, deps) {
1249
+ return runHandler(async () => {
1250
+ const capsuleId = parseCapsuleId(ctx);
1251
+ const body = await readJsonObject(ctx.req);
1252
+ const reindexRequest = validateCapsuleReindexRequest({ ...body, capsuleId });
1253
+ if (!reindexRequest.ok) {
1254
+ throw new InvalidRequest(reindexRequest.errors.join(" "));
1255
+ }
1256
+ const mode = reindexRequest.value.mode;
1257
+ const force = reindexRequest.value.force ?? false;
1258
+ const env = openStoreForDeps(deps);
1259
+ try {
1260
+ const capsule = getCapsule(env.store, capsuleId);
1261
+ if (capsule === undefined) {
1262
+ return notFound(`Capsule not found: ${capsuleId}`);
1263
+ }
1264
+ if (capsule.sourceIds.length === 0) {
1265
+ return emptyCapsuleIndexingConflict();
1266
+ }
1267
+ if (configuredProviderForCapsule(deps, capsule) === undefined) {
1268
+ return conflict("No configured embedding-capable model matches this capsule. Update the Model Gateway configuration before refreshing it.");
1269
+ }
1270
+ // LK-003 (Epic #189): same concurrent-run guard as the start handler.
1271
+ const runningJobId = latestRunningJobId(env.store, capsule.id);
1272
+ if (runningJobId !== undefined) {
1273
+ return indexingConflict("indexing-already-running", "An indexing job is already running for this capsule.", capsule.id, runningJobId);
1274
+ }
1275
+ const terminal = await runCapsuleIndexingJob(deps, env.store, capsule, {
1276
+ mode,
1277
+ force,
1278
+ });
1279
+ return indexingCompletionResponse(capsule.id, terminal, "Capsule refresh failed. Review the capsule health diagnostics and job history for details.");
1280
+ }
1281
+ finally {
1282
+ env.close();
1283
+ }
1284
+ });
1285
+ }