@rubytech/create-maxy-code 0.1.536 → 0.1.538

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 (215) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/hooks/__tests__/mailbox-inject.test.sh +11 -5
  3. package/payload/platform/plugins/admin/hooks/mailbox-inject.sh +12 -7
  4. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +135 -4
  5. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
  6. package/payload/platform/plugins/docs/PLUGIN.md +2 -0
  7. package/payload/platform/plugins/docs/references/admin-ui.md +54 -3
  8. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +75 -0
  9. package/payload/platform/plugins/whatsapp/PLUGIN.md +2 -0
  10. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +22 -7
  11. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  12. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +16 -0
  13. package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +1 -1
  14. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +19 -3
  15. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  16. package/payload/platform/services/claude-session-manager/dist/http-server.js +67 -4
  17. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  18. package/payload/platform/services/claude-session-manager/dist/index.js +23 -2
  19. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  20. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +6 -0
  21. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  22. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +22 -3
  23. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  24. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts +8 -2
  25. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
  26. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +32 -27
  27. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
  28. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +1 -1
  29. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  30. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +1 -1
  31. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  32. package/payload/platform/services/whatsapp-channel/dist/targets.js +5 -1
  33. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  34. package/payload/server/{chunk-44Z4ZFYS.js → chunk-FQO5RDWZ.js} +2 -0
  35. package/payload/server/{manager-AJSPM6X2.js → manager-EBZQRV5V.js} +1 -1
  36. package/payload/server/public/activity.html +5 -5
  37. package/payload/server/public/agents.html +4 -4
  38. package/payload/server/public/assets/{AdminLoginScreens-BpHVvUsH.js → AdminLoginScreens-CfXQwKIY.js} +1 -1
  39. package/payload/server/public/assets/AdminLoginScreens-CfXQwKIY.js.br +0 -0
  40. package/payload/server/public/assets/AdminLoginScreens-CfXQwKIY.js.gz +0 -0
  41. package/payload/server/public/assets/AdminShell-y0_mzlDF.js +2 -0
  42. package/payload/server/public/assets/AdminShell-y0_mzlDF.js.br +0 -0
  43. package/payload/server/public/assets/AdminShell-y0_mzlDF.js.gz +0 -0
  44. package/payload/server/public/assets/{activity-B3WEyiTh.js → activity-h8-sjHz9.js} +1 -1
  45. package/payload/server/public/assets/activity-h8-sjHz9.js.br +0 -0
  46. package/payload/server/public/assets/activity-h8-sjHz9.js.gz +0 -0
  47. package/payload/server/public/assets/admin-BzhhLC7C.js +1 -0
  48. package/payload/server/public/assets/admin-BzhhLC7C.js.br +0 -0
  49. package/payload/server/public/assets/admin-BzhhLC7C.js.gz +0 -0
  50. package/payload/server/public/assets/{agents-CmYDQbdM.js → agents-DLuhIIgn.js} +1 -1
  51. package/payload/server/public/assets/agents-DLuhIIgn.js.br +0 -0
  52. package/payload/server/public/assets/agents-DLuhIIgn.js.gz +0 -0
  53. package/payload/server/public/assets/{browser-CP8TnIUm.js → browser-FAnTY27n.js} +1 -1
  54. package/payload/server/public/assets/browser-FAnTY27n.js.br +0 -0
  55. package/payload/server/public/assets/browser-FAnTY27n.js.gz +0 -0
  56. package/payload/server/public/assets/calendar-C_PkqlkQ.js +1 -0
  57. package/payload/server/public/assets/calendar-C_PkqlkQ.js.br +0 -0
  58. package/payload/server/public/assets/calendar-C_PkqlkQ.js.gz +0 -0
  59. package/payload/server/public/assets/chat-DW81Ra89.js +1 -0
  60. package/payload/server/public/assets/chat-DW81Ra89.js.br +0 -0
  61. package/payload/server/public/assets/chat-DW81Ra89.js.gz +0 -0
  62. package/payload/server/public/assets/chevron-left-zV-WRzHb.js +1 -0
  63. package/payload/server/public/assets/chevron-left-zV-WRzHb.js.br +0 -0
  64. package/payload/server/public/assets/chevron-right-fnINU4bx.js +1 -0
  65. package/payload/server/public/assets/chevron-right-fnINU4bx.js.br +0 -0
  66. package/payload/server/public/assets/clock-2fSxBl6d.js +1 -0
  67. package/payload/server/public/assets/clock-2fSxBl6d.js.br +0 -0
  68. package/payload/server/public/assets/copy-Rde_TL9Q.js +1 -0
  69. package/payload/server/public/assets/copy-Rde_TL9Q.js.br +0 -0
  70. package/payload/server/public/assets/copy-Rde_TL9Q.js.gz +0 -0
  71. package/payload/server/public/assets/data-S5g4wIDe.js +1 -0
  72. package/payload/server/public/assets/data-S5g4wIDe.js.br +0 -0
  73. package/payload/server/public/assets/data-S5g4wIDe.js.gz +0 -0
  74. package/payload/server/public/assets/{file-text-lsHlk4lR.js → file-text-DMCUVg80.js} +1 -1
  75. package/payload/server/public/assets/file-text-DMCUVg80.js.br +0 -0
  76. package/payload/server/public/assets/file-text-DMCUVg80.js.gz +0 -0
  77. package/payload/server/public/assets/graph-Bo-WcsKS.js +51 -0
  78. package/payload/server/public/assets/graph-Bo-WcsKS.js.br +0 -0
  79. package/payload/server/public/assets/graph-Bo-WcsKS.js.gz +0 -0
  80. package/payload/server/public/assets/{graph-labels-Ci5Jz1oY.js → graph-labels-g-JodMWQ.js} +1 -1
  81. package/payload/server/public/assets/graph-labels-g-JodMWQ.js.br +0 -0
  82. package/payload/server/public/assets/graph-labels-g-JodMWQ.js.gz +0 -0
  83. package/payload/server/public/assets/operator-X_LcOnAI.js +1 -0
  84. package/payload/server/public/assets/operator-X_LcOnAI.js.br +0 -0
  85. package/payload/server/public/assets/operator-X_LcOnAI.js.gz +0 -0
  86. package/payload/server/public/assets/page-Cz0p1mBf.js +1 -0
  87. package/payload/server/public/assets/page-Cz0p1mBf.js.br +0 -0
  88. package/payload/server/public/assets/page-Cz0p1mBf.js.gz +0 -0
  89. package/payload/server/public/assets/page-Dzs3MsFL.js +32 -0
  90. package/payload/server/public/assets/page-Dzs3MsFL.js.br +0 -0
  91. package/payload/server/public/assets/page-Dzs3MsFL.js.gz +0 -0
  92. package/payload/server/public/assets/play-CraN5kUU.js +1 -0
  93. package/payload/server/public/assets/play-CraN5kUU.js.br +0 -0
  94. package/payload/server/public/assets/play-CraN5kUU.js.gz +0 -0
  95. package/payload/server/public/assets/{public-BNAVdC2B.js → public--NUjaJ-q.js} +1 -1
  96. package/payload/server/public/assets/public--NUjaJ-q.js.br +0 -0
  97. package/payload/server/public/assets/public--NUjaJ-q.js.gz +0 -0
  98. package/payload/server/public/assets/{rotate-ccw-D6DZb7tR.js → rotate-ccw-DshFgg2T.js} +1 -1
  99. package/payload/server/public/assets/rotate-ccw-DshFgg2T.js.br +0 -0
  100. package/payload/server/public/assets/rotate-ccw-DshFgg2T.js.gz +0 -0
  101. package/payload/server/public/assets/routines-CfAS4gri.js +2 -0
  102. package/payload/server/public/assets/routines-CfAS4gri.js.br +0 -0
  103. package/payload/server/public/assets/routines-CfAS4gri.js.gz +0 -0
  104. package/payload/server/public/assets/{skills-9S_nNIpO.js → skills-CM6ryWpx.js} +1 -1
  105. package/payload/server/public/assets/skills-CM6ryWpx.js.br +0 -0
  106. package/payload/server/public/assets/skills-CM6ryWpx.js.gz +0 -0
  107. package/payload/server/public/assets/tasks-B07m9LIA.js +3 -0
  108. package/payload/server/public/assets/tasks-B07m9LIA.js.br +0 -0
  109. package/payload/server/public/assets/tasks-B07m9LIA.js.gz +0 -0
  110. package/payload/server/public/assets/{triangle-alert-BcpCZDHe.js → triangle-alert-CKHyS_gC.js} +1 -1
  111. package/payload/server/public/assets/triangle-alert-CKHyS_gC.js.br +0 -0
  112. package/payload/server/public/assets/triangle-alert-CKHyS_gC.js.gz +0 -0
  113. package/payload/server/public/assets/{useCopyFeedback-DqTnYnz5.js → useCopyFeedback-CmawEsVi.js} +1 -1
  114. package/payload/server/public/assets/useCopyFeedback-CmawEsVi.js.br +0 -0
  115. package/payload/server/public/assets/useCopyFeedback-CmawEsVi.js.gz +0 -0
  116. package/payload/server/public/assets/useSubAccountSwitcher-CVHqKsqK.js +14 -0
  117. package/payload/server/public/assets/useSubAccountSwitcher-CVHqKsqK.js.br +0 -0
  118. package/payload/server/public/assets/useSubAccountSwitcher-CVHqKsqK.js.gz +0 -0
  119. package/payload/server/public/assets/{useSubAccountSwitcher-CYEDydoq.css → useSubAccountSwitcher-yN4lhH2x.css} +1 -1
  120. package/payload/server/public/assets/useSubAccountSwitcher-yN4lhH2x.css.br +0 -0
  121. package/payload/server/public/assets/{useSubAccountSwitcher-CYEDydoq.css.gz → useSubAccountSwitcher-yN4lhH2x.css.gz} +0 -0
  122. package/payload/server/public/assets/useVoiceRecorder-DpQTvvqD.js +2 -0
  123. package/payload/server/public/assets/useVoiceRecorder-DpQTvvqD.js.br +0 -0
  124. package/payload/server/public/assets/useVoiceRecorder-DpQTvvqD.js.gz +0 -0
  125. package/payload/server/public/assets/{wrench-DvUj2R3p.js → wrench-BPOEUNJB.js} +1 -1
  126. package/payload/server/public/assets/wrench-BPOEUNJB.js.br +0 -0
  127. package/payload/server/public/assets/wrench-BPOEUNJB.js.gz +0 -0
  128. package/payload/server/public/browser.html +4 -4
  129. package/payload/server/public/calendar.html +7 -7
  130. package/payload/server/public/chat.html +13 -13
  131. package/payload/server/public/data.html +11 -11
  132. package/payload/server/public/graph.html +9 -9
  133. package/payload/server/public/index.html +14 -14
  134. package/payload/server/public/operator.html +14 -14
  135. package/payload/server/public/public.html +13 -13
  136. package/payload/server/public/routines.html +6 -6
  137. package/payload/server/public/skills.html +5 -5
  138. package/payload/server/public/tasks.html +6 -6
  139. package/payload/server/server.js +989 -511
  140. package/payload/server/public/assets/AdminLoginScreens-BpHVvUsH.js.br +0 -0
  141. package/payload/server/public/assets/AdminLoginScreens-BpHVvUsH.js.gz +0 -0
  142. package/payload/server/public/assets/AdminShell-BT7VQIu7.js +0 -2
  143. package/payload/server/public/assets/AdminShell-BT7VQIu7.js.br +0 -0
  144. package/payload/server/public/assets/AdminShell-BT7VQIu7.js.gz +0 -0
  145. package/payload/server/public/assets/activity-B3WEyiTh.js.br +0 -0
  146. package/payload/server/public/assets/activity-B3WEyiTh.js.gz +0 -0
  147. package/payload/server/public/assets/admin-BEKL9Bq_.js +0 -1
  148. package/payload/server/public/assets/admin-BEKL9Bq_.js.br +0 -0
  149. package/payload/server/public/assets/admin-BEKL9Bq_.js.gz +0 -0
  150. package/payload/server/public/assets/agents-CmYDQbdM.js.br +0 -0
  151. package/payload/server/public/assets/agents-CmYDQbdM.js.gz +0 -0
  152. package/payload/server/public/assets/browser-CP8TnIUm.js.br +0 -0
  153. package/payload/server/public/assets/browser-CP8TnIUm.js.gz +0 -0
  154. package/payload/server/public/assets/calendar-DM6Bl8Ra.js +0 -1
  155. package/payload/server/public/assets/calendar-DM6Bl8Ra.js.br +0 -0
  156. package/payload/server/public/assets/calendar-DM6Bl8Ra.js.gz +0 -0
  157. package/payload/server/public/assets/chat-BJWdNwCN.js +0 -1
  158. package/payload/server/public/assets/chat-BJWdNwCN.js.br +0 -2
  159. package/payload/server/public/assets/chat-BJWdNwCN.js.gz +0 -0
  160. package/payload/server/public/assets/chevron-left-DU8Es8Xq.js +0 -1
  161. package/payload/server/public/assets/chevron-right-D8HTOkyy.js +0 -1
  162. package/payload/server/public/assets/chevron-right-D8HTOkyy.js.br +0 -0
  163. package/payload/server/public/assets/clock-BdNuUyUU.js +0 -1
  164. package/payload/server/public/assets/clock-BdNuUyUU.js.br +0 -0
  165. package/payload/server/public/assets/clock-BdNuUyUU.js.gz +0 -0
  166. package/payload/server/public/assets/data-EjLIuy-W.js +0 -1
  167. package/payload/server/public/assets/data-EjLIuy-W.js.br +0 -0
  168. package/payload/server/public/assets/data-EjLIuy-W.js.gz +0 -0
  169. package/payload/server/public/assets/file-text-lsHlk4lR.js.br +0 -0
  170. package/payload/server/public/assets/file-text-lsHlk4lR.js.gz +0 -0
  171. package/payload/server/public/assets/graph-B0awsgVj.js +0 -51
  172. package/payload/server/public/assets/graph-B0awsgVj.js.br +0 -0
  173. package/payload/server/public/assets/graph-B0awsgVj.js.gz +0 -0
  174. package/payload/server/public/assets/graph-labels-Ci5Jz1oY.js.br +0 -0
  175. package/payload/server/public/assets/graph-labels-Ci5Jz1oY.js.gz +0 -0
  176. package/payload/server/public/assets/maximize-2-DRndYysc.js +0 -1
  177. package/payload/server/public/assets/maximize-2-DRndYysc.js.br +0 -0
  178. package/payload/server/public/assets/maximize-2-DRndYysc.js.gz +0 -0
  179. package/payload/server/public/assets/operator-tiI-kz6v.js +0 -1
  180. package/payload/server/public/assets/operator-tiI-kz6v.js.br +0 -0
  181. package/payload/server/public/assets/operator-tiI-kz6v.js.gz +0 -0
  182. package/payload/server/public/assets/page-RkwjxAgF.js +0 -32
  183. package/payload/server/public/assets/page-RkwjxAgF.js.br +0 -0
  184. package/payload/server/public/assets/page-RkwjxAgF.js.gz +0 -0
  185. package/payload/server/public/assets/page-ZG7SXMO0.js +0 -1
  186. package/payload/server/public/assets/page-ZG7SXMO0.js.br +0 -0
  187. package/payload/server/public/assets/page-ZG7SXMO0.js.gz +0 -0
  188. package/payload/server/public/assets/public-BNAVdC2B.js.br +0 -0
  189. package/payload/server/public/assets/public-BNAVdC2B.js.gz +0 -0
  190. package/payload/server/public/assets/rotate-ccw-D6DZb7tR.js.br +0 -0
  191. package/payload/server/public/assets/rotate-ccw-D6DZb7tR.js.gz +0 -0
  192. package/payload/server/public/assets/routines-BVcPovu-.js +0 -2
  193. package/payload/server/public/assets/routines-BVcPovu-.js.br +0 -0
  194. package/payload/server/public/assets/routines-BVcPovu-.js.gz +0 -0
  195. package/payload/server/public/assets/skills-9S_nNIpO.js.br +0 -0
  196. package/payload/server/public/assets/skills-9S_nNIpO.js.gz +0 -0
  197. package/payload/server/public/assets/tasks-D-D2efaL.js +0 -3
  198. package/payload/server/public/assets/tasks-D-D2efaL.js.br +0 -0
  199. package/payload/server/public/assets/tasks-D-D2efaL.js.gz +0 -0
  200. package/payload/server/public/assets/time-entry-format-CKpHcWWs.js +0 -1
  201. package/payload/server/public/assets/time-entry-format-CKpHcWWs.js.br +0 -0
  202. package/payload/server/public/assets/time-entry-format-CKpHcWWs.js.gz +0 -0
  203. package/payload/server/public/assets/triangle-alert-BcpCZDHe.js.br +0 -0
  204. package/payload/server/public/assets/triangle-alert-BcpCZDHe.js.gz +0 -0
  205. package/payload/server/public/assets/useCopyFeedback-DqTnYnz5.js.br +0 -0
  206. package/payload/server/public/assets/useCopyFeedback-DqTnYnz5.js.gz +0 -0
  207. package/payload/server/public/assets/useSubAccountSwitcher-CYEDydoq.css.br +0 -0
  208. package/payload/server/public/assets/useSubAccountSwitcher-ny9tP8am.js +0 -14
  209. package/payload/server/public/assets/useSubAccountSwitcher-ny9tP8am.js.br +0 -0
  210. package/payload/server/public/assets/useSubAccountSwitcher-ny9tP8am.js.gz +0 -0
  211. package/payload/server/public/assets/useVoiceRecorder-DcgtZl-4.js +0 -2
  212. package/payload/server/public/assets/useVoiceRecorder-DcgtZl-4.js.br +0 -0
  213. package/payload/server/public/assets/useVoiceRecorder-DcgtZl-4.js.gz +0 -0
  214. package/payload/server/public/assets/wrench-DvUj2R3p.js.br +0 -2
  215. package/payload/server/public/assets/wrench-DvUj2R3p.js.gz +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.536",
