@rubytech/create-maxy-code 0.1.542 → 0.1.544

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 (371) hide show
  1. package/dist/__tests__/public-instruction-seed.test.js +102 -0
  2. package/dist/index.js +47 -0
  3. package/dist/public-instruction-seed.js +70 -0
  4. package/package.json +1 -1
  5. package/payload/platform/lib/shared-folder/dist/index.d.ts +47 -0
  6. package/payload/platform/lib/shared-folder/dist/index.d.ts.map +1 -0
  7. package/payload/platform/lib/shared-folder/dist/index.js +93 -0
  8. package/payload/platform/lib/shared-folder/dist/index.js.map +1 -0
  9. package/payload/platform/lib/shared-folder/src/index.ts +92 -0
  10. package/payload/platform/lib/shared-folder/tsconfig.json +5 -0
  11. package/payload/platform/package.json +2 -2
  12. package/payload/platform/plugins/admin/PLUGIN.md +2 -0
  13. package/payload/platform/plugins/admin/hooks/__tests__/public-tool-surface.test.sh +173 -0
  14. package/payload/platform/plugins/admin/hooks/public-tool-surface.sh +123 -0
  15. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.d.ts.map +1 -1
  16. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.js +13 -1
  17. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.js.map +1 -1
  18. package/payload/platform/plugins/admin/skills/channel-instruction/SKILL.md +46 -0
  19. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -1
  20. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +15 -0
  21. package/payload/platform/plugins/docs/references/access-control.md +14 -0
  22. package/payload/platform/plugins/docs/references/internals.md +2 -0
  23. package/payload/platform/plugins/docs/references/platform.md +1 -0
  24. package/payload/platform/plugins/filesystem/.claude-plugin/plugin.json +1 -1
  25. package/payload/platform/plugins/filesystem/PLUGIN.md +11 -1
  26. package/payload/platform/plugins/filesystem/mcp/dist/index.js +23 -7
  27. package/payload/platform/plugins/filesystem/mcp/dist/index.js.map +1 -1
  28. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.js +104 -4
  29. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.js.map +1 -1
  30. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/shared-folder.test.d.ts +2 -0
  31. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/shared-folder.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/shared-folder.test.js +67 -0
  33. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/shared-folder.test.js.map +1 -0
  34. package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts +16 -2
  35. package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts.map +1 -1
  36. package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js +54 -1
  37. package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js.map +1 -1
  38. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.js +76 -2
  39. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.js.map +1 -1
  40. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.d.ts +1 -0
  41. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.d.ts.map +1 -1
  42. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js +3 -3
  43. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js.map +1 -1
  44. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.d.ts +1 -0
  45. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.d.ts.map +1 -1
  46. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.js +1 -1
  47. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.js.map +1 -1
  48. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.d.ts +1 -0
  49. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.d.ts.map +1 -1
  50. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.js +3 -3
  51. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.js.map +1 -1
  52. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.d.ts +1 -0
  53. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.d.ts.map +1 -1
  54. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.js +3 -3
  55. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.js.map +1 -1
  56. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.d.ts +1 -0
  57. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.d.ts.map +1 -1
  58. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.js +3 -3
  59. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.js.map +1 -1
  60. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.d.ts +1 -0
  61. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.d.ts.map +1 -1
  62. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js +3 -3
  63. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js.map +1 -1
  64. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +81 -1
  65. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -1
  66. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +22 -0
  67. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  68. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +80 -0
  69. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  70. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +13 -1
  71. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  72. package/payload/platform/plugins/whatsapp/PLUGIN.md +2 -2
  73. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +1 -1
  74. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  75. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +1 -1
  76. package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +102 -0
  77. package/payload/platform/scripts/check-no-esm-require.mjs +5 -0
  78. package/payload/platform/scripts/lib/__tests__/account-settings-public-surface.test.sh +109 -0
  79. package/payload/platform/scripts/lib/account-settings-public-surface.sh +109 -0
  80. package/payload/platform/scripts/lib/provision-account-dir.sh +6 -3
  81. package/payload/platform/scripts/setup-account.sh +14 -0
  82. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts +8 -4
  83. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts.map +1 -1
  84. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js +27 -7
  85. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js.map +1 -1
  86. package/payload/platform/services/claude-session-manager/dist/config.d.ts +11 -6
  87. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  88. package/payload/platform/services/claude-session-manager/dist/config.js +8 -4
  89. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  90. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts +24 -2
  91. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
  92. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +23 -1
  93. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
  94. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +35 -0
  95. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  96. package/payload/platform/services/claude-session-manager/dist/http-server.js +286 -25
  97. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  98. package/payload/platform/services/claude-session-manager/dist/index.js +169 -51
  99. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  100. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +24 -12
  101. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  102. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +194 -28
  103. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  104. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
  105. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +4 -0
  106. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
  107. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.d.ts +24 -3
  108. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.d.ts.map +1 -1
  109. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.js +29 -3
  110. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.js.map +1 -1
  111. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts +27 -0
  112. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts.map +1 -1
  113. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js +32 -0
  114. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js.map +1 -1
  115. package/payload/platform/services/claude-session-manager/dist/role-census.d.ts +24 -0
  116. package/payload/platform/services/claude-session-manager/dist/role-census.d.ts.map +1 -0
  117. package/payload/platform/services/claude-session-manager/dist/role-census.js +50 -0
  118. package/payload/platform/services/claude-session-manager/dist/role-census.js.map +1 -0
  119. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +24 -2
  120. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
  121. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +21 -0
  122. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
  123. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.d.ts +76 -0
  124. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.d.ts.map +1 -1
  125. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.js +110 -19
  126. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.js.map +1 -1
  127. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.d.ts +60 -0
  128. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.d.ts.map +1 -0
  129. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.js +93 -0
  130. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.js.map +1 -0
  131. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts +31 -1
  132. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts.map +1 -1
  133. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js +72 -9
  134. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js.map +1 -1
  135. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +1 -1
  136. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  137. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +9 -1
  138. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  139. package/payload/platform/services/claude-session-manager/dist/types.d.ts +9 -3
  140. package/payload/platform/services/claude-session-manager/dist/types.d.ts.map +1 -1
  141. package/payload/platform/services/telegram-channel/dist/instructions.d.ts +0 -1
  142. package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
  143. package/payload/platform/services/telegram-channel/dist/instructions.js +0 -46
  144. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  145. package/payload/platform/services/telegram-channel/dist/notification.d.ts +16 -4
  146. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  147. package/payload/platform/services/telegram-channel/dist/notification.js +27 -14
  148. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  149. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  150. package/payload/platform/services/telegram-channel/dist/server.js +10 -0
  151. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  152. package/payload/platform/services/webchat-channel/dist/instructions.d.ts +0 -1
  153. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
  154. package/payload/platform/services/webchat-channel/dist/instructions.js +0 -60
  155. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  156. package/payload/platform/services/webchat-channel/dist/notification.d.ts +14 -2
  157. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  158. package/payload/platform/services/webchat-channel/dist/notification.js +28 -12
  159. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  160. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  161. package/payload/platform/services/webchat-channel/dist/server.js +10 -0
  162. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  163. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +97 -33
  164. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  165. package/payload/platform/services/whatsapp-channel/dist/notification.js +98 -76
  166. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  167. package/payload/platform/services/whatsapp-channel/dist/server.d.ts +18 -0
  168. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  169. package/payload/platform/services/whatsapp-channel/dist/server.js +87 -5
  170. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  171. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +41 -9
  172. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  173. package/payload/platform/services/whatsapp-channel/dist/targets.js +42 -7
  174. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  175. package/payload/platform/templates/agents/admin/INSTRUCTION.md +0 -0
  176. package/payload/server/{adminuser-self-heal-LC7HZAC6.js → adminuser-self-heal-ZHFNDKJK.js} +1 -1
  177. package/payload/server/{chunk-HZP7BKBF.js → chunk-CVUSUNXM.js} +128 -4
  178. package/payload/server/{chunk-CKTHAHL4.js → chunk-DU7GPDJJ.js} +4 -2
  179. package/payload/server/{chunk-PFF6I7KP.js → chunk-HYQNUVGO.js} +8 -1
  180. package/payload/server/{chunk-ERIM3EFB.js → chunk-X2RRMHVV.js} +1 -1
  181. package/payload/server/{manager-CM55K4J2.js → manager-TI2YJQVZ.js} +3 -3
  182. package/payload/server/maxy-edge.js +3 -3
  183. package/payload/server/public/activity.html +5 -5
  184. package/payload/server/public/agents.html +4 -4
  185. package/payload/server/public/assets/AdminLoginScreens-BGKtDqAk.js +1 -0
  186. package/payload/server/public/assets/AdminLoginScreens-BGKtDqAk.js.br +0 -0
  187. package/payload/server/public/assets/AdminLoginScreens-BGKtDqAk.js.gz +0 -0
  188. package/payload/server/public/assets/AdminShell-DnfgsAQl.js +2 -0
  189. package/payload/server/public/assets/AdminShell-DnfgsAQl.js.br +0 -0
  190. package/payload/server/public/assets/AdminShell-DnfgsAQl.js.gz +0 -0
  191. package/payload/server/public/assets/activity-ByJSut0Q.js +1 -0
  192. package/payload/server/public/assets/activity-ByJSut0Q.js.br +0 -0
  193. package/payload/server/public/assets/activity-ByJSut0Q.js.gz +0 -0
  194. package/payload/server/public/assets/admin-CkeJO6kA.js +1 -0
  195. package/payload/server/public/assets/admin-CkeJO6kA.js.br +0 -0
  196. package/payload/server/public/assets/admin-CkeJO6kA.js.gz +0 -0
  197. package/payload/server/public/assets/agents-D8phtRX5.js +1 -0
  198. package/payload/server/public/assets/agents-D8phtRX5.js.br +0 -0
  199. package/payload/server/public/assets/agents-D8phtRX5.js.gz +0 -0
  200. package/payload/server/public/assets/browser-DQSTjdom.js +1 -0
  201. package/payload/server/public/assets/browser-DQSTjdom.js.br +0 -0
  202. package/payload/server/public/assets/browser-DQSTjdom.js.gz +0 -0
  203. package/payload/server/public/assets/calendar-DFJu1XjP.js +1 -0
  204. package/payload/server/public/assets/calendar-DFJu1XjP.js.br +0 -0
  205. package/payload/server/public/assets/calendar-DFJu1XjP.js.gz +0 -0
  206. package/payload/server/public/assets/chat-DUqtIsOt.js +1 -0
  207. package/payload/server/public/assets/chat-DUqtIsOt.js.br +0 -0
  208. package/payload/server/public/assets/chat-DUqtIsOt.js.gz +0 -0
  209. package/payload/server/public/assets/chevron-left-DFK18xVX.js +1 -0
  210. package/payload/server/public/assets/chevron-left-DFK18xVX.js.br +0 -0
  211. package/payload/server/public/assets/chevron-right-DJ4oaHoN.js +1 -0
  212. package/payload/server/public/assets/chevron-right-DJ4oaHoN.js.br +0 -0
  213. package/payload/server/public/assets/clock-wHIan2DW.js +1 -0
  214. package/payload/server/public/assets/clock-wHIan2DW.js.br +0 -0
  215. package/payload/server/public/assets/{copy-3kj9Oaoc.js → copy-lFFhXRGk.js} +1 -1
  216. package/payload/server/public/assets/copy-lFFhXRGk.js.br +0 -0
  217. package/payload/server/public/assets/copy-lFFhXRGk.js.gz +0 -0
  218. package/payload/server/public/assets/data-Cxv97ARy.js +1 -0
  219. package/payload/server/public/assets/data-Cxv97ARy.js.br +0 -0
  220. package/payload/server/public/assets/data-Cxv97ARy.js.gz +0 -0
  221. package/payload/server/public/assets/{file-text-CBaewT76.js → file-text-D2Y-bkNy.js} +1 -1
  222. package/payload/server/public/assets/file-text-D2Y-bkNy.js.br +0 -0
  223. package/payload/server/public/assets/file-text-D2Y-bkNy.js.gz +0 -0
  224. package/payload/server/public/assets/{graph-B8-e2_Z-.js → graph-C-3yN6JD.js} +3 -3
  225. package/payload/server/public/assets/graph-C-3yN6JD.js.br +0 -0
  226. package/payload/server/public/assets/graph-C-3yN6JD.js.gz +0 -0
  227. package/payload/server/public/assets/{graph-labels-Bsc23eZU.js → graph-labels-DiaPLP27.js} +1 -1
  228. package/payload/server/public/assets/graph-labels-DiaPLP27.js.br +0 -0
  229. package/payload/server/public/assets/graph-labels-DiaPLP27.js.gz +0 -0
  230. package/payload/server/public/assets/operator-KuGPvSa9.js +1 -0
  231. package/payload/server/public/assets/operator-KuGPvSa9.js.br +0 -0
  232. package/payload/server/public/assets/operator-KuGPvSa9.js.gz +0 -0
  233. package/payload/server/public/assets/page-BhGeFXWA.js +1 -0
  234. package/payload/server/public/assets/page-BhGeFXWA.js.br +0 -0
  235. package/payload/server/public/assets/page-BhGeFXWA.js.gz +0 -0
  236. package/payload/server/public/assets/page-EnbVC-yM.js +32 -0
  237. package/payload/server/public/assets/page-EnbVC-yM.js.br +0 -0
  238. package/payload/server/public/assets/page-EnbVC-yM.js.gz +0 -0
  239. package/payload/server/public/assets/play-jZFZAG-t.js +1 -0
  240. package/payload/server/public/assets/play-jZFZAG-t.js.br +0 -0
  241. package/payload/server/public/assets/play-jZFZAG-t.js.gz +0 -0
  242. package/payload/server/public/assets/public-CdSgMNC7.js +1 -0
  243. package/payload/server/public/assets/public-CdSgMNC7.js.br +0 -0
  244. package/payload/server/public/assets/public-CdSgMNC7.js.gz +0 -0
  245. package/payload/server/public/assets/{rotate-ccw-FZW7c8p8.js → rotate-ccw-rTRuTqei.js} +1 -1
  246. package/payload/server/public/assets/rotate-ccw-rTRuTqei.js.br +0 -0
  247. package/payload/server/public/assets/rotate-ccw-rTRuTqei.js.gz +0 -0
  248. package/payload/server/public/assets/routines-DNRxNRSQ.js +2 -0
  249. package/payload/server/public/assets/routines-DNRxNRSQ.js.br +0 -0
  250. package/payload/server/public/assets/routines-DNRxNRSQ.js.gz +0 -0
  251. package/payload/server/public/assets/skills-DFWtZNkW.js +1 -0
  252. package/payload/server/public/assets/skills-DFWtZNkW.js.br +0 -0
  253. package/payload/server/public/assets/skills-DFWtZNkW.js.gz +0 -0
  254. package/payload/server/public/assets/tasks-B94AuKSy.js +3 -0
  255. package/payload/server/public/assets/tasks-B94AuKSy.js.br +0 -0
  256. package/payload/server/public/assets/tasks-B94AuKSy.js.gz +0 -0
  257. package/payload/server/public/assets/{triangle-alert-CR-jRJdI.js → triangle-alert-DtWUg6jR.js} +1 -1
  258. package/payload/server/public/assets/triangle-alert-DtWUg6jR.js.br +0 -0
  259. package/payload/server/public/assets/triangle-alert-DtWUg6jR.js.gz +0 -0
  260. package/payload/server/public/assets/useCopyFeedback-CjKYO-Qf.js +1 -0
  261. package/payload/server/public/assets/useCopyFeedback-CjKYO-Qf.js.br +0 -0
  262. package/payload/server/public/assets/useCopyFeedback-CjKYO-Qf.js.gz +0 -0
  263. package/payload/server/public/assets/useMediaQuery-BYIHo-xL.js +14 -0
  264. package/payload/server/public/assets/useMediaQuery-BYIHo-xL.js.br +0 -0
  265. package/payload/server/public/assets/useMediaQuery-BYIHo-xL.js.gz +0 -0
  266. package/payload/server/public/assets/useMediaQuery-DlOWP56H.css +1 -0
  267. package/payload/server/public/assets/useMediaQuery-DlOWP56H.css.br +0 -0
  268. package/payload/server/public/assets/useMediaQuery-DlOWP56H.css.gz +0 -0
  269. package/payload/server/public/assets/useVoiceRecorder-BKFAYuzz.js +2 -0
  270. package/payload/server/public/assets/useVoiceRecorder-BKFAYuzz.js.br +0 -0
  271. package/payload/server/public/assets/useVoiceRecorder-BKFAYuzz.js.gz +0 -0
  272. package/payload/server/public/assets/{wrench-CpCOYUBc.js → wrench-Ct4kf-uv.js} +1 -1
  273. package/payload/server/public/assets/wrench-Ct4kf-uv.js.br +0 -0
  274. package/payload/server/public/assets/wrench-Ct4kf-uv.js.gz +0 -0
  275. package/payload/server/public/browser.html +4 -4
  276. package/payload/server/public/calendar.html +8 -8
  277. package/payload/server/public/chat.html +13 -13
  278. package/payload/server/public/data.html +11 -11
  279. package/payload/server/public/graph.html +9 -9
  280. package/payload/server/public/index.html +14 -14
  281. package/payload/server/public/operator.html +14 -14
  282. package/payload/server/public/public.html +13 -13
  283. package/payload/server/public/routines.html +6 -6
  284. package/payload/server/public/skills.html +5 -5
  285. package/payload/server/public/tasks.html +6 -6
  286. package/payload/server/server.js +537 -263
  287. package/payload/server/{src-JN6PIAJN.js → src-MM64CYYZ.js} +1 -1
  288. package/payload/server/{userprofile-reconcile-QHVLY2AH.js → userprofile-reconcile-TL4JKBPG.js} +1 -1
  289. package/payload/server/public/assets/AdminLoginScreens-DuFCjbz2.js +0 -1
  290. package/payload/server/public/assets/AdminLoginScreens-DuFCjbz2.js.br +0 -0
  291. package/payload/server/public/assets/AdminLoginScreens-DuFCjbz2.js.gz +0 -0
  292. package/payload/server/public/assets/AdminShell-DJL5DzRX.js +0 -2
  293. package/payload/server/public/assets/AdminShell-DJL5DzRX.js.br +0 -0
  294. package/payload/server/public/assets/AdminShell-DJL5DzRX.js.gz +0 -0
  295. package/payload/server/public/assets/activity-CpMnNr5c.js +0 -1
  296. package/payload/server/public/assets/activity-CpMnNr5c.js.br +0 -0
  297. package/payload/server/public/assets/activity-CpMnNr5c.js.gz +0 -0
  298. package/payload/server/public/assets/admin-CBwKIfaI.js +0 -1
  299. package/payload/server/public/assets/admin-CBwKIfaI.js.br +0 -0
  300. package/payload/server/public/assets/admin-CBwKIfaI.js.gz +0 -0
  301. package/payload/server/public/assets/agents-C4LmSKjb.js +0 -1
  302. package/payload/server/public/assets/agents-C4LmSKjb.js.br +0 -0
  303. package/payload/server/public/assets/agents-C4LmSKjb.js.gz +0 -0
  304. package/payload/server/public/assets/browser-Bm9xm_yc.js +0 -1
  305. package/payload/server/public/assets/browser-Bm9xm_yc.js.br +0 -0
  306. package/payload/server/public/assets/browser-Bm9xm_yc.js.gz +0 -0
  307. package/payload/server/public/assets/calendar-CNY9FTWe.js +0 -1
  308. package/payload/server/public/assets/calendar-CNY9FTWe.js.br +0 -0
  309. package/payload/server/public/assets/calendar-CNY9FTWe.js.gz +0 -0
  310. package/payload/server/public/assets/chat-3WoUGCTd.js +0 -1
  311. package/payload/server/public/assets/chat-3WoUGCTd.js.br +0 -0
  312. package/payload/server/public/assets/chat-3WoUGCTd.js.gz +0 -0
  313. package/payload/server/public/assets/chevron-left-BE16BJnw.js +0 -1
  314. package/payload/server/public/assets/chevron-left-BE16BJnw.js.br +0 -0
  315. package/payload/server/public/assets/chevron-right-Bu2VKpeU.js +0 -1
  316. package/payload/server/public/assets/chevron-right-Bu2VKpeU.js.br +0 -0
  317. package/payload/server/public/assets/clock-C4Nu2aRV.js +0 -1
  318. package/payload/server/public/assets/clock-C4Nu2aRV.js.br +0 -0
  319. package/payload/server/public/assets/copy-3kj9Oaoc.js.br +0 -0
  320. package/payload/server/public/assets/copy-3kj9Oaoc.js.gz +0 -0
  321. package/payload/server/public/assets/data-C-x5EHyi.js +0 -1
  322. package/payload/server/public/assets/data-C-x5EHyi.js.br +0 -1
  323. package/payload/server/public/assets/data-C-x5EHyi.js.gz +0 -0
  324. package/payload/server/public/assets/file-text-CBaewT76.js.br +0 -0
  325. package/payload/server/public/assets/file-text-CBaewT76.js.gz +0 -0
  326. package/payload/server/public/assets/graph-B8-e2_Z-.js.br +0 -0
  327. package/payload/server/public/assets/graph-B8-e2_Z-.js.gz +0 -0
  328. package/payload/server/public/assets/graph-labels-Bsc23eZU.js.br +0 -0
  329. package/payload/server/public/assets/graph-labels-Bsc23eZU.js.gz +0 -0
  330. package/payload/server/public/assets/operator-Cve0-qfO.js +0 -1
  331. package/payload/server/public/assets/operator-Cve0-qfO.js.br +0 -0
  332. package/payload/server/public/assets/operator-Cve0-qfO.js.gz +0 -0
  333. package/payload/server/public/assets/page-BgVMs6Vf.js +0 -1
  334. package/payload/server/public/assets/page-BgVMs6Vf.js.br +0 -0
  335. package/payload/server/public/assets/page-BgVMs6Vf.js.gz +0 -0
  336. package/payload/server/public/assets/page-fABub-w8.js +0 -32
  337. package/payload/server/public/assets/page-fABub-w8.js.br +0 -0
  338. package/payload/server/public/assets/page-fABub-w8.js.gz +0 -0
  339. package/payload/server/public/assets/play-B6zXB9K7.js +0 -1
  340. package/payload/server/public/assets/play-B6zXB9K7.js.br +0 -0
  341. package/payload/server/public/assets/play-B6zXB9K7.js.gz +0 -0
  342. package/payload/server/public/assets/public--R2DbdWs.js +0 -1
  343. package/payload/server/public/assets/public--R2DbdWs.js.br +0 -0
  344. package/payload/server/public/assets/public--R2DbdWs.js.gz +0 -0
  345. package/payload/server/public/assets/rotate-ccw-FZW7c8p8.js.br +0 -0
  346. package/payload/server/public/assets/rotate-ccw-FZW7c8p8.js.gz +0 -0
  347. package/payload/server/public/assets/routines-BpZs2SaT.js +0 -2
  348. package/payload/server/public/assets/routines-BpZs2SaT.js.br +0 -0
  349. package/payload/server/public/assets/routines-BpZs2SaT.js.gz +0 -0
  350. package/payload/server/public/assets/skills-DF7uyEEz.js +0 -1
  351. package/payload/server/public/assets/skills-DF7uyEEz.js.br +0 -0
  352. package/payload/server/public/assets/skills-DF7uyEEz.js.gz +0 -0
  353. package/payload/server/public/assets/tasks-BfJJmoWO.js +0 -3
  354. package/payload/server/public/assets/tasks-BfJJmoWO.js.br +0 -0
  355. package/payload/server/public/assets/tasks-BfJJmoWO.js.gz +0 -0
  356. package/payload/server/public/assets/triangle-alert-CR-jRJdI.js.br +0 -0
  357. package/payload/server/public/assets/triangle-alert-CR-jRJdI.js.gz +0 -0
  358. package/payload/server/public/assets/useCopyFeedback-IOuPUAHY.js +0 -1
  359. package/payload/server/public/assets/useCopyFeedback-IOuPUAHY.js.br +0 -0
  360. package/payload/server/public/assets/useCopyFeedback-IOuPUAHY.js.gz +0 -0
  361. package/payload/server/public/assets/useMediaQuery-CA6dR5iz.js +0 -14
  362. package/payload/server/public/assets/useMediaQuery-CA6dR5iz.js.br +0 -0
  363. package/payload/server/public/assets/useMediaQuery-CA6dR5iz.js.gz +0 -0
  364. package/payload/server/public/assets/useMediaQuery-DpwgV1Da.css +0 -1
  365. package/payload/server/public/assets/useMediaQuery-DpwgV1Da.css.br +0 -0
  366. package/payload/server/public/assets/useMediaQuery-DpwgV1Da.css.gz +0 -0
  367. package/payload/server/public/assets/useVoiceRecorder-DGhsN7nV.js +0 -2
  368. package/payload/server/public/assets/useVoiceRecorder-DGhsN7nV.js.br +0 -0
  369. package/payload/server/public/assets/useVoiceRecorder-DGhsN7nV.js.gz +0 -0
  370. package/payload/server/public/assets/wrench-CpCOYUBc.js.br +0 -0
  371. package/payload/server/public/assets/wrench-CpCOYUBc.js.gz +0 -0
