@rubytech/create-maxy-code 0.1.480 → 0.1.482
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-20-storage-pages-create.md +82 -0
- package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +1 -1
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +1 -1
- package/payload/platform/plugins/storage-broker/PLUGIN.md +6 -2
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +23 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts +26 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +46 -4
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +30 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +3 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +25 -3
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +12 -0
- package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +6 -0
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +10 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/server.js +474 -219
package/payload/server/server.js
CHANGED
|
@@ -1563,7 +1563,7 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1563
1563
|
import { readFileSync as readFileSync42, existsSync as existsSync38, watchFile, readdirSync as readdirSync26, statSync as statSync17 } from "fs";
|
|
1564
1564
|
import { spawn as spawn3 } from "child_process";
|
|
1565
1565
|
import { createHash as createHash7 } from "crypto";
|
|
1566
|
-
import { resolve as
|
|
1566
|
+
import { resolve as resolve38, join as join45, basename as basename12 } from "path";
|
|
1567
1567
|
import { homedir as homedir4 } from "os";
|
|
1568
1568
|
import { monitorEventLoopDelay } from "perf_hooks";
|
|
1569
1569
|
|
|
@@ -2472,7 +2472,8 @@ var WhatsAppAccountSchema = z.object({
|
|
|
2472
2472
|
}).strict().optional().describe("React to incoming messages with an emoji to acknowledge receipt before the agent responds."),
|
|
2473
2473
|
debounceMs: z.number().int().nonnegative().optional().default(2e3).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. Default 2000ms catches most multi-message bursts. 0 means process each message immediately."),
|
|
2474
2474
|
publicAgent: z.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones for this account. Overrides the top-level publicAgent when set."),
|
|
2475
|
-
afterHoursMessage: z.string().optional().describe("Auto-reply text sent to public senders when the business is closed (outside opening hours). If not set, messages received outside business hours are silently skipped with no reply.")
|
|
2475
|
+
afterHoursMessage: z.string().optional().describe("Auto-reply text sent to public senders when the business is closed (outside opening hours). If not set, messages received outside business hours are silently skipped with no reply."),
|
|
2476
|
+
dispatchInbound: z.boolean().optional().describe("When false, this account's WhatsApp socket only observes: inbound is stored and readable but never dispatched to an agent. Default true. Set with set-dispatch-inbound.")
|
|
2476
2477
|
}).strict().superRefine((value, ctx) => {
|
|
2477
2478
|
if (value.dmPolicy !== "open") return;
|
|
2478
2479
|
const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
|
|
@@ -2836,7 +2837,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
2836
2837
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
2837
2838
|
console.error(`${TAG6} draining credential save queue\u2026`);
|
|
2838
2839
|
const timer = new Promise(
|
|
2839
|
-
(
|
|
2840
|
+
(resolve39) => setTimeout(() => resolve39("timeout"), timeoutMs)
|
|
2840
2841
|
);
|
|
2841
2842
|
const result = await Promise.race([
|
|
2842
2843
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -3013,11 +3014,11 @@ async function createWaSocket(opts) {
|
|
|
3013
3014
|
return sock;
|
|
3014
3015
|
}
|
|
3015
3016
|
async function waitForConnection(sock) {
|
|
3016
|
-
return new Promise((
|
|
3017
|
+
return new Promise((resolve39, reject) => {
|
|
3017
3018
|
const handler = (update) => {
|
|
3018
3019
|
if (update.connection === "open") {
|
|
3019
3020
|
sock.ev.off("connection.update", handler);
|
|
3020
|
-
|
|
3021
|
+
resolve39();
|
|
3021
3022
|
}
|
|
3022
3023
|
if (update.connection === "close") {
|
|
3023
3024
|
sock.ev.off("connection.update", handler);
|
|
@@ -3125,7 +3126,7 @@ function isTimeoutError(err) {
|
|
|
3125
3126
|
return Boolean(err?.__maxyTimeout);
|
|
3126
3127
|
}
|
|
3127
3128
|
function withTimeout(label, promise, timeoutMs) {
|
|
3128
|
-
return new Promise((
|
|
3129
|
+
return new Promise((resolve39, reject) => {
|
|
3129
3130
|
const timer = setTimeout(() => {
|
|
3130
3131
|
const err = new Error(`${label} timed out after ${timeoutMs}ms`);
|
|
3131
3132
|
err.__maxyTimeout = true;
|
|
@@ -3135,7 +3136,7 @@ function withTimeout(label, promise, timeoutMs) {
|
|
|
3135
3136
|
promise.then(
|
|
3136
3137
|
(value) => {
|
|
3137
3138
|
clearTimeout(timer);
|
|
3138
|
-
|
|
3139
|
+
resolve39(value);
|
|
3139
3140
|
},
|
|
3140
3141
|
(err) => {
|
|
3141
3142
|
clearTimeout(timer);
|
|
@@ -3622,6 +3623,18 @@ function checkGroupAccess(_params) {
|
|
|
3622
3623
|
return { allowed: false, reason: "group-observe-only", agentType: "public" };
|
|
3623
3624
|
}
|
|
3624
3625
|
|
|
3626
|
+
// app/lib/whatsapp/inbound/dispatch-mode.ts
|
|
3627
|
+
function shouldDispatchInbound(config, accountId) {
|
|
3628
|
+
return config.accounts?.[accountId]?.dispatchInbound ?? true;
|
|
3629
|
+
}
|
|
3630
|
+
function decideDispatch(conn, payload, deps) {
|
|
3631
|
+
if (shouldDispatchInbound(deps.config, conn.accountId)) {
|
|
3632
|
+
deps.emit(payload);
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
deps.log(`[whatsapp:observe] op=dispatch-suppressed account=${conn.accountId} sender=${payload.senderPhone} agentType=${payload.agentType}`);
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3625
3638
|
// app/lib/access-gate.ts
|
|
3626
3639
|
import neo4j from "neo4j-driver";
|
|
3627
3640
|
import { readFileSync as readFileSync6 } from "fs";
|
|
@@ -4175,8 +4188,8 @@ async function persistWhatsAppMessage(input) {
|
|
|
4175
4188
|
const { givenName, familyName } = splitName(input.pushName);
|
|
4176
4189
|
const prev = sessionWriteLocks.get(input.cacheKey);
|
|
4177
4190
|
let release;
|
|
4178
|
-
const mine = new Promise((
|
|
4179
|
-
release =
|
|
4191
|
+
const mine = new Promise((resolve39) => {
|
|
4192
|
+
release = resolve39;
|
|
4180
4193
|
});
|
|
4181
4194
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
4182
4195
|
sessionWriteLocks.set(input.cacheKey, chained);
|
|
@@ -5484,6 +5497,16 @@ var configDir = null;
|
|
|
5484
5497
|
var whatsAppConfig = {};
|
|
5485
5498
|
var onInboundMessage = null;
|
|
5486
5499
|
var initialized = false;
|
|
5500
|
+
function dispatchInboundPayload(conn, payload) {
|
|
5501
|
+
if (!onInboundMessage) return;
|
|
5502
|
+
decideDispatch(conn, payload, { config: whatsAppConfig, emit: onInboundMessage, log: (l) => console.error(l) });
|
|
5503
|
+
}
|
|
5504
|
+
function isObserveAccount(accountId) {
|
|
5505
|
+
return !shouldDispatchInbound(whatsAppConfig, accountId);
|
|
5506
|
+
}
|
|
5507
|
+
function getWhatsAppConfig() {
|
|
5508
|
+
return whatsAppConfig;
|
|
5509
|
+
}
|
|
5487
5510
|
var messageStore = /* @__PURE__ */ new Map();
|
|
5488
5511
|
function messageStoreKey(accountId, remoteJid) {
|
|
5489
5512
|
return `${accountId}:${remoteJid}`;
|
|
@@ -5903,11 +5926,11 @@ async function connectWithReconnect(conn) {
|
|
|
5903
5926
|
console.error(
|
|
5904
5927
|
`${TAG17} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
5905
5928
|
);
|
|
5906
|
-
await new Promise((
|
|
5907
|
-
const timer = setTimeout(
|
|
5929
|
+
await new Promise((resolve39) => {
|
|
5930
|
+
const timer = setTimeout(resolve39, delay);
|
|
5908
5931
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
5909
5932
|
clearTimeout(timer);
|
|
5910
|
-
|
|
5933
|
+
resolve39();
|
|
5911
5934
|
}, { once: true });
|
|
5912
5935
|
});
|
|
5913
5936
|
}
|
|
@@ -5915,16 +5938,16 @@ async function connectWithReconnect(conn) {
|
|
|
5915
5938
|
}
|
|
5916
5939
|
}
|
|
5917
5940
|
function waitForDisconnectEvent(conn) {
|
|
5918
|
-
return new Promise((
|
|
5941
|
+
return new Promise((resolve39) => {
|
|
5919
5942
|
if (!conn.sock) {
|
|
5920
|
-
|
|
5943
|
+
resolve39();
|
|
5921
5944
|
return;
|
|
5922
5945
|
}
|
|
5923
5946
|
const sock = conn.sock;
|
|
5924
5947
|
const handler = (update) => {
|
|
5925
5948
|
if (update.connection === "close") {
|
|
5926
5949
|
sock.ev.off("connection.update", handler);
|
|
5927
|
-
|
|
5950
|
+
resolve39();
|
|
5928
5951
|
}
|
|
5929
5952
|
};
|
|
5930
5953
|
sock.ev.on("connection.update", handler);
|
|
@@ -5977,7 +6000,7 @@ function monitorInbound(conn) {
|
|
|
5977
6000
|
if (entries.length > 1) {
|
|
5978
6001
|
console.error(`${TAG17} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
5979
6002
|
}
|
|
5980
|
-
|
|
6003
|
+
dispatchInboundPayload(conn, combineInboundBatch(entries));
|
|
5981
6004
|
},
|
|
5982
6005
|
onError: (err) => {
|
|
5983
6006
|
console.error(`${TAG17} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
@@ -6202,7 +6225,7 @@ function monitorInbound(conn) {
|
|
|
6202
6225
|
const currentSock = conn.sock;
|
|
6203
6226
|
if (currentSock) await sendComposing(currentSock, remoteJid);
|
|
6204
6227
|
};
|
|
6205
|
-
|
|
6228
|
+
dispatchInboundPayload(conn, buildSelfChatDispatch({
|
|
6206
6229
|
accountId: conn.accountId,
|
|
6207
6230
|
selfPhone: conn.selfPhone,
|
|
6208
6231
|
remoteJid,
|
|
@@ -6311,7 +6334,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
6311
6334
|
const currentSock = conn.sock;
|
|
6312
6335
|
if (currentSock) await sendComposing(currentSock, remoteJid);
|
|
6313
6336
|
};
|
|
6314
|
-
|
|
6337
|
+
dispatchInboundPayload(conn, {
|
|
6315
6338
|
accountId: conn.accountId,
|
|
6316
6339
|
agentType: "admin",
|
|
6317
6340
|
senderPhone: senderPhone2,
|
|
@@ -6358,8 +6381,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
6358
6381
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
6359
6382
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
6360
6383
|
let resolvePending;
|
|
6361
|
-
const sttPending = new Promise((
|
|
6362
|
-
resolvePending =
|
|
6384
|
+
const sttPending = new Promise((resolve39) => {
|
|
6385
|
+
resolvePending = resolve39;
|
|
6363
6386
|
});
|
|
6364
6387
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
6365
6388
|
try {
|
|
@@ -6477,7 +6500,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
6477
6500
|
if (conn.debouncer) {
|
|
6478
6501
|
conn.debouncer.enqueue(payload);
|
|
6479
6502
|
} else {
|
|
6480
|
-
|
|
6503
|
+
dispatchInboundPayload(conn, payload);
|
|
6481
6504
|
}
|
|
6482
6505
|
}
|
|
6483
6506
|
|
|
@@ -6787,20 +6810,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
6787
6810
|
|
|
6788
6811
|
// server/routes/health.ts
|
|
6789
6812
|
function checkPort(port2, timeoutMs = 500) {
|
|
6790
|
-
return new Promise((
|
|
6813
|
+
return new Promise((resolve39) => {
|
|
6791
6814
|
const socket = createConnection2(port2, "127.0.0.1");
|
|
6792
6815
|
socket.setTimeout(timeoutMs);
|
|
6793
6816
|
socket.once("connect", () => {
|
|
6794
6817
|
socket.destroy();
|
|
6795
|
-
|
|
6818
|
+
resolve39(true);
|
|
6796
6819
|
});
|
|
6797
6820
|
socket.once("error", () => {
|
|
6798
6821
|
socket.destroy();
|
|
6799
|
-
|
|
6822
|
+
resolve39(false);
|
|
6800
6823
|
});
|
|
6801
6824
|
socket.once("timeout", () => {
|
|
6802
6825
|
socket.destroy();
|
|
6803
|
-
|
|
6826
|
+
resolve39(false);
|
|
6804
6827
|
});
|
|
6805
6828
|
});
|
|
6806
6829
|
}
|
|
@@ -7751,6 +7774,48 @@ function setAccountManager(accountDir, phone, subAccountId, mode = "active") {
|
|
|
7751
7774
|
return { ok: false, error: msg };
|
|
7752
7775
|
}
|
|
7753
7776
|
}
|
|
7777
|
+
function setDispatchInbound(accountDir, accountId, value) {
|
|
7778
|
+
const id = accountId.trim();
|
|
7779
|
+
if (!id) return { ok: false, error: "Missing account id." };
|
|
7780
|
+
let validIds;
|
|
7781
|
+
try {
|
|
7782
|
+
validIds = listValidAccounts().map((a) => a.accountId);
|
|
7783
|
+
} catch (err) {
|
|
7784
|
+
return { ok: false, error: `Could not read the account registry to validate the account: ${err instanceof Error ? err.message : String(err)}. Try again.` };
|
|
7785
|
+
}
|
|
7786
|
+
if (!validIds.includes(id)) {
|
|
7787
|
+
return { ok: false, error: `Account "${id}" is not in this install's account registry.` };
|
|
7788
|
+
}
|
|
7789
|
+
try {
|
|
7790
|
+
const config = readConfig(accountDir);
|
|
7791
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") {
|
|
7792
|
+
config.whatsapp = {};
|
|
7793
|
+
}
|
|
7794
|
+
const wa = config.whatsapp;
|
|
7795
|
+
if (!wa.accounts || typeof wa.accounts !== "object") {
|
|
7796
|
+
wa.accounts = {};
|
|
7797
|
+
}
|
|
7798
|
+
const accounts = wa.accounts;
|
|
7799
|
+
if (!accounts[id] || typeof accounts[id] !== "object") {
|
|
7800
|
+
accounts[id] = { name: "Main" };
|
|
7801
|
+
}
|
|
7802
|
+
accounts[id].dispatchInbound = value;
|
|
7803
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
7804
|
+
if (!parsed.success) {
|
|
7805
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
7806
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
7807
|
+
}
|
|
7808
|
+
config.whatsapp = parsed.data;
|
|
7809
|
+
writeConfig(accountDir, config);
|
|
7810
|
+
console.error(`${TAG21} set dispatchInbound account=${id} value=${value}`);
|
|
7811
|
+
reloadManagerConfig(accountDir);
|
|
7812
|
+
return { ok: true };
|
|
7813
|
+
} catch (err) {
|
|
7814
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7815
|
+
console.error(`${TAG21} setDispatchInbound failed: ${msg}`);
|
|
7816
|
+
return { ok: false, error: msg };
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7754
7819
|
function clearAccountManager(accountDir, phone) {
|
|
7755
7820
|
const normalized = phone.trim();
|
|
7756
7821
|
try {
|
|
@@ -8089,8 +8154,8 @@ async function startLogin(opts) {
|
|
|
8089
8154
|
await clearAuth(authDir);
|
|
8090
8155
|
let resolveCode = null;
|
|
8091
8156
|
let rejectCode = null;
|
|
8092
|
-
const codePromise = new Promise((
|
|
8093
|
-
resolveCode =
|
|
8157
|
+
const codePromise = new Promise((resolve39, reject) => {
|
|
8158
|
+
resolveCode = resolve39;
|
|
8094
8159
|
rejectCode = reject;
|
|
8095
8160
|
});
|
|
8096
8161
|
const codeTimer = setTimeout(
|
|
@@ -8489,6 +8554,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
8489
8554
|
return entries;
|
|
8490
8555
|
}
|
|
8491
8556
|
|
|
8557
|
+
// app/lib/whatsapp/outbound/reply-guard.ts
|
|
8558
|
+
function admitReply(input) {
|
|
8559
|
+
if (isGroupJid(input.remoteJid)) return { ok: false, status: 403, reason: "group-send-blocked" };
|
|
8560
|
+
if (!isUserJid(input.remoteJid)) return { ok: false, status: 400, reason: "invalid-remote-jid" };
|
|
8561
|
+
if (!input.hasConversation) return { ok: false, status: 403, reason: "no-existing-conversation" };
|
|
8562
|
+
return { ok: true };
|
|
8563
|
+
}
|
|
8564
|
+
|
|
8492
8565
|
// app/lib/whatsapp-reader/select-sessions.ts
|
|
8493
8566
|
function isReaderChannelSession(role, channel) {
|
|
8494
8567
|
return role === "admin" && (channel === "whatsapp" || channel === "telegram") || role === "public" && (channel === "whatsapp" || channel === "webchat");
|
|
@@ -8647,6 +8720,15 @@ function houseSocketScopeDenial(c, op) {
|
|
|
8647
8720
|
console.error(`${TAG25} op=authz-deny route=${op} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
|
|
8648
8721
|
return c.json({ ok: false, error: "Not authorized. The WhatsApp connection and its settings are house-scoped; this session may only act on its own account." }, 403);
|
|
8649
8722
|
}
|
|
8723
|
+
function selfOrHouseScopeDenial(c, targetAccountId, op) {
|
|
8724
|
+
const { decision, callerAccountId, houseAdminScope } = evaluateCallerScope(c, targetAccountId);
|
|
8725
|
+
if (decision.authorized) {
|
|
8726
|
+
console.error(`${TAG25} op=authz-ok route=${op} target=${targetAccountId} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
|
|
8727
|
+
return null;
|
|
8728
|
+
}
|
|
8729
|
+
console.error(`${TAG25} op=authz-deny route=${op} target=${targetAccountId} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
|
|
8730
|
+
return c.json({ ok: false, error: "Not authorized. This session may only pair or manage its own account." }, 403);
|
|
8731
|
+
}
|
|
8650
8732
|
var app2 = new Hono();
|
|
8651
8733
|
app2.get("/status", (c) => {
|
|
8652
8734
|
try {
|
|
@@ -8658,8 +8740,12 @@ app2.get("/status", (c) => {
|
|
|
8658
8740
|
const credsRoot = join12(MAXY_DIR, "credentials", "whatsapp");
|
|
8659
8741
|
const accountDir = resolveAccount()?.accountDir ?? null;
|
|
8660
8742
|
const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds, activeLoginAccountIds: activeLoginIds });
|
|
8661
|
-
const
|
|
8662
|
-
const
|
|
8743
|
+
const cfg = getWhatsAppConfig();
|
|
8744
|
+
const accounts = [...live, ...reconciled].map((a) => ({
|
|
8745
|
+
...a,
|
|
8746
|
+
dispatchInbound: shouldDispatchInbound(cfg, a.accountId)
|
|
8747
|
+
}));
|
|
8748
|
+
const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}:${a.dispatchInbound ? "dispatch" : "observe"}`).join(", ");
|
|
8663
8749
|
console.error(`${TAG25} status accounts=${accounts.length} [${summary}]`);
|
|
8664
8750
|
const reachableAccountIds = [
|
|
8665
8751
|
.../* @__PURE__ */ new Set([...listStoredAccountIds(), ...listConnectionPlatformAccountIds()])
|
|
@@ -8679,10 +8765,10 @@ app2.get("/status", (c) => {
|
|
|
8679
8765
|
});
|
|
8680
8766
|
app2.post("/login/start", async (c) => {
|
|
8681
8767
|
try {
|
|
8682
|
-
const denial = houseSocketScopeDenial(c, "login/start");
|
|
8683
|
-
if (denial) return denial;
|
|
8684
8768
|
const body = await c.req.json().catch(() => ({}));
|
|
8685
8769
|
const accountId = validateAccountId(body.accountId);
|
|
8770
|
+
const denial = selfOrHouseScopeDenial(c, accountId, "login/start");
|
|
8771
|
+
if (denial) return denial;
|
|
8686
8772
|
const force = body.force ?? false;
|
|
8687
8773
|
const phone = typeof body.phone === "string" ? body.phone.trim() : "";
|
|
8688
8774
|
if (!phone) {
|
|
@@ -8700,10 +8786,10 @@ app2.post("/login/start", async (c) => {
|
|
|
8700
8786
|
});
|
|
8701
8787
|
app2.post("/login/wait", async (c) => {
|
|
8702
8788
|
try {
|
|
8703
|
-
const denial = houseSocketScopeDenial(c, "login/wait");
|
|
8704
|
-
if (denial) return denial;
|
|
8705
8789
|
const body = await c.req.json().catch(() => ({}));
|
|
8706
8790
|
const accountId = validateAccountId(body.accountId);
|
|
8791
|
+
const denial = selfOrHouseScopeDenial(c, accountId, "login/wait");
|
|
8792
|
+
if (denial) return denial;
|
|
8707
8793
|
const timeoutMs = body.timeoutMs ?? 6e4;
|
|
8708
8794
|
const result = await waitForLogin({ accountId, timeoutMs });
|
|
8709
8795
|
console.error(`${TAG25} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
|
|
@@ -8720,10 +8806,10 @@ app2.post("/login/wait", async (c) => {
|
|
|
8720
8806
|
});
|
|
8721
8807
|
app2.post("/disconnect", async (c) => {
|
|
8722
8808
|
try {
|
|
8723
|
-
const denial = houseSocketScopeDenial(c, "disconnect");
|
|
8724
|
-
if (denial) return denial;
|
|
8725
8809
|
const body = await c.req.json().catch(() => ({}));
|
|
8726
8810
|
const accountId = validateAccountId(body.accountId);
|
|
8811
|
+
const denial = selfOrHouseScopeDenial(c, accountId, "disconnect");
|
|
8812
|
+
if (denial) return denial;
|
|
8727
8813
|
await stopConnection(accountId);
|
|
8728
8814
|
return c.json({ disconnected: true, accountId });
|
|
8729
8815
|
} catch (err) {
|
|
@@ -8733,10 +8819,10 @@ app2.post("/disconnect", async (c) => {
|
|
|
8733
8819
|
});
|
|
8734
8820
|
app2.post("/reconnect", async (c) => {
|
|
8735
8821
|
try {
|
|
8736
|
-
const denial = houseSocketScopeDenial(c, "reconnect");
|
|
8737
|
-
if (denial) return denial;
|
|
8738
8822
|
const body = await c.req.json().catch(() => ({}));
|
|
8739
8823
|
const accountId = validateAccountId(body.accountId);
|
|
8824
|
+
const denial = selfOrHouseScopeDenial(c, accountId, "reconnect");
|
|
8825
|
+
if (denial) return denial;
|
|
8740
8826
|
await startConnection(accountId);
|
|
8741
8827
|
return c.json({ reconnecting: true, accountId });
|
|
8742
8828
|
} catch (err) {
|
|
@@ -8751,6 +8837,22 @@ app2.post("/config", async (c) => {
|
|
|
8751
8837
|
if (!action || typeof action !== "string") {
|
|
8752
8838
|
return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
|
|
8753
8839
|
}
|
|
8840
|
+
if (action === "set-dispatch-inbound") {
|
|
8841
|
+
if (!accountId || typeof accountId !== "string") {
|
|
8842
|
+
return c.json({ ok: false, error: 'Missing required field "accountId" (the account whose socket to set).' }, 400);
|
|
8843
|
+
}
|
|
8844
|
+
const target = validateAccountId(accountId);
|
|
8845
|
+
const scopeDenial = selfOrHouseScopeDenial(c, target, `config action=set-dispatch-inbound target=${target}`);
|
|
8846
|
+
if (scopeDenial) return scopeDenial;
|
|
8847
|
+
if (typeof body.dispatchInbound !== "boolean") {
|
|
8848
|
+
return c.json({ ok: false, error: 'Missing required boolean field "dispatchInbound".' }, 400);
|
|
8849
|
+
}
|
|
8850
|
+
const acct = resolveAccount();
|
|
8851
|
+
if (!acct) return c.json({ ok: false, error: "No account configured." }, 500);
|
|
8852
|
+
const result = setDispatchInbound(acct.accountDir, target, body.dispatchInbound);
|
|
8853
|
+
console.error(`${TAG25} config action=set-dispatch-inbound account=${target} dispatchInbound=${body.dispatchInbound} ok=${result.ok}`);
|
|
8854
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
8855
|
+
}
|
|
8754
8856
|
const denial = houseSocketScopeDenial(c, `config action=${action}`);
|
|
8755
8857
|
if (denial) return denial;
|
|
8756
8858
|
const account = resolveAccount();
|
|
@@ -8961,6 +9063,65 @@ app2.post("/send-admin", async (c) => {
|
|
|
8961
9063
|
return c.json({ error: String(err) }, 500);
|
|
8962
9064
|
}
|
|
8963
9065
|
});
|
|
9066
|
+
app2.post("/reply", async (c) => {
|
|
9067
|
+
const sendId = process.hrtime.bigint().toString(36).slice(-8);
|
|
9068
|
+
try {
|
|
9069
|
+
const body = await c.req.json().catch(() => ({}));
|
|
9070
|
+
if (!body.accountId || typeof body.accountId !== "string") {
|
|
9071
|
+
return c.json({ error: "accountId is required" }, 400);
|
|
9072
|
+
}
|
|
9073
|
+
const accountId = validateAccountId(body.accountId);
|
|
9074
|
+
const denial = selfOrHouseScopeDenial(c, accountId, "reply");
|
|
9075
|
+
if (denial) return denial;
|
|
9076
|
+
const remoteJid = typeof body.remoteJid === "string" ? body.remoteJid : "";
|
|
9077
|
+
const text = typeof body.text === "string" && body.text.trim().length ? body.text : "";
|
|
9078
|
+
if (!remoteJid || !text) {
|
|
9079
|
+
return c.json({ error: "accountId, remoteJid and text are required" }, 400);
|
|
9080
|
+
}
|
|
9081
|
+
const storeKey = resolveReadStoreKey(accountId);
|
|
9082
|
+
const convo = readConversation(storeKey, remoteJid);
|
|
9083
|
+
const admit = admitReply({ remoteJid, hasConversation: convo.length > 0 });
|
|
9084
|
+
if (!admit.ok) {
|
|
9085
|
+
console.error(`[whatsapp:observe] op=reply-refused sendId=${sendId} account=${accountId} remoteJid=${remoteJid} reason=${admit.reason}`);
|
|
9086
|
+
return c.json({ error: admit.reason, reason: admit.reason }, admit.status);
|
|
9087
|
+
}
|
|
9088
|
+
const res = resolveSocket(accountId);
|
|
9089
|
+
if (!res.ok) {
|
|
9090
|
+
console.error(`[whatsapp:observe] op=reply-socket sendId=${sendId} account=${accountId} result=${res.reason} presentKeys=${res.presentKeys.join(",") || "none"}`);
|
|
9091
|
+
return c.json({ error: formatSocketResolutionMessage(res, accountId), reason: res.reason }, 503);
|
|
9092
|
+
}
|
|
9093
|
+
const sent = await sendTextMessage(res.sock, remoteJid, text, { accountId: res.accountId });
|
|
9094
|
+
if (!sent.success) {
|
|
9095
|
+
console.error(`[whatsapp:observe] op=reply-send-fail sendId=${sendId} account=${accountId} error=${sent.error ?? ""}`);
|
|
9096
|
+
return c.json({ error: sent.error ?? "send failed" }, 500);
|
|
9097
|
+
}
|
|
9098
|
+
if (sent.messageId) {
|
|
9099
|
+
const template = convo[convo.length - 1];
|
|
9100
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
9101
|
+
const rec = {
|
|
9102
|
+
messageId: `whatsapp-live:${res.accountId}:${remoteJid}:${sent.messageId}`,
|
|
9103
|
+
sessionId: template.sessionId,
|
|
9104
|
+
dateSent: nowIso,
|
|
9105
|
+
body: text,
|
|
9106
|
+
fromMe: true,
|
|
9107
|
+
senderTelephone: template.fromMe ? template.senderTelephone : "",
|
|
9108
|
+
senderName: null,
|
|
9109
|
+
remoteJid,
|
|
9110
|
+
msgKeyId: sent.messageId,
|
|
9111
|
+
quotedId: null,
|
|
9112
|
+
quotedSender: null,
|
|
9113
|
+
scope: template.scope,
|
|
9114
|
+
createdAt: nowIso
|
|
9115
|
+
};
|
|
9116
|
+
appendMessage(storeKey, rec);
|
|
9117
|
+
}
|
|
9118
|
+
console.error(`[whatsapp:observe] op=reply-sent sendId=${sendId} account=${accountId} socket=${res.accountId} waMessageId=${sent.messageId ?? ""}`);
|
|
9119
|
+
return c.json({ success: true, messageId: sent.messageId });
|
|
9120
|
+
} catch (err) {
|
|
9121
|
+
console.error(`${TAG25} reply error: ${String(err)}`);
|
|
9122
|
+
return c.json({ error: String(err) }, 500);
|
|
9123
|
+
}
|
|
9124
|
+
});
|
|
8964
9125
|
app2.get("/activity", (c) => {
|
|
8965
9126
|
try {
|
|
8966
9127
|
const accountId = c.req.query("accountId") ?? void 0;
|
|
@@ -9439,6 +9600,58 @@ app3.post("/pages/adopt", nameBodyLimit("pages"), async (c) => {
|
|
|
9439
9600
|
await session.close();
|
|
9440
9601
|
}
|
|
9441
9602
|
});
|
|
9603
|
+
app3.post("/pages/create", nameBodyLimit("pages"), async (c) => {
|
|
9604
|
+
const pub = randomUUID7().slice(0, 8);
|
|
9605
|
+
const account = caller(c);
|
|
9606
|
+
if (!account) {
|
|
9607
|
+
log("write", "pages", "none", "*", null, "deny", "no-caller");
|
|
9608
|
+
return c.json({ error: "no-caller" }, 403);
|
|
9609
|
+
}
|
|
9610
|
+
const parsed = await readJsonBody(c);
|
|
9611
|
+
if (!parsed.ok) return parsed.res;
|
|
9612
|
+
const { project } = parsed.body;
|
|
9613
|
+
if (!project || typeof project !== "string") return c.json({ error: "project-required" }, 400);
|
|
9614
|
+
console.error(`[pages-broker] op=request pub=${pub} caller=${account} project=${project}`);
|
|
9615
|
+
const session = getSession();
|
|
9616
|
+
try {
|
|
9617
|
+
const owner = await resolveOwner(session, "pages", project);
|
|
9618
|
+
if (owner !== null && owner !== account) {
|
|
9619
|
+
log("write", "pages", account, project, owner, "deny");
|
|
9620
|
+
console.error(
|
|
9621
|
+
`[pages-broker] op=create pub=${pub} project=${project} existedOnCf=unchecked created=false decision=deny reason=cross-account`
|
|
9622
|
+
);
|
|
9623
|
+
return c.json({ error: "cross-account" }, 403);
|
|
9624
|
+
}
|
|
9625
|
+
if (owner === account) {
|
|
9626
|
+
log("write", "pages", account, project, account, "allow");
|
|
9627
|
+
console.error(
|
|
9628
|
+
`[pages-broker] op=create pub=${pub} project=${project} existedOnCf=unchecked created=false decision=allow reason=already-owned`
|
|
9629
|
+
);
|
|
9630
|
+
return c.json({ project, created: false });
|
|
9631
|
+
}
|
|
9632
|
+
const exec = await makeHousePagesExec(platformRoot());
|
|
9633
|
+
const existedOnCf = (await exec.pagesProjectList()).some((p) => p.name === project);
|
|
9634
|
+
if (existedOnCf) {
|
|
9635
|
+
log("write", "pages", account, project, null, "deny", "exists-unregistered");
|
|
9636
|
+
console.error(
|
|
9637
|
+
`[pages-broker] op=create pub=${pub} project=${project} existedOnCf=true created=false decision=deny reason=exists-unregistered`
|
|
9638
|
+
);
|
|
9639
|
+
return c.json({ error: "exists-unregistered" }, 409);
|
|
9640
|
+
}
|
|
9641
|
+
await exec.pagesProjectCreate(project, "main");
|
|
9642
|
+
await registerResource(session, { accountId: account, kind: "pages", name: project, cfResourceId: project });
|
|
9643
|
+
log("write", "pages", account, project, account, "allow");
|
|
9644
|
+
console.error(
|
|
9645
|
+
`[pages-broker] op=create pub=${pub} project=${project} existedOnCf=false created=true decision=allow reason=owner`
|
|
9646
|
+
);
|
|
9647
|
+
return c.json({ project, created: true });
|
|
9648
|
+
} catch (err) {
|
|
9649
|
+
console.error(`[pages-broker] op=exit pub=${pub} result=error detail="${String(err)}"`);
|
|
9650
|
+
return c.json({ error: String(err) }, 500);
|
|
9651
|
+
} finally {
|
|
9652
|
+
await session.close();
|
|
9653
|
+
}
|
|
9654
|
+
});
|
|
9442
9655
|
app3.get("/d1/list", async (c) => {
|
|
9443
9656
|
const account = caller(c);
|
|
9444
9657
|
if (!account) return c.json({ error: "no-caller" }, 403);
|
|
@@ -14290,15 +14503,15 @@ function operatorRoleFor(config, userId) {
|
|
|
14290
14503
|
async function buildAccountOptionList(accounts, userId) {
|
|
14291
14504
|
const houseAccount = accounts.find((a) => a.config.role === "house");
|
|
14292
14505
|
console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
|
|
14293
|
-
const
|
|
14506
|
+
const resolve39 = async (a) => {
|
|
14294
14507
|
const businessName = await fetchAccountName(a.accountId) || void 0;
|
|
14295
14508
|
return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
|
|
14296
14509
|
};
|
|
14297
14510
|
const houseOptions = await Promise.all(
|
|
14298
|
-
accounts.filter((a) => a.config.role === "house").map(
|
|
14511
|
+
accounts.filter((a) => a.config.role === "house").map(resolve39)
|
|
14299
14512
|
);
|
|
14300
14513
|
const nonHouseOptions = await Promise.all(
|
|
14301
|
-
accounts.filter((a) => a.config.role !== "house").map(
|
|
14514
|
+
accounts.filter((a) => a.config.role !== "house").map(resolve39)
|
|
14302
14515
|
);
|
|
14303
14516
|
const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
|
|
14304
14517
|
nonHouseOptions.sort((x, y) => {
|
|
@@ -20315,6 +20528,76 @@ function isWithin(target, root) {
|
|
|
20315
20528
|
}
|
|
20316
20529
|
var sidebar_artefacts_default = app29;
|
|
20317
20530
|
|
|
20531
|
+
// server/channel-session-override.ts
|
|
20532
|
+
import { readFileSync as readFileSync34, writeFileSync as writeFileSync12, renameSync as renameSync9 } from "fs";
|
|
20533
|
+
import { join as join35 } from "path";
|
|
20534
|
+
var FILE2 = "channel-session-override.json";
|
|
20535
|
+
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}$/;
|
|
20536
|
+
function channelOverridePath(accountDir) {
|
|
20537
|
+
return join35(accountDir, FILE2);
|
|
20538
|
+
}
|
|
20539
|
+
function senderKey(channel, senderId) {
|
|
20540
|
+
return `${channel}:${senderId}`;
|
|
20541
|
+
}
|
|
20542
|
+
function readRaw2(accountDir) {
|
|
20543
|
+
try {
|
|
20544
|
+
return JSON.parse(readFileSync34(channelOverridePath(accountDir), "utf8"));
|
|
20545
|
+
} catch {
|
|
20546
|
+
return null;
|
|
20547
|
+
}
|
|
20548
|
+
}
|
|
20549
|
+
function readChannelOverrideId(accountDir, channel, senderId) {
|
|
20550
|
+
const raw = readRaw2(accountDir);
|
|
20551
|
+
if (!raw || !raw.bySender || typeof raw.bySender !== "object") return null;
|
|
20552
|
+
const v = raw.bySender[senderKey(channel, senderId)];
|
|
20553
|
+
return typeof v === "string" && UUID2.test(v) ? v : null;
|
|
20554
|
+
}
|
|
20555
|
+
function writeChannelOverrideId(accountDir, channel, senderId, sessionId) {
|
|
20556
|
+
const raw = readRaw2(accountDir);
|
|
20557
|
+
const bySender = {};
|
|
20558
|
+
if (raw?.bySender && typeof raw.bySender === "object") {
|
|
20559
|
+
for (const [k, v] of Object.entries(raw.bySender)) {
|
|
20560
|
+
if (typeof v === "string") bySender[k] = v;
|
|
20561
|
+
}
|
|
20562
|
+
}
|
|
20563
|
+
bySender[senderKey(channel, senderId)] = sessionId;
|
|
20564
|
+
const path3 = channelOverridePath(accountDir);
|
|
20565
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
20566
|
+
writeFileSync12(tmp, JSON.stringify({ bySender }, null, 2), "utf8");
|
|
20567
|
+
renameSync9(tmp, path3);
|
|
20568
|
+
}
|
|
20569
|
+
function clearChannelOverrideForSession(accountDir, sessionId) {
|
|
20570
|
+
const raw = readRaw2(accountDir);
|
|
20571
|
+
if (!raw?.bySender || typeof raw.bySender !== "object") return [];
|
|
20572
|
+
const kept = {};
|
|
20573
|
+
const cleared = [];
|
|
20574
|
+
for (const [k, v] of Object.entries(raw.bySender)) {
|
|
20575
|
+
if (typeof v !== "string") continue;
|
|
20576
|
+
if (v === sessionId) cleared.push(k);
|
|
20577
|
+
else kept[k] = v;
|
|
20578
|
+
}
|
|
20579
|
+
if (cleared.length === 0) return [];
|
|
20580
|
+
const path3 = channelOverridePath(accountDir);
|
|
20581
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
20582
|
+
writeFileSync12(tmp, JSON.stringify({ bySender: kept }, null, 2), "utf8");
|
|
20583
|
+
renameSync9(tmp, path3);
|
|
20584
|
+
return cleared;
|
|
20585
|
+
}
|
|
20586
|
+
|
|
20587
|
+
// server/routes/admin/clear-forward-pointers.ts
|
|
20588
|
+
function clearForwardPointers(sessionId) {
|
|
20589
|
+
try {
|
|
20590
|
+
for (const a of listValidAccounts()) {
|
|
20591
|
+
const cleared = clearChannelOverrideForSession(a.accountDir, sessionId);
|
|
20592
|
+
if (cleared.length > 0) {
|
|
20593
|
+
console.log(`[session-lifecycle] op=channel-forward-cleared sessionId=${sessionId.slice(0, 8)} account=${a.accountId} senderKeys=${cleared.join(",")}`);
|
|
20594
|
+
}
|
|
20595
|
+
}
|
|
20596
|
+
} catch (err) {
|
|
20597
|
+
console.error(`[session-lifecycle] op=channel-forward-clear-failed sessionId=${sessionId.slice(0, 8)} err=${err instanceof Error ? err.message : String(err)}`);
|
|
20598
|
+
}
|
|
20599
|
+
}
|
|
20600
|
+
|
|
20318
20601
|
// server/routes/admin/session-delete.ts
|
|
20319
20602
|
var app30 = new Hono();
|
|
20320
20603
|
app30.post("/", requireAdminSession, async (c) => {
|
|
@@ -20347,6 +20630,7 @@ app30.post("/", requireAdminSession, async (c) => {
|
|
|
20347
20630
|
return c.json({ error: "manager-unreachable" }, 502);
|
|
20348
20631
|
}
|
|
20349
20632
|
if (del.status === 204) {
|
|
20633
|
+
clearForwardPointers(sessionId);
|
|
20350
20634
|
console.log(`[session-delete] sessionId=${sessionId} deleted`);
|
|
20351
20635
|
return c.json({ deleted: true });
|
|
20352
20636
|
}
|
|
@@ -20415,6 +20699,7 @@ app32.post("/", requireAdminSession, async (c) => {
|
|
|
20415
20699
|
return c.json({ error: "manager-unreachable" }, 502);
|
|
20416
20700
|
}
|
|
20417
20701
|
if (res.status === 200) {
|
|
20702
|
+
if (mode === "archive") clearForwardPointers(sessionId);
|
|
20418
20703
|
const dir = mode === "archive" ? "top\u2192archive" : "archive\u2192top";
|
|
20419
20704
|
console.log(`[session-archive] sessionId=${sessionId.slice(0, 8)} moved ${dir}`);
|
|
20420
20705
|
return c.json({ archived: mode === "archive" });
|
|
@@ -20644,49 +20929,40 @@ var session_rc_spawn_default = app35;
|
|
|
20644
20929
|
// server/routes/admin/session-reseat.ts
|
|
20645
20930
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
20646
20931
|
|
|
20647
|
-
// server/
|
|
20648
|
-
import {
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
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}$/;
|
|
20652
|
-
function channelOverridePath(accountDir) {
|
|
20653
|
-
return join35(accountDir, FILE2);
|
|
20932
|
+
// server/wa-descriptor.ts
|
|
20933
|
+
import { resolve as resolve26, join as join36 } from "path";
|
|
20934
|
+
function waGatewayUrl() {
|
|
20935
|
+
return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
|
|
20654
20936
|
}
|
|
20655
|
-
function
|
|
20656
|
-
return
|
|
20937
|
+
function waServerPath() {
|
|
20938
|
+
return process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve26(process.env.MAXY_PLATFORM_ROOT ?? join36(__dirname, ".."), "services/whatsapp-channel/dist/server.js");
|
|
20657
20939
|
}
|
|
20658
|
-
function
|
|
20659
|
-
|
|
20660
|
-
return JSON.parse(readFileSync34(channelOverridePath(accountDir), "utf8"));
|
|
20661
|
-
} catch {
|
|
20662
|
-
return null;
|
|
20663
|
-
}
|
|
20940
|
+
function waChannelDescriptor(senderId) {
|
|
20941
|
+
return { senderId, gatewayUrl: waGatewayUrl(), serverPath: waServerPath() };
|
|
20664
20942
|
}
|
|
20665
|
-
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
return
|
|
20943
|
+
|
|
20944
|
+
// server/telegram-descriptor.ts
|
|
20945
|
+
import { resolve as resolve27, join as join37 } from "path";
|
|
20946
|
+
function telegramGatewayUrl() {
|
|
20947
|
+
return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
|
|
20670
20948
|
}
|
|
20671
|
-
function
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
if (typeof v === "string") bySender[k] = v;
|
|
20677
|
-
}
|
|
20678
|
-
}
|
|
20679
|
-
bySender[senderKey(channel, senderId)] = sessionId;
|
|
20680
|
-
const path3 = channelOverridePath(accountDir);
|
|
20681
|
-
const tmp = `${path3}.${process.pid}.tmp`;
|
|
20682
|
-
writeFileSync12(tmp, JSON.stringify({ bySender }, null, 2), "utf8");
|
|
20683
|
-
renameSync9(tmp, path3);
|
|
20949
|
+
function telegramServerPath() {
|
|
20950
|
+
return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve27(process.env.MAXY_PLATFORM_ROOT ?? join37(__dirname, ".."), "services/telegram-channel/dist/server.js");
|
|
20951
|
+
}
|
|
20952
|
+
function telegramChannelDescriptor(senderId) {
|
|
20953
|
+
return { senderId, gatewayUrl: telegramGatewayUrl(), serverPath: telegramServerPath() };
|
|
20684
20954
|
}
|
|
20685
20955
|
|
|
20686
20956
|
// server/routes/admin/session-reseat.ts
|
|
20687
20957
|
function isAdminChannelSource(source) {
|
|
20688
20958
|
return !!source && source.role === "admin" && (source.channel === "whatsapp" || source.channel === "telegram") && typeof source.senderId === "string" && source.senderId.length > 0;
|
|
20689
20959
|
}
|
|
20960
|
+
function shouldWriteChannelForward(source) {
|
|
20961
|
+
return isAdminChannelSource(source) && !!source.accountId;
|
|
20962
|
+
}
|
|
20963
|
+
function isPublicChannelSource(source) {
|
|
20964
|
+
return !!source && source.role === "public" && (source.channel === "whatsapp" || source.channel === "telegram");
|
|
20965
|
+
}
|
|
20690
20966
|
function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticatedName, source) {
|
|
20691
20967
|
const sessionId = mintId();
|
|
20692
20968
|
const key = `session:${sessionId}`;
|
|
@@ -20700,6 +20976,8 @@ function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticat
|
|
|
20700
20976
|
payload.channel = source.channel;
|
|
20701
20977
|
payload.senderId = source.senderId;
|
|
20702
20978
|
if (source.accountId) payload.targetAccountId = source.accountId;
|
|
20979
|
+
if (source.channel === "whatsapp") payload.waChannel = waChannelDescriptor(source.senderId);
|
|
20980
|
+
else payload.telegramChannel = telegramChannelDescriptor(source.senderId);
|
|
20703
20981
|
} else {
|
|
20704
20982
|
payload.channel = "webchat";
|
|
20705
20983
|
payload.senderId = key;
|
|
@@ -20747,6 +21025,10 @@ app36.post("/", requireAdminSession, async (c) => {
|
|
|
20747
21025
|
const authenticatedName = accountId ? await resolveAuthenticatedName(accountId, adminUserId) : void 0;
|
|
20748
21026
|
const sourceMeta = readSourceRowMeta(fromSessionId);
|
|
20749
21027
|
const source = sourceMeta ? { channel: sourceMeta.channel, senderId: sourceMeta.senderId, role: sourceMeta.role, accountId: sourceMeta.accountId } : null;
|
|
21028
|
+
if (isPublicChannelSource(source)) {
|
|
21029
|
+
console.error(`[chat-reseat] op=reseat-refused from=${fromSessionId} reason=public-channel channel=${source?.channel ?? "none"}`);
|
|
21030
|
+
return c.json({ error: "public channel re-seat not supported (would detach the seat)" }, 400);
|
|
21031
|
+
}
|
|
20750
21032
|
const plan = resolveReseatPlan(
|
|
20751
21033
|
{ fromSessionId, model, ...permissionMode ? { permissionMode } : {}, ...effort ? { effort } : {} },
|
|
20752
21034
|
randomUUID12,
|
|
@@ -20799,19 +21081,21 @@ app36.post("/", requireAdminSession, async (c) => {
|
|
|
20799
21081
|
if (isChannelFork) {
|
|
20800
21082
|
const channel = plan.payload.channel;
|
|
20801
21083
|
const senderId = plan.payload.senderId;
|
|
20802
|
-
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
|
|
20807
|
-
|
|
20808
|
-
|
|
20809
|
-
|
|
21084
|
+
if (!shouldWriteChannelForward(source)) {
|
|
21085
|
+
console.error(`[chat-reseat] op=channel-forward-skipped reseatReqId=${reseatReqId} senderId=${senderId} from=${fromSessionId} reason=no-source-account`);
|
|
21086
|
+
} else {
|
|
21087
|
+
try {
|
|
21088
|
+
const dir = listValidAccounts().find((a) => a.accountId === source.accountId)?.accountDir ?? null;
|
|
21089
|
+
if (dir) {
|
|
21090
|
+
writeChannelOverrideId(dir, channel, senderId, plan.sessionId);
|
|
21091
|
+
console.log(`[chat-reseat] op=channel-forward reseatReqId=${reseatReqId} senderId=${senderId} from=${fromSessionId} to=${plan.sessionId} account=${source.accountId}`);
|
|
21092
|
+
} else {
|
|
21093
|
+
console.error(`[session-reseat] channel-forward-failed reason=no-account-dir accountId=${source.accountId} senderId=${senderId}`);
|
|
21094
|
+
}
|
|
21095
|
+
} catch (err) {
|
|
21096
|
+
console.error(`[session-reseat] channel-forward-failed err=${err instanceof Error ? err.message : String(err)} senderId=${senderId}`);
|
|
20810
21097
|
}
|
|
20811
|
-
} catch (err) {
|
|
20812
|
-
console.error(`[session-reseat] channel-forward-failed err=${err instanceof Error ? err.message : String(err)} senderId=${senderId}`);
|
|
20813
21098
|
}
|
|
20814
|
-
console.log(`[chat-reseat] op=channel-fork-unbound reseatReqId=${reseatReqId} senderId=${senderId} session=${plan.sessionId} note=binds-on-next-inbound`);
|
|
20815
21099
|
}
|
|
20816
21100
|
const target = `/chat?session=${plan.sessionId}`;
|
|
20817
21101
|
console.log(`[chat-reseat] op=acquired reseatReqId=${reseatReqId} pid=${parsed?.spawnedPid ?? "none"} new=${plan.sessionId}`);
|
|
@@ -20948,10 +21232,10 @@ var system_stats_default = app37;
|
|
|
20948
21232
|
|
|
20949
21233
|
// server/routes/admin/health.ts
|
|
20950
21234
|
import { existsSync as existsSync31, readFileSync as readFileSync35 } from "fs";
|
|
20951
|
-
import { resolve as
|
|
20952
|
-
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ??
|
|
21235
|
+
import { resolve as resolve28, join as join38 } from "path";
|
|
21236
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve28(process.cwd(), "..");
|
|
20953
21237
|
var brandHostname = "maxy";
|
|
20954
|
-
var brandJsonPath =
|
|
21238
|
+
var brandJsonPath = join38(PLATFORM_ROOT6, "config", "brand.json");
|
|
20955
21239
|
if (existsSync31(brandJsonPath)) {
|
|
20956
21240
|
try {
|
|
20957
21241
|
const brand = JSON.parse(readFileSync35(brandJsonPath, "utf-8"));
|
|
@@ -20959,7 +21243,7 @@ if (existsSync31(brandJsonPath)) {
|
|
|
20959
21243
|
} catch {
|
|
20960
21244
|
}
|
|
20961
21245
|
}
|
|
20962
|
-
var VERSION_FILE =
|
|
21246
|
+
var VERSION_FILE = resolve28(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
|
|
20963
21247
|
var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
20964
21248
|
var PROBE_TIMEOUT_MS = 1e3;
|
|
20965
21249
|
function readVersion() {
|
|
@@ -21741,7 +22025,7 @@ var browser_default = app45;
|
|
|
21741
22025
|
|
|
21742
22026
|
// server/routes/admin/calendar.ts
|
|
21743
22027
|
import { existsSync as existsSync32 } from "fs";
|
|
21744
|
-
import { join as
|
|
22028
|
+
import { join as join40 } from "path";
|
|
21745
22029
|
|
|
21746
22030
|
// server/lib/calendar-ics.ts
|
|
21747
22031
|
var CRLF = "\r\n";
|
|
@@ -21841,7 +22125,7 @@ function countVEvents(ics) {
|
|
|
21841
22125
|
|
|
21842
22126
|
// server/lib/calendar-availability.ts
|
|
21843
22127
|
import { readFileSync as readFileSync36 } from "fs";
|
|
21844
|
-
import { join as
|
|
22128
|
+
import { join as join39 } from "path";
|
|
21845
22129
|
var AVAILABILITY_FILENAME = "calendar-availability.json";
|
|
21846
22130
|
var REQUIRED_KEYS = [
|
|
21847
22131
|
"timezone",
|
|
@@ -21850,7 +22134,7 @@ var REQUIRED_KEYS = [
|
|
|
21850
22134
|
"weekly"
|
|
21851
22135
|
];
|
|
21852
22136
|
function readAvailabilityConfig(accountDir) {
|
|
21853
|
-
const path3 =
|
|
22137
|
+
const path3 = join39(accountDir, AVAILABILITY_FILENAME);
|
|
21854
22138
|
let raw;
|
|
21855
22139
|
try {
|
|
21856
22140
|
raw = readFileSync36(path3, "utf-8");
|
|
@@ -22212,7 +22496,7 @@ app46.get("/booking-link", requireAdminSession, (c) => {
|
|
|
22212
22496
|
console.error('[calendar] op=booking-link-fail err="no account configured"');
|
|
22213
22497
|
return c.json({ bookingDomain: null });
|
|
22214
22498
|
}
|
|
22215
|
-
if (!existsSync32(
|
|
22499
|
+
if (!existsSync32(join40(account.accountDir, AVAILABILITY_FILENAME))) {
|
|
22216
22500
|
console.log("[calendar] op=booking-link bookingDomain=none source=availability-config");
|
|
22217
22501
|
return c.json({ bookingDomain: null });
|
|
22218
22502
|
}
|
|
@@ -22871,9 +23155,9 @@ var verify_token_default = app54;
|
|
|
22871
23155
|
|
|
22872
23156
|
// app/lib/access-email.ts
|
|
22873
23157
|
import { spawn as spawn2 } from "child_process";
|
|
22874
|
-
import { resolve as
|
|
22875
|
-
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ??
|
|
22876
|
-
var SEND_SCRIPT =
|
|
23158
|
+
import { resolve as resolve29 } from "path";
|
|
23159
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve29(process.cwd(), "..");
|
|
23160
|
+
var SEND_SCRIPT = resolve29(
|
|
22877
23161
|
PLATFORM_ROOT7,
|
|
22878
23162
|
"plugins",
|
|
22879
23163
|
"email",
|
|
@@ -23016,7 +23300,7 @@ var access_default = app56;
|
|
|
23016
23300
|
|
|
23017
23301
|
// server/routes/sites.ts
|
|
23018
23302
|
import { existsSync as existsSync33, readFileSync as readFileSync37, realpathSync as realpathSync7, statSync as statSync14 } from "fs";
|
|
23019
|
-
import { resolve as
|
|
23303
|
+
import { resolve as resolve30 } from "path";
|
|
23020
23304
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
23021
23305
|
var MIME = {
|
|
23022
23306
|
".html": "text/html; charset=utf-8",
|
|
@@ -23073,8 +23357,8 @@ app57.get("/:rel{.*}", (c) => {
|
|
|
23073
23357
|
}
|
|
23074
23358
|
segments.push(seg);
|
|
23075
23359
|
}
|
|
23076
|
-
const rootDir =
|
|
23077
|
-
let filePath = segments.length === 0 ? rootDir :
|
|
23360
|
+
const rootDir = resolve30(account.accountDir, "sites");
|
|
23361
|
+
let filePath = segments.length === 0 ? rootDir : resolve30(rootDir, ...segments);
|
|
23078
23362
|
if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
|
|
23079
23363
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
23080
23364
|
return c.text("Forbidden", 403);
|
|
@@ -23094,7 +23378,7 @@ app57.get("/:rel{.*}", (c) => {
|
|
|
23094
23378
|
return c.redirect(target, 301);
|
|
23095
23379
|
}
|
|
23096
23380
|
if (stat9?.isDirectory()) {
|
|
23097
|
-
filePath =
|
|
23381
|
+
filePath = resolve30(filePath, "index.html");
|
|
23098
23382
|
}
|
|
23099
23383
|
if (!filePath.startsWith(rootDir + "/")) {
|
|
23100
23384
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
@@ -23232,7 +23516,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
|
23232
23516
|
|
|
23233
23517
|
// app/lib/brand-config.ts
|
|
23234
23518
|
import { existsSync as existsSync34, readFileSync as readFileSync39 } from "fs";
|
|
23235
|
-
import { join as
|
|
23519
|
+
import { join as join41 } from "path";
|
|
23236
23520
|
var cached2 = null;
|
|
23237
23521
|
var cachedAttempted = false;
|
|
23238
23522
|
function readBrandConfig() {
|
|
@@ -23240,7 +23524,7 @@ function readBrandConfig() {
|
|
|
23240
23524
|
cachedAttempted = true;
|
|
23241
23525
|
const platformRoot5 = process.env.MAXY_PLATFORM_ROOT;
|
|
23242
23526
|
if (!platformRoot5) return null;
|
|
23243
|
-
const brandPath =
|
|
23527
|
+
const brandPath = join41(platformRoot5, "config", "brand.json");
|
|
23244
23528
|
if (!existsSync34(brandPath)) return null;
|
|
23245
23529
|
try {
|
|
23246
23530
|
cached2 = JSON.parse(readFileSync39(brandPath, "utf-8"));
|
|
@@ -23740,14 +24024,14 @@ async function writeEvent(opts) {
|
|
|
23740
24024
|
var visitor_event_default = app60;
|
|
23741
24025
|
|
|
23742
24026
|
// server/routes/session.ts
|
|
23743
|
-
import { resolve as
|
|
24027
|
+
import { resolve as resolve31 } from "path";
|
|
23744
24028
|
import { existsSync as existsSync35, writeFileSync as writeFileSync14, mkdirSync as mkdirSync9 } from "fs";
|
|
23745
24029
|
var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23746
24030
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
23747
24031
|
try {
|
|
23748
|
-
const cacheDir =
|
|
24032
|
+
const cacheDir = resolve31(MAXY_DIR, "branding-cache", accountId);
|
|
23749
24033
|
mkdirSync9(cacheDir, { recursive: true });
|
|
23750
|
-
writeFileSync14(
|
|
24034
|
+
writeFileSync14(resolve31(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
23751
24035
|
} catch (err) {
|
|
23752
24036
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
23753
24037
|
}
|
|
@@ -23824,8 +24108,8 @@ app61.post("/", async (c) => {
|
|
|
23824
24108
|
}
|
|
23825
24109
|
let agentConfig = null;
|
|
23826
24110
|
if (account) {
|
|
23827
|
-
const agentDir =
|
|
23828
|
-
if (!existsSync35(agentDir) || !existsSync35(
|
|
24111
|
+
const agentDir = resolve31(account.accountDir, "agents", agentSlug);
|
|
24112
|
+
if (!existsSync35(agentDir) || !existsSync35(resolve31(agentDir, "config.json"))) {
|
|
23829
24113
|
return c.json({ error: "Agent not found" }, 404);
|
|
23830
24114
|
}
|
|
23831
24115
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -24075,7 +24359,7 @@ var calendar_public_default = app62;
|
|
|
24075
24359
|
// server/routes/portal-fetch.ts
|
|
24076
24360
|
import { createReadStream as createReadStream3 } from "fs";
|
|
24077
24361
|
import { stat as stat6, readFile as readFile6, realpath } from "fs/promises";
|
|
24078
|
-
import { join as
|
|
24362
|
+
import { join as join42, resolve as resolve32, sep as sep8 } from "path";
|
|
24079
24363
|
import { Readable as Readable3 } from "stream";
|
|
24080
24364
|
|
|
24081
24365
|
// ../plugins/cloudflare/bin/schema-exposed-dirs.mjs
|
|
@@ -24176,7 +24460,7 @@ ${expiresAtMs}`)
|
|
|
24176
24460
|
return diff === 0;
|
|
24177
24461
|
}
|
|
24178
24462
|
async function readAccountSecret(accountId) {
|
|
24179
|
-
const path3 =
|
|
24463
|
+
const path3 = resolve32(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
|
|
24180
24464
|
let text;
|
|
24181
24465
|
try {
|
|
24182
24466
|
text = await readFile6(path3, "utf8");
|
|
@@ -24197,13 +24481,13 @@ function isValidAccountId2(value) {
|
|
|
24197
24481
|
async function resolveFetchTarget(accountDir, relPath) {
|
|
24198
24482
|
let schemaText = null;
|
|
24199
24483
|
try {
|
|
24200
|
-
schemaText = await readFile6(
|
|
24484
|
+
schemaText = await readFile6(join42(accountDir, "SCHEMA.md"), "utf8");
|
|
24201
24485
|
} catch {
|
|
24202
24486
|
schemaText = null;
|
|
24203
24487
|
}
|
|
24204
24488
|
const { exposed } = resolveExposedDirs(schemaText);
|
|
24205
24489
|
if (exposed.length === 0) return { ok: false, reason: "not-exposed" };
|
|
24206
|
-
const lexical =
|
|
24490
|
+
const lexical = resolve32(accountDir, relPath);
|
|
24207
24491
|
let root;
|
|
24208
24492
|
let absolute;
|
|
24209
24493
|
try {
|
|
@@ -24241,7 +24525,7 @@ async function handle(c, headOnly) {
|
|
|
24241
24525
|
console.error(`${TAG45} op=denied account=${q(accountId)} reason=secret`);
|
|
24242
24526
|
return c.json({ ok: false, error: "denied" }, 401);
|
|
24243
24527
|
}
|
|
24244
|
-
const target = await resolveFetchTarget(
|
|
24528
|
+
const target = await resolveFetchTarget(resolve32(DATA_ROOT, "accounts", accountId), relPath);
|
|
24245
24529
|
if (!target.ok) {
|
|
24246
24530
|
const op = target.reason === "enoent" ? "miss" : "denied";
|
|
24247
24531
|
console.error(`${TAG45} op=${op} account=${accountId} reason=${target.reason}`);
|
|
@@ -24353,7 +24637,7 @@ function startTimeEntryCensus(openSession, opts = {}) {
|
|
|
24353
24637
|
|
|
24354
24638
|
// app/lib/ledger-census.ts
|
|
24355
24639
|
import { readdirSync as readdirSync25, readFileSync as readFileSync40, statSync as statSync15 } from "fs";
|
|
24356
|
-
import { join as
|
|
24640
|
+
import { join as join43 } from "path";
|
|
24357
24641
|
|
|
24358
24642
|
// ../lib/ledger-core/dist/reconcile.js
|
|
24359
24643
|
var LEDGER_INDEX_NAMES = [
|
|
@@ -24407,7 +24691,7 @@ function countWriteRejects24h(logDir, now) {
|
|
|
24407
24691
|
}
|
|
24408
24692
|
let total = 0;
|
|
24409
24693
|
for (const name of entries) {
|
|
24410
|
-
const path3 =
|
|
24694
|
+
const path3 = join43(logDir, name);
|
|
24411
24695
|
try {
|
|
24412
24696
|
if (now - statSync15(path3).mtimeMs > DAY_MS) continue;
|
|
24413
24697
|
for (const line of readFileSync40(path3, "utf8").split("\n")) {
|
|
@@ -24679,7 +24963,7 @@ function startGraphHealthTimer() {
|
|
|
24679
24963
|
|
|
24680
24964
|
// app/lib/file-watcher.ts
|
|
24681
24965
|
import * as fsp2 from "fs/promises";
|
|
24682
|
-
import { resolve as
|
|
24966
|
+
import { resolve as resolve33, sep as sep9 } from "path";
|
|
24683
24967
|
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;
|
|
24684
24968
|
var DEFAULT_COALESCE_MS = 500;
|
|
24685
24969
|
var ROOTS = ["accounts"];
|
|
@@ -24698,7 +24982,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
24698
24982
|
const dropFn = opts.drop ?? dropFileIndex;
|
|
24699
24983
|
for (const r of ROOTS) {
|
|
24700
24984
|
try {
|
|
24701
|
-
await fsp2.mkdir(
|
|
24985
|
+
await fsp2.mkdir(resolve33(dataRoot, r), { recursive: true });
|
|
24702
24986
|
} catch (err) {
|
|
24703
24987
|
console.error(
|
|
24704
24988
|
`[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
|
|
@@ -24719,7 +25003,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
24719
25003
|
timers.set(relativePath, t);
|
|
24720
25004
|
}
|
|
24721
25005
|
async function runHook(relativePath, accountId) {
|
|
24722
|
-
const absolute =
|
|
25006
|
+
const absolute = resolve33(dataRoot, relativePath);
|
|
24723
25007
|
let exists = false;
|
|
24724
25008
|
try {
|
|
24725
25009
|
const st = await fsp2.stat(absolute);
|
|
@@ -24743,7 +25027,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
24743
25027
|
}
|
|
24744
25028
|
}
|
|
24745
25029
|
async function watchRoot(rootName) {
|
|
24746
|
-
const absRoot =
|
|
25030
|
+
const absRoot = resolve33(dataRoot, rootName);
|
|
24747
25031
|
try {
|
|
24748
25032
|
const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
|
|
24749
25033
|
for await (const event of iter) {
|
|
@@ -24782,7 +25066,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
24782
25066
|
|
|
24783
25067
|
// app/lib/migrate-uploads.ts
|
|
24784
25068
|
import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
|
|
24785
|
-
import { dirname as dirname13, relative as relative6, resolve as
|
|
25069
|
+
import { dirname as dirname13, relative as relative6, resolve as resolve34 } from "path";
|
|
24786
25070
|
var ACCOUNT_UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24787
25071
|
async function walkFiles(dir) {
|
|
24788
25072
|
const out = [];
|
|
@@ -24793,7 +25077,7 @@ async function walkFiles(dir) {
|
|
|
24793
25077
|
return out;
|
|
24794
25078
|
}
|
|
24795
25079
|
for (const e of entries) {
|
|
24796
|
-
const abs =
|
|
25080
|
+
const abs = resolve34(dir, e.name);
|
|
24797
25081
|
if (e.isDirectory()) {
|
|
24798
25082
|
out.push(...await walkFiles(abs));
|
|
24799
25083
|
} else if (e.isFile()) {
|
|
@@ -24813,7 +25097,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
24813
25097
|
let moved = 0;
|
|
24814
25098
|
for (const oldAbs of files) {
|
|
24815
25099
|
const suffix = relative6(srcDir, oldAbs);
|
|
24816
|
-
const newAbs =
|
|
25100
|
+
const newAbs = resolve34(destDir, suffix);
|
|
24817
25101
|
await mkdir5(dirname13(newAbs), { recursive: true });
|
|
24818
25102
|
await rename2(oldAbs, newAbs);
|
|
24819
25103
|
moved++;
|
|
@@ -24840,7 +25124,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
24840
25124
|
}
|
|
24841
25125
|
async function migrateUploads(opts = {}) {
|
|
24842
25126
|
const dataRoot = opts.dataRoot ?? DATA_ROOT;
|
|
24843
|
-
const oldRoot =
|
|
25127
|
+
const oldRoot = resolve34(dataRoot, "uploads");
|
|
24844
25128
|
let topEntries;
|
|
24845
25129
|
try {
|
|
24846
25130
|
topEntries = await readdir5(oldRoot, { withFileTypes: true });
|
|
@@ -24861,8 +25145,8 @@ async function migrateUploads(opts = {}) {
|
|
|
24861
25145
|
const name = entry.name;
|
|
24862
25146
|
if (ACCOUNT_UUID_RE5.test(name)) {
|
|
24863
25147
|
moved += await relocateTree(
|
|
24864
|
-
|
|
24865
|
-
|
|
25148
|
+
resolve34(oldRoot, name),
|
|
25149
|
+
resolve34(dataRoot, "accounts", name, "uploads"),
|
|
24866
25150
|
dataRoot,
|
|
24867
25151
|
name,
|
|
24868
25152
|
session
|
|
@@ -24874,8 +25158,8 @@ async function migrateUploads(opts = {}) {
|
|
|
24874
25158
|
continue;
|
|
24875
25159
|
}
|
|
24876
25160
|
moved += await relocateTree(
|
|
24877
|
-
|
|
24878
|
-
|
|
25161
|
+
resolve34(oldRoot, "public"),
|
|
25162
|
+
resolve34(dataRoot, "accounts", installAccountId, "uploads", "public"),
|
|
24879
25163
|
dataRoot,
|
|
24880
25164
|
null,
|
|
24881
25165
|
// public uploads carry no graph nodes
|
|
@@ -24906,7 +25190,7 @@ async function migrateUploads(opts = {}) {
|
|
|
24906
25190
|
// app/lib/migrate-admin-webchat-sidecars.ts
|
|
24907
25191
|
import { readdir as readdir6, readFile as readFile7, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
|
|
24908
25192
|
import { existsSync as existsSync36 } from "fs";
|
|
24909
|
-
import { join as
|
|
25193
|
+
import { join as join44 } from "path";
|
|
24910
25194
|
var ADMIN_ROLE2 = "admin";
|
|
24911
25195
|
var WEBCHAT_CHANNEL2 = "webchat";
|
|
24912
25196
|
var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
@@ -24951,7 +25235,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
24951
25235
|
}
|
|
24952
25236
|
for (const slug of slugs) {
|
|
24953
25237
|
if (!slug.isDirectory()) continue;
|
|
24954
|
-
const slugDir =
|
|
25238
|
+
const slugDir = join44(projectsRoot, slug.name);
|
|
24955
25239
|
let entries;
|
|
24956
25240
|
try {
|
|
24957
25241
|
entries = await readdir6(slugDir, { withFileTypes: true });
|
|
@@ -24960,9 +25244,9 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
24960
25244
|
}
|
|
24961
25245
|
for (const entry of entries) {
|
|
24962
25246
|
if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
|
|
24963
|
-
out.push(
|
|
25247
|
+
out.push(join44(slugDir, entry.name));
|
|
24964
25248
|
} else if (entry.isDirectory() && entry.name === "subagents") {
|
|
24965
|
-
const subDir =
|
|
25249
|
+
const subDir = join44(slugDir, entry.name);
|
|
24966
25250
|
let subs;
|
|
24967
25251
|
try {
|
|
24968
25252
|
subs = await readdir6(subDir, { withFileTypes: true });
|
|
@@ -24970,7 +25254,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
24970
25254
|
continue;
|
|
24971
25255
|
}
|
|
24972
25256
|
for (const s of subs) {
|
|
24973
|
-
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(
|
|
25257
|
+
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join44(subDir, s.name));
|
|
24974
25258
|
}
|
|
24975
25259
|
}
|
|
24976
25260
|
}
|
|
@@ -24982,7 +25266,7 @@ function shortId(path3) {
|
|
|
24982
25266
|
return base.slice(0, 8);
|
|
24983
25267
|
}
|
|
24984
25268
|
async function migrateAdminWebchatSidecars(opts = {}) {
|
|
24985
|
-
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
25269
|
+
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join44(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
|
|
24986
25270
|
const usersFile = opts.usersFile ?? USERS_FILE;
|
|
24987
25271
|
const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
|
|
24988
25272
|
const resolveName = opts.resolveName ?? defaultResolveName;
|
|
@@ -25336,11 +25620,9 @@ function createWaChannelRoutes(deps) {
|
|
|
25336
25620
|
void stream2.writeSSE({ data: JSON.stringify(payload) });
|
|
25337
25621
|
});
|
|
25338
25622
|
console.error(`[whatsapp-native] op=channel-attached senderId=${senderId}`);
|
|
25339
|
-
deps.onAttached?.(senderId);
|
|
25340
25623
|
stream2.onAbort(() => {
|
|
25341
25624
|
deps.hub.detach(senderId);
|
|
25342
25625
|
console.error(`[whatsapp-native] op=channel-detached senderId=${senderId}`);
|
|
25343
|
-
deps.onDetached?.(senderId);
|
|
25344
25626
|
});
|
|
25345
25627
|
while (!stream2.aborted) {
|
|
25346
25628
|
await stream2.sleep(15e3);
|
|
@@ -25481,31 +25763,16 @@ var WaGateway = class {
|
|
|
25481
25763
|
docContexts = /* @__PURE__ */ new Map();
|
|
25482
25764
|
spawning = /* @__PURE__ */ new Set();
|
|
25483
25765
|
seq = 0;
|
|
25484
|
-
/** Hono sub-app exposing the loopback channel routes; mount on the UI app.
|
|
25485
|
-
|
|
25486
|
-
*
|
|
25487
|
-
|
|
25488
|
-
|
|
25489
|
-
|
|
25490
|
-
|
|
25491
|
-
}
|
|
25492
|
-
/** Seat regained — the sender is no longer unseated. */
|
|
25493
|
-
recordAttached(senderId) {
|
|
25494
|
-
this.detachedAt.delete(senderId);
|
|
25495
|
-
}
|
|
25496
|
-
/** Senders with no channel server for longer than `olderThanMs`. */
|
|
25497
|
-
unseatedSenders(nowMs, olderThanMs) {
|
|
25498
|
-
const out = [];
|
|
25499
|
-
for (const [senderId, sinceMs] of this.detachedAt) {
|
|
25500
|
-
if (nowMs - sinceMs > olderThanMs) out.push({ senderId, sinceMs });
|
|
25501
|
-
}
|
|
25502
|
-
return out.sort((a, b) => a.senderId.localeCompare(b.senderId));
|
|
25503
|
-
}
|
|
25766
|
+
/** Hono sub-app exposing the loopback channel routes; mount on the UI app.
|
|
25767
|
+
* Task 1836 — the process-local unseated-sender census that used to live here
|
|
25768
|
+
* was retired. Its quiet state was ambiguous (a UI restart emptied it, so
|
|
25769
|
+
* silence meant "no detach seen since boot", not "every sender is seated") and
|
|
25770
|
+
* it grew unbounded. The genuine "no seat with pending work" signal is
|
|
25771
|
+
* InboundHub.sweep -> spawn-undelivered, which is driven by live queue state
|
|
25772
|
+
* and self-prunes. */
|
|
25504
25773
|
routes() {
|
|
25505
25774
|
return createWaChannelRoutes({
|
|
25506
25775
|
hub: this.hub,
|
|
25507
|
-
onAttached: (senderId) => this.recordAttached(senderId),
|
|
25508
|
-
onDetached: (senderId) => this.recordDetached(senderId, Date.now()),
|
|
25509
25776
|
sendOutbound: (senderId, text) => this.sendOutbound(senderId, text),
|
|
25510
25777
|
sendDocument: (senderId, filePath, caption) => this.sendDocument(senderId, filePath, caption),
|
|
25511
25778
|
onReady: (senderId) => {
|
|
@@ -25684,12 +25951,6 @@ function logDuplicateSenderGroups(groups) {
|
|
|
25684
25951
|
);
|
|
25685
25952
|
}
|
|
25686
25953
|
}
|
|
25687
|
-
function logUnseatedSenders(entries, nowMs) {
|
|
25688
|
-
for (const e of entries) {
|
|
25689
|
-
const secs = Math.round((nowMs - e.sinceMs) / 1e3);
|
|
25690
|
-
console.error(`[whatsapp-native] op=sender-unseated senderId=${e.senderId} since=${new Date(e.sinceMs).toISOString()} forSeconds=${secs}`);
|
|
25691
|
-
}
|
|
25692
|
-
}
|
|
25693
25954
|
|
|
25694
25955
|
// app/lib/whatsapp/gateway/spawn-request.ts
|
|
25695
25956
|
function buildWaSpawnRequest(input) {
|
|
@@ -26045,16 +26306,16 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
26045
26306
|
* The public /api/chat route awaits this to return the reply on the same SSE
|
|
26046
26307
|
* response, preserving the pre-756 POST→single-blob browser contract. */
|
|
26047
26308
|
awaitReply(key, timeoutMs) {
|
|
26048
|
-
return new Promise((
|
|
26309
|
+
return new Promise((resolve39) => {
|
|
26049
26310
|
const timer = setTimeout(() => {
|
|
26050
26311
|
this.replyAwaiters.delete(key);
|
|
26051
|
-
|
|
26312
|
+
resolve39({ timeout: true });
|
|
26052
26313
|
}, timeoutMs);
|
|
26053
26314
|
if (timer.unref) timer.unref();
|
|
26054
26315
|
this.replyAwaiters.set(key, (r) => {
|
|
26055
26316
|
clearTimeout(timer);
|
|
26056
26317
|
this.replyAwaiters.delete(key);
|
|
26057
|
-
|
|
26318
|
+
resolve39(r);
|
|
26058
26319
|
});
|
|
26059
26320
|
});
|
|
26060
26321
|
}
|
|
@@ -26065,11 +26326,11 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
26065
26326
|
* terminal dialog stays answerable). */
|
|
26066
26327
|
awaitPermissionVerdict(p, timeoutMs) {
|
|
26067
26328
|
const k = _WebchatGateway.promptKey(p.key, p.requestId);
|
|
26068
|
-
return new Promise((
|
|
26329
|
+
return new Promise((resolve39) => {
|
|
26069
26330
|
this.pendingPrompts.get(k)?.resolve({ timeout: true });
|
|
26070
26331
|
const timer = setTimeout(() => {
|
|
26071
26332
|
this.pendingPrompts.delete(k);
|
|
26072
|
-
|
|
26333
|
+
resolve39({ timeout: true });
|
|
26073
26334
|
}, timeoutMs);
|
|
26074
26335
|
if (timer.unref) timer.unref();
|
|
26075
26336
|
this.pendingPrompts.set(k, {
|
|
@@ -26078,7 +26339,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
26078
26339
|
resolve: (r) => {
|
|
26079
26340
|
clearTimeout(timer);
|
|
26080
26341
|
this.pendingPrompts.delete(k);
|
|
26081
|
-
|
|
26342
|
+
resolve39(r);
|
|
26082
26343
|
}
|
|
26083
26344
|
});
|
|
26084
26345
|
console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
|
|
@@ -26643,7 +26904,7 @@ async function fanOut(subscribers, text, onError, tag) {
|
|
|
26643
26904
|
|
|
26644
26905
|
// app/lib/webchat/file-delivery.ts
|
|
26645
26906
|
import { realpathSync as realpathSync8 } from "fs";
|
|
26646
|
-
import { resolve as
|
|
26907
|
+
import { resolve as resolve35 } from "path";
|
|
26647
26908
|
|
|
26648
26909
|
// app/lib/channel-pty-bridge/file-delivery.ts
|
|
26649
26910
|
var SEND_USER_FILE = "SendUserFile";
|
|
@@ -26744,7 +27005,7 @@ function makeWebchatSendFile(entry) {
|
|
|
26744
27005
|
console.error(`${TAG49} file-delivery reject reason=no-account sender=${entry.senderId}`);
|
|
26745
27006
|
return { ok: false, error: "no-account" };
|
|
26746
27007
|
}
|
|
26747
|
-
const accountDir =
|
|
27008
|
+
const accountDir = resolve35(platformRoot2(), "..", "data/accounts", entry.accountId);
|
|
26748
27009
|
try {
|
|
26749
27010
|
const resolved = realpathSync8(filePath);
|
|
26750
27011
|
const accountResolved = realpathSync8(accountDir);
|
|
@@ -27311,7 +27572,7 @@ function buildTelegramSpawnRequest(input) {
|
|
|
27311
27572
|
// app/lib/telegram/outbound/send-document.ts
|
|
27312
27573
|
import { realpathSync as realpathSync9 } from "fs";
|
|
27313
27574
|
import { readFile as readFile8, stat as stat8 } from "fs/promises";
|
|
27314
|
-
import { resolve as
|
|
27575
|
+
import { resolve as resolve36, basename as basename11 } from "path";
|
|
27315
27576
|
var TAG51 = "[telegram:outbound]";
|
|
27316
27577
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
27317
27578
|
async function sendTelegramDocument(input) {
|
|
@@ -27322,7 +27583,7 @@ async function sendTelegramDocument(input) {
|
|
|
27322
27583
|
if (!maxyAccountId || !platformRoot5) {
|
|
27323
27584
|
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
27324
27585
|
}
|
|
27325
|
-
const accountDir =
|
|
27586
|
+
const accountDir = resolve36(platformRoot5, "..", "data/accounts", maxyAccountId);
|
|
27326
27587
|
let resolvedPath;
|
|
27327
27588
|
try {
|
|
27328
27589
|
resolvedPath = realpathSync9(filePath);
|
|
@@ -27448,15 +27709,6 @@ function startTelegramNativeFileFollower(input) {
|
|
|
27448
27709
|
});
|
|
27449
27710
|
}
|
|
27450
27711
|
|
|
27451
|
-
// server/telegram-descriptor.ts
|
|
27452
|
-
import { resolve as resolve35, join as join43 } from "path";
|
|
27453
|
-
function telegramGatewayUrl() {
|
|
27454
|
-
return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
|
|
27455
|
-
}
|
|
27456
|
-
function telegramServerPath() {
|
|
27457
|
-
return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve35(process.env.MAXY_PLATFORM_ROOT ?? join43(__dirname, ".."), "services/telegram-channel/dist/server.js");
|
|
27458
|
-
}
|
|
27459
|
-
|
|
27460
27712
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
27461
27713
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
27462
27714
|
var TAG53 = "[public-session-review]";
|
|
@@ -27742,7 +27994,7 @@ function broadcastAdminShutdown(reason) {
|
|
|
27742
27994
|
// ../lib/entitlement/src/index.ts
|
|
27743
27995
|
import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
|
|
27744
27996
|
import { existsSync as existsSync37, readFileSync as readFileSync41, statSync as statSync16 } from "fs";
|
|
27745
|
-
import { resolve as
|
|
27997
|
+
import { resolve as resolve37 } from "path";
|
|
27746
27998
|
|
|
27747
27999
|
// ../lib/entitlement/src/canonicalize.ts
|
|
27748
28000
|
function canonicalize(value) {
|
|
@@ -27777,7 +28029,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
|
|
|
27777
28029
|
var GRACE_DAYS = 7;
|
|
27778
28030
|
var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
27779
28031
|
function pubkeyPath(brand) {
|
|
27780
|
-
return
|
|
28032
|
+
return resolve37(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
|
|
27781
28033
|
}
|
|
27782
28034
|
var memo = null;
|
|
27783
28035
|
function memoKey(mtimeMs, account) {
|
|
@@ -27789,7 +28041,7 @@ function resolveEntitlement(brand, account) {
|
|
|
27789
28041
|
if (brand.commercialMode !== true) {
|
|
27790
28042
|
return logResolved(implicitTrust(account), null);
|
|
27791
28043
|
}
|
|
27792
|
-
const entitlementPath =
|
|
28044
|
+
const entitlementPath = resolve37(brand.configDir, "entitlement.json");
|
|
27793
28045
|
if (!existsSync37(entitlementPath)) {
|
|
27794
28046
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
27795
28047
|
}
|
|
@@ -27981,7 +28233,7 @@ function clientFrom(c) {
|
|
|
27981
28233
|
}
|
|
27982
28234
|
installMediaDownloadGuard();
|
|
27983
28235
|
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
27984
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT8 ?
|
|
28236
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join45(PLATFORM_ROOT8, "config", "brand.json") : "";
|
|
27985
28237
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
27986
28238
|
if (BRAND_JSON_PATH && !existsSync38(BRAND_JSON_PATH)) {
|
|
27987
28239
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
@@ -28020,7 +28272,7 @@ var brandLoginOpts = {
|
|
|
28020
28272
|
appleTouchIconPath: brandAppIcon512Path,
|
|
28021
28273
|
themeColor: BRAND.defaultColors?.primary
|
|
28022
28274
|
};
|
|
28023
|
-
var ALIAS_DOMAINS_PATH =
|
|
28275
|
+
var ALIAS_DOMAINS_PATH = join45(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
28024
28276
|
function loadAliasDomains() {
|
|
28025
28277
|
try {
|
|
28026
28278
|
if (!existsSync38(ALIAS_DOMAINS_PATH)) return null;
|
|
@@ -28097,7 +28349,7 @@ var waGateway = new WaGateway({
|
|
|
28097
28349
|
fetchStandingRules: fetchAccountStandingRules,
|
|
28098
28350
|
resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
|
|
28099
28351
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
28100
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ??
|
|
28352
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve38(process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
28101
28353
|
// Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
|
|
28102
28354
|
// path-validation scope) is the sender's effective SESSION account, resolved
|
|
28103
28355
|
// once at the inbound gate and threaded here via the gateway's per-sender doc
|
|
@@ -28112,7 +28364,7 @@ var waGateway = new WaGateway({
|
|
|
28112
28364
|
caption,
|
|
28113
28365
|
accountId,
|
|
28114
28366
|
maxyAccountId,
|
|
28115
|
-
platformRoot:
|
|
28367
|
+
platformRoot: resolve38(process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, ".."))
|
|
28116
28368
|
});
|
|
28117
28369
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
28118
28370
|
},
|
|
@@ -28226,7 +28478,7 @@ function runDuplicateSenderAudit() {
|
|
|
28226
28478
|
try {
|
|
28227
28479
|
const configDir2 = process.env.CLAUDE_CONFIG_DIR;
|
|
28228
28480
|
if (!configDir2) return;
|
|
28229
|
-
const projectsRoot =
|
|
28481
|
+
const projectsRoot = join45(configDir2, "projects");
|
|
28230
28482
|
const records = [];
|
|
28231
28483
|
for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
|
|
28232
28484
|
if (isSubagent || archived) continue;
|
|
@@ -28236,8 +28488,6 @@ function runDuplicateSenderAudit() {
|
|
|
28236
28488
|
records.push({ sessionId, channel: meta.channel, senderId: meta.senderId, accountId: meta.accountId });
|
|
28237
28489
|
}
|
|
28238
28490
|
logDuplicateSenderGroups(findDuplicateSenderGroups(records));
|
|
28239
|
-
const now = Date.now();
|
|
28240
|
-
logUnseatedSenders(waGateway.unseatedSenders(now, 15 * 6e4), now);
|
|
28241
28491
|
} catch (err) {
|
|
28242
28492
|
console.error(`[whatsapp-native] op=duplicate-sender-audit-error error=${err instanceof Error ? err.message : String(err)}`);
|
|
28243
28493
|
}
|
|
@@ -28248,6 +28498,7 @@ registerLoop({
|
|
|
28248
28498
|
firstRunDelayMs: 0,
|
|
28249
28499
|
run: runDuplicateSenderAudit
|
|
28250
28500
|
});
|
|
28501
|
+
console.log("[1789-sequel] op=cluster-complete items=[1835,1836,1837,1839]");
|
|
28251
28502
|
var webchatGateway = new WebchatGateway({
|
|
28252
28503
|
fetchStandingRules: fetchAccountStandingRules,
|
|
28253
28504
|
gatewayUrl: webchatGatewayUrl(),
|
|
@@ -28789,8 +29040,8 @@ app64.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
28789
29040
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
28790
29041
|
return c.text("Not found", 404);
|
|
28791
29042
|
}
|
|
28792
|
-
const filePath =
|
|
28793
|
-
const expectedDir =
|
|
29043
|
+
const filePath = resolve38(account.accountDir, "agents", slug, "assets", filename);
|
|
29044
|
+
const expectedDir = resolve38(account.accountDir, "agents", slug, "assets");
|
|
28794
29045
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
28795
29046
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
28796
29047
|
return c.text("Forbidden", 403);
|
|
@@ -28819,8 +29070,8 @@ app64.get("/generated/:filename", (c) => {
|
|
|
28819
29070
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
28820
29071
|
return c.text("Not found", 404);
|
|
28821
29072
|
}
|
|
28822
|
-
const filePath =
|
|
28823
|
-
const expectedDir =
|
|
29073
|
+
const filePath = resolve38(account.accountDir, "generated", filename);
|
|
29074
|
+
const expectedDir = resolve38(account.accountDir, "generated");
|
|
28824
29075
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
28825
29076
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
28826
29077
|
return c.text("Forbidden", 403);
|
|
@@ -28874,11 +29125,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
28874
29125
|
var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
|
|
28875
29126
|
var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
|
|
28876
29127
|
var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
|
|
28877
|
-
(p) => existsSync38(
|
|
29128
|
+
(p) => existsSync38(resolve38(process.cwd(), "public", p.replace(/^\//, "")))
|
|
28878
29129
|
);
|
|
28879
29130
|
var SW_SOURCE = (() => {
|
|
28880
29131
|
try {
|
|
28881
|
-
return readFileSync42(
|
|
29132
|
+
return readFileSync42(resolve38(process.cwd(), "public", "sw.js"), "utf-8");
|
|
28882
29133
|
} catch {
|
|
28883
29134
|
return null;
|
|
28884
29135
|
}
|
|
@@ -28886,7 +29137,7 @@ var SW_SOURCE = (() => {
|
|
|
28886
29137
|
function readInstalledVersion() {
|
|
28887
29138
|
try {
|
|
28888
29139
|
if (!PLATFORM_ROOT8) return "unknown";
|
|
28889
|
-
const versionFile =
|
|
29140
|
+
const versionFile = join45(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
|
|
28890
29141
|
if (!existsSync38(versionFile)) return "unknown";
|
|
28891
29142
|
const content = readFileSync42(versionFile, "utf-8").trim();
|
|
28892
29143
|
return content || "unknown";
|
|
@@ -28929,7 +29180,7 @@ var clientErrorReporterScript = `<script>
|
|
|
28929
29180
|
function cachedHtml(file) {
|
|
28930
29181
|
let html = htmlCache.get(file);
|
|
28931
29182
|
if (!html) {
|
|
28932
|
-
html = readFileSync42(
|
|
29183
|
+
html = readFileSync42(resolve38(process.cwd(), "public", file), "utf-8");
|
|
28933
29184
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
28934
29185
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
28935
29186
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -28946,11 +29197,11 @@ ${clientErrorReporterScript}
|
|
|
28946
29197
|
return html;
|
|
28947
29198
|
}
|
|
28948
29199
|
function loadBrandingCache(agentSlug) {
|
|
28949
|
-
const configDir2 =
|
|
29200
|
+
const configDir2 = join45(homedir4(), BRAND.configDir);
|
|
28950
29201
|
try {
|
|
28951
29202
|
const accountId = getDefaultAccountId();
|
|
28952
29203
|
if (!accountId) return null;
|
|
28953
|
-
const cachePath =
|
|
29204
|
+
const cachePath = join45(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
28954
29205
|
if (!existsSync38(cachePath)) return null;
|
|
28955
29206
|
return JSON.parse(readFileSync42(cachePath, "utf-8"));
|
|
28956
29207
|
} catch {
|
|
@@ -29047,7 +29298,7 @@ app64.use("/vnc-popout.html", logViewerFetch);
|
|
|
29047
29298
|
app64.get("/vnc-popout.html", (c) => {
|
|
29048
29299
|
let html = htmlCache.get("vnc-popout.html");
|
|
29049
29300
|
if (!html) {
|
|
29050
|
-
html = readFileSync42(
|
|
29301
|
+
html = readFileSync42(resolve38(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
29051
29302
|
const name = escapeHtml(BRAND.productName);
|
|
29052
29303
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
29053
29304
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -29184,8 +29435,8 @@ var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
|
29184
29435
|
var httpServer = serve({ fetch: app64.fetch, port, hostname });
|
|
29185
29436
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
29186
29437
|
{
|
|
29187
|
-
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
29188
|
-
const reconcileScript =
|
|
29438
|
+
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
|
|
29439
|
+
const reconcileScript = resolve38(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
|
|
29189
29440
|
const RECONCILE_INTERVAL_MS = 12e4;
|
|
29190
29441
|
const runReconcile = (ctx) => {
|
|
29191
29442
|
if (!existsSync38(reconcileScript)) return;
|
|
@@ -29210,8 +29461,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29210
29461
|
});
|
|
29211
29462
|
}
|
|
29212
29463
|
{
|
|
29213
|
-
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
29214
|
-
const outlookScript =
|
|
29464
|
+
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
|
|
29465
|
+
const outlookScript = resolve38(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
|
|
29215
29466
|
const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
|
|
29216
29467
|
const runOutlookComplete = (ctx) => {
|
|
29217
29468
|
if (!existsSync38(outlookScript)) return;
|
|
@@ -29236,8 +29487,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29236
29487
|
});
|
|
29237
29488
|
}
|
|
29238
29489
|
{
|
|
29239
|
-
const auditRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
29240
|
-
const strandedAccountsDir =
|
|
29490
|
+
const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
|
|
29491
|
+
const strandedAccountsDir = resolve38(auditRoot, "..", "data/accounts");
|
|
29241
29492
|
const STRANDED_AUDIT_INTERVAL_MS = 3e5;
|
|
29242
29493
|
const STRANDED_AGE_MS = 16 * 6e4;
|
|
29243
29494
|
const STRANDED_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -29247,7 +29498,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29247
29498
|
const now = Date.now();
|
|
29248
29499
|
for (const name of readdirSync26(strandedAccountsDir)) {
|
|
29249
29500
|
if (!STRANDED_UUID_RE.test(name)) continue;
|
|
29250
|
-
const pendingPath2 =
|
|
29501
|
+
const pendingPath2 = resolve38(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
|
|
29251
29502
|
if (!existsSync38(pendingPath2)) continue;
|
|
29252
29503
|
const ageMs = now - statSync17(pendingPath2).mtimeMs;
|
|
29253
29504
|
if (ageMs > STRANDED_AGE_MS) {
|
|
@@ -29266,8 +29517,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29266
29517
|
});
|
|
29267
29518
|
}
|
|
29268
29519
|
{
|
|
29269
|
-
const googleRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
29270
|
-
const googleAccountsDir =
|
|
29520
|
+
const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
|
|
29521
|
+
const googleAccountsDir = resolve38(googleRoot, "..", "data/accounts");
|
|
29271
29522
|
const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
|
|
29272
29523
|
const runGooglePendingAuditSafe = () => {
|
|
29273
29524
|
try {
|
|
@@ -29282,7 +29533,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29282
29533
|
firstRunDelayMs: 27e3,
|
|
29283
29534
|
run: runGooglePendingAuditSafe
|
|
29284
29535
|
});
|
|
29285
|
-
const googleAuditScript =
|
|
29536
|
+
const googleAuditScript = resolve38(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
|
|
29286
29537
|
const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
|
|
29287
29538
|
const runGoogleAccountAudit = (ctx) => {
|
|
29288
29539
|
if (!existsSync38(googleAuditScript)) return;
|
|
@@ -29310,7 +29561,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29310
29561
|
});
|
|
29311
29562
|
}
|
|
29312
29563
|
{
|
|
29313
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
29564
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
|
|
29314
29565
|
const STORAGE_AUDIT_INTERVAL_MS = 3e5;
|
|
29315
29566
|
const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
|
|
29316
29567
|
console.error(`[storage-audit] error="${err.message}"`);
|
|
@@ -29344,8 +29595,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29344
29595
|
});
|
|
29345
29596
|
}
|
|
29346
29597
|
{
|
|
29347
|
-
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
29348
|
-
const publishScript =
|
|
29598
|
+
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
|
|
29599
|
+
const publishScript = resolve38(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
|
|
29349
29600
|
const PUBLISH_INTERVAL_MS = 3e5;
|
|
29350
29601
|
const currentAccount = () => {
|
|
29351
29602
|
try {
|
|
@@ -29375,7 +29626,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29375
29626
|
}
|
|
29376
29627
|
};
|
|
29377
29628
|
const auditSnapshotAge = (account) => {
|
|
29378
|
-
const availPath =
|
|
29629
|
+
const availPath = resolve38(account.accountDir, "calendar-availability.json");
|
|
29379
29630
|
let hasBookingSite = false;
|
|
29380
29631
|
try {
|
|
29381
29632
|
if (existsSync38(availPath)) {
|
|
@@ -29385,7 +29636,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29385
29636
|
} catch {
|
|
29386
29637
|
}
|
|
29387
29638
|
if (!hasBookingSite) return;
|
|
29388
|
-
const statePath =
|
|
29639
|
+
const statePath = resolve38(account.accountDir, "state", "booking-availability", "last-publish.json");
|
|
29389
29640
|
let lastSuccessAt = null;
|
|
29390
29641
|
if (existsSync38(statePath)) {
|
|
29391
29642
|
try {
|
|
@@ -29417,8 +29668,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
29417
29668
|
startTimeEntryCensus(() => getSession());
|
|
29418
29669
|
startLedgerCensus(() => getSession());
|
|
29419
29670
|
{
|
|
29420
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
29421
|
-
const auditScript =
|
|
29671
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
|
|
29672
|
+
const auditScript = resolve38(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
|
|
29422
29673
|
const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
|
|
29423
29674
|
const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
|
|
29424
29675
|
const runConnectorAudit = (ctx) => {
|
|
@@ -29727,9 +29978,13 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
29727
29978
|
}
|
|
29728
29979
|
init({
|
|
29729
29980
|
configDir: configDirForWhatsApp,
|
|
29730
|
-
platformRoot:
|
|
29981
|
+
platformRoot: resolve38(process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..")),
|
|
29731
29982
|
accountConfig: bootAccountConfig,
|
|
29732
29983
|
onMessage: async (msg) => {
|
|
29984
|
+
if (isObserveAccount(msg.accountId)) {
|
|
29985
|
+
console.error(`[whatsapp:observe] op=BREACH account=${msg.accountId} sender=${msg.senderPhone} \u2014 dispatch reached the router from an observe socket`);
|
|
29986
|
+
return;
|
|
29987
|
+
}
|
|
29733
29988
|
if (msg.isOwnerMirror) {
|
|
29734
29989
|
console.error(`[whatsapp:route] skipped reason=owner-mirror senderId=${msg.senderPhone}`);
|
|
29735
29990
|
return;
|