@rubytech/create-maxy-code 0.1.39 → 0.1.41
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/dist/index.js +35 -101
- package/dist/uninstall.js +11 -102
- package/package.json +1 -1
- package/payload/platform/lib/mcp-stderr-tee/package.json +7 -0
- package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +142 -52
- package/payload/platform/plugins/docs/references/deployment.md +7 -3
- package/payload/platform/plugins/docs/references/platform.md +18 -4
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts +17 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.js +37 -1
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts +54 -0
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +516 -0
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/hooks-discovery.d.ts +7 -0
- package/payload/platform/services/claude-session-manager/dist/hooks-discovery.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/hooks-discovery.js +90 -0
- package/payload/platform/services/claude-session-manager/dist/hooks-discovery.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +3 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +426 -204
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +60 -13
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js +11 -6
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +40 -45
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +146 -162
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.d.ts +24 -0
- package/payload/platform/services/claude-session-manager/dist/reaper.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/reaper.js +114 -0
- package/payload/platform/services/claude-session-manager/dist/reaper.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +16 -41
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.js +29 -68
- package/payload/platform/services/claude-session-manager/dist/session-store.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +54 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +111 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +4 -2
- package/payload/platform/templates/agents/admin/SOUL.md +8 -14
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.d.ts +51 -0
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.js +196 -0
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.js.map +1 -0
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/package.json +7 -0
- package/payload/server/{chunk-NNHHGQJL.js → chunk-4P2MPERV.js} +6 -163
- package/payload/server/{chunk-3G6JMV7N.js → chunk-MEGIQYUD.js} +1 -1
- package/payload/server/{chunk-QC5LHYPH.js → chunk-XJVJ2B2Q.js} +1 -1
- package/payload/server/{cloudflare-task-tracker-NEB3X6O2.js → cloudflare-task-tracker-R6MMORFU.js} +2 -2
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{Checkbox-CmCjmfJy.js → Checkbox-blxv2XPY.js} +1 -1
- package/payload/server/public/assets/{admin-CtodW6As.js → admin-C3EvHpLN.js} +1 -1
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-CfOjbx0H.js → architectureDiagram-Q4EWVU46-bETsVzGS.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-D0kA2C9i.js → blockDiagram-DXYQGD6D-5y0GBnfQ.js} +1 -1
- package/payload/server/public/assets/{brand-6hKfBgOI.css → brand-DQlLKLDH.css} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-CDgXiXgj.js → c4Diagram-AHTNJAMY-C1znpVGu.js} +1 -1
- package/payload/server/public/assets/channel-BWqTaXzb.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-BfBTcT5X.js → chunk-336JU56O-DlCVUGzQ.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-BdSSZ1Kw.js → chunk-426QAEUC-bGYkmA4z.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-UvE1im9g.js → chunk-4TB4RGXK-6Sb0Undm.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-Dx4DslXz.js → chunk-5FUZZQ4R-CAT_Pq2T.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-CNHawhLJ.js → chunk-5PVQY5BW-j231ESse.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-Rmjv-1et.js → chunk-EDXVE4YY-BAlS4QL7.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CL-LeH3r.js → chunk-ENJZ2VHE-D3Ku_BMP.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-B2MFOJDt.js → chunk-ICPOFSXX-AR86A5WB.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-BxcIcCt5.js → chunk-OYMX7WX6-DxGhapkW.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-COzwOBrY.js → chunk-U2HBQHQK-DAlqsDo_.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-B0rjT5hL.js → chunk-X2U36JSP-lZdkkdyB.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-pwrIw0XH.js → chunk-YZCP3GAM-CCjtXOFD.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-BXpeAm0k.js → chunk-ZZ45TVLE-ComtdKny.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-B9hC6fOR.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-afl_ydIG.js +1 -0
- package/payload/server/public/assets/clone-Lj83OaZ3.js +1 -0
- package/payload/server/public/assets/{dagre-DjOJn05k.js → dagre-DX8QJeVt.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-PczvQiEh.js → dagre-KV5264BT-BcRbiZ1q.js} +1 -1
- package/payload/server/public/assets/data-CS_R5VWQ.js +1 -0
- package/payload/server/public/assets/{device-url-actions-C55ZS_Zg.js → device-url-actions-Cag9IJ7n.js} +1 -1
- package/payload/server/public/assets/{diagram-5BDNPKRD-D477xo5F.js → diagram-5BDNPKRD-Bw47VwHO.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-C-HAtbtT.js → diagram-G4DWMVQ6-B-aVfhiD.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-B9wus4H0.js → diagram-MMDJMWI5-DERz2dKo.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-UMZLloFr.js → diagram-TYMM5635-B7eEX9Ta.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-B8E52Mp4.js → erDiagram-SMLLAGMA-G_JJq3zB.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-CUel34tq.js → flowDiagram-DWJPFMVM-BeY9QaO0.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BfwE-Xav.js → ganttDiagram-T4ZO3ILL-IXviGYnx.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-2d6tXRIM.js → gitGraphDiagram-UUTBAWPF-GlY6pZQ1.js} +1 -1
- package/payload/server/public/assets/graph-DtGkQBFr.js +1 -0
- package/payload/server/public/assets/graph-labels-Dwu1ON5v.js +1 -0
- package/payload/server/public/assets/{graphlib-CCo9-wNJ.js → graphlib-BWgAjZSb.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-DB1SVWL6.js → infoDiagram-42DDH7IO-D4YZM0gD.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CdeMUTYS.js → ishikawaDiagram-UXIWVN3A-fR71GDOp.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-D8x8TUoc.js → journeyDiagram-VCZTEJTY-C1TcY_6m.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-Cmkhd1mO.js → kanban-definition-6JOO6SKY-IPZKpnnG.js} +1 -1
- package/payload/server/public/assets/{line-CoHkpBv3.js → line-B2JtEL1B.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-C7R2PfZC.js → mermaid-parser.core-BKCLXqUh.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-lQaq-kll.js → mermaid.core-CmNpCLB-.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CsPq3pX1.js → mindmap-definition-QFDTVHPH-BS0E2FPD.js} +1 -1
- package/payload/server/public/assets/{page-DwEaAdVp.js → page-BdI3GCun.js} +1 -1
- package/payload/server/public/assets/{page-CBKzjUHO.js → page-zIjAkU2y.js} +1 -1
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-FZH9sTnP.js → pieDiagram-DEJITSTG-J61JDyk8.js} +1 -1
- package/payload/server/public/assets/{public-hGLzxhE_.js → public-C5vaQnC_.js} +4 -4
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-9poxfoqh.js → quadrantDiagram-34T5L4WZ-CLI03OtR.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-Ce7iOdT_.js → requirementDiagram-MS252O5E-C8MCt5NO.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DINuXatI.js → sankeyDiagram-XADWPNL6-DQuugruj.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CSMec3EK.js → sequenceDiagram-FGHM5R23-CoYitwhO.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-15DKzypj.js → stateDiagram-FHFEXIEX-G68cHHoI.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Dtgpgr_D.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BxIRc9Pu.js → timeline-definition-GMOUNBTQ-CelLeJVS.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DdgRyU55.js → vennDiagram-DHZGUBPP-BVgFPkJq.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-CKvhU1Uj.js → wardleyDiagram-NUSXRM2D-CzD_8hl2.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-d0V1V4PD.js → xychartDiagram-5P7HB3ND-BuTxWdX5.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +1058 -846
- package/payload/server/public/assets/channel-DA7MGMzQ.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DqfmcWSr.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BVOMSbZ_.js +0 -1
- package/payload/server/public/assets/clone-Byp7hagU.js +0 -1
- package/payload/server/public/assets/data-A7azIkZU.js +0 -1
- package/payload/server/public/assets/graph-Dx83EhtP.js +0 -1
- package/payload/server/public/assets/graph-labels-Bg2Bzelb.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-AcaK_QfD.js +0 -1
- /package/payload/server/public/assets/{brand-DXOx08r0.js → brand-D6mXeyxV.js} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
vncLog,
|
|
61
61
|
waitForExit,
|
|
62
62
|
walkPremiumBundles
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-MEGIQYUD.js";
|
|
64
64
|
import {
|
|
65
65
|
CLOUDFLARE_TASK_DIAGNOSTICS,
|
|
66
66
|
appendCloudflareSteps,
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
openCloudflareTask,
|
|
70
70
|
readTunnelState,
|
|
71
71
|
resolveUnitGoneVerdict
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-XJVJ2B2Q.js";
|
|
73
73
|
import {
|
|
74
74
|
GREETING_DIRECTIVE,
|
|
75
75
|
HAIKU_MODEL,
|
|
@@ -93,7 +93,7 @@ import {
|
|
|
93
93
|
verifyAndGetConversationUpdatedAt,
|
|
94
94
|
verifyConversationOwnership,
|
|
95
95
|
writeAdminUserAndPerson
|
|
96
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-4P2MPERV.js";
|
|
97
97
|
import {
|
|
98
98
|
__commonJS,
|
|
99
99
|
__toESM
|
|
@@ -596,14 +596,14 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
596
596
|
|
|
597
597
|
// server/index.ts
|
|
598
598
|
import { readFileSync as readFileSync20, existsSync as existsSync22, watchFile } from "fs";
|
|
599
|
-
import { resolve as resolve23, join as
|
|
599
|
+
import { resolve as resolve23, join as join13, basename as basename5 } from "path";
|
|
600
600
|
import { homedir as homedir4 } from "os";
|
|
601
601
|
|
|
602
602
|
// app/lib/agent-slug-pattern.ts
|
|
603
603
|
var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
604
604
|
|
|
605
605
|
// server/routes/health.ts
|
|
606
|
-
import { existsSync as
|
|
606
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
607
607
|
import { createConnection as createConnection2 } from "net";
|
|
608
608
|
|
|
609
609
|
// app/lib/claude-auth.ts
|
|
@@ -863,617 +863,254 @@ var WhatsAppConfigSchema = z.object({
|
|
|
863
863
|
});
|
|
864
864
|
});
|
|
865
865
|
|
|
866
|
-
// app/lib/whatsapp/
|
|
867
|
-
import
|
|
868
|
-
import
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
866
|
+
// app/lib/whatsapp/auth-store.ts
|
|
867
|
+
import fsSync from "fs";
|
|
868
|
+
import fs from "fs/promises";
|
|
869
|
+
import path from "path";
|
|
870
|
+
import os from "os";
|
|
871
|
+
var TAG = "[whatsapp:auth]";
|
|
872
|
+
function resolveAuthDir(configDir2, accountId) {
|
|
873
|
+
return path.join(os.homedir(), configDir2, "credentials", "whatsapp", accountId);
|
|
872
874
|
}
|
|
873
|
-
function
|
|
874
|
-
|
|
875
|
-
if (!existsSync2(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
876
|
-
return JSON.parse(readFileSync2(path2, "utf-8"));
|
|
875
|
+
function resolveCredsPath(authDir) {
|
|
876
|
+
return path.join(authDir, "creds.json");
|
|
877
877
|
}
|
|
878
|
-
function
|
|
879
|
-
|
|
880
|
-
writeFileSync2(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
878
|
+
function resolveCredsBackupPath(authDir) {
|
|
879
|
+
return path.join(authDir, "creds.json.bak");
|
|
881
880
|
}
|
|
882
|
-
function
|
|
881
|
+
function hasCredsSync(authDir) {
|
|
883
882
|
try {
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
console.error(`${TAG} manager config reload failed: ${String(err)}`);
|
|
883
|
+
const stats = fsSync.statSync(resolveCredsPath(authDir));
|
|
884
|
+
return stats.isFile() && stats.size > 1;
|
|
885
|
+
} catch {
|
|
886
|
+
return false;
|
|
889
887
|
}
|
|
890
888
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const remaining = [];
|
|
898
|
-
for (const entry of allowFrom) {
|
|
899
|
-
if (E164_PATTERN.test(entry)) {
|
|
900
|
-
phones.push(entry);
|
|
901
|
-
} else {
|
|
902
|
-
remaining.push(entry);
|
|
903
|
-
}
|
|
889
|
+
async function authExists(authDir) {
|
|
890
|
+
const credsPath = resolveCredsPath(authDir);
|
|
891
|
+
try {
|
|
892
|
+
await fs.access(authDir);
|
|
893
|
+
} catch {
|
|
894
|
+
return false;
|
|
904
895
|
}
|
|
905
|
-
if (phones.length === 0) return 0;
|
|
906
|
-
wa.adminPhones = phones;
|
|
907
|
-
wa.allowFrom = remaining;
|
|
908
|
-
console.error(`${TAG} migrated ${phones.length} phone(s) from allowFrom to adminPhones`);
|
|
909
|
-
return phones.length;
|
|
910
|
-
}
|
|
911
|
-
function persistAfterPairing(accountDir, accountId, selfPhone) {
|
|
912
896
|
try {
|
|
913
|
-
const
|
|
914
|
-
if (!
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
if (!wa.accounts || typeof wa.accounts !== "object") {
|
|
919
|
-
wa.accounts = {};
|
|
920
|
-
}
|
|
921
|
-
const accounts = wa.accounts;
|
|
922
|
-
if (!accounts[accountId] || typeof accounts[accountId] !== "object") {
|
|
923
|
-
accounts[accountId] = { name: "Main" };
|
|
924
|
-
}
|
|
925
|
-
if (selfPhone) {
|
|
926
|
-
const normalized = selfPhone.startsWith("+") ? selfPhone : `+${selfPhone}`;
|
|
927
|
-
if (!Array.isArray(wa.adminPhones)) {
|
|
928
|
-
wa.adminPhones = [];
|
|
929
|
-
}
|
|
930
|
-
const adminPhones = wa.adminPhones;
|
|
931
|
-
if (!adminPhones.includes(normalized)) {
|
|
932
|
-
adminPhones.push(normalized);
|
|
933
|
-
console.error(`${TAG} added selfPhone=${normalized} to adminPhones`);
|
|
934
|
-
}
|
|
935
|
-
} else {
|
|
936
|
-
console.error(`${TAG} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
|
|
937
|
-
}
|
|
938
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
939
|
-
if (!parsed.success) {
|
|
940
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
941
|
-
console.error(`${TAG} validation failed after pairing: ${msg}`);
|
|
942
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
943
|
-
}
|
|
944
|
-
config.whatsapp = parsed.data;
|
|
945
|
-
writeConfig(accountDir, config);
|
|
946
|
-
console.error(`${TAG} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
|
|
947
|
-
reloadManagerConfig(accountDir);
|
|
948
|
-
return { ok: true };
|
|
897
|
+
const stats = await fs.stat(credsPath);
|
|
898
|
+
if (!stats.isFile() || stats.size <= 1) return false;
|
|
899
|
+
const raw = await fs.readFile(credsPath, "utf-8");
|
|
900
|
+
JSON.parse(raw);
|
|
901
|
+
return true;
|
|
949
902
|
} catch (err) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
return { ok: false, error: msg };
|
|
903
|
+
console.warn(`${TAG} credential validation failed path=${credsPath}: ${String(err)}`);
|
|
904
|
+
return false;
|
|
953
905
|
}
|
|
954
906
|
}
|
|
955
|
-
function
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
907
|
+
function readCredsJsonRaw(filePath) {
|
|
908
|
+
try {
|
|
909
|
+
if (!fsSync.existsSync(filePath)) return null;
|
|
910
|
+
const stats = fsSync.statSync(filePath);
|
|
911
|
+
if (!stats.isFile() || stats.size <= 1) return null;
|
|
912
|
+
return fsSync.readFileSync(filePath, "utf-8");
|
|
913
|
+
} catch {
|
|
914
|
+
return null;
|
|
959
915
|
}
|
|
916
|
+
}
|
|
917
|
+
function maybeRestoreCredsFromBackup(authDir) {
|
|
960
918
|
try {
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
if (!Array.isArray(wa.adminPhones)) {
|
|
968
|
-
wa.adminPhones = [];
|
|
969
|
-
}
|
|
970
|
-
const adminPhones = wa.adminPhones;
|
|
971
|
-
if (adminPhones.includes(normalized)) {
|
|
972
|
-
return { ok: true, message: `Phone ${normalized} is already in the admin list.` };
|
|
973
|
-
}
|
|
974
|
-
adminPhones.push(normalized);
|
|
975
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
976
|
-
if (!parsed.success) {
|
|
977
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
978
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
919
|
+
const credsPath = resolveCredsPath(authDir);
|
|
920
|
+
const backupPath = resolveCredsBackupPath(authDir);
|
|
921
|
+
const raw = readCredsJsonRaw(credsPath);
|
|
922
|
+
if (raw) {
|
|
923
|
+
JSON.parse(raw);
|
|
924
|
+
return;
|
|
979
925
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
926
|
+
const backupRaw = readCredsJsonRaw(backupPath);
|
|
927
|
+
if (!backupRaw) return;
|
|
928
|
+
JSON.parse(backupRaw);
|
|
929
|
+
fsSync.copyFileSync(backupPath, credsPath);
|
|
930
|
+
console.error(`${TAG} restored corrupted creds.json from backup in ${authDir}`);
|
|
985
931
|
} catch (err) {
|
|
986
|
-
|
|
987
|
-
console.error(`${TAG} addAdminPhone failed: ${msg}`);
|
|
988
|
-
return { ok: false, error: msg };
|
|
932
|
+
console.warn(`${TAG} credential restore failed authDir=${authDir}: ${String(err)}`);
|
|
989
933
|
}
|
|
990
934
|
}
|
|
991
|
-
function
|
|
992
|
-
const normalized = phone.trim();
|
|
935
|
+
function readSelfId(authDir) {
|
|
993
936
|
try {
|
|
994
|
-
const
|
|
995
|
-
if (!
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const idx = adminPhones.indexOf(normalized);
|
|
1005
|
-
if (idx === -1) {
|
|
1006
|
-
return { ok: true, message: `Phone ${normalized} is not in the admin list.` };
|
|
937
|
+
const credsPath = resolveCredsPath(authDir);
|
|
938
|
+
if (!fsSync.existsSync(credsPath)) return { e164: null, jid: null, lid: null };
|
|
939
|
+
const raw = fsSync.readFileSync(credsPath, "utf-8");
|
|
940
|
+
const parsed = JSON.parse(raw);
|
|
941
|
+
const jid = parsed?.me?.id ?? null;
|
|
942
|
+
const lidRaw = parsed?.me?.lid ?? null;
|
|
943
|
+
let e164 = null;
|
|
944
|
+
if (jid) {
|
|
945
|
+
const match = jid.match(/^(\d+)(?::\d+)?@s\.whatsapp\.net$/i);
|
|
946
|
+
if (match) e164 = match[1];
|
|
1007
947
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
948
|
+
let lid = null;
|
|
949
|
+
if (lidRaw) {
|
|
950
|
+
const lidMatch = lidRaw.match(/^(\d+)(?::\d+)?@lid$/i);
|
|
951
|
+
if (lidMatch) lid = `${lidMatch[1]}@lid`;
|
|
1013
952
|
}
|
|
1014
|
-
|
|
1015
|
-
writeConfig(accountDir, config);
|
|
1016
|
-
console.error(`${TAG} removed admin phone=${normalized}`);
|
|
1017
|
-
reloadManagerConfig(accountDir);
|
|
1018
|
-
return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
|
|
953
|
+
return { e164, jid, lid };
|
|
1019
954
|
} catch (err) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
return { ok: false, error: msg };
|
|
955
|
+
console.warn(`${TAG} readSelfId failed authDir=${authDir}: ${String(err)}`);
|
|
956
|
+
return { e164: null, jid: null, lid: null };
|
|
1023
957
|
}
|
|
1024
958
|
}
|
|
1025
|
-
function
|
|
959
|
+
async function clearAuth(authDir) {
|
|
1026
960
|
try {
|
|
1027
|
-
|
|
1028
|
-
const wa = config.whatsapp;
|
|
1029
|
-
if (!wa) return [];
|
|
1030
|
-
const migrated = migrateAdminPhones(wa);
|
|
1031
|
-
if (migrated > 0) {
|
|
1032
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
1033
|
-
if (parsed.success) {
|
|
1034
|
-
config.whatsapp = parsed.data;
|
|
1035
|
-
writeConfig(accountDir, config);
|
|
1036
|
-
reloadManagerConfig(accountDir);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
if (!Array.isArray(wa.adminPhones)) return [];
|
|
1040
|
-
return wa.adminPhones.filter((p) => typeof p === "string");
|
|
961
|
+
await fs.access(authDir);
|
|
1041
962
|
} catch {
|
|
1042
|
-
|
|
963
|
+
console.error(`${TAG} no auth dir found at ${authDir}`);
|
|
964
|
+
return false;
|
|
1043
965
|
}
|
|
966
|
+
await fs.rm(authDir, { recursive: true, force: true });
|
|
967
|
+
console.error(`${TAG} cleared credentials at ${authDir}`);
|
|
968
|
+
return true;
|
|
1044
969
|
}
|
|
1045
|
-
function
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
970
|
+
function listCredentialAccountIds(configDir2) {
|
|
971
|
+
const credsRoot = path.join(os.homedir(), configDir2, "credentials", "whatsapp");
|
|
972
|
+
try {
|
|
973
|
+
const entries = fsSync.readdirSync(credsRoot, { withFileTypes: true });
|
|
974
|
+
return entries.filter((e) => e.isDirectory() && hasCredsSync(path.join(credsRoot, e.name))).map((e) => e.name);
|
|
975
|
+
} catch {
|
|
976
|
+
return [];
|
|
1049
977
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// app/lib/whatsapp/session.ts
|
|
981
|
+
import { randomUUID } from "crypto";
|
|
982
|
+
import fsSync2 from "fs";
|
|
983
|
+
import fs2 from "fs/promises";
|
|
984
|
+
import { inspect } from "util";
|
|
985
|
+
import {
|
|
986
|
+
Browsers,
|
|
987
|
+
DisconnectReason,
|
|
988
|
+
fetchLatestBaileysVersion,
|
|
989
|
+
makeCacheableSignalKeyStore,
|
|
990
|
+
makeWASocket,
|
|
991
|
+
useMultiFileAuthState
|
|
992
|
+
} from "@whiskeysockets/baileys";
|
|
993
|
+
import { createRequire } from "module";
|
|
994
|
+
var TAG2 = "[whatsapp:session]";
|
|
995
|
+
var baileysPkgVersion = (() => {
|
|
996
|
+
try {
|
|
997
|
+
const req = createRequire(import.meta.url);
|
|
998
|
+
return req("@whiskeysockets/baileys/package.json").version ?? "unknown";
|
|
999
|
+
} catch {
|
|
1000
|
+
return "unknown";
|
|
1053
1001
|
}
|
|
1002
|
+
})();
|
|
1003
|
+
console.error(`${TAG2} baileys package loaded version=${baileysPkgVersion}`);
|
|
1004
|
+
var __BAILEYS_TAG = "[whatsapp:baileys]";
|
|
1005
|
+
var INSPECT_OPTS = {
|
|
1006
|
+
depth: null,
|
|
1007
|
+
breakLength: Infinity,
|
|
1008
|
+
compact: true,
|
|
1009
|
+
maxArrayLength: 100,
|
|
1010
|
+
maxStringLength: 1e4
|
|
1011
|
+
};
|
|
1012
|
+
function serializeArg(arg) {
|
|
1013
|
+
if (typeof arg === "string") return arg;
|
|
1054
1014
|
try {
|
|
1055
|
-
|
|
1056
|
-
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
1057
|
-
config.whatsapp = {};
|
|
1058
|
-
}
|
|
1059
|
-
const wa = config.whatsapp;
|
|
1060
|
-
wa.publicAgent = trimmed;
|
|
1061
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
1062
|
-
if (!parsed.success) {
|
|
1063
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1064
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
1065
|
-
}
|
|
1066
|
-
config.whatsapp = parsed.data;
|
|
1067
|
-
writeConfig(accountDir, config);
|
|
1068
|
-
console.error(`${TAG} publicAgent set to ${trimmed}`);
|
|
1069
|
-
reloadManagerConfig(accountDir);
|
|
1070
|
-
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
1015
|
+
return inspect(arg, INSPECT_OPTS);
|
|
1071
1016
|
} catch (err) {
|
|
1072
|
-
|
|
1073
|
-
console.error(`${TAG} setPublicAgent failed: ${msg}`);
|
|
1074
|
-
return { ok: false, error: msg };
|
|
1017
|
+
return `[inspect-failed: ${String(err)}]`;
|
|
1075
1018
|
}
|
|
1076
1019
|
}
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
if (opts.groupJid) {
|
|
1083
|
-
const accounts2 = wa.accounts;
|
|
1084
|
-
const account2 = accounts2?.[opts.accountId];
|
|
1085
|
-
const groups = account2?.groups;
|
|
1086
|
-
const group = groups?.[opts.groupJid];
|
|
1087
|
-
const groupSlug = group?.publicAgent;
|
|
1088
|
-
if (typeof groupSlug === "string" && groupSlug.trim()) {
|
|
1089
|
-
return { slug: groupSlug.trim(), source: "group" };
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
const accounts = wa.accounts;
|
|
1093
|
-
const account = accounts?.[opts.accountId];
|
|
1094
|
-
const accountSlug = account?.publicAgent;
|
|
1095
|
-
if (typeof accountSlug === "string" && accountSlug.trim()) {
|
|
1096
|
-
return { slug: accountSlug.trim(), source: "account" };
|
|
1097
|
-
}
|
|
1098
|
-
const topSlug = wa.publicAgent;
|
|
1099
|
-
if (typeof topSlug === "string" && topSlug.trim()) {
|
|
1100
|
-
return { slug: topSlug.trim(), source: "top-level" };
|
|
1101
|
-
}
|
|
1102
|
-
return null;
|
|
1103
|
-
} catch {
|
|
1104
|
-
return null;
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
function setGroupPublicAgent(accountDir, accountId, groupJid, slug) {
|
|
1108
|
-
const trimmedSlug = slug.trim();
|
|
1109
|
-
const trimmedGroup = groupJid.trim();
|
|
1110
|
-
const trimmedAccount = accountId.trim();
|
|
1111
|
-
if (!trimmedSlug) return { ok: false, error: "Agent slug cannot be empty." };
|
|
1112
|
-
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
1113
|
-
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
1114
|
-
const agentConfigPath = join2(accountDir, "agents", trimmedSlug, "config.json");
|
|
1115
|
-
if (!existsSync2(agentConfigPath)) {
|
|
1116
|
-
return { ok: false, error: `Agent "${trimmedSlug}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
1117
|
-
}
|
|
1118
|
-
try {
|
|
1119
|
-
const config = readConfig(accountDir);
|
|
1120
|
-
if (!config.whatsapp || typeof config.whatsapp !== "object") config.whatsapp = {};
|
|
1121
|
-
const wa = config.whatsapp;
|
|
1122
|
-
if (!wa.accounts || typeof wa.accounts !== "object") wa.accounts = {};
|
|
1123
|
-
const accounts = wa.accounts;
|
|
1124
|
-
if (!accounts[trimmedAccount] || typeof accounts[trimmedAccount] !== "object") accounts[trimmedAccount] = {};
|
|
1125
|
-
const account = accounts[trimmedAccount];
|
|
1126
|
-
if (!account.groups || typeof account.groups !== "object") account.groups = {};
|
|
1127
|
-
const groups = account.groups;
|
|
1128
|
-
const existing = groups[trimmedGroup] && typeof groups[trimmedGroup] === "object" ? groups[trimmedGroup] : {};
|
|
1129
|
-
groups[trimmedGroup] = { ...existing, publicAgent: trimmedSlug };
|
|
1130
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
1131
|
-
if (!parsed.success) {
|
|
1132
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1133
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
1134
|
-
}
|
|
1135
|
-
config.whatsapp = parsed.data;
|
|
1136
|
-
writeConfig(accountDir, config);
|
|
1137
|
-
console.error(`${TAG} setGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup} slug=${trimmedSlug}`);
|
|
1138
|
-
reloadManagerConfig(accountDir);
|
|
1139
|
-
return { ok: true, message: `Per-group public agent set to "${trimmedSlug}" for group ${trimmedGroup}.` };
|
|
1140
|
-
} catch (err) {
|
|
1141
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1142
|
-
console.error(`${TAG} setGroupPublicAgent failed: ${msg}`);
|
|
1143
|
-
return { ok: false, error: msg };
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
function unsetGroupPublicAgent(accountDir, accountId, groupJid) {
|
|
1147
|
-
const trimmedGroup = groupJid.trim();
|
|
1148
|
-
const trimmedAccount = accountId.trim();
|
|
1149
|
-
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
1150
|
-
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
1151
|
-
try {
|
|
1152
|
-
const config = readConfig(accountDir);
|
|
1153
|
-
const wa = config.whatsapp ?? {};
|
|
1154
|
-
const accounts = wa.accounts;
|
|
1155
|
-
const account = accounts?.[trimmedAccount];
|
|
1156
|
-
const groups = account?.groups;
|
|
1157
|
-
const group = groups?.[trimmedGroup];
|
|
1158
|
-
if (!group || typeof group.publicAgent !== "string") {
|
|
1159
|
-
return { ok: true, message: `No per-group publicAgent override for group ${trimmedGroup}; nothing to remove.` };
|
|
1160
|
-
}
|
|
1161
|
-
delete group.publicAgent;
|
|
1162
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
1163
|
-
if (!parsed.success) {
|
|
1164
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1165
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
1166
|
-
}
|
|
1167
|
-
config.whatsapp = parsed.data;
|
|
1168
|
-
writeConfig(accountDir, config);
|
|
1169
|
-
console.error(`${TAG} unsetGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup}`);
|
|
1170
|
-
reloadManagerConfig(accountDir);
|
|
1171
|
-
return { ok: true, message: `Per-group public agent override removed for group ${trimmedGroup}.` };
|
|
1172
|
-
} catch (err) {
|
|
1173
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1174
|
-
console.error(`${TAG} unsetGroupPublicAgent failed: ${msg}`);
|
|
1175
|
-
return { ok: false, error: msg };
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
function updateConfig(accountDir, fields) {
|
|
1179
|
-
const fieldNames = Object.keys(fields);
|
|
1180
|
-
if (fieldNames.length === 0) {
|
|
1181
|
-
return { ok: false, error: "No fields provided to update." };
|
|
1182
|
-
}
|
|
1183
|
-
try {
|
|
1184
|
-
const config = readConfig(accountDir);
|
|
1185
|
-
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
1186
|
-
config.whatsapp = {};
|
|
1187
|
-
}
|
|
1188
|
-
const wa = config.whatsapp;
|
|
1189
|
-
migrateAdminPhones(wa);
|
|
1190
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
1191
|
-
wa[key] = value;
|
|
1192
|
-
}
|
|
1193
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
1194
|
-
if (!parsed.success) {
|
|
1195
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1196
|
-
console.error(`${TAG} update validation failed: ${msg}`);
|
|
1197
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
1198
|
-
}
|
|
1199
|
-
config.whatsapp = parsed.data;
|
|
1200
|
-
writeConfig(accountDir, config);
|
|
1201
|
-
console.error(`${TAG} updated fields=[${fieldNames.join(",")}]`);
|
|
1202
|
-
reloadManagerConfig(accountDir);
|
|
1203
|
-
return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
|
|
1204
|
-
} catch (err) {
|
|
1205
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1206
|
-
console.error(`${TAG} updateConfig failed: ${msg}`);
|
|
1207
|
-
return { ok: false, error: msg };
|
|
1020
|
+
function bindingsToPrefix(bindings) {
|
|
1021
|
+
const parts = [];
|
|
1022
|
+
for (const [key, value] of Object.entries(bindings)) {
|
|
1023
|
+
const rendered = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : serializeArg(value);
|
|
1024
|
+
parts.push(`${key}=${rendered}`);
|
|
1208
1025
|
}
|
|
1026
|
+
return parts.join(" ");
|
|
1209
1027
|
}
|
|
1210
|
-
function
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
const
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
if (migrated > 0) {
|
|
1219
|
-
config.whatsapp = parsed.data;
|
|
1220
|
-
writeConfig(accountDir, config);
|
|
1221
|
-
reloadManagerConfig(accountDir);
|
|
1028
|
+
function createBaileysLogger(bindings = {}) {
|
|
1029
|
+
const prefix = Object.keys(bindings).length > 0 ? bindingsToPrefix(bindings) : "";
|
|
1030
|
+
const emit = (level, args) => {
|
|
1031
|
+
const serialized = args.map(serializeArg).join(" ");
|
|
1032
|
+
if (prefix) {
|
|
1033
|
+
console.error(__BAILEYS_TAG, level, prefix, serialized);
|
|
1034
|
+
} else {
|
|
1035
|
+
console.error(__BAILEYS_TAG, level, serialized);
|
|
1222
1036
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
}
|
|
1238
|
-
function resolveCredsPath(authDir) {
|
|
1239
|
-
return path.join(authDir, "creds.json");
|
|
1240
|
-
}
|
|
1241
|
-
function resolveCredsBackupPath(authDir) {
|
|
1242
|
-
return path.join(authDir, "creds.json.bak");
|
|
1243
|
-
}
|
|
1244
|
-
function hasCredsSync(authDir) {
|
|
1245
|
-
try {
|
|
1246
|
-
const stats = fsSync.statSync(resolveCredsPath(authDir));
|
|
1247
|
-
return stats.isFile() && stats.size > 1;
|
|
1248
|
-
} catch {
|
|
1249
|
-
return false;
|
|
1250
|
-
}
|
|
1037
|
+
};
|
|
1038
|
+
return {
|
|
1039
|
+
level: "warn",
|
|
1040
|
+
trace: () => {
|
|
1041
|
+
},
|
|
1042
|
+
debug: () => {
|
|
1043
|
+
},
|
|
1044
|
+
info: () => {
|
|
1045
|
+
},
|
|
1046
|
+
warn: (...args) => emit("WARN", args),
|
|
1047
|
+
error: (...args) => emit("ERROR", args),
|
|
1048
|
+
fatal: (...args) => emit("FATAL", args),
|
|
1049
|
+
child: (childBindings) => createBaileysLogger({ ...bindings, ...childBindings ?? {} })
|
|
1050
|
+
};
|
|
1251
1051
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
console.warn(`${TAG2} credential validation failed path=${credsPath}: ${String(err)}`);
|
|
1267
|
-
return false;
|
|
1052
|
+
var credsSaveQueue = Promise.resolve();
|
|
1053
|
+
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
1054
|
+
console.error(`${TAG2} draining credential save queue\u2026`);
|
|
1055
|
+
const timer2 = new Promise(
|
|
1056
|
+
(resolve24) => setTimeout(() => resolve24("timeout"), timeoutMs)
|
|
1057
|
+
);
|
|
1058
|
+
const result = await Promise.race([
|
|
1059
|
+
credsSaveQueue.then(() => "drained"),
|
|
1060
|
+
timer2
|
|
1061
|
+
]);
|
|
1062
|
+
if (result === "timeout") {
|
|
1063
|
+
console.error(`${TAG2} credential save queue drain timed out after ${timeoutMs}ms`);
|
|
1064
|
+
} else {
|
|
1065
|
+
console.error(`${TAG2} credential save queue drained`);
|
|
1268
1066
|
}
|
|
1269
1067
|
}
|
|
1270
|
-
function
|
|
1068
|
+
function readCredsJsonRaw2(filePath) {
|
|
1271
1069
|
try {
|
|
1272
|
-
if (!
|
|
1273
|
-
const stats =
|
|
1070
|
+
if (!fsSync2.existsSync(filePath)) return null;
|
|
1071
|
+
const stats = fsSync2.statSync(filePath);
|
|
1274
1072
|
if (!stats.isFile() || stats.size <= 1) return null;
|
|
1275
|
-
return
|
|
1073
|
+
return fsSync2.readFileSync(filePath, "utf-8");
|
|
1276
1074
|
} catch {
|
|
1277
1075
|
return null;
|
|
1278
1076
|
}
|
|
1279
1077
|
}
|
|
1280
|
-
function
|
|
1078
|
+
async function safeSaveCreds(authDir, saveCreds) {
|
|
1281
1079
|
try {
|
|
1282
1080
|
const credsPath = resolveCredsPath(authDir);
|
|
1283
1081
|
const backupPath = resolveCredsBackupPath(authDir);
|
|
1284
|
-
const raw =
|
|
1082
|
+
const raw = readCredsJsonRaw2(credsPath);
|
|
1285
1083
|
if (raw) {
|
|
1286
|
-
|
|
1287
|
-
|
|
1084
|
+
try {
|
|
1085
|
+
JSON.parse(raw);
|
|
1086
|
+
fsSync2.copyFileSync(credsPath, backupPath);
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
console.warn(`${TAG2} backup pre-copy failed (parse or write) authDir=${authDir}: ${String(err)}`);
|
|
1089
|
+
}
|
|
1288
1090
|
}
|
|
1289
|
-
const backupRaw = readCredsJsonRaw(backupPath);
|
|
1290
|
-
if (!backupRaw) return;
|
|
1291
|
-
JSON.parse(backupRaw);
|
|
1292
|
-
fsSync.copyFileSync(backupPath, credsPath);
|
|
1293
|
-
console.error(`${TAG2} restored corrupted creds.json from backup in ${authDir}`);
|
|
1294
1091
|
} catch (err) {
|
|
1295
|
-
console.warn(`${TAG2}
|
|
1092
|
+
console.warn(`${TAG2} backup preparation failed authDir=${authDir}: ${String(err)}`);
|
|
1296
1093
|
}
|
|
1297
|
-
}
|
|
1298
|
-
function readSelfId(authDir) {
|
|
1299
1094
|
try {
|
|
1095
|
+
await Promise.resolve(saveCreds());
|
|
1300
1096
|
const credsPath = resolveCredsPath(authDir);
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1311
|
-
let lid = null;
|
|
1312
|
-
if (lidRaw) {
|
|
1313
|
-
const lidMatch = lidRaw.match(/^(\d+)(?::\d+)?@lid$/i);
|
|
1314
|
-
if (lidMatch) lid = `${lidMatch[1]}@lid`;
|
|
1097
|
+
try {
|
|
1098
|
+
const stats = await fs2.stat(credsPath);
|
|
1099
|
+
if (stats.size <= 1) {
|
|
1100
|
+
console.error(`${TAG2} WARNING: creds file empty/truncated after save path=${credsPath} size=${stats.size}`);
|
|
1101
|
+
} else {
|
|
1102
|
+
console.error(`${TAG2} creds saved and verified path=${credsPath} size=${stats.size}`);
|
|
1103
|
+
}
|
|
1104
|
+
} catch (statErr) {
|
|
1105
|
+
console.error(`${TAG2} creds NOT found on disk after save path=${credsPath}: ${String(statErr)}`);
|
|
1315
1106
|
}
|
|
1316
|
-
return { e164, jid, lid };
|
|
1317
1107
|
} catch (err) {
|
|
1318
|
-
console.
|
|
1319
|
-
return { e164: null, jid: null, lid: null };
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
async function clearAuth(authDir) {
|
|
1323
|
-
try {
|
|
1324
|
-
await fs.access(authDir);
|
|
1325
|
-
} catch {
|
|
1326
|
-
console.error(`${TAG2} no auth dir found at ${authDir}`);
|
|
1327
|
-
return false;
|
|
1328
|
-
}
|
|
1329
|
-
await fs.rm(authDir, { recursive: true, force: true });
|
|
1330
|
-
console.error(`${TAG2} cleared credentials at ${authDir}`);
|
|
1331
|
-
return true;
|
|
1332
|
-
}
|
|
1333
|
-
function listCredentialAccountIds(configDir2) {
|
|
1334
|
-
const credsRoot = path.join(os.homedir(), configDir2, "credentials", "whatsapp");
|
|
1335
|
-
try {
|
|
1336
|
-
const entries = fsSync.readdirSync(credsRoot, { withFileTypes: true });
|
|
1337
|
-
return entries.filter((e) => e.isDirectory() && hasCredsSync(path.join(credsRoot, e.name))).map((e) => e.name);
|
|
1338
|
-
} catch {
|
|
1339
|
-
return [];
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// app/lib/whatsapp/session.ts
|
|
1344
|
-
import { randomUUID } from "crypto";
|
|
1345
|
-
import fsSync2 from "fs";
|
|
1346
|
-
import fs2 from "fs/promises";
|
|
1347
|
-
import { inspect } from "util";
|
|
1348
|
-
import {
|
|
1349
|
-
Browsers,
|
|
1350
|
-
DisconnectReason,
|
|
1351
|
-
fetchLatestBaileysVersion,
|
|
1352
|
-
makeCacheableSignalKeyStore,
|
|
1353
|
-
makeWASocket,
|
|
1354
|
-
useMultiFileAuthState
|
|
1355
|
-
} from "@whiskeysockets/baileys";
|
|
1356
|
-
import { createRequire } from "module";
|
|
1357
|
-
var TAG3 = "[whatsapp:session]";
|
|
1358
|
-
var baileysPkgVersion = (() => {
|
|
1359
|
-
try {
|
|
1360
|
-
const req = createRequire(import.meta.url);
|
|
1361
|
-
return req("@whiskeysockets/baileys/package.json").version ?? "unknown";
|
|
1362
|
-
} catch {
|
|
1363
|
-
return "unknown";
|
|
1364
|
-
}
|
|
1365
|
-
})();
|
|
1366
|
-
console.error(`${TAG3} baileys package loaded version=${baileysPkgVersion}`);
|
|
1367
|
-
var __BAILEYS_TAG = "[whatsapp:baileys]";
|
|
1368
|
-
var INSPECT_OPTS = {
|
|
1369
|
-
depth: null,
|
|
1370
|
-
breakLength: Infinity,
|
|
1371
|
-
compact: true,
|
|
1372
|
-
maxArrayLength: 100,
|
|
1373
|
-
maxStringLength: 1e4
|
|
1374
|
-
};
|
|
1375
|
-
function serializeArg(arg) {
|
|
1376
|
-
if (typeof arg === "string") return arg;
|
|
1377
|
-
try {
|
|
1378
|
-
return inspect(arg, INSPECT_OPTS);
|
|
1379
|
-
} catch (err) {
|
|
1380
|
-
return `[inspect-failed: ${String(err)}]`;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
function bindingsToPrefix(bindings) {
|
|
1384
|
-
const parts = [];
|
|
1385
|
-
for (const [key, value] of Object.entries(bindings)) {
|
|
1386
|
-
const rendered = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : serializeArg(value);
|
|
1387
|
-
parts.push(`${key}=${rendered}`);
|
|
1388
|
-
}
|
|
1389
|
-
return parts.join(" ");
|
|
1390
|
-
}
|
|
1391
|
-
function createBaileysLogger(bindings = {}) {
|
|
1392
|
-
const prefix = Object.keys(bindings).length > 0 ? bindingsToPrefix(bindings) : "";
|
|
1393
|
-
const emit = (level, args) => {
|
|
1394
|
-
const serialized = args.map(serializeArg).join(" ");
|
|
1395
|
-
if (prefix) {
|
|
1396
|
-
console.error(__BAILEYS_TAG, level, prefix, serialized);
|
|
1397
|
-
} else {
|
|
1398
|
-
console.error(__BAILEYS_TAG, level, serialized);
|
|
1399
|
-
}
|
|
1400
|
-
};
|
|
1401
|
-
return {
|
|
1402
|
-
level: "warn",
|
|
1403
|
-
trace: () => {
|
|
1404
|
-
},
|
|
1405
|
-
debug: () => {
|
|
1406
|
-
},
|
|
1407
|
-
info: () => {
|
|
1408
|
-
},
|
|
1409
|
-
warn: (...args) => emit("WARN", args),
|
|
1410
|
-
error: (...args) => emit("ERROR", args),
|
|
1411
|
-
fatal: (...args) => emit("FATAL", args),
|
|
1412
|
-
child: (childBindings) => createBaileysLogger({ ...bindings, ...childBindings ?? {} })
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
var credsSaveQueue = Promise.resolve();
|
|
1416
|
-
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
1417
|
-
console.error(`${TAG3} draining credential save queue\u2026`);
|
|
1418
|
-
const timer2 = new Promise(
|
|
1419
|
-
(resolve24) => setTimeout(() => resolve24("timeout"), timeoutMs)
|
|
1420
|
-
);
|
|
1421
|
-
const result = await Promise.race([
|
|
1422
|
-
credsSaveQueue.then(() => "drained"),
|
|
1423
|
-
timer2
|
|
1424
|
-
]);
|
|
1425
|
-
if (result === "timeout") {
|
|
1426
|
-
console.error(`${TAG3} credential save queue drain timed out after ${timeoutMs}ms`);
|
|
1427
|
-
} else {
|
|
1428
|
-
console.error(`${TAG3} credential save queue drained`);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
function readCredsJsonRaw2(filePath) {
|
|
1432
|
-
try {
|
|
1433
|
-
if (!fsSync2.existsSync(filePath)) return null;
|
|
1434
|
-
const stats = fsSync2.statSync(filePath);
|
|
1435
|
-
if (!stats.isFile() || stats.size <= 1) return null;
|
|
1436
|
-
return fsSync2.readFileSync(filePath, "utf-8");
|
|
1437
|
-
} catch {
|
|
1438
|
-
return null;
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
async function safeSaveCreds(authDir, saveCreds) {
|
|
1442
|
-
try {
|
|
1443
|
-
const credsPath = resolveCredsPath(authDir);
|
|
1444
|
-
const backupPath = resolveCredsBackupPath(authDir);
|
|
1445
|
-
const raw = readCredsJsonRaw2(credsPath);
|
|
1446
|
-
if (raw) {
|
|
1447
|
-
try {
|
|
1448
|
-
JSON.parse(raw);
|
|
1449
|
-
fsSync2.copyFileSync(credsPath, backupPath);
|
|
1450
|
-
} catch (err) {
|
|
1451
|
-
console.warn(`${TAG3} backup pre-copy failed (parse or write) authDir=${authDir}: ${String(err)}`);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
} catch (err) {
|
|
1455
|
-
console.warn(`${TAG3} backup preparation failed authDir=${authDir}: ${String(err)}`);
|
|
1456
|
-
}
|
|
1457
|
-
try {
|
|
1458
|
-
await Promise.resolve(saveCreds());
|
|
1459
|
-
const credsPath = resolveCredsPath(authDir);
|
|
1460
|
-
try {
|
|
1461
|
-
const stats = await fs2.stat(credsPath);
|
|
1462
|
-
if (stats.size <= 1) {
|
|
1463
|
-
console.error(`${TAG3} WARNING: creds file empty/truncated after save path=${credsPath} size=${stats.size}`);
|
|
1464
|
-
} else {
|
|
1465
|
-
console.error(`${TAG3} creds saved and verified path=${credsPath} size=${stats.size}`);
|
|
1466
|
-
}
|
|
1467
|
-
} catch (statErr) {
|
|
1468
|
-
console.error(`${TAG3} creds NOT found on disk after save path=${credsPath}: ${String(statErr)}`);
|
|
1469
|
-
}
|
|
1470
|
-
} catch (err) {
|
|
1471
|
-
console.error(`${TAG3} failed saving creds to ${authDir}: ${String(err)}`);
|
|
1108
|
+
console.error(`${TAG2} failed saving creds to ${authDir}: ${String(err)}`);
|
|
1472
1109
|
}
|
|
1473
1110
|
}
|
|
1474
1111
|
function enqueueSaveCreds(authDir, saveCreds) {
|
|
1475
1112
|
credsSaveQueue = credsSaveQueue.then(() => safeSaveCreds(authDir, saveCreds)).catch((err) => {
|
|
1476
|
-
console.error(`${
|
|
1113
|
+
console.error(`${TAG2} creds save queue error: ${String(err)}`);
|
|
1477
1114
|
});
|
|
1478
1115
|
}
|
|
1479
1116
|
async function createWaSocket(opts) {
|
|
@@ -1483,7 +1120,7 @@ async function createWaSocket(opts) {
|
|
|
1483
1120
|
const { state, saveCreds } = await useMultiFileAuthState(authDir);
|
|
1484
1121
|
const { version } = await fetchLatestBaileysVersion();
|
|
1485
1122
|
console.error(
|
|
1486
|
-
`${
|
|
1123
|
+
`${TAG2} creating socket authDir=${authDir} baileysVersion=${baileysPkgVersion} waProtocolVersion=${version.join(".")}`
|
|
1487
1124
|
);
|
|
1488
1125
|
const baileysLogger = createBaileysLogger();
|
|
1489
1126
|
const sock = makeWASocket({
|
|
@@ -1500,7 +1137,7 @@ async function createWaSocket(opts) {
|
|
|
1500
1137
|
fireInitQueries: false
|
|
1501
1138
|
});
|
|
1502
1139
|
sock.ev.on("creds.update", () => {
|
|
1503
|
-
console.error(`${
|
|
1140
|
+
console.error(`${TAG2} creds.update received \u2014 saving to ${authDir}`);
|
|
1504
1141
|
enqueueSaveCreds(authDir, saveCreds);
|
|
1505
1142
|
});
|
|
1506
1143
|
let qrSequence = 0;
|
|
@@ -1517,7 +1154,7 @@ async function createWaSocket(opts) {
|
|
|
1517
1154
|
parts.push(`disconnectReason=${formatError(update.lastDisconnect.error)}`);
|
|
1518
1155
|
}
|
|
1519
1156
|
if (!silent && parts.length > 0) {
|
|
1520
|
-
console.error(`${
|
|
1157
|
+
console.error(`${TAG2} connection.update ${parts.join(" ")}`);
|
|
1521
1158
|
}
|
|
1522
1159
|
if (update.qr && onQr) {
|
|
1523
1160
|
onQr(update.qr);
|
|
@@ -1525,20 +1162,20 @@ async function createWaSocket(opts) {
|
|
|
1525
1162
|
if (update.connection === "close") {
|
|
1526
1163
|
const status = getStatusCode(update.lastDisconnect?.error);
|
|
1527
1164
|
if (status === DisconnectReason.loggedOut && !silent) {
|
|
1528
|
-
console.error(`${
|
|
1165
|
+
console.error(`${TAG2} session logged out (401) \u2014 re-link required`);
|
|
1529
1166
|
}
|
|
1530
1167
|
}
|
|
1531
1168
|
if (update.connection === "open" && !silent) {
|
|
1532
|
-
console.error(`${
|
|
1169
|
+
console.error(`${TAG2} connected`);
|
|
1533
1170
|
}
|
|
1534
1171
|
onConnectionUpdate?.(update);
|
|
1535
1172
|
} catch (err) {
|
|
1536
|
-
console.error(`${
|
|
1173
|
+
console.error(`${TAG2} connection.update handler error: ${String(err)}`);
|
|
1537
1174
|
}
|
|
1538
1175
|
});
|
|
1539
1176
|
if (sock.ws && typeof sock.ws.on === "function") {
|
|
1540
1177
|
sock.ws.on("error", (err) => {
|
|
1541
|
-
console.error(`${
|
|
1178
|
+
console.error(`${TAG2} WebSocket error: ${String(err)}`);
|
|
1542
1179
|
});
|
|
1543
1180
|
}
|
|
1544
1181
|
return sock;
|
|
@@ -1644,7 +1281,7 @@ function computeBackoff(attempt, config = {}) {
|
|
|
1644
1281
|
|
|
1645
1282
|
// app/lib/whatsapp/init-queries.ts
|
|
1646
1283
|
import { inspect as inspect2 } from "util";
|
|
1647
|
-
var
|
|
1284
|
+
var TAG3 = "[whatsapp:init]";
|
|
1648
1285
|
var DEFAULT_QUERY_TIMEOUT_MS = 15e3;
|
|
1649
1286
|
var INSPECT_OPTS2 = {
|
|
1650
1287
|
depth: null,
|
|
@@ -1689,20 +1326,20 @@ async function runInitQueries(sock, ctx) {
|
|
|
1689
1326
|
elapsedMs: 0
|
|
1690
1327
|
};
|
|
1691
1328
|
console.error(
|
|
1692
|
-
`${
|
|
1329
|
+
`${TAG3} runInitQueries start account=${ctx.accountId} attempt=${ctx.attempt} selfPhone=${ctx.selfPhone ?? "unknown"} authDir=${ctx.authDir} queryTimeoutMs=${queryTimeoutMs}`
|
|
1693
1330
|
);
|
|
1694
1331
|
console.error(
|
|
1695
|
-
`${
|
|
1332
|
+
`${TAG3} fetchProps SKIPPED account=${ctx.accountId} reason=no-platform-consumer`
|
|
1696
1333
|
);
|
|
1697
1334
|
if (ctx.signal?.aborted) {
|
|
1698
1335
|
result.aborted = true;
|
|
1699
1336
|
result.elapsedMs = Date.now() - startedAt;
|
|
1700
1337
|
console.error(
|
|
1701
|
-
`${
|
|
1338
|
+
`${TAG3} runInitQueries aborted before start account=${ctx.accountId}`
|
|
1702
1339
|
);
|
|
1703
1340
|
return result;
|
|
1704
1341
|
}
|
|
1705
|
-
console.error(`${
|
|
1342
|
+
console.error(`${TAG3} fetchBlocklist start account=${ctx.accountId}`);
|
|
1706
1343
|
try {
|
|
1707
1344
|
const blocklist = await withTimeout(
|
|
1708
1345
|
"fetchBlocklist",
|
|
@@ -1712,24 +1349,24 @@ async function runInitQueries(sock, ctx) {
|
|
|
1712
1349
|
const count = Array.isArray(blocklist) ? blocklist.length : 0;
|
|
1713
1350
|
result.blocklistOk = true;
|
|
1714
1351
|
console.error(
|
|
1715
|
-
`${
|
|
1352
|
+
`${TAG3} fetchBlocklist ok account=${ctx.accountId} count=${count}`
|
|
1716
1353
|
);
|
|
1717
1354
|
} catch (err) {
|
|
1718
1355
|
const formatted = formatErr(err);
|
|
1719
1356
|
result.errors.blocklist = formatted;
|
|
1720
1357
|
console.error(
|
|
1721
|
-
`${
|
|
1358
|
+
`${TAG3} fetchBlocklist FAILED account=${ctx.accountId}: ${formatted}`
|
|
1722
1359
|
);
|
|
1723
1360
|
}
|
|
1724
1361
|
if (ctx.signal?.aborted) {
|
|
1725
1362
|
result.aborted = true;
|
|
1726
1363
|
result.elapsedMs = Date.now() - startedAt;
|
|
1727
1364
|
console.error(
|
|
1728
|
-
`${
|
|
1365
|
+
`${TAG3} runInitQueries aborted mid-sequence account=${ctx.accountId} after=fetchBlocklist`
|
|
1729
1366
|
);
|
|
1730
1367
|
return result;
|
|
1731
1368
|
}
|
|
1732
|
-
console.error(`${
|
|
1369
|
+
console.error(`${TAG3} fetchPrivacySettings start account=${ctx.accountId}`);
|
|
1733
1370
|
try {
|
|
1734
1371
|
const settings = await withTimeout(
|
|
1735
1372
|
"fetchPrivacySettings",
|
|
@@ -1739,18 +1376,18 @@ async function runInitQueries(sock, ctx) {
|
|
|
1739
1376
|
);
|
|
1740
1377
|
result.privacySettingsOk = true;
|
|
1741
1378
|
console.error(
|
|
1742
|
-
`${
|
|
1379
|
+
`${TAG3} fetchPrivacySettings ok account=${ctx.accountId} keys=${settings ? Object.keys(settings).join(",") : ""}`
|
|
1743
1380
|
);
|
|
1744
1381
|
} catch (err) {
|
|
1745
1382
|
const formatted = formatErr(err);
|
|
1746
1383
|
result.errors.privacySettings = formatted;
|
|
1747
1384
|
console.error(
|
|
1748
|
-
`${
|
|
1385
|
+
`${TAG3} fetchPrivacySettings FAILED account=${ctx.accountId}: ${formatted}`
|
|
1749
1386
|
);
|
|
1750
1387
|
}
|
|
1751
1388
|
result.elapsedMs = Date.now() - startedAt;
|
|
1752
1389
|
console.error(
|
|
1753
|
-
`${
|
|
1390
|
+
`${TAG3} runInitQueries done account=${ctx.accountId} blocklistOk=${result.blocklistOk} privacySettingsOk=${result.privacySettingsOk} elapsedMs=${result.elapsedMs}`
|
|
1754
1391
|
);
|
|
1755
1392
|
return result;
|
|
1756
1393
|
}
|
|
@@ -2054,7 +1691,7 @@ function isDuplicateInbound(key) {
|
|
|
2054
1691
|
}
|
|
2055
1692
|
|
|
2056
1693
|
// app/lib/whatsapp/activity.ts
|
|
2057
|
-
var
|
|
1694
|
+
var TAG4 = "[whatsapp:activity]";
|
|
2058
1695
|
var RECENT_EVENTS_MAX = 200;
|
|
2059
1696
|
var counters = /* @__PURE__ */ new Map();
|
|
2060
1697
|
var recentEvents = [];
|
|
@@ -2082,10 +1719,10 @@ function recordActivity(event) {
|
|
|
2082
1719
|
recentEvents.splice(0, recentEvents.length - RECENT_EVENTS_MAX);
|
|
2083
1720
|
}
|
|
2084
1721
|
console.error(
|
|
2085
|
-
`${
|
|
1722
|
+
`${TAG4} channel-activity: direction=${direction} account=${accountId} jid=${jid}` + (messageType ? ` type=${messageType}` : "")
|
|
2086
1723
|
);
|
|
2087
1724
|
} catch (err) {
|
|
2088
|
-
console.error(`${
|
|
1725
|
+
console.error(`${TAG4} recording failed: ${String(err)}`);
|
|
2089
1726
|
}
|
|
2090
1727
|
}
|
|
2091
1728
|
function getChannelActivity(accountId) {
|
|
@@ -2106,7 +1743,7 @@ function getChannelActivity(accountId) {
|
|
|
2106
1743
|
}
|
|
2107
1744
|
|
|
2108
1745
|
// app/lib/whatsapp/outbound/send.ts
|
|
2109
|
-
var
|
|
1746
|
+
var TAG5 = "[whatsapp:outbound]";
|
|
2110
1747
|
async function sendTextMessage(sock, to, text, opts) {
|
|
2111
1748
|
try {
|
|
2112
1749
|
const jid = to.includes("@") ? to : toWhatsappJid(to);
|
|
@@ -2118,14 +1755,14 @@ async function sendTextMessage(sock, to, text, opts) {
|
|
|
2118
1755
|
const messageId = result?.key?.id;
|
|
2119
1756
|
if (messageId) {
|
|
2120
1757
|
trackAgentSentMessage(messageId);
|
|
2121
|
-
console.error(`${
|
|
1758
|
+
console.error(`${TAG5} sent text to=${jid} id=${messageId}`);
|
|
2122
1759
|
}
|
|
2123
1760
|
if (opts?.accountId) {
|
|
2124
1761
|
recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: "text" });
|
|
2125
1762
|
}
|
|
2126
1763
|
return { success: true, messageId: messageId ?? void 0 };
|
|
2127
1764
|
} catch (err) {
|
|
2128
|
-
console.error(`${
|
|
1765
|
+
console.error(`${TAG5} send failed to=${to}: ${String(err)}`);
|
|
2129
1766
|
return { success: false, error: String(err) };
|
|
2130
1767
|
}
|
|
2131
1768
|
}
|
|
@@ -2180,20 +1817,20 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
2180
1817
|
const messageId = result?.key?.id;
|
|
2181
1818
|
if (messageId) {
|
|
2182
1819
|
trackAgentSentMessage(messageId);
|
|
2183
|
-
console.error(`${
|
|
1820
|
+
console.error(`${TAG5} sent ${media.type} to=${jid} id=${messageId}`);
|
|
2184
1821
|
}
|
|
2185
1822
|
if (opts?.accountId) {
|
|
2186
1823
|
recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: media.type });
|
|
2187
1824
|
}
|
|
2188
1825
|
return { success: true, messageId: messageId ?? void 0 };
|
|
2189
1826
|
} catch (err) {
|
|
2190
|
-
console.error(`${
|
|
1827
|
+
console.error(`${TAG5} send media failed to=${to}: ${String(err)}`);
|
|
2191
1828
|
return { success: false, error: String(err) };
|
|
2192
1829
|
}
|
|
2193
1830
|
}
|
|
2194
1831
|
|
|
2195
1832
|
// app/lib/whatsapp/persist-message.ts
|
|
2196
|
-
var
|
|
1833
|
+
var TAG6 = "[whatsapp-persist]";
|
|
2197
1834
|
var sessionWriteLocks = /* @__PURE__ */ new Map();
|
|
2198
1835
|
async function persistWhatsAppMessage(input) {
|
|
2199
1836
|
if (!input.body || !input.msgKeyId) return null;
|
|
@@ -2298,7 +1935,7 @@ async function persistWhatsAppMessage(input) {
|
|
|
2298
1935
|
const result = await session.run(cypher, params);
|
|
2299
1936
|
const ms = Date.now() - t0;
|
|
2300
1937
|
if (result.records.length === 0) {
|
|
2301
|
-
console.error(`${
|
|
1938
|
+
console.error(`${TAG6} skip reason=conversation-not-found accountId=${input.platformAccountId} cacheKey=${input.cacheKey} messageId=${messageId}`);
|
|
2302
1939
|
return null;
|
|
2303
1940
|
}
|
|
2304
1941
|
const rec = result.records[0];
|
|
@@ -2307,26 +1944,26 @@ async function persistWhatsAppMessage(input) {
|
|
|
2307
1944
|
const senderElementId = rec.get("senderElementId");
|
|
2308
1945
|
const senderReused = rec.get("senderReused") === true;
|
|
2309
1946
|
if (existed) {
|
|
2310
|
-
console.error(`${
|
|
1947
|
+
console.error(`${TAG6} noop reason=existing-message messageId=${messageId}`);
|
|
2311
1948
|
return { messageId, created: false, senderElementId };
|
|
2312
1949
|
}
|
|
2313
1950
|
console.error(
|
|
2314
|
-
`${
|
|
1951
|
+
`${TAG6} sender-resolved telephone=${senderTelephone} nodeKind=Person elementId=${senderElementId} reused=${senderReused}`
|
|
2315
1952
|
);
|
|
2316
1953
|
if (prevMessageId) {
|
|
2317
|
-
console.error(`${
|
|
1954
|
+
console.error(`${TAG6} next-merge prior=${prevMessageId} next=${messageId}`);
|
|
2318
1955
|
} else {
|
|
2319
|
-
console.error(`${
|
|
1956
|
+
console.error(`${TAG6} next-skip reason=no-prior msgId=${messageId}`);
|
|
2320
1957
|
}
|
|
2321
1958
|
console.error(
|
|
2322
|
-
`${
|
|
1959
|
+
`${TAG6} write messageId=${messageId} accountId=${input.platformAccountId} cacheKey=${input.cacheKey} fromMe=${input.fromMe} dateSent=${dateSentIso} bodyBytes=${Buffer.byteLength(input.body, "utf8")} ms=${ms}`
|
|
2323
1960
|
);
|
|
2324
1961
|
return { messageId, created: true, senderElementId };
|
|
2325
1962
|
} catch (err) {
|
|
2326
1963
|
const ms = Date.now() - t0;
|
|
2327
1964
|
const reason = sanitizeReason(err);
|
|
2328
1965
|
console.error(
|
|
2329
|
-
`${
|
|
1966
|
+
`${TAG6} FAIL accountId=${input.platformAccountId} waname=${input.accountId} remoteJid=${input.remoteJid} msgKey=${input.msgKeyId} reason=${reason} ms=${ms}`
|
|
2330
1967
|
);
|
|
2331
1968
|
return null;
|
|
2332
1969
|
} finally {
|
|
@@ -2359,7 +1996,7 @@ function sanitizeReason(err) {
|
|
|
2359
1996
|
}
|
|
2360
1997
|
|
|
2361
1998
|
// app/lib/whatsapp/ensure-conversation.ts
|
|
2362
|
-
var
|
|
1999
|
+
var TAG7 = "[whatsapp-persist]";
|
|
2363
2000
|
async function ensureWhatsAppConversation(input) {
|
|
2364
2001
|
const t0 = Date.now();
|
|
2365
2002
|
try {
|
|
@@ -2371,27 +2008,27 @@ async function ensureWhatsAppConversation(input) {
|
|
|
2371
2008
|
const ms = Date.now() - t0;
|
|
2372
2009
|
if (!result.conversationId) {
|
|
2373
2010
|
console.error(
|
|
2374
|
-
`${
|
|
2011
|
+
`${TAG7} conversation-merged FAIL cacheKey=${input.cacheKey} reason=null-conversationId ms=${ms}`
|
|
2375
2012
|
);
|
|
2376
2013
|
return null;
|
|
2377
2014
|
}
|
|
2378
2015
|
console.error(
|
|
2379
|
-
`${
|
|
2016
|
+
`${TAG7} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
|
|
2380
2017
|
);
|
|
2381
2018
|
return { conversationId: result.conversationId, created: result.created };
|
|
2382
2019
|
} catch (err) {
|
|
2383
2020
|
const ms = Date.now() - t0;
|
|
2384
2021
|
const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
|
|
2385
2022
|
console.error(
|
|
2386
|
-
`${
|
|
2023
|
+
`${TAG7} conversation-merged FAIL cacheKey=${input.cacheKey} reason=${reason} ms=${ms}`
|
|
2387
2024
|
);
|
|
2388
2025
|
return null;
|
|
2389
2026
|
}
|
|
2390
2027
|
}
|
|
2391
2028
|
|
|
2392
2029
|
// ../lib/account-enumeration/src/index.ts
|
|
2393
|
-
import { readdirSync, readFileSync as
|
|
2394
|
-
import { resolve
|
|
2030
|
+
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
2031
|
+
import { resolve } from "path";
|
|
2395
2032
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2396
2033
|
var cache = /* @__PURE__ */ new Map();
|
|
2397
2034
|
function enumerateValidAccountIds(accountsDir) {
|
|
@@ -2410,9 +2047,9 @@ function enumerateValidAccountIds(accountsDir) {
|
|
|
2410
2047
|
const valid = [];
|
|
2411
2048
|
for (const name of names) {
|
|
2412
2049
|
if (!UUID_RE.test(name)) continue;
|
|
2413
|
-
const configPath2 =
|
|
2050
|
+
const configPath2 = resolve(accountsDir, name, "account.json");
|
|
2414
2051
|
try {
|
|
2415
|
-
JSON.parse(
|
|
2052
|
+
JSON.parse(readFileSync2(configPath2, "utf-8"));
|
|
2416
2053
|
valid.push(name);
|
|
2417
2054
|
} catch (err) {
|
|
2418
2055
|
const code = err.code;
|
|
@@ -2429,7 +2066,7 @@ function getAccountsDirFromEnv() {
|
|
|
2429
2066
|
"[graph-write] MAXY_PLATFORM_ROOT not set \u2014 cannot enforce accountId gate. Set MAXY_PLATFORM_ROOT in the spawning process or pass `accountsDir` explicitly."
|
|
2430
2067
|
);
|
|
2431
2068
|
}
|
|
2432
|
-
return
|
|
2069
|
+
return resolve(root, "..", "data/accounts");
|
|
2433
2070
|
}
|
|
2434
2071
|
function validateAccountIdEnv(envValue, diskIds) {
|
|
2435
2072
|
if (!envValue) {
|
|
@@ -2468,13 +2105,13 @@ function resolvePlatformAccountId(accountsDir = ACCOUNTS_DIR) {
|
|
|
2468
2105
|
// app/lib/whatsapp/inbound/media.ts
|
|
2469
2106
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2470
2107
|
import { writeFile, mkdir } from "fs/promises";
|
|
2471
|
-
import { join as
|
|
2108
|
+
import { join as join2 } from "path";
|
|
2472
2109
|
import {
|
|
2473
2110
|
downloadMediaMessage,
|
|
2474
2111
|
downloadContentFromMessage,
|
|
2475
2112
|
normalizeMessageContent as normalizeMessageContent2
|
|
2476
2113
|
} from "@whiskeysockets/baileys";
|
|
2477
|
-
var
|
|
2114
|
+
var TAG8 = "[whatsapp:media]";
|
|
2478
2115
|
var MEDIA_DIR = "/tmp/maxy-media";
|
|
2479
2116
|
function mimeToExt(mimetype) {
|
|
2480
2117
|
const map = {
|
|
@@ -2530,47 +2167,47 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
2530
2167
|
}
|
|
2531
2168
|
);
|
|
2532
2169
|
if (!buffer || buffer.length === 0) {
|
|
2533
|
-
console.error(`${
|
|
2170
|
+
console.error(`${TAG8} primary download returned empty, trying direct fallback`);
|
|
2534
2171
|
const downloadable = getDownloadableContent(content);
|
|
2535
2172
|
if (downloadable) {
|
|
2536
2173
|
try {
|
|
2537
2174
|
const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
2538
2175
|
buffer = await streamToBuffer(stream);
|
|
2539
2176
|
} catch (fallbackErr) {
|
|
2540
|
-
console.error(`${
|
|
2177
|
+
console.error(`${TAG8} direct download fallback failed: ${String(fallbackErr)}`);
|
|
2541
2178
|
}
|
|
2542
2179
|
}
|
|
2543
2180
|
}
|
|
2544
2181
|
if (!buffer || buffer.length === 0) {
|
|
2545
|
-
console.error(`${
|
|
2182
|
+
console.error(`${TAG8} download failed: empty buffer for ${mimetype ?? "unknown"}`);
|
|
2546
2183
|
return void 0;
|
|
2547
2184
|
}
|
|
2548
2185
|
if (buffer.length > maxBytes) {
|
|
2549
2186
|
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
2550
2187
|
const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
|
|
2551
|
-
console.error(`${
|
|
2188
|
+
console.error(`${TAG8} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
|
|
2552
2189
|
return void 0;
|
|
2553
2190
|
}
|
|
2554
2191
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
2555
2192
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
2556
2193
|
const filename = `${randomUUID2()}.${ext}`;
|
|
2557
|
-
const filePath =
|
|
2194
|
+
const filePath = join2(MEDIA_DIR, filename);
|
|
2558
2195
|
await writeFile(filePath, buffer);
|
|
2559
2196
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
2560
|
-
console.error(`${
|
|
2197
|
+
console.error(`${TAG8} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
2561
2198
|
return {
|
|
2562
2199
|
path: filePath,
|
|
2563
2200
|
mimetype: mimetype ?? "application/octet-stream",
|
|
2564
2201
|
size: buffer.length
|
|
2565
2202
|
};
|
|
2566
2203
|
} catch (err) {
|
|
2567
|
-
console.error(`${
|
|
2204
|
+
console.error(`${TAG8} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
|
|
2568
2205
|
return void 0;
|
|
2569
2206
|
}
|
|
2570
2207
|
}
|
|
2571
2208
|
|
|
2572
2209
|
// app/lib/whatsapp/inbound/debounce.ts
|
|
2573
|
-
var
|
|
2210
|
+
var TAG9 = "[whatsapp:debounce]";
|
|
2574
2211
|
var STT_TAG = "[whatsapp:stt-await]";
|
|
2575
2212
|
function createInboundDebouncer(opts) {
|
|
2576
2213
|
const { debounceMs, buildKey, onFlush, onError } = opts;
|
|
@@ -2601,7 +2238,7 @@ function createInboundDebouncer(opts) {
|
|
|
2601
2238
|
pending.delete(key);
|
|
2602
2239
|
const batchSize = batch.entries.length;
|
|
2603
2240
|
try {
|
|
2604
|
-
console.error(`${
|
|
2241
|
+
console.error(`${TAG9} debounce flush key=${key} batchSize=${batchSize}`);
|
|
2605
2242
|
const result = onFlush(batch.entries);
|
|
2606
2243
|
if (result && typeof result.catch === "function") {
|
|
2607
2244
|
result.catch(onError);
|
|
@@ -2673,7 +2310,7 @@ function createInboundDebouncer(opts) {
|
|
|
2673
2310
|
}
|
|
2674
2311
|
|
|
2675
2312
|
// app/lib/whatsapp/opening-hours.ts
|
|
2676
|
-
var
|
|
2313
|
+
var TAG10 = "[whatsapp:hours]";
|
|
2677
2314
|
async function isBusinessOpen(accountId) {
|
|
2678
2315
|
try {
|
|
2679
2316
|
const timezone = await resolveTimezone(accountId);
|
|
@@ -2691,7 +2328,7 @@ async function isBusinessOpen(accountId) {
|
|
|
2691
2328
|
{ accountId, dayOfWeek, previousDayOfWeek }
|
|
2692
2329
|
);
|
|
2693
2330
|
if (result.records.length === 0) {
|
|
2694
|
-
console.error(`${
|
|
2331
|
+
console.error(`${TAG10} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
2695
2332
|
return { open: true, reason: "no opening hours configured" };
|
|
2696
2333
|
}
|
|
2697
2334
|
const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
|
|
@@ -2700,7 +2337,7 @@ async function isBusinessOpen(accountId) {
|
|
|
2700
2337
|
closes: String(r.get("closes")).trim()
|
|
2701
2338
|
}));
|
|
2702
2339
|
if (specs.length === 0) {
|
|
2703
|
-
console.error(`${
|
|
2340
|
+
console.error(`${TAG10} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
2704
2341
|
return { open: true, reason: "no opening hours configured" };
|
|
2705
2342
|
}
|
|
2706
2343
|
for (const spec of specs) {
|
|
@@ -2710,7 +2347,7 @@ async function isBusinessOpen(accountId) {
|
|
|
2710
2347
|
if (spec.opens > spec.closes && currentTime < spec.closes) {
|
|
2711
2348
|
const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
|
|
2712
2349
|
console.error(
|
|
2713
|
-
`${
|
|
2350
|
+
`${TAG10} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
2714
2351
|
);
|
|
2715
2352
|
return {
|
|
2716
2353
|
open: true,
|
|
@@ -2723,7 +2360,7 @@ async function isBusinessOpen(accountId) {
|
|
|
2723
2360
|
} else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
|
|
2724
2361
|
const hoursStr = `${spec.opens}-${spec.closes}`;
|
|
2725
2362
|
console.error(
|
|
2726
|
-
`${
|
|
2363
|
+
`${TAG10} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
2727
2364
|
);
|
|
2728
2365
|
return {
|
|
2729
2366
|
open: true,
|
|
@@ -2737,7 +2374,7 @@ async function isBusinessOpen(accountId) {
|
|
|
2737
2374
|
const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
|
|
2738
2375
|
const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
|
|
2739
2376
|
console.error(
|
|
2740
|
-
`${
|
|
2377
|
+
`${TAG10} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
|
|
2741
2378
|
);
|
|
2742
2379
|
return {
|
|
2743
2380
|
open: false,
|
|
@@ -2751,7 +2388,7 @@ async function isBusinessOpen(accountId) {
|
|
|
2751
2388
|
}
|
|
2752
2389
|
} catch (err) {
|
|
2753
2390
|
console.error(
|
|
2754
|
-
`${
|
|
2391
|
+
`${TAG10} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
|
|
2755
2392
|
);
|
|
2756
2393
|
return { open: true, reason: "hours check failed (treating as open)" };
|
|
2757
2394
|
}
|
|
@@ -2797,7 +2434,7 @@ import { execFile } from "child_process";
|
|
|
2797
2434
|
import { unlink, stat } from "fs/promises";
|
|
2798
2435
|
import { promisify } from "util";
|
|
2799
2436
|
var execFileAsync = promisify(execFile);
|
|
2800
|
-
var
|
|
2437
|
+
var TAG11 = "[stt]";
|
|
2801
2438
|
var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
|
|
2802
2439
|
var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
|
|
2803
2440
|
var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
@@ -2808,11 +2445,11 @@ async function transcribe(audioPath, mimetype) {
|
|
|
2808
2445
|
const s = await stat(audioPath);
|
|
2809
2446
|
audioBytes = s.size;
|
|
2810
2447
|
} catch {
|
|
2811
|
-
console.error(`${
|
|
2448
|
+
console.error(`${TAG11} failed: file not readable path=${audioPath}`);
|
|
2812
2449
|
return void 0;
|
|
2813
2450
|
}
|
|
2814
2451
|
console.error(
|
|
2815
|
-
`${
|
|
2452
|
+
`${TAG11} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
|
|
2816
2453
|
);
|
|
2817
2454
|
const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
|
|
2818
2455
|
try {
|
|
@@ -2831,7 +2468,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
2831
2468
|
], { timeout: 3e4 });
|
|
2832
2469
|
} catch (err) {
|
|
2833
2470
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2834
|
-
console.error(`${
|
|
2471
|
+
console.error(`${TAG11} failed: ffmpeg conversion error=${reason}`);
|
|
2835
2472
|
return void 0;
|
|
2836
2473
|
}
|
|
2837
2474
|
try {
|
|
@@ -2849,20 +2486,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
2849
2486
|
const text = stdout.trim();
|
|
2850
2487
|
const durationMs = Date.now() - startMs;
|
|
2851
2488
|
if (!text) {
|
|
2852
|
-
console.error(`${
|
|
2489
|
+
console.error(`${TAG11} failed: whisper returned empty output duration_ms=${durationMs}`);
|
|
2853
2490
|
return void 0;
|
|
2854
2491
|
}
|
|
2855
2492
|
const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
|
|
2856
2493
|
const language = langMatch?.[1] ?? "unknown";
|
|
2857
2494
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
2858
2495
|
console.error(
|
|
2859
|
-
`${
|
|
2496
|
+
`${TAG11} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
|
|
2860
2497
|
);
|
|
2861
2498
|
return { text, language, durationMs };
|
|
2862
2499
|
} catch (err) {
|
|
2863
2500
|
const durationMs = Date.now() - startMs;
|
|
2864
2501
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2865
|
-
console.error(`${
|
|
2502
|
+
console.error(`${TAG11} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
|
|
2866
2503
|
return void 0;
|
|
2867
2504
|
} finally {
|
|
2868
2505
|
unlink(wavPath).catch(() => {
|
|
@@ -2871,7 +2508,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
2871
2508
|
}
|
|
2872
2509
|
|
|
2873
2510
|
// app/lib/whatsapp/manager.ts
|
|
2874
|
-
var
|
|
2511
|
+
var TAG12 = "[whatsapp:manager]";
|
|
2875
2512
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
2876
2513
|
var MESSAGE_STORE_MAX = 500;
|
|
2877
2514
|
var GROUP_META_TTL = 5 * 60 * 1e3;
|
|
@@ -2891,7 +2528,7 @@ function storeMessage(storeKey, entry) {
|
|
|
2891
2528
|
if (entries.length > MESSAGE_STORE_MAX) {
|
|
2892
2529
|
const trimmed = entries.length - MESSAGE_STORE_MAX;
|
|
2893
2530
|
entries.splice(0, trimmed);
|
|
2894
|
-
console.error(`${
|
|
2531
|
+
console.error(`${TAG12} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
2895
2532
|
}
|
|
2896
2533
|
}
|
|
2897
2534
|
function deriveCacheKey(input) {
|
|
@@ -2910,7 +2547,7 @@ function deriveCacheKey(input) {
|
|
|
2910
2547
|
}
|
|
2911
2548
|
async function init(opts) {
|
|
2912
2549
|
if (initialized) {
|
|
2913
|
-
console.error(`${
|
|
2550
|
+
console.error(`${TAG12} already initialized`);
|
|
2914
2551
|
return;
|
|
2915
2552
|
}
|
|
2916
2553
|
configDir = opts.configDir;
|
|
@@ -2918,20 +2555,20 @@ async function init(opts) {
|
|
|
2918
2555
|
loadConfig(opts.accountConfig);
|
|
2919
2556
|
const accountIds = listCredentialAccountIds(configDir);
|
|
2920
2557
|
if (accountIds.length === 0) {
|
|
2921
|
-
console.error(`${
|
|
2558
|
+
console.error(`${TAG12} init: no stored WhatsApp credentials found`);
|
|
2922
2559
|
initialized = true;
|
|
2923
2560
|
return;
|
|
2924
2561
|
}
|
|
2925
|
-
console.error(`${
|
|
2562
|
+
console.error(`${TAG12} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
|
|
2926
2563
|
initialized = true;
|
|
2927
2564
|
for (const accountId of accountIds) {
|
|
2928
2565
|
const accountCfg = whatsAppConfig.accounts?.[accountId];
|
|
2929
2566
|
if (accountCfg?.enabled === false) {
|
|
2930
|
-
console.error(`${
|
|
2567
|
+
console.error(`${TAG12} skipping disabled account=${accountId}`);
|
|
2931
2568
|
continue;
|
|
2932
2569
|
}
|
|
2933
2570
|
startConnection(accountId).catch((err) => {
|
|
2934
|
-
console.error(`${
|
|
2571
|
+
console.error(`${TAG12} failed to auto-start account=${accountId}: ${formatError(err)}`);
|
|
2935
2572
|
});
|
|
2936
2573
|
}
|
|
2937
2574
|
}
|
|
@@ -2940,7 +2577,7 @@ async function startConnection(accountId) {
|
|
|
2940
2577
|
const authDir = resolveAuthDir(configDir, accountId);
|
|
2941
2578
|
const hasAuth = await authExists(authDir);
|
|
2942
2579
|
if (!hasAuth) {
|
|
2943
|
-
console.error(`${
|
|
2580
|
+
console.error(`${TAG12} no credentials for account=${accountId}`);
|
|
2944
2581
|
return;
|
|
2945
2582
|
}
|
|
2946
2583
|
let platformAccountId;
|
|
@@ -2991,11 +2628,11 @@ async function stopConnection(accountId) {
|
|
|
2991
2628
|
conn.sock.ev.removeAllListeners("creds.update");
|
|
2992
2629
|
conn.sock.ws?.close?.();
|
|
2993
2630
|
} catch (err) {
|
|
2994
|
-
console.warn(`${
|
|
2631
|
+
console.warn(`${TAG12} socket cleanup error during stop account=${accountId}: ${String(err)}`);
|
|
2995
2632
|
}
|
|
2996
2633
|
}
|
|
2997
2634
|
connections.delete(accountId);
|
|
2998
|
-
console.error(`${
|
|
2635
|
+
console.error(`${TAG12} stopped account=${accountId}`);
|
|
2999
2636
|
}
|
|
3000
2637
|
function getStatus() {
|
|
3001
2638
|
return Array.from(connections.values()).map((conn) => ({
|
|
@@ -3057,9 +2694,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
3057
2694
|
connections.set(accountId, conn);
|
|
3058
2695
|
try {
|
|
3059
2696
|
await sock.sendPresenceUpdate("available");
|
|
3060
|
-
console.error(`${
|
|
2697
|
+
console.error(`${TAG12} presence set to available account=${accountId}`);
|
|
3061
2698
|
} catch (err) {
|
|
3062
|
-
console.error(`${
|
|
2699
|
+
console.error(`${TAG12} presence update failed account=${accountId}: ${String(err)}`);
|
|
3063
2700
|
}
|
|
3064
2701
|
await runInitQueries(sock, {
|
|
3065
2702
|
accountId,
|
|
@@ -3071,7 +2708,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
3071
2708
|
monitorInbound(conn);
|
|
3072
2709
|
watchForDisconnect(conn);
|
|
3073
2710
|
attachSelfIdRefreshOnCredsUpdate(conn);
|
|
3074
|
-
console.error(`${
|
|
2711
|
+
console.error(`${TAG12} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
3075
2712
|
}
|
|
3076
2713
|
function attachSelfIdRefreshOnCredsUpdate(conn) {
|
|
3077
2714
|
if (!conn.sock) return;
|
|
@@ -3122,24 +2759,23 @@ async function shutdown() {
|
|
|
3122
2759
|
const ids = Array.from(connections.keys());
|
|
3123
2760
|
await Promise.all(ids.map((id) => stopConnection(id)));
|
|
3124
2761
|
initialized = false;
|
|
3125
|
-
console.error(`${
|
|
2762
|
+
console.error(`${TAG12} shutdown complete`);
|
|
3126
2763
|
}
|
|
3127
2764
|
function loadConfig(accountConfig) {
|
|
3128
2765
|
try {
|
|
3129
2766
|
const raw = accountConfig?.whatsapp;
|
|
3130
2767
|
if (raw && typeof raw === "object") {
|
|
3131
2768
|
const wa = raw;
|
|
3132
|
-
migrateAdminPhones(wa);
|
|
3133
2769
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
3134
2770
|
if (parsed.success) {
|
|
3135
2771
|
whatsAppConfig = parsed.data;
|
|
3136
2772
|
} else {
|
|
3137
|
-
console.error(`${
|
|
2773
|
+
console.error(`${TAG12} config validation failed: ${parsed.error.message}`);
|
|
3138
2774
|
whatsAppConfig = {};
|
|
3139
2775
|
}
|
|
3140
2776
|
}
|
|
3141
2777
|
} catch (err) {
|
|
3142
|
-
console.error(`${
|
|
2778
|
+
console.error(`${TAG12} config load error: ${String(err)}`);
|
|
3143
2779
|
whatsAppConfig = {};
|
|
3144
2780
|
}
|
|
3145
2781
|
}
|
|
@@ -3150,13 +2786,13 @@ async function connectWithReconnect(conn) {
|
|
|
3150
2786
|
let cycleError = null;
|
|
3151
2787
|
let connectedAt;
|
|
3152
2788
|
try {
|
|
3153
|
-
console.error(`${
|
|
2789
|
+
console.error(`${TAG12} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
|
|
3154
2790
|
const sock = await createWaSocket({
|
|
3155
2791
|
authDir: conn.authDir,
|
|
3156
2792
|
silent: true
|
|
3157
2793
|
});
|
|
3158
2794
|
conn.sock = sock;
|
|
3159
|
-
console.error(`${
|
|
2795
|
+
console.error(`${TAG12} socket created account=${conn.accountId} \u2014 waiting for connection`);
|
|
3160
2796
|
await waitForConnection(sock);
|
|
3161
2797
|
const selfId = readSelfId(conn.authDir);
|
|
3162
2798
|
connectedAt = Date.now();
|
|
@@ -3173,12 +2809,12 @@ async function connectWithReconnect(conn) {
|
|
|
3173
2809
|
} else {
|
|
3174
2810
|
console.error(`[whatsapp:lid] FAIL self-lid-resolution accountId=${conn.accountId} reason=creds-missing-lid`);
|
|
3175
2811
|
}
|
|
3176
|
-
console.error(`${
|
|
2812
|
+
console.error(`${TAG12} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
3177
2813
|
try {
|
|
3178
2814
|
await sock.sendPresenceUpdate("available");
|
|
3179
|
-
console.error(`${
|
|
2815
|
+
console.error(`${TAG12} presence set to available account=${conn.accountId}`);
|
|
3180
2816
|
} catch (err) {
|
|
3181
|
-
console.error(`${
|
|
2817
|
+
console.error(`${TAG12} presence update failed account=${conn.accountId}: ${String(err)}`);
|
|
3182
2818
|
}
|
|
3183
2819
|
await runInitQueries(sock, {
|
|
3184
2820
|
accountId: conn.accountId,
|
|
@@ -3206,9 +2842,9 @@ async function connectWithReconnect(conn) {
|
|
|
3206
2842
|
}
|
|
3207
2843
|
const classification = classifyDisconnect(err);
|
|
3208
2844
|
conn.lastError = classification.message;
|
|
3209
|
-
console.error(`${
|
|
2845
|
+
console.error(`${TAG12} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
3210
2846
|
if (!classification.shouldRetry) {
|
|
3211
|
-
console.error(`${
|
|
2847
|
+
console.error(`${TAG12} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
3212
2848
|
return;
|
|
3213
2849
|
}
|
|
3214
2850
|
}
|
|
@@ -3221,7 +2857,7 @@ async function connectWithReconnect(conn) {
|
|
|
3221
2857
|
if (decision.action === "abort") {
|
|
3222
2858
|
const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
|
|
3223
2859
|
console.error(
|
|
3224
|
-
`${
|
|
2860
|
+
`${TAG12} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
|
|
3225
2861
|
);
|
|
3226
2862
|
conn.sessionStuckReason = stuckReason;
|
|
3227
2863
|
conn.lastError = stuckReason;
|
|
@@ -3230,17 +2866,17 @@ async function connectWithReconnect(conn) {
|
|
|
3230
2866
|
conn.reconnectAttempts = decision.nextAttempts;
|
|
3231
2867
|
if (decision.reason === "short-lived") {
|
|
3232
2868
|
console.error(
|
|
3233
|
-
`${
|
|
2869
|
+
`${TAG12} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
|
|
3234
2870
|
);
|
|
3235
2871
|
} else {
|
|
3236
2872
|
console.error(
|
|
3237
|
-
`${
|
|
2873
|
+
`${TAG12} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
|
|
3238
2874
|
);
|
|
3239
2875
|
}
|
|
3240
2876
|
if (decision.reason === "short-lived" || cycleError) {
|
|
3241
2877
|
const delay = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
3242
2878
|
console.error(
|
|
3243
|
-
`${
|
|
2879
|
+
`${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
3244
2880
|
);
|
|
3245
2881
|
await new Promise((resolve24) => {
|
|
3246
2882
|
const timer2 = setTimeout(resolve24, delay);
|
|
@@ -3274,11 +2910,11 @@ function watchForDisconnect(conn) {
|
|
|
3274
2910
|
conn.sock.ev.on("connection.update", (update) => {
|
|
3275
2911
|
if (update.connection === "close") {
|
|
3276
2912
|
if (connections.get(conn.accountId) !== conn) return;
|
|
3277
|
-
console.error(`${
|
|
2913
|
+
console.error(`${TAG12} socket disconnected for account=${conn.accountId}`);
|
|
3278
2914
|
conn.connected = false;
|
|
3279
2915
|
conn.sock = null;
|
|
3280
2916
|
connectWithReconnect(conn).catch((err) => {
|
|
3281
|
-
console.error(`${
|
|
2917
|
+
console.error(`${TAG12} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
|
|
3282
2918
|
});
|
|
3283
2919
|
}
|
|
3284
2920
|
});
|
|
@@ -3287,7 +2923,7 @@ async function getGroupMeta(conn, jid) {
|
|
|
3287
2923
|
const cached2 = conn.groupMetaCache.get(jid);
|
|
3288
2924
|
if (cached2 && cached2.expires > Date.now()) return cached2;
|
|
3289
2925
|
if (!conn.sock) return null;
|
|
3290
|
-
console.error(`${
|
|
2926
|
+
console.error(`${TAG12} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
|
|
3291
2927
|
try {
|
|
3292
2928
|
const meta = await conn.sock.groupMetadata(jid);
|
|
3293
2929
|
const participants = await Promise.all(
|
|
@@ -3302,12 +2938,12 @@ async function getGroupMeta(conn, jid) {
|
|
|
3302
2938
|
};
|
|
3303
2939
|
conn.groupMetaCache.set(jid, entry);
|
|
3304
2940
|
console.error(
|
|
3305
|
-
`${
|
|
2941
|
+
`${TAG12} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
3306
2942
|
);
|
|
3307
2943
|
return entry;
|
|
3308
2944
|
} catch (err) {
|
|
3309
2945
|
console.error(
|
|
3310
|
-
`${
|
|
2946
|
+
`${TAG12} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
3311
2947
|
);
|
|
3312
2948
|
const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
|
|
3313
2949
|
conn.groupMetaCache.set(jid, emptyEntry);
|
|
@@ -3318,7 +2954,7 @@ function monitorInbound(conn) {
|
|
|
3318
2954
|
if (!conn.sock || !onInboundMessage) return;
|
|
3319
2955
|
const sock = conn.sock;
|
|
3320
2956
|
const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
|
|
3321
|
-
console.error(`${
|
|
2957
|
+
console.error(`${TAG12} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
|
|
3322
2958
|
conn.debouncer = createInboundDebouncer({
|
|
3323
2959
|
debounceMs,
|
|
3324
2960
|
buildKey: (payload) => {
|
|
@@ -3331,7 +2967,7 @@ function monitorInbound(conn) {
|
|
|
3331
2967
|
onInboundMessage(entries[0]);
|
|
3332
2968
|
return;
|
|
3333
2969
|
}
|
|
3334
|
-
console.error(`${
|
|
2970
|
+
console.error(`${TAG12} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
3335
2971
|
const last = entries[entries.length - 1];
|
|
3336
2972
|
const mediaEntry = entries.find((e) => e.mediaPath);
|
|
3337
2973
|
const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
|
|
@@ -3344,7 +2980,7 @@ function monitorInbound(conn) {
|
|
|
3344
2980
|
});
|
|
3345
2981
|
},
|
|
3346
2982
|
onError: (err) => {
|
|
3347
|
-
console.error(`${
|
|
2983
|
+
console.error(`${TAG12} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
3348
2984
|
}
|
|
3349
2985
|
});
|
|
3350
2986
|
sock.ev.on("messages.upsert", async (upsert) => {
|
|
@@ -3372,7 +3008,7 @@ function monitorInbound(conn) {
|
|
|
3372
3008
|
});
|
|
3373
3009
|
const entries = messageStore.get(storeKey);
|
|
3374
3010
|
console.error(
|
|
3375
|
-
`${
|
|
3011
|
+
`${TAG12} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
|
|
3376
3012
|
);
|
|
3377
3013
|
recordActivity({
|
|
3378
3014
|
accountId: conn.accountId,
|
|
@@ -3436,13 +3072,13 @@ function monitorInbound(conn) {
|
|
|
3436
3072
|
);
|
|
3437
3073
|
}
|
|
3438
3074
|
console.error(
|
|
3439
|
-
`${
|
|
3075
|
+
`${TAG12} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
|
|
3440
3076
|
);
|
|
3441
3077
|
continue;
|
|
3442
3078
|
}
|
|
3443
3079
|
await handleInboundMessage(conn, msg);
|
|
3444
3080
|
} catch (err) {
|
|
3445
|
-
console.error(`${
|
|
3081
|
+
console.error(`${TAG12} inbound handler error account=${conn.accountId}: ${String(err)}`);
|
|
3446
3082
|
}
|
|
3447
3083
|
}
|
|
3448
3084
|
});
|
|
@@ -3452,31 +3088,31 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3452
3088
|
const remoteJid = msg.key.remoteJid;
|
|
3453
3089
|
if (!remoteJid) return;
|
|
3454
3090
|
if (remoteJid === "status@broadcast") {
|
|
3455
|
-
console.error(`${
|
|
3091
|
+
console.error(`${TAG12} drop: status broadcast account=${conn.accountId}`);
|
|
3456
3092
|
return;
|
|
3457
3093
|
}
|
|
3458
3094
|
if (!msg.message) {
|
|
3459
|
-
console.error(`${
|
|
3095
|
+
console.error(`${TAG12} drop: empty message account=${conn.accountId} from=${remoteJid}`);
|
|
3460
3096
|
return;
|
|
3461
3097
|
}
|
|
3462
3098
|
const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
|
|
3463
3099
|
if (isDuplicateInbound(dedupKey)) {
|
|
3464
|
-
console.error(`${
|
|
3100
|
+
console.error(`${TAG12} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
|
|
3465
3101
|
return;
|
|
3466
3102
|
}
|
|
3467
3103
|
if (msg.key.fromMe) {
|
|
3468
3104
|
if (msg.key.id && isAgentSentMessage(msg.key.id)) {
|
|
3469
|
-
console.error(`${
|
|
3105
|
+
console.error(`${TAG12} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
|
|
3470
3106
|
return;
|
|
3471
3107
|
}
|
|
3472
3108
|
const extracted2 = extractMessage(msg);
|
|
3473
3109
|
if (!extracted2.text) {
|
|
3474
|
-
console.error(`${
|
|
3110
|
+
console.error(`${TAG12} owner reply skipped \u2014 no text content account=${conn.accountId}`);
|
|
3475
3111
|
return;
|
|
3476
3112
|
}
|
|
3477
3113
|
const isGroup2 = isGroupJid(remoteJid);
|
|
3478
3114
|
const senderPhone2 = conn.selfPhone ?? "owner";
|
|
3479
|
-
console.error(`${
|
|
3115
|
+
console.error(`${TAG12} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
|
|
3480
3116
|
const reply2 = async (text) => {
|
|
3481
3117
|
const currentSock = conn.sock;
|
|
3482
3118
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
@@ -3504,7 +3140,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3504
3140
|
}
|
|
3505
3141
|
const extracted = extractMessage(msg);
|
|
3506
3142
|
if (!extracted.text && !extracted.mediaType) {
|
|
3507
|
-
console.error(`${
|
|
3143
|
+
console.error(`${TAG12} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
3508
3144
|
return;
|
|
3509
3145
|
}
|
|
3510
3146
|
let mediaResult;
|
|
@@ -3514,7 +3150,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3514
3150
|
maxBytes: maxMb * 1024 * 1024
|
|
3515
3151
|
});
|
|
3516
3152
|
if (!mediaResult) {
|
|
3517
|
-
console.error(`${
|
|
3153
|
+
console.error(`${TAG12} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
3518
3154
|
}
|
|
3519
3155
|
}
|
|
3520
3156
|
const isGroup = isGroupJid(remoteJid);
|
|
@@ -3567,7 +3203,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3567
3203
|
});
|
|
3568
3204
|
}
|
|
3569
3205
|
console.error(
|
|
3570
|
-
`${
|
|
3206
|
+
`${TAG12} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
|
|
3571
3207
|
);
|
|
3572
3208
|
if (!accessResult.allowed) return;
|
|
3573
3209
|
let groupSubject;
|
|
@@ -3610,15 +3246,15 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3610
3246
|
if (accessResult.agentType === "public") {
|
|
3611
3247
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
3612
3248
|
if (!hoursResult.open) {
|
|
3613
|
-
console.error(`${
|
|
3249
|
+
console.error(`${TAG12} [${conn.accountId}] dispatch skipped: business closed`);
|
|
3614
3250
|
const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
|
|
3615
3251
|
if (afterHoursMessage) {
|
|
3616
3252
|
try {
|
|
3617
3253
|
await reply(afterHoursMessage);
|
|
3618
|
-
console.error(`${
|
|
3254
|
+
console.error(`${TAG12} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
|
|
3619
3255
|
} catch (err) {
|
|
3620
3256
|
console.error(
|
|
3621
|
-
`${
|
|
3257
|
+
`${TAG12} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3622
3258
|
);
|
|
3623
3259
|
}
|
|
3624
3260
|
}
|
|
@@ -3635,10 +3271,10 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3635
3271
|
// app/lib/vnc.ts
|
|
3636
3272
|
import { spawnSync, execFileSync } from "child_process";
|
|
3637
3273
|
import { createConnection } from "net";
|
|
3638
|
-
import { mkdirSync, readFileSync as
|
|
3639
|
-
import { resolve as
|
|
3640
|
-
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ??
|
|
3641
|
-
var VNC_SCRIPT =
|
|
3274
|
+
import { mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3275
|
+
import { resolve as resolve2 } from "path";
|
|
3276
|
+
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
|
|
3277
|
+
var VNC_SCRIPT = resolve2(PLATFORM_ROOT2, "scripts/vnc.sh");
|
|
3642
3278
|
var displayMode = process.env.DISPLAY_MODE ?? "virtual";
|
|
3643
3279
|
if (displayMode === "native") {
|
|
3644
3280
|
console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
|
|
@@ -3693,7 +3329,7 @@ function discoverNativeDisplay() {
|
|
|
3693
3329
|
const leaderPid = leaderResult.stdout?.trim();
|
|
3694
3330
|
if (leaderPid) {
|
|
3695
3331
|
try {
|
|
3696
|
-
const environ =
|
|
3332
|
+
const environ = readFileSync3(`/proc/${leaderPid}/environ`, "utf8");
|
|
3697
3333
|
const match = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
|
|
3698
3334
|
if (match) waylandDisplay = match.split("=")[1];
|
|
3699
3335
|
} catch {
|
|
@@ -3751,7 +3387,7 @@ function ensureLogDir() {
|
|
|
3751
3387
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
3752
3388
|
}
|
|
3753
3389
|
function logPath(name) {
|
|
3754
|
-
return
|
|
3390
|
+
return resolve2(LOG_DIR, `${name}.log`);
|
|
3755
3391
|
}
|
|
3756
3392
|
async function ensureVnc() {
|
|
3757
3393
|
const up = await waitForPort(RFB_PORT, 1e3);
|
|
@@ -3830,8 +3466,8 @@ function killChromium() {
|
|
|
3830
3466
|
}
|
|
3831
3467
|
function writeChromiumWrapper() {
|
|
3832
3468
|
mkdirSync(BIN_DIR, { recursive: true });
|
|
3833
|
-
const wrapperPath =
|
|
3834
|
-
|
|
3469
|
+
const wrapperPath = resolve2(BIN_DIR, "chromium");
|
|
3470
|
+
writeFileSync2(wrapperPath, `#!/bin/bash
|
|
3835
3471
|
LOG="${LOG_DIR}/chromium.log"
|
|
3836
3472
|
echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
|
|
3837
3473
|
echo " DISPLAY=$DISPLAY WAYLAND=$WAYLAND_DISPLAY XDG_SESSION_TYPE=$XDG_SESSION_TYPE" >> "$LOG"
|
|
@@ -3952,8 +3588,8 @@ app.get("/", async (c) => {
|
|
|
3952
3588
|
const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
3953
3589
|
let pinConfigured = false;
|
|
3954
3590
|
try {
|
|
3955
|
-
if (
|
|
3956
|
-
const raw =
|
|
3591
|
+
if (existsSync2(USERS_FILE)) {
|
|
3592
|
+
const raw = readFileSync4(USERS_FILE, "utf-8").trim();
|
|
3957
3593
|
if (raw) {
|
|
3958
3594
|
const users = JSON.parse(raw);
|
|
3959
3595
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -4010,9 +3646,9 @@ var health_default = app;
|
|
|
4010
3646
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4011
3647
|
import { mkdir as mkdir2, readFile, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
4012
3648
|
import { realpathSync } from "fs";
|
|
4013
|
-
import { resolve as
|
|
4014
|
-
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ??
|
|
4015
|
-
var ATTACHMENTS_ROOT =
|
|
3649
|
+
import { resolve as resolve3, extname, basename } from "path";
|
|
3650
|
+
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "../platform");
|
|
3651
|
+
var ATTACHMENTS_ROOT = resolve3(PLATFORM_ROOT3, "..", "data/uploads");
|
|
4016
3652
|
var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
4017
3653
|
"image/jpeg",
|
|
4018
3654
|
"image/png",
|
|
@@ -4039,11 +3675,11 @@ function assertSupportedMime(mimeType) {
|
|
|
4039
3675
|
}
|
|
4040
3676
|
async function writeAttachment(scope, filename, mimeType, sizeBytes, buffer) {
|
|
4041
3677
|
const attachmentId = randomUUID3();
|
|
4042
|
-
const dir =
|
|
3678
|
+
const dir = resolve3(ATTACHMENTS_ROOT, scope, attachmentId);
|
|
4043
3679
|
await mkdir2(dir, { recursive: true });
|
|
4044
3680
|
const ext = extname(filename) || "";
|
|
4045
|
-
const storagePath =
|
|
4046
|
-
const metaPath =
|
|
3681
|
+
const storagePath = resolve3(dir, `${attachmentId}${ext}`);
|
|
3682
|
+
const metaPath = resolve3(dir, `${attachmentId}.meta.json`);
|
|
4047
3683
|
const meta = {
|
|
4048
3684
|
attachmentId,
|
|
4049
3685
|
scope,
|
|
@@ -4119,11 +3755,11 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
|
|
|
4119
3755
|
`Component artefact exceeds the 50 MB limit (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB).`
|
|
4120
3756
|
);
|
|
4121
3757
|
}
|
|
4122
|
-
const dir =
|
|
3758
|
+
const dir = resolve3(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
4123
3759
|
await mkdir2(dir, { recursive: true });
|
|
4124
3760
|
const ext = extname(filename) || (mimeType === "text/html" ? ".html" : ".md");
|
|
4125
|
-
const storagePath =
|
|
4126
|
-
const metaPath =
|
|
3761
|
+
const storagePath = resolve3(dir, `${attachmentId}${ext}`);
|
|
3762
|
+
const metaPath = resolve3(dir, `${attachmentId}.meta.json`);
|
|
4127
3763
|
const meta = {
|
|
4128
3764
|
attachmentId,
|
|
4129
3765
|
scope: accountId,
|
|
@@ -4140,8 +3776,8 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
|
|
|
4140
3776
|
// app/lib/stt/voice-note.ts
|
|
4141
3777
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
4142
3778
|
import { tmpdir } from "os";
|
|
4143
|
-
import { join as
|
|
4144
|
-
var
|
|
3779
|
+
import { join as join3 } from "path";
|
|
3780
|
+
var TAG13 = "[voice]";
|
|
4145
3781
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
4146
3782
|
"audio/ogg",
|
|
4147
3783
|
"audio/opus",
|
|
@@ -4179,19 +3815,19 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4179
3815
|
const bytes = file.size;
|
|
4180
3816
|
const mimeType = file.type;
|
|
4181
3817
|
console.error(
|
|
4182
|
-
`${
|
|
3818
|
+
`${TAG13} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
|
|
4183
3819
|
);
|
|
4184
3820
|
let tempDir;
|
|
4185
3821
|
let tempPath;
|
|
4186
3822
|
try {
|
|
4187
|
-
tempDir = await mkdtemp(
|
|
3823
|
+
tempDir = await mkdtemp(join3(tmpdir(), "voice-"));
|
|
4188
3824
|
const ext = audioExtension(mimeType);
|
|
4189
|
-
tempPath =
|
|
3825
|
+
tempPath = join3(tempDir, `recording${ext}`);
|
|
4190
3826
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
4191
3827
|
await writeFile3(tempPath, buffer);
|
|
4192
3828
|
} catch (err) {
|
|
4193
3829
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4194
|
-
console.error(`${
|
|
3830
|
+
console.error(`${TAG13} failed source=${source} error=temp-file-write: ${reason}`);
|
|
4195
3831
|
return { ok: false, error: "Could not process voice note" };
|
|
4196
3832
|
}
|
|
4197
3833
|
try {
|
|
@@ -4199,7 +3835,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4199
3835
|
if (!sttResult) {
|
|
4200
3836
|
const elapsed2 = Date.now() - startMs;
|
|
4201
3837
|
console.error(
|
|
4202
|
-
`${
|
|
3838
|
+
`${TAG13} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
|
|
4203
3839
|
);
|
|
4204
3840
|
return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
|
|
4205
3841
|
}
|
|
@@ -4207,7 +3843,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4207
3843
|
const elapsed = Date.now() - startMs;
|
|
4208
3844
|
const words = rawText.split(/\s+/).filter(Boolean).length;
|
|
4209
3845
|
console.error(
|
|
4210
|
-
`${
|
|
3846
|
+
`${TAG13} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
|
|
4211
3847
|
);
|
|
4212
3848
|
return {
|
|
4213
3849
|
ok: true,
|
|
@@ -4217,7 +3853,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4217
3853
|
const elapsed = Date.now() - startMs;
|
|
4218
3854
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4219
3855
|
console.error(
|
|
4220
|
-
`${
|
|
3856
|
+
`${TAG13} failed source=${source} error=${reason} duration_ms=${elapsed}`
|
|
4221
3857
|
);
|
|
4222
3858
|
return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
|
|
4223
3859
|
} finally {
|
|
@@ -4227,10 +3863,10 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4227
3863
|
}
|
|
4228
3864
|
|
|
4229
3865
|
// ../lib/oauth-llm/src/index.ts
|
|
4230
|
-
import { readFileSync as
|
|
4231
|
-
import { resolve as
|
|
3866
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
3867
|
+
import { resolve as resolve4 } from "path";
|
|
4232
3868
|
import { homedir } from "os";
|
|
4233
|
-
var CREDENTIALS_FILE = process.env.CLAUDE_CONFIG_DIR ?
|
|
3869
|
+
var CREDENTIALS_FILE = process.env.CLAUDE_CONFIG_DIR ? resolve4(process.env.CLAUDE_CONFIG_DIR, ".credentials.json") : resolve4(homedir(), ".claude", ".credentials.json");
|
|
4234
3870
|
var TOKEN_ENDPOINT2 = "https://platform.claude.com/v1/oauth/token";
|
|
4235
3871
|
var ANTHROPIC_MESSAGES_ENDPOINT = "https://api.anthropic.com/v1/messages";
|
|
4236
3872
|
var ANTHROPIC_VERSION = "2023-06-01";
|
|
@@ -4249,7 +3885,7 @@ function isInputTooLargeBody(body) {
|
|
|
4249
3885
|
function readCredentials2() {
|
|
4250
3886
|
let raw;
|
|
4251
3887
|
try {
|
|
4252
|
-
raw =
|
|
3888
|
+
raw = readFileSync5(CREDENTIALS_FILE, "utf-8");
|
|
4253
3889
|
} catch {
|
|
4254
3890
|
return null;
|
|
4255
3891
|
}
|
|
@@ -4278,7 +3914,7 @@ function writeCredentials(tokens) {
|
|
|
4278
3914
|
const newExpiresAt = typeof tokens.expiresIn === "number" && tokens.expiresIn > 0 ? Date.now() + tokens.expiresIn * 1e3 : Date.now() + 3600 * 1e3;
|
|
4279
3915
|
let fileData = {};
|
|
4280
3916
|
try {
|
|
4281
|
-
fileData = JSON.parse(
|
|
3917
|
+
fileData = JSON.parse(readFileSync5(CREDENTIALS_FILE, "utf-8"));
|
|
4282
3918
|
} catch {
|
|
4283
3919
|
}
|
|
4284
3920
|
const existing = fileData.claudeAiOauth;
|
|
@@ -4288,7 +3924,7 @@ function writeCredentials(tokens) {
|
|
|
4288
3924
|
...tokens.refreshToken != null ? { refreshToken: tokens.refreshToken } : {},
|
|
4289
3925
|
expiresAt: newExpiresAt
|
|
4290
3926
|
};
|
|
4291
|
-
|
|
3927
|
+
writeFileSync3(CREDENTIALS_FILE, JSON.stringify(fileData, null, 2), "utf-8");
|
|
4292
3928
|
return newExpiresAt;
|
|
4293
3929
|
}
|
|
4294
3930
|
var refreshLock2 = null;
|
|
@@ -4556,7 +4192,7 @@ var VERDICT_DEFINITIONS = {
|
|
|
4556
4192
|
};
|
|
4557
4193
|
|
|
4558
4194
|
// app/lib/inbound-gateway.ts
|
|
4559
|
-
var
|
|
4195
|
+
var TAG14 = "[inbound-gateway]";
|
|
4560
4196
|
var GATEWAY_TIMEOUT_MS = 1e4;
|
|
4561
4197
|
var MIN_WORDS_FOR_PROCESSING = 5;
|
|
4562
4198
|
function defaultResult(rawText, latencyMs) {
|
|
@@ -4679,11 +4315,11 @@ async function processInbound(rawText, channel) {
|
|
|
4679
4315
|
};
|
|
4680
4316
|
result.fallthrough = false;
|
|
4681
4317
|
console.warn(
|
|
4682
|
-
`${
|
|
4318
|
+
`${TAG14} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
|
|
4683
4319
|
);
|
|
4684
4320
|
} else {
|
|
4685
4321
|
console.log(
|
|
4686
|
-
`${
|
|
4322
|
+
`${TAG14} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
|
|
4687
4323
|
);
|
|
4688
4324
|
}
|
|
4689
4325
|
return result;
|
|
@@ -4700,13 +4336,13 @@ async function processInbound(rawText, channel) {
|
|
|
4700
4336
|
const latencyMs = Date.now() - startMs;
|
|
4701
4337
|
if (llmResult.kind === "fallback") {
|
|
4702
4338
|
console.warn(
|
|
4703
|
-
`${
|
|
4339
|
+
`${TAG14} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
|
|
4704
4340
|
);
|
|
4705
4341
|
return defaultResult(rawText.trim(), latencyMs);
|
|
4706
4342
|
}
|
|
4707
4343
|
if (llmResult.kind !== "ok-tool") {
|
|
4708
4344
|
console.warn(
|
|
4709
|
-
`${
|
|
4345
|
+
`${TAG14} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
|
|
4710
4346
|
);
|
|
4711
4347
|
return defaultResult(rawText.trim(), latencyMs);
|
|
4712
4348
|
}
|
|
@@ -4716,7 +4352,7 @@ async function processInbound(rawText, channel) {
|
|
|
4716
4352
|
const verdict = input.verdict;
|
|
4717
4353
|
if (!processedText || !verdict || !["clean", "suspicious", "discard"].includes(verdict)) {
|
|
4718
4354
|
console.warn(
|
|
4719
|
-
`${
|
|
4355
|
+
`${TAG14} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
|
|
4720
4356
|
);
|
|
4721
4357
|
return defaultResult(rawText.trim(), latencyMs);
|
|
4722
4358
|
}
|
|
@@ -4744,25 +4380,25 @@ async function processInbound(rawText, channel) {
|
|
|
4744
4380
|
fallthrough: false
|
|
4745
4381
|
};
|
|
4746
4382
|
console.log(
|
|
4747
|
-
`${
|
|
4383
|
+
`${TAG14} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
|
|
4748
4384
|
);
|
|
4749
4385
|
if (verdict !== "clean") {
|
|
4750
4386
|
console.warn(
|
|
4751
|
-
`${
|
|
4387
|
+
`${TAG14} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
|
|
4752
4388
|
);
|
|
4753
4389
|
}
|
|
4754
4390
|
return result;
|
|
4755
4391
|
} catch (err) {
|
|
4756
4392
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4757
4393
|
console.warn(
|
|
4758
|
-
`${
|
|
4394
|
+
`${TAG14} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
|
|
4759
4395
|
);
|
|
4760
4396
|
return defaultResult(rawText.trim(), latencyMs);
|
|
4761
4397
|
}
|
|
4762
4398
|
}
|
|
4763
4399
|
|
|
4764
4400
|
// app/lib/channel-pty-bridge/bridge.ts
|
|
4765
|
-
import { resolve as
|
|
4401
|
+
import { resolve as resolve5 } from "path";
|
|
4766
4402
|
|
|
4767
4403
|
// app/lib/channel-pty-bridge/manager-client.ts
|
|
4768
4404
|
function managerBase() {
|
|
@@ -4780,7 +4416,9 @@ async function managerSpawn(opts) {
|
|
|
4780
4416
|
accountId: opts.accountId,
|
|
4781
4417
|
attachmentDir: opts.attachmentDir,
|
|
4782
4418
|
aboutOwner: opts.aboutOwner,
|
|
4783
|
-
dormantPlugins: opts.dormantPlugins
|
|
4419
|
+
dormantPlugins: opts.dormantPlugins,
|
|
4420
|
+
activePlugins: opts.activePlugins,
|
|
4421
|
+
specialistDomains: opts.specialistDomains
|
|
4784
4422
|
})
|
|
4785
4423
|
}).catch((err) => ({ __throw: err instanceof Error ? err.message : String(err) }));
|
|
4786
4424
|
if ("__throw" in res) {
|
|
@@ -4922,7 +4560,7 @@ async function ensureEntry(input) {
|
|
|
4922
4560
|
}
|
|
4923
4561
|
return existing;
|
|
4924
4562
|
}
|
|
4925
|
-
const attachmentDir = input.role === "public" ?
|
|
4563
|
+
const attachmentDir = input.role === "public" ? resolve5(ATTACHMENTS_ROOT, "public", input.senderId) : void 0;
|
|
4926
4564
|
const spawned = await managerSpawn({
|
|
4927
4565
|
senderId: input.senderId,
|
|
4928
4566
|
role: input.role,
|
|
@@ -5285,14 +4923,14 @@ import { realpathSync as realpathSync2, readdirSync as readdirSync2, readFileSyn
|
|
|
5285
4923
|
|
|
5286
4924
|
// app/lib/whatsapp/login.ts
|
|
5287
4925
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5288
|
-
var
|
|
4926
|
+
var TAG15 = "[whatsapp:login]";
|
|
5289
4927
|
var ACTIVE_LOGIN_TTL_MS = 3 * 6e4;
|
|
5290
4928
|
var activeLogins = /* @__PURE__ */ new Map();
|
|
5291
4929
|
function closeSocket(sock) {
|
|
5292
4930
|
try {
|
|
5293
4931
|
sock.ws?.close?.();
|
|
5294
4932
|
} catch (err) {
|
|
5295
|
-
console.warn(`${
|
|
4933
|
+
console.warn(`${TAG15} socket close error during cleanup: ${String(err)}`);
|
|
5296
4934
|
}
|
|
5297
4935
|
}
|
|
5298
4936
|
function resetActiveLogin(accountId) {
|
|
@@ -5315,7 +4953,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
5315
4953
|
const current = activeLogins.get(accountId);
|
|
5316
4954
|
if (current?.id === login.id) {
|
|
5317
4955
|
current.connected = true;
|
|
5318
|
-
console.error(`${
|
|
4956
|
+
console.error(`${TAG15} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
|
|
5319
4957
|
}
|
|
5320
4958
|
return;
|
|
5321
4959
|
} catch (err) {
|
|
@@ -5325,7 +4963,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
5325
4963
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
5326
4964
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
5327
4965
|
console.error(
|
|
5328
|
-
`${
|
|
4966
|
+
`${TAG15} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
5329
4967
|
);
|
|
5330
4968
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
5331
4969
|
} else {
|
|
@@ -5337,7 +4975,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
5337
4975
|
attempt++;
|
|
5338
4976
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
5339
4977
|
console.error(
|
|
5340
|
-
`${
|
|
4978
|
+
`${TAG15} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
5341
4979
|
);
|
|
5342
4980
|
closeSocket(current.sock);
|
|
5343
4981
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -5348,7 +4986,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
5348
4986
|
current.sock = newSock;
|
|
5349
4987
|
} catch (sockErr) {
|
|
5350
4988
|
console.error(
|
|
5351
|
-
`${
|
|
4989
|
+
`${TAG15} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
5352
4990
|
);
|
|
5353
4991
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
5354
4992
|
return;
|
|
@@ -5362,7 +5000,7 @@ async function startLogin(opts) {
|
|
|
5362
5000
|
const hasAuth = await authExists(authDir);
|
|
5363
5001
|
const selfId = readSelfId(authDir);
|
|
5364
5002
|
console.error(
|
|
5365
|
-
`${
|
|
5003
|
+
`${TAG15} startLogin account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasQr=${!!existing0.qr}}` : " activeLogin=none")
|
|
5366
5004
|
);
|
|
5367
5005
|
if (hasAuth && !force) {
|
|
5368
5006
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -5374,7 +5012,7 @@ async function startLogin(opts) {
|
|
|
5374
5012
|
await clearAuth(authDir);
|
|
5375
5013
|
const existing = activeLogins.get(accountId);
|
|
5376
5014
|
if (existing && isLoginFresh(existing) && existing.qrDataUrl && !force) {
|
|
5377
|
-
console.error(`${
|
|
5015
|
+
console.error(`${TAG15} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
5378
5016
|
return {
|
|
5379
5017
|
qrDataUrl: existing.qrDataUrl,
|
|
5380
5018
|
qrRaw: existing.qr,
|
|
@@ -5382,7 +5020,7 @@ async function startLogin(opts) {
|
|
|
5382
5020
|
};
|
|
5383
5021
|
}
|
|
5384
5022
|
if (existing) {
|
|
5385
|
-
console.error(`${
|
|
5023
|
+
console.error(`${TAG15} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
|
|
5386
5024
|
}
|
|
5387
5025
|
resetActiveLogin(accountId);
|
|
5388
5026
|
let resolveQr = null;
|
|
@@ -5399,99 +5037,425 @@ async function startLogin(opts) {
|
|
|
5399
5037
|
let pendingQr = null;
|
|
5400
5038
|
let loginQrCount = 0;
|
|
5401
5039
|
try {
|
|
5402
|
-
sock = await createWaSocket({
|
|
5403
|
-
authDir,
|
|
5404
|
-
onQr: (qr2) => {
|
|
5405
|
-
loginQrCount++;
|
|
5406
|
-
if (pendingQr) {
|
|
5407
|
-
console.error(`${
|
|
5408
|
-
return;
|
|
5409
|
-
}
|
|
5410
|
-
pendingQr = qr2;
|
|
5411
|
-
const current = activeLogins.get(accountId);
|
|
5412
|
-
if (current && !current.qr) current.qr = qr2;
|
|
5413
|
-
clearTimeout(qrTimer);
|
|
5414
|
-
console.error(`${
|
|
5415
|
-
resolveQr?.(qr2);
|
|
5416
|
-
}
|
|
5417
|
-
});
|
|
5040
|
+
sock = await createWaSocket({
|
|
5041
|
+
authDir,
|
|
5042
|
+
onQr: (qr2) => {
|
|
5043
|
+
loginQrCount++;
|
|
5044
|
+
if (pendingQr) {
|
|
5045
|
+
console.error(`${TAG15} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
|
|
5046
|
+
return;
|
|
5047
|
+
}
|
|
5048
|
+
pendingQr = qr2;
|
|
5049
|
+
const current = activeLogins.get(accountId);
|
|
5050
|
+
if (current && !current.qr) current.qr = qr2;
|
|
5051
|
+
clearTimeout(qrTimer);
|
|
5052
|
+
console.error(`${TAG15} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
|
|
5053
|
+
resolveQr?.(qr2);
|
|
5054
|
+
}
|
|
5055
|
+
});
|
|
5056
|
+
} catch (err) {
|
|
5057
|
+
clearTimeout(qrTimer);
|
|
5058
|
+
resetActiveLogin(accountId);
|
|
5059
|
+
return { message: `Failed to start WhatsApp login: ${String(err)}` };
|
|
5060
|
+
}
|
|
5061
|
+
const login = {
|
|
5062
|
+
accountId,
|
|
5063
|
+
authDir,
|
|
5064
|
+
id: randomUUID4(),
|
|
5065
|
+
sock,
|
|
5066
|
+
startedAt: Date.now(),
|
|
5067
|
+
connected: false
|
|
5068
|
+
};
|
|
5069
|
+
activeLogins.set(accountId, login);
|
|
5070
|
+
if (pendingQr && !login.qr) login.qr = pendingQr;
|
|
5071
|
+
loginConnectionLoop(accountId, login).catch((err) => {
|
|
5072
|
+
console.error(`${TAG15} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
5073
|
+
const current = activeLogins.get(accountId);
|
|
5074
|
+
if (current?.id === login.id) {
|
|
5075
|
+
current.error = `Unexpected login error: ${String(err)}`;
|
|
5076
|
+
}
|
|
5077
|
+
});
|
|
5078
|
+
let qr;
|
|
5079
|
+
try {
|
|
5080
|
+
qr = await qrPromise;
|
|
5081
|
+
} catch (err) {
|
|
5082
|
+
clearTimeout(qrTimer);
|
|
5083
|
+
resetActiveLogin(accountId);
|
|
5084
|
+
return { message: `Failed to get QR: ${String(err)}` };
|
|
5085
|
+
}
|
|
5086
|
+
login.qrDataUrl = `qr:${qr}`;
|
|
5087
|
+
return {
|
|
5088
|
+
qrDataUrl: login.qrDataUrl,
|
|
5089
|
+
qrRaw: qr,
|
|
5090
|
+
message: "Scan this QR in WhatsApp \u2192 Linked Devices."
|
|
5091
|
+
};
|
|
5092
|
+
}
|
|
5093
|
+
async function waitForLogin(opts) {
|
|
5094
|
+
const { accountId, timeoutMs = 6e4 } = opts;
|
|
5095
|
+
const login = activeLogins.get(accountId);
|
|
5096
|
+
console.error(
|
|
5097
|
+
`${TAG15} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasQr=${!!login.qr}}` : " login=none")
|
|
5098
|
+
);
|
|
5099
|
+
if (!login) {
|
|
5100
|
+
return { connected: false, message: "No active WhatsApp login in progress." };
|
|
5101
|
+
}
|
|
5102
|
+
if (!isLoginFresh(login)) {
|
|
5103
|
+
resetActiveLogin(accountId);
|
|
5104
|
+
return { connected: false, message: "The login QR expired. Ask me to generate a new one." };
|
|
5105
|
+
}
|
|
5106
|
+
const deadline = Date.now() + timeoutMs;
|
|
5107
|
+
while (Date.now() < deadline) {
|
|
5108
|
+
if (login.connected) {
|
|
5109
|
+
const selfId = readSelfId(login.authDir);
|
|
5110
|
+
console.error(`${TAG15} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
5111
|
+
const sock = login.sock;
|
|
5112
|
+
const authDir = login.authDir;
|
|
5113
|
+
activeLogins.delete(accountId);
|
|
5114
|
+
return {
|
|
5115
|
+
connected: true,
|
|
5116
|
+
message: `WhatsApp connected as ${selfId.e164 ?? selfId.jid ?? "unknown"}.`,
|
|
5117
|
+
selfPhone: selfId.e164 ?? void 0,
|
|
5118
|
+
sock,
|
|
5119
|
+
authDir
|
|
5120
|
+
};
|
|
5121
|
+
}
|
|
5122
|
+
if (login.error) {
|
|
5123
|
+
const msg = login.error;
|
|
5124
|
+
resetActiveLogin(accountId);
|
|
5125
|
+
return { connected: false, message: `WhatsApp login failed: ${msg}` };
|
|
5126
|
+
}
|
|
5127
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
5128
|
+
}
|
|
5129
|
+
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
5130
|
+
console.error(`${TAG15} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
|
|
5131
|
+
resetActiveLogin(accountId);
|
|
5132
|
+
return { connected: false, message: "Login timed out. Try generating a new QR." };
|
|
5133
|
+
}
|
|
5134
|
+
|
|
5135
|
+
// app/lib/whatsapp/config-persist.ts
|
|
5136
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync3 } from "fs";
|
|
5137
|
+
import { resolve as resolve6, join as join4 } from "path";
|
|
5138
|
+
var TAG16 = "[whatsapp:config]";
|
|
5139
|
+
function configPath(accountDir) {
|
|
5140
|
+
return resolve6(accountDir, "account.json");
|
|
5141
|
+
}
|
|
5142
|
+
function readConfig(accountDir) {
|
|
5143
|
+
const path2 = configPath(accountDir);
|
|
5144
|
+
if (!existsSync3(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
5145
|
+
return JSON.parse(readFileSync6(path2, "utf-8"));
|
|
5146
|
+
}
|
|
5147
|
+
function writeConfig(accountDir, config) {
|
|
5148
|
+
const path2 = configPath(accountDir);
|
|
5149
|
+
writeFileSync4(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
5150
|
+
}
|
|
5151
|
+
function reloadManagerConfig(accountDir) {
|
|
5152
|
+
try {
|
|
5153
|
+
const config = readConfig(accountDir);
|
|
5154
|
+
reloadConfig(config);
|
|
5155
|
+
console.error(`${TAG16} reloaded manager config`);
|
|
5156
|
+
} catch (err) {
|
|
5157
|
+
console.error(`${TAG16} manager config reload failed: ${String(err)}`);
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
var E164_PATTERN = /^\+\d{7,15}$/;
|
|
5161
|
+
function persistAfterPairing(accountDir, accountId, selfPhone) {
|
|
5162
|
+
try {
|
|
5163
|
+
const config = readConfig(accountDir);
|
|
5164
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
5165
|
+
config.whatsapp = {};
|
|
5166
|
+
}
|
|
5167
|
+
const wa = config.whatsapp;
|
|
5168
|
+
if (!wa.accounts || typeof wa.accounts !== "object") {
|
|
5169
|
+
wa.accounts = {};
|
|
5170
|
+
}
|
|
5171
|
+
const accounts = wa.accounts;
|
|
5172
|
+
if (!accounts[accountId] || typeof accounts[accountId] !== "object") {
|
|
5173
|
+
accounts[accountId] = { name: "Main" };
|
|
5174
|
+
}
|
|
5175
|
+
if (selfPhone) {
|
|
5176
|
+
const normalized = selfPhone.startsWith("+") ? selfPhone : `+${selfPhone}`;
|
|
5177
|
+
if (!Array.isArray(wa.adminPhones)) {
|
|
5178
|
+
wa.adminPhones = [];
|
|
5179
|
+
}
|
|
5180
|
+
const adminPhones = wa.adminPhones;
|
|
5181
|
+
if (!adminPhones.includes(normalized)) {
|
|
5182
|
+
adminPhones.push(normalized);
|
|
5183
|
+
console.error(`${TAG16} added selfPhone=${normalized} to adminPhones`);
|
|
5184
|
+
}
|
|
5185
|
+
} else {
|
|
5186
|
+
console.error(`${TAG16} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
|
|
5187
|
+
}
|
|
5188
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5189
|
+
if (!parsed.success) {
|
|
5190
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5191
|
+
console.error(`${TAG16} validation failed after pairing: ${msg}`);
|
|
5192
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5193
|
+
}
|
|
5194
|
+
config.whatsapp = parsed.data;
|
|
5195
|
+
writeConfig(accountDir, config);
|
|
5196
|
+
console.error(`${TAG16} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
|
|
5197
|
+
reloadManagerConfig(accountDir);
|
|
5198
|
+
return { ok: true };
|
|
5199
|
+
} catch (err) {
|
|
5200
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5201
|
+
console.error(`${TAG16} persist failed account=${accountId}: ${msg}`);
|
|
5202
|
+
return { ok: false, error: msg };
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
function addAdminPhone(accountDir, phone) {
|
|
5206
|
+
const normalized = phone.trim();
|
|
5207
|
+
if (!E164_PATTERN.test(normalized)) {
|
|
5208
|
+
return { ok: false, error: `Invalid phone format "${normalized}". Expected E.164 (e.g. +441234567890).` };
|
|
5209
|
+
}
|
|
5210
|
+
try {
|
|
5211
|
+
const config = readConfig(accountDir);
|
|
5212
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
5213
|
+
config.whatsapp = {};
|
|
5214
|
+
}
|
|
5215
|
+
const wa = config.whatsapp;
|
|
5216
|
+
if (!Array.isArray(wa.adminPhones)) {
|
|
5217
|
+
wa.adminPhones = [];
|
|
5218
|
+
}
|
|
5219
|
+
const adminPhones = wa.adminPhones;
|
|
5220
|
+
if (adminPhones.includes(normalized)) {
|
|
5221
|
+
return { ok: true, message: `Phone ${normalized} is already in the admin list.` };
|
|
5222
|
+
}
|
|
5223
|
+
adminPhones.push(normalized);
|
|
5224
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5225
|
+
if (!parsed.success) {
|
|
5226
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5227
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5228
|
+
}
|
|
5229
|
+
config.whatsapp = parsed.data;
|
|
5230
|
+
writeConfig(accountDir, config);
|
|
5231
|
+
console.error(`${TAG16} added admin phone=${normalized}`);
|
|
5232
|
+
reloadManagerConfig(accountDir);
|
|
5233
|
+
return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
|
|
5234
|
+
} catch (err) {
|
|
5235
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5236
|
+
console.error(`${TAG16} addAdminPhone failed: ${msg}`);
|
|
5237
|
+
return { ok: false, error: msg };
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
function removeAdminPhone(accountDir, phone) {
|
|
5241
|
+
const normalized = phone.trim();
|
|
5242
|
+
try {
|
|
5243
|
+
const config = readConfig(accountDir);
|
|
5244
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
5245
|
+
return { ok: true, message: `No WhatsApp config exists \u2014 nothing to remove.` };
|
|
5246
|
+
}
|
|
5247
|
+
const wa = config.whatsapp;
|
|
5248
|
+
if (!Array.isArray(wa.adminPhones)) {
|
|
5249
|
+
return { ok: true, message: `No admin phones configured \u2014 nothing to remove.` };
|
|
5250
|
+
}
|
|
5251
|
+
const adminPhones = wa.adminPhones;
|
|
5252
|
+
const idx = adminPhones.indexOf(normalized);
|
|
5253
|
+
if (idx === -1) {
|
|
5254
|
+
return { ok: true, message: `Phone ${normalized} is not in the admin list.` };
|
|
5255
|
+
}
|
|
5256
|
+
adminPhones.splice(idx, 1);
|
|
5257
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5258
|
+
if (!parsed.success) {
|
|
5259
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5260
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5261
|
+
}
|
|
5262
|
+
config.whatsapp = parsed.data;
|
|
5263
|
+
writeConfig(accountDir, config);
|
|
5264
|
+
console.error(`${TAG16} removed admin phone=${normalized}`);
|
|
5265
|
+
reloadManagerConfig(accountDir);
|
|
5266
|
+
return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
|
|
5267
|
+
} catch (err) {
|
|
5268
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5269
|
+
console.error(`${TAG16} removeAdminPhone failed: ${msg}`);
|
|
5270
|
+
return { ok: false, error: msg };
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
function readAdminPhones(accountDir) {
|
|
5274
|
+
try {
|
|
5275
|
+
const config = readConfig(accountDir);
|
|
5276
|
+
const wa = config.whatsapp;
|
|
5277
|
+
if (!wa) return [];
|
|
5278
|
+
if (!Array.isArray(wa.adminPhones)) return [];
|
|
5279
|
+
return wa.adminPhones.filter((p) => typeof p === "string");
|
|
5280
|
+
} catch {
|
|
5281
|
+
return [];
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
function setPublicAgent(accountDir, slug) {
|
|
5285
|
+
const trimmed = slug.trim();
|
|
5286
|
+
if (!trimmed) {
|
|
5287
|
+
return { ok: false, error: "Agent slug cannot be empty." };
|
|
5288
|
+
}
|
|
5289
|
+
const agentConfigPath = join4(accountDir, "agents", trimmed, "config.json");
|
|
5290
|
+
if (!existsSync3(agentConfigPath)) {
|
|
5291
|
+
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
5292
|
+
}
|
|
5293
|
+
try {
|
|
5294
|
+
const config = readConfig(accountDir);
|
|
5295
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
5296
|
+
config.whatsapp = {};
|
|
5297
|
+
}
|
|
5298
|
+
const wa = config.whatsapp;
|
|
5299
|
+
wa.publicAgent = trimmed;
|
|
5300
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5301
|
+
if (!parsed.success) {
|
|
5302
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5303
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5304
|
+
}
|
|
5305
|
+
config.whatsapp = parsed.data;
|
|
5306
|
+
writeConfig(accountDir, config);
|
|
5307
|
+
console.error(`${TAG16} publicAgent set to ${trimmed}`);
|
|
5308
|
+
reloadManagerConfig(accountDir);
|
|
5309
|
+
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
5310
|
+
} catch (err) {
|
|
5311
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5312
|
+
console.error(`${TAG16} setPublicAgent failed: ${msg}`);
|
|
5313
|
+
return { ok: false, error: msg };
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
function resolvePublicAgent(accountDir, opts) {
|
|
5317
|
+
try {
|
|
5318
|
+
const config = readConfig(accountDir);
|
|
5319
|
+
const wa = config.whatsapp;
|
|
5320
|
+
if (!wa) return null;
|
|
5321
|
+
if (opts.groupJid) {
|
|
5322
|
+
const accounts2 = wa.accounts;
|
|
5323
|
+
const account2 = accounts2?.[opts.accountId];
|
|
5324
|
+
const groups = account2?.groups;
|
|
5325
|
+
const group = groups?.[opts.groupJid];
|
|
5326
|
+
const groupSlug = group?.publicAgent;
|
|
5327
|
+
if (typeof groupSlug === "string" && groupSlug.trim()) {
|
|
5328
|
+
return { slug: groupSlug.trim(), source: "group" };
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
const accounts = wa.accounts;
|
|
5332
|
+
const account = accounts?.[opts.accountId];
|
|
5333
|
+
const accountSlug = account?.publicAgent;
|
|
5334
|
+
if (typeof accountSlug === "string" && accountSlug.trim()) {
|
|
5335
|
+
return { slug: accountSlug.trim(), source: "account" };
|
|
5336
|
+
}
|
|
5337
|
+
const topSlug = wa.publicAgent;
|
|
5338
|
+
if (typeof topSlug === "string" && topSlug.trim()) {
|
|
5339
|
+
return { slug: topSlug.trim(), source: "top-level" };
|
|
5340
|
+
}
|
|
5341
|
+
return null;
|
|
5342
|
+
} catch {
|
|
5343
|
+
return null;
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
function setGroupPublicAgent(accountDir, accountId, groupJid, slug) {
|
|
5347
|
+
const trimmedSlug = slug.trim();
|
|
5348
|
+
const trimmedGroup = groupJid.trim();
|
|
5349
|
+
const trimmedAccount = accountId.trim();
|
|
5350
|
+
if (!trimmedSlug) return { ok: false, error: "Agent slug cannot be empty." };
|
|
5351
|
+
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
5352
|
+
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
5353
|
+
const agentConfigPath = join4(accountDir, "agents", trimmedSlug, "config.json");
|
|
5354
|
+
if (!existsSync3(agentConfigPath)) {
|
|
5355
|
+
return { ok: false, error: `Agent "${trimmedSlug}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
5356
|
+
}
|
|
5357
|
+
try {
|
|
5358
|
+
const config = readConfig(accountDir);
|
|
5359
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") config.whatsapp = {};
|
|
5360
|
+
const wa = config.whatsapp;
|
|
5361
|
+
if (!wa.accounts || typeof wa.accounts !== "object") wa.accounts = {};
|
|
5362
|
+
const accounts = wa.accounts;
|
|
5363
|
+
if (!accounts[trimmedAccount] || typeof accounts[trimmedAccount] !== "object") accounts[trimmedAccount] = {};
|
|
5364
|
+
const account = accounts[trimmedAccount];
|
|
5365
|
+
if (!account.groups || typeof account.groups !== "object") account.groups = {};
|
|
5366
|
+
const groups = account.groups;
|
|
5367
|
+
const existing = groups[trimmedGroup] && typeof groups[trimmedGroup] === "object" ? groups[trimmedGroup] : {};
|
|
5368
|
+
groups[trimmedGroup] = { ...existing, publicAgent: trimmedSlug };
|
|
5369
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5370
|
+
if (!parsed.success) {
|
|
5371
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5372
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5373
|
+
}
|
|
5374
|
+
config.whatsapp = parsed.data;
|
|
5375
|
+
writeConfig(accountDir, config);
|
|
5376
|
+
console.error(`${TAG16} setGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup} slug=${trimmedSlug}`);
|
|
5377
|
+
reloadManagerConfig(accountDir);
|
|
5378
|
+
return { ok: true, message: `Per-group public agent set to "${trimmedSlug}" for group ${trimmedGroup}.` };
|
|
5418
5379
|
} catch (err) {
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
return {
|
|
5380
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5381
|
+
console.error(`${TAG16} setGroupPublicAgent failed: ${msg}`);
|
|
5382
|
+
return { ok: false, error: msg };
|
|
5422
5383
|
}
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
connected: false
|
|
5430
|
-
};
|
|
5431
|
-
activeLogins.set(accountId, login);
|
|
5432
|
-
if (pendingQr && !login.qr) login.qr = pendingQr;
|
|
5433
|
-
loginConnectionLoop(accountId, login).catch((err) => {
|
|
5434
|
-
console.error(`${TAG16} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
5435
|
-
const current = activeLogins.get(accountId);
|
|
5436
|
-
if (current?.id === login.id) {
|
|
5437
|
-
current.error = `Unexpected login error: ${String(err)}`;
|
|
5438
|
-
}
|
|
5439
|
-
});
|
|
5440
|
-
let qr;
|
|
5384
|
+
}
|
|
5385
|
+
function unsetGroupPublicAgent(accountDir, accountId, groupJid) {
|
|
5386
|
+
const trimmedGroup = groupJid.trim();
|
|
5387
|
+
const trimmedAccount = accountId.trim();
|
|
5388
|
+
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
5389
|
+
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
5441
5390
|
try {
|
|
5442
|
-
|
|
5391
|
+
const config = readConfig(accountDir);
|
|
5392
|
+
const wa = config.whatsapp ?? {};
|
|
5393
|
+
const accounts = wa.accounts;
|
|
5394
|
+
const account = accounts?.[trimmedAccount];
|
|
5395
|
+
const groups = account?.groups;
|
|
5396
|
+
const group = groups?.[trimmedGroup];
|
|
5397
|
+
if (!group || typeof group.publicAgent !== "string") {
|
|
5398
|
+
return { ok: true, message: `No per-group publicAgent override for group ${trimmedGroup}; nothing to remove.` };
|
|
5399
|
+
}
|
|
5400
|
+
delete group.publicAgent;
|
|
5401
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5402
|
+
if (!parsed.success) {
|
|
5403
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5404
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5405
|
+
}
|
|
5406
|
+
config.whatsapp = parsed.data;
|
|
5407
|
+
writeConfig(accountDir, config);
|
|
5408
|
+
console.error(`${TAG16} unsetGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup}`);
|
|
5409
|
+
reloadManagerConfig(accountDir);
|
|
5410
|
+
return { ok: true, message: `Per-group public agent override removed for group ${trimmedGroup}.` };
|
|
5443
5411
|
} catch (err) {
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
return {
|
|
5412
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5413
|
+
console.error(`${TAG16} unsetGroupPublicAgent failed: ${msg}`);
|
|
5414
|
+
return { ok: false, error: msg };
|
|
5447
5415
|
}
|
|
5448
|
-
login.qrDataUrl = `qr:${qr}`;
|
|
5449
|
-
return {
|
|
5450
|
-
qrDataUrl: login.qrDataUrl,
|
|
5451
|
-
qrRaw: qr,
|
|
5452
|
-
message: "Scan this QR in WhatsApp \u2192 Linked Devices."
|
|
5453
|
-
};
|
|
5454
5416
|
}
|
|
5455
|
-
|
|
5456
|
-
const
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
`${TAG16} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasQr=${!!login.qr}}` : " login=none")
|
|
5460
|
-
);
|
|
5461
|
-
if (!login) {
|
|
5462
|
-
return { connected: false, message: "No active WhatsApp login in progress." };
|
|
5463
|
-
}
|
|
5464
|
-
if (!isLoginFresh(login)) {
|
|
5465
|
-
resetActiveLogin(accountId);
|
|
5466
|
-
return { connected: false, message: "The login QR expired. Ask me to generate a new one." };
|
|
5417
|
+
function updateConfig(accountDir, fields) {
|
|
5418
|
+
const fieldNames = Object.keys(fields);
|
|
5419
|
+
if (fieldNames.length === 0) {
|
|
5420
|
+
return { ok: false, error: "No fields provided to update." };
|
|
5467
5421
|
}
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
if (
|
|
5471
|
-
|
|
5472
|
-
console.error(`${TAG16} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
5473
|
-
const sock = login.sock;
|
|
5474
|
-
const authDir = login.authDir;
|
|
5475
|
-
activeLogins.delete(accountId);
|
|
5476
|
-
return {
|
|
5477
|
-
connected: true,
|
|
5478
|
-
message: `WhatsApp connected as ${selfId.e164 ?? selfId.jid ?? "unknown"}.`,
|
|
5479
|
-
selfPhone: selfId.e164 ?? void 0,
|
|
5480
|
-
sock,
|
|
5481
|
-
authDir
|
|
5482
|
-
};
|
|
5422
|
+
try {
|
|
5423
|
+
const config = readConfig(accountDir);
|
|
5424
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
5425
|
+
config.whatsapp = {};
|
|
5483
5426
|
}
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
return { connected: false, message: `WhatsApp login failed: ${msg}` };
|
|
5427
|
+
const wa = config.whatsapp;
|
|
5428
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
5429
|
+
wa[key] = value;
|
|
5488
5430
|
}
|
|
5489
|
-
|
|
5431
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5432
|
+
if (!parsed.success) {
|
|
5433
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5434
|
+
console.error(`${TAG16} update validation failed: ${msg}`);
|
|
5435
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5436
|
+
}
|
|
5437
|
+
config.whatsapp = parsed.data;
|
|
5438
|
+
writeConfig(accountDir, config);
|
|
5439
|
+
console.error(`${TAG16} updated fields=[${fieldNames.join(",")}]`);
|
|
5440
|
+
reloadManagerConfig(accountDir);
|
|
5441
|
+
return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
|
|
5442
|
+
} catch (err) {
|
|
5443
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5444
|
+
console.error(`${TAG16} updateConfig failed: ${msg}`);
|
|
5445
|
+
return { ok: false, error: msg };
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
function getConfig(accountDir) {
|
|
5449
|
+
try {
|
|
5450
|
+
const config = readConfig(accountDir);
|
|
5451
|
+
const wa = config.whatsapp;
|
|
5452
|
+
if (!wa) return null;
|
|
5453
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5454
|
+
if (!parsed.success) return wa;
|
|
5455
|
+
return parsed.data;
|
|
5456
|
+
} catch {
|
|
5457
|
+
return null;
|
|
5490
5458
|
}
|
|
5491
|
-
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
5492
|
-
console.error(`${TAG16} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
|
|
5493
|
-
resetActiveLogin(accountId);
|
|
5494
|
-
return { connected: false, message: "Login timed out. Try generating a new QR." };
|
|
5495
5459
|
}
|
|
5496
5460
|
|
|
5497
5461
|
// app/lib/whatsapp/validate.ts
|
|
@@ -7886,8 +7850,8 @@ app14.put("/:id/label", requireAdminSession, async (c) => {
|
|
|
7886
7850
|
var sessions_default = app14;
|
|
7887
7851
|
|
|
7888
7852
|
// app/lib/claude-agent/spawn-context.ts
|
|
7889
|
-
import { existsSync as existsSync14, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
7890
|
-
import { resolve as resolve11 } from "path";
|
|
7853
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
|
|
7854
|
+
import { dirname as dirname2, resolve as resolve11, join as join10 } from "path";
|
|
7891
7855
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
7892
7856
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
7893
7857
|
try {
|
|
@@ -7913,6 +7877,213 @@ function frontmatterField(manifest, field) {
|
|
|
7913
7877
|
}
|
|
7914
7878
|
return value || null;
|
|
7915
7879
|
}
|
|
7880
|
+
function extractPluginToolDescriptions(pluginDir) {
|
|
7881
|
+
const out = /* @__PURE__ */ new Map();
|
|
7882
|
+
const candidates = [
|
|
7883
|
+
join10(pluginDir, "mcp/dist/index.js"),
|
|
7884
|
+
join10(pluginDir, "mcp/src/index.ts")
|
|
7885
|
+
];
|
|
7886
|
+
let raw = null;
|
|
7887
|
+
for (const path2 of candidates) {
|
|
7888
|
+
if (!existsSync14(path2)) continue;
|
|
7889
|
+
try {
|
|
7890
|
+
raw = readFileSync13(path2, "utf-8");
|
|
7891
|
+
break;
|
|
7892
|
+
} catch {
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7895
|
+
if (!raw) return out;
|
|
7896
|
+
const re = /(?:server\.tool\(|eagerTool\(\s*server\s*,)\s*"((?:[^"\\]|\\.)*)"\s*,\s*"((?:[^"\\]|\\.)*)"/g;
|
|
7897
|
+
let m;
|
|
7898
|
+
while ((m = re.exec(raw)) !== null) {
|
|
7899
|
+
const name = m[1];
|
|
7900
|
+
const desc = m[2].replace(/\\n/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\").replace(/\s+/g, " ").trim();
|
|
7901
|
+
if (name && desc && !out.has(name)) out.set(name, desc);
|
|
7902
|
+
}
|
|
7903
|
+
return out;
|
|
7904
|
+
}
|
|
7905
|
+
function parseToolNamesFromFrontmatter(fm) {
|
|
7906
|
+
const lines = fm.split(/\r?\n/);
|
|
7907
|
+
let i = 0;
|
|
7908
|
+
while (i < lines.length && !/^tools:\s*$/.test(lines[i])) i++;
|
|
7909
|
+
if (i >= lines.length) return [];
|
|
7910
|
+
i++;
|
|
7911
|
+
const out = [];
|
|
7912
|
+
for (; i < lines.length; i++) {
|
|
7913
|
+
const line = lines[i];
|
|
7914
|
+
if (line.length === 0) continue;
|
|
7915
|
+
if (/^[A-Za-z_]/.test(line)) break;
|
|
7916
|
+
const m = line.match(/^\s+-\s+name:\s*(.+?)\s*$/);
|
|
7917
|
+
if (m) out.push(m[1]);
|
|
7918
|
+
}
|
|
7919
|
+
return out;
|
|
7920
|
+
}
|
|
7921
|
+
function parseSkillPathsFromFrontmatter(fm) {
|
|
7922
|
+
const lines = fm.split(/\r?\n/);
|
|
7923
|
+
let i = 0;
|
|
7924
|
+
while (i < lines.length && !/^skills:\s*$/.test(lines[i])) i++;
|
|
7925
|
+
if (i >= lines.length) return [];
|
|
7926
|
+
i++;
|
|
7927
|
+
const out = [];
|
|
7928
|
+
for (; i < lines.length; i++) {
|
|
7929
|
+
const line = lines[i];
|
|
7930
|
+
if (line.length === 0) continue;
|
|
7931
|
+
if (/^[A-Za-z_]/.test(line)) break;
|
|
7932
|
+
const m = line.match(/^\s+-\s+(.+?)\s*$/);
|
|
7933
|
+
if (m) out.push(m[1]);
|
|
7934
|
+
}
|
|
7935
|
+
return out;
|
|
7936
|
+
}
|
|
7937
|
+
function listPluginDirs() {
|
|
7938
|
+
const out = /* @__PURE__ */ new Map();
|
|
7939
|
+
const platformPlugins = resolve11(PLATFORM_ROOT, "plugins");
|
|
7940
|
+
if (existsSync14(platformPlugins)) {
|
|
7941
|
+
for (const name of readdirSync7(platformPlugins)) {
|
|
7942
|
+
const dir = join10(platformPlugins, name);
|
|
7943
|
+
try {
|
|
7944
|
+
if (statSync6(dir).isDirectory()) out.set(name, dir);
|
|
7945
|
+
} catch {
|
|
7946
|
+
}
|
|
7947
|
+
}
|
|
7948
|
+
}
|
|
7949
|
+
const premiumRoot = resolve11(dirname2(PLATFORM_ROOT), "premium-plugins");
|
|
7950
|
+
if (existsSync14(premiumRoot)) {
|
|
7951
|
+
for (const bundle of readdirSync7(premiumRoot)) {
|
|
7952
|
+
const bundlePlugins = join10(premiumRoot, bundle, "plugins");
|
|
7953
|
+
if (!existsSync14(bundlePlugins)) continue;
|
|
7954
|
+
try {
|
|
7955
|
+
for (const name of readdirSync7(bundlePlugins)) {
|
|
7956
|
+
const dir = join10(bundlePlugins, name);
|
|
7957
|
+
try {
|
|
7958
|
+
if (statSync6(dir).isDirectory()) out.set(name, dir);
|
|
7959
|
+
} catch {
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7962
|
+
} catch {
|
|
7963
|
+
}
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
return out;
|
|
7967
|
+
}
|
|
7968
|
+
function readEnabledPlugins(accountId) {
|
|
7969
|
+
const accountFile = resolve11(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
7970
|
+
if (!existsSync14(accountFile)) return /* @__PURE__ */ new Set();
|
|
7971
|
+
try {
|
|
7972
|
+
const parsed = JSON.parse(readFileSync13(accountFile, "utf-8"));
|
|
7973
|
+
return new Set(
|
|
7974
|
+
Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
|
|
7975
|
+
);
|
|
7976
|
+
} catch {
|
|
7977
|
+
return /* @__PURE__ */ new Set();
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
function readFrontmatter(path2) {
|
|
7981
|
+
if (!existsSync14(path2)) return null;
|
|
7982
|
+
let raw;
|
|
7983
|
+
try {
|
|
7984
|
+
raw = readFileSync13(path2, "utf-8");
|
|
7985
|
+
} catch {
|
|
7986
|
+
return null;
|
|
7987
|
+
}
|
|
7988
|
+
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7989
|
+
return m ? m[1] : null;
|
|
7990
|
+
}
|
|
7991
|
+
function computeActivePlugins(accountId) {
|
|
7992
|
+
const enabled = readEnabledPlugins(accountId);
|
|
7993
|
+
if (enabled.size === 0) return [];
|
|
7994
|
+
const pluginDirs = listPluginDirs();
|
|
7995
|
+
const out = [];
|
|
7996
|
+
for (const name of Array.from(enabled).sort()) {
|
|
7997
|
+
const dir = pluginDirs.get(name);
|
|
7998
|
+
if (!dir) continue;
|
|
7999
|
+
const fm = readFrontmatter(join10(dir, "PLUGIN.md"));
|
|
8000
|
+
if (!fm) continue;
|
|
8001
|
+
const description = frontmatterField(`---
|
|
8002
|
+
${fm}
|
|
8003
|
+
---`, "description");
|
|
8004
|
+
if (!description) continue;
|
|
8005
|
+
const toolNames = parseToolNamesFromFrontmatter(fm);
|
|
8006
|
+
const descMap = extractPluginToolDescriptions(dir);
|
|
8007
|
+
const tools = toolNames.map((n) => {
|
|
8008
|
+
const d = descMap.get(n);
|
|
8009
|
+
return d ? { name: n, description: d } : { name: n };
|
|
8010
|
+
});
|
|
8011
|
+
const withDesc = tools.filter((t) => t.description).length;
|
|
8012
|
+
console.log(
|
|
8013
|
+
`plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
|
|
8014
|
+
);
|
|
8015
|
+
if (toolNames.length > 0 && descMap.size === 0) {
|
|
8016
|
+
const hasSource = existsSync14(join10(dir, "mcp/dist/index.js")) || existsSync14(join10(dir, "mcp/src/index.ts"));
|
|
8017
|
+
if (hasSource) {
|
|
8018
|
+
console.warn(
|
|
8019
|
+
`[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
|
|
8020
|
+
);
|
|
8021
|
+
}
|
|
8022
|
+
}
|
|
8023
|
+
const skillPaths = parseSkillPathsFromFrontmatter(fm);
|
|
8024
|
+
const skills = [];
|
|
8025
|
+
for (const relPath2 of skillPaths) {
|
|
8026
|
+
const skillPath = join10(dir, relPath2);
|
|
8027
|
+
const skillFm = readFrontmatter(skillPath);
|
|
8028
|
+
if (!skillFm) continue;
|
|
8029
|
+
const skillName = frontmatterField(`---
|
|
8030
|
+
${skillFm}
|
|
8031
|
+
---`, "name") ?? relPath2;
|
|
8032
|
+
const skillDesc = frontmatterField(`---
|
|
8033
|
+
${skillFm}
|
|
8034
|
+
---`, "description");
|
|
8035
|
+
skills.push(skillDesc ? { name: skillName, description: skillDesc } : { name: skillName });
|
|
8036
|
+
}
|
|
8037
|
+
out.push({ name, description, tools, skills });
|
|
8038
|
+
}
|
|
8039
|
+
return out;
|
|
8040
|
+
}
|
|
8041
|
+
function computeSpecialistDomains(accountId) {
|
|
8042
|
+
const specialistsDir = resolve11(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
|
|
8043
|
+
if (!existsSync14(specialistsDir)) return [];
|
|
8044
|
+
let entries;
|
|
8045
|
+
try {
|
|
8046
|
+
entries = readdirSync7(specialistsDir);
|
|
8047
|
+
} catch {
|
|
8048
|
+
return [];
|
|
8049
|
+
}
|
|
8050
|
+
const pluginDirs = listPluginDirs();
|
|
8051
|
+
const descByPlugin = /* @__PURE__ */ new Map();
|
|
8052
|
+
const resolveDesc = (qualified) => {
|
|
8053
|
+
if (!qualified.startsWith("mcp__")) return void 0;
|
|
8054
|
+
const rest = qualified.slice(5);
|
|
8055
|
+
const sep4 = rest.indexOf("__");
|
|
8056
|
+
if (sep4 < 0) return void 0;
|
|
8057
|
+
const plugin = rest.slice(0, sep4);
|
|
8058
|
+
const tool = rest.slice(sep4 + 2);
|
|
8059
|
+
let map = descByPlugin.get(plugin);
|
|
8060
|
+
if (!map) {
|
|
8061
|
+
const dir = pluginDirs.get(plugin);
|
|
8062
|
+
map = dir ? extractPluginToolDescriptions(dir) : /* @__PURE__ */ new Map();
|
|
8063
|
+
descByPlugin.set(plugin, map);
|
|
8064
|
+
}
|
|
8065
|
+
return map.get(tool);
|
|
8066
|
+
};
|
|
8067
|
+
const out = [];
|
|
8068
|
+
for (const file of entries.sort()) {
|
|
8069
|
+
if (!file.endsWith(".md")) continue;
|
|
8070
|
+
const fm = readFrontmatter(join10(specialistsDir, file));
|
|
8071
|
+
if (!fm) continue;
|
|
8072
|
+
const wrapped = `---
|
|
8073
|
+
${fm}
|
|
8074
|
+
---`;
|
|
8075
|
+
const name = frontmatterField(wrapped, "name");
|
|
8076
|
+
const description = frontmatterField(wrapped, "description");
|
|
8077
|
+
if (!name || !description) continue;
|
|
8078
|
+
const toolsRaw = frontmatterField(wrapped, "tools") ?? "";
|
|
8079
|
+
const tools = toolsRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map((t) => {
|
|
8080
|
+
const d = resolveDesc(t);
|
|
8081
|
+
return d ? { name: t, description: d } : { name: t };
|
|
8082
|
+
});
|
|
8083
|
+
out.push({ name, description, tools });
|
|
8084
|
+
}
|
|
8085
|
+
return out;
|
|
8086
|
+
}
|
|
7916
8087
|
function computeDormantPlugins(accountId) {
|
|
7917
8088
|
const accountFile = resolve11(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
7918
8089
|
if (!existsSync14(accountFile)) return [];
|
|
@@ -8007,6 +8178,12 @@ app15.post("/", async (c) => {
|
|
|
8007
8178
|
const dormantStart = Date.now();
|
|
8008
8179
|
const dormantPlugins = computeDormantPlugins(senderId);
|
|
8009
8180
|
console.log(`${TAG18} dormant-plugins-computed count=${Array.isArray(dormantPlugins) ? dormantPlugins.length : 0} ms=${Date.now() - dormantStart}`);
|
|
8181
|
+
const activeStart = Date.now();
|
|
8182
|
+
const activePlugins = computeActivePlugins(senderId);
|
|
8183
|
+
console.log(`${TAG18} active-plugins-computed count=${activePlugins.length} ms=${Date.now() - activeStart}`);
|
|
8184
|
+
const specStart = Date.now();
|
|
8185
|
+
const specialistDomains = computeSpecialistDomains(senderId);
|
|
8186
|
+
console.log(`${TAG18} specialist-domains-computed count=${specialistDomains.length} ms=${Date.now() - specStart}`);
|
|
8010
8187
|
const tunnelUrl = resolveTunnelUrl();
|
|
8011
8188
|
console.log(`${TAG18} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
8012
8189
|
const forwardStart = Date.now();
|
|
@@ -8017,6 +8194,8 @@ app15.post("/", async (c) => {
|
|
|
8017
8194
|
permissionMode,
|
|
8018
8195
|
aboutOwner,
|
|
8019
8196
|
dormantPlugins,
|
|
8197
|
+
activePlugins,
|
|
8198
|
+
specialistDomains,
|
|
8020
8199
|
tunnelUrl
|
|
8021
8200
|
});
|
|
8022
8201
|
console.log(`${TAG18} forward-spawn-start managerBase=${managerBase2()} bytes=${upstreamPayload.length}`);
|
|
@@ -8048,6 +8227,24 @@ app15.post("/", async (c) => {
|
|
|
8048
8227
|
}
|
|
8049
8228
|
return new Response(upstream.body, { status: upstream.status, headers: upstream.headers });
|
|
8050
8229
|
});
|
|
8230
|
+
app15.get("/events", async (c) => {
|
|
8231
|
+
const upstream = await fetch(`${managerBase2()}/events`, {
|
|
8232
|
+
headers: { accept: "text/event-stream" }
|
|
8233
|
+
}).catch((err) => {
|
|
8234
|
+
console.error(`${TAG18} fetch-failed op=events message=${err instanceof Error ? err.message : String(err)}`);
|
|
8235
|
+
return null;
|
|
8236
|
+
});
|
|
8237
|
+
if (!upstream || !upstream.body) return c.json({ error: "manager-unreachable" }, 503);
|
|
8238
|
+
return new Response(upstream.body, {
|
|
8239
|
+
status: upstream.status,
|
|
8240
|
+
headers: {
|
|
8241
|
+
"content-type": "text/event-stream",
|
|
8242
|
+
"cache-control": "no-cache, no-transform",
|
|
8243
|
+
"x-accel-buffering": "no",
|
|
8244
|
+
connection: "keep-alive"
|
|
8245
|
+
}
|
|
8246
|
+
});
|
|
8247
|
+
});
|
|
8051
8248
|
app15.get("/", async (c) => {
|
|
8052
8249
|
const cacheKey = c.get("cacheKey") ?? "";
|
|
8053
8250
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
@@ -8078,6 +8275,8 @@ app15.post("/resume", async (c) => {
|
|
|
8078
8275
|
const channel = typeof body.channel === "string" ? body.channel : "browser";
|
|
8079
8276
|
const aboutOwner = await resolveOwnerProfileBlock(senderId, userId);
|
|
8080
8277
|
const dormantPlugins = computeDormantPlugins(senderId);
|
|
8278
|
+
const activePlugins = computeActivePlugins(senderId);
|
|
8279
|
+
const specialistDomains = computeSpecialistDomains(senderId);
|
|
8081
8280
|
const tunnelUrl = resolveTunnelUrl();
|
|
8082
8281
|
const upstream = await fetch(`${managerBase2()}/resume`, {
|
|
8083
8282
|
method: "POST",
|
|
@@ -8089,6 +8288,8 @@ app15.post("/resume", async (c) => {
|
|
|
8089
8288
|
claudeSessionId: body.claudeSessionId,
|
|
8090
8289
|
aboutOwner,
|
|
8091
8290
|
dormantPlugins,
|
|
8291
|
+
activePlugins,
|
|
8292
|
+
specialistDomains,
|
|
8092
8293
|
tunnelUrl
|
|
8093
8294
|
})
|
|
8094
8295
|
}).catch((err) => {
|
|
@@ -8098,11 +8299,22 @@ app15.post("/resume", async (c) => {
|
|
|
8098
8299
|
if (!upstream) return c.json({ error: "manager-unreachable" }, 503);
|
|
8099
8300
|
return new Response(upstream.body, { status: upstream.status, headers: upstream.headers });
|
|
8100
8301
|
});
|
|
8302
|
+
app15.post("/:sessionId/stop", async (c) => {
|
|
8303
|
+
const sessionId = c.req.param("sessionId");
|
|
8304
|
+
const upstream = await fetch(
|
|
8305
|
+
`${managerBase2()}/${encodeURIComponent(sessionId)}/stop`,
|
|
8306
|
+
{ method: "POST" }
|
|
8307
|
+
).catch((err) => {
|
|
8308
|
+
console.error(`${TAG18} fetch-failed op=stop message=${err instanceof Error ? err.message : String(err)}`);
|
|
8309
|
+
return null;
|
|
8310
|
+
});
|
|
8311
|
+
if (!upstream) return c.json({ error: "manager-unreachable" }, 503);
|
|
8312
|
+
return new Response(upstream.body, { status: upstream.status, headers: upstream.headers });
|
|
8313
|
+
});
|
|
8101
8314
|
app15.delete("/:sessionId", async (c) => {
|
|
8102
8315
|
const sessionId = c.req.param("sessionId");
|
|
8103
|
-
const purge = c.req.query("purge") === "1" ? "?purge=1" : "";
|
|
8104
8316
|
const upstream = await fetch(
|
|
8105
|
-
`${managerBase2()}/${encodeURIComponent(sessionId)}
|
|
8317
|
+
`${managerBase2()}/${encodeURIComponent(sessionId)}`,
|
|
8106
8318
|
{ method: "DELETE" }
|
|
8107
8319
|
).catch((err) => {
|
|
8108
8320
|
console.error(`${TAG18} fetch-failed op=delete message=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -8444,7 +8656,7 @@ function isValidDomain(value) {
|
|
|
8444
8656
|
|
|
8445
8657
|
// app/lib/alias-domains.ts
|
|
8446
8658
|
import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
8447
|
-
import { dirname as
|
|
8659
|
+
import { dirname as dirname3 } from "path";
|
|
8448
8660
|
import { resolve as resolve13 } from "path";
|
|
8449
8661
|
var ALIAS_DOMAINS_PATH = resolve13(MAXY_DIR, "alias-domains.json");
|
|
8450
8662
|
function readExisting() {
|
|
@@ -8461,14 +8673,14 @@ function addAliasDomain(hostname2) {
|
|
|
8461
8673
|
const existing = readExisting();
|
|
8462
8674
|
if (existing.has(hostname2)) return;
|
|
8463
8675
|
existing.add(hostname2);
|
|
8464
|
-
mkdirSync4(
|
|
8676
|
+
mkdirSync4(dirname3(ALIAS_DOMAINS_PATH), { recursive: true });
|
|
8465
8677
|
writeFileSync8(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
8466
8678
|
}
|
|
8467
8679
|
|
|
8468
8680
|
// app/lib/script-stream-tailer.ts
|
|
8469
8681
|
import * as childProcess from "child_process";
|
|
8470
|
-
import { appendFileSync as appendFileSync2, createReadStream as createReadStream2, mkdirSync as mkdirSync5, statSync as
|
|
8471
|
-
import { dirname as
|
|
8682
|
+
import { appendFileSync as appendFileSync2, createReadStream as createReadStream2, mkdirSync as mkdirSync5, statSync as statSync7 } from "fs";
|
|
8683
|
+
import { dirname as dirname4 } from "path";
|
|
8472
8684
|
import { StringDecoder } from "string_decoder";
|
|
8473
8685
|
var SCRIPT_STREAM_RE = /^\[([^\]]+)\] \[script:([a-z][a-z0-9-]*)((?::[a-z0-9:_-]+)?)\] (.*)$/;
|
|
8474
8686
|
function parseLine(line) {
|
|
@@ -8486,7 +8698,7 @@ function startScriptStreamTailer(opts) {
|
|
|
8486
8698
|
const { path: path2, onEvent, onError } = opts;
|
|
8487
8699
|
let offset;
|
|
8488
8700
|
try {
|
|
8489
|
-
offset =
|
|
8701
|
+
offset = statSync7(path2).size;
|
|
8490
8702
|
} catch {
|
|
8491
8703
|
offset = 0;
|
|
8492
8704
|
}
|
|
@@ -8505,7 +8717,7 @@ function startScriptStreamTailer(opts) {
|
|
|
8505
8717
|
try {
|
|
8506
8718
|
let size;
|
|
8507
8719
|
try {
|
|
8508
|
-
size =
|
|
8720
|
+
size = statSync7(path2).size;
|
|
8509
8721
|
} catch {
|
|
8510
8722
|
return;
|
|
8511
8723
|
}
|
|
@@ -8577,7 +8789,7 @@ function writeRouteMilestone(streamLogPath, scope, line) {
|
|
|
8577
8789
|
}
|
|
8578
8790
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
8579
8791
|
try {
|
|
8580
|
-
mkdirSync5(
|
|
8792
|
+
mkdirSync5(dirname4(streamLogPath), { recursive: true });
|
|
8581
8793
|
appendFileSync2(streamLogPath, `[${ts}] [script:${scope}] ${line}
|
|
8582
8794
|
`);
|
|
8583
8795
|
} catch (err) {
|
|
@@ -9295,7 +9507,7 @@ var cloudflare_default = app20;
|
|
|
9295
9507
|
import { createReadStream as createReadStream3 } from "fs";
|
|
9296
9508
|
import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
9297
9509
|
import { realpathSync as realpathSync4 } from "fs";
|
|
9298
|
-
import { basename as basename4, dirname as
|
|
9510
|
+
import { basename as basename4, dirname as dirname5, join as join11, resolve as resolve16, sep as sep3 } from "path";
|
|
9299
9511
|
import { Readable as Readable2 } from "stream";
|
|
9300
9512
|
|
|
9301
9513
|
// app/lib/data-path.ts
|
|
@@ -9652,7 +9864,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
9652
9864
|
var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9653
9865
|
async function readMeta(absDir, baseName) {
|
|
9654
9866
|
try {
|
|
9655
|
-
const raw = await readFile4(
|
|
9867
|
+
const raw = await readFile4(join11(absDir, `${baseName}.meta.json`), "utf8");
|
|
9656
9868
|
const parsed = JSON.parse(raw);
|
|
9657
9869
|
if (typeof parsed?.filename === "string") {
|
|
9658
9870
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -9690,7 +9902,7 @@ async function readAccountNames() {
|
|
|
9690
9902
|
}
|
|
9691
9903
|
async function enrich(absolute, entry, accountNames) {
|
|
9692
9904
|
if (entry.kind === "directory" && UUID_RE4.test(entry.name)) {
|
|
9693
|
-
const meta = await readMeta(
|
|
9905
|
+
const meta = await readMeta(join11(absolute, entry.name), entry.name);
|
|
9694
9906
|
if (meta?.filename) {
|
|
9695
9907
|
entry.displayName = meta.filename;
|
|
9696
9908
|
entry.mimeType = meta.mimeType;
|
|
@@ -9749,7 +9961,7 @@ app21.get("/", requireAdminSession, async (c) => {
|
|
|
9749
9961
|
continue;
|
|
9750
9962
|
}
|
|
9751
9963
|
try {
|
|
9752
|
-
const entryPath =
|
|
9964
|
+
const entryPath = join11(absolute, name);
|
|
9753
9965
|
const s = await stat4(entryPath);
|
|
9754
9966
|
entries.push({
|
|
9755
9967
|
name,
|
|
@@ -9922,7 +10134,7 @@ app21.delete("/", requireAdminSession, async (c) => {
|
|
|
9922
10134
|
}
|
|
9923
10135
|
const dot = base.lastIndexOf(".");
|
|
9924
10136
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
9925
|
-
const sidecarPath = UUID_RE4.test(stem) && base !== `${stem}.meta.json` ?
|
|
10137
|
+
const sidecarPath = UUID_RE4.test(stem) && base !== `${stem}.meta.json` ? join11(dirname5(absolute), `${stem}.meta.json`) : null;
|
|
9926
10138
|
await unlink2(absolute);
|
|
9927
10139
|
if (sidecarPath) {
|
|
9928
10140
|
try {
|
|
@@ -11969,10 +12181,10 @@ var sidebar_artefact_content_default = app31;
|
|
|
11969
12181
|
|
|
11970
12182
|
// server/routes/admin/health.ts
|
|
11971
12183
|
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
11972
|
-
import { resolve as resolve20, join as
|
|
12184
|
+
import { resolve as resolve20, join as join12 } from "path";
|
|
11973
12185
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
|
|
11974
12186
|
var brandHostname = "maxy";
|
|
11975
|
-
var brandJsonPath =
|
|
12187
|
+
var brandJsonPath = join12(PLATFORM_ROOT6, "config", "brand.json");
|
|
11976
12188
|
if (existsSync19(brandJsonPath)) {
|
|
11977
12189
|
try {
|
|
11978
12190
|
const brand = JSON.parse(readFileSync17(brandJsonPath, "utf-8"));
|
|
@@ -12067,7 +12279,7 @@ app33.route("/health-brand", health_default2);
|
|
|
12067
12279
|
var admin_default = app33;
|
|
12068
12280
|
|
|
12069
12281
|
// server/routes/sites.ts
|
|
12070
|
-
import { existsSync as existsSync20, readFileSync as readFileSync18, realpathSync as realpathSync5, statSync as
|
|
12282
|
+
import { existsSync as existsSync20, readFileSync as readFileSync18, realpathSync as realpathSync5, statSync as statSync8 } from "fs";
|
|
12071
12283
|
import { resolve as resolve21 } from "path";
|
|
12072
12284
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
12073
12285
|
var MIME = {
|
|
@@ -12133,7 +12345,7 @@ app34.get("/:rel{.*}", (c) => {
|
|
|
12133
12345
|
}
|
|
12134
12346
|
let stat7;
|
|
12135
12347
|
try {
|
|
12136
|
-
stat7 = existsSync20(filePath) ?
|
|
12348
|
+
stat7 = existsSync20(filePath) ? statSync8(filePath) : null;
|
|
12137
12349
|
} catch {
|
|
12138
12350
|
stat7 = null;
|
|
12139
12351
|
}
|
|
@@ -12317,7 +12529,7 @@ function startReaper2() {
|
|
|
12317
12529
|
|
|
12318
12530
|
// ../lib/entitlement/src/index.ts
|
|
12319
12531
|
import { createPublicKey, createHash as createHash3, verify as cryptoVerify } from "crypto";
|
|
12320
|
-
import { existsSync as existsSync21, readFileSync as readFileSync19, statSync as
|
|
12532
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
|
|
12321
12533
|
import { resolve as resolve22 } from "path";
|
|
12322
12534
|
|
|
12323
12535
|
// ../lib/entitlement/src/canonicalize.ts
|
|
@@ -12369,7 +12581,7 @@ function resolveEntitlement(brand, account) {
|
|
|
12369
12581
|
if (!existsSync21(entitlementPath)) {
|
|
12370
12582
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
12371
12583
|
}
|
|
12372
|
-
const stat7 =
|
|
12584
|
+
const stat7 = statSync9(entitlementPath);
|
|
12373
12585
|
const key = memoKey(stat7.mtimeMs, account);
|
|
12374
12586
|
if (memo && memo.key === key) {
|
|
12375
12587
|
return memo.result;
|
|
@@ -12556,7 +12768,7 @@ function clientFrom(c) {
|
|
|
12556
12768
|
);
|
|
12557
12769
|
}
|
|
12558
12770
|
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
12559
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT7 ?
|
|
12771
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join13(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
12560
12772
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
12561
12773
|
if (BRAND_JSON_PATH && !existsSync22(BRAND_JSON_PATH)) {
|
|
12562
12774
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
@@ -12582,7 +12794,7 @@ var brandLoginOpts = {
|
|
|
12582
12794
|
bodyFont: BRAND.defaultFonts?.body,
|
|
12583
12795
|
logoContainsName: !!BRAND.logoContainsName
|
|
12584
12796
|
};
|
|
12585
|
-
var ALIAS_DOMAINS_PATH2 =
|
|
12797
|
+
var ALIAS_DOMAINS_PATH2 = join13(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
12586
12798
|
function loadAliasDomains() {
|
|
12587
12799
|
try {
|
|
12588
12800
|
if (!existsSync22(ALIAS_DOMAINS_PATH2)) return null;
|
|
@@ -13028,7 +13240,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
13028
13240
|
function readInstalledVersion() {
|
|
13029
13241
|
try {
|
|
13030
13242
|
if (!PLATFORM_ROOT7) return "unknown";
|
|
13031
|
-
const versionFile =
|
|
13243
|
+
const versionFile = join13(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
|
|
13032
13244
|
if (!existsSync22(versionFile)) return "unknown";
|
|
13033
13245
|
const content = readFileSync20(versionFile, "utf-8").trim();
|
|
13034
13246
|
return content || "unknown";
|
|
@@ -13087,14 +13299,14 @@ ${clientErrorReporterScript}
|
|
|
13087
13299
|
}
|
|
13088
13300
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
13089
13301
|
function loadBrandingCache(agentSlug) {
|
|
13090
|
-
const configDir2 =
|
|
13302
|
+
const configDir2 = join13(homedir4(), BRAND.configDir);
|
|
13091
13303
|
try {
|
|
13092
|
-
const accountJsonPath =
|
|
13304
|
+
const accountJsonPath = join13(configDir2, "account.json");
|
|
13093
13305
|
if (!existsSync22(accountJsonPath)) return null;
|
|
13094
13306
|
const account = JSON.parse(readFileSync20(accountJsonPath, "utf-8"));
|
|
13095
13307
|
const accountId = account.accountId;
|
|
13096
13308
|
if (!accountId) return null;
|
|
13097
|
-
const cachePath =
|
|
13309
|
+
const cachePath = join13(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
13098
13310
|
if (!existsSync22(cachePath)) return null;
|
|
13099
13311
|
return JSON.parse(readFileSync20(cachePath, "utf-8"));
|
|
13100
13312
|
} catch {
|
|
@@ -13103,8 +13315,8 @@ function loadBrandingCache(agentSlug) {
|
|
|
13103
13315
|
}
|
|
13104
13316
|
function resolveDefaultSlug() {
|
|
13105
13317
|
try {
|
|
13106
|
-
const configDir2 =
|
|
13107
|
-
const accountJsonPath =
|
|
13318
|
+
const configDir2 = join13(homedir4(), BRAND.configDir);
|
|
13319
|
+
const accountJsonPath = join13(configDir2, "account.json");
|
|
13108
13320
|
if (!existsSync22(accountJsonPath)) return null;
|
|
13109
13321
|
const account = JSON.parse(readFileSync20(accountJsonPath, "utf-8"));
|
|
13110
13322
|
return account.defaultAgent || null;
|
|
@@ -13352,7 +13564,7 @@ reconcileEnabledPlugins(bootAccount?.accountDir, bootAccount?.config);
|
|
|
13352
13564
|
(async () => {
|
|
13353
13565
|
if (!bootAccount) return;
|
|
13354
13566
|
try {
|
|
13355
|
-
const { recoverRunningCloudflareTasks } = await import("./cloudflare-task-tracker-
|
|
13567
|
+
const { recoverRunningCloudflareTasks } = await import("./cloudflare-task-tracker-R6MMORFU.js");
|
|
13356
13568
|
const result = await recoverRunningCloudflareTasks(
|
|
13357
13569
|
bootAccount.accountId,
|
|
13358
13570
|
configDirForWhatsApp,
|
|
@@ -13412,7 +13624,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
13412
13624
|
}
|
|
13413
13625
|
init({
|
|
13414
13626
|
configDir: configDirForWhatsApp,
|
|
13415
|
-
platformRoot: resolve23(process.env.MAXY_PLATFORM_ROOT ??
|
|
13627
|
+
platformRoot: resolve23(process.env.MAXY_PLATFORM_ROOT ?? join13(__dirname, "..")),
|
|
13416
13628
|
accountConfig: bootAccountConfig,
|
|
13417
13629
|
onMessage: async (msg) => {
|
|
13418
13630
|
if (process.env.WHATSAPP_PTY_BRIDGE_ENABLED === "true" && msg.text && !msg.isOwnerMirror) {
|