@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
@@ -0,0 +1,206 @@
1
+ import type { Router, RequestHandler } from 'express';
2
+ import type { IToolRegistry } from '../tool-registry/tool.contract.js';
3
+ import { ToolError } from '../tool-helpers/tool.contract.js';
4
+ import { toolDef } from '../tool-helpers/tool-def.js';
5
+ import type { ToolHandlerFactory } from '../tool-helpers/tool-handler.js';
6
+ import type { ToolContext } from '../tool-helpers/tool.contract.js';
7
+ import type { KbContext } from '../../shared/kb-context.js';
8
+ import type { ReadForTool } from '../workspace/workspace.tools.js';
9
+ import type { IEmbedService } from './embed.interface.js';
10
+ import { requireExternalSource } from '../tool-auth/tool-auth.middleware.js';
11
+ import { SESSION_ID_INPUT, type ToolDescriptionNotes } from '../workspace/agent-access.gate.js';
12
+ import { sessionIdInputOf } from '../workspace/workspace.tools.js';
13
+
14
+ /** The tool name, in one place: the app manifest keys its view by it. */
15
+ export const OPEN_PAGE_TOOL = 'open_page';
16
+
17
+ /** What `open_page` answers when the deployment cannot be framed. */
18
+ export const HTTP_DEPLOYMENT_NOTE =
19
+ 'This deployment is reached over plain http, so no chat host can show the page inline — ' +
20
+ 'an embedded view needs an https deployment. The page is above, and its address in the app ' +
21
+ 'opens the whole thing.';
22
+
23
+ /** What a path outside the knowledge base is told. */
24
+ export const OUTSIDE_REPO = (kbDirName: string): string =>
25
+ `open_page shows a knowledge-base page, so \`path\` has to name a file under \`${kbDirName}/\`.`;
26
+
27
+ export interface EmbedToolsDeps {
28
+ embedService: IEmbedService;
29
+ kb: KbContext;
30
+ /** `read_file`'s own read — see {@link ReadForTool}. */
31
+ readForTool: ReadForTool;
32
+ /** Whether a chat host's sandbox can load from this deployment's public address at all (https). */
33
+ canBeReached: () => boolean;
34
+ /** The file's address in the app, for the answer and for the view's fallback link. */
35
+ appUrlFor: (repoRelative: string, slug?: string) => string;
36
+ /** The notes a deployment registers for the gated tools — `read_file`'s, and so this tool's. */
37
+ notes: ToolDescriptionNotes;
38
+ }
39
+
40
+ /**
41
+ * `open_page`: the knowledge-base page, in the chat.
42
+ *
43
+ * The tool answers the file's TEXT exactly as `read_file` does — the same
44
+ * read hook, the same access gate, the same refusals — and adds the address
45
+ * of an embedded view that renders the page with the app's own renderer, so a
46
+ * host that supports MCP Apps shows it inline and lets the reader edit it or
47
+ * propose a change. A host that does not support them shows the text and the
48
+ * app address, and nothing errors: the answer stands on its own.
49
+ *
50
+ * `read_file` is deliberately untouched (Decision 2). Carrying a view on it
51
+ * would mint a token and render an iframe on every read an agent makes, for
52
+ * every file it reads while thinking.
53
+ *
54
+ * No `branch` argument, by decision: the embedded view is editable, and an
55
+ * editable embed targets the default branch only. The branch it rendered
56
+ * comes back in the answer so nothing is implied.
57
+ */
58
+ export function registerEmbedTools(
59
+ registry: IToolRegistry,
60
+ router: Router,
61
+ toolAuth: RequestHandler,
62
+ toolHandler: ToolHandlerFactory,
63
+ deps: EmbedToolsDeps,
64
+ ): void {
65
+ const { kbDirName } = deps.kb;
66
+ const DESCRIPTION =
67
+ 'Show a knowledge-base page to the person you are talking to, rendered inside this chat. ' +
68
+ 'Returns the same `{ path, content }` `read_file` returns — the page as text, for you to read — ' +
69
+ 'plus `embedUrl` (the view the chat renders), `appUrl` (the page in the app), and `branch` ' +
70
+ "(always the default branch). The rendered view uses the app's own renderer for the file's type, " +
71
+ 'and offers the reader Edit when they may write the page and Propose changes when they may not, ' +
72
+ 'as that same person — not as you. Use it when somebody wants to SEE or FIX a page; use `read_file` ' +
73
+ 'when you only need to read one yourself. A chat host that cannot render a view shows the text, ' +
74
+ 'so calling this is never worse than reading.';
75
+ const def = toolDef({
76
+ name: OPEN_PAGE_TOOL,
77
+ description: DESCRIPTION + deps.notes.gatedToolNote(),
78
+ path: `/api/agent/tools/${OPEN_PAGE_TOOL}`,
79
+ inputs: {
80
+ type: 'object',
81
+ properties: {
82
+ path: {
83
+ type: 'string',
84
+ minLength: 1,
85
+ description:
86
+ `Path of the page to show, under \`${kbDirName}/\` (e.g. \`${kbDirName}/KnowledgeBase/Foo.md\`), ` +
87
+ 'with or without a leading slash — a path without that prefix is placed under ' +
88
+ `\`${kbDirName}/\`. The page is always shown from the default branch.`,
89
+ },
90
+ heading: {
91
+ type: 'string',
92
+ description:
93
+ 'Optional: the anchor slug of one heading (e.g. `problem-statement`) the view should open at. ' +
94
+ 'The whole page is shown either way — this only says where to start reading.',
95
+ },
96
+ // As `read_file` takes it: the read is `read_file`'s own, and a
97
+ // deployment whose read hook wants the session must be able to get it.
98
+ sessionId: SESSION_ID_INPUT,
99
+ },
100
+ required: ['path'],
101
+ additionalProperties: false,
102
+ },
103
+ outputs: {
104
+ type: 'object',
105
+ properties: {
106
+ path: { type: 'string', description: 'The path that was shown (echoes the input).' },
107
+ content: { type: 'string', description: "The page's text, exactly as `read_file` answers it." },
108
+ embedUrl: {
109
+ type: 'string',
110
+ description:
111
+ 'The address of the view the chat renders. Absent when this deployment cannot be framed — ' +
112
+ 'see `note`.',
113
+ },
114
+ appUrl: { type: 'string', description: "The page's address in the app." },
115
+ branch: { type: 'string', description: 'The branch the page was rendered from — the default branch.' },
116
+ note: {
117
+ type: 'string',
118
+ description: 'Present only when there is no `embedUrl`: why there is no embedded view.',
119
+ },
120
+ },
121
+ required: ['path', 'content', 'appUrl', 'branch'],
122
+ },
123
+ tags: ['workspace'],
124
+ });
125
+ // EXTERNAL only. The view exists for a chat somewhere else; the in-app
126
+ // agent's reader is already looking at the app, where every page is a
127
+ // click away — handing it an iframe of the page it is standing on would be
128
+ // a tool that cannot help.
129
+ registry.registerExternalTool(def);
130
+ // The read is `read_file`'s own, so what the agent reads about it is too:
131
+ // the note a deployment registers for the gated tools, and the `sessionId`
132
+ // description with its note — applied now and on every later registration,
133
+ // exactly as the file tools apply them (`sessionIdInputOf` is theirs).
134
+ const redescribe = (): void => {
135
+ def.description = DESCRIPTION + deps.notes.gatedToolNote();
136
+ const sessionId = sessionIdInputOf(def);
137
+ if (sessionId) sessionId.description = deps.notes.sessionIdDescription();
138
+ };
139
+ redescribe();
140
+ deps.notes.onChange(redescribe);
141
+
142
+ router.post(
143
+ `/agent/tools/${OPEN_PAGE_TOOL}`,
144
+ toolAuth,
145
+ // Registered external-only above; the route holds the same line, so an
146
+ // in-process agent that guessed the endpoint is refused here too.
147
+ requireExternalSource,
148
+ toolHandler(async (args, ctx: ToolContext) => {
149
+ const raw = typeof args.path === 'string' ? args.path.trim() : '';
150
+ if (!raw) throw new ToolError('`path` is required.', 400);
151
+ const heading = typeof args.heading === 'string' && args.heading.trim() ? args.heading.trim() : undefined;
152
+ const repoRelative = toRepoRelative(raw, kbDirName);
153
+ if (repoRelative === null) throw new ToolError(OUTSIDE_REPO(kbDirName), 400);
154
+ const branch = deps.kb.defaultBranch;
155
+ const wsPath = `${kbDirName}/${repoRelative}`;
156
+
157
+ // The read comes FIRST, and it is `read_file`'s own: a path the caller
158
+ // may not read, or one that is not there, is refused here in the words
159
+ // `read_file` refuses it — and no token is minted, because the mint is
160
+ // below this line.
161
+ const result = await deps.readForTool(branch, wsPath, ctx);
162
+ const content =
163
+ result.kind === 'text' ? result.text : result.kind === 'image' ? result.note : result.message;
164
+
165
+ const appUrl = deps.appUrlFor(repoRelative, heading);
166
+ // No view on a plain-http deployment (Decision 8): a host's https
167
+ // sandbox cannot load from it. The text and the app address are still
168
+ // the answer, and the tool says why there is no view rather than
169
+ // handing back an address no host can open.
170
+ if (!deps.canBeReached()) {
171
+ return { path: raw, content, appUrl, branch, note: HTTP_DEPLOYMENT_NOTE };
172
+ }
173
+ // Minted for the identity THIS MCP session authenticated — the
174
+ // signed-in user, or the owner of the connection key the agent carries.
175
+ // Whoever is in the chat therefore sees and may change exactly what
176
+ // that person may, and the audit log attributes it to them.
177
+ const { embedUrl } = await deps.embedService.mintForUser({
178
+ userId: ctx.user.id,
179
+ reference: heading ? `${repoRelative}#${heading}` : repoRelative,
180
+ });
181
+ return { path: raw, content, embedUrl, appUrl, branch };
182
+ }),
183
+ );
184
+ }
185
+
186
+ /**
187
+ * `path` as a repo-relative path, or null when it names nothing inside the
188
+ * knowledge base. Accepts it with or without the `<kbDir>/` prefix and with
189
+ * or without a leading slash, which is what `read_file` documents.
190
+ */
191
+ export function toRepoRelative(path: string, kbDirName: string): string | null {
192
+ // A backslash is refused, not read as a separator — `read_file`'s contract,
193
+ // and a token must never name a file other than the one the caller wrote.
194
+ if (path.includes('\\')) return null;
195
+ const norm = path.replace(/^\.?\/+/, '').replace(/\/+$/, '');
196
+ if (!norm) return null;
197
+ const rel = norm === kbDirName ? '' : norm.startsWith(`${kbDirName}/`) ? norm.slice(kbDirName.length + 1) : norm;
198
+ if (!rel) return null;
199
+ // Traversal and the odd segment are refused HERE rather than deeper: the
200
+ // path is about to become part of a signed token, and a token is the one
201
+ // place a bad path would outlive the request that sent it.
202
+ // eslint-disable-next-line no-control-regex
203
+ if (/[\x00-\x1f\x7f]/.test(rel)) return null;
204
+ if (!rel.split('/').every((seg) => seg !== '' && seg !== '.' && seg !== '..')) return null;
205
+ return rel;
206
+ }
@@ -0,0 +1,41 @@
1
+ import express, { type RequestHandler } from 'express';
2
+ import type { McpAppManifest } from '@bevel-software/platform-mcp-core';
3
+
4
+ /**
5
+ * The deployment's MCP Apps over REST, for the LOCAL MCP server.
6
+ *
7
+ * `hexis-mcp` bridges a host to this deployment over HTTP: it discovers the
8
+ * tools as a UTCP manual, which has nowhere to carry a view, and it serves
9
+ * `resources/read` itself. So it needs the manifest — which tools carry a
10
+ * view, and the view's HTML and sandbox metadata — as data. One route, one
11
+ * answer, so the local server advertises the same apps the hosted endpoint
12
+ * does without knowing what any of them render.
13
+ *
14
+ * `manualAuth` for the same reason `all-tools` and `catalog-revision` use it:
15
+ * this is the agent surface, reached with a connection key or an exchanged
16
+ * grant. Nothing here is per-caller — every connection gets the same view —
17
+ * so the credential is a door, not a filter.
18
+ */
19
+ export function createMcpAppRoutes(
20
+ apps: { manifest(): Promise<McpAppManifest> },
21
+ manualAuth: RequestHandler,
22
+ ): express.Router {
23
+ const router = express.Router();
24
+
25
+ router.get('/agent/mcp-app', manualAuth, async (_req, res) => {
26
+ try {
27
+ const manifest = await apps.manifest();
28
+ // The view's HTML rides the manifest rather than sitting behind a
29
+ // second round trip per resource: it is one small static file, the
30
+ // local server needs all of it before it can declare the capability at
31
+ // all, and a second fetch is a second thing that can half-fail.
32
+ res.setHeader('Cache-Control', 'no-store');
33
+ res.json(manifest);
34
+ } catch (err) {
35
+ console.error('[embed] could not build the MCP App manifest:', err);
36
+ res.status(500).json({ error: 'Something went wrong.' });
37
+ }
38
+ });
39
+
40
+ return router;
41
+ }
@@ -0,0 +1,173 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {
5
+ MCP_APP_MIME_TYPE,
6
+ type McpAppManifest,
7
+ type McpAppResource,
8
+ } from '@bevel-software/platform-mcp-core';
9
+ import { mcpAppDir } from '../../assets.js';
10
+ import { logger } from '../../shared/logging.js';
11
+ import { OPEN_PAGE_TOOL } from './embed.tools.js';
12
+
13
+ const log = logger('embed');
14
+
15
+ /**
16
+ * The `ui://` URI of the one view this deployment serves, NAMED BY ITS
17
+ * CONTENT: `ui://hexis/page-<hash>.html`, the hash being the view's own
18
+ * bytes.
19
+ *
20
+ * A host caches a view by its URI, and a changed view under an unchanged
21
+ * URI is served stale until the host happens to refetch — which is what
22
+ * Claude did after the view was rewritten (#391): it kept rendering the
23
+ * view it had cached, the one that goes blank. With the content in the
24
+ * name, every change to the view is a new URI, so no host can keep serving
25
+ * the old one. The prefix is stable so a reader can still tell what the
26
+ * resource is.
27
+ */
28
+ export const OPEN_PAGE_VIEW_URI_PREFIX = 'ui://hexis/page-';
29
+
30
+ /** The URI the view with these bytes is served under. */
31
+ export function openPageViewUri(text: string): string {
32
+ return `${OPEN_PAGE_VIEW_URI_PREFIX}${createHash('sha256').update(text).digest('hex').slice(0, 12)}.html`;
33
+ }
34
+
35
+ /** The view's file inside the packaged `mcp-app/` folder. */
36
+ const OPEN_PAGE_VIEW_FILE = 'page.html';
37
+
38
+ /** The MCP App views a deployment serves — the port the MCP endpoint, the manifest route and the catalog revision consume. */
39
+ export interface IMcpAppService {
40
+ /** The manifest: which tools carry a view, and the views themselves. */
41
+ manifest(): Promise<McpAppManifest>;
42
+ /** One view by its `ui://` address, or null when this deployment serves none at it. */
43
+ resource(uri: string): Promise<McpAppResource | null>;
44
+ }
45
+
46
+ /**
47
+ * The deployment's MCP Apps, built once per process: which tools carry a
48
+ * view, and the views themselves with the sandbox metadata the host needs.
49
+ *
50
+ * Read once and cached — the file ships with the package and cannot change
51
+ * under a running process, and `resources/read` is on the handshake path of
52
+ * every connection that renders a page.
53
+ */
54
+ export class McpAppService implements IMcpAppService {
55
+ private cached: Promise<McpAppManifest> | null = null;
56
+
57
+ constructor(
58
+ private readonly config: {
59
+ /** The deployment's public frontend address — the ONE origin the view may reach. */
60
+ readonly publicFrontendUrl: string;
61
+ /** Where the view's file lives. The packaged `mcp-app/` folder unless a test says otherwise. */
62
+ readonly viewDir?: string;
63
+ },
64
+ ) {}
65
+
66
+ /** Every app this deployment serves. Empty only if the packaged view is unreadable. */
67
+ manifest(): Promise<McpAppManifest> {
68
+ return (this.cached ??= this.build());
69
+ }
70
+
71
+ /** One view by URI, or null when this deployment serves none under it. */
72
+ async resource(uri: string): Promise<McpAppResource | null> {
73
+ const { resources } = await this.manifest();
74
+ return resources.find((r) => r.uri === uri) ?? null;
75
+ }
76
+
77
+ private async build(): Promise<McpAppManifest> {
78
+ const file = path.join(this.config.viewDir ?? mcpAppDir(), OPEN_PAGE_VIEW_FILE);
79
+ let text: string;
80
+ try {
81
+ text = await readFile(file, 'utf8');
82
+ } catch (err) {
83
+ // No view, no app. Advertising a `resourceUri` whose resource cannot be
84
+ // read would have a host preload a failure and show an empty frame
85
+ // where the tool's text used to be; with no manifest `open_page` is an
86
+ // ordinary tool answering ordinary text, which is the graceful shape.
87
+ log.error(
88
+ `could not read the MCP App view at ${file}; ` +
89
+ `open_page will carry no view: ${err instanceof Error ? err.message : String(err)}`,
90
+ );
91
+ return { tools: {}, resources: [] };
92
+ }
93
+ const origin = originOf(this.config.publicFrontendUrl);
94
+ const uri = openPageViewUri(text);
95
+ return {
96
+ tools: { [OPEN_PAGE_TOOL]: { resourceUri: uri } },
97
+ resources: [
98
+ {
99
+ uri,
100
+ name: 'knowledge-base-page',
101
+ title: 'Knowledge base page',
102
+ description:
103
+ "The knowledge-base page an agent opened, rendered by the app's own renderer for its type, " +
104
+ 'with Edit for a writer and Propose changes for everybody else.',
105
+ mimeType: MCP_APP_MIME_TYPE,
106
+ text,
107
+ ui: {
108
+ // EXACTLY this deployment's own public origin, and nothing else,
109
+ // for the two things the view asks of the host's sandbox:
110
+ // FETCHING the build manifest and the token-only embed API
111
+ // (`connectDomains`), and LOADING the embed bundle's scripts and
112
+ // stylesheets and the bytes the renderers draw
113
+ // (`resourceDomains`). No `frameDomains`: the view frames
114
+ // nothing. Claude's host drops that field and pins the sandbox's
115
+ // `frame-src` to `'self'`, which turned a framed `/embed` into a
116
+ // blank box — so the view loads the deployment's embed bundle
117
+ // into its own document instead. A knowledge-base HTML page
118
+ // still renders through the renderer's `srcdoc` sandbox, which
119
+ // that policy allows.
120
+ csp: {
121
+ connectDomains: origin ? [origin] : [],
122
+ resourceDomains: origin ? [origin] : [],
123
+ },
124
+ // Deliberately NO `domain`. The field asks the host for a
125
+ // dedicated sandbox origin, and the specification leaves its
126
+ // format and validation to each host ("servers MUST consult
127
+ // host-specific documentation") — a value one host accepts,
128
+ // another may reject, and a rejected resource is a view that
129
+ // never renders. This view keeps no storage and has no OAuth
130
+ // callback, so it needs no stable origin of its own; the host's
131
+ // default sandbox is the right one.
132
+ // The framed page draws its own surface; a second border around
133
+ // it reads as a box inside a box.
134
+ prefersBorder: false,
135
+ },
136
+ },
137
+ ],
138
+ };
139
+ }
140
+ }
141
+
142
+ /**
143
+ * `url`'s origin, or null when it does not parse. Only the origin: a
144
+ * `connectDomains` / `resourceDomains` entry is an origin, and a path or a
145
+ * credential in one is either ignored or rejected by the host.
146
+ */
147
+ export function originOf(url: string): string | null {
148
+ try {
149
+ const origin = new URL(url).origin;
150
+ // An address that parses but has no web origin (`file:`, `data:`) is
151
+ // serialised as the literal "null" — not an origin a host's CSP can name.
152
+ return origin === 'null' ? null : origin;
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Whether a public address can be reached from inside a host's sandbox at all.
160
+ *
161
+ * Hosts run an MCP App's view in a sandboxed, https iframe, and no browser
162
+ * will let an https document load scripts from, or fetch from, a plain-http
163
+ * origin. So a deployment reached over http has no embedded view —
164
+ * `open_page` still answers the page's text and its address in the app, and
165
+ * says why there is nothing rendered (see Decision 8).
166
+ */
167
+ export function isSandboxReachableOrigin(url: string): boolean {
168
+ try {
169
+ return new URL(url).protocol === 'https:';
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
@@ -9,6 +9,8 @@ import { DeploymentSettingsService, SettingsValidationError } from '../../settin
9
9
  import { GitHubAppClient, GitHubAppError, appJwt, normalizePrivateKey } from '../github-app.client.js';
10
10
  import { GitHubAppConnection } from '../github-app.connection.js';
11
11
  import { createGitHubAppRoutes, githubAppManifest } from '../github-app.routes.js';
12
+ import { setLogger } from '../../../shared/logging.js';
13
+ import type { ILogger, LogFields } from '../../../shared/logger.contract.js';
12
14
 
13
15
  const ENC_KEY = 'kToAi8FXWDpDn3A6yQ/60O39bv05N7XzVOIu/0CJrFc=';
14
16
  const { privateKey: PEM, publicKey: PUBLIC } = generateKeyPairSync('rsa', {
@@ -393,6 +395,80 @@ describe('GitHubAppConnection: the token git presents', () => {
393
395
  await settled();
394
396
  expect(connection.token()).toBe('installation-token-1');
395
397
  });
398
+
399
+ /**
400
+ * The git runner reporting that the host threw the token out. The clock
401
+ * may still give it fifty minutes: the host's verdict wins, the token is
402
+ * dropped, and GitHub is asked for another at once — past the backoff a
403
+ * recent failure would otherwise impose. When GitHub will not replace it,
404
+ * NO token remains: that is what keeps the runner from offering the
405
+ * refused one again.
406
+ */
407
+ it('replaces a token the host refused, with time to spare or not, and drops it when GitHub will not', async () => {
408
+ const world: Parameters<typeof github>[0] = {};
409
+ const { connection, hub, clock } = connected(world);
410
+ await connection.prepare();
411
+ expect(connection.token()).toBe('installation-token-1');
412
+ clock.now += 5 * 60_000;
413
+ await connection.prepare({ refused: true });
414
+ expect(connection.token()).toBe('installation-token-2');
415
+ expect(hub.tokensIssued()).toBe(2);
416
+
417
+ // Inside a remembered failure's backoff, the refusal still asks.
418
+ world.down = true;
419
+ await expect(connection.prepare({ refused: true })).rejects.toThrow();
420
+ expect(connection.token()).toBeNull();
421
+ world.down = false;
422
+ await expect(connection.prepare({ refused: true })).resolves.toBeUndefined();
423
+ expect(connection.token()).toBe('installation-token-3');
424
+ });
425
+
426
+ /**
427
+ * A deployment whose app settings are incomplete used to run every git
428
+ * call without a credential and say nothing about it — for hours, with
429
+ * every push failing. It says so now: once per state, not before every
430
+ * call, and again if the state changes.
431
+ */
432
+ it('says ONCE which settings keep it from asking for a token, and says it again when they change', async () => {
433
+ const lines: string[] = [];
434
+ const capture = (bindings: LogFields = {}): ILogger => {
435
+ const line = (level: string) => (message: string, fields?: LogFields) =>
436
+ void lines.push(`${level} ${message} ${JSON.stringify({ ...bindings, ...fields })}`);
437
+ return { debug: line('debug'), info: line('info'), warn: line('warn'), error: line('error'), child: (m) => capture({ ...bindings, ...m }) };
438
+ };
439
+ const previous = setLogger(capture());
440
+ try {
441
+ const { connection, values } = connected();
442
+ const key = values.githubAppPrivateKey!;
443
+ values.githubAppPrivateKey = '';
444
+ await connection.prepare();
445
+ await connection.prepare();
446
+ await connection.prepare();
447
+ const said = lines.filter((l) => l.includes('no installation token can be asked for'));
448
+ expect(said).toHaveLength(1);
449
+ expect(said[0]).toContain('githubAppPrivateKey');
450
+ expect(said[0]).not.toContain('githubInstallationId');
451
+ expect(connection.token()).toBeNull();
452
+
453
+ // The state changes: a second setting goes too. Said again, naming both.
454
+ values.githubInstallationId = '';
455
+ await connection.prepare();
456
+ await connection.prepare();
457
+ expect(lines.filter((l) => l.includes('no installation token can be asked for'))).toHaveLength(2);
458
+ expect(lines.at(-1)).toContain('githubAppPrivateKey, githubInstallationId');
459
+
460
+ // Settings back: a token is asked for, and the next gap is said afresh.
461
+ values.githubAppPrivateKey = key;
462
+ values.githubInstallationId = '77';
463
+ await connection.prepare();
464
+ expect(connection.token()).toBe('installation-token-1');
465
+ values.githubInstallationId = '';
466
+ await connection.prepare();
467
+ expect(lines.filter((l) => l.includes('no installation token can be asked for'))).toHaveLength(3);
468
+ } finally {
469
+ setLogger(previous);
470
+ }
471
+ });
396
472
  });
397
473
 
398
474
  describe('GitHubAppConnection: what the deployment may be pointed at', () => {
@@ -59,6 +59,13 @@ export class GitHubAppConnection implements GitHubAppRepository {
59
59
  private renewing: Promise<void> | null = null;
60
60
  /** The last renewal failed: how many in a row have, and until when GitHub is not asked again. */
61
61
  private failed: { times: number; until: number } | null = null;
62
+ /**
63
+ * The settings that were empty the last time no token could be asked for,
64
+ * so the log says so ONCE per state and not before every git call. A
65
+ * deployment in this state used to run for hours with every push failing
66
+ * and nothing in the log naming the cause.
67
+ */
68
+ private saidUnaskable: string | null = null;
62
69
 
63
70
  constructor(private readonly opts: GitHubAppConnectionOptions) {
64
71
  this.client = opts.client ?? new GitHubAppClient();
@@ -138,20 +145,47 @@ export class GitHubAppConnection implements GitHubAppRepository {
138
145
  * `asked` is for an admin waiting on the answer itself (the setup screen
139
146
  * listing repositories, proving a connection): GitHub is asked whatever
140
147
  * is remembered, and the failure is thrown.
148
+ *
149
+ * `refused` is the git runner reporting that the host threw out the token
150
+ * in hand. However much time the clock gives it, that token is dead: it is
151
+ * dropped first, so nothing presents it again, and GitHub is asked for
152
+ * another the way `asked` asks — now, and with the failure thrown. A
153
+ * renewal that fails leaves NO token, which is what tells the runner not
154
+ * to run the command again with the one the host just refused.
141
155
  */
142
- async prepare(opts: { asked?: boolean } = {}): Promise<void> {
156
+ async prepare(opts: { asked?: boolean; refused?: boolean } = {}): Promise<void> {
143
157
  const credentials = this.credentials();
144
158
  const installationId = this.installationId();
145
- if (!credentials || !installationId) return;
159
+ if (!credentials || !installationId) {
160
+ // Said once per state: every git call lands here while it lasts.
161
+ const empty = ['githubAppId', 'githubAppSlug', 'githubAppPrivateKey', 'githubAppClientId', 'githubAppClientSecret', 'githubInstallationId']
162
+ .filter((key) => !this.opts.read(key))
163
+ .join(', ');
164
+ if (this.saidUnaskable !== empty) {
165
+ this.saidUnaskable = empty;
166
+ this.log().error(
167
+ `no installation token can be asked for while these settings are empty: ${empty}. ` +
168
+ 'Every git call to the repository host runs without a credential until they are set.',
169
+ );
170
+ }
171
+ return;
172
+ }
173
+ this.saidUnaskable = null;
174
+ if (opts.refused) {
175
+ // The host's verdict outranks the clock: what it refused is not
176
+ // presented again, by anyone, whatever a renewal in flight brings.
177
+ this.held = null;
178
+ }
179
+ const insist = Boolean(opts.asked || opts.refused);
146
180
  const held = this.held?.for === this.holder() ? this.held : null;
147
181
  const good = held !== null && held.expiresAt > this.now();
148
182
  if (good && held.expiresAt - this.now() > RENEW_AHEAD_MS) return;
149
- if (!opts.asked && this.failed && this.now() < this.failed.until) return;
183
+ if (!insist && this.failed && this.now() < this.failed.until) return;
150
184
 
151
185
  this.renewing ??= this.renew(credentials, installationId).finally(() => {
152
186
  this.renewing = null;
153
187
  });
154
- if (good && !opts.asked) {
188
+ if (good && !insist) {
155
189
  // Not waited on, so not thrown to anyone: the renewal has logged it.
156
190
  this.renewing.catch(() => undefined);
157
191
  return;