@bevel-software/platform-core-backend 0.28.1 → 0.30.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 (274) hide show
  1. package/agent-guide/access-control.md +15 -0
  2. package/agent-guide/skills.md +8 -0
  3. package/dist/__tests__/test-repositories.d.ts +3 -0
  4. package/dist/__tests__/test-repositories.d.ts.map +1 -0
  5. package/dist/__tests__/test-repositories.js +18 -0
  6. package/dist/__tests__/test-repositories.js.map +1 -0
  7. package/dist/core/catalog-revision.d.ts.map +1 -1
  8. package/dist/core/catalog-revision.js +4 -2
  9. package/dist/core/catalog-revision.js.map +1 -1
  10. package/dist/core/compose-embed.d.ts +35 -0
  11. package/dist/core/compose-embed.d.ts.map +1 -0
  12. package/dist/core/compose-embed.js +14 -0
  13. package/dist/core/compose-embed.js.map +1 -0
  14. package/dist/core/core-ports.d.ts +10 -0
  15. package/dist/core/core-ports.d.ts.map +1 -1
  16. package/dist/core/core-ports.js.map +1 -1
  17. package/dist/core/create-core-server.d.ts.map +1 -1
  18. package/dist/core/create-core-server.js +8 -2
  19. package/dist/core/create-core-server.js.map +1 -1
  20. package/dist/core/create-core-services.d.ts +4 -1
  21. package/dist/core/create-core-services.d.ts.map +1 -1
  22. package/dist/core/create-core-services.js +57 -18
  23. package/dist/core/create-core-services.js.map +1 -1
  24. package/dist/core/lifecycle.d.ts +19 -6
  25. package/dist/core/lifecycle.d.ts.map +1 -1
  26. package/dist/core/lifecycle.js +17 -6
  27. package/dist/core/lifecycle.js.map +1 -1
  28. package/dist/modules/access/access-control.interface.d.ts +20 -0
  29. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  30. package/dist/modules/access/access-control.service.d.ts +68 -15
  31. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  32. package/dist/modules/access/access-control.service.js +282 -58
  33. package/dist/modules/access/access-control.service.js.map +1 -1
  34. package/dist/modules/access/access.routes.d.ts.map +1 -1
  35. package/dist/modules/access/access.routes.js +20 -6
  36. package/dist/modules/access/access.routes.js.map +1 -1
  37. package/dist/modules/agent-instructions/shared-file-rules.d.ts +1 -1
  38. package/dist/modules/agent-instructions/shared-file-rules.js +1 -1
  39. package/dist/modules/auth/index.d.ts +15 -0
  40. package/dist/modules/auth/index.d.ts.map +1 -0
  41. package/dist/modules/auth/index.js +20 -0
  42. package/dist/modules/auth/index.js.map +1 -0
  43. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  44. package/dist/modules/diff/diff.service.js +7 -1
  45. package/dist/modules/diff/diff.service.js.map +1 -1
  46. package/dist/modules/embed/embed.interface.d.ts +22 -0
  47. package/dist/modules/embed/embed.interface.d.ts.map +1 -1
  48. package/dist/modules/embed/embed.service.d.ts +28 -14
  49. package/dist/modules/embed/embed.service.d.ts.map +1 -1
  50. package/dist/modules/embed/embed.service.js +38 -17
  51. package/dist/modules/embed/embed.service.js.map +1 -1
  52. package/dist/modules/embed/embed.tools.d.ts.map +1 -1
  53. package/dist/modules/embed/embed.tools.js +10 -2
  54. package/dist/modules/embed/embed.tools.js.map +1 -1
  55. package/dist/modules/kb-fs/git-dir.d.ts +21 -0
  56. package/dist/modules/kb-fs/git-dir.d.ts.map +1 -0
  57. package/dist/modules/kb-fs/git-dir.js +58 -0
  58. package/dist/modules/kb-fs/git-dir.js.map +1 -0
  59. package/dist/modules/kb-fs/index.d.ts +9 -0
  60. package/dist/modules/kb-fs/index.d.ts.map +1 -0
  61. package/dist/modules/kb-fs/index.js +3 -0
  62. package/dist/modules/kb-fs/index.js.map +1 -0
  63. package/dist/modules/kb-fs/ref-files.d.ts +63 -0
  64. package/dist/modules/kb-fs/ref-files.d.ts.map +1 -0
  65. package/dist/modules/kb-fs/ref-files.js +249 -0
  66. package/dist/modules/kb-fs/ref-files.js.map +1 -0
  67. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
  68. package/dist/modules/plugins/join-request-jobs.service.js +43 -8
  69. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
  70. package/dist/modules/plugins/teams.routes.d.ts +4 -0
  71. package/dist/modules/plugins/teams.routes.d.ts.map +1 -1
  72. package/dist/modules/plugins/teams.routes.js +9 -1
  73. package/dist/modules/plugins/teams.routes.js.map +1 -1
  74. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  75. package/dist/modules/skills/skills.tools.d.ts +8 -1
  76. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  77. package/dist/modules/skills/skills.tools.js +115 -11
  78. package/dist/modules/skills/skills.tools.js.map +1 -1
  79. package/dist/modules/tool-auth/internal-token.service.d.ts +1 -1
  80. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +1 -2
  81. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  82. package/dist/modules/tool-auth/tool-auth.middleware.js +1 -2
  83. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  84. package/dist/modules/tool-helpers/default-branch-write.d.ts +69 -0
  85. package/dist/modules/tool-helpers/default-branch-write.d.ts.map +1 -0
  86. package/dist/modules/tool-helpers/default-branch-write.js +63 -0
  87. package/dist/modules/tool-helpers/default-branch-write.js.map +1 -0
  88. package/dist/modules/tool-helpers/index.d.ts +1 -0
  89. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  90. package/dist/modules/tool-helpers/index.js +1 -0
  91. package/dist/modules/tool-helpers/index.js.map +1 -1
  92. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -2
  93. package/dist/modules/tool-helpers/tool.contract.d.ts +2 -2
  94. package/dist/modules/workflow/file-lock.service.d.ts +27 -5
  95. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  96. package/dist/modules/workflow/file-lock.service.js +70 -18
  97. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  98. package/dist/modules/workflow/git/git.service.d.ts +42 -0
  99. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  100. package/dist/modules/workflow/git/git.service.js +201 -24
  101. package/dist/modules/workflow/git/git.service.js.map +1 -1
  102. package/dist/modules/workflow/index.d.ts +8 -0
  103. package/dist/modules/workflow/index.d.ts.map +1 -0
  104. package/dist/modules/workflow/index.js +8 -0
  105. package/dist/modules/workflow/index.js.map +1 -0
  106. package/dist/modules/workflow/pending-commits.service.d.ts +2 -17
  107. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  108. package/dist/modules/workflow/pending-commits.service.js +4 -24
  109. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  110. package/dist/modules/workflow/workflow-hooks.d.ts +1 -1
  111. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  112. package/dist/modules/workflow/workflow.routes.js +20 -1
  113. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  114. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  115. package/dist/modules/workflow/workflow.service.js +5 -3
  116. package/dist/modules/workflow/workflow.service.js.map +1 -1
  117. package/dist/modules/workspace/agent-access.gate.d.ts +2 -2
  118. package/dist/modules/workspace/agent-access.gate.js +2 -2
  119. package/dist/modules/workspace/bounded-matcher.d.ts +79 -0
  120. package/dist/modules/workspace/bounded-matcher.d.ts.map +1 -0
  121. package/dist/modules/workspace/bounded-matcher.js +261 -0
  122. package/dist/modules/workspace/bounded-matcher.js.map +1 -0
  123. package/dist/modules/workspace/fetch-instant.d.ts +9 -0
  124. package/dist/modules/workspace/fetch-instant.d.ts.map +1 -0
  125. package/dist/modules/workspace/fetch-instant.js +13 -0
  126. package/dist/modules/workspace/fetch-instant.js.map +1 -0
  127. package/dist/modules/workspace/index.d.ts +17 -0
  128. package/dist/modules/workspace/index.d.ts.map +1 -0
  129. package/dist/modules/workspace/index.js +16 -0
  130. package/dist/modules/workspace/index.js.map +1 -0
  131. package/dist/modules/workspace/kb-repository.contract.d.ts +124 -0
  132. package/dist/modules/workspace/kb-repository.contract.d.ts.map +1 -0
  133. package/dist/modules/workspace/kb-repository.contract.js +19 -0
  134. package/dist/modules/workspace/kb-repository.contract.js.map +1 -0
  135. package/dist/modules/workspace/kb-repository.d.ts +236 -0
  136. package/dist/modules/workspace/kb-repository.d.ts.map +1 -0
  137. package/dist/modules/workspace/kb-repository.js +811 -0
  138. package/dist/modules/workspace/kb-repository.js.map +1 -0
  139. package/dist/modules/workspace/pattern-matcher.contract.d.ts +50 -0
  140. package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -0
  141. package/dist/modules/workspace/pattern-matcher.contract.js +27 -0
  142. package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -0
  143. package/dist/modules/workspace/routine-write-policy.d.ts +0 -7
  144. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  145. package/dist/modules/workspace/routine-write-policy.js +0 -5
  146. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  147. package/dist/modules/workspace/set-aside-clone.d.ts +40 -0
  148. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -1
  149. package/dist/modules/workspace/set-aside-clone.js +139 -0
  150. package/dist/modules/workspace/set-aside-clone.js.map +1 -1
  151. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +99 -0
  152. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  153. package/dist/modules/workspace/startup/kb-startup-runner.js +407 -29
  154. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  155. package/dist/modules/workspace/workspace.service.d.ts +68 -32
  156. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  157. package/dist/modules/workspace/workspace.service.js +240 -161
  158. package/dist/modules/workspace/workspace.service.js.map +1 -1
  159. package/dist/modules/workspace/workspace.tools.d.ts +13 -4
  160. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  161. package/dist/modules/workspace/workspace.tools.js +268 -356
  162. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  163. package/dist/shared/workspace-id.d.ts +17 -0
  164. package/dist/shared/workspace-id.d.ts.map +1 -1
  165. package/dist/shared/workspace-id.js +24 -0
  166. package/dist/shared/workspace-id.js.map +1 -1
  167. package/dist/test-setup.js +4 -0
  168. package/dist/test-setup.js.map +1 -1
  169. package/mcp-app/page.html +142 -8
  170. package/package.json +3 -3
  171. package/src/__tests__/test-repositories.ts +22 -0
  172. package/src/__tests__/workspace-service.ts +69 -0
  173. package/src/core/__tests__/compose-embed.test.ts +65 -0
  174. package/src/core/__tests__/lifecycle.test.ts +45 -26
  175. package/src/core/catalog-revision.ts +4 -2
  176. package/src/core/compose-embed.ts +50 -0
  177. package/src/core/core-ports.ts +10 -0
  178. package/src/core/create-core-server.ts +8 -2
  179. package/src/core/create-core-services.ts +63 -24
  180. package/src/core/lifecycle.ts +34 -12
  181. package/src/modules/access/__tests__/access-control.atref-cache.test.ts +443 -25
  182. package/src/modules/access/__tests__/access-control.group-read.test.ts +73 -0
  183. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +66 -0
  184. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
  185. package/src/modules/access/access-control.interface.ts +22 -0
  186. package/src/modules/access/access-control.service.ts +302 -58
  187. package/src/modules/access/access.routes.ts +22 -6
  188. package/src/modules/agent-instructions/shared-file-rules.ts +1 -1
  189. package/src/modules/auth/index.ts +29 -0
  190. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +2 -1
  191. package/src/modules/diff/__tests__/diff.service.test.ts +2 -1
  192. package/src/modules/diff/__tests__/diff.service.worktree.test.ts +76 -0
  193. package/src/modules/diff/diff.service.ts +6 -4
  194. package/src/modules/embed/__tests__/embed.service.test.ts +76 -3
  195. package/src/modules/embed/__tests__/embed.tools.test.ts +29 -0
  196. package/src/modules/embed/embed.interface.ts +24 -0
  197. package/src/modules/embed/embed.service.ts +44 -19
  198. package/src/modules/embed/embed.tools.ts +10 -2
  199. package/src/modules/kb-fs/__tests__/git-dir.test.ts +77 -0
  200. package/src/modules/kb-fs/__tests__/ref-files.test.ts +338 -0
  201. package/src/modules/kb-fs/git-dir.ts +53 -0
  202. package/src/modules/kb-fs/index.ts +8 -0
  203. package/src/modules/kb-fs/ref-files.ts +279 -0
  204. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +73 -6
  205. package/src/modules/plugins/__tests__/teams.routes.test.ts +55 -0
  206. package/src/modules/plugins/join-request-jobs.service.ts +45 -8
  207. package/src/modules/plugins/teams.routes.ts +14 -1
  208. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +2 -2
  209. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +2 -2
  210. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
  211. package/src/modules/skills/skills.contract.ts +4 -0
  212. package/src/modules/skills/skills.tools.ts +120 -10
  213. package/src/modules/tool-auth/internal-token.service.ts +1 -1
  214. package/src/modules/tool-auth/tool-auth.middleware.ts +1 -2
  215. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +2 -2
  216. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +581 -0
  217. package/src/modules/tool-helpers/default-branch-write.ts +90 -0
  218. package/src/modules/tool-helpers/index.ts +5 -0
  219. package/src/modules/tool-helpers/tool-handler.ts +2 -2
  220. package/src/modules/tool-helpers/tool.contract.ts +2 -2
  221. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +42 -3
  222. package/src/modules/workflow/__tests__/fake-file-lock-db.ts +13 -2
  223. package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +168 -1
  224. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +102 -6
  225. package/src/modules/workflow/file-lock.service.ts +79 -18
  226. package/src/modules/workflow/git/__tests__/git.service.changedPathsMemo.test.ts +175 -0
  227. package/src/modules/workflow/git/__tests__/git.service.readFileAtRef.test.ts +42 -0
  228. package/src/modules/workflow/git/__tests__/git.service.worktrees.test.ts +284 -0
  229. package/src/modules/workflow/git/git.service.ts +207 -22
  230. package/src/modules/workflow/index.ts +7 -0
  231. package/src/modules/workflow/pending-commits.service.ts +4 -23
  232. package/src/modules/workflow/workflow-hooks.ts +1 -1
  233. package/src/modules/workflow/workflow.routes.ts +24 -1
  234. package/src/modules/workflow/workflow.service.ts +3 -2
  235. package/src/modules/workspace/__tests__/agent-downloads.test.ts +2 -2
  236. package/src/modules/workspace/__tests__/bounded-matcher.test.ts +213 -0
  237. package/src/modules/workspace/__tests__/git-internals.security.test.ts +22 -7
  238. package/src/modules/workspace/__tests__/kb-repository.test.ts +481 -0
  239. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +14 -0
  240. package/src/modules/workspace/__tests__/routine-write-policy.test.ts +0 -11
  241. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +2 -2
  242. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +2 -2
  243. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +4 -3
  244. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +2 -2
  245. package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +2 -2
  246. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +12 -5
  247. package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -6
  248. package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +2 -1
  249. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -1
  250. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +2 -1
  251. package/src/modules/workspace/__tests__/workspace.service.test.ts +24 -23
  252. package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +2 -1
  253. package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +620 -0
  254. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +15 -1
  255. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +71 -5
  256. package/src/modules/workspace/__tests__/workspace.tools.test.ts +174 -309
  257. package/src/modules/workspace/agent-access.gate.ts +2 -2
  258. package/src/modules/workspace/bounded-matcher.ts +328 -0
  259. package/src/modules/workspace/fetch-instant.ts +13 -0
  260. package/src/modules/workspace/index.ts +26 -0
  261. package/src/modules/workspace/kb-repository.contract.ts +124 -0
  262. package/src/modules/workspace/kb-repository.ts +844 -0
  263. package/src/modules/workspace/pattern-matcher.contract.ts +69 -0
  264. package/src/modules/workspace/routine-write-policy.ts +0 -15
  265. package/src/modules/workspace/set-aside-clone.ts +140 -0
  266. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +3 -2
  267. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +12 -10
  268. package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +613 -0
  269. package/src/modules/workspace/startup/kb-startup-runner.ts +422 -29
  270. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +4 -3
  271. package/src/modules/workspace/workspace.service.ts +247 -169
  272. package/src/modules/workspace/workspace.tools.ts +299 -385
  273. package/src/shared/workspace-id.ts +24 -0
  274. package/src/test-setup.ts +5 -0
