@rubytech/create-sitedesk-code 0.1.520 → 0.1.522
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.
- package/dist/index.js +17 -3
- package/package.json +1 -1
- package/payload/platform/lib/account-schema-regions/package-lock.json +12 -0
- package/payload/platform/lib/active-rules/package-lock.json +12 -0
- package/payload/platform/lib/mcp-lifeline/package-lock.json +12 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +46 -7
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +11 -1
- package/payload/platform/plugins/docs/references/internals.md +4 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +27 -4
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-metadata-calendar.test.js +46 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-metadata-calendar.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +14 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +35 -4
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/references/email-reference.md +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +9 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.js +151 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.js +151 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +38 -4
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.js +146 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.js +120 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js +8 -8
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.js +27 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.js +70 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.js +152 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.d.ts +57 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.js +163 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +12 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +21 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts +35 -4
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js +92 -10
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +106 -3
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +49 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +126 -8
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts +13 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js +9 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts +42 -8
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js +59 -19
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.d.ts +47 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.js +67 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.d.ts +12 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.js +18 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.js.map +1 -0
- package/payload/platform/plugins/outlook/references/auth.md +15 -2
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +8 -0
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +21 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
- package/payload/platform/scripts/check-architecture-skill-no-drift.mjs +20 -6
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +17 -5
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +30 -6
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +53 -21
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +16 -0
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +41 -2
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/{chunk-TIIWMF5G.js → chunk-K7CJGZPU.js} +459 -196
- package/payload/server/{manager-B6OFOXSW.js → manager-RKL7JZ4Y.js} +3 -1
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-SW7mEMcP.js → AdminLoginScreens-zc2_qqh-.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-zc2_qqh-.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-zc2_qqh-.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-IGUdTx5q.js +2 -0
- package/payload/server/public/assets/AdminShell-IGUdTx5q.js.br +0 -0
- package/payload/server/public/assets/AdminShell-IGUdTx5q.js.gz +0 -0
- package/payload/server/public/assets/{activity-sqbyX0pX.js → activity-DVgO8YuM.js} +1 -1
- package/payload/server/public/assets/activity-DVgO8YuM.js.br +0 -0
- package/payload/server/public/assets/activity-DVgO8YuM.js.gz +0 -0
- package/payload/server/public/assets/admin-xtpM7fgl.js +1 -0
- package/payload/server/public/assets/admin-xtpM7fgl.js.br +0 -0
- package/payload/server/public/assets/admin-xtpM7fgl.js.gz +0 -0
- package/payload/server/public/assets/{agents-BFYM5EDA.js → agents-P0nTIxo-.js} +1 -1
- package/payload/server/public/assets/agents-P0nTIxo-.js.br +0 -0
- package/payload/server/public/assets/agents-P0nTIxo-.js.gz +0 -0
- package/payload/server/public/assets/{browser-DP5GZe4G.js → browser-Mw7DY3ft.js} +1 -1
- package/payload/server/public/assets/browser-Mw7DY3ft.js.br +0 -0
- package/payload/server/public/assets/browser-Mw7DY3ft.js.gz +0 -0
- package/payload/server/public/assets/{calendar-BKaemLDG.js → calendar-B9gvL_1_.js} +1 -1
- package/payload/server/public/assets/calendar-B9gvL_1_.js.br +0 -0
- package/payload/server/public/assets/calendar-B9gvL_1_.js.gz +0 -0
- package/payload/server/public/assets/chat-DBGqh4wg.js +1 -0
- package/payload/server/public/assets/chat-DBGqh4wg.js.br +2 -0
- package/payload/server/public/assets/chat-DBGqh4wg.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-Bl4vpOM0.js +1 -0
- package/payload/server/public/assets/chevron-left-Bl4vpOM0.js.br +0 -0
- package/payload/server/public/assets/chevron-right-BExigCnp.js +1 -0
- package/payload/server/public/assets/chevron-right-BExigCnp.js.br +0 -0
- package/payload/server/public/assets/clock-D8TJvgJC.js +1 -0
- package/payload/server/public/assets/clock-D8TJvgJC.js.br +0 -0
- package/payload/server/public/assets/data-HfFSxaC2.js +1 -0
- package/payload/server/public/assets/data-HfFSxaC2.js.br +0 -0
- package/payload/server/public/assets/data-HfFSxaC2.js.gz +0 -0
- package/payload/server/public/assets/{file-text-BKK7R-k_.js → file-text-dYBnXNEX.js} +1 -1
- package/payload/server/public/assets/file-text-dYBnXNEX.js.br +0 -0
- package/payload/server/public/assets/file-text-dYBnXNEX.js.gz +0 -0
- package/payload/server/public/assets/{graph-FuxYySjK.js → graph-CDyzd1Et.js} +2 -2
- package/payload/server/public/assets/graph-CDyzd1Et.js.br +0 -0
- package/payload/server/public/assets/graph-CDyzd1Et.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-Cip6fmKK.js → graph-labels-D1LkKiJr.js} +1 -1
- package/payload/server/public/assets/graph-labels-D1LkKiJr.js.br +0 -0
- package/payload/server/public/assets/graph-labels-D1LkKiJr.js.gz +0 -0
- package/payload/server/public/assets/{maximize-2-D79AhVDo.js → maximize-2-M-iSaznd.js} +1 -1
- package/payload/server/public/assets/maximize-2-M-iSaznd.js.br +0 -0
- package/payload/server/public/assets/maximize-2-M-iSaznd.js.gz +0 -0
- package/payload/server/public/assets/{operator-BPAfgFhP.js → operator-9jfCv1Fg.js} +1 -1
- package/payload/server/public/assets/operator-9jfCv1Fg.js.br +0 -0
- package/payload/server/public/assets/operator-9jfCv1Fg.js.gz +0 -0
- package/payload/server/public/assets/page-CZ-73FAk.js +32 -0
- package/payload/server/public/assets/page-CZ-73FAk.js.br +0 -0
- package/payload/server/public/assets/page-CZ-73FAk.js.gz +0 -0
- package/payload/server/public/assets/{page-BqEyS0W4.js → page-CjwSt7kx.js} +1 -1
- package/payload/server/public/assets/page-CjwSt7kx.js.br +0 -0
- package/payload/server/public/assets/page-CjwSt7kx.js.gz +0 -0
- package/payload/server/public/assets/{public-D1xs3FzD.js → public-CN-ltC8x.js} +1 -1
- package/payload/server/public/assets/public-CN-ltC8x.js.br +0 -0
- package/payload/server/public/assets/public-CN-ltC8x.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-CcU_XD8B.js → rotate-ccw-Buopsc22.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-Buopsc22.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-Buopsc22.js.gz +0 -0
- package/payload/server/public/assets/routines-C-yr8ddT.js +1 -0
- package/payload/server/public/assets/routines-C-yr8ddT.js.br +0 -0
- package/payload/server/public/assets/routines-C-yr8ddT.js.gz +0 -0
- package/payload/server/public/assets/skills-DiiuVzDK.js +1 -0
- package/payload/server/public/assets/skills-DiiuVzDK.js.br +0 -0
- package/payload/server/public/assets/skills-DiiuVzDK.js.gz +0 -0
- package/payload/server/public/assets/tasks-CnHIfn2_.js +1 -0
- package/payload/server/public/assets/tasks-CnHIfn2_.js.br +0 -0
- package/payload/server/public/assets/tasks-CnHIfn2_.js.gz +0 -0
- package/payload/server/public/assets/{time-entry-format-IFqinQJj.js → time-entry-format-BBokCF-D.js} +1 -1
- package/payload/server/public/assets/time-entry-format-BBokCF-D.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-BBokCF-D.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-C5vH__sq.js → triangle-alert-C_AcpTW5.js} +1 -1
- package/payload/server/public/assets/triangle-alert-C_AcpTW5.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-C_AcpTW5.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-BeL2gPl6.js → useCopyFeedback-TdimmPVR.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-TdimmPVR.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-TdimmPVR.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BwHqV55p.js +14 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BwHqV55p.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BwHqV55p.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CsrPYqtm.css +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CsrPYqtm.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CsrPYqtm.css.gz +0 -0
- package/payload/server/public/assets/{useVoiceRecorder-Cpw3aOSt.js → useVoiceRecorder-BT2738ZS.js} +1 -1
- package/payload/server/public/assets/useVoiceRecorder-BT2738ZS.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BT2738ZS.js.gz +0 -0
- package/payload/server/public/assets/{wrench-BBEup3hd.js → wrench-B51s5kGA.js} +1 -1
- package/payload/server/public/assets/wrench-B51s5kGA.js.br +0 -0
- package/payload/server/public/assets/wrench-B51s5kGA.js.gz +0 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +7 -7
- package/payload/server/public/chat.html +13 -13
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +13 -13
- package/payload/server/public/routines.html +6 -6
- package/payload/server/public/skills.html +5 -5
- package/payload/server/public/tasks.html +6 -6
- package/payload/server/server.js +1238 -653
- package/payload/server/public/assets/AdminLoginScreens-SW7mEMcP.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-SW7mEMcP.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-Djg6ndne.js +0 -2
- package/payload/server/public/assets/AdminShell-Djg6ndne.js.br +0 -0
- package/payload/server/public/assets/AdminShell-Djg6ndne.js.gz +0 -0
- package/payload/server/public/assets/activity-sqbyX0pX.js.br +0 -0
- package/payload/server/public/assets/activity-sqbyX0pX.js.gz +0 -0
- package/payload/server/public/assets/admin-D9Bt47GY.js +0 -1
- package/payload/server/public/assets/admin-D9Bt47GY.js.br +0 -0
- package/payload/server/public/assets/admin-D9Bt47GY.js.gz +0 -0
- package/payload/server/public/assets/agents-BFYM5EDA.js.br +0 -0
- package/payload/server/public/assets/agents-BFYM5EDA.js.gz +0 -0
- package/payload/server/public/assets/browser-DP5GZe4G.js.br +0 -0
- package/payload/server/public/assets/browser-DP5GZe4G.js.gz +0 -0
- package/payload/server/public/assets/calendar-BKaemLDG.js.br +0 -0
- package/payload/server/public/assets/calendar-BKaemLDG.js.gz +0 -0
- package/payload/server/public/assets/chat-DPhT6urD.js +0 -1
- package/payload/server/public/assets/chat-DPhT6urD.js.br +0 -1
- package/payload/server/public/assets/chat-DPhT6urD.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-BSdXvJcH.js +0 -1
- package/payload/server/public/assets/chevron-right-DI7vI3IB.js +0 -1
- package/payload/server/public/assets/chevron-right-DI7vI3IB.js.br +0 -0
- package/payload/server/public/assets/clock-YUTPXZH5.js +0 -1
- package/payload/server/public/assets/clock-YUTPXZH5.js.br +0 -0
- package/payload/server/public/assets/clock-YUTPXZH5.js.gz +0 -0
- package/payload/server/public/assets/data-CfjttTvR.js +0 -1
- package/payload/server/public/assets/data-CfjttTvR.js.br +0 -2
- package/payload/server/public/assets/data-CfjttTvR.js.gz +0 -0
- package/payload/server/public/assets/file-text-BKK7R-k_.js.br +0 -0
- package/payload/server/public/assets/file-text-BKK7R-k_.js.gz +0 -0
- package/payload/server/public/assets/graph-FuxYySjK.js.br +0 -0
- package/payload/server/public/assets/graph-FuxYySjK.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-Cip6fmKK.js.br +0 -0
- package/payload/server/public/assets/graph-labels-Cip6fmKK.js.gz +0 -0
- package/payload/server/public/assets/maximize-2-D79AhVDo.js.br +0 -0
- package/payload/server/public/assets/maximize-2-D79AhVDo.js.gz +0 -0
- package/payload/server/public/assets/operator-BPAfgFhP.js.br +0 -0
- package/payload/server/public/assets/operator-BPAfgFhP.js.gz +0 -0
- package/payload/server/public/assets/page--78CbdFs.js +0 -32
- package/payload/server/public/assets/page--78CbdFs.js.br +0 -0
- package/payload/server/public/assets/page--78CbdFs.js.gz +0 -0
- package/payload/server/public/assets/page-BqEyS0W4.js.br +0 -0
- package/payload/server/public/assets/page-BqEyS0W4.js.gz +0 -0
- package/payload/server/public/assets/public-D1xs3FzD.js.br +0 -0
- package/payload/server/public/assets/public-D1xs3FzD.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-CcU_XD8B.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-CcU_XD8B.js.gz +0 -0
- package/payload/server/public/assets/routines-CA7D0myr.js +0 -1
- package/payload/server/public/assets/routines-CA7D0myr.js.br +0 -0
- package/payload/server/public/assets/routines-CA7D0myr.js.gz +0 -0
- package/payload/server/public/assets/skills-CfVPPSxO.js +0 -1
- package/payload/server/public/assets/skills-CfVPPSxO.js.br +0 -0
- package/payload/server/public/assets/skills-CfVPPSxO.js.gz +0 -0
- package/payload/server/public/assets/tasks-DgieyZqz.js +0 -1
- package/payload/server/public/assets/tasks-DgieyZqz.js.br +0 -0
- package/payload/server/public/assets/tasks-DgieyZqz.js.gz +0 -0
- package/payload/server/public/assets/time-entry-format-IFqinQJj.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-IFqinQJj.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-C5vH__sq.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-C5vH__sq.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-BeL2gPl6.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-BeL2gPl6.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CH9qm2Ad.js +0 -14
- package/payload/server/public/assets/useSubAccountSwitcher-CH9qm2Ad.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CH9qm2Ad.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-Cpw3aOSt.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-Cpw3aOSt.js.gz +0 -0
- package/payload/server/public/assets/wrench-BBEup3hd.js.br +0 -0
- package/payload/server/public/assets/wrench-BBEup3hd.js.gz +0 -0
package/dist/index.js
CHANGED
|
@@ -5398,16 +5398,30 @@ try {
|
|
|
5398
5398
|
console.log("================================================================");
|
|
5399
5399
|
console.log("");
|
|
5400
5400
|
if (isLinux()) {
|
|
5401
|
+
const edgeDecision = pickEdgeBindDecision(networkInterfaces());
|
|
5401
5402
|
// Only true when the edge binds 0.0.0.0. On a host classified to loopback
|
|
5402
5403
|
// (Task 2082) nothing answers on the LAN address, so printing this line
|
|
5403
5404
|
// there would be a false instruction; the SSH-forward recipe below is the
|
|
5404
5405
|
// whole story for that host class.
|
|
5405
|
-
if (
|
|
5406
|
+
if (edgeDecision.hostname === "0.0.0.0") {
|
|
5406
5407
|
console.log(` Same network (Pi / LAN): http://${DEVICE_HOSTNAME}.local:${PORT}`);
|
|
5407
5408
|
console.log("");
|
|
5408
5409
|
}
|
|
5409
|
-
|
|
5410
|
-
|
|
5410
|
+
// Provider-neutral. This recipe is identical on every cloud host with no
|
|
5411
|
+
// LAN to the operator, so naming one provider mis-describes the others: it
|
|
5412
|
+
// was printed verbatim on a netcup RS 8000 install (2026-07-28) while
|
|
5413
|
+
// saying "Hetzner". The host class, not the vendor, is what selects it.
|
|
5414
|
+
//
|
|
5415
|
+
// Print the host's own public address and the user that owns the install
|
|
5416
|
+
// rather than `admin@<server-ipv4>`. Both are already known here —
|
|
5417
|
+
// `publicAddresses` is what the loopback bind decision was just made from —
|
|
5418
|
+
// and a placeholder in a copy-pasted line is one more thing to get wrong.
|
|
5419
|
+
// Falls back to the placeholder only when the interface table shows no
|
|
5420
|
+
// public IPv4, which is the 1:1-NAT case the decision cannot see.
|
|
5421
|
+
const sshUser = userInfo().username;
|
|
5422
|
+
const sshTarget = `${sshUser}@${edgeDecision.publicAddresses[0] ?? "<server-ipv4>"}`;
|
|
5423
|
+
console.log(` Remote access (cloud server, no LAN to you): on your local machine run`);
|
|
5424
|
+
console.log(` ssh -L ${PORT}:localhost:${PORT} -L ${BRAND.websockifyPort}:localhost:${BRAND.websockifyPort} ${sshTarget}`);
|
|
5411
5425
|
console.log(` Then open:`);
|
|
5412
5426
|
console.log(` Dashboard: http://localhost:${PORT}`);
|
|
5413
5427
|
console.log(` VNC browser (Claude OAuth + Cloudflare): http://localhost:${BRAND.websockifyPort}/vnc.html`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what SiteDesk 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:
|
|
4
|
+
content-hash: sha256:3e9150568da700b668d3284d2edf7c780e6bc13323f5851c88371f559407b120
|
|
5
5
|
brand: sitedesk-code
|
|
6
6
|
product-name: SiteDesk
|
|
7
7
|
---
|
|
@@ -1294,6 +1294,8 @@ In netcup's Customer Control Panel:
|
|
|
1294
1294
|
2. When the server is provisioned, install **Ubuntu 24.04 LTS** from the CCP's image list.
|
|
1295
1295
|
3. Add your SSH public key during image setup, or paste it into `/root/.ssh/authorized_keys` from the remote console afterwards.
|
|
1296
1296
|
|
|
1297
|
+
The image form's **Timezone** dropdown offers only netcup's own datacentre zones (`Europe/Berlin`, `Europe/Vienna`), so an install serving any other region cannot set its real timezone here. Leave the default and correct it in step 2 with `timedatectl set-timezone <zone>`. This is not cosmetic: the scheduler and cron fire on system local time, so a UK install left on Berlin runs an hour early. **Locale** is a free choice on the same form and should be set there; the fleet uses `en_US.UTF-8`.
|
|
1298
|
+
|
|
1297
1299
|
netcup does not provide a network firewall product in front of the server, so at this point the host firewall is the only inbound control. Close everything except SSH before going further:
|
|
1298
1300
|
|
|
1299
1301
|
```bash
|
|
@@ -1353,7 +1355,7 @@ Take another snapshot immediately before any subsequent platform upgrade.
|
|
|
1353
1355
|
|
|
1354
1356
|
## 4. Run the installer
|
|
1355
1357
|
|
|
1356
|
-
Run as the `admin` user (do not use `sudo`; the installer escalates internally where it needs to). The `--hostname` flag is required on a cloud VM
|
|
1358
|
+
Run as the `admin` user (do not use `sudo`; the installer escalates internally where it needs to). The `--hostname` flag is required on a cloud VM. It sets the **OS** hostname via `hostnamectl` and names the Cloudflare-fronted host; it does **not** name the systemd units, which stay brand-derived. Measured on a netcup install 2026-07-28: passing `--hostname orchard` to `create-realagent-code` produced `hostname` = `orchard` alongside units still named `realagent-code.service`, `realagent-code-edge.service`, `realagent-code-claude-session-manager.service` and `realagent-code-rss-sampler.service`. Pass the same value on every re-run, so the flag rather than OS detection decides it.
|
|
1357
1359
|
|
|
1358
1360
|
```bash
|
|
1359
1361
|
npx -y @rubytech/create-maxy-code@latest --hostname <hostname>
|
|
@@ -2386,7 +2388,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2386
2388
|
|
|
2387
2389
|
1. **No app to register.** The client ID ships with the brand (`brand.json#outlookClientId`), and the installer stamps it onto the session-manager env, so there is zero operator Azure work. On a brand that ships an empty value the plugin stays inert until its vendor app exists — see `platform/plugins/outlook/references/auth.md` for the one-time-per-brand vendor provisioning runbook (a vendor step, not an operator or end-user step), including setting **Allow public client flows** to Yes.
|
|
2388
2390
|
2. **Per account: register the Outlook account** — in admin chat, ask the agent to "register my Outlook account". The agent runs `outlook-account-register`, which returns a short user code and the URL `microsoft.com/devicelogin` and relays both to you.
|
|
2389
|
-
3. **Consent on any device** — open `microsoft.com/devicelogin` on your phone or laptop, enter the code, sign in to your Microsoft account, and consent to the requested scopes (`offline_access`, `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `Calendars.ReadWrite`, `Contacts.Read`). `Mail.Send` enables sending
|
|
2391
|
+
3. **Consent on any device** — open `microsoft.com/devicelogin` on your phone or laptop, enter the code, sign in to your Microsoft account, and consent to the requested scopes (`offline_access`, `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `Mail.ReadWrite.Shared`, `Mail.Send.Shared`, `Calendars.ReadWrite`, `Contacts.Read`). `Mail.Send` enables sending, `Calendars.ReadWrite` enables calendar control, and the two `.Shared` scopes let this mailbox reach a shared mailbox it has Full Access to; a mailbox last consented on an older scope set must re-register before send, any calendar write, or any shared mailbox succeeds. Nothing else to do: the server finishes the sign-in automatically once you consent, usually within about half a minute, and the mailbox is then connected.
|
|
2390
2392
|
4. **Done.** Subsequent tool calls (mail, calendar, contacts) use the persisted refresh token transparently.
|
|
2391
2393
|
5. **Several mailboxes on one account.** Register again to add a second mailbox; it is stored alongside the first, not in place of it. Every operational tool then takes an optional `mailbox` argument — an email address or graphUserId. With one mailbox, omit it. With two or more, name it; omitting it returns a refusal listing the connected mailboxes. `outlook-mailbox-list` shows every connected mailbox. Only one registration runs at a time per account; starting another while one is live returns an in-progress signal.
|
|
2392
2394
|
|
|
@@ -2413,12 +2415,32 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2413
2415
|
| `outlook-calendar-freebusy` | Availability for one or more addresses over a window. |
|
|
2414
2416
|
| `outlook-contacts-list` | Top contacts. Default top=50. |
|
|
2415
2417
|
| `outlook-mailbox-info` | Health probe for one mailbox — auth state, refresh-window, folder count. Name the mailbox with `mailbox` when several are attached. |
|
|
2416
|
-
| `outlook-mailbox-list` | Every connected mailbox with its email, scopes, and token health. Local only — no network — so it also flags a mailbox whose stored credentials cannot be read. |
|
|
2418
|
+
| `outlook-mailbox-list` | Every connected mailbox with its email, scopes, and token health, signed-in and shared alike (`kind`, and `via` for a shared one). Local only by default — no network — so it also flags a mailbox whose stored credentials cannot be read. `verify:true` additionally probes each shared mailbox and reports `reachable`. |
|
|
2419
|
+
| `outlook-shared-mailbox-add` | Attach a shared mailbox (no sign-in, no licence) through the Full Access of a mailbox that is already connected. `address` is the shared mailbox; `via` names the connected mailbox when more than one is attached. Proves the access before storing anything, so a refusal is the real answer. |
|
|
2420
|
+
| `outlook-shared-mailbox-remove` | Detach a shared mailbox. Drops the pointer only; no credential is touched. |
|
|
2417
2421
|
| `outlook-mail-send` | Send a message. No attachments → `POST /me/sendMail`; with attachments → draft, attach, send (`/me/sendMail` carries attachments inline and cannot fit a large file). to/cc/bcc arrays, subject, body, isHtml, attachments (account-dir paths, 25 MB/file). Success asserted on Graph 202; no message id on either route. Needs `Mail.Send` (+ `Mail.ReadWrite` to attach). |
|
|
2418
2422
|
| `outlook-draft` | Create a draft in Drafts, either a new message or a threaded reply. Returns the draft id; never sends. Attachments are attached before the id is returned, so the draft is ready to send. Needs `Mail.ReadWrite`. **New message** (no `replyToMessageId`): `POST /me/messages`, requires `to` + `subject`, same body model as `outlook-mail-send`, threads under nothing. **Threaded reply** (`replyToMessageId` = the id of the message being answered): `POST /me/messages/{id}/createReply` — the draft carries the parent's conversationId, so it lands under the original. This is the only way to draft a reply without sending it: `outlook-mail-reply` threads but always sends. Graph sets the recipients and `RE:` subject from the original and composes the body above its own quote, matching what `outlook-mail-reply` sends, so the threaded path is **plain text only** — `to`, `subject`, and `isHtml: true` are **refused** (not ignored) when replying. `replyAll` includes everyone on the original; `cc`/`bcc` add recipients. |
|
|
2419
2423
|
| `outlook-draft-edit` | Update an existing draft in place (`PATCH /me/messages/{draftId}`) and/or add and remove attachments. Graph drafts are mutable — pass draftId plus the fields to change (to/cc/bcc/subject/body/isHtml/attachments/removeAttachments). `attachments` adds; `removeAttachments` takes files off by name or attachment id; both together replace a file in one call. Sent messages cannot be edited. Needs `Mail.ReadWrite`. |
|
|
2420
2424
|
| `outlook-draft-send` | Send an existing draft by id (`POST /me/messages/{id}/send`). Graph consumes the draft, returns 202. Needs `Mail.Send`. |
|
|
2421
2425
|
|
|
2426
|
+
## Shared mailboxes
|
|
2427
|
+
|
|
2428
|
+
A shared mailbox (`team@`, `info@`, `accounts@`) has no sign-in of its own, so nobody can connect it the way a normal mailbox is connected. **It does not need a licence and should never be given one for this.** Whoever has Full Access to it connects their own mailbox in the normal way, then attaches the shared one to it:
|
|
2429
|
+
|
|
2430
|
+
```
|
|
2431
|
+
outlook-shared-mailbox-add address=team@yourcompany.com
|
|
2432
|
+
```
|
|
2433
|
+
|
|
2434
|
+
Add `via=<your address>` if this account has more than one mailbox connected. From then on the shared address works as the `mailbox` argument on any mail tool, and `outlook-mailbox-list` shows it as `kind:delegated`.
|
|
2435
|
+
|
|
2436
|
+
The attach call checks the access before it stores anything, so what it says back is the real state of things:
|
|
2437
|
+
|
|
2438
|
+
- **"does not have Full Access"** — the delegation is missing. Grant it in the Microsoft 365 admin centre: Teams & groups → Shared mailboxes → the mailbox → Members / Read and manage. Nothing is purchased.
|
|
2439
|
+
- **"no mailbox with this address in the tenant"** — the address is wrong, or it is a distribution list. A distribution list has no mailbox and cannot be attached.
|
|
2440
|
+
- **"connected without the Mail.ReadWrite.Shared scope"** — your mailbox was connected before the plugin asked for the shared scopes. Register it again and it will work.
|
|
2441
|
+
|
|
2442
|
+
Mail only: calendar and contacts tools refuse on a shared mailbox and say so. If a shared mailbox stops answering, the delegation was probably removed in Exchange — `outlook-mailbox-list verify:true` reports which ones are still reachable.
|
|
2443
|
+
|
|
2422
2444
|
## Drafting to a Contact
|
|
2423
2445
|
|
|
2424
2446
|
When the admin agent drafts or sends a message to a known contact, it reads that contact's record first and applies any routing the record carries, for example a preferred send-to address, showing the result for your approval before anything is sent.
|
|
@@ -2438,7 +2460,10 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
2438
2460
|
| /me failed post-token | `me-failed account=<id> status=<N>` |
|
|
2439
2461
|
| Auth ok | `auth-ok account=<id> graphUserId=<id> mail=<addr> scopes=<csv> tokenExpSec=<N>` |
|
|
2440
2462
|
| Registration already in flight | `register-in-progress account=<id>` (start refused; one device-code flow at a time) |
|
|
2441
|
-
| Credential resolve | `cred-resolve account=<id> mailbox=<id-or-empty> source=arg|sole|none|refused
|
|
2463
|
+
| Credential resolve | `cred-resolve account=<id> mailbox=<id-or-empty> source=arg|sole|none|refused kind=signed-in|delegated via=<delegator-or-empty>` — names the resolved mailbox and how it was chosen; `source=refused` is the ambiguity/not-found refusal, and `kind=delegated` means the call ran against a shared mailbox through `via` |
|
|
2464
|
+
| Shared mailbox attached | `shared-add account=<id> address=<addr> via=<key> outcome=ok|ok-reattached|no-scope|forbidden|not-found` — every refusal path is named, so a failed attach is diagnosable without reproducing it |
|
|
2465
|
+
| Shared mailbox detached | `shared-remove account=<id> address=<addr> outcome=ok|absent` |
|
|
2466
|
+
| Calendar/contacts on a shared mailbox | `shared-tool-refused account=<id> tool=<tool> prefix=<users-path>` — refused before any request is built |
|
|
2442
2467
|
| Token refreshed | `token-refreshed account=<id> oldExpSec=<N> newExpSec=<N>` |
|
|
2443
2468
|
| Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
|
|
2444
2469
|
| Mail list | `mail-list op=resolve account=<id> folderIn=<name> folderResolved=<token> asRawId=<bool> count=<N> elapsedMs=<N>` (on error, `error=<msg>` replaces `count`) |
|
|
@@ -2456,7 +2481,7 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
2456
2481
|
| Scope insufficient | `scope-insufficient tool=<t> account=<id> missing=Calendars.ReadWrite` (stale read-only consent) |
|
|
2457
2482
|
| Contacts list | `contacts-list account=<id> count=<N> elapsedMs=<N>` |
|
|
2458
2483
|
| Mailbox info | `mailbox-info account=<id> tokenWithinRefreshWindow=<bool> folderCount=<N>` |
|
|
2459
|
-
| Mailbox list | `mailbox-list account=<id> count=<N> unreadable=<N>` (unreadable>0 flags an orphaned or corrupt mailbox store
|
|
2484
|
+
| Mailbox list | `mailbox-list account=<id> count=<N> unreadable=<N> addressable=<N> delegated=<N> verified=<N>` — `count` covers signed-in plus delegated entries, `addressable` counts only signed-in ones that are usable by their email, `delegated` counts the attached shared mailboxes, and `verified` is how many of those were probed (0 unless `verify:true`). `unreadable>0` flags an orphaned or corrupt mailbox store |
|
|
2460
2485
|
| Graph error | `graph-error account=<id> status=<N> code=<graphErrorCode> retryAfterMs=<N-or-null>` — writes flow through the same `callGraph` loop, so a failed write logs `graph-error` too |
|
|
2461
2486
|
| On-prem rejected | `on-prem-rejected account=<id> mailServer=<host>` (terminal) |
|
|
2462
2487
|
| Scope insufficient | `scope-insufficient account=<id> tool=<t> missing=Mail.Send` (terminal — re-register to grant send) |
|
|
@@ -3246,7 +3271,7 @@ either is a regression.
|
|
|
3246
3271
|
| `/sessions` | Legacy admin-server conversation routes. No UI consumer remains after the ConversationsModal was retired; the surviving handlers are deletion candidates and not described here. | (legacy, no live caller) |
|
|
3247
3272
|
| `/sidebar-sessions` | Sole data path for the sidebar Sessions list. One JSONL on disk equals one row. The row's delete button is the only way a row disappears. Each row carries `sessionId`, `title`, `startedAt`, `live`, `isSubagent`, `pid: number \| null` (basename of the matched `sessions/<pid>.json`), and `projectDir` (the directory holding the JSONL — consumed by the delete route). The payload also carries top-level `accountId` so the pane renders the full UUID label whose first ~8 chars prefix-match the truncated Remote Control daemon entry in claude.ai/code. The legacy `rcUrl` field is gone — the row's external-link affordance now POSTs `/session-rc-spawn` to start a fresh local `claude --remote-control <name> --session-id <sid>` PTY on every click. **WhatsApp and Telegram sessions (admin + public) appear in this list**, colour-coded by channel (WhatsApp green `#25D366`, Telegram blue `#229ED9`, via a `conv-channel-*` left accent bar) and carrying the standard row kebab (Usage & cost, Reset/re-seat, Rename, Archive, Delete) so the operator can inspect and re-seat a channel session's params. Each row's `channel` is the authoritative `.meta.json` sidecar value (`whatsapp`/`telegram`/`webchat`/`browser`/null), not the JSONL-envelope channel. Each row also carries its sidecar `senderId`. For a WhatsApp/Telegram row with a `senderId` and no operator rename or ai-title (`titleSource:'prefix'`), the route resolves a contact `personName` and the sidebar renders the reader panel's `"<name> <number>"` label via the shared `operatorConvName`, degrading to the number alone then the stripped title on a miss. On a `whatsapp` row the number is printed in international form (`+44 7504 472444`, `+33 7 68 24 30 67`) by `formatSenderPhone` — `normalizeE164` then libphonenumber-js `formatInternational`, gated on `isPossible` so an unresolved LID prints raw; a `telegram` senderId is a Telegram user id and a `webchat` one is `session:<id>`, so neither is regrouped. The formatting is display-only: row identity, matching, and store keys stay on the normalized digits. Because the label function is shared, a Sessions row and that conversation's reader-panel row read identically. A channel row name has two sources, in precedence order. First the shared `resolveOperatorNamesBySender` helper the WhatsApp reader also calls, which matches the sender phone against the account's admins — that names an admin WhatsApp session and every Telegram session. Second, for a `whatsapp` row only, the WhatsApp pushName from the account's on-disk message store (`storePushNamesBySender` over `readConversationSummaries`, keyed by `normalizeE164` of the counterparty phone so a `+44 7…` sender matches a `447…` store key). The store is consulted only when the resolver reports **no binding** for that sender. That is typically a public WhatsApp customer, the case this exists for — such a sender has no admin record and so can only be named by the store, and without it those rows degraded to the bare number while the reader panel named them — though an admin missing from `users.json` qualifies too. A resolver miss caused by Neo4j being unreachable deliberately does NOT qualify: that is an outage rather than an absent binding, so the row degrades to the number and still logs `reason=neo4j-down`, keeping the outage signal intact. Telegram has no message store and is never looked up. The store read is guarded: a corrupt or absent store degrades to no pushName and logs `op=store-unreadable`, never 500s the list. Both sources are the ones the reader itself uses, so the two surfaces cannot drift; a renamed or ai-titled channel row keeps its own title. The Sessions list reads the viewed account's own store only. An account-manager's thread persists under the house account rather than the sub-account it manages, so on a multi-account install viewing a sub-account that row is not named here even though the reader panel names it — the reader additionally consults the house store, filtered to manager phones bound to that sub. The only role/channel excluded from the list is `public`+`webchat` (its dedicated reader is the sole surface); the exclusion predicate is `isSessionListExcluded`, decoupled from the reader's inclusion predicate `isReaderChannelSession` (unchanged), so the WhatsApp/Telegram reader panels are unaffected — a store-only WhatsApp conversation with no session JSONL stays reader-only. Observability rides `[admin-sessions-list] … channelInList=whatsapp:<n>,telegram:<n>,webchat:<n> channelNameCandidates=<n> channelNamesResolved=<n> (operator=<n> store=<n>) unknownChannel=<n> excludedPublicWebchat=<n>`, and each unresolved channel name logs one `[admin-sessions-list] op=channel-name-unresolved sessionId=<id8> channel=<whatsapp\|telegram> senderId=<id> reason=<no-senderId\|classify-miss\|neo4j-down>` line — emitted only when **both** sources miss, so a residual shortfall means a genuinely nameless sender rather than the expected public-customer floor, and the `operator=`/`store=` split says which source is carrying the names. | `GET /` |
|
|
3248
3273
|
| `/session-delete` | POST `{ sessionId }`. Thin proxy over the manager: POST `/:id/stop` (idempotent) then DELETE `/:id`. Sends **no** accountId. The manager resolves the session by scanning every slug under `projects/` for its JSONL (`slugForExistingJsonl`, covering `archive/`), exactly as the sidebar list enumerates rows, and gates on file existence alone — so any session the operator can see is deletable. This replaces the earlier accountId-derived slug and the `!row` term, which narrowed resolution below the list and left a session under a non-house slug with no watcher row (e.g. a legacy `-home-admin` session on a single-account install) listed yet un-deletable. Account scope is a view filter, not a delete gate — admin access is install-wide, so there is nothing to scope against. The sibling `/session-stop` (the standalone End control) resolves identically and also forwards no accountId. | `POST /` |
|
|
3249
|
-
| `/session-rc-spawn` | POST `{ sessionId?, name? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. For a webchat-bound spawn (every admin-gated host's "New session", returning a same-origin `/chat?session=<id>` target — `resolveRcSpawnOutcome` → `sameOrigin:true`) the Sidebar navigates the **current** tab via `window.location.assign`, replacing the dashboard in place (back returns to it); only a claude.ai/code slug (`sameOrigin:false`, the bare-admin resume bridge, never a new-session outcome) navigates a separately-opened tab. On `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. | `POST /` |
|
|
3274
|
+
| `/session-rc-spawn` | POST `{ sessionId?, name?, title?, model?, effort? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. For a webchat-bound spawn (every admin-gated host's "New session", returning a same-origin `/chat?session=<id>` target — `resolveRcSpawnOutcome` → `sameOrigin:true`) the Sidebar navigates the **current** tab via `window.location.assign`, replacing the dashboard in place (back returns to it); only a claude.ai/code slug (`sameOrigin:false`, the bare-admin resume bridge, never a new-session outcome) navigates a separately-opened tab. On `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. **Naming.** `title` is the operator's name for a NEW session, typed in the naming modal that gates all four New-session controls (sidebar CTA, composer popover, zero-sessions splash, Conversations flyout); nothing is POSTed until the operator confirms it, and the modal prefills `New session: <date, time>`. `resolveSpawnPlan` trims it, it never rides the manager spawn payload, and this route writes it to the user-title store through the manager's `POST /:id/rename` **before it answers**, so the client cannot navigate ahead of the write; the response gains `titleSource: 'user' \| 'none'`. A resume carries no title. A rejected or unreachable rename leaves the spawn intact and returns `'none'`. Lifeline: `[chat-spawn] op=title spawnReqId=… sessionId=… titleLen=… source=modal\|absent outcome=applied\|rejected\|failed`, where `source=absent` on a new spawn is the standing signal that a control bypassed the modal. | `POST /` |
|
|
3250
3275
|
| `/claude-sessions` | **Spawn surface only**. `POST /` is the Sidebar new-session-with-prompt path, cookie-auth only (the recorder loopback caller was removed; LinkedIn ingest moved to `/rc-spawn`). The former UI-facing handlers (SSE row feed, list, resume, stop, rename, archive, delete, `/:id/meta`, `/:id/input`, `/:id/log`) were removed — the maxy dashboard no longer manages or displays sessions. | `POST /` |
|
|
3251
3276
|
|
|
3252
3277
|
**Admin session management moved entirely to claude's own interfaces** (claude.ai/code, claude desktop). A manager-owned per-account `claude rc --spawn same-dir` daemon registers the device as a Remote Control target there; the composer creates / resumes / stops / renames / archives / deletes sessions, with model + permission-mode applied at inception. The model lever is `account.json.adminModel` → `CLAUDE_CONFIG_DIR/settings.json "model"`, written by the daemon supervisor at boot; the reasoning-effort lever is `account.json.effort` → `settings.json "effortLevel"` (accepted values `low|medium|high|xhigh`; any other value, e.g. legacy `"auto"`, leaves the key unset so claude's own default governs), written by the same read-merge-write at boot. A third inception lever, permission mode, is added: `account.json.adminPermissionMode` → `settings.json "permissions.defaultMode"` (the 5 composer-writable modes `default|acceptEdits|plan|auto|bypassPermissions`; `dontAsk` is out of scope). Unlike model/effort (top-level keys), this lever **deep-merges** the nested `permissions.defaultMode`, owning only that key and preserving sibling `permissions` keys (allow/deny/…) — what the binary and the spawn lifeline actually read. **For admin sessions, permission mode is fixed (Bypass) and not operator-chosen, while model and effort ARE operator-chosen at launch** via the composer's `+` new-session popover — a radio model list (Opus / Sonnet / Haiku) plus a 5-stop Faster→Smarter effort slider (Low / Medium / High / Extra / Max, where `max` applies via the `--effort` argv rather than `settings.json`); the popover description lives in `.docs/admin-webchat-native-channel.md`. Absent a pick (and on every resume), the daemon-pinned defaults govern: the `/chat` composer *footer* carries no live re-seat pickers — `rc-daemon.ts` pins settings.json `model`/`effortLevel`/`permissions.defaultMode` to `claude-opus-5[1m]` / `medium` / `bypassPermissions` regardless of `account.json` (read by the rc-spawn webchat path), and `pty-spawner.ts` forces the claude.ai/code PTY `--permission-mode` to `bypassPermissions` for `role=admin`. Public sessions are fixed to `claude-sonnet-5` (the `/public-spawn` chokepoint + `buildPublicWebchatSpawnRequest`), keeping `dontAsk`. **WhatsApp admin sessions override the daemon default:** the `/rc-spawn` handler pins `claude-sonnet-5` / `--effort max` / `bypassPermissions` for a `role=admin` WhatsApp spawn (constants `WHATSAPP_ADMIN_MODEL`/`WHATSAPP_ADMIN_EFFORT` in `http-server.ts`), pushing `--model`/`--effort` on both the fresh spawn and every resume so the params hold for the session's whole life. The override is scoped to sessions *born* under it: a fresh WhatsApp admin spawn stamps its classification sidecar `model=claude-sonnet-5`, and a resume re-asserts Sonnet/max only when that stamp is present — so a WhatsApp session created before this change (sidecar `model=null`) finishes its life on the Opus/medium default. Webchat and Telegram admin sessions are unaffected; public WhatsApp inbound never spawns. The composer footer instead renders a **read-only** params row (mode · model · effort labels) plus a context-window usage figure (`pct% · ~used/~window`), all sourced from the session pointer; the full path is in `.docs/admin-webchat-native-channel.md`. The composer cannot re-seat a session, so the "no transcript" 400 on a brand-new chat is unreachable; the `/api/admin/session-reseat` + `/api/webchat/settings` routes and the dashboard re-seat form stay as off-composer surfaces. When the live session runs in `default` ("Ask permissions") mode, a tool the agent attempts that needs approval surfaces an Allow/Deny card in the composer (tool, description, argument preview); a click sends the verdict over Claude Code's channel permission relay and the tool runs or is refused — webchat is the only interactive ask surface (WhatsApp stays text-only; Telegram has no agent channel). Beyond the composer, the maxy admin UI keeps a single "New session" link (`https://claude.ai/code`, opens in a new tab) and no separate session list, viewer, controls, or model/mode picker. The daemon supervisor lives at [`platform/services/claude-session-manager/src/rc-daemon.ts`](../../../services/claude-session-manager/src/rc-daemon.ts). The `/session-defaults` route and `SpawnPreference` node were deleted with the picker. `/new-session-failure`, `/new-session-submit`, and `/claude-capabilities` are now orphaned (consumed only by the deleted NewSessionModal) — see [`.tasks/501`](../../../.tasks/) for their removal.
|
|
@@ -3900,6 +3925,16 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
3900
3925
|
> - **Dashboard home** renders on `/` (a greeting, stat cards, a Live-now column,
|
|
3901
3926
|
> a Recent artefacts card, and a Channels column), consuming existing endpoints
|
|
3902
3927
|
> only.
|
|
3928
|
+
> - **Channels card rows.** A WhatsApp row shows a profile picture,
|
|
3929
|
+
> the conversation's name, its last message, and the time, matching the layout
|
|
3930
|
+
> of the WhatsApp client. The name is the one WhatsApp itself holds: a group
|
|
3931
|
+
> shows its group subject, a 1:1 shows the contact name saved on the paired
|
|
3932
|
+
> phone. A group row carries an extra `Group` chip. When no name has been
|
|
3933
|
+
> captured yet, a group falls back to its group id and a 1:1 to its phone
|
|
3934
|
+
> number; neither ever shows a group member's name or the install's own
|
|
3935
|
+
> WhatsApp display name. The existing `Public` chip (the message came from
|
|
3936
|
+
> outside) and `House` / `Client` chip (which account owns the thread) are
|
|
3937
|
+
> unchanged.
|
|
3903
3938
|
> - **Per-surface header.** Each surface carries its own header (`PageHeader`):
|
|
3904
3939
|
> the sidebar collapse toggle, the page title, and a Claude status pill on the
|
|
3905
3940
|
> right (Connected / Connect, driven by `/api/health`). The pill replaces the
|
|
@@ -5030,8 +5065,12 @@ For estate-agent client work surfaced on the `/tasks` operator page, `work-creat
|
|
|
5030
5065
|
|
|
5031
5066
|
Two surfaces feed the gate. (1) **Workflow path:** `memory-write` accepts an optional `producedByTaskId` parameter. When set, an inbound `:PRODUCED` edge from that Task is composed into the write's relationships before the gate runs — the typical agent-side pattern is to call `work-create` at the start of an autonomous flow, capture `taskId`, and pass it as `producedByTaskId` on every subsequent `memory-write` for a gated label. The gate verifies Task and write share the same `accountId`; mismatch is rejected loud. (2) **Direct-ask path:** the admin server resolves the active `:AdminConversation`'s `sessionId` UUID and stamps it as `SESSION_NODE_ID` in the spawn env at PTY-spawn time. The same stamp propagates onto specialist subagent spawns the admin dispatches so listing-curator, content-producer, database-operator etc. inherit the same conversation anchor. The `contact-create` and `memory-write` wrappers call `injectConversationProvenance` (exported from [`@maxy/graph-write`](../../../lib/graph-write/src/conversation-provenance.ts)) which MATCHes `(c:Conversation {sessionId, accountId})` — account isolation is part of the natural key, not a separate gate — and prepends the synthetic `:PRODUCED` edge (composed by Neo4j elementId, which the helper reads off the MATCH). No agent-visible schema field changes. `memory-write` uses the env-stamp only as a fallback when `producedByTaskId` is unset; `contact-create` has no `producedByTaskId` parameter today and relies on the env-stamp alone. Autonomous (cron-driven) specialists with no parent conversation legitimately have no env-stamp; those must thread `producedByTaskId`.
|
|
5032
5067
|
|
|
5068
|
+
A task also carries a **workspace** edge, distinct from the two provenance ones above: `(t:Task)-[:WORKED_ON_IN]->(c:Conversation)` is the session an operator works the task in, written only by the `/tasks` page Session control. `RAISED_DURING` and `PART_OF` say where a task was *raised*, so neither can answer "which chat do I continue this task in" — a task raised in an unrelated conversation would resume that one. At most one such edge exists per task: the control's link route deletes any edge to a different Conversation before merging the new one, which is how a resume the session manager redirected to a live fork re-points itself rather than staying on the dead source. The `:Task` also carries `sessionSpawnRequestedAt`, stamped before the spawn and removed when the link lands, so a session that was created but never linked is countable rather than invisible.
|
|
5069
|
+
|
|
5033
5070
|
Operator audit cyphers:
|
|
5034
5071
|
- "What entities did this conversation's actions produce?" — `MATCH (c:AdminConversation {sessionId:$id})<-[:RAISED_DURING]-(t:Task)-[:PRODUCED]->(e) RETURN labels(e), e.name, t.kind, t.status`
|
|
5072
|
+
- "Which session is this task worked in?" — `MATCH (t:Task {taskId:$id})-[:WORKED_ON_IN]->(c:Conversation) RETURN c.sessionId, c.name`
|
|
5073
|
+
- "Which tasks were marked for a session spawn that never linked?" — `MATCH (t:Task {accountId:$acct}) WHERE t.sessionSpawnRequestedAt IS NOT NULL AND NOT (t)-[:WORKED_ON_IN]->(:Conversation) RETURN t.taskId, t.name, t.sessionSpawnRequestedAt`
|
|
5035
5074
|
- "What cloudflare resources did this tunnel-login produce?" — `MATCH (t:Task {kind:'cloudflare-tunnel-login', status:'completed'})-[:PRODUCED]->(r) RETURN t.taskId, r.tunnelId, r.hostnameValue ORDER BY t.completedAt DESC`
|
|
5036
5075
|
|
|
5037
5076
|
See `.docs/neo4j.md § Process provenance doctrine` for the full enforcement contract, observability surface, and out-of-scope deferrals.
|
|
@@ -9,6 +9,18 @@ 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-28 (0.1.522)
|
|
13
|
+
|
|
14
|
+
- You can now connect a shared Outlook mailbox you have been given access to, without needing a separate licence for it.
|
|
15
|
+
- WhatsApp conversations now show the real chat name, contact avatar and last message, instead of guessing from the first message.
|
|
16
|
+
- Open tasks now have a Session button that opens or resumes a chat session named for that task.
|
|
17
|
+
|
|
18
|
+
## 2026-07-28 (0.1.521)
|
|
19
|
+
|
|
20
|
+
- Starting a new session now opens a naming modal first, and New-session controls read white on black across every brand.
|
|
21
|
+
- WhatsApp conversation reader now identifies group message senders by their real JID, and a turn no longer reports a still-in-flight attachment as missing.
|
|
22
|
+
- A stale pending-interactive entry no longer sends its narration to the customer; the calendar attachment diagnostic now names the flat single-part shape it used to drop.
|
|
23
|
+
|
|
12
24
|
## 2026-07-28 (0.1.520)
|
|
13
25
|
|
|
14
26
|
- The admin now lands on the Dashboard, with a Public pages card linking the booking site and file-drop portal.
|
|
@@ -58,7 +58,7 @@ either is a regression.
|
|
|
58
58
|
| `/sessions` | Legacy admin-server conversation routes. No UI consumer remains after the ConversationsModal was retired; the surviving handlers are deletion candidates and not described here. | (legacy, no live caller) |
|
|
59
59
|
| `/sidebar-sessions` | Sole data path for the sidebar Sessions list. One JSONL on disk equals one row. The row's delete button is the only way a row disappears. Each row carries `sessionId`, `title`, `startedAt`, `live`, `isSubagent`, `pid: number \| null` (basename of the matched `sessions/<pid>.json`), and `projectDir` (the directory holding the JSONL — consumed by the delete route). The payload also carries top-level `accountId` so the pane renders the full UUID label whose first ~8 chars prefix-match the truncated Remote Control daemon entry in claude.ai/code. The legacy `rcUrl` field is gone — the row's external-link affordance now POSTs `/session-rc-spawn` to start a fresh local `claude --remote-control <name> --session-id <sid>` PTY on every click. **WhatsApp and Telegram sessions (admin + public) appear in this list**, colour-coded by channel (WhatsApp green `#25D366`, Telegram blue `#229ED9`, via a `conv-channel-*` left accent bar) and carrying the standard row kebab (Usage & cost, Reset/re-seat, Rename, Archive, Delete) so the operator can inspect and re-seat a channel session's params. Each row's `channel` is the authoritative `.meta.json` sidecar value (`whatsapp`/`telegram`/`webchat`/`browser`/null), not the JSONL-envelope channel. Each row also carries its sidecar `senderId`. For a WhatsApp/Telegram row with a `senderId` and no operator rename or ai-title (`titleSource:'prefix'`), the route resolves a contact `personName` and the sidebar renders the reader panel's `"<name> <number>"` label via the shared `operatorConvName`, degrading to the number alone then the stripped title on a miss. On a `whatsapp` row the number is printed in international form (`+44 7504 472444`, `+33 7 68 24 30 67`) by `formatSenderPhone` — `normalizeE164` then libphonenumber-js `formatInternational`, gated on `isPossible` so an unresolved LID prints raw; a `telegram` senderId is a Telegram user id and a `webchat` one is `session:<id>`, so neither is regrouped. The formatting is display-only: row identity, matching, and store keys stay on the normalized digits. Because the label function is shared, a Sessions row and that conversation's reader-panel row read identically. A channel row name has two sources, in precedence order. First the shared `resolveOperatorNamesBySender` helper the WhatsApp reader also calls, which matches the sender phone against the account's admins — that names an admin WhatsApp session and every Telegram session. Second, for a `whatsapp` row only, the WhatsApp pushName from the account's on-disk message store (`storePushNamesBySender` over `readConversationSummaries`, keyed by `normalizeE164` of the counterparty phone so a `+44 7…` sender matches a `447…` store key). The store is consulted only when the resolver reports **no binding** for that sender. That is typically a public WhatsApp customer, the case this exists for — such a sender has no admin record and so can only be named by the store, and without it those rows degraded to the bare number while the reader panel named them — though an admin missing from `users.json` qualifies too. A resolver miss caused by Neo4j being unreachable deliberately does NOT qualify: that is an outage rather than an absent binding, so the row degrades to the number and still logs `reason=neo4j-down`, keeping the outage signal intact. Telegram has no message store and is never looked up. The store read is guarded: a corrupt or absent store degrades to no pushName and logs `op=store-unreadable`, never 500s the list. Both sources are the ones the reader itself uses, so the two surfaces cannot drift; a renamed or ai-titled channel row keeps its own title. The Sessions list reads the viewed account's own store only. An account-manager's thread persists under the house account rather than the sub-account it manages, so on a multi-account install viewing a sub-account that row is not named here even though the reader panel names it — the reader additionally consults the house store, filtered to manager phones bound to that sub. The only role/channel excluded from the list is `public`+`webchat` (its dedicated reader is the sole surface); the exclusion predicate is `isSessionListExcluded`, decoupled from the reader's inclusion predicate `isReaderChannelSession` (unchanged), so the WhatsApp/Telegram reader panels are unaffected — a store-only WhatsApp conversation with no session JSONL stays reader-only. Observability rides `[admin-sessions-list] … channelInList=whatsapp:<n>,telegram:<n>,webchat:<n> channelNameCandidates=<n> channelNamesResolved=<n> (operator=<n> store=<n>) unknownChannel=<n> excludedPublicWebchat=<n>`, and each unresolved channel name logs one `[admin-sessions-list] op=channel-name-unresolved sessionId=<id8> channel=<whatsapp\|telegram> senderId=<id> reason=<no-senderId\|classify-miss\|neo4j-down>` line — emitted only when **both** sources miss, so a residual shortfall means a genuinely nameless sender rather than the expected public-customer floor, and the `operator=`/`store=` split says which source is carrying the names. | `GET /` |
|
|
60
60
|
| `/session-delete` | POST `{ sessionId }`. Thin proxy over the manager: POST `/:id/stop` (idempotent) then DELETE `/:id`. Sends **no** accountId. The manager resolves the session by scanning every slug under `projects/` for its JSONL (`slugForExistingJsonl`, covering `archive/`), exactly as the sidebar list enumerates rows, and gates on file existence alone — so any session the operator can see is deletable. This replaces the earlier accountId-derived slug and the `!row` term, which narrowed resolution below the list and left a session under a non-house slug with no watcher row (e.g. a legacy `-home-admin` session on a single-account install) listed yet un-deletable. Account scope is a view filter, not a delete gate — admin access is install-wide, so there is nothing to scope against. The sibling `/session-stop` (the standalone End control) resolves identically and also forwards no accountId. | `POST /` |
|
|
61
|
-
| `/session-rc-spawn` | POST `{ sessionId?, name? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. For a webchat-bound spawn (every admin-gated host's "New session", returning a same-origin `/chat?session=<id>` target — `resolveRcSpawnOutcome` → `sameOrigin:true`) the Sidebar navigates the **current** tab via `window.location.assign`, replacing the dashboard in place (back returns to it); only a claude.ai/code slug (`sameOrigin:false`, the bare-admin resume bridge, never a new-session outcome) navigates a separately-opened tab. On `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. | `POST /` |
|
|
61
|
+
| `/session-rc-spawn` | POST `{ sessionId?, name?, title?, model?, effort? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. For a webchat-bound spawn (every admin-gated host's "New session", returning a same-origin `/chat?session=<id>` target — `resolveRcSpawnOutcome` → `sameOrigin:true`) the Sidebar navigates the **current** tab via `window.location.assign`, replacing the dashboard in place (back returns to it); only a claude.ai/code slug (`sameOrigin:false`, the bare-admin resume bridge, never a new-session outcome) navigates a separately-opened tab. On `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. **Naming.** `title` is the operator's name for a NEW session, typed in the naming modal that gates all four New-session controls (sidebar CTA, composer popover, zero-sessions splash, Conversations flyout); nothing is POSTed until the operator confirms it, and the modal prefills `New session: <date, time>`. `resolveSpawnPlan` trims it, it never rides the manager spawn payload, and this route writes it to the user-title store through the manager's `POST /:id/rename` **before it answers**, so the client cannot navigate ahead of the write; the response gains `titleSource: 'user' \| 'none'`. A resume carries no title. A rejected or unreachable rename leaves the spawn intact and returns `'none'`. Lifeline: `[chat-spawn] op=title spawnReqId=… sessionId=… titleLen=… source=modal\|absent outcome=applied\|rejected\|failed`, where `source=absent` on a new spawn is the standing signal that a control bypassed the modal. | `POST /` |
|
|
62
62
|
| `/claude-sessions` | **Spawn surface only**. `POST /` is the Sidebar new-session-with-prompt path, cookie-auth only (the recorder loopback caller was removed; LinkedIn ingest moved to `/rc-spawn`). The former UI-facing handlers (SSE row feed, list, resume, stop, rename, archive, delete, `/:id/meta`, `/:id/input`, `/:id/log`) were removed — the maxy dashboard no longer manages or displays sessions. | `POST /` |
|
|
63
63
|
|
|
64
64
|
**Admin session management moved entirely to claude's own interfaces** (claude.ai/code, claude desktop). A manager-owned per-account `claude rc --spawn same-dir` daemon registers the device as a Remote Control target there; the composer creates / resumes / stops / renames / archives / deletes sessions, with model + permission-mode applied at inception. The model lever is `account.json.adminModel` → `CLAUDE_CONFIG_DIR/settings.json "model"`, written by the daemon supervisor at boot; the reasoning-effort lever is `account.json.effort` → `settings.json "effortLevel"` (accepted values `low|medium|high|xhigh`; any other value, e.g. legacy `"auto"`, leaves the key unset so claude's own default governs), written by the same read-merge-write at boot. A third inception lever, permission mode, is added: `account.json.adminPermissionMode` → `settings.json "permissions.defaultMode"` (the 5 composer-writable modes `default|acceptEdits|plan|auto|bypassPermissions`; `dontAsk` is out of scope). Unlike model/effort (top-level keys), this lever **deep-merges** the nested `permissions.defaultMode`, owning only that key and preserving sibling `permissions` keys (allow/deny/…) — what the binary and the spawn lifeline actually read. **For admin sessions, permission mode is fixed (Bypass) and not operator-chosen, while model and effort ARE operator-chosen at launch** via the composer's `+` new-session popover — a radio model list (Opus / Sonnet / Haiku) plus a 5-stop Faster→Smarter effort slider (Low / Medium / High / Extra / Max, where `max` applies via the `--effort` argv rather than `settings.json`); the popover description lives in `.docs/admin-webchat-native-channel.md`. Absent a pick (and on every resume), the daemon-pinned defaults govern: the `/chat` composer *footer* carries no live re-seat pickers — `rc-daemon.ts` pins settings.json `model`/`effortLevel`/`permissions.defaultMode` to `claude-opus-5[1m]` / `medium` / `bypassPermissions` regardless of `account.json` (read by the rc-spawn webchat path), and `pty-spawner.ts` forces the claude.ai/code PTY `--permission-mode` to `bypassPermissions` for `role=admin`. Public sessions are fixed to `claude-sonnet-5` (the `/public-spawn` chokepoint + `buildPublicWebchatSpawnRequest`), keeping `dontAsk`. **WhatsApp admin sessions override the daemon default:** the `/rc-spawn` handler pins `claude-sonnet-5` / `--effort max` / `bypassPermissions` for a `role=admin` WhatsApp spawn (constants `WHATSAPP_ADMIN_MODEL`/`WHATSAPP_ADMIN_EFFORT` in `http-server.ts`), pushing `--model`/`--effort` on both the fresh spawn and every resume so the params hold for the session's whole life. The override is scoped to sessions *born* under it: a fresh WhatsApp admin spawn stamps its classification sidecar `model=claude-sonnet-5`, and a resume re-asserts Sonnet/max only when that stamp is present — so a WhatsApp session created before this change (sidecar `model=null`) finishes its life on the Opus/medium default. Webchat and Telegram admin sessions are unaffected; public WhatsApp inbound never spawns. The composer footer instead renders a **read-only** params row (mode · model · effort labels) plus a context-window usage figure (`pct% · ~used/~window`), all sourced from the session pointer; the full path is in `.docs/admin-webchat-native-channel.md`. The composer cannot re-seat a session, so the "no transcript" 400 on a brand-new chat is unreachable; the `/api/admin/session-reseat` + `/api/webchat/settings` routes and the dashboard re-seat form stay as off-composer surfaces. When the live session runs in `default` ("Ask permissions") mode, a tool the agent attempts that needs approval surfaces an Allow/Deny card in the composer (tool, description, argument preview); a click sends the verdict over Claude Code's channel permission relay and the tool runs or is refused — webchat is the only interactive ask surface (WhatsApp stays text-only; Telegram has no agent channel). Beyond the composer, the maxy admin UI keeps a single "New session" link (`https://claude.ai/code`, opens in a new tab) and no separate session list, viewer, controls, or model/mode picker. The daemon supervisor lives at [`platform/services/claude-session-manager/src/rc-daemon.ts`](../../../services/claude-session-manager/src/rc-daemon.ts). The `/session-defaults` route and `SpawnPreference` node were deleted with the picker. `/new-session-failure`, `/new-session-submit`, and `/claude-capabilities` are now orphaned (consumed only by the deleted NewSessionModal) — see [`.tasks/501`](../../../.tasks/) for their removal.
|
|
@@ -712,6 +712,16 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
712
712
|
> - **Dashboard home** renders on `/` (a greeting, stat cards, a Live-now column,
|
|
713
713
|
> a Recent artefacts card, and a Channels column), consuming existing endpoints
|
|
714
714
|
> only.
|
|
715
|
+
> - **Channels card rows.** A WhatsApp row shows a profile picture,
|
|
716
|
+
> the conversation's name, its last message, and the time, matching the layout
|
|
717
|
+
> of the WhatsApp client. The name is the one WhatsApp itself holds: a group
|
|
718
|
+
> shows its group subject, a 1:1 shows the contact name saved on the paired
|
|
719
|
+
> phone. A group row carries an extra `Group` chip. When no name has been
|
|
720
|
+
> captured yet, a group falls back to its group id and a 1:1 to its phone
|
|
721
|
+
> number; neither ever shows a group member's name or the install's own
|
|
722
|
+
> WhatsApp display name. The existing `Public` chip (the message came from
|
|
723
|
+
> outside) and `House` / `Client` chip (which account owns the thread) are
|
|
724
|
+
> unchanged.
|
|
715
725
|
> - **Per-surface header.** Each surface carries its own header (`PageHeader`):
|
|
716
726
|
> the sidebar collapse toggle, the page title, and a Claude status pill on the
|
|
717
727
|
> right (Connected / Connect, driven by `/api/health`). The pill replaces the
|
|
@@ -560,8 +560,12 @@ For estate-agent client work surfaced on the `/tasks` operator page, `work-creat
|
|
|
560
560
|
|
|
561
561
|
Two surfaces feed the gate. (1) **Workflow path:** `memory-write` accepts an optional `producedByTaskId` parameter. When set, an inbound `:PRODUCED` edge from that Task is composed into the write's relationships before the gate runs — the typical agent-side pattern is to call `work-create` at the start of an autonomous flow, capture `taskId`, and pass it as `producedByTaskId` on every subsequent `memory-write` for a gated label. The gate verifies Task and write share the same `accountId`; mismatch is rejected loud. (2) **Direct-ask path:** the admin server resolves the active `:AdminConversation`'s `sessionId` UUID and stamps it as `SESSION_NODE_ID` in the spawn env at PTY-spawn time. The same stamp propagates onto specialist subagent spawns the admin dispatches so listing-curator, content-producer, database-operator etc. inherit the same conversation anchor. The `contact-create` and `memory-write` wrappers call `injectConversationProvenance` (exported from [`@maxy/graph-write`](../../../lib/graph-write/src/conversation-provenance.ts)) which MATCHes `(c:Conversation {sessionId, accountId})` — account isolation is part of the natural key, not a separate gate — and prepends the synthetic `:PRODUCED` edge (composed by Neo4j elementId, which the helper reads off the MATCH). No agent-visible schema field changes. `memory-write` uses the env-stamp only as a fallback when `producedByTaskId` is unset; `contact-create` has no `producedByTaskId` parameter today and relies on the env-stamp alone. Autonomous (cron-driven) specialists with no parent conversation legitimately have no env-stamp; those must thread `producedByTaskId`.
|
|
562
562
|
|
|
563
|
+
A task also carries a **workspace** edge, distinct from the two provenance ones above: `(t:Task)-[:WORKED_ON_IN]->(c:Conversation)` is the session an operator works the task in, written only by the `/tasks` page Session control. `RAISED_DURING` and `PART_OF` say where a task was *raised*, so neither can answer "which chat do I continue this task in" — a task raised in an unrelated conversation would resume that one. At most one such edge exists per task: the control's link route deletes any edge to a different Conversation before merging the new one, which is how a resume the session manager redirected to a live fork re-points itself rather than staying on the dead source. The `:Task` also carries `sessionSpawnRequestedAt`, stamped before the spawn and removed when the link lands, so a session that was created but never linked is countable rather than invisible.
|
|
564
|
+
|
|
563
565
|
Operator audit cyphers:
|
|
564
566
|
- "What entities did this conversation's actions produce?" — `MATCH (c:AdminConversation {sessionId:$id})<-[:RAISED_DURING]-(t:Task)-[:PRODUCED]->(e) RETURN labels(e), e.name, t.kind, t.status`
|
|
567
|
+
- "Which session is this task worked in?" — `MATCH (t:Task {taskId:$id})-[:WORKED_ON_IN]->(c:Conversation) RETURN c.sessionId, c.name`
|
|
568
|
+
- "Which tasks were marked for a session spawn that never linked?" — `MATCH (t:Task {accountId:$acct}) WHERE t.sessionSpawnRequestedAt IS NOT NULL AND NOT (t)-[:WORKED_ON_IN]->(:Conversation) RETURN t.taskId, t.name, t.sessionSpawnRequestedAt`
|
|
565
569
|
- "What cloudflare resources did this tunnel-login produce?" — `MATCH (t:Task {kind:'cloudflare-tunnel-login', status:'completed'})-[:PRODUCED]->(r) RETURN t.taskId, r.tunnelId, r.hostnameValue ORDER BY t.completedAt DESC`
|
|
566
570
|
|
|
567
571
|
See `.docs/neo4j.md § Process provenance doctrine` for the full enforcement contract, observability surface, and out-of-scope deferrals.
|
|
@@ -6,7 +6,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
6
6
|
|
|
7
7
|
1. **No app to register.** The client ID ships with the brand (`brand.json#outlookClientId`), and the installer stamps it onto the session-manager env, so there is zero operator Azure work. On a brand that ships an empty value the plugin stays inert until its vendor app exists — see `platform/plugins/outlook/references/auth.md` for the one-time-per-brand vendor provisioning runbook (a vendor step, not an operator or end-user step), including setting **Allow public client flows** to Yes.
|
|
8
8
|
2. **Per account: register the Outlook account** — in admin chat, ask the agent to "register my Outlook account". The agent runs `outlook-account-register`, which returns a short user code and the URL `microsoft.com/devicelogin` and relays both to you.
|
|
9
|
-
3. **Consent on any device** — open `microsoft.com/devicelogin` on your phone or laptop, enter the code, sign in to your Microsoft account, and consent to the requested scopes (`offline_access`, `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `Calendars.ReadWrite`, `Contacts.Read`). `Mail.Send` enables sending
|
|
9
|
+
3. **Consent on any device** — open `microsoft.com/devicelogin` on your phone or laptop, enter the code, sign in to your Microsoft account, and consent to the requested scopes (`offline_access`, `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `Mail.ReadWrite.Shared`, `Mail.Send.Shared`, `Calendars.ReadWrite`, `Contacts.Read`). `Mail.Send` enables sending, `Calendars.ReadWrite` enables calendar control, and the two `.Shared` scopes let this mailbox reach a shared mailbox it has Full Access to; a mailbox last consented on an older scope set must re-register before send, any calendar write, or any shared mailbox succeeds. Nothing else to do: the server finishes the sign-in automatically once you consent, usually within about half a minute, and the mailbox is then connected.
|
|
10
10
|
4. **Done.** Subsequent tool calls (mail, calendar, contacts) use the persisted refresh token transparently.
|
|
11
11
|
5. **Several mailboxes on one account.** Register again to add a second mailbox; it is stored alongside the first, not in place of it. Every operational tool then takes an optional `mailbox` argument — an email address or graphUserId. With one mailbox, omit it. With two or more, name it; omitting it returns a refusal listing the connected mailboxes. `outlook-mailbox-list` shows every connected mailbox. Only one registration runs at a time per account; starting another while one is live returns an in-progress signal.
|
|
12
12
|
|
|
@@ -33,12 +33,32 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
33
33
|
| `outlook-calendar-freebusy` | Availability for one or more addresses over a window. |
|
|
34
34
|
| `outlook-contacts-list` | Top contacts. Default top=50. |
|
|
35
35
|
| `outlook-mailbox-info` | Health probe for one mailbox — auth state, refresh-window, folder count. Name the mailbox with `mailbox` when several are attached. |
|
|
36
|
-
| `outlook-mailbox-list` | Every connected mailbox with its email, scopes, and token health. Local only — no network — so it also flags a mailbox whose stored credentials cannot be read. |
|
|
36
|
+
| `outlook-mailbox-list` | Every connected mailbox with its email, scopes, and token health, signed-in and shared alike (`kind`, and `via` for a shared one). Local only by default — no network — so it also flags a mailbox whose stored credentials cannot be read. `verify:true` additionally probes each shared mailbox and reports `reachable`. |
|
|
37
|
+
| `outlook-shared-mailbox-add` | Attach a shared mailbox (no sign-in, no licence) through the Full Access of a mailbox that is already connected. `address` is the shared mailbox; `via` names the connected mailbox when more than one is attached. Proves the access before storing anything, so a refusal is the real answer. |
|
|
38
|
+
| `outlook-shared-mailbox-remove` | Detach a shared mailbox. Drops the pointer only; no credential is touched. |
|
|
37
39
|
| `outlook-mail-send` | Send a message. No attachments → `POST /me/sendMail`; with attachments → draft, attach, send (`/me/sendMail` carries attachments inline and cannot fit a large file). to/cc/bcc arrays, subject, body, isHtml, attachments (account-dir paths, 25 MB/file). Success asserted on Graph 202; no message id on either route. Needs `Mail.Send` (+ `Mail.ReadWrite` to attach). |
|
|
38
40
|
| `outlook-draft` | Create a draft in Drafts, either a new message or a threaded reply. Returns the draft id; never sends. Attachments are attached before the id is returned, so the draft is ready to send. Needs `Mail.ReadWrite`. **New message** (no `replyToMessageId`): `POST /me/messages`, requires `to` + `subject`, same body model as `outlook-mail-send`, threads under nothing. **Threaded reply** (`replyToMessageId` = the id of the message being answered): `POST /me/messages/{id}/createReply` — the draft carries the parent's conversationId, so it lands under the original. This is the only way to draft a reply without sending it: `outlook-mail-reply` threads but always sends. Graph sets the recipients and `RE:` subject from the original and composes the body above its own quote, matching what `outlook-mail-reply` sends, so the threaded path is **plain text only** — `to`, `subject`, and `isHtml: true` are **refused** (not ignored) when replying. `replyAll` includes everyone on the original; `cc`/`bcc` add recipients. |
|
|
39
41
|
| `outlook-draft-edit` | Update an existing draft in place (`PATCH /me/messages/{draftId}`) and/or add and remove attachments. Graph drafts are mutable — pass draftId plus the fields to change (to/cc/bcc/subject/body/isHtml/attachments/removeAttachments). `attachments` adds; `removeAttachments` takes files off by name or attachment id; both together replace a file in one call. Sent messages cannot be edited. Needs `Mail.ReadWrite`. |
|
|
40
42
|
| `outlook-draft-send` | Send an existing draft by id (`POST /me/messages/{id}/send`). Graph consumes the draft, returns 202. Needs `Mail.Send`. |
|
|
41
43
|
|
|
44
|
+
## Shared mailboxes
|
|
45
|
+
|
|
46
|
+
A shared mailbox (`team@`, `info@`, `accounts@`) has no sign-in of its own, so nobody can connect it the way a normal mailbox is connected. **It does not need a licence and should never be given one for this.** Whoever has Full Access to it connects their own mailbox in the normal way, then attaches the shared one to it:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
outlook-shared-mailbox-add address=team@yourcompany.com
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Add `via=<your address>` if this account has more than one mailbox connected. From then on the shared address works as the `mailbox` argument on any mail tool, and `outlook-mailbox-list` shows it as `kind:delegated`.
|
|
53
|
+
|
|
54
|
+
The attach call checks the access before it stores anything, so what it says back is the real state of things:
|
|
55
|
+
|
|
56
|
+
- **"does not have Full Access"** — the delegation is missing. Grant it in the Microsoft 365 admin centre: Teams & groups → Shared mailboxes → the mailbox → Members / Read and manage. Nothing is purchased.
|
|
57
|
+
- **"no mailbox with this address in the tenant"** — the address is wrong, or it is a distribution list. A distribution list has no mailbox and cannot be attached.
|
|
58
|
+
- **"connected without the Mail.ReadWrite.Shared scope"** — your mailbox was connected before the plugin asked for the shared scopes. Register it again and it will work.
|
|
59
|
+
|
|
60
|
+
Mail only: calendar and contacts tools refuse on a shared mailbox and say so. If a shared mailbox stops answering, the delegation was probably removed in Exchange — `outlook-mailbox-list verify:true` reports which ones are still reachable.
|
|
61
|
+
|
|
42
62
|
## Drafting to a Contact
|
|
43
63
|
|
|
44
64
|
When the admin agent drafts or sends a message to a known contact, it reads that contact's record first and applies any routing the record carries, for example a preferred send-to address, showing the result for your approval before anything is sent.
|
|
@@ -58,7 +78,10 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
58
78
|
| /me failed post-token | `me-failed account=<id> status=<N>` |
|
|
59
79
|
| Auth ok | `auth-ok account=<id> graphUserId=<id> mail=<addr> scopes=<csv> tokenExpSec=<N>` |
|
|
60
80
|
| Registration already in flight | `register-in-progress account=<id>` (start refused; one device-code flow at a time) |
|
|
61
|
-
| Credential resolve | `cred-resolve account=<id> mailbox=<id-or-empty> source=arg|sole|none|refused
|
|
81
|
+
| Credential resolve | `cred-resolve account=<id> mailbox=<id-or-empty> source=arg|sole|none|refused kind=signed-in|delegated via=<delegator-or-empty>` — names the resolved mailbox and how it was chosen; `source=refused` is the ambiguity/not-found refusal, and `kind=delegated` means the call ran against a shared mailbox through `via` |
|
|
82
|
+
| Shared mailbox attached | `shared-add account=<id> address=<addr> via=<key> outcome=ok|ok-reattached|no-scope|forbidden|not-found` — every refusal path is named, so a failed attach is diagnosable without reproducing it |
|
|
83
|
+
| Shared mailbox detached | `shared-remove account=<id> address=<addr> outcome=ok|absent` |
|
|
84
|
+
| Calendar/contacts on a shared mailbox | `shared-tool-refused account=<id> tool=<tool> prefix=<users-path>` — refused before any request is built |
|
|
62
85
|
| Token refreshed | `token-refreshed account=<id> oldExpSec=<N> newExpSec=<N>` |
|
|
63
86
|
| Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
|
|
64
87
|
| Mail list | `mail-list op=resolve account=<id> folderIn=<name> folderResolved=<token> asRawId=<bool> count=<N> elapsedMs=<N>` (on error, `error=<msg>` replaces `count`) |
|
|
@@ -76,7 +99,7 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
76
99
|
| Scope insufficient | `scope-insufficient tool=<t> account=<id> missing=Calendars.ReadWrite` (stale read-only consent) |
|
|
77
100
|
| Contacts list | `contacts-list account=<id> count=<N> elapsedMs=<N>` |
|
|
78
101
|
| Mailbox info | `mailbox-info account=<id> tokenWithinRefreshWindow=<bool> folderCount=<N>` |
|
|
79
|
-
| Mailbox list | `mailbox-list account=<id> count=<N> unreadable=<N>` (unreadable>0 flags an orphaned or corrupt mailbox store
|
|
102
|
+
| Mailbox list | `mailbox-list account=<id> count=<N> unreadable=<N> addressable=<N> delegated=<N> verified=<N>` — `count` covers signed-in plus delegated entries, `addressable` counts only signed-in ones that are usable by their email, `delegated` counts the attached shared mailboxes, and `verified` is how many of those were probed (0 unless `verify:true`). `unreadable>0` flags an orphaned or corrupt mailbox store |
|
|
80
103
|
| Graph error | `graph-error account=<id> status=<N> code=<graphErrorCode> retryAfterMs=<N-or-null>` — writes flow through the same `callGraph` loop, so a failed write logs `graph-error` too |
|
|
81
104
|
| On-prem rejected | `on-prem-rejected account=<id> mailServer=<host>` (terminal) |
|
|
82
105
|
| Scope insufficient | `scope-insufficient account=<id> tool=<t> missing=Mail.Send` (terminal — re-register to grant send) |
|
package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-metadata-calendar.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { walkAttachmentMetadata, countBodyStructureLeaves } from '../lib/imap.js';
|
|
2
|
+
import { walkAttachmentMetadata, countBodyStructureLeaves, isFlatCalendarRoot } from '../lib/imap.js';
|
|
3
3
|
// Measured on the live mailbox 2026-07-27: UIDs 5104/5105, Outlook meeting
|
|
4
4
|
// invites from ebony@magneticbd.co.uk. The calendar leaf carries no
|
|
5
5
|
// disposition and no name, which is why it was dropped (Task 2066).
|
|
@@ -80,6 +80,37 @@ describe('Task 2066 — text/calendar leaves are attachment-shaped', () => {
|
|
|
80
80
|
]);
|
|
81
81
|
});
|
|
82
82
|
});
|
|
83
|
+
describe('Task 2068 — a message whose whole content type is text/calendar', () => {
|
|
84
|
+
// Measured 2026-07-28 against mail.privateemail.com, mailbox info@rubytech.llc:
|
|
85
|
+
// an APPENDed flat invite came back with no `part` on the root node, and
|
|
86
|
+
// BODY[1] returned the decoded VEVENT text (301 bytes).
|
|
87
|
+
const FLAT_INVITE = {
|
|
88
|
+
type: 'text/calendar',
|
|
89
|
+
parameters: { charset: 'utf-8', method: 'REQUEST' },
|
|
90
|
+
encoding: 'base64',
|
|
91
|
+
size: 416,
|
|
92
|
+
};
|
|
93
|
+
it('reports the root calendar node as part 1', () => {
|
|
94
|
+
expect(walkAttachmentMetadata(FLAT_INVITE)).toEqual([
|
|
95
|
+
{ partKey: '1', filename: 'invite-1.ics', mimeType: 'text/calendar', sizeBytes: 416, encoding: 'base64' },
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
it('still excludes a flat text/plain root', () => {
|
|
99
|
+
expect(walkAttachmentMetadata({ type: 'text/plain', encoding: '7bit', size: 120 })).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
it('does not admit a flat root of another type that carries an attachment disposition', () => {
|
|
102
|
+
// Out of scope for 2068 — no such message has been measured, and a guessed
|
|
103
|
+
// part key would fetch the wrong bytes.
|
|
104
|
+
const flatPdf = {
|
|
105
|
+
type: 'application/pdf',
|
|
106
|
+
disposition: 'attachment',
|
|
107
|
+
dispositionParameters: { filename: 'report.pdf' },
|
|
108
|
+
encoding: 'base64',
|
|
109
|
+
size: 9000,
|
|
110
|
+
};
|
|
111
|
+
expect(walkAttachmentMetadata(flatPdf)).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
83
114
|
describe('Task 2066 — leaf count for the attachments diag', () => {
|
|
84
115
|
it('counts every non-multipart leaf of the invite', () => {
|
|
85
116
|
expect(countBodyStructureLeaves(INVITE)).toBe(3);
|
|
@@ -91,4 +122,18 @@ describe('Task 2066 — leaf count for the attachments diag', () => {
|
|
|
91
122
|
expect(countBodyStructureLeaves(undefined)).toBe(0);
|
|
92
123
|
});
|
|
93
124
|
});
|
|
125
|
+
describe('Task 2068 — the flat calendar shape is named in the diag', () => {
|
|
126
|
+
it('is true for a root calendar node with no part key', () => {
|
|
127
|
+
expect(isFlatCalendarRoot({ type: 'text/calendar', encoding: 'base64', size: 416 })).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
it('is false for a flat text/plain root', () => {
|
|
130
|
+
expect(isFlatCalendarRoot({ type: 'text/plain', size: 120 })).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
it('is false for a calendar part inside a multipart message', () => {
|
|
133
|
+
expect(isFlatCalendarRoot(INVITE)).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
it('is false for an absent structure', () => {
|
|
136
|
+
expect(isFlatCalendarRoot(undefined)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
94
139
|
//# sourceMappingURL=attachment-metadata-calendar.test.js.map
|
package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-metadata-calendar.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-metadata-calendar.test.js","sourceRoot":"","sources":["../../src/__tests__/attachment-metadata-calendar.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"attachment-metadata-calendar.test.js","sourceRoot":"","sources":["../../src/__tests__/attachment-metadata-calendar.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -120,6 +120,11 @@ interface BodyStructureNode {
|
|
|
120
120
|
*
|
|
121
121
|
* Inline parts without a filename (e.g. the message body) are excluded.
|
|
122
122
|
*
|
|
123
|
+
* A message whose whole Content-Type is text/calendar has no MIME part number
|
|
124
|
+
* of its own — ImapFlow only numbers a node when its walk path is non-empty —
|
|
125
|
+
* and is reported as part "1", the RFC 3501 number for a non-multipart
|
|
126
|
+
* message's text (Task 2068). No other flat root is admitted this way.
|
|
127
|
+
*
|
|
123
128
|
* A single-part message embedded in a message/rfc822 part reuses the wrapper's
|
|
124
129
|
* part number rather than getting its own (ImapFlow tools.js `walk(node, path)`
|
|
125
130
|
* for message/rfc822, deliberately, per RFC 3501 part numbering). Fetching that
|
|
@@ -136,6 +141,15 @@ export declare function walkAttachmentMetadata(structure: BodyStructureNode | un
|
|
|
136
141
|
* ambiguity that hid Task 2066's dropped calendar part.
|
|
137
142
|
*/
|
|
138
143
|
export declare function countBodyStructureLeaves(structure: BodyStructureNode | undefined): number;
|
|
144
|
+
/**
|
|
145
|
+
* True when a message's whole content type is `text/calendar` — a non-multipart
|
|
146
|
+
* root node, which ImapFlow leaves without a part key. Reported in the
|
|
147
|
+
* attachments diag so this shape stays greppable now that it is handled: with
|
|
148
|
+
* one leaf, Task 2066's `attachments=0 leaves>1` signal cannot distinguish it
|
|
149
|
+
* from a body-only message, so the next case of this class would otherwise be
|
|
150
|
+
* undetectable in the log (Task 2068).
|
|
151
|
+
*/
|
|
152
|
+
export declare function isFlatCalendarRoot(structure: BodyStructureNode | undefined): boolean;
|
|
139
153
|
export declare function formatBytes(n: number): string;
|
|
140
154
|
/**
|
|
141
155
|
* Format an AttachmentMeta[] as a single-line operator-readable footer.
|