@bevel-software/platform-core-backend 0.25.2 → 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 (733) hide show
  1. package/agent-guide/access-control.md +251 -0
  2. package/agent-guide/conventions.md +35 -0
  3. package/agent-guide/directory-structure.md +145 -0
  4. package/agent-guide/finding-things.md +7 -0
  5. package/agent-guide/html-views.md +83 -0
  6. package/agent-guide/introduction.md +27 -0
  7. package/agent-guide/new-knowledge-base.md +24 -0
  8. package/agent-guide/skills.md +47 -0
  9. package/agent-guide/tool-manuals.md +227 -0
  10. package/agent-guide/where-a-new-file-goes.md +36 -0
  11. package/dist/assets.d.ts +26 -0
  12. package/dist/assets.d.ts.map +1 -1
  13. package/dist/assets.js +35 -2
  14. package/dist/assets.js.map +1 -1
  15. package/dist/core/catalog-revision.d.ts +19 -1
  16. package/dist/core/catalog-revision.d.ts.map +1 -1
  17. package/dist/core/catalog-revision.js +14 -3
  18. package/dist/core/catalog-revision.js.map +1 -1
  19. package/dist/core/core-ports.d.ts +11 -0
  20. package/dist/core/core-ports.d.ts.map +1 -1
  21. package/dist/core/core-ports.js.map +1 -1
  22. package/dist/core/create-core-server.d.ts +5 -0
  23. package/dist/core/create-core-server.d.ts.map +1 -1
  24. package/dist/core/create-core-server.js +65 -3
  25. package/dist/core/create-core-server.js.map +1 -1
  26. package/dist/core/create-core-services.d.ts +35 -0
  27. package/dist/core/create-core-services.d.ts.map +1 -1
  28. package/dist/core/create-core-services.js +114 -12
  29. package/dist/core/create-core-services.js.map +1 -1
  30. package/dist/core/lifecycle.d.ts +16 -1
  31. package/dist/core/lifecycle.d.ts.map +1 -1
  32. package/dist/core/lifecycle.js +18 -1
  33. package/dist/core/lifecycle.js.map +1 -1
  34. package/dist/core-config.d.ts +21 -0
  35. package/dist/core-config.d.ts.map +1 -1
  36. package/dist/core-config.js +22 -1
  37. package/dist/core-config.js.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +2 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/modules/access/access-control.interface.d.ts +17 -0
  43. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  44. package/dist/modules/access/access-control.service.d.ts +2 -0
  45. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  46. package/dist/modules/access/access-control.service.js +27 -0
  47. package/dist/modules/access/access-control.service.js.map +1 -1
  48. package/dist/modules/access/directory-sync-bot.js +1 -1
  49. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  50. package/dist/modules/agent-guide/agent-guide.d.ts +145 -0
  51. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
  52. package/dist/modules/agent-guide/agent-guide.js +199 -0
  53. package/dist/modules/agent-guide/agent-guide.js.map +1 -0
  54. package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
  55. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
  56. package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
  57. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
  58. package/dist/modules/agent-guide/index.d.ts +4 -0
  59. package/dist/modules/agent-guide/index.d.ts.map +1 -0
  60. package/dist/modules/agent-guide/index.js +4 -0
  61. package/dist/modules/agent-guide/index.js.map +1 -0
  62. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
  63. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  64. package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
  65. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  66. package/dist/modules/agent-instructions/compose.d.ts +14 -6
  67. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  68. package/dist/modules/agent-instructions/compose.js +28 -8
  69. package/dist/modules/agent-instructions/compose.js.map +1 -1
  70. package/dist/modules/agent-instructions/index.d.ts +1 -1
  71. package/dist/modules/agent-instructions/index.d.ts.map +1 -1
  72. package/dist/modules/agent-instructions/index.js +1 -1
  73. package/dist/modules/agent-instructions/index.js.map +1 -1
  74. package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
  75. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  76. package/dist/modules/agent-instructions/shared-file-rules.js +51 -104
  77. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  78. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  79. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  80. package/dist/modules/audit/agent-audit.service.js +43 -2
  81. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  82. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  83. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  84. package/dist/modules/audit/agent-connection.routes.js +38 -0
  85. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  86. package/dist/modules/audit/audit.contract.d.ts +24 -0
  87. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  88. package/dist/modules/audit/audit.contract.js.map +1 -1
  89. package/dist/modules/audit/index.d.ts +1 -0
  90. package/dist/modules/audit/index.d.ts.map +1 -1
  91. package/dist/modules/audit/index.js +1 -0
  92. package/dist/modules/audit/index.js.map +1 -1
  93. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  94. package/dist/modules/auth/account.routes.js +10 -3
  95. package/dist/modules/auth/account.routes.js.map +1 -1
  96. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  97. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  98. package/dist/modules/auth/auth.middleware.js +1 -1
  99. package/dist/modules/auth/auth.middleware.js.map +1 -1
  100. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  101. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  102. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  103. package/dist/modules/database/core-schema.d.ts +96 -0
  104. package/dist/modules/database/core-schema.d.ts.map +1 -1
  105. package/dist/modules/database/core-schema.js +32 -3
  106. package/dist/modules/database/core-schema.js.map +1 -1
  107. package/dist/modules/embed/account-link.service.d.ts +42 -0
  108. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  109. package/dist/modules/embed/account-link.service.js +50 -0
  110. package/dist/modules/embed/account-link.service.js.map +1 -0
  111. package/dist/modules/embed/embed-link.d.ts +58 -0
  112. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  113. package/dist/modules/embed/embed-link.js +160 -0
  114. package/dist/modules/embed/embed-link.js.map +1 -0
  115. package/dist/modules/embed/embed.errors.d.ts +30 -0
  116. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  117. package/dist/modules/embed/embed.errors.js +42 -0
  118. package/dist/modules/embed/embed.errors.js.map +1 -0
  119. package/dist/modules/embed/embed.interface.d.ts +204 -0
  120. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  121. package/dist/modules/embed/embed.interface.js +2 -0
  122. package/dist/modules/embed/embed.interface.js.map +1 -0
  123. package/dist/modules/embed/embed.routes.d.ts +59 -0
  124. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  125. package/dist/modules/embed/embed.routes.js +343 -0
  126. package/dist/modules/embed/embed.routes.js.map +1 -0
  127. package/dist/modules/embed/embed.service.d.ts +169 -0
  128. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  129. package/dist/modules/embed/embed.service.js +627 -0
  130. package/dist/modules/embed/embed.service.js.map +1 -0
  131. package/dist/modules/embed/embed.tools.d.ts +51 -0
  132. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  133. package/dist/modules/embed/embed.tools.js +170 -0
  134. package/dist/modules/embed/embed.tools.js.map +1 -0
  135. package/dist/modules/embed/mcp-app.d.ts +64 -0
  136. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  137. package/dist/modules/embed/mcp-app.js +147 -0
  138. package/dist/modules/embed/mcp-app.js.map +1 -0
  139. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  140. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  141. package/dist/modules/embed/mcp-app.routes.js +36 -0
  142. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  143. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  144. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  145. package/dist/modules/github-app/github-app.connection.js +34 -3
  146. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  147. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  148. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  149. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  150. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  151. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  152. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  153. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  154. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  155. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  156. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  157. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  158. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  159. package/dist/modules/mcp/mcp.service.d.ts +57 -3
  160. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  161. package/dist/modules/mcp/mcp.service.js +177 -24
  162. package/dist/modules/mcp/mcp.service.js.map +1 -1
  163. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  164. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  165. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  167. package/dist/modules/mcp/tool-schema-guard.d.ts +105 -0
  168. package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
  169. package/dist/modules/mcp/tool-schema-guard.js +171 -0
  170. package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
  171. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  172. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  173. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  174. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  175. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  176. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  177. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  178. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  179. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  180. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  181. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  182. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  183. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  184. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  185. package/dist/modules/onboarding/starter-packs.js +156 -0
  186. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  187. package/dist/modules/plugins/index.d.ts +1 -1
  188. package/dist/modules/plugins/index.d.ts.map +1 -1
  189. package/dist/modules/plugins/index.js +1 -1
  190. package/dist/modules/plugins/index.js.map +1 -1
  191. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  192. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  193. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  194. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  195. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  196. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  197. package/dist/modules/plugins/plugins.routes.js +31 -0
  198. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  199. package/dist/modules/plugins/plugins.tools.d.ts +36 -2
  200. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
  201. package/dist/modules/plugins/plugins.tools.js +71 -14
  202. package/dist/modules/plugins/plugins.tools.js.map +1 -1
  203. package/dist/modules/settings/deployment-settings.service.d.ts +34 -7
  204. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  205. package/dist/modules/settings/deployment-settings.service.js +148 -54
  206. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  207. package/dist/modules/settings/repository-source.d.ts +1 -0
  208. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  209. package/dist/modules/settings/repository-source.js +3 -3
  210. package/dist/modules/settings/repository-source.js.map +1 -1
  211. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  212. package/dist/modules/settings/setup.routes.js +3 -6
  213. package/dist/modules/settings/setup.routes.js.map +1 -1
  214. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  215. package/dist/modules/skills/skills.tools.js +103 -43
  216. package/dist/modules/skills/skills.tools.js.map +1 -1
  217. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  218. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  219. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  220. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  221. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  222. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  223. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  224. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  225. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  226. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  227. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  228. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  229. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  230. package/dist/modules/tool-helpers/index.d.ts +28 -1
  231. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  232. package/dist/modules/tool-helpers/index.js +28 -1
  233. package/dist/modules/tool-helpers/index.js.map +1 -1
  234. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  235. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  236. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  237. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  238. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  239. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  240. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  241. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  242. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  243. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  244. package/dist/modules/tool-helpers/tool-def.js +70 -6
  245. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  246. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  247. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  248. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  249. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  250. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  251. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  252. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  253. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +23 -4
  254. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  255. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  256. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
  257. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  258. package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
  259. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  260. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
  261. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  262. package/dist/modules/tool-manuals/tool-manuals.tools.js +77 -38
  263. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  264. package/dist/modules/tool-registry/description-length.d.ts +20 -16
  265. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  266. package/dist/modules/tool-registry/description-length.js +50 -30
  267. package/dist/modules/tool-registry/description-length.js.map +1 -1
  268. package/dist/modules/tool-registry/guide-first.d.ts +27 -0
  269. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
  270. package/dist/modules/tool-registry/guide-first.js +40 -0
  271. package/dist/modules/tool-registry/guide-first.js.map +1 -0
  272. package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
  273. package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
  274. package/dist/modules/tool-registry/tool-registry.js +9 -2
  275. package/dist/modules/tool-registry/tool-registry.js.map +1 -1
  276. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
  277. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
  278. package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
  279. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
  280. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +126 -0
  281. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
  282. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +611 -0
  283. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
  284. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
  285. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
  286. package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
  287. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -1
  288. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  289. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  290. package/dist/modules/workflow/agent-tools/workflow.tools.js +133 -37
  291. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  292. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  293. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  294. package/dist/modules/workflow/file-lock.service.js +62 -5
  295. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  296. package/dist/modules/workflow/git/git.service.d.ts +282 -14
  297. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  298. package/dist/modules/workflow/git/git.service.js +627 -126
  299. package/dist/modules/workflow/git/git.service.js.map +1 -1
  300. package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
  301. package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
  302. package/dist/modules/workflow/git/merge-commit.js +89 -0
  303. package/dist/modules/workflow/git/merge-commit.js.map +1 -0
  304. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  305. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  306. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  307. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  308. package/dist/modules/workflow/git/pull-request.service.d.ts +94 -1
  309. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  310. package/dist/modules/workflow/git/pull-request.service.js +332 -37
  311. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  312. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  313. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  314. package/dist/modules/workflow/pending-commits.service.js +25 -1
  315. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  316. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  317. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  318. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  319. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  320. package/dist/modules/workflow/recovery-bot.js +1 -1
  321. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  322. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +35 -0
  323. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  324. package/dist/modules/workflow/review-workflow/review-workflow.service.js +180 -13
  325. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  326. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  327. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  328. package/dist/modules/workflow/sanitize-error.js +45 -0
  329. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  330. package/dist/modules/workflow/workflow.routes.d.ts +6 -2
  331. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  332. package/dist/modules/workflow/workflow.routes.js +7 -2
  333. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  334. package/dist/modules/workflow/workflow.service.d.ts +187 -15
  335. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  336. package/dist/modules/workflow/workflow.service.js +808 -106
  337. package/dist/modules/workflow/workflow.service.js.map +1 -1
  338. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  339. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  340. package/dist/modules/workspace/agent-download.builder.js +238 -0
  341. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  342. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  343. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  344. package/dist/modules/workspace/agent-download.routes.js +156 -0
  345. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  346. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  347. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  348. package/dist/modules/workspace/agent-download.store.js +423 -0
  349. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  350. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  351. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  352. package/dist/modules/workspace/agent-upload.store.js +16 -6
  353. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  354. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  355. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  356. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  357. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  358. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  359. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  360. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  361. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  362. package/dist/modules/workspace/first-run.d.ts +111 -0
  363. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  364. package/dist/modules/workspace/first-run.js +181 -0
  365. package/dist/modules/workspace/first-run.js.map +1 -0
  366. package/dist/modules/workspace/move-links.d.ts +92 -0
  367. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  368. package/dist/modules/workspace/move-links.js +173 -0
  369. package/dist/modules/workspace/move-links.js.map +1 -0
  370. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  371. package/dist/modules/workspace/startup/steps/seed-tree.js +24 -39
  372. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  373. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
  374. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  375. package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
  376. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  377. package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
  378. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  379. package/dist/modules/workspace/startup/steps/template-source.js +5 -3
  380. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  381. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  382. package/dist/modules/workspace/workspace.routes.js +63 -22
  383. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  384. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  385. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  386. package/dist/modules/workspace/workspace.service.js +197 -24
  387. package/dist/modules/workspace/workspace.service.js.map +1 -1
  388. package/dist/modules/workspace/workspace.tools.d.ts +68 -1
  389. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  390. package/dist/modules/workspace/workspace.tools.js +642 -71
  391. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  392. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  393. package/dist/modules/write-access/write-access.js +9 -0
  394. package/dist/modules/write-access/write-access.js.map +1 -1
  395. package/dist/shared/domain-errors.d.ts +84 -1
  396. package/dist/shared/domain-errors.d.ts.map +1 -1
  397. package/dist/shared/domain-errors.js +100 -12
  398. package/dist/shared/domain-errors.js.map +1 -1
  399. package/dist/shared/git.contract.d.ts +10 -1
  400. package/dist/shared/git.contract.d.ts.map +1 -1
  401. package/dist/shared/git.contract.js.map +1 -1
  402. package/dist/shared/hidden-tools.d.ts +44 -0
  403. package/dist/shared/hidden-tools.d.ts.map +1 -0
  404. package/dist/shared/hidden-tools.js +13 -0
  405. package/dist/shared/hidden-tools.js.map +1 -0
  406. package/dist/shared/utf8-text.d.ts +15 -0
  407. package/dist/shared/utf8-text.d.ts.map +1 -0
  408. package/dist/shared/utf8-text.js +20 -0
  409. package/dist/shared/utf8-text.js.map +1 -0
  410. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  411. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  412. package/dist/tenancy/static-tenant-source.js +9 -2
  413. package/dist/tenancy/static-tenant-source.js.map +1 -1
  414. package/kb-template/.bevelignore +0 -5
  415. package/mcp-app/page.html +349 -0
  416. package/migrations/0017_atlassian_account_links.sql +37 -0
  417. package/migrations/meta/0017_snapshot.json +2396 -0
  418. package/migrations/meta/_journal.json +8 -1
  419. package/package.json +6 -3
  420. package/src/__tests__/kb-layout-config.test.ts +10 -100
  421. package/src/__tests__/packaged-assets-ship.test.ts +66 -0
  422. package/src/assets.ts +38 -2
  423. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  424. package/src/core/__tests__/gated-boot.test.ts +1 -0
  425. package/src/core/__tests__/lifecycle.test.ts +14 -0
  426. package/src/core/catalog-revision.ts +15 -2
  427. package/src/core/core-ports.ts +11 -0
  428. package/src/core/create-core-server.ts +78 -3
  429. package/src/core/create-core-services.ts +168 -10
  430. package/src/core/lifecycle.ts +28 -2
  431. package/src/core-config.ts +24 -1
  432. package/src/index.ts +2 -2
  433. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +58 -0
  434. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
  435. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  436. package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
  437. package/src/modules/access/access-control.interface.ts +28 -0
  438. package/src/modules/access/access-control.service.ts +37 -0
  439. package/src/modules/access/directory-sync-bot.ts +1 -1
  440. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +386 -0
  441. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +207 -0
  442. package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
  443. package/src/modules/agent-guide/agent-guide.ts +300 -0
  444. package/src/modules/agent-guide/index.ts +22 -0
  445. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +39 -118
  446. package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
  447. package/src/modules/agent-instructions/compose.ts +26 -8
  448. package/src/modules/agent-instructions/index.ts +0 -3
  449. package/src/modules/agent-instructions/shared-file-rules.ts +50 -112
  450. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  451. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  452. package/src/modules/audit/agent-audit.service.ts +58 -3
  453. package/src/modules/audit/agent-connection.routes.ts +56 -0
  454. package/src/modules/audit/audit.contract.ts +26 -0
  455. package/src/modules/audit/index.ts +1 -0
  456. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  457. package/src/modules/auth/account.routes.ts +10 -3
  458. package/src/modules/auth/auth.middleware.ts +1 -1
  459. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  460. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  461. package/src/modules/database/core-schema.ts +33 -3
  462. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  463. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  464. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  465. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  466. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  467. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  468. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  469. package/src/modules/embed/account-link.service.ts +76 -0
  470. package/src/modules/embed/embed-link.ts +180 -0
  471. package/src/modules/embed/embed.errors.ts +43 -0
  472. package/src/modules/embed/embed.interface.ts +203 -0
  473. package/src/modules/embed/embed.routes.ts +363 -0
  474. package/src/modules/embed/embed.service.ts +705 -0
  475. package/src/modules/embed/embed.tools.ts +206 -0
  476. package/src/modules/embed/mcp-app.routes.ts +41 -0
  477. package/src/modules/embed/mcp-app.ts +173 -0
  478. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  479. package/src/modules/github-app/github-app.connection.ts +38 -4
  480. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  481. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  482. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  483. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  484. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  485. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  486. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +14 -3
  487. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  488. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  489. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +252 -0
  490. package/src/modules/mcp/__tests__/mcp.service.test.ts +42 -24
  491. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  492. package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
  493. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  494. package/src/modules/mcp/mcp.service.ts +218 -31
  495. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  496. package/src/modules/mcp/tool-schema-guard.ts +196 -0
  497. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  498. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  499. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  500. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  501. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  502. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  503. package/src/modules/onboarding/starter-packs.ts +182 -0
  504. package/src/modules/plugins/__tests__/plugins.tools.test.ts +177 -4
  505. package/src/modules/plugins/index.ts +1 -0
  506. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  507. package/src/modules/plugins/plugins.routes.ts +29 -0
  508. package/src/modules/plugins/plugins.tools.ts +75 -15
  509. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +176 -56
  510. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  511. package/src/modules/settings/deployment-settings.service.ts +144 -55
  512. package/src/modules/settings/repository-source.ts +4 -4
  513. package/src/modules/settings/setup.routes.ts +3 -6
  514. package/src/modules/skills/__tests__/skills.tools.description.test.ts +95 -0
  515. package/src/modules/skills/skills.tools.ts +112 -46
  516. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  517. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  518. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  519. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  520. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  521. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  522. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  523. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  524. package/src/modules/tool-helpers/index.ts +34 -1
  525. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  526. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  527. package/src/modules/tool-helpers/tool-def.ts +132 -7
  528. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  529. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  530. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +57 -0
  531. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
  532. package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
  533. package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
  534. package/src/modules/tool-manuals/tool-manuals.tools.ts +86 -38
  535. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +173 -0
  536. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +81 -66
  537. package/src/modules/tool-registry/description-length.ts +57 -32
  538. package/src/modules/tool-registry/guide-first.ts +41 -0
  539. package/src/modules/tool-registry/tool-registry.ts +9 -2
  540. package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -1
  541. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  542. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  543. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  544. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  545. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  546. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  547. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  548. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  549. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  550. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  551. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  552. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  553. package/src/modules/workflow/agent-tools/__tests__/change-request-read-shape.test.ts +705 -0
  554. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1554 -0
  555. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +150 -4
  556. package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
  557. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +759 -0
  558. package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
  559. package/src/modules/workflow/agent-tools/workflow.tools.ts +137 -35
  560. package/src/modules/workflow/file-lock.service.ts +67 -5
  561. package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
  562. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
  563. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  564. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -0
  565. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  566. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  567. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +72 -2
  568. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
  569. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
  570. package/src/modules/workflow/git/git.service.ts +714 -133
  571. package/src/modules/workflow/git/merge-commit.ts +88 -0
  572. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  573. package/src/modules/workflow/git/pull-request.service.ts +380 -54
  574. package/src/modules/workflow/pending-commits.service.ts +29 -1
  575. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  576. package/src/modules/workflow/recovery-bot.ts +1 -1
  577. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +7 -1
  578. package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
  579. package/src/modules/workflow/review-workflow/review-workflow.service.ts +191 -12
  580. package/src/modules/workflow/sanitize-error.ts +50 -0
  581. package/src/modules/workflow/workflow.routes.ts +7 -2
  582. package/src/modules/workflow/workflow.service.ts +886 -112
  583. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  584. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
  585. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  586. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  587. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  588. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  589. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  590. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  591. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  592. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  593. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  594. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +21 -10
  595. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  596. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  597. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +33 -55
  598. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  599. package/src/modules/workspace/__tests__/workspace.tools.test.ts +664 -34
  600. package/src/modules/workspace/agent-download.builder.ts +308 -0
  601. package/src/modules/workspace/agent-download.routes.ts +183 -0
  602. package/src/modules/workspace/agent-download.store.ts +533 -0
  603. package/src/modules/workspace/agent-upload.store.ts +23 -7
  604. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  605. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  606. package/src/modules/workspace/first-run.ts +222 -0
  607. package/src/modules/workspace/move-links.ts +237 -0
  608. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +191 -489
  609. package/src/modules/workspace/startup/steps/seed-tree.ts +23 -38
  610. package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
  611. package/src/modules/workspace/startup/steps/template-source.ts +5 -3
  612. package/src/modules/workspace/workspace.routes.ts +64 -22
  613. package/src/modules/workspace/workspace.service.ts +202 -25
  614. package/src/modules/workspace/workspace.tools.ts +707 -68
  615. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  616. package/src/modules/write-access/write-access.ts +9 -0
  617. package/src/shared/domain-errors.ts +123 -11
  618. package/src/shared/git.contract.ts +8 -1
  619. package/src/shared/hidden-tools.ts +45 -0
  620. package/src/shared/utf8-text.ts +21 -0
  621. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  622. package/src/tenancy/static-tenant-source.ts +10 -2
  623. package/starter-packs/README.md +103 -0
  624. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  625. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  626. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  627. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  628. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  629. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  630. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  631. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  632. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  633. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  634. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  635. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  636. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  637. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  638. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  639. package/starter-packs/engineering/pack.yaml +11 -0
  640. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  641. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  642. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  643. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  644. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  645. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  646. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  647. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  648. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  649. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  650. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  651. package/starter-packs/general/pack.yaml +10 -0
  652. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  653. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  654. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  655. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  656. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  657. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  658. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  659. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  660. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  661. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  662. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  663. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  664. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  665. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  666. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  667. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  668. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  669. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  670. package/starter-packs/operations/pack.yaml +12 -0
  671. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  672. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  673. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  674. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  675. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  676. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  677. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  678. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  679. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  680. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  681. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  682. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  683. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  684. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  685. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  686. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  687. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  688. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  689. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  690. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  691. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  692. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  693. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  694. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  695. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  696. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  697. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  698. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  699. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  700. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  701. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  702. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  703. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  704. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  705. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  706. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  707. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  708. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  709. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  710. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  711. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  712. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  713. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  714. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  715. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  716. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  717. package/starter-packs/sales/pack.yaml +13 -0
  718. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  719. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  720. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  721. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  722. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  723. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  724. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  725. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  726. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  727. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  728. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  729. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  730. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  731. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  732. package/starter-packs/support/pack.yaml +12 -0
  733. package/kb-template/AGENTS.md +0 -730