3
+ "version": "0.1.538",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -105,14 +105,20 @@ else
105
105
  fi
106
106
  rm -rf "$A"
107
107
 
108
- # --- Case 2: ACCOUNT_DIR unset, cwd is the account dir ----------------------
108
+ # --- Case 2: ACCOUNT_DIR unset (a public spawn) -> silent -------------------
109
+ # Task 2271 — ACCOUNT_DIR is stamped for admin and specialist spawns only
110
+ # (pty-spawner.ts, the `isSpecialistSpawn || role === admin` gate) and
111
+ # unconditionally on the rc-spawn path. Its ABSENCE therefore means a
112
+ # role:public spawn, and a public visitor must never receive the operator's
113
+ # mailbox list. The cwd fallback this case used to assert was handing a stranger
114
+ # the HOUSE's addresses, because a public spawn's cwd is the house account dir.
109
115
  A=$(newacct)
110
116
  OUT=$(cd "$A" && printf '%s' "$STDIN" | env -u ACCOUNT_DIR LOG_DIR="$A/logs" bash "$HOOK" 2>/dev/null); EX=$?
111
- C=$(ctx)
112
- if [[ "$EX" -eq 0 && "$C" == *"<mailboxes>"* ]]; then
113
- ok "ACCOUNT_DIR unset -> account dir resolved from cwd"
117
+ if [[ "$EX" -eq 0 && -z "$OUT" ]] \
118
+ && /usr/bin/grep -q "op=skip reason=no-account-dir" "$A/logs/mailbox-inject.log"; then
119
+ ok "ACCOUNT_DIR unset (public spawn) -> exit 0, no block emitted"
114
120
  else
