@rubytech/create-realagent-code 0.1.572 → 0.1.574
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/config/brand.json +2 -1
- package/payload/platform/lib/admins-write/__tests__/index.test.ts +97 -0
- package/payload/platform/lib/admins-write/dist/index.d.ts +12 -0
- package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/admins-write/dist/index.js +27 -5
- package/payload/platform/lib/admins-write/dist/index.js.map +1 -1
- package/payload/platform/lib/admins-write/src/index.ts +35 -5
- package/payload/platform/lib/admins-write/vitest.config.ts +14 -0
- package/payload/platform/lib/dispatch-read/dist/session.d.ts.map +1 -1
- package/payload/platform/lib/dispatch-read/dist/session.js +19 -4
- package/payload/platform/lib/dispatch-read/dist/session.js.map +1 -1
- package/payload/platform/lib/dispatch-read/src/__tests__/session.test.ts +91 -0
- package/payload/platform/lib/dispatch-read/src/session.ts +25 -6
- package/payload/platform/lib/routine-templates/dist/index.d.ts +2 -2
- package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/routine-templates/dist/index.js +9 -1
- package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
- package/payload/platform/lib/routine-templates/src/index.ts +14 -2
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +8 -3
- package/payload/platform/plugins/admin/hooks/__tests__/html-structure-gate.test.sh +264 -0
- package/payload/platform/plugins/admin/hooks/__tests__/quote-render-pdf-conformance.test.sh +122 -24
- package/payload/platform/plugins/admin/hooks/__tests__/specialist-dispatch-gate.test.sh +158 -0
- package/payload/platform/plugins/admin/hooks/html-structure-gate.sh +165 -0
- package/payload/platform/plugins/admin/hooks/quote-render-pdf-conformance.sh +111 -19
- package/payload/platform/plugins/admin/hooks/specialist-dispatch-gate.sh +159 -0
- package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts +27 -0
- package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js +29 -0
- package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts +11 -0
- package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/binding-validate.js +20 -0
- package/payload/platform/plugins/admin/mcp/dist/binding-validate.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +137 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +32 -2
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +192 -90
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +18 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +71 -31
- package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +50 -57
- package/payload/platform/plugins/docs/references/internals.md +6 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +31 -1
- package/payload/platform/plugins/docs/references/telegram-guide.md +23 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
- package/payload/platform/plugins/email/PLUGIN.md +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +36 -5
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +10 -5
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +12 -6
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +18 -8
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +41 -10
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
- package/payload/platform/plugins/email/references/email-reference.md +12 -0
- package/payload/platform/plugins/outlook/PLUGIN.md +3 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js +177 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js +130 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js +162 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js +168 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts +64 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js +110 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts +119 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js +0 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts +8 -0
- package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js +247 -0
- package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js +10 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +21 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +16 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +5 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +4 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +2 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +24 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +2 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js +29 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js.map +1 -1
- package/payload/platform/plugins/scheduling/PLUGIN.md +4 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js +21 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts +13 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js +10 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +54 -0
- 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-gate-write.test.js +78 -5
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +133 -2
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +11 -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 +30 -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.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +8 -0
- 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.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +37 -6
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
- package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts +32 -0
- package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js +144 -0
- package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js.map +1 -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 +3 -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/html-structure-census.d.ts +44 -0
- package/payload/platform/services/claude-session-manager/dist/html-structure-census.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/html-structure-census.js +185 -0
- package/payload/platform/services/claude-session-manager/dist/html-structure-census.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +32 -5
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +125 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +12 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +13 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js +7 -0
- package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts +42 -0
- package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/quote-print-census.js +254 -0
- package/payload/platform/services/claude-session-manager/dist/quote-print-census.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts +92 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-census.js +227 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-census.js.map +1 -0
- package/payload/platform/services/telegram-channel/dist/jsonl.d.ts +8 -0
- package/payload/platform/services/telegram-channel/dist/jsonl.d.ts.map +1 -0
- package/payload/platform/services/telegram-channel/dist/jsonl.js +42 -0
- package/payload/platform/services/telegram-channel/dist/jsonl.js.map +1 -0
- package/payload/platform/services/telegram-channel/dist/notification.d.ts +0 -31
- package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/notification.js +4 -33
- package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/server.d.ts +0 -5
- package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/server.js +7 -101
- package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +0 -66
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +2 -64
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts +0 -12
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +15 -268
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/templates/account-settings.json +8 -4
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/agents.html +5 -5
- package/payload/server/public/assets/{AdminLoginScreens-CMdj1W5p.js → AdminLoginScreens-Dp6k1XgD.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.gz +0 -0
- package/payload/server/public/assets/{AdminShell-CZcaW3BQ.js → AdminShell-D-wB90c3.js} +1 -1
- package/payload/server/public/assets/AdminShell-D-wB90c3.js.br +0 -0
- package/payload/server/public/assets/AdminShell-D-wB90c3.js.gz +0 -0
- package/payload/server/public/assets/{activity-CAhibgb-.js → activity-BJxhblz-.js} +1 -1
- package/payload/server/public/assets/activity-BJxhblz-.js.br +0 -0
- package/payload/server/public/assets/activity-BJxhblz-.js.gz +0 -0
- package/payload/server/public/assets/{admin-xEXv3w9e.js → admin-DzEi8shs.js} +1 -1
- package/payload/server/public/assets/admin-DzEi8shs.js.br +0 -0
- package/payload/server/public/assets/admin-DzEi8shs.js.gz +0 -0
- package/payload/server/public/assets/{agents-CiAmzTaT.js → agents-DTL8kjfD.js} +1 -1
- package/payload/server/public/assets/agents-DTL8kjfD.js.br +0 -0
- package/payload/server/public/assets/agents-DTL8kjfD.js.gz +0 -0
- package/payload/server/public/assets/{browser-D36BiYhS.js → browser-UZp75zhx.js} +1 -1
- package/payload/server/public/assets/browser-UZp75zhx.js.br +0 -0
- package/payload/server/public/assets/browser-UZp75zhx.js.gz +0 -0
- package/payload/server/public/assets/{calendar-CJAM-iXn.js → calendar-Dfo0O2K_.js} +1 -1
- package/payload/server/public/assets/calendar-Dfo0O2K_.js.br +0 -0
- package/payload/server/public/assets/calendar-Dfo0O2K_.js.gz +0 -0
- package/payload/server/public/assets/chat-ByJAvEWZ.js +1 -0
- package/payload/server/public/assets/chat-ByJAvEWZ.js.br +0 -0
- package/payload/server/public/assets/chat-ByJAvEWZ.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-iG7sYNm7.js +1 -0
- package/payload/server/public/assets/chevron-left-iG7sYNm7.js.br +0 -0
- package/payload/server/public/assets/clock-xSs0UF10.js +1 -0
- package/payload/server/public/assets/clock-xSs0UF10.js.br +0 -0
- package/payload/server/public/assets/clock-xSs0UF10.js.gz +0 -0
- package/payload/server/public/assets/{copy-axBe92iY.js → copy-CGI2jioK.js} +1 -1
- package/payload/server/public/assets/copy-CGI2jioK.js.br +0 -0
- package/payload/server/public/assets/copy-CGI2jioK.js.gz +0 -0
- package/payload/server/public/assets/data-dvwK4BcP.js +1 -0
- package/payload/server/public/assets/data-dvwK4BcP.js.br +0 -0
- package/payload/server/public/assets/data-dvwK4BcP.js.gz +0 -0
- package/payload/server/public/assets/{dispatch-D_i2ko7K.js → dispatch-Dx9-ty1K.js} +1 -1
- package/payload/server/public/assets/dispatch-Dx9-ty1K.js.br +0 -0
- package/payload/server/public/assets/dispatch-Dx9-ty1K.js.gz +0 -0
- package/payload/server/public/assets/{field-DFgkqq1P.js → field-D-P6CsWA.js} +1 -1
- package/payload/server/public/assets/field-D-P6CsWA.js.br +0 -0
- package/payload/server/public/assets/field-D-P6CsWA.js.gz +0 -0
- package/payload/server/public/assets/{file-text-CEFoq0Je.js → file-text-RWintp9q.js} +1 -1
- package/payload/server/public/assets/file-text-RWintp9q.js.br +0 -0
- package/payload/server/public/assets/file-text-RWintp9q.js.gz +0 -0
- package/payload/server/public/assets/{graph-BrpbilNr.js → graph-CpBlfYrJ.js} +1 -1
- package/payload/server/public/assets/graph-CpBlfYrJ.js.br +0 -0
- package/payload/server/public/assets/graph-CpBlfYrJ.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-BtmcWzo7.js → graph-labels-BOMyPppX.js} +1 -1
- package/payload/server/public/assets/graph-labels-BOMyPppX.js.br +0 -0
- package/payload/server/public/assets/graph-labels-BOMyPppX.js.gz +0 -0
- package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css +1 -0
- package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.br +0 -0
- package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.gz +0 -0
- package/payload/server/public/assets/{operator-Dl7dJt1G.js → operator-DFgW6GiH.js} +1 -1
- package/payload/server/public/assets/operator-DFgW6GiH.js.br +0 -0
- package/payload/server/public/assets/operator-DFgW6GiH.js.gz +0 -0
- package/payload/server/public/assets/{page-nx31Ij0T.js → page-BRShHSH0.js} +1 -1
- package/payload/server/public/assets/page-BRShHSH0.js.br +0 -0
- package/payload/server/public/assets/page-BRShHSH0.js.gz +0 -0
- package/payload/server/public/assets/{page-zC66xzNT.js → page-BbBCu9iP.js} +5 -5
- package/payload/server/public/assets/page-BbBCu9iP.js.br +0 -0
- package/payload/server/public/assets/page-BbBCu9iP.js.gz +0 -0
- package/payload/server/public/assets/play-BJxUaf6-.js +1 -0
- package/payload/server/public/assets/play-BJxUaf6-.js.br +0 -0
- package/payload/server/public/assets/play-BJxUaf6-.js.gz +0 -0
- package/payload/server/public/assets/{public-DEKnH5Q0.js → public-DkObqkzL.js} +1 -1
- package/payload/server/public/assets/public-DkObqkzL.js.br +0 -0
- package/payload/server/public/assets/public-DkObqkzL.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-CBex8gon.js → rotate-ccw-DgMO69nh.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.gz +0 -0
- package/payload/server/public/assets/routines-D9kvg_Xu.js +2 -0
- package/payload/server/public/assets/routines-D9kvg_Xu.js.br +0 -0
- package/payload/server/public/assets/routines-D9kvg_Xu.js.gz +0 -0
- package/payload/server/public/assets/{skills-BmxjPOmq.js → skills-l5I9HZ3D.js} +1 -1
- package/payload/server/public/assets/skills-l5I9HZ3D.js.br +0 -0
- package/payload/server/public/assets/skills-l5I9HZ3D.js.gz +0 -0
- package/payload/server/public/assets/{tasks-BTFYO9vT.js → tasks-C2qJMv6i.js} +1 -1
- package/payload/server/public/assets/tasks-C2qJMv6i.js.br +0 -0
- package/payload/server/public/assets/tasks-C2qJMv6i.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-px1YRJJ7.js → triangle-alert-BnC67uSs.js} +1 -1
- package/payload/server/public/assets/triangle-alert-BnC67uSs.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-BnC67uSs.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-nMXwRcDX.js → useCopyFeedback-1RmgkTFh.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.gz +0 -0
- package/payload/server/public/assets/{useMediaQuery-DjbOTk2X.js → useMediaQuery-BQslddHQ.js} +1 -1
- package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.gz +0 -0
- package/payload/server/public/assets/{useVoiceRecorder-Cx7AOmib.js → useVoiceRecorder-BeZNQEC4.js} +1 -1
- package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.gz +0 -0
- package/payload/server/public/assets/{wrench-Cexvlx6J.js → wrench-D4ufQG6C.js} +1 -1
- package/payload/server/public/assets/wrench-D4ufQG6C.js.br +0 -0
- package/payload/server/public/assets/wrench-D4ufQG6C.js.gz +0 -0
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +8 -8
- package/payload/server/public/chat.html +14 -14
- package/payload/server/public/data.html +12 -12
- package/payload/server/public/dispatch.html +5 -5
- package/payload/server/public/field.html +3 -3
- package/payload/server/public/graph.html +10 -10
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +16 -16
- package/payload/server/public/public.html +14 -14
- package/payload/server/public/routines.html +8 -8
- package/payload/server/public/skills.html +6 -6
- package/payload/server/public/tasks.html +7 -7
- package/payload/server/server.js +1088 -694
- package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.br +0 -0
- package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.gz +0 -0
- package/payload/server/public/assets/activity-CAhibgb-.js.br +0 -0
- package/payload/server/public/assets/activity-CAhibgb-.js.gz +0 -0
- package/payload/server/public/assets/admin-xEXv3w9e.js.br +0 -0
- package/payload/server/public/assets/admin-xEXv3w9e.js.gz +0 -0
- package/payload/server/public/assets/agents-CiAmzTaT.js.br +0 -0
- package/payload/server/public/assets/agents-CiAmzTaT.js.gz +0 -0
- package/payload/server/public/assets/browser-D36BiYhS.js.br +0 -0
- package/payload/server/public/assets/browser-D36BiYhS.js.gz +0 -0
- package/payload/server/public/assets/calendar-CJAM-iXn.js.br +0 -0
- package/payload/server/public/assets/calendar-CJAM-iXn.js.gz +0 -0
- package/payload/server/public/assets/chat-DhZSvtZK.js +0 -1
- package/payload/server/public/assets/chat-DhZSvtZK.js.br +0 -0
- package/payload/server/public/assets/chat-DhZSvtZK.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-CXEcNlc3.js +0 -1
- package/payload/server/public/assets/chevron-left-CXEcNlc3.js.br +0 -0
- package/payload/server/public/assets/clock-DdxIJwTr.js +0 -1
- package/payload/server/public/assets/clock-DdxIJwTr.js.br +0 -0
- package/payload/server/public/assets/clock-DdxIJwTr.js.gz +0 -0
- package/payload/server/public/assets/copy-axBe92iY.js.br +0 -0
- package/payload/server/public/assets/copy-axBe92iY.js.gz +0 -0
- package/payload/server/public/assets/data-DUvkI-zq.js +0 -1
- package/payload/server/public/assets/data-DUvkI-zq.js.br +0 -0
- package/payload/server/public/assets/data-DUvkI-zq.js.gz +0 -0
- package/payload/server/public/assets/dispatch-D_i2ko7K.js.br +0 -0
- package/payload/server/public/assets/dispatch-D_i2ko7K.js.gz +0 -0
- package/payload/server/public/assets/field-DFgkqq1P.js.br +0 -0
- package/payload/server/public/assets/field-DFgkqq1P.js.gz +0 -0
- package/payload/server/public/assets/file-text-CEFoq0Je.js.br +0 -0
- package/payload/server/public/assets/file-text-CEFoq0Je.js.gz +0 -0
- package/payload/server/public/assets/graph-BrpbilNr.js.br +0 -0
- package/payload/server/public/assets/graph-BrpbilNr.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-BtmcWzo7.js.br +0 -0
- package/payload/server/public/assets/graph-labels-BtmcWzo7.js.gz +0 -0
- package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css +0 -1
- package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.br +0 -0
- package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.gz +0 -0
- package/payload/server/public/assets/operator-Dl7dJt1G.js.br +0 -3
- package/payload/server/public/assets/operator-Dl7dJt1G.js.gz +0 -0
- package/payload/server/public/assets/page-nx31Ij0T.js.br +0 -0
- package/payload/server/public/assets/page-nx31Ij0T.js.gz +0 -0
- package/payload/server/public/assets/page-zC66xzNT.js.br +0 -0
- package/payload/server/public/assets/page-zC66xzNT.js.gz +0 -0
- package/payload/server/public/assets/play-MbU8O2iC.js +0 -1
- package/payload/server/public/assets/play-MbU8O2iC.js.br +0 -0
- package/payload/server/public/assets/play-MbU8O2iC.js.gz +0 -0
- package/payload/server/public/assets/public-DEKnH5Q0.js.br +0 -0
- package/payload/server/public/assets/public-DEKnH5Q0.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-CBex8gon.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-CBex8gon.js.gz +0 -0
- package/payload/server/public/assets/routines-BqCmbvFJ.js +0 -2
- package/payload/server/public/assets/routines-BqCmbvFJ.js.br +0 -0
- package/payload/server/public/assets/routines-BqCmbvFJ.js.gz +0 -0
- package/payload/server/public/assets/skills-BmxjPOmq.js.br +0 -0
- package/payload/server/public/assets/skills-BmxjPOmq.js.gz +0 -0
- package/payload/server/public/assets/tasks-BTFYO9vT.js.br +0 -0
- package/payload/server/public/assets/tasks-BTFYO9vT.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.gz +0 -0
- package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.gz +0 -0
- package/payload/server/public/assets/wrench-Cexvlx6J.js.br +0 -0
- package/payload/server/public/assets/wrench-Cexvlx6J.js.gz +0 -0
- /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js → jsx-runtime-5lmxFBIw.js} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.br → jsx-runtime-5lmxFBIw.js.br} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.gz → jsx-runtime-5lmxFBIw.js.gz} +0 -0
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
# P. a block names the headerless page numbers
|
|
30
30
|
# Q. a running footer repeating the ref does NOT count as a header
|
|
31
31
|
# R. @page :first declared before @page still yields the marker
|
|
32
|
+
#
|
|
33
|
+
# Running-image geometry (only a document declaring an image margin box):
|
|
34
|
+
# S. running image within a third of the page width → ALLOWED
|
|
35
|
+
# T. running image wider than a third → BLOCKED
|
|
36
|
+
# U. an oversized image on ONE page only (content photo) → ALLOWED
|
|
37
|
+
# W. source declares no image margin box → ALLOWED (never gated)
|
|
38
|
+
# V. pdfimages unusable → ALLOWED, and the skip says reason=no-poppler
|
|
39
|
+
# X. the link stage's non-gated return names its reason rather than going quiet
|
|
32
40
|
|
|
33
41
|
set -u
|
|
34
42
|
|
|
@@ -59,39 +67,66 @@ mk_job() { # jobId, html_body, pdf_uri_repeat
|
|
|
59
67
|
> "$DOCS/quote-$job.pdf"
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
# A minimal,
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# network, deterministic
|
|
68
|
-
|
|
70
|
+
# A minimal, multi-page PDF. Each page argument is "<top>|<bottom>|<images>";
|
|
71
|
+
# the bottom line is optional and stands in for a running footer, and images is
|
|
72
|
+
# an optional ";"-separated list of "<pixel width>,<pixel height>,<rendered pt>".
|
|
73
|
+
# The /URI stubs above are not readable by pdftotext, and the header check needs
|
|
74
|
+
# real per-page text at real positions, so the header fixtures need a genuine
|
|
75
|
+
# PDF. Pure stdlib — no third-party generator, no network, deterministic
|
|
76
|
+
# byte-for-byte.
|
|
77
|
+
#
|
|
78
|
+
# An image is drawn through a transform matrix, so its rendered size on the page
|
|
79
|
+
# is independent of its pixel size: that is what makes `pdfimages -list` report a
|
|
80
|
+
# controlled x-ppi, and rendered width = pixel width / x-ppi * 72 is exactly what
|
|
81
|
+
# the geometry check asserts. The image object is created once per (width,height)
|
|
82
|
+
# and referenced from every page carrying it, which is what a running header is
|
|
83
|
+
# in a real print and what makes "present on every checked page" testable.
|
|
84
|
+
mk_pdf() { # out_path, then one "top|bottom|W,H,pt;W,H,pt" argument per page
|
|
69
85
|
local out="$1"; shift
|
|
70
86
|
python3 - "$out" "$@" <<'PY'
|
|
71
|
-
import sys
|
|
72
|
-
out,
|
|
87
|
+
import sys, zlib
|
|
88
|
+
out, specs = sys.argv[1], sys.argv[2:]
|
|
73
89
|
objs = []
|
|
74
|
-
|
|
75
|
-
objs.append(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
def add(b):
|
|
91
|
+
objs.append(b if isinstance(b, bytes) else b.encode("latin-1"))
|
|
92
|
+
return len(objs)
|
|
93
|
+
add("<< /Type /Catalog /Pages 2 0 R >>")
|
|
94
|
+
add("PLACEHOLDER") # 2: /Pages, patched once the kid object numbers are known
|
|
95
|
+
add("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
|
96
|
+
kids, shared = [], {}
|
|
97
|
+
for spec in specs:
|
|
98
|
+
top, bottom, imgs = (spec.split("|") + ["", ""])[:3]
|
|
80
99
|
body = "BT /F1 11 Tf 72 760 Td (%s) Tj ET" % top
|
|
81
100
|
if bottom:
|
|
82
101
|
body += " BT /F1 8 Tf 72 40 Td (%s) Tj ET" % bottom
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
res, xo = "/Font << /F1 3 0 R >>", []
|
|
103
|
+
for k, one in enumerate(x for x in imgs.split(";") if x):
|
|
104
|
+
ws, hs, pts = one.split(",")
|
|
105
|
+
w, h, pt = int(ws), int(hs), float(pts)
|
|
106
|
+
if (w, h) not in shared:
|
|
107
|
+
comp = zlib.compress(bytes([200, 30, 30]) * (w * h))
|
|
108
|
+
shared[(w, h)] = add(
|
|
109
|
+
b"<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB "
|
|
110
|
+
b"/BitsPerComponent 8 /Filter /FlateDecode /Length %d >>\nstream\n"
|
|
111
|
+
% (w, h, len(comp)) + comp + b"\nendstream")
|
|
112
|
+
xo.append("/Im%d %d 0 R" % (k, shared[(w, h)]))
|
|
113
|
+
body += " q %.3f 0 0 %.3f 40 %d cm /Im%d Do Q" % (pt, pt * h / w, 700 - k * 120, k)
|
|
114
|
+
if xo:
|
|
115
|
+
res += " /XObject << %s >>" % " ".join(xo)
|
|
116
|
+
c = add("<< /Length %d >>\nstream\n%s\nendstream" % (len(body), body))
|
|
117
|
+
kids.append(add("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] "
|
|
118
|
+
"/Resources << %s >> /Contents %d 0 R >>" % (res, c)))
|
|
119
|
+
objs[1] = ("<< /Type /Pages /Kids [%s] /Count %d >>"
|
|
120
|
+
% (" ".join("%d 0 R" % k for k in kids), len(kids))).encode("latin-1")
|
|
121
|
+
pdf, offsets = b"%PDF-1.4\n", []
|
|
87
122
|
for i, o in enumerate(objs, start=1):
|
|
88
123
|
offsets.append(len(pdf))
|
|
89
|
-
pdf += "%d 0 obj\n
|
|
124
|
+
pdf += b"%d 0 obj\n" % i + o + b"\nendobj\n"
|
|
90
125
|
start = len(pdf)
|
|
91
|
-
pdf += "xref\n0 %d\n0000000000 65535 f \n" % (len(objs) + 1)
|
|
92
|
-
pdf += "".join("%010d 00000 n \n" % off for off in offsets)
|
|
93
|
-
pdf += "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (len(objs) + 1, start)
|
|
94
|
-
open(out, "wb").write(pdf
|
|
126
|
+
pdf += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objs) + 1)
|
|
127
|
+
pdf += b"".join(b"%010d 00000 n \n" % off for off in offsets)
|
|
128
|
+
pdf += b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (len(objs) + 1, start)
|
|
129
|
+
open(out, "wb").write(pdf)
|
|
95
130
|
PY
|
|
96
131
|
}
|
|
97
132
|
|
|
@@ -192,6 +227,69 @@ run_case "Q: footer repeats the ref, page 3 headerless → BLOCKED" \
|
|
|
192
227
|
run_case "R: @page :first declared first, page 3 headerless → BLOCKED" \
|
|
193
228
|
"$(envelope "$BROWSER" "$DOCS/hdr-first-declared-first.pdf")" 2
|
|
194
229
|
|
|
230
|
+
# ---- running-image geometry ----
|
|
231
|
+
# Chromium paints a page-margin box's content:url() at intrinsic pixel size and
|
|
232
|
+
# does not scale it to the declared box, so a full-resolution logo covers most of
|
|
233
|
+
# the page on every page the box paints. The assertion is scoped to the RUNNING
|
|
234
|
+
# image — the object present on every checked page — because a content photo
|
|
235
|
+
# legitimately runs wider: measured on real quotes, running headers 9.0-18.9% of
|
|
236
|
+
# page width against content photos at 38.7-77.2% and the defect at 88.2%.
|
|
237
|
+
mk_doc_img() { # basename, css, then one "top|bottom|W,H,pt;…" per page
|
|
238
|
+
local base="$1" css="$2"; shift 2
|
|
239
|
+
printf '<html><head>%s</head><body>doc</body></html>' "$css" > "$DOCS/$base.html"
|
|
240
|
+
mk_pdf "$DOCS/$base.pdf" "$@"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# S: 125px at 96ppi = 93.8pt = 15.8% of the 595pt page. The corrected print.
|
|
244
|
+
mk_doc_img "geo-ok" "$HDR_CSS" \
|
|
245
|
+
"cover masthead" "Quote 2468/1 two||125,45,93.8" "Quote 2468/1 three||125,45,93.8"
|
|
246
|
+
# T: 700px at 96ppi = 525.0pt = 88.2%. The measured defect.
|
|
247
|
+
mk_doc_img "geo-wide" "$HDR_CSS" \
|
|
248
|
+
"cover masthead" "Quote 2468/1 two||700,252,525" "Quote 2468/1 three||700,252,525"
|
|
249
|
+
# U: compliant running header plus a one-page 1300px photo at 230pt = 38.7%.
|
|
250
|
+
# A whole-page third-of-width rule blocks this document, and it is legitimate.
|
|
251
|
+
mk_doc_img "geo-photo" "$HDR_CSS" \
|
|
252
|
+
"cover masthead" "Quote 2468/1 two||125,45,93.8;1300,1300,230" "Quote 2468/1 three||125,45,93.8"
|
|
253
|
+
# W: no image margin box declared, so geometry never applies however wide.
|
|
254
|
+
mk_doc_img "geo-nobox" "$NOHDR_CSS" \
|
|
255
|
+
"page one" "page two||700,252,525" "page three||700,252,525"
|
|
256
|
+
|
|
257
|
+
run_case "S: running image within a third of page width → ALLOWED" \
|
|
258
|
+
"$(envelope "$BROWSER" "$DOCS/geo-ok.pdf")" 0
|
|
259
|
+
run_case "T: running image over a third of page width → BLOCKED" \
|
|
260
|
+
"$(envelope "$BROWSER" "$DOCS/geo-wide.pdf")" 2
|
|
261
|
+
run_case "U: an oversized photo on one page only → ALLOWED" \
|
|
262
|
+
"$(envelope "$BROWSER" "$DOCS/geo-photo.pdf")" 0
|
|
263
|
+
run_case "W: no image margin box declared → ALLOWED" \
|
|
264
|
+
"$(envelope "$BROWSER" "$DOCS/geo-nobox.pdf")" 0
|
|
265
|
+
|
|
266
|
+
# V: `pdfimages` alone is unusable. Shadowing pdfinfo too would make the header
|
|
267
|
+
# stage take its own pre-existing no-poppler exit and the geometry stage would
|
|
268
|
+
# never run, so the case would pass without testing anything new. The stub is
|
|
269
|
+
# EXECUTABLE and prints nothing: a non-executable file is skipped by the PATH
|
|
270
|
+
# search rather than failing, so the real binary would still be found. An absent
|
|
271
|
+
# binary and an unusable one converge on the same empty listing.
|
|
272
|
+
STUB="$ACCT/nopdfimages"
|
|
273
|
+
mkdir -p "$STUB"
|
|
274
|
+
printf '#!/bin/sh\nexit 127\n' > "$STUB/pdfimages"
|
|
275
|
+
chmod +x "$STUB/pdfimages"
|
|
276
|
+
V_OUT=$( cd "$ACCT" && PATH="$STUB:$PATH" bash -c 'printf "%s" "$1" | bash "$2" 2>&1 >/dev/null' \
|
|
277
|
+
_ "$(envelope "$BROWSER" "$DOCS/geo-ok.pdf")" "$HOOK" )
|
|
278
|
+
if printf '%s' "$V_OUT" | grep -q 'op=pdf-image-geometry .*reason=no-poppler'; then
|
|
279
|
+
echo "PASS: V: absent pdfimages emits reason=no-poppler"; PASS=$((PASS + 1))
|
|
280
|
+
else
|
|
281
|
+
echo "FAIL: V: geometry no-poppler skip absent (got: $V_OUT)" >&2; FAIL=$((FAIL + 1))
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# X: the link stage returns without inspecting anything on a non-quote path. It
|
|
285
|
+
# did so silently, which is indistinguishable from a gate that never ran at all.
|
|
286
|
+
X_OUT=$( cd "$ACCT" && printf '%s' "$(envelope "$BROWSER" "$DOCS/brochure.pdf")" | bash "$HOOK" 2>&1 >/dev/null )
|
|
287
|
+
if printf '%s' "$X_OUT" | grep -q 'op=skip reason=not-gated'; then
|
|
288
|
+
echo "PASS: X: link stage skip names its reason"; PASS=$((PASS + 1))
|
|
289
|
+
else
|
|
290
|
+
echo "FAIL: X: link skip reason absent (got: $X_OUT)" >&2; FAIL=$((FAIL + 1))
|
|
291
|
+
fi
|
|
292
|
+
|
|
195
293
|
# O: a clean run is distinguishable from a check that never ran.
|
|
196
294
|
STDERR=$( cd "$ACCT" && printf '%s' "$(envelope "$BROWSER" "$DOCS/hdr-ok.pdf")" | bash "$HOOK" 2>&1 >/dev/null )
|
|
197
295
|
if printf '%s' "$STDERR" | grep -q 'op=pdf-header-conformance .*pagesChecked=2 pagesHeaderless=0'; then
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression test for specialist-dispatch-gate.sh.
|
|
3
|
+
#
|
|
4
|
+
# An account's own specialists own their tools: the admin does not do their work
|
|
5
|
+
# by hand. The gate refuses a main-conversation call to a tool one of the
|
|
6
|
+
# account's authored cards declares, and names the specialist to dispatch.
|
|
7
|
+
#
|
|
8
|
+
# Two narrowings are load-bearing and both are asserted here. Only cards the
|
|
9
|
+
# ACCOUNT authored count — provisioning copies every shipped template in, and one
|
|
10
|
+
# of them declares roughly 130 tools including the whole email surface, so
|
|
11
|
+
# reading every card would refuse the admin nearly its entire seat. And native
|
|
12
|
+
# tools are never gated, because every card declares some of them.
|
|
13
|
+
#
|
|
14
|
+
# Caller identity is `agent_type`, which Claude Code sets when a hook fires
|
|
15
|
+
# inside a subagent and leaves empty in the main conversation.
|
|
16
|
+
#
|
|
17
|
+
# Covers:
|
|
18
|
+
# A. main conversation, authored tool → BLOCKED, names the specialist
|
|
19
|
+
# B. same call inside a subagent → ALLOWED (never filters a subagent)
|
|
20
|
+
# C. a native tool the card also declares → ALLOWED
|
|
21
|
+
# D. a tool only a shipped template declares → ALLOWED
|
|
22
|
+
# E. a tool only a premium bundle declares → ALLOWED
|
|
23
|
+
# F. an account with no authored cards → ALLOWED
|
|
24
|
+
# G. empty stdin → ALLOWED
|
|
25
|
+
# H. unparseable envelope → ALLOWED
|
|
26
|
+
# I. an unreadable specialists dir → ALLOWED, op=degraded on stderr
|
|
27
|
+
# J. a card under the ACCOUNT's own plugin root → BLOCKED (still the account's own)
|
|
28
|
+
# K. the refuse line carries the account and the tool
|
|
29
|
+
# L. the native set matches CC_NATIVE_TOOLS in the session manager
|
|
30
|
+
|
|
31
|
+
set -uo pipefail
|
|
32
|
+
HOOKS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
33
|
+
GATE="$HOOKS_DIR/specialist-dispatch-gate.sh"
|
|
34
|
+
DRIFT_SOURCE="$(cd "$HOOKS_DIR/../../../services/claude-session-manager/src" && pwd)/specialist-drift.ts"
|
|
35
|
+
|
|
36
|
+
PASS=0
|
|
37
|
+
FAIL=0
|
|
38
|
+
ok() { PASS=$((PASS + 1)); echo " ok $1"; }
|
|
39
|
+
bad() { FAIL=$((FAIL + 1)); echo " FAIL $1"; }
|
|
40
|
+
|
|
41
|
+
ROOT="$(mktemp -d)"
|
|
42
|
+
trap 'chmod -R u+rwX "$ROOT" 2>/dev/null; rm -rf "$ROOT"' EXIT
|
|
43
|
+
|
|
44
|
+
ACCT="$ROOT/2078cb54aaaa"
|
|
45
|
+
PLATFORM="$ROOT/platform"
|
|
46
|
+
PREMIUM="$ROOT/premium-plugins"
|
|
47
|
+
mkdir -p "$ACCT/specialists/agents" \
|
|
48
|
+
"$PLATFORM/templates/specialists/agents" \
|
|
49
|
+
"$PLATFORM/plugins" \
|
|
50
|
+
"$PREMIUM/sitedesk/agents"
|
|
51
|
+
|
|
52
|
+
card() { # dir file name tools
|
|
53
|
+
mkdir -p "$1"
|
|
54
|
+
printf -- '---\nname: %s\ndescription: "d"\ntools: %s\n---\nbody\n' "$3" "$4" > "$1/$2"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# The account's own clerk: no copy in any shipped root.
|
|
58
|
+
card "$ACCT/specialists/agents" "glsmith--email-clerk.md" "email-clerk" \
|
|
59
|
+
"Read, Bash, mcp__plugin_email_email__email-draft"
|
|
60
|
+
# A shipped template, copied into the account by provisioning.
|
|
61
|
+
card "$ACCT/specialists/agents" "personal-assistant.md" "personal-assistant" \
|
|
62
|
+
"mcp__plugin_whatsapp_whatsapp__whatsapp-send-admin"
|
|
63
|
+
card "$PLATFORM/templates/specialists/agents" "personal-assistant.md" "personal-assistant" \
|
|
64
|
+
"mcp__plugin_whatsapp_whatsapp__whatsapp-send-admin"
|
|
65
|
+
# A premium bundle card, also copied in.
|
|
66
|
+
card "$ACCT/specialists/agents" "sitedesk--quoter.md" "quoter" \
|
|
67
|
+
"mcp__plugin_sitedesk-job_sitedesk-job__quote-render"
|
|
68
|
+
card "$PREMIUM/sitedesk/agents" "sitedesk--quoter.md" "quoter" \
|
|
69
|
+
"mcp__plugin_sitedesk-job_sitedesk-job__quote-render"
|
|
70
|
+
# An authored card whose durable copy lives under the ACCOUNT's own plugin root.
|
|
71
|
+
card "$ACCT/specialists/agents" "glsmith--order-clerk.md" "order-clerk" \
|
|
72
|
+
"mcp__plugin_work_work__work-create"
|
|
73
|
+
card "$ACCT/plugins/glsmith/agents" "glsmith--order-clerk.md" "order-clerk" \
|
|
74
|
+
"mcp__plugin_work_work__work-create"
|
|
75
|
+
|
|
76
|
+
envelope() { printf '{"tool_name":"%s","agent_type":%s,"tool_input":{}}' "$1" "$2"; }
|
|
77
|
+
|
|
78
|
+
# fire <cwd> <json> — sets RC and ERR
|
|
79
|
+
fire() {
|
|
80
|
+
ERR="$(cd "$1" && printf '%s' "$2" | PLATFORM_ROOT="$PLATFORM" bash "$GATE" 2>&1 >/dev/null)"
|
|
81
|
+
RC=$?
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# A
|
|
85
|
+
fire "$ACCT" "$(envelope mcp__plugin_email_email__email-draft '""')"
|
|
86
|
+
if [ "$RC" = 2 ] && printf '%s' "$ERR" | grep -q 'email-clerk'; then
|
|
87
|
+
ok "A blocks the main conversation and names the specialist"
|
|
88
|
+
else bad "A rc=$RC err=$ERR"; fi
|
|
89
|
+
|
|
90
|
+
# B
|
|
91
|
+
fire "$ACCT" "$(envelope mcp__plugin_email_email__email-draft '"specialists:email-clerk"')"
|
|
92
|
+
if [ "$RC" = 0 ]; then ok "B allows the same call inside a subagent"; else bad "B rc=$RC err=$ERR"; fi
|
|
93
|
+
|
|
94
|
+
# C
|
|
95
|
+
fire "$ACCT" "$(envelope Bash '""')"
|
|
96
|
+
if [ "$RC" = 0 ]; then ok "C allows a native tool the card declares"; else bad "C rc=$RC err=$ERR"; fi
|
|
97
|
+
|
|
98
|
+
# D
|
|
99
|
+
fire "$ACCT" "$(envelope mcp__plugin_whatsapp_whatsapp__whatsapp-send-admin '""')"
|
|
100
|
+
if [ "$RC" = 0 ]; then ok "D allows a tool only a shipped template declares"; else bad "D rc=$RC err=$ERR"; fi
|
|
101
|
+
|
|
102
|
+
# E
|
|
103
|
+
fire "$ACCT" "$(envelope mcp__plugin_sitedesk-job_sitedesk-job__quote-render '""')"
|
|
104
|
+
if [ "$RC" = 0 ]; then ok "E allows a tool only a premium bundle declares"; else bad "E rc=$RC err=$ERR"; fi
|
|
105
|
+
|
|
106
|
+
# F
|
|
107
|
+
EMPTY="$ROOT/empty"
|
|
108
|
+
mkdir -p "$EMPTY/specialists/agents"
|
|
109
|
+
fire "$EMPTY" "$(envelope mcp__plugin_email_email__email-draft '""')"
|
|
110
|
+
if [ "$RC" = 0 ]; then ok "F allows on an account with no authored cards"; else bad "F rc=$RC err=$ERR"; fi
|
|
111
|
+
|
|
112
|
+
# G
|
|
113
|
+
fire "$ACCT" ""
|
|
114
|
+
if [ "$RC" = 0 ]; then ok "G allows on empty stdin"; else bad "G rc=$RC err=$ERR"; fi
|
|
115
|
+
|
|
116
|
+
# H
|
|
117
|
+
fire "$ACCT" '{not json'
|
|
118
|
+
if [ "$RC" = 0 ]; then ok "H allows on an unparseable envelope"; else bad "H rc=$RC err=$ERR"; fi
|
|
119
|
+
|
|
120
|
+
# I — failing open is fine; failing open SILENTLY is the fault.
|
|
121
|
+
BROKEN="$ROOT/brokenacct"
|
|
122
|
+
mkdir -p "$BROKEN/specialists/agents"
|
|
123
|
+
chmod 000 "$BROKEN/specialists/agents"
|
|
124
|
+
fire "$BROKEN" "$(envelope mcp__plugin_email_email__email-draft '""')"
|
|
125
|
+
chmod 755 "$BROKEN/specialists/agents"
|
|
126
|
+
if [ "$RC" = 0 ] && printf '%s' "$ERR" | grep -q 'op=degraded'; then
|
|
127
|
+
ok "I fails open and says so"
|
|
128
|
+
else bad "I rc=$RC err=$ERR"; fi
|
|
129
|
+
|
|
130
|
+
# J
|
|
131
|
+
fire "$ACCT" "$(envelope mcp__plugin_work_work__work-create '""')"
|
|
132
|
+
if [ "$RC" = 2 ] && printf '%s' "$ERR" | grep -q 'order-clerk'; then
|
|
133
|
+
ok "J blocks a card whose durable copy is under the account plugin root"
|
|
134
|
+
else bad "J rc=$RC err=$ERR"; fi
|
|
135
|
+
|
|
136
|
+
# K
|
|
137
|
+
fire "$ACCT" "$(envelope mcp__plugin_email_email__email-draft '""')"
|
|
138
|
+
if printf '%s' "$ERR" | grep -q 'op=refuse account=2078cb54 tool=mcp__plugin_email_email__email-draft specialist=email-clerk'; then
|
|
139
|
+
ok "K the refuse line carries the account, the tool and the specialist"
|
|
140
|
+
else bad "K err=$ERR"; fi
|
|
141
|
+
|
|
142
|
+
# L — the hook cannot import the manager's TypeScript, so it mirrors
|
|
143
|
+
# CC_NATIVE_TOOLS as a python literal. A mirror that drifts silently un-gates or
|
|
144
|
+
# over-gates, so the two sets are compared rather than trusted.
|
|
145
|
+
TS_SET="$(sed -n '/export const CC_NATIVE_TOOLS/,/^\])/p' "$DRIFT_SOURCE" \
|
|
146
|
+
| grep -oE "'[A-Za-z]+'" | tr -d "'" | sort -u)"
|
|
147
|
+
SH_SET="$(sed -n '/^NATIVE = {/,/^}/p' "$GATE" \
|
|
148
|
+
| grep -oE '"[A-Za-z]+"' | tr -d '"' | sort -u)"
|
|
149
|
+
if [ "$TS_SET" = "$SH_SET" ]; then
|
|
150
|
+
ok "L the native set matches CC_NATIVE_TOOLS"
|
|
151
|
+
else
|
|
152
|
+
bad "L native sets differ:"
|
|
153
|
+
diff <(printf '%s\n' "$TS_SET") <(printf '%s\n' "$SH_SET") | sed 's/^/ /'
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
echo "──────── specialist-dispatch-gate summary ────────"
|
|
157
|
+
echo "pass=$PASS fail=$FAIL"
|
|
158
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# html-structure-gate — PreToolUse gate on Write / Edit / MultiEdit.
|
|
3
|
+
#
|
|
4
|
+
# Refuses a write that would produce an HTML document with a <style> element
|
|
5
|
+
# opened inside another one, or with more openers than closers.
|
|
6
|
+
#
|
|
7
|
+
# The fault is not cosmetic. HTML parses style content as raw text, so the FIRST
|
|
8
|
+
# </style> closes the outer element: everything after it becomes body content and
|
|
9
|
+
# renders as visible text, and the rule immediately after the stray opener is
|
|
10
|
+
# eaten as part of an invalid selector. One fault, two symptoms — a leaked
|
|
11
|
+
# stylesheet above the header and an unstyled box further down the same page.
|
|
12
|
+
#
|
|
13
|
+
# The detector has three rules, and the same three are implemented in the
|
|
14
|
+
# session-manager census that walks what is already on disk:
|
|
15
|
+
# 1. HTML comments are stripped first, each replaced by its own newlines, so a
|
|
16
|
+
# <style> inside <!-- … --> is not scanned and line numbers do not shift.
|
|
17
|
+
# That comment case is real: two account templates carry one and are clean,
|
|
18
|
+
# and a comment-blind detector reports both.
|
|
19
|
+
# 2. <style…> and </style…> are walked case-insensitively, tracking depth. An
|
|
20
|
+
# opener seen at depth > 0 is the fault.
|
|
21
|
+
# 3. At end of input, openers must equal closers; an unmatched opener is
|
|
22
|
+
# reported at its own line.
|
|
23
|
+
#
|
|
24
|
+
# It walks the document the call would PRODUCE, not the fragment it carries. For
|
|
25
|
+
# Write that is `content`; for Edit and MultiEdit the on-disk file is read and
|
|
26
|
+
# the replacements applied in memory first. Walking a fragment cannot work: rule
|
|
27
|
+
# 3 would refuse every partial edit, and a nested opener whose outer <style> is
|
|
28
|
+
# already on disk would pass.
|
|
29
|
+
#
|
|
30
|
+
# Scope is the whole account with an exclusion list, never a bucket list: every
|
|
31
|
+
# .html/.htm resolving inside the account dir except under node_modules/,
|
|
32
|
+
# .wrangler/, .tool/ and .trash/. A bucket list is what let the live document
|
|
33
|
+
# through — it sat under sites/<project>/public/, which no quote bucket covers.
|
|
34
|
+
# The account dir is this hook's cwd, as in fs-schema-guard.sh.
|
|
35
|
+
#
|
|
36
|
+
# Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail OPEN on an
|
|
37
|
+
# uninspectable envelope — tty, empty stdin, unreadable target, unparseable edit.
|
|
38
|
+
# A gate that bricks writes on a parse failure is worse than the fault it catches.
|
|
39
|
+
#
|
|
40
|
+
# The observability line is a direct append to the resolved server.log, not an
|
|
41
|
+
# echo: a hook's stderr reaches no sink on an allow, so a stderr-only line would
|
|
42
|
+
# be an audit nothing reads. No task numbers or internal refs in operator text.
|
|
43
|
+
|
|
44
|
+
set -uo pipefail
|
|
45
|
+
|
|
46
|
+
# No envelope (tty or empty stdin) → allow: cannot inspect, must not brick writes.
|
|
47
|
+
if [ -t 0 ]; then exit 0; fi
|
|
48
|
+
INPUT=$(cat)
|
|
49
|
+
[ -z "$INPUT" ] && exit 0
|
|
50
|
+
|
|
51
|
+
VERDICT=$(INPUT="$INPUT" ACCT="$PWD" python3 - <<'PY' 2>/dev/null || true
|
|
52
|
+
import os, re, json, sys
|
|
53
|
+
|
|
54
|
+
COMMENT = re.compile(r"<!--.*?-->", re.S)
|
|
55
|
+
CSS_COMMENT = re.compile(r"/\*.*?\*/", re.S)
|
|
56
|
+
TAG = re.compile(r"<\s*(/)?\s*style\b[^>]*>", re.I)
|
|
57
|
+
EXCLUDED = ("node_modules", ".wrangler", ".tool", ".trash")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def fault_line(s):
|
|
61
|
+
"""Line of the first nested or unmatched <style> opener, else None."""
|
|
62
|
+
t = COMMENT.sub(lambda m: "\n" * m.group(0).count("\n"), s)
|
|
63
|
+
spans = [(m.start(), m.end()) for m in CSS_COMMENT.finditer(t)]
|
|
64
|
+
depth = 0
|
|
65
|
+
open_line = 0
|
|
66
|
+
for m in TAG.finditer(t):
|
|
67
|
+
line = t.count("\n", 0, m.start()) + 1
|
|
68
|
+
if m.group(1):
|
|
69
|
+
if depth > 0:
|
|
70
|
+
depth -= 1
|
|
71
|
+
elif depth > 0:
|
|
72
|
+
# A CSS comment protects an opener but never a closer. Style content
|
|
73
|
+
# is raw text to the HTML parser, so </style> terminates the element
|
|
74
|
+
# wherever it appears; an opener is never a parse event there, only
|
|
75
|
+
# text the CSS parser reads as an invalid selector — and inside a
|
|
76
|
+
# comment it is discarded, so nothing is eaten.
|
|
77
|
+
if any(a <= m.start() < b for a, b in spans):
|
|
78
|
+
continue
|
|
79
|
+
return line
|
|
80
|
+
else:
|
|
81
|
+
depth = 1
|
|
82
|
+
open_line = line
|
|
83
|
+
return open_line if depth > 0 else None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
d = json.loads(os.environ["INPUT"])
|
|
88
|
+
except Exception:
|
|
89
|
+
sys.exit(0)
|
|
90
|
+
if (d.get("tool_name", "") or "") not in ("Write", "Edit", "MultiEdit"):
|
|
91
|
+
sys.exit(0)
|
|
92
|
+
tool = d["tool_name"]
|
|
93
|
+
ti = d.get("tool_input", {}) or {}
|
|
94
|
+
target = ti.get("file_path") or ""
|
|
95
|
+
if not target:
|
|
96
|
+
sys.exit(0)
|
|
97
|
+
|
|
98
|
+
acct = os.path.realpath(os.environ["ACCT"])
|
|
99
|
+
ab = os.path.realpath(target if os.path.isabs(target) else os.path.join(acct, target))
|
|
100
|
+
if not ab.startswith(acct + os.sep):
|
|
101
|
+
sys.exit(0)
|
|
102
|
+
rel = os.path.relpath(ab, acct)
|
|
103
|
+
if os.path.splitext(rel)[1].lower() not in (".html", ".htm"):
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
if any(p in EXCLUDED for p in rel.split(os.sep)):
|
|
106
|
+
sys.exit(0)
|
|
107
|
+
|
|
108
|
+
if tool == "Write":
|
|
109
|
+
produced = ti.get("content")
|
|
110
|
+
if not isinstance(produced, str):
|
|
111
|
+
sys.exit(0)
|
|
112
|
+
else:
|
|
113
|
+
try:
|
|
114
|
+
with open(ab, encoding="utf-8", errors="replace") as fh:
|
|
115
|
+
produced = fh.read()
|
|
116
|
+
except Exception:
|
|
117
|
+
sys.exit(0)
|
|
118
|
+
edits = ti.get("edits") if tool == "MultiEdit" else [ti]
|
|
119
|
+
if not isinstance(edits, list):
|
|
120
|
+
sys.exit(0)
|
|
121
|
+
for e in edits:
|
|
122
|
+
if not isinstance(e, dict):
|
|
123
|
+
sys.exit(0)
|
|
124
|
+
old, new = e.get("old_string"), e.get("new_string")
|
|
125
|
+
if not isinstance(old, str) or not isinstance(new, str):
|
|
126
|
+
sys.exit(0)
|
|
127
|
+
produced = produced.replace(old, new) if e.get("replace_all") else produced.replace(old, new, 1)
|
|
128
|
+
|
|
129
|
+
line = fault_line(produced)
|
|
130
|
+
if line is None:
|
|
131
|
+
sys.exit(0)
|
|
132
|
+
print(line)
|
|
133
|
+
print(json.dumps(rel))
|
|
134
|
+
PY
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
[ -z "$VERDICT" ] && exit 0
|
|
138
|
+
LINE=$(printf '%s' "$VERDICT" | sed -n 1p)
|
|
139
|
+
QREL=$(printf '%s' "$VERDICT" | sed -n 2p)
|
|
140
|
+
[ -z "$LINE" ] && exit 0
|
|
141
|
+
|
|
142
|
+
# Sink ladder, as quote-render-pdf-conformance.sh: LOG_DIR is stamped on the
|
|
143
|
+
# rc-spawn path, CLAUDE_SESSION_MANAGER_PERSIST_DIR is on the systemd unit and
|
|
144
|
+
# resolves to the same directory, the account's own logs dir is the last resort.
|
|
145
|
+
#
|
|
146
|
+
# There is deliberately no hard-coded brand fallback. Four brands are co-resident
|
|
147
|
+
# on one box, each with its own ~/.<brand>/logs, so a literal would file this
|
|
148
|
+
# account's path in a SIBLING brand's server.log. When no rung resolves, the line
|
|
149
|
+
# is dropped: the refusal still blocks and still reaches the agent on stderr.
|
|
150
|
+
LOGD=""
|
|
151
|
+
if [ -n "${LOG_DIR:-}" ] && [ -d "${LOG_DIR}" ]; then
|
|
152
|
+
LOGD="$LOG_DIR"
|
|
153
|
+
elif [ -n "${CLAUDE_SESSION_MANAGER_PERSIST_DIR:-}" ] && [ -d "${CLAUDE_SESSION_MANAGER_PERSIST_DIR}/logs" ]; then
|
|
154
|
+
LOGD="$CLAUDE_SESSION_MANAGER_PERSIST_DIR/logs"
|
|
155
|
+
elif [ -d "$PWD/logs" ]; then
|
|
156
|
+
LOGD="$PWD/logs"
|
|
157
|
+
fi
|
|
158
|
+
if [ -n "$LOGD" ]; then
|
|
159
|
+
ID8=$(basename "$PWD" | cut -c1-8)
|
|
160
|
+
printf '[html-structure-gate] op=refuse account=%s file=%s line=%s\n' \
|
|
161
|
+
"$ID8" "$QREL" "$LINE" >>"$LOGD/server.log" 2>/dev/null || true
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
echo "Blocked: this document opens a <style> element inside another one (line $LINE). The browser closes the outer element at the first </style>, so the rest of the stylesheet renders as visible text on the page and the rule after the stray opener is discarded. Put the block in the body as its own <style> element, or merge its rules into the one already open." >&2
|
|
165
|
+
exit 2
|