@rubytech/create-realagent-code 0.1.572 → 0.1.573
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/config/brand.json +2 -1
- package/payload/platform/lib/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/mcp/dist/index.js +4 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +39 -48
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
- package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +15 -47
- package/payload/platform/plugins/docs/references/telegram-guide.md +23 -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/scheduling/PLUGIN.md +4 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +78 -5
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +133 -2
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +11 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +30 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +8 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +37 -6
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -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/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/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/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/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 +739 -449
- 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"}`
|
|
@@ -5087,10 +5095,10 @@ var require_dist7 = __commonJS({
|
|
|
5087
5095
|
|
|
5088
5096
|
// server/index.ts
|
|
5089
5097
|
init_dist();
|
|
5090
|
-
import { readFileSync as
|
|
5098
|
+
import { readFileSync as readFileSync55, existsSync as existsSync56, watchFile, readdirSync as readdirSync32, statSync as statSync30 } from "fs";
|
|
5091
5099
|
import { spawn as spawn3 } from "child_process";
|
|
5092
5100
|
import { createHash as createHash8 } from "crypto";
|
|
5093
|
-
import { resolve as
|
|
5101
|
+
import { resolve as resolve51, join as join63, basename as basename17 } from "path";
|
|
5094
5102
|
import { homedir as homedir5 } from "os";
|
|
5095
5103
|
|
|
5096
5104
|
// server/lib/route-cache-watchers.ts
|
|
@@ -6341,12 +6349,12 @@ function getLanIp() {
|
|
|
6341
6349
|
let fallback = null;
|
|
6342
6350
|
for (const entries2 of Object.values(nets)) {
|
|
6343
6351
|
if (!entries2) continue;
|
|
6344
|
-
for (const
|
|
6345
|
-
if (
|
|
6346
|
-
if (
|
|
6347
|
-
return
|
|
6352
|
+
for (const entry2 of entries2) {
|
|
6353
|
+
if (entry2.family !== "IPv4" || entry2.internal) continue;
|
|
6354
|
+
if (entry2.address.startsWith("192.168.") || entry2.address.startsWith("10.")) {
|
|
6355
|
+
return entry2.address;
|
|
6348
6356
|
}
|
|
6349
|
-
if (!fallback) fallback =
|
|
6357
|
+
if (!fallback) fallback = entry2.address;
|
|
6350
6358
|
}
|
|
6351
6359
|
}
|
|
6352
6360
|
return fallback;
|
|
@@ -6650,20 +6658,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
6650
6658
|
// server/routes/health.ts
|
|
6651
6659
|
init_claude_agent();
|
|
6652
6660
|
function checkPort(port2, timeoutMs = 500) {
|
|
6653
|
-
return new Promise((
|
|
6661
|
+
return new Promise((resolve52) => {
|
|
6654
6662
|
const socket = createConnection2(port2, "127.0.0.1");
|
|
6655
6663
|
socket.setTimeout(timeoutMs);
|
|
6656
6664
|
socket.once("connect", () => {
|
|
6657
6665
|
socket.destroy();
|
|
6658
|
-
|
|
6666
|
+
resolve52(true);
|
|
6659
6667
|
});
|
|
6660
6668
|
socket.once("error", () => {
|
|
6661
6669
|
socket.destroy();
|
|
6662
|
-
|
|
6670
|
+
resolve52(false);
|
|
6663
6671
|
});
|
|
6664
6672
|
socket.once("timeout", () => {
|
|
6665
6673
|
socket.destroy();
|
|
6666
|
-
|
|
6674
|
+
resolve52(false);
|
|
6667
6675
|
});
|
|
6668
6676
|
});
|
|
6669
6677
|
}
|
|
@@ -7296,12 +7304,12 @@ function readConversationSummaries2(accountId) {
|
|
|
7296
7304
|
const seen = /* @__PURE__ */ new Set();
|
|
7297
7305
|
try {
|
|
7298
7306
|
if (!existsSync3(dir)) return out;
|
|
7299
|
-
for (const
|
|
7300
|
-
if (!
|
|
7301
|
-
const stem =
|
|
7307
|
+
for (const entry2 of readdirSync2(dir)) {
|
|
7308
|
+
if (!entry2.endsWith(".jsonl")) continue;
|
|
7309
|
+
const stem = entry2.slice(0, -".jsonl".length);
|
|
7302
7310
|
if (!isStorableStem(stem)) continue;
|
|
7303
7311
|
const channelKey = fileStemToChannelKey(stem);
|
|
7304
|
-
const file = join7(dir,
|
|
7312
|
+
const file = join7(dir, entry2);
|
|
7305
7313
|
seen.add(file);
|
|
7306
7314
|
let size;
|
|
7307
7315
|
let mtimeMs;
|
|
@@ -7640,7 +7648,7 @@ async function defaultResolve(accountId) {
|
|
|
7640
7648
|
return resolveSocket2(accountId);
|
|
7641
7649
|
}
|
|
7642
7650
|
async function fetchAvatar(accountId, jid, deps = {}) {
|
|
7643
|
-
const
|
|
7651
|
+
const resolve52 = deps.resolve;
|
|
7644
7652
|
const httpGet = deps.httpGet ?? defaultHttpGet;
|
|
7645
7653
|
const maxAgeMs = deps.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
7646
7654
|
const cached3 = readAvatar(accountId, jid);
|
|
@@ -7650,7 +7658,7 @@ async function fetchAvatar(accountId, jid, deps = {}) {
|
|
|
7650
7658
|
return "fresh";
|
|
7651
7659
|
}
|
|
7652
7660
|
console.log(`${TAG7} op=cache jid=${jid} hit=false ageMs=${ageMs2}`);
|
|
7653
|
-
const res =
|
|
7661
|
+
const res = resolve52 ? resolve52(accountId) : await defaultResolve(accountId);
|
|
7654
7662
|
if (!res.ok) {
|
|
7655
7663
|
console.log(`${TAG7} op=no-socket jid=${jid} account=${accountId.slice(0, 8)} reason=${res.reason}`);
|
|
7656
7664
|
return "no-socket";
|
|
@@ -7772,12 +7780,12 @@ async function sweepAvatars() {
|
|
|
7772
7780
|
var TAG9 = "[wa-chatmeta]";
|
|
7773
7781
|
function diffStoredAgainstLive(stored, live) {
|
|
7774
7782
|
const out = [];
|
|
7775
|
-
for (const
|
|
7776
|
-
if (!
|
|
7777
|
-
const seen = live.get(
|
|
7783
|
+
for (const entry2 of stored.values()) {
|
|
7784
|
+
if (!entry2.isGroup) continue;
|
|
7785
|
+
const seen = live.get(entry2.jid);
|
|
7778
7786
|
if (seen === void 0) continue;
|
|
7779
|
-
if (seen.name !==
|
|
7780
|
-
out.push({ jid:
|
|
7787
|
+
if (seen.name !== entry2.name) {
|
|
7788
|
+
out.push({ jid: entry2.jid, stored: entry2.name, live: seen.name, observedAt: seen.observedAt });
|
|
7781
7789
|
}
|
|
7782
7790
|
}
|
|
7783
7791
|
return out;
|
|
@@ -7793,13 +7801,13 @@ async function sweepChatMetadata() {
|
|
|
7793
7801
|
const sock = conn.sock;
|
|
7794
7802
|
const stored = readChatMetadata(conn.platformAccountId);
|
|
7795
7803
|
const live = /* @__PURE__ */ new Map();
|
|
7796
|
-
for (const
|
|
7797
|
-
if (!
|
|
7804
|
+
for (const entry2 of stored.values()) {
|
|
7805
|
+
if (!entry2.isGroup) continue;
|
|
7798
7806
|
groups += 1;
|
|
7799
7807
|
try {
|
|
7800
|
-
const meta = await sock.groupMetadata(
|
|
7808
|
+
const meta = await sock.groupMetadata(entry2.jid);
|
|
7801
7809
|
if (typeof meta.subject === "string" && meta.subject.length > 0) {
|
|
7802
|
-
live.set(
|
|
7810
|
+
live.set(entry2.jid, { name: meta.subject, observedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7803
7811
|
checked2 += 1;
|
|
7804
7812
|
}
|
|
7805
7813
|
} catch {
|
|
@@ -8177,9 +8185,9 @@ function purgeNonSocketAccountManagers(accountDir, socketOwnerId) {
|
|
|
8177
8185
|
if (targetId === socketOwnerId) return { removed: [] };
|
|
8178
8186
|
const map = wa.accountManagers;
|
|
8179
8187
|
if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
|
|
8180
|
-
const removed = Object.entries(map).map(([phone,
|
|
8188
|
+
const removed = Object.entries(map).map(([phone, entry2]) => ({
|
|
8181
8189
|
phone,
|
|
8182
|
-
managesAccount: typeof
|
|
8190
|
+
managesAccount: typeof entry2 === "string" ? entry2 : entry2 && typeof entry2 === "object" && typeof entry2.managesAccount === "string" ? entry2.managesAccount : ""
|
|
8183
8191
|
}));
|
|
8184
8192
|
if (removed.length === 0) return { removed: [] };
|
|
8185
8193
|
delete wa.accountManagers;
|
|
@@ -8482,10 +8490,10 @@ function purgeNonSocketRegisteredParties(accountDir, socketOwnerId) {
|
|
|
8482
8490
|
if (targetId === socketOwnerId) return { removed: [] };
|
|
8483
8491
|
const map = wa.registeredParties;
|
|
8484
8492
|
if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
|
|
8485
|
-
const removed = Object.entries(map).map(([phone,
|
|
8493
|
+
const removed = Object.entries(map).map(([phone, entry2]) => ({
|
|
8486
8494
|
phone,
|
|
8487
|
-
account:
|
|
8488
|
-
party:
|
|
8495
|
+
account: entry2 && typeof entry2 === "object" && typeof entry2.account === "string" ? entry2.account : "",
|
|
8496
|
+
party: entry2 && typeof entry2 === "object" && typeof entry2.party === "string" ? entry2.party : ""
|
|
8489
8497
|
}));
|
|
8490
8498
|
if (removed.length === 0) return { removed: [] };
|
|
8491
8499
|
delete wa.registeredParties;
|
|
@@ -9141,8 +9149,8 @@ async function startLogin(opts) {
|
|
|
9141
9149
|
await clearAuth(authDir);
|
|
9142
9150
|
let resolveCode = null;
|
|
9143
9151
|
let rejectCode = null;
|
|
9144
|
-
const codePromise = new Promise((
|
|
9145
|
-
resolveCode =
|
|
9152
|
+
const codePromise = new Promise((resolve52, reject) => {
|
|
9153
|
+
resolveCode = resolve52;
|
|
9146
9154
|
rejectCode = reject;
|
|
9147
9155
|
});
|
|
9148
9156
|
const codeTimer = setTimeout(
|
|
@@ -9319,27 +9327,27 @@ function bare(value) {
|
|
|
9319
9327
|
function reconcilePersonIdentities(rows) {
|
|
9320
9328
|
const perAccount = /* @__PURE__ */ new Map();
|
|
9321
9329
|
for (const row of rows) {
|
|
9322
|
-
let
|
|
9323
|
-
if (!
|
|
9324
|
-
|
|
9325
|
-
perAccount.set(row.accountId,
|
|
9330
|
+
let entry2 = perAccount.get(row.accountId);
|
|
9331
|
+
if (!entry2) {
|
|
9332
|
+
entry2 = { seen: /* @__PURE__ */ new Map(), divergent: 0 };
|
|
9333
|
+
perAccount.set(row.accountId, entry2);
|
|
9326
9334
|
}
|
|
9327
9335
|
const p = row.phone ? bare(row.phone) : null;
|
|
9328
9336
|
const t = row.telephone ? bare(row.telephone) : null;
|
|
9329
|
-
if (p && t && p !== t)
|
|
9337
|
+
if (p && t && p !== t) entry2.divergent += 1;
|
|
9330
9338
|
const key2 = p ?? t;
|
|
9331
|
-
if (key2)
|
|
9339
|
+
if (key2) entry2.seen.set(key2, (entry2.seen.get(key2) ?? 0) + 1);
|
|
9332
9340
|
}
|
|
9333
9341
|
const accounts = [];
|
|
9334
9342
|
let duplicates = 0;
|
|
9335
9343
|
let divergent = 0;
|
|
9336
|
-
for (const [accountId,
|
|
9344
|
+
for (const [accountId, entry2] of perAccount) {
|
|
9337
9345
|
let dup = 0;
|
|
9338
|
-
for (const count of
|
|
9346
|
+
for (const count of entry2.seen.values()) if (count > 1) dup += count - 1;
|
|
9339
9347
|
duplicates += dup;
|
|
9340
|
-
divergent +=
|
|
9341
|
-
if (dup > 0 ||
|
|
9342
|
-
accounts.push({ accountId, duplicates: dup, divergent:
|
|
9348
|
+
divergent += entry2.divergent;
|
|
9349
|
+
if (dup > 0 || entry2.divergent > 0) {
|
|
9350
|
+
accounts.push({ accountId, duplicates: dup, divergent: entry2.divergent });
|
|
9343
9351
|
}
|
|
9344
9352
|
}
|
|
9345
9353
|
return {
|
|
@@ -9362,7 +9370,7 @@ function reconcileSelfPhoneAdmins(input) {
|
|
|
9362
9370
|
for (const cred of nonHouse) {
|
|
9363
9371
|
const selfPhone = cred.selfPhone;
|
|
9364
9372
|
if (!selfPhone) continue;
|
|
9365
|
-
if (input.adminPhones.some((
|
|
9373
|
+
if (input.adminPhones.some((entry2) => phonesMatch(entry2, selfPhone))) {
|
|
9366
9374
|
matches.push({ accountId: cred.accountId, phone: selfPhone });
|
|
9367
9375
|
}
|
|
9368
9376
|
}
|
|
@@ -9771,11 +9779,11 @@ init_src2();
|
|
|
9771
9779
|
function buildRegisteredPartyReconcile(params) {
|
|
9772
9780
|
const rows = [];
|
|
9773
9781
|
const entries2 = Object.entries(params.registeredParties);
|
|
9774
|
-
for (const [phone,
|
|
9782
|
+
for (const [phone, entry2] of entries2) {
|
|
9775
9783
|
const shadowedBy = isAdminPhone(phone, params.adminPhones) ? "adminPhones" : managedAccountFor(params.accountManagers, phone) ? "accountManagers" : null;
|
|
9776
|
-
const unresolved2 = !params.isValidAccount(
|
|
9784
|
+
const unresolved2 = !params.isValidAccount(entry2.account);
|
|
9777
9785
|
if (shadowedBy || unresolved2) {
|
|
9778
|
-
rows.push({ phone, account:
|
|
9786
|
+
rows.push({ phone, account: entry2.account, party: entry2.party, shadowedBy, unresolved: unresolved2 });
|
|
9779
9787
|
}
|
|
9780
9788
|
}
|
|
9781
9789
|
const shadowed = rows.filter((r) => r.shadowedBy !== null).length;
|
|
@@ -10185,15 +10193,15 @@ app3.post("/config", async (c) => {
|
|
|
10185
10193
|
const agents = [];
|
|
10186
10194
|
if (existsSync9(agentsDir)) {
|
|
10187
10195
|
try {
|
|
10188
|
-
for (const
|
|
10189
|
-
if (!
|
|
10190
|
-
const configPath3 = resolve8(agentsDir,
|
|
10196
|
+
for (const entry2 of readdirSync8(agentsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
10197
|
+
if (!entry2.isDirectory() || entry2.name === "admin") continue;
|
|
10198
|
+
const configPath3 = resolve8(agentsDir, entry2.name, "config.json");
|
|
10191
10199
|
if (!existsSync9(configPath3)) continue;
|
|
10192
10200
|
try {
|
|
10193
10201
|
const parsed = JSON.parse(readFileSync11(configPath3, "utf-8"));
|
|
10194
|
-
agents.push({ slug:
|
|
10202
|
+
agents.push({ slug: entry2.name, displayName: parsed.displayName ?? entry2.name });
|
|
10195
10203
|
} catch {
|
|
10196
|
-
console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${
|
|
10204
|
+
console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${entry2.name}" \u2014 skipping`);
|
|
10197
10205
|
}
|
|
10198
10206
|
}
|
|
10199
10207
|
} catch (err) {
|
|
@@ -10213,8 +10221,8 @@ app3.post("/config", async (c) => {
|
|
|
10213
10221
|
const listDenial = bindingScopeDenial(c, callerAccountId, "list-account-managers");
|
|
10214
10222
|
if (listDenial) return listDenial;
|
|
10215
10223
|
const all = readAccountManagers(acct.accountDir);
|
|
10216
|
-
const boundAccountOf = (
|
|
10217
|
-
const accountManagers = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([,
|
|
10224
|
+
const boundAccountOf = (entry2) => typeof entry2 === "string" ? entry2 : entry2.managesAccount;
|
|
10225
|
+
const accountManagers = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => boundAccountOf(entry2) === callerAccountId));
|
|
10218
10226
|
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
10227
|
return c.json({ ok: true, accountManagers });
|
|
10220
10228
|
}
|
|
@@ -10252,7 +10260,7 @@ app3.post("/config", async (c) => {
|
|
|
10252
10260
|
const listDenial = bindingScopeDenial(c, callerAccountId, "list-registered-parties");
|
|
10253
10261
|
if (listDenial) return listDenial;
|
|
10254
10262
|
const all = readRegisteredParties(acct.accountDir);
|
|
10255
|
-
const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([,
|
|
10263
|
+
const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => entry2.account === callerAccountId));
|
|
10256
10264
|
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
10265
|
return c.json({ ok: true, registeredParties });
|
|
10258
10266
|
}
|
|
@@ -12162,10 +12170,10 @@ function readState(platformRoot5) {
|
|
|
12162
12170
|
return {};
|
|
12163
12171
|
}
|
|
12164
12172
|
}
|
|
12165
|
-
function recordSuccess(platformRoot5, accountId,
|
|
12173
|
+
function recordSuccess(platformRoot5, accountId, entry2) {
|
|
12166
12174
|
const path = stateFilePath(platformRoot5);
|
|
12167
12175
|
const state = readState(platformRoot5);
|
|
12168
|
-
state[accountId] =
|
|
12176
|
+
state[accountId] = entry2;
|
|
12169
12177
|
try {
|
|
12170
12178
|
writeFileSync6(path, JSON.stringify(state, null, 1));
|
|
12171
12179
|
} catch (err) {
|
|
@@ -12439,10 +12447,10 @@ function readVisitsPushState(platformRoot5) {
|
|
|
12439
12447
|
return {};
|
|
12440
12448
|
}
|
|
12441
12449
|
}
|
|
12442
|
-
function recordVisitsPush(platformRoot5, accountId,
|
|
12450
|
+
function recordVisitsPush(platformRoot5, accountId, entry2) {
|
|
12443
12451
|
const path = visitsStateFilePath(platformRoot5);
|
|
12444
12452
|
const state = readVisitsPushState(platformRoot5);
|
|
12445
|
-
state[accountId] =
|
|
12453
|
+
state[accountId] = entry2;
|
|
12446
12454
|
try {
|
|
12447
12455
|
writeFileSync7(path, JSON.stringify(state, null, 1));
|
|
12448
12456
|
} catch (err) {
|
|
@@ -13871,8 +13879,8 @@ function listBotEntries(config) {
|
|
|
13871
13879
|
function resolveBotEntry(accounts, botId) {
|
|
13872
13880
|
const hits = [];
|
|
13873
13881
|
for (const a of accounts) {
|
|
13874
|
-
for (const
|
|
13875
|
-
if (
|
|
13882
|
+
for (const entry2 of listBotEntries(a.config.telegram)) {
|
|
13883
|
+
if (entry2.id === botId) hits.push({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 });
|
|
13876
13884
|
}
|
|
13877
13885
|
}
|
|
13878
13886
|
if (hits.length === 0) return { kind: "none" };
|
|
@@ -14252,6 +14260,12 @@ function pickActivity(entries2, grown, nowMs, concurrencyWindowMs) {
|
|
|
14252
14260
|
// server/routes/session-reader.ts
|
|
14253
14261
|
init_dist();
|
|
14254
14262
|
var app7 = new Hono();
|
|
14263
|
+
function probeDataRef(path) {
|
|
14264
|
+
const res = resolveDataPath(path);
|
|
14265
|
+
if (!res.ok) {
|
|
14266
|
+
console.error(`[data] op=download-ref-unservable root="data" path="${path}" reason=resolve-failed status=${res.status}`);
|
|
14267
|
+
}
|
|
14268
|
+
}
|
|
14255
14269
|
function enrichDownloads(turn, scratchpadRoot) {
|
|
14256
14270
|
if (turn.kind !== "agent-file") return turn;
|
|
14257
14271
|
let scratchpadReal = null;
|
|
@@ -14263,6 +14277,7 @@ function enrichDownloads(turn, scratchpadRoot) {
|
|
|
14263
14277
|
const downloads = turn.files.map((f) => {
|
|
14264
14278
|
const dataRel = relative3(DATA_ROOT, f);
|
|
14265
14279
|
if (dataRel && !dataRel.startsWith("..") && !isAbsolute2(dataRel)) {
|
|
14280
|
+
probeDataRef(dataRel);
|
|
14266
14281
|
return { root: "data", path: dataRel };
|
|
14267
14282
|
}
|
|
14268
14283
|
if (scratchpadReal) {
|
|
@@ -15701,20 +15716,20 @@ function getTelegramGateway() {
|
|
|
15701
15716
|
|
|
15702
15717
|
// app/lib/telegram/access-control.ts
|
|
15703
15718
|
function checkTelegramAccess(params) {
|
|
15704
|
-
const { senderId, entry } = params;
|
|
15705
|
-
if (
|
|
15706
|
-
const adminUsers =
|
|
15719
|
+
const { senderId, entry: entry2 } = params;
|
|
15720
|
+
if (entry2.role === "admin") {
|
|
15721
|
+
const adminUsers = entry2.adminUsers ?? [];
|
|
15707
15722
|
if (adminUsers.includes(senderId)) {
|
|
15708
15723
|
return { allowed: true, reason: "admin-binding", agentType: "admin" };
|
|
15709
15724
|
}
|
|
15710
15725
|
return { allowed: false, reason: "not-admin-user", agentType: "admin" };
|
|
15711
15726
|
}
|
|
15712
|
-
const policy =
|
|
15727
|
+
const policy = entry2.dmPolicy ?? "disabled";
|
|
15713
15728
|
switch (policy) {
|
|
15714
15729
|
case "open":
|
|
15715
15730
|
return { allowed: true, reason: "dm-policy-open", agentType: "public" };
|
|
15716
15731
|
case "allowlist": {
|
|
15717
|
-
const allowFrom =
|
|
15732
|
+
const allowFrom = entry2.allowFrom ?? [];
|
|
15718
15733
|
if (allowFrom.includes(senderId)) {
|
|
15719
15734
|
return { allowed: true, reason: "allowlist-match", agentType: "public" };
|
|
15720
15735
|
}
|
|
@@ -16224,8 +16239,8 @@ var NOTIFY_MAX = 5e3;
|
|
|
16224
16239
|
var cache = /* @__PURE__ */ new Map();
|
|
16225
16240
|
function isTelegramNotifyDuplicate(notifyId) {
|
|
16226
16241
|
const now = Date.now();
|
|
16227
|
-
const
|
|
16228
|
-
if (
|
|
16242
|
+
const entry2 = cache.get(notifyId);
|
|
16243
|
+
if (entry2 && now - entry2.ts <= NOTIFY_TTL_MS) return true;
|
|
16229
16244
|
for (const [key2, e] of cache) {
|
|
16230
16245
|
if (now - e.ts > NOTIFY_TTL_MS) cache.delete(key2);
|
|
16231
16246
|
}
|
|
@@ -16324,9 +16339,9 @@ app11.post("/", async (c) => {
|
|
|
16324
16339
|
console.error(`${TAG26} op=reject reason=duplicate-bot-id botId=${botId} accounts=${resolved.count}`);
|
|
16325
16340
|
return c.json({ ok: false }, 401);
|
|
16326
16341
|
}
|
|
16327
|
-
const { accountId, accountDir, entry } = resolved;
|
|
16328
|
-
const agentSlug =
|
|
16329
|
-
console.error(`${TAG26} op=entry botId=${botId} accountId=${accountId} role=${
|
|
16342
|
+
const { accountId, accountDir, entry: entry2 } = resolved;
|
|
16343
|
+
const agentSlug = entry2.role === "admin" ? "admin" : entry2.agent;
|
|
16344
|
+
console.error(`${TAG26} op=entry botId=${botId} accountId=${accountId} role=${entry2.role} agent=${agentSlug}`);
|
|
16330
16345
|
const sp = secretPath(botId);
|
|
16331
16346
|
if (!existsSync22(sp)) {
|
|
16332
16347
|
console.error(`${TAG26} op=secret botId=${botId} result=missing-file`);
|
|
@@ -16349,7 +16364,7 @@ app11.post("/", async (c) => {
|
|
|
16349
16364
|
console.error(
|
|
16350
16365
|
`${TAG26} op=inbound-kind botId=${botId} update=${shape.envelope} content=${shape.content} isEdit=${shape.isEdit ? "yes" : "no"}`
|
|
16351
16366
|
);
|
|
16352
|
-
const decision = routeTelegramUpdate({ update, entry });
|
|
16367
|
+
const decision = routeTelegramUpdate({ update, entry: entry2 });
|
|
16353
16368
|
noteInboundKind(shape.primaryKind, decision.kind === "dispatch");
|
|
16354
16369
|
if (decision.kind === "ignore") {
|
|
16355
16370
|
console.error(`${TAG26} op=ignore botId=${botId} reason=${decision.reason}`);
|
|
@@ -16369,7 +16384,7 @@ app11.post("/", async (c) => {
|
|
|
16369
16384
|
if (decision.allowed) {
|
|
16370
16385
|
const cbKey = telegramChannelKey(botId, decision.senderId);
|
|
16371
16386
|
const cbAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16372
|
-
const cbAdminUserId =
|
|
16387
|
+
const cbAdminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: decision.senderId }) : null;
|
|
16373
16388
|
appendMessage2(accountId, {
|
|
16374
16389
|
messageId: `telegram:${accountId}:${cbKey}:cb:${decision.callbackId}`,
|
|
16375
16390
|
sessionId: cbAdminUserId ? adminSessionIdFor(accountId, decision.senderId, cbAdminUserId) : adminSessionIdFor(accountId, decision.senderId, void 0, botId),
|
|
@@ -16380,13 +16395,13 @@ app11.post("/", async (c) => {
|
|
|
16380
16395
|
senderName: decision.senderDisplay,
|
|
16381
16396
|
chatId: String(decision.chatId),
|
|
16382
16397
|
channelKey: cbKey,
|
|
16383
|
-
scope:
|
|
16398
|
+
scope: entry2.role === "admin" ? "admin" : "public",
|
|
16384
16399
|
origin: "inbound",
|
|
16385
16400
|
createdAt: cbAt
|
|
16386
16401
|
});
|
|
16387
16402
|
}
|
|
16388
16403
|
const answerStartedAt = Date.now();
|
|
16389
|
-
const answered3 = await answerTelegramCallback(
|
|
16404
|
+
const answered3 = await answerTelegramCallback(entry2.token, decision.callbackId);
|
|
16390
16405
|
const outcome = !answered3.ok ? "error" : decision.allowed ? "stored" : "refused";
|
|
16391
16406
|
console.error(
|
|
16392
16407
|
`${TAG26} op=callback-answered botId=${botId} callbackId=${decision.callbackId} ms=${Date.now() - answerStartedAt} outcome=${outcome}${answered3.error ? ` error=${logValue(answered3.error)}` : ""}`
|
|
@@ -16396,8 +16411,8 @@ app11.post("/", async (c) => {
|
|
|
16396
16411
|
const { senderId, chatId, text } = decision;
|
|
16397
16412
|
console.error(`${TAG26} op=access botId=${botId} senderId=${senderId} allowed=true reason=${decision.reason}`);
|
|
16398
16413
|
const channelKey = telegramChannelKey(botId, senderId);
|
|
16399
|
-
const scope =
|
|
16400
|
-
const adminUserId =
|
|
16414
|
+
const scope = entry2.role === "admin" ? "admin" : "public";
|
|
16415
|
+
const adminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: senderId }) : null;
|
|
16401
16416
|
const sessionId = adminUserId ? adminSessionIdFor(accountId, senderId, adminUserId) : adminSessionIdFor(accountId, senderId, void 0, botId);
|
|
16402
16417
|
const inboundAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16403
16418
|
const editSuffix = decision.isEdit ? `:edit:${decision.editDate}` : "";
|
|
@@ -16473,7 +16488,7 @@ app11.post("/", async (c) => {
|
|
|
16473
16488
|
});
|
|
16474
16489
|
}
|
|
16475
16490
|
}
|
|
16476
|
-
const active =
|
|
16491
|
+
const active = entry2.role === "admin" ? true : isActiveAgentSlug(accountDir, entry2.agent);
|
|
16477
16492
|
console.error(`${TAG26} op=agent botId=${botId} slug=${agentSlug} active=${active}`);
|
|
16478
16493
|
if (!active) {
|
|
16479
16494
|
console.error(`${TAG26} op=reject reason=agent-inactive botId=${botId} agent=${agentSlug}`);
|
|
@@ -16485,7 +16500,7 @@ app11.post("/", async (c) => {
|
|
|
16485
16500
|
return c.json({ ok: true }, 200);
|
|
16486
16501
|
}
|
|
16487
16502
|
const reply = async (replyText) => {
|
|
16488
|
-
const sent = await sendTelegramText(
|
|
16503
|
+
const sent = await sendTelegramText(entry2.token, chatId, replyText);
|
|
16489
16504
|
if (sent.ok) {
|
|
16490
16505
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
16491
16506
|
appendMessage2(accountId, {
|
|
@@ -16529,7 +16544,7 @@ app11.post("/", async (c) => {
|
|
|
16529
16544
|
});
|
|
16530
16545
|
if (line) console.error(line);
|
|
16531
16546
|
}
|
|
16532
|
-
const media = await downloadTelegramMedia({ token:
|
|
16547
|
+
const media = await downloadTelegramMedia({ token: entry2.token, botId, items: decision.media });
|
|
16533
16548
|
void (async () => {
|
|
16534
16549
|
const heard = await transcribeTelegramAudio({
|
|
16535
16550
|
botId,
|
|
@@ -16541,10 +16556,10 @@ app11.post("/", async (c) => {
|
|
|
16541
16556
|
accountId,
|
|
16542
16557
|
accountDir,
|
|
16543
16558
|
botId,
|
|
16544
|
-
botToken:
|
|
16559
|
+
botToken: entry2.token,
|
|
16545
16560
|
agentSlug,
|
|
16546
16561
|
senderId,
|
|
16547
|
-
role:
|
|
16562
|
+
role: entry2.role,
|
|
16548
16563
|
personId: null,
|
|
16549
16564
|
// Task 2078 — the admin person this chat is bound to (the users.json
|
|
16550
16565
|
// telegram binding). It is the SUBJECT of the session id, so one admin's
|
|
@@ -16942,9 +16957,9 @@ function computeAdminStoreDivergence(input) {
|
|
|
16942
16957
|
result.errors.push({ source: input.accountsDir, detail: err instanceof Error ? err.message : String(err) });
|
|
16943
16958
|
return result;
|
|
16944
16959
|
}
|
|
16945
|
-
for (const
|
|
16946
|
-
if (
|
|
16947
|
-
const accountDir = join35(input.accountsDir,
|
|
16960
|
+
for (const entry2 of entries2) {
|
|
16961
|
+
if (entry2.startsWith(".")) continue;
|
|
16962
|
+
const accountDir = join35(input.accountsDir, entry2);
|
|
16948
16963
|
try {
|
|
16949
16964
|
if (!statSync18(accountDir).isDirectory()) continue;
|
|
16950
16965
|
} catch {
|
|
@@ -18987,15 +19002,15 @@ function operatorRoleFor(config, userId) {
|
|
|
18987
19002
|
async function buildAccountOptionList(accounts, userId) {
|
|
18988
19003
|
const houseAccount = accounts.find((a) => a.config.role === "house");
|
|
18989
19004
|
console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
|
|
18990
|
-
const
|
|
19005
|
+
const resolve52 = async (a) => {
|
|
18991
19006
|
const businessName = await fetchAccountName(a.accountId) || void 0;
|
|
18992
19007
|
return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
|
|
18993
19008
|
};
|
|
18994
19009
|
const houseOptions = await Promise.all(
|
|
18995
|
-
accounts.filter((a) => a.config.role === "house").map(
|
|
19010
|
+
accounts.filter((a) => a.config.role === "house").map(resolve52)
|
|
18996
19011
|
);
|
|
18997
19012
|
const nonHouseOptions = await Promise.all(
|
|
18998
|
-
accounts.filter((a) => a.config.role !== "house").map(
|
|
19013
|
+
accounts.filter((a) => a.config.role !== "house").map(resolve52)
|
|
18999
19014
|
);
|
|
19000
19015
|
const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
|
|
19001
19016
|
nonHouseOptions.sort((x, y) => {
|
|
@@ -19972,7 +19987,7 @@ function authoredAgentFilenames(accountDir) {
|
|
|
19972
19987
|
const dir = resolve25(pluginsDir, plugin, "agents");
|
|
19973
19988
|
if (!existsSync34(dir)) continue;
|
|
19974
19989
|
try {
|
|
19975
|
-
for (const
|
|
19990
|
+
for (const entry2 of readdirSync22(dir)) if (entry2.endsWith(".md")) names.add(entry2);
|
|
19976
19991
|
} catch (err) {
|
|
19977
19992
|
console.error(`[admin/agents] op=authored-scan read-failed plugin=${plugin} error="${err}"`);
|
|
19978
19993
|
}
|
|
@@ -19998,7 +20013,7 @@ function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, author
|
|
|
19998
20013
|
for (const dir of dirs) {
|
|
19999
20014
|
if (!existsSync34(dir)) continue;
|
|
20000
20015
|
try {
|
|
20001
|
-
for (const
|
|
20016
|
+
for (const entry2 of readdirSync22(dir)) if (entry2.endsWith(".md")) names.add(entry2);
|
|
20002
20017
|
} catch (err) {
|
|
20003
20018
|
console.error(`[admin/agents] op=list-shipped read-failed dir=${dir} error="${err}"`);
|
|
20004
20019
|
}
|
|
@@ -20081,19 +20096,19 @@ function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
|
|
|
20081
20096
|
} catch {
|
|
20082
20097
|
continue;
|
|
20083
20098
|
}
|
|
20084
|
-
for (const
|
|
20085
|
-
if (!
|
|
20099
|
+
for (const entry2 of entries2) {
|
|
20100
|
+
if (!entry2.isFile() || !entry2.name.endsWith(".md")) continue;
|
|
20086
20101
|
try {
|
|
20087
|
-
const { fm } = splitFrontmatter(readFileSync35(resolve25(agentsDirP,
|
|
20102
|
+
const { fm } = splitFrontmatter(readFileSync35(resolve25(agentsDirP, entry2.name), "utf-8"));
|
|
20088
20103
|
if (!fm.name) {
|
|
20089
20104
|
specialistsSkipped++;
|
|
20090
|
-
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${
|
|
20105
|
+
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
|
|
20091
20106
|
continue;
|
|
20092
20107
|
}
|
|
20093
20108
|
const specialistTools = parseToolsLine(fm.tools);
|
|
20094
20109
|
const r = classify(specialistTools, riskByTool, riskSurfaceFailed);
|
|
20095
20110
|
specialists.push({
|
|
20096
|
-
slug:
|
|
20111
|
+
slug: entry2.name.replace(/\.md$/, ""),
|
|
20097
20112
|
displayName: fm.name,
|
|
20098
20113
|
kind: "specialist",
|
|
20099
20114
|
origin: "specialist",
|
|
@@ -20116,11 +20131,11 @@ function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
|
|
|
20116
20131
|
// have the surface claim an agent is stopped while it is still being
|
|
20117
20132
|
// spawned. Absence from the live dir alone is also not the test: a
|
|
20118
20133
|
// specialist that was never activated was never switched off either.
|
|
20119
|
-
disabled: existsSync34(resolve25(accountDir, ...QUARANTINE_DIR,
|
|
20134
|
+
disabled: existsSync34(resolve25(accountDir, ...QUARANTINE_DIR, entry2.name)) && !existsSync34(resolve25(accountDir, ...SPECIALISTS_DIR, entry2.name))
|
|
20120
20135
|
});
|
|
20121
20136
|
} catch {
|
|
20122
20137
|
specialistsSkipped++;
|
|
20123
|
-
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${
|
|
20138
|
+
console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
|
|
20124
20139
|
}
|
|
20125
20140
|
}
|
|
20126
20141
|
}
|
|
@@ -20147,16 +20162,16 @@ app23.get("/", requireAdminSession, (c) => {
|
|
|
20147
20162
|
if (existsSync34(agentsDir)) {
|
|
20148
20163
|
try {
|
|
20149
20164
|
const entries2 = readdirSync22(agentsDir, { withFileTypes: true });
|
|
20150
|
-
for (const
|
|
20151
|
-
if (!
|
|
20152
|
-
if (
|
|
20153
|
-
const configPath3 = resolve25(agentsDir,
|
|
20165
|
+
for (const entry2 of entries2.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
20166
|
+
if (!entry2.isDirectory()) continue;
|
|
20167
|
+
if (entry2.name === "admin") continue;
|
|
20168
|
+
const configPath3 = resolve25(agentsDir, entry2.name, "config.json");
|
|
20154
20169
|
if (!existsSync34(configPath3)) continue;
|
|
20155
20170
|
try {
|
|
20156
20171
|
const config = JSON.parse(readFileSync35(configPath3, "utf-8"));
|
|
20157
20172
|
agents.push({
|
|
20158
|
-
slug:
|
|
20159
|
-
displayName: config.displayName ??
|
|
20173
|
+
slug: entry2.name,
|
|
20174
|
+
displayName: config.displayName ?? entry2.name,
|
|
20160
20175
|
status: config.status ?? "unknown",
|
|
20161
20176
|
kind: "public",
|
|
20162
20177
|
origin: "public",
|
|
@@ -20169,7 +20184,7 @@ app23.get("/", requireAdminSession, (c) => {
|
|
|
20169
20184
|
});
|
|
20170
20185
|
} catch {
|
|
20171
20186
|
skipped++;
|
|
20172
|
-
console.error(`[admin/agents] failed to parse config.json for agent "${
|
|
20187
|
+
console.error(`[admin/agents] failed to parse config.json for agent "${entry2.name}" \u2014 skipping`);
|
|
20173
20188
|
}
|
|
20174
20189
|
}
|
|
20175
20190
|
} catch (err) {
|
|
@@ -20827,7 +20842,7 @@ app24.get("/", requireAdminSession, async (c) => {
|
|
|
20827
20842
|
if (!userId) return c.json({ error: "User identity required \u2014 authenticate with users.json PIN" }, 401);
|
|
20828
20843
|
try {
|
|
20829
20844
|
const flushed = await listAdminSessions(accountId, userId, 20);
|
|
20830
|
-
const
|
|
20845
|
+
const sessions3 = flushed.map((r) => ({
|
|
20831
20846
|
sessionId: r.sessionId,
|
|
20832
20847
|
cacheKey: null,
|
|
20833
20848
|
name: r.name,
|
|
@@ -20835,15 +20850,15 @@ app24.get("/", requireAdminSession, async (c) => {
|
|
|
20835
20850
|
phase: "flushed",
|
|
20836
20851
|
channel: r.channel
|
|
20837
20852
|
})).sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
|
|
20838
|
-
const channelCounts =
|
|
20853
|
+
const channelCounts = sessions3.reduce((acc, s) => {
|
|
20839
20854
|
const k = s.channel ?? "unknown";
|
|
20840
20855
|
acc[k] = (acc[k] ?? 0) + 1;
|
|
20841
20856
|
return acc;
|
|
20842
20857
|
}, {});
|
|
20843
20858
|
console.error(
|
|
20844
|
-
`[conversations-list] render rows=${
|
|
20859
|
+
`[conversations-list] render rows=${sessions3.length} channels=${JSON.stringify(channelCounts)}`
|
|
20845
20860
|
);
|
|
20846
|
-
return c.json({ sessions:
|
|
20861
|
+
return c.json({ sessions: sessions3 });
|
|
20847
20862
|
} catch (err) {
|
|
20848
20863
|
console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
20849
20864
|
return c.json({ error: "Failed to fetch sessions" }, 500);
|
|
@@ -22539,14 +22554,14 @@ async function walkSubtree(root, dataRoot, out) {
|
|
|
22539
22554
|
return { clean: false };
|
|
22540
22555
|
}
|
|
22541
22556
|
let clean = true;
|
|
22542
|
-
for (const
|
|
22543
|
-
if (
|
|
22544
|
-
if (
|
|
22545
|
-
const abs = join43(root,
|
|
22546
|
-
if (
|
|
22557
|
+
for (const entry2 of entries2) {
|
|
22558
|
+
if (entry2.isSymbolicLink()) continue;
|
|
22559
|
+
if (entry2.isDirectory() && entry2.name === ".uploads-tmp") continue;
|
|
22560
|
+
const abs = join43(root, entry2.name);
|
|
22561
|
+
if (entry2.isDirectory()) {
|
|
22547
22562
|
const sub = await walkSubtree(abs, dataRoot, out);
|
|
22548
22563
|
if (!sub.clean) clean = false;
|
|
22549
|
-
} else if (
|
|
22564
|
+
} else if (entry2.isFile()) {
|
|
22550
22565
|
try {
|
|
22551
22566
|
const st = await fsp.stat(abs);
|
|
22552
22567
|
out.push({
|
|
@@ -22838,14 +22853,14 @@ function buildZip(entries2) {
|
|
|
22838
22853
|
const localChunks = [];
|
|
22839
22854
|
const centralChunks = [];
|
|
22840
22855
|
let offset = 0;
|
|
22841
|
-
for (const
|
|
22842
|
-
const nameBytes = Buffer.from(
|
|
22843
|
-
const crc = crc32(
|
|
22844
|
-
const uncompressedSize =
|
|
22845
|
-
const deflated = deflateRawSync(
|
|
22856
|
+
for (const entry2 of entries2) {
|
|
22857
|
+
const nameBytes = Buffer.from(entry2.name, "utf8");
|
|
22858
|
+
const crc = crc32(entry2.data);
|
|
22859
|
+
const uncompressedSize = entry2.data.length;
|
|
22860
|
+
const deflated = deflateRawSync(entry2.data);
|
|
22846
22861
|
const useDeflate = deflated.length < uncompressedSize;
|
|
22847
22862
|
const method = useDeflate ? 8 : 0;
|
|
22848
|
-
const payload = useDeflate ? deflated :
|
|
22863
|
+
const payload = useDeflate ? deflated : entry2.data;
|
|
22849
22864
|
const compressedSize = payload.length;
|
|
22850
22865
|
const localHeader = Buffer.alloc(30);
|
|
22851
22866
|
localHeader.writeUInt32LE(LOCAL_SIG, 0);
|
|
@@ -22971,47 +22986,47 @@ async function servableFilesIn(dirAbs, uuid) {
|
|
|
22971
22986
|
}
|
|
22972
22987
|
return out;
|
|
22973
22988
|
}
|
|
22974
|
-
async function enrich(absolute,
|
|
22975
|
-
if (
|
|
22976
|
-
const dirAbs = join44(absolute,
|
|
22977
|
-
const meta = await readMeta2(dirAbs,
|
|
22989
|
+
async function enrich(absolute, entry2, accountNames) {
|
|
22990
|
+
if (entry2.kind === "directory" && UUID_RE3.test(entry2.name)) {
|
|
22991
|
+
const dirAbs = join44(absolute, entry2.name);
|
|
22992
|
+
const meta = await readMeta2(dirAbs, entry2.name);
|
|
22978
22993
|
if (meta?.filename) {
|
|
22979
|
-
const servable = await servableFilesIn(dirAbs,
|
|
22994
|
+
const servable = await servableFilesIn(dirAbs, entry2.name);
|
|
22980
22995
|
if (servable.length === 1) {
|
|
22981
22996
|
const innerName = servable[0];
|
|
22982
22997
|
let size = null;
|
|
22983
|
-
let modifiedAt =
|
|
22998
|
+
let modifiedAt = entry2.modifiedAt;
|
|
22984
22999
|
try {
|
|
22985
23000
|
const st = await stat6(join44(dirAbs, innerName));
|
|
22986
23001
|
size = st.size;
|
|
22987
23002
|
modifiedAt = st.mtime.toISOString();
|
|
22988
23003
|
} catch {
|
|
22989
23004
|
}
|
|
22990
|
-
|
|
22991
|
-
|
|
22992
|
-
|
|
22993
|
-
|
|
22994
|
-
|
|
22995
|
-
|
|
23005
|
+
entry2.kind = "file";
|
|
23006
|
+
entry2.displayName = meta.filename;
|
|
23007
|
+
entry2.mimeType = meta.mimeType;
|
|
23008
|
+
entry2.sizeBytes = size;
|
|
23009
|
+
entry2.modifiedAt = modifiedAt;
|
|
23010
|
+
entry2.entryPath = `${entry2.name}/${innerName}`;
|
|
22996
23011
|
return "flattened";
|
|
22997
23012
|
}
|
|
22998
|
-
|
|
23013
|
+
entry2.displayName = meta.filename;
|
|
22999
23014
|
return "kept-as-dir";
|
|
23000
23015
|
}
|
|
23001
|
-
const accountName = accountNames.get(
|
|
23016
|
+
const accountName = accountNames.get(entry2.name);
|
|
23002
23017
|
if (accountName) {
|
|
23003
|
-
|
|
23018
|
+
entry2.displayName = accountName;
|
|
23004
23019
|
}
|
|
23005
23020
|
return null;
|
|
23006
23021
|
}
|
|
23007
|
-
if (
|
|
23008
|
-
const dot =
|
|
23009
|
-
const base = dot === -1 ?
|
|
23022
|
+
if (entry2.kind === "file") {
|
|
23023
|
+
const dot = entry2.name.lastIndexOf(".");
|
|
23024
|
+
const base = dot === -1 ? entry2.name : entry2.name.slice(0, dot);
|
|
23010
23025
|
if (UUID_RE3.test(base)) {
|
|
23011
23026
|
const meta = await readMeta2(absolute, base);
|
|
23012
23027
|
if (meta?.filename) {
|
|
23013
|
-
|
|
23014
|
-
|
|
23028
|
+
entry2.displayName = meta.filename;
|
|
23029
|
+
entry2.mimeType = meta.mimeType;
|
|
23015
23030
|
}
|
|
23016
23031
|
}
|
|
23017
23032
|
}
|
|
@@ -23102,6 +23117,10 @@ function isValidEntryName(name) {
|
|
|
23102
23117
|
if (name.startsWith("@")) return false;
|
|
23103
23118
|
return !/[\\/\0]/.test(name);
|
|
23104
23119
|
}
|
|
23120
|
+
function crossAccountOwner(relPath) {
|
|
23121
|
+
const segs = relPath.split("/").filter(Boolean);
|
|
23122
|
+
return segs.length >= 2 && segs[0] === "accounts" ? segs[1] : "";
|
|
23123
|
+
}
|
|
23105
23124
|
function resolveOwnAccountWrite(rawPath, accountId, endpoint) {
|
|
23106
23125
|
const res = resolveDataPath(rawPath);
|
|
23107
23126
|
if (!res.ok) {
|
|
@@ -23169,8 +23188,7 @@ app29.get("/", requireAdminSession, async (c) => {
|
|
|
23169
23188
|
}
|
|
23170
23189
|
const { absolute, relative: relPath } = resolution;
|
|
23171
23190
|
if (crossesForeignAccountPartition(relPath, accountId)) {
|
|
23172
|
-
console.error(`[data] account-
|
|
23173
|
-
return c.json({ error: "Not found" }, 404);
|
|
23191
|
+
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
23192
|
}
|
|
23175
23193
|
try {
|
|
23176
23194
|
const info = await stat6(absolute);
|
|
@@ -23346,8 +23364,7 @@ app29.get("/download", requireAdminSession, async (c) => {
|
|
|
23346
23364
|
return c.json({ error: "Not found" }, 404);
|
|
23347
23365
|
}
|
|
23348
23366
|
} else if (root === "data" && crossesForeignAccountPartition(relPath, accountId)) {
|
|
23349
|
-
console.error(`[data] account-
|
|
23350
|
-
return c.json({ error: "Not found" }, 404);
|
|
23367
|
+
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
23368
|
}
|
|
23352
23369
|
try {
|
|
23353
23370
|
const info = await stat6(absolute);
|
|
@@ -23460,8 +23477,7 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
|
|
|
23460
23477
|
}
|
|
23461
23478
|
const { absolute, relative: relPath } = resolution;
|
|
23462
23479
|
if (crossesForeignAccountPartition(relPath, accountId)) {
|
|
23463
|
-
console.error(`[data] account-
|
|
23464
|
-
return c.json({ error: "Not found" }, 404);
|
|
23480
|
+
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
23481
|
}
|
|
23466
23482
|
try {
|
|
23467
23483
|
const info = await stat6(absolute);
|
|
@@ -23469,9 +23485,6 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
|
|
|
23469
23485
|
dirsWalked++;
|
|
23470
23486
|
const parentAbs = dirname15(absolute);
|
|
23471
23487
|
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
23488
|
const fileInfo = await stat6(fileAbs);
|
|
23476
23489
|
const member = relative6(parentAbs, fileAbs).split(sep9).join("/");
|
|
23477
23490
|
const over = await addFile(fileAbs, fileInfo.size, member);
|
|
@@ -26461,8 +26474,8 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
|
26461
26474
|
if (!existsSync39(dir)) continue;
|
|
26462
26475
|
try {
|
|
26463
26476
|
const entries2 = await readdir5(dir);
|
|
26464
|
-
for (const
|
|
26465
|
-
if (
|
|
26477
|
+
for (const entry2 of entries2) {
|
|
26478
|
+
if (entry2.endsWith(".md")) names.add(entry2);
|
|
26466
26479
|
}
|
|
26467
26480
|
} catch (err) {
|
|
26468
26481
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -27498,6 +27511,7 @@ async function managerRcRecover(opts) {
|
|
|
27498
27511
|
body: JSON.stringify({
|
|
27499
27512
|
sessionId: opts.sessionId,
|
|
27500
27513
|
recover: true,
|
|
27514
|
+
subscriberAttached: opts.subscriberAttached,
|
|
27501
27515
|
role: "admin",
|
|
27502
27516
|
channel: "webchat",
|
|
27503
27517
|
senderId: opts.webchatChannel.key,
|
|
@@ -28945,6 +28959,8 @@ init_neo4j_store();
|
|
|
28945
28959
|
init_src2();
|
|
28946
28960
|
import cronParser from "cron-parser";
|
|
28947
28961
|
import { int as int2 } from "neo4j-driver";
|
|
28962
|
+
import { readFileSync as readFileSync44 } from "fs";
|
|
28963
|
+
import { resolve as resolve37 } from "path";
|
|
28948
28964
|
|
|
28949
28965
|
// ../lib/agent-dispatch-rule/src/index.ts
|
|
28950
28966
|
import { readFileSync as readFileSync43 } from "fs";
|
|
@@ -29112,6 +29128,23 @@ var ROUTINE_COLS = `e.eventId AS eventId, e.name AS name, e.eventStatus AS event
|
|
|
29112
29128
|
function since24h() {
|
|
29113
29129
|
return new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
29114
29130
|
}
|
|
29131
|
+
function gateClassification(row) {
|
|
29132
|
+
const judged = row.eventStatus === "scheduled" && row.recurrence !== null && row.agentChannel !== null && row.agentDestination !== null && row.agentPrompt !== null;
|
|
29133
|
+
if (!judged) return null;
|
|
29134
|
+
if (row.gatePlugin) return true;
|
|
29135
|
+
return typeof row.gateWaived === "string" && row.gateWaived.trim() !== "";
|
|
29136
|
+
}
|
|
29137
|
+
function gateWatermarkPath(eventId) {
|
|
29138
|
+
return resolve37(PLATFORM_ROOT, "..", "state", "scheduling-gate", `${eventId}.json`);
|
|
29139
|
+
}
|
|
29140
|
+
function readGateLastCommitAt(eventId) {
|
|
29141
|
+
try {
|
|
29142
|
+
const parsed = JSON.parse(readFileSync44(gateWatermarkPath(eventId), "utf-8"));
|
|
29143
|
+
return typeof parsed?.lastCommitAt === "string" ? parsed.lastCommitAt : null;
|
|
29144
|
+
} catch {
|
|
29145
|
+
return null;
|
|
29146
|
+
}
|
|
29147
|
+
}
|
|
29115
29148
|
function mapRoutine(r) {
|
|
29116
29149
|
const g = (k) => r.get(k) ?? null;
|
|
29117
29150
|
const n = (k) => {
|
|
@@ -29161,7 +29194,23 @@ function mapRoutine(r) {
|
|
|
29161
29194
|
// only the list: the modal's save replaces the list row wholesale
|
|
29162
29195
|
// (page.tsx:202), so counts served on the list alone would blank on save.
|
|
29163
29196
|
runs24h: n("runs24h"),
|
|
29164
|
-
gated24h: n("gated24h")
|
|
29197
|
+
gated24h: n("gated24h"),
|
|
29198
|
+
// Task 2472 — computed from columns already projected above, so no Cypher
|
|
29199
|
+
// changes and no second query. See gateClassification for why `null` is a
|
|
29200
|
+
// third state rather than a default `true`.
|
|
29201
|
+
gateClassified: gateClassification({
|
|
29202
|
+
eventStatus: g("eventStatus"),
|
|
29203
|
+
recurrence: g("recurrence"),
|
|
29204
|
+
agentChannel: g("agentChannel"),
|
|
29205
|
+
agentDestination: g("agentDestination"),
|
|
29206
|
+
agentPrompt: g("agentPrompt"),
|
|
29207
|
+
gatePlugin: g("gatePlugin"),
|
|
29208
|
+
gateWaived: g("gateWaived")
|
|
29209
|
+
}),
|
|
29210
|
+
// Task 2472 — read only for a gated row: an ungated one has no gate to be
|
|
29211
|
+
// late, and a file read per card on every list would otherwise be spent
|
|
29212
|
+
// proving that.
|
|
29213
|
+
gateLastCommitAt: g("gateTool") === null ? null : readGateLastCommitAt(g("eventId") ?? "")
|
|
29165
29214
|
};
|
|
29166
29215
|
}
|
|
29167
29216
|
function dispatchFieldsOf(r) {
|
|
@@ -29919,8 +29968,8 @@ init_dist();
|
|
|
29919
29968
|
init_helpers();
|
|
29920
29969
|
init_account_scope();
|
|
29921
29970
|
init_route_cache();
|
|
29922
|
-
import { existsSync as existsSync44, readdirSync as readdirSync26, readFileSync as
|
|
29923
|
-
import { resolve as
|
|
29971
|
+
import { existsSync as existsSync44, readdirSync as readdirSync26, readFileSync as readFileSync45, rmSync as rmSync6 } from "fs";
|
|
29972
|
+
import { resolve as resolve38 } from "path";
|
|
29924
29973
|
var app56 = new Hono();
|
|
29925
29974
|
var NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
29926
29975
|
function splitSkillMd(text) {
|
|
@@ -29958,18 +30007,18 @@ app56.get("/", requireAdminSession, async (c) => {
|
|
|
29958
30007
|
}
|
|
29959
30008
|
ensureAccountWatchers(acct.accountId, acct.accountDir);
|
|
29960
30009
|
const { value, state } = await getOrCompute("skills", acct.accountId, async () => {
|
|
29961
|
-
const pluginsDir =
|
|
30010
|
+
const pluginsDir = resolve38(acct.accountDir, "plugins");
|
|
29962
30011
|
const skills = [];
|
|
29963
30012
|
let pluginCount = 0;
|
|
29964
30013
|
for (const plugin of subdirs2(pluginsDir)) {
|
|
29965
30014
|
pluginCount++;
|
|
29966
|
-
const skillsDir =
|
|
30015
|
+
const skillsDir = resolve38(pluginsDir, plugin, "skills");
|
|
29967
30016
|
for (const skill of subdirs2(skillsDir)) {
|
|
29968
|
-
const skillMd =
|
|
30017
|
+
const skillMd = resolve38(skillsDir, skill, "SKILL.md");
|
|
29969
30018
|
if (!existsSync44(skillMd)) continue;
|
|
29970
30019
|
try {
|
|
29971
|
-
const { frontmatter } = splitSkillMd(
|
|
29972
|
-
const refsDir =
|
|
30020
|
+
const { frontmatter } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
|
|
30021
|
+
const refsDir = resolve38(skillsDir, skill, "references");
|
|
29973
30022
|
const refCount = existsSync44(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).length : 0;
|
|
29974
30023
|
skills.push({ plugin, skill, description: frontmatter.description, refCount });
|
|
29975
30024
|
} catch (err) {
|
|
@@ -29992,14 +30041,14 @@ app56.get("/:plugin/:skill", requireAdminSession, async (c) => {
|
|
|
29992
30041
|
console.error(`[admin:skills] op=get outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
|
|
29993
30042
|
return c.json({ error: "Invalid plugin or skill name" }, 400);
|
|
29994
30043
|
}
|
|
29995
|
-
const skillDir =
|
|
29996
|
-
const skillMd =
|
|
30044
|
+
const skillDir = resolve38(acct.accountDir, "plugins", plugin, "skills", skill);
|
|
30045
|
+
const skillMd = resolve38(skillDir, "SKILL.md");
|
|
29997
30046
|
const found = existsSync44(skillMd);
|
|
29998
30047
|
console.log(`[admin:skills] op=get plugin=${plugin} skill=${skill} found=${found}`);
|
|
29999
30048
|
if (!found) return c.json({ error: "Skill not found" }, 404);
|
|
30000
30049
|
try {
|
|
30001
|
-
const { frontmatter, body } = splitSkillMd(
|
|
30002
|
-
const refsDir =
|
|
30050
|
+
const { frontmatter, body } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
|
|
30051
|
+
const refsDir = resolve38(skillDir, "references");
|
|
30003
30052
|
const references = existsSync44(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort() : [];
|
|
30004
30053
|
return c.json({ skill: { plugin, skill, frontmatter, body, references } });
|
|
30005
30054
|
} catch (err) {
|
|
@@ -30017,10 +30066,10 @@ app56.delete("/:plugin/:skill", requireAdminSession, async (c) => {
|
|
|
30017
30066
|
console.error(`[admin:skills] op=delete outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
|
|
30018
30067
|
return c.json({ error: "Invalid plugin or skill name" }, 400);
|
|
30019
30068
|
}
|
|
30020
|
-
const pluginDir =
|
|
30021
|
-
const skillsDir =
|
|
30022
|
-
const skillDir =
|
|
30023
|
-
if (!existsSync44(
|
|
30069
|
+
const pluginDir = resolve38(acct.accountDir, "plugins", plugin);
|
|
30070
|
+
const skillsDir = resolve38(pluginDir, "skills");
|
|
30071
|
+
const skillDir = resolve38(skillsDir, skill);
|
|
30072
|
+
if (!existsSync44(resolve38(skillDir, "SKILL.md"))) {
|
|
30024
30073
|
console.error(`[admin:skills] op=delete outcome=rejected reason=not-found plugin=${plugin} skill=${skill}`);
|
|
30025
30074
|
return c.json({ error: "Skill not found" }, 404);
|
|
30026
30075
|
}
|
|
@@ -30672,8 +30721,8 @@ init_neo4j_store();
|
|
|
30672
30721
|
// app/lib/admin-roster.ts
|
|
30673
30722
|
init_account();
|
|
30674
30723
|
init_neo4j_store();
|
|
30675
|
-
import { existsSync as existsSync45, readdirSync as readdirSync27, readFileSync as
|
|
30676
|
-
import { resolve as
|
|
30724
|
+
import { existsSync as existsSync45, readdirSync as readdirSync27, readFileSync as readFileSync46 } from "fs";
|
|
30725
|
+
import { resolve as resolve39 } from "path";
|
|
30677
30726
|
function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
|
|
30678
30727
|
let entries2;
|
|
30679
30728
|
try {
|
|
@@ -30681,11 +30730,11 @@ function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
|
|
|
30681
30730
|
} catch {
|
|
30682
30731
|
return [];
|
|
30683
30732
|
}
|
|
30684
|
-
for (const
|
|
30685
|
-
const cfgPath =
|
|
30733
|
+
for (const entry2 of entries2) {
|
|
30734
|
+
const cfgPath = resolve39(accountsDir, entry2, "account.json");
|
|
30686
30735
|
if (!existsSync45(cfgPath)) continue;
|
|
30687
30736
|
try {
|
|
30688
|
-
const cfg = JSON.parse(
|
|
30737
|
+
const cfg = JSON.parse(readFileSync46(cfgPath, "utf-8"));
|
|
30689
30738
|
if (cfg.role !== "house") continue;
|
|
30690
30739
|
if (!Array.isArray(cfg.admins)) return [];
|
|
30691
30740
|
return cfg.admins.map(
|
|
@@ -31033,7 +31082,7 @@ init_claude_agent();
|
|
|
31033
31082
|
init_account();
|
|
31034
31083
|
init_neo4j_store();
|
|
31035
31084
|
init_src2();
|
|
31036
|
-
import { resolve as
|
|
31085
|
+
import { resolve as resolve40 } from "path";
|
|
31037
31086
|
var TAG49 = "[admin:routines]";
|
|
31038
31087
|
function scopeFor(d, houseDir, houseAccountId) {
|
|
31039
31088
|
if (d.channel !== "whatsapp") return null;
|
|
@@ -31063,7 +31112,7 @@ app67.get("/", requireAdminSession, async (c) => {
|
|
|
31063
31112
|
console.error(`${TAG49} op=destinations account=${accountId.slice(0, 8)} outcome=house-unresolved reason="${message}"`);
|
|
31064
31113
|
}
|
|
31065
31114
|
const destinations = listDispatchDestinations({ accountsDir: ACCOUNTS_DIR, houseAccountId, accountId });
|
|
31066
|
-
const houseDir = houseAccountId === null ? null :
|
|
31115
|
+
const houseDir = houseAccountId === null ? null : resolve40(ACCOUNTS_DIR, houseAccountId);
|
|
31067
31116
|
const byScope = /* @__PURE__ */ new Map();
|
|
31068
31117
|
for (const d of destinations) {
|
|
31069
31118
|
const scope = houseDir === null || houseAccountId === null ? null : scopeFor(d, houseDir, houseAccountId);
|
|
@@ -31413,9 +31462,9 @@ init_dist();
|
|
|
31413
31462
|
|
|
31414
31463
|
// app/lib/access-email.ts
|
|
31415
31464
|
import { spawn as spawn2 } from "child_process";
|
|
31416
|
-
import { resolve as
|
|
31417
|
-
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ??
|
|
31418
|
-
var SEND_SCRIPT =
|
|
31465
|
+
import { resolve as resolve41 } from "path";
|
|
31466
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve41(process.cwd(), "..");
|
|
31467
|
+
var SEND_SCRIPT = resolve41(
|
|
31419
31468
|
PLATFORM_ROOT5,
|
|
31420
31469
|
"plugins",
|
|
31421
31470
|
"email",
|
|
@@ -31559,8 +31608,8 @@ var access_default = app72;
|
|
|
31559
31608
|
// server/routes/sites.ts
|
|
31560
31609
|
init_dist();
|
|
31561
31610
|
init_claude_agent();
|
|
31562
|
-
import { existsSync as existsSync46, readFileSync as
|
|
31563
|
-
import { resolve as
|
|
31611
|
+
import { existsSync as existsSync46, readFileSync as readFileSync47, realpathSync as realpathSync8, statSync as statSync25 } from "fs";
|
|
31612
|
+
import { resolve as resolve42 } from "path";
|
|
31564
31613
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
31565
31614
|
var MIME = {
|
|
31566
31615
|
".html": "text/html; charset=utf-8",
|
|
@@ -31617,8 +31666,8 @@ app73.get("/:rel{.*}", (c) => {
|
|
|
31617
31666
|
}
|
|
31618
31667
|
segments.push(seg);
|
|
31619
31668
|
}
|
|
31620
|
-
const rootDir =
|
|
31621
|
-
let filePath = segments.length === 0 ? rootDir :
|
|
31669
|
+
const rootDir = resolve42(account.accountDir, "sites");
|
|
31670
|
+
let filePath = segments.length === 0 ? rootDir : resolve42(rootDir, ...segments);
|
|
31622
31671
|
if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
|
|
31623
31672
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
31624
31673
|
return c.text("Forbidden", 403);
|
|
@@ -31638,7 +31687,7 @@ app73.get("/:rel{.*}", (c) => {
|
|
|
31638
31687
|
return c.redirect(target, 301);
|
|
31639
31688
|
}
|
|
31640
31689
|
if (stat11?.isDirectory()) {
|
|
31641
|
-
filePath =
|
|
31690
|
+
filePath = resolve42(filePath, "index.html");
|
|
31642
31691
|
}
|
|
31643
31692
|
if (!filePath.startsWith(rootDir + "/")) {
|
|
31644
31693
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
@@ -31663,7 +31712,7 @@ app73.get("/:rel{.*}", (c) => {
|
|
|
31663
31712
|
}
|
|
31664
31713
|
let body;
|
|
31665
31714
|
try {
|
|
31666
|
-
body =
|
|
31715
|
+
body = readFileSync47(realPath);
|
|
31667
31716
|
} catch (err) {
|
|
31668
31717
|
const code = err?.code;
|
|
31669
31718
|
if (code === "EISDIR") {
|
|
@@ -31705,7 +31754,7 @@ init_neo4j_store();
|
|
|
31705
31754
|
// app/lib/visitor-token.ts
|
|
31706
31755
|
init_paths();
|
|
31707
31756
|
import { createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
31708
|
-
import { mkdirSync as mkdirSync9, readFileSync as
|
|
31757
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync48, writeFileSync as writeFileSync17 } from "fs";
|
|
31709
31758
|
import { dirname as dirname16 } from "path";
|
|
31710
31759
|
var TOKEN_PREFIX = "v1.";
|
|
31711
31760
|
var SECRET_BYTES = 32;
|
|
@@ -31714,7 +31763,7 @@ var cachedSecret = null;
|
|
|
31714
31763
|
function getSecret() {
|
|
31715
31764
|
if (cachedSecret) return cachedSecret;
|
|
31716
31765
|
try {
|
|
31717
|
-
const hex2 =
|
|
31766
|
+
const hex2 = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
31718
31767
|
if (hex2.length === SECRET_BYTES * 2) {
|
|
31719
31768
|
cachedSecret = Buffer.from(hex2, "hex");
|
|
31720
31769
|
return cachedSecret;
|
|
@@ -31728,7 +31777,7 @@ function getSecret() {
|
|
|
31728
31777
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
31729
31778
|
} catch {
|
|
31730
31779
|
}
|
|
31731
|
-
const hex =
|
|
31780
|
+
const hex = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
31732
31781
|
cachedSecret = Buffer.from(hex, "hex");
|
|
31733
31782
|
return cachedSecret;
|
|
31734
31783
|
}
|
|
@@ -31784,7 +31833,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
|
31784
31833
|
init_dist();
|
|
31785
31834
|
|
|
31786
31835
|
// app/lib/brand-config.ts
|
|
31787
|
-
import { existsSync as existsSync47, readFileSync as
|
|
31836
|
+
import { existsSync as existsSync47, readFileSync as readFileSync49 } from "fs";
|
|
31788
31837
|
import { join as join53 } from "path";
|
|
31789
31838
|
var cached2 = null;
|
|
31790
31839
|
var cachedAttempted = false;
|
|
@@ -31796,7 +31845,7 @@ function readBrandConfig() {
|
|
|
31796
31845
|
const brandPath = join53(platformRoot5, "config", "brand.json");
|
|
31797
31846
|
if (!existsSync47(brandPath)) return null;
|
|
31798
31847
|
try {
|
|
31799
|
-
cached2 = JSON.parse(
|
|
31848
|
+
cached2 = JSON.parse(readFileSync49(brandPath, "utf-8"));
|
|
31800
31849
|
return cached2;
|
|
31801
31850
|
} catch {
|
|
31802
31851
|
return null;
|
|
@@ -32075,9 +32124,9 @@ function originAllowed(origin, allowlist) {
|
|
|
32075
32124
|
try {
|
|
32076
32125
|
const u = new URL(origin);
|
|
32077
32126
|
const host = u.host;
|
|
32078
|
-
return allowlist.some((
|
|
32079
|
-
if (
|
|
32080
|
-
if (
|
|
32127
|
+
return allowlist.some((entry2) => {
|
|
32128
|
+
if (entry2 === host) return true;
|
|
32129
|
+
if (entry2.startsWith("*.")) return host.endsWith(entry2.slice(1));
|
|
32081
32130
|
return false;
|
|
32082
32131
|
});
|
|
32083
32132
|
} catch {
|
|
@@ -32299,16 +32348,16 @@ var visitor_event_default = app76;
|
|
|
32299
32348
|
// server/routes/session.ts
|
|
32300
32349
|
init_dist();
|
|
32301
32350
|
init_claude_agent();
|
|
32302
|
-
import { resolve as
|
|
32351
|
+
import { resolve as resolve43 } from "path";
|
|
32303
32352
|
import { existsSync as existsSync48, writeFileSync as writeFileSync18, mkdirSync as mkdirSync10 } from "fs";
|
|
32304
32353
|
init_neo4j_store();
|
|
32305
32354
|
init_paths();
|
|
32306
32355
|
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
32356
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
32308
32357
|
try {
|
|
32309
|
-
const cacheDir =
|
|
32358
|
+
const cacheDir = resolve43(MAXY_DIR, "branding-cache", accountId);
|
|
32310
32359
|
mkdirSync10(cacheDir, { recursive: true });
|
|
32311
|
-
writeFileSync18(
|
|
32360
|
+
writeFileSync18(resolve43(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
32312
32361
|
} catch (err) {
|
|
32313
32362
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
32314
32363
|
}
|
|
@@ -32389,8 +32438,8 @@ app77.post("/", async (c) => {
|
|
|
32389
32438
|
}
|
|
32390
32439
|
let agentConfig = null;
|
|
32391
32440
|
if (account) {
|
|
32392
|
-
const agentDir =
|
|
32393
|
-
if (!existsSync48(agentDir) || !existsSync48(
|
|
32441
|
+
const agentDir = resolve43(account.accountDir, "agents", agentSlug);
|
|
32442
|
+
if (!existsSync48(agentDir) || !existsSync48(resolve43(agentDir, "config.json"))) {
|
|
32394
32443
|
return c.json({ error: "Agent not found" }, 404);
|
|
32395
32444
|
}
|
|
32396
32445
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -32574,7 +32623,7 @@ var calendar_public_default = app78;
|
|
|
32574
32623
|
init_dist();
|
|
32575
32624
|
import { createReadStream as createReadStream3 } from "fs";
|
|
32576
32625
|
import { stat as stat8, readFile as readFile11, realpath } from "fs/promises";
|
|
32577
|
-
import { join as join54, resolve as
|
|
32626
|
+
import { join as join54, resolve as resolve44, sep as sep11 } from "path";
|
|
32578
32627
|
import { Readable as Readable3 } from "stream";
|
|
32579
32628
|
init_data_path();
|
|
32580
32629
|
init_attachments();
|
|
@@ -32629,7 +32678,7 @@ ${expiresAtMs}`)
|
|
|
32629
32678
|
return diff2 === 0;
|
|
32630
32679
|
}
|
|
32631
32680
|
async function readAccountSecret(accountId) {
|
|
32632
|
-
const path =
|
|
32681
|
+
const path = resolve44(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
|
|
32633
32682
|
let text;
|
|
32634
32683
|
try {
|
|
32635
32684
|
text = await readFile11(path, "utf8");
|
|
@@ -32665,7 +32714,7 @@ async function resolveFetchTarget(accountDir, relPath, ownerId) {
|
|
|
32665
32714
|
const exposeFolders = await readExposeFolders2(accountDir);
|
|
32666
32715
|
const { exposed } = resolveExposedDirs(schemaText, exposeFolders);
|
|
32667
32716
|
if (exposed.length === 0 && !ownerId) return { ok: false, reason: "not-exposed" };
|
|
32668
|
-
const lexical =
|
|
32717
|
+
const lexical = resolve44(accountDir, relPath);
|
|
32669
32718
|
let root;
|
|
32670
32719
|
let absolute;
|
|
32671
32720
|
try {
|
|
@@ -32714,7 +32763,7 @@ async function handle(c, headOnly) {
|
|
|
32714
32763
|
return c.json({ ok: false, error: "denied" }, 401);
|
|
32715
32764
|
}
|
|
32716
32765
|
const target = await resolveFetchTarget(
|
|
32717
|
-
|
|
32766
|
+
resolve44(DATA_ROOT, "accounts", accountId),
|
|
32718
32767
|
relPath,
|
|
32719
32768
|
owner || void 0
|
|
32720
32769
|
);
|
|
@@ -32836,7 +32885,7 @@ init_neo4j_store();
|
|
|
32836
32885
|
|
|
32837
32886
|
// app/lib/ledger-census.ts
|
|
32838
32887
|
init_loop_registry();
|
|
32839
|
-
import { readdirSync as readdirSync29, readFileSync as
|
|
32888
|
+
import { readdirSync as readdirSync29, readFileSync as readFileSync50, statSync as statSync26 } from "fs";
|
|
32840
32889
|
import { join as join55 } from "path";
|
|
32841
32890
|
|
|
32842
32891
|
// ../lib/ledger-core/dist/reconcile.js
|
|
@@ -32894,7 +32943,7 @@ function countWriteRejects24h(logDir, now) {
|
|
|
32894
32943
|
const path = join55(logDir, name);
|
|
32895
32944
|
try {
|
|
32896
32945
|
if (now - statSync26(path).mtimeMs > DAY_MS) continue;
|
|
32897
|
-
for (const line of
|
|
32946
|
+
for (const line of readFileSync50(path, "utf8").split("\n")) {
|
|
32898
32947
|
if (line.includes("[graph-write] reject")) total += 1;
|
|
32899
32948
|
}
|
|
32900
32949
|
} catch {
|
|
@@ -32997,7 +33046,7 @@ function startLedgerCensus(openSession, opts = {}) {
|
|
|
32997
33046
|
// app/lib/dispatch-census.ts
|
|
32998
33047
|
init_loop_registry();
|
|
32999
33048
|
init_account();
|
|
33000
|
-
import { readFileSync as
|
|
33049
|
+
import { readFileSync as readFileSync51 } from "fs";
|
|
33001
33050
|
import { join as join56 } from "path";
|
|
33002
33051
|
var CENSUS_TAG2 = "[dispatch-census]";
|
|
33003
33052
|
var CONFIG_AUDIT_TAG = "[dispatch-config-audit]";
|
|
@@ -33031,7 +33080,7 @@ function percentile(sortedAsc, p) {
|
|
|
33031
33080
|
function auditConfigFile(accountDir) {
|
|
33032
33081
|
let cfg;
|
|
33033
33082
|
try {
|
|
33034
|
-
cfg = JSON.parse(
|
|
33083
|
+
cfg = JSON.parse(readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8"));
|
|
33035
33084
|
} catch {
|
|
33036
33085
|
return null;
|
|
33037
33086
|
}
|
|
@@ -33056,7 +33105,7 @@ function auditConfigFile(accountDir) {
|
|
|
33056
33105
|
function terminalStatuses(accountDir) {
|
|
33057
33106
|
try {
|
|
33058
33107
|
const cfg = JSON.parse(
|
|
33059
|
-
|
|
33108
|
+
readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8")
|
|
33060
33109
|
);
|
|
33061
33110
|
return {
|
|
33062
33111
|
job: (cfg.statuses?.job ?? []).filter((s) => s.terminal).map((s) => s.name),
|
|
@@ -33493,7 +33542,7 @@ function startSharedFolderCensus() {
|
|
|
33493
33542
|
// app/lib/file-watcher.ts
|
|
33494
33543
|
init_data_path();
|
|
33495
33544
|
import * as fsp2 from "fs/promises";
|
|
33496
|
-
import { resolve as
|
|
33545
|
+
import { resolve as resolve45, sep as sep12 } from "path";
|
|
33497
33546
|
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
33547
|
var DEFAULT_COALESCE_MS = 500;
|
|
33499
33548
|
var ROOTS = ["accounts"];
|
|
@@ -33512,7 +33561,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
33512
33561
|
const dropFn = opts.drop ?? dropFileIndex;
|
|
33513
33562
|
for (const r of ROOTS) {
|
|
33514
33563
|
try {
|
|
33515
|
-
await fsp2.mkdir(
|
|
33564
|
+
await fsp2.mkdir(resolve45(dataRoot, r), { recursive: true });
|
|
33516
33565
|
} catch (err) {
|
|
33517
33566
|
console.error(
|
|
33518
33567
|
`[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
|
|
@@ -33533,7 +33582,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
33533
33582
|
timers.set(relativePath, t);
|
|
33534
33583
|
}
|
|
33535
33584
|
async function runHook(relativePath, accountId) {
|
|
33536
|
-
const absolute =
|
|
33585
|
+
const absolute = resolve45(dataRoot, relativePath);
|
|
33537
33586
|
let exists = false;
|
|
33538
33587
|
try {
|
|
33539
33588
|
const st = await fsp2.stat(absolute);
|
|
@@ -33557,7 +33606,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
33557
33606
|
}
|
|
33558
33607
|
}
|
|
33559
33608
|
async function watchRoot(rootName) {
|
|
33560
|
-
const absRoot =
|
|
33609
|
+
const absRoot = resolve45(dataRoot, rootName);
|
|
33561
33610
|
try {
|
|
33562
33611
|
const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
|
|
33563
33612
|
for await (const event of iter) {
|
|
@@ -33599,7 +33648,7 @@ init_data_path();
|
|
|
33599
33648
|
init_neo4j_store();
|
|
33600
33649
|
init_account();
|
|
33601
33650
|
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
|
|
33651
|
+
import { dirname as dirname17, relative as relative8, resolve as resolve46 } from "path";
|
|
33603
33652
|
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
33653
|
async function walkFiles(dir) {
|
|
33605
33654
|
const out = [];
|
|
@@ -33610,7 +33659,7 @@ async function walkFiles(dir) {
|
|
|
33610
33659
|
return out;
|
|
33611
33660
|
}
|
|
33612
33661
|
for (const e of entries2) {
|
|
33613
|
-
const abs =
|
|
33662
|
+
const abs = resolve46(dir, e.name);
|
|
33614
33663
|
if (e.isDirectory()) {
|
|
33615
33664
|
out.push(...await walkFiles(abs));
|
|
33616
33665
|
} else if (e.isFile()) {
|
|
@@ -33630,7 +33679,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
33630
33679
|
let moved = 0;
|
|
33631
33680
|
for (const oldAbs of files) {
|
|
33632
33681
|
const suffix = relative8(srcDir, oldAbs);
|
|
33633
|
-
const newAbs =
|
|
33682
|
+
const newAbs = resolve46(destDir, suffix);
|
|
33634
33683
|
await mkdir5(dirname17(newAbs), { recursive: true });
|
|
33635
33684
|
await rename4(oldAbs, newAbs);
|
|
33636
33685
|
moved++;
|
|
@@ -33657,7 +33706,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
33657
33706
|
}
|
|
33658
33707
|
async function migrateUploads(opts = {}) {
|
|
33659
33708
|
const dataRoot = opts.dataRoot ?? DATA_ROOT;
|
|
33660
|
-
const oldRoot =
|
|
33709
|
+
const oldRoot = resolve46(dataRoot, "uploads");
|
|
33661
33710
|
let topEntries;
|
|
33662
33711
|
try {
|
|
33663
33712
|
topEntries = await readdir6(oldRoot, { withFileTypes: true });
|
|
@@ -33674,12 +33723,12 @@ async function migrateUploads(opts = {}) {
|
|
|
33674
33723
|
let moved = 0;
|
|
33675
33724
|
let skipped = 0;
|
|
33676
33725
|
try {
|
|
33677
|
-
for (const
|
|
33678
|
-
const name =
|
|
33726
|
+
for (const entry2 of topEntries) {
|
|
33727
|
+
const name = entry2.name;
|
|
33679
33728
|
if (ACCOUNT_UUID_RE4.test(name)) {
|
|
33680
33729
|
moved += await relocateTree(
|
|
33681
|
-
|
|
33682
|
-
|
|
33730
|
+
resolve46(oldRoot, name),
|
|
33731
|
+
resolve46(dataRoot, "accounts", name, "uploads"),
|
|
33683
33732
|
dataRoot,
|
|
33684
33733
|
name,
|
|
33685
33734
|
session
|
|
@@ -33691,8 +33740,8 @@ async function migrateUploads(opts = {}) {
|
|
|
33691
33740
|
continue;
|
|
33692
33741
|
}
|
|
33693
33742
|
moved += await relocateTree(
|
|
33694
|
-
|
|
33695
|
-
|
|
33743
|
+
resolve46(oldRoot, "public"),
|
|
33744
|
+
resolve46(dataRoot, "accounts", installAccountId, "uploads", "public"),
|
|
33696
33745
|
dataRoot,
|
|
33697
33746
|
null,
|
|
33698
33747
|
// public uploads carry no graph nodes
|
|
@@ -33779,11 +33828,11 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
33779
33828
|
} catch {
|
|
33780
33829
|
continue;
|
|
33781
33830
|
}
|
|
33782
|
-
for (const
|
|
33783
|
-
if (
|
|
33784
|
-
out.push(join58(slugDir,
|
|
33785
|
-
} else if (
|
|
33786
|
-
const subDir = join58(slugDir,
|
|
33831
|
+
for (const entry2 of entries2) {
|
|
33832
|
+
if (entry2.isFile() && SESSION_META_RE.test(entry2.name)) {
|
|
33833
|
+
out.push(join58(slugDir, entry2.name));
|
|
33834
|
+
} else if (entry2.isDirectory() && entry2.name === "subagents") {
|
|
33835
|
+
const subDir = join58(slugDir, entry2.name);
|
|
33787
33836
|
let subs;
|
|
33788
33837
|
try {
|
|
33789
33838
|
subs = await readdir7(subDir, { withFileTypes: true });
|
|
@@ -33934,11 +33983,11 @@ async function collectSidecars(projectsRoot) {
|
|
|
33934
33983
|
} catch {
|
|
33935
33984
|
continue;
|
|
33936
33985
|
}
|
|
33937
|
-
for (const
|
|
33938
|
-
if (
|
|
33939
|
-
out.push({ path: join59(slugDir,
|
|
33940
|
-
} else if (
|
|
33941
|
-
const subDir = join59(slugDir,
|
|
33986
|
+
for (const entry2 of entries2) {
|
|
33987
|
+
if (entry2.isFile() && SESSION_META_RE2.test(entry2.name)) {
|
|
33988
|
+
out.push({ path: join59(slugDir, entry2.name), slug: slug.name });
|
|
33989
|
+
} else if (entry2.isDirectory() && (entry2.name === "subagents" || entry2.name === "archive")) {
|
|
33990
|
+
const subDir = join59(slugDir, entry2.name);
|
|
33942
33991
|
let subs;
|
|
33943
33992
|
try {
|
|
33944
33993
|
subs = await readdir8(subDir, { withFileTypes: true });
|
|
@@ -35326,12 +35375,20 @@ var InboundHub2 = class {
|
|
|
35326
35375
|
/** Task-962 — terminal-drop one still-queued in-flight message by id. Returns
|
|
35327
35376
|
* whether a matching queued entry was found and removed. */
|
|
35328
35377
|
dropQueued(key2, messageId) {
|
|
35378
|
+
return this.takeQueued(key2, messageId) !== null;
|
|
35379
|
+
}
|
|
35380
|
+
/** Task 2466 — remove one still-queued in-flight message by id and return its
|
|
35381
|
+
* payload, so the caller can re-route it. `dropQueued` discards; this hands
|
|
35382
|
+
* over, and the two share this one removal so a message can never be both
|
|
35383
|
+
* dropped and re-routed. Null when no matching queued entry exists — a
|
|
35384
|
+
* message that has already drained, acked, or been dropped is not takeable. */
|
|
35385
|
+
takeQueued(key2, messageId) {
|
|
35329
35386
|
const s = this.keys.get(key2);
|
|
35330
|
-
if (!s) return
|
|
35387
|
+
if (!s) return null;
|
|
35331
35388
|
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
|
|
35389
|
+
if (i < 0) return null;
|
|
35390
|
+
const [entry2] = s.inFlight.splice(i, 1);
|
|
35391
|
+
return entry2.payload;
|
|
35335
35392
|
}
|
|
35336
35393
|
/** Whether a key currently has a live channel server attached. The gateway
|
|
35337
35394
|
* uses this to decide whether an inbound needs a cold-start spawn/resume. */
|
|
@@ -35573,6 +35630,12 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35573
35630
|
/** Task-962 — `${key}::${messageId}` for messages a recovery call has already
|
|
35574
35631
|
* been issued for, so recovery is attempted at most once per message. */
|
|
35575
35632
|
recoveryAttempted = /* @__PURE__ */ new Set();
|
|
35633
|
+
/** Task 2465 — which session each conversation key's child is running. Written
|
|
35634
|
+
* at spawn (the admin id is decided before the manager call, so it is already
|
|
35635
|
+
* known there) and at turn-end (which carries key AND sessionId, so the map
|
|
35636
|
+
* survives a UI restart for any child that has completed a turn). Read only by
|
|
35637
|
+
* auditKeyOrphans. */
|
|
35638
|
+
keySession = /* @__PURE__ */ new Map();
|
|
35576
35639
|
/** Task-962 — guards against overlapping async recovery passes. */
|
|
35577
35640
|
recovering = false;
|
|
35578
35641
|
seq = 0;
|
|
@@ -35581,16 +35644,16 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35581
35644
|
* The public /api/chat route awaits this to return the reply on the same SSE
|
|
35582
35645
|
* response, preserving the pre-756 POST→single-blob browser contract. */
|
|
35583
35646
|
awaitReply(key2, timeoutMs) {
|
|
35584
|
-
return new Promise((
|
|
35647
|
+
return new Promise((resolve52) => {
|
|
35585
35648
|
const timer = setTimeout(() => {
|
|
35586
35649
|
this.replyAwaiters.delete(key2);
|
|
35587
|
-
|
|
35650
|
+
resolve52({ timeout: true });
|
|
35588
35651
|
}, timeoutMs);
|
|
35589
35652
|
if (timer.unref) timer.unref();
|
|
35590
35653
|
this.replyAwaiters.set(key2, (r) => {
|
|
35591
35654
|
clearTimeout(timer);
|
|
35592
35655
|
this.replyAwaiters.delete(key2);
|
|
35593
|
-
|
|
35656
|
+
resolve52(r);
|
|
35594
35657
|
});
|
|
35595
35658
|
});
|
|
35596
35659
|
}
|
|
@@ -35601,11 +35664,11 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35601
35664
|
* terminal dialog stays answerable). */
|
|
35602
35665
|
awaitPermissionVerdict(p, timeoutMs) {
|
|
35603
35666
|
const k = _WebchatGateway.promptKey(p.key, p.requestId);
|
|
35604
|
-
return new Promise((
|
|
35667
|
+
return new Promise((resolve52) => {
|
|
35605
35668
|
this.pendingPrompts.get(k)?.resolve({ timeout: true });
|
|
35606
35669
|
const timer = setTimeout(() => {
|
|
35607
35670
|
this.pendingPrompts.delete(k);
|
|
35608
|
-
|
|
35671
|
+
resolve52({ timeout: true });
|
|
35609
35672
|
}, timeoutMs);
|
|
35610
35673
|
if (timer.unref) timer.unref();
|
|
35611
35674
|
this.pendingPrompts.set(k, {
|
|
@@ -35615,7 +35678,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35615
35678
|
clearTimeout(timer);
|
|
35616
35679
|
this.pendingPrompts.delete(k);
|
|
35617
35680
|
this.deps.onPointerChange?.(p.key);
|
|
35618
|
-
|
|
35681
|
+
resolve52(r);
|
|
35619
35682
|
}
|
|
35620
35683
|
});
|
|
35621
35684
|
console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
|
|
@@ -35626,9 +35689,9 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35626
35689
|
* false (and is a no-op) when no prompt with this key+id is open — a late or
|
|
35627
35690
|
* duplicate click. */
|
|
35628
35691
|
resolvePermissionVerdict(key2, requestId, behavior) {
|
|
35629
|
-
const
|
|
35630
|
-
if (!
|
|
35631
|
-
|
|
35692
|
+
const entry2 = this.pendingPrompts.get(_WebchatGateway.promptKey(key2, requestId));
|
|
35693
|
+
if (!entry2) return false;
|
|
35694
|
+
entry2.resolve({ behavior });
|
|
35632
35695
|
console.error(`[webchat:perm] op=verdict key=${keyDisplay(key2)} id=${requestId} behavior=${behavior}`);
|
|
35633
35696
|
return true;
|
|
35634
35697
|
}
|
|
@@ -35771,6 +35834,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35771
35834
|
}
|
|
35772
35835
|
},
|
|
35773
35836
|
onTurnEnd: (key2, role, turn) => {
|
|
35837
|
+
this.keySession.set(key2, turn.sessionId);
|
|
35774
35838
|
if (role === "admin") {
|
|
35775
35839
|
this.lastDeliveredAt.delete(key2);
|
|
35776
35840
|
return "none";
|
|
@@ -35801,6 +35865,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35801
35865
|
});
|
|
35802
35866
|
for (const e of events) {
|
|
35803
35867
|
console.error(`[webchat:inbound] op=${e.kind} key=${e.key} messageId=${e.messageId} ageMs=${e.ageMs}`);
|
|
35868
|
+
this.deliveryFailures.set(e.key, { messageId: e.messageId, at: now });
|
|
35804
35869
|
}
|
|
35805
35870
|
return events;
|
|
35806
35871
|
}
|
|
@@ -35830,6 +35895,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35830
35895
|
run: () => {
|
|
35831
35896
|
const now = Date.now();
|
|
35832
35897
|
const events = this.reconcile(now);
|
|
35898
|
+
this.auditKeyOrphans(now);
|
|
35833
35899
|
void this.recoverStalled(now);
|
|
35834
35900
|
return events.length > 0 ? `${events.length} terminal events` : void 0;
|
|
35835
35901
|
}
|
|
@@ -35846,6 +35912,60 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35846
35912
|
clearDeliveryFailure(key2) {
|
|
35847
35913
|
this.deliveryFailures.delete(key2);
|
|
35848
35914
|
}
|
|
35915
|
+
/** Task 2465 — record which session a conversation key's child is running.
|
|
35916
|
+
* Public so the audit below can be driven directly in tests without standing
|
|
35917
|
+
* up the channel-server HTTP surface; production writes come from runSpawn and
|
|
35918
|
+
* the turn-end handler. */
|
|
35919
|
+
noteKeySession(key2, sessionId) {
|
|
35920
|
+
this.keySession.set(key2, sessionId);
|
|
35921
|
+
}
|
|
35922
|
+
/** Task 2465 — STANDING RECONCILIATION: a queued message on a key no live child
|
|
35923
|
+
* subscribes to, while the session that key names IS live under a different
|
|
35924
|
+
* key. Expected against actual, on the sweep tick.
|
|
35925
|
+
*
|
|
35926
|
+
* There is no action to hang this on and re-running the send reproduces the
|
|
35927
|
+
* same silence, which is why it is an audit rather than another action log.
|
|
35928
|
+
* Before this, the state emitted nothing until the 30s recovery tick, and the
|
|
35929
|
+
* queued key and the live key never appeared on one line anywhere in
|
|
35930
|
+
* server.log — the reason the 2026-08-06 incident read as a wedge throughout.
|
|
35931
|
+
* `liveKey` is the whole diagnosis, and it fires on the FIRST occurrence.
|
|
35932
|
+
*
|
|
35933
|
+
* Covers: a queued key with no subscriber whose named session is held by a
|
|
35934
|
+
* different attached key. Does NOT cover a live child this process neither
|
|
35935
|
+
* spawned nor saw finish a turn — it is absent from keySession, and this is
|
|
35936
|
+
* silent for it. Silent when the keys agree, which is every healthy send.
|
|
35937
|
+
*
|
|
35938
|
+
* Public for the same reason noteKeySession is.
|
|
35939
|
+
*
|
|
35940
|
+
* Task 2466 — the live-key scan moved to sessionSubscriberKey, which the
|
|
35941
|
+
* recovery pass also reads. The audit's own behaviour is unchanged. */
|
|
35942
|
+
auditKeyOrphans(now) {
|
|
35943
|
+
for (const c of this.hub.staleQueued(now, 0)) {
|
|
35944
|
+
const sessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key);
|
|
35945
|
+
if (!sessionId) continue;
|
|
35946
|
+
const liveKey = this.sessionSubscriberKey(sessionId);
|
|
35947
|
+
if (liveKey === null || liveKey === c.key) continue;
|
|
35948
|
+
console.error(
|
|
35949
|
+
`[webchat:inbound] op=key-orphan key=${c.key} messageId=${c.messageId} ageMs=${c.ageMs} liveKey=${liveKey} sessionId=${sessionId.slice(0, 8)}`
|
|
35950
|
+
);
|
|
35951
|
+
}
|
|
35952
|
+
}
|
|
35953
|
+
/** Task 2466 — the conversation key whose channel server is attached right now
|
|
35954
|
+
* and which this gateway records as serving `sessionId`, or null when no such
|
|
35955
|
+
* key exists. The one place "is this session reachable over its channel" is
|
|
35956
|
+
* decided; the orphan audit above and the recovery pass below both read it.
|
|
35957
|
+
*
|
|
35958
|
+
* Both readers are recovery-shaped, so the caller always holds a key with no
|
|
35959
|
+
* subscriber and this can never return that same key. Blind, by construction,
|
|
35960
|
+
* to a live child this process neither spawned nor saw finish a turn: it is
|
|
35961
|
+
* absent from keySession, and the answer is null. */
|
|
35962
|
+
sessionSubscriberKey(sessionId) {
|
|
35963
|
+
for (const [liveKey, liveSession] of this.keySession) {
|
|
35964
|
+
if (liveSession !== sessionId) continue;
|
|
35965
|
+
if (this.hub.hasSubscriber(liveKey)) return liveKey;
|
|
35966
|
+
}
|
|
35967
|
+
return null;
|
|
35968
|
+
}
|
|
35849
35969
|
/** Task-962 — recovery-aware pass over queued-no-subscriber messages.
|
|
35850
35970
|
* - age in [RECOVER_TRIGGER_MS, DELIVER_FINAL_MS): ask the manager to recover
|
|
35851
35971
|
* the live session ONCE. busy → leave queued for the channel server's
|
|
@@ -35874,15 +35994,19 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35874
35994
|
const d = this.recoverable.get(c.key);
|
|
35875
35995
|
if (!d || !this.deps.recoverLiveSession) {
|
|
35876
35996
|
this.hub.dropQueued(c.key, c.messageId);
|
|
35997
|
+
this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
|
|
35877
35998
|
console.error(
|
|
35878
35999
|
`[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=no-descriptor`
|
|
35879
36000
|
);
|
|
35880
36001
|
continue;
|
|
35881
36002
|
}
|
|
35882
36003
|
this.recoveryAttempted.add(attemptKey);
|
|
36004
|
+
const recoverSessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key) ?? d.sessionId;
|
|
36005
|
+
const liveKey = recoverSessionId === void 0 ? null : this.sessionSubscriberKey(recoverSessionId);
|
|
36006
|
+
const subscriberAttached = liveKey !== null;
|
|
35883
36007
|
let r;
|
|
35884
36008
|
try {
|
|
35885
|
-
r = await this.deps.recoverLiveSession(d);
|
|
36009
|
+
r = await this.deps.recoverLiveSession({ ...d, subscriberAttached });
|
|
35886
36010
|
} catch (err) {
|
|
35887
36011
|
console.error(
|
|
35888
36012
|
`[webchat:inbound] op=recover-error key=${keyDisplay(c.key)} messageId=${c.messageId} err=${err instanceof Error ? err.message : String(err)}`
|
|
@@ -35892,6 +36016,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35892
36016
|
if (!r.live) {
|
|
35893
36017
|
this.hub.dropQueued(c.key, c.messageId);
|
|
35894
36018
|
this.recoveryAttempted.delete(attemptKey);
|
|
36019
|
+
this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
|
|
35895
36020
|
console.error(
|
|
35896
36021
|
`[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=not-live`
|
|
35897
36022
|
);
|
|
@@ -35899,6 +36024,16 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35899
36024
|
console.error(
|
|
35900
36025
|
`[webchat:inbound] op=recover key=${keyDisplay(c.key)} messageId=${c.messageId} classification=${r.classification ?? "unknown"} respawned=${r.recovered === true}`
|
|
35901
36026
|
);
|
|
36027
|
+
if (r.classification === "busy" && liveKey !== null && liveKey !== c.key) {
|
|
36028
|
+
const handed = this.hub.takeQueued(c.key, c.messageId);
|
|
36029
|
+
if (handed) {
|
|
36030
|
+
this.recoveryAttempted.delete(attemptKey);
|
|
36031
|
+
this.hub.deliver({ ...handed, key: liveKey }, now);
|
|
36032
|
+
console.error(
|
|
36033
|
+
`[webchat:inbound] op=recover-redeliver key=${c.key} liveKey=${liveKey} messageId=${c.messageId} ageMs=${c.ageMs}`
|
|
36034
|
+
);
|
|
36035
|
+
}
|
|
36036
|
+
}
|
|
35902
36037
|
}
|
|
35903
36038
|
}
|
|
35904
36039
|
for (const key2 of this.recoverable.keys()) {
|
|
@@ -35963,6 +36098,10 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
35963
36098
|
accountId: input.accountId,
|
|
35964
36099
|
key: input.key,
|
|
35965
36100
|
role: input.role,
|
|
36101
|
+
// Task 2466 — a template value. Reachability is a fact about NOW, not
|
|
36102
|
+
// about when the message arrived, so recoverStalled re-stamps it on the
|
|
36103
|
+
// copy it hands to the manager.
|
|
36104
|
+
subscriberAttached: false,
|
|
35966
36105
|
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
35967
36106
|
...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {}
|
|
35968
36107
|
});
|
|
@@ -36011,6 +36150,9 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
36011
36150
|
lastActivityAt: Date.now()
|
|
36012
36151
|
});
|
|
36013
36152
|
}
|
|
36153
|
+
if (input.role === "admin" && input.sessionId !== void 0) {
|
|
36154
|
+
this.keySession.set(input.key, input.sessionId);
|
|
36155
|
+
}
|
|
36014
36156
|
} catch (err) {
|
|
36015
36157
|
const reason = (err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ");
|
|
36016
36158
|
console.error(
|
|
@@ -36023,7 +36165,60 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
36023
36165
|
}
|
|
36024
36166
|
};
|
|
36025
36167
|
|
|
36026
|
-
// app/lib/channel-
|
|
36168
|
+
// app/lib/channel-delivery/answer-accumulator.ts
|
|
36169
|
+
var AnswerAccumulator = class {
|
|
36170
|
+
openId = "";
|
|
36171
|
+
buf = "";
|
|
36172
|
+
open = false;
|
|
36173
|
+
emitted = false;
|
|
36174
|
+
/** Feed one JSONL line. Returns every answer completed by this line, in order:
|
|
36175
|
+
* at most the previous message's, then this one's. */
|
|
36176
|
+
push(line) {
|
|
36177
|
+
if (!line) return [];
|
|
36178
|
+
let row;
|
|
36179
|
+
try {
|
|
36180
|
+
row = JSON.parse(line);
|
|
36181
|
+
} catch {
|
|
36182
|
+
return [];
|
|
36183
|
+
}
|
|
36184
|
+
if (row.type !== "assistant") return this.complete();
|
|
36185
|
+
const msg = row.message;
|
|
36186
|
+
const id = typeof msg?.id === "string" ? msg.id : "";
|
|
36187
|
+
const out = [];
|
|
36188
|
+
if (!this.open || id !== this.openId) {
|
|
36189
|
+
out.push(...this.complete());
|
|
36190
|
+
this.openId = id;
|
|
36191
|
+
this.buf = "";
|
|
36192
|
+
this.open = true;
|
|
36193
|
+
this.emitted = false;
|
|
36194
|
+
}
|
|
36195
|
+
if (Array.isArray(msg?.content)) {
|
|
36196
|
+
for (const block of msg.content) {
|
|
36197
|
+
if (block?.type === "text" && typeof block.text === "string") this.buf += block.text;
|
|
36198
|
+
}
|
|
36199
|
+
}
|
|
36200
|
+
if (!this.emitted && msg?.stop_reason === "end_turn" && this.buf.trim().length > 0) {
|
|
36201
|
+
this.emitted = true;
|
|
36202
|
+
out.push({ messageId: this.openId, text: this.buf });
|
|
36203
|
+
}
|
|
36204
|
+
return out;
|
|
36205
|
+
}
|
|
36206
|
+
/** Complete any open message. Called when the stream ends. */
|
|
36207
|
+
flush() {
|
|
36208
|
+
return this.complete();
|
|
36209
|
+
}
|
|
36210
|
+
complete() {
|
|
36211
|
+
if (!this.open) return [];
|
|
36212
|
+
const answer = !this.emitted && this.buf.trim().length > 0 ? { messageId: this.openId, text: this.buf } : null;
|
|
36213
|
+
this.open = false;
|
|
36214
|
+
this.openId = "";
|
|
36215
|
+
this.buf = "";
|
|
36216
|
+
this.emitted = false;
|
|
36217
|
+
return answer ? [answer] : [];
|
|
36218
|
+
}
|
|
36219
|
+
};
|
|
36220
|
+
|
|
36221
|
+
// app/lib/channel-delivery/emitter.ts
|
|
36027
36222
|
function followerPendingMaxMs() {
|
|
36028
36223
|
return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
|
|
36029
36224
|
}
|
|
@@ -36037,19 +36232,31 @@ function toolResultText(content) {
|
|
|
36037
36232
|
(b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : ""
|
|
36038
36233
|
).join("");
|
|
36039
36234
|
}
|
|
36040
|
-
function
|
|
36235
|
+
function startEmitter(opts) {
|
|
36041
36236
|
const abort2 = new AbortController();
|
|
36042
|
-
const { entry, tag } = opts;
|
|
36237
|
+
const { entry: entry2, tag } = opts;
|
|
36238
|
+
const answers = new AnswerAccumulator();
|
|
36239
|
+
const emitAnswer = async (answer) => {
|
|
36240
|
+
if (answer.text.trim()) await fanOut2(entry2.subscribers, answer.text, opts.onError, tag);
|
|
36241
|
+
if (!opts.onAnswer) return;
|
|
36242
|
+
try {
|
|
36243
|
+
await opts.onAnswer(answer);
|
|
36244
|
+
} catch (err) {
|
|
36245
|
+
console.error(
|
|
36246
|
+
`${tag} answer-error sessionId=${entry2.sessionId.slice(0, 8)} messageId=${answer.messageId} message=${err instanceof Error ? err.message : String(err)}`
|
|
36247
|
+
);
|
|
36248
|
+
}
|
|
36249
|
+
};
|
|
36043
36250
|
void (async () => {
|
|
36044
36251
|
try {
|
|
36045
|
-
const sid =
|
|
36252
|
+
const sid = entry2.sessionId.slice(0, 8);
|
|
36046
36253
|
const deadline = Date.now() + followerPendingMaxMs();
|
|
36047
36254
|
const retryMs = followerRetryMs();
|
|
36048
36255
|
let res;
|
|
36049
36256
|
let attempt = 0;
|
|
36050
36257
|
for (; ; ) {
|
|
36051
36258
|
res = await fetch(
|
|
36052
|
-
managerLogFollowUrl(
|
|
36259
|
+
managerLogFollowUrl(entry2.sessionId, { boundary: opts.suppressResumeReplay === true }),
|
|
36053
36260
|
{ signal: abort2.signal }
|
|
36054
36261
|
);
|
|
36055
36262
|
console.error(`${tag} follower-connect sessionId=${sid} status=${res.status}`);
|
|
@@ -36083,7 +36290,7 @@ function startFollower(opts) {
|
|
|
36083
36290
|
const awaitingVerdict = /* @__PURE__ */ new Set();
|
|
36084
36291
|
let firedFileTools = [];
|
|
36085
36292
|
let suppressing = opts.suppressResumeReplay === true;
|
|
36086
|
-
let
|
|
36293
|
+
let discardedAnswers = 0;
|
|
36087
36294
|
let discardedFileTools = 0;
|
|
36088
36295
|
while (!abort2.signal.aborted) {
|
|
36089
36296
|
const { value, done } = await reader.read();
|
|
@@ -36100,7 +36307,7 @@ function startFollower(opts) {
|
|
|
36100
36307
|
event = JSON.parse(line);
|
|
36101
36308
|
} catch (err) {
|
|
36102
36309
|
console.error(
|
|
36103
|
-
`${tag} jsonl-parse-skip sessionId=${
|
|
36310
|
+
`${tag} jsonl-parse-skip sessionId=${entry2.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
|
|
36104
36311
|
);
|
|
36105
36312
|
continue;
|
|
36106
36313
|
}
|
|
@@ -36108,11 +36315,18 @@ function startFollower(opts) {
|
|
|
36108
36315
|
if (suppressing) {
|
|
36109
36316
|
suppressing = false;
|
|
36110
36317
|
console.error(
|
|
36111
|
-
`${tag}
|
|
36318
|
+
`${tag} emitter-resume-boundary sessionId=${sid} discarded-head-answers=${discardedAnswers} discarded-head-filetools=${discardedFileTools}`
|
|
36112
36319
|
);
|
|
36113
36320
|
}
|
|
36114
36321
|
continue;
|
|
36115
36322
|
}
|
|
36323
|
+
for (const answer of answers.push(line)) {
|
|
36324
|
+
if (suppressing) {
|
|
36325
|
+
discardedAnswers += 1;
|
|
36326
|
+
continue;
|
|
36327
|
+
}
|
|
36328
|
+
await emitAnswer(answer);
|
|
36329
|
+
}
|
|
36116
36330
|
if (event.type === "user") {
|
|
36117
36331
|
if (captureVerdicts && awaitingVerdict.size > 0 && !suppressing) {
|
|
36118
36332
|
const umsg = event.message;
|
|
@@ -36136,7 +36350,6 @@ function startFollower(opts) {
|
|
|
36136
36350
|
}
|
|
36137
36351
|
}
|
|
36138
36352
|
}
|
|
36139
|
-
entry.pendingTurnText = "";
|
|
36140
36353
|
firedFileTools = [];
|
|
36141
36354
|
continue;
|
|
36142
36355
|
}
|
|
@@ -36147,7 +36360,6 @@ function startFollower(opts) {
|
|
|
36147
36360
|
if (Array.isArray(msg.content)) {
|
|
36148
36361
|
for (const block of msg.content) {
|
|
36149
36362
|
if (block?.type === "text" && typeof block.text === "string") {
|
|
36150
|
-
entry.pendingTurnText += block.text;
|
|
36151
36363
|
sawContent = true;
|
|
36152
36364
|
} else if (block?.type === "tool_use") {
|
|
36153
36365
|
sawContent = true;
|
|
@@ -36172,16 +36384,9 @@ function startFollower(opts) {
|
|
|
36172
36384
|
}
|
|
36173
36385
|
if (msg.stop_reason === "end_turn") {
|
|
36174
36386
|
if (suppressing) {
|
|
36175
|
-
entry.pendingTurnText = "";
|
|
36176
36387
|
firedFileTools = [];
|
|
36177
|
-
discardedTurns += 1;
|
|
36178
36388
|
continue;
|
|
36179
36389
|
}
|
|
36180
|
-
const flush = entry.pendingTurnText;
|
|
36181
|
-
entry.pendingTurnText = "";
|
|
36182
|
-
if (flush.trim()) {
|
|
36183
|
-
await fanOut2(entry.subscribers, flush, opts.onError, tag);
|
|
36184
|
-
}
|
|
36185
36390
|
if (fileDelivery && (firedFileTools.length > 0 || captureVerdicts)) {
|
|
36186
36391
|
const fired = firedFileTools;
|
|
36187
36392
|
firedFileTools = [];
|
|
@@ -36200,9 +36405,10 @@ function startFollower(opts) {
|
|
|
36200
36405
|
}
|
|
36201
36406
|
}
|
|
36202
36407
|
}
|
|
36408
|
+
for (const answer of answers.flush()) await emitAnswer(answer);
|
|
36203
36409
|
} catch (err) {
|
|
36204
36410
|
if (!abort2.signal.aborted) {
|
|
36205
|
-
opts.onError?.(`
|
|
36411
|
+
opts.onError?.(`emitter-error: ${err instanceof Error ? err.message : String(err)}`);
|
|
36206
36412
|
}
|
|
36207
36413
|
} finally {
|
|
36208
36414
|
opts.onClose();
|
|
@@ -36223,14 +36429,42 @@ async function fanOut2(subscribers, text, onError, tag) {
|
|
|
36223
36429
|
);
|
|
36224
36430
|
}
|
|
36225
36431
|
|
|
36432
|
+
// app/lib/channel-delivery/parity-census.ts
|
|
36433
|
+
var sessions2 = /* @__PURE__ */ new Map();
|
|
36434
|
+
function entry(sessionId) {
|
|
36435
|
+
let e = sessions2.get(sessionId);
|
|
36436
|
+
if (!e) {
|
|
36437
|
+
e = { rendered: /* @__PURE__ */ new Set(), sent: /* @__PURE__ */ new Set() };
|
|
36438
|
+
sessions2.set(sessionId, e);
|
|
36439
|
+
}
|
|
36440
|
+
return e;
|
|
36441
|
+
}
|
|
36442
|
+
function noteAnswer(sessionId, messageId) {
|
|
36443
|
+
entry(sessionId).rendered.add(messageId);
|
|
36444
|
+
}
|
|
36445
|
+
function noteSent(sessionId, messageId, channels) {
|
|
36446
|
+
if (channels.length === 0) return;
|
|
36447
|
+
entry(sessionId).sent.add(messageId);
|
|
36448
|
+
}
|
|
36449
|
+
function parityCensusLines() {
|
|
36450
|
+
const out = [];
|
|
36451
|
+
for (const [sessionId, e] of sessions2) {
|
|
36452
|
+
const missing = [...e.rendered].filter((id) => !e.sent.has(id));
|
|
36453
|
+
out.push(
|
|
36454
|
+
`[channel-parity] op=parity-census sessionId=${sessionId} rendered=${e.rendered.size} sent=${e.sent.size} missing=${missing.length} missingIds=${missing.join(",") || "none"}`
|
|
36455
|
+
);
|
|
36456
|
+
}
|
|
36457
|
+
return out;
|
|
36458
|
+
}
|
|
36459
|
+
|
|
36226
36460
|
// app/lib/webchat/file-delivery.ts
|
|
36227
36461
|
import { realpathSync as realpathSync9 } from "fs";
|
|
36228
|
-
import { resolve as
|
|
36462
|
+
import { resolve as resolve47 } from "path";
|
|
36229
36463
|
|
|
36230
36464
|
// app/lib/channel-pty-bridge/file-delivery.ts
|
|
36231
36465
|
var SEND_USER_FILE = "SendUserFile";
|
|
36232
36466
|
function makeFileDelivery(opts) {
|
|
36233
|
-
const { entry, tag, channel, sendFile, deferUntilVerdict = false } = opts;
|
|
36467
|
+
const { entry: entry2, tag, channel, sendFile, deferUntilVerdict = false } = opts;
|
|
36234
36468
|
let failedFiles = [];
|
|
36235
36469
|
let attempts = 0;
|
|
36236
36470
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -36257,7 +36491,7 @@ function makeFileDelivery(opts) {
|
|
|
36257
36491
|
function unreconciledCall(reason, files, detail) {
|
|
36258
36492
|
const d = detail ? ` detail="${detail.replace(/\s+/g, " ").slice(0, 120)}"` : "";
|
|
36259
36493
|
console.error(
|
|
36260
|
-
`${tag} file-delivery-unreconciled sender=${
|
|
36494
|
+
`${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${entry2.sessionId.slice(0, 8)} tool=${SEND_USER_FILE} reason=${reason} files=${files}${d}`
|
|
36261
36495
|
);
|
|
36262
36496
|
}
|
|
36263
36497
|
const handler = {
|
|
@@ -36278,10 +36512,10 @@ function makeFileDelivery(opts) {
|
|
|
36278
36512
|
const tried = attempts;
|
|
36279
36513
|
failedFiles = [];
|
|
36280
36514
|
attempts = 0;
|
|
36281
|
-
const sid =
|
|
36515
|
+
const sid = entry2.sessionId.slice(0, 8);
|
|
36282
36516
|
for (const file of failed2) {
|
|
36283
36517
|
console.error(
|
|
36284
|
-
`${tag} file-delivery-unreconciled sender=${
|
|
36518
|
+
`${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
36285
36519
|
);
|
|
36286
36520
|
}
|
|
36287
36521
|
if (deferUntilVerdict) {
|
|
@@ -36291,7 +36525,7 @@ function makeFileDelivery(opts) {
|
|
|
36291
36525
|
}
|
|
36292
36526
|
if (firedTools.includes(SEND_USER_FILE) && tried === 0) {
|
|
36293
36527
|
console.error(
|
|
36294
|
-
`${tag} file-delivery-unreconciled sender=${
|
|
36528
|
+
`${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
|
|
36295
36529
|
);
|
|
36296
36530
|
}
|
|
36297
36531
|
}
|
|
@@ -36320,43 +36554,43 @@ var TAG58 = "[webchat-adaptor]";
|
|
|
36320
36554
|
function platformRoot2() {
|
|
36321
36555
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
36322
36556
|
}
|
|
36323
|
-
function makeWebchatSendFile(
|
|
36557
|
+
function makeWebchatSendFile(entry2) {
|
|
36324
36558
|
return async (filePath) => {
|
|
36325
|
-
if (!
|
|
36326
|
-
console.error(`${TAG58} file-delivery reject reason=no-account sender=${
|
|
36559
|
+
if (!entry2.accountId) {
|
|
36560
|
+
console.error(`${TAG58} file-delivery reject reason=no-account sender=${entry2.senderId}`);
|
|
36327
36561
|
return { ok: false, error: "no-account" };
|
|
36328
36562
|
}
|
|
36329
|
-
const accountDir =
|
|
36563
|
+
const accountDir = resolve47(platformRoot2(), "..", "data/accounts", entry2.accountId);
|
|
36330
36564
|
try {
|
|
36331
36565
|
const resolved = realpathSync9(filePath);
|
|
36332
36566
|
const accountResolved = realpathSync9(accountDir);
|
|
36333
36567
|
if (!resolved.startsWith(accountResolved + "/")) {
|
|
36334
|
-
console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${
|
|
36568
|
+
console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${entry2.senderId}`);
|
|
36335
36569
|
return { ok: false, error: "outside-account" };
|
|
36336
36570
|
}
|
|
36337
36571
|
return { ok: true };
|
|
36338
36572
|
} catch (err) {
|
|
36339
36573
|
const code = err.code;
|
|
36340
36574
|
console.error(
|
|
36341
|
-
`${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${
|
|
36575
|
+
`${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry2.senderId}`
|
|
36342
36576
|
);
|
|
36343
36577
|
return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
|
|
36344
36578
|
}
|
|
36345
36579
|
};
|
|
36346
36580
|
}
|
|
36347
|
-
function makeWebchatFileDelivery(
|
|
36581
|
+
function makeWebchatFileDelivery(entry2) {
|
|
36348
36582
|
return makeFileDelivery({
|
|
36349
|
-
entry,
|
|
36583
|
+
entry: entry2,
|
|
36350
36584
|
tag: TAG58,
|
|
36351
36585
|
channel: "webchat",
|
|
36352
|
-
sendFile: makeWebchatSendFile(
|
|
36586
|
+
sendFile: makeWebchatSendFile(entry2),
|
|
36353
36587
|
deferUntilVerdict: true
|
|
36354
36588
|
});
|
|
36355
36589
|
}
|
|
36356
36590
|
|
|
36357
36591
|
// app/lib/webchat/gateway/native-file-follower.ts
|
|
36358
36592
|
function startWebchatNativeFileFollower(input) {
|
|
36359
|
-
const
|
|
36593
|
+
const entry2 = {
|
|
36360
36594
|
sessionId: input.sessionId,
|
|
36361
36595
|
role: "admin",
|
|
36362
36596
|
channel: "webchat",
|
|
@@ -36366,7 +36600,6 @@ function startWebchatNativeFileFollower(input) {
|
|
|
36366
36600
|
lastInboundAt: Date.now(),
|
|
36367
36601
|
writeChain: Promise.resolve(),
|
|
36368
36602
|
subscribers: /* @__PURE__ */ new Set(),
|
|
36369
|
-
pendingTurnText: "",
|
|
36370
36603
|
followerAbort: null,
|
|
36371
36604
|
followerRunning: false,
|
|
36372
36605
|
sliceToken: "",
|
|
@@ -36375,13 +36608,24 @@ function startWebchatNativeFileFollower(input) {
|
|
|
36375
36608
|
// Webchat renders from the transcript; there is no separate reply target.
|
|
36376
36609
|
replyTarget: null
|
|
36377
36610
|
};
|
|
36378
|
-
return
|
|
36379
|
-
entry,
|
|
36611
|
+
return startEmitter({
|
|
36612
|
+
entry: entry2,
|
|
36380
36613
|
tag: "[webchat-adaptor]",
|
|
36381
|
-
fileDelivery: makeWebchatFileDelivery(
|
|
36614
|
+
fileDelivery: makeWebchatFileDelivery(entry2),
|
|
36382
36615
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
36383
36616
|
// suppress replay so historical calls are not re-reconciled on attach.
|
|
36384
36617
|
suppressResumeReplay: true,
|
|
36618
|
+
// Task 2521 — the answer reaches every bound door through the one fan-out.
|
|
36619
|
+
// Ungated by design: whatever woke the turn, the reader and the chats show
|
|
36620
|
+
// the same thing.
|
|
36621
|
+
onAnswer: async (answer) => {
|
|
36622
|
+
const targets = input.fanTargets?.() ?? [];
|
|
36623
|
+
if (targets.length === 0) return;
|
|
36624
|
+
noteAnswer(input.sessionId, answer.messageId);
|
|
36625
|
+
const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
|
|
36626
|
+
noteSent(input.sessionId, answer.messageId, reached);
|
|
36627
|
+
console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
|
|
36628
|
+
},
|
|
36385
36629
|
onClose: input.onClose ?? (() => {
|
|
36386
36630
|
})
|
|
36387
36631
|
});
|
|
@@ -36417,29 +36661,29 @@ var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
|
36417
36661
|
function platformRoot3() {
|
|
36418
36662
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
36419
36663
|
}
|
|
36420
|
-
function makeWhatsAppSendFile(
|
|
36664
|
+
function makeWhatsAppSendFile(entry2, maxyAccountId) {
|
|
36421
36665
|
return async (filePath, caption) => {
|
|
36422
36666
|
const result = await sendWhatsAppDocument({
|
|
36423
|
-
to:
|
|
36667
|
+
to: entry2.senderId,
|
|
36424
36668
|
filePath,
|
|
36425
36669
|
caption,
|
|
36426
|
-
accountId:
|
|
36670
|
+
accountId: entry2.accountId,
|
|
36427
36671
|
maxyAccountId,
|
|
36428
36672
|
platformRoot: platformRoot3()
|
|
36429
36673
|
});
|
|
36430
36674
|
if (result.ok) return { ok: true };
|
|
36431
36675
|
console.error(
|
|
36432
|
-
`${TAG59} file-delivery reject reason=send-failed sender=${
|
|
36676
|
+
`${TAG59} file-delivery reject reason=send-failed sender=${entry2.senderId} status=${result.status} message=${result.error}`
|
|
36433
36677
|
);
|
|
36434
36678
|
return { ok: false, error: result.error };
|
|
36435
36679
|
};
|
|
36436
36680
|
}
|
|
36437
|
-
function makeWhatsAppFileDelivery(
|
|
36681
|
+
function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
|
|
36438
36682
|
const shared = makeFileDelivery({
|
|
36439
|
-
entry,
|
|
36683
|
+
entry: entry2,
|
|
36440
36684
|
tag: TAG59,
|
|
36441
36685
|
channel: "whatsapp",
|
|
36442
|
-
sendFile: makeWhatsAppSendFile(
|
|
36686
|
+
sendFile: makeWhatsAppSendFile(entry2, maxyAccountId)
|
|
36443
36687
|
});
|
|
36444
36688
|
let turnStartedAt = null;
|
|
36445
36689
|
let routeCalls = [];
|
|
@@ -36464,7 +36708,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
|
36464
36708
|
const routes = routeCalls;
|
|
36465
36709
|
turnStartedAt = null;
|
|
36466
36710
|
routeCalls = [];
|
|
36467
|
-
const sid =
|
|
36711
|
+
const sid = entry2.sessionId.slice(0, 8);
|
|
36468
36712
|
shared.onTurnEnd(firedTools);
|
|
36469
36713
|
for (const call of routes) {
|
|
36470
36714
|
const routeAt = call.to !== void 0 && call.filePath !== void 0 ? routeDocumentOutboundAt(call.to, call.filePath) : void 0;
|
|
@@ -36472,7 +36716,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
|
36472
36716
|
if (!delivered) {
|
|
36473
36717
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
36474
36718
|
console.error(
|
|
36475
|
-
`${TAG59} file-delivery-unreconciled sender=${
|
|
36719
|
+
`${TAG59} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
36476
36720
|
);
|
|
36477
36721
|
}
|
|
36478
36722
|
}
|
|
@@ -36483,17 +36727,16 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
|
36483
36727
|
// app/lib/whatsapp/gateway/native-file-follower.ts
|
|
36484
36728
|
var COMPOSING_REFRESH_MS = 1e4;
|
|
36485
36729
|
function startNativeFileFollower(input) {
|
|
36486
|
-
const
|
|
36730
|
+
const entry2 = {
|
|
36487
36731
|
sessionId: input.sessionId,
|
|
36488
|
-
role:
|
|
36732
|
+
role: input.role,
|
|
36489
36733
|
channel: "whatsapp",
|
|
36490
36734
|
accountId: input.accountId,
|
|
36491
36735
|
senderId: input.senderId,
|
|
36492
|
-
agentSlug: "admin",
|
|
36736
|
+
agentSlug: input.role === "admin" ? "admin" : "default",
|
|
36493
36737
|
lastInboundAt: Date.now(),
|
|
36494
36738
|
writeChain: Promise.resolve(),
|
|
36495
36739
|
subscribers: /* @__PURE__ */ new Set(),
|
|
36496
|
-
pendingTurnText: "",
|
|
36497
36740
|
followerAbort: null,
|
|
36498
36741
|
followerRunning: false,
|
|
36499
36742
|
sliceToken: "",
|
|
@@ -36529,15 +36772,28 @@ function startNativeFileFollower(input) {
|
|
|
36529
36772
|
if (!sock) return;
|
|
36530
36773
|
void sendPaused(sock, input.senderId);
|
|
36531
36774
|
};
|
|
36532
|
-
return
|
|
36533
|
-
entry,
|
|
36775
|
+
return startEmitter({
|
|
36776
|
+
entry: entry2,
|
|
36534
36777
|
tag: "[whatsapp-adaptor]",
|
|
36535
|
-
|
|
36778
|
+
// Task 2521 — admin-only. A public spawn has no tools (Task 2078), so a
|
|
36779
|
+
// handler here would reconcile a call that can never fire.
|
|
36780
|
+
fileDelivery: input.role === "admin" ? makeWhatsAppFileDelivery(entry2, input.maxyAccountId) : null,
|
|
36536
36781
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
36537
36782
|
// suppress replay so historical files are not re-sent on attach.
|
|
36538
36783
|
suppressResumeReplay: true,
|
|
36539
36784
|
onActivity,
|
|
36540
36785
|
onTurnComplete,
|
|
36786
|
+
// Task 2521 — the answer reaches every bound door through the one fan-out.
|
|
36787
|
+
// Ungated by design: whatever woke the turn, the reader and the chats show
|
|
36788
|
+
// the same thing.
|
|
36789
|
+
onAnswer: async (answer) => {
|
|
36790
|
+
const targets = input.fanTargets?.() ?? [];
|
|
36791
|
+
if (targets.length === 0) return;
|
|
36792
|
+
noteAnswer(input.sessionId, answer.messageId);
|
|
36793
|
+
const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
|
|
36794
|
+
noteSent(input.sessionId, answer.messageId, reached);
|
|
36795
|
+
console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
|
|
36796
|
+
},
|
|
36541
36797
|
onClose: input.onClose ?? (() => {
|
|
36542
36798
|
})
|
|
36543
36799
|
});
|
|
@@ -37090,6 +37346,9 @@ function buildTelegramEnsureSession(deps) {
|
|
|
37090
37346
|
accountId: input.accountId,
|
|
37091
37347
|
chatId: input.chatId,
|
|
37092
37348
|
botToken: input.botToken,
|
|
37349
|
+
// Task 2521 — resolved per answer, not captured once, so a door that goes
|
|
37350
|
+
// live after this session started still receives.
|
|
37351
|
+
fanTargets: () => deps.fanTargets(req.channelKey),
|
|
37093
37352
|
// Identity-guarded eviction: only delete if this controller is still the
|
|
37094
37353
|
// current one, so a stale self-close never removes a live follower.
|
|
37095
37354
|
onClose: () => {
|
|
@@ -37104,7 +37363,7 @@ function buildTelegramEnsureSession(deps) {
|
|
|
37104
37363
|
init_attachments();
|
|
37105
37364
|
import { realpathSync as realpathSync10 } from "fs";
|
|
37106
37365
|
import { readFile as readFile14, stat as stat10 } from "fs/promises";
|
|
37107
|
-
import { resolve as
|
|
37366
|
+
import { resolve as resolve48, basename as basename16 } from "path";
|
|
37108
37367
|
var TAG61 = "[telegram:outbound]";
|
|
37109
37368
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
37110
37369
|
async function sendTelegramDocument(input) {
|
|
@@ -37115,7 +37374,7 @@ async function sendTelegramDocument(input) {
|
|
|
37115
37374
|
if (!maxyAccountId || !platformRoot5) {
|
|
37116
37375
|
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
37117
37376
|
}
|
|
37118
|
-
const accountDir =
|
|
37377
|
+
const accountDir = resolve48(platformRoot5, "..", "data/accounts", maxyAccountId);
|
|
37119
37378
|
let accountResolved;
|
|
37120
37379
|
try {
|
|
37121
37380
|
accountResolved = realpathSync10(accountDir);
|
|
@@ -37184,39 +37443,39 @@ var TAG62 = "[telegram:outbound]";
|
|
|
37184
37443
|
function platformRoot4() {
|
|
37185
37444
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
37186
37445
|
}
|
|
37187
|
-
function makeTelegramSendFile(
|
|
37446
|
+
function makeTelegramSendFile(entry2, botToken) {
|
|
37188
37447
|
return async (filePath, caption) => {
|
|
37189
37448
|
if (!botToken) {
|
|
37190
|
-
console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${
|
|
37449
|
+
console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${entry2.senderId} role=${entry2.role}`);
|
|
37191
37450
|
return { ok: false, error: "no-bot-token" };
|
|
37192
37451
|
}
|
|
37193
|
-
if (
|
|
37194
|
-
console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${
|
|
37452
|
+
if (entry2.replyTarget == null) {
|
|
37453
|
+
console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${entry2.senderId} role=${entry2.role}`);
|
|
37195
37454
|
return { ok: false, error: "no-reply-target" };
|
|
37196
37455
|
}
|
|
37197
37456
|
const result = await sendTelegramDocument({
|
|
37198
37457
|
botToken,
|
|
37199
|
-
chatId: Number(
|
|
37458
|
+
chatId: Number(entry2.replyTarget),
|
|
37200
37459
|
filePath,
|
|
37201
37460
|
caption,
|
|
37202
|
-
maxyAccountId:
|
|
37461
|
+
maxyAccountId: entry2.accountId,
|
|
37203
37462
|
platformRoot: platformRoot4()
|
|
37204
37463
|
});
|
|
37205
37464
|
return result.ok ? { ok: true } : { ok: false, error: result.error };
|
|
37206
37465
|
};
|
|
37207
37466
|
}
|
|
37208
|
-
function makeTelegramFileDelivery(
|
|
37467
|
+
function makeTelegramFileDelivery(entry2, botToken) {
|
|
37209
37468
|
return makeFileDelivery({
|
|
37210
|
-
entry,
|
|
37469
|
+
entry: entry2,
|
|
37211
37470
|
tag: TAG62,
|
|
37212
37471
|
channel: "telegram",
|
|
37213
|
-
sendFile: makeTelegramSendFile(
|
|
37472
|
+
sendFile: makeTelegramSendFile(entry2, botToken)
|
|
37214
37473
|
});
|
|
37215
37474
|
}
|
|
37216
37475
|
|
|
37217
37476
|
// app/lib/telegram/gateway/native-file-follower.ts
|
|
37218
37477
|
function startTelegramNativeFileFollower(input) {
|
|
37219
|
-
const
|
|
37478
|
+
const entry2 = {
|
|
37220
37479
|
sessionId: input.sessionId,
|
|
37221
37480
|
role: "admin",
|
|
37222
37481
|
channel: "telegram",
|
|
@@ -37226,7 +37485,6 @@ function startTelegramNativeFileFollower(input) {
|
|
|
37226
37485
|
lastInboundAt: Date.now(),
|
|
37227
37486
|
writeChain: Promise.resolve(),
|
|
37228
37487
|
subscribers: /* @__PURE__ */ new Set(),
|
|
37229
|
-
pendingTurnText: "",
|
|
37230
37488
|
followerAbort: null,
|
|
37231
37489
|
followerRunning: false,
|
|
37232
37490
|
sliceToken: "",
|
|
@@ -37235,13 +37493,24 @@ function startTelegramNativeFileFollower(input) {
|
|
|
37235
37493
|
// The chat id the document is addressed to (set per spawn from the inbound).
|
|
37236
37494
|
replyTarget: input.chatId
|
|
37237
37495
|
};
|
|
37238
|
-
return
|
|
37239
|
-
entry,
|
|
37496
|
+
return startEmitter({
|
|
37497
|
+
entry: entry2,
|
|
37240
37498
|
tag: "[telegram-adaptor]",
|
|
37241
|
-
fileDelivery: makeTelegramFileDelivery(
|
|
37499
|
+
fileDelivery: makeTelegramFileDelivery(entry2, input.botToken),
|
|
37242
37500
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
37243
37501
|
// suppress replay so historical files are not re-sent on attach.
|
|
37244
37502
|
suppressResumeReplay: true,
|
|
37503
|
+
// Task 2521 — the answer reaches every bound door through the one fan-out.
|
|
37504
|
+
// Ungated by design: whatever woke the turn, the reader and the chats show
|
|
37505
|
+
// the same thing.
|
|
37506
|
+
onAnswer: async (answer) => {
|
|
37507
|
+
const targets = input.fanTargets?.() ?? [];
|
|
37508
|
+
if (targets.length === 0) return;
|
|
37509
|
+
noteAnswer(input.sessionId, answer.messageId);
|
|
37510
|
+
const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
|
|
37511
|
+
noteSent(input.sessionId, answer.messageId, reached);
|
|
37512
|
+
console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
|
|
37513
|
+
},
|
|
37245
37514
|
onClose: input.onClose ?? (() => {
|
|
37246
37515
|
})
|
|
37247
37516
|
});
|
|
@@ -37269,22 +37538,22 @@ function adminFanTargets(p) {
|
|
|
37269
37538
|
noTarget(p.origin, p.accountId, "users-read-failed");
|
|
37270
37539
|
return [origin];
|
|
37271
37540
|
}
|
|
37272
|
-
const
|
|
37273
|
-
if (!
|
|
37541
|
+
const entry2 = (users ?? []).find((u) => u.userId === p.adminUserId);
|
|
37542
|
+
if (!entry2) {
|
|
37274
37543
|
noTarget(p.origin, p.accountId, "no-entry");
|
|
37275
37544
|
return [origin];
|
|
37276
37545
|
}
|
|
37277
|
-
if (!
|
|
37546
|
+
if (!entry2.phone && !entry2.telegram) {
|
|
37278
37547
|
noTarget(p.origin, p.accountId, "no-bindings");
|
|
37279
37548
|
return [origin];
|
|
37280
37549
|
}
|
|
37281
37550
|
const bound = [];
|
|
37282
|
-
if (
|
|
37283
|
-
const wa = p.deps.whatsappLiveTarget(
|
|
37551
|
+
if (entry2.phone) {
|
|
37552
|
+
const wa = p.deps.whatsappLiveTarget(entry2.phone);
|
|
37284
37553
|
bound.push(wa ? { channel: "whatsapp", live: true, send: wa } : { channel: "whatsapp", live: false });
|
|
37285
37554
|
}
|
|
37286
|
-
if (
|
|
37287
|
-
const tg = p.deps.telegramLiveTarget(
|
|
37555
|
+
if (entry2.telegram) {
|
|
37556
|
+
const tg = p.deps.telegramLiveTarget(entry2.telegram);
|
|
37288
37557
|
bound.push(tg ? { channel: "telegram", live: true, send: tg } : { channel: "telegram", live: false });
|
|
37289
37558
|
}
|
|
37290
37559
|
return [origin, ...bound.filter((t) => t.channel !== p.origin)];
|
|
@@ -37491,8 +37760,8 @@ init_resolve_admin_userid();
|
|
|
37491
37760
|
init_src2();
|
|
37492
37761
|
function findAccountManagerDrift(accountManagers, validAccountIds) {
|
|
37493
37762
|
const drift = [];
|
|
37494
|
-
for (const [phone,
|
|
37495
|
-
const managesAccount = typeof
|
|
37763
|
+
for (const [phone, entry2] of Object.entries(accountManagers)) {
|
|
37764
|
+
const managesAccount = typeof entry2 === "string" ? entry2 : entry2.managesAccount;
|
|
37496
37765
|
if (!validAccountIds.includes(managesAccount)) {
|
|
37497
37766
|
drift.push({ phone, managesAccount, reason: "not-in-registry" });
|
|
37498
37767
|
}
|
|
@@ -37607,36 +37876,36 @@ init_sidebar_sessions();
|
|
|
37607
37876
|
function buildTelegramAuditLines(input) {
|
|
37608
37877
|
const lines = [];
|
|
37609
37878
|
const entries2 = input.accounts.flatMap(
|
|
37610
|
-
(a) => listBotEntries(a.telegram).map((
|
|
37879
|
+
(a) => listBotEntries(a.telegram).map((entry2) => ({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 }))
|
|
37611
37880
|
);
|
|
37612
37881
|
if (entries2.length === 0) return lines;
|
|
37613
37882
|
const byId = /* @__PURE__ */ new Map();
|
|
37614
|
-
for (const { entry } of entries2) byId.set(
|
|
37883
|
+
for (const { entry: entry2 } of entries2) byId.set(entry2.id, (byId.get(entry2.id) ?? 0) + 1);
|
|
37615
37884
|
for (const [botId, count] of byId) {
|
|
37616
37885
|
if (count > 1) lines.push(`[telegram-audit] op=duplicate-bot-id botId=${botId} accounts=${count}`);
|
|
37617
37886
|
}
|
|
37618
|
-
for (const { accountId, accountDir, entry } of entries2) {
|
|
37619
|
-
if (
|
|
37887
|
+
for (const { accountId, accountDir, entry: entry2 } of entries2) {
|
|
37888
|
+
if (entry2.role === "public" && !input.agentActive(accountDir, entry2.agent)) {
|
|
37620
37889
|
lines.push(
|
|
37621
|
-
`[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${
|
|
37890
|
+
`[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry2.id} agent=${entry2.agent}`
|
|
37622
37891
|
);
|
|
37623
37892
|
}
|
|
37624
|
-
if (!input.secretFileExists(
|
|
37625
|
-
lines.push(`[telegram-audit] op=secret-orphan botId=${
|
|
37893
|
+
if (!input.secretFileExists(entry2.id)) {
|
|
37894
|
+
lines.push(`[telegram-audit] op=secret-orphan botId=${entry2.id} side=entry`);
|
|
37626
37895
|
}
|
|
37627
37896
|
}
|
|
37628
37897
|
const known = new Set(entries2.map((e) => e.entry.id));
|
|
37629
37898
|
for (const botId of input.secretFilesOnDisk) {
|
|
37630
37899
|
if (!known.has(botId)) lines.push(`[telegram-audit] op=secret-orphan botId=${botId} side=file`);
|
|
37631
37900
|
}
|
|
37632
|
-
for (const { accountId, entry } of entries2) {
|
|
37633
|
-
const last = input.lastActivityByBot(
|
|
37901
|
+
for (const { accountId, entry: entry2 } of entries2) {
|
|
37902
|
+
const last = input.lastActivityByBot(entry2.id);
|
|
37634
37903
|
const spawnAge = last === null ? "never" : String(input.now - last.spawnMs);
|
|
37635
37904
|
const turnAge = last === null || last.turnMs === null ? "never" : String(input.now - last.turnMs);
|
|
37636
|
-
const agent =
|
|
37637
|
-
const secretFile = input.secretFileExists(
|
|
37905
|
+
const agent = entry2.role === "admin" ? "admin" : entry2.agent;
|
|
37906
|
+
const secretFile = input.secretFileExists(entry2.id) ? "present" : "absent";
|
|
37638
37907
|
lines.push(
|
|
37639
|
-
`[telegram-audit] op=configured botId=${
|
|
37908
|
+
`[telegram-audit] op=configured botId=${entry2.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastSpawnAgeMs=${spawnAge} lastTurnAgeMs=${turnAge}`
|
|
37640
37909
|
);
|
|
37641
37910
|
}
|
|
37642
37911
|
return lines;
|
|
@@ -37693,7 +37962,7 @@ function* walkTelegramSidecars() {
|
|
|
37693
37962
|
}
|
|
37694
37963
|
|
|
37695
37964
|
// server/telegram-migrate.ts
|
|
37696
|
-
import { readFileSync as
|
|
37965
|
+
import { readFileSync as readFileSync52, writeFileSync as writeFileSync19, existsSync as existsSync53, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
|
|
37697
37966
|
import { dirname as dirname18, join as join62 } from "path";
|
|
37698
37967
|
function migrateTelegramTwoSlot(input) {
|
|
37699
37968
|
const out = [];
|
|
@@ -37701,7 +37970,7 @@ function migrateTelegramTwoSlot(input) {
|
|
|
37701
37970
|
const configPath3 = join62(account.accountDir, "account.json");
|
|
37702
37971
|
let config;
|
|
37703
37972
|
try {
|
|
37704
|
-
config = JSON.parse(
|
|
37973
|
+
config = JSON.parse(readFileSync52(configPath3, "utf-8"));
|
|
37705
37974
|
} catch {
|
|
37706
37975
|
console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
|
|
37707
37976
|
continue;
|
|
@@ -37803,14 +38072,14 @@ function backfillMisfiledRecords(params) {
|
|
|
37803
38072
|
init_message_store();
|
|
37804
38073
|
|
|
37805
38074
|
// server/lib/booking-site-accounts.ts
|
|
37806
|
-
import { existsSync as existsSync54, readFileSync as
|
|
37807
|
-
import { resolve as
|
|
38075
|
+
import { existsSync as existsSync54, readFileSync as readFileSync53 } from "fs";
|
|
38076
|
+
import { resolve as resolve49 } from "path";
|
|
37808
38077
|
function listBookingSiteAccounts(accounts) {
|
|
37809
38078
|
return accounts.filter((a) => {
|
|
37810
|
-
const availPath =
|
|
38079
|
+
const availPath = resolve49(a.accountDir, "calendar-availability.json");
|
|
37811
38080
|
if (!existsSync54(availPath)) return false;
|
|
37812
38081
|
try {
|
|
37813
|
-
const cfg = JSON.parse(
|
|
38082
|
+
const cfg = JSON.parse(readFileSync53(availPath, "utf-8"));
|
|
37814
38083
|
return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
37815
38084
|
} catch {
|
|
37816
38085
|
return false;
|
|
@@ -37832,17 +38101,17 @@ function broadcastAdminShutdown(reason) {
|
|
|
37832
38101
|
const done = encoder.encode(`data: [DONE]
|
|
37833
38102
|
|
|
37834
38103
|
`);
|
|
37835
|
-
for (const
|
|
38104
|
+
for (const entry2 of activeAdminSSEControllers) {
|
|
37836
38105
|
try {
|
|
37837
|
-
|
|
38106
|
+
entry2.controller.enqueue(frame);
|
|
37838
38107
|
} catch {
|
|
37839
38108
|
}
|
|
37840
38109
|
try {
|
|
37841
|
-
|
|
38110
|
+
entry2.controller.enqueue(done);
|
|
37842
38111
|
} catch {
|
|
37843
38112
|
}
|
|
37844
38113
|
try {
|
|
37845
|
-
|
|
38114
|
+
entry2.controller.close();
|
|
37846
38115
|
} catch {
|
|
37847
38116
|
}
|
|
37848
38117
|
}
|
|
@@ -37855,8 +38124,8 @@ init_paths();
|
|
|
37855
38124
|
|
|
37856
38125
|
// ../lib/entitlement/src/index.ts
|
|
37857
38126
|
import { createPublicKey, createHash as createHash7, verify as cryptoVerify } from "crypto";
|
|
37858
|
-
import { existsSync as existsSync55, readFileSync as
|
|
37859
|
-
import { resolve as
|
|
38127
|
+
import { existsSync as existsSync55, readFileSync as readFileSync54, statSync as statSync29 } from "fs";
|
|
38128
|
+
import { resolve as resolve50 } from "path";
|
|
37860
38129
|
|
|
37861
38130
|
// ../lib/entitlement/src/canonicalize.ts
|
|
37862
38131
|
function canonicalize(value) {
|
|
@@ -37891,7 +38160,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
|
|
|
37891
38160
|
var GRACE_DAYS = 7;
|
|
37892
38161
|
var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
37893
38162
|
function pubkeyPath(brand) {
|
|
37894
|
-
return
|
|
38163
|
+
return resolve50(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
|
|
37895
38164
|
}
|
|
37896
38165
|
var memo = null;
|
|
37897
38166
|
function memoKey(mtimeMs, account) {
|
|
@@ -37903,7 +38172,7 @@ function resolveEntitlement(brand, account) {
|
|
|
37903
38172
|
if (brand.commercialMode !== true) {
|
|
37904
38173
|
return logResolved(implicitTrust(account), null);
|
|
37905
38174
|
}
|
|
37906
|
-
const entitlementPath =
|
|
38175
|
+
const entitlementPath = resolve50(brand.configDir, "entitlement.json");
|
|
37907
38176
|
if (!existsSync55(entitlementPath)) {
|
|
37908
38177
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
37909
38178
|
}
|
|
@@ -37919,7 +38188,7 @@ function resolveEntitlement(brand, account) {
|
|
|
37919
38188
|
function verifyAndResolve(brand, entitlementPath, account) {
|
|
37920
38189
|
let pubkeyPem;
|
|
37921
38190
|
try {
|
|
37922
|
-
pubkeyPem =
|
|
38191
|
+
pubkeyPem = readFileSync54(pubkeyPath(brand), "utf-8");
|
|
37923
38192
|
} catch (err) {
|
|
37924
38193
|
return logResolved(anonymousFallback("pubkey-missing"), {
|
|
37925
38194
|
reason: "pubkey-missing"
|
|
@@ -37933,7 +38202,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
|
|
|
37933
38202
|
}
|
|
37934
38203
|
let envelope;
|
|
37935
38204
|
try {
|
|
37936
|
-
envelope = JSON.parse(
|
|
38205
|
+
envelope = JSON.parse(readFileSync54(entitlementPath, "utf-8"));
|
|
37937
38206
|
} catch {
|
|
37938
38207
|
return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
|
|
37939
38208
|
}
|
|
@@ -38102,7 +38371,7 @@ if (BRAND_JSON_PATH && !existsSync56(BRAND_JSON_PATH)) {
|
|
|
38102
38371
|
}
|
|
38103
38372
|
if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
|
|
38104
38373
|
try {
|
|
38105
|
-
const parsed = JSON.parse(
|
|
38374
|
+
const parsed = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
|
|
38106
38375
|
BRAND = { ...BRAND, ...parsed };
|
|
38107
38376
|
} catch (err) {
|
|
38108
38377
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -38138,7 +38407,7 @@ var ALIAS_DOMAINS_PATH = join63(homedir5(), BRAND.configDir, "alias-domains.json
|
|
|
38138
38407
|
function loadAliasDomains() {
|
|
38139
38408
|
try {
|
|
38140
38409
|
if (!existsSync56(ALIAS_DOMAINS_PATH)) return null;
|
|
38141
|
-
const parsed = JSON.parse(
|
|
38410
|
+
const parsed = JSON.parse(readFileSync55(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
38142
38411
|
if (!Array.isArray(parsed)) {
|
|
38143
38412
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
38144
38413
|
return null;
|
|
@@ -38209,6 +38478,23 @@ async function recordPassiveDispatch(input) {
|
|
|
38209
38478
|
await session.close();
|
|
38210
38479
|
}
|
|
38211
38480
|
}
|
|
38481
|
+
registerLoop({
|
|
38482
|
+
name: "channel-parity",
|
|
38483
|
+
intervalMs: 6e4,
|
|
38484
|
+
run: () => {
|
|
38485
|
+
const lines = parityCensusLines();
|
|
38486
|
+
for (const line of lines) console.error(line);
|
|
38487
|
+
const missing = lines.filter((l) => !l.includes("missing=0")).length;
|
|
38488
|
+
return `${lines.length} session(s), ${missing} with a missing answer`;
|
|
38489
|
+
}
|
|
38490
|
+
});
|
|
38491
|
+
var whatsappFanTargets = (accountId, senderId) => adminFanTargets({
|
|
38492
|
+
origin: "whatsapp",
|
|
38493
|
+
originKey: senderId,
|
|
38494
|
+
accountId,
|
|
38495
|
+
adminUserId: resolveAdminUserId({ accountId, senderPhone: senderId }),
|
|
38496
|
+
deps: adminFanDeps("whatsapp", accountId)
|
|
38497
|
+
});
|
|
38212
38498
|
var waGateway = new WaGateway({
|
|
38213
38499
|
fetchStandingRules: fetchAccountStandingRules,
|
|
38214
38500
|
// Task 2321 — the account's own `## Instruction` body, read from that
|
|
@@ -38231,15 +38517,11 @@ var waGateway = new WaGateway({
|
|
|
38231
38517
|
// Task 2078 — one answer reaches every live door for this admin person.
|
|
38232
38518
|
// Task 2459 — the origin door is resolved from `senderId`, so it is present
|
|
38233
38519
|
// 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
|
-
}),
|
|
38520
|
+
// Task 2521 — hoisted, because the emitter's per-answer fan-out needs the same
|
|
38521
|
+
// resolver and `fanTargetsFor` is a constructor dep, not a callable method.
|
|
38522
|
+
fanTargetsFor: whatsappFanTargets,
|
|
38241
38523
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
38242
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ??
|
|
38524
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve51(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
38243
38525
|
// Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
|
|
38244
38526
|
// path-validation scope) is the sender's effective SESSION account, resolved
|
|
38245
38527
|
// once at the inbound gate and threaded here via the gateway's per-sender doc
|
|
@@ -38254,7 +38536,7 @@ var waGateway = new WaGateway({
|
|
|
38254
38536
|
caption,
|
|
38255
38537
|
accountId,
|
|
38256
38538
|
maxyAccountId,
|
|
38257
|
-
platformRoot:
|
|
38539
|
+
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."))
|
|
38258
38540
|
});
|
|
38259
38541
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
38260
38542
|
},
|
|
@@ -38389,7 +38671,6 @@ var waGateway = new WaGateway({
|
|
|
38389
38671
|
console.error(`[whatsapp-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
|
|
38390
38672
|
return;
|
|
38391
38673
|
}
|
|
38392
|
-
if (role !== "admin") return;
|
|
38393
38674
|
const prior = nativeFileFollowers.get(senderId);
|
|
38394
38675
|
if (prior) prior.abort();
|
|
38395
38676
|
let ac;
|
|
@@ -38401,6 +38682,11 @@ var waGateway = new WaGateway({
|
|
|
38401
38682
|
// session account (the sub-account for an account-manager), not the house
|
|
38402
38683
|
// socket account. accountId stays the getSocket key.
|
|
38403
38684
|
maxyAccountId: effectiveAccountId,
|
|
38685
|
+
// Task 2521 — file delivery is admin-only; the text emitter is not.
|
|
38686
|
+
role,
|
|
38687
|
+
// Task 2521 — resolved per answer, not captured once, so a door that goes
|
|
38688
|
+
// live after this session started still receives.
|
|
38689
|
+
fanTargets: () => whatsappFanTargets(accountId, senderId),
|
|
38404
38690
|
onClose: () => {
|
|
38405
38691
|
if (nativeFileFollowers.get(senderId) === ac) nativeFileFollowers.delete(senderId);
|
|
38406
38692
|
}
|
|
@@ -38547,12 +38833,15 @@ var webchatGateway = new WebchatGateway({
|
|
|
38547
38833
|
// live admin session (rc-spawn recover:true). Resolve the session id the same
|
|
38548
38834
|
// way ensureChannelSession does. Public sessions recover via the idle reaper,
|
|
38549
38835
|
// so a non-admin descriptor is reported not-live (no respawn).
|
|
38550
|
-
recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId }) => {
|
|
38836
|
+
recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId, subscriberAttached }) => {
|
|
38551
38837
|
if (role !== "admin") return { live: false };
|
|
38552
38838
|
const id = sessionId ?? adminSessionIdFor(accountId, key2, adminUserId);
|
|
38553
38839
|
const r = await managerRcRecover({
|
|
38554
38840
|
sessionId: id,
|
|
38555
38841
|
webchatChannel: { key: key2, gatewayUrl: webchatGatewayUrl(), serverPath: webchatServerPath() },
|
|
38842
|
+
// Task 2466 — the gateway resolved this against the live subscriber table
|
|
38843
|
+
// just before the call; the manager classifies from it.
|
|
38844
|
+
subscriberAttached,
|
|
38556
38845
|
...adminUserId !== void 0 ? { adminUserId } : {},
|
|
38557
38846
|
logContext: `channel=webchat key=${key2} recover`
|
|
38558
38847
|
});
|
|
@@ -38573,6 +38862,24 @@ app80.route("/", webchatGateway.routes());
|
|
|
38573
38862
|
webchatGateway.startSweeper();
|
|
38574
38863
|
webchatGateway.startPublicReaper();
|
|
38575
38864
|
var telegramFileFollowers = /* @__PURE__ */ new Map();
|
|
38865
|
+
var telegramFanTargets = (key2) => {
|
|
38866
|
+
const chatId = key2.slice(key2.indexOf(":") + 1);
|
|
38867
|
+
const account = listValidAccounts().find(
|
|
38868
|
+
(a) => listBotEntries(a.config.telegram).some((e) => e.id === key2.slice(0, key2.indexOf(":")))
|
|
38869
|
+
);
|
|
38870
|
+
if (!account) {
|
|
38871
|
+
console.error(`[channel-fanout] op=no-target origin=telegram key=${key2} accountId=none reason=no-account`);
|
|
38872
|
+
const send = telegramOriginTarget(key2);
|
|
38873
|
+
return [send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false }];
|
|
38874
|
+
}
|
|
38875
|
+
return adminFanTargets({
|
|
38876
|
+
origin: "telegram",
|
|
38877
|
+
originKey: key2,
|
|
38878
|
+
accountId: account.accountId,
|
|
38879
|
+
adminUserId: resolveTelegramAdminUserId({ accountId: account.accountId, chatId }),
|
|
38880
|
+
deps: adminFanDeps("telegram", account.accountId)
|
|
38881
|
+
});
|
|
38882
|
+
};
|
|
38576
38883
|
var telegramGateway = new TelegramGateway({
|
|
38577
38884
|
fetchStandingRules: fetchAccountStandingRules,
|
|
38578
38885
|
// Task 2353 — no turn-instruction resolver on this door. See the dep comment in
|
|
@@ -38582,26 +38889,8 @@ var telegramGateway = new TelegramGateway({
|
|
|
38582
38889
|
// is what the classification sidecar records (the manager reads
|
|
38583
38890
|
// telegramChannel.senderId) and therefore what session-reseat.ts writes.
|
|
38584
38891
|
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
|
-
},
|
|
38892
|
+
// Task 2521 — hoisted above; see `telegramFanTargets`.
|
|
38893
|
+
fanTargetsFor: telegramFanTargets,
|
|
38605
38894
|
gatewayUrl: telegramGatewayUrl(),
|
|
38606
38895
|
serverPath: telegramServerPath(),
|
|
38607
38896
|
// Task 2368 — the role branch lives in its own module so it has a test that
|
|
@@ -38612,6 +38901,7 @@ var telegramGateway = new TelegramGateway({
|
|
|
38612
38901
|
managerSpawn,
|
|
38613
38902
|
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
38614
38903
|
startFileFollower: startTelegramNativeFileFollower,
|
|
38904
|
+
fanTargets: telegramFanTargets,
|
|
38615
38905
|
followers: telegramFileFollowers
|
|
38616
38906
|
})
|
|
38617
38907
|
});
|
|
@@ -38670,9 +38960,9 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
|
|
|
38670
38960
|
const entries2 = listBotEntries(a.config.telegram);
|
|
38671
38961
|
for (const bot of readChannelAdmins(a.accountDir, "telegram").byBot) {
|
|
38672
38962
|
if (!bot.adminUsers.includes(asNumber)) continue;
|
|
38673
|
-
const
|
|
38674
|
-
if (!
|
|
38675
|
-
hits.push({ accountId: a.accountId, botId: bot.botId, botToken:
|
|
38963
|
+
const entry2 = entries2.find((e) => e.id === bot.botId);
|
|
38964
|
+
if (!entry2) continue;
|
|
38965
|
+
hits.push({ accountId: a.accountId, botId: bot.botId, botToken: entry2.token });
|
|
38676
38966
|
}
|
|
38677
38967
|
}
|
|
38678
38968
|
if (hits.length === 0) return { kind: "none" };
|
|
@@ -39052,8 +39342,8 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
39052
39342
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
39053
39343
|
return c.text("Not found", 404);
|
|
39054
39344
|
}
|
|
39055
|
-
const filePath =
|
|
39056
|
-
const expectedDir =
|
|
39345
|
+
const filePath = resolve51(account.accountDir, "agents", slug, "assets", filename);
|
|
39346
|
+
const expectedDir = resolve51(account.accountDir, "agents", slug, "assets");
|
|
39057
39347
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
39058
39348
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
39059
39349
|
return c.text("Forbidden", 403);
|
|
@@ -39065,7 +39355,7 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
39065
39355
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
39066
39356
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
39067
39357
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
39068
|
-
const body =
|
|
39358
|
+
const body = readFileSync55(filePath);
|
|
39069
39359
|
return c.body(body, 200, {
|
|
39070
39360
|
"Content-Type": contentType,
|
|
39071
39361
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -39082,8 +39372,8 @@ app80.get("/generated/:filename", (c) => {
|
|
|
39082
39372
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
39083
39373
|
return c.text("Not found", 404);
|
|
39084
39374
|
}
|
|
39085
|
-
const filePath =
|
|
39086
|
-
const expectedDir =
|
|
39375
|
+
const filePath = resolve51(account.accountDir, "generated", filename);
|
|
39376
|
+
const expectedDir = resolve51(account.accountDir, "generated");
|
|
39087
39377
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
39088
39378
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
39089
39379
|
return c.text("Forbidden", 403);
|
|
@@ -39095,7 +39385,7 @@ app80.get("/generated/:filename", (c) => {
|
|
|
39095
39385
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
39096
39386
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
39097
39387
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
39098
|
-
const body =
|
|
39388
|
+
const body = readFileSync55(filePath);
|
|
39099
39389
|
return c.body(body, 200, {
|
|
39100
39390
|
"Content-Type": contentType,
|
|
39101
39391
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -39110,7 +39400,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
|
39110
39400
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
39111
39401
|
if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
|
|
39112
39402
|
try {
|
|
39113
|
-
const fullBrand = JSON.parse(
|
|
39403
|
+
const fullBrand = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
|
|
39114
39404
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
39115
39405
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
39116
39406
|
} catch {
|
|
@@ -39137,11 +39427,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
39137
39427
|
var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
|
|
39138
39428
|
var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
|
|
39139
39429
|
var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
|
|
39140
|
-
(p) => existsSync56(
|
|
39430
|
+
(p) => existsSync56(resolve51(process.cwd(), "public", p.replace(/^\//, "")))
|
|
39141
39431
|
);
|
|
39142
39432
|
var SW_SOURCE = (() => {
|
|
39143
39433
|
try {
|
|
39144
|
-
return
|
|
39434
|
+
return readFileSync55(resolve51(process.cwd(), "public", "sw.js"), "utf-8");
|
|
39145
39435
|
} catch {
|
|
39146
39436
|
return null;
|
|
39147
39437
|
}
|
|
@@ -39151,7 +39441,7 @@ function readInstalledVersion() {
|
|
|
39151
39441
|
if (!PLATFORM_ROOT6) return "unknown";
|
|
39152
39442
|
const versionFile = join63(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
|
|
39153
39443
|
if (!existsSync56(versionFile)) return "unknown";
|
|
39154
|
-
const content =
|
|
39444
|
+
const content = readFileSync55(versionFile, "utf-8").trim();
|
|
39155
39445
|
return content || "unknown";
|
|
39156
39446
|
} catch {
|
|
39157
39447
|
return "unknown";
|
|
@@ -39162,7 +39452,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
|
|
|
39162
39452
|
function cachedHtml(file) {
|
|
39163
39453
|
let html = htmlCache.get(file);
|
|
39164
39454
|
if (!html) {
|
|
39165
|
-
html =
|
|
39455
|
+
html = readFileSync55(resolve51(process.cwd(), "public", file), "utf-8");
|
|
39166
39456
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
39167
39457
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
39168
39458
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -39186,7 +39476,7 @@ function loadBrandingCache(agentSlug) {
|
|
|
39186
39476
|
if (!accountId) return null;
|
|
39187
39477
|
const cachePath = join63(configDir, "branding-cache", accountId, `${agentSlug}.json`);
|
|
39188
39478
|
if (!existsSync56(cachePath)) return null;
|
|
39189
|
-
return JSON.parse(
|
|
39479
|
+
return JSON.parse(readFileSync55(cachePath, "utf-8"));
|
|
39190
39480
|
} catch {
|
|
39191
39481
|
return null;
|
|
39192
39482
|
}
|
|
@@ -39281,7 +39571,7 @@ app80.use("/vnc-popout.html", logViewerFetch);
|
|
|
39281
39571
|
app80.get("/vnc-popout.html", (c) => {
|
|
39282
39572
|
let html = htmlCache.get("vnc-popout.html");
|
|
39283
39573
|
if (!html) {
|
|
39284
|
-
html =
|
|
39574
|
+
html = readFileSync55(resolve51(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
39285
39575
|
const name = escapeHtml(BRAND.productName);
|
|
39286
39576
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
39287
39577
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -39441,7 +39731,7 @@ var httpServer = serve({ fetch: app80.fetch, port, hostname });
|
|
|
39441
39731
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
39442
39732
|
{
|
|
39443
39733
|
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39444
|
-
const reconcileScript =
|
|
39734
|
+
const reconcileScript = resolve51(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
|
|
39445
39735
|
const runReconcile = (ctx) => {
|
|
39446
39736
|
if (!existsSync56(reconcileScript)) return;
|
|
39447
39737
|
try {
|
|
@@ -39482,7 +39772,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39482
39772
|
{
|
|
39483
39773
|
const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
|
|
39484
39774
|
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39485
|
-
const dreamSweepScript =
|
|
39775
|
+
const dreamSweepScript = resolve51(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
|
|
39486
39776
|
registerLoop({
|
|
39487
39777
|
name: "dream-cycle-sweep",
|
|
39488
39778
|
intervalMs: DREAM_CYCLE_SWEEP_INTERVAL_MS,
|
|
@@ -39506,7 +39796,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39506
39796
|
}
|
|
39507
39797
|
{
|
|
39508
39798
|
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39509
|
-
const outlookScript =
|
|
39799
|
+
const outlookScript = resolve51(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
|
|
39510
39800
|
const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
|
|
39511
39801
|
const runOutlookComplete = (ctx) => {
|
|
39512
39802
|
if (!existsSync56(outlookScript)) return;
|
|
@@ -39532,7 +39822,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39532
39822
|
}
|
|
39533
39823
|
{
|
|
39534
39824
|
const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39535
|
-
const strandedAccountsDir =
|
|
39825
|
+
const strandedAccountsDir = resolve51(auditRoot, "..", "data/accounts");
|
|
39536
39826
|
const STRANDED_AUDIT_INTERVAL_MS = 3e5;
|
|
39537
39827
|
const STRANDED_AGE_MS = 16 * 6e4;
|
|
39538
39828
|
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;
|
|
@@ -39542,7 +39832,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39542
39832
|
const now = Date.now();
|
|
39543
39833
|
for (const name of readdirSync32(strandedAccountsDir)) {
|
|
39544
39834
|
if (!STRANDED_UUID_RE.test(name)) continue;
|
|
39545
|
-
const pendingPath2 =
|
|
39835
|
+
const pendingPath2 = resolve51(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
|
|
39546
39836
|
if (!existsSync56(pendingPath2)) continue;
|
|
39547
39837
|
const ageMs2 = now - statSync30(pendingPath2).mtimeMs;
|
|
39548
39838
|
if (ageMs2 > STRANDED_AGE_MS) {
|
|
@@ -39562,7 +39852,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39562
39852
|
}
|
|
39563
39853
|
{
|
|
39564
39854
|
const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39565
|
-
const googleAccountsDir =
|
|
39855
|
+
const googleAccountsDir = resolve51(googleRoot, "..", "data/accounts");
|
|
39566
39856
|
const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
|
|
39567
39857
|
const runGooglePendingAuditSafe = () => {
|
|
39568
39858
|
try {
|
|
@@ -39577,7 +39867,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39577
39867
|
firstRunDelayMs: 27e3,
|
|
39578
39868
|
run: runGooglePendingAuditSafe
|
|
39579
39869
|
});
|
|
39580
|
-
const googleAuditScript =
|
|
39870
|
+
const googleAuditScript = resolve51(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
|
|
39581
39871
|
const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
|
|
39582
39872
|
const runGoogleAccountAudit = (ctx) => {
|
|
39583
39873
|
if (!existsSync56(googleAuditScript)) return;
|
|
@@ -39640,7 +39930,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39640
39930
|
for (const o of migrateTelegramTwoSlot({
|
|
39641
39931
|
accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
|
|
39642
39932
|
secretPathFor: secretPath,
|
|
39643
|
-
legacySecretPathFor: (botType, accountId) =>
|
|
39933
|
+
legacySecretPathFor: (botType, accountId) => resolve51(
|
|
39644
39934
|
homedir5(),
|
|
39645
39935
|
configDirName2(),
|
|
39646
39936
|
`${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
|
|
@@ -39808,7 +40098,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39808
40098
|
}
|
|
39809
40099
|
{
|
|
39810
40100
|
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39811
|
-
const publishScript =
|
|
40101
|
+
const publishScript = resolve51(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
|
|
39812
40102
|
const PUBLISH_INTERVAL_MS = 3e5;
|
|
39813
40103
|
const bookingAccounts = () => {
|
|
39814
40104
|
try {
|
|
@@ -39838,21 +40128,21 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
39838
40128
|
}
|
|
39839
40129
|
};
|
|
39840
40130
|
const auditSnapshotAge = (account) => {
|
|
39841
|
-
const availPath =
|
|
40131
|
+
const availPath = resolve51(account.accountDir, "calendar-availability.json");
|
|
39842
40132
|
let hasBookingSite = false;
|
|
39843
40133
|
try {
|
|
39844
40134
|
if (existsSync56(availPath)) {
|
|
39845
|
-
const cfg = JSON.parse(
|
|
40135
|
+
const cfg = JSON.parse(readFileSync55(availPath, "utf-8"));
|
|
39846
40136
|
hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
39847
40137
|
}
|
|
39848
40138
|
} catch {
|
|
39849
40139
|
}
|
|
39850
40140
|
if (!hasBookingSite) return;
|
|
39851
|
-
const statePath =
|
|
40141
|
+
const statePath = resolve51(account.accountDir, "state", "booking-availability", "last-publish.json");
|
|
39852
40142
|
let lastSuccessAt = null;
|
|
39853
40143
|
if (existsSync56(statePath)) {
|
|
39854
40144
|
try {
|
|
39855
|
-
const rec = JSON.parse(
|
|
40145
|
+
const rec = JSON.parse(readFileSync55(statePath, "utf-8"));
|
|
39856
40146
|
lastSuccessAt = rec.lastSuccessAt ?? null;
|
|
39857
40147
|
} catch {
|
|
39858
40148
|
console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
|
|
@@ -39888,7 +40178,7 @@ startPlannerDivergence();
|
|
|
39888
40178
|
startDispatchFieldCensus(() => getSession());
|
|
39889
40179
|
{
|
|
39890
40180
|
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
|
|
39891
|
-
const auditScript =
|
|
40181
|
+
const auditScript = resolve51(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
|
|
39892
40182
|
const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
|
|
39893
40183
|
const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
|
|
39894
40184
|
const runConnectorAudit = (ctx) => {
|
|
@@ -40018,7 +40308,7 @@ async function runAdminUserReconcileTick() {
|
|
|
40018
40308
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
40019
40309
|
return;
|
|
40020
40310
|
}
|
|
40021
|
-
const usersRaw =
|
|
40311
|
+
const usersRaw = readFileSync55(USERS_FILE, "utf-8").trim();
|
|
40022
40312
|
if (!usersRaw) {
|
|
40023
40313
|
console.error("[adminuser-self-heal] skip reason=empty-users-file");
|
|
40024
40314
|
return;
|
|
@@ -40165,7 +40455,7 @@ registerLoop({
|
|
|
40165
40455
|
var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
40166
40456
|
function countUsersRows() {
|
|
40167
40457
|
if (!existsSync56(USERS_FILE)) return 0;
|
|
40168
|
-
const raw =
|
|
40458
|
+
const raw = readFileSync55(USERS_FILE, "utf-8").trim();
|
|
40169
40459
|
if (!raw) return 0;
|
|
40170
40460
|
const users = JSON.parse(raw);
|
|
40171
40461
|
return users.filter((u) => typeof u.userId === "string").length;
|
|
@@ -40333,7 +40623,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
40333
40623
|
}
|
|
40334
40624
|
init({
|
|
40335
40625
|
configDir: configDirForWhatsApp,
|
|
40336
|
-
platformRoot:
|
|
40626
|
+
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..")),
|
|
40337
40627
|
accountConfig: bootAccountConfig,
|
|
40338
40628
|
// Task 2074 — enrich with the sub-account name and fan out to every open
|
|
40339
40629
|
// admin SSE stream. Fire-and-forget: the manager's persist path must not
|