115
- bad "cwd fallback (exit=$EX ctx=$C)"
121
+ bad "public-spawn silence (exit=$EX stdout=$OUT log=$(cat "$A/logs/mailbox-inject.log" 2>/dev/null))"
116
122
  fi
117
123
  rm -rf "$A"
118
124
 
@@ -37,16 +37,21 @@ command -v node >/dev/null 2>&1 || exit 0
37
37
 
38
38
  # --- account dir -----------------------------------------------------------
39
39
  # ACCOUNT_DIR is stamped unconditionally on the rc-spawn path (rc-daemon.ts
40
- # buildRcChildEnv) but only for admin/specialist on the pty-spawner path
41
- # (pty-spawner.ts, the `isSpecialistSpawn || role === admin` gate), so a
42
- # role:public webchat or passive WhatsApp spawn has none. The spawn cwd IS the
43
- # account dir on both paths (config.ts: spawnCwd defaults to accountDir), which
44
- # is how fs-schema-guard.sh already resolves it.
40
+ # buildRcChildEnv) and, on the pty-spawner path, only for admin/specialist
41
+ # (pty-spawner.ts, the `isSpecialistSpawn || role === admin` gate).
42
+ #
43
+ # Task 2271 requiring it IS the role gate. This hook is wired unconditionally
44
+ # for every account (provision-account-dir.sh) with no role of its own, and the
45
+ # previous `$PWD` fallback existed to serve the spawns that carry no
46
+ # ACCOUNT_DIR. On that path those are exactly the role:public ones: a public
47
+ # WhatsApp or webchat visitor, or a passive intake whose only tool is
48
+ # work-create. None of them should receive the operator's mailbox list, and a
49
+ # public visitor session was receiving the HOUSE's, because a public spawn's cwd
50
+ # is the house account dir. The block's only prior protection was a sentence
51
+ # asking the model not to recite it.
45
52
  ACCT=""
