@rubytech/create-maxy-code 0.1.539 → 0.1.540

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 (261) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/agent-slug/dist/index.d.ts +31 -0
  3. package/payload/platform/lib/agent-slug/dist/index.d.ts.map +1 -0
  4. package/payload/platform/lib/agent-slug/dist/index.js +53 -0
  5. package/payload/platform/lib/agent-slug/dist/index.js.map +1 -0
  6. package/payload/platform/lib/agent-slug/src/index.ts +53 -0
  7. package/payload/platform/lib/agent-slug/tsconfig.json +8 -0
  8. package/payload/platform/package.json +2 -2
  9. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  10. package/payload/platform/plugins/admin/PLUGIN.md +33 -1
  11. package/payload/platform/plugins/admin/mcp/dist/index.js +136 -0
  12. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/admin/mcp/dist/tools/agent-config-schema.d.ts +9 -0
  14. package/payload/platform/plugins/admin/mcp/dist/tools/agent-config-schema.d.ts.map +1 -0
  15. package/payload/platform/plugins/admin/mcp/dist/tools/agent-config-schema.js +49 -0
  16. package/payload/platform/plugins/admin/mcp/dist/tools/agent-config-schema.js.map +1 -0
  17. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.d.ts +64 -0
  18. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.d.ts.map +1 -0
  19. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.js +151 -0
  20. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.js.map +1 -0
  21. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +81 -13
  22. package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +36 -20
  23. package/payload/platform/plugins/admin/skills/update-knowledge/SKILL.md +4 -3
  24. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +7 -0
  25. package/payload/platform/plugins/docs/references/admin-ui.md +47 -0
  26. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +31 -10
  27. package/payload/platform/plugins/docs/references/internals.md +2 -2
  28. package/payload/platform/plugins/whatsapp/PLUGIN.md +2 -2
  29. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +6 -1
  30. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  31. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +3 -1
  32. package/payload/platform/scripts/__tests__/check-plugin-tool-declarations.test.ts +51 -0
  33. package/payload/platform/scripts/check-plugin-tool-declarations.mjs +97 -0
  34. package/payload/platform/scripts/check-risk-class.mjs +5 -0
  35. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  36. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +8 -0
  37. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  38. package/payload/platform/services/claude-session-manager/dist/channel-targets.d.ts +3 -0
  39. package/payload/platform/services/claude-session-manager/dist/channel-targets.d.ts.map +1 -1
  40. package/payload/platform/services/claude-session-manager/dist/channel-targets.js.map +1 -1
  41. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  42. package/payload/platform/services/claude-session-manager/dist/http-server.js +20 -1
  43. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  44. package/payload/platform/services/claude-session-manager/dist/index.js +35 -18
  45. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  46. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  47. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +57 -8
  48. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  49. package/payload/platform/services/claude-session-manager/dist/public-knowledge-audit.d.ts +58 -0
  50. package/payload/platform/services/claude-session-manager/dist/public-knowledge-audit.d.ts.map +1 -0
  51. package/payload/platform/services/claude-session-manager/dist/public-knowledge-audit.js +152 -0
  52. package/payload/platform/services/claude-session-manager/dist/public-knowledge-audit.js.map +1 -0
  53. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +4 -0
  54. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
  55. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +3 -0
  56. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
  57. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +40 -3
  58. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  59. package/payload/platform/services/whatsapp-channel/dist/notification.js +43 -4
  60. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  61. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  62. package/payload/platform/services/whatsapp-channel/dist/server.js +41 -25
  63. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  64. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +1 -0
  65. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  66. package/payload/platform/services/whatsapp-channel/dist/targets.js +9 -5
  67. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  68. package/payload/server/{chunk-BP7LH7MC.js → chunk-CKTHAHL4.js} +26 -16
  69. package/payload/server/{chunk-IALJPXIO.js → chunk-ERIM3EFB.js} +1 -1
  70. package/payload/server/{chunk-S4FIEM3L.js → chunk-TQTZXAK3.js} +64 -7
  71. package/payload/server/{manager-5QV2EAZ2.js → manager-F6NXSOJW.js} +2 -2
  72. package/payload/server/maxy-edge.js +2 -2
  73. package/payload/server/public/activity.html +5 -5
  74. package/payload/server/public/agents.html +4 -4
  75. package/payload/server/public/assets/AdminLoginScreens-DuFCjbz2.js +1 -0
  76. package/payload/server/public/assets/AdminLoginScreens-DuFCjbz2.js.br +0 -0
  77. package/payload/server/public/assets/AdminLoginScreens-DuFCjbz2.js.gz +0 -0
  78. package/payload/server/public/assets/AdminShell-DJL5DzRX.js +2 -0
  79. package/payload/server/public/assets/AdminShell-DJL5DzRX.js.br +0 -0
  80. package/payload/server/public/assets/AdminShell-DJL5DzRX.js.gz +0 -0
  81. package/payload/server/public/assets/activity-CpMnNr5c.js +1 -0
  82. package/payload/server/public/assets/activity-CpMnNr5c.js.br +0 -0
  83. package/payload/server/public/assets/activity-CpMnNr5c.js.gz +0 -0
  84. package/payload/server/public/assets/admin-CBwKIfaI.js +1 -0
  85. package/payload/server/public/assets/admin-CBwKIfaI.js.br +0 -0
  86. package/payload/server/public/assets/admin-CBwKIfaI.js.gz +0 -0
  87. package/payload/server/public/assets/agents-C4LmSKjb.js +1 -0
  88. package/payload/server/public/assets/agents-C4LmSKjb.js.br +0 -0
  89. package/payload/server/public/assets/agents-C4LmSKjb.js.gz +0 -0
  90. package/payload/server/public/assets/browser-Bm9xm_yc.js +1 -0
  91. package/payload/server/public/assets/browser-Bm9xm_yc.js.br +0 -0
  92. package/payload/server/public/assets/browser-Bm9xm_yc.js.gz +0 -0
  93. package/payload/server/public/assets/calendar-CNY9FTWe.js +1 -0
  94. package/payload/server/public/assets/calendar-CNY9FTWe.js.br +0 -0
  95. package/payload/server/public/assets/calendar-CNY9FTWe.js.gz +0 -0
  96. package/payload/server/public/assets/chat-3WoUGCTd.js +1 -0
  97. package/payload/server/public/assets/chat-3WoUGCTd.js.br +0 -0
  98. package/payload/server/public/assets/chat-3WoUGCTd.js.gz +0 -0
  99. package/payload/server/public/assets/chevron-left-BE16BJnw.js +1 -0
  100. package/payload/server/public/assets/chevron-left-BE16BJnw.js.br +0 -0
  101. package/payload/server/public/assets/chevron-right-Bu2VKpeU.js +1 -0
  102. package/payload/server/public/assets/chevron-right-Bu2VKpeU.js.br +0 -0
  103. package/payload/server/public/assets/clock-C4Nu2aRV.js +1 -0
  104. package/payload/server/public/assets/clock-C4Nu2aRV.js.br +0 -0
  105. package/payload/server/public/assets/copy-3kj9Oaoc.js +1 -0
  106. package/payload/server/public/assets/copy-3kj9Oaoc.js.br +0 -0
  107. package/payload/server/public/assets/copy-3kj9Oaoc.js.gz +0 -0
  108. package/payload/server/public/assets/data-C-x5EHyi.js +1 -0
  109. package/payload/server/public/assets/data-C-x5EHyi.js.br +1 -0
  110. package/payload/server/public/assets/data-C-x5EHyi.js.gz +0 -0
  111. package/payload/server/public/assets/file-text-CBaewT76.js +1 -0
  112. package/payload/server/public/assets/file-text-CBaewT76.js.br +0 -0
  113. package/payload/server/public/assets/file-text-CBaewT76.js.gz +0 -0
  114. package/payload/server/public/assets/{graph-C8cQ6Nqf.js → graph-B8-e2_Z-.js} +3 -3
  115. package/payload/server/public/assets/graph-B8-e2_Z-.js.br +0 -0
  116. package/payload/server/public/assets/graph-B8-e2_Z-.js.gz +0 -0
  117. package/payload/server/public/assets/graph-labels-Bsc23eZU.js +1 -0
  118. package/payload/server/public/assets/graph-labels-Bsc23eZU.js.br +0 -0
  119. package/payload/server/public/assets/graph-labels-Bsc23eZU.js.gz +0 -0
  120. package/payload/server/public/assets/operator-Cve0-qfO.js +1 -0
  121. package/payload/server/public/assets/operator-Cve0-qfO.js.br +0 -0
  122. package/payload/server/public/assets/operator-Cve0-qfO.js.gz +0 -0
  123. package/payload/server/public/assets/page-BgVMs6Vf.js +1 -0
  124. package/payload/server/public/assets/page-BgVMs6Vf.js.br +0 -0
  125. package/payload/server/public/assets/page-BgVMs6Vf.js.gz +0 -0
  126. package/payload/server/public/assets/page-fABub-w8.js +32 -0
  127. package/payload/server/public/assets/page-fABub-w8.js.br +0 -0
  128. package/payload/server/public/assets/page-fABub-w8.js.gz +0 -0
  129. package/payload/server/public/assets/play-B6zXB9K7.js +1 -0
  130. package/payload/server/public/assets/play-B6zXB9K7.js.br +0 -0
  131. package/payload/server/public/assets/play-B6zXB9K7.js.gz +0 -0
  132. package/payload/server/public/assets/public--R2DbdWs.js +1 -0
  133. package/payload/server/public/assets/public--R2DbdWs.js.br +0 -0
  134. package/payload/server/public/assets/public--R2DbdWs.js.gz +0 -0
  135. package/payload/server/public/assets/rotate-ccw-FZW7c8p8.js +1 -0
  136. package/payload/server/public/assets/rotate-ccw-FZW7c8p8.js.br +0 -0
  137. package/payload/server/public/assets/rotate-ccw-FZW7c8p8.js.gz +0 -0
  138. package/payload/server/public/assets/routines-BpZs2SaT.js +2 -0
  139. package/payload/server/public/assets/routines-BpZs2SaT.js.br +0 -0
  140. package/payload/server/public/assets/routines-BpZs2SaT.js.gz +0 -0
  141. package/payload/server/public/assets/skills-DF7uyEEz.js +1 -0
  142. package/payload/server/public/assets/skills-DF7uyEEz.js.br +0 -0
  143. package/payload/server/public/assets/skills-DF7uyEEz.js.gz +0 -0
  144. package/payload/server/public/assets/tasks-BfJJmoWO.js +3 -0
  145. package/payload/server/public/assets/tasks-BfJJmoWO.js.br +0 -0
  146. package/payload/server/public/assets/tasks-BfJJmoWO.js.gz +0 -0
  147. package/payload/server/public/assets/triangle-alert-CR-jRJdI.js +1 -0
  148. package/payload/server/public/assets/triangle-alert-CR-jRJdI.js.br +0 -0
  149. package/payload/server/public/assets/triangle-alert-CR-jRJdI.js.gz +0 -0
  150. package/payload/server/public/assets/useCopyFeedback-IOuPUAHY.js +1 -0
  151. package/payload/server/public/assets/useCopyFeedback-IOuPUAHY.js.br +0 -0
  152. package/payload/server/public/assets/useCopyFeedback-IOuPUAHY.js.gz +0 -0
  153. package/payload/server/public/assets/{useSubAccountSwitcher-C7TtIbR3.js → useMediaQuery-CA6dR5iz.js} +1 -1
  154. package/payload/server/public/assets/useMediaQuery-CA6dR5iz.js.br +0 -0
  155. package/payload/server/public/assets/{useSubAccountSwitcher-C7TtIbR3.js.gz → useMediaQuery-CA6dR5iz.js.gz} +0 -0
  156. package/payload/server/public/assets/{useSubAccountSwitcher-DCkivCBS.css → useMediaQuery-DpwgV1Da.css} +1 -1
  157. package/payload/server/public/assets/useMediaQuery-DpwgV1Da.css.br +0 -0
  158. package/payload/server/public/assets/{useSubAccountSwitcher-DCkivCBS.css.gz → useMediaQuery-DpwgV1Da.css.gz} +0 -0
  159. package/payload/server/public/assets/useVoiceRecorder-DGhsN7nV.js +2 -0
  160. package/payload/server/public/assets/useVoiceRecorder-DGhsN7nV.js.br +0 -0
  161. package/payload/server/public/assets/useVoiceRecorder-DGhsN7nV.js.gz +0 -0
  162. package/payload/server/public/assets/wrench-CpCOYUBc.js +1 -0
  163. package/payload/server/public/assets/wrench-CpCOYUBc.js.br +0 -0
  164. package/payload/server/public/assets/wrench-CpCOYUBc.js.gz +0 -0
  165. package/payload/server/public/browser.html +4 -4
  166. package/payload/server/public/calendar.html +8 -8
  167. package/payload/server/public/chat.html +13 -13
  168. package/payload/server/public/data.html +11 -11
  169. package/payload/server/public/graph.html +9 -9
  170. package/payload/server/public/index.html +14 -14
  171. package/payload/server/public/operator.html +14 -14
  172. package/payload/server/public/public.html +13 -13
  173. package/payload/server/public/routines.html +6 -6
  174. package/payload/server/public/skills.html +5 -5
  175. package/payload/server/public/tasks.html +6 -6
  176. package/payload/server/server.js +329 -104
  177. package/payload/server/public/assets/AdminLoginScreens-BpHnjHp_.js +0 -1
  178. package/payload/server/public/assets/AdminLoginScreens-BpHnjHp_.js.br +0 -0
  179. package/payload/server/public/assets/AdminLoginScreens-BpHnjHp_.js.gz +0 -0
  180. package/payload/server/public/assets/AdminShell-D24evn4t.js +0 -2
  181. package/payload/server/public/assets/AdminShell-D24evn4t.js.br +0 -0
  182. package/payload/server/public/assets/AdminShell-D24evn4t.js.gz +0 -0
  183. package/payload/server/public/assets/activity-ZVUTB5uZ.js +0 -1
  184. package/payload/server/public/assets/activity-ZVUTB5uZ.js.br +0 -0
  185. package/payload/server/public/assets/activity-ZVUTB5uZ.js.gz +0 -0
  186. package/payload/server/public/assets/admin-XJqQz3QJ.js +0 -1
  187. package/payload/server/public/assets/admin-XJqQz3QJ.js.br +0 -0
  188. package/payload/server/public/assets/admin-XJqQz3QJ.js.gz +0 -0
  189. package/payload/server/public/assets/agents-Dc9SNC9N.js +0 -1
  190. package/payload/server/public/assets/agents-Dc9SNC9N.js.br +0 -0
  191. package/payload/server/public/assets/agents-Dc9SNC9N.js.gz +0 -0
  192. package/payload/server/public/assets/browser-CCedMIga.js +0 -1
  193. package/payload/server/public/assets/browser-CCedMIga.js.br +0 -0
  194. package/payload/server/public/assets/browser-CCedMIga.js.gz +0 -0
  195. package/payload/server/public/assets/calendar-BDoMLP2j.js +0 -1
  196. package/payload/server/public/assets/calendar-BDoMLP2j.js.br +0 -0
  197. package/payload/server/public/assets/calendar-BDoMLP2j.js.gz +0 -0
  198. package/payload/server/public/assets/chat-BAhoqTY9.js +0 -1
  199. package/payload/server/public/assets/chat-BAhoqTY9.js.br +0 -0
  200. package/payload/server/public/assets/chat-BAhoqTY9.js.gz +0 -0
  201. package/payload/server/public/assets/chevron-left-NGLHgPjF.js +0 -1
  202. package/payload/server/public/assets/chevron-right-B4B7OkG8.js +0 -1
  203. package/payload/server/public/assets/chevron-right-B4B7OkG8.js.br +0 -0
  204. package/payload/server/public/assets/clock-7IMwxJdH.js +0 -1
  205. package/payload/server/public/assets/clock-7IMwxJdH.js.br +0 -0
  206. package/payload/server/public/assets/clock-7IMwxJdH.js.gz +0 -0
  207. package/payload/server/public/assets/copy-PkR4EFD6.js +0 -1
  208. package/payload/server/public/assets/copy-PkR4EFD6.js.br +0 -0
  209. package/payload/server/public/assets/copy-PkR4EFD6.js.gz +0 -0
  210. package/payload/server/public/assets/data-yQYhw2IR.js +0 -1
  211. package/payload/server/public/assets/data-yQYhw2IR.js.br +0 -1
  212. package/payload/server/public/assets/data-yQYhw2IR.js.gz +0 -0
  213. package/payload/server/public/assets/file-text-BA9p7TLU.js +0 -1
  214. package/payload/server/public/assets/file-text-BA9p7TLU.js.br +0 -0
  215. package/payload/server/public/assets/file-text-BA9p7TLU.js.gz +0 -0
  216. package/payload/server/public/assets/graph-C8cQ6Nqf.js.br +0 -0
  217. package/payload/server/public/assets/graph-C8cQ6Nqf.js.gz +0 -0
  218. package/payload/server/public/assets/graph-labels-CiK412vE.js +0 -1
  219. package/payload/server/public/assets/graph-labels-CiK412vE.js.br +0 -0
  220. package/payload/server/public/assets/graph-labels-CiK412vE.js.gz +0 -0
  221. package/payload/server/public/assets/operator-CaQAEUBu.js +0 -1
  222. package/payload/server/public/assets/operator-CaQAEUBu.js.br +0 -0
  223. package/payload/server/public/assets/operator-CaQAEUBu.js.gz +0 -0
  224. package/payload/server/public/assets/page-CpJpyoPS.js +0 -32
  225. package/payload/server/public/assets/page-CpJpyoPS.js.br +0 -0
  226. package/payload/server/public/assets/page-CpJpyoPS.js.gz +0 -0
  227. package/payload/server/public/assets/page-D0ShNyGE.js +0 -1
  228. package/payload/server/public/assets/page-D0ShNyGE.js.br +0 -0
  229. package/payload/server/public/assets/page-D0ShNyGE.js.gz +0 -0
  230. package/payload/server/public/assets/play-huZpq9ra.js +0 -1
  231. package/payload/server/public/assets/play-huZpq9ra.js.br +0 -0
  232. package/payload/server/public/assets/play-huZpq9ra.js.gz +0 -0
  233. package/payload/server/public/assets/public--8RWWGjF.js +0 -1
  234. package/payload/server/public/assets/public--8RWWGjF.js.br +0 -0
  235. package/payload/server/public/assets/public--8RWWGjF.js.gz +0 -0
  236. package/payload/server/public/assets/rotate-ccw-g7l4ZSR3.js +0 -1
  237. package/payload/server/public/assets/rotate-ccw-g7l4ZSR3.js.br +0 -0
  238. package/payload/server/public/assets/rotate-ccw-g7l4ZSR3.js.gz +0 -0
  239. package/payload/server/public/assets/routines-DzFcPNMb.js +0 -2
  240. package/payload/server/public/assets/routines-DzFcPNMb.js.br +0 -0
  241. package/payload/server/public/assets/routines-DzFcPNMb.js.gz +0 -0
  242. package/payload/server/public/assets/skills-5vl_Bt9U.js +0 -1
  243. package/payload/server/public/assets/skills-5vl_Bt9U.js.br +0 -0
  244. package/payload/server/public/assets/skills-5vl_Bt9U.js.gz +0 -0
  245. package/payload/server/public/assets/tasks-DQHtJ8Pq.js +0 -3
  246. package/payload/server/public/assets/tasks-DQHtJ8Pq.js.br +0 -0
  247. package/payload/server/public/assets/tasks-DQHtJ8Pq.js.gz +0 -0
  248. package/payload/server/public/assets/triangle-alert-Cy4Z0uFv.js +0 -1
  249. package/payload/server/public/assets/triangle-alert-Cy4Z0uFv.js.br +0 -0
  250. package/payload/server/public/assets/triangle-alert-Cy4Z0uFv.js.gz +0 -0
  251. package/payload/server/public/assets/useCopyFeedback-BwK1RhmF.js +0 -1
  252. package/payload/server/public/assets/useCopyFeedback-BwK1RhmF.js.br +0 -0
  253. package/payload/server/public/assets/useCopyFeedback-BwK1RhmF.js.gz +0 -0
  254. package/payload/server/public/assets/useSubAccountSwitcher-C7TtIbR3.js.br +0 -0
  255. package/payload/server/public/assets/useSubAccountSwitcher-DCkivCBS.css.br +0 -0
  256. package/payload/server/public/assets/useVoiceRecorder-DgyAGgmF.js +0 -2
  257. package/payload/server/public/assets/useVoiceRecorder-DgyAGgmF.js.br +0 -0
  258. package/payload/server/public/assets/useVoiceRecorder-DgyAGgmF.js.gz +0 -0
  259. package/payload/server/public/assets/wrench-CICORrSc.js +0 -1
  260. package/payload/server/public/assets/wrench-CICORrSc.js.br +0 -0
  261. package/payload/server/public/assets/wrench-CICORrSc.js.gz +0 -0