@@ -27,7 +27,7 @@ import { parseWaChannelBody, sessionIdFieldsFor } from './public-spawn-session.j
27
27
  import { buildWebchatChannelMcpServers, buildWebchatChannelArgv, webchatChannelMcpConfigPath, WEBCHAT_CHANNEL_SERVER_NAME } from './webchat-channel-mcp.js';
28
28
  import { buildTelegramChannelMcpServers, buildTelegramChannelArgv, telegramChannelMcpConfigPath, TELEGRAM_CHANNEL_SERVER_NAME } from './telegram-channel-mcp.js';
29
29
  import { buildChannelMcpServers, buildChannelArgv, channelMcpConfigPath, CHANNEL_SERVER_NAME } from './channel-mcp.js';
30
- import { buildRcPublicLockdownArgv, formatToolAllowlistLine } from './rc-public-lockdown.js';
30
+ import { buildRcPublicLockdownArgv, formatToolAllowlistLine, PUBLIC_ALLOWED_TOOLS_ENV, allowedToolsFromArgv, formatRoleResolutionLine, } from './rc-public-lockdown.js';
31
31
  import { mergeTarget, isMultiTarget, targetKinds } from './channel-targets.js';
32
32
  import { randomUUID, createHash } from 'node:crypto';
33
33
  import { execFile, execFileSync } from 'node:child_process';