@@ -2,16 +2,16 @@ import { spawn } from 'node:child_process';
2
2
  import nodeFs from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import AdmZip from 'adm-zip';
5
- import { ToolError } from '../tool-helpers/tool.contract.js';
5
+ import { hasHttpStatus, ToolError } from '../tool-helpers/tool.contract.js';
6
6
  import { BRANCH_INPUT, toolDef } from '../tool-helpers/tool-def.js';
7
7
  import { notifyAgentRead, assertAgentWriteAllowed, SESSION_ID_INPUT, } from './agent-access.gate.js';
8
8
  import { requireInternalSource, requireExternalSource } from '../tool-auth/tool-auth.middleware.js';
9
9
  import { workspaceIdForBranch } from '../../shared/workspace-id.js';
10
- import { assertBranchProvided, GitInternalsError, WorkflowValidationError } from '../../shared/domain-errors.js';
10
+ import { GitInternalsError, WorkflowValidationError } from '../../shared/domain-errors.js';
11
11
  // Leaf-level shared primitive (same exception `workspace.service.ts` already
12
12
  // relies on) — not a workflow service, so this stays inside the module boundary.
13
13
  import { assertValidBranchName } from '../kb-fs/branch-name.js';
14
- import { assertInsideRepo, assertRepoRootNameFree, assertRepoRootNameFreeArgs, isInsideRepo, normalizePathArgs, } from '../kb-fs/repo-path.js';
14
+ import { assertInsideRepo, assertRepoRootNameFree, assertRepoRootNameFreeArgs, isInsideRepo, normalizePathArgs, normalizeWorkspacePath, } from '../kb-fs/repo-path.js';
15
15
  import { GitGuardedFilesystem } from '../kb-fs/git-guarded-filesystem.js';