46
53
  if [ -n "${ACCOUNT_DIR:-}" ] && [ -d "${ACCOUNT_DIR}" ]; then
47
54
  ACCT="$ACCOUNT_DIR"
48
- elif [ -f "$PWD/.claude/settings.json" ] && [ -d "$PWD/secrets" ]; then
49
- ACCT="$PWD"
50
55
  fi
51
56
 
52
57
  # --- log dir ---------------------------------------------------------------
@@ -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:46709b4084d565de5c59a8b8a2fcf7450e2ac4202ef08ebafde533dd5cd3385c
4
+ content-hash: sha256:08b03d80446b9b08b4b64926a3ac7d7f55a6f07d4182512dcca1d107c78cc9a2
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -1661,6 +1661,86 @@ Changes are per-account and take effect immediately.
1661
1661
 
1662
1662
  Brand isolation extends to the device filesystem. Every Maxy install provisions an SMB share scoped to that brand's install folder, credentialled by the brand's install owner and the Maxy PIN. A device that hosts more than one brand carries one share per brand; tearing one brand down never exposes another brand's files. See [Samba Share](./samba.md) for the credential model, per-OS mount syntax, and peer-brand lifecycle.
1663
1663
 
1664
+ ---
1665
+ # What Wakes a Session
1666
+ Source: https://docs.getmaxy.com/channel-wake-and-prompt.md
1667
+
1668
+ # What wakes a session, and what is in its prompt
1669
+
1670
+ Two questions an agent cannot answer from inside itself, and both have caused real faults: *what woke me?* and *which files did my prompt come from?* This page answers both. When the answer is not here, the logs have it: `logs-read --tail server`, filtered on the `[whatsapp:*]` tags.
1671
+
1672
+ ## What wakes a session
1673
+
1674
+ Five things start or resume a WhatsApp session. They are distinguishable, and the distinction matters, because two of them are written by the platform rather than by the person named as the sender.
1675
+
1676
+ **An admin phone or an account manager messages the line.** This resumes that person's own admin session. There is no per-account admin session: the session id is a hash of the account plus the person, so one person messaging from two channels resumes one thread, and two managers on one account have two threads.
1677
+
1678
+ **A public sender messages the line.** Three things follow, in this order, and each is independent of the ones after it.
1679
+
1680
+ 1. The account's registered managers are notified, verbatim, unless the account has turned that off. This fires before any agent starts, so it survives a spawn that fails.
1681
+ 2. Every bound manager's own admin session is woken with a `public-relay` turn carrying the sender's message as fenced content. This is the carrier for anything the account needs to do about the visitor: translate, put a request to a registered counterparty, carry the answer back.
1682
+ 3. The account's public agent answers the visitor, if the account has one and has enabled it. If not, the visitor receives nothing at all, and steps 1 and 2 have already happened regardless.
1683
+
1684
+ **The scheduler fires a due event.** This raises a `schedule` turn on the destination's own session, carrying the event's prompt.
1685
+
1686
+ **A passive account-manager binding receives a message.** This spawns a constrained one-shot that files a task and never replies. It cannot hold a conversation or answer against existing state.
1687
+
1688
+ ## Reading why you woke
1689
+
1690
+ Every turn carries a `source`. Three values exist:
1691
+
1692
+ | `source` | Meaning |
1693
+ |---|---|
1694
+ | `user` | A real person sent this message to the line. Also the value when the field is absent. |
1695
+ | `schedule` | The scheduler wrote this turn. The named destination did not send it. |
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
+
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.
1699
+
1700
+ ## Which files compose the prompt
1701
+
1702
+ The system prompt is assembled per spawn from one agent directory, and **which directory depends on the role**:
1703
+
1704
+ - A `role:'public'` spawn reads `agents/<the account's public-agent slug>/`.
1705
+ - Every other role reads `agents/admin/`.
1706
+
1707
+ From that one directory it takes `IDENTITY.md` and `SOUL.md`, verbatim.
1708
+
1709
+ The consequence catches people out: **an instruction written into `agents/admin/SOUL.md` is never read by a public spawn**, and an instruction written into a public agent's `SOUL.md` is never read by an admin turn. Before writing an instruction into a role file, decide which role will actually be running when it needs to apply. If the answer is "the admin agent, woken by a stranger's message", the file is `agents/admin/SOUL.md` and the trigger is the `public-relay` dispatch above.
1710
+
1711
+ A public agent's directory also carries `KNOWLEDGE.md` and `config.json`. An account can hold as many public agents as it likes; only one is bound to WhatsApp at a time, and that binding is per account.
1712
+
1713
+ ## The switches, and what each one stops
1714
+
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.
1716
+
1717
+ | Setting | What it stops | What it leaves running |
1718
+ |---|---|---|
1719
+ | `dmPolicy: disabled` | Public senders are refused at the gate. | Admin and manager inbound. |
1720
+ | `publicAgentEnabled: false` | The visitor gets no reply. | The manager notification and the relay. |
1721
+ | `managerNotifyEnabled: false` | The verbatim copy of the visitor's message. | The relay, and any reply the woken turn sends. |
1722
+ | `dispatchInbound: false` | Every agent turn on that account's socket. | Storage: messages are still stored and readable. |
1723
+
1724
+ `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
+
1726
+ Read the current value with `whatsapp-config {action:'get-manager-notify-enabled'}` and set it with `set-manager-notify-enabled`. Both are scoped to the calling session's own account.
1727
+
1728
+ ## When the answer is not here
1729
+
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:
1731
+
1732
+ ```
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
1740
+ ```
1741
+
1742
+ Three standing audits reconcile that chain rather than waiting for someone to notice. `op=public-notify-reconcile` reports `gap`, `relayGap` and `englishGap`, 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
+
1664
1744
  ---
1665
1745
  # Settings
1666
1746
  Source: https://docs.getmaxy.com/settings.md
@@ -3535,6 +3615,46 @@ scheduling heartbeat and report on every tick including zero:
3535
3615
  `op=dispatch-mode-none` (a routine that fires and delivers nothing) and
3536
3616
  `op=null-timezone` (a routine that fires at the box's hour).
3537
3617
 
3618
+ ### Tasks
3619
+
3620
+ The Tasks surface (`/tasks`) lists the account's `:Task` nodes on Open,
3621
+ Completed and Clients tabs and records billable time against each one. Its
3622
+ routes are all account-scoped:
3623
+
3624
+ | Mount | Purpose |
3625
+ |---|---|
3626
+ | `/tasks-list` | Open/Completed partition, plus the standing censuses described below. |
3627
+ | `/task-timer-start`, `/task-timer-stop` | Open and close a `:TimeEntry`. Timers on different tasks run concurrently; there is no auto-stop. |
3628
+ | `/task-complete` | Status → completed. Does **not** stop a running timer. |
3629
+ | `/task-time-adjust` | Manual time edit. Writes one `:TimeAdjustment`; 409s while the task has an open `:TimeEntry`. |
3630
+ | `/task-delete` | Removes the `:Task` **and** every `:TimeEntry` and `:TimeAdjustment` on it. Nothing is recoverable and there is no restore route. 409s while the task has an open `:TimeEntry`; 404s a wrong-account task. |
3631
+ | `/task-assign`, `/task-session-open`, `/task-session-link` | Assignee writes, and the Session control's spawn-or-resume path. |
3632
+
3633
+ **Row controls.** Each row carries a burger — the same `SessionRowActions`
3634
+ component the sidebar's session rows use — holding Edit time, Reset logged time
3635
+ to 0:00, Session and Delete. Only Complete (icon only) and Start/Stop stay
3636
+ inline, on open rows; a completed row carries the burger alone. A row whose
3637
+ `:Task` has no `taskId` is unactionable and shows a "Needs repair" marker with no
3638
+ burger. While a row's timer runs, Edit time, Reset and Delete are refused and
3639
+ each tooltip says why. Delete opens a confirm anchored beside the row that names
3640
+ the task and its logged time before it writes.
3641
+
3642
+ **Observability.** `[task-delete]` emits one lifeline per deletion keyed by
3643
+ `deletionId`: `op=request` (carrying `confirmed`), `op=guard`, `op=reject
3644
+ reason=…`, `op=persisted` and `op=error`. `op=persisted` carries `remaining=`,
3645
+ a **fresh read after the delete** counting what still holds that `taskId` — the
3646
+ delete's own counters agree with themselves by construction, so only the read-back
3647
+ proves the rows are gone. A non-zero `remaining` also emits `op=incomplete` at
3648
+ error level.
3649
+
3650
+ Separately, `/tasks-list` reconciles time records against tasks on **every**
3651
+ compute and reports `op=orphan-time-records`, or
3652
+ `op=orphan-time-records-census-failed` when the census itself could not run. This
3653
+ is not aimed at `task-delete`, which is atomic and self-verifying: it catches any
3654
+ other path that removes a `:Task` without its records — the graph trash tooling
3655
+ handles `:Task` and knows nothing about `:TimeEntry` or `:TimeAdjustment`, and
3656
+ strands both silently.
3657
+
3538
3658
  ### Skills
3539
3659
 
3540
3660
  The Skills surface (`/skills`) is the admin view of the account's operator-
@@ -4131,9 +4251,20 @@ a browser that is already running it re-ran the same launch check and changed
4131
4251
  nothing anyone could see, so it now appears only when the browser is unreachable
4132
4252
  or has no tabs open.
4133
4253
 
4134
- The globe button in the chat header opens the browser as a second pane beside
4135
- the conversation, and remembers that choice for the next visit. The pane is
4136
- hidden on narrow screens, where there is no room for two columns.
4254
+ The browser opens beside the conversation from the globe button at the top
4255
+ right of the chat header. That button is there only while the browser is
4256
+ closed; once it is open you close it with the cross at the top right of the
4257
+ browser itself, and the button comes back. Beside the cross is a full-screen
4258
+ control that gives the browser the whole width and hides the chat, and the same
4259
+ control returns you to the split.
4260
+
4261
+ Drag the divider between the chat and the browser to change how the width is
4262
+ shared. Double-click it to go back to an even split. The chat can never be
4263
+ pushed below a readable width, and the choice is remembered for next time. The
4264
+ pane is hidden on narrow screens where there is no room for two columns, and
4265
+ the page is rendered at the pane's own size so it fills it.
4266
+
4267
+ There is no Browser item in the sidebar: the browser lives beside the chat.
4137
4268
 
4138
4269
  Tab opening and closing go to the admin server, which is the only thing that can
4139
4270
  reach the browser's control port. Typing an address, going back and going
@@ -9,6 +9,16 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-07-31 (0.1.538)
13
+
14
+ - A message from someone outside your team now also reaches the account's manager in their own chat, written in plain English, and each account can turn the separate raw notification off.
15
+ - The device browser pane can be toggled, opened full screen and resized.
16
+
17
+ ## 2026-07-31 (0.1.537)
18
+
19
+ - A sub-account's public agent now runs entirely in that account: its own folder, its own settings, and no falling back to the main account's agent. A standing check reports an account switched on with no agent of its own.
20
+ - Task rows keep Complete and Start to hand, with the rest behind a menu, and a task can now be deleted along with its time records after a confirm. A standing check reports any time record left behind.
21
+
12
22
  ## 2026-07-31 (0.1.536)
13
23
 
14
24
  - You can now switch on a public agent per account to answer WhatsApp messages from people outside your team, with a standing check that reports an account switched on but pointing at an agent that no longer exists.
@@ -23,6 +23,7 @@ Load these when users ask about Maxy features or need guidance:
23
23
  - **Telegram** → `references/telegram-guide.md` — Telegram setup, the bot, daily use
24
24
  - **Settings** → `references/settings.md` — output style, effort level, account preferences
25
25
  - **Access Control** → `references/access-control.md` — who can chat with your public agent, invitations, authentication
26
+ - **What wakes a session** → `references/channel-wake-and-prompt.md` — the five things that start or resume a WhatsApp session, the `source` value each carries, which agent directory composes the prompt for which role, and the four switches on the public-inbound path. Load before writing an instruction into any role file, or when an agent cannot say why it woke.
26
27
  - **Projects** → `references/projects-guide.md` — creating projects, tracking health, phase transitions, completing
27
28
  - **Workflows** → `references/workflows.md` — saving and running multi-step processes, declared inputs, scheduling, run history
28
29
  - **Slides** → `references/slides.md` — generating, theming, and reviewing HTML slide decks with `/slides`
@@ -51,6 +52,7 @@ Load these when performing admin tasks or diagnosing platform behaviour:
51
52
  - references/telegram-guide.md
52
53
  - references/settings.md
53
54
  - references/access-control.md
55
+ - references/channel-wake-and-prompt.md
54
56
  - references/projects-guide.md
55
57
  - references/troubleshooting.md
56
58
  - references/platform.md
@@ -258,6 +258,46 @@ scheduling heartbeat and report on every tick including zero:
258
258
  `op=dispatch-mode-none` (a routine that fires and delivers nothing) and
259
259
  `op=null-timezone` (a routine that fires at the box's hour).
260
260
 
261
+ ### Tasks
262
+
263
+ The Tasks surface (`/tasks`) lists the account's `:Task` nodes on Open,
264
+ Completed and Clients tabs and records billable time against each one. Its
265
+ routes are all account-scoped:
266
+
267
+ | Mount | Purpose |
268
+ |---|---|
269
+ | `/tasks-list` | Open/Completed partition, plus the standing censuses described below. |
270
+ | `/task-timer-start`, `/task-timer-stop` | Open and close a `:TimeEntry`. Timers on different tasks run concurrently; there is no auto-stop. |
271
+ | `/task-complete` | Status → completed. Does **not** stop a running timer. |
272
+ | `/task-time-adjust` | Manual time edit. Writes one `:TimeAdjustment`; 409s while the task has an open `:TimeEntry`. |
273
+ | `/task-delete` | Removes the `:Task` **and** every `:TimeEntry` and `:TimeAdjustment` on it. Nothing is recoverable and there is no restore route. 409s while the task has an open `:TimeEntry`; 404s a wrong-account task. |
274
+ | `/task-assign`, `/task-session-open`, `/task-session-link` | Assignee writes, and the Session control's spawn-or-resume path. |
275
+
276
+ **Row controls.** Each row carries a burger — the same `SessionRowActions`
277
+ component the sidebar's session rows use — holding Edit time, Reset logged time
278
+ to 0:00, Session and Delete. Only Complete (icon only) and Start/Stop stay
279
+ inline, on open rows; a completed row carries the burger alone. A row whose
280
+ `:Task` has no `taskId` is unactionable and shows a "Needs repair" marker with no
281
+ burger. While a row's timer runs, Edit time, Reset and Delete are refused and
282
+ each tooltip says why. Delete opens a confirm anchored beside the row that names
283
+ the task and its logged time before it writes.
284
+
285
+ **Observability.** `[task-delete]` emits one lifeline per deletion keyed by
286
+ `deletionId`: `op=request` (carrying `confirmed`), `op=guard`, `op=reject
287
+ reason=…`, `op=persisted` and `op=error`. `op=persisted` carries `remaining=`,
288
+ a **fresh read after the delete** counting what still holds that `taskId` — the
289
+ delete's own counters agree with themselves by construction, so only the read-back
290
+ proves the rows are gone. A non-zero `remaining` also emits `op=incomplete` at
291
+ error level.
292
+
293
+ Separately, `/tasks-list` reconciles time records against tasks on **every**
294
+ compute and reports `op=orphan-time-records`, or
295
+ `op=orphan-time-records-census-failed` when the census itself could not run. This
296
+ is not aimed at `task-delete`, which is atomic and self-verifying: it catches any
297
+ other path that removes a `:Task` without its records — the graph trash tooling
298
+ handles `:Task` and knows nothing about `:TimeEntry` or `:TimeAdjustment`, and
299
+ strands both silently.
300
+
261
301
  ### Skills
262
302
 
263
303
  The Skills surface (`/skills`) is the admin view of the account's operator-
@@ -854,9 +894,20 @@ a browser that is already running it re-ran the same launch check and changed
854
894
  nothing anyone could see, so it now appears only when the browser is unreachable
855
895
  or has no tabs open.
856
896
 
857
- The globe button in the chat header opens the browser as a second pane beside
858
- the conversation, and remembers that choice for the next visit. The pane is
859
- hidden on narrow screens, where there is no room for two columns.
897
+ The browser opens beside the conversation from the globe button at the top
898
+ right of the chat header. That button is there only while the browser is
899
+ closed; once it is open you close it with the cross at the top right of the
900
+ browser itself, and the button comes back. Beside the cross is a full-screen
901
+ control that gives the browser the whole width and hides the chat, and the same
902
+ control returns you to the split.
903
+
904
+ Drag the divider between the chat and the browser to change how the width is
905
+ shared. Double-click it to go back to an even split. The chat can never be
906
+ pushed below a readable width, and the choice is remembered for next time. The
907
+ pane is hidden on narrow screens where there is no room for two columns, and
908
+ the page is rendered at the pane's own size so it fills it.
909
+
910
+ There is no Browser item in the sidebar: the browser lives beside the chat.
860
911
 
861
912
  Tab opening and closing go to the admin server, which is the only thing that can
862
913
  reach the browser's control port. Typing an address, going back and going
@@ -0,0 +1,75 @@
1
+ # What wakes a session, and what is in its prompt
2
+
3
+ Two questions an agent cannot answer from inside itself, and both have caused real faults: *what woke me?* and *which files did my prompt come from?* This page answers both. When the answer is not here, the logs have it: `logs-read --tail server`, filtered on the `[whatsapp:*]` tags.
4
+
5
+ ## What wakes a session
6
+
7
+ Five things start or resume a WhatsApp session. They are distinguishable, and the distinction matters, because two of them are written by the platform rather than by the person named as the sender.
8
+
9
+ **An admin phone or an account manager messages the line.** This resumes that person's own admin session. There is no per-account admin session: the session id is a hash of the account plus the person, so one person messaging from two channels resumes one thread, and two managers on one account have two threads.
10
+
11
+ **A public sender messages the line.** Three things follow, in this order, and each is independent of the ones after it.
12
+
13
+ 1. The account's registered managers are notified, verbatim, unless the account has turned that off. This fires before any agent starts, so it survives a spawn that fails.
14
+ 2. Every bound manager's own admin session is woken with a `public-relay` turn carrying the sender's message as fenced content. This is the carrier for anything the account needs to do about the visitor: translate, put a request to a registered counterparty, carry the answer back.
15
+ 3. The account's public agent answers the visitor, if the account has one and has enabled it. If not, the visitor receives nothing at all, and steps 1 and 2 have already happened regardless.
16
+
17
+ **The scheduler fires a due event.** This raises a `schedule` turn on the destination's own session, carrying the event's prompt.
18
+
19
+ **A passive account-manager binding receives a message.** This spawns a constrained one-shot that files a task and never replies. It cannot hold a conversation or answer against existing state.
20
+
21
+ ## Reading why you woke
22
+
23
+ Every turn carries a `source`. Three values exist:
24
+
25
+ | `source` | Meaning |
26
+ |---|---|
27
+ | `user` | A real person sent this message to the line. Also the value when the field is absent. |
28
+ | `schedule` | The scheduler wrote this turn. The named destination did not send it. |
29
+ | `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. |
30
+
31
+ 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.
32
+
33
+ ## Which files compose the prompt
34
+
35
+ The system prompt is assembled per spawn from one agent directory, and **which directory depends on the role**:
36
+
37
+ - A `role:'public'` spawn reads `agents/<the account's public-agent slug>/`.
38
+ - Every other role reads `agents/admin/`.
39
+
40
+ From that one directory it takes `IDENTITY.md` and `SOUL.md`, verbatim.
41
+
42
+ The consequence catches people out: **an instruction written into `agents/admin/SOUL.md` is never read by a public spawn**, and an instruction written into a public agent's `SOUL.md` is never read by an admin turn. Before writing an instruction into a role file, decide which role will actually be running when it needs to apply. If the answer is "the admin agent, woken by a stranger's message", the file is `agents/admin/SOUL.md` and the trigger is the `public-relay` dispatch above.
43
+
44
+ A public agent's directory also carries `KNOWLEDGE.md` and `config.json`. An account can hold as many public agents as it likes; only one is bound to WhatsApp at a time, and that binding is per account.
45
+
46
+ ## The switches, and what each one stops
47
+
48
+ 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.
49
+
50
+ | Setting | What it stops | What it leaves running |
51
+ |---|---|---|
52
+ | `dmPolicy: disabled` | Public senders are refused at the gate. | Admin and manager inbound. |
53
+ | `publicAgentEnabled: false` | The visitor gets no reply. | The manager notification and the relay. |
54
+ | `managerNotifyEnabled: false` | The verbatim copy of the visitor's message. | The relay, and any reply the woken turn sends. |
55
+ | `dispatchInbound: false` | Every agent turn on that account's socket. | Storage: messages are still stored and readable. |
56
+
57
+ `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.
58
+
59
+ Read the current value with `whatsapp-config {action:'get-manager-notify-enabled'}` and set it with `set-manager-notify-enabled`. Both are scoped to the calling session's own account.
60
+
61
+ ## When the answer is not here
62
+
63
+ `logs-read --tail server` and filter on the tags. One public inbound writes this chain, in order, with `account=` and `sender=` on each line:
64
+
65
+ ```
66
+ [whatsapp:wire] op=stanza
67
+ [wa-notify] op=eligible reason=<how the sender was admitted>
68
+ [whatsapp:route] op=routed agentType=public
69
+ [whatsapp:notify] op=public-inbound enabled=0|1 outcome=sent|failed|no-recipients|disabled
70
+ [whatsapp:relay] op=dispatch outcome=sent|failed|no-recipients
71
+ [whatsapp:public-agent] op=route enabled=… slug=… spawned=0|1
72
+ [whatsapp:public-agent] op=spawn-ok | op=spawn-failed
73
+ ```
74
+
75
+ Three standing audits reconcile that chain rather than waiting for someone to notice. `op=public-notify-reconcile` reports `gap`, `relayGap` and `englishGap`, 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.
@@ -82,8 +82,10 @@ Activate when the user asks about WhatsApp — connecting, linking, checking sta
82
82
  - Group observation — group messages are stored, persisted, and browsable, but the agent never responds in a group (observation-only)
83
83
  - DM policy enforcement (open, allowlist, disabled)
84
84
  - Conversation browsing — list active conversations, read message history, inspect group metadata
85
+ - Inbound media surface — `extractMessage` ([`inbound/extract.ts`](../../ui/app/lib/whatsapp/inbound/extract.ts)) handles image, audio, video, document and sticker messages, giving the agent the caption where one exists and a synthetic `[document: <name>]` / `[sticker]` label where none does, so a media message never reaches the agent as an empty body
85
86
  - Inbound voice note transcription (OGG Opus → text via local whisper.cpp)
86
87
  - Outbound voice note normalization (OGG Opus via ffmpeg)
88
+ - Inbound location sharing — a shared pin arrives as `[location: <name> — <address> — <lat>,<lon>]` and a live share as `[live location: <lat>,<lon>]`, on the same `extracted.text` field that carries a voice transcript, so an agent can read and relay a coordinate as text with no further platform work. A live share is enrolled for updates so the pin keeps moving rather than freezing at the first fix, and the operator's `/whatsapp` reader draws a map on the bubble. **There is no outbound location send:** the agent cannot put a pin into a chat, only text and documents. See [channels-whatsapp.md](references/channels-whatsapp.md)
87
89
  - Auto-reconnection with exponential backoff
88
90
  - Business hours gating — public messages gated by `OpeningHoursSpecification` from Neo4j, with optional after-hours auto-reply via the `afterHoursMessage` config field
89
91
 
@@ -175,10 +175,12 @@ lifelineTool(server, "whatsapp-config", [
175
175
  " - add-registered-party (register a known non-operator counterparty — a driver, a supplier, a contractor — on an account's own number; requires `phone`, `account` and `party`. Their messages to THAT account's number reach its PUBLIC agent carrying the label, and they are NOT granted admin. Use this, not add-account-manager, for anyone who is not an operator of the account)",
176
176
  " - remove-registered-party (clear a phone's registered-party registration; requires `phone`)",
177
177
  " - list-registered-parties (show phone → account and label)",
178
- " - set-public-agent (set the top-level public-agent slug for non-admin DMs; requires `slug`)",
179
- " - get-public-agent (show the resolved public-agent slug for DMs)",
180
- " - list-public-agents (slug and display name for each available public agent)",
181
- " - set-public-agent-enabled (turn public-agent answering on or off; requires boolean `enabled`. Pass `accountId` to set one account, omit it for the install-wide default. Refuses to enable when no public agent is configured for that scope, or when the configured slug has no agent on disk. OFF by default: while off, a public sender's message is stored and the account managers are notified, but the sender receives nothing)",
178
+ " - set-public-agent (set the public-agent slug for non-admin DMs; requires `slug`. Scoped to your own account by default; a house admin may pass `accountId` to set another account's, or omit it to set the install-wide top-level default. The slug must name an agent that exists in THAT account's own agents directory.)",
179
+ " - get-public-agent (show the resolved public-agent slug for DMs on this account. A sub-account resolves only its own agent — it never inherits the house's.)",
180
+ " - list-public-agents (slug and display name for each agent THIS account can name as its public agent, from its own agents directory)",
181
+ " - set-public-agent-enabled (turn public-agent answering on or off; requires boolean `enabled`. Scoped to your own account by default; a house admin may pass `accountId` for another, or omit it for the install-wide top-level default. Refuses to enable when no public agent is configured for that scope, or when the configured slug has no agent on disk. OFF by default: while off, a public sender's message is stored and the account managers are notified, but the sender receives nothing)",
182
+ " - set-manager-notify-enabled (turn the raw public-inbound notification on or off for this account; requires boolean `enabled`. ON by default. While on, every message from a public sender is forwarded verbatim to the account's registered managers. Turning it off stops that copy and leaves the admin relay, and the translated reply that relay produces, untouched — so a manager who wants the summary but not the raw firehose says so here rather than by unbinding themselves. Scoped to your own account; a house admin may pass `accountId` for another, or omit it for the install-wide top-level default.)",
183
+ " - get-manager-notify-enabled (read whether the raw public-inbound notification fires for this account, and whether that value is the account's own or the default)",
182
184
  " - update-config (change any WhatsApp config field — pass `fields` as a JSON string of field/value pairs)",
183
185
  " - get-config (return the full current WhatsApp config object)",
184
186
  " - set-dispatch-inbound (turn a paired socket into an OBSERVE socket or back; requires `accountId` and boolean `dispatchInbound`. false = the account's WhatsApp is read-only: messages are stored and readable but never dispatched to an agent. Scoped to your own account.)",
@@ -187,7 +189,7 @@ lifelineTool(server, "whatsapp-config", [
187
189
  " - schema (return all config field definitions — names, types, defaults, descriptions, constraints)",
188
190
  " - list-groups (return WhatsApp groups the account belongs to — names, JIDs, participant counts)",
189
191
  "Groups are observation-only — the agent never responds in a group, so there is no per-group config. list-groups still lists them for reference.",
190
- "Public-agent precedence at routing time (DMs): per-account default top-level default.",
192
+ "Public-agent resolution at routing time (DMs): an account answers with the agent named in its OWN config, from its OWN agents directory. There is no inheritance — a sub-account that names no agent of its own answers nobody. The top-level publicAgent is the HOUSE's own value, not a default for everyone.",
191
193
  "A phone may be a house admin AND a sub-account manager at the same time — both registrations are accepted, and the number the sender messaged decides which scope they get: the house number gives house scope, the sub-account's own number gives that sub-account's scope. A manager bound to a sub-account with no number of its own is reached through the house number. A manager phone is not reachable by whatsapp-send-admin (message it directly instead).",
192
194
  "Three inbound sender classes exist: an OPERATOR (an admin phone or an account manager — admin session), a REGISTERED PARTY (a known counterparty — public session carrying its label), and an unknown visitor (whatever the DM policy allows). A registered party is never granted admin, and the registration applies only on the registered account's own number: on the house number they take the house DM policy like anyone else. A phone cannot hold a party registration and an admin or manager registration at the same time — either write is refused with reason 'party-registration-shadowed', because the operator arms are checked first and the party registration would never be reached.",
193
195
  "Phone numbers must be E.164 format (e.g. +441234567890).",
@@ -206,6 +208,8 @@ lifelineTool(server, "whatsapp-config", [
206
208
  "get-public-agent",
207
209
  "list-public-agents",
208
210
  "set-public-agent-enabled",
211
+ "set-manager-notify-enabled",
212
+ "get-manager-notify-enabled",
209
213
  "update-config",
210
214
  "get-config",
211
215
  "set-dispatch-inbound",
@@ -220,9 +224,9 @@ lifelineTool(server, "whatsapp-config", [
220
224
  account: z.string().optional().describe("Account UUID whose own number this phone is a known counterparty on (required for add-registered-party). Their messages to THAT account's number reach its public agent; on the house number they take the house DM policy instead."),
221
225
  party: z.string().optional().describe("Label for this counterparty (required for add-registered-party), e.g. 'driver'. Lowercase letters, digits and hyphens, 1-64 characters. The platform carries it to the session and never interprets it; the account's own skill decides what it means."),
222
226
  slug: z.string().optional().describe("Agent slug (required for set-public-agent)"),
223
- enabled: z.boolean().optional().describe("On/off value (required for set-public-agent-enabled). true = public senders are answered by the resolved public agent; false = they receive nothing."),
227
+ enabled: z.boolean().optional().describe("On/off value (required for set-public-agent-enabled and set-manager-notify-enabled). For set-public-agent-enabled: true = public senders are answered by the resolved public agent; false = they receive nothing. For set-manager-notify-enabled: true = every public inbound is forwarded verbatim to the account's managers; false = it is not, and the admin relay is unaffected."),
224
228
  fields: z.string().optional().describe('JSON string of config fields to update (required for update-config, e.g. \'{"dmPolicy":"open","allowFrom":["*"]}\')'),
225
- accountId: z.string().optional().describe('Account ID. Defaults to the calling session’s own account; the house admin may pass another account id explicitly. Used by list-groups, set-dispatch-inbound, set-account-policy and get-account-policy.'),
229
+ accountId: z.string().optional().describe('Account ID. Defaults to the calling session’s own account; the house admin may pass another account id explicitly. Used by list-groups, set-dispatch-inbound, set-account-policy, get-account-policy, set-public-agent, get-public-agent, set-public-agent-enabled, list-public-agents, set-manager-notify-enabled and get-manager-notify-enabled.'),
226
230
  dispatchInbound: z.boolean().optional().describe("For set-dispatch-inbound: false makes the account an observe socket (stored + readable, never dispatched); true restores agent dispatch."),
227
231
  policy: z.object({
228
232
  dmPolicy: z.enum(["open", "allowlist", "disabled"]).optional(),
@@ -295,6 +299,17 @@ lifelineTool(server, "whatsapp-config", [
295
299
  const sourceLabel = source ? ` (source: ${source})` : "";
296
300
  return textResult(`WhatsApp public agent: ${agentSlug}${sourceLabel}`);
297
301
  }
302
+ // Task 2277 — its own formatter. The tail fallback renders `result.message`,
303
+ // which this read does not carry, so without this the agent would be told
304
+ // "Done." and learn nothing about the value it asked for.
305
+ if (action === "get-manager-notify-enabled") {
306
+ const on = result.enabled === true;
307
+ const source = result.source;
308
+ const sourceLabel = source === "default" ? " (the default; this account has set no value of its own)" : ` (source: ${source})`;
309
+ return textResult(on
310
+ ? `Raw public-inbound notification is ON${sourceLabel}. Every message from a public sender is forwarded verbatim to this account's managers.`
311
+ : `Raw public-inbound notification is OFF${sourceLabel}. Public senders' messages are not forwarded verbatim; the admin relay and its reply are unaffected.`);
312
+ }
298
313
  if (action === "list-public-agents") {
299
314
  const agents = result.agents ?? [];
300
315
  if (agents.length === 0)