@rubytech/create-maxy-code 0.1.551 → 0.1.553
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/package.json +1 -1
- package/payload/platform/lib/routine-templates/dist/index.d.ts +15 -2
- package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/routine-templates/dist/index.js +25 -1
- package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
- package/payload/platform/lib/routine-templates/dist/roster.d.ts +76 -1
- package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -1
- package/payload/platform/lib/routine-templates/dist/roster.js +60 -3
- package/payload/platform/lib/routine-templates/dist/roster.js.map +1 -1
- package/payload/platform/lib/routine-templates/src/__tests__/roster.test.ts +93 -1
- package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +74 -0
- package/payload/platform/lib/routine-templates/src/index.ts +25 -1
- package/payload/platform/lib/routine-templates/src/roster.ts +111 -2
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +11 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +54 -4
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +113 -6
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.js +216 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/fires-per-day.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/fires-per-day.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/fires-per-day.test.js +44 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/fires-per-day.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js +128 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-classification.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-classification.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-classification.test.js +92 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-classification.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js +135 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js +210 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.js +18 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.d.ts +86 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.js +240 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/fires-per-day.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/fires-per-day.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/fires-per-day.js +47 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/fires-per-day.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts +92 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js +193 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts +8 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts +152 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js +472 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.js +11 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js +146 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +27 -0
- 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 +18 -0
- 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.js +146 -64
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js +84 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-waiver.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-waiver.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-waiver.test.js +200 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-waiver.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +238 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-recurrence-record.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-recurrence-record.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-recurrence-record.test.js +208 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-recurrence-record.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +291 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts +26 -2
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js +51 -2
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +71 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +109 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +3 -1
- 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 +124 -3
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +148 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +410 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/package.json +1 -0
- package/payload/platform/plugins/workflows/skills/workflow-manager/SKILL.md +4 -0
- 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 +1 -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/pty-census.d.ts +51 -4
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.js +60 -10
- package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +64 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +189 -6
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-Bjcs9ta9.js → AdminLoginScreens-Ca2QhL18.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-Ca2QhL18.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-Ca2QhL18.js.gz +0 -0
- package/payload/server/public/assets/{AdminShell-DVBAMUCy.js → AdminShell-Dc5du5xX.js} +1 -1
- package/payload/server/public/assets/AdminShell-Dc5du5xX.js.br +0 -0
- package/payload/server/public/assets/AdminShell-Dc5du5xX.js.gz +0 -0
- package/payload/server/public/assets/{activity-C8E8-EwQ.js → activity-TSDKmIYb.js} +1 -1
- package/payload/server/public/assets/activity-TSDKmIYb.js.br +0 -0
- package/payload/server/public/assets/activity-TSDKmIYb.js.gz +0 -0
- package/payload/server/public/assets/{admin-CeWAlpRS.js → admin-Ba729Eyg.js} +1 -1
- package/payload/server/public/assets/admin-Ba729Eyg.js.br +0 -0
- package/payload/server/public/assets/admin-Ba729Eyg.js.gz +0 -0
- package/payload/server/public/assets/{agents-DuN92iFb.js → agents-9hlQHOS5.js} +1 -1
- package/payload/server/public/assets/agents-9hlQHOS5.js.br +0 -0
- package/payload/server/public/assets/agents-9hlQHOS5.js.gz +0 -0
- package/payload/server/public/assets/{browser-BxebdKbh.js → browser-BrqoCsqh.js} +1 -1
- package/payload/server/public/assets/browser-BrqoCsqh.js.br +0 -0
- package/payload/server/public/assets/browser-BrqoCsqh.js.gz +0 -0
- package/payload/server/public/assets/{calendar-BtFZQ5Np.js → calendar-DOObQ_m8.js} +1 -1
- package/payload/server/public/assets/calendar-DOObQ_m8.js.br +0 -0
- package/payload/server/public/assets/calendar-DOObQ_m8.js.gz +0 -0
- package/payload/server/public/assets/chat-Xaul2lLF.js +1 -0
- package/payload/server/public/assets/chat-Xaul2lLF.js.br +3 -0
- package/payload/server/public/assets/chat-Xaul2lLF.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-BYH5vH4S.js +1 -0
- package/payload/server/public/assets/chevron-left-BYH5vH4S.js.br +0 -0
- package/payload/server/public/assets/chevron-right-XxQPHfNH.js +1 -0
- package/payload/server/public/assets/chevron-right-XxQPHfNH.js.br +0 -0
- package/payload/server/public/assets/clock-BNp2JTKh.js +1 -0
- package/payload/server/public/assets/clock-BNp2JTKh.js.br +0 -0
- package/payload/server/public/assets/clock-BNp2JTKh.js.gz +0 -0
- package/payload/server/public/assets/{copy-DCDHXq3O.js → copy-BkbgmpJo.js} +1 -1
- package/payload/server/public/assets/copy-BkbgmpJo.js.br +0 -0
- package/payload/server/public/assets/copy-BkbgmpJo.js.gz +0 -0
- package/payload/server/public/assets/data-yuHj8g0C.js +1 -0
- package/payload/server/public/assets/data-yuHj8g0C.js.br +3 -0
- package/payload/server/public/assets/data-yuHj8g0C.js.gz +0 -0
- package/payload/server/public/assets/{file-text-zQgEQWCF.js → file-text-DDCrqbWB.js} +1 -1
- package/payload/server/public/assets/file-text-DDCrqbWB.js.br +0 -0
- package/payload/server/public/assets/file-text-DDCrqbWB.js.gz +0 -0
- package/payload/server/public/assets/{graph-CFCEh_YI.js → graph-BKPdL5ph.js} +1 -1
- package/payload/server/public/assets/graph-BKPdL5ph.js.br +0 -0
- package/payload/server/public/assets/graph-BKPdL5ph.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-CA1UcDuS.js → graph-labels-B2PrHK6O.js} +1 -1
- package/payload/server/public/assets/graph-labels-B2PrHK6O.js.br +0 -0
- package/payload/server/public/assets/graph-labels-B2PrHK6O.js.gz +0 -0
- package/payload/server/public/assets/{operator-hFXPDc2X.js → operator-BVULmN09.js} +1 -1
- package/payload/server/public/assets/operator-BVULmN09.js.br +0 -0
- package/payload/server/public/assets/operator-BVULmN09.js.gz +0 -0
- package/payload/server/public/assets/{page-ne7I_r9L.js → page-BbzdSxuU.js} +1 -1
- package/payload/server/public/assets/page-BbzdSxuU.js.br +0 -0
- package/payload/server/public/assets/page-BbzdSxuU.js.gz +0 -0
- package/payload/server/public/assets/{page-C8jyPH_3.js → page-baQ9FQIg.js} +1 -1
- package/payload/server/public/assets/page-baQ9FQIg.js.br +0 -0
- package/payload/server/public/assets/page-baQ9FQIg.js.gz +0 -0
- package/payload/server/public/assets/play-3hlqzxLI.js +1 -0
- package/payload/server/public/assets/play-3hlqzxLI.js.br +0 -0
- package/payload/server/public/assets/play-3hlqzxLI.js.gz +0 -0
- package/payload/server/public/assets/{public-BgnqjAal.js → public-B_SEBxWy.js} +1 -1
- package/payload/server/public/assets/public-B_SEBxWy.js.br +0 -0
- package/payload/server/public/assets/public-B_SEBxWy.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-DfJWjXAg.js → rotate-ccw-DzXTXh3g.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-DzXTXh3g.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-DzXTXh3g.js.gz +0 -0
- package/payload/server/public/assets/routines-BQzE0xzC.js +2 -0
- package/payload/server/public/assets/routines-BQzE0xzC.js.br +0 -0
- package/payload/server/public/assets/routines-BQzE0xzC.js.gz +0 -0
- package/payload/server/public/assets/{skills-D-6EbgfP.js → skills-yCZhYVIs.js} +1 -1
- package/payload/server/public/assets/skills-yCZhYVIs.js.br +0 -0
- package/payload/server/public/assets/skills-yCZhYVIs.js.gz +0 -0
- package/payload/server/public/assets/{tasks-DnN5Fyds.js → tasks-DxNFLsHv.js} +1 -1
- package/payload/server/public/assets/tasks-DxNFLsHv.js.br +0 -0
- package/payload/server/public/assets/tasks-DxNFLsHv.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-eh9UOKDT.js → triangle-alert-CYwHTcvU.js} +1 -1
- package/payload/server/public/assets/triangle-alert-CYwHTcvU.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-CYwHTcvU.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-Bz9OJHBb.js → useCopyFeedback-BCRjKc-H.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-BCRjKc-H.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-BCRjKc-H.js.gz +0 -0
- package/payload/server/public/assets/{useMediaQuery-CNxsjKZp.css → useMediaQuery-Vxpamxqq.css} +1 -1
- package/payload/server/public/assets/useMediaQuery-Vxpamxqq.css.br +0 -0
- package/payload/server/public/assets/{useMediaQuery-CNxsjKZp.css.gz → useMediaQuery-Vxpamxqq.css.gz} +0 -0
- package/payload/server/public/assets/{useVoiceRecorder-BaDpMYxX.js → useVoiceRecorder-BaAtMu9T.js} +1 -1
- package/payload/server/public/assets/useVoiceRecorder-BaAtMu9T.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BaAtMu9T.js.gz +0 -0
- package/payload/server/public/assets/{wrench-BO-l46rF.js → wrench-iI-vx-g2.js} +1 -1
- package/payload/server/public/assets/wrench-iI-vx-g2.js.br +0 -0
- package/payload/server/public/assets/wrench-iI-vx-g2.js.gz +0 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +8 -8
- 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 +36 -5
- package/payload/server/public/assets/AdminLoginScreens-Bjcs9ta9.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-Bjcs9ta9.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-DVBAMUCy.js.br +0 -0
- package/payload/server/public/assets/AdminShell-DVBAMUCy.js.gz +0 -0
- package/payload/server/public/assets/activity-C8E8-EwQ.js.br +0 -0
- package/payload/server/public/assets/activity-C8E8-EwQ.js.gz +0 -0
- package/payload/server/public/assets/admin-CeWAlpRS.js.br +0 -0
- package/payload/server/public/assets/admin-CeWAlpRS.js.gz +0 -0
- package/payload/server/public/assets/agents-DuN92iFb.js.br +0 -0
- package/payload/server/public/assets/agents-DuN92iFb.js.gz +0 -0
- package/payload/server/public/assets/browser-BxebdKbh.js.br +0 -0
- package/payload/server/public/assets/browser-BxebdKbh.js.gz +0 -0
- package/payload/server/public/assets/calendar-BtFZQ5Np.js.br +0 -0
- package/payload/server/public/assets/calendar-BtFZQ5Np.js.gz +0 -0
- package/payload/server/public/assets/chat-CGIwynaH.js +0 -1
- package/payload/server/public/assets/chat-CGIwynaH.js.br +0 -3
- package/payload/server/public/assets/chat-CGIwynaH.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-DaNP1zDb.js +0 -1
- package/payload/server/public/assets/chevron-left-DaNP1zDb.js.br +0 -0
- package/payload/server/public/assets/chevron-right-BbbuTUnt.js +0 -1
- package/payload/server/public/assets/chevron-right-BbbuTUnt.js.br +0 -0
- package/payload/server/public/assets/clock-CfDdatjp.js +0 -1
- package/payload/server/public/assets/clock-CfDdatjp.js.br +0 -0
- package/payload/server/public/assets/clock-CfDdatjp.js.gz +0 -0
- package/payload/server/public/assets/copy-DCDHXq3O.js.br +0 -0
- package/payload/server/public/assets/copy-DCDHXq3O.js.gz +0 -0
- package/payload/server/public/assets/data-BchSWOju.js +0 -1
- package/payload/server/public/assets/data-BchSWOju.js.br +0 -1
- package/payload/server/public/assets/data-BchSWOju.js.gz +0 -0
- package/payload/server/public/assets/file-text-zQgEQWCF.js.br +0 -0
- package/payload/server/public/assets/file-text-zQgEQWCF.js.gz +0 -0
- package/payload/server/public/assets/graph-CFCEh_YI.js.br +0 -0
- package/payload/server/public/assets/graph-CFCEh_YI.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-CA1UcDuS.js.br +0 -0
- package/payload/server/public/assets/graph-labels-CA1UcDuS.js.gz +0 -0
- package/payload/server/public/assets/operator-hFXPDc2X.js.br +0 -0
- package/payload/server/public/assets/operator-hFXPDc2X.js.gz +0 -0
- package/payload/server/public/assets/page-C8jyPH_3.js.br +0 -0
- package/payload/server/public/assets/page-C8jyPH_3.js.gz +0 -0
- package/payload/server/public/assets/page-ne7I_r9L.js.br +0 -0
- package/payload/server/public/assets/page-ne7I_r9L.js.gz +0 -0
- package/payload/server/public/assets/play-Ep0SZTbr.js +0 -1
- package/payload/server/public/assets/play-Ep0SZTbr.js.br +0 -0
- package/payload/server/public/assets/play-Ep0SZTbr.js.gz +0 -0
- package/payload/server/public/assets/public-BgnqjAal.js.br +0 -0
- package/payload/server/public/assets/public-BgnqjAal.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-DfJWjXAg.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-DfJWjXAg.js.gz +0 -0
- package/payload/server/public/assets/routines-OGFXwTFO.js +0 -2
- package/payload/server/public/assets/routines-OGFXwTFO.js.br +0 -0
- package/payload/server/public/assets/routines-OGFXwTFO.js.gz +0 -0
- package/payload/server/public/assets/skills-D-6EbgfP.js.br +0 -0
- package/payload/server/public/assets/skills-D-6EbgfP.js.gz +0 -0
- package/payload/server/public/assets/tasks-DnN5Fyds.js.br +0 -0
- package/payload/server/public/assets/tasks-DnN5Fyds.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-eh9UOKDT.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-eh9UOKDT.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-Bz9OJHBb.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-Bz9OJHBb.js.gz +0 -0
- package/payload/server/public/assets/useMediaQuery-CNxsjKZp.css.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BaDpMYxX.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BaDpMYxX.js.gz +0 -0
- package/payload/server/public/assets/wrench-BO-l46rF.js.br +0 -0
- package/payload/server/public/assets/wrench-BO-l46rF.js.gz +0 -0
- /package/payload/server/public/assets/{useMediaQuery-_mMln3Fu.js → useMediaQuery-Baf4Huvv.js} +0 -0
- /package/payload/server/public/assets/{useMediaQuery-_mMln3Fu.js.br → useMediaQuery-Baf4Huvv.js.br} +0 -0
- /package/payload/server/public/assets/{useMediaQuery-_mMln3Fu.js.gz → useMediaQuery-Baf4Huvv.js.gz} +0 -0
|
@@ -166,7 +166,20 @@ export async function resolveOwnerTimezone(
|
|
|
166
166
|
* destroy the operator's own caption on the next version bump; and the
|
|
167
167
|
* scheduling plugin's one-shot `converge-routine-description` has already given
|
|
168
168
|
* every pre-existing row a description, so no row is left holding only a
|
|
169
|
-
* title.
|
|
169
|
+
* title.
|
|
170
|
+
*
|
|
171
|
+
* The gate (Task 2379) is written by a `SET` after the MERGE rather than inside
|
|
172
|
+
* either branch, because both branches need `e.eventId` and only one of them
|
|
173
|
+
* has just minted it. `writing` is the same condition the prompt follows, so a
|
|
174
|
+
* gate is written on create, refreshed on a version bump of an unedited row,
|
|
175
|
+
* and never touched on an operator-edited one.
|
|
176
|
+
*
|
|
177
|
+
* Two details are load-bearing. The null branch is symmetric on purpose: a
|
|
178
|
+
* template reclassified gateless must have its three properties cleared, or the
|
|
179
|
+
* row keeps a gate its template no longer declares and nothing reports it. And
|
|
180
|
+
* the commit token is substituted from the same `row.gateChecks` the gate is
|
|
181
|
+
* built from, so the precondition and the turn's own commit call cannot
|
|
182
|
+
* disagree about what this routine checks. */
|
|
170
183
|
export const MERGE_CYPHER = `
|
|
171
184
|
UNWIND $rows AS row
|
|
172
185
|
OPTIONAL MATCH (pre:Event {accountId: $accountId, routineTemplate: row.key})
|
|
@@ -202,6 +215,13 @@ MERGE (e:Event {accountId: $accountId, routineTemplate: row.key})
|
|
|
202
215
|
e.description = CASE WHEN refreshing THEN row.description ELSE e.description END,
|
|
203
216
|
e.routineTemplateVersion = CASE WHEN refreshing THEN $version ELSE e.routineTemplateVersion END,
|
|
204
217
|
e.updatedAt = CASE WHEN refreshing THEN $now ELSE e.updatedAt END
|
|
218
|
+
WITH row, e, existed, priorVersion, edited, (NOT existed OR refreshing) AS writing
|
|
219
|
+
SET e.gatePlugin = CASE WHEN writing THEN (CASE WHEN row.gateChecks IS NULL THEN null ELSE 'scheduling' END) ELSE e.gatePlugin END,
|
|
220
|
+
e.gateTool = CASE WHEN writing THEN (CASE WHEN row.gateChecks IS NULL THEN null ELSE 'scheduling-gate' END) ELSE e.gateTool END,
|
|
221
|
+
e.gateArgs = CASE WHEN writing THEN (CASE WHEN row.gateChecks IS NULL THEN null ELSE '{"eventId":"' + e.eventId + '","checks":' + row.gateChecks + '}' END) ELSE e.gateArgs END,
|
|
222
|
+
e.agentPrompt = CASE WHEN writing AND row.gateChecks IS NOT NULL
|
|
223
|
+
THEN replace(e.agentPrompt, '<<gateCommit>>', '{"eventId":"' + e.eventId + '","checks":' + row.gateChecks + ',"commit":true}')
|
|
224
|
+
ELSE e.agentPrompt END
|
|
205
225
|
RETURN row.key AS key,
|
|
206
226
|
e.eventId AS eventId,
|
|
207
227
|
CASE WHEN NOT existed THEN 'create'
|
|
@@ -277,6 +297,10 @@ export async function seedRoutineRoster(args: SeedRoutineRosterArgs): Promise<Ro
|
|
|
277
297
|
description: r.description,
|
|
278
298
|
prompt: r.prompt,
|
|
279
299
|
recurrence: r.recurrence,
|
|
300
|
+
// Task 2379 — the only place the checks are serialised. Null for a
|
|
301
|
+
// gateless template, which the MERGE reads as "clear the three gate
|
|
302
|
+
// properties on the write path".
|
|
303
|
+
gateChecks: r.gate ? JSON.stringify(r.gate.checks) : null,
|
|
280
304
|
})),
|
|
281
305
|
});
|
|
282
306
|
|
|
@@ -35,7 +35,76 @@
|
|
|
35
35
|
* routine's prompt only when the stored `routineTemplateVersion` is BELOW this
|
|
36
36
|
* and no operator has edited the row, so an unbumped edit ships to new installs
|
|
37
37
|
* and never reaches existing ones — bump it in the same commit as the edit. */
|
|
38
|
-
export const ROUTINE_TEMPLATE_VERSION =
|
|
38
|
+
export const ROUTINE_TEMPLATE_VERSION = 3;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Task 2379 — the check shapes a shipped template may name.
|
|
42
|
+
*
|
|
43
|
+
* Declared here rather than imported from the scheduling plugin:
|
|
44
|
+
* `routine-templates` is a `platform/lib` package and importing from
|
|
45
|
+
* `platform/plugins` would invert the dependency direction and break the lib
|
|
46
|
+
* build, which has no plugin on its path. The roster test reads the tool's own
|
|
47
|
+
* source and asserts every type used here is one the tool declares, which is the
|
|
48
|
+
* same drift control this file already applies to tool names.
|
|
49
|
+
*/
|
|
50
|
+
export interface RoutineMailCheck {
|
|
51
|
+
readonly type: "mail";
|
|
52
|
+
readonly mailbox: string;
|
|
53
|
+
readonly folder?: string;
|
|
54
|
+
readonly subjectPrefixes?: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
export interface RoutineD1Check {
|
|
57
|
+
readonly type: "d1";
|
|
58
|
+
readonly database: string;
|
|
59
|
+
readonly sql: string;
|
|
60
|
+
}
|
|
61
|
+
export interface RoutineWhatsappCheck {
|
|
62
|
+
readonly type: "whatsapp";
|
|
63
|
+
readonly scope: "inbound" | "status";
|
|
64
|
+
}
|
|
65
|
+
export interface RoutineCalendarCheck {
|
|
66
|
+
readonly type: "calendar";
|
|
67
|
+
readonly connector: string;
|
|
68
|
+
}
|
|
69
|
+
export type RoutineGateCheck =
|
|
70
|
+
| RoutineMailCheck
|
|
71
|
+
| RoutineD1Check
|
|
72
|
+
| RoutineWhatsappCheck
|
|
73
|
+
| RoutineCalendarCheck;
|
|
74
|
+
|
|
75
|
+
/** The plugin and the tool are constants for every roster gate, so a template
|
|
76
|
+
* carries neither. `hasWork` is true when ANY check is true. */
|
|
77
|
+
export interface RoutineGate {
|
|
78
|
+
readonly checks: readonly RoutineGateCheck[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Substituted by the seed with the full `scheduling-gate` argument object,
|
|
82
|
+
* including the row's own event id.
|
|
83
|
+
*
|
|
84
|
+
* A template cannot write that id: it is minted by the MERGE. Nor can the turn
|
|
85
|
+
* supply it, because the only place a scheduled turn sees its own event is the
|
|
86
|
+
* provenance preamble, which carries the first eight characters. The watermark
|
|
87
|
+
* file is named from the whole id, so a turn committing against the prefix
|
|
88
|
+
* would write a file the next gate never reads and the routine would report
|
|
89
|
+
* work for ever. */
|
|
90
|
+
export const GATE_COMMIT_TOKEN = "<<gateCommit>>";
|
|
91
|
+
|
|
92
|
+
/** True when the check compares against a stored position rather than reading a
|
|
93
|
+
* current one.
|
|
94
|
+
*
|
|
95
|
+
* A watermarked check reads its stored value or zero, so with no watermark file
|
|
96
|
+
* it reports work on every fire until something commits: such a gate is inert
|
|
97
|
+
* until its routine's prompt carries the commit line. Mirrors `probe`'s `mark`
|
|
98
|
+
* field in the scheduling plugin's `tools/scheduling-gate.ts`, which is a
|
|
99
|
+
* function for exactly these three and null for the other two. */
|
|
100
|
+
export function isWatermarkedCheck(check: RoutineGateCheck): boolean {
|
|
101
|
+
if (check.type === "whatsapp") return check.scope === "inbound";
|
|
102
|
+
return check.type === "mail" || check.type === "calendar";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function gateNeedsCommit(gate: RoutineGate): boolean {
|
|
106
|
+
return gate.checks.some(isWatermarkedCheck);
|
|
107
|
+
}
|
|
39
108
|
|
|
40
109
|
export interface RoutineTemplate {
|
|
41
110
|
/** Stable identity. Half of the MERGE key `(accountId, routineTemplate)`, so
|
|
@@ -54,6 +123,31 @@ export interface RoutineTemplate {
|
|
|
54
123
|
readonly recurrence: string;
|
|
55
124
|
/** The injected turn. Refreshed on a version bump when the row is unedited. */
|
|
56
125
|
readonly prompt: string;
|
|
126
|
+
/** Task 2379 — the precondition, present when this routine's work is
|
|
127
|
+
* conditional AND the check vocabulary can express its source without
|
|
128
|
+
* per-account data. Seeded onto `gatePlugin`/`gateTool`/`gateArgs` under the
|
|
129
|
+
* same rule the prompt follows: written on create, refreshed only on a
|
|
130
|
+
* version bump of an unedited row. */
|
|
131
|
+
readonly gate?: RoutineGate;
|
|
132
|
+
/** Why this template ships with no gate. Exactly one of `gate` and `gateless`
|
|
133
|
+
* is present on every template, and the roster test fails one carrying
|
|
134
|
+
* neither, so a template added later cannot skip the decision. A field rather
|
|
135
|
+
* than a comment because a comment cannot be asserted. */
|
|
136
|
+
readonly gateless?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** A template is classified when it carries exactly one of `gate` and a
|
|
140
|
+
* non-empty `gateless`.
|
|
141
|
+
*
|
|
142
|
+
* The rule lives here, in one function, rather than inline in the assertion
|
|
143
|
+
* that walks the roster. A predicate the test only ever feeds passing input
|
|
144
|
+
* reports clean while checking nothing, so the roster test asserts this both
|
|
145
|
+
* ways: true for all thirteen, and false for a template carrying neither, both
|
|
146
|
+
* and an empty reason. */
|
|
147
|
+
export function isClassified(template: RoutineTemplate): boolean {
|
|
148
|
+
const gated = template.gate != null;
|
|
149
|
+
const declaredGateless = typeof template.gateless === "string" && template.gateless.trim() !== "";
|
|
150
|
+
return gated !== declaredGateless;
|
|
57
151
|
}
|
|
58
152
|
|
|
59
153
|
const START_OF_DAY = `Prepare the operator's morning briefing and reply with it as a single message.
|
|
@@ -128,7 +222,9 @@ An appointment on the provider calendar with no counterpart in the graph is invi
|
|
|
128
222
|
|
|
129
223
|
If the account has a busy-time sync configured, run schedule-connector-busy-sync so availability reflects what was just reconciled.
|
|
130
224
|
|
|
131
|
-
Reply only when something was actually changed, naming what and why. A clean reconcile ends the turn without replying, every time. This routine runs often, so a message on every clean run trains the operator to ignore it
|
|
225
|
+
Reply only when something was actually changed, naming what and why. A clean reconcile ends the turn without replying, every time. This routine runs often, so a message on every clean run trains the operator to ignore it.
|
|
226
|
+
|
|
227
|
+
When the run has finished and nothing failed, call scheduling-gate with <<gateCommit>> so the next run does not re-examine what you have just reconciled. Do not call it after a run that failed or that you stopped early: the work would be marked as handled without anyone having seen it.`;
|
|
132
228
|
|
|
133
229
|
const CONTACT_RECONCILE = `Reconcile the people the business actually deals with against the people it has recorded.
|
|
134
230
|
|
|
@@ -231,6 +327,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
231
327
|
"Sends one morning message covering what needs a decision, what is time-bound and what changed overnight; stays silent when there is nothing worth saying.",
|
|
232
328
|
recurrence: "30 7 * * 1-5",
|
|
233
329
|
prompt: START_OF_DAY,
|
|
330
|
+
gateless: "A morning briefing is judgement every fire, so a gate would either always pass or suppress the briefing.",
|
|
234
331
|
},
|
|
235
332
|
{
|
|
236
333
|
key: "inbound-check",
|
|
@@ -239,6 +336,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
239
336
|
"Triages mail that arrived since the last check and writes replies for you to approve; it never sends anything itself.",
|
|
240
337
|
recurrence: "*/30 9-17 * * 1-5",
|
|
241
338
|
prompt: INBOUND_CHECK,
|
|
339
|
+
gateless: "Conditional on new mail, but the mail check matches an exact mailbox address and no shipped template can name one (Task 2389).",
|
|
242
340
|
},
|
|
243
341
|
{
|
|
244
342
|
key: "end-of-day",
|
|
@@ -247,6 +345,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
247
345
|
"Closes the day with your unsent drafts, overdue work and tomorrow's diary; says nothing when all three are empty.",
|
|
248
346
|
recurrence: "0 18 * * 1-5",
|
|
249
347
|
prompt: END_OF_DAY,
|
|
348
|
+
gateless: "A wrap-up is judgement every fire, so no cheap check stands in for reading the three surfaces it reports.",
|
|
250
349
|
},
|
|
251
350
|
{
|
|
252
351
|
key: "appointment-reminder",
|
|
@@ -255,6 +354,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
255
354
|
"Reminds each attendee once before a booked appointment, on a route they already use; it never opens a conversation with a stranger.",
|
|
256
355
|
recurrence: "0 * * * *",
|
|
257
356
|
prompt: APPOINTMENT_REMINDER,
|
|
357
|
+
gateless: "Conditional on an appointment falling in the window, but no check type reads appointments held in the graph (Task 2389).",
|
|
258
358
|
},
|
|
259
359
|
{
|
|
260
360
|
key: "commitment-chase",
|
|
@@ -263,6 +363,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
263
363
|
"Surfaces promises the business made and did not keep, and promises made to it that nobody kept; it reports them rather than chasing them.",
|
|
264
364
|
recurrence: "0 10 * * 2",
|
|
265
365
|
prompt: COMMITMENT_CHASE,
|
|
366
|
+
gateless: "Whether a promise has been dropped is the judgement itself, so no check can answer it more cheaply.",
|
|
266
367
|
},
|
|
267
368
|
{
|
|
268
369
|
key: "calendar-reconcile",
|
|
@@ -271,6 +372,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
271
372
|
"Reconciles the provider calendar against the appointments held in the graph, writing what is missing and marking what was cancelled; a clean run says nothing.",
|
|
272
373
|
recurrence: "*/15 * * * *",
|
|
273
374
|
prompt: CALENDAR_RECONCILE,
|
|
375
|
+
gate: { checks: [{ type: "calendar", connector: "account" }] },
|
|
274
376
|
},
|
|
275
377
|
{
|
|
276
378
|
key: "contact-reconcile",
|
|
@@ -279,6 +381,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
279
381
|
"Reconciles the people you correspond with against the people on record, creating and updating contacts; it never merges two records that might be two people.",
|
|
280
382
|
recurrence: "45 6 * * 1-5",
|
|
281
383
|
prompt: CONTACT_RECONCILE,
|
|
384
|
+
gateless: "Conditional on new correspondence, but no check type reads correspondence (Task 2389).",
|
|
282
385
|
},
|
|
283
386
|
{
|
|
284
387
|
key: "crm-reconcile",
|
|
@@ -287,6 +390,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
287
390
|
"Brings across what changed in whichever operational system this business runs; it reads only and never writes back to that system.",
|
|
288
391
|
recurrence: "0 3 * * *",
|
|
289
392
|
prompt: CRM_RECONCILE,
|
|
393
|
+
gateless: "Conditional on connector movement, but no check type reads a connector queue (Task 2389).",
|
|
290
394
|
},
|
|
291
395
|
{
|
|
292
396
|
key: "finance-reconcile",
|
|
@@ -295,6 +399,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
295
399
|
"Reconciles the accounting system against the internal ledger and reports what does not agree; it never adjusts an entry to make the two sides match.",
|
|
296
400
|
recurrence: "0 4 * * 1",
|
|
297
401
|
prompt: FINANCE_RECONCILE,
|
|
402
|
+
gateless: "Conditional on accounting movement, but no check type reads an accounting ledger (Task 2389).",
|
|
298
403
|
},
|
|
299
404
|
{
|
|
300
405
|
key: "weekly-digest",
|
|
@@ -303,6 +408,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
303
408
|
"Writes the week in three paragraphs at most: what changed, what is stuck, and what next week turns on.",
|
|
304
409
|
recurrence: "0 16 * * 5",
|
|
305
410
|
prompt: WEEKLY_DIGEST,
|
|
411
|
+
gateless: "A digest is judgement every fire, and a gate would suppress the report it exists to send.",
|
|
306
412
|
},
|
|
307
413
|
{
|
|
308
414
|
key: "channel-link-check",
|
|
@@ -311,6 +417,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
311
417
|
"Reports a messaging link that has gone down and whether it can recover on its own; it never disconnects a link or starts a re-pair.",
|
|
312
418
|
recurrence: "*/15 * * * *",
|
|
313
419
|
prompt: CHANNEL_LINK_CHECK,
|
|
420
|
+
gate: { checks: [{ type: "whatsapp", scope: "status" }] },
|
|
314
421
|
},
|
|
315
422
|
{
|
|
316
423
|
key: "graph-health",
|
|
@@ -319,6 +426,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
319
426
|
"Reports what the hourly graph sweep last did and what is waiting for review; it reads only and never runs the sweep itself.",
|
|
320
427
|
recurrence: "0 9 * * 1",
|
|
321
428
|
prompt: GRAPH_HEALTH,
|
|
429
|
+
gateless: "Reports the sweep's state every fire by design, so there is nothing conditional to check.",
|
|
322
430
|
},
|
|
323
431
|
{
|
|
324
432
|
key: "filesystem-health",
|
|
@@ -327,6 +435,7 @@ export const ROUTINE_ROSTER: readonly RoutineTemplate[] = [
|
|
|
327
435
|
"Reports what has grown under the account directory and which paths are worth attention; it deletes nothing.",
|
|
328
436
|
recurrence: "0 9 * * 1",
|
|
329
437
|
prompt: FILESYSTEM_HEALTH,
|
|
438
|
+
gateless: "Reports what has grown every fire by design, so there is nothing conditional to check.",
|
|
330
439
|
},
|
|
331
440
|
];
|
|
332
441
|
|
|
@@ -9,6 +9,17 @@ Invoked by the admin agent directly.
|
|
|
9
9
|
|
|
10
10
|
This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
|
|
11
11
|
|
|
12
|
+
## 2026-08-03 (0.1.553)
|
|
13
|
+
|
|
14
|
+
- Each routine now shows how it runs, what its precondition checks, and how often it fired in the last day, so a routine that skipped can be told apart from one that ran quietly.
|
|
15
|
+
- Every built-in routine is now classified as gated or ungated, and a new recurring routine has to answer that question rather than defaulting to waking on the clock.
|
|
16
|
+
- Changing how often a routine runs is now recorded with its cost difference, and the rate across the install is counted on each check.
|
|
17
|
+
|
|
18
|
+
## 2026-08-02 (0.1.552)
|
|
19
|
+
|
|
20
|
+
- A recurring routine can now check a condition first, so the clock alone no longer wakes the assistant when there is nothing to do. A one-time reminder is never gated, since skipping it would lose it.
|
|
21
|
+
- When the device runs short on memory, the largest chat is now stopped before the system starts swapping, rather than after everything has slowed down.
|
|
22
|
+
|
|
12
23
|
## 2026-08-02 (0.1.551)
|
|
13
24
|
|
|
14
25
|
- You can now run as many Telegram bots as you like, each paired to its own agent, with its own access list, secret and conversations kept apart. Existing setups are converted automatically, the sidebar splits by bot, and a standing check reports each one.
|
|
@@ -39,6 +39,10 @@ tools:
|
|
|
39
39
|
publicAllowlist: false
|
|
40
40
|
adminAllowlist: false
|
|
41
41
|
riskClass: write_local
|
|
42
|
+
- name: scheduling-gate
|
|
43
|
+
publicAllowlist: false
|
|
44
|
+
adminAllowlist: true
|
|
45
|
+
riskClass: read
|
|
42
46
|
- name: schedule-connector-busy-sync
|
|
43
47
|
publicAllowlist: false
|
|
44
48
|
adminAllowlist: true
|
|
@@ -101,6 +105,24 @@ is planned; until it ships, the internal calendar plus `.ics` import is the clie
|
|
|
101
105
|
|
|
102
106
|
Automatic connector-to-internal availability sync is separate future work.
|
|
103
107
|
|
|
108
|
+
## Reflecting a connector calendar into the booking page
|
|
109
|
+
|
|
110
|
+
`schedule-connector-busy-sync` replaces this account's stored connector-busy set. Call it with **no
|
|
111
|
+
arguments**: it resolves the account's own authorised connector from its token store, reads the next
|
|
112
|
+
15 days of free/busy from that connector, and syncs. Nothing about that is a judgement, so nothing
|
|
113
|
+
about it needs you — the tool spawns the google or outlook plugin's own free/busy tool to do the
|
|
114
|
+
reading, and it is safe to invoke from a scheduled `action` dispatch with `args: {}`.
|
|
115
|
+
|
|
116
|
+
An account with no authorised connector syncs `connector='none'` with an empty set. That is a
|
|
117
|
+
success, not a failure: it clears a deauthorised connector's stale blocks.
|
|
118
|
+
|
|
119
|
+
A failed read is an error and writes nothing. The stored busy set survives a transient provider
|
|
120
|
+
outage, because publishing the operator's booked time as free is worse than not syncing at all.
|
|
121
|
+
|
|
122
|
+
Passing `connector` and `busy` together is the explicit form and behaves exactly as before. Passing
|
|
123
|
+
`busy` without `connector` is refused, because the connector tag is what a later sync compares
|
|
124
|
+
against and it cannot be guessed from the intervals.
|
|
125
|
+
|
|
104
126
|
## Meetings vs events — what `schedule-event` writes
|
|
105
127
|
|
|
106
128
|
`schedule-event` writes one of two graph nodes, decided from the parameters you pass — never from keywords in the title:
|
|
@@ -264,17 +286,45 @@ Users and plugins both create events with actions via the same `schedule-event`
|
|
|
264
286
|
|
|
265
287
|
## Agent-bearing dispatch
|
|
266
288
|
|
|
267
|
-
|
|
289
|
+
Choosing a dispatch mode is **two questions, not one**: should this fire run *at all*, and what should it *say* once it does. Answer them separately. A routine that needs a model to compose its message very often needs no model at all to decide whether there is anything to compose, and treating those as one question is how a conditional sweep ends up spawning a full agent turn every fifteen minutes to discover there was nothing to do.
|
|
290
|
+
|
|
291
|
+
1. **`action`** — nothing here needs a model. One pre-encoded MCP tool, no reasoning, no spawn.
|
|
292
|
+
2. **`agentDispatch` with a `gate`** — a model is needed to compose or to judge, but only when there is something to act on. **This is the default for anything conditional.** The dispatcher calls the gate tool headlessly before spawning; `hasWork:false` skips the fire entirely.
|
|
293
|
+
3. **`agentDispatch` with no gate** — judgement applies on *every* fire, whatever the state of the world: a daily briefing, a weekly digest. This is the exception, and it is stated as one. A recurring `agentDispatch` with no `gate` is refused unless it carries `gateWaived` naming why.
|
|
294
|
+
|
|
295
|
+
When an agent-bearing event fires, the heartbeat hands the prompt to the destination's native channel: the channel spawns or resumes the deterministic admin session for that phone or chat, the agent runs the prompt with full admin tools, and its answer is delivered back over the account's paired socket. No operator session need be open.
|
|
296
|
+
|
|
297
|
+
The worked example is the second shape. The signature check is a gate; the prompt keeps only the composing and the messaging:
|
|
268
298
|
|
|
269
299
|
```json
|
|
270
|
-
{
|
|
300
|
+
{
|
|
301
|
+
"agentDispatch": {
|
|
302
|
+
"channel": "whatsapp",
|
|
303
|
+
"destination": "+447700900123",
|
|
304
|
+
"prompt": "The tenancy agreement has just been signed. Write the one-line confirmation and send it."
|
|
305
|
+
},
|
|
306
|
+
"gate": {
|
|
307
|
+
"plugin": "scheduling",
|
|
308
|
+
"tool": "scheduling-gate",
|
|
309
|
+
"args": {
|
|
310
|
+
"eventId": "<this event's id>",
|
|
311
|
+
"checks": [{ "kind": "d1", "database": "<the account's database>", "sql": "SELECT count(*) FROM agreements WHERE signed_at > '<watermark>'" }]
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
271
315
|
```
|
|
272
316
|
|
|
317
|
+
Written as one model turn instead — "check whether it is signed yet and, if it is, message me" — the check runs inside the spawned agent, so the turn costs a full agent run on every fire whether or not anything was signed.
|
|
318
|
+
|
|
273
319
|
- `channel` — `whatsapp` or `telegram`.
|
|
274
320
|
- `destination` — the E.164 phone (WhatsApp) or chat id (Telegram) to run the turn for and reply to. It **must already be a registered admin or account-manager** on that channel; a destination that is not is rejected when the event is created. For WhatsApp the authoritative admin/manager lists are read from the socket-owning **house** account (never the event's own account), and the destination's registered account must match the account that created the schedule: an admin belongs to the house, a manager to its bound sub-account. The house/owner account may target any registered admin or manager; a sub-account is locked to its own manager(s). A refusal names the true reason — `not-registered` (in no house list) or `cross-account` (registered to a different account, naming both accounts).
|
|
275
|
-
- `prompt` — the instruction the agent runs.
|
|
321
|
+
- `prompt` — the instruction the agent runs. Write it for the case where there **is** work: the gate has already decided that.
|
|
322
|
+
- `gate` — `{ plugin, tool, args }`. The precondition, evaluated headlessly between the `nextRun` advance and the spawn. It must return `structuredContent {hasWork:boolean}`; the dispatcher reads that boolean and nothing else. A gate that errors, times out or returns no boolean **dispatches anyway**, so a broken check never silently mutes a routine. Requires a complete `agentDispatch`, and is refused on an action-mode event.
|
|
323
|
+
- `gateWaived` — a string reason, and the only way to create a recurring `agentDispatch` with no gate. Give the reason this routine has judgement to apply on every fire ("the morning briefing composes from scratch each day"). It is stored on the event and counted by the standing gate-classification check, so a deliberate exception stays readable and the fleet's drift back to ungated is a number someone can look at. A conditional sweep is not an exception; gate it.
|
|
324
|
+
|
|
325
|
+
**Recurring only.** A one-time agent event is never gated — its row is already `due` by the time the dispatcher sees it, so a skip there would drop the routine for good — and it is therefore outside the refusal. `schedule-update` takes no waiver either: an existing routine keeps what it has until it is edited.
|
|
276
326
|
|
|
277
|
-
An event carries **either** an `action` **or** an `agentDispatch`, never both — setting one clears the other. Agent dispatch is stored as `agentChannel`, `agentDestination`, `agentPrompt`. Use `schedule-update` with `agentDispatch` to modify or `agentDispatch: null` to clear. On WhatsApp, a destination bound as an account-manager scopes the spawned session to its sub-account — unless that same phone is also on the house `adminPhones` list, in which case it scopes to the channel account, because the dispatch rides the house socket and so arrives from the house number. On Telegram the session runs on the house account.
|
|
327
|
+
An event carries **either** an `action` **or** an `agentDispatch`, never both — setting one clears the other. Agent dispatch is stored as `agentChannel`, `agentDestination`, `agentPrompt`; the gate as `gatePlugin`, `gateTool`, `gateArgs`; the waiver as `gateWaived`. Use `schedule-update` with `agentDispatch` to modify or `agentDispatch: null` to clear. On WhatsApp, a destination bound as an account-manager scopes the spawned session to its sub-account — unless that same phone is also on the house `adminPhones` list, in which case it scopes to the channel account, because the dispatch rides the house socket and so arrives from the house number. On Telegram the session runs on the house account.
|
|
278
328
|
|
|
279
329
|
A WhatsApp `agentDispatch` destination may therefore be an account manager, not only an admin phone — this is the supported way to send a manager a proactive or scheduled WhatsApp message. The reply is delivered over the house's paired line regardless of which sub-account the session is scoped to; socket ownership never restricts which number the dispatch can reach.
|
|
280
330
|
|
|
@@ -3,6 +3,7 @@ import { eagerTool } from "../../../../lib/mcp-eager/dist/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { scheduleEvent } from "./tools/schedule-event.js";
|
|
6
|
+
import { runSchedulingGate } from "./tools/scheduling-gate.js";
|
|
6
7
|
import { scheduleList } from "./tools/schedule-list.js";
|
|
7
8
|
import { scheduleGet } from "./tools/schedule-get.js";
|
|
8
9
|
import { scheduleRuns } from "./tools/schedule-runs.js";
|
|
@@ -12,9 +13,11 @@ import { scheduleCancel } from "./tools/schedule-cancel.js";
|
|
|
12
13
|
import { scheduleExportIcs } from "./tools/schedule-export-ics.js";
|
|
13
14
|
import { scheduleImportIcs } from "./tools/schedule-import-ics.js";
|
|
14
15
|
import { scheduleArchiveIcs } from "./tools/schedule-archive-ics.js";
|
|
15
|
-
import { syncConnectorBusy } from "./tools/schedule-connector-busy-sync.js";
|
|
16
|
+
import { resolveAndSyncConnectorBusy, syncConnectorBusy, } from "./tools/schedule-connector-busy-sync.js";
|
|
17
|
+
import { resolveConnector, spawnProviderCall } from "./lib/connector-freebusy.js";
|
|
16
18
|
import { closeDriver, getUserTimezone, getSession } from "./lib/neo4j.js";
|
|
17
19
|
import { formatCombined } from "./lib/time-format.js";
|
|
20
|
+
import { join, resolve } from "node:path";
|
|
18
21
|
// Cast to `any` to avoid TS2589. TypeScript hits its 50-level instantiation
|
|
19
22
|
// limit when accumulating typed tool signatures on a generic McpServer.
|
|
20
23
|
// Individual tool handlers retain full type safety in their own files.
|
|
@@ -61,6 +64,17 @@ const eventStatusSchema = z
|
|
|
61
64
|
.enum(EVENT_STATUSES)
|
|
62
65
|
.optional()
|
|
63
66
|
.describe("Event status. 'suspended' pauses a routine: it stops firing and stays paused until it is set back to 'scheduled', which resumes it at its next natural occurrence. 'cancelled' is not set here — use schedule-cancel, which ends the series permanently.");
|
|
67
|
+
// Task 2378 — the precondition, shared by both write tools so the create path
|
|
68
|
+
// and the edit path cannot describe it differently to the agent.
|
|
69
|
+
const gateShape = {
|
|
70
|
+
plugin: z.string().describe("Plugin exposing the gate tool (e.g. 'scheduling')"),
|
|
71
|
+
tool: z.string().describe("MCP tool to call before the routine fires. It must return structuredContent {hasWork:boolean}."),
|
|
72
|
+
args: z.record(z.unknown()).optional().describe("Arguments passed to the gate tool verbatim"),
|
|
73
|
+
};
|
|
74
|
+
const GATE_DESCRIPTION = "Precondition: before this routine spawns an agent turn, the dispatcher calls this tool headlessly and reads structuredContent.hasWork. False skips the turn entirely; true dispatches as normal. A gate that errors, times out or returns no boolean dispatches anyway, so a broken gate never silently mutes the routine. Requires a complete agentDispatch and is refused on an action-mode event.";
|
|
75
|
+
// Task 2383 — the declared exception, on the create path only. `schedule-update`
|
|
76
|
+
// takes no waiver: an existing routine keeps what it has until it is edited.
|
|
77
|
+
const GATE_WAIVED_DESCRIPTION = "The declared reason this recurring routine runs a model on every fire with no gate. A daily briefing or a weekly digest composes from scratch each time and has judgement to apply whether or not anything changed; a conditional sweep does not, and belongs behind a gate. Required when a recurring agentDispatch carries no gate — the write is refused without one. Stored on the event and counted by the standing gate-classification check, so every deliberate exception stays readable.";
|
|
64
78
|
// ---------------------------------------------------------------------------
|
|
65
79
|
// schedule-event
|
|
66
80
|
// ---------------------------------------------------------------------------
|
|
@@ -88,6 +102,8 @@ eagerTool(server, "schedule-event", "Create a scheduled event or appointment. A
|
|
|
88
102
|
destination: z.string().describe("E.164 phone (WhatsApp) or Telegram chat id — must already be a registered admin/account-manager on that channel"),
|
|
89
103
|
prompt: z.string().describe("The instruction the agent runs when the event fires"),
|
|
90
104
|
}).optional().describe("Agent-bearing dispatch: when the event fires, spawns or resumes the admin channel session for `destination` and runs `prompt` with full admin tools, delivering the answer back over the channel. Mutually exclusive with `action`. Always records an event, never a meeting."),
|
|
105
|
+
gate: z.object(gateShape).optional().describe(GATE_DESCRIPTION),
|
|
106
|
+
gateWaived: z.string().optional().describe(GATE_WAIVED_DESCRIPTION),
|
|
91
107
|
}, async (params) => {
|
|
92
108
|
if (!accountId)
|
|
93
109
|
return refuseNoAccount("schedule-event");
|
|
@@ -126,6 +142,10 @@ eagerTool(server, "schedule-event", "Create a scheduled event or appointment. A
|
|
|
126
142
|
parts.push(`action: ${params.action.plugin}/${params.action.tool}`);
|
|
127
143
|
if (params.agentDispatch)
|
|
128
144
|
parts.push(`agent dispatch: ${params.agentDispatch.channel} → ${params.agentDispatch.destination}`);
|
|
145
|
+
if (params.gate)
|
|
146
|
+
parts.push(`gate: ${params.gate.plugin}/${params.gate.tool}`);
|
|
147
|
+
if (params.gateWaived)
|
|
148
|
+
parts.push(`gate waived: ${params.gateWaived}`);
|
|
129
149
|
return { content: [{ type: "text", text: parts.join(", ") }] };
|
|
130
150
|
}
|
|
131
151
|
catch (err) {
|
|
@@ -138,24 +158,61 @@ eagerTool(server, "schedule-event", "Create a scheduled event or appointment. A
|
|
|
138
158
|
// ---------------------------------------------------------------------------
|
|
139
159
|
// schedule-connector-busy-sync (Task 1654)
|
|
140
160
|
// ---------------------------------------------------------------------------
|
|
141
|
-
eagerTool(server, "schedule-connector-busy-sync", "Reflect your calendar connector's busy blocks into your booking page availability. Call
|
|
142
|
-
connector: z.enum(["google", "m365", "none"]).describe("Which calendar connector these busy blocks came from, or 'none' if no connector is authorised"),
|
|
161
|
+
eagerTool(server, "schedule-connector-busy-sync", "Reflect your calendar connector's busy blocks into your booking page availability. Call it with NO arguments and it resolves the account's own authorised connector, reads the next 15 days of free/busy itself, and syncs — that is the normal use and needs no reading step from you. Passing connector and busy together is the explicit form: connector='google' or 'm365' with the list of {start,end} ISO 8601 busy intervals, or connector='none' and busy=[] to clear stale blocks. Wholesale-replaces the stored connector-busy set for this account.", {
|
|
162
|
+
connector: z.enum(["google", "m365", "none"]).optional().describe("Which calendar connector these busy blocks came from, or 'none' if no connector is authorised. Omit together with busy to have the tool resolve the account's own connector."),
|
|
143
163
|
busy: z.array(z.object({
|
|
144
164
|
start: z.string().describe("ISO 8601 busy-block start"),
|
|
145
165
|
end: z.string().describe("ISO 8601 busy-block end"),
|
|
146
|
-
})).describe("Busy intervals read from the connector. Empty when connector='none'."),
|
|
166
|
+
})).optional().describe("Busy intervals read from the connector. Empty when connector='none'. Omit to have the tool read the next 15 days itself."),
|
|
147
167
|
}, async (params) => {
|
|
148
168
|
if (!accountId)
|
|
149
169
|
return refuseNoAccount("schedule-connector-busy-sync");
|
|
170
|
+
// Both halves are refused rather than defaulted, and for the same reason:
|
|
171
|
+
// either one alone would be silently reinterpreted. A busy list with no
|
|
172
|
+
// connector would have to guess where the intervals came from, and that tag
|
|
173
|
+
// is what a later sync compares against. A connector with no busy list would
|
|
174
|
+
// be discarded in favour of whatever the account actually holds, so a caller
|
|
175
|
+
// naming the wrong connector would be told nothing and get a different one.
|
|
176
|
+
if (Boolean(params.busy) !== Boolean(params.connector)) {
|
|
177
|
+
const passed = params.busy ? "busy" : "connector";
|
|
178
|
+
const missing = params.busy ? "connector" : "busy";
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: `schedule-connector-busy-sync: ${passed} was passed without ${missing}. Pass both, or neither to have the tool resolve and read the account's own connector.` }],
|
|
181
|
+
isError: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
150
184
|
const session = getSession();
|
|
151
185
|
try {
|
|
152
|
-
|
|
186
|
+
let r;
|
|
187
|
+
if (params.busy && params.connector) {
|
|
188
|
+
r = await syncConnectorBusy(session, accountId, { connector: params.connector, busy: params.busy });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const platformRoot = process.env.PLATFORM_ROOT ?? process.cwd();
|
|
192
|
+
const accountsDir = resolve(platformRoot, "..", "data/accounts");
|
|
193
|
+
// Resolved twice — here and inside resolveAndSyncConnectorBusy — because
|
|
194
|
+
// spawnProviderCall builds the provider environment eagerly and throws
|
|
195
|
+
// when the brand ships no vendor app. An account with no connector at all
|
|
196
|
+
// must reach the clean empty sync instead.
|
|
197
|
+
const { connector } = resolveConnector(join(accountsDir, accountId));
|
|
198
|
+
r = await resolveAndSyncConnectorBusy(session, accountId, {
|
|
199
|
+
accountsDir,
|
|
200
|
+
platformRoot,
|
|
201
|
+
now: new Date(),
|
|
202
|
+
call: connector === "none"
|
|
203
|
+
? async () => { throw new Error("no calendar connector is authorised for this account"); }
|
|
204
|
+
: spawnProviderCall(platformRoot, accountId, connector),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
153
207
|
process.stderr.write(`[calendar-availability] op=cal-sync connector=${r.connector} busyBlocks=${r.busyBlocks} reflected=${r.reflected}\n`);
|
|
154
208
|
return { content: [{ type: "text", text: `Synced ${r.reflected} connector busy block(s) from ${r.connector}.` }] };
|
|
155
209
|
}
|
|
156
210
|
catch (err) {
|
|
157
211
|
const reason = err instanceof Error ? err.message : String(err);
|
|
158
|
-
|
|
212
|
+
// `resolved` rather than `undefined` when the caller passed no connector:
|
|
213
|
+
// the connector on this line is what the caller supplied, and the resolved
|
|
214
|
+
// value is already on the `[connector-busy-sync] op=` lines above.
|
|
215
|
+
process.stderr.write(`[calendar-availability] op=cal-sync-fail connector=${params.connector ?? "resolved"} reason=${JSON.stringify(reason)}\n`);
|
|
159
216
|
return { content: [{ type: "text", text: `Connector busy sync failed: ${reason}` }], isError: true };
|
|
160
217
|
}
|
|
161
218
|
finally {
|
|
@@ -325,6 +382,7 @@ eagerTool(server, "schedule-update", "Update an event's properties or append a n
|
|
|
325
382
|
destination: z.string().describe("E.164 phone (WhatsApp) or Telegram chat id — must already be a registered admin/account-manager on that channel"),
|
|
326
383
|
prompt: z.string().describe("The instruction the agent runs when the event fires"),
|
|
327
384
|
}).nullable().optional().describe("Set/replace the agent-bearing dispatch, or pass null to clear it. Mutually exclusive with action."),
|
|
385
|
+
gate: z.object(gateShape).nullable().optional().describe(`${GATE_DESCRIPTION} Pass null to clear it.`),
|
|
328
386
|
}, async (params) => {
|
|
329
387
|
if (!accountId)
|
|
330
388
|
return refuseNoAccount("schedule-update");
|
|
@@ -346,6 +404,55 @@ eagerTool(server, "schedule-update", "Update an event's properties or append a n
|
|
|
346
404
|
}
|
|
347
405
|
});
|
|
348
406
|
// ---------------------------------------------------------------------------
|
|
407
|
+
// scheduling-gate (Task 2378)
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
eagerTool(server, "scheduling-gate", "Precondition check for a scheduled routine: answers whether any of the named sources has new work, without running a turn. The dispatcher calls this headlessly before firing a gated routine and reads structuredContent.hasWork. Call it yourself with commit=true as the LAST step of a successful routine run, with the same eventId and checks, to advance the watermarks past what you just handled. Never call commit=true on a run that failed or that you did not finish — the work would be marked consumed and the next fire would skip it.", {
|
|
410
|
+
eventId: z.string().describe("The gated routine's event ID. Names the watermark file, so it must match the binding."),
|
|
411
|
+
checks: z.array(z.record(z.unknown())).describe("Checks to run. mail {type,mailbox,folder?,subjectPrefixes?} · d1 {type,database,sql} · whatsapp {type,scope:'inbound'|'status'} · calendar {type,connector}. hasWork is true when ANY check is true."),
|
|
412
|
+
commit: z.boolean().optional().describe("Advance every watermark named in checks to what the sources hold now. Only the routine's own turn sets this, at the end of a successful run."),
|
|
413
|
+
}, async (params) => {
|
|
414
|
+
if (!accountId)
|
|
415
|
+
return refuseNoAccount("scheduling-gate");
|
|
416
|
+
const platformRoot = process.env.PLATFORM_ROOT;
|
|
417
|
+
if (!platformRoot) {
|
|
418
|
+
// Refused rather than defaulted: a guessed root would put the watermark
|
|
419
|
+
// file somewhere the next call would not look, and the routine would
|
|
420
|
+
// report work for ever.
|
|
421
|
+
return {
|
|
422
|
+
content: [{ type: "text", text: "scheduling-gate cannot execute: PLATFORM_ROOT is not set." }],
|
|
423
|
+
isError: true,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
const result = await runSchedulingGate({ eventId: params.eventId, checks: params.checks, commit: params.commit }, accountId, platformRoot);
|
|
428
|
+
// structuredContent is what the dispatcher reads; the text is for a human
|
|
429
|
+
// or an agent reading the transcript and is never parsed for control flow.
|
|
430
|
+
// No outputSchema is declared: the MCP server validates structuredContent
|
|
431
|
+
// only when a tool has one, so this passes to the wire untouched, and
|
|
432
|
+
// declaring one would mean changing the shared eagerTool signature.
|
|
433
|
+
return {
|
|
434
|
+
content: [
|
|
435
|
+
{
|
|
436
|
+
type: "text",
|
|
437
|
+
text: result.advanced
|
|
438
|
+
? `hasWork=${result.hasWork} reasons=[${result.reasons.join(", ")}] advanced=[${result.advanced.join(", ")}]`
|
|
439
|
+
: `hasWork=${result.hasWork} reasons=[${result.reasons.join(", ")}]`,
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
structuredContent: result,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
// A throw here reaches the dispatcher as an unreadable gate, which fails
|
|
447
|
+
// OPEN and dispatches. That is the safe direction and is why this does not
|
|
448
|
+
// try to synthesise a hasWork value it did not measure.
|
|
449
|
+
return {
|
|
450
|
+
content: [{ type: "text", text: `Gate check failed: ${err instanceof Error ? err.message : String(err)}` }],
|
|
451
|
+
isError: true,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
349
456
|
// schedule-cancel
|
|
350
457
|
// ---------------------------------------------------------------------------
|
|
351
458
|
eagerTool(server, "schedule-cancel", "Cancel a scheduled event. For recurring events, this cancels the entire series. Idempotent — cancelling an already-cancelled event is a no-op. Passing a meeting uid deletes that meeting and its attendee links, the same as deleting it on the Calendar page.", {
|