@rubytech/create-sitedesk-code 0.1.520 → 0.1.521
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/plugins/admin/skills/platform-architecture/SKILL.md +4 -2
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-metadata-calendar.test.js +46 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-metadata-calendar.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +14 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +35 -4
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/references/email-reference.md +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
- package/payload/platform/scripts/check-architecture-skill-no-drift.mjs +20 -6
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +17 -5
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +30 -6
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +53 -21
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +16 -0
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +41 -2
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +1 -1
- package/payload/server/{chunk-TIIWMF5G.js → chunk-AZLT4LVO.js} +40 -7
- package/payload/server/{manager-B6OFOXSW.js → manager-CQBSUJXR.js} +1 -1
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-SW7mEMcP.js → AdminLoginScreens-Dar_MMJm.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-Dar_MMJm.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-Dar_MMJm.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-CpaI2F_p.js +2 -0
- package/payload/server/public/assets/AdminShell-CpaI2F_p.js.br +0 -0
- package/payload/server/public/assets/AdminShell-CpaI2F_p.js.gz +0 -0
- package/payload/server/public/assets/{activity-sqbyX0pX.js → activity-B1rt4GIy.js} +1 -1
- package/payload/server/public/assets/activity-B1rt4GIy.js.br +0 -0
- package/payload/server/public/assets/activity-B1rt4GIy.js.gz +0 -0
- package/payload/server/public/assets/{admin-D9Bt47GY.js → admin-DE3lLNje.js} +1 -1
- package/payload/server/public/assets/admin-DE3lLNje.js.br +0 -0
- package/payload/server/public/assets/admin-DE3lLNje.js.gz +0 -0
- package/payload/server/public/assets/{agents-BFYM5EDA.js → agents-BXq8woVf.js} +1 -1
- package/payload/server/public/assets/agents-BXq8woVf.js.br +0 -0
- package/payload/server/public/assets/agents-BXq8woVf.js.gz +0 -0
- package/payload/server/public/assets/{browser-DP5GZe4G.js → browser-1QSCPl3X.js} +1 -1
- package/payload/server/public/assets/browser-1QSCPl3X.js.br +0 -0
- package/payload/server/public/assets/browser-1QSCPl3X.js.gz +0 -0
- package/payload/server/public/assets/{calendar-BKaemLDG.js → calendar-D009qWVV.js} +1 -1
- package/payload/server/public/assets/calendar-D009qWVV.js.br +0 -0
- package/payload/server/public/assets/calendar-D009qWVV.js.gz +0 -0
- package/payload/server/public/assets/chat-CUg1fTqJ.js +1 -0
- package/payload/server/public/assets/chat-CUg1fTqJ.js.br +1 -0
- package/payload/server/public/assets/chat-CUg1fTqJ.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-C2JEy7uU.js +1 -0
- package/payload/server/public/assets/chevron-left-C2JEy7uU.js.br +0 -0
- package/payload/server/public/assets/chevron-right-DWa047Ys.js +1 -0
- package/payload/server/public/assets/chevron-right-DWa047Ys.js.br +0 -0
- package/payload/server/public/assets/clock-C3OqfCnn.js +1 -0
- package/payload/server/public/assets/clock-C3OqfCnn.js.br +0 -0
- package/payload/server/public/assets/clock-C3OqfCnn.js.gz +0 -0
- package/payload/server/public/assets/data-Bu_yCScC.js +1 -0
- package/payload/server/public/assets/data-Bu_yCScC.js.br +0 -0
- package/payload/server/public/assets/data-Bu_yCScC.js.gz +0 -0
- package/payload/server/public/assets/{file-text-BKK7R-k_.js → file-text-DwGAfCsd.js} +1 -1
- package/payload/server/public/assets/file-text-DwGAfCsd.js.br +0 -0
- package/payload/server/public/assets/file-text-DwGAfCsd.js.gz +0 -0
- package/payload/server/public/assets/{graph-FuxYySjK.js → graph-D-DGlfhW.js} +2 -2
- package/payload/server/public/assets/graph-D-DGlfhW.js.br +0 -0
- package/payload/server/public/assets/graph-D-DGlfhW.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-Cip6fmKK.js → graph-labels-BbX3fXzl.js} +1 -1
- package/payload/server/public/assets/graph-labels-BbX3fXzl.js.br +0 -0
- package/payload/server/public/assets/graph-labels-BbX3fXzl.js.gz +0 -0
- package/payload/server/public/assets/{maximize-2-D79AhVDo.js → maximize-2-C_-KapxH.js} +1 -1
- package/payload/server/public/assets/maximize-2-C_-KapxH.js.br +0 -0
- package/payload/server/public/assets/maximize-2-C_-KapxH.js.gz +0 -0
- package/payload/server/public/assets/{operator-BPAfgFhP.js → operator-CFrZVcv6.js} +1 -1
- package/payload/server/public/assets/operator-CFrZVcv6.js.br +0 -0
- package/payload/server/public/assets/operator-CFrZVcv6.js.gz +0 -0
- package/payload/server/public/assets/{page-BqEyS0W4.js → page-CHt62f1_.js} +1 -1
- package/payload/server/public/assets/page-CHt62f1_.js.br +0 -0
- package/payload/server/public/assets/page-CHt62f1_.js.gz +0 -0
- package/payload/server/public/assets/page-E74MYB18.js +32 -0
- package/payload/server/public/assets/page-E74MYB18.js.br +0 -0
- package/payload/server/public/assets/page-E74MYB18.js.gz +0 -0
- package/payload/server/public/assets/{public-D1xs3FzD.js → public-CI6K1sNW.js} +1 -1
- package/payload/server/public/assets/public-CI6K1sNW.js.br +0 -0
- package/payload/server/public/assets/public-CI6K1sNW.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-CcU_XD8B.js → rotate-ccw-PWu_QGrD.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-PWu_QGrD.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-PWu_QGrD.js.gz +0 -0
- package/payload/server/public/assets/routines-Cp3_XS7S.js +1 -0
- package/payload/server/public/assets/routines-Cp3_XS7S.js.br +0 -0
- package/payload/server/public/assets/routines-Cp3_XS7S.js.gz +0 -0
- package/payload/server/public/assets/skills-g8HvunPA.js +1 -0
- package/payload/server/public/assets/skills-g8HvunPA.js.br +0 -0
- package/payload/server/public/assets/skills-g8HvunPA.js.gz +0 -0
- package/payload/server/public/assets/{tasks-DgieyZqz.js → tasks-CMC_jDVm.js} +1 -1
- package/payload/server/public/assets/tasks-CMC_jDVm.js.br +0 -0
- package/payload/server/public/assets/tasks-CMC_jDVm.js.gz +0 -0
- package/payload/server/public/assets/{time-entry-format-IFqinQJj.js → time-entry-format-BXWuQ6Sw.js} +1 -1
- package/payload/server/public/assets/time-entry-format-BXWuQ6Sw.js.br +2 -0
- package/payload/server/public/assets/time-entry-format-BXWuQ6Sw.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-C5vH__sq.js → triangle-alert-BNshRC13.js} +1 -1
- package/payload/server/public/assets/triangle-alert-BNshRC13.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-BNshRC13.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-BeL2gPl6.js → useCopyFeedback-CwTZ9DqH.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-CwTZ9DqH.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-CwTZ9DqH.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js +14 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css.gz +0 -0
- package/payload/server/public/assets/{useVoiceRecorder-Cpw3aOSt.js → useVoiceRecorder-BGqV5R6u.js} +1 -1
- package/payload/server/public/assets/useVoiceRecorder-BGqV5R6u.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BGqV5R6u.js.gz +0 -0
- package/payload/server/public/assets/{wrench-BBEup3hd.js → wrench-DQ045Pyk.js} +1 -1
- package/payload/server/public/assets/wrench-DQ045Pyk.js.br +0 -0
- package/payload/server/public/assets/wrench-DQ045Pyk.js.gz +0 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +7 -7
- package/payload/server/public/chat.html +13 -13
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +13 -13
- package/payload/server/public/routines.html +6 -6
- package/payload/server/public/skills.html +5 -5
- package/payload/server/public/tasks.html +6 -6
- package/payload/server/server.js +234 -49
- package/payload/server/public/assets/AdminLoginScreens-SW7mEMcP.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-SW7mEMcP.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-Djg6ndne.js +0 -2
- package/payload/server/public/assets/AdminShell-Djg6ndne.js.br +0 -0
- package/payload/server/public/assets/AdminShell-Djg6ndne.js.gz +0 -0
- package/payload/server/public/assets/activity-sqbyX0pX.js.br +0 -0
- package/payload/server/public/assets/activity-sqbyX0pX.js.gz +0 -0
- package/payload/server/public/assets/admin-D9Bt47GY.js.br +0 -0
- package/payload/server/public/assets/admin-D9Bt47GY.js.gz +0 -0
- package/payload/server/public/assets/agents-BFYM5EDA.js.br +0 -0
- package/payload/server/public/assets/agents-BFYM5EDA.js.gz +0 -0
- package/payload/server/public/assets/browser-DP5GZe4G.js.br +0 -0
- package/payload/server/public/assets/browser-DP5GZe4G.js.gz +0 -0
- package/payload/server/public/assets/calendar-BKaemLDG.js.br +0 -0
- package/payload/server/public/assets/calendar-BKaemLDG.js.gz +0 -0
- package/payload/server/public/assets/chat-DPhT6urD.js +0 -1
- package/payload/server/public/assets/chat-DPhT6urD.js.br +0 -1
- package/payload/server/public/assets/chat-DPhT6urD.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-BSdXvJcH.js +0 -1
- package/payload/server/public/assets/chevron-right-DI7vI3IB.js +0 -1
- package/payload/server/public/assets/chevron-right-DI7vI3IB.js.br +0 -0
- package/payload/server/public/assets/clock-YUTPXZH5.js +0 -1
- package/payload/server/public/assets/clock-YUTPXZH5.js.br +0 -0
- package/payload/server/public/assets/clock-YUTPXZH5.js.gz +0 -0
- package/payload/server/public/assets/data-CfjttTvR.js +0 -1
- package/payload/server/public/assets/data-CfjttTvR.js.br +0 -2
- package/payload/server/public/assets/data-CfjttTvR.js.gz +0 -0
- package/payload/server/public/assets/file-text-BKK7R-k_.js.br +0 -0
- package/payload/server/public/assets/file-text-BKK7R-k_.js.gz +0 -0
- package/payload/server/public/assets/graph-FuxYySjK.js.br +0 -0
- package/payload/server/public/assets/graph-FuxYySjK.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-Cip6fmKK.js.br +0 -0
- package/payload/server/public/assets/graph-labels-Cip6fmKK.js.gz +0 -0
- package/payload/server/public/assets/maximize-2-D79AhVDo.js.br +0 -0
- package/payload/server/public/assets/maximize-2-D79AhVDo.js.gz +0 -0
- package/payload/server/public/assets/operator-BPAfgFhP.js.br +0 -0
- package/payload/server/public/assets/operator-BPAfgFhP.js.gz +0 -0
- package/payload/server/public/assets/page--78CbdFs.js +0 -32
- package/payload/server/public/assets/page--78CbdFs.js.br +0 -0
- package/payload/server/public/assets/page--78CbdFs.js.gz +0 -0
- package/payload/server/public/assets/page-BqEyS0W4.js.br +0 -0
- package/payload/server/public/assets/page-BqEyS0W4.js.gz +0 -0
- package/payload/server/public/assets/public-D1xs3FzD.js.br +0 -0
- package/payload/server/public/assets/public-D1xs3FzD.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-CcU_XD8B.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-CcU_XD8B.js.gz +0 -0
- package/payload/server/public/assets/routines-CA7D0myr.js +0 -1
- package/payload/server/public/assets/routines-CA7D0myr.js.br +0 -0
- package/payload/server/public/assets/routines-CA7D0myr.js.gz +0 -0
- package/payload/server/public/assets/skills-CfVPPSxO.js +0 -1
- package/payload/server/public/assets/skills-CfVPPSxO.js.br +0 -0
- package/payload/server/public/assets/skills-CfVPPSxO.js.gz +0 -0
- package/payload/server/public/assets/tasks-DgieyZqz.js.br +0 -0
- package/payload/server/public/assets/tasks-DgieyZqz.js.gz +0 -0
- package/payload/server/public/assets/time-entry-format-IFqinQJj.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-IFqinQJj.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-C5vH__sq.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-C5vH__sq.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-BeL2gPl6.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-BeL2gPl6.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CH9qm2Ad.js +0 -14
- package/payload/server/public/assets/useSubAccountSwitcher-CH9qm2Ad.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CH9qm2Ad.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-Cpw3aOSt.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-Cpw3aOSt.js.gz +0 -0
- package/payload/server/public/assets/wrench-BBEup3hd.js.br +0 -0
- package/payload/server/public/assets/wrench-BBEup3hd.js.gz +0 -0
package/payload/server/server.js
CHANGED
|
@@ -163,7 +163,6 @@ import {
|
|
|
163
163
|
sendMediaMessage,
|
|
164
164
|
sendPaused,
|
|
165
165
|
sendTextMessage,
|
|
166
|
-
senderJidFromTelephone,
|
|
167
166
|
sessionScratchpadRoot,
|
|
168
167
|
shouldDispatchInbound,
|
|
169
168
|
shutdown,
|
|
@@ -178,7 +177,7 @@ import {
|
|
|
178
177
|
validateFilePathInAccount,
|
|
179
178
|
waitForConnection,
|
|
180
179
|
warnNonUuidAccount
|
|
181
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-AZLT4LVO.js";
|
|
182
181
|
import {
|
|
183
182
|
ACCOUNTS_DIR,
|
|
184
183
|
BIN_DIR,
|
|
@@ -3919,7 +3918,7 @@ function createScheduleInjectRoutes(deps) {
|
|
|
3919
3918
|
var import_dist9 = __toESM(require_dist(), 1);
|
|
3920
3919
|
|
|
3921
3920
|
// app/lib/whatsapp/avatar-store.ts
|
|
3922
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
|
|
3921
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync4, rmSync } from "fs";
|
|
3923
3922
|
import { join as join6 } from "path";
|
|
3924
3923
|
function safeName(jid) {
|
|
3925
3924
|
if (jid.length === 0 || jid.includes("/") || jid.includes("\\") || jid.includes("..")) {
|
|
@@ -3964,30 +3963,41 @@ function writeAvatarAbsent(accountId, jid) {
|
|
|
3964
3963
|
const dir = dirFor(accountId);
|
|
3965
3964
|
const base = join6(dir, safeName(jid));
|
|
3966
3965
|
mkdirSync2(dir, { recursive: true });
|
|
3966
|
+
rmSync(`${base}.bin`, { force: true });
|
|
3967
3967
|
const meta = { jid, state: "absent", fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3968
3968
|
writeFileSync3(`${base}.json`, JSON.stringify(meta));
|
|
3969
3969
|
}
|
|
3970
|
-
function
|
|
3970
|
+
function removeAvatar(accountId, jid) {
|
|
3971
|
+
const base = join6(dirFor(accountId), safeName(jid));
|
|
3972
|
+
rmSync(`${base}.bin`, { force: true });
|
|
3973
|
+
rmSync(`${base}.json`, { force: true });
|
|
3974
|
+
}
|
|
3975
|
+
function listAvatarEntries(accountId) {
|
|
3971
3976
|
const dir = dirFor(accountId);
|
|
3972
3977
|
if (!existsSync3(dir)) return [];
|
|
3973
3978
|
const out = [];
|
|
3974
3979
|
for (const f of readdirSync2(dir)) {
|
|
3975
3980
|
if (!f.endsWith(".json")) continue;
|
|
3976
3981
|
const meta = readMeta(join6(dir, f));
|
|
3977
|
-
if (meta?.jid)
|
|
3982
|
+
if (!meta?.jid) continue;
|
|
3983
|
+
let bytes = 0;
|
|
3984
|
+
if (meta.state === "present") {
|
|
3985
|
+
try {
|
|
3986
|
+
bytes = statSync4(join6(dir, `${f.slice(0, -".json".length)}.bin`)).size;
|
|
3987
|
+
} catch {
|
|
3988
|
+
bytes = 0;
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
out.push({ jid: meta.jid, state: meta.state, bytes, fetchedAt: meta.fetchedAt });
|
|
3978
3992
|
}
|
|
3979
3993
|
return out;
|
|
3980
3994
|
}
|
|
3995
|
+
function listAvatarJids(accountId) {
|
|
3996
|
+
return listAvatarEntries(accountId).map((e) => e.jid);
|
|
3997
|
+
}
|
|
3981
3998
|
function avatarCensus(accountId) {
|
|
3982
|
-
const
|
|
3983
|
-
|
|
3984
|
-
let entries2 = 0;
|
|
3985
|
-
let bytes = 0;
|
|
3986
|
-
for (const f of readdirSync2(dir)) {
|
|
3987
|
-
if (f.endsWith(".json")) entries2++;
|
|
3988
|
-
else if (f.endsWith(".bin")) bytes += statSync4(join6(dir, f)).size;
|
|
3989
|
-
}
|
|
3990
|
-
return { entries: entries2, bytes };
|
|
3999
|
+
const entries2 = listAvatarEntries(accountId);
|
|
4000
|
+
return { entries: entries2.length, bytes: entries2.reduce((n, e) => n + e.bytes, 0) };
|
|
3991
4001
|
}
|
|
3992
4002
|
|
|
3993
4003
|
// app/lib/whatsapp/avatar-fetch.ts
|
|
@@ -4007,7 +4017,7 @@ async function defaultHttpGet(url) {
|
|
|
4007
4017
|
}
|
|
4008
4018
|
}
|
|
4009
4019
|
async function defaultResolve(accountId) {
|
|
4010
|
-
const { resolveSocket: resolveSocket2 } = await import("./manager-
|
|
4020
|
+
const { resolveSocket: resolveSocket2 } = await import("./manager-CQBSUJXR.js");
|
|
4011
4021
|
return resolveSocket2(accountId);
|
|
4012
4022
|
}
|
|
4013
4023
|
async function fetchAvatar(accountId, jid, deps = {}) {
|
|
@@ -4055,25 +4065,66 @@ async function fetchAvatar(accountId, jid, deps = {}) {
|
|
|
4055
4065
|
// app/lib/whatsapp/avatar-sweep.ts
|
|
4056
4066
|
var TAG7 = "[wa-avatar]";
|
|
4057
4067
|
var CENSUS_TAG = "[wa-avatar-census]";
|
|
4068
|
+
var AVATAR_STORE_MAX_BYTES = 16 * 1024 * 1024;
|
|
4058
4069
|
function liveJidsForAccount(accountId) {
|
|
4059
4070
|
const live = /* @__PURE__ */ new Set();
|
|
4060
4071
|
for (const [remoteJid, records] of readAllConversations(accountId)) {
|
|
4061
|
-
live.add(
|
|
4062
|
-
for (const r of records) {
|
|
4063
|
-
if (r.fromMe) continue;
|
|
4064
|
-
const jid = senderJidFromTelephone(r.senderTelephone);
|
|
4065
|
-
if (jid) live.add(jid);
|
|
4066
|
-
}
|
|
4072
|
+
for (const jid of avatarJidsForConversation(remoteJid, records)) live.add(jid);
|
|
4067
4073
|
}
|
|
4068
4074
|
return live;
|
|
4069
4075
|
}
|
|
4076
|
+
function avatarJidsForConversation(remoteJid, records) {
|
|
4077
|
+
const jids = /* @__PURE__ */ new Set([remoteJid]);
|
|
4078
|
+
if (!isGroupJid(remoteJid)) return jids;
|
|
4079
|
+
for (const r of records) {
|
|
4080
|
+
if (r.fromMe) continue;
|
|
4081
|
+
if (r.senderJid && isUserJid(r.senderJid)) jids.add(r.senderJid);
|
|
4082
|
+
}
|
|
4083
|
+
return jids;
|
|
4084
|
+
}
|
|
4085
|
+
function evictAvatars(accountId, live) {
|
|
4086
|
+
let unreferenced = 0;
|
|
4087
|
+
let overBound = 0;
|
|
4088
|
+
let freedBytes = 0;
|
|
4089
|
+
const kept = [];
|
|
4090
|
+
for (const e of listAvatarEntries(accountId)) {
|
|
4091
|
+
if (live.has(e.jid)) {
|
|
4092
|
+
kept.push(e);
|
|
4093
|
+
continue;
|
|
4094
|
+
}
|
|
4095
|
+
removeAvatar(accountId, e.jid);
|
|
4096
|
+
unreferenced++;
|
|
4097
|
+
freedBytes += e.bytes;
|
|
4098
|
+
}
|
|
4099
|
+
let bytes = kept.reduce((n, e) => n + e.bytes, 0);
|
|
4100
|
+
if (bytes > AVATAR_STORE_MAX_BYTES) {
|
|
4101
|
+
const oldestFirst = kept.filter((e) => e.state === "present").sort((a, b) => a.fetchedAt.localeCompare(b.fetchedAt));
|
|
4102
|
+
for (const e of oldestFirst) {
|
|
4103
|
+
if (bytes <= AVATAR_STORE_MAX_BYTES) break;
|
|
4104
|
+
removeAvatar(accountId, e.jid);
|
|
4105
|
+
overBound++;
|
|
4106
|
+
freedBytes += e.bytes;
|
|
4107
|
+
bytes -= e.bytes;
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
return { unreferenced, overBound, freedBytes };
|
|
4111
|
+
}
|
|
4070
4112
|
async function sweepAvatars() {
|
|
4071
4113
|
const started2 = Date.now();
|
|
4072
4114
|
let jids = 0;
|
|
4073
4115
|
let refetched = 0;
|
|
4074
4116
|
let skipped = 0;
|
|
4075
4117
|
let failed = 0;
|
|
4118
|
+
let evicted = 0;
|
|
4076
4119
|
for (const accountId of listStoredAccountIds()) {
|
|
4120
|
+
const live = liveJidsForAccount(accountId);
|
|
4121
|
+
const ev = evictAvatars(accountId, live);
|
|
4122
|
+
evicted += ev.unreferenced + ev.overBound;
|
|
4123
|
+
if (ev.unreferenced > 0 || ev.overBound > 0) {
|
|
4124
|
+
console.log(
|
|
4125
|
+
`${TAG7} op=evict account=${accountId.slice(0, 8)} unreferenced=${ev.unreferenced} overBound=${ev.overBound} freedBytes=${ev.freedBytes}`
|
|
4126
|
+
);
|
|
4127
|
+
}
|
|
4077
4128
|
const cached3 = listAvatarJids(accountId);
|
|
4078
4129
|
for (const jid of cached3) {
|
|
4079
4130
|
jids++;
|
|
@@ -4089,11 +4140,10 @@ async function sweepAvatars() {
|
|
|
4089
4140
|
else if (outcome === "failed") failed++;
|
|
4090
4141
|
else skipped++;
|
|
4091
4142
|
}
|
|
4092
|
-
const live = liveJidsForAccount(accountId);
|
|
4093
4143
|
const census = avatarCensus(accountId);
|
|
4094
4144
|
console.log(`${CENSUS_TAG} account=${accountId.slice(0, 8)} entries=${census.entries} bytes=${census.bytes} live=${live.size}`);
|
|
4095
4145
|
}
|
|
4096
|
-
const note = `jids=${jids} refetched=${refetched} skipped=${skipped} failed=${failed} ms=${Date.now() - started2}`;
|
|
4146
|
+
const note = `jids=${jids} refetched=${refetched} skipped=${skipped} failed=${failed} evicted=${evicted} ms=${Date.now() - started2}`;
|
|
4097
4147
|
console.log(`${TAG7} op=sweep ${note}`);
|
|
4098
4148
|
return note;
|
|
4099
4149
|
}
|
|
@@ -4104,7 +4154,9 @@ var OFFENDER_CAP = 10;
|
|
|
4104
4154
|
function auditGroupOutbound() {
|
|
4105
4155
|
let convos = 0;
|
|
4106
4156
|
let outbound = 0;
|
|
4107
|
-
let
|
|
4157
|
+
let operator = 0;
|
|
4158
|
+
let agent = 0;
|
|
4159
|
+
let unstamped = 0;
|
|
4108
4160
|
const offenders = [];
|
|
4109
4161
|
for (const accountId of listStoredAccountIds()) {
|
|
4110
4162
|
for (const [remoteJid, records] of readAllConversations(accountId)) {
|
|
@@ -4113,18 +4165,20 @@ function auditGroupOutbound() {
|
|
|
4113
4165
|
for (const r of records) {
|
|
4114
4166
|
if (!r.fromMe) continue;
|
|
4115
4167
|
outbound++;
|
|
4116
|
-
if (r.origin === "operator-manual")
|
|
4117
|
-
|
|
4118
|
-
|
|
4168
|
+
if (r.origin === "operator-manual") operator++;
|
|
4169
|
+
else if (r.origin === "agent") {
|
|
4170
|
+
agent++;
|
|
4171
|
+
if (offenders.length < OFFENDER_CAP) offenders.push(r.msgKeyId);
|
|
4172
|
+
} else unstamped++;
|
|
4119
4173
|
}
|
|
4120
4174
|
}
|
|
4121
4175
|
}
|
|
4122
|
-
return { convos, outbound,
|
|
4176
|
+
return { convos, outbound, operator, agent, unstamped, offenders };
|
|
4123
4177
|
}
|
|
4124
4178
|
function auditGroupOutboundNote() {
|
|
4125
4179
|
const a = auditGroupOutbound();
|
|
4126
|
-
const tail = a.
|
|
4127
|
-
const note = `convos=${a.convos} outbound=${a.outbound}
|
|
4180
|
+
const tail = a.agent > 0 ? ` offenders=${a.offenders.join(",")}` : "";
|
|
4181
|
+
const note = `convos=${a.convos} outbound=${a.outbound} operator=${a.operator} agent=${a.agent} unstamped=${a.unstamped}${tail}`;
|
|
4128
4182
|
console.log(`${TAG8} op=group-outbound-audit ${note}`);
|
|
4129
4183
|
return note;
|
|
4130
4184
|
}
|
|
@@ -5868,6 +5922,7 @@ app2.post("/reply", async (c) => {
|
|
|
5868
5922
|
quotedId: null,
|
|
5869
5923
|
quotedSender: null,
|
|
5870
5924
|
scope: template.scope,
|
|
5925
|
+
origin: "agent",
|
|
5871
5926
|
createdAt: nowIso
|
|
5872
5927
|
};
|
|
5873
5928
|
appendMessage(storeKey, rec);
|
|
@@ -8020,12 +8075,7 @@ function resolveReplyAppendAccount(args) {
|
|
|
8020
8075
|
return args.resolve(args.sendAccountId, args.counterpartyPhone) || args.sendAccountId;
|
|
8021
8076
|
}
|
|
8022
8077
|
function warmConversationAvatars(accountId, remoteJid, records) {
|
|
8023
|
-
const jids =
|
|
8024
|
-
for (const r of records) {
|
|
8025
|
-
if (r.fromMe) continue;
|
|
8026
|
-
const jid = senderJidFromTelephone(r.senderTelephone);
|
|
8027
|
-
if (jid) jids.add(jid);
|
|
8028
|
-
}
|
|
8078
|
+
const jids = avatarJidsForConversation(remoteJid, records);
|
|
8029
8079
|
console.log(`[wa-avatar] op=request account=${accountId.slice(0, 8)} convo=${remoteJid.slice(0, 12)} jids=${jids.size} reason=reader-open`);
|
|
8030
8080
|
for (const jid of jids) {
|
|
8031
8081
|
void fetchAvatar(accountId, jid).catch((e) => {
|
|
@@ -8096,6 +8146,10 @@ app4.get("/store-stream", requireAdminSession, (c) => {
|
|
|
8096
8146
|
// Task 2081 — the group transcript's per-sender colour key. Stable across a
|
|
8097
8147
|
// display-name change, which `senderName` is not.
|
|
8098
8148
|
senderTelephone: r.senderTelephone,
|
|
8149
|
+
// Task 2096 — the sender's wire JID, for the per-bubble avatar. Omitted
|
|
8150
|
+
// rather than sent null when the record has none, so the client's own
|
|
8151
|
+
// presence check is the whole test. Pre-2096 records have none.
|
|
8152
|
+
...r.senderJid ? { senderJid: r.senderJid } : {},
|
|
8099
8153
|
origin: r.origin,
|
|
8100
8154
|
msgKeyId: r.msgKeyId,
|
|
8101
8155
|
// Task 1619 — folded inbound reactions for this bubble (omitted when none).
|
|
@@ -11727,7 +11781,7 @@ var telegram_default = app10;
|
|
|
11727
11781
|
|
|
11728
11782
|
// server/routes/quickbooks.ts
|
|
11729
11783
|
import { join as join28 } from "path";
|
|
11730
|
-
import { existsSync as existsSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync5 } from "fs";
|
|
11784
|
+
import { existsSync as existsSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, rmSync as rmSync2, renameSync as renameSync5 } from "fs";
|
|
11731
11785
|
var TAG18 = "[quickbooks]";
|
|
11732
11786
|
var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
|
|
11733
11787
|
var STATE_RE = /^[A-Za-z0-9_-]+$/;
|
|
@@ -11808,7 +11862,7 @@ async function completeConsent(args) {
|
|
|
11808
11862
|
console.error(`${TAG18} op=token-exchange realmId=${realmId} persisted=${persisted}`);
|
|
11809
11863
|
return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
|
|
11810
11864
|
} finally {
|
|
11811
|
-
|
|
11865
|
+
rmSync2(claimFile, { force: true });
|
|
11812
11866
|
}
|
|
11813
11867
|
}
|
|
11814
11868
|
function page(title, detail) {
|
|
@@ -13243,7 +13297,7 @@ var session_upload_default = app19;
|
|
|
13243
13297
|
|
|
13244
13298
|
// server/routes/admin/agents.ts
|
|
13245
13299
|
import { resolve as resolve20 } from "path";
|
|
13246
|
-
import { readdirSync as readdirSync21, readFileSync as readFileSync28, existsSync as existsSync30, rmSync as
|
|
13300
|
+
import { readdirSync as readdirSync21, readFileSync as readFileSync28, existsSync as existsSync30, rmSync as rmSync3, renameSync as renameSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync12 } from "fs";
|
|
13247
13301
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
13248
13302
|
|
|
13249
13303
|
// ../services/claude-session-manager/src/tool-surface.ts
|
|
@@ -14136,7 +14190,7 @@ app20.delete("/:slug", async (c) => {
|
|
|
14136
14190
|
return c.json({ error: `Graph cleanup failed: ${msg}` }, 500);
|
|
14137
14191
|
}
|
|
14138
14192
|
try {
|
|
14139
|
-
|
|
14193
|
+
rmSync3(agentDir, { recursive: true, force: true });
|
|
14140
14194
|
console.log(`[admin/agents] op=delete accountId=${account.accountId.slice(0, 8)} slug=${slug} outcome=ok`);
|
|
14141
14195
|
return c.json({ ok: true });
|
|
14142
14196
|
} catch (err) {
|
|
@@ -20234,6 +20288,7 @@ function resolveSpawnPlan(host, operatorDomains, body, mintId = randomUUID7, adm
|
|
|
20234
20288
|
const operator = isOperatorHost(host, operatorDomains);
|
|
20235
20289
|
const resumeId = typeof body.sessionId === "string" && body.sessionId.length > 0 ? body.sessionId : void 0;
|
|
20236
20290
|
const name = typeof body.name === "string" && body.name.length > 0 ? body.name : void 0;
|
|
20291
|
+
const rawTitle = typeof body.title === "string" ? body.title.trim() : "";
|
|
20237
20292
|
const webchatBind = resumeId === void 0 || operator;
|
|
20238
20293
|
const kind = resumeId === void 0 ? "new" : "resume";
|
|
20239
20294
|
const origin = operator ? "operator" : "admin";
|
|
@@ -20264,13 +20319,25 @@ function resolveSpawnPlan(host, operatorDomains, body, mintId = randomUUID7, adm
|
|
|
20264
20319
|
payload.sessionId = resumeId;
|
|
20265
20320
|
if (name) payload.name = name;
|
|
20266
20321
|
}
|
|
20267
|
-
return {
|
|
20322
|
+
return {
|
|
20323
|
+
webchatBind,
|
|
20324
|
+
origin,
|
|
20325
|
+
kind,
|
|
20326
|
+
sessionId,
|
|
20327
|
+
payload,
|
|
20328
|
+
model: pickedModel,
|
|
20329
|
+
effort: pickedEffort,
|
|
20330
|
+
paramsSource,
|
|
20331
|
+
// Never rides `payload`: the manager's rc-spawn takes no title. The route
|
|
20332
|
+
// applies it through the rename endpoint once the session id exists.
|
|
20333
|
+
title: kind === "new" && webchatBind && rawTitle.length > 0 ? rawTitle : null
|
|
20334
|
+
};
|
|
20268
20335
|
}
|
|
20269
|
-
function shapeWebchatResponse(sessionId, manager) {
|
|
20336
|
+
function shapeWebchatResponse(sessionId, manager, titleSource = "none") {
|
|
20270
20337
|
const outcome = typeof manager?.outcome === "string" ? manager.outcome : "bound";
|
|
20271
20338
|
const redirected = manager?.redirected === true && typeof manager?.sessionId === "string";
|
|
20272
20339
|
const landed = redirected ? manager.sessionId : sessionId;
|
|
20273
|
-
return { sessionId: landed, outcome, target: `/chat?session=${landed}
|
|
20340
|
+
return { sessionId: landed, outcome, target: `/chat?session=${landed}`, titleSource };
|
|
20274
20341
|
}
|
|
20275
20342
|
var app38 = new Hono();
|
|
20276
20343
|
app38.post("/", requireAdminSession, async (c) => {
|
|
@@ -20349,9 +20416,40 @@ app38.post("/", requireAdminSession, async (c) => {
|
|
|
20349
20416
|
);
|
|
20350
20417
|
}
|
|
20351
20418
|
}
|
|
20419
|
+
let titleSource = "none";
|
|
20420
|
+
if (plan.kind === "new") {
|
|
20421
|
+
if (plan.title === null) {
|
|
20422
|
+
console.log(
|
|
20423
|
+
`[chat-spawn] op=title spawnReqId=${spawnReqId} sessionId=${plan.sessionId} titleLen=0 source=absent outcome=skipped`
|
|
20424
|
+
);
|
|
20425
|
+
} else {
|
|
20426
|
+
try {
|
|
20427
|
+
const tRes = await fetch(`${managerBase("session-rc-spawn:title")}/${plan.sessionId}/rename`, {
|
|
20428
|
+
method: "POST",
|
|
20429
|
+
headers: { "Content-Type": "application/json" },
|
|
20430
|
+
body: JSON.stringify({ title: plan.title })
|
|
20431
|
+
});
|
|
20432
|
+
if (tRes.status === 200) {
|
|
20433
|
+
titleSource = "user";
|
|
20434
|
+
console.log(
|
|
20435
|
+
`[chat-spawn] op=title spawnReqId=${spawnReqId} sessionId=${plan.sessionId} titleLen=${plan.title.length} source=modal outcome=applied`
|
|
20436
|
+
);
|
|
20437
|
+
} else {
|
|
20438
|
+
const reason = (await tRes.json().catch(() => ({}))).reason ?? `status-${tRes.status}`;
|
|
20439
|
+
console.error(
|
|
20440
|
+
`[chat-spawn] op=title spawnReqId=${spawnReqId} sessionId=${plan.sessionId} titleLen=${plan.title.length} source=modal outcome=rejected reason=${reason}`
|
|
20441
|
+
);
|
|
20442
|
+
}
|
|
20443
|
+
} catch (err) {
|
|
20444
|
+
console.error(
|
|
20445
|
+
`[chat-spawn] op=title spawnReqId=${spawnReqId} sessionId=${plan.sessionId} titleLen=${plan.title.length} source=modal outcome=failed reason=${err instanceof Error ? err.message : String(err)}`
|
|
20446
|
+
);
|
|
20447
|
+
}
|
|
20448
|
+
}
|
|
20449
|
+
}
|
|
20352
20450
|
console.log(`[chat-spawn] op=navigate spawnReqId=${spawnReqId} target=${target}`);
|
|
20353
20451
|
console.log(`[chat-spawn] op=exit spawnReqId=${spawnReqId} outcome=bound`);
|
|
20354
|
-
return c.json(shapeWebchatResponse(plan.sessionId, parsed));
|
|
20452
|
+
return c.json(shapeWebchatResponse(plan.sessionId, parsed, titleSource));
|
|
20355
20453
|
}
|
|
20356
20454
|
return c.json(parsed ?? {});
|
|
20357
20455
|
});
|
|
@@ -22162,7 +22260,7 @@ app51.delete("/:eventId", requireAdminSession, async (c) => {
|
|
|
22162
22260
|
var routines_default = app51;
|
|
22163
22261
|
|
|
22164
22262
|
// server/routes/admin/skills.ts
|
|
22165
|
-
import { existsSync as existsSync39, readdirSync as readdirSync24, readFileSync as readFileSync36, rmSync as
|
|
22263
|
+
import { existsSync as existsSync39, readdirSync as readdirSync24, readFileSync as readFileSync36, rmSync as rmSync4 } from "fs";
|
|
22166
22264
|
import { resolve as resolve30 } from "path";
|
|
22167
22265
|
var app52 = new Hono();
|
|
22168
22266
|
var NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -22268,9 +22366,9 @@ app52.delete("/:plugin/:skill", requireAdminSession, async (c) => {
|
|
|
22268
22366
|
return c.json({ error: "Skill not found" }, 404);
|
|
22269
22367
|
}
|
|
22270
22368
|
try {
|
|
22271
|
-
|
|
22369
|
+
rmSync4(skillDir, { recursive: true, force: true });
|
|
22272
22370
|
const pluginDirRemoved = subdirs2(skillsDir).length === 0;
|
|
22273
|
-
if (pluginDirRemoved)
|
|
22371
|
+
if (pluginDirRemoved) rmSync4(pluginDir, { recursive: true, force: true });
|
|
22274
22372
|
const skillDirGone = !existsSync39(skillDir);
|
|
22275
22373
|
if (!skillDirGone) {
|
|
22276
22374
|
console.error(`[admin:skills] op=delete plugin=${plugin} skill=${skill} outcome=rejected reason=rm-incomplete skillDirGone=false`);
|
|
@@ -25749,6 +25847,7 @@ function createWaChannelRoutes(deps) {
|
|
|
25749
25847
|
return c.json({ error: message, terminal }, 500);
|
|
25750
25848
|
}
|
|
25751
25849
|
console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
|
|
25850
|
+
deps.onReplyDispatch?.(senderId, Date.now());
|
|
25752
25851
|
return c.json({ ok: true });
|
|
25753
25852
|
});
|
|
25754
25853
|
app73.post("/wa-channel/reply-document", async (c) => {
|
|
@@ -25823,9 +25922,10 @@ function createWaChannelRoutes(deps) {
|
|
|
25823
25922
|
const finalBytes = num(body?.finalBytes);
|
|
25824
25923
|
const queueDepth = num(body?.queueDepth);
|
|
25825
25924
|
const pendingTurns = num(body?.pendingTurns);
|
|
25925
|
+
const consumed2 = num(body?.consumed);
|
|
25826
25926
|
const sid = sessionId.slice(0, 8);
|
|
25827
25927
|
const deliver = interactive && !replied && finalText.trim().length > 0;
|
|
25828
|
-
const decision = `sessionId=${sid} replied=${replied ? "yes" : "no"} interactive=${interactive ? "yes" : "no"} finalBytes=${finalBytes} queueDepth=${queueDepth} pendingTurns=${pendingTurns}`;
|
|
25928
|
+
const decision = `sessionId=${sid} replied=${replied ? "yes" : "no"} interactive=${interactive ? "yes" : "no"} finalBytes=${finalBytes} queueDepth=${queueDepth} pendingTurns=${pendingTurns} consumed=${consumed2}`;
|
|
25829
25929
|
if (!replied && finalBytes > 0 && !deliver) {
|
|
25830
25930
|
const op = interactive ? "turn-answer-dropped" : "turn-answer-withheld";
|
|
25831
25931
|
console.error(
|
|
@@ -25844,6 +25944,7 @@ function createWaChannelRoutes(deps) {
|
|
|
25844
25944
|
return c.json({ ok: true, delivered: "undelivered" });
|
|
25845
25945
|
}
|
|
25846
25946
|
console.error(`[whatsapp-native] op=turn-end ${decision} delivered=fallback bytes=${bytes}`);
|
|
25947
|
+
deps.onReplyDispatch?.(senderId, Date.now());
|
|
25847
25948
|
return c.json({ ok: true, delivered: "fallback" });
|
|
25848
25949
|
}
|
|
25849
25950
|
if (replied) {
|
|
@@ -25860,6 +25961,7 @@ function createWaChannelRoutes(deps) {
|
|
|
25860
25961
|
var QUEUED_THRESHOLD_MS = 3e4;
|
|
25861
25962
|
var ACK_THRESHOLD_MS = 3e4;
|
|
25862
25963
|
var SWEEP_INTERVAL_MS = 15e3;
|
|
25964
|
+
var MEDIA_STANZA_STALE_MS = 12e4;
|
|
25863
25965
|
var WaGateway = class {
|
|
25864
25966
|
constructor(deps) {
|
|
25865
25967
|
this.deps = deps;
|
|
@@ -25874,6 +25976,66 @@ var WaGateway = class {
|
|
|
25874
25976
|
docContexts = /* @__PURE__ */ new Map();
|
|
25875
25977
|
spawning = /* @__PURE__ */ new Set();
|
|
25876
25978
|
seq = 0;
|
|
25979
|
+
// Task 2110 — senderId -> the arrival ms of every still-unemitted non-audio
|
|
25980
|
+
// media stanza for that sender, oldest first. This is the race window: a reply
|
|
25981
|
+
// dispatched while the list is non-empty was composed by a turn that could not
|
|
25982
|
+
// yet see an incoming file, and the window is measured from the OLDEST entry.
|
|
25983
|
+
//
|
|
25984
|
+
// One entry per stanza, not one per sender. Downloads finish out of order and
|
|
25985
|
+
// the debouncer flushes whatever has landed, so a small file can be emitted
|
|
25986
|
+
// while a large one is still downloading. Holding a single timestamp meant that
|
|
25987
|
+
// first inbound closed the window and the still-blind replies that followed
|
|
25988
|
+
// were never counted, with the census reading zero throughout.
|
|
25989
|
+
pendingMediaStanza = /* @__PURE__ */ new Map();
|
|
25990
|
+
// Counters for the per-sweep census, reset on every print.
|
|
25991
|
+
mediaRaces = 0;
|
|
25992
|
+
mediaRaceSenders = /* @__PURE__ */ new Set();
|
|
25993
|
+
mediaStanzaExpired = 0;
|
|
25994
|
+
/** Task 2110 — the WhatsApp manager reports that a non-audio media stanza for
|
|
25995
|
+
* this sender has started downloading. The gateway cannot derive this itself:
|
|
25996
|
+
* it first learns of the media when the completed inbound arrives, which is
|
|
25997
|
+
* the CLOSE of the window, not its open. Audio is excluded by the caller —
|
|
25998
|
+
* its transcript rides in as `text`, so no turn is ever blind to a voice note.
|
|
25999
|
+
* The key is canonicalised on write, the same rule handleInbound applies, so
|
|
26000
|
+
* a "+"-prefixed value and a digits-only one address one window. */
|
|
26001
|
+
noteMediaStanza(senderId, atMs) {
|
|
26002
|
+
const key = normalizeTarget(senderId) ?? senderId;
|
|
26003
|
+
const inFlight2 = this.pendingMediaStanza.get(key);
|
|
26004
|
+
if (inFlight2) inFlight2.push(atMs);
|
|
26005
|
+
else this.pendingMediaStanza.set(key, [atMs]);
|
|
26006
|
+
}
|
|
26007
|
+
/** Task 2110 — drop every entry past the staleness cap, oldest first, counting
|
|
26008
|
+
* each into the census. Entries are appended in arrival order, so the scan
|
|
26009
|
+
* stops at the first one still inside the cap. A download that never produced
|
|
26010
|
+
* an inbound leaves its entry here; without this it would hold the window open
|
|
26011
|
+
* forever and make every later reply to that sender read as a race. */
|
|
26012
|
+
expireStaleStanzas(key, inFlight2, now) {
|
|
26013
|
+
while (inFlight2.length > 0 && now - inFlight2[0] > MEDIA_STANZA_STALE_MS) {
|
|
26014
|
+
inFlight2.shift();
|
|
26015
|
+
this.mediaStanzaExpired++;
|
|
26016
|
+
}
|
|
26017
|
+
if (inFlight2.length === 0) this.pendingMediaStanza.delete(key);
|
|
26018
|
+
}
|
|
26019
|
+
/** Task 2110 — a reply was dispatched to this sender. When a media stanza for
|
|
26020
|
+
* the same sender is in flight, the turn that composed this reply could not
|
|
26021
|
+
* see the incoming file, so the exposure is recorded whether or not the agent
|
|
26022
|
+
* actually misspoke: a steering clause has no machine-checkable
|
|
26023
|
+
* post-condition, and this counts entry into the window, never the phrasing.
|
|
26024
|
+
* A window past the staleness cap is a download that never produced an
|
|
26025
|
+
* inbound; it is dropped and counted as expired, not reported as a race. */
|
|
26026
|
+
noteReplyDispatch(senderId, atMs) {
|
|
26027
|
+
const key = normalizeTarget(senderId) ?? senderId;
|
|
26028
|
+
const inFlight2 = this.pendingMediaStanza.get(key);
|
|
26029
|
+
if (inFlight2 === void 0) return;
|
|
26030
|
+
this.expireStaleStanzas(key, inFlight2, atMs);
|
|
26031
|
+
if (inFlight2.length === 0) return;
|
|
26032
|
+
const stanzaAt = inFlight2[0];
|
|
26033
|
+
this.mediaRaces++;
|
|
26034
|
+
this.mediaRaceSenders.add(key);
|
|
26035
|
+
console.error(
|
|
26036
|
+
`[whatsapp-native] op=media-race senderId=${key} replyAt=${new Date(atMs).toISOString()} mediaStanzaAt=${new Date(stanzaAt).toISOString()} gapMs=${atMs - stanzaAt}`
|
|
26037
|
+
);
|
|
26038
|
+
}
|
|
25877
26039
|
/** Hono sub-app exposing the loopback channel routes; mount on the UI app.
|
|
25878
26040
|
* Task 1836 — the process-local unseated-sender census that used to live here
|
|
25879
26041
|
* was retired. Its quiet state was ambiguous (a UI restart emptied it, so
|
|
@@ -25895,7 +26057,8 @@ var WaGateway = class {
|
|
|
25895
26057
|
console.error(
|
|
25896
26058
|
`[whatsapp-native] op=channel-received senderId=${senderId} waMessageId=${waMessageId}${found ? "" : " note=unknown"}`
|
|
25897
26059
|
);
|
|
25898
|
-
}
|
|
26060
|
+
},
|
|
26061
|
+
onReplyDispatch: (senderId, atMs) => this.noteReplyDispatch(senderId, atMs)
|
|
25899
26062
|
});
|
|
25900
26063
|
}
|
|
25901
26064
|
/** Run one reconciliation pass: log every terminal delivery event the sweep
|
|
@@ -25915,6 +26078,15 @@ var WaGateway = class {
|
|
|
25915
26078
|
`[whatsapp-native] op=${e.kind} senderId=${e.senderId} waMessageId=${e.waMessageId} ageMs=${e.ageMs}`
|
|
25916
26079
|
);
|
|
25917
26080
|
}
|
|
26081
|
+
for (const [key, inFlight2] of [...this.pendingMediaStanza]) {
|
|
26082
|
+
this.expireStaleStanzas(key, inFlight2, now);
|
|
26083
|
+
}
|
|
26084
|
+
console.error(
|
|
26085
|
+
`[whatsapp-native] op=media-race-census races=${this.mediaRaces} senders=${this.mediaRaceSenders.size} expired=${this.mediaStanzaExpired}`
|
|
26086
|
+
);
|
|
26087
|
+
this.mediaRaces = 0;
|
|
26088
|
+
this.mediaRaceSenders.clear();
|
|
26089
|
+
this.mediaStanzaExpired = 0;
|
|
25918
26090
|
return events;
|
|
25919
26091
|
}
|
|
25920
26092
|
/** Arm the periodic reconciliation sweep in the long-lived UI process.
|
|
@@ -25946,6 +26118,14 @@ var WaGateway = class {
|
|
|
25946
26118
|
if (senderId !== input.senderId) {
|
|
25947
26119
|
console.error(`[whatsapp-native] op=sender-key raw=${input.senderId} canonical=${senderId}`);
|
|
25948
26120
|
}
|
|
26121
|
+
const arrived = input.media.filter((m) => m.type !== "audio").length;
|
|
26122
|
+
if (arrived > 0) {
|
|
26123
|
+
const inFlight2 = this.pendingMediaStanza.get(senderId);
|
|
26124
|
+
if (inFlight2) {
|
|
26125
|
+
inFlight2.splice(0, arrived);
|
|
26126
|
+
if (inFlight2.length === 0) this.pendingMediaStanza.delete(senderId);
|
|
26127
|
+
}
|
|
26128
|
+
}
|
|
25949
26129
|
if (input.passive) {
|
|
25950
26130
|
const msgId = `wa-${++this.seq}`;
|
|
25951
26131
|
await this.deps.ensureChannelSession({
|
|
@@ -30276,6 +30456,11 @@ init({
|
|
|
30276
30456
|
);
|
|
30277
30457
|
});
|
|
30278
30458
|
},
|
|
30459
|
+
// Task 2110 — the manager is the only place that sees a media stanza start
|
|
30460
|
+
// downloading; the gateway first sees the media on the completed inbound,
|
|
30461
|
+
// which is the race window's close. Wiring it here keeps the manager free of
|
|
30462
|
+
// any gateway import, exactly as onMessage and onNotify are wired.
|
|
30463
|
+
onMediaStanza: (senderId, atMs) => waGateway.noteMediaStanza(senderId, atMs),
|
|
30279
30464
|
onMessage: async (msg) => {
|
|
30280
30465
|
if (isObserveAccount(msg.accountId)) {
|
|
30281
30466
|
console.error(`[whatsapp:observe] op=BREACH account=${msg.accountId} sender=${msg.senderPhone} \u2014 dispatch reached the router from an observe socket`);
|
|
Binary file
|
|
Binary file
|