@bevel-software/platform-core-backend 0.26.0 → 0.27.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 (613) hide show
  1. package/agent-guide/access-control.md +17 -0
  2. package/agent-guide/conventions.md +8 -0
  3. package/agent-guide/html-views.md +83 -0
  4. package/agent-guide/new-knowledge-base.md +24 -0
  5. package/agent-guide/tool-manuals.md +10 -0
  6. package/dist/assets.d.ts +19 -0
  7. package/dist/assets.d.ts.map +1 -1
  8. package/dist/assets.js +26 -2
  9. package/dist/assets.js.map +1 -1
  10. package/dist/core/catalog-revision.d.ts +19 -1
  11. package/dist/core/catalog-revision.d.ts.map +1 -1
  12. package/dist/core/catalog-revision.js +14 -3
  13. package/dist/core/catalog-revision.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts +5 -0
  15. package/dist/core/create-core-server.d.ts.map +1 -1
  16. package/dist/core/create-core-server.js +54 -3
  17. package/dist/core/create-core-server.js.map +1 -1
  18. package/dist/core/create-core-services.d.ts +26 -0
  19. package/dist/core/create-core-services.d.ts.map +1 -1
  20. package/dist/core/create-core-services.js +100 -8
  21. package/dist/core/create-core-services.js.map +1 -1
  22. package/dist/core/lifecycle.d.ts +16 -1
  23. package/dist/core/lifecycle.d.ts.map +1 -1
  24. package/dist/core/lifecycle.js +18 -1
  25. package/dist/core/lifecycle.js.map +1 -1
  26. package/dist/core-config.d.ts +21 -0
  27. package/dist/core-config.d.ts.map +1 -1
  28. package/dist/core-config.js +22 -1
  29. package/dist/core-config.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/modules/access/access-control.interface.d.ts +8 -0
  35. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  36. package/dist/modules/access/access-control.service.d.ts +1 -0
  37. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.js +11 -0
  39. package/dist/modules/access/access-control.service.js.map +1 -1
  40. package/dist/modules/access/directory-sync-bot.js +1 -1
  41. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  42. package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
  43. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  44. package/dist/modules/agent-guide/agent-guide.js +8 -0
  45. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  46. package/dist/modules/agent-guide/index.d.ts +1 -1
  47. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  48. package/dist/modules/agent-guide/index.js +1 -1
  49. package/dist/modules/agent-guide/index.js.map +1 -1
  50. package/dist/modules/agent-instructions/compose.d.ts +5 -0
  51. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  52. package/dist/modules/agent-instructions/compose.js +19 -2
  53. package/dist/modules/agent-instructions/compose.js.map +1 -1
  54. package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
  55. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  56. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  57. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  58. package/dist/modules/audit/agent-audit.service.js +43 -2
  59. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  60. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  61. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  62. package/dist/modules/audit/agent-connection.routes.js +38 -0
  63. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  64. package/dist/modules/audit/audit.contract.d.ts +24 -0
  65. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  66. package/dist/modules/audit/audit.contract.js.map +1 -1
  67. package/dist/modules/audit/index.d.ts +1 -0
  68. package/dist/modules/audit/index.d.ts.map +1 -1
  69. package/dist/modules/audit/index.js +1 -0
  70. package/dist/modules/audit/index.js.map +1 -1
  71. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  72. package/dist/modules/auth/account.routes.js +10 -3
  73. package/dist/modules/auth/account.routes.js.map +1 -1
  74. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  75. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  76. package/dist/modules/auth/auth.middleware.js +1 -1
  77. package/dist/modules/auth/auth.middleware.js.map +1 -1
  78. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  79. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  80. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  81. package/dist/modules/database/core-schema.d.ts +96 -0
  82. package/dist/modules/database/core-schema.d.ts.map +1 -1
  83. package/dist/modules/database/core-schema.js +32 -3
  84. package/dist/modules/database/core-schema.js.map +1 -1
  85. package/dist/modules/embed/account-link.service.d.ts +42 -0
  86. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  87. package/dist/modules/embed/account-link.service.js +50 -0
  88. package/dist/modules/embed/account-link.service.js.map +1 -0
  89. package/dist/modules/embed/embed-link.d.ts +58 -0
  90. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  91. package/dist/modules/embed/embed-link.js +160 -0
  92. package/dist/modules/embed/embed-link.js.map +1 -0
  93. package/dist/modules/embed/embed.errors.d.ts +30 -0
  94. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  95. package/dist/modules/embed/embed.errors.js +42 -0
  96. package/dist/modules/embed/embed.errors.js.map +1 -0
  97. package/dist/modules/embed/embed.interface.d.ts +204 -0
  98. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  99. package/dist/modules/embed/embed.interface.js +2 -0
  100. package/dist/modules/embed/embed.interface.js.map +1 -0
  101. package/dist/modules/embed/embed.routes.d.ts +59 -0
  102. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  103. package/dist/modules/embed/embed.routes.js +343 -0
  104. package/dist/modules/embed/embed.routes.js.map +1 -0
  105. package/dist/modules/embed/embed.service.d.ts +169 -0
  106. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  107. package/dist/modules/embed/embed.service.js +627 -0
  108. package/dist/modules/embed/embed.service.js.map +1 -0
  109. package/dist/modules/embed/embed.tools.d.ts +51 -0
  110. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  111. package/dist/modules/embed/embed.tools.js +170 -0
  112. package/dist/modules/embed/embed.tools.js.map +1 -0
  113. package/dist/modules/embed/mcp-app.d.ts +64 -0
  114. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  115. package/dist/modules/embed/mcp-app.js +147 -0
  116. package/dist/modules/embed/mcp-app.js.map +1 -0
  117. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  118. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  119. package/dist/modules/embed/mcp-app.routes.js +36 -0
  120. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  121. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  122. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  123. package/dist/modules/github-app/github-app.connection.js +34 -3
  124. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  125. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  126. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  127. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  128. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  129. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  130. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  131. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  132. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  133. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  134. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  135. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  136. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  137. package/dist/modules/mcp/mcp.service.d.ts +28 -1
  138. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  139. package/dist/modules/mcp/mcp.service.js +64 -8
  140. package/dist/modules/mcp/mcp.service.js.map +1 -1
  141. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  142. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  143. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  144. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  145. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  146. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  147. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  148. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  149. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  150. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  151. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  152. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  153. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  154. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  155. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  156. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  157. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  158. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  159. package/dist/modules/onboarding/starter-packs.js +156 -0
  160. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  161. package/dist/modules/plugins/index.d.ts +1 -1
  162. package/dist/modules/plugins/index.d.ts.map +1 -1
  163. package/dist/modules/plugins/index.js +1 -1
  164. package/dist/modules/plugins/index.js.map +1 -1
  165. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  166. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  167. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  168. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  169. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  170. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  171. package/dist/modules/plugins/plugins.routes.js +31 -0
  172. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  173. package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
  174. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  175. package/dist/modules/settings/deployment-settings.service.js +134 -1
  176. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  177. package/dist/modules/settings/repository-source.d.ts +1 -0
  178. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  179. package/dist/modules/settings/repository-source.js +3 -3
  180. package/dist/modules/settings/repository-source.js.map +1 -1
  181. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  182. package/dist/modules/skills/skills.tools.js +50 -32
  183. package/dist/modules/skills/skills.tools.js.map +1 -1
  184. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  185. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  186. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  187. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  188. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  189. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  190. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  191. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  192. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  193. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  194. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  195. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  196. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  197. package/dist/modules/tool-helpers/index.d.ts +28 -1
  198. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  199. package/dist/modules/tool-helpers/index.js +28 -1
  200. package/dist/modules/tool-helpers/index.js.map +1 -1
  201. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  202. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  203. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  204. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  205. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  206. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  207. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  208. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  209. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  210. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  211. package/dist/modules/tool-helpers/tool-def.js +70 -6
  212. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  213. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  214. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  215. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  216. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  217. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  218. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  219. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  220. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  221. package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
  222. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  223. package/dist/modules/tool-registry/description-length.d.ts +6 -2
  224. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  225. package/dist/modules/tool-registry/description-length.js +27 -5
  226. package/dist/modules/tool-registry/description-length.js.map +1 -1
  227. package/dist/modules/tool-registry/guide-first.d.ts +4 -0
  228. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
  229. package/dist/modules/tool-registry/guide-first.js +9 -1
  230. package/dist/modules/tool-registry/guide-first.js.map +1 -1
  231. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
  232. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
  233. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
  234. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
  235. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  236. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  237. package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
  238. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  239. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  240. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  241. package/dist/modules/workflow/file-lock.service.js +62 -5
  242. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  243. package/dist/modules/workflow/git/git.service.d.ts +72 -1
  244. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  245. package/dist/modules/workflow/git/git.service.js +172 -36
  246. package/dist/modules/workflow/git/git.service.js.map +1 -1
  247. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  248. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  249. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  250. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  251. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  252. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  253. package/dist/modules/workflow/pending-commits.service.js +25 -1
  254. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  255. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  256. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  257. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  258. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  259. package/dist/modules/workflow/recovery-bot.js +1 -1
  260. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  261. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
  263. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  264. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  265. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  266. package/dist/modules/workflow/sanitize-error.js +45 -0
  267. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  268. package/dist/modules/workflow/workflow.service.d.ts +183 -15
  269. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  270. package/dist/modules/workflow/workflow.service.js +805 -106
  271. package/dist/modules/workflow/workflow.service.js.map +1 -1
  272. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  273. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  274. package/dist/modules/workspace/agent-download.builder.js +238 -0
  275. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  276. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  277. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  278. package/dist/modules/workspace/agent-download.routes.js +156 -0
  279. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  280. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  281. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  282. package/dist/modules/workspace/agent-download.store.js +423 -0
  283. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  284. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  285. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  286. package/dist/modules/workspace/agent-upload.store.js +16 -6
  287. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  288. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  289. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  290. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  291. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  292. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  293. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  294. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  295. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  296. package/dist/modules/workspace/first-run.d.ts +111 -0
  297. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  298. package/dist/modules/workspace/first-run.js +181 -0
  299. package/dist/modules/workspace/first-run.js.map +1 -0
  300. package/dist/modules/workspace/move-links.d.ts +92 -0
  301. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  302. package/dist/modules/workspace/move-links.js +173 -0
  303. package/dist/modules/workspace/move-links.js.map +1 -0
  304. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  305. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
  306. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  307. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  308. package/dist/modules/workspace/workspace.routes.js +63 -22
  309. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  310. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  311. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  312. package/dist/modules/workspace/workspace.service.js +197 -24
  313. package/dist/modules/workspace/workspace.service.js.map +1 -1
  314. package/dist/modules/workspace/workspace.tools.d.ts +59 -1
  315. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  316. package/dist/modules/workspace/workspace.tools.js +443 -65
  317. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  318. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  319. package/dist/modules/write-access/write-access.js +9 -0
  320. package/dist/modules/write-access/write-access.js.map +1 -1
  321. package/dist/shared/domain-errors.d.ts +73 -1
  322. package/dist/shared/domain-errors.d.ts.map +1 -1
  323. package/dist/shared/domain-errors.js +86 -12
  324. package/dist/shared/domain-errors.js.map +1 -1
  325. package/dist/shared/git.contract.d.ts +10 -1
  326. package/dist/shared/git.contract.d.ts.map +1 -1
  327. package/dist/shared/git.contract.js.map +1 -1
  328. package/dist/shared/utf8-text.d.ts +15 -0
  329. package/dist/shared/utf8-text.d.ts.map +1 -0
  330. package/dist/shared/utf8-text.js +20 -0
  331. package/dist/shared/utf8-text.js.map +1 -0
  332. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  333. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  334. package/dist/tenancy/static-tenant-source.js +9 -2
  335. package/dist/tenancy/static-tenant-source.js.map +1 -1
  336. package/mcp-app/page.html +349 -0
  337. package/migrations/0017_atlassian_account_links.sql +37 -0
  338. package/migrations/meta/0017_snapshot.json +2396 -0
  339. package/migrations/meta/_journal.json +8 -1
  340. package/package.json +5 -3
  341. package/src/__tests__/packaged-assets-ship.test.ts +16 -4
  342. package/src/assets.ts +28 -2
  343. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  344. package/src/core/__tests__/gated-boot.test.ts +1 -0
  345. package/src/core/__tests__/lifecycle.test.ts +14 -0
  346. package/src/core/catalog-revision.ts +15 -2
  347. package/src/core/create-core-server.ts +67 -3
  348. package/src/core/create-core-services.ts +140 -6
  349. package/src/core/lifecycle.ts +28 -2
  350. package/src/core-config.ts +24 -1
  351. package/src/index.ts +2 -2
  352. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  353. package/src/modules/access/access-control.interface.ts +13 -0
  354. package/src/modules/access/access-control.service.ts +16 -0
  355. package/src/modules/access/directory-sync-bot.ts +1 -1
  356. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
  357. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
  358. package/src/modules/agent-guide/agent-guide.ts +9 -0
  359. package/src/modules/agent-guide/index.ts +1 -0
  360. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
  361. package/src/modules/agent-instructions/compose.ts +17 -2
  362. package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
  363. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  364. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  365. package/src/modules/audit/agent-audit.service.ts +58 -3
  366. package/src/modules/audit/agent-connection.routes.ts +56 -0
  367. package/src/modules/audit/audit.contract.ts +26 -0
  368. package/src/modules/audit/index.ts +1 -0
  369. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  370. package/src/modules/auth/account.routes.ts +10 -3
  371. package/src/modules/auth/auth.middleware.ts +1 -1
  372. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  373. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  374. package/src/modules/database/core-schema.ts +33 -3
  375. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  376. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  377. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  378. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  379. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  380. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  381. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  382. package/src/modules/embed/account-link.service.ts +76 -0
  383. package/src/modules/embed/embed-link.ts +180 -0
  384. package/src/modules/embed/embed.errors.ts +43 -0
  385. package/src/modules/embed/embed.interface.ts +203 -0
  386. package/src/modules/embed/embed.routes.ts +363 -0
  387. package/src/modules/embed/embed.service.ts +705 -0
  388. package/src/modules/embed/embed.tools.ts +206 -0
  389. package/src/modules/embed/mcp-app.routes.ts +41 -0
  390. package/src/modules/embed/mcp-app.ts +173 -0
  391. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  392. package/src/modules/github-app/github-app.connection.ts +38 -4
  393. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  394. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  395. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  396. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  397. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  398. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  399. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  400. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  401. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
  402. package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
  403. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  404. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  405. package/src/modules/mcp/mcp.service.ts +81 -12
  406. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  407. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  408. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  409. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  410. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  411. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  412. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  413. package/src/modules/onboarding/starter-packs.ts +182 -0
  414. package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
  415. package/src/modules/plugins/index.ts +1 -0
  416. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  417. package/src/modules/plugins/plugins.routes.ts +29 -0
  418. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
  419. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  420. package/src/modules/settings/deployment-settings.service.ts +131 -1
  421. package/src/modules/settings/repository-source.ts +4 -4
  422. package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
  423. package/src/modules/skills/skills.tools.ts +55 -35
  424. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  425. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  426. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  427. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  428. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  429. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  430. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  431. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  432. package/src/modules/tool-helpers/index.ts +34 -1
  433. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  434. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  435. package/src/modules/tool-helpers/tool-def.ts +132 -7
  436. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  437. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  438. package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
  439. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
  440. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
  441. package/src/modules/tool-registry/description-length.ts +34 -7
  442. package/src/modules/tool-registry/guide-first.ts +8 -1
  443. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  444. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  445. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  446. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  447. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  448. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  449. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  450. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  451. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  452. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  453. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  454. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  455. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
  457. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
  458. package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
  459. package/src/modules/workflow/file-lock.service.ts +67 -5
  460. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  461. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  462. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  463. package/src/modules/workflow/git/git.service.ts +176 -38
  464. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  465. package/src/modules/workflow/pending-commits.service.ts +29 -1
  466. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  467. package/src/modules/workflow/recovery-bot.ts +1 -1
  468. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
  469. package/src/modules/workflow/sanitize-error.ts +50 -0
  470. package/src/modules/workflow/workflow.service.ts +879 -112
  471. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  472. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  473. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  474. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  475. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  476. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  477. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  478. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  479. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  480. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  481. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  482. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  483. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  484. package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
  485. package/src/modules/workspace/agent-download.builder.ts +308 -0
  486. package/src/modules/workspace/agent-download.routes.ts +183 -0
  487. package/src/modules/workspace/agent-download.store.ts +533 -0
  488. package/src/modules/workspace/agent-upload.store.ts +23 -7
  489. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  490. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  491. package/src/modules/workspace/first-run.ts +222 -0
  492. package/src/modules/workspace/move-links.ts +237 -0
  493. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
  494. package/src/modules/workspace/workspace.routes.ts +64 -22
  495. package/src/modules/workspace/workspace.service.ts +202 -25
  496. package/src/modules/workspace/workspace.tools.ts +492 -63
  497. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  498. package/src/modules/write-access/write-access.ts +9 -0
  499. package/src/shared/domain-errors.ts +108 -11
  500. package/src/shared/git.contract.ts +8 -1
  501. package/src/shared/utf8-text.ts +21 -0
  502. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  503. package/src/tenancy/static-tenant-source.ts +10 -2
  504. package/starter-packs/README.md +103 -0
  505. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  506. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  507. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  508. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  509. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  510. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  511. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  512. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  513. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  514. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  515. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  516. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  517. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  518. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  519. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  520. package/starter-packs/engineering/pack.yaml +11 -0
  521. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  522. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  523. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  524. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  525. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  526. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  527. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  528. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  529. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  530. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  531. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  532. package/starter-packs/general/pack.yaml +10 -0
  533. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  534. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  535. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  536. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  537. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  538. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  539. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  540. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  541. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  542. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  543. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  544. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  545. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  546. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  547. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  548. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  549. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  550. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  551. package/starter-packs/operations/pack.yaml +12 -0
  552. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  553. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  554. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  555. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  556. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  557. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  558. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  559. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  560. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  561. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  562. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  563. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  564. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  565. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  566. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  567. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  568. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  569. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  570. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  571. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  572. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  573. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  574. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  575. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  576. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  577. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  578. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  579. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  580. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  581. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  582. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  583. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  584. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  585. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  586. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  587. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  588. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  589. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  590. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  591. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  592. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  593. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  594. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  595. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  596. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  597. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  598. package/starter-packs/sales/pack.yaml +13 -0
  599. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  600. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  601. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  602. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  603. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  604. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  605. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  606. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  607. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  608. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  609. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  610. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  611. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  612. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  613. package/starter-packs/support/pack.yaml +12 -0
