@rubytech/create-maxy-code 0.1.386 → 0.1.388
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 +9 -4
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +12 -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 +713 -331
- 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
|
@@ -1307,7 +1307,7 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1307
1307
|
// server/index.ts
|
|
1308
1308
|
import { readFileSync as readFileSync35, existsSync as existsSync33, watchFile } from "fs";
|
|
1309
1309
|
import { spawn as spawn3 } from "child_process";
|
|
1310
|
-
import { resolve as resolve33, join as join34, basename as
|
|
1310
|
+
import { resolve as resolve33, join as join34, basename as basename13 } from "path";
|
|
1311
1311
|
import { homedir as homedir3 } from "os";
|
|
1312
1312
|
import { monitorEventLoopDelay } from "perf_hooks";
|
|
1313
1313
|
|
|
@@ -1726,7 +1726,8 @@ var WhatsAppAccountSchema = z.object({
|
|
|
1726
1726
|
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
|
-
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."),
|
|
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. Authoritative only on the account that owns the WhatsApp paired socket; a list on any other account is inert and purged at boot \u2014 use accountManagers for sub-account routing."),
|
|
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) {
|
|
@@ -2616,24 +2628,31 @@ function isAdminPhone(phone, adminPhones) {
|
|
|
2616
2628
|
return adminPhones.some((entry) => phonesMatch(entry, normalized));
|
|
2617
2629
|
}
|
|
2618
2630
|
function checkDmAccess(params) {
|
|
2619
|
-
const { senderPhone, selfPhone } = params;
|
|
2631
|
+
const { senderPhone, selfPhone, accountId } = params;
|
|
2620
2632
|
const cfg = resolveAccountConfig(params.config, params.accountConfig);
|
|
2621
2633
|
if (phonesMatch(senderPhone, selfPhone)) {
|
|
2622
|
-
return { allowed: true, reason: "self-phone", agentType: "admin" };
|
|
2634
|
+
return { allowed: true, reason: "self-phone", agentType: "admin", effectiveAccountId: accountId };
|
|
2623
2635
|
}
|
|
2624
2636
|
if (isAdminPhone(senderPhone, cfg.adminPhones)) {
|
|
2625
|
-
return { allowed: true, reason: "admin-binding", agentType: "admin" };
|
|
2637
|
+
return { allowed: true, reason: "admin-binding", agentType: "admin", effectiveAccountId: accountId };
|
|
2638
|
+
}
|
|
2639
|
+
const managedSub = managedAccountFor(cfg.accountManagers, senderPhone);
|
|
2640
|
+
if (managedSub) {
|
|
2641
|
+
if (params.isValidAccount && !params.isValidAccount(managedSub)) {
|
|
2642
|
+
return { allowed: false, reason: "account-manager-unresolved", agentType: "admin" };
|
|
2643
|
+
}
|
|
2644
|
+
return { allowed: true, reason: "account-manager", agentType: "admin", effectiveAccountId: managedSub };
|
|
2626
2645
|
}
|
|
2627
2646
|
switch (cfg.dmPolicy) {
|
|
2628
2647
|
case "open":
|
|
2629
|
-
return { allowed: true, reason: "dm-policy-open", agentType: "public" };
|
|
2648
|
+
return { allowed: true, reason: "dm-policy-open", agentType: "public", effectiveAccountId: accountId };
|
|
2630
2649
|
case "allowlist": {
|
|
2631
2650
|
const inList = cfg.allowFrom.some((entry) => {
|
|
2632
2651
|
if (entry === "*") return true;
|
|
2633
2652
|
return phonesMatch(entry, senderPhone);
|
|
2634
2653
|
});
|
|
2635
2654
|
if (inList) {
|
|
2636
|
-
return { allowed: true, reason: "allowlist-match", agentType: "public" };
|
|
2655
|
+
return { allowed: true, reason: "allowlist-match", agentType: "public", effectiveAccountId: accountId };
|
|
2637
2656
|
}
|
|
2638
2657
|
return { allowed: false, reason: "not-in-allowlist", agentType: "public" };
|
|
2639
2658
|
}
|
|
@@ -4085,7 +4104,9 @@ function buildSelfChatDispatch(input) {
|
|
|
4085
4104
|
senderPhone,
|
|
4086
4105
|
isGroup: false
|
|
4087
4106
|
}),
|
|
4088
|
-
media: []
|
|
4107
|
+
media: [],
|
|
4108
|
+
// Self-chat is the owner: the session scopes to the house account (Task 1383).
|
|
4109
|
+
effectiveAccountId: input.accountId
|
|
4089
4110
|
};
|
|
4090
4111
|
}
|
|
4091
4112
|
async function init(opts) {
|
|
@@ -4733,12 +4754,24 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4733
4754
|
config: whatsAppConfig,
|
|
4734
4755
|
accountConfig: whatsAppConfig.accounts?.[conn.accountId],
|
|
4735
4756
|
accountId: conn.accountId,
|
|
4757
|
+
isValidAccount: (id) => listValidAccounts().some((a) => a.accountId === id),
|
|
4736
4758
|
resolvePersonByPhone
|
|
4737
4759
|
});
|
|
4738
4760
|
}
|
|
4739
4761
|
console.error(
|
|
4740
4762
|
`${TAG13} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
|
|
4741
4763
|
);
|
|
4764
|
+
if (!accessResult.allowed && accessResult.reason === "account-manager-unresolved") {
|
|
4765
|
+
console.error(
|
|
4766
|
+
`[whatsapp-native] op=account-manager-reject senderId=${senderPhone} reason=unresolved-effective-account`
|
|
4767
|
+
);
|
|
4768
|
+
}
|
|
4769
|
+
if (accessResult.allowed && accessResult.reason === "account-manager" && accessResult.effectiveAccountId === conn.accountId) {
|
|
4770
|
+
console.error(
|
|
4771
|
+
`[whatsapp-native] op=escalation-tripwire senderId=${senderPhone} reason=account-manager-routed-to-house effectiveAccount=${accessResult.effectiveAccountId}`
|
|
4772
|
+
);
|
|
4773
|
+
return;
|
|
4774
|
+
}
|
|
4742
4775
|
if (!accessResult.allowed) return;
|
|
4743
4776
|
if (isCustomerTurnSuppressed(accessResult.agentType, isHouseManagedService())) {
|
|
4744
4777
|
console.error(
|
|
@@ -4786,7 +4819,10 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4786
4819
|
composing,
|
|
4787
4820
|
cacheKey,
|
|
4788
4821
|
media: mediaResult?.path ? [{ path: mediaResult.path, type: extracted.mediaType, mimetype: mediaResult.mimetype }] : [],
|
|
4789
|
-
replyContext: extracted.quotedMessage
|
|
4822
|
+
replyContext: extracted.quotedMessage,
|
|
4823
|
+
// Task 1383 — the gate's single resolution feeds the spawn (house or the
|
|
4824
|
+
// bound sub-account); the router never re-resolves.
|
|
4825
|
+
effectiveAccountId: accessResult.effectiveAccountId
|
|
4790
4826
|
};
|
|
4791
4827
|
if (accessResult.agentType === "public") {
|
|
4792
4828
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
@@ -5971,6 +6007,137 @@ ${result.result.text}` : result.result.text;
|
|
|
5971
6007
|
return app56;
|
|
5972
6008
|
}
|
|
5973
6009
|
|
|
6010
|
+
// app/lib/channel-pty-bridge/admin-session-id.ts
|
|
6011
|
+
import { createHash } from "crypto";
|
|
6012
|
+
function adminSessionIdFor(accountId, senderId, personId) {
|
|
6013
|
+
const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
|
|
6014
|
+
const b = createHash("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
|
|
6015
|
+
const v = Buffer.from(b);
|
|
6016
|
+
v[6] = v[6] & 15 | 64;
|
|
6017
|
+
v[8] = v[8] & 63 | 128;
|
|
6018
|
+
const h = v.toString("hex");
|
|
6019
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
6020
|
+
}
|
|
6021
|
+
|
|
6022
|
+
// server/routes/channel/schedule-inject.ts
|
|
6023
|
+
var TAG16 = "[schedule-inject]";
|
|
6024
|
+
function isLoopbackAddr(addr) {
|
|
6025
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
6026
|
+
}
|
|
6027
|
+
function createScheduleInjectRoutes(deps) {
|
|
6028
|
+
const app56 = new Hono();
|
|
6029
|
+
app56.post("/", async (c) => {
|
|
6030
|
+
const env = c.env;
|
|
6031
|
+
const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
|
|
6032
|
+
if (!isLoopbackAddr(remoteAddr)) {
|
|
6033
|
+
console.error(`${TAG16} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
6034
|
+
return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
|
|
6035
|
+
}
|
|
6036
|
+
const xffHeader = env?.incoming?.headers?.["x-forwarded-for"];
|
|
6037
|
+
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
6038
|
+
if (xffRaw.length > 0) {
|
|
6039
|
+
const offender = xffRaw.split(",").map((t) => t.trim()).filter(Boolean).find((t) => !isLoopbackAddr(t));
|
|
6040
|
+
if (offender !== void 0) {
|
|
6041
|
+
console.error(`${TAG16} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
6042
|
+
return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
let body;
|
|
6046
|
+
try {
|
|
6047
|
+
body = await c.req.json();
|
|
6048
|
+
} catch {
|
|
6049
|
+
return c.json({ ok: false, error: "invalid-json" }, 400);
|
|
6050
|
+
}
|
|
6051
|
+
const channel = body.channel;
|
|
6052
|
+
const destination = typeof body.destination === "string" ? body.destination : "";
|
|
6053
|
+
const prompt = typeof body.prompt === "string" ? body.prompt : "";
|
|
6054
|
+
const eventId = typeof body.eventId === "string" ? body.eventId : "";
|
|
6055
|
+
if (channel !== "whatsapp" && channel !== "telegram" || !destination || !prompt) {
|
|
6056
|
+
console.error(`${TAG16} reject reason=bad-request channel=${String(channel)} hasDestination=${Boolean(destination)} hasPrompt=${Boolean(prompt)}`);
|
|
6057
|
+
return c.json({ ok: false, error: "schedule-inject-bad-request" }, 400);
|
|
6058
|
+
}
|
|
6059
|
+
const account = deps.resolveAccount();
|
|
6060
|
+
if (!account) {
|
|
6061
|
+
console.error(`${TAG16} reject reason=no-account eventId=${eventId}`);
|
|
6062
|
+
return c.json({ ok: false, error: "no-account" }, 500);
|
|
6063
|
+
}
|
|
6064
|
+
const houseAccountId = account.accountId;
|
|
6065
|
+
console.error(`${TAG16} op=received eventId=${eventId} channel=${channel} destination=${destination} account=${houseAccountId}`);
|
|
6066
|
+
if (channel === "whatsapp") {
|
|
6067
|
+
const effectiveAccount2 = deps.effectiveAccountFor(houseAccountId, account.accountDir, destination);
|
|
6068
|
+
console.error(`${TAG16} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount2}`);
|
|
6069
|
+
const sessionId2 = adminSessionIdFor(effectiveAccount2, destination);
|
|
6070
|
+
const reply2 = (text) => deps.sendWhatsAppText(houseAccountId, destination, text);
|
|
6071
|
+
try {
|
|
6072
|
+
await deps.waHandleInbound({
|
|
6073
|
+
accountId: houseAccountId,
|
|
6074
|
+
// Task 1383 — the router (ensureChannelSession) no longer re-resolves
|
|
6075
|
+
// the accountManagers map; it consumes this value. The scheduler
|
|
6076
|
+
// supplies its own resolution (effectiveAccountFor, computed above) so
|
|
6077
|
+
// a manager destination scopes the spawned session to the sub-account,
|
|
6078
|
+
// exactly as a real inbound now does via the gate.
|
|
6079
|
+
effectiveAccountId: effectiveAccount2,
|
|
6080
|
+
senderId: destination,
|
|
6081
|
+
role: "admin",
|
|
6082
|
+
personId: null,
|
|
6083
|
+
text: prompt,
|
|
6084
|
+
media: [],
|
|
6085
|
+
source: "schedule",
|
|
6086
|
+
reply: reply2
|
|
6087
|
+
});
|
|
6088
|
+
} catch (err) {
|
|
6089
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
|
|
6090
|
+
return c.json({ ok: false, error: "inject-error" }, 502);
|
|
6091
|
+
}
|
|
6092
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=ok status=-`);
|
|
6093
|
+
return c.json({ ok: true }, 200);
|
|
6094
|
+
}
|
|
6095
|
+
const effectiveAccount = houseAccountId;
|
|
6096
|
+
console.error(`${TAG16} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount}`);
|
|
6097
|
+
const botToken = account.config.telegram?.adminBotToken;
|
|
6098
|
+
if (!botToken) {
|
|
6099
|
+
console.error(`${TAG16} reject reason=no-admin-bot-token eventId=${eventId}`);
|
|
6100
|
+
return c.json({ ok: false, error: "no-admin-bot-token" }, 400);
|
|
6101
|
+
}
|
|
6102
|
+
const chatId = Number(destination);
|
|
6103
|
+
const sessionId = adminSessionIdFor(effectiveAccount, destination);
|
|
6104
|
+
const reply = (text) => deps.sendTelegramText(botToken, chatId, text);
|
|
6105
|
+
try {
|
|
6106
|
+
await deps.tgHandleInbound({
|
|
6107
|
+
accountId: houseAccountId,
|
|
6108
|
+
senderId: destination,
|
|
6109
|
+
chatId: destination,
|
|
6110
|
+
role: "admin",
|
|
6111
|
+
personId: null,
|
|
6112
|
+
text: prompt,
|
|
6113
|
+
source: "schedule",
|
|
6114
|
+
reply
|
|
6115
|
+
});
|
|
6116
|
+
} catch (err) {
|
|
6117
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
|
|
6118
|
+
return c.json({ ok: false, error: "inject-error" }, 502);
|
|
6119
|
+
}
|
|
6120
|
+
console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
|
|
6121
|
+
return c.json({ ok: true }, 200);
|
|
6122
|
+
});
|
|
6123
|
+
return app56;
|
|
6124
|
+
}
|
|
6125
|
+
|
|
6126
|
+
// app/lib/telegram/outbound/send-text.ts
|
|
6127
|
+
async function sendTelegramText(botToken, chatId, text) {
|
|
6128
|
+
try {
|
|
6129
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
6130
|
+
method: "POST",
|
|
6131
|
+
headers: { "Content-Type": "application/json" },
|
|
6132
|
+
body: JSON.stringify({ chat_id: chatId, text })
|
|
6133
|
+
});
|
|
6134
|
+
const data = await res.json();
|
|
6135
|
+
return data.ok ? { ok: true } : { ok: false, error: data.description ?? "send failed" };
|
|
6136
|
+
} catch (e) {
|
|
6137
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
|
|
5974
6141
|
// server/routes/whatsapp.ts
|
|
5975
6142
|
import { join as join10, resolve as resolve10 } from "path";
|
|
5976
6143
|
import { readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync6 } from "fs";
|
|
@@ -5980,8 +6147,8 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
5980
6147
|
|
|
5981
6148
|
// app/lib/whatsapp/config-persist.ts
|
|
5982
6149
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
5983
|
-
import { resolve as resolve8, join as join8 } from "path";
|
|
5984
|
-
var
|
|
6150
|
+
import { resolve as resolve8, join as join8, dirname as dirname2, basename as basename2 } from "path";
|
|
6151
|
+
var TAG17 = "[whatsapp:config]";
|
|
5985
6152
|
function configPath(accountDir) {
|
|
5986
6153
|
return resolve8(accountDir, "account.json");
|
|
5987
6154
|
}
|
|
@@ -5998,9 +6165,9 @@ function reloadManagerConfig(accountDir) {
|
|
|
5998
6165
|
try {
|
|
5999
6166
|
const config = readConfig(accountDir);
|
|
6000
6167
|
reloadConfig(config);
|
|
6001
|
-
console.error(`${
|
|
6168
|
+
console.error(`${TAG17} reloaded manager config`);
|
|
6002
6169
|
} catch (err) {
|
|
6003
|
-
console.error(`${
|
|
6170
|
+
console.error(`${TAG17} manager config reload failed: ${String(err)}`);
|
|
6004
6171
|
}
|
|
6005
6172
|
}
|
|
6006
6173
|
var E164_PATTERN = /^\+\d{7,15}$/;
|
|
@@ -6026,25 +6193,25 @@ function persistAfterPairing(accountDir, accountId, selfPhone) {
|
|
|
6026
6193
|
const adminPhones = wa.adminPhones;
|
|
6027
6194
|
if (!adminPhones.includes(normalized)) {
|
|
6028
6195
|
adminPhones.push(normalized);
|
|
6029
|
-
console.error(`${
|
|
6196
|
+
console.error(`${TAG17} added selfPhone=${normalized} to adminPhones`);
|
|
6030
6197
|
}
|
|
6031
6198
|
} else {
|
|
6032
|
-
console.error(`${
|
|
6199
|
+
console.error(`${TAG17} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
|
|
6033
6200
|
}
|
|
6034
6201
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6035
6202
|
if (!parsed.success) {
|
|
6036
6203
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6037
|
-
console.error(`${
|
|
6204
|
+
console.error(`${TAG17} validation failed after pairing: ${msg}`);
|
|
6038
6205
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6039
6206
|
}
|
|
6040
6207
|
config.whatsapp = parsed.data;
|
|
6041
6208
|
writeConfig(accountDir, config);
|
|
6042
|
-
console.error(`${
|
|
6209
|
+
console.error(`${TAG17} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
|
|
6043
6210
|
reloadManagerConfig(accountDir);
|
|
6044
6211
|
return { ok: true };
|
|
6045
6212
|
} catch (err) {
|
|
6046
6213
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6047
|
-
console.error(`${
|
|
6214
|
+
console.error(`${TAG17} persist failed account=${accountId}: ${msg}`);
|
|
6048
6215
|
return { ok: false, error: msg };
|
|
6049
6216
|
}
|
|
6050
6217
|
}
|
|
@@ -6055,6 +6222,16 @@ function addAdminPhone(accountDir, phone) {
|
|
|
6055
6222
|
}
|
|
6056
6223
|
try {
|
|
6057
6224
|
const config = readConfig(accountDir);
|
|
6225
|
+
const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
|
|
6226
|
+
const socketOwner = resolveHouseOrSoleAccountId(dirname2(resolve8(accountDir)));
|
|
6227
|
+
if (targetId !== socketOwner) {
|
|
6228
|
+
console.error(`${TAG17} add-admin-phone rejected accountId=${targetId} reason=not-socket-account`);
|
|
6229
|
+
return {
|
|
6230
|
+
ok: false,
|
|
6231
|
+
reason: "not-socket-account",
|
|
6232
|
+
error: `${targetId} does not own the WhatsApp paired socket, so its admin phones are never consulted. To route a phone to a sub-account, bind it as an account manager (add-account-manager) instead.`
|
|
6233
|
+
};
|
|
6234
|
+
}
|
|
6058
6235
|
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
6059
6236
|
config.whatsapp = {};
|
|
6060
6237
|
}
|
|
@@ -6066,6 +6243,9 @@ function addAdminPhone(accountDir, phone) {
|
|
|
6066
6243
|
if (adminPhones.includes(normalized)) {
|
|
6067
6244
|
return { ok: true, message: `Phone ${normalized} is already in the admin list.` };
|
|
6068
6245
|
}
|
|
6246
|
+
if (managedAccountFor(readAccountManagers(accountDir), normalized)) {
|
|
6247
|
+
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.` };
|
|
6248
|
+
}
|
|
6069
6249
|
adminPhones.push(normalized);
|
|
6070
6250
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6071
6251
|
if (!parsed.success) {
|
|
@@ -6074,12 +6254,12 @@ function addAdminPhone(accountDir, phone) {
|
|
|
6074
6254
|
}
|
|
6075
6255
|
config.whatsapp = parsed.data;
|
|
6076
6256
|
writeConfig(accountDir, config);
|
|
6077
|
-
console.error(`${
|
|
6257
|
+
console.error(`${TAG17} added admin phone=${normalized}`);
|
|
6078
6258
|
reloadManagerConfig(accountDir);
|
|
6079
6259
|
return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
|
|
6080
6260
|
} catch (err) {
|
|
6081
6261
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6082
|
-
console.error(`${
|
|
6262
|
+
console.error(`${TAG17} addAdminPhone failed: ${msg}`);
|
|
6083
6263
|
return { ok: false, error: msg };
|
|
6084
6264
|
}
|
|
6085
6265
|
}
|
|
@@ -6107,12 +6287,12 @@ function removeAdminPhone(accountDir, phone) {
|
|
|
6107
6287
|
}
|
|
6108
6288
|
config.whatsapp = parsed.data;
|
|
6109
6289
|
writeConfig(accountDir, config);
|
|
6110
|
-
console.error(`${
|
|
6290
|
+
console.error(`${TAG17} removed admin phone=${normalized}`);
|
|
6111
6291
|
reloadManagerConfig(accountDir);
|
|
6112
6292
|
return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
|
|
6113
6293
|
} catch (err) {
|
|
6114
6294
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6115
|
-
console.error(`${
|
|
6295
|
+
console.error(`${TAG17} removeAdminPhone failed: ${msg}`);
|
|
6116
6296
|
return { ok: false, error: msg };
|
|
6117
6297
|
}
|
|
6118
6298
|
}
|
|
@@ -6127,6 +6307,139 @@ function readAdminPhones(accountDir) {
|
|
|
6127
6307
|
return [];
|
|
6128
6308
|
}
|
|
6129
6309
|
}
|
|
6310
|
+
function purgeNonSocketAdminPhones(accountDir, socketOwnerId) {
|
|
6311
|
+
try {
|
|
6312
|
+
const config = readConfig(accountDir);
|
|
6313
|
+
const wa = config.whatsapp;
|
|
6314
|
+
if (!wa || typeof wa !== "object") return { removed: [] };
|
|
6315
|
+
const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
|
|
6316
|
+
if (targetId === socketOwnerId) return { removed: [] };
|
|
6317
|
+
const adminPhones = Array.isArray(wa.adminPhones) ? wa.adminPhones.filter((p) => typeof p === "string") : [];
|
|
6318
|
+
if (adminPhones.length === 0) return { removed: [] };
|
|
6319
|
+
delete wa.adminPhones;
|
|
6320
|
+
writeConfig(accountDir, config);
|
|
6321
|
+
for (const phone of adminPhones) {
|
|
6322
|
+
console.error(`[admin-identity] op=purge-nonsocket-adminphone accountId=${targetId} phone=${phone} removed=true`);
|
|
6323
|
+
}
|
|
6324
|
+
return { removed: adminPhones };
|
|
6325
|
+
} catch (err) {
|
|
6326
|
+
console.error(`[admin-identity] purge-nonsocket-adminphone failed dir=${accountDir}: ${String(err)}`);
|
|
6327
|
+
return { removed: [] };
|
|
6328
|
+
}
|
|
6329
|
+
}
|
|
6330
|
+
function purgeNonSocketAdminPhonesAtBoot() {
|
|
6331
|
+
let socketOwner;
|
|
6332
|
+
try {
|
|
6333
|
+
socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
|
|
6334
|
+
} catch (err) {
|
|
6335
|
+
console.error(`[admin-identity] purge-nonsocket-adminphone skipped \u2014 cannot resolve socket owner: ${String(err)}`);
|
|
6336
|
+
return;
|
|
6337
|
+
}
|
|
6338
|
+
let accounts;
|
|
6339
|
+
try {
|
|
6340
|
+
accounts = listValidAccounts();
|
|
6341
|
+
} catch {
|
|
6342
|
+
return;
|
|
6343
|
+
}
|
|
6344
|
+
for (const acct of accounts) {
|
|
6345
|
+
purgeNonSocketAdminPhones(acct.accountDir, socketOwner);
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
function setAccountManager(accountDir, phone, subAccountId) {
|
|
6349
|
+
const normalized = phone.trim();
|
|
6350
|
+
if (!E164_PATTERN.test(normalized)) {
|
|
6351
|
+
return { ok: false, error: `Invalid phone format "${normalized}". Expected E.164 (e.g. +441234567890).` };
|
|
6352
|
+
}
|
|
6353
|
+
const sub = subAccountId.trim();
|
|
6354
|
+
if (!sub) {
|
|
6355
|
+
return { ok: false, error: "Missing sub-account id." };
|
|
6356
|
+
}
|
|
6357
|
+
let validIds;
|
|
6358
|
+
try {
|
|
6359
|
+
validIds = listValidAccounts().map((a) => a.accountId);
|
|
6360
|
+
} catch (err) {
|
|
6361
|
+
return { ok: false, error: `Could not read the account registry to validate the sub-account: ${err instanceof Error ? err.message : String(err)}. Try again.` };
|
|
6362
|
+
}
|
|
6363
|
+
if (!validIds.includes(sub)) {
|
|
6364
|
+
return { ok: false, error: `Sub-account "${sub}" is not in this install's account registry. Check the sub-account id and try again.` };
|
|
6365
|
+
}
|
|
6366
|
+
try {
|
|
6367
|
+
const config = readConfig(accountDir);
|
|
6368
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
6369
|
+
config.whatsapp = {};
|
|
6370
|
+
}
|
|
6371
|
+
const wa = config.whatsapp;
|
|
6372
|
+
const adminPhones = Array.isArray(wa.adminPhones) ? wa.adminPhones.filter((p) => typeof p === "string") : [];
|
|
6373
|
+
if (isAdminPhone(normalized, adminPhones)) {
|
|
6374
|
+
return {
|
|
6375
|
+
ok: false,
|
|
6376
|
+
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.`
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
if (!wa.accountManagers || typeof wa.accountManagers !== "object") {
|
|
6380
|
+
wa.accountManagers = {};
|
|
6381
|
+
}
|
|
6382
|
+
wa.accountManagers[normalized] = sub;
|
|
6383
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6384
|
+
if (!parsed.success) {
|
|
6385
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6386
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6387
|
+
}
|
|
6388
|
+
config.whatsapp = parsed.data;
|
|
6389
|
+
writeConfig(accountDir, config);
|
|
6390
|
+
console.error(`${TAG17} bound account manager phone=${normalized} managesAccount=${sub}`);
|
|
6391
|
+
reloadManagerConfig(accountDir);
|
|
6392
|
+
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.` };
|
|
6393
|
+
} catch (err) {
|
|
6394
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6395
|
+
console.error(`${TAG17} setAccountManager failed: ${msg}`);
|
|
6396
|
+
return { ok: false, error: msg };
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
function clearAccountManager(accountDir, phone) {
|
|
6400
|
+
const normalized = phone.trim();
|
|
6401
|
+
try {
|
|
6402
|
+
const config = readConfig(accountDir);
|
|
6403
|
+
const wa = config.whatsapp;
|
|
6404
|
+
if (!wa || typeof wa !== "object" || !wa.accountManagers || typeof wa.accountManagers !== "object") {
|
|
6405
|
+
return { ok: true, message: `No account-manager binding for ${normalized}.` };
|
|
6406
|
+
}
|
|
6407
|
+
const map = wa.accountManagers;
|
|
6408
|
+
const key = Object.keys(map).find((k) => phonesMatch(k, normalized));
|
|
6409
|
+
if (!key) {
|
|
6410
|
+
return { ok: true, message: `No account-manager binding for ${normalized}.` };
|
|
6411
|
+
}
|
|
6412
|
+
delete map[key];
|
|
6413
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6414
|
+
if (!parsed.success) {
|
|
6415
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6416
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6417
|
+
}
|
|
6418
|
+
config.whatsapp = parsed.data;
|
|
6419
|
+
writeConfig(accountDir, config);
|
|
6420
|
+
console.error(`${TAG17} cleared account manager phone=${normalized}`);
|
|
6421
|
+
reloadManagerConfig(accountDir);
|
|
6422
|
+
return { ok: true, message: `Cleared the account-manager binding for ${normalized}.` };
|
|
6423
|
+
} catch (err) {
|
|
6424
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6425
|
+
console.error(`${TAG17} clearAccountManager failed: ${msg}`);
|
|
6426
|
+
return { ok: false, error: msg };
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6429
|
+
function readAccountManagers(accountDir) {
|
|
6430
|
+
try {
|
|
6431
|
+
const config = readConfig(accountDir);
|
|
6432
|
+
const wa = config.whatsapp;
|
|
6433
|
+
if (!wa || typeof wa.accountManagers !== "object" || wa.accountManagers === null || Array.isArray(wa.accountManagers)) return {};
|
|
6434
|
+
const out = {};
|
|
6435
|
+
for (const [k, v] of Object.entries(wa.accountManagers)) {
|
|
6436
|
+
if (typeof v === "string") out[k] = v;
|
|
6437
|
+
}
|
|
6438
|
+
return out;
|
|
6439
|
+
} catch {
|
|
6440
|
+
return {};
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6130
6443
|
function setPublicAgent(accountDir, slug) {
|
|
6131
6444
|
const trimmed = slug.trim();
|
|
6132
6445
|
if (!trimmed) {
|
|
@@ -6150,12 +6463,12 @@ function setPublicAgent(accountDir, slug) {
|
|
|
6150
6463
|
}
|
|
6151
6464
|
config.whatsapp = parsed.data;
|
|
6152
6465
|
writeConfig(accountDir, config);
|
|
6153
|
-
console.error(`${
|
|
6466
|
+
console.error(`${TAG17} publicAgent set to ${trimmed}`);
|
|
6154
6467
|
reloadManagerConfig(accountDir);
|
|
6155
6468
|
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
6156
6469
|
} catch (err) {
|
|
6157
6470
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6158
|
-
console.error(`${
|
|
6471
|
+
console.error(`${TAG17} setPublicAgent failed: ${msg}`);
|
|
6159
6472
|
return { ok: false, error: msg };
|
|
6160
6473
|
}
|
|
6161
6474
|
}
|
|
@@ -6196,17 +6509,17 @@ function updateConfig(accountDir, fields) {
|
|
|
6196
6509
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6197
6510
|
if (!parsed.success) {
|
|
6198
6511
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6199
|
-
console.error(`${
|
|
6512
|
+
console.error(`${TAG17} update validation failed: ${msg}`);
|
|
6200
6513
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6201
6514
|
}
|
|
6202
6515
|
config.whatsapp = parsed.data;
|
|
6203
6516
|
writeConfig(accountDir, config);
|
|
6204
|
-
console.error(`${
|
|
6517
|
+
console.error(`${TAG17} updated fields=[${fieldNames.join(",")}]`);
|
|
6205
6518
|
reloadManagerConfig(accountDir);
|
|
6206
6519
|
return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
|
|
6207
6520
|
} catch (err) {
|
|
6208
6521
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6209
|
-
console.error(`${
|
|
6522
|
+
console.error(`${TAG17} updateConfig failed: ${msg}`);
|
|
6210
6523
|
return { ok: false, error: msg };
|
|
6211
6524
|
}
|
|
6212
6525
|
}
|
|
@@ -6243,17 +6556,17 @@ function migrateRemovedConfigKeys(accountDir) {
|
|
|
6243
6556
|
}
|
|
6244
6557
|
writeConfig(accountDir, config);
|
|
6245
6558
|
console.error(
|
|
6246
|
-
`${
|
|
6559
|
+
`${TAG17} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
|
|
6247
6560
|
);
|
|
6248
6561
|
return { dropped: result.droppedKeys };
|
|
6249
6562
|
} catch (err) {
|
|
6250
|
-
console.error(`${
|
|
6563
|
+
console.error(`${TAG17} migration failed: ${String(err)}`);
|
|
6251
6564
|
return { dropped: [] };
|
|
6252
6565
|
}
|
|
6253
6566
|
}
|
|
6254
6567
|
|
|
6255
6568
|
// app/lib/whatsapp/login.ts
|
|
6256
|
-
var
|
|
6569
|
+
var TAG18 = "[whatsapp:login]";
|
|
6257
6570
|
function maskPhone(digits) {
|
|
6258
6571
|
if (digits.length <= 4) return "*".repeat(digits.length);
|
|
6259
6572
|
return digits.slice(0, 2) + "*".repeat(digits.length - 4) + digits.slice(-2);
|
|
@@ -6264,7 +6577,7 @@ function closeSocket(sock) {
|
|
|
6264
6577
|
try {
|
|
6265
6578
|
sock.ws?.close?.();
|
|
6266
6579
|
} catch (err) {
|
|
6267
|
-
console.warn(`${
|
|
6580
|
+
console.warn(`${TAG18} socket close error during cleanup: ${String(err)}`);
|
|
6268
6581
|
}
|
|
6269
6582
|
}
|
|
6270
6583
|
function resetActiveLogin(accountId) {
|
|
@@ -6291,18 +6604,18 @@ async function runPostPairing(login) {
|
|
|
6291
6604
|
const masked = selfPhone ? `${selfPhone.slice(0, 4)}***` : "null";
|
|
6292
6605
|
console.error(`[whatsapp-persist] wa-persist-on-connect account=${login.accountId} phone=${masked}`);
|
|
6293
6606
|
} else {
|
|
6294
|
-
console.error(`${
|
|
6607
|
+
console.error(`${TAG18} wa-persist-on-connect FAILED account=${login.accountId}: ${result.error}`);
|
|
6295
6608
|
}
|
|
6296
6609
|
} catch (err) {
|
|
6297
|
-
console.error(`${
|
|
6610
|
+
console.error(`${TAG18} wa-persist-on-connect error account=${login.accountId}: ${String(err)}`);
|
|
6298
6611
|
}
|
|
6299
6612
|
} else {
|
|
6300
|
-
console.error(`${
|
|
6613
|
+
console.error(`${TAG18} wa-persist-on-connect skipped \u2014 no accountDir account=${login.accountId}`);
|
|
6301
6614
|
}
|
|
6302
6615
|
try {
|
|
6303
6616
|
await registerLoginSocket(login.accountId, login.sock, login.authDir);
|
|
6304
6617
|
} catch (err) {
|
|
6305
|
-
console.error(`${
|
|
6618
|
+
console.error(`${TAG18} registerLoginSocket failed account=${login.accountId}: ${String(err)}`);
|
|
6306
6619
|
}
|
|
6307
6620
|
}
|
|
6308
6621
|
async function loginConnectionLoop(accountId, login) {
|
|
@@ -6311,7 +6624,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6311
6624
|
const logTerminal = (outcome, errMsg) => {
|
|
6312
6625
|
const fc = credsCensus(login.authDir);
|
|
6313
6626
|
console.error(
|
|
6314
|
-
`${
|
|
6627
|
+
`${TAG18} op=terminal cid=${cidShort} outcome=${outcome} credsFinal={registered=${fc.registered},account=${fc.hasAccountSignature},me=${fc.e164 ?? "null"}}` + (errMsg ? ` error="${errMsg}"` : "")
|
|
6315
6628
|
);
|
|
6316
6629
|
};
|
|
6317
6630
|
while (true) {
|
|
@@ -6321,7 +6634,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6321
6634
|
if (current?.id === login.id) {
|
|
6322
6635
|
await runPostPairing(current);
|
|
6323
6636
|
current.connected = true;
|
|
6324
|
-
console.error(`${
|
|
6637
|
+
console.error(`${TAG18} loginConnectionLoop: connected account=${accountId} attempt=${attempt} configPersisted=${current.configPersisted}`);
|
|
6325
6638
|
logTerminal("connected");
|
|
6326
6639
|
}
|
|
6327
6640
|
return;
|
|
@@ -6333,7 +6646,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6333
6646
|
if (!preRetryCensus.hasAccountSignature) {
|
|
6334
6647
|
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
6648
|
console.error(
|
|
6336
|
-
`${
|
|
6649
|
+
`${TAG18} op=pairing-incomplete cid=${cidShort} closeKind=${closeKind(getStatusCode(err), false)} reason=no-account-signature`
|
|
6337
6650
|
);
|
|
6338
6651
|
return;
|
|
6339
6652
|
}
|
|
@@ -6341,7 +6654,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6341
6654
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6342
6655
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6343
6656
|
console.error(
|
|
6344
|
-
`${
|
|
6657
|
+
`${TAG18} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
6345
6658
|
);
|
|
6346
6659
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
6347
6660
|
} else {
|
|
@@ -6354,7 +6667,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6354
6667
|
attempt++;
|
|
6355
6668
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
6356
6669
|
console.error(
|
|
6357
|
-
`${
|
|
6670
|
+
`${TAG18} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
6358
6671
|
);
|
|
6359
6672
|
closeSocket(current.sock);
|
|
6360
6673
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -6362,14 +6675,14 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6362
6675
|
if (afterDelay?.id !== login.id) return;
|
|
6363
6676
|
const rc = credsCensus(login.authDir);
|
|
6364
6677
|
console.error(
|
|
6365
|
-
`${
|
|
6678
|
+
`${TAG18} op=reconnect cid=${cidShort} attempt=${attempt}/${LOGIN_MAX_RECONNECTS} withRegistered=${rc.registered} withAccount=${rc.hasAccountSignature}`
|
|
6366
6679
|
);
|
|
6367
6680
|
try {
|
|
6368
6681
|
const newSock = await createWaSocket({ authDir: login.authDir, correlationId: cidShort, account: accountId });
|
|
6369
6682
|
current.sock = newSock;
|
|
6370
6683
|
} catch (sockErr) {
|
|
6371
6684
|
console.error(
|
|
6372
|
-
`${
|
|
6685
|
+
`${TAG18} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
6373
6686
|
);
|
|
6374
6687
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
6375
6688
|
logTerminal("error", current.error);
|
|
@@ -6386,7 +6699,7 @@ async function startLogin(opts) {
|
|
|
6386
6699
|
const hasAuth = await authExists(authDir);
|
|
6387
6700
|
const selfId = readSelfId(authDir);
|
|
6388
6701
|
console.error(
|
|
6389
|
-
`${
|
|
6702
|
+
`${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
6703
|
);
|
|
6391
6704
|
if (hasAuth && !force) {
|
|
6392
6705
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -6401,7 +6714,7 @@ async function startLogin(opts) {
|
|
|
6401
6714
|
}
|
|
6402
6715
|
const existing = activeLogins.get(accountId);
|
|
6403
6716
|
if (existing && isLoginFresh(existing) && existing.pairingCode && !force) {
|
|
6404
|
-
console.error(`${
|
|
6717
|
+
console.error(`${TAG18} startLogin account=${accountId} guard: returning existing pairing code (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
6405
6718
|
return {
|
|
6406
6719
|
pairingCode: existing.pairingCode,
|
|
6407
6720
|
phone: existing.phone,
|
|
@@ -6409,7 +6722,7 @@ async function startLogin(opts) {
|
|
|
6409
6722
|
};
|
|
6410
6723
|
}
|
|
6411
6724
|
if (existing) {
|
|
6412
|
-
console.error(`${
|
|
6725
|
+
console.error(`${TAG18} startLogin account=${accountId} ${force ? "force override" : "stale/no-code"}, resetting active login`);
|
|
6413
6726
|
}
|
|
6414
6727
|
resetActiveLogin(accountId);
|
|
6415
6728
|
await clearAuth(authDir);
|
|
@@ -6440,7 +6753,7 @@ async function startLogin(opts) {
|
|
|
6440
6753
|
if (requested) return;
|
|
6441
6754
|
requested = true;
|
|
6442
6755
|
console.error(
|
|
6443
|
-
`${
|
|
6756
|
+
`${TAG18} op=pairing-request cid=${cid} account=${accountId} qrRef=#1 sinceOpenMs=${socketOpenTs ? Date.now() - socketOpenTs : 0}`
|
|
6444
6757
|
);
|
|
6445
6758
|
void sock.requestPairingCode(digits).then((code) => {
|
|
6446
6759
|
clearTimeout(codeTimer);
|
|
@@ -6449,7 +6762,7 @@ async function startLogin(opts) {
|
|
|
6449
6762
|
if (current?.id !== login.id) return;
|
|
6450
6763
|
if (!current.pairingCode) current.pairingCode = code;
|
|
6451
6764
|
console.error(
|
|
6452
|
-
`${
|
|
6765
|
+
`${TAG18} op=code-issued cid=${cid} account=${accountId} phone=${maskPhone(digits)} codeLen=${code.length} sinceOpenMs=${socketOpenTs ? codeIssuedTs - socketOpenTs : 0}`
|
|
6453
6766
|
);
|
|
6454
6767
|
resolveCode?.(code);
|
|
6455
6768
|
}).catch((err) => {
|
|
@@ -6480,7 +6793,7 @@ async function startLogin(opts) {
|
|
|
6480
6793
|
const ttlTimer = setTimeout(() => {
|
|
6481
6794
|
const current = activeLogins.get(accountId);
|
|
6482
6795
|
if (current?.id === login.id && !current.connected) {
|
|
6483
|
-
console.error(`${
|
|
6796
|
+
console.error(`${TAG18} pairing-window-elapsed account=${accountId} (ttl sweep)`);
|
|
6484
6797
|
resetActiveLogin(accountId);
|
|
6485
6798
|
}
|
|
6486
6799
|
}, ACTIVE_LOGIN_TTL_MS);
|
|
@@ -6488,7 +6801,7 @@ async function startLogin(opts) {
|
|
|
6488
6801
|
ttlTimer.unref();
|
|
6489
6802
|
}
|
|
6490
6803
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
6491
|
-
console.error(`${
|
|
6804
|
+
console.error(`${TAG18} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
6492
6805
|
const current = activeLogins.get(accountId);
|
|
6493
6806
|
if (current?.id === login.id) {
|
|
6494
6807
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -6513,13 +6826,13 @@ async function waitForLogin(opts) {
|
|
|
6513
6826
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
6514
6827
|
const login = activeLogins.get(accountId);
|
|
6515
6828
|
console.error(
|
|
6516
|
-
`${
|
|
6829
|
+
`${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
6830
|
);
|
|
6518
6831
|
if (!login) {
|
|
6519
6832
|
return { connected: false, message: "No active WhatsApp login in progress.", configPersisted: false };
|
|
6520
6833
|
}
|
|
6521
6834
|
if (!isLoginFresh(login)) {
|
|
6522
|
-
console.error(`${
|
|
6835
|
+
console.error(`${TAG18} pairing-window-elapsed account=${accountId}`);
|
|
6523
6836
|
resetActiveLogin(accountId);
|
|
6524
6837
|
return { connected: false, message: "The pairing window expired. Ask me to generate a new code.", configPersisted: false };
|
|
6525
6838
|
}
|
|
@@ -6527,8 +6840,8 @@ async function waitForLogin(opts) {
|
|
|
6527
6840
|
while (Date.now() < deadline) {
|
|
6528
6841
|
if (login.connected) {
|
|
6529
6842
|
const selfId = readSelfId(login.authDir);
|
|
6530
|
-
console.error(`${
|
|
6531
|
-
console.error(`${
|
|
6843
|
+
console.error(`${TAG18} pairing-connected account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
6844
|
+
console.error(`${TAG18} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"} configPersisted=${login.configPersisted}`);
|
|
6532
6845
|
const configPersisted = login.configPersisted;
|
|
6533
6846
|
activeLogins.delete(accountId);
|
|
6534
6847
|
return {
|
|
@@ -6546,7 +6859,7 @@ async function waitForLogin(opts) {
|
|
|
6546
6859
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
6547
6860
|
}
|
|
6548
6861
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
6549
|
-
console.error(`${
|
|
6862
|
+
console.error(`${TAG18} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
|
|
6550
6863
|
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
6864
|
}
|
|
6552
6865
|
function listActiveLoginAccountIds() {
|
|
@@ -6560,8 +6873,8 @@ function listActiveLoginAccountIds() {
|
|
|
6560
6873
|
// app/lib/whatsapp/outbound/send-document.ts
|
|
6561
6874
|
import { realpathSync as realpathSync3 } from "fs";
|
|
6562
6875
|
import { readFile, stat as stat2 } from "fs/promises";
|
|
6563
|
-
import { resolve as resolve9, basename as
|
|
6564
|
-
var
|
|
6876
|
+
import { resolve as resolve9, basename as basename3 } from "path";
|
|
6877
|
+
var TAG19 = "[whatsapp:outbound]";
|
|
6565
6878
|
var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
|
|
6566
6879
|
var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
6567
6880
|
var lastRouteDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
@@ -6595,16 +6908,16 @@ async function sendWhatsAppDocument(input) {
|
|
|
6595
6908
|
const accountResolved = realpathSync3(accountDir);
|
|
6596
6909
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
6597
6910
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
6598
|
-
console.error(`${
|
|
6911
|
+
console.error(`${TAG19} document REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
6599
6912
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
6600
6913
|
}
|
|
6601
6914
|
} catch (err) {
|
|
6602
6915
|
const code = err.code;
|
|
6603
6916
|
if (code === "ENOENT") {
|
|
6604
|
-
console.error(`${
|
|
6917
|
+
console.error(`${TAG19} document ENOENT path=${filePath}`);
|
|
6605
6918
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
6606
6919
|
}
|
|
6607
|
-
console.error(`${
|
|
6920
|
+
console.error(`${TAG19} document path error: ${String(err)}`);
|
|
6608
6921
|
return { ok: false, status: 500, error: String(err) };
|
|
6609
6922
|
}
|
|
6610
6923
|
const fileStat = await stat2(resolvedPath);
|
|
@@ -6615,11 +6928,11 @@ async function sendWhatsAppDocument(input) {
|
|
|
6615
6928
|
error: `File exceeds 100 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
|
|
6616
6929
|
};
|
|
6617
6930
|
}
|
|
6618
|
-
const filename =
|
|
6931
|
+
const filename = basename3(resolvedPath);
|
|
6619
6932
|
const jid = normalizeJid2(to);
|
|
6620
6933
|
const sock = getSocket(accountId);
|
|
6621
6934
|
if (!sock) {
|
|
6622
|
-
console.error(`${
|
|
6935
|
+
console.error(`${TAG19} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=false reason=not-connected`);
|
|
6623
6936
|
return { ok: false, status: 503, error: `WhatsApp account "${accountId}" is not connected` };
|
|
6624
6937
|
}
|
|
6625
6938
|
const buffer = Buffer.from(await readFile(resolvedPath));
|
|
@@ -6631,7 +6944,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
6631
6944
|
{ accountId }
|
|
6632
6945
|
);
|
|
6633
6946
|
console.error(
|
|
6634
|
-
`${
|
|
6947
|
+
`${TAG19} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
6635
6948
|
);
|
|
6636
6949
|
if (result.success) {
|
|
6637
6950
|
recordDocumentOutbound(to);
|
|
@@ -6751,7 +7064,7 @@ function serializeWhatsAppSchema() {
|
|
|
6751
7064
|
// app/lib/whatsapp/status-reconcile.ts
|
|
6752
7065
|
import { readdirSync as readdirSync3 } from "fs";
|
|
6753
7066
|
import { join as join9 } from "path";
|
|
6754
|
-
var
|
|
7067
|
+
var TAG20 = "[whatsapp:reconcile]";
|
|
6755
7068
|
var HALF_REGISTERED_MIN_AGE_MS = 5 * 6e4;
|
|
6756
7069
|
function listCredsAccountIds(credsRoot) {
|
|
6757
7070
|
try {
|
|
@@ -6777,14 +7090,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6777
7090
|
const ageMs = getAuthAgeMs(authDir);
|
|
6778
7091
|
const abandoned = !inFlight && ageMs !== null && ageMs >= HALF_REGISTERED_MIN_AGE_MS;
|
|
6779
7092
|
if (!abandoned) {
|
|
6780
|
-
console.error(`${
|
|
7093
|
+
console.error(`${TAG20} op=half-registered account=${accountId} registered=true hasAccount=false inFlight=${inFlight} ageMs=${ageMs ?? "null"}`);
|
|
6781
7094
|
entries.push({ accountId, connected: false, linkedUnconfigured: true });
|
|
6782
7095
|
continue;
|
|
6783
7096
|
}
|
|
6784
|
-
console.error(`${
|
|
7097
|
+
console.error(`${TAG20} op=discard-half-registered account=${accountId} registered=true hasAccount=false ageMs=${ageMs}`);
|
|
6785
7098
|
const removed = discardAuthDirSync(authDir);
|
|
6786
7099
|
if (!removed) {
|
|
6787
|
-
console.error(`${
|
|
7100
|
+
console.error(`${TAG20} op=discard-half-registered-failed account=${accountId} \u2014 residue persists`);
|
|
6788
7101
|
entries.push({ accountId, connected: false, linkedUnconfigured: true });
|
|
6789
7102
|
}
|
|
6790
7103
|
continue;
|
|
@@ -6792,14 +7105,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6792
7105
|
const selfPhone = readSelfId(authDir).e164 ?? void 0;
|
|
6793
7106
|
const configured = accountDir ? isAccountConfigured(accountDir, accountId) : false;
|
|
6794
7107
|
console.error(
|
|
6795
|
-
`${
|
|
7108
|
+
`${TAG20} wa-link-unconfigured account=${accountId} creds=present config=${configured ? "present" : "absent"}`
|
|
6796
7109
|
);
|
|
6797
7110
|
if (!configured && accountDir) {
|
|
6798
7111
|
const result = persistAfterPairing(accountDir, accountId, selfPhone ?? null);
|
|
6799
7112
|
if (result.ok) {
|
|
6800
|
-
console.error(`${
|
|
7113
|
+
console.error(`${TAG20} self-healed account=${accountId}`);
|
|
6801
7114
|
} else {
|
|
6802
|
-
console.error(`${
|
|
7115
|
+
console.error(`${TAG20} self-heal FAILED account=${accountId}: ${result.error}`);
|
|
6803
7116
|
}
|
|
6804
7117
|
}
|
|
6805
7118
|
entries.push({ accountId, selfPhone, connected: false, linkedUnconfigured: !configured });
|
|
@@ -6808,7 +7121,7 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6808
7121
|
}
|
|
6809
7122
|
|
|
6810
7123
|
// server/routes/whatsapp.ts
|
|
6811
|
-
var
|
|
7124
|
+
var TAG21 = "[whatsapp:api]";
|
|
6812
7125
|
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
6813
7126
|
var app2 = new Hono();
|
|
6814
7127
|
app2.get("/status", (c) => {
|
|
@@ -6821,10 +7134,10 @@ app2.get("/status", (c) => {
|
|
|
6821
7134
|
const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds, activeLoginAccountIds: activeLoginIds });
|
|
6822
7135
|
const accounts = [...live, ...reconciled];
|
|
6823
7136
|
const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
|
|
6824
|
-
console.error(`${
|
|
7137
|
+
console.error(`${TAG21} status accounts=${accounts.length} [${summary}]`);
|
|
6825
7138
|
return c.json({ accounts });
|
|
6826
7139
|
} catch (err) {
|
|
6827
|
-
console.error(`${
|
|
7140
|
+
console.error(`${TAG21} status error: ${String(err)}`);
|
|
6828
7141
|
return c.json({ error: String(err) }, 500);
|
|
6829
7142
|
}
|
|
6830
7143
|
});
|
|
@@ -6840,10 +7153,10 @@ app2.post("/login/start", async (c) => {
|
|
|
6840
7153
|
const authDir = join10(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
6841
7154
|
const accountDir = resolveAccount()?.accountDir ?? null;
|
|
6842
7155
|
const result = await startLogin({ accountId, authDir, phone, accountDir, force });
|
|
6843
|
-
console.error(`${
|
|
7156
|
+
console.error(`${TAG21} login/start result account=${accountId} hasCode=${!!result.pairingCode}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
6844
7157
|
return c.json(result);
|
|
6845
7158
|
} catch (err) {
|
|
6846
|
-
console.error(`${
|
|
7159
|
+
console.error(`${TAG21} login/start error: ${String(err)}`);
|
|
6847
7160
|
return c.json({ error: String(err) }, 500);
|
|
6848
7161
|
}
|
|
6849
7162
|
});
|
|
@@ -6853,7 +7166,7 @@ app2.post("/login/wait", async (c) => {
|
|
|
6853
7166
|
const accountId = validateAccountId(body.accountId);
|
|
6854
7167
|
const timeoutMs = body.timeoutMs ?? 6e4;
|
|
6855
7168
|
const result = await waitForLogin({ accountId, timeoutMs });
|
|
6856
|
-
console.error(`${
|
|
7169
|
+
console.error(`${TAG21} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
|
|
6857
7170
|
return c.json({
|
|
6858
7171
|
connected: result.connected,
|
|
6859
7172
|
message: result.message,
|
|
@@ -6861,7 +7174,7 @@ app2.post("/login/wait", async (c) => {
|
|
|
6861
7174
|
configPersisted: result.configPersisted
|
|
6862
7175
|
});
|
|
6863
7176
|
} catch (err) {
|
|
6864
|
-
console.error(`${
|
|
7177
|
+
console.error(`${TAG21} login/wait error: ${String(err)}`);
|
|
6865
7178
|
return c.json({ error: String(err) }, 500);
|
|
6866
7179
|
}
|
|
6867
7180
|
});
|
|
@@ -6872,7 +7185,7 @@ app2.post("/disconnect", async (c) => {
|
|
|
6872
7185
|
await stopConnection(accountId);
|
|
6873
7186
|
return c.json({ disconnected: true, accountId });
|
|
6874
7187
|
} catch (err) {
|
|
6875
|
-
console.error(`${
|
|
7188
|
+
console.error(`${TAG21} disconnect error: ${String(err)}`);
|
|
6876
7189
|
return c.json({ error: String(err) }, 500);
|
|
6877
7190
|
}
|
|
6878
7191
|
});
|
|
@@ -6883,14 +7196,14 @@ app2.post("/reconnect", async (c) => {
|
|
|
6883
7196
|
await startConnection(accountId);
|
|
6884
7197
|
return c.json({ reconnecting: true, accountId });
|
|
6885
7198
|
} catch (err) {
|
|
6886
|
-
console.error(`${
|
|
7199
|
+
console.error(`${TAG21} reconnect error: ${String(err)}`);
|
|
6887
7200
|
return c.json({ error: String(err) }, 500);
|
|
6888
7201
|
}
|
|
6889
7202
|
});
|
|
6890
7203
|
app2.post("/config", async (c) => {
|
|
6891
7204
|
try {
|
|
6892
7205
|
const body = await c.req.json().catch(() => ({}));
|
|
6893
|
-
const { action, phone, slug, fields, accountId } = body;
|
|
7206
|
+
const { action, phone, slug, fields, accountId, managesAccount } = body;
|
|
6894
7207
|
if (!action || typeof action !== "string") {
|
|
6895
7208
|
return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
|
|
6896
7209
|
}
|
|
@@ -6904,7 +7217,8 @@ app2.post("/config", async (c) => {
|
|
|
6904
7217
|
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
6905
7218
|
}
|
|
6906
7219
|
const result = addAdminPhone(account.accountDir, phone);
|
|
6907
|
-
|
|
7220
|
+
const reasonTail = !result.ok && result.reason ? ` reason=${result.reason}` : "";
|
|
7221
|
+
console.error(`${TAG21} config action=add-admin-phone accountId=${account.accountId} phone=${phone} ok=${result.ok}${reasonTail}`);
|
|
6908
7222
|
return c.json(result, result.ok ? 200 : 400);
|
|
6909
7223
|
}
|
|
6910
7224
|
case "remove-admin-phone": {
|
|
@@ -6912,26 +7226,50 @@ app2.post("/config", async (c) => {
|
|
|
6912
7226
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
6913
7227
|
}
|
|
6914
7228
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
6915
|
-
console.error(`${
|
|
7229
|
+
console.error(`${TAG21} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
6916
7230
|
return c.json(result, result.ok ? 200 : 400);
|
|
6917
7231
|
}
|
|
6918
7232
|
case "list-admin-phones": {
|
|
6919
7233
|
const phones = readAdminPhones(account.accountDir);
|
|
6920
|
-
console.error(`${
|
|
7234
|
+
console.error(`${TAG21} config action=list-admin-phones count=${phones.length}`);
|
|
6921
7235
|
return c.json({ ok: true, phones });
|
|
6922
7236
|
}
|
|
7237
|
+
case "add-account-manager": {
|
|
7238
|
+
if (!phone || typeof phone !== "string") {
|
|
7239
|
+
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
7240
|
+
}
|
|
7241
|
+
if (!managesAccount || typeof managesAccount !== "string") {
|
|
7242
|
+
return c.json({ ok: false, error: 'Missing required field "managesAccount" (the sub-account UUID this phone should manage).' }, 400);
|
|
7243
|
+
}
|
|
7244
|
+
const result = setAccountManager(account.accountDir, phone, managesAccount);
|
|
7245
|
+
console.error(`${TAG21} config action=add-account-manager phone=${phone} managesAccount=${managesAccount} ok=${result.ok}`);
|
|
7246
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
7247
|
+
}
|
|
7248
|
+
case "remove-account-manager": {
|
|
7249
|
+
if (!phone || typeof phone !== "string") {
|
|
7250
|
+
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
7251
|
+
}
|
|
7252
|
+
const result = clearAccountManager(account.accountDir, phone);
|
|
7253
|
+
console.error(`${TAG21} config action=remove-account-manager phone=${phone} ok=${result.ok}`);
|
|
7254
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
7255
|
+
}
|
|
7256
|
+
case "list-account-managers": {
|
|
7257
|
+
const accountManagers = readAccountManagers(account.accountDir);
|
|
7258
|
+
console.error(`${TAG21} config action=list-account-managers count=${Object.keys(accountManagers).length}`);
|
|
7259
|
+
return c.json({ ok: true, accountManagers });
|
|
7260
|
+
}
|
|
6923
7261
|
case "set-public-agent": {
|
|
6924
7262
|
if (!slug || typeof slug !== "string") {
|
|
6925
7263
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
6926
7264
|
}
|
|
6927
7265
|
const result = setPublicAgent(account.accountDir, slug);
|
|
6928
|
-
console.error(`${
|
|
7266
|
+
console.error(`${TAG21} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
6929
7267
|
return c.json(result, result.ok ? 200 : 400);
|
|
6930
7268
|
}
|
|
6931
7269
|
case "get-public-agent": {
|
|
6932
7270
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
6933
7271
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
|
|
6934
|
-
console.error(`${
|
|
7272
|
+
console.error(`${TAG21} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
6935
7273
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
6936
7274
|
}
|
|
6937
7275
|
case "list-public-agents": {
|
|
@@ -6948,26 +7286,26 @@ app2.post("/config", async (c) => {
|
|
|
6948
7286
|
const config = JSON.parse(readFileSync11(configPath2, "utf-8"));
|
|
6949
7287
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
6950
7288
|
} catch {
|
|
6951
|
-
console.error(`${
|
|
7289
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
6952
7290
|
}
|
|
6953
7291
|
}
|
|
6954
7292
|
} catch (err) {
|
|
6955
|
-
console.error(`${
|
|
7293
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
6956
7294
|
}
|
|
6957
7295
|
}
|
|
6958
|
-
console.error(`${
|
|
7296
|
+
console.error(`${TAG21} config action=list-public-agents count=${agents.length}`);
|
|
6959
7297
|
return c.json({ ok: true, agents });
|
|
6960
7298
|
}
|
|
6961
7299
|
case "schema": {
|
|
6962
7300
|
const text = serializeWhatsAppSchema();
|
|
6963
|
-
console.error(`${
|
|
7301
|
+
console.error(`${TAG21} config action=schema`);
|
|
6964
7302
|
return c.json({ ok: true, text });
|
|
6965
7303
|
}
|
|
6966
7304
|
case "list-groups": {
|
|
6967
7305
|
const groupAccountId = accountId ?? "default";
|
|
6968
7306
|
const sock = getSocket(groupAccountId);
|
|
6969
7307
|
if (!sock) {
|
|
6970
|
-
console.error(`${
|
|
7308
|
+
console.error(`${TAG21} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
6971
7309
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
6972
7310
|
}
|
|
6973
7311
|
try {
|
|
@@ -6977,10 +7315,10 @@ app2.post("/config", async (c) => {
|
|
|
6977
7315
|
name: g.subject ?? g.id,
|
|
6978
7316
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
6979
7317
|
}));
|
|
6980
|
-
console.error(`${
|
|
7318
|
+
console.error(`${TAG21} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
6981
7319
|
return c.json({ ok: true, groups });
|
|
6982
7320
|
} catch (err) {
|
|
6983
|
-
console.error(`${
|
|
7321
|
+
console.error(`${TAG21} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
6984
7322
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
6985
7323
|
}
|
|
6986
7324
|
}
|
|
@@ -6990,22 +7328,22 @@ app2.post("/config", async (c) => {
|
|
|
6990
7328
|
}
|
|
6991
7329
|
const result = updateConfig(account.accountDir, fields);
|
|
6992
7330
|
const fieldNames = Object.keys(fields);
|
|
6993
|
-
console.error(`${
|
|
7331
|
+
console.error(`${TAG21} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
6994
7332
|
return c.json(result, result.ok ? 200 : 400);
|
|
6995
7333
|
}
|
|
6996
7334
|
case "get-config": {
|
|
6997
7335
|
const waConfig = getConfig(account.accountDir);
|
|
6998
|
-
console.error(`${
|
|
7336
|
+
console.error(`${TAG21} config action=get-config`);
|
|
6999
7337
|
return c.json({ ok: true, config: waConfig });
|
|
7000
7338
|
}
|
|
7001
7339
|
default:
|
|
7002
7340
|
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.` },
|
|
7341
|
+
{ 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
7342
|
400
|
|
7005
7343
|
);
|
|
7006
7344
|
}
|
|
7007
7345
|
} catch (err) {
|
|
7008
|
-
console.error(`${
|
|
7346
|
+
console.error(`${TAG21} config error: ${String(err)}`);
|
|
7009
7347
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
7010
7348
|
}
|
|
7011
7349
|
});
|
|
@@ -7026,7 +7364,7 @@ app2.post("/send-document", async (c) => {
|
|
|
7026
7364
|
recordRouteDocumentOutbound(to, filePath);
|
|
7027
7365
|
return c.json({ success: true, messageId: result.messageId });
|
|
7028
7366
|
} catch (err) {
|
|
7029
|
-
console.error(`${
|
|
7367
|
+
console.error(`${TAG21} send-document error: ${String(err)}`);
|
|
7030
7368
|
return c.json({ error: String(err) }, 500);
|
|
7031
7369
|
}
|
|
7032
7370
|
});
|
|
@@ -7063,7 +7401,7 @@ app2.post("/send-admin", async (c) => {
|
|
|
7063
7401
|
}
|
|
7064
7402
|
return c.json({ success: true, messageId: result.messageId });
|
|
7065
7403
|
} catch (err) {
|
|
7066
|
-
console.error(`${
|
|
7404
|
+
console.error(`${TAG21} send-admin error: ${String(err)}`);
|
|
7067
7405
|
return c.json({ error: String(err) }, 500);
|
|
7068
7406
|
}
|
|
7069
7407
|
});
|
|
@@ -7073,11 +7411,11 @@ app2.get("/activity", (c) => {
|
|
|
7073
7411
|
const result = getChannelActivity(accountId);
|
|
7074
7412
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
7075
7413
|
console.error(
|
|
7076
|
-
`${
|
|
7414
|
+
`${TAG21} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
7077
7415
|
);
|
|
7078
7416
|
return c.json(result);
|
|
7079
7417
|
} catch (err) {
|
|
7080
|
-
console.error(`${
|
|
7418
|
+
console.error(`${TAG21} activity error: ${String(err)}`);
|
|
7081
7419
|
return c.json({ error: String(err) }, 500);
|
|
7082
7420
|
}
|
|
7083
7421
|
});
|
|
@@ -7096,10 +7434,10 @@ app2.get("/conversations", (c) => {
|
|
|
7096
7434
|
};
|
|
7097
7435
|
});
|
|
7098
7436
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
7099
|
-
console.error(`${
|
|
7437
|
+
console.error(`${TAG21} conversations account=${accountId} count=${conversations.length}`);
|
|
7100
7438
|
return c.json({ conversations });
|
|
7101
7439
|
} catch (err) {
|
|
7102
|
-
console.error(`${
|
|
7440
|
+
console.error(`${TAG21} conversations error: ${String(err)}`);
|
|
7103
7441
|
return c.json({ error: String(err) }, 500);
|
|
7104
7442
|
}
|
|
7105
7443
|
});
|
|
@@ -7114,10 +7452,10 @@ app2.get("/messages", (c) => {
|
|
|
7114
7452
|
const limit = limitParam ? parseInt(limitParam, 10) : void 0;
|
|
7115
7453
|
const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
|
|
7116
7454
|
const messages = getMessages(accountId, jid, effectiveLimit);
|
|
7117
|
-
console.error(`${
|
|
7455
|
+
console.error(`${TAG21} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
|
|
7118
7456
|
return c.json({ messages });
|
|
7119
7457
|
} catch (err) {
|
|
7120
|
-
console.error(`${
|
|
7458
|
+
console.error(`${TAG21} messages error: ${String(err)}`);
|
|
7121
7459
|
return c.json({ error: String(err) }, 500);
|
|
7122
7460
|
}
|
|
7123
7461
|
});
|
|
@@ -7198,7 +7536,7 @@ app2.get("/conversation-graph-state", async (c) => {
|
|
|
7198
7536
|
ms
|
|
7199
7537
|
});
|
|
7200
7538
|
} catch (err) {
|
|
7201
|
-
console.error(`${
|
|
7539
|
+
console.error(`${TAG21} conversation-graph-state error: ${String(err)}`);
|
|
7202
7540
|
return c.json({ error: String(err) }, 500);
|
|
7203
7541
|
}
|
|
7204
7542
|
});
|
|
@@ -7210,12 +7548,12 @@ app2.get("/group-info", async (c) => {
|
|
|
7210
7548
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
7211
7549
|
}
|
|
7212
7550
|
if (!isGroupJid(jid)) {
|
|
7213
|
-
console.error(`${
|
|
7551
|
+
console.error(`${TAG21} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
7214
7552
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
7215
7553
|
}
|
|
7216
7554
|
const sock = getSocket(accountId);
|
|
7217
7555
|
if (!sock) {
|
|
7218
|
-
console.error(`${
|
|
7556
|
+
console.error(`${TAG21} group-info error="not connected" account=${accountId}`);
|
|
7219
7557
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
7220
7558
|
}
|
|
7221
7559
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -7228,10 +7566,10 @@ app2.get("/group-info", async (c) => {
|
|
|
7228
7566
|
participantCount: meta.participants.length,
|
|
7229
7567
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
7230
7568
|
};
|
|
7231
|
-
console.error(`${
|
|
7569
|
+
console.error(`${TAG21} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
7232
7570
|
return c.json(result);
|
|
7233
7571
|
} catch (err) {
|
|
7234
|
-
console.error(`${
|
|
7572
|
+
console.error(`${TAG21} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
7235
7573
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
7236
7574
|
}
|
|
7237
7575
|
});
|
|
@@ -7239,11 +7577,11 @@ var whatsapp_default = app2;
|
|
|
7239
7577
|
|
|
7240
7578
|
// server/routes/whatsapp-reader.ts
|
|
7241
7579
|
import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync8, readdirSync as readdirSync8, realpathSync as realpathSync4 } from "fs";
|
|
7242
|
-
import { basename as
|
|
7580
|
+
import { basename as basename5, dirname as dirname4, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
|
|
7243
7581
|
|
|
7244
7582
|
// server/routes/admin/sidebar-sessions.ts
|
|
7245
7583
|
import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
|
|
7246
|
-
import { basename as
|
|
7584
|
+
import { basename as basename4, dirname as dirname3, join as join11, resolve as resolve11 } from "path";
|
|
7247
7585
|
|
|
7248
7586
|
// app/lib/whatsapp-reader/select-sessions.ts
|
|
7249
7587
|
function isReaderChannelSession(role, channel) {
|
|
@@ -7324,7 +7662,7 @@ function findSessionProjectDir(sessionId) {
|
|
|
7324
7662
|
const cfg = claudeConfigDir();
|
|
7325
7663
|
if (!cfg) return null;
|
|
7326
7664
|
for (const { path: path2 } of enumerateJsonls(join11(cfg, "projects"))) {
|
|
7327
|
-
if (
|
|
7665
|
+
if (basename4(path2) === `${sessionId}.jsonl`) return dirname3(path2);
|
|
7328
7666
|
}
|
|
7329
7667
|
return null;
|
|
7330
7668
|
}
|
|
@@ -7649,11 +7987,11 @@ app3.get("/", requireAdminSession, async (c) => {
|
|
|
7649
7987
|
var sidebar_sessions_default = app3;
|
|
7650
7988
|
|
|
7651
7989
|
// app/lib/admin-identity/pin-validator.ts
|
|
7652
|
-
import { createHash } from "crypto";
|
|
7990
|
+
import { createHash as createHash2 } from "crypto";
|
|
7653
7991
|
import { existsSync as existsSync7, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
7654
7992
|
import { join as join12 } from "path";
|
|
7655
7993
|
function hashPin(pin) {
|
|
7656
|
-
return
|
|
7994
|
+
return createHash2("sha256").update(pin).digest("hex");
|
|
7657
7995
|
}
|
|
7658
7996
|
function readUsersFile(usersFilePath) {
|
|
7659
7997
|
if (!existsSync7(usersFilePath)) return null;
|
|
@@ -8053,8 +8391,8 @@ app4.get("/conversations", requireAdminSession, async (c) => {
|
|
|
8053
8391
|
const rows = [];
|
|
8054
8392
|
let sessionRowsExcludedUntagged = 0;
|
|
8055
8393
|
for (const { path: path2 } of enumerateJsonls(projectsRoot)) {
|
|
8056
|
-
const sessionId =
|
|
8057
|
-
const projectDir =
|
|
8394
|
+
const sessionId = basename5(path2).replace(/\.jsonl$/, "");
|
|
8395
|
+
const projectDir = dirname4(path2);
|
|
8058
8396
|
const meta = readSidecarMeta(join14(projectDir, `${sessionId}.meta.json`));
|
|
8059
8397
|
if (!isReaderChannelSession(meta.role, meta.channel)) continue;
|
|
8060
8398
|
if (multiAccount) {
|
|
@@ -8584,7 +8922,7 @@ var whatsapp_reader_default = app4;
|
|
|
8584
8922
|
|
|
8585
8923
|
// server/routes/public-reader.ts
|
|
8586
8924
|
import { statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync16, readdirSync as readdirSync9 } from "fs";
|
|
8587
|
-
import { basename as
|
|
8925
|
+
import { basename as basename6, dirname as dirname5, join as join15, resolve as resolve13, sep as sep4 } from "path";
|
|
8588
8926
|
|
|
8589
8927
|
// app/lib/whatsapp-reader/delivered-kinds.ts
|
|
8590
8928
|
var PUBLIC_DELIVERED_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -8631,7 +8969,7 @@ function resolveOpenVisitorSession(rows, visitorId, agentSlug) {
|
|
|
8631
8969
|
|
|
8632
8970
|
// server/routes/public-reader.ts
|
|
8633
8971
|
var app5 = new Hono();
|
|
8634
|
-
var
|
|
8972
|
+
var TAG22 = "[public-webchat]";
|
|
8635
8973
|
function parseAccessSessionId(cookieHeader) {
|
|
8636
8974
|
if (!cookieHeader) return null;
|
|
8637
8975
|
const part = cookieHeader.split(";").map((p) => p.trim()).find((p) => p.startsWith("__access_session="));
|
|
@@ -8659,8 +8997,8 @@ function enumeratePublicRows() {
|
|
|
8659
8997
|
if (!cfg) return [];
|
|
8660
8998
|
const rows = [];
|
|
8661
8999
|
for (const { path: path2 } of enumerateJsonls(join15(cfg, "projects"))) {
|
|
8662
|
-
const sessionId =
|
|
8663
|
-
const projectDir =
|
|
9000
|
+
const sessionId = basename6(path2).replace(/\.jsonl$/, "");
|
|
9001
|
+
const projectDir = dirname5(path2);
|
|
8664
9002
|
const meta = readSidecarMeta(join15(projectDir, `${sessionId}.meta.json`));
|
|
8665
9003
|
rows.push({
|
|
8666
9004
|
sessionId,
|
|
@@ -8679,7 +9017,7 @@ function enumeratePublicRows() {
|
|
|
8679
9017
|
app5.get("/session", (c) => {
|
|
8680
9018
|
const visitor = resolveVisitor(c);
|
|
8681
9019
|
if (!visitor) {
|
|
8682
|
-
console.error(`${
|
|
9020
|
+
console.error(`${TAG22} op=reader-refused reason=no-anchor path=/session`);
|
|
8683
9021
|
return c.json({ error: "gate-required" }, 401);
|
|
8684
9022
|
}
|
|
8685
9023
|
const rows = enumeratePublicRows();
|
|
@@ -8698,11 +9036,11 @@ app5.get("/session", (c) => {
|
|
|
8698
9036
|
found = effectiveSlug ? resolveOpenVisitorSession(rows, visitor.visitorId, effectiveSlug) : null;
|
|
8699
9037
|
}
|
|
8700
9038
|
if (found) {
|
|
8701
|
-
console.log(`${
|
|
9039
|
+
console.log(`${TAG22} op=session-resume anchor=${visitor.kind} key=${(found.senderId ?? "").slice(0, 8)} sessionId=${found.sessionId.slice(0, 8)}`);
|
|
8702
9040
|
return c.json({ sessionId: found.sessionId, projectDir: found.projectDir, sessionKey: found.senderId });
|
|
8703
9041
|
}
|
|
8704
9042
|
const sessionKey = crypto.randomUUID();
|
|
8705
|
-
console.log(`${
|
|
9043
|
+
console.log(`${TAG22} op=session-new anchor=${visitor.kind} key=${sessionKey.slice(0, 8)}`);
|
|
8706
9044
|
return c.json({ sessionId: null, projectDir: null, sessionKey });
|
|
8707
9045
|
});
|
|
8708
9046
|
function publicUploadsDir(accountId, sessionId) {
|
|
@@ -8731,7 +9069,7 @@ function readFrom2(path2, from) {
|
|
|
8731
9069
|
app5.get("/stream", (c) => {
|
|
8732
9070
|
const visitor = resolveVisitor(c);
|
|
8733
9071
|
if (!visitor) {
|
|
8734
|
-
console.error(`${
|
|
9072
|
+
console.error(`${TAG22} op=reader-refused reason=no-anchor path=/stream`);
|
|
8735
9073
|
return c.json({ error: "gate-required" }, 401);
|
|
8736
9074
|
}
|
|
8737
9075
|
const sessionId = c.req.query("sessionId") ?? "";
|
|
@@ -8749,12 +9087,12 @@ app5.get("/stream", (c) => {
|
|
|
8749
9087
|
const isPublicWebchat = meta.role === "public" && meta.channel === "webchat";
|
|
8750
9088
|
const owns = visitor.kind === "person" ? meta.personId === visitor.personId : meta.personId === null && meta.visitorId === visitor.visitorId;
|
|
8751
9089
|
if (!(isPublicWebchat && owns)) {
|
|
8752
|
-
console.error(`${
|
|
9090
|
+
console.error(`${TAG22} op=reader-refused reason=not-owner anchor=${visitor.kind} sessionId=${sessionId.slice(0, 8)}`);
|
|
8753
9091
|
return c.json({ error: "forbidden" }, 403);
|
|
8754
9092
|
}
|
|
8755
9093
|
const senderShort = (meta.senderId ?? "").slice(0, 8);
|
|
8756
9094
|
const encoder = new TextEncoder();
|
|
8757
|
-
console.log(`${
|
|
9095
|
+
console.log(`${TAG22} op=reader-open anchor=${visitor.kind} key=${senderShort} mode=delivered-only sessionId=${sessionId.slice(0, 8)}`);
|
|
8758
9096
|
const send = (controller, turn, id) => controller.enqueue(encoder.encode(`id: ${id}
|
|
8759
9097
|
data: ${JSON.stringify(turn)}
|
|
8760
9098
|
|
|
@@ -8769,7 +9107,7 @@ data: ${JSON.stringify(turn)}
|
|
|
8769
9107
|
const { turns, dropped } = filterDeliveredFrames(lines);
|
|
8770
9108
|
if (uploadsDir) enrichPublicAttachments(turns, listSessionAttachmentsInDir(uploadsDir), attachmentCursor);
|
|
8771
9109
|
for (const turn of turns) send(controller, turn, offset);
|
|
8772
|
-
if (dropped > 0) console.log(`${
|
|
9110
|
+
if (dropped > 0) console.log(`${TAG22} op=reader-filtered key=${senderShort} dropped=${dropped}`);
|
|
8773
9111
|
};
|
|
8774
9112
|
try {
|
|
8775
9113
|
const { buf, end } = readFrom2(jsonlPath, 0);
|
|
@@ -8804,7 +9142,7 @@ data: ${JSON.stringify(turn)}
|
|
|
8804
9142
|
watcher?.close();
|
|
8805
9143
|
clearInterval(poll);
|
|
8806
9144
|
clearInterval(heartbeat);
|
|
8807
|
-
console.log(`${
|
|
9145
|
+
console.log(`${TAG22} op=reader-close key=${senderShort}`);
|
|
8808
9146
|
try {
|
|
8809
9147
|
controller.close();
|
|
8810
9148
|
} catch {
|
|
@@ -8878,7 +9216,7 @@ app5.get("/attachment/:attachmentId", (c) => {
|
|
|
8878
9216
|
var public_reader_default = app5;
|
|
8879
9217
|
|
|
8880
9218
|
// server/routes/webchat.ts
|
|
8881
|
-
import { basename as
|
|
9219
|
+
import { basename as basename7, dirname as dirname6 } from "path";
|
|
8882
9220
|
import { join as join18 } from "path";
|
|
8883
9221
|
import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync3, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
8884
9222
|
|
|
@@ -8924,18 +9262,6 @@ function writeCanonicalOverrideId(accountDir, userId, sessionId) {
|
|
|
8924
9262
|
renameSync(tmp, path2);
|
|
8925
9263
|
}
|
|
8926
9264
|
|
|
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
9265
|
// ../lib/models/src/index.ts
|
|
8940
9266
|
var OPUS_MODEL = "claude-opus-4-8[1m]";
|
|
8941
9267
|
var SONNET_MODEL = "claude-sonnet-5";
|
|
@@ -9288,8 +9614,8 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
|
|
|
9288
9614
|
let best = null;
|
|
9289
9615
|
for (const { path: path2, isSubagent, archived } of enumerateJsonls(join18(cfg, "projects"))) {
|
|
9290
9616
|
if (isSubagent || archived) continue;
|
|
9291
|
-
const id =
|
|
9292
|
-
const meta = readSidecarMeta(join18(
|
|
9617
|
+
const id = basename7(path2).slice(0, -".jsonl".length);
|
|
9618
|
+
const meta = readSidecarMeta(join18(dirname6(path2), `${id}.meta.json`));
|
|
9293
9619
|
if (meta.role !== "admin" || meta.channel !== "webchat") continue;
|
|
9294
9620
|
if (multiAccount && meta.accountId !== scopeAccountId) continue;
|
|
9295
9621
|
let mtimeMs;
|
|
@@ -9312,7 +9638,7 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
|
|
|
9312
9638
|
function overrideKnownNonArchived(id) {
|
|
9313
9639
|
const { projectDir } = locateSession(id);
|
|
9314
9640
|
if (projectDir === null) return sessionSidecarExists(id);
|
|
9315
|
-
return
|
|
9641
|
+
return basename7(projectDir) !== "archive";
|
|
9316
9642
|
}
|
|
9317
9643
|
function resolveCanonical(accountId, accountDir, requesterUserId, primaryUserId, scopeAccountId, multiAccount) {
|
|
9318
9644
|
const bootstrapAccountId = scopeAccountId ?? accountId;
|
|
@@ -9680,8 +10006,8 @@ ${note}` : note;
|
|
|
9680
10006
|
let projectDir = null;
|
|
9681
10007
|
if (cfg) {
|
|
9682
10008
|
for (const { path: path2 } of enumerateJsonls(join18(cfg, "projects"))) {
|
|
9683
|
-
if (
|
|
9684
|
-
projectDir =
|
|
10009
|
+
if (basename7(path2) === `${sessionId}.jsonl`) {
|
|
10010
|
+
projectDir = dirname6(path2);
|
|
9685
10011
|
break;
|
|
9686
10012
|
}
|
|
9687
10013
|
}
|
|
@@ -9913,7 +10239,7 @@ function routeTelegramUpdate(input) {
|
|
|
9913
10239
|
}
|
|
9914
10240
|
|
|
9915
10241
|
// server/routes/telegram.ts
|
|
9916
|
-
var
|
|
10242
|
+
var TAG23 = "[telegram-inbound]";
|
|
9917
10243
|
function configDirName() {
|
|
9918
10244
|
const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
|
|
9919
10245
|
const brandPath = join20(platformRoot3, "config", "brand.json");
|
|
@@ -9929,36 +10255,23 @@ function secretPath(botType) {
|
|
|
9929
10255
|
const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
|
|
9930
10256
|
return resolve15(homedir(), configDirName(), filename);
|
|
9931
10257
|
}
|
|
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
10258
|
var app7 = new Hono();
|
|
9946
10259
|
app7.post("/", async (c) => {
|
|
9947
10260
|
const botParam = c.req.query("bot");
|
|
9948
10261
|
const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
|
|
9949
10262
|
if (!botType) {
|
|
9950
|
-
console.error(`${
|
|
10263
|
+
console.error(`${TAG23} op=reject reason=missing-bot-param`);
|
|
9951
10264
|
return c.json({ ok: false }, 400);
|
|
9952
10265
|
}
|
|
9953
10266
|
const sp = secretPath(botType);
|
|
9954
10267
|
if (!existsSync12(sp)) {
|
|
9955
|
-
console.error(`${
|
|
10268
|
+
console.error(`${TAG23} op=reject reason=no-secret-file botType=${botType}`);
|
|
9956
10269
|
return c.json({ ok: false }, 401);
|
|
9957
10270
|
}
|
|
9958
10271
|
const expected = readFileSync21(sp, "utf-8").trim();
|
|
9959
10272
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
9960
10273
|
if (got !== expected) {
|
|
9961
|
-
console.error(`${
|
|
10274
|
+
console.error(`${TAG23} op=reject reason=bad-secret botType=${botType}`);
|
|
9962
10275
|
return c.json({ ok: false }, 401);
|
|
9963
10276
|
}
|
|
9964
10277
|
let update;
|
|
@@ -9969,40 +10282,40 @@ app7.post("/", async (c) => {
|
|
|
9969
10282
|
}
|
|
9970
10283
|
const account = resolveAccount();
|
|
9971
10284
|
if (!account) {
|
|
9972
|
-
console.error(`${
|
|
10285
|
+
console.error(`${TAG23} op=reject reason=no-account`);
|
|
9973
10286
|
return c.json({ ok: true }, 200);
|
|
9974
10287
|
}
|
|
9975
10288
|
const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
|
|
9976
10289
|
if (decision.kind === "ignore") {
|
|
9977
|
-
console.error(`${
|
|
10290
|
+
console.error(`${TAG23} op=ignore reason=${decision.reason}`);
|
|
9978
10291
|
return c.json({ ok: true }, 200);
|
|
9979
10292
|
}
|
|
9980
10293
|
if (decision.kind === "denied") {
|
|
9981
|
-
console.error(`${
|
|
10294
|
+
console.error(`${TAG23} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
|
|
9982
10295
|
return c.json({ ok: true }, 200);
|
|
9983
10296
|
}
|
|
9984
10297
|
const role = decision.agentType === "admin" ? "admin" : "public";
|
|
9985
10298
|
const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
|
|
9986
10299
|
if (!agentSlug) {
|
|
9987
|
-
console.error(`${
|
|
10300
|
+
console.error(`${TAG23} op=reject reason=no-default-agent`);
|
|
9988
10301
|
return c.json({ ok: true }, 200);
|
|
9989
10302
|
}
|
|
9990
|
-
console.error(`${
|
|
10303
|
+
console.error(`${TAG23} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
9991
10304
|
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
9992
10305
|
const { senderId, chatId, text } = decision;
|
|
9993
10306
|
const gateway = getTelegramGateway();
|
|
9994
10307
|
if (!gateway) {
|
|
9995
|
-
console.error(`${
|
|
10308
|
+
console.error(`${TAG23} op=reject reason=gateway-not-ready from=${senderId}`);
|
|
9996
10309
|
return c.json({ ok: true }, 200);
|
|
9997
10310
|
}
|
|
9998
10311
|
const reply = async (replyText) => {
|
|
9999
10312
|
if (!botToken) {
|
|
10000
10313
|
const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
|
|
10001
|
-
console.error(`${
|
|
10314
|
+
console.error(`${TAG23} op=reply-dropped reason=${reason} botType=${botType} from=${senderId}`);
|
|
10002
10315
|
return;
|
|
10003
10316
|
}
|
|
10004
|
-
const sent = await
|
|
10005
|
-
console.error(`${
|
|
10317
|
+
const sent = await sendTelegramText(botToken, chatId, replyText);
|
|
10318
|
+
console.error(`${TAG23} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
10006
10319
|
};
|
|
10007
10320
|
void gateway.handleInbound({
|
|
10008
10321
|
accountId: account.accountId,
|
|
@@ -10020,7 +10333,7 @@ var telegram_default = app7;
|
|
|
10020
10333
|
// server/routes/quickbooks.ts
|
|
10021
10334
|
import { join as join21 } from "path";
|
|
10022
10335
|
import { existsSync as existsSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync4 } from "fs";
|
|
10023
|
-
var
|
|
10336
|
+
var TAG24 = "[quickbooks]";
|
|
10024
10337
|
var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
|
|
10025
10338
|
var STATE_RE = /^[A-Za-z0-9_-]+$/;
|
|
10026
10339
|
function pendingPath(accountDir, state) {
|
|
@@ -10033,14 +10346,14 @@ async function completeConsent(args) {
|
|
|
10033
10346
|
const { accountDir, code, realmId } = args;
|
|
10034
10347
|
const state = args.state;
|
|
10035
10348
|
if (!state || !STATE_RE.test(state) || !existsSync13(pendingPath(accountDir, state))) {
|
|
10036
|
-
console.error(`${
|
|
10349
|
+
console.error(`${TAG24} op=callback state=${state ?? ""} stateValid=false realmId=${realmId ?? ""}`);
|
|
10037
10350
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
10038
10351
|
}
|
|
10039
10352
|
const claimFile = `${pendingPath(accountDir, state)}.claimed`;
|
|
10040
10353
|
try {
|
|
10041
10354
|
renameSync4(pendingPath(accountDir, state), claimFile);
|
|
10042
10355
|
} catch {
|
|
10043
|
-
console.error(`${
|
|
10356
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
10044
10357
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
10045
10358
|
}
|
|
10046
10359
|
try {
|
|
@@ -10048,14 +10361,14 @@ async function completeConsent(args) {
|
|
|
10048
10361
|
try {
|
|
10049
10362
|
pending = JSON.parse(readFileSync22(claimFile, "utf-8"));
|
|
10050
10363
|
} catch {
|
|
10051
|
-
console.error(`${
|
|
10364
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
10052
10365
|
return { ok: false, status: 400, message: "Authorization state could not be read.", stateValid: false };
|
|
10053
10366
|
}
|
|
10054
10367
|
if (Date.now() > pending.expiresAt) {
|
|
10055
|
-
console.error(`${
|
|
10368
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
10056
10369
|
return { ok: false, status: 400, message: "Authorization request expired. Generate a new consent link.", stateValid: false };
|
|
10057
10370
|
}
|
|
10058
|
-
console.error(`${
|
|
10371
|
+
console.error(`${TAG24} op=callback state=${state} stateValid=true realmId=${realmId ?? ""}`);
|
|
10059
10372
|
if (!code || !realmId) {
|
|
10060
10373
|
return { ok: false, status: 400, message: "Missing code or realmId in the callback.", stateValid: true };
|
|
10061
10374
|
}
|
|
@@ -10079,12 +10392,12 @@ async function completeConsent(args) {
|
|
|
10079
10392
|
});
|
|
10080
10393
|
const text = await res.text();
|
|
10081
10394
|
if (!res.ok) {
|
|
10082
|
-
console.error(`${
|
|
10395
|
+
console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=false`);
|
|
10083
10396
|
return { ok: false, status: 502, message: `Token exchange failed (${res.status}). Generate a new consent link to retry.`, stateValid: true };
|
|
10084
10397
|
}
|
|
10085
10398
|
const body = JSON.parse(text);
|
|
10086
10399
|
if (!body.refresh_token) {
|
|
10087
|
-
console.error(`${
|
|
10400
|
+
console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=false`);
|
|
10088
10401
|
return { ok: false, status: 502, message: "Token exchange returned no refresh token.", stateValid: true };
|
|
10089
10402
|
}
|
|
10090
10403
|
const now = Date.now();
|
|
@@ -10097,7 +10410,7 @@ async function completeConsent(args) {
|
|
|
10097
10410
|
mkdirSync4(join21(accountDir, "secrets"), { recursive: true });
|
|
10098
10411
|
writeFileSync8(storePath(accountDir), JSON.stringify(store2, null, 2), { mode: 384 });
|
|
10099
10412
|
const persisted = JSON.parse(readFileSync22(storePath(accountDir), "utf-8")).connections?.[realmId]?.refreshToken === body.refresh_token;
|
|
10100
|
-
console.error(`${
|
|
10413
|
+
console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=${persisted}`);
|
|
10101
10414
|
return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
|
|
10102
10415
|
} finally {
|
|
10103
10416
|
rmSync(claimFile, { force: true });
|
|
@@ -10110,7 +10423,7 @@ var app8 = new Hono();
|
|
|
10110
10423
|
app8.get("/callback", async (c) => {
|
|
10111
10424
|
const account = resolveAccount();
|
|
10112
10425
|
if (!account) {
|
|
10113
|
-
console.error(`${
|
|
10426
|
+
console.error(`${TAG24} op=callback stateValid=false realmId= reason=no-account`);
|
|
10114
10427
|
return c.html(page("QuickBooks", "This install has no account configured."), 500);
|
|
10115
10428
|
}
|
|
10116
10429
|
const result = await completeConsent({
|
|
@@ -10132,7 +10445,7 @@ import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
|
|
|
10132
10445
|
|
|
10133
10446
|
// ../lib/admins-write/src/index.ts
|
|
10134
10447
|
import { existsSync as existsSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync13, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
|
|
10135
|
-
import { dirname as
|
|
10448
|
+
import { dirname as dirname7, join as join22 } from "path";
|
|
10136
10449
|
function id8(value) {
|
|
10137
10450
|
return value.slice(0, 8);
|
|
10138
10451
|
}
|
|
@@ -10145,7 +10458,7 @@ function appendUsersAuditLine(audit, fields) {
|
|
|
10145
10458
|
const line = `[users-audit] action=${fields.action} actor=${actor}${sess} field=${fields.field} rowsBefore=${fields.rowsBefore} rowsAfter=${fields.rowsAfter} ts=${(/* @__PURE__ */ new Date()).toISOString()}
|
|
10146
10459
|
`;
|
|
10147
10460
|
try {
|
|
10148
|
-
mkdirSync5(
|
|
10461
|
+
mkdirSync5(dirname7(audit.logFile), { recursive: true, mode: 448 });
|
|
10149
10462
|
appendFileSync2(audit.logFile, line, { mode: 384 });
|
|
10150
10463
|
} catch (err) {
|
|
10151
10464
|
console.error(
|
|
@@ -10163,7 +10476,7 @@ function logLine(input, result) {
|
|
|
10163
10476
|
);
|
|
10164
10477
|
}
|
|
10165
10478
|
function writeFileAtomic(filePath, contents, mode) {
|
|
10166
|
-
mkdirSync5(
|
|
10479
|
+
mkdirSync5(dirname7(filePath), { recursive: true });
|
|
10167
10480
|
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
10168
10481
|
writeFileSync9(tempPath, contents, mode !== void 0 ? { mode } : void 0);
|
|
10169
10482
|
renameSync5(tempPath, filePath);
|
|
@@ -11012,7 +11325,7 @@ var accounts_default = app12;
|
|
|
11012
11325
|
|
|
11013
11326
|
// server/routes/admin/logs.ts
|
|
11014
11327
|
import { existsSync as existsSync18, readdirSync as readdirSync14, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
|
|
11015
|
-
import { resolve as resolve16, basename as
|
|
11328
|
+
import { resolve as resolve16, basename as basename8 } from "path";
|
|
11016
11329
|
|
|
11017
11330
|
// app/lib/logs-read-resolve.ts
|
|
11018
11331
|
import { existsSync as existsSync17 } from "fs";
|
|
@@ -11044,7 +11357,7 @@ app13.get("/", async (c) => {
|
|
|
11044
11357
|
if (accountLogDir) logDirs.push(accountLogDir);
|
|
11045
11358
|
logDirs.push(LOG_DIR);
|
|
11046
11359
|
if (fileParam) {
|
|
11047
|
-
const safe =
|
|
11360
|
+
const safe = basename8(fileParam);
|
|
11048
11361
|
const searched = [];
|
|
11049
11362
|
for (const dir of logDirs) {
|
|
11050
11363
|
const filePath = resolve16(dir, safe);
|
|
@@ -11114,7 +11427,7 @@ app13.get("/", async (c) => {
|
|
|
11114
11427
|
if (hit) {
|
|
11115
11428
|
console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
|
|
11116
11429
|
try {
|
|
11117
|
-
const filename =
|
|
11430
|
+
const filename = basename8(hit.path);
|
|
11118
11431
|
const buffer = readFileSync25(hit.path);
|
|
11119
11432
|
const onDiskBytes = statSync10(hit.path).size;
|
|
11120
11433
|
const headers = {
|
|
@@ -12048,7 +12361,7 @@ var sessions_default = app17;
|
|
|
12048
12361
|
|
|
12049
12362
|
// app/lib/claude-agent/spawn-context.ts
|
|
12050
12363
|
import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync16, statSync as statSync11 } from "fs";
|
|
12051
|
-
import { dirname as
|
|
12364
|
+
import { dirname as dirname8, resolve as resolve19, join as join25 } from "path";
|
|
12052
12365
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
12053
12366
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
12054
12367
|
try {
|
|
@@ -12155,7 +12468,7 @@ function listPluginDirs() {
|
|
|
12155
12468
|
}
|
|
12156
12469
|
}
|
|
12157
12470
|
}
|
|
12158
|
-
const premiumRoot = resolve19(
|
|
12471
|
+
const premiumRoot = resolve19(dirname8(PLATFORM_ROOT), "premium-plugins");
|
|
12159
12472
|
if (existsSync22(premiumRoot)) {
|
|
12160
12473
|
for (const bundle of readdirSync16(premiumRoot)) {
|
|
12161
12474
|
const bundlePlugins = join25(premiumRoot, bundle, "plugins");
|
|
@@ -12369,7 +12682,7 @@ function resolveTunnelUrl() {
|
|
|
12369
12682
|
if (!state) return null;
|
|
12370
12683
|
return `https://${hostname2}.${state.domain}`;
|
|
12371
12684
|
}
|
|
12372
|
-
var
|
|
12685
|
+
var TAG25 = "[claude-session-manager:wrapper]";
|
|
12373
12686
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
12374
12687
|
const auth = await ensureAuth();
|
|
12375
12688
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -12387,12 +12700,12 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
12387
12700
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
12388
12701
|
const ownerMs = Date.now() - ownerStart;
|
|
12389
12702
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
12390
|
-
console.log(`${
|
|
12703
|
+
console.log(`${TAG25} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
12391
12704
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
12392
12705
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
12393
12706
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
12394
12707
|
const tunnelUrl = resolveTunnelUrl();
|
|
12395
|
-
console.log(`${
|
|
12708
|
+
console.log(`${TAG25} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
12396
12709
|
const upstreamPayload = JSON.stringify({
|
|
12397
12710
|
senderId: args.senderId,
|
|
12398
12711
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -12419,24 +12732,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
12419
12732
|
// unshapely values.
|
|
12420
12733
|
conversationNodeId: args.conversationNodeId
|
|
12421
12734
|
});
|
|
12422
|
-
console.log(`${
|
|
12735
|
+
console.log(`${TAG25} forward-spawn-start managerBase=${managerBase("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
|
|
12423
12736
|
const forwardStart = Date.now();
|
|
12424
12737
|
const upstream = await fetch(`${managerBase("claude-session-manager:wrapper")}/public-spawn`, {
|
|
12425
12738
|
method: "POST",
|
|
12426
12739
|
headers: { "content-type": "application/json" },
|
|
12427
12740
|
body: upstreamPayload
|
|
12428
12741
|
}).catch((err) => {
|
|
12429
|
-
console.error(`${
|
|
12742
|
+
console.error(`${TAG25} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
12430
12743
|
return null;
|
|
12431
12744
|
});
|
|
12432
12745
|
if (!upstream) return {
|
|
12433
12746
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
12434
12747
|
claudeSessionId: null
|
|
12435
12748
|
};
|
|
12436
|
-
console.log(`${
|
|
12749
|
+
console.log(`${TAG25} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
12437
12750
|
if (args.initialMessage) {
|
|
12438
12751
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
12439
|
-
console.log(`${
|
|
12752
|
+
console.log(`${TAG25} initial-message-inlined bytes=${inputBytes}`);
|
|
12440
12753
|
}
|
|
12441
12754
|
const bodyText = await upstream.text().catch(() => "");
|
|
12442
12755
|
let claudeSessionId = null;
|
|
@@ -12471,7 +12784,7 @@ app18.post("/", async (c) => {
|
|
|
12471
12784
|
if (refusal) return refusal;
|
|
12472
12785
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
12473
12786
|
if (!senderId) {
|
|
12474
|
-
console.error(`${
|
|
12787
|
+
console.error(`${TAG25} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
12475
12788
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
12476
12789
|
}
|
|
12477
12790
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -12480,7 +12793,7 @@ app18.post("/", async (c) => {
|
|
|
12480
12793
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
12481
12794
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
12482
12795
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
12483
|
-
console.log(`${
|
|
12796
|
+
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
12797
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
12485
12798
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
12486
12799
|
senderId,
|
|
@@ -12499,33 +12812,33 @@ app18.post("/", async (c) => {
|
|
|
12499
12812
|
claudeSessionId,
|
|
12500
12813
|
senderId
|
|
12501
12814
|
);
|
|
12502
|
-
console.log(`${
|
|
12815
|
+
console.log(`${TAG25} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
12503
12816
|
return response;
|
|
12504
12817
|
});
|
|
12505
12818
|
var claude_sessions_default = app18;
|
|
12506
12819
|
|
|
12507
12820
|
// server/routes/admin/log-ingest.ts
|
|
12508
|
-
var
|
|
12821
|
+
var TAG26 = "[log-ingest]";
|
|
12509
12822
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
12510
12823
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
12511
12824
|
var MAX_LINE_BYTES = 4096;
|
|
12512
12825
|
var app19 = new Hono();
|
|
12513
|
-
function
|
|
12826
|
+
function isLoopbackAddr2(addr) {
|
|
12514
12827
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
12515
12828
|
}
|
|
12516
12829
|
app19.post("/", async (c) => {
|
|
12517
12830
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
12518
|
-
if (!
|
|
12519
|
-
console.error(`${
|
|
12831
|
+
if (!isLoopbackAddr2(remoteAddr)) {
|
|
12832
|
+
console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
12520
12833
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
12521
12834
|
}
|
|
12522
12835
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
12523
12836
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
12524
12837
|
if (xffRaw.length > 0) {
|
|
12525
12838
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
12526
|
-
const offender = tokens.find((t) => !
|
|
12839
|
+
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
12527
12840
|
if (offender !== void 0) {
|
|
12528
|
-
console.error(`${
|
|
12841
|
+
console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
12529
12842
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
12530
12843
|
}
|
|
12531
12844
|
}
|
|
@@ -12567,18 +12880,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
12567
12880
|
]);
|
|
12568
12881
|
var app20 = new Hono();
|
|
12569
12882
|
app20.post("/", async (c) => {
|
|
12570
|
-
const
|
|
12883
|
+
const TAG39 = "[admin:events]";
|
|
12571
12884
|
let body;
|
|
12572
12885
|
try {
|
|
12573
12886
|
body = await c.req.json();
|
|
12574
12887
|
} catch (err) {
|
|
12575
12888
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12576
|
-
console.error(`${
|
|
12889
|
+
console.error(`${TAG39} reject reason=body-not-json detail=${detail}`);
|
|
12577
12890
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
12578
12891
|
}
|
|
12579
12892
|
const event = typeof body.event === "string" ? body.event : "";
|
|
12580
12893
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
12581
|
-
console.error(`${
|
|
12894
|
+
console.error(`${TAG39} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
12582
12895
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
12583
12896
|
}
|
|
12584
12897
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -12604,7 +12917,7 @@ import { createReadStream as createReadStream2, createWriteStream as createWrite
|
|
|
12604
12917
|
import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, unlink as unlink2, rename } from "fs/promises";
|
|
12605
12918
|
import { realpathSync as realpathSync5 } from "fs";
|
|
12606
12919
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
12607
|
-
import { basename as
|
|
12920
|
+
import { basename as basename10, dirname as dirname9, join as join27, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
|
|
12608
12921
|
import { Readable as Readable2 } from "stream";
|
|
12609
12922
|
|
|
12610
12923
|
// ../lib/graph-trash/src/index.ts
|
|
@@ -12922,7 +13235,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
12922
13235
|
|
|
12923
13236
|
// app/lib/file-index.ts
|
|
12924
13237
|
import * as fsp from "fs/promises";
|
|
12925
|
-
import { resolve as resolve21, relative as relative3, join as join26, basename as
|
|
13238
|
+
import { resolve as resolve21, relative as relative3, join as join26, basename as basename9, extname as extname2, sep as sep5 } from "path";
|
|
12926
13239
|
import { tmpdir as tmpdir2 } from "os";
|
|
12927
13240
|
import { execFile as execFile2 } from "child_process";
|
|
12928
13241
|
import { promisify as promisify2 } from "util";
|
|
@@ -13060,8 +13373,8 @@ async function extractFileContent(absolute) {
|
|
|
13060
13373
|
}
|
|
13061
13374
|
}
|
|
13062
13375
|
async function readDisplayName(absolute) {
|
|
13063
|
-
const dir = absolute.slice(0, absolute.length -
|
|
13064
|
-
const base =
|
|
13376
|
+
const dir = absolute.slice(0, absolute.length - basename9(absolute).length);
|
|
13377
|
+
const base = basename9(absolute);
|
|
13065
13378
|
const dot = base.lastIndexOf(".");
|
|
13066
13379
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
13067
13380
|
if (base === `${stem}.meta.json`) return null;
|
|
@@ -13199,7 +13512,7 @@ async function cascadeTrashLinkedKnowledgeDocs(session, accountId, relativePaths
|
|
|
13199
13512
|
return { kds, sections, chunks };
|
|
13200
13513
|
}
|
|
13201
13514
|
async function buildArtifact(accountId, wf, embed2) {
|
|
13202
|
-
const name =
|
|
13515
|
+
const name = basename9(wf.absolute);
|
|
13203
13516
|
const { content, route } = await extractFileContent(wf.absolute);
|
|
13204
13517
|
const displayName = await readDisplayName(wf.absolute);
|
|
13205
13518
|
const embedInput = `${name}
|
|
@@ -13729,7 +14042,7 @@ app21.get("/download", requireAdminSession, async (c) => {
|
|
|
13729
14042
|
if (!info.isFile()) {
|
|
13730
14043
|
return c.json({ error: "Path is not a file" }, 400);
|
|
13731
14044
|
}
|
|
13732
|
-
const filename =
|
|
14045
|
+
const filename = basename10(absolute);
|
|
13733
14046
|
const mimeType = detectMimeType(absolute);
|
|
13734
14047
|
const nodeStream = createReadStream2(absolute);
|
|
13735
14048
|
const webStream = Readable2.toWeb(nodeStream);
|
|
@@ -13834,7 +14147,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
|
|
|
13834
14147
|
const info = await stat4(absolute);
|
|
13835
14148
|
if (info.isDirectory()) {
|
|
13836
14149
|
dirsWalked++;
|
|
13837
|
-
const parentAbs =
|
|
14150
|
+
const parentAbs = dirname9(absolute);
|
|
13838
14151
|
for await (const fileAbs of walkRegularFiles(absolute)) {
|
|
13839
14152
|
const within = relative4(absolute, fileAbs).split(sep6).join("/");
|
|
13840
14153
|
const fileRel = relPath === "" || relPath === "." ? within : `${relPath}/${within}`;
|
|
@@ -13845,7 +14158,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
|
|
|
13845
14158
|
if (over) return over;
|
|
13846
14159
|
}
|
|
13847
14160
|
} else if (info.isFile()) {
|
|
13848
|
-
const over = await addFile(absolute, info.size,
|
|
14161
|
+
const over = await addFile(absolute, info.size, basename10(absolute));
|
|
13849
14162
|
if (over) return over;
|
|
13850
14163
|
} else {
|
|
13851
14164
|
return c.json({ error: "Path is not a file or directory" }, 400);
|
|
@@ -13874,12 +14187,12 @@ function dataUploadCeiling() {
|
|
|
13874
14187
|
}
|
|
13875
14188
|
async function resolveUploadTarget(c, accountId, uid) {
|
|
13876
14189
|
const rawName = c.req.query("filename") ?? "";
|
|
13877
|
-
const safeName =
|
|
14190
|
+
const safeName = basename10(rawName).replace(/[\0/\\]/g, "_");
|
|
13878
14191
|
if (!safeName) return { ok: false, status: 400, error: "filename query param required" };
|
|
13879
14192
|
const rawRelpath = c.req.query("relpath") ?? "";
|
|
13880
14193
|
const relpath = rawRelpath !== "" ? rawRelpath : null;
|
|
13881
14194
|
const token = c.req.query("token") ?? "";
|
|
13882
|
-
const finalName = relpath ?
|
|
14195
|
+
const finalName = relpath ? basename10(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
|
|
13883
14196
|
const mimeType = (c.req.header("content-type") ?? "").split(";")[0].trim();
|
|
13884
14197
|
if (!SUPPORTED_MIME_TYPES.has(mimeType)) {
|
|
13885
14198
|
console.error(`[data-upload] op=reject-mime uid=${uid} mime="${mimeType}" token=${token} rel="${relpath ?? ""}"`);
|
|
@@ -13888,7 +14201,7 @@ async function resolveUploadTarget(c, accountId, uid) {
|
|
|
13888
14201
|
const rawDir = c.req.query("path") ?? "";
|
|
13889
14202
|
const base = resolveOwnAccountWrite(rawDir, accountId, "POST /api/admin/files/upload");
|
|
13890
14203
|
if (!base.ok) return { ok: false, status: base.status, error: base.error };
|
|
13891
|
-
const relDir = relpath ?
|
|
14204
|
+
const relDir = relpath ? dirname9(relpath) : ".";
|
|
13892
14205
|
const destRel = relDir === "." ? base.relative : join27(base.relative, relDir);
|
|
13893
14206
|
if (crossesForeignAccountPartition(destRel, accountId)) {
|
|
13894
14207
|
console.error(`[data] account-scope-blocked endpoint="POST /api/admin/files/upload" path="${destRel}" account=${accountId.slice(0, 8)}\u2026`);
|
|
@@ -14249,7 +14562,7 @@ app21.delete("/", requireAdminSession, async (c) => {
|
|
|
14249
14562
|
console.error(`[data] account-scope-blocked endpoint="DELETE /api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
|
|
14250
14563
|
return c.json({ error: "Not found" }, 404);
|
|
14251
14564
|
}
|
|
14252
|
-
const base =
|
|
14565
|
+
const base = basename10(absolute);
|
|
14253
14566
|
const protection = isProtectedFromDeletion(relPath);
|
|
14254
14567
|
if (protection.protected) {
|
|
14255
14568
|
console.error(`[data] file-delete blocked path="${relPath}" reason="protected" rule="${protection.rule}"`);
|
|
@@ -14265,7 +14578,7 @@ app21.delete("/", requireAdminSession, async (c) => {
|
|
|
14265
14578
|
}
|
|
14266
14579
|
const dot = base.lastIndexOf(".");
|
|
14267
14580
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
14268
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join27(
|
|
14581
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join27(dirname9(absolute), `${stem}.meta.json`) : null;
|
|
14269
14582
|
await unlink2(absolute);
|
|
14270
14583
|
if (sidecarPath) {
|
|
14271
14584
|
try {
|
|
@@ -14361,8 +14674,8 @@ app21.post("/rename", requireAdminSession, async (c) => {
|
|
|
14361
14674
|
console.error(`[data] file-rename blocked path="${src.relative}" reason="protected"`);
|
|
14362
14675
|
return c.json({ error: "Protected entry \u2014 refusing to rename" }, 403);
|
|
14363
14676
|
}
|
|
14364
|
-
const destAbs = resolve22(
|
|
14365
|
-
const newRel = `${
|
|
14677
|
+
const destAbs = resolve22(dirname9(src.absolute), newName);
|
|
14678
|
+
const newRel = `${dirname9(src.relative)}/${newName}`.replace(/^\.\//, "");
|
|
14366
14679
|
if (isProtectedFromRename(newRel)) {
|
|
14367
14680
|
console.error(`[data] file-rename blocked path="${newRel}" reason="protected-target"`);
|
|
14368
14681
|
return c.json({ error: "That name is reserved" }, 403);
|
|
@@ -16493,7 +16806,7 @@ var graph_default_view_default = app27;
|
|
|
16493
16806
|
|
|
16494
16807
|
// server/routes/admin/sidebar-artefacts.ts
|
|
16495
16808
|
import { readdir as readdir4, stat as stat5 } from "fs/promises";
|
|
16496
|
-
import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as
|
|
16809
|
+
import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as basename11 } from "path";
|
|
16497
16810
|
import { existsSync as existsSync25 } from "fs";
|
|
16498
16811
|
var LIMIT = 50;
|
|
16499
16812
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
@@ -16546,7 +16859,7 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
16546
16859
|
const modifiedAt = r.get("modifiedAt") ?? "";
|
|
16547
16860
|
return {
|
|
16548
16861
|
id: `account-file:${relativePath}`,
|
|
16549
|
-
name: displayName ??
|
|
16862
|
+
name: displayName ?? basename11(relativePath),
|
|
16550
16863
|
kind: "account-file",
|
|
16551
16864
|
updatedAt: modifiedAt,
|
|
16552
16865
|
mimeType,
|
|
@@ -17393,7 +17706,7 @@ function managerLogFollowUrl(sessionId, opts) {
|
|
|
17393
17706
|
}
|
|
17394
17707
|
|
|
17395
17708
|
// server/routes/admin/linkedin-ingest.ts
|
|
17396
|
-
var
|
|
17709
|
+
var TAG27 = "[linkedin-ingest-route]";
|
|
17397
17710
|
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
17711
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
17399
17712
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -17497,29 +17810,29 @@ app37.post("/", requireAdminSession, async (c) => {
|
|
|
17497
17810
|
try {
|
|
17498
17811
|
body = await c.req.json();
|
|
17499
17812
|
} catch {
|
|
17500
|
-
console.error(
|
|
17813
|
+
console.error(TAG27 + " rejected status=400 reason=schema:body-not-json");
|
|
17501
17814
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
17502
17815
|
}
|
|
17503
17816
|
const v = validate(body);
|
|
17504
17817
|
if (!v.ok) {
|
|
17505
|
-
console.error(
|
|
17818
|
+
console.error(TAG27 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
17506
17819
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
17507
17820
|
}
|
|
17508
17821
|
const envelope = v.envelope;
|
|
17509
17822
|
const cacheKey = c.var.cacheKey ?? "";
|
|
17510
17823
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
17511
17824
|
if (!senderId) {
|
|
17512
|
-
console.error(
|
|
17825
|
+
console.error(TAG27 + " rejected status=500 reason=admin-account-not-resolved");
|
|
17513
17826
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
17514
17827
|
}
|
|
17515
17828
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
17516
17829
|
console.log(
|
|
17517
|
-
|
|
17830
|
+
TAG27 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
17518
17831
|
);
|
|
17519
17832
|
const initialMessage = buildInitialMessage(envelope);
|
|
17520
17833
|
const spawnStart = Date.now();
|
|
17521
17834
|
const sessionId = randomUUID12();
|
|
17522
|
-
console.log(
|
|
17835
|
+
console.log(TAG27 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
17523
17836
|
const spawned = await managerRcSpawn({
|
|
17524
17837
|
sessionId,
|
|
17525
17838
|
initialMessage,
|
|
@@ -17528,12 +17841,12 @@ app37.post("/", requireAdminSession, async (c) => {
|
|
|
17528
17841
|
});
|
|
17529
17842
|
if ("error" in spawned) {
|
|
17530
17843
|
console.error(
|
|
17531
|
-
|
|
17844
|
+
TAG27 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
17532
17845
|
);
|
|
17533
17846
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
17534
17847
|
}
|
|
17535
17848
|
console.log(
|
|
17536
|
-
|
|
17849
|
+
TAG27 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
17537
17850
|
);
|
|
17538
17851
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
17539
17852
|
});
|
|
@@ -17541,7 +17854,7 @@ var linkedin_ingest_default = app37;
|
|
|
17541
17854
|
|
|
17542
17855
|
// server/routes/admin/post-turn-context.ts
|
|
17543
17856
|
import neo4j3 from "neo4j-driver";
|
|
17544
|
-
var
|
|
17857
|
+
var TAG28 = "[post-turn-context]";
|
|
17545
17858
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
17546
17859
|
"embedding",
|
|
17547
17860
|
"passwordHash",
|
|
@@ -17549,7 +17862,7 @@ var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
|
17549
17862
|
"otpCode",
|
|
17550
17863
|
"cacheKey"
|
|
17551
17864
|
]);
|
|
17552
|
-
function
|
|
17865
|
+
function isLoopbackAddr3(addr) {
|
|
17553
17866
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17554
17867
|
}
|
|
17555
17868
|
function convertValue(value) {
|
|
@@ -17582,17 +17895,17 @@ function pruneProperties(raw) {
|
|
|
17582
17895
|
var app38 = new Hono();
|
|
17583
17896
|
app38.get("/", async (c) => {
|
|
17584
17897
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17585
|
-
if (!
|
|
17586
|
-
console.error(`${
|
|
17898
|
+
if (!isLoopbackAddr3(remoteAddr)) {
|
|
17899
|
+
console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17587
17900
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
17588
17901
|
}
|
|
17589
17902
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17590
17903
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17591
17904
|
if (xffRaw.length > 0) {
|
|
17592
17905
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17593
|
-
const offender = tokens.find((t) => !
|
|
17906
|
+
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
17594
17907
|
if (offender !== void 0) {
|
|
17595
|
-
console.error(`${
|
|
17908
|
+
console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17596
17909
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
17597
17910
|
}
|
|
17598
17911
|
}
|
|
@@ -17624,7 +17937,7 @@ app38.get("/", async (c) => {
|
|
|
17624
17937
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
17625
17938
|
const total = Date.now() - started;
|
|
17626
17939
|
console.log(
|
|
17627
|
-
`${
|
|
17940
|
+
`${TAG28} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
17628
17941
|
);
|
|
17629
17942
|
return c.json({
|
|
17630
17943
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -17633,7 +17946,7 @@ app38.get("/", async (c) => {
|
|
|
17633
17946
|
const elapsed = Date.now() - started;
|
|
17634
17947
|
const message = err instanceof Error ? err.message : String(err);
|
|
17635
17948
|
console.error(
|
|
17636
|
-
`${
|
|
17949
|
+
`${TAG28} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
17637
17950
|
);
|
|
17638
17951
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
17639
17952
|
} finally {
|
|
@@ -17746,24 +18059,24 @@ function formatPreviousContext(writes) {
|
|
|
17746
18059
|
}
|
|
17747
18060
|
|
|
17748
18061
|
// server/routes/admin/public-session-context.ts
|
|
17749
|
-
var
|
|
17750
|
-
function
|
|
18062
|
+
var TAG29 = "[public-session-context]";
|
|
18063
|
+
function isLoopbackAddr4(addr) {
|
|
17751
18064
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17752
18065
|
}
|
|
17753
18066
|
var app39 = new Hono();
|
|
17754
18067
|
app39.get("/", async (c) => {
|
|
17755
18068
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17756
|
-
if (!
|
|
17757
|
-
console.error(`${
|
|
18069
|
+
if (!isLoopbackAddr4(remoteAddr)) {
|
|
18070
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17758
18071
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
17759
18072
|
}
|
|
17760
18073
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17761
18074
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17762
18075
|
if (xffRaw.length > 0) {
|
|
17763
18076
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17764
|
-
const offender = tokens.find((t) => !
|
|
18077
|
+
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
17765
18078
|
if (offender !== void 0) {
|
|
17766
|
-
console.error(`${
|
|
18079
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17767
18080
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
17768
18081
|
}
|
|
17769
18082
|
}
|
|
@@ -17777,14 +18090,14 @@ app39.get("/", async (c) => {
|
|
|
17777
18090
|
const writes = await fetchSliceWrites(session, sliceToken, accountId);
|
|
17778
18091
|
const total = Date.now() - started;
|
|
17779
18092
|
console.log(
|
|
17780
|
-
`${
|
|
18093
|
+
`${TAG29} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
17781
18094
|
);
|
|
17782
18095
|
return c.json({ writes });
|
|
17783
18096
|
} catch (err) {
|
|
17784
18097
|
const elapsed = Date.now() - started;
|
|
17785
18098
|
const message = err instanceof Error ? err.message : String(err);
|
|
17786
18099
|
console.error(
|
|
17787
|
-
`${
|
|
18100
|
+
`${TAG29} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
17788
18101
|
);
|
|
17789
18102
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
17790
18103
|
} finally {
|
|
@@ -17803,24 +18116,24 @@ function getWebchatGateway() {
|
|
|
17803
18116
|
}
|
|
17804
18117
|
|
|
17805
18118
|
// server/routes/admin/public-session-exit.ts
|
|
17806
|
-
var
|
|
17807
|
-
function
|
|
18119
|
+
var TAG30 = "[public-session-exit-route]";
|
|
18120
|
+
function isLoopbackAddr5(addr) {
|
|
17808
18121
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17809
18122
|
}
|
|
17810
18123
|
var app40 = new Hono();
|
|
17811
18124
|
app40.post("/", async (c) => {
|
|
17812
18125
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17813
|
-
if (!
|
|
17814
|
-
console.error(`${
|
|
18126
|
+
if (!isLoopbackAddr5(remoteAddr)) {
|
|
18127
|
+
console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17815
18128
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17816
18129
|
}
|
|
17817
18130
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17818
18131
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17819
18132
|
if (xffRaw.length > 0) {
|
|
17820
18133
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17821
|
-
const offender = tokens.find((t) => !
|
|
18134
|
+
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
17822
18135
|
if (offender !== void 0) {
|
|
17823
|
-
console.error(`${
|
|
18136
|
+
console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17824
18137
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17825
18138
|
}
|
|
17826
18139
|
}
|
|
@@ -17834,7 +18147,7 @@ app40.post("/", async (c) => {
|
|
|
17834
18147
|
if (!sessionId) return c.json({ error: "sessionId required" }, 400);
|
|
17835
18148
|
const gateway = getWebchatGateway();
|
|
17836
18149
|
if (!gateway) {
|
|
17837
|
-
console.error(`${
|
|
18150
|
+
console.error(`${TAG30} reject reason=gateway-unset sessionId=${sessionId.slice(0, 8)}`);
|
|
17838
18151
|
return c.json({ error: "webchat-gateway-unset" }, 503);
|
|
17839
18152
|
}
|
|
17840
18153
|
gateway.handlePublicSessionExit(sessionId);
|
|
@@ -17843,24 +18156,24 @@ app40.post("/", async (c) => {
|
|
|
17843
18156
|
var public_session_exit_default = app40;
|
|
17844
18157
|
|
|
17845
18158
|
// server/routes/admin/access-session-evict.ts
|
|
17846
|
-
var
|
|
17847
|
-
function
|
|
18159
|
+
var TAG31 = "[access-session-evict]";
|
|
18160
|
+
function isLoopbackAddr6(addr) {
|
|
17848
18161
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17849
18162
|
}
|
|
17850
18163
|
var app41 = new Hono();
|
|
17851
18164
|
app41.post("/", async (c) => {
|
|
17852
18165
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17853
|
-
if (!
|
|
17854
|
-
console.error(`${
|
|
18166
|
+
if (!isLoopbackAddr6(remoteAddr)) {
|
|
18167
|
+
console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17855
18168
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17856
18169
|
}
|
|
17857
18170
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17858
18171
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17859
18172
|
if (xffRaw.length > 0) {
|
|
17860
18173
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17861
|
-
const offender = tokens.find((t) => !
|
|
18174
|
+
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
17862
18175
|
if (offender !== void 0) {
|
|
17863
|
-
console.error(`${
|
|
18176
|
+
console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17864
18177
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17865
18178
|
}
|
|
17866
18179
|
}
|
|
@@ -17873,31 +18186,31 @@ app41.post("/", async (c) => {
|
|
|
17873
18186
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
17874
18187
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
17875
18188
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
17876
|
-
console.log(`${
|
|
18189
|
+
console.log(`${TAG31} grantId=${grantId} dropped=${dropped}`);
|
|
17877
18190
|
return c.json({ ok: true, dropped });
|
|
17878
18191
|
});
|
|
17879
18192
|
var access_session_evict_default = app41;
|
|
17880
18193
|
|
|
17881
18194
|
// server/routes/admin/enrol-person.ts
|
|
17882
|
-
var
|
|
17883
|
-
function
|
|
18195
|
+
var TAG32 = "[enrol]";
|
|
18196
|
+
function isLoopbackAddr7(addr) {
|
|
17884
18197
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17885
18198
|
}
|
|
17886
18199
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17887
18200
|
var app42 = new Hono();
|
|
17888
18201
|
app42.post("/", async (c) => {
|
|
17889
18202
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17890
|
-
if (!
|
|
17891
|
-
console.error(`${
|
|
18203
|
+
if (!isLoopbackAddr7(remoteAddr)) {
|
|
18204
|
+
console.error(`${TAG32} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17892
18205
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17893
18206
|
}
|
|
17894
18207
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17895
18208
|
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17896
18209
|
if (xffRaw.length > 0) {
|
|
17897
18210
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17898
|
-
const offender = tokens.find((t) => !
|
|
18211
|
+
const offender = tokens.find((t) => !isLoopbackAddr7(t));
|
|
17899
18212
|
if (offender !== void 0) {
|
|
17900
|
-
console.error(`${
|
|
18213
|
+
console.error(`${TAG32} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17901
18214
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17902
18215
|
}
|
|
17903
18216
|
}
|
|
@@ -17922,7 +18235,7 @@ app42.post("/", async (c) => {
|
|
|
17922
18235
|
}
|
|
17923
18236
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
17924
18237
|
if (!accountId) {
|
|
17925
|
-
console.error(`${
|
|
18238
|
+
console.error(`${TAG32} op=person result=no-account-id`);
|
|
17926
18239
|
return c.json({ error: "no-account-id" }, 500);
|
|
17927
18240
|
}
|
|
17928
18241
|
let personId;
|
|
@@ -17930,7 +18243,7 @@ app42.post("/", async (c) => {
|
|
|
17930
18243
|
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
17931
18244
|
} catch (err) {
|
|
17932
18245
|
console.error(
|
|
17933
|
-
`${
|
|
18246
|
+
`${TAG32} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17934
18247
|
);
|
|
17935
18248
|
return c.json({ error: "enrol-failed" }, 500);
|
|
17936
18249
|
}
|
|
@@ -17940,11 +18253,11 @@ app42.post("/", async (c) => {
|
|
|
17940
18253
|
if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
|
|
17941
18254
|
} catch (err) {
|
|
17942
18255
|
console.error(
|
|
17943
|
-
`${
|
|
18256
|
+
`${TAG32} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17944
18257
|
);
|
|
17945
18258
|
}
|
|
17946
18259
|
console.log(
|
|
17947
|
-
`${
|
|
18260
|
+
`${TAG32} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
|
|
17948
18261
|
);
|
|
17949
18262
|
return c.json({ personId, grant }, 200);
|
|
17950
18263
|
});
|
|
@@ -18513,7 +18826,7 @@ app45.route("/calendar", calendar_default);
|
|
|
18513
18826
|
var admin_default = app45;
|
|
18514
18827
|
|
|
18515
18828
|
// server/routes/access/verify-token.ts
|
|
18516
|
-
var
|
|
18829
|
+
var TAG33 = "[access-verify]";
|
|
18517
18830
|
var MINT_TAG = "[access-session-mint]";
|
|
18518
18831
|
var COOKIE_NAME = "__access_session";
|
|
18519
18832
|
var app46 = new Hono();
|
|
@@ -18532,39 +18845,39 @@ app46.post("/", async (c) => {
|
|
|
18532
18845
|
}
|
|
18533
18846
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
18534
18847
|
if (rateMsg) {
|
|
18535
|
-
console.error(`${
|
|
18848
|
+
console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
18536
18849
|
return c.json({ error: rateMsg }, 429);
|
|
18537
18850
|
}
|
|
18538
18851
|
const grant = await findGrantByMagicToken(token);
|
|
18539
18852
|
if (!grant) {
|
|
18540
18853
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18541
|
-
console.error(`${
|
|
18854
|
+
console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
18542
18855
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
18543
18856
|
}
|
|
18544
18857
|
if (grant.agentSlug !== agentSlug) {
|
|
18545
18858
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18546
18859
|
console.error(
|
|
18547
|
-
`${
|
|
18860
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
18548
18861
|
);
|
|
18549
18862
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
18550
18863
|
}
|
|
18551
18864
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
18552
18865
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18553
18866
|
console.error(
|
|
18554
|
-
`${
|
|
18867
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
18555
18868
|
);
|
|
18556
18869
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
18557
18870
|
}
|
|
18558
18871
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
18559
18872
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
18560
18873
|
console.error(
|
|
18561
|
-
`${
|
|
18874
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
18562
18875
|
);
|
|
18563
18876
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
18564
18877
|
}
|
|
18565
18878
|
if (!grant.sliceToken) {
|
|
18566
18879
|
console.error(
|
|
18567
|
-
`${
|
|
18880
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
18568
18881
|
);
|
|
18569
18882
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
18570
18883
|
}
|
|
@@ -18580,12 +18893,12 @@ app46.post("/", async (c) => {
|
|
|
18580
18893
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
18581
18894
|
} catch (err) {
|
|
18582
18895
|
console.error(
|
|
18583
|
-
`${
|
|
18896
|
+
`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
18584
18897
|
);
|
|
18585
18898
|
return c.json({ error: "verification-failed" }, 500);
|
|
18586
18899
|
}
|
|
18587
18900
|
clearAccessRateLimit(ip, agentSlug);
|
|
18588
|
-
console.log(`${
|
|
18901
|
+
console.log(`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
18589
18902
|
console.log(
|
|
18590
18903
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
18591
18904
|
);
|
|
@@ -18661,7 +18974,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
18661
18974
|
}
|
|
18662
18975
|
|
|
18663
18976
|
// server/routes/access/request-magic-link.ts
|
|
18664
|
-
var
|
|
18977
|
+
var TAG34 = "[access-request-link]";
|
|
18665
18978
|
var app47 = new Hono();
|
|
18666
18979
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
18667
18980
|
app47.post("/", async (c) => {
|
|
@@ -18678,18 +18991,18 @@ app47.post("/", async (c) => {
|
|
|
18678
18991
|
}
|
|
18679
18992
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
18680
18993
|
if (rateMsg) {
|
|
18681
|
-
console.error(`${
|
|
18994
|
+
console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
18682
18995
|
return c.json({ error: rateMsg }, 429);
|
|
18683
18996
|
}
|
|
18684
18997
|
recordRequestLinkAttempt(contactValue);
|
|
18685
18998
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
18686
18999
|
if (!accountId) {
|
|
18687
|
-
console.error(`${
|
|
19000
|
+
console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
18688
19001
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18689
19002
|
}
|
|
18690
19003
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
18691
19004
|
if (!grant) {
|
|
18692
|
-
console.log(`${
|
|
19005
|
+
console.log(`${TAG34} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
18693
19006
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18694
19007
|
}
|
|
18695
19008
|
let token;
|
|
@@ -18697,7 +19010,7 @@ app47.post("/", async (c) => {
|
|
|
18697
19010
|
token = await generateNewMagicToken(grant.grantId);
|
|
18698
19011
|
} catch (err) {
|
|
18699
19012
|
console.error(
|
|
18700
|
-
`${
|
|
19013
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
18701
19014
|
);
|
|
18702
19015
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18703
19016
|
}
|
|
@@ -18729,12 +19042,12 @@ app47.post("/", async (c) => {
|
|
|
18729
19042
|
});
|
|
18730
19043
|
if (!sendResult.ok) {
|
|
18731
19044
|
console.error(
|
|
18732
|
-
`${
|
|
19045
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
18733
19046
|
);
|
|
18734
19047
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18735
19048
|
}
|
|
18736
19049
|
console.log(
|
|
18737
|
-
`${
|
|
19050
|
+
`${TAG34} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
18738
19051
|
);
|
|
18739
19052
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18740
19053
|
});
|
|
@@ -18888,7 +19201,7 @@ var sites_default = app49;
|
|
|
18888
19201
|
// app/lib/visitor-token.ts
|
|
18889
19202
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
18890
19203
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync32, writeFileSync as writeFileSync11 } from "fs";
|
|
18891
|
-
import { dirname as
|
|
19204
|
+
import { dirname as dirname10 } from "path";
|
|
18892
19205
|
var TOKEN_PREFIX = "v1.";
|
|
18893
19206
|
var SECRET_BYTES = 32;
|
|
18894
19207
|
var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -18905,7 +19218,7 @@ function getSecret() {
|
|
|
18905
19218
|
}
|
|
18906
19219
|
const fresh = randomBytes(SECRET_BYTES).toString("hex");
|
|
18907
19220
|
try {
|
|
18908
|
-
mkdirSync7(
|
|
19221
|
+
mkdirSync7(dirname10(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
|
|
18909
19222
|
writeFileSync11(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
|
|
18910
19223
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
18911
19224
|
} catch {
|
|
@@ -20013,7 +20326,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
20013
20326
|
|
|
20014
20327
|
// app/lib/migrate-uploads.ts
|
|
20015
20328
|
import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
|
|
20016
|
-
import { dirname as
|
|
20329
|
+
import { dirname as dirname11, relative as relative6, resolve as resolve29 } from "path";
|
|
20017
20330
|
var ACCOUNT_UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20018
20331
|
async function walkFiles(dir) {
|
|
20019
20332
|
const out = [];
|
|
@@ -20045,7 +20358,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
20045
20358
|
for (const oldAbs of files) {
|
|
20046
20359
|
const suffix = relative6(srcDir, oldAbs);
|
|
20047
20360
|
const newAbs = resolve29(destDir, suffix);
|
|
20048
|
-
await mkdir5(
|
|
20361
|
+
await mkdir5(dirname11(newAbs), { recursive: true });
|
|
20049
20362
|
await rename2(oldAbs, newAbs);
|
|
20050
20363
|
moved++;
|
|
20051
20364
|
const oldRel = relative6(dataRoot, oldAbs);
|
|
@@ -20752,6 +21065,7 @@ var WaGateway = class {
|
|
|
20752
21065
|
const willDeliverNow = this.hub.isReady(input.senderId);
|
|
20753
21066
|
const openable = input.media.filter((m) => m.type !== "audio");
|
|
20754
21067
|
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";
|
|
21068
|
+
const source = input.source ?? "user";
|
|
20755
21069
|
this.replies.set(input.senderId, input.reply);
|
|
20756
21070
|
this.docContexts.set(input.senderId, { accountId: input.accountId });
|
|
20757
21071
|
this.hub.deliver(
|
|
@@ -20759,12 +21073,13 @@ var WaGateway = class {
|
|
|
20759
21073
|
senderId: input.senderId,
|
|
20760
21074
|
text: input.text,
|
|
20761
21075
|
waMessageId: `wa-${++this.seq}`,
|
|
20762
|
-
media: input.media
|
|
21076
|
+
media: input.media,
|
|
21077
|
+
source
|
|
20763
21078
|
},
|
|
20764
21079
|
Date.now()
|
|
20765
21080
|
);
|
|
20766
21081
|
console.error(
|
|
20767
|
-
`[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} ${mediaField} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
21082
|
+
`[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} ${mediaField} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
20768
21083
|
);
|
|
20769
21084
|
if (!hadSubscriber && !this.spawning.has(input.senderId)) {
|
|
20770
21085
|
this.spawning.add(input.senderId);
|
|
@@ -20772,6 +21087,7 @@ var WaGateway = class {
|
|
|
20772
21087
|
try {
|
|
20773
21088
|
await this.deps.ensureChannelSession({
|
|
20774
21089
|
accountId: input.accountId,
|
|
21090
|
+
effectiveAccountId: input.effectiveAccountId,
|
|
20775
21091
|
senderId: input.senderId,
|
|
20776
21092
|
role: input.role ?? "admin",
|
|
20777
21093
|
personId: input.personId ?? null,
|
|
@@ -21701,7 +22017,7 @@ function makeFileDelivery(opts) {
|
|
|
21701
22017
|
}
|
|
21702
22018
|
|
|
21703
22019
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
21704
|
-
var
|
|
22020
|
+
var TAG35 = "[whatsapp-adaptor]";
|
|
21705
22021
|
var SEND_USER_FILE2 = "SendUserFile";
|
|
21706
22022
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
21707
22023
|
function platformRoot() {
|
|
@@ -21714,7 +22030,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21714
22030
|
maxyAccountId = resolvePlatformAccountId();
|
|
21715
22031
|
} catch (err) {
|
|
21716
22032
|
console.error(
|
|
21717
|
-
`${
|
|
22033
|
+
`${TAG35} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
21718
22034
|
);
|
|
21719
22035
|
return { ok: false, error: "account-unresolved" };
|
|
21720
22036
|
}
|
|
@@ -21728,7 +22044,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21728
22044
|
});
|
|
21729
22045
|
if (result.ok) return { ok: true };
|
|
21730
22046
|
console.error(
|
|
21731
|
-
`${
|
|
22047
|
+
`${TAG35} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
21732
22048
|
);
|
|
21733
22049
|
return { ok: false, error: result.error };
|
|
21734
22050
|
};
|
|
@@ -21736,7 +22052,7 @@ function makeWhatsAppSendFile(entry) {
|
|
|
21736
22052
|
function makeWhatsAppFileDelivery(entry) {
|
|
21737
22053
|
const shared = makeFileDelivery({
|
|
21738
22054
|
entry,
|
|
21739
|
-
tag:
|
|
22055
|
+
tag: TAG35,
|
|
21740
22056
|
channel: "whatsapp",
|
|
21741
22057
|
sendFile: makeWhatsAppSendFile(entry)
|
|
21742
22058
|
});
|
|
@@ -21771,7 +22087,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
21771
22087
|
if (!delivered) {
|
|
21772
22088
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
21773
22089
|
console.error(
|
|
21774
|
-
`${
|
|
22090
|
+
`${TAG35} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
21775
22091
|
);
|
|
21776
22092
|
}
|
|
21777
22093
|
}
|
|
@@ -22080,13 +22396,14 @@ var TelegramGateway = class {
|
|
|
22080
22396
|
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
22081
22397
|
const hadSubscriber = this.hub.hasSubscriber(input.senderId);
|
|
22082
22398
|
const willDeliverNow = this.hub.isReady(input.senderId);
|
|
22399
|
+
const source = input.source ?? "user";
|
|
22083
22400
|
this.replies.set(input.senderId, input.reply);
|
|
22084
22401
|
this.hub.deliver(
|
|
22085
|
-
{ key: input.senderId, text: input.text, messageId: `tg-${++this.seq}
|
|
22402
|
+
{ key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source },
|
|
22086
22403
|
Date.now()
|
|
22087
22404
|
);
|
|
22088
22405
|
console.error(
|
|
22089
|
-
`[telegram-native] op=inbound key=${input.senderId} accountId=${input.accountId} botType=${input.role} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
22406
|
+
`[telegram-native] op=inbound key=${input.senderId} accountId=${input.accountId} botType=${input.role} bytes=${bytes} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
22090
22407
|
);
|
|
22091
22408
|
if (!hadSubscriber && !this.spawning.has(input.senderId)) {
|
|
22092
22409
|
this.spawning.add(input.senderId);
|
|
@@ -22135,8 +22452,8 @@ function buildTelegramSpawnRequest(input) {
|
|
|
22135
22452
|
// app/lib/telegram/outbound/send-document.ts
|
|
22136
22453
|
import { realpathSync as realpathSync7 } from "fs";
|
|
22137
22454
|
import { readFile as readFile7, stat as stat7 } from "fs/promises";
|
|
22138
|
-
import { resolve as resolve30, basename as
|
|
22139
|
-
var
|
|
22455
|
+
import { resolve as resolve30, basename as basename12 } from "path";
|
|
22456
|
+
var TAG36 = "[telegram:outbound]";
|
|
22140
22457
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
22141
22458
|
async function sendTelegramDocument(input) {
|
|
22142
22459
|
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot3 } = input;
|
|
@@ -22152,16 +22469,16 @@ async function sendTelegramDocument(input) {
|
|
|
22152
22469
|
resolvedPath = realpathSync7(filePath);
|
|
22153
22470
|
const accountResolved = realpathSync7(accountDir);
|
|
22154
22471
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
22155
|
-
console.error(`${
|
|
22472
|
+
console.error(`${TAG36} document REJECTED reason=outside_account_directory`);
|
|
22156
22473
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
22157
22474
|
}
|
|
22158
22475
|
} catch (err) {
|
|
22159
22476
|
const code = err.code;
|
|
22160
22477
|
if (code === "ENOENT") {
|
|
22161
|
-
console.error(`${
|
|
22478
|
+
console.error(`${TAG36} document ENOENT path=${filePath}`);
|
|
22162
22479
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
22163
22480
|
}
|
|
22164
|
-
console.error(`${
|
|
22481
|
+
console.error(`${TAG36} document path error: ${String(err)}`);
|
|
22165
22482
|
return { ok: false, status: 500, error: String(err) };
|
|
22166
22483
|
}
|
|
22167
22484
|
const fileStat = await stat7(resolvedPath);
|
|
@@ -22172,7 +22489,7 @@ async function sendTelegramDocument(input) {
|
|
|
22172
22489
|
error: `File exceeds 50 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
|
|
22173
22490
|
};
|
|
22174
22491
|
}
|
|
22175
|
-
const filename =
|
|
22492
|
+
const filename = basename12(resolvedPath);
|
|
22176
22493
|
const buffer = Buffer.from(await readFile7(resolvedPath));
|
|
22177
22494
|
const form = new FormData();
|
|
22178
22495
|
form.append("chat_id", String(chatId));
|
|
@@ -22194,13 +22511,13 @@ async function sendTelegramDocument(input) {
|
|
|
22194
22511
|
error = e instanceof Error ? e.message : String(e);
|
|
22195
22512
|
}
|
|
22196
22513
|
console.error(
|
|
22197
|
-
`${
|
|
22514
|
+
`${TAG36} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
22198
22515
|
);
|
|
22199
22516
|
return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
22200
22517
|
}
|
|
22201
22518
|
|
|
22202
22519
|
// app/lib/telegram/outbound/file-delivery.ts
|
|
22203
|
-
var
|
|
22520
|
+
var TAG37 = "[telegram:outbound]";
|
|
22204
22521
|
function platformRoot2() {
|
|
22205
22522
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
22206
22523
|
}
|
|
@@ -22212,11 +22529,11 @@ function makeTelegramSendFile(entry) {
|
|
|
22212
22529
|
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
22213
22530
|
}
|
|
22214
22531
|
if (!botToken) {
|
|
22215
|
-
console.error(`${
|
|
22532
|
+
console.error(`${TAG37} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
22216
22533
|
return { ok: false, error: "no-bot-token" };
|
|
22217
22534
|
}
|
|
22218
22535
|
if (entry.replyTarget == null) {
|
|
22219
|
-
console.error(`${
|
|
22536
|
+
console.error(`${TAG37} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
22220
22537
|
return { ok: false, error: "no-reply-target" };
|
|
22221
22538
|
}
|
|
22222
22539
|
let maxyAccountId;
|
|
@@ -22224,7 +22541,7 @@ function makeTelegramSendFile(entry) {
|
|
|
22224
22541
|
maxyAccountId = resolvePlatformAccountId();
|
|
22225
22542
|
} catch (err) {
|
|
22226
22543
|
console.error(
|
|
22227
|
-
`${
|
|
22544
|
+
`${TAG37} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
22228
22545
|
);
|
|
22229
22546
|
return { ok: false, error: "account-unresolved" };
|
|
22230
22547
|
}
|
|
@@ -22242,7 +22559,7 @@ function makeTelegramSendFile(entry) {
|
|
|
22242
22559
|
function makeTelegramFileDelivery(entry) {
|
|
22243
22560
|
return makeFileDelivery({
|
|
22244
22561
|
entry,
|
|
22245
|
-
tag:
|
|
22562
|
+
tag: TAG37,
|
|
22246
22563
|
channel: "telegram",
|
|
22247
22564
|
sendFile: makeTelegramSendFile(entry)
|
|
22248
22565
|
});
|
|
@@ -22292,7 +22609,7 @@ function telegramServerPath() {
|
|
|
22292
22609
|
|
|
22293
22610
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
22294
22611
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
22295
|
-
var
|
|
22612
|
+
var TAG38 = "[public-session-review]";
|
|
22296
22613
|
var CONSUMED_CAP = 500;
|
|
22297
22614
|
var consumed = /* @__PURE__ */ new Set();
|
|
22298
22615
|
function consumeOnce(sessionId) {
|
|
@@ -22319,7 +22636,7 @@ async function fetchJsonl(sessionId) {
|
|
|
22319
22636
|
return await res.text();
|
|
22320
22637
|
} catch (err) {
|
|
22321
22638
|
console.error(
|
|
22322
|
-
`${
|
|
22639
|
+
`${TAG38} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
22323
22640
|
);
|
|
22324
22641
|
return null;
|
|
22325
22642
|
}
|
|
@@ -22343,7 +22660,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
22343
22660
|
const res = await fetch(url);
|
|
22344
22661
|
if (!res.ok) {
|
|
22345
22662
|
console.error(
|
|
22346
|
-
`${
|
|
22663
|
+
`${TAG38} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
22347
22664
|
);
|
|
22348
22665
|
return [];
|
|
22349
22666
|
}
|
|
@@ -22351,7 +22668,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
22351
22668
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
22352
22669
|
} catch (err) {
|
|
22353
22670
|
console.error(
|
|
22354
|
-
`${
|
|
22671
|
+
`${TAG38} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
22355
22672
|
);
|
|
22356
22673
|
return [];
|
|
22357
22674
|
}
|
|
@@ -22380,7 +22697,7 @@ function composeInitialMessage(input) {
|
|
|
22380
22697
|
}
|
|
22381
22698
|
async function dispatchReviewer(input, initialMessage) {
|
|
22382
22699
|
const sessionId = randomUUID13();
|
|
22383
|
-
console.log(`${
|
|
22700
|
+
console.log(`${TAG38} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
22384
22701
|
const spawned = await managerRcSpawn({
|
|
22385
22702
|
sessionId,
|
|
22386
22703
|
initialMessage,
|
|
@@ -22400,21 +22717,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
22400
22717
|
const dispatchedAt = Date.now();
|
|
22401
22718
|
if (consumed.has(input.sessionId)) {
|
|
22402
22719
|
console.log(
|
|
22403
|
-
`${
|
|
22720
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
22404
22721
|
);
|
|
22405
22722
|
return;
|
|
22406
22723
|
}
|
|
22407
22724
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
22408
22725
|
if (!jsonl) {
|
|
22409
22726
|
console.log(
|
|
22410
|
-
`${
|
|
22727
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
22411
22728
|
);
|
|
22412
22729
|
return;
|
|
22413
22730
|
}
|
|
22414
22731
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
22415
22732
|
if (operatorTurns === 0) {
|
|
22416
22733
|
console.log(
|
|
22417
|
-
`${
|
|
22734
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
22418
22735
|
);
|
|
22419
22736
|
return;
|
|
22420
22737
|
}
|
|
@@ -22428,23 +22745,34 @@ async function firePublicSessionEndReview(input) {
|
|
|
22428
22745
|
});
|
|
22429
22746
|
if (!consumeOnce(input.sessionId)) {
|
|
22430
22747
|
console.log(
|
|
22431
|
-
`${
|
|
22748
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
22432
22749
|
);
|
|
22433
22750
|
return;
|
|
22434
22751
|
}
|
|
22435
22752
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
22436
22753
|
if (!dispatched.ok) {
|
|
22437
22754
|
console.error(
|
|
22438
|
-
`${
|
|
22755
|
+
`${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
22439
22756
|
);
|
|
22440
22757
|
return;
|
|
22441
22758
|
}
|
|
22442
22759
|
console.log(
|
|
22443
|
-
`${
|
|
22760
|
+
`${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
22761
|
);
|
|
22445
22762
|
}
|
|
22446
22763
|
|
|
22447
22764
|
// app/lib/whatsapp/inbound/channel-admin-binding-drift.ts
|
|
22765
|
+
function findAccountManagerDrift(accountManagers, adminPhones, validAccountIds) {
|
|
22766
|
+
const drift = [];
|
|
22767
|
+
for (const [phone, managesAccount] of Object.entries(accountManagers)) {
|
|
22768
|
+
if (!validAccountIds.includes(managesAccount)) {
|
|
22769
|
+
drift.push({ phone, managesAccount, reason: "not-in-registry" });
|
|
22770
|
+
} else if (adminPhones.some((a) => phonesMatch(a, phone))) {
|
|
22771
|
+
drift.push({ phone, managesAccount, reason: "phone-also-admin" });
|
|
22772
|
+
}
|
|
22773
|
+
}
|
|
22774
|
+
return drift;
|
|
22775
|
+
}
|
|
22448
22776
|
function findChannelAdminBindingDrift(adminPhones, admins, users) {
|
|
22449
22777
|
const drift = [];
|
|
22450
22778
|
for (const phone of adminPhones) {
|
|
@@ -22454,6 +22782,10 @@ function findChannelAdminBindingDrift(adminPhones, admins, users) {
|
|
|
22454
22782
|
}
|
|
22455
22783
|
return drift;
|
|
22456
22784
|
}
|
|
22785
|
+
function classifyAdminPhonesForAccount(isSocketOwner, adminPhones, admins, users) {
|
|
22786
|
+
if (!isSocketOwner) return { nonSocketPhones: [...adminPhones], drift: [] };
|
|
22787
|
+
return { nonSocketPhones: [], drift: findChannelAdminBindingDrift(adminPhones, admins, users) };
|
|
22788
|
+
}
|
|
22457
22789
|
function warnOnChannelAdminBindingDrift() {
|
|
22458
22790
|
let users;
|
|
22459
22791
|
try {
|
|
@@ -22467,15 +22799,39 @@ function warnOnChannelAdminBindingDrift() {
|
|
|
22467
22799
|
} catch {
|
|
22468
22800
|
return;
|
|
22469
22801
|
}
|
|
22802
|
+
let socketOwner;
|
|
22803
|
+
try {
|
|
22804
|
+
socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
|
|
22805
|
+
} catch {
|
|
22806
|
+
socketOwner = null;
|
|
22807
|
+
}
|
|
22808
|
+
const validAccountIds = accounts.map((a) => a.accountId);
|
|
22470
22809
|
for (const acct of accounts) {
|
|
22471
22810
|
const adminPhones = readAdminPhones(acct.accountDir);
|
|
22472
|
-
const
|
|
22811
|
+
const isSocketOwner = socketOwner === null || acct.accountId === socketOwner;
|
|
22812
|
+
const { nonSocketPhones, drift } = classifyAdminPhonesForAccount(
|
|
22813
|
+
isSocketOwner,
|
|
22814
|
+
adminPhones,
|
|
22815
|
+
acct.config.admins ?? [],
|
|
22816
|
+
users
|
|
22817
|
+
);
|
|
22818
|
+
for (const phone of nonSocketPhones) {
|
|
22819
|
+
console.error(
|
|
22820
|
+
`[admin-identity] adminphones-on-non-socket-account accountId=${acct.accountId} phone=${phone} count=${nonSocketPhones.length}`
|
|
22821
|
+
);
|
|
22822
|
+
}
|
|
22473
22823
|
for (const d of drift) {
|
|
22474
22824
|
const tail = d.reason === "userid-not-admin" ? ` userId=${d.userId}` : "";
|
|
22475
22825
|
console.error(
|
|
22476
22826
|
`[admin-identity] binding-drift accountId=${acct.accountId} phone=${d.phone} reason=${d.reason}${tail}`
|
|
22477
22827
|
);
|
|
22478
22828
|
}
|
|
22829
|
+
const managerDrift = findAccountManagerDrift(readAccountManagers(acct.accountDir), adminPhones, validAccountIds);
|
|
22830
|
+
for (const d of managerDrift) {
|
|
22831
|
+
console.error(
|
|
22832
|
+
`[admin-identity] account-manager-drift accountId=${acct.accountId} phone=${d.phone} managesAccount=${d.managesAccount} reason=${d.reason}`
|
|
22833
|
+
);
|
|
22834
|
+
}
|
|
22479
22835
|
}
|
|
22480
22836
|
}
|
|
22481
22837
|
|
|
@@ -22839,9 +23195,13 @@ var waGateway = new WaGateway({
|
|
|
22839
23195
|
});
|
|
22840
23196
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
22841
23197
|
},
|
|
22842
|
-
ensureChannelSession: async ({ accountId, senderId, role, personId, gatewayUrl, serverPath }) => {
|
|
22843
|
-
|
|
22844
|
-
|
|
23198
|
+
ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath }) => {
|
|
23199
|
+
if (role === "admin") {
|
|
23200
|
+
const managesAccount = effectiveAccountId !== accountId ? effectiveAccountId : "none";
|
|
23201
|
+
console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
|
|
23202
|
+
}
|
|
23203
|
+
const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
|
|
23204
|
+
const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? void 0 : void 0;
|
|
22845
23205
|
if (role === "admin") {
|
|
22846
23206
|
console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
|
|
22847
23207
|
}
|
|
@@ -22987,6 +23347,22 @@ var chatRoutes = createChatRoutes({
|
|
|
22987
23347
|
handleInbound: (input) => webchatGateway.handleInbound(input),
|
|
22988
23348
|
awaitReply: (key, timeoutMs) => webchatGateway.awaitReply(key, timeoutMs)
|
|
22989
23349
|
});
|
|
23350
|
+
var scheduleInjectRoutes = createScheduleInjectRoutes({
|
|
23351
|
+
resolveAccount: () => resolveAccount(),
|
|
23352
|
+
waHandleInbound: (input) => waGateway.handleInbound(input),
|
|
23353
|
+
tgHandleInbound: (input) => telegramGateway.handleInbound(input),
|
|
23354
|
+
sendWhatsAppText: async (accountId, destination, text) => {
|
|
23355
|
+
const sock = getSocket(accountId);
|
|
23356
|
+
if (!sock) throw new Error("WhatsApp disconnected \u2014 cannot deliver scheduled reply");
|
|
23357
|
+
await sendTextMessage(sock, toWhatsappJid(destination), text, { accountId });
|
|
23358
|
+
},
|
|
23359
|
+
sendTelegramText: async (botToken, chatId, text) => {
|
|
23360
|
+
const sent = await sendTelegramText(botToken, chatId, text);
|
|
23361
|
+
if (!sent.ok) throw new Error(sent.error ?? "telegram send failed");
|
|
23362
|
+
},
|
|
23363
|
+
effectiveAccountFor: (accountId, accountDir, destination) => managedAccountFor(readAccountManagers(accountDir), destination) ?? accountId
|
|
23364
|
+
});
|
|
23365
|
+
app55.route("/api/channel/schedule-inject", scheduleInjectRoutes);
|
|
22990
23366
|
app55.use("*", clientIpMiddleware);
|
|
22991
23367
|
function allowsSameOriginFraming(path2) {
|
|
22992
23368
|
return path2 === "/vnc-viewer.html" || path2.startsWith("/api/admin/attachment/") || path2.startsWith("/api/public-reader/attachment/") || path2 === "/api/admin/files/download";
|
|
@@ -23963,7 +24339,7 @@ try {
|
|
|
23963
24339
|
} catch (err) {
|
|
23964
24340
|
console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
|
|
23965
24341
|
}
|
|
23966
|
-
var configDirForWhatsApp =
|
|
24342
|
+
var configDirForWhatsApp = basename13(MAXY_DIR) || ".maxy";
|
|
23967
24343
|
var bootAccount = resolveAccount();
|
|
23968
24344
|
if (bootAccount) {
|
|
23969
24345
|
migrateRemovedConfigKeys(bootAccount.accountDir);
|
|
@@ -23983,6 +24359,7 @@ reconcileEnabledPlugins(bootAccount?.accountDir, bootAccount?.config, bootAccoun
|
|
|
23983
24359
|
for (const acct of listValidAccounts()) {
|
|
23984
24360
|
cleanupLeakedPremiumSubs(acct.accountDir, acct.config, acct.accountId);
|
|
23985
24361
|
}
|
|
24362
|
+
purgeNonSocketAdminPhonesAtBoot();
|
|
23986
24363
|
warnOnChannelAdminBindingDrift();
|
|
23987
24364
|
var bootEnabled = Array.isArray(bootAccountConfig?.enabledPlugins) ? bootAccountConfig.enabledPlugins : [];
|
|
23988
24365
|
var bootDelivered = [];
|
|
@@ -24042,6 +24419,10 @@ init({
|
|
|
24042
24419
|
console.error(`[whatsapp:route] dropped reason=no-text-no-media senderId=${msg.senderPhone} mediaCount=${msg.media.length}`);
|
|
24043
24420
|
return;
|
|
24044
24421
|
}
|
|
24422
|
+
if (!msg.effectiveAccountId) {
|
|
24423
|
+
console.error(`[whatsapp:route] op=dropped reason=no-effective-account senderId=${msg.senderPhone}`);
|
|
24424
|
+
return;
|
|
24425
|
+
}
|
|
24045
24426
|
if (decision.role === "public") {
|
|
24046
24427
|
console.error(`[whatsapp:route] op=routed agentType=public personId=${decision.personId} senderId=${msg.senderPhone}`);
|
|
24047
24428
|
}
|
|
@@ -24050,6 +24431,7 @@ init({
|
|
|
24050
24431
|
});
|
|
24051
24432
|
await waGateway.handleInbound({
|
|
24052
24433
|
accountId: msg.accountId,
|
|
24434
|
+
effectiveAccountId: msg.effectiveAccountId,
|
|
24053
24435
|
senderId: msg.senderPhone,
|
|
24054
24436
|
role: decision.role,
|
|
24055
24437
|
personId: decision.personId,
|