@rubytech/create-sitedesk-code 0.1.521 → 0.1.523
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/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +28 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/caption-coverage.test.ts +7 -0
- package/payload/platform/lib/graph-style/src/index.ts +24 -0
- package/payload/platform/lib/graph-write/dist/__tests__/merge-on.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/merge-on.test.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts +16 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +83 -6
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/index.ts +117 -10
- package/payload/platform/lib/mcp-lifeline/package-lock.json +12 -0
- package/payload/platform/lib/routine-templates/dist/index.d.ts +151 -0
- package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/routine-templates/dist/index.js +207 -0
- package/payload/platform/lib/routine-templates/dist/index.js.map +1 -0
- package/payload/platform/lib/routine-templates/dist/roster.d.ts +57 -0
- package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -0
- package/payload/platform/lib/routine-templates/dist/roster.js +182 -0
- package/payload/platform/lib/routine-templates/dist/roster.js.map +1 -0
- package/payload/platform/lib/routine-templates/dist/seed-cli.d.ts +2 -0
- package/payload/platform/lib/routine-templates/dist/seed-cli.d.ts.map +1 -0
- package/payload/platform/lib/routine-templates/dist/seed-cli.js +64 -0
- package/payload/platform/lib/routine-templates/dist/seed-cli.js.map +1 -0
- package/payload/platform/lib/routine-templates/package.json +6 -0
- package/payload/platform/lib/routine-templates/src/__tests__/roster.test.ts +170 -0
- package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +298 -0
- package/payload/platform/lib/routine-templates/src/index.ts +299 -0
- package/payload/platform/lib/routine-templates/src/roster.ts +206 -0
- package/payload/platform/lib/routine-templates/src/seed-cli.ts +65 -0
- package/payload/platform/lib/routine-templates/tsconfig.json +9 -0
- package/payload/platform/lib/routine-templates/vitest.config.ts +9 -0
- package/payload/platform/neo4j/edge-annotations.json +2 -2
- package/payload/platform/neo4j/schema.cypher +25 -1
- package/payload/platform/package.json +3 -2
- package/payload/platform/plugins/admin/PLUGIN.md +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +39 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +319 -66
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +275 -59
- package/payload/platform/plugins/docs/references/internals.md +4 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +11 -1
- package/payload/platform/plugins/docs/references/outlook-guide.md +27 -4
- package/payload/platform/plugins/graph/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/graph/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +12 -5
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.js +197 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +55 -9
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +96 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.d.ts +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.js +126 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js +7 -4
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js +18 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +7 -0
- 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 +4 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +2 -2
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +4 -4
- 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/quickbooks/PLUGIN.md +10 -10
- package/payload/platform/plugins/scheduling/PLUGIN.md +49 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +48 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.js +78 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js +457 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.d.ts +35 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.js +39 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts +175 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js +533 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +123 -9
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js +189 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.js +95 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.js +114 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +109 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +340 -9
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts +9 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +210 -45
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +43 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js +55 -7
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.js +45 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.js +96 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js +2 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +5 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +13 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts +4 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js +24 -3
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.d.ts +33 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.js +51 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +12 -2
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +30 -2
- package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +1 -1
- package/payload/platform/scripts/check-specialist-tool-surface.mjs +92 -16
- package/payload/platform/scripts/seed-neo4j.sh +41 -0
- package/payload/platform/scripts/verify-skill-tool-surface.sh +91 -2
- 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 +3 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/templates/specialists/agents/librarian.md +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/{chunk-AZLT4LVO.js → chunk-VUXKXOJU.js} +755 -335
- package/payload/server/{chunk-UX2FRNO7.js → chunk-WT2PFGY4.js} +37 -265
- package/payload/server/{chunk-PXNY2TAP.js → chunk-XCR5D2G3.js} +239 -10
- package/payload/server/{manager-CQBSUJXR.js → manager-4TJK3YOY.js} +4 -2
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-Dar_MMJm.js → AdminLoginScreens-uknqh2Pm.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-uknqh2Pm.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-uknqh2Pm.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-GqtMImyQ.js +2 -0
- package/payload/server/public/assets/AdminShell-GqtMImyQ.js.br +0 -0
- package/payload/server/public/assets/AdminShell-GqtMImyQ.js.gz +0 -0
- package/payload/server/public/assets/{activity-B1rt4GIy.js → activity-B4KfMJqO.js} +1 -1
- package/payload/server/public/assets/activity-B4KfMJqO.js.br +0 -0
- package/payload/server/public/assets/activity-B4KfMJqO.js.gz +0 -0
- package/payload/server/public/assets/admin-DxCZeVw0.js +1 -0
- package/payload/server/public/assets/admin-DxCZeVw0.js.br +0 -0
- package/payload/server/public/assets/admin-DxCZeVw0.js.gz +0 -0
- package/payload/server/public/assets/agents-BtwBuO4G.js +1 -0
- package/payload/server/public/assets/agents-BtwBuO4G.js.br +0 -0
- package/payload/server/public/assets/agents-BtwBuO4G.js.gz +0 -0
- package/payload/server/public/assets/{browser-1QSCPl3X.js → browser-B3hSzI3O.js} +1 -1
- package/payload/server/public/assets/browser-B3hSzI3O.js.br +0 -0
- package/payload/server/public/assets/browser-B3hSzI3O.js.gz +0 -0
- package/payload/server/public/assets/{calendar-D009qWVV.js → calendar-BggxXxhS.js} +1 -1
- package/payload/server/public/assets/calendar-BggxXxhS.js.br +0 -0
- package/payload/server/public/assets/calendar-BggxXxhS.js.gz +0 -0
- package/payload/server/public/assets/chat-flivHt0t.js +1 -0
- package/payload/server/public/assets/chat-flivHt0t.js.br +0 -0
- package/payload/server/public/assets/chat-flivHt0t.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-nVRThX7f.js +1 -0
- package/payload/server/public/assets/chevron-right-CLqfrkRA.js +1 -0
- package/payload/server/public/assets/chevron-right-CLqfrkRA.js.br +0 -0
- package/payload/server/public/assets/clock-Dj3SRZKo.js +1 -0
- package/payload/server/public/assets/clock-Dj3SRZKo.js.br +0 -0
- package/payload/server/public/assets/clock-Dj3SRZKo.js.gz +0 -0
- package/payload/server/public/assets/data-Ckjkne54.js +1 -0
- package/payload/server/public/assets/data-Ckjkne54.js.br +3 -0
- package/payload/server/public/assets/data-Ckjkne54.js.gz +0 -0
- package/payload/server/public/assets/{file-text-DwGAfCsd.js → file-text-BKs9eyiw.js} +1 -1
- package/payload/server/public/assets/file-text-BKs9eyiw.js.br +0 -0
- package/payload/server/public/assets/file-text-BKs9eyiw.js.gz +0 -0
- package/payload/server/public/assets/{graph-D-DGlfhW.js → graph-DTi71NaZ.js} +3 -3
- package/payload/server/public/assets/graph-DTi71NaZ.js.br +0 -0
- package/payload/server/public/assets/graph-DTi71NaZ.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-BT3S6e9H.js +1 -0
- package/payload/server/public/assets/graph-labels-BT3S6e9H.js.br +0 -0
- package/payload/server/public/assets/graph-labels-BT3S6e9H.js.gz +0 -0
- package/payload/server/public/assets/{maximize-2-C_-KapxH.js → maximize-2-zC94tsxY.js} +1 -1
- package/payload/server/public/assets/maximize-2-zC94tsxY.js.br +0 -0
- package/payload/server/public/assets/maximize-2-zC94tsxY.js.gz +0 -0
- package/payload/server/public/assets/operator-BtWNEMh-.js +1 -0
- package/payload/server/public/assets/operator-BtWNEMh-.js.br +0 -0
- package/payload/server/public/assets/operator-BtWNEMh-.js.gz +0 -0
- package/payload/server/public/assets/page-BFm91p0e.js +1 -0
- package/payload/server/public/assets/page-BFm91p0e.js.br +0 -0
- package/payload/server/public/assets/page-BFm91p0e.js.gz +0 -0
- package/payload/server/public/assets/{page-E74MYB18.js → page-Ccl0FazM.js} +3 -3
- package/payload/server/public/assets/page-Ccl0FazM.js.br +0 -0
- package/payload/server/public/assets/page-Ccl0FazM.js.gz +0 -0
- package/payload/server/public/assets/{public-CI6K1sNW.js → public-BpC6cn9S.js} +1 -1
- package/payload/server/public/assets/public-BpC6cn9S.js.br +0 -0
- package/payload/server/public/assets/public-BpC6cn9S.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-PWu_QGrD.js → rotate-ccw-BNcqbHkZ.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-BNcqbHkZ.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-BNcqbHkZ.js.gz +0 -0
- package/payload/server/public/assets/routines-D5iia-XG.js +1 -0
- package/payload/server/public/assets/routines-D5iia-XG.js.br +0 -0
- package/payload/server/public/assets/routines-D5iia-XG.js.gz +0 -0
- package/payload/server/public/assets/{skills-g8HvunPA.js → skills-COLAw_jt.js} +1 -1
- package/payload/server/public/assets/skills-COLAw_jt.js.br +0 -0
- package/payload/server/public/assets/skills-COLAw_jt.js.gz +0 -0
- package/payload/server/public/assets/tasks-CZDQI4nB.js +1 -0
- package/payload/server/public/assets/tasks-CZDQI4nB.js.br +0 -0
- package/payload/server/public/assets/tasks-CZDQI4nB.js.gz +0 -0
- package/payload/server/public/assets/{time-entry-format-BXWuQ6Sw.js → time-entry-format-laTFzawg.js} +1 -1
- package/payload/server/public/assets/time-entry-format-laTFzawg.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-laTFzawg.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-BNshRC13.js → triangle-alert-DrwqhEog.js} +1 -1
- package/payload/server/public/assets/triangle-alert-DrwqhEog.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-DrwqhEog.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-CwTZ9DqH.js → useCopyFeedback-Cxme5GK6.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-Cxme5GK6.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-Cxme5GK6.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-B-K8y5a4.css +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-B-K8y5a4.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-B-K8y5a4.css.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-C_1E11Dv.js +14 -0
- package/payload/server/public/assets/useSubAccountSwitcher-C_1E11Dv.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-C_1E11Dv.js.gz +0 -0
- package/payload/server/public/assets/{useVoiceRecorder-BGqV5R6u.js → useVoiceRecorder-DUBpVa2T.js} +1 -1
- package/payload/server/public/assets/useVoiceRecorder-DUBpVa2T.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-DUBpVa2T.js.gz +0 -0
- package/payload/server/public/assets/{wrench-DQ045Pyk.js → wrench-DWuxPnzF.js} +1 -1
- package/payload/server/public/assets/wrench-DWuxPnzF.js.br +0 -0
- package/payload/server/public/assets/wrench-DWuxPnzF.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 +6631 -5690
- package/payload/server/public/assets/AdminLoginScreens-Dar_MMJm.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-Dar_MMJm.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-CpaI2F_p.js +0 -2
- package/payload/server/public/assets/AdminShell-CpaI2F_p.js.br +0 -0
- package/payload/server/public/assets/AdminShell-CpaI2F_p.js.gz +0 -0
- package/payload/server/public/assets/activity-B1rt4GIy.js.br +0 -0
- package/payload/server/public/assets/activity-B1rt4GIy.js.gz +0 -0
- package/payload/server/public/assets/admin-DE3lLNje.js +0 -1
- package/payload/server/public/assets/admin-DE3lLNje.js.br +0 -0
- package/payload/server/public/assets/admin-DE3lLNje.js.gz +0 -0
- package/payload/server/public/assets/agents-BXq8woVf.js +0 -1
- package/payload/server/public/assets/agents-BXq8woVf.js.br +0 -0
- package/payload/server/public/assets/agents-BXq8woVf.js.gz +0 -0
- package/payload/server/public/assets/browser-1QSCPl3X.js.br +0 -0
- package/payload/server/public/assets/browser-1QSCPl3X.js.gz +0 -0
- package/payload/server/public/assets/calendar-D009qWVV.js.br +0 -0
- package/payload/server/public/assets/calendar-D009qWVV.js.gz +0 -0
- package/payload/server/public/assets/chat-CUg1fTqJ.js +0 -1
- package/payload/server/public/assets/chat-CUg1fTqJ.js.br +0 -1
- package/payload/server/public/assets/chat-CUg1fTqJ.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-C2JEy7uU.js +0 -1
- package/payload/server/public/assets/chevron-left-C2JEy7uU.js.br +0 -0
- package/payload/server/public/assets/chevron-right-DWa047Ys.js +0 -1
- package/payload/server/public/assets/chevron-right-DWa047Ys.js.br +0 -0
- package/payload/server/public/assets/clock-C3OqfCnn.js +0 -1
- package/payload/server/public/assets/clock-C3OqfCnn.js.br +0 -0
- package/payload/server/public/assets/clock-C3OqfCnn.js.gz +0 -0
- package/payload/server/public/assets/data-Bu_yCScC.js +0 -1
- package/payload/server/public/assets/data-Bu_yCScC.js.br +0 -0
- package/payload/server/public/assets/data-Bu_yCScC.js.gz +0 -0
- package/payload/server/public/assets/file-text-DwGAfCsd.js.br +0 -0
- package/payload/server/public/assets/file-text-DwGAfCsd.js.gz +0 -0
- package/payload/server/public/assets/graph-D-DGlfhW.js.br +0 -0
- package/payload/server/public/assets/graph-D-DGlfhW.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-BbX3fXzl.js +0 -1
- package/payload/server/public/assets/graph-labels-BbX3fXzl.js.br +0 -0
- package/payload/server/public/assets/graph-labels-BbX3fXzl.js.gz +0 -0
- package/payload/server/public/assets/maximize-2-C_-KapxH.js.br +0 -0
- package/payload/server/public/assets/maximize-2-C_-KapxH.js.gz +0 -0
- package/payload/server/public/assets/operator-CFrZVcv6.js +0 -1
- package/payload/server/public/assets/operator-CFrZVcv6.js.br +0 -0
- package/payload/server/public/assets/operator-CFrZVcv6.js.gz +0 -0
- package/payload/server/public/assets/page-CHt62f1_.js +0 -1
- package/payload/server/public/assets/page-CHt62f1_.js.br +0 -0
- package/payload/server/public/assets/page-CHt62f1_.js.gz +0 -0
- package/payload/server/public/assets/page-E74MYB18.js.br +0 -0
- package/payload/server/public/assets/page-E74MYB18.js.gz +0 -0
- package/payload/server/public/assets/public-CI6K1sNW.js.br +0 -0
- package/payload/server/public/assets/public-CI6K1sNW.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-PWu_QGrD.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-PWu_QGrD.js.gz +0 -0
- package/payload/server/public/assets/routines-Cp3_XS7S.js +0 -1
- package/payload/server/public/assets/routines-Cp3_XS7S.js.br +0 -0
- package/payload/server/public/assets/routines-Cp3_XS7S.js.gz +0 -0
- package/payload/server/public/assets/skills-g8HvunPA.js.br +0 -0
- package/payload/server/public/assets/skills-g8HvunPA.js.gz +0 -0
- package/payload/server/public/assets/tasks-CMC_jDVm.js +0 -1
- package/payload/server/public/assets/tasks-CMC_jDVm.js.br +0 -0
- package/payload/server/public/assets/tasks-CMC_jDVm.js.gz +0 -0
- package/payload/server/public/assets/time-entry-format-BXWuQ6Sw.js.br +0 -2
- package/payload/server/public/assets/time-entry-format-BXWuQ6Sw.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-BNshRC13.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-BNshRC13.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-CwTZ9DqH.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-CwTZ9DqH.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js +0 -14
- package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BGqV5R6u.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BGqV5R6u.js.gz +0 -0
- package/payload/server/public/assets/wrench-DQ045Pyk.js.br +0 -0
- package/payload/server/public/assets/wrench-DQ045Pyk.js.gz +0 -0
|
@@ -110,13 +110,95 @@ The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.t
|
|
|
110
110
|
| `GET /api/admin/routines` | Lists automation Events: `recurrence IS NOT NULL OR agentChannel IS NOT NULL OR actionPlugin IS NOT NULL`. Keyed by the `eventId` property. |
|
|
111
111
|
| `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
|
|
112
112
|
| `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt` and/or `recurrence`. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
|
|
113
|
+
| `POST /api/admin/routines/:eventId/suspend` | Writes `eventStatus = 'suspended'` on a routine currently `'scheduled'`. 404s otherwise, so a second suspend is not a silent no-op. |
|
|
114
|
+
| `POST /api/admin/routines/:eventId/resume` | Returns a suspended routine to `'scheduled'`. 404s if it is not suspended, 409s if it cannot be resumed. |
|
|
115
|
+
| `POST /api/admin/routines/suspend-all` | Suspends every routine currently `'scheduled'` and marks each one. Returns the count. |
|
|
116
|
+
| `POST /api/admin/routines/resume-all` | Resumes only the routines `suspend-all` marked. Returns `restored` and a `refused` list. |
|
|
117
|
+
|
|
118
|
+
**Suspension is the reversible stop.** Before it, the only way to stop a routine
|
|
119
|
+
from the dashboard was to delete it. A suspended routine keeps its schedule, its
|
|
120
|
+
instruction and its history; it simply stops being selected. The enforcement is
|
|
121
|
+
not a new guard: all four `eventStatus = 'scheduled'` predicates in
|
|
122
|
+
`check-due-events.ts` are equalities, so any other status is excluded by
|
|
123
|
+
construction, and `'suspended'` is the literal the shipped roster already seeds
|
|
124
|
+
with.
|
|
125
|
+
|
|
126
|
+
The status write lives on dedicated routes rather than on `PATCH`, which admits
|
|
127
|
+
only `agentPrompt` and `recurrence` and 400s on anything else. Widening `PATCH`
|
|
128
|
+
would put a free-form status on the edit path.
|
|
129
|
+
|
|
130
|
+
**Resume schedules forward and never backfills.** For a recurring routine
|
|
131
|
+
`nextRun` is recomputed from the cron in the routine's own timezone; the stored
|
|
132
|
+
value is deliberately not restored, because the sweep selects `nextRun <= now`
|
|
133
|
+
and a stale one fires on the next tick. A one-time routine has no cron to
|
|
134
|
+
advance, so resuming one whose date has passed is refused (409) with the date
|
|
135
|
+
named — resuming it would run it immediately, for a window the operator did not
|
|
136
|
+
choose. A one-time routine still in the future resumes with its date untouched.
|
|
137
|
+
|
|
138
|
+
**Suspend-all acts on exactly the dispatchable set**, so the number on the
|
|
139
|
+
control is the number of routines that could have fired; rows already due,
|
|
140
|
+
cancelled or suspended are untouched and a deliberately cancelled routine is
|
|
141
|
+
never re-armed. Each row it stops is marked, and resume-all selects only marked
|
|
142
|
+
rows — a routine suspended one at a time carries no mark and stays suspended,
|
|
143
|
+
which is what makes resume-all safe to press. The seeded roster is unmarked by
|
|
144
|
+
construction, so resume-all never enables it.
|
|
113
145
|
|
|
114
146
|
Observability rides `[admin:routines]`: `op=list accountId=<8> count=<n>`,
|
|
115
|
-
`op=get id=<8> found=<bool>`,
|
|
116
|
-
reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
147
|
+
`op=get id=<8> found=<bool>`, `op=patch id=<8> fields=<…> outcome=ok|rejected
|
|
148
|
+
reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized>`, and
|
|
149
|
+
`op=suspend|resume id=<8> scope=<one|all> outcome=<applied|refused|failed>` with
|
|
150
|
+
`count=`/`restored=` and, on a refusal, `reason=<not-scheduled|not-suspended|
|
|
151
|
+
past-one-time|invalid-cron>`. A `count=0` while routines exist for the account is
|
|
152
|
+
the account-scope-miss signature; the logged `cron=` on `outcome=ok` confirms the
|
|
153
|
+
persisted expression without opening the graph.
|
|
154
|
+
|
|
155
|
+
Two scheduler-side lines close the loop, because a suspend that the sweep ignores
|
|
156
|
+
looks identical to a healthy routine until it fires. `[schedule-sweep]
|
|
157
|
+
op=skip-suspended account=<8> count=<n>` is emitted every sweep for every local
|
|
158
|
+
account including zero, filtered to automation rows by the same predicate the
|
|
159
|
+
routes and the census use, so all three count one set. Nothing else in the sweep
|
|
160
|
+
ever mentions a suspended row, so without this line the whole class is invisible
|
|
161
|
+
there and a suspension that stopped holding would look exactly like one that
|
|
162
|
+
still holds. `[schedule-audit] op=suspension-census acct=<8> routines=<n>
|
|
163
|
+
dispatchable=<n> suspended=<n> due=<n> cancelled=<n> completed=<n> other=<n>` is
|
|
164
|
+
the standing reconciliation: the named buckets are the whole shipped status enum
|
|
165
|
+
plus `suspended`, so they sum to `routines` by construction and `other` above
|
|
166
|
+
zero means a routine carries a status the system does not handle — nothing else
|
|
167
|
+
would say so. `completed` gets its own bucket even though no code path writes it
|
|
168
|
+
today, because `schedule-update` takes `eventStatus` as an unvalidated string and
|
|
169
|
+
one completed routine would otherwise pin `other` above zero forever and kill the
|
|
170
|
+
alarm.
|
|
171
|
+
|
|
172
|
+
**The shipped roster.** Every account starts with ten routines — `start-of-day`,
|
|
173
|
+
`inbound-check`, `end-of-day`, `appointment-reminder`, `commitment-chase`,
|
|
174
|
+
`calendar-reconcile`, `contact-reconcile`, `crm-reconcile`, `finance-reconcile`,
|
|
175
|
+
`weekly-digest` — seeded `eventStatus:'suspended'` with `nextRun` NULL and no
|
|
176
|
+
channel or destination, so `/routines` is a catalogue to enable rather than a
|
|
177
|
+
blank page. The list query applies no status filter and `AUTOMATION_WHERE`
|
|
178
|
+
admits them on `recurrence IS NOT NULL`, so they render with no change here.
|
|
179
|
+
They cannot fire: every dispatch query in `check-due-events.ts` selects
|
|
180
|
+
`eventStatus = 'scheduled'`, and the recurring one additionally requires
|
|
181
|
+
`nextRun IS NOT NULL`.
|
|
182
|
+
|
|
183
|
+
Enabling one is `schedule-update {eventId, eventStatus:'scheduled', recurrence,
|
|
184
|
+
timezone}` — all four arguments. `eventStatus` on its own does not touch
|
|
185
|
+
`nextRun`, so the row would read enabled and never fire; omitting `timezone`
|
|
186
|
+
recomputes `nextRun` in the server's zone rather than the operator's, so it
|
|
187
|
+
fires at the wrong hour and still looks healthy. Binding a channel and a
|
|
188
|
+
destination is separate and equally required: `chooseDispatchMode` returns
|
|
189
|
+
`{kind:'none'}` unless `agentChannel`, `agentDestination` and `agentPrompt` are
|
|
190
|
+
all present, and a destination cannot be seeded because
|
|
191
|
+
`validateAgentDestination` is fail-closed against the account's
|
|
192
|
+
`whatsapp.adminPhones` / `telegram.adminUsers`, both empty on a fresh install.
|
|
193
|
+
|
|
194
|
+
The seed re-runs on every upgrade and never writes `eventStatus`,
|
|
195
|
+
`agentChannel`, `agentDestination`, `recurrence` or `timezone` on a row that
|
|
196
|
+
already exists, so an upgrade cannot re-suspend an enabled routine, unbind a
|
|
197
|
+
bound destination or reset a retimed cron. Three standing checks ride the
|
|
198
|
+
scheduling heartbeat and report on every tick including zero:
|
|
199
|
+
`[schedule-audit] op=roster` (a seed that never ran or ran short),
|
|
200
|
+
`op=dispatch-mode-none` (a routine that fires and delivers nothing) and
|
|
201
|
+
`op=null-timezone` (a routine that fires at the box's hour).
|
|
120
202
|
|
|
121
203
|
### Skills
|
|
122
204
|
|
|
@@ -171,7 +253,9 @@ both `kind: 'specialist'` and differ only by origin.
|
|
|
171
253
|
`agent-builder`). The specialist modal shows the model, tools, the owning
|
|
172
254
|
plugin's skills, and the system-prompt body. Specialists are authored, edited,
|
|
173
255
|
and deleted through chat (`agent-builder` / `specialist-management`), so the
|
|
174
|
-
specialist modal offers no delete or open-agent link.
|
|
256
|
+
specialist modal offers no delete or open-agent link. **These are the only
|
|
257
|
+
agents that can be switched off from this page**, and the route enforces that
|
|
258
|
+
rather than relying on the UI to withhold the control.
|
|
175
259
|
- **Shipped specialists** (`origin: 'shipped'`) — the union of
|
|
176
260
|
`<accountDir>/specialists/agents/`, `<accountDir>/specialists/agents-disabled/`
|
|
177
261
|
and `PLATFORM_ROOT/templates/specialists/agents/`, in that precedence order.
|
|
@@ -179,22 +263,27 @@ both `kind: 'specialist'` and differ only by origin.
|
|
|
179
263
|
a disabled agent still appears there through its bundled-template row. The
|
|
180
264
|
route once walked only the account plugins tree, so the twelve
|
|
181
265
|
highest-capability agents on the box were the ones the operator could not see.
|
|
182
|
-
|
|
183
|
-
|
|
266
|
+
**A shipped agent cannot be switched off here.** It is part of the product, so
|
|
267
|
+
disabling one breaks a capability the operator did not author and cannot
|
|
268
|
+
repair, and it is not what runs unattended: an agent runs only when something
|
|
269
|
+
dispatches it, an inbound message is operator-initiated, so the only autonomous
|
|
270
|
+
path is the scheduler. The modal says so and points at the routines instead.
|
|
184
271
|
**A file in the LIVE directory whose name also exists under
|
|
185
272
|
`<accountDir>/plugins/*/agents/` is excluded from this set.** `agent-builder`
|
|
186
273
|
copies the authored file into `specialists/agents/` because presence there is
|
|
187
274
|
what makes an agent dispatchable, so the live directory says nothing about who
|
|
188
275
|
wrote the agent and every activated user-created specialist used to render
|
|
189
276
|
twice, the second time as Shipped. The account plugin tree is the authorship
|
|
190
|
-
evidence and its row wins
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
and
|
|
277
|
+
evidence and its row wins; it is the same discriminator `disable` requires.
|
|
278
|
+
Both gates read filenames off disk, so a plugin file whose frontmatter does not
|
|
279
|
+
parse still suppresses its live twin — it is authored either way, and
|
|
280
|
+
`specialistsSkipped` is what reports the parse failure. **The exclusion covers
|
|
281
|
+
both account directories, live and quarantine.** It was live-only while only a
|
|
282
|
+
shipped agent could be quarantined; now that an authored specialist can be
|
|
283
|
+
switched off, its specialist row carries the plugin, the skills, the detail
|
|
284
|
+
route and its own disabled state, so a second row from the shipped walk would
|
|
285
|
+
be a duplicate with none of that. The bundled templates are never deduped: a
|
|
286
|
+
bundled name is shipped by definition. Each exclusion logs
|
|
198
287
|
`op=list-shipped-dedup file=<name>` and the count rides `op=list` and the
|
|
199
288
|
response as `deduped`.
|
|
200
289
|
|
|
@@ -222,35 +311,137 @@ no class is dashed and uncoloured rather than painted as if it were understood.
|
|
|
222
311
|
Badge colours come from the shared semantic status set: `-solid` with white text
|
|
223
312
|
for the badge, `-tint` with `-textOnTint` for a chip, never white.
|
|
224
313
|
|
|
314
|
+
**No tool name on a card.** The card renders the badge word and the plugin (or,
|
|
315
|
+
for a public agent, its status). It does not render `riskiestTool`: a qualified
|
|
316
|
+
tool symbol tells the person reading this dashboard nothing the badge word did
|
|
317
|
+
not. `riskiestTool` stays on the wire and on the `op=risk` line as a diagnostic,
|
|
318
|
+
so a wrong class is still traceable to the tool that set it without re-deriving
|
|
319
|
+
the classification.
|
|
320
|
+
|
|
321
|
+
**Combination rules.** The per-tool max answers "what is the worst single thing
|
|
322
|
+
this agent can do". It cannot answer "what can it do in sequence", which is the
|
|
323
|
+
question the person reading the dashboard is actually asking: an agent that
|
|
324
|
+
reads a customer record and an agent that emails a stranger are both red, and so
|
|
325
|
+
is the one that does both. Four rules in `server/lib/agent-risk.ts` run *after*
|
|
326
|
+
the max. A rule fires when **every** risk class it names is present among the
|
|
327
|
+
resolved tools and its own class is not below the class the max produced; the
|
|
328
|
+
first match in table order wins and supplies the sentence the modal renders.
|
|
329
|
+
|
|
330
|
+
| Rule | Fires when the agent holds | Class | What the operator reads |
|
|
331
|
+
|---|---|---|---|
|
|
332
|
+
| `changes-and-sends` | a `write_local` tool and an `external` tool | red | It can change your records and also send messages out, so a wrong change can reach someone outside the business. |
|
|
333
|
+
| `reads-and-sends` | a `read` tool and an `external` tool | red | It can read what is held on this device and also send things out of it, so anything it reads can leave. |
|
|
334
|
+
| `changes-and-runs` | a `write_local` tool and an `exec` tool | red | It can change files on this device and run commands, so a mistake is not confined to one file. |
|
|
335
|
+
| `reads-and-runs` | a `read` tool and an `exec` tool | red | It can read what is held on this device and run commands on it. |
|
|
336
|
+
|
|
337
|
+
The order is the priority order: reaching outside the business outranks running
|
|
338
|
+
a command inside it, and changing something outranks reading it. A rule may only
|
|
339
|
+
**raise** a class, never lower one, and because `exec` and `external` both
|
|
340
|
+
saturate at red today every rule is itself red — so on the shipped fleet the
|
|
341
|
+
rules change no colour and their whole product is the named hazard in words. A
|
|
342
|
+
rule whose class sat below the derived one would be skipped rather than applied,
|
|
343
|
+
so a softer sentence can never be attached to a harder class.
|
|
344
|
+
|
|
345
|
+
With no rule matched the sentence comes from the class itself: green says it can
|
|
346
|
+
only read, amber says it can change what is held here and nothing reaches
|
|
347
|
+
outside, red says it can run commands or reach services outside. The three
|
|
348
|
+
forced-red paths carry their own sentences instead, because none of them derived
|
|
349
|
+
a capability: a tool with no risk class on this install, an agent with no
|
|
350
|
+
`tools:` line at all, and a plugin registry that failed to load.
|
|
351
|
+
|
|
352
|
+
Of the four rules, three fire on the twelve shipped specialists
|
|
353
|
+
(`changes-and-sends`, `changes-and-runs`, `reads-and-runs`) and `reads-and-sends`
|
|
354
|
+
fires on none of them, which is what the `op=rule-census` line exists to report
|
|
355
|
+
across real accounts.
|
|
356
|
+
|
|
225
357
|
**Disable.** `<accountDir>/specialists/agents/` is the live dispatchable set —
|
|
226
358
|
`spawn-context.ts` reads it to build the spawn manifest — so disabling **moves**
|
|
227
|
-
the agent file to `<accountDir>/specialists/agents-disabled
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
359
|
+
the agent file to `<accountDir>/specialists/agents-disabled/`. A flag alone would
|
|
360
|
+
leave the agent running while the card claimed otherwise. `agent-builder` copies
|
|
361
|
+
an authored specialist into that same live directory, because presence there is
|
|
362
|
+
what makes it dispatchable, so the same move applies to it.
|
|
363
|
+
|
|
364
|
+
The file is moved, never deleted: an authored specialist's activated copy can
|
|
365
|
+
carry operator edits its plugin-tree original does not, so deleting one and later
|
|
366
|
+
restoring "the original" would hand back a different agent than the operator
|
|
367
|
+
switched off. A disabled agent stays listed, because an agent that vanished from
|
|
368
|
+
the surface that disabled it could never be switched back on.
|
|
369
|
+
|
|
370
|
+
**Authorisation is drawn in both states.** Every specialist card carries a
|
|
371
|
+
`Live` or `Stood down` pill, and the header above the list reads
|
|
372
|
+
`<n> agents · <n> stood down`, derived from the same rows the cards render.
|
|
373
|
+
Drawing only the disabled half made a live agent indistinguishable from a pill
|
|
374
|
+
that failed to render, and left the operator counting cards to answer how much
|
|
375
|
+
of the fleet was switched off. Public agents carry no pill: they have no disable
|
|
376
|
+
path, so `Live` there would assert something the surface never derived.
|
|
377
|
+
|
|
378
|
+
**The disable confirm names what stops.** Before the confirm it reads
|
|
379
|
+
`Stop this agent being dispatched?` plus the skills that run through the agent
|
|
380
|
+
and the fact that nothing can hand it work until it is switched back on. It does
|
|
381
|
+
**not** name the routines that dispatch the agent, and cannot: a routine
|
|
382
|
+
`:Event` stores `agentChannel`, `agentDestination` and `agentPrompt`, never an
|
|
383
|
+
agent, so there is nothing on disk to join against. A shipped agent's detail
|
|
384
|
+
also carries no skills, because a shipped specialist owns no skills directory, so
|
|
385
|
+
today the skills clause appears only on a plugin-backed specialist. The
|
|
386
|
+
shipped-agent sentence is a different thing and does not conflict: it names
|
|
387
|
+
suspending routines as the general way to stop unattended dispatch, which is
|
|
388
|
+
true of every shipped agent, rather than claiming this one's routines can be
|
|
389
|
+
listed.
|
|
390
|
+
|
|
391
|
+
Three cases, no more. Authored with a live copy → moved to quarantine. Authored
|
|
392
|
+
with no live copy → refused 409: it is not in the dispatchable set, so there is
|
|
393
|
+
nothing to switch off and recording one would claim work that did not happen.
|
|
394
|
+
Not authored → refused 404, naming the routines as what stops it instead.
|
|
395
|
+
|
|
396
|
+
**Enable is deliberately not symmetric, and takes no authorship check.** An
|
|
397
|
+
install that disabled a shipped agent before the gate was inverted holds it in
|
|
398
|
+
the quarantine directory and names it in `agents-disabled.json`; requiring
|
|
399
|
+
authorship on enable would strand it there with no route back through the
|
|
400
|
+
product. So enable serves both classes: a quarantined authored specialist, and a
|
|
401
|
+
shipped agent disabled under the old rule (whether it was moved, or was
|
|
402
|
+
bundled-only and recorded in the store alone).
|
|
403
|
+
|
|
404
|
+
**Nothing writes `agents-disabled.json` any more.** That 0600 store exists only
|
|
405
|
+
to stop the next provisioning run re-delivering a withheld shipped file —
|
|
406
|
+
`provision-account-dir.sh` re-reads it after its core-specialist recopy and
|
|
407
|
+
removes anything named in it — and shipped disable is now refused, so no new
|
|
408
|
+
entry can be created. An authored specialist needs no entry either: provisioning
|
|
409
|
+
never writes the account plugin tree, so the quarantined file is its whole state.
|
|
410
|
+
The store is now read-and-clear: the listing reads it for shipped rows and enable
|
|
411
|
+
clears entries from it, rewriting it whole and re-applying 0600 explicitly
|
|
412
|
+
(`writeFileSync`'s `mode` applies only on creation, and nothing creates this file
|
|
413
|
+
now). An unreadable store withholds nothing and says so: a corrupt file is not
|
|
414
|
+
evidence that an agent was disabled.
|
|
415
|
+
|
|
416
|
+
Both routes reconcile the admin's routing prose. `agents/admin/AGENTS.md` carries
|
|
417
|
+
one `- **specialists:<name>**:` line per dispatchable specialist, and
|
|
244
418
|
`agents-md-bootstrap.sh` used to only ever append, so a disabled agent stayed
|
|
245
419
|
advertised and the admin dispatched to a file that was no longer there. The
|
|
246
|
-
script
|
|
247
|
-
|
|
420
|
+
script withholds a line for every name in the store and both routes exec it after
|
|
421
|
+
the move, so the prose is right immediately rather than after the next
|
|
248
422
|
provisioning run. It emits `[agents-md] op=specialist-lines account=<id8>
|
|
249
|
-
kept=<n> withheld=<comma-list|none|store-unreadable>`.
|
|
250
|
-
|
|
423
|
+
kept=<n> withheld=<comma-list|none|store-unreadable>`. Note the reach, because it is not
|
|
424
|
+
symmetric: the script skips every `--` filename as out of scope, and its prune is
|
|
425
|
+
keyed on a store basename (`acme--helper`) while the routing line is keyed on the
|
|
426
|
+
frontmatter name (`helper`) that the activation path in `specialist-management`
|
|
427
|
+
wrote. So the bootstrap can neither remove nor restore an authored specialist's
|
|
428
|
+
line, and before this change that did not matter because that class could not be
|
|
429
|
+
disabled at all. It is now the only class that can be, so the route makes a
|
|
430
|
+
second, targeted pass for it, reading the name from whichever copy of the file
|
|
431
|
+
exists after the move and emitting `op=agents-md-authored`. Without it the admin
|
|
432
|
+
would keep advertising a specialist whose file is quarantined and dispatch would
|
|
433
|
+
fail inside the turn, which is the defect the bootstrap's prune was written to
|
|
434
|
+
prevent, on the one class it does not reach. `system-status` reads the same store, so a shipped agent
|
|
435
|
+
disabled under the old rule reports `disabled (operator)` rather than
|
|
251
436
|
`missing file`, and the header counts the two separately
|
|
252
437
|
(`<n> registered, missing=<x> disabled=<y>`).
|
|
253
438
|
|
|
439
|
+
Observability rides `[admin/agents] op=disable|enable accountId=<8> slug=<s>
|
|
440
|
+
origin=<specialist|shipped> eligible=<bool> outcome=<applied|refused|failed>`
|
|
441
|
+
with `reason=<not-user-created|not-dispatchable|not-disabled>` on a refusal.
|
|
442
|
+
`eligible=false outcome=refused` is what proves the gate is live rather than
|
|
443
|
+
merely re-labelled in the UI.
|
|
444
|
+
|
|
254
445
|
**Account scope.** The route (`server/routes/admin/agents.ts`) never infers an
|
|
255
446
|
account from device state. The two reads resolve from the caller's admin session
|
|
256
447
|
(`requireAdminSession` + the shared `accountDirForSession` in
|
|
@@ -264,10 +455,10 @@ explicit `?plugin=` parameter, not inferred from `--` in the slug.
|
|
|
264
455
|
|
|
265
456
|
| Route | Behaviour |
|
|
266
457
|
|-------|-----------|
|
|
267
|
-
| `GET /api/admin/agents` | Session-scoped. Lists the session account's public `agents/*/` dirs (never the `admin` agent), its user-created specialists from `plugins/*/agents/*.md`, and its shipped specialists, each row tagged `kind` and `origin` and carrying `risk`, `riskiestTool`, `unresolved` and `disabled`. A live-directory candidate whose filename also exists under `plugins/*/agents/` is dropped and counted, so an activated user-created agent is listed once. Returns `{agents, accountId, skipped, specialistsSkipped, shippedSkipped, deduped, riskSurfaceFailed}`. 401 when the session maps to no account. |
|
|
268
|
-
| `GET /api/admin/agents/:slug` | Session-scoped. Without `?plugin=` or `?origin=`, returns a public agent's config fields + four owned docs + a `present` map (a missing or unreadable doc is `''`/`present.<role>=false`, never a 500). With `?plugin=`, that plugin's user-created specialist; with `?origin=shipped`, the shipped one. Both specialist shapes add `{risk, riskiestTool, unresolved, byTool, disabled}`, where `byTool` is one class per declared tool. Selection is explicit rather than inferred, because a premium file and a user-created specialist under a plugin of the same name produce the same slug. Unknown 404s, 401 as above. |
|
|
269
|
-
| `POST /api/admin/agents/:slug/disable?accountId=` |
|
|
270
|
-
| `POST /api/admin/agents/:slug/enable?accountId=` |
|
|
458
|
+
| `GET /api/admin/agents` | Session-scoped. Lists the session account's public `agents/*/` dirs (never the `admin` agent), its user-created specialists from `plugins/*/agents/*.md`, and its shipped specialists, each row tagged `kind` and `origin` and carrying `risk`, `riskiestTool`, `riskRule`, `riskReason`, `unresolved` and `disabled`. A live-directory candidate whose filename also exists under `plugins/*/agents/` is dropped and counted, so an activated user-created agent is listed once. Returns `{agents, accountId, skipped, specialistsSkipped, shippedSkipped, deduped, riskSurfaceFailed}`. 401 when the session maps to no account. |
|
|
459
|
+
| `GET /api/admin/agents/:slug` | Session-scoped. Without `?plugin=` or `?origin=`, returns a public agent's config fields + four owned docs + a `present` map (a missing or unreadable doc is `''`/`present.<role>=false`, never a 500). With `?plugin=`, that plugin's user-created specialist; with `?origin=shipped`, the shipped one. Both specialist shapes add `{risk, riskiestTool, riskRule, riskReason, unresolved, byTool, disabled}`, where `byTool` is one class per declared tool. Selection is explicit rather than inferred, because a premium file and a user-created specialist under a plugin of the same name produce the same slug. Unknown 404s, 401 as above. |
|
|
460
|
+
| `POST /api/admin/agents/:slug/disable?accountId=` | Agents the operator authored only, enforced here rather than left to the UI: a slug NOT present under `plugins/*/agents/` 404s (`reason=not-user-created`), and the message names suspending the routines as what stops a shipped agent instead. An authored slug with no copy in `specialists/agents/` 409s (`reason=not-dispatchable`): it is already not dispatchable, so there is nothing to move and nothing to record. Otherwise it moves the file from `specialists/agents/` to `specialists/agents-disabled/` and returns `{ok, moved:true}` — the only outcome, since the two other cases refuse. It writes NO store entry: `agents-disabled.json` exists to stop provisioning re-delivering a withheld shipped file, and provisioning never writes the account plugin tree. Returns `routingReconciled`, false when either AGENTS.md pass threw; the move already happened and already stopped dispatch, so the request is not failed and the caller is told rather than left assuming the prose is right. Same `accountId` contract as delete. |
|
|
461
|
+
| `POST /api/admin/agents/:slug/enable?accountId=` | Takes NO authorship check, and that asymmetry with disable is deliberate: an install that switched a shipped agent off under the old gate must still reach this route, or inverting the gate strands it. For an authored slug the quarantined file is the whole state — 404 when it is absent, `restored:true` when it moves back, and the store is never touched. For a shipped slug it covers both of the old disable's outcomes: a quarantined file moves back (`restored:true`), and a bundled-only agent has nothing to move so clearing the store entry is the whole job (`restored:false`), which is what stops provisioning withholding it. 404 only when neither the quarantine directory nor the store knows the slug. The store is read strictly first, because this is the read half of a read-modify-write that rewrites the file whole, and it is written back only when an entry was actually removed — so enabling never creates a store an account did not have. |
|
|
271
462
|
| `DELETE /api/admin/agents/:slug?accountId=` | Public agents only, on the named validated account. Removes the dir after `deleteAgentProjection`; refuses the `admin` slug (403) and a missing/unknown `accountId` (400) with no write. Loud-fail: a graph-cleanup throw aborts the file removal. |
|
|
272
463
|
| `POST /api/admin/agents/:slug/project?accountId=` | Re-projects the named account's on-disk agent into the graph. Same `accountId` contract as delete. |
|
|
273
464
|
|
|
@@ -301,25 +492,40 @@ Agents page survived undiagnosed on a multi-account install.
|
|
|
301
492
|
does not add up is visible without reproducing anything. The over-exclusion to
|
|
302
493
|
watch for is a `deduped` line naming a file that is also a bundled or premium
|
|
303
494
|
agent: the authored file then hides a genuinely shipped one.
|
|
304
|
-
- `op=
|
|
305
|
-
|
|
306
|
-
disabled=<bool>` — one line
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
**A non-
|
|
495
|
+
- `[agents] op=risk account=<id8> slug=<name> origin=<shipped|public|specialist>
|
|
496
|
+
class=<green|amber|red> rule=<id|none> riskiestTool=<tool> tools=<n>
|
|
497
|
+
unresolved=<n> unresolvedTools=<comma-list|none> disabled=<bool>` — one line
|
|
498
|
+
per classified agent per listing, so it fires on every page load.
|
|
499
|
+
**`rule=` is the field that proves the combination table is live**: a
|
|
500
|
+
mis-specified rule emits nothing of its own and is otherwise indistinguishable
|
|
501
|
+
from an absent one. `riskiestTool` makes a wrong class diagnosable without
|
|
502
|
+
re-deriving it; its value is the FULL canonical tool name and it is no longer
|
|
503
|
+
rendered anywhere, so this line is where it is read.
|
|
504
|
+
**A non-zero `unresolved=` is the one to act on**: the agent
|
|
314
505
|
is forced red, and the cause is that the built-in table or the registry
|
|
315
|
-
mapping has drifted, not that the agent changed.
|
|
316
|
-
- `op=
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
506
|
+
mapping has drifted, not that the agent changed. `unresolvedTools=` says which.
|
|
507
|
+
- `[agents] op=rule-census account=<id8> rules=<n> classified=<n> fired=<n>
|
|
508
|
+
never=<comma-list|none>` — one line per listing, not per agent.
|
|
509
|
+
**This is the standing check.** A rule that never fires produces no evidence of
|
|
510
|
+
its own, so nothing except this line would ever say so; a rule id permanently
|
|
511
|
+
in `never` across every account is dead or wrong. `fired + never` always equals
|
|
512
|
+
`rules`. Read `never` only when `classified` is non-zero: an account with no
|
|
513
|
+
specialists puts every rule in `never` and would otherwise be
|
|
514
|
+
indistinguishable from a table that was never wired. Today `reads-and-sends`
|
|
515
|
+
sits in `never` on an install whose agents are all shipped, which is expected
|
|
516
|
+
and not a fault.
|
|
517
|
+
- `op=risk-surface status=load-failed reason=<msg>`, emitted with
|
|
518
|
+
`[agents] op=risk-surface-failed reason=registry-unreadable detail=<msg>` — the
|
|
519
|
+
plugin registry did not parse, so every row is forced red and the listing
|
|
520
|
+
returns `riskSurfaceFailed: true`. Both lines say the same thing; the second is
|
|
521
|
+
the greppable shape, so one pattern (`\[agents\] op=(risk|rule-census|risk-surface-failed)`)
|
|
522
|
+
covers the per-agent lines, the census and the outage that forced them. Without
|
|
523
|
+
the forced red, every plugin tool would land in `unresolved` and a registry
|
|
524
|
+
outage would read as agent drift across every card at once; without the
|
|
525
|
+
response field, a red row would be indistinguishable from a genuinely dangerous
|
|
526
|
+
agent, because nothing resolved so `unresolved` is empty and the per-tool
|
|
527
|
+
explanation cannot fire. Every row's reason then says the registry is the
|
|
528
|
+
cause rather than asserting a capability the derivation never produced.
|
|
323
529
|
- `op=disabled-store status=parse-failed reason=<msg>` — the listing then treats
|
|
324
530
|
every agent as enabled, which is what they actually are. A read surface shows
|
|
325
531
|
the true dispatchable set rather than a comforting one.
|
|
@@ -712,6 +918,16 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
712
918
|
> - **Dashboard home** renders on `/` (a greeting, stat cards, a Live-now column,
|
|
713
919
|
> a Recent artefacts card, and a Channels column), consuming existing endpoints
|
|
714
920
|
> only.
|
|
921
|
+
> - **Channels card rows.** A WhatsApp row shows a profile picture,
|
|
922
|
+
> the conversation's name, its last message, and the time, matching the layout
|
|
923
|
+
> of the WhatsApp client. The name is the one WhatsApp itself holds: a group
|
|
924
|
+
> shows its group subject, a 1:1 shows the contact name saved on the paired
|
|
925
|
+
> phone. A group row carries an extra `Group` chip. When no name has been
|
|
926
|
+
> captured yet, a group falls back to its group id and a 1:1 to its phone
|
|
927
|
+
> number; neither ever shows a group member's name or the install's own
|
|
928
|
+
> WhatsApp display name. The existing `Public` chip (the message came from
|
|
929
|
+
> outside) and `House` / `Client` chip (which account owns the thread) are
|
|
930
|
+
> unchanged.
|
|
715
931
|
> - **Per-surface header.** Each surface carries its own header (`PageHeader`):
|
|
716
932
|
> the sidebar collapse toggle, the page title, and a Claude status pill on the
|
|
717
933
|
> 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.
|
|
@@ -144,12 +144,22 @@ Every new node in {{productName}}'s graph is created with at least one connectio
|
|
|
144
144
|
|
|
145
145
|
Every node also carries a provenance stamp — which agent wrote it, in which session, via which tool. You never see these fields, but they are how operators trace unusual growth back to the code path that produced it, and why your graph stays clean over time.
|
|
146
146
|
|
|
147
|
-
**Two write surfaces, one substrate.** General agents write through schema-aware helpers — {{productName}} can record a new contact, a new commitment, a new preference without ever typing a database query, and the helper enforces the connection-and-provenance rule above structurally. The
|
|
147
|
+
**Two write surfaces, one substrate.** General agents write through schema-aware helpers — {{productName}} can record a new contact, a new commitment, a new preference without ever typing a database query, and the helper enforces the connection-and-provenance rule above structurally. The librarian (the specialist {{productName}} dispatches for ingest and graph hygiene — "merge those two duplicate contacts," "wire those four tasks to the meeting," "import this archive") additionally has a raw Cypher write tool, scoped to executing templates other tools emit, for the multi-step operations the helpers cannot express. The steward role internalises the same connection-and-provenance discipline in its prompt; a post-write audit emits a warning on every breach so the same rules apply to both surfaces. Both paths feed the same hourly orphan trend and the same forensic provenance fields — read-side, you cannot tell the two apart, and that is the point.
|
|
148
148
|
|
|
149
149
|
## Vertical schemas
|
|
150
150
|
|
|
151
151
|
On top of the base graph, each brand boots one optional **vertical** — an extra set of entity types tailored to a trade. The vertical is named by `brand.json#vertical` and defined in a `schema-<name>.md` reference; the memory plugin loads it at startup and validates every write against base + the active vertical. Real Agent boots `schema-estate-agent` (Listing, Property, Viewing, Offer). SiteDesk boots `schema-construction`, the canonical JobLogic-derived field-service ontology, which adds the service-delivery entities — `Job`, `Customer`, `Site`, `Asset`, `Visit`, `Quote`, `QuoteLine`, `Valuation`, `VariationNote`, `JobCost`, `Part`, `PurchaseOrder`, `Invoice`, `InboundInvoice`, `PpmContract`, `WhatsAppGroup` (with `Engineer` and `Supplier` as additional labels on `Person`/`Organization`) — grounded in real builder job folders so a job's customer, site, quote, valuations, variations, costs, and received supplier invoices all hang off one `Job` node. The default SiteDesk brand boots no vertical (base graph only).
|
|
152
152
|
|
|
153
|
+
### Estate agents: a listing needs no photo, a property needs no listing
|
|
154
|
+
|
|
155
|
+
Two things {{productName}} used to refuse, and no longer does.
|
|
156
|
+
|
|
157
|
+
**A listing can be recorded before it has a photo.** You take the instruction on Monday and the photographer comes on Thursday. The listing exists as a business fact for those three days, so {{productName}} records it as soon as you tell it about it, with the photo and the live page link added whenever they arrive. The same applies to the link: a property's page does not exist until the site is published.
|
|
158
|
+
|
|
159
|
+
**A property can be recorded before it has a listing.** A property you have valued, or been asked to appraise, or are holding off-market is a real property. {{productName}} records it as the property itself, with no marketing attached, and the listing hangs off it later — the same property can be listed for sale, withdrawn, and re-listed to let without ever becoming three properties.
|
|
160
|
+
|
|
161
|
+
Neither gap is forgotten. Once a day {{productName}} counts the listings visible to the public that still have no photo, and the properties that still have no listing, so a gap that should have closed by now can be seen rather than guessed at. If a public listing genuinely has no photo, its property card is shown as text without one rather than with a broken image.
|
|
162
|
+
|
|
153
163
|
## Public-facing summaries for customer-readable subjects
|
|
154
164
|
|
|
155
165
|
Some entities in your graph are knowable by people outside your team — companies you work with, projects you've delivered, the business itself. For those entities ({{productName}} treats `:Organization`, `:Concept`, `:Project`, and `:LocalBusiness` this way), {{productName}} maintains two summaries: a private one only you and your specialist agents see, and a customer-facing public one your public agents are allowed to surface.
|
|
@@ -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) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graph",
|
|
3
|
-
"description": "Raw Neo4j Cypher proxy. Wraps the upstream `mcp-neo4j-cypher` server (spawned under uvx by `platform/lib/graph-mcp`) under the stable `maxy-graph` namespace, so its tools register as `maxy-graph-read_neo4j_cypher`, `maxy-graph-get_neo4j_schema`, and `maxy-graph-write_neo4j_cypher`. The read and schema tools are admin-allowlisted for schema checks and read-only queries; the write tool is specialist-only
|
|
3
|
+
"description": "Raw Neo4j Cypher proxy. Wraps the upstream `mcp-neo4j-cypher` server (spawned under uvx by `platform/lib/graph-mcp`) under the stable `maxy-graph` namespace, so its tools register as `maxy-graph-read_neo4j_cypher`, `maxy-graph-get_neo4j_schema`, and `maxy-graph-write_neo4j_cypher`. The read and schema tools are admin-allowlisted for schema checks and read-only queries; the write tool is specialist-only: the `librarian` executes operator-confirmed `mergeCypher` templates through it, never hand-authored Cypher. `database-operator` does not hold it: that specialist is scoped to the wrapped writers and stays narrow. The shim validates write Cypher against the live schema, stamps provenance, and rejects unknown labels/edges. Account isolation is per-brand Neo4j; the shim adds no query-layer tenant filter.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -27,7 +27,7 @@ mcp:
|
|
|
27
27
|
SESSION_ID: ${SESSION_ID}
|
|
28
28
|
NEO4J_URI: ${NEO4J_URI}
|
|
29
29
|
NEO4J_PASSWORD: ${NEO4J_PASSWORD}
|
|
30
|
-
description: "Raw Neo4j Cypher proxy. Wraps the upstream `mcp-neo4j-cypher` server (spawned under uvx by `platform/lib/graph-mcp`) under the stable `maxy-graph` namespace, so its tools register as `maxy-graph-read_neo4j_cypher`, `maxy-graph-get_neo4j_schema`, and `maxy-graph-write_neo4j_cypher`. The read and schema tools are admin-allowlisted for schema checks and read-only queries; the write tool is specialist-only
|
|
30
|
+
description: "Raw Neo4j Cypher proxy. Wraps the upstream `mcp-neo4j-cypher` server (spawned under uvx by `platform/lib/graph-mcp`) under the stable `maxy-graph` namespace, so its tools register as `maxy-graph-read_neo4j_cypher`, `maxy-graph-get_neo4j_schema`, and `maxy-graph-write_neo4j_cypher`. The read and schema tools are admin-allowlisted for schema checks and read-only queries; the write tool is specialist-only: the `librarian` executes operator-confirmed `mergeCypher` templates through it, never hand-authored Cypher. `database-operator` does not hold it: that specialist is scoped to the wrapped writers and stays narrow. The shim validates write Cypher against the live schema, stamps provenance, and rejects unknown labels/edges. Account isolation is per-brand Neo4j; the shim adds no query-layer tenant filter."
|
|
31
31
|
---
|
|
32
32
|
|
|
33
33
|
# graph
|