@rubytech/create-maxy-code 0.1.384 → 0.1.387
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/__tests__/cron-heartbeat-registration.test.js +5 -0
- package/dist/cron-registration.js +1 -0
- package/dist/index.js +4 -0
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +36 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +73 -2
- package/payload/platform/plugins/docs/references/admin-ui.md +12 -1
- package/payload/platform/plugins/docs/references/graph.md +11 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
- package/payload/platform/plugins/docs/references/sweep-guide.md +43 -0
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +34 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +17 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +12 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +101 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +54 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +117 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts +7 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js +6 -3
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +88 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js +77 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +59 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +131 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +62 -15
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +110 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js +35 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js +81 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +7 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +21 -4
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +4 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +6 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +2 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +32 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
- package/payload/platform/plugins/sweep/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/sweep/PLUGIN.md +15 -0
- package/payload/platform/plugins/sweep/skills/sweep-workflow/SKILL.md +113 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +19 -4
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +7 -4
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +10 -0
- package/payload/platform/scripts/seed-neo4j.sh +38 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +5 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-C_h8V0Xs.js → AdminLoginScreens-BejIjbmU.js} +1 -1
- package/payload/server/public/assets/{AdminShell-Dqnxpn7U.js → AdminShell-D2-uBSB5.js} +1 -1
- package/payload/server/public/assets/{Checkbox-Bl1WRVGb.js → Checkbox-1F1tzqca.js} +1 -1
- package/payload/server/public/assets/Transcript-DkGa4CQH.js +2 -0
- package/payload/server/public/assets/{admin-ByoqNaSf.js → admin-DqQARkjy.js} +1 -1
- package/payload/server/public/assets/{browser-BXY1sNxv.js → browser-nDtEK6RC.js} +1 -1
- package/payload/server/public/assets/{calendar-CObRTPpU.js → calendar-CO4Bwmho.js} +1 -1
- package/payload/server/public/assets/chat-DeIge_bC.js +1 -0
- package/payload/server/public/assets/chevron-left-DhVdq0aN.js +1 -0
- package/payload/server/public/assets/data-B1GIdzHk.js +1 -0
- package/payload/server/public/assets/{graph-BCL0fVDH.js → graph-DFyicKd7.js} +1 -1
- package/payload/server/public/assets/{graph-labels-C0nn9nQi.js → graph-labels-D3BQdcpD.js} +1 -1
- package/payload/server/public/assets/{operator-B_1qYO7m.js → operator-BxrjWdT9.js} +1 -1
- package/payload/server/public/assets/{page-BQEl7aZO.js → page-ByDLq_o1.js} +1 -1
- package/payload/server/public/assets/page-D-Ep4bXd.js +32 -0
- package/payload/server/public/assets/{public-BzvwPHM0.js → public-DbdqfdYq.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-CHep9vl5.js → rotate-ccw-BkX8HODe.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-B1Kf32uG.js → useSubAccountSwitcher-DMbRhLPv.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-CfI3J2IX.css → useSubAccountSwitcher-DS0iqSkP.css} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +5 -5
- package/payload/server/public/chat.html +8 -8
- package/payload/server/public/data.html +8 -7
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +11 -10
- package/payload/server/public/operator.html +10 -10
- package/payload/server/public/public.html +8 -8
- package/payload/server/server.js +556 -273
- package/payload/server/public/assets/Transcript-C93Lifnb.js +0 -1
- package/payload/server/public/assets/chat-Dq-VHp7a.js +0 -1
- package/payload/server/public/assets/chevron-left-DjbK_wdQ.js +0 -1
- package/payload/server/public/assets/data-BXOoK8Ci.js +0 -1
- package/payload/server/public/assets/page-Dck23z9U.js +0 -32
package/payload/server/server.js
CHANGED
|
@@ -1727,6 +1727,7 @@ var WhatsAppConfigSchema = z.object({
|
|
|
1727
1727
|
accounts: z.record(z.string(), WhatsAppAccountSchema.optional()).optional().describe("Per-account config keyed by account ID."),
|
|
1728
1728
|
dmPolicy: DmPolicySchema.optional().default("disabled").describe("Who can message your public agent via WhatsApp DM. 'Open' lets anyone message. 'Allowlist' restricts to approved phone numbers. 'Disabled' blocks all public messages."),
|
|
1729
1729
|
adminPhones: z.array(z.string()).optional().describe("Phone numbers (E.164) that route to the admin agent. Managed via add-admin-phone / remove-admin-phone."),
|
|
1730
|
+
accountManagers: z.record(z.string(), z.string()).optional().describe("Account-manager bindings: E.164 phone \u2192 sub-account UUID. A designated phone spawns an admin session scoped to that sub-account rather than the house. Managed via add-account-manager / remove-account-manager. A phone here must not also be in adminPhones."),
|
|
1730
1731
|
allowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to message the public agent when DM policy is 'allowlist'. Add '*' for open access. Does not affect admin routing \u2014 use adminPhones for that."),
|
|
1731
1732
|
sendReadReceipts: z.boolean().optional().default(true).describe("Whether to send read receipts (blue ticks) when messages are received. Disabling may feel less responsive but preserves privacy."),
|
|
1732
1733
|
mediaMaxMb: z.number().int().positive().optional().default(50).describe("Maximum file size in MB for media the agent will download and process."),
|
|
@@ -2601,13 +2602,24 @@ function extractMessage(msg) {
|
|
|
2601
2602
|
return result;
|
|
2602
2603
|
}
|
|
2603
2604
|
|
|
2605
|
+
// app/lib/whatsapp/managed-account.ts
|
|
2606
|
+
function managedAccountFor(accountManagers, phone) {
|
|
2607
|
+
for (const [managerPhone, subAccountId] of Object.entries(accountManagers)) {
|
|
2608
|
+
if (subAccountId && phonesMatch(managerPhone, phone)) return subAccountId;
|
|
2609
|
+
}
|
|
2610
|
+
return null;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2604
2613
|
// app/lib/whatsapp/inbound/access-control.ts
|
|
2605
2614
|
function resolveAccountConfig(config, accountConfig) {
|
|
2606
2615
|
return {
|
|
2607
2616
|
dmPolicy: accountConfig?.dmPolicy ?? config.dmPolicy ?? "disabled",
|
|
2608
2617
|
adminPhones: accountConfig?.adminPhones ?? config.adminPhones ?? [],
|
|
2609
2618
|
allowFrom: accountConfig?.allowFrom ?? config.allowFrom ?? [],
|
|
2610
|
-
selfChatMode: accountConfig?.selfChatMode ?? false
|
|
2619
|
+
selfChatMode: accountConfig?.selfChatMode ?? false,
|
|
2620
|
+
// Account-manager bindings are a house-level routing table only — never a
|
|
2621
|
+
// per-account override, so this reads the top-level map unconditionally.
|
|
2622
|
+
accountManagers: config.accountManagers ?? {}
|
|
2611
2623
|
};
|
|
2612
2624
|
}
|
|
2613
2625
|
function isAdminPhone(phone, adminPhones) {
|
|
@@ -2624,6 +2636,9 @@ function checkDmAccess(params) {
|
|
|
2624
2636
|
if (isAdminPhone(senderPhone, cfg.adminPhones)) {
|
|
2625
2637
|
return { allowed: true, reason: "admin-binding", agentType: "admin" };
|
|
2626
2638
|
}
|
|
2639
|
+
if (managedAccountFor(cfg.accountManagers, senderPhone)) {
|
|
2640
|
+
return { allowed: true, reason: "account-manager", agentType: "admin" };
|
|
2641
|
+
}
|
|
2627
2642
|
switch (cfg.dmPolicy) {
|
|
2628
2643
|
case "open":
|
|
2629
2644
|
return { allowed: true, reason: "dm-policy-open", agentType: "public" };
|
|
@@ -5971,6 +5986,131 @@ ${result.result.text}` : result.result.text;
|
|
|
5971
5986
|
return app56;
|
|
5972
5987
|
}
|
|
5973
5988
|
|
|
5989
|
+
// app/lib/channel-pty-bridge/admin-session-id.ts
|
|
5990
|
+
import { createHash } from "crypto";
|
|
5991
|
+
function adminSessionIdFor(accountId, senderId, personId) {
|
|
5992
|
+
const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
|
|
5993
|
+
const b = createHash("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
|
|
5994
|
+
const v = Buffer.from(b);
|
|
5995
|
+
v[6] = v[6] & 15 | 64;
|
|
5996
|
+
v[8] = v[8] & 63 | 128;
|
|
5997
|
+
const h = v.toString("hex");
|
|
5998
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
// server/routes/channel/schedule-inject.ts
|
|
6002
|
+
var TAG16 = "[schedule-inject]";
|
|
6003
|
+
function isLoopbackAddr(addr) {
|
|
6004
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
6005
|
+
}
|
|
6006
|
+
function createScheduleInjectRoutes(deps) {
|
|
6007
|
+
const app56 = new Hono();
|
|
6008
|
+
app56.post("/", async (c) => {
|
|
6009
|
+
const env = c.env;
|
|
6010
|
+
const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
|
|
6011
|
+
if (!isLoopbackAddr(remoteAddr)) {
|
|
6012
|
+
console.error(`${TAG16} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
6013
|
+
return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
|
|
6014
|
+
}
|
|
6015
|
+
const xffHeader = env?.incoming?.headers?.["x-forwarded-for"];
|
|
6016
|
+
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
6017
|
+
if (xffRaw.length > 0) {
|
|
6018
|
+
const offender = xffRaw.split(",").map((t) => t.trim()).filter(Boolean).find((t) => !isLoopbackAddr(t));
|
|
6019
|
+
if (offender !== void 0) {
|
|
6020
|
+
console.error(`${TAG16} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
6021
|
+
return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
6024
|
+
let body;
|
|
6025
|
+
try {
|
|
6026
|
+
body = await c.req.json();
|
|
6027
|
+
} catch {
|
|
6028
|
+
return c.json({ ok: false, error: "invalid-json" }, 400);
|
|
6029
|
+
}
|
|
6030
|
+
const channel = body.channel;
|
|
6031
|
+
const destination = typeof body.destination === "string" ? body.destination : "";
|
|
6032
|
+
const prompt = typeof body.prompt === "string" ? body.prompt : "";
|
|
6033
|
+
const eventId = typeof body.eventId === "string" ? body.eventId : "";
|
|
6034
|
+
if (channel !== "whatsapp" && channel !== "telegram" || !destination || !prompt) {
|
|
6035
|
+
console.error(`${TAG16} reject reason=bad-request channel=${String(channel)} hasDestination=${Boolean(destination)} hasPrompt=${Boolean(prompt)}`);
|
|
6036
|
+
return c.json({ ok: false, error: "schedule-inject-bad-request" }, 400);
|
|
6037
|
+
}
|
|
6038
|
+
const account = deps.resolveAccount();
|
|
6039
|
+
if (!account) {
|
|
6040
|
+
console.error(`${TAG16} reject reason=no-account eventId=${eventId}`);
|
|
6041
|
+
return c.json({ ok: false, error: "no-account" }, 500);
|
|
6042
|
+
}
|
|
6043
|
+
const houseAccountId = account.accountId;
|
|
6044
|
+
console.error(`${TAG16} op=received eventId=${eventId} channel=${channel} destination=${destination} account=${houseAccountId}`);
|
|
6045
|
+
if (channel === "whatsapp") {
|
|
6046
|
+
const effectiveAccount2 = deps.effectiveAccountFor(houseAccountId, account.accountDir, destination);
|
|
6047
|
+
console.error(`${TAG16} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount2}`);
|
|
6048
|
+
const sessionId2 = adminSessionIdFor(effectiveAccount2, destination);
|
|
6049
|
+
const reply2 = (text) => deps.sendWhatsAppText(houseAccountId, destination, text);
|
|
6050
|
+
try {
|
|
6051
|
+
await deps.waHandleInbound({
|
|
6052
|
+
accountId: houseAccountId,
|
|
6053
|
+
senderId: destination,
|
|
6054
|
+
role: "admin",
|
|
6055
|
+
personId: null,
|
|
6056
|
+
text: prompt,
|
|
6057
|
+
media: [],
|
|
6058
|
+
source: "schedule",
|
|
6059
|
+
reply: reply2
|
|
6060
|
+
});
|
|
6061
|
+
} catch (err) {
|
|
6062
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
|
|
6063
|
+
return c.json({ ok: false, error: "inject-error" }, 502);
|
|
6064
|
+
}
|
|
6065
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=ok status=-`);
|
|
6066
|
+
return c.json({ ok: true }, 200);
|
|
6067
|
+
}
|
|
6068
|
+
const effectiveAccount = houseAccountId;
|
|
6069
|
+
console.error(`${TAG16} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount}`);
|
|
6070
|
+
const botToken = account.config.telegram?.adminBotToken;
|
|
6071
|
+
if (!botToken) {
|
|
6072
|
+
console.error(`${TAG16} reject reason=no-admin-bot-token eventId=${eventId}`);
|
|
6073
|
+
return c.json({ ok: false, error: "no-admin-bot-token" }, 400);
|
|
6074
|
+
}
|
|
6075
|
+
const chatId = Number(destination);
|
|
6076
|
+
const sessionId = adminSessionIdFor(effectiveAccount, destination);
|
|
6077
|
+
const reply = (text) => deps.sendTelegramText(botToken, chatId, text);
|
|
6078
|
+
try {
|
|
6079
|
+
await deps.tgHandleInbound({
|
|
6080
|
+
accountId: houseAccountId,
|
|
6081
|
+
senderId: destination,
|
|
6082
|
+
chatId: destination,
|
|
6083
|
+
role: "admin",
|
|
6084
|
+
personId: null,
|
|
6085
|
+
text: prompt,
|
|
6086
|
+
source: "schedule",
|
|
6087
|
+
reply
|
|
6088
|
+
});
|
|
6089
|
+
} catch (err) {
|
|
6090
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
|
|
6091
|
+
return c.json({ ok: false, error: "inject-error" }, 502);
|
|
6092
|
+
}
|
|
6093
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
|
|
6094
|
+
return c.json({ ok: true }, 200);
|
|
6095
|
+
});
|
|
6096
|
+
return app56;
|
|
6097
|
+
}
|
|
6098
|
+
|
|
6099
|
+
// app/lib/telegram/outbound/send-text.ts
|
|
6100
|
+
async function sendTelegramText(botToken, chatId, text) {
|
|
6101
|
+
try {
|
|
6102
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
6103
|
+
method: "POST",
|
|
6104
|
+
headers: { "Content-Type": "application/json" },
|
|
6105
|
+
body: JSON.stringify({ chat_id: chatId, text })
|
|
6106
|
+
});
|
|
6107
|
+
const data = await res.json();
|
|
6108
|
+
return data.ok ? { ok: true } : { ok: false, error: data.description ?? "send failed" };
|
|
6109
|
+
} catch (e) {
|
|
6110
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
|
|
5974
6114
|
// server/routes/whatsapp.ts
|
|
5975
6115
|
import { join as join10, resolve as resolve10 } from "path";
|
|
5976
6116
|
import { readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync6 } from "fs";
|
|
@@ -5981,7 +6121,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
5981
6121
|
// app/lib/whatsapp/config-persist.ts
|
|
5982
6122
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
5983
6123
|
import { resolve as resolve8, join as join8 } from "path";
|
|
5984
|
-
var
|
|
6124
|
+
var TAG17 = "[whatsapp:config]";
|
|
5985
6125
|
function configPath(accountDir) {
|
|
5986
6126
|
return resolve8(accountDir, "account.json");
|
|
5987
6127
|
}
|
|
@@ -5998,9 +6138,9 @@ function reloadManagerConfig(accountDir) {
|
|
|
5998
6138
|
try {
|
|
5999
6139
|
const config = readConfig(accountDir);
|
|
6000
6140
|
reloadConfig(config);
|
|
6001
|
-
console.error(`${
|
|
6141
|
+
console.error(`${TAG17} reloaded manager config`);
|
|
6002
6142
|
} catch (err) {
|
|
6003
|
-
console.error(`${
|
|
6143
|
+
console.error(`${TAG17} manager config reload failed: ${String(err)}`);
|
|
6004
6144
|
}
|
|
6005
6145
|
}
|
|
6006
6146
|
var E164_PATTERN = /^\+\d{7,15}$/;
|
|
@@ -6026,25 +6166,25 @@ function persistAfterPairing(accountDir, accountId, selfPhone) {
|
|
|
6026
6166
|
const adminPhones = wa.adminPhones;
|
|
6027
6167
|
if (!adminPhones.includes(normalized)) {
|
|
6028
6168
|
adminPhones.push(normalized);
|
|
6029
|
-
console.error(`${
|
|
6169
|
+
console.error(`${TAG17} added selfPhone=${normalized} to adminPhones`);
|
|
6030
6170
|
}
|
|
6031
6171
|
} else {
|
|
6032
|
-
console.error(`${
|
|
6172
|
+
console.error(`${TAG17} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
|
|
6033
6173
|
}
|
|
6034
6174
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6035
6175
|
if (!parsed.success) {
|
|
6036
6176
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6037
|
-
console.error(`${
|
|
6177
|
+
console.error(`${TAG17} validation failed after pairing: ${msg}`);
|
|
6038
6178
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6039
6179
|
}
|
|
6040
6180
|
config.whatsapp = parsed.data;
|
|
6041
6181
|
writeConfig(accountDir, config);
|
|
6042
|
-
console.error(`${
|
|
6182
|
+
console.error(`${TAG17} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
|
|
6043
6183
|
reloadManagerConfig(accountDir);
|
|
6044
6184
|
return { ok: true };
|
|
6045
6185
|
} catch (err) {
|
|
6046
6186
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6047
|
-
console.error(`${
|
|
6187
|
+
console.error(`${TAG17} persist failed account=${accountId}: ${msg}`);
|
|
6048
6188
|
return { ok: false, error: msg };
|
|
6049
6189
|
}
|
|
6050
6190
|
}
|
|
@@ -6066,6 +6206,9 @@ function addAdminPhone(accountDir, phone) {
|
|
|
6066
6206
|
if (adminPhones.includes(normalized)) {
|
|
6067
6207
|
return { ok: true, message: `Phone ${normalized} is already in the admin list.` };
|
|
6068
6208
|
}
|
|
6209
|
+
if (managedAccountFor(readAccountManagers(accountDir), normalized)) {
|
|
6210
|
+
return { ok: false, error: `${normalized} is bound as an account manager. Remove the account-manager binding before adding it as a house admin \u2014 a phone is a house admin or a sub-account manager, never both.` };
|
|
6211
|
+
}
|
|
6069
6212
|
adminPhones.push(normalized);
|
|
6070
6213
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6071
6214
|
if (!parsed.success) {
|
|
@@ -6074,12 +6217,12 @@ function addAdminPhone(accountDir, phone) {
|
|
|
6074
6217
|
}
|
|
6075
6218
|
config.whatsapp = parsed.data;
|
|
6076
6219
|
writeConfig(accountDir, config);
|
|
6077
|
-
console.error(`${
|
|
6220
|
+
console.error(`${TAG17} added admin phone=${normalized}`);
|
|
6078
6221
|
reloadManagerConfig(accountDir);
|
|
6079
6222
|
return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
|
|
6080
6223
|
} catch (err) {
|
|
6081
6224
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6082
|
-
console.error(`${
|
|
6225
|
+
console.error(`${TAG17} addAdminPhone failed: ${msg}`);
|
|
6083
6226
|
return { ok: false, error: msg };
|
|
6084
6227
|
}
|
|
6085
6228
|
}
|
|
@@ -6107,12 +6250,12 @@ function removeAdminPhone(accountDir, phone) {
|
|
|
6107
6250
|
}
|
|
6108
6251
|
config.whatsapp = parsed.data;
|
|
6109
6252
|
writeConfig(accountDir, config);
|
|
6110
|
-
console.error(`${
|
|
6253
|
+
console.error(`${TAG17} removed admin phone=${normalized}`);
|
|
6111
6254
|
reloadManagerConfig(accountDir);
|
|
6112
6255
|
return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
|
|
6113
6256
|
} catch (err) {
|
|
6114
6257
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6115
|
-
console.error(`${
|
|
6258
|
+
console.error(`${TAG17} removeAdminPhone failed: ${msg}`);
|
|
6116
6259
|
return { ok: false, error: msg };
|
|
6117
6260
|
}
|
|
6118
6261
|
}
|
|
@@ -6127,6 +6270,101 @@ function readAdminPhones(accountDir) {
|
|
|
6127
6270
|
return [];
|
|
6128
6271
|
}
|
|
6129
6272
|
}
|
|
6273
|
+
function setAccountManager(accountDir, phone, subAccountId) {
|
|
6274
|
+
const normalized = phone.trim();
|
|
6275
|
+
if (!E164_PATTERN.test(normalized)) {
|
|
6276
|
+
return { ok: false, error: `Invalid phone format "${normalized}". Expected E.164 (e.g. +441234567890).` };
|
|
6277
|
+
}
|
|
6278
|
+
const sub = subAccountId.trim();
|
|
6279
|
+
if (!sub) {
|
|
6280
|
+
return { ok: false, error: "Missing sub-account id." };
|
|
6281
|
+
}
|
|
6282
|
+
let validIds;
|
|
6283
|
+
try {
|
|
6284
|
+
validIds = listValidAccounts().map((a) => a.accountId);
|
|
6285
|
+
} catch (err) {
|
|
6286
|
+
return { ok: false, error: `Could not read the account registry to validate the sub-account: ${err instanceof Error ? err.message : String(err)}. Try again.` };
|
|
6287
|
+
}
|
|
6288
|
+
if (!validIds.includes(sub)) {
|
|
6289
|
+
return { ok: false, error: `Sub-account "${sub}" is not in this install's account registry. Check the sub-account id and try again.` };
|
|
6290
|
+
}
|
|
6291
|
+
try {
|
|
6292
|
+
const config = readConfig(accountDir);
|
|
6293
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
6294
|
+
config.whatsapp = {};
|
|
6295
|
+
}
|
|
6296
|
+
const wa = config.whatsapp;
|
|
6297
|
+
const adminPhones = Array.isArray(wa.adminPhones) ? wa.adminPhones.filter((p) => typeof p === "string") : [];
|
|
6298
|
+
if (isAdminPhone(normalized, adminPhones)) {
|
|
6299
|
+
return {
|
|
6300
|
+
ok: false,
|
|
6301
|
+
error: `${normalized} is a house admin phone. Remove it from admin phones before binding it as an account manager \u2014 a phone is a house admin or a sub-account manager, never both.`
|
|
6302
|
+
};
|
|
6303
|
+
}
|
|
6304
|
+
if (!wa.accountManagers || typeof wa.accountManagers !== "object") {
|
|
6305
|
+
wa.accountManagers = {};
|
|
6306
|
+
}
|
|
6307
|
+
wa.accountManagers[normalized] = sub;
|
|
6308
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6309
|
+
if (!parsed.success) {
|
|
6310
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6311
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6312
|
+
}
|
|
6313
|
+
config.whatsapp = parsed.data;
|
|
6314
|
+
writeConfig(accountDir, config);
|
|
6315
|
+
console.error(`${TAG17} bound account manager phone=${normalized} managesAccount=${sub}`);
|
|
6316
|
+
reloadManagerConfig(accountDir);
|
|
6317
|
+
return { ok: true, message: `Bound ${normalized} as account manager for sub-account ${sub}. Their WhatsApp messages will reach that sub-account's admin agent; replies still go out over this account's WhatsApp device.` };
|
|
6318
|
+
} catch (err) {
|
|
6319
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6320
|
+
console.error(`${TAG17} setAccountManager failed: ${msg}`);
|
|
6321
|
+
return { ok: false, error: msg };
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
function clearAccountManager(accountDir, phone) {
|
|
6325
|
+
const normalized = phone.trim();
|
|
6326
|
+
try {
|
|
6327
|
+
const config = readConfig(accountDir);
|
|
6328
|
+
const wa = config.whatsapp;
|
|
6329
|
+
if (!wa || typeof wa !== "object" || !wa.accountManagers || typeof wa.accountManagers !== "object") {
|
|
6330
|
+
return { ok: true, message: `No account-manager binding for ${normalized}.` };
|
|
6331
|
+
}
|
|
6332
|
+
const map = wa.accountManagers;
|
|
6333
|
+
const key = Object.keys(map).find((k) => phonesMatch(k, normalized));
|
|
6334
|
+
if (!key) {
|
|
6335
|
+
return { ok: true, message: `No account-manager binding for ${normalized}.` };
|
|
6336
|
+
}
|
|
6337
|
+
delete map[key];
|
|
6338
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6339
|
+
if (!parsed.success) {
|
|
6340
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6341
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6342
|
+
}
|
|
6343
|
+
config.whatsapp = parsed.data;
|
|
6344
|
+
writeConfig(accountDir, config);
|
|
6345
|
+
console.error(`${TAG17} cleared account manager phone=${normalized}`);
|
|
6346
|
+
reloadManagerConfig(accountDir);
|
|
6347
|
+
return { ok: true, message: `Cleared the account-manager binding for ${normalized}.` };
|
|
6348
|
+
} catch (err) {
|
|
6349
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6350
|
+
console.error(`${TAG17} clearAccountManager failed: ${msg}`);
|
|
6351
|
+
return { ok: false, error: msg };
|
|
6352
|
+
}
|
|
6353
|
+
}
|
|
6354
|
+
function readAccountManagers(accountDir) {
|
|
6355
|
+
try {
|
|
6356
|
+
const config = readConfig(accountDir);
|
|
6357
|
+
const wa = config.whatsapp;
|
|
6358
|
+
if (!wa || typeof wa.accountManagers !== "object" || wa.accountManagers === null || Array.isArray(wa.accountManagers)) return {};
|
|
6359
|
+
const out = {};
|
|
6360
|
+
for (const [k, v] of Object.entries(wa.accountManagers)) {
|
|
6361
|
+
if (typeof v === "string") out[k] = v;
|
|
6362
|
+
}
|
|
6363
|
+
return out;
|
|
6364
|
+
} catch {
|
|
6365
|
+
return {};
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6130
6368
|
function setPublicAgent(accountDir, slug) {
|
|
6131
6369
|
const trimmed = slug.trim();
|
|
6132
6370
|
if (!trimmed) {
|
|
@@ -6150,12 +6388,12 @@ function setPublicAgent(accountDir, slug) {
|
|
|
6150
6388
|
}
|
|
6151
6389
|
config.whatsapp = parsed.data;
|
|
6152
6390
|
writeConfig(accountDir, config);
|
|
6153
|
-
console.error(`${
|
|
6391
|
+
console.error(`${TAG17} publicAgent set to ${trimmed}`);
|
|
6154
6392
|
reloadManagerConfig(accountDir);
|
|
6155
6393
|
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
6156
6394
|
} catch (err) {
|
|
6157
6395
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6158
|
-
console.error(`${
|
|
6396
|
+
console.error(`${TAG17} setPublicAgent failed: ${msg}`);
|
|
6159
6397
|
return { ok: false, error: msg };
|
|
6160
6398
|
}
|
|
6161
6399
|
}
|
|
@@ -6196,17 +6434,17 @@ function updateConfig(accountDir, fields) {
|
|
|
6196
6434
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6197
6435
|
if (!parsed.success) {
|
|
6198
6436
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6199
|
-
console.error(`${
|
|
6437
|
+
console.error(`${TAG17} update validation failed: ${msg}`);
|
|
6200
6438
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6201
6439
|
}
|
|
6202
6440
|
config.whatsapp = parsed.data;
|
|
6203
6441
|
writeConfig(accountDir, config);
|
|
6204
|
-
console.error(`${
|
|
6442
|
+
console.error(`${TAG17} updated fields=[${fieldNames.join(",")}]`);
|
|
6205
6443
|
reloadManagerConfig(accountDir);
|
|
6206
6444
|
return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
|
|
6207
6445
|
} catch (err) {
|
|
6208
6446
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6209
|
-
console.error(`${
|
|
6447
|
+
console.error(`${TAG17} updateConfig failed: ${msg}`);
|
|
6210
6448
|
return { ok: false, error: msg };
|
|
6211
6449
|
}
|
|
6212
6450
|
}
|
|
@@ -6243,17 +6481,17 @@ function migrateRemovedConfigKeys(accountDir) {
|
|
|
6243
6481
|
}
|
|
6244
6482
|
writeConfig(accountDir, config);
|
|
6245
6483
|
console.error(
|
|
6246
|
-
`${
|
|
6484
|
+
`${TAG17} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
|
|
6247
6485
|
);
|
|
6248
6486
|
return { dropped: result.droppedKeys };
|
|
6249
6487
|
} catch (err) {
|
|
6250
|
-
console.error(`${
|
|
6488
|
+
console.error(`${TAG17} migration failed: ${String(err)}`);
|
|
6251
6489
|
return { dropped: [] };
|
|
6252
6490
|
}
|
|
6253
6491
|
}
|
|
6254
6492
|
|
|
6255
6493
|
// app/lib/whatsapp/login.ts
|
|
6256
|
-
var
|
|
6494
|
+
var TAG18 = "[whatsapp:login]";
|
|
6257
6495
|
function maskPhone(digits) {
|
|
6258
6496
|
if (digits.length <= 4) return "*".repeat(digits.length);
|
|
6259
6497
|
return digits.slice(0, 2) + "*".repeat(digits.length - 4) + digits.slice(-2);
|
|
@@ -6264,7 +6502,7 @@ function closeSocket(sock) {
|
|
|
6264
6502
|
try {
|
|
6265
6503
|
sock.ws?.close?.();
|
|
6266
6504
|
} catch (err) {
|
|
6267
|
-
console.warn(`${
|
|
6505
|
+
console.warn(`${TAG18} socket close error during cleanup: ${String(err)}`);
|
|
6268
6506
|
}
|
|
6269
6507
|
}
|
|
6270
6508
|
function resetActiveLogin(accountId) {
|
|
@@ -6291,18 +6529,18 @@ async function runPostPairing(login) {
|
|
|
6291
6529
|
const masked = selfPhone ? `${selfPhone.slice(0, 4)}***` : "null";
|
|
6292
6530
|
console.error(`[whatsapp-persist] wa-persist-on-connect account=${login.accountId} phone=${masked}`);
|
|
6293
6531
|
} else {
|
|
6294
|
-
console.error(`${
|
|
6532
|
+
console.error(`${TAG18} wa-persist-on-connect FAILED account=${login.accountId}: ${result.error}`);
|
|
6295
6533
|
}
|
|
6296
6534
|
} catch (err) {
|
|
6297
|
-
console.error(`${
|
|
6535
|
+
console.error(`${TAG18} wa-persist-on-connect error account=${login.accountId}: ${String(err)}`);
|
|
6298
6536
|
}
|
|
6299
6537
|
} else {
|
|
6300
|
-
console.error(`${
|
|
6538
|
+
console.error(`${TAG18} wa-persist-on-connect skipped \u2014 no accountDir account=${login.accountId}`);
|
|
6301
6539
|
}
|
|
6302
6540
|
try {
|
|
6303
6541
|
await registerLoginSocket(login.accountId, login.sock, login.authDir);
|
|
6304
6542
|
} catch (err) {
|
|
6305
|
-
console.error(`${
|
|
6543
|
+
console.error(`${TAG18} registerLoginSocket failed account=${login.accountId}: ${String(err)}`);
|
|
6306
6544
|
}
|
|
6307
6545
|
}
|
|
6308
6546
|
async function loginConnectionLoop(accountId, login) {
|
|
@@ -6311,7 +6549,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6311
6549
|
const logTerminal = (outcome, errMsg) => {
|
|
6312
6550
|
const fc = credsCensus(login.authDir);
|
|
6313
6551
|
console.error(
|
|
6314
|
-
`${
|
|
6552
|
+
`${TAG18} op=terminal cid=${cidShort} outcome=${outcome} credsFinal={registered=${fc.registered},account=${fc.hasAccountSignature},me=${fc.e164 ?? "null"}}` + (errMsg ? ` error="${errMsg}"` : "")
|
|
6315
6553
|
);
|
|
6316
6554
|
};
|
|
6317
6555
|
while (true) {
|
|
@@ -6321,7 +6559,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6321
6559
|
if (current?.id === login.id) {
|
|
6322
6560
|
await runPostPairing(current);
|
|
6323
6561
|
current.connected = true;
|
|
6324
|
-
console.error(`${
|
|
6562
|
+
console.error(`${TAG18} loginConnectionLoop: connected account=${accountId} attempt=${attempt} configPersisted=${current.configPersisted}`);
|
|
6325
6563
|
logTerminal("connected");
|
|
6326
6564
|
}
|
|
6327
6565
|
return;
|
|
@@ -6333,7 +6571,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6333
6571
|
if (!preRetryCensus.hasAccountSignature) {
|
|
6334
6572
|
current.error = "WhatsApp accepted the code but did not finish linking. This usually means the number has reached its linked-device limit or has a stale linked device. On the phone, open WhatsApp, Linked Devices, remove existing devices, then ask for a new code.";
|
|
6335
6573
|
console.error(
|
|
6336
|
-
`${
|
|
6574
|
+
`${TAG18} op=pairing-incomplete cid=${cidShort} closeKind=${closeKind(getStatusCode(err), false)} reason=no-account-signature`
|
|
6337
6575
|
);
|
|
6338
6576
|
return;
|
|
6339
6577
|
}
|
|
@@ -6341,7 +6579,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6341
6579
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6342
6580
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6343
6581
|
console.error(
|
|
6344
|
-
`${
|
|
6582
|
+
`${TAG18} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
6345
6583
|
);
|
|
6346
6584
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
6347
6585
|
} else {
|
|
@@ -6354,7 +6592,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6354
6592
|
attempt++;
|
|
6355
6593
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
6356
6594
|
console.error(
|
|
6357
|
-
`${
|
|
6595
|
+
`${TAG18} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
6358
6596
|
);
|
|
6359
6597
|
closeSocket(current.sock);
|
|
6360
6598
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -6362,14 +6600,14 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6362
6600
|
if (afterDelay?.id !== login.id) return;
|
|
6363
6601
|
const rc = credsCensus(login.authDir);
|
|
6364
6602
|
console.error(
|
|
6365
|
-
`${
|
|
6603
|
+
`${TAG18} op=reconnect cid=${cidShort} attempt=${attempt}/${LOGIN_MAX_RECONNECTS} withRegistered=${rc.registered} withAccount=${rc.hasAccountSignature}`
|
|
6366
6604
|
);
|
|
6367
6605
|
try {
|
|
6368
6606
|
const newSock = await createWaSocket({ authDir: login.authDir, correlationId: cidShort, account: accountId });
|
|
6369
6607
|
current.sock = newSock;
|
|
6370
6608
|
} catch (sockErr) {
|
|
6371
6609
|
console.error(
|
|
6372
|
-
`${
|
|
6610
|
+
`${TAG18} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
6373
6611
|
);
|
|
6374
6612
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
6375
6613
|
logTerminal("error", current.error);
|
|
@@ -6386,7 +6624,7 @@ async function startLogin(opts) {
|
|
|
6386
6624
|
const hasAuth = await authExists(authDir);
|
|
6387
6625
|
const selfId = readSelfId(authDir);
|
|
6388
6626
|
console.error(
|
|
6389
|
-
`${
|
|
6627
|
+
`${TAG18} op=start cid=${cid} account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasCode=${!!existing0.pairingCode}}` : " activeLogin=none")
|
|
6390
6628
|
);
|
|
6391
6629
|
if (hasAuth && !force) {
|
|
6392
6630
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -6401,7 +6639,7 @@ async function startLogin(opts) {
|
|
|
6401
6639
|
}
|
|
6402
6640
|
const existing = activeLogins.get(accountId);
|
|
6403
6641
|
if (existing && isLoginFresh(existing) && existing.pairingCode && !force) {
|
|
6404
|
-
console.error(`${
|
|
6642
|
+
console.error(`${TAG18} startLogin account=${accountId} guard: returning existing pairing code (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
6405
6643
|
return {
|
|
6406
6644
|
pairingCode: existing.pairingCode,
|
|
6407
6645
|
phone: existing.phone,
|
|
@@ -6409,7 +6647,7 @@ async function startLogin(opts) {
|
|
|
6409
6647
|
};
|
|
6410
6648
|
}
|
|
6411
6649
|
if (existing) {
|
|
6412
|
-
console.error(`${
|
|
6650
|
+
console.error(`${TAG18} startLogin account=${accountId} ${force ? "force override" : "stale/no-code"}, resetting active login`);
|
|
6413
6651
|
}
|
|
6414
6652
|
resetActiveLogin(accountId);
|
|
6415
6653
|
await clearAuth(authDir);
|
|
@@ -6440,7 +6678,7 @@ async function startLogin(opts) {
|
|
|
6440
6678
|
if (requested) return;
|
|
6441
6679
|
requested = true;
|
|
6442
6680
|
console.error(
|
|
6443
|
-
`${
|
|
6681
|
+
`${TAG18} op=pairing-request cid=${cid} account=${accountId} qrRef=#1 sinceOpenMs=${socketOpenTs ? Date.now() - socketOpenTs : 0}`
|
|
6444
6682
|
);
|
|
6445
6683
|
void sock.requestPairingCode(digits).then((code) => {
|
|
6446
6684
|
clearTimeout(codeTimer);
|
|
@@ -6449,7 +6687,7 @@ async function startLogin(opts) {
|
|
|
6449
6687
|
if (current?.id !== login.id) return;
|
|
6450
6688
|
if (!current.pairingCode) current.pairingCode = code;
|
|
6451
6689
|
console.error(
|
|
6452
|
-
`${
|
|
6690
|
+
`${TAG18} op=code-issued cid=${cid} account=${accountId} phone=${maskPhone(digits)} codeLen=${code.length} sinceOpenMs=${socketOpenTs ? codeIssuedTs - socketOpenTs : 0}`
|
|
6453
6691
|
);
|
|
6454
6692
|
resolveCode?.(code);
|
|
6455
6693
|
}).catch((err) => {
|
|
@@ -6480,7 +6718,7 @@ async function startLogin(opts) {
|
|
|
6480
6718
|
const ttlTimer = setTimeout(() => {
|
|
6481
6719
|
const current = activeLogins.get(accountId);
|
|
6482
6720
|
if (current?.id === login.id && !current.connected) {
|
|
6483
|
-
console.error(`${
|
|
6721
|
+
console.error(`${TAG18} pairing-window-elapsed account=${accountId} (ttl sweep)`);
|
|
6484
6722
|
resetActiveLogin(accountId);
|
|
6485
6723
|
}
|
|
6486
6724
|
}, ACTIVE_LOGIN_TTL_MS);
|
|
@@ -6488,7 +6726,7 @@ async function startLogin(opts) {
|
|
|
6488
6726
|
ttlTimer.unref();
|
|
6489
6727
|
}
|
|
6490
6728
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
6491
|
-
console.error(`${
|
|
6729
|
+
console.error(`${TAG18} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
6492
6730
|
const current = activeLogins.get(accountId);
|
|
6493
6731
|
if (current?.id === login.id) {
|
|
6494
6732
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -6513,13 +6751,13 @@ async function waitForLogin(opts) {
|
|
|
6513
6751
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
6514
6752
|
const login = activeLogins.get(accountId);
|
|
6515
6753
|
console.error(
|
|
6516
|
-
`${
|
|
6754
|
+
`${TAG18} 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},hasCode=${!!login.pairingCode}}` : " login=none")
|
|
6517
6755
|
);
|
|
6518
6756
|
if (!login) {
|
|
6519
6757
|
return { connected: false, message: "No active WhatsApp login in progress.", configPersisted: false };
|
|
6520
6758
|
}
|
|
6521
6759
|
if (!isLoginFresh(login)) {
|
|
6522
|
-
console.error(`${
|
|
6760
|
+
console.error(`${TAG18} pairing-window-elapsed account=${accountId}`);
|
|
6523
6761
|
resetActiveLogin(accountId);
|
|
6524
6762
|
return { connected: false, message: "The pairing window expired. Ask me to generate a new code.", configPersisted: false };
|
|
6525
6763
|
}
|
|
@@ -6527,8 +6765,8 @@ async function waitForLogin(opts) {
|
|
|
6527
6765
|
while (Date.now() < deadline) {
|
|
6528
6766
|
if (login.connected) {
|
|
6529
6767
|
const selfId = readSelfId(login.authDir);
|
|
6530
|
-
console.error(`${
|
|
6531
|
-
console.error(`${
|
|
6768
|
+
console.error(`${TAG18} pairing-connected account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
6769
|
+
console.error(`${TAG18} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"} configPersisted=${login.configPersisted}`);
|
|
6532
6770
|
const configPersisted = login.configPersisted;
|
|
6533
6771
|
activeLogins.delete(accountId);
|
|
6534
6772
|
return {
|
|
@@ -6546,7 +6784,7 @@ async function waitForLogin(opts) {
|
|
|
6546
6784
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
6547
6785
|
}
|
|
6548
6786
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
6549
|
-
console.error(`${
|
|
6787
|
+
console.error(`${TAG18} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
|
|
6550
6788
|
return { connected: false, message: "Still waiting for you to enter the pairing code. The code is still valid \u2014 ask me to keep waiting, or request a new code.", configPersisted: false };
|
|
6551
6789
|
}
|
|
6552
6790
|
function listActiveLoginAccountIds() {
|
|
@@ -6561,7 +6799,7 @@ function listActiveLoginAccountIds() {
|
|
|
6561
6799
|
import { realpathSync as realpathSync3 } from "fs";
|
|
6562
6800
|
import { readFile, stat as stat2 } from "fs/promises";
|
|
6563
6801
|
import { resolve as resolve9, basename as basename2 } from "path";
|
|
6564
|
-
var
|
|
6802
|
+
var TAG19 = "[whatsapp:outbound]";
|
|
6565
6803
|
var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
|
|
6566
6804
|
var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
6567
6805
|
var lastRouteDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
@@ -6595,16 +6833,16 @@ async function sendWhatsAppDocument(input) {
|
|
|
6595
6833
|
const accountResolved = realpathSync3(accountDir);
|
|
6596
6834
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
6597
6835
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
6598
|
-
console.error(`${
|
|
6836
|
+
console.error(`${TAG19} document REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
6599
6837
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
6600
6838
|
}
|
|
6601
6839
|
} catch (err) {
|
|
6602
6840
|
const code = err.code;
|
|
6603
6841
|
if (code === "ENOENT") {
|
|
6604
|
-
console.error(`${
|
|
6842
|
+
console.error(`${TAG19} document ENOENT path=${filePath}`);
|
|
6605
6843
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
6606
6844
|
}
|
|
6607
|
-
console.error(`${
|
|
6845
|
+
console.error(`${TAG19} document path error: ${String(err)}`);
|
|
6608
6846
|
return { ok: false, status: 500, error: String(err) };
|
|
6609
6847
|
}
|
|
6610
6848
|
const fileStat = await stat2(resolvedPath);
|
|
@@ -6619,7 +6857,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
6619
6857
|
const jid = normalizeJid2(to);
|
|
6620
6858
|
const sock = getSocket(accountId);
|
|
6621
6859
|
if (!sock) {
|
|
6622
|
-
console.error(`${
|
|
6860
|
+
console.error(`${TAG19} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=false reason=not-connected`);
|
|
6623
6861
|
return { ok: false, status: 503, error: `WhatsApp account "${accountId}" is not connected` };
|
|
6624
6862
|
}
|
|
6625
6863
|
const buffer = Buffer.from(await readFile(resolvedPath));
|
|
@@ -6631,7 +6869,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
6631
6869
|
{ accountId }
|
|
6632
6870
|
);
|
|
6633
6871
|
console.error(
|
|
6634
|
-
`${
|
|
6872
|
+
`${TAG19} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
6635
6873
|
);
|
|
6636
6874
|
if (result.success) {
|
|
6637
6875
|
recordDocumentOutbound(to);
|
|
@@ -6751,7 +6989,7 @@ function serializeWhatsAppSchema() {
|
|
|
6751
6989
|
// app/lib/whatsapp/status-reconcile.ts
|
|
6752
6990
|
import { readdirSync as readdirSync3 } from "fs";
|
|
6753
6991
|
import { join as join9 } from "path";
|
|
6754
|
-
var
|
|
6992
|
+
var TAG20 = "[whatsapp:reconcile]";
|
|
6755
6993
|
var HALF_REGISTERED_MIN_AGE_MS = 5 * 6e4;
|
|
6756
6994
|
function listCredsAccountIds(credsRoot) {
|
|
6757
6995
|
try {
|
|
@@ -6777,14 +7015,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6777
7015
|
const ageMs = getAuthAgeMs(authDir);
|
|
6778
7016
|
const abandoned = !inFlight && ageMs !== null && ageMs >= HALF_REGISTERED_MIN_AGE_MS;
|
|
6779
7017
|
if (!abandoned) {
|
|
6780
|
-
console.error(`${
|
|
7018
|
+
console.error(`${TAG20} op=half-registered account=${accountId} registered=true hasAccount=false inFlight=${inFlight} ageMs=${ageMs ?? "null"}`);
|
|
6781
7019
|
entries.push({ accountId, connected: false, linkedUnconfigured: true });
|
|
6782
7020
|
continue;
|
|
6783
7021
|
}
|
|
6784
|
-
console.error(`${
|
|
7022
|
+
console.error(`${TAG20} op=discard-half-registered account=${accountId} registered=true hasAccount=false ageMs=${ageMs}`);
|
|
6785
7023
|
const removed = discardAuthDirSync(authDir);
|
|
6786
7024
|
if (!removed) {
|
|
6787
|
-
console.error(`${
|
|
7025
|
+
console.error(`${TAG20} op=discard-half-registered-failed account=${accountId} \u2014 residue persists`);
|
|
6788
7026
|
entries.push({ accountId, connected: false, linkedUnconfigured: true });
|
|
6789
7027
|
}
|
|
6790
7028
|
continue;
|
|
@@ -6792,14 +7030,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6792
7030
|
const selfPhone = readSelfId(authDir).e164 ?? void 0;
|
|
6793
7031
|
const configured = accountDir ? isAccountConfigured(accountDir, accountId) : false;
|
|
6794
7032
|
console.error(
|
|
6795
|
-
`${
|
|
7033
|
+
`${TAG20} wa-link-unconfigured account=${accountId} creds=present config=${configured ? "present" : "absent"}`
|
|
6796
7034
|
);
|
|
6797
7035
|
if (!configured && accountDir) {
|
|
6798
7036
|
const result = persistAfterPairing(accountDir, accountId, selfPhone ?? null);
|
|
6799
7037
|
if (result.ok) {
|
|
6800
|
-
console.error(`${
|
|
7038
|
+
console.error(`${TAG20} self-healed account=${accountId}`);
|
|
6801
7039
|
} else {
|
|
6802
|
-
console.error(`${
|
|
7040
|
+
console.error(`${TAG20} self-heal FAILED account=${accountId}: ${result.error}`);
|
|
6803
7041
|
}
|
|
6804
7042
|
}
|
|
6805
7043
|
entries.push({ accountId, selfPhone, connected: false, linkedUnconfigured: !configured });
|
|
@@ -6808,7 +7046,7 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6808
7046
|
}
|
|
6809
7047
|
|
|
6810
7048
|
// server/routes/whatsapp.ts
|
|
6811
|
-
var
|
|
7049
|
+
var TAG21 = "[whatsapp:api]";
|
|
6812
7050
|
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
6813
7051
|
var app2 = new Hono();
|
|
6814
7052
|
app2.get("/status", (c) => {
|
|
@@ -6821,10 +7059,10 @@ app2.get("/status", (c) => {
|
|
|
6821
7059
|
const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds, activeLoginAccountIds: activeLoginIds });
|
|
6822
7060
|
const accounts = [...live, ...reconciled];
|
|
6823
7061
|
const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
|
|
6824
|
-
console.error(`${
|
|
7062
|
+
console.error(`${TAG21} status accounts=${accounts.length} [${summary}]`);
|
|
6825
7063
|
return c.json({ accounts });
|
|
6826
7064
|
} catch (err) {
|
|
6827
|
-
console.error(`${
|
|
7065
|
+
console.error(`${TAG21} status error: ${String(err)}`);
|
|
6828
7066
|
return c.json({ error: String(err) }, 500);
|
|
6829
7067
|
}
|
|
6830
7068
|
});
|
|
@@ -6840,10 +7078,10 @@ app2.post("/login/start", async (c) => {
|
|
|
6840
7078
|
const authDir = join10(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
6841
7079
|
const accountDir = resolveAccount()?.accountDir ?? null;
|
|
6842
7080
|
const result = await startLogin({ accountId, authDir, phone, accountDir, force });
|
|
6843
|
-
console.error(`${
|
|
7081
|
+
console.error(`${TAG21} login/start result account=${accountId} hasCode=${!!result.pairingCode}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
6844
7082
|
return c.json(result);
|
|
6845
7083
|
} catch (err) {
|
|
6846
|
-
console.error(`${
|
|
7084
|
+
console.error(`${TAG21} login/start error: ${String(err)}`);
|
|
6847
7085
|
return c.json({ error: String(err) }, 500);
|
|
6848
7086
|
}
|
|
6849
7087
|
});
|
|
@@ -6853,7 +7091,7 @@ app2.post("/login/wait", async (c) => {
|
|
|
6853
7091
|
const accountId = validateAccountId(body.accountId);
|
|
6854
7092
|
const timeoutMs = body.timeoutMs ?? 6e4;
|
|
6855
7093
|
const result = await waitForLogin({ accountId, timeoutMs });
|
|
6856
|
-
console.error(`${
|
|
7094
|
+
console.error(`${TAG21} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
|
|
6857
7095
|
return c.json({
|
|
6858
7096
|
connected: result.connected,
|
|
6859
7097
|
message: result.message,
|
|
@@ -6861,7 +7099,7 @@ app2.post("/login/wait", async (c) => {
|
|
|
6861
7099
|
configPersisted: result.configPersisted
|
|
6862
7100
|
});
|
|
6863
7101
|
} catch (err) {
|
|
6864
|
-
console.error(`${
|
|
7102
|
+
console.error(`${TAG21} login/wait error: ${String(err)}`);
|
|
6865
7103
|
return c.json({ error: String(err) }, 500);
|
|
6866
7104
|
}
|
|
6867
7105
|
});
|
|
@@ -6872,7 +7110,7 @@ app2.post("/disconnect", async (c) => {
|
|
|
6872
7110
|
await stopConnection(accountId);
|
|
6873
7111
|
return c.json({ disconnected: true, accountId });
|
|
6874
7112
|
} catch (err) {
|
|
6875
|
-
console.error(`${
|
|
7113
|
+
console.error(`${TAG21} disconnect error: ${String(err)}`);
|
|
6876
7114
|
return c.json({ error: String(err) }, 500);
|
|
6877
7115
|
}
|
|
6878
7116
|
});
|
|
@@ -6883,14 +7121,14 @@ app2.post("/reconnect", async (c) => {
|
|
|
6883
7121
|
await startConnection(accountId);
|
|
6884
7122
|
return c.json({ reconnecting: true, accountId });
|
|
6885
7123
|
} catch (err) {
|
|
6886
|
-
console.error(`${
|
|
7124
|
+
console.error(`${TAG21} reconnect error: ${String(err)}`);
|
|
6887
7125
|
return c.json({ error: String(err) }, 500);
|
|
6888
7126
|
}
|
|
6889
7127
|
});
|
|
6890
7128
|
app2.post("/config", async (c) => {
|
|
6891
7129
|
try {
|
|
6892
7130
|
const body = await c.req.json().catch(() => ({}));
|
|
6893
|
-
const { action, phone, slug, fields, accountId } = body;
|
|
7131
|
+
const { action, phone, slug, fields, accountId, managesAccount } = body;
|
|
6894
7132
|
if (!action || typeof action !== "string") {
|
|
6895
7133
|
return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
|
|
6896
7134
|
}
|
|
@@ -6904,7 +7142,7 @@ app2.post("/config", async (c) => {
|
|
|
6904
7142
|
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
6905
7143
|
}
|
|
6906
7144
|
const result = addAdminPhone(account.accountDir, phone);
|
|
6907
|
-
console.error(`${
|
|
7145
|
+
console.error(`${TAG21} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
|
|
6908
7146
|
return c.json(result, result.ok ? 200 : 400);
|
|
6909
7147
|
}
|
|
6910
7148
|
case "remove-admin-phone": {
|
|
@@ -6912,26 +7150,50 @@ app2.post("/config", async (c) => {
|
|
|
6912
7150
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
6913
7151
|
}
|
|
6914
7152
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
6915
|
-
console.error(`${
|
|
7153
|
+
console.error(`${TAG21} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
6916
7154
|
return c.json(result, result.ok ? 200 : 400);
|
|
6917
7155
|
}
|
|
6918
7156
|
case "list-admin-phones": {
|
|
6919
7157
|
const phones = readAdminPhones(account.accountDir);
|
|
6920
|
-
console.error(`${
|
|
7158
|
+
console.error(`${TAG21} config action=list-admin-phones count=${phones.length}`);
|
|
6921
7159
|
return c.json({ ok: true, phones });
|
|
6922
7160
|
}
|
|
7161
|
+
case "add-account-manager": {
|
|
7162
|
+
if (!phone || typeof phone !== "string") {
|
|
7163
|
+
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
7164
|
+
}
|
|
7165
|
+
if (!managesAccount || typeof managesAccount !== "string") {
|
|
7166
|
+
return c.json({ ok: false, error: 'Missing required field "managesAccount" (the sub-account UUID this phone should manage).' }, 400);
|
|
7167
|
+
}
|
|
7168
|
+
const result = setAccountManager(account.accountDir, phone, managesAccount);
|
|
7169
|
+
console.error(`${TAG21} config action=add-account-manager phone=${phone} managesAccount=${managesAccount} ok=${result.ok}`);
|
|
7170
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
7171
|
+
}
|
|
7172
|
+
case "remove-account-manager": {
|
|
7173
|
+
if (!phone || typeof phone !== "string") {
|
|
7174
|
+
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
7175
|
+
}
|
|
7176
|
+
const result = clearAccountManager(account.accountDir, phone);
|
|
7177
|
+
console.error(`${TAG21} config action=remove-account-manager phone=${phone} ok=${result.ok}`);
|
|
7178
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
7179
|
+
}
|
|
7180
|
+
case "list-account-managers": {
|
|
7181
|
+
const accountManagers = readAccountManagers(account.accountDir);
|
|
7182
|
+
console.error(`${TAG21} config action=list-account-managers count=${Object.keys(accountManagers).length}`);
|
|
7183
|
+
return c.json({ ok: true, accountManagers });
|
|
7184
|
+
}
|
|
6923
7185
|
case "set-public-agent": {
|
|
6924
7186
|
if (!slug || typeof slug !== "string") {
|
|
6925
7187
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
6926
7188
|
}
|
|
6927
7189
|
const result = setPublicAgent(account.accountDir, slug);
|
|
6928
|
-
console.error(`${
|
|
7190
|
+
console.error(`${TAG21} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
6929
7191
|
return c.json(result, result.ok ? 200 : 400);
|
|
6930
7192
|
}
|
|
6931
7193
|
case "get-public-agent": {
|
|
6932
7194
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
6933
7195
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
|
|
6934
|
-
console.error(`${
|
|
7196
|
+
console.error(`${TAG21} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
6935
7197
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
6936
7198
|
}
|
|
6937
7199
|
case "list-public-agents": {
|
|
@@ -6948,26 +7210,26 @@ app2.post("/config", async (c) => {
|
|
|
6948
7210
|
const config = JSON.parse(readFileSync11(configPath2, "utf-8"));
|
|
6949
7211
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
6950
7212
|
} catch {
|
|
6951
|
-
console.error(`${
|
|
7213
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
6952
7214
|
}
|
|
6953
7215
|
}
|
|
6954
7216
|
} catch (err) {
|
|
6955
|
-
console.error(`${
|
|
7217
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
6956
7218
|
}
|
|
6957
7219
|
}
|
|
6958
|
-
console.error(`${
|
|
7220
|
+
console.error(`${TAG21} config action=list-public-agents count=${agents.length}`);
|
|
6959
7221
|
return c.json({ ok: true, agents });
|
|
6960
7222
|
}
|
|
6961
7223
|
case "schema": {
|
|
6962
7224
|
const text = serializeWhatsAppSchema();
|
|
6963
|
-
console.error(`${
|
|
7225
|
+
console.error(`${TAG21} config action=schema`);
|
|
6964
7226
|
return c.json({ ok: true, text });
|
|
6965
7227
|
}
|
|
6966
7228
|
case "list-groups": {
|
|
6967
7229
|
const groupAccountId = accountId ?? "default";
|
|
6968
7230
|
const sock = getSocket(groupAccountId);
|
|
6969
7231
|
if (!sock) {
|
|
6970
|
-
console.error(`${
|
|
7232
|
+
console.error(`${TAG21} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
6971
7233
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
6972
7234
|
}
|
|
6973
7235
|
try {
|
|
@@ -6977,10 +7239,10 @@ app2.post("/config", async (c) => {
|
|
|
6977
7239
|
name: g.subject ?? g.id,
|
|
6978
7240
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
6979
7241
|
}));
|
|
6980
|
-
console.error(`${
|
|
7242
|
+
console.error(`${TAG21} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
6981
7243
|
return c.json({ ok: true, groups });
|
|
6982
7244
|
} catch (err) {
|
|
6983
|
-
console.error(`${
|
|
7245
|
+
console.error(`${TAG21} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
6984
7246
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
6985
7247
|
}
|
|
6986
7248
|
}
|
|
@@ -6990,22 +7252,22 @@ app2.post("/config", async (c) => {
|
|
|
6990
7252
|
}
|
|
6991
7253
|
const result = updateConfig(account.accountDir, fields);
|
|
6992
7254
|
const fieldNames = Object.keys(fields);
|
|
6993
|
-
console.error(`${
|
|
7255
|
+
console.error(`${TAG21} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
6994
7256
|
return c.json(result, result.ok ? 200 : 400);
|
|
6995
7257
|
}
|
|
6996
7258
|
case "get-config": {
|
|
6997
7259
|
const waConfig = getConfig(account.accountDir);
|
|
6998
|
-
console.error(`${
|
|
7260
|
+
console.error(`${TAG21} config action=get-config`);
|
|
6999
7261
|
return c.json({ ok: true, config: waConfig });
|
|
7000
7262
|
}
|
|
7001
7263
|
default:
|
|
7002
7264
|
return c.json(
|
|
7003
|
-
{ ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
|
|
7265
|
+
{ ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, add-account-manager, remove-account-manager, list-account-managers, set-public-agent, get-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
|
|
7004
7266
|
400
|
|
7005
7267
|
);
|
|
7006
7268
|
}
|
|
7007
7269
|
} catch (err) {
|
|
7008
|
-
console.error(`${
|
|
7270
|
+
console.error(`${TAG21} config error: ${String(err)}`);
|
|
7009
7271
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
7010
7272
|
}
|
|
7011
7273
|
});
|
|
@@ -7026,7 +7288,7 @@ app2.post("/send-document", async (c) => {
|
|
|
7026
7288
|
recordRouteDocumentOutbound(to, filePath);
|
|
7027
7289
|
return c.json({ success: true, messageId: result.messageId });
|
|
7028
7290
|
} catch (err) {
|
|
7029
|
-
console.error(`${
|
|
7291
|
+
console.error(`${TAG21} send-document error: ${String(err)}`);
|
|
7030
7292
|
return c.json({ error: String(err) }, 500);
|
|
7031
7293
|
}
|
|
7032
7294
|
});
|
|
@@ -7063,7 +7325,7 @@ app2.post("/send-admin", async (c) => {
|
|
|
7063
7325
|
}
|
|
7064
7326
|
return c.json({ success: true, messageId: result.messageId });
|
|
7065
7327
|
} catch (err) {
|
|
7066
|
-
console.error(`${
|
|
7328
|
+
console.error(`${TAG21} send-admin error: ${String(err)}`);
|
|
7067
7329
|
return c.json({ error: String(err) }, 500);
|
|
7068
7330
|
}
|
|
7069
7331
|
});
|
|
@@ -7073,11 +7335,11 @@ app2.get("/activity", (c) => {
|
|
|
7073
7335
|
const result = getChannelActivity(accountId);
|
|
7074
7336
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
7075
7337
|
console.error(
|
|
7076
|
-
`${
|
|
7338
|
+
`${TAG21} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
7077
7339
|
);
|
|
7078
7340
|
return c.json(result);
|
|
7079
7341
|
} catch (err) {
|
|
7080
|
-
console.error(`${
|
|
7342
|
+
console.error(`${TAG21} activity error: ${String(err)}`);
|
|
7081
7343
|
return c.json({ error: String(err) }, 500);
|
|
7082
7344
|
}
|
|
7083
7345
|
});
|
|
@@ -7096,10 +7358,10 @@ app2.get("/conversations", (c) => {
|
|
|
7096
7358
|
};
|
|
7097
7359
|
});
|
|
7098
7360
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
7099
|
-
console.error(`${
|
|
7361
|
+
console.error(`${TAG21} conversations account=${accountId} count=${conversations.length}`);
|
|
7100
7362
|
return c.json({ conversations });
|
|
7101
7363
|
} catch (err) {
|
|
7102
|
-
console.error(`${
|
|
7364
|
+
console.error(`${TAG21} conversations error: ${String(err)}`);
|
|
7103
7365
|
return c.json({ error: String(err) }, 500);
|
|
7104
7366
|
}
|
|
7105
7367
|
});
|
|
@@ -7114,10 +7376,10 @@ app2.get("/messages", (c) => {
|
|
|
7114
7376
|
const limit = limitParam ? parseInt(limitParam, 10) : void 0;
|
|
7115
7377
|
const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
|
|
7116
7378
|
const messages = getMessages(accountId, jid, effectiveLimit);
|
|
7117
|
-
console.error(`${
|
|
7379
|
+
console.error(`${TAG21} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
|
|
7118
7380
|
return c.json({ messages });
|
|
7119
7381
|
} catch (err) {
|
|
7120
|
-
console.error(`${
|
|
7382
|
+
console.error(`${TAG21} messages error: ${String(err)}`);
|
|
7121
7383
|
return c.json({ error: String(err) }, 500);
|
|
7122
7384
|
}
|
|
7123
7385
|
});
|
|
@@ -7198,7 +7460,7 @@ app2.get("/conversation-graph-state", async (c) => {
|
|
|
7198
7460
|
ms
|
|
7199
7461
|
});
|
|
7200
7462
|
} catch (err) {
|
|
7201
|
-
console.error(`${
|
|
7463
|
+
console.error(`${TAG21} conversation-graph-state error: ${String(err)}`);
|
|
7202
7464
|
return c.json({ error: String(err) }, 500);
|
|
7203
7465
|
}
|
|
7204
7466
|
});
|
|
@@ -7210,12 +7472,12 @@ app2.get("/group-info", async (c) => {
|
|
|
7210
7472
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
7211
7473
|
}
|
|
7212
7474
|
if (!isGroupJid(jid)) {
|
|
7213
|
-
console.error(`${
|
|
7475
|
+
console.error(`${TAG21} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
7214
7476
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
7215
7477
|
}
|
|
7216
7478
|
const sock = getSocket(accountId);
|
|
7217
7479
|
if (!sock) {
|
|
7218
|
-
console.error(`${
|
|
7480
|
+
console.error(`${TAG21} group-info error="not connected" account=${accountId}`);
|
|
7219
7481
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
7220
7482
|
}
|
|
7221
7483
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -7228,10 +7490,10 @@ app2.get("/group-info", async (c) => {
|
|
|
7228
7490
|
participantCount: meta.participants.length,
|
|
7229
7491
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
7230
7492
|
};
|
|
7231
|
-
console.error(`${
|
|
7493
|
+
console.error(`${TAG21} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
7232
7494
|
return c.json(result);
|
|
7233
7495
|
} catch (err) {
|
|
7234
|
-
console.error(`${
|
|
7496
|
+
console.error(`${TAG21} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
7235
7497
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
7236
7498
|
}
|
|
7237
7499
|
});
|
|
@@ -7649,11 +7911,11 @@ app3.get("/", requireAdminSession, async (c) => {
|
|
|
7649
7911
|
var sidebar_sessions_default = app3;
|
|
7650
7912
|
|
|
7651
7913
|
// app/lib/admin-identity/pin-validator.ts
|
|
7652
|
-
import { createHash } from "crypto";
|
|
7914
|
+
import { createHash as createHash2 } from "crypto";
|
|
7653
7915
|
import { existsSync as existsSync7, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
7654
7916
|
import { join as join12 } from "path";
|
|
7655
7917
|
function hashPin(pin) {
|
|
7656
|
-
return
|
|
7918
|
+
return createHash2("sha256").update(pin).digest("hex");
|
|
7657
7919
|
}
|
|
7658
7920
|
function readUsersFile(usersFilePath) {
|
|
7659
7921
|
if (!existsSync7(usersFilePath)) return null;
|
|
@@ -8631,7 +8893,7 @@ function resolveOpenVisitorSession(rows, visitorId, agentSlug) {
|
|
|
8631
8893
|
|
|
8632
8894
|
// server/routes/public-reader.ts
|
|
8633
8895
|
var app5 = new Hono();
|
|
8634
|
-
var
|
|
8896
|
+
var TAG22 = "[public-webchat]";
|
|
8635
8897
|
function parseAccessSessionId(cookieHeader) {
|
|
8636
8898
|
if (!cookieHeader) return null;
|
|
8637
8899
|
const part = cookieHeader.split(";").map((p) => p.trim()).find((p) => p.startsWith("__access_session="));
|
|
@@ -8679,7 +8941,7 @@ function enumeratePublicRows() {
|
|
|
8679
8941
|
app5.get("/session", (c) => {
|
|
8680
8942
|
const visitor = resolveVisitor(c);
|
|
8681
8943
|
if (!visitor) {
|
|
8682
|
-
console.error(`${
|
|
8944
|
+
console.error(`${TAG22} op=reader-refused reason=no-anchor path=/session`);
|
|
8683
8945
|
return c.json({ error: "gate-required" }, 401);
|
|
8684
8946
|
}
|
|
8685
8947
|
const rows = enumeratePublicRows();
|
|
@@ -8698,11 +8960,11 @@ app5.get("/session", (c) => {
|
|
|
8698
8960
|
found = effectiveSlug ? resolveOpenVisitorSession(rows, visitor.visitorId, effectiveSlug) : null;
|
|
8699
8961
|
}
|
|
8700
8962
|
if (found) {
|
|
8701
|
-
console.log(`${
|
|
8963
|
+
console.log(`${TAG22} op=session-resume anchor=${visitor.kind} key=${(found.senderId ?? "").slice(0, 8)} sessionId=${found.sessionId.slice(0, 8)}`);
|
|
8702
8964
|
return c.json({ sessionId: found.sessionId, projectDir: found.projectDir, sessionKey: found.senderId });
|
|
8703
8965
|
}
|
|
8704
8966
|
const sessionKey = crypto.randomUUID();
|
|
8705
|
-
console.log(`${
|
|
8967
|
+
console.log(`${TAG22} op=session-new anchor=${visitor.kind} key=${sessionKey.slice(0, 8)}`);
|
|
8706
8968
|
return c.json({ sessionId: null, projectDir: null, sessionKey });
|
|
8707
8969
|
});
|
|
8708
8970
|
function publicUploadsDir(accountId, sessionId) {
|
|
@@ -8731,7 +8993,7 @@ function readFrom2(path2, from) {
|
|
|
8731
8993
|
app5.get("/stream", (c) => {
|
|
8732
8994
|
const visitor = resolveVisitor(c);
|
|
8733
8995
|
if (!visitor) {
|
|
8734
|
-
console.error(`${
|
|
8996
|
+
console.error(`${TAG22} op=reader-refused reason=no-anchor path=/stream`);
|
|
8735
8997
|
return c.json({ error: "gate-required" }, 401);
|
|
8736
8998
|
}
|
|
8737
8999
|
const sessionId = c.req.query("sessionId") ?? "";
|
|
@@ -8749,12 +9011,12 @@ app5.get("/stream", (c) => {
|
|
|
8749
9011
|
const isPublicWebchat = meta.role === "public" && meta.channel === "webchat";
|
|
8750
9012
|
const owns = visitor.kind === "person" ? meta.personId === visitor.personId : meta.personId === null && meta.visitorId === visitor.visitorId;
|
|
8751
9013
|
if (!(isPublicWebchat && owns)) {
|
|
8752
|
-
console.error(`${
|
|
9014
|
+
console.error(`${TAG22} op=reader-refused reason=not-owner anchor=${visitor.kind} sessionId=${sessionId.slice(0, 8)}`);
|
|
8753
9015
|
return c.json({ error: "forbidden" }, 403);
|
|
8754
9016
|
}
|
|
8755
9017
|
const senderShort = (meta.senderId ?? "").slice(0, 8);
|
|
8756
9018
|
const encoder = new TextEncoder();
|
|
8757
|
-
console.log(`${
|
|
9019
|
+
console.log(`${TAG22} op=reader-open anchor=${visitor.kind} key=${senderShort} mode=delivered-only sessionId=${sessionId.slice(0, 8)}`);
|
|
8758
9020
|
const send = (controller, turn, id) => controller.enqueue(encoder.encode(`id: ${id}
|
|
8759
9021
|
data: ${JSON.stringify(turn)}
|
|
8760
9022
|
|
|
@@ -8769,7 +9031,7 @@ data: ${JSON.stringify(turn)}
|
|
|
8769
9031
|
const { turns, dropped } = filterDeliveredFrames(lines);
|
|
8770
9032
|
if (uploadsDir) enrichPublicAttachments(turns, listSessionAttachmentsInDir(uploadsDir), attachmentCursor);
|
|
8771
9033
|
for (const turn of turns) send(controller, turn, offset);
|
|
8772
|
-
if (dropped > 0) console.log(`${
|
|
9034
|
+
if (dropped > 0) console.log(`${TAG22} op=reader-filtered key=${senderShort} dropped=${dropped}`);
|
|
8773
9035
|
};
|
|
8774
9036
|
try {
|
|
8775
9037
|
const { buf, end } = readFrom2(jsonlPath, 0);
|
|
@@ -8804,7 +9066,7 @@ data: ${JSON.stringify(turn)}
|
|
|
8804
9066
|
watcher?.close();
|
|
8805
9067
|
clearInterval(poll);
|
|
8806
9068
|
clearInterval(heartbeat);
|
|
8807
|
-
console.log(`${
|
|
9069
|
+
console.log(`${TAG22} op=reader-close key=${senderShort}`);
|
|
8808
9070
|
try {
|
|
8809
9071
|
controller.close();
|
|
8810
9072
|
} catch {
|
|
@@ -8924,18 +9186,6 @@ function writeCanonicalOverrideId(accountDir, userId, sessionId) {
|
|
|
8924
9186
|
renameSync(tmp, path2);
|
|
8925
9187
|
}
|
|
8926
9188
|
|
|
8927
|
-
// app/lib/channel-pty-bridge/admin-session-id.ts
|
|
8928
|
-
import { createHash as createHash2 } from "crypto";
|
|
8929
|
-
function adminSessionIdFor(accountId, senderId, personId) {
|
|
8930
|
-
const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
|
|
8931
|
-
const b = createHash2("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
|
|
8932
|
-
const v = Buffer.from(b);
|
|
8933
|
-
v[6] = v[6] & 15 | 64;
|
|
8934
|
-
v[8] = v[8] & 63 | 128;
|
|
8935
|
-
const h = v.toString("hex");
|
|
8936
|
-
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
8937
|
-
}
|
|
8938
|
-
|
|
8939
9189
|
// ../lib/models/src/index.ts
|
|
8940
9190
|
var OPUS_MODEL = "claude-opus-4-8[1m]";
|
|
8941
9191
|
var SONNET_MODEL = "claude-sonnet-5";
|
|
@@ -9913,7 +10163,7 @@ function routeTelegramUpdate(input) {
|
|
|
9913
10163
|
}
|
|
9914
10164
|
|
|
9915
10165
|
// server/routes/telegram.ts
|
|
9916
|
-
var
|
|
10166
|
+
var TAG23 = "[telegram-inbound]";
|
|
9917
10167
|
function configDirName() {
|
|
9918
10168
|
const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
|
|
9919
10169
|
const brandPath = join20(platformRoot3, "config", "brand.json");
|
|
@@ -9929,36 +10179,23 @@ function secretPath(botType) {
|
|
|
9929
10179
|
const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
|
|
9930
10180
|
return resolve15(homedir(), configDirName(), filename);
|
|
9931
10181
|
}
|
|
9932
|
-
async function sendTelegram(botToken, chatId, text) {
|
|
9933
|
-
try {
|
|
9934
|
-
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
9935
|
-
method: "POST",
|
|
9936
|
-
headers: { "Content-Type": "application/json" },
|
|
9937
|
-
body: JSON.stringify({ chat_id: chatId, text })
|
|
9938
|
-
});
|
|
9939
|
-
const data = await res.json();
|
|
9940
|
-
return data.ok ? { ok: true } : { ok: false, error: data.description ?? "send failed" };
|
|
9941
|
-
} catch (e) {
|
|
9942
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
9943
|
-
}
|
|
9944
|
-
}
|
|
9945
10182
|
var app7 = new Hono();
|
|
9946
10183
|
app7.post("/", async (c) => {
|
|
9947
10184
|
const botParam = c.req.query("bot");
|
|
9948
10185
|
const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
|
|
9949
10186
|
if (!botType) {
|
|
9950
|
-
console.error(`${
|
|
10187
|
+
console.error(`${TAG23} op=reject reason=missing-bot-param`);
|
|
9951
10188
|
return c.json({ ok: false }, 400);
|
|
9952
10189
|
}
|
|
9953
10190
|
const sp = secretPath(botType);
|
|
9954
10191
|
if (!existsSync12(sp)) {
|
|
9955
|
-
console.error(`${
|
|
10192
|
+
console.error(`${TAG23} op=reject reason=no-secret-file botType=${botType}`);
|
|
9956
10193
|
return c.json({ ok: false }, 401);
|
|
9957
10194
|
}
|
|
9958
10195
|
const expected = readFileSync21(sp, "utf-8").trim();
|
|
9959
10196
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
9960
10197
|
if (got !== expected) {
|
|
9961
|
-
console.error(`${
|
|
10198
|
+
console.error(`${TAG23} op=reject reason=bad-secret botType=${botType}`);
|
|
9962
10199
|
return c.json({ ok: false }, 401);
|
|
9963
10200
|
}
|
|
9964
10201
|
let update;
|
|
@@ -9969,40 +10206,40 @@ app7.post("/", async (c) => {
|
|
|
9969
10206
|
}
|
|
9970
10207
|
const account = resolveAccount();
|
|
9971
10208
|
if (!account) {
|
|
9972
|
-
console.error(`${
|
|
10209
|
+
console.error(`${TAG23} op=reject reason=no-account`);
|
|
9973
10210
|
return c.json({ ok: true }, 200);
|
|
9974
10211
|
}
|
|
9975
10212
|
const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
|
|
9976
10213
|
if (decision.kind === "ignore") {
|
|
9977
|
-
console.error(`${
|
|
10214
|
+
console.error(`${TAG23} op=ignore reason=${decision.reason}`);
|
|
9978
10215
|
return c.json({ ok: true }, 200);
|
|
9979
10216
|
}
|
|
9980
10217
|
if (decision.kind === "denied") {
|
|
9981
|
-
console.error(`${
|
|
10218
|
+
console.error(`${TAG23} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
|
|
9982
10219
|
return c.json({ ok: true }, 200);
|
|
9983
10220
|
}
|
|
9984
10221
|
const role = decision.agentType === "admin" ? "admin" : "public";
|
|
9985
10222
|
const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
|
|
9986
10223
|
if (!agentSlug) {
|
|
9987
|
-
console.error(`${
|
|
10224
|
+
console.error(`${TAG23} op=reject reason=no-default-agent`);
|
|
9988
10225
|
return c.json({ ok: true }, 200);
|
|
9989
10226
|
}
|
|
9990
|
-
console.error(`${
|
|
10227
|
+
console.error(`${TAG23} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
9991
10228
|
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
9992
10229
|
const { senderId, chatId, text } = decision;
|
|
9993
10230
|
const gateway = getTelegramGateway();
|
|
9994
10231
|
if (!gateway) {
|
|
9995
|
-
console.error(`${
|
|
10232
|
+
console.error(`${TAG23} op=reject reason=gateway-not-ready from=${senderId}`);
|
|
9996
10233
|
return c.json({ ok: true }, 200);
|
|
9997
10234
|
}
|
|
9998
10235
|
const reply = async (replyText) => {
|
|
9999
10236
|
if (!botToken) {
|
|
10000
10237
|
const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
|
|
10001
|
-
console.error(`${
|
|
10238
|
+
console.error(`${TAG23} op=reply-dropped reason=${reason} botType=${botType} from=${senderId}`);
|
|
10002
10239
|
return;
|
|
10003
10240
|
}
|
|
10004
|
-
const sent = await
|
|
10005
|
-
console.error(`${
|
|
10241
|
+
const sent = await sendTelegramText(botToken, chatId, replyText);
|
|
10242
|
+
console.error(`${TAG23} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
10006
10243
|
};
|
|
10007
10244
|
void gateway.handleInbound({
|
|
10008
10245
|
accountId: account.accountId,
|
|
@@ -10020,7 +10257,7 @@ var telegram_default = app7;
|
|
|
10020
10257
|
// server/routes/quickbooks.ts
|
|
10021
10258
|
import { join as join21 } from "path";
|
|
10022
10259
|
import { existsSync as existsSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync4 } from "fs";
|
|
10023
|
-
var
|
|
10260
|
+
var TAG24 = "[quickbooks]";
|
|
10024
10261
|
var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
|
|
10025
10262
|
var STATE_RE = /^[A-Za-z0-9_-]+$/;
|
|
10026
10263
|
function pendingPath(accountDir, state) {
|
|
@@ -10033,14 +10270,14 @@ async function completeConsent(args) {
|
|
|
10033
10270
|
const { accountDir, code, realmId } = args;
|
|
10034
10271
|
const state = args.state;
|
|
10035
10272
|
if (!state || !STATE_RE.test(state) || !existsSync13(pendingPath(accountDir, state))) {
|
|
10036
|
-
console.error(`${
|
|
10273
|
+
console.error(`${TAG24} op=callback state=${state ?? ""} stateValid=false realmId=${realmId ?? ""}`);
|
|
10037
10274
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
10038
10275
|
}
|
|
10039
10276
|
const claimFile = `${pendingPath(accountDir, state)}.claimed`;
|
|
10040
10277
|
try {
|
|
10041
10278
|
renameSync4(pendingPath(accountDir, state), claimFile);
|
|
10042
10279
|
} catch {
|
|
10043
|
-
console.error(`${
|
|
10280
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
10044
10281
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
10045
10282
|
}
|
|
10046
10283
|
try {
|
|
@@ -10048,14 +10285,14 @@ async function completeConsent(args) {
|
|
|
10048
10285
|
try {
|
|
10049
10286
|
pending = JSON.parse(readFileSync22(claimFile, "utf-8"));
|
|
10050
10287
|
} catch {
|
|
10051
|
-
console.error(`${
|
|
10288
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
10052
10289
|
return { ok: false, status: 400, message: "Authorization state could not be read.", stateValid: false };
|
|
10053
10290
|
}
|
|
10054
10291
|
if (Date.now() > pending.expiresAt) {
|
|
10055
|
-
console.error(`${
|
|
10292
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
10056
10293
|
return { ok: false, status: 400, message: "Authorization request expired. Generate a new consent link.", stateValid: false };
|
|
10057
10294
|
}
|
|
10058
|
-
console.error(`${
|
|
10295
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=true realmId=${realmId ?? ""}`);
|
|
10059
10296
|
if (!code || !realmId) {
|
|
10060
10297
|
return { ok: false, status: 400, message: "Missing code or realmId in the callback.", stateValid: true };
|
|
10061
10298
|
}
|
|
@@ -10079,12 +10316,12 @@ async function completeConsent(args) {
|
|
|
10079
10316
|
});
|
|
10080
10317
|
const text = await res.text();
|
|
10081
10318
|
if (!res.ok) {
|
|
10082
|
-
console.error(`${
|
|
10319
|
+
console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=false`);
|
|
10083
10320
|
return { ok: false, status: 502, message: `Token exchange failed (${res.status}). Generate a new consent link to retry.`, stateValid: true };
|
|
10084
10321
|
}
|
|
10085
10322
|
const body = JSON.parse(text);
|
|
10086
10323
|
if (!body.refresh_token) {
|
|
10087
|
-
console.error(`${
|
|
10324
|
+
console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=false`);
|
|
10088
10325
|
return { ok: false, status: 502, message: "Token exchange returned no refresh token.", stateValid: true };
|
|
10089
10326
|
}
|
|
10090
10327
|
const now = Date.now();
|
|
@@ -10097,7 +10334,7 @@ async function completeConsent(args) {
|
|
|
10097
10334
|
mkdirSync4(join21(accountDir, "secrets"), { recursive: true });
|
|
10098
10335
|
writeFileSync8(storePath(accountDir), JSON.stringify(store2, null, 2), { mode: 384 });
|
|
10099
10336
|
const persisted = JSON.parse(readFileSync22(storePath(accountDir), "utf-8")).connections?.[realmId]?.refreshToken === body.refresh_token;
|
|
10100
|
-
console.error(`${
|
|
10337
|
+
console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=${persisted}`);
|
|
10101
10338
|
return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
|
|
10102
10339
|
} finally {
|
|
10103
10340
|
rmSync(claimFile, { force: true });
|
|
@@ -10110,7 +10347,7 @@ var app8 = new Hono();
|
|
|
10110
10347
|
app8.get("/callback", async (c) => {
|
|
10111
10348
|
const account = resolveAccount();
|
|
10112
10349
|
if (!account) {
|
|
10113
|
-
console.error(`${
|
|
10350
|
+
console.error(`${TAG24} op=callback stateValid=false realmId= reason=no-account`);
|
|
10114
10351
|
return c.html(page("QuickBooks", "This install has no account configured."), 500);
|
|
10115
10352
|
}
|
|
10116
10353
|
const result = await completeConsent({
|
|
@@ -11211,11 +11448,15 @@ function uploadsDirFor2(accountId, attachmentId) {
|
|
|
11211
11448
|
app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
11212
11449
|
const attachmentId = c.req.param("attachmentId");
|
|
11213
11450
|
const cacheKey = c.var.cacheKey;
|
|
11451
|
+
const shortId2 = attachmentId.slice(0, 8);
|
|
11452
|
+
const shortSession = (c.req.query("session_key") ?? "").slice(0, 8);
|
|
11214
11453
|
const pinnedAccountId = getAccountIdForSession(cacheKey);
|
|
11215
11454
|
if (!pinnedAccountId) {
|
|
11455
|
+
console.log(`[admin-attachment] op=miss attachmentId=${shortId2} session=${shortSession} status=401 reason=no-account`);
|
|
11216
11456
|
return new Response("Unauthorized", { status: 401 });
|
|
11217
11457
|
}
|
|
11218
11458
|
if (!ATTACHMENT_ID_RE2.test(attachmentId)) {
|
|
11459
|
+
console.log(`[admin-attachment] op=miss attachmentId=${shortId2} session=${shortSession} status=404 reason=bad-id`);
|
|
11219
11460
|
return new Response("Not found", { status: 404 });
|
|
11220
11461
|
}
|
|
11221
11462
|
let accountId = pinnedAccountId;
|
|
@@ -11233,10 +11474,9 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
11233
11474
|
}
|
|
11234
11475
|
}
|
|
11235
11476
|
}
|
|
11236
|
-
const shortId2 = attachmentId.slice(0, 8);
|
|
11237
11477
|
const shortPinned = pinnedAccountId.slice(0, 8);
|
|
11238
11478
|
const crossAccountNote = found && accountId !== pinnedAccountId ? ` resolvedAccount=${accountId.slice(0, 8)}` : "";
|
|
11239
|
-
console.log(`[admin-attachment] op=lookup attachmentId=${shortId2} pinnedAccount=${shortPinned} found=${found}${crossAccountNote}`);
|
|
11479
|
+
console.log(`[admin-attachment] op=lookup attachmentId=${shortId2} session=${shortSession} pinnedAccount=${shortPinned} found=${found}${crossAccountNote}`);
|
|
11240
11480
|
if (!found) {
|
|
11241
11481
|
return new Response("Not found", { status: 404 });
|
|
11242
11482
|
}
|
|
@@ -12366,7 +12606,7 @@ function resolveTunnelUrl() {
|
|
|
12366
12606
|
if (!state) return null;
|
|
12367
12607
|
return `https://${hostname2}.${state.domain}`;
|
|
12368
12608
|
}
|
|
12369
|
-
var
|
|
12609
|
+
var TAG25 = "[claude-session-manager:wrapper]";
|
|
12370
12610
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
12371
12611
|
const auth = await ensureAuth();
|
|
12372
12612
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -12384,12 +12624,12 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
12384
12624
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
12385
12625
|
const ownerMs = Date.now() - ownerStart;
|
|
12386
12626
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
12387
|
-
console.log(`${
|
|
12627
|
+
console.log(`${TAG25} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
12388
12628
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
12389
12629
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
12390
12630
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
12391
12631
|
const tunnelUrl = resolveTunnelUrl();
|
|
12392
|
-
console.log(`${
|
|
12632
|
+
console.log(`${TAG25} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
12393
12633
|
const upstreamPayload = JSON.stringify({
|
|
12394
12634
|
senderId: args.senderId,
|
|
12395
12635
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -12416,24 +12656,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
12416
12656
|
// unshapely values.
|
|
12417
12657
|
conversationNodeId: args.conversationNodeId
|
|
12418
12658
|
});
|
|
12419
|
-
console.log(`${
|
|
12659
|
+
console.log(`${TAG25} forward-spawn-start managerBase=${managerBase("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
|
|
12420
12660
|
const forwardStart = Date.now();
|
|
12421
12661
|
const upstream = await fetch(`${managerBase("claude-session-manager:wrapper")}/public-spawn`, {
|
|
12422
12662
|
method: "POST",
|
|
12423
12663
|
headers: { "content-type": "application/json" },
|
|
12424
12664
|
body: upstreamPayload
|
|
12425
12665
|
}).catch((err) => {
|
|
12426
|
-
console.error(`${
|
|
12666
|
+
console.error(`${TAG25} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
12427
12667
|
return null;
|
|
12428
12668
|
});
|
|
12429
12669
|
if (!upstream) return {
|
|
12430
12670
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
12431
12671
|
claudeSessionId: null
|
|
12432
12672
|
};
|
|
12433
|
-
console.log(`${
|
|
12673
|
+
console.log(`${TAG25} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
12434
12674
|
if (args.initialMessage) {
|
|
12435
12675
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
12436
|
-
console.log(`${
|
|
12676
|
+
console.log(`${TAG25} initial-message-inlined bytes=${inputBytes}`);
|
|
12437
12677
|
}
|
|
12438
12678
|
const bodyText = await upstream.text().catch(() => "");
|
|
12439
12679
|
let claudeSessionId = null;
|
|
@@ -12468,7 +12708,7 @@ app18.post("/", async (c) => {
|
|
|
12468
12708
|
if (refusal) return refusal;
|
|
12469
12709
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
12470
12710
|
if (!senderId) {
|
|
12471
|
-
console.error(`${
|
|
12711
|
+
console.error(`${TAG25} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
12472
12712
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
12473
12713
|
}
|
|
12474
12714
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -12477,7 +12717,7 @@ app18.post("/", async (c) => {
|
|
|
12477
12717
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
12478
12718
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
12479
12719
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
12480
|
-
console.log(`${
|
|
12720
|
+
console.log(`${TAG25} spawn-request-in surface=cookie accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
|
|
12481
12721
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
12482
12722
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
12483
12723
|
senderId,
|
|
@@ -12496,33 +12736,33 @@ app18.post("/", async (c) => {
|
|
|
12496
12736
|
claudeSessionId,
|
|
12497
12737
|
senderId
|
|
12498
12738
|
);
|
|
12499
|
-
console.log(`${
|
|
12739
|
+
console.log(`${TAG25} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
12500
12740
|
return response;
|
|
12501
12741
|
});
|
|
12502
12742
|
var claude_sessions_default = app18;
|
|
12503
12743
|
|
|
12504
12744
|
// server/routes/admin/log-ingest.ts
|
|
12505
|
-
var
|
|
12745
|
+
var TAG26 = "[log-ingest]";
|
|
12506
12746
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
12507
12747
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
12508
12748
|
var MAX_LINE_BYTES = 4096;
|
|
12509
12749
|
var app19 = new Hono();
|
|
12510
|
-
function
|
|
12750
|
+
function isLoopbackAddr2(addr) {
|
|
12511
12751
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
12512
12752
|
}
|
|
12513
12753
|
app19.post("/", async (c) => {
|
|
12514
12754
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
12515
|
-
if (!
|
|
12516
|
-
console.error(`${
|
|
12755
|
+
if (!isLoopbackAddr2(remoteAddr)) {
|
|
12756
|
+
console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
12517
12757
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
12518
12758
|
}
|
|
12519
12759
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
12520
12760
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
12521
12761
|
if (xffRaw.length > 0) {
|
|
12522
12762
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
12523
|
-
const offender = tokens.find((t) => !
|
|
12763
|
+
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
12524
12764
|
if (offender !== void 0) {
|
|
12525
|
-
console.error(`${
|
|
12765
|
+
console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
12526
12766
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
12527
12767
|
}
|
|
12528
12768
|
}
|
|
@@ -12564,18 +12804,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
12564
12804
|
]);
|
|
12565
12805
|
var app20 = new Hono();
|
|
12566
12806
|
app20.post("/", async (c) => {
|
|
12567
|
-
const
|
|
12807
|
+
const TAG39 = "[admin:events]";
|
|
12568
12808
|
let body;
|
|
12569
12809
|
try {
|
|
12570
12810
|
body = await c.req.json();
|
|
12571
12811
|
} catch (err) {
|
|
12572
12812
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12573
|
-
console.error(`${
|
|
12813
|
+
console.error(`${TAG39} reject reason=body-not-json detail=${detail}`);
|
|
12574
12814
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
12575
12815
|
}
|
|
12576
12816
|
const event = typeof body.event === "string" ? body.event : "";
|
|
12577
12817
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
12578
|
-
console.error(`${
|
|
12818
|
+
console.error(`${TAG39} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
12579
12819
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
12580
12820
|
}
|
|
12581
12821
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -17390,7 +17630,7 @@ function managerLogFollowUrl(sessionId, opts) {
|
|
|
17390
17630
|
}
|
|
17391
17631
|
|
|
17392
17632
|
// server/routes/admin/linkedin-ingest.ts
|
|
17393
|
-
var
|
|
17633
|
+
var TAG27 = "[linkedin-ingest-route]";
|
|
17394
17634
|
var UUID2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
17395
17635
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
17396
17636
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -17494,29 +17734,29 @@ app37.post("/", requireAdminSession, async (c) => {
|
|
|
17494
17734
|
try {
|
|
17495
17735
|
body = await c.req.json();
|
|
17496
17736
|
} catch {
|
|
17497
|
-
console.error(
|
|
17737
|
+
console.error(TAG27 + " rejected status=400 reason=schema:body-not-json");
|
|
17498
17738
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
17499
17739
|
}
|
|
17500
17740
|
const v = validate(body);
|
|
17501
17741
|
if (!v.ok) {
|
|
17502
|
-
console.error(
|
|
17742
|
+
console.error(TAG27 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
17503
17743
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
17504
17744
|
}
|
|
17505
17745
|
const envelope = v.envelope;
|
|
17506
17746
|
const cacheKey = c.var.cacheKey ?? "";
|
|
17507
17747
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
17508
17748
|
if (!senderId) {
|
|
17509
|
-
console.error(
|
|
17749
|
+
console.error(TAG27 + " rejected status=500 reason=admin-account-not-resolved");
|
|
17510
17750
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
17511
17751
|
}
|
|
17512
17752
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
17513
17753
|
console.log(
|
|
17514
|
-
|
|
17754
|
+
TAG27 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
17515
17755
|
);
|
|
17516
17756
|
const initialMessage = buildInitialMessage(envelope);
|
|
17517
17757
|
const spawnStart = Date.now();
|
|
17518
17758
|
const sessionId = randomUUID12();
|
|
17519
|
-
console.log(
|
|
17759
|
+
console.log(TAG27 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
17520
17760
|
const spawned = await managerRcSpawn({
|
|
17521
17761
|
sessionId,
|
|
17522
17762
|
initialMessage,
|
|
@@ -17525,12 +17765,12 @@ app37.post("/", requireAdminSession, async (c) => {
|
|
|
17525
17765
|
});
|
|
17526
17766
|
if ("error" in spawned) {
|
|
17527
17767
|
console.error(
|
|
17528
|
-
|
|
17768
|
+
TAG27 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
17529
17769
|
);
|
|
17530
17770
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
17531
17771
|
}
|
|
17532
17772
|
console.log(
|
|
17533
|
-
|
|
17773
|
+
TAG27 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
17534
17774
|
);
|
|
17535
17775
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
17536
17776
|
});
|
|
@@ -17538,7 +17778,7 @@ var linkedin_ingest_default = app37;
|
|
|
17538
17778
|
|
|
17539
17779
|
// server/routes/admin/post-turn-context.ts
|
|
17540
17780
|
import neo4j3 from "neo4j-driver";
|
|
17541
|
-
var
|
|
17781
|
+
var TAG28 = "[post-turn-context]";
|
|
17542
17782
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
17543
17783
|
"embedding",
|
|
17544
17784
|
"passwordHash",
|
|
@@ -17546,7 +17786,7 @@ var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
|
17546
17786
|
"otpCode",
|
|
17547
17787
|
"cacheKey"
|
|
17548
17788
|
]);
|
|
17549
|
-
function
|
|
17789
|
+
function isLoopbackAddr3(addr) {
|
|
17550
17790
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17551
17791
|
}
|
|
17552
17792
|
function convertValue(value) {
|
|
@@ -17579,17 +17819,17 @@ function pruneProperties(raw) {
|
|
|
17579
17819
|
var app38 = new Hono();
|
|
17580
17820
|
app38.get("/", async (c) => {
|
|
17581
17821
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17582
|
-
if (!
|
|
17583
|
-
console.error(`${
|
|
17822
|
+
if (!isLoopbackAddr3(remoteAddr)) {
|
|
17823
|
+
console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17584
17824
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
17585
17825
|
}
|
|
17586
17826
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17587
17827
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17588
17828
|
if (xffRaw.length > 0) {
|
|
17589
17829
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17590
|
-
const offender = tokens.find((t) => !
|
|
17830
|
+
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
17591
17831
|
if (offender !== void 0) {
|
|
17592
|
-
console.error(`${
|
|
17832
|
+
console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17593
17833
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
17594
17834
|
}
|
|
17595
17835
|
}
|
|
@@ -17621,7 +17861,7 @@ app38.get("/", async (c) => {
|
|
|
17621
17861
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
17622
17862
|
const total = Date.now() - started;
|
|
17623
17863
|
console.log(
|
|
17624
|
-
`${
|
|
17864
|
+
`${TAG28} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
17625
17865
|
);
|
|
17626
17866
|
return c.json({
|
|
17627
17867
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -17630,7 +17870,7 @@ app38.get("/", async (c) => {
|
|
|
17630
17870
|
const elapsed = Date.now() - started;
|
|
17631
17871
|
const message = err instanceof Error ? err.message : String(err);
|
|
17632
17872
|
console.error(
|
|
17633
|
-
`${
|
|
17873
|
+
`${TAG28} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
17634
17874
|
);
|
|
17635
17875
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
17636
17876
|
} finally {
|
|
@@ -17743,24 +17983,24 @@ function formatPreviousContext(writes) {
|
|
|
17743
17983
|
}
|
|
17744
17984
|
|
|
17745
17985
|
// server/routes/admin/public-session-context.ts
|
|
17746
|
-
var
|
|
17747
|
-
function
|
|
17986
|
+
var TAG29 = "[public-session-context]";
|
|
17987
|
+
function isLoopbackAddr4(addr) {
|
|
17748
17988
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17749
17989
|
}
|
|
17750
17990
|
var app39 = new Hono();
|
|
17751
17991
|
app39.get("/", async (c) => {
|
|
17752
17992
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17753
|
-
if (!
|
|
17754
|
-
console.error(`${
|
|
17993
|
+
if (!isLoopbackAddr4(remoteAddr)) {
|
|
17994
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17755
17995
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
17756
17996
|
}
|
|
17757
17997
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17758
17998
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17759
17999
|
if (xffRaw.length > 0) {
|
|
17760
18000
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17761
|
-
const offender = tokens.find((t) => !
|
|
18001
|
+
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
17762
18002
|
if (offender !== void 0) {
|
|
17763
|
-
console.error(`${
|
|
18003
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17764
18004
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
17765
18005
|
}
|
|
17766
18006
|
}
|
|
@@ -17774,14 +18014,14 @@ app39.get("/", async (c) => {
|
|
|
17774
18014
|
const writes = await fetchSliceWrites(session, sliceToken, accountId);
|
|
17775
18015
|
const total = Date.now() - started;
|
|
17776
18016
|
console.log(
|
|
17777
|
-
`${
|
|
18017
|
+
`${TAG29} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
17778
18018
|
);
|
|
17779
18019
|
return c.json({ writes });
|
|
17780
18020
|
} catch (err) {
|
|
17781
18021
|
const elapsed = Date.now() - started;
|
|
17782
18022
|
const message = err instanceof Error ? err.message : String(err);
|
|
17783
18023
|
console.error(
|
|
17784
|
-
`${
|
|
18024
|
+
`${TAG29} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
17785
18025
|
);
|
|
17786
18026
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
17787
18027
|
} finally {
|
|
@@ -17800,24 +18040,24 @@ function getWebchatGateway() {
|
|
|
17800
18040
|
}
|
|
17801
18041
|
|
|
17802
18042
|
// server/routes/admin/public-session-exit.ts
|
|
17803
|
-
var
|
|
17804
|
-
function
|
|
18043
|
+
var TAG30 = "[public-session-exit-route]";
|
|
18044
|
+
function isLoopbackAddr5(addr) {
|
|
17805
18045
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17806
18046
|
}
|
|
17807
18047
|
var app40 = new Hono();
|
|
17808
18048
|
app40.post("/", async (c) => {
|
|
17809
18049
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17810
|
-
if (!
|
|
17811
|
-
console.error(`${
|
|
18050
|
+
if (!isLoopbackAddr5(remoteAddr)) {
|
|
18051
|
+
console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17812
18052
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17813
18053
|
}
|
|
17814
18054
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17815
18055
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17816
18056
|
if (xffRaw.length > 0) {
|
|
17817
18057
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17818
|
-
const offender = tokens.find((t) => !
|
|
18058
|
+
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
17819
18059
|
if (offender !== void 0) {
|
|
17820
|
-
console.error(`${
|
|
18060
|
+
console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17821
18061
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17822
18062
|
}
|
|
17823
18063
|
}
|
|
@@ -17831,7 +18071,7 @@ app40.post("/", async (c) => {
|
|
|
17831
18071
|
if (!sessionId) return c.json({ error: "sessionId required" }, 400);
|
|
17832
18072
|
const gateway = getWebchatGateway();
|
|
17833
18073
|
if (!gateway) {
|
|
17834
|
-
console.error(`${
|
|
18074
|
+
console.error(`${TAG30} reject reason=gateway-unset sessionId=${sessionId.slice(0, 8)}`);
|
|
17835
18075
|
return c.json({ error: "webchat-gateway-unset" }, 503);
|
|
17836
18076
|
}
|
|
17837
18077
|
gateway.handlePublicSessionExit(sessionId);
|
|
@@ -17840,24 +18080,24 @@ app40.post("/", async (c) => {
|
|
|
17840
18080
|
var public_session_exit_default = app40;
|
|
17841
18081
|
|
|
17842
18082
|
// server/routes/admin/access-session-evict.ts
|
|
17843
|
-
var
|
|
17844
|
-
function
|
|
18083
|
+
var TAG31 = "[access-session-evict]";
|
|
18084
|
+
function isLoopbackAddr6(addr) {
|
|
17845
18085
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17846
18086
|
}
|
|
17847
18087
|
var app41 = new Hono();
|
|
17848
18088
|
app41.post("/", async (c) => {
|
|
17849
18089
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17850
|
-
if (!
|
|
17851
|
-
console.error(`${
|
|
18090
|
+
if (!isLoopbackAddr6(remoteAddr)) {
|
|
18091
|
+
console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17852
18092
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17853
18093
|
}
|
|
17854
18094
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17855
18095
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17856
18096
|
if (xffRaw.length > 0) {
|
|
17857
18097
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17858
|
-
const offender = tokens.find((t) => !
|
|
18098
|
+
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
17859
18099
|
if (offender !== void 0) {
|
|
17860
|
-
console.error(`${
|
|
18100
|
+
console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17861
18101
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17862
18102
|
}
|
|
17863
18103
|
}
|
|
@@ -17870,31 +18110,31 @@ app41.post("/", async (c) => {
|
|
|
17870
18110
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
17871
18111
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
17872
18112
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
17873
|
-
console.log(`${
|
|
18113
|
+
console.log(`${TAG31} grantId=${grantId} dropped=${dropped}`);
|
|
17874
18114
|
return c.json({ ok: true, dropped });
|
|
17875
18115
|
});
|
|
17876
18116
|
var access_session_evict_default = app41;
|
|
17877
18117
|
|
|
17878
18118
|
// server/routes/admin/enrol-person.ts
|
|
17879
|
-
var
|
|
17880
|
-
function
|
|
18119
|
+
var TAG32 = "[enrol]";
|
|
18120
|
+
function isLoopbackAddr7(addr) {
|
|
17881
18121
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17882
18122
|
}
|
|
17883
18123
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17884
18124
|
var app42 = new Hono();
|
|
17885
18125
|
app42.post("/", async (c) => {
|
|
17886
18126
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17887
|
-
if (!
|
|
17888
|
-
console.error(`${
|
|
18127
|
+
if (!isLoopbackAddr7(remoteAddr)) {
|
|
18128
|
+
console.error(`${TAG32} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17889
18129
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17890
18130
|
}
|
|
17891
18131
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17892
18132
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17893
18133
|
if (xffRaw.length > 0) {
|
|
17894
18134
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17895
|
-
const offender = tokens.find((t) => !
|
|
18135
|
+
const offender = tokens.find((t) => !isLoopbackAddr7(t));
|
|
17896
18136
|
if (offender !== void 0) {
|
|
17897
|
-
console.error(`${
|
|
18137
|
+
console.error(`${TAG32} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17898
18138
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17899
18139
|
}
|
|
17900
18140
|
}
|
|
@@ -17919,7 +18159,7 @@ app42.post("/", async (c) => {
|
|
|
17919
18159
|
}
|
|
17920
18160
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
17921
18161
|
if (!accountId) {
|
|
17922
|
-
console.error(`${
|
|
18162
|
+
console.error(`${TAG32} op=person result=no-account-id`);
|
|
17923
18163
|
return c.json({ error: "no-account-id" }, 500);
|
|
17924
18164
|
}
|
|
17925
18165
|
let personId;
|
|
@@ -17927,7 +18167,7 @@ app42.post("/", async (c) => {
|
|
|
17927
18167
|
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
17928
18168
|
} catch (err) {
|
|
17929
18169
|
console.error(
|
|
17930
|
-
`${
|
|
18170
|
+
`${TAG32} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17931
18171
|
);
|
|
17932
18172
|
return c.json({ error: "enrol-failed" }, 500);
|
|
17933
18173
|
}
|
|
@@ -17937,11 +18177,11 @@ app42.post("/", async (c) => {
|
|
|
17937
18177
|
if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
|
|
17938
18178
|
} catch (err) {
|
|
17939
18179
|
console.error(
|
|
17940
|
-
`${
|
|
18180
|
+
`${TAG32} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17941
18181
|
);
|
|
17942
18182
|
}
|
|
17943
18183
|
console.log(
|
|
17944
|
-
`${
|
|
18184
|
+
`${TAG32} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
|
|
17945
18185
|
);
|
|
17946
18186
|
return c.json({ personId, grant }, 200);
|
|
17947
18187
|
});
|
|
@@ -18510,7 +18750,7 @@ app45.route("/calendar", calendar_default);
|
|
|
18510
18750
|
var admin_default = app45;
|
|
18511
18751
|
|
|
18512
18752
|
// server/routes/access/verify-token.ts
|
|
18513
|
-
var
|
|
18753
|
+
var TAG33 = "[access-verify]";
|
|
18514
18754
|
var MINT_TAG = "[access-session-mint]";
|
|
18515
18755
|
var COOKIE_NAME = "__access_session";
|
|
18516
18756
|
var app46 = new Hono();
|
|
@@ -18529,39 +18769,39 @@ app46.post("/", async (c) => {
|
|
|
18529
18769
|
}
|
|
18530
18770
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
18531
18771
|
if (rateMsg) {
|
|
18532
|
-
console.error(`${
|
|
18772
|
+
console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
18533
18773
|
return c.json({ error: rateMsg }, 429);
|
|
18534
18774
|
}
|
|
18535
18775
|
const grant = await findGrantByMagicToken(token);
|
|
18536
18776
|
if (!grant) {
|
|
18537
18777
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18538
|
-
console.error(`${
|
|
18778
|
+
console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
18539
18779
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
18540
18780
|
}
|
|
18541
18781
|
if (grant.agentSlug !== agentSlug) {
|
|
18542
18782
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18543
18783
|
console.error(
|
|
18544
|
-
`${
|
|
18784
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
18545
18785
|
);
|
|
18546
18786
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
18547
18787
|
}
|
|
18548
18788
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
18549
18789
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18550
18790
|
console.error(
|
|
18551
|
-
`${
|
|
18791
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
18552
18792
|
);
|
|
18553
18793
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
18554
18794
|
}
|
|
18555
18795
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
18556
18796
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18557
18797
|
console.error(
|
|
18558
|
-
`${
|
|
18798
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
18559
18799
|
);
|
|
18560
18800
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
18561
18801
|
}
|
|
18562
18802
|
if (!grant.sliceToken) {
|
|
18563
18803
|
console.error(
|
|
18564
|
-
`${
|
|
18804
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
18565
18805
|
);
|
|
18566
18806
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
18567
18807
|
}
|
|
@@ -18577,12 +18817,12 @@ app46.post("/", async (c) => {
|
|
|
18577
18817
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
18578
18818
|
} catch (err) {
|
|
18579
18819
|
console.error(
|
|
18580
|
-
`${
|
|
18820
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
18581
18821
|
);
|
|
18582
18822
|
return c.json({ error: "verification-failed" }, 500);
|
|
18583
18823
|
}
|
|
18584
18824
|
clearAccessRateLimit(ip, agentSlug);
|
|
18585
|
-
console.log(`${
|
|
18825
|
+
console.log(`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
18586
18826
|
console.log(
|
|
18587
18827
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
18588
18828
|
);
|
|
@@ -18658,7 +18898,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
18658
18898
|
}
|
|
18659
18899
|
|
|
18660
18900
|
// server/routes/access/request-magic-link.ts
|
|
18661
|
-
var
|
|
18901
|
+
var TAG34 = "[access-request-link]";
|
|
18662
18902
|
var app47 = new Hono();
|
|
18663
18903
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
18664
18904
|
app47.post("/", async (c) => {
|
|
@@ -18675,18 +18915,18 @@ app47.post("/", async (c) => {
|
|
|
18675
18915
|
}
|
|
18676
18916
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
18677
18917
|
if (rateMsg) {
|
|
18678
|
-
console.error(`${
|
|
18918
|
+
console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
18679
18919
|
return c.json({ error: rateMsg }, 429);
|
|
18680
18920
|
}
|
|
18681
18921
|
recordRequestLinkAttempt(contactValue);
|
|
18682
18922
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
18683
18923
|
if (!accountId) {
|
|
18684
|
-
console.error(`${
|
|
18924
|
+
console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
18685
18925
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18686
18926
|
}
|
|
18687
18927
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
18688
18928
|
if (!grant) {
|
|
18689
|
-
console.log(`${
|
|
18929
|
+
console.log(`${TAG34} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
18690
18930
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18691
18931
|
}
|
|
18692
18932
|
let token;
|
|
@@ -18694,7 +18934,7 @@ app47.post("/", async (c) => {
|
|
|
18694
18934
|
token = await generateNewMagicToken(grant.grantId);
|
|
18695
18935
|
} catch (err) {
|
|
18696
18936
|
console.error(
|
|
18697
|
-
`${
|
|
18937
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
18698
18938
|
);
|
|
18699
18939
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18700
18940
|
}
|
|
@@ -18726,12 +18966,12 @@ app47.post("/", async (c) => {
|
|
|
18726
18966
|
});
|
|
18727
18967
|
if (!sendResult.ok) {
|
|
18728
18968
|
console.error(
|
|
18729
|
-
`${
|
|
18969
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
18730
18970
|
);
|
|
18731
18971
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18732
18972
|
}
|
|
18733
18973
|
console.log(
|
|
18734
|
-
`${
|
|
18974
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
18735
18975
|
);
|
|
18736
18976
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18737
18977
|
});
|
|
@@ -20749,6 +20989,7 @@ var WaGateway = class {
|
|
|
20749
20989
|
const willDeliverNow = this.hub.isReady(input.senderId);
|
|
20750
20990
|
const openable = input.media.filter((m) => m.type !== "audio");
|
|
20751
20991
|
const mediaField = openable.length > 0 ? `media=${openable.map((m) => m.type).join(",")} mediaPaths=${openable.map((m) => m.path).join(",")}` : input.media.some((m) => m.type === "audio") ? "media=audio mediaPath=transcribed" : "media=none";
|
|
20992
|
+
const source = input.source ?? "user";
|
|
20752
20993
|
this.replies.set(input.senderId, input.reply);
|
|
20753
20994
|
this.docContexts.set(input.senderId, { accountId: input.accountId });
|
|
20754
20995
|
this.hub.deliver(
|
|
@@ -20756,12 +20997,13 @@ var WaGateway = class {
|
|
|
20756
20997
|
senderId: input.senderId,
|
|
20757
20998
|
text: input.text,
|
|
20758
20999
|
waMessageId: `wa-${++this.seq}`,
|
|
20759
|
-
media: input.media
|
|
21000
|
+
media: input.media,
|
|
21001
|
+
source
|
|
20760
21002
|
},
|
|
20761
21003
|
Date.now()
|
|
20762
21004
|
);
|
|
20763
21005
|
console.error(
|
|
20764
|
-
`[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} ${mediaField} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
21006
|
+
`[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} ${mediaField} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
20765
21007
|
);
|
|
20766
21008
|
if (!hadSubscriber && !this.spawning.has(input.senderId)) {
|
|
20767
21009
|
this.spawning.add(input.senderId);
|
|
@@ -21698,7 +21940,7 @@ function makeFileDelivery(opts) {
|
|
|
21698
21940
|
}
|
|
21699
21941
|
|
|
21700
21942
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
21701
|
-
var
|
|
21943
|
+
var TAG35 = "[whatsapp-adaptor]";
|
|
21702
21944
|
var SEND_USER_FILE2 = "SendUserFile";
|
|
21703
21945
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
21704
21946
|
function platformRoot() {
|
|
@@ -21711,7 +21953,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21711
21953
|
maxyAccountId = resolvePlatformAccountId();
|
|
21712
21954
|
} catch (err) {
|
|
21713
21955
|
console.error(
|
|
21714
|
-
`${
|
|
21956
|
+
`${TAG35} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
21715
21957
|
);
|
|
21716
21958
|
return { ok: false, error: "account-unresolved" };
|
|
21717
21959
|
}
|
|
@@ -21725,7 +21967,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21725
21967
|
});
|
|
21726
21968
|
if (result.ok) return { ok: true };
|
|
21727
21969
|
console.error(
|
|
21728
|
-
`${
|
|
21970
|
+
`${TAG35} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
21729
21971
|
);
|
|
21730
21972
|
return { ok: false, error: result.error };
|
|
21731
21973
|
};
|
|
@@ -21733,7 +21975,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21733
21975
|
function makeWhatsAppFileDelivery(entry) {
|
|
21734
21976
|
const shared = makeFileDelivery({
|
|
21735
21977
|
entry,
|
|
21736
|
-
tag:
|
|
21978
|
+
tag: TAG35,
|
|
21737
21979
|
channel: "whatsapp",
|
|
21738
21980
|
sendFile: makeWhatsAppSendFile(entry)
|
|
21739
21981
|
});
|
|
@@ -21768,7 +22010,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
21768
22010
|
if (!delivered) {
|
|
21769
22011
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
21770
22012
|
console.error(
|
|
21771
|
-
`${
|
|
22013
|
+
`${TAG35} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
21772
22014
|
);
|
|
21773
22015
|
}
|
|
21774
22016
|
}
|
|
@@ -22077,13 +22319,14 @@ var TelegramGateway = class {
|
|
|
22077
22319
|
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
22078
22320
|
const hadSubscriber = this.hub.hasSubscriber(input.senderId);
|
|
22079
22321
|
const willDeliverNow = this.hub.isReady(input.senderId);
|
|
22322
|
+
const source = input.source ?? "user";
|
|
22080
22323
|
this.replies.set(input.senderId, input.reply);
|
|
22081
22324
|
this.hub.deliver(
|
|
22082
|
-
{ key: input.senderId, text: input.text, messageId: `tg-${++this.seq}
|
|
22325
|
+
{ key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source },
|
|
22083
22326
|
Date.now()
|
|
22084
22327
|
);
|
|
22085
22328
|
console.error(
|
|
22086
|
-
`[telegram-native] op=inbound key=${input.senderId} accountId=${input.accountId} botType=${input.role} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
22329
|
+
`[telegram-native] op=inbound key=${input.senderId} accountId=${input.accountId} botType=${input.role} bytes=${bytes} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
22087
22330
|
);
|
|
22088
22331
|
if (!hadSubscriber && !this.spawning.has(input.senderId)) {
|
|
22089
22332
|
this.spawning.add(input.senderId);
|
|
@@ -22133,7 +22376,7 @@ function buildTelegramSpawnRequest(input) {
|
|
|
22133
22376
|
import { realpathSync as realpathSync7 } from "fs";
|
|
22134
22377
|
import { readFile as readFile7, stat as stat7 } from "fs/promises";
|
|
22135
22378
|
import { resolve as resolve30, basename as basename11 } from "path";
|
|
22136
|
-
var
|
|
22379
|
+
var TAG36 = "[telegram:outbound]";
|
|
22137
22380
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
22138
22381
|
async function sendTelegramDocument(input) {
|
|
22139
22382
|
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot3 } = input;
|
|
@@ -22149,16 +22392,16 @@ async function sendTelegramDocument(input) {
|
|
|
22149
22392
|
resolvedPath = realpathSync7(filePath);
|
|
22150
22393
|
const accountResolved = realpathSync7(accountDir);
|
|
22151
22394
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
22152
|
-
console.error(`${
|
|
22395
|
+
console.error(`${TAG36} document REJECTED reason=outside_account_directory`);
|
|
22153
22396
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
22154
22397
|
}
|
|
22155
22398
|
} catch (err) {
|
|
22156
22399
|
const code = err.code;
|
|
22157
22400
|
if (code === "ENOENT") {
|
|
22158
|
-
console.error(`${
|
|
22401
|
+
console.error(`${TAG36} document ENOENT path=${filePath}`);
|
|
22159
22402
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
22160
22403
|
}
|
|
22161
|
-
console.error(`${
|
|
22404
|
+
console.error(`${TAG36} document path error: ${String(err)}`);
|
|
22162
22405
|
return { ok: false, status: 500, error: String(err) };
|
|
22163
22406
|
}
|
|
22164
22407
|
const fileStat = await stat7(resolvedPath);
|
|
@@ -22191,13 +22434,13 @@ async function sendTelegramDocument(input) {
|
|
|
22191
22434
|
error = e instanceof Error ? e.message : String(e);
|
|
22192
22435
|
}
|
|
22193
22436
|
console.error(
|
|
22194
|
-
`${
|
|
22437
|
+
`${TAG36} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
22195
22438
|
);
|
|
22196
22439
|
return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
22197
22440
|
}
|
|
22198
22441
|
|
|
22199
22442
|
// app/lib/telegram/outbound/file-delivery.ts
|
|
22200
|
-
var
|
|
22443
|
+
var TAG37 = "[telegram:outbound]";
|
|
22201
22444
|
function platformRoot2() {
|
|
22202
22445
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
22203
22446
|
}
|
|
@@ -22209,11 +22452,11 @@ function makeTelegramSendFile(entry) {
|
|
|
22209
22452
|
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
22210
22453
|
}
|
|
22211
22454
|
if (!botToken) {
|
|
22212
|
-
console.error(`${
|
|
22455
|
+
console.error(`${TAG37} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
22213
22456
|
return { ok: false, error: "no-bot-token" };
|
|
22214
22457
|
}
|
|
22215
22458
|
if (entry.replyTarget == null) {
|
|
22216
|
-
console.error(`${
|
|
22459
|
+
console.error(`${TAG37} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
22217
22460
|
return { ok: false, error: "no-reply-target" };
|
|
22218
22461
|
}
|
|
22219
22462
|
let maxyAccountId;
|
|
@@ -22221,7 +22464,7 @@ function makeTelegramSendFile(entry) {
|
|
|
22221
22464
|
maxyAccountId = resolvePlatformAccountId();
|
|
22222
22465
|
} catch (err) {
|
|
22223
22466
|
console.error(
|
|
22224
|
-
`${
|
|
22467
|
+
`${TAG37} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
22225
22468
|
);
|
|
22226
22469
|
return { ok: false, error: "account-unresolved" };
|
|
22227
22470
|
}
|
|
@@ -22239,7 +22482,7 @@ function makeTelegramSendFile(entry) {
|
|
|
22239
22482
|
function makeTelegramFileDelivery(entry) {
|
|
22240
22483
|
return makeFileDelivery({
|
|
22241
22484
|
entry,
|
|
22242
|
-
tag:
|
|
22485
|
+
tag: TAG37,
|
|
22243
22486
|
channel: "telegram",
|
|
22244
22487
|
sendFile: makeTelegramSendFile(entry)
|
|
22245
22488
|
});
|
|
@@ -22289,7 +22532,7 @@ function telegramServerPath() {
|
|
|
22289
22532
|
|
|
22290
22533
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
22291
22534
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
22292
|
-
var
|
|
22535
|
+
var TAG38 = "[public-session-review]";
|
|
22293
22536
|
var CONSUMED_CAP = 500;
|
|
22294
22537
|
var consumed = /* @__PURE__ */ new Set();
|
|
22295
22538
|
function consumeOnce(sessionId) {
|
|
@@ -22316,7 +22559,7 @@ async function fetchJsonl(sessionId) {
|
|
|
22316
22559
|
return await res.text();
|
|
22317
22560
|
} catch (err) {
|
|
22318
22561
|
console.error(
|
|
22319
|
-
`${
|
|
22562
|
+
`${TAG38} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
22320
22563
|
);
|
|
22321
22564
|
return null;
|
|
22322
22565
|
}
|
|
@@ -22340,7 +22583,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
22340
22583
|
const res = await fetch(url);
|
|
22341
22584
|
if (!res.ok) {
|
|
22342
22585
|
console.error(
|
|
22343
|
-
`${
|
|
22586
|
+
`${TAG38} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
22344
22587
|
);
|
|
22345
22588
|
return [];
|
|
22346
22589
|
}
|
|
@@ -22348,7 +22591,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
22348
22591
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
22349
22592
|
} catch (err) {
|
|
22350
22593
|
console.error(
|
|
22351
|
-
`${
|
|
22594
|
+
`${TAG38} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
22352
22595
|
);
|
|
22353
22596
|
return [];
|
|
22354
22597
|
}
|
|
@@ -22377,7 +22620,7 @@ function composeInitialMessage(input) {
|
|
|
22377
22620
|
}
|
|
22378
22621
|
async function dispatchReviewer(input, initialMessage) {
|
|
22379
22622
|
const sessionId = randomUUID13();
|
|
22380
|
-
console.log(`${
|
|
22623
|
+
console.log(`${TAG38} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
22381
22624
|
const spawned = await managerRcSpawn({
|
|
22382
22625
|
sessionId,
|
|
22383
22626
|
initialMessage,
|
|
@@ -22397,21 +22640,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
22397
22640
|
const dispatchedAt = Date.now();
|
|
22398
22641
|
if (consumed.has(input.sessionId)) {
|
|
22399
22642
|
console.log(
|
|
22400
|
-
`${
|
|
22643
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
22401
22644
|
);
|
|
22402
22645
|
return;
|
|
22403
22646
|
}
|
|
22404
22647
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
22405
22648
|
if (!jsonl) {
|
|
22406
22649
|
console.log(
|
|
22407
|
-
`${
|
|
22650
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
22408
22651
|
);
|
|
22409
22652
|
return;
|
|
22410
22653
|
}
|
|
22411
22654
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
22412
22655
|
if (operatorTurns === 0) {
|
|
22413
22656
|
console.log(
|
|
22414
|
-
`${
|
|
22657
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
22415
22658
|
);
|
|
22416
22659
|
return;
|
|
22417
22660
|
}
|
|
@@ -22425,23 +22668,34 @@ async function firePublicSessionEndReview(input) {
|
|
|
22425
22668
|
});
|
|
22426
22669
|
if (!consumeOnce(input.sessionId)) {
|
|
22427
22670
|
console.log(
|
|
22428
|
-
`${
|
|
22671
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
22429
22672
|
);
|
|
22430
22673
|
return;
|
|
22431
22674
|
}
|
|
22432
22675
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
22433
22676
|
if (!dispatched.ok) {
|
|
22434
22677
|
console.error(
|
|
22435
|
-
`${
|
|
22678
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
22436
22679
|
);
|
|
22437
22680
|
return;
|
|
22438
22681
|
}
|
|
22439
22682
|
console.log(
|
|
22440
|
-
`${
|
|
22683
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
22441
22684
|
);
|
|
22442
22685
|
}
|
|
22443
22686
|
|
|
22444
22687
|
// app/lib/whatsapp/inbound/channel-admin-binding-drift.ts
|
|
22688
|
+
function findAccountManagerDrift(accountManagers, adminPhones, validAccountIds) {
|
|
22689
|
+
const drift = [];
|
|
22690
|
+
for (const [phone, managesAccount] of Object.entries(accountManagers)) {
|
|
22691
|
+
if (!validAccountIds.includes(managesAccount)) {
|
|
22692
|
+
drift.push({ phone, managesAccount, reason: "not-in-registry" });
|
|
22693
|
+
} else if (adminPhones.some((a) => phonesMatch(a, phone))) {
|
|
22694
|
+
drift.push({ phone, managesAccount, reason: "phone-also-admin" });
|
|
22695
|
+
}
|
|
22696
|
+
}
|
|
22697
|
+
return drift;
|
|
22698
|
+
}
|
|
22445
22699
|
function findChannelAdminBindingDrift(adminPhones, admins, users) {
|
|
22446
22700
|
const drift = [];
|
|
22447
22701
|
for (const phone of adminPhones) {
|
|
@@ -22464,6 +22718,7 @@ function warnOnChannelAdminBindingDrift() {
|
|
|
22464
22718
|
} catch {
|
|
22465
22719
|
return;
|
|
22466
22720
|
}
|
|
22721
|
+
const validAccountIds = accounts.map((a) => a.accountId);
|
|
22467
22722
|
for (const acct of accounts) {
|
|
22468
22723
|
const adminPhones = readAdminPhones(acct.accountDir);
|
|
22469
22724
|
const drift = findChannelAdminBindingDrift(adminPhones, acct.config.admins ?? [], users);
|
|
@@ -22473,6 +22728,12 @@ function warnOnChannelAdminBindingDrift() {
|
|
|
22473
22728
|
`[admin-identity] binding-drift accountId=${acct.accountId} phone=${d.phone} reason=${d.reason}${tail}`
|
|
22474
22729
|
);
|
|
22475
22730
|
}
|
|
22731
|
+
const managerDrift = findAccountManagerDrift(readAccountManagers(acct.accountDir), adminPhones, validAccountIds);
|
|
22732
|
+
for (const d of managerDrift) {
|
|
22733
|
+
console.error(
|
|
22734
|
+
`[admin-identity] account-manager-drift accountId=${acct.accountId} phone=${d.phone} managesAccount=${d.managesAccount} reason=${d.reason}`
|
|
22735
|
+
);
|
|
22736
|
+
}
|
|
22476
22737
|
}
|
|
22477
22738
|
}
|
|
22478
22739
|
|
|
@@ -22837,8 +23098,14 @@ var waGateway = new WaGateway({
|
|
|
22837
23098
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
22838
23099
|
},
|
|
22839
23100
|
ensureChannelSession: async ({ accountId, senderId, role, personId, gatewayUrl, serverPath }) => {
|
|
22840
|
-
const
|
|
22841
|
-
const
|
|
23101
|
+
const houseDir = role === "admin" ? listValidAccounts().find((a) => a.accountId === accountId)?.accountDir ?? null : null;
|
|
23102
|
+
const managed = houseDir ? managedAccountFor(readAccountManagers(houseDir), senderId) : null;
|
|
23103
|
+
const effectiveAccountId = managed ?? accountId;
|
|
23104
|
+
if (role === "admin") {
|
|
23105
|
+
console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managed ?? "none"} effectiveAccount=${effectiveAccountId}`);
|
|
23106
|
+
}
|
|
23107
|
+
const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
|
|
23108
|
+
const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? void 0 : void 0;
|
|
22842
23109
|
if (role === "admin") {
|
|
22843
23110
|
console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
|
|
22844
23111
|
}
|
|
@@ -22984,6 +23251,22 @@ var chatRoutes = createChatRoutes({
|
|
|
22984
23251
|
handleInbound: (input) => webchatGateway.handleInbound(input),
|
|
22985
23252
|
awaitReply: (key, timeoutMs) => webchatGateway.awaitReply(key, timeoutMs)
|
|
22986
23253
|
});
|
|
23254
|
+
var scheduleInjectRoutes = createScheduleInjectRoutes({
|
|
23255
|
+
resolveAccount: () => resolveAccount(),
|
|
23256
|
+
waHandleInbound: (input) => waGateway.handleInbound(input),
|
|
23257
|
+
tgHandleInbound: (input) => telegramGateway.handleInbound(input),
|
|
23258
|
+
sendWhatsAppText: async (accountId, destination, text) => {
|
|
23259
|
+
const sock = getSocket(accountId);
|
|
23260
|
+
if (!sock) throw new Error("WhatsApp disconnected \u2014 cannot deliver scheduled reply");
|
|
23261
|
+
await sendTextMessage(sock, toWhatsappJid(destination), text, { accountId });
|
|
23262
|
+
},
|
|
23263
|
+
sendTelegramText: async (botToken, chatId, text) => {
|
|
23264
|
+
const sent = await sendTelegramText(botToken, chatId, text);
|
|
23265
|
+
if (!sent.ok) throw new Error(sent.error ?? "telegram send failed");
|
|
23266
|
+
},
|
|
23267
|
+
effectiveAccountFor: (accountId, accountDir, destination) => managedAccountFor(readAccountManagers(accountDir), destination) ?? accountId
|
|
23268
|
+
});
|
|
23269
|
+
app55.route("/api/channel/schedule-inject", scheduleInjectRoutes);
|
|
22987
23270
|
app55.use("*", clientIpMiddleware);
|
|
22988
23271
|
function allowsSameOriginFraming(path2) {
|
|
22989
23272
|
return path2 === "/vnc-viewer.html" || path2.startsWith("/api/admin/attachment/") || path2.startsWith("/api/public-reader/attachment/") || path2 === "/api/admin/files/download";
|