@rubytech/create-maxy-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 -2
- 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/plugins/telegram/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/telegram/PLUGIN.md +14 -2
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js +77 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js +49 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js +25 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js +115 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js +83 -0
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js +42 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts +32 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js +59 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts +18 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js +35 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts +38 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js +21 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts +24 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js +57 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts +16 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js +34 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts +26 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api.js +80 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/api.js.map +1 -0
- package/payload/platform/plugins/telegram/skills/build/SKILL.md +110 -0
- package/payload/platform/plugins/telegram/skills/build/references/methods.md +219 -0
- 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
package/payload/server/server.js
CHANGED
|
@@ -340,26 +340,26 @@ function keyOf(resource, accountId) {
|
|
|
340
340
|
function generationOf(resource, key2) {
|
|
341
341
|
return (keyGenerations.get(key2) ?? 0) + (resourceGenerations.get(resource) ?? 0);
|
|
342
342
|
}
|
|
343
|
-
function recomputeFloorMs(
|
|
343
|
+
function recomputeFloorMs(entry2) {
|
|
344
344
|
return Math.min(
|
|
345
|
-
Math.max(
|
|
345
|
+
Math.max(entry2.computeMs * RECOMPUTE_FLOOR_FACTOR, MIN_RECOMPUTE_FLOOR_MS),
|
|
346
346
|
MAX_RECOMPUTE_FLOOR_MS
|
|
347
347
|
);
|
|
348
348
|
}
|
|
349
349
|
async function getOrCompute(resource, accountId, compute) {
|
|
350
350
|
const key2 = keyOf(resource, accountId);
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
353
|
-
const age = Date.now() -
|
|
354
|
-
if (!
|
|
355
|
-
return { value:
|
|
351
|
+
const entry2 = entries.get(key2);
|
|
352
|
+
if (entry2 !== void 0) {
|
|
353
|
+
const age = Date.now() - entry2.computedAt;
|
|
354
|
+
if (!entry2.dirty && age <= BACKSTOP_MS) {
|
|
355
|
+
return { value: entry2.value, state: "hit" };
|
|
356
356
|
}
|
|
357
|
-
const sinceAttempt = Date.now() -
|
|
358
|
-
if (
|
|
359
|
-
return { value:
|
|
357
|
+
const sinceAttempt = Date.now() - entry2.lastAttemptAt;
|
|
358
|
+
if (entry2.dirty && sinceAttempt < recomputeFloorMs(entry2) && age <= BACKSTOP_MS) {
|
|
359
|
+
return { value: entry2.value, state: "dirty-floor" };
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
|
-
const state =
|
|
362
|
+
const state = entry2 === void 0 ? "miss" : "stale-backstop";
|
|
363
363
|
const existing = inFlight.get(key2);
|
|
364
364
|
if (existing) return { value: await existing, state };
|
|
365
365
|
const startedGeneration = generationOf(resource, key2);
|
|
@@ -404,8 +404,8 @@ function invalidate(resource, accountId, source = "action") {
|
|
|
404
404
|
entries.delete(key2);
|
|
405
405
|
return;
|
|
406
406
|
}
|
|
407
|
-
const
|
|
408
|
-
if (
|
|
407
|
+
const entry2 = entries.get(key2);
|
|
408
|
+
if (entry2) entry2.dirty = true;
|
|
409
409
|
};
|
|
410
410
|
if (accountId === null) {
|
|
411
411
|
resourceGenerations.set(resource, (resourceGenerations.get(resource) ?? 0) + 1);
|
|
@@ -974,7 +974,7 @@ function parseTitleCache(raw) {
|
|
|
974
974
|
}
|
|
975
975
|
function serializeTitleCache(cache2) {
|
|
976
976
|
const obj = {};
|
|
977
|
-
for (const [sessionId,
|
|
977
|
+
for (const [sessionId, entry2] of cache2) obj[sessionId] = entry2;
|
|
978
978
|
return JSON.stringify(obj);
|
|
979
979
|
}
|
|
980
980
|
async function loadTitleCacheOutcome(accountDir) {
|
|
@@ -1007,7 +1007,7 @@ async function saveTitleCacheMerged(accountDir, updates, evict) {
|
|
|
1007
1007
|
if (err.code !== "ENOENT") throw err;
|
|
1008
1008
|
merged = /* @__PURE__ */ new Map();
|
|
1009
1009
|
}
|
|
1010
|
-
for (const [sessionId,
|
|
1010
|
+
for (const [sessionId, entry2] of updates) merged.set(sessionId, entry2);
|
|
1011
1011
|
let evicted = 0;
|
|
1012
1012
|
if (evict !== void 0) {
|
|
1013
1013
|
for (const sessionId of evict) {
|
|
@@ -1441,11 +1441,11 @@ function enumerateJsonlsWithSkips(projectsRoot, fileRe = SESSION_ID_RE) {
|
|
|
1441
1441
|
skipped += 1;
|
|
1442
1442
|
continue;
|
|
1443
1443
|
}
|
|
1444
|
-
for (const
|
|
1445
|
-
if (
|
|
1446
|
-
out.push({ path: join11(slugDir,
|
|
1447
|
-
} else if (
|
|
1448
|
-
const subDir = join11(slugDir,
|
|
1444
|
+
for (const entry2 of entries2) {
|
|
1445
|
+
if (entry2.isFile() && fileRe.test(entry2.name)) {
|
|
1446
|
+
out.push({ path: join11(slugDir, entry2.name), isSubagent: false, archived: false });
|
|
1447
|
+
} else if (entry2.isDirectory() && entry2.name === "subagents") {
|
|
1448
|
+
const subDir = join11(slugDir, entry2.name);
|
|
1449
1449
|
let subEntries;
|
|
1450
1450
|
try {
|
|
1451
1451
|
subEntries = readdirSync5(subDir, { withFileTypes: true });
|
|
@@ -1460,8 +1460,8 @@ function enumerateJsonlsWithSkips(projectsRoot, fileRe = SESSION_ID_RE) {
|
|
|
1460
1460
|
out.push({ path: join11(subDir, sub.name), isSubagent: true, archived: false });
|
|
1461
1461
|
}
|
|
1462
1462
|
}
|
|
1463
|
-
} else if (
|
|
1464
|
-
const archiveDir = join11(slugDir,
|
|
1463
|
+
} else if (entry2.isDirectory() && entry2.name === "archive") {
|
|
1464
|
+
const archiveDir = join11(slugDir, entry2.name);
|
|
1465
1465
|
let archiveEntries;
|
|
1466
1466
|
try {
|
|
1467
1467
|
archiveEntries = readdirSync5(archiveDir, { withFileTypes: true });
|
|
@@ -2147,7 +2147,7 @@ function createSidecarStore(config) {
|
|
|
2147
2147
|
function readAll(sessionsDir, onSkip) {
|
|
2148
2148
|
let names;
|
|
2149
2149
|
try {
|
|
2150
|
-
names = readdirSync16(sessionsDir, { withFileTypes: true }).filter((
|
|
2150
|
+
names = readdirSync16(sessionsDir, { withFileTypes: true }).filter((entry2) => entry2.isFile()).map((entry2) => entry2.name);
|
|
2151
2151
|
} catch {
|
|
2152
2152
|
return [];
|
|
2153
2153
|
}
|
|
@@ -2563,7 +2563,7 @@ function createWebchatRoutes(deps) {
|
|
|
2563
2563
|
clearSignal = body?.clearSignal === true;
|
|
2564
2564
|
quotedUuid = typeof body?.quotedUuid === "string" && body.quotedUuid ? body.quotedUuid : void 0;
|
|
2565
2565
|
}
|
|
2566
|
-
let deliveryKey
|
|
2566
|
+
let deliveryKey;
|
|
2567
2567
|
let deliverySessionId;
|
|
2568
2568
|
let deliveryAccountId = accountId;
|
|
2569
2569
|
const explicitTarget = target !== void 0;
|
|
@@ -2606,6 +2606,10 @@ function createWebchatRoutes(deps) {
|
|
|
2606
2606
|
deliveryKey = `session:${target}`;
|
|
2607
2607
|
deliverySessionId = target;
|
|
2608
2608
|
deliveryAccountId = sessionAccountId ?? accountId;
|
|
2609
|
+
} else {
|
|
2610
|
+
deliverySessionId = adminSessionIdFor(bootstrapAccountId, WEBCHAT_ADMIN_KEY, requesterUserId ?? void 0);
|
|
2611
|
+
deliveryKey = `session:${deliverySessionId}`;
|
|
2612
|
+
deliveryAccountId = bootstrapAccountId;
|
|
2609
2613
|
}
|
|
2610
2614
|
if (isMultipart) {
|
|
2611
2615
|
const kind = files.some((f) => isAudioMime(f.type)) ? "voice" : "files";
|
|
@@ -2676,20 +2680,24 @@ ${note}` : note;
|
|
|
2676
2680
|
}
|
|
2677
2681
|
console.log(`[webchat:inbound] op=send-parsed kind=${kind} files=${stored.length} bytes=${Buffer.byteLength(text, "utf8")}`);
|
|
2678
2682
|
}
|
|
2679
|
-
if (
|
|
2683
|
+
if (target !== void 0 && await managerActivity(deliverySessionId) === "busy") {
|
|
2680
2684
|
console.log(`[webchat:inbound] op=queued-busy sessionId=${deliverySessionId.slice(0, 8)} key=${deliveryKey}`);
|
|
2681
2685
|
}
|
|
2682
2686
|
const adminUserId = requesterUserId ?? void 0;
|
|
2683
2687
|
const inboundStartedAt = Date.now();
|
|
2684
2688
|
const sendId = sendIdFromResolve ?? ++sendSeq;
|
|
2685
2689
|
const replyContext = resolveQuoted(deliverySessionId, quotedUuid, sendId);
|
|
2686
|
-
const inbound =
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2690
|
+
const inbound = await deps.handleInbound({
|
|
2691
|
+
role: "admin",
|
|
2692
|
+
accountId: deliveryAccountId,
|
|
2693
|
+
key: deliveryKey,
|
|
2694
|
+
text,
|
|
2695
|
+
sessionId: deliverySessionId,
|
|
2696
|
+
adminUserId,
|
|
2697
|
+
...replyContext ? { replyContext } : {}
|
|
2698
|
+
});
|
|
2699
|
+
const turnSessionId = deliverySessionId;
|
|
2700
|
+
const keyDisplay2 = deliveryKey.slice(0, "session:".length + 8);
|
|
2693
2701
|
const baseline = jsonlSizeBytes(locateSession(turnSessionId).projectDir, turnSessionId);
|
|
2694
2702
|
console.error(
|
|
2695
2703
|
`[webchat:inbound] op=send-accepted key=${keyDisplay2} sendId=${sendId} baselineBytes=${baseline ?? "absent"} acceptMs=${Date.now() - inboundStartedAt} spawn=${inbound.spawn !== null ? "triggered" : "none"}`
|
|
@@ -2975,15 +2983,22 @@ var require_session = __commonJS({
|
|
|
2975
2983
|
exports.withSession = withSession3;
|
|
2976
2984
|
exports.__setDriverForTests = __setDriverForTests;
|
|
2977
2985
|
var node_fs_1 = __require("fs");
|
|
2986
|
+
var node_path_1 = __require("path");
|
|
2978
2987
|
var neo4j_driver_1 = __importDefault(__require("neo4j-driver"));
|
|
2979
2988
|
var driver2 = null;
|
|
2980
2989
|
function password2() {
|
|
2981
2990
|
if (process.env.NEO4J_PASSWORD)
|
|
2982
2991
|
return process.env.NEO4J_PASSWORD;
|
|
2983
|
-
const
|
|
2984
|
-
if (
|
|
2985
|
-
|
|
2986
|
-
|
|
2992
|
+
const root = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT;
|
|
2993
|
+
if (!root) {
|
|
2994
|
+
throw new Error("[dispatch-read] neither MAXY_PLATFORM_ROOT nor PLATFORM_ROOT is set \u2014 cannot locate config/.neo4j-password");
|
|
2995
|
+
}
|
|
2996
|
+
const passwordFile = (0, node_path_1.resolve)(root, "config/.neo4j-password");
|
|
2997
|
+
try {
|
|
2998
|
+
return (0, node_fs_1.readFileSync)(passwordFile, "utf-8").trim();
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
throw new Error(`[dispatch-read] Neo4j password not found. Expected at ${passwordFile}, or in NEO4J_PASSWORD. Read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3001
|
+
}
|
|
2987
3002
|
}
|
|
2988
3003
|
function getDriver2() {
|
|
2989
3004
|
if (driver2)
|
|
@@ -5087,10 +5102,10 @@ var require_dist7 = __commonJS({
|
|
|
5087
5102
|
|
|
5088
5103
|
// server/index.ts
|
|
5089
5104
|
init_dist();
|
|
5090
|
-
import { readFileSync as
|
|
5105
|
+
import { readFileSync as readFileSync55, existsSync as existsSync55, watchFile, readdirSync as readdirSync32, statSync as statSync30 } from "fs";
|
|
5091
5106
|
import { spawn as spawn3 } from "child_process";
|
|
5092
5107
|
import { createHash as createHash8 } from "crypto";
|
|
5093
|
-
import { resolve as
|
|
5108
|
+
import { resolve as resolve52, join as join63, basename as basename17 } from "path";
|
|
5094
5109
|
import { homedir as homedir5 } from "os";
|
|
5095
5110
|
|
|
5096
5111
|
// server/lib/route-cache-watchers.ts
|
|
@@ -6341,12 +6356,12 @@ function getLanIp() {
|
|
|
6341
6356
|
let fallback = null;
|
|
6342
6357
|
for (const entries2 of Object.values(nets)) {
|
|
6343
6358
|
if (!entries2) continue;
|
|
6344
|
-
for (const
|
|
6345
|
-
if (
|
|
6346
|
-
if (
|
|
6347
|
-
return
|
|
6359
|
+
for (const entry2 of entries2) {
|
|
6360
|
+
if (entry2.family !== "IPv4" || entry2.internal) continue;
|
|
6361
|
+
if (entry2.address.startsWith("192.168.") || entry2.address.startsWith("10.")) {
|
|
6362
|
+
return entry2.address;
|
|
6348
6363
|
}
|
|
6349
|
-
if (!fallback) fallback =
|
|
6364
|
+
if (!fallback) fallback = entry2.address;
|
|
6350
6365
|
}
|
|
6351
6366
|
}
|
|
6352
6367
|
return fallback;
|
|
@@ -6650,20 +6665,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
6650
6665
|
// server/routes/health.ts
|
|
6651
6666
|
init_claude_agent();
|
|
6652
6667
|
function checkPort(port2, timeoutMs = 500) {
|
|
6653
|
-
return new Promise((
|
|
6668
|
+
return new Promise((resolve53) => {
|
|
6654
6669
|
const socket = createConnection2(port2, "127.0.0.1");
|
|
6655
6670
|
socket.setTimeout(timeoutMs);
|
|
6656
6671
|
socket.once("connect", () => {
|
|
6657
6672
|
socket.destroy();
|
|
6658
|
-
|
|
6673
|
+
resolve53(true);
|
|
6659
6674
|
});
|
|
6660
6675
|
socket.once("error", () => {
|
|
6661
6676
|
socket.destroy();
|
|
6662
|
-
|
|
6677
|
+
resolve53(false);
|
|
6663
6678
|
});
|
|
6664
6679
|
socket.once("timeout", () => {
|
|
6665
6680
|
socket.destroy();
|
|
6666
|
-
|
|
6681
|
+
resolve53(false);
|
|
6667
6682
|
});
|
|
6668
6683
|
});
|
|
6669
6684
|
}
|
|
@@ -7296,12 +7311,12 @@ function readConversationSummaries2(accountId) {
|
|
|
7296
7311
|
const seen = /* @__PURE__ */ new Set();
|
|
7297
7312
|
try {
|
|
7298
7313
|
if (!existsSync3(dir)) return out;
|
|
7299
|
-
for (const
|
|
7300
|
-
if (!
|
|
7301
|
-
const stem =
|
|
7314
|
+
for (const entry2 of readdirSync2(dir)) {
|
|
7315
|
+
if (!entry2.endsWith(".jsonl")) continue;
|
|
7316
|
+
const stem = entry2.slice(0, -".jsonl".length);
|
|
7302
7317
|
if (!isStorableStem(stem)) continue;
|
|
7303
7318
|
const channelKey = fileStemToChannelKey(stem);
|
|
7304
|
-
const file = join7(dir,
|
|
7319
|
+
const file = join7(dir, entry2);
|
|
7305
7320
|
seen.add(file);
|
|
7306
7321
|
let size;
|
|
7307
7322
|
let mtimeMs;
|
|
@@ -7640,7 +7655,7 @@ async function defaultResolve(accountId) {
|
|
|
7640
7655
|
return resolveSocket2(accountId);
|
|
7641
7656
|
}
|
|
7642
7657
|
async function fetchAvatar(accountId, jid, deps = {}) {
|
|
7643
|
-
const
|
|
7658
|
+
const resolve53 = deps.resolve;
|
|
7644
7659
|
const httpGet = deps.httpGet ?? defaultHttpGet;
|
|
7645
7660
|
const maxAgeMs = deps.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
7646
7661
|
const cached3 = readAvatar(accountId, jid);
|
|
@@ -7650,7 +7665,7 @@ async function fetchAvatar(accountId, jid, deps = {}) {
|
|
|
7650
7665
|
return "fresh";
|
|
7651
7666
|
}
|
|
7652
7667
|
console.log(`${TAG7} op=cache jid=${jid} hit=false ageMs=${ageMs2}`);
|
|
7653
|
-
const res =
|
|
7668
|
+
const res = resolve53 ? resolve53(accountId) : await defaultResolve(accountId);
|
|
7654
7669
|
if (!res.ok) {
|
|
7655
7670
|
console.log(`${TAG7} op=no-socket jid=${jid} account=${accountId.slice(0, 8)} reason=${res.reason}`);
|
|
7656
7671
|
return "no-socket";
|
|
@@ -7772,12 +7787,12 @@ async function sweepAvatars() {
|
|
|
7772
7787
|
var TAG9 = "[wa-chatmeta]";
|
|
7773
7788
|
function diffStoredAgainstLive(stored, live) {
|
|
7774
7789
|
const out = [];
|
|
7775
|
-
for (const
|
|
7776
|
-
if (!
|
|
7777
|
-
const seen = live.get(
|
|
7790
|
+
for (const entry2 of stored.values()) {
|
|
7791
|
+
if (!entry2.isGroup) continue;
|
|
7792
|
+
const seen = live.get(entry2.jid);
|
|
7778
7793
|
if (seen === void 0) continue;
|
|
7779
|
-
if (seen.name !==
|
|
7780
|
-
out.push({ jid:
|
|
7794
|
+
if (seen.name !== entry2.name) {
|
|
7795
|
+
out.push({ jid: entry2.jid, stored: entry2.name, live: seen.name, observedAt: seen.observedAt });
|
|
7781
7796
|
}
|
|
7782
7797
|
}
|
|
7783
7798
|
return out;
|
|
@@ -7793,13 +7808,13 @@ async function sweepChatMetadata() {
|
|
|
7793
7808
|
const sock = conn.sock;
|
|
7794
7809
|
const stored = readChatMetadata(conn.platformAccountId);
|
|
7795
7810
|
const live = /* @__PURE__ */ new Map();
|
|
7796
|
-
for (const
|
|
7797
|
-
if (!
|
|
7811
|
+
for (const entry2 of stored.values()) {
|
|
7812
|
+
if (!entry2.isGroup) continue;
|
|
7798
7813
|
groups += 1;
|
|
7799
7814
|
try {
|
|
7800
|
-
const meta = await sock.groupMetadata(
|
|
7815
|
+
const meta = await sock.groupMetadata(entry2.jid);
|
|
7801
7816
|
if (typeof meta.subject === "string" && meta.subject.length > 0) {
|
|
7802
|
-
live.set(
|
|
7817
|
+
live.set(entry2.jid, { name: meta.subject, observedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7803
7818
|
checked2 += 1;
|
|
7804
7819
|
}
|
|
7805
7820
|
} catch {
|
|
@@ -8177,9 +8192,9 @@ function purgeNonSocketAccountManagers(accountDir, socketOwnerId) {
|
|
|
8177
8192
|
if (targetId === socketOwnerId) return { removed: [] };
|
|
8178
8193
|
const map = wa.accountManagers;
|
|
8179
8194
|
if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
|
|
8180
|
-
const removed = Object.entries(map).map(([phone,
|
|
8195
|
+
const removed = Object.entries(map).map(([phone, entry2]) => ({
|
|
8181
8196
|
phone,
|
|
8182
|
-
managesAccount: typeof
|
|
8197
|
+
managesAccount: typeof entry2 === "string" ? entry2 : entry2 && typeof entry2 === "object" && typeof entry2.managesAccount === "string" ? entry2.managesAccount : ""
|
|
8183
8198
|
}));
|
|
8184
8199
|
if (removed.length === 0) return { removed: [] };
|
|
8185
8200
|
delete wa.accountManagers;
|
|
@@ -8482,10 +8497,10 @@ function purgeNonSocketRegisteredParties(accountDir, socketOwnerId) {
|
|
|
8482
8497
|
if (targetId === socketOwnerId) return { removed: [] };
|
|
8483
8498
|
const map = wa.registeredParties;
|
|
8484
8499
|
if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
|
|
8485
|
-
const removed = Object.entries(map).map(([phone,
|
|
8500
|
+
const removed = Object.entries(map).map(([phone, entry2]) => ({
|
|
8486
8501
|
phone,
|
|
8487
|
-
account:
|
|
8488
|
-
party:
|
|
8502
|
+
account: entry2 && typeof entry2 === "object" && typeof entry2.account === "string" ? entry2.account : "",
|
|
8503
|
+
party: entry2 && typeof entry2 === "object" && typeof entry2.party === "string" ? entry2.party : ""
|
|
8489
8504
|
}));
|
|
8490
8505
|
if (removed.length === 0) return { removed: [] };
|
|
8491
8506
|
delete wa.registeredParties;
|
|
@@ -9141,8 +9156,8 @@ async function startLogin(opts) {
|
|
|
9141
9156
|
await clearAuth(authDir);
|
|
9142
9157
|
let resolveCode = null;
|
|
9143
9158
|
let rejectCode = null;
|
|
9144
|
-
const codePromise = new Promise((
|
|
9145
|
-
resolveCode =
|
|
9159
|
+
const codePromise = new Promise((resolve53, reject) => {
|
|
9160
|
+
resolveCode = resolve53;
|
|
9146
9161
|
rejectCode = reject;
|
|
9147
9162
|
});
|
|
9148
9163
|
const codeTimer = setTimeout(
|
|
@@ -9319,27 +9334,27 @@ function bare(value) {
|
|
|
9319
9334
|
function reconcilePersonIdentities(rows) {
|
|
9320
9335
|
const perAccount = /* @__PURE__ */ new Map();
|
|
9321
9336
|
for (const row of rows) {
|
|
9322
|
-
let
|
|
9323
|
-
if (!
|
|
9324
|
-
|
|
9325
|
-
perAccount.set(row.accountId,
|
|
9337
|
+
let entry2 = perAccount.get(row.accountId);
|
|
9338
|
+
if (!entry2) {
|
|
9339
|
+
entry2 = { seen: /* @__PURE__ */ new Map(), divergent: 0 };
|
|
9340
|
+
perAccount.set(row.accountId, entry2);
|
|
9326
9341
|
}
|
|
9327
9342
|
const p = row.phone ? bare(row.phone) : null;
|
|
9328
9343
|
const t = row.telephone ? bare(row.telephone) : null;
|
|
9329
|
-
if (p && t && p !== t)
|
|
9344
|
+
if (p && t && p !== t) entry2.divergent += 1;
|
|
9330
9345
|
const key2 = p ?? t;
|
|
9331
|
-
if (key2)
|
|
9346
|
+
if (key2) entry2.seen.set(key2, (entry2.seen.get(key2) ?? 0) + 1);
|
|
9332
9347
|
}
|
|
9333
9348
|
const accounts = [];
|
|
9334
9349
|
let duplicates = 0;
|
|
9335
9350
|
let divergent = 0;
|
|
9336
|
-
for (const [accountId,
|
|
9351
|
+
for (const [accountId, entry2] of perAccount) {
|
|
9337
9352
|
let dup = 0;
|
|
9338
|
-
for (const count of
|
|
9353
|
+
for (const count of entry2.seen.values()) if (count > 1) dup += count - 1;
|
|
9339
9354
|
duplicates += dup;
|
|
9340
|
-
divergent +=
|
|
9341
|
-
if (dup > 0 ||
|
|
9342
|
-
accounts.push({ accountId, duplicates: dup, divergent:
|
|
9355
|
+
divergent += entry2.divergent;
|
|
9356
|
+
if (dup > 0 || entry2.divergent > 0) {
|
|
9357
|
+
accounts.push({ accountId, duplicates: dup, divergent: entry2.divergent });
|
|
9343
9358
|
}
|
|
9344
9359
|
}
|
|
9345
9360
|
return {
|
|
@@ -9362,7 +9377,7 @@ function reconcileSelfPhoneAdmins(input) {
|
|
|
9362
9377
|
for (const cred of nonHouse) {
|
|
9363
9378
|
const selfPhone = cred.selfPhone;
|
|
9364
9379
|
if (!selfPhone) continue;
|
|
9365
|
-
if (input.adminPhones.some((
|
|
9380
|
+
if (input.adminPhones.some((entry2) => phonesMatch(entry2, selfPhone))) {
|
|
9366
9381
|
matches.push({ accountId: cred.accountId, phone: selfPhone });
|
|
9367
9382
|
}
|
|
9368
9383
|
}
|
|
@@ -9771,11 +9786,11 @@ init_src2();
|
|
|
9771
9786
|
function buildRegisteredPartyReconcile(params) {
|
|
9772
9787
|
const rows = [];
|
|
9773
9788
|
const entries2 = Object.entries(params.registeredParties);
|
|
9774
|
-
for (const [phone,
|
|
9789
|
+
for (const [phone, entry2] of entries2) {
|
|
9775
9790
|
const shadowedBy = isAdminPhone(phone, params.adminPhones) ? "adminPhones" : managedAccountFor(params.accountManagers, phone) ? "accountManagers" : null;
|
|
9776
|
-
const unresolved2 = !params.isValidAccount(
|
|
9791
|
+
const unresolved2 = !params.isValidAccount(entry2.account);
|
|
9777
9792
|
if (shadowedBy || unresolved2) {
|
|
9778
|
-
rows.push({ phone, account:
|
|
9793
|
+
rows.push({ phone, account: entry2.account, party: entry2.party, shadowedBy, unresolved: unresolved2 });
|
|
9779
9794
|
}
|
|
9780
9795
|
}
|
|
9781
9796
|
const shadowed = rows.filter((r) => r.shadowedBy !== null).length;
|
|
@@ -10185,15 +10200,15 @@ app3.post("/config", async (c) => {
|
|
|
10185
10200
|
const agents = [];
|
|
10186
10201
|
if (existsSync9(agentsDir)) {
|
|
10187
10202
|
try {
|
|
10188
|
-
for (const
|
|
10189
|
-
if (!
|
|
10190
|
-
const configPath3 = resolve8(agentsDir,
|
|
10203
|
+
for (const entry2 of readdirSync8(agentsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
10204
|
+
if (!entry2.isDirectory() || entry2.name === "admin") continue;
|
|
10205
|
+
const configPath3 = resolve8(agentsDir, entry2.name, "config.json");
|
|
10191
10206
|
if (!existsSync9(configPath3)) continue;
|
|
10192
10207
|
try {
|
|
10193
10208
|
const parsed = JSON.parse(readFileSync11(configPath3, "utf-8"));
|
|
10194
|
-
agents.push({ slug:
|
|
10209
|
+
agents.push({ slug: entry2.name, displayName: parsed.displayName ?? entry2.name });
|
|
10195
10210
|
} catch {
|
|
10196
|
-
console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${
|
|
10211
|
+
console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${entry2.name}" \u2014 skipping`);
|
|
10197
10212
|
}
|
|
10198
10213
|
}
|
|
10199
10214
|
} catch (err) {
|
|
@@ -10213,8 +10228,8 @@ app3.post("/config", async (c) => {
|
|
|
10213
10228
|
const listDenial = bindingScopeDenial(c, callerAccountId, "list-account-managers");
|
|
10214
10229
|
if (listDenial) return listDenial;
|
|
10215
10230
|
const all = readAccountManagers(acct.accountDir);
|
|
10216
|
-
const boundAccountOf = (
|
|
10217
|
-
const accountManagers = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([,
|
|
10231
|
+
const boundAccountOf = (entry2) => typeof entry2 === "string" ? entry2 : entry2.managesAccount;
|
|
10232
|
+
const accountManagers = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => boundAccountOf(entry2) === callerAccountId));
|
|
10218
10233
|
console.error(`${TAG16} config action=list-account-managers caller=${callerAccountId || "none"} houseAdmin=${houseAdmin ? "y" : "n"} count=${Object.keys(accountManagers).length} of=${Object.keys(all).length}`);
|
|
10219
10234
|
return c.json({ ok: true, accountManagers });
|
|
10220
10235
|
}
|
|
@@ -10252,7 +10267,7 @@ app3.post("/config", async (c) => {
|
|
|
10252
10267
|
const listDenial = bindingScopeDenial(c, callerAccountId, "list-registered-parties");
|
|
10253
10268
|
if (listDenial) return listDenial;
|
|
10254
10269
|
const all = readRegisteredParties(acct.accountDir);
|
|
10255
|
-
const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([,
|
|
10270
|
+
const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => entry2.account === callerAccountId));
|
|
10256
10271
|
console.error(`${TAG16} config action=list-registered-parties caller=${callerAccountId || "none"} houseAdmin=${houseAdmin ? "y" : "n"} count=${Object.keys(registeredParties).length} of=${Object.keys(all).length}`);
|
|
10257
10272
|
return c.json({ ok: true, registeredParties });
|
|
10258
10273
|
}
|
|
@@ -12162,10 +12177,10 @@ function readState(platformRoot5) {
|
|
|
12162
12177
|
return {};
|
|
12163
12178
|
}
|
|
12164
12179
|
}
|
|
12165
|
-
function recordSuccess(platformRoot5, accountId,
|
|
12180
|
+
function recordSuccess(platformRoot5, accountId, entry2) {
|
|
12166
12181
|
const path = stateFilePath(platformRoot5);
|
|
12167
12182
|
const state = readState(platformRoot5);
|
|
12168
|
-
state[accountId] =
|
|
12183
|
+
state[accountId] = entry2;
|
|
12169
12184
|
try {
|
|
12170
12185
|
writeFileSync6(path, JSON.stringify(state, null, 1));
|
|
12171
12186
|
} catch (err) {
|
|
@@ -12439,10 +12454,10 @@ function readVisitsPushState(platformRoot5) {
|
|
|
12439
12454
|
return {};
|
|
12440
12455
|
}
|
|
12441
12456
|
}
|
|
12442
|
-
function recordVisitsPush(platformRoot5, accountId,
|
|
12457
|
+
function recordVisitsPush(platformRoot5, accountId, entry2) {
|
|
12443
12458
|
const path = visitsStateFilePath(platformRoot5);
|
|
12444
12459
|
const state = readVisitsPushState(platformRoot5);
|
|
12445
|
-
state[accountId] =
|
|
12460
|
+
state[accountId] = entry2;
|
|
12446
12461
|
try {
|
|
12447
12462
|
writeFileSync7(path, JSON.stringify(state, null, 1));
|
|
12448
12463
|
} catch (err) {
|
|
@@ -13871,8 +13886,8 @@ function listBotEntries(config) {
|
|
|
13871
13886
|
function resolveBotEntry(accounts, botId) {
|
|
13872
13887
|
const hits = [];
|
|
13873
13888
|
for (const a of accounts) {
|
|
13874
|
-
for (const
|
|
13875
|
-
if (
|
|
13889
|
+
for (const entry2 of listBotEntries(a.config.telegram)) {
|
|
13890
|
+
if (entry2.id === botId) hits.push({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 });
|
|
13876
13891
|
}
|
|
13877
13892
|
}
|
|
13878
13893
|
if (hits.length === 0) return { kind: "none" };
|
|
@@ -14252,6 +14267,12 @@ function pickActivity(entries2, grown, nowMs, concurrencyWindowMs) {
|
|
|
14252
14267
|
// server/routes/session-reader.ts
|
|
14253
14268
|
init_dist();
|
|
14254
14269
|
var app7 = new Hono();
|
|
14270
|
+
function probeDataRef(path) {
|
|
14271
|
+
const res = resolveDataPath(path);
|
|
14272
|
+
if (!res.ok) {
|
|
14273
|
+
console.error(`[data] op=download-ref-unservable root="data" path="${path}" reason=resolve-failed status=${res.status}`);
|
|
14274
|
+
}
|
|
14275
|
+
}
|
|
14255
14276
|
function enrichDownloads(turn, scratchpadRoot) {
|
|
14256
14277
|
if (turn.kind !== "agent-file") return turn;
|
|
14257
14278
|
let scratchpadReal = null;
|
|
@@ -14263,6 +14284,7 @@ function enrichDownloads(turn, scratchpadRoot) {
|
|
|
14263
14284
|
const downloads = turn.files.map((f) => {
|
|
14264
14285
|
const dataRel = relative3(DATA_ROOT, f);
|
|
14265
14286
|
if (dataRel && !dataRel.startsWith("..") && !isAbsolute2(dataRel)) {
|
|
14287
|
+
probeDataRef(dataRel);
|
|
14266
14288
|
return { root: "data", path: dataRel };
|
|
14267
14289
|
}
|
|
14268
14290
|
if (scratchpadReal) {
|
|
@@ -15701,20 +15723,20 @@ function getTelegramGateway() {
|
|
|
15701
15723
|
|
|
15702
15724
|
// app/lib/telegram/access-control.ts
|
|
15703
15725
|
function checkTelegramAccess(params) {
|
|
15704
|
-
const { senderId, entry } = params;
|
|
15705
|
-
if (
|
|
15706
|
-
const adminUsers =
|
|
15726
|
+
const { senderId, entry: entry2 } = params;
|
|
15727
|
+
if (entry2.role === "admin") {
|
|
15728
|
+
const adminUsers = entry2.adminUsers ?? [];
|
|
15707
15729
|
if (adminUsers.includes(senderId)) {
|
|
15708
15730
|
return { allowed: true, reason: "admin-binding", agentType: "admin" };
|
|
15709
15731
|
}
|
|
15710
15732
|
return { allowed: false, reason: "not-admin-user", agentType: "admin" };
|
|
15711
15733
|
}
|
|
15712
|
-
const policy =
|
|
15734
|
+
const policy = entry2.dmPolicy ?? "disabled";
|
|
15713
15735
|
switch (policy) {
|
|
15714
15736
|
case "open":
|
|
15715
15737
|
return { allowed: true, reason: "dm-policy-open", agentType: "public" };
|
|
15716
15738
|
case "allowlist": {
|
|
15717
|
-
const allowFrom =
|
|
15739
|
+
const allowFrom = entry2.allowFrom ?? [];
|
|
15718
15740
|
if (allowFrom.includes(senderId)) {
|
|
15719
15741
|
return { allowed: true, reason: "allowlist-match", agentType: "public" };
|
|
15720
15742
|
}
|
|
@@ -16224,8 +16246,8 @@ var NOTIFY_MAX = 5e3;
|
|
|
16224
16246
|
var cache = /* @__PURE__ */ new Map();
|
|
16225
16247
|
function isTelegramNotifyDuplicate(notifyId) {
|
|
16226
16248
|
const now = Date.now();
|
|
16227
|
-
const
|
|
16228
|
-
if (
|
|
16249
|
+
const entry2 = cache.get(notifyId);
|
|
16250
|
+
if (entry2 && now - entry2.ts <= NOTIFY_TTL_MS) return true;
|
|
16229
16251
|
for (const [key2, e] of cache) {
|
|
16230
16252
|
if (now - e.ts > NOTIFY_TTL_MS) cache.delete(key2);
|
|
16231
16253
|
}
|
|
@@ -16324,9 +16346,9 @@ app11.post("/", async (c) => {
|
|
|
16324
16346
|
console.error(`${TAG26} op=reject reason=duplicate-bot-id botId=${botId} accounts=${resolved.count}`);
|
|
16325
16347
|
return c.json({ ok: false }, 401);
|
|
16326
16348
|
}
|
|
16327
|
-
const { accountId, accountDir, entry } = resolved;
|
|
16328
|
-
const agentSlug =
|
|
16329
|
-
console.error(`${TAG26} op=entry botId=${botId} accountId=${accountId} role=${
|
|
16349
|
+
const { accountId, accountDir, entry: entry2 } = resolved;
|
|
16350
|
+
const agentSlug = entry2.role === "admin" ? "admin" : entry2.agent;
|
|
16351
|
+
console.error(`${TAG26} op=entry botId=${botId} accountId=${accountId} role=${entry2.role} agent=${agentSlug}`);
|
|
16330
16352
|
const sp = secretPath(botId);
|
|
16331
16353
|
if (!existsSync22(sp)) {
|
|
16332
16354
|
console.error(`${TAG26} op=secret botId=${botId} result=missing-file`);
|
|
@@ -16349,7 +16371,7 @@ app11.post("/", async (c) => {
|
|
|
16349
16371
|
console.error(
|
|
16350
16372
|
`${TAG26} op=inbound-kind botId=${botId} update=${shape.envelope} content=${shape.content} isEdit=${shape.isEdit ? "yes" : "no"}`
|
|
16351
16373
|
);
|
|
16352
|
-
const decision = routeTelegramUpdate({ update, entry });
|
|
16374
|
+
const decision = routeTelegramUpdate({ update, entry: entry2 });
|
|
16353
16375
|
noteInboundKind(shape.primaryKind, decision.kind === "dispatch");
|
|
16354
16376
|
if (decision.kind === "ignore") {
|
|
16355
16377
|
console.error(`${TAG26} op=ignore botId=${botId} reason=${decision.reason}`);
|
|
@@ -16369,7 +16391,7 @@ app11.post("/", async (c) => {
|
|
|
16369
16391
|
if (decision.allowed) {
|
|
16370
16392
|
const cbKey = telegramChannelKey(botId, decision.senderId);
|
|
16371
16393
|
const cbAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16372
|
-
const cbAdminUserId =
|
|
16394
|
+
const cbAdminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: decision.senderId }) : null;
|
|
16373
16395
|
appendMessage2(accountId, {
|
|
16374
16396
|
messageId: `telegram:${accountId}:${cbKey}:cb:${decision.callbackId}`,
|
|
16375
16397
|
sessionId: cbAdminUserId ? adminSessionIdFor(accountId, decision.senderId, cbAdminUserId) : adminSessionIdFor(accountId, decision.senderId, void 0, botId),
|
|
@@ -16380,13 +16402,13 @@ app11.post("/", async (c) => {
|
|
|
16380
16402
|
senderName: decision.senderDisplay,
|
|
16381
16403
|
chatId: String(decision.chatId),
|
|
16382
16404
|
channelKey: cbKey,
|
|
16383
|
-
scope:
|
|
16405
|
+
scope: entry2.role === "admin" ? "admin" : "public",
|
|
16384
16406
|
origin: "inbound",
|
|
16385
16407
|
createdAt: cbAt
|
|
16386
16408
|
});
|
|
16387
16409
|
}
|
|
16388
16410
|
const answerStartedAt = Date.now();
|
|
16389
|
-
const answered3 = await answerTelegramCallback(
|
|
16411
|
+
const answered3 = await answerTelegramCallback(entry2.token, decision.callbackId);
|
|
16390
16412
|
const outcome = !answered3.ok ? "error" : decision.allowed ? "stored" : "refused";
|
|
16391
16413
|
console.error(
|
|
16392
16414
|
`${TAG26} op=callback-answered botId=${botId} callbackId=${decision.callbackId} ms=${Date.now() - answerStartedAt} outcome=${outcome}${answered3.error ? ` error=${logValue(answered3.error)}` : ""}`
|
|
@@ -16396,8 +16418,8 @@ app11.post("/", async (c) => {
|
|
|
16396
16418
|
const { senderId, chatId, text } = decision;
|
|
16397
16419
|
console.error(`${TAG26} op=access botId=${botId} senderId=${senderId} allowed=true reason=${decision.reason}`);
|
|
16398
16420
|
const channelKey = telegramChannelKey(botId, senderId);
|
|
16399
|
-
const scope =
|
|
16400
|
-
const adminUserId =
|
|
16421
|
+
const scope = entry2.role === "admin" ? "admin" : "public";
|
|
16422
|
+
const adminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: senderId }) : null;
|
|
16401
16423
|
const sessionId = adminUserId ? adminSessionIdFor(accountId, senderId, adminUserId) : adminSessionIdFor(accountId, senderId, void 0, botId);
|
|
16402
16424
|
const inboundAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16403
16425
|
const editSuffix = decision.isEdit ? `:edit:${decision.editDate}` : "";
|
|
@@ -16473,7 +16495,7 @@ app11.post("/", async (c) => {
|
|
|
16473
16495
|
});
|
|
16474
16496
|
}
|
|
16475
16497
|
}
|
|
16476
|
-
const active =
|
|
16498
|
+
const active = entry2.role === "admin" ? true : isActiveAgentSlug(accountDir, entry2.agent);
|
|
16477
16499
|
console.error(`${TAG26} op=agent botId=${botId} slug=${agentSlug} active=${active}`);
|
|
16478
16500
|
if (!active) {
|
|
16479
16501
|
console.error(`${TAG26} op=reject reason=agent-inactive botId=${botId} agent=${agentSlug}`);
|
|
@@ -16485,7 +16507,7 @@ app11.post("/", async (c) => {
|
|
|
16485
16507
|
return c.json({ ok: true }, 200);
|
|
16486
16508
|
}
|
|
16487
16509
|
const reply = async (replyText) => {
|
|
16488
|
-
const sent = await sendTelegramText(
|
|
16510
|
+
const sent = await sendTelegramText(entry2.token, chatId, replyText);
|
|
16489
16511
|
if (sent.ok) {
|
|
16490
16512
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
16491
16513
|
appendMessage2(accountId, {
|
|
@@ -16529,7 +16551,7 @@ app11.post("/", async (c) => {
|
|
|
16529
16551
|
});
|
|
16530
16552
|
if (line) console.error(line);
|
|
16531
16553
|
}
|
|
16532
|
-
const media = await downloadTelegramMedia({ token:
|
|
16554
|
+
const media = await downloadTelegramMedia({ token: entry2.token, botId, items: decision.media });
|
|
16533
16555
|
void (async () => {
|
|
16534
16556
|
const heard = await transcribeTelegramAudio({
|
|
16535
16557
|
botId,
|
|
@@ -16541,10 +16563,10 @@ app11.post("/", async (c) => {
|
|
|
16541
16563
|
accountId,
|
|
16542
16564
|
accountDir,
|
|
16543
16565
|
botId,
|
|
16544
|
-
botToken:
|
|
16566
|
+
botToken: entry2.token,
|
|
16545
16567
|
agentSlug,
|
|
16546
16568
|
senderId,
|
|
16547
|
-
role:
|
|
16569
|
+
role: entry2.role,
|
|
16548
16570
|
personId: null,
|
|
16549
16571
|
// Task 2078 — the admin person this chat is bound to (the users.json
|
|
16550
16572
|
// telegram binding). It is the SUBJECT of the session id, so one admin's
|
|
@@ -16876,16 +16898,27 @@ function writeAdminEntry(input) {
|
|
|
16876
16898
|
}
|
|
16877
16899
|
const rowsBefore = users.map((u) => id8(u.userId)).join(",");
|
|
16878
16900
|
const existing = users.findIndex((u) => u.userId === input.userId);
|
|
16901
|
+
const pinRotated = existing !== -1 && users[existing].pin !== input.pin;
|
|
16902
|
+
const boundFields = [
|
|
16903
|
+
input.phone !== void 0 ? "phone" : null,
|
|
16904
|
+
input.telegram !== void 0 ? "telegram" : null
|
|
16905
|
+
].filter(Boolean);
|
|
16879
16906
|
if (existing === -1) {
|
|
16880
|
-
|
|
16907
|
+
const row = { userId: input.userId, pin: input.pin };
|
|
16908
|
+
if (input.phone !== void 0) row.phone = input.phone;
|
|
16909
|
+
if (input.telegram !== void 0) row.telegram = input.telegram;
|
|
16910
|
+
users.push(row);
|
|
16881
16911
|
} else {
|
|
16882
|
-
|
|
16912
|
+
const row = { ...users[existing], pin: input.pin };
|
|
16913
|
+
if (input.phone !== void 0) row.phone = input.phone;
|
|
16914
|
+
if (input.telegram !== void 0) row.telegram = input.telegram;
|
|
16915
|
+
users[existing] = row;
|
|
16883
16916
|
}
|
|
16884
16917
|
writeFileAtomic(input.usersFile, JSON.stringify(users, null, 2) + "\n", 384);
|
|
16885
16918
|
result.usersJsonResult = "ok";
|
|
16886
16919
|
appendUsersAuditLine(input.audit, {
|
|
16887
|
-
action: existing === -1 ? "add" : "set-pin",
|
|
16888
|
-
field: existing === -1 ? "row" : "pin",
|
|
16920
|
+
action: existing === -1 ? "add" : pinRotated ? "set-pin" : "set-binding",
|
|
16921
|
+
field: existing === -1 ? "row" : pinRotated ? "pin" : boundFields.join(","),
|
|
16889
16922
|
rowsBefore,
|
|
16890
16923
|
rowsAfter: users.map((u) => id8(u.userId)).join(",")
|
|
16891
16924
|
});
|
|
@@ -16942,9 +16975,9 @@ function computeAdminStoreDivergence(input) {
|
|
|
16942
16975
|
result.errors.push({ source: input.accountsDir, detail: err instanceof Error ? err.message : String(err) });
|
|
16943
16976
|
return result;
|
|
16944
16977
|
}
|
|
16945
|
-
for (const
|
|
16946
|
-
if (
|
|
16947
|
-
const accountDir = join35(input.accountsDir,
|
|
16978
|
+
for (const entry2 of entries2) {
|
|
16979
|
+
if (entry2.startsWith(".")) continue;
|
|
16980
|
+
const accountDir = join35(input.accountsDir, entry2);
|
|
16948
16981
|
try {
|
|
16949
16982
|
if (!statSync18(accountDir).isDirectory()) continue;
|
|
16950
16983
|
} catch {
|
|
@@ -17395,7 +17428,7 @@ var onboarding_default = app13;
|
|
|
17395
17428
|
|
|
17396
17429
|
// server/routes/dispatch.ts
|
|
17397
17430
|
init_dist();
|
|
17398
|
-
import { resolve as
|
|
17431
|
+
import { resolve as resolve23 } from "path";
|
|
17399
17432
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
17400
17433
|
|
|
17401
17434
|
// ../lib/dispatch-read/src/config.ts
|
|
@@ -17439,16 +17472,26 @@ function terminalVisitStatuses(cfg) {
|
|
|
17439
17472
|
}
|
|
17440
17473
|
|
|
17441
17474
|
// ../lib/dispatch-read/src/session.ts
|
|
17442
|
-
import {
|
|
17475
|
+
import { readFileSync as readFileSync32 } from "fs";
|
|
17476
|
+
import { resolve as resolve22 } from "path";
|
|
17443
17477
|
import neo4j from "neo4j-driver";
|
|
17444
17478
|
var driver = null;
|
|
17445
17479
|
function password() {
|
|
17446
17480
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
17447
|
-
const
|
|
17448
|
-
if (
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17481
|
+
const root = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT;
|
|
17482
|
+
if (!root) {
|
|
17483
|
+
throw new Error(
|
|
17484
|
+
"[dispatch-read] neither MAXY_PLATFORM_ROOT nor PLATFORM_ROOT is set \u2014 cannot locate config/.neo4j-password"
|
|
17485
|
+
);
|
|
17486
|
+
}
|
|
17487
|
+
const passwordFile = resolve22(root, "config/.neo4j-password");
|
|
17488
|
+
try {
|
|
17489
|
+
return readFileSync32(passwordFile, "utf-8").trim();
|
|
17490
|
+
} catch (err) {
|
|
17491
|
+
throw new Error(
|
|
17492
|
+
`[dispatch-read] Neo4j password not found. Expected at ${passwordFile}, or in NEO4J_PASSWORD. Read failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17493
|
+
);
|
|
17494
|
+
}
|
|
17452
17495
|
}
|
|
17453
17496
|
function getDriver() {
|
|
17454
17497
|
if (driver) return driver;
|
|
@@ -17888,7 +17931,7 @@ function accountFor(cacheKey) {
|
|
|
17888
17931
|
if (!cacheKey) return null;
|
|
17889
17932
|
const accountId = getAccountIdForSession(cacheKey);
|
|
17890
17933
|
if (!accountId) return null;
|
|
17891
|
-
return { accountId, accountDir:
|
|
17934
|
+
return { accountId, accountDir: resolve23(ACCOUNTS_DIR, accountId) };
|
|
17892
17935
|
}
|
|
17893
17936
|
var app14 = new Hono();
|
|
17894
17937
|
app14.use("*", requireAdminSession);
|
|
@@ -18712,7 +18755,7 @@ init_dist();
|
|
|
18712
18755
|
init_paths();
|
|
18713
18756
|
init_remote_auth();
|
|
18714
18757
|
init_loop_registry();
|
|
18715
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
18758
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync27, renameSync as renameSync9, statSync as statSync19 } from "fs";
|
|
18716
18759
|
import { join as join38 } from "path";
|
|
18717
18760
|
var CLIENT_ERRORS_LOG = join38(LOG_DIR, "client-errors.log");
|
|
18718
18761
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
@@ -18771,7 +18814,7 @@ function stackHead(stack) {
|
|
|
18771
18814
|
}
|
|
18772
18815
|
function rotateIfNeeded() {
|
|
18773
18816
|
try {
|
|
18774
|
-
if (!
|
|
18817
|
+
if (!existsSync27(CLIENT_ERRORS_LOG)) return;
|
|
18775
18818
|
const stats = statSync19(CLIENT_ERRORS_LOG);
|
|
18776
18819
|
if (stats.size < MAX_LOG_SIZE) return;
|
|
18777
18820
|
renameSync9(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
|
|
@@ -18987,15 +19030,15 @@ function operatorRoleFor(config, userId) {
|
|
|
18987
19030
|
async function buildAccountOptionList(accounts, userId) {
|
|
18988
19031
|
const houseAccount = accounts.find((a) => a.config.role === "house");
|
|
18989
19032
|
console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
|
|
18990
|
-
const
|
|
19033
|
+
const resolve53 = async (a) => {
|
|
18991
19034
|
const businessName = await fetchAccountName(a.accountId) || void 0;
|
|
18992
19035
|
return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
|
|
18993
19036
|
};
|
|
18994
19037
|
const houseOptions = await Promise.all(
|
|
18995
|
-
accounts.filter((a) => a.config.role === "house").map(
|
|
19038
|
+
accounts.filter((a) => a.config.role === "house").map(resolve53)
|
|
18996
19039
|
);
|
|
18997
19040
|
const nonHouseOptions = await Promise.all(
|
|
18998
|
-
accounts.filter((a) => a.config.role !== "house").map(
|
|
19041
|
+
accounts.filter((a) => a.config.role !== "house").map(resolve53)
|
|
18999
19042
|
);
|
|
19000
19043
|
const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
|
|
19001
19044
|
nonHouseOptions.sort((x, y) => {
|
|
@@ -19137,18 +19180,18 @@ var accounts_default = app18;
|
|
|
19137
19180
|
init_dist();
|
|
19138
19181
|
init_paths();
|
|
19139
19182
|
init_claude_agent();
|
|
19140
|
-
import { existsSync as
|
|
19141
|
-
import { resolve as
|
|
19183
|
+
import { existsSync as existsSync29, readdirSync as readdirSync20, readFileSync as readFileSync33, statSync as statSync20 } from "fs";
|
|
19184
|
+
import { resolve as resolve24, basename as basename11 } from "path";
|
|
19142
19185
|
|
|
19143
19186
|
// app/lib/logs-read-resolve.ts
|
|
19144
|
-
import { existsSync as
|
|
19187
|
+
import { existsSync as existsSync28 } from "fs";
|
|
19145
19188
|
import { join as join39 } from "path";
|
|
19146
19189
|
function resolveSessionLogPaths(filename, logDirs) {
|
|
19147
19190
|
const tried = [filename];
|
|
19148
19191
|
const hits = [];
|
|
19149
19192
|
for (const dir of logDirs) {
|
|
19150
19193
|
const fullPath = join39(dir, filename);
|
|
19151
|
-
if (
|
|
19194
|
+
if (existsSync28(fullPath)) {
|
|
19152
19195
|
hits.push({ path: fullPath, dir });
|
|
19153
19196
|
}
|
|
19154
19197
|
}
|
|
@@ -19166,13 +19209,13 @@ app19.get("/", async (c) => {
|
|
|
19166
19209
|
const sessionIdParam = c.req.query("sessionId");
|
|
19167
19210
|
const cacheKeyParam = c.req.query("cacheKey");
|
|
19168
19211
|
const download = c.req.query("download") === "1";
|
|
19169
|
-
const logDirs = listValidAccounts().slice().sort((a, b) => a.accountId.localeCompare(b.accountId)).map((a) =>
|
|
19212
|
+
const logDirs = listValidAccounts().slice().sort((a, b) => a.accountId.localeCompare(b.accountId)).map((a) => resolve24(a.accountDir, "logs"));
|
|
19170
19213
|
logDirs.push(LOG_DIR);
|
|
19171
19214
|
if (fileParam) {
|
|
19172
19215
|
const safe = basename11(fileParam);
|
|
19173
19216
|
const searched = [];
|
|
19174
19217
|
for (const dir of logDirs) {
|
|
19175
|
-
const filePath =
|
|
19218
|
+
const filePath = resolve24(dir, safe);
|
|
19176
19219
|
searched.push(filePath);
|
|
19177
19220
|
try {
|
|
19178
19221
|
const buffer = readFileSync33(filePath);
|
|
@@ -19275,7 +19318,7 @@ app19.get("/", async (c) => {
|
|
|
19275
19318
|
const seen = /* @__PURE__ */ new Set();
|
|
19276
19319
|
const logs = {};
|
|
19277
19320
|
for (const dir of logDirs) {
|
|
19278
|
-
if (!
|
|
19321
|
+
if (!existsSync29(dir)) continue;
|
|
19279
19322
|
let files;
|
|
19280
19323
|
try {
|
|
19281
19324
|
files = readdirSync20(dir).filter((f) => f.endsWith(".log"));
|
|
@@ -19284,10 +19327,10 @@ app19.get("/", async (c) => {
|
|
|
19284
19327
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
19285
19328
|
continue;
|
|
19286
19329
|
}
|
|
19287
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync20(
|
|
19330
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync20(resolve24(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
19288
19331
|
seen.add(name);
|
|
19289
19332
|
try {
|
|
19290
|
-
const content = readFileSync33(
|
|
19333
|
+
const content = readFileSync33(resolve24(dir, name));
|
|
19291
19334
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
19292
19335
|
logs[name] = tail.trim() || "(empty)";
|
|
19293
19336
|
} catch (err) {
|
|
@@ -19340,12 +19383,12 @@ init_claude_agent();
|
|
|
19340
19383
|
init_data_path();
|
|
19341
19384
|
init_attachments();
|
|
19342
19385
|
import { readFile as readFile6, readdir as readdir2 } from "fs/promises";
|
|
19343
|
-
import { existsSync as
|
|
19344
|
-
import { resolve as
|
|
19386
|
+
import { existsSync as existsSync30 } from "fs";
|
|
19387
|
+
import { resolve as resolve25 } from "path";
|
|
19345
19388
|
var app21 = new Hono();
|
|
19346
19389
|
var ATTACHMENT_ID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
19347
19390
|
function uploadsDirFor(accountId, attachmentId) {
|
|
19348
|
-
return
|
|
19391
|
+
return resolve25(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
|
|
19349
19392
|
}
|
|
19350
19393
|
app21.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
19351
19394
|
const attachmentId = c.req.param("attachmentId");
|
|
@@ -19363,12 +19406,12 @@ app21.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19363
19406
|
}
|
|
19364
19407
|
let accountId = pinnedAccountId;
|
|
19365
19408
|
let dir = uploadsDirFor(accountId, attachmentId);
|
|
19366
|
-
let found =
|
|
19409
|
+
let found = existsSync30(dir);
|
|
19367
19410
|
if (!found) {
|
|
19368
19411
|
for (const candidate of listValidAccounts()) {
|
|
19369
19412
|
if (candidate.accountId === pinnedAccountId) continue;
|
|
19370
19413
|
const candidateDir = uploadsDirFor(candidate.accountId, attachmentId);
|
|
19371
|
-
if (
|
|
19414
|
+
if (existsSync30(candidateDir)) {
|
|
19372
19415
|
accountId = candidate.accountId;
|
|
19373
19416
|
dir = candidateDir;
|
|
19374
19417
|
found = true;
|
|
@@ -19382,8 +19425,8 @@ app21.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19382
19425
|
if (!found) {
|
|
19383
19426
|
return new Response("Not found", { status: 404 });
|
|
19384
19427
|
}
|
|
19385
|
-
const metaPath =
|
|
19386
|
-
if (!
|
|
19428
|
+
const metaPath = resolve25(dir, `${attachmentId}.meta.json`);
|
|
19429
|
+
if (!existsSync30(metaPath)) {
|
|
19387
19430
|
return new Response("Not found", { status: 404 });
|
|
19388
19431
|
}
|
|
19389
19432
|
let meta;
|
|
@@ -19397,7 +19440,7 @@ app21.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19397
19440
|
if (!dataFile) {
|
|
19398
19441
|
return new Response("Not found", { status: 404 });
|
|
19399
19442
|
}
|
|
19400
|
-
const filePath =
|
|
19443
|
+
const filePath = resolve25(dir, dataFile);
|
|
19401
19444
|
const buffer = await readFile6(filePath);
|
|
19402
19445
|
const inlineSafe = isInlineSafeMime(meta.mimeType);
|
|
19403
19446
|
const safeName2 = meta.filename.replace(/["\\\r\n]/g, "_");
|
|
@@ -19423,7 +19466,7 @@ init_neo4j_store();
|
|
|
19423
19466
|
init_data_path();
|
|
19424
19467
|
init_attachments();
|
|
19425
19468
|
import { readFile as readFile7 } from "fs/promises";
|
|
19426
|
-
import { existsSync as
|
|
19469
|
+
import { existsSync as existsSync31, statSync as statSync21 } from "fs";
|
|
19427
19470
|
var app22 = new Hono();
|
|
19428
19471
|
app22.get("/", requireAdminSession, async (c) => {
|
|
19429
19472
|
const cacheKey = c.var.cacheKey;
|
|
@@ -19444,7 +19487,7 @@ app22.get("/", requireAdminSession, async (c) => {
|
|
|
19444
19487
|
return c.json({ error: "Not found" }, 404);
|
|
19445
19488
|
}
|
|
19446
19489
|
const resolution = resolveClaudeUploadsPath(`${sessionId}/${file}`);
|
|
19447
|
-
if (!resolution.ok || !
|
|
19490
|
+
if (!resolution.ok || !existsSync31(resolution.absolute) || !statSync21(resolution.absolute).isFile()) {
|
|
19448
19491
|
console.log(`[session-upload] op=miss session=${short2} file=${file} status=404 reason=absent`);
|
|
19449
19492
|
return c.json({ error: "Not found" }, 404);
|
|
19450
19493
|
}
|
|
@@ -19470,12 +19513,12 @@ init_helpers();
|
|
|
19470
19513
|
init_account_scope();
|
|
19471
19514
|
init_neo4j_store();
|
|
19472
19515
|
init_account();
|
|
19473
|
-
import { resolve as
|
|
19474
|
-
import { readdirSync as readdirSync22, readFileSync as readFileSync35, existsSync as
|
|
19516
|
+
import { resolve as resolve26 } from "path";
|
|
19517
|
+
import { readdirSync as readdirSync22, readFileSync as readFileSync35, existsSync as existsSync33, rmSync as rmSync5, renameSync as renameSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync15, chmodSync } from "fs";
|
|
19475
19518
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
19476
19519
|
|
|
19477
19520
|
// ../services/claude-session-manager/src/tool-surface.ts
|
|
19478
|
-
import { readFileSync as readFileSync34, readdirSync as readdirSync21, statSync as statSync22, existsSync as
|
|
19521
|
+
import { readFileSync as readFileSync34, readdirSync as readdirSync21, statSync as statSync22, existsSync as existsSync32 } from "fs";
|
|
19479
19522
|
import { createHash as createHash5 } from "crypto";
|
|
19480
19523
|
import { join as join40 } from "path";
|
|
19481
19524
|
|
|
@@ -19721,7 +19764,7 @@ function parseMcpBlock(frontmatter, pluginDir) {
|
|
|
19721
19764
|
function loadToolSurface(pluginsRoots) {
|
|
19722
19765
|
const roots = Array.isArray(pluginsRoots) ? pluginsRoots : [pluginsRoots];
|
|
19723
19766
|
for (const r of roots) {
|
|
19724
|
-
if (!
|
|
19767
|
+
if (!existsSync32(r)) throw new ToolSurfaceError("plugins-root-missing", r);
|
|
19725
19768
|
}
|
|
19726
19769
|
const admin = /* @__PURE__ */ new Set();
|
|
19727
19770
|
const pub = /* @__PURE__ */ new Set();
|
|
@@ -19737,7 +19780,7 @@ function loadToolSurface(pluginsRoots) {
|
|
|
19737
19780
|
return;
|
|
19738
19781
|
}
|
|
19739
19782
|
const manifestPath = join40(pluginDir, "PLUGIN.md");
|
|
19740
|
-
if (!
|
|
19783
|
+
if (!existsSync32(manifestPath)) return;
|
|
19741
19784
|
const raw = readFileSync34(manifestPath, "utf-8");
|
|
19742
19785
|
const frontmatter = extractFrontmatter(raw, plugin);
|
|
19743
19786
|
const tools = parseToolsBlock(frontmatter, plugin);
|
|
@@ -19918,7 +19961,7 @@ function agentRiskClass(tools, riskByTool) {
|
|
|
19918
19961
|
// server/routes/admin/agents.ts
|
|
19919
19962
|
var SLUG_UNSAFE = (s) => s.includes("/") || s.includes("..") || s.includes("\\");
|
|
19920
19963
|
function subdirs(dir) {
|
|
19921
|
-
if (!
|
|
19964
|
+
if (!existsSync33(dir)) return [];
|
|
19922
19965
|
try {
|
|
19923
19966
|
return readdirSync22(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
19924
19967
|
} catch {
|
|
@@ -19939,7 +19982,7 @@ var SPECIALISTS_DIR = ["specialists", "agents"];
|
|
|
19939
19982
|
var QUARANTINE_DIR = ["specialists", "agents-disabled"];
|
|
19940
19983
|
var DISABLED_STORE = "agents-disabled.json";
|
|
19941
19984
|
function loadRiskSurface() {
|
|
19942
|
-
const roots = [
|
|
19985
|
+
const roots = [resolve26(PLATFORM_ROOT, "plugins"), resolve26(PLATFORM_ROOT, "..", "premium-plugins")].filter((r) => existsSync33(r));
|
|
19943
19986
|
try {
|
|
19944
19987
|
return { riskByTool: loadToolSurface(roots).riskByTool, failed: null };
|
|
19945
19988
|
} catch (err) {
|
|
@@ -19950,8 +19993,8 @@ function loadRiskSurface() {
|
|
|
19950
19993
|
}
|
|
19951
19994
|
}
|
|
19952
19995
|
function readDisabledStoreStrict(accountDir) {
|
|
19953
|
-
const p =
|
|
19954
|
-
if (!
|
|
19996
|
+
const p = resolve26(accountDir, DISABLED_STORE);
|
|
19997
|
+
if (!existsSync33(p)) return /* @__PURE__ */ new Set();
|
|
19955
19998
|
const parsed = JSON.parse(readFileSync35(p, "utf-8"));
|
|
19956
19999
|
if (!Array.isArray(parsed.disabled)) throw new Error("disabled is not an array");
|
|
19957
20000
|
return new Set(parsed.disabled.filter((x) => typeof x === "string"));
|
|
@@ -19965,40 +20008,88 @@ function readDisabledStore(accountDir) {
|
|
|
19965
20008
|
return /* @__PURE__ */ new Set();
|
|
19966
20009
|
}
|
|
19967
20010
|
}
|
|
19968
|
-
function
|
|
19969
|
-
const pluginsDir =
|
|
19970
|
-
const
|
|
20011
|
+
function authoredAgentFiles(accountDir) {
|
|
20012
|
+
const pluginsDir = resolve26(accountDir, "plugins");
|
|
20013
|
+
const byName = /* @__PURE__ */ new Map();
|
|
19971
20014
|
for (const plugin of subdirs(pluginsDir)) {
|
|
19972
|
-
const dir =
|
|
19973
|
-
if (!
|
|
20015
|
+
const dir = resolve26(pluginsDir, plugin, "agents");
|
|
20016
|
+
if (!existsSync33(dir)) continue;
|
|
19974
20017
|
try {
|
|
19975
|
-
for (const
|
|
20018
|
+
for (const entry2 of readdirSync22(dir)) {
|
|
20019
|
+
if (!entry2.endsWith(".md")) continue;
|
|
20020
|
+
const path = resolve26(dir, entry2);
|
|
20021
|
+
const paths = byName.get(entry2);
|
|
20022
|
+
if (paths) paths.push(path);
|
|
20023
|
+
else byName.set(entry2, [path]);
|
|
20024
|
+
}
|
|
19976
20025
|
} catch (err) {
|
|
19977
20026
|
console.error(`[admin/agents] op=authored-scan read-failed plugin=${plugin} error="${err}"`);
|
|
19978
20027
|
}
|
|
19979
20028
|
}
|
|
19980
|
-
return
|
|
20029
|
+
return byName;
|
|
19981
20030
|
}
|
|
19982
20031
|
function shippedSourceDirs(accountDir) {
|
|
19983
20032
|
return [
|
|
19984
|
-
|
|
19985
|
-
|
|
19986
|
-
|
|
20033
|
+
resolve26(accountDir, ...SPECIALISTS_DIR),
|
|
20034
|
+
resolve26(accountDir, ...QUARANTINE_DIR),
|
|
20035
|
+
resolve26(PLATFORM_ROOT, "templates", "specialists", "agents")
|
|
19987
20036
|
];
|
|
19988
20037
|
}
|
|
20038
|
+
function readRowFields(path) {
|
|
20039
|
+
const { fm } = splitFrontmatter(readFileSync35(path, "utf-8"));
|
|
20040
|
+
return {
|
|
20041
|
+
name: fm.name ?? "",
|
|
20042
|
+
description: fm.description ?? "",
|
|
20043
|
+
tools: new Set(parseToolsLine(fm.tools))
|
|
20044
|
+
};
|
|
20045
|
+
}
|
|
20046
|
+
function divergentFields(a, b) {
|
|
20047
|
+
const out = [];
|
|
20048
|
+
if (a.name !== b.name) out.push("name");
|
|
20049
|
+
if (a.description !== b.description) out.push("description");
|
|
20050
|
+
if (a.tools.size !== b.tools.size || [...a.tools].some((t) => !b.tools.has(t))) out.push("tools");
|
|
20051
|
+
return out;
|
|
20052
|
+
}
|
|
20053
|
+
function compareToAuthored(candidatePath, authoredPaths, filename) {
|
|
20054
|
+
let candidate;
|
|
20055
|
+
try {
|
|
20056
|
+
candidate = readRowFields(candidatePath);
|
|
20057
|
+
} catch (err) {
|
|
20058
|
+
console.error(`[admin/agents] op=list-shipped-compare-failed file=${filename} side=found reason="${err}"`);
|
|
20059
|
+
return { verdict: "unreadable" };
|
|
20060
|
+
}
|
|
20061
|
+
let firstDivergence = null;
|
|
20062
|
+
let anyUnreadable = false;
|
|
20063
|
+
for (const path of authoredPaths) {
|
|
20064
|
+
let authored;
|
|
20065
|
+
try {
|
|
20066
|
+
authored = readRowFields(path);
|
|
20067
|
+
} catch (err) {
|
|
20068
|
+
console.error(`[admin/agents] op=list-shipped-compare-failed file=${filename} side=authored reason="${err}"`);
|
|
20069
|
+
anyUnreadable = true;
|
|
20070
|
+
continue;
|
|
20071
|
+
}
|
|
20072
|
+
const fields = divergentFields(candidate, authored);
|
|
20073
|
+
if (fields.length === 0) return { verdict: "matched" };
|
|
20074
|
+
firstDivergence ??= { authoredPath: path, fields };
|
|
20075
|
+
}
|
|
20076
|
+
if (firstDivergence) return { verdict: "divergent", ...firstDivergence };
|
|
20077
|
+
if (anyUnreadable) return { verdict: "unreadable" };
|
|
20078
|
+
return { verdict: "unreadable" };
|
|
20079
|
+
}
|
|
19989
20080
|
function writeDisabledStore(accountDir, disabled) {
|
|
19990
|
-
const path =
|
|
20081
|
+
const path = resolve26(accountDir, DISABLED_STORE);
|
|
19991
20082
|
writeFileSync15(path, `${JSON.stringify({ disabled: [...disabled].sort() }, null, 2)}
|
|
19992
20083
|
`, { mode: 384 });
|
|
19993
20084
|
chmodSync(path, 384);
|
|
19994
20085
|
}
|
|
19995
|
-
function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed,
|
|
20086
|
+
function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, authoredFiles) {
|
|
19996
20087
|
const dirs = shippedSourceDirs(accountDir);
|
|
19997
20088
|
const names = /* @__PURE__ */ new Set();
|
|
19998
20089
|
for (const dir of dirs) {
|
|
19999
|
-
if (!
|
|
20090
|
+
if (!existsSync33(dir)) continue;
|
|
20000
20091
|
try {
|
|
20001
|
-
for (const
|
|
20092
|
+
for (const entry2 of readdirSync22(dir)) if (entry2.endsWith(".md")) names.add(entry2);
|
|
20002
20093
|
} catch (err) {
|
|
20003
20094
|
console.error(`[admin/agents] op=list-shipped read-failed dir=${dir} error="${err}"`);
|
|
20004
20095
|
}
|
|
@@ -20006,16 +20097,27 @@ function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, author
|
|
|
20006
20097
|
const shipped = [];
|
|
20007
20098
|
let shippedSkipped = 0;
|
|
20008
20099
|
let deduped = 0;
|
|
20100
|
+
let divergent = 0;
|
|
20009
20101
|
for (const filename of [...names].sort()) {
|
|
20010
|
-
const source = dirs.find((d) =>
|
|
20102
|
+
const source = dirs.find((d) => existsSync33(resolve26(d, filename)));
|
|
20011
20103
|
if (!source) continue;
|
|
20012
|
-
|
|
20013
|
-
|
|
20014
|
-
|
|
20015
|
-
|
|
20104
|
+
const authoredPaths = source === dirs[0] || source === dirs[1] ? authoredFiles.get(filename) : void 0;
|
|
20105
|
+
if (authoredPaths) {
|
|
20106
|
+
const cmp = compareToAuthored(resolve26(source, filename), authoredPaths, filename);
|
|
20107
|
+
if (cmp.verdict === "matched") {
|
|
20108
|
+
deduped++;
|
|
20109
|
+
console.log(`[admin/agents] op=list-shipped-dedup file=${filename}`);
|
|
20110
|
+
continue;
|
|
20111
|
+
}
|
|
20112
|
+
if (cmp.verdict === "divergent") {
|
|
20113
|
+
divergent++;
|
|
20114
|
+
console.log(
|
|
20115
|
+
`[admin/agents] op=list-shipped-divergent file=${filename} found=${resolve26(source, filename)} authored=${cmp.authoredPath} fields=${cmp.fields.join(",")}`
|
|
20116
|
+
);
|
|
20117
|
+
}
|
|
20016
20118
|
}
|
|
20017
20119
|
try {
|
|
20018
|
-
const { fm } = splitFrontmatter(readFileSync35(
|
|
20120
|
+
const { fm } = splitFrontmatter(readFileSync35(resolve26(source, filename), "utf-8"));
|
|
20019
20121
|
if (!fm.name) {
|
|
20020
20122
|
shippedSkipped++;
|
|
20021
20123
|
console.error(`[admin/agents] op=list-shipped-skip file=${filename} reason=no-name`);
|
|
@@ -20048,7 +20150,7 @@ function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, author
|
|
|
20048
20150
|
console.error(`[admin/agents] op=list-shipped-skip file=${filename} error="${err}"`);
|
|
20049
20151
|
}
|
|
20050
20152
|
}
|
|
20051
|
-
return { shipped, shippedSkipped, deduped };
|
|
20153
|
+
return { shipped, shippedSkipped, deduped, divergent };
|
|
20052
20154
|
}
|
|
20053
20155
|
function parseToolsLine(raw) {
|
|
20054
20156
|
return raw ? raw.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
@@ -20069,31 +20171,31 @@ function classify(tools, riskByTool, riskSurfaceFailed) {
|
|
|
20069
20171
|
return agentRiskClass(tools, riskByTool);
|
|
20070
20172
|
}
|
|
20071
20173
|
function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
|
|
20072
|
-
const pluginsDir =
|
|
20174
|
+
const pluginsDir = resolve26(accountDir, "plugins");
|
|
20073
20175
|
const specialists = [];
|
|
20074
20176
|
let specialistsSkipped = 0;
|
|
20075
20177
|
for (const plugin of subdirs(pluginsDir).sort()) {
|
|
20076
|
-
const agentsDirP =
|
|
20077
|
-
if (!
|
|
20178
|
+
const agentsDirP = resolve26(pluginsDir, plugin, "agents");
|
|
20179
|
+
if (!existsSync33(agentsDirP)) continue;
|
|
20078
20180
|
let entries2;
|
|
20079
20181
|
try {
|
|
20080
20182
|
entries2 = readdirSync22(agentsDirP, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
20081
20183
|
} catch {
|
|
20082
20184
|
continue;
|
|
20083
20185
|
}
|
|
20084
|
-
for (const
|
|
20085
|
-
if (!
|
|
20186
|
+
for (const entry2 of entries2) {
|
|
20187
|
+
if (!entry2.isFile() || !entry2.name.endsWith(".md")) continue;
|
|
20086
20188
|
try {
|
|
20087
|
-
const { fm } = splitFrontmatter(readFileSync35(
|
|
20189
|
+
const { fm } = splitFrontmatter(readFileSync35(resolve26(agentsDirP, entry2.name), "utf-8"));
|
|
20088
20190
|
if (!fm.name) {
|
|
20089
20191
|
specialistsSkipped++;
|
|
20090
|
-
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${
|
|
20192
|
+
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
|
|
20091
20193
|
continue;
|
|
20092
20194
|
}
|
|
20093
20195
|
const specialistTools = parseToolsLine(fm.tools);
|
|
20094
20196
|
const r = classify(specialistTools, riskByTool, riskSurfaceFailed);
|
|
20095
20197
|
specialists.push({
|
|
20096
|
-
slug:
|
|
20198
|
+
slug: entry2.name.replace(/\.md$/, ""),
|
|
20097
20199
|
displayName: fm.name,
|
|
20098
20200
|
kind: "specialist",
|
|
20099
20201
|
origin: "specialist",
|
|
@@ -20116,11 +20218,11 @@ function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
|
|
|
20116
20218
|
// have the surface claim an agent is stopped while it is still being
|
|
20117
20219
|
// spawned. Absence from the live dir alone is also not the test: a
|
|
20118
20220
|
// specialist that was never activated was never switched off either.
|
|
20119
|
-
disabled:
|
|
20221
|
+
disabled: existsSync33(resolve26(accountDir, ...QUARANTINE_DIR, entry2.name)) && !existsSync33(resolve26(accountDir, ...SPECIALISTS_DIR, entry2.name))
|
|
20120
20222
|
});
|
|
20121
20223
|
} catch {
|
|
20122
20224
|
specialistsSkipped++;
|
|
20123
|
-
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${
|
|
20225
|
+
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
|
|
20124
20226
|
}
|
|
20125
20227
|
}
|
|
20126
20228
|
}
|
|
@@ -20143,20 +20245,20 @@ app23.get("/", requireAdminSession, (c) => {
|
|
|
20143
20245
|
const disabled = readDisabledStore(account.accountDir);
|
|
20144
20246
|
const agents = [];
|
|
20145
20247
|
let skipped = 0;
|
|
20146
|
-
const agentsDir =
|
|
20147
|
-
if (
|
|
20248
|
+
const agentsDir = resolve26(account.accountDir, "agents");
|
|
20249
|
+
if (existsSync33(agentsDir)) {
|
|
20148
20250
|
try {
|
|
20149
20251
|
const entries2 = readdirSync22(agentsDir, { withFileTypes: true });
|
|
20150
|
-
for (const
|
|
20151
|
-
if (!
|
|
20152
|
-
if (
|
|
20153
|
-
const configPath3 =
|
|
20154
|
-
if (!
|
|
20252
|
+
for (const entry2 of entries2.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
20253
|
+
if (!entry2.isDirectory()) continue;
|
|
20254
|
+
if (entry2.name === "admin") continue;
|
|
20255
|
+
const configPath3 = resolve26(agentsDir, entry2.name, "config.json");
|
|
20256
|
+
if (!existsSync33(configPath3)) continue;
|
|
20155
20257
|
try {
|
|
20156
20258
|
const config = JSON.parse(readFileSync35(configPath3, "utf-8"));
|
|
20157
20259
|
agents.push({
|
|
20158
|
-
slug:
|
|
20159
|
-
displayName: config.displayName ??
|
|
20260
|
+
slug: entry2.name,
|
|
20261
|
+
displayName: config.displayName ?? entry2.name,
|
|
20160
20262
|
status: config.status ?? "unknown",
|
|
20161
20263
|
kind: "public",
|
|
20162
20264
|
origin: "public",
|
|
@@ -20169,7 +20271,7 @@ app23.get("/", requireAdminSession, (c) => {
|
|
|
20169
20271
|
});
|
|
20170
20272
|
} catch {
|
|
20171
20273
|
skipped++;
|
|
20172
|
-
console.error(`[admin/agents] failed to parse config.json for agent "${
|
|
20274
|
+
console.error(`[admin/agents] failed to parse config.json for agent "${entry2.name}" \u2014 skipping`);
|
|
20173
20275
|
}
|
|
20174
20276
|
}
|
|
20175
20277
|
} catch (err) {
|
|
@@ -20177,12 +20279,12 @@ app23.get("/", requireAdminSession, (c) => {
|
|
|
20177
20279
|
}
|
|
20178
20280
|
}
|
|
20179
20281
|
const { specialists, specialistsSkipped } = listSpecialists(account.accountDir, riskByTool, riskSurfaceFailed !== null);
|
|
20180
|
-
const { shipped, shippedSkipped, deduped } = listShipped(
|
|
20282
|
+
const { shipped, shippedSkipped, deduped, divergent } = listShipped(
|
|
20181
20283
|
account.accountDir,
|
|
20182
20284
|
riskByTool,
|
|
20183
20285
|
disabled,
|
|
20184
20286
|
riskSurfaceFailed !== null,
|
|
20185
|
-
|
|
20287
|
+
authoredAgentFiles(account.accountDir)
|
|
20186
20288
|
);
|
|
20187
20289
|
const rows = [...agents, ...specialists, ...shipped];
|
|
20188
20290
|
const acct = account.accountId.slice(0, 8);
|
|
@@ -20200,17 +20302,26 @@ app23.get("/", requireAdminSession, (c) => {
|
|
|
20200
20302
|
console.log(
|
|
20201
20303
|
`[agents] op=rule-census account=${acct} rules=${COMBINATION_RULES.length} classified=${classified} fired=${firedRules.size} never=${never.length > 0 ? never.join(",") : "none"}`
|
|
20202
20304
|
);
|
|
20203
|
-
console.log(`[admin/agents] op=list accountId=${acct} agents=${agents.length} specialists=${specialists.length} shipped=${shipped.length} skipped=${skipped} specialistsSkipped=${specialistsSkipped} shippedSkipped=${shippedSkipped} deduped=${deduped}`);
|
|
20305
|
+
console.log(`[admin/agents] op=list accountId=${acct} agents=${agents.length} specialists=${specialists.length} shipped=${shipped.length} skipped=${skipped} specialistsSkipped=${specialistsSkipped} shippedSkipped=${shippedSkipped} deduped=${deduped} divergent=${divergent}`);
|
|
20204
20306
|
return c.json({
|
|
20205
20307
|
agents: rows,
|
|
20206
20308
|
accountId: account.accountId,
|
|
20207
20309
|
skipped,
|
|
20208
20310
|
specialistsSkipped,
|
|
20209
20311
|
shippedSkipped,
|
|
20210
|
-
// How many live-directory candidates were dropped as
|
|
20211
|
-
//
|
|
20212
|
-
//
|
|
20312
|
+
// How many live-directory or quarantine-directory candidates were dropped as
|
|
20313
|
+
// authored duplicates — meaning the two files agree on the three fields the
|
|
20314
|
+
// row shows. `shipped + shippedSkipped + deduped` equals the number of
|
|
20315
|
+
// distinct `.md` names across the three source directories.
|
|
20213
20316
|
deduped,
|
|
20317
|
+
// How many candidates were KEPT because both files read and the three
|
|
20318
|
+
// fields DISAGREE. Never a fourth term in the identity above: a kept
|
|
20319
|
+
// candidate is parsed like any other, so it lands in `shipped`, or in
|
|
20320
|
+
// `shippedSkipped` when its own frontmatter carries no `name:`. A candidate
|
|
20321
|
+
// that could not be compared at all is not counted here — that is
|
|
20322
|
+
// `op=list-shipped-compare-failed`, and reporting it as drift would claim a
|
|
20323
|
+
// disagreement nobody read.
|
|
20324
|
+
divergent,
|
|
20214
20325
|
riskSurfaceFailed: riskSurfaceFailed !== null
|
|
20215
20326
|
});
|
|
20216
20327
|
});
|
|
@@ -20224,9 +20335,9 @@ app23.get("/:slug", requireAdminSession, (c) => {
|
|
|
20224
20335
|
if (SLUG_UNSAFE(slug)) return c.json({ error: "Invalid agent slug" }, 400);
|
|
20225
20336
|
if (c.req.query("origin") === "shipped") {
|
|
20226
20337
|
const dirs = shippedSourceDirs(account.accountDir);
|
|
20227
|
-
const source = dirs.find((d) =>
|
|
20338
|
+
const source = dirs.find((d) => existsSync33(resolve26(d, `${slug}.md`)));
|
|
20228
20339
|
if (!source) return c.json({ error: "Agent not found" }, 404);
|
|
20229
|
-
const { fm, body } = splitFrontmatter(readFileSync35(
|
|
20340
|
+
const { fm, body } = splitFrontmatter(readFileSync35(resolve26(source, `${slug}.md`), "utf-8"));
|
|
20230
20341
|
const { riskByTool, failed: failed2 } = loadRiskSurface();
|
|
20231
20342
|
const tools = parseToolsLine(fm.tools);
|
|
20232
20343
|
const r = classify(tools, riskByTool, failed2 !== null);
|
|
@@ -20256,10 +20367,10 @@ app23.get("/:slug", requireAdminSession, (c) => {
|
|
|
20256
20367
|
const pluginParam = c.req.query("plugin");
|
|
20257
20368
|
if (pluginParam) {
|
|
20258
20369
|
if (SLUG_UNSAFE(pluginParam)) return c.json({ error: "Invalid plugin" }, 400);
|
|
20259
|
-
const file =
|
|
20260
|
-
if (!
|
|
20370
|
+
const file = resolve26(account.accountDir, "plugins", pluginParam, "agents", `${slug}.md`);
|
|
20371
|
+
if (!existsSync33(file)) return c.json({ error: "Agent not found" }, 404);
|
|
20261
20372
|
const { fm, body } = splitFrontmatter(readFileSync35(file, "utf-8"));
|
|
20262
|
-
const skills = subdirs(
|
|
20373
|
+
const skills = subdirs(resolve26(account.accountDir, "plugins", pluginParam, "skills"));
|
|
20263
20374
|
const { riskByTool, failed: failed2 } = loadRiskSurface();
|
|
20264
20375
|
const tools = parseToolsLine(fm.tools);
|
|
20265
20376
|
const r = classify(tools, riskByTool, failed2 !== null);
|
|
@@ -20286,9 +20397,9 @@ app23.get("/:slug", requireAdminSession, (c) => {
|
|
|
20286
20397
|
}
|
|
20287
20398
|
});
|
|
20288
20399
|
}
|
|
20289
|
-
const agentDir =
|
|
20290
|
-
const configPath3 =
|
|
20291
|
-
if (!
|
|
20400
|
+
const agentDir = resolve26(account.accountDir, "agents", slug);
|
|
20401
|
+
const configPath3 = resolve26(agentDir, "config.json");
|
|
20402
|
+
if (!existsSync33(configPath3)) return c.json({ error: "Agent not found" }, 404);
|
|
20292
20403
|
let config;
|
|
20293
20404
|
try {
|
|
20294
20405
|
config = JSON.parse(readFileSync35(configPath3, "utf-8"));
|
|
@@ -20299,9 +20410,9 @@ app23.get("/:slug", requireAdminSession, (c) => {
|
|
|
20299
20410
|
const docs = {};
|
|
20300
20411
|
const present = {};
|
|
20301
20412
|
for (const { file, key: key2 } of OWNED_DOCS) {
|
|
20302
|
-
const p =
|
|
20413
|
+
const p = resolve26(agentDir, file);
|
|
20303
20414
|
try {
|
|
20304
|
-
if (
|
|
20415
|
+
if (existsSync33(p)) {
|
|
20305
20416
|
docs[key2] = readFileSync35(p, "utf-8");
|
|
20306
20417
|
present[key2] = true;
|
|
20307
20418
|
} else {
|
|
@@ -20350,7 +20461,7 @@ async function resolveWritableAccount(c, op, slug) {
|
|
|
20350
20461
|
return account;
|
|
20351
20462
|
}
|
|
20352
20463
|
function reconcileRoutingProse(accountDir, accountId, op, slug) {
|
|
20353
|
-
const script =
|
|
20464
|
+
const script = resolve26(PLATFORM_ROOT, "scripts", "lib", "agents-md-bootstrap.sh");
|
|
20354
20465
|
try {
|
|
20355
20466
|
execFileSync6("bash", [script, accountDir], { encoding: "utf-8", timeout: 1e4 });
|
|
20356
20467
|
return true;
|
|
@@ -20361,8 +20472,8 @@ function reconcileRoutingProse(accountDir, accountId, op, slug) {
|
|
|
20361
20472
|
}
|
|
20362
20473
|
}
|
|
20363
20474
|
function reconcileAuthoredRoutingLine(accountDir, accountId, op, source) {
|
|
20364
|
-
const agentsMd =
|
|
20365
|
-
if (!
|
|
20475
|
+
const agentsMd = resolve26(accountDir, "agents", "admin", "AGENTS.md");
|
|
20476
|
+
if (!existsSync33(agentsMd) || !existsSync33(source)) return false;
|
|
20366
20477
|
try {
|
|
20367
20478
|
const { fm } = splitFrontmatter(readFileSync35(source, "utf-8"));
|
|
20368
20479
|
if (!fm.name) return false;
|
|
@@ -20385,18 +20496,18 @@ app23.post("/:slug/disable", async (c) => {
|
|
|
20385
20496
|
const resolved = await resolveWritableAccount(c, "disable", slug);
|
|
20386
20497
|
if ("status" in resolved) return c.json({ error: resolved.error }, resolved.status);
|
|
20387
20498
|
const filename = `${slug}.md`;
|
|
20388
|
-
const live =
|
|
20389
|
-
const quarantined =
|
|
20390
|
-
if (!
|
|
20499
|
+
const live = resolve26(resolved.accountDir, ...SPECIALISTS_DIR, filename);
|
|
20500
|
+
const quarantined = resolve26(resolved.accountDir, ...QUARANTINE_DIR, filename);
|
|
20501
|
+
if (!authoredAgentFiles(resolved.accountDir).has(filename)) {
|
|
20391
20502
|
console.error(`[admin/agents] op=disable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=not-authored eligible=false outcome=refused reason=not-user-created`);
|
|
20392
20503
|
return c.json({ error: "Only an agent you created can be switched off here. A shipped agent is stopped by suspending the routines that dispatch it." }, 404);
|
|
20393
20504
|
}
|
|
20394
|
-
if (!
|
|
20505
|
+
if (!existsSync33(live)) {
|
|
20395
20506
|
console.error(`[admin/agents] op=disable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=specialist eligible=true outcome=refused reason=not-dispatchable`);
|
|
20396
20507
|
return c.json({ error: "This agent is not in the dispatchable set, so there is nothing to switch off." }, 409);
|
|
20397
20508
|
}
|
|
20398
20509
|
try {
|
|
20399
|
-
mkdirSync7(
|
|
20510
|
+
mkdirSync7(resolve26(resolved.accountDir, ...QUARANTINE_DIR), { recursive: true });
|
|
20400
20511
|
renameSync10(live, quarantined);
|
|
20401
20512
|
} catch (err) {
|
|
20402
20513
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -20413,28 +20524,28 @@ app23.post("/:slug/enable", async (c) => {
|
|
|
20413
20524
|
const resolved = await resolveWritableAccount(c, "enable", slug);
|
|
20414
20525
|
if ("status" in resolved) return c.json({ error: resolved.error }, resolved.status);
|
|
20415
20526
|
const filename = `${slug}.md`;
|
|
20416
|
-
const live =
|
|
20417
|
-
const quarantined =
|
|
20418
|
-
const authored =
|
|
20527
|
+
const live = resolve26(resolved.accountDir, ...SPECIALISTS_DIR, filename);
|
|
20528
|
+
const quarantined = resolve26(resolved.accountDir, ...QUARANTINE_DIR, filename);
|
|
20529
|
+
const authored = authoredAgentFiles(resolved.accountDir).has(filename);
|
|
20419
20530
|
const origin = authored ? "specialist" : "shipped";
|
|
20420
20531
|
let restored = false;
|
|
20421
20532
|
try {
|
|
20422
20533
|
if (authored) {
|
|
20423
|
-
if (!
|
|
20534
|
+
if (!existsSync33(quarantined)) {
|
|
20424
20535
|
console.error(`[admin/agents] op=enable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=specialist eligible=true outcome=refused reason=not-disabled`);
|
|
20425
20536
|
return c.json({ error: "This agent is not disabled" }, 404);
|
|
20426
20537
|
}
|
|
20427
|
-
mkdirSync7(
|
|
20538
|
+
mkdirSync7(resolve26(resolved.accountDir, ...SPECIALISTS_DIR), { recursive: true });
|
|
20428
20539
|
renameSync10(quarantined, live);
|
|
20429
20540
|
restored = true;
|
|
20430
20541
|
} else {
|
|
20431
20542
|
const disabled = readDisabledStoreStrict(resolved.accountDir);
|
|
20432
|
-
if (!
|
|
20543
|
+
if (!existsSync33(quarantined) && !disabled.has(filename)) {
|
|
20433
20544
|
console.error(`[admin/agents] op=enable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=shipped eligible=true outcome=refused reason=not-disabled`);
|
|
20434
20545
|
return c.json({ error: "This agent is not disabled" }, 404);
|
|
20435
20546
|
}
|
|
20436
|
-
if (
|
|
20437
|
-
mkdirSync7(
|
|
20547
|
+
if (existsSync33(quarantined)) {
|
|
20548
|
+
mkdirSync7(resolve26(resolved.accountDir, ...SPECIALISTS_DIR), { recursive: true });
|
|
20438
20549
|
renameSync10(quarantined, live);
|
|
20439
20550
|
restored = true;
|
|
20440
20551
|
}
|
|
@@ -20475,8 +20586,8 @@ app23.delete("/:slug", async (c) => {
|
|
|
20475
20586
|
console.error(`[admin/agents] op=delete rejected reason=${authz.reason} accountId=${account.accountId.slice(0, 8)} slug=${slug}`);
|
|
20476
20587
|
return c.json({ error: "Not authorized for this account" }, 403);
|
|
20477
20588
|
}
|
|
20478
|
-
const agentDir =
|
|
20479
|
-
if (!
|
|
20589
|
+
const agentDir = resolve26(account.accountDir, "agents", slug);
|
|
20590
|
+
if (!existsSync33(agentDir)) {
|
|
20480
20591
|
return c.json({ error: "Agent not found" }, 404);
|
|
20481
20592
|
}
|
|
20482
20593
|
try {
|
|
@@ -20518,8 +20629,8 @@ app23.post("/:slug/project", async (c) => {
|
|
|
20518
20629
|
console.error(`[admin/agents] op=project rejected reason=${authz.reason} accountId=${account.accountId.slice(0, 8)} slug=${slug}`);
|
|
20519
20630
|
return c.json({ error: "Not authorized for this account" }, 403);
|
|
20520
20631
|
}
|
|
20521
|
-
const agentDir =
|
|
20522
|
-
if (!
|
|
20632
|
+
const agentDir = resolve26(account.accountDir, "agents", slug);
|
|
20633
|
+
if (!existsSync33(agentDir)) {
|
|
20523
20634
|
console.error(`[admin/agents] op=project rejected reason=agent-not-on-disk accountId=${account.accountId.slice(0, 8)} slug=${slug} probed=${agentDir}`);
|
|
20524
20635
|
return c.json({ error: "Agent not found on disk" }, 404);
|
|
20525
20636
|
}
|
|
@@ -20543,7 +20654,7 @@ init_account();
|
|
|
20543
20654
|
init_neo4j_store();
|
|
20544
20655
|
import crypto2 from "crypto";
|
|
20545
20656
|
import { resolve as resolvePath } from "path";
|
|
20546
|
-
import { existsSync as
|
|
20657
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync8, createWriteStream } from "fs";
|
|
20547
20658
|
|
|
20548
20659
|
// app/lib/admin-conversation-cascade.ts
|
|
20549
20660
|
init_neo4j_store();
|
|
@@ -20711,7 +20822,7 @@ function validateAndShapeAttachments(raws, conversationAccountId, sessionId, mes
|
|
|
20711
20822
|
let reason = null;
|
|
20712
20823
|
if (!a.attachmentId || !a.filename || !a.mimeType || !a.storagePath) reason = "schema-fail";
|
|
20713
20824
|
else if (a.accountId !== conversationAccountId) reason = "account-mismatch";
|
|
20714
|
-
else if (!
|
|
20825
|
+
else if (!existsSync34(a.storagePath)) reason = "missing-file";
|
|
20715
20826
|
if (reason) {
|
|
20716
20827
|
invalid++;
|
|
20717
20828
|
try {
|
|
@@ -20827,7 +20938,7 @@ app24.get("/", requireAdminSession, async (c) => {
|
|
|
20827
20938
|
if (!userId) return c.json({ error: "User identity required \u2014 authenticate with users.json PIN" }, 401);
|
|
20828
20939
|
try {
|
|
20829
20940
|
const flushed = await listAdminSessions(accountId, userId, 20);
|
|
20830
|
-
const
|
|
20941
|
+
const sessions3 = flushed.map((r) => ({
|
|
20831
20942
|
sessionId: r.sessionId,
|
|
20832
20943
|
cacheKey: null,
|
|
20833
20944
|
name: r.name,
|
|
@@ -20835,15 +20946,15 @@ app24.get("/", requireAdminSession, async (c) => {
|
|
|
20835
20946
|
phase: "flushed",
|
|
20836
20947
|
channel: r.channel
|
|
20837
20948
|
})).sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
|
|
20838
|
-
const channelCounts =
|
|
20949
|
+
const channelCounts = sessions3.reduce((acc, s) => {
|
|
20839
20950
|
const k = s.channel ?? "unknown";
|
|
20840
20951
|
acc[k] = (acc[k] ?? 0) + 1;
|
|
20841
20952
|
return acc;
|
|
20842
20953
|
}, {});
|
|
20843
20954
|
console.error(
|
|
20844
|
-
`[conversations-list] render rows=${
|
|
20955
|
+
`[conversations-list] render rows=${sessions3.length} channels=${JSON.stringify(channelCounts)}`
|
|
20845
20956
|
);
|
|
20846
|
-
return c.json({ sessions:
|
|
20957
|
+
return c.json({ sessions: sessions3 });
|
|
20847
20958
|
} catch (err) {
|
|
20848
20959
|
console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
20849
20960
|
return c.json({ error: "Failed to fetch sessions" }, 500);
|
|
@@ -21244,8 +21355,8 @@ init_claude_agent();
|
|
|
21244
21355
|
// app/lib/claude-agent/spawn-context.ts
|
|
21245
21356
|
init_account();
|
|
21246
21357
|
init_neo4j_store();
|
|
21247
|
-
import { existsSync as
|
|
21248
|
-
import { dirname as dirname14, resolve as
|
|
21358
|
+
import { existsSync as existsSync35, readFileSync as readFileSync36, readdirSync as readdirSync23, statSync as statSync23 } from "fs";
|
|
21359
|
+
import { dirname as dirname14, resolve as resolve27, join as join41 } from "path";
|
|
21249
21360
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
21250
21361
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
21251
21362
|
try {
|
|
@@ -21291,7 +21402,7 @@ function extractPluginToolDescriptions(pluginDir) {
|
|
|
21291
21402
|
];
|
|
21292
21403
|
let raw = null;
|
|
21293
21404
|
for (const path of candidates) {
|
|
21294
|
-
if (!
|
|
21405
|
+
if (!existsSync35(path)) continue;
|
|
21295
21406
|
try {
|
|
21296
21407
|
raw = readFileSync36(path, "utf-8");
|
|
21297
21408
|
break;
|
|
@@ -21342,8 +21453,8 @@ function parseSkillPathsFromFrontmatter(fm) {
|
|
|
21342
21453
|
}
|
|
21343
21454
|
function listPluginDirs(accountPluginsDir) {
|
|
21344
21455
|
const out = /* @__PURE__ */ new Map();
|
|
21345
|
-
const platformPlugins =
|
|
21346
|
-
if (
|
|
21456
|
+
const platformPlugins = resolve27(PLATFORM_ROOT, "plugins");
|
|
21457
|
+
if (existsSync35(platformPlugins)) {
|
|
21347
21458
|
for (const name of readdirSync23(platformPlugins)) {
|
|
21348
21459
|
const dir = join41(platformPlugins, name);
|
|
21349
21460
|
try {
|
|
@@ -21352,11 +21463,11 @@ function listPluginDirs(accountPluginsDir) {
|
|
|
21352
21463
|
}
|
|
21353
21464
|
}
|
|
21354
21465
|
}
|
|
21355
|
-
const premiumRoot =
|
|
21356
|
-
if (
|
|
21466
|
+
const premiumRoot = resolve27(dirname14(PLATFORM_ROOT), "premium-plugins");
|
|
21467
|
+
if (existsSync35(premiumRoot)) {
|
|
21357
21468
|
for (const bundle of readdirSync23(premiumRoot)) {
|
|
21358
21469
|
const bundlePlugins = join41(premiumRoot, bundle, "plugins");
|
|
21359
|
-
if (!
|
|
21470
|
+
if (!existsSync35(bundlePlugins)) continue;
|
|
21360
21471
|
try {
|
|
21361
21472
|
for (const name of readdirSync23(bundlePlugins)) {
|
|
21362
21473
|
const dir = join41(bundlePlugins, name);
|
|
@@ -21369,7 +21480,7 @@ function listPluginDirs(accountPluginsDir) {
|
|
|
21369
21480
|
}
|
|
21370
21481
|
}
|
|
21371
21482
|
}
|
|
21372
|
-
if (accountPluginsDir &&
|
|
21483
|
+
if (accountPluginsDir && existsSync35(accountPluginsDir)) {
|
|
21373
21484
|
try {
|
|
21374
21485
|
for (const name of readdirSync23(accountPluginsDir)) {
|
|
21375
21486
|
if (out.has(name)) continue;
|
|
@@ -21385,10 +21496,10 @@ function listPluginDirs(accountPluginsDir) {
|
|
|
21385
21496
|
return out;
|
|
21386
21497
|
}
|
|
21387
21498
|
function accountPluginsDirFor(accountId) {
|
|
21388
|
-
return
|
|
21499
|
+
return resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "plugins");
|
|
21389
21500
|
}
|
|
21390
21501
|
function listAccountPluginNames(accountPluginsDir) {
|
|
21391
|
-
if (!
|
|
21502
|
+
if (!existsSync35(accountPluginsDir)) return [];
|
|
21392
21503
|
try {
|
|
21393
21504
|
return readdirSync23(accountPluginsDir).filter((name) => {
|
|
21394
21505
|
try {
|
|
@@ -21402,8 +21513,8 @@ function listAccountPluginNames(accountPluginsDir) {
|
|
|
21402
21513
|
}
|
|
21403
21514
|
}
|
|
21404
21515
|
function readEnabledPlugins(accountId) {
|
|
21405
|
-
const accountFile =
|
|
21406
|
-
if (!
|
|
21516
|
+
const accountFile = resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
21517
|
+
if (!existsSync35(accountFile)) return /* @__PURE__ */ new Set();
|
|
21407
21518
|
try {
|
|
21408
21519
|
const parsed = JSON.parse(readFileSync36(accountFile, "utf-8"));
|
|
21409
21520
|
return new Set(
|
|
@@ -21414,7 +21525,7 @@ function readEnabledPlugins(accountId) {
|
|
|
21414
21525
|
}
|
|
21415
21526
|
}
|
|
21416
21527
|
function readFrontmatter(path) {
|
|
21417
|
-
if (!
|
|
21528
|
+
if (!existsSync35(path)) return null;
|
|
21418
21529
|
let raw;
|
|
21419
21530
|
try {
|
|
21420
21531
|
raw = readFileSync36(path, "utf-8");
|
|
@@ -21452,7 +21563,7 @@ ${fm}
|
|
|
21452
21563
|
`plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
|
|
21453
21564
|
);
|
|
21454
21565
|
if (toolNames.length > 0 && descMap.size === 0) {
|
|
21455
|
-
const hasSource =
|
|
21566
|
+
const hasSource = existsSync35(join41(dir, "mcp/dist/index.js")) || existsSync35(join41(dir, "mcp/src/index.ts"));
|
|
21456
21567
|
if (hasSource) {
|
|
21457
21568
|
console.warn(
|
|
21458
21569
|
`[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
|
|
@@ -21478,8 +21589,8 @@ ${skillFm}
|
|
|
21478
21589
|
return out;
|
|
21479
21590
|
}
|
|
21480
21591
|
function computeSpecialistDomains(accountId) {
|
|
21481
|
-
const specialistsDir =
|
|
21482
|
-
if (!
|
|
21592
|
+
const specialistsDir = resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
|
|
21593
|
+
if (!existsSync35(specialistsDir)) return [];
|
|
21483
21594
|
let entries2;
|
|
21484
21595
|
try {
|
|
21485
21596
|
entries2 = readdirSync23(specialistsDir);
|
|
@@ -21524,8 +21635,8 @@ ${fm}
|
|
|
21524
21635
|
return out;
|
|
21525
21636
|
}
|
|
21526
21637
|
function computeDormantPlugins(accountId) {
|
|
21527
|
-
const accountFile =
|
|
21528
|
-
if (!
|
|
21638
|
+
const accountFile = resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
21639
|
+
if (!existsSync35(accountFile)) return [];
|
|
21529
21640
|
let enabled;
|
|
21530
21641
|
try {
|
|
21531
21642
|
const raw = readFileSync36(accountFile, "utf-8");
|
|
@@ -21539,8 +21650,8 @@ function computeDormantPlugins(accountId) {
|
|
|
21539
21650
|
);
|
|
21540
21651
|
return [];
|
|
21541
21652
|
}
|
|
21542
|
-
const pluginsDir =
|
|
21543
|
-
if (!
|
|
21653
|
+
const pluginsDir = resolve27(PLATFORM_ROOT, "plugins");
|
|
21654
|
+
if (!existsSync35(pluginsDir)) return [];
|
|
21544
21655
|
let entries2;
|
|
21545
21656
|
try {
|
|
21546
21657
|
entries2 = readdirSync23(pluginsDir);
|
|
@@ -21550,8 +21661,8 @@ function computeDormantPlugins(accountId) {
|
|
|
21550
21661
|
const out = [];
|
|
21551
21662
|
for (const name of entries2) {
|
|
21552
21663
|
if (enabled.has(name)) continue;
|
|
21553
|
-
const manifestPath =
|
|
21554
|
-
if (!
|
|
21664
|
+
const manifestPath = resolve27(pluginsDir, name, "PLUGIN.md");
|
|
21665
|
+
if (!existsSync35(manifestPath)) continue;
|
|
21555
21666
|
let manifest;
|
|
21556
21667
|
try {
|
|
21557
21668
|
manifest = readFileSync36(manifestPath, "utf-8");
|
|
@@ -21569,11 +21680,11 @@ function computeDormantPlugins(accountId) {
|
|
|
21569
21680
|
|
|
21570
21681
|
// server/routes/admin/claude-sessions.ts
|
|
21571
21682
|
init_neo4j_store();
|
|
21572
|
-
import { existsSync as
|
|
21573
|
-
import { resolve as
|
|
21683
|
+
import { existsSync as existsSync36, readFileSync as readFileSync37 } from "fs";
|
|
21684
|
+
import { resolve as resolve28 } from "path";
|
|
21574
21685
|
function readTunnelState(brandConfigDir) {
|
|
21575
21686
|
const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
|
|
21576
|
-
if (!
|
|
21687
|
+
if (!existsSync36(statePath)) return null;
|
|
21577
21688
|
try {
|
|
21578
21689
|
const parsed = JSON.parse(readFileSync37(statePath, "utf-8"));
|
|
21579
21690
|
const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
|
|
@@ -21586,10 +21697,10 @@ function readTunnelState(brandConfigDir) {
|
|
|
21586
21697
|
}
|
|
21587
21698
|
}
|
|
21588
21699
|
function resolveTunnelUrl() {
|
|
21589
|
-
const platformRoot5 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ??
|
|
21700
|
+
const platformRoot5 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve28(process.cwd(), "..");
|
|
21590
21701
|
let brand;
|
|
21591
21702
|
try {
|
|
21592
|
-
brand = JSON.parse(readFileSync37(
|
|
21703
|
+
brand = JSON.parse(readFileSync37(resolve28(platformRoot5, "config", "brand.json"), "utf-8"));
|
|
21593
21704
|
} catch {
|
|
21594
21705
|
return null;
|
|
21595
21706
|
}
|
|
@@ -22049,11 +22160,11 @@ init_helpers();
|
|
|
22049
22160
|
init_claude_agent();
|
|
22050
22161
|
init_data_path();
|
|
22051
22162
|
init_sidebar_sessions();
|
|
22052
|
-
import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as
|
|
22163
|
+
import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync37 } from "fs";
|
|
22053
22164
|
import { readdir as readdir4, readFile as readFile9, stat as stat6, mkdir as mkdir3, unlink as unlink3, rename as rename3, rmdir } from "fs/promises";
|
|
22054
22165
|
import { realpathSync as realpathSync7 } from "fs";
|
|
22055
22166
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
22056
|
-
import { basename as basename13, dirname as dirname15, join as join44, relative as relative6, resolve as
|
|
22167
|
+
import { basename as basename13, dirname as dirname15, join as join44, relative as relative6, resolve as resolve30, sep as sep9 } from "path";
|
|
22057
22168
|
import { Readable as Readable2 } from "stream";
|
|
22058
22169
|
init_attachments();
|
|
22059
22170
|
|
|
@@ -22377,7 +22488,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
22377
22488
|
init_data_path();
|
|
22378
22489
|
init_neo4j_store();
|
|
22379
22490
|
import * as fsp from "fs/promises";
|
|
22380
|
-
import { resolve as
|
|
22491
|
+
import { resolve as resolve29, relative as relative5, join as join43, basename as basename12, extname, sep as sep8 } from "path";
|
|
22381
22492
|
import { tmpdir as tmpdir3 } from "os";
|
|
22382
22493
|
import { execFile } from "child_process";
|
|
22383
22494
|
import { promisify } from "util";
|
|
@@ -22539,14 +22650,14 @@ async function walkSubtree(root, dataRoot, out) {
|
|
|
22539
22650
|
return { clean: false };
|
|
22540
22651
|
}
|
|
22541
22652
|
let clean = true;
|
|
22542
|
-
for (const
|
|
22543
|
-
if (
|
|
22544
|
-
if (
|
|
22545
|
-
const abs = join43(root,
|
|
22546
|
-
if (
|
|
22653
|
+
for (const entry2 of entries2) {
|
|
22654
|
+
if (entry2.isSymbolicLink()) continue;
|
|
22655
|
+
if (entry2.isDirectory() && entry2.name === ".uploads-tmp") continue;
|
|
22656
|
+
const abs = join43(root, entry2.name);
|
|
22657
|
+
if (entry2.isDirectory()) {
|
|
22547
22658
|
const sub = await walkSubtree(abs, dataRoot, out);
|
|
22548
22659
|
if (!sub.clean) clean = false;
|
|
22549
|
-
} else if (
|
|
22660
|
+
} else if (entry2.isFile()) {
|
|
22550
22661
|
try {
|
|
22551
22662
|
const st = await fsp.stat(abs);
|
|
22552
22663
|
out.push({
|
|
@@ -22697,7 +22808,7 @@ async function reconcileFileIndex(accountId, opts) {
|
|
|
22697
22808
|
return withSession2(opts, async (session) => {
|
|
22698
22809
|
const walked = [];
|
|
22699
22810
|
const subtreeRoots = [
|
|
22700
|
-
|
|
22811
|
+
resolve29(dataRoot, "accounts", accountId)
|
|
22701
22812
|
];
|
|
22702
22813
|
let clean = true;
|
|
22703
22814
|
for (const root of subtreeRoots) {
|
|
@@ -22780,7 +22891,7 @@ async function reconcileFileIndexDebounced(accountId, opts) {
|
|
|
22780
22891
|
async function indexFile(accountId, relativePath, opts) {
|
|
22781
22892
|
const dataRoot = opts?.dataRoot ?? DATA_ROOT;
|
|
22782
22893
|
const embed2 = opts?.embed ?? embed;
|
|
22783
|
-
const absolute =
|
|
22894
|
+
const absolute = resolve29(dataRoot, relativePath);
|
|
22784
22895
|
await withSession2(opts, async (session) => {
|
|
22785
22896
|
const st = await fsp.stat(absolute);
|
|
22786
22897
|
const wf = {
|
|
@@ -22838,14 +22949,14 @@ function buildZip(entries2) {
|
|
|
22838
22949
|
const localChunks = [];
|
|
22839
22950
|
const centralChunks = [];
|
|
22840
22951
|
let offset = 0;
|
|
22841
|
-
for (const
|
|
22842
|
-
const nameBytes = Buffer.from(
|
|
22843
|
-
const crc = crc32(
|
|
22844
|
-
const uncompressedSize =
|
|
22845
|
-
const deflated = deflateRawSync(
|
|
22952
|
+
for (const entry2 of entries2) {
|
|
22953
|
+
const nameBytes = Buffer.from(entry2.name, "utf8");
|
|
22954
|
+
const crc = crc32(entry2.data);
|
|
22955
|
+
const uncompressedSize = entry2.data.length;
|
|
22956
|
+
const deflated = deflateRawSync(entry2.data);
|
|
22846
22957
|
const useDeflate = deflated.length < uncompressedSize;
|
|
22847
22958
|
const method = useDeflate ? 8 : 0;
|
|
22848
|
-
const payload = useDeflate ? deflated :
|
|
22959
|
+
const payload = useDeflate ? deflated : entry2.data;
|
|
22849
22960
|
const compressedSize = payload.length;
|
|
22850
22961
|
const localHeader = Buffer.alloc(30);
|
|
22851
22962
|
localHeader.writeUInt32LE(LOCAL_SIG, 0);
|
|
@@ -22929,7 +23040,7 @@ async function readMeta2(absDir, baseName) {
|
|
|
22929
23040
|
}
|
|
22930
23041
|
async function readAccountNames() {
|
|
22931
23042
|
const map = /* @__PURE__ */ new Map();
|
|
22932
|
-
const accountsDir =
|
|
23043
|
+
const accountsDir = resolve30(DATA_ROOT, "accounts");
|
|
22933
23044
|
let names;
|
|
22934
23045
|
try {
|
|
22935
23046
|
names = await readdir4(accountsDir);
|
|
@@ -22938,7 +23049,7 @@ async function readAccountNames() {
|
|
|
22938
23049
|
}
|
|
22939
23050
|
for (const name of names) {
|
|
22940
23051
|
if (!UUID_RE3.test(name)) continue;
|
|
22941
|
-
const configPath3 =
|
|
23052
|
+
const configPath3 = resolve30(accountsDir, name, "account.json");
|
|
22942
23053
|
try {
|
|
22943
23054
|
const raw = await readFile9(configPath3, "utf8");
|
|
22944
23055
|
const parsed = JSON.parse(raw);
|
|
@@ -22971,47 +23082,47 @@ async function servableFilesIn(dirAbs, uuid) {
|
|
|
22971
23082
|
}
|
|
22972
23083
|
return out;
|
|
22973
23084
|
}
|
|
22974
|
-
async function enrich(absolute,
|
|
22975
|
-
if (
|
|
22976
|
-
const dirAbs = join44(absolute,
|
|
22977
|
-
const meta = await readMeta2(dirAbs,
|
|
23085
|
+
async function enrich(absolute, entry2, accountNames) {
|
|
23086
|
+
if (entry2.kind === "directory" && UUID_RE3.test(entry2.name)) {
|
|
23087
|
+
const dirAbs = join44(absolute, entry2.name);
|
|
23088
|
+
const meta = await readMeta2(dirAbs, entry2.name);
|
|
22978
23089
|
if (meta?.filename) {
|
|
22979
|
-
const servable = await servableFilesIn(dirAbs,
|
|
23090
|
+
const servable = await servableFilesIn(dirAbs, entry2.name);
|
|
22980
23091
|
if (servable.length === 1) {
|
|
22981
23092
|
const innerName = servable[0];
|
|
22982
23093
|
let size = null;
|
|
22983
|
-
let modifiedAt =
|
|
23094
|
+
let modifiedAt = entry2.modifiedAt;
|
|
22984
23095
|
try {
|
|
22985
23096
|
const st = await stat6(join44(dirAbs, innerName));
|
|
22986
23097
|
size = st.size;
|
|
22987
23098
|
modifiedAt = st.mtime.toISOString();
|
|
22988
23099
|
} catch {
|
|
22989
23100
|
}
|
|
22990
|
-
|
|
22991
|
-
|
|
22992
|
-
|
|
22993
|
-
|
|
22994
|
-
|
|
22995
|
-
|
|
23101
|
+
entry2.kind = "file";
|
|
23102
|
+
entry2.displayName = meta.filename;
|
|
23103
|
+
entry2.mimeType = meta.mimeType;
|
|
23104
|
+
entry2.sizeBytes = size;
|
|
23105
|
+
entry2.modifiedAt = modifiedAt;
|
|
23106
|
+
entry2.entryPath = `${entry2.name}/${innerName}`;
|
|
22996
23107
|
return "flattened";
|
|
22997
23108
|
}
|
|
22998
|
-
|
|
23109
|
+
entry2.displayName = meta.filename;
|
|
22999
23110
|
return "kept-as-dir";
|
|
23000
23111
|
}
|
|
23001
|
-
const accountName = accountNames.get(
|
|
23112
|
+
const accountName = accountNames.get(entry2.name);
|
|
23002
23113
|
if (accountName) {
|
|
23003
|
-
|
|
23114
|
+
entry2.displayName = accountName;
|
|
23004
23115
|
}
|
|
23005
23116
|
return null;
|
|
23006
23117
|
}
|
|
23007
|
-
if (
|
|
23008
|
-
const dot =
|
|
23009
|
-
const base = dot === -1 ?
|
|
23118
|
+
if (entry2.kind === "file") {
|
|
23119
|
+
const dot = entry2.name.lastIndexOf(".");
|
|
23120
|
+
const base = dot === -1 ? entry2.name : entry2.name.slice(0, dot);
|
|
23010
23121
|
if (UUID_RE3.test(base)) {
|
|
23011
23122
|
const meta = await readMeta2(absolute, base);
|
|
23012
23123
|
if (meta?.filename) {
|
|
23013
|
-
|
|
23014
|
-
|
|
23124
|
+
entry2.displayName = meta.filename;
|
|
23125
|
+
entry2.mimeType = meta.mimeType;
|
|
23015
23126
|
}
|
|
23016
23127
|
}
|
|
23017
23128
|
}
|
|
@@ -23102,6 +23213,10 @@ function isValidEntryName(name) {
|
|
|
23102
23213
|
if (name.startsWith("@")) return false;
|
|
23103
23214
|
return !/[\\/\0]/.test(name);
|
|
23104
23215
|
}
|
|
23216
|
+
function crossAccountOwner(relPath) {
|
|
23217
|
+
const segs = relPath.split("/").filter(Boolean);
|
|
23218
|
+
return segs.length >= 2 && segs[0] === "accounts" ? segs[1] : "";
|
|
23219
|
+
}
|
|
23105
23220
|
function resolveOwnAccountWrite(rawPath, accountId, endpoint) {
|
|
23106
23221
|
const res = resolveDataPath(rawPath);
|
|
23107
23222
|
if (!res.ok) {
|
|
@@ -23169,8 +23284,7 @@ app29.get("/", requireAdminSession, async (c) => {
|
|
|
23169
23284
|
}
|
|
23170
23285
|
const { absolute, relative: relPath } = resolution;
|
|
23171
23286
|
if (crossesForeignAccountPartition(relPath, accountId)) {
|
|
23172
|
-
console.error(`[data] account-
|
|
23173
|
-
return c.json({ error: "Not found" }, 404);
|
|
23287
|
+
console.error(`[data] op=cross-account-read endpoint="GET /api/admin/files" path="${relPath}" pinned=${accountId.slice(0, 8)}\u2026 owner=${crossAccountOwner(relPath).slice(0, 8)}\u2026`);
|
|
23174
23288
|
}
|
|
23175
23289
|
try {
|
|
23176
23290
|
const info = await stat6(absolute);
|
|
@@ -23346,8 +23460,7 @@ app29.get("/download", requireAdminSession, async (c) => {
|
|
|
23346
23460
|
return c.json({ error: "Not found" }, 404);
|
|
23347
23461
|
}
|
|
23348
23462
|
} else if (root === "data" && crossesForeignAccountPartition(relPath, accountId)) {
|
|
23349
|
-
console.error(`[data] account-
|
|
23350
|
-
return c.json({ error: "Not found" }, 404);
|
|
23463
|
+
console.error(`[data] op=cross-account-read endpoint="GET /api/admin/files/download" path="${relPath}" pinned=${accountId.slice(0, 8)}\u2026 owner=${crossAccountOwner(relPath).slice(0, 8)}\u2026`);
|
|
23351
23464
|
}
|
|
23352
23465
|
try {
|
|
23353
23466
|
const info = await stat6(absolute);
|
|
@@ -23460,8 +23573,7 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
|
|
|
23460
23573
|
}
|
|
23461
23574
|
const { absolute, relative: relPath } = resolution;
|
|
23462
23575
|
if (crossesForeignAccountPartition(relPath, accountId)) {
|
|
23463
|
-
console.error(`[data] account-
|
|
23464
|
-
return c.json({ error: "Not found" }, 404);
|
|
23576
|
+
console.error(`[data] op=cross-account-read endpoint="GET /api/admin/files/download-zip" path="${relPath}" pinned=${accountId.slice(0, 8)}\u2026 owner=${crossAccountOwner(relPath).slice(0, 8)}\u2026`);
|
|
23465
23577
|
}
|
|
23466
23578
|
try {
|
|
23467
23579
|
const info = await stat6(absolute);
|
|
@@ -23469,9 +23581,6 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
|
|
|
23469
23581
|
dirsWalked++;
|
|
23470
23582
|
const parentAbs = dirname15(absolute);
|
|
23471
23583
|
for await (const fileAbs of walkRegularFiles(absolute)) {
|
|
23472
|
-
const within = relative6(absolute, fileAbs).split(sep9).join("/");
|
|
23473
|
-
const fileRel = relPath === "" || relPath === "." ? within : `${relPath}/${within}`;
|
|
23474
|
-
if (crossesForeignAccountPartition(fileRel, accountId)) continue;
|
|
23475
23584
|
const fileInfo = await stat6(fileAbs);
|
|
23476
23585
|
const member = relative6(parentAbs, fileAbs).split(sep9).join("/");
|
|
23477
23586
|
const over = await addFile(fileAbs, fileInfo.size, member);
|
|
@@ -23542,8 +23651,8 @@ async function resolveUploadTarget(c, accountId, uid) {
|
|
|
23542
23651
|
return { ok: false, status: 403, error: "Path is outside your account folder" };
|
|
23543
23652
|
}
|
|
23544
23653
|
}
|
|
23545
|
-
const destDir = relDir === "." ? base.absolute :
|
|
23546
|
-
const destPath =
|
|
23654
|
+
const destDir = relDir === "." ? base.absolute : resolve30(base.absolute, relDir);
|
|
23655
|
+
const destPath = resolve30(destDir, finalName);
|
|
23547
23656
|
if (relpath) {
|
|
23548
23657
|
const firstCreated = await mkdir3(destDir, { recursive: true });
|
|
23549
23658
|
if (firstCreated) console.error(`[data-upload] op=mkdir uid=${uid} token=${token} dir="${destRel}"`);
|
|
@@ -23560,7 +23669,7 @@ async function resolveUploadTarget(c, accountId, uid) {
|
|
|
23560
23669
|
return { ok: true, destDir, destPath, destRel, finalName, mimeType, relpath, token };
|
|
23561
23670
|
}
|
|
23562
23671
|
async function logUploadsTmpCensus(accountId) {
|
|
23563
|
-
const tmpDir =
|
|
23672
|
+
const tmpDir = resolve30(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
|
|
23564
23673
|
let live = 0;
|
|
23565
23674
|
try {
|
|
23566
23675
|
live = (await readdir4(tmpDir)).length;
|
|
@@ -23587,9 +23696,9 @@ app29.post("/upload", requireAdminSession, async (c) => {
|
|
|
23587
23696
|
if (!body) return c.json({ error: "Empty request body" }, 400);
|
|
23588
23697
|
console.error(`[data-upload] op=request uid=${uid} account=${accountId} dir="${destRel}" token=${token} rel="${relpath ?? ""}" declaredBytes=${declaredBytes}`);
|
|
23589
23698
|
const ceiling = dataUploadCeiling();
|
|
23590
|
-
const tmpDir =
|
|
23699
|
+
const tmpDir = resolve30(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
|
|
23591
23700
|
await mkdir3(tmpDir, { recursive: true });
|
|
23592
|
-
const tmpPath =
|
|
23701
|
+
const tmpPath = resolve30(tmpDir, `${uid}.part`);
|
|
23593
23702
|
const out = createWriteStream2(tmpPath);
|
|
23594
23703
|
const t0 = Date.now();
|
|
23595
23704
|
let received2 = 0;
|
|
@@ -23607,7 +23716,7 @@ app29.post("/upload", requireAdminSession, async (c) => {
|
|
|
23607
23716
|
});
|
|
23608
23717
|
await unlink3(tmpPath).catch(() => {
|
|
23609
23718
|
});
|
|
23610
|
-
const tempRemoved = !
|
|
23719
|
+
const tempRemoved = !existsSync37(tmpPath);
|
|
23611
23720
|
console.error(`[data-upload] op=cleanup uid=${uid} tempRemoved=${tempRemoved}`);
|
|
23612
23721
|
};
|
|
23613
23722
|
const reader = body.getReader();
|
|
@@ -23681,7 +23790,7 @@ async function discardChunkUpload(uploadId, state) {
|
|
|
23681
23790
|
});
|
|
23682
23791
|
await unlink3(state.tmpPath).catch(() => {
|
|
23683
23792
|
});
|
|
23684
|
-
const tempRemoved = !
|
|
23793
|
+
const tempRemoved = !existsSync37(state.tmpPath);
|
|
23685
23794
|
console.error(`[data-upload] op=cleanup uid=${uploadId} tempRemoved=${tempRemoved}`);
|
|
23686
23795
|
}
|
|
23687
23796
|
async function writeChunkBody(body, out, alreadyReceived, ceiling) {
|
|
@@ -23736,9 +23845,9 @@ async function handleChunkUpload(c, accountId) {
|
|
|
23736
23845
|
}
|
|
23737
23846
|
const target = await resolveUploadTarget(c, accountId, uploadId);
|
|
23738
23847
|
if (!target.ok) return c.json({ error: target.error }, target.status);
|
|
23739
|
-
const tmpDir =
|
|
23848
|
+
const tmpDir = resolve30(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
|
|
23740
23849
|
await mkdir3(tmpDir, { recursive: true });
|
|
23741
|
-
const tmpPath =
|
|
23850
|
+
const tmpPath = resolve30(tmpDir, `${uploadId}.part`);
|
|
23742
23851
|
const out = createWriteStream2(tmpPath);
|
|
23743
23852
|
const state2 = {
|
|
23744
23853
|
accountId,
|
|
@@ -23844,7 +23953,7 @@ async function evictStaleChunkUploads(ttlMs) {
|
|
|
23844
23953
|
return evicted;
|
|
23845
23954
|
}
|
|
23846
23955
|
async function sweepStaleUploadTemps() {
|
|
23847
|
-
const accountsRoot =
|
|
23956
|
+
const accountsRoot = resolve30(DATA_ROOT, "accounts");
|
|
23848
23957
|
let accounts;
|
|
23849
23958
|
try {
|
|
23850
23959
|
accounts = await readdir4(accountsRoot, { withFileTypes: true });
|
|
@@ -23854,7 +23963,7 @@ async function sweepStaleUploadTemps() {
|
|
|
23854
23963
|
let removed = 0;
|
|
23855
23964
|
for (const acc of accounts) {
|
|
23856
23965
|
if (!acc.isDirectory()) continue;
|
|
23857
|
-
const tmpDir =
|
|
23966
|
+
const tmpDir = resolve30(accountsRoot, acc.name, UPLOAD_TMP_DIR);
|
|
23858
23967
|
let entries2;
|
|
23859
23968
|
try {
|
|
23860
23969
|
entries2 = await readdir4(tmpDir);
|
|
@@ -23863,7 +23972,7 @@ async function sweepStaleUploadTemps() {
|
|
|
23863
23972
|
}
|
|
23864
23973
|
for (const name of entries2) {
|
|
23865
23974
|
try {
|
|
23866
|
-
await unlink3(
|
|
23975
|
+
await unlink3(resolve30(tmpDir, name));
|
|
23867
23976
|
removed++;
|
|
23868
23977
|
} catch {
|
|
23869
23978
|
}
|
|
@@ -23978,7 +24087,7 @@ app29.post("/folder", requireAdminSession, async (c) => {
|
|
|
23978
24087
|
if (!isValidEntryName(name)) return c.json({ error: "Invalid folder name" }, 400);
|
|
23979
24088
|
const parent = resolveSharedOrOwnAccountWrite(rawPath, accountId, "POST /api/admin/files/folder");
|
|
23980
24089
|
if (!parent.ok) return c.json({ error: parent.error }, parent.status);
|
|
23981
|
-
const newAbs =
|
|
24090
|
+
const newAbs = resolve30(parent.absolute, name);
|
|
23982
24091
|
const newRel = `${parent.relative}/${name}`;
|
|
23983
24092
|
try {
|
|
23984
24093
|
const info = await stat6(parent.absolute);
|
|
@@ -24017,7 +24126,7 @@ app29.post("/rename", requireAdminSession, async (c) => {
|
|
|
24017
24126
|
console.error(`[data] file-rename blocked path="${src.relative}" reason="protected"`);
|
|
24018
24127
|
return c.json({ error: "Protected entry \u2014 refusing to rename" }, 403);
|
|
24019
24128
|
}
|
|
24020
|
-
const destAbs =
|
|
24129
|
+
const destAbs = resolve30(dirname15(src.absolute), newName);
|
|
24021
24130
|
const newRel = `${dirname15(src.relative)}/${newName}`.replace(/^\.\//, "");
|
|
24022
24131
|
if (isProtectedFromRename(newRel)) {
|
|
24023
24132
|
console.error(`[data] file-rename blocked path="${newRel}" reason="protected-target"`);
|
|
@@ -25050,7 +25159,7 @@ function isHiddenByDefault(label) {
|
|
|
25050
25159
|
// server/lib/top-level-labels.ts
|
|
25051
25160
|
var import_dist8 = __toESM(require_dist6(), 1);
|
|
25052
25161
|
import { readdirSync as readdirSync24, readFileSync as readFileSync38 } from "fs";
|
|
25053
|
-
import { join as join45, resolve as
|
|
25162
|
+
import { join as join45, resolve as resolve31 } from "path";
|
|
25054
25163
|
var STATIC_TOP_LEVEL_LABELS = Object.freeze(
|
|
25055
25164
|
/* @__PURE__ */ new Set([
|
|
25056
25165
|
// Base-schema infra (schema-base.md has no top-level section).
|
|
@@ -25156,7 +25265,7 @@ function resolveReferencesDir() {
|
|
|
25156
25265
|
if (process.env.MAXY_PLATFORM_ROOT) {
|
|
25157
25266
|
return join45(process.env.MAXY_PLATFORM_ROOT, "plugins", "memory", "references");
|
|
25158
25267
|
}
|
|
25159
|
-
return
|
|
25268
|
+
return resolve31(import.meta.dirname, "../../../plugins/memory/references");
|
|
25160
25269
|
}
|
|
25161
25270
|
function parseTableTopLevelLabels(referencesDir) {
|
|
25162
25271
|
const labels = /* @__PURE__ */ new Set();
|
|
@@ -26342,8 +26451,8 @@ init_claude_agent();
|
|
|
26342
26451
|
init_account();
|
|
26343
26452
|
init_route_cache();
|
|
26344
26453
|
import { readdir as readdir5, stat as stat7 } from "fs/promises";
|
|
26345
|
-
import { resolve as
|
|
26346
|
-
import { existsSync as
|
|
26454
|
+
import { resolve as resolve32, relative as relative7, isAbsolute as isAbsolute4, sep as sep10, basename as basename14 } from "path";
|
|
26455
|
+
import { existsSync as existsSync38 } from "fs";
|
|
26347
26456
|
init_neo4j_store();
|
|
26348
26457
|
init_attachments();
|
|
26349
26458
|
init_data_path();
|
|
@@ -26361,11 +26470,11 @@ app36.get("/", requireAdminSession, async (c) => {
|
|
|
26361
26470
|
return c.json({ error: "Account not found for session" }, 401);
|
|
26362
26471
|
}
|
|
26363
26472
|
const start = Date.now();
|
|
26364
|
-
ensureAccountWatchers(accountId,
|
|
26473
|
+
ensureAccountWatchers(accountId, resolve32(ACCOUNTS_DIR, accountId));
|
|
26365
26474
|
const { value, state } = await getOrCompute("sidebar-artefacts", accountId, async () => {
|
|
26366
26475
|
const accountFiles = await fetchAccountFileArtefacts(accountId);
|
|
26367
26476
|
if (accountFiles === null) return null;
|
|
26368
|
-
const accountDir =
|
|
26477
|
+
const accountDir = resolve32(ACCOUNTS_DIR, accountId);
|
|
26369
26478
|
const agents = await fetchAgentTemplateRows(accountDir);
|
|
26370
26479
|
const artefacts = [...accountFiles, ...agents].sort(
|
|
26371
26480
|
(a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
|
|
@@ -26422,8 +26531,8 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
26422
26531
|
async function fetchAgentTemplateRows(accountDir) {
|
|
26423
26532
|
const rows = [];
|
|
26424
26533
|
for (const filename of ADMIN_AGENT_FILES) {
|
|
26425
|
-
const overridePath =
|
|
26426
|
-
const bundledPath =
|
|
26534
|
+
const overridePath = resolve32(accountDir, "agents", "admin", filename);
|
|
26535
|
+
const bundledPath = resolve32(PLATFORM_ROOT, "templates", "agents", "admin", filename);
|
|
26427
26536
|
const labelStem = filename.replace(/\.md$/, "");
|
|
26428
26537
|
const row = await readAgentTemplateRow({
|
|
26429
26538
|
id: `agent-template:admin:${filename}`,
|
|
@@ -26436,12 +26545,12 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
26436
26545
|
});
|
|
26437
26546
|
if (row) rows.push(row);
|
|
26438
26547
|
}
|
|
26439
|
-
const overrideDir =
|
|
26440
|
-
const bundledDir =
|
|
26548
|
+
const overrideDir = resolve32(accountDir, "specialists", "agents");
|
|
26549
|
+
const bundledDir = resolve32(PLATFORM_ROOT, "templates", "specialists", "agents");
|
|
26441
26550
|
const specialistNames = await unionSpecialistFilenames(overrideDir, bundledDir);
|
|
26442
26551
|
for (const filename of specialistNames) {
|
|
26443
|
-
const overridePath =
|
|
26444
|
-
const bundledPath =
|
|
26552
|
+
const overridePath = resolve32(overrideDir, filename);
|
|
26553
|
+
const bundledPath = resolve32(bundledDir, filename);
|
|
26445
26554
|
const row = await readAgentTemplateRow({
|
|
26446
26555
|
id: `agent-template:specialist:${filename}`,
|
|
26447
26556
|
displayName: filename.replace(/\.md$/, ""),
|
|
@@ -26458,11 +26567,11 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
26458
26567
|
async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
26459
26568
|
const names = /* @__PURE__ */ new Set();
|
|
26460
26569
|
for (const dir of [overrideDir, bundledDir]) {
|
|
26461
|
-
if (!
|
|
26570
|
+
if (!existsSync38(dir)) continue;
|
|
26462
26571
|
try {
|
|
26463
26572
|
const entries2 = await readdir5(dir);
|
|
26464
|
-
for (const
|
|
26465
|
-
if (
|
|
26573
|
+
for (const entry2 of entries2) {
|
|
26574
|
+
if (entry2.endsWith(".md")) names.add(entry2);
|
|
26466
26575
|
}
|
|
26467
26576
|
} catch (err) {
|
|
26468
26577
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -26473,7 +26582,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
|
26473
26582
|
}
|
|
26474
26583
|
async function readAgentTemplateRow(inp) {
|
|
26475
26584
|
let chosenPath = null;
|
|
26476
|
-
if (
|
|
26585
|
+
if (existsSync38(inp.overridePath)) {
|
|
26477
26586
|
try {
|
|
26478
26587
|
validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
|
|
26479
26588
|
chosenPath = inp.overridePath;
|
|
@@ -26484,7 +26593,7 @@ async function readAgentTemplateRow(inp) {
|
|
|
26484
26593
|
);
|
|
26485
26594
|
return null;
|
|
26486
26595
|
}
|
|
26487
|
-
} else if (
|
|
26596
|
+
} else if (existsSync38(inp.bundledPath)) {
|
|
26488
26597
|
if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
|
|
26489
26598
|
console.error(
|
|
26490
26599
|
`[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
|
|
@@ -26944,24 +27053,24 @@ init_session_store();
|
|
|
26944
27053
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
26945
27054
|
|
|
26946
27055
|
// server/wa-descriptor.ts
|
|
26947
|
-
import { resolve as
|
|
27056
|
+
import { resolve as resolve33, join as join46 } from "path";
|
|
26948
27057
|
function waGatewayUrl() {
|
|
26949
27058
|
return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
|
|
26950
27059
|
}
|
|
26951
27060
|
function waServerPath() {
|
|
26952
|
-
return process.env.MAXY_WA_CHANNEL_SERVER_PATH ??
|
|
27061
|
+
return process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve33(process.env.MAXY_PLATFORM_ROOT ?? join46(__dirname, ".."), "services/whatsapp-channel/dist/server.js");
|
|
26953
27062
|
}
|
|
26954
27063
|
function waChannelDescriptor(senderId) {
|
|
26955
27064
|
return { senderId, gatewayUrl: waGatewayUrl(), serverPath: waServerPath() };
|
|
26956
27065
|
}
|
|
26957
27066
|
|
|
26958
27067
|
// server/telegram-descriptor.ts
|
|
26959
|
-
import { resolve as
|
|
27068
|
+
import { resolve as resolve34, join as join47 } from "path";
|
|
26960
27069
|
function telegramGatewayUrl() {
|
|
26961
27070
|
return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
|
|
26962
27071
|
}
|
|
26963
27072
|
function telegramServerPath() {
|
|
26964
|
-
return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ??
|
|
27073
|
+
return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve34(process.env.MAXY_PLATFORM_ROOT ?? join47(__dirname, ".."), "services/telegram-channel/dist/server.js");
|
|
26965
27074
|
}
|
|
26966
27075
|
function telegramChannelDescriptor(senderId) {
|
|
26967
27076
|
return { senderId, gatewayUrl: telegramGatewayUrl(), serverPath: telegramServerPath() };
|
|
@@ -27257,23 +27366,23 @@ var system_stats_default = app44;
|
|
|
27257
27366
|
// server/routes/admin/health.ts
|
|
27258
27367
|
init_dist();
|
|
27259
27368
|
init_neo4j_store();
|
|
27260
|
-
import { existsSync as
|
|
27261
|
-
import { resolve as
|
|
27262
|
-
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ??
|
|
27369
|
+
import { existsSync as existsSync39, readFileSync as readFileSync39 } from "fs";
|
|
27370
|
+
import { resolve as resolve35, join as join48 } from "path";
|
|
27371
|
+
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve35(process.cwd(), "..");
|
|
27263
27372
|
var brandHostname = "maxy";
|
|
27264
27373
|
var brandJsonPath = join48(PLATFORM_ROOT4, "config", "brand.json");
|
|
27265
|
-
if (
|
|
27374
|
+
if (existsSync39(brandJsonPath)) {
|
|
27266
27375
|
try {
|
|
27267
27376
|
const brand = JSON.parse(readFileSync39(brandJsonPath, "utf-8"));
|
|
27268
27377
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
27269
27378
|
} catch {
|
|
27270
27379
|
}
|
|
27271
27380
|
}
|
|
27272
|
-
var VERSION_FILE =
|
|
27381
|
+
var VERSION_FILE = resolve35(PLATFORM_ROOT4, `config/.${brandHostname}-version`);
|
|
27273
27382
|
var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
27274
27383
|
var PROBE_TIMEOUT_MS = 1e3;
|
|
27275
27384
|
function readVersion() {
|
|
27276
|
-
if (!
|
|
27385
|
+
if (!existsSync39(VERSION_FILE)) return "unknown";
|
|
27277
27386
|
return readFileSync39(VERSION_FILE, "utf-8").trim() || "unknown";
|
|
27278
27387
|
}
|
|
27279
27388
|
async function probeConversationDb() {
|
|
@@ -27330,17 +27439,17 @@ import { randomUUID as randomUUID10 } from "crypto";
|
|
|
27330
27439
|
|
|
27331
27440
|
// ../lib/account-ingest-token/src/index.ts
|
|
27332
27441
|
import { randomBytes, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
27333
|
-
import { existsSync as
|
|
27334
|
-
import { resolve as
|
|
27442
|
+
import { existsSync as existsSync40, readdirSync as readdirSync25, readFileSync as readFileSync40, writeFileSync as writeFileSync16 } from "fs";
|
|
27443
|
+
import { resolve as resolve36 } from "path";
|
|
27335
27444
|
var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
27336
27445
|
var INGEST_TOKEN_FIELD = "ingestToken";
|
|
27337
27446
|
var TOKEN_RE = /^[0-9a-f]{64}$/;
|
|
27338
27447
|
function configPath2(accountsDir, accountId) {
|
|
27339
|
-
return
|
|
27448
|
+
return resolve36(accountsDir, accountId, "account.json");
|
|
27340
27449
|
}
|
|
27341
27450
|
function readConfig2(accountsDir, accountId) {
|
|
27342
27451
|
const path = configPath2(accountsDir, accountId);
|
|
27343
|
-
if (!
|
|
27452
|
+
if (!existsSync40(path)) return null;
|
|
27344
27453
|
try {
|
|
27345
27454
|
const parsed = JSON.parse(readFileSync40(path, "utf-8"));
|
|
27346
27455
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
@@ -27353,7 +27462,7 @@ function readIngestToken(accountsDir, accountId) {
|
|
|
27353
27462
|
return typeof value === "string" && TOKEN_RE.test(value) ? value : null;
|
|
27354
27463
|
}
|
|
27355
27464
|
function listAccountIdsOnDisk(accountsDir) {
|
|
27356
|
-
if (!
|
|
27465
|
+
if (!existsSync40(accountsDir)) return [];
|
|
27357
27466
|
let entries2;
|
|
27358
27467
|
try {
|
|
27359
27468
|
entries2 = readdirSync25(accountsDir);
|
|
@@ -27361,7 +27470,7 @@ function listAccountIdsOnDisk(accountsDir) {
|
|
|
27361
27470
|
return [];
|
|
27362
27471
|
}
|
|
27363
27472
|
return entries2.filter(
|
|
27364
|
-
(name) => UUID_RE4.test(name) &&
|
|
27473
|
+
(name) => UUID_RE4.test(name) && existsSync40(resolve36(accountsDir, name, "account.json"))
|
|
27365
27474
|
);
|
|
27366
27475
|
}
|
|
27367
27476
|
function resolveAccountIdByIngestToken(accountsDir, token) {
|
|
@@ -27498,6 +27607,7 @@ async function managerRcRecover(opts) {
|
|
|
27498
27607
|
body: JSON.stringify({
|
|
27499
27608
|
sessionId: opts.sessionId,
|
|
27500
27609
|
recover: true,
|
|
27610
|
+
subscriberAttached: opts.subscriberAttached,
|
|
27501
27611
|
role: "admin",
|
|
27502
27612
|
channel: "webchat",
|
|
27503
27613
|
senderId: opts.webchatChannel.key,
|
|
@@ -28215,7 +28325,7 @@ init_helpers();
|
|
|
28215
28325
|
init_claude_agent();
|
|
28216
28326
|
init_account_scope();
|
|
28217
28327
|
init_neo4j_store();
|
|
28218
|
-
import { existsSync as
|
|
28328
|
+
import { existsSync as existsSync41 } from "fs";
|
|
28219
28329
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
28220
28330
|
import { join as join50 } from "path";
|
|
28221
28331
|
|
|
@@ -28885,7 +28995,7 @@ app53.get("/booking-link", requireAdminSession, (c) => {
|
|
|
28885
28995
|
}
|
|
28886
28996
|
const id82 = account.accountId.slice(0, 8);
|
|
28887
28997
|
try {
|
|
28888
|
-
if (!
|
|
28998
|
+
if (!existsSync41(join50(account.accountDir, AVAILABILITY_FILENAME))) {
|
|
28889
28999
|
console.log(`[calendar] op=booking-link accountId=${id82} bookingDomain=none source=availability-config`);
|
|
28890
29000
|
return c.json({ bookingDomain: null });
|
|
28891
29001
|
}
|
|
@@ -28907,7 +29017,7 @@ var calendar_default = app53;
|
|
|
28907
29017
|
init_dist();
|
|
28908
29018
|
init_helpers();
|
|
28909
29019
|
init_account_scope();
|
|
28910
|
-
import { existsSync as
|
|
29020
|
+
import { existsSync as existsSync42, readFileSync as readFileSync42 } from "fs";
|
|
28911
29021
|
import { join as join51 } from "path";
|
|
28912
29022
|
var app54 = new Hono();
|
|
28913
29023
|
var PORTAL_FILENAME = "data-portal.json";
|
|
@@ -28920,7 +29030,7 @@ app54.get("/portal-link", requireAdminSession, (c) => {
|
|
|
28920
29030
|
const id82 = account.accountId.slice(0, 8);
|
|
28921
29031
|
try {
|
|
28922
29032
|
const path = join51(account.accountDir, PORTAL_FILENAME);
|
|
28923
|
-
if (!
|
|
29033
|
+
if (!existsSync42(path)) {
|
|
28924
29034
|
console.log(`[data-portal] op=portal-link accountId=${id82} portalDomain=none source=data-portal-config`);
|
|
28925
29035
|
return c.json({ portalDomain: null });
|
|
28926
29036
|
}
|
|
@@ -28945,10 +29055,12 @@ init_neo4j_store();
|
|
|
28945
29055
|
init_src2();
|
|
28946
29056
|
import cronParser from "cron-parser";
|
|
28947
29057
|
import { int as int2 } from "neo4j-driver";
|
|
29058
|
+
import { readFileSync as readFileSync44 } from "fs";
|
|
29059
|
+
import { resolve as resolve38 } from "path";
|
|
28948
29060
|
|
|
28949
29061
|
// ../lib/agent-dispatch-rule/src/index.ts
|
|
28950
29062
|
import { readFileSync as readFileSync43 } from "fs";
|
|
28951
|
-
import { resolve as
|
|
29063
|
+
import { resolve as resolve37 } from "path";
|
|
28952
29064
|
function normalizeE1642(value) {
|
|
28953
29065
|
let candidate = value.trim().replace(/[\s\-().]/g, "");
|
|
28954
29066
|
candidate = candidate.replace(/^whatsapp:/i, "");
|
|
@@ -28965,7 +29077,7 @@ function phonesMatch2(a, b) {
|
|
|
28965
29077
|
function readChannelAdmins(accountDir, channel) {
|
|
28966
29078
|
let config = {};
|
|
28967
29079
|
try {
|
|
28968
|
-
config = JSON.parse(readFileSync43(
|
|
29080
|
+
config = JSON.parse(readFileSync43(resolve37(accountDir, "account.json"), "utf-8"));
|
|
28969
29081
|
} catch {
|
|
28970
29082
|
config = {};
|
|
28971
29083
|
}
|
|
@@ -29022,7 +29134,7 @@ function boundSubAccountFor(accountDir, destination) {
|
|
|
29022
29134
|
}
|
|
29023
29135
|
function validateAgentDestination(accountsDir, houseAccountId, creatingAccountId, channel, destination) {
|
|
29024
29136
|
if (channel === "whatsapp") {
|
|
29025
|
-
const houseDir =
|
|
29137
|
+
const houseDir = resolve37(accountsDir, houseAccountId);
|
|
29026
29138
|
const registered = registeredAccountForDestination(houseDir, houseAccountId, destination);
|
|
29027
29139
|
if (registered === null) return { valid: false, reason: "not-registered" };
|
|
29028
29140
|
if (creatingAccountId === houseAccountId) return { valid: true };
|
|
@@ -29034,7 +29146,7 @@ function validateAgentDestination(accountsDir, houseAccountId, creatingAccountId
|
|
|
29034
29146
|
scheduleAccount: creatingAccountId
|
|
29035
29147
|
};
|
|
29036
29148
|
}
|
|
29037
|
-
const { adminUsers } = readChannelAdmins(
|
|
29149
|
+
const { adminUsers } = readChannelAdmins(resolve37(accountsDir, creatingAccountId), "telegram");
|
|
29038
29150
|
const asNumber = Number(destination);
|
|
29039
29151
|
const ok2 = Number.isInteger(asNumber) && adminUsers.includes(asNumber);
|
|
29040
29152
|
return ok2 ? { valid: true } : { valid: false, reason: "not-registered" };
|
|
@@ -29044,7 +29156,7 @@ function listDispatchDestinations(input) {
|
|
|
29044
29156
|
const managers = [];
|
|
29045
29157
|
const admins = [];
|
|
29046
29158
|
if (houseAccountId !== null) {
|
|
29047
|
-
const houseDir =
|
|
29159
|
+
const houseDir = resolve37(accountsDir, houseAccountId);
|
|
29048
29160
|
const { adminPhones, accountManagers } = readChannelAdmins(houseDir, "whatsapp");
|
|
29049
29161
|
const seen = /* @__PURE__ */ new Set();
|
|
29050
29162
|
for (const [phone, subAccountId] of Object.entries(accountManagers)) {
|
|
@@ -29062,7 +29174,7 @@ function listDispatchDestinations(input) {
|
|
|
29062
29174
|
admins.push({ channel: "whatsapp", destination: phone, label: "House admin" });
|
|
29063
29175
|
}
|
|
29064
29176
|
}
|
|
29065
|
-
const { byBot: byBot2 } = readChannelAdmins(
|
|
29177
|
+
const { byBot: byBot2 } = readChannelAdmins(resolve37(accountsDir, accountId), "telegram");
|
|
29066
29178
|
const telegram = byBot2.flatMap((b) => b.adminUsers.map((id) => ({ botId: b.botId, id }))).sort((a, b) => a.id - b.id || a.botId.localeCompare(b.botId)).map(({ botId, id }) => ({
|
|
29067
29179
|
channel: "telegram",
|
|
29068
29180
|
destination: String(id),
|
|
@@ -29112,6 +29224,23 @@ var ROUTINE_COLS = `e.eventId AS eventId, e.name AS name, e.eventStatus AS event
|
|
|
29112
29224
|
function since24h() {
|
|
29113
29225
|
return new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
29114
29226
|
}
|
|
29227
|
+
function gateClassification(row) {
|
|
29228
|
+
const judged = row.eventStatus === "scheduled" && row.recurrence !== null && row.agentChannel !== null && row.agentDestination !== null && row.agentPrompt !== null;
|
|
29229
|
+
if (!judged) return null;
|
|
29230
|
+
if (row.gatePlugin) return true;
|
|
29231
|
+
return typeof row.gateWaived === "string" && row.gateWaived.trim() !== "";
|
|
29232
|
+
}
|
|
29233
|
+
function gateWatermarkPath(eventId) {
|
|
29234
|
+
return resolve38(PLATFORM_ROOT, "..", "state", "scheduling-gate", `${eventId}.json`);
|
|
29235
|
+
}
|
|
29236
|
+
function readGateLastCommitAt(eventId) {
|
|
29237
|
+
try {
|
|
29238
|
+
const parsed = JSON.parse(readFileSync44(gateWatermarkPath(eventId), "utf-8"));
|
|
29239
|
+
return typeof parsed?.lastCommitAt === "string" ? parsed.lastCommitAt : null;
|
|
29240
|
+
} catch {
|
|
29241
|
+
return null;
|
|
29242
|
+
}
|
|
29243
|
+
}
|
|
29115
29244
|
function mapRoutine(r) {
|
|
29116
29245
|
const g = (k) => r.get(k) ?? null;
|
|
29117
29246
|
const n = (k) => {
|
|
@@ -29161,7 +29290,23 @@ function mapRoutine(r) {
|
|
|
29161
29290
|
// only the list: the modal's save replaces the list row wholesale
|
|
29162
29291
|
// (page.tsx:202), so counts served on the list alone would blank on save.
|
|
29163
29292
|
runs24h: n("runs24h"),
|
|
29164
|
-
gated24h: n("gated24h")
|
|
29293
|
+
gated24h: n("gated24h"),
|
|
29294
|
+
// Task 2472 — computed from columns already projected above, so no Cypher
|
|
29295
|
+
// changes and no second query. See gateClassification for why `null` is a
|
|
29296
|
+
// third state rather than a default `true`.
|
|
29297
|
+
gateClassified: gateClassification({
|
|
29298
|
+
eventStatus: g("eventStatus"),
|
|
29299
|
+
recurrence: g("recurrence"),
|
|
29300
|
+
agentChannel: g("agentChannel"),
|
|
29301
|
+
agentDestination: g("agentDestination"),
|
|
29302
|
+
agentPrompt: g("agentPrompt"),
|
|
29303
|
+
gatePlugin: g("gatePlugin"),
|
|
29304
|
+
gateWaived: g("gateWaived")
|
|
29305
|
+
}),
|
|
29306
|
+
// Task 2472 — read only for a gated row: an ungated one has no gate to be
|
|
29307
|
+
// late, and a file read per card on every list would otherwise be spent
|
|
29308
|
+
// proving that.
|
|
29309
|
+
gateLastCommitAt: g("gateTool") === null ? null : readGateLastCommitAt(g("eventId") ?? "")
|
|
29165
29310
|
};
|
|
29166
29311
|
}
|
|
29167
29312
|
function dispatchFieldsOf(r) {
|
|
@@ -29919,8 +30064,8 @@ init_dist();
|
|
|
29919
30064
|
init_helpers();
|
|
29920
30065
|
init_account_scope();
|
|
29921
30066
|
init_route_cache();
|
|
29922
|
-
import { existsSync as
|
|
29923
|
-
import { resolve as
|
|
30067
|
+
import { existsSync as existsSync43, readdirSync as readdirSync26, readFileSync as readFileSync45, rmSync as rmSync6 } from "fs";
|
|
30068
|
+
import { resolve as resolve39 } from "path";
|
|
29924
30069
|
var app56 = new Hono();
|
|
29925
30070
|
var NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
29926
30071
|
function splitSkillMd(text) {
|
|
@@ -29943,7 +30088,7 @@ function stripQuotes(s) {
|
|
|
29943
30088
|
return s;
|
|
29944
30089
|
}
|
|
29945
30090
|
function subdirs2(dir) {
|
|
29946
|
-
if (!
|
|
30091
|
+
if (!existsSync43(dir)) return [];
|
|
29947
30092
|
try {
|
|
29948
30093
|
return readdirSync26(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
29949
30094
|
} catch {
|
|
@@ -29958,19 +30103,19 @@ app56.get("/", requireAdminSession, async (c) => {
|
|
|
29958
30103
|
}
|
|
29959
30104
|
ensureAccountWatchers(acct.accountId, acct.accountDir);
|
|
29960
30105
|
const { value, state } = await getOrCompute("skills", acct.accountId, async () => {
|
|
29961
|
-
const pluginsDir =
|
|
30106
|
+
const pluginsDir = resolve39(acct.accountDir, "plugins");
|
|
29962
30107
|
const skills = [];
|
|
29963
30108
|
let pluginCount = 0;
|
|
29964
30109
|
for (const plugin of subdirs2(pluginsDir)) {
|
|
29965
30110
|
pluginCount++;
|
|
29966
|
-
const skillsDir =
|
|
30111
|
+
const skillsDir = resolve39(pluginsDir, plugin, "skills");
|
|
29967
30112
|
for (const skill of subdirs2(skillsDir)) {
|
|
29968
|
-
const skillMd =
|
|
29969
|
-
if (!
|
|
30113
|
+
const skillMd = resolve39(skillsDir, skill, "SKILL.md");
|
|
30114
|
+
if (!existsSync43(skillMd)) continue;
|
|
29970
30115
|
try {
|
|
29971
|
-
const { frontmatter } = splitSkillMd(
|
|
29972
|
-
const refsDir =
|
|
29973
|
-
const refCount =
|
|
30116
|
+
const { frontmatter } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
|
|
30117
|
+
const refsDir = resolve39(skillsDir, skill, "references");
|
|
30118
|
+
const refCount = existsSync43(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).length : 0;
|
|
29974
30119
|
skills.push({ plugin, skill, description: frontmatter.description, refCount });
|
|
29975
30120
|
} catch (err) {
|
|
29976
30121
|
console.error(`[admin:skills] op=list-skip plugin=${plugin} skill=${skill} reason="${err instanceof Error ? err.message : String(err)}"`);
|
|
@@ -29992,15 +30137,15 @@ app56.get("/:plugin/:skill", requireAdminSession, async (c) => {
|
|
|
29992
30137
|
console.error(`[admin:skills] op=get outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
|
|
29993
30138
|
return c.json({ error: "Invalid plugin or skill name" }, 400);
|
|
29994
30139
|
}
|
|
29995
|
-
const skillDir =
|
|
29996
|
-
const skillMd =
|
|
29997
|
-
const found =
|
|
30140
|
+
const skillDir = resolve39(acct.accountDir, "plugins", plugin, "skills", skill);
|
|
30141
|
+
const skillMd = resolve39(skillDir, "SKILL.md");
|
|
30142
|
+
const found = existsSync43(skillMd);
|
|
29998
30143
|
console.log(`[admin:skills] op=get plugin=${plugin} skill=${skill} found=${found}`);
|
|
29999
30144
|
if (!found) return c.json({ error: "Skill not found" }, 404);
|
|
30000
30145
|
try {
|
|
30001
|
-
const { frontmatter, body } = splitSkillMd(
|
|
30002
|
-
const refsDir =
|
|
30003
|
-
const references =
|
|
30146
|
+
const { frontmatter, body } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
|
|
30147
|
+
const refsDir = resolve39(skillDir, "references");
|
|
30148
|
+
const references = existsSync43(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort() : [];
|
|
30004
30149
|
return c.json({ skill: { plugin, skill, frontmatter, body, references } });
|
|
30005
30150
|
} catch (err) {
|
|
30006
30151
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -30017,10 +30162,10 @@ app56.delete("/:plugin/:skill", requireAdminSession, async (c) => {
|
|
|
30017
30162
|
console.error(`[admin:skills] op=delete outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
|
|
30018
30163
|
return c.json({ error: "Invalid plugin or skill name" }, 400);
|
|
30019
30164
|
}
|
|
30020
|
-
const pluginDir =
|
|
30021
|
-
const skillsDir =
|
|
30022
|
-
const skillDir =
|
|
30023
|
-
if (!
|
|
30165
|
+
const pluginDir = resolve39(acct.accountDir, "plugins", plugin);
|
|
30166
|
+
const skillsDir = resolve39(pluginDir, "skills");
|
|
30167
|
+
const skillDir = resolve39(skillsDir, skill);
|
|
30168
|
+
if (!existsSync43(resolve39(skillDir, "SKILL.md"))) {
|
|
30024
30169
|
console.error(`[admin:skills] op=delete outcome=rejected reason=not-found plugin=${plugin} skill=${skill}`);
|
|
30025
30170
|
return c.json({ error: "Skill not found" }, 404);
|
|
30026
30171
|
}
|
|
@@ -30028,7 +30173,7 @@ app56.delete("/:plugin/:skill", requireAdminSession, async (c) => {
|
|
|
30028
30173
|
rmSync6(skillDir, { recursive: true, force: true });
|
|
30029
30174
|
const pluginDirRemoved = subdirs2(skillsDir).length === 0;
|
|
30030
30175
|
if (pluginDirRemoved) rmSync6(pluginDir, { recursive: true, force: true });
|
|
30031
|
-
const skillDirGone = !
|
|
30176
|
+
const skillDirGone = !existsSync43(skillDir);
|
|
30032
30177
|
if (!skillDirGone) {
|
|
30033
30178
|
console.error(`[admin:skills] op=delete plugin=${plugin} skill=${skill} outcome=rejected reason=rm-incomplete skillDirGone=false`);
|
|
30034
30179
|
return c.json({ error: "Delete did not remove the skill directory" }, 503);
|
|
@@ -30672,8 +30817,8 @@ init_neo4j_store();
|
|
|
30672
30817
|
// app/lib/admin-roster.ts
|
|
30673
30818
|
init_account();
|
|
30674
30819
|
init_neo4j_store();
|
|
30675
|
-
import { existsSync as
|
|
30676
|
-
import { resolve as
|
|
30820
|
+
import { existsSync as existsSync44, readdirSync as readdirSync27, readFileSync as readFileSync46 } from "fs";
|
|
30821
|
+
import { resolve as resolve40 } from "path";
|
|
30677
30822
|
function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
|
|
30678
30823
|
let entries2;
|
|
30679
30824
|
try {
|
|
@@ -30681,11 +30826,11 @@ function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
|
|
|
30681
30826
|
} catch {
|
|
30682
30827
|
return [];
|
|
30683
30828
|
}
|
|
30684
|
-
for (const
|
|
30685
|
-
const cfgPath =
|
|
30686
|
-
if (!
|
|
30829
|
+
for (const entry2 of entries2) {
|
|
30830
|
+
const cfgPath = resolve40(accountsDir, entry2, "account.json");
|
|
30831
|
+
if (!existsSync44(cfgPath)) continue;
|
|
30687
30832
|
try {
|
|
30688
|
-
const cfg = JSON.parse(
|
|
30833
|
+
const cfg = JSON.parse(readFileSync46(cfgPath, "utf-8"));
|
|
30689
30834
|
if (cfg.role !== "house") continue;
|
|
30690
30835
|
if (!Array.isArray(cfg.admins)) return [];
|
|
30691
30836
|
return cfg.admins.map(
|
|
@@ -31033,7 +31178,7 @@ init_claude_agent();
|
|
|
31033
31178
|
init_account();
|
|
31034
31179
|
init_neo4j_store();
|
|
31035
31180
|
init_src2();
|
|
31036
|
-
import { resolve as
|
|
31181
|
+
import { resolve as resolve41 } from "path";
|
|
31037
31182
|
var TAG49 = "[admin:routines]";
|
|
31038
31183
|
function scopeFor(d, houseDir, houseAccountId) {
|
|
31039
31184
|
if (d.channel !== "whatsapp") return null;
|
|
@@ -31063,7 +31208,7 @@ app67.get("/", requireAdminSession, async (c) => {
|
|
|
31063
31208
|
console.error(`${TAG49} op=destinations account=${accountId.slice(0, 8)} outcome=house-unresolved reason="${message}"`);
|
|
31064
31209
|
}
|
|
31065
31210
|
const destinations = listDispatchDestinations({ accountsDir: ACCOUNTS_DIR, houseAccountId, accountId });
|
|
31066
|
-
const houseDir = houseAccountId === null ? null :
|
|
31211
|
+
const houseDir = houseAccountId === null ? null : resolve41(ACCOUNTS_DIR, houseAccountId);
|
|
31067
31212
|
const byScope = /* @__PURE__ */ new Map();
|
|
31068
31213
|
for (const d of destinations) {
|
|
31069
31214
|
const scope = houseDir === null || houseAccountId === null ? null : scopeFor(d, houseDir, houseAccountId);
|
|
@@ -31413,9 +31558,9 @@ init_dist();
|
|
|
31413
31558
|
|
|
31414
31559
|
// app/lib/access-email.ts
|
|
31415
31560
|
import { spawn as spawn2 } from "child_process";
|
|
31416
|
-
import { resolve as
|
|
31417
|
-
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ??
|
|
31418
|
-
var SEND_SCRIPT =
|
|
31561
|
+
import { resolve as resolve42 } from "path";
|
|
31562
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve42(process.cwd(), "..");
|
|
31563
|
+
var SEND_SCRIPT = resolve42(
|
|
31419
31564
|
PLATFORM_ROOT5,
|
|
31420
31565
|
"plugins",
|
|
31421
31566
|
"email",
|
|
@@ -31559,8 +31704,8 @@ var access_default = app72;
|
|
|
31559
31704
|
// server/routes/sites.ts
|
|
31560
31705
|
init_dist();
|
|
31561
31706
|
init_claude_agent();
|
|
31562
|
-
import { existsSync as
|
|
31563
|
-
import { resolve as
|
|
31707
|
+
import { existsSync as existsSync45, readFileSync as readFileSync47, realpathSync as realpathSync8, statSync as statSync25 } from "fs";
|
|
31708
|
+
import { resolve as resolve43 } from "path";
|
|
31564
31709
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
31565
31710
|
var MIME = {
|
|
31566
31711
|
".html": "text/html; charset=utf-8",
|
|
@@ -31617,15 +31762,15 @@ app73.get("/:rel{.*}", (c) => {
|
|
|
31617
31762
|
}
|
|
31618
31763
|
segments.push(seg);
|
|
31619
31764
|
}
|
|
31620
|
-
const rootDir =
|
|
31621
|
-
let filePath = segments.length === 0 ? rootDir :
|
|
31765
|
+
const rootDir = resolve43(account.accountDir, "sites");
|
|
31766
|
+
let filePath = segments.length === 0 ? rootDir : resolve43(rootDir, ...segments);
|
|
31622
31767
|
if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
|
|
31623
31768
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
31624
31769
|
return c.text("Forbidden", 403);
|
|
31625
31770
|
}
|
|
31626
31771
|
let stat11;
|
|
31627
31772
|
try {
|
|
31628
|
-
stat11 =
|
|
31773
|
+
stat11 = existsSync45(filePath) ? statSync25(filePath) : null;
|
|
31629
31774
|
} catch {
|
|
31630
31775
|
stat11 = null;
|
|
31631
31776
|
}
|
|
@@ -31638,13 +31783,13 @@ app73.get("/:rel{.*}", (c) => {
|
|
|
31638
31783
|
return c.redirect(target, 301);
|
|
31639
31784
|
}
|
|
31640
31785
|
if (stat11?.isDirectory()) {
|
|
31641
|
-
filePath =
|
|
31786
|
+
filePath = resolve43(filePath, "index.html");
|
|
31642
31787
|
}
|
|
31643
31788
|
if (!filePath.startsWith(rootDir + "/")) {
|
|
31644
31789
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
31645
31790
|
return c.text("Forbidden", 403);
|
|
31646
31791
|
}
|
|
31647
|
-
if (!
|
|
31792
|
+
if (!existsSync45(filePath)) {
|
|
31648
31793
|
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
31649
31794
|
return c.text("Not found", 404);
|
|
31650
31795
|
}
|
|
@@ -31663,7 +31808,7 @@ app73.get("/:rel{.*}", (c) => {
|
|
|
31663
31808
|
}
|
|
31664
31809
|
let body;
|
|
31665
31810
|
try {
|
|
31666
|
-
body =
|
|
31811
|
+
body = readFileSync47(realPath);
|
|
31667
31812
|
} catch (err) {
|
|
31668
31813
|
const code = err?.code;
|
|
31669
31814
|
if (code === "EISDIR") {
|
|
@@ -31705,7 +31850,7 @@ init_neo4j_store();
|
|
|
31705
31850
|
// app/lib/visitor-token.ts
|
|
31706
31851
|
init_paths();
|
|
31707
31852
|
import { createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
31708
|
-
import { mkdirSync as mkdirSync9, readFileSync as
|
|
31853
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync48, writeFileSync as writeFileSync17 } from "fs";
|
|
31709
31854
|
import { dirname as dirname16 } from "path";
|
|
31710
31855
|
var TOKEN_PREFIX = "v1.";
|
|
31711
31856
|
var SECRET_BYTES = 32;
|
|
@@ -31714,7 +31859,7 @@ var cachedSecret = null;
|
|
|
31714
31859
|
function getSecret() {
|
|
31715
31860
|
if (cachedSecret) return cachedSecret;
|
|
31716
31861
|
try {
|
|
31717
|
-
const hex2 =
|
|
31862
|
+
const hex2 = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
31718
31863
|
if (hex2.length === SECRET_BYTES * 2) {
|
|
31719
31864
|
cachedSecret = Buffer.from(hex2, "hex");
|
|
31720
31865
|
return cachedSecret;
|
|
@@ -31728,7 +31873,7 @@ function getSecret() {
|
|
|
31728
31873
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
31729
31874
|
} catch {
|
|
31730
31875
|
}
|
|
31731
|
-
const hex =
|
|
31876
|
+
const hex = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
31732
31877
|
cachedSecret = Buffer.from(hex, "hex");
|
|
31733
31878
|
return cachedSecret;
|
|
31734
31879
|
}
|
|
@@ -31784,7 +31929,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
|
31784
31929
|
init_dist();
|
|
31785
31930
|
|
|
31786
31931
|
// app/lib/brand-config.ts
|
|
31787
|
-
import { existsSync as
|
|
31932
|
+
import { existsSync as existsSync46, readFileSync as readFileSync49 } from "fs";
|
|
31788
31933
|
import { join as join53 } from "path";
|
|
31789
31934
|
var cached2 = null;
|
|
31790
31935
|
var cachedAttempted = false;
|
|
@@ -31794,9 +31939,9 @@ function readBrandConfig() {
|
|
|
31794
31939
|
const platformRoot5 = process.env.MAXY_PLATFORM_ROOT;
|
|
31795
31940
|
if (!platformRoot5) return null;
|
|
31796
31941
|
const brandPath = join53(platformRoot5, "config", "brand.json");
|
|
31797
|
-
if (!
|
|
31942
|
+
if (!existsSync46(brandPath)) return null;
|
|
31798
31943
|
try {
|
|
31799
|
-
cached2 = JSON.parse(
|
|
31944
|
+
cached2 = JSON.parse(readFileSync49(brandPath, "utf-8"));
|
|
31800
31945
|
return cached2;
|
|
31801
31946
|
} catch {
|
|
31802
31947
|
return null;
|
|
@@ -32075,9 +32220,9 @@ function originAllowed(origin, allowlist) {
|
|
|
32075
32220
|
try {
|
|
32076
32221
|
const u = new URL(origin);
|
|
32077
32222
|
const host = u.host;
|
|
32078
|
-
return allowlist.some((
|
|
32079
|
-
if (
|
|
32080
|
-
if (
|
|
32223
|
+
return allowlist.some((entry2) => {
|
|
32224
|
+
if (entry2 === host) return true;
|
|
32225
|
+
if (entry2.startsWith("*.")) return host.endsWith(entry2.slice(1));
|
|
32081
32226
|
return false;
|
|
32082
32227
|
});
|
|
32083
32228
|
} catch {
|
|
@@ -32299,16 +32444,16 @@ var visitor_event_default = app76;
|
|
|
32299
32444
|
// server/routes/session.ts
|
|
32300
32445
|
init_dist();
|
|
32301
32446
|
init_claude_agent();
|
|
32302
|
-
import { resolve as
|
|
32303
|
-
import { existsSync as
|
|
32447
|
+
import { resolve as resolve44 } from "path";
|
|
32448
|
+
import { existsSync as existsSync47, writeFileSync as writeFileSync18, mkdirSync as mkdirSync10 } from "fs";
|
|
32304
32449
|
init_neo4j_store();
|
|
32305
32450
|
init_paths();
|
|
32306
32451
|
var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
32307
32452
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
32308
32453
|
try {
|
|
32309
|
-
const cacheDir =
|
|
32454
|
+
const cacheDir = resolve44(MAXY_DIR, "branding-cache", accountId);
|
|
32310
32455
|
mkdirSync10(cacheDir, { recursive: true });
|
|
32311
|
-
writeFileSync18(
|
|
32456
|
+
writeFileSync18(resolve44(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
32312
32457
|
} catch (err) {
|
|
32313
32458
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
32314
32459
|
}
|
|
@@ -32389,8 +32534,8 @@ app77.post("/", async (c) => {
|
|
|
32389
32534
|
}
|
|
32390
32535
|
let agentConfig = null;
|
|
32391
32536
|
if (account) {
|
|
32392
|
-
const agentDir =
|
|
32393
|
-
if (!
|
|
32537
|
+
const agentDir = resolve44(account.accountDir, "agents", agentSlug);
|
|
32538
|
+
if (!existsSync47(agentDir) || !existsSync47(resolve44(agentDir, "config.json"))) {
|
|
32394
32539
|
return c.json({ error: "Agent not found" }, 404);
|
|
32395
32540
|
}
|
|
32396
32541
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -32574,7 +32719,7 @@ var calendar_public_default = app78;
|
|
|
32574
32719
|
init_dist();
|
|
32575
32720
|
import { createReadStream as createReadStream3 } from "fs";
|
|
32576
32721
|
import { stat as stat8, readFile as readFile11, realpath } from "fs/promises";
|
|
32577
|
-
import { join as join54, resolve as
|
|
32722
|
+
import { join as join54, resolve as resolve45, sep as sep11 } from "path";
|
|
32578
32723
|
import { Readable as Readable3 } from "stream";
|
|
32579
32724
|
init_data_path();
|
|
32580
32725
|
init_attachments();
|
|
@@ -32629,7 +32774,7 @@ ${expiresAtMs}`)
|
|
|
32629
32774
|
return diff2 === 0;
|
|
32630
32775
|
}
|
|
32631
32776
|
async function readAccountSecret(accountId) {
|
|
32632
|
-
const path =
|
|
32777
|
+
const path = resolve45(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
|
|
32633
32778
|
let text;
|
|
32634
32779
|
try {
|
|
32635
32780
|
text = await readFile11(path, "utf8");
|
|
@@ -32665,7 +32810,7 @@ async function resolveFetchTarget(accountDir, relPath, ownerId) {
|
|
|
32665
32810
|
const exposeFolders = await readExposeFolders2(accountDir);
|
|
32666
32811
|
const { exposed } = resolveExposedDirs(schemaText, exposeFolders);
|
|
32667
32812
|
if (exposed.length === 0 && !ownerId) return { ok: false, reason: "not-exposed" };
|
|
32668
|
-
const lexical =
|
|
32813
|
+
const lexical = resolve45(accountDir, relPath);
|
|
32669
32814
|
let root;
|
|
32670
32815
|
let absolute;
|
|
32671
32816
|
try {
|
|
@@ -32714,7 +32859,7 @@ async function handle(c, headOnly) {
|
|
|
32714
32859
|
return c.json({ ok: false, error: "denied" }, 401);
|
|
32715
32860
|
}
|
|
32716
32861
|
const target = await resolveFetchTarget(
|
|
32717
|
-
|
|
32862
|
+
resolve45(DATA_ROOT, "accounts", accountId),
|
|
32718
32863
|
relPath,
|
|
32719
32864
|
owner || void 0
|
|
32720
32865
|
);
|
|
@@ -32836,7 +32981,7 @@ init_neo4j_store();
|
|
|
32836
32981
|
|
|
32837
32982
|
// app/lib/ledger-census.ts
|
|
32838
32983
|
init_loop_registry();
|
|
32839
|
-
import { readdirSync as readdirSync29, readFileSync as
|
|
32984
|
+
import { readdirSync as readdirSync29, readFileSync as readFileSync50, statSync as statSync26 } from "fs";
|
|
32840
32985
|
import { join as join55 } from "path";
|
|
32841
32986
|
|
|
32842
32987
|
// ../lib/ledger-core/dist/reconcile.js
|
|
@@ -32894,7 +33039,7 @@ function countWriteRejects24h(logDir, now) {
|
|
|
32894
33039
|
const path = join55(logDir, name);
|
|
32895
33040
|
try {
|
|
32896
33041
|
if (now - statSync26(path).mtimeMs > DAY_MS) continue;
|
|
32897
|
-
for (const line of
|
|
33042
|
+
for (const line of readFileSync50(path, "utf8").split("\n")) {
|
|
32898
33043
|
if (line.includes("[graph-write] reject")) total += 1;
|
|
32899
33044
|
}
|
|
32900
33045
|
} catch {
|
|
@@ -32997,7 +33142,7 @@ function startLedgerCensus(openSession, opts = {}) {
|
|
|
32997
33142
|
// app/lib/dispatch-census.ts
|
|
32998
33143
|
init_loop_registry();
|
|
32999
33144
|
init_account();
|
|
33000
|
-
import { readFileSync as
|
|
33145
|
+
import { readFileSync as readFileSync51 } from "fs";
|
|
33001
33146
|
import { join as join56 } from "path";
|
|
33002
33147
|
var CENSUS_TAG2 = "[dispatch-census]";
|
|
33003
33148
|
var CONFIG_AUDIT_TAG = "[dispatch-config-audit]";
|
|
@@ -33031,7 +33176,7 @@ function percentile(sortedAsc, p) {
|
|
|
33031
33176
|
function auditConfigFile(accountDir) {
|
|
33032
33177
|
let cfg;
|
|
33033
33178
|
try {
|
|
33034
|
-
cfg = JSON.parse(
|
|
33179
|
+
cfg = JSON.parse(readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8"));
|
|
33035
33180
|
} catch {
|
|
33036
33181
|
return null;
|
|
33037
33182
|
}
|
|
@@ -33056,7 +33201,7 @@ function auditConfigFile(accountDir) {
|
|
|
33056
33201
|
function terminalStatuses(accountDir) {
|
|
33057
33202
|
try {
|
|
33058
33203
|
const cfg = JSON.parse(
|
|
33059
|
-
|
|
33204
|
+
readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8")
|
|
33060
33205
|
);
|
|
33061
33206
|
return {
|
|
33062
33207
|
job: (cfg.statuses?.job ?? []).filter((s) => s.terminal).map((s) => s.name),
|
|
@@ -33447,7 +33592,7 @@ function startGraphHealthTimer() {
|
|
|
33447
33592
|
init_loop_registry();
|
|
33448
33593
|
init_data_path();
|
|
33449
33594
|
var import_dist11 = __toESM(require_dist(), 1);
|
|
33450
|
-
import { existsSync as
|
|
33595
|
+
import { existsSync as existsSync48, statSync as statSync27, readdirSync as readdirSync30 } from "fs";
|
|
33451
33596
|
import { join as join57 } from "path";
|
|
33452
33597
|
var TAG57 = "[shared-census]";
|
|
33453
33598
|
var WIRED_SURFACES = 3;
|
|
@@ -33467,7 +33612,7 @@ function countFiles(dir) {
|
|
|
33467
33612
|
}
|
|
33468
33613
|
function runSharedFolderCensus(dataRoot = DATA_ROOT) {
|
|
33469
33614
|
const root = (0, import_dist11.sharedRoot)(dataRoot);
|
|
33470
|
-
const exists =
|
|
33615
|
+
const exists = existsSync48(root);
|
|
33471
33616
|
let isDir = false;
|
|
33472
33617
|
if (exists) {
|
|
33473
33618
|
try {
|
|
@@ -33493,7 +33638,7 @@ function startSharedFolderCensus() {
|
|
|
33493
33638
|
// app/lib/file-watcher.ts
|
|
33494
33639
|
init_data_path();
|
|
33495
33640
|
import * as fsp2 from "fs/promises";
|
|
33496
|
-
import { resolve as
|
|
33641
|
+
import { resolve as resolve46, sep as sep12 } from "path";
|
|
33497
33642
|
var ACCOUNT_UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
33498
33643
|
var DEFAULT_COALESCE_MS = 500;
|
|
33499
33644
|
var ROOTS = ["accounts"];
|
|
@@ -33512,7 +33657,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
33512
33657
|
const dropFn = opts.drop ?? dropFileIndex;
|
|
33513
33658
|
for (const r of ROOTS) {
|
|
33514
33659
|
try {
|
|
33515
|
-
await fsp2.mkdir(
|
|
33660
|
+
await fsp2.mkdir(resolve46(dataRoot, r), { recursive: true });
|
|
33516
33661
|
} catch (err) {
|
|
33517
33662
|
console.error(
|
|
33518
33663
|
`[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
|
|
@@ -33533,7 +33678,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
33533
33678
|
timers.set(relativePath, t);
|
|
33534
33679
|
}
|
|
33535
33680
|
async function runHook(relativePath, accountId) {
|
|
33536
|
-
const absolute =
|
|
33681
|
+
const absolute = resolve46(dataRoot, relativePath);
|
|
33537
33682
|
let exists = false;
|
|
33538
33683
|
try {
|
|
33539
33684
|
const st = await fsp2.stat(absolute);
|
|
@@ -33557,7 +33702,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
33557
33702
|
}
|
|
33558
33703
|
}
|
|
33559
33704
|
async function watchRoot(rootName) {
|
|
33560
|
-
const absRoot =
|
|
33705
|
+
const absRoot = resolve46(dataRoot, rootName);
|
|
33561
33706
|
try {
|
|
33562
33707
|
const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
|
|
33563
33708
|
for await (const event of iter) {
|
|
@@ -33599,7 +33744,7 @@ init_data_path();
|
|
|
33599
33744
|
init_neo4j_store();
|
|
33600
33745
|
init_account();
|
|
33601
33746
|
import { mkdir as mkdir5, readdir as readdir6, rename as rename4, rm as rm3 } from "fs/promises";
|
|
33602
|
-
import { dirname as dirname17, relative as relative8, resolve as
|
|
33747
|
+
import { dirname as dirname17, relative as relative8, resolve as resolve47 } from "path";
|
|
33603
33748
|
var ACCOUNT_UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
33604
33749
|
async function walkFiles(dir) {
|
|
33605
33750
|
const out = [];
|
|
@@ -33610,7 +33755,7 @@ async function walkFiles(dir) {
|
|
|
33610
33755
|
return out;
|
|
33611
33756
|
}
|
|
33612
33757
|
for (const e of entries2) {
|
|
33613
|
-
const abs =
|
|
33758
|
+
const abs = resolve47(dir, e.name);
|
|
33614
33759
|
if (e.isDirectory()) {
|
|
33615
33760
|
out.push(...await walkFiles(abs));
|
|
33616
33761
|
} else if (e.isFile()) {
|
|
@@ -33630,7 +33775,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
33630
33775
|
let moved = 0;
|
|
33631
33776
|
for (const oldAbs of files) {
|
|
33632
33777
|
const suffix = relative8(srcDir, oldAbs);
|
|
33633
|
-
const newAbs =
|
|
33778
|
+
const newAbs = resolve47(destDir, suffix);
|
|
33634
33779
|
await mkdir5(dirname17(newAbs), { recursive: true });
|
|
33635
33780
|
await rename4(oldAbs, newAbs);
|
|
33636
33781
|
moved++;
|
|
@@ -33657,7 +33802,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
33657
33802
|
}
|
|
33658
33803
|
async function migrateUploads(opts = {}) {
|
|
33659
33804
|
const dataRoot = opts.dataRoot ?? DATA_ROOT;
|
|
33660
|
-
const oldRoot =
|
|
33805
|
+
const oldRoot = resolve47(dataRoot, "uploads");
|
|
33661
33806
|
let topEntries;
|
|
33662
33807
|
try {
|
|
33663
33808
|
topEntries = await readdir6(oldRoot, { withFileTypes: true });
|
|
@@ -33674,12 +33819,12 @@ async function migrateUploads(opts = {}) {
|
|
|
33674
33819
|
let moved = 0;
|
|
33675
33820
|
let skipped = 0;
|
|
33676
33821
|
try {
|
|
33677
|
-
for (const
|
|
33678
|
-
const name =
|
|
33822
|
+
for (const entry2 of topEntries) {
|
|
33823
|
+
const name = entry2.name;
|
|
33679
33824
|
if (ACCOUNT_UUID_RE4.test(name)) {
|
|
33680
33825
|
moved += await relocateTree(
|
|
33681
|
-
|
|
33682
|
-
|
|
33826
|
+
resolve47(oldRoot, name),
|
|
33827
|
+
resolve47(dataRoot, "accounts", name, "uploads"),
|
|
33683
33828
|
dataRoot,
|
|
33684
33829
|
name,
|
|
33685
33830
|
session
|
|
@@ -33691,8 +33836,8 @@ async function migrateUploads(opts = {}) {
|
|
|
33691
33836
|
continue;
|
|
33692
33837
|
}
|
|
33693
33838
|
moved += await relocateTree(
|
|
33694
|
-
|
|
33695
|
-
|
|
33839
|
+
resolve47(oldRoot, "public"),
|
|
33840
|
+
resolve47(dataRoot, "accounts", installAccountId, "uploads", "public"),
|
|
33696
33841
|
dataRoot,
|
|
33697
33842
|
null,
|
|
33698
33843
|
// public uploads carry no graph nodes
|
|
@@ -33726,7 +33871,7 @@ init_pin_validator();
|
|
|
33726
33871
|
init_account();
|
|
33727
33872
|
init_neo4j_store();
|
|
33728
33873
|
import { readdir as readdir7, readFile as readFile12, rename as rename5, writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
|
|
33729
|
-
import { existsSync as
|
|
33874
|
+
import { existsSync as existsSync49 } from "fs";
|
|
33730
33875
|
import { join as join58 } from "path";
|
|
33731
33876
|
var ADMIN_ROLE2 = "admin";
|
|
33732
33877
|
var WEBCHAT_CHANNEL2 = "webchat";
|
|
@@ -33756,7 +33901,7 @@ async function writeRaw(path, obj) {
|
|
|
33756
33901
|
await rename5(tmp, path);
|
|
33757
33902
|
} catch (err) {
|
|
33758
33903
|
try {
|
|
33759
|
-
if (
|
|
33904
|
+
if (existsSync49(tmp)) await unlink4(tmp);
|
|
33760
33905
|
} catch {
|
|
33761
33906
|
}
|
|
33762
33907
|
throw err;
|
|
@@ -33779,11 +33924,11 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
33779
33924
|
} catch {
|
|
33780
33925
|
continue;
|
|
33781
33926
|
}
|
|
33782
|
-
for (const
|
|
33783
|
-
if (
|
|
33784
|
-
out.push(join58(slugDir,
|
|
33785
|
-
} else if (
|
|
33786
|
-
const subDir = join58(slugDir,
|
|
33927
|
+
for (const entry2 of entries2) {
|
|
33928
|
+
if (entry2.isFile() && SESSION_META_RE.test(entry2.name)) {
|
|
33929
|
+
out.push(join58(slugDir, entry2.name));
|
|
33930
|
+
} else if (entry2.isDirectory() && entry2.name === "subagents") {
|
|
33931
|
+
const subDir = join58(slugDir, entry2.name);
|
|
33787
33932
|
let subs;
|
|
33788
33933
|
try {
|
|
33789
33934
|
subs = await readdir7(subDir, { withFileTypes: true });
|
|
@@ -33880,7 +34025,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
|
|
|
33880
34025
|
// app/lib/migrate-sidecar-account-ids.ts
|
|
33881
34026
|
init_account();
|
|
33882
34027
|
import { readdir as readdir8, readFile as readFile13, rename as rename6, writeFile as writeFile6, unlink as unlink5 } from "fs/promises";
|
|
33883
|
-
import { existsSync as
|
|
34028
|
+
import { existsSync as existsSync50 } from "fs";
|
|
33884
34029
|
import { join as join59 } from "path";
|
|
33885
34030
|
var SESSION_META_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
33886
34031
|
function projectSlugForCwd(cwd) {
|
|
@@ -33907,7 +34052,7 @@ async function writeRaw2(path, obj) {
|
|
|
33907
34052
|
await rename6(tmp, path);
|
|
33908
34053
|
} catch (err) {
|
|
33909
34054
|
try {
|
|
33910
|
-
if (
|
|
34055
|
+
if (existsSync50(tmp)) await unlink5(tmp);
|
|
33911
34056
|
} catch {
|
|
33912
34057
|
}
|
|
33913
34058
|
throw err;
|
|
@@ -33934,11 +34079,11 @@ async function collectSidecars(projectsRoot) {
|
|
|
33934
34079
|
} catch {
|
|
33935
34080
|
continue;
|
|
33936
34081
|
}
|
|
33937
|
-
for (const
|
|
33938
|
-
if (
|
|
33939
|
-
out.push({ path: join59(slugDir,
|
|
33940
|
-
} else if (
|
|
33941
|
-
const subDir = join59(slugDir,
|
|
34082
|
+
for (const entry2 of entries2) {
|
|
34083
|
+
if (entry2.isFile() && SESSION_META_RE2.test(entry2.name)) {
|
|
34084
|
+
out.push({ path: join59(slugDir, entry2.name), slug: slug.name });
|
|
34085
|
+
} else if (entry2.isDirectory() && (entry2.name === "subagents" || entry2.name === "archive")) {
|
|
34086
|
+
const subDir = join59(slugDir, entry2.name);
|
|
33942
34087
|
let subs;
|
|
33943
34088
|
try {
|
|
33944
34089
|
subs = await readdir8(subDir, { withFileTypes: true });
|
|
@@ -35326,12 +35471,20 @@ var InboundHub2 = class {
|
|
|
35326
35471
|
/** Task-962 — terminal-drop one still-queued in-flight message by id. Returns
|
|
35327
35472
|
* whether a matching queued entry was found and removed. */
|
|
35328
35473
|
dropQueued(key2, messageId) {
|
|
35474
|
+
return this.takeQueued(key2, messageId) !== null;
|
|
35475
|
+
}
|
|
35476
|
+
/** Task 2466 — remove one still-queued in-flight message by id and return its
|
|
35477
|
+
* payload, so the caller can re-route it. `dropQueued` discards; this hands
|
|
35478
|
+
* over, and the two share this one removal so a message can never be both
|
|
35479
|
+
* dropped and re-routed. Null when no matching queued entry exists — a
|
|
35480
|
+
* message that has already drained, acked, or been dropped is not takeable. */
|
|
35481
|
+
takeQueued(key2, messageId) {
|
|
35329
35482
|
const s = this.keys.get(key2);
|
|
35330
|
-
if (!s) return
|
|
35483
|
+
if (!s) return null;
|
|
35331
35484
|
const i = s.inFlight.findIndex((e) => e.payload.messageId === messageId && e.state === "queued");
|
|
35332
|
-
if (i < 0) return
|
|
35333
|
-
s.inFlight.splice(i, 1);
|
|
35334
|
-
return
|
|
35485
|
+
if (i < 0) return null;
|
|
35486
|
+
const [entry2] = s.inFlight.splice(i, 1);
|
|
35487
|
+
return entry2.payload;
|
|
35335
35488
|
}
|
|
35336
35489
|
/** Whether a key currently has a live channel server attached. The gateway
|
|
35337
35490
|
* uses this to decide whether an inbound needs a cold-start spawn/resume. */
|
|
@@ -35481,7 +35634,7 @@ function createWebchatChannelRoutes(deps) {
|
|
|
35481
35634
|
}
|
|
35482
35635
|
|
|
35483
35636
|
// app/lib/webchat/gateway/resolve-turn-answer.ts
|
|
35484
|
-
import { existsSync as
|
|
35637
|
+
import { existsSync as existsSync51, openSync as openSync9, readSync as readSync8, closeSync as closeSync9, readdirSync as readdirSync31 } from "fs";
|
|
35485
35638
|
import { join as join60 } from "path";
|
|
35486
35639
|
function resolveTurnAnswer(p) {
|
|
35487
35640
|
if (!SESSION_ID_RE2.test(p.sessionId)) return "";
|
|
@@ -35510,7 +35663,7 @@ function resolveTurnAnswer(p) {
|
|
|
35510
35663
|
return segmentText(parts.join("\n\n")).join("\n\n");
|
|
35511
35664
|
}
|
|
35512
35665
|
function findJsonl(projectsRoot, sessionId) {
|
|
35513
|
-
if (!
|
|
35666
|
+
if (!existsSync51(projectsRoot)) return null;
|
|
35514
35667
|
let slugs;
|
|
35515
35668
|
try {
|
|
35516
35669
|
slugs = readdirSync31(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
@@ -35519,7 +35672,7 @@ function findJsonl(projectsRoot, sessionId) {
|
|
|
35519
35672
|
}
|
|
35520
35673
|
for (const slug of slugs) {
|
|
35521
35674
|
const live = join60(projectsRoot, slug, `${sessionId}.jsonl`);
|
|
35522
|
-
if (
|
|
35675
|
+
if (existsSync51(live)) return live;
|
|
35523
35676
|
}
|
|
35524
35677
|
return null;
|
|
35525
35678
|
}
|
|
@@ -35573,6 +35726,12 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35573
35726
|
/** Task-962 — `${key}::${messageId}` for messages a recovery call has already
|
|
35574
35727
|
* been issued for, so recovery is attempted at most once per message. */
|
|
35575
35728
|
recoveryAttempted = /* @__PURE__ */ new Set();
|
|
35729
|
+
/** Task 2465 — which session each conversation key's child is running. Written
|
|
35730
|
+
* at spawn (the admin id is decided before the manager call, so it is already
|
|
35731
|
+
* known there) and at turn-end (which carries key AND sessionId, so the map
|
|
35732
|
+
* survives a UI restart for any child that has completed a turn). Read only by
|
|
35733
|
+
* auditKeyOrphans. */
|
|
35734
|
+
keySession = /* @__PURE__ */ new Map();
|
|
35576
35735
|
/** Task-962 — guards against overlapping async recovery passes. */
|
|
35577
35736
|
recovering = false;
|
|
35578
35737
|
seq = 0;
|
|
@@ -35581,16 +35740,16 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35581
35740
|
* The public /api/chat route awaits this to return the reply on the same SSE
|
|
35582
35741
|
* response, preserving the pre-756 POST→single-blob browser contract. */
|
|
35583
35742
|
awaitReply(key2, timeoutMs) {
|
|
35584
|
-
return new Promise((
|
|
35743
|
+
return new Promise((resolve53) => {
|
|
35585
35744
|
const timer = setTimeout(() => {
|
|
35586
35745
|
this.replyAwaiters.delete(key2);
|
|
35587
|
-
|
|
35746
|
+
resolve53({ timeout: true });
|
|
35588
35747
|
}, timeoutMs);
|
|
35589
35748
|
if (timer.unref) timer.unref();
|
|
35590
35749
|
this.replyAwaiters.set(key2, (r) => {
|
|
35591
35750
|
clearTimeout(timer);
|
|
35592
35751
|
this.replyAwaiters.delete(key2);
|
|
35593
|
-
|
|
35752
|
+
resolve53(r);
|
|
35594
35753
|
});
|
|
35595
35754
|
});
|
|
35596
35755
|
}
|
|
@@ -35601,11 +35760,11 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35601
35760
|
* terminal dialog stays answerable). */
|
|
35602
35761
|
awaitPermissionVerdict(p, timeoutMs) {
|
|
35603
35762
|
const k = _WebchatGateway.promptKey(p.key, p.requestId);
|
|
35604
|
-
return new Promise((
|
|
35763
|
+
return new Promise((resolve53) => {
|
|
35605
35764
|
this.pendingPrompts.get(k)?.resolve({ timeout: true });
|
|
35606
35765
|
const timer = setTimeout(() => {
|
|
35607
35766
|
this.pendingPrompts.delete(k);
|
|
35608
|
-
|
|
35767
|
+
resolve53({ timeout: true });
|
|
35609
35768
|
}, timeoutMs);
|
|
35610
35769
|
if (timer.unref) timer.unref();
|
|
35611
35770
|
this.pendingPrompts.set(k, {
|
|
@@ -35615,7 +35774,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35615
35774
|
clearTimeout(timer);
|
|
35616
35775
|
this.pendingPrompts.delete(k);
|
|
35617
35776
|
this.deps.onPointerChange?.(p.key);
|
|
35618
|
-
|
|
35777
|
+
resolve53(r);
|
|
35619
35778
|
}
|
|
35620
35779
|
});
|
|
35621
35780
|
console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
|
|
@@ -35626,9 +35785,9 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35626
35785
|
* false (and is a no-op) when no prompt with this key+id is open — a late or
|
|
35627
35786
|
* duplicate click. */
|
|
35628
35787
|
resolvePermissionVerdict(key2, requestId, behavior) {
|
|
35629
|
-
const
|
|
35630
|
-
if (!
|
|
35631
|
-
|
|
35788
|
+
const entry2 = this.pendingPrompts.get(_WebchatGateway.promptKey(key2, requestId));
|
|
35789
|
+
if (!entry2) return false;
|
|
35790
|
+
entry2.resolve({ behavior });
|
|
35632
35791
|
console.error(`[webchat:perm] op=verdict key=${keyDisplay(key2)} id=${requestId} behavior=${behavior}`);
|
|
35633
35792
|
return true;
|
|
35634
35793
|
}
|
|
@@ -35771,6 +35930,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35771
35930
|
}
|
|
35772
35931
|
},
|
|
35773
35932
|
onTurnEnd: (key2, role, turn) => {
|
|
35933
|
+
this.keySession.set(key2, turn.sessionId);
|
|
35774
35934
|
if (role === "admin") {
|
|
35775
35935
|
this.lastDeliveredAt.delete(key2);
|
|
35776
35936
|
return "none";
|
|
@@ -35801,6 +35961,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35801
35961
|
});
|
|
35802
35962
|
for (const e of events) {
|
|
35803
35963
|
console.error(`[webchat:inbound] op=${e.kind} key=${e.key} messageId=${e.messageId} ageMs=${e.ageMs}`);
|
|
35964
|
+
this.deliveryFailures.set(e.key, { messageId: e.messageId, at: now });
|
|
35804
35965
|
}
|
|
35805
35966
|
return events;
|
|
35806
35967
|
}
|
|
@@ -35830,6 +35991,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35830
35991
|
run: () => {
|
|
35831
35992
|
const now = Date.now();
|
|
35832
35993
|
const events = this.reconcile(now);
|
|
35994
|
+
this.auditKeyOrphans(now);
|
|
35833
35995
|
void this.recoverStalled(now);
|
|
35834
35996
|
return events.length > 0 ? `${events.length} terminal events` : void 0;
|
|
35835
35997
|
}
|
|
@@ -35846,6 +36008,60 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35846
36008
|
clearDeliveryFailure(key2) {
|
|
35847
36009
|
this.deliveryFailures.delete(key2);
|
|
35848
36010
|
}
|
|
36011
|
+
/** Task 2465 — record which session a conversation key's child is running.
|
|
36012
|
+
* Public so the audit below can be driven directly in tests without standing
|
|
36013
|
+
* up the channel-server HTTP surface; production writes come from runSpawn and
|
|
36014
|
+
* the turn-end handler. */
|
|
36015
|
+
noteKeySession(key2, sessionId) {
|
|
36016
|
+
this.keySession.set(key2, sessionId);
|
|
36017
|
+
}
|
|
36018
|
+
/** Task 2465 — STANDING RECONCILIATION: a queued message on a key no live child
|
|
36019
|
+
* subscribes to, while the session that key names IS live under a different
|
|
36020
|
+
* key. Expected against actual, on the sweep tick.
|
|
36021
|
+
*
|
|
36022
|
+
* There is no action to hang this on and re-running the send reproduces the
|
|
36023
|
+
* same silence, which is why it is an audit rather than another action log.
|
|
36024
|
+
* Before this, the state emitted nothing until the 30s recovery tick, and the
|
|
36025
|
+
* queued key and the live key never appeared on one line anywhere in
|
|
36026
|
+
* server.log — the reason the 2026-08-06 incident read as a wedge throughout.
|
|
36027
|
+
* `liveKey` is the whole diagnosis, and it fires on the FIRST occurrence.
|
|
36028
|
+
*
|
|
36029
|
+
* Covers: a queued key with no subscriber whose named session is held by a
|
|
36030
|
+
* different attached key. Does NOT cover a live child this process neither
|
|
36031
|
+
* spawned nor saw finish a turn — it is absent from keySession, and this is
|
|
36032
|
+
* silent for it. Silent when the keys agree, which is every healthy send.
|
|
36033
|
+
*
|
|
36034
|
+
* Public for the same reason noteKeySession is.
|
|
36035
|
+
*
|
|
36036
|
+
* Task 2466 — the live-key scan moved to sessionSubscriberKey, which the
|
|
36037
|
+
* recovery pass also reads. The audit's own behaviour is unchanged. */
|
|
36038
|
+
auditKeyOrphans(now) {
|
|
36039
|
+
for (const c of this.hub.staleQueued(now, 0)) {
|
|
36040
|
+
const sessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key);
|
|
36041
|
+
if (!sessionId) continue;
|
|
36042
|
+
const liveKey = this.sessionSubscriberKey(sessionId);
|
|
36043
|
+
if (liveKey === null || liveKey === c.key) continue;
|
|
36044
|
+
console.error(
|
|
36045
|
+
`[webchat:inbound] op=key-orphan key=${c.key} messageId=${c.messageId} ageMs=${c.ageMs} liveKey=${liveKey} sessionId=${sessionId.slice(0, 8)}`
|
|
36046
|
+
);
|
|
36047
|
+
}
|
|
36048
|
+
}
|
|
36049
|
+
/** Task 2466 — the conversation key whose channel server is attached right now
|
|
36050
|
+
* and which this gateway records as serving `sessionId`, or null when no such
|
|
36051
|
+
* key exists. The one place "is this session reachable over its channel" is
|
|
36052
|
+
* decided; the orphan audit above and the recovery pass below both read it.
|
|
36053
|
+
*
|
|
36054
|
+
* Both readers are recovery-shaped, so the caller always holds a key with no
|
|
36055
|
+
* subscriber and this can never return that same key. Blind, by construction,
|
|
36056
|
+
* to a live child this process neither spawned nor saw finish a turn: it is
|
|
36057
|
+
* absent from keySession, and the answer is null. */
|
|
36058
|
+
sessionSubscriberKey(sessionId) {
|
|
36059
|
+
for (const [liveKey, liveSession] of this.keySession) {
|
|
36060
|
+
if (liveSession !== sessionId) continue;
|
|
36061
|
+
if (this.hub.hasSubscriber(liveKey)) return liveKey;
|
|
36062
|
+
}
|
|
36063
|
+
return null;
|
|
36064
|
+
}
|
|
35849
36065
|
/** Task-962 — recovery-aware pass over queued-no-subscriber messages.
|
|
35850
36066
|
* - age in [RECOVER_TRIGGER_MS, DELIVER_FINAL_MS): ask the manager to recover
|
|
35851
36067
|
* the live session ONCE. busy → leave queued for the channel server's
|
|
@@ -35874,15 +36090,19 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35874
36090
|
const d = this.recoverable.get(c.key);
|
|
35875
36091
|
if (!d || !this.deps.recoverLiveSession) {
|
|
35876
36092
|
this.hub.dropQueued(c.key, c.messageId);
|
|
36093
|
+
this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
|
|
35877
36094
|
console.error(
|
|
35878
36095
|
`[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=no-descriptor`
|
|
35879
36096
|
);
|
|
35880
36097
|
continue;
|
|
35881
36098
|
}
|
|
35882
36099
|
this.recoveryAttempted.add(attemptKey);
|
|
36100
|
+
const recoverSessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key) ?? d.sessionId;
|
|
36101
|
+
const liveKey = recoverSessionId === void 0 ? null : this.sessionSubscriberKey(recoverSessionId);
|
|
36102
|
+
const subscriberAttached = liveKey !== null;
|
|
35883
36103
|
let r;
|
|
35884
36104
|
try {
|
|
35885
|
-
r = await this.deps.recoverLiveSession(d);
|
|
36105
|
+
r = await this.deps.recoverLiveSession({ ...d, subscriberAttached });
|
|
35886
36106
|
} catch (err) {
|
|
35887
36107
|
console.error(
|
|
35888
36108
|
`[webchat:inbound] op=recover-error key=${keyDisplay(c.key)} messageId=${c.messageId} err=${err instanceof Error ? err.message : String(err)}`
|
|
@@ -35892,6 +36112,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35892
36112
|
if (!r.live) {
|
|
35893
36113
|
this.hub.dropQueued(c.key, c.messageId);
|
|
35894
36114
|
this.recoveryAttempted.delete(attemptKey);
|
|
36115
|
+
this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
|
|
35895
36116
|
console.error(
|
|
35896
36117
|
`[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=not-live`
|
|
35897
36118
|
);
|
|
@@ -35899,6 +36120,16 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35899
36120
|
console.error(
|
|
35900
36121
|
`[webchat:inbound] op=recover key=${keyDisplay(c.key)} messageId=${c.messageId} classification=${r.classification ?? "unknown"} respawned=${r.recovered === true}`
|
|
35901
36122
|
);
|
|
36123
|
+
if (r.classification === "busy" && liveKey !== null && liveKey !== c.key) {
|
|
36124
|
+
const handed = this.hub.takeQueued(c.key, c.messageId);
|
|
36125
|
+
if (handed) {
|
|
36126
|
+
this.recoveryAttempted.delete(attemptKey);
|
|
36127
|
+
this.hub.deliver({ ...handed, key: liveKey }, now);
|
|
36128
|
+
console.error(
|
|
36129
|
+
`[webchat:inbound] op=recover-redeliver key=${c.key} liveKey=${liveKey} messageId=${c.messageId} ageMs=${c.ageMs}`
|
|
36130
|
+
);
|
|
36131
|
+
}
|
|
36132
|
+
}
|
|
35902
36133
|
}
|
|
35903
36134
|
}
|
|
35904
36135
|
for (const key2 of this.recoverable.keys()) {
|
|
@@ -35963,6 +36194,10 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35963
36194
|
accountId: input.accountId,
|
|
35964
36195
|
key: input.key,
|
|
35965
36196
|
role: input.role,
|
|
36197
|
+
// Task 2466 — a template value. Reachability is a fact about NOW, not
|
|
36198
|
+
// about when the message arrived, so recoverStalled re-stamps it on the
|
|
36199
|
+
// copy it hands to the manager.
|
|
36200
|
+
subscriberAttached: false,
|
|
35966
36201
|
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
35967
36202
|
...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {}
|
|
35968
36203
|
});
|
|
@@ -36011,6 +36246,9 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
36011
36246
|
lastActivityAt: Date.now()
|
|
36012
36247
|
});
|
|
36013
36248
|
}
|
|
36249
|
+
if (input.role === "admin" && input.sessionId !== void 0) {
|
|
36250
|
+
this.keySession.set(input.key, input.sessionId);
|
|
36251
|
+
}
|
|
36014
36252
|
} catch (err) {
|
|
36015
36253
|
const reason = (err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ");
|
|
36016
36254
|
console.error(
|
|
@@ -36023,7 +36261,60 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
36023
36261
|
}
|
|
36024
36262
|
};
|
|
36025
36263
|
|
|
36026
|
-
// app/lib/channel-
|
|
36264
|
+
// app/lib/channel-delivery/answer-accumulator.ts
|
|
36265
|
+
var AnswerAccumulator = class {
|
|
36266
|
+
openId = "";
|
|
36267
|
+
buf = "";
|
|
36268
|
+
open = false;
|
|
36269
|
+
emitted = false;
|
|
36270
|
+
/** Feed one JSONL line. Returns every answer completed by this line, in order:
|
|
36271
|
+
* at most the previous message's, then this one's. */
|
|
36272
|
+
push(line) {
|
|
36273
|
+
if (!line) return [];
|
|
36274
|
+
let row;
|
|
36275
|
+
try {
|
|
36276
|
+
row = JSON.parse(line);
|
|
36277
|
+
} catch {
|
|
36278
|
+
return [];
|
|
36279
|
+
}
|
|
36280
|
+
if (row.type !== "assistant") return this.complete();
|
|
36281
|
+
const msg = row.message;
|
|
36282
|
+
const id = typeof msg?.id === "string" ? msg.id : "";
|
|
36283
|
+
const out = [];
|
|
36284
|
+
if (!this.open || id !== this.openId) {
|
|
36285
|
+
out.push(...this.complete());
|
|
36286
|
+
this.openId = id;
|
|
36287
|
+
this.buf = "";
|
|
36288
|
+
this.open = true;
|
|
36289
|
+
this.emitted = false;
|
|
36290
|
+
}
|
|
36291
|
+
if (Array.isArray(msg?.content)) {
|
|
36292
|
+
for (const block of msg.content) {
|
|
36293
|
+
if (block?.type === "text" && typeof block.text === "string") this.buf += block.text;
|
|
36294
|
+
}
|
|
36295
|
+
}
|
|
36296
|
+
if (!this.emitted && msg?.stop_reason === "end_turn" && this.buf.trim().length > 0) {
|
|
36297
|
+
this.emitted = true;
|
|
36298
|
+
out.push({ messageId: this.openId, text: this.buf });
|
|
36299
|
+
}
|
|
36300
|
+
return out;
|
|
36301
|
+
}
|
|
36302
|
+
/** Complete any open message. Called when the stream ends. */
|
|
36303
|
+
flush() {
|
|
36304
|
+
return this.complete();
|
|
36305
|
+
}
|
|
36306
|
+
complete() {
|
|
36307
|
+
if (!this.open) return [];
|
|
36308
|
+
const answer = !this.emitted && this.buf.trim().length > 0 ? { messageId: this.openId, text: this.buf } : null;
|
|
36309
|
+
this.open = false;
|
|
36310
|
+
this.openId = "";
|
|
36311
|
+
this.buf = "";
|
|
36312
|
+
this.emitted = false;
|
|
36313
|
+
return answer ? [answer] : [];
|
|
36314
|
+
}
|
|
36315
|
+
};
|
|
36316
|
+
|
|
36317
|
+
// app/lib/channel-delivery/emitter.ts
|
|
36027
36318
|
function followerPendingMaxMs() {
|
|
36028
36319
|
return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
|
|
36029
36320
|
}
|
|
@@ -36037,19 +36328,31 @@ function toolResultText(content) {
|
|
|
36037
36328
|
(b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : ""
|
|
36038
36329
|
).join("");
|
|
36039
36330
|
}
|
|
36040
|
-
function
|
|
36331
|
+
function startEmitter(opts) {
|
|
36041
36332
|
const abort2 = new AbortController();
|
|
36042
|
-
const { entry, tag } = opts;
|
|
36333
|
+
const { entry: entry2, tag } = opts;
|
|
36334
|
+
const answers = new AnswerAccumulator();
|
|
36335
|
+
const emitAnswer = async (answer) => {
|
|
36336
|
+
if (answer.text.trim()) await fanOut2(entry2.subscribers, answer.text, opts.onError, tag);
|
|
36337
|
+
if (!opts.onAnswer) return;
|
|
36338
|
+
try {
|
|
36339
|
+
await opts.onAnswer(answer);
|
|
36340
|
+
} catch (err) {
|
|
36341
|
+
console.error(
|
|
36342
|
+
`${tag} answer-error sessionId=${entry2.sessionId.slice(0, 8)} messageId=${answer.messageId} message=${err instanceof Error ? err.message : String(err)}`
|
|
36343
|
+
);
|
|
36344
|
+
}
|
|
36345
|
+
};
|
|
36043
36346
|
void (async () => {
|
|
36044
36347
|
try {
|
|
36045
|
-
const sid =
|
|
36348
|
+
const sid = entry2.sessionId.slice(0, 8);
|
|
36046
36349
|
const deadline = Date.now() + followerPendingMaxMs();
|
|
36047
36350
|
const retryMs = followerRetryMs();
|
|
36048
36351
|
let res;
|
|
36049
36352
|
let attempt = 0;
|
|
36050
36353
|
for (; ; ) {
|
|
36051
36354
|
res = await fetch(
|
|
36052
|
-
managerLogFollowUrl(
|
|
36355
|
+
managerLogFollowUrl(entry2.sessionId, { boundary: opts.suppressResumeReplay === true }),
|
|
36053
36356
|
{ signal: abort2.signal }
|
|
36054
36357
|
);
|
|
36055
36358
|
console.error(`${tag} follower-connect sessionId=${sid} status=${res.status}`);
|
|
@@ -36083,7 +36386,7 @@ function startFollower(opts) {
|
|
|
36083
36386
|
const awaitingVerdict = /* @__PURE__ */ new Set();
|
|
36084
36387
|
let firedFileTools = [];
|
|
36085
36388
|
let suppressing = opts.suppressResumeReplay === true;
|
|
36086
|
-
let
|
|
36389
|
+
let discardedAnswers = 0;
|
|
36087
36390
|
let discardedFileTools = 0;
|
|
36088
36391
|
while (!abort2.signal.aborted) {
|
|
36089
36392
|
const { value, done } = await reader.read();
|
|
@@ -36100,7 +36403,7 @@ function startFollower(opts) {
|
|
|
36100
36403
|
event = JSON.parse(line);
|
|
36101
36404
|
} catch (err) {
|
|
36102
36405
|
console.error(
|
|
36103
|
-
`${tag} jsonl-parse-skip sessionId=${
|
|
36406
|
+
`${tag} jsonl-parse-skip sessionId=${entry2.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
|
|
36104
36407
|
);
|
|
36105
36408
|
continue;
|
|
36106
36409
|
}
|
|
@@ -36108,11 +36411,18 @@ function startFollower(opts) {
|
|
|
36108
36411
|
if (suppressing) {
|
|
36109
36412
|
suppressing = false;
|
|
36110
36413
|
console.error(
|
|
36111
|
-
`${tag}
|
|
36414
|
+
`${tag} emitter-resume-boundary sessionId=${sid} discarded-head-answers=${discardedAnswers} discarded-head-filetools=${discardedFileTools}`
|
|
36112
36415
|
);
|
|
36113
36416
|
}
|
|
36114
36417
|
continue;
|
|
36115
36418
|
}
|
|
36419
|
+
for (const answer of answers.push(line)) {
|
|
36420
|
+
if (suppressing) {
|
|
36421
|
+
discardedAnswers += 1;
|
|
36422
|
+
continue;
|
|
36423
|
+
}
|
|
36424
|
+
await emitAnswer(answer);
|
|
36425
|
+
}
|
|
36116
36426
|
if (event.type === "user") {
|
|
36117
36427
|
if (captureVerdicts && awaitingVerdict.size > 0 && !suppressing) {
|
|
36118
36428
|
const umsg = event.message;
|
|
@@ -36136,7 +36446,6 @@ function startFollower(opts) {
|
|
|
36136
36446
|
}
|
|
36137
36447
|
}
|
|
36138
36448
|
}
|
|
36139
|
-
entry.pendingTurnText = "";
|
|
36140
36449
|
firedFileTools = [];
|
|
36141
36450
|
continue;
|
|
36142
36451
|
}
|
|
@@ -36147,7 +36456,6 @@ function startFollower(opts) {
|
|
|
36147
36456
|
if (Array.isArray(msg.content)) {
|
|
36148
36457
|
for (const block of msg.content) {
|
|
36149
36458
|
if (block?.type === "text" && typeof block.text === "string") {
|
|
36150
|
-
entry.pendingTurnText += block.text;
|
|
36151
36459
|
sawContent = true;
|
|
36152
36460
|
} else if (block?.type === "tool_use") {
|
|
36153
36461
|
sawContent = true;
|
|
@@ -36172,16 +36480,9 @@ function startFollower(opts) {
|
|
|
36172
36480
|
}
|
|
36173
36481
|
if (msg.stop_reason === "end_turn") {
|
|
36174
36482
|
if (suppressing) {
|
|
36175
|
-
entry.pendingTurnText = "";
|
|
36176
36483
|
firedFileTools = [];
|
|
36177
|
-
discardedTurns += 1;
|
|
36178
36484
|
continue;
|
|
36179
36485
|
}
|
|
36180
|
-
const flush = entry.pendingTurnText;
|
|
36181
|
-
entry.pendingTurnText = "";
|
|
36182
|
-
if (flush.trim()) {
|
|
36183
|
-
await fanOut2(entry.subscribers, flush, opts.onError, tag);
|
|
36184
|
-
}
|
|
36185
36486
|
if (fileDelivery && (firedFileTools.length > 0 || captureVerdicts)) {
|
|
36186
36487
|
const fired = firedFileTools;
|
|
36187
36488
|
firedFileTools = [];
|
|
@@ -36200,9 +36501,10 @@ function startFollower(opts) {
|
|
|
36200
36501
|
}
|
|
36201
36502
|
}
|
|
36202
36503
|
}
|
|
36504
|
+
for (const answer of answers.flush()) await emitAnswer(answer);
|
|
36203
36505
|
} catch (err) {
|
|
36204
36506
|
if (!abort2.signal.aborted) {
|
|
36205
|
-
opts.onError?.(`
|
|
36507
|
+
opts.onError?.(`emitter-error: ${err instanceof Error ? err.message : String(err)}`);
|
|
36206
36508
|
}
|
|
36207
36509
|
} finally {
|
|
36208
36510
|
opts.onClose();
|
|
@@ -36223,14 +36525,42 @@ async function fanOut2(subscribers, text, onError, tag) {
|
|
|
36223
36525
|
);
|
|
36224
36526
|
}
|
|
36225
36527
|
|
|
36528
|
+
// app/lib/channel-delivery/parity-census.ts
|
|
36529
|
+
var sessions2 = /* @__PURE__ */ new Map();
|
|
36530
|
+
function entry(sessionId) {
|
|
36531
|
+
let e = sessions2.get(sessionId);
|
|
36532
|
+
if (!e) {
|
|
36533
|
+
e = { rendered: /* @__PURE__ */ new Set(), sent: /* @__PURE__ */ new Set() };
|
|
36534
|
+
sessions2.set(sessionId, e);
|
|
36535
|
+
}
|
|
36536
|
+
return e;
|
|
36537
|
+
}
|
|
36538
|
+
function noteAnswer(sessionId, messageId) {
|
|
36539
|
+
entry(sessionId).rendered.add(messageId);
|
|
36540
|
+
}
|
|
36541
|
+
function noteSent(sessionId, messageId, channels) {
|
|
36542
|
+
if (channels.length === 0) return;
|
|
36543
|
+
entry(sessionId).sent.add(messageId);
|
|
36544
|
+
}
|
|
36545
|
+
function parityCensusLines() {
|
|
36546
|
+
const out = [];
|
|
36547
|
+
for (const [sessionId, e] of sessions2) {
|
|
36548
|
+
const missing = [...e.rendered].filter((id) => !e.sent.has(id));
|
|
36549
|
+
out.push(
|
|
36550
|
+
`[channel-parity] op=parity-census sessionId=${sessionId} rendered=${e.rendered.size} sent=${e.sent.size} missing=${missing.length} missingIds=${missing.join(",") || "none"}`
|
|
36551
|
+
);
|
|
36552
|
+
}
|
|
36553
|
+
return out;
|
|
36554
|
+
}
|
|
36555
|
+
|
|
36226
36556
|
// app/lib/webchat/file-delivery.ts
|
|
36227
36557
|
import { realpathSync as realpathSync9 } from "fs";
|
|
36228
|
-
import { resolve as
|
|
36558
|
+
import { resolve as resolve48 } from "path";
|
|
36229
36559
|
|
|
36230
36560
|
// app/lib/channel-pty-bridge/file-delivery.ts
|
|
36231
36561
|
var SEND_USER_FILE = "SendUserFile";
|
|
36232
36562
|
function makeFileDelivery(opts) {
|
|
36233
|
-
const { entry, tag, channel, sendFile, deferUntilVerdict = false } = opts;
|
|
36563
|
+
const { entry: entry2, tag, channel, sendFile, deferUntilVerdict = false } = opts;
|
|
36234
36564
|
let failedFiles = [];
|
|
36235
36565
|
let attempts = 0;
|
|
36236
36566
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -36257,7 +36587,7 @@ function makeFileDelivery(opts) {
|
|
|
36257
36587
|
function unreconciledCall(reason, files, detail) {
|
|
36258
36588
|
const d = detail ? ` detail="${detail.replace(/\s+/g, " ").slice(0, 120)}"` : "";
|
|
36259
36589
|
console.error(
|
|
36260
|
-
`${tag} file-delivery-unreconciled sender=${
|
|
36590
|
+
`${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${entry2.sessionId.slice(0, 8)} tool=${SEND_USER_FILE} reason=${reason} files=${files}${d}`
|
|
36261
36591
|
);
|
|
36262
36592
|
}
|
|
36263
36593
|
const handler = {
|
|
@@ -36278,10 +36608,10 @@ function makeFileDelivery(opts) {
|
|
|
36278
36608
|
const tried = attempts;
|
|
36279
36609
|
failedFiles = [];
|
|
36280
36610
|
attempts = 0;
|
|
36281
|
-
const sid =
|
|
36611
|
+
const sid = entry2.sessionId.slice(0, 8);
|
|
36282
36612
|
for (const file of failed2) {
|
|
36283
36613
|
console.error(
|
|
36284
|
-
`${tag} file-delivery-unreconciled sender=${
|
|
36614
|
+
`${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
36285
36615
|
);
|
|
36286
36616
|
}
|
|
36287
36617
|
if (deferUntilVerdict) {
|
|
@@ -36291,7 +36621,7 @@ function makeFileDelivery(opts) {
|
|
|
36291
36621
|
}
|
|
36292
36622
|
if (firedTools.includes(SEND_USER_FILE) && tried === 0) {
|
|
36293
36623
|
console.error(
|
|
36294
|
-
`${tag} file-delivery-unreconciled sender=${
|
|
36624
|
+
`${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
|
|
36295
36625
|
);
|
|
36296
36626
|
}
|
|
36297
36627
|
}
|
|
@@ -36320,43 +36650,43 @@ var TAG58 = "[webchat-adaptor]";
|
|
|
36320
36650
|
function platformRoot2() {
|
|
36321
36651
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
36322
36652
|
}
|
|
36323
|
-
function makeWebchatSendFile(
|
|
36653
|
+
function makeWebchatSendFile(entry2) {
|
|
36324
36654
|
return async (filePath) => {
|
|
36325
|
-
if (!
|
|
36326
|
-
console.error(`${TAG58} file-delivery reject reason=no-account sender=${
|
|
36655
|
+
if (!entry2.accountId) {
|
|
36656
|
+
console.error(`${TAG58} file-delivery reject reason=no-account sender=${entry2.senderId}`);
|
|
36327
36657
|
return { ok: false, error: "no-account" };
|
|
36328
36658
|
}
|
|
36329
|
-
const accountDir =
|
|
36659
|
+
const accountDir = resolve48(platformRoot2(), "..", "data/accounts", entry2.accountId);
|
|
36330
36660
|
try {
|
|
36331
36661
|
const resolved = realpathSync9(filePath);
|
|
36332
36662
|
const accountResolved = realpathSync9(accountDir);
|
|
36333
36663
|
if (!resolved.startsWith(accountResolved + "/")) {
|
|
36334
|
-
console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${
|
|
36664
|
+
console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${entry2.senderId}`);
|
|
36335
36665
|
return { ok: false, error: "outside-account" };
|
|
36336
36666
|
}
|
|
36337
36667
|
return { ok: true };
|
|
36338
36668
|
} catch (err) {
|
|
36339
36669
|
const code = err.code;
|
|
36340
36670
|
console.error(
|
|
36341
|
-
`${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${
|
|
36671
|
+
`${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry2.senderId}`
|
|
36342
36672
|
);
|
|
36343
36673
|
return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
|
|
36344
36674
|
}
|
|
36345
36675
|
};
|
|
36346
36676
|
}
|
|
36347
|
-
function makeWebchatFileDelivery(
|
|
36677
|
+
function makeWebchatFileDelivery(entry2) {
|
|
36348
36678
|
return makeFileDelivery({
|
|
36349
|
-
entry,
|
|
36679
|
+
entry: entry2,
|
|
36350
36680
|
tag: TAG58,
|
|
36351
36681
|
channel: "webchat",
|
|
36352
|
-
sendFile: makeWebchatSendFile(
|
|
36682
|
+
sendFile: makeWebchatSendFile(entry2),
|
|
36353
36683
|
deferUntilVerdict: true
|
|
36354
36684
|
});
|
|
36355
36685
|
}
|
|
36356
36686
|
|
|
36357
36687
|
// app/lib/webchat/gateway/native-file-follower.ts
|
|
36358
36688
|
function startWebchatNativeFileFollower(input) {
|
|
36359
|
-
const
|
|
36689
|
+
const entry2 = {
|
|
36360
36690
|
sessionId: input.sessionId,
|
|
36361
36691
|
role: "admin",
|
|
36362
36692
|
channel: "webchat",
|
|
@@ -36366,7 +36696,6 @@ function startWebchatNativeFileFollower(input) {
|
|
|
36366
36696
|
lastInboundAt: Date.now(),
|
|
36367
36697
|
writeChain: Promise.resolve(),
|
|
36368
36698
|
subscribers: /* @__PURE__ */ new Set(),
|
|
36369
|
-
pendingTurnText: "",
|
|
36370
36699
|
followerAbort: null,
|
|
36371
36700
|
followerRunning: false,
|
|
36372
36701
|
sliceToken: "",
|
|
@@ -36375,13 +36704,24 @@ function startWebchatNativeFileFollower(input) {
|
|
|
36375
36704
|
// Webchat renders from the transcript; there is no separate reply target.
|
|
36376
36705
|
replyTarget: null
|
|
36377
36706
|
};
|
|
36378
|
-
return
|
|
36379
|
-
entry,
|
|
36707
|
+
return startEmitter({
|
|
36708
|
+
entry: entry2,
|
|
36380
36709
|
tag: "[webchat-adaptor]",
|
|
36381
|
-
fileDelivery: makeWebchatFileDelivery(
|
|
36710
|
+
fileDelivery: makeWebchatFileDelivery(entry2),
|
|
36382
36711
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
36383
36712
|
// suppress replay so historical calls are not re-reconciled on attach.
|
|
36384
36713
|
suppressResumeReplay: true,
|
|
36714
|
+
// Task 2521 — the answer reaches every bound door through the one fan-out.
|
|
36715
|
+
// Ungated by design: whatever woke the turn, the reader and the chats show
|
|
36716
|
+
// the same thing.
|
|
36717
|
+
onAnswer: async (answer) => {
|
|
36718
|
+
const targets = input.fanTargets?.() ?? [];
|
|
36719
|
+
if (targets.length === 0) return;
|
|
36720
|
+
noteAnswer(input.sessionId, answer.messageId);
|
|
36721
|
+
const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
|
|
36722
|
+
noteSent(input.sessionId, answer.messageId, reached);
|
|
36723
|
+
console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
|
|
36724
|
+
},
|
|
36385
36725
|
onClose: input.onClose ?? (() => {
|
|
36386
36726
|
})
|
|
36387
36727
|
});
|
|
@@ -36417,29 +36757,29 @@ var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
|
36417
36757
|
function platformRoot3() {
|
|
36418
36758
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
36419
36759
|
}
|
|
36420
|
-
function makeWhatsAppSendFile(
|
|
36760
|
+
function makeWhatsAppSendFile(entry2, maxyAccountId) {
|
|
36421
36761
|
return async (filePath, caption) => {
|
|
36422
36762
|
const result = await sendWhatsAppDocument({
|
|
36423
|
-
to:
|
|
36763
|
+
to: entry2.senderId,
|
|
36424
36764
|
filePath,
|
|
36425
36765
|
caption,
|
|
36426
|
-
accountId:
|
|
36766
|
+
accountId: entry2.accountId,
|
|
36427
36767
|
maxyAccountId,
|
|
36428
36768
|
platformRoot: platformRoot3()
|
|
36429
36769
|
});
|
|
36430
36770
|
if (result.ok) return { ok: true };
|
|
36431
36771
|
console.error(
|
|
36432
|
-
`${TAG59} file-delivery reject reason=send-failed sender=${
|
|
36772
|
+
`${TAG59} file-delivery reject reason=send-failed sender=${entry2.senderId} status=${result.status} message=${result.error}`
|
|
36433
36773
|
);
|
|
36434
36774
|
return { ok: false, error: result.error };
|
|
36435
36775
|
};
|
|
36436
36776
|
}
|
|
36437
|
-
function makeWhatsAppFileDelivery(
|
|
36777
|
+
function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
|
|
36438
36778
|
const shared = makeFileDelivery({
|
|
36439
|
-
entry,
|
|
36779
|
+
entry: entry2,
|
|
36440
36780
|
tag: TAG59,
|
|
36441
36781
|
channel: "whatsapp",
|
|
36442
|
-
sendFile: makeWhatsAppSendFile(
|
|
36782
|
+
sendFile: makeWhatsAppSendFile(entry2, maxyAccountId)
|
|
36443
36783
|
});
|
|
36444
36784
|
let turnStartedAt = null;
|
|
36445
36785
|
let routeCalls = [];
|
|
@@ -36464,7 +36804,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
|
36464
36804
|
const routes = routeCalls;
|
|
36465
36805
|
turnStartedAt = null;
|
|
36466
36806
|
routeCalls = [];
|
|
36467
|
-
const sid =
|
|
36807
|
+
const sid = entry2.sessionId.slice(0, 8);
|
|
36468
36808
|
shared.onTurnEnd(firedTools);
|
|
36469
36809
|
for (const call of routes) {
|
|
36470
36810
|
const routeAt = call.to !== void 0 && call.filePath !== void 0 ? routeDocumentOutboundAt(call.to, call.filePath) : void 0;
|
|
@@ -36472,7 +36812,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
|
36472
36812
|
if (!delivered) {
|
|
36473
36813
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
36474
36814
|
console.error(
|
|
36475
|
-
`${TAG59} file-delivery-unreconciled sender=${
|
|
36815
|
+
`${TAG59} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
36476
36816
|
);
|
|
36477
36817
|
}
|
|
36478
36818
|
}
|
|
@@ -36483,17 +36823,16 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
|
36483
36823
|
// app/lib/whatsapp/gateway/native-file-follower.ts
|
|
36484
36824
|
var COMPOSING_REFRESH_MS = 1e4;
|
|
36485
36825
|
function startNativeFileFollower(input) {
|
|
36486
|
-
const
|
|
36826
|
+
const entry2 = {
|
|
36487
36827
|
sessionId: input.sessionId,
|
|
36488
|
-
role:
|
|
36828
|
+
role: input.role,
|
|
36489
36829
|
channel: "whatsapp",
|
|
36490
36830
|
accountId: input.accountId,
|
|
36491
36831
|
senderId: input.senderId,
|
|
36492
|
-
agentSlug: "admin",
|
|
36832
|
+
agentSlug: input.role === "admin" ? "admin" : "default",
|
|
36493
36833
|
lastInboundAt: Date.now(),
|
|
36494
36834
|
writeChain: Promise.resolve(),
|
|
36495
36835
|
subscribers: /* @__PURE__ */ new Set(),
|
|
36496
|
-
pendingTurnText: "",
|
|
36497
36836
|
followerAbort: null,
|
|
36498
36837
|
followerRunning: false,
|
|
36499
36838
|
sliceToken: "",
|
|
@@ -36529,15 +36868,28 @@ function startNativeFileFollower(input) {
|
|
|
36529
36868
|
if (!sock) return;
|
|
36530
36869
|
void sendPaused(sock, input.senderId);
|
|
36531
36870
|
};
|
|
36532
|
-
return
|
|
36533
|
-
entry,
|
|
36871
|
+
return startEmitter({
|
|
36872
|
+
entry: entry2,
|
|
36534
36873
|
tag: "[whatsapp-adaptor]",
|
|
36535
|
-
|
|
36874
|
+
// Task 2521 — admin-only. A public spawn has no tools (Task 2078), so a
|
|
36875
|
+
// handler here would reconcile a call that can never fire.
|
|
36876
|
+
fileDelivery: input.role === "admin" ? makeWhatsAppFileDelivery(entry2, input.maxyAccountId) : null,
|
|
36536
36877
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
36537
36878
|
// suppress replay so historical files are not re-sent on attach.
|
|
36538
36879
|
suppressResumeReplay: true,
|
|
36539
36880
|
onActivity,
|
|
36540
36881
|
onTurnComplete,
|
|
36882
|
+
// Task 2521 — the answer reaches every bound door through the one fan-out.
|
|
36883
|
+
// Ungated by design: whatever woke the turn, the reader and the chats show
|
|
36884
|
+
// the same thing.
|
|
36885
|
+
onAnswer: async (answer) => {
|
|
36886
|
+
const targets = input.fanTargets?.() ?? [];
|
|
36887
|
+
if (targets.length === 0) return;
|
|
36888
|
+
noteAnswer(input.sessionId, answer.messageId);
|
|
36889
|
+
const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
|
|
36890
|
+
noteSent(input.sessionId, answer.messageId, reached);
|
|
36891
|
+
console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
|
|
36892
|
+
},
|
|
36541
36893
|
onClose: input.onClose ?? (() => {
|
|
36542
36894
|
})
|
|
36543
36895
|
});
|
|
@@ -37090,6 +37442,9 @@ function buildTelegramEnsureSession(deps) {
|
|
|
37090
37442
|
accountId: input.accountId,
|
|
37091
37443
|
chatId: input.chatId,
|
|
37092
37444
|
botToken: input.botToken,
|
|
37445
|
+
// Task 2521 — resolved per answer, not captured once, so a door that goes
|
|
37446
|
+
// live after this session started still receives.
|
|
37447
|
+
fanTargets: () => deps.fanTargets(req.channelKey),
|
|
37093
37448
|
// Identity-guarded eviction: only delete if this controller is still the
|
|
37094
37449
|
// current one, so a stale self-close never removes a live follower.
|
|
37095
37450
|
onClose: () => {
|
|
@@ -37104,7 +37459,7 @@ function buildTelegramEnsureSession(deps) {
|
|
|
37104
37459
|
init_attachments();
|
|
37105
37460
|
import { realpathSync as realpathSync10 } from "fs";
|
|
37106
37461
|
import { readFile as readFile14, stat as stat10 } from "fs/promises";
|
|
37107
|
-
import { resolve as
|
|
37462
|
+
import { resolve as resolve49, basename as basename16 } from "path";
|
|
37108
37463
|
var TAG61 = "[telegram:outbound]";
|
|
37109
37464
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
37110
37465
|
async function sendTelegramDocument(input) {
|
|
@@ -37115,7 +37470,7 @@ async function sendTelegramDocument(input) {
|
|
|
37115
37470
|
if (!maxyAccountId || !platformRoot5) {
|
|
37116
37471
|
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
37117
37472
|
}
|
|
37118
|
-
const accountDir =
|
|
37473
|
+
const accountDir = resolve49(platformRoot5, "..", "data/accounts", maxyAccountId);
|
|
37119
37474
|
let accountResolved;
|
|
37120
37475
|
try {
|
|
37121
37476
|
accountResolved = realpathSync10(accountDir);
|
|
@@ -37184,39 +37539,39 @@ var TAG62 = "[telegram:outbound]";
|
|
|
37184
37539
|
function platformRoot4() {
|
|
37185
37540
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
37186
37541
|
}
|
|
37187
|
-
function makeTelegramSendFile(
|
|
37542
|
+
function makeTelegramSendFile(entry2, botToken) {
|
|
37188
37543
|
return async (filePath, caption) => {
|
|
37189
37544
|
if (!botToken) {
|
|
37190
|
-
console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${
|
|
37545
|
+
console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${entry2.senderId} role=${entry2.role}`);
|
|
37191
37546
|
return { ok: false, error: "no-bot-token" };
|
|
37192
37547
|
}
|
|
37193
|
-
if (
|
|
37194
|
-
console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${
|
|
37548
|
+
if (entry2.replyTarget == null) {
|
|
37549
|
+
console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${entry2.senderId} role=${entry2.role}`);
|
|
37195
37550
|
return { ok: false, error: "no-reply-target" };
|
|
37196
37551
|
}
|
|
37197
37552
|
const result = await sendTelegramDocument({
|
|
37198
37553
|
botToken,
|
|
37199
|
-
chatId: Number(
|
|
37554
|
+
chatId: Number(entry2.replyTarget),
|
|
37200
37555
|
filePath,
|
|
37201
37556
|
caption,
|
|
37202
|
-
maxyAccountId:
|
|
37557
|
+
maxyAccountId: entry2.accountId,
|
|
37203
37558
|
platformRoot: platformRoot4()
|
|
37204
37559
|
});
|
|
37205
37560
|
return result.ok ? { ok: true } : { ok: false, error: result.error };
|
|
37206
37561
|
};
|
|
37207
37562
|
}
|
|
37208
|
-
function makeTelegramFileDelivery(
|
|
37563
|
+
function makeTelegramFileDelivery(entry2, botToken) {
|
|
37209
37564
|
return makeFileDelivery({
|
|
37210
|
-
entry,
|
|
37565
|
+
entry: entry2,
|
|
37211
37566
|
tag: TAG62,
|
|
37212
37567
|
channel: "telegram",
|
|
37213
|
-
sendFile: makeTelegramSendFile(
|
|
37568
|
+
sendFile: makeTelegramSendFile(entry2, botToken)
|
|
37214
37569
|
});
|
|
37215
37570
|
}
|
|
37216
37571
|
|
|
37217
37572
|
// app/lib/telegram/gateway/native-file-follower.ts
|
|
37218
37573
|
function startTelegramNativeFileFollower(input) {
|
|
37219
|
-
const
|
|
37574
|
+
const entry2 = {
|
|
37220
37575
|
sessionId: input.sessionId,
|
|
37221
37576
|
role: "admin",
|
|
37222
37577
|
channel: "telegram",
|
|
@@ -37226,7 +37581,6 @@ function startTelegramNativeFileFollower(input) {
|
|
|
37226
37581
|
lastInboundAt: Date.now(),
|
|
37227
37582
|
writeChain: Promise.resolve(),
|
|
37228
37583
|
subscribers: /* @__PURE__ */ new Set(),
|
|
37229
|
-
pendingTurnText: "",
|
|
37230
37584
|
followerAbort: null,
|
|
37231
37585
|
followerRunning: false,
|
|
37232
37586
|
sliceToken: "",
|
|
@@ -37235,13 +37589,24 @@ function startTelegramNativeFileFollower(input) {
|
|
|
37235
37589
|
// The chat id the document is addressed to (set per spawn from the inbound).
|
|
37236
37590
|
replyTarget: input.chatId
|
|
37237
37591
|
};
|
|
37238
|
-
return
|
|
37239
|
-
entry,
|
|
37592
|
+
return startEmitter({
|
|
37593
|
+
entry: entry2,
|
|
37240
37594
|
tag: "[telegram-adaptor]",
|
|
37241
|
-
fileDelivery: makeTelegramFileDelivery(
|
|
37595
|
+
fileDelivery: makeTelegramFileDelivery(entry2, input.botToken),
|
|
37242
37596
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
37243
37597
|
// suppress replay so historical files are not re-sent on attach.
|
|
37244
37598
|
suppressResumeReplay: true,
|
|
37599
|
+
// Task 2521 — the answer reaches every bound door through the one fan-out.
|
|
37600
|
+
// Ungated by design: whatever woke the turn, the reader and the chats show
|
|
37601
|
+
// the same thing.
|
|
37602
|
+
onAnswer: async (answer) => {
|
|
37603
|
+
const targets = input.fanTargets?.() ?? [];
|
|
37604
|
+
if (targets.length === 0) return;
|
|
37605
|
+
noteAnswer(input.sessionId, answer.messageId);
|
|
37606
|
+
const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
|
|
37607
|
+
noteSent(input.sessionId, answer.messageId, reached);
|
|
37608
|
+
console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
|
|
37609
|
+
},
|
|
37245
37610
|
onClose: input.onClose ?? (() => {
|
|
37246
37611
|
})
|
|
37247
37612
|
});
|
|
@@ -37269,22 +37634,22 @@ function adminFanTargets(p) {
|
|
|
37269
37634
|
noTarget(p.origin, p.accountId, "users-read-failed");
|
|
37270
37635
|
return [origin];
|
|
37271
37636
|
}
|
|
37272
|
-
const
|
|
37273
|
-
if (!
|
|
37637
|
+
const entry2 = (users ?? []).find((u) => u.userId === p.adminUserId);
|
|
37638
|
+
if (!entry2) {
|
|
37274
37639
|
noTarget(p.origin, p.accountId, "no-entry");
|
|
37275
37640
|
return [origin];
|
|
37276
37641
|
}
|
|
37277
|
-
if (!
|
|
37642
|
+
if (!entry2.phone && !entry2.telegram) {
|
|
37278
37643
|
noTarget(p.origin, p.accountId, "no-bindings");
|
|
37279
37644
|
return [origin];
|
|
37280
37645
|
}
|
|
37281
37646
|
const bound = [];
|
|
37282
|
-
if (
|
|
37283
|
-
const wa = p.deps.whatsappLiveTarget(
|
|
37647
|
+
if (entry2.phone) {
|
|
37648
|
+
const wa = p.deps.whatsappLiveTarget(entry2.phone);
|
|
37284
37649
|
bound.push(wa ? { channel: "whatsapp", live: true, send: wa } : { channel: "whatsapp", live: false });
|
|
37285
37650
|
}
|
|
37286
|
-
if (
|
|
37287
|
-
const tg = p.deps.telegramLiveTarget(
|
|
37651
|
+
if (entry2.telegram) {
|
|
37652
|
+
const tg = p.deps.telegramLiveTarget(entry2.telegram);
|
|
37288
37653
|
bound.push(tg ? { channel: "telegram", live: true, send: tg } : { channel: "telegram", live: false });
|
|
37289
37654
|
}
|
|
37290
37655
|
return [origin, ...bound.filter((t) => t.channel !== p.origin)];
|
|
@@ -37491,8 +37856,8 @@ init_resolve_admin_userid();
|
|
|
37491
37856
|
init_src2();
|
|
37492
37857
|
function findAccountManagerDrift(accountManagers, validAccountIds) {
|
|
37493
37858
|
const drift = [];
|
|
37494
|
-
for (const [phone,
|
|
37495
|
-
const managesAccount = typeof
|
|
37859
|
+
for (const [phone, entry2] of Object.entries(accountManagers)) {
|
|
37860
|
+
const managesAccount = typeof entry2 === "string" ? entry2 : entry2.managesAccount;
|
|
37496
37861
|
if (!validAccountIds.includes(managesAccount)) {
|
|
37497
37862
|
drift.push({ phone, managesAccount, reason: "not-in-registry" });
|
|
37498
37863
|
}
|
|
@@ -37607,36 +37972,36 @@ init_sidebar_sessions();
|
|
|
37607
37972
|
function buildTelegramAuditLines(input) {
|
|
37608
37973
|
const lines = [];
|
|
37609
37974
|
const entries2 = input.accounts.flatMap(
|
|
37610
|
-
(a) => listBotEntries(a.telegram).map((
|
|
37975
|
+
(a) => listBotEntries(a.telegram).map((entry2) => ({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 }))
|
|
37611
37976
|
);
|
|
37612
37977
|
if (entries2.length === 0) return lines;
|
|
37613
37978
|
const byId = /* @__PURE__ */ new Map();
|
|
37614
|
-
for (const { entry } of entries2) byId.set(
|
|
37979
|
+
for (const { entry: entry2 } of entries2) byId.set(entry2.id, (byId.get(entry2.id) ?? 0) + 1);
|
|
37615
37980
|
for (const [botId, count] of byId) {
|
|
37616
37981
|
if (count > 1) lines.push(`[telegram-audit] op=duplicate-bot-id botId=${botId} accounts=${count}`);
|
|
37617
37982
|
}
|
|
37618
|
-
for (const { accountId, accountDir, entry } of entries2) {
|
|
37619
|
-
if (
|
|
37983
|
+
for (const { accountId, accountDir, entry: entry2 } of entries2) {
|
|
37984
|
+
if (entry2.role === "public" && !input.agentActive(accountDir, entry2.agent)) {
|
|
37620
37985
|
lines.push(
|
|
37621
|
-
`[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${
|
|
37986
|
+
`[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry2.id} agent=${entry2.agent}`
|
|
37622
37987
|
);
|
|
37623
37988
|
}
|
|
37624
|
-
if (!input.secretFileExists(
|
|
37625
|
-
lines.push(`[telegram-audit] op=secret-orphan botId=${
|
|
37989
|
+
if (!input.secretFileExists(entry2.id)) {
|
|
37990
|
+
lines.push(`[telegram-audit] op=secret-orphan botId=${entry2.id} side=entry`);
|
|
37626
37991
|
}
|
|
37627
37992
|
}
|
|
37628
37993
|
const known = new Set(entries2.map((e) => e.entry.id));
|
|
37629
37994
|
for (const botId of input.secretFilesOnDisk) {
|
|
37630
37995
|
if (!known.has(botId)) lines.push(`[telegram-audit] op=secret-orphan botId=${botId} side=file`);
|
|
37631
37996
|
}
|
|
37632
|
-
for (const { accountId, entry } of entries2) {
|
|
37633
|
-
const last = input.lastActivityByBot(
|
|
37997
|
+
for (const { accountId, entry: entry2 } of entries2) {
|
|
37998
|
+
const last = input.lastActivityByBot(entry2.id);
|
|
37634
37999
|
const spawnAge = last === null ? "never" : String(input.now - last.spawnMs);
|
|
37635
38000
|
const turnAge = last === null || last.turnMs === null ? "never" : String(input.now - last.turnMs);
|
|
37636
|
-
const agent =
|
|
37637
|
-
const secretFile = input.secretFileExists(
|
|
38001
|
+
const agent = entry2.role === "admin" ? "admin" : entry2.agent;
|
|
38002
|
+
const secretFile = input.secretFileExists(entry2.id) ? "present" : "absent";
|
|
37638
38003
|
lines.push(
|
|
37639
|
-
`[telegram-audit] op=configured botId=${
|
|
38004
|
+
`[telegram-audit] op=configured botId=${entry2.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastSpawnAgeMs=${spawnAge} lastTurnAgeMs=${turnAge}`
|
|
37640
38005
|
);
|
|
37641
38006
|
}
|
|
37642
38007
|
return lines;
|
|
@@ -37693,7 +38058,7 @@ function* walkTelegramSidecars() {
|
|
|
37693
38058
|
}
|
|
37694
38059
|
|
|
37695
38060
|
// server/telegram-migrate.ts
|
|
37696
|
-
import { readFileSync as
|
|
38061
|
+
import { readFileSync as readFileSync52, writeFileSync as writeFileSync19, existsSync as existsSync52, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
|
|
37697
38062
|
import { dirname as dirname18, join as join62 } from "path";
|
|
37698
38063
|
function migrateTelegramTwoSlot(input) {
|
|
37699
38064
|
const out = [];
|
|
@@ -37701,7 +38066,7 @@ function migrateTelegramTwoSlot(input) {
|
|
|
37701
38066
|
const configPath3 = join62(account.accountDir, "account.json");
|
|
37702
38067
|
let config;
|
|
37703
38068
|
try {
|
|
37704
|
-
config = JSON.parse(
|
|
38069
|
+
config = JSON.parse(readFileSync52(configPath3, "utf-8"));
|
|
37705
38070
|
} catch {
|
|
37706
38071
|
console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
|
|
37707
38072
|
continue;
|
|
@@ -37754,7 +38119,7 @@ function migrateTelegramTwoSlot(input) {
|
|
|
37754
38119
|
return out;
|
|
37755
38120
|
}
|
|
37756
38121
|
function moveSecret(from, to) {
|
|
37757
|
-
if (!
|
|
38122
|
+
if (!existsSync52(from)) return;
|
|
37758
38123
|
mkdirSync11(dirname18(to), { recursive: true, mode: 448 });
|
|
37759
38124
|
renameSync11(from, to);
|
|
37760
38125
|
}
|
|
@@ -37803,14 +38168,14 @@ function backfillMisfiledRecords(params) {
|
|
|
37803
38168
|
init_message_store();
|
|
37804
38169
|
|
|
37805
38170
|
// server/lib/booking-site-accounts.ts
|
|
37806
|
-
import { existsSync as
|
|
37807
|
-
import { resolve as
|
|
38171
|
+
import { existsSync as existsSync53, readFileSync as readFileSync53 } from "fs";
|
|
38172
|
+
import { resolve as resolve50 } from "path";
|
|
37808
38173
|
function listBookingSiteAccounts(accounts) {
|
|
37809
38174
|
return accounts.filter((a) => {
|
|
37810
|
-
const availPath =
|
|
37811
|
-
if (!
|
|
38175
|
+
const availPath = resolve50(a.accountDir, "calendar-availability.json");
|
|
38176
|
+
if (!existsSync53(availPath)) return false;
|
|
37812
38177
|
try {
|
|
37813
|
-
const cfg = JSON.parse(
|
|
38178
|
+
const cfg = JSON.parse(readFileSync53(availPath, "utf-8"));
|
|
37814
38179
|
return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
37815
38180
|
} catch {
|
|
37816
38181
|
return false;
|
|
@@ -37832,17 +38197,17 @@ function broadcastAdminShutdown(reason) {
|
|
|
37832
38197
|
const done = encoder.encode(`data: [DONE]
|
|
37833
38198
|
|
|
37834
38199
|
`);
|
|
37835
|
-
for (const
|
|
38200
|
+
for (const entry2 of activeAdminSSEControllers) {
|
|
37836
38201
|
try {
|
|
37837
|
-
|
|
38202
|
+
entry2.controller.enqueue(frame);
|
|
37838
38203
|
} catch {
|
|
37839
38204
|
}
|
|
37840
38205
|
try {
|
|
37841
|
-
|
|
38206
|
+
entry2.controller.enqueue(done);
|
|
37842
38207
|
} catch {
|
|
37843
38208
|
}
|
|
37844
38209
|
try {
|
|
37845
|
-
|
|
38210
|
+
entry2.controller.close();
|
|
37846
38211
|
} catch {
|
|
37847
38212
|
}
|
|
37848
38213
|
}
|
|
@@ -37855,8 +38220,8 @@ init_paths();
|
|
|
37855
38220
|
|
|
37856
38221
|
// ../lib/entitlement/src/index.ts
|
|
37857
38222
|
import { createPublicKey, createHash as createHash7, verify as cryptoVerify } from "crypto";
|
|
37858
|
-
import { existsSync as
|
|
37859
|
-
import { resolve as
|
|
38223
|
+
import { existsSync as existsSync54, readFileSync as readFileSync54, statSync as statSync29 } from "fs";
|
|
38224
|
+
import { resolve as resolve51 } from "path";
|
|
37860
38225
|
|
|
37861
38226
|
// ../lib/entitlement/src/canonicalize.ts
|
|
37862
38227
|
function canonicalize(value) {
|
|
@@ -37891,7 +38256,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
|
|
|
37891
38256
|
var GRACE_DAYS = 7;
|
|
37892
38257
|
var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
37893
38258
|
function pubkeyPath(brand) {
|
|
37894
|
-
return
|
|
38259
|
+
return resolve51(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
|
|
37895
38260
|
}
|
|
37896
38261
|
var memo = null;
|
|
37897
38262
|
function memoKey(mtimeMs, account) {
|
|
@@ -37903,8 +38268,8 @@ function resolveEntitlement(brand, account) {
|
|
|
37903
38268
|
if (brand.commercialMode !== true) {
|
|
37904
38269
|
return logResolved(implicitTrust(account), null);
|
|
37905
38270
|
}
|
|
37906
|
-
const entitlementPath =
|
|
37907
|
-
if (!
|
|
38271
|
+
const entitlementPath = resolve51(brand.configDir, "entitlement.json");
|
|
38272
|
+
if (!existsSync54(entitlementPath)) {
|
|
37908
38273
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
37909
38274
|
}
|
|
37910
38275
|
const stat11 = statSync29(entitlementPath);
|
|
@@ -37919,7 +38284,7 @@ function resolveEntitlement(brand, account) {
|
|
|
37919
38284
|
function verifyAndResolve(brand, entitlementPath, account) {
|
|
37920
38285
|
let pubkeyPem;
|
|
37921
38286
|
try {
|
|
37922
|
-
pubkeyPem =
|
|
38287
|
+
pubkeyPem = readFileSync54(pubkeyPath(brand), "utf-8");
|
|
37923
38288
|
} catch (err) {
|
|
37924
38289
|
return logResolved(anonymousFallback("pubkey-missing"), {
|
|
37925
38290
|
reason: "pubkey-missing"
|
|
@@ -37933,7 +38298,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
|
|
|
37933
38298
|
}
|
|
37934
38299
|
let envelope;
|
|
37935
38300
|
try {
|
|
37936
|
-
envelope = JSON.parse(
|
|
38301
|
+
envelope = JSON.parse(readFileSync54(entitlementPath, "utf-8"));
|
|
37937
38302
|
} catch {
|
|
37938
38303
|
return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
|
|
37939
38304
|
}
|
|
@@ -38097,12 +38462,12 @@ installMediaDownloadGuard();
|
|
|
38097
38462
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
38098
38463
|
var BRAND_JSON_PATH = PLATFORM_ROOT6 ? join63(PLATFORM_ROOT6, "config", "brand.json") : "";
|
|
38099
38464
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
38100
|
-
if (BRAND_JSON_PATH && !
|
|
38465
|
+
if (BRAND_JSON_PATH && !existsSync55(BRAND_JSON_PATH)) {
|
|
38101
38466
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
38102
38467
|
}
|
|
38103
|
-
if (BRAND_JSON_PATH &&
|
|
38468
|
+
if (BRAND_JSON_PATH && existsSync55(BRAND_JSON_PATH)) {
|
|
38104
38469
|
try {
|
|
38105
|
-
const parsed = JSON.parse(
|
|
38470
|
+
const parsed = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
|
|
38106
38471
|
BRAND = { ...BRAND, ...parsed };
|
|
38107
38472
|
} catch (err) {
|
|
38108
38473
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -38137,8 +38502,8 @@ var brandLoginOpts = {
|
|
|
38137
38502
|
var ALIAS_DOMAINS_PATH = join63(homedir5(), BRAND.configDir, "alias-domains.json");
|
|
38138
38503
|
function loadAliasDomains() {
|
|
38139
38504
|
try {
|
|
38140
|
-
if (!
|
|
38141
|
-
const parsed = JSON.parse(
|
|
38505
|
+
if (!existsSync55(ALIAS_DOMAINS_PATH)) return null;
|
|
38506
|
+
const parsed = JSON.parse(readFileSync55(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
38142
38507
|
if (!Array.isArray(parsed)) {
|
|
38143
38508
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
38144
38509
|
return null;
|
|
@@ -38209,6 +38574,26 @@ async function recordPassiveDispatch(input) {
|
|
|
38209
38574
|
await session.close();
|
|
38210
38575
|
}
|
|
38211
38576
|
}
|
|
38577
|
+
registerLoop({
|
|
38578
|
+
name: "channel-parity",
|
|
38579
|
+
intervalMs: 6e4,
|
|
38580
|
+
run: () => {
|
|
38581
|
+
const lines = parityCensusLines();
|
|
38582
|
+
for (const line of lines) console.error(line);
|
|
38583
|
+
const missing = lines.filter((l) => !l.includes("missing=0")).length;
|
|
38584
|
+
return `${lines.length} session(s), ${missing} with a missing answer`;
|
|
38585
|
+
}
|
|
38586
|
+
});
|
|
38587
|
+
var whatsappFanTargets = (accountId, senderId) => {
|
|
38588
|
+
warnNonUuidAccount("fan-out", accountId);
|
|
38589
|
+
return adminFanTargets({
|
|
38590
|
+
origin: "whatsapp",
|
|
38591
|
+
originKey: senderId,
|
|
38592
|
+
accountId,
|
|
38593
|
+
adminUserId: resolveAdminUserId({ accountId, senderPhone: senderId }),
|
|
38594
|
+
deps: adminFanDeps("whatsapp", accountId)
|
|
38595
|
+
});
|
|
38596
|
+
};
|
|
38212
38597
|
var waGateway = new WaGateway({
|
|
38213
38598
|
fetchStandingRules: fetchAccountStandingRules,
|
|
38214
38599
|
// Task 2321 — the account's own `## Instruction` body, read from that
|
|
@@ -38231,15 +38616,11 @@ var waGateway = new WaGateway({
|
|
|
38231
38616
|
// Task 2078 — one answer reaches every live door for this admin person.
|
|
38232
38617
|
// Task 2459 — the origin door is resolved from `senderId`, so it is present
|
|
38233
38618
|
// even when the sender resolves to no admin at all.
|
|
38234
|
-
|
|
38235
|
-
|
|
38236
|
-
|
|
38237
|
-
accountId,
|
|
38238
|
-
adminUserId: resolveAdminUserId({ accountId, senderPhone: senderId }),
|
|
38239
|
-
deps: adminFanDeps("whatsapp", accountId)
|
|
38240
|
-
}),
|
|
38619
|
+
// Task 2521 — hoisted, because the emitter's per-answer fan-out needs the same
|
|
38620
|
+
// resolver and `fanTargetsFor` is a constructor dep, not a callable method.
|
|
38621
|
+
fanTargetsFor: whatsappFanTargets,
|
|
38241
38622
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
38242
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ??
|
|
38623
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve52(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
38243
38624
|
// Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
|
|
38244
38625
|
// path-validation scope) is the sender's effective SESSION account, resolved
|
|
38245
38626
|
// once at the inbound gate and threaded here via the gateway's per-sender doc
|
|
@@ -38254,7 +38635,7 @@ var waGateway = new WaGateway({
|
|
|
38254
38635
|
caption,
|
|
38255
38636
|
accountId,
|
|
38256
38637
|
maxyAccountId,
|
|
38257
|
-
platformRoot:
|
|
38638
|
+
platformRoot: resolve52(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."))
|
|
38258
38639
|
});
|
|
38259
38640
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
38260
38641
|
},
|
|
@@ -38389,7 +38770,6 @@ var waGateway = new WaGateway({
|
|
|
38389
38770
|
console.error(`[whatsapp-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
|
|
38390
38771
|
return;
|
|
38391
38772
|
}
|
|
38392
|
-
if (role !== "admin") return;
|
|
38393
38773
|
const prior = nativeFileFollowers.get(senderId);
|
|
38394
38774
|
if (prior) prior.abort();
|
|
38395
38775
|
let ac;
|
|
@@ -38401,6 +38781,16 @@ var waGateway = new WaGateway({
|
|
|
38401
38781
|
// session account (the sub-account for an account-manager), not the house
|
|
38402
38782
|
// socket account. accountId stays the getSocket key.
|
|
38403
38783
|
maxyAccountId: effectiveAccountId,
|
|
38784
|
+
// Task 2521 — file delivery is admin-only; the text emitter is not.
|
|
38785
|
+
role,
|
|
38786
|
+
// Task 2521 — resolved per answer, not captured once, so a door that goes
|
|
38787
|
+
// live after this session started still receives.
|
|
38788
|
+
// Task 2531 — `effectiveAccountId`, NOT `accountId`. The reply map is
|
|
38789
|
+
// keyed on the platform UUID (wa-gateway.ts:567) while `accountId` here is
|
|
38790
|
+
// the Baileys credential dirname (see the Task 1850 note above). Passing
|
|
38791
|
+
// the dirname missed every lookup and delivered session 8e5bc4e8's answer
|
|
38792
|
+
// to nothing. `accountId` stays correct on line 718: that is the socket.
|
|
38793
|
+
fanTargets: () => whatsappFanTargets(effectiveAccountId, senderId),
|
|
38404
38794
|
onClose: () => {
|
|
38405
38795
|
if (nativeFileFollowers.get(senderId) === ac) nativeFileFollowers.delete(senderId);
|
|
38406
38796
|
}
|
|
@@ -38547,12 +38937,15 @@ var webchatGateway = new WebchatGateway({
|
|
|
38547
38937
|
// live admin session (rc-spawn recover:true). Resolve the session id the same
|
|
38548
38938
|
// way ensureChannelSession does. Public sessions recover via the idle reaper,
|
|
38549
38939
|
// so a non-admin descriptor is reported not-live (no respawn).
|
|
38550
|
-
recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId }) => {
|
|
38940
|
+
recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId, subscriberAttached }) => {
|
|
38551
38941
|
if (role !== "admin") return { live: false };
|
|
38552
38942
|
const id = sessionId ?? adminSessionIdFor(accountId, key2, adminUserId);
|
|
38553
38943
|
const r = await managerRcRecover({
|
|
38554
38944
|
sessionId: id,
|
|
38555
38945
|
webchatChannel: { key: key2, gatewayUrl: webchatGatewayUrl(), serverPath: webchatServerPath() },
|
|
38946
|
+
// Task 2466 — the gateway resolved this against the live subscriber table
|
|
38947
|
+
// just before the call; the manager classifies from it.
|
|
38948
|
+
subscriberAttached,
|
|
38556
38949
|
...adminUserId !== void 0 ? { adminUserId } : {},
|
|
38557
38950
|
logContext: `channel=webchat key=${key2} recover`
|
|
38558
38951
|
});
|
|
@@ -38573,6 +38966,24 @@ app80.route("/", webchatGateway.routes());
|
|
|
38573
38966
|
webchatGateway.startSweeper();
|
|
38574
38967
|
webchatGateway.startPublicReaper();
|
|
38575
38968
|
var telegramFileFollowers = /* @__PURE__ */ new Map();
|
|
38969
|
+
var telegramFanTargets = (key2) => {
|
|
38970
|
+
const chatId = key2.slice(key2.indexOf(":") + 1);
|
|
38971
|
+
const account = listValidAccounts().find(
|
|
38972
|
+
(a) => listBotEntries(a.config.telegram).some((e) => e.id === key2.slice(0, key2.indexOf(":")))
|
|
38973
|
+
);
|
|
38974
|
+
if (!account) {
|
|
38975
|
+
console.error(`[channel-fanout] op=no-target origin=telegram key=${key2} accountId=none reason=no-account`);
|
|
38976
|
+
const send = telegramOriginTarget(key2);
|
|
38977
|
+
return [send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false }];
|
|
38978
|
+
}
|
|
38979
|
+
return adminFanTargets({
|
|
38980
|
+
origin: "telegram",
|
|
38981
|
+
originKey: key2,
|
|
38982
|
+
accountId: account.accountId,
|
|
38983
|
+
adminUserId: resolveTelegramAdminUserId({ accountId: account.accountId, chatId }),
|
|
38984
|
+
deps: adminFanDeps("telegram", account.accountId)
|
|
38985
|
+
});
|
|
38986
|
+
};
|
|
38576
38987
|
var telegramGateway = new TelegramGateway({
|
|
38577
38988
|
fetchStandingRules: fetchAccountStandingRules,
|
|
38578
38989
|
// Task 2353 — no turn-instruction resolver on this door. See the dep comment in
|
|
@@ -38582,26 +38993,8 @@ var telegramGateway = new TelegramGateway({
|
|
|
38582
38993
|
// is what the classification sidecar records (the manager reads
|
|
38583
38994
|
// telegramChannel.senderId) and therefore what session-reseat.ts writes.
|
|
38584
38995
|
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
38585
|
-
// Task
|
|
38586
|
-
|
|
38587
|
-
fanTargetsFor: (key2) => {
|
|
38588
|
-
const chatId = key2.slice(key2.indexOf(":") + 1);
|
|
38589
|
-
const account = listValidAccounts().find(
|
|
38590
|
-
(a) => listBotEntries(a.config.telegram).some((e) => e.id === key2.slice(0, key2.indexOf(":")))
|
|
38591
|
-
);
|
|
38592
|
-
if (!account) {
|
|
38593
|
-
console.error(`[channel-fanout] op=no-target origin=telegram key=${key2} accountId=none reason=no-account`);
|
|
38594
|
-
const send = telegramOriginTarget(key2);
|
|
38595
|
-
return [send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false }];
|
|
38596
|
-
}
|
|
38597
|
-
return adminFanTargets({
|
|
38598
|
-
origin: "telegram",
|
|
38599
|
-
originKey: key2,
|
|
38600
|
-
accountId: account.accountId,
|
|
38601
|
-
adminUserId: resolveTelegramAdminUserId({ accountId: account.accountId, chatId }),
|
|
38602
|
-
deps: adminFanDeps("telegram", account.accountId)
|
|
38603
|
-
});
|
|
38604
|
-
},
|
|
38996
|
+
// Task 2521 — hoisted above; see `telegramFanTargets`.
|
|
38997
|
+
fanTargetsFor: telegramFanTargets,
|
|
38605
38998
|
gatewayUrl: telegramGatewayUrl(),
|
|
38606
38999
|
serverPath: telegramServerPath(),
|
|
38607
39000
|
// Task 2368 — the role branch lives in its own module so it has a test that
|
|
@@ -38612,6 +39005,7 @@ var telegramGateway = new TelegramGateway({
|
|
|
38612
39005
|
managerSpawn,
|
|
38613
39006
|
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
38614
39007
|
startFileFollower: startTelegramNativeFileFollower,
|
|
39008
|
+
fanTargets: telegramFanTargets,
|
|
38615
39009
|
followers: telegramFileFollowers
|
|
38616
39010
|
})
|
|
38617
39011
|
});
|
|
@@ -38670,9 +39064,9 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
|
|
|
38670
39064
|
const entries2 = listBotEntries(a.config.telegram);
|
|
38671
39065
|
for (const bot of readChannelAdmins(a.accountDir, "telegram").byBot) {
|
|
38672
39066
|
if (!bot.adminUsers.includes(asNumber)) continue;
|
|
38673
|
-
const
|
|
38674
|
-
if (!
|
|
38675
|
-
hits.push({ accountId: a.accountId, botId: bot.botId, botToken:
|
|
39067
|
+
const entry2 = entries2.find((e) => e.id === bot.botId);
|
|
39068
|
+
if (!entry2) continue;
|
|
39069
|
+
hits.push({ accountId: a.accountId, botId: bot.botId, botToken: entry2.token });
|
|
38676
39070
|
}
|
|
38677
39071
|
}
|
|
38678
39072
|
if (hits.length === 0) return { kind: "none" };
|
|
@@ -39052,20 +39446,20 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
39052
39446
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
39053
39447
|
return c.text("Not found", 404);
|
|
39054
39448
|
}
|
|
39055
|
-
const filePath =
|
|
39056
|
-
const expectedDir =
|
|
39449
|
+
const filePath = resolve52(account.accountDir, "agents", slug, "assets", filename);
|
|
39450
|
+
const expectedDir = resolve52(account.accountDir, "agents", slug, "assets");
|
|
39057
39451
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
39058
39452
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
39059
39453
|
return c.text("Forbidden", 403);
|
|
39060
39454
|
}
|
|
39061
|
-
if (!
|
|
39455
|
+
if (!existsSync55(filePath)) {
|
|
39062
39456
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
39063
39457
|
return c.text("Not found", 404);
|
|
39064
39458
|
}
|
|
39065
39459
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
39066
39460
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
39067
39461
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
39068
|
-
const body =
|
|
39462
|
+
const body = readFileSync55(filePath);
|
|
39069
39463
|
return c.body(body, 200, {
|
|
39070
39464
|
"Content-Type": contentType,
|
|
39071
39465
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -39082,20 +39476,20 @@ app80.get("/generated/:filename", (c) => {
|
|
|
39082
39476
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
39083
39477
|
return c.text("Not found", 404);
|
|
39084
39478
|
}
|
|
39085
|
-
const filePath =
|
|
39086
|
-
const expectedDir =
|
|
39479
|
+
const filePath = resolve52(account.accountDir, "generated", filename);
|
|
39480
|
+
const expectedDir = resolve52(account.accountDir, "generated");
|
|
39087
39481
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
39088
39482
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
39089
39483
|
return c.text("Forbidden", 403);
|
|
39090
39484
|
}
|
|
39091
|
-
if (!
|
|
39485
|
+
if (!existsSync55(filePath)) {
|
|
39092
39486
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
39093
39487
|
return c.text("Not found", 404);
|
|
39094
39488
|
}
|
|
39095
39489
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
39096
39490
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
39097
39491
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
39098
|
-
const body =
|
|
39492
|
+
const body = readFileSync55(filePath);
|
|
39099
39493
|
return c.body(body, 200, {
|
|
39100
39494
|
"Content-Type": contentType,
|
|
39101
39495
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -39108,9 +39502,9 @@ app80.route("/v", visitor_consent_default);
|
|
|
39108
39502
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
39109
39503
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
39110
39504
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
39111
|
-
if (BRAND_JSON_PATH &&
|
|
39505
|
+
if (BRAND_JSON_PATH && existsSync55(BRAND_JSON_PATH)) {
|
|
39112
39506
|
try {
|
|
39113
|
-
const fullBrand = JSON.parse(
|
|
39507
|
+
const fullBrand = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
|
|
39114
39508
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
39115
39509
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
39116
39510
|
} catch {
|
|
@@ -39137,11 +39531,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
39137
39531
|
var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
|
|
39138
39532
|
var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
|
|
39139
39533
|
var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
|
|
39140
|
-
(p) =>
|
|
39534
|
+
(p) => existsSync55(resolve52(process.cwd(), "public", p.replace(/^\//, "")))
|
|
39141
39535
|
);
|
|
39142
39536
|
var SW_SOURCE = (() => {
|
|
39143
39537
|
try {
|
|
39144
|
-
return
|
|
39538
|
+
return readFileSync55(resolve52(process.cwd(), "public", "sw.js"), "utf-8");
|
|
39145
39539
|
} catch {
|
|
39146
39540
|
return null;
|
|
39147
39541
|
}
|
|
@@ -39150,8 +39544,8 @@ function readInstalledVersion() {
|
|
|
39150
39544
|
try {
|
|
39151
39545
|
if (!PLATFORM_ROOT6) return "unknown";
|
|
39152
39546
|
const versionFile = join63(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
|
|
39153
|
-
if (!
|
|
39154
|
-
const content =
|
|
39547
|
+
if (!existsSync55(versionFile)) return "unknown";
|
|
39548
|
+
const content = readFileSync55(versionFile, "utf-8").trim();
|
|
39155
39549
|
return content || "unknown";
|
|
39156
39550
|
} catch {
|
|
39157
39551
|
return "unknown";
|
|
@@ -39162,7 +39556,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
|
|
|
39162
39556
|
function cachedHtml(file) {
|
|
39163
39557
|
let html = htmlCache.get(file);
|
|
39164
39558
|
if (!html) {
|
|
39165
|
-
html =
|
|
39559
|
+
html = readFileSync55(resolve52(process.cwd(), "public", file), "utf-8");
|
|
39166
39560
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
39167
39561
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
39168
39562
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -39185,8 +39579,8 @@ function loadBrandingCache(agentSlug) {
|
|
|
39185
39579
|
const accountId = verdict.kind === "served" ? verdict.owner.accountId : getDefaultAccountId();
|
|
39186
39580
|
if (!accountId) return null;
|
|
39187
39581
|
const cachePath = join63(configDir, "branding-cache", accountId, `${agentSlug}.json`);
|
|
39188
|
-
if (!
|
|
39189
|
-
return JSON.parse(
|
|
39582
|
+
if (!existsSync55(cachePath)) return null;
|
|
39583
|
+
return JSON.parse(readFileSync55(cachePath, "utf-8"));
|
|
39190
39584
|
} catch {
|
|
39191
39585
|
return null;
|
|
39192
39586
|
}
|
|
@@ -39281,7 +39675,7 @@ app80.use("/vnc-popout.html", logViewerFetch);
|
|
|
39281
39675
|
app80.get("/vnc-popout.html", (c) => {
|
|
39282
39676
|
let html = htmlCache.get("vnc-popout.html");
|
|
39283
39677
|
if (!html) {
|
|
39284
|
-
html =
|
|
39678
|
+
html = readFileSync55(resolve52(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
39285
39679
|
const name = escapeHtml(BRAND.productName);
|
|
39286
39680
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
39287
39681
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -39441,9 +39835,9 @@ var httpServer = serve({ fetch: app80.fetch, port, hostname });
|
|
|
39441
39835
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
39442
39836
|
{
|
|
39443
39837
|
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39444
|
-
const reconcileScript =
|
|
39838
|
+
const reconcileScript = resolve52(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
|
|
39445
39839
|
const runReconcile = (ctx) => {
|
|
39446
|
-
if (!
|
|
39840
|
+
if (!existsSync55(reconcileScript)) return;
|
|
39447
39841
|
try {
|
|
39448
39842
|
const child = spawn3(process.execPath, [reconcileScript], {
|
|
39449
39843
|
env: { ...process.env, PLATFORM_ROOT: reconcilePlatformRoot },
|
|
@@ -39482,14 +39876,14 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39482
39876
|
{
|
|
39483
39877
|
const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
|
|
39484
39878
|
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39485
|
-
const dreamSweepScript =
|
|
39879
|
+
const dreamSweepScript = resolve52(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
|
|
39486
39880
|
registerLoop({
|
|
39487
39881
|
name: "dream-cycle-sweep",
|
|
39488
39882
|
intervalMs: DREAM_CYCLE_SWEEP_INTERVAL_MS,
|
|
39489
39883
|
firstRunDelayMs: 3e4,
|
|
39490
39884
|
detached: true,
|
|
39491
39885
|
run: (ctx) => {
|
|
39492
|
-
if (!
|
|
39886
|
+
if (!existsSync55(dreamSweepScript)) return;
|
|
39493
39887
|
try {
|
|
39494
39888
|
const child = spawn3(process.execPath, [dreamSweepScript], {
|
|
39495
39889
|
env: { ...process.env, PLATFORM_ROOT: dreamPlatformRoot },
|
|
@@ -39506,10 +39900,10 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39506
39900
|
}
|
|
39507
39901
|
{
|
|
39508
39902
|
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39509
|
-
const outlookScript =
|
|
39903
|
+
const outlookScript = resolve52(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
|
|
39510
39904
|
const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
|
|
39511
39905
|
const runOutlookComplete = (ctx) => {
|
|
39512
|
-
if (!
|
|
39906
|
+
if (!existsSync55(outlookScript)) return;
|
|
39513
39907
|
try {
|
|
39514
39908
|
const child = spawn3(process.execPath, [outlookScript], {
|
|
39515
39909
|
env: { ...process.env, PLATFORM_ROOT: outlookPlatformRoot },
|
|
@@ -39532,18 +39926,18 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39532
39926
|
}
|
|
39533
39927
|
{
|
|
39534
39928
|
const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39535
|
-
const strandedAccountsDir =
|
|
39929
|
+
const strandedAccountsDir = resolve52(auditRoot, "..", "data/accounts");
|
|
39536
39930
|
const STRANDED_AUDIT_INTERVAL_MS = 3e5;
|
|
39537
39931
|
const STRANDED_AGE_MS = 16 * 6e4;
|
|
39538
39932
|
const STRANDED_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
39539
39933
|
const runStrandedAudit = () => {
|
|
39540
39934
|
try {
|
|
39541
|
-
if (!
|
|
39935
|
+
if (!existsSync55(strandedAccountsDir)) return;
|
|
39542
39936
|
const now = Date.now();
|
|
39543
39937
|
for (const name of readdirSync32(strandedAccountsDir)) {
|
|
39544
39938
|
if (!STRANDED_UUID_RE.test(name)) continue;
|
|
39545
|
-
const pendingPath2 =
|
|
39546
|
-
if (!
|
|
39939
|
+
const pendingPath2 = resolve52(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
|
|
39940
|
+
if (!existsSync55(pendingPath2)) continue;
|
|
39547
39941
|
const ageMs2 = now - statSync30(pendingPath2).mtimeMs;
|
|
39548
39942
|
if (ageMs2 > STRANDED_AGE_MS) {
|
|
39549
39943
|
console.error(`[outlook-mcp] devicecode-stranded account=${name} ageSec=${Math.floor(ageMs2 / 1e3)}`);
|
|
@@ -39562,7 +39956,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39562
39956
|
}
|
|
39563
39957
|
{
|
|
39564
39958
|
const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39565
|
-
const googleAccountsDir =
|
|
39959
|
+
const googleAccountsDir = resolve52(googleRoot, "..", "data/accounts");
|
|
39566
39960
|
const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
|
|
39567
39961
|
const runGooglePendingAuditSafe = () => {
|
|
39568
39962
|
try {
|
|
@@ -39577,10 +39971,10 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39577
39971
|
firstRunDelayMs: 27e3,
|
|
39578
39972
|
run: runGooglePendingAuditSafe
|
|
39579
39973
|
});
|
|
39580
|
-
const googleAuditScript =
|
|
39974
|
+
const googleAuditScript = resolve52(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
|
|
39581
39975
|
const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
|
|
39582
39976
|
const runGoogleAccountAudit = (ctx) => {
|
|
39583
|
-
if (!
|
|
39977
|
+
if (!existsSync55(googleAuditScript)) return;
|
|
39584
39978
|
try {
|
|
39585
39979
|
const child = spawn3(process.execPath, [googleAuditScript], {
|
|
39586
39980
|
env: { ...process.env, PLATFORM_ROOT: googleRoot },
|
|
@@ -39640,7 +40034,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39640
40034
|
for (const o of migrateTelegramTwoSlot({
|
|
39641
40035
|
accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
|
|
39642
40036
|
secretPathFor: secretPath,
|
|
39643
|
-
legacySecretPathFor: (botType, accountId) =>
|
|
40037
|
+
legacySecretPathFor: (botType, accountId) => resolve52(
|
|
39644
40038
|
homedir5(),
|
|
39645
40039
|
configDirName2(),
|
|
39646
40040
|
`${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
|
|
@@ -39669,7 +40063,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39669
40063
|
}
|
|
39670
40064
|
const lines = buildTelegramAuditLines({
|
|
39671
40065
|
accounts,
|
|
39672
|
-
secretFileExists: (botId) =>
|
|
40066
|
+
secretFileExists: (botId) => existsSync55(secretPath(botId)),
|
|
39673
40067
|
agentActive: isActiveAgentSlug,
|
|
39674
40068
|
secretFilesOnDisk,
|
|
39675
40069
|
lastActivityByBot: (botId) => lastByBot.get(botId) ?? null,
|
|
@@ -39808,7 +40202,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39808
40202
|
}
|
|
39809
40203
|
{
|
|
39810
40204
|
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39811
|
-
const publishScript =
|
|
40205
|
+
const publishScript = resolve52(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
|
|
39812
40206
|
const PUBLISH_INTERVAL_MS = 3e5;
|
|
39813
40207
|
const bookingAccounts = () => {
|
|
39814
40208
|
try {
|
|
@@ -39819,7 +40213,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39819
40213
|
}
|
|
39820
40214
|
};
|
|
39821
40215
|
const spawnPublish = (account, ctx) => {
|
|
39822
|
-
if (!
|
|
40216
|
+
if (!existsSync55(publishScript)) return;
|
|
39823
40217
|
try {
|
|
39824
40218
|
const child = spawn3(process.execPath, [publishScript], {
|
|
39825
40219
|
env: {
|
|
@@ -39838,21 +40232,21 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39838
40232
|
}
|
|
39839
40233
|
};
|
|
39840
40234
|
const auditSnapshotAge = (account) => {
|
|
39841
|
-
const availPath =
|
|
40235
|
+
const availPath = resolve52(account.accountDir, "calendar-availability.json");
|
|
39842
40236
|
let hasBookingSite = false;
|
|
39843
40237
|
try {
|
|
39844
|
-
if (
|
|
39845
|
-
const cfg = JSON.parse(
|
|
40238
|
+
if (existsSync55(availPath)) {
|
|
40239
|
+
const cfg = JSON.parse(readFileSync55(availPath, "utf-8"));
|
|
39846
40240
|
hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
39847
40241
|
}
|
|
39848
40242
|
} catch {
|
|
39849
40243
|
}
|
|
39850
40244
|
if (!hasBookingSite) return;
|
|
39851
|
-
const statePath =
|
|
40245
|
+
const statePath = resolve52(account.accountDir, "state", "booking-availability", "last-publish.json");
|
|
39852
40246
|
let lastSuccessAt = null;
|
|
39853
|
-
if (
|
|
40247
|
+
if (existsSync55(statePath)) {
|
|
39854
40248
|
try {
|
|
39855
|
-
const rec = JSON.parse(
|
|
40249
|
+
const rec = JSON.parse(readFileSync55(statePath, "utf-8"));
|
|
39856
40250
|
lastSuccessAt = rec.lastSuccessAt ?? null;
|
|
39857
40251
|
} catch {
|
|
39858
40252
|
console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
|
|
@@ -39888,11 +40282,11 @@ startPlannerDivergence();
|
|
|
39888
40282
|
startDispatchFieldCensus(() => getSession());
|
|
39889
40283
|
{
|
|
39890
40284
|
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39891
|
-
const auditScript =
|
|
40285
|
+
const auditScript = resolve52(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
|
|
39892
40286
|
const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
|
|
39893
40287
|
const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
|
|
39894
40288
|
const runConnectorAudit = (ctx) => {
|
|
39895
|
-
if (!
|
|
40289
|
+
if (!existsSync55(auditScript)) return;
|
|
39896
40290
|
try {
|
|
39897
40291
|
const child = spawn3(process.execPath, [auditScript], {
|
|
39898
40292
|
env: { ...process.env, PLATFORM_ROOT: auditPlatformRoot, LOG_DIR: auditLogDir },
|
|
@@ -40014,11 +40408,11 @@ try {
|
|
|
40014
40408
|
var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
40015
40409
|
async function runAdminUserReconcileTick() {
|
|
40016
40410
|
try {
|
|
40017
|
-
if (!
|
|
40411
|
+
if (!existsSync55(USERS_FILE)) {
|
|
40018
40412
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
40019
40413
|
return;
|
|
40020
40414
|
}
|
|
40021
|
-
const usersRaw =
|
|
40415
|
+
const usersRaw = readFileSync55(USERS_FILE, "utf-8").trim();
|
|
40022
40416
|
if (!usersRaw) {
|
|
40023
40417
|
console.error("[adminuser-self-heal] skip reason=empty-users-file");
|
|
40024
40418
|
return;
|
|
@@ -40101,7 +40495,7 @@ var runIngestAuditTick = () => {
|
|
|
40101
40495
|
agentExists: (accountId, slug) => {
|
|
40102
40496
|
if (!house) return false;
|
|
40103
40497
|
const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir : join63(ACCOUNTS_DIR, accountId);
|
|
40104
|
-
return
|
|
40498
|
+
return existsSync55(join63(dir, "agents", slug, "config.json"));
|
|
40105
40499
|
}
|
|
40106
40500
|
});
|
|
40107
40501
|
} catch (err) {
|
|
@@ -40150,7 +40544,7 @@ registerLoop({
|
|
|
40150
40544
|
houseAccountId: () => resolveAccount()?.accountId ?? null,
|
|
40151
40545
|
activeSlugs: () => listValidAccounts().map((a) => {
|
|
40152
40546
|
const agentsRoot = join63(a.accountDir, "agents");
|
|
40153
|
-
if (!
|
|
40547
|
+
if (!existsSync55(agentsRoot)) return { accountId: a.accountId, slugs: [] };
|
|
40154
40548
|
const slugs = readdirSync32(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((slug) => isActiveAgentSlug(a.accountDir, slug));
|
|
40155
40549
|
return { accountId: a.accountId, slugs };
|
|
40156
40550
|
})
|
|
@@ -40164,8 +40558,8 @@ registerLoop({
|
|
|
40164
40558
|
});
|
|
40165
40559
|
var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
40166
40560
|
function countUsersRows() {
|
|
40167
|
-
if (!
|
|
40168
|
-
const raw =
|
|
40561
|
+
if (!existsSync55(USERS_FILE)) return 0;
|
|
40562
|
+
const raw = readFileSync55(USERS_FILE, "utf-8").trim();
|
|
40169
40563
|
if (!raw) return 0;
|
|
40170
40564
|
const users = JSON.parse(raw);
|
|
40171
40565
|
return users.filter((u) => typeof u.userId === "string").length;
|
|
@@ -40333,7 +40727,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
40333
40727
|
}
|
|
40334
40728
|
init({
|
|
40335
40729
|
configDir: configDirForWhatsApp,
|
|
40336
|
-
platformRoot:
|
|
40730
|
+
platformRoot: resolve52(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..")),
|
|
40337
40731
|
accountConfig: bootAccountConfig,
|
|
40338
40732
|
// Task 2074 — enrich with the sub-account name and fan out to every open
|
|
40339
40733
|
// admin SSE stream. Fire-and-forget: the manager's persist path must not
|