@rubytech/create-maxy-code 0.1.352 → 0.1.354
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/lib/admin-access-password/__tests__/index.test.ts +96 -14
- package/payload/platform/lib/admin-access-password/dist/index.d.ts +20 -4
- package/payload/platform/lib/admin-access-password/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/admin-access-password/dist/index.js +43 -4
- package/payload/platform/lib/admin-access-password/dist/index.js.map +1 -1
- package/payload/platform/lib/admin-access-password/src/index.ts +67 -4
- package/payload/platform/lib/admins-write/__tests__/index.test.ts +182 -0
- package/payload/platform/lib/admins-write/dist/index.d.ts +65 -11
- package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/admins-write/dist/index.js +93 -31
- package/payload/platform/lib/admins-write/dist/index.js.map +1 -1
- package/payload/platform/lib/admins-write/src/index.ts +133 -36
- package/payload/platform/package-lock.json +76 -1
- package/payload/platform/plugins/admin/PLUGIN.md +4 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +15 -3
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +16 -1
- package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +2 -2
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/public/booking.css +41 -0
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/public/booking.js +236 -47
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/public/index.html +6 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/call.d.ts +27 -9
- package/payload/platform/plugins/connector/mcp/dist/lib/call.d.ts.map +1 -1
- package/payload/platform/plugins/connector/mcp/dist/lib/call.js +102 -29
- package/payload/platform/plugins/connector/mcp/dist/lib/call.js.map +1 -1
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.d.ts +17 -6
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.d.ts.map +1 -1
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.js +28 -10
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.js.map +1 -1
- package/payload/platform/plugins/connector/mcp/package.json +13 -3
- package/payload/platform/plugins/docs/references/troubleshooting.md +15 -0
- package/payload/server/{chunk-QAKVFSEJ.js → chunk-7GNXBVGY.js} +38 -11
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-lt72mYrz.js → AdminLoginScreens-6WgO-Z3m.js} +1 -1
- package/payload/server/public/assets/{AdminShell-B-GeoG3j.js → AdminShell-GGnPXCOe.js} +1 -1
- package/payload/server/public/assets/{Checkbox-DoGMXVpF.js → Checkbox-D3XFVUMT.js} +1 -1
- package/payload/server/public/assets/OperatorConversations-CFWXdnjB.css +1 -0
- package/payload/server/public/assets/{OperatorConversations-CfG1EYyP.js → OperatorConversations-DeZ2wDEK.js} +1 -1
- package/payload/server/public/assets/{admin-DzLxv-nz.js → admin-B2yNhVvH.js} +1 -1
- package/payload/server/public/assets/{browser-Cyi6v8BS.js → browser-ByaQ0ItQ.js} +1 -1
- package/payload/server/public/assets/calendar-BjqCT8cW.js +1 -0
- package/payload/server/public/assets/chat-kl-Ips-o.js +1 -0
- package/payload/server/public/assets/{data-CrJZjlQ1.js → data-p9p-181v.js} +1 -1
- package/payload/server/public/assets/{graph-CutaO0w1.js → graph-BuunC8eP.js} +1 -1
- package/payload/server/public/assets/{graph-labels-CH0sWvcA.js → graph-labels-BF3Ak8q0.js} +1 -1
- package/payload/server/public/assets/{operator-MASl7IqX.js → operator-D7c8mN4J.js} +1 -1
- package/payload/server/public/assets/page-C6uMZdT1.js +32 -0
- package/payload/server/public/assets/{public-77Ubicd7.js → public-B9_UhjEa.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +4 -4
- package/payload/server/public/chat.html +5 -5
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +7 -7
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +1127 -1042
- package/payload/server/public/assets/OperatorConversations-C8G6Gfgu.css +0 -1
- package/payload/server/public/assets/calendar-CA3_Dm8j.js +0 -1
- package/payload/server/public/assets/chat-B9aNK1FG.js +0 -1
- package/payload/server/public/assets/page-CZy33qUH.js +0 -30
package/payload/server/server.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
REMOTE_PASSWORD_FILE,
|
|
22
22
|
RFB_PORT,
|
|
23
23
|
SYSTEM_PATH_SLUGS,
|
|
24
|
+
USERS_AUDIT_LOG,
|
|
24
25
|
USERS_FILE,
|
|
25
26
|
VISITOR_TOKEN_SECRET_FILE,
|
|
26
27
|
VNC_DISPLAY,
|
|
@@ -105,7 +106,7 @@ import {
|
|
|
105
106
|
vncLog,
|
|
106
107
|
walkPremiumBundles,
|
|
107
108
|
writeAdminUserAndPerson
|
|
108
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-7GNXBVGY.js";
|
|
109
110
|
import {
|
|
110
111
|
__commonJS,
|
|
111
112
|
__toESM
|
|
@@ -361,9 +362,9 @@ var require_dist = __commonJS({
|
|
|
361
362
|
try {
|
|
362
363
|
await onEmpty(c);
|
|
363
364
|
} catch (err) {
|
|
364
|
-
const
|
|
365
|
+
const id82 = c.elementId.slice(0, 8);
|
|
365
366
|
const message = err instanceof Error ? err.message : String(err);
|
|
366
|
-
process.stderr.write(`[trash:empty-run] reason=onEmpty-failed elementId=${
|
|
367
|
+
process.stderr.write(`[trash:empty-run] reason=onEmpty-failed elementId=${id82} labels=${c.labels.join(",")} message=${message}
|
|
367
368
|
`);
|
|
368
369
|
continue;
|
|
369
370
|
}
|
|
@@ -1301,9 +1302,9 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1301
1302
|
};
|
|
1302
1303
|
|
|
1303
1304
|
// server/index.ts
|
|
1304
|
-
import { readFileSync as readFileSync33, existsSync as
|
|
1305
|
+
import { readFileSync as readFileSync33, existsSync as existsSync32, watchFile } from "fs";
|
|
1305
1306
|
import { spawn as spawn3 } from "child_process";
|
|
1306
|
-
import { resolve as
|
|
1307
|
+
import { resolve as resolve32, join as join32, basename as basename10 } from "path";
|
|
1307
1308
|
import { homedir as homedir3 } from "os";
|
|
1308
1309
|
import { monitorEventLoopDelay } from "perf_hooks";
|
|
1309
1310
|
|
|
@@ -1961,7 +1962,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
1961
1962
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
1962
1963
|
console.error(`${TAG2} draining credential save queue\u2026`);
|
|
1963
1964
|
const timer2 = new Promise(
|
|
1964
|
-
(
|
|
1965
|
+
(resolve33) => setTimeout(() => resolve33("timeout"), timeoutMs)
|
|
1965
1966
|
);
|
|
1966
1967
|
const result = await Promise.race([
|
|
1967
1968
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -2089,11 +2090,11 @@ async function createWaSocket(opts) {
|
|
|
2089
2090
|
return sock;
|
|
2090
2091
|
}
|
|
2091
2092
|
async function waitForConnection(sock) {
|
|
2092
|
-
return new Promise((
|
|
2093
|
+
return new Promise((resolve33, reject) => {
|
|
2093
2094
|
const handler = (update) => {
|
|
2094
2095
|
if (update.connection === "open") {
|
|
2095
2096
|
sock.ev.off("connection.update", handler);
|
|
2096
|
-
|
|
2097
|
+
resolve33();
|
|
2097
2098
|
}
|
|
2098
2099
|
if (update.connection === "close") {
|
|
2099
2100
|
sock.ev.off("connection.update", handler);
|
|
@@ -2207,14 +2208,14 @@ ${inspected}`;
|
|
|
2207
2208
|
return inspect2(err, INSPECT_OPTS2);
|
|
2208
2209
|
}
|
|
2209
2210
|
function withTimeout(label, promise, timeoutMs) {
|
|
2210
|
-
return new Promise((
|
|
2211
|
+
return new Promise((resolve33, reject) => {
|
|
2211
2212
|
const timer2 = setTimeout(() => {
|
|
2212
2213
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
2213
2214
|
}, timeoutMs);
|
|
2214
2215
|
promise.then(
|
|
2215
2216
|
(value) => {
|
|
2216
2217
|
clearTimeout(timer2);
|
|
2217
|
-
|
|
2218
|
+
resolve33(value);
|
|
2218
2219
|
},
|
|
2219
2220
|
(err) => {
|
|
2220
2221
|
clearTimeout(timer2);
|
|
@@ -3098,8 +3099,8 @@ async function persistWhatsAppMessage(input) {
|
|
|
3098
3099
|
const { givenName, familyName } = splitName(input.pushName);
|
|
3099
3100
|
const prev = sessionWriteLocks.get(input.cacheKey);
|
|
3100
3101
|
let release;
|
|
3101
|
-
const mine = new Promise((
|
|
3102
|
-
release =
|
|
3102
|
+
const mine = new Promise((resolve33) => {
|
|
3103
|
+
release = resolve33;
|
|
3103
3104
|
});
|
|
3104
3105
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
3105
3106
|
sessionWriteLocks.set(input.cacheKey, chained);
|
|
@@ -4209,11 +4210,11 @@ async function connectWithReconnect(conn) {
|
|
|
4209
4210
|
console.error(
|
|
4210
4211
|
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
4211
4212
|
);
|
|
4212
|
-
await new Promise((
|
|
4213
|
-
const timer2 = setTimeout(
|
|
4213
|
+
await new Promise((resolve33) => {
|
|
4214
|
+
const timer2 = setTimeout(resolve33, delay);
|
|
4214
4215
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
4215
4216
|
clearTimeout(timer2);
|
|
4216
|
-
|
|
4217
|
+
resolve33();
|
|
4217
4218
|
}, { once: true });
|
|
4218
4219
|
});
|
|
4219
4220
|
}
|
|
@@ -4221,16 +4222,16 @@ async function connectWithReconnect(conn) {
|
|
|
4221
4222
|
}
|
|
4222
4223
|
}
|
|
4223
4224
|
function waitForDisconnectEvent(conn) {
|
|
4224
|
-
return new Promise((
|
|
4225
|
+
return new Promise((resolve33) => {
|
|
4225
4226
|
if (!conn.sock) {
|
|
4226
|
-
|
|
4227
|
+
resolve33();
|
|
4227
4228
|
return;
|
|
4228
4229
|
}
|
|
4229
4230
|
const sock = conn.sock;
|
|
4230
4231
|
const handler = (update) => {
|
|
4231
4232
|
if (update.connection === "close") {
|
|
4232
4233
|
sock.ev.off("connection.update", handler);
|
|
4233
|
-
|
|
4234
|
+
resolve33();
|
|
4234
4235
|
}
|
|
4235
4236
|
};
|
|
4236
4237
|
sock.ev.on("connection.update", handler);
|
|
@@ -4490,8 +4491,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4490
4491
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
4491
4492
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
4492
4493
|
let resolvePending;
|
|
4493
|
-
const sttPending = new Promise((
|
|
4494
|
-
resolvePending =
|
|
4494
|
+
const sttPending = new Promise((resolve33) => {
|
|
4495
|
+
resolvePending = resolve33;
|
|
4495
4496
|
});
|
|
4496
4497
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
4497
4498
|
try {
|
|
@@ -4891,20 +4892,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
4891
4892
|
|
|
4892
4893
|
// server/routes/health.ts
|
|
4893
4894
|
function checkPort(port2, timeoutMs = 500) {
|
|
4894
|
-
return new Promise((
|
|
4895
|
+
return new Promise((resolve33) => {
|
|
4895
4896
|
const socket = createConnection2(port2, "127.0.0.1");
|
|
4896
4897
|
socket.setTimeout(timeoutMs);
|
|
4897
4898
|
socket.once("connect", () => {
|
|
4898
4899
|
socket.destroy();
|
|
4899
|
-
|
|
4900
|
+
resolve33(true);
|
|
4900
4901
|
});
|
|
4901
4902
|
socket.once("error", () => {
|
|
4902
4903
|
socket.destroy();
|
|
4903
|
-
|
|
4904
|
+
resolve33(false);
|
|
4904
4905
|
});
|
|
4905
4906
|
socket.once("timeout", () => {
|
|
4906
4907
|
socket.destroy();
|
|
4907
|
-
|
|
4908
|
+
resolve33(false);
|
|
4908
4909
|
});
|
|
4909
4910
|
});
|
|
4910
4911
|
}
|
|
@@ -5173,7 +5174,8 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
|
|
|
5173
5174
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
5174
5175
|
import { tmpdir } from "os";
|
|
5175
5176
|
import { join as join5 } from "path";
|
|
5176
|
-
|
|
5177
|
+
|
|
5178
|
+
// app/lib/stt/audio-mime.ts
|
|
5177
5179
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
5178
5180
|
"audio/ogg",
|
|
5179
5181
|
"audio/opus",
|
|
@@ -5212,6 +5214,9 @@ function audioExtension(mimeType) {
|
|
|
5212
5214
|
return ".audio";
|
|
5213
5215
|
}
|
|
5214
5216
|
}
|
|
5217
|
+
|
|
5218
|
+
// app/lib/stt/voice-note.ts
|
|
5219
|
+
var TAG14 = "[voice]";
|
|
5215
5220
|
async function transcribeVoiceNote(file, source) {
|
|
5216
5221
|
const startMs = Date.now();
|
|
5217
5222
|
const bytes = file.size;
|
|
@@ -6040,8 +6045,8 @@ async function startLogin(opts) {
|
|
|
6040
6045
|
await clearAuth(authDir);
|
|
6041
6046
|
let resolveCode = null;
|
|
6042
6047
|
let rejectCode = null;
|
|
6043
|
-
const codePromise = new Promise((
|
|
6044
|
-
resolveCode =
|
|
6048
|
+
const codePromise = new Promise((resolve33, reject) => {
|
|
6049
|
+
resolveCode = resolve33;
|
|
6045
6050
|
rejectCode = reject;
|
|
6046
6051
|
});
|
|
6047
6052
|
const codeTimer = setTimeout(
|
|
@@ -9467,8 +9472,31 @@ import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeF
|
|
|
9467
9472
|
import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
|
|
9468
9473
|
|
|
9469
9474
|
// ../lib/admins-write/src/index.ts
|
|
9470
|
-
import { existsSync as existsSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync12, statSync as statSync7 } from "fs";
|
|
9475
|
+
import { existsSync as existsSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync12, statSync as statSync7, appendFileSync } from "fs";
|
|
9471
9476
|
import { dirname as dirname4, join as join20 } from "path";
|
|
9477
|
+
function id8(value) {
|
|
9478
|
+
return value.slice(0, 8);
|
|
9479
|
+
}
|
|
9480
|
+
function formatAuditRowIds(userIds) {
|
|
9481
|
+
return userIds.map(id8).join(",");
|
|
9482
|
+
}
|
|
9483
|
+
function appendUsersAuditLine(audit, fields) {
|
|
9484
|
+
const sess = audit.session ? ` session=${id8(audit.session)}` : "";
|
|
9485
|
+
const actor = audit.actor.includes("-") ? id8(audit.actor) : audit.actor;
|
|
9486
|
+
const line = `[users-audit] action=${fields.action} actor=${actor}${sess} field=${fields.field} rowsBefore=${fields.rowsBefore} rowsAfter=${fields.rowsAfter} ts=${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9487
|
+
`;
|
|
9488
|
+
try {
|
|
9489
|
+
mkdirSync4(dirname4(audit.logFile), { recursive: true, mode: 448 });
|
|
9490
|
+
appendFileSync(audit.logFile, line, { mode: 384 });
|
|
9491
|
+
} catch (err) {
|
|
9492
|
+
console.error(
|
|
9493
|
+
`[users-audit] op=write-failed logFile=${audit.logFile} error=${err instanceof Error ? err.message : String(err)}`
|
|
9494
|
+
);
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
function logUsersAudit(audit, fields) {
|
|
9498
|
+
appendUsersAuditLine(audit, fields);
|
|
9499
|
+
}
|
|
9472
9500
|
function logLine(input, result) {
|
|
9473
9501
|
const userIdShort = input.userId.slice(0, 8);
|
|
9474
9502
|
console.error(
|
|
@@ -9495,6 +9523,7 @@ function writeAdminEntry(input) {
|
|
|
9495
9523
|
users = parsed;
|
|
9496
9524
|
}
|
|
9497
9525
|
}
|
|
9526
|
+
const rowsBefore = users.map((u) => id8(u.userId)).join(",");
|
|
9498
9527
|
const existing = users.findIndex((u) => u.userId === input.userId);
|
|
9499
9528
|
if (existing === -1) {
|
|
9500
9529
|
users.push({ userId: input.userId, pin: input.pin });
|
|
@@ -9503,6 +9532,12 @@ function writeAdminEntry(input) {
|
|
|
9503
9532
|
}
|
|
9504
9533
|
writeFileAtomic(input.usersFile, JSON.stringify(users, null, 2) + "\n", 384);
|
|
9505
9534
|
result.usersJsonResult = "ok";
|
|
9535
|
+
appendUsersAuditLine(input.audit, {
|
|
9536
|
+
action: existing === -1 ? "add" : "set-pin",
|
|
9537
|
+
field: existing === -1 ? "row" : "pin",
|
|
9538
|
+
rowsBefore,
|
|
9539
|
+
rowsAfter: users.map((u) => id8(u.userId)).join(",")
|
|
9540
|
+
});
|
|
9506
9541
|
} catch (err) {
|
|
9507
9542
|
result.usersJsonResult = "fail";
|
|
9508
9543
|
result.usersError = err instanceof Error ? err.message : String(err);
|
|
@@ -9531,12 +9566,8 @@ function writeAdminEntry(input) {
|
|
|
9531
9566
|
logLine(input, result);
|
|
9532
9567
|
return result;
|
|
9533
9568
|
}
|
|
9534
|
-
function
|
|
9535
|
-
const result = {
|
|
9536
|
-
divergences: 0,
|
|
9537
|
-
accountWithoutUsers: [],
|
|
9538
|
-
usersWithoutAccount: []
|
|
9539
|
-
};
|
|
9569
|
+
function computeAdminStoreDivergence(input) {
|
|
9570
|
+
const result = { divergences: 0, accountWithoutUsers: [], usersWithoutAccount: [], errors: [] };
|
|
9540
9571
|
const usersUserIds = /* @__PURE__ */ new Set();
|
|
9541
9572
|
if (existsSync13(input.usersFile)) {
|
|
9542
9573
|
try {
|
|
@@ -9548,8 +9579,7 @@ function checkAdminAuthInvariant(input) {
|
|
|
9548
9579
|
}
|
|
9549
9580
|
}
|
|
9550
9581
|
} catch (err) {
|
|
9551
|
-
|
|
9552
|
-
console.error(`[${input.tag}] users.json unreadable usersFile=${input.usersFile} error=${msg}`);
|
|
9582
|
+
result.errors.push({ source: input.usersFile, detail: err instanceof Error ? err.message : String(err) });
|
|
9553
9583
|
}
|
|
9554
9584
|
}
|
|
9555
9585
|
const accountUserIds = /* @__PURE__ */ new Set();
|
|
@@ -9558,9 +9588,7 @@ function checkAdminAuthInvariant(input) {
|
|
|
9558
9588
|
try {
|
|
9559
9589
|
entries = readdirSync12(input.accountsDir);
|
|
9560
9590
|
} catch (err) {
|
|
9561
|
-
|
|
9562
|
-
console.error(`[${input.tag}] accounts-dir unreadable accountsDir=${input.accountsDir} error=${msg}`);
|
|
9563
|
-
console.error(`[${input.tag}] check complete divergences=0 (accounts-dir unreadable)`);
|
|
9591
|
+
result.errors.push({ source: input.accountsDir, detail: err instanceof Error ? err.message : String(err) });
|
|
9564
9592
|
return result;
|
|
9565
9593
|
}
|
|
9566
9594
|
for (const entry of entries) {
|
|
@@ -9578,18 +9606,13 @@ function checkAdminAuthInvariant(input) {
|
|
|
9578
9606
|
const config = JSON.parse(readFileSync21(accountJsonPath, "utf-8"));
|
|
9579
9607
|
admins = config.admins ?? [];
|
|
9580
9608
|
} catch (err) {
|
|
9581
|
-
|
|
9582
|
-
console.error(`[${input.tag}] account.json unreadable source=${accountJsonPath} error=${msg}`);
|
|
9609
|
+
result.errors.push({ source: accountJsonPath, detail: err instanceof Error ? err.message : String(err) });
|
|
9583
9610
|
continue;
|
|
9584
9611
|
}
|
|
9585
9612
|
for (const a of admins) {
|
|
9586
9613
|
if (typeof a.userId !== "string") continue;
|
|
9587
9614
|
accountUserIds.add(a.userId);
|
|
9588
9615
|
if (!usersUserIds.has(a.userId)) {
|
|
9589
|
-
const userIdShort = a.userId.slice(0, 8);
|
|
9590
|
-
console.error(
|
|
9591
|
-
`[${input.tag}] direction=account-without-users userId=${userIdShort} source=${accountJsonPath}`
|
|
9592
|
-
);
|
|
9593
9616
|
result.accountWithoutUsers.push({ userId: a.userId, source: accountJsonPath });
|
|
9594
9617
|
result.divergences += 1;
|
|
9595
9618
|
}
|
|
@@ -9598,17 +9621,30 @@ function checkAdminAuthInvariant(input) {
|
|
|
9598
9621
|
}
|
|
9599
9622
|
for (const uid of usersUserIds) {
|
|
9600
9623
|
if (!accountUserIds.has(uid)) {
|
|
9601
|
-
const userIdShort = uid.slice(0, 8);
|
|
9602
|
-
console.error(
|
|
9603
|
-
`[${input.tag}] direction=users-without-account userId=${userIdShort} source=${input.usersFile}`
|
|
9604
|
-
);
|
|
9605
9624
|
result.usersWithoutAccount.push({ userId: uid });
|
|
9606
9625
|
result.divergences += 1;
|
|
9607
9626
|
}
|
|
9608
9627
|
}
|
|
9609
|
-
console.error(`[${input.tag}] check complete divergences=${result.divergences}`);
|
|
9610
9628
|
return result;
|
|
9611
9629
|
}
|
|
9630
|
+
function checkAdminAuthInvariant(input) {
|
|
9631
|
+
const d = computeAdminStoreDivergence({ usersFile: input.usersFile, accountsDir: input.accountsDir });
|
|
9632
|
+
for (const e of d.errors) {
|
|
9633
|
+
console.error(`[${input.tag}] unreadable source=${e.source} error=${e.detail}`);
|
|
9634
|
+
}
|
|
9635
|
+
for (const a of d.accountWithoutUsers) {
|
|
9636
|
+
console.error(`[${input.tag}] direction=account-without-users userId=${a.userId.slice(0, 8)} source=${a.source}`);
|
|
9637
|
+
}
|
|
9638
|
+
for (const u of d.usersWithoutAccount) {
|
|
9639
|
+
console.error(`[${input.tag}] direction=users-without-account userId=${u.userId.slice(0, 8)} source=${input.usersFile}`);
|
|
9640
|
+
}
|
|
9641
|
+
console.error(`[${input.tag}] check complete divergences=${d.divergences}`);
|
|
9642
|
+
return {
|
|
9643
|
+
divergences: d.divergences,
|
|
9644
|
+
accountWithoutUsers: d.accountWithoutUsers,
|
|
9645
|
+
usersWithoutAccount: d.usersWithoutAccount
|
|
9646
|
+
};
|
|
9647
|
+
}
|
|
9612
9648
|
|
|
9613
9649
|
// server/routes/onboarding.ts
|
|
9614
9650
|
function hashPin2(pin) {
|
|
@@ -9763,7 +9799,8 @@ app9.post("/set-pin", async (c) => {
|
|
|
9763
9799
|
role: "owner",
|
|
9764
9800
|
usersFile: USERS_FILE,
|
|
9765
9801
|
accountDir: account.accountDir,
|
|
9766
|
-
caller: "set-pin"
|
|
9802
|
+
caller: "set-pin",
|
|
9803
|
+
audit: { actor: userId, logFile: USERS_AUDIT_LOG }
|
|
9767
9804
|
});
|
|
9768
9805
|
if (result.usersJsonResult !== "ok") {
|
|
9769
9806
|
console.error(`[set-pin] users.json write failed: ${result.usersError ?? "unknown"}`);
|
|
@@ -9854,12 +9891,16 @@ app9.delete("/set-pin", async (c) => {
|
|
|
9854
9891
|
writeFileSync10(USERS_FILE, JSON.stringify(remaining), { mode: 384 });
|
|
9855
9892
|
console.log(`[set-pin] removed entry from users.json: userId=${matchedUser.userId.slice(0, 8)}\u2026 remaining=${remaining.length}`);
|
|
9856
9893
|
}
|
|
9894
|
+
logUsersAudit(
|
|
9895
|
+
{ actor: matchedUser.userId, logFile: USERS_AUDIT_LOG },
|
|
9896
|
+
{ action: "remove", field: "row", rowsBefore: formatAuditRowIds(users.map((u) => u.userId)), rowsAfter: formatAuditRowIds(remaining.map((u) => u.userId)) }
|
|
9897
|
+
);
|
|
9857
9898
|
return c.json({ ok: true });
|
|
9858
9899
|
});
|
|
9859
9900
|
var onboarding_default = app9;
|
|
9860
9901
|
|
|
9861
9902
|
// server/routes/client-error.ts
|
|
9862
|
-
import { appendFileSync, existsSync as existsSync15, renameSync as renameSync6, statSync as statSync8 } from "fs";
|
|
9903
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync15, renameSync as renameSync6, statSync as statSync8 } from "fs";
|
|
9863
9904
|
import { join as join21 } from "path";
|
|
9864
9905
|
var CLIENT_ERRORS_LOG = join21(LOG_DIR, "client-errors.log");
|
|
9865
9906
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
@@ -10008,7 +10049,7 @@ app10.post("/", async (c) => {
|
|
|
10008
10049
|
tag: typeof body.tag === "string" ? truncate(body.tag, 32) : void 0,
|
|
10009
10050
|
status: typeof body.status === "number" ? body.status : void 0
|
|
10010
10051
|
};
|
|
10011
|
-
|
|
10052
|
+
appendFileSync2(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
10012
10053
|
} catch (err) {
|
|
10013
10054
|
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10014
10055
|
}
|
|
@@ -10507,7 +10548,7 @@ import { existsSync as existsSync20, mkdirSync as mkdirSync5, createWriteStream
|
|
|
10507
10548
|
async function purgeAdminConversationJsonls(args) {
|
|
10508
10549
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
10509
10550
|
const log = args.logger ?? ((line) => console.log(line));
|
|
10510
|
-
const
|
|
10551
|
+
const id82 = args.sessionId.slice(0, 8);
|
|
10511
10552
|
const purged = [];
|
|
10512
10553
|
for (const sessionId of args.claudeSessionIds) {
|
|
10513
10554
|
const url = `${args.managerBase}/${encodeURIComponent(sessionId)}`;
|
|
@@ -10517,7 +10558,7 @@ async function purgeAdminConversationJsonls(args) {
|
|
|
10517
10558
|
} catch (err) {
|
|
10518
10559
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
10519
10560
|
log(
|
|
10520
|
-
`[admin-conversation-delete] sessionId=${
|
|
10561
|
+
`[admin-conversation-delete] sessionId=${id82} reason=jsonl-purge-failed claudeSessionId=${sessionId.slice(0, 8)} message=${message2}`
|
|
10521
10562
|
);
|
|
10522
10563
|
return {
|
|
10523
10564
|
ok: false,
|
|
@@ -10534,7 +10575,7 @@ async function purgeAdminConversationJsonls(args) {
|
|
|
10534
10575
|
}
|
|
10535
10576
|
if (res.status === 409) {
|
|
10536
10577
|
log(
|
|
10537
|
-
`[admin-conversation-delete] sessionId=${
|
|
10578
|
+
`[admin-conversation-delete] sessionId=${id82} reason=pty-still-alive claudeSessionId=${sessionId.slice(0, 8)}`
|
|
10538
10579
|
);
|
|
10539
10580
|
return {
|
|
10540
10581
|
ok: false,
|
|
@@ -10551,7 +10592,7 @@ async function purgeAdminConversationJsonls(args) {
|
|
|
10551
10592
|
} catch {
|
|
10552
10593
|
}
|
|
10553
10594
|
log(
|
|
10554
|
-
`[admin-conversation-delete] sessionId=${
|
|
10595
|
+
`[admin-conversation-delete] sessionId=${id82} reason=jsonl-purge-failed claudeSessionId=${sessionId.slice(0, 8)} message=${message}`
|
|
10555
10596
|
);
|
|
10556
10597
|
return {
|
|
10557
10598
|
ok: false,
|
|
@@ -10570,13 +10611,13 @@ async function cascadeAdminConversationDelete(args) {
|
|
|
10570
10611
|
const started = Date.now();
|
|
10571
10612
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
10572
10613
|
const log = args.logger ?? ((line) => console.log(line));
|
|
10573
|
-
const
|
|
10614
|
+
const id82 = args.sessionId.slice(0, 8);
|
|
10574
10615
|
let claudeSessionIds;
|
|
10575
10616
|
try {
|
|
10576
10617
|
claudeSessionIds = args.claudeSessionIds ?? await getConversationClaudeSessionIds(args.sessionId, args.accountId);
|
|
10577
10618
|
} catch (err) {
|
|
10578
10619
|
const message = err instanceof Error ? err.message : String(err);
|
|
10579
|
-
log(`[admin-conversation-delete] sessionId=${
|
|
10620
|
+
log(`[admin-conversation-delete] sessionId=${id82} reason=graph-delete-failed claudeSessionIds-purged= message=transcript-resolve:${message}`);
|
|
10580
10621
|
return { ok: false, reason: "graph-delete-failed", claudeSessionIdsPurged: [], message };
|
|
10581
10622
|
}
|
|
10582
10623
|
const purgeOutcome = await purgeAdminConversationJsonls({
|
|
@@ -10607,7 +10648,7 @@ async function cascadeAdminConversationDelete(args) {
|
|
|
10607
10648
|
} catch (err) {
|
|
10608
10649
|
const message = err instanceof Error ? err.message : String(err);
|
|
10609
10650
|
const csv2 = purged.map((s) => s.slice(0, 8)).join(",");
|
|
10610
|
-
log(`[admin-conversation-delete] sessionId=${
|
|
10651
|
+
log(`[admin-conversation-delete] sessionId=${id82} reason=graph-delete-failed claudeSessionIds-purged=${csv2} message=${message}`);
|
|
10611
10652
|
return {
|
|
10612
10653
|
ok: false,
|
|
10613
10654
|
reason: "graph-delete-failed",
|
|
@@ -10620,7 +10661,7 @@ async function cascadeAdminConversationDelete(args) {
|
|
|
10620
10661
|
}
|
|
10621
10662
|
const ms = Date.now() - started;
|
|
10622
10663
|
const csv = purged.map((s) => s.slice(0, 8)).join(",");
|
|
10623
|
-
log(`[admin-conversation-delete] sessionId=${
|
|
10664
|
+
log(`[admin-conversation-delete] sessionId=${id82} claudeSessionIds=${csv} ms=${ms}`);
|
|
10624
10665
|
return { ok: true, claudeSessionIds: purged, ms };
|
|
10625
10666
|
}
|
|
10626
10667
|
|
|
@@ -16603,765 +16644,146 @@ app38.get("/", async (c) => {
|
|
|
16603
16644
|
});
|
|
16604
16645
|
var public_session_context_default = app38;
|
|
16605
16646
|
|
|
16606
|
-
// app/lib/
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
import { randomUUID as randomUUID13 } from "crypto";
|
|
16611
|
-
var TAG29 = "[public-session-review]";
|
|
16612
|
-
var CONSUMED_CAP = 500;
|
|
16613
|
-
var consumed = /* @__PURE__ */ new Set();
|
|
16614
|
-
function consumeOnce(sessionId) {
|
|
16615
|
-
if (consumed.has(sessionId)) return false;
|
|
16616
|
-
consumed.add(sessionId);
|
|
16617
|
-
if (consumed.size > CONSUMED_CAP) {
|
|
16618
|
-
const oldest = consumed.values().next().value;
|
|
16619
|
-
if (oldest !== void 0) consumed.delete(oldest);
|
|
16620
|
-
}
|
|
16621
|
-
return true;
|
|
16647
|
+
// app/lib/webchat/gateway/instance.ts
|
|
16648
|
+
var instance2 = null;
|
|
16649
|
+
function setWebchatGateway(gw) {
|
|
16650
|
+
instance2 = gw;
|
|
16622
16651
|
}
|
|
16623
|
-
function
|
|
16624
|
-
|
|
16625
|
-
return `http://127.0.0.1:${port2}`;
|
|
16652
|
+
function getWebchatGateway() {
|
|
16653
|
+
return instance2;
|
|
16626
16654
|
}
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16655
|
+
|
|
16656
|
+
// server/routes/admin/public-session-exit.ts
|
|
16657
|
+
var TAG29 = "[public-session-exit-route]";
|
|
16658
|
+
function isLoopbackAddr4(addr) {
|
|
16659
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
16630
16660
|
}
|
|
16631
|
-
|
|
16661
|
+
var app39 = new Hono();
|
|
16662
|
+
app39.post("/", async (c) => {
|
|
16663
|
+
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16664
|
+
if (!isLoopbackAddr4(remoteAddr)) {
|
|
16665
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16666
|
+
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
16667
|
+
}
|
|
16668
|
+
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
16669
|
+
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
16670
|
+
if (xffRaw.length > 0) {
|
|
16671
|
+
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16672
|
+
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
16673
|
+
if (offender !== void 0) {
|
|
16674
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16675
|
+
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
16676
|
+
}
|
|
16677
|
+
}
|
|
16678
|
+
let body;
|
|
16632
16679
|
try {
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
return
|
|
16636
|
-
}
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16680
|
+
body = await c.req.json();
|
|
16681
|
+
} catch {
|
|
16682
|
+
return c.json({ error: "invalid-json" }, 400);
|
|
16683
|
+
}
|
|
16684
|
+
const sessionId = typeof body.sessionId === "string" ? body.sessionId.trim() : "";
|
|
16685
|
+
if (!sessionId) return c.json({ error: "sessionId required" }, 400);
|
|
16686
|
+
const gateway = getWebchatGateway();
|
|
16687
|
+
if (!gateway) {
|
|
16688
|
+
console.error(`${TAG29} reject reason=gateway-unset sessionId=${sessionId.slice(0, 8)}`);
|
|
16689
|
+
return c.json({ error: "webchat-gateway-unset" }, 503);
|
|
16641
16690
|
}
|
|
16691
|
+
gateway.handlePublicSessionExit(sessionId);
|
|
16692
|
+
return c.json({ ok: true });
|
|
16693
|
+
});
|
|
16694
|
+
var public_session_exit_default = app39;
|
|
16695
|
+
|
|
16696
|
+
// server/routes/admin/access-session-evict.ts
|
|
16697
|
+
var TAG30 = "[access-session-evict]";
|
|
16698
|
+
function isLoopbackAddr5(addr) {
|
|
16699
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
16642
16700
|
}
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16701
|
+
var app40 = new Hono();
|
|
16702
|
+
app40.post("/", async (c) => {
|
|
16703
|
+
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16704
|
+
if (!isLoopbackAddr5(remoteAddr)) {
|
|
16705
|
+
console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16706
|
+
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
16707
|
+
}
|
|
16708
|
+
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
16709
|
+
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
16710
|
+
if (xffRaw.length > 0) {
|
|
16711
|
+
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16712
|
+
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
16713
|
+
if (offender !== void 0) {
|
|
16714
|
+
console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16715
|
+
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
16652
16716
|
}
|
|
16653
16717
|
}
|
|
16654
|
-
|
|
16655
|
-
}
|
|
16656
|
-
async function fetchPriorWrites(sliceToken, accountId) {
|
|
16657
|
-
const url = `${uiBase()}/api/admin/public-session-context?sliceToken=${encodeURIComponent(sliceToken)}&accountId=${encodeURIComponent(accountId)}`;
|
|
16718
|
+
let body;
|
|
16658
16719
|
try {
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
`${TAG29} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
16663
|
-
);
|
|
16664
|
-
return [];
|
|
16665
|
-
}
|
|
16666
|
-
const body = await res.json();
|
|
16667
|
-
return Array.isArray(body.writes) ? body.writes : [];
|
|
16668
|
-
} catch (err) {
|
|
16669
|
-
console.error(
|
|
16670
|
-
`${TAG29} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
16671
|
-
);
|
|
16672
|
-
return [];
|
|
16720
|
+
body = await c.req.json();
|
|
16721
|
+
} catch {
|
|
16722
|
+
return c.json({ error: "invalid-json" }, 400);
|
|
16673
16723
|
}
|
|
16724
|
+
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
16725
|
+
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
16726
|
+
const dropped = evictAccessSessionsByGrant(grantId);
|
|
16727
|
+
console.log(`${TAG30} grantId=${grantId} dropped=${dropped}`);
|
|
16728
|
+
return c.json({ ok: true, dropped });
|
|
16729
|
+
});
|
|
16730
|
+
var access_session_evict_default = app40;
|
|
16731
|
+
|
|
16732
|
+
// server/routes/admin/enrol-person.ts
|
|
16733
|
+
var TAG31 = "[enrol]";
|
|
16734
|
+
function isLoopbackAddr6(addr) {
|
|
16735
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
16674
16736
|
}
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
"</slice-token>",
|
|
16683
|
-
"",
|
|
16684
|
-
"<person-id>",
|
|
16685
|
-
input.personId ?? "(unknown)",
|
|
16686
|
-
"</person-id>",
|
|
16687
|
-
"",
|
|
16688
|
-
"<prior-writes>",
|
|
16689
|
-
priorJson,
|
|
16690
|
-
"</prior-writes>",
|
|
16691
|
-
"",
|
|
16692
|
-
"<transcript>",
|
|
16693
|
-
input.jsonl,
|
|
16694
|
-
"</transcript>"
|
|
16695
|
-
].join("\n");
|
|
16696
|
-
}
|
|
16697
|
-
async function dispatchReviewer(input, initialMessage) {
|
|
16698
|
-
const sessionId = randomUUID13();
|
|
16699
|
-
console.log(`${TAG29} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
16700
|
-
const spawned = await managerRcSpawn({
|
|
16701
|
-
sessionId,
|
|
16702
|
-
initialMessage,
|
|
16703
|
-
closeAfterTurn: true,
|
|
16704
|
-
sliceToken: input.sliceToken,
|
|
16705
|
-
personId: input.personId ?? void 0,
|
|
16706
|
-
logContext: `public-session-end sourceSession=${input.sessionId.slice(0, 8)}`
|
|
16707
|
-
});
|
|
16708
|
-
if ("error" in spawned) {
|
|
16709
|
-
return { ok: false, reason: `rc-spawn-${spawned.status}-${spawned.error}` };
|
|
16737
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
16738
|
+
var app41 = new Hono();
|
|
16739
|
+
app41.post("/", async (c) => {
|
|
16740
|
+
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16741
|
+
if (!isLoopbackAddr6(remoteAddr)) {
|
|
16742
|
+
console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16743
|
+
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
16710
16744
|
}
|
|
16711
|
-
|
|
16712
|
-
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
);
|
|
16721
|
-
return;
|
|
16745
|
+
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
16746
|
+
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
16747
|
+
if (xffRaw.length > 0) {
|
|
16748
|
+
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16749
|
+
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
16750
|
+
if (offender !== void 0) {
|
|
16751
|
+
console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16752
|
+
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
16753
|
+
}
|
|
16722
16754
|
}
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
);
|
|
16728
|
-
return;
|
|
16755
|
+
let body;
|
|
16756
|
+
try {
|
|
16757
|
+
body = await c.req.json();
|
|
16758
|
+
} catch {
|
|
16759
|
+
return c.json({ error: "invalid-json" }, 400);
|
|
16729
16760
|
}
|
|
16730
|
-
const
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
);
|
|
16735
|
-
return;
|
|
16761
|
+
const rawPhone = typeof body.phone === "string" ? body.phone : "";
|
|
16762
|
+
const phone = normalizeE164(rawPhone);
|
|
16763
|
+
if (phone.length <= 1) {
|
|
16764
|
+
return c.json({ error: "invalid-phone" }, 400);
|
|
16736
16765
|
}
|
|
16737
|
-
const
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
sliceToken: input.sliceToken,
|
|
16741
|
-
personId: input.personId,
|
|
16742
|
-
jsonl,
|
|
16743
|
-
priorWrites
|
|
16744
|
-
});
|
|
16745
|
-
if (!consumeOnce(input.sessionId)) {
|
|
16746
|
-
console.log(
|
|
16747
|
-
`${TAG29} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
16748
|
-
);
|
|
16749
|
-
return;
|
|
16766
|
+
const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
|
|
16767
|
+
if (!EMAIL_RE.test(email)) {
|
|
16768
|
+
return c.json({ error: "invalid-email" }, 400);
|
|
16750
16769
|
}
|
|
16751
|
-
const
|
|
16752
|
-
if (!
|
|
16770
|
+
const agentSlug = typeof body.agentSlug === "string" ? body.agentSlug.trim() : "";
|
|
16771
|
+
if (!agentSlug) {
|
|
16772
|
+
return c.json({ error: "agentSlug required" }, 400);
|
|
16773
|
+
}
|
|
16774
|
+
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
16775
|
+
if (!accountId) {
|
|
16776
|
+
console.error(`${TAG31} op=person result=no-account-id`);
|
|
16777
|
+
return c.json({ error: "no-account-id" }, 500);
|
|
16778
|
+
}
|
|
16779
|
+
let personId;
|
|
16780
|
+
try {
|
|
16781
|
+
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
16782
|
+
} catch (err) {
|
|
16753
16783
|
console.error(
|
|
16754
|
-
`${
|
|
16784
|
+
`${TAG31} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
16755
16785
|
);
|
|
16756
|
-
return;
|
|
16757
|
-
}
|
|
16758
|
-
console.log(
|
|
16759
|
-
`${TAG29} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
16760
|
-
);
|
|
16761
|
-
}
|
|
16762
|
-
|
|
16763
|
-
// app/lib/channel-pty-bridge/follower.ts
|
|
16764
|
-
function followerPendingMaxMs() {
|
|
16765
|
-
return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
|
|
16766
|
-
}
|
|
16767
|
-
function followerRetryMs() {
|
|
16768
|
-
return Number(process.env.CHANNEL_PTY_FOLLOWER_RETRY_MS ?? String(1e3));
|
|
16769
|
-
}
|
|
16770
|
-
function startFollower(opts) {
|
|
16771
|
-
const abort = new AbortController();
|
|
16772
|
-
const { entry, tag } = opts;
|
|
16773
|
-
void (async () => {
|
|
16774
|
-
try {
|
|
16775
|
-
const sid = entry.sessionId.slice(0, 8);
|
|
16776
|
-
const deadline = Date.now() + followerPendingMaxMs();
|
|
16777
|
-
const retryMs = followerRetryMs();
|
|
16778
|
-
let res;
|
|
16779
|
-
let attempt = 0;
|
|
16780
|
-
for (; ; ) {
|
|
16781
|
-
res = await fetch(
|
|
16782
|
-
managerLogFollowUrl(entry.sessionId, { boundary: opts.suppressResumeReplay === true }),
|
|
16783
|
-
{ signal: abort.signal }
|
|
16784
|
-
);
|
|
16785
|
-
console.error(`${tag} follower-connect sessionId=${sid} status=${res.status}`);
|
|
16786
|
-
if (res.status === 202) {
|
|
16787
|
-
attempt += 1;
|
|
16788
|
-
await res.body?.cancel().catch(() => {
|
|
16789
|
-
});
|
|
16790
|
-
if (abort.signal.aborted) return;
|
|
16791
|
-
if (Date.now() >= deadline) {
|
|
16792
|
-
console.error(`${tag} follower-give-up sessionId=${sid} reason=pending-timeout attempts=${attempt}`);
|
|
16793
|
-
opts.onError?.("follow-pending-timeout");
|
|
16794
|
-
return;
|
|
16795
|
-
}
|
|
16796
|
-
console.error(`${tag} follower-retry sessionId=${sid} attempt=${attempt} reason=pending`);
|
|
16797
|
-
await new Promise((r) => setTimeout(r, retryMs));
|
|
16798
|
-
if (abort.signal.aborted) return;
|
|
16799
|
-
continue;
|
|
16800
|
-
}
|
|
16801
|
-
break;
|
|
16802
|
-
}
|
|
16803
|
-
if (!res.ok || !res.body) {
|
|
16804
|
-
opts.onError?.(`follow-status-${res.status}`);
|
|
16805
|
-
return;
|
|
16806
|
-
}
|
|
16807
|
-
console.error(`${tag} follower-open sessionId=${sid}`);
|
|
16808
|
-
const reader = res.body.getReader();
|
|
16809
|
-
const decoder = new TextDecoder("utf8");
|
|
16810
|
-
let buffered = "";
|
|
16811
|
-
const fileDelivery = opts.fileDelivery ?? null;
|
|
16812
|
-
let firedFileTools = [];
|
|
16813
|
-
let suppressing = opts.suppressResumeReplay === true;
|
|
16814
|
-
let discardedTurns = 0;
|
|
16815
|
-
let discardedFileTools = 0;
|
|
16816
|
-
while (!abort.signal.aborted) {
|
|
16817
|
-
const { value, done } = await reader.read();
|
|
16818
|
-
if (done) break;
|
|
16819
|
-
buffered += decoder.decode(value, { stream: true });
|
|
16820
|
-
let nl = buffered.indexOf("\n");
|
|
16821
|
-
while (nl !== -1) {
|
|
16822
|
-
const line = buffered.slice(0, nl);
|
|
16823
|
-
buffered = buffered.slice(nl + 1);
|
|
16824
|
-
nl = buffered.indexOf("\n");
|
|
16825
|
-
if (!line) continue;
|
|
16826
|
-
let event;
|
|
16827
|
-
try {
|
|
16828
|
-
event = JSON.parse(line);
|
|
16829
|
-
} catch (err) {
|
|
16830
|
-
console.error(
|
|
16831
|
-
`${tag} jsonl-parse-skip sessionId=${entry.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
|
|
16832
|
-
);
|
|
16833
|
-
continue;
|
|
16834
|
-
}
|
|
16835
|
-
if (event.type === "__channel_resume_boundary__") {
|
|
16836
|
-
if (suppressing) {
|
|
16837
|
-
suppressing = false;
|
|
16838
|
-
console.error(
|
|
16839
|
-
`${tag} follower-resume-boundary sessionId=${sid} discarded-head-turns=${discardedTurns} discarded-head-filetools=${discardedFileTools}`
|
|
16840
|
-
);
|
|
16841
|
-
}
|
|
16842
|
-
continue;
|
|
16843
|
-
}
|
|
16844
|
-
if (event.type === "user") {
|
|
16845
|
-
entry.pendingTurnText = "";
|
|
16846
|
-
firedFileTools = [];
|
|
16847
|
-
continue;
|
|
16848
|
-
}
|
|
16849
|
-
if (event.type !== "assistant") continue;
|
|
16850
|
-
const msg = event.message;
|
|
16851
|
-
if (!msg) continue;
|
|
16852
|
-
let sawContent = false;
|
|
16853
|
-
if (Array.isArray(msg.content)) {
|
|
16854
|
-
for (const block of msg.content) {
|
|
16855
|
-
if (block?.type === "text" && typeof block.text === "string") {
|
|
16856
|
-
entry.pendingTurnText += block.text;
|
|
16857
|
-
sawContent = true;
|
|
16858
|
-
} else if (block?.type === "tool_use") {
|
|
16859
|
-
sawContent = true;
|
|
16860
|
-
if (fileDelivery && typeof block.name === "string" && fileDelivery.isFileDeliveryTool(block.name)) {
|
|
16861
|
-
if (suppressing) {
|
|
16862
|
-
discardedFileTools += 1;
|
|
16863
|
-
continue;
|
|
16864
|
-
}
|
|
16865
|
-
firedFileTools.push(block.name);
|
|
16866
|
-
try {
|
|
16867
|
-
await fileDelivery.onFileToolUse({ toolName: block.name, input: block.input });
|
|
16868
|
-
} catch (err) {
|
|
16869
|
-
console.error(
|
|
16870
|
-
`${tag} file-delivery-error sessionId=${sid} tool=${block.name} message=${err instanceof Error ? err.message : String(err)}`
|
|
16871
|
-
);
|
|
16872
|
-
}
|
|
16873
|
-
}
|
|
16874
|
-
}
|
|
16875
|
-
}
|
|
16876
|
-
}
|
|
16877
|
-
if (msg.stop_reason === "end_turn") {
|
|
16878
|
-
if (suppressing) {
|
|
16879
|
-
entry.pendingTurnText = "";
|
|
16880
|
-
firedFileTools = [];
|
|
16881
|
-
discardedTurns += 1;
|
|
16882
|
-
continue;
|
|
16883
|
-
}
|
|
16884
|
-
const flush = entry.pendingTurnText;
|
|
16885
|
-
entry.pendingTurnText = "";
|
|
16886
|
-
if (flush.trim()) {
|
|
16887
|
-
await fanOut(entry.subscribers, flush, opts.onError, tag);
|
|
16888
|
-
}
|
|
16889
|
-
if (fileDelivery && firedFileTools.length > 0) {
|
|
16890
|
-
const fired = firedFileTools;
|
|
16891
|
-
firedFileTools = [];
|
|
16892
|
-
try {
|
|
16893
|
-
fileDelivery.onTurnEnd(fired);
|
|
16894
|
-
} catch (err) {
|
|
16895
|
-
console.error(
|
|
16896
|
-
`${tag} file-delivery-error sessionId=${sid} phase=turn-end message=${err instanceof Error ? err.message : String(err)}`
|
|
16897
|
-
);
|
|
16898
|
-
}
|
|
16899
|
-
}
|
|
16900
|
-
opts.onTurnComplete?.();
|
|
16901
|
-
} else if (sawContent && !suppressing) {
|
|
16902
|
-
opts.onActivity?.();
|
|
16903
|
-
}
|
|
16904
|
-
}
|
|
16905
|
-
}
|
|
16906
|
-
} catch (err) {
|
|
16907
|
-
if (!abort.signal.aborted) {
|
|
16908
|
-
opts.onError?.(`follower-error: ${err instanceof Error ? err.message : String(err)}`);
|
|
16909
|
-
}
|
|
16910
|
-
} finally {
|
|
16911
|
-
opts.onClose();
|
|
16912
|
-
}
|
|
16913
|
-
})();
|
|
16914
|
-
return abort;
|
|
16915
|
-
}
|
|
16916
|
-
async function fanOut(subscribers, text, onError, tag) {
|
|
16917
|
-
const callbacks = Array.from(subscribers);
|
|
16918
|
-
await Promise.all(
|
|
16919
|
-
callbacks.map(
|
|
16920
|
-
(cb) => Promise.resolve().then(() => cb(text)).catch((err) => {
|
|
16921
|
-
const m = err instanceof Error ? err.message : String(err);
|
|
16922
|
-
console.error(`${tag} subscriber-error message=${m}`);
|
|
16923
|
-
onError?.(`subscriber-error: ${m}`);
|
|
16924
|
-
})
|
|
16925
|
-
)
|
|
16926
|
-
);
|
|
16927
|
-
}
|
|
16928
|
-
|
|
16929
|
-
// app/lib/channel-pty-bridge/file-delivery.ts
|
|
16930
|
-
var SEND_USER_FILE = "SendUserFile";
|
|
16931
|
-
function makeFileDelivery(opts) {
|
|
16932
|
-
const { entry, tag, channel, sendFile } = opts;
|
|
16933
|
-
let failedFiles = [];
|
|
16934
|
-
let attempts = 0;
|
|
16935
|
-
return {
|
|
16936
|
-
isFileDeliveryTool(toolName) {
|
|
16937
|
-
return toolName === SEND_USER_FILE;
|
|
16938
|
-
},
|
|
16939
|
-
async onFileToolUse(use) {
|
|
16940
|
-
if (use.toolName !== SEND_USER_FILE) return;
|
|
16941
|
-
const input = use.input ?? {};
|
|
16942
|
-
const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
|
|
16943
|
-
const caption = typeof input.caption === "string" ? input.caption : void 0;
|
|
16944
|
-
for (let i = 0; i < files.length; i++) {
|
|
16945
|
-
attempts++;
|
|
16946
|
-
const result = await sendFile(files[i], i === 0 ? caption : void 0);
|
|
16947
|
-
const name = files[i].split("/").pop() ?? files[i];
|
|
16948
|
-
console.error(`${tag} op=file-forward channel=${channel} file=${name} outcome=${result.ok ? "ok" : "fail"}`);
|
|
16949
|
-
if (!result.ok) failedFiles.push(files[i]);
|
|
16950
|
-
}
|
|
16951
|
-
},
|
|
16952
|
-
onTurnEnd(firedTools) {
|
|
16953
|
-
const failed = failedFiles;
|
|
16954
|
-
const tried = attempts;
|
|
16955
|
-
failedFiles = [];
|
|
16956
|
-
attempts = 0;
|
|
16957
|
-
const sid = entry.sessionId.slice(0, 8);
|
|
16958
|
-
for (const file of failed) {
|
|
16959
|
-
console.error(
|
|
16960
|
-
`${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
16961
|
-
);
|
|
16962
|
-
}
|
|
16963
|
-
if (firedTools.includes(SEND_USER_FILE) && tried === 0) {
|
|
16964
|
-
console.error(
|
|
16965
|
-
`${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
|
|
16966
|
-
);
|
|
16967
|
-
}
|
|
16968
|
-
}
|
|
16969
|
-
};
|
|
16970
|
-
}
|
|
16971
|
-
|
|
16972
|
-
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
16973
|
-
var TAG30 = "[whatsapp-adaptor]";
|
|
16974
|
-
var SEND_USER_FILE2 = "SendUserFile";
|
|
16975
|
-
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
16976
|
-
function platformRoot() {
|
|
16977
|
-
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
16978
|
-
}
|
|
16979
|
-
function makeWhatsAppSendFile(entry) {
|
|
16980
|
-
return async (filePath, caption) => {
|
|
16981
|
-
let maxyAccountId;
|
|
16982
|
-
try {
|
|
16983
|
-
maxyAccountId = resolvePlatformAccountId();
|
|
16984
|
-
} catch (err) {
|
|
16985
|
-
console.error(
|
|
16986
|
-
`${TAG30} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
16987
|
-
);
|
|
16988
|
-
return { ok: false, error: "account-unresolved" };
|
|
16989
|
-
}
|
|
16990
|
-
const result = await sendWhatsAppDocument({
|
|
16991
|
-
to: entry.senderId,
|
|
16992
|
-
filePath,
|
|
16993
|
-
caption,
|
|
16994
|
-
accountId: entry.accountId,
|
|
16995
|
-
maxyAccountId,
|
|
16996
|
-
platformRoot: platformRoot()
|
|
16997
|
-
});
|
|
16998
|
-
if (result.ok) return { ok: true };
|
|
16999
|
-
console.error(
|
|
17000
|
-
`${TAG30} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
17001
|
-
);
|
|
17002
|
-
return { ok: false, error: result.error };
|
|
17003
|
-
};
|
|
17004
|
-
}
|
|
17005
|
-
function makeWhatsAppFileDelivery(entry) {
|
|
17006
|
-
const shared = makeFileDelivery({
|
|
17007
|
-
entry,
|
|
17008
|
-
tag: TAG30,
|
|
17009
|
-
channel: "whatsapp",
|
|
17010
|
-
sendFile: makeWhatsAppSendFile(entry)
|
|
17011
|
-
});
|
|
17012
|
-
let turnStartedAt = null;
|
|
17013
|
-
let routeCalls = [];
|
|
17014
|
-
return {
|
|
17015
|
-
isFileDeliveryTool(toolName) {
|
|
17016
|
-
return toolName === SEND_USER_FILE2 || toolName === WHATSAPP_SEND_DOCUMENT;
|
|
17017
|
-
},
|
|
17018
|
-
async onFileToolUse(use) {
|
|
17019
|
-
if (turnStartedAt === null) turnStartedAt = Date.now();
|
|
17020
|
-
if (use.toolName === WHATSAPP_SEND_DOCUMENT) {
|
|
17021
|
-
const input = use.input ?? {};
|
|
17022
|
-
routeCalls.push({
|
|
17023
|
-
to: typeof input.to === "string" ? input.to : void 0,
|
|
17024
|
-
filePath: typeof input.filePath === "string" ? input.filePath : void 0
|
|
17025
|
-
});
|
|
17026
|
-
return;
|
|
17027
|
-
}
|
|
17028
|
-
await shared.onFileToolUse(use);
|
|
17029
|
-
},
|
|
17030
|
-
onTurnEnd(firedTools) {
|
|
17031
|
-
const startedAt = turnStartedAt ?? 0;
|
|
17032
|
-
const routes = routeCalls;
|
|
17033
|
-
turnStartedAt = null;
|
|
17034
|
-
routeCalls = [];
|
|
17035
|
-
const sid = entry.sessionId.slice(0, 8);
|
|
17036
|
-
shared.onTurnEnd(firedTools);
|
|
17037
|
-
for (const call of routes) {
|
|
17038
|
-
const routeAt = call.to !== void 0 && call.filePath !== void 0 ? routeDocumentOutboundAt(call.to, call.filePath) : void 0;
|
|
17039
|
-
const delivered = routeAt !== void 0 && routeAt >= startedAt;
|
|
17040
|
-
if (!delivered) {
|
|
17041
|
-
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
17042
|
-
console.error(
|
|
17043
|
-
`${TAG30} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
17044
|
-
);
|
|
17045
|
-
}
|
|
17046
|
-
}
|
|
17047
|
-
}
|
|
17048
|
-
};
|
|
17049
|
-
}
|
|
17050
|
-
|
|
17051
|
-
// app/lib/telegram/outbound/send-document.ts
|
|
17052
|
-
import { realpathSync as realpathSync5 } from "fs";
|
|
17053
|
-
import { readFile as readFile6, stat as stat6 } from "fs/promises";
|
|
17054
|
-
import { resolve as resolve24, basename as basename9 } from "path";
|
|
17055
|
-
var TAG31 = "[telegram:outbound]";
|
|
17056
|
-
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
17057
|
-
async function sendTelegramDocument(input) {
|
|
17058
|
-
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot3 } = input;
|
|
17059
|
-
if (!botToken || !filePath) {
|
|
17060
|
-
return { ok: false, status: 400, error: "Missing required fields: botToken, filePath" };
|
|
17061
|
-
}
|
|
17062
|
-
if (!maxyAccountId || !platformRoot3) {
|
|
17063
|
-
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
17064
|
-
}
|
|
17065
|
-
const accountDir = resolve24(platformRoot3, "..", "data/accounts", maxyAccountId);
|
|
17066
|
-
let resolvedPath;
|
|
17067
|
-
try {
|
|
17068
|
-
resolvedPath = realpathSync5(filePath);
|
|
17069
|
-
const accountResolved = realpathSync5(accountDir);
|
|
17070
|
-
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
17071
|
-
console.error(`${TAG31} document REJECTED reason=outside_account_directory`);
|
|
17072
|
-
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
17073
|
-
}
|
|
17074
|
-
} catch (err) {
|
|
17075
|
-
const code = err.code;
|
|
17076
|
-
if (code === "ENOENT") {
|
|
17077
|
-
console.error(`${TAG31} document ENOENT path=${filePath}`);
|
|
17078
|
-
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
17079
|
-
}
|
|
17080
|
-
console.error(`${TAG31} document path error: ${String(err)}`);
|
|
17081
|
-
return { ok: false, status: 500, error: String(err) };
|
|
17082
|
-
}
|
|
17083
|
-
const fileStat = await stat6(resolvedPath);
|
|
17084
|
-
if (fileStat.size > TELEGRAM_DOCUMENT_MAX_BYTES) {
|
|
17085
|
-
return {
|
|
17086
|
-
ok: false,
|
|
17087
|
-
status: 400,
|
|
17088
|
-
error: `File exceeds 50 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
|
|
17089
|
-
};
|
|
17090
|
-
}
|
|
17091
|
-
const filename = basename9(resolvedPath);
|
|
17092
|
-
const buffer = Buffer.from(await readFile6(resolvedPath));
|
|
17093
|
-
const form = new FormData();
|
|
17094
|
-
form.append("chat_id", String(chatId));
|
|
17095
|
-
if (caption) form.append("caption", caption);
|
|
17096
|
-
form.append("document", new File([buffer], filename, { type: detectMimeType(resolvedPath) }));
|
|
17097
|
-
let ok = false;
|
|
17098
|
-
let messageId;
|
|
17099
|
-
let error = "send failed";
|
|
17100
|
-
try {
|
|
17101
|
-
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendDocument`, {
|
|
17102
|
-
method: "POST",
|
|
17103
|
-
body: form
|
|
17104
|
-
});
|
|
17105
|
-
const data = await res.json();
|
|
17106
|
-
ok = data.ok;
|
|
17107
|
-
messageId = data.result?.message_id;
|
|
17108
|
-
if (!data.ok) error = data.description ?? "Unknown Telegram error";
|
|
17109
|
-
} catch (e) {
|
|
17110
|
-
error = e instanceof Error ? e.message : String(e);
|
|
17111
|
-
}
|
|
17112
|
-
console.error(
|
|
17113
|
-
`${TAG31} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
17114
|
-
);
|
|
17115
|
-
return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
17116
|
-
}
|
|
17117
|
-
|
|
17118
|
-
// app/lib/telegram/outbound/file-delivery.ts
|
|
17119
|
-
var TAG32 = "[telegram:outbound]";
|
|
17120
|
-
function platformRoot2() {
|
|
17121
|
-
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
17122
|
-
}
|
|
17123
|
-
function makeTelegramSendFile(entry) {
|
|
17124
|
-
let botToken;
|
|
17125
|
-
return async (filePath, caption) => {
|
|
17126
|
-
if (botToken === void 0) {
|
|
17127
|
-
const tg = resolveAccount()?.config.telegram;
|
|
17128
|
-
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
17129
|
-
}
|
|
17130
|
-
if (!botToken) {
|
|
17131
|
-
console.error(`${TAG32} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
17132
|
-
return { ok: false, error: "no-bot-token" };
|
|
17133
|
-
}
|
|
17134
|
-
if (entry.replyTarget == null) {
|
|
17135
|
-
console.error(`${TAG32} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
17136
|
-
return { ok: false, error: "no-reply-target" };
|
|
17137
|
-
}
|
|
17138
|
-
let maxyAccountId;
|
|
17139
|
-
try {
|
|
17140
|
-
maxyAccountId = resolvePlatformAccountId();
|
|
17141
|
-
} catch (err) {
|
|
17142
|
-
console.error(
|
|
17143
|
-
`${TAG32} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
17144
|
-
);
|
|
17145
|
-
return { ok: false, error: "account-unresolved" };
|
|
17146
|
-
}
|
|
17147
|
-
const result = await sendTelegramDocument({
|
|
17148
|
-
botToken,
|
|
17149
|
-
chatId: Number(entry.replyTarget),
|
|
17150
|
-
filePath,
|
|
17151
|
-
caption,
|
|
17152
|
-
maxyAccountId,
|
|
17153
|
-
platformRoot: platformRoot2()
|
|
17154
|
-
});
|
|
17155
|
-
return result.ok ? { ok: true } : { ok: false, error: result.error };
|
|
17156
|
-
};
|
|
17157
|
-
}
|
|
17158
|
-
function makeTelegramFileDelivery(entry) {
|
|
17159
|
-
return makeFileDelivery({
|
|
17160
|
-
entry,
|
|
17161
|
-
tag: TAG32,
|
|
17162
|
-
channel: "telegram",
|
|
17163
|
-
sendFile: makeTelegramSendFile(entry)
|
|
17164
|
-
});
|
|
17165
|
-
}
|
|
17166
|
-
|
|
17167
|
-
// app/lib/webchat/file-delivery.ts
|
|
17168
|
-
import { realpathSync as realpathSync6 } from "fs";
|
|
17169
|
-
import { resolve as resolve25 } from "path";
|
|
17170
|
-
|
|
17171
|
-
// app/lib/channel-pty-bridge/bridge.ts
|
|
17172
|
-
function tagFor(channel) {
|
|
17173
|
-
return `[${channel}-adaptor]`;
|
|
17174
|
-
}
|
|
17175
|
-
function publicIdleMs() {
|
|
17176
|
-
return Number(process.env.CHANNEL_PTY_IDLE_MS ?? String(5 * 6e4));
|
|
17177
|
-
}
|
|
17178
|
-
var index = /* @__PURE__ */ new Map();
|
|
17179
|
-
var reaperHandle = null;
|
|
17180
|
-
function startReaper() {
|
|
17181
|
-
if (reaperHandle) return;
|
|
17182
|
-
reaperHandle = setInterval(() => {
|
|
17183
|
-
const now = Date.now();
|
|
17184
|
-
const cutoff = now - publicIdleMs();
|
|
17185
|
-
for (const [key, entry] of index) {
|
|
17186
|
-
if (entry.role !== "public") continue;
|
|
17187
|
-
if (entry.subscribers.size > 0) continue;
|
|
17188
|
-
if (entry.lastInboundAt > cutoff) continue;
|
|
17189
|
-
const idleMs = now - entry.lastInboundAt;
|
|
17190
|
-
const tag = tagFor(entry.channel);
|
|
17191
|
-
console.error(
|
|
17192
|
-
`${tag} reap senderId=${entry.senderId} idle-ms=${idleMs} sessionId=${entry.sessionId.slice(0, 8)}`
|
|
17193
|
-
);
|
|
17194
|
-
if (entry.followerAbort) entry.followerAbort.abort();
|
|
17195
|
-
index.delete(key);
|
|
17196
|
-
if (entry.role === "public" && entry.sliceToken.length > 0) {
|
|
17197
|
-
void firePublicSessionEndReview({
|
|
17198
|
-
sessionId: entry.sessionId,
|
|
17199
|
-
sliceToken: entry.sliceToken,
|
|
17200
|
-
personId: entry.personId,
|
|
17201
|
-
accountId: entry.accountId,
|
|
17202
|
-
agentSlug: entry.agentSlug,
|
|
17203
|
-
dispatchFor: "eviction"
|
|
17204
|
-
});
|
|
17205
|
-
}
|
|
17206
|
-
void managerDelete(entry.sessionId);
|
|
17207
|
-
}
|
|
17208
|
-
}, 6e4);
|
|
17209
|
-
if (reaperHandle.unref) reaperHandle.unref();
|
|
17210
|
-
}
|
|
17211
|
-
function handlePublicSessionExit(sessionId) {
|
|
17212
|
-
let found = null;
|
|
17213
|
-
for (const [key2, entry2] of index) {
|
|
17214
|
-
if (entry2.sessionId === sessionId) {
|
|
17215
|
-
found = { key: key2, entry: entry2 };
|
|
17216
|
-
break;
|
|
17217
|
-
}
|
|
17218
|
-
}
|
|
17219
|
-
if (!found) {
|
|
17220
|
-
console.error(`[public-session-exit] sessionId=${sessionId} result=no-entry`);
|
|
17221
|
-
return;
|
|
17222
|
-
}
|
|
17223
|
-
const { key, entry } = found;
|
|
17224
|
-
if (entry.role !== "public" || entry.sliceToken.length === 0) {
|
|
17225
|
-
return;
|
|
17226
|
-
}
|
|
17227
|
-
if (entry.followerAbort) entry.followerAbort.abort();
|
|
17228
|
-
index.delete(key);
|
|
17229
|
-
void firePublicSessionEndReview({
|
|
17230
|
-
sessionId: entry.sessionId,
|
|
17231
|
-
sliceToken: entry.sliceToken,
|
|
17232
|
-
personId: entry.personId,
|
|
17233
|
-
accountId: entry.accountId,
|
|
17234
|
-
agentSlug: entry.agentSlug,
|
|
17235
|
-
dispatchFor: "exit"
|
|
17236
|
-
});
|
|
17237
|
-
}
|
|
17238
|
-
|
|
17239
|
-
// server/routes/admin/public-session-exit.ts
|
|
17240
|
-
var TAG33 = "[public-session-exit-route]";
|
|
17241
|
-
function isLoopbackAddr4(addr) {
|
|
17242
|
-
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17243
|
-
}
|
|
17244
|
-
var app39 = new Hono();
|
|
17245
|
-
app39.post("/", async (c) => {
|
|
17246
|
-
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17247
|
-
if (!isLoopbackAddr4(remoteAddr)) {
|
|
17248
|
-
console.error(`${TAG33} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17249
|
-
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17250
|
-
}
|
|
17251
|
-
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17252
|
-
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17253
|
-
if (xffRaw.length > 0) {
|
|
17254
|
-
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17255
|
-
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
17256
|
-
if (offender !== void 0) {
|
|
17257
|
-
console.error(`${TAG33} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17258
|
-
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
17259
|
-
}
|
|
17260
|
-
}
|
|
17261
|
-
let body;
|
|
17262
|
-
try {
|
|
17263
|
-
body = await c.req.json();
|
|
17264
|
-
} catch {
|
|
17265
|
-
return c.json({ error: "invalid-json" }, 400);
|
|
17266
|
-
}
|
|
17267
|
-
const sessionId = typeof body.sessionId === "string" ? body.sessionId.trim() : "";
|
|
17268
|
-
if (!sessionId) return c.json({ error: "sessionId required" }, 400);
|
|
17269
|
-
handlePublicSessionExit(sessionId);
|
|
17270
|
-
return c.json({ ok: true });
|
|
17271
|
-
});
|
|
17272
|
-
var public_session_exit_default = app39;
|
|
17273
|
-
|
|
17274
|
-
// server/routes/admin/access-session-evict.ts
|
|
17275
|
-
var TAG34 = "[access-session-evict]";
|
|
17276
|
-
function isLoopbackAddr5(addr) {
|
|
17277
|
-
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17278
|
-
}
|
|
17279
|
-
var app40 = new Hono();
|
|
17280
|
-
app40.post("/", async (c) => {
|
|
17281
|
-
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17282
|
-
if (!isLoopbackAddr5(remoteAddr)) {
|
|
17283
|
-
console.error(`${TAG34} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17284
|
-
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17285
|
-
}
|
|
17286
|
-
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17287
|
-
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17288
|
-
if (xffRaw.length > 0) {
|
|
17289
|
-
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17290
|
-
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
17291
|
-
if (offender !== void 0) {
|
|
17292
|
-
console.error(`${TAG34} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17293
|
-
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
17294
|
-
}
|
|
17295
|
-
}
|
|
17296
|
-
let body;
|
|
17297
|
-
try {
|
|
17298
|
-
body = await c.req.json();
|
|
17299
|
-
} catch {
|
|
17300
|
-
return c.json({ error: "invalid-json" }, 400);
|
|
17301
|
-
}
|
|
17302
|
-
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
17303
|
-
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
17304
|
-
const dropped = evictAccessSessionsByGrant(grantId);
|
|
17305
|
-
console.log(`${TAG34} grantId=${grantId} dropped=${dropped}`);
|
|
17306
|
-
return c.json({ ok: true, dropped });
|
|
17307
|
-
});
|
|
17308
|
-
var access_session_evict_default = app40;
|
|
17309
|
-
|
|
17310
|
-
// server/routes/admin/enrol-person.ts
|
|
17311
|
-
var TAG35 = "[enrol]";
|
|
17312
|
-
function isLoopbackAddr6(addr) {
|
|
17313
|
-
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
17314
|
-
}
|
|
17315
|
-
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17316
|
-
var app41 = new Hono();
|
|
17317
|
-
app41.post("/", async (c) => {
|
|
17318
|
-
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
17319
|
-
if (!isLoopbackAddr6(remoteAddr)) {
|
|
17320
|
-
console.error(`${TAG35} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
17321
|
-
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17322
|
-
}
|
|
17323
|
-
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
17324
|
-
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
17325
|
-
if (xffRaw.length > 0) {
|
|
17326
|
-
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
17327
|
-
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
17328
|
-
if (offender !== void 0) {
|
|
17329
|
-
console.error(`${TAG35} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
17330
|
-
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
17331
|
-
}
|
|
17332
|
-
}
|
|
17333
|
-
let body;
|
|
17334
|
-
try {
|
|
17335
|
-
body = await c.req.json();
|
|
17336
|
-
} catch {
|
|
17337
|
-
return c.json({ error: "invalid-json" }, 400);
|
|
17338
|
-
}
|
|
17339
|
-
const rawPhone = typeof body.phone === "string" ? body.phone : "";
|
|
17340
|
-
const phone = normalizeE164(rawPhone);
|
|
17341
|
-
if (phone.length <= 1) {
|
|
17342
|
-
return c.json({ error: "invalid-phone" }, 400);
|
|
17343
|
-
}
|
|
17344
|
-
const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
|
|
17345
|
-
if (!EMAIL_RE.test(email)) {
|
|
17346
|
-
return c.json({ error: "invalid-email" }, 400);
|
|
17347
|
-
}
|
|
17348
|
-
const agentSlug = typeof body.agentSlug === "string" ? body.agentSlug.trim() : "";
|
|
17349
|
-
if (!agentSlug) {
|
|
17350
|
-
return c.json({ error: "agentSlug required" }, 400);
|
|
17351
|
-
}
|
|
17352
|
-
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
17353
|
-
if (!accountId) {
|
|
17354
|
-
console.error(`${TAG35} op=person result=no-account-id`);
|
|
17355
|
-
return c.json({ error: "no-account-id" }, 500);
|
|
17356
|
-
}
|
|
17357
|
-
let personId;
|
|
17358
|
-
try {
|
|
17359
|
-
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
17360
|
-
} catch (err) {
|
|
17361
|
-
console.error(
|
|
17362
|
-
`${TAG35} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17363
|
-
);
|
|
17364
|
-
return c.json({ error: "enrol-failed" }, 500);
|
|
16786
|
+
return c.json({ error: "enrol-failed" }, 500);
|
|
17365
16787
|
}
|
|
17366
16788
|
let grant = null;
|
|
17367
16789
|
try {
|
|
@@ -17369,11 +16791,11 @@ app41.post("/", async (c) => {
|
|
|
17369
16791
|
if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
|
|
17370
16792
|
} catch (err) {
|
|
17371
16793
|
console.error(
|
|
17372
|
-
`${
|
|
16794
|
+
`${TAG31} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
17373
16795
|
);
|
|
17374
16796
|
}
|
|
17375
16797
|
console.log(
|
|
17376
|
-
`${
|
|
16798
|
+
`${TAG31} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
|
|
17377
16799
|
);
|
|
17378
16800
|
return c.json({ personId, grant }, 200);
|
|
17379
16801
|
});
|
|
@@ -17411,6 +16833,10 @@ app42.post("/launch", requireAdminSession, async (c) => {
|
|
|
17411
16833
|
});
|
|
17412
16834
|
var browser_default = app42;
|
|
17413
16835
|
|
|
16836
|
+
// server/routes/admin/calendar.ts
|
|
16837
|
+
import { existsSync as existsSync26 } from "fs";
|
|
16838
|
+
import { join as join28 } from "path";
|
|
16839
|
+
|
|
17414
16840
|
// server/lib/calendar-ics.ts
|
|
17415
16841
|
var CRLF = "\r\n";
|
|
17416
16842
|
var PRODID = "-//Maxy//Calendar//EN";
|
|
@@ -17495,16 +16921,59 @@ function countVEvents(ics) {
|
|
|
17495
16921
|
nested = 0;
|
|
17496
16922
|
continue;
|
|
17497
16923
|
}
|
|
17498
|
-
if (!inEvent) continue;
|
|
17499
|
-
if (line === "END:VEVENT") {
|
|
17500
|
-
inEvent = false;
|
|
17501
|
-
count++;
|
|
17502
|
-
continue;
|
|
16924
|
+
if (!inEvent) continue;
|
|
16925
|
+
if (line === "END:VEVENT") {
|
|
16926
|
+
inEvent = false;
|
|
16927
|
+
count++;
|
|
16928
|
+
continue;
|
|
16929
|
+
}
|
|
16930
|
+
if (line.startsWith("BEGIN:")) nested++;
|
|
16931
|
+
else if (line.startsWith("END:") && nested > 0) nested--;
|
|
16932
|
+
}
|
|
16933
|
+
return count;
|
|
16934
|
+
}
|
|
16935
|
+
|
|
16936
|
+
// server/lib/calendar-availability.ts
|
|
16937
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
16938
|
+
import { join as join27 } from "path";
|
|
16939
|
+
var AVAILABILITY_FILENAME = "calendar-availability.json";
|
|
16940
|
+
var REQUIRED_KEYS = [
|
|
16941
|
+
"timezone",
|
|
16942
|
+
"durationMins",
|
|
16943
|
+
"bufferMins",
|
|
16944
|
+
"weekly"
|
|
16945
|
+
];
|
|
16946
|
+
function readAvailabilityConfig(accountDir) {
|
|
16947
|
+
const path2 = join27(accountDir, AVAILABILITY_FILENAME);
|
|
16948
|
+
let raw;
|
|
16949
|
+
try {
|
|
16950
|
+
raw = readFileSync28(path2, "utf-8");
|
|
16951
|
+
} catch {
|
|
16952
|
+
throw new Error(`availability not configured: ${path2} not found`);
|
|
16953
|
+
}
|
|
16954
|
+
let parsed;
|
|
16955
|
+
try {
|
|
16956
|
+
parsed = JSON.parse(raw);
|
|
16957
|
+
} catch (err) {
|
|
16958
|
+
throw new Error(`invalid JSON in ${path2}: ${err.message}`);
|
|
16959
|
+
}
|
|
16960
|
+
const cfg = parsed;
|
|
16961
|
+
for (const key of REQUIRED_KEYS) {
|
|
16962
|
+
if (cfg[key] === void 0 || cfg[key] === null) {
|
|
16963
|
+
throw new Error(`availability config ${path2} missing required key: ${key}`);
|
|
16964
|
+
}
|
|
16965
|
+
}
|
|
16966
|
+
if (cfg.daysAhead !== void 0 && cfg.daysAhead !== null) {
|
|
16967
|
+
if (typeof cfg.daysAhead !== "number" || !Number.isInteger(cfg.daysAhead) || cfg.daysAhead < 1) {
|
|
16968
|
+
throw new Error(`availability config ${path2} invalid daysAhead: must be a positive integer`);
|
|
17503
16969
|
}
|
|
17504
|
-
if (line.startsWith("BEGIN:")) nested++;
|
|
17505
|
-
else if (line.startsWith("END:") && nested > 0) nested--;
|
|
17506
16970
|
}
|
|
17507
|
-
|
|
16971
|
+
if (cfg.allowMultiSlot !== void 0 && cfg.allowMultiSlot !== null) {
|
|
16972
|
+
if (typeof cfg.allowMultiSlot !== "boolean") {
|
|
16973
|
+
throw new Error(`availability config ${path2} invalid allowMultiSlot: must be a boolean`);
|
|
16974
|
+
}
|
|
16975
|
+
}
|
|
16976
|
+
return cfg;
|
|
17508
16977
|
}
|
|
17509
16978
|
|
|
17510
16979
|
// server/routes/admin/calendar.ts
|
|
@@ -17830,6 +17299,29 @@ app43.delete("/meetings/:id", requireAdminSession, async (c) => {
|
|
|
17830
17299
|
await session.close();
|
|
17831
17300
|
}
|
|
17832
17301
|
});
|
|
17302
|
+
app43.get("/booking-link", requireAdminSession, (c) => {
|
|
17303
|
+
try {
|
|
17304
|
+
const account = resolveAccount();
|
|
17305
|
+
if (!account) {
|
|
17306
|
+
console.error('[calendar] op=booking-link-fail err="no account configured"');
|
|
17307
|
+
return c.json({ bookingDomain: null });
|
|
17308
|
+
}
|
|
17309
|
+
if (!existsSync26(join28(account.accountDir, AVAILABILITY_FILENAME))) {
|
|
17310
|
+
console.log("[calendar] op=booking-link bookingDomain=none source=availability-config");
|
|
17311
|
+
return c.json({ bookingDomain: null });
|
|
17312
|
+
}
|
|
17313
|
+
const config = readAvailabilityConfig(account.accountDir);
|
|
17314
|
+
const bookingDomain = typeof config.bookingDomain === "string" && config.bookingDomain.trim() ? config.bookingDomain.trim() : null;
|
|
17315
|
+
console.log(
|
|
17316
|
+
`[calendar] op=booking-link bookingDomain=${bookingDomain ?? "none"} source=availability-config`
|
|
17317
|
+
);
|
|
17318
|
+
return c.json({ bookingDomain });
|
|
17319
|
+
} catch (err) {
|
|
17320
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17321
|
+
console.error(`[calendar] op=booking-link-fail err="${message}"`);
|
|
17322
|
+
return c.json({ bookingDomain: null });
|
|
17323
|
+
}
|
|
17324
|
+
});
|
|
17833
17325
|
var calendar_default = app43;
|
|
17834
17326
|
|
|
17835
17327
|
// server/routes/admin/index.ts
|
|
@@ -17871,7 +17363,7 @@ app44.route("/calendar", calendar_default);
|
|
|
17871
17363
|
var admin_default = app44;
|
|
17872
17364
|
|
|
17873
17365
|
// server/routes/access/verify-token.ts
|
|
17874
|
-
var
|
|
17366
|
+
var TAG32 = "[access-verify]";
|
|
17875
17367
|
var MINT_TAG = "[access-session-mint]";
|
|
17876
17368
|
var COOKIE_NAME = "__access_session";
|
|
17877
17369
|
var app45 = new Hono();
|
|
@@ -17890,39 +17382,39 @@ app45.post("/", async (c) => {
|
|
|
17890
17382
|
}
|
|
17891
17383
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
17892
17384
|
if (rateMsg) {
|
|
17893
|
-
console.error(`${
|
|
17385
|
+
console.error(`${TAG32} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
17894
17386
|
return c.json({ error: rateMsg }, 429);
|
|
17895
17387
|
}
|
|
17896
17388
|
const grant = await findGrantByMagicToken(token);
|
|
17897
17389
|
if (!grant) {
|
|
17898
17390
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
17899
|
-
console.error(`${
|
|
17391
|
+
console.error(`${TAG32} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
17900
17392
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
17901
17393
|
}
|
|
17902
17394
|
if (grant.agentSlug !== agentSlug) {
|
|
17903
17395
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
17904
17396
|
console.error(
|
|
17905
|
-
`${
|
|
17397
|
+
`${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
17906
17398
|
);
|
|
17907
17399
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
17908
17400
|
}
|
|
17909
17401
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
17910
17402
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
17911
17403
|
console.error(
|
|
17912
|
-
`${
|
|
17404
|
+
`${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
17913
17405
|
);
|
|
17914
17406
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
17915
17407
|
}
|
|
17916
17408
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
17917
17409
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
17918
17410
|
console.error(
|
|
17919
|
-
`${
|
|
17411
|
+
`${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
17920
17412
|
);
|
|
17921
17413
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
17922
17414
|
}
|
|
17923
17415
|
if (!grant.sliceToken) {
|
|
17924
17416
|
console.error(
|
|
17925
|
-
`${
|
|
17417
|
+
`${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
17926
17418
|
);
|
|
17927
17419
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
17928
17420
|
}
|
|
@@ -17938,12 +17430,12 @@ app45.post("/", async (c) => {
|
|
|
17938
17430
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
17939
17431
|
} catch (err) {
|
|
17940
17432
|
console.error(
|
|
17941
|
-
`${
|
|
17433
|
+
`${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
17942
17434
|
);
|
|
17943
17435
|
return c.json({ error: "verification-failed" }, 500);
|
|
17944
17436
|
}
|
|
17945
17437
|
clearAccessRateLimit(ip, agentSlug);
|
|
17946
|
-
console.log(`${
|
|
17438
|
+
console.log(`${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
17947
17439
|
console.log(
|
|
17948
17440
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
17949
17441
|
);
|
|
@@ -17961,9 +17453,9 @@ var verify_token_default = app45;
|
|
|
17961
17453
|
|
|
17962
17454
|
// app/lib/access-email.ts
|
|
17963
17455
|
import { spawn as spawn2 } from "child_process";
|
|
17964
|
-
import { resolve as
|
|
17965
|
-
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ??
|
|
17966
|
-
var SEND_SCRIPT =
|
|
17456
|
+
import { resolve as resolve24 } from "path";
|
|
17457
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
|
|
17458
|
+
var SEND_SCRIPT = resolve24(
|
|
17967
17459
|
PLATFORM_ROOT7,
|
|
17968
17460
|
"plugins",
|
|
17969
17461
|
"email",
|
|
@@ -18019,7 +17511,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
18019
17511
|
}
|
|
18020
17512
|
|
|
18021
17513
|
// server/routes/access/request-magic-link.ts
|
|
18022
|
-
var
|
|
17514
|
+
var TAG33 = "[access-request-link]";
|
|
18023
17515
|
var app46 = new Hono();
|
|
18024
17516
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
18025
17517
|
app46.post("/", async (c) => {
|
|
@@ -18036,18 +17528,18 @@ app46.post("/", async (c) => {
|
|
|
18036
17528
|
}
|
|
18037
17529
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
18038
17530
|
if (rateMsg) {
|
|
18039
|
-
console.error(`${
|
|
17531
|
+
console.error(`${TAG33} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
18040
17532
|
return c.json({ error: rateMsg }, 429);
|
|
18041
17533
|
}
|
|
18042
17534
|
recordRequestLinkAttempt(contactValue);
|
|
18043
17535
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
18044
17536
|
if (!accountId) {
|
|
18045
|
-
console.error(`${
|
|
17537
|
+
console.error(`${TAG33} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
18046
17538
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18047
17539
|
}
|
|
18048
17540
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
18049
17541
|
if (!grant) {
|
|
18050
|
-
console.log(`${
|
|
17542
|
+
console.log(`${TAG33} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
18051
17543
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18052
17544
|
}
|
|
18053
17545
|
let token;
|
|
@@ -18055,7 +17547,7 @@ app46.post("/", async (c) => {
|
|
|
18055
17547
|
token = await generateNewMagicToken(grant.grantId);
|
|
18056
17548
|
} catch (err) {
|
|
18057
17549
|
console.error(
|
|
18058
|
-
`${
|
|
17550
|
+
`${TAG33} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
18059
17551
|
);
|
|
18060
17552
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18061
17553
|
}
|
|
@@ -18087,12 +17579,12 @@ app46.post("/", async (c) => {
|
|
|
18087
17579
|
});
|
|
18088
17580
|
if (!sendResult.ok) {
|
|
18089
17581
|
console.error(
|
|
18090
|
-
`${
|
|
17582
|
+
`${TAG33} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
18091
17583
|
);
|
|
18092
17584
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18093
17585
|
}
|
|
18094
17586
|
console.log(
|
|
18095
|
-
`${
|
|
17587
|
+
`${TAG33} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
18096
17588
|
);
|
|
18097
17589
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
18098
17590
|
});
|
|
@@ -18105,8 +17597,8 @@ app47.route("/request-magic-link", request_magic_link_default);
|
|
|
18105
17597
|
var access_default = app47;
|
|
18106
17598
|
|
|
18107
17599
|
// server/routes/sites.ts
|
|
18108
|
-
import { existsSync as
|
|
18109
|
-
import { resolve as
|
|
17600
|
+
import { existsSync as existsSync27, readFileSync as readFileSync29, realpathSync as realpathSync5, statSync as statSync11 } from "fs";
|
|
17601
|
+
import { resolve as resolve25 } from "path";
|
|
18110
17602
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
18111
17603
|
var MIME = {
|
|
18112
17604
|
".html": "text/html; charset=utf-8",
|
|
@@ -18163,15 +17655,15 @@ app48.get("/:rel{.*}", (c) => {
|
|
|
18163
17655
|
}
|
|
18164
17656
|
segments.push(seg);
|
|
18165
17657
|
}
|
|
18166
|
-
const rootDir =
|
|
18167
|
-
let filePath = segments.length === 0 ? rootDir :
|
|
17658
|
+
const rootDir = resolve25(account.accountDir, "sites");
|
|
17659
|
+
let filePath = segments.length === 0 ? rootDir : resolve25(rootDir, ...segments);
|
|
18168
17660
|
if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
|
|
18169
17661
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
18170
17662
|
return c.text("Forbidden", 403);
|
|
18171
17663
|
}
|
|
18172
17664
|
let stat8;
|
|
18173
17665
|
try {
|
|
18174
|
-
stat8 =
|
|
17666
|
+
stat8 = existsSync27(filePath) ? statSync11(filePath) : null;
|
|
18175
17667
|
} catch {
|
|
18176
17668
|
stat8 = null;
|
|
18177
17669
|
}
|
|
@@ -18184,21 +17676,21 @@ app48.get("/:rel{.*}", (c) => {
|
|
|
18184
17676
|
return c.redirect(target, 301);
|
|
18185
17677
|
}
|
|
18186
17678
|
if (stat8?.isDirectory()) {
|
|
18187
|
-
filePath =
|
|
17679
|
+
filePath = resolve25(filePath, "index.html");
|
|
18188
17680
|
}
|
|
18189
17681
|
if (!filePath.startsWith(rootDir + "/")) {
|
|
18190
17682
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
18191
17683
|
return c.text("Forbidden", 403);
|
|
18192
17684
|
}
|
|
18193
|
-
if (!
|
|
17685
|
+
if (!existsSync27(filePath)) {
|
|
18194
17686
|
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
18195
17687
|
return c.text("Not found", 404);
|
|
18196
17688
|
}
|
|
18197
17689
|
let realPath;
|
|
18198
17690
|
let realRoot;
|
|
18199
17691
|
try {
|
|
18200
|
-
realPath =
|
|
18201
|
-
realRoot =
|
|
17692
|
+
realPath = realpathSync5(filePath);
|
|
17693
|
+
realRoot = realpathSync5(rootDir);
|
|
18202
17694
|
} catch {
|
|
18203
17695
|
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
18204
17696
|
return c.text("Not found", 404);
|
|
@@ -18209,7 +17701,7 @@ app48.get("/:rel{.*}", (c) => {
|
|
|
18209
17701
|
}
|
|
18210
17702
|
let body;
|
|
18211
17703
|
try {
|
|
18212
|
-
body =
|
|
17704
|
+
body = readFileSync29(realPath);
|
|
18213
17705
|
} catch (err) {
|
|
18214
17706
|
const code = err?.code;
|
|
18215
17707
|
if (code === "EISDIR") {
|
|
@@ -18245,7 +17737,7 @@ var sites_default = app48;
|
|
|
18245
17737
|
|
|
18246
17738
|
// app/lib/visitor-token.ts
|
|
18247
17739
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
18248
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
17740
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync30, writeFileSync as writeFileSync11 } from "fs";
|
|
18249
17741
|
import { dirname as dirname7 } from "path";
|
|
18250
17742
|
var TOKEN_PREFIX = "v1.";
|
|
18251
17743
|
var SECRET_BYTES = 32;
|
|
@@ -18254,7 +17746,7 @@ var cachedSecret = null;
|
|
|
18254
17746
|
function getSecret() {
|
|
18255
17747
|
if (cachedSecret) return cachedSecret;
|
|
18256
17748
|
try {
|
|
18257
|
-
const hex2 =
|
|
17749
|
+
const hex2 = readFileSync30(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
18258
17750
|
if (hex2.length === SECRET_BYTES * 2) {
|
|
18259
17751
|
cachedSecret = Buffer.from(hex2, "hex");
|
|
18260
17752
|
return cachedSecret;
|
|
@@ -18268,7 +17760,7 @@ function getSecret() {
|
|
|
18268
17760
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
18269
17761
|
} catch {
|
|
18270
17762
|
}
|
|
18271
|
-
const hex =
|
|
17763
|
+
const hex = readFileSync30(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
18272
17764
|
cachedSecret = Buffer.from(hex, "hex");
|
|
18273
17765
|
return cachedSecret;
|
|
18274
17766
|
}
|
|
@@ -18321,8 +17813,8 @@ var VISITOR_COOKIE_NAME = "mxy_v";
|
|
|
18321
17813
|
var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
18322
17814
|
|
|
18323
17815
|
// app/lib/brand-config.ts
|
|
18324
|
-
import { existsSync as
|
|
18325
|
-
import { join as
|
|
17816
|
+
import { existsSync as existsSync28, readFileSync as readFileSync31 } from "fs";
|
|
17817
|
+
import { join as join29 } from "path";
|
|
18326
17818
|
var cached2 = null;
|
|
18327
17819
|
var cachedAttempted = false;
|
|
18328
17820
|
function readBrandConfig() {
|
|
@@ -18330,10 +17822,10 @@ function readBrandConfig() {
|
|
|
18330
17822
|
cachedAttempted = true;
|
|
18331
17823
|
const platformRoot3 = process.env.MAXY_PLATFORM_ROOT;
|
|
18332
17824
|
if (!platformRoot3) return null;
|
|
18333
|
-
const brandPath =
|
|
18334
|
-
if (!
|
|
17825
|
+
const brandPath = join29(platformRoot3, "config", "brand.json");
|
|
17826
|
+
if (!existsSync28(brandPath)) return null;
|
|
18335
17827
|
try {
|
|
18336
|
-
cached2 = JSON.parse(
|
|
17828
|
+
cached2 = JSON.parse(readFileSync31(brandPath, "utf-8"));
|
|
18337
17829
|
return cached2;
|
|
18338
17830
|
} catch {
|
|
18339
17831
|
return null;
|
|
@@ -18821,14 +18313,14 @@ async function writeEvent(opts) {
|
|
|
18821
18313
|
var visitor_event_default = app51;
|
|
18822
18314
|
|
|
18823
18315
|
// server/routes/session.ts
|
|
18824
|
-
import { resolve as
|
|
18825
|
-
import { existsSync as
|
|
18316
|
+
import { resolve as resolve26 } from "path";
|
|
18317
|
+
import { existsSync as existsSync29, writeFileSync as writeFileSync12, mkdirSync as mkdirSync7 } from "fs";
|
|
18826
18318
|
var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
18827
18319
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
18828
18320
|
try {
|
|
18829
|
-
const cacheDir =
|
|
18321
|
+
const cacheDir = resolve26(MAXY_DIR, "branding-cache", accountId);
|
|
18830
18322
|
mkdirSync7(cacheDir, { recursive: true });
|
|
18831
|
-
writeFileSync12(
|
|
18323
|
+
writeFileSync12(resolve26(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
18832
18324
|
} catch (err) {
|
|
18833
18325
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18834
18326
|
}
|
|
@@ -18905,8 +18397,8 @@ app52.post("/", async (c) => {
|
|
|
18905
18397
|
}
|
|
18906
18398
|
let agentConfig = null;
|
|
18907
18399
|
if (account) {
|
|
18908
|
-
const agentDir =
|
|
18909
|
-
if (!
|
|
18400
|
+
const agentDir = resolve26(account.accountDir, "agents", agentSlug);
|
|
18401
|
+
if (!existsSync29(agentDir) || !existsSync29(resolve26(agentDir, "config.json"))) {
|
|
18910
18402
|
return c.json({ error: "Agent not found" }, 404);
|
|
18911
18403
|
}
|
|
18912
18404
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -19014,39 +18506,6 @@ app52.post("/", async (c) => {
|
|
|
19014
18506
|
});
|
|
19015
18507
|
var session_default2 = app52;
|
|
19016
18508
|
|
|
19017
|
-
// server/lib/calendar-availability.ts
|
|
19018
|
-
import { readFileSync as readFileSync31 } from "fs";
|
|
19019
|
-
import { join as join28 } from "path";
|
|
19020
|
-
var AVAILABILITY_FILENAME = "calendar-availability.json";
|
|
19021
|
-
var REQUIRED_KEYS = [
|
|
19022
|
-
"timezone",
|
|
19023
|
-
"durationMins",
|
|
19024
|
-
"bufferMins",
|
|
19025
|
-
"weekly"
|
|
19026
|
-
];
|
|
19027
|
-
function readAvailabilityConfig(accountDir) {
|
|
19028
|
-
const path2 = join28(accountDir, AVAILABILITY_FILENAME);
|
|
19029
|
-
let raw;
|
|
19030
|
-
try {
|
|
19031
|
-
raw = readFileSync31(path2, "utf-8");
|
|
19032
|
-
} catch {
|
|
19033
|
-
throw new Error(`availability not configured: ${path2} not found`);
|
|
19034
|
-
}
|
|
19035
|
-
let parsed;
|
|
19036
|
-
try {
|
|
19037
|
-
parsed = JSON.parse(raw);
|
|
19038
|
-
} catch (err) {
|
|
19039
|
-
throw new Error(`invalid JSON in ${path2}: ${err.message}`);
|
|
19040
|
-
}
|
|
19041
|
-
const cfg = parsed;
|
|
19042
|
-
for (const key of REQUIRED_KEYS) {
|
|
19043
|
-
if (cfg[key] === void 0 || cfg[key] === null) {
|
|
19044
|
-
throw new Error(`availability config ${path2} missing required key: ${key}`);
|
|
19045
|
-
}
|
|
19046
|
-
}
|
|
19047
|
-
return cfg;
|
|
19048
|
-
}
|
|
19049
|
-
|
|
19050
18509
|
// server/lib/calendar-slots.ts
|
|
19051
18510
|
var WEEKDAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
19052
18511
|
function tzOffsetMs(instant, timeZone) {
|
|
@@ -19301,7 +18760,7 @@ function startGraphHealthTimer() {
|
|
|
19301
18760
|
|
|
19302
18761
|
// app/lib/file-watcher.ts
|
|
19303
18762
|
import * as fsp2 from "fs/promises";
|
|
19304
|
-
import { resolve as
|
|
18763
|
+
import { resolve as resolve27, sep as sep8 } from "path";
|
|
19305
18764
|
var ACCOUNT_UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19306
18765
|
var DEFAULT_COALESCE_MS = 500;
|
|
19307
18766
|
var ROOTS = ["accounts"];
|
|
@@ -19320,7 +18779,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
19320
18779
|
const dropFn = opts.drop ?? dropFileIndex;
|
|
19321
18780
|
for (const r of ROOTS) {
|
|
19322
18781
|
try {
|
|
19323
|
-
await fsp2.mkdir(
|
|
18782
|
+
await fsp2.mkdir(resolve27(dataRoot, r), { recursive: true });
|
|
19324
18783
|
} catch (err) {
|
|
19325
18784
|
console.error(
|
|
19326
18785
|
`[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
|
|
@@ -19341,7 +18800,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
19341
18800
|
timers.set(relativePath, t);
|
|
19342
18801
|
}
|
|
19343
18802
|
async function runHook(relativePath, accountId) {
|
|
19344
|
-
const absolute =
|
|
18803
|
+
const absolute = resolve27(dataRoot, relativePath);
|
|
19345
18804
|
let exists = false;
|
|
19346
18805
|
try {
|
|
19347
18806
|
const st = await fsp2.stat(absolute);
|
|
@@ -19365,7 +18824,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
19365
18824
|
}
|
|
19366
18825
|
}
|
|
19367
18826
|
async function watchRoot(rootName) {
|
|
19368
|
-
const absRoot =
|
|
18827
|
+
const absRoot = resolve27(dataRoot, rootName);
|
|
19369
18828
|
try {
|
|
19370
18829
|
const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
|
|
19371
18830
|
for await (const event of iter) {
|
|
@@ -19404,7 +18863,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
19404
18863
|
|
|
19405
18864
|
// app/lib/migrate-uploads.ts
|
|
19406
18865
|
import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
|
|
19407
|
-
import { dirname as dirname8, relative as relative6, resolve as
|
|
18866
|
+
import { dirname as dirname8, relative as relative6, resolve as resolve28 } from "path";
|
|
19408
18867
|
var ACCOUNT_UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19409
18868
|
async function walkFiles(dir) {
|
|
19410
18869
|
const out = [];
|
|
@@ -19415,7 +18874,7 @@ async function walkFiles(dir) {
|
|
|
19415
18874
|
return out;
|
|
19416
18875
|
}
|
|
19417
18876
|
for (const e of entries) {
|
|
19418
|
-
const abs =
|
|
18877
|
+
const abs = resolve28(dir, e.name);
|
|
19419
18878
|
if (e.isDirectory()) {
|
|
19420
18879
|
out.push(...await walkFiles(abs));
|
|
19421
18880
|
} else if (e.isFile()) {
|
|
@@ -19435,7 +18894,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
19435
18894
|
let moved = 0;
|
|
19436
18895
|
for (const oldAbs of files) {
|
|
19437
18896
|
const suffix = relative6(srcDir, oldAbs);
|
|
19438
|
-
const newAbs =
|
|
18897
|
+
const newAbs = resolve28(destDir, suffix);
|
|
19439
18898
|
await mkdir5(dirname8(newAbs), { recursive: true });
|
|
19440
18899
|
await rename2(oldAbs, newAbs);
|
|
19441
18900
|
moved++;
|
|
@@ -19462,7 +18921,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
19462
18921
|
}
|
|
19463
18922
|
async function migrateUploads(opts = {}) {
|
|
19464
18923
|
const dataRoot = opts.dataRoot ?? DATA_ROOT;
|
|
19465
|
-
const oldRoot =
|
|
18924
|
+
const oldRoot = resolve28(dataRoot, "uploads");
|
|
19466
18925
|
let topEntries;
|
|
19467
18926
|
try {
|
|
19468
18927
|
topEntries = await readdir5(oldRoot, { withFileTypes: true });
|
|
@@ -19483,8 +18942,8 @@ async function migrateUploads(opts = {}) {
|
|
|
19483
18942
|
const name = entry.name;
|
|
19484
18943
|
if (ACCOUNT_UUID_RE4.test(name)) {
|
|
19485
18944
|
moved += await relocateTree(
|
|
19486
|
-
|
|
19487
|
-
|
|
18945
|
+
resolve28(oldRoot, name),
|
|
18946
|
+
resolve28(dataRoot, "accounts", name, "uploads"),
|
|
19488
18947
|
dataRoot,
|
|
19489
18948
|
name,
|
|
19490
18949
|
session
|
|
@@ -19496,8 +18955,8 @@ async function migrateUploads(opts = {}) {
|
|
|
19496
18955
|
continue;
|
|
19497
18956
|
}
|
|
19498
18957
|
moved += await relocateTree(
|
|
19499
|
-
|
|
19500
|
-
|
|
18958
|
+
resolve28(oldRoot, "public"),
|
|
18959
|
+
resolve28(dataRoot, "accounts", installAccountId, "uploads", "public"),
|
|
19501
18960
|
dataRoot,
|
|
19502
18961
|
null,
|
|
19503
18962
|
// public uploads carry no graph nodes
|
|
@@ -19526,9 +18985,9 @@ async function migrateUploads(opts = {}) {
|
|
|
19526
18985
|
}
|
|
19527
18986
|
|
|
19528
18987
|
// app/lib/migrate-admin-webchat-sidecars.ts
|
|
19529
|
-
import { readdir as readdir6, readFile as
|
|
19530
|
-
import { existsSync as
|
|
19531
|
-
import { join as
|
|
18988
|
+
import { readdir as readdir6, readFile as readFile6, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
|
|
18989
|
+
import { existsSync as existsSync30 } from "fs";
|
|
18990
|
+
import { join as join30 } from "path";
|
|
19532
18991
|
var ADMIN_ROLE2 = "admin";
|
|
19533
18992
|
var WEBCHAT_CHANNEL2 = "webchat";
|
|
19534
18993
|
var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
@@ -19539,7 +18998,7 @@ var defaultResolveName = async (accountId, userId) => {
|
|
|
19539
18998
|
async function readRaw2(path2) {
|
|
19540
18999
|
let raw;
|
|
19541
19000
|
try {
|
|
19542
|
-
raw = await
|
|
19001
|
+
raw = await readFile6(path2, "utf8");
|
|
19543
19002
|
} catch {
|
|
19544
19003
|
return null;
|
|
19545
19004
|
}
|
|
@@ -19557,7 +19016,7 @@ async function writeRaw(path2, obj) {
|
|
|
19557
19016
|
await rename3(tmp, path2);
|
|
19558
19017
|
} catch (err) {
|
|
19559
19018
|
try {
|
|
19560
|
-
if (
|
|
19019
|
+
if (existsSync30(tmp)) await unlink3(tmp);
|
|
19561
19020
|
} catch {
|
|
19562
19021
|
}
|
|
19563
19022
|
throw err;
|
|
@@ -19573,7 +19032,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
19573
19032
|
}
|
|
19574
19033
|
for (const slug of slugs) {
|
|
19575
19034
|
if (!slug.isDirectory()) continue;
|
|
19576
|
-
const slugDir =
|
|
19035
|
+
const slugDir = join30(projectsRoot, slug.name);
|
|
19577
19036
|
let entries;
|
|
19578
19037
|
try {
|
|
19579
19038
|
entries = await readdir6(slugDir, { withFileTypes: true });
|
|
@@ -19582,9 +19041,9 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
19582
19041
|
}
|
|
19583
19042
|
for (const entry of entries) {
|
|
19584
19043
|
if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
|
|
19585
|
-
out.push(
|
|
19044
|
+
out.push(join30(slugDir, entry.name));
|
|
19586
19045
|
} else if (entry.isDirectory() && entry.name === "subagents") {
|
|
19587
|
-
const subDir =
|
|
19046
|
+
const subDir = join30(slugDir, entry.name);
|
|
19588
19047
|
let subs;
|
|
19589
19048
|
try {
|
|
19590
19049
|
subs = await readdir6(subDir, { withFileTypes: true });
|
|
@@ -19592,7 +19051,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
19592
19051
|
continue;
|
|
19593
19052
|
}
|
|
19594
19053
|
for (const s of subs) {
|
|
19595
|
-
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(
|
|
19054
|
+
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join30(subDir, s.name));
|
|
19596
19055
|
}
|
|
19597
19056
|
}
|
|
19598
19057
|
}
|
|
@@ -19604,7 +19063,7 @@ function shortId(path2) {
|
|
|
19604
19063
|
return base.slice(0, 8);
|
|
19605
19064
|
}
|
|
19606
19065
|
async function migrateAdminWebchatSidecars(opts = {}) {
|
|
19607
|
-
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
19066
|
+
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join30(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
|
|
19608
19067
|
const usersFile = opts.usersFile ?? USERS_FILE;
|
|
19609
19068
|
const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
|
|
19610
19069
|
const resolveName = opts.resolveName ?? defaultResolveName;
|
|
@@ -20508,16 +19967,16 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
20508
19967
|
* The public /api/chat route awaits this to return the reply on the same SSE
|
|
20509
19968
|
* response, preserving the pre-756 POST→single-blob browser contract. */
|
|
20510
19969
|
awaitReply(key, timeoutMs) {
|
|
20511
|
-
return new Promise((
|
|
19970
|
+
return new Promise((resolve33) => {
|
|
20512
19971
|
const timer2 = setTimeout(() => {
|
|
20513
19972
|
this.replyAwaiters.delete(key);
|
|
20514
|
-
|
|
19973
|
+
resolve33({ timeout: true });
|
|
20515
19974
|
}, timeoutMs);
|
|
20516
19975
|
if (timer2.unref) timer2.unref();
|
|
20517
19976
|
this.replyAwaiters.set(key, (r) => {
|
|
20518
19977
|
clearTimeout(timer2);
|
|
20519
19978
|
this.replyAwaiters.delete(key);
|
|
20520
|
-
|
|
19979
|
+
resolve33(r);
|
|
20521
19980
|
});
|
|
20522
19981
|
});
|
|
20523
19982
|
}
|
|
@@ -20528,11 +19987,11 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
20528
19987
|
* terminal dialog stays answerable). */
|
|
20529
19988
|
awaitPermissionVerdict(p, timeoutMs) {
|
|
20530
19989
|
const k = _WebchatGateway.promptKey(p.key, p.requestId);
|
|
20531
|
-
return new Promise((
|
|
19990
|
+
return new Promise((resolve33) => {
|
|
20532
19991
|
this.pendingPrompts.get(k)?.resolve({ timeout: true });
|
|
20533
19992
|
const timer2 = setTimeout(() => {
|
|
20534
19993
|
this.pendingPrompts.delete(k);
|
|
20535
|
-
|
|
19994
|
+
resolve33({ timeout: true });
|
|
20536
19995
|
}, timeoutMs);
|
|
20537
19996
|
if (timer2.unref) timer2.unref();
|
|
20538
19997
|
this.pendingPrompts.set(k, {
|
|
@@ -20541,7 +20000,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
20541
20000
|
resolve: (r) => {
|
|
20542
20001
|
clearTimeout(timer2);
|
|
20543
20002
|
this.pendingPrompts.delete(k);
|
|
20544
|
-
|
|
20003
|
+
resolve33(r);
|
|
20545
20004
|
}
|
|
20546
20005
|
});
|
|
20547
20006
|
console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
|
|
@@ -20613,6 +20072,45 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
20613
20072
|
if (handle.unref) handle.unref();
|
|
20614
20073
|
return handle;
|
|
20615
20074
|
}
|
|
20075
|
+
/** A gated public PTY exited explicitly on the manager side (operator stop,
|
|
20076
|
+
* claude crash, manager DELETE), not via the idle reaper. The manager POSTs
|
|
20077
|
+
* the sessionId to the loopback /api/admin/public-session-exit route, which
|
|
20078
|
+
* reaches this through the process-wide gateway handle. The slice ringfence
|
|
20079
|
+
* the reviewer needs lives only in this gateway's publicSessions map — the
|
|
20080
|
+
* manager has none of it — so resolve the record here and dispatch the same
|
|
20081
|
+
* reviewer the idle reaper uses, tagged dispatchFor='exit'. Dedup is internal
|
|
20082
|
+
* to firePublicSessionEndReview, so a session already reviewed by the reaper
|
|
20083
|
+
* (or a racing respawn) is a no-op.
|
|
20084
|
+
*
|
|
20085
|
+
* Only gated records (non-empty slice) are reviewed and evicted here, the
|
|
20086
|
+
* same gate the reaper applies. An open-mode public record is left for the
|
|
20087
|
+
* idle reaper. No deleteSession — the PTY is already gone and the JSONL is
|
|
20088
|
+
* the audit record the reviewer just read. */
|
|
20089
|
+
handlePublicSessionExit(sessionId) {
|
|
20090
|
+
let found = null;
|
|
20091
|
+
for (const [key2, rec2] of this.publicSessions) {
|
|
20092
|
+
if (rec2.sessionId === sessionId) {
|
|
20093
|
+
found = { key: key2, rec: rec2 };
|
|
20094
|
+
break;
|
|
20095
|
+
}
|
|
20096
|
+
}
|
|
20097
|
+
if (!found) {
|
|
20098
|
+
console.error(`[webchat:inbound] op=public-exit sessionId=${sessionId.slice(0, 8)} result=no-record`);
|
|
20099
|
+
return;
|
|
20100
|
+
}
|
|
20101
|
+
const { key, rec } = found;
|
|
20102
|
+
if (!rec.sliceToken || rec.sliceToken.length === 0) return;
|
|
20103
|
+
this.publicSessions.delete(key);
|
|
20104
|
+
console.error(`[webchat:inbound] op=public-exit key=${keyDisplay(key)} sessionId=${sessionId.slice(0, 8)} result=review`);
|
|
20105
|
+
void this.deps.firePublicSessionEndReview?.({
|
|
20106
|
+
sessionId: rec.sessionId,
|
|
20107
|
+
sliceToken: rec.sliceToken,
|
|
20108
|
+
personId: rec.personId ?? null,
|
|
20109
|
+
accountId: rec.accountId,
|
|
20110
|
+
agentSlug: rec.agentSlug,
|
|
20111
|
+
dispatchFor: "exit"
|
|
20112
|
+
});
|
|
20113
|
+
}
|
|
20616
20114
|
/** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
|
|
20617
20115
|
routes() {
|
|
20618
20116
|
return createWebchatChannelRoutes({
|
|
@@ -20729,97 +20227,385 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
20729
20227
|
);
|
|
20730
20228
|
}
|
|
20731
20229
|
}
|
|
20732
|
-
for (const key of this.recoverable.keys()) {
|
|
20733
|
-
if (!this.hub.hasInFlight(key)) this.recoverable.delete(key);
|
|
20734
|
-
}
|
|
20735
|
-
if (this.recoveryAttempted.size > 0) {
|
|
20736
|
-
const liveIds = this.hub.inFlightIds();
|
|
20737
|
-
for (const attemptKey of this.recoveryAttempted) {
|
|
20738
|
-
if (!liveIds.has(attemptKey)) this.recoveryAttempted.delete(attemptKey);
|
|
20739
|
-
}
|
|
20230
|
+
for (const key of this.recoverable.keys()) {
|
|
20231
|
+
if (!this.hub.hasInFlight(key)) this.recoverable.delete(key);
|
|
20232
|
+
}
|
|
20233
|
+
if (this.recoveryAttempted.size > 0) {
|
|
20234
|
+
const liveIds = this.hub.inFlightIds();
|
|
20235
|
+
for (const attemptKey of this.recoveryAttempted) {
|
|
20236
|
+
if (!liveIds.has(attemptKey)) this.recoveryAttempted.delete(attemptKey);
|
|
20237
|
+
}
|
|
20238
|
+
}
|
|
20239
|
+
} finally {
|
|
20240
|
+
this.recovering = false;
|
|
20241
|
+
}
|
|
20242
|
+
}
|
|
20243
|
+
/** Handle one inbound webchat message (browser POST → here). */
|
|
20244
|
+
async handleInbound(input) {
|
|
20245
|
+
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
20246
|
+
const hadSubscriber = this.hub.hasSubscriber(input.key);
|
|
20247
|
+
const willDeliverNow = this.hub.isReady(input.key);
|
|
20248
|
+
this.hub.deliver(
|
|
20249
|
+
{ key: input.key, text: input.text, messageId: `wc-${++this.seq}` },
|
|
20250
|
+
Date.now()
|
|
20251
|
+
);
|
|
20252
|
+
if (willDeliverNow) this.lastDeliveredAt.set(input.key, Date.now());
|
|
20253
|
+
console.error(
|
|
20254
|
+
`[webchat:inbound] op=received key=${keyDisplay(input.key)} role=${input.role} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
20255
|
+
);
|
|
20256
|
+
if (input.role === "admin") {
|
|
20257
|
+
this.recoverable.set(input.key, {
|
|
20258
|
+
accountId: input.accountId,
|
|
20259
|
+
key: input.key,
|
|
20260
|
+
role: input.role,
|
|
20261
|
+
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
20262
|
+
...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {}
|
|
20263
|
+
});
|
|
20264
|
+
}
|
|
20265
|
+
const existing = this.publicSessions.get(input.key);
|
|
20266
|
+
if (existing) existing.lastActivityAt = Date.now();
|
|
20267
|
+
if (!hadSubscriber && !this.spawning.has(input.key)) {
|
|
20268
|
+
this.spawning.add(input.key);
|
|
20269
|
+
console.error(`[webchat:inbound] op=spawn-trigger key=${input.key} role=${input.role}`);
|
|
20270
|
+
try {
|
|
20271
|
+
const spawned = await this.deps.ensureChannelSession({
|
|
20272
|
+
accountId: input.accountId,
|
|
20273
|
+
key: input.key,
|
|
20274
|
+
role: input.role,
|
|
20275
|
+
gatewayUrl: this.deps.gatewayUrl,
|
|
20276
|
+
serverPath: this.deps.serverPath,
|
|
20277
|
+
// Task 800 — only present on a session-targeted launch, so the
|
|
20278
|
+
// default-path call shape stays byte-identical.
|
|
20279
|
+
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
20280
|
+
// The authenticated admin's userId on an admin bootstrap inbound; the
|
|
20281
|
+
// default-path call shape stays byte-identical when absent.
|
|
20282
|
+
...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {},
|
|
20283
|
+
// Task 756 — public spawn params on a role:'public' inbound.
|
|
20284
|
+
...input.public !== void 0 ? { public: input.public } : {}
|
|
20285
|
+
});
|
|
20286
|
+
if (input.role === "public" && input.public && spawned && "sessionId" in spawned) {
|
|
20287
|
+
this.publicSessions.set(input.key, {
|
|
20288
|
+
sessionId: spawned.sessionId,
|
|
20289
|
+
accountId: input.accountId,
|
|
20290
|
+
agentSlug: input.public.agentSlug,
|
|
20291
|
+
sliceToken: input.public.sliceToken,
|
|
20292
|
+
personId: input.public.personId,
|
|
20293
|
+
lastActivityAt: Date.now()
|
|
20294
|
+
});
|
|
20295
|
+
}
|
|
20296
|
+
} finally {
|
|
20297
|
+
this.spawning.delete(input.key);
|
|
20298
|
+
}
|
|
20299
|
+
}
|
|
20300
|
+
}
|
|
20301
|
+
};
|
|
20302
|
+
|
|
20303
|
+
// app/lib/webchat/gateway/public-spawn-request.ts
|
|
20304
|
+
function buildPublicWebchatSpawnRequest(input) {
|
|
20305
|
+
const { agentSlug, sliceToken, personId, visitorId, name } = input.public;
|
|
20306
|
+
return {
|
|
20307
|
+
senderId: input.key,
|
|
20308
|
+
role: "public",
|
|
20309
|
+
channel: "webchat",
|
|
20310
|
+
accountId: input.accountId,
|
|
20311
|
+
agentSlug,
|
|
20312
|
+
model: SONNET_MODEL,
|
|
20313
|
+
// sliceToken/personId are present only for a gated visitor; open-mode
|
|
20314
|
+
// public agents carry neither (mirrors the bridge's open-mode key).
|
|
20315
|
+
...sliceToken ? { sliceToken } : {},
|
|
20316
|
+
...personId ? { personId } : {},
|
|
20317
|
+
...visitorId ? { visitorId } : {},
|
|
20318
|
+
...input.previousContext ? { previousContext: input.previousContext } : {},
|
|
20319
|
+
name,
|
|
20320
|
+
webchatChannel: { key: input.key, gatewayUrl: input.gatewayUrl, serverPath: input.serverPath }
|
|
20321
|
+
};
|
|
20322
|
+
}
|
|
20323
|
+
|
|
20324
|
+
// app/lib/channel-pty-bridge/follower.ts
|
|
20325
|
+
function followerPendingMaxMs() {
|
|
20326
|
+
return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
|
|
20327
|
+
}
|
|
20328
|
+
function followerRetryMs() {
|
|
20329
|
+
return Number(process.env.CHANNEL_PTY_FOLLOWER_RETRY_MS ?? String(1e3));
|
|
20330
|
+
}
|
|
20331
|
+
function startFollower(opts) {
|
|
20332
|
+
const abort = new AbortController();
|
|
20333
|
+
const { entry, tag } = opts;
|
|
20334
|
+
void (async () => {
|
|
20335
|
+
try {
|
|
20336
|
+
const sid = entry.sessionId.slice(0, 8);
|
|
20337
|
+
const deadline = Date.now() + followerPendingMaxMs();
|
|
20338
|
+
const retryMs = followerRetryMs();
|
|
20339
|
+
let res;
|
|
20340
|
+
let attempt = 0;
|
|
20341
|
+
for (; ; ) {
|
|
20342
|
+
res = await fetch(
|
|
20343
|
+
managerLogFollowUrl(entry.sessionId, { boundary: opts.suppressResumeReplay === true }),
|
|
20344
|
+
{ signal: abort.signal }
|
|
20345
|
+
);
|
|
20346
|
+
console.error(`${tag} follower-connect sessionId=${sid} status=${res.status}`);
|
|
20347
|
+
if (res.status === 202) {
|
|
20348
|
+
attempt += 1;
|
|
20349
|
+
await res.body?.cancel().catch(() => {
|
|
20350
|
+
});
|
|
20351
|
+
if (abort.signal.aborted) return;
|
|
20352
|
+
if (Date.now() >= deadline) {
|
|
20353
|
+
console.error(`${tag} follower-give-up sessionId=${sid} reason=pending-timeout attempts=${attempt}`);
|
|
20354
|
+
opts.onError?.("follow-pending-timeout");
|
|
20355
|
+
return;
|
|
20356
|
+
}
|
|
20357
|
+
console.error(`${tag} follower-retry sessionId=${sid} attempt=${attempt} reason=pending`);
|
|
20358
|
+
await new Promise((r) => setTimeout(r, retryMs));
|
|
20359
|
+
if (abort.signal.aborted) return;
|
|
20360
|
+
continue;
|
|
20361
|
+
}
|
|
20362
|
+
break;
|
|
20363
|
+
}
|
|
20364
|
+
if (!res.ok || !res.body) {
|
|
20365
|
+
opts.onError?.(`follow-status-${res.status}`);
|
|
20366
|
+
return;
|
|
20367
|
+
}
|
|
20368
|
+
console.error(`${tag} follower-open sessionId=${sid}`);
|
|
20369
|
+
const reader = res.body.getReader();
|
|
20370
|
+
const decoder = new TextDecoder("utf8");
|
|
20371
|
+
let buffered = "";
|
|
20372
|
+
const fileDelivery = opts.fileDelivery ?? null;
|
|
20373
|
+
let firedFileTools = [];
|
|
20374
|
+
let suppressing = opts.suppressResumeReplay === true;
|
|
20375
|
+
let discardedTurns = 0;
|
|
20376
|
+
let discardedFileTools = 0;
|
|
20377
|
+
while (!abort.signal.aborted) {
|
|
20378
|
+
const { value, done } = await reader.read();
|
|
20379
|
+
if (done) break;
|
|
20380
|
+
buffered += decoder.decode(value, { stream: true });
|
|
20381
|
+
let nl = buffered.indexOf("\n");
|
|
20382
|
+
while (nl !== -1) {
|
|
20383
|
+
const line = buffered.slice(0, nl);
|
|
20384
|
+
buffered = buffered.slice(nl + 1);
|
|
20385
|
+
nl = buffered.indexOf("\n");
|
|
20386
|
+
if (!line) continue;
|
|
20387
|
+
let event;
|
|
20388
|
+
try {
|
|
20389
|
+
event = JSON.parse(line);
|
|
20390
|
+
} catch (err) {
|
|
20391
|
+
console.error(
|
|
20392
|
+
`${tag} jsonl-parse-skip sessionId=${entry.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
|
|
20393
|
+
);
|
|
20394
|
+
continue;
|
|
20395
|
+
}
|
|
20396
|
+
if (event.type === "__channel_resume_boundary__") {
|
|
20397
|
+
if (suppressing) {
|
|
20398
|
+
suppressing = false;
|
|
20399
|
+
console.error(
|
|
20400
|
+
`${tag} follower-resume-boundary sessionId=${sid} discarded-head-turns=${discardedTurns} discarded-head-filetools=${discardedFileTools}`
|
|
20401
|
+
);
|
|
20402
|
+
}
|
|
20403
|
+
continue;
|
|
20404
|
+
}
|
|
20405
|
+
if (event.type === "user") {
|
|
20406
|
+
entry.pendingTurnText = "";
|
|
20407
|
+
firedFileTools = [];
|
|
20408
|
+
continue;
|
|
20409
|
+
}
|
|
20410
|
+
if (event.type !== "assistant") continue;
|
|
20411
|
+
const msg = event.message;
|
|
20412
|
+
if (!msg) continue;
|
|
20413
|
+
let sawContent = false;
|
|
20414
|
+
if (Array.isArray(msg.content)) {
|
|
20415
|
+
for (const block of msg.content) {
|
|
20416
|
+
if (block?.type === "text" && typeof block.text === "string") {
|
|
20417
|
+
entry.pendingTurnText += block.text;
|
|
20418
|
+
sawContent = true;
|
|
20419
|
+
} else if (block?.type === "tool_use") {
|
|
20420
|
+
sawContent = true;
|
|
20421
|
+
if (fileDelivery && typeof block.name === "string" && fileDelivery.isFileDeliveryTool(block.name)) {
|
|
20422
|
+
if (suppressing) {
|
|
20423
|
+
discardedFileTools += 1;
|
|
20424
|
+
continue;
|
|
20425
|
+
}
|
|
20426
|
+
firedFileTools.push(block.name);
|
|
20427
|
+
try {
|
|
20428
|
+
await fileDelivery.onFileToolUse({ toolName: block.name, input: block.input });
|
|
20429
|
+
} catch (err) {
|
|
20430
|
+
console.error(
|
|
20431
|
+
`${tag} file-delivery-error sessionId=${sid} tool=${block.name} message=${err instanceof Error ? err.message : String(err)}`
|
|
20432
|
+
);
|
|
20433
|
+
}
|
|
20434
|
+
}
|
|
20435
|
+
}
|
|
20436
|
+
}
|
|
20437
|
+
}
|
|
20438
|
+
if (msg.stop_reason === "end_turn") {
|
|
20439
|
+
if (suppressing) {
|
|
20440
|
+
entry.pendingTurnText = "";
|
|
20441
|
+
firedFileTools = [];
|
|
20442
|
+
discardedTurns += 1;
|
|
20443
|
+
continue;
|
|
20444
|
+
}
|
|
20445
|
+
const flush = entry.pendingTurnText;
|
|
20446
|
+
entry.pendingTurnText = "";
|
|
20447
|
+
if (flush.trim()) {
|
|
20448
|
+
await fanOut(entry.subscribers, flush, opts.onError, tag);
|
|
20449
|
+
}
|
|
20450
|
+
if (fileDelivery && firedFileTools.length > 0) {
|
|
20451
|
+
const fired = firedFileTools;
|
|
20452
|
+
firedFileTools = [];
|
|
20453
|
+
try {
|
|
20454
|
+
fileDelivery.onTurnEnd(fired);
|
|
20455
|
+
} catch (err) {
|
|
20456
|
+
console.error(
|
|
20457
|
+
`${tag} file-delivery-error sessionId=${sid} phase=turn-end message=${err instanceof Error ? err.message : String(err)}`
|
|
20458
|
+
);
|
|
20459
|
+
}
|
|
20460
|
+
}
|
|
20461
|
+
opts.onTurnComplete?.();
|
|
20462
|
+
} else if (sawContent && !suppressing) {
|
|
20463
|
+
opts.onActivity?.();
|
|
20464
|
+
}
|
|
20465
|
+
}
|
|
20466
|
+
}
|
|
20467
|
+
} catch (err) {
|
|
20468
|
+
if (!abort.signal.aborted) {
|
|
20469
|
+
opts.onError?.(`follower-error: ${err instanceof Error ? err.message : String(err)}`);
|
|
20740
20470
|
}
|
|
20741
20471
|
} finally {
|
|
20742
|
-
|
|
20743
|
-
}
|
|
20744
|
-
}
|
|
20745
|
-
/** Handle one inbound webchat message (browser POST → here). */
|
|
20746
|
-
async handleInbound(input) {
|
|
20747
|
-
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
20748
|
-
const hadSubscriber = this.hub.hasSubscriber(input.key);
|
|
20749
|
-
const willDeliverNow = this.hub.isReady(input.key);
|
|
20750
|
-
this.hub.deliver(
|
|
20751
|
-
{ key: input.key, text: input.text, messageId: `wc-${++this.seq}` },
|
|
20752
|
-
Date.now()
|
|
20753
|
-
);
|
|
20754
|
-
if (willDeliverNow) this.lastDeliveredAt.set(input.key, Date.now());
|
|
20755
|
-
console.error(
|
|
20756
|
-
`[webchat:inbound] op=received key=${keyDisplay(input.key)} role=${input.role} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
20757
|
-
);
|
|
20758
|
-
if (input.role === "admin") {
|
|
20759
|
-
this.recoverable.set(input.key, {
|
|
20760
|
-
accountId: input.accountId,
|
|
20761
|
-
key: input.key,
|
|
20762
|
-
role: input.role,
|
|
20763
|
-
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
20764
|
-
...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {}
|
|
20765
|
-
});
|
|
20472
|
+
opts.onClose();
|
|
20766
20473
|
}
|
|
20767
|
-
|
|
20768
|
-
|
|
20769
|
-
|
|
20770
|
-
|
|
20771
|
-
|
|
20772
|
-
|
|
20773
|
-
|
|
20774
|
-
|
|
20775
|
-
|
|
20776
|
-
|
|
20777
|
-
|
|
20778
|
-
|
|
20779
|
-
|
|
20780
|
-
|
|
20781
|
-
|
|
20782
|
-
|
|
20783
|
-
|
|
20784
|
-
|
|
20785
|
-
|
|
20786
|
-
|
|
20787
|
-
|
|
20788
|
-
|
|
20789
|
-
|
|
20790
|
-
|
|
20791
|
-
|
|
20792
|
-
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
|
|
20798
|
-
|
|
20799
|
-
|
|
20474
|
+
})();
|
|
20475
|
+
return abort;
|
|
20476
|
+
}
|
|
20477
|
+
async function fanOut(subscribers, text, onError, tag) {
|
|
20478
|
+
const callbacks = Array.from(subscribers);
|
|
20479
|
+
await Promise.all(
|
|
20480
|
+
callbacks.map(
|
|
20481
|
+
(cb) => Promise.resolve().then(() => cb(text)).catch((err) => {
|
|
20482
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
20483
|
+
console.error(`${tag} subscriber-error message=${m}`);
|
|
20484
|
+
onError?.(`subscriber-error: ${m}`);
|
|
20485
|
+
})
|
|
20486
|
+
)
|
|
20487
|
+
);
|
|
20488
|
+
}
|
|
20489
|
+
|
|
20490
|
+
// app/lib/channel-pty-bridge/file-delivery.ts
|
|
20491
|
+
var SEND_USER_FILE = "SendUserFile";
|
|
20492
|
+
function makeFileDelivery(opts) {
|
|
20493
|
+
const { entry, tag, channel, sendFile } = opts;
|
|
20494
|
+
let failedFiles = [];
|
|
20495
|
+
let attempts = 0;
|
|
20496
|
+
return {
|
|
20497
|
+
isFileDeliveryTool(toolName) {
|
|
20498
|
+
return toolName === SEND_USER_FILE;
|
|
20499
|
+
},
|
|
20500
|
+
async onFileToolUse(use) {
|
|
20501
|
+
if (use.toolName !== SEND_USER_FILE) return;
|
|
20502
|
+
const input = use.input ?? {};
|
|
20503
|
+
const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
|
|
20504
|
+
const caption = typeof input.caption === "string" ? input.caption : void 0;
|
|
20505
|
+
for (let i = 0; i < files.length; i++) {
|
|
20506
|
+
attempts++;
|
|
20507
|
+
const result = await sendFile(files[i], i === 0 ? caption : void 0);
|
|
20508
|
+
const name = files[i].split("/").pop() ?? files[i];
|
|
20509
|
+
console.error(`${tag} op=file-forward channel=${channel} file=${name} outcome=${result.ok ? "ok" : "fail"}`);
|
|
20510
|
+
if (!result.ok) failedFiles.push(files[i]);
|
|
20511
|
+
}
|
|
20512
|
+
},
|
|
20513
|
+
onTurnEnd(firedTools) {
|
|
20514
|
+
const failed = failedFiles;
|
|
20515
|
+
const tried = attempts;
|
|
20516
|
+
failedFiles = [];
|
|
20517
|
+
attempts = 0;
|
|
20518
|
+
const sid = entry.sessionId.slice(0, 8);
|
|
20519
|
+
for (const file of failed) {
|
|
20520
|
+
console.error(
|
|
20521
|
+
`${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
20522
|
+
);
|
|
20523
|
+
}
|
|
20524
|
+
if (firedTools.includes(SEND_USER_FILE) && tried === 0) {
|
|
20525
|
+
console.error(
|
|
20526
|
+
`${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
|
|
20527
|
+
);
|
|
20800
20528
|
}
|
|
20801
20529
|
}
|
|
20802
|
-
}
|
|
20803
|
-
}
|
|
20530
|
+
};
|
|
20531
|
+
}
|
|
20804
20532
|
|
|
20805
|
-
// app/lib/
|
|
20806
|
-
|
|
20807
|
-
|
|
20533
|
+
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
20534
|
+
var TAG34 = "[whatsapp-adaptor]";
|
|
20535
|
+
var SEND_USER_FILE2 = "SendUserFile";
|
|
20536
|
+
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
20537
|
+
function platformRoot() {
|
|
20538
|
+
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
20539
|
+
}
|
|
20540
|
+
function makeWhatsAppSendFile(entry) {
|
|
20541
|
+
return async (filePath, caption) => {
|
|
20542
|
+
let maxyAccountId;
|
|
20543
|
+
try {
|
|
20544
|
+
maxyAccountId = resolvePlatformAccountId();
|
|
20545
|
+
} catch (err) {
|
|
20546
|
+
console.error(
|
|
20547
|
+
`${TAG34} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
20548
|
+
);
|
|
20549
|
+
return { ok: false, error: "account-unresolved" };
|
|
20550
|
+
}
|
|
20551
|
+
const result = await sendWhatsAppDocument({
|
|
20552
|
+
to: entry.senderId,
|
|
20553
|
+
filePath,
|
|
20554
|
+
caption,
|
|
20555
|
+
accountId: entry.accountId,
|
|
20556
|
+
maxyAccountId,
|
|
20557
|
+
platformRoot: platformRoot()
|
|
20558
|
+
});
|
|
20559
|
+
if (result.ok) return { ok: true };
|
|
20560
|
+
console.error(
|
|
20561
|
+
`${TAG34} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
20562
|
+
);
|
|
20563
|
+
return { ok: false, error: result.error };
|
|
20564
|
+
};
|
|
20565
|
+
}
|
|
20566
|
+
function makeWhatsAppFileDelivery(entry) {
|
|
20567
|
+
const shared = makeFileDelivery({
|
|
20568
|
+
entry,
|
|
20569
|
+
tag: TAG34,
|
|
20570
|
+
channel: "whatsapp",
|
|
20571
|
+
sendFile: makeWhatsAppSendFile(entry)
|
|
20572
|
+
});
|
|
20573
|
+
let turnStartedAt = null;
|
|
20574
|
+
let routeCalls = [];
|
|
20808
20575
|
return {
|
|
20809
|
-
|
|
20810
|
-
|
|
20811
|
-
|
|
20812
|
-
|
|
20813
|
-
|
|
20814
|
-
|
|
20815
|
-
|
|
20816
|
-
|
|
20817
|
-
|
|
20818
|
-
|
|
20819
|
-
|
|
20820
|
-
|
|
20821
|
-
|
|
20822
|
-
|
|
20576
|
+
isFileDeliveryTool(toolName) {
|
|
20577
|
+
return toolName === SEND_USER_FILE2 || toolName === WHATSAPP_SEND_DOCUMENT;
|
|
20578
|
+
},
|
|
20579
|
+
async onFileToolUse(use) {
|
|
20580
|
+
if (turnStartedAt === null) turnStartedAt = Date.now();
|
|
20581
|
+
if (use.toolName === WHATSAPP_SEND_DOCUMENT) {
|
|
20582
|
+
const input = use.input ?? {};
|
|
20583
|
+
routeCalls.push({
|
|
20584
|
+
to: typeof input.to === "string" ? input.to : void 0,
|
|
20585
|
+
filePath: typeof input.filePath === "string" ? input.filePath : void 0
|
|
20586
|
+
});
|
|
20587
|
+
return;
|
|
20588
|
+
}
|
|
20589
|
+
await shared.onFileToolUse(use);
|
|
20590
|
+
},
|
|
20591
|
+
onTurnEnd(firedTools) {
|
|
20592
|
+
const startedAt = turnStartedAt ?? 0;
|
|
20593
|
+
const routes = routeCalls;
|
|
20594
|
+
turnStartedAt = null;
|
|
20595
|
+
routeCalls = [];
|
|
20596
|
+
const sid = entry.sessionId.slice(0, 8);
|
|
20597
|
+
shared.onTurnEnd(firedTools);
|
|
20598
|
+
for (const call of routes) {
|
|
20599
|
+
const routeAt = call.to !== void 0 && call.filePath !== void 0 ? routeDocumentOutboundAt(call.to, call.filePath) : void 0;
|
|
20600
|
+
const delivered = routeAt !== void 0 && routeAt >= startedAt;
|
|
20601
|
+
if (!delivered) {
|
|
20602
|
+
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
20603
|
+
console.error(
|
|
20604
|
+
`${TAG34} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
20605
|
+
);
|
|
20606
|
+
}
|
|
20607
|
+
}
|
|
20608
|
+
}
|
|
20823
20609
|
};
|
|
20824
20610
|
}
|
|
20825
20611
|
|
|
@@ -21170,6 +20956,122 @@ function buildTelegramSpawnRequest(input) {
|
|
|
21170
20956
|
};
|
|
21171
20957
|
}
|
|
21172
20958
|
|
|
20959
|
+
// app/lib/telegram/outbound/send-document.ts
|
|
20960
|
+
import { realpathSync as realpathSync6 } from "fs";
|
|
20961
|
+
import { readFile as readFile7, stat as stat7 } from "fs/promises";
|
|
20962
|
+
import { resolve as resolve29, basename as basename9 } from "path";
|
|
20963
|
+
var TAG35 = "[telegram:outbound]";
|
|
20964
|
+
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
20965
|
+
async function sendTelegramDocument(input) {
|
|
20966
|
+
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot3 } = input;
|
|
20967
|
+
if (!botToken || !filePath) {
|
|
20968
|
+
return { ok: false, status: 400, error: "Missing required fields: botToken, filePath" };
|
|
20969
|
+
}
|
|
20970
|
+
if (!maxyAccountId || !platformRoot3) {
|
|
20971
|
+
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
20972
|
+
}
|
|
20973
|
+
const accountDir = resolve29(platformRoot3, "..", "data/accounts", maxyAccountId);
|
|
20974
|
+
let resolvedPath;
|
|
20975
|
+
try {
|
|
20976
|
+
resolvedPath = realpathSync6(filePath);
|
|
20977
|
+
const accountResolved = realpathSync6(accountDir);
|
|
20978
|
+
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
20979
|
+
console.error(`${TAG35} document REJECTED reason=outside_account_directory`);
|
|
20980
|
+
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
20981
|
+
}
|
|
20982
|
+
} catch (err) {
|
|
20983
|
+
const code = err.code;
|
|
20984
|
+
if (code === "ENOENT") {
|
|
20985
|
+
console.error(`${TAG35} document ENOENT path=${filePath}`);
|
|
20986
|
+
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
20987
|
+
}
|
|
20988
|
+
console.error(`${TAG35} document path error: ${String(err)}`);
|
|
20989
|
+
return { ok: false, status: 500, error: String(err) };
|
|
20990
|
+
}
|
|
20991
|
+
const fileStat = await stat7(resolvedPath);
|
|
20992
|
+
if (fileStat.size > TELEGRAM_DOCUMENT_MAX_BYTES) {
|
|
20993
|
+
return {
|
|
20994
|
+
ok: false,
|
|
20995
|
+
status: 400,
|
|
20996
|
+
error: `File exceeds 50 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
|
|
20997
|
+
};
|
|
20998
|
+
}
|
|
20999
|
+
const filename = basename9(resolvedPath);
|
|
21000
|
+
const buffer = Buffer.from(await readFile7(resolvedPath));
|
|
21001
|
+
const form = new FormData();
|
|
21002
|
+
form.append("chat_id", String(chatId));
|
|
21003
|
+
if (caption) form.append("caption", caption);
|
|
21004
|
+
form.append("document", new File([buffer], filename, { type: detectMimeType(resolvedPath) }));
|
|
21005
|
+
let ok = false;
|
|
21006
|
+
let messageId;
|
|
21007
|
+
let error = "send failed";
|
|
21008
|
+
try {
|
|
21009
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendDocument`, {
|
|
21010
|
+
method: "POST",
|
|
21011
|
+
body: form
|
|
21012
|
+
});
|
|
21013
|
+
const data = await res.json();
|
|
21014
|
+
ok = data.ok;
|
|
21015
|
+
messageId = data.result?.message_id;
|
|
21016
|
+
if (!data.ok) error = data.description ?? "Unknown Telegram error";
|
|
21017
|
+
} catch (e) {
|
|
21018
|
+
error = e instanceof Error ? e.message : String(e);
|
|
21019
|
+
}
|
|
21020
|
+
console.error(
|
|
21021
|
+
`${TAG35} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
21022
|
+
);
|
|
21023
|
+
return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
21024
|
+
}
|
|
21025
|
+
|
|
21026
|
+
// app/lib/telegram/outbound/file-delivery.ts
|
|
21027
|
+
var TAG36 = "[telegram:outbound]";
|
|
21028
|
+
function platformRoot2() {
|
|
21029
|
+
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
21030
|
+
}
|
|
21031
|
+
function makeTelegramSendFile(entry) {
|
|
21032
|
+
let botToken;
|
|
21033
|
+
return async (filePath, caption) => {
|
|
21034
|
+
if (botToken === void 0) {
|
|
21035
|
+
const tg = resolveAccount()?.config.telegram;
|
|
21036
|
+
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
21037
|
+
}
|
|
21038
|
+
if (!botToken) {
|
|
21039
|
+
console.error(`${TAG36} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
21040
|
+
return { ok: false, error: "no-bot-token" };
|
|
21041
|
+
}
|
|
21042
|
+
if (entry.replyTarget == null) {
|
|
21043
|
+
console.error(`${TAG36} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
21044
|
+
return { ok: false, error: "no-reply-target" };
|
|
21045
|
+
}
|
|
21046
|
+
let maxyAccountId;
|
|
21047
|
+
try {
|
|
21048
|
+
maxyAccountId = resolvePlatformAccountId();
|
|
21049
|
+
} catch (err) {
|
|
21050
|
+
console.error(
|
|
21051
|
+
`${TAG36} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
21052
|
+
);
|
|
21053
|
+
return { ok: false, error: "account-unresolved" };
|
|
21054
|
+
}
|
|
21055
|
+
const result = await sendTelegramDocument({
|
|
21056
|
+
botToken,
|
|
21057
|
+
chatId: Number(entry.replyTarget),
|
|
21058
|
+
filePath,
|
|
21059
|
+
caption,
|
|
21060
|
+
maxyAccountId,
|
|
21061
|
+
platformRoot: platformRoot2()
|
|
21062
|
+
});
|
|
21063
|
+
return result.ok ? { ok: true } : { ok: false, error: result.error };
|
|
21064
|
+
};
|
|
21065
|
+
}
|
|
21066
|
+
function makeTelegramFileDelivery(entry) {
|
|
21067
|
+
return makeFileDelivery({
|
|
21068
|
+
entry,
|
|
21069
|
+
tag: TAG36,
|
|
21070
|
+
channel: "telegram",
|
|
21071
|
+
sendFile: makeTelegramSendFile(entry)
|
|
21072
|
+
});
|
|
21073
|
+
}
|
|
21074
|
+
|
|
21173
21075
|
// app/lib/telegram/gateway/native-file-follower.ts
|
|
21174
21076
|
function startTelegramNativeFileFollower(input) {
|
|
21175
21077
|
const entry = {
|
|
@@ -21204,12 +21106,166 @@ function startTelegramNativeFileFollower(input) {
|
|
|
21204
21106
|
}
|
|
21205
21107
|
|
|
21206
21108
|
// server/telegram-descriptor.ts
|
|
21207
|
-
import { resolve as
|
|
21109
|
+
import { resolve as resolve30, join as join31 } from "path";
|
|
21208
21110
|
function telegramGatewayUrl() {
|
|
21209
21111
|
return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
|
|
21210
21112
|
}
|
|
21211
21113
|
function telegramServerPath() {
|
|
21212
|
-
return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ??
|
|
21114
|
+
return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve30(process.env.MAXY_PLATFORM_ROOT ?? join31(__dirname, ".."), "services/telegram-channel/dist/server.js");
|
|
21115
|
+
}
|
|
21116
|
+
|
|
21117
|
+
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
21118
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
21119
|
+
var TAG37 = "[public-session-review]";
|
|
21120
|
+
var CONSUMED_CAP = 500;
|
|
21121
|
+
var consumed = /* @__PURE__ */ new Set();
|
|
21122
|
+
function consumeOnce(sessionId) {
|
|
21123
|
+
if (consumed.has(sessionId)) return false;
|
|
21124
|
+
consumed.add(sessionId);
|
|
21125
|
+
if (consumed.size > CONSUMED_CAP) {
|
|
21126
|
+
const oldest = consumed.values().next().value;
|
|
21127
|
+
if (oldest !== void 0) consumed.delete(oldest);
|
|
21128
|
+
}
|
|
21129
|
+
return true;
|
|
21130
|
+
}
|
|
21131
|
+
function managerBase3() {
|
|
21132
|
+
const port2 = requirePortEnv("CLAUDE_SESSION_MANAGER_PORT", { tag: "public-session-end-review:manager" });
|
|
21133
|
+
return `http://127.0.0.1:${port2}`;
|
|
21134
|
+
}
|
|
21135
|
+
function uiBase() {
|
|
21136
|
+
const port2 = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "public-session-end-review:ui" });
|
|
21137
|
+
return `http://127.0.0.1:${port2}`;
|
|
21138
|
+
}
|
|
21139
|
+
async function fetchJsonl(sessionId) {
|
|
21140
|
+
try {
|
|
21141
|
+
const res = await fetch(`${managerBase3()}/${sessionId}/log?download=1`);
|
|
21142
|
+
if (!res.ok) return null;
|
|
21143
|
+
return await res.text();
|
|
21144
|
+
} catch (err) {
|
|
21145
|
+
console.error(
|
|
21146
|
+
`${TAG37} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
21147
|
+
);
|
|
21148
|
+
return null;
|
|
21149
|
+
}
|
|
21150
|
+
}
|
|
21151
|
+
function countOperatorTurns(jsonl) {
|
|
21152
|
+
let count = 0;
|
|
21153
|
+
for (const raw of jsonl.split("\n")) {
|
|
21154
|
+
const line = raw.trim();
|
|
21155
|
+
if (!line) continue;
|
|
21156
|
+
try {
|
|
21157
|
+
const obj = JSON.parse(line);
|
|
21158
|
+
if (obj.type === "user") count += 1;
|
|
21159
|
+
} catch {
|
|
21160
|
+
}
|
|
21161
|
+
}
|
|
21162
|
+
return count;
|
|
21163
|
+
}
|
|
21164
|
+
async function fetchPriorWrites(sliceToken, accountId) {
|
|
21165
|
+
const url = `${uiBase()}/api/admin/public-session-context?sliceToken=${encodeURIComponent(sliceToken)}&accountId=${encodeURIComponent(accountId)}`;
|
|
21166
|
+
try {
|
|
21167
|
+
const res = await fetch(url);
|
|
21168
|
+
if (!res.ok) {
|
|
21169
|
+
console.error(
|
|
21170
|
+
`${TAG37} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
21171
|
+
);
|
|
21172
|
+
return [];
|
|
21173
|
+
}
|
|
21174
|
+
const body = await res.json();
|
|
21175
|
+
return Array.isArray(body.writes) ? body.writes : [];
|
|
21176
|
+
} catch (err) {
|
|
21177
|
+
console.error(
|
|
21178
|
+
`${TAG37} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
21179
|
+
);
|
|
21180
|
+
return [];
|
|
21181
|
+
}
|
|
21182
|
+
}
|
|
21183
|
+
function composeInitialMessage(input) {
|
|
21184
|
+
const priorJson = JSON.stringify(input.priorWrites, null, 2);
|
|
21185
|
+
return [
|
|
21186
|
+
`The gated public-agent session for agent "${input.agentSlug}" has just been reaped. Review the transcript and decide what is worth saving to the visitor's per-visitor memory slice. Dispatch \`database-operator\` for each individual write via the Task tool. Do not call memory-write or memory-update directly.`,
|
|
21187
|
+
"",
|
|
21188
|
+
"<slice-token>",
|
|
21189
|
+
input.sliceToken,
|
|
21190
|
+
"</slice-token>",
|
|
21191
|
+
"",
|
|
21192
|
+
"<person-id>",
|
|
21193
|
+
input.personId ?? "(unknown)",
|
|
21194
|
+
"</person-id>",
|
|
21195
|
+
"",
|
|
21196
|
+
"<prior-writes>",
|
|
21197
|
+
priorJson,
|
|
21198
|
+
"</prior-writes>",
|
|
21199
|
+
"",
|
|
21200
|
+
"<transcript>",
|
|
21201
|
+
input.jsonl,
|
|
21202
|
+
"</transcript>"
|
|
21203
|
+
].join("\n");
|
|
21204
|
+
}
|
|
21205
|
+
async function dispatchReviewer(input, initialMessage) {
|
|
21206
|
+
const sessionId = randomUUID13();
|
|
21207
|
+
console.log(`${TAG37} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
21208
|
+
const spawned = await managerRcSpawn({
|
|
21209
|
+
sessionId,
|
|
21210
|
+
initialMessage,
|
|
21211
|
+
closeAfterTurn: true,
|
|
21212
|
+
sliceToken: input.sliceToken,
|
|
21213
|
+
personId: input.personId ?? void 0,
|
|
21214
|
+
logContext: `public-session-end sourceSession=${input.sessionId.slice(0, 8)}`
|
|
21215
|
+
});
|
|
21216
|
+
if ("error" in spawned) {
|
|
21217
|
+
return { ok: false, reason: `rc-spawn-${spawned.status}-${spawned.error}` };
|
|
21218
|
+
}
|
|
21219
|
+
return { ok: true, managerSessionId: spawned.sessionId };
|
|
21220
|
+
}
|
|
21221
|
+
async function firePublicSessionEndReview(input) {
|
|
21222
|
+
const sliceShort = input.sliceToken.slice(0, 8);
|
|
21223
|
+
const personField = input.personId ?? "none";
|
|
21224
|
+
const dispatchedAt = Date.now();
|
|
21225
|
+
if (consumed.has(input.sessionId)) {
|
|
21226
|
+
console.log(
|
|
21227
|
+
`${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
21228
|
+
);
|
|
21229
|
+
return;
|
|
21230
|
+
}
|
|
21231
|
+
const jsonl = await fetchJsonl(input.sessionId);
|
|
21232
|
+
if (!jsonl) {
|
|
21233
|
+
console.log(
|
|
21234
|
+
`${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
21235
|
+
);
|
|
21236
|
+
return;
|
|
21237
|
+
}
|
|
21238
|
+
const operatorTurns = countOperatorTurns(jsonl);
|
|
21239
|
+
if (operatorTurns === 0) {
|
|
21240
|
+
console.log(
|
|
21241
|
+
`${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
21242
|
+
);
|
|
21243
|
+
return;
|
|
21244
|
+
}
|
|
21245
|
+
const priorWrites = await fetchPriorWrites(input.sliceToken, input.accountId);
|
|
21246
|
+
const initialMessage = composeInitialMessage({
|
|
21247
|
+
agentSlug: input.agentSlug,
|
|
21248
|
+
sliceToken: input.sliceToken,
|
|
21249
|
+
personId: input.personId,
|
|
21250
|
+
jsonl,
|
|
21251
|
+
priorWrites
|
|
21252
|
+
});
|
|
21253
|
+
if (!consumeOnce(input.sessionId)) {
|
|
21254
|
+
console.log(
|
|
21255
|
+
`${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
21256
|
+
);
|
|
21257
|
+
return;
|
|
21258
|
+
}
|
|
21259
|
+
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
21260
|
+
if (!dispatched.ok) {
|
|
21261
|
+
console.error(
|
|
21262
|
+
`${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
21263
|
+
);
|
|
21264
|
+
return;
|
|
21265
|
+
}
|
|
21266
|
+
console.log(
|
|
21267
|
+
`${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
21268
|
+
);
|
|
21213
21269
|
}
|
|
21214
21270
|
|
|
21215
21271
|
// app/lib/whatsapp/inbound/channel-admin-binding-drift.ts
|
|
@@ -21277,8 +21333,8 @@ function broadcastAdminShutdown(reason) {
|
|
|
21277
21333
|
|
|
21278
21334
|
// ../lib/entitlement/src/index.ts
|
|
21279
21335
|
import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
|
|
21280
|
-
import { existsSync as
|
|
21281
|
-
import { resolve as
|
|
21336
|
+
import { existsSync as existsSync31, readFileSync as readFileSync32, statSync as statSync12 } from "fs";
|
|
21337
|
+
import { resolve as resolve31 } from "path";
|
|
21282
21338
|
|
|
21283
21339
|
// ../lib/entitlement/src/canonicalize.ts
|
|
21284
21340
|
function canonicalize(value) {
|
|
@@ -21313,7 +21369,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
|
|
|
21313
21369
|
var GRACE_DAYS = 7;
|
|
21314
21370
|
var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
21315
21371
|
function pubkeyPath(brand) {
|
|
21316
|
-
return
|
|
21372
|
+
return resolve31(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
|
|
21317
21373
|
}
|
|
21318
21374
|
var memo = null;
|
|
21319
21375
|
function memoKey(mtimeMs, account) {
|
|
@@ -21325,8 +21381,8 @@ function resolveEntitlement(brand, account) {
|
|
|
21325
21381
|
if (brand.commercialMode !== true) {
|
|
21326
21382
|
return logResolved(implicitTrust(account), null);
|
|
21327
21383
|
}
|
|
21328
|
-
const entitlementPath =
|
|
21329
|
-
if (!
|
|
21384
|
+
const entitlementPath = resolve31(brand.configDir, "entitlement.json");
|
|
21385
|
+
if (!existsSync31(entitlementPath)) {
|
|
21330
21386
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
21331
21387
|
}
|
|
21332
21388
|
const stat8 = statSync12(entitlementPath);
|
|
@@ -21516,12 +21572,12 @@ function clientFrom(c) {
|
|
|
21516
21572
|
);
|
|
21517
21573
|
}
|
|
21518
21574
|
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
21519
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT8 ?
|
|
21575
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join32(PLATFORM_ROOT8, "config", "brand.json") : "";
|
|
21520
21576
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
21521
|
-
if (BRAND_JSON_PATH && !
|
|
21577
|
+
if (BRAND_JSON_PATH && !existsSync32(BRAND_JSON_PATH)) {
|
|
21522
21578
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
21523
21579
|
}
|
|
21524
|
-
if (BRAND_JSON_PATH &&
|
|
21580
|
+
if (BRAND_JSON_PATH && existsSync32(BRAND_JSON_PATH)) {
|
|
21525
21581
|
try {
|
|
21526
21582
|
const parsed = JSON.parse(readFileSync33(BRAND_JSON_PATH, "utf-8"));
|
|
21527
21583
|
BRAND = { ...BRAND, ...parsed };
|
|
@@ -21555,10 +21611,10 @@ var brandLoginOpts = {
|
|
|
21555
21611
|
appleTouchIconPath: brandAppIcon512Path,
|
|
21556
21612
|
themeColor: BRAND.defaultColors?.primary
|
|
21557
21613
|
};
|
|
21558
|
-
var ALIAS_DOMAINS_PATH =
|
|
21614
|
+
var ALIAS_DOMAINS_PATH = join32(homedir3(), BRAND.configDir, "alias-domains.json");
|
|
21559
21615
|
function loadAliasDomains() {
|
|
21560
21616
|
try {
|
|
21561
|
-
if (!
|
|
21617
|
+
if (!existsSync32(ALIAS_DOMAINS_PATH)) return null;
|
|
21562
21618
|
const parsed = JSON.parse(readFileSync33(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
21563
21619
|
if (!Array.isArray(parsed)) {
|
|
21564
21620
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
@@ -21587,7 +21643,7 @@ var app54 = new Hono();
|
|
|
21587
21643
|
var nativeFileFollowers = /* @__PURE__ */ new Map();
|
|
21588
21644
|
var waGateway = new WaGateway({
|
|
21589
21645
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
21590
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ??
|
|
21646
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve32(process.env.MAXY_PLATFORM_ROOT ?? join32(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
21591
21647
|
// Task 751 — file delivery on the native channel: resolve the platform
|
|
21592
21648
|
// account + path validation here and funnel through the shared send core.
|
|
21593
21649
|
sendDocument: async ({ senderId, accountId, filePath, caption }) => {
|
|
@@ -21603,7 +21659,7 @@ var waGateway = new WaGateway({
|
|
|
21603
21659
|
caption,
|
|
21604
21660
|
accountId,
|
|
21605
21661
|
maxyAccountId,
|
|
21606
|
-
platformRoot:
|
|
21662
|
+
platformRoot: resolve32(process.env.MAXY_PLATFORM_ROOT ?? join32(__dirname, ".."))
|
|
21607
21663
|
});
|
|
21608
21664
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
21609
21665
|
},
|
|
@@ -21706,6 +21762,7 @@ var webchatGateway = new WebchatGateway({
|
|
|
21706
21762
|
deleteSession: (sessionId) => managerDelete(sessionId),
|
|
21707
21763
|
firePublicSessionEndReview: (input) => firePublicSessionEndReview(input)
|
|
21708
21764
|
});
|
|
21765
|
+
setWebchatGateway(webchatGateway);
|
|
21709
21766
|
app54.route("/", webchatGateway.routes());
|
|
21710
21767
|
webchatGateway.startSweeper();
|
|
21711
21768
|
webchatGateway.startPublicReaper();
|
|
@@ -21917,7 +21974,7 @@ app54.post("/__remote-auth/change-password", async (c) => {
|
|
|
21917
21974
|
}
|
|
21918
21975
|
try {
|
|
21919
21976
|
if (targetUserId) {
|
|
21920
|
-
await setAccessPassword(targetUserId, newPassword, USERS_FILE);
|
|
21977
|
+
await setAccessPassword(targetUserId, newPassword, USERS_FILE, { actor: targetUserId, logFile: USERS_AUDIT_LOG });
|
|
21921
21978
|
} else {
|
|
21922
21979
|
await setRemotePassword(newPassword);
|
|
21923
21980
|
}
|
|
@@ -22004,7 +22061,7 @@ app54.post("/api/remote-auth/set-password", async (c) => {
|
|
|
22004
22061
|
}
|
|
22005
22062
|
try {
|
|
22006
22063
|
if (sessionUserId) {
|
|
22007
|
-
await setAccessPassword(sessionUserId, body.password, USERS_FILE);
|
|
22064
|
+
await setAccessPassword(sessionUserId, body.password, USERS_FILE, { actor: sessionUserId, logFile: USERS_AUDIT_LOG });
|
|
22008
22065
|
} else {
|
|
22009
22066
|
await setRemotePassword(body.password);
|
|
22010
22067
|
}
|
|
@@ -22109,13 +22166,13 @@ app54.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
22109
22166
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
22110
22167
|
return c.text("Not found", 404);
|
|
22111
22168
|
}
|
|
22112
|
-
const filePath =
|
|
22113
|
-
const expectedDir =
|
|
22169
|
+
const filePath = resolve32(account.accountDir, "agents", slug, "assets", filename);
|
|
22170
|
+
const expectedDir = resolve32(account.accountDir, "agents", slug, "assets");
|
|
22114
22171
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22115
22172
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
22116
22173
|
return c.text("Forbidden", 403);
|
|
22117
22174
|
}
|
|
22118
|
-
if (!
|
|
22175
|
+
if (!existsSync32(filePath)) {
|
|
22119
22176
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
22120
22177
|
return c.text("Not found", 404);
|
|
22121
22178
|
}
|
|
@@ -22139,13 +22196,13 @@ app54.get("/generated/:filename", (c) => {
|
|
|
22139
22196
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22140
22197
|
return c.text("Not found", 404);
|
|
22141
22198
|
}
|
|
22142
|
-
const filePath =
|
|
22143
|
-
const expectedDir =
|
|
22199
|
+
const filePath = resolve32(account.accountDir, "generated", filename);
|
|
22200
|
+
const expectedDir = resolve32(account.accountDir, "generated");
|
|
22144
22201
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22145
22202
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
22146
22203
|
return c.text("Forbidden", 403);
|
|
22147
22204
|
}
|
|
22148
|
-
if (!
|
|
22205
|
+
if (!existsSync32(filePath)) {
|
|
22149
22206
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22150
22207
|
return c.text("Not found", 404);
|
|
22151
22208
|
}
|
|
@@ -22165,7 +22222,7 @@ app54.route("/v", visitor_consent_default);
|
|
|
22165
22222
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
22166
22223
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
22167
22224
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
22168
|
-
if (BRAND_JSON_PATH &&
|
|
22225
|
+
if (BRAND_JSON_PATH && existsSync32(BRAND_JSON_PATH)) {
|
|
22169
22226
|
try {
|
|
22170
22227
|
const fullBrand = JSON.parse(readFileSync33(BRAND_JSON_PATH, "utf-8"));
|
|
22171
22228
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
@@ -22194,11 +22251,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
22194
22251
|
var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
|
|
22195
22252
|
var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
|
|
22196
22253
|
var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
|
|
22197
|
-
(p) =>
|
|
22254
|
+
(p) => existsSync32(resolve32(process.cwd(), "public", p.replace(/^\//, "")))
|
|
22198
22255
|
);
|
|
22199
22256
|
var SW_SOURCE = (() => {
|
|
22200
22257
|
try {
|
|
22201
|
-
return readFileSync33(
|
|
22258
|
+
return readFileSync33(resolve32(process.cwd(), "public", "sw.js"), "utf-8");
|
|
22202
22259
|
} catch {
|
|
22203
22260
|
return null;
|
|
22204
22261
|
}
|
|
@@ -22206,8 +22263,8 @@ var SW_SOURCE = (() => {
|
|
|
22206
22263
|
function readInstalledVersion() {
|
|
22207
22264
|
try {
|
|
22208
22265
|
if (!PLATFORM_ROOT8) return "unknown";
|
|
22209
|
-
const versionFile =
|
|
22210
|
-
if (!
|
|
22266
|
+
const versionFile = join32(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
|
|
22267
|
+
if (!existsSync32(versionFile)) return "unknown";
|
|
22211
22268
|
const content = readFileSync33(versionFile, "utf-8").trim();
|
|
22212
22269
|
return content || "unknown";
|
|
22213
22270
|
} catch {
|
|
@@ -22249,7 +22306,7 @@ var clientErrorReporterScript = `<script>
|
|
|
22249
22306
|
function cachedHtml(file) {
|
|
22250
22307
|
let html = htmlCache.get(file);
|
|
22251
22308
|
if (!html) {
|
|
22252
|
-
html = readFileSync33(
|
|
22309
|
+
html = readFileSync33(resolve32(process.cwd(), "public", file), "utf-8");
|
|
22253
22310
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
22254
22311
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
22255
22312
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -22266,12 +22323,12 @@ ${clientErrorReporterScript}
|
|
|
22266
22323
|
return html;
|
|
22267
22324
|
}
|
|
22268
22325
|
function loadBrandingCache(agentSlug) {
|
|
22269
|
-
const configDir2 =
|
|
22326
|
+
const configDir2 = join32(homedir3(), BRAND.configDir);
|
|
22270
22327
|
try {
|
|
22271
22328
|
const accountId = getDefaultAccountId();
|
|
22272
22329
|
if (!accountId) return null;
|
|
22273
|
-
const cachePath =
|
|
22274
|
-
if (!
|
|
22330
|
+
const cachePath = join32(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
22331
|
+
if (!existsSync32(cachePath)) return null;
|
|
22275
22332
|
return JSON.parse(readFileSync33(cachePath, "utf-8"));
|
|
22276
22333
|
} catch {
|
|
22277
22334
|
return null;
|
|
@@ -22367,7 +22424,7 @@ app54.use("/vnc-popout.html", logViewerFetch);
|
|
|
22367
22424
|
app54.get("/vnc-popout.html", (c) => {
|
|
22368
22425
|
let html = htmlCache.get("vnc-popout.html");
|
|
22369
22426
|
if (!html) {
|
|
22370
|
-
html = readFileSync33(
|
|
22427
|
+
html = readFileSync33(resolve32(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
22371
22428
|
const name = escapeHtml(BRAND.productName);
|
|
22372
22429
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
22373
22430
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -22490,11 +22547,11 @@ var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
|
22490
22547
|
var httpServer = serve({ fetch: app54.fetch, port, hostname });
|
|
22491
22548
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
22492
22549
|
{
|
|
22493
|
-
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
22494
|
-
const reconcileScript =
|
|
22550
|
+
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join32(__dirname, "..");
|
|
22551
|
+
const reconcileScript = resolve32(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
|
|
22495
22552
|
const RECONCILE_INTERVAL_MS = 12e4;
|
|
22496
22553
|
const runReconcile = () => {
|
|
22497
|
-
if (!
|
|
22554
|
+
if (!existsSync32(reconcileScript)) return;
|
|
22498
22555
|
try {
|
|
22499
22556
|
const child = spawn3(process.execPath, [reconcileScript], {
|
|
22500
22557
|
env: { ...process.env, PLATFORM_ROOT: reconcilePlatformRoot },
|
|
@@ -22512,12 +22569,12 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
22512
22569
|
loop.unref();
|
|
22513
22570
|
}
|
|
22514
22571
|
{
|
|
22515
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
22516
|
-
const auditScript =
|
|
22572
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join32(__dirname, "..");
|
|
22573
|
+
const auditScript = resolve32(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
|
|
22517
22574
|
const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
|
|
22518
22575
|
const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
|
|
22519
22576
|
const runConnectorAudit = () => {
|
|
22520
|
-
if (!
|
|
22577
|
+
if (!existsSync32(auditScript)) return;
|
|
22521
22578
|
try {
|
|
22522
22579
|
const child = spawn3(process.execPath, [auditScript], {
|
|
22523
22580
|
env: { ...process.env, PLATFORM_ROOT: auditPlatformRoot, LOG_DIR: auditLogDir },
|
|
@@ -22616,7 +22673,7 @@ try {
|
|
|
22616
22673
|
const migAccount = resolveAccount();
|
|
22617
22674
|
const ownerUserId = migAccount?.config.admins?.find((a) => a.role === "owner")?.userId;
|
|
22618
22675
|
if (ownerUserId) {
|
|
22619
|
-
const result = migrateLegacyRemotePassword(USERS_FILE, REMOTE_PASSWORD_FILE, ownerUserId);
|
|
22676
|
+
const result = migrateLegacyRemotePassword(USERS_FILE, REMOTE_PASSWORD_FILE, ownerUserId, { actor: "boot", logFile: USERS_AUDIT_LOG });
|
|
22620
22677
|
console.error(`[remote-access-migrate] result=${result} ownerUserId=${ownerUserId.slice(0, 8)}`);
|
|
22621
22678
|
} else {
|
|
22622
22679
|
console.error("[remote-access-migrate] result=noop-no-owner ownerUserId=none");
|
|
@@ -22627,7 +22684,7 @@ try {
|
|
|
22627
22684
|
var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
22628
22685
|
async function runAdminUserReconcileTick() {
|
|
22629
22686
|
try {
|
|
22630
|
-
if (!
|
|
22687
|
+
if (!existsSync32(USERS_FILE)) {
|
|
22631
22688
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
22632
22689
|
return;
|
|
22633
22690
|
}
|
|
@@ -22657,6 +22714,35 @@ var adminUserReconcileTimer = setInterval(() => {
|
|
|
22657
22714
|
void runAdminUserReconcileTick();
|
|
22658
22715
|
}, ADMINUSER_RECONCILE_INTERVAL_MS);
|
|
22659
22716
|
if (typeof adminUserReconcileTimer.unref === "function") adminUserReconcileTimer.unref();
|
|
22717
|
+
var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
22718
|
+
function countUsersRows() {
|
|
22719
|
+
if (!existsSync32(USERS_FILE)) return 0;
|
|
22720
|
+
const raw = readFileSync33(USERS_FILE, "utf-8").trim();
|
|
22721
|
+
if (!raw) return 0;
|
|
22722
|
+
const users = JSON.parse(raw);
|
|
22723
|
+
return users.filter((u) => typeof u.userId === "string").length;
|
|
22724
|
+
}
|
|
22725
|
+
function runUsersReconcileTick() {
|
|
22726
|
+
try {
|
|
22727
|
+
const d = computeAdminStoreDivergence({ usersFile: USERS_FILE, accountsDir: ACCOUNTS_DIR });
|
|
22728
|
+
if (d.errors.length > 0) {
|
|
22729
|
+
console.error(`[users-reconcile] op=error detail=${d.errors.map((e) => `${e.source}:${e.detail}`).join(";")}`);
|
|
22730
|
+
return;
|
|
22731
|
+
}
|
|
22732
|
+
if (d.divergences === 0) {
|
|
22733
|
+
console.error(`[users-reconcile] op=ok admins=${countUsersRows()}`);
|
|
22734
|
+
return;
|
|
22735
|
+
}
|
|
22736
|
+
const accountOnly = d.accountWithoutUsers.map((a) => a.userId.slice(0, 8)).join(",");
|
|
22737
|
+
const usersOnly = d.usersWithoutAccount.map((u) => u.userId.slice(0, 8)).join(",");
|
|
22738
|
+
console.error(`[users-reconcile] op=divergence accountOnly=${accountOnly} usersOnly=${usersOnly}`);
|
|
22739
|
+
} catch (err) {
|
|
22740
|
+
console.error(`[users-reconcile] op=error detail=${err instanceof Error ? err.message : String(err)}`);
|
|
22741
|
+
}
|
|
22742
|
+
}
|
|
22743
|
+
runUsersReconcileTick();
|
|
22744
|
+
var usersReconcileTimer = setInterval(runUsersReconcileTick, USERS_RECONCILE_INTERVAL_MS);
|
|
22745
|
+
if (typeof usersReconcileTimer.unref === "function") usersReconcileTimer.unref();
|
|
22660
22746
|
startGraphHealthTimer();
|
|
22661
22747
|
void migrateUploads().catch((err) => console.error(`[uploads-migrate] failed err="${err instanceof Error ? err.message : String(err)}"`)).finally(() => {
|
|
22662
22748
|
void startFileWatcher().catch((err) => {
|
|
@@ -22745,7 +22831,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
22745
22831
|
}
|
|
22746
22832
|
init({
|
|
22747
22833
|
configDir: configDirForWhatsApp,
|
|
22748
|
-
platformRoot:
|
|
22834
|
+
platformRoot: resolve32(process.env.MAXY_PLATFORM_ROOT ?? join32(__dirname, "..")),
|
|
22749
22835
|
accountConfig: bootAccountConfig,
|
|
22750
22836
|
onMessage: async (msg) => {
|
|
22751
22837
|
if (msg.isOwnerMirror) {
|
|
@@ -22786,7 +22872,6 @@ init({
|
|
|
22786
22872
|
}).catch((err) => {
|
|
22787
22873
|
console.error(`[whatsapp] init failed: ${String(err)}`);
|
|
22788
22874
|
});
|
|
22789
|
-
startReaper();
|
|
22790
22875
|
var shuttingDown = false;
|
|
22791
22876
|
process.on("SIGTERM", async () => {
|
|
22792
22877
|
if (shuttingDown) {
|