16
16
  import { assertNoGitInternalsSegment, assertNotGitInternals, hasGitInternalsSegment } from '../../shared/git-internals.js';
17
17
  import { isRolesYamlPath } from '../access-model/roles-yaml-guard.js';
@@ -24,17 +24,24 @@ import { fileTypeOf, needsContent } from './file-readers/content-mode.js';
24
24
  import { createFileReaderRegistry } from './file-readers/file-reader.registry.js';
25
25
  import { DocumentReader } from './file-readers/document-reader.js';
26
26
  import { mcpImageResult } from '@bevel-software/platform-mcp-core';
27
- import { folderPlaceholderPath, isFolderPlaceholder, isPlatformFile, isPlatformFolder, platformFileCreationRefusal, platformFileRefusal, platformFileUploadRefusal, platformFolderRefusal, entryExistsMessage, } from '@bevel-software/platform-shared';
27
+ import { folderPlaceholderPath, isFolderPlaceholder, isPlatformFile, isPlatformFolder, platformFileCreationRefusal, platformFileRefusal, platformFileUploadRefusal, platformFolderRefusal, isRepositoryOwnFile, repositoryOwnFileDeleteRefusal, entryExistsMessage, } from '@bevel-software/platform-shared';
28
28
  import { AccessDeniedError } from '../access-model/access-errors.js';
29
+ import { AGENT_GUIDE_FILE, isAgentGuidePath, isManagedGuide, withPlatformGuideAppended, } from '../agent-guide/agent-guide.js';
29
30
  import { removeEmptyDirs } from './empty-dirs.js';
31
+ import { FIRST_RUN_SECTION_ID, firstRunNote, knowledgeFolderIsNew } from './first-run.js';
32
+ import { planMoveLinks } from './move-links.js';
33
+ import { MoveLockedError, MoveRacedError } from '../kb-fs/locking-filesystem.js';
30
34
  import { rethrowAsWriteDenial } from './write-denial.js';
31
- import { sharedRulesPointer } from '../agent-instructions/shared-file-rules.js';
32
35
  import { notFound, orDeclaredNotFound, orNotFound } from './not-found.js';
33
36
  import { logger } from '../../shared/logging.js';
34
37
  import { printable } from '../../shared/printable.js';
35
38
  import { DestinationTakenError, inspectDestination } from '../../shared/rename-no-replace.js';
39
+ import { buildDownload } from './agent-download.builder.js';
40
+ import { DOWNLOAD_MAX_FILES, ZIP_DOWNLOAD_MAX_BYTES } from './workspace.service.js';
36
41
  import { isSymlinkZipEntry, isZipNoiseEntry, readZipEntry, zipEntryName, zipEntryNameRefusal, zipEntrySegments, } from './zip-entry-rules.js';
37
42
  const log = logger('workspace-tools');
43
+ /** Types that run scripts wherever they are opened, sent by `request_file_download` as plain bytes. */
44
+ const ACTIVE_CONTENT_TYPES = new Set(['image/svg+xml', 'text/html', 'application/xhtml+xml']);
38
45
  /** How many files `file_stat` counts under a folder before it stops and says so. */
39
46
  const DESCENDANTS_CAP = 10_000;
40
47
  /** How many of a folder's files a `delete_folder` answer names. */
@@ -540,7 +547,9 @@ async function searchRootKind(fs, path) {
540
547
  }
541
548
  }
542
549
  /** JS grep over the workspace tree (read methods only) — bounded by match + depth caps. */
