@rubytech/create-maxy-code 0.1.416 → 0.1.419
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/__tests__/brand-port-uniqueness.test.js +74 -0
- package/dist/__tests__/outlook-client-id-env.test.js +46 -0
- package/dist/index.js +31 -0
- package/dist/known-brands.js +1 -0
- package/dist/port-resolution.js +8 -1
- package/package.json +1 -1
- package/payload/platform/config/brand-registry.json +8 -0
- package/payload/platform/config/brand.json +1 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/is-provisioned.test.d.ts +2 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/is-provisioned.test.d.ts.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/is-provisioned.test.js +81 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/is-provisioned.test.js.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/index.d.ts +17 -0
- package/payload/platform/lib/account-enumeration/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/account-enumeration/dist/index.js +46 -9
- package/payload/platform/lib/account-enumeration/dist/index.js.map +1 -1
- package/payload/platform/lib/account-enumeration/src/__tests__/is-provisioned.test.ts +86 -0
- package/payload/platform/lib/account-enumeration/src/index.ts +45 -9
- package/payload/platform/lib/account-scope/dist/index.d.ts +14 -5
- package/payload/platform/lib/account-scope/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/account-scope/dist/index.js +14 -5
- package/payload/platform/lib/account-scope/dist/index.js.map +1 -1
- package/payload/platform/lib/account-scope/src/index.ts +22 -5
- package/payload/platform/lib/active-rules/dist/index.d.ts +35 -0
- package/payload/platform/lib/active-rules/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/active-rules/dist/index.js +94 -0
- package/payload/platform/lib/active-rules/dist/index.js.map +1 -0
- package/payload/platform/lib/active-rules/package.json +7 -0
- package/payload/platform/lib/active-rules/src/index.test.ts +89 -0
- package/payload/platform/lib/active-rules/src/index.ts +133 -0
- package/payload/platform/lib/active-rules/tsconfig.json +9 -0
- package/payload/platform/package.json +4 -3
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +55 -10
- package/payload/platform/plugins/docs/references/internals.md +10 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +2 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +42 -9
- package/payload/platform/plugins/email/references/email-reference.md +1 -1
- package/payload/platform/plugins/filesystem/PLUGIN.md +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.js +16 -2
- package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.js.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js +8 -3
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.js +2 -2
- package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.js.map +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index-testable.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index-testable.js +14 -5
- package/payload/platform/plugins/memory/mcp/dist/index-testable.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +5 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j-constraint.d.ts +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j-constraint.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j-constraint.js +44 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j-constraint.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/owner-person-embed-text.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/owner-person-embed-text.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/owner-person-embed-text.test.js +22 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/owner-person-embed-text.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js +49 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-bootstrap.test.js +105 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-bootstrap.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-embed.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-embed.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-embed.test.js +78 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-embed.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.js +99 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-tool-account.test.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-tool-account.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +0 -5
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +2 -12
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +11 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +22 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +212 -24
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +40 -7
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js +137 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/calendar-write.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/calendar-write.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/calendar-write.test.js +210 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/calendar-write.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +110 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-retry.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-retry.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-retry.test.js +108 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-retry.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +13 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/message.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/message.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/message.test.js +32 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/message.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-store.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-store.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-store.test.js +67 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-store.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +7 -5
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/write-tools.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/write-tools.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/write-tools.test.js +141 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/write-tools.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +69 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +129 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-store.d.ts +39 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-store.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-store.js +101 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-store.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +3 -2
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +187 -5
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +34 -1
- 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 +96 -7
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.d.ts +31 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.js +30 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +41 -17
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +87 -28
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-cancel.d.ts +17 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-cancel.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-cancel.js +38 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-cancel.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.d.ts +42 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.js +59 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-freebusy.d.ts +34 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-freebusy.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-freebusy.js +46 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-freebusy.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-respond.d.ts +15 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-respond.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-respond.js +27 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-respond.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-update.d.ts +11 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-update.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-update.js +20 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-update.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.d.ts +17 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js +26 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts +11 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +26 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts +14 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js +26 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/scripts/verify-doc-impl.sh +2 -2
- package/payload/platform/plugins/outlook/references/auth.md +51 -54
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +97 -4
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +34 -9
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +10 -5
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +9 -1
- package/payload/platform/scripts/__tests__/provision-role-stamp.test.sh +3 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +9 -1
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts +1 -1
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js +5 -1
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js.map +1 -1
- 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 +9 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.d.ts +18 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.js +16 -0
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +36 -4
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +14 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts +46 -0
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js +96 -0
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +33 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +61 -7
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +5 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +6 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +20 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts +9 -0
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -0
- package/payload/platform/services/telegram-channel/dist/instructions.js +100 -0
- package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -0
- package/payload/platform/services/telegram-channel/dist/notification.d.ts +93 -0
- package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -0
- package/payload/platform/services/telegram-channel/dist/notification.js +119 -0
- package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -0
- package/payload/platform/services/telegram-channel/dist/server.d.ts +40 -0
- package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -0
- package/payload/platform/services/telegram-channel/dist/server.js +291 -0
- package/payload/platform/services/telegram-channel/dist/server.js.map +1 -0
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +28 -0
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -0
- package/payload/platform/services/telegram-channel/dist/turn-follow.js +188 -0
- package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -0
- package/payload/platform/services/webchat-channel/dist/notification.d.ts +5 -0
- package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/notification.js +6 -1
- package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +6 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +8 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/templates/agents/passive/IDENTITY.md +21 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-CBlE6FB4.js → AdminLoginScreens-B1PkOF66.js} +1 -1
- package/payload/server/public/assets/AdminShell-D8wNArGQ.js +2 -0
- package/payload/server/public/assets/{Checkbox-B0pbYJDQ.js → Checkbox-Bm9Er726.js} +1 -1
- package/payload/server/public/assets/{Transcript-CYaAPBFX.js → Transcript-CBIyQ8T5.js} +1 -1
- package/payload/server/public/assets/{admin-CVh9HO-f.js → admin-CWM9G_yA.js} +1 -1
- package/payload/server/public/assets/{browser-C4f_D4Ih.js → browser-D8QhNffV.js} +1 -1
- package/payload/server/public/assets/{calendar-WUxQ4fVo.js → calendar-qob5K4t_.js} +1 -1
- package/payload/server/public/assets/chat-BNrxVUF6.js +1 -0
- package/payload/server/public/assets/chevron-left-BQThFVrd.js +1 -0
- package/payload/server/public/assets/data-C-EqNbRi.js +1 -0
- package/payload/server/public/assets/{graph-BNXE6mKP.js → graph-DZ2HMpIB.js} +1 -1
- package/payload/server/public/assets/{graph-labels-Csnovm_c.js → graph-labels-BFG4bjXQ.js} +1 -1
- package/payload/server/public/assets/{maximize-2-WRUJsg5n.js → maximize-2-DkZ40lSl.js} +1 -1
- package/payload/server/public/assets/{operator-CXtygMh6.js → operator-FrXrSsBj.js} +1 -1
- package/payload/server/public/assets/page-CHWMI9Tm.js +32 -0
- package/payload/server/public/assets/{page-IQDLdebG.js → page-CXKV01gT.js} +1 -1
- package/payload/server/public/assets/{public-DSeYu61E.js → public-2p1pJ4WF.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BayMWwfc.js → rotate-ccw-D3o_bMBA.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-8dLvF3Ux.js +9 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BUvlETHN.css +1 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +5 -5
- package/payload/server/public/chat.html +9 -9
- package/payload/server/public/data.html +8 -8
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +11 -11
- package/payload/server/public/operator.html +11 -11
- package/payload/server/public/public.html +9 -9
- package/payload/server/server.js +245 -40
- package/payload/server/public/assets/AdminShell-d1WQ3lLu.js +0 -2
- package/payload/server/public/assets/chat-NJ2HXhOf.js +0 -1
- package/payload/server/public/assets/chevron-left-Da-iZGi_.js +0 -1
- package/payload/server/public/assets/data-ZUdT44Ba.js +0 -1
- package/payload/server/public/assets/page-DjpQp_ir.js +0 -32
- package/payload/server/public/assets/useSubAccountSwitcher-C9aLg8yI.js +0 -9
- package/payload/server/public/assets/useSubAccountSwitcher-CRZfyJXR.css +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Standing check: no two brands may fight for the same host resource when
|
|
2
|
+
// co-resident on one device. Two namespaces are at stake:
|
|
3
|
+
//
|
|
4
|
+
// 1. Host TCP ports. neo4jPort / ttydPort / rfbPort / websockifyPort /
|
|
5
|
+
// cdpPort / claudeSessionManagerPort all bind TCP on the same host, so they
|
|
6
|
+
// must be globally unique across every (brand, field) pair — not merely
|
|
7
|
+
// unique within each field. A future brand whose ttydPort equals another
|
|
8
|
+
// brand's neo4jPort would EADDRINUSE at runtime with no build event, even
|
|
9
|
+
// though each field is individually unique.
|
|
10
|
+
// 2. X display numbers. vncDisplay is an Xvnc display index (:NN and its
|
|
11
|
+
// /tmp/.X<NN>-lock), a namespace distinct from TCP ports, so it must be
|
|
12
|
+
// unique across brands on its own.
|
|
13
|
+
//
|
|
14
|
+
// This reconciles all brands/*/brand.json on every CI run. Path resolution
|
|
15
|
+
// mirrors known-brand-hostnames.test.ts.
|
|
16
|
+
import test from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { dirname, resolve, join } from "node:path";
|
|
21
|
+
// dist/__tests__/brand-port-uniqueness.test.js → maxy-code/brands
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const BRANDS_DIR = resolve(here, "../../../../brands");
|
|
24
|
+
// Fields that bind a host TCP port (one shared namespace across all fields).
|
|
25
|
+
const TCP_PORT_FIELDS = [
|
|
26
|
+
"neo4jPort",
|
|
27
|
+
"ttydPort",
|
|
28
|
+
"rfbPort",
|
|
29
|
+
"websockifyPort",
|
|
30
|
+
"cdpPort",
|
|
31
|
+
"claudeSessionManagerPort",
|
|
32
|
+
];
|
|
33
|
+
// X display index — its own namespace, unique per brand.
|
|
34
|
+
const DISPLAY_FIELD = "vncDisplay";
|
|
35
|
+
const ALL_FIELDS = [...TCP_PORT_FIELDS, DISPLAY_FIELD];
|
|
36
|
+
function brandManifests() {
|
|
37
|
+
return readdirSync(BRANDS_DIR, { withFileTypes: true })
|
|
38
|
+
.filter((e) => e.isDirectory())
|
|
39
|
+
.map((e) => ({ name: e.name, path: join(BRANDS_DIR, e.name, "brand.json") }))
|
|
40
|
+
.filter((b) => existsSync(b.path))
|
|
41
|
+
.map((b) => ({ name: b.name, manifest: JSON.parse(readFileSync(b.path, "utf-8")) }));
|
|
42
|
+
}
|
|
43
|
+
test("brands/ is discoverable and every brand declares all port fields", () => {
|
|
44
|
+
const brands = brandManifests();
|
|
45
|
+
assert.ok(brands.length > 0, `no brand.json found under ${BRANDS_DIR}`);
|
|
46
|
+
for (const { name, manifest } of brands) {
|
|
47
|
+
for (const field of ALL_FIELDS) {
|
|
48
|
+
assert.equal(typeof manifest[field], "number", `brands/${name}/brand.json is missing numeric ${field}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
test("host TCP ports are globally unique across every (brand, field)", () => {
|
|
53
|
+
const brands = brandManifests();
|
|
54
|
+
const seen = new Map(); // port -> "brand.field" that claimed it
|
|
55
|
+
for (const { name, manifest } of brands) {
|
|
56
|
+
for (const field of TCP_PORT_FIELDS) {
|
|
57
|
+
const value = manifest[field];
|
|
58
|
+
const owner = `${name}.${field}`;
|
|
59
|
+
const prior = seen.get(value);
|
|
60
|
+
assert.equal(prior, undefined, `TCP port collision: ${prior} and ${owner} both bind ${value} — they would EADDRINUSE when co-resident`);
|
|
61
|
+
seen.set(value, owner);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
test("vncDisplay is unique across brands", () => {
|
|
66
|
+
const brands = brandManifests();
|
|
67
|
+
const seen = new Map();
|
|
68
|
+
for (const { name, manifest } of brands) {
|
|
69
|
+
const value = manifest[DISPLAY_FIELD];
|
|
70
|
+
const prior = seen.get(value);
|
|
71
|
+
assert.equal(prior, undefined, `vncDisplay collision: brands "${prior}" and "${name}" both use :${value}`);
|
|
72
|
+
seen.set(value, name);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// acceptance gate for OUTLOOK_CLIENT_ID stamping in the claude-session-manager
|
|
2
|
+
// systemd unit (Task 1473).
|
|
3
|
+
//
|
|
4
|
+
// The outlook MCP reads process.env.OUTLOOK_CLIENT_ID; the session manager is
|
|
5
|
+
// the process that spawns claude -> MCP, so the client ID must land on this
|
|
6
|
+
// unit's Environment= block. Two invariants:
|
|
7
|
+
// (a) a non-empty brand outlookClientId emits BOTH
|
|
8
|
+
// `Environment=OUTLOOK_CLIENT_ID=<id>` and
|
|
9
|
+
// `Environment=OUTLOOK_TENANT_ID=common` (multi-tenant /common app).
|
|
10
|
+
// (b) an empty / absent brand value emits NEITHER line — the plugin stays
|
|
11
|
+
// inert on that brand exactly as before (no regression), and the MCP's
|
|
12
|
+
// module-init no-throw path keeps the registry intact.
|
|
13
|
+
import test from "node:test";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
import { buildClaudeSessionManagerUnitFile } from "../port-resolution.js";
|
|
16
|
+
const ACCOUNT_ID = "12345678-9abc-def0-1234-56789abcdef0";
|
|
17
|
+
const CLIENT_ID = "0fa68fe2-8bec-4007-a0c7-776636fbcf97";
|
|
18
|
+
function build(outlookClientId) {
|
|
19
|
+
return buildClaudeSessionManagerUnitFile({
|
|
20
|
+
productName: "SiteDesk",
|
|
21
|
+
brandHostname: "sitedesk-code",
|
|
22
|
+
installDir: "/home/me/sitedesk-code",
|
|
23
|
+
persistDir: "/home/me/.sitedesk-code",
|
|
24
|
+
claudeSessionManagerPort: 19402,
|
|
25
|
+
brandPort: 19300,
|
|
26
|
+
maxyUiInternalPort: 19301,
|
|
27
|
+
accountId: ACCOUNT_ID,
|
|
28
|
+
cdpPort: 9224,
|
|
29
|
+
outlookClientId,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
test("stamps OUTLOOK_CLIENT_ID + OUTLOOK_TENANT_ID for a non-empty brand value", () => {
|
|
33
|
+
const unit = build(CLIENT_ID);
|
|
34
|
+
assert.match(unit, new RegExp(`^Environment=OUTLOOK_CLIENT_ID=${CLIENT_ID}$`, "m"), "OUTLOOK_CLIENT_ID line missing or malformed");
|
|
35
|
+
assert.match(unit, /^Environment=OUTLOOK_TENANT_ID=common$/m, "OUTLOOK_TENANT_ID=common line missing");
|
|
36
|
+
});
|
|
37
|
+
test("stamps NEITHER line when the brand value is empty", () => {
|
|
38
|
+
const unit = build("");
|
|
39
|
+
assert.doesNotMatch(unit, /OUTLOOK_CLIENT_ID/, "unexpected OUTLOOK_CLIENT_ID on empty brand");
|
|
40
|
+
assert.doesNotMatch(unit, /OUTLOOK_TENANT_ID/, "unexpected OUTLOOK_TENANT_ID on empty brand");
|
|
41
|
+
});
|
|
42
|
+
test("stamps NEITHER line when the brand value is absent", () => {
|
|
43
|
+
const unit = build(undefined);
|
|
44
|
+
assert.doesNotMatch(unit, /OUTLOOK_CLIENT_ID/, "unexpected OUTLOOK_CLIENT_ID on absent brand");
|
|
45
|
+
assert.doesNotMatch(unit, /OUTLOOK_TENANT_ID/, "unexpected OUTLOOK_TENANT_ID on absent brand");
|
|
46
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -3579,6 +3579,32 @@ function installServiceDarwin() {
|
|
|
3579
3579
|
else
|
|
3580
3580
|
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `NODE_BIN=${nodeBin}\n`;
|
|
3581
3581
|
}
|
|
3582
|
+
// Task 1473 — darwin has no systemd EnvironmentFile primitive, so the vendor
|
|
3583
|
+
// Outlook app env is mirrored into .env (the wrapper sources it before exec).
|
|
3584
|
+
// Symmetric with the Linux unit, which is rebuilt wholesale each run: a
|
|
3585
|
+
// non-empty brand outlookClientId upserts BOTH vars together; empty / absent
|
|
3586
|
+
// STRIPS both lines. Stripping matters because .env persists across installs —
|
|
3587
|
+
// without it, a brand whose outlookClientId is later blanked (vendor-app
|
|
3588
|
+
// rotation/revocation) would keep sourcing a stale, now-invalid client ID and
|
|
3589
|
+
// keep reporting clientIdConfigured=true against its current empty intent.
|
|
3590
|
+
if (BRAND.outlookClientId) {
|
|
3591
|
+
for (const [key, value] of [
|
|
3592
|
+
["OUTLOOK_CLIENT_ID", BRAND.outlookClientId],
|
|
3593
|
+
["OUTLOOK_TENANT_ID", "common"],
|
|
3594
|
+
]) {
|
|
3595
|
+
const re = new RegExp(`^${key}=.*$`, "m");
|
|
3596
|
+
if (re.test(envContent))
|
|
3597
|
+
envContent = envContent.replace(re, `${key}=${value}`);
|
|
3598
|
+
else
|
|
3599
|
+
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `${key}=${value}\n`;
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
else {
|
|
3603
|
+
for (const key of ["OUTLOOK_CLIENT_ID", "OUTLOOK_TENANT_ID"]) {
|
|
3604
|
+
envContent = envContent.replace(new RegExp(`^${key}=.*\\n?`, "m"), "");
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
logFile(` [installer] op=outlook-client-id brand=${BRAND.hostname} stamped=${BRAND.outlookClientId ? "true" : "false"}`);
|
|
3582
3608
|
writeFileSync(envPath, envContent);
|
|
3583
3609
|
logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}, MAXY_UI_INTERNAL_PORT=${PORT} (darwin-collapsed), CLAUDE_SESSION_MANAGER_PORT=${BRAND.claudeSessionManagerPort}, HOSTNAME=0.0.0.0, ACCOUNT_ID=${installAccountId.slice(0, 8)}…, CLAUDE_CONFIG_DIR=${persistDir}/.claude, CLAUDE_BIN=${canonicalClaude ?? "unresolved(PATH)"}, NODE_BIN=${nodeBin}, PATH=${darwinServiceEnvPath}`);
|
|
3584
3610
|
}
|
|
@@ -4161,9 +4187,14 @@ function installService() {
|
|
|
4161
4187
|
maxyUiInternalPort: MAXY_UI_INTERNAL_PORT,
|
|
4162
4188
|
accountId: installAccountId,
|
|
4163
4189
|
cdpPort: CDP_PORT_BRAND,
|
|
4190
|
+
outlookClientId: BRAND.outlookClientId,
|
|
4164
4191
|
});
|
|
4165
4192
|
writeFileSync(join(serviceDir, claudeSessionManagerUnitName), claudeSessionManagerUnit);
|
|
4166
4193
|
logFile(` ${claudeSessionManagerUnitName}: CLAUDE_SESSION_MANAGER_PORT=${CLAUDE_SESSION_MANAGER_PORT_BRAND} MAXY_UI_INTERNAL_PORT=${MAXY_UI_INTERNAL_PORT}`);
|
|
4194
|
+
// Task 1473 — standing install-time signal reconciling brand.json intent
|
|
4195
|
+
// (a non-empty outlookClientId) against what the unit shipped. `stamped=false`
|
|
4196
|
+
// while the brand has a value is the mis-stamp signature.
|
|
4197
|
+
logFile(` [installer] op=outlook-client-id brand=${BRAND.hostname} stamped=${BRAND.outlookClientId ? "true" : "false"}`);
|
|
4167
4198
|
// Task 250 — write the brand-agnostic `claude-ptys.slice` unit. Every
|
|
4168
4199
|
// brand's session manager drops its per-spawn scope units under this
|
|
4169
4200
|
// slice. Writing it once per install is idempotent (the content is
|
package/dist/known-brands.js
CHANGED
package/dist/port-resolution.js
CHANGED
|
@@ -128,6 +128,13 @@ export function buildClaudeSessionManagerUnitFile(o) {
|
|
|
128
128
|
if (!o.accountId) {
|
|
129
129
|
throw new Error("buildClaudeSessionManagerUnitFile: accountId is required — caller must resolve the on-disk account UUID before stamping the systemd unit.");
|
|
130
130
|
}
|
|
131
|
+
// Task 1473 — a non-empty brand outlookClientId adds the vendor Outlook app
|
|
132
|
+
// env. Both lines land together so OUTLOOK_TENANT_ID (multi-tenant /common)
|
|
133
|
+
// never trails a bare client ID. Empty / absent brands stamp neither line —
|
|
134
|
+
// the plugin stays inert, no regression.
|
|
135
|
+
const outlookEnv = o.outlookClientId
|
|
136
|
+
? `\nEnvironment=OUTLOOK_CLIENT_ID=${o.outlookClientId}\nEnvironment=OUTLOOK_TENANT_ID=common`
|
|
137
|
+
: "";
|
|
131
138
|
return `[Unit]
|
|
132
139
|
Description=${o.productName} Claude Session Manager
|
|
133
140
|
After=network.target
|
|
@@ -149,7 +156,7 @@ Environment=PLATFORM_ROOT=${o.installDir}/platform
|
|
|
149
156
|
Environment=MAXY_BRAND_PORT=${o.brandPort}
|
|
150
157
|
Environment=MAXY_UI_INTERNAL_PORT=${o.maxyUiInternalPort}
|
|
151
158
|
Environment=PLATFORM_PORT=${o.maxyUiInternalPort}
|
|
152
|
-
Environment=CDP_PORT=${o.cdpPort}
|
|
159
|
+
Environment=CDP_PORT=${o.cdpPort}${outlookEnv}
|
|
153
160
|
EnvironmentFile=-${o.persistDir}/.env
|
|
154
161
|
StandardOutput=append:${o.persistDir}/logs/server.log
|
|
155
162
|
StandardError=append:${o.persistDir}/logs/server.log
|
package/package.json
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
"websockifyPort": 6080,
|
|
9
9
|
"cdpPort": 9222
|
|
10
10
|
},
|
|
11
|
+
{
|
|
12
|
+
"hostname": "property-administrators",
|
|
13
|
+
"configDir": ".property-administrators",
|
|
14
|
+
"vncDisplay": 102,
|
|
15
|
+
"rfbPort": 5903,
|
|
16
|
+
"websockifyPort": 6083,
|
|
17
|
+
"cdpPort": 9225
|
|
18
|
+
},
|
|
11
19
|
{
|
|
12
20
|
"hostname": "realagent-code",
|
|
13
21
|
"configDir": ".realagent-code",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"websockifyPort": 6080,
|
|
15
15
|
"cdpPort": 9222,
|
|
16
16
|
"claudeSessionManagerPort": 19400,
|
|
17
|
+
"outlookClientId": "",
|
|
17
18
|
"commercialMode": false,
|
|
18
19
|
"vertical": "schema-knowledge-work",
|
|
19
20
|
"shipsPremiumBundles": ["venture-studio", "writer-craft", "teaching", "management-consulting"],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-provisioned.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/is-provisioned.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
7
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const index_js_1 = require("../index.js");
|
|
12
|
+
const BOOT_ID = "12345678-9abc-def0-1234-56789abcdef0";
|
|
13
|
+
const LATER_ID = "99999999-aaaa-bbbb-cccc-dddddddddddd";
|
|
14
|
+
/** One valid account dir (BOOT_ID) that existed at "boot". */
|
|
15
|
+
function makeFixture() {
|
|
16
|
+
const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "acct-prov-"));
|
|
17
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, BOOT_ID));
|
|
18
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, BOOT_ID, "account.json"), JSON.stringify({ id: BOOT_ID }));
|
|
19
|
+
return root;
|
|
20
|
+
}
|
|
21
|
+
/** Provision a second account dir on disk AFTER boot (simulates account_create). */
|
|
22
|
+
function provisionLater(root) {
|
|
23
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, LATER_ID));
|
|
24
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, LATER_ID, "account.json"), JSON.stringify({ id: LATER_ID }));
|
|
25
|
+
}
|
|
26
|
+
(0, node_test_1.default)("isProvisionedAccount: a created-after-boot id resolves via re-scan on miss", () => {
|
|
27
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
28
|
+
const root = makeFixture();
|
|
29
|
+
try {
|
|
30
|
+
// Warm the cache at "boot" — only BOOT_ID is present.
|
|
31
|
+
strict_1.default.deepEqual((0, index_js_1.enumerateValidAccountIds)(root), [BOOT_ID]);
|
|
32
|
+
// A sub-account is provisioned after the snapshot.
|
|
33
|
+
provisionLater(root);
|
|
34
|
+
// Miss against the cached set must re-scan disk and find it — no restart.
|
|
35
|
+
strict_1.default.equal((0, index_js_1.isProvisionedAccount)(root, LATER_ID), true);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
(0, node_fs_1.rmSync)(root, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
(0, node_test_1.default)("isProvisionedAccount: a truly-absent id still returns false after re-scan", () => {
|
|
42
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
43
|
+
const root = makeFixture();
|
|
44
|
+
try {
|
|
45
|
+
(0, index_js_1.enumerateValidAccountIds)(root); // warm
|
|
46
|
+
const bogus = "00000000-0000-0000-0000-000000000000";
|
|
47
|
+
strict_1.default.equal((0, index_js_1.isProvisionedAccount)(root, bogus), false);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
(0, node_fs_1.rmSync)(root, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
(0, node_test_1.default)("isProvisionedAccount: a warmed hit returns true without re-scanning", () => {
|
|
54
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
55
|
+
const root = makeFixture();
|
|
56
|
+
try {
|
|
57
|
+
(0, index_js_1.enumerateValidAccountIds)(root); // warm = [BOOT_ID]
|
|
58
|
+
// Provision a second dir AFTER warming.
|
|
59
|
+
provisionLater(root);
|
|
60
|
+
// HIT: BOOT_ID is already in the cached set → true, and the cache must NOT
|
|
61
|
+
// be refreshed (no readdirSync). Proof: the cached set still hides LATER_ID.
|
|
62
|
+
strict_1.default.equal((0, index_js_1.isProvisionedAccount)(root, BOOT_ID), true);
|
|
63
|
+
strict_1.default.deepEqual((0, index_js_1.enumerateValidAccountIds)(root), [BOOT_ID], "a hit must not refresh the cache — LATER_ID must stay hidden");
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
(0, node_fs_1.rmSync)(root, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
(0, node_test_1.default)("isProvisionedAccount: cold cache scans disk and answers", () => {
|
|
70
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
71
|
+
const root = makeFixture();
|
|
72
|
+
try {
|
|
73
|
+
// No warm call — cache is cold. A miss (cold) must scan and answer both ways.
|
|
74
|
+
strict_1.default.equal((0, index_js_1.isProvisionedAccount)(root, BOOT_ID), true);
|
|
75
|
+
strict_1.default.equal((0, index_js_1.isProvisionedAccount)(root, LATER_ID), false);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
(0, node_fs_1.rmSync)(root, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=is-provisioned.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-provisioned.test.js","sourceRoot":"","sources":["../../src/__tests__/is-provisioned.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,23 @@
|
|
|
6
6
|
* be hiding).
|
|
7
7
|
*/
|
|
8
8
|
export declare function enumerateValidAccountIds(accountsDir: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Membership test with re-scan-on-miss: is `id` a currently-provisioned account
|
|
11
|
+
* under `accountsDir`?
|
|
12
|
+
*
|
|
13
|
+
* Hit path — `id` is in the cached set — returns true with no I/O (the common
|
|
14
|
+
* case; the cache still serves it). Miss path — `id` is absent from the cached
|
|
15
|
+
* set, or the cache is cold — drops the stale entry and re-scans `accountsDir`
|
|
16
|
+
* once (refreshing the cache) before answering.
|
|
17
|
+
*
|
|
18
|
+
* This closes the created-after-boot gap (Task 1471): a sub-account provisioned
|
|
19
|
+
* by the runtime `account_create` lifecycle tool after a consumer process booted
|
|
20
|
+
* is invisible to that process's boot-time cached set. Without the re-scan, the
|
|
21
|
+
* cross-account resolver rejects the id as not-provisioned even though its dir
|
|
22
|
+
* and `account.json` exist on disk. A single `readdirSync` on the miss finds it;
|
|
23
|
+
* a genuinely-absent id pays the same one scan and correctly returns false.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isProvisionedAccount(accountsDir: string, id: string): boolean;
|
|
9
26
|
/**
|
|
10
27
|
* Returns the single `role:"house"` account UUID under `accountsDir`.
|
|
11
28
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwCA;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAgCtE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAO7E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAsBjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAqBvE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAS9C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,mCAAmC,GAC3C,SAAS,GACT,oBAAoB,GACpB,UAAU,CAAC;AAEf,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAC9C;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,mCAAmC,CAAC;IAC5C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEN,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,MAAM,EAAE,GAChB,sBAAsB,CAWxB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.enumerateValidAccountIds = enumerateValidAccountIds;
|
|
4
|
+
exports.isProvisionedAccount = isProvisionedAccount;
|
|
4
5
|
exports.resolveHouseAccountId = resolveHouseAccountId;
|
|
5
6
|
exports.resolveHouseOrSoleAccountId = resolveHouseOrSoleAccountId;
|
|
6
7
|
exports.getAccountsDirFromEnv = getAccountsDirFromEnv;
|
|
@@ -16,16 +17,27 @@ exports.validateAccountIdEnv = validateAccountIdEnv;
|
|
|
16
17
|
* (`WHERE n.accountId = $accountId`) silently hides leaks from every UI;
|
|
17
18
|
* an unenforced writer can leak indefinitely with zero downstream symptom.
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* Consumers of the enumerated set:
|
|
21
|
+
* - The cross-account resolver's injected validity check (the `memory` and
|
|
22
|
+
* `filesystem` MCP plugins) — the check that gates a `targetAccountId`.
|
|
23
|
+
* These call `isProvisionedAccount` (re-scan-on-miss), not the cached list
|
|
24
|
+
* directly, so a sub-account created at runtime is not masked by the boot
|
|
25
|
+
* snapshot (Task 1471).
|
|
26
|
+
* - `resolvePlatformAccountId` (platform/ui/app/lib/whatsapp/platform-account-id.ts)
|
|
27
|
+
* — WhatsApp writer-side helper; loud-throws on zero or multi accounts.
|
|
28
|
+
* - Boot-time env-vs-disk validation (`validateAccountIdEnv`).
|
|
25
29
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
30
|
+
* NOTE: `writeNodeWithEdges` (platform/lib/graph-write/) does NOT enumerate.
|
|
31
|
+
* Its account floor compares `props.accountId` against `process.env.ACCOUNT_ID`
|
|
32
|
+
* (the spawning process's boot-validated id), deliberately staying out of the
|
|
33
|
+
* `node:fs` import chain so the ESM payload bundle emits no `__require("fs")`
|
|
34
|
+
* shim. So the write gate does not share the cache's staleness.
|
|
35
|
+
*
|
|
36
|
+
* Per-process cache keyed on `accountsDir`. The install-time invariant (accounts
|
|
37
|
+
* created only at install) no longer holds: the runtime `account_create`
|
|
38
|
+
* lifecycle tool provisions sub-accounts during a live session. `enumerate…`
|
|
39
|
+
* keeps its cached-list semantics for the hit path; `isProvisionedAccount`
|
|
40
|
+
* re-scans on a miss so a created-after-boot account resolves without restart.
|
|
29
41
|
*/
|
|
30
42
|
const node_fs_1 = require("node:fs");
|
|
31
43
|
const node_path_1 = require("node:path");
|
|
@@ -73,6 +85,31 @@ function enumerateValidAccountIds(accountsDir) {
|
|
|
73
85
|
cache.set(accountsDir, valid);
|
|
74
86
|
return valid;
|
|
75
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Membership test with re-scan-on-miss: is `id` a currently-provisioned account
|
|
90
|
+
* under `accountsDir`?
|
|
91
|
+
*
|
|
92
|
+
* Hit path — `id` is in the cached set — returns true with no I/O (the common
|
|
93
|
+
* case; the cache still serves it). Miss path — `id` is absent from the cached
|
|
94
|
+
* set, or the cache is cold — drops the stale entry and re-scans `accountsDir`
|
|
95
|
+
* once (refreshing the cache) before answering.
|
|
96
|
+
*
|
|
97
|
+
* This closes the created-after-boot gap (Task 1471): a sub-account provisioned
|
|
98
|
+
* by the runtime `account_create` lifecycle tool after a consumer process booted
|
|
99
|
+
* is invisible to that process's boot-time cached set. Without the re-scan, the
|
|
100
|
+
* cross-account resolver rejects the id as not-provisioned even though its dir
|
|
101
|
+
* and `account.json` exist on disk. A single `readdirSync` on the miss finds it;
|
|
102
|
+
* a genuinely-absent id pays the same one scan and correctly returns false.
|
|
103
|
+
*/
|
|
104
|
+
function isProvisionedAccount(accountsDir, id) {
|
|
105
|
+
const cached = cache.get(accountsDir);
|
|
106
|
+
if (cached?.includes(id))
|
|
107
|
+
return true;
|
|
108
|
+
// Miss: `id` is not in the cached set (or the cache is cold). Drop the stale
|
|
109
|
+
// snapshot and re-scan disk once so a real, on-disk account is not masked.
|
|
110
|
+
cache.delete(accountsDir);
|
|
111
|
+
return enumerateValidAccountIds(accountsDir).includes(id);
|
|
112
|
+
}
|
|
76
113
|
/**
|
|
77
114
|
* Returns the single `role:"house"` account UUID under `accountsDir`.
|
|
78
115
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
enumerateValidAccountIds,
|
|
8
|
+
isProvisionedAccount,
|
|
9
|
+
_resetEnumerationCache,
|
|
10
|
+
} from "../index.js";
|
|
11
|
+
|
|
12
|
+
const BOOT_ID = "12345678-9abc-def0-1234-56789abcdef0";
|
|
13
|
+
const LATER_ID = "99999999-aaaa-bbbb-cccc-dddddddddddd";
|
|
14
|
+
|
|
15
|
+
/** One valid account dir (BOOT_ID) that existed at "boot". */
|
|
16
|
+
function makeFixture(): string {
|
|
17
|
+
const root = mkdtempSync(join(tmpdir(), "acct-prov-"));
|
|
18
|
+
mkdirSync(join(root, BOOT_ID));
|
|
19
|
+
writeFileSync(join(root, BOOT_ID, "account.json"), JSON.stringify({ id: BOOT_ID }));
|
|
20
|
+
return root;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Provision a second account dir on disk AFTER boot (simulates account_create). */
|
|
24
|
+
function provisionLater(root: string): void {
|
|
25
|
+
mkdirSync(join(root, LATER_ID));
|
|
26
|
+
writeFileSync(join(root, LATER_ID, "account.json"), JSON.stringify({ id: LATER_ID }));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("isProvisionedAccount: a created-after-boot id resolves via re-scan on miss", () => {
|
|
30
|
+
_resetEnumerationCache();
|
|
31
|
+
const root = makeFixture();
|
|
32
|
+
try {
|
|
33
|
+
// Warm the cache at "boot" — only BOOT_ID is present.
|
|
34
|
+
assert.deepEqual(enumerateValidAccountIds(root), [BOOT_ID]);
|
|
35
|
+
// A sub-account is provisioned after the snapshot.
|
|
36
|
+
provisionLater(root);
|
|
37
|
+
// Miss against the cached set must re-scan disk and find it — no restart.
|
|
38
|
+
assert.equal(isProvisionedAccount(root, LATER_ID), true);
|
|
39
|
+
} finally {
|
|
40
|
+
rmSync(root, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("isProvisionedAccount: a truly-absent id still returns false after re-scan", () => {
|
|
45
|
+
_resetEnumerationCache();
|
|
46
|
+
const root = makeFixture();
|
|
47
|
+
try {
|
|
48
|
+
enumerateValidAccountIds(root); // warm
|
|
49
|
+
const bogus = "00000000-0000-0000-0000-000000000000";
|
|
50
|
+
assert.equal(isProvisionedAccount(root, bogus), false);
|
|
51
|
+
} finally {
|
|
52
|
+
rmSync(root, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("isProvisionedAccount: a warmed hit returns true without re-scanning", () => {
|
|
57
|
+
_resetEnumerationCache();
|
|
58
|
+
const root = makeFixture();
|
|
59
|
+
try {
|
|
60
|
+
enumerateValidAccountIds(root); // warm = [BOOT_ID]
|
|
61
|
+
// Provision a second dir AFTER warming.
|
|
62
|
+
provisionLater(root);
|
|
63
|
+
// HIT: BOOT_ID is already in the cached set → true, and the cache must NOT
|
|
64
|
+
// be refreshed (no readdirSync). Proof: the cached set still hides LATER_ID.
|
|
65
|
+
assert.equal(isProvisionedAccount(root, BOOT_ID), true);
|
|
66
|
+
assert.deepEqual(
|
|
67
|
+
enumerateValidAccountIds(root),
|
|
68
|
+
[BOOT_ID],
|
|
69
|
+
"a hit must not refresh the cache — LATER_ID must stay hidden",
|
|
70
|
+
);
|
|
71
|
+
} finally {
|
|
72
|
+
rmSync(root, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("isProvisionedAccount: cold cache scans disk and answers", () => {
|
|
77
|
+
_resetEnumerationCache();
|
|
78
|
+
const root = makeFixture();
|
|
79
|
+
try {
|
|
80
|
+
// No warm call — cache is cold. A miss (cold) must scan and answer both ways.
|
|
81
|
+
assert.equal(isProvisionedAccount(root, BOOT_ID), true);
|
|
82
|
+
assert.equal(isProvisionedAccount(root, LATER_ID), false);
|
|
83
|
+
} finally {
|
|
84
|
+
rmSync(root, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
@@ -8,16 +8,27 @@
|
|
|
8
8
|
* (`WHERE n.accountId = $accountId`) silently hides leaks from every UI;
|
|
9
9
|
* an unenforced writer can leak indefinitely with zero downstream symptom.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* Consumers of the enumerated set:
|
|
12
|
+
* - The cross-account resolver's injected validity check (the `memory` and
|
|
13
|
+
* `filesystem` MCP plugins) — the check that gates a `targetAccountId`.
|
|
14
|
+
* These call `isProvisionedAccount` (re-scan-on-miss), not the cached list
|
|
15
|
+
* directly, so a sub-account created at runtime is not masked by the boot
|
|
16
|
+
* snapshot (Task 1471).
|
|
17
|
+
* - `resolvePlatformAccountId` (platform/ui/app/lib/whatsapp/platform-account-id.ts)
|
|
18
|
+
* — WhatsApp writer-side helper; loud-throws on zero or multi accounts.
|
|
19
|
+
* - Boot-time env-vs-disk validation (`validateAccountIdEnv`).
|
|
17
20
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* NOTE: `writeNodeWithEdges` (platform/lib/graph-write/) does NOT enumerate.
|
|
22
|
+
* Its account floor compares `props.accountId` against `process.env.ACCOUNT_ID`
|
|
23
|
+
* (the spawning process's boot-validated id), deliberately staying out of the
|
|
24
|
+
* `node:fs` import chain so the ESM payload bundle emits no `__require("fs")`
|
|
25
|
+
* shim. So the write gate does not share the cache's staleness.
|
|
26
|
+
*
|
|
27
|
+
* Per-process cache keyed on `accountsDir`. The install-time invariant (accounts
|
|
28
|
+
* created only at install) no longer holds: the runtime `account_create`
|
|
29
|
+
* lifecycle tool provisions sub-accounts during a live session. `enumerate…`
|
|
30
|
+
* keeps its cached-list semantics for the hit path; `isProvisionedAccount`
|
|
31
|
+
* re-scans on a miss so a created-after-boot account resolves without restart.
|
|
21
32
|
*/
|
|
22
33
|
import { readdirSync, readFileSync } from "node:fs";
|
|
23
34
|
import { resolve } from "node:path";
|
|
@@ -68,6 +79,31 @@ export function enumerateValidAccountIds(accountsDir: string): string[] {
|
|
|
68
79
|
return valid;
|
|
69
80
|
}
|
|
70
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Membership test with re-scan-on-miss: is `id` a currently-provisioned account
|
|
84
|
+
* under `accountsDir`?
|
|
85
|
+
*
|
|
86
|
+
* Hit path — `id` is in the cached set — returns true with no I/O (the common
|
|
87
|
+
* case; the cache still serves it). Miss path — `id` is absent from the cached
|
|
88
|
+
* set, or the cache is cold — drops the stale entry and re-scans `accountsDir`
|
|
89
|
+
* once (refreshing the cache) before answering.
|
|
90
|
+
*
|
|
91
|
+
* This closes the created-after-boot gap (Task 1471): a sub-account provisioned
|
|
92
|
+
* by the runtime `account_create` lifecycle tool after a consumer process booted
|
|
93
|
+
* is invisible to that process's boot-time cached set. Without the re-scan, the
|
|
94
|
+
* cross-account resolver rejects the id as not-provisioned even though its dir
|
|
95
|
+
* and `account.json` exist on disk. A single `readdirSync` on the miss finds it;
|
|
96
|
+
* a genuinely-absent id pays the same one scan and correctly returns false.
|
|
97
|
+
*/
|
|
98
|
+
export function isProvisionedAccount(accountsDir: string, id: string): boolean {
|
|
99
|
+
const cached = cache.get(accountsDir);
|
|
100
|
+
if (cached?.includes(id)) return true;
|
|
101
|
+
// Miss: `id` is not in the cached set (or the cache is cold). Drop the stale
|
|
102
|
+
// snapshot and re-scan disk once so a real, on-disk account is not masked.
|
|
103
|
+
cache.delete(accountsDir);
|
|
104
|
+
return enumerateValidAccountIds(accountsDir).includes(id);
|
|
105
|
+
}
|
|
106
|
+
|
|
71
107
|
/**
|
|
72
108
|
* Returns the single `role:"house"` account UUID under `accountsDir`.
|
|
73
109
|
*
|