@@ -475,7 +475,7 @@ export async function createCoreServer(
475
475
  // because they are the only ones that gate their whole payload on the
476
476
  // caller's read access, so they take the access service and nothing else.
477
477
  registerChangeRequestReadTools(core.toolRegistry, toolsRouter, ta, th, core.accessControl, core.kb);
478
- const workspaceTools = registerWorkspaceTools(core.toolRegistry, toolsRouter, ta, th, core.spillStore, core.docExtractService, core.accessControl, core.kb, agentAccessGate, core.routineWritePolicy, core.sessionSink, allowedToolsChecker, core.changeGate, core.agentUploadStore, core.agentGuide, core.agentDownloadStore, core.starterPackService, core.disk);
478
+ const workspaceTools = registerWorkspaceTools(core.toolRegistry, toolsRouter, ta, th, core.spillStore, core.docExtractService, core.accessControl, core.kb, agentAccessGate, core.routineWritePolicy, core.sessionSink, allowedToolsChecker, core.changeGate, core.agentUploadStore, core.agentGuide, core.agentDownloadStore, core.starterPackService, core.disk, core.patternMatcher);
479
479
  // `open_page` — the knowledge-base page shown inside a chat. Registered
480
480
  // here, on the same router as the file tools, because it answers with
481
481
  // `read_file`'s own read: the read hook, the access gate and the
@@ -509,7 +509,13 @@ export async function createCoreServer(
509
509
  // one-time link is the whole credential an agent's `curl` carries. Every
510
510
  // file behind it was judged when `request_file_download` issued it.
511
511
  toolsRouter.use(createAgentDownloadRoutes({ downloads: core.agentDownloadStore, identify: core.agentDownloadFetcher }));
512
- registerSkillsTools(core.toolRegistry, toolsRouter, ta, th, core.skillService, allowedToolsChecker);
512
+ registerSkillsTools(core.toolRegistry, toolsRouter, ta, th, core.skillService, allowedToolsChecker, {
513
+ accessControl: core.accessControl,
514
+ defaultWorkspaceId: () => core.kb.defaultWorkspaceId(),
515
+ // A skill's files as committed on the default branch, for the whole-skill
516
+ // verdict: the same clone and the same git the skill's history is read from.
517
+ committedFiles: core.gitService,
518
+ });
513
519
  // Definitions only: the endpoints they describe are the app's own plugin
514
520
  // creation routes, mounted below behind the key-or-session gate.
515
521
  registerPluginsTools(core.toolRegistry, core.kb);
@@ -17,8 +17,11 @@ import { getDb, type Database } from '../modules/database/connection.js';
17
17
  import { runCoreMigrations } from '../modules/database/migrate.js';
18
18
  import { coreMigrationsDir } from '../assets.js';
19
19
  import { WorkspaceService } from '../modules/workspace/workspace.service.js';
20
+ import { KbRepository, repositoryDirFor } from '../modules/workspace/kb-repository.js';
21
+ import type { IKbRepository } from '../modules/workspace/kb-repository.contract.js';
20
22
  import { AccountLinkService } from '../modules/embed/account-link.service.js';
21
- import { EmbedService } from '../modules/embed/embed.service.js';
23
+ import type { EmbedService } from '../modules/embed/embed.service.js';
24
+ import { composeEmbedService } from './compose-embed.js';
22
25
  import { McpAppService, type IMcpAppService } from '../modules/embed/mcp-app.js';
23
26
  import { createFileReaderRegistry } from '../modules/workspace/file-readers/file-reader.registry.js';
24
27
  import { RoutineWritePolicyService } from '../modules/workspace/routine-write-policy.js';
@@ -61,6 +64,8 @@ import { SpillStore } from '../modules/workspace/spill-store.js';
61
64
  import { AgentUploadStore, assertUploadsRootOutsideWorkspaces } from '../modules/workspace/agent-upload.store.js';
62
65
  import { AgentDownloadStore, type IAgentDownloadStore } from '../modules/workspace/agent-download.store.js';
63
66
  import { createDownloadFetcherIdentifier } from '../modules/workspace/agent-download.routes.js';
67
+ import { WorkerPatternMatcher } from '../modules/workspace/bounded-matcher.js';
68
+ import type { IPatternMatcher } from '../modules/workspace/pattern-matcher.contract.js';
64
69
  import type { Request } from 'express';
65
70
  import { DocExtractService } from '../modules/workspace/file-readers/doc-extract.service.js';
66
71
  import { UuidSessionSink, type ISessionSink } from '../modules/workspace/session-sink.js';
@@ -242,6 +247,8 @@ export interface CoreServices {
242
247
  agentUploadStore: AgentUploadStore;
243
248
  /** The bytes `request_file_download` captured, held until their one-time link is fetched or expires. */
244
249
  agentDownloadStore: IAgentDownloadStore;
250
+ /** Runs a caller's `grep` pattern off the request thread under a budget; its workers end when the graph stops. */
251
+ patternMatcher: IPatternMatcher;
245
252
  /**
246
253
  * Every user a download fetch identifies itself as, by its bearer and its
247
254
  * session cookie — none when it carries none that verifies — so the
@@ -569,6 +576,30 @@ export async function createCoreServices(
569
576
  * Under the backups root: a persistent volume of its own, and one nothing
570
577
  * sweeps (see the runner's `reconcileClonesWithConfiguredRepository`).
571
578
  */
579
+ // Shared mutex so git and diff operations on the same workspace serialize
580
+ // against each other — a backup-reseed races a concurrent commit otherwise.
581
+ const workspaceMutex = new WorkspaceMutex();
582
+ /**
583
+ * The knowledge base's ONE repository: every branch workspace is a worktree
584
+ * of it (see `kb-repository.ts`). One instance, handed to the workspace
585
+ * service, the startup phase and (through the workspace service) the git
586
+ * layer, so one fetch serves every workspace and the writes to its one
587
+ * config file are serialized in one place. Git operations in the worktrees
588
+ * are not serialized against each other beyond the workspace mutex: git's
589
+ * own locks cover a repository shared by worktrees.
590
+ *
591
+ * Under the backups root, like the set-aside copies: a persistent volume in
592
+ * every shipped compose file, so no new mount is needed, and outside the
593
+ * workspaces root, where the orphan sweep never sees it. The folder name
594
+ * starts with a dot, which no branch — and so no backup ledger's workspace
595
+ * id — can. On hexis-cloud every tenant has its own backups root: one
596
+ * repository per tenant.
597
+ */
598
+ const kbRepository: IKbRepository = new KbRepository({
599
+ dir: repositoryDirFor(config.workspacesRoot, kbDirName, path.join(config.backupsRoot, '.kb-repository')),
600
+ kbRepoUrl: () => repositorySource.url(),
601
+ gitRunner,
602
+ });
572
603
  const replacedWorkingCopiesRoot = path.join(config.backupsRoot, 'replaced-working-copies');
573
604
  /**
574
605
  * What must leave with a working copy that is set aside, whoever sets it
@@ -634,6 +665,7 @@ export async function createCoreServices(
634
665
  );
635
666
  });
636
667
  }),
668
+ kbRepository,
637
669
  );
638
670
  // The KB startup phase: every seeding, scaffolding and migration concern,
639
671
  // run through one runner at the deployment's quiet moments (boot + setup
@@ -671,6 +703,9 @@ export async function createCoreServices(
671
703
  // The same folder the workspace service sets aside into — see
672
704
  // `replacedWorkingCopiesRoot` above for why the two must agree.
673
705
  setAsideRoot: replacedWorkingCopiesRoot,
706
+ // The one repository every branch workspace is a worktree of — the same
707
+ // instance the workspace service holds (see `kbRepository` above).
708
+ repository: kbRepository,
674
709
  kbDirName,
675
710
  templateDir: config.kbTemplateDir,
676
711
  defaultBranch: () => kb.defaultBranch,
@@ -740,6 +775,7 @@ export async function createCoreServices(
740
775
  'captured download bytes wait there for their one-time links, so a root inside a workspace would let the ' +
741
776
  'file tools read them. Point AGENT_UPLOADS_ROOT at a directory whose parent is outside WORKSPACES_ROOT.',
742
777
  });
778
+ const patternMatcher = new WorkerPatternMatcher();
743
779
  const agentDownloadStore = new AgentDownloadStore({
744
780
  root: agentDownloadsRoot,
745
781
  publicBaseUrl: config.publicBackendUrl,
@@ -826,9 +862,6 @@ export async function createCoreServices(
826
862
  // registers its listener here, after this returns (see `AccountHooks`).
827
863
  const accountHooks = new AccountHooks();
828
864
 
829
- // Shared mutex so git and diff operations on the same workspace serialize
830
- // against each other — a backup-reseed races a concurrent commit otherwise.
831
- const workspaceMutex = new WorkspaceMutex();
832
865
  // Workflow lifecycle hooks — the ONE registry this composition shares
833
866
  // between GitService (advisory commit validation), the agent-access gate
834
867
  // (the blocking agentRead / preWrite hooks), and the composition root,
@@ -1288,26 +1321,31 @@ export async function createCoreServices(
1288
1321
  // judges them with the same access resolver the app file page uses, so what
1289
1322
  // a reader may see and change inside a chat is what they may see and change
1290
1323
  // in the app — never a second answer to the same question.
1291
- const embedService = new EmbedService(
1292
- // The RESOLVED checkout folder, not the env's: `config.kbDirName` is only
1293
- // the environment value, empty on a deployment that took the default or
1294
- // named it in setup, and every path the embed builds starts with it.
1295
- { ...config, kbDirName },
1296
- kb,
1297
- workspaceService,
1298
- accessControl,
1299
- authService,
1300
- workflowService,
1301
- gitService,
1302
- new AccountLinkService(db),
1303
- // The SAME extension-to-reader registry `read_file` dispatches on, so the
1304
- // embed answer to "is this text, or bytes a renderer fetches?" cannot
1305
- // disagree with what a read of the file returns.
1306
- createFileReaderRegistry(docExtractService),
1307
- // The SAME pre-disk gate the file editor and the agent tools run: a
1308
- // `roles.yaml` that would not parse is refused before it is written, from
1309
- // a chat exactly as from the app.
1310
- makeRolesYamlWriteValidator(kbDirName),
1324
+ const embedService = composeEmbedService(
1325
+ {
1326
+ // The RESOLVED checkout folder, not the env's: `config.kbDirName` is only
1327
+ // the environment value, empty on a deployment that took the default or
1328
+ // named it in setup, and every path the embed builds starts with it.
1329
+ config: { ...config, kbDirName },
1330
+ kb,
1331
+ workspaceService,
1332
+ accessControl,
1333
+ authService,
1334
+ workflowService,
1335
+ gitService,
1336
+ accountLinks: new AccountLinkService(db),
1337
+ // The SAME extension-to-reader registry `read_file` dispatches on, so the
1338
+ // embed answer to "is this text, or bytes a renderer fetches?" cannot
1339
+ // disagree with what a read of the file returns.
1340
+ readers: createFileReaderRegistry(docExtractService),
1341
+ // The SAME pre-disk gate the file editor and the agent tools run: a
1342
+ // `roles.yaml` that would not parse is refused before it is written, from
1343
+ // a chat exactly as from the app.
1344
+ validateWrite: makeRolesYamlWriteValidator(kbDirName),
1345
+ },
1346
+ // A node-id reference resolves only where a node graph is: the
1347
+ // distribution's, when it registers one (`ports.embedNodeIdResolver`).
1348
+ ports,
1311
1349
  );
1312
1350
  // The `ui://` view `open_page` carries, with the one origin it may frame:
1313
1351
  // this deployment own public origin.
@@ -1592,6 +1630,7 @@ export async function createCoreServices(
1592
1630
  spillStore,
1593
1631
  agentUploadStore,
1594
1632
  agentDownloadStore,
1633
+ patternMatcher,
1595
1634
  agentDownloadFetcher,
1596
1635
  docExtractService,
1597
1636
  accessControl,
@@ -425,13 +425,18 @@ export interface CoreStopDeps {
425
425
  /** The lease loop from {@link holdCommitWorkerLease}: its stop finishes the in-flight commit and releases the lease. */
426
426
  commitWorker: Pick<LeaseLoopHandle, 'stop'>;
427
427
  /**
428
- * Background work to wind down before the pool goes — the join-request
429
- * sweep and its jobs, today. The timer is stopped first so nothing new is
430
- * scheduled, then the jobs already running are awaited (within the budget)
431
- * so the pool is not ended under a clone or a push that is still writing
432
- * to it. Optional: a caller that has not built them yet passes nothing.
428
+ * The plugin join-request jobs, whose sweep must be wound down before the
429
+ * pool goes. The timer is stopped first so nothing new is scheduled, then
430
+ * the jobs already running are awaited (within the budget) so the pool is
431
+ * not ended under a clone or a push that is still writing to it. Optional:
432
+ * a caller that has not built them yet passes nothing.
433
+ *
434
+ * Named as `CoreServices` names it, because the graph IS what is passed
435
+ * here (`stopCore(graph.core)`): under another name the slot stayed empty,
436
+ * the sweep outlived every stopped graph, and a multi-tenant host gained
437
+ * one more sweeper querying an ended pool, every minute, per eviction.
433
438
  */
434
- backgroundJobs?: { stopSweeping(): void; drain(): Promise<void> };
439
+ pluginJoinRequestJobs?: { stopSweeping(): void; drain(): Promise<void> };
435
440
  /**
436
441
  * The agent upload store, whose own sweep deletes staged bytes nobody
437
442
  * applied. Stopped with this graph because the next graph for the same
@@ -443,6 +448,12 @@ export interface CoreStopDeps {
443
448
  agentUploadStore?: { stopSweeping(): void; drainSweep(): Promise<void> };
444
449
  /** The agent download store, whose sweep deletes captured bytes nobody fetched — stopped for the same reason. */
445
450
  agentDownloadStore?: { stopSweeping(): void; drainSweep(): Promise<void> };
451
+ /**
452
+ * The grep pattern matcher, whose worker threads are this graph's own: ended
453
+ * here so a graph a tenant host evicts leaves no worker behind. Optional: a
454
+ * caller that has not built one passes nothing.
455
+ */
456
+ patternMatcher?: { close(): Promise<void> };
446
457
  /**
447
458
  * The workflow service, for the tidy-up it runs after each sweep (closing
448
459
  * empty requests, removing leftover branches): detached from whatever
@@ -532,7 +543,7 @@ async function releaseCore(deps: CoreStopDeps, remaining: () => number, log: (m:
532
543
  // server is closed (or the tenant is no longer routed to), so this is the
533
544
  // earliest point the interval is dead weight. Synchronous and unfailing
534
545
  // — it just clears an interval — so it needs no budget of its own.
535
- deps.backgroundJobs?.stopSweeping();
546
+ deps.pluginJoinRequestJobs?.stopSweeping();
536
547
  // And the upload store's sweep, for the same reason and in the same breath:
537
548
  // synchronous, and it only clears an interval and tells a sweep already
538
549
  // running to stop deleting.
@@ -548,11 +559,18 @@ async function releaseCore(deps: CoreStopDeps, remaining: () => number, log: (m:
548
559
  // Clearing the interval stops future ticks; it does nothing to a job a
549
560
  // tick already started, which holds a claim it heartbeats through the
550
561
  // pool and is mid-clone or mid-push. Awaited here, in the budget: a job
551
- // that finishes releases or marks its row; one that cannot in time is
552
- // killed with the process and its claim goes stale for the next sweep
553
- // — which is the recovery every crash already gets.
554
- if (deps.backgroundJobs) {
555
- await bounded(deps.backgroundJobs.drain(), remaining(), 'finishing the background jobs', log);
562
+ // that finishes releases or marks its row. One that cannot in time is
563
+ // NOT cancelled — the bounded wait only stops waiting — and what becomes
564
+ // of it depends on who is stopping: a process shutting down takes it
565
+ // along; a tenant host evicting one graph ends the pool under it, which
566
+ // cancels nothing already running — a clone or push in flight finishes
567
+ // on its own — but its next heartbeat or database write fails, so from
568
+ // there the job records nothing and stops. Either way its claim goes
569
+ // stale and the next sweep — the next boot's, or a live graph's — picks
570
+ // the row up and does the whole thing again, which is the recovery every
571
+ // crash already gets (and why each step is written to be redone).
572
+ if (deps.pluginJoinRequestJobs) {
573
+ await bounded(deps.pluginJoinRequestJobs.drain(), remaining(), 'finishing the join-request jobs', log);
556
574
  }
557
575
  // The sweep in flight when the interval was cleared: awaited so the next
558
576
  // graph for this tenant cannot start staging bytes while a store it has
@@ -569,6 +587,10 @@ async function releaseCore(deps: CoreStopDeps, remaining: () => number, log: (m:
569
587
  if (deps.workflowService) {
570
588
  await bounded(deps.workflowService.drainTidy(), remaining(), 'finishing the branch tidy-up', log);
571
589
  }
590
+ // Its workers end with the graph; a search still running is answered busy.
591
+ if (deps.patternMatcher) {
592
+ await bounded(deps.patternMatcher.close(), remaining(), 'ending the search workers', log);
593
+ }
572
594
  await bounded(deps.commitWorker.stop(), remaining(), 'stopping the commit worker', log);
573
595
  if (deps.secretsScope !== undefined) unregisterBevelSecretsVariableLoader(deps.secretsScope);
574
596
  await bounded(closeDb(deps.db), remaining(), 'ending the database pool', log);