@@ -59,7 +59,40 @@ import { claudeStateRoot, projectSlugForCwd, jsonlPathForSessionId, sidecarPathF
59
59
  import { basename, dirname, join } from 'node:path';
60
60
  import { validateUserTitle } from './user-title-store.js';
61
61
  import { credentialsPathForConfigDir, snapshotAuthForFailureReason } from './auth-snapshot.js';
62
+ // Task 2325 — the wire subset of AgentRole. `passive` is a persona role
63
+ // resolved at the /public-spawn boundary from role:'public' plus the passive
64
+ // flag; it is deliberately absent here so no client can POST it directly.
62
65
  const ROLES = ['admin', 'public'];
66
+ /** Task 2325 — the rc-spawn role, as supplied by the request body. Task 1468
67
+ * widened this position to allow `undefined` because rc-spawn's role was
68
+ * absent for a non-admin/non-public request; null now means "the body named
69
+ * none", and the handler resolves it against the prior sidecar before
70
+ * deciding whether that is an error. `passive` is not accepted here:
71
+ * rc-spawn composes no persona at all (its --append-system-prompt segments
72
+ * are reseat / voice / specialist-domains / authenticated-identity
73
+ * fragments, never composeAppendSystemPrompt). */
74
+ export function parseRcSpawnRole(body) {
75
+ return body.role === 'admin' || body.role === 'public' ? body.role : null;
76
+ }
77
+ /** Task 2325 — the single place a /public-spawn wire role becomes a typed one.
78
+ * Returns the wire role (always 'admin' or 'public', narrowed by the ROLES
79
+ * allowlist above) and the persona role the spawn composes from. They differ
80
+ * only for a passive spawn. Anything the allowlist does not name returns null
81
+ * and the caller refuses; nothing falls through to 'admin'.
82
+ *
83
+ * The passive condition reproduces the Task 1483 invariant exactly, moved
84
+ * here from pty-spawner: a passive spawn rides `role:'public'` on the wire
85
+ * and is never a specialist, so a passive flag on any other role or on a
86
+ * specialist is ignored rather than silently registering a different (e.g.
87
+ * full-admin) server surface while the allowlist locks to work-create. */
88
+ export function parseSpawnRole(body, specialist) {
89
+ const raw = body.role;
90
+ if (typeof raw !== 'string' || !ROLES.includes(raw))
91
+ return null;
92
+ const role = raw;
93
+ const agentRole = body.passive === true && role === 'public' && specialist === undefined ? 'passive' : role;
94
+ return { role, agentRole };
95
+ }
63
96
  const CHANNELS = ['browser', 'whatsapp', 'telegram', 'webchat', 'email'];
64
97
  // Task 382 — body.conversationNodeId shape gate. The value is the
65
98
  // sessionId UUID (v4) minted by createNewAdminConversation in
@@ -1037,12 +1070,21 @@ export function buildHttpApp(deps) {
1037
1070
  const userId = typeof body.userId === 'string' && body.userId.length > 0 && body.userId.length <= 128
1038
1071
  ? body.userId
1039
1072
  : undefined;
1040
- const role = typeof body.role === 'string' ? body.role : '';
1041
1073
  const channel = typeof body.channel === 'string' ? body.channel : '';
1042
1074
  const hidden = body.hidden === true;
1043
1075
  const specialist = typeof body.specialist === 'string' && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist)
1044
1076
  ? body.specialist
1045
1077
  : undefined;
1078
+ // Task 2325 — one parse, before any other use of the role. `role` is what
1079
+ // arrived on the wire; `agentRole` is which persona it resolves to. They
1080
+ // differ only for a passive spawn. `role` keeps its string-or-empty shape
1081
+ // so every existing read of it, including the spawn-request log line's
1082
+ // `role=${role || 'missing'}`, is unchanged.
1083
+ const parsedRole = parseSpawnRole(body, specialist);
1084
+ const role = parsedRole?.role ?? '';
1085
+ const agentRole = parsedRole?.agentRole ?? null;
1086
+ // Task 2325 — recorded before the guard so a refused spawn is counted.
1087
+ deps.roleCensus?.record(agentRole);
1046
1088
  // Task 956 — public sessions are pinned to Sonnet 5 (operator decision:
1047
1089
  // admin-only Bypass/Opus; public stays Sonnet + fail-closed dontAsk). The
1048
1090
  // role==='public' force is the chokepoint covering every public spawn
@@ -1128,6 +1170,13 @@ export function buildHttpApp(deps) {
1128
1170
  // webchatChannel above. This door could attach webchat but never WhatsApp,
1129
1171
  // which is why the public WhatsApp route had nowhere to land before.
1130
1172
  const waChannel = parseWaChannelBody(body.waChannel);
1173
+ // Task 2338 — the telegram twin. `parseWaChannelBody` is shape-identical
1174
+ // for telegram (senderId + http gateway + absolute server path), so it is
1175
+ // reused rather than duplicated. pty-spawner has accepted a
1176
+ // `telegramChannel` arg since the public allowlist gained its telegram arm
1177
+ // (pty-spawner.ts:844, public-allowlist.ts:24); this route could not supply
1178
+ // one, so a public telegram spawn fell through to the deny-basis allowlist.
1179
+ const telegramChannel = parseWaChannelBody(body.telegramChannel);
1131
1180
  // Task 2262 — the caller's deterministic per-sender id, so a public visitor
1132
1181
  // keeps one conversation across cold starts instead of meeting a fresh agent
1133
1182
  // every time. Shape-gated to the same UUID v4 pattern the other id fields on
@@ -1152,10 +1201,42 @@ export function buildHttpApp(deps) {
1152
1201
  : body.dormantPlugins == null ? 'absent' : typeof body.dormantPlugins;
1153
1202
  const initialMessageBytes = typeof body.initialMessage === 'string' ? Buffer.byteLength(body.initialMessage, 'utf8') : 0;
1154
1203
  deps.logger(`spawn-request senderId=${senderId || 'missing'} userId=${userId ? userId.slice(0, 8) : 'absent'} role=${role || 'missing'} channel=${channel || 'missing'} permissionMode=${typeof body.permissionMode === 'string' ? body.permissionMode : 'default'} channels=${Array.isArray(body.channels) ? body.channels.length : 0} attachmentDir=${typeof body.attachmentDir === 'string' ? 'set' : 'absent'} aboutOwner=${aboutOwnerShape} dormantPlugins=${dormantPluginsShape} tunnelUrl=${typeof body.tunnelUrl === 'string' ? 'set' : (body.tunnelUrl === null ? 'null' : 'absent')} initialMessageBytes=${initialMessageBytes}`);
1155
- if (!senderId || !ROLES.includes(role) || !CHANNELS.includes(channel)) {
1204
+ if (!senderId || parsedRole === null || agentRole === null || !CHANNELS.includes(channel)) {
1205
+ // Task 2325 — emitted only when the ROLE is what failed. The guard also
1206
+ // covers a missing senderId and a bad channel, and naming those
1207
+ // `role-unresolved` would make the line lie about its own cause, which
1208
+ // is the exact defect class this task exists to remove. The 400 body
1209
+ // below still reports all three fields.
1210
+ if (parsedRole === null) {
1211
+ deps.logger(`[spawn] op=role-unresolved sessionId=new role=${typeof body.role === 'string' ? body.role : String(body.role)} site=public-spawn`);
1212
+ }
1156
1213
  timed(deps.logger, 'POST', '/public-spawn', 400, Date.now() - start);
1157
1214
  return c.json({ error: 'invalid-arguments', detail: { senderId: !!senderId, role, channel } }, 400);
1158
1215
  }
1216
+ // Task 2274 — a public spawn may not name a specialist. `isPublic` and
1217
+ // `isSpecialistSpawn` are independent in pty-spawner and the specialist
1218
+ // branch wins at two of the four lockdown levers: the `--allowed-tools` push
1219
+ // is skipped entirely, and `effectiveMode` resolves the caller-supplied mode
1220
+ // (defaulting to `bypassPermissions`) instead of the fail-closed `dontAsk`.
1221
+ // The `permissionMode` this body may also carry is parsed further down and
1222
+ // would be honoured on that branch, which is why the refusal sits above the
1223
+ // parse rather than beside it.
1224
+ //
1225
+ // Neither production issuer sends the combination: the channel bridge
1226
+ // (`manager-client.ts`) is the only one whose role can be 'public' and it
1227
+ // carries no `specialist` field, while the cookie-auth wrapper
1228
+ // (`claude-sessions.ts`) types its role as the literal 'admin'. Refusing
1229
+ // states that invariant at the door; `spawnClaudeSession` refuses the same
1230
+ // pairing again at the chokepoint, so a future route cannot reinherit it.
1231
+ //
1232
+ // `specialist` here is the shape-gated value, so a body whose field failed
1233
+ // the slug pattern resolved to undefined and is an ordinary public spawn —
1234
+ // the same value the spawner would have seen.
1235
+ if (role === 'public' && specialist !== undefined) {
1236
+ deps.logger(`[spawn] op=public-specialist-refused site=public-spawn specialist=${specialist} channel=${channel} senderId=${senderId}`);
1237
+ timed(deps.logger, 'POST', '/public-spawn', 400, Date.now() - start);
1238
+ return c.json({ error: 'specialist-not-permitted-on-public', detail: { specialist } }, 400);
1239
+ }
1159
1240
  // Task 1489 — a passive intake spawn rides role:'public' on this path but is
1160
1241
  // a trusted, sub-account-bound account manager, not an anonymous visitor. It
1161
1242
  // is exempt from the anonymous-visitor limiter: `!passive` keeps a manager's
@@ -1266,6 +1347,22 @@ export function buildHttpApp(deps) {
1266
1347
  deps.logger(`[spawn] op=agent-account-scope route=public-spawn account=${agentAccountId} applied=true`);
1267
1348
  }
1268
1349
  }
1350
+ // Task 2338 — the write-through /rc-spawn has always made (:3336, :3346,
1351
+ // :3355) and this route never did. Without it a session first spawned here
1352
+ // has no binding on disk, so the stuck-turn recycler has nothing to re-post
1353
+ // and the respawn loses the visitor's reply transport. A public spawn is
1354
+ // wa XOR webchat XOR telegram (pty-spawner.ts:1893), so at most one of
1355
+ // these fires. Gated on a caller-supplied sessionId because that is the
1356
+ // only id that exists before the spawn returns; a route that mints its own
1357
+ // id has no session to key on and no recycler re-post to serve.
1358
+ if (callerSessionId) {
1359
+ if (waChannel)
1360
+ deps.waChannelStore.set(callerSessionId, waChannel);
1361
+ if (webchatChannel)
1362
+ deps.webchatChannelStore?.set(callerSessionId, webchatChannel);
1363
+ if (telegramChannel)
1364
+ deps.telegramChannelStore?.set(callerSessionId, telegramChannel);
1365
+ }
1269
1366
  const result = await spawnClaudeSession({
1270
1367
  ...deps,
1271
1368
  spawnCwd,
@@ -1282,7 +1379,7 @@ export function buildHttpApp(deps) {
1282
1379
  if (!passive) {
1283
1380
  // Task 2271 — `spawnCwd` here is the re-scoped value, so the audit
1284
1381
  // follows a public-agent session into its own account's slug.
1285
- attachPublicAudit(deps, tracker.sessionId, role, spawnCwd);
1382
+ attachPublicAudit(deps, tracker.sessionId, parsedRole.role, spawnCwd);
1286
1383
  // Task 2262 — the exit notify is webchat machinery: the route it
1287
1384
  // calls resolves the sessionId against the WEBCHAT gateway's
1288
1385
  // publicSessions map to fire a slice-review, so a WhatsApp public
@@ -1292,7 +1389,7 @@ export function buildHttpApp(deps) {
1292
1389
  // because a WhatsApp visitor's realised tool surface is exactly what
1293
1390
  // Task 2260 wants audited.
1294
1391
  if (channel !== 'whatsapp') {
1295
- attachPublicExitNotify(tracker.sessionId, role, deps.logger);
1392
+ attachPublicExitNotify(tracker.sessionId, parsedRole.role, deps.logger);
1296
1393
  }
1297
1394
  }
1298
1395
  attachSpecialistEndTurnAutoArchive(deps, tracker.sessionId, tracker.specialist);
@@ -1304,7 +1401,10 @@ export function buildHttpApp(deps) {
1304
1401
  }, {
1305
1402
  senderId,
1306
1403
  userId,
1307
- role: role,
1404
+ // Task 2325 — the PERSONA role the spawn composes from, resolved by
1405
+ // the boundary parse above. No cast: `agentRole` is non-null past the
1406
+ // guard. The wire role stays on the sidecar (pty-spawner maps it back).
1407
+ role: agentRole,
1308
1408
  channel: channel,
1309
1409
  channels,
1310
1410
  permissionMode,
@@ -1324,9 +1424,9 @@ export function buildHttpApp(deps) {
1324
1424
  visitorId,
1325
1425
  webchatChannel,
1326
1426
  waChannel,
1427
+ telegramChannel,
1327
1428
  ...sessionIdFields,
1328
1429
  previousContext,
1329
- passive,
1330
1430
  targetAccountId,
1331
1431
  // Task 2271 — the RESOLVED variable, not the parsed field: when the
1332
1432
  // same-slug rule declines the re-scope, the sidecar stays at the house
@@ -2692,7 +2792,11 @@ export function buildHttpApp(deps) {
2692
2792
  // session-end review) supply no channel and get no sidecar. Task 852 widens
2693
2793
  // the accepted role to 'public' (authenticated-public WhatsApp person) and
2694
2794
  // the sidecar records the actual role.
2695
- const rcRole = body.role === 'admin' || body.role === 'public' ? body.role : undefined;
2795
+ // Task 2325 the BODY's role. Null when absent or unrecognised. A
2796
+ // channel-bound resume legitimately omits it and inherits from its prior
2797
+ // sidecar, so the refusal is not here; it is where both sources have been
2798
+ // consulted, just below `priorSidecar`.
2799
+ const rcRole = parseRcSpawnRole(body);
2696
2800
  const adminChannel = typeof body.channel === 'string' && CHANNELS.includes(body.channel)
2697
2801
  ? body.channel
2698
2802
  : undefined;
@@ -3035,11 +3139,67 @@ export function buildHttpApp(deps) {
3035
3139
  // yields null; the gate degrades to the not-resolvable identity form.
3036
3140
  // Task 1214 — widened from webchat-only to any channel-bound session so the
3037
3141
  // admin-role signal resolves for WhatsApp/Telegram resumes that omit role
3038
- // too (the voice-contract gate below keys on `adminBinding`). Every
3039
- // channel-less resume still skips the disk touch.
3040
- const priorSidecar = sessionId && (effectiveWebchatChannel || effectiveWaChannel || effectiveTelegramChannel)
3041
- ? readSidecar(sidecarPathForSessionId(effectiveAccountDir, sessionId), deps.logger).sidecar
3142
+ // too (the voice-contract gate below keys on `adminBinding`).
3143
+ // Task 2325 — widened again to EVERY resume, channel-bound or not, so the
3144
+ // role can be inherited from disk before the refusal below. The dropped
3145
+ // "channel-less resumes skip the disk touch" optimisation cost one small
3146
+ // readFileSync on a path that then spawns a PTY. Without it, the bare
3147
+ // `{ sessionId }` resumes that wedged-session recovery (index.ts, the
3148
+ // recoverWedgedSession resumeSpawn) and the re-seat route send would be
3149
+ // refused for carrying no role, when their role has been on disk all along.
3150
+ // Task 2267 — the whole read outcome, not just the sidecar. `readSidecar`
3151
+ // returns `ok` | `missing` | `parse-failed` and both failure verdicts yield
3152
+ // a null sidecar, so collapsing to `.sidecar` here made a damaged file
3153
+ // indistinguishable from a session that legitimately has none. The lockdown
3154
+ // gate below and the resolution line both read the verdict.
3155
+ const priorSidecarRead = sessionId
3156
+ ? readSidecar(sidecarPathForSessionId(effectiveAccountDir, sessionId), deps.logger)
3042
3157
  : null;
3158
+ const priorSidecar = priorSidecarRead?.sidecar ?? null;
3159
+ // Task 2325 — the session's role, from the body or inherited from disk.
3160
+ // The refusal sits here rather than at the body parse because an absent
3161
+ // `role` has THREE legitimate readings and only one of them is an error:
3162
+ // 1. a channel-bound resume, which inherits from `priorSidecar` (Task
3163
+ // 2260's own words: "a resume omits `role` in the body");
3164
+ // 2. a one-shot admin job, which is not a channel conversation — those
3165
+ // now pass role:'admin' and are told apart by their absent `channel`,
3166
+ // which is what the classification-sidecar gate below reads;
3167
+ // 3. a caller that simply supplied none on a FRESH spawn, which is the
3168
+ // error. Before this it became `undefined` and rode on, and
3169
+ // `sessionRole=undefined` was documented (Task 1468) as a value to
3170
+ // preserve rather than treated as a fault.
3171
+ //
3172
+ // The `mode === 'fresh'` half of the gate is load-bearing, not caution. A
3173
+ // resume that inherits nothing is the claude.ai/code bridge: the bare
3174
+ // admin-host branch of session-rc-spawn.ts posts `{ sessionId, name }`
3175
+ // with no role and no channel, and wedged-session recovery posts
3176
+ // `{ sessionId }`. Both resume a session whose role predates the request.
3177
+ // Refusing them would take out a core operator surface to close a hole
3178
+ // that only a fresh spawn can open: a resume composes no new persona,
3179
+ // and rc-spawn composes none at all.
3180
+ const effectiveRcRole = rcRole ?? priorSidecar?.role ?? null;
3181
+ // Task 2325 — recorded before the refusal so a refused spawn is counted.
3182
+ deps.roleCensus?.record(effectiveRcRole);
3183
+ if (effectiveRcRole === null && mode === 'fresh') {
3184
+ deps.logger(`[rc-spawn] op=role-unresolved sessionId=${sessionId ? sessionId.slice(0, 8) : 'new'} role=${String(body.role)} site=rc-spawn`);
3185
+ timed(deps.logger, 'POST', '/rc-spawn', 400, Date.now() - start);
3186
+ return c.json({ error: 'invalid-arguments', detail: { role: body.role ?? null } }, 400);
3187
+ }
3188
+ // Task 2267 — one line per channel-bound rc-spawn naming where the role came
3189
+ // from. Emitted only for a channel-bound spawn: a one-shot admin job has no
3190
+ // channel and no sidecar, so a line on every rc-spawn would be dominated by
3191
+ // that benign case. `source=unresolved` is the fail-closed spawn, and the
3192
+ // outcome behind it separates a deleted sidecar from a corrupted one.
3193
+ // Hoisted from the Task 1214 voice gate below, which reads the same value
3194
+ // and now shares this one binding.
3195
+ const channelBound = !!(effectiveWebchatChannel || effectiveWaChannel || effectiveTelegramChannel);
3196
+ if (sessionId && channelBound) {
3197
+ deps.logger(formatRoleResolutionLine({
3198
+ sessionId,
3199
+ source: rcRole ? 'body' : priorSidecar?.role ? 'sidecar' : 'unresolved',
3200
+ outcome: priorSidecarRead?.outcome ?? 'missing',
3201
+ }));
3202
+ }
3043
3203
  // Task 850 — assemble the bound target set and choose the channel server:
3044
3204
  // - ≥2 kinds: ONE unified `channel` server (one reply tool fanning to every
3045
3205
  // target's existing gateway endpoint). The mature per-kind servers are
@@ -3136,7 +3296,9 @@ export function buildHttpApp(deps) {
3136
3296
  gatewayUrl: effectiveTelegramChannel.gatewayUrl,
3137
3297
  serverPath: effectiveTelegramChannel.serverPath,
3138
3298
  nodeBin: deps.nodeBin,
3139
- role: rcRole,
3299
+ // Task 2325 — the body role, unchanged in meaning; `?? undefined`
3300
+ // only bridges null to the optional parameter this builder declares.
3301
+ role: rcRole ?? undefined,
3140
3302
  });
3141
3303
  telegramPath = telegramChannelMcpConfigPath(sessionId, tmpdir());
3142
3304
  try {
@@ -3158,9 +3320,28 @@ export function buildHttpApp(deps) {
3158
3320
  // the first. `priorSidecar` is already read for any channel-bound session,
3159
3321
  // which is every public spawn that can reply. Pushed after the channel argv
3160
3322
  // so `--strict-mcp-config` leaves exactly the per-spawn channel server
3161
- // registered; `admin` and roleless spawns produce byte-identical argv.
3162
- const effectiveRcRole = rcRole ?? priorSidecar?.role;
3163
- const publicLockdown = effectiveRcRole === 'public' ? buildRcPublicLockdownArgv(channelServerName) : null;
3323
+ // registered; an `admin` spawn produces byte-identical argv.
3324
+ // Task 2325 resolved once, above, beside the refusal.
3325
+ //
3326
+ // Task 2267 — a null role on a channel-bound spawn takes the lockdown too.
3327
+ // `effectiveRcRole` is null when neither the body nor the sidecar produced a
3328
+ // role, and a live public session always had a sidecar written at spawn
3329
+ // (pty-spawner kills the PTY when that write throws), so null on a
3330
+ // channel-bound session means the file was damaged after the spawn
3331
+ // succeeded, not that the session is legitimately roleless. Reaching here
3332
+ // with null is always a resume: a fresh spawn with no resolvable role is
3333
+ // refused with a 400 above, so no mode test is added. A roleless spawn with
3334
+ // NO channel binding — the one-shot admin jobs and the bare claude.ai/code
3335
+ // bridge resume — still produces byte-identical argv.
3336
+ //
3337
+ // The cost is stated rather than hidden. A channel-bound ADMIN resume whose
3338
+ // sidecar is unreadable comes back holding only its channel reply tool, and
3339
+ // stays that way on every later resume, because the sidecar write below is
3340
+ // itself gated on a non-null role and so never repairs the file. The
3341
+ // `op=role-resolution` line above is what makes that state greppable.
3342
+ const publicLockdown = effectiveRcRole === 'public' || (effectiveRcRole === null && channelBound)
3343
+ ? buildRcPublicLockdownArgv(channelServerName)
3344
+ : null;
3164
3345
  if (publicLockdown) {
3165
3346
  // A re-seat fork pushes its own caller-picked --permission-mode above,
3166
3347
  // before the role is known. Two conflicting flags do not reliably
@@ -3202,7 +3383,9 @@ export function buildHttpApp(deps) {
3202
3383
  // overlay: a missing/empty/headingless file degrades to no voice segment and
3203
3384
  // never fails the spawn. `voiceBytes` rides the op=argv line as the
3204
3385
  // at-a-glance present/absent signal whose absence hid this bug.
3205
- const channelBound = !!(effectiveWebchatChannel || effectiveWaChannel || effectiveTelegramChannel);
3386
+ // Task 2267 — `channelBound` was declared here; it is now resolved once,
3387
+ // beside the role resolution above, because the public lockdown gate needs
3388
+ // the same value earlier. Same expression, same meaning.
3206
3389
  let voiceBytes = 0;
3207
3390
  if (adminBinding && channelBound) {
3208
3391
  let voice = null;
@@ -3279,7 +3462,25 @@ export function buildHttpApp(deps) {
3279
3462
  // (effectiveAccountId !== house) spawns — fail-closed, no escalation.
3280
3463
  const acctIsHouse = isHouseAccount(deps.platformRoot, effectiveAccountId);
3281
3464
  const rcHouseAdminScope = houseAdminScopeFor({
3282
- role: rcRole,
3465
+ // Task 2335 — the SESSION's role (body, else prior sidecar), not the
3466
+ // body's `rcRole` alone. A resume that omits the role — the stuck-turn
3467
+ // recycler posts `{ sessionId, targetAccountId }` — used to fail the
3468
+ // `role === 'admin'` test and come back with no cross-account authority
3469
+ // while the same session's fresh spawn carried it. That divergence was
3470
+ // measured on one house session as a single `inject=false` line between
3471
+ // four `inject=true` lines, on the recycle resume 21s after a wedge kill.
3472
+ // Inheriting is not a widening, for two reasons that hold together. The
3473
+ // sidecar is read from `sidecarPathForSessionId(effectiveAccountDir, …)`
3474
+ // above — the SAME account that decides `isHouse` — so an inherited role
3475
+ // can only come from the resolved account's own record of this session;
3476
+ // a sub-account session resumed against the house account finds no
3477
+ // sidecar there and resolves null. And that record is read back through
3478
+ // the `['admin','public']` allowlist in `parseSidecar`, which rejects the
3479
+ // whole row on anything else, so the only role that can arrive here from
3480
+ // disk and open the gate is the `admin` this session's own fresh spawn
3481
+ // asserted on this account. `isHouse` still excludes every sub-account
3482
+ // resume on top of that.
3483
+ role: effectiveRcRole,
3283
3484
  accountId: effectiveAccountId,
3284
3485
  isHouse: acctIsHouse,
3285
3486
  isSpecialist: false,
@@ -3290,7 +3491,12 @@ export function buildHttpApp(deps) {
3290
3491
  // line makes the rc-spawn decision greppable per spawn.
3291
3492
  deps.logger(formatHouseScopeInjectLine({
3292
3493
  sessionId,
3293
- sessionRole: rcRole,
3494
+ // Task 2325 — the session's role, so this line can no longer render
3495
+ // `sessionRole=undefined`. A resume that inherits no role renders the
3496
+ // explicit token `none`, matching the idiom this handler already uses
3497
+ // for the same value below. A stringified absence read as a formatting
3498
+ // artefact, which is how an unnamed role stayed invisible for two tasks.
3499
+ sessionRole: effectiveRcRole ?? 'none',
3294
3500
  acctIsHouse,
3295
3501
  inject: rcHouseAdminScope !== null,
3296
3502
  }));
@@ -3424,6 +3630,15 @@ export function buildHttpApp(deps) {
3424
3630
  strictMcp: publicLockdown !== null,
3425
3631
  mode: effectiveRcPermissionMode,
3426
3632
  }));
3633
+ // Task 2339 — the realised-surface hook's only input. `tool-allowlist` above
3634
+ // records the surface this spawn was GIVEN; nothing recorded the surface it
3635
+ // REALISES, so five tools ran under a one-tool allowlist and produced no
3636
+ // line. Stamped here rather than inside buildRcChildEnv so it lands after
3637
+ // the last argv push and the value is this spawn's final `--allowed-tools`
3638
+ // set. Public spawns only; its presence is the signal the hook keys on.
3639
+ if (publicLockdown) {
3640
+ env[PUBLIC_ALLOWED_TOOLS_ENV] = allowedToolsFromArgv(argv).join(',');
3641
+ }
3427
3642
  deps.logger(`[rc-spawn] op=argv unitToken=${unitToken} cwd=${effectiveAccountDir} permissionMode=${effectiveRcPermissionMode} model=${effectiveRcModel ?? 'unset'} effort=${effectiveRcEffort ?? 'unset'} positionalBytes=${positionalBytes} voiceBytes=${voiceBytes} argv=${JSON.stringify([deps.claudeBin, ...argv])}`);
3428
3643
  emitRcLife(deps.logger, 'argv', 'rc-spawn', 'node-pty', rcLifeMode, unitToken, {
3429
3644
  cwd: effectiveAccountDir,
@@ -3509,18 +3724,31 @@ export function buildHttpApp(deps) {
3509
3724
  // when one exists. Non-fatal: the PTY here is already serving a live thread —
3510
3725
  // a failed classification sidecar must not tear it down, so it logs and the
3511
3726
  // session keeps running (the pre-fix reader-blind behaviour, no worse).
3512
- const classifyChannel = rcRole !== undefined ? adminChannel : undefined;
3727
+ // Task 2325 the sidecar is written for a channel conversation. What
3728
+ // distinguishes one from a one-shot admin job is the CHANNEL, not whether
3729
+ // a role was supplied: the LinkedIn ingest and the public session-end
3730
+ // review are role:'admin' and pass no channel, and they still get no
3731
+ // sidecar. Before this the absent role carried both meanings at once.
3732
+ const classifyChannel = adminChannel;
3513
3733
  const classifySenderId = adminSenderId ?? effectiveWaChannel?.senderId ?? effectiveWebchatChannel?.key ?? effectiveTelegramChannel?.senderId;
3514
- if (sessionId && classifyChannel && classifySenderId) {
3734
+ // Task 2325 `effectiveRcRole !== null` joins the guard rather than being
3735
+ // cast away. The sidecar's `role` is read back through an allowlist that
3736
+ // rejects the whole row on a bad value, so a session with no resolvable
3737
+ // role gets NO sidecar rather than one recording an absence.
3738
+ if (sessionId && classifyChannel && classifySenderId && effectiveRcRole !== null) {
3515
3739
  const classifySidecarPath = sidecarPathForSessionId(effectiveAccountDir, sessionId);
3516
3740
  if (!existsSync(classifySidecarPath)) {
3517
3741
  try {
3518
3742
  writeSidecar(classifySidecarPath, {
3519
3743
  senderId: classifySenderId,
3520
- // Task 852 — the actual role from the body ('admin' | 'public'),
3521
- // not a hardcoded 'admin'; rcRole is defined whenever
3522
- // classifyChannel is.
3523
- role: rcRole,
3744
+ // Task 852 — the actual role, not a hardcoded 'admin'. Task 2325
3745
+ // the effective role (body, else inherited from disk),
3746
+ // non-null by the guard above. The old
3747
+ // `rcRole as 'admin' | 'public'` relied on this gate being
3748
+ // role-derived; the gate now reads the channel, so without the
3749
+ // guard a resume with a channel and no body role would have
3750
+ // written a cast `undefined` into the sidecar.
3751
+ role: effectiveRcRole,
3524
3752
  channel: classifyChannel,
3525
3753
  url: null,
3526
3754
  startedAt: new Date().toISOString(),
@@ -3562,6 +3790,11 @@ export function buildHttpApp(deps) {
3562
3790
  // into the child env above (rcHouseAdminScope), so env and sidecar
3563
3791
  // are one decision with two consumers and can never drift again.
3564
3792
  houseAdminScope: rcHouseAdminScope,
3793
+ // Task 2338 — the route that produced this process. Only
3794
+ // /public-spawn composes a persona, so a `public` role recorded
3795
+ // here with route `rc-spawn` is a session running without its
3796
+ // soul file, which is what the persona audit counts.
3797
+ spawnRoute: 'rc-spawn',
3565
3798
  }, deps.logger);
3566
3799
  deps.watcher.refreshSidecar?.(sessionId);
3567
3800
  }
@@ -3578,6 +3811,34 @@ export function buildHttpApp(deps) {
3578
3811
  deps.watcher.refreshSidecar?.(sessionId);
3579
3812
  }
3580
3813
  }
3814
+ // Task 2338 — the route stamp sits OUTSIDE the classification block above,
3815
+ // and that placement is the whole point. That block is gated on a channel
3816
+ // in the request BODY (`adminChannel`, :3037), and the resume shapes that
3817
+ // matter here carry none: the claude.ai/code bridge posts
3818
+ // `{ sessionId, name }` and wedged-session recovery posts `{ sessionId }`
3819
+ // (see the note at :3390). Inside the block, a bare resume of a public
3820
+ // session would leave `spawnRoute` reading `public-spawn` and the persona
3821
+ // audit would report zero through a live defect — which is exactly the
3822
+ // 2026-08-01 sequence this task exists to make visible.
3823
+ //
3824
+ // Patched, never written fresh: with no prior sidecar there is no row to
3825
+ // record a route on, and inventing one here would bypass the role/channel
3826
+ // allowlist the write above enforces. Patched only when the value would
3827
+ // change, so a repeated rc-spawn resume does not rewrite the file each turn.
3828
+ if (sessionId && priorSidecar && priorSidecar.spawnRoute !== 'rc-spawn') {
3829
+ const routeSidecarPath = sidecarPathForSessionId(effectiveAccountDir, sessionId);
3830
+ try {
3831
+ const patched = updateSidecar(routeSidecarPath, { spawnRoute: 'rc-spawn' }, deps.logger);
3832
+ if (patched)
3833
+ deps.watcher.refreshSidecar?.(sessionId);
3834
+ }
3835
+ catch (err) {
3836
+ // Non-fatal, same contract as the classification write: the PTY here is
3837
+ // already serving a live thread and a diagnostic write must not tear it
3838
+ // down.
3839
+ deps.logger(`[rc-spawn] op=spawn-route-patch-failed sessionId=${sessionId.slice(0, 8)} err=${JSON.stringify(err instanceof Error ? err.message : String(err))}`);
3840
+ }
3841
+ }
3581
3842
  // Task 626 — one-shot job (LinkedIn ingest, public session-end review):
3582
3843
  // stop the PTY once its single assistant turn lands `end_turn`. closeAfterTurn
3583
3844
  // implies a sessionId (validated above), so trackerKey === sessionId and the