543
- async function grepWalk(fs, dir, re, out, max, depth, gate, notifyRead, docs) {
550
+ async function grepWalk(fs, dir, re, out, max, depth, gate, notifyRead, docs,
551
+ /** A file the walk leaves to its caller: never opened, never counted against `max`. */
552
+ skip = () => false) {
544
553
  if (out.length >= max || depth > 12)
545
554
  return;
546
555
  let entries;
@@ -562,7 +571,10 @@ async function grepWalk(fs, dir, re, out, max, depth, gate, notifyRead, docs) {
562
571
  continue;
563
572
  const p = dir ? `${dir}/${e.name}` : e.name;
564
573
  if (e.type === 'directory') {
565
- await grepWalk(fs, p, re, out, max, depth + 1, gate, notifyRead, docs);
574
+ await grepWalk(fs, p, re, out, max, depth + 1, gate, notifyRead, docs, skip);
575
+ }
576
+ else if (skip(p)) {
577
+ continue;
566
578
  }
567
579
  else {
568
580
  // Opening a file is a read of it, even when the walk started at a root
@@ -605,7 +617,7 @@ const BATCH_SAVE_WARNINGS_OUTPUT = {
605
617
  * the tools were built has to be written here rather than onto the shared
606
618
  * `SESSION_ID_INPUT` constant.
607
619
  */
608
- function sessionIdInputOf(def) {
620
+ export function sessionIdInputOf(def) {
609
621
  const inputs = def.inputs;
610
622
  return inputs?.properties?.body?.properties?.sessionId;
611
623
  }
@@ -640,7 +652,35 @@ changeGate,
640
652
  * real composition wires it (`create-core-server.ts`), and without it the
641
653
  * two tools are not mounted at all rather than mounted and broken.
642
654
  */
643
- uploads) {
655
+ uploads,
656
+ /**
657
+ * The platform's agent guide (see `modules/agent-guide`), which `read_file`
658
+ * and `file_stat` answer at the guide's name in the repository root — after
659
+ * the knowledge base's own file of that name, when it has one. Optional for
660
+ * the harnesses that are about the file primitives; without it the two
661
+ * tools read the disk and nothing else.
662
+ */
663
+ agentGuide,
664
+ /**
665
+ * The download-link store behind `request_file_download` — the way an agent
666
+ * takes files OUT without their content passing through the model. Optional
667
+ * for the harnesses about the file primitives; without it the tool is not
668
+ * mounted.
669
+ */
670
+ downloads,
671
+ /**
672
+ * The starter pack the knowledge base was filled from, if any (see
673
+ * `modules/onboarding`): its untouched pages do not end the `firstRun`
674
+ * note, and the note names the pages it suggests. Optional; without it the
675
+ * note reads the knowledge folder alone.
676
+ */
677
+ starterPacks,
678
+ /**
679
+ * The one tree walk (see `shared/fs.contract.ts`), for the `firstRun`
680
+ * note's look at the knowledge folder. Optional for the harnesses about the
681
+ * file primitives; without it `start_session` answers the id alone.
682
+ */
683
+ disk) {
644
684
  const { kbDirName } = kb;
645
685
  /**
646
686
  * The one extension→reader registry every read-shaped decision routes
@@ -979,6 +1019,26 @@ uploads) {
979
1019
  const rel = toKbRelative(norm, kbDirName);
980
1020
  return rel !== null && isPlatformFolder(rel, kb.layout) ? platformFolderRefusal(rel) : undefined;
981
1021
  };
1022
+ /**
1023
+ * Why `delete_file` may not delete the FILE at `path` whoever asks, or
1024
+ * undefined when the caller's write access decides. Narrower than
1025
+ * {@link managedReason}: a nested `access.md` never moves, but whoever may
1026
+ * write it may delete it — as in the app — and its folder then follows its
1027
+ * parent's rules. The root's `access.md` and `roles.yaml` are deleted by
1028
+ * nobody. Judged on the on-disk spelling, as `managedReason` is.
1029
+ */
1030
+ const fileDeleteRefusal = (path) => {
1031
+ const norm = path.replace(/^\.?\/+/, '').replace(/\/+$/, '');
1032
+ if (isGitMetadata(norm))
1033
+ return managedReason(norm, 'file');
1034
+ const rel = toKbRelative(norm, kbDirName);
1035
+ if (rel === null || !isPlatformFile(rel, kb.layout))
1036
+ return undefined;
1037
+ if (isRepositoryOwnFile(rel, kb.layout))
1038
+ return repositoryOwnFileDeleteRefusal(rel);
1039
+ const name = rel.slice(rel.lastIndexOf('/') + 1);
1040
+ return name === 'access.md' ? undefined : `${name} is a platform file and cannot be deleted through the agent tools.`;
1041
+ };
982
1042
  /** The workspace root on disk for `branch`. */
983
1043
  const workspaceRoot = (branch, ctx) => ctx.workspaceService.getWorkspacePath(workspaceIdForBranch(branch));
984
1044
  /**
@@ -1190,23 +1250,22 @@ uploads) {
1190
1250
  // description from the END, where what is specific to the tool sits. The
1191
1251
  // rules themselves are in the handshake instructions and in the managed
1192
1252
  // guide (see `shared-file-rules.ts`), stated once and from one text.
1193
- // Whether a call to this tool MUST name a branch, read off the tool's own
1194
- // declaration rather than assumed of the family. Every tool mounted here
1195
- // requires `branch` today; keying on the schema means a tool that declares
1196
- // it optional (and resolves absence itself, as `list_tool_setup` does on its
1197
- // own route) is not handed a refusal it never asked for.
1198
- const requiresBranch = (spec.inputs.required ?? []).includes('branch');
1199
1253
  const describe = () => (typeof spec.description === 'function' ? spec.description() : spec.description) +
1200
- (spec.gated ? agentAccessGate.notes.gatedToolNote() : '') +
1201
- sharedRulesPointer(kb.layout);
1254
+ (spec.gated ? agentAccessGate.notes.gatedToolNote() : '');
1202
1255
  const def = toolDef({
1203
1256
  name: spec.name,
1204
1257
  description: describe(),
1205
1258
  path,
1206
1259
  inputs: spec.inputs,
1207
1260
  outputs: spec.outputs,
1261
+ refusesItself: spec.refusesItself,
1208
1262
  tags: spec.write ? ['workspace', 'write'] : ['workspace'],
1209
1263
  });
1264
+ // Every tool here declares `branch` required in its inputs, which is how
1265
+ // `toolDef` records it: the tool handler refuses a branch-less call before
1266
+ // the path work below and before any handler. Most of these tools would
1267
+ // meet the same refusal one layer down at `getFilesystem`, but not all —
1268
+ // `unzip` hands `branch` straight to the workspace service by id.
1210
1269
  registry.registerInternalTool(def);
1211
1270
  if (!spec.internalOnly)
1212
1271
  registry.registerExternalTool(def);
@@ -1253,17 +1312,6 @@ uploads) {
1253
1312
  // path, and exempting it there would be a workspace-relative path that
1254
1313
  // never reached the repository — the whole bug, spelled with a prefix.
1255
1314
  toolHandler(async (args, ctx) => {
1256
- // FIRST, before the path work and before any handler: every tool
1257
- // mounted here declares `branch` as a required, non-empty string, and
1258
- // nothing enforced that, so a call that named none was carried down
1259
- // until `workspaceIdForBranch` made a workspace directory out of the
1260
- // missing value. Most of these tools would meet the same refusal one
1261
- // layer down at `getFilesystem`, but not all of them do — `unzip`
1262
- // hands `branch` straight to the workspace service by id — so the
1263
- // check belongs on the mount every one of them shares rather than on
1264
- // the resolver only some of them reach.
1265
- if (requiresBranch && !spec.resolvesBranchItself)
1266
- assertBranchProvided(args.branch);
1267
1315
  // BEFORE the normaliser: see `assertToolPathsNotGitInternals`.
1268
1316
  if (spec.fileTool !== false)
1269
1317
  await assertToolPathsNotGitInternals(args, ctx);
@@ -1291,7 +1339,9 @@ uploads) {
1291
1339
  catch (err) {
1292
1340
  return rethrowAsWriteDenial(err, { tool: spec.name, branch: args.branch, userEmail: ctx.user.email, userId: ctx.user.id }, accessControl, kb, changeGate);
1293
1341
  }
1294
- }, { write: spec.write }));
1342
+ },
1343
+ // `execute_command` resolves its own branch (see `resolvesBranchItself`).
1344
+ { write: spec.write, writeScope: spec.writeScope, ...(spec.resolvesBranchItself ? { branch: 'own' } : {}) }));
1295
1345
  };
1296
1346
  // ── session bootstrap (external agents) ─────────────────────────────────
1297
1347
  // Every read/write tool below takes a `sessionId`: the conversation the
@@ -1319,12 +1369,15 @@ uploads) {
1319
1369
  // transport hiccup on its first call as an unrecoverable start.
1320
1370
  const startSessionDef = toolDef({
1321
1371
  name: 'start_session',
1322
- description: 'Mint the id of this conversation, which the KnowledgeBase tools take as `sessionId`. Call this ONCE, at the start of your work and only once per run — minting a new id mid-run starts a second conversation as far as the server is concerned. The id is also a chat session in the app, so you can hand the SAME id to the `ask` tool: your reads and your questions are then one conversation. Pass the returned id explicitly as `sessionId` on every subsequent KnowledgeBase tool call (direct MCP calls and inside `call_tool_chain` alike). RETRYING IS SAFE: a call that fails created nothing, so retry it — there is no half-made session to clean up. If a retry lands after a success you simply hold two independent ids, which is harmless: keep passing the one id you have already used for the rest of the run and ignore the other. Returns `{ sessionId }`.',
1372
+ description: 'Mint this conversation\'s id: the `sessionId` KnowledgeBase tools take. Call this ONCE, at the start of your work — minting a new id mid-run starts a second conversation as far as the server is concerned. The id is also a chat session in the app, so you can hand the SAME id to the `ask` tool: your reads and your questions are then one conversation. Pass the returned id as `sessionId` on every later KnowledgeBase tool call, inside `call_tool_chain` too. RETRYING IS SAFE: a call that fails created nothing, so retry it. If a retry lands after a success you hold two independent ids, which is harmless: keep passing the one you already used and ignore the other. Returns `{ sessionId }`, plus `firstRun` while the knowledge base is still empty: a note to act on.',
1323
1373
  path: '/api/agent/tools/start_session',
1324
1374
  inputs: { type: 'object', properties: {}, additionalProperties: false },
1325
1375
  outputs: {
1326
1376
  type: 'object',
1327
- properties: { sessionId: str('The minted session id — pass it as `sessionId` on subsequent KnowledgeBase tool calls and to `ask`.') },
1377
+ properties: {
1378
+ sessionId: str('The minted session id — pass it as `sessionId` on subsequent KnowledgeBase tool calls and to `ask`.'),
1379
+ firstRun: str(`Present only while the knowledge base holds nothing but its starter guide (and a starter pack's untouched pages): what to offer the person (the guide's \`${FIRST_RUN_SECTION_ID}\` section says how).`),
1380
+ },
1328
1381
  required: ['sessionId'],
1329
1382
  },
1330
1383
  tags: ['workspace'],
@@ -1341,9 +1394,99 @@ uploads) {
1341
1394
  // per-chat sessionIds over its lifetime.
1342
1395
  requireExternalSource, toolHandler(async (_args, ctx) => {
1343
1396
  const { sessionId } = await sessionSink.createSession(ctx.user.id, new Date());
1344
- return { sessionId };
1397
+ const firstRun = await firstRunFor(ctx);
1398
+ return firstRun ? { sessionId, firstRun } : { sessionId };
1345
1399
  }));
1400
+ /**
1401
+ * The `firstRun` note (see `first-run.ts`) while the default branch's
1402
+ * knowledge folder holds nothing but the starter guide, else null. Asked of
1403
+ * a clone that is ALREADY there — never one this call would have to make, so
1404
+ * the first call of a conversation does no clone — and never allowed to fail
1405
+ * the call: minting the id is what `start_session` is for, and the note is a
1406
+ * courtesy on top of it.
1407
+ */
1408
+ const firstRunFor = async (ctx) => {
1409
+ try {
1410
+ if (!disk || !kb.isBranchModelConfigured())
1411
+ return null;
1412
+ const workspaceId = kb.defaultWorkspaceId();
1413
+ if (!(await ctx.workspaceService.hasBootstrappedWorkspace(workspaceId)))
1414
+ return null;
1415
+ const knowledgeDir = kb.layout.knowledgeBaseDir;
1416
+ // GATED LIKE A READ. The note says what the knowledge folder holds —
1417
+ // nothing, or a pack's pages still as the pack wrote them — so it goes
1418
+ // only to a caller who may read that folder, and judges the folder as
1419
+ // THEY may see it: a page they may not read does not make it old for
1420
+ // them (`mayRead` below), since reading the folder is no leave to learn
1421
+ // what restricted pages sit in it.
1422
+ if (!(await accessControl.canRead(workspaceId, ctx.user.email, knowledgeDir)))
1423
+ return null;
1424
+ const root = await ctx.workspaceService.getWorkspacePath(workspaceId);
1425
+ const mayRead = async (rels) => {
1426
+ const verdicts = await accessControl.canReadBatch(workspaceId, ctx.user.email, rels.map((rel) => `${knowledgeDir}/${rel}`));
1427
+ return new Map(rels.map((rel) => [rel, verdicts.get(`${knowledgeDir}/${rel}`) === true]));
1428
+ };
1429
+ // A starter pack's pages, still as the pack wrote them, are tasks to
1430
+ // fill in rather than pages anyone wrote: they leave the note standing,
1431
+ // and the note names what the pack suggests drafting first. A pack
1432
+ // page the caller may not read keeps the note away altogether: it
1433
+ // names the pack and what it suggests drafting, which is about pages
1434
+ // this caller is not to know of — in the checkout or gone from it.
1435
+ const starter = (await starterPacks?.firstRunStarter()) ?? null;
1436
+ const pages = starter ? await readableStarterPages(ctx, workspaceId, knowledgeDir, starter.pages) : undefined;
1437
+ if (starter && pages && pages.size < starter.pages.size)
1438
+ return null;
1439
+ if (!(await knowledgeFolderIsNew(disk, join(root, kbDirName), knowledgeDir, pages, mayRead)))
1440
+ return null;
1441
+ return firstRunNote(`${kbDirName}/${knowledgeDir}`, starter ?? undefined);
1442
+ }
1443
+ catch (err) {
1444
+ log.debug('start_session: could not tell whether the knowledge base is new', {
1445
+ error: err instanceof Error ? err.message : String(err),
1446
+ });
1447
+ return null;
1448
+ }
1449
+ };
1450
+ /** The starter pages (paths below the knowledge folder → text) the caller may read, and no other. */
1451
+ const readableStarterPages = async (ctx, workspaceId, knowledgeDir, pages) => {
1452
+ const rels = [...pages.keys()];
1453
+ const verdicts = await accessControl.canReadBatch(workspaceId, ctx.user.email, rels.map((rel) => `${knowledgeDir}/${rel}`));
1454
+ return new Map(rels.filter((rel) => verdicts.get(`${knowledgeDir}/${rel}`)).map((rel) => [rel, pages.get(rel)]));
1455
+ };
1346
1456
  // ── reads ──────────────────────────────────────────────────────────────
1457
+ /**
1458
+ * What reading a workspace file ANSWERS, gate and all — `read_file`'s whole
1459
+ * behaviour minus the spill ref and the `offset`/`limit` slice, which are
1460
+ * that tool's own arguments.
1461
+ *
1462
+ * Factored out because a second tool has to answer the same way: `open_page`
1463
+ * (see `modules/embed`) promises the file's text "the way `read_file` does",
1464
+ * and the refusals `read_file` gives for a path the caller may not read or
1465
+ * one that does not exist. Any of that re-derived there would be a second
1466
+ * answer to a question with one correct answer — the access gate, the read
1467
+ * hook, the extraction and the not-found message all have to match, and a
1468
+ * copy drifts on the first change to any of them.
1469
+ */
1470
+ const readForTool = async (branch, p, ctx) => {
1471
+ // The guide's name at the repository root answers with the platform's
1472
+ // guide, which is text the code owns and every agent may read: no gate
1473
+ // and no read hook for it. A file the knowledge base keeps under that
1474
+ // name is ITS OWN conventions page and is read as any file is — gated,
1475
+ // noted — and comes first, with the guide after it. A copy of the
1476
+ // guide an earlier release wrote to disk (still on a draft, say) is
1477
+ // recognised by its header and not served a second time.
1478
+ if (agentGuide && isAgentGuidePath(toKbRelative(p, kbDirName) ?? '')) {
1479
+ return { kind: 'text', text: await guideAt(branch, ctx, p) };
1480
+ }
1481
+ await notifyAgentRead(agentAccessGate, ctx, branch, p);
1482
+ await assertCanRead(readGateFor(branch, ctx), p);
1483
+ const fs = await ctx.getFilesystem(branch);
1484
+ // Reading (extraction, image and binary handling included) happens AFTER
1485
+ // the access gate and the read hook above — a document read is still a
1486
+ // KB read. ONE registry dispatch picks the reader by extension.
1487
+ const bytes = await orNotFound(p, async () => asBytes(await fs.readFile(p)));
1488
+ return readers.readerFor(p).read(bytes, p);
1489
+ };
1347
1490
  mount({
1348
1491
  name: 'read_file',
1349
1492
  gated: true,
@@ -1379,16 +1522,13 @@ uploads) {
1379
1522
  if (spillStore.isSpillRef(p)) {
1380
1523
  return { path: p, content: await spillStore.read(p, offset, limit) };
1381
1524
  }
1382
- await notifyAgentRead(agentAccessGate, ctx, a.branch, p);
1383
- await assertCanRead(readGateFor(a.branch, ctx), p);
1384
- const fs = await ctx.getFilesystem(a.branch);
1385
- // Reading (extraction, image and binary handling included) happens AFTER
1386
- // the access gate and the read hook above — a document read is still a
1387
- // KB read. ONE registry dispatch picks the reader by
1388
- // extension; everything below just maps its ReadResult onto the tool's
1389
- // result shape.
1390
- const bytes = await orNotFound(p, async () => asBytes(await fs.readFile(p)));
1391
- const result = await readers.readerFor(p).read(bytes, p);
1525
+ const slice = (content) => {
1526
+ const start = offset && offset > 0 ? offset : 0;
1527
+ return offset !== undefined || limit !== undefined
1528
+ ? content.slice(start, limit !== undefined ? start + limit : undefined)
1529
+ : content;
1530
+ };
1531
+ const result = await readForTool(a.branch, p, ctx);
1392
1532
  // Images return the picture itself as an MCP image content block, so a
1393
1533
  // multimodal model SEES it. The handler returns the `McpImageResult`
1394
1534
  // sentinel; the MCP result shaping (`toCallToolResult` in
@@ -1407,13 +1547,102 @@ uploads) {
1407
1547
  // document, unreadable binary, oversized image) IS the file's honest
1408
1548
  // textual answer, sliced like any other content.
1409
1549
  const content = result.kind === 'text' ? result.text : result.message;
1410
- const start = offset && offset > 0 ? offset : 0;
1411
- const sliced = offset !== undefined || limit !== undefined
1412
- ? content.slice(start, limit !== undefined ? start + limit : undefined)
1413
- : content;
1414
- return { path: p, content: sliced };
1550
+ return { path: p, content: slice(content) };
1415
1551
  },
1416
1552
  });
1553
+ /**
1554
+ * The knowledge base's OWN file at the guide's path, read as any file is —
1555
+ * through the read hook and the access gate — or null when there is none,
1556
+ * when the caller MAY NOT READ IT, or when what is there is a copy of the
1557
+ * platform's guide an earlier release wrote (recognised by its header),
1558
+ * which the guide served beside it would only repeat. A file that is not
1559
+ * text (a binary squatting the name) is read for what it is: its honest
1560
+ * textual answer.
1561
+ *
1562
+ * A file the caller may not read answers EXACTLY as no file does: the guide
1563
+ * alone, with nothing said. A refusal here would tell a caller the root
1564
+ * denies that a conventions file exists, which is the one thing the
1565
+ * platform never tells about a file someone may not read — a restricted
1566
+ * node is indistinguishable from an absent one on every other read.
1567
+ */
1568
+ /** What a read of the guide's path answers: the guide, after the knowledge base's own readable file when it has one. */
1569
+ const guideAt = async (branch, ctx, p) => {
1570
+ const guide = await agentGuide();
1571
+ const own = await ownGuideFile(branch, ctx, p);
1572
+ return own === null ? guide : withPlatformGuideAppended(own, guide);
1573
+ };
1574
+ const ownGuideFile = async (branch, ctx, p) => {
1575
+ const fs = await ctx.getFilesystem(branch);
1576
+ // Existence first, then the gate, then the hook and the bytes — nothing of
1577
+ // theirs is read, or noted as read, before they are allowed to read it.
1578
+ if (!(await ownGuideReadable(fs, branch, ctx, p)))
1579
+ return null;
1580
+ await notifyAgentRead(agentAccessGate, ctx, branch, p);
1581
+ // Gone between the probe and the read — a concurrent delete — is the
1582
+ // absent case: the guide alone, as a read a moment later would answer.
1583
+ const bytes = await fs.readFile(p).then(asBytes, (err) => {
1584
+ if (isAbsence(err))
1585
+ return null;
1586
+ throw err;
1587
+ });
1588
+ if (bytes === null)
1589
+ return null;
1590
+ const result = await readers.readerFor(p).read(bytes, p);
1591
+ const text = result.kind === 'text' ? result.text : result.kind === 'image' ? result.note : result.message;
1592
+ return isManagedGuide(text) ? null : text;
1593
+ };
1594
+ /**
1595
+ * Whether there is a file of the knowledge base's own at the guide's path
1596
+ * that THIS caller may read. False for nothing there and for a file the
1597
+ * access rules close to them, on purpose and without distinction (see
1598
+ * {@link ownGuideFile}).
1599
+ */
1600
+ const ownGuideReadable = async (fs, branch, ctx, p) => {
1601
+ // The permission verdict BEFORE the filesystem is asked anything, as on
1602
+ // every other read: a caller the rules close the path to learns nothing
1603
+ // from it — not that something is there, and not what the filesystem
1604
+ // says about an entry it cannot stat.
1605
+ const gate = readGateFor(branch, ctx);
1606
+ const rel = toKbRelative(p, gate.kbDirName);
1607
+ if (rel !== null && !(await gate.accessControl.canRead(gate.workspaceId, gate.userEmail, rel)))
1608
+ return false;
1609
+ return existsAt(fs, p);
1610
+ };
1611
+ /** Whether something is at `p` on `fs` — absence is false, any other failure is thrown. */
1612
+ const existsAt = async (fs, p) => fs.stat(p).then(() => true, (err) => {
1613
+ if (isAbsence(err))
1614
+ return false;
1615
+ throw err;
1616
+ });
1617
+ /**
1618
+ * Whether what is at `p` is an entry of the knowledge base's own for the
1619
+ * ordinary stat to describe: anything there except a plain file that is a
1620
+ * copy of the guide an earlier release wrote (recognised by its header).
1621
+ * A folder at the guide's name is theirs and is never read — reading a
1622
+ * folder is an error, not an absence. Nothing there, at the stat or at the
1623
+ * read a moment later (a concurrent delete), is the absent case, which the
1624
+ * caller answers with the guide.
1625
+ */
1626
+ const isOwnEntryStill = async (fs, p) => {
1627
+ const type = await fs.stat(p).then((st) => st.type, (err) => {
1628
+ if (isAbsence(err))
1629
+ return undefined;
1630
+ throw err;
1631
+ });
1632
+ if (type === undefined)
1633
+ return false;
1634
+ if (type !== 'file')
1635
+ return true;
1636
+ const bytes = await fs.readFile(p).then(asBytes, (err) => {
1637
+ if (isAbsence(err))
1638
+ return null;
1639
+ throw err;
1640
+ });
1641
+ if (bytes === null)
1642
+ return false;
1643
+ const result = await readers.readerFor(p).read(bytes, p);
1644
+ return !(result.kind === 'text' && isManagedGuide(result.text));
1645
+ };
1417
1646
  mount({
1418
1647
  name: 'list_files',
1419
1648
  gated: true,
@@ -1466,8 +1695,8 @@ uploads) {
1466
1695
  'never move or delete, judged like the dry runs (on a draft branch writes are not gated); `movable` judges the SOURCE ' +
1467
1696
  'side only, so the destination still wants a `move_file` dry run. ' +
1468
1697
  'For a folder, `descendants` counts the files under it at any depth; counting stops at 10000 and ' +
1469
- '`descendantsTruncated` says so, past which `movable` and `deletable` are false — a folder that large was not judged ' +
1470
- 'in full, so run the `move_file` or `delete_folder` dry run for the real verdict.',
1698
+ '`descendantsTruncated` says so, past which `movable` and `deletable` are false (not judged in full): run the ' +
1699
+ '`move_file` or `delete_folder` dry run for the real verdict.',
1471
1700
  inputs: {
1472
1701
  type: 'object',
1473
1702
  properties: {
@@ -1532,6 +1761,10 @@ uploads) {
1532
1761
  },
1533
1762
  textEditable: { type: 'boolean', description: 'Files only: whether write_file/write_files/edit_file accept this file as it is now.' },
1534
1763
  mimeNote: str('Present when `mimeSource` is `fallback`: says the MIME type is a fallback, not a detected type.'),
1764
+ platformGuide: {
1765
+ type: 'boolean',
1766
+ description: "True at the agent guide's name in the repository root when the knowledge base has no file of its own there: what read_file answers is the platform's guide, which is not on disk and cannot be written, moved or deleted.",
1767
+ },
1535
1768
  },
1536
1769
  required: ['managed', 'movable', 'deletable', 'access'],
1537
1770
  additionalProperties: true,
@@ -1540,7 +1773,49 @@ uploads) {
1540
1773
  handler: async (a, ctx) => {
1541
1774
  const p = a.path;
1542
1775
  const branch = a.branch;
1543
- await notifyAgentRead(agentAccessGate, ctx, branch, p);
1776
+ // The guide's name with no file of the knowledge base's own under it —
1777
+ // or one the caller may not read, or a copy of the guide an earlier
1778
+ // release wrote, none of which `read_file` serves: what a read answers
1779
+ // there is the platform's guide, so stat says a text file is there to
1780
+ // read — ungated, like the read — and that nothing can be moved,
1781
+ // deleted or written at it through these tools. The three cases get
1782
+ // ONE answer on purpose: a different one for the file the caller may
1783
+ // not read would tell them it exists. A file of the knowledge base's
1784
+ // own that the caller may read is a file like any other, and the
1785
+ // ordinary answer below describes it.
1786
+ let noted = false;
1787
+ if (agentGuide && isAgentGuidePath(toKbRelative(p, kbDirName) ?? '')) {
1788
+ const fs = await ctx.getFilesystem(branch);
1789
+ const readable = await ownGuideReadable(fs, branch, ctx, p);
1790
+ // Telling the organisation's own file from a stale copy READS it, so
1791
+ // the read hook hears of it as it hears of a read_file there — after
1792
+ // the gate, never before, and once (the ordinary stat below is told).
1793
+ if (readable) {
1794
+ await notifyAgentRead(agentAccessGate, ctx, branch, p);
1795
+ noted = true;
1796
+ }
1797
+ const own = readable && (await isOwnEntryStill(fs, p));
1798
+ if (!own) {
1799
+ const guide = await agentGuide();
1800
+ return {
1801
+ name: p.slice(p.lastIndexOf('/') + 1),
1802
+ type: 'file',
1803
+ size: Buffer.byteLength(guide, 'utf8'),
1804
+ managed: true,
1805
+ movable: false,
1806
+ deletable: false,
1807
+ access: { read: true, write: false, download: false, owner: false },
1808
+ contentMode: 'text',
1809
+ kind: 'text',
1810
+ mime: 'text/markdown',
1811
+ mimeSource: 'extension',
1812
+ textEditable: false,
1813
+ platformGuide: true,
1814
+ };
1815
+ }
1816
+ }
1817
+ if (!noted)
1818
+ await notifyAgentRead(agentAccessGate, ctx, branch, p);
1544
1819
  await assertCanRead(readGateFor(branch, ctx), p);
1545
1820
  // Nothing there is a 404, and the placeholder — never content — gets
1546
1821
  // exactly that answer: the one every file tool gives (see not-found.ts).
@@ -1571,7 +1846,11 @@ uploads) {
1571
1846
  // `mime` below, so it is never passed through.
1572
1847
  delete stat.mimeType;
1573
1848
  const kind = stat.type === 'directory' ? 'folder' : 'file';
1574
- const managed = managedReason(await onDiskSpelling(root, p), kind) !== undefined;
1849
+ const onDisk = await onDiskSpelling(root, p);
1850
+ const managed = managedReason(onDisk, kind) !== undefined;
1851
+ // A nested `access.md` is managed — it never moves — yet deleted by
1852
+ // whoever may write it, so a FILE's delete is judged on its own rule.
1853
+ const undeletable = kind === 'file' ? fileDeleteRefusal(onDisk) !== undefined : managed;
1575
1854
  const verdicts = await accessAt(branch, ctx, p);
1576
1855
  const access = a.explainAccess === true ? { ...verdicts, ...(await explainAccessAt(branch, ctx, p, kind)) } : verdicts;
1577
1856
  const link = !plain || viaLink !== undefined;
@@ -1590,7 +1869,7 @@ uploads) {
1590
1869
  // `movable`/`deletable` false rather than judging part of a folder
1591
1870
  // and calling it the whole (`delete_folder`'s dry run, which walks
1592
1871
  // uncapped, remains the authority for a folder that large).
1593
- const decided = managed || link;
1872
+ const decided = (managed && undeletable) || link;
1594
1873
  const { files, links, truncated } = kind === 'folder'
1595
1874
  ? await filesUnder(fs, p, DESCENDANTS_CAP)
1596
1875
  : { files: [p], links: [], truncated: false };
@@ -1611,9 +1890,9 @@ uploads) {
1611
1890
  const out = {
1612
1891
  ...stat,
1613
1892
  managed,
1614
- movable: open,
1893
+ movable: open && !managed,
1615
1894
  // delete_folder also refuses a folder holding a link.
1616
- deletable: open && links.length === 0,
1895
+ deletable: open && !undeletable && links.length === 0,
1617
1896
  access,
1618
1897
  };
1619
1898
  if (kind === 'folder') {
@@ -1643,7 +1922,7 @@ uploads) {
1643
1922
  mount({
1644
1923
  name: 'grep',
1645
1924
  gated: true,
1646
- description: 'Regex content search across the workspace. Returns `{ matches: [{ path, line, text }] }` (capped). Use to find where something is defined/referenced. `path` may name a DIRECTORY (searches the subtree) or a single FILE (searches just that file); a path with nothing at it is an error, never an empty result. Searches INSIDE Office and OpenDocument files (.docx/.pptx/.xlsx, .odt/.odp/.ods), PDFs and email files (.eml/.msg) via their extracted text — matches there carry the extraction\'s line numbers, and the `[slide N]`/`[sheet: Name]`/`[page N]`/`[from]`/`[subject]` marker lines locate them; a bounded number of not-yet-extracted documents is extracted per call, and the result notes how many were skipped (re-run to cover them).',
1925
+ description: 'Regex search across the workspace. Returns `{ matches: [{ path, line, text }] }` (capped). Use to find where something is defined/referenced. `path` may name a DIRECTORY (searches the subtree) or a single FILE (searches just that file); a path with nothing at it is an error, never an empty result. Searches INSIDE Office and OpenDocument files (.docx/.pptx/.xlsx, .odt/.odp/.ods), PDFs and email files (.eml/.msg) via their extracted text — matches there carry the extraction\'s line numbers, and the `[slide N]`/`[sheet: Name]`/`[page N]`/`[from]`/`[subject]` marker lines locate them; a bounded number of not-yet-extracted documents is extracted per call, and the result notes how many were skipped (re-run to cover them).',
1647
1926
  inputs: {
1648
1927
  type: 'object',
1649
1928
  properties: {
@@ -1694,10 +1973,6 @@ uploads) {
1694
1973
  // (an empty path is the handler's to explain), and here it would
1695
1974
  // otherwise name the workspace directory by another spelling.
1696
1975
  const searchRoot = typeof a.path === 'string' && a.path.length > 0 ? a.path : kbDirName;
1697
- // The search root itself goes to the read hook here; each file the walk
1698
- // actually opens goes to it per-file below, so a hook sees every path a
1699
- // grep reached rather than only the root it started from.
1700
- await notifyAgentRead(agentAccessGate, ctx, a.branch, searchRoot);
1701
1976
  const fs = await ctx.getFilesystem(a.branch);
1702
1977
  const gate = readGateFor(a.branch, ctx);
1703
1978
  const out = [];
@@ -1714,7 +1989,41 @@ uploads) {
1714
1989
  : await searchRootKind(fs, searchRoot);
1715
1990
  /** Why a single-file search found nothing, when "no matches" would be a lie. */
1716
1991
  let fileNote;
1717
- if (kind === 'directory') {
1992
+ // The guide is searched where it is read: a search of the repository
1993
+ // root covers it, and a search of its own path is a search of what
1994
+ // `read_file` answers there. The composed text is what is searched —
1995
+ // the knowledge base's own readable file first, then the platform's
1996
+ // guide — under the guide's path and with the line numbers a read of
1997
+ // it gives. The walk leaves that one file to this: a match the walk
1998
+ // made there would be the same line again, and one it COUNTED against
1999
+ // `max_results` would be a file later in the tree never searched while
2000
+ // the answer says nothing was cut. A file the caller may not read is
2001
+ // absent from it, as it is from the read.
2002
+ const guidePath = `${kbDirName}/${AGENT_GUIDE_FILE}`;
2003
+ const rel = toKbRelative(searchRoot, kbDirName);
2004
+ const coversGuide = agentGuide !== undefined && (kind === 'directory' ? rel === null : isAgentGuidePath(rel ?? ''));
2005
+ // The search root goes to the read hook — once, and only when it is
2006
+ // repository content: a folder, or a file of the knowledge base's own.
2007
+ // The guide's own path is not told here, because `guideAt` tells the
2008
+ // hook of the organisation's file there exactly as `read_file` does
2009
+ // (after the gate), and the platform's guide alone is nobody's file to
2010
+ // note. Each file the walk opens goes to the hook per file below, so a
2011
+ // hook sees every path a grep reached rather than only its root.
2012
+ if (!(coversGuide && kind !== 'directory')) {
2013
+ await notifyAgentRead(agentAccessGate, ctx, a.branch, searchRoot);
2014
+ }
2015
+ if (coversGuide) {
2016
+ if (kind === 'directory') {
2017
+ await grepWalk(fs, searchRoot, re, out, max, 0, gate, (p) => notifyAgentRead(agentAccessGate, ctx, a.branch, p), docs, (p) => p === guidePath);
2018
+ }
2019
+ const lines = (await guideAt(a.branch, ctx, guidePath)).split('\n');
2020
+ for (let i = 0; i < lines.length && out.length < max; i++) {
2021
+ re.lastIndex = 0;
2022
+ if (re.test(lines[i]))
2023
+ out.push({ path: guidePath, line: i + 1, text: lines[i].slice(0, 300) });
2024
+ }
2025
+ }
2026
+ else if (kind === 'directory') {
1718
2027
  await grepWalk(fs, searchRoot, re, out, max, 0, gate, (p) => notifyAgentRead(agentAccessGate, ctx, a.branch, p), docs);
1719
2028
  }
1720
2029
  else {
@@ -1754,6 +2063,8 @@ uploads) {
1754
2063
  // ── writes (through the lock/commit pipeline) ───────────────────────────
1755
2064
  mount({
1756
2065
  name: 'write_file',
2066
+ // A mode that is not one of the three answers `bad_mode`, which lists them.
2067
+ refusesItself: ['mode'],
1757
2068
  gated: true,
1758
2069
  description: 'Write a workspace TEXT file. The change is committed + pushed as you. Returns `{ path, bytes, outcome }`, where `outcome` is ' +
1759
2070
  '`created`, `replaced` or `updated`.' +
@@ -1826,6 +2137,8 @@ uploads) {
1826
2137
  });
1827
2138
  mount({
1828
2139
  name: 'write_files',
2140
+ // A mode that is not one of the three answers `bad_mode`, which lists them.
2141
+ refusesItself: ['mode'],
1829
2142
  gated: true,
1830
2143
  description: 'Batch-write many files in ONE commit — far faster than calling write_file once per file when ' +
1831
2144
  'creating many files at once (e.g. seeding a knowledge base). Each entry is `{ path, content }`, and the files it ' +
@@ -1886,9 +2199,11 @@ uploads) {
1886
2199
  proposable: true,
1887
2200
  handler: async (a, ctx) => {
1888
2201
  const files = a.files ?? [];
2202
+ // The mode is judged before anything else, so an empty batch with a mode
2203
+ // that is not one answers `bad_mode` like any other call would.
2204
+ const mode = modeOf(a);
1889
2205
  if (files.length === 0)
1890
2206
  return { count: 0, files: [] };
1891
- const mode = modeOf(a);
1892
2207
  // The POLICY gate still judges the whole batch: a restricted run is a
1893
2208
  // call that should not have been made at all, not a per-path outcome.
1894
2209
  // The write hook is asked PER PATH, below, so a path it refuses is that
@@ -2113,11 +2428,9 @@ uploads) {
2113
2428
  throw new ToolError(`"${path}" is a folder, not a file — use delete_folder to delete it and the files under it.`, 400);
2114
2429
  }
2115
2430
  const onDisk = await onDiskSpelling(root, path);
2116
- if (isGitMetadata(onDisk))
2117
- throw new ToolError(managedReason(onDisk, 'file'), 400);
2118
- if (managedReason(onDisk, 'file') !== undefined) {
2119
- throw new ToolError(`${onDisk.slice(onDisk.lastIndexOf('/') + 1)} is a platform file and cannot be deleted through the agent tools.`, 400);
2120
- }
2431
+ const refused = fileDeleteRefusal(onDisk);
2432
+ if (refused !== undefined)
2433
+ throw new ToolError(refused, 400);
2121
2434
  await assertNoSymlinkOnPath(root, path, true);
2122
2435
  if ((await writeBlocked(branch, ctx, [path])).length > 0)
2123
2436
  throw await writeRefusal(branch, path);
@@ -2299,12 +2612,13 @@ uploads) {
2299
2612
  gated: true,
2300
2613
  // A plain string again: what refuses a move names the guide, and that is in
2301
2614
  // the shared rules now, which are rebuilt from the layout where they live.
2302
- description: 'Move or rename a workspace FILE or FOLDER; a folder moves recursively, with everything under it. `dest` is the full new path, not the folder to move into. Lands as a delete + create, committed + pushed as you. ' +
2615
+ description: 'Move or rename a workspace FILE or FOLDER; a folder moves recursively, with everything under it. `dest` is the full new path, not the folder to move into. Committed + pushed as you. ' +
2303
2616
  'The destination must not exist — a move never overwrites a file or merges into a folder. Access follows the ' +
2304
2617
  'DESTINATION folder, so a move can change what you (and others) may do with the file: the dry run answers ' +
2305
2618
  '`{ src, dest, kind, descendants, access: { before, after }, accessChanges, allowed, reason? }`, where `access` is your ' +
2306
2619
  'own `{ read, write, download, owner }` at the source and at the destination AS IT WILL BE once the move has landed, ' +
2307
- 'with every `access.md` inside a moved folder counted at its new place, and a move whose `accessChanges` is true wants `confirm: true`.',
2620
+ 'with every `access.md` inside a moved folder counted at its new place, and a move whose `accessChanges` is true wants `confirm: true`. ' +
2621
+ 'Links are rewritten by default, in one commit with the move: those in the moved files and those in other markdown files pointing at them (`links` reports them). `rewriteLinks: false` turns that off. Path mentions in plain prose or code are never changed.',
2308
2622
  inputs: {
2309
2623
  type: 'object',
2310
2624
  properties: {
@@ -2313,6 +2627,7 @@ uploads) {
2313
2627
  dest: wsPath(kbDirName, 'Destination path — the full new path; must not exist yet'),
2314
2628
  dryRun: { type: 'boolean', description: 'Answer with the impact and change nothing.' },
2315
2629
  confirm: { type: 'boolean', description: 'Required when the move changes your access. Set it only after a dry run.' },
2630
+ rewriteLinks: { type: 'boolean', description: 'Rewrite the links into, out of and between the moved files (default true). `false` moves without touching any link.' },
2316
2631
  sessionId: SESSION_ID_INPUT,
2317
2632
  },
2318
2633
  required: ['branch', 'src', 'dest'],
@@ -2333,6 +2648,11 @@ uploads) {
2333
2648
  confirmationRequired: { type: 'boolean', description: 'True when the call stopped for want of `confirm: true`.' },
2334
2649
  message: str('One sentence on what happened (or did not).'),
2335
2650
  moved: { type: 'boolean', description: 'True once the move landed.' },
2651
+ links: {
2652
+ type: 'object',
2653
+ description: 'The link rewrite, the same on a dry run and on the move: `{ filesEdited, linksRewritten, edits: [{ path, from, to }] (the first 100), ' +
2654
+ 'notRewritten: [{ path, reason, links }], unsearched? }`. Absent with `rewriteLinks: false`.',
2655
+ },
2336
2656
  },
2337
2657
  required: ['src', 'dest', 'moved'],
2338
2658
  },
@@ -2423,6 +2743,52 @@ uploads) {
2423
2743
  : occupiedBy !== null
2424
2744
  ? entryExistsMessage(occupiedBy, dest)
2425
2745
  : undefined;
2746
+ // The links are planned only for a move that may run: a refused one
2747
+ // reads no page. The plan is the same on a dry run and on the move.
2748
+ const rewriteLinks = a.rewriteLinks !== false;
2749
+ const linkPlan = rewriteLinks && reason === undefined
2750
+ ? await planMoveLinks({
2751
+ src,
2752
+ dest,
2753
+ branch,
2754
+ kbDirName,
2755
+ allFiles: await (async () => {
2756
+ const { files, links } = await filesUnder(fs, kbDirName);
2757
+ const symlinks = new Set(links);
2758
+ return files.filter((f) => !symlinks.has(f));
2759
+ })(),
2760
+ canRead: (paths) => resolveReadableMap((wid, email, rels) => accessControl.canReadBatch(wid, email, rels), workspaceIdForBranch(branch), ctx.user.email, kbDirName, paths),
2761
+ writeBlocked: (paths) => writeBlocked(branch, ctx, paths),
2762
+ // Through the guarded filesystem the tools read with, never the raw
2763
+ // disk: a page replaced by a link since the listing is refused there
2764
+ // instead of read through to wherever the link points.
2765
+ readText: async (p) => String(await fs.readFile(p, { encoding: 'utf8' })),
2766
+ // The read hook, for every page the answer would NAME — an edited
2767
+ // one, one left with its links listed — and for no page merely
2768
+ // searched: naming is the disclosure, and the hook's refusal makes
2769
+ // the page one the caller cannot read, covered by the one sentence.
2770
+ readRefused: async (path) => {
2771
+ try {
2772
+ await notifyAgentRead(agentAccessGate, ctx, branch, path);
2773
+ return false;
2774
+ }
2775
+ catch {
2776
+ return true;
2777
+ }
2778
+ },
2779
+ // The write hook, for a page the move would edit, at its post-move path.
2780
+ writeRefusal: async (_lockAt, path) => {
2781
+ try {
2782
+ await assertAgentWriteAllowed(agentAccessGate, ctx, branch, path);
2783
+ return null;
2784
+ }
2785
+ catch (err) {
2786
+ return `refused: ${err instanceof Error ? err.message : String(err)}`;
2787
+ }
2788
+ },
2789
+ })
2790
+ : undefined;
2791
+ const moveReason = reason ?? linkPlan?.overCap;
2426
2792
  const impact = {
2427
2793
  src,
2428
2794
  dest,
@@ -2430,8 +2796,9 @@ uploads) {
2430
2796
  descendants,
2431
2797
  access: { before, after },
2432
2798
  accessChanges,
2433
- allowed: reason === undefined,
2434
- ...(reason !== undefined ? { reason } : {}),
2799
+ allowed: moveReason === undefined,
2800
+ ...(moveReason !== undefined ? { reason: moveReason } : {}),
2801
+ ...(linkPlan ? { links: linkPlan.report } : {}),
2435
2802
  };
2436
2803
  if (a.dryRun === true)
2437
2804
  return { ...impact, dryRun: true, moved: false };
@@ -2446,6 +2813,8 @@ uploads) {
2446
2813
  }
2447
2814
  if (collision)
2448
2815
  throw new ToolError(reason, 409);
2816
+ if (linkPlan?.overCap !== undefined)
2817
+ throw new ToolError(linkPlan.overCap, 400);
2449
2818
  if (accessChanges && a.confirm !== true) {
2450
2819
  return {
2451
2820
  ...impact,
@@ -2458,7 +2827,42 @@ uploads) {
2458
2827
  // no-replace move is what guarantees it. A destination created between
2459
2828
  // the two — by another agent, or by the sidebar, which moves without
2460
2829
  // taking this lock — comes back here as a refusal, not an overwrite.
2461
- await asEntryExists(() => fs.moveFile(src, dest));
2830
+ if (linkPlan === undefined) {
2831
+ await asEntryExists(() => fs.moveFile(src, dest));
2832
+ }
2833
+ else {
2834
+ // The move and every link edit, one commit. Under the locks, each page
2835
+ // is checked to still hold the bytes its edit was computed from.
2836
+ const edits = linkPlan.edits.map((e) => ({ path: e.path, lockAt: e.lockAt, content: e.content }));
2837
+ const check = async () => {
2838
+ for (const e of linkPlan.edits) {
2839
+ const now = await fs.readFile(e.lockAt, { encoding: 'utf8' }).then(String, () => null);
2840
+ if (now !== e.original) {
2841
+ throw new ToolError(`"${e.lockAt}" changed while the move was being planned, so nothing was moved. Run the move again.`, 409);
2842
+ }
2843
+ }
2844
+ };
2845
+ const summary = `Move ${src} to ${dest}`.slice(0, 200);
2846
+ const locking = fs;
2847
+ try {
2848
+ if (typeof locking.moveWithEdits === 'function') {
2849
+ await asEntryExists(() => locking.moveWithEdits(src, dest, edits, summary, check));
2850
+ }
2851
+ else {
2852
+ // A filesystem without the one-commit move (none the agent is
2853
+ // handed for writing): the move, then each edit.
2854
+ await check();
2855
+ await asEntryExists(() => fs.moveFile(src, dest));
2856
+ for (const e of edits)
2857
+ await fs.writeFile(e.path, e.content);
2858
+ }
2859
+ }
2860
+ catch (err) {
2861
+ if (err instanceof MoveLockedError || err instanceof MoveRacedError)
2862
+ throw new ToolError(err.message, 409);
2863
+ throw err;
2864
+ }
2865
+ }
2462
2866
  // Moving the last file — or a whole folder — out leaves the folder it
2463
2867
  // came from in place, like a delete.
2464
2868
  await keepFolderOf(fs, ctx, branch, src, kbDirName);
@@ -2872,6 +3276,8 @@ uploads) {
2872
3276
  });
2873
3277
  mount({
2874
3278
  name: 'apply_file_upload',
3279
+ // A mode that is not one of the three answers `bad_mode`, which lists them.
3280
+ refusesItself: ['mode'],
2875
3281
  gated: true,
2876
3282
  description: 'Land a file you have already uploaded (see `request_file_upload`) in a folder on a branch, in ONE commit, as you. ' +
2877
3283
  'A single file lands under the name it was sent with; a zip lands as its entries, keeping their folder structure. ' +
@@ -2934,6 +3340,169 @@ uploads) {
2934
3340
  handler: async (a, ctx) => applyFileUpload(a, ctx, uploads),
2935
3341
  });
2936
3342
  }
3343
+ // ── downloads (bytes that never pass through the model, the other way) ──
3344
+ // The twin of the upload pair: `read_file` answers content INTO the
3345
+ // conversation, so an agent that needs exact bytes on its own disk had no
3346
+ // way to get them. `request_file_download` judges every file on its own,
3347
+ // captures the ones that pass, and answers a one-time link per file (and a
3348
+ // zip per requested folder) that any HTTP client can fetch.
3349
+ /** The type a file's link answers with: the reader's, as `file_stat` reports it — but never active content. */
3350
+ const downloadContentType = (p, bytes) => {
3351
+ const reader = readers.readerFor(p);
3352
+ const mime = fileTypeOf(reader, p, needsContent(reader) ? bytes : undefined).mime;
3353
+ // SVG and HTML run scripts wherever they are opened — saved to disk and
3354
+ // re-opened under `file://`, too — so they go out as bytes, as the app's
3355
+ // own Download button sends them.
3356
+ return ACTIVE_CONTENT_TYPES.has(mime) ? 'application/octet-stream' : mime;
3357
+ };
3358
+ if (downloads) {
3359
+ mount({
3360
+ name: 'request_file_download',
3361
+ gated: true,
3362
+ description: 'Copy knowledge-base files onto your own disk without their content passing through the conversation — the ' +
3363
+ 'way out, as `request_file_upload` is the way in. Give `branch` and `paths` (files or folders). Every file, ' +
3364
+ 'each one inside a folder too, is included only if you may read AND download that file. Returns ' +
3365
+ '`{ expiresAt, expiresInSeconds, files: [{ path, bytes, sha256, downloadUrl }], folders: [{ path, bytes, ' +
3366
+ 'downloadUrl, files }], refused: [{ path, reason }] }`: a link per file, with its own content type, and per ' +
3367
+ `folder a zip at full repository paths (\`apply_file_upload\` it at \`${kbDirName}/\` to put every file back). ` +
3368
+ 'Fetch with any HTTP client (`curl -o <name> "<downloadUrl>"`, or the address without its last segment and an ' +
3369
+ '`x-download-token` header). Each link works ONCE, for 15 minutes, and serves the files as they are now. ' +
3370
+ 'Refused: `not found` (missing or named-but-unreadable; folders omit unreadable files), ' +
3371
+ '`download permission required`; no link when nothing is included. At most 500 MB per request.',
3372
+ inputs: {
3373
+ type: 'object',
3374
+ properties: {
3375
+ branch: BRANCH_INPUT,
3376
+ paths: {
3377
+ type: 'array',
3378
+ minItems: 1,
3379
+ items: { type: 'string' },
3380
+ description: `Files and folders to download, under \`${kbDirName}/\` (e.g. \`${kbDirName}/KnowledgeBase/Foo.md\`), with or without a leading slash — a path without that prefix is placed under \`${kbDirName}/\`.`,
3381
+ },
3382
+ sessionId: SESSION_ID_INPUT,
3383
+ },
3384
+ required: ['branch', 'paths'],
3385
+ additionalProperties: false,
3386
+ },
3387
+ outputs: {
3388
+ type: 'object',
3389
+ properties: {
3390
+ expiresAt: {
3391
+ type: ['string', 'null'],
3392
+ description: 'ISO-8601 instant after which every link of this answer is gone; null when no link was issued.',
3393
+ },
3394
+ expiresInSeconds: int('Seconds until `expiresAt`; 0 when no link was issued.'),
3395
+ files: {
3396
+ type: 'array',
3397
+ description: 'One entry per included file, each named once however many times it was asked for.',
3398
+ items: {
3399
+ type: 'object',
3400
+ properties: {
3401
+ path: str('The workspace path.'),
3402
+ bytes: int('Size in bytes.'),
3403
+ sha256: str('SHA-256 of the bytes the link serves, hex.'),
3404
+ downloadUrl: str('One-time link to the file itself.'),
3405
+ },
3406
+ required: ['path', 'bytes', 'sha256', 'downloadUrl'],
3407
+ },
3408
+ },
3409
+ folders: {
3410
+ type: 'array',
3411
+ description: 'One entry per requested folder that kept at least one file.',
3412
+ items: {
3413
+ type: 'object',
3414
+ properties: {
3415
+ path: str('The folder, as requested.'),
3416
+ bytes: int('Uncompressed total of the files in its zip.'),
3417
+ downloadUrl: str('One-time link to the zip.'),
3418
+ files: { type: 'array', items: { type: 'string' }, description: 'The workspace paths the zip holds.' },
3419
+ },
3420
+ required: ['path', 'bytes', 'downloadUrl', 'files'],
3421
+ },
3422
+ },
3423
+ refused: {
3424
+ type: 'array',
3425
+ description: 'Every path left out, with why.',
3426
+ items: {
3427
+ type: 'object',
3428
+ properties: { path: str('The path left out.'), reason: str('Why: `not found`, `download permission required`, or the deployment\'s own words.') },
3429
+ required: ['path', 'reason'],
3430
+ },
3431
+ },
3432
+ },
3433
+ required: ['expiresAt', 'expiresInSeconds', 'files', 'folders', 'refused'],
3434
+ },
3435
+ // A download is a READ: a read-only deployment still serves it. A
3436
+ // read-only CREDENTIAL may not take bytes out, though.
3437
+ write: false,
3438
+ writeScope: true,
3439
+ handler: async (a, ctx) => requestFileDownload(a, ctx, downloads),
3440
+ });
3441
+ }
3442
+ /** `request_file_download`'s handler: judge, capture, and issue the links. */
3443
+ async function requestFileDownload(a, ctx, store) {
3444
+ const branch = a.branch;
3445
+ const raw = a.paths;
3446
+ if (!Array.isArray(raw) || raw.length === 0 || raw.some((p) => typeof p !== 'string' || p.trim() === '')) {
3447
+ throw new ToolError('Name at least one path to download in `paths`: an array of file and folder paths.', 400);
3448
+ }
3449
+ // At the cap, say so before a branch is resolved; the slot below counts again.
3450
+ store.assertCanIssue(ctx.user);
3451
+ // Resolves (clones, if need be) the branch's workspace, as a read does —
3452
+ // so an unknown branch is the call's refusal, not every path's.
3453
+ await ctx.getFilesystem(branch);
3454
+ const workspaceId = workspaceIdForBranch(branch);
3455
+ const refusedSpelling = [];
3456
+ const requested = [];
3457
+ for (const p of raw) {
3458
+ try {
3459
+ // As written: a name may begin or end with a space, and trimming it
3460
+ // would ask for another file. A trailing slash is the one spelling
3461
+ // folded, so `Shared/` and `Shared` are one request, not two zips.
3462
+ requested.push(normalizeWorkspacePath(p, kbDirName).replace(/\/+$/, ''));
3463
+ }
3464
+ catch (err) {
3465
+ if (!hasHttpStatus(err))
3466
+ throw err;
3467
+ refusedSpelling.push({ path: p, reason: err.message });
3468
+ }
3469
+ }
3470
+ // A slot is taken before anything is built (a caller at the cap is told
3471
+ // so at once) and given back when nothing is issued.
3472
+ return store.withRequestSlot(ctx.user, async (issue) => {
3473
+ const built = await buildDownload(requested, {
3474
+ kbDirName,
3475
+ maxBytes: ZIP_DOWNLOAD_MAX_BYTES,
3476
+ maxFiles: DOWNLOAD_MAX_FILES,
3477
+ candidatesAt: (p) => ctx.workspaceService.downloadCandidatesAt(workspaceId, p),
3478
+ canReadBatch: (paths) => accessControl.canReadBatch(workspaceId, ctx.user.email, paths),
3479
+ canDownloadBatch: (paths) => accessControl.canDownloadBatch(workspaceId, ctx.user.email, paths),
3480
+ // The folder-level gate the app's zip route applies, before the files.
3481
+ canDownloadFolder: (p) => accessControl.canDownload(workspaceId, ctx.user.email, toKbRelative(p, kbDirName) ?? p),
3482
+ notifyRead: (p) => notifyAgentRead(agentAccessGate, ctx, branch, p),
3483
+ readFile: (p) => ctx.workspaceService.readFileBinary(workspaceId, p),
3484
+ contentTypeOf: downloadContentType,
3485
+ });
3486
+ const refused = [...refusedSpelling, ...built.refused];
3487
+ if (built.artifacts.length === 0) {
3488
+ return { expiresAt: null, expiresInSeconds: 0, files: [], folders: [], refused };
3489
+ }
3490
+ const issued = await issue(built.artifacts);
3491
+ const urls = issued.downloadUrls;
3492
+ return {
3493
+ expiresAt: issued.expiresAt,
3494
+ expiresInSeconds: issued.expiresInSeconds,
3495
+ files: built.files.map((f, i) => ({ ...f, downloadUrl: urls[i] })),
3496
+ folders: built.folders.map((f, i) => ({
3497
+ path: f.path,
3498
+ bytes: f.bytes,
3499
+ downloadUrl: urls[built.files.length + i],
3500
+ files: f.files,
3501
+ })),
3502
+ refused,
3503
+ };
3504
+ });
3505
+ }
2937
3506
  // ── shell (internal-only) ───────────────────────────────────────────────
2938
3507
  mount({
2939
3508
  name: 'execute_command',
@@ -3116,5 +3685,7 @@ uploads) {
3116
3685
  });
3117
3686
  },
3118
3687
  });
3688
+ // The one read `open_page` borrows — see `readForTool` above.
3689
+ return { readForTool };
3119
3690
  }
3120
3691
  //# sourceMappingURL=workspace.tools.js.map