@rubytech/create-realagent-code 0.1.577 → 0.1.579
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/lib/routine-templates/dist/index.d.ts +4 -4
- package/payload/platform/lib/routine-templates/dist/index.js +4 -4
- package/payload/platform/lib/routine-templates/src/index.ts +4 -4
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +11 -1
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +19 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +6 -0
- package/payload/platform/plugins/docs/references/platform.md +2 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +6 -0
- package/payload/platform/services/webchat-channel/dist/server.js +4 -4
- package/payload/platform/services/webchat-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-BLGZEaKU.js → AdminLoginScreens-Bus-Y-rG.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-Bus-Y-rG.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-Bus-Y-rG.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-Bsm7q3CW.js +3 -0
- package/payload/server/public/assets/AdminShell-Bsm7q3CW.js.br +0 -0
- package/payload/server/public/assets/AdminShell-Bsm7q3CW.js.gz +0 -0
- package/payload/server/public/assets/{activity-BL6qbB9a.js → activity-BWqrCyYM.js} +1 -1
- package/payload/server/public/assets/activity-BWqrCyYM.js.br +0 -0
- package/payload/server/public/assets/activity-BWqrCyYM.js.gz +0 -0
- package/payload/server/public/assets/admin-zly7Y6EZ.js +1 -0
- package/payload/server/public/assets/admin-zly7Y6EZ.js.br +0 -0
- package/payload/server/public/assets/admin-zly7Y6EZ.js.gz +0 -0
- package/payload/server/public/assets/{agents-BzhfKZDN.js → agents-BsxGD1V1.js} +1 -1
- package/payload/server/public/assets/agents-BsxGD1V1.js.br +0 -0
- package/payload/server/public/assets/agents-BsxGD1V1.js.gz +0 -0
- package/payload/server/public/assets/{browser-C-6io2B9.js → browser-CRLxTaw2.js} +1 -1
- package/payload/server/public/assets/browser-CRLxTaw2.js.br +0 -0
- package/payload/server/public/assets/browser-CRLxTaw2.js.gz +0 -0
- package/payload/server/public/assets/{calendar-Dt3gS88s.js → calendar-Bmp6qI_P.js} +1 -1
- package/payload/server/public/assets/calendar-Bmp6qI_P.js.br +0 -0
- package/payload/server/public/assets/calendar-Bmp6qI_P.js.gz +0 -0
- package/payload/server/public/assets/chat-ChObDKHy.js +1 -0
- package/payload/server/public/assets/chat-ChObDKHy.js.br +0 -0
- package/payload/server/public/assets/chat-ChObDKHy.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-D2l4jMcd.js +1 -0
- package/payload/server/public/assets/chevron-left-D2l4jMcd.js.br +0 -0
- package/payload/server/public/assets/clock-CMdfsroW.js +1 -0
- package/payload/server/public/assets/clock-CMdfsroW.js.br +0 -0
- package/payload/server/public/assets/clock-CMdfsroW.js.gz +0 -0
- package/payload/server/public/assets/{copy-TkPJgllS.js → copy-Bcjpxqzn.js} +1 -1
- package/payload/server/public/assets/copy-Bcjpxqzn.js.br +0 -0
- package/payload/server/public/assets/copy-Bcjpxqzn.js.gz +0 -0
- package/payload/server/public/assets/data-CFB5Lgw-.js +1 -0
- package/payload/server/public/assets/data-CFB5Lgw-.js.br +0 -0
- package/payload/server/public/assets/data-CFB5Lgw-.js.gz +0 -0
- package/payload/server/public/assets/{field-B490-evJ.js → field-ChKB2l_d.js} +1 -1
- package/payload/server/public/assets/field-ChKB2l_d.js.br +0 -0
- package/payload/server/public/assets/field-ChKB2l_d.js.gz +0 -0
- package/payload/server/public/assets/{file-text-DyAL2DCh.js → file-text-COZcf7mU.js} +1 -1
- package/payload/server/public/assets/file-text-COZcf7mU.js.br +0 -0
- package/payload/server/public/assets/file-text-COZcf7mU.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-8jKFrLHn.js → graph-labels-x-1MT-3c.js} +1 -1
- package/payload/server/public/assets/graph-labels-x-1MT-3c.js.br +0 -0
- package/payload/server/public/assets/graph-labels-x-1MT-3c.js.gz +0 -0
- package/payload/server/public/assets/{graph-PiNIcCfX.js → graph-nnV47iqM.js} +1 -1
- package/payload/server/public/assets/graph-nnV47iqM.js.br +0 -0
- package/payload/server/public/assets/graph-nnV47iqM.js.gz +0 -0
- package/payload/server/public/assets/{jsx-runtime-ChVh0xOm.css → jsx-runtime-CeNZasfC.css} +1 -1
- package/payload/server/public/assets/jsx-runtime-CeNZasfC.css.br +0 -0
- package/payload/server/public/assets/jsx-runtime-CeNZasfC.css.gz +0 -0
- package/payload/server/public/assets/{operator-DKGKGzrb.js → operator-BAO0-mjU.js} +1 -1
- package/payload/server/public/assets/operator-BAO0-mjU.js.br +0 -0
- package/payload/server/public/assets/operator-BAO0-mjU.js.gz +0 -0
- package/payload/server/public/assets/{page-DXz_K10G.js → page-B-F08pod.js} +1 -1
- package/payload/server/public/assets/page-B-F08pod.js.br +0 -0
- package/payload/server/public/assets/page-B-F08pod.js.gz +0 -0
- package/payload/server/public/assets/page-BMOCy2aD.js +1 -0
- package/payload/server/public/assets/page-BMOCy2aD.js.br +0 -0
- package/payload/server/public/assets/page-BMOCy2aD.js.gz +0 -0
- package/payload/server/public/assets/play-DFtlk8Zs.js +1 -0
- package/payload/server/public/assets/play-DFtlk8Zs.js.br +0 -0
- package/payload/server/public/assets/play-DFtlk8Zs.js.gz +0 -0
- package/payload/server/public/assets/{public-BmA3-jLj.js → public-_HJsUohA.js} +1 -1
- package/payload/server/public/assets/public-_HJsUohA.js.br +0 -0
- package/payload/server/public/assets/public-_HJsUohA.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-fCH0LSMq.js → rotate-ccw-DJeAnYLQ.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-DJeAnYLQ.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-DJeAnYLQ.js.gz +0 -0
- package/payload/server/public/assets/{routines-BeFrmkls.js → routines-CoIVXRCB.js} +2 -2
- package/payload/server/public/assets/routines-CoIVXRCB.js.br +0 -0
- package/payload/server/public/assets/routines-CoIVXRCB.js.gz +0 -0
- package/payload/server/public/assets/{skills-mjls5HBa.js → skills-BjHt5lDG.js} +1 -1
- package/payload/server/public/assets/skills-BjHt5lDG.js.br +0 -0
- package/payload/server/public/assets/skills-BjHt5lDG.js.gz +0 -0
- package/payload/server/public/assets/{tasks-p6puHHD0.js → tasks-iUFfeA9_.js} +2 -2
- package/payload/server/public/assets/tasks-iUFfeA9_.js.br +0 -0
- package/payload/server/public/assets/tasks-iUFfeA9_.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-CrZ1ewbd.js → triangle-alert-Dwa986Gg.js} +1 -1
- package/payload/server/public/assets/triangle-alert-Dwa986Gg.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-Dwa986Gg.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-zOQNrIwd.js → useCopyFeedback-B1Jp6CsE.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-B1Jp6CsE.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-B1Jp6CsE.js.gz +0 -0
- package/payload/server/public/assets/{useMediaQuery-BmG090FR.js → useMediaQuery-CVZYjRf6.js} +2 -2
- package/payload/server/public/assets/useMediaQuery-CVZYjRf6.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-CVZYjRf6.js.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-D5b4r4z0.js +2 -0
- package/payload/server/public/assets/useVoiceRecorder-D5b4r4z0.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-D5b4r4z0.js.gz +0 -0
- package/payload/server/public/assets/{wrench-k4GYG5rc.js → wrench-BD7xwbV1.js} +1 -1
- package/payload/server/public/assets/wrench-BD7xwbV1.js.br +0 -0
- package/payload/server/public/assets/wrench-BD7xwbV1.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/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 +1169 -519
- package/payload/platform/plugins/dispatch/mcp/dist/scripts/converge-visit-instants.d.ts +0 -6
- package/payload/platform/plugins/dispatch/mcp/dist/scripts/converge-visit-instants.d.ts.map +0 -1
- package/payload/platform/plugins/dispatch/mcp/dist/scripts/converge-visit-instants.js +0 -102
- package/payload/platform/plugins/dispatch/mcp/dist/scripts/converge-visit-instants.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.d.ts +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.js +0 -87
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-startdate-utc.test.d.ts +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-startdate-utc.test.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-startdate-utc.test.js +0 -79
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-startdate-utc.test.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.d.ts +0 -35
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.js +0 -56
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-startdate-utc.d.ts +0 -24
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-startdate-utc.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-startdate-utc.js +0 -87
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-startdate-utc.js.map +0 -1
- package/payload/server/public/assets/AdminLoginScreens-BLGZEaKU.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-BLGZEaKU.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-C_M7YE5Z.js +0 -3
- package/payload/server/public/assets/AdminShell-C_M7YE5Z.js.br +0 -0
- package/payload/server/public/assets/AdminShell-C_M7YE5Z.js.gz +0 -0
- package/payload/server/public/assets/activity-BL6qbB9a.js.br +0 -0
- package/payload/server/public/assets/activity-BL6qbB9a.js.gz +0 -0
- package/payload/server/public/assets/admin-jnJ3P2u4.js +0 -1
- package/payload/server/public/assets/admin-jnJ3P2u4.js.br +0 -0
- package/payload/server/public/assets/admin-jnJ3P2u4.js.gz +0 -0
- package/payload/server/public/assets/agents-BzhfKZDN.js.br +0 -0
- package/payload/server/public/assets/agents-BzhfKZDN.js.gz +0 -0
- package/payload/server/public/assets/browser-C-6io2B9.js.br +0 -0
- package/payload/server/public/assets/browser-C-6io2B9.js.gz +0 -0
- package/payload/server/public/assets/calendar-Dt3gS88s.js.br +0 -0
- package/payload/server/public/assets/calendar-Dt3gS88s.js.gz +0 -0
- package/payload/server/public/assets/chat-IUOvVkL-.js +0 -1
- package/payload/server/public/assets/chat-IUOvVkL-.js.br +0 -1
- package/payload/server/public/assets/chat-IUOvVkL-.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-DSzR9zR7.js +0 -1
- package/payload/server/public/assets/chevron-left-DSzR9zR7.js.br +0 -0
- package/payload/server/public/assets/clock-C5xE5p2I.js +0 -1
- package/payload/server/public/assets/clock-C5xE5p2I.js.br +0 -0
- package/payload/server/public/assets/copy-TkPJgllS.js.br +0 -0
- package/payload/server/public/assets/copy-TkPJgllS.js.gz +0 -0
- package/payload/server/public/assets/data-Bhv0Pvfl.js +0 -1
- package/payload/server/public/assets/data-Bhv0Pvfl.js.br +0 -0
- package/payload/server/public/assets/data-Bhv0Pvfl.js.gz +0 -0
- package/payload/server/public/assets/field-B490-evJ.js.br +0 -0
- package/payload/server/public/assets/field-B490-evJ.js.gz +0 -0
- package/payload/server/public/assets/file-text-DyAL2DCh.js.br +0 -0
- package/payload/server/public/assets/file-text-DyAL2DCh.js.gz +0 -0
- package/payload/server/public/assets/graph-PiNIcCfX.js.br +0 -0
- package/payload/server/public/assets/graph-PiNIcCfX.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-8jKFrLHn.js.br +0 -0
- package/payload/server/public/assets/graph-labels-8jKFrLHn.js.gz +0 -0
- package/payload/server/public/assets/jsx-runtime-ChVh0xOm.css.br +0 -0
- package/payload/server/public/assets/jsx-runtime-ChVh0xOm.css.gz +0 -0
- package/payload/server/public/assets/operator-DKGKGzrb.js.br +0 -0
- package/payload/server/public/assets/operator-DKGKGzrb.js.gz +0 -0
- package/payload/server/public/assets/page-BU5vlY9Y.js +0 -1
- package/payload/server/public/assets/page-BU5vlY9Y.js.br +0 -0
- package/payload/server/public/assets/page-BU5vlY9Y.js.gz +0 -0
- package/payload/server/public/assets/page-DXz_K10G.js.br +0 -0
- package/payload/server/public/assets/page-DXz_K10G.js.gz +0 -0
- package/payload/server/public/assets/play-CmSyYrB6.js +0 -1
- package/payload/server/public/assets/play-CmSyYrB6.js.br +0 -0
- package/payload/server/public/assets/play-CmSyYrB6.js.gz +0 -0
- package/payload/server/public/assets/public-BmA3-jLj.js.br +0 -0
- package/payload/server/public/assets/public-BmA3-jLj.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-fCH0LSMq.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-fCH0LSMq.js.gz +0 -0
- package/payload/server/public/assets/routines-BeFrmkls.js.br +0 -0
- package/payload/server/public/assets/routines-BeFrmkls.js.gz +0 -0
- package/payload/server/public/assets/skills-mjls5HBa.js.br +0 -0
- package/payload/server/public/assets/skills-mjls5HBa.js.gz +0 -0
- package/payload/server/public/assets/tasks-p6puHHD0.js.br +0 -0
- package/payload/server/public/assets/tasks-p6puHHD0.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-CrZ1ewbd.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-CrZ1ewbd.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-zOQNrIwd.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-zOQNrIwd.js.gz +0 -0
- package/payload/server/public/assets/useMediaQuery-BmG090FR.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-BmG090FR.js.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-9pf6FFJ-.js +0 -2
- package/payload/server/public/assets/useVoiceRecorder-9pf6FFJ-.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-9pf6FFJ-.js.gz +0 -0
- package/payload/server/public/assets/wrench-k4GYG5rc.js.br +0 -0
- package/payload/server/public/assets/wrench-k4GYG5rc.js.gz +0 -0
- /package/payload/server/public/assets/{jsx-runtime-YcUntiud.js → jsx-runtime-CjJ246F7.js} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-YcUntiud.js.br → jsx-runtime-CjJ246F7.js.br} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-YcUntiud.js.gz → jsx-runtime-CjJ246F7.js.gz} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -471,6 +471,16 @@ var init_agent_errors = __esm({
|
|
|
471
471
|
});
|
|
472
472
|
|
|
473
473
|
// app/lib/reader/parse-transcript.ts
|
|
474
|
+
function emptyEnvelopeCensus() {
|
|
475
|
+
return {
|
|
476
|
+
rows: 0,
|
|
477
|
+
wrapped: 0,
|
|
478
|
+
nested: 0,
|
|
479
|
+
stripped: 0,
|
|
480
|
+
unstripped: 0,
|
|
481
|
+
sources: { whatsapp: 0, webchat: 0, telegram: 0, none: 0 }
|
|
482
|
+
};
|
|
483
|
+
}
|
|
474
484
|
function isCompactSummaryRow(row, content) {
|
|
475
485
|
return row.isCompactSummary === true || content !== null && content.startsWith(COMPACT_SUMMARY_PREFIX);
|
|
476
486
|
}
|
|
@@ -481,10 +491,11 @@ function isScheduleSource(text) {
|
|
|
481
491
|
}
|
|
482
492
|
function unwrapChannel(text) {
|
|
483
493
|
const m = text.match(CHANNEL_WRAPPER);
|
|
484
|
-
if (!m) return { text };
|
|
494
|
+
if (!m) return { text, wrapped: false, nested: false };
|
|
485
495
|
let source = SOURCE_MAP[m[1].match(SOURCE_ATTR)?.[1] ?? ""];
|
|
486
496
|
let inner = m[2];
|
|
487
497
|
const im = inner.match(CHANNEL_WRAPPER);
|
|
498
|
+
const nested = im !== null;
|
|
488
499
|
if (im) {
|
|
489
500
|
const innerSource = SOURCE_MAP[im[1].match(SOURCE_ATTR)?.[1] ?? ""];
|
|
490
501
|
if (innerSource) {
|
|
@@ -492,7 +503,7 @@ function unwrapChannel(text) {
|
|
|
492
503
|
inner = im[2];
|
|
493
504
|
}
|
|
494
505
|
}
|
|
495
|
-
return source ? { text: inner, source } : { text: inner };
|
|
506
|
+
return source ? { text: inner, source, wrapped: true, nested } : { text: inner, wrapped: true, nested };
|
|
496
507
|
}
|
|
497
508
|
function splitReplyNote(payload) {
|
|
498
509
|
const m = payload.match(REPLY_NOTE);
|
|
@@ -508,7 +519,17 @@ function uploadCountOf(text) {
|
|
|
508
519
|
const n = Number(text.match(UPLOAD_COUNT)?.[1]);
|
|
509
520
|
return Number.isInteger(n) && n > 0 ? n : void 0;
|
|
510
521
|
}
|
|
511
|
-
function
|
|
522
|
+
function noteEnvelope(census, u) {
|
|
523
|
+
if (!census) return;
|
|
524
|
+
census.rows++;
|
|
525
|
+
if (u.wrapped) census.wrapped++;
|
|
526
|
+
if (u.nested) census.nested++;
|
|
527
|
+
if (u.text.startsWith("<channel ")) census.unstripped++;
|
|
528
|
+
else if (u.wrapped) census.stripped++;
|
|
529
|
+
census.sources[u.source ?? "none"]++;
|
|
530
|
+
}
|
|
531
|
+
function operatorInboundTurn(u, ts, uuid, census) {
|
|
532
|
+
noteEnvelope(census, u);
|
|
512
533
|
const framed = rawFromComposedAdminFrame(u.text);
|
|
513
534
|
const split = framed === void 0 ? void 0 : splitReplyNote(framed);
|
|
514
535
|
const rawBody = split?.body;
|
|
@@ -635,12 +656,12 @@ function lastTurnTs(turns) {
|
|
|
635
656
|
function isModelGated(turns) {
|
|
636
657
|
return turns.some((t) => t.kind === "agent-error" && t.code === "model_unavailable");
|
|
637
658
|
}
|
|
638
|
-
function renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress, uuid) {
|
|
659
|
+
function renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress, uuid, census) {
|
|
639
660
|
const armed2 = queuedPendingSuppress.get(u.text) ?? 0;
|
|
640
661
|
if (armed2 > 0) {
|
|
641
662
|
queuedPendingSuppress.set(u.text, armed2 - 1);
|
|
642
663
|
} else {
|
|
643
|
-
out.push(operatorInboundTurn(u, ts, uuid));
|
|
664
|
+
out.push(operatorInboundTurn(u, ts, uuid, census));
|
|
644
665
|
}
|
|
645
666
|
}
|
|
646
667
|
function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(), turnCtx = {}) {
|
|
@@ -678,7 +699,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
|
|
|
678
699
|
const u = unwrapChannel(content);
|
|
679
700
|
turnCtx.lastChannelSource = u.source;
|
|
680
701
|
if (!isScheduleSource(content)) {
|
|
681
|
-
out.push(operatorInboundTurn(u, ts));
|
|
702
|
+
out.push(operatorInboundTurn(u, ts, void 0, turnCtx.envelopeCensus));
|
|
682
703
|
queuedPendingSuppress.set(u.text, (queuedPendingSuppress.get(u.text) ?? 0) + 1);
|
|
683
704
|
}
|
|
684
705
|
}
|
|
@@ -691,7 +712,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
|
|
|
691
712
|
const u = unwrapChannel(att.prompt);
|
|
692
713
|
turnCtx.lastChannelSource = u.source;
|
|
693
714
|
if (!isScheduleSource(att.prompt)) {
|
|
694
|
-
renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress, asString(row.uuid) ?? void 0);
|
|
715
|
+
renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress, asString(row.uuid) ?? void 0, turnCtx.envelopeCensus);
|
|
695
716
|
}
|
|
696
717
|
}
|
|
697
718
|
continue;
|
|
@@ -720,7 +741,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
|
|
|
720
741
|
const u = unwrapChannel(text);
|
|
721
742
|
turnCtx.lastChannelSource = u.source;
|
|
722
743
|
if (!isScheduleSource(text)) {
|
|
723
|
-
renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress, asString(row.uuid) ?? void 0);
|
|
744
|
+
renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress, asString(row.uuid) ?? void 0, turnCtx.envelopeCensus);
|
|
724
745
|
}
|
|
725
746
|
} else if (row.isMeta === true) {
|
|
726
747
|
out.push({ kind: "harness-internal", text, ts });
|
|
@@ -747,7 +768,7 @@ var init_parse_transcript = __esm({
|
|
|
747
768
|
HARNESS_LIFECYCLE_PREFIXES = ["<task-notification>"];
|
|
748
769
|
CC_IMAGE_VISION_HINT = /^\[Image:[^\]]*Multiply coordinates by [\d.]+ to map to original image\.\]$/;
|
|
749
770
|
COMPACT_SUMMARY_PREFIX = "This session is being continued from a previous conversation that ran out of context.";
|
|
750
|
-
CHANNEL_WRAPPER = /^<channel\b([^>]*)>\n?([\s\S]*?)\n
|
|
771
|
+
CHANNEL_WRAPPER = /^<channel\b([^>]*)>\n?([\s\S]*?)\n?<\\?\/channel>$/;
|
|
751
772
|
SOURCE_ATTR = /\bsource="([^"]+)"/;
|
|
752
773
|
SOURCE_MAP = {
|
|
753
774
|
whatsapp: "whatsapp",
|
|
@@ -815,7 +836,7 @@ var init_audio_mime = __esm({
|
|
|
815
836
|
// app/lib/stt/voice-note.ts
|
|
816
837
|
import { writeFile, mkdtemp, rm } from "fs/promises";
|
|
817
838
|
import { tmpdir as tmpdir2 } from "os";
|
|
818
|
-
import { join as
|
|
839
|
+
import { join as join7 } from "path";
|
|
819
840
|
async function transcribeVoiceNote(file, source) {
|
|
820
841
|
const startMs = Date.now();
|
|
821
842
|
const bytes = file.size;
|
|
@@ -826,9 +847,9 @@ async function transcribeVoiceNote(file, source) {
|
|
|
826
847
|
let tempDir;
|
|
827
848
|
let tempPath;
|
|
828
849
|
try {
|
|
829
|
-
tempDir = await mkdtemp(
|
|
850
|
+
tempDir = await mkdtemp(join7(tmpdir2(), "voice-"));
|
|
830
851
|
const ext = audioExtension(mimeType);
|
|
831
|
-
tempPath =
|
|
852
|
+
tempPath = join7(tempDir, `recording${ext}`);
|
|
832
853
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
833
854
|
await writeFile(tempPath, buffer);
|
|
834
855
|
} catch (err) {
|
|
@@ -1239,10 +1260,56 @@ function buildStoreConversationRows(accountId, summaries, chatMeta) {
|
|
|
1239
1260
|
}
|
|
1240
1261
|
return rows.sort((a, b) => (b.lastMessageAt ?? "").localeCompare(a.lastMessageAt ?? ""));
|
|
1241
1262
|
}
|
|
1263
|
+
function buildTelegramStoreConversationRows(accountId, summaries) {
|
|
1264
|
+
const rows = [];
|
|
1265
|
+
for (const [channelKey, { first, last }] of summaries) {
|
|
1266
|
+
rows.push({
|
|
1267
|
+
sessionId: "",
|
|
1268
|
+
projectDir: "",
|
|
1269
|
+
title: channelKey,
|
|
1270
|
+
senderId: last.senderId,
|
|
1271
|
+
startedAt: first.dateSent,
|
|
1272
|
+
channel: "telegram",
|
|
1273
|
+
role: last.scope === "admin" ? "admin" : "public",
|
|
1274
|
+
operatorName: null,
|
|
1275
|
+
whatsappName: null,
|
|
1276
|
+
// The name is what it is NOW, matching how `scope` is taken from `last`.
|
|
1277
|
+
// The fallback covers an outbound last record: the outbound write points
|
|
1278
|
+
// have no sender to name, so they store null.
|
|
1279
|
+
telegramName: last.senderName ?? first.senderName ?? null,
|
|
1280
|
+
lastMessageAt: last.dateSent,
|
|
1281
|
+
modelGated: false,
|
|
1282
|
+
model: null,
|
|
1283
|
+
source: "store",
|
|
1284
|
+
accountId,
|
|
1285
|
+
channelKey,
|
|
1286
|
+
scope: last.scope,
|
|
1287
|
+
preview: last.body
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
return rows.sort((a, b) => (b.lastMessageAt ?? "").localeCompare(a.lastMessageAt ?? ""));
|
|
1291
|
+
}
|
|
1292
|
+
function dropTelegramSessionDuplicates(rows, telegramStoreRows) {
|
|
1293
|
+
const coveredKeys = /* @__PURE__ */ new Set();
|
|
1294
|
+
const coveredSenders = /* @__PURE__ */ new Set();
|
|
1295
|
+
for (const r of telegramStoreRows) {
|
|
1296
|
+
const parsed = r.channelKey ? parseTelegramChannelKey(r.channelKey) : null;
|
|
1297
|
+
if (!parsed) continue;
|
|
1298
|
+
coveredKeys.add(r.channelKey);
|
|
1299
|
+
coveredSenders.add(parsed.senderId);
|
|
1300
|
+
}
|
|
1301
|
+
if (coveredKeys.size === 0) return rows;
|
|
1302
|
+
return rows.filter((r) => {
|
|
1303
|
+
if (r.source !== "session" || r.channel !== "telegram" || r.senderId == null) return true;
|
|
1304
|
+
const parsed = parseTelegramChannelKey(r.senderId);
|
|
1305
|
+
return parsed ? !coveredKeys.has(r.senderId) : !coveredSenders.has(r.senderId);
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1242
1308
|
var init_select_sessions = __esm({
|
|
1243
1309
|
"app/lib/reader/select-sessions.ts"() {
|
|
1244
1310
|
"use strict";
|
|
1245
1311
|
init_normalize();
|
|
1312
|
+
init_channel_key();
|
|
1246
1313
|
init_conversation_preview();
|
|
1247
1314
|
}
|
|
1248
1315
|
});
|
|
@@ -5450,19 +5517,196 @@ var require_dist7 = __commonJS({
|
|
|
5450
5517
|
|
|
5451
5518
|
// server/index.ts
|
|
5452
5519
|
init_dist();
|
|
5453
|
-
import { readFileSync as
|
|
5520
|
+
import { readFileSync as readFileSync57, existsSync as existsSync56, watchFile, readdirSync as readdirSync33, statSync as statSync30 } from "fs";
|
|
5454
5521
|
import { spawn as spawn3 } from "child_process";
|
|
5455
5522
|
import { createHash as createHash8 } from "crypto";
|
|
5456
|
-
import { resolve as resolve51, join as
|
|
5457
|
-
import { homedir as
|
|
5523
|
+
import { resolve as resolve51, join as join65, basename as basename17 } from "path";
|
|
5524
|
+
import { homedir as homedir6 } from "os";
|
|
5458
5525
|
|
|
5459
5526
|
// server/lib/route-cache-watchers.ts
|
|
5460
5527
|
init_route_cache();
|
|
5461
5528
|
init_account();
|
|
5462
5529
|
init_message_store();
|
|
5463
|
-
init_src2();
|
|
5464
5530
|
import { watch } from "fs";
|
|
5465
|
-
import { resolve } from "path";
|
|
5531
|
+
import { resolve as resolve2 } from "path";
|
|
5532
|
+
|
|
5533
|
+
// app/lib/telegram/message-store.ts
|
|
5534
|
+
import { existsSync, mkdirSync, readFileSync, appendFileSync, readdirSync, statSync } from "fs";
|
|
5535
|
+
import { homedir } from "os";
|
|
5536
|
+
import { dirname, join, resolve } from "path";
|
|
5537
|
+
function configDirName() {
|
|
5538
|
+
const platformRoot5 = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
|
|
5539
|
+
const brandPath = join(platformRoot5, "config", "brand.json");
|
|
5540
|
+
if (existsSync(brandPath)) {
|
|
5541
|
+
try {
|
|
5542
|
+
return JSON.parse(readFileSync(brandPath, "utf-8")).configDir ?? ".maxy";
|
|
5543
|
+
} catch {
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
return ".maxy";
|
|
5547
|
+
}
|
|
5548
|
+
var STORE_ROOT2 = () => join(process.env.MAXY_DIR_OVERRIDE ?? resolve(homedir(), configDirName()), "data", "telegram-messages");
|
|
5549
|
+
function isStorableChannelKey(channelKey) {
|
|
5550
|
+
return /^-?\d+:-?\d+$/.test(channelKey);
|
|
5551
|
+
}
|
|
5552
|
+
function channelKeyToFileStem(channelKey) {
|
|
5553
|
+
if (!isStorableChannelKey(channelKey)) {
|
|
5554
|
+
throw new Error(`telegram-message-store: refusing unstorable channelKey ${JSON.stringify(channelKey)}`);
|
|
5555
|
+
}
|
|
5556
|
+
return channelKey.replace(":", "_");
|
|
5557
|
+
}
|
|
5558
|
+
function fileStemToChannelKey(stem) {
|
|
5559
|
+
const i = stem.indexOf("_");
|
|
5560
|
+
return i <= 0 ? stem : `${stem.slice(0, i)}:${stem.slice(i + 1)}`;
|
|
5561
|
+
}
|
|
5562
|
+
function conversationFile(accountId, channelKey) {
|
|
5563
|
+
return join(STORE_ROOT2(), accountId, `${channelKeyToFileStem(channelKey)}.jsonl`);
|
|
5564
|
+
}
|
|
5565
|
+
function conversationFilePath2(accountId, channelKey) {
|
|
5566
|
+
return conversationFile(accountId, channelKey);
|
|
5567
|
+
}
|
|
5568
|
+
function isStorableStem(stem) {
|
|
5569
|
+
return isStorableChannelKey(fileStemToChannelKey(stem));
|
|
5570
|
+
}
|
|
5571
|
+
function listStoredAccountIds2() {
|
|
5572
|
+
const root = STORE_ROOT2();
|
|
5573
|
+
if (!existsSync(root)) return [];
|
|
5574
|
+
return readdirSync(root).filter((name) => {
|
|
5575
|
+
try {
|
|
5576
|
+
return statSync(join(root, name)).isDirectory();
|
|
5577
|
+
} catch {
|
|
5578
|
+
return false;
|
|
5579
|
+
}
|
|
5580
|
+
});
|
|
5581
|
+
}
|
|
5582
|
+
function parseLines(raw) {
|
|
5583
|
+
const out = [];
|
|
5584
|
+
for (const line of raw.split("\n")) {
|
|
5585
|
+
if (!line.trim()) continue;
|
|
5586
|
+
try {
|
|
5587
|
+
out.push(JSON.parse(line));
|
|
5588
|
+
} catch {
|
|
5589
|
+
console.error(`[telegram-message-store] skip reason=corrupt-line`);
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
return out;
|
|
5593
|
+
}
|
|
5594
|
+
var byDateSentAsc = (a, b) => a.dateSent.localeCompare(b.dateSent);
|
|
5595
|
+
function appendMessage2(accountId, record) {
|
|
5596
|
+
if (!isStorableChannelKey(record.channelKey)) {
|
|
5597
|
+
console.error(
|
|
5598
|
+
`[telegram-message-store] FAIL op=append reason=unstorable-channel-key messageId=${record.messageId}`
|
|
5599
|
+
);
|
|
5600
|
+
return { created: false };
|
|
5601
|
+
}
|
|
5602
|
+
const file = conversationFile(accountId, record.channelKey);
|
|
5603
|
+
try {
|
|
5604
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
5605
|
+
} catch (err) {
|
|
5606
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5607
|
+
console.error(
|
|
5608
|
+
`[telegram-message-store] FAIL op=append reason=${reason.slice(0, 100)} messageId=${record.messageId}`
|
|
5609
|
+
);
|
|
5610
|
+
return { created: false };
|
|
5611
|
+
}
|
|
5612
|
+
try {
|
|
5613
|
+
if (existsSync(file)) {
|
|
5614
|
+
const existing = parseLines(readFileSync(file, "utf8"));
|
|
5615
|
+
if (existing.some((r) => r.messageId === record.messageId)) {
|
|
5616
|
+
console.error(
|
|
5617
|
+
`[telegram-message-store] op=append channelKey=${record.channelKey} origin=${record.origin} created=false`
|
|
5618
|
+
);
|
|
5619
|
+
return { created: false };
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
appendFileSync(file, `${JSON.stringify(record)}
|
|
5623
|
+
`);
|
|
5624
|
+
console.error(
|
|
5625
|
+
`[telegram-message-store] op=append channelKey=${record.channelKey} origin=${record.origin} created=true`
|
|
5626
|
+
);
|
|
5627
|
+
return { created: true };
|
|
5628
|
+
} catch (err) {
|
|
5629
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5630
|
+
console.error(
|
|
5631
|
+
`[telegram-message-store] FAIL op=append reason=${reason.slice(0, 100)} messageId=${record.messageId}`
|
|
5632
|
+
);
|
|
5633
|
+
return { created: false };
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
function readConversation2(accountId, channelKey) {
|
|
5637
|
+
if (!isStorableChannelKey(channelKey)) return [];
|
|
5638
|
+
const file = conversationFile(accountId, channelKey);
|
|
5639
|
+
try {
|
|
5640
|
+
if (!existsSync(file)) return [];
|
|
5641
|
+
return parseLines(readFileSync(file, "utf8")).sort(byDateSentAsc);
|
|
5642
|
+
} catch (err) {
|
|
5643
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5644
|
+
console.error(
|
|
5645
|
+
`[telegram-message-store] FAIL op=read reason=${reason.slice(0, 100)} channelKey=${channelKey}`
|
|
5646
|
+
);
|
|
5647
|
+
return [];
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
var summaryCache = /* @__PURE__ */ new Map();
|
|
5651
|
+
function summariseRecords(records2) {
|
|
5652
|
+
if (records2.length === 0) return null;
|
|
5653
|
+
const sorted = records2.slice().sort(byDateSentAsc);
|
|
5654
|
+
return { first: sorted[0], last: sorted[sorted.length - 1] };
|
|
5655
|
+
}
|
|
5656
|
+
function readConversationSummaries2(accountId) {
|
|
5657
|
+
const dir = join(STORE_ROOT2(), accountId);
|
|
5658
|
+
const out = /* @__PURE__ */ new Map();
|
|
5659
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5660
|
+
try {
|
|
5661
|
+
if (!existsSync(dir)) return out;
|
|
5662
|
+
for (const entry2 of readdirSync(dir)) {
|
|
5663
|
+
if (!entry2.endsWith(".jsonl")) continue;
|
|
5664
|
+
const stem = entry2.slice(0, -".jsonl".length);
|
|
5665
|
+
if (!isStorableStem(stem)) continue;
|
|
5666
|
+
const channelKey = fileStemToChannelKey(stem);
|
|
5667
|
+
const file = join(dir, entry2);
|
|
5668
|
+
seen.add(file);
|
|
5669
|
+
let size;
|
|
5670
|
+
let mtimeMs;
|
|
5671
|
+
try {
|
|
5672
|
+
const st = statSync(file);
|
|
5673
|
+
size = st.size;
|
|
5674
|
+
mtimeMs = st.mtimeMs;
|
|
5675
|
+
} catch {
|
|
5676
|
+
continue;
|
|
5677
|
+
}
|
|
5678
|
+
const cached3 = summaryCache.get(file);
|
|
5679
|
+
if (cached3 && cached3.size === size && cached3.mtimeMs === mtimeMs) {
|
|
5680
|
+
out.set(channelKey, cached3.summary);
|
|
5681
|
+
continue;
|
|
5682
|
+
}
|
|
5683
|
+
let summary;
|
|
5684
|
+
try {
|
|
5685
|
+
summary = summariseRecords(parseLines(readFileSync(file, "utf8")));
|
|
5686
|
+
} catch (err) {
|
|
5687
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5688
|
+
console.error(
|
|
5689
|
+
`[telegram-message-store] FAIL op=summarise reason=${reason.slice(0, 100)} channelKey=${channelKey}`
|
|
5690
|
+
);
|
|
5691
|
+
continue;
|
|
5692
|
+
}
|
|
5693
|
+
if (!summary) continue;
|
|
5694
|
+
summaryCache.set(file, { size, mtimeMs, summary });
|
|
5695
|
+
out.set(channelKey, summary);
|
|
5696
|
+
}
|
|
5697
|
+
} catch (err) {
|
|
5698
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5699
|
+
console.error(`[telegram-message-store] FAIL op=summarise-dir reason=${reason.slice(0, 100)}`);
|
|
5700
|
+
return out;
|
|
5701
|
+
}
|
|
5702
|
+
for (const key2 of [...summaryCache.keys()]) {
|
|
5703
|
+
if (key2.startsWith(dir) && !seen.has(key2)) summaryCache.delete(key2);
|
|
5704
|
+
}
|
|
5705
|
+
return out;
|
|
5706
|
+
}
|
|
5707
|
+
|
|
5708
|
+
// server/lib/route-cache-watchers.ts
|
|
5709
|
+
init_src2();
|
|
5466
5710
|
function watchDirInto(dir, resource, accountId) {
|
|
5467
5711
|
try {
|
|
5468
5712
|
const w = watch(dir, { recursive: true }, () => {
|
|
@@ -5483,9 +5727,9 @@ function watchDirInto(dir, resource, accountId) {
|
|
|
5483
5727
|
var perAccountAttached = /* @__PURE__ */ new Set();
|
|
5484
5728
|
function ensureAccountWatchers(accountId, accountDir) {
|
|
5485
5729
|
for (const [dir, resource] of [
|
|
5486
|
-
[
|
|
5487
|
-
[
|
|
5488
|
-
[
|
|
5730
|
+
[resolve2(accountDir, "plugins"), "skills"],
|
|
5731
|
+
[resolve2(accountDir, "agents", "admin"), "sidebar-artefacts"],
|
|
5732
|
+
[resolve2(accountDir, "specialists", "agents"), "sidebar-artefacts"]
|
|
5489
5733
|
]) {
|
|
5490
5734
|
if (perAccountAttached.has(dir)) continue;
|
|
5491
5735
|
const { attached: attached2 } = watchDirInto(dir, resource, accountId);
|
|
@@ -5498,11 +5742,11 @@ function attachRouteCacheWatchers() {
|
|
|
5498
5742
|
attached = true;
|
|
5499
5743
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
5500
5744
|
if (configDir) {
|
|
5501
|
-
watchDirInto(
|
|
5745
|
+
watchDirInto(resolve2(configDir, "projects"), "sidebar-sessions", null);
|
|
5502
5746
|
} else {
|
|
5503
5747
|
console.error("[admin-events] op=watch-failed resource=sidebar-sessions reason=CLAUDE_CONFIG_DIR-unset");
|
|
5504
5748
|
}
|
|
5505
|
-
watchDirInto(
|
|
5749
|
+
watchDirInto(resolve2(PLATFORM_ROOT, "templates", "specialists", "agents"), "sidebar-artefacts", null);
|
|
5506
5750
|
let houseId = null;
|
|
5507
5751
|
try {
|
|
5508
5752
|
houseId = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
|
|
@@ -5511,6 +5755,7 @@ function attachRouteCacheWatchers() {
|
|
|
5511
5755
|
}
|
|
5512
5756
|
if (houseId) {
|
|
5513
5757
|
watchDirInto(STORE_ROOT(), "conversations-all", houseId);
|
|
5758
|
+
watchDirInto(STORE_ROOT2(), "conversations-all", houseId);
|
|
5514
5759
|
} else {
|
|
5515
5760
|
console.error("[admin-events] op=watch-failed resource=conversations-all reason=house-account-unresolved");
|
|
5516
5761
|
}
|
|
@@ -5707,8 +5952,8 @@ var _baseMimes = {
|
|
|
5707
5952
|
var baseMimes = _baseMimes;
|
|
5708
5953
|
|
|
5709
5954
|
// node_modules/@hono/node-server/dist/serve-static.mjs
|
|
5710
|
-
import { createReadStream, statSync, existsSync } from "fs";
|
|
5711
|
-
import { join } from "path";
|
|
5955
|
+
import { createReadStream, statSync as statSync2, existsSync as existsSync2 } from "fs";
|
|
5956
|
+
import { join as join2 } from "path";
|
|
5712
5957
|
import { versions } from "process";
|
|
5713
5958
|
import { Readable } from "stream";
|
|
5714
5959
|
var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
|
|
@@ -5748,7 +5993,7 @@ var createStreamBody = (stream2) => {
|
|
|
5748
5993
|
var getStats = (path) => {
|
|
5749
5994
|
let stats;
|
|
5750
5995
|
try {
|
|
5751
|
-
stats =
|
|
5996
|
+
stats = statSync2(path);
|
|
5752
5997
|
} catch {
|
|
5753
5998
|
}
|
|
5754
5999
|
return stats;
|
|
@@ -5770,7 +6015,7 @@ var tryDecodeURI = (str2) => tryDecode(str2, decodeURI);
|
|
|
5770
6015
|
var serveStatic = (options = { root: "" }) => {
|
|
5771
6016
|
const root = options.root || "";
|
|
5772
6017
|
const optionPath = options.path;
|
|
5773
|
-
if (root !== "" && !
|
|
6018
|
+
if (root !== "" && !existsSync2(root)) {
|
|
5774
6019
|
console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
|
|
5775
6020
|
}
|
|
5776
6021
|
return async (c, next) => {
|
|
@@ -5791,14 +6036,14 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
5791
6036
|
return next();
|
|
5792
6037
|
}
|
|
5793
6038
|
}
|
|
5794
|
-
let path =
|
|
6039
|
+
let path = join2(
|
|
5795
6040
|
root,
|
|
5796
6041
|
!optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename
|
|
5797
6042
|
);
|
|
5798
6043
|
let stats = getStats(path);
|
|
5799
6044
|
if (stats && stats.isDirectory()) {
|
|
5800
6045
|
const indexFile2 = options.index ?? "index.html";
|
|
5801
|
-
path =
|
|
6046
|
+
path = join2(path, indexFile2);
|
|
5802
6047
|
stats = getStats(path);
|
|
5803
6048
|
}
|
|
5804
6049
|
if (!stats) {
|
|
@@ -5856,13 +6101,13 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
5856
6101
|
};
|
|
5857
6102
|
|
|
5858
6103
|
// server/lib/static-cache.ts
|
|
5859
|
-
import { statSync as
|
|
5860
|
-
import { join as
|
|
6104
|
+
import { statSync as statSync3 } from "fs";
|
|
6105
|
+
import { join as join3 } from "path";
|
|
5861
6106
|
var IMMUTABLE = "public, max-age=31536000, immutable";
|
|
5862
6107
|
var REVALIDATE = "public, no-cache";
|
|
5863
6108
|
function statOrNull(path) {
|
|
5864
6109
|
try {
|
|
5865
|
-
return
|
|
6110
|
+
return statSync3(path);
|
|
5866
6111
|
} catch {
|
|
5867
6112
|
return null;
|
|
5868
6113
|
}
|
|
@@ -5883,10 +6128,10 @@ function tryDecodeURI2(str2) {
|
|
|
5883
6128
|
function resolveStaticFile(root, reqPath) {
|
|
5884
6129
|
const filename = tryDecodeURI2(reqPath);
|
|
5885
6130
|
if (/(?:^|[/\\])\.\.(?:$|[/\\])/.test(filename)) return null;
|
|
5886
|
-
let path =
|
|
6131
|
+
let path = join3(root, filename);
|
|
5887
6132
|
let stats = statOrNull(path);
|
|
5888
6133
|
if (stats?.isDirectory()) {
|
|
5889
|
-
path =
|
|
6134
|
+
path = join3(path, "index.html");
|
|
5890
6135
|
stats = statOrNull(path);
|
|
5891
6136
|
}
|
|
5892
6137
|
if (!stats?.isFile()) return null;
|
|
@@ -5961,8 +6206,8 @@ ${clientErrorReporterBody}
|
|
|
5961
6206
|
</script>`;
|
|
5962
6207
|
|
|
5963
6208
|
// app/lib/whatsapp/ingest-audit.ts
|
|
5964
|
-
import { readdirSync, readFileSync, statSync as
|
|
5965
|
-
import { join as
|
|
6209
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync4 } from "fs";
|
|
6210
|
+
import { join as join4 } from "path";
|
|
5966
6211
|
init_message_store();
|
|
5967
6212
|
var TAG2 = "[wa-ingest-audit]";
|
|
5968
6213
|
var NOTIFY_TAG = "[wa-notify]";
|
|
@@ -5983,16 +6228,16 @@ function readAllConversationsCached(accountId) {
|
|
|
5983
6228
|
const out = /* @__PURE__ */ new Map();
|
|
5984
6229
|
let dir;
|
|
5985
6230
|
try {
|
|
5986
|
-
dir =
|
|
6231
|
+
dir = readdirSync2(join4(STORE_ROOT(), accountId));
|
|
5987
6232
|
} catch {
|
|
5988
6233
|
return out;
|
|
5989
6234
|
}
|
|
5990
6235
|
for (const file of dir) {
|
|
5991
6236
|
if (!file.endsWith(".jsonl")) continue;
|
|
5992
|
-
const path =
|
|
6237
|
+
const path = join4(STORE_ROOT(), accountId, file);
|
|
5993
6238
|
let stat11;
|
|
5994
6239
|
try {
|
|
5995
|
-
stat11 =
|
|
6240
|
+
stat11 = statSync4(path);
|
|
5996
6241
|
} catch {
|
|
5997
6242
|
continue;
|
|
5998
6243
|
}
|
|
@@ -6003,7 +6248,7 @@ function readAllConversationsCached(accountId) {
|
|
|
6003
6248
|
}
|
|
6004
6249
|
const parsed = [];
|
|
6005
6250
|
try {
|
|
6006
|
-
for (const line of
|
|
6251
|
+
for (const line of readFileSync2(path, "utf8").split("\n")) {
|
|
6007
6252
|
if (!line.trim()) continue;
|
|
6008
6253
|
try {
|
|
6009
6254
|
const row = JSON.parse(line);
|
|
@@ -6119,17 +6364,17 @@ function runNotifyReconcile(opts) {
|
|
|
6119
6364
|
init_parse_transcript();
|
|
6120
6365
|
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
6121
6366
|
import { tmpdir } from "os";
|
|
6122
|
-
import { join as
|
|
6367
|
+
import { join as join5 } from "path";
|
|
6123
6368
|
|
|
6124
6369
|
// server/lib/tail-window.ts
|
|
6125
|
-
import { openSync, readSync, closeSync, statSync as
|
|
6370
|
+
import { openSync, readSync, closeSync, statSync as statSync5 } from "fs";
|
|
6126
6371
|
var TAIL_WINDOW_BYTES = 1024 * 1024;
|
|
6127
6372
|
var ALIGN_SCAN_MAX_BYTES = 4 * 1024 * 1024;
|
|
6128
6373
|
function tailWindowStart(fileEnd, budget = TAIL_WINDOW_BYTES) {
|
|
6129
6374
|
return Math.max(0, fileEnd - budget);
|
|
6130
6375
|
}
|
|
6131
6376
|
function readRange(path, from, to) {
|
|
6132
|
-
const size =
|
|
6377
|
+
const size = statSync5(path).size;
|
|
6133
6378
|
const start = Math.max(0, from);
|
|
6134
6379
|
const end = Math.min(to, size);
|
|
6135
6380
|
if (end <= start) return Buffer.alloc(0);
|
|
@@ -6251,8 +6496,62 @@ function censusSnapshot() {
|
|
|
6251
6496
|
return { windowed: windowedOpens.size, withBackfill };
|
|
6252
6497
|
}
|
|
6253
6498
|
|
|
6499
|
+
// server/lib/frame-strip-census.ts
|
|
6500
|
+
var MAX_ACCOUNTS = 50;
|
|
6501
|
+
var MAX_UNSTRIPPED_SESSIONS = 3;
|
|
6502
|
+
var byAccount = /* @__PURE__ */ new Map();
|
|
6503
|
+
function rowFor(accountId) {
|
|
6504
|
+
const existing = byAccount.get(accountId);
|
|
6505
|
+
if (existing) return existing;
|
|
6506
|
+
if (byAccount.size >= MAX_ACCOUNTS) {
|
|
6507
|
+
const oldest = byAccount.keys().next();
|
|
6508
|
+
if (!oldest.done) byAccount.delete(oldest.value);
|
|
6509
|
+
}
|
|
6510
|
+
const fresh = {
|
|
6511
|
+
accountId,
|
|
6512
|
+
rows: 0,
|
|
6513
|
+
wrapped: 0,
|
|
6514
|
+
nested: 0,
|
|
6515
|
+
stripped: 0,
|
|
6516
|
+
unstripped: 0,
|
|
6517
|
+
sources: { whatsapp: 0, webchat: 0, telegram: 0, none: 0 },
|
|
6518
|
+
unstrippedSessions: []
|
|
6519
|
+
};
|
|
6520
|
+
byAccount.set(accountId, fresh);
|
|
6521
|
+
return fresh;
|
|
6522
|
+
}
|
|
6523
|
+
function foldFrameCensus(accountId, sessionId, acc) {
|
|
6524
|
+
if (acc.rows === 0) return;
|
|
6525
|
+
const row = rowFor(accountId);
|
|
6526
|
+
row.rows += acc.rows;
|
|
6527
|
+
row.wrapped += acc.wrapped;
|
|
6528
|
+
row.nested += acc.nested;
|
|
6529
|
+
row.stripped += acc.stripped;
|
|
6530
|
+
row.unstripped += acc.unstripped;
|
|
6531
|
+
row.sources.whatsapp += acc.sources.whatsapp;
|
|
6532
|
+
row.sources.webchat += acc.sources.webchat;
|
|
6533
|
+
row.sources.telegram += acc.sources.telegram;
|
|
6534
|
+
row.sources.none += acc.sources.none;
|
|
6535
|
+
if (acc.unstripped > 0 && row.unstrippedSessions.length < MAX_UNSTRIPPED_SESSIONS && !row.unstrippedSessions.includes(sessionId)) {
|
|
6536
|
+
row.unstrippedSessions.push(sessionId);
|
|
6537
|
+
}
|
|
6538
|
+
acc.rows = 0;
|
|
6539
|
+
acc.wrapped = 0;
|
|
6540
|
+
acc.nested = 0;
|
|
6541
|
+
acc.stripped = 0;
|
|
6542
|
+
acc.unstripped = 0;
|
|
6543
|
+
acc.sources.whatsapp = 0;
|
|
6544
|
+
acc.sources.webchat = 0;
|
|
6545
|
+
acc.sources.telegram = 0;
|
|
6546
|
+
acc.sources.none = 0;
|
|
6547
|
+
}
|
|
6548
|
+
function frameCensusSnapshot() {
|
|
6549
|
+
return [...byAccount.values()];
|
|
6550
|
+
}
|
|
6551
|
+
|
|
6254
6552
|
// server/lib/reader-window-audit.ts
|
|
6255
6553
|
var TAG3 = "[reader-audit]";
|
|
6554
|
+
var FRAME_TAG = "[chat-reader]";
|
|
6256
6555
|
var FIXTURE_ROWS = 60;
|
|
6257
6556
|
var FIXTURE_BUDGET = 2e3;
|
|
6258
6557
|
function fixtureBody() {
|
|
@@ -6286,8 +6585,25 @@ var key = (t) => JSON.stringify(t);
|
|
|
6286
6585
|
function runReaderWindowAudit() {
|
|
6287
6586
|
const census = censusSnapshot();
|
|
6288
6587
|
console.log(`${TAG3} op=backfill-reach windowed=${census.windowed} withBackfill=${census.withBackfill}`);
|
|
6289
|
-
const
|
|
6290
|
-
|
|
6588
|
+
const frames = frameCensusSnapshot();
|
|
6589
|
+
let frameRows = 0;
|
|
6590
|
+
let frameUnstripped = 0;
|
|
6591
|
+
if (frames.length === 0) {
|
|
6592
|
+
console.log(`${FRAME_TAG} op=frame-census accounts=0`);
|
|
6593
|
+
}
|
|
6594
|
+
for (const f of frames) {
|
|
6595
|
+
frameRows += f.rows;
|
|
6596
|
+
frameUnstripped += f.unstripped;
|
|
6597
|
+
console.log(
|
|
6598
|
+
`${FRAME_TAG} op=frame-census accountId=${f.accountId} rows=${f.rows} wrapped=${f.wrapped} nested=${f.nested} stripped=${f.stripped} sources=whatsapp:${f.sources.whatsapp},webchat:${f.sources.webchat},telegram:${f.sources.telegram},none:${f.sources.none}`
|
|
6599
|
+
);
|
|
6600
|
+
console.log(
|
|
6601
|
+
`${FRAME_TAG} op=frame-unstripped accountId=${f.accountId} rows=${f.unstripped} sessions=${f.unstrippedSessions.join(",")}`
|
|
6602
|
+
);
|
|
6603
|
+
}
|
|
6604
|
+
const frameSummary = `frames=${frameRows} unstripped=${frameUnstripped}`;
|
|
6605
|
+
const dir = mkdtempSync(join5(tmpdir(), "reader-audit-"));
|
|
6606
|
+
const path = join5(dir, "fixture.jsonl");
|
|
6291
6607
|
try {
|
|
6292
6608
|
const body = fixtureBody();
|
|
6293
6609
|
writeFileSync(path, body);
|
|
@@ -6332,11 +6648,11 @@ function runReaderWindowAudit() {
|
|
|
6332
6648
|
`${TAG3} op=window-kinds fixture=synthetic windowTurns=${windowTurns.length} mismatched=${mismatched}`
|
|
6333
6649
|
);
|
|
6334
6650
|
const ok2 = duplicates === 0 && gaps === 0 && ordered && walkTerminated && mismatched === 0;
|
|
6335
|
-
return `continuity=${ok2 ? "ok" : "FAILED"} full=${full.length} assembled=${assembled.length} walk=${walkTerminated ? "ok" : "STUCK"} reach=${census.withBackfill}/${census.windowed}`;
|
|
6651
|
+
return `continuity=${ok2 ? "ok" : "FAILED"} full=${full.length} assembled=${assembled.length} walk=${walkTerminated ? "ok" : "STUCK"} reach=${census.withBackfill}/${census.windowed} ${frameSummary}`;
|
|
6336
6652
|
} catch (err) {
|
|
6337
6653
|
const reason = err instanceof Error ? err.message : String(err);
|
|
6338
6654
|
console.error(`${TAG3} op=audit-failed reason=${reason.slice(0, 120)}`);
|
|
6339
|
-
return `continuity=ERROR ${reason.slice(0, 60)}`;
|
|
6655
|
+
return `continuity=ERROR ${reason.slice(0, 60)} ${frameSummary}`;
|
|
6340
6656
|
} finally {
|
|
6341
6657
|
try {
|
|
6342
6658
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -6398,12 +6714,12 @@ init_src();
|
|
|
6398
6714
|
init_pwa();
|
|
6399
6715
|
|
|
6400
6716
|
// server/webchat-descriptor.ts
|
|
6401
|
-
import { resolve as
|
|
6717
|
+
import { resolve as resolve3, join as join6 } from "path";
|
|
6402
6718
|
function webchatGatewayUrl() {
|
|
6403
6719
|
return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
|
|
6404
6720
|
}
|
|
6405
6721
|
function webchatServerPath() {
|
|
6406
|
-
return process.env.MAXY_WEBCHAT_CHANNEL_SERVER_PATH ??
|
|
6722
|
+
return process.env.MAXY_WEBCHAT_CHANNEL_SERVER_PATH ?? resolve3(process.env.MAXY_PLATFORM_ROOT ?? join6(__dirname, ".."), "services/webchat-channel/dist/server.js");
|
|
6407
6723
|
}
|
|
6408
6724
|
function webchatChannelDescriptor(key2) {
|
|
6409
6725
|
return { key: key2, gatewayUrl: webchatGatewayUrl(), serverPath: webchatServerPath() };
|
|
@@ -6421,7 +6737,7 @@ init_paths();
|
|
|
6421
6737
|
// app/lib/claude-auth.ts
|
|
6422
6738
|
init_paths();
|
|
6423
6739
|
init_loop_registry();
|
|
6424
|
-
import { readFileSync as
|
|
6740
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
|
|
6425
6741
|
import { sep } from "path";
|
|
6426
6742
|
import { lock } from "proper-lockfile";
|
|
6427
6743
|
if (!CLAUDE_CREDENTIALS_FILE.startsWith(MAXY_DIR + sep)) {
|
|
@@ -6438,7 +6754,7 @@ var REFRESH_NOT_FIRING_THRESHOLD = 2;
|
|
|
6438
6754
|
function readCredentials() {
|
|
6439
6755
|
let raw;
|
|
6440
6756
|
try {
|
|
6441
|
-
raw =
|
|
6757
|
+
raw = readFileSync3(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
6442
6758
|
} catch {
|
|
6443
6759
|
return null;
|
|
6444
6760
|
}
|
|
@@ -6461,20 +6777,32 @@ function readCredentials() {
|
|
|
6461
6777
|
expiresAt
|
|
6462
6778
|
};
|
|
6463
6779
|
}
|
|
6464
|
-
function
|
|
6780
|
+
function logCredentialWrite(site, refreshTokenLen) {
|
|
6781
|
+
console.log(`[claude-auth] op=cred-write site=${site} refreshTokenLen=${refreshTokenLen}`);
|
|
6782
|
+
}
|
|
6783
|
+
function writeHostCredsFile(credentialsFile, blob, refreshTokenLen) {
|
|
6784
|
+
const tmp = `${credentialsFile}.${process.pid}.tmp`;
|
|
6785
|
+
writeFileSync2(tmp, blob, { mode: 384 });
|
|
6786
|
+
renameSync(tmp, credentialsFile);
|
|
6787
|
+
logCredentialWrite("login-writeback", refreshTokenLen);
|
|
6788
|
+
}
|
|
6789
|
+
function writeOAuthCredentials(tokens, site) {
|
|
6465
6790
|
const newExpiresAt = typeof tokens.expiresIn === "number" && tokens.expiresIn > 0 ? Date.now() + tokens.expiresIn * 1e3 : Date.now() + 3600 * 1e3;
|
|
6466
6791
|
try {
|
|
6467
6792
|
let fileData = {};
|
|
6468
6793
|
try {
|
|
6469
|
-
const rawFile =
|
|
6794
|
+
const rawFile = readFileSync3(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
6470
6795
|
fileData = JSON.parse(rawFile);
|
|
6471
6796
|
} catch {
|
|
6472
6797
|
}
|
|
6473
6798
|
const existingOauth = fileData.claudeAiOauth;
|
|
6799
|
+
const existingRefresh = typeof existingOauth?.refreshToken === "string" ? existingOauth.refreshToken : "";
|
|
6800
|
+
const incomingRefresh = typeof tokens.refreshToken === "string" ? tokens.refreshToken : "";
|
|
6801
|
+
const blankingRefused = incomingRefresh.length === 0 && existingRefresh.length > 0;
|
|
6474
6802
|
fileData.claudeAiOauth = {
|
|
6475
6803
|
...existingOauth,
|
|
6476
6804
|
accessToken: tokens.accessToken,
|
|
6477
|
-
...
|
|
6805
|
+
...incomingRefresh.length > 0 ? { refreshToken: incomingRefresh } : {},
|
|
6478
6806
|
expiresAt: newExpiresAt
|
|
6479
6807
|
};
|
|
6480
6808
|
const tmpPath = `${CLAUDE_CREDENTIALS_FILE}.${process.pid}.tmp`;
|
|
@@ -6488,6 +6816,13 @@ function writeOAuthCredentials(tokens) {
|
|
|
6488
6816
|
}
|
|
6489
6817
|
throw writeErr;
|
|
6490
6818
|
}
|
|
6819
|
+
if (blankingRefused) {
|
|
6820
|
+
console.warn(
|
|
6821
|
+
`[claude-auth] op=cred-write-refused site=${site} reason=blank-refresh-token storedLen=${existingRefresh.length}`
|
|
6822
|
+
);
|
|
6823
|
+
}
|
|
6824
|
+
const landed = fileData.claudeAiOauth.refreshToken;
|
|
6825
|
+
logCredentialWrite(site, typeof landed === "string" ? landed.length : 0);
|
|
6491
6826
|
return newExpiresAt;
|
|
6492
6827
|
} catch (err) {
|
|
6493
6828
|
console.error(`[claude-auth] credential write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6617,9 +6952,11 @@ async function doRefreshLocked() {
|
|
|
6617
6952
|
}
|
|
6618
6953
|
const newExpiresAt = writeOAuthCredentials({
|
|
6619
6954
|
accessToken: newAccessToken,
|
|
6620
|
-
|
|
6955
|
+
// Task 2572 — `??` does not fall back on the empty string, so a response
|
|
6956
|
+
// carrying `refresh_token: ""` used to overwrite a good stored token.
|
|
6957
|
+
refreshToken: typeof newRefreshToken === "string" && newRefreshToken.length > 0 ? newRefreshToken : creds.refreshToken,
|
|
6621
6958
|
expiresIn: typeof expiresIn === "number" && expiresIn > 0 ? expiresIn : void 0
|
|
6622
|
-
});
|
|
6959
|
+
}, "refresh");
|
|
6623
6960
|
if (newExpiresAt) {
|
|
6624
6961
|
console.log(`[auth-refresh] op=renewed expiresAt=${new Date(newExpiresAt).toISOString()}`);
|
|
6625
6962
|
} else {
|
|
@@ -6694,7 +7031,7 @@ init_helpers();
|
|
|
6694
7031
|
// server/routes/health.ts
|
|
6695
7032
|
init_dist();
|
|
6696
7033
|
init_paths();
|
|
6697
|
-
import { existsSync as
|
|
7034
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
6698
7035
|
import { createConnection as createConnection2 } from "net";
|
|
6699
7036
|
|
|
6700
7037
|
// app/lib/network.ts
|
|
@@ -6719,11 +7056,11 @@ function getLanIp() {
|
|
|
6719
7056
|
init_paths();
|
|
6720
7057
|
import { spawnSync, execFileSync } from "child_process";
|
|
6721
7058
|
import { createConnection } from "net";
|
|
6722
|
-
import { mkdirSync, readFileSync as
|
|
6723
|
-
import { resolve as
|
|
7059
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
7060
|
+
import { resolve as resolve4 } from "path";
|
|
6724
7061
|
init_remote_auth();
|
|
6725
|
-
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ??
|
|
6726
|
-
var VNC_SCRIPT =
|
|
7062
|
+
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
7063
|
+
var VNC_SCRIPT = resolve4(PLATFORM_ROOT2, "scripts/vnc.sh");
|
|
6727
7064
|
var displayMode = process.env.DISPLAY_MODE ?? "virtual";
|
|
6728
7065
|
if (displayMode === "native") {
|
|
6729
7066
|
console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
|
|
@@ -6778,7 +7115,7 @@ function discoverNativeDisplay() {
|
|
|
6778
7115
|
const leaderPid = leaderResult.stdout?.trim();
|
|
6779
7116
|
if (leaderPid) {
|
|
6780
7117
|
try {
|
|
6781
|
-
const environ =
|
|
7118
|
+
const environ = readFileSync4(`/proc/${leaderPid}/environ`, "utf8");
|
|
6782
7119
|
const match = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
|
|
6783
7120
|
if (match) waylandDisplay = match.split("=")[1];
|
|
6784
7121
|
} catch {
|
|
@@ -6833,10 +7170,10 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
6833
7170
|
return false;
|
|
6834
7171
|
}
|
|
6835
7172
|
function ensureLogDir() {
|
|
6836
|
-
|
|
7173
|
+
mkdirSync2(LOG_DIR, { recursive: true });
|
|
6837
7174
|
}
|
|
6838
7175
|
function logPath(name) {
|
|
6839
|
-
return
|
|
7176
|
+
return resolve4(LOG_DIR, `${name}.log`);
|
|
6840
7177
|
}
|
|
6841
7178
|
async function ensureVnc() {
|
|
6842
7179
|
if (process.platform !== "linux") {
|
|
@@ -6921,19 +7258,36 @@ async function ensureCdp(transport = "vnc") {
|
|
|
6921
7258
|
function killChromium() {
|
|
6922
7259
|
spawnSync("pkill", ["-f", "chromium"], { stdio: "pipe" });
|
|
6923
7260
|
}
|
|
6924
|
-
var AUTH_TARGET_ID_FILE =
|
|
7261
|
+
var AUTH_TARGET_ID_FILE = resolve4(LOG_DIR, "claude-auth-target.id");
|
|
6925
7262
|
function readAuthTargetId() {
|
|
6926
7263
|
try {
|
|
6927
|
-
const
|
|
7264
|
+
const parts = readFileSync4(AUTH_TARGET_ID_FILE, "utf8").trim().split(/\s+/);
|
|
7265
|
+
const id = parts[parts.length - 1];
|
|
6928
7266
|
return id.length > 0 ? id : null;
|
|
6929
7267
|
} catch {
|
|
6930
7268
|
return null;
|
|
6931
7269
|
}
|
|
6932
7270
|
}
|
|
6933
|
-
function
|
|
6934
|
-
|
|
7271
|
+
function readAuthAttemptId() {
|
|
7272
|
+
try {
|
|
7273
|
+
const parts = readFileSync4(AUTH_TARGET_ID_FILE, "utf8").trim().split(/\s+/);
|
|
7274
|
+
return parts.length === 2 && parts[0].length > 0 ? parts[0] : null;
|
|
7275
|
+
} catch {
|
|
7276
|
+
return null;
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7279
|
+
async function closeAuthTarget(targetId) {
|
|
7280
|
+
try {
|
|
7281
|
+
const res = await fetch(`http://127.0.0.1:${CDP_PORT}/json/close/${encodeURIComponent(targetId)}`);
|
|
7282
|
+
return res.ok;
|
|
7283
|
+
} catch {
|
|
7284
|
+
return false;
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
function writeChromiumWrapper(attemptId) {
|
|
7288
|
+
mkdirSync2(BIN_DIR, { recursive: true });
|
|
6935
7289
|
rmSync2(AUTH_TARGET_ID_FILE, { force: true });
|
|
6936
|
-
const wrapperPath =
|
|
7290
|
+
const wrapperPath = resolve4(BIN_DIR, "chromium");
|
|
6937
7291
|
writeFileSync3(wrapperPath, `#!/bin/bash
|
|
6938
7292
|
# Generated by writeChromiumWrapper (app/lib/vnc.ts) \u2014 Task 1562.
|
|
6939
7293
|
# Hands its URL argument to the brand's running Chromium over CDP.
|
|
@@ -6941,6 +7295,9 @@ LOG_DIR="${LOG_DIR}"
|
|
|
6941
7295
|
LOG="$LOG_DIR/chromium.log"
|
|
6942
7296
|
TARGET_ID_FILE="$LOG_DIR/claude-auth-target.id"
|
|
6943
7297
|
CDP="http://127.0.0.1:${CDP_PORT}/json/new"
|
|
7298
|
+
# The sign-in this wrapper was generated for. Server-minted hex from
|
|
7299
|
+
# randomUUID().slice(0, 8), never caller-supplied, so it carries no shell.
|
|
7300
|
+
ATTEMPT_ID="${attemptId}"
|
|
6944
7301
|
|
|
6945
7302
|
mkdir -p "$LOG_DIR"
|
|
6946
7303
|
echo "==== [$(date)] chromium wrapper (cdp-handover) ====" >> "$LOG"
|
|
@@ -6972,9 +7329,9 @@ if [ -z "$TARGET_ID" ]; then
|
|
|
6972
7329
|
exit 65
|
|
6973
7330
|
fi
|
|
6974
7331
|
|
|
6975
|
-
printf '%s' "$TARGET_ID" > "$TARGET_ID_FILE"
|
|
6976
|
-
echo " opened target $TARGET_ID" >> "$LOG"
|
|
6977
|
-
echo "[chromium-wrapper] op=cdp-handover outcome=ready targetId=$TARGET_ID" >> "$LOG"
|
|
7332
|
+
printf '%s %s' "$ATTEMPT_ID" "$TARGET_ID" > "$TARGET_ID_FILE"
|
|
7333
|
+
echo " opened target $TARGET_ID for attempt $ATTEMPT_ID" >> "$LOG"
|
|
7334
|
+
echo "[chromium-wrapper] op=cdp-handover outcome=ready attempt=$ATTEMPT_ID targetId=$TARGET_ID" >> "$LOG"
|
|
6978
7335
|
`, { mode: 493 });
|
|
6979
7336
|
return wrapperPath;
|
|
6980
7337
|
}
|
|
@@ -7035,8 +7392,8 @@ app.get("/", async (c) => {
|
|
|
7035
7392
|
const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
7036
7393
|
let pinConfigured = false;
|
|
7037
7394
|
try {
|
|
7038
|
-
if (
|
|
7039
|
-
const raw =
|
|
7395
|
+
if (existsSync3(USERS_FILE)) {
|
|
7396
|
+
const raw = readFileSync5(USERS_FILE, "utf-8").trim();
|
|
7040
7397
|
if (raw) {
|
|
7041
7398
|
const users = JSON.parse(raw);
|
|
7042
7399
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -7529,183 +7886,6 @@ init_dist();
|
|
|
7529
7886
|
init_channel_key();
|
|
7530
7887
|
import { statSync as statSync7 } from "fs";
|
|
7531
7888
|
import { join as join12 } from "path";
|
|
7532
|
-
|
|
7533
|
-
// app/lib/telegram/message-store.ts
|
|
7534
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, appendFileSync, readdirSync as readdirSync2, statSync as statSync5 } from "fs";
|
|
7535
|
-
import { homedir } from "os";
|
|
7536
|
-
import { dirname, join as join7, resolve as resolve4 } from "path";
|
|
7537
|
-
function configDirName() {
|
|
7538
|
-
const platformRoot5 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
7539
|
-
const brandPath = join7(platformRoot5, "config", "brand.json");
|
|
7540
|
-
if (existsSync3(brandPath)) {
|
|
7541
|
-
try {
|
|
7542
|
-
return JSON.parse(readFileSync5(brandPath, "utf-8")).configDir ?? ".maxy";
|
|
7543
|
-
} catch {
|
|
7544
|
-
}
|
|
7545
|
-
}
|
|
7546
|
-
return ".maxy";
|
|
7547
|
-
}
|
|
7548
|
-
var STORE_ROOT2 = () => join7(process.env.MAXY_DIR_OVERRIDE ?? resolve4(homedir(), configDirName()), "data", "telegram-messages");
|
|
7549
|
-
function isStorableChannelKey(channelKey) {
|
|
7550
|
-
return /^-?\d+:-?\d+$/.test(channelKey);
|
|
7551
|
-
}
|
|
7552
|
-
function channelKeyToFileStem(channelKey) {
|
|
7553
|
-
if (!isStorableChannelKey(channelKey)) {
|
|
7554
|
-
throw new Error(`telegram-message-store: refusing unstorable channelKey ${JSON.stringify(channelKey)}`);
|
|
7555
|
-
}
|
|
7556
|
-
return channelKey.replace(":", "_");
|
|
7557
|
-
}
|
|
7558
|
-
function fileStemToChannelKey(stem) {
|
|
7559
|
-
const i = stem.indexOf("_");
|
|
7560
|
-
return i <= 0 ? stem : `${stem.slice(0, i)}:${stem.slice(i + 1)}`;
|
|
7561
|
-
}
|
|
7562
|
-
function conversationFile(accountId, channelKey) {
|
|
7563
|
-
return join7(STORE_ROOT2(), accountId, `${channelKeyToFileStem(channelKey)}.jsonl`);
|
|
7564
|
-
}
|
|
7565
|
-
function conversationFilePath2(accountId, channelKey) {
|
|
7566
|
-
return conversationFile(accountId, channelKey);
|
|
7567
|
-
}
|
|
7568
|
-
function isStorableStem(stem) {
|
|
7569
|
-
return isStorableChannelKey(fileStemToChannelKey(stem));
|
|
7570
|
-
}
|
|
7571
|
-
function listStoredAccountIds2() {
|
|
7572
|
-
const root = STORE_ROOT2();
|
|
7573
|
-
if (!existsSync3(root)) return [];
|
|
7574
|
-
return readdirSync2(root).filter((name) => {
|
|
7575
|
-
try {
|
|
7576
|
-
return statSync5(join7(root, name)).isDirectory();
|
|
7577
|
-
} catch {
|
|
7578
|
-
return false;
|
|
7579
|
-
}
|
|
7580
|
-
});
|
|
7581
|
-
}
|
|
7582
|
-
function parseLines(raw) {
|
|
7583
|
-
const out = [];
|
|
7584
|
-
for (const line of raw.split("\n")) {
|
|
7585
|
-
if (!line.trim()) continue;
|
|
7586
|
-
try {
|
|
7587
|
-
out.push(JSON.parse(line));
|
|
7588
|
-
} catch {
|
|
7589
|
-
console.error(`[telegram-message-store] skip reason=corrupt-line`);
|
|
7590
|
-
}
|
|
7591
|
-
}
|
|
7592
|
-
return out;
|
|
7593
|
-
}
|
|
7594
|
-
var byDateSentAsc = (a, b) => a.dateSent.localeCompare(b.dateSent);
|
|
7595
|
-
function appendMessage2(accountId, record) {
|
|
7596
|
-
if (!isStorableChannelKey(record.channelKey)) {
|
|
7597
|
-
console.error(
|
|
7598
|
-
`[telegram-message-store] FAIL op=append reason=unstorable-channel-key messageId=${record.messageId}`
|
|
7599
|
-
);
|
|
7600
|
-
return { created: false };
|
|
7601
|
-
}
|
|
7602
|
-
const file = conversationFile(accountId, record.channelKey);
|
|
7603
|
-
try {
|
|
7604
|
-
mkdirSync2(dirname(file), { recursive: true });
|
|
7605
|
-
} catch (err) {
|
|
7606
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
7607
|
-
console.error(
|
|
7608
|
-
`[telegram-message-store] FAIL op=append reason=${reason.slice(0, 100)} messageId=${record.messageId}`
|
|
7609
|
-
);
|
|
7610
|
-
return { created: false };
|
|
7611
|
-
}
|
|
7612
|
-
try {
|
|
7613
|
-
if (existsSync3(file)) {
|
|
7614
|
-
const existing = parseLines(readFileSync5(file, "utf8"));
|
|
7615
|
-
if (existing.some((r) => r.messageId === record.messageId)) {
|
|
7616
|
-
console.error(
|
|
7617
|
-
`[telegram-message-store] op=append channelKey=${record.channelKey} origin=${record.origin} created=false`
|
|
7618
|
-
);
|
|
7619
|
-
return { created: false };
|
|
7620
|
-
}
|
|
7621
|
-
}
|
|
7622
|
-
appendFileSync(file, `${JSON.stringify(record)}
|
|
7623
|
-
`);
|
|
7624
|
-
console.error(
|
|
7625
|
-
`[telegram-message-store] op=append channelKey=${record.channelKey} origin=${record.origin} created=true`
|
|
7626
|
-
);
|
|
7627
|
-
return { created: true };
|
|
7628
|
-
} catch (err) {
|
|
7629
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
7630
|
-
console.error(
|
|
7631
|
-
`[telegram-message-store] FAIL op=append reason=${reason.slice(0, 100)} messageId=${record.messageId}`
|
|
7632
|
-
);
|
|
7633
|
-
return { created: false };
|
|
7634
|
-
}
|
|
7635
|
-
}
|
|
7636
|
-
function readConversation2(accountId, channelKey) {
|
|
7637
|
-
if (!isStorableChannelKey(channelKey)) return [];
|
|
7638
|
-
const file = conversationFile(accountId, channelKey);
|
|
7639
|
-
try {
|
|
7640
|
-
if (!existsSync3(file)) return [];
|
|
7641
|
-
return parseLines(readFileSync5(file, "utf8")).sort(byDateSentAsc);
|
|
7642
|
-
} catch (err) {
|
|
7643
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
7644
|
-
console.error(
|
|
7645
|
-
`[telegram-message-store] FAIL op=read reason=${reason.slice(0, 100)} channelKey=${channelKey}`
|
|
7646
|
-
);
|
|
7647
|
-
return [];
|
|
7648
|
-
}
|
|
7649
|
-
}
|
|
7650
|
-
var summaryCache = /* @__PURE__ */ new Map();
|
|
7651
|
-
function summariseRecords(records2) {
|
|
7652
|
-
if (records2.length === 0) return null;
|
|
7653
|
-
const sorted = records2.slice().sort(byDateSentAsc);
|
|
7654
|
-
return { first: sorted[0], last: sorted[sorted.length - 1] };
|
|
7655
|
-
}
|
|
7656
|
-
function readConversationSummaries2(accountId) {
|
|
7657
|
-
const dir = join7(STORE_ROOT2(), accountId);
|
|
7658
|
-
const out = /* @__PURE__ */ new Map();
|
|
7659
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7660
|
-
try {
|
|
7661
|
-
if (!existsSync3(dir)) return out;
|
|
7662
|
-
for (const entry2 of readdirSync2(dir)) {
|
|
7663
|
-
if (!entry2.endsWith(".jsonl")) continue;
|
|
7664
|
-
const stem = entry2.slice(0, -".jsonl".length);
|
|
7665
|
-
if (!isStorableStem(stem)) continue;
|
|
7666
|
-
const channelKey = fileStemToChannelKey(stem);
|
|
7667
|
-
const file = join7(dir, entry2);
|
|
7668
|
-
seen.add(file);
|
|
7669
|
-
let size;
|
|
7670
|
-
let mtimeMs;
|
|
7671
|
-
try {
|
|
7672
|
-
const st = statSync5(file);
|
|
7673
|
-
size = st.size;
|
|
7674
|
-
mtimeMs = st.mtimeMs;
|
|
7675
|
-
} catch {
|
|
7676
|
-
continue;
|
|
7677
|
-
}
|
|
7678
|
-
const cached3 = summaryCache.get(file);
|
|
7679
|
-
if (cached3 && cached3.size === size && cached3.mtimeMs === mtimeMs) {
|
|
7680
|
-
out.set(channelKey, cached3.summary);
|
|
7681
|
-
continue;
|
|
7682
|
-
}
|
|
7683
|
-
let summary;
|
|
7684
|
-
try {
|
|
7685
|
-
summary = summariseRecords(parseLines(readFileSync5(file, "utf8")));
|
|
7686
|
-
} catch (err) {
|
|
7687
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
7688
|
-
console.error(
|
|
7689
|
-
`[telegram-message-store] FAIL op=summarise reason=${reason.slice(0, 100)} channelKey=${channelKey}`
|
|
7690
|
-
);
|
|
7691
|
-
continue;
|
|
7692
|
-
}
|
|
7693
|
-
if (!summary) continue;
|
|
7694
|
-
summaryCache.set(file, { size, mtimeMs, summary });
|
|
7695
|
-
out.set(channelKey, summary);
|
|
7696
|
-
}
|
|
7697
|
-
} catch (err) {
|
|
7698
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
7699
|
-
console.error(`[telegram-message-store] FAIL op=summarise-dir reason=${reason.slice(0, 100)}`);
|
|
7700
|
-
return out;
|
|
7701
|
-
}
|
|
7702
|
-
for (const key2 of [...summaryCache.keys()]) {
|
|
7703
|
-
if (key2.startsWith(dir) && !seen.has(key2)) summaryCache.delete(key2);
|
|
7704
|
-
}
|
|
7705
|
-
return out;
|
|
7706
|
-
}
|
|
7707
|
-
|
|
7708
|
-
// server/routes/channel/schedule-inject.ts
|
|
7709
7889
|
init_admin_session_id();
|
|
7710
7890
|
init_normalize();
|
|
7711
7891
|
|
|
@@ -8288,6 +8468,32 @@ async function sendTelegramText(botToken, chatId, text) {
|
|
|
8288
8468
|
}
|
|
8289
8469
|
}
|
|
8290
8470
|
|
|
8471
|
+
// app/lib/telegram/outbound/send-chat-action.ts
|
|
8472
|
+
async function sendTelegramChatAction(botToken, chatId, key2, site) {
|
|
8473
|
+
const report = (ok2, reason) => console.error(
|
|
8474
|
+
`[telegram-native] op=chat-action key=${key2} site=${site} ok=${ok2}${ok2 ? "" : ` reason=${reason}`}`
|
|
8475
|
+
);
|
|
8476
|
+
try {
|
|
8477
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendChatAction`, {
|
|
8478
|
+
method: "POST",
|
|
8479
|
+
headers: { "Content-Type": "application/json" },
|
|
8480
|
+
body: JSON.stringify({ chat_id: chatId, action: "typing" })
|
|
8481
|
+
});
|
|
8482
|
+
const data = await res.json();
|
|
8483
|
+
if (data.ok) {
|
|
8484
|
+
report(true);
|
|
8485
|
+
return { ok: true };
|
|
8486
|
+
}
|
|
8487
|
+
const error = data.description ?? "chat action failed";
|
|
8488
|
+
report(false, error);
|
|
8489
|
+
return { ok: false, error };
|
|
8490
|
+
} catch (e) {
|
|
8491
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
8492
|
+
report(false, error);
|
|
8493
|
+
return { ok: false, error };
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
|
|
8291
8497
|
// server/routes/whatsapp.ts
|
|
8292
8498
|
init_dist();
|
|
8293
8499
|
import { basename as basename3, join as join17, resolve as resolve8 } from "path";
|
|
@@ -15549,6 +15755,7 @@ function pickActivity(entries2, grown, nowMs, concurrencyWindowMs) {
|
|
|
15549
15755
|
}
|
|
15550
15756
|
|
|
15551
15757
|
// server/routes/session-reader.ts
|
|
15758
|
+
init_session_store();
|
|
15552
15759
|
init_dist();
|
|
15553
15760
|
var app7 = new Hono();
|
|
15554
15761
|
function probeDataRef(path) {
|
|
@@ -15724,6 +15931,7 @@ data: ${JSON.stringify(turn)}
|
|
|
15724
15931
|
|
|
15725
15932
|
`));
|
|
15726
15933
|
const connId = `${sessionId.slice(0, 8)}-${process.hrtime.bigint().toString(36).slice(-6)}`;
|
|
15934
|
+
const censusAccountId = getAccountIdForSession(c.get("cacheKey") ?? "") ?? "unresolved";
|
|
15727
15935
|
const startedAt = Date.now();
|
|
15728
15936
|
const lastEventId = c.req.header("Last-Event-ID");
|
|
15729
15937
|
let teardown = () => {
|
|
@@ -15759,7 +15967,8 @@ data: ${JSON.stringify(obj)}
|
|
|
15759
15967
|
log: (line) => console.log(line)
|
|
15760
15968
|
});
|
|
15761
15969
|
const queuedPendingSuppress = /* @__PURE__ */ new Map();
|
|
15762
|
-
const
|
|
15970
|
+
const envelopeCensus = emptyEnvelopeCensus();
|
|
15971
|
+
const turnCtx = { envelopeCensus };
|
|
15763
15972
|
const drain = () => {
|
|
15764
15973
|
try {
|
|
15765
15974
|
const { buf, end } = readFrom(jsonlPath, offset);
|
|
@@ -15776,6 +15985,7 @@ data: ${JSON.stringify(obj)}
|
|
|
15776
15985
|
n++;
|
|
15777
15986
|
}
|
|
15778
15987
|
if (n > 0) {
|
|
15988
|
+
foldFrameCensus(censusAccountId, sessionId, envelopeCensus);
|
|
15779
15989
|
actx.parentGrewThisTick = true;
|
|
15780
15990
|
console.log(`[wa-stream] op=drain conn=${connId} turns=${n}`);
|
|
15781
15991
|
}
|
|
@@ -15822,6 +16032,7 @@ data: ${JSON.stringify({ from: alignedFrom, fileEnd: fileEnd0 })}
|
|
|
15822
16032
|
}
|
|
15823
16033
|
n++;
|
|
15824
16034
|
}
|
|
16035
|
+
foldFrameCensus(censusAccountId, sessionId, envelopeCensus);
|
|
15825
16036
|
console.log(
|
|
15826
16037
|
`[wa-stream] op=backlog conn=${connId} turns=${n} bytes=${end - alignedFrom} from=${alignedFrom}`
|
|
15827
16038
|
);
|
|
@@ -16005,7 +16216,7 @@ import { basename as basename8, dirname as dirname9, join as join26 } from "path
|
|
|
16005
16216
|
// app/lib/reader/operator-conversations.ts
|
|
16006
16217
|
function operatorConvName(row) {
|
|
16007
16218
|
if (row.isGroup === true) return row.chatName ?? row.title;
|
|
16008
|
-
const name = row.operatorName ?? row.chatName ?? row.whatsappName;
|
|
16219
|
+
const name = row.operatorName ?? row.chatName ?? row.whatsappName ?? row.telegramName;
|
|
16009
16220
|
const senderId = row.senderId != null && row.channel === "whatsapp" ? formatSenderPhone(row.senderId) : row.senderId;
|
|
16010
16221
|
if (name) return senderId ? `${name} ${senderId}` : name;
|
|
16011
16222
|
return senderId ?? row.title;
|
|
@@ -16015,6 +16226,7 @@ function operatorConvNameSource(row) {
|
|
|
16015
16226
|
if (row.operatorName) return "operator";
|
|
16016
16227
|
if (row.chatName) return "chatmeta";
|
|
16017
16228
|
if (row.whatsappName) return "inbound-pushname";
|
|
16229
|
+
if (row.telegramName) return "telegram-sender-name";
|
|
16018
16230
|
if (row.senderId != null) return "counterparty-number";
|
|
16019
16231
|
return "jid";
|
|
16020
16232
|
}
|
|
@@ -16234,6 +16446,14 @@ app8.get("/conversations", requireAdminSession, async (c) => {
|
|
|
16234
16446
|
);
|
|
16235
16447
|
const adminScope = allStoreRows.filter((r) => r.scope === "admin").length;
|
|
16236
16448
|
const publicScope = allStoreRows.length - adminScope;
|
|
16449
|
+
const telegramStoreRows = account ? buildTelegramStoreConversationRows(account.accountId, readConversationSummaries2(account.accountId)) : [];
|
|
16450
|
+
const dedupedSessionRows = dropTelegramSessionDuplicates(sessionConversations, telegramStoreRows);
|
|
16451
|
+
const telegramRows = [
|
|
16452
|
+
...dedupedSessionRows.filter((r) => r.channel === "telegram"),
|
|
16453
|
+
...telegramStoreRows
|
|
16454
|
+
].sort((a, b) => (b.lastMessageAt ?? "").localeCompare(a.lastMessageAt ?? ""));
|
|
16455
|
+
const otherSessionRows = dedupedSessionRows.filter((r) => r.channel !== "telegram");
|
|
16456
|
+
const conversations = [...otherSessionRows, ...allStoreRows, ...telegramRows];
|
|
16237
16457
|
const census = buildLabelCensus(allStoreRows, selfPushNames(summaries));
|
|
16238
16458
|
for (const f of fallbackLabelSources(allStoreRows)) {
|
|
16239
16459
|
console.log(`[wa-label] op=row-label jid=${f.jid} source=${f.source} route=conversations`);
|
|
@@ -16241,11 +16461,11 @@ app8.get("/conversations", requireAdminSession, async (c) => {
|
|
|
16241
16461
|
console.log(
|
|
16242
16462
|
`[wa-label-census] route=conversations chats=${census.chats} withMeta=${census.withMeta} groups=${census.groups} groupsWithSubject=${census.groupsWithSubject} labelledByMember=${census.labelledByMember} dms=${census.dms} namedDms=${census.namedDms} labelledBySelfPushName=${census.labelledBySelfPushName}`
|
|
16243
16463
|
);
|
|
16244
|
-
const sessionRowCount =
|
|
16464
|
+
const sessionRowCount = dedupedSessionRows.length;
|
|
16245
16465
|
console.log(
|
|
16246
|
-
`[wa-reader] op=list pinnedAccount=${scopeAccountId?.slice(0, 8) ?? "null"} resolvedAccount=${account?.accountId.slice(0, 8) ?? "null"} multiAccount=${multiAccount} conversations=${
|
|
16466
|
+
`[wa-reader] op=list pinnedAccount=${scopeAccountId?.slice(0, 8) ?? "null"} resolvedAccount=${account?.accountId.slice(0, 8) ?? "null"} multiAccount=${multiAccount} conversations=${conversations.length} sessionRows=${sessionRowCount} storeRows=${allStoreRows.length} storeRowsByChannel=whatsapp:${allStoreRows.length},telegram:${telegramStoreRows.length} adminScope=${adminScope} publicScope=${publicScope} sessionRowsExcludedUntagged=${sessionRowsExcludedUntagged} houseResolved=${houseId ? houseId.slice(0, 8) : "null"} managerPhonesBound=${managerPhonesBound} managedStoreRows=${managedRows.length}`
|
|
16247
16467
|
);
|
|
16248
|
-
return c.json({ conversations
|
|
16468
|
+
return c.json({ conversations });
|
|
16249
16469
|
});
|
|
16250
16470
|
function tagAndMergeInstallConversations(perAccount) {
|
|
16251
16471
|
const all = [];
|
|
@@ -16256,7 +16476,7 @@ function tagAndMergeInstallConversations(perAccount) {
|
|
|
16256
16476
|
}
|
|
16257
16477
|
function dropHouseRowsOwnedBySubAccount(rows, subManagerPhones) {
|
|
16258
16478
|
return rows.filter((r) => {
|
|
16259
|
-
if (r.isHouse !== true || r.source !== "store" || r.senderId == null) return true;
|
|
16479
|
+
if (r.channel !== "whatsapp" || r.isHouse !== true || r.source !== "store" || r.senderId == null) return true;
|
|
16260
16480
|
let owner = null;
|
|
16261
16481
|
for (const [subId, phones] of subManagerPhones) {
|
|
16262
16482
|
if (counterpartyBoundToSubAccount(phones, r.senderId)) {
|
|
@@ -16396,7 +16616,14 @@ app8.get("/conversations-all", requireAdminSession, async (c) => {
|
|
|
16396
16616
|
readConversationSummaries(account.accountId),
|
|
16397
16617
|
readChatMetadata(account.accountId)
|
|
16398
16618
|
);
|
|
16399
|
-
|
|
16619
|
+
const telegramStoreRows = buildTelegramStoreConversationRows(
|
|
16620
|
+
account.accountId,
|
|
16621
|
+
readConversationSummaries2(account.accountId)
|
|
16622
|
+
);
|
|
16623
|
+
perAccount.push({
|
|
16624
|
+
isHouse,
|
|
16625
|
+
rows: [...dropTelegramSessionDuplicates(named, telegramStoreRows), ...storeRows, ...telegramStoreRows]
|
|
16626
|
+
});
|
|
16400
16627
|
}
|
|
16401
16628
|
const houseAccount = validAccounts.find((a) => a.accountId === houseId) ?? null;
|
|
16402
16629
|
const subManagerPhones = /* @__PURE__ */ new Map();
|
|
@@ -16414,16 +16641,19 @@ app8.get("/conversations-all", requireAdminSession, async (c) => {
|
|
|
16414
16641
|
for (const account of validAccounts) {
|
|
16415
16642
|
for (const [jid, s] of readConversationSummaries(account.accountId)) allSummaries.set(`${account.accountId}:${jid}`, s);
|
|
16416
16643
|
}
|
|
16417
|
-
const
|
|
16418
|
-
|
|
16644
|
+
const whatsappRows = conversations.filter((r) => r.channel === "whatsapp");
|
|
16645
|
+
const census = buildLabelCensus(whatsappRows, selfPushNames(allSummaries));
|
|
16646
|
+
for (const f of fallbackLabelSources(whatsappRows)) {
|
|
16419
16647
|
console.log(`[wa-label] op=row-label jid=${f.jid} source=${f.source} route=conversations-all`);
|
|
16420
16648
|
}
|
|
16421
16649
|
console.log(
|
|
16422
16650
|
`[wa-label-census] route=conversations-all chats=${census.chats} withMeta=${census.withMeta} groups=${census.groups} groupsWithSubject=${census.groupsWithSubject} labelledByMember=${census.labelledByMember} dms=${census.dms} namedDms=${census.namedDms} labelledBySelfPushName=${census.labelledBySelfPushName}`
|
|
16423
16651
|
);
|
|
16424
16652
|
const houseRows = conversations.filter((r) => r.isHouse).length;
|
|
16653
|
+
const storeByChannel = conversations.filter((r) => r.source === "store");
|
|
16654
|
+
const waStoreRows = storeByChannel.filter((r) => r.channel === "whatsapp").length;
|
|
16425
16655
|
console.log(
|
|
16426
|
-
`[wa-reader] op=conversations-all accounts=${validAccounts.length} rows=${conversations.length} house=${houseRows} sub=${conversations.length - houseRows} deduped=${merged.length - conversations.length}`
|
|
16656
|
+
`[wa-reader] op=conversations-all accounts=${validAccounts.length} rows=${conversations.length} house=${houseRows} sub=${conversations.length - houseRows} deduped=${merged.length - conversations.length} storeRowsByChannel=whatsapp:${waStoreRows},telegram:${storeByChannel.length - waStoreRows}`
|
|
16427
16657
|
);
|
|
16428
16658
|
return { conversations };
|
|
16429
16659
|
});
|
|
@@ -18653,7 +18883,7 @@ var quickbooks_default = app12;
|
|
|
18653
18883
|
// server/routes/onboarding.ts
|
|
18654
18884
|
init_dist();
|
|
18655
18885
|
import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync4 } from "child_process";
|
|
18656
|
-
import { openSync as openSync8, closeSync as closeSync8, writeFileSync as writeFileSync14, writeSync, existsSync as existsSync26, readFileSync as readFileSync32, unlinkSync as unlinkSync3
|
|
18886
|
+
import { openSync as openSync8, closeSync as closeSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync14, writeSync, existsSync as existsSync26, readFileSync as readFileSync32, unlinkSync as unlinkSync3 } from "fs";
|
|
18657
18887
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
18658
18888
|
init_claude_agent();
|
|
18659
18889
|
init_neo4j_store();
|
|
@@ -18726,8 +18956,10 @@ function bridgeKeychainToFile(deps) {
|
|
|
18726
18956
|
deps.log(`[onboarding] op=login-writeback result=no-claudeAiOauth service=${JSON.stringify(service)}`);
|
|
18727
18957
|
return { wrote: false, reason: "no-claudeAiOauth", bytes: 0, service };
|
|
18728
18958
|
}
|
|
18959
|
+
const oauth = parsed.claudeAiOauth;
|
|
18960
|
+
const refreshTokenLen = typeof oauth.refreshToken === "string" ? oauth.refreshToken.length : 0;
|
|
18729
18961
|
try {
|
|
18730
|
-
deps.writeFile(blob);
|
|
18962
|
+
deps.writeFile(blob, refreshTokenLen);
|
|
18731
18963
|
} catch (err) {
|
|
18732
18964
|
deps.log(`[onboarding] op=login-writeback result=write-failed service=${JSON.stringify(service)} err=${err instanceof Error ? err.message : String(err)}`);
|
|
18733
18965
|
return { wrote: false, reason: "write-failed", bytes: 0, service };
|
|
@@ -18991,7 +19223,35 @@ function readUsersFile2() {
|
|
|
18991
19223
|
if (!raw) return [];
|
|
18992
19224
|
return JSON.parse(raw);
|
|
18993
19225
|
}
|
|
18994
|
-
var
|
|
19226
|
+
var pendingAuthLogin = null;
|
|
19227
|
+
async function endLogin(pending) {
|
|
19228
|
+
if (!pending) return { priorId: null, priorPid: null, tabClosed: null };
|
|
19229
|
+
let tabClosed = null;
|
|
19230
|
+
const targetId = readAuthTargetId();
|
|
19231
|
+
if (targetId && readAuthAttemptId() === pending.attemptId) {
|
|
19232
|
+
tabClosed = await closeAuthTarget(targetId);
|
|
19233
|
+
}
|
|
19234
|
+
const priorPid = pending.child?.pid ?? null;
|
|
19235
|
+
try {
|
|
19236
|
+
pending.child?.kill("SIGTERM");
|
|
19237
|
+
} catch {
|
|
19238
|
+
}
|
|
19239
|
+
return { priorId: pending.attemptId, priorPid, tabClosed };
|
|
19240
|
+
}
|
|
19241
|
+
function adoptOrStopChild(attemptId, child) {
|
|
19242
|
+
if (pendingAuthLogin?.attemptId === attemptId) {
|
|
19243
|
+
pendingAuthLogin.child = child;
|
|
19244
|
+
return true;
|
|
19245
|
+
}
|
|
19246
|
+
try {
|
|
19247
|
+
child.kill("SIGTERM");
|
|
19248
|
+
} catch {
|
|
19249
|
+
}
|
|
19250
|
+
return false;
|
|
19251
|
+
}
|
|
19252
|
+
function formatEnded(ended) {
|
|
19253
|
+
return `superseded=${ended.priorId ?? "none"} priorPid=${ended.priorPid ?? "none"} tabClosed=${ended.tabClosed === null ? "na" : ended.tabClosed}`;
|
|
19254
|
+
}
|
|
18995
19255
|
var darwinCredsBefore = {};
|
|
18996
19256
|
async function checkAuthStatus() {
|
|
18997
19257
|
let status;
|
|
@@ -19032,7 +19292,11 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19032
19292
|
const remoteAddress = c.env?.incoming?.socket?.remoteAddress;
|
|
19033
19293
|
const transport = resolveBrowserTransport(c.req.raw, remoteAddress);
|
|
19034
19294
|
if (body.action === "stop") {
|
|
19035
|
-
|
|
19295
|
+
const superseded2 = pendingAuthLogin;
|
|
19296
|
+
pendingAuthLogin = null;
|
|
19297
|
+
const ended2 = await endLogin(superseded2);
|
|
19298
|
+
console.log(`[onboarding] op=auth-login-stop attempt=${ended2.priorId ?? "none"} outcome=${ended2.priorId ? "stopped" : "no-pending"} priorPid=${ended2.priorPid ?? "none"} tabClosed=${ended2.tabClosed === null ? "na" : ended2.tabClosed}`);
|
|
19299
|
+
return c.json({ stopped: ended2.priorId !== null });
|
|
19036
19300
|
}
|
|
19037
19301
|
if (body.action === "logout") {
|
|
19038
19302
|
const start = Date.now();
|
|
@@ -19057,7 +19321,7 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19057
19321
|
}
|
|
19058
19322
|
if (body.action === "paste-code") {
|
|
19059
19323
|
const code = typeof body.code === "string" ? body.code : "";
|
|
19060
|
-
const child =
|
|
19324
|
+
const child = pendingAuthLogin?.child ?? null;
|
|
19061
19325
|
if (!child || !child.stdin || child.stdin.destroyed) {
|
|
19062
19326
|
console.log("[onboarding] op=claude-paste-code accepted=false reason=no-active-login");
|
|
19063
19327
|
return c.json({ accepted: false, error: "No active login to paste into. Start sign-in first." }, 400);
|
|
@@ -19079,8 +19343,13 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19079
19343
|
const cdpReady = await ensureCdp(transport);
|
|
19080
19344
|
return c.json({ launched: cdpReady });
|
|
19081
19345
|
}
|
|
19346
|
+
const attemptId = randomUUID4().slice(0, 8);
|
|
19347
|
+
const superseded = pendingAuthLogin;
|
|
19348
|
+
pendingAuthLogin = { child: null, attemptId, startedAt: Date.now() };
|
|
19349
|
+
const ended = await endLogin(superseded);
|
|
19082
19350
|
ensureLogDir();
|
|
19083
|
-
|
|
19351
|
+
appendFileSync3(logPath("claude-auth"), `==== attempt ${attemptId} ${(/* @__PURE__ */ new Date()).toISOString()} ====
|
|
19352
|
+
`);
|
|
19084
19353
|
const claudeAuthLogFd = openSync8(logPath("claude-auth"), "a");
|
|
19085
19354
|
const onClaudeOutput = (chunk) => writeSync(claudeAuthLogFd, chunk);
|
|
19086
19355
|
if (process.platform === "darwin") {
|
|
@@ -19091,7 +19360,8 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19091
19360
|
stdio: ["pipe", "pipe", "pipe"]
|
|
19092
19361
|
});
|
|
19093
19362
|
claudeProc2.unref();
|
|
19094
|
-
|
|
19363
|
+
const adopted2 = adoptOrStopChild(attemptId, claudeProc2);
|
|
19364
|
+
console.log(`[onboarding] op=auth-login-start attempt=${attemptId} platform=darwin transport=native outcome=${adopted2 ? "started" : "self-superseded"} ${formatEnded(ended)}`);
|
|
19095
19365
|
claudeProc2.stdin?.on("error", () => {
|
|
19096
19366
|
});
|
|
19097
19367
|
claudeProc2.stdout?.on("data", onClaudeOutput);
|
|
@@ -19103,11 +19373,9 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19103
19373
|
before: darwinCredsBefore,
|
|
19104
19374
|
listMdats: listHostCredsMdats,
|
|
19105
19375
|
readBlob: readHostCredsBlob,
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
renameSync8(tmp, CLAUDE_CREDENTIALS_FILE);
|
|
19110
|
-
},
|
|
19376
|
+
// Task 2572 — the write itself lives in claude-auth.ts beside the other
|
|
19377
|
+
// writer of this file, so both emit provenance and both are testable.
|
|
19378
|
+
writeFile: (blob, refreshTokenLen) => writeHostCredsFile(CLAUDE_CREDENTIALS_FILE, blob, refreshTokenLen),
|
|
19111
19379
|
log: (line) => console.log(line)
|
|
19112
19380
|
});
|
|
19113
19381
|
persistKeychainService(
|
|
@@ -19115,7 +19383,7 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19115
19383
|
(service) => writeFileSync14(join37(dirname13(CLAUDE_CREDENTIALS_FILE), ".keychain-service"), service, { mode: 384 }),
|
|
19116
19384
|
(line) => console.log(line)
|
|
19117
19385
|
);
|
|
19118
|
-
if (
|
|
19386
|
+
if (pendingAuthLogin?.child === claudeProc2) pendingAuthLogin = null;
|
|
19119
19387
|
});
|
|
19120
19388
|
return c.json({ started: true, transport: "native" });
|
|
19121
19389
|
}
|
|
@@ -19124,7 +19392,7 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19124
19392
|
if (!vncReady) return c.json({ error: "VNC display failed to start" }, 500);
|
|
19125
19393
|
}
|
|
19126
19394
|
await ensureCdp(transport);
|
|
19127
|
-
const chromiumWrapper = writeChromiumWrapper();
|
|
19395
|
+
const chromiumWrapper = writeChromiumWrapper(attemptId);
|
|
19128
19396
|
const x11Env = buildX11Env(chromiumWrapper, transport);
|
|
19129
19397
|
vncLog("claude-auth", { action: "start", transport });
|
|
19130
19398
|
const claudeProc = spawn("claude", ["auth", "login"], {
|
|
@@ -19132,9 +19400,14 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19132
19400
|
stdio: ["ignore", "pipe", "pipe"]
|
|
19133
19401
|
});
|
|
19134
19402
|
claudeProc.unref();
|
|
19403
|
+
const adopted = adoptOrStopChild(attemptId, claudeProc);
|
|
19404
|
+
console.log(`[onboarding] op=auth-login-start attempt=${attemptId} platform=${process.platform} transport=${transport} outcome=${adopted ? "started" : "self-superseded"} ${formatEnded(ended)}`);
|
|
19135
19405
|
claudeProc.stdout?.on("data", onClaudeOutput);
|
|
19136
19406
|
claudeProc.stderr?.on("data", onClaudeOutput);
|
|
19137
|
-
claudeProc.once("close", () =>
|
|
19407
|
+
claudeProc.once("close", () => {
|
|
19408
|
+
closeSync8(claudeAuthLogFd);
|
|
19409
|
+
if (pendingAuthLogin?.child === claudeProc) pendingAuthLogin = null;
|
|
19410
|
+
});
|
|
19138
19411
|
await waitForAuthPage(2e4);
|
|
19139
19412
|
return c.json({ started: true, transport });
|
|
19140
19413
|
});
|
|
@@ -19814,7 +20087,7 @@ init_dist();
|
|
|
19814
20087
|
init_paths();
|
|
19815
20088
|
init_remote_auth();
|
|
19816
20089
|
init_loop_registry();
|
|
19817
|
-
import { appendFileSync as
|
|
20090
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync27, renameSync as renameSync9, statSync as statSync19 } from "fs";
|
|
19818
20091
|
import { join as join38 } from "path";
|
|
19819
20092
|
var CLIENT_ERRORS_LOG = join38(LOG_DIR, "client-errors.log");
|
|
19820
20093
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
@@ -19960,7 +20233,7 @@ app15.post("/", async (c) => {
|
|
|
19960
20233
|
const safe = typeof v === "string" ? truncate(v, 200) : typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v).slice(0, 200);
|
|
19961
20234
|
return `${k}=${safe}`;
|
|
19962
20235
|
}).join(" ");
|
|
19963
|
-
const TAGGED_PREFIX_SOURCES = /* @__PURE__ */ new Set(["stream-log-click", "pwa", "graph-settle", "dashboard-load", "routines", "calendar-legibility"]);
|
|
20236
|
+
const TAGGED_PREFIX_SOURCES = /* @__PURE__ */ new Set(["stream-log-click", "pwa", "graph-settle", "dashboard-load", "routines", "calendar-legibility", "channels-card"]);
|
|
19964
20237
|
if (TAGGED_PREFIX_SOURCES.has(source)) {
|
|
19965
20238
|
console.log(
|
|
19966
20239
|
`[${source}] ts=${ts} ip=${ip} version=${version || "unknown"}${extra ? " " + extra : ""}`
|
|
@@ -19993,7 +20266,7 @@ app15.post("/", async (c) => {
|
|
|
19993
20266
|
tag: typeof body.tag === "string" ? truncate(body.tag, 32) : void 0,
|
|
19994
20267
|
status: typeof body.status === "number" ? body.status : void 0
|
|
19995
20268
|
};
|
|
19996
|
-
|
|
20269
|
+
appendFileSync4(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
19997
20270
|
} catch (err) {
|
|
19998
20271
|
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
19999
20272
|
}
|
|
@@ -23086,18 +23359,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
23086
23359
|
]);
|
|
23087
23360
|
var app27 = new Hono();
|
|
23088
23361
|
app27.post("/", async (c) => {
|
|
23089
|
-
const
|
|
23362
|
+
const TAG68 = "[admin:events]";
|
|
23090
23363
|
let body;
|
|
23091
23364
|
try {
|
|
23092
23365
|
body = await c.req.json();
|
|
23093
23366
|
} catch (err) {
|
|
23094
23367
|
const detail = err instanceof Error ? err.message : String(err);
|
|
23095
|
-
console.error(`${
|
|
23368
|
+
console.error(`${TAG68} reject reason=body-not-json detail=${detail}`);
|
|
23096
23369
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
23097
23370
|
}
|
|
23098
23371
|
const event = typeof body.event === "string" ? body.event : "";
|
|
23099
23372
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
23100
|
-
console.error(`${
|
|
23373
|
+
console.error(`${TAG68} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
23101
23374
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
23102
23375
|
}
|
|
23103
23376
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -34796,6 +35069,68 @@ function startSharedFolderCensus() {
|
|
|
34796
35069
|
});
|
|
34797
35070
|
}
|
|
34798
35071
|
|
|
35072
|
+
// app/lib/cred-refresh-census.ts
|
|
35073
|
+
init_loop_registry();
|
|
35074
|
+
import { readdirSync as readdirSync31, readFileSync as readFileSync53 } from "fs";
|
|
35075
|
+
import { homedir as homedir5 } from "os";
|
|
35076
|
+
import { join as join59 } from "path";
|
|
35077
|
+
var TAG60 = "[claude-auth]";
|
|
35078
|
+
function refreshTokenLength(raw) {
|
|
35079
|
+
let data;
|
|
35080
|
+
try {
|
|
35081
|
+
data = JSON.parse(raw);
|
|
35082
|
+
} catch {
|
|
35083
|
+
return null;
|
|
35084
|
+
}
|
|
35085
|
+
const oauth = data.claudeAiOauth;
|
|
35086
|
+
if (!oauth || typeof oauth !== "object") return null;
|
|
35087
|
+
return typeof oauth.refreshToken === "string" ? oauth.refreshToken.length : null;
|
|
35088
|
+
}
|
|
35089
|
+
function surveyBrandCredentials(home) {
|
|
35090
|
+
let entries2;
|
|
35091
|
+
try {
|
|
35092
|
+
entries2 = readdirSync31(home);
|
|
35093
|
+
} catch {
|
|
35094
|
+
return [];
|
|
35095
|
+
}
|
|
35096
|
+
const rows = [];
|
|
35097
|
+
for (const entry2 of entries2.sort()) {
|
|
35098
|
+
if (!entry2.startsWith(".")) continue;
|
|
35099
|
+
let raw;
|
|
35100
|
+
try {
|
|
35101
|
+
raw = readFileSync53(join59(home, entry2, ".claude", ".credentials.json"), "utf-8");
|
|
35102
|
+
} catch {
|
|
35103
|
+
continue;
|
|
35104
|
+
}
|
|
35105
|
+
rows.push({ brand: entry2.slice(1), refreshTokenLen: refreshTokenLength(raw) });
|
|
35106
|
+
}
|
|
35107
|
+
return rows;
|
|
35108
|
+
}
|
|
35109
|
+
function isBlank(row) {
|
|
35110
|
+
return row.refreshTokenLen === null || row.refreshTokenLen === 0;
|
|
35111
|
+
}
|
|
35112
|
+
function censusResult(rows) {
|
|
35113
|
+
if (rows.length === 0) return "no-brands-found";
|
|
35114
|
+
return rows.some(isBlank) ? "blank-tokens" : "ok";
|
|
35115
|
+
}
|
|
35116
|
+
function runCredRefreshCensus(home = homedir5()) {
|
|
35117
|
+
const rows = surveyBrandCredentials(home);
|
|
35118
|
+
const blank = rows.filter(isBlank).length;
|
|
35119
|
+
const detail = rows.map((r) => `${r.brand}:${r.refreshTokenLen === null ? "unreadable" : r.refreshTokenLen}`).join(",");
|
|
35120
|
+
console.log(
|
|
35121
|
+
`${TAG60} op=cred-refresh-census result=${censusResult(rows)} brands=${rows.length} blank=${blank} detail=${detail}`
|
|
35122
|
+
);
|
|
35123
|
+
return `${rows.length} brand(s), ${blank} blank`;
|
|
35124
|
+
}
|
|
35125
|
+
function startCredRefreshCensus() {
|
|
35126
|
+
registerLoop({
|
|
35127
|
+
name: "cred-refresh-census",
|
|
35128
|
+
intervalMs: 60 * 60 * 1e3,
|
|
35129
|
+
firstRunDelayMs: 0,
|
|
35130
|
+
run: () => runCredRefreshCensus()
|
|
35131
|
+
});
|
|
35132
|
+
}
|
|
35133
|
+
|
|
34799
35134
|
// app/lib/file-watcher.ts
|
|
34800
35135
|
init_data_path();
|
|
34801
35136
|
import * as fsp2 from "fs/promises";
|
|
@@ -35033,7 +35368,7 @@ init_account();
|
|
|
35033
35368
|
init_neo4j_store();
|
|
35034
35369
|
import { readdir as readdir7, readFile as readFile12, rename as rename5, writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
|
|
35035
35370
|
import { existsSync as existsSync50 } from "fs";
|
|
35036
|
-
import { join as
|
|
35371
|
+
import { join as join60 } from "path";
|
|
35037
35372
|
var ADMIN_ROLE2 = "admin";
|
|
35038
35373
|
var WEBCHAT_CHANNEL2 = "webchat";
|
|
35039
35374
|
var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
@@ -35078,7 +35413,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
35078
35413
|
}
|
|
35079
35414
|
for (const slug of slugs) {
|
|
35080
35415
|
if (!slug.isDirectory()) continue;
|
|
35081
|
-
const slugDir =
|
|
35416
|
+
const slugDir = join60(projectsRoot, slug.name);
|
|
35082
35417
|
let entries2;
|
|
35083
35418
|
try {
|
|
35084
35419
|
entries2 = await readdir7(slugDir, { withFileTypes: true });
|
|
@@ -35087,9 +35422,9 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
35087
35422
|
}
|
|
35088
35423
|
for (const entry2 of entries2) {
|
|
35089
35424
|
if (entry2.isFile() && SESSION_META_RE.test(entry2.name)) {
|
|
35090
|
-
out.push(
|
|
35425
|
+
out.push(join60(slugDir, entry2.name));
|
|
35091
35426
|
} else if (entry2.isDirectory() && entry2.name === "subagents") {
|
|
35092
|
-
const subDir =
|
|
35427
|
+
const subDir = join60(slugDir, entry2.name);
|
|
35093
35428
|
let subs;
|
|
35094
35429
|
try {
|
|
35095
35430
|
subs = await readdir7(subDir, { withFileTypes: true });
|
|
@@ -35097,7 +35432,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
35097
35432
|
continue;
|
|
35098
35433
|
}
|
|
35099
35434
|
for (const s of subs) {
|
|
35100
|
-
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(
|
|
35435
|
+
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join60(subDir, s.name));
|
|
35101
35436
|
}
|
|
35102
35437
|
}
|
|
35103
35438
|
}
|
|
@@ -35109,7 +35444,7 @@ function shortId(path) {
|
|
|
35109
35444
|
return base.slice(0, 8);
|
|
35110
35445
|
}
|
|
35111
35446
|
async function migrateAdminWebchatSidecars(opts = {}) {
|
|
35112
|
-
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
35447
|
+
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join60(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
|
|
35113
35448
|
const usersFile = opts.usersFile ?? USERS_FILE;
|
|
35114
35449
|
const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
|
|
35115
35450
|
const resolveName = opts.resolveName ?? defaultResolveName;
|
|
@@ -35187,7 +35522,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
|
|
|
35187
35522
|
init_account();
|
|
35188
35523
|
import { readdir as readdir8, readFile as readFile13, rename as rename6, writeFile as writeFile6, unlink as unlink5 } from "fs/promises";
|
|
35189
35524
|
import { existsSync as existsSync51 } from "fs";
|
|
35190
|
-
import { join as
|
|
35525
|
+
import { join as join61 } from "path";
|
|
35191
35526
|
var SESSION_META_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
35192
35527
|
function projectSlugForCwd(cwd) {
|
|
35193
35528
|
return cwd.replace(/\//g, "-");
|
|
@@ -35233,7 +35568,7 @@ async function collectSidecars(projectsRoot) {
|
|
|
35233
35568
|
}
|
|
35234
35569
|
for (const slug of slugs) {
|
|
35235
35570
|
if (!slug.isDirectory()) continue;
|
|
35236
|
-
const slugDir =
|
|
35571
|
+
const slugDir = join61(projectsRoot, slug.name);
|
|
35237
35572
|
let entries2;
|
|
35238
35573
|
try {
|
|
35239
35574
|
entries2 = await readdir8(slugDir, { withFileTypes: true });
|
|
@@ -35242,9 +35577,9 @@ async function collectSidecars(projectsRoot) {
|
|
|
35242
35577
|
}
|
|
35243
35578
|
for (const entry2 of entries2) {
|
|
35244
35579
|
if (entry2.isFile() && SESSION_META_RE2.test(entry2.name)) {
|
|
35245
|
-
out.push({ path:
|
|
35580
|
+
out.push({ path: join61(slugDir, entry2.name), slug: slug.name });
|
|
35246
35581
|
} else if (entry2.isDirectory() && (entry2.name === "subagents" || entry2.name === "archive")) {
|
|
35247
|
-
const subDir =
|
|
35582
|
+
const subDir = join61(slugDir, entry2.name);
|
|
35248
35583
|
let subs;
|
|
35249
35584
|
try {
|
|
35250
35585
|
subs = await readdir8(subDir, { withFileTypes: true });
|
|
@@ -35253,7 +35588,7 @@ async function collectSidecars(projectsRoot) {
|
|
|
35253
35588
|
}
|
|
35254
35589
|
for (const s of subs) {
|
|
35255
35590
|
if (s.isFile() && SESSION_META_RE2.test(s.name)) {
|
|
35256
|
-
out.push({ path:
|
|
35591
|
+
out.push({ path: join61(subDir, s.name), slug: slug.name });
|
|
35257
35592
|
}
|
|
35258
35593
|
}
|
|
35259
35594
|
}
|
|
@@ -35262,7 +35597,7 @@ async function collectSidecars(projectsRoot) {
|
|
|
35262
35597
|
return out;
|
|
35263
35598
|
}
|
|
35264
35599
|
async function migrateSidecarAccountIds(opts = {}) {
|
|
35265
|
-
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
35600
|
+
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join61(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
|
|
35266
35601
|
const accounts = opts.accounts ?? listValidAccounts();
|
|
35267
35602
|
const result = {
|
|
35268
35603
|
scanned: 0,
|
|
@@ -35357,13 +35692,13 @@ var InboundHub = class {
|
|
|
35357
35692
|
if (s.subscriber && s.ready) {
|
|
35358
35693
|
if (payload.accountId !== s.accountId) this.crossAccountDelivered++;
|
|
35359
35694
|
s.subscriber(payload);
|
|
35360
|
-
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now });
|
|
35695
|
+
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now, spawnUndeliveredAt: null });
|
|
35361
35696
|
return;
|
|
35362
35697
|
}
|
|
35363
35698
|
if (this.subscriberAccounts(payload.senderId).some((a) => a !== payload.accountId)) {
|
|
35364
35699
|
this.crossAccountQueued++;
|
|
35365
35700
|
}
|
|
35366
|
-
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null });
|
|
35701
|
+
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null, spawnUndeliveredAt: null });
|
|
35367
35702
|
}
|
|
35368
35703
|
/** This account's channel server for the sender connected. Register the
|
|
35369
35704
|
* subscriber but do NOT drain yet — the drain waits for markReady so nothing
|
|
@@ -35422,13 +35757,16 @@ var InboundHub = class {
|
|
|
35422
35757
|
* is attached -> inbound-undelivered (the readiness handoff broke);
|
|
35423
35758
|
* - a still-queued message older than queuedThresholdMs with NO subscriber
|
|
35424
35759
|
* attached -> spawn-undelivered (the channel server never attached: spawn
|
|
35425
|
-
* failed, or the session ended and never resumed)
|
|
35760
|
+
* failed, or the session ended and never resumed). Task 2575 — reported
|
|
35761
|
+
* ONCE and RETAINED, because the gateway retries the spawn for it;
|
|
35762
|
+
* - a still-queued message with NO subscriber older than giveUpMs ->
|
|
35763
|
+
* spawn-given-up (Task 2575: the retry window closed), removed;
|
|
35426
35764
|
* - a drained message unacked past ackThresholdMs -> drain-undelivered (the
|
|
35427
35765
|
* channel server never confirmed receipt — the silent cold-start drop).
|
|
35428
35766
|
* Task 2290 — "a subscriber" means one attached for the SAME account, so a
|
|
35429
35767
|
* message waiting on its own account's cold start is not masked by another
|
|
35430
35768
|
* account's live subscriber on the same phone. Each message is emitted at
|
|
35431
|
-
* most once
|
|
35769
|
+
* most once per kind; every kind but spawn-undelivered removes it. */
|
|
35432
35770
|
sweep(now, t) {
|
|
35433
35771
|
const events = [];
|
|
35434
35772
|
for (const s of this.senders.values()) {
|
|
@@ -35438,9 +35776,17 @@ var InboundHub = class {
|
|
|
35438
35776
|
events.push({ kind: "inbound-undelivered", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: now - e.enqueuedAt });
|
|
35439
35777
|
return false;
|
|
35440
35778
|
}
|
|
35441
|
-
if (e.state === "queued" && !s.subscriber
|
|
35442
|
-
|
|
35443
|
-
|
|
35779
|
+
if (e.state === "queued" && !s.subscriber) {
|
|
35780
|
+
const age = now - e.enqueuedAt;
|
|
35781
|
+
if (age > t.giveUpMs) {
|
|
35782
|
+
events.push({ kind: "spawn-given-up", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: age });
|
|
35783
|
+
return false;
|
|
35784
|
+
}
|
|
35785
|
+
if (age > t.queuedThresholdMs && e.spawnUndeliveredAt == null) {
|
|
35786
|
+
e.spawnUndeliveredAt = now;
|
|
35787
|
+
events.push({ kind: "spawn-undelivered", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: age });
|
|
35788
|
+
}
|
|
35789
|
+
return true;
|
|
35444
35790
|
}
|
|
35445
35791
|
if (e.state === "drained" && e.drainedAt != null && now - e.drainedAt > t.ackThresholdMs) {
|
|
35446
35792
|
events.push({ kind: "drain-undelivered", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: now - e.drainedAt });
|
|
@@ -35451,6 +35797,23 @@ var InboundHub = class {
|
|
|
35451
35797
|
}
|
|
35452
35798
|
return events;
|
|
35453
35799
|
}
|
|
35800
|
+
/** Task 2575 — every key holding queued work with NO subscriber whose oldest
|
|
35801
|
+
* message is older than `thresholdMs`. This is the population a refused spawn
|
|
35802
|
+
* leaves behind: the gateway's sweep re-invokes the retained spawn arguments
|
|
35803
|
+
* for each one. Read-only — it emits nothing and mutates nothing, so calling
|
|
35804
|
+
* it cannot consume a terminal event the sweep is about to report. */
|
|
35805
|
+
heldSpawns(now, thresholdMs) {
|
|
35806
|
+
const out = [];
|
|
35807
|
+
for (const s of this.senders.values()) {
|
|
35808
|
+
if (s.subscriber) continue;
|
|
35809
|
+
const queued = s.inFlight.filter((e) => e.state === "queued");
|
|
35810
|
+
if (queued.length === 0) continue;
|
|
35811
|
+
const oldestAgeMs = Math.max(...queued.map((e) => now - e.enqueuedAt));
|
|
35812
|
+
if (oldestAgeMs <= thresholdMs) continue;
|
|
35813
|
+
out.push({ accountId: s.accountId, senderId: s.senderId, oldestAgeMs, count: queued.length });
|
|
35814
|
+
}
|
|
35815
|
+
return out;
|
|
35816
|
+
}
|
|
35454
35817
|
/** Task 2290 — every account holding a live subscriber for this phone, sorted.
|
|
35455
35818
|
* Read-only. Feeds the cross-account field on the inbound log line and the
|
|
35456
35819
|
* queued census, so a phone shared across accounts is greppable at the moment
|
|
@@ -35953,6 +36316,7 @@ init_loop_registry();
|
|
|
35953
36316
|
var QUEUED_THRESHOLD_MS = 3e4;
|
|
35954
36317
|
var ACK_THRESHOLD_MS = 3e4;
|
|
35955
36318
|
var SWEEP_INTERVAL_MS = 15e3;
|
|
36319
|
+
var GIVE_UP_MS = 3e5;
|
|
35956
36320
|
var MEDIA_STANZA_STALE_MS = 12e4;
|
|
35957
36321
|
var WaGateway = class {
|
|
35958
36322
|
constructor(deps) {
|
|
@@ -35974,6 +36338,14 @@ var WaGateway = class {
|
|
|
35974
36338
|
// effective session account — the file-path validation scope.
|
|
35975
36339
|
docContexts = /* @__PURE__ */ new Map();
|
|
35976
36340
|
spawning = /* @__PURE__ */ new Set();
|
|
36341
|
+
/** Task 2575 — the arguments of the last `ensureChannelSession` call per hub
|
|
36342
|
+
* key, so the sweep can re-invoke a spawn that was refused. The refusal is not
|
|
36343
|
+
* visible here — `ensureChannelSession` returns void and server/index.ts logs
|
|
36344
|
+
* and returns on the `{ error }` envelope — so the retry keys off queue state
|
|
36345
|
+
* (a queued message with no subscriber), which is the observable consequence
|
|
36346
|
+
* of the refusal. `attempts` is the per-key retry counter that rides the log
|
|
36347
|
+
* line, reset whenever a fresh inbound takes the spawn path. */
|
|
36348
|
+
heldSpawnArgs = /* @__PURE__ */ new Map();
|
|
35977
36349
|
seq = 0;
|
|
35978
36350
|
// Task 2110 — senderId -> the arrival ms of every still-unemitted non-audio
|
|
35979
36351
|
// media stanza for that sender, oldest first. This is the race window: a reply
|
|
@@ -36110,7 +36482,8 @@ var WaGateway = class {
|
|
|
36110
36482
|
reconcile(now) {
|
|
36111
36483
|
const events = this.hub.sweep(now, {
|
|
36112
36484
|
queuedThresholdMs: QUEUED_THRESHOLD_MS,
|
|
36113
|
-
ackThresholdMs: ACK_THRESHOLD_MS
|
|
36485
|
+
ackThresholdMs: ACK_THRESHOLD_MS,
|
|
36486
|
+
giveUpMs: GIVE_UP_MS
|
|
36114
36487
|
});
|
|
36115
36488
|
for (const e of events) {
|
|
36116
36489
|
console.error(
|
|
@@ -36132,6 +36505,53 @@ var WaGateway = class {
|
|
|
36132
36505
|
);
|
|
36133
36506
|
return events;
|
|
36134
36507
|
}
|
|
36508
|
+
/** Task 2575 — re-invoke the retained spawn for every key holding queued work
|
|
36509
|
+
* with no channel server attached. Driven by startSweeper() beside reconcile();
|
|
36510
|
+
* called directly with an injected clock in tests. Returns the number of
|
|
36511
|
+
* ensureChannelSession calls made.
|
|
36512
|
+
*
|
|
36513
|
+
* This is the missing action behind `op=spawn-undelivered`. Before it, that
|
|
36514
|
+
* line was emitted and nothing consumed it: on 2026-08-08 it fired for wa-1 at
|
|
36515
|
+
* ageMs=37743 and the message was never delivered.
|
|
36516
|
+
*
|
|
36517
|
+
* A retry while auth is still dead re-enters the same refusal and returns; the
|
|
36518
|
+
* cost is one refusal line plus one op=spawn-retry line per key per sweep,
|
|
36519
|
+
* bounded by the hub's 5-minute give-up. */
|
|
36520
|
+
async retryHeldSpawns(now) {
|
|
36521
|
+
const held = this.hub.heldSpawns(now, QUEUED_THRESHOLD_MS);
|
|
36522
|
+
let calls = 0;
|
|
36523
|
+
for (const h of held) {
|
|
36524
|
+
const key2 = hubKey(h.accountId, h.senderId);
|
|
36525
|
+
if (this.spawning.has(key2)) continue;
|
|
36526
|
+
const entry2 = this.heldSpawnArgs.get(key2);
|
|
36527
|
+
if (!entry2) {
|
|
36528
|
+
console.error(
|
|
36529
|
+
`[whatsapp-native] op=spawn-retry-skipped senderId=${h.senderId} accountId=${h.accountId} ageMs=${h.oldestAgeMs} queued=${h.count} reason=no-args`
|
|
36530
|
+
);
|
|
36531
|
+
continue;
|
|
36532
|
+
}
|
|
36533
|
+
entry2.attempts++;
|
|
36534
|
+
this.spawning.add(key2);
|
|
36535
|
+
calls++;
|
|
36536
|
+
try {
|
|
36537
|
+
await this.deps.ensureChannelSession(entry2.args);
|
|
36538
|
+
console.error(
|
|
36539
|
+
`[whatsapp-native] op=spawn-retry senderId=${h.senderId} accountId=${h.accountId} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=true`
|
|
36540
|
+
);
|
|
36541
|
+
} catch (err) {
|
|
36542
|
+
console.error(
|
|
36543
|
+
`[whatsapp-native] op=spawn-retry senderId=${h.senderId} accountId=${h.accountId} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=false reason=${err instanceof Error ? err.message : String(err)}`
|
|
36544
|
+
);
|
|
36545
|
+
} finally {
|
|
36546
|
+
this.spawning.delete(key2);
|
|
36547
|
+
}
|
|
36548
|
+
}
|
|
36549
|
+
const heldKeys = new Set(held.map((h) => hubKey(h.accountId, h.senderId)));
|
|
36550
|
+
for (const k of [...this.heldSpawnArgs.keys()]) {
|
|
36551
|
+
if (!heldKeys.has(k) && !this.spawning.has(k)) this.heldSpawnArgs.delete(k);
|
|
36552
|
+
}
|
|
36553
|
+
return calls;
|
|
36554
|
+
}
|
|
36135
36555
|
/** Arm the periodic reconciliation sweep in the long-lived UI process.
|
|
36136
36556
|
*
|
|
36137
36557
|
* Task 1814 — armed through the registry, so it appears on /activity. This
|
|
@@ -36149,9 +36569,14 @@ var WaGateway = class {
|
|
|
36149
36569
|
return registerLoop({
|
|
36150
36570
|
name: "whatsapp-native-sweep",
|
|
36151
36571
|
intervalMs,
|
|
36152
|
-
run: () => {
|
|
36153
|
-
const
|
|
36154
|
-
|
|
36572
|
+
run: async () => {
|
|
36573
|
+
const now = Date.now();
|
|
36574
|
+
const events = this.reconcile(now);
|
|
36575
|
+
const retried = await this.retryHeldSpawns(now);
|
|
36576
|
+
const parts = [];
|
|
36577
|
+
if (events.length > 0) parts.push(`${events.length} terminal events`);
|
|
36578
|
+
if (retried > 0) parts.push(`${retried} spawn retries`);
|
|
36579
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
36155
36580
|
}
|
|
36156
36581
|
});
|
|
36157
36582
|
}
|
|
@@ -36259,33 +36684,35 @@ var WaGateway = class {
|
|
|
36259
36684
|
console.error(
|
|
36260
36685
|
`[whatsapp-native] op=spawn-trigger senderId=${senderId} accountId=${input.effectiveAccountId}`
|
|
36261
36686
|
);
|
|
36687
|
+
const spawnArgs = {
|
|
36688
|
+
accountId: input.accountId,
|
|
36689
|
+
effectiveAccountId: input.effectiveAccountId,
|
|
36690
|
+
senderId,
|
|
36691
|
+
role: input.role ?? "admin",
|
|
36692
|
+
personId: input.personId ?? null,
|
|
36693
|
+
gatewayUrl: this.deps.gatewayUrl,
|
|
36694
|
+
serverPath: this.deps.serverPath,
|
|
36695
|
+
// Task 1575 — the gate's owner-self verdict; drives owner attribution
|
|
36696
|
+
// for an unbound owner self-phone at the spawn site.
|
|
36697
|
+
ownerSelfPhone: input.ownerSelfPhone,
|
|
36698
|
+
// Task 2262 — resolved once in onMessage; its presence routes this
|
|
36699
|
+
// spawn to /public-spawn under the named public agent.
|
|
36700
|
+
publicAgentSlug: input.publicAgentSlug,
|
|
36701
|
+
// Task 2281 — the correlation id, so the spawn outcome lines join the
|
|
36702
|
+
// rest of this inbound's lifeline. Only this call site takes it: the
|
|
36703
|
+
// passive branch above never reaches the public spawn, which is where
|
|
36704
|
+
// op=spawn-ok and op=spawn-failed are emitted.
|
|
36705
|
+
inboundId: input.inboundId,
|
|
36706
|
+
// Task 2234 — the registered-party label, forwarded verbatim so the
|
|
36707
|
+
// spawn env carries WA_SENDER_PARTY. Undefined for every other sender.
|
|
36708
|
+
// Only this call site takes it: the passive branch above hardcodes
|
|
36709
|
+
// role:'admin', and a registered party is public, so the combination
|
|
36710
|
+
// cannot arise there.
|
|
36711
|
+
party: input.party
|
|
36712
|
+
};
|
|
36713
|
+
this.heldSpawnArgs.set(spawnKey, { args: spawnArgs, attempts: 0 });
|
|
36262
36714
|
try {
|
|
36263
|
-
await this.deps.ensureChannelSession(
|
|
36264
|
-
accountId: input.accountId,
|
|
36265
|
-
effectiveAccountId: input.effectiveAccountId,
|
|
36266
|
-
senderId,
|
|
36267
|
-
role: input.role ?? "admin",
|
|
36268
|
-
personId: input.personId ?? null,
|
|
36269
|
-
gatewayUrl: this.deps.gatewayUrl,
|
|
36270
|
-
serverPath: this.deps.serverPath,
|
|
36271
|
-
// Task 1575 — the gate's owner-self verdict; drives owner attribution
|
|
36272
|
-
// for an unbound owner self-phone at the spawn site.
|
|
36273
|
-
ownerSelfPhone: input.ownerSelfPhone,
|
|
36274
|
-
// Task 2262 — resolved once in onMessage; its presence routes this
|
|
36275
|
-
// spawn to /public-spawn under the named public agent.
|
|
36276
|
-
publicAgentSlug: input.publicAgentSlug,
|
|
36277
|
-
// Task 2281 — the correlation id, so the spawn outcome lines join the
|
|
36278
|
-
// rest of this inbound's lifeline. Only this call site takes it: the
|
|
36279
|
-
// passive branch above never reaches the public spawn, which is where
|
|
36280
|
-
// op=spawn-ok and op=spawn-failed are emitted.
|
|
36281
|
-
inboundId: input.inboundId,
|
|
36282
|
-
// Task 2234 — the registered-party label, forwarded verbatim so the
|
|
36283
|
-
// spawn env carries WA_SENDER_PARTY. Undefined for every other sender.
|
|
36284
|
-
// Only this call site takes it: the passive branch above hardcodes
|
|
36285
|
-
// role:'admin', and a registered party is public, so the combination
|
|
36286
|
-
// cannot arise there.
|
|
36287
|
-
party: input.party
|
|
36288
|
-
});
|
|
36715
|
+
await this.deps.ensureChannelSession(spawnArgs);
|
|
36289
36716
|
} finally {
|
|
36290
36717
|
this.spawning.delete(spawnKey);
|
|
36291
36718
|
}
|
|
@@ -36813,8 +37240,41 @@ function createWebchatChannelRoutes(deps) {
|
|
|
36813
37240
|
}
|
|
36814
37241
|
|
|
36815
37242
|
// app/lib/webchat/gateway/resolve-turn-answer.ts
|
|
36816
|
-
import { existsSync as existsSync52, openSync as openSync9, readSync as readSync8, closeSync as closeSync9, readdirSync as
|
|
36817
|
-
import { join as
|
|
37243
|
+
import { existsSync as existsSync52, openSync as openSync9, readSync as readSync8, closeSync as closeSync9, readdirSync as readdirSync32 } from "fs";
|
|
37244
|
+
import { join as join62 } from "path";
|
|
37245
|
+
|
|
37246
|
+
// app/lib/channel-delivery/outbound-guard.ts
|
|
37247
|
+
var INTERNAL_LEAK_PATTERNS = [
|
|
37248
|
+
{ name: "task-id", re: /\bTasks?[\s-]+\d{2,4}\b/g },
|
|
37249
|
+
{ name: "media-path", re: /\/tmp\/maxy-media[A-Za-z0-9_./-]*/g },
|
|
37250
|
+
{ name: "repo-path", re: /\bplatform\/[A-Za-z0-9_./-]+/g },
|
|
37251
|
+
{ name: "bundle", re: /\bserver\.(?:js|cjs|mjs)\b/g },
|
|
37252
|
+
{ name: "source-file", re: /\b[A-Za-z0-9_-]+\.tsx?\b/g },
|
|
37253
|
+
{ name: "log-tag", re: /\[[a-z]+(?:-[a-z]+)+\]/g },
|
|
37254
|
+
{ name: "op-tag", re: /\bop=[a-z][a-z-]*\b/g }
|
|
37255
|
+
];
|
|
37256
|
+
function redactInternalLeaks(text) {
|
|
37257
|
+
const redactions = [];
|
|
37258
|
+
let clean = text;
|
|
37259
|
+
for (const { name, re } of INTERNAL_LEAK_PATTERNS) {
|
|
37260
|
+
clean = clean.replace(re, (m) => {
|
|
37261
|
+
redactions.push({ pattern: name, match: m });
|
|
37262
|
+
return "[redacted]";
|
|
37263
|
+
});
|
|
37264
|
+
}
|
|
37265
|
+
return { clean, redactions };
|
|
37266
|
+
}
|
|
37267
|
+
function guardOutbound(senderId, text) {
|
|
37268
|
+
const { clean, redactions } = redactInternalLeaks(text);
|
|
37269
|
+
for (const r of redactions) {
|
|
37270
|
+
console.error(
|
|
37271
|
+
`[channel-guard] op=redact senderId=${senderId} pattern=${r.pattern} bytes=${Buffer.byteLength(r.match, "utf8")}`
|
|
37272
|
+
);
|
|
37273
|
+
}
|
|
37274
|
+
return clean;
|
|
37275
|
+
}
|
|
37276
|
+
|
|
37277
|
+
// app/lib/webchat/gateway/resolve-turn-answer.ts
|
|
36818
37278
|
function resolveTurnAnswer(p) {
|
|
36819
37279
|
if (!SESSION_ID_RE2.test(p.sessionId)) return "";
|
|
36820
37280
|
const path = findJsonl(p.projectsRoot, p.sessionId);
|
|
@@ -36839,18 +37299,18 @@ function resolveTurnAnswer(p) {
|
|
|
36839
37299
|
for (const turn of turns) {
|
|
36840
37300
|
if (turn.kind === "agent-webchat-final" || turn.kind === "agent-reply") parts.push(turn.text);
|
|
36841
37301
|
}
|
|
36842
|
-
return segmentText(parts.join("\n\n")).join("\n\n");
|
|
37302
|
+
return segmentText(guardOutbound(p.senderId, parts.join("\n\n"))).join("\n\n");
|
|
36843
37303
|
}
|
|
36844
37304
|
function findJsonl(projectsRoot, sessionId) {
|
|
36845
37305
|
if (!existsSync52(projectsRoot)) return null;
|
|
36846
37306
|
let slugs;
|
|
36847
37307
|
try {
|
|
36848
|
-
slugs =
|
|
37308
|
+
slugs = readdirSync32(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
36849
37309
|
} catch {
|
|
36850
37310
|
return null;
|
|
36851
37311
|
}
|
|
36852
37312
|
for (const slug of slugs) {
|
|
36853
|
-
const live =
|
|
37313
|
+
const live = join62(projectsRoot, slug, `${sessionId}.jsonl`);
|
|
36854
37314
|
if (existsSync52(live)) return live;
|
|
36855
37315
|
}
|
|
36856
37316
|
return null;
|
|
@@ -36998,7 +37458,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
36998
37458
|
if (rec) rec.lastActivityAt = Date.now();
|
|
36999
37459
|
const awaiter = this.replyAwaiters.get(key2);
|
|
37000
37460
|
const projectsRoot = this.deps.publicProjectsRoot?.() ?? "";
|
|
37001
|
-
const text = projectsRoot ? resolveTurnAnswer({ ...turn, projectsRoot }) : "";
|
|
37461
|
+
const text = projectsRoot ? resolveTurnAnswer({ ...turn, senderId: keyDisplay(key2), projectsRoot }) : "";
|
|
37002
37462
|
const bytes = Buffer.byteLength(text, "utf8");
|
|
37003
37463
|
const deliveredAt = this.lastDeliveredAt.get(key2);
|
|
37004
37464
|
const latency = deliveredAt != null ? Date.now() - deliveredAt : -1;
|
|
@@ -37493,37 +37953,6 @@ var AnswerAccumulator = class {
|
|
|
37493
37953
|
}
|
|
37494
37954
|
};
|
|
37495
37955
|
|
|
37496
|
-
// app/lib/channel-delivery/outbound-guard.ts
|
|
37497
|
-
var INTERNAL_LEAK_PATTERNS = [
|
|
37498
|
-
{ name: "task-id", re: /\bTasks?[\s-]+\d{2,4}\b/g },
|
|
37499
|
-
{ name: "media-path", re: /\/tmp\/maxy-media[A-Za-z0-9_./-]*/g },
|
|
37500
|
-
{ name: "repo-path", re: /\bplatform\/[A-Za-z0-9_./-]+/g },
|
|
37501
|
-
{ name: "bundle", re: /\bserver\.(?:js|cjs|mjs)\b/g },
|
|
37502
|
-
{ name: "source-file", re: /\b[A-Za-z0-9_-]+\.tsx?\b/g },
|
|
37503
|
-
{ name: "log-tag", re: /\[[a-z]+(?:-[a-z]+)+\]/g },
|
|
37504
|
-
{ name: "op-tag", re: /\bop=[a-z][a-z-]*\b/g }
|
|
37505
|
-
];
|
|
37506
|
-
function redactInternalLeaks(text) {
|
|
37507
|
-
const redactions = [];
|
|
37508
|
-
let clean = text;
|
|
37509
|
-
for (const { name, re } of INTERNAL_LEAK_PATTERNS) {
|
|
37510
|
-
clean = clean.replace(re, (m) => {
|
|
37511
|
-
redactions.push({ pattern: name, match: m });
|
|
37512
|
-
return "[redacted]";
|
|
37513
|
-
});
|
|
37514
|
-
}
|
|
37515
|
-
return { clean, redactions };
|
|
37516
|
-
}
|
|
37517
|
-
function guardOutbound(senderId, text) {
|
|
37518
|
-
const { clean, redactions } = redactInternalLeaks(text);
|
|
37519
|
-
for (const r of redactions) {
|
|
37520
|
-
console.error(
|
|
37521
|
-
`[channel-guard] op=redact senderId=${senderId} pattern=${r.pattern} bytes=${Buffer.byteLength(r.match, "utf8")}`
|
|
37522
|
-
);
|
|
37523
|
-
}
|
|
37524
|
-
return clean;
|
|
37525
|
-
}
|
|
37526
|
-
|
|
37527
37956
|
// app/lib/channel-delivery/emitter.ts
|
|
37528
37957
|
function followerPendingMaxMs() {
|
|
37529
37958
|
return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
|
|
@@ -37865,14 +38294,14 @@ function makeFileDelivery(opts) {
|
|
|
37865
38294
|
}
|
|
37866
38295
|
|
|
37867
38296
|
// app/lib/webchat/file-delivery.ts
|
|
37868
|
-
var
|
|
38297
|
+
var TAG61 = "[webchat-adaptor]";
|
|
37869
38298
|
function platformRoot2() {
|
|
37870
38299
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
37871
38300
|
}
|
|
37872
38301
|
function makeWebchatSendFile(entry2) {
|
|
37873
38302
|
return async (filePath) => {
|
|
37874
38303
|
if (!entry2.accountId) {
|
|
37875
|
-
console.error(`${
|
|
38304
|
+
console.error(`${TAG61} file-delivery reject reason=no-account sender=${entry2.senderId}`);
|
|
37876
38305
|
return { ok: false, error: "no-account" };
|
|
37877
38306
|
}
|
|
37878
38307
|
const accountDir = resolve47(platformRoot2(), "..", "data/accounts", entry2.accountId);
|
|
@@ -37880,14 +38309,14 @@ function makeWebchatSendFile(entry2) {
|
|
|
37880
38309
|
const resolved = realpathSync9(filePath);
|
|
37881
38310
|
const accountResolved = realpathSync9(accountDir);
|
|
37882
38311
|
if (!resolved.startsWith(accountResolved + "/")) {
|
|
37883
|
-
console.error(`${
|
|
38312
|
+
console.error(`${TAG61} file-delivery reject reason=outside_account_directory sender=${entry2.senderId}`);
|
|
37884
38313
|
return { ok: false, error: "outside-account" };
|
|
37885
38314
|
}
|
|
37886
38315
|
return { ok: true };
|
|
37887
38316
|
} catch (err) {
|
|
37888
38317
|
const code = err.code;
|
|
37889
38318
|
console.error(
|
|
37890
|
-
`${
|
|
38319
|
+
`${TAG61} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry2.senderId}`
|
|
37891
38320
|
);
|
|
37892
38321
|
return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
|
|
37893
38322
|
}
|
|
@@ -37896,7 +38325,7 @@ function makeWebchatSendFile(entry2) {
|
|
|
37896
38325
|
function makeWebchatFileDelivery(entry2) {
|
|
37897
38326
|
return makeFileDelivery({
|
|
37898
38327
|
entry: entry2,
|
|
37899
|
-
tag:
|
|
38328
|
+
tag: TAG61,
|
|
37900
38329
|
channel: "webchat",
|
|
37901
38330
|
sendFile: makeWebchatSendFile(entry2),
|
|
37902
38331
|
deferUntilVerdict: true
|
|
@@ -37976,7 +38405,7 @@ function buildPublicWebchatSpawnRequest(input) {
|
|
|
37976
38405
|
}
|
|
37977
38406
|
|
|
37978
38407
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
37979
|
-
var
|
|
38408
|
+
var TAG62 = "[whatsapp-adaptor]";
|
|
37980
38409
|
var SEND_USER_FILE2 = "SendUserFile";
|
|
37981
38410
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
37982
38411
|
function platformRoot3() {
|
|
@@ -37994,7 +38423,7 @@ function makeWhatsAppSendFile(entry2, maxyAccountId) {
|
|
|
37994
38423
|
});
|
|
37995
38424
|
if (result.ok) return { ok: true };
|
|
37996
38425
|
console.error(
|
|
37997
|
-
`${
|
|
38426
|
+
`${TAG62} file-delivery reject reason=send-failed sender=${entry2.senderId} status=${result.status} message=${result.error}`
|
|
37998
38427
|
);
|
|
37999
38428
|
return { ok: false, error: result.error };
|
|
38000
38429
|
};
|
|
@@ -38002,7 +38431,7 @@ function makeWhatsAppSendFile(entry2, maxyAccountId) {
|
|
|
38002
38431
|
function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
|
|
38003
38432
|
const shared = makeFileDelivery({
|
|
38004
38433
|
entry: entry2,
|
|
38005
|
-
tag:
|
|
38434
|
+
tag: TAG62,
|
|
38006
38435
|
channel: "whatsapp",
|
|
38007
38436
|
sendFile: makeWhatsAppSendFile(entry2, maxyAccountId)
|
|
38008
38437
|
});
|
|
@@ -38037,7 +38466,7 @@ function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
|
|
|
38037
38466
|
if (!delivered) {
|
|
38038
38467
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
38039
38468
|
console.error(
|
|
38040
|
-
`${
|
|
38469
|
+
`${TAG62} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
38041
38470
|
);
|
|
38042
38471
|
}
|
|
38043
38472
|
}
|
|
@@ -38129,21 +38558,22 @@ function startNativeFileFollower(input) {
|
|
|
38129
38558
|
}
|
|
38130
38559
|
|
|
38131
38560
|
// app/lib/channel-delivery/follower-registry.ts
|
|
38132
|
-
function createFollowerRegistry(start) {
|
|
38561
|
+
function createFollowerRegistry(start, keyOf2 = (p) => p.senderId) {
|
|
38133
38562
|
const controllers = /* @__PURE__ */ new Map();
|
|
38134
38563
|
const params = /* @__PURE__ */ new Map();
|
|
38135
38564
|
return {
|
|
38136
38565
|
attach(p) {
|
|
38137
|
-
|
|
38138
|
-
|
|
38566
|
+
const doorId = keyOf2(p);
|
|
38567
|
+
controllers.get(doorId)?.abort();
|
|
38568
|
+
params.set(doorId, p);
|
|
38139
38569
|
let ac;
|
|
38140
38570
|
ac = start(p, () => {
|
|
38141
|
-
if (controllers.get(
|
|
38571
|
+
if (controllers.get(doorId) === ac) controllers.delete(doorId);
|
|
38142
38572
|
});
|
|
38143
|
-
controllers.set(
|
|
38573
|
+
controllers.set(doorId, ac);
|
|
38144
38574
|
},
|
|
38145
38575
|
live: () => [...controllers.keys()],
|
|
38146
|
-
paramsFor: (
|
|
38576
|
+
paramsFor: (doorId) => params.get(doorId) ?? null
|
|
38147
38577
|
};
|
|
38148
38578
|
}
|
|
38149
38579
|
|
|
@@ -38156,41 +38586,79 @@ var id82 = (s) => s.slice(0, 8);
|
|
|
38156
38586
|
function followerCensusTick(deps, state = makeCensusState()) {
|
|
38157
38587
|
const bound = deps.bound();
|
|
38158
38588
|
const live = new Set(deps.live());
|
|
38159
|
-
|
|
38160
|
-
const
|
|
38589
|
+
const label = deps.label ?? id82;
|
|
38590
|
+
for (const doorId of live) state.attempts.delete(doorId);
|
|
38591
|
+
const missing = bound.filter((doorId) => !live.has(doorId));
|
|
38161
38592
|
console.error(
|
|
38162
|
-
`[channel-follower] op=follower-census channel=${deps.channel} bound=${bound.length} attached=${live.size} missing=${missing.length} missingSenders=${missing.map(
|
|
38593
|
+
`[channel-follower] op=follower-census channel=${deps.channel} bound=${bound.length} attached=${live.size} missing=${missing.length} missingSenders=${missing.map(label).join(",") || "none"}`
|
|
38163
38594
|
);
|
|
38164
|
-
for (const
|
|
38165
|
-
const used = state.attempts.get(
|
|
38595
|
+
for (const doorId of missing) {
|
|
38596
|
+
const used = state.attempts.get(doorId) ?? 0;
|
|
38166
38597
|
if (used >= CENSUS_MAX_REATTEMPTS) {
|
|
38167
38598
|
console.error(
|
|
38168
|
-
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=${id82(deps.paramsFor(
|
|
38599
|
+
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=${id82(deps.paramsFor(doorId)?.sessionId ?? "none")} senderId=${label(doorId)} outcome=skipped reason=reattach-exhausted attempts=${used}`
|
|
38169
38600
|
);
|
|
38170
38601
|
continue;
|
|
38171
38602
|
}
|
|
38172
|
-
const p = deps.paramsFor(
|
|
38603
|
+
const p = deps.paramsFor(doorId);
|
|
38173
38604
|
if (!p) {
|
|
38174
38605
|
console.error(
|
|
38175
|
-
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=none senderId=${
|
|
38606
|
+
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=none senderId=${label(doorId)} outcome=failed reason=no-attach-params`
|
|
38176
38607
|
);
|
|
38177
38608
|
continue;
|
|
38178
38609
|
}
|
|
38179
38610
|
try {
|
|
38180
|
-
state.attempts.set(
|
|
38611
|
+
state.attempts.set(doorId, used + 1);
|
|
38181
38612
|
deps.attach(p);
|
|
38182
38613
|
console.error(
|
|
38183
|
-
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=${id82(p.sessionId)} senderId=${
|
|
38614
|
+
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=${id82(p.sessionId)} senderId=${label(doorId)} outcome=attached reason=none`
|
|
38184
38615
|
);
|
|
38185
38616
|
} catch (err) {
|
|
38186
38617
|
console.error(
|
|
38187
|
-
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=${id82(p.sessionId)} senderId=${
|
|
38618
|
+
`[channel-follower] op=follower-reattach channel=${deps.channel} sessionId=${id82(p.sessionId)} senderId=${label(doorId)} outcome=failed reason=${err instanceof Error ? err.message : String(err)}`
|
|
38188
38619
|
);
|
|
38189
38620
|
}
|
|
38190
38621
|
}
|
|
38191
38622
|
return `${deps.channel}: ${bound.length} door(s), ${missing.length} without a reader`;
|
|
38192
38623
|
}
|
|
38193
38624
|
|
|
38625
|
+
// app/lib/channel-delivery/whatsapp-door.ts
|
|
38626
|
+
function whatsappDoorId(p) {
|
|
38627
|
+
return hubKey(p.effectiveAccountId, p.senderId);
|
|
38628
|
+
}
|
|
38629
|
+
function whatsappDoorLabel(doorId) {
|
|
38630
|
+
const [decoded] = decodeHubKeys([doorId]);
|
|
38631
|
+
return decoded ? `${id82(decoded.accountId)}:${id82(decoded.senderId)}` : id82(doorId);
|
|
38632
|
+
}
|
|
38633
|
+
|
|
38634
|
+
// app/lib/channel-delivery/refusal-notice.ts
|
|
38635
|
+
var TAG63 = "[channel-delivery]";
|
|
38636
|
+
var notified = /* @__PURE__ */ new Set();
|
|
38637
|
+
function refusalNoticeText(reason) {
|
|
38638
|
+
return reason === "claude-auth-dead" ? "Your message arrived but no assistant could be started: the Claude sign-in on this install is not active. An operator has to sign in again; send this again once they have." : `Your message arrived but no assistant could be started (${reason}). Nothing was lost; try again shortly.`;
|
|
38639
|
+
}
|
|
38640
|
+
async function sendRefusalNotice(input) {
|
|
38641
|
+
if (notified.has(input.doorId)) return;
|
|
38642
|
+
const fields = `channel=${input.channel} sender=${input.sender} reason=${input.reason}`;
|
|
38643
|
+
if (!input.send) {
|
|
38644
|
+
console.error(`${TAG63} op=refusal-notice-failed ${fields} error=no-target`);
|
|
38645
|
+
return;
|
|
38646
|
+
}
|
|
38647
|
+
try {
|
|
38648
|
+
await input.send(guardOutbound(input.sender, refusalNoticeText(input.reason)));
|
|
38649
|
+
} catch (e) {
|
|
38650
|
+
console.error(
|
|
38651
|
+
`${TAG63} op=refusal-notice-failed ${fields} error=${e instanceof Error ? e.message : String(e)}`
|
|
38652
|
+
);
|
|
38653
|
+
return;
|
|
38654
|
+
}
|
|
38655
|
+
notified.add(input.doorId);
|
|
38656
|
+
console.error(`${TAG63} op=refusal-notice ${fields} status=${input.status}`);
|
|
38657
|
+
}
|
|
38658
|
+
function clearRefusalNotice(doorId) {
|
|
38659
|
+
notified.delete(doorId);
|
|
38660
|
+
}
|
|
38661
|
+
|
|
38194
38662
|
// app/lib/telegram/gateway/inbound-hub.ts
|
|
38195
38663
|
var InboundHub3 = class {
|
|
38196
38664
|
keys = /* @__PURE__ */ new Map();
|
|
@@ -38211,9 +38679,9 @@ var InboundHub3 = class {
|
|
|
38211
38679
|
const s = this.state(payload.key);
|
|
38212
38680
|
if (s.subscriber && s.ready) {
|
|
38213
38681
|
s.subscriber(payload);
|
|
38214
|
-
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now });
|
|
38682
|
+
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now, spawnUndeliveredAt: null });
|
|
38215
38683
|
} else {
|
|
38216
|
-
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null });
|
|
38684
|
+
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null, spawnUndeliveredAt: null });
|
|
38217
38685
|
}
|
|
38218
38686
|
}
|
|
38219
38687
|
/** A key's channel server connected. Register the subscriber but do NOT
|
|
@@ -38273,9 +38741,18 @@ var InboundHub3 = class {
|
|
|
38273
38741
|
* that never reached the agent:
|
|
38274
38742
|
* - a still-queued message older than queuedThresholdMs WHILE a subscriber
|
|
38275
38743
|
* is attached -> inbound-undelivered (the readiness handoff broke);
|
|
38744
|
+
* - a still-queued message older than queuedThresholdMs with NO subscriber
|
|
38745
|
+
* attached -> spawn-undelivered (the channel server never attached: the
|
|
38746
|
+
* spawn was refused, or the session ended and never resumed). Task 2575 —
|
|
38747
|
+
* this case did not exist on this door at all before, which is why tg-1
|
|
38748
|
+
* survived the 2026-08-08 outage unreported. Reported ONCE and RETAINED,
|
|
38749
|
+
* because the gateway retries the spawn for it;
|
|
38750
|
+
* - a still-queued message with NO subscriber older than giveUpMs ->
|
|
38751
|
+
* spawn-given-up (Task 2575: the retry window closed), removed;
|
|
38276
38752
|
* - a drained message unacked past ackThresholdMs -> drain-undelivered (the
|
|
38277
38753
|
* channel server never confirmed receipt — the silent cold-start drop).
|
|
38278
|
-
* Each message is emitted at most once
|
|
38754
|
+
* Each message is emitted at most once per kind; every kind but
|
|
38755
|
+
* spawn-undelivered removes it. */
|
|
38279
38756
|
sweep(now, t) {
|
|
38280
38757
|
const events = [];
|
|
38281
38758
|
for (const [key2, s] of this.keys) {
|
|
@@ -38284,6 +38761,18 @@ var InboundHub3 = class {
|
|
|
38284
38761
|
events.push({ kind: "inbound-undelivered", key: key2, messageId: e.payload.messageId, ageMs: now - e.enqueuedAt });
|
|
38285
38762
|
return false;
|
|
38286
38763
|
}
|
|
38764
|
+
if (e.state === "queued" && !s.subscriber) {
|
|
38765
|
+
const age = now - e.enqueuedAt;
|
|
38766
|
+
if (age > t.giveUpMs) {
|
|
38767
|
+
events.push({ kind: "spawn-given-up", key: key2, messageId: e.payload.messageId, ageMs: age });
|
|
38768
|
+
return false;
|
|
38769
|
+
}
|
|
38770
|
+
if (age > t.queuedThresholdMs && e.spawnUndeliveredAt == null) {
|
|
38771
|
+
e.spawnUndeliveredAt = now;
|
|
38772
|
+
events.push({ kind: "spawn-undelivered", key: key2, messageId: e.payload.messageId, ageMs: age });
|
|
38773
|
+
}
|
|
38774
|
+
return true;
|
|
38775
|
+
}
|
|
38287
38776
|
if (e.state === "drained" && e.drainedAt != null && now - e.drainedAt > t.ackThresholdMs) {
|
|
38288
38777
|
events.push({ kind: "drain-undelivered", key: key2, messageId: e.payload.messageId, ageMs: now - e.drainedAt });
|
|
38289
38778
|
return false;
|
|
@@ -38293,6 +38782,23 @@ var InboundHub3 = class {
|
|
|
38293
38782
|
}
|
|
38294
38783
|
return events;
|
|
38295
38784
|
}
|
|
38785
|
+
/** Task 2575 — every key holding queued work with NO subscriber whose oldest
|
|
38786
|
+
* message is older than `thresholdMs`. The gateway's sweep re-invokes the
|
|
38787
|
+
* retained spawn arguments for each one. Read-only — it emits nothing and
|
|
38788
|
+
* mutates nothing, so calling it cannot consume a terminal event the sweep is
|
|
38789
|
+
* about to report. */
|
|
38790
|
+
heldSpawns(now, thresholdMs) {
|
|
38791
|
+
const out = [];
|
|
38792
|
+
for (const [key2, s] of this.keys) {
|
|
38793
|
+
if (s.subscriber) continue;
|
|
38794
|
+
const queued = s.inFlight.filter((e) => e.state === "queued");
|
|
38795
|
+
if (queued.length === 0) continue;
|
|
38796
|
+
const oldestAgeMs = Math.max(...queued.map((e) => now - e.enqueuedAt));
|
|
38797
|
+
if (oldestAgeMs <= thresholdMs) continue;
|
|
38798
|
+
out.push({ key: key2, oldestAgeMs, count: queued.length });
|
|
38799
|
+
}
|
|
38800
|
+
return out;
|
|
38801
|
+
}
|
|
38296
38802
|
/** Whether a key currently has a live channel server attached. The gateway
|
|
38297
38803
|
* uses this to decide whether an inbound needs a cold-start spawn/resume. */
|
|
38298
38804
|
hasSubscriber(key2) {
|
|
@@ -38466,6 +38972,7 @@ init_loop_registry();
|
|
|
38466
38972
|
var QUEUED_THRESHOLD_MS3 = 3e4;
|
|
38467
38973
|
var ACK_THRESHOLD_MS3 = 3e4;
|
|
38468
38974
|
var SWEEP_INTERVAL_MS3 = 15e3;
|
|
38975
|
+
var GIVE_UP_MS2 = 3e5;
|
|
38469
38976
|
function telegramSubjectSessionId(input) {
|
|
38470
38977
|
if (input.role === "admin") {
|
|
38471
38978
|
const adminUserId = input.adminUserId ?? void 0;
|
|
@@ -38479,16 +38986,6 @@ var TelegramGateway = class {
|
|
|
38479
38986
|
}
|
|
38480
38987
|
hub = new InboundHub3();
|
|
38481
38988
|
replies = /* @__PURE__ */ new Map();
|
|
38482
|
-
/** Task 2559 — the keys whose last inbound was `role:'admin'`. The follower
|
|
38483
|
-
* census's bound set is the doors that SHOULD have a reader, and a public
|
|
38484
|
-
* Telegram session is given none by design: the public branch of
|
|
38485
|
-
* `buildTelegramEnsureSession` returns before the attach block, because the
|
|
38486
|
-
* file follower is an admin surface. Including a public key would report it
|
|
38487
|
-
* missing on every tick for ever and stop `missing=` ever reaching zero,
|
|
38488
|
-
* which is the one signal the census exists to give. Removed on a public
|
|
38489
|
-
* inbound, because the role is read from account config on every inbound and
|
|
38490
|
-
* a bot can be flipped between the two. */
|
|
38491
|
-
adminKeys = /* @__PURE__ */ new Set();
|
|
38492
38989
|
spawning = /* @__PURE__ */ new Set();
|
|
38493
38990
|
/** Task 2428 — keys whose channel server signalled ready while their spawn was
|
|
38494
38991
|
* still in flight. The harness discards a channel notification that arrives
|
|
@@ -38499,6 +38996,14 @@ var TelegramGateway = class {
|
|
|
38499
38996
|
* `resume-bound`. Holding the signal here rather than draining leaves
|
|
38500
38997
|
* `hub.ready` false, so every message in the window queues and none is lost. */
|
|
38501
38998
|
readyWhileSpawning = /* @__PURE__ */ new Set();
|
|
38999
|
+
/** Task 2575 — the arguments of the last `ensureChannelSession` call per key,
|
|
39000
|
+
* so the sweep can re-invoke a spawn that was refused. The refusal is not
|
|
39001
|
+
* visible here — `ensureChannelSession` returns void and the refusal sites in
|
|
39002
|
+
* server/telegram-ensure-session.ts log and return — so the retry keys off
|
|
39003
|
+
* queue state, the observable consequence of the refusal. `attempts` is the
|
|
39004
|
+
* per-key retry counter that rides the log line, reset whenever a fresh
|
|
39005
|
+
* inbound takes the spawn path. */
|
|
39006
|
+
heldSpawnArgs = /* @__PURE__ */ new Map();
|
|
38502
39007
|
seq = 0;
|
|
38503
39008
|
/** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
|
|
38504
39009
|
routes() {
|
|
@@ -38545,7 +39050,8 @@ var TelegramGateway = class {
|
|
|
38545
39050
|
reconcile(now) {
|
|
38546
39051
|
const events = this.hub.sweep(now, {
|
|
38547
39052
|
queuedThresholdMs: QUEUED_THRESHOLD_MS3,
|
|
38548
|
-
ackThresholdMs: ACK_THRESHOLD_MS3
|
|
39053
|
+
ackThresholdMs: ACK_THRESHOLD_MS3,
|
|
39054
|
+
giveUpMs: GIVE_UP_MS2
|
|
38549
39055
|
});
|
|
38550
39056
|
for (const e of events) {
|
|
38551
39057
|
console.error(
|
|
@@ -38554,6 +39060,49 @@ var TelegramGateway = class {
|
|
|
38554
39060
|
}
|
|
38555
39061
|
return events;
|
|
38556
39062
|
}
|
|
39063
|
+
/** Task 2575 — re-invoke the retained spawn for every key holding queued work
|
|
39064
|
+
* with no channel server attached. Driven by startSweeper() beside reconcile();
|
|
39065
|
+
* called directly with an injected clock in tests. Returns the number of
|
|
39066
|
+
* ensureChannelSession calls made.
|
|
39067
|
+
*
|
|
39068
|
+
* This door kept its held message across the 2026-08-08 outage but still
|
|
39069
|
+
* waited 4m41s for it, because the drain fires only when a later inbound
|
|
39070
|
+
* spawns successfully. This is the trigger that removes that wait. */
|
|
39071
|
+
async retryHeldSpawns(now) {
|
|
39072
|
+
const held = this.hub.heldSpawns(now, QUEUED_THRESHOLD_MS3);
|
|
39073
|
+
let calls = 0;
|
|
39074
|
+
for (const h of held) {
|
|
39075
|
+
if (this.spawning.has(h.key)) continue;
|
|
39076
|
+
const entry2 = this.heldSpawnArgs.get(h.key);
|
|
39077
|
+
if (!entry2) {
|
|
39078
|
+
console.error(
|
|
39079
|
+
`[telegram-native] op=spawn-retry-skipped key=${h.key} ageMs=${h.oldestAgeMs} queued=${h.count} reason=no-args`
|
|
39080
|
+
);
|
|
39081
|
+
continue;
|
|
39082
|
+
}
|
|
39083
|
+
entry2.attempts++;
|
|
39084
|
+
this.spawning.add(h.key);
|
|
39085
|
+
calls++;
|
|
39086
|
+
try {
|
|
39087
|
+
await this.deps.ensureChannelSession(entry2.args);
|
|
39088
|
+
console.error(
|
|
39089
|
+
`[telegram-native] op=spawn-retry key=${h.key} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=true`
|
|
39090
|
+
);
|
|
39091
|
+
} catch (err) {
|
|
39092
|
+
console.error(
|
|
39093
|
+
`[telegram-native] op=spawn-retry key=${h.key} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=false reason=${err instanceof Error ? err.message : String(err)}`
|
|
39094
|
+
);
|
|
39095
|
+
} finally {
|
|
39096
|
+
this.spawning.delete(h.key);
|
|
39097
|
+
if (this.readyWhileSpawning.delete(h.key)) this.drain(h.key);
|
|
39098
|
+
}
|
|
39099
|
+
}
|
|
39100
|
+
const heldKeys = new Set(held.map((h) => h.key));
|
|
39101
|
+
for (const k of [...this.heldSpawnArgs.keys()]) {
|
|
39102
|
+
if (!heldKeys.has(k) && !this.spawning.has(k)) this.heldSpawnArgs.delete(k);
|
|
39103
|
+
}
|
|
39104
|
+
return calls;
|
|
39105
|
+
}
|
|
38557
39106
|
/** Arm the periodic reconciliation sweep in the long-lived UI process.
|
|
38558
39107
|
*
|
|
38559
39108
|
* Task 1814 — armed through the registry, so it appears on /activity. This
|
|
@@ -38571,9 +39120,14 @@ var TelegramGateway = class {
|
|
|
38571
39120
|
return registerLoop({
|
|
38572
39121
|
name: "telegram-native-sweep",
|
|
38573
39122
|
intervalMs,
|
|
38574
|
-
run: () => {
|
|
38575
|
-
const
|
|
38576
|
-
|
|
39123
|
+
run: async () => {
|
|
39124
|
+
const now = Date.now();
|
|
39125
|
+
const events = this.reconcile(now);
|
|
39126
|
+
const retried = await this.retryHeldSpawns(now);
|
|
39127
|
+
const parts = [];
|
|
39128
|
+
if (events.length > 0) parts.push(`${events.length} terminal events`);
|
|
39129
|
+
if (retried > 0) parts.push(`${retried} spawn retries`);
|
|
39130
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
38577
39131
|
}
|
|
38578
39132
|
});
|
|
38579
39133
|
}
|
|
@@ -38600,8 +39154,6 @@ var TelegramGateway = class {
|
|
|
38600
39154
|
const willDeliverNow = this.hub.isReady(key2);
|
|
38601
39155
|
const source = input.source ?? "user";
|
|
38602
39156
|
this.replies.set(key2, input.reply);
|
|
38603
|
-
if (input.role === "admin") this.adminKeys.add(key2);
|
|
38604
|
-
else this.adminKeys.delete(key2);
|
|
38605
39157
|
let standingRules;
|
|
38606
39158
|
if (input.role === "admin" && this.deps.fetchStandingRules) {
|
|
38607
39159
|
const res = await this.deps.fetchStandingRules(input.accountId);
|
|
@@ -38634,25 +39186,36 @@ var TelegramGateway = class {
|
|
|
38634
39186
|
console.error(
|
|
38635
39187
|
`[telegram-native] op=inbound key=${key2} botId=${input.botId} accountId=${input.accountId} role=${input.role} agent=${input.agentSlug} bytes=${bytes} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"} sessionId=${resolvedSessionId}`
|
|
38636
39188
|
);
|
|
39189
|
+
try {
|
|
39190
|
+
if (source === "user") {
|
|
39191
|
+
this.deps.sendChatAction?.({ botToken: input.botToken, chatId: input.chatId, key: key2 });
|
|
39192
|
+
}
|
|
39193
|
+
} catch (err) {
|
|
39194
|
+
console.error(
|
|
39195
|
+
`[telegram-native] op=chat-action key=${key2} site=inbound ok=false reason=${err instanceof Error ? err.message : String(err)}`
|
|
39196
|
+
);
|
|
39197
|
+
}
|
|
38637
39198
|
if (!hadSubscriber && !this.spawning.has(key2)) {
|
|
38638
39199
|
this.spawning.add(key2);
|
|
38639
39200
|
console.error(`[telegram-native] op=spawn-trigger key=${key2} botId=${input.botId} role=${input.role}`);
|
|
38640
39201
|
noteSpawnAttempt2(input.botId);
|
|
39202
|
+
const spawnArgs = {
|
|
39203
|
+
accountId: input.accountId,
|
|
39204
|
+
accountDir: input.accountDir,
|
|
39205
|
+
botId: input.botId,
|
|
39206
|
+
botToken: input.botToken,
|
|
39207
|
+
agentSlug: input.agentSlug,
|
|
39208
|
+
channelKey: key2,
|
|
39209
|
+
senderId: input.senderId,
|
|
39210
|
+
role: input.role,
|
|
39211
|
+
personId: input.personId ?? null,
|
|
39212
|
+
chatId: input.chatId,
|
|
39213
|
+
gatewayUrl: this.deps.gatewayUrl,
|
|
39214
|
+
serverPath: this.deps.serverPath
|
|
39215
|
+
};
|
|
39216
|
+
this.heldSpawnArgs.set(key2, { args: spawnArgs, attempts: 0 });
|
|
38641
39217
|
try {
|
|
38642
|
-
await this.deps.ensureChannelSession(
|
|
38643
|
-
accountId: input.accountId,
|
|
38644
|
-
accountDir: input.accountDir,
|
|
38645
|
-
botId: input.botId,
|
|
38646
|
-
botToken: input.botToken,
|
|
38647
|
-
agentSlug: input.agentSlug,
|
|
38648
|
-
channelKey: key2,
|
|
38649
|
-
senderId: input.senderId,
|
|
38650
|
-
role: input.role,
|
|
38651
|
-
personId: input.personId ?? null,
|
|
38652
|
-
chatId: input.chatId,
|
|
38653
|
-
gatewayUrl: this.deps.gatewayUrl,
|
|
38654
|
-
serverPath: this.deps.serverPath
|
|
38655
|
-
});
|
|
39218
|
+
await this.deps.ensureChannelSession(spawnArgs);
|
|
38656
39219
|
} finally {
|
|
38657
39220
|
this.spawning.delete(key2);
|
|
38658
39221
|
if (this.readyWhileSpawning.delete(key2)) this.drain(key2);
|
|
@@ -38667,17 +39230,21 @@ var TelegramGateway = class {
|
|
|
38667
39230
|
liveReplyTarget(key2) {
|
|
38668
39231
|
return this.replies.get(key2) ?? null;
|
|
38669
39232
|
}
|
|
38670
|
-
/** Task 2559 — every
|
|
38671
|
-
*
|
|
38672
|
-
*
|
|
38673
|
-
*
|
|
38674
|
-
*
|
|
38675
|
-
*
|
|
38676
|
-
* offset, which would yield a door id nothing keys on.
|
|
39233
|
+
/** Task 2559 — every door this gateway currently holds a live delivery closure
|
|
39234
|
+
* for, as the `<botId>:<senderId>` channel key the follower registry keys on.
|
|
39235
|
+
* The twin of `WaGateway.boundTargets()`: the census reconciles against
|
|
39236
|
+
* exactly this set, so "should have a reader" and "can be sent to" are one
|
|
39237
|
+
* fact and cannot drift. A key `parseTelegramChannelKey` rejects is the
|
|
39238
|
+
* pre-2368 bare-sender shape, skipped rather than halved at an arbitrary
|
|
39239
|
+
* offset, which would yield a door id nothing keys on.
|
|
39240
|
+
*
|
|
39241
|
+
* Task 2541 — every role, not admin only. 2559 filtered public keys out of
|
|
39242
|
+
* this set because a public session was given no reader; it has one now, so
|
|
39243
|
+
* the filter would hide the door instead of the absence. That is what it did:
|
|
39244
|
+
* `bound=1 attached=1 missing=0` printed on every tick while a public
|
|
39245
|
+
* visitor's answer reached no chat at all. */
|
|
38677
39246
|
boundTargets() {
|
|
38678
|
-
return [...this.replies.keys()].filter(
|
|
38679
|
-
(key2) => this.adminKeys.has(key2) && parseTelegramChannelKey(key2) !== null
|
|
38680
|
-
);
|
|
39247
|
+
return [...this.replies.keys()].filter((key2) => parseTelegramChannelKey(key2) !== null);
|
|
38681
39248
|
}
|
|
38682
39249
|
async sendOutbound(key2, text) {
|
|
38683
39250
|
const reply = this.replies.get(key2);
|
|
@@ -38719,7 +39286,7 @@ function buildTelegramSpawnRequest(input) {
|
|
|
38719
39286
|
}
|
|
38720
39287
|
|
|
38721
39288
|
// server/telegram-ensure-session.ts
|
|
38722
|
-
var
|
|
39289
|
+
var TAG64 = "[telegram-inbound]";
|
|
38723
39290
|
function buildTelegramEnsureSession(deps) {
|
|
38724
39291
|
return async (input) => {
|
|
38725
39292
|
const req = buildTelegramSpawnRequest({
|
|
@@ -38733,6 +39300,18 @@ function buildTelegramEnsureSession(deps) {
|
|
|
38733
39300
|
// Task 1789 — admin rows only; see the WhatsApp twin.
|
|
38734
39301
|
overrideSessionId: input.role === "admin" ? deps.resolveSessionOverride(input.accountId, input.channelKey) : null
|
|
38735
39302
|
});
|
|
39303
|
+
const notifySend = async (text) => {
|
|
39304
|
+
const sent = await deps.sendText(input.botToken, Number(input.chatId), text);
|
|
39305
|
+
if (!sent.ok) throw new Error(sent.error ?? "send failed");
|
|
39306
|
+
};
|
|
39307
|
+
const notifyRefused = (result2) => sendRefusalNotice({
|
|
39308
|
+
channel: "telegram",
|
|
39309
|
+
doorId: req.channelKey,
|
|
39310
|
+
sender: req.channelKey,
|
|
39311
|
+
reason: result2.error,
|
|
39312
|
+
status: result2.status,
|
|
39313
|
+
send: notifySend
|
|
39314
|
+
});
|
|
38736
39315
|
if (input.role === "public") {
|
|
38737
39316
|
const result2 = await deps.managerRcSpawn({
|
|
38738
39317
|
senderId: req.channelKey,
|
|
@@ -38750,15 +39329,24 @@ function buildTelegramEnsureSession(deps) {
|
|
|
38750
39329
|
`[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=public agent=${input.agentSlug} error=${result2.error} status=${result2.status}`
|
|
38751
39330
|
);
|
|
38752
39331
|
noteSpawnFailed2(input.botId);
|
|
39332
|
+
await notifyRefused(result2);
|
|
38753
39333
|
return;
|
|
38754
39334
|
}
|
|
38755
39335
|
console.error(
|
|
38756
|
-
`${
|
|
39336
|
+
`${TAG64} op=telegram-spawn-door botId=${input.botId} role=public door=rc agent=${input.agentSlug}`
|
|
38757
39337
|
);
|
|
38758
39338
|
console.error(
|
|
38759
|
-
`${
|
|
39339
|
+
`${TAG64} op=spawn botId=${input.botId} sessionId=${result2.sessionId} targetAccountId=${input.accountId} role=public`
|
|
38760
39340
|
);
|
|
38761
39341
|
noteSpawnOk2(input.botId);
|
|
39342
|
+
clearRefusalNotice(req.channelKey);
|
|
39343
|
+
deps.followers.attach({
|
|
39344
|
+
sessionId: result2.sessionId,
|
|
39345
|
+
senderId: req.channelKey,
|
|
39346
|
+
accountId: input.accountId,
|
|
39347
|
+
chatId: input.chatId,
|
|
39348
|
+
botToken: input.botToken
|
|
39349
|
+
});
|
|
38762
39350
|
return;
|
|
38763
39351
|
}
|
|
38764
39352
|
const result = await deps.managerRcSpawn(req);
|
|
@@ -38767,12 +39355,14 @@ function buildTelegramEnsureSession(deps) {
|
|
|
38767
39355
|
`[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=admin error=${result.error} status=${result.status}`
|
|
38768
39356
|
);
|
|
38769
39357
|
noteSpawnFailed2(input.botId);
|
|
39358
|
+
await notifyRefused(result);
|
|
38770
39359
|
return;
|
|
38771
39360
|
}
|
|
38772
39361
|
console.error(
|
|
38773
|
-
`${
|
|
39362
|
+
`${TAG64} op=spawn botId=${input.botId} sessionId=${req.sessionId} targetAccountId=${input.accountId} role=admin`
|
|
38774
39363
|
);
|
|
38775
39364
|
noteSpawnOk2(input.botId);
|
|
39365
|
+
clearRefusalNotice(req.channelKey);
|
|
38776
39366
|
deps.followers.attach({
|
|
38777
39367
|
sessionId: req.sessionId,
|
|
38778
39368
|
senderId: req.channelKey,
|
|
@@ -38788,7 +39378,7 @@ init_attachments();
|
|
|
38788
39378
|
import { realpathSync as realpathSync10 } from "fs";
|
|
38789
39379
|
import { readFile as readFile14, stat as stat10 } from "fs/promises";
|
|
38790
39380
|
import { resolve as resolve48, basename as basename16 } from "path";
|
|
38791
|
-
var
|
|
39381
|
+
var TAG65 = "[telegram:outbound]";
|
|
38792
39382
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
38793
39383
|
async function sendTelegramDocument(input) {
|
|
38794
39384
|
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
|
|
@@ -38805,10 +39395,10 @@ async function sendTelegramDocument(input) {
|
|
|
38805
39395
|
} catch (err) {
|
|
38806
39396
|
const code = err.code;
|
|
38807
39397
|
if (code === "ENOENT") {
|
|
38808
|
-
console.error(`${
|
|
39398
|
+
console.error(`${TAG65} document ENOENT scope=account-dir accountDir=${accountDir} maxyAccountId=${maxyAccountId}`);
|
|
38809
39399
|
return { ok: false, status: 500, error: `Account directory not resolvable for ${maxyAccountId}` };
|
|
38810
39400
|
}
|
|
38811
|
-
console.error(`${
|
|
39401
|
+
console.error(`${TAG65} document path error scope=account-dir maxyAccountId=${maxyAccountId}: ${String(err)}`);
|
|
38812
39402
|
return { ok: false, status: 500, error: String(err) };
|
|
38813
39403
|
}
|
|
38814
39404
|
let resolvedPath;
|
|
@@ -38817,14 +39407,14 @@ async function sendTelegramDocument(input) {
|
|
|
38817
39407
|
} catch (err) {
|
|
38818
39408
|
const code = err.code;
|
|
38819
39409
|
if (code === "ENOENT") {
|
|
38820
|
-
console.error(`${
|
|
39410
|
+
console.error(`${TAG65} document ENOENT scope=file path=${filePath} maxyAccountId=${maxyAccountId}`);
|
|
38821
39411
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
38822
39412
|
}
|
|
38823
|
-
console.error(`${
|
|
39413
|
+
console.error(`${TAG65} document path error scope=file maxyAccountId=${maxyAccountId}: ${String(err)}`);
|
|
38824
39414
|
return { ok: false, status: 500, error: String(err) };
|
|
38825
39415
|
}
|
|
38826
39416
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
38827
|
-
console.error(`${
|
|
39417
|
+
console.error(`${TAG65} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
|
|
38828
39418
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
38829
39419
|
}
|
|
38830
39420
|
const fileStat = await stat10(resolvedPath);
|
|
@@ -38857,24 +39447,24 @@ async function sendTelegramDocument(input) {
|
|
|
38857
39447
|
error = e instanceof Error ? e.message : String(e);
|
|
38858
39448
|
}
|
|
38859
39449
|
console.error(
|
|
38860
|
-
`${
|
|
39450
|
+
`${TAG65} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok2}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
38861
39451
|
);
|
|
38862
39452
|
return ok2 ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
38863
39453
|
}
|
|
38864
39454
|
|
|
38865
39455
|
// app/lib/telegram/outbound/file-delivery.ts
|
|
38866
|
-
var
|
|
39456
|
+
var TAG66 = "[telegram:outbound]";
|
|
38867
39457
|
function platformRoot4() {
|
|
38868
39458
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
38869
39459
|
}
|
|
38870
39460
|
function makeTelegramSendFile(entry2, botToken) {
|
|
38871
39461
|
return async (filePath, caption) => {
|
|
38872
39462
|
if (!botToken) {
|
|
38873
|
-
console.error(`${
|
|
39463
|
+
console.error(`${TAG66} file-delivery reject reason=no-bot-token sender=${entry2.senderId} role=${entry2.role}`);
|
|
38874
39464
|
return { ok: false, error: "no-bot-token" };
|
|
38875
39465
|
}
|
|
38876
39466
|
if (entry2.replyTarget == null) {
|
|
38877
|
-
console.error(`${
|
|
39467
|
+
console.error(`${TAG66} file-delivery reject reason=no-reply-target sender=${entry2.senderId} role=${entry2.role}`);
|
|
38878
39468
|
return { ok: false, error: "no-reply-target" };
|
|
38879
39469
|
}
|
|
38880
39470
|
const result = await sendTelegramDocument({
|
|
@@ -38891,13 +39481,14 @@ function makeTelegramSendFile(entry2, botToken) {
|
|
|
38891
39481
|
function makeTelegramFileDelivery(entry2, botToken) {
|
|
38892
39482
|
return makeFileDelivery({
|
|
38893
39483
|
entry: entry2,
|
|
38894
|
-
tag:
|
|
39484
|
+
tag: TAG66,
|
|
38895
39485
|
channel: "telegram",
|
|
38896
39486
|
sendFile: makeTelegramSendFile(entry2, botToken)
|
|
38897
39487
|
});
|
|
38898
39488
|
}
|
|
38899
39489
|
|
|
38900
39490
|
// app/lib/telegram/gateway/native-file-follower.ts
|
|
39491
|
+
var CHAT_ACTION_REFRESH_MS = 4e3;
|
|
38901
39492
|
function startTelegramNativeFileFollower(input) {
|
|
38902
39493
|
const entry2 = {
|
|
38903
39494
|
sessionId: input.sessionId,
|
|
@@ -38917,6 +39508,16 @@ function startTelegramNativeFileFollower(input) {
|
|
|
38917
39508
|
// The chat id the document is addressed to (set per spawn from the inbound).
|
|
38918
39509
|
replyTarget: input.chatId
|
|
38919
39510
|
};
|
|
39511
|
+
let lastChatActionAt = Number.NEGATIVE_INFINITY;
|
|
39512
|
+
const onActivity = () => {
|
|
39513
|
+
const now = Date.now();
|
|
39514
|
+
if (now - lastChatActionAt < CHAT_ACTION_REFRESH_MS) return;
|
|
39515
|
+
lastChatActionAt = now;
|
|
39516
|
+
void sendTelegramChatAction(input.botToken, input.chatId, input.senderId, "activity");
|
|
39517
|
+
};
|
|
39518
|
+
const onTurnComplete = () => {
|
|
39519
|
+
lastChatActionAt = Number.NEGATIVE_INFINITY;
|
|
39520
|
+
};
|
|
38920
39521
|
return startEmitter({
|
|
38921
39522
|
entry: entry2,
|
|
38922
39523
|
tag: "[telegram-adaptor]",
|
|
@@ -38930,6 +39531,8 @@ function startTelegramNativeFileFollower(input) {
|
|
|
38930
39531
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
38931
39532
|
// suppress replay so historical files are not re-sent on attach.
|
|
38932
39533
|
suppressResumeReplay: true,
|
|
39534
|
+
onActivity,
|
|
39535
|
+
onTurnComplete,
|
|
38933
39536
|
// Task 2521 — the answer reaches every bound door through the one fan-out.
|
|
38934
39537
|
// Ungated by design: whatever woke the turn, the reader and the chats show
|
|
38935
39538
|
// the same thing.
|
|
@@ -38968,7 +39571,7 @@ init_webchat();
|
|
|
38968
39571
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
38969
39572
|
init_src3();
|
|
38970
39573
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
38971
|
-
var
|
|
39574
|
+
var TAG67 = "[public-session-review]";
|
|
38972
39575
|
var CONSUMED_CAP = 500;
|
|
38973
39576
|
var consumed = /* @__PURE__ */ new Set();
|
|
38974
39577
|
function consumeOnce(sessionId) {
|
|
@@ -38995,7 +39598,7 @@ async function fetchJsonl(sessionId) {
|
|
|
38995
39598
|
return await res.text();
|
|
38996
39599
|
} catch (err) {
|
|
38997
39600
|
console.error(
|
|
38998
|
-
`${
|
|
39601
|
+
`${TAG67} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
38999
39602
|
);
|
|
39000
39603
|
return null;
|
|
39001
39604
|
}
|
|
@@ -39019,7 +39622,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
39019
39622
|
const res = await fetch(url);
|
|
39020
39623
|
if (!res.ok) {
|
|
39021
39624
|
console.error(
|
|
39022
|
-
`${
|
|
39625
|
+
`${TAG67} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
39023
39626
|
);
|
|
39024
39627
|
return [];
|
|
39025
39628
|
}
|
|
@@ -39027,7 +39630,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
39027
39630
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
39028
39631
|
} catch (err) {
|
|
39029
39632
|
console.error(
|
|
39030
|
-
`${
|
|
39633
|
+
`${TAG67} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
39031
39634
|
);
|
|
39032
39635
|
return [];
|
|
39033
39636
|
}
|
|
@@ -39056,7 +39659,7 @@ function composeInitialMessage(input) {
|
|
|
39056
39659
|
}
|
|
39057
39660
|
async function dispatchReviewer(input, initialMessage) {
|
|
39058
39661
|
const sessionId = randomUUID14();
|
|
39059
|
-
console.log(`${
|
|
39662
|
+
console.log(`${TAG67} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
39060
39663
|
const spawned = await managerRcSpawn({
|
|
39061
39664
|
sessionId,
|
|
39062
39665
|
initialMessage,
|
|
@@ -39080,21 +39683,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
39080
39683
|
const dispatchedAt = Date.now();
|
|
39081
39684
|
if (consumed.has(input.sessionId)) {
|
|
39082
39685
|
console.log(
|
|
39083
|
-
`${
|
|
39686
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
39084
39687
|
);
|
|
39085
39688
|
return;
|
|
39086
39689
|
}
|
|
39087
39690
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
39088
39691
|
if (!jsonl) {
|
|
39089
39692
|
console.log(
|
|
39090
|
-
`${
|
|
39693
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
39091
39694
|
);
|
|
39092
39695
|
return;
|
|
39093
39696
|
}
|
|
39094
39697
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
39095
39698
|
if (operatorTurns === 0) {
|
|
39096
39699
|
console.log(
|
|
39097
|
-
`${
|
|
39700
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
39098
39701
|
);
|
|
39099
39702
|
return;
|
|
39100
39703
|
}
|
|
@@ -39108,19 +39711,19 @@ async function firePublicSessionEndReview(input) {
|
|
|
39108
39711
|
});
|
|
39109
39712
|
if (!consumeOnce(input.sessionId)) {
|
|
39110
39713
|
console.log(
|
|
39111
|
-
`${
|
|
39714
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
39112
39715
|
);
|
|
39113
39716
|
return;
|
|
39114
39717
|
}
|
|
39115
39718
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
39116
39719
|
if (!dispatched.ok) {
|
|
39117
39720
|
console.error(
|
|
39118
|
-
`${
|
|
39721
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
39119
39722
|
);
|
|
39120
39723
|
return;
|
|
39121
39724
|
}
|
|
39122
39725
|
console.log(
|
|
39123
|
-
`${
|
|
39726
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
39124
39727
|
);
|
|
39125
39728
|
}
|
|
39126
39729
|
|
|
@@ -39272,7 +39875,7 @@ init_account();
|
|
|
39272
39875
|
|
|
39273
39876
|
// server/telegram-audit.ts
|
|
39274
39877
|
import { statSync as statSync28 } from "fs";
|
|
39275
|
-
import { join as
|
|
39878
|
+
import { join as join63 } from "path";
|
|
39276
39879
|
init_channel_key();
|
|
39277
39880
|
init_sidebar_sessions();
|
|
39278
39881
|
function buildTelegramAuditLines(input) {
|
|
@@ -39338,7 +39941,7 @@ function countTelegramSessionsForAccount(accountId) {
|
|
|
39338
39941
|
function* walkTelegramSidecars() {
|
|
39339
39942
|
const cfg = claudeConfigDir();
|
|
39340
39943
|
if (!cfg) return;
|
|
39341
|
-
const projectsRoot =
|
|
39944
|
+
const projectsRoot = join63(cfg, "projects");
|
|
39342
39945
|
for (const { path } of enumerateSessionSidecars(projectsRoot)) {
|
|
39343
39946
|
const meta = readSidecarMeta(path);
|
|
39344
39947
|
if (meta.channel !== "telegram" || !meta.accountId) continue;
|
|
@@ -39363,16 +39966,25 @@ function* walkTelegramSidecars() {
|
|
|
39363
39966
|
}
|
|
39364
39967
|
}
|
|
39365
39968
|
|
|
39969
|
+
// app/lib/telegram/gateway/origin-target.ts
|
|
39970
|
+
function answeringOriginTarget(send, key2, noteAnswered3) {
|
|
39971
|
+
if (!send) return null;
|
|
39972
|
+
return async (text) => {
|
|
39973
|
+
await send(text);
|
|
39974
|
+
noteAnswered3(key2);
|
|
39975
|
+
};
|
|
39976
|
+
}
|
|
39977
|
+
|
|
39366
39978
|
// server/telegram-migrate.ts
|
|
39367
|
-
import { readFileSync as
|
|
39368
|
-
import { dirname as dirname18, join as
|
|
39979
|
+
import { readFileSync as readFileSync54, writeFileSync as writeFileSync19, existsSync as existsSync53, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
|
|
39980
|
+
import { dirname as dirname18, join as join64 } from "path";
|
|
39369
39981
|
function migrateTelegramTwoSlot(input) {
|
|
39370
39982
|
const out = [];
|
|
39371
39983
|
for (const account of input.accounts) {
|
|
39372
|
-
const configPath3 =
|
|
39984
|
+
const configPath3 = join64(account.accountDir, "account.json");
|
|
39373
39985
|
let config;
|
|
39374
39986
|
try {
|
|
39375
|
-
config = JSON.parse(
|
|
39987
|
+
config = JSON.parse(readFileSync54(configPath3, "utf-8"));
|
|
39376
39988
|
} catch {
|
|
39377
39989
|
console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
|
|
39378
39990
|
continue;
|
|
@@ -39474,14 +40086,14 @@ function backfillMisfiledRecords(params) {
|
|
|
39474
40086
|
init_message_store();
|
|
39475
40087
|
|
|
39476
40088
|
// server/lib/booking-site-accounts.ts
|
|
39477
|
-
import { existsSync as existsSync54, readFileSync as
|
|
40089
|
+
import { existsSync as existsSync54, readFileSync as readFileSync55 } from "fs";
|
|
39478
40090
|
import { resolve as resolve49 } from "path";
|
|
39479
40091
|
function listBookingSiteAccounts(accounts) {
|
|
39480
40092
|
return accounts.filter((a) => {
|
|
39481
40093
|
const availPath = resolve49(a.accountDir, "calendar-availability.json");
|
|
39482
40094
|
if (!existsSync54(availPath)) return false;
|
|
39483
40095
|
try {
|
|
39484
|
-
const cfg = JSON.parse(
|
|
40096
|
+
const cfg = JSON.parse(readFileSync55(availPath, "utf-8"));
|
|
39485
40097
|
return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
39486
40098
|
} catch {
|
|
39487
40099
|
return false;
|
|
@@ -39526,7 +40138,7 @@ init_paths();
|
|
|
39526
40138
|
|
|
39527
40139
|
// ../lib/entitlement/src/index.ts
|
|
39528
40140
|
import { createPublicKey, createHash as createHash7, verify as cryptoVerify } from "crypto";
|
|
39529
|
-
import { existsSync as existsSync55, readFileSync as
|
|
40141
|
+
import { existsSync as existsSync55, readFileSync as readFileSync56, statSync as statSync29 } from "fs";
|
|
39530
40142
|
import { resolve as resolve50 } from "path";
|
|
39531
40143
|
|
|
39532
40144
|
// ../lib/entitlement/src/canonicalize.ts
|
|
@@ -39590,7 +40202,7 @@ function resolveEntitlement(brand, account) {
|
|
|
39590
40202
|
function verifyAndResolve(brand, entitlementPath, account) {
|
|
39591
40203
|
let pubkeyPem;
|
|
39592
40204
|
try {
|
|
39593
|
-
pubkeyPem =
|
|
40205
|
+
pubkeyPem = readFileSync56(pubkeyPath(brand), "utf-8");
|
|
39594
40206
|
} catch (err) {
|
|
39595
40207
|
return logResolved(anonymousFallback("pubkey-missing"), {
|
|
39596
40208
|
reason: "pubkey-missing"
|
|
@@ -39604,7 +40216,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
|
|
|
39604
40216
|
}
|
|
39605
40217
|
let envelope;
|
|
39606
40218
|
try {
|
|
39607
|
-
envelope = JSON.parse(
|
|
40219
|
+
envelope = JSON.parse(readFileSync56(entitlementPath, "utf-8"));
|
|
39608
40220
|
} catch {
|
|
39609
40221
|
return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
|
|
39610
40222
|
}
|
|
@@ -39766,14 +40378,14 @@ function clientFrom(c) {
|
|
|
39766
40378
|
}
|
|
39767
40379
|
installMediaDownloadGuard();
|
|
39768
40380
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
39769
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT6 ?
|
|
40381
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT6 ? join65(PLATFORM_ROOT6, "config", "brand.json") : "";
|
|
39770
40382
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
39771
40383
|
if (BRAND_JSON_PATH && !existsSync56(BRAND_JSON_PATH)) {
|
|
39772
40384
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
39773
40385
|
}
|
|
39774
40386
|
if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
|
|
39775
40387
|
try {
|
|
39776
|
-
const parsed = JSON.parse(
|
|
40388
|
+
const parsed = JSON.parse(readFileSync57(BRAND_JSON_PATH, "utf-8"));
|
|
39777
40389
|
BRAND = { ...BRAND, ...parsed };
|
|
39778
40390
|
} catch (err) {
|
|
39779
40391
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -39805,11 +40417,11 @@ var brandLoginOpts = {
|
|
|
39805
40417
|
appleTouchIconPath: brandAppIcon512Path,
|
|
39806
40418
|
themeColor: BRAND.defaultColors?.primary
|
|
39807
40419
|
};
|
|
39808
|
-
var ALIAS_DOMAINS_PATH =
|
|
40420
|
+
var ALIAS_DOMAINS_PATH = join65(homedir6(), BRAND.configDir, "alias-domains.json");
|
|
39809
40421
|
function loadAliasDomains() {
|
|
39810
40422
|
try {
|
|
39811
40423
|
if (!existsSync56(ALIAS_DOMAINS_PATH)) return null;
|
|
39812
|
-
const parsed = JSON.parse(
|
|
40424
|
+
const parsed = JSON.parse(readFileSync57(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
39813
40425
|
if (!Array.isArray(parsed)) {
|
|
39814
40426
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
39815
40427
|
return null;
|
|
@@ -39863,7 +40475,12 @@ var nativeFileFollowers = createFollowerRegistry(
|
|
|
39863
40475
|
// delivered session 8e5bc4e8's answer to nothing.
|
|
39864
40476
|
fanTargets: () => whatsappFanTargets(p.effectiveAccountId, p.senderId),
|
|
39865
40477
|
onClose
|
|
39866
|
-
})
|
|
40478
|
+
}),
|
|
40479
|
+
// Task 2569 — the door is (effective account, sender), the same pair the
|
|
40480
|
+
// reply map and the session id key on. Keyed on the sender alone, a second
|
|
40481
|
+
// account's spawn for the same phone aborted the first account's live reader
|
|
40482
|
+
// and overwrote its recorded parameters, and the census called that healthy.
|
|
40483
|
+
whatsappDoorId
|
|
39867
40484
|
);
|
|
39868
40485
|
var webchatFileFollowers = /* @__PURE__ */ new Map();
|
|
39869
40486
|
async function fetchAccountStandingRules(accountId) {
|
|
@@ -39945,7 +40562,7 @@ var waGateway = new WaGateway({
|
|
|
39945
40562
|
// resolver and `fanTargetsFor` is a constructor dep, not a callable method.
|
|
39946
40563
|
fanTargetsFor: whatsappFanTargets,
|
|
39947
40564
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
39948
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve51(process.env.MAXY_PLATFORM_ROOT ??
|
|
40565
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve51(process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
39949
40566
|
// Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
|
|
39950
40567
|
// path-validation scope) is the sender's effective SESSION account, resolved
|
|
39951
40568
|
// once at the inbound gate and threaded here via the gateway's per-sender doc
|
|
@@ -39960,7 +40577,7 @@ var waGateway = new WaGateway({
|
|
|
39960
40577
|
caption,
|
|
39961
40578
|
accountId,
|
|
39962
40579
|
maxyAccountId,
|
|
39963
|
-
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ??
|
|
40580
|
+
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, ".."))
|
|
39964
40581
|
});
|
|
39965
40582
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
39966
40583
|
},
|
|
@@ -40050,11 +40667,20 @@ var waGateway = new WaGateway({
|
|
|
40050
40667
|
`[whatsapp:public-agent] op=spawn-failed inboundId=${inboundId} senderId=${senderId} slug=${publicAgentSlug} error=${publicResult.error}`
|
|
40051
40668
|
);
|
|
40052
40669
|
noteSpawnFailed();
|
|
40670
|
+
await sendRefusalNotice({
|
|
40671
|
+
channel: "whatsapp",
|
|
40672
|
+
doorId: whatsappDoorId({ effectiveAccountId, senderId }),
|
|
40673
|
+
sender: senderId,
|
|
40674
|
+
reason: publicResult.error,
|
|
40675
|
+
status: publicResult.status,
|
|
40676
|
+
send: waGateway.liveReplyTarget(effectiveAccountId, senderId)
|
|
40677
|
+
});
|
|
40053
40678
|
} else {
|
|
40054
40679
|
console.error(
|
|
40055
40680
|
`[whatsapp:public-agent] op=spawn-ok inboundId=${inboundId} senderId=${senderId} slug=${publicAgentSlug} ms=${Date.now() - publicSpawnStartedAt}`
|
|
40056
40681
|
);
|
|
40057
40682
|
noteSpawnOk();
|
|
40683
|
+
clearRefusalNotice(whatsappDoorId({ effectiveAccountId, senderId }));
|
|
40058
40684
|
}
|
|
40059
40685
|
return;
|
|
40060
40686
|
}
|
|
@@ -40093,8 +40719,17 @@ var waGateway = new WaGateway({
|
|
|
40093
40719
|
const result = await managerRcSpawn({ ...req, userId, party });
|
|
40094
40720
|
if ("error" in result) {
|
|
40095
40721
|
console.error(`[whatsapp-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
|
|
40722
|
+
await sendRefusalNotice({
|
|
40723
|
+
channel: "whatsapp",
|
|
40724
|
+
doorId: whatsappDoorId({ effectiveAccountId, senderId }),
|
|
40725
|
+
sender: senderId,
|
|
40726
|
+
reason: result.error,
|
|
40727
|
+
status: result.status,
|
|
40728
|
+
send: waGateway.liveReplyTarget(effectiveAccountId, senderId)
|
|
40729
|
+
});
|
|
40096
40730
|
return;
|
|
40097
40731
|
}
|
|
40732
|
+
clearRefusalNotice(whatsappDoorId({ effectiveAccountId, senderId }));
|
|
40098
40733
|
nativeFileFollowers.attach({
|
|
40099
40734
|
sessionId: req.sessionId,
|
|
40100
40735
|
senderId,
|
|
@@ -40117,7 +40752,7 @@ function runDuplicateSenderAudit() {
|
|
|
40117
40752
|
try {
|
|
40118
40753
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
40119
40754
|
if (!configDir) return;
|
|
40120
|
-
const projectsRoot =
|
|
40755
|
+
const projectsRoot = join65(configDir, "projects");
|
|
40121
40756
|
const records2 = [];
|
|
40122
40757
|
for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
|
|
40123
40758
|
if (isSubagent || archived) continue;
|
|
@@ -40169,7 +40804,7 @@ var webchatGateway = new WebchatGateway({
|
|
|
40169
40804
|
// rather than hanging the POST.
|
|
40170
40805
|
publicProjectsRoot: () => {
|
|
40171
40806
|
const cfg = claudeConfigDir();
|
|
40172
|
-
return cfg ?
|
|
40807
|
+
return cfg ? join65(cfg, "projects") : "";
|
|
40173
40808
|
},
|
|
40174
40809
|
gatewayUrl: webchatGatewayUrl(),
|
|
40175
40810
|
serverPath: webchatServerPath(),
|
|
@@ -40311,6 +40946,12 @@ var telegramGateway = new TelegramGateway({
|
|
|
40311
40946
|
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
40312
40947
|
// Task 2521 — hoisted above; see `telegramFanTargets`.
|
|
40313
40948
|
fanTargetsFor: telegramFanTargets,
|
|
40949
|
+
// Task 2576 — the typing indicator. Injected here because the gateway is
|
|
40950
|
+
// contracted to hold no Bot API import; see the dep comment in
|
|
40951
|
+
// telegram-gateway.ts. Not awaited: an indicator never paces a turn.
|
|
40952
|
+
sendChatAction: ({ botToken, chatId, key: key2 }) => {
|
|
40953
|
+
void sendTelegramChatAction(botToken, chatId, key2, "inbound");
|
|
40954
|
+
},
|
|
40314
40955
|
gatewayUrl: telegramGatewayUrl(),
|
|
40315
40956
|
serverPath: telegramServerPath(),
|
|
40316
40957
|
// Task 2368 — the role branch lives in its own module so it has a test that
|
|
@@ -40320,6 +40961,8 @@ var telegramGateway = new TelegramGateway({
|
|
|
40320
40961
|
managerRcSpawn,
|
|
40321
40962
|
managerSpawn,
|
|
40322
40963
|
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
40964
|
+
// Task 2573 — the send the refusal notice goes out on.
|
|
40965
|
+
sendText: sendTelegramText,
|
|
40323
40966
|
followers: telegramFileFollowers
|
|
40324
40967
|
})
|
|
40325
40968
|
});
|
|
@@ -40334,10 +40977,16 @@ registerLoop({
|
|
|
40334
40977
|
followerCensusTick(
|
|
40335
40978
|
{
|
|
40336
40979
|
channel: "whatsapp",
|
|
40337
|
-
|
|
40980
|
+
// Task 2569 — the whole hub key, not its sender half. `boundTargets()`
|
|
40981
|
+
// decodes the reply-map keys and this re-encodes them through the same
|
|
40982
|
+
// helper pair, so the census and the gateway compare one fact. Halving
|
|
40983
|
+
// it here is what let one phone's two accounts collapse to one door.
|
|
40984
|
+
bound: () => waGateway.boundTargets().map((t) => hubKey(t.accountId, t.senderId)),
|
|
40338
40985
|
live: () => nativeFileFollowers.live(),
|
|
40339
|
-
paramsFor: (
|
|
40340
|
-
attach: (p) => nativeFileFollowers.attach(p)
|
|
40986
|
+
paramsFor: (doorId) => nativeFileFollowers.paramsFor(doorId),
|
|
40987
|
+
attach: (p) => nativeFileFollowers.attach(p),
|
|
40988
|
+
// The hub key carries a NUL, which must never reach server.log.
|
|
40989
|
+
label: whatsappDoorLabel
|
|
40341
40990
|
},
|
|
40342
40991
|
followerCensusState.whatsapp
|
|
40343
40992
|
),
|
|
@@ -40346,7 +40995,7 @@ registerLoop({
|
|
|
40346
40995
|
channel: "telegram",
|
|
40347
40996
|
bound: () => telegramGateway.boundTargets(),
|
|
40348
40997
|
live: () => telegramFileFollowers.live(),
|
|
40349
|
-
paramsFor: (
|
|
40998
|
+
paramsFor: (doorId) => telegramFileFollowers.paramsFor(doorId),
|
|
40350
40999
|
attach: (p) => telegramFileFollowers.attach(p)
|
|
40351
41000
|
},
|
|
40352
41001
|
followerCensusState.telegram
|
|
@@ -40799,7 +41448,7 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
40799
41448
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
40800
41449
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
40801
41450
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
40802
|
-
const body =
|
|
41451
|
+
const body = readFileSync57(filePath);
|
|
40803
41452
|
return c.body(body, 200, {
|
|
40804
41453
|
"Content-Type": contentType,
|
|
40805
41454
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -40829,7 +41478,7 @@ app80.get("/generated/:filename", (c) => {
|
|
|
40829
41478
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
40830
41479
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
40831
41480
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
40832
|
-
const body =
|
|
41481
|
+
const body = readFileSync57(filePath);
|
|
40833
41482
|
return c.body(body, 200, {
|
|
40834
41483
|
"Content-Type": contentType,
|
|
40835
41484
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -40844,7 +41493,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
|
40844
41493
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
40845
41494
|
if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
|
|
40846
41495
|
try {
|
|
40847
|
-
const fullBrand = JSON.parse(
|
|
41496
|
+
const fullBrand = JSON.parse(readFileSync57(BRAND_JSON_PATH, "utf-8"));
|
|
40848
41497
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
40849
41498
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
40850
41499
|
} catch {
|
|
@@ -40875,7 +41524,7 @@ var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskabl
|
|
|
40875
41524
|
);
|
|
40876
41525
|
var SW_SOURCE = (() => {
|
|
40877
41526
|
try {
|
|
40878
|
-
return
|
|
41527
|
+
return readFileSync57(resolve51(process.cwd(), "public", "sw.js"), "utf-8");
|
|
40879
41528
|
} catch {
|
|
40880
41529
|
return null;
|
|
40881
41530
|
}
|
|
@@ -40883,9 +41532,9 @@ var SW_SOURCE = (() => {
|
|
|
40883
41532
|
function readInstalledVersion() {
|
|
40884
41533
|
try {
|
|
40885
41534
|
if (!PLATFORM_ROOT6) return "unknown";
|
|
40886
|
-
const versionFile =
|
|
41535
|
+
const versionFile = join65(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
|
|
40887
41536
|
if (!existsSync56(versionFile)) return "unknown";
|
|
40888
|
-
const content =
|
|
41537
|
+
const content = readFileSync57(versionFile, "utf-8").trim();
|
|
40889
41538
|
return content || "unknown";
|
|
40890
41539
|
} catch {
|
|
40891
41540
|
return "unknown";
|
|
@@ -40896,7 +41545,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
|
|
|
40896
41545
|
function cachedHtml(file) {
|
|
40897
41546
|
let html = htmlCache.get(file);
|
|
40898
41547
|
if (!html) {
|
|
40899
|
-
html =
|
|
41548
|
+
html = readFileSync57(resolve51(process.cwd(), "public", file), "utf-8");
|
|
40900
41549
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
40901
41550
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
40902
41551
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -40913,14 +41562,14 @@ ${clientErrorReporterScript}
|
|
|
40913
41562
|
return html;
|
|
40914
41563
|
}
|
|
40915
41564
|
function loadBrandingCache(agentSlug) {
|
|
40916
|
-
const configDir =
|
|
41565
|
+
const configDir = join65(homedir6(), BRAND.configDir);
|
|
40917
41566
|
try {
|
|
40918
41567
|
const verdict = resolvePublicAddressFromDisk(agentSlug);
|
|
40919
41568
|
const accountId = verdict.kind === "served" ? verdict.owner.accountId : getDefaultAccountId();
|
|
40920
41569
|
if (!accountId) return null;
|
|
40921
|
-
const cachePath =
|
|
41570
|
+
const cachePath = join65(configDir, "branding-cache", accountId, `${agentSlug}.json`);
|
|
40922
41571
|
if (!existsSync56(cachePath)) return null;
|
|
40923
|
-
return JSON.parse(
|
|
41572
|
+
return JSON.parse(readFileSync57(cachePath, "utf-8"));
|
|
40924
41573
|
} catch {
|
|
40925
41574
|
return null;
|
|
40926
41575
|
}
|
|
@@ -41015,7 +41664,7 @@ app80.use("/vnc-popout.html", logViewerFetch);
|
|
|
41015
41664
|
app80.get("/vnc-popout.html", (c) => {
|
|
41016
41665
|
let html = htmlCache.get("vnc-popout.html");
|
|
41017
41666
|
if (!html) {
|
|
41018
|
-
html =
|
|
41667
|
+
html = readFileSync57(resolve51(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
41019
41668
|
const name = escapeHtml(BRAND.productName);
|
|
41020
41669
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
41021
41670
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -41169,7 +41818,7 @@ var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
|
41169
41818
|
var httpServer = serve({ fetch: app80.fetch, port, hostname });
|
|
41170
41819
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
41171
41820
|
{
|
|
41172
|
-
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41821
|
+
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41173
41822
|
const reconcileScript = resolve51(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
|
|
41174
41823
|
const runReconcile = (ctx) => {
|
|
41175
41824
|
if (!existsSync56(reconcileScript)) return;
|
|
@@ -41192,7 +41841,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41192
41841
|
detached: true,
|
|
41193
41842
|
run: runReconcile
|
|
41194
41843
|
});
|
|
41195
|
-
const reconcileStatePath =
|
|
41844
|
+
const reconcileStatePath = join65(MAXY_DIR, "booking-reconcile-state.json");
|
|
41196
41845
|
registerLoop({
|
|
41197
41846
|
name: "calendar-reconcile-liveness",
|
|
41198
41847
|
intervalMs: 6e5,
|
|
@@ -41210,7 +41859,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41210
41859
|
}
|
|
41211
41860
|
{
|
|
41212
41861
|
const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
|
|
41213
|
-
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41862
|
+
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41214
41863
|
const dreamSweepScript = resolve51(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
|
|
41215
41864
|
registerLoop({
|
|
41216
41865
|
name: "dream-cycle-sweep",
|
|
@@ -41234,7 +41883,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41234
41883
|
});
|
|
41235
41884
|
}
|
|
41236
41885
|
{
|
|
41237
|
-
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41886
|
+
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41238
41887
|
const outlookScript = resolve51(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
|
|
41239
41888
|
const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
|
|
41240
41889
|
const runOutlookComplete = (ctx) => {
|
|
@@ -41260,7 +41909,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41260
41909
|
});
|
|
41261
41910
|
}
|
|
41262
41911
|
{
|
|
41263
|
-
const auditRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41912
|
+
const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41264
41913
|
const strandedAccountsDir = resolve51(auditRoot, "..", "data/accounts");
|
|
41265
41914
|
const STRANDED_AUDIT_INTERVAL_MS = 3e5;
|
|
41266
41915
|
const STRANDED_AGE_MS = 16 * 6e4;
|
|
@@ -41269,7 +41918,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41269
41918
|
try {
|
|
41270
41919
|
if (!existsSync56(strandedAccountsDir)) return;
|
|
41271
41920
|
const now = Date.now();
|
|
41272
|
-
for (const name of
|
|
41921
|
+
for (const name of readdirSync33(strandedAccountsDir)) {
|
|
41273
41922
|
if (!STRANDED_UUID_RE.test(name)) continue;
|
|
41274
41923
|
const pendingPath2 = resolve51(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
|
|
41275
41924
|
if (!existsSync56(pendingPath2)) continue;
|
|
@@ -41290,7 +41939,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41290
41939
|
});
|
|
41291
41940
|
}
|
|
41292
41941
|
{
|
|
41293
|
-
const googleRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41942
|
+
const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41294
41943
|
const googleAccountsDir = resolve51(googleRoot, "..", "data/accounts");
|
|
41295
41944
|
const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
|
|
41296
41945
|
const runGooglePendingAuditSafe = () => {
|
|
@@ -41334,7 +41983,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41334
41983
|
});
|
|
41335
41984
|
}
|
|
41336
41985
|
{
|
|
41337
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41986
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41338
41987
|
const STORAGE_AUDIT_INTERVAL_MS = 3e5;
|
|
41339
41988
|
const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
|
|
41340
41989
|
console.error(`[storage-audit] error="${err.message}"`);
|
|
@@ -41370,7 +42019,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41370
42019
|
accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
|
|
41371
42020
|
secretPathFor: secretPath,
|
|
41372
42021
|
legacySecretPathFor: (botType, accountId) => resolve51(
|
|
41373
|
-
|
|
42022
|
+
homedir6(),
|
|
41374
42023
|
configDirName2(),
|
|
41375
42024
|
`${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
|
|
41376
42025
|
),
|
|
@@ -41393,7 +42042,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41393
42042
|
const lastByBot = newestTelegramSessionByBot();
|
|
41394
42043
|
let secretFilesOnDisk = [];
|
|
41395
42044
|
try {
|
|
41396
|
-
secretFilesOnDisk =
|
|
42045
|
+
secretFilesOnDisk = readdirSync33(telegramSecretsDir());
|
|
41397
42046
|
} catch {
|
|
41398
42047
|
}
|
|
41399
42048
|
const lines = buildTelegramAuditLines({
|
|
@@ -41536,7 +42185,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41536
42185
|
});
|
|
41537
42186
|
}
|
|
41538
42187
|
{
|
|
41539
|
-
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
42188
|
+
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41540
42189
|
const publishScript = resolve51(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
|
|
41541
42190
|
const PUBLISH_INTERVAL_MS = 3e5;
|
|
41542
42191
|
const bookingAccounts = () => {
|
|
@@ -41571,7 +42220,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41571
42220
|
let hasBookingSite = false;
|
|
41572
42221
|
try {
|
|
41573
42222
|
if (existsSync56(availPath)) {
|
|
41574
|
-
const cfg = JSON.parse(
|
|
42223
|
+
const cfg = JSON.parse(readFileSync57(availPath, "utf-8"));
|
|
41575
42224
|
hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
41576
42225
|
}
|
|
41577
42226
|
} catch {
|
|
@@ -41581,7 +42230,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41581
42230
|
let lastSuccessAt = null;
|
|
41582
42231
|
if (existsSync56(statePath)) {
|
|
41583
42232
|
try {
|
|
41584
|
-
const rec = JSON.parse(
|
|
42233
|
+
const rec = JSON.parse(readFileSync57(statePath, "utf-8"));
|
|
41585
42234
|
lastSuccessAt = rec.lastSuccessAt ?? null;
|
|
41586
42235
|
} catch {
|
|
41587
42236
|
console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
|
|
@@ -41616,7 +42265,7 @@ startDispatchCensus(() => getSession());
|
|
|
41616
42265
|
startDispatchFieldCensus(() => getSession());
|
|
41617
42266
|
startDispatchTgCensus(() => getSession());
|
|
41618
42267
|
{
|
|
41619
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
42268
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41620
42269
|
const auditScript = resolve51(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
|
|
41621
42270
|
const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
|
|
41622
42271
|
const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
|
|
@@ -41747,7 +42396,7 @@ async function runAdminUserReconcileTick() {
|
|
|
41747
42396
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
41748
42397
|
return;
|
|
41749
42398
|
}
|
|
41750
|
-
const usersRaw =
|
|
42399
|
+
const usersRaw = readFileSync57(USERS_FILE, "utf-8").trim();
|
|
41751
42400
|
if (!usersRaw) {
|
|
41752
42401
|
console.error("[adminuser-self-heal] skip reason=empty-users-file");
|
|
41753
42402
|
return;
|
|
@@ -41829,8 +42478,8 @@ var runIngestAuditTick = () => {
|
|
|
41829
42478
|
},
|
|
41830
42479
|
agentExists: (accountId, slug) => {
|
|
41831
42480
|
if (!house) return false;
|
|
41832
|
-
const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir :
|
|
41833
|
-
return existsSync56(
|
|
42481
|
+
const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir : join65(ACCOUNTS_DIR, accountId);
|
|
42482
|
+
return existsSync56(join65(dir, "agents", slug, "config.json"));
|
|
41834
42483
|
}
|
|
41835
42484
|
});
|
|
41836
42485
|
} catch (err) {
|
|
@@ -41878,9 +42527,9 @@ registerLoop({
|
|
|
41878
42527
|
runPublicAddressCollisionAudit({
|
|
41879
42528
|
houseAccountId: () => resolveAccount()?.accountId ?? null,
|
|
41880
42529
|
activeSlugs: () => listValidAccounts().map((a) => {
|
|
41881
|
-
const agentsRoot =
|
|
42530
|
+
const agentsRoot = join65(a.accountDir, "agents");
|
|
41882
42531
|
if (!existsSync56(agentsRoot)) return { accountId: a.accountId, slugs: [] };
|
|
41883
|
-
const slugs =
|
|
42532
|
+
const slugs = readdirSync33(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((slug) => isActiveAgentSlug(a.accountDir, slug));
|
|
41884
42533
|
return { accountId: a.accountId, slugs };
|
|
41885
42534
|
})
|
|
41886
42535
|
});
|
|
@@ -41894,7 +42543,7 @@ registerLoop({
|
|
|
41894
42543
|
var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
41895
42544
|
function countUsersRows() {
|
|
41896
42545
|
if (!existsSync56(USERS_FILE)) return 0;
|
|
41897
|
-
const raw =
|
|
42546
|
+
const raw = readFileSync57(USERS_FILE, "utf-8").trim();
|
|
41898
42547
|
if (!raw) return 0;
|
|
41899
42548
|
const users = JSON.parse(raw);
|
|
41900
42549
|
return users.filter((u) => typeof u.userId === "string").length;
|
|
@@ -41925,6 +42574,7 @@ registerLoop({
|
|
|
41925
42574
|
});
|
|
41926
42575
|
startGraphHealthTimer();
|
|
41927
42576
|
startSharedFolderCensus();
|
|
42577
|
+
startCredRefreshCensus();
|
|
41928
42578
|
void migrateUploads().catch((err) => console.error(`[uploads-migrate] failed err="${err instanceof Error ? err.message : String(err)}"`)).finally(() => {
|
|
41929
42579
|
void startFileWatcher().catch((err) => {
|
|
41930
42580
|
console.error(`[file-watcher] start-failed err="${err instanceof Error ? err.message : String(err)}"`);
|
|
@@ -41979,7 +42629,7 @@ function whatsappSender(accountId, key2) {
|
|
|
41979
42629
|
};
|
|
41980
42630
|
}
|
|
41981
42631
|
function telegramOriginTarget(key2) {
|
|
41982
|
-
return telegramGateway.liveReplyTarget(key2);
|
|
42632
|
+
return answeringOriginTarget(telegramGateway.liveReplyTarget(key2), key2, noteAnswered);
|
|
41983
42633
|
}
|
|
41984
42634
|
var configDirForWhatsApp = basename17(MAXY_DIR) || ".maxy";
|
|
41985
42635
|
var bootAccount = resolveAccount();
|
|
@@ -42047,7 +42697,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
42047
42697
|
}
|
|
42048
42698
|
init({
|
|
42049
42699
|
configDir: configDirForWhatsApp,
|
|
42050
|
-
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ??
|
|
42700
|
+
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..")),
|
|
42051
42701
|
accountConfig: bootAccountConfig,
|
|
42052
42702
|
// Task 2074 — enrich with the sub-account name and fan out to every open
|
|
42053
42703
|
// admin SSE stream. Fire-and-forget: the manager's persist path must not
|