@rubytech/create-maxy-code 0.1.386 → 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 +61 -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-oxqHKL3C.js → AdminLoginScreens-BejIjbmU.js} +1 -1
- package/payload/server/public/assets/{AdminShell-DBD6ruS1.js → AdminShell-D2-uBSB5.js} +1 -1
- package/payload/server/public/assets/{Checkbox-yr8-ftrE.js → Checkbox-1F1tzqca.js} +1 -1
- package/payload/server/public/assets/Transcript-DkGa4CQH.js +2 -0
- package/payload/server/public/assets/{admin-CQfZnuFy.js → admin-DqQARkjy.js} +1 -1
- package/payload/server/public/assets/{browser-DbdPeDK1.js → browser-nDtEK6RC.js} +1 -1
- package/payload/server/public/assets/{calendar-CFdOhe7Q.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-Bluocsf_.js → graph-DFyicKd7.js} +1 -1
- package/payload/server/public/assets/{graph-labels-BcLJmY-v.js → graph-labels-D3BQdcpD.js} +1 -1
- package/payload/server/public/assets/{operator-DjK3MBSS.js → operator-BxrjWdT9.js} +1 -1
- package/payload/server/public/assets/{page-DQNYTb2d.js → page-ByDLq_o1.js} +1 -1
- package/payload/server/public/assets/{page-zJQTRXB2.js → page-D-Ep4bXd.js} +1 -1
- package/payload/server/public/assets/{public-CHCniosp.js → public-DbdqfdYq.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-JDfwjfIL.js → rotate-ccw-BkX8HODe.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 +7 -7
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +10 -10
- package/payload/server/public/operator.html +10 -10
- package/payload/server/public/public.html +8 -8
- package/payload/server/server.js +551 -271
- package/payload/server/public/assets/Transcript-FX_w6no6.js +0 -2
- package/payload/server/public/assets/chat-BunxcNDk.js +0 -1
- package/payload/server/public/assets/chevron-left-Cngmu0Py.js +0 -1
- package/payload/server/public/assets/data-IqZlMSwG.js +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-BpQ3oKiJ.js → useSubAccountSwitcher-DMbRhLPv.js} +0 -0
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({
|
|
@@ -12369,7 +12606,7 @@ function resolveTunnelUrl() {
|
|
|
12369
12606
|
if (!state) return null;
|
|
12370
12607
|
return `https://${hostname2}.${state.domain}`;
|
|
12371
12608
|
}
|
|
12372
|
-
var
|
|
12609
|
+
var TAG25 = "[claude-session-manager:wrapper]";
|
|
12373
12610
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
12374
12611
|
const auth = await ensureAuth();
|
|
12375
12612
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -12387,12 +12624,12 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
12387
12624
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
12388
12625
|
const ownerMs = Date.now() - ownerStart;
|
|
12389
12626
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
12390
|
-
console.log(`${
|
|
12627
|
+
console.log(`${TAG25} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
12391
12628
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
12392
12629
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
12393
12630
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
12394
12631
|
const tunnelUrl = resolveTunnelUrl();
|
|
12395
|
-
console.log(`${
|
|
12632
|
+
console.log(`${TAG25} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
12396
12633
|
const upstreamPayload = JSON.stringify({
|
|
12397
12634
|
senderId: args.senderId,
|
|
12398
12635
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -12419,24 +12656,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
12419
12656
|
// unshapely values.
|
|
12420
12657
|
conversationNodeId: args.conversationNodeId
|
|
12421
12658
|
});
|
|
12422
|
-
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"}`);
|
|
12423
12660
|
const forwardStart = Date.now();
|
|
12424
12661
|
const upstream = await fetch(`${managerBase("claude-session-manager:wrapper")}/public-spawn`, {
|
|
12425
12662
|
method: "POST",
|
|
12426
12663
|
headers: { "content-type": "application/json" },
|
|
12427
12664
|
body: upstreamPayload
|
|
12428
12665
|
}).catch((err) => {
|
|
12429
|
-
console.error(`${
|
|
12666
|
+
console.error(`${TAG25} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
12430
12667
|
return null;
|
|
12431
12668
|
});
|
|
12432
12669
|
if (!upstream) return {
|
|
12433
12670
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
12434
12671
|
claudeSessionId: null
|
|
12435
12672
|
};
|
|
12436
|
-
console.log(`${
|
|
12673
|
+
console.log(`${TAG25} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
12437
12674
|
if (args.initialMessage) {
|
|
12438
12675
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
12439
|
-
console.log(`${
|
|
12676
|
+
console.log(`${TAG25} initial-message-inlined bytes=${inputBytes}`);
|
|
12440
12677
|
}
|
|
12441
12678
|
const bodyText = await upstream.text().catch(() => "");
|
|
12442
12679
|
let claudeSessionId = null;
|
|
@@ -12471,7 +12708,7 @@ app18.post("/", async (c) => {
|
|
|
12471
12708
|
if (refusal) return refusal;
|
|
12472
12709
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
12473
12710
|
if (!senderId) {
|
|
12474
|
-
console.error(`${
|
|
12711
|
+
console.error(`${TAG25} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
12475
12712
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
12476
12713
|
}
|
|
12477
12714
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -12480,7 +12717,7 @@ app18.post("/", async (c) => {
|
|
|
12480
12717
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
12481
12718
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
12482
12719
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
12483
|
-
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"}`);
|
|
12484
12721
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
12485
12722
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
12486
12723
|
senderId,
|
|
@@ -12499,33 +12736,33 @@ app18.post("/", async (c) => {
|
|
|
12499
12736
|
claudeSessionId,
|
|
12500
12737
|
senderId
|
|
12501
12738
|
);
|
|
12502
|
-
console.log(`${
|
|
12739
|
+
console.log(`${TAG25} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
12503
12740
|
return response;
|
|
12504
12741
|
});
|
|
12505
12742
|
var claude_sessions_default = app18;
|
|
12506
12743
|
|
|
12507
12744
|
// server/routes/admin/log-ingest.ts
|
|
12508
|
-
var
|
|
12745
|
+
var TAG26 = "[log-ingest]";
|
|
12509
12746
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
12510
12747
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
12511
12748
|
var MAX_LINE_BYTES = 4096;
|
|
12512
12749
|
var app19 = new Hono();
|
|
12513
|
-
function
|
|
12750
|
+
function isLoopbackAddr2(addr) {
|
|
12514
12751
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
12515
12752
|
}
|
|
12516
12753
|
app19.post("/", async (c) => {
|
|
12517
12754
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
12518
|
-
if (!
|
|
12519
|
-
console.error(`${
|
|
12755
|
+
if (!isLoopbackAddr2(remoteAddr)) {
|
|
12756
|
+
console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
12520
12757
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
12521
12758
|
}
|
|
12522
12759
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
12523
12760
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
12524
12761
|
if (xffRaw.length > 0) {
|
|
12525
12762
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
12526
|
-
const offender = tokens.find((t) => !
|
|
12763
|
+
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
12527
12764
|
if (offender !== void 0) {
|
|
12528
|
-
console.error(`${
|
|
12765
|
+
console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
12529
12766
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
12530
12767
|
}
|
|
12531
12768
|
}
|
|
@@ -12567,18 +12804,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
12567
12804
|
]);
|
|
12568
12805
|
var app20 = new Hono();
|
|
12569
12806
|
app20.post("/", async (c) => {
|
|
12570
|
-
const
|
|
12807
|
+
const TAG39 = "[admin:events]";
|
|
12571
12808
|
let body;
|
|
12572
12809
|
try {
|
|
12573
12810
|
body = await c.req.json();
|
|
12574
12811
|
} catch (err) {
|
|
12575
12812
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12576
|
-
console.error(`${
|
|
12813
|
+
console.error(`${TAG39} reject reason=body-not-json detail=${detail}`);
|
|
12577
12814
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
12578
12815
|
}
|
|
12579
12816
|
const event = typeof body.event === "string" ? body.event : "";
|
|
12580
12817
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
12581
|
-
console.error(`${
|
|
12818
|
+
console.error(`${TAG39} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
12582
12819
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
12583
12820
|
}
|
|
12584
12821
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -17393,7 +17630,7 @@ function managerLogFollowUrl(sessionId, opts) {
|
|
|
17393
17630
|
}
|
|
17394
17631
|
|
|
17395
17632
|
// server/routes/admin/linkedin-ingest.ts
|
|
17396
|
-
var
|
|
17633
|
+
var TAG27 = "[linkedin-ingest-route]";
|
|
17397
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}$/;
|
|
17398
17635
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
17399
17636
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -17497,29 +17734,29 @@ app37.post("/", requireAdminSession, async (c) => {
|
|
|
17497
17734
|
try {
|
|
17498
17735
|
body = await c.req.json();
|
|
17499
17736
|
} catch {
|
|
17500
|
-
console.error(
|
|
17737
|
+
console.error(TAG27 + " rejected status=400 reason=schema:body-not-json");
|
|
17501
17738
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
17502
17739
|
}
|
|
17503
17740
|
const v = validate(body);
|
|
17504
17741
|
if (!v.ok) {
|
|
17505
|
-
console.error(
|
|
17742
|
+
console.error(TAG27 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
17506
17743
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
17507
17744
|
}
|
|
17508
17745
|
const envelope = v.envelope;
|
|
17509
17746
|
const cacheKey = c.var.cacheKey ?? "";
|
|
17510
17747
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
17511
17748
|
if (!senderId) {
|
|
17512
|
-
console.error(
|
|
17749
|
+
console.error(TAG27 + " rejected status=500 reason=admin-account-not-resolved");
|
|
17513
17750
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
17514
17751
|
}
|
|
17515
17752
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
17516
17753
|
console.log(
|
|
17517
|
-
|
|
17754
|
+
TAG27 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
17518
17755
|
);
|
|
17519
17756
|
const initialMessage = buildInitialMessage(envelope);
|
|
17520
17757
|
const spawnStart = Date.now();
|
|
17521
17758
|
const sessionId = randomUUID12();
|
|
17522
|
-
console.log(
|
|
17759
|
+
console.log(TAG27 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
17523
17760
|
const spawned = await managerRcSpawn({
|
|
17524
17761
|
sessionId,
|
|
17525
17762
|
initialMessage,
|
|
@@ -17528,12 +17765,12 @@ app37.post("/", requireAdminSession, async (c) => {
|
|
|
17528
17765
|
});
|
|
17529
17766
|
if ("error" in spawned) {
|
|
17530
17767
|
console.error(
|
|
17531
|
-
|
|
17768
|
+
TAG27 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
17532
17769
|
);
|
|
17533
17770
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
17534
17771
|
}
|
|
17535
17772
|
console.log(
|
|
17536
|
-
|
|
17773
|
+
TAG27 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
17537
17774
|
);
|
|
17538
17775
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
17539
17776
|
});
|
|
@@ -17541,7 +17778,7 @@ var linkedin_ingest_default = app37;
|
|
|
17541
17778
|
|
|
17542
17779
|
// server/routes/admin/post-turn-context.ts
|
|
17543
17780
|
import neo4j3 from "neo4j-driver";
|
|
17544
|
-
var
|
|
17781
|
+
var TAG28 = "[post-turn-context]";
|
|
17545
17782
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
17546
17783
|
"embedding",
|
|
17547
17784
|
"passwordHash",
|
|
@@ -17549,7 +17786,7 @@ var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
|
17549
17786
|
"otpCode",
|
|
17550
17787
|
"cacheKey"
|
|
17551
17788
|
]);
|
|
17552
|
-
function
|
|
17789
|
+
function isLoopbackAddr3(addr) {
|
|
17553
17790
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17554
17791
|
}
|
|
17555
17792
|
function convertValue(value) {
|
|
@@ -17582,17 +17819,17 @@ function pruneProperties(raw) {
|
|
|
17582
17819
|
var app38 = new Hono();
|
|
17583
17820
|
app38.get("/", async (c) => {
|
|
17584
17821
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17585
|
-
if (!
|
|
17586
|
-
console.error(`${
|
|
17822
|
+
if (!isLoopbackAddr3(remoteAddr)) {
|
|
17823
|
+
console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17587
17824
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
17588
17825
|
}
|
|
17589
17826
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17590
17827
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17591
17828
|
if (xffRaw.length > 0) {
|
|
17592
17829
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17593
|
-
const offender = tokens.find((t) => !
|
|
17830
|
+
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
17594
17831
|
if (offender !== void 0) {
|
|
17595
|
-
console.error(`${
|
|
17832
|
+
console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17596
17833
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
17597
17834
|
}
|
|
17598
17835
|
}
|
|
@@ -17624,7 +17861,7 @@ app38.get("/", async (c) => {
|
|
|
17624
17861
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
17625
17862
|
const total = Date.now() - started;
|
|
17626
17863
|
console.log(
|
|
17627
|
-
`${
|
|
17864
|
+
`${TAG28} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
17628
17865
|
);
|
|
17629
17866
|
return c.json({
|
|
17630
17867
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -17633,7 +17870,7 @@ app38.get("/", async (c) => {
|
|
|
17633
17870
|
const elapsed = Date.now() - started;
|
|
17634
17871
|
const message = err instanceof Error ? err.message : String(err);
|
|
17635
17872
|
console.error(
|
|
17636
|
-
`${
|
|
17873
|
+
`${TAG28} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
17637
17874
|
);
|
|
17638
17875
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
17639
17876
|
} finally {
|
|
@@ -17746,24 +17983,24 @@ function formatPreviousContext(writes) {
|
|
|
17746
17983
|
}
|
|
17747
17984
|
|
|
17748
17985
|
// server/routes/admin/public-session-context.ts
|
|
17749
|
-
var
|
|
17750
|
-
function
|
|
17986
|
+
var TAG29 = "[public-session-context]";
|
|
17987
|
+
function isLoopbackAddr4(addr) {
|
|
17751
17988
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17752
17989
|
}
|
|
17753
17990
|
var app39 = new Hono();
|
|
17754
17991
|
app39.get("/", async (c) => {
|
|
17755
17992
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17756
|
-
if (!
|
|
17757
|
-
console.error(`${
|
|
17993
|
+
if (!isLoopbackAddr4(remoteAddr)) {
|
|
17994
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17758
17995
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
17759
17996
|
}
|
|
17760
17997
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17761
17998
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17762
17999
|
if (xffRaw.length > 0) {
|
|
17763
18000
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17764
|
-
const offender = tokens.find((t) => !
|
|
18001
|
+
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
17765
18002
|
if (offender !== void 0) {
|
|
17766
|
-
console.error(`${
|
|
18003
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17767
18004
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
17768
18005
|
}
|
|
17769
18006
|
}
|
|
@@ -17777,14 +18014,14 @@ app39.get("/", async (c) => {
|
|
|
17777
18014
|
const writes = await fetchSliceWrites(session, sliceToken, accountId);
|
|
17778
18015
|
const total = Date.now() - started;
|
|
17779
18016
|
console.log(
|
|
17780
|
-
`${
|
|
18017
|
+
`${TAG29} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
17781
18018
|
);
|
|
17782
18019
|
return c.json({ writes });
|
|
17783
18020
|
} catch (err) {
|
|
17784
18021
|
const elapsed = Date.now() - started;
|
|
17785
18022
|
const message = err instanceof Error ? err.message : String(err);
|
|
17786
18023
|
console.error(
|
|
17787
|
-
`${
|
|
18024
|
+
`${TAG29} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
17788
18025
|
);
|
|
17789
18026
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
17790
18027
|
} finally {
|
|
@@ -17803,24 +18040,24 @@ function getWebchatGateway() {
|
|
|
17803
18040
|
}
|
|
17804
18041
|
|
|
17805
18042
|
// server/routes/admin/public-session-exit.ts
|
|
17806
|
-
var
|
|
17807
|
-
function
|
|
18043
|
+
var TAG30 = "[public-session-exit-route]";
|
|
18044
|
+
function isLoopbackAddr5(addr) {
|
|
17808
18045
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17809
18046
|
}
|
|
17810
18047
|
var app40 = new Hono();
|
|
17811
18048
|
app40.post("/", async (c) => {
|
|
17812
18049
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17813
|
-
if (!
|
|
17814
|
-
console.error(`${
|
|
18050
|
+
if (!isLoopbackAddr5(remoteAddr)) {
|
|
18051
|
+
console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17815
18052
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17816
18053
|
}
|
|
17817
18054
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17818
18055
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17819
18056
|
if (xffRaw.length > 0) {
|
|
17820
18057
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17821
|
-
const offender = tokens.find((t) => !
|
|
18058
|
+
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
17822
18059
|
if (offender !== void 0) {
|
|
17823
|
-
console.error(`${
|
|
18060
|
+
console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17824
18061
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17825
18062
|
}
|
|
17826
18063
|
}
|
|
@@ -17834,7 +18071,7 @@ app40.post("/", async (c) => {
|
|
|
17834
18071
|
if (!sessionId) return c.json({ error: "sessionId required" }, 400);
|
|
17835
18072
|
const gateway = getWebchatGateway();
|
|
17836
18073
|
if (!gateway) {
|
|
17837
|
-
console.error(`${
|
|
18074
|
+
console.error(`${TAG30} reject reason=gateway-unset sessionId=${sessionId.slice(0, 8)}`);
|
|
17838
18075
|
return c.json({ error: "webchat-gateway-unset" }, 503);
|
|
17839
18076
|
}
|
|
17840
18077
|
gateway.handlePublicSessionExit(sessionId);
|
|
@@ -17843,24 +18080,24 @@ app40.post("/", async (c) => {
|
|
|
17843
18080
|
var public_session_exit_default = app40;
|
|
17844
18081
|
|
|
17845
18082
|
// server/routes/admin/access-session-evict.ts
|
|
17846
|
-
var
|
|
17847
|
-
function
|
|
18083
|
+
var TAG31 = "[access-session-evict]";
|
|
18084
|
+
function isLoopbackAddr6(addr) {
|
|
17848
18085
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17849
18086
|
}
|
|
17850
18087
|
var app41 = new Hono();
|
|
17851
18088
|
app41.post("/", async (c) => {
|
|
17852
18089
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17853
|
-
if (!
|
|
17854
|
-
console.error(`${
|
|
18090
|
+
if (!isLoopbackAddr6(remoteAddr)) {
|
|
18091
|
+
console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17855
18092
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17856
18093
|
}
|
|
17857
18094
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17858
18095
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17859
18096
|
if (xffRaw.length > 0) {
|
|
17860
18097
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17861
|
-
const offender = tokens.find((t) => !
|
|
18098
|
+
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
17862
18099
|
if (offender !== void 0) {
|
|
17863
|
-
console.error(`${
|
|
18100
|
+
console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17864
18101
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17865
18102
|
}
|
|
17866
18103
|
}
|
|
@@ -17873,31 +18110,31 @@ app41.post("/", async (c) => {
|
|
|
17873
18110
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
17874
18111
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
17875
18112
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
17876
|
-
console.log(`${
|
|
18113
|
+
console.log(`${TAG31} grantId=${grantId} dropped=${dropped}`);
|
|
17877
18114
|
return c.json({ ok: true, dropped });
|
|
17878
18115
|
});
|
|
17879
18116
|
var access_session_evict_default = app41;
|
|
17880
18117
|
|
|
17881
18118
|
// server/routes/admin/enrol-person.ts
|
|
17882
|
-
var
|
|
17883
|
-
function
|
|
18119
|
+
var TAG32 = "[enrol]";
|
|
18120
|
+
function isLoopbackAddr7(addr) {
|
|
17884
18121
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17885
18122
|
}
|
|
17886
18123
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17887
18124
|
var app42 = new Hono();
|
|
17888
18125
|
app42.post("/", async (c) => {
|
|
17889
18126
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17890
|
-
if (!
|
|
17891
|
-
console.error(`${
|
|
18127
|
+
if (!isLoopbackAddr7(remoteAddr)) {
|
|
18128
|
+
console.error(`${TAG32} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17892
18129
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17893
18130
|
}
|
|
17894
18131
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17895
18132
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17896
18133
|
if (xffRaw.length > 0) {
|
|
17897
18134
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17898
|
-
const offender = tokens.find((t) => !
|
|
18135
|
+
const offender = tokens.find((t) => !isLoopbackAddr7(t));
|
|
17899
18136
|
if (offender !== void 0) {
|
|
17900
|
-
console.error(`${
|
|
18137
|
+
console.error(`${TAG32} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17901
18138
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17902
18139
|
}
|
|
17903
18140
|
}
|
|
@@ -17922,7 +18159,7 @@ app42.post("/", async (c) => {
|
|
|
17922
18159
|
}
|
|
17923
18160
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
17924
18161
|
if (!accountId) {
|
|
17925
|
-
console.error(`${
|
|
18162
|
+
console.error(`${TAG32} op=person result=no-account-id`);
|
|
17926
18163
|
return c.json({ error: "no-account-id" }, 500);
|
|
17927
18164
|
}
|
|
17928
18165
|
let personId;
|
|
@@ -17930,7 +18167,7 @@ app42.post("/", async (c) => {
|
|
|
17930
18167
|
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
17931
18168
|
} catch (err) {
|
|
17932
18169
|
console.error(
|
|
17933
|
-
`${
|
|
18170
|
+
`${TAG32} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17934
18171
|
);
|
|
17935
18172
|
return c.json({ error: "enrol-failed" }, 500);
|
|
17936
18173
|
}
|
|
@@ -17940,11 +18177,11 @@ app42.post("/", async (c) => {
|
|
|
17940
18177
|
if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
|
|
17941
18178
|
} catch (err) {
|
|
17942
18179
|
console.error(
|
|
17943
|
-
`${
|
|
18180
|
+
`${TAG32} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17944
18181
|
);
|
|
17945
18182
|
}
|
|
17946
18183
|
console.log(
|
|
17947
|
-
`${
|
|
18184
|
+
`${TAG32} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
|
|
17948
18185
|
);
|
|
17949
18186
|
return c.json({ personId, grant }, 200);
|
|
17950
18187
|
});
|
|
@@ -18513,7 +18750,7 @@ app45.route("/calendar", calendar_default);
|
|
|
18513
18750
|
var admin_default = app45;
|
|
18514
18751
|
|
|
18515
18752
|
// server/routes/access/verify-token.ts
|
|
18516
|
-
var
|
|
18753
|
+
var TAG33 = "[access-verify]";
|
|
18517
18754
|
var MINT_TAG = "[access-session-mint]";
|
|
18518
18755
|
var COOKIE_NAME = "__access_session";
|
|
18519
18756
|
var app46 = new Hono();
|
|
@@ -18532,39 +18769,39 @@ app46.post("/", async (c) => {
|
|
|
18532
18769
|
}
|
|
18533
18770
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
18534
18771
|
if (rateMsg) {
|
|
18535
|
-
console.error(`${
|
|
18772
|
+
console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
18536
18773
|
return c.json({ error: rateMsg }, 429);
|
|
18537
18774
|
}
|
|
18538
18775
|
const grant = await findGrantByMagicToken(token);
|
|
18539
18776
|
if (!grant) {
|
|
18540
18777
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18541
|
-
console.error(`${
|
|
18778
|
+
console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
18542
18779
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
18543
18780
|
}
|
|
18544
18781
|
if (grant.agentSlug !== agentSlug) {
|
|
18545
18782
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18546
18783
|
console.error(
|
|
18547
|
-
`${
|
|
18784
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
18548
18785
|
);
|
|
18549
18786
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
18550
18787
|
}
|
|
18551
18788
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
18552
18789
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18553
18790
|
console.error(
|
|
18554
|
-
`${
|
|
18791
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
18555
18792
|
);
|
|
18556
18793
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
18557
18794
|
}
|
|
18558
18795
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
18559
18796
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18560
18797
|
console.error(
|
|
18561
|
-
`${
|
|
18798
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
18562
18799
|
);
|
|
18563
18800
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
18564
18801
|
}
|
|
18565
18802
|
if (!grant.sliceToken) {
|
|
18566
18803
|
console.error(
|
|
18567
|
-
`${
|
|
18804
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
18568
18805
|
);
|
|
18569
18806
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
18570
18807
|
}
|
|
@@ -18580,12 +18817,12 @@ app46.post("/", async (c) => {
|
|
|
18580
18817
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
18581
18818
|
} catch (err) {
|
|
18582
18819
|
console.error(
|
|
18583
|
-
`${
|
|
18820
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
18584
18821
|
);
|
|
18585
18822
|
return c.json({ error: "verification-failed" }, 500);
|
|
18586
18823
|
}
|
|
18587
18824
|
clearAccessRateLimit(ip, agentSlug);
|
|
18588
|
-
console.log(`${
|
|
18825
|
+
console.log(`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
18589
18826
|
console.log(
|
|
18590
18827
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
18591
18828
|
);
|
|
@@ -18661,7 +18898,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
18661
18898
|
}
|
|
18662
18899
|
|
|
18663
18900
|
// server/routes/access/request-magic-link.ts
|
|
18664
|
-
var
|
|
18901
|
+
var TAG34 = "[access-request-link]";
|
|
18665
18902
|
var app47 = new Hono();
|
|
18666
18903
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
18667
18904
|
app47.post("/", async (c) => {
|
|
@@ -18678,18 +18915,18 @@ app47.post("/", async (c) => {
|
|
|
18678
18915
|
}
|
|
18679
18916
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
18680
18917
|
if (rateMsg) {
|
|
18681
|
-
console.error(`${
|
|
18918
|
+
console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
18682
18919
|
return c.json({ error: rateMsg }, 429);
|
|
18683
18920
|
}
|
|
18684
18921
|
recordRequestLinkAttempt(contactValue);
|
|
18685
18922
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
18686
18923
|
if (!accountId) {
|
|
18687
|
-
console.error(`${
|
|
18924
|
+
console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
18688
18925
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18689
18926
|
}
|
|
18690
18927
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
18691
18928
|
if (!grant) {
|
|
18692
|
-
console.log(`${
|
|
18929
|
+
console.log(`${TAG34} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
18693
18930
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18694
18931
|
}
|
|
18695
18932
|
let token;
|
|
@@ -18697,7 +18934,7 @@ app47.post("/", async (c) => {
|
|
|
18697
18934
|
token = await generateNewMagicToken(grant.grantId);
|
|
18698
18935
|
} catch (err) {
|
|
18699
18936
|
console.error(
|
|
18700
|
-
`${
|
|
18937
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
18701
18938
|
);
|
|
18702
18939
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18703
18940
|
}
|
|
@@ -18729,12 +18966,12 @@ app47.post("/", async (c) => {
|
|
|
18729
18966
|
});
|
|
18730
18967
|
if (!sendResult.ok) {
|
|
18731
18968
|
console.error(
|
|
18732
|
-
`${
|
|
18969
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
18733
18970
|
);
|
|
18734
18971
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18735
18972
|
}
|
|
18736
18973
|
console.log(
|
|
18737
|
-
`${
|
|
18974
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
18738
18975
|
);
|
|
18739
18976
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18740
18977
|
});
|
|
@@ -20752,6 +20989,7 @@ var WaGateway = class {
|
|
|
20752
20989
|
const willDeliverNow = this.hub.isReady(input.senderId);
|
|
20753
20990
|
const openable = input.media.filter((m) => m.type !== "audio");
|
|
20754
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";
|
|
20755
20993
|
this.replies.set(input.senderId, input.reply);
|
|
20756
20994
|
this.docContexts.set(input.senderId, { accountId: input.accountId });
|
|
20757
20995
|
this.hub.deliver(
|
|
@@ -20759,12 +20997,13 @@ var WaGateway = class {
|
|
|
20759
20997
|
senderId: input.senderId,
|
|
20760
20998
|
text: input.text,
|
|
20761
20999
|
waMessageId: `wa-${++this.seq}`,
|
|
20762
|
-
media: input.media
|
|
21000
|
+
media: input.media,
|
|
21001
|
+
source
|
|
20763
21002
|
},
|
|
20764
21003
|
Date.now()
|
|
20765
21004
|
);
|
|
20766
21005
|
console.error(
|
|
20767
|
-
`[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"}`
|
|
20768
21007
|
);
|
|
20769
21008
|
if (!hadSubscriber && !this.spawning.has(input.senderId)) {
|
|
20770
21009
|
this.spawning.add(input.senderId);
|
|
@@ -21701,7 +21940,7 @@ function makeFileDelivery(opts) {
|
|
|
21701
21940
|
}
|
|
21702
21941
|
|
|
21703
21942
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
21704
|
-
var
|
|
21943
|
+
var TAG35 = "[whatsapp-adaptor]";
|
|
21705
21944
|
var SEND_USER_FILE2 = "SendUserFile";
|
|
21706
21945
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
21707
21946
|
function platformRoot() {
|
|
@@ -21714,7 +21953,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21714
21953
|
maxyAccountId = resolvePlatformAccountId();
|
|
21715
21954
|
} catch (err) {
|
|
21716
21955
|
console.error(
|
|
21717
|
-
`${
|
|
21956
|
+
`${TAG35} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
21718
21957
|
);
|
|
21719
21958
|
return { ok: false, error: "account-unresolved" };
|
|
21720
21959
|
}
|
|
@@ -21728,7 +21967,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21728
21967
|
});
|
|
21729
21968
|
if (result.ok) return { ok: true };
|
|
21730
21969
|
console.error(
|
|
21731
|
-
`${
|
|
21970
|
+
`${TAG35} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
21732
21971
|
);
|
|
21733
21972
|
return { ok: false, error: result.error };
|
|
21734
21973
|
};
|
|
@@ -21736,7 +21975,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21736
21975
|
function makeWhatsAppFileDelivery(entry) {
|
|
21737
21976
|
const shared = makeFileDelivery({
|
|
21738
21977
|
entry,
|
|
21739
|
-
tag:
|
|
21978
|
+
tag: TAG35,
|
|
21740
21979
|
channel: "whatsapp",
|
|
21741
21980
|
sendFile: makeWhatsAppSendFile(entry)
|
|
21742
21981
|
});
|
|
@@ -21771,7 +22010,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
21771
22010
|
if (!delivered) {
|
|
21772
22011
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
21773
22012
|
console.error(
|
|
21774
|
-
`${
|
|
22013
|
+
`${TAG35} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
21775
22014
|
);
|
|
21776
22015
|
}
|
|
21777
22016
|
}
|
|
@@ -22080,13 +22319,14 @@ var TelegramGateway = class {
|
|
|
22080
22319
|
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
22081
22320
|
const hadSubscriber = this.hub.hasSubscriber(input.senderId);
|
|
22082
22321
|
const willDeliverNow = this.hub.isReady(input.senderId);
|
|
22322
|
+
const source = input.source ?? "user";
|
|
22083
22323
|
this.replies.set(input.senderId, input.reply);
|
|
22084
22324
|
this.hub.deliver(
|
|
22085
|
-
{ key: input.senderId, text: input.text, messageId: `tg-${++this.seq}
|
|
22325
|
+
{ key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source },
|
|
22086
22326
|
Date.now()
|
|
22087
22327
|
);
|
|
22088
22328
|
console.error(
|
|
22089
|
-
`[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"}`
|
|
22090
22330
|
);
|
|
22091
22331
|
if (!hadSubscriber && !this.spawning.has(input.senderId)) {
|
|
22092
22332
|
this.spawning.add(input.senderId);
|
|
@@ -22136,7 +22376,7 @@ function buildTelegramSpawnRequest(input) {
|
|
|
22136
22376
|
import { realpathSync as realpathSync7 } from "fs";
|
|
22137
22377
|
import { readFile as readFile7, stat as stat7 } from "fs/promises";
|
|
22138
22378
|
import { resolve as resolve30, basename as basename11 } from "path";
|
|
22139
|
-
var
|
|
22379
|
+
var TAG36 = "[telegram:outbound]";
|
|
22140
22380
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
22141
22381
|
async function sendTelegramDocument(input) {
|
|
22142
22382
|
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot3 } = input;
|
|
@@ -22152,16 +22392,16 @@ async function sendTelegramDocument(input) {
|
|
|
22152
22392
|
resolvedPath = realpathSync7(filePath);
|
|
22153
22393
|
const accountResolved = realpathSync7(accountDir);
|
|
22154
22394
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
22155
|
-
console.error(`${
|
|
22395
|
+
console.error(`${TAG36} document REJECTED reason=outside_account_directory`);
|
|
22156
22396
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
22157
22397
|
}
|
|
22158
22398
|
} catch (err) {
|
|
22159
22399
|
const code = err.code;
|
|
22160
22400
|
if (code === "ENOENT") {
|
|
22161
|
-
console.error(`${
|
|
22401
|
+
console.error(`${TAG36} document ENOENT path=${filePath}`);
|
|
22162
22402
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
22163
22403
|
}
|
|
22164
|
-
console.error(`${
|
|
22404
|
+
console.error(`${TAG36} document path error: ${String(err)}`);
|
|
22165
22405
|
return { ok: false, status: 500, error: String(err) };
|
|
22166
22406
|
}
|
|
22167
22407
|
const fileStat = await stat7(resolvedPath);
|
|
@@ -22194,13 +22434,13 @@ async function sendTelegramDocument(input) {
|
|
|
22194
22434
|
error = e instanceof Error ? e.message : String(e);
|
|
22195
22435
|
}
|
|
22196
22436
|
console.error(
|
|
22197
|
-
`${
|
|
22437
|
+
`${TAG36} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
22198
22438
|
);
|
|
22199
22439
|
return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
22200
22440
|
}
|
|
22201
22441
|
|
|
22202
22442
|
// app/lib/telegram/outbound/file-delivery.ts
|
|
22203
|
-
var
|
|
22443
|
+
var TAG37 = "[telegram:outbound]";
|
|
22204
22444
|
function platformRoot2() {
|
|
22205
22445
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
22206
22446
|
}
|
|
@@ -22212,11 +22452,11 @@ function makeTelegramSendFile(entry) {
|
|
|
22212
22452
|
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
22213
22453
|
}
|
|
22214
22454
|
if (!botToken) {
|
|
22215
|
-
console.error(`${
|
|
22455
|
+
console.error(`${TAG37} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
22216
22456
|
return { ok: false, error: "no-bot-token" };
|
|
22217
22457
|
}
|
|
22218
22458
|
if (entry.replyTarget == null) {
|
|
22219
|
-
console.error(`${
|
|
22459
|
+
console.error(`${TAG37} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
22220
22460
|
return { ok: false, error: "no-reply-target" };
|
|
22221
22461
|
}
|
|
22222
22462
|
let maxyAccountId;
|
|
@@ -22224,7 +22464,7 @@ function makeTelegramSendFile(entry) {
|
|
|
22224
22464
|
maxyAccountId = resolvePlatformAccountId();
|
|
22225
22465
|
} catch (err) {
|
|
22226
22466
|
console.error(
|
|
22227
|
-
`${
|
|
22467
|
+
`${TAG37} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
22228
22468
|
);
|
|
22229
22469
|
return { ok: false, error: "account-unresolved" };
|
|
22230
22470
|
}
|
|
@@ -22242,7 +22482,7 @@ function makeTelegramSendFile(entry) {
|
|
|
22242
22482
|
function makeTelegramFileDelivery(entry) {
|
|
22243
22483
|
return makeFileDelivery({
|
|
22244
22484
|
entry,
|
|
22245
|
-
tag:
|
|
22485
|
+
tag: TAG37,
|
|
22246
22486
|
channel: "telegram",
|
|
22247
22487
|
sendFile: makeTelegramSendFile(entry)
|
|
22248
22488
|
});
|
|
@@ -22292,7 +22532,7 @@ function telegramServerPath() {
|
|
|
22292
22532
|
|
|
22293
22533
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
22294
22534
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
22295
|
-
var
|
|
22535
|
+
var TAG38 = "[public-session-review]";
|
|
22296
22536
|
var CONSUMED_CAP = 500;
|
|
22297
22537
|
var consumed = /* @__PURE__ */ new Set();
|
|
22298
22538
|
function consumeOnce(sessionId) {
|
|
@@ -22319,7 +22559,7 @@ async function fetchJsonl(sessionId) {
|
|
|
22319
22559
|
return await res.text();
|
|
22320
22560
|
} catch (err) {
|
|
22321
22561
|
console.error(
|
|
22322
|
-
`${
|
|
22562
|
+
`${TAG38} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
22323
22563
|
);
|
|
22324
22564
|
return null;
|
|
22325
22565
|
}
|
|
@@ -22343,7 +22583,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
22343
22583
|
const res = await fetch(url);
|
|
22344
22584
|
if (!res.ok) {
|
|
22345
22585
|
console.error(
|
|
22346
|
-
`${
|
|
22586
|
+
`${TAG38} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
22347
22587
|
);
|
|
22348
22588
|
return [];
|
|
22349
22589
|
}
|
|
@@ -22351,7 +22591,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
22351
22591
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
22352
22592
|
} catch (err) {
|
|
22353
22593
|
console.error(
|
|
22354
|
-
`${
|
|
22594
|
+
`${TAG38} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
22355
22595
|
);
|
|
22356
22596
|
return [];
|
|
22357
22597
|
}
|
|
@@ -22380,7 +22620,7 @@ function composeInitialMessage(input) {
|
|
|
22380
22620
|
}
|
|
22381
22621
|
async function dispatchReviewer(input, initialMessage) {
|
|
22382
22622
|
const sessionId = randomUUID13();
|
|
22383
|
-
console.log(`${
|
|
22623
|
+
console.log(`${TAG38} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
22384
22624
|
const spawned = await managerRcSpawn({
|
|
22385
22625
|
sessionId,
|
|
22386
22626
|
initialMessage,
|
|
@@ -22400,21 +22640,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
22400
22640
|
const dispatchedAt = Date.now();
|
|
22401
22641
|
if (consumed.has(input.sessionId)) {
|
|
22402
22642
|
console.log(
|
|
22403
|
-
`${
|
|
22643
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
22404
22644
|
);
|
|
22405
22645
|
return;
|
|
22406
22646
|
}
|
|
22407
22647
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
22408
22648
|
if (!jsonl) {
|
|
22409
22649
|
console.log(
|
|
22410
|
-
`${
|
|
22650
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
22411
22651
|
);
|
|
22412
22652
|
return;
|
|
22413
22653
|
}
|
|
22414
22654
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
22415
22655
|
if (operatorTurns === 0) {
|
|
22416
22656
|
console.log(
|
|
22417
|
-
`${
|
|
22657
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
22418
22658
|
);
|
|
22419
22659
|
return;
|
|
22420
22660
|
}
|
|
@@ -22428,23 +22668,34 @@ async function firePublicSessionEndReview(input) {
|
|
|
22428
22668
|
});
|
|
22429
22669
|
if (!consumeOnce(input.sessionId)) {
|
|
22430
22670
|
console.log(
|
|
22431
|
-
`${
|
|
22671
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
22432
22672
|
);
|
|
22433
22673
|
return;
|
|
22434
22674
|
}
|
|
22435
22675
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
22436
22676
|
if (!dispatched.ok) {
|
|
22437
22677
|
console.error(
|
|
22438
|
-
`${
|
|
22678
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
22439
22679
|
);
|
|
22440
22680
|
return;
|
|
22441
22681
|
}
|
|
22442
22682
|
console.log(
|
|
22443
|
-
`${
|
|
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}`
|
|
22444
22684
|
);
|
|
22445
22685
|
}
|
|
22446
22686
|
|
|
22447
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
|
+
}
|
|
22448
22699
|
function findChannelAdminBindingDrift(adminPhones, admins, users) {
|
|
22449
22700
|
const drift = [];
|
|
22450
22701
|
for (const phone of adminPhones) {
|
|
@@ -22467,6 +22718,7 @@ function warnOnChannelAdminBindingDrift() {
|
|
|
22467
22718
|
} catch {
|
|
22468
22719
|
return;
|
|
22469
22720
|
}
|
|
22721
|
+
const validAccountIds = accounts.map((a) => a.accountId);
|
|
22470
22722
|
for (const acct of accounts) {
|
|
22471
22723
|
const adminPhones = readAdminPhones(acct.accountDir);
|
|
22472
22724
|
const drift = findChannelAdminBindingDrift(adminPhones, acct.config.admins ?? [], users);
|
|
@@ -22476,6 +22728,12 @@ function warnOnChannelAdminBindingDrift() {
|
|
|
22476
22728
|
`[admin-identity] binding-drift accountId=${acct.accountId} phone=${d.phone} reason=${d.reason}${tail}`
|
|
22477
22729
|
);
|
|
22478
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
|
+
}
|
|
22479
22737
|
}
|
|
22480
22738
|
}
|
|
22481
22739
|
|
|
@@ -22840,8 +23098,14 @@ var waGateway = new WaGateway({
|
|
|
22840
23098
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
22841
23099
|
},
|
|
22842
23100
|
ensureChannelSession: async ({ accountId, senderId, role, personId, gatewayUrl, serverPath }) => {
|
|
22843
|
-
const
|
|
22844
|
-
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;
|
|
22845
23109
|
if (role === "admin") {
|
|
22846
23110
|
console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
|
|
22847
23111
|
}
|
|
@@ -22987,6 +23251,22 @@ var chatRoutes = createChatRoutes({
|
|
|
22987
23251
|
handleInbound: (input) => webchatGateway.handleInbound(input),
|
|
22988
23252
|
awaitReply: (key, timeoutMs) => webchatGateway.awaitReply(key, timeoutMs)
|
|
22989
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);
|
|
22990
23270
|
app55.use("*", clientIpMiddleware);
|
|
22991
23271
|
function allowsSameOriginFraming(path2) {
|
|
22992
23272
|
return path2 === "/vnc-viewer.html" || path2.startsWith("/api/admin/attachment/") || path2.startsWith("/api/public-reader/attachment/") || path2 === "/api/admin/files/download";
|