@@ -114,6 +114,20 @@ describe('the shared file rules are one text, in two places', () => {
114
114
 
115
115
  });
116
116
 
117
+ describe('the delete rule for platform files', () => {
118
+ const managedItems = (): string => sharedFileRules(DEFAULT_KB_LAYOUT).find((r) => r.id === 'managed-items')!.body;
119
+
120
+ it('says a nested access.md is deleted by whoever may write it, and the root\'s platform files by nobody', async () => {
121
+ const body = managedItems();
122
+ expect(body).toContain('delete_file deletes a nested `access.md` for whoever may write it');
123
+ expect(body).toContain("its folder then follows its parent's rules");
124
+ expect(body).toContain("the repository root's `access.md` and `roles.yaml` are deleted by nobody");
125
+ // The sentence the refusal actually carries, as the tools and the app say it.
126
+ expect(body).toContain("\"<name> is the repository's own file and cannot be deleted.\"");
127
+ expect(await composeAgentGuide(DEFAULT_KB_LAYOUT)).toContain(body);
128
+ });
129
+ });
130
+
117
131
  describe('the handshake text stays inside the length it pins', () => {
118
132
  it('holds the shared section under its own cap', () => {
119
133
  const section = sharedFileRulesSection(DEFAULT_KB_LAYOUT);
@@ -30,6 +30,7 @@ import {
30
30
  stripHtmlComments,
31
31
  type KbLayout,
32
32
  } from '@bevel-software/platform-shared';
33
+ import { splitCallLine } from '@bevel-software/platform-mcp-core';
33
34
  import { sharedFileRulesSection } from './shared-file-rules.js';
34
35
 
35
36
  export { PREAMBLE_CAP, PREAMBLE_FILE, TOOL_PREFIX_CAP };
@@ -48,7 +49,13 @@ export const PLATFORM_HEADER =
48
49
  'Before answering a question about the organisation, its people, customers, products, processes, projects or internal terms, ' +
49
50
  'search the knowledge base: call `start_session` once, then `grep` for the key terms, `list_files` to orient, and `read_file` what matches. ' +
50
51
  'Prefer what you find there over memory or the web, and say so when the knowledge base is silent on something the organisation should have documented. ' +
51
- 'Skills are available as prompts and through `list_skills` and `get_skill`.';
52
+ 'Skills are available as prompts and through `list_skills` and `get_skill`. ' +
53
+ // There is no one calling shape to state: the knowledge-base tools take
54
+ // their arguments under `body`, a tool that calls another service takes them
55
+ // flat, and an agent told one rule for all of them sends a GET with a body.
56
+ // The per-tool `Call:` line is generated from each tool's own input schema,
57
+ // so pointing at it is both shorter and always right.
58
+ 'Tools do not share one calling shape: call each tool exactly as the `Call:` line at the top of its description shows.';
52
59
 
53
60
  /**
54
61
  * The fixed first line of the tool prefix. It always leads, so an admin's
@@ -167,9 +174,17 @@ export function composeAgentInstructions(
167
174
  * A tool description with the prefix ahead of it: the prefix, a blank line,
168
175
  * then the original. Purpose line first, because claude.ai cuts descriptions
169
176
  * near 500 characters.
177
+ *
178
+ * Except for the `Call:` line, which stays the very first line whatever else
179
+ * is prepended: it is how an agent learns the shape this tool's arguments take,
180
+ * and a description that opens with the purpose prefix instead would bury the
181
+ * one line the refusal for a wrong call points back to.
170
182
  */
171
183
  export function prefixToolDescription(toolPrefix: string, description: string | undefined): string {
172
- return description ? `${toolPrefix}\n\n${description}` : toolPrefix;
184
+ if (!description) return toolPrefix;
185
+ const { call, rest } = splitCallLine(description);
186
+ if (call === null) return `${toolPrefix}\n\n${description}`;
187
+ return rest === '' ? `${call}\n\n${toolPrefix}` : `${call}\n\n${toolPrefix}\n\n${rest}`;
173
188
  }
174
189
 
175
190
  /** An ATX heading line: `#` to `######`, then a space or the end. */
@@ -145,15 +145,15 @@ export function sharedFileRules(layout: KbLayout): readonly SharedFileRule[] {
145
145
  // that take content as a JSON string names the route in one sentence of
146
146
  // its own; WHY a file should go that way, and how, is here.
147
147
  id: 'upload-route',
148
- heading: 'Large, escape-heavy and binary content goes by upload',
148
+ heading: 'Large, escape-heavy and binary content: upload in, download out',
149
149
  body:
150
- 'write_file, write_files and edit_file take content as a JSON string you have to type out in full, so a long ' +
151
- 'file is cut off mid-answer, a file full of backslashes or `\\u` escapes fails to parse as a parameter, and ' +
152
- 'an image, a PDF or a zip cannot be sent at all. For any of those: call `request_file_upload`, POST the file — ' +
153
- 'or one zip holding many files — to the address it answers with any HTTP client ' +
154
- '(`curl -X POST --data-binary @<file> "<uploadUrl>?filename=<name>"`), then `apply_file_upload` to land it on ' +
155
- 'a branch in one commit. The bytes never pass through the conversation, so nothing is cut or mangled on the ' +
156
- 'way. A person can also use Upload in the app.',
150
+ 'write_file, write_files and edit_file take content as a JSON string you type out in full, so a long file ' +
151
+ 'is cut off mid-answer, a file full of backslashes or `\\u` escapes fails to parse as a parameter, and an ' +
152
+ 'image, a PDF or a zip cannot be sent at all. For those, call `request_file_upload`, POST the file (or one ' +
153
+ 'zip of many) with any HTTP client (`curl -X POST --data-binary @<file> ' +
154
+ '"<uploadUrl>?filename=<name>"`), then `apply_file_upload` lands it in one commit. To take copies OUT, ' +
155
+ '`request_file_download` answers a one-time link per file and a zip per folder (`curl -o <name> ' +
156
+ '"<downloadUrl>"`). Either way no bytes pass through the conversation.',
157
157
  },
158
158
  {
159
159
  id: 'dry-run-confirm',
@@ -163,22 +163,22 @@ export function sharedFileRules(layout: KbLayout): readonly SharedFileRule[] {
163
163
  'what the call would touch, `allowed`, and `reason` when it may not run. A non-empty folder is deleted, and a ' +
164
164
  'move that changes your access runs, only with `confirm: true`; without it the call changes nothing and ' +
165
165
  'returns the same impact with `confirmationRequired: true`. Do NOT set `confirm: true` on your first call — ' +
166
- 'dry-run, check the impact, then confirm. delete_file takes neither: it removes the one file you named, so ' +
167
- 'check it first with file_stat (`deletable`) if you are unsure.',
166
+ 'dry-run, check the impact, then confirm. delete_file takes neither: check it first with file_stat ' +
167
+ '(`deletable`) if you are unsure. delete_branch has its own `dryRun`.',
168
168
  },
169
169
  {
170
170
  id: 'managed-items',
171
171
  heading: 'What these tools never move or delete',
172
172
  body:
173
- `A platform file (${platformFileList(layout)}) is refused with ` +
174
- '"<name> is a platform file and stays in its folder." — a folder that moves ' +
175
- 'takes its own platform files along, still in their folder, and a folder that is deleted takes them with it in ' +
176
- 'the same one change, so its files are never left ungoverned part-way. A platform folder (the repository root ' +
177
- `or a reserved root folder such as \`${layout.knowledgeBaseDir}/\`) and git metadata are refused, and so is creating a ` +
178
- 'platform file or folder at a destination (renaming a note to `access.md` is refused). A path that is, or goes ' +
179
- 'through, a symbolic link is refused: links are never followed or removed. On a protected branch you must be ' +
180
- 'able to write everything the call touches — for a folder, every file under it, at its old and its new path. ' +
181
- 'file_stat reports `managed`, `movable` and `deletable` so you can tell before the call.',
173
+ `A platform file (${platformFileList(layout)}) is never moved: "<name> is a platform file and stays in its folder." ` +
174
+ 'A folder moved or deleted takes its own along, in one change. delete_file deletes a nested `access.md` for ' +
175
+ "whoever may write it (its folder then follows its parent's rules) and refuses `.bevelignore`; the repository " +
176
+ "root's `access.md` and `roles.yaml` are deleted by nobody: \"<name> is the repository's own file and cannot be " +
177
+ 'deleted." A platform folder (the root, or a reserved one like ' +
178
+ `\`${layout.knowledgeBaseDir}/\`), git metadata, and making a platform file or folder at a destination are ` +
179
+ 'refused; so is a path that is or crosses a symlink: links are never followed or removed. On a protected ' +
180
+ 'branch you must be able to write all a call touches — for a folder, every file under it, at its old and new ' +
181
+ 'path. file_stat reports `managed`, `movable` and `deletable` up front.',
182
182
  },
183
183
  {
184
184
  id: 'refused-for-permissions',
@@ -14,13 +14,17 @@ import type { IExternalApiKeyService } from '../../tool-auth/external-api-key.in
14
14
  * update / delete` pops the next queued result. `transaction` runs its
15
15
  * callback against the same fake, so queued results are consumed in order.
16
16
  * Every `where` predicate and `update` target is captured, so a test can pin
17
- * WHICH rows a statement addressed (see {@link render}), not just that it ran.
17
+ * WHICH rows a statement addressed (see {@link render}), not just that it ran;
18
+ * `orderBy` and `limit` too, since the fake applies neither and a test can
19
+ * only pin WHICH row of several a read keeps by the clauses it sent.
18
20
  */
19
21
  function makeFakeDb(queue: unknown[]) {
20
- const captured: { values: any[]; set: any[]; where: SQL[]; updateTargets: unknown[] } = {
22
+ const captured: { values: any[]; set: any[]; where: SQL[]; orderBy: SQL[][]; limit: number[]; updateTargets: unknown[] } = {
21
23
  values: [],
22
24
  set: [],
23
25
  where: [],
26
+ orderBy: [],
27
+ limit: [],
24
28
  updateTargets: [],
25
29
  };
26
30
  const counts = { insert: 0, select: 0, update: 0, delete: 0 };
@@ -36,10 +40,10 @@ function makeFakeDb(queue: unknown[]) {
36
40
  chain.values = passthrough((a) => captured.values.push(a[0]));
37
41
  chain.set = passthrough((a) => captured.set.push(a[0]));
38
42
  chain.where = passthrough((a) => captured.where.push(a[0]));
39
- chain.limit = passthrough();
43
+ chain.limit = passthrough((a) => captured.limit.push(a[0]));
40
44
  chain.innerJoin = passthrough();
41
45
  chain.from = passthrough();
42
- chain.orderBy = passthrough();
46
+ chain.orderBy = passthrough((a) => captured.orderBy.push(a));
43
47
  chain.groupBy = passthrough();
44
48
  chain.returning = passthrough();
45
49
  chain.then = (onF: any, onR: any) => Promise.resolve(result).then(onF, onR);
@@ -355,6 +359,62 @@ describe('AgentAuditService.revokeConnection', () => {
355
359
  });
356
360
  });
357
361
 
362
+ /**
363
+ * The onboarding's "is your agent connected yet?". The page polls it, so the
364
+ * contract worth pinning is what it reads (one person's LIVE, USED rows of
365
+ * each kind — never the event log) and how the two kinds are combined.
366
+ */
367
+ describe('AgentAuditService.lastAgentUse', () => {
368
+ it('answers null when neither an agent connection nor a key has been used', async () => {
369
+ const { service, counts } = makeService([[] /* connections */, [] /* keys */]);
370
+ await expect(service.lastAgentUse(ALICE.id)).resolves.toBeNull();
371
+ expect(counts.select).toBe(2);
372
+ });
373
+
374
+ it("reads only the caller's live, used rows of each kind", async () => {
375
+ const { service, captured } = makeService([[], []]);
376
+ await service.lastAgentUse(ALICE.id);
377
+ const [connections, keys] = captured.where.map(render);
378
+ expect(connections!.sql).toContain('"agent_connections"."user_id" = $1');
379
+ expect(connections!.sql).toContain('"agent_connections"."revoked_at" is null');
380
+ expect(connections!.sql).toContain('"agent_connections"."last_used_at" is not null');
381
+ expect(connections!.params).toEqual([ALICE.id]);
382
+ expect(keys!.sql).toContain('"api_tokens"."user_id" = $1');
383
+ expect(keys!.sql).toContain('"api_tokens"."revoked_at" is null');
384
+ expect(keys!.sql).toContain('"api_tokens"."last_used_at" is not null');
385
+ expect(keys!.params).toEqual([ALICE.id]);
386
+ });
387
+
388
+ it('keeps the most recently used row of each kind, of however many a person has', async () => {
389
+ // The fake neither sorts nor limits, so what is pinned is what each read
390
+ // asks the database for: newest use first, one row.
391
+ const { service, captured } = makeService([[], []]);
392
+ await service.lastAgentUse(ALICE.id);
393
+ expect(captured.orderBy.map((clauses) => clauses.map((c) => render(c).sql))).toEqual([
394
+ ['"agent_connections"."last_used_at" desc'],
395
+ ['"api_tokens"."last_used_at" desc'],
396
+ ]);
397
+ expect(captured.limit).toEqual([1, 1]);
398
+ });
399
+
400
+ it("names an agent connection by its client, and an unnamed one as such", async () => {
401
+ const at = new Date(NOW - 60_000);
402
+ const named = makeService([[{ client: 'Claude', at }], []]);
403
+ await expect(named.service.lastAgentUse(ALICE.id)).resolves.toEqual({ at, client: 'Claude', kind: 'agent' });
404
+ const unnamed = makeService([[{ client: null, at }], []]);
405
+ await expect(unnamed.service.lastAgentUse(ALICE.id)).resolves.toEqual({ at, client: 'Unnamed agent', kind: 'agent' });
406
+ });
407
+
408
+ it('answers with the newer of the two when both kinds have been used', async () => {
409
+ const older = new Date(NOW - DAY);
410
+ const newer = new Date(NOW - 60_000);
411
+ const keyNewer = makeService([[{ client: 'Claude', at: older }], [{ client: 'Laptop', at: newer }]]);
412
+ await expect(keyNewer.service.lastAgentUse(ALICE.id)).resolves.toEqual({ at: newer, client: 'Laptop', kind: 'key' });
413
+ const agentNewer = makeService([[{ client: 'Claude', at: newer }], [{ client: 'Laptop', at: older }]]);
414
+ await expect(agentNewer.service.lastAgentUse(ALICE.id)).resolves.toEqual({ at: newer, client: 'Claude', kind: 'agent' });
415
+ });
416
+ });
417
+
358
418
  describe('retentionDaysFrom', () => {
359
419
  it('reads a positive whole number of days, and blank, zero or negative as forever', () => {
360
420
  expect(retentionDaysFrom('30')).toBe(30);
@@ -0,0 +1,102 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import express from 'express';
3
+ import type { Server } from 'node:http';
4
+ import { createAgentConnectionRoutes } from '../agent-connection.routes.js';
5
+ import type { AgentUse, IAgentConnectionStatus } from '../audit.contract.js';
6
+
7
+ /**
8
+ * GET /onboarding/agent-connection — what the connect-your-agent page polls.
9
+ * The contract worth pinning:
10
+ *
11
+ * - it answers for the AUTHENTICATED user (req.userId) and nobody else;
12
+ * nothing in the request — query or otherwise — can name another account;
13
+ * - "not yet" and "connected" are the two shapes, the second with when, as
14
+ * what, and whether that name is an agent's or a connection key's label;
15
+ * - the answer is never cached, since the next poll expects it to change;
16
+ * - a failure is a generic 500, never the driver's message.
17
+ */
18
+
19
+ const ALICE = 'u-alice';
20
+ const BOB = 'u-bob';
21
+ const CARA = 'u-cara';
22
+
23
+ const USES: Record<string, AgentUse> = {
24
+ [BOB]: { at: new Date(Date.UTC(2026, 9, 7, 12, 0, 0)), client: 'Claude', kind: 'agent' },
25
+ [CARA]: { at: new Date(Date.UTC(2026, 9, 7, 13, 0, 0)), client: 'Laptop', kind: 'key' },
26
+ };
27
+
28
+ const status = {
29
+ lastAgentUse: vi.fn(async (userId: string) => USES[userId] ?? null),
30
+ } satisfies IAgentConnectionStatus;
31
+
32
+ let server: Server | undefined;
33
+ afterEach(() => {
34
+ server?.close();
35
+ server = undefined;
36
+ vi.clearAllMocks();
37
+ vi.restoreAllMocks();
38
+ });
39
+
40
+ /** Mount the route with a stand-in auth middleware stamping `as` as the caller. */
41
+ async function listen(as: string): Promise<string> {
42
+ const app = express();
43
+ app.use((req, _res, next) => {
44
+ req.userId = as;
45
+ next();
46
+ });
47
+ app.use('/api', createAgentConnectionRoutes(status));
48
+ await new Promise<void>((resolve) => {
49
+ server = app.listen(0, resolve);
50
+ });
51
+ const address = server!.address();
52
+ if (typeof address === 'string' || !address) throw new Error('no port');
53
+ return `http://127.0.0.1:${address.port}`;
54
+ }
55
+
56
+ describe('GET /onboarding/agent-connection', () => {
57
+ it('says "not yet" for a caller whose agents have not been used', async () => {
58
+ const base = await listen(ALICE);
59
+ const res = await fetch(`${base}/api/onboarding/agent-connection`);
60
+ expect(res.status).toBe(200);
61
+ expect(await res.json()).toEqual({ connected: false });
62
+ expect(status.lastAgentUse).toHaveBeenCalledWith(ALICE);
63
+ });
64
+
65
+ it('says connected, with when and as what, once one has', async () => {
66
+ const base = await listen(BOB);
67
+ const res = await fetch(`${base}/api/onboarding/agent-connection`);
68
+ expect(await res.json()).toEqual({ connected: true, at: '2026-10-07T12:00:00.000Z', client: 'Claude', kind: 'agent' });
69
+ });
70
+
71
+ it("keeps a connection key's kind: its label says nothing about which app holds it", async () => {
72
+ const base = await listen(CARA);
73
+ const res = await fetch(`${base}/api/onboarding/agent-connection`);
74
+ expect(await res.json()).toEqual({ connected: true, at: '2026-10-07T13:00:00.000Z', client: 'Laptop', kind: 'key' });
75
+ });
76
+
77
+ it("answers for the caller alone — a named account in the query is not asked about", async () => {
78
+ const base = await listen(ALICE);
79
+ const res = await fetch(`${base}/api/onboarding/agent-connection?userId=${BOB}&user=${BOB}`);
80
+ expect(await res.json()).toEqual({ connected: false });
81
+ expect(status.lastAgentUse).toHaveBeenCalledTimes(1);
82
+ expect(status.lastAgentUse).toHaveBeenCalledWith(ALICE);
83
+ });
84
+
85
+ it('is never cached: the next poll expects a different answer', async () => {
86
+ const base = await listen(ALICE);
87
+ const res = await fetch(`${base}/api/onboarding/agent-connection`);
88
+ expect(res.headers.get('cache-control')).toBe('no-store');
89
+ });
90
+
91
+ it('reports a failed read as a generic 500, keeping the detail server-side', async () => {
92
+ const detail = 'connection to 10.0.0.4:5432 refused';
93
+ status.lastAgentUse.mockRejectedValueOnce(new Error(detail));
94
+ vi.spyOn(console, 'error').mockImplementation(() => {});
95
+ const base = await listen(ALICE);
96
+ const res = await fetch(`${base}/api/onboarding/agent-connection`);
97
+ expect(res.status).toBe(500);
98
+ const body = (await res.json()) as { error: string };
99
+ expect(body.error).toBe('Could not check your agent');
100
+ expect(body.error).not.toContain(detail);
101
+ });
102
+ });
@@ -1,7 +1,7 @@
1
- import { and, count, desc, eq, isNull, lt, or, type SQL } from 'drizzle-orm';
1
+ import { and, count, desc, eq, isNotNull, isNull, lt, or, type SQL } from 'drizzle-orm';
2
2
  import { logger } from '../../shared/logging.js';
3
3
  import type { Database } from '../database/connection.js';
4
- import { agentConnections, agentEvents, oauthTokens, users } from '../database/schema.js';
4
+ import { agentConnections, agentEvents, externalApiKeys, oauthTokens, users } from '../database/schema.js';
5
5
  import type { IExternalApiKeyService } from '../tool-auth/external-api-key.interface.js';
6
6
  import {
7
7
  AuditPrincipalNotFoundError,
@@ -11,10 +11,12 @@ import {
11
11
  type AgentEventInput,
12
12
  type AgentEventPage,
13
13
  type AgentEventView,
14
+ type AgentUse,
14
15
  type AuditPrincipal,
15
16
  type AuditPrincipalRef,
16
17
  type AuditScope,
17
18
  type IAgentAuditService,
19
+ type IAgentConnectionStatus,
18
20
  type IAgentEventRecorder,
19
21
  } from './audit.contract.js';
20
22
 
@@ -39,7 +41,7 @@ const PRUNE_INTERVAL_MS = 60 * 60_000;
39
41
  * Connection keys are read through the key service rather than joined here,
40
42
  * so their summary shape (kind, revoked-by, …) has exactly one definition.
41
43
  */
42
- export class AgentAuditService implements IAgentAuditService, IAgentEventRecorder {
44
+ export class AgentAuditService implements IAgentAuditService, IAgentEventRecorder, IAgentConnectionStatus {
43
45
  private lastPruneAt = 0;
44
46
 
45
47
  constructor(
@@ -242,6 +244,59 @@ export class AgentAuditService implements IAgentAuditService, IAgentEventRecorde
242
244
  };
243
245
  }
244
246
 
247
+ /**
248
+ * Whether one person's agent has reached the platform yet — what the
249
+ * onboarding asks once on arrival (the `agent-connected` event the stamp
250
+ * paths emit tells it the moment it changes). Two single-row reads on the
251
+ * per-user indexes, never a scan of the event log.
252
+ *
253
+ * Read from the `last_used_at` stamps, not from `agent_events`: an agent
254
+ * stamps its connection (or key) on its FIRST authenticated request — the
255
+ * `initialize` / `tools/list` every client sends straight after connecting
256
+ * — while an event is written only once it calls a tool, which may be
257
+ * never. The stamps are also outside the retention sweep, so "connected"
258
+ * cannot quietly turn back into "waiting" when old events are pruned.
259
+ *
260
+ * Live rows only: an agent its owner disconnected, or an admin revoked, is
261
+ * not a connected agent, whatever it did before. The key's columns are read
262
+ * here rather than through the key service because the service has no
263
+ * single-row read and this question needs only two columns of one row.
264
+ */
265
+ async lastAgentUse(userId: string): Promise<AgentUse | null> {
266
+ const [[agent], [key]] = await Promise.all([
267
+ this.db
268
+ .select({ client: agentConnections.clientName, at: agentConnections.lastUsedAt })
269
+ .from(agentConnections)
270
+ .where(
271
+ and(
272
+ eq(agentConnections.userId, userId),
273
+ isNull(agentConnections.revokedAt),
274
+ isNotNull(agentConnections.lastUsedAt),
275
+ ),
276
+ )
277
+ .orderBy(desc(agentConnections.lastUsedAt))
278
+ .limit(1),
279
+ this.db
280
+ .select({ client: externalApiKeys.label, at: externalApiKeys.lastUsedAt })
281
+ .from(externalApiKeys)
282
+ .where(
283
+ and(
284
+ eq(externalApiKeys.userId, userId),
285
+ isNull(externalApiKeys.revokedAt),
286
+ isNotNull(externalApiKeys.lastUsedAt),
287
+ ),
288
+ )
289
+ .orderBy(desc(externalApiKeys.lastUsedAt))
290
+ .limit(1),
291
+ ]);
292
+ const uses = [
293
+ agent?.at ? { at: agent.at, client: agent.client ?? 'Unnamed agent', kind: 'agent' as const } : null,
294
+ key?.at ? { at: key.at, client: key.client, kind: 'key' as const } : null,
295
+ ].filter((u): u is AgentUse => u !== null);
296
+ if (uses.length === 0) return null;
297
+ return uses.reduce((newest, u) => (u.at.getTime() > newest.at.getTime() ? u : newest));
298
+ }
299
+
245
300
  async revokeConnection(id: string, by: 'owner' | 'admin', ownerUserId?: string): Promise<void> {
246
301
  const scope = ownerUserId
247
302
  ? and(eq(agentConnections.id, id), eq(agentConnections.userId, ownerUserId))!
@@ -0,0 +1,56 @@
1
+ import express from 'express';
2
+ import { logger } from '../../shared/logging.js';
3
+ import type { IAgentConnectionStatus } from './audit.contract.js';
4
+ import '../auth/auth.middleware.js'; // Express Request augmentation (req.userId)
5
+
6
+ const log = logger('audit');
7
+
8
+ /** What the onboarding reads: connected or not, and — once connected — when, as what, and through which kind of credential. */
9
+ export interface AgentConnectionResponse {
10
+ connected: boolean;
11
+ /** The agent's most recent call, ISO 8601. */
12
+ at?: string;
13
+ /** The agent's registered client name, or the connection key's label. */
14
+ client?: string;
15
+ /**
16
+ * Which of the two `client` is: `agent` for an OAuth connection's
17
+ * registered name, `key` for a connection key's free-text label. Only an
18
+ * `agent` name says which app connected.
19
+ */
20
+ kind?: 'agent' | 'key';
21
+ }
22
+
23
+ /**
24
+ * GET /api/onboarding/agent-connection — "has an agent of mine reached the
25
+ * platform yet?", JWT-only (mounted behind the session middleware).
26
+ *
27
+ * The connect-your-agent page and the Get set up list ask it once on
28
+ * arrival, and again when the person comes back to the tab; the moment it
29
+ * changes is told by the `agent-connected` event the use-stamp paths emit
30
+ * over the event stream, so nothing asks on a timer. It answers for the
31
+ * CALLER alone: the user is the token's, and nothing in the request can name
32
+ * anyone else — there is no parameter to try.
33
+ *
34
+ * `no-store` because the answer is expected to change between two asks; a
35
+ * cached "not yet" would keep a page waiting on an agent that has already
36
+ * arrived.
37
+ */
38
+ export function createAgentConnectionRoutes(status: IAgentConnectionStatus): express.Router {
39
+ const router = express.Router();
40
+
41
+ router.get('/onboarding/agent-connection', async (req, res) => {
42
+ res.set('Cache-Control', 'no-store');
43
+ try {
44
+ const use = await status.lastAgentUse(req.userId!);
45
+ const body: AgentConnectionResponse = use
46
+ ? { connected: true, at: use.at.toISOString(), client: use.client, kind: use.kind }
47
+ : { connected: false };
48
+ res.json(body);
49
+ } catch (err) {
50
+ log.error('agent connection status failed:', { err });
51
+ res.status(500).json({ error: 'Could not check your agent' });
52
+ }
53
+ });
54
+
55
+ return router;
56
+ }
@@ -121,6 +121,32 @@ export interface IAgentAuditService {
121
121
  revokeConnection(id: string, by: 'owner' | 'admin', ownerUserId?: string): Promise<void>;
122
122
  }
123
123
 
124
+ /**
125
+ * The most recent use of any of one person's live agents: when, what it
126
+ * calls itself (the agent's registered client name, or the key's label), and
127
+ * which of the two that name is.
128
+ */
129
+ export interface AgentUse {
130
+ at: Date;
131
+ client: string;
132
+ /**
133
+ * `agent`: an OAuth connection, and `client` is the name the agent
134
+ * registered. `key`: a connection key, and `client` is whatever label its
135
+ * owner typed — it says nothing about which app holds the key.
136
+ */
137
+ kind: 'agent' | 'key';
138
+ }
139
+
140
+ /**
141
+ * The onboarding's "is your agent connected yet?" — answered for ONE person,
142
+ * from the use stamps the auth paths already write, so it costs two indexed
143
+ * single-row reads and records nothing new.
144
+ */
145
+ export interface IAgentConnectionStatus {
146
+ /** The newest use among the person's live agent connections and connection keys; null when none has been used. */
147
+ lastAgentUse(userId: string): Promise<AgentUse | null>;
148
+ }
149
+
124
150
  export class AuditPrincipalNotFoundError extends Error {
125
151
  constructor() {
126
152
  super('No such agent or key');
@@ -2,6 +2,7 @@ export * from './audit.contract.js';
2
2
  export { AgentAuditService, MAX_EVENT_PAGE, retentionDaysFrom } from './agent-audit.service.js';
3
3
  export { createAgentRestAuditMiddleware } from './agent-rest-audit.middleware.js';
4
4
  export { createAuditRoutes } from './audit.routes.js';
5
+ export { createAgentConnectionRoutes, type AgentConnectionResponse } from './agent-connection.routes.js';
5
6
  export { RequestAudit } from './request-audit.js';
6
7
  export {
7
8
  classifyToolCall,
@@ -67,6 +67,8 @@ describe('account routes — admin gate', () => {
67
67
  body: JSON.stringify({ email: 'x@example.com', password: 'long-enough-pw' }),
68
68
  });
69
69
  expect(create.status).toBe(403);
70
+ // Not an admission refusal: nothing here may read as "no seat left".
71
+ expect(await create.json()).toEqual({ error: 'Admins only' });
70
72
  expect(authService.createAccount).not.toHaveBeenCalled();
71
73
  });
72
74
 
@@ -151,7 +153,7 @@ describe('account routes — admin gate', () => {
151
153
  expect(accountErasure.eraseUser).toHaveBeenLastCalledWith('u2');
152
154
  });
153
155
 
154
- it('an account the admission port refuses → 403 carrying the port\'s own words', async () => {
156
+ it('an account the admission port refuses → 403 carrying the port\'s own words, marked as an admission refusal', async () => {
155
157
  const base = await listen(makeApp({ admin: true }));
156
158
  vi.mocked(authService.createAccount).mockRejectedValueOnce(
157
159
  new AccountAdmissionRefusedError('No seat left on this plan'),
@@ -162,7 +164,7 @@ describe('account routes — admin gate', () => {
162
164
  body: JSON.stringify({ email: 'b@example.com', password: 'long-enough-pw' }),
163
165
  });
164
166
  expect(refused.status).toBe(403);
165
- expect(((await refused.json()) as { error: string }).error).toBe('No seat left on this plan');
167
+ expect(await refused.json()).toEqual({ error: 'No seat left on this plan', kind: 'admission' });
166
168
  });
167
169
 
168
170
  it('erasure failure → 500 with a generic body (no internal error text)', async () => {
@@ -370,7 +372,7 @@ describe('account routes — switching an account off and on', () => {
370
372
  const base = await listen(makeApp({ admin: true }));
371
373
  const res = await post(base, 'u2/reactivate');
372
374
  expect(res.status).toBe(403);
373
- expect(((await res.json()) as { error: string }).error).toBe('All 3 seats are taken');
375
+ expect(await res.json()).toEqual({ error: 'All 3 seats are taken', kind: 'admission' });
374
376
  });
375
377
 
376
378
  it('answers a refused deactivation (the deployment admin, say) with 400 and the reason', async () => {
@@ -73,7 +73,13 @@ export function createAccountRoutes(
73
73
  const msg = error instanceof Error ? error.message : 'Unknown error';
74
74
  // The deployment has no place for the account (a seat limit, say): the
75
75
  // port's own words, as a refusal rather than a malformed request.
76
- res.status(error instanceof AccountAdmissionRefusedError ? 403 : 400).json({ error: msg });
76
+ // `kind` tells it apart from `requireAdmin`'s 403, which a caller must
77
+ // not read as "no seat left".
78
+ if (error instanceof AccountAdmissionRefusedError) {
79
+ res.status(403).json({ error: msg, kind: 'admission' });
80
+ return;
81
+ }
82
+ res.status(400).json({ error: msg });
77
83
  }
78
84
  });
79
85
 
@@ -107,7 +113,8 @@ export function createAccountRoutes(
107
113
 
108
114
  // POST /api/admin/accounts/:userId/reactivate — switch it back on, if the
109
115
  // deployment has room: the admission port is asked, as for a new account
110
- // (a host that sells seats answers from its plan; 403 with its words).
116
+ // (a host that sells seats answers from its plan; 403 with its words and
117
+ // `kind: 'admission'`).
111
118
  router.post('/admin/accounts/:userId/reactivate', requireAdmin, async (req, res) => {
112
119
  const userId = String(req.params.userId);
113
120
  try {
@@ -119,7 +126,7 @@ export function createAccountRoutes(
119
126
  res.status(204).end();
120
127
  } catch (error) {
121
128
  if (error instanceof AccountAdmissionRefusedError) {
122
- res.status(403).json({ error: error.message });
129
+ res.status(403).json({ error: error.message, kind: 'admission' });
123
130
  return;
124
131
  }
125
132
  log.error('switching an account on failed:', { err: error });
@@ -30,7 +30,7 @@ export const AUTH_COOKIE_NAME = 'bevel_token';
30
30
  * beats the dep surface of a middleware. Returns `null` if the cookie
31
31
  * is absent or empty.
32
32
  */
33
- function readAuthCookie(req: Request): string | null {
33
+ export function readAuthCookie(req: Request): string | null {
34
34
  const header = req.headers.cookie;
35
35
  if (!header) return null;
36
36
  for (const segment of header.split(';')) {
@@ -27,6 +27,16 @@ vi.mock('../code-mode-names.js', async (importOriginal) => {
27
27
  }
28
28
  if (name === 'down.run') throw new Error('repository unavailable: ECONNREFUSED');
29
29
  if (name === 'solo.run') return { tool: { name: 'solo.run' }, utcpName: 'solo.run' };
30
+ if (name === 'kb.read_file') {
31
+ return {
32
+ tool: {
33
+ name: 'kb.read_file',
34
+ description: 'Read a file.',
35
+ inputs: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
36
+ },
37
+ utcpName: 'kb.read_file',
38
+ };
39
+ }
30
40
  return null;
31
41
  }),
32
42
  };
@@ -35,7 +45,8 @@ vi.mock('../code-mode-names.js', async (importOriginal) => {
35
45
  const { createToolsInfoTool } = await import('../code-mode.tool.js');
36
46
 
37
47
  const client = {
38
- toolToTypeScriptInterface: (tool: { name: string }) => `interface ${tool.name}`,
48
+ toolToTypeScriptInterface: (tool: { name: string; description?: string }) =>
49
+ tool.description ? `/** ${tool.description} */ interface ${tool.name}` : `interface ${tool.name}`,
39
50
  } as unknown as CodeModeUtcpClient;
40
51
 
41
52
  type ToolsInfoResult = { interfaces: string; not_found: string[]; errors?: string[] };
@@ -58,11 +69,16 @@ describe('tools_info', () => {
58
69
 
59
70
  it('omits the errors field entirely when every name resolves or misses cleanly', async () => {
60
71
  const result = await run(['solo.run', 'nope']);
61
- expect(result.interfaces).toBe('interface solo.run');
72
+ expect(result.interfaces).toBe('/** Call: solo.run({}) */ interface solo.run');
62
73
  expect(result.not_found).toEqual(['nope']);
63
74
  expect(result.errors).toBeUndefined();
64
75
  });
65
76
 
77
+ it('shows each tool with its Call: line, as the chain description promises', async () => {
78
+ const result = await run(['kb.read_file']);
79
+ expect(result.interfaces).toBe('/** Call: kb.read_file({ query: "..." })\n\nRead a file. */ interface kb.read_file');
80
+ });
81
+
66
82
  it('contains ONLY ambiguity — an outage rethrows instead of posing as partial success', async () => {
67
83
  await expect(run(['solo.run', 'down.run'])).rejects.toThrow(/repository unavailable/);
68
84
  });