@@ -0,0 +1,9 @@
1
+ export type AgentConfigVerdict = {
2
+ ok: true;
3
+ } | {
4
+ ok: false;
5
+ field: string;
6
+ detail: string;
7
+ };
8
+ export declare function validateAgentConfig(config: unknown): AgentConfigVerdict;
9
+ //# sourceMappingURL=agent-config-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-config-schema.d.ts","sourceRoot":"","sources":["../../src/tools/agent-config-schema.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,kBAAkB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7F,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,kBAAkB,CAuCvE"}
@@ -0,0 +1,49 @@
1
+ import { isSelectableModel } from "../../../../../lib/models/dist/index.js";
2
+ // What a public agent's config.json must satisfy to be servable.
3
+ //
4
+ // resolveAgentConfig (platform/ui/app/lib/claude-agent/account.ts) silently
5
+ // defaults every bad field: an unknown status makes the agent unreachable with
6
+ // no error, an unselectable model fails at spawn, a missing displayName renders
7
+ // the slug in the chat header. Refusing at the write boundary turns each of
8
+ // those into a message the operator reads before the file exists.
9
+ //
10
+ // Unknown keys pass. A forward-compatible field written by an unreleased
11
+ // feature must not be rejected by a validator that predates it.
12
+ const LEGAL_STATUS = ["active", "inactive"];
13
+ const LEGAL_IMAGE_SHAPE = ["circle", "rounded"];
14
+ const LEGAL_ACCESS_MODE = ["open", "gated", "paid"];
15
+ const MAX_KNOWLEDGE_KEYWORDS = 5;
16
+ export function validateAgentConfig(config) {
17
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
18
+ return { ok: false, field: "(root)", detail: "config must be a JSON object" };
19
+ }
20
+ const c = config;
21
+ if (typeof c.displayName !== "string" || c.displayName.trim() === "") {
22
+ return { ok: false, field: "displayName", detail: "required, must be a non-blank string" };
23
+ }
24
+ if (typeof c.model !== "string" || !isSelectableModel(c.model)) {
25
+ return { ok: false, field: "model", detail: "required, must be a selectable platform model id" };
26
+ }
27
+ if (typeof c.status !== "string" || !LEGAL_STATUS.includes(c.status)) {
28
+ return { ok: false, field: "status", detail: `required, must be one of ${LEGAL_STATUS.join(" | ")}` };
29
+ }
30
+ if (c.imageShape !== undefined && (typeof c.imageShape !== "string" || !LEGAL_IMAGE_SHAPE.includes(c.imageShape))) {
31
+ return { ok: false, field: "imageShape", detail: `must be one of ${LEGAL_IMAGE_SHAPE.join(" | ")} when present` };
32
+ }
33
+ if (c.accessMode !== undefined && (typeof c.accessMode !== "string" || !LEGAL_ACCESS_MODE.includes(c.accessMode))) {
34
+ return { ok: false, field: "accessMode", detail: `must be one of ${LEGAL_ACCESS_MODE.join(" | ")} when present` };
35
+ }
36
+ if (c.knowledgeKeywords !== undefined) {
37
+ if (!Array.isArray(c.knowledgeKeywords)) {
38
+ return { ok: false, field: "knowledgeKeywords", detail: "must be an array of strings when present" };
39
+ }
40
+ if (c.knowledgeKeywords.length > MAX_KNOWLEDGE_KEYWORDS) {
41
+ return { ok: false, field: "knowledgeKeywords", detail: `at most ${MAX_KNOWLEDGE_KEYWORDS} entries` };
42
+ }
43
+ if (c.knowledgeKeywords.some((k) => typeof k !== "string")) {
44
+ return { ok: false, field: "knowledgeKeywords", detail: "every entry must be a string" };
45
+ }
46
+ }
47
+ return { ok: true };
48
+ }
49
+ //# sourceMappingURL=agent-config-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-config-schema.js","sourceRoot":"","sources":["../../src/tools/agent-config-schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ export type AgentWriteRefusal = "slug-unsafe" | "config-invalid" | "knowledge-empty" | "exists" | "not-found";
2
+ export type AgentWriteResult = {
3
+ ok: true;
4
+ slug: string;
5
+ filesWritten: string[];
6
+ } | {
7
+ ok: false;
8
+ reason: AgentWriteRefusal;
9
+ detail: string;
10
+ };
11
+ export interface CreateAgentArgs {
12
+ accountDir: string;
13
+ platformRoot: string;
14
+ slug: string;
15
+ displayName: string;
16
+ model: string;
17
+ soul: string;
18
+ knowledge: string;
19
+ knowledgeKeywords?: string[];
20
+ status?: string;
21
+ showAgentName?: boolean | "none";
22
+ accessMode?: string;
23
+ }
24
+ export interface UpdateAgentArgs {
25
+ accountDir: string;
26
+ slug: string;
27
+ displayName?: string;
28
+ model?: string;
29
+ soul?: string;
30
+ knowledge?: string;
31
+ knowledgeKeywords?: string[];
32
+ status?: string;
33
+ showAgentName?: boolean | "none";
34
+ accessMode?: string;
35
+ }
36
+ export declare function createAgent(args: CreateAgentArgs): AgentWriteResult;
37
+ export declare function updateAgent(args: UpdateAgentArgs): AgentWriteResult;
38
+ export interface ProjectOpts {
39
+ platformPort: string;
40
+ accountId: string;
41
+ slug: string;
42
+ houseAdminScope?: string;
43
+ /** Injected in tests. Production passes nothing and the global fetch is used. */
44
+ fetchImpl?: typeof fetch;
45
+ }
46
+ /**
47
+ * Project the on-disk agent into the graph through the platform route that
48
+ * already owns the projector, rather than re-implementing the MERGE here. The
49
+ * returned `projected` is read from the response, never assumed: a write that
50
+ * succeeded with `projected=0` is the beagle state (files on disk, no :Agent
51
+ * node) and must be reportable as such.
52
+ *
53
+ * What authorises the call is the loopback peer, not the header:
54
+ * `authorizeAccountWrite` (platform/ui/server/routes/admin/_account-scope.ts:67)
55
+ * returns `via: "on-device"` for any 127.0.0.1 caller with no x-forwarded-for,
56
+ * and never reads x-maxy-caller-account. The header is still sent because it is
57
+ * the identity every other admin-MCP call to the platform carries, so the
58
+ * route's gate can tighten without this caller becoming the exception.
59
+ */
60
+ export declare function projectAgentViaPlatform(opts: ProjectOpts): Promise<{
61
+ projected: number;
62
+ error?: string;
63
+ }>;
64
+ //# sourceMappingURL=agent-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-write.d.ts","sourceRoot":"","sources":["../../src/tools/agent-write.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,iBAAiB,GACzB,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,QAAQ,GACR,WAAW,CAAC;AAEhB,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,GAClD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8BD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,gBAAgB,CAsDnE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,gBAAgB,CA4CnE;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB/G"}
@@ -0,0 +1,151 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { checkAgentSlug } from "../../../../../lib/agent-slug/dist/index.js";
4
+ import { validateAgentConfig } from "./agent-config-schema.js";
5
+ function agentDirOf(accountDir, slug) {
6
+ return resolve(accountDir, "agents", slug);
7
+ }
8
+ /** Config fields the tools own. An absent optional is left out of the file
9
+ * entirely rather than written as null, so the result matches what the admin
10
+ * UI's creation form produces. */
11
+ function buildConfig(base, args) {
12
+ const next = { ...base };
13
+ if (args.displayName !== undefined)
14
+ next.displayName = args.displayName;
15
+ if (args.model !== undefined)
16
+ next.model = args.model;
17
+ if (args.status !== undefined)
18
+ next.status = args.status;
19
+ if (args.knowledgeKeywords !== undefined)
20
+ next.knowledgeKeywords = args.knowledgeKeywords;
21
+ if (args.showAgentName !== undefined)
22
+ next.showAgentName = args.showAgentName;
23
+ if (args.accessMode !== undefined)
24
+ next.accessMode = args.accessMode;
25
+ return next;
26
+ }
27
+ export function createAgent(args) {
28
+ const verdict = checkAgentSlug(args.slug);
29
+ if (!verdict.ok)
30
+ return { ok: false, reason: "slug-unsafe", detail: verdict.reason };
31
+ const dir = agentDirOf(args.accountDir, args.slug);
32
+ if (existsSync(dir)) {
33
+ return { ok: false, reason: "exists", detail: `agents/${args.slug}/ already exists` };
34
+ }
35
+ if (args.knowledge.trim() === "") {
36
+ return { ok: false, reason: "knowledge-empty", detail: "KNOWLEDGE.md would be empty" };
37
+ }
38
+ const config = buildConfig({}, {
39
+ displayName: args.displayName,
40
+ model: args.model,
41
+ status: args.status ?? "active",
42
+ knowledgeKeywords: args.knowledgeKeywords,
43
+ showAgentName: args.showAgentName,
44
+ accessMode: args.accessMode,
45
+ });
46
+ const configVerdict = validateAgentConfig(config);
47
+ if (!configVerdict.ok) {
48
+ return { ok: false, reason: "config-invalid", detail: configVerdict.field };
49
+ }
50
+ // IDENTITY.md is Rubytech-controlled and copied verbatim. It is not a
51
+ // caller-supplied field: the toolless-public directive is the one part of a
52
+ // public agent the operator does not author. Read before mkdir so an absent
53
+ // template refuses instead of leaving three of the four files on disk.
54
+ const identityPath = resolve(args.platformRoot, "templates", "agents", "public", "IDENTITY.md");
55
+ if (!existsSync(identityPath)) {
56
+ return { ok: false, reason: "not-found", detail: `IDENTITY template missing at ${identityPath}` };
57
+ }
58
+ const identity = readFileSync(identityPath, "utf-8");
59
+ mkdirSync(dir, { recursive: true });
60
+ try {
61
+ writeFileSync(resolve(dir, "IDENTITY.md"), identity);
62
+ writeFileSync(resolve(dir, "SOUL.md"), args.soul);
63
+ writeFileSync(resolve(dir, "KNOWLEDGE.md"), args.knowledge);
64
+ writeFileSync(resolve(dir, "config.json"), `${JSON.stringify(config, null, 2)}\n`);
65
+ }
66
+ catch (err) {
67
+ // The directory did not exist before this call, so removing it restores the
68
+ // pre-call state exactly. A half-written agent is worse than none: it would
69
+ // fail the `exists` check on the retry.
70
+ rmSync(dir, { recursive: true, force: true });
71
+ throw err;
72
+ }
73
+ return { ok: true, slug: args.slug, filesWritten: ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md", "config.json"] };
74
+ }
75
+ export function updateAgent(args) {
76
+ const verdict = checkAgentSlug(args.slug);
77
+ if (!verdict.ok)
78
+ return { ok: false, reason: "slug-unsafe", detail: verdict.reason };
79
+ const dir = agentDirOf(args.accountDir, args.slug);
80
+ const configPath = resolve(dir, "config.json");
81
+ if (!existsSync(configPath)) {
82
+ return { ok: false, reason: "not-found", detail: `no config.json at agents/${args.slug}/` };
83
+ }
84
+ if (args.knowledge !== undefined && args.knowledge.trim() === "") {
85
+ return { ok: false, reason: "knowledge-empty", detail: "KNOWLEDGE.md would be empty" };
86
+ }
87
+ let base;
88
+ try {
89
+ base = JSON.parse(readFileSync(configPath, "utf-8"));
90
+ }
91
+ catch (err) {
92
+ return {
93
+ ok: false,
94
+ reason: "config-invalid",
95
+ detail: `existing config.json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
96
+ };
97
+ }
98
+ const config = buildConfig(base, args);
99
+ const configVerdict = validateAgentConfig(config);
100
+ if (!configVerdict.ok) {
101
+ return { ok: false, reason: "config-invalid", detail: configVerdict.field };
102
+ }
103
+ const written = [];
104
+ if (args.soul !== undefined) {
105
+ writeFileSync(resolve(dir, "SOUL.md"), args.soul);
106
+ written.push("SOUL.md");
107
+ }
108
+ if (args.knowledge !== undefined) {
109
+ writeFileSync(resolve(dir, "KNOWLEDGE.md"), args.knowledge);
110
+ written.push("KNOWLEDGE.md");
111
+ }
112
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
113
+ written.push("config.json");
114
+ return { ok: true, slug: args.slug, filesWritten: written };
115
+ }
116
+ /**
117
+ * Project the on-disk agent into the graph through the platform route that
118
+ * already owns the projector, rather than re-implementing the MERGE here. The
119
+ * returned `projected` is read from the response, never assumed: a write that
120
+ * succeeded with `projected=0` is the beagle state (files on disk, no :Agent
121
+ * node) and must be reportable as such.
122
+ *
123
+ * What authorises the call is the loopback peer, not the header:
124
+ * `authorizeAccountWrite` (platform/ui/server/routes/admin/_account-scope.ts:67)
125
+ * returns `via: "on-device"` for any 127.0.0.1 caller with no x-forwarded-for,
126
+ * and never reads x-maxy-caller-account. The header is still sent because it is
127
+ * the identity every other admin-MCP call to the platform carries, so the
128
+ * route's gate can tighten without this caller becoming the exception.
129
+ */
130
+ export async function projectAgentViaPlatform(opts) {
131
+ const headers = {
132
+ "content-type": "application/json",
133
+ "x-maxy-caller-account": opts.accountId,
134
+ };
135
+ if (opts.houseAdminScope)
136
+ headers["x-maxy-house-admin-scope"] = opts.houseAdminScope;
137
+ const url = `http://127.0.0.1:${opts.platformPort}/api/admin/agents/${opts.slug}/project?accountId=${opts.accountId}`;
138
+ const doFetch = opts.fetchImpl ?? fetch;
139
+ try {
140
+ const res = await doFetch(url, { method: "POST", headers, signal: AbortSignal.timeout(5000) });
141
+ if (!res.ok) {
142
+ const body = await res.text().catch(() => "");
143
+ return { projected: 0, error: `projection route returned ${res.status} ${body}`.trim() };
144
+ }
145
+ return { projected: 1 };
146
+ }
147
+ catch (err) {
148
+ return { projected: 0, error: err instanceof Error ? err.message : String(err) };
149
+ }
150
+ }
151
+ //# sourceMappingURL=agent-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-write.js","sourceRoot":"","sources":["../../src/tools/agent-write.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:e737732428725be0a3852ca8ba4cd1f9dd5f62bdcd6f7c0a85b82f4ac1c9bccc
4
+ content-hash: sha256:3b275a96d70deb1ae39a1fe77c45c8dbc5beb65985a7c5fea62927b99c97e46b
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -1695,7 +1695,9 @@ Every turn carries a `source`. Three values exist:
1695
1695
  | `schedule` | The scheduler wrote this turn. The named destination did not send it. |
1696
1696
  | `public-relay` | The platform wrote this turn because a public visitor messaged the account. The named sender is a manager being told, not someone who wrote to you. |
1697
1697
 
1698
- A `public-relay` turn also says so in its own first two lines. If a turn claims a manager wrote something they plainly would not have written, check the source before answering them.
1698
+ A `public-relay` turn also says so in its own first two lines, and states which handset its reply tool reaches. That handset is the manager's, never the visitor's: the turn runs in the manager's own admin session, so a reply written to the visitor is delivered to the manager. If a turn claims a manager wrote something they plainly would not have written, check the source before answering them.
1699
+
1700
+ **On a `public-relay` turn the reply tool is the only way to reach anyone.** On an ordinary turn the platform forwards your closing text to the sender when you called no reply tool. On a relay-woken turn it does not, because the relay carries its own reply path. So whatever you decide the manager should receive has to go through the reply tool; text you merely write at the end of the turn reaches nobody, and a decision not to reply is silence rather than a message.
1699
1701
 
1700
1702
  ## Which files compose the prompt
1701
1703
 
@@ -1712,7 +1714,7 @@ A public agent's directory also carries `KNOWLEDGE.md` and `config.json`. An acc
1712
1714
 
1713
1715
  ## The switches, and what each one stops
1714
1716
 
1715
- Four settings sit on this path. They are not interchangeable, and reaching for the wrong one is how an operator ends up silencing more than they meant.
1717
+ Five settings sit on this path. They are not interchangeable, and reaching for the wrong one is how an operator ends up silencing more than they meant.
1716
1718
 
1717
1719
  | Setting | What it stops | What it leaves running |
1718
1720
  |---|---|---|
@@ -1720,6 +1722,7 @@ Four settings sit on this path. They are not interchangeable, and reaching for t
1720
1722
  | `publicAgentEnabled: false` | The visitor gets no reply. | The manager notification and the relay. |
1721
1723
  | `managerNotifyEnabled: false` | The verbatim copy of the visitor's message. | The relay, and any reply the woken turn sends. |
1722
1724
  | `dispatchInbound: false` | Every agent turn on that account's socket. | Storage: messages are still stored and readable. |
1725
+ | `dispatch: false` on one registered party | The public-agent turn for that one phone. | Everything else: storage, the label, the manager notification, the relay, and every other sender on the line. |
1723
1726
 
1724
1727
  `managerNotifyEnabled` is the fine-grained one, and the one to reach for when the notifications are unwanted but the work is not. Unbinding a manager is not a substitute: the manager registry is shared by the notification, the relay and `whatsapp-notify-manager`, so unbinding silences all three.
1725
1728
 
@@ -1727,18 +1730,36 @@ Read the current value with `whatsapp-config {action:'get-manager-notify-enabled
1727
1730
 
1728
1731
  ## When the answer is not here
1729
1732
 
1730
- `logs-read --tail server` and filter on the tags. One public inbound writes this chain, in order, with `account=` and `sender=` on each line:
1733
+ `logs-read --tail server` and filter on the tags. One public inbound writes this chain, in order:
1731
1734
 
1732
1735
  ```
1733
- [whatsapp:wire] op=stanza
1734
- [wa-notify] op=eligible reason=<how the sender was admitted>
1735
- [whatsapp:route] op=routed agentType=public
1736
- [whatsapp:notify] op=public-inbound enabled=0|1 outcome=sent|failed|no-recipients|disabled
1737
- [whatsapp:relay] op=dispatch outcome=sent|failed|no-recipients
1738
- [whatsapp:public-agent] op=route enabled=… slug=… spawned=0|1
1739
- [whatsapp:public-agent] op=spawn-ok | op=spawn-failed
1736
+ [whatsapp:wire] op=stanza msgId=<id>
1737
+ [wa-notify] op=eligible msgId=<id> reason=<how the sender was admitted>
1738
+ [whatsapp:route] op=routed inboundId=<id> msgIds=<the stanzas this payload covers>
1739
+ [whatsapp:notify] op=public-inbound inboundId=<id> enabled=0|1 outcome=sent|failed|no-recipients|disabled
1740
+ [whatsapp:relay] op=dispatch inboundId=<id> outcome=sent|failed|no-recipients
1741
+ [whatsapp:party] op=dispatch-decision inboundId=<id> party=… dispatch=true|false spawned=0|1 (labelled senders only)
1742
+ [whatsapp:public-agent] op=route inboundId=<id> enabled=… slug=… spawned=0|1
1743
+ [whatsapp:public-agent] op=spawn-ok | op=spawn-failed inboundId=<id>
1740
1744
  ```
1741
1745
 
1746
+ Follow one inbound by its `inboundId`. That selects the last five lines and nothing else, however many visitors messaged in the same window. For the first two, read `msgIds=` off the `op=routed` line and grep one of those ids. Two greps rather than one, because the debouncer merges stanzas into a single routed payload and the two halves are keyed either side of that merge.
1747
+
1748
+ Anything that failed carries the same id: the drop lines, `op=public-inbound-rejected`, `op=dispatch-rejected` and `op=spawn-failed`. Fewer lines than expected, with none of them a failure, means a step emitted nothing at all — which is what the standing audits below are for.
1749
+
1750
+ Do not try to follow an inbound by its sender. `sender=` appears on two of these lines, and the same visitor is a LID on `op=stanza` and an E.164 on `op=routed`, so no sender-based filter spans the merge.
1751
+
1752
+ `op=spawn-failed` there is the WhatsApp side reporting a 500. What the spawn itself did is in the manager's own lines, correlated by `pid`:
1753
+
1754
+ ```
1755
+ op=startup-watch state=armed
1756
+ op=public-dev-channel-confirmed channel=<kind> bufferCleared=<bool> bytesDropped=<n>
1757
+ op=startup-watch state=reset
1758
+ op=startup-watch state=disposed
1759
+ ```
1760
+
1761
+ The first-run dialog is answered for the session, and the watcher that guards against an *unanswered* one drops what it saw before that answer. `bufferCleared=false` means the confirm fired and the drop did not, which is the shape that killed a resumed spawn on 2026-08-01: the answered dialog's own footer stayed in the buffer, the replayed transcript slid past it, and the spawn was blocked by a line the agent itself had written the day before.
1762
+
1742
1763
  Three standing audits reconcile that chain rather than waiting for someone to notice. `op=public-notify-reconcile` reports `gap`, `relayGap` and `answerGap`, each of which counts a step that a routed inbound reached and the next one did not. `op=spawn-census` reports attempts against successes. A non-zero gap, or attempts with no successes, is a fault even when nothing has been reported.
1743
1764
 
1744
1765
  ---
@@ -3919,6 +3940,40 @@ explicit `?plugin=` parameter, not inferred from `--` in the slug.
3919
3940
  | `DELETE /api/admin/agents/:slug?accountId=` | Public agents only, on the named validated account. Removes the dir after `deleteAgentProjection`; refuses the `admin` slug (403) and a missing/unknown `accountId` (400) with no write. Loud-fail: a graph-cleanup throw aborts the file removal. |
3920
3941
  | `POST /api/admin/agents/:slug/project?accountId=` | Re-projects the named account's on-disk agent into the graph. Same `accountId` contract as delete. |
3921
3942
 
3943
+ ### Agent write tools (admin MCP)
3944
+
3945
+ Public-agent CRUD is tool-only. There is no create or update HTTP route: the three
3946
+ admin MCP tools own the write path, and raw file writes reach none of their checks.
3947
+ That is what left the `beagle` agent on disk and absent from the graph for a day.
3948
+
3949
+ | Tool | Refuses | On success |
3950
+ |---|---|---|
3951
+ | `agent-create` | `slug-unsafe` (shape, reserved or route-shadowed, by the same rule the router serves by), `exists` (the directory is occupied), `knowledge-empty`, `config-invalid` (naming the field), `not-found` (the platform IDENTITY template is missing). Every check runs before the first write, so a refusal leaves no directory. | Writes `IDENTITY.md` (copied verbatim from `templates/agents/public/`), `SOUL.md`, `KNOWLEDGE.md` and `config.json`, then projects. Returns an error when the write landed but the projection did not. |
3952
+ | `agent-update` | `slug-unsafe`, `not-found`, `knowledge-empty` when `knowledge` is supplied, `config-invalid` on the merged config. A refusal leaves the directory byte-identical. | Writes only the fields supplied, merges config fields into the existing file, then re-projects. Called with the slug alone it re-projects and nothing else. |
3953
+ | `agent-delete` | The `admin` slug, at the tool boundary as well as at the route. | Calls `DELETE /api/admin/agents/:slug`, which removes the graph projection before the directory and preserves the files if the graph write throws. |
3954
+
3955
+ The config schema (`plugins/admin/mcp/src/tools/agent-config-schema.ts`) requires a
3956
+ non-blank `displayName`, a `model` passing `isSelectableModel`, and a `status` of
3957
+ `active` or `inactive`; it constrains `imageShape`, `accessMode` and
3958
+ `knowledgeKeywords` when present, and passes unknown keys so a field written by an
3959
+ unreleased feature is not blocked. `KNOWLEDGE-SUMMARY.md` is the one agent file no
3960
+ write tool owns; write it directly and the next `agent-update` projects it.
3961
+
3962
+ Every call emits one line:
3963
+
3964
+ [admin/agents] op=agent-write action=<create|update|delete> slug=<slug> accountId=<8>
3965
+ outcome=<ok|refused> reason=<slug-unsafe|config-invalid|knowledge-empty|exists|not-found|projection-failed|delete-failed|write-failed|none>
3966
+ detail=<shape|reserved|shadowed|field|error text|-> projected=<n>
3967
+
3968
+ `projected` is read from the projection response, never assumed. The
3969
+ disk-without-graph signature is `action=create|update outcome=ok projected=0` —
3970
+ the action matters, because a successful delete also logs `projected=0`, having
3971
+ removed the projection on purpose.
3972
+
3973
+ `reason=write-failed` covers an unresolvable account directory or an I/O error.
3974
+ It exists because `getAccountDir` throws and the lifeline wrapper re-throws, so
3975
+ without a catch in the handler those failures would emit no line at all.
3976
+
3922
3977
  Observability rides `[admin/agents]`, and **every line names the account it acted
3923
3978
  on** (`accountId=<id8>`) — a well-formed answer about the wrong account is
3924
3979
  otherwise indistinguishable from a correct one, which is exactly how an empty
@@ -3939,6 +3994,19 @@ Agents page survived undiagnosed on a multi-account install.
3939
3994
  - `op=<delete|project> rejected reason=<missing-accountId|unknown-accountId>` —
3940
3995
  a caller that did not name its account. A skill that was not updated produces
3941
3996
  this on its first call instead of writing to the wrong account.
3997
+ - `op=project rejected reason=agent-not-on-disk accountId=<id8> slug=<…>
3998
+ probed=<agentDir>` — the projection 404. `probed` names the directory that
3999
+ actually resolved, so a "the route says not found, the files are plainly
4000
+ there" report is refutable without reproducing the call; before it existed the
4001
+ route returned that 404 silently and such a report could be neither confirmed
4002
+ nor refuted. Its two siblings are `op=project ok accountId=<id8> slug=<…>
4003
+ nodesWritten=<n>` and `op=project failed accountId=<id8> slug=<…>
4004
+ error="<msg>"` on a projector throw. `nodesWritten` counts nodes MERGEd — the
4005
+ Agent node plus one KnowledgeDocument per present body file — so it stays
4006
+ non-zero on an idempotent re-projection, and **`nodesWritten=0` on an `ok` is
4007
+ the defect signature**: `projectAgent` returns early without throwing on an
4008
+ unsafe slug, a missing `config.json` or a body-file read error, and the route
4009
+ answers `200 {"ok":true}` in all three cases.
3942
4010
  - `op=delete accountId=<id8> slug=<…> outcome=<ok|failed>` with
3943
4011
  `reason=<graph-cleanup-failed|rm-error>` on failure (files preserved on a
3944
4012
  graph-cleanup throw).
@@ -5425,7 +5493,7 @@ A `Service` node matched by vector search will have its `PriceSpecification`, `R
5425
5493
 
5426
5494
  ## Keyword Subscriptions — Reactive Per-Agent Knowledge
5427
5495
 
5428
- Each public agent can subscribe to up to 5 keywords via `knowledgeKeywords` in its `config.json`. These subscriptions make the agent reactive to new graph content matching its topics — content added after the agent was created becomes discoverable without manual tag updates.
5496
+ Each public agent can subscribe to up to 5 keywords via `knowledgeKeywords` in its `config.json`. Subscriptions default to empty and are opt-in: the operator asks for them or the agent has none. A subscription makes new graph content matching its topics **discoverable at the next refresh** it surfaces candidate lines for the operator to approve, and never adds anything to a public agent's knowledge on its own. Whatever a public agent can say is a line the operator approved for a stranger to read.
5429
5497
 
5430
5498
  ### Dual search per keyword
5431
5499
 
@@ -5443,7 +5511,7 @@ Results from keyword subscription searches are merged into the same scored map a
5443
5511
 
5444
5512
  ### Lifecycle
5445
5513
 
5446
- Keywords are consumed by the `update-knowledge` admin skill when regenerating KNOWLEDGE.md — the regeneration query broadens the operator-tagged set with keyword matches so newly-added graph content that shares a subscribed topic lands in the next baked snapshot. There is no runtime keyword-injection path on the public PTY surface.
5514
+ Keywords are consumed by the `update-knowledge` admin skill when regenerating KNOWLEDGE.md — the regeneration query broadens the operator-tagged set with keyword matches, so newly-added graph content sharing a subscribed topic is *proposed* for the next baked snapshot. It is proposed, not baked: the skill presents the gathered lines and writes only the ones the operator approves. There is no runtime keyword-injection path on the public PTY surface.
5447
5515
 
5448
5516
  ---
5449
5517