@rubytech/create-maxy-code 0.1.463 → 0.1.465
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-17-task-1736-retire-mcp-stderr-tee.md +397 -0
- package/payload/platform/docs/superpowers/specs/2026-07-17-task-1736-retire-mcp-stderr-tee-design.md +128 -0
- package/payload/platform/lib/graph-mcp/dist/index.js +5 -24
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-mcp/src/index.ts +7 -25
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +2 -2
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js +2 -2
- package/payload/platform/lib/mcp-spawn-tee/src/__tests__/spawn-tee.test.ts +0 -25
- package/payload/platform/lib/mcp-spawn-tee/src/index.ts +2 -2
- package/payload/platform/lib/models/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/models/dist/index.js +7 -2
- package/payload/platform/lib/models/dist/index.js.map +1 -1
- package/payload/platform/lib/models/src/index.ts +7 -2
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +27 -36
- package/payload/platform/plugins/aeo/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/aeo/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/aeo/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/browser/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/browser/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/browser/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +10 -1
- package/payload/platform/plugins/connector/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/connector/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/connector/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/contacts/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/contacts/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/claudeai-connectors.md +16 -7
- package/payload/platform/plugins/docs/references/plugins-guide.md +10 -28
- package/payload/platform/plugins/email/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/email/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/filesystem/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/filesystem/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/filesystem/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/scripts/boot-smoke.sh +0 -1
- package/payload/platform/plugins/outlook/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/quickbooks/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/quickbooks/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/quickbooks/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/replicate/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/replicate/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/replicate/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/PLUGIN.md +21 -9
- package/payload/platform/plugins/scheduling/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/storage-broker/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/telegram/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/telegram/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/url-get/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/url-get/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/url-get/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/work/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/work/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/work/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/workflows/lib/mcp-spawn-tee/index.js +2 -2
- package/payload/platform/plugins/workflows/mcp/dist/index.js +0 -2
- package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -1
- package/payload/platform/scripts/__tests__/logs-rotate.test.sh +2 -2
- package/payload/platform/scripts/check-architecture-skill-no-drift.mjs +21 -14
- package/payload/platform/scripts/logs-rotate.sh +6 -6
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +23 -2
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +27 -2
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts +8 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js +11 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/passive-print-surface.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/passive-print-surface.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/passive-print-surface.js +31 -0
- package/payload/platform/services/claude-session-manager/dist/passive-print-surface.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/print-child-launcher.d.ts +8 -0
- package/payload/platform/services/claude-session-manager/dist/print-child-launcher.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/print-child-launcher.js +50 -0
- package/payload/platform/services/claude-session-manager/dist/print-child-launcher.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +23 -2
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +2 -2
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/server/server.js +699 -559
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +0 -69
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +0 -1
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js +0 -189
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +0 -1
- package/payload/platform/lib/mcp-stderr-tee/package.json +0 -7
- package/payload/platform/lib/mcp-stderr-tee/src/index.ts +0 -197
- package/payload/platform/lib/mcp-stderr-tee/tsconfig.json +0 -8
package/payload/server/server.js
CHANGED
|
@@ -1422,10 +1422,10 @@ var createStreamBody = (stream2) => {
|
|
|
1422
1422
|
});
|
|
1423
1423
|
return body;
|
|
1424
1424
|
};
|
|
1425
|
-
var getStats = (
|
|
1425
|
+
var getStats = (path3) => {
|
|
1426
1426
|
let stats;
|
|
1427
1427
|
try {
|
|
1428
|
-
stats = statSync(
|
|
1428
|
+
stats = statSync(path3);
|
|
1429
1429
|
} catch {
|
|
1430
1430
|
}
|
|
1431
1431
|
return stats;
|
|
@@ -1468,21 +1468,21 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1468
1468
|
return next();
|
|
1469
1469
|
}
|
|
1470
1470
|
}
|
|
1471
|
-
let
|
|
1471
|
+
let path3 = join(
|
|
1472
1472
|
root,
|
|
1473
1473
|
!optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename
|
|
1474
1474
|
);
|
|
1475
|
-
let stats = getStats(
|
|
1475
|
+
let stats = getStats(path3);
|
|
1476
1476
|
if (stats && stats.isDirectory()) {
|
|
1477
1477
|
const indexFile2 = options.index ?? "index.html";
|
|
1478
|
-
|
|
1479
|
-
stats = getStats(
|
|
1478
|
+
path3 = join(path3, indexFile2);
|
|
1479
|
+
stats = getStats(path3);
|
|
1480
1480
|
}
|
|
1481
1481
|
if (!stats) {
|
|
1482
|
-
await options.onNotFound?.(
|
|
1482
|
+
await options.onNotFound?.(path3, c);
|
|
1483
1483
|
return next();
|
|
1484
1484
|
}
|
|
1485
|
-
const mimeType = getMimeType(
|
|
1485
|
+
const mimeType = getMimeType(path3);
|
|
1486
1486
|
c.header("Content-Type", mimeType || "application/octet-stream");
|
|
1487
1487
|
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
|
1488
1488
|
const acceptEncodingSet = new Set(
|
|
@@ -1492,12 +1492,12 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1492
1492
|
if (!acceptEncodingSet.has(encoding)) {
|
|
1493
1493
|
continue;
|
|
1494
1494
|
}
|
|
1495
|
-
const precompressedStats = getStats(
|
|
1495
|
+
const precompressedStats = getStats(path3 + ENCODINGS[encoding]);
|
|
1496
1496
|
if (precompressedStats) {
|
|
1497
1497
|
c.header("Content-Encoding", encoding);
|
|
1498
1498
|
c.header("Vary", "Accept-Encoding", { append: true });
|
|
1499
1499
|
stats = precompressedStats;
|
|
1500
|
-
|
|
1500
|
+
path3 = path3 + ENCODINGS[encoding];
|
|
1501
1501
|
break;
|
|
1502
1502
|
}
|
|
1503
1503
|
}
|
|
@@ -1511,7 +1511,7 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1511
1511
|
result = c.body(null);
|
|
1512
1512
|
} else if (!range) {
|
|
1513
1513
|
c.header("Content-Length", size.toString());
|
|
1514
|
-
result = c.body(createStreamBody(createReadStream(
|
|
1514
|
+
result = c.body(createStreamBody(createReadStream(path3)), 200);
|
|
1515
1515
|
} else {
|
|
1516
1516
|
c.header("Accept-Ranges", "bytes");
|
|
1517
1517
|
c.header("Date", stats.birthtime.toUTCString());
|
|
@@ -1522,12 +1522,12 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1522
1522
|
end = size - 1;
|
|
1523
1523
|
}
|
|
1524
1524
|
const chunksize = end - start + 1;
|
|
1525
|
-
const stream2 = createReadStream(
|
|
1525
|
+
const stream2 = createReadStream(path3, { start, end });
|
|
1526
1526
|
c.header("Content-Length", chunksize.toString());
|
|
1527
1527
|
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
|
1528
1528
|
result = c.body(createStreamBody(stream2), 206);
|
|
1529
1529
|
}
|
|
1530
|
-
await options.onFound?.(
|
|
1530
|
+
await options.onFound?.(path3, c);
|
|
1531
1531
|
return result;
|
|
1532
1532
|
};
|
|
1533
1533
|
};
|
|
@@ -2176,7 +2176,7 @@ function listCredentialAccountIds(configDir2) {
|
|
|
2176
2176
|
// app/lib/whatsapp/session.ts
|
|
2177
2177
|
import { randomUUID } from "crypto";
|
|
2178
2178
|
import fsSync2 from "fs";
|
|
2179
|
-
import
|
|
2179
|
+
import fs3 from "fs/promises";
|
|
2180
2180
|
import { inspect } from "util";
|
|
2181
2181
|
import {
|
|
2182
2182
|
Browsers,
|
|
@@ -2187,7 +2187,63 @@ import {
|
|
|
2187
2187
|
useMultiFileAuthState
|
|
2188
2188
|
} from "@whiskeysockets/baileys";
|
|
2189
2189
|
import { createRequire } from "module";
|
|
2190
|
+
|
|
2191
|
+
// app/lib/whatsapp/baileys-debug-window.ts
|
|
2192
|
+
import fs2 from "fs";
|
|
2193
|
+
import path2 from "path";
|
|
2190
2194
|
var TAG2 = "[whatsapp:session]";
|
|
2195
|
+
var SENTINEL_BASENAME = "baileys-debug-until";
|
|
2196
|
+
var STAT_INTERVAL_MS = 1e3;
|
|
2197
|
+
var VALID_LEVELS = /* @__PURE__ */ new Set(["trace", "debug", "info"]);
|
|
2198
|
+
var INACTIVE = { active: false, level: "warn" };
|
|
2199
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2200
|
+
function sentinelPath(authDir) {
|
|
2201
|
+
return path2.join(authDir, SENTINEL_BASENAME);
|
|
2202
|
+
}
|
|
2203
|
+
function readSentinel(authDir, nowMs) {
|
|
2204
|
+
let raw;
|
|
2205
|
+
try {
|
|
2206
|
+
raw = fs2.readFileSync(sentinelPath(authDir), "utf-8");
|
|
2207
|
+
} catch {
|
|
2208
|
+
return { state: INACTIVE, deadlineMs: null };
|
|
2209
|
+
}
|
|
2210
|
+
const line = raw.split("\n", 1)[0].trim();
|
|
2211
|
+
if (!line) return { state: INACTIVE, deadlineMs: null };
|
|
2212
|
+
const tokens = line.split(/\s+/);
|
|
2213
|
+
const deadlineMs = Date.parse(tokens[0]);
|
|
2214
|
+
if (Number.isNaN(deadlineMs) || deadlineMs <= nowMs) {
|
|
2215
|
+
return { state: INACTIVE, deadlineMs: null };
|
|
2216
|
+
}
|
|
2217
|
+
let level = "trace";
|
|
2218
|
+
if (tokens.length > 1) {
|
|
2219
|
+
if (!VALID_LEVELS.has(tokens[1])) return { state: INACTIVE, deadlineMs: null };
|
|
2220
|
+
level = tokens[1];
|
|
2221
|
+
}
|
|
2222
|
+
return { state: { active: true, level }, deadlineMs };
|
|
2223
|
+
}
|
|
2224
|
+
function resolveDebugWindow(authDir, nowMs = Date.now()) {
|
|
2225
|
+
if (!authDir) return INACTIVE;
|
|
2226
|
+
const prev = cache.get(authDir);
|
|
2227
|
+
if (prev && nowMs - prev.checkedAtMs < STAT_INTERVAL_MS) {
|
|
2228
|
+
return prev.state;
|
|
2229
|
+
}
|
|
2230
|
+
const { state, deadlineMs } = readSentinel(authDir, nowMs);
|
|
2231
|
+
const wasActive = prev?.state.active ?? false;
|
|
2232
|
+
if (!wasActive && state.active) {
|
|
2233
|
+
console.error(
|
|
2234
|
+
`${TAG2} op=baileys-debug-window-open account=${path2.basename(authDir)} level=${state.level} until=${new Date(deadlineMs).toISOString()}`
|
|
2235
|
+
);
|
|
2236
|
+
} else if (wasActive && !state.active) {
|
|
2237
|
+
console.error(
|
|
2238
|
+
`${TAG2} op=baileys-debug-window-close account=${path2.basename(authDir)}`
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
cache.set(authDir, { checkedAtMs: nowMs, state });
|
|
2242
|
+
return state;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// app/lib/whatsapp/session.ts
|
|
2246
|
+
var TAG3 = "[whatsapp:session]";
|
|
2191
2247
|
var WIRE_TAG = "[whatsapp:wire]";
|
|
2192
2248
|
var baileysPkgVersion = (() => {
|
|
2193
2249
|
try {
|
|
@@ -2197,7 +2253,7 @@ var baileysPkgVersion = (() => {
|
|
|
2197
2253
|
return "unknown";
|
|
2198
2254
|
}
|
|
2199
2255
|
})();
|
|
2200
|
-
console.error(`${
|
|
2256
|
+
console.error(`${TAG3} baileys package loaded version=${baileysPkgVersion}`);
|
|
2201
2257
|
var __BAILEYS_TAG = "[whatsapp:baileys]";
|
|
2202
2258
|
var INSPECT_OPTS = {
|
|
2203
2259
|
depth: null,
|
|
@@ -2222,7 +2278,8 @@ function bindingsToPrefix(bindings) {
|
|
|
2222
2278
|
}
|
|
2223
2279
|
return parts.join(" ");
|
|
2224
2280
|
}
|
|
2225
|
-
|
|
2281
|
+
var LEVEL_RANK = { trace: 10, debug: 20, info: 30 };
|
|
2282
|
+
function createBaileysLogger(bindings = {}, authDir) {
|
|
2226
2283
|
const prefix = Object.keys(bindings).length > 0 ? bindingsToPrefix(bindings) : "";
|
|
2227
2284
|
const emit = (level, args) => {
|
|
2228
2285
|
const serialized = args.map(serializeArg).join(" ");
|
|
@@ -2232,23 +2289,32 @@ function createBaileysLogger(bindings = {}) {
|
|
|
2232
2289
|
console.error(__BAILEYS_TAG, level, serialized);
|
|
2233
2290
|
}
|
|
2234
2291
|
};
|
|
2292
|
+
const emitIfWindowed = (rank, level, args) => {
|
|
2293
|
+
const w = resolveDebugWindow(authDir);
|
|
2294
|
+
if (w.active && rank >= LEVEL_RANK[w.level]) {
|
|
2295
|
+
emit(level, args);
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2235
2298
|
return {
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2299
|
+
// Live getter: Baileys reads config.logger.level per-frame and (verified on
|
|
2300
|
+
// the pinned version) never shallow-copies the logger, so the window's level
|
|
2301
|
+
// reaches socket.js:449/465 as it changes. A Baileys that spread the logger
|
|
2302
|
+
// would freeze this to one value — revisit on upgrade.
|
|
2303
|
+
get level() {
|
|
2304
|
+
return resolveDebugWindow(authDir).level;
|
|
2242
2305
|
},
|
|
2306
|
+
trace: (...args) => emitIfWindowed(LEVEL_RANK.trace, "TRACE", args),
|
|
2307
|
+
debug: (...args) => emitIfWindowed(LEVEL_RANK.debug, "DEBUG", args),
|
|
2308
|
+
info: (...args) => emitIfWindowed(LEVEL_RANK.info, "INFO", args),
|
|
2243
2309
|
warn: (...args) => emit("WARN", args),
|
|
2244
2310
|
error: (...args) => emit("ERROR", args),
|
|
2245
2311
|
fatal: (...args) => emit("FATAL", args),
|
|
2246
|
-
child: (childBindings) => createBaileysLogger({ ...bindings, ...childBindings ?? {} })
|
|
2312
|
+
child: (childBindings) => createBaileysLogger({ ...bindings, ...childBindings ?? {} }, authDir)
|
|
2247
2313
|
};
|
|
2248
2314
|
}
|
|
2249
2315
|
var credsSaveQueue = Promise.resolve();
|
|
2250
2316
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
2251
|
-
console.error(`${
|
|
2317
|
+
console.error(`${TAG3} draining credential save queue\u2026`);
|
|
2252
2318
|
const timer2 = new Promise(
|
|
2253
2319
|
(resolve37) => setTimeout(() => resolve37("timeout"), timeoutMs)
|
|
2254
2320
|
);
|
|
@@ -2257,9 +2323,9 @@ async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
|
2257
2323
|
timer2
|
|
2258
2324
|
]);
|
|
2259
2325
|
if (result === "timeout") {
|
|
2260
|
-
console.error(`${
|
|
2326
|
+
console.error(`${TAG3} credential save queue drain timed out after ${timeoutMs}ms`);
|
|
2261
2327
|
} else {
|
|
2262
|
-
console.error(`${
|
|
2328
|
+
console.error(`${TAG3} credential save queue drained`);
|
|
2263
2329
|
}
|
|
2264
2330
|
}
|
|
2265
2331
|
function readCredsJsonRaw2(filePath) {
|
|
@@ -2282,53 +2348,53 @@ async function safeSaveCreds(authDir, saveCreds) {
|
|
|
2282
2348
|
JSON.parse(raw);
|
|
2283
2349
|
fsSync2.copyFileSync(credsPath, backupPath);
|
|
2284
2350
|
} catch (err) {
|
|
2285
|
-
console.warn(`${
|
|
2351
|
+
console.warn(`${TAG3} backup pre-copy failed (parse or write) authDir=${authDir}: ${String(err)}`);
|
|
2286
2352
|
}
|
|
2287
2353
|
}
|
|
2288
2354
|
} catch (err) {
|
|
2289
|
-
console.warn(`${
|
|
2355
|
+
console.warn(`${TAG3} backup preparation failed authDir=${authDir}: ${String(err)}`);
|
|
2290
2356
|
}
|
|
2291
2357
|
try {
|
|
2292
2358
|
await Promise.resolve(saveCreds());
|
|
2293
2359
|
const credsPath = resolveCredsPath(authDir);
|
|
2294
2360
|
try {
|
|
2295
|
-
const stats = await
|
|
2361
|
+
const stats = await fs3.stat(credsPath);
|
|
2296
2362
|
if (stats.size <= 1) {
|
|
2297
|
-
console.error(`${
|
|
2363
|
+
console.error(`${TAG3} WARNING: creds file empty/truncated after save path=${credsPath} size=${stats.size}`);
|
|
2298
2364
|
} else {
|
|
2299
|
-
console.error(`${
|
|
2365
|
+
console.error(`${TAG3} creds saved and verified path=${credsPath} size=${stats.size}`);
|
|
2300
2366
|
}
|
|
2301
2367
|
} catch (statErr) {
|
|
2302
|
-
console.error(`${
|
|
2368
|
+
console.error(`${TAG3} creds NOT found on disk after save path=${credsPath}: ${String(statErr)}`);
|
|
2303
2369
|
}
|
|
2304
2370
|
} catch (err) {
|
|
2305
|
-
console.error(`${
|
|
2371
|
+
console.error(`${TAG3} failed saving creds to ${authDir}: ${String(err)}`);
|
|
2306
2372
|
}
|
|
2307
2373
|
}
|
|
2308
2374
|
function enqueueSaveCreds(authDir, saveCreds) {
|
|
2309
2375
|
credsSaveQueue = credsSaveQueue.then(() => safeSaveCreds(authDir, saveCreds)).catch((err) => {
|
|
2310
|
-
console.error(`${
|
|
2376
|
+
console.error(`${TAG3} creds save queue error: ${String(err)}`);
|
|
2311
2377
|
});
|
|
2312
2378
|
}
|
|
2313
2379
|
async function createWaSocket(opts) {
|
|
2314
2380
|
const { authDir, onQr, onConnectionUpdate, silent, correlationId, account, getCodeIssuedTs } = opts;
|
|
2315
2381
|
const cid = correlationId ?? "none";
|
|
2316
2382
|
const acct = account ?? "unknown";
|
|
2317
|
-
await
|
|
2383
|
+
await fs3.mkdir(authDir, { recursive: true });
|
|
2318
2384
|
maybeRestoreCredsFromBackup(authDir);
|
|
2319
2385
|
const { state, saveCreds } = await useMultiFileAuthState(authDir);
|
|
2320
2386
|
const { version } = await fetchLatestBaileysVersion();
|
|
2321
2387
|
if (!silent) {
|
|
2322
2388
|
const preCensus = credsCensus(authDir);
|
|
2323
2389
|
console.error(
|
|
2324
|
-
`${
|
|
2390
|
+
`${TAG3} op=creds-pre cid=${cid} account=${acct} credsPresent=${preCensus.credsPresent} registered=${preCensus.registered} hasAccount=${preCensus.hasAccountSignature}`
|
|
2325
2391
|
);
|
|
2326
2392
|
}
|
|
2327
2393
|
const openTs = Date.now();
|
|
2328
2394
|
console.error(
|
|
2329
|
-
`${
|
|
2395
|
+
`${TAG3} op=socket-open cid=${cid} account=${acct} baileysVersion=${baileysPkgVersion} waProtocolVersion=${version.join(".")} openTs=${openTs}`
|
|
2330
2396
|
);
|
|
2331
|
-
const baileysLogger = createBaileysLogger();
|
|
2397
|
+
const baileysLogger = createBaileysLogger({}, authDir);
|
|
2332
2398
|
const sock = makeWASocket({
|
|
2333
2399
|
auth: {
|
|
2334
2400
|
creds: state.creds,
|
|
@@ -2343,7 +2409,7 @@ async function createWaSocket(opts) {
|
|
|
2343
2409
|
fireInitQueries: false
|
|
2344
2410
|
});
|
|
2345
2411
|
sock.ev.on("creds.update", () => {
|
|
2346
|
-
console.error(`${
|
|
2412
|
+
console.error(`${TAG3} creds.update received \u2014 saving to ${authDir}`);
|
|
2347
2413
|
enqueueSaveCreds(authDir, saveCreds);
|
|
2348
2414
|
});
|
|
2349
2415
|
let qrSequence = 0;
|
|
@@ -2363,14 +2429,14 @@ async function createWaSocket(opts) {
|
|
|
2363
2429
|
parts.push(`disconnectReason=${formatError(update.lastDisconnect.error)}`);
|
|
2364
2430
|
}
|
|
2365
2431
|
if (!silent && parts.length > 0) {
|
|
2366
|
-
console.error(`${
|
|
2432
|
+
console.error(`${TAG3} connection.update ${parts.join(" ")}`);
|
|
2367
2433
|
}
|
|
2368
2434
|
if (update.qr && !silent) {
|
|
2369
2435
|
const now = Date.now();
|
|
2370
2436
|
const sinceLastRefMs = lastQrTs ? now - lastQrTs : null;
|
|
2371
2437
|
lastQrTs = now;
|
|
2372
2438
|
console.error(
|
|
2373
|
-
`${
|
|
2439
|
+
`${TAG3} op=qr cid=${cid} qr=#${qrSequence} sinceOpenMs=${now - openTs} sinceLastRefMs=${sinceLastRefMs ?? "n/a"}`
|
|
2374
2440
|
);
|
|
2375
2441
|
}
|
|
2376
2442
|
if (update.isNewLogin === true && !pairSuccessLogged && !silent) {
|
|
@@ -2379,7 +2445,7 @@ async function createWaSocket(opts) {
|
|
|
2379
2445
|
const sinceCodeIssuedMs = codeIssuedTs ? Date.now() - codeIssuedTs : null;
|
|
2380
2446
|
const afterCensus = credsCensus(authDir);
|
|
2381
2447
|
console.error(
|
|
2382
|
-
`${
|
|
2448
|
+
`${TAG3} op=pair-success cid=${cid} account=${acct} sinceCodeIssuedMs=${sinceCodeIssuedMs ?? "n/a"} hasAccountAfter=${afterCensus.hasAccountSignature}`
|
|
2383
2449
|
);
|
|
2384
2450
|
}
|
|
2385
2451
|
if (update.qr && onQr) {
|
|
@@ -2389,25 +2455,25 @@ async function createWaSocket(opts) {
|
|
|
2389
2455
|
const status = getStatusCode(update.lastDisconnect?.error);
|
|
2390
2456
|
if (!silent) {
|
|
2391
2457
|
console.error(
|
|
2392
|
-
`${
|
|
2458
|
+
`${TAG3} op=close cid=${cid} account=${acct} status=${status ?? "unknown"} reason="${formatError(update.lastDisconnect?.error)}" socketLifeMs=${Date.now() - openTs} kind=${closeKind(status, everOpen)}`
|
|
2393
2459
|
);
|
|
2394
2460
|
}
|
|
2395
2461
|
if (status === DisconnectReason.loggedOut && !silent) {
|
|
2396
|
-
console.error(`${
|
|
2462
|
+
console.error(`${TAG3} session logged out (401) \u2014 re-link required`);
|
|
2397
2463
|
}
|
|
2398
2464
|
}
|
|
2399
2465
|
if (update.connection === "open") {
|
|
2400
2466
|
everOpen = true;
|
|
2401
|
-
if (!silent) console.error(`${
|
|
2467
|
+
if (!silent) console.error(`${TAG3} connected`);
|
|
2402
2468
|
}
|
|
2403
2469
|
onConnectionUpdate?.(update);
|
|
2404
2470
|
} catch (err) {
|
|
2405
|
-
console.error(`${
|
|
2471
|
+
console.error(`${TAG3} connection.update handler error: ${String(err)}`);
|
|
2406
2472
|
}
|
|
2407
2473
|
});
|
|
2408
2474
|
if (sock.ws && typeof sock.ws.on === "function") {
|
|
2409
2475
|
sock.ws.on("error", (err) => {
|
|
2410
|
-
console.error(`${
|
|
2476
|
+
console.error(`${TAG3} WebSocket error: ${String(err)}`);
|
|
2411
2477
|
});
|
|
2412
2478
|
sock.ws.on("CB:message", (node) => {
|
|
2413
2479
|
const attrs = node?.attrs ?? {};
|
|
@@ -2547,7 +2613,7 @@ function withTimeout(label, promise, timeoutMs) {
|
|
|
2547
2613
|
}
|
|
2548
2614
|
|
|
2549
2615
|
// app/lib/whatsapp/init-queries.ts
|
|
2550
|
-
var
|
|
2616
|
+
var TAG4 = "[whatsapp:init]";
|
|
2551
2617
|
var DEFAULT_QUERY_TIMEOUT_MS = 15e3;
|
|
2552
2618
|
var INSPECT_OPTS2 = {
|
|
2553
2619
|
depth: null,
|
|
@@ -2575,20 +2641,20 @@ async function runInitQueries(sock, ctx) {
|
|
|
2575
2641
|
elapsedMs: 0
|
|
2576
2642
|
};
|
|
2577
2643
|
console.error(
|
|
2578
|
-
`${
|
|
2644
|
+
`${TAG4} runInitQueries start account=${ctx.accountId} attempt=${ctx.attempt} selfPhone=${ctx.selfPhone ?? "unknown"} authDir=${ctx.authDir} queryTimeoutMs=${queryTimeoutMs}`
|
|
2579
2645
|
);
|
|
2580
2646
|
console.error(
|
|
2581
|
-
`${
|
|
2647
|
+
`${TAG4} fetchProps SKIPPED account=${ctx.accountId} reason=no-platform-consumer`
|
|
2582
2648
|
);
|
|
2583
2649
|
if (ctx.signal?.aborted) {
|
|
2584
2650
|
result.aborted = true;
|
|
2585
2651
|
result.elapsedMs = Date.now() - startedAt;
|
|
2586
2652
|
console.error(
|
|
2587
|
-
`${
|
|
2653
|
+
`${TAG4} runInitQueries aborted before start account=${ctx.accountId}`
|
|
2588
2654
|
);
|
|
2589
2655
|
return result;
|
|
2590
2656
|
}
|
|
2591
|
-
console.error(`${
|
|
2657
|
+
console.error(`${TAG4} fetchBlocklist start account=${ctx.accountId}`);
|
|
2592
2658
|
try {
|
|
2593
2659
|
const blocklist = await withTimeout(
|
|
2594
2660
|
"fetchBlocklist",
|
|
@@ -2598,24 +2664,24 @@ async function runInitQueries(sock, ctx) {
|
|
|
2598
2664
|
const count = Array.isArray(blocklist) ? blocklist.length : 0;
|
|
2599
2665
|
result.blocklistOk = true;
|
|
2600
2666
|
console.error(
|
|
2601
|
-
`${
|
|
2667
|
+
`${TAG4} fetchBlocklist ok account=${ctx.accountId} count=${count}`
|
|
2602
2668
|
);
|
|
2603
2669
|
} catch (err) {
|
|
2604
2670
|
const formatted = formatErr(err);
|
|
2605
2671
|
result.errors.blocklist = formatted;
|
|
2606
2672
|
console.error(
|
|
2607
|
-
`${
|
|
2673
|
+
`${TAG4} fetchBlocklist FAILED account=${ctx.accountId}: ${formatted}`
|
|
2608
2674
|
);
|
|
2609
2675
|
}
|
|
2610
2676
|
if (ctx.signal?.aborted) {
|
|
2611
2677
|
result.aborted = true;
|
|
2612
2678
|
result.elapsedMs = Date.now() - startedAt;
|
|
2613
2679
|
console.error(
|
|
2614
|
-
`${
|
|
2680
|
+
`${TAG4} runInitQueries aborted mid-sequence account=${ctx.accountId} after=fetchBlocklist`
|
|
2615
2681
|
);
|
|
2616
2682
|
return result;
|
|
2617
2683
|
}
|
|
2618
|
-
console.error(`${
|
|
2684
|
+
console.error(`${TAG4} fetchPrivacySettings start account=${ctx.accountId}`);
|
|
2619
2685
|
try {
|
|
2620
2686
|
const settings = await withTimeout(
|
|
2621
2687
|
"fetchPrivacySettings",
|
|
@@ -2625,18 +2691,18 @@ async function runInitQueries(sock, ctx) {
|
|
|
2625
2691
|
);
|
|
2626
2692
|
result.privacySettingsOk = true;
|
|
2627
2693
|
console.error(
|
|
2628
|
-
`${
|
|
2694
|
+
`${TAG4} fetchPrivacySettings ok account=${ctx.accountId} keys=${settings ? Object.keys(settings).join(",") : ""}`
|
|
2629
2695
|
);
|
|
2630
2696
|
} catch (err) {
|
|
2631
2697
|
const formatted = formatErr(err);
|
|
2632
2698
|
result.errors.privacySettings = formatted;
|
|
2633
2699
|
console.error(
|
|
2634
|
-
`${
|
|
2700
|
+
`${TAG4} fetchPrivacySettings FAILED account=${ctx.accountId}: ${formatted}`
|
|
2635
2701
|
);
|
|
2636
2702
|
}
|
|
2637
2703
|
result.elapsedMs = Date.now() - startedAt;
|
|
2638
2704
|
console.error(
|
|
2639
|
-
`${
|
|
2705
|
+
`${TAG4} runInitQueries done account=${ctx.accountId} blocklistOk=${result.blocklistOk} privacySettingsOk=${result.privacySettingsOk} elapsedMs=${result.elapsedMs}`
|
|
2640
2706
|
);
|
|
2641
2707
|
return result;
|
|
2642
2708
|
}
|
|
@@ -3143,6 +3209,12 @@ function readAllConversations(platformAccountId) {
|
|
|
3143
3209
|
return out;
|
|
3144
3210
|
}
|
|
3145
3211
|
|
|
3212
|
+
// app/lib/whatsapp/echo-already-recorded.ts
|
|
3213
|
+
function echoAlreadyRecorded(args) {
|
|
3214
|
+
const messageId = `whatsapp-live:${args.accountId}:${args.remoteJid}:${args.msgKeyId}`;
|
|
3215
|
+
return args.readConversation(args.platformAccountId, args.canonicalRemoteJid).some((r) => r.messageId === messageId);
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3146
3218
|
// app/lib/whatsapp/managed-account.ts
|
|
3147
3219
|
function normalizeEntry(v) {
|
|
3148
3220
|
return typeof v === "string" ? { account: v, mode: "active" } : { account: v.managesAccount, mode: v.mode ?? "active" };
|
|
@@ -3527,17 +3599,17 @@ var INBOUND_TTL_MS = 20 * 6e4;
|
|
|
3527
3599
|
var INBOUND_MAX = 5e3;
|
|
3528
3600
|
var agentSentCache = /* @__PURE__ */ new Map();
|
|
3529
3601
|
var inboundCache = /* @__PURE__ */ new Map();
|
|
3530
|
-
function pruneCache(
|
|
3602
|
+
function pruneCache(cache3, ttl, max) {
|
|
3531
3603
|
const now = Date.now();
|
|
3532
|
-
for (const [key, entry] of
|
|
3533
|
-
if (now - entry.ts > ttl)
|
|
3604
|
+
for (const [key, entry] of cache3) {
|
|
3605
|
+
if (now - entry.ts > ttl) cache3.delete(key);
|
|
3534
3606
|
}
|
|
3535
|
-
if (
|
|
3536
|
-
const excess =
|
|
3607
|
+
if (cache3.size > max) {
|
|
3608
|
+
const excess = cache3.size - max;
|
|
3537
3609
|
let count = 0;
|
|
3538
|
-
for (const key of
|
|
3610
|
+
for (const key of cache3.keys()) {
|
|
3539
3611
|
if (count >= excess) break;
|
|
3540
|
-
|
|
3612
|
+
cache3.delete(key);
|
|
3541
3613
|
count++;
|
|
3542
3614
|
}
|
|
3543
3615
|
}
|
|
@@ -3571,7 +3643,7 @@ function isDuplicateInbound(key) {
|
|
|
3571
3643
|
}
|
|
3572
3644
|
|
|
3573
3645
|
// app/lib/whatsapp/inbound/self-chat.ts
|
|
3574
|
-
var
|
|
3646
|
+
var TAG5 = "[whatsapp:self-chat]";
|
|
3575
3647
|
function normalizeJid(jid) {
|
|
3576
3648
|
const m = jid.match(/^(\d+)(?::\d+)?@(s\.whatsapp\.net|lid)$/i);
|
|
3577
3649
|
return m ? `${m[1]}@${m[2].toLowerCase()}` : jid;
|
|
@@ -3591,10 +3663,10 @@ function evaluateSelfChatCommand(input) {
|
|
|
3591
3663
|
const connAtMs = input.lastConnectedAtMs ?? 0;
|
|
3592
3664
|
const msgId = input.msgId ?? "?";
|
|
3593
3665
|
console.error(
|
|
3594
|
-
`${
|
|
3666
|
+
`${TAG5} op=seen account=${input.accountId} msgId=${msgId} tsMs=${tsMs} connAtMs=${connAtMs} fromMe=true`
|
|
3595
3667
|
);
|
|
3596
3668
|
const skip = (reason) => {
|
|
3597
|
-
console.error(`${
|
|
3669
|
+
console.error(`${TAG5} op=skip account=${input.accountId} msgId=${msgId} reason=${reason}`);
|
|
3598
3670
|
return { kind: "skip", reason };
|
|
3599
3671
|
};
|
|
3600
3672
|
if (input.msgId && isAgentSentMessage(input.msgId)) return skip("echo");
|
|
@@ -3602,13 +3674,13 @@ function evaluateSelfChatCommand(input) {
|
|
|
3602
3674
|
if (isDuplicateInbound(`${input.accountId}:${input.remoteJid}:${input.msgId}`)) return skip("duplicate");
|
|
3603
3675
|
if (!input.text) return skip("no-text");
|
|
3604
3676
|
console.error(
|
|
3605
|
-
`${
|
|
3677
|
+
`${TAG5} op=dispatch account=${input.accountId} msgId=${msgId} bytes=${Buffer.byteLength(input.text, "utf8")} agentType=admin`
|
|
3606
3678
|
);
|
|
3607
3679
|
return { kind: "dispatch", text: input.text };
|
|
3608
3680
|
}
|
|
3609
3681
|
|
|
3610
3682
|
// app/lib/whatsapp/activity.ts
|
|
3611
|
-
var
|
|
3683
|
+
var TAG6 = "[whatsapp:activity]";
|
|
3612
3684
|
var RECENT_EVENTS_MAX = 200;
|
|
3613
3685
|
var counters = /* @__PURE__ */ new Map();
|
|
3614
3686
|
var recentEvents = [];
|
|
@@ -3636,10 +3708,10 @@ function recordActivity(event) {
|
|
|
3636
3708
|
recentEvents.splice(0, recentEvents.length - RECENT_EVENTS_MAX);
|
|
3637
3709
|
}
|
|
3638
3710
|
console.error(
|
|
3639
|
-
`${
|
|
3711
|
+
`${TAG6} channel-activity: direction=${direction} account=${accountId} jid=${jid}` + (messageType ? ` type=${messageType}` : "")
|
|
3640
3712
|
);
|
|
3641
3713
|
} catch (err) {
|
|
3642
|
-
console.error(`${
|
|
3714
|
+
console.error(`${TAG6} recording failed: ${String(err)}`);
|
|
3643
3715
|
}
|
|
3644
3716
|
}
|
|
3645
3717
|
function getChannelActivity(accountId) {
|
|
@@ -3660,7 +3732,7 @@ function getChannelActivity(accountId) {
|
|
|
3660
3732
|
}
|
|
3661
3733
|
|
|
3662
3734
|
// app/lib/whatsapp/outbound/send.ts
|
|
3663
|
-
var
|
|
3735
|
+
var TAG7 = "[whatsapp:outbound]";
|
|
3664
3736
|
function buildQuoted(q) {
|
|
3665
3737
|
return {
|
|
3666
3738
|
key: {
|
|
@@ -3681,14 +3753,14 @@ async function sendTextMessage(sock, to, text, opts) {
|
|
|
3681
3753
|
const messageId = result?.key?.id;
|
|
3682
3754
|
if (messageId) {
|
|
3683
3755
|
trackAgentSentMessage(messageId);
|
|
3684
|
-
console.error(`${
|
|
3756
|
+
console.error(`${TAG7} sent text to=${jid} id=${messageId}`);
|
|
3685
3757
|
}
|
|
3686
3758
|
if (opts?.accountId) {
|
|
3687
3759
|
recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: "text" });
|
|
3688
3760
|
}
|
|
3689
3761
|
return { success: true, messageId: messageId ?? void 0 };
|
|
3690
3762
|
} catch (err) {
|
|
3691
|
-
console.error(`${
|
|
3763
|
+
console.error(`${TAG7} send failed to=${to}: ${String(err)}`);
|
|
3692
3764
|
return { success: false, error: String(err) };
|
|
3693
3765
|
}
|
|
3694
3766
|
}
|
|
@@ -3758,20 +3830,20 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
3758
3830
|
const messageId = result?.key?.id;
|
|
3759
3831
|
if (messageId) {
|
|
3760
3832
|
trackAgentSentMessage(messageId);
|
|
3761
|
-
console.error(`${
|
|
3833
|
+
console.error(`${TAG7} sent ${media.type} to=${jid} id=${messageId}`);
|
|
3762
3834
|
}
|
|
3763
3835
|
if (opts?.accountId) {
|
|
3764
3836
|
recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: media.type });
|
|
3765
3837
|
}
|
|
3766
3838
|
return { success: true, messageId: messageId ?? void 0 };
|
|
3767
3839
|
} catch (err) {
|
|
3768
|
-
console.error(`${
|
|
3840
|
+
console.error(`${TAG7} send media failed to=${to}: ${String(err)}`);
|
|
3769
3841
|
return { success: false, error: String(err) };
|
|
3770
3842
|
}
|
|
3771
3843
|
}
|
|
3772
3844
|
|
|
3773
3845
|
// app/lib/whatsapp/persist-message.ts
|
|
3774
|
-
var
|
|
3846
|
+
var TAG8 = "[whatsapp-persist]";
|
|
3775
3847
|
var sessionWriteLocks = /* @__PURE__ */ new Map();
|
|
3776
3848
|
async function persistWhatsAppMessage(input) {
|
|
3777
3849
|
if (!input.body && !input.attachmentId || !input.msgKeyId) return null;
|
|
@@ -3822,14 +3894,14 @@ async function persistWhatsAppMessage(input) {
|
|
|
3822
3894
|
};
|
|
3823
3895
|
const result = await session.run(cypher, params);
|
|
3824
3896
|
if (result.records.length === 0) {
|
|
3825
|
-
console.error(`${
|
|
3897
|
+
console.error(`${TAG8} skip reason=conversation-not-found accountId=${input.platformAccountId} cacheKey=${input.cacheKey} messageId=${messageId}`);
|
|
3826
3898
|
return null;
|
|
3827
3899
|
}
|
|
3828
3900
|
const rec = result.records[0];
|
|
3829
3901
|
const senderElementId = rec.get("senderElementId");
|
|
3830
3902
|
const senderReused = rec.get("senderReused") === true;
|
|
3831
3903
|
console.error(
|
|
3832
|
-
`${
|
|
3904
|
+
`${TAG8} sender-resolved telephone=${senderTelephone} nodeKind=Person elementId=${senderElementId} reused=${senderReused}`
|
|
3833
3905
|
);
|
|
3834
3906
|
const diskRecord = {
|
|
3835
3907
|
messageId,
|
|
@@ -3851,18 +3923,18 @@ async function persistWhatsAppMessage(input) {
|
|
|
3851
3923
|
const { created } = appendMessage(input.platformAccountId, diskRecord, altDedupeRemoteJids);
|
|
3852
3924
|
const ms = Date.now() - t0;
|
|
3853
3925
|
if (!created) {
|
|
3854
|
-
console.error(`${
|
|
3926
|
+
console.error(`${TAG8} noop reason=existing-message messageId=${messageId}`);
|
|
3855
3927
|
return { messageId, created: false, senderElementId };
|
|
3856
3928
|
}
|
|
3857
3929
|
console.error(
|
|
3858
|
-
`${
|
|
3930
|
+
`${TAG8} write messageId=${messageId} accountId=${input.platformAccountId} cacheKey=${input.cacheKey} fromMe=${input.fromMe} dateSent=${dateSentIso} bodyBytes=${Buffer.byteLength(input.body, "utf8")} attachment=${input.attachmentId ?? "none"} ms=${ms}`
|
|
3859
3931
|
);
|
|
3860
3932
|
return { messageId, created: true, senderElementId };
|
|
3861
3933
|
} catch (err) {
|
|
3862
3934
|
const ms = Date.now() - t0;
|
|
3863
3935
|
const reason = sanitizeReason(err);
|
|
3864
3936
|
console.error(
|
|
3865
|
-
`${
|
|
3937
|
+
`${TAG8} FAIL accountId=${input.platformAccountId} waname=${input.accountId} remoteJid=${input.remoteJid} msgKey=${input.msgKeyId} reason=${reason} ms=${ms}`
|
|
3866
3938
|
);
|
|
3867
3939
|
return null;
|
|
3868
3940
|
} finally {
|
|
@@ -3895,7 +3967,7 @@ function sanitizeReason(err) {
|
|
|
3895
3967
|
}
|
|
3896
3968
|
|
|
3897
3969
|
// app/lib/whatsapp/ensure-conversation.ts
|
|
3898
|
-
var
|
|
3970
|
+
var TAG9 = "[whatsapp-persist]";
|
|
3899
3971
|
async function ensureWhatsAppConversation(input) {
|
|
3900
3972
|
const t0 = Date.now();
|
|
3901
3973
|
try {
|
|
@@ -3907,19 +3979,19 @@ async function ensureWhatsAppConversation(input) {
|
|
|
3907
3979
|
const ms = Date.now() - t0;
|
|
3908
3980
|
if (!result.sessionId) {
|
|
3909
3981
|
console.error(
|
|
3910
|
-
`${
|
|
3982
|
+
`${TAG9} conversation-merged FAIL cacheKey=${input.cacheKey} reason=null-sessionId ms=${ms}`
|
|
3911
3983
|
);
|
|
3912
3984
|
return null;
|
|
3913
3985
|
}
|
|
3914
3986
|
console.error(
|
|
3915
|
-
`${
|
|
3987
|
+
`${TAG9} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
|
|
3916
3988
|
);
|
|
3917
3989
|
return { sessionId: result.sessionId, created: result.created };
|
|
3918
3990
|
} catch (err) {
|
|
3919
3991
|
const ms = Date.now() - t0;
|
|
3920
3992
|
const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
|
|
3921
3993
|
console.error(
|
|
3922
|
-
`${
|
|
3994
|
+
`${TAG9} conversation-merged FAIL cacheKey=${input.cacheKey} reason=${reason} ms=${ms}`
|
|
3923
3995
|
);
|
|
3924
3996
|
return null;
|
|
3925
3997
|
}
|
|
@@ -3929,16 +4001,16 @@ async function ensureWhatsAppConversation(input) {
|
|
|
3929
4001
|
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
3930
4002
|
import { resolve as resolve3 } from "path";
|
|
3931
4003
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3932
|
-
var
|
|
4004
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
3933
4005
|
function enumerateValidAccountIds(accountsDir) {
|
|
3934
|
-
const cached3 =
|
|
4006
|
+
const cached3 = cache2.get(accountsDir);
|
|
3935
4007
|
if (cached3 !== void 0) return cached3;
|
|
3936
4008
|
let names;
|
|
3937
4009
|
try {
|
|
3938
4010
|
names = readdirSync2(accountsDir);
|
|
3939
4011
|
} catch (err) {
|
|
3940
4012
|
if (err.code === "ENOENT") {
|
|
3941
|
-
|
|
4013
|
+
cache2.set(accountsDir, []);
|
|
3942
4014
|
return [];
|
|
3943
4015
|
}
|
|
3944
4016
|
throw err;
|
|
@@ -3955,7 +4027,7 @@ function enumerateValidAccountIds(accountsDir) {
|
|
|
3955
4027
|
if (code === "ENOENT") continue;
|
|
3956
4028
|
}
|
|
3957
4029
|
}
|
|
3958
|
-
|
|
4030
|
+
cache2.set(accountsDir, valid);
|
|
3959
4031
|
return valid;
|
|
3960
4032
|
}
|
|
3961
4033
|
function resolveHouseOrSoleAccountId(accountsDir) {
|
|
@@ -4553,7 +4625,7 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
|
|
|
4553
4625
|
}
|
|
4554
4626
|
|
|
4555
4627
|
// app/lib/whatsapp/inbound/media.ts
|
|
4556
|
-
var
|
|
4628
|
+
var TAG10 = "[whatsapp:media]";
|
|
4557
4629
|
var MEDIA_DIR = "/tmp/maxy-media";
|
|
4558
4630
|
var DOWNLOAD_TIMEOUT_MS = 45e3;
|
|
4559
4631
|
function deterministicAttachmentId(seed) {
|
|
@@ -4641,25 +4713,25 @@ async function downloadInboundMediaBuffer(msg, sock, opts) {
|
|
|
4641
4713
|
}
|
|
4642
4714
|
);
|
|
4643
4715
|
if (!buffer || buffer.length === 0) {
|
|
4644
|
-
console.error(`${
|
|
4716
|
+
console.error(`${TAG10} primary download returned empty, trying direct fallback`);
|
|
4645
4717
|
const downloadable = getDownloadableContent(content);
|
|
4646
4718
|
if (downloadable) {
|
|
4647
4719
|
try {
|
|
4648
4720
|
const stream2 = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
4649
4721
|
buffer = await streamToBuffer(stream2);
|
|
4650
4722
|
} catch (fallbackErr) {
|
|
4651
|
-
console.error(`${
|
|
4723
|
+
console.error(`${TAG10} direct download fallback failed: ${String(fallbackErr)}`);
|
|
4652
4724
|
}
|
|
4653
4725
|
}
|
|
4654
4726
|
}
|
|
4655
4727
|
if (!buffer || buffer.length === 0) {
|
|
4656
|
-
console.error(`${
|
|
4728
|
+
console.error(`${TAG10} download failed: empty buffer for ${mimetype ?? "unknown"}`);
|
|
4657
4729
|
return void 0;
|
|
4658
4730
|
}
|
|
4659
4731
|
if (buffer.length > maxBytes) {
|
|
4660
4732
|
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
4661
4733
|
const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
|
|
4662
|
-
console.error(`${
|
|
4734
|
+
console.error(`${TAG10} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
|
|
4663
4735
|
return void 0;
|
|
4664
4736
|
}
|
|
4665
4737
|
return {
|
|
@@ -4671,7 +4743,7 @@ async function downloadInboundMediaBuffer(msg, sock, opts) {
|
|
|
4671
4743
|
timeoutMs
|
|
4672
4744
|
);
|
|
4673
4745
|
} catch (err) {
|
|
4674
|
-
console.error(`${
|
|
4746
|
+
console.error(`${TAG10} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
|
|
4675
4747
|
return void 0;
|
|
4676
4748
|
}
|
|
4677
4749
|
}
|
|
@@ -4683,7 +4755,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
4683
4755
|
const filePath = join6(MEDIA_DIR, `${randomUUID4()}.${ext}`);
|
|
4684
4756
|
await writeFile2(filePath, res.buffer);
|
|
4685
4757
|
const sizeKB = (res.buffer.length / 1024).toFixed(0);
|
|
4686
|
-
console.error(`${
|
|
4758
|
+
console.error(`${TAG10} media downloaded type=${res.mimetype} size=${sizeKB}KB path=${filePath}`);
|
|
4687
4759
|
return { path: filePath, mimetype: res.mimetype, size: res.buffer.length };
|
|
4688
4760
|
}
|
|
4689
4761
|
async function storeInboundMediaServable(msg, sock, accountId, seed, opts) {
|
|
@@ -4692,19 +4764,27 @@ async function storeInboundMediaServable(msg, sock, accountId, seed, opts) {
|
|
|
4692
4764
|
if (attachmentExists(accountId, attachmentId)) return attachmentId;
|
|
4693
4765
|
const res = await downloadInboundMediaBufferCached(msg, sock, opts);
|
|
4694
4766
|
if (!res) {
|
|
4695
|
-
console.error(`${
|
|
4767
|
+
console.error(`${TAG10} servable-store skip reason=no-buffer id=${attachmentId}`);
|
|
4696
4768
|
return null;
|
|
4697
4769
|
}
|
|
4698
4770
|
const filename = res.filename ?? `${attachmentId}.${mimeToExt(res.mimetype)}`;
|
|
4699
4771
|
await storeMediaBuffer(accountId, attachmentId, filename, res.mimetype, res.buffer);
|
|
4700
|
-
console.error(`${
|
|
4772
|
+
console.error(`${TAG10} servable-store ok id=${attachmentId} mime=${res.mimetype} bytes=${res.buffer.length}`);
|
|
4701
4773
|
return attachmentId;
|
|
4702
4774
|
} catch (err) {
|
|
4703
|
-
console.error(`${
|
|
4775
|
+
console.error(`${TAG10} servable-store FAIL id=${attachmentId} reason=${String(err).slice(0, 120)}`);
|
|
4704
4776
|
return null;
|
|
4705
4777
|
}
|
|
4706
4778
|
}
|
|
4707
4779
|
|
|
4780
|
+
// app/lib/whatsapp/outbound/document-limits.ts
|
|
4781
|
+
var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
|
|
4782
|
+
|
|
4783
|
+
// app/lib/whatsapp/echo-store-max-bytes.ts
|
|
4784
|
+
function echoStoreMaxBytes(args) {
|
|
4785
|
+
return args.fromMe ? WHATSAPP_DOCUMENT_MAX_BYTES : (args.mediaMaxMb ?? 50) * 1024 * 1024;
|
|
4786
|
+
}
|
|
4787
|
+
|
|
4708
4788
|
// app/lib/whatsapp/inbound/bounded-queue.ts
|
|
4709
4789
|
function createBoundedQueue(limit, opts) {
|
|
4710
4790
|
if (!Number.isInteger(limit) || limit < 1) {
|
|
@@ -4740,7 +4820,7 @@ function createBoundedQueue(limit, opts) {
|
|
|
4740
4820
|
}
|
|
4741
4821
|
|
|
4742
4822
|
// app/lib/whatsapp/inbound/debounce.ts
|
|
4743
|
-
var
|
|
4823
|
+
var TAG11 = "[whatsapp:debounce]";
|
|
4744
4824
|
var STT_TAG = "[whatsapp:stt-await]";
|
|
4745
4825
|
function createInboundDebouncer(opts) {
|
|
4746
4826
|
const { debounceMs, buildKey, onFlush, onError } = opts;
|
|
@@ -4771,7 +4851,7 @@ function createInboundDebouncer(opts) {
|
|
|
4771
4851
|
pending.delete(key);
|
|
4772
4852
|
const batchSize = batch.entries.length;
|
|
4773
4853
|
try {
|
|
4774
|
-
console.error(`${
|
|
4854
|
+
console.error(`${TAG11} debounce flush key=${key} batchSize=${batchSize}`);
|
|
4775
4855
|
const result = onFlush(batch.entries);
|
|
4776
4856
|
if (result && typeof result.catch === "function") {
|
|
4777
4857
|
result.catch(onError);
|
|
@@ -4851,7 +4931,7 @@ function combineInboundBatch(entries) {
|
|
|
4851
4931
|
}
|
|
4852
4932
|
|
|
4853
4933
|
// app/lib/whatsapp/opening-hours.ts
|
|
4854
|
-
var
|
|
4934
|
+
var TAG12 = "[whatsapp:hours]";
|
|
4855
4935
|
async function isBusinessOpen(accountId) {
|
|
4856
4936
|
try {
|
|
4857
4937
|
const timezone = await resolveTimezone(accountId);
|
|
@@ -4869,7 +4949,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4869
4949
|
{ accountId, dayOfWeek, previousDayOfWeek }
|
|
4870
4950
|
);
|
|
4871
4951
|
if (result.records.length === 0) {
|
|
4872
|
-
console.error(`${
|
|
4952
|
+
console.error(`${TAG12} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
4873
4953
|
return { open: true, reason: "no opening hours configured" };
|
|
4874
4954
|
}
|
|
4875
4955
|
const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
|
|
@@ -4878,7 +4958,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4878
4958
|
closes: String(r.get("closes")).trim()
|
|
4879
4959
|
}));
|
|
4880
4960
|
if (specs.length === 0) {
|
|
4881
|
-
console.error(`${
|
|
4961
|
+
console.error(`${TAG12} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
4882
4962
|
return { open: true, reason: "no opening hours configured" };
|
|
4883
4963
|
}
|
|
4884
4964
|
for (const spec of specs) {
|
|
@@ -4888,7 +4968,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4888
4968
|
if (spec.opens > spec.closes && currentTime < spec.closes) {
|
|
4889
4969
|
const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
|
|
4890
4970
|
console.error(
|
|
4891
|
-
`${
|
|
4971
|
+
`${TAG12} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
4892
4972
|
);
|
|
4893
4973
|
return {
|
|
4894
4974
|
open: true,
|
|
@@ -4901,7 +4981,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4901
4981
|
} else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
|
|
4902
4982
|
const hoursStr = `${spec.opens}-${spec.closes}`;
|
|
4903
4983
|
console.error(
|
|
4904
|
-
`${
|
|
4984
|
+
`${TAG12} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
4905
4985
|
);
|
|
4906
4986
|
return {
|
|
4907
4987
|
open: true,
|
|
@@ -4915,7 +4995,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4915
4995
|
const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
|
|
4916
4996
|
const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
|
|
4917
4997
|
console.error(
|
|
4918
|
-
`${
|
|
4998
|
+
`${TAG12} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
|
|
4919
4999
|
);
|
|
4920
5000
|
return {
|
|
4921
5001
|
open: false,
|
|
@@ -4929,7 +5009,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4929
5009
|
}
|
|
4930
5010
|
} catch (err) {
|
|
4931
5011
|
console.error(
|
|
4932
|
-
`${
|
|
5012
|
+
`${TAG12} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
|
|
4933
5013
|
);
|
|
4934
5014
|
return { open: true, reason: "hours check failed (treating as open)" };
|
|
4935
5015
|
}
|
|
@@ -4975,7 +5055,7 @@ import { execFile } from "child_process";
|
|
|
4975
5055
|
import { unlink, stat } from "fs/promises";
|
|
4976
5056
|
import { promisify } from "util";
|
|
4977
5057
|
var execFileAsync = promisify(execFile);
|
|
4978
|
-
var
|
|
5058
|
+
var TAG13 = "[stt]";
|
|
4979
5059
|
var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
|
|
4980
5060
|
var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
|
|
4981
5061
|
var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
@@ -4994,11 +5074,11 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4994
5074
|
const s = await stat(audioPath);
|
|
4995
5075
|
audioBytes = s.size;
|
|
4996
5076
|
} catch {
|
|
4997
|
-
console.error(`${
|
|
5077
|
+
console.error(`${TAG13} failed: file not readable path=${audioPath}`);
|
|
4998
5078
|
return void 0;
|
|
4999
5079
|
}
|
|
5000
5080
|
console.error(
|
|
5001
|
-
`${
|
|
5081
|
+
`${TAG13} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
|
|
5002
5082
|
);
|
|
5003
5083
|
const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
|
|
5004
5084
|
try {
|
|
@@ -5017,7 +5097,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
5017
5097
|
], { timeout: 3e4 });
|
|
5018
5098
|
} catch (err) {
|
|
5019
5099
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5020
|
-
console.error(`${
|
|
5100
|
+
console.error(`${TAG13} failed: ffmpeg conversion error=${reason}`);
|
|
5021
5101
|
return void 0;
|
|
5022
5102
|
}
|
|
5023
5103
|
try {
|
|
@@ -5033,20 +5113,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
5033
5113
|
const durationMs = Date.now() - startMs;
|
|
5034
5114
|
const { text, count: stripped } = stripSpecialTokens(stdout.trim());
|
|
5035
5115
|
if (!text) {
|
|
5036
|
-
console.error(`${
|
|
5116
|
+
console.error(`${TAG13} failed: whisper returned empty output duration_ms=${durationMs}`);
|
|
5037
5117
|
return void 0;
|
|
5038
5118
|
}
|
|
5039
5119
|
const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
|
|
5040
5120
|
const language = langMatch?.[1] ?? "unknown";
|
|
5041
5121
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
5042
5122
|
console.error(
|
|
5043
|
-
`${
|
|
5123
|
+
`${TAG13} done provider=whisper-local duration_ms=${durationMs} words=${words} stripped=${stripped} lang=${language}`
|
|
5044
5124
|
);
|
|
5045
5125
|
return { text, language, durationMs };
|
|
5046
5126
|
} catch (err) {
|
|
5047
5127
|
const durationMs = Date.now() - startMs;
|
|
5048
5128
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5049
|
-
console.error(`${
|
|
5129
|
+
console.error(`${TAG13} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
|
|
5050
5130
|
return void 0;
|
|
5051
5131
|
} finally {
|
|
5052
5132
|
unlink(wavPath).catch(() => {
|
|
@@ -5065,7 +5145,7 @@ var WaReplyError = class extends Error {
|
|
|
5065
5145
|
};
|
|
5066
5146
|
|
|
5067
5147
|
// app/lib/whatsapp/manager.ts
|
|
5068
|
-
var
|
|
5148
|
+
var TAG14 = "[whatsapp:manager]";
|
|
5069
5149
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
5070
5150
|
var MESSAGE_STORE_MAX = 500;
|
|
5071
5151
|
var APPEND_MEDIA_CONCURRENCY = 3;
|
|
@@ -5092,7 +5172,7 @@ function storeMessage(storeKey, entry) {
|
|
|
5092
5172
|
if (entries.length > MESSAGE_STORE_MAX) {
|
|
5093
5173
|
const trimmed = entries.length - MESSAGE_STORE_MAX;
|
|
5094
5174
|
entries.splice(0, trimmed);
|
|
5095
|
-
console.error(`${
|
|
5175
|
+
console.error(`${TAG14} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
5096
5176
|
}
|
|
5097
5177
|
}
|
|
5098
5178
|
function deriveCacheKey(input) {
|
|
@@ -5135,7 +5215,7 @@ function buildSelfChatDispatch(input) {
|
|
|
5135
5215
|
}
|
|
5136
5216
|
async function init(opts) {
|
|
5137
5217
|
if (initialized) {
|
|
5138
|
-
console.error(`${
|
|
5218
|
+
console.error(`${TAG14} already initialized`);
|
|
5139
5219
|
return;
|
|
5140
5220
|
}
|
|
5141
5221
|
configDir = opts.configDir;
|
|
@@ -5143,20 +5223,20 @@ async function init(opts) {
|
|
|
5143
5223
|
loadConfig(opts.accountConfig);
|
|
5144
5224
|
const accountIds = listCredentialAccountIds(configDir);
|
|
5145
5225
|
if (accountIds.length === 0) {
|
|
5146
|
-
console.error(`${
|
|
5226
|
+
console.error(`${TAG14} init: no stored WhatsApp credentials found`);
|
|
5147
5227
|
initialized = true;
|
|
5148
5228
|
return;
|
|
5149
5229
|
}
|
|
5150
|
-
console.error(`${
|
|
5230
|
+
console.error(`${TAG14} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
|
|
5151
5231
|
initialized = true;
|
|
5152
5232
|
for (const accountId of accountIds) {
|
|
5153
5233
|
const accountCfg = whatsAppConfig.accounts?.[accountId];
|
|
5154
5234
|
if (accountCfg?.enabled === false) {
|
|
5155
|
-
console.error(`${
|
|
5235
|
+
console.error(`${TAG14} skipping disabled account=${accountId}`);
|
|
5156
5236
|
continue;
|
|
5157
5237
|
}
|
|
5158
5238
|
startConnection(accountId).catch((err) => {
|
|
5159
|
-
console.error(`${
|
|
5239
|
+
console.error(`${TAG14} failed to auto-start account=${accountId}: ${formatError(err)}`);
|
|
5160
5240
|
});
|
|
5161
5241
|
}
|
|
5162
5242
|
}
|
|
@@ -5165,7 +5245,7 @@ async function startConnection(accountId) {
|
|
|
5165
5245
|
const authDir = resolveAuthDir(configDir, accountId);
|
|
5166
5246
|
const hasAuth = await authExists(authDir);
|
|
5167
5247
|
if (!hasAuth) {
|
|
5168
|
-
console.error(`${
|
|
5248
|
+
console.error(`${TAG14} no credentials for account=${accountId}`);
|
|
5169
5249
|
return;
|
|
5170
5250
|
}
|
|
5171
5251
|
let platformAccountId;
|
|
@@ -5216,11 +5296,11 @@ async function stopConnection(accountId) {
|
|
|
5216
5296
|
conn.sock.ev.removeAllListeners("creds.update");
|
|
5217
5297
|
conn.sock.ws?.close?.();
|
|
5218
5298
|
} catch (err) {
|
|
5219
|
-
console.warn(`${
|
|
5299
|
+
console.warn(`${TAG14} socket cleanup error during stop account=${accountId}: ${String(err)}`);
|
|
5220
5300
|
}
|
|
5221
5301
|
}
|
|
5222
5302
|
connections.delete(accountId);
|
|
5223
|
-
console.error(`${
|
|
5303
|
+
console.error(`${TAG14} stopped account=${accountId}`);
|
|
5224
5304
|
}
|
|
5225
5305
|
function getStatus() {
|
|
5226
5306
|
return Array.from(connections.values()).map((conn) => ({
|
|
@@ -5299,9 +5379,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
5299
5379
|
monitorInbound(conn);
|
|
5300
5380
|
try {
|
|
5301
5381
|
await sock.sendPresenceUpdate("available");
|
|
5302
|
-
console.error(`${
|
|
5382
|
+
console.error(`${TAG14} presence set to available account=${accountId}`);
|
|
5303
5383
|
} catch (err) {
|
|
5304
|
-
console.error(`${
|
|
5384
|
+
console.error(`${TAG14} presence update failed account=${accountId}: ${String(err)}`);
|
|
5305
5385
|
}
|
|
5306
5386
|
await runInitQueries(sock, {
|
|
5307
5387
|
accountId,
|
|
@@ -5312,7 +5392,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
5312
5392
|
});
|
|
5313
5393
|
watchForDisconnect(conn);
|
|
5314
5394
|
attachSelfIdRefreshOnCredsUpdate(conn);
|
|
5315
|
-
console.error(`${
|
|
5395
|
+
console.error(`${TAG14} registered login socket for account=${accountId} phone=${conn.selfPhone ?? "unknown"}`);
|
|
5316
5396
|
}
|
|
5317
5397
|
function attachSelfIdRefreshOnCredsUpdate(conn) {
|
|
5318
5398
|
if (!conn.sock) return;
|
|
@@ -5357,7 +5437,7 @@ async function shutdown() {
|
|
|
5357
5437
|
const ids = Array.from(connections.keys());
|
|
5358
5438
|
await Promise.all(ids.map((id) => stopConnection(id)));
|
|
5359
5439
|
initialized = false;
|
|
5360
|
-
console.error(`${
|
|
5440
|
+
console.error(`${TAG14} shutdown complete`);
|
|
5361
5441
|
}
|
|
5362
5442
|
function loadConfig(accountConfig) {
|
|
5363
5443
|
try {
|
|
@@ -5368,19 +5448,19 @@ function loadConfig(accountConfig) {
|
|
|
5368
5448
|
if (result.ok) {
|
|
5369
5449
|
whatsAppConfig = result.data;
|
|
5370
5450
|
if (result.droppedKeys.length > 0) {
|
|
5371
|
-
console.error(`${
|
|
5451
|
+
console.error(`${TAG14} config: dropped unknown keys=[${result.droppedKeys.join(",")}] preserved known config`);
|
|
5372
5452
|
}
|
|
5373
5453
|
} else {
|
|
5374
|
-
console.error(`${
|
|
5454
|
+
console.error(`${TAG14} config validation failed: ${result.error.message}`);
|
|
5375
5455
|
whatsAppConfig = {};
|
|
5376
5456
|
}
|
|
5377
5457
|
const diskAdmin = Array.isArray(wa.adminPhones) ? wa.adminPhones : [];
|
|
5378
5458
|
if (diskAdmin.length > 0 && (whatsAppConfig.adminPhones?.length ?? 0) === 0) {
|
|
5379
|
-
console.error(`${
|
|
5459
|
+
console.error(`${TAG14} WARN adminPhones present on disk but empty after load`);
|
|
5380
5460
|
}
|
|
5381
5461
|
}
|
|
5382
5462
|
} catch (err) {
|
|
5383
|
-
console.error(`${
|
|
5463
|
+
console.error(`${TAG14} config load error: ${String(err)}`);
|
|
5384
5464
|
whatsAppConfig = {};
|
|
5385
5465
|
}
|
|
5386
5466
|
}
|
|
@@ -5391,7 +5471,7 @@ async function connectWithReconnect(conn) {
|
|
|
5391
5471
|
let cycleError = null;
|
|
5392
5472
|
let connectedAt;
|
|
5393
5473
|
try {
|
|
5394
|
-
console.error(`${
|
|
5474
|
+
console.error(`${TAG14} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
|
|
5395
5475
|
if (conn.debouncer) {
|
|
5396
5476
|
await conn.debouncer.destroy();
|
|
5397
5477
|
conn.debouncer = null;
|
|
@@ -5404,7 +5484,7 @@ async function connectWithReconnect(conn) {
|
|
|
5404
5484
|
conn.sock = sock;
|
|
5405
5485
|
applySelfIdentity(conn, sock, "pre-listener");
|
|
5406
5486
|
monitorInbound(conn);
|
|
5407
|
-
console.error(`${
|
|
5487
|
+
console.error(`${TAG14} socket created account=${conn.accountId} \u2014 waiting for connection`);
|
|
5408
5488
|
await waitForConnection(sock);
|
|
5409
5489
|
connectedAt = Date.now();
|
|
5410
5490
|
conn.connected = true;
|
|
@@ -5412,12 +5492,12 @@ async function connectWithReconnect(conn) {
|
|
|
5412
5492
|
conn.lastError = void 0;
|
|
5413
5493
|
conn.terminalReason = void 0;
|
|
5414
5494
|
applySelfIdentity(conn, sock, "post-connect");
|
|
5415
|
-
console.error(`${
|
|
5495
|
+
console.error(`${TAG14} connected account=${conn.accountId} phone=${conn.selfPhone ?? "unknown"}`);
|
|
5416
5496
|
try {
|
|
5417
5497
|
await sock.sendPresenceUpdate("available");
|
|
5418
|
-
console.error(`${
|
|
5498
|
+
console.error(`${TAG14} presence set to available account=${conn.accountId}`);
|
|
5419
5499
|
} catch (err) {
|
|
5420
|
-
console.error(`${
|
|
5500
|
+
console.error(`${TAG14} presence update failed account=${conn.accountId}: ${String(err)}`);
|
|
5421
5501
|
}
|
|
5422
5502
|
await runInitQueries(sock, {
|
|
5423
5503
|
accountId: conn.accountId,
|
|
@@ -5440,10 +5520,10 @@ async function connectWithReconnect(conn) {
|
|
|
5440
5520
|
}
|
|
5441
5521
|
const classification = classifyDisconnect(err);
|
|
5442
5522
|
conn.lastError = classification.message;
|
|
5443
|
-
console.error(`${
|
|
5523
|
+
console.error(`${TAG14} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
5444
5524
|
if (!classification.shouldRetry) {
|
|
5445
5525
|
conn.terminalReason = classification.message;
|
|
5446
|
-
console.error(`${
|
|
5526
|
+
console.error(`${TAG14} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
5447
5527
|
return;
|
|
5448
5528
|
}
|
|
5449
5529
|
}
|
|
@@ -5456,7 +5536,7 @@ async function connectWithReconnect(conn) {
|
|
|
5456
5536
|
if (decision.action === "abort") {
|
|
5457
5537
|
const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
|
|
5458
5538
|
console.error(
|
|
5459
|
-
`${
|
|
5539
|
+
`${TAG14} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
|
|
5460
5540
|
);
|
|
5461
5541
|
conn.sessionStuckReason = stuckReason;
|
|
5462
5542
|
conn.lastError = stuckReason;
|
|
@@ -5465,17 +5545,17 @@ async function connectWithReconnect(conn) {
|
|
|
5465
5545
|
conn.reconnectAttempts = decision.nextAttempts;
|
|
5466
5546
|
if (decision.reason === "short-lived") {
|
|
5467
5547
|
console.error(
|
|
5468
|
-
`${
|
|
5548
|
+
`${TAG14} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
|
|
5469
5549
|
);
|
|
5470
5550
|
} else {
|
|
5471
5551
|
console.error(
|
|
5472
|
-
`${
|
|
5552
|
+
`${TAG14} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
|
|
5473
5553
|
);
|
|
5474
5554
|
}
|
|
5475
5555
|
if (decision.reason === "short-lived" || cycleError) {
|
|
5476
5556
|
const delay = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
5477
5557
|
console.error(
|
|
5478
|
-
`${
|
|
5558
|
+
`${TAG14} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
5479
5559
|
);
|
|
5480
5560
|
await new Promise((resolve37) => {
|
|
5481
5561
|
const timer2 = setTimeout(resolve37, delay);
|
|
@@ -5509,11 +5589,11 @@ function watchForDisconnect(conn) {
|
|
|
5509
5589
|
conn.sock.ev.on("connection.update", (update) => {
|
|
5510
5590
|
if (update.connection === "close") {
|
|
5511
5591
|
if (connections.get(conn.accountId) !== conn) return;
|
|
5512
|
-
console.error(`${
|
|
5592
|
+
console.error(`${TAG14} socket disconnected for account=${conn.accountId}`);
|
|
5513
5593
|
conn.connected = false;
|
|
5514
5594
|
conn.sock = null;
|
|
5515
5595
|
connectWithReconnect(conn).catch((err) => {
|
|
5516
|
-
console.error(`${
|
|
5596
|
+
console.error(`${TAG14} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
|
|
5517
5597
|
});
|
|
5518
5598
|
}
|
|
5519
5599
|
});
|
|
@@ -5539,7 +5619,7 @@ function monitorInbound(conn) {
|
|
|
5539
5619
|
if (!conn.sock || !onInboundMessage) return;
|
|
5540
5620
|
const sock = conn.sock;
|
|
5541
5621
|
const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
|
|
5542
|
-
console.error(`${
|
|
5622
|
+
console.error(`${TAG14} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
|
|
5543
5623
|
conn.debouncer = createInboundDebouncer({
|
|
5544
5624
|
debounceMs,
|
|
5545
5625
|
buildKey: (payload) => {
|
|
@@ -5549,12 +5629,12 @@ function monitorInbound(conn) {
|
|
|
5549
5629
|
onFlush: (entries) => {
|
|
5550
5630
|
if (!onInboundMessage) return;
|
|
5551
5631
|
if (entries.length > 1) {
|
|
5552
|
-
console.error(`${
|
|
5632
|
+
console.error(`${TAG14} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
5553
5633
|
}
|
|
5554
5634
|
onInboundMessage(combineInboundBatch(entries));
|
|
5555
5635
|
},
|
|
5556
5636
|
onError: (err) => {
|
|
5557
|
-
console.error(`${
|
|
5637
|
+
console.error(`${TAG14} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
5558
5638
|
}
|
|
5559
5639
|
});
|
|
5560
5640
|
sock.ev.on("messages.upsert", async (upsert) => {
|
|
@@ -5586,7 +5666,7 @@ function monitorInbound(conn) {
|
|
|
5586
5666
|
}
|
|
5587
5667
|
const entries = messageStore.get(storeKey);
|
|
5588
5668
|
console.error(
|
|
5589
|
-
`${
|
|
5669
|
+
`${TAG14} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.platformAccountId}`
|
|
5590
5670
|
);
|
|
5591
5671
|
recordActivity({
|
|
5592
5672
|
accountId: conn.accountId,
|
|
@@ -5621,9 +5701,25 @@ function monitorInbound(conn) {
|
|
|
5621
5701
|
remoteJid,
|
|
5622
5702
|
conn.lidMapping
|
|
5623
5703
|
);
|
|
5624
|
-
const
|
|
5704
|
+
const alreadyRecorded = fromMe && echoAlreadyRecorded({
|
|
5705
|
+
readConversation,
|
|
5706
|
+
platformAccountId: conn.platformAccountId,
|
|
5707
|
+
canonicalRemoteJid,
|
|
5708
|
+
accountId: conn.accountId,
|
|
5709
|
+
remoteJid,
|
|
5710
|
+
msgKeyId: msg.key.id
|
|
5711
|
+
});
|
|
5712
|
+
const wantsServableMedia = mediaServable && !!conn.sock && (!fromMe || !alreadyRecorded);
|
|
5713
|
+
if (fromMe && mediaServable && !!conn.sock) {
|
|
5714
|
+
console.error(
|
|
5715
|
+
`${TAG14} fromMe-media-echo msgKey=${msg.key.id} decision=${alreadyRecorded ? "skip" : "store"} reason=${alreadyRecorded ? "already-recorded" : "no-prior-record"}`
|
|
5716
|
+
);
|
|
5717
|
+
}
|
|
5625
5718
|
const mediaSeed = `whatsapp-live:${conn.accountId}:${canonicalRemoteJid}:${msg.key.id}`;
|
|
5626
|
-
const maxBytes = (
|
|
5719
|
+
const maxBytes = echoStoreMaxBytes({
|
|
5720
|
+
fromMe,
|
|
5721
|
+
mediaMaxMb: whatsAppConfig.mediaMaxMb
|
|
5722
|
+
});
|
|
5627
5723
|
const basePersist = {
|
|
5628
5724
|
accountId: conn.accountId,
|
|
5629
5725
|
platformAccountId: conn.platformAccountId,
|
|
@@ -5668,7 +5764,7 @@ function monitorInbound(conn) {
|
|
|
5668
5764
|
receiptDeferredToWorker = true;
|
|
5669
5765
|
if (!accepted) {
|
|
5670
5766
|
console.error(
|
|
5671
|
-
`${
|
|
5767
|
+
`${TAG14} append-media overflow account=${conn.platformAccountId} msgKey=${msgKeyId ?? "?"} \u2014 pending cap ${APPEND_MEDIA_MAX_PENDING} reached, text-fallback persist, inline media shed`
|
|
5672
5768
|
);
|
|
5673
5769
|
const persisted = await persistWhatsAppMessage({
|
|
5674
5770
|
...basePersist,
|
|
@@ -5739,14 +5835,16 @@ function monitorInbound(conn) {
|
|
|
5739
5835
|
}));
|
|
5740
5836
|
continue;
|
|
5741
5837
|
}
|
|
5838
|
+
const skippedSenderJid = isGroupJid(remoteJid) ? msg.key.participant ?? remoteJid : remoteJid;
|
|
5839
|
+
const skippedSenderPhone = await resolveJidToE164(skippedSenderJid, conn.lidMapping) ?? skippedSenderJid;
|
|
5742
5840
|
console.error(
|
|
5743
|
-
`${
|
|
5841
|
+
`${TAG14} append-type message ${msg.key.id ?? "?"} dispatch skipped from=${skippedSenderPhone}${msg.pushName ? ` name=${msg.pushName}` : ""} account=${conn.accountId} (persistence not confirmed here)`
|
|
5744
5842
|
);
|
|
5745
5843
|
continue;
|
|
5746
5844
|
}
|
|
5747
5845
|
await handleInboundMessage(conn, msg);
|
|
5748
5846
|
} catch (err) {
|
|
5749
|
-
console.error(`${
|
|
5847
|
+
console.error(`${TAG14} inbound handler error account=${conn.accountId}: ${String(err)}`);
|
|
5750
5848
|
}
|
|
5751
5849
|
}
|
|
5752
5850
|
});
|
|
@@ -5802,31 +5900,31 @@ async function handleInboundMessage(conn, msg) {
|
|
|
5802
5900
|
const remoteJid = msg.key.remoteJid;
|
|
5803
5901
|
if (!remoteJid) return;
|
|
5804
5902
|
if (remoteJid === "status@broadcast") {
|
|
5805
|
-
console.error(`${
|
|
5903
|
+
console.error(`${TAG14} drop: status broadcast account=${conn.accountId}`);
|
|
5806
5904
|
return;
|
|
5807
5905
|
}
|
|
5808
5906
|
if (!msg.message) {
|
|
5809
|
-
console.error(`${
|
|
5907
|
+
console.error(`${TAG14} drop: empty message account=${conn.accountId} from=${remoteJid}`);
|
|
5810
5908
|
return;
|
|
5811
5909
|
}
|
|
5812
5910
|
const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
|
|
5813
5911
|
if (isDuplicateInbound(dedupKey)) {
|
|
5814
|
-
console.error(`${
|
|
5912
|
+
console.error(`${TAG14} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
|
|
5815
5913
|
return;
|
|
5816
5914
|
}
|
|
5817
5915
|
if (msg.key.fromMe) {
|
|
5818
5916
|
if (msg.key.id && isAgentSentMessage(msg.key.id)) {
|
|
5819
|
-
console.error(`${
|
|
5917
|
+
console.error(`${TAG14} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
|
|
5820
5918
|
return;
|
|
5821
5919
|
}
|
|
5822
5920
|
const extracted2 = extractMessage(msg);
|
|
5823
5921
|
if (!extracted2.text) {
|
|
5824
|
-
console.error(`${
|
|
5922
|
+
console.error(`${TAG14} owner reply skipped \u2014 no text content account=${conn.accountId}`);
|
|
5825
5923
|
return;
|
|
5826
5924
|
}
|
|
5827
5925
|
const isGroup2 = isGroupJid(remoteJid);
|
|
5828
5926
|
const senderPhone2 = conn.selfPhone ?? "owner";
|
|
5829
|
-
console.error(`${
|
|
5927
|
+
console.error(`${TAG14} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
|
|
5830
5928
|
const reply2 = async (text) => {
|
|
5831
5929
|
const currentSock = conn.sock;
|
|
5832
5930
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
@@ -5860,7 +5958,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
5860
5958
|
}
|
|
5861
5959
|
const extracted = extractMessage(msg);
|
|
5862
5960
|
if (!extracted.text && !extracted.mediaType) {
|
|
5863
|
-
console.error(`${
|
|
5961
|
+
console.error(`${TAG14} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
5864
5962
|
return;
|
|
5865
5963
|
}
|
|
5866
5964
|
let mediaResult;
|
|
@@ -5870,7 +5968,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
5870
5968
|
maxBytes: maxMb * 1024 * 1024
|
|
5871
5969
|
});
|
|
5872
5970
|
if (!mediaResult) {
|
|
5873
|
-
console.error(`${
|
|
5971
|
+
console.error(`${TAG14} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
5874
5972
|
}
|
|
5875
5973
|
}
|
|
5876
5974
|
const isGroup = isGroupJid(remoteJid);
|
|
@@ -5911,7 +6009,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
5911
6009
|
});
|
|
5912
6010
|
}
|
|
5913
6011
|
console.error(
|
|
5914
|
-
`${
|
|
6012
|
+
`${TAG14} 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}` : "")
|
|
5915
6013
|
);
|
|
5916
6014
|
if (!accessResult.allowed && accessResult.reason === "account-manager-unresolved") {
|
|
5917
6015
|
console.error(
|
|
@@ -5937,7 +6035,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
5937
6035
|
}
|
|
5938
6036
|
const reply = async (text) => {
|
|
5939
6037
|
if (isGroupJid(remoteJid)) {
|
|
5940
|
-
console.error(`${
|
|
6038
|
+
console.error(`${TAG14} op=group-reply-blocked jid=${remoteJid}`);
|
|
5941
6039
|
throw new WaReplyError("group reply blocked \u2014 observe-only", false);
|
|
5942
6040
|
}
|
|
5943
6041
|
const currentSock = conn.sock;
|
|
@@ -5984,15 +6082,15 @@ async function handleInboundMessage(conn, msg) {
|
|
|
5984
6082
|
if (accessResult.agentType === "public") {
|
|
5985
6083
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
5986
6084
|
if (!hoursResult.open) {
|
|
5987
|
-
console.error(`${
|
|
6085
|
+
console.error(`${TAG14} [${conn.accountId}] dispatch skipped: business closed`);
|
|
5988
6086
|
const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
|
|
5989
6087
|
if (afterHoursMessage) {
|
|
5990
6088
|
try {
|
|
5991
6089
|
await reply(afterHoursMessage);
|
|
5992
|
-
console.error(`${
|
|
6090
|
+
console.error(`${TAG14} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
|
|
5993
6091
|
} catch (err) {
|
|
5994
6092
|
console.error(
|
|
5995
|
-
`${
|
|
6093
|
+
`${TAG14} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5996
6094
|
);
|
|
5997
6095
|
}
|
|
5998
6096
|
}
|
|
@@ -6437,13 +6535,13 @@ function audioExtension(mimeType) {
|
|
|
6437
6535
|
}
|
|
6438
6536
|
|
|
6439
6537
|
// app/lib/stt/voice-note.ts
|
|
6440
|
-
var
|
|
6538
|
+
var TAG15 = "[voice]";
|
|
6441
6539
|
async function transcribeVoiceNote(file, source) {
|
|
6442
6540
|
const startMs = Date.now();
|
|
6443
6541
|
const bytes = file.size;
|
|
6444
6542
|
const mimeType = file.type;
|
|
6445
6543
|
console.error(
|
|
6446
|
-
`${
|
|
6544
|
+
`${TAG15} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
|
|
6447
6545
|
);
|
|
6448
6546
|
let tempDir;
|
|
6449
6547
|
let tempPath;
|
|
@@ -6455,7 +6553,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
6455
6553
|
await writeFile3(tempPath, buffer);
|
|
6456
6554
|
} catch (err) {
|
|
6457
6555
|
const reason = err instanceof Error ? err.message : String(err);
|
|
6458
|
-
console.error(`${
|
|
6556
|
+
console.error(`${TAG15} failed source=${source} error=temp-file-write: ${reason}`);
|
|
6459
6557
|
return { ok: false, error: "Could not process voice note" };
|
|
6460
6558
|
}
|
|
6461
6559
|
try {
|
|
@@ -6463,7 +6561,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
6463
6561
|
if (!sttResult) {
|
|
6464
6562
|
const elapsed2 = Date.now() - startMs;
|
|
6465
6563
|
console.error(
|
|
6466
|
-
`${
|
|
6564
|
+
`${TAG15} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
|
|
6467
6565
|
);
|
|
6468
6566
|
return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
|
|
6469
6567
|
}
|
|
@@ -6471,7 +6569,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
6471
6569
|
const elapsed = Date.now() - startMs;
|
|
6472
6570
|
const words = rawText.split(/\s+/).filter(Boolean).length;
|
|
6473
6571
|
console.error(
|
|
6474
|
-
`${
|
|
6572
|
+
`${TAG15} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
|
|
6475
6573
|
);
|
|
6476
6574
|
return {
|
|
6477
6575
|
ok: true,
|
|
@@ -6481,7 +6579,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
6481
6579
|
const elapsed = Date.now() - startMs;
|
|
6482
6580
|
const reason = err instanceof Error ? err.message : String(err);
|
|
6483
6581
|
console.error(
|
|
6484
|
-
`${
|
|
6582
|
+
`${TAG15} failed source=${source} error=${reason} duration_ms=${elapsed}`
|
|
6485
6583
|
);
|
|
6486
6584
|
return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
|
|
6487
6585
|
} finally {
|
|
@@ -6528,7 +6626,7 @@ function composePublicSessionTitle(input) {
|
|
|
6528
6626
|
}
|
|
6529
6627
|
|
|
6530
6628
|
// app/lib/webchat/persist-customer-inbound.ts
|
|
6531
|
-
var
|
|
6629
|
+
var TAG16 = "[channel-house]";
|
|
6532
6630
|
async function persistCustomerWebchatInbound(input) {
|
|
6533
6631
|
if (!input.text) return null;
|
|
6534
6632
|
try {
|
|
@@ -6544,7 +6642,7 @@ async function persistCustomerWebchatInbound(input) {
|
|
|
6544
6642
|
);
|
|
6545
6643
|
if (!conv.sessionId) {
|
|
6546
6644
|
console.error(
|
|
6547
|
-
`${
|
|
6645
|
+
`${TAG16} op=persist-skip reason=conversation-merge-failed accountId=${input.houseAccountId} key=${input.sessionKey}`
|
|
6548
6646
|
);
|
|
6549
6647
|
return null;
|
|
6550
6648
|
}
|
|
@@ -6556,17 +6654,17 @@ async function persistCustomerWebchatInbound(input) {
|
|
|
6556
6654
|
);
|
|
6557
6655
|
if (!messageId) {
|
|
6558
6656
|
console.error(
|
|
6559
|
-
`${
|
|
6657
|
+
`${TAG16} op=persist-skip reason=message-write-failed accountId=${input.houseAccountId} sessionId=${conv.sessionId.slice(0, 8)}`
|
|
6560
6658
|
);
|
|
6561
6659
|
return null;
|
|
6562
6660
|
}
|
|
6563
6661
|
console.error(
|
|
6564
|
-
`${
|
|
6662
|
+
`${TAG16} op=persist accountId=${input.houseAccountId} sessionId=${conv.sessionId.slice(0, 8)} messageId=${messageId.slice(0, 8)} bytes=${Buffer.byteLength(input.text, "utf8")}`
|
|
6565
6663
|
);
|
|
6566
6664
|
return { sessionId: conv.sessionId, messageId };
|
|
6567
6665
|
} catch (err) {
|
|
6568
6666
|
console.error(
|
|
6569
|
-
`${
|
|
6667
|
+
`${TAG16} op=persist-fail accountId=${input.houseAccountId} key=${input.sessionKey} reason=${err instanceof Error ? err.message : String(err)}`
|
|
6570
6668
|
);
|
|
6571
6669
|
return null;
|
|
6572
6670
|
}
|
|
@@ -6874,7 +6972,7 @@ function parseScheduleProvenance(v) {
|
|
|
6874
6972
|
}
|
|
6875
6973
|
|
|
6876
6974
|
// server/routes/channel/schedule-inject.ts
|
|
6877
|
-
var
|
|
6975
|
+
var TAG17 = "[schedule-inject]";
|
|
6878
6976
|
function isLoopbackAddr(addr) {
|
|
6879
6977
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
6880
6978
|
}
|
|
@@ -6884,7 +6982,7 @@ function createScheduleInjectRoutes(deps) {
|
|
|
6884
6982
|
const env = c.env;
|
|
6885
6983
|
const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
|
|
6886
6984
|
if (!isLoopbackAddr(remoteAddr)) {
|
|
6887
|
-
console.error(`${
|
|
6985
|
+
console.error(`${TAG17} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
6888
6986
|
return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
|
|
6889
6987
|
}
|
|
6890
6988
|
const xffHeader = env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -6892,7 +6990,7 @@ function createScheduleInjectRoutes(deps) {
|
|
|
6892
6990
|
if (xffRaw.length > 0) {
|
|
6893
6991
|
const offender = xffRaw.split(",").map((t) => t.trim()).filter(Boolean).find((t) => !isLoopbackAddr(t));
|
|
6894
6992
|
if (offender !== void 0) {
|
|
6895
|
-
console.error(`${
|
|
6993
|
+
console.error(`${TAG17} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
6896
6994
|
return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
|
|
6897
6995
|
}
|
|
6898
6996
|
}
|
|
@@ -6908,23 +7006,23 @@ function createScheduleInjectRoutes(deps) {
|
|
|
6908
7006
|
const eventId = typeof body.eventId === "string" ? body.eventId : "";
|
|
6909
7007
|
const scheduleProvenance = parseScheduleProvenance(body.scheduleProvenance);
|
|
6910
7008
|
if (channel !== "whatsapp" && channel !== "telegram" || !destination || !prompt) {
|
|
6911
|
-
console.error(`${
|
|
7009
|
+
console.error(`${TAG17} reject reason=bad-request channel=${String(channel)} hasDestination=${Boolean(destination)} hasPrompt=${Boolean(prompt)}`);
|
|
6912
7010
|
return c.json({ ok: false, error: "schedule-inject-bad-request" }, 400);
|
|
6913
7011
|
}
|
|
6914
7012
|
const account = deps.resolveAccount();
|
|
6915
7013
|
if (!account) {
|
|
6916
|
-
console.error(`${
|
|
7014
|
+
console.error(`${TAG17} reject reason=no-account eventId=${eventId}`);
|
|
6917
7015
|
return c.json({ ok: false, error: "no-account" }, 500);
|
|
6918
7016
|
}
|
|
6919
7017
|
const houseAccountId = account.accountId;
|
|
6920
|
-
console.error(`${
|
|
7018
|
+
console.error(`${TAG17} op=received eventId=${eventId} channel=${channel} destination=${destination} account=${houseAccountId}`);
|
|
6921
7019
|
if (channel === "whatsapp") {
|
|
6922
7020
|
const effectiveAccount2 = deps.effectiveAccountFor(houseAccountId, account.accountDir, destination);
|
|
6923
7021
|
if (effectiveAccount2 === null) {
|
|
6924
|
-
console.error(`${
|
|
7022
|
+
console.error(`${TAG17} op=schedule-account-manager-reject eventId=${eventId} destination=${destination} reason=unresolved-effective-account`);
|
|
6925
7023
|
return c.json({ ok: false, error: "account-manager-unresolved" }, 403);
|
|
6926
7024
|
}
|
|
6927
|
-
console.error(`${
|
|
7025
|
+
console.error(`${TAG17} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount2}`);
|
|
6928
7026
|
const sessionId2 = adminSessionIdFor(effectiveAccount2, normalizeTarget(destination) ?? destination);
|
|
6929
7027
|
const reply2 = (text) => deps.sendWhatsAppText(houseAccountId, destination, text);
|
|
6930
7028
|
try {
|
|
@@ -6946,17 +7044,17 @@ function createScheduleInjectRoutes(deps) {
|
|
|
6946
7044
|
reply: reply2
|
|
6947
7045
|
});
|
|
6948
7046
|
} catch (err) {
|
|
6949
|
-
console.error(`${
|
|
7047
|
+
console.error(`${TAG17} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
|
|
6950
7048
|
return c.json({ ok: false, error: "inject-error" }, 502);
|
|
6951
7049
|
}
|
|
6952
|
-
console.error(`${
|
|
7050
|
+
console.error(`${TAG17} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=ok status=-`);
|
|
6953
7051
|
return c.json({ ok: true }, 200);
|
|
6954
7052
|
}
|
|
6955
7053
|
const effectiveAccount = houseAccountId;
|
|
6956
|
-
console.error(`${
|
|
7054
|
+
console.error(`${TAG17} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount}`);
|
|
6957
7055
|
const botToken = account.config.telegram?.adminBotToken;
|
|
6958
7056
|
if (!botToken) {
|
|
6959
|
-
console.error(`${
|
|
7057
|
+
console.error(`${TAG17} reject reason=no-admin-bot-token eventId=${eventId}`);
|
|
6960
7058
|
return c.json({ ok: false, error: "no-admin-bot-token" }, 400);
|
|
6961
7059
|
}
|
|
6962
7060
|
const chatId = Number(destination);
|
|
@@ -6975,17 +7073,17 @@ function createScheduleInjectRoutes(deps) {
|
|
|
6975
7073
|
reply
|
|
6976
7074
|
});
|
|
6977
7075
|
} catch (err) {
|
|
6978
|
-
console.error(`${
|
|
7076
|
+
console.error(`${TAG17} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
|
|
6979
7077
|
return c.json({ ok: false, error: "inject-error" }, 502);
|
|
6980
7078
|
}
|
|
6981
|
-
console.error(`${
|
|
7079
|
+
console.error(`${TAG17} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
|
|
6982
7080
|
return c.json({ ok: true }, 200);
|
|
6983
7081
|
});
|
|
6984
7082
|
return app63;
|
|
6985
7083
|
}
|
|
6986
7084
|
|
|
6987
7085
|
// server/index.ts
|
|
6988
|
-
var
|
|
7086
|
+
var import_dist9 = __toESM(require_dist(), 1);
|
|
6989
7087
|
|
|
6990
7088
|
// app/lib/telegram/outbound/send-text.ts
|
|
6991
7089
|
async function sendTelegramText(botToken, chatId, text) {
|
|
@@ -7012,26 +7110,26 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
7012
7110
|
// app/lib/whatsapp/config-persist.ts
|
|
7013
7111
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
|
|
7014
7112
|
import { resolve as resolve8, join as join8, dirname as dirname2, basename as basename2 } from "path";
|
|
7015
|
-
var
|
|
7113
|
+
var TAG18 = "[whatsapp:config]";
|
|
7016
7114
|
function configPath(accountDir) {
|
|
7017
7115
|
return resolve8(accountDir, "account.json");
|
|
7018
7116
|
}
|
|
7019
7117
|
function readConfig(accountDir) {
|
|
7020
|
-
const
|
|
7021
|
-
if (!existsSync6(
|
|
7022
|
-
return JSON.parse(readFileSync11(
|
|
7118
|
+
const path3 = configPath(accountDir);
|
|
7119
|
+
if (!existsSync6(path3)) throw new Error(`account.json not found at ${path3}`);
|
|
7120
|
+
return JSON.parse(readFileSync11(path3, "utf-8"));
|
|
7023
7121
|
}
|
|
7024
7122
|
function writeConfig(accountDir, config) {
|
|
7025
|
-
const
|
|
7026
|
-
writeFileSync4(
|
|
7123
|
+
const path3 = configPath(accountDir);
|
|
7124
|
+
writeFileSync4(path3, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
7027
7125
|
}
|
|
7028
7126
|
function reloadManagerConfig(accountDir) {
|
|
7029
7127
|
try {
|
|
7030
7128
|
const config = readConfig(accountDir);
|
|
7031
7129
|
reloadConfig(config);
|
|
7032
|
-
console.error(`${
|
|
7130
|
+
console.error(`${TAG18} reloaded manager config`);
|
|
7033
7131
|
} catch (err) {
|
|
7034
|
-
console.error(`${
|
|
7132
|
+
console.error(`${TAG18} manager config reload failed: ${String(err)}`);
|
|
7035
7133
|
}
|
|
7036
7134
|
}
|
|
7037
7135
|
var E164_PATTERN = /^\+\d{7,15}$/;
|
|
@@ -7057,25 +7155,25 @@ function persistAfterPairing(accountDir, accountId, selfPhone) {
|
|
|
7057
7155
|
const adminPhones = wa.adminPhones;
|
|
7058
7156
|
if (!adminPhones.includes(normalized)) {
|
|
7059
7157
|
adminPhones.push(normalized);
|
|
7060
|
-
console.error(`${
|
|
7158
|
+
console.error(`${TAG18} added selfPhone=${normalized} to adminPhones`);
|
|
7061
7159
|
}
|
|
7062
7160
|
} else {
|
|
7063
|
-
console.error(`${
|
|
7161
|
+
console.error(`${TAG18} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
|
|
7064
7162
|
}
|
|
7065
7163
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
7066
7164
|
if (!parsed.success) {
|
|
7067
7165
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
7068
|
-
console.error(`${
|
|
7166
|
+
console.error(`${TAG18} validation failed after pairing: ${msg}`);
|
|
7069
7167
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
7070
7168
|
}
|
|
7071
7169
|
config.whatsapp = parsed.data;
|
|
7072
7170
|
writeConfig(accountDir, config);
|
|
7073
|
-
console.error(`${
|
|
7171
|
+
console.error(`${TAG18} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
|
|
7074
7172
|
reloadManagerConfig(accountDir);
|
|
7075
7173
|
return { ok: true };
|
|
7076
7174
|
} catch (err) {
|
|
7077
7175
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7078
|
-
console.error(`${
|
|
7176
|
+
console.error(`${TAG18} persist failed account=${accountId}: ${msg}`);
|
|
7079
7177
|
return { ok: false, error: msg };
|
|
7080
7178
|
}
|
|
7081
7179
|
}
|
|
@@ -7089,7 +7187,7 @@ function addAdminPhone(accountDir, phone) {
|
|
|
7089
7187
|
const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
|
|
7090
7188
|
const socketOwner = resolveHouseOrSoleAccountId(dirname2(resolve8(accountDir)));
|
|
7091
7189
|
if (targetId !== socketOwner) {
|
|
7092
|
-
console.error(`${
|
|
7190
|
+
console.error(`${TAG18} add-admin-phone rejected accountId=${targetId} reason=not-socket-account`);
|
|
7093
7191
|
return {
|
|
7094
7192
|
ok: false,
|
|
7095
7193
|
reason: "not-socket-account",
|
|
@@ -7118,12 +7216,12 @@ function addAdminPhone(accountDir, phone) {
|
|
|
7118
7216
|
}
|
|
7119
7217
|
config.whatsapp = parsed.data;
|
|
7120
7218
|
writeConfig(accountDir, config);
|
|
7121
|
-
console.error(`${
|
|
7219
|
+
console.error(`${TAG18} added admin phone=${normalized}`);
|
|
7122
7220
|
reloadManagerConfig(accountDir);
|
|
7123
7221
|
return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
|
|
7124
7222
|
} catch (err) {
|
|
7125
7223
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7126
|
-
console.error(`${
|
|
7224
|
+
console.error(`${TAG18} addAdminPhone failed: ${msg}`);
|
|
7127
7225
|
return { ok: false, error: msg };
|
|
7128
7226
|
}
|
|
7129
7227
|
}
|
|
@@ -7151,12 +7249,12 @@ function removeAdminPhone(accountDir, phone) {
|
|
|
7151
7249
|
}
|
|
7152
7250
|
config.whatsapp = parsed.data;
|
|
7153
7251
|
writeConfig(accountDir, config);
|
|
7154
|
-
console.error(`${
|
|
7252
|
+
console.error(`${TAG18} removed admin phone=${normalized}`);
|
|
7155
7253
|
reloadManagerConfig(accountDir);
|
|
7156
7254
|
return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
|
|
7157
7255
|
} catch (err) {
|
|
7158
7256
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7159
|
-
console.error(`${
|
|
7257
|
+
console.error(`${TAG18} removeAdminPhone failed: ${msg}`);
|
|
7160
7258
|
return { ok: false, error: msg };
|
|
7161
7259
|
}
|
|
7162
7260
|
}
|
|
@@ -7251,13 +7349,13 @@ function setAccountManager(accountDir, phone, subAccountId, mode = "active") {
|
|
|
7251
7349
|
}
|
|
7252
7350
|
config.whatsapp = parsed.data;
|
|
7253
7351
|
writeConfig(accountDir, config);
|
|
7254
|
-
console.error(`${
|
|
7352
|
+
console.error(`${TAG18} bound account manager phone=${normalized} managesAccount=${sub} mode=${mode}`);
|
|
7255
7353
|
reloadManagerConfig(accountDir);
|
|
7256
7354
|
const behaviour = mode === "passive" ? `Their WhatsApp messages will each file one task scoped to that sub-account; the agent sends nothing back.` : `Their WhatsApp messages will reach that sub-account's admin agent; replies still go out over this account's WhatsApp device.`;
|
|
7257
7355
|
return { ok: true, message: `Bound ${normalized} as ${mode} account manager for sub-account ${sub}. ${behaviour}` };
|
|
7258
7356
|
} catch (err) {
|
|
7259
7357
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7260
|
-
console.error(`${
|
|
7358
|
+
console.error(`${TAG18} setAccountManager failed: ${msg}`);
|
|
7261
7359
|
return { ok: false, error: msg };
|
|
7262
7360
|
}
|
|
7263
7361
|
}
|
|
@@ -7282,12 +7380,12 @@ function clearAccountManager(accountDir, phone) {
|
|
|
7282
7380
|
}
|
|
7283
7381
|
config.whatsapp = parsed.data;
|
|
7284
7382
|
writeConfig(accountDir, config);
|
|
7285
|
-
console.error(`${
|
|
7383
|
+
console.error(`${TAG18} cleared account manager phone=${normalized}`);
|
|
7286
7384
|
reloadManagerConfig(accountDir);
|
|
7287
7385
|
return { ok: true, message: `Cleared the account-manager binding for ${normalized}.` };
|
|
7288
7386
|
} catch (err) {
|
|
7289
7387
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7290
|
-
console.error(`${
|
|
7388
|
+
console.error(`${TAG18} clearAccountManager failed: ${msg}`);
|
|
7291
7389
|
return { ok: false, error: msg };
|
|
7292
7390
|
}
|
|
7293
7391
|
}
|
|
@@ -7334,12 +7432,12 @@ function setPublicAgent(accountDir, slug) {
|
|
|
7334
7432
|
}
|
|
7335
7433
|
config.whatsapp = parsed.data;
|
|
7336
7434
|
writeConfig(accountDir, config);
|
|
7337
|
-
console.error(`${
|
|
7435
|
+
console.error(`${TAG18} publicAgent set to ${trimmed}`);
|
|
7338
7436
|
reloadManagerConfig(accountDir);
|
|
7339
7437
|
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
7340
7438
|
} catch (err) {
|
|
7341
7439
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7342
|
-
console.error(`${
|
|
7440
|
+
console.error(`${TAG18} setPublicAgent failed: ${msg}`);
|
|
7343
7441
|
return { ok: false, error: msg };
|
|
7344
7442
|
}
|
|
7345
7443
|
}
|
|
@@ -7380,17 +7478,17 @@ function updateConfig(accountDir, fields) {
|
|
|
7380
7478
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
7381
7479
|
if (!parsed.success) {
|
|
7382
7480
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
7383
|
-
console.error(`${
|
|
7481
|
+
console.error(`${TAG18} update validation failed: ${msg}`);
|
|
7384
7482
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
7385
7483
|
}
|
|
7386
7484
|
config.whatsapp = parsed.data;
|
|
7387
7485
|
writeConfig(accountDir, config);
|
|
7388
|
-
console.error(`${
|
|
7486
|
+
console.error(`${TAG18} updated fields=[${fieldNames.join(",")}]`);
|
|
7389
7487
|
reloadManagerConfig(accountDir);
|
|
7390
7488
|
return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
|
|
7391
7489
|
} catch (err) {
|
|
7392
7490
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7393
|
-
console.error(`${
|
|
7491
|
+
console.error(`${TAG18} updateConfig failed: ${msg}`);
|
|
7394
7492
|
return { ok: false, error: msg };
|
|
7395
7493
|
}
|
|
7396
7494
|
}
|
|
@@ -7427,17 +7525,17 @@ function migrateRemovedConfigKeys(accountDir) {
|
|
|
7427
7525
|
}
|
|
7428
7526
|
writeConfig(accountDir, config);
|
|
7429
7527
|
console.error(
|
|
7430
|
-
`${
|
|
7528
|
+
`${TAG18} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
|
|
7431
7529
|
);
|
|
7432
7530
|
return { dropped: result.droppedKeys };
|
|
7433
7531
|
} catch (err) {
|
|
7434
|
-
console.error(`${
|
|
7532
|
+
console.error(`${TAG18} migration failed: ${String(err)}`);
|
|
7435
7533
|
return { dropped: [] };
|
|
7436
7534
|
}
|
|
7437
7535
|
}
|
|
7438
7536
|
|
|
7439
7537
|
// app/lib/whatsapp/login.ts
|
|
7440
|
-
var
|
|
7538
|
+
var TAG19 = "[whatsapp:login]";
|
|
7441
7539
|
function maskPhone(digits) {
|
|
7442
7540
|
if (digits.length <= 4) return "*".repeat(digits.length);
|
|
7443
7541
|
return digits.slice(0, 2) + "*".repeat(digits.length - 4) + digits.slice(-2);
|
|
@@ -7448,7 +7546,7 @@ function closeSocket(sock) {
|
|
|
7448
7546
|
try {
|
|
7449
7547
|
sock.ws?.close?.();
|
|
7450
7548
|
} catch (err) {
|
|
7451
|
-
console.warn(`${
|
|
7549
|
+
console.warn(`${TAG19} socket close error during cleanup: ${String(err)}`);
|
|
7452
7550
|
}
|
|
7453
7551
|
}
|
|
7454
7552
|
function resetActiveLogin(accountId) {
|
|
@@ -7475,18 +7573,18 @@ async function runPostPairing(login) {
|
|
|
7475
7573
|
const masked = selfPhone ? `${selfPhone.slice(0, 4)}***` : "null";
|
|
7476
7574
|
console.error(`[whatsapp-persist] wa-persist-on-connect account=${login.accountId} phone=${masked}`);
|
|
7477
7575
|
} else {
|
|
7478
|
-
console.error(`${
|
|
7576
|
+
console.error(`${TAG19} wa-persist-on-connect FAILED account=${login.accountId}: ${result.error}`);
|
|
7479
7577
|
}
|
|
7480
7578
|
} catch (err) {
|
|
7481
|
-
console.error(`${
|
|
7579
|
+
console.error(`${TAG19} wa-persist-on-connect error account=${login.accountId}: ${String(err)}`);
|
|
7482
7580
|
}
|
|
7483
7581
|
} else {
|
|
7484
|
-
console.error(`${
|
|
7582
|
+
console.error(`${TAG19} wa-persist-on-connect skipped \u2014 no accountDir account=${login.accountId}`);
|
|
7485
7583
|
}
|
|
7486
7584
|
try {
|
|
7487
7585
|
await registerLoginSocket(login.accountId, login.sock, login.authDir);
|
|
7488
7586
|
} catch (err) {
|
|
7489
|
-
console.error(`${
|
|
7587
|
+
console.error(`${TAG19} registerLoginSocket failed account=${login.accountId}: ${String(err)}`);
|
|
7490
7588
|
}
|
|
7491
7589
|
}
|
|
7492
7590
|
async function loginConnectionLoop(accountId, login) {
|
|
@@ -7495,7 +7593,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7495
7593
|
const logTerminal = (outcome, errMsg) => {
|
|
7496
7594
|
const fc = credsCensus(login.authDir);
|
|
7497
7595
|
console.error(
|
|
7498
|
-
`${
|
|
7596
|
+
`${TAG19} op=terminal cid=${cidShort} outcome=${outcome} credsFinal={registered=${fc.registered},account=${fc.hasAccountSignature},me=${fc.e164 ?? "null"}}` + (errMsg ? ` error="${errMsg}"` : "")
|
|
7499
7597
|
);
|
|
7500
7598
|
};
|
|
7501
7599
|
while (true) {
|
|
@@ -7505,7 +7603,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7505
7603
|
if (current?.id === login.id) {
|
|
7506
7604
|
await runPostPairing(current);
|
|
7507
7605
|
current.connected = true;
|
|
7508
|
-
console.error(`${
|
|
7606
|
+
console.error(`${TAG19} loginConnectionLoop: connected account=${accountId} attempt=${attempt} configPersisted=${current.configPersisted}`);
|
|
7509
7607
|
logTerminal("connected");
|
|
7510
7608
|
}
|
|
7511
7609
|
return;
|
|
@@ -7517,7 +7615,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7517
7615
|
if (!preRetryCensus.hasAccountSignature) {
|
|
7518
7616
|
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.";
|
|
7519
7617
|
console.error(
|
|
7520
|
-
`${
|
|
7618
|
+
`${TAG19} op=pairing-incomplete cid=${cidShort} closeKind=${closeKind(getStatusCode(err), false)} reason=no-account-signature`
|
|
7521
7619
|
);
|
|
7522
7620
|
return;
|
|
7523
7621
|
}
|
|
@@ -7525,7 +7623,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7525
7623
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
7526
7624
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
7527
7625
|
console.error(
|
|
7528
|
-
`${
|
|
7626
|
+
`${TAG19} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
7529
7627
|
);
|
|
7530
7628
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
7531
7629
|
} else {
|
|
@@ -7538,7 +7636,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7538
7636
|
attempt++;
|
|
7539
7637
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
7540
7638
|
console.error(
|
|
7541
|
-
`${
|
|
7639
|
+
`${TAG19} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
7542
7640
|
);
|
|
7543
7641
|
closeSocket(current.sock);
|
|
7544
7642
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -7546,14 +7644,14 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7546
7644
|
if (afterDelay?.id !== login.id) return;
|
|
7547
7645
|
const rc = credsCensus(login.authDir);
|
|
7548
7646
|
console.error(
|
|
7549
|
-
`${
|
|
7647
|
+
`${TAG19} op=reconnect cid=${cidShort} attempt=${attempt}/${LOGIN_MAX_RECONNECTS} withRegistered=${rc.registered} withAccount=${rc.hasAccountSignature}`
|
|
7550
7648
|
);
|
|
7551
7649
|
try {
|
|
7552
7650
|
const newSock = await createWaSocket({ authDir: login.authDir, correlationId: cidShort, account: accountId });
|
|
7553
7651
|
current.sock = newSock;
|
|
7554
7652
|
} catch (sockErr) {
|
|
7555
7653
|
console.error(
|
|
7556
|
-
`${
|
|
7654
|
+
`${TAG19} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
7557
7655
|
);
|
|
7558
7656
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
7559
7657
|
logTerminal("error", current.error);
|
|
@@ -7570,7 +7668,7 @@ async function startLogin(opts) {
|
|
|
7570
7668
|
const hasAuth = await authExists(authDir);
|
|
7571
7669
|
const selfId = readSelfId(authDir);
|
|
7572
7670
|
console.error(
|
|
7573
|
-
`${
|
|
7671
|
+
`${TAG19} 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")
|
|
7574
7672
|
);
|
|
7575
7673
|
if (hasAuth && !force) {
|
|
7576
7674
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -7585,7 +7683,7 @@ async function startLogin(opts) {
|
|
|
7585
7683
|
}
|
|
7586
7684
|
const existing = activeLogins.get(accountId);
|
|
7587
7685
|
if (existing && isLoginFresh(existing) && existing.pairingCode && !force) {
|
|
7588
|
-
console.error(`${
|
|
7686
|
+
console.error(`${TAG19} startLogin account=${accountId} guard: returning existing pairing code (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
7589
7687
|
return {
|
|
7590
7688
|
pairingCode: existing.pairingCode,
|
|
7591
7689
|
phone: existing.phone,
|
|
@@ -7593,7 +7691,7 @@ async function startLogin(opts) {
|
|
|
7593
7691
|
};
|
|
7594
7692
|
}
|
|
7595
7693
|
if (existing) {
|
|
7596
|
-
console.error(`${
|
|
7694
|
+
console.error(`${TAG19} startLogin account=${accountId} ${force ? "force override" : "stale/no-code"}, resetting active login`);
|
|
7597
7695
|
}
|
|
7598
7696
|
resetActiveLogin(accountId);
|
|
7599
7697
|
await clearAuth(authDir);
|
|
@@ -7624,7 +7722,7 @@ async function startLogin(opts) {
|
|
|
7624
7722
|
if (requested) return;
|
|
7625
7723
|
requested = true;
|
|
7626
7724
|
console.error(
|
|
7627
|
-
`${
|
|
7725
|
+
`${TAG19} op=pairing-request cid=${cid} account=${accountId} qrRef=#1 sinceOpenMs=${socketOpenTs ? Date.now() - socketOpenTs : 0}`
|
|
7628
7726
|
);
|
|
7629
7727
|
void sock.requestPairingCode(digits).then((code) => {
|
|
7630
7728
|
clearTimeout(codeTimer);
|
|
@@ -7633,7 +7731,7 @@ async function startLogin(opts) {
|
|
|
7633
7731
|
if (current?.id !== login.id) return;
|
|
7634
7732
|
if (!current.pairingCode) current.pairingCode = code;
|
|
7635
7733
|
console.error(
|
|
7636
|
-
`${
|
|
7734
|
+
`${TAG19} op=code-issued cid=${cid} account=${accountId} phone=${maskPhone(digits)} codeLen=${code.length} sinceOpenMs=${socketOpenTs ? codeIssuedTs - socketOpenTs : 0}`
|
|
7637
7735
|
);
|
|
7638
7736
|
resolveCode?.(code);
|
|
7639
7737
|
}).catch((err) => {
|
|
@@ -7664,7 +7762,7 @@ async function startLogin(opts) {
|
|
|
7664
7762
|
const ttlTimer = setTimeout(() => {
|
|
7665
7763
|
const current = activeLogins.get(accountId);
|
|
7666
7764
|
if (current?.id === login.id && !current.connected) {
|
|
7667
|
-
console.error(`${
|
|
7765
|
+
console.error(`${TAG19} pairing-window-elapsed account=${accountId} (ttl sweep)`);
|
|
7668
7766
|
resetActiveLogin(accountId);
|
|
7669
7767
|
}
|
|
7670
7768
|
}, ACTIVE_LOGIN_TTL_MS);
|
|
@@ -7672,7 +7770,7 @@ async function startLogin(opts) {
|
|
|
7672
7770
|
ttlTimer.unref();
|
|
7673
7771
|
}
|
|
7674
7772
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
7675
|
-
console.error(`${
|
|
7773
|
+
console.error(`${TAG19} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
7676
7774
|
const current = activeLogins.get(accountId);
|
|
7677
7775
|
if (current?.id === login.id) {
|
|
7678
7776
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -7697,13 +7795,13 @@ async function waitForLogin(opts) {
|
|
|
7697
7795
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
7698
7796
|
const login = activeLogins.get(accountId);
|
|
7699
7797
|
console.error(
|
|
7700
|
-
`${
|
|
7798
|
+
`${TAG19} 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")
|
|
7701
7799
|
);
|
|
7702
7800
|
if (!login) {
|
|
7703
7801
|
return { connected: false, message: "No active WhatsApp login in progress.", configPersisted: false };
|
|
7704
7802
|
}
|
|
7705
7803
|
if (!isLoginFresh(login)) {
|
|
7706
|
-
console.error(`${
|
|
7804
|
+
console.error(`${TAG19} pairing-window-elapsed account=${accountId}`);
|
|
7707
7805
|
resetActiveLogin(accountId);
|
|
7708
7806
|
return { connected: false, message: "The pairing window expired. Ask me to generate a new code.", configPersisted: false };
|
|
7709
7807
|
}
|
|
@@ -7711,8 +7809,8 @@ async function waitForLogin(opts) {
|
|
|
7711
7809
|
while (Date.now() < deadline) {
|
|
7712
7810
|
if (login.connected) {
|
|
7713
7811
|
const selfId = readSelfId(login.authDir);
|
|
7714
|
-
console.error(`${
|
|
7715
|
-
console.error(`${
|
|
7812
|
+
console.error(`${TAG19} pairing-connected account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
7813
|
+
console.error(`${TAG19} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"} configPersisted=${login.configPersisted}`);
|
|
7716
7814
|
const configPersisted = login.configPersisted;
|
|
7717
7815
|
activeLogins.delete(accountId);
|
|
7718
7816
|
return {
|
|
@@ -7730,7 +7828,7 @@ async function waitForLogin(opts) {
|
|
|
7730
7828
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
7731
7829
|
}
|
|
7732
7830
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
7733
|
-
console.error(`${
|
|
7831
|
+
console.error(`${TAG19} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
|
|
7734
7832
|
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 };
|
|
7735
7833
|
}
|
|
7736
7834
|
function listActiveLoginAccountIds() {
|
|
@@ -7752,8 +7850,7 @@ function reconcileMessageStoreAccountKeys(input) {
|
|
|
7752
7850
|
import { realpathSync as realpathSync3 } from "fs";
|
|
7753
7851
|
import { readFile, stat as stat2 } from "fs/promises";
|
|
7754
7852
|
import { resolve as resolve9, basename as basename3 } from "path";
|
|
7755
|
-
var
|
|
7756
|
-
var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
|
|
7853
|
+
var TAG20 = "[whatsapp:outbound]";
|
|
7757
7854
|
var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
7758
7855
|
var lastRouteDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
7759
7856
|
function normalizeJid2(to) {
|
|
@@ -7786,16 +7883,16 @@ async function sendWhatsAppDocument(input) {
|
|
|
7786
7883
|
const accountResolved = realpathSync3(accountDir);
|
|
7787
7884
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
7788
7885
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
7789
|
-
console.error(`${
|
|
7886
|
+
console.error(`${TAG20} document REJECTED path=${sanitised} reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
|
|
7790
7887
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
7791
7888
|
}
|
|
7792
7889
|
} catch (err) {
|
|
7793
7890
|
const code = err.code;
|
|
7794
7891
|
if (code === "ENOENT") {
|
|
7795
|
-
console.error(`${
|
|
7892
|
+
console.error(`${TAG20} document ENOENT path=${filePath}`);
|
|
7796
7893
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
7797
7894
|
}
|
|
7798
|
-
console.error(`${
|
|
7895
|
+
console.error(`${TAG20} document path error: ${String(err)}`);
|
|
7799
7896
|
return { ok: false, status: 500, error: String(err) };
|
|
7800
7897
|
}
|
|
7801
7898
|
const fileStat = await stat2(resolvedPath);
|
|
@@ -7808,21 +7905,30 @@ async function sendWhatsAppDocument(input) {
|
|
|
7808
7905
|
}
|
|
7809
7906
|
const filename = basename3(resolvedPath);
|
|
7810
7907
|
const jid = normalizeJid2(to);
|
|
7811
|
-
const
|
|
7812
|
-
if (!
|
|
7813
|
-
|
|
7814
|
-
|
|
7908
|
+
const res = resolveSocket(accountId);
|
|
7909
|
+
if (!res.ok) {
|
|
7910
|
+
const reason = res.reason === "key-miss" ? `key-miss presentKeys=${res.presentKeys.join(",") || "none"}` : "disconnected";
|
|
7911
|
+
console.error(`${TAG20} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=false reason=${reason}`);
|
|
7912
|
+
if (res.reason === "key-miss") {
|
|
7913
|
+
return {
|
|
7914
|
+
ok: false,
|
|
7915
|
+
status: 503,
|
|
7916
|
+
error: `WhatsApp socket key miss: no socket is registered for accountId=${accountId} (present keys: ${res.presentKeys.join(",") || "none"}). This is an account-keying fault, not a dropped link \u2014 do not advise re-pairing.`
|
|
7917
|
+
};
|
|
7918
|
+
}
|
|
7919
|
+
return { ok: false, status: 503, error: `WhatsApp account "${accountId}" is registered but its transport is down.` };
|
|
7815
7920
|
}
|
|
7921
|
+
const sock = res.sock;
|
|
7816
7922
|
const buffer = Buffer.from(await readFile(resolvedPath));
|
|
7817
7923
|
const mimetype = detectMimeType(resolvedPath);
|
|
7818
7924
|
const result = await sendMediaMessage(
|
|
7819
7925
|
sock,
|
|
7820
7926
|
to,
|
|
7821
7927
|
{ type: "document", buffer, mimetype, filename, caption },
|
|
7822
|
-
{ accountId }
|
|
7928
|
+
{ accountId: res.accountId }
|
|
7823
7929
|
);
|
|
7824
7930
|
console.error(
|
|
7825
|
-
`${
|
|
7931
|
+
`${TAG20} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
7826
7932
|
);
|
|
7827
7933
|
if (result.success) {
|
|
7828
7934
|
recordDocumentOutbound(to);
|
|
@@ -7942,7 +8048,7 @@ function serializeWhatsAppSchema() {
|
|
|
7942
8048
|
// app/lib/whatsapp/status-reconcile.ts
|
|
7943
8049
|
import { readdirSync as readdirSync4 } from "fs";
|
|
7944
8050
|
import { join as join9 } from "path";
|
|
7945
|
-
var
|
|
8051
|
+
var TAG21 = "[whatsapp:reconcile]";
|
|
7946
8052
|
var HALF_REGISTERED_MIN_AGE_MS = 5 * 6e4;
|
|
7947
8053
|
function listCredsAccountIds(credsRoot) {
|
|
7948
8054
|
try {
|
|
@@ -7968,14 +8074,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
7968
8074
|
const ageMs = getAuthAgeMs(authDir);
|
|
7969
8075
|
const abandoned = !inFlight && ageMs !== null && ageMs >= HALF_REGISTERED_MIN_AGE_MS;
|
|
7970
8076
|
if (!abandoned) {
|
|
7971
|
-
console.error(`${
|
|
8077
|
+
console.error(`${TAG21} op=half-registered account=${accountId} registered=true hasAccount=false inFlight=${inFlight} ageMs=${ageMs ?? "null"}`);
|
|
7972
8078
|
entries.push({ accountId, connected: false, linkedUnconfigured: true });
|
|
7973
8079
|
continue;
|
|
7974
8080
|
}
|
|
7975
|
-
console.error(`${
|
|
8081
|
+
console.error(`${TAG21} op=discard-half-registered account=${accountId} registered=true hasAccount=false ageMs=${ageMs}`);
|
|
7976
8082
|
const removed = discardAuthDirSync(authDir);
|
|
7977
8083
|
if (!removed) {
|
|
7978
|
-
console.error(`${
|
|
8084
|
+
console.error(`${TAG21} op=discard-half-registered-failed account=${accountId} \u2014 residue persists`);
|
|
7979
8085
|
entries.push({ accountId, connected: false, linkedUnconfigured: true });
|
|
7980
8086
|
}
|
|
7981
8087
|
continue;
|
|
@@ -7983,14 +8089,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
7983
8089
|
const selfPhone = readSelfId(authDir).e164 ?? void 0;
|
|
7984
8090
|
const configured = accountDir ? isAccountConfigured(accountDir, accountId) : false;
|
|
7985
8091
|
console.error(
|
|
7986
|
-
`${
|
|
8092
|
+
`${TAG21} wa-link-unconfigured account=${accountId} creds=present config=${configured ? "present" : "absent"}`
|
|
7987
8093
|
);
|
|
7988
8094
|
if (!configured && accountDir) {
|
|
7989
8095
|
const result = persistAfterPairing(accountDir, accountId, selfPhone ?? null);
|
|
7990
8096
|
if (result.ok) {
|
|
7991
|
-
console.error(`${
|
|
8097
|
+
console.error(`${TAG21} self-healed account=${accountId}`);
|
|
7992
8098
|
} else {
|
|
7993
|
-
console.error(`${
|
|
8099
|
+
console.error(`${TAG21} self-heal FAILED account=${accountId}: ${result.error}`);
|
|
7994
8100
|
}
|
|
7995
8101
|
}
|
|
7996
8102
|
entries.push({ accountId, selfPhone, connected: false, linkedUnconfigured: !configured });
|
|
@@ -8091,7 +8197,7 @@ function authorizeRecallRead(input) {
|
|
|
8091
8197
|
}
|
|
8092
8198
|
|
|
8093
8199
|
// server/routes/whatsapp.ts
|
|
8094
|
-
var
|
|
8200
|
+
var TAG22 = "[whatsapp:api]";
|
|
8095
8201
|
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
8096
8202
|
function recallAttachment(storeAccountId, attachmentId) {
|
|
8097
8203
|
if (!attachmentId) return void 0;
|
|
@@ -8136,7 +8242,7 @@ function houseSocketScopeDenial(c, op) {
|
|
|
8136
8242
|
const houseAccountId = resolveAccount()?.accountId ?? "";
|
|
8137
8243
|
const { decision, callerAccountId, houseAdminScope } = evaluateCallerScope(c, houseAccountId);
|
|
8138
8244
|
if (decision.authorized) return null;
|
|
8139
|
-
console.error(`${
|
|
8245
|
+
console.error(`${TAG22} op=authz-deny route=${op} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
|
|
8140
8246
|
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);
|
|
8141
8247
|
}
|
|
8142
8248
|
var app2 = new Hono();
|
|
@@ -8152,7 +8258,7 @@ app2.get("/status", (c) => {
|
|
|
8152
8258
|
const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds, activeLoginAccountIds: activeLoginIds });
|
|
8153
8259
|
const accounts = [...live, ...reconciled];
|
|
8154
8260
|
const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
|
|
8155
|
-
console.error(`${
|
|
8261
|
+
console.error(`${TAG22} status accounts=${accounts.length} [${summary}]`);
|
|
8156
8262
|
const reachableAccountIds = [
|
|
8157
8263
|
.../* @__PURE__ */ new Set([...listStoredAccountIds(), ...listConnectionPlatformAccountIds()])
|
|
8158
8264
|
];
|
|
@@ -8161,11 +8267,11 @@ app2.get("/status", (c) => {
|
|
|
8161
8267
|
reachableAccountIds
|
|
8162
8268
|
});
|
|
8163
8269
|
if (!storeKeyReconcile.ok) {
|
|
8164
|
-
console.warn(`${
|
|
8270
|
+
console.warn(`${TAG22} store-key-drift orphanLiveKeys=[${storeKeyReconcile.orphanLiveKeys.join(",")}] \u2014 in-memory cache keyed by a value with no disk account; read tools will miss`);
|
|
8165
8271
|
}
|
|
8166
8272
|
return c.json({ accounts, storeKeyReconcile });
|
|
8167
8273
|
} catch (err) {
|
|
8168
|
-
console.error(`${
|
|
8274
|
+
console.error(`${TAG22} status error: ${String(err)}`);
|
|
8169
8275
|
return c.json({ error: String(err) }, 500);
|
|
8170
8276
|
}
|
|
8171
8277
|
});
|
|
@@ -8183,10 +8289,10 @@ app2.post("/login/start", async (c) => {
|
|
|
8183
8289
|
const authDir = join10(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
8184
8290
|
const accountDir = resolveAccount()?.accountDir ?? null;
|
|
8185
8291
|
const result = await startLogin({ accountId, authDir, phone, accountDir, force });
|
|
8186
|
-
console.error(`${
|
|
8292
|
+
console.error(`${TAG22} login/start result account=${accountId} hasCode=${!!result.pairingCode}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
8187
8293
|
return c.json(result);
|
|
8188
8294
|
} catch (err) {
|
|
8189
|
-
console.error(`${
|
|
8295
|
+
console.error(`${TAG22} login/start error: ${String(err)}`);
|
|
8190
8296
|
return c.json({ error: String(err) }, 500);
|
|
8191
8297
|
}
|
|
8192
8298
|
});
|
|
@@ -8198,7 +8304,7 @@ app2.post("/login/wait", async (c) => {
|
|
|
8198
8304
|
const accountId = validateAccountId(body.accountId);
|
|
8199
8305
|
const timeoutMs = body.timeoutMs ?? 6e4;
|
|
8200
8306
|
const result = await waitForLogin({ accountId, timeoutMs });
|
|
8201
|
-
console.error(`${
|
|
8307
|
+
console.error(`${TAG22} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
|
|
8202
8308
|
return c.json({
|
|
8203
8309
|
connected: result.connected,
|
|
8204
8310
|
message: result.message,
|
|
@@ -8206,7 +8312,7 @@ app2.post("/login/wait", async (c) => {
|
|
|
8206
8312
|
configPersisted: result.configPersisted
|
|
8207
8313
|
});
|
|
8208
8314
|
} catch (err) {
|
|
8209
|
-
console.error(`${
|
|
8315
|
+
console.error(`${TAG22} login/wait error: ${String(err)}`);
|
|
8210
8316
|
return c.json({ error: String(err) }, 500);
|
|
8211
8317
|
}
|
|
8212
8318
|
});
|
|
@@ -8219,7 +8325,7 @@ app2.post("/disconnect", async (c) => {
|
|
|
8219
8325
|
await stopConnection(accountId);
|
|
8220
8326
|
return c.json({ disconnected: true, accountId });
|
|
8221
8327
|
} catch (err) {
|
|
8222
|
-
console.error(`${
|
|
8328
|
+
console.error(`${TAG22} disconnect error: ${String(err)}`);
|
|
8223
8329
|
return c.json({ error: String(err) }, 500);
|
|
8224
8330
|
}
|
|
8225
8331
|
});
|
|
@@ -8232,7 +8338,7 @@ app2.post("/reconnect", async (c) => {
|
|
|
8232
8338
|
await startConnection(accountId);
|
|
8233
8339
|
return c.json({ reconnecting: true, accountId });
|
|
8234
8340
|
} catch (err) {
|
|
8235
|
-
console.error(`${
|
|
8341
|
+
console.error(`${TAG22} reconnect error: ${String(err)}`);
|
|
8236
8342
|
return c.json({ error: String(err) }, 500);
|
|
8237
8343
|
}
|
|
8238
8344
|
});
|
|
@@ -8256,7 +8362,7 @@ app2.post("/config", async (c) => {
|
|
|
8256
8362
|
}
|
|
8257
8363
|
const result = addAdminPhone(account.accountDir, phone);
|
|
8258
8364
|
const reasonTail = !result.ok && result.reason ? ` reason=${result.reason}` : "";
|
|
8259
|
-
console.error(`${
|
|
8365
|
+
console.error(`${TAG22} config action=add-admin-phone accountId=${account.accountId} phone=${phone} ok=${result.ok}${reasonTail}`);
|
|
8260
8366
|
return c.json(result, result.ok ? 200 : 400);
|
|
8261
8367
|
}
|
|
8262
8368
|
case "remove-admin-phone": {
|
|
@@ -8264,12 +8370,12 @@ app2.post("/config", async (c) => {
|
|
|
8264
8370
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
8265
8371
|
}
|
|
8266
8372
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
8267
|
-
console.error(`${
|
|
8373
|
+
console.error(`${TAG22} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
8268
8374
|
return c.json(result, result.ok ? 200 : 400);
|
|
8269
8375
|
}
|
|
8270
8376
|
case "list-admin-phones": {
|
|
8271
8377
|
const phones = readAdminPhones(account.accountDir);
|
|
8272
|
-
console.error(`${
|
|
8378
|
+
console.error(`${TAG22} config action=list-admin-phones count=${phones.length}`);
|
|
8273
8379
|
return c.json({ ok: true, phones });
|
|
8274
8380
|
}
|
|
8275
8381
|
case "add-account-manager": {
|
|
@@ -8281,7 +8387,7 @@ app2.post("/config", async (c) => {
|
|
|
8281
8387
|
}
|
|
8282
8388
|
const managerMode = mode === "passive" ? "passive" : "active";
|
|
8283
8389
|
const result = setAccountManager(account.accountDir, phone, managesAccount, managerMode);
|
|
8284
|
-
console.error(`${
|
|
8390
|
+
console.error(`${TAG22} config action=add-account-manager phone=${phone} managesAccount=${managesAccount} mode=${managerMode} ok=${result.ok}`);
|
|
8285
8391
|
return c.json(result, result.ok ? 200 : 400);
|
|
8286
8392
|
}
|
|
8287
8393
|
case "remove-account-manager": {
|
|
@@ -8289,12 +8395,12 @@ app2.post("/config", async (c) => {
|
|
|
8289
8395
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
8290
8396
|
}
|
|
8291
8397
|
const result = clearAccountManager(account.accountDir, phone);
|
|
8292
|
-
console.error(`${
|
|
8398
|
+
console.error(`${TAG22} config action=remove-account-manager phone=${phone} ok=${result.ok}`);
|
|
8293
8399
|
return c.json(result, result.ok ? 200 : 400);
|
|
8294
8400
|
}
|
|
8295
8401
|
case "list-account-managers": {
|
|
8296
8402
|
const accountManagers = readAccountManagers(account.accountDir);
|
|
8297
|
-
console.error(`${
|
|
8403
|
+
console.error(`${TAG22} config action=list-account-managers count=${Object.keys(accountManagers).length}`);
|
|
8298
8404
|
return c.json({ ok: true, accountManagers });
|
|
8299
8405
|
}
|
|
8300
8406
|
case "set-public-agent": {
|
|
@@ -8302,13 +8408,13 @@ app2.post("/config", async (c) => {
|
|
|
8302
8408
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
8303
8409
|
}
|
|
8304
8410
|
const result = setPublicAgent(account.accountDir, slug);
|
|
8305
|
-
console.error(`${
|
|
8411
|
+
console.error(`${TAG22} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
8306
8412
|
return c.json(result, result.ok ? 200 : 400);
|
|
8307
8413
|
}
|
|
8308
8414
|
case "get-public-agent": {
|
|
8309
8415
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
8310
8416
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
|
|
8311
|
-
console.error(`${
|
|
8417
|
+
console.error(`${TAG22} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
8312
8418
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
8313
8419
|
}
|
|
8314
8420
|
case "list-public-agents": {
|
|
@@ -8325,26 +8431,26 @@ app2.post("/config", async (c) => {
|
|
|
8325
8431
|
const config = JSON.parse(readFileSync12(configPath2, "utf-8"));
|
|
8326
8432
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
8327
8433
|
} catch {
|
|
8328
|
-
console.error(`${
|
|
8434
|
+
console.error(`${TAG22} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
8329
8435
|
}
|
|
8330
8436
|
}
|
|
8331
8437
|
} catch (err) {
|
|
8332
|
-
console.error(`${
|
|
8438
|
+
console.error(`${TAG22} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
8333
8439
|
}
|
|
8334
8440
|
}
|
|
8335
|
-
console.error(`${
|
|
8441
|
+
console.error(`${TAG22} config action=list-public-agents count=${agents.length}`);
|
|
8336
8442
|
return c.json({ ok: true, agents });
|
|
8337
8443
|
}
|
|
8338
8444
|
case "schema": {
|
|
8339
8445
|
const text = serializeWhatsAppSchema();
|
|
8340
|
-
console.error(`${
|
|
8446
|
+
console.error(`${TAG22} config action=schema`);
|
|
8341
8447
|
return c.json({ ok: true, text });
|
|
8342
8448
|
}
|
|
8343
8449
|
case "list-groups": {
|
|
8344
8450
|
const groupAccountId = accountId ?? "default";
|
|
8345
8451
|
const sock = getSocket(groupAccountId);
|
|
8346
8452
|
if (!sock) {
|
|
8347
|
-
console.error(`${
|
|
8453
|
+
console.error(`${TAG22} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
8348
8454
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
8349
8455
|
}
|
|
8350
8456
|
try {
|
|
@@ -8354,10 +8460,10 @@ app2.post("/config", async (c) => {
|
|
|
8354
8460
|
name: g.subject ?? g.id,
|
|
8355
8461
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
8356
8462
|
}));
|
|
8357
|
-
console.error(`${
|
|
8463
|
+
console.error(`${TAG22} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
8358
8464
|
return c.json({ ok: true, groups });
|
|
8359
8465
|
} catch (err) {
|
|
8360
|
-
console.error(`${
|
|
8466
|
+
console.error(`${TAG22} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
8361
8467
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
8362
8468
|
}
|
|
8363
8469
|
}
|
|
@@ -8367,12 +8473,12 @@ app2.post("/config", async (c) => {
|
|
|
8367
8473
|
}
|
|
8368
8474
|
const result = updateConfig(account.accountDir, fields);
|
|
8369
8475
|
const fieldNames = Object.keys(fields);
|
|
8370
|
-
console.error(`${
|
|
8476
|
+
console.error(`${TAG22} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
8371
8477
|
return c.json(result, result.ok ? 200 : 400);
|
|
8372
8478
|
}
|
|
8373
8479
|
case "get-config": {
|
|
8374
8480
|
const waConfig = getConfig(account.accountDir);
|
|
8375
|
-
console.error(`${
|
|
8481
|
+
console.error(`${TAG22} config action=get-config`);
|
|
8376
8482
|
return c.json({ ok: true, config: waConfig });
|
|
8377
8483
|
}
|
|
8378
8484
|
default:
|
|
@@ -8382,7 +8488,7 @@ app2.post("/config", async (c) => {
|
|
|
8382
8488
|
);
|
|
8383
8489
|
}
|
|
8384
8490
|
} catch (err) {
|
|
8385
|
-
console.error(`${
|
|
8491
|
+
console.error(`${TAG22} config error: ${String(err)}`);
|
|
8386
8492
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
8387
8493
|
}
|
|
8388
8494
|
});
|
|
@@ -8403,7 +8509,7 @@ app2.post("/send-document", async (c) => {
|
|
|
8403
8509
|
recordRouteDocumentOutbound(to, filePath);
|
|
8404
8510
|
return c.json({ success: true, messageId: result.messageId });
|
|
8405
8511
|
} catch (err) {
|
|
8406
|
-
console.error(`${
|
|
8512
|
+
console.error(`${TAG22} send-document error: ${String(err)}`);
|
|
8407
8513
|
return c.json({ error: String(err) }, 500);
|
|
8408
8514
|
}
|
|
8409
8515
|
});
|
|
@@ -8448,7 +8554,7 @@ app2.post("/send-admin", async (c) => {
|
|
|
8448
8554
|
console.error(`[whatsapp:outbound] op=send-admin-ok via=${via} to=${phone}`);
|
|
8449
8555
|
return c.json({ success: true, messageId: result.messageId });
|
|
8450
8556
|
} catch (err) {
|
|
8451
|
-
console.error(`${
|
|
8557
|
+
console.error(`${TAG22} send-admin error: ${String(err)}`);
|
|
8452
8558
|
return c.json({ error: String(err) }, 500);
|
|
8453
8559
|
}
|
|
8454
8560
|
});
|
|
@@ -8458,11 +8564,11 @@ app2.get("/activity", (c) => {
|
|
|
8458
8564
|
const result = getChannelActivity(accountId);
|
|
8459
8565
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
8460
8566
|
console.error(
|
|
8461
|
-
`${
|
|
8567
|
+
`${TAG22} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
8462
8568
|
);
|
|
8463
8569
|
return c.json(result);
|
|
8464
8570
|
} catch (err) {
|
|
8465
|
-
console.error(`${
|
|
8571
|
+
console.error(`${TAG22} activity error: ${String(err)}`);
|
|
8466
8572
|
return c.json({ error: String(err) }, 500);
|
|
8467
8573
|
}
|
|
8468
8574
|
});
|
|
@@ -8487,10 +8593,10 @@ app2.get("/conversations", (c) => {
|
|
|
8487
8593
|
};
|
|
8488
8594
|
});
|
|
8489
8595
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
8490
|
-
console.error(`${
|
|
8596
|
+
console.error(`${TAG22} conversations requested=${requestedAccountId} account=${storeAccountId} projected=${projecting} storeAccount=${projecting ? storeAccountId : "self"} managerPhonesBound=${managerPhonesBound} storeKeyFound=${allConversations.size > 0} count=${conversations.length}`);
|
|
8491
8597
|
return c.json({ conversations });
|
|
8492
8598
|
} catch (err) {
|
|
8493
|
-
console.error(`${
|
|
8599
|
+
console.error(`${TAG22} conversations error: ${String(err)}`);
|
|
8494
8600
|
return c.json({ error: String(err) }, 500);
|
|
8495
8601
|
}
|
|
8496
8602
|
});
|
|
@@ -8535,10 +8641,10 @@ app2.get("/messages", (c) => {
|
|
|
8535
8641
|
...reactions && reactions.length > 0 ? { reactions } : {}
|
|
8536
8642
|
};
|
|
8537
8643
|
});
|
|
8538
|
-
console.error(`${
|
|
8644
|
+
console.error(`${TAG22} messages requested=${requestedAccountId} account=${storeAccountId} jid=${jid} projected=${projecting} storeAccount=${projecting ? storeAccountId : "self"} managerPhonesBound=${managerPhonesBound} storeKeyFound=${storeMessages.length > 0} limit=${effectiveLimit ?? "all"} returned=${messages.length} withAttachments=${withAttachments}`);
|
|
8539
8645
|
return c.json({ messages });
|
|
8540
8646
|
} catch (err) {
|
|
8541
|
-
console.error(`${
|
|
8647
|
+
console.error(`${TAG22} messages error: ${String(err)}`);
|
|
8542
8648
|
return c.json({ error: String(err) }, 500);
|
|
8543
8649
|
}
|
|
8544
8650
|
});
|
|
@@ -8640,7 +8746,7 @@ app2.get("/conversation-graph-state", async (c) => {
|
|
|
8640
8746
|
ms
|
|
8641
8747
|
});
|
|
8642
8748
|
} catch (err) {
|
|
8643
|
-
console.error(`${
|
|
8749
|
+
console.error(`${TAG22} conversation-graph-state error: ${String(err)}`);
|
|
8644
8750
|
return c.json({ error: String(err) }, 500);
|
|
8645
8751
|
}
|
|
8646
8752
|
});
|
|
@@ -8652,12 +8758,12 @@ app2.get("/group-info", async (c) => {
|
|
|
8652
8758
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
8653
8759
|
}
|
|
8654
8760
|
if (!isGroupJid(jid)) {
|
|
8655
|
-
console.error(`${
|
|
8761
|
+
console.error(`${TAG22} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
8656
8762
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
8657
8763
|
}
|
|
8658
8764
|
const sock = getSocket(accountId);
|
|
8659
8765
|
if (!sock) {
|
|
8660
|
-
console.error(`${
|
|
8766
|
+
console.error(`${TAG22} group-info error="not connected" account=${accountId}`);
|
|
8661
8767
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
8662
8768
|
}
|
|
8663
8769
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -8670,10 +8776,10 @@ app2.get("/group-info", async (c) => {
|
|
|
8670
8776
|
participantCount: meta.participants.length,
|
|
8671
8777
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
8672
8778
|
};
|
|
8673
|
-
console.error(`${
|
|
8779
|
+
console.error(`${TAG22} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
8674
8780
|
return c.json(result);
|
|
8675
8781
|
} catch (err) {
|
|
8676
|
-
console.error(`${
|
|
8782
|
+
console.error(`${TAG22} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
8677
8783
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
8678
8784
|
}
|
|
8679
8785
|
});
|
|
@@ -9480,10 +9586,10 @@ async function fetchLiveSessions() {
|
|
|
9480
9586
|
function loadUserTitles(accountDir) {
|
|
9481
9587
|
const out = /* @__PURE__ */ new Map();
|
|
9482
9588
|
if (!accountDir) return out;
|
|
9483
|
-
const
|
|
9589
|
+
const path3 = join14(accountDir, "session-titles.json");
|
|
9484
9590
|
let raw;
|
|
9485
9591
|
try {
|
|
9486
|
-
raw = readFileSync15(
|
|
9592
|
+
raw = readFileSync15(path3, "utf8");
|
|
9487
9593
|
} catch {
|
|
9488
9594
|
return out;
|
|
9489
9595
|
}
|
|
@@ -9638,21 +9744,21 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
9638
9744
|
const adminUserIdBySession = /* @__PURE__ */ new Map();
|
|
9639
9745
|
const channelSenderBySession = /* @__PURE__ */ new Map();
|
|
9640
9746
|
const channelMissNoSender = [];
|
|
9641
|
-
for (const { path:
|
|
9642
|
-
const lastSlash =
|
|
9643
|
-
const base =
|
|
9644
|
-
const projectDir =
|
|
9747
|
+
for (const { path: path3, isSubagent, archived } of jsonls) {
|
|
9748
|
+
const lastSlash = path3.lastIndexOf("/");
|
|
9749
|
+
const base = path3.slice(lastSlash + 1);
|
|
9750
|
+
const projectDir = path3.slice(0, lastSlash);
|
|
9645
9751
|
const sessionId = base.slice(0, -".jsonl".length);
|
|
9646
9752
|
if (seenIds.has(sessionId)) {
|
|
9647
|
-
console.error(`[admin-sessions-list] duplicate-sessionId sessionId=${sessionId} path=${
|
|
9753
|
+
console.error(`[admin-sessions-list] duplicate-sessionId sessionId=${sessionId} path=${path3}`);
|
|
9648
9754
|
continue;
|
|
9649
9755
|
}
|
|
9650
9756
|
seenIds.add(sessionId);
|
|
9651
9757
|
let body;
|
|
9652
9758
|
let mtimeMs;
|
|
9653
9759
|
try {
|
|
9654
|
-
body = readFileSync15(
|
|
9655
|
-
mtimeMs = statSync4(
|
|
9760
|
+
body = readFileSync15(path3, "utf8");
|
|
9761
|
+
mtimeMs = statSync4(path3).mtimeMs;
|
|
9656
9762
|
} catch {
|
|
9657
9763
|
continue;
|
|
9658
9764
|
}
|
|
@@ -10271,9 +10377,9 @@ app5.get("/conversations", requireAdminSession, async (c) => {
|
|
|
10271
10377
|
const userTitles = loadUserTitles(ACCOUNTS_DIR ?? null);
|
|
10272
10378
|
const rows = [];
|
|
10273
10379
|
let sessionRowsExcludedUntagged = 0;
|
|
10274
|
-
for (const { path:
|
|
10275
|
-
const sessionId = basename4(
|
|
10276
|
-
const projectDir = dirname5(
|
|
10380
|
+
for (const { path: path3 } of enumerateJsonls(projectsRoot)) {
|
|
10381
|
+
const sessionId = basename4(path3).replace(/\.jsonl$/, "");
|
|
10382
|
+
const projectDir = dirname5(path3);
|
|
10277
10383
|
const meta = readSidecarMeta(join16(projectDir, `${sessionId}.meta.json`));
|
|
10278
10384
|
if (!isReaderChannelSession(meta.role, meta.channel)) continue;
|
|
10279
10385
|
if (multiAccount) {
|
|
@@ -10282,7 +10388,7 @@ app5.get("/conversations", requireAdminSession, async (c) => {
|
|
|
10282
10388
|
}
|
|
10283
10389
|
let body = "";
|
|
10284
10390
|
try {
|
|
10285
|
-
body = readFileSync17(
|
|
10391
|
+
body = readFileSync17(path3, "utf8");
|
|
10286
10392
|
} catch {
|
|
10287
10393
|
}
|
|
10288
10394
|
const { title } = resolveTitle(body, sessionId, userTitles);
|
|
@@ -10365,10 +10471,10 @@ app5.get("/conversations", requireAdminSession, async (c) => {
|
|
|
10365
10471
|
);
|
|
10366
10472
|
return c.json({ conversations: [...sessionConversations, ...allStoreRows] });
|
|
10367
10473
|
});
|
|
10368
|
-
function readFrom(
|
|
10369
|
-
const end = statSync5(
|
|
10474
|
+
function readFrom(path3, from) {
|
|
10475
|
+
const end = statSync5(path3).size;
|
|
10370
10476
|
if (end <= from) return { buf: Buffer.alloc(0), end: from };
|
|
10371
|
-
const fd = openSync(
|
|
10477
|
+
const fd = openSync(path3, "r");
|
|
10372
10478
|
try {
|
|
10373
10479
|
const buf = Buffer.alloc(end - from);
|
|
10374
10480
|
readSync(fd, buf, 0, buf.length, from);
|
|
@@ -10703,11 +10809,11 @@ app5.get("/directive", requireAdminSession, (c) => {
|
|
|
10703
10809
|
}
|
|
10704
10810
|
const dir = directiveStoreDir(sessionId);
|
|
10705
10811
|
if (!dir) return c.json({ error: "no account" }, 404);
|
|
10706
|
-
const
|
|
10707
|
-
if (!
|
|
10812
|
+
const path3 = resolve13(join16(dir, name));
|
|
10813
|
+
if (!path3.startsWith(dir + sep3)) return c.json({ error: "bad reference" }, 400);
|
|
10708
10814
|
let body;
|
|
10709
10815
|
try {
|
|
10710
|
-
body = readFileSync17(
|
|
10816
|
+
body = readFileSync17(path3, "utf8");
|
|
10711
10817
|
} catch {
|
|
10712
10818
|
return c.json({ error: "not found" }, 404);
|
|
10713
10819
|
}
|
|
@@ -11184,7 +11290,7 @@ function resolveOpenVisitorSession(rows, visitorId, agentSlug) {
|
|
|
11184
11290
|
|
|
11185
11291
|
// server/routes/public-reader.ts
|
|
11186
11292
|
var app6 = new Hono();
|
|
11187
|
-
var
|
|
11293
|
+
var TAG23 = "[public-webchat]";
|
|
11188
11294
|
function parseAccessSessionId(cookieHeader) {
|
|
11189
11295
|
if (!cookieHeader) return null;
|
|
11190
11296
|
const part = cookieHeader.split(";").map((p) => p.trim()).find((p) => p.startsWith("__access_session="));
|
|
@@ -11211,9 +11317,9 @@ function enumeratePublicRows() {
|
|
|
11211
11317
|
const cfg = claudeConfigDir();
|
|
11212
11318
|
if (!cfg) return [];
|
|
11213
11319
|
const rows = [];
|
|
11214
|
-
for (const { path:
|
|
11215
|
-
const sessionId = basename5(
|
|
11216
|
-
const projectDir = dirname6(
|
|
11320
|
+
for (const { path: path3 } of enumerateJsonls(join17(cfg, "projects"))) {
|
|
11321
|
+
const sessionId = basename5(path3).replace(/\.jsonl$/, "");
|
|
11322
|
+
const projectDir = dirname6(path3);
|
|
11217
11323
|
const meta = readSidecarMeta(join17(projectDir, `${sessionId}.meta.json`));
|
|
11218
11324
|
rows.push({
|
|
11219
11325
|
sessionId,
|
|
@@ -11232,7 +11338,7 @@ function enumeratePublicRows() {
|
|
|
11232
11338
|
app6.get("/session", (c) => {
|
|
11233
11339
|
const visitor = resolveVisitor(c);
|
|
11234
11340
|
if (!visitor) {
|
|
11235
|
-
console.error(`${
|
|
11341
|
+
console.error(`${TAG23} op=reader-refused reason=no-anchor path=/session`);
|
|
11236
11342
|
return c.json({ error: "gate-required" }, 401);
|
|
11237
11343
|
}
|
|
11238
11344
|
const rows = enumeratePublicRows();
|
|
@@ -11251,11 +11357,11 @@ app6.get("/session", (c) => {
|
|
|
11251
11357
|
found = effectiveSlug ? resolveOpenVisitorSession(rows, visitor.visitorId, effectiveSlug) : null;
|
|
11252
11358
|
}
|
|
11253
11359
|
if (found) {
|
|
11254
|
-
console.log(`${
|
|
11360
|
+
console.log(`${TAG23} op=session-resume anchor=${visitor.kind} key=${(found.senderId ?? "").slice(0, 8)} sessionId=${found.sessionId.slice(0, 8)}`);
|
|
11255
11361
|
return c.json({ sessionId: found.sessionId, projectDir: found.projectDir, sessionKey: found.senderId });
|
|
11256
11362
|
}
|
|
11257
11363
|
const sessionKey = crypto.randomUUID();
|
|
11258
|
-
console.log(`${
|
|
11364
|
+
console.log(`${TAG23} op=session-new anchor=${visitor.kind} key=${sessionKey.slice(0, 8)}`);
|
|
11259
11365
|
return c.json({ sessionId: null, projectDir: null, sessionKey });
|
|
11260
11366
|
});
|
|
11261
11367
|
function publicUploadsDir(accountId, sessionId) {
|
|
@@ -11269,10 +11375,10 @@ function enrichPublicAttachments(turns, sessionAttachments, cursor) {
|
|
|
11269
11375
|
if (slice.length > 0) turn.attachments = slice;
|
|
11270
11376
|
}
|
|
11271
11377
|
}
|
|
11272
|
-
function readFrom2(
|
|
11273
|
-
const end = statSync6(
|
|
11378
|
+
function readFrom2(path3, from) {
|
|
11379
|
+
const end = statSync6(path3).size;
|
|
11274
11380
|
if (end <= from) return { buf: Buffer.alloc(0), end: from };
|
|
11275
|
-
const fd = openSync2(
|
|
11381
|
+
const fd = openSync2(path3, "r");
|
|
11276
11382
|
try {
|
|
11277
11383
|
const buf = Buffer.alloc(end - from);
|
|
11278
11384
|
readSync2(fd, buf, 0, buf.length, from);
|
|
@@ -11284,7 +11390,7 @@ function readFrom2(path2, from) {
|
|
|
11284
11390
|
app6.get("/stream", (c) => {
|
|
11285
11391
|
const visitor = resolveVisitor(c);
|
|
11286
11392
|
if (!visitor) {
|
|
11287
|
-
console.error(`${
|
|
11393
|
+
console.error(`${TAG23} op=reader-refused reason=no-anchor path=/stream`);
|
|
11288
11394
|
return c.json({ error: "gate-required" }, 401);
|
|
11289
11395
|
}
|
|
11290
11396
|
const sessionId = c.req.query("sessionId") ?? "";
|
|
@@ -11302,12 +11408,12 @@ app6.get("/stream", (c) => {
|
|
|
11302
11408
|
const isPublicWebchat = meta.role === "public" && meta.channel === "webchat";
|
|
11303
11409
|
const owns = visitor.kind === "person" ? meta.personId === visitor.personId : meta.personId === null && meta.visitorId === visitor.visitorId;
|
|
11304
11410
|
if (!(isPublicWebchat && owns)) {
|
|
11305
|
-
console.error(`${
|
|
11411
|
+
console.error(`${TAG23} op=reader-refused reason=not-owner anchor=${visitor.kind} sessionId=${sessionId.slice(0, 8)}`);
|
|
11306
11412
|
return c.json({ error: "forbidden" }, 403);
|
|
11307
11413
|
}
|
|
11308
11414
|
const senderShort = (meta.senderId ?? "").slice(0, 8);
|
|
11309
11415
|
const encoder = new TextEncoder();
|
|
11310
|
-
console.log(`${
|
|
11416
|
+
console.log(`${TAG23} op=reader-open anchor=${visitor.kind} key=${senderShort} mode=delivered-only sessionId=${sessionId.slice(0, 8)}`);
|
|
11311
11417
|
const send = (controller, turn, id) => controller.enqueue(encoder.encode(`id: ${id}
|
|
11312
11418
|
data: ${JSON.stringify(turn)}
|
|
11313
11419
|
|
|
@@ -11322,7 +11428,7 @@ data: ${JSON.stringify(turn)}
|
|
|
11322
11428
|
const { turns, dropped } = filterDeliveredFrames(lines);
|
|
11323
11429
|
if (uploadsDir) enrichPublicAttachments(turns, listSessionAttachmentsInDir(uploadsDir), attachmentCursor);
|
|
11324
11430
|
for (const turn of turns) send(controller, turn, offset);
|
|
11325
|
-
if (dropped > 0) console.log(`${
|
|
11431
|
+
if (dropped > 0) console.log(`${TAG23} op=reader-filtered key=${senderShort} dropped=${dropped}`);
|
|
11326
11432
|
};
|
|
11327
11433
|
try {
|
|
11328
11434
|
const { buf, end } = readFrom2(jsonlPath, 0);
|
|
@@ -11357,7 +11463,7 @@ data: ${JSON.stringify(turn)}
|
|
|
11357
11463
|
watcher?.close();
|
|
11358
11464
|
clearInterval(poll);
|
|
11359
11465
|
clearInterval(heartbeat);
|
|
11360
|
-
console.log(`${
|
|
11466
|
+
console.log(`${TAG23} op=reader-close key=${senderShort}`);
|
|
11361
11467
|
try {
|
|
11362
11468
|
controller.close();
|
|
11363
11469
|
} catch {
|
|
@@ -11474,10 +11580,10 @@ function writeCanonicalOverrideId(accountDir, userId, sessionId) {
|
|
|
11474
11580
|
}
|
|
11475
11581
|
}
|
|
11476
11582
|
byAdmin[userId] = sessionId;
|
|
11477
|
-
const
|
|
11478
|
-
const tmp = `${
|
|
11583
|
+
const path3 = canonicalOverridePath(accountDir);
|
|
11584
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
11479
11585
|
writeFileSync5(tmp, JSON.stringify({ byAdmin }, null, 2), "utf8");
|
|
11480
|
-
renameSync2(tmp,
|
|
11586
|
+
renameSync2(tmp, path3);
|
|
11481
11587
|
}
|
|
11482
11588
|
|
|
11483
11589
|
// ../lib/models/src/index.ts
|
|
@@ -11486,7 +11592,7 @@ var SONNET_MODEL = "claude-sonnet-5";
|
|
|
11486
11592
|
var HAIKU_MODEL = "claude-haiku-4-5";
|
|
11487
11593
|
var MODEL_CONTEXT_WINDOW = {
|
|
11488
11594
|
[OPUS_MODEL]: 1e6,
|
|
11489
|
-
[SONNET_MODEL]:
|
|
11595
|
+
[SONNET_MODEL]: 1e6,
|
|
11490
11596
|
[HAIKU_MODEL]: 2e5
|
|
11491
11597
|
};
|
|
11492
11598
|
function contextWindow(model) {
|
|
@@ -11536,11 +11642,11 @@ function createSidecarStore(config) {
|
|
|
11536
11642
|
}
|
|
11537
11643
|
function write(sessionsDir, id, record) {
|
|
11538
11644
|
mkdirSync3(sessionsDir, { recursive: true });
|
|
11539
|
-
const
|
|
11540
|
-
const tmp = `${
|
|
11645
|
+
const path3 = pathFor(sessionsDir, id);
|
|
11646
|
+
const tmp = `${path3}.tmp.${process.pid}`;
|
|
11541
11647
|
try {
|
|
11542
11648
|
writeFileSync6(tmp, JSON.stringify(record), "utf8");
|
|
11543
|
-
renameSync3(tmp,
|
|
11649
|
+
renameSync3(tmp, path3);
|
|
11544
11650
|
return { ok: true };
|
|
11545
11651
|
} catch (error) {
|
|
11546
11652
|
try {
|
|
@@ -11550,10 +11656,10 @@ function createSidecarStore(config) {
|
|
|
11550
11656
|
return { ok: false, error };
|
|
11551
11657
|
}
|
|
11552
11658
|
}
|
|
11553
|
-
function readFileAt(
|
|
11659
|
+
function readFileAt(path3) {
|
|
11554
11660
|
let raw;
|
|
11555
11661
|
try {
|
|
11556
|
-
raw = readFileSync20(
|
|
11662
|
+
raw = readFileSync20(path3, "utf8");
|
|
11557
11663
|
} catch {
|
|
11558
11664
|
return null;
|
|
11559
11665
|
}
|
|
@@ -11576,10 +11682,10 @@ function createSidecarStore(config) {
|
|
|
11576
11682
|
const out = [];
|
|
11577
11683
|
for (const name of names) {
|
|
11578
11684
|
if (!suffixRe.test(name)) continue;
|
|
11579
|
-
const
|
|
11685
|
+
const path3 = join19(sessionsDir, name);
|
|
11580
11686
|
let raw;
|
|
11581
11687
|
try {
|
|
11582
|
-
raw = readFileSync20(
|
|
11688
|
+
raw = readFileSync20(path3, "utf8");
|
|
11583
11689
|
} catch {
|
|
11584
11690
|
onSkip?.(name, "unreadable");
|
|
11585
11691
|
continue;
|
|
@@ -11598,10 +11704,10 @@ function createSidecarStore(config) {
|
|
|
11598
11704
|
return out;
|
|
11599
11705
|
}
|
|
11600
11706
|
function clear(sessionsDir, id) {
|
|
11601
|
-
const
|
|
11707
|
+
const path3 = pathFor(sessionsDir, id);
|
|
11602
11708
|
try {
|
|
11603
|
-
if (existsSync12(
|
|
11604
|
-
unlinkSync2(
|
|
11709
|
+
if (existsSync12(path3)) {
|
|
11710
|
+
unlinkSync2(path3);
|
|
11605
11711
|
return { ok: true, removed: true };
|
|
11606
11712
|
}
|
|
11607
11713
|
return { ok: true, removed: false };
|
|
@@ -11830,15 +11936,15 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
|
|
|
11830
11936
|
const cfg = claudeConfigDir();
|
|
11831
11937
|
if (!cfg) return null;
|
|
11832
11938
|
let best = null;
|
|
11833
|
-
for (const { path:
|
|
11939
|
+
for (const { path: path3, isSubagent, archived } of enumerateJsonls(join20(cfg, "projects"))) {
|
|
11834
11940
|
if (isSubagent || archived) continue;
|
|
11835
|
-
const id = basename6(
|
|
11836
|
-
const meta = readSidecarMeta(join20(dirname7(
|
|
11941
|
+
const id = basename6(path3).slice(0, -".jsonl".length);
|
|
11942
|
+
const meta = readSidecarMeta(join20(dirname7(path3), `${id}.meta.json`));
|
|
11837
11943
|
if (meta.role !== "admin" || meta.channel !== "webchat") continue;
|
|
11838
11944
|
if (multiAccount && meta.accountId !== scopeAccountId) continue;
|
|
11839
11945
|
let mtimeMs;
|
|
11840
11946
|
try {
|
|
11841
|
-
mtimeMs = statSync7(
|
|
11947
|
+
mtimeMs = statSync7(path3).mtimeMs;
|
|
11842
11948
|
} catch {
|
|
11843
11949
|
continue;
|
|
11844
11950
|
}
|
|
@@ -12235,9 +12341,9 @@ ${note}` : note;
|
|
|
12235
12341
|
const cfg = claudeConfigDir();
|
|
12236
12342
|
let projectDir = null;
|
|
12237
12343
|
if (cfg) {
|
|
12238
|
-
for (const { path:
|
|
12239
|
-
if (basename6(
|
|
12240
|
-
projectDir = dirname7(
|
|
12344
|
+
for (const { path: path3 } of enumerateJsonls(join20(cfg, "projects"))) {
|
|
12345
|
+
if (basename6(path3) === `${sessionId}.jsonl`) {
|
|
12346
|
+
projectDir = dirname7(path3);
|
|
12241
12347
|
break;
|
|
12242
12348
|
}
|
|
12243
12349
|
}
|
|
@@ -12469,7 +12575,7 @@ function routeTelegramUpdate(input) {
|
|
|
12469
12575
|
}
|
|
12470
12576
|
|
|
12471
12577
|
// server/routes/telegram.ts
|
|
12472
|
-
var
|
|
12578
|
+
var TAG24 = "[telegram-inbound]";
|
|
12473
12579
|
function configDirName() {
|
|
12474
12580
|
const platformRoot5 = process.env.MAXY_PLATFORM_ROOT ?? resolve16(process.cwd(), "..");
|
|
12475
12581
|
const brandPath = join22(platformRoot5, "config", "brand.json");
|
|
@@ -12490,18 +12596,18 @@ app8.post("/", async (c) => {
|
|
|
12490
12596
|
const botParam = c.req.query("bot");
|
|
12491
12597
|
const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
|
|
12492
12598
|
if (!botType) {
|
|
12493
|
-
console.error(`${
|
|
12599
|
+
console.error(`${TAG24} op=reject reason=missing-bot-param`);
|
|
12494
12600
|
return c.json({ ok: false }, 400);
|
|
12495
12601
|
}
|
|
12496
12602
|
const sp = secretPath(botType);
|
|
12497
12603
|
if (!existsSync15(sp)) {
|
|
12498
|
-
console.error(`${
|
|
12604
|
+
console.error(`${TAG24} op=reject reason=no-secret-file botType=${botType}`);
|
|
12499
12605
|
return c.json({ ok: false }, 401);
|
|
12500
12606
|
}
|
|
12501
12607
|
const expected = readFileSync23(sp, "utf-8").trim();
|
|
12502
12608
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
12503
12609
|
if (got !== expected) {
|
|
12504
|
-
console.error(`${
|
|
12610
|
+
console.error(`${TAG24} op=reject reason=bad-secret botType=${botType}`);
|
|
12505
12611
|
return c.json({ ok: false }, 401);
|
|
12506
12612
|
}
|
|
12507
12613
|
let update;
|
|
@@ -12512,40 +12618,40 @@ app8.post("/", async (c) => {
|
|
|
12512
12618
|
}
|
|
12513
12619
|
const account = resolveAccount();
|
|
12514
12620
|
if (!account) {
|
|
12515
|
-
console.error(`${
|
|
12621
|
+
console.error(`${TAG24} op=reject reason=no-account`);
|
|
12516
12622
|
return c.json({ ok: true }, 200);
|
|
12517
12623
|
}
|
|
12518
12624
|
const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
|
|
12519
12625
|
if (decision.kind === "ignore") {
|
|
12520
|
-
console.error(`${
|
|
12626
|
+
console.error(`${TAG24} op=ignore reason=${decision.reason}`);
|
|
12521
12627
|
return c.json({ ok: true }, 200);
|
|
12522
12628
|
}
|
|
12523
12629
|
if (decision.kind === "denied") {
|
|
12524
|
-
console.error(`${
|
|
12630
|
+
console.error(`${TAG24} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
|
|
12525
12631
|
return c.json({ ok: true }, 200);
|
|
12526
12632
|
}
|
|
12527
12633
|
const role = decision.agentType === "admin" ? "admin" : "public";
|
|
12528
12634
|
const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
|
|
12529
12635
|
if (!agentSlug) {
|
|
12530
|
-
console.error(`${
|
|
12636
|
+
console.error(`${TAG24} op=reject reason=no-default-agent`);
|
|
12531
12637
|
return c.json({ ok: true }, 200);
|
|
12532
12638
|
}
|
|
12533
|
-
console.error(`${
|
|
12639
|
+
console.error(`${TAG24} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
12534
12640
|
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
12535
12641
|
const { senderId, chatId, text } = decision;
|
|
12536
12642
|
const gateway = getTelegramGateway();
|
|
12537
12643
|
if (!gateway) {
|
|
12538
|
-
console.error(`${
|
|
12644
|
+
console.error(`${TAG24} op=reject reason=gateway-not-ready from=${senderId}`);
|
|
12539
12645
|
return c.json({ ok: true }, 200);
|
|
12540
12646
|
}
|
|
12541
12647
|
const reply = async (replyText) => {
|
|
12542
12648
|
if (!botToken) {
|
|
12543
12649
|
const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
|
|
12544
|
-
console.error(`${
|
|
12650
|
+
console.error(`${TAG24} op=reply-dropped reason=${reason} botType=${botType} from=${senderId}`);
|
|
12545
12651
|
return;
|
|
12546
12652
|
}
|
|
12547
12653
|
const sent = await sendTelegramText(botToken, chatId, replyText);
|
|
12548
|
-
console.error(`${
|
|
12654
|
+
console.error(`${TAG24} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
12549
12655
|
};
|
|
12550
12656
|
void gateway.handleInbound({
|
|
12551
12657
|
accountId: account.accountId,
|
|
@@ -12563,7 +12669,7 @@ var telegram_default = app8;
|
|
|
12563
12669
|
// server/routes/quickbooks.ts
|
|
12564
12670
|
import { join as join23 } from "path";
|
|
12565
12671
|
import { existsSync as existsSync16, readFileSync as readFileSync24, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync5 } from "fs";
|
|
12566
|
-
var
|
|
12672
|
+
var TAG25 = "[quickbooks]";
|
|
12567
12673
|
var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
|
|
12568
12674
|
var STATE_RE = /^[A-Za-z0-9_-]+$/;
|
|
12569
12675
|
function pendingPath(accountDir, state) {
|
|
@@ -12576,14 +12682,14 @@ async function completeConsent(args) {
|
|
|
12576
12682
|
const { accountDir, code, realmId } = args;
|
|
12577
12683
|
const state = args.state;
|
|
12578
12684
|
if (!state || !STATE_RE.test(state) || !existsSync16(pendingPath(accountDir, state))) {
|
|
12579
|
-
console.error(`${
|
|
12685
|
+
console.error(`${TAG25} op=callback state=${state ?? ""} stateValid=false realmId=${realmId ?? ""}`);
|
|
12580
12686
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
12581
12687
|
}
|
|
12582
12688
|
const claimFile = `${pendingPath(accountDir, state)}.claimed`;
|
|
12583
12689
|
try {
|
|
12584
12690
|
renameSync5(pendingPath(accountDir, state), claimFile);
|
|
12585
12691
|
} catch {
|
|
12586
|
-
console.error(`${
|
|
12692
|
+
console.error(`${TAG25} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
12587
12693
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
12588
12694
|
}
|
|
12589
12695
|
try {
|
|
@@ -12591,14 +12697,14 @@ async function completeConsent(args) {
|
|
|
12591
12697
|
try {
|
|
12592
12698
|
pending = JSON.parse(readFileSync24(claimFile, "utf-8"));
|
|
12593
12699
|
} catch {
|
|
12594
|
-
console.error(`${
|
|
12700
|
+
console.error(`${TAG25} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
12595
12701
|
return { ok: false, status: 400, message: "Authorization state could not be read.", stateValid: false };
|
|
12596
12702
|
}
|
|
12597
12703
|
if (Date.now() > pending.expiresAt) {
|
|
12598
|
-
console.error(`${
|
|
12704
|
+
console.error(`${TAG25} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
12599
12705
|
return { ok: false, status: 400, message: "Authorization request expired. Generate a new consent link.", stateValid: false };
|
|
12600
12706
|
}
|
|
12601
|
-
console.error(`${
|
|
12707
|
+
console.error(`${TAG25} op=callback state=${state} stateValid=true realmId=${realmId ?? ""}`);
|
|
12602
12708
|
if (!code || !realmId) {
|
|
12603
12709
|
return { ok: false, status: 400, message: "Missing code or realmId in the callback.", stateValid: true };
|
|
12604
12710
|
}
|
|
@@ -12622,12 +12728,12 @@ async function completeConsent(args) {
|
|
|
12622
12728
|
});
|
|
12623
12729
|
const text = await res.text();
|
|
12624
12730
|
if (!res.ok) {
|
|
12625
|
-
console.error(`${
|
|
12731
|
+
console.error(`${TAG25} op=token-exchange realmId=${realmId} persisted=false`);
|
|
12626
12732
|
return { ok: false, status: 502, message: `Token exchange failed (${res.status}). Generate a new consent link to retry.`, stateValid: true };
|
|
12627
12733
|
}
|
|
12628
12734
|
const body = JSON.parse(text);
|
|
12629
12735
|
if (!body.refresh_token) {
|
|
12630
|
-
console.error(`${
|
|
12736
|
+
console.error(`${TAG25} op=token-exchange realmId=${realmId} persisted=false`);
|
|
12631
12737
|
return { ok: false, status: 502, message: "Token exchange returned no refresh token.", stateValid: true };
|
|
12632
12738
|
}
|
|
12633
12739
|
const now = Date.now();
|
|
@@ -12640,7 +12746,7 @@ async function completeConsent(args) {
|
|
|
12640
12746
|
mkdirSync4(join23(accountDir, "secrets"), { recursive: true });
|
|
12641
12747
|
writeFileSync8(storePath(accountDir), JSON.stringify(store2, null, 2), { mode: 384 });
|
|
12642
12748
|
const persisted = JSON.parse(readFileSync24(storePath(accountDir), "utf-8")).connections?.[realmId]?.refreshToken === body.refresh_token;
|
|
12643
|
-
console.error(`${
|
|
12749
|
+
console.error(`${TAG25} op=token-exchange realmId=${realmId} persisted=${persisted}`);
|
|
12644
12750
|
return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
|
|
12645
12751
|
} finally {
|
|
12646
12752
|
rmSync(claimFile, { force: true });
|
|
@@ -12653,7 +12759,7 @@ var app9 = new Hono();
|
|
|
12653
12759
|
app9.get("/callback", async (c) => {
|
|
12654
12760
|
const account = resolveAccount();
|
|
12655
12761
|
if (!account) {
|
|
12656
|
-
console.error(`${
|
|
12762
|
+
console.error(`${TAG25} op=callback stateValid=false realmId= reason=no-account`);
|
|
12657
12763
|
return c.html(page("QuickBooks", "This install has no account configured."), 500);
|
|
12658
12764
|
}
|
|
12659
12765
|
const result = await completeConsent({
|
|
@@ -14845,10 +14951,10 @@ function extractPluginToolDescriptions(pluginDir) {
|
|
|
14845
14951
|
join28(pluginDir, "mcp/src/index.ts")
|
|
14846
14952
|
];
|
|
14847
14953
|
let raw = null;
|
|
14848
|
-
for (const
|
|
14849
|
-
if (!existsSync25(
|
|
14954
|
+
for (const path3 of candidates) {
|
|
14955
|
+
if (!existsSync25(path3)) continue;
|
|
14850
14956
|
try {
|
|
14851
|
-
raw = readFileSync29(
|
|
14957
|
+
raw = readFileSync29(path3, "utf-8");
|
|
14852
14958
|
break;
|
|
14853
14959
|
} catch {
|
|
14854
14960
|
}
|
|
@@ -14938,11 +15044,11 @@ function readEnabledPlugins(accountId) {
|
|
|
14938
15044
|
return /* @__PURE__ */ new Set();
|
|
14939
15045
|
}
|
|
14940
15046
|
}
|
|
14941
|
-
function readFrontmatter(
|
|
14942
|
-
if (!existsSync25(
|
|
15047
|
+
function readFrontmatter(path3) {
|
|
15048
|
+
if (!existsSync25(path3)) return null;
|
|
14943
15049
|
let raw;
|
|
14944
15050
|
try {
|
|
14945
|
-
raw = readFileSync29(
|
|
15051
|
+
raw = readFileSync29(path3, "utf-8");
|
|
14946
15052
|
} catch {
|
|
14947
15053
|
return null;
|
|
14948
15054
|
}
|
|
@@ -15121,7 +15227,7 @@ function resolveTunnelUrl() {
|
|
|
15121
15227
|
if (!state) return null;
|
|
15122
15228
|
return `https://${hostname2}.${state.domain}`;
|
|
15123
15229
|
}
|
|
15124
|
-
var
|
|
15230
|
+
var TAG26 = "[claude-session-manager:wrapper]";
|
|
15125
15231
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
15126
15232
|
const auth = await ensureAuth();
|
|
15127
15233
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -15139,12 +15245,12 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
15139
15245
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
15140
15246
|
const ownerMs = Date.now() - ownerStart;
|
|
15141
15247
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
15142
|
-
console.log(`${
|
|
15248
|
+
console.log(`${TAG26} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
15143
15249
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
15144
15250
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
15145
15251
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
15146
15252
|
const tunnelUrl = resolveTunnelUrl();
|
|
15147
|
-
console.log(`${
|
|
15253
|
+
console.log(`${TAG26} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
15148
15254
|
const upstreamPayload = JSON.stringify({
|
|
15149
15255
|
senderId: args.senderId,
|
|
15150
15256
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -15171,24 +15277,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
15171
15277
|
// unshapely values.
|
|
15172
15278
|
conversationNodeId: args.conversationNodeId
|
|
15173
15279
|
});
|
|
15174
|
-
console.log(`${
|
|
15280
|
+
console.log(`${TAG26} forward-spawn-start managerBase=${managerBase("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
|
|
15175
15281
|
const forwardStart = Date.now();
|
|
15176
15282
|
const upstream = await fetch(`${managerBase("claude-session-manager:wrapper")}/public-spawn`, {
|
|
15177
15283
|
method: "POST",
|
|
15178
15284
|
headers: { "content-type": "application/json" },
|
|
15179
15285
|
body: upstreamPayload
|
|
15180
15286
|
}).catch((err) => {
|
|
15181
|
-
console.error(`${
|
|
15287
|
+
console.error(`${TAG26} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
15182
15288
|
return null;
|
|
15183
15289
|
});
|
|
15184
15290
|
if (!upstream) return {
|
|
15185
15291
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
15186
15292
|
claudeSessionId: null
|
|
15187
15293
|
};
|
|
15188
|
-
console.log(`${
|
|
15294
|
+
console.log(`${TAG26} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
15189
15295
|
if (args.initialMessage) {
|
|
15190
15296
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
15191
|
-
console.log(`${
|
|
15297
|
+
console.log(`${TAG26} initial-message-inlined bytes=${inputBytes}`);
|
|
15192
15298
|
}
|
|
15193
15299
|
const bodyText = await upstream.text().catch(() => "");
|
|
15194
15300
|
let claudeSessionId = null;
|
|
@@ -15223,7 +15329,7 @@ app19.post("/", async (c) => {
|
|
|
15223
15329
|
if (refusal) return refusal;
|
|
15224
15330
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
15225
15331
|
if (!senderId) {
|
|
15226
|
-
console.error(`${
|
|
15332
|
+
console.error(`${TAG26} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
15227
15333
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
15228
15334
|
}
|
|
15229
15335
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -15232,7 +15338,7 @@ app19.post("/", async (c) => {
|
|
|
15232
15338
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
15233
15339
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
15234
15340
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
15235
|
-
console.log(`${
|
|
15341
|
+
console.log(`${TAG26} 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"}`);
|
|
15236
15342
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
15237
15343
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
15238
15344
|
senderId,
|
|
@@ -15251,13 +15357,13 @@ app19.post("/", async (c) => {
|
|
|
15251
15357
|
claudeSessionId,
|
|
15252
15358
|
senderId
|
|
15253
15359
|
);
|
|
15254
|
-
console.log(`${
|
|
15360
|
+
console.log(`${TAG26} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
15255
15361
|
return response;
|
|
15256
15362
|
});
|
|
15257
15363
|
var claude_sessions_default = app19;
|
|
15258
15364
|
|
|
15259
15365
|
// server/routes/admin/log-ingest.ts
|
|
15260
|
-
var
|
|
15366
|
+
var TAG27 = "[log-ingest]";
|
|
15261
15367
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
15262
15368
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
15263
15369
|
var MAX_LINE_BYTES = 4096;
|
|
@@ -15268,7 +15374,7 @@ function isLoopbackAddr2(addr) {
|
|
|
15268
15374
|
app20.post("/", async (c) => {
|
|
15269
15375
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
15270
15376
|
if (!isLoopbackAddr2(remoteAddr)) {
|
|
15271
|
-
console.error(`${
|
|
15377
|
+
console.error(`${TAG27} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
15272
15378
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
15273
15379
|
}
|
|
15274
15380
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -15277,7 +15383,7 @@ app20.post("/", async (c) => {
|
|
|
15277
15383
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
15278
15384
|
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
15279
15385
|
if (offender !== void 0) {
|
|
15280
|
-
console.error(`${
|
|
15386
|
+
console.error(`${TAG27} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
15281
15387
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
15282
15388
|
}
|
|
15283
15389
|
}
|
|
@@ -15319,18 +15425,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
15319
15425
|
]);
|
|
15320
15426
|
var app21 = new Hono();
|
|
15321
15427
|
app21.post("/", async (c) => {
|
|
15322
|
-
const
|
|
15428
|
+
const TAG47 = "[admin:events]";
|
|
15323
15429
|
let body;
|
|
15324
15430
|
try {
|
|
15325
15431
|
body = await c.req.json();
|
|
15326
15432
|
} catch (err) {
|
|
15327
15433
|
const detail = err instanceof Error ? err.message : String(err);
|
|
15328
|
-
console.error(`${
|
|
15434
|
+
console.error(`${TAG47} reject reason=body-not-json detail=${detail}`);
|
|
15329
15435
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
15330
15436
|
}
|
|
15331
15437
|
const event = typeof body.event === "string" ? body.event : "";
|
|
15332
15438
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
15333
|
-
console.error(`${
|
|
15439
|
+
console.error(`${TAG47} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
15334
15440
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
15335
15441
|
}
|
|
15336
15442
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -20382,7 +20488,7 @@ function managerLogFollowUrl(sessionId, opts) {
|
|
|
20382
20488
|
}
|
|
20383
20489
|
|
|
20384
20490
|
// server/routes/admin/linkedin-ingest.ts
|
|
20385
|
-
var
|
|
20491
|
+
var TAG28 = "[linkedin-ingest-route]";
|
|
20386
20492
|
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}$/;
|
|
20387
20493
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
20388
20494
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -20486,29 +20592,29 @@ app39.post("/", requireAdminSession, async (c) => {
|
|
|
20486
20592
|
try {
|
|
20487
20593
|
body = await c.req.json();
|
|
20488
20594
|
} catch {
|
|
20489
|
-
console.error(
|
|
20595
|
+
console.error(TAG28 + " rejected status=400 reason=schema:body-not-json");
|
|
20490
20596
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
20491
20597
|
}
|
|
20492
20598
|
const v = validate(body);
|
|
20493
20599
|
if (!v.ok) {
|
|
20494
|
-
console.error(
|
|
20600
|
+
console.error(TAG28 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
20495
20601
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
20496
20602
|
}
|
|
20497
20603
|
const envelope = v.envelope;
|
|
20498
20604
|
const cacheKey = c.var.cacheKey ?? "";
|
|
20499
20605
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
20500
20606
|
if (!senderId) {
|
|
20501
|
-
console.error(
|
|
20607
|
+
console.error(TAG28 + " rejected status=500 reason=admin-account-not-resolved");
|
|
20502
20608
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
20503
20609
|
}
|
|
20504
20610
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
20505
20611
|
console.log(
|
|
20506
|
-
|
|
20612
|
+
TAG28 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
20507
20613
|
);
|
|
20508
20614
|
const initialMessage = buildInitialMessage(envelope);
|
|
20509
20615
|
const spawnStart = Date.now();
|
|
20510
20616
|
const sessionId = randomUUID13();
|
|
20511
|
-
console.log(
|
|
20617
|
+
console.log(TAG28 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
20512
20618
|
const spawned = await managerRcSpawn({
|
|
20513
20619
|
sessionId,
|
|
20514
20620
|
initialMessage,
|
|
@@ -20517,12 +20623,12 @@ app39.post("/", requireAdminSession, async (c) => {
|
|
|
20517
20623
|
});
|
|
20518
20624
|
if ("error" in spawned) {
|
|
20519
20625
|
console.error(
|
|
20520
|
-
|
|
20626
|
+
TAG28 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
20521
20627
|
);
|
|
20522
20628
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
20523
20629
|
}
|
|
20524
20630
|
console.log(
|
|
20525
|
-
|
|
20631
|
+
TAG28 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
20526
20632
|
);
|
|
20527
20633
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
20528
20634
|
});
|
|
@@ -20530,7 +20636,7 @@ var linkedin_ingest_default = app39;
|
|
|
20530
20636
|
|
|
20531
20637
|
// server/routes/admin/post-turn-context.ts
|
|
20532
20638
|
import neo4j3 from "neo4j-driver";
|
|
20533
|
-
var
|
|
20639
|
+
var TAG29 = "[post-turn-context]";
|
|
20534
20640
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
20535
20641
|
"embedding",
|
|
20536
20642
|
"passwordHash",
|
|
@@ -20572,7 +20678,7 @@ var app40 = new Hono();
|
|
|
20572
20678
|
app40.get("/", async (c) => {
|
|
20573
20679
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
20574
20680
|
if (!isLoopbackAddr3(remoteAddr)) {
|
|
20575
|
-
console.error(`${
|
|
20681
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
20576
20682
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
20577
20683
|
}
|
|
20578
20684
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -20581,7 +20687,7 @@ app40.get("/", async (c) => {
|
|
|
20581
20687
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
20582
20688
|
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
20583
20689
|
if (offender !== void 0) {
|
|
20584
|
-
console.error(`${
|
|
20690
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
20585
20691
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
20586
20692
|
}
|
|
20587
20693
|
}
|
|
@@ -20613,7 +20719,7 @@ app40.get("/", async (c) => {
|
|
|
20613
20719
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
20614
20720
|
const total = Date.now() - started;
|
|
20615
20721
|
console.log(
|
|
20616
|
-
`${
|
|
20722
|
+
`${TAG29} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
20617
20723
|
);
|
|
20618
20724
|
return c.json({
|
|
20619
20725
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -20622,7 +20728,7 @@ app40.get("/", async (c) => {
|
|
|
20622
20728
|
const elapsed = Date.now() - started;
|
|
20623
20729
|
const message = err instanceof Error ? err.message : String(err);
|
|
20624
20730
|
console.error(
|
|
20625
|
-
`${
|
|
20731
|
+
`${TAG29} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
20626
20732
|
);
|
|
20627
20733
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
20628
20734
|
} finally {
|
|
@@ -20735,7 +20841,7 @@ function formatPreviousContext(writes) {
|
|
|
20735
20841
|
}
|
|
20736
20842
|
|
|
20737
20843
|
// server/routes/admin/public-session-context.ts
|
|
20738
|
-
var
|
|
20844
|
+
var TAG30 = "[public-session-context]";
|
|
20739
20845
|
function isLoopbackAddr4(addr) {
|
|
20740
20846
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
20741
20847
|
}
|
|
@@ -20743,7 +20849,7 @@ var app41 = new Hono();
|
|
|
20743
20849
|
app41.get("/", async (c) => {
|
|
20744
20850
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
20745
20851
|
if (!isLoopbackAddr4(remoteAddr)) {
|
|
20746
|
-
console.error(`${
|
|
20852
|
+
console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
20747
20853
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
20748
20854
|
}
|
|
20749
20855
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -20752,7 +20858,7 @@ app41.get("/", async (c) => {
|
|
|
20752
20858
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
20753
20859
|
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
20754
20860
|
if (offender !== void 0) {
|
|
20755
|
-
console.error(`${
|
|
20861
|
+
console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
20756
20862
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
20757
20863
|
}
|
|
20758
20864
|
}
|
|
@@ -20766,14 +20872,14 @@ app41.get("/", async (c) => {
|
|
|
20766
20872
|
const writes = await fetchSliceWrites(session, sliceToken, accountId);
|
|
20767
20873
|
const total = Date.now() - started;
|
|
20768
20874
|
console.log(
|
|
20769
|
-
`${
|
|
20875
|
+
`${TAG30} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
20770
20876
|
);
|
|
20771
20877
|
return c.json({ writes });
|
|
20772
20878
|
} catch (err) {
|
|
20773
20879
|
const elapsed = Date.now() - started;
|
|
20774
20880
|
const message = err instanceof Error ? err.message : String(err);
|
|
20775
20881
|
console.error(
|
|
20776
|
-
`${
|
|
20882
|
+
`${TAG30} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
20777
20883
|
);
|
|
20778
20884
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
20779
20885
|
} finally {
|
|
@@ -20792,7 +20898,7 @@ function getWebchatGateway() {
|
|
|
20792
20898
|
}
|
|
20793
20899
|
|
|
20794
20900
|
// server/routes/admin/public-session-exit.ts
|
|
20795
|
-
var
|
|
20901
|
+
var TAG31 = "[public-session-exit-route]";
|
|
20796
20902
|
function isLoopbackAddr5(addr) {
|
|
20797
20903
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
20798
20904
|
}
|
|
@@ -20800,7 +20906,7 @@ var app42 = new Hono();
|
|
|
20800
20906
|
app42.post("/", async (c) => {
|
|
20801
20907
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
20802
20908
|
if (!isLoopbackAddr5(remoteAddr)) {
|
|
20803
|
-
console.error(`${
|
|
20909
|
+
console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
20804
20910
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
20805
20911
|
}
|
|
20806
20912
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -20809,7 +20915,7 @@ app42.post("/", async (c) => {
|
|
|
20809
20915
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
20810
20916
|
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
20811
20917
|
if (offender !== void 0) {
|
|
20812
|
-
console.error(`${
|
|
20918
|
+
console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
20813
20919
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
20814
20920
|
}
|
|
20815
20921
|
}
|
|
@@ -20823,7 +20929,7 @@ app42.post("/", async (c) => {
|
|
|
20823
20929
|
if (!sessionId) return c.json({ error: "sessionId required" }, 400);
|
|
20824
20930
|
const gateway = getWebchatGateway();
|
|
20825
20931
|
if (!gateway) {
|
|
20826
|
-
console.error(`${
|
|
20932
|
+
console.error(`${TAG31} reject reason=gateway-unset sessionId=${sessionId.slice(0, 8)}`);
|
|
20827
20933
|
return c.json({ error: "webchat-gateway-unset" }, 503);
|
|
20828
20934
|
}
|
|
20829
20935
|
gateway.handlePublicSessionExit(sessionId);
|
|
@@ -20832,7 +20938,7 @@ app42.post("/", async (c) => {
|
|
|
20832
20938
|
var public_session_exit_default = app42;
|
|
20833
20939
|
|
|
20834
20940
|
// server/routes/admin/access-session-evict.ts
|
|
20835
|
-
var
|
|
20941
|
+
var TAG32 = "[access-session-evict]";
|
|
20836
20942
|
function isLoopbackAddr6(addr) {
|
|
20837
20943
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
20838
20944
|
}
|
|
@@ -20840,7 +20946,7 @@ var app43 = new Hono();
|
|
|
20840
20946
|
app43.post("/", async (c) => {
|
|
20841
20947
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
20842
20948
|
if (!isLoopbackAddr6(remoteAddr)) {
|
|
20843
|
-
console.error(`${
|
|
20949
|
+
console.error(`${TAG32} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
20844
20950
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
20845
20951
|
}
|
|
20846
20952
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -20849,7 +20955,7 @@ app43.post("/", async (c) => {
|
|
|
20849
20955
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
20850
20956
|
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
20851
20957
|
if (offender !== void 0) {
|
|
20852
|
-
console.error(`${
|
|
20958
|
+
console.error(`${TAG32} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
20853
20959
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
20854
20960
|
}
|
|
20855
20961
|
}
|
|
@@ -20862,13 +20968,13 @@ app43.post("/", async (c) => {
|
|
|
20862
20968
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
20863
20969
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
20864
20970
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
20865
|
-
console.log(`${
|
|
20971
|
+
console.log(`${TAG32} grantId=${grantId} dropped=${dropped}`);
|
|
20866
20972
|
return c.json({ ok: true, dropped });
|
|
20867
20973
|
});
|
|
20868
20974
|
var access_session_evict_default = app43;
|
|
20869
20975
|
|
|
20870
20976
|
// server/routes/admin/enrol-person.ts
|
|
20871
|
-
var
|
|
20977
|
+
var TAG33 = "[enrol]";
|
|
20872
20978
|
function isLoopbackAddr7(addr) {
|
|
20873
20979
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
20874
20980
|
}
|
|
@@ -20877,7 +20983,7 @@ var app44 = new Hono();
|
|
|
20877
20983
|
app44.post("/", async (c) => {
|
|
20878
20984
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
20879
20985
|
if (!isLoopbackAddr7(remoteAddr)) {
|
|
20880
|
-
console.error(`${
|
|
20986
|
+
console.error(`${TAG33} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
20881
20987
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
20882
20988
|
}
|
|
20883
20989
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -20886,7 +20992,7 @@ app44.post("/", async (c) => {
|
|
|
20886
20992
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
20887
20993
|
const offender = tokens.find((t) => !isLoopbackAddr7(t));
|
|
20888
20994
|
if (offender !== void 0) {
|
|
20889
|
-
console.error(`${
|
|
20995
|
+
console.error(`${TAG33} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
20890
20996
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
20891
20997
|
}
|
|
20892
20998
|
}
|
|
@@ -20911,7 +21017,7 @@ app44.post("/", async (c) => {
|
|
|
20911
21017
|
}
|
|
20912
21018
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
20913
21019
|
if (!accountId) {
|
|
20914
|
-
console.error(`${
|
|
21020
|
+
console.error(`${TAG33} op=person result=no-account-id`);
|
|
20915
21021
|
return c.json({ error: "no-account-id" }, 500);
|
|
20916
21022
|
}
|
|
20917
21023
|
let personId;
|
|
@@ -20919,7 +21025,7 @@ app44.post("/", async (c) => {
|
|
|
20919
21025
|
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
20920
21026
|
} catch (err) {
|
|
20921
21027
|
console.error(
|
|
20922
|
-
`${
|
|
21028
|
+
`${TAG33} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
20923
21029
|
);
|
|
20924
21030
|
return c.json({ error: "enrol-failed" }, 500);
|
|
20925
21031
|
}
|
|
@@ -20929,11 +21035,11 @@ app44.post("/", async (c) => {
|
|
|
20929
21035
|
if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
|
|
20930
21036
|
} catch (err) {
|
|
20931
21037
|
console.error(
|
|
20932
|
-
`${
|
|
21038
|
+
`${TAG33} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
20933
21039
|
);
|
|
20934
21040
|
}
|
|
20935
21041
|
console.log(
|
|
20936
|
-
`${
|
|
21042
|
+
`${TAG33} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
|
|
20937
21043
|
);
|
|
20938
21044
|
return c.json({ personId, grant }, 200);
|
|
20939
21045
|
});
|
|
@@ -21089,33 +21195,33 @@ var REQUIRED_KEYS = [
|
|
|
21089
21195
|
"weekly"
|
|
21090
21196
|
];
|
|
21091
21197
|
function readAvailabilityConfig(accountDir) {
|
|
21092
|
-
const
|
|
21198
|
+
const path3 = join33(accountDir, AVAILABILITY_FILENAME);
|
|
21093
21199
|
let raw;
|
|
21094
21200
|
try {
|
|
21095
|
-
raw = readFileSync33(
|
|
21201
|
+
raw = readFileSync33(path3, "utf-8");
|
|
21096
21202
|
} catch {
|
|
21097
|
-
throw new Error(`availability not configured: ${
|
|
21203
|
+
throw new Error(`availability not configured: ${path3} not found`);
|
|
21098
21204
|
}
|
|
21099
21205
|
let parsed;
|
|
21100
21206
|
try {
|
|
21101
21207
|
parsed = JSON.parse(raw);
|
|
21102
21208
|
} catch (err) {
|
|
21103
|
-
throw new Error(`invalid JSON in ${
|
|
21209
|
+
throw new Error(`invalid JSON in ${path3}: ${err.message}`);
|
|
21104
21210
|
}
|
|
21105
21211
|
const cfg = parsed;
|
|
21106
21212
|
for (const key of REQUIRED_KEYS) {
|
|
21107
21213
|
if (cfg[key] === void 0 || cfg[key] === null) {
|
|
21108
|
-
throw new Error(`availability config ${
|
|
21214
|
+
throw new Error(`availability config ${path3} missing required key: ${key}`);
|
|
21109
21215
|
}
|
|
21110
21216
|
}
|
|
21111
21217
|
if (cfg.daysAhead !== void 0 && cfg.daysAhead !== null) {
|
|
21112
21218
|
if (typeof cfg.daysAhead !== "number" || !Number.isInteger(cfg.daysAhead) || cfg.daysAhead < 1) {
|
|
21113
|
-
throw new Error(`availability config ${
|
|
21219
|
+
throw new Error(`availability config ${path3} invalid daysAhead: must be a positive integer`);
|
|
21114
21220
|
}
|
|
21115
21221
|
}
|
|
21116
21222
|
if (cfg.allowMultiSlot !== void 0 && cfg.allowMultiSlot !== null) {
|
|
21117
21223
|
if (typeof cfg.allowMultiSlot !== "boolean") {
|
|
21118
|
-
throw new Error(`availability config ${
|
|
21224
|
+
throw new Error(`availability config ${path3} invalid allowMultiSlot: must be a boolean`);
|
|
21119
21225
|
}
|
|
21120
21226
|
}
|
|
21121
21227
|
return cfg;
|
|
@@ -21487,7 +21593,7 @@ function toNum(v) {
|
|
|
21487
21593
|
}
|
|
21488
21594
|
|
|
21489
21595
|
// server/routes/admin/tasks-list.ts
|
|
21490
|
-
var
|
|
21596
|
+
var TAG34 = "[task-timer]";
|
|
21491
21597
|
var COMPLETED = /* @__PURE__ */ new Set(["completed"]);
|
|
21492
21598
|
var HIDDEN_FROM_OPEN = /* @__PURE__ */ new Set(["completed", "cancelled"]);
|
|
21493
21599
|
var app47 = new Hono();
|
|
@@ -21495,7 +21601,7 @@ app47.get("/", requireAdminSession, async (c) => {
|
|
|
21495
21601
|
const cacheKey = c.var.cacheKey;
|
|
21496
21602
|
const accountId = getAccountIdForSession(cacheKey);
|
|
21497
21603
|
if (!accountId) {
|
|
21498
|
-
console.error(`${
|
|
21604
|
+
console.error(`${TAG34} op=list auth-rejected reason="no account for session"`);
|
|
21499
21605
|
return c.json({ error: "Account not found for session" }, 401);
|
|
21500
21606
|
}
|
|
21501
21607
|
const session = getSession();
|
|
@@ -21533,10 +21639,10 @@ app47.get("/", requireAdminSession, async (c) => {
|
|
|
21533
21639
|
if (COMPLETED.has(status)) completed.push(row);
|
|
21534
21640
|
else if (!HIDDEN_FROM_OPEN.has(status)) open.push(row);
|
|
21535
21641
|
}
|
|
21536
|
-
console.log(`${
|
|
21642
|
+
console.log(`${TAG34} op=list account=${accountId} open=${open.length} completed=${completed.length}`);
|
|
21537
21643
|
return c.json({ open, completed });
|
|
21538
21644
|
} catch (err) {
|
|
21539
|
-
console.error(`${
|
|
21645
|
+
console.error(`${TAG34} op=list error="${err instanceof Error ? err.message : String(err)}"`);
|
|
21540
21646
|
return c.json({ error: "tasks-list-failed" }, 500);
|
|
21541
21647
|
} finally {
|
|
21542
21648
|
await session.close();
|
|
@@ -21546,7 +21652,7 @@ var tasks_list_default = app47;
|
|
|
21546
21652
|
|
|
21547
21653
|
// server/routes/admin/task-timer-start.ts
|
|
21548
21654
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
21549
|
-
var
|
|
21655
|
+
var TAG35 = "[task-timer]";
|
|
21550
21656
|
var app48 = new Hono();
|
|
21551
21657
|
app48.post("/", requireAdminSession, async (c) => {
|
|
21552
21658
|
const cacheKey = c.var.cacheKey;
|
|
@@ -21568,7 +21674,7 @@ app48.post("/", requireAdminSession, async (c) => {
|
|
|
21568
21674
|
{ taskId, accountId }
|
|
21569
21675
|
);
|
|
21570
21676
|
if (target.records.length === 0) {
|
|
21571
|
-
console.error(`${
|
|
21677
|
+
console.error(`${TAG35} op=start taskId=${taskId} accountId=${accountId} result=task-not-found`);
|
|
21572
21678
|
return c.json({ error: "task-not-found" }, 404);
|
|
21573
21679
|
}
|
|
21574
21680
|
const open = await session.run(
|
|
@@ -21580,7 +21686,7 @@ app48.post("/", requireAdminSession, async (c) => {
|
|
|
21580
21686
|
const priorEntryId = open.records[0]?.get("entryId");
|
|
21581
21687
|
const priorStartedAt = open.records[0]?.get("startedAt");
|
|
21582
21688
|
if (priorEntryId) {
|
|
21583
|
-
console.log(`${
|
|
21689
|
+
console.log(`${TAG35} op=start taskId=${taskId} accountId=${accountId} result=already-running`);
|
|
21584
21690
|
return c.json({ ok: true, running: true, entryId: priorEntryId, startedAt: priorStartedAt });
|
|
21585
21691
|
}
|
|
21586
21692
|
const entryId = randomUUID14();
|
|
@@ -21590,10 +21696,10 @@ app48.post("/", requireAdminSession, async (c) => {
|
|
|
21590
21696
|
CREATE (te)-[:LOGGED_AGAINST]->(t)`,
|
|
21591
21697
|
{ taskId, accountId, entryId, now }
|
|
21592
21698
|
);
|
|
21593
|
-
console.log(`${
|
|
21699
|
+
console.log(`${TAG35} op=opened taskId=${taskId} entryId=${entryId} startedAt=${now}`);
|
|
21594
21700
|
return c.json({ ok: true, running: true, entryId, startedAt: now });
|
|
21595
21701
|
} catch (err) {
|
|
21596
|
-
console.error(`${
|
|
21702
|
+
console.error(`${TAG35} op=start error="${err instanceof Error ? err.message : String(err)}"`);
|
|
21597
21703
|
return c.json({ error: "task-timer-start-failed" }, 500);
|
|
21598
21704
|
} finally {
|
|
21599
21705
|
await session.close();
|
|
@@ -21603,7 +21709,7 @@ var task_timer_start_default = app48;
|
|
|
21603
21709
|
|
|
21604
21710
|
// server/routes/admin/task-timer-stop.ts
|
|
21605
21711
|
import neo4j5 from "neo4j-driver";
|
|
21606
|
-
var
|
|
21712
|
+
var TAG36 = "[task-timer]";
|
|
21607
21713
|
var app49 = new Hono();
|
|
21608
21714
|
app49.post("/", requireAdminSession, async (c) => {
|
|
21609
21715
|
const cacheKey = c.var.cacheKey;
|
|
@@ -21635,7 +21741,7 @@ app49.post("/", requireAdminSession, async (c) => {
|
|
|
21635
21741
|
{ taskId, accountId }
|
|
21636
21742
|
);
|
|
21637
21743
|
const secondsLogged2 = toNum(cur.records[0]?.get("secondsLogged"));
|
|
21638
|
-
console.log(`${
|
|
21744
|
+
console.log(`${TAG36} op=stop taskId=${taskId} entryId=none seconds=0 totalAfter=${secondsLogged2}`);
|
|
21639
21745
|
return c.json({ ok: true, running: false, seconds: 0, secondsLogged: secondsLogged2 });
|
|
21640
21746
|
}
|
|
21641
21747
|
const seconds = secondsBetween(startedAt, now);
|
|
@@ -21658,11 +21764,11 @@ app49.post("/", requireAdminSession, async (c) => {
|
|
|
21658
21764
|
const sumEntries = toNum(verify.records[0]?.get("sumEntries"));
|
|
21659
21765
|
const sumAdjustments = toNum(verify.records[0]?.get("sumAdjustments"));
|
|
21660
21766
|
const secondsLogged = toNum(verify.records[0]?.get("secondsLogged"));
|
|
21661
|
-
console.log(`${
|
|
21662
|
-
console.log(`${
|
|
21767
|
+
console.log(`${TAG36} op=stop taskId=${taskId} entryId=${entryId} seconds=${seconds} totalAfter=${secondsLogged}`);
|
|
21768
|
+
console.log(`${TAG36} op=persisted taskId=${taskId} entryId=${entryId} sumEntries=${sumEntries} sumAdjustments=${sumAdjustments} secondsLogged=${secondsLogged}`);
|
|
21663
21769
|
return c.json({ ok: true, running: false, seconds, secondsLogged });
|
|
21664
21770
|
} catch (err) {
|
|
21665
|
-
console.error(`${
|
|
21771
|
+
console.error(`${TAG36} op=stop error="${err instanceof Error ? err.message : String(err)}"`);
|
|
21666
21772
|
return c.json({ error: "task-timer-stop-failed" }, 500);
|
|
21667
21773
|
} finally {
|
|
21668
21774
|
await session.close();
|
|
@@ -21671,7 +21777,7 @@ app49.post("/", requireAdminSession, async (c) => {
|
|
|
21671
21777
|
var task_timer_stop_default = app49;
|
|
21672
21778
|
|
|
21673
21779
|
// server/routes/admin/task-complete.ts
|
|
21674
|
-
var
|
|
21780
|
+
var TAG37 = "[task-timer]";
|
|
21675
21781
|
var app50 = new Hono();
|
|
21676
21782
|
app50.post("/", requireAdminSession, async (c) => {
|
|
21677
21783
|
const cacheKey = c.var.cacheKey;
|
|
@@ -21698,10 +21804,10 @@ app50.post("/", requireAdminSession, async (c) => {
|
|
|
21698
21804
|
return c.json({ error: "task-not-found" }, 404);
|
|
21699
21805
|
}
|
|
21700
21806
|
const secondsLogged = toNum(result.records[0].get("secondsLogged"));
|
|
21701
|
-
console.log(`${
|
|
21807
|
+
console.log(`${TAG37} op=complete taskId=${taskId} status=completed secondsLogged=${secondsLogged}`);
|
|
21702
21808
|
return c.json({ ok: true, status: "completed", secondsLogged });
|
|
21703
21809
|
} catch (err) {
|
|
21704
|
-
console.error(`${
|
|
21810
|
+
console.error(`${TAG37} op=complete error="${err instanceof Error ? err.message : String(err)}"`);
|
|
21705
21811
|
return c.json({ error: "task-complete-failed" }, 500);
|
|
21706
21812
|
} finally {
|
|
21707
21813
|
await session.close();
|
|
@@ -21712,7 +21818,7 @@ var task_complete_default = app50;
|
|
|
21712
21818
|
// server/routes/admin/task-time-adjust.ts
|
|
21713
21819
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
21714
21820
|
import neo4j6 from "neo4j-driver";
|
|
21715
|
-
var
|
|
21821
|
+
var TAG38 = "[task-time-adjust]";
|
|
21716
21822
|
var app51 = new Hono();
|
|
21717
21823
|
app51.post("/", requireAdminSession, async (c) => {
|
|
21718
21824
|
const cacheKey = c.var.cacheKey;
|
|
@@ -21722,22 +21828,22 @@ app51.post("/", requireAdminSession, async (c) => {
|
|
|
21722
21828
|
try {
|
|
21723
21829
|
body = await c.req.json();
|
|
21724
21830
|
} catch {
|
|
21725
|
-
console.error(`${
|
|
21831
|
+
console.error(`${TAG38} op=reject reason=bad-json`);
|
|
21726
21832
|
return c.json({ error: "invalid-json" }, 400);
|
|
21727
21833
|
}
|
|
21728
21834
|
const taskId = typeof body.taskId === "string" ? body.taskId : "";
|
|
21729
21835
|
if (!taskId) {
|
|
21730
|
-
console.error(`${
|
|
21836
|
+
console.error(`${TAG38} op=reject reason=no-task`);
|
|
21731
21837
|
return c.json({ error: "taskId required" }, 400);
|
|
21732
21838
|
}
|
|
21733
21839
|
const newSeconds = body.newSeconds;
|
|
21734
21840
|
if (typeof newSeconds !== "number" || !Number.isInteger(newSeconds) || newSeconds < 0) {
|
|
21735
|
-
console.error(`${
|
|
21841
|
+
console.error(`${TAG38} op=reject reason=invalid-seconds taskId=${taskId} accountId=${accountId}`);
|
|
21736
21842
|
return c.json({ error: "newSeconds must be a non-negative integer" }, 400);
|
|
21737
21843
|
}
|
|
21738
21844
|
const adjustmentId = randomUUID15();
|
|
21739
21845
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21740
|
-
console.log(`${
|
|
21846
|
+
console.log(`${TAG38} op=request adjustmentId=${adjustmentId} taskId=${taskId} accountId=${accountId} newSeconds=${newSeconds}`);
|
|
21741
21847
|
const session = getSession();
|
|
21742
21848
|
try {
|
|
21743
21849
|
const guard = await session.run(
|
|
@@ -21747,12 +21853,12 @@ app51.post("/", requireAdminSession, async (c) => {
|
|
|
21747
21853
|
{ taskId, accountId }
|
|
21748
21854
|
);
|
|
21749
21855
|
if (guard.records.length === 0) {
|
|
21750
|
-
console.error(`${
|
|
21856
|
+
console.error(`${TAG38} op=reject reason=no-task adjustmentId=${adjustmentId} taskId=${taskId} accountId=${accountId}`);
|
|
21751
21857
|
return c.json({ error: "task-not-found" }, 404);
|
|
21752
21858
|
}
|
|
21753
21859
|
const openCount = toNum(guard.records[0].get("openCount"));
|
|
21754
21860
|
if (openCount > 0) {
|
|
21755
|
-
console.error(`${
|
|
21861
|
+
console.error(`${TAG38} op=reject reason=running adjustmentId=${adjustmentId} taskId=${taskId} accountId=${accountId}`);
|
|
21756
21862
|
return c.json({ error: "task-timer-running" }, 409);
|
|
21757
21863
|
}
|
|
21758
21864
|
const previousSeconds = toNum(guard.records[0].get("previousSeconds"));
|
|
@@ -21778,10 +21884,10 @@ app51.post("/", requireAdminSession, async (c) => {
|
|
|
21778
21884
|
}
|
|
21779
21885
|
);
|
|
21780
21886
|
const secondsLoggedAfter = toNum(write.records[0]?.get("secondsLoggedAfter"));
|
|
21781
|
-
console.log(`${
|
|
21887
|
+
console.log(`${TAG38} op=persisted adjustmentId=${adjustmentId} previousSeconds=${previousSeconds} newSeconds=${newSeconds} delta=${delta} secondsLoggedAfter=${secondsLoggedAfter}`);
|
|
21782
21888
|
return c.json({ ok: true, secondsLogged: secondsLoggedAfter });
|
|
21783
21889
|
} catch (err) {
|
|
21784
|
-
console.error(`${
|
|
21890
|
+
console.error(`${TAG38} op=error adjustmentId=${adjustmentId} taskId=${taskId} error="${err instanceof Error ? err.message : String(err)}"`);
|
|
21785
21891
|
return c.json({ error: "task-time-adjust-failed" }, 500);
|
|
21786
21892
|
} finally {
|
|
21787
21893
|
await session.close();
|
|
@@ -21835,7 +21941,7 @@ app52.route("/task-time-adjust", task_time_adjust_default);
|
|
|
21835
21941
|
var admin_default = app52;
|
|
21836
21942
|
|
|
21837
21943
|
// server/routes/access/verify-token.ts
|
|
21838
|
-
var
|
|
21944
|
+
var TAG39 = "[access-verify]";
|
|
21839
21945
|
var MINT_TAG = "[access-session-mint]";
|
|
21840
21946
|
var COOKIE_NAME = "__access_session";
|
|
21841
21947
|
var app53 = new Hono();
|
|
@@ -21854,39 +21960,39 @@ app53.post("/", async (c) => {
|
|
|
21854
21960
|
}
|
|
21855
21961
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
21856
21962
|
if (rateMsg) {
|
|
21857
|
-
console.error(`${
|
|
21963
|
+
console.error(`${TAG39} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
21858
21964
|
return c.json({ error: rateMsg }, 429);
|
|
21859
21965
|
}
|
|
21860
21966
|
const grant = await findGrantByMagicToken(token);
|
|
21861
21967
|
if (!grant) {
|
|
21862
21968
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
21863
|
-
console.error(`${
|
|
21969
|
+
console.error(`${TAG39} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
21864
21970
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
21865
21971
|
}
|
|
21866
21972
|
if (grant.agentSlug !== agentSlug) {
|
|
21867
21973
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
21868
21974
|
console.error(
|
|
21869
|
-
`${
|
|
21975
|
+
`${TAG39} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
21870
21976
|
);
|
|
21871
21977
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
21872
21978
|
}
|
|
21873
21979
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
21874
21980
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
21875
21981
|
console.error(
|
|
21876
|
-
`${
|
|
21982
|
+
`${TAG39} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
21877
21983
|
);
|
|
21878
21984
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
21879
21985
|
}
|
|
21880
21986
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
21881
21987
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
21882
21988
|
console.error(
|
|
21883
|
-
`${
|
|
21989
|
+
`${TAG39} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
21884
21990
|
);
|
|
21885
21991
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
21886
21992
|
}
|
|
21887
21993
|
if (!grant.sliceToken) {
|
|
21888
21994
|
console.error(
|
|
21889
|
-
`${
|
|
21995
|
+
`${TAG39} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
21890
21996
|
);
|
|
21891
21997
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
21892
21998
|
}
|
|
@@ -21902,12 +22008,12 @@ app53.post("/", async (c) => {
|
|
|
21902
22008
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
21903
22009
|
} catch (err) {
|
|
21904
22010
|
console.error(
|
|
21905
|
-
`${
|
|
22011
|
+
`${TAG39} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
21906
22012
|
);
|
|
21907
22013
|
return c.json({ error: "verification-failed" }, 500);
|
|
21908
22014
|
}
|
|
21909
22015
|
clearAccessRateLimit(ip, agentSlug);
|
|
21910
|
-
console.log(`${
|
|
22016
|
+
console.log(`${TAG39} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
21911
22017
|
console.log(
|
|
21912
22018
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
21913
22019
|
);
|
|
@@ -21983,7 +22089,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
21983
22089
|
}
|
|
21984
22090
|
|
|
21985
22091
|
// server/routes/access/request-magic-link.ts
|
|
21986
|
-
var
|
|
22092
|
+
var TAG40 = "[access-request-link]";
|
|
21987
22093
|
var app54 = new Hono();
|
|
21988
22094
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
21989
22095
|
app54.post("/", async (c) => {
|
|
@@ -22000,18 +22106,18 @@ app54.post("/", async (c) => {
|
|
|
22000
22106
|
}
|
|
22001
22107
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
22002
22108
|
if (rateMsg) {
|
|
22003
|
-
console.error(`${
|
|
22109
|
+
console.error(`${TAG40} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
22004
22110
|
return c.json({ error: rateMsg }, 429);
|
|
22005
22111
|
}
|
|
22006
22112
|
recordRequestLinkAttempt(contactValue);
|
|
22007
22113
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
22008
22114
|
if (!accountId) {
|
|
22009
|
-
console.error(`${
|
|
22115
|
+
console.error(`${TAG40} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
22010
22116
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
22011
22117
|
}
|
|
22012
22118
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
22013
22119
|
if (!grant) {
|
|
22014
|
-
console.log(`${
|
|
22120
|
+
console.log(`${TAG40} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
22015
22121
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
22016
22122
|
}
|
|
22017
22123
|
let token;
|
|
@@ -22019,7 +22125,7 @@ app54.post("/", async (c) => {
|
|
|
22019
22125
|
token = await generateNewMagicToken(grant.grantId);
|
|
22020
22126
|
} catch (err) {
|
|
22021
22127
|
console.error(
|
|
22022
|
-
`${
|
|
22128
|
+
`${TAG40} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
22023
22129
|
);
|
|
22024
22130
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
22025
22131
|
}
|
|
@@ -22051,12 +22157,12 @@ app54.post("/", async (c) => {
|
|
|
22051
22157
|
});
|
|
22052
22158
|
if (!sendResult.ok) {
|
|
22053
22159
|
console.error(
|
|
22054
|
-
`${
|
|
22160
|
+
`${TAG40} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
22055
22161
|
);
|
|
22056
22162
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
22057
22163
|
}
|
|
22058
22164
|
console.log(
|
|
22059
|
-
`${
|
|
22165
|
+
`${TAG40} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
22060
22166
|
);
|
|
22061
22167
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
22062
22168
|
});
|
|
@@ -23118,7 +23224,7 @@ app61.get("/free-busy", async (c) => {
|
|
|
23118
23224
|
var calendar_public_default = app61;
|
|
23119
23225
|
|
|
23120
23226
|
// app/lib/timeentry-census.ts
|
|
23121
|
-
var
|
|
23227
|
+
var TAG41 = "[timeentry-census]";
|
|
23122
23228
|
function reconcileTimeEntries(rows, now, horizonMs) {
|
|
23123
23229
|
const openEntries = rows.openEntries.length;
|
|
23124
23230
|
let oldestOpenAgeMs = 0;
|
|
@@ -23158,12 +23264,12 @@ async function runTimeEntryCensusOnce(session, now, horizonMs) {
|
|
|
23158
23264
|
sumAdjustments: toNum(r.get("sumAdjustments"))
|
|
23159
23265
|
}));
|
|
23160
23266
|
const finding = reconcileTimeEntries({ openEntries, taskSums }, now, horizonMs);
|
|
23161
|
-
const line = `${
|
|
23267
|
+
const line = `${TAG41} openEntries=${finding.openEntries} oldestOpenAgeMin=${finding.oldestOpenAgeMin} maxOpenPerAccount=${finding.maxOpenPerAccount} driftTasks=${finding.driftTasks}`;
|
|
23162
23268
|
if (finding.alarm) console.error(`${line} alarm=true`);
|
|
23163
23269
|
else console.log(line);
|
|
23164
23270
|
return finding;
|
|
23165
23271
|
} catch (err) {
|
|
23166
|
-
console.error(`${
|
|
23272
|
+
console.error(`${TAG41} error="${err instanceof Error ? err.message : String(err)}"`);
|
|
23167
23273
|
return null;
|
|
23168
23274
|
}
|
|
23169
23275
|
}
|
|
@@ -23177,13 +23283,13 @@ function startTimeEntryCensus(openSession, opts = {}) {
|
|
|
23177
23283
|
try {
|
|
23178
23284
|
session = openSession();
|
|
23179
23285
|
} catch (err) {
|
|
23180
|
-
console.error(`${
|
|
23286
|
+
console.error(`${TAG41} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`);
|
|
23181
23287
|
return;
|
|
23182
23288
|
}
|
|
23183
23289
|
try {
|
|
23184
23290
|
await runTimeEntryCensusOnce(session, Date.now(), horizonMs);
|
|
23185
23291
|
} catch (err) {
|
|
23186
|
-
console.error(`${
|
|
23292
|
+
console.error(`${TAG41} tick-failed error="${err instanceof Error ? err.message : String(err)}"`);
|
|
23187
23293
|
} finally {
|
|
23188
23294
|
try {
|
|
23189
23295
|
await session.close();
|
|
@@ -23555,10 +23661,10 @@ var defaultResolveName = async (_accountId, userId) => {
|
|
|
23555
23661
|
const res = await loadAdminUserName(userId);
|
|
23556
23662
|
return res.source === "neo4j" && res.joined.length > 0 ? res.joined : null;
|
|
23557
23663
|
};
|
|
23558
|
-
async function readRaw2(
|
|
23664
|
+
async function readRaw2(path3) {
|
|
23559
23665
|
let raw;
|
|
23560
23666
|
try {
|
|
23561
|
-
raw = await readFile6(
|
|
23667
|
+
raw = await readFile6(path3, "utf8");
|
|
23562
23668
|
} catch {
|
|
23563
23669
|
return null;
|
|
23564
23670
|
}
|
|
@@ -23569,11 +23675,11 @@ async function readRaw2(path2) {
|
|
|
23569
23675
|
return null;
|
|
23570
23676
|
}
|
|
23571
23677
|
}
|
|
23572
|
-
async function writeRaw(
|
|
23573
|
-
const tmp = `${
|
|
23678
|
+
async function writeRaw(path3, obj) {
|
|
23679
|
+
const tmp = `${path3}.tmp.${process.pid}.${Date.now()}`;
|
|
23574
23680
|
try {
|
|
23575
23681
|
await writeFile4(tmp, JSON.stringify(obj, null, 2), "utf8");
|
|
23576
|
-
await rename3(tmp,
|
|
23682
|
+
await rename3(tmp, path3);
|
|
23577
23683
|
} catch (err) {
|
|
23578
23684
|
try {
|
|
23579
23685
|
if (existsSync34(tmp)) await unlink3(tmp);
|
|
@@ -23618,8 +23724,8 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
23618
23724
|
}
|
|
23619
23725
|
return out;
|
|
23620
23726
|
}
|
|
23621
|
-
function shortId(
|
|
23622
|
-
const base =
|
|
23727
|
+
function shortId(path3) {
|
|
23728
|
+
const base = path3.slice(path3.lastIndexOf("/") + 1).replace(/\.meta\.json$/, "");
|
|
23623
23729
|
return base.slice(0, 8);
|
|
23624
23730
|
}
|
|
23625
23731
|
async function migrateAdminWebchatSidecars(opts = {}) {
|
|
@@ -23662,8 +23768,8 @@ async function migrateAdminWebchatSidecars(opts = {}) {
|
|
|
23662
23768
|
}
|
|
23663
23769
|
};
|
|
23664
23770
|
const sidecars = await collectLiveSidecars(projectsRoot);
|
|
23665
|
-
for (const
|
|
23666
|
-
const raw = await readRaw2(
|
|
23771
|
+
for (const path3 of sidecars) {
|
|
23772
|
+
const raw = await readRaw2(path3);
|
|
23667
23773
|
if (!raw) continue;
|
|
23668
23774
|
if (raw.role !== ADMIN_ROLE2 || raw.channel !== WEBCHAT_CHANNEL2) continue;
|
|
23669
23775
|
result.scanned++;
|
|
@@ -23674,21 +23780,21 @@ async function migrateAdminWebchatSidecars(opts = {}) {
|
|
|
23674
23780
|
}
|
|
23675
23781
|
if (!singleAdmin) {
|
|
23676
23782
|
result.skippedAmbiguous++;
|
|
23677
|
-
console.error(`[sidecar-backfill] skip sessionId=${shortId(
|
|
23783
|
+
console.error(`[sidecar-backfill] skip sessionId=${shortId(path3)} reason=admin-count-not-one count=${adminCount < 0 ? "unparseable" : adminCount}`);
|
|
23678
23784
|
continue;
|
|
23679
23785
|
}
|
|
23680
23786
|
raw.adminUserId = soleAdminUserId;
|
|
23681
|
-
await writeRaw(
|
|
23787
|
+
await writeRaw(path3, raw);
|
|
23682
23788
|
result.backfilled++;
|
|
23683
23789
|
await resolveAdminName();
|
|
23684
|
-
const back = await readRaw2(
|
|
23790
|
+
const back = await readRaw2(path3);
|
|
23685
23791
|
const wrote = back && back.adminUserId === soleAdminUserId;
|
|
23686
23792
|
if (wrote && resolvedNameForAdmin) {
|
|
23687
23793
|
result.resolvedName++;
|
|
23688
23794
|
} else if (wrote) {
|
|
23689
|
-
console.error(`[sidecar-backfill] backfilled-no-name sessionId=${shortId(
|
|
23795
|
+
console.error(`[sidecar-backfill] backfilled-no-name sessionId=${shortId(path3)} userId=${soleAdminUserId.slice(0, 8)}`);
|
|
23690
23796
|
} else {
|
|
23691
|
-
console.error(`[sidecar-backfill] post-write-verify-failed sessionId=${shortId(
|
|
23797
|
+
console.error(`[sidecar-backfill] post-write-verify-failed sessionId=${shortId(path3)}`);
|
|
23692
23798
|
}
|
|
23693
23799
|
}
|
|
23694
23800
|
console.error(
|
|
@@ -25291,14 +25397,14 @@ function makeFileDelivery(opts) {
|
|
|
25291
25397
|
}
|
|
25292
25398
|
|
|
25293
25399
|
// app/lib/webchat/file-delivery.ts
|
|
25294
|
-
var
|
|
25400
|
+
var TAG42 = "[webchat-adaptor]";
|
|
25295
25401
|
function platformRoot2() {
|
|
25296
25402
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
25297
25403
|
}
|
|
25298
25404
|
function makeWebchatSendFile(entry) {
|
|
25299
25405
|
return async (filePath) => {
|
|
25300
25406
|
if (!entry.accountId) {
|
|
25301
|
-
console.error(`${
|
|
25407
|
+
console.error(`${TAG42} file-delivery reject reason=no-account sender=${entry.senderId}`);
|
|
25302
25408
|
return { ok: false, error: "no-account" };
|
|
25303
25409
|
}
|
|
25304
25410
|
const accountDir = resolve32(platformRoot2(), "..", "data/accounts", entry.accountId);
|
|
@@ -25306,14 +25412,14 @@ function makeWebchatSendFile(entry) {
|
|
|
25306
25412
|
const resolved = realpathSync8(filePath);
|
|
25307
25413
|
const accountResolved = realpathSync8(accountDir);
|
|
25308
25414
|
if (!resolved.startsWith(accountResolved + "/")) {
|
|
25309
|
-
console.error(`${
|
|
25415
|
+
console.error(`${TAG42} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
|
|
25310
25416
|
return { ok: false, error: "outside-account" };
|
|
25311
25417
|
}
|
|
25312
25418
|
return { ok: true };
|
|
25313
25419
|
} catch (err) {
|
|
25314
25420
|
const code = err.code;
|
|
25315
25421
|
console.error(
|
|
25316
|
-
`${
|
|
25422
|
+
`${TAG42} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
|
|
25317
25423
|
);
|
|
25318
25424
|
return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
|
|
25319
25425
|
}
|
|
@@ -25322,7 +25428,7 @@ function makeWebchatSendFile(entry) {
|
|
|
25322
25428
|
function makeWebchatFileDelivery(entry) {
|
|
25323
25429
|
return makeFileDelivery({
|
|
25324
25430
|
entry,
|
|
25325
|
-
tag:
|
|
25431
|
+
tag: TAG42,
|
|
25326
25432
|
channel: "webchat",
|
|
25327
25433
|
sendFile: makeWebchatSendFile(entry),
|
|
25328
25434
|
deferUntilVerdict: true
|
|
@@ -25384,7 +25490,7 @@ function buildPublicWebchatSpawnRequest(input) {
|
|
|
25384
25490
|
}
|
|
25385
25491
|
|
|
25386
25492
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
25387
|
-
var
|
|
25493
|
+
var TAG43 = "[whatsapp-adaptor]";
|
|
25388
25494
|
var SEND_USER_FILE2 = "SendUserFile";
|
|
25389
25495
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
25390
25496
|
function platformRoot3() {
|
|
@@ -25402,7 +25508,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
|
|
|
25402
25508
|
});
|
|
25403
25509
|
if (result.ok) return { ok: true };
|
|
25404
25510
|
console.error(
|
|
25405
|
-
`${
|
|
25511
|
+
`${TAG43} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
25406
25512
|
);
|
|
25407
25513
|
return { ok: false, error: result.error };
|
|
25408
25514
|
};
|
|
@@ -25410,7 +25516,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
|
|
|
25410
25516
|
function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
25411
25517
|
const shared = makeFileDelivery({
|
|
25412
25518
|
entry,
|
|
25413
|
-
tag:
|
|
25519
|
+
tag: TAG43,
|
|
25414
25520
|
channel: "whatsapp",
|
|
25415
25521
|
sendFile: makeWhatsAppSendFile(entry, maxyAccountId)
|
|
25416
25522
|
});
|
|
@@ -25445,7 +25551,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
|
|
|
25445
25551
|
if (!delivered) {
|
|
25446
25552
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
25447
25553
|
console.error(
|
|
25448
|
-
`${
|
|
25554
|
+
`${TAG43} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
25449
25555
|
);
|
|
25450
25556
|
}
|
|
25451
25557
|
}
|
|
@@ -25840,7 +25946,7 @@ function buildTelegramSpawnRequest(input) {
|
|
|
25840
25946
|
import { realpathSync as realpathSync9 } from "fs";
|
|
25841
25947
|
import { readFile as readFile7, stat as stat7 } from "fs/promises";
|
|
25842
25948
|
import { resolve as resolve33, basename as basename11 } from "path";
|
|
25843
|
-
var
|
|
25949
|
+
var TAG44 = "[telegram:outbound]";
|
|
25844
25950
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
25845
25951
|
async function sendTelegramDocument(input) {
|
|
25846
25952
|
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
|
|
@@ -25856,16 +25962,16 @@ async function sendTelegramDocument(input) {
|
|
|
25856
25962
|
resolvedPath = realpathSync9(filePath);
|
|
25857
25963
|
const accountResolved = realpathSync9(accountDir);
|
|
25858
25964
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
25859
|
-
console.error(`${
|
|
25965
|
+
console.error(`${TAG44} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
|
|
25860
25966
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
25861
25967
|
}
|
|
25862
25968
|
} catch (err) {
|
|
25863
25969
|
const code = err.code;
|
|
25864
25970
|
if (code === "ENOENT") {
|
|
25865
|
-
console.error(`${
|
|
25971
|
+
console.error(`${TAG44} document ENOENT path=${filePath}`);
|
|
25866
25972
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
25867
25973
|
}
|
|
25868
|
-
console.error(`${
|
|
25974
|
+
console.error(`${TAG44} document path error: ${String(err)}`);
|
|
25869
25975
|
return { ok: false, status: 500, error: String(err) };
|
|
25870
25976
|
}
|
|
25871
25977
|
const fileStat = await stat7(resolvedPath);
|
|
@@ -25898,13 +26004,13 @@ async function sendTelegramDocument(input) {
|
|
|
25898
26004
|
error = e instanceof Error ? e.message : String(e);
|
|
25899
26005
|
}
|
|
25900
26006
|
console.error(
|
|
25901
|
-
`${
|
|
26007
|
+
`${TAG44} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
25902
26008
|
);
|
|
25903
26009
|
return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
25904
26010
|
}
|
|
25905
26011
|
|
|
25906
26012
|
// app/lib/telegram/outbound/file-delivery.ts
|
|
25907
|
-
var
|
|
26013
|
+
var TAG45 = "[telegram:outbound]";
|
|
25908
26014
|
function platformRoot4() {
|
|
25909
26015
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
25910
26016
|
}
|
|
@@ -25916,11 +26022,11 @@ function makeTelegramSendFile(entry) {
|
|
|
25916
26022
|
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
25917
26023
|
}
|
|
25918
26024
|
if (!botToken) {
|
|
25919
|
-
console.error(`${
|
|
26025
|
+
console.error(`${TAG45} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
25920
26026
|
return { ok: false, error: "no-bot-token" };
|
|
25921
26027
|
}
|
|
25922
26028
|
if (entry.replyTarget == null) {
|
|
25923
|
-
console.error(`${
|
|
26029
|
+
console.error(`${TAG45} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
25924
26030
|
return { ok: false, error: "no-reply-target" };
|
|
25925
26031
|
}
|
|
25926
26032
|
const result = await sendTelegramDocument({
|
|
@@ -25937,7 +26043,7 @@ function makeTelegramSendFile(entry) {
|
|
|
25937
26043
|
function makeTelegramFileDelivery(entry) {
|
|
25938
26044
|
return makeFileDelivery({
|
|
25939
26045
|
entry,
|
|
25940
|
-
tag:
|
|
26046
|
+
tag: TAG45,
|
|
25941
26047
|
channel: "telegram",
|
|
25942
26048
|
sendFile: makeTelegramSendFile(entry)
|
|
25943
26049
|
});
|
|
@@ -25987,7 +26093,7 @@ function telegramServerPath() {
|
|
|
25987
26093
|
|
|
25988
26094
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
25989
26095
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
25990
|
-
var
|
|
26096
|
+
var TAG46 = "[public-session-review]";
|
|
25991
26097
|
var CONSUMED_CAP = 500;
|
|
25992
26098
|
var consumed = /* @__PURE__ */ new Set();
|
|
25993
26099
|
function consumeOnce(sessionId) {
|
|
@@ -26014,7 +26120,7 @@ async function fetchJsonl(sessionId) {
|
|
|
26014
26120
|
return await res.text();
|
|
26015
26121
|
} catch (err) {
|
|
26016
26122
|
console.error(
|
|
26017
|
-
`${
|
|
26123
|
+
`${TAG46} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
26018
26124
|
);
|
|
26019
26125
|
return null;
|
|
26020
26126
|
}
|
|
@@ -26038,7 +26144,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
26038
26144
|
const res = await fetch(url);
|
|
26039
26145
|
if (!res.ok) {
|
|
26040
26146
|
console.error(
|
|
26041
|
-
`${
|
|
26147
|
+
`${TAG46} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
26042
26148
|
);
|
|
26043
26149
|
return [];
|
|
26044
26150
|
}
|
|
@@ -26046,7 +26152,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
26046
26152
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
26047
26153
|
} catch (err) {
|
|
26048
26154
|
console.error(
|
|
26049
|
-
`${
|
|
26155
|
+
`${TAG46} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
26050
26156
|
);
|
|
26051
26157
|
return [];
|
|
26052
26158
|
}
|
|
@@ -26075,7 +26181,7 @@ function composeInitialMessage(input) {
|
|
|
26075
26181
|
}
|
|
26076
26182
|
async function dispatchReviewer(input, initialMessage) {
|
|
26077
26183
|
const sessionId = randomUUID16();
|
|
26078
|
-
console.log(`${
|
|
26184
|
+
console.log(`${TAG46} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
26079
26185
|
const spawned = await managerRcSpawn({
|
|
26080
26186
|
sessionId,
|
|
26081
26187
|
initialMessage,
|
|
@@ -26095,21 +26201,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
26095
26201
|
const dispatchedAt = Date.now();
|
|
26096
26202
|
if (consumed.has(input.sessionId)) {
|
|
26097
26203
|
console.log(
|
|
26098
|
-
`${
|
|
26204
|
+
`${TAG46} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
26099
26205
|
);
|
|
26100
26206
|
return;
|
|
26101
26207
|
}
|
|
26102
26208
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
26103
26209
|
if (!jsonl) {
|
|
26104
26210
|
console.log(
|
|
26105
|
-
`${
|
|
26211
|
+
`${TAG46} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
26106
26212
|
);
|
|
26107
26213
|
return;
|
|
26108
26214
|
}
|
|
26109
26215
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
26110
26216
|
if (operatorTurns === 0) {
|
|
26111
26217
|
console.log(
|
|
26112
|
-
`${
|
|
26218
|
+
`${TAG46} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
26113
26219
|
);
|
|
26114
26220
|
return;
|
|
26115
26221
|
}
|
|
@@ -26123,22 +26229,47 @@ async function firePublicSessionEndReview(input) {
|
|
|
26123
26229
|
});
|
|
26124
26230
|
if (!consumeOnce(input.sessionId)) {
|
|
26125
26231
|
console.log(
|
|
26126
|
-
`${
|
|
26232
|
+
`${TAG46} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
26127
26233
|
);
|
|
26128
26234
|
return;
|
|
26129
26235
|
}
|
|
26130
26236
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
26131
26237
|
if (!dispatched.ok) {
|
|
26132
26238
|
console.error(
|
|
26133
|
-
`${
|
|
26239
|
+
`${TAG46} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
26134
26240
|
);
|
|
26135
26241
|
return;
|
|
26136
26242
|
}
|
|
26137
26243
|
console.log(
|
|
26138
|
-
`${
|
|
26244
|
+
`${TAG46} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
26139
26245
|
);
|
|
26140
26246
|
}
|
|
26141
26247
|
|
|
26248
|
+
// app/lib/whatsapp/inbound/resolve-client-graph-owner.ts
|
|
26249
|
+
var import_dist8 = __toESM(require_dist(), 1);
|
|
26250
|
+
async function resolveClientOwnerUserId(accountId, deps = {}) {
|
|
26251
|
+
const listAccounts = deps.listAccounts ?? listValidAccounts;
|
|
26252
|
+
const getSession4 = deps.getSession ?? getSession;
|
|
26253
|
+
const resolveOwner2 = deps.resolveOwner ?? import_dist8.resolveOwnerUserId;
|
|
26254
|
+
const role = listAccounts().find((a) => a.accountId === accountId)?.config.role;
|
|
26255
|
+
if (role !== "client") return null;
|
|
26256
|
+
const session = getSession4();
|
|
26257
|
+
try {
|
|
26258
|
+
return await resolveOwner2(session, accountId);
|
|
26259
|
+
} catch (err) {
|
|
26260
|
+
console.error(
|
|
26261
|
+
`[whatsapp-native] op=graph-owner-error account=${accountId} error=${err instanceof Error ? err.message : String(err)}`
|
|
26262
|
+
);
|
|
26263
|
+
return null;
|
|
26264
|
+
} finally {
|
|
26265
|
+
await session.close().catch((err) => {
|
|
26266
|
+
console.error(
|
|
26267
|
+
`[whatsapp-native] op=graph-owner-close-error account=${accountId} error=${err instanceof Error ? err.message : String(err)}`
|
|
26268
|
+
);
|
|
26269
|
+
});
|
|
26270
|
+
}
|
|
26271
|
+
}
|
|
26272
|
+
|
|
26142
26273
|
// app/lib/whatsapp/inbound/channel-admin-binding-drift.ts
|
|
26143
26274
|
function findAccountManagerDrift(accountManagers, adminPhones, validAccountIds) {
|
|
26144
26275
|
const drift = [];
|
|
@@ -26556,8 +26687,8 @@ var webchatFileFollowers = /* @__PURE__ */ new Map();
|
|
|
26556
26687
|
async function fetchAccountStandingRules(accountId) {
|
|
26557
26688
|
const session = getSession();
|
|
26558
26689
|
try {
|
|
26559
|
-
const res = await (0,
|
|
26560
|
-
return { block: (0,
|
|
26690
|
+
const res = await (0, import_dist9.resolveActiveRules)(session, accountId);
|
|
26691
|
+
return { block: (0, import_dist9.formatStandingRulesBlock)(res.rules), ownerUserId: res.ownerUserId, source: res.source };
|
|
26561
26692
|
} catch (err) {
|
|
26562
26693
|
console.error(
|
|
26563
26694
|
`[preference-inject] op=fetch-failed accountId=${accountId} error=${err instanceof Error ? err.message : String(err)}`
|
|
@@ -26658,10 +26789,19 @@ var waGateway = new WaGateway({
|
|
|
26658
26789
|
console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
|
|
26659
26790
|
}
|
|
26660
26791
|
const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
|
|
26661
|
-
|
|
26792
|
+
let userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? (ownerSelfPhone ? resolveOwnerUserId(effectiveAccountId) ?? void 0 : void 0) : void 0;
|
|
26793
|
+
let graphOwnerResolved = false;
|
|
26794
|
+
if (role === "admin" && userId === void 0) {
|
|
26795
|
+
const graphOwner = await resolveClientOwnerUserId(effectiveAccountId);
|
|
26796
|
+
if (graphOwner) {
|
|
26797
|
+
userId = graphOwner;
|
|
26798
|
+
graphOwnerResolved = true;
|
|
26799
|
+
}
|
|
26800
|
+
}
|
|
26662
26801
|
if (role === "admin") {
|
|
26663
26802
|
const attribution = userId ?? (ownerSelfPhone ? "owner-self-unresolved" : "unbound-empty");
|
|
26664
|
-
|
|
26803
|
+
const sourceSuffix = graphOwnerResolved ? " source=graph-owner" : "";
|
|
26804
|
+
console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${attribution}${sourceSuffix}`);
|
|
26665
26805
|
}
|
|
26666
26806
|
const result = await managerRcSpawn({ ...req, userId });
|
|
26667
26807
|
if ("error" in result) {
|
|
@@ -26874,8 +27014,8 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
|
|
|
26874
27014
|
});
|
|
26875
27015
|
app62.route("/api/channel/schedule-inject", scheduleInjectRoutes);
|
|
26876
27016
|
app62.use("*", clientIpMiddleware);
|
|
26877
|
-
function allowsSameOriginFraming(
|
|
26878
|
-
return
|
|
27017
|
+
function allowsSameOriginFraming(path3) {
|
|
27018
|
+
return path3 === "/vnc-viewer.html" || path3.startsWith("/api/admin/attachment/") || path3.startsWith("/api/public-reader/attachment/") || path3 === "/api/admin/files/download";
|
|
26879
27019
|
}
|
|
26880
27020
|
app62.use("*", async (c, next) => {
|
|
26881
27021
|
await next();
|
|
@@ -26908,12 +27048,12 @@ app62.use("*", async (c, next) => {
|
|
|
26908
27048
|
await next();
|
|
26909
27049
|
return;
|
|
26910
27050
|
}
|
|
26911
|
-
const
|
|
26912
|
-
if (isPublicPathAllowed(
|
|
27051
|
+
const path3 = c.req.path;
|
|
27052
|
+
if (isPublicPathAllowed(path3)) {
|
|
26913
27053
|
await next();
|
|
26914
27054
|
return;
|
|
26915
27055
|
}
|
|
26916
|
-
console.error(`[public-host] blocked path=${
|
|
27056
|
+
console.error(`[public-host] blocked path=${path3}`);
|
|
26917
27057
|
return c.text("Not found", 404);
|
|
26918
27058
|
});
|
|
26919
27059
|
var lastRemoteAuthDisplayName = "";
|
|
@@ -27148,12 +27288,12 @@ console.log("[client-error-route] mounted");
|
|
|
27148
27288
|
var PWA_PUBLIC_PATHS = /* @__PURE__ */ new Set(["/sw.js", ...PWA_SURFACES.map((s) => s.manifestPath)]);
|
|
27149
27289
|
app62.use("*", async (c, next) => {
|
|
27150
27290
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
27151
|
-
const
|
|
27152
|
-
if (
|
|
27291
|
+
const path3 = c.req.path;
|
|
27292
|
+
if (path3 === "/favicon.ico" || path3.startsWith("/assets/") || path3.startsWith("/brand/") || // Public free/busy is read by the booking page (a separate Cloudflare Pages
|
|
27153
27293
|
// origin) against the brand host with no admin cookie. It is public by
|
|
27154
27294
|
// design — open slots only, no :Meeting detail — so it bypasses the gate
|
|
27155
27295
|
// exactly like /brand/. The detail-leak guard lives in the route + its test.
|
|
27156
|
-
|
|
27296
|
+
path3 === "/api/calendar/free-busy" || PWA_PUBLIC_PATHS.has(path3)) {
|
|
27157
27297
|
await next();
|
|
27158
27298
|
return;
|
|
27159
27299
|
}
|
|
@@ -27180,15 +27320,15 @@ app62.use("*", async (c, next) => {
|
|
|
27180
27320
|
}
|
|
27181
27321
|
const disambig = `remoteAddress="${rawRemote ?? ""}" xff="${rawXff ?? ""}" resolvedKind=${decision.client.kind}`;
|
|
27182
27322
|
if (decision.reason === "remote-unconfigured") {
|
|
27183
|
-
console.error(`[remote-auth] not configured, redirecting to setup ip=${clientIp} path=${
|
|
27323
|
+
console.error(`[remote-auth] not configured, redirecting to setup ip=${clientIp} path=${path3} ${disambig}`);
|
|
27184
27324
|
return c.redirect("/__remote-auth/setup");
|
|
27185
27325
|
}
|
|
27186
|
-
const respond = isApiRequestPath(
|
|
27187
|
-
console.error(`[remote-auth] login required ip=${clientIp} path=${
|
|
27326
|
+
const respond = isApiRequestPath(path3) ? "401" : "html";
|
|
27327
|
+
console.error(`[remote-auth] login required ip=${clientIp} path=${path3} respond=${respond} ${disambig}`);
|
|
27188
27328
|
if (respond === "401") {
|
|
27189
27329
|
return c.json({ code: "remote-auth-required" }, 401);
|
|
27190
27330
|
}
|
|
27191
|
-
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), redirect:
|
|
27331
|
+
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), redirect: path3 }), 200);
|
|
27192
27332
|
});
|
|
27193
27333
|
app62.route("/api/health", health_default);
|
|
27194
27334
|
app62.route("/api/chat", chatRoutes);
|
|
@@ -27613,11 +27753,11 @@ if (brandFaviconPath !== "/favicon.ico") {
|
|
|
27613
27753
|
app62.use("/*", serveStatic({ root: "./public" }));
|
|
27614
27754
|
app62.all("*", (c) => {
|
|
27615
27755
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
27616
|
-
const
|
|
27756
|
+
const path3 = c.req.path;
|
|
27617
27757
|
if (isPublicHost(host)) {
|
|
27618
|
-
const inAllowedList = PUBLIC_ALLOWED_EXACT.includes(
|
|
27758
|
+
const inAllowedList = PUBLIC_ALLOWED_EXACT.includes(path3) || PUBLIC_ALLOWED_PREFIXES.some((prefix) => path3.startsWith(prefix));
|
|
27619
27759
|
if (inAllowedList) {
|
|
27620
|
-
console.error(`[public-host] unmatched-allowed path=${
|
|
27760
|
+
console.error(`[public-host] unmatched-allowed path=${path3}`);
|
|
27621
27761
|
return c.json({ error: "not-found" }, 404);
|
|
27622
27762
|
}
|
|
27623
27763
|
}
|