@rubytech/create-maxy-code 0.1.303 → 0.1.304
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/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +12 -0
- package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +10 -6
- package/payload/platform/plugins/admin/skills/access-manager/SKILL.md +1 -0
- package/payload/platform/plugins/admin/skills/access-manager/references/operations.md +22 -0
- package/payload/platform/plugins/docs/references/admin-session.md +2 -0
- package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +4 -4
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.d.ts +15 -0
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.js +34 -0
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/channel-targets.d.ts +26 -0
- package/payload/platform/services/claude-session-manager/dist/channel-targets.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/channel-targets.js +19 -0
- package/payload/platform/services/claude-session-manager/dist/channel-targets.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +80 -51
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +15 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +13 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts +8 -0
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +183 -21
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +82 -0
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/targets.js +131 -0
- package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -0
- package/payload/server/{chunk-JHSF5VPE.js → chunk-TUY3RB5G.js} +4 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/AdminLoginScreens-BXKCagZn.js +1 -0
- package/payload/server/public/assets/{AdminShell-CXYu03dO.js → AdminShell-CxKd_V3O.js} +1 -1
- package/payload/server/public/assets/{Checkbox-D01p5NFD.js → Checkbox-BHmeAgrw.js} +1 -1
- package/payload/server/public/assets/Transcript-DPDJitcb.js +1 -0
- package/payload/server/public/assets/{admin-DdoI2RXi.js → admin-DHLuBUtk.js} +1 -1
- package/payload/server/public/assets/{audio-attachment-mime-WRXiXXUG.js → audio-attachment-mime-BxI3Kl4_.js} +1 -1
- package/payload/server/public/assets/{browser-Db0TaQdG.js → browser-DgtLMsM1.js} +1 -1
- package/payload/server/public/assets/chat-Ib78Kdhg.js +1 -0
- package/payload/server/public/assets/data-C317FUk4.js +1 -0
- package/payload/server/public/assets/file-download-DXFcVkh6.js +1 -0
- package/payload/server/public/assets/{graph-CWxi74vF.js → graph-CHf_9bmD.js} +2 -2
- package/payload/server/public/assets/{graph-labels-Bxt4mIUp.js → graph-labels-BbiX-sz-.js} +1 -1
- package/payload/server/public/assets/{jsx-runtime-BAMC3pMk.css → jsx-runtime-BkXvvpkS.css} +1 -1
- package/payload/server/public/assets/operator-BJ5tN5vl.js +1 -0
- package/payload/server/public/assets/page-CjuqiMAQ.js +1 -0
- package/payload/server/public/assets/{public-C3iO20f3.js → public-D3G4tgFu.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-DDpddXMY.js → useSelectionMode-DcgaoBP6.js} +1 -1
- package/payload/server/public/browser.html +6 -6
- package/payload/server/public/chat.html +9 -9
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +9 -9
- package/payload/server/public/operator.html +11 -11
- package/payload/server/public/public.html +6 -6
- package/payload/server/server.js +776 -570
- package/payload/server/public/assets/AdminLoginScreens-B11_s63y.js +0 -1
- package/payload/server/public/assets/Transcript-NcTfeF7W.js +0 -1
- package/payload/server/public/assets/chat-BMTC0f2h.js +0 -1
- package/payload/server/public/assets/data-BiWVJQmi.js +0 -1
- package/payload/server/public/assets/file-download-7T2ye2nL.js +0 -1
- package/payload/server/public/assets/operator-Dw1Sep47.js +0 -1
- package/payload/server/public/assets/page-DZ5OxzgL.js +0 -1
- /package/payload/server/public/assets/{jsx-runtime-DhMPqcKn.js → jsx-runtime-BabLCDgS.js} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
getUserTimezone,
|
|
61
61
|
httpLog,
|
|
62
62
|
isActiveAgentSlug,
|
|
63
|
+
isApiRequestPath,
|
|
63
64
|
isOperatorHost,
|
|
64
65
|
isPasswordValid,
|
|
65
66
|
isPublicPathAllowed,
|
|
@@ -104,7 +105,7 @@ import {
|
|
|
104
105
|
vncLog,
|
|
105
106
|
walkPremiumBundles,
|
|
106
107
|
writeAdminUserAndPerson
|
|
107
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-TUY3RB5G.js";
|
|
108
109
|
import {
|
|
109
110
|
__commonJS,
|
|
110
111
|
__toESM
|
|
@@ -1495,7 +1496,7 @@ async function ensureAuth() {
|
|
|
1495
1496
|
}
|
|
1496
1497
|
|
|
1497
1498
|
// server/routes/health.ts
|
|
1498
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1499
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
1499
1500
|
import { createConnection as createConnection2 } from "net";
|
|
1500
1501
|
|
|
1501
1502
|
// app/lib/network.ts
|
|
@@ -2383,10 +2384,295 @@ function checkDmAccess(params) {
|
|
|
2383
2384
|
return { allowed: false, reason: "unknown-dm-policy", agentType: "public" };
|
|
2384
2385
|
}
|
|
2385
2386
|
}
|
|
2387
|
+
async function resolveDmAccess(params) {
|
|
2388
|
+
const base = checkDmAccess(params);
|
|
2389
|
+
if (!base.allowed || base.agentType !== "public") return base;
|
|
2390
|
+
const e164 = normalizeE164(params.senderPhone);
|
|
2391
|
+
if (!e164 || !params.accountId) return base;
|
|
2392
|
+
try {
|
|
2393
|
+
const personId = await params.resolvePersonByPhone(params.accountId, e164);
|
|
2394
|
+
return { ...base, personId };
|
|
2395
|
+
} catch (err) {
|
|
2396
|
+
console.error(
|
|
2397
|
+
`[whatsapp:route] op=person-resolve-error senderId=${e164} err=${err instanceof Error ? err.message : String(err)}`
|
|
2398
|
+
);
|
|
2399
|
+
return base;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2386
2402
|
function checkGroupAccess(_params) {
|
|
2387
2403
|
return { allowed: false, reason: "group-observe-only", agentType: "public" };
|
|
2388
2404
|
}
|
|
2389
2405
|
|
|
2406
|
+
// app/lib/access-gate.ts
|
|
2407
|
+
import neo4j from "neo4j-driver";
|
|
2408
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2409
|
+
import { resolve as resolve2 } from "path";
|
|
2410
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2411
|
+
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
|
|
2412
|
+
var driver = null;
|
|
2413
|
+
function readPassword() {
|
|
2414
|
+
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
2415
|
+
const passwordFile = resolve2(PLATFORM_ROOT2, "config/.neo4j-password");
|
|
2416
|
+
try {
|
|
2417
|
+
return readFileSync3(passwordFile, "utf-8").trim();
|
|
2418
|
+
} catch {
|
|
2419
|
+
throw new Error(
|
|
2420
|
+
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
function getDriver() {
|
|
2425
|
+
if (!driver) {
|
|
2426
|
+
const uri = process.env.NEO4J_URI;
|
|
2427
|
+
if (!uri) {
|
|
2428
|
+
throw new Error(
|
|
2429
|
+
"[ui/access-gate] NEO4J_URI unset \u2014 refusing to default to bolt://localhost:7687"
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
const user = process.env.NEO4J_USER ?? "neo4j";
|
|
2433
|
+
const password = readPassword();
|
|
2434
|
+
console.error(`[ui/access-gate] resolved neo4j_uri=${uri}`);
|
|
2435
|
+
driver = neo4j.driver(uri, neo4j.auth.basic(user, password), {
|
|
2436
|
+
maxConnectionPoolSize: 5
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
return driver;
|
|
2440
|
+
}
|
|
2441
|
+
var sessionFactory = null;
|
|
2442
|
+
function getSession2() {
|
|
2443
|
+
return sessionFactory ? sessionFactory() : getDriver().session();
|
|
2444
|
+
}
|
|
2445
|
+
process.on("SIGINT", async () => {
|
|
2446
|
+
if (driver) {
|
|
2447
|
+
await driver.close();
|
|
2448
|
+
driver = null;
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
2452
|
+
var ACCESS_MAX_ATTEMPTS = 5;
|
|
2453
|
+
var ACCESS_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
2454
|
+
var ACCESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
2455
|
+
function checkAccessRateLimit(ip, agentSlug) {
|
|
2456
|
+
const key = `${ip}:${agentSlug}`;
|
|
2457
|
+
const entry = rateLimitMap.get(key);
|
|
2458
|
+
if (!entry) return null;
|
|
2459
|
+
const now = Date.now();
|
|
2460
|
+
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
2461
|
+
const remainingMs = entry.lockedUntil - now;
|
|
2462
|
+
const remainingS = Math.ceil(remainingMs / 1e3);
|
|
2463
|
+
return `Too many attempts. Try again in ${remainingS}s`;
|
|
2464
|
+
}
|
|
2465
|
+
if (now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
2466
|
+
rateLimitMap.delete(key);
|
|
2467
|
+
return null;
|
|
2468
|
+
}
|
|
2469
|
+
return null;
|
|
2470
|
+
}
|
|
2471
|
+
function recordAccessFailedAttempt(ip, agentSlug) {
|
|
2472
|
+
const key = `${ip}:${agentSlug}`;
|
|
2473
|
+
const now = Date.now();
|
|
2474
|
+
const entry = rateLimitMap.get(key);
|
|
2475
|
+
if (!entry || now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
2476
|
+
rateLimitMap.set(key, { attempts: 1, firstAttempt: now });
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
entry.attempts++;
|
|
2480
|
+
if (entry.attempts >= ACCESS_MAX_ATTEMPTS) {
|
|
2481
|
+
entry.lockedUntil = now + ACCESS_LOCKOUT_MS;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
function clearAccessRateLimit(ip, agentSlug) {
|
|
2485
|
+
rateLimitMap.delete(`${ip}:${agentSlug}`);
|
|
2486
|
+
}
|
|
2487
|
+
var contactRateMap = /* @__PURE__ */ new Map();
|
|
2488
|
+
var REQUEST_LINK_MAX = 3;
|
|
2489
|
+
var REQUEST_LINK_WINDOW_MS = 60 * 60 * 1e3;
|
|
2490
|
+
function checkRequestLinkRateLimit(contactValue) {
|
|
2491
|
+
const entry = contactRateMap.get(contactValue);
|
|
2492
|
+
if (!entry) return null;
|
|
2493
|
+
const now = Date.now();
|
|
2494
|
+
if (now - entry.windowStart > REQUEST_LINK_WINDOW_MS) {
|
|
2495
|
+
contactRateMap.delete(contactValue);
|
|
2496
|
+
return null;
|
|
2497
|
+
}
|
|
2498
|
+
if (entry.count >= REQUEST_LINK_MAX) {
|
|
2499
|
+
const remainingMs = REQUEST_LINK_WINDOW_MS - (now - entry.windowStart);
|
|
2500
|
+
const remainingM = Math.ceil(remainingMs / 6e4);
|
|
2501
|
+
return `Too many requests. Try again in ${remainingM} minute(s)`;
|
|
2502
|
+
}
|
|
2503
|
+
return null;
|
|
2504
|
+
}
|
|
2505
|
+
function recordRequestLinkAttempt(contactValue) {
|
|
2506
|
+
const now = Date.now();
|
|
2507
|
+
const entry = contactRateMap.get(contactValue);
|
|
2508
|
+
if (!entry || now - entry.windowStart > REQUEST_LINK_WINDOW_MS) {
|
|
2509
|
+
contactRateMap.set(contactValue, { count: 1, windowStart: now });
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
entry.count++;
|
|
2513
|
+
}
|
|
2514
|
+
function maskContact(value) {
|
|
2515
|
+
if (value.includes("@")) {
|
|
2516
|
+
const [local, domain] = value.split("@");
|
|
2517
|
+
return `${local[0]}***@${domain}`;
|
|
2518
|
+
}
|
|
2519
|
+
return "****";
|
|
2520
|
+
}
|
|
2521
|
+
function readGrant(record) {
|
|
2522
|
+
const g = record.get("g").properties;
|
|
2523
|
+
const personId = record.get("personId");
|
|
2524
|
+
return {
|
|
2525
|
+
grantId: record.get("grantId"),
|
|
2526
|
+
agentSlug: String(g.agentSlug),
|
|
2527
|
+
accountId: String(g.accountId),
|
|
2528
|
+
contactValue: String(g.contactValue),
|
|
2529
|
+
displayName: record.get("givenName") ?? null,
|
|
2530
|
+
personId,
|
|
2531
|
+
expiresAt: g.expiresAt ? new Date(String(g.expiresAt)).getTime() : null,
|
|
2532
|
+
status: String(g.status),
|
|
2533
|
+
sliceToken: String(g.sliceToken ?? "")
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
async function findGrantByMagicToken(token) {
|
|
2537
|
+
const session = getSession2();
|
|
2538
|
+
try {
|
|
2539
|
+
const result = await session.run(
|
|
2540
|
+
`MATCH (p:Person)-[:HAS_ACCESS]->(g:AccessGrant {magicToken: $token})
|
|
2541
|
+
RETURN g, p.givenName AS givenName,
|
|
2542
|
+
elementId(g) AS grantId,
|
|
2543
|
+
elementId(p) AS personId`,
|
|
2544
|
+
{ token }
|
|
2545
|
+
);
|
|
2546
|
+
if (result.records.length === 0) return null;
|
|
2547
|
+
return readGrant(result.records[0]);
|
|
2548
|
+
} finally {
|
|
2549
|
+
await session.close();
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
|
|
2553
|
+
const session = getSession2();
|
|
2554
|
+
try {
|
|
2555
|
+
const result = await session.run(
|
|
2556
|
+
`MATCH (p:Person)-[:HAS_ACCESS]->(g:AccessGrant {
|
|
2557
|
+
contactValue: $contactValue,
|
|
2558
|
+
agentSlug: $agentSlug,
|
|
2559
|
+
accountId: $accountId
|
|
2560
|
+
})
|
|
2561
|
+
WHERE g.status IN ['invited', 'active']
|
|
2562
|
+
RETURN g, p.givenName AS givenName,
|
|
2563
|
+
elementId(g) AS grantId,
|
|
2564
|
+
elementId(p) AS personId`,
|
|
2565
|
+
{ contactValue, agentSlug, accountId }
|
|
2566
|
+
);
|
|
2567
|
+
if (result.records.length === 0) return null;
|
|
2568
|
+
return readGrant(result.records[0]);
|
|
2569
|
+
} finally {
|
|
2570
|
+
await session.close();
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
async function findGrantByContact(contactValue, agentSlug, accountId) {
|
|
2574
|
+
const session = getSession2();
|
|
2575
|
+
try {
|
|
2576
|
+
const result = await session.run(
|
|
2577
|
+
`MATCH (p:Person)-[:HAS_ACCESS]->(g:AccessGrant {
|
|
2578
|
+
contactValue: $contactValue,
|
|
2579
|
+
agentSlug: $agentSlug,
|
|
2580
|
+
accountId: $accountId
|
|
2581
|
+
})
|
|
2582
|
+
RETURN g, p.givenName AS givenName,
|
|
2583
|
+
elementId(g) AS grantId,
|
|
2584
|
+
elementId(p) AS personId
|
|
2585
|
+
LIMIT 1`,
|
|
2586
|
+
{ contactValue, agentSlug, accountId }
|
|
2587
|
+
);
|
|
2588
|
+
if (result.records.length === 0) return null;
|
|
2589
|
+
return readGrant(result.records[0]);
|
|
2590
|
+
} finally {
|
|
2591
|
+
await session.close();
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
async function resolvePersonByPhone(accountId, phone) {
|
|
2595
|
+
const session = getSession2();
|
|
2596
|
+
try {
|
|
2597
|
+
const result = await session.run(
|
|
2598
|
+
`MATCH (p:Person {accountId: $accountId, phone: $phone})
|
|
2599
|
+
RETURN elementId(p) AS personId
|
|
2600
|
+
LIMIT 1`,
|
|
2601
|
+
{ accountId, phone }
|
|
2602
|
+
);
|
|
2603
|
+
if (result.records.length === 0) return null;
|
|
2604
|
+
return result.records[0].get("personId");
|
|
2605
|
+
} finally {
|
|
2606
|
+
await session.close();
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
async function enrolPerson(input) {
|
|
2610
|
+
const session = getSession2();
|
|
2611
|
+
const sliceToken = randomUUID2();
|
|
2612
|
+
const phone = normalizeE164(input.phone) || input.phone;
|
|
2613
|
+
try {
|
|
2614
|
+
const result = await session.run(
|
|
2615
|
+
`MERGE (p:Person {accountId: $accountId, phone: $phone})
|
|
2616
|
+
MERGE (p)-[:HAS_ACCESS]->(g:AccessGrant {
|
|
2617
|
+
accountId: $accountId, agentSlug: $agentSlug, contactValue: $email
|
|
2618
|
+
})
|
|
2619
|
+
ON CREATE SET g.status = 'invited', g.sliceToken = $sliceToken
|
|
2620
|
+
RETURN elementId(p) AS personId`,
|
|
2621
|
+
{
|
|
2622
|
+
accountId: input.accountId,
|
|
2623
|
+
phone,
|
|
2624
|
+
email: input.email,
|
|
2625
|
+
agentSlug: input.agentSlug,
|
|
2626
|
+
sliceToken
|
|
2627
|
+
}
|
|
2628
|
+
);
|
|
2629
|
+
return result.records[0].get("personId");
|
|
2630
|
+
} finally {
|
|
2631
|
+
await session.close();
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
async function consumeMagicTokenAndActivate(grantId) {
|
|
2635
|
+
const session = getSession2();
|
|
2636
|
+
try {
|
|
2637
|
+
const result = await session.run(
|
|
2638
|
+
`MATCH (g:AccessGrant) WHERE elementId(g) = $grantId
|
|
2639
|
+
SET g.magicToken = null,
|
|
2640
|
+
g.magicTokenExpiresAt = null,
|
|
2641
|
+
g.status = "active"
|
|
2642
|
+
RETURN count(g) AS affected`,
|
|
2643
|
+
{ grantId }
|
|
2644
|
+
);
|
|
2645
|
+
const affected = result.records[0]?.get("affected");
|
|
2646
|
+
const count = typeof affected === "number" ? affected : affected?.toNumber?.() ?? 0;
|
|
2647
|
+
if (count === 0) {
|
|
2648
|
+
throw new Error(`[access-gate] consumeMagicTokenAndActivate: no grant matched grantId=${grantId}`);
|
|
2649
|
+
}
|
|
2650
|
+
} finally {
|
|
2651
|
+
await session.close();
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
async function generateNewMagicToken(grantId) {
|
|
2655
|
+
const token = randomUUID2();
|
|
2656
|
+
const session = getSession2();
|
|
2657
|
+
try {
|
|
2658
|
+
const result = await session.run(
|
|
2659
|
+
`MATCH (g:AccessGrant) WHERE elementId(g) = $grantId
|
|
2660
|
+
SET g.magicToken = $token,
|
|
2661
|
+
g.magicTokenExpiresAt = datetime() + duration('PT15M')
|
|
2662
|
+
RETURN count(g) AS affected`,
|
|
2663
|
+
{ grantId, token }
|
|
2664
|
+
);
|
|
2665
|
+
const affected = result.records[0]?.get("affected");
|
|
2666
|
+
const count = typeof affected === "number" ? affected : affected?.toNumber?.() ?? 0;
|
|
2667
|
+
if (count === 0) {
|
|
2668
|
+
throw new Error(`[access-gate] generateNewMagicToken: no grant matched grantId=${grantId}`);
|
|
2669
|
+
}
|
|
2670
|
+
} finally {
|
|
2671
|
+
await session.close();
|
|
2672
|
+
}
|
|
2673
|
+
return token;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2390
2676
|
// app/lib/whatsapp/inbound/dedupe.ts
|
|
2391
2677
|
var AGENT_SENT_TTL_MS = 2 * 6e4;
|
|
2392
2678
|
var AGENT_SENT_MAX = 500;
|
|
@@ -2825,8 +3111,8 @@ async function ensureWhatsAppConversation(input) {
|
|
|
2825
3111
|
}
|
|
2826
3112
|
|
|
2827
3113
|
// ../lib/account-enumeration/src/index.ts
|
|
2828
|
-
import { readdirSync, readFileSync as
|
|
2829
|
-
import { resolve as
|
|
3114
|
+
import { readdirSync, readFileSync as readFileSync4 } from "fs";
|
|
3115
|
+
import { resolve as resolve3 } from "path";
|
|
2830
3116
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2831
3117
|
var cache = /* @__PURE__ */ new Map();
|
|
2832
3118
|
function enumerateValidAccountIds(accountsDir) {
|
|
@@ -2845,9 +3131,9 @@ function enumerateValidAccountIds(accountsDir) {
|
|
|
2845
3131
|
const valid = [];
|
|
2846
3132
|
for (const name of names) {
|
|
2847
3133
|
if (!UUID_RE.test(name)) continue;
|
|
2848
|
-
const configPath2 =
|
|
3134
|
+
const configPath2 = resolve3(accountsDir, name, "account.json");
|
|
2849
3135
|
try {
|
|
2850
|
-
JSON.parse(
|
|
3136
|
+
JSON.parse(readFileSync4(configPath2, "utf-8"));
|
|
2851
3137
|
valid.push(name);
|
|
2852
3138
|
} catch (err) {
|
|
2853
3139
|
const code = err.code;
|
|
@@ -2864,7 +3150,7 @@ function getAccountsDirFromEnv() {
|
|
|
2864
3150
|
"[graph-write] MAXY_PLATFORM_ROOT not set \u2014 cannot enforce accountId gate. Set MAXY_PLATFORM_ROOT in the spawning process or pass `accountsDir` explicitly."
|
|
2865
3151
|
);
|
|
2866
3152
|
}
|
|
2867
|
-
return
|
|
3153
|
+
return resolve3(root, "..", "data/accounts");
|
|
2868
3154
|
}
|
|
2869
3155
|
function validateAccountIdEnv(envValue, diskIds) {
|
|
2870
3156
|
if (!envValue) {
|
|
@@ -2901,7 +3187,7 @@ function resolvePlatformAccountId(accountsDir = ACCOUNTS_DIR) {
|
|
|
2901
3187
|
}
|
|
2902
3188
|
|
|
2903
3189
|
// app/lib/whatsapp/inbound/media.ts
|
|
2904
|
-
import { randomUUID as
|
|
3190
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2905
3191
|
import { writeFile, mkdir } from "fs/promises";
|
|
2906
3192
|
import { join as join3 } from "path";
|
|
2907
3193
|
import {
|
|
@@ -2988,7 +3274,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
2988
3274
|
}
|
|
2989
3275
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
2990
3276
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
2991
|
-
const filename = `${
|
|
3277
|
+
const filename = `${randomUUID3()}.${ext}`;
|
|
2992
3278
|
const filePath = join3(MEDIA_DIR, filename);
|
|
2993
3279
|
await writeFile(filePath, buffer);
|
|
2994
3280
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
@@ -4018,11 +4304,13 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4018
4304
|
if (isGroup) {
|
|
4019
4305
|
accessResult = checkGroupAccess({ groupJid: remoteJid });
|
|
4020
4306
|
} else {
|
|
4021
|
-
accessResult =
|
|
4307
|
+
accessResult = await resolveDmAccess({
|
|
4022
4308
|
senderPhone,
|
|
4023
4309
|
selfPhone,
|
|
4024
4310
|
config: whatsAppConfig,
|
|
4025
|
-
accountConfig: whatsAppConfig.accounts?.[conn.accountId]
|
|
4311
|
+
accountConfig: whatsAppConfig.accounts?.[conn.accountId],
|
|
4312
|
+
accountId: conn.accountId,
|
|
4313
|
+
resolvePersonByPhone
|
|
4026
4314
|
});
|
|
4027
4315
|
}
|
|
4028
4316
|
console.error(
|
|
@@ -4060,6 +4348,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4060
4348
|
const payload = {
|
|
4061
4349
|
accountId: conn.accountId,
|
|
4062
4350
|
agentType: accessResult.agentType,
|
|
4351
|
+
personId: accessResult.personId ?? null,
|
|
4063
4352
|
senderPhone,
|
|
4064
4353
|
text: extracted.text,
|
|
4065
4354
|
isGroup,
|
|
@@ -4098,10 +4387,10 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4098
4387
|
// app/lib/vnc.ts
|
|
4099
4388
|
import { spawnSync, execFileSync } from "child_process";
|
|
4100
4389
|
import { createConnection } from "net";
|
|
4101
|
-
import { mkdirSync, readFileSync as
|
|
4102
|
-
import { resolve as
|
|
4103
|
-
var
|
|
4104
|
-
var VNC_SCRIPT =
|
|
4390
|
+
import { mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
4391
|
+
import { resolve as resolve4 } from "path";
|
|
4392
|
+
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
4393
|
+
var VNC_SCRIPT = resolve4(PLATFORM_ROOT3, "scripts/vnc.sh");
|
|
4105
4394
|
var displayMode = process.env.DISPLAY_MODE ?? "virtual";
|
|
4106
4395
|
if (displayMode === "native") {
|
|
4107
4396
|
console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
|
|
@@ -4156,7 +4445,7 @@ function discoverNativeDisplay() {
|
|
|
4156
4445
|
const leaderPid = leaderResult.stdout?.trim();
|
|
4157
4446
|
if (leaderPid) {
|
|
4158
4447
|
try {
|
|
4159
|
-
const environ =
|
|
4448
|
+
const environ = readFileSync5(`/proc/${leaderPid}/environ`, "utf8");
|
|
4160
4449
|
const match = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
|
|
4161
4450
|
if (match) waylandDisplay = match.split("=")[1];
|
|
4162
4451
|
} catch {
|
|
@@ -4214,7 +4503,7 @@ function ensureLogDir() {
|
|
|
4214
4503
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
4215
4504
|
}
|
|
4216
4505
|
function logPath(name) {
|
|
4217
|
-
return
|
|
4506
|
+
return resolve4(LOG_DIR, `${name}.log`);
|
|
4218
4507
|
}
|
|
4219
4508
|
async function ensureVnc() {
|
|
4220
4509
|
const up = await waitForPort(RFB_PORT, 1e3);
|
|
@@ -4293,7 +4582,7 @@ function killChromium() {
|
|
|
4293
4582
|
}
|
|
4294
4583
|
function writeChromiumWrapper() {
|
|
4295
4584
|
mkdirSync(BIN_DIR, { recursive: true });
|
|
4296
|
-
const wrapperPath =
|
|
4585
|
+
const wrapperPath = resolve4(BIN_DIR, "chromium");
|
|
4297
4586
|
writeFileSync3(wrapperPath, `#!/bin/bash
|
|
4298
4587
|
LOG="${LOG_DIR}/chromium.log"
|
|
4299
4588
|
echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
|
|
@@ -4315,7 +4604,7 @@ fi
|
|
|
4315
4604
|
# replaced). Refusing the legacy probe loop closes the silent-fallback path
|
|
4316
4605
|
# where /snap/bin/chromium would re-introduce the AppArmor SingletonLock
|
|
4317
4606
|
# denial that triggered.
|
|
4318
|
-
PATH_FILE="${
|
|
4607
|
+
PATH_FILE="${PLATFORM_ROOT3}/config/chromium-binary.path"
|
|
4319
4608
|
if [ ! -r "$PATH_FILE" ]; then
|
|
4320
4609
|
echo " ERROR: $PATH_FILE missing \u2014 re-run installer" >> "$LOG"
|
|
4321
4610
|
exit 1
|
|
@@ -4416,7 +4705,7 @@ app.get("/", async (c) => {
|
|
|
4416
4705
|
let pinConfigured = false;
|
|
4417
4706
|
try {
|
|
4418
4707
|
if (existsSync3(USERS_FILE)) {
|
|
4419
|
-
const raw =
|
|
4708
|
+
const raw = readFileSync6(USERS_FILE, "utf-8").trim();
|
|
4420
4709
|
if (raw) {
|
|
4421
4710
|
const users = JSON.parse(raw);
|
|
4422
4711
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -4473,12 +4762,12 @@ app.get("/", async (c) => {
|
|
|
4473
4762
|
var health_default = app;
|
|
4474
4763
|
|
|
4475
4764
|
// app/lib/attachments.ts
|
|
4476
|
-
import { randomUUID as
|
|
4765
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4477
4766
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
4478
4767
|
import { realpathSync } from "fs";
|
|
4479
|
-
import { resolve as
|
|
4480
|
-
var
|
|
4481
|
-
var ATTACHMENTS_ROOT =
|
|
4768
|
+
import { resolve as resolve5, extname } from "path";
|
|
4769
|
+
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "../platform");
|
|
4770
|
+
var ATTACHMENTS_ROOT = resolve5(PLATFORM_ROOT4, "..", "data/uploads");
|
|
4482
4771
|
var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
4483
4772
|
"image/jpeg",
|
|
4484
4773
|
"image/png",
|
|
@@ -4504,12 +4793,12 @@ function assertSupportedMime(mimeType) {
|
|
|
4504
4793
|
}
|
|
4505
4794
|
}
|
|
4506
4795
|
async function writeAttachment(scope, filename, mimeType, sizeBytes, buffer) {
|
|
4507
|
-
const attachmentId =
|
|
4508
|
-
const dir =
|
|
4796
|
+
const attachmentId = randomUUID4();
|
|
4797
|
+
const dir = resolve5(ATTACHMENTS_ROOT, scope, attachmentId);
|
|
4509
4798
|
await mkdir2(dir, { recursive: true });
|
|
4510
4799
|
const ext = extname(filename) || "";
|
|
4511
|
-
const storagePath =
|
|
4512
|
-
const metaPath =
|
|
4800
|
+
const storagePath = resolve5(dir, `${attachmentId}${ext}`);
|
|
4801
|
+
const metaPath = resolve5(dir, `${attachmentId}.meta.json`);
|
|
4513
4802
|
const meta = {
|
|
4514
4803
|
attachmentId,
|
|
4515
4804
|
scope,
|
|
@@ -4583,11 +4872,11 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
|
|
|
4583
4872
|
`Component artefact exceeds the 50 MB limit (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB).`
|
|
4584
4873
|
);
|
|
4585
4874
|
}
|
|
4586
|
-
const dir =
|
|
4875
|
+
const dir = resolve5(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
4587
4876
|
await mkdir2(dir, { recursive: true });
|
|
4588
4877
|
const ext = extname(filename) || (mimeType === "text/html" ? ".html" : ".md");
|
|
4589
|
-
const storagePath =
|
|
4590
|
-
const metaPath =
|
|
4878
|
+
const storagePath = resolve5(dir, `${attachmentId}${ext}`);
|
|
4879
|
+
const metaPath = resolve5(dir, `${attachmentId}.meta.json`);
|
|
4591
4880
|
const meta = {
|
|
4592
4881
|
attachmentId,
|
|
4593
4882
|
scope: accountId,
|
|
@@ -4691,7 +4980,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4691
4980
|
}
|
|
4692
4981
|
|
|
4693
4982
|
// app/lib/channel-pty-bridge/bridge.ts
|
|
4694
|
-
import { resolve as
|
|
4983
|
+
import { resolve as resolve7 } from "path";
|
|
4695
4984
|
|
|
4696
4985
|
// app/lib/channel-pty-bridge/manager-client.ts
|
|
4697
4986
|
function managerBase() {
|
|
@@ -4811,8 +5100,9 @@ function classifyTimeout(ndjson, writeAtMs) {
|
|
|
4811
5100
|
|
|
4812
5101
|
// app/lib/channel-pty-bridge/admin-session-id.ts
|
|
4813
5102
|
import { createHash } from "crypto";
|
|
4814
|
-
function adminSessionIdFor(accountId, senderId) {
|
|
4815
|
-
const
|
|
5103
|
+
function adminSessionIdFor(accountId, senderId, personId) {
|
|
5104
|
+
const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
|
|
5105
|
+
const b = createHash("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
|
|
4816
5106
|
const v = Buffer.from(b);
|
|
4817
5107
|
v[6] = v[6] & 15 | 64;
|
|
4818
5108
|
v[8] = v[8] & 63 | 128;
|
|
@@ -4821,7 +5111,7 @@ function adminSessionIdFor(accountId, senderId) {
|
|
|
4821
5111
|
}
|
|
4822
5112
|
|
|
4823
5113
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
4824
|
-
import { randomUUID as
|
|
5114
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
4825
5115
|
var TAG15 = "[public-session-review]";
|
|
4826
5116
|
var CONSUMED_CAP = 500;
|
|
4827
5117
|
var consumed = /* @__PURE__ */ new Set();
|
|
@@ -4909,7 +5199,7 @@ function composeInitialMessage(input) {
|
|
|
4909
5199
|
].join("\n");
|
|
4910
5200
|
}
|
|
4911
5201
|
async function dispatchReviewer(input, initialMessage) {
|
|
4912
|
-
const sessionId =
|
|
5202
|
+
const sessionId = randomUUID5();
|
|
4913
5203
|
console.log(`${TAG15} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
4914
5204
|
const spawned = await managerRcSpawn({
|
|
4915
5205
|
sessionId,
|
|
@@ -5143,7 +5433,7 @@ async function fanOut(subscribers, text, onError, tag) {
|
|
|
5143
5433
|
// app/lib/whatsapp/outbound/send-document.ts
|
|
5144
5434
|
import { realpathSync as realpathSync2 } from "fs";
|
|
5145
5435
|
import { readFile, stat as stat2 } from "fs/promises";
|
|
5146
|
-
import { resolve as
|
|
5436
|
+
import { resolve as resolve6, basename } from "path";
|
|
5147
5437
|
var TAG16 = "[whatsapp:outbound]";
|
|
5148
5438
|
var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
|
|
5149
5439
|
var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
@@ -5174,7 +5464,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
5174
5464
|
if (!maxyAccountId || !platformRoot2) {
|
|
5175
5465
|
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
5176
5466
|
}
|
|
5177
|
-
const accountDir =
|
|
5467
|
+
const accountDir = resolve6(platformRoot2, "..", "data/accounts", maxyAccountId);
|
|
5178
5468
|
let resolvedPath;
|
|
5179
5469
|
try {
|
|
5180
5470
|
resolvedPath = realpathSync2(filePath);
|
|
@@ -5331,7 +5621,10 @@ function publicIdleMs() {
|
|
|
5331
5621
|
return Number(process.env.CHANNEL_PTY_IDLE_MS ?? String(5 * 6e4));
|
|
5332
5622
|
}
|
|
5333
5623
|
var index = /* @__PURE__ */ new Map();
|
|
5334
|
-
function indexKey(accountId, agentSlug, senderId, sliceToken = "") {
|
|
5624
|
+
function indexKey(accountId, agentSlug, senderId, sliceToken = "", personId = null) {
|
|
5625
|
+
if (personId && personId.length > 0) {
|
|
5626
|
+
return `${accountId}:${agentSlug}:person:${personId}`;
|
|
5627
|
+
}
|
|
5335
5628
|
const slice = sliceToken && sliceToken.length > 0 ? sliceToken : "open";
|
|
5336
5629
|
return `${accountId}:${agentSlug}:${slice}:${senderId}`;
|
|
5337
5630
|
}
|
|
@@ -5340,7 +5633,7 @@ async function ensureEntry(input) {
|
|
|
5340
5633
|
const sliceTokenValue = input.sliceToken ?? "";
|
|
5341
5634
|
const personIdValue = input.personId ?? null;
|
|
5342
5635
|
const nameValue = input.name ?? null;
|
|
5343
|
-
const key = indexKey(input.accountId, input.agentSlug, input.senderId, sliceTokenValue);
|
|
5636
|
+
const key = indexKey(input.accountId, input.agentSlug, input.senderId, sliceTokenValue, personIdValue);
|
|
5344
5637
|
const existing = index.get(key);
|
|
5345
5638
|
const tag = tagFor(input.channel);
|
|
5346
5639
|
if (existing) {
|
|
@@ -5365,7 +5658,7 @@ async function ensureEntry(input) {
|
|
|
5365
5658
|
}
|
|
5366
5659
|
let spawned;
|
|
5367
5660
|
if (input.role === "admin") {
|
|
5368
|
-
const adminSessionId = adminSessionIdFor(input.accountId, input.senderId);
|
|
5661
|
+
const adminSessionId = adminSessionIdFor(input.accountId, input.senderId, input.personId);
|
|
5369
5662
|
console.error(`${tag} route role=admin target=rc-spawn senderId=${input.senderId}`);
|
|
5370
5663
|
spawned = await managerRcSpawn({
|
|
5371
5664
|
sessionId: adminSessionId,
|
|
@@ -5381,7 +5674,7 @@ async function ensureEntry(input) {
|
|
|
5381
5674
|
});
|
|
5382
5675
|
} else {
|
|
5383
5676
|
console.error(`${tag} route role=${input.role} target=public-spawn senderId=${input.senderId}`);
|
|
5384
|
-
const attachmentDir =
|
|
5677
|
+
const attachmentDir = resolve7(ATTACHMENTS_ROOT, "public", input.senderId);
|
|
5385
5678
|
spawned = await managerSpawn({
|
|
5386
5679
|
senderId: input.senderId,
|
|
5387
5680
|
role: input.role,
|
|
@@ -5460,7 +5753,7 @@ async function writeInput(entry, text) {
|
|
|
5460
5753
|
dispatchFor: "exit"
|
|
5461
5754
|
});
|
|
5462
5755
|
}
|
|
5463
|
-
index.delete(indexKey(entry.accountId, entry.agentSlug, entry.senderId, entry.sliceToken));
|
|
5756
|
+
index.delete(indexKey(entry.accountId, entry.agentSlug, entry.senderId, entry.sliceToken, entry.personId));
|
|
5464
5757
|
const respawned = await ensureEntry({
|
|
5465
5758
|
accountId: entry.accountId,
|
|
5466
5759
|
senderId: entry.senderId,
|
|
@@ -5621,10 +5914,10 @@ function handlePublicSessionExit(sessionId) {
|
|
|
5621
5914
|
}
|
|
5622
5915
|
|
|
5623
5916
|
// app/lib/access-session-store.ts
|
|
5624
|
-
import { randomUUID as
|
|
5917
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5625
5918
|
var sessions = /* @__PURE__ */ new Map();
|
|
5626
5919
|
function createAccessSession(input) {
|
|
5627
|
-
const sessionId =
|
|
5920
|
+
const sessionId = randomUUID6();
|
|
5628
5921
|
const session = {
|
|
5629
5922
|
...input,
|
|
5630
5923
|
sessionId,
|
|
@@ -5888,23 +6181,23 @@ ${result.result.text}` : result.result.text;
|
|
|
5888
6181
|
var chat_default = app2;
|
|
5889
6182
|
|
|
5890
6183
|
// server/routes/whatsapp.ts
|
|
5891
|
-
import { join as join7, resolve as
|
|
5892
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
6184
|
+
import { join as join7, resolve as resolve9 } from "path";
|
|
6185
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync8, existsSync as existsSync5 } from "fs";
|
|
5893
6186
|
|
|
5894
6187
|
// app/lib/whatsapp/login.ts
|
|
5895
|
-
import { randomUUID as
|
|
6188
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
5896
6189
|
|
|
5897
6190
|
// app/lib/whatsapp/config-persist.ts
|
|
5898
|
-
import { readFileSync as
|
|
5899
|
-
import { resolve as
|
|
6191
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
|
|
6192
|
+
import { resolve as resolve8, join as join5 } from "path";
|
|
5900
6193
|
var TAG18 = "[whatsapp:config]";
|
|
5901
6194
|
function configPath(accountDir) {
|
|
5902
|
-
return
|
|
6195
|
+
return resolve8(accountDir, "account.json");
|
|
5903
6196
|
}
|
|
5904
6197
|
function readConfig(accountDir) {
|
|
5905
6198
|
const path2 = configPath(accountDir);
|
|
5906
6199
|
if (!existsSync4(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
5907
|
-
return JSON.parse(
|
|
6200
|
+
return JSON.parse(readFileSync7(path2, "utf-8"));
|
|
5908
6201
|
}
|
|
5909
6202
|
function writeConfig(accountDir, config) {
|
|
5910
6203
|
const path2 = configPath(accountDir);
|
|
@@ -6347,7 +6640,7 @@ async function startLogin(opts) {
|
|
|
6347
6640
|
accountId,
|
|
6348
6641
|
authDir,
|
|
6349
6642
|
accountDir,
|
|
6350
|
-
id:
|
|
6643
|
+
id: randomUUID7(),
|
|
6351
6644
|
sock,
|
|
6352
6645
|
startedAt: Date.now(),
|
|
6353
6646
|
phone: digits,
|
|
@@ -6579,7 +6872,7 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6579
6872
|
|
|
6580
6873
|
// server/routes/whatsapp.ts
|
|
6581
6874
|
var TAG21 = "[whatsapp:api]";
|
|
6582
|
-
var
|
|
6875
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
6583
6876
|
var app3 = new Hono();
|
|
6584
6877
|
app3.get("/status", (c) => {
|
|
6585
6878
|
try {
|
|
@@ -6704,17 +6997,17 @@ app3.post("/config", async (c) => {
|
|
|
6704
6997
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
6705
6998
|
}
|
|
6706
6999
|
case "list-public-agents": {
|
|
6707
|
-
const agentsDir =
|
|
7000
|
+
const agentsDir = resolve9(account.accountDir, "agents");
|
|
6708
7001
|
const agents = [];
|
|
6709
7002
|
if (existsSync5(agentsDir)) {
|
|
6710
7003
|
try {
|
|
6711
7004
|
const entries = readdirSync3(agentsDir, { withFileTypes: true });
|
|
6712
7005
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6713
7006
|
if (!entry.isDirectory() || entry.name === "admin") continue;
|
|
6714
|
-
const configPath2 =
|
|
7007
|
+
const configPath2 = resolve9(agentsDir, entry.name, "config.json");
|
|
6715
7008
|
if (!existsSync5(configPath2)) continue;
|
|
6716
7009
|
try {
|
|
6717
|
-
const config = JSON.parse(
|
|
7010
|
+
const config = JSON.parse(readFileSync8(configPath2, "utf-8"));
|
|
6718
7011
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
6719
7012
|
} catch {
|
|
6720
7013
|
console.error(`${TAG21} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
@@ -6789,7 +7082,7 @@ app3.post("/send-document", async (c) => {
|
|
|
6789
7082
|
caption,
|
|
6790
7083
|
accountId,
|
|
6791
7084
|
maxyAccountId,
|
|
6792
|
-
platformRoot:
|
|
7085
|
+
platformRoot: PLATFORM_ROOT5
|
|
6793
7086
|
});
|
|
6794
7087
|
if (!result.ok) return c.json({ error: result.error }, result.status);
|
|
6795
7088
|
recordRouteDocumentOutbound(to, filePath);
|
|
@@ -6983,12 +7276,12 @@ app3.get("/group-info", async (c) => {
|
|
|
6983
7276
|
var whatsapp_default = app3;
|
|
6984
7277
|
|
|
6985
7278
|
// server/routes/whatsapp-reader.ts
|
|
6986
|
-
import { readFileSync as
|
|
6987
|
-
import { basename as basename2, dirname, join as join10, resolve as
|
|
7279
|
+
import { readFileSync as readFileSync11, watch, statSync as statSync4, openSync, readSync, closeSync, existsSync as existsSync7, readdirSync as readdirSync6 } from "fs";
|
|
7280
|
+
import { basename as basename2, dirname, join as join10, resolve as resolve11, sep as sep2 } from "path";
|
|
6988
7281
|
|
|
6989
7282
|
// server/routes/admin/sidebar-sessions.ts
|
|
6990
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
6991
|
-
import { join as join8, resolve as
|
|
7283
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
|
|
7284
|
+
import { join as join8, resolve as resolve10 } from "path";
|
|
6992
7285
|
var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i;
|
|
6993
7286
|
var CLI_MARKER_PREFIXES = ["<local-command-", "<command-name>", "<command-message>"];
|
|
6994
7287
|
var PUBLIC_ROLE = "public";
|
|
@@ -7082,7 +7375,7 @@ function loadUserTitles(accountDir) {
|
|
|
7082
7375
|
const path2 = join8(accountDir, "session-titles.json");
|
|
7083
7376
|
let raw;
|
|
7084
7377
|
try {
|
|
7085
|
-
raw =
|
|
7378
|
+
raw = readFileSync9(path2, "utf8");
|
|
7086
7379
|
} catch {
|
|
7087
7380
|
return out;
|
|
7088
7381
|
}
|
|
@@ -7108,7 +7401,7 @@ function readSidecarMeta(metaPath) {
|
|
|
7108
7401
|
const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null };
|
|
7109
7402
|
let raw;
|
|
7110
7403
|
try {
|
|
7111
|
-
raw =
|
|
7404
|
+
raw = readFileSync9(metaPath, "utf8");
|
|
7112
7405
|
} catch {
|
|
7113
7406
|
return empty;
|
|
7114
7407
|
}
|
|
@@ -7179,7 +7472,7 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
7179
7472
|
const projectsRoot = join8(configDir2, "projects");
|
|
7180
7473
|
const jsonls = enumerateJsonls(projectsRoot);
|
|
7181
7474
|
const liveSessions = await fetchLiveSessions();
|
|
7182
|
-
const accountDir = accountId ?
|
|
7475
|
+
const accountDir = accountId ? resolve10(ACCOUNTS_DIR, accountId) : null;
|
|
7183
7476
|
const userTitles = loadUserTitles(accountDir);
|
|
7184
7477
|
const rows = [];
|
|
7185
7478
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -7201,7 +7494,7 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
7201
7494
|
let body;
|
|
7202
7495
|
let mtimeMs;
|
|
7203
7496
|
try {
|
|
7204
|
-
body =
|
|
7497
|
+
body = readFileSync9(path2, "utf8");
|
|
7205
7498
|
mtimeMs = statSync2(path2).mtimeMs;
|
|
7206
7499
|
} catch {
|
|
7207
7500
|
continue;
|
|
@@ -7256,14 +7549,14 @@ function selectAdminChannelSessions(rows) {
|
|
|
7256
7549
|
|
|
7257
7550
|
// app/lib/admin-identity/pin-validator.ts
|
|
7258
7551
|
import { createHash as createHash2 } from "crypto";
|
|
7259
|
-
import { existsSync as existsSync6, readFileSync as
|
|
7552
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
|
|
7260
7553
|
import { join as join9 } from "path";
|
|
7261
7554
|
function hashPin(pin) {
|
|
7262
7555
|
return createHash2("sha256").update(pin).digest("hex");
|
|
7263
7556
|
}
|
|
7264
7557
|
function readUsersFile(usersFilePath) {
|
|
7265
7558
|
if (!existsSync6(usersFilePath)) return null;
|
|
7266
|
-
const raw =
|
|
7559
|
+
const raw = readFileSync10(usersFilePath, "utf-8").trim();
|
|
7267
7560
|
if (!raw) return [];
|
|
7268
7561
|
return JSON.parse(raw);
|
|
7269
7562
|
}
|
|
@@ -7300,7 +7593,7 @@ function scanForOrphanedAccountAdmins(users, accountsDir) {
|
|
|
7300
7593
|
const accountJsonPath = join9(accountDir, "account.json");
|
|
7301
7594
|
if (!existsSync6(accountJsonPath)) continue;
|
|
7302
7595
|
try {
|
|
7303
|
-
const config = JSON.parse(
|
|
7596
|
+
const config = JSON.parse(readFileSync10(accountJsonPath, "utf-8"));
|
|
7304
7597
|
const admins = config.admins ?? [];
|
|
7305
7598
|
for (const a of admins) {
|
|
7306
7599
|
if (typeof a.userId === "string" && !known.has(a.userId)) orphans.push(a.userId);
|
|
@@ -7355,9 +7648,32 @@ function resolveOperatorDisplay(userIdRes, nameRes) {
|
|
|
7355
7648
|
var WA_REPLY_TOOL = "mcp__whatsapp-channel__reply";
|
|
7356
7649
|
var WA_REPLY_DOCUMENT_TOOL = "mcp__whatsapp-channel__reply-document";
|
|
7357
7650
|
var WEBCHAT_REPLY_TOOL = "mcp__webchat-channel__reply";
|
|
7651
|
+
var CHANNEL_REPLY_TOOL = "mcp__channel__reply";
|
|
7358
7652
|
var CLI_MARKER_PREFIXES2 = ["<local-command-", "<command-name>", "<command-message>"];
|
|
7359
|
-
var CHANNEL_WRAPPER = /^<channel\b[^>]
|
|
7360
|
-
|
|
7653
|
+
var CHANNEL_WRAPPER = /^<channel\b([^>]*)>\n?([\s\S]*?)\n?<\/channel>$/;
|
|
7654
|
+
var SOURCE_ATTR = /\bsource="([^"]+)"/;
|
|
7655
|
+
var SOURCE_MAP = {
|
|
7656
|
+
whatsapp: "whatsapp",
|
|
7657
|
+
"whatsapp-channel": "whatsapp",
|
|
7658
|
+
webchat: "webchat",
|
|
7659
|
+
"webchat-channel": "webchat"
|
|
7660
|
+
};
|
|
7661
|
+
function unwrapChannel(text) {
|
|
7662
|
+
const m = text.match(CHANNEL_WRAPPER);
|
|
7663
|
+
if (!m) return { text };
|
|
7664
|
+
let source = SOURCE_MAP[m[1].match(SOURCE_ATTR)?.[1] ?? ""];
|
|
7665
|
+
let inner = m[2];
|
|
7666
|
+
const im = inner.match(CHANNEL_WRAPPER);
|
|
7667
|
+
if (im) {
|
|
7668
|
+
const innerSource = SOURCE_MAP[im[1].match(SOURCE_ATTR)?.[1] ?? ""];
|
|
7669
|
+
if (innerSource) {
|
|
7670
|
+
source = innerSource;
|
|
7671
|
+
inner = im[2];
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
return source ? { text: inner, source } : { text: inner };
|
|
7675
|
+
}
|
|
7676
|
+
function asString(content) {
|
|
7361
7677
|
return typeof content === "string" ? content : null;
|
|
7362
7678
|
}
|
|
7363
7679
|
function tsOf(row) {
|
|
@@ -7371,7 +7687,7 @@ function assistantTurns(content, ts) {
|
|
|
7371
7687
|
const b = block;
|
|
7372
7688
|
if (b.type === "text" && typeof b.text === "string") {
|
|
7373
7689
|
out.push({ kind: "agent-text", text: b.text, ts });
|
|
7374
|
-
} else if (b.type === "tool_use" && (b.name === WA_REPLY_TOOL || b.name === WEBCHAT_REPLY_TOOL)) {
|
|
7690
|
+
} else if (b.type === "tool_use" && (b.name === WA_REPLY_TOOL || b.name === WEBCHAT_REPLY_TOOL || b.name === CHANNEL_REPLY_TOOL)) {
|
|
7375
7691
|
const text = b.input?.text;
|
|
7376
7692
|
out.push({ kind: "agent-reply", text: typeof text === "string" ? text : "", ts });
|
|
7377
7693
|
} else if (b.type === "tool_use" && b.name === WA_REPLY_DOCUMENT_TOOL) {
|
|
@@ -7427,9 +7743,8 @@ function parseTranscript(lines) {
|
|
|
7427
7743
|
const att = row.attachment;
|
|
7428
7744
|
const isChannelQueued = att?.type === "queued_command" && att.isMeta === true && typeof att.origin === "object" && att.origin !== null && att.origin.kind === "channel" && typeof att.prompt === "string";
|
|
7429
7745
|
if (isChannelQueued) {
|
|
7430
|
-
const
|
|
7431
|
-
|
|
7432
|
-
out.push({ kind: "operator-inbound", text: m ? m[1] : prompt, ts });
|
|
7746
|
+
const u = unwrapChannel(att.prompt);
|
|
7747
|
+
out.push({ kind: "operator-inbound", text: u.text, ts, ...u.source ? { source: u.source } : {} });
|
|
7433
7748
|
}
|
|
7434
7749
|
continue;
|
|
7435
7750
|
}
|
|
@@ -7443,8 +7758,8 @@ function parseTranscript(lines) {
|
|
|
7443
7758
|
if (CLI_MARKER_PREFIXES2.some((p) => text.startsWith(p))) continue;
|
|
7444
7759
|
const isChannel = row.isMeta === true && typeof row.origin === "object" && row.origin !== null && row.origin.kind === "channel";
|
|
7445
7760
|
if (isChannel) {
|
|
7446
|
-
const
|
|
7447
|
-
out.push({ kind: "operator-inbound", text:
|
|
7761
|
+
const u = unwrapChannel(text);
|
|
7762
|
+
out.push({ kind: "operator-inbound", text: u.text, ts, ...u.source ? { source: u.source } : {} });
|
|
7448
7763
|
} else {
|
|
7449
7764
|
out.push({ kind: "operator-typed", text, ts });
|
|
7450
7765
|
}
|
|
@@ -7487,7 +7802,7 @@ app5.get("/conversations", requireAdminSession, async (c) => {
|
|
|
7487
7802
|
if (meta.role !== "admin" || meta.channel !== "whatsapp" && meta.channel !== "telegram") continue;
|
|
7488
7803
|
let body = "";
|
|
7489
7804
|
try {
|
|
7490
|
-
body =
|
|
7805
|
+
body = readFileSync11(path2, "utf8");
|
|
7491
7806
|
} catch {
|
|
7492
7807
|
}
|
|
7493
7808
|
const { title } = resolveTitle(body, sessionId, userTitles);
|
|
@@ -7570,11 +7885,11 @@ app5.get("/stream", requireAdminSession, (c) => {
|
|
|
7570
7885
|
const sessionId = c.req.query("sessionId") ?? "";
|
|
7571
7886
|
const projectDir = c.req.query("projectDir") ?? "";
|
|
7572
7887
|
const cfg = claudeConfigDir();
|
|
7573
|
-
const projectsRoot = cfg ?
|
|
7888
|
+
const projectsRoot = cfg ? resolve11(join10(cfg, "projects")) : null;
|
|
7574
7889
|
if (!cfg || !projectsRoot || !SESSION_ID_RE2.test(sessionId)) {
|
|
7575
7890
|
return c.json({ error: "bad session reference" }, 400);
|
|
7576
7891
|
}
|
|
7577
|
-
const jsonlPath =
|
|
7892
|
+
const jsonlPath = resolve11(join10(projectDir, `${sessionId}.jsonl`));
|
|
7578
7893
|
if (!jsonlPath.startsWith(projectsRoot + sep2)) {
|
|
7579
7894
|
return c.json({ error: "bad session reference" }, 400);
|
|
7580
7895
|
}
|
|
@@ -7661,7 +7976,7 @@ var DIRECTIVE_NAME_RE = /^\d+-\d+\.txt$/;
|
|
|
7661
7976
|
function directiveStoreDir(sessionId) {
|
|
7662
7977
|
const account = resolveAccount();
|
|
7663
7978
|
if (!account) return null;
|
|
7664
|
-
return
|
|
7979
|
+
return resolve11(account.accountDir, "logs", "prompt-optimiser-directives", sessionId);
|
|
7665
7980
|
}
|
|
7666
7981
|
app5.get("/directives", requireAdminSession, (c) => {
|
|
7667
7982
|
const sessionId = c.req.query("sessionId") ?? "";
|
|
@@ -7695,11 +8010,11 @@ app5.get("/directive", requireAdminSession, (c) => {
|
|
|
7695
8010
|
}
|
|
7696
8011
|
const dir = directiveStoreDir(sessionId);
|
|
7697
8012
|
if (!dir) return c.json({ error: "no account" }, 404);
|
|
7698
|
-
const path2 =
|
|
8013
|
+
const path2 = resolve11(join10(dir, name));
|
|
7699
8014
|
if (!path2.startsWith(dir + sep2)) return c.json({ error: "bad reference" }, 400);
|
|
7700
8015
|
let body;
|
|
7701
8016
|
try {
|
|
7702
|
-
body =
|
|
8017
|
+
body = readFileSync11(path2, "utf8");
|
|
7703
8018
|
} catch {
|
|
7704
8019
|
return c.json({ error: "not found" }, 404);
|
|
7705
8020
|
}
|
|
@@ -7710,7 +8025,7 @@ var whatsapp_reader_default = app5;
|
|
|
7710
8025
|
// server/routes/webchat.ts
|
|
7711
8026
|
import { basename as basename3, dirname as dirname2 } from "path";
|
|
7712
8027
|
import { join as join11 } from "path";
|
|
7713
|
-
import { existsSync as existsSync8, readdirSync as readdirSync7, readFileSync as
|
|
8028
|
+
import { existsSync as existsSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, renameSync, writeFileSync as writeFileSync5 } from "fs";
|
|
7714
8029
|
var WEBCHAT_ADMIN_KEY = "webchat-admin";
|
|
7715
8030
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7716
8031
|
function locateSession(sessionId) {
|
|
@@ -7823,8 +8138,8 @@ async function reapplyLeversViaManager() {
|
|
|
7823
8138
|
}
|
|
7824
8139
|
}
|
|
7825
8140
|
function createWebchatRoutes(deps) {
|
|
7826
|
-
const
|
|
7827
|
-
|
|
8141
|
+
const app50 = new Hono();
|
|
8142
|
+
app50.post("/send", requireAdminSession, async (c) => {
|
|
7828
8143
|
let accountId;
|
|
7829
8144
|
try {
|
|
7830
8145
|
accountId = resolvePlatformAccountId();
|
|
@@ -7955,7 +8270,7 @@ ${note}` : note;
|
|
|
7955
8270
|
}
|
|
7956
8271
|
return c.json({ ok: true });
|
|
7957
8272
|
});
|
|
7958
|
-
|
|
8273
|
+
app50.post("/settings", requireAdminSession, async (c) => {
|
|
7959
8274
|
const body = await c.req.json().catch(() => null);
|
|
7960
8275
|
const op = body?.op;
|
|
7961
8276
|
const value = body?.value;
|
|
@@ -7979,7 +8294,7 @@ ${note}` : note;
|
|
|
7979
8294
|
}
|
|
7980
8295
|
const accountJsonPath = join11(account.accountDir, "account.json");
|
|
7981
8296
|
try {
|
|
7982
|
-
const parsed = JSON.parse(
|
|
8297
|
+
const parsed = JSON.parse(readFileSync12(accountJsonPath, "utf8"));
|
|
7983
8298
|
if (op === "model") parsed.adminModel = value;
|
|
7984
8299
|
else if (op === "effort") parsed.effort = value;
|
|
7985
8300
|
else parsed.adminPermissionMode = value;
|
|
@@ -7995,7 +8310,7 @@ ${note}` : note;
|
|
|
7995
8310
|
console.log(`[webchat:settings] op=${op} outcome=accepted value=${value} settingsApplied=${settingsApplied}`);
|
|
7996
8311
|
return c.json({ ok: true, settingsApplied });
|
|
7997
8312
|
});
|
|
7998
|
-
|
|
8313
|
+
app50.get("/session", requireAdminSession, async (c) => {
|
|
7999
8314
|
let accountId;
|
|
8000
8315
|
try {
|
|
8001
8316
|
accountId = resolvePlatformAccountId();
|
|
@@ -8028,14 +8343,14 @@ ${note}` : note;
|
|
|
8028
8343
|
const indicators = await fetchComposerIndicators(sessionId);
|
|
8029
8344
|
return c.json({ sessionId, projectDir, ...indicators, ...readLevers() });
|
|
8030
8345
|
});
|
|
8031
|
-
return
|
|
8346
|
+
return app50;
|
|
8032
8347
|
}
|
|
8033
8348
|
|
|
8034
8349
|
// server/routes/webchat-greeting.ts
|
|
8035
|
-
import { resolve as
|
|
8350
|
+
import { resolve as resolve12 } from "path";
|
|
8036
8351
|
|
|
8037
8352
|
// app/lib/claude-agent/specialist-roster.ts
|
|
8038
|
-
import { existsSync as existsSync9, readFileSync as
|
|
8353
|
+
import { existsSync as existsSync9, readFileSync as readFileSync13, readdirSync as readdirSync8 } from "fs";
|
|
8039
8354
|
import { join as join12 } from "path";
|
|
8040
8355
|
function field(fm, name) {
|
|
8041
8356
|
const m = fm.match(new RegExp(`^${name}:\\s*(.*?)\\r?$`, "m"));
|
|
@@ -8055,7 +8370,7 @@ function readSpecialistRoster(specialistsDir) {
|
|
|
8055
8370
|
if (!file.endsWith(".md")) continue;
|
|
8056
8371
|
let raw;
|
|
8057
8372
|
try {
|
|
8058
|
-
raw =
|
|
8373
|
+
raw = readFileSync13(join12(specialistsDir, file), "utf-8");
|
|
8059
8374
|
} catch (err) {
|
|
8060
8375
|
skipped.push({ file, reason: err instanceof Error ? err.message : String(err) });
|
|
8061
8376
|
continue;
|
|
@@ -8137,7 +8452,7 @@ app6.get("/", requireAdminSession, async (c) => {
|
|
|
8137
8452
|
let specialists = [];
|
|
8138
8453
|
try {
|
|
8139
8454
|
const roster = readSpecialistRoster(
|
|
8140
|
-
|
|
8455
|
+
resolve12(ACCOUNTS_DIR, accountId, "specialists", "agents")
|
|
8141
8456
|
);
|
|
8142
8457
|
specialists = roster.specialists;
|
|
8143
8458
|
for (const s of roster.skipped) {
|
|
@@ -8172,8 +8487,8 @@ var webchat_greeting_default = app6;
|
|
|
8172
8487
|
|
|
8173
8488
|
// server/routes/telegram.ts
|
|
8174
8489
|
import { homedir } from "os";
|
|
8175
|
-
import { resolve as
|
|
8176
|
-
import { existsSync as existsSync10, readFileSync as
|
|
8490
|
+
import { resolve as resolve13, join as join13 } from "path";
|
|
8491
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
|
|
8177
8492
|
|
|
8178
8493
|
// app/lib/telegram/access-control.ts
|
|
8179
8494
|
function checkTelegramAccess(params) {
|
|
@@ -8223,11 +8538,11 @@ function routeTelegramUpdate(input) {
|
|
|
8223
8538
|
var TAG22 = "[telegram-inbound]";
|
|
8224
8539
|
var DISPATCH_TIMEOUT_MS = 12e4;
|
|
8225
8540
|
function configDirName() {
|
|
8226
|
-
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ??
|
|
8541
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "..");
|
|
8227
8542
|
const brandPath = join13(platformRoot2, "config", "brand.json");
|
|
8228
8543
|
if (existsSync10(brandPath)) {
|
|
8229
8544
|
try {
|
|
8230
|
-
return JSON.parse(
|
|
8545
|
+
return JSON.parse(readFileSync14(brandPath, "utf-8")).configDir ?? ".maxy";
|
|
8231
8546
|
} catch {
|
|
8232
8547
|
}
|
|
8233
8548
|
}
|
|
@@ -8235,7 +8550,7 @@ function configDirName() {
|
|
|
8235
8550
|
}
|
|
8236
8551
|
function secretPath(botType) {
|
|
8237
8552
|
const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
|
|
8238
|
-
return
|
|
8553
|
+
return resolve13(homedir(), configDirName(), filename);
|
|
8239
8554
|
}
|
|
8240
8555
|
async function sendTelegram(botToken, chatId, text) {
|
|
8241
8556
|
try {
|
|
@@ -8263,7 +8578,7 @@ app7.post("/", async (c) => {
|
|
|
8263
8578
|
console.error(`${TAG22} op=reject reason=no-secret-file botType=${botType}`);
|
|
8264
8579
|
return c.json({ ok: false }, 401);
|
|
8265
8580
|
}
|
|
8266
|
-
const expected =
|
|
8581
|
+
const expected = readFileSync14(sp, "utf-8").trim();
|
|
8267
8582
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
8268
8583
|
if (got !== expected) {
|
|
8269
8584
|
console.error(`${TAG22} op=reject reason=bad-secret botType=${botType}`);
|
|
@@ -8326,11 +8641,11 @@ var telegram_default = app7;
|
|
|
8326
8641
|
|
|
8327
8642
|
// server/routes/onboarding.ts
|
|
8328
8643
|
import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync2 } from "child_process";
|
|
8329
|
-
import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync7, writeSync, existsSync as existsSync12, readFileSync as
|
|
8330
|
-
import { createHash as createHash3, randomUUID as
|
|
8644
|
+
import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync7, writeSync, existsSync as existsSync12, readFileSync as readFileSync16, unlinkSync } from "fs";
|
|
8645
|
+
import { createHash as createHash3, randomUUID as randomUUID8 } from "crypto";
|
|
8331
8646
|
|
|
8332
8647
|
// ../lib/admins-write/src/index.ts
|
|
8333
|
-
import { existsSync as existsSync11, readFileSync as
|
|
8648
|
+
import { existsSync as existsSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync6, renameSync as renameSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync9, statSync as statSync5 } from "fs";
|
|
8334
8649
|
import { dirname as dirname3, join as join14 } from "path";
|
|
8335
8650
|
function logLine(input, result) {
|
|
8336
8651
|
const userIdShort = input.userId.slice(0, 8);
|
|
@@ -8349,7 +8664,7 @@ function writeAdminEntry(input) {
|
|
|
8349
8664
|
try {
|
|
8350
8665
|
let users = [];
|
|
8351
8666
|
if (existsSync11(input.usersFile)) {
|
|
8352
|
-
const raw =
|
|
8667
|
+
const raw = readFileSync15(input.usersFile, "utf-8").trim();
|
|
8353
8668
|
if (raw) {
|
|
8354
8669
|
const parsed = JSON.parse(raw);
|
|
8355
8670
|
if (!Array.isArray(parsed)) {
|
|
@@ -8377,7 +8692,7 @@ function writeAdminEntry(input) {
|
|
|
8377
8692
|
if (!existsSync11(accountJsonPath)) {
|
|
8378
8693
|
throw new Error(`account.json not found at ${accountJsonPath}`);
|
|
8379
8694
|
}
|
|
8380
|
-
const config = JSON.parse(
|
|
8695
|
+
const config = JSON.parse(readFileSync15(accountJsonPath, "utf-8"));
|
|
8381
8696
|
const admins = config.admins ?? [];
|
|
8382
8697
|
if (admins.some((a) => a.userId === input.userId)) {
|
|
8383
8698
|
result.accountJsonResult = "noop";
|
|
@@ -8403,7 +8718,7 @@ function checkAdminAuthInvariant(input) {
|
|
|
8403
8718
|
const usersUserIds = /* @__PURE__ */ new Set();
|
|
8404
8719
|
if (existsSync11(input.usersFile)) {
|
|
8405
8720
|
try {
|
|
8406
|
-
const raw =
|
|
8721
|
+
const raw = readFileSync15(input.usersFile, "utf-8").trim();
|
|
8407
8722
|
if (raw) {
|
|
8408
8723
|
const users = JSON.parse(raw);
|
|
8409
8724
|
for (const u of users) {
|
|
@@ -8438,7 +8753,7 @@ function checkAdminAuthInvariant(input) {
|
|
|
8438
8753
|
if (!existsSync11(accountJsonPath)) continue;
|
|
8439
8754
|
let admins = [];
|
|
8440
8755
|
try {
|
|
8441
|
-
const config = JSON.parse(
|
|
8756
|
+
const config = JSON.parse(readFileSync15(accountJsonPath, "utf-8"));
|
|
8442
8757
|
admins = config.admins ?? [];
|
|
8443
8758
|
} catch (err) {
|
|
8444
8759
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -8479,7 +8794,7 @@ function hashPin2(pin) {
|
|
|
8479
8794
|
}
|
|
8480
8795
|
function readUsersFile2() {
|
|
8481
8796
|
if (!existsSync12(USERS_FILE)) return null;
|
|
8482
|
-
const raw =
|
|
8797
|
+
const raw = readFileSync16(USERS_FILE, "utf-8").trim();
|
|
8483
8798
|
if (!raw) return [];
|
|
8484
8799
|
return JSON.parse(raw);
|
|
8485
8800
|
}
|
|
@@ -8610,7 +8925,7 @@ app8.post("/set-pin", async (c) => {
|
|
|
8610
8925
|
const hash = hashPin2(body.pin);
|
|
8611
8926
|
const account = resolveAccount();
|
|
8612
8927
|
const existingOwnerUserId = account?.config.admins?.find((a) => a.role === "owner")?.userId;
|
|
8613
|
-
const userId = existingOwnerUserId ??
|
|
8928
|
+
const userId = existingOwnerUserId ?? randomUUID8();
|
|
8614
8929
|
if (existingOwnerUserId) {
|
|
8615
8930
|
console.log(`[set-pin] reusing existing owner userId=${userId.slice(0, 8)}\u2026 (change-PIN preserves identity)`);
|
|
8616
8931
|
} else {
|
|
@@ -8641,7 +8956,7 @@ app8.post("/set-pin", async (c) => {
|
|
|
8641
8956
|
let installOwner = null;
|
|
8642
8957
|
if (existsSync12(INSTALL_OWNER_FILE)) {
|
|
8643
8958
|
try {
|
|
8644
|
-
const raw =
|
|
8959
|
+
const raw = readFileSync16(INSTALL_OWNER_FILE, "utf-8").trim();
|
|
8645
8960
|
if (raw.length > 0) installOwner = raw;
|
|
8646
8961
|
} catch (err) {
|
|
8647
8962
|
console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -8881,7 +9196,7 @@ app9.post("/", async (c) => {
|
|
|
8881
9196
|
var client_error_default = app9;
|
|
8882
9197
|
|
|
8883
9198
|
// server/routes/admin/session.ts
|
|
8884
|
-
import { randomUUID as
|
|
9199
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
8885
9200
|
async function resolveUserIdentity(accountId, userId) {
|
|
8886
9201
|
const result = await loadAdminUserName(accountId, userId);
|
|
8887
9202
|
if (result.source === "neo4j") {
|
|
@@ -8895,7 +9210,7 @@ async function resolveUserIdentity(accountId, userId) {
|
|
|
8895
9210
|
async function createAdminSession(accountId, thinkingView, userId, userName, role, avatar) {
|
|
8896
9211
|
const account = resolveAccount();
|
|
8897
9212
|
const effectiveThinkingView = thinkingView ?? account?.config.thinkingView ?? "default";
|
|
8898
|
-
const signedSessionToken =
|
|
9213
|
+
const signedSessionToken = randomUUID9();
|
|
8899
9214
|
const cacheKey = fingerprintSessionKey(signedSessionToken);
|
|
8900
9215
|
registerSession(cacheKey, "admin", accountId, void 0, userId, userName, role);
|
|
8901
9216
|
if (userId) setWantsPriorConversation(cacheKey);
|
|
@@ -9049,8 +9364,8 @@ app10.post("/", async (c) => {
|
|
|
9049
9364
|
var session_default = app10;
|
|
9050
9365
|
|
|
9051
9366
|
// server/routes/admin/logs.ts
|
|
9052
|
-
import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as
|
|
9053
|
-
import { resolve as
|
|
9367
|
+
import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
|
|
9368
|
+
import { resolve as resolve14, basename as basename4 } from "path";
|
|
9054
9369
|
|
|
9055
9370
|
// app/lib/logs-read-resolve.ts
|
|
9056
9371
|
import { existsSync as existsSync14 } from "fs";
|
|
@@ -9077,7 +9392,7 @@ app11.get("/", async (c) => {
|
|
|
9077
9392
|
const cacheKeyParam = c.req.query("cacheKey");
|
|
9078
9393
|
const download = c.req.query("download") === "1";
|
|
9079
9394
|
const account = resolveAccount();
|
|
9080
|
-
const accountLogDir = account ?
|
|
9395
|
+
const accountLogDir = account ? resolve14(account.accountDir, "logs") : null;
|
|
9081
9396
|
const logDirs = [];
|
|
9082
9397
|
if (accountLogDir) logDirs.push(accountLogDir);
|
|
9083
9398
|
logDirs.push(LOG_DIR);
|
|
@@ -9085,10 +9400,10 @@ app11.get("/", async (c) => {
|
|
|
9085
9400
|
const safe = basename4(fileParam);
|
|
9086
9401
|
const searched = [];
|
|
9087
9402
|
for (const dir of logDirs) {
|
|
9088
|
-
const filePath =
|
|
9403
|
+
const filePath = resolve14(dir, safe);
|
|
9089
9404
|
searched.push(filePath);
|
|
9090
9405
|
try {
|
|
9091
|
-
const buffer =
|
|
9406
|
+
const buffer = readFileSync17(filePath);
|
|
9092
9407
|
const onDiskBytes = statSync7(filePath).size;
|
|
9093
9408
|
const headers = {
|
|
9094
9409
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -9153,7 +9468,7 @@ app11.get("/", async (c) => {
|
|
|
9153
9468
|
console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
|
|
9154
9469
|
try {
|
|
9155
9470
|
const filename = basename4(hit.path);
|
|
9156
|
-
const buffer =
|
|
9471
|
+
const buffer = readFileSync17(hit.path);
|
|
9157
9472
|
const onDiskBytes = statSync7(hit.path).size;
|
|
9158
9473
|
const headers = {
|
|
9159
9474
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -9197,10 +9512,10 @@ app11.get("/", async (c) => {
|
|
|
9197
9512
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
9198
9513
|
continue;
|
|
9199
9514
|
}
|
|
9200
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(
|
|
9515
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(resolve14(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
9201
9516
|
seen.add(name);
|
|
9202
9517
|
try {
|
|
9203
|
-
const content =
|
|
9518
|
+
const content = readFileSync17(resolve14(dir, name));
|
|
9204
9519
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
9205
9520
|
logs[name] = tail.trim() || "(empty)";
|
|
9206
9521
|
} catch (err) {
|
|
@@ -9240,7 +9555,7 @@ var claude_info_default = app12;
|
|
|
9240
9555
|
// server/routes/admin/attachment.ts
|
|
9241
9556
|
import { readFile as readFile2, readdir } from "fs/promises";
|
|
9242
9557
|
import { existsSync as existsSync16 } from "fs";
|
|
9243
|
-
import { resolve as
|
|
9558
|
+
import { resolve as resolve15 } from "path";
|
|
9244
9559
|
var app13 = new Hono();
|
|
9245
9560
|
app13.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
9246
9561
|
const attachmentId = c.req.param("attachmentId");
|
|
@@ -9252,11 +9567,11 @@ app13.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
9252
9567
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
9253
9568
|
return new Response("Not found", { status: 404 });
|
|
9254
9569
|
}
|
|
9255
|
-
const dir =
|
|
9570
|
+
const dir = resolve15(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
9256
9571
|
if (!existsSync16(dir)) {
|
|
9257
9572
|
return new Response("Not found", { status: 404 });
|
|
9258
9573
|
}
|
|
9259
|
-
const metaPath =
|
|
9574
|
+
const metaPath = resolve15(dir, `${attachmentId}.meta.json`);
|
|
9260
9575
|
if (!existsSync16(metaPath)) {
|
|
9261
9576
|
return new Response("Not found", { status: 404 });
|
|
9262
9577
|
}
|
|
@@ -9271,7 +9586,7 @@ app13.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
9271
9586
|
if (!dataFile) {
|
|
9272
9587
|
return new Response("Not found", { status: 404 });
|
|
9273
9588
|
}
|
|
9274
|
-
const filePath =
|
|
9589
|
+
const filePath = resolve15(dir, dataFile);
|
|
9275
9590
|
const buffer = await readFile2(filePath);
|
|
9276
9591
|
return new Response(new Uint8Array(buffer), {
|
|
9277
9592
|
headers: {
|
|
@@ -9284,13 +9599,13 @@ app13.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
9284
9599
|
var attachment_default = app13;
|
|
9285
9600
|
|
|
9286
9601
|
// server/routes/admin/agents.ts
|
|
9287
|
-
import { resolve as
|
|
9288
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
9602
|
+
import { resolve as resolve16 } from "path";
|
|
9603
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync18, existsSync as existsSync17, rmSync } from "fs";
|
|
9289
9604
|
var app14 = new Hono();
|
|
9290
9605
|
app14.get("/", (c) => {
|
|
9291
9606
|
const account = resolveAccount();
|
|
9292
9607
|
if (!account) return c.json({ agents: [] });
|
|
9293
|
-
const agentsDir =
|
|
9608
|
+
const agentsDir = resolve16(account.accountDir, "agents");
|
|
9294
9609
|
if (!existsSync17(agentsDir)) return c.json({ agents: [] });
|
|
9295
9610
|
const agents = [];
|
|
9296
9611
|
try {
|
|
@@ -9298,10 +9613,10 @@ app14.get("/", (c) => {
|
|
|
9298
9613
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
9299
9614
|
if (!entry.isDirectory()) continue;
|
|
9300
9615
|
if (entry.name === "admin") continue;
|
|
9301
|
-
const configPath2 =
|
|
9616
|
+
const configPath2 = resolve16(agentsDir, entry.name, "config.json");
|
|
9302
9617
|
if (!existsSync17(configPath2)) continue;
|
|
9303
9618
|
try {
|
|
9304
|
-
const config = JSON.parse(
|
|
9619
|
+
const config = JSON.parse(readFileSync18(configPath2, "utf-8"));
|
|
9305
9620
|
agents.push({
|
|
9306
9621
|
slug: entry.name,
|
|
9307
9622
|
displayName: config.displayName ?? entry.name,
|
|
@@ -9327,7 +9642,7 @@ app14.delete("/:slug", async (c) => {
|
|
|
9327
9642
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
9328
9643
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
9329
9644
|
}
|
|
9330
|
-
const agentDir =
|
|
9645
|
+
const agentDir = resolve16(account.accountDir, "agents", slug);
|
|
9331
9646
|
if (!existsSync17(agentDir)) {
|
|
9332
9647
|
return c.json({ error: "Agent not found" }, 404);
|
|
9333
9648
|
}
|
|
@@ -9357,7 +9672,7 @@ app14.post("/:slug/project", async (c) => {
|
|
|
9357
9672
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
9358
9673
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
9359
9674
|
}
|
|
9360
|
-
const agentDir =
|
|
9675
|
+
const agentDir = resolve16(account.accountDir, "agents", slug);
|
|
9361
9676
|
if (!existsSync17(agentDir)) {
|
|
9362
9677
|
return c.json({ error: "Agent not found on disk" }, 404);
|
|
9363
9678
|
}
|
|
@@ -10060,8 +10375,8 @@ app15.put("/:id/label", requireAdminSession, async (c) => {
|
|
|
10060
10375
|
var sessions_default = app15;
|
|
10061
10376
|
|
|
10062
10377
|
// app/lib/claude-agent/spawn-context.ts
|
|
10063
|
-
import { existsSync as existsSync19, readFileSync as
|
|
10064
|
-
import { dirname as dirname4, resolve as
|
|
10378
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync8 } from "fs";
|
|
10379
|
+
import { dirname as dirname4, resolve as resolve17, join as join17 } from "path";
|
|
10065
10380
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
10066
10381
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
10067
10382
|
try {
|
|
@@ -10097,7 +10412,7 @@ function extractPluginToolDescriptions(pluginDir) {
|
|
|
10097
10412
|
for (const path2 of candidates) {
|
|
10098
10413
|
if (!existsSync19(path2)) continue;
|
|
10099
10414
|
try {
|
|
10100
|
-
raw =
|
|
10415
|
+
raw = readFileSync19(path2, "utf-8");
|
|
10101
10416
|
break;
|
|
10102
10417
|
} catch {
|
|
10103
10418
|
}
|
|
@@ -10146,7 +10461,7 @@ function parseSkillPathsFromFrontmatter(fm) {
|
|
|
10146
10461
|
}
|
|
10147
10462
|
function listPluginDirs() {
|
|
10148
10463
|
const out = /* @__PURE__ */ new Map();
|
|
10149
|
-
const platformPlugins =
|
|
10464
|
+
const platformPlugins = resolve17(PLATFORM_ROOT, "plugins");
|
|
10150
10465
|
if (existsSync19(platformPlugins)) {
|
|
10151
10466
|
for (const name of readdirSync12(platformPlugins)) {
|
|
10152
10467
|
const dir = join17(platformPlugins, name);
|
|
@@ -10156,7 +10471,7 @@ function listPluginDirs() {
|
|
|
10156
10471
|
}
|
|
10157
10472
|
}
|
|
10158
10473
|
}
|
|
10159
|
-
const premiumRoot =
|
|
10474
|
+
const premiumRoot = resolve17(dirname4(PLATFORM_ROOT), "premium-plugins");
|
|
10160
10475
|
if (existsSync19(premiumRoot)) {
|
|
10161
10476
|
for (const bundle of readdirSync12(premiumRoot)) {
|
|
10162
10477
|
const bundlePlugins = join17(premiumRoot, bundle, "plugins");
|
|
@@ -10176,10 +10491,10 @@ function listPluginDirs() {
|
|
|
10176
10491
|
return out;
|
|
10177
10492
|
}
|
|
10178
10493
|
function readEnabledPlugins(accountId) {
|
|
10179
|
-
const accountFile =
|
|
10494
|
+
const accountFile = resolve17(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
10180
10495
|
if (!existsSync19(accountFile)) return /* @__PURE__ */ new Set();
|
|
10181
10496
|
try {
|
|
10182
|
-
const parsed = JSON.parse(
|
|
10497
|
+
const parsed = JSON.parse(readFileSync19(accountFile, "utf-8"));
|
|
10183
10498
|
return new Set(
|
|
10184
10499
|
Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
|
|
10185
10500
|
);
|
|
@@ -10191,7 +10506,7 @@ function readFrontmatter(path2) {
|
|
|
10191
10506
|
if (!existsSync19(path2)) return null;
|
|
10192
10507
|
let raw;
|
|
10193
10508
|
try {
|
|
10194
|
-
raw =
|
|
10509
|
+
raw = readFileSync19(path2, "utf-8");
|
|
10195
10510
|
} catch {
|
|
10196
10511
|
return null;
|
|
10197
10512
|
}
|
|
@@ -10249,7 +10564,7 @@ ${skillFm}
|
|
|
10249
10564
|
return out;
|
|
10250
10565
|
}
|
|
10251
10566
|
function computeSpecialistDomains(accountId) {
|
|
10252
|
-
const specialistsDir =
|
|
10567
|
+
const specialistsDir = resolve17(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
|
|
10253
10568
|
if (!existsSync19(specialistsDir)) return [];
|
|
10254
10569
|
let entries;
|
|
10255
10570
|
try {
|
|
@@ -10295,11 +10610,11 @@ ${fm}
|
|
|
10295
10610
|
return out;
|
|
10296
10611
|
}
|
|
10297
10612
|
function computeDormantPlugins(accountId) {
|
|
10298
|
-
const accountFile =
|
|
10613
|
+
const accountFile = resolve17(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
10299
10614
|
if (!existsSync19(accountFile)) return [];
|
|
10300
10615
|
let enabled;
|
|
10301
10616
|
try {
|
|
10302
|
-
const raw =
|
|
10617
|
+
const raw = readFileSync19(accountFile, "utf-8");
|
|
10303
10618
|
const parsed = JSON.parse(raw);
|
|
10304
10619
|
enabled = new Set(
|
|
10305
10620
|
Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
|
|
@@ -10310,7 +10625,7 @@ function computeDormantPlugins(accountId) {
|
|
|
10310
10625
|
);
|
|
10311
10626
|
return [];
|
|
10312
10627
|
}
|
|
10313
|
-
const pluginsDir =
|
|
10628
|
+
const pluginsDir = resolve17(PLATFORM_ROOT, "plugins");
|
|
10314
10629
|
if (!existsSync19(pluginsDir)) return [];
|
|
10315
10630
|
let entries;
|
|
10316
10631
|
try {
|
|
@@ -10321,11 +10636,11 @@ function computeDormantPlugins(accountId) {
|
|
|
10321
10636
|
const out = [];
|
|
10322
10637
|
for (const name of entries) {
|
|
10323
10638
|
if (enabled.has(name)) continue;
|
|
10324
|
-
const manifestPath =
|
|
10639
|
+
const manifestPath = resolve17(pluginsDir, name, "PLUGIN.md");
|
|
10325
10640
|
if (!existsSync19(manifestPath)) continue;
|
|
10326
10641
|
let manifest;
|
|
10327
10642
|
try {
|
|
10328
|
-
manifest =
|
|
10643
|
+
manifest = readFileSync19(manifestPath, "utf-8");
|
|
10329
10644
|
} catch {
|
|
10330
10645
|
continue;
|
|
10331
10646
|
}
|
|
@@ -10339,13 +10654,13 @@ function computeDormantPlugins(accountId) {
|
|
|
10339
10654
|
}
|
|
10340
10655
|
|
|
10341
10656
|
// server/routes/admin/claude-sessions.ts
|
|
10342
|
-
import { existsSync as existsSync20, readFileSync as
|
|
10343
|
-
import { resolve as
|
|
10657
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
|
|
10658
|
+
import { resolve as resolve18 } from "path";
|
|
10344
10659
|
function readTunnelState(brandConfigDir) {
|
|
10345
10660
|
const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
|
|
10346
10661
|
if (!existsSync20(statePath)) return null;
|
|
10347
10662
|
try {
|
|
10348
|
-
const parsed = JSON.parse(
|
|
10663
|
+
const parsed = JSON.parse(readFileSync20(statePath, "utf-8"));
|
|
10349
10664
|
const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
|
|
10350
10665
|
const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
|
|
10351
10666
|
const domain = typeof parsed.domain === "string" ? parsed.domain : null;
|
|
@@ -10356,10 +10671,10 @@ function readTunnelState(brandConfigDir) {
|
|
|
10356
10671
|
}
|
|
10357
10672
|
}
|
|
10358
10673
|
function resolveTunnelUrl() {
|
|
10359
|
-
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ??
|
|
10674
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve18(process.cwd(), "..");
|
|
10360
10675
|
let brand;
|
|
10361
10676
|
try {
|
|
10362
|
-
brand = JSON.parse(
|
|
10677
|
+
brand = JSON.parse(readFileSync20(resolve18(platformRoot2, "config", "brand.json"), "utf-8"));
|
|
10363
10678
|
} catch {
|
|
10364
10679
|
return null;
|
|
10365
10680
|
}
|
|
@@ -10568,18 +10883,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
10568
10883
|
]);
|
|
10569
10884
|
var app18 = new Hono();
|
|
10570
10885
|
app18.post("/", async (c) => {
|
|
10571
|
-
const
|
|
10886
|
+
const TAG33 = "[admin:events]";
|
|
10572
10887
|
let body;
|
|
10573
10888
|
try {
|
|
10574
10889
|
body = await c.req.json();
|
|
10575
10890
|
} catch (err) {
|
|
10576
10891
|
const detail = err instanceof Error ? err.message : String(err);
|
|
10577
|
-
console.error(`${
|
|
10892
|
+
console.error(`${TAG33} reject reason=body-not-json detail=${detail}`);
|
|
10578
10893
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
10579
10894
|
}
|
|
10580
10895
|
const event = typeof body.event === "string" ? body.event : "";
|
|
10581
10896
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
10582
|
-
console.error(`${
|
|
10897
|
+
console.error(`${TAG33} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
10583
10898
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
10584
10899
|
}
|
|
10585
10900
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -10604,15 +10919,15 @@ var events_default = app18;
|
|
|
10604
10919
|
import { createReadStream as createReadStream2 } from "fs";
|
|
10605
10920
|
import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
10606
10921
|
import { realpathSync as realpathSync4 } from "fs";
|
|
10607
|
-
import { basename as basename6, dirname as dirname5, join as join19, resolve as
|
|
10922
|
+
import { basename as basename6, dirname as dirname5, join as join19, resolve as resolve21, sep as sep5 } from "path";
|
|
10608
10923
|
import { Readable as Readable2 } from "stream";
|
|
10609
10924
|
|
|
10610
10925
|
// app/lib/data-path.ts
|
|
10611
10926
|
import { realpathSync as realpathSync3 } from "fs";
|
|
10612
|
-
import { resolve as
|
|
10613
|
-
var
|
|
10614
|
-
var DATA_ROOT =
|
|
10615
|
-
var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ?
|
|
10927
|
+
import { resolve as resolve19, normalize, sep as sep3, relative } from "path";
|
|
10928
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve19(process.cwd(), "../platform");
|
|
10929
|
+
var DATA_ROOT = resolve19(PLATFORM_ROOT6, "..", "data");
|
|
10930
|
+
var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve19(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
|
|
10616
10931
|
var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["uploads", "accounts"]);
|
|
10617
10932
|
var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10618
10933
|
function crossesForeignAccountPartition(relPath, accountId) {
|
|
@@ -10625,7 +10940,7 @@ function crossesForeignAccountPartition(relPath, accountId) {
|
|
|
10625
10940
|
}
|
|
10626
10941
|
function resolveUnderRoot(raw, rootAbs, rootLabel) {
|
|
10627
10942
|
const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
10628
|
-
const absolute =
|
|
10943
|
+
const absolute = resolve19(rootAbs, cleaned);
|
|
10629
10944
|
let rootReal;
|
|
10630
10945
|
try {
|
|
10631
10946
|
rootReal = realpathSync3(rootAbs);
|
|
@@ -10984,7 +11299,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
10984
11299
|
|
|
10985
11300
|
// app/lib/file-index.ts
|
|
10986
11301
|
import * as fsp from "fs/promises";
|
|
10987
|
-
import { resolve as
|
|
11302
|
+
import { resolve as resolve20, relative as relative2, join as join18, basename as basename5, extname as extname2, sep as sep4 } from "path";
|
|
10988
11303
|
import { tmpdir as tmpdir2 } from "os";
|
|
10989
11304
|
import { execFile as execFile2 } from "child_process";
|
|
10990
11305
|
import { promisify as promisify2 } from "util";
|
|
@@ -11292,8 +11607,8 @@ async function reconcileFileIndex(accountId, opts) {
|
|
|
11292
11607
|
return withSession(opts, async (session) => {
|
|
11293
11608
|
const walked = [];
|
|
11294
11609
|
const subtreeRoots = [
|
|
11295
|
-
|
|
11296
|
-
|
|
11610
|
+
resolve20(dataRoot, "uploads", accountId),
|
|
11611
|
+
resolve20(dataRoot, "accounts", accountId)
|
|
11297
11612
|
];
|
|
11298
11613
|
let clean = true;
|
|
11299
11614
|
for (const root of subtreeRoots) {
|
|
@@ -11374,7 +11689,7 @@ async function reconcileFileIndexDebounced(accountId, opts) {
|
|
|
11374
11689
|
async function indexFile(accountId, relativePath, opts) {
|
|
11375
11690
|
const dataRoot = opts?.dataRoot ?? DATA_ROOT;
|
|
11376
11691
|
const embed2 = opts?.embed ?? embed;
|
|
11377
|
-
const absolute =
|
|
11692
|
+
const absolute = resolve20(dataRoot, relativePath);
|
|
11378
11693
|
await withSession(opts, async (session) => {
|
|
11379
11694
|
const st = await fsp.stat(absolute);
|
|
11380
11695
|
const wf = {
|
|
@@ -11419,7 +11734,7 @@ async function readMeta(absDir, baseName) {
|
|
|
11419
11734
|
}
|
|
11420
11735
|
async function readAccountNames() {
|
|
11421
11736
|
const map = /* @__PURE__ */ new Map();
|
|
11422
|
-
const accountsDir =
|
|
11737
|
+
const accountsDir = resolve21(DATA_ROOT, "accounts");
|
|
11423
11738
|
let names;
|
|
11424
11739
|
try {
|
|
11425
11740
|
names = await readdir3(accountsDir);
|
|
@@ -11428,7 +11743,7 @@ async function readAccountNames() {
|
|
|
11428
11743
|
}
|
|
11429
11744
|
for (const name of names) {
|
|
11430
11745
|
if (!UUID_RE3.test(name)) continue;
|
|
11431
|
-
const configPath2 =
|
|
11746
|
+
const configPath2 = resolve21(accountsDir, name, "account.json");
|
|
11432
11747
|
try {
|
|
11433
11748
|
const raw = await readFile4(configPath2, "utf8");
|
|
11434
11749
|
const parsed = JSON.parse(raw);
|
|
@@ -11669,8 +11984,8 @@ app19.post("/upload", requireAdminSession, async (c) => {
|
|
|
11669
11984
|
}
|
|
11670
11985
|
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
11671
11986
|
const finalName = `${Date.now()}-${safeName}`;
|
|
11672
|
-
const destDir =
|
|
11673
|
-
const destPath =
|
|
11987
|
+
const destDir = resolve21(DATA_ROOT, "uploads", accountId);
|
|
11988
|
+
const destPath = resolve21(destDir, finalName);
|
|
11674
11989
|
try {
|
|
11675
11990
|
await mkdir3(destDir, { recursive: true });
|
|
11676
11991
|
const dataRootReal = realpathSync4(DATA_ROOT);
|
|
@@ -12679,7 +12994,7 @@ app20.get("/", requireAdminSession, async (c) => {
|
|
|
12679
12994
|
var graph_search_default = app20;
|
|
12680
12995
|
|
|
12681
12996
|
// server/routes/admin/graph-subgraph.ts
|
|
12682
|
-
import
|
|
12997
|
+
import neo4j2 from "neo4j-driver";
|
|
12683
12998
|
|
|
12684
12999
|
// app/lib/graph-labels.ts
|
|
12685
13000
|
var import_dist2 = __toESM(require_dist2(), 1);
|
|
@@ -12933,12 +13248,12 @@ async function handleNeighbourhood(c, accountId) {
|
|
|
12933
13248
|
// `neo4j.int` keeps the LIMIT value as a 64-bit Integer on the wire —
|
|
12934
13249
|
// bare JS `number` works on Pi 4 today but errors on driver versions
|
|
12935
13250
|
// that strictly type-check LIMIT clauses against Long.
|
|
12936
|
-
neighbourhoodLimit:
|
|
13251
|
+
neighbourhoodLimit: neo4j2.int(NEIGHBOURHOOD_LIMIT),
|
|
12937
13252
|
// same Long-int discipline for the cluster-expand cap.
|
|
12938
13253
|
// Used by NEIGHBOURHOOD_CYPHER's `siblingMessages[0..$clusterMessagesCap]`
|
|
12939
13254
|
// slice. The search-intersect variant ignores it (cluster-expand
|
|
12940
13255
|
// does not apply when search is narrowing the canvas).
|
|
12941
|
-
clusterMessagesCap:
|
|
13256
|
+
clusterMessagesCap: neo4j2.int(MAX_CLUSTER_MESSAGES)
|
|
12942
13257
|
};
|
|
12943
13258
|
if (allowedIds !== null) cypherParams.allowedIds = allowedIds;
|
|
12944
13259
|
const result = await session.executeRead(async (tx) => {
|
|
@@ -13281,10 +13596,10 @@ function structuralTemporalToIso(v) {
|
|
|
13281
13596
|
return `${y}-${m}-${d}T${hh}:${mm}:${ss}${frac}${sign}${tzH}:${tzM}`;
|
|
13282
13597
|
}
|
|
13283
13598
|
function convertNeo4jValue(value, warnedClasses) {
|
|
13284
|
-
if (
|
|
13599
|
+
if (neo4j2.isDateTime(value) || neo4j2.isDate(value) || neo4j2.isLocalDateTime(value) || neo4j2.isLocalTime(value) || neo4j2.isTime(value) || neo4j2.isDuration(value)) {
|
|
13285
13600
|
return value.toString();
|
|
13286
13601
|
}
|
|
13287
|
-
if (
|
|
13602
|
+
if (neo4j2.isInt(value)) {
|
|
13288
13603
|
return value.inSafeRange() ? value.toNumber() : value.toString();
|
|
13289
13604
|
}
|
|
13290
13605
|
if (isStructuralInteger(value)) {
|
|
@@ -13784,7 +14099,7 @@ var graph_default_view_default = app25;
|
|
|
13784
14099
|
|
|
13785
14100
|
// server/routes/admin/sidebar-artefacts.ts
|
|
13786
14101
|
import { readdir as readdir4, stat as stat5 } from "fs/promises";
|
|
13787
|
-
import { resolve as
|
|
14102
|
+
import { resolve as resolve22, relative as relative3, isAbsolute, sep as sep6, basename as basename7 } from "path";
|
|
13788
14103
|
import { existsSync as existsSync21 } from "fs";
|
|
13789
14104
|
var LIMIT = 50;
|
|
13790
14105
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
@@ -13804,7 +14119,7 @@ app26.get("/", requireAdminSession, async (c) => {
|
|
|
13804
14119
|
if (accountFiles === null) {
|
|
13805
14120
|
return c.json({ error: "Failed to load artefacts" }, 500);
|
|
13806
14121
|
}
|
|
13807
|
-
const accountDir =
|
|
14122
|
+
const accountDir = resolve22(ACCOUNTS_DIR, accountId);
|
|
13808
14123
|
const agents = await fetchAgentTemplateRows(accountDir);
|
|
13809
14124
|
const artefacts = [...accountFiles, ...agents].sort(
|
|
13810
14125
|
(a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
|
|
@@ -13855,8 +14170,8 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
13855
14170
|
async function fetchAgentTemplateRows(accountDir) {
|
|
13856
14171
|
const rows = [];
|
|
13857
14172
|
for (const filename of ADMIN_AGENT_FILES) {
|
|
13858
|
-
const overridePath =
|
|
13859
|
-
const bundledPath =
|
|
14173
|
+
const overridePath = resolve22(accountDir, "agents", "admin", filename);
|
|
14174
|
+
const bundledPath = resolve22(PLATFORM_ROOT, "templates", "agents", "admin", filename);
|
|
13860
14175
|
const labelStem = filename.replace(/\.md$/, "");
|
|
13861
14176
|
const row = await readAgentTemplateRow({
|
|
13862
14177
|
id: `agent-template:admin:${filename}`,
|
|
@@ -13869,12 +14184,12 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
13869
14184
|
});
|
|
13870
14185
|
if (row) rows.push(row);
|
|
13871
14186
|
}
|
|
13872
|
-
const overrideDir =
|
|
13873
|
-
const bundledDir =
|
|
14187
|
+
const overrideDir = resolve22(accountDir, "specialists", "agents");
|
|
14188
|
+
const bundledDir = resolve22(PLATFORM_ROOT, "templates", "specialists", "agents");
|
|
13874
14189
|
const specialistNames = await unionSpecialistFilenames(overrideDir, bundledDir);
|
|
13875
14190
|
for (const filename of specialistNames) {
|
|
13876
|
-
const overridePath =
|
|
13877
|
-
const bundledPath =
|
|
14191
|
+
const overridePath = resolve22(overrideDir, filename);
|
|
14192
|
+
const bundledPath = resolve22(bundledDir, filename);
|
|
13878
14193
|
const row = await readAgentTemplateRow({
|
|
13879
14194
|
id: `agent-template:specialist:${filename}`,
|
|
13880
14195
|
displayName: filename.replace(/\.md$/, ""),
|
|
@@ -14082,8 +14397,8 @@ app29.post("/", requireAdminSession, async (c) => {
|
|
|
14082
14397
|
var session_archive_default = app29;
|
|
14083
14398
|
|
|
14084
14399
|
// server/routes/admin/session-rc-spawn.ts
|
|
14085
|
-
import { randomUUID as
|
|
14086
|
-
function resolveSpawnPlan(host, operatorDomains, body, mintId =
|
|
14400
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
14401
|
+
function resolveSpawnPlan(host, operatorDomains, body, mintId = randomUUID10) {
|
|
14087
14402
|
const operator = isOperatorHost(host, operatorDomains);
|
|
14088
14403
|
const resumeId = typeof body.sessionId === "string" && body.sessionId.length > 0 ? body.sessionId : void 0;
|
|
14089
14404
|
const name = typeof body.name === "string" && body.name.length > 0 ? body.name : void 0;
|
|
@@ -14122,7 +14437,7 @@ app30.post("/", requireAdminSession, async (c) => {
|
|
|
14122
14437
|
}
|
|
14123
14438
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
14124
14439
|
const plan = resolveSpawnPlan(host, getOperatorDomains(), body);
|
|
14125
|
-
const spawnReqId =
|
|
14440
|
+
const spawnReqId = randomUUID10();
|
|
14126
14441
|
console.log(
|
|
14127
14442
|
`[chat-spawn] op=request spawnReqId=${spawnReqId} origin=${plan.origin} kind=${plan.kind} sessionId=${plan.kind === "new" ? "new" : plan.sessionId}`
|
|
14128
14443
|
);
|
|
@@ -14288,24 +14603,24 @@ app31.get("/", async (c) => {
|
|
|
14288
14603
|
var system_stats_default = app31;
|
|
14289
14604
|
|
|
14290
14605
|
// server/routes/admin/health.ts
|
|
14291
|
-
import { existsSync as existsSync22, readFileSync as
|
|
14292
|
-
import { resolve as
|
|
14293
|
-
var
|
|
14606
|
+
import { existsSync as existsSync22, readFileSync as readFileSync21 } from "fs";
|
|
14607
|
+
import { resolve as resolve23, join as join20 } from "path";
|
|
14608
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve23(process.cwd(), "..");
|
|
14294
14609
|
var brandHostname = "maxy";
|
|
14295
|
-
var brandJsonPath = join20(
|
|
14610
|
+
var brandJsonPath = join20(PLATFORM_ROOT7, "config", "brand.json");
|
|
14296
14611
|
if (existsSync22(brandJsonPath)) {
|
|
14297
14612
|
try {
|
|
14298
|
-
const brand = JSON.parse(
|
|
14613
|
+
const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
|
|
14299
14614
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
14300
14615
|
} catch {
|
|
14301
14616
|
}
|
|
14302
14617
|
}
|
|
14303
|
-
var VERSION_FILE =
|
|
14618
|
+
var VERSION_FILE = resolve23(PLATFORM_ROOT7, `config/.${brandHostname}-version`);
|
|
14304
14619
|
var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
14305
14620
|
var PROBE_TIMEOUT_MS = 1e3;
|
|
14306
14621
|
function readVersion() {
|
|
14307
14622
|
if (!existsSync22(VERSION_FILE)) return "unknown";
|
|
14308
|
-
return
|
|
14623
|
+
return readFileSync21(VERSION_FILE, "utf-8").trim() || "unknown";
|
|
14309
14624
|
}
|
|
14310
14625
|
async function probeConversationDb() {
|
|
14311
14626
|
let session;
|
|
@@ -14356,7 +14671,7 @@ app32.get("/", async (c) => {
|
|
|
14356
14671
|
var health_default2 = app32;
|
|
14357
14672
|
|
|
14358
14673
|
// server/routes/admin/linkedin-ingest.ts
|
|
14359
|
-
import { randomUUID as
|
|
14674
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
14360
14675
|
var TAG25 = "[linkedin-ingest-route]";
|
|
14361
14676
|
var UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
14362
14677
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
@@ -14482,7 +14797,7 @@ app33.post("/", requireAdminSession, async (c) => {
|
|
|
14482
14797
|
);
|
|
14483
14798
|
const initialMessage = buildInitialMessage(envelope);
|
|
14484
14799
|
const spawnStart = Date.now();
|
|
14485
|
-
const sessionId =
|
|
14800
|
+
const sessionId = randomUUID11();
|
|
14486
14801
|
console.log(TAG25 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
14487
14802
|
const spawned = await managerRcSpawn({
|
|
14488
14803
|
sessionId,
|
|
@@ -14504,7 +14819,7 @@ app33.post("/", requireAdminSession, async (c) => {
|
|
|
14504
14819
|
var linkedin_ingest_default = app33;
|
|
14505
14820
|
|
|
14506
14821
|
// server/routes/admin/post-turn-context.ts
|
|
14507
|
-
import
|
|
14822
|
+
import neo4j3 from "neo4j-driver";
|
|
14508
14823
|
var TAG26 = "[post-turn-context]";
|
|
14509
14824
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
14510
14825
|
"embedding",
|
|
@@ -14517,10 +14832,10 @@ function isLoopbackAddr2(addr) {
|
|
|
14517
14832
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
14518
14833
|
}
|
|
14519
14834
|
function convertValue(value) {
|
|
14520
|
-
if (
|
|
14835
|
+
if (neo4j3.isDateTime(value) || neo4j3.isDate(value) || neo4j3.isLocalDateTime(value) || neo4j3.isLocalTime(value) || neo4j3.isTime(value) || neo4j3.isDuration(value)) {
|
|
14521
14836
|
return value.toString();
|
|
14522
14837
|
}
|
|
14523
|
-
if (
|
|
14838
|
+
if (neo4j3.isInt(value)) {
|
|
14524
14839
|
return value.inSafeRange() ? value.toNumber() : value.toString();
|
|
14525
14840
|
}
|
|
14526
14841
|
if (Array.isArray(value)) {
|
|
@@ -14607,7 +14922,7 @@ app34.get("/", async (c) => {
|
|
|
14607
14922
|
var post_turn_context_default = app34;
|
|
14608
14923
|
|
|
14609
14924
|
// server/routes/admin/public-session-context.ts
|
|
14610
|
-
import
|
|
14925
|
+
import neo4j4 from "neo4j-driver";
|
|
14611
14926
|
var TAG27 = "[public-session-context]";
|
|
14612
14927
|
var STRIPPED_PROPERTIES3 = /* @__PURE__ */ new Set([
|
|
14613
14928
|
"embedding",
|
|
@@ -14620,10 +14935,10 @@ function isLoopbackAddr3(addr) {
|
|
|
14620
14935
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
14621
14936
|
}
|
|
14622
14937
|
function convertValue2(value) {
|
|
14623
|
-
if (
|
|
14938
|
+
if (neo4j4.isDateTime(value) || neo4j4.isDate(value) || neo4j4.isLocalDateTime(value) || neo4j4.isLocalTime(value) || neo4j4.isTime(value) || neo4j4.isDuration(value)) {
|
|
14624
14939
|
return value.toString();
|
|
14625
14940
|
}
|
|
14626
|
-
if (
|
|
14941
|
+
if (neo4j4.isInt(value)) {
|
|
14627
14942
|
return value.inSafeRange() ? value.toNumber() : value.toString();
|
|
14628
14943
|
}
|
|
14629
14944
|
if (Array.isArray(value)) {
|
|
@@ -14779,9 +15094,81 @@ app37.post("/", async (c) => {
|
|
|
14779
15094
|
});
|
|
14780
15095
|
var access_session_evict_default = app37;
|
|
14781
15096
|
|
|
14782
|
-
// server/routes/admin/
|
|
15097
|
+
// server/routes/admin/enrol-person.ts
|
|
15098
|
+
var TAG30 = "[enrol]";
|
|
15099
|
+
function isLoopbackAddr6(addr) {
|
|
15100
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
15101
|
+
}
|
|
15102
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
14783
15103
|
var app38 = new Hono();
|
|
14784
|
-
app38.post("/
|
|
15104
|
+
app38.post("/", async (c) => {
|
|
15105
|
+
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
15106
|
+
if (!isLoopbackAddr6(remoteAddr)) {
|
|
15107
|
+
console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
15108
|
+
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
15109
|
+
}
|
|
15110
|
+
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
15111
|
+
const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
|
|
15112
|
+
if (xffRaw.length > 0) {
|
|
15113
|
+
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
15114
|
+
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
15115
|
+
if (offender !== void 0) {
|
|
15116
|
+
console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
15117
|
+
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
15118
|
+
}
|
|
15119
|
+
}
|
|
15120
|
+
let body;
|
|
15121
|
+
try {
|
|
15122
|
+
body = await c.req.json();
|
|
15123
|
+
} catch {
|
|
15124
|
+
return c.json({ error: "invalid-json" }, 400);
|
|
15125
|
+
}
|
|
15126
|
+
const rawPhone = typeof body.phone === "string" ? body.phone : "";
|
|
15127
|
+
const phone = normalizeE164(rawPhone);
|
|
15128
|
+
if (phone.length <= 1) {
|
|
15129
|
+
return c.json({ error: "invalid-phone" }, 400);
|
|
15130
|
+
}
|
|
15131
|
+
const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
|
|
15132
|
+
if (!EMAIL_RE.test(email)) {
|
|
15133
|
+
return c.json({ error: "invalid-email" }, 400);
|
|
15134
|
+
}
|
|
15135
|
+
const agentSlug = typeof body.agentSlug === "string" ? body.agentSlug.trim() : "";
|
|
15136
|
+
if (!agentSlug) {
|
|
15137
|
+
return c.json({ error: "agentSlug required" }, 400);
|
|
15138
|
+
}
|
|
15139
|
+
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
15140
|
+
if (!accountId) {
|
|
15141
|
+
console.error(`${TAG30} op=person result=no-account-id`);
|
|
15142
|
+
return c.json({ error: "no-account-id" }, 500);
|
|
15143
|
+
}
|
|
15144
|
+
let personId;
|
|
15145
|
+
try {
|
|
15146
|
+
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
15147
|
+
} catch (err) {
|
|
15148
|
+
console.error(
|
|
15149
|
+
`${TAG30} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
15150
|
+
);
|
|
15151
|
+
return c.json({ error: "enrol-failed" }, 500);
|
|
15152
|
+
}
|
|
15153
|
+
let grant = null;
|
|
15154
|
+
try {
|
|
15155
|
+
const g = await findGrantByContact(email, agentSlug, accountId);
|
|
15156
|
+
if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
|
|
15157
|
+
} catch (err) {
|
|
15158
|
+
console.error(
|
|
15159
|
+
`${TAG30} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
15160
|
+
);
|
|
15161
|
+
}
|
|
15162
|
+
console.log(
|
|
15163
|
+
`${TAG30} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
|
|
15164
|
+
);
|
|
15165
|
+
return c.json({ personId, grant }, 200);
|
|
15166
|
+
});
|
|
15167
|
+
var enrol_person_default = app38;
|
|
15168
|
+
|
|
15169
|
+
// server/routes/admin/browser.ts
|
|
15170
|
+
var app39 = new Hono();
|
|
15171
|
+
app39.post("/launch", requireAdminSession, async (c) => {
|
|
14785
15172
|
try {
|
|
14786
15173
|
const transport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
14787
15174
|
console.error(`[admin/browser/launch] op=request transport=${transport}`);
|
|
@@ -14809,256 +15196,49 @@ app38.post("/launch", requireAdminSession, async (c) => {
|
|
|
14809
15196
|
);
|
|
14810
15197
|
}
|
|
14811
15198
|
});
|
|
14812
|
-
var browser_default =
|
|
15199
|
+
var browser_default = app39;
|
|
14813
15200
|
|
|
14814
15201
|
// server/routes/admin/index.ts
|
|
14815
|
-
var
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
|
|
14836
|
-
|
|
14837
|
-
|
|
14838
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14848
|
-
// app/lib/access-gate.ts
|
|
14849
|
-
import neo4j4 from "neo4j-driver";
|
|
14850
|
-
import { readFileSync as readFileSync21 } from "fs";
|
|
14851
|
-
import { resolve as resolve23 } from "path";
|
|
14852
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
14853
|
-
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve23(process.cwd(), "..");
|
|
14854
|
-
var driver = null;
|
|
14855
|
-
function readPassword() {
|
|
14856
|
-
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
14857
|
-
const passwordFile = resolve23(PLATFORM_ROOT7, "config/.neo4j-password");
|
|
14858
|
-
try {
|
|
14859
|
-
return readFileSync21(passwordFile, "utf-8").trim();
|
|
14860
|
-
} catch {
|
|
14861
|
-
throw new Error(
|
|
14862
|
-
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
14863
|
-
);
|
|
14864
|
-
}
|
|
14865
|
-
}
|
|
14866
|
-
function getDriver() {
|
|
14867
|
-
if (!driver) {
|
|
14868
|
-
const uri = process.env.NEO4J_URI;
|
|
14869
|
-
if (!uri) {
|
|
14870
|
-
throw new Error(
|
|
14871
|
-
"[ui/access-gate] NEO4J_URI unset \u2014 refusing to default to bolt://localhost:7687"
|
|
14872
|
-
);
|
|
14873
|
-
}
|
|
14874
|
-
const user = process.env.NEO4J_USER ?? "neo4j";
|
|
14875
|
-
const password = readPassword();
|
|
14876
|
-
console.error(`[ui/access-gate] resolved neo4j_uri=${uri}`);
|
|
14877
|
-
driver = neo4j4.driver(uri, neo4j4.auth.basic(user, password), {
|
|
14878
|
-
maxConnectionPoolSize: 5
|
|
14879
|
-
});
|
|
14880
|
-
}
|
|
14881
|
-
return driver;
|
|
14882
|
-
}
|
|
14883
|
-
function getSession3() {
|
|
14884
|
-
return getDriver().session();
|
|
14885
|
-
}
|
|
14886
|
-
process.on("SIGINT", async () => {
|
|
14887
|
-
if (driver) {
|
|
14888
|
-
await driver.close();
|
|
14889
|
-
driver = null;
|
|
14890
|
-
}
|
|
14891
|
-
});
|
|
14892
|
-
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
14893
|
-
var ACCESS_MAX_ATTEMPTS = 5;
|
|
14894
|
-
var ACCESS_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
14895
|
-
var ACCESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
14896
|
-
function checkAccessRateLimit(ip, agentSlug) {
|
|
14897
|
-
const key = `${ip}:${agentSlug}`;
|
|
14898
|
-
const entry = rateLimitMap.get(key);
|
|
14899
|
-
if (!entry) return null;
|
|
14900
|
-
const now = Date.now();
|
|
14901
|
-
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
14902
|
-
const remainingMs = entry.lockedUntil - now;
|
|
14903
|
-
const remainingS = Math.ceil(remainingMs / 1e3);
|
|
14904
|
-
return `Too many attempts. Try again in ${remainingS}s`;
|
|
14905
|
-
}
|
|
14906
|
-
if (now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
14907
|
-
rateLimitMap.delete(key);
|
|
14908
|
-
return null;
|
|
14909
|
-
}
|
|
14910
|
-
return null;
|
|
14911
|
-
}
|
|
14912
|
-
function recordAccessFailedAttempt(ip, agentSlug) {
|
|
14913
|
-
const key = `${ip}:${agentSlug}`;
|
|
14914
|
-
const now = Date.now();
|
|
14915
|
-
const entry = rateLimitMap.get(key);
|
|
14916
|
-
if (!entry || now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
14917
|
-
rateLimitMap.set(key, { attempts: 1, firstAttempt: now });
|
|
14918
|
-
return;
|
|
14919
|
-
}
|
|
14920
|
-
entry.attempts++;
|
|
14921
|
-
if (entry.attempts >= ACCESS_MAX_ATTEMPTS) {
|
|
14922
|
-
entry.lockedUntil = now + ACCESS_LOCKOUT_MS;
|
|
14923
|
-
}
|
|
14924
|
-
}
|
|
14925
|
-
function clearAccessRateLimit(ip, agentSlug) {
|
|
14926
|
-
rateLimitMap.delete(`${ip}:${agentSlug}`);
|
|
14927
|
-
}
|
|
14928
|
-
var contactRateMap = /* @__PURE__ */ new Map();
|
|
14929
|
-
var REQUEST_LINK_MAX = 3;
|
|
14930
|
-
var REQUEST_LINK_WINDOW_MS = 60 * 60 * 1e3;
|
|
14931
|
-
function checkRequestLinkRateLimit(contactValue) {
|
|
14932
|
-
const entry = contactRateMap.get(contactValue);
|
|
14933
|
-
if (!entry) return null;
|
|
14934
|
-
const now = Date.now();
|
|
14935
|
-
if (now - entry.windowStart > REQUEST_LINK_WINDOW_MS) {
|
|
14936
|
-
contactRateMap.delete(contactValue);
|
|
14937
|
-
return null;
|
|
14938
|
-
}
|
|
14939
|
-
if (entry.count >= REQUEST_LINK_MAX) {
|
|
14940
|
-
const remainingMs = REQUEST_LINK_WINDOW_MS - (now - entry.windowStart);
|
|
14941
|
-
const remainingM = Math.ceil(remainingMs / 6e4);
|
|
14942
|
-
return `Too many requests. Try again in ${remainingM} minute(s)`;
|
|
14943
|
-
}
|
|
14944
|
-
return null;
|
|
14945
|
-
}
|
|
14946
|
-
function recordRequestLinkAttempt(contactValue) {
|
|
14947
|
-
const now = Date.now();
|
|
14948
|
-
const entry = contactRateMap.get(contactValue);
|
|
14949
|
-
if (!entry || now - entry.windowStart > REQUEST_LINK_WINDOW_MS) {
|
|
14950
|
-
contactRateMap.set(contactValue, { count: 1, windowStart: now });
|
|
14951
|
-
return;
|
|
14952
|
-
}
|
|
14953
|
-
entry.count++;
|
|
14954
|
-
}
|
|
14955
|
-
function maskContact(value) {
|
|
14956
|
-
if (value.includes("@")) {
|
|
14957
|
-
const [local, domain] = value.split("@");
|
|
14958
|
-
return `${local[0]}***@${domain}`;
|
|
14959
|
-
}
|
|
14960
|
-
return "****";
|
|
14961
|
-
}
|
|
14962
|
-
function readGrant(record) {
|
|
14963
|
-
const g = record.get("g").properties;
|
|
14964
|
-
const personId = record.get("personId");
|
|
14965
|
-
return {
|
|
14966
|
-
grantId: record.get("grantId"),
|
|
14967
|
-
agentSlug: String(g.agentSlug),
|
|
14968
|
-
accountId: String(g.accountId),
|
|
14969
|
-
contactValue: String(g.contactValue),
|
|
14970
|
-
displayName: record.get("givenName") ?? null,
|
|
14971
|
-
personId,
|
|
14972
|
-
expiresAt: g.expiresAt ? new Date(String(g.expiresAt)).getTime() : null,
|
|
14973
|
-
status: String(g.status),
|
|
14974
|
-
sliceToken: String(g.sliceToken ?? "")
|
|
14975
|
-
};
|
|
14976
|
-
}
|
|
14977
|
-
async function findGrantByMagicToken(token) {
|
|
14978
|
-
const session = getSession3();
|
|
14979
|
-
try {
|
|
14980
|
-
const result = await session.run(
|
|
14981
|
-
`MATCH (p:Person)-[:HAS_ACCESS]->(g:AccessGrant {magicToken: $token})
|
|
14982
|
-
RETURN g, p.givenName AS givenName,
|
|
14983
|
-
elementId(g) AS grantId,
|
|
14984
|
-
elementId(p) AS personId`,
|
|
14985
|
-
{ token }
|
|
14986
|
-
);
|
|
14987
|
-
if (result.records.length === 0) return null;
|
|
14988
|
-
return readGrant(result.records[0]);
|
|
14989
|
-
} finally {
|
|
14990
|
-
await session.close();
|
|
14991
|
-
}
|
|
14992
|
-
}
|
|
14993
|
-
async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
|
|
14994
|
-
const session = getSession3();
|
|
14995
|
-
try {
|
|
14996
|
-
const result = await session.run(
|
|
14997
|
-
`MATCH (p:Person)-[:HAS_ACCESS]->(g:AccessGrant {
|
|
14998
|
-
contactValue: $contactValue,
|
|
14999
|
-
agentSlug: $agentSlug,
|
|
15000
|
-
accountId: $accountId
|
|
15001
|
-
})
|
|
15002
|
-
WHERE g.status IN ['invited', 'active']
|
|
15003
|
-
RETURN g, p.givenName AS givenName,
|
|
15004
|
-
elementId(g) AS grantId,
|
|
15005
|
-
elementId(p) AS personId`,
|
|
15006
|
-
{ contactValue, agentSlug, accountId }
|
|
15007
|
-
);
|
|
15008
|
-
if (result.records.length === 0) return null;
|
|
15009
|
-
return readGrant(result.records[0]);
|
|
15010
|
-
} finally {
|
|
15011
|
-
await session.close();
|
|
15012
|
-
}
|
|
15013
|
-
}
|
|
15014
|
-
async function consumeMagicTokenAndActivate(grantId) {
|
|
15015
|
-
const session = getSession3();
|
|
15016
|
-
try {
|
|
15017
|
-
const result = await session.run(
|
|
15018
|
-
`MATCH (g:AccessGrant) WHERE elementId(g) = $grantId
|
|
15019
|
-
SET g.magicToken = null,
|
|
15020
|
-
g.magicTokenExpiresAt = null,
|
|
15021
|
-
g.status = "active"
|
|
15022
|
-
RETURN count(g) AS affected`,
|
|
15023
|
-
{ grantId }
|
|
15024
|
-
);
|
|
15025
|
-
const affected = result.records[0]?.get("affected");
|
|
15026
|
-
const count = typeof affected === "number" ? affected : affected?.toNumber?.() ?? 0;
|
|
15027
|
-
if (count === 0) {
|
|
15028
|
-
throw new Error(`[access-gate] consumeMagicTokenAndActivate: no grant matched grantId=${grantId}`);
|
|
15029
|
-
}
|
|
15030
|
-
} finally {
|
|
15031
|
-
await session.close();
|
|
15032
|
-
}
|
|
15033
|
-
}
|
|
15034
|
-
async function generateNewMagicToken(grantId) {
|
|
15035
|
-
const token = randomUUID11();
|
|
15036
|
-
const session = getSession3();
|
|
15037
|
-
try {
|
|
15038
|
-
const result = await session.run(
|
|
15039
|
-
`MATCH (g:AccessGrant) WHERE elementId(g) = $grantId
|
|
15040
|
-
SET g.magicToken = $token,
|
|
15041
|
-
g.magicTokenExpiresAt = datetime() + duration('PT15M')
|
|
15042
|
-
RETURN count(g) AS affected`,
|
|
15043
|
-
{ grantId, token }
|
|
15044
|
-
);
|
|
15045
|
-
const affected = result.records[0]?.get("affected");
|
|
15046
|
-
const count = typeof affected === "number" ? affected : affected?.toNumber?.() ?? 0;
|
|
15047
|
-
if (count === 0) {
|
|
15048
|
-
throw new Error(`[access-gate] generateNewMagicToken: no grant matched grantId=${grantId}`);
|
|
15049
|
-
}
|
|
15050
|
-
} finally {
|
|
15051
|
-
await session.close();
|
|
15052
|
-
}
|
|
15053
|
-
return token;
|
|
15054
|
-
}
|
|
15202
|
+
var app40 = new Hono();
|
|
15203
|
+
app40.route("/session", session_default);
|
|
15204
|
+
app40.route("/logs", logs_default);
|
|
15205
|
+
app40.route("/claude-info", claude_info_default);
|
|
15206
|
+
app40.route("/attachment", attachment_default);
|
|
15207
|
+
app40.route("/agents", agents_default);
|
|
15208
|
+
app40.route("/sessions", sessions_default);
|
|
15209
|
+
app40.route("/claude-sessions", claude_sessions_default);
|
|
15210
|
+
app40.route("/log-ingest", log_ingest_default);
|
|
15211
|
+
app40.route("/events", events_default);
|
|
15212
|
+
app40.route("/files", files_default);
|
|
15213
|
+
app40.route("/graph-search", graph_search_default);
|
|
15214
|
+
app40.route("/graph-subgraph", graph_subgraph_default);
|
|
15215
|
+
app40.route("/graph-delete", graph_delete_default);
|
|
15216
|
+
app40.route("/graph-restore", graph_restore_default);
|
|
15217
|
+
app40.route("/graph-labels-in-graph", graph_labels_in_graph_default);
|
|
15218
|
+
app40.route("/graph-default-view", graph_default_view_default);
|
|
15219
|
+
app40.route("/sidebar-artefacts", sidebar_artefacts_default);
|
|
15220
|
+
app40.route("/sidebar-sessions", sidebar_sessions_default);
|
|
15221
|
+
app40.route("/session-delete", session_delete_default);
|
|
15222
|
+
app40.route("/session-stop", session_stop_default);
|
|
15223
|
+
app40.route("/session-archive", session_archive_default);
|
|
15224
|
+
app40.route("/browser", browser_default);
|
|
15225
|
+
app40.route("/session-rc-spawn", session_rc_spawn_default);
|
|
15226
|
+
app40.route("/system-stats", system_stats_default);
|
|
15227
|
+
app40.route("/health-brand", health_default2);
|
|
15228
|
+
app40.route("/linkedin-ingest", linkedin_ingest_default);
|
|
15229
|
+
app40.route("/post-turn-context", post_turn_context_default);
|
|
15230
|
+
app40.route("/public-session-context", public_session_context_default);
|
|
15231
|
+
app40.route("/public-session-exit", public_session_exit_default);
|
|
15232
|
+
app40.route("/access-session-evict", access_session_evict_default);
|
|
15233
|
+
app40.route("/enrol-person", enrol_person_default);
|
|
15234
|
+
var admin_default = app40;
|
|
15055
15235
|
|
|
15056
15236
|
// server/routes/access/verify-token.ts
|
|
15057
|
-
var
|
|
15237
|
+
var TAG31 = "[access-verify]";
|
|
15058
15238
|
var MINT_TAG = "[access-session-mint]";
|
|
15059
15239
|
var COOKIE_NAME = "__access_session";
|
|
15060
|
-
var
|
|
15061
|
-
|
|
15240
|
+
var app41 = new Hono();
|
|
15241
|
+
app41.post("/", async (c) => {
|
|
15062
15242
|
const ip = c.var.clientIp || "unknown";
|
|
15063
15243
|
let body;
|
|
15064
15244
|
try {
|
|
@@ -15073,39 +15253,39 @@ app40.post("/", async (c) => {
|
|
|
15073
15253
|
}
|
|
15074
15254
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
15075
15255
|
if (rateMsg) {
|
|
15076
|
-
console.error(`${
|
|
15256
|
+
console.error(`${TAG31} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
15077
15257
|
return c.json({ error: rateMsg }, 429);
|
|
15078
15258
|
}
|
|
15079
15259
|
const grant = await findGrantByMagicToken(token);
|
|
15080
15260
|
if (!grant) {
|
|
15081
15261
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
15082
|
-
console.error(`${
|
|
15262
|
+
console.error(`${TAG31} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
15083
15263
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
15084
15264
|
}
|
|
15085
15265
|
if (grant.agentSlug !== agentSlug) {
|
|
15086
15266
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
15087
15267
|
console.error(
|
|
15088
|
-
`${
|
|
15268
|
+
`${TAG31} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
15089
15269
|
);
|
|
15090
15270
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
15091
15271
|
}
|
|
15092
15272
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
15093
15273
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
15094
15274
|
console.error(
|
|
15095
|
-
`${
|
|
15275
|
+
`${TAG31} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
15096
15276
|
);
|
|
15097
15277
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
15098
15278
|
}
|
|
15099
15279
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
15100
15280
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
15101
15281
|
console.error(
|
|
15102
|
-
`${
|
|
15282
|
+
`${TAG31} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
15103
15283
|
);
|
|
15104
15284
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
15105
15285
|
}
|
|
15106
15286
|
if (!grant.sliceToken) {
|
|
15107
15287
|
console.error(
|
|
15108
|
-
`${
|
|
15288
|
+
`${TAG31} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
15109
15289
|
);
|
|
15110
15290
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
15111
15291
|
}
|
|
@@ -15121,12 +15301,12 @@ app40.post("/", async (c) => {
|
|
|
15121
15301
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
15122
15302
|
} catch (err) {
|
|
15123
15303
|
console.error(
|
|
15124
|
-
`${
|
|
15304
|
+
`${TAG31} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
15125
15305
|
);
|
|
15126
15306
|
return c.json({ error: "verification-failed" }, 500);
|
|
15127
15307
|
}
|
|
15128
15308
|
clearAccessRateLimit(ip, agentSlug);
|
|
15129
|
-
console.log(`${
|
|
15309
|
+
console.log(`${TAG31} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
15130
15310
|
console.log(
|
|
15131
15311
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
15132
15312
|
);
|
|
@@ -15140,7 +15320,7 @@ app40.post("/", async (c) => {
|
|
|
15140
15320
|
displayName: grant.displayName
|
|
15141
15321
|
});
|
|
15142
15322
|
});
|
|
15143
|
-
var verify_token_default =
|
|
15323
|
+
var verify_token_default = app41;
|
|
15144
15324
|
|
|
15145
15325
|
// app/lib/access-email.ts
|
|
15146
15326
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -15202,10 +15382,10 @@ async function sendMagicLinkEmail(payload) {
|
|
|
15202
15382
|
}
|
|
15203
15383
|
|
|
15204
15384
|
// server/routes/access/request-magic-link.ts
|
|
15205
|
-
var
|
|
15206
|
-
var
|
|
15385
|
+
var TAG32 = "[access-request-link]";
|
|
15386
|
+
var app42 = new Hono();
|
|
15207
15387
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
15208
|
-
|
|
15388
|
+
app42.post("/", async (c) => {
|
|
15209
15389
|
let body;
|
|
15210
15390
|
try {
|
|
15211
15391
|
body = await c.req.json();
|
|
@@ -15219,18 +15399,18 @@ app41.post("/", async (c) => {
|
|
|
15219
15399
|
}
|
|
15220
15400
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
15221
15401
|
if (rateMsg) {
|
|
15222
|
-
console.error(`${
|
|
15402
|
+
console.error(`${TAG32} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
15223
15403
|
return c.json({ error: rateMsg }, 429);
|
|
15224
15404
|
}
|
|
15225
15405
|
recordRequestLinkAttempt(contactValue);
|
|
15226
15406
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
15227
15407
|
if (!accountId) {
|
|
15228
|
-
console.error(`${
|
|
15408
|
+
console.error(`${TAG32} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
15229
15409
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
15230
15410
|
}
|
|
15231
15411
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
15232
15412
|
if (!grant) {
|
|
15233
|
-
console.log(`${
|
|
15413
|
+
console.log(`${TAG32} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
15234
15414
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
15235
15415
|
}
|
|
15236
15416
|
let token;
|
|
@@ -15238,7 +15418,7 @@ app41.post("/", async (c) => {
|
|
|
15238
15418
|
token = await generateNewMagicToken(grant.grantId);
|
|
15239
15419
|
} catch (err) {
|
|
15240
15420
|
console.error(
|
|
15241
|
-
`${
|
|
15421
|
+
`${TAG32} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
15242
15422
|
);
|
|
15243
15423
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
15244
15424
|
}
|
|
@@ -15270,22 +15450,22 @@ app41.post("/", async (c) => {
|
|
|
15270
15450
|
});
|
|
15271
15451
|
if (!sendResult.ok) {
|
|
15272
15452
|
console.error(
|
|
15273
|
-
`${
|
|
15453
|
+
`${TAG32} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
15274
15454
|
);
|
|
15275
15455
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
15276
15456
|
}
|
|
15277
15457
|
console.log(
|
|
15278
|
-
`${
|
|
15458
|
+
`${TAG32} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
15279
15459
|
);
|
|
15280
15460
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
15281
15461
|
});
|
|
15282
|
-
var request_magic_link_default =
|
|
15462
|
+
var request_magic_link_default = app42;
|
|
15283
15463
|
|
|
15284
15464
|
// server/routes/access/index.ts
|
|
15285
|
-
var
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
var access_default =
|
|
15465
|
+
var app43 = new Hono();
|
|
15466
|
+
app43.route("/verify-token", verify_token_default);
|
|
15467
|
+
app43.route("/request-magic-link", request_magic_link_default);
|
|
15468
|
+
var access_default = app43;
|
|
15289
15469
|
|
|
15290
15470
|
// server/routes/sites.ts
|
|
15291
15471
|
import { existsSync as existsSync23, readFileSync as readFileSync22, realpathSync as realpathSync5, statSync as statSync9 } from "fs";
|
|
@@ -15320,8 +15500,8 @@ function getExt(p) {
|
|
|
15320
15500
|
if (idx < p.lastIndexOf("/")) return "";
|
|
15321
15501
|
return p.slice(idx).toLowerCase();
|
|
15322
15502
|
}
|
|
15323
|
-
var
|
|
15324
|
-
|
|
15503
|
+
var app44 = new Hono();
|
|
15504
|
+
app44.get("/:rel{.*}", (c) => {
|
|
15325
15505
|
const reqPath = c.req.path;
|
|
15326
15506
|
const rawRel = c.req.param("rel") ?? "";
|
|
15327
15507
|
const trimmed = rawRel.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
@@ -15424,7 +15604,7 @@ app43.get("/:rel{.*}", (c) => {
|
|
|
15424
15604
|
"X-Content-Type-Options": "nosniff"
|
|
15425
15605
|
});
|
|
15426
15606
|
});
|
|
15427
|
-
var sites_default =
|
|
15607
|
+
var sites_default = app44;
|
|
15428
15608
|
|
|
15429
15609
|
// app/lib/visitor-token.ts
|
|
15430
15610
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
@@ -15524,7 +15704,7 @@ function readBrandConfig() {
|
|
|
15524
15704
|
}
|
|
15525
15705
|
|
|
15526
15706
|
// server/routes/visitor-consent.ts
|
|
15527
|
-
var
|
|
15707
|
+
var app45 = new Hono();
|
|
15528
15708
|
var CONSENT_COOKIE_NAME = "mxy_consent";
|
|
15529
15709
|
var CONSENT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
|
|
15530
15710
|
var DEFAULT_CONSENT_COPY = {
|
|
@@ -15569,17 +15749,17 @@ function siteSlugFromReferer(referer) {
|
|
|
15569
15749
|
return "";
|
|
15570
15750
|
}
|
|
15571
15751
|
}
|
|
15572
|
-
|
|
15752
|
+
app45.options("/consent", (c) => {
|
|
15573
15753
|
const origin = getOrigin(c);
|
|
15574
15754
|
setCorsHeaders(c, origin);
|
|
15575
15755
|
return c.body(null, 204);
|
|
15576
15756
|
});
|
|
15577
|
-
|
|
15757
|
+
app45.options("/brand-config", (c) => {
|
|
15578
15758
|
const origin = getOrigin(c);
|
|
15579
15759
|
setCorsHeaders(c, origin);
|
|
15580
15760
|
return c.body(null, 204);
|
|
15581
15761
|
});
|
|
15582
|
-
|
|
15762
|
+
app45.post("/consent", async (c) => {
|
|
15583
15763
|
const origin = getOrigin(c);
|
|
15584
15764
|
setCorsHeaders(c, origin);
|
|
15585
15765
|
let raw;
|
|
@@ -15619,7 +15799,7 @@ app44.post("/consent", async (c) => {
|
|
|
15619
15799
|
console.log(`[consent] ${parsed.decision} site=${site} brand=${brandName} tokenBound=${tokenBound}`);
|
|
15620
15800
|
return c.body(null, 204);
|
|
15621
15801
|
});
|
|
15622
|
-
|
|
15802
|
+
app45.get("/brand-config", (c) => {
|
|
15623
15803
|
const origin = getOrigin(c);
|
|
15624
15804
|
setCorsHeaders(c, origin);
|
|
15625
15805
|
const brand = readBrandConfig();
|
|
@@ -15629,7 +15809,7 @@ app44.get("/brand-config", (c) => {
|
|
|
15629
15809
|
c.header("Cache-Control", "public, max-age=300");
|
|
15630
15810
|
return c.json({ consent: { copy, palette } });
|
|
15631
15811
|
});
|
|
15632
|
-
var visitor_consent_default =
|
|
15812
|
+
var visitor_consent_default = app45;
|
|
15633
15813
|
|
|
15634
15814
|
// server/routes/listings.ts
|
|
15635
15815
|
function getCookie(headerValue, name) {
|
|
@@ -15656,8 +15836,8 @@ function appendConsentParams(pageUrl, token) {
|
|
|
15656
15836
|
}
|
|
15657
15837
|
var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
|
|
15658
15838
|
var CHAT_SESSION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
|
|
15659
|
-
var
|
|
15660
|
-
|
|
15839
|
+
var app46 = new Hono();
|
|
15840
|
+
app46.get("/:slug/click", async (c) => {
|
|
15661
15841
|
const slug = c.req.param("slug") ?? "";
|
|
15662
15842
|
const rawSession = c.req.query("session") ?? "";
|
|
15663
15843
|
const sessionKey = CHAT_SESSION_KEY_RE.test(rawSession) ? rawSession : "invalid";
|
|
@@ -15721,10 +15901,10 @@ app45.get("/:slug/click", async (c) => {
|
|
|
15721
15901
|
console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} consent=${consentCookie ?? "absent"} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
15722
15902
|
return c.redirect(redirectUrl, 302);
|
|
15723
15903
|
});
|
|
15724
|
-
var listings_default =
|
|
15904
|
+
var listings_default = app46;
|
|
15725
15905
|
|
|
15726
15906
|
// server/routes/visitor-event.ts
|
|
15727
|
-
var
|
|
15907
|
+
var app47 = new Hono();
|
|
15728
15908
|
var BOT_UA_RE = /\b(bot|crawl|spider|slurp|headlesschrome|phantomjs|googlebot|bingbot|yandex|baiduspider|ahrefsbot|semrushbot|mj12bot|dotbot|petalbot)\b/i;
|
|
15729
15909
|
var buckets = /* @__PURE__ */ new Map();
|
|
15730
15910
|
var RATE_LIMIT = 60;
|
|
@@ -15791,12 +15971,12 @@ function originAllowed(origin, allowlist) {
|
|
|
15791
15971
|
return false;
|
|
15792
15972
|
}
|
|
15793
15973
|
}
|
|
15794
|
-
|
|
15974
|
+
app47.options("/event", (c) => {
|
|
15795
15975
|
const origin = getOrigin2(c);
|
|
15796
15976
|
setCorsHeaders2(c, origin);
|
|
15797
15977
|
return c.body(null, 204);
|
|
15798
15978
|
});
|
|
15799
|
-
|
|
15979
|
+
app47.post("/event", async (c) => {
|
|
15800
15980
|
const origin = getOrigin2(c);
|
|
15801
15981
|
setCorsHeaders2(c, origin);
|
|
15802
15982
|
const ua = c.req.header("user-agent") ?? "";
|
|
@@ -16001,7 +16181,7 @@ async function writeEvent(opts) {
|
|
|
16001
16181
|
);
|
|
16002
16182
|
}
|
|
16003
16183
|
}
|
|
16004
|
-
var visitor_event_default =
|
|
16184
|
+
var visitor_event_default = app47;
|
|
16005
16185
|
|
|
16006
16186
|
// server/routes/session.ts
|
|
16007
16187
|
import { resolve as resolve26 } from "path";
|
|
@@ -16047,8 +16227,8 @@ function withVisitorCookie(response, visitorId) {
|
|
|
16047
16227
|
headers
|
|
16048
16228
|
});
|
|
16049
16229
|
}
|
|
16050
|
-
var
|
|
16051
|
-
|
|
16230
|
+
var app48 = new Hono();
|
|
16231
|
+
app48.post("/", async (c) => {
|
|
16052
16232
|
let body;
|
|
16053
16233
|
try {
|
|
16054
16234
|
body = await c.req.json();
|
|
@@ -16259,7 +16439,7 @@ app47.post("/", async (c) => {
|
|
|
16259
16439
|
newVisitorId
|
|
16260
16440
|
);
|
|
16261
16441
|
});
|
|
16262
|
-
var session_default2 =
|
|
16442
|
+
var session_default2 = app48;
|
|
16263
16443
|
|
|
16264
16444
|
// app/lib/graph-health.ts
|
|
16265
16445
|
var import_dist4 = __toESM(require_dist3(), 1);
|
|
@@ -16749,8 +16929,8 @@ var streamSSE = (c, cb, onError) => {
|
|
|
16749
16929
|
|
|
16750
16930
|
// app/lib/whatsapp/gateway/routes.ts
|
|
16751
16931
|
function createWaChannelRoutes(deps) {
|
|
16752
|
-
const
|
|
16753
|
-
|
|
16932
|
+
const app50 = new Hono();
|
|
16933
|
+
app50.get("/wa-channel/inbound", (c) => {
|
|
16754
16934
|
const senderId = c.req.query("senderId");
|
|
16755
16935
|
if (!senderId) return c.json({ error: "senderId required" }, 400);
|
|
16756
16936
|
return streamSSE(c, async (stream2) => {
|
|
@@ -16768,7 +16948,7 @@ function createWaChannelRoutes(deps) {
|
|
|
16768
16948
|
}
|
|
16769
16949
|
});
|
|
16770
16950
|
});
|
|
16771
|
-
|
|
16951
|
+
app50.post("/wa-channel/reply", async (c) => {
|
|
16772
16952
|
const body = await c.req.json().catch(() => null);
|
|
16773
16953
|
const senderId = body?.senderId;
|
|
16774
16954
|
const text = body?.text;
|
|
@@ -16789,7 +16969,7 @@ function createWaChannelRoutes(deps) {
|
|
|
16789
16969
|
console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
|
|
16790
16970
|
return c.json({ ok: true });
|
|
16791
16971
|
});
|
|
16792
|
-
|
|
16972
|
+
app50.post("/wa-channel/reply-document", async (c) => {
|
|
16793
16973
|
const body = await c.req.json().catch(() => null);
|
|
16794
16974
|
const senderId = body?.senderId;
|
|
16795
16975
|
const files = body?.files;
|
|
@@ -16828,7 +17008,7 @@ function createWaChannelRoutes(deps) {
|
|
|
16828
17008
|
}
|
|
16829
17009
|
return c.json({ ok: true, results });
|
|
16830
17010
|
});
|
|
16831
|
-
|
|
17011
|
+
app50.post("/wa-channel/ready", async (c) => {
|
|
16832
17012
|
const body = await c.req.json().catch(() => null);
|
|
16833
17013
|
const senderId = body?.senderId;
|
|
16834
17014
|
if (typeof senderId !== "string") {
|
|
@@ -16837,7 +17017,7 @@ function createWaChannelRoutes(deps) {
|
|
|
16837
17017
|
deps.onReady?.(senderId);
|
|
16838
17018
|
return c.json({ ok: true });
|
|
16839
17019
|
});
|
|
16840
|
-
|
|
17020
|
+
app50.post("/wa-channel/received", async (c) => {
|
|
16841
17021
|
const body = await c.req.json().catch(() => null);
|
|
16842
17022
|
const senderId = body?.senderId;
|
|
16843
17023
|
const waMessageId = body?.waMessageId;
|
|
@@ -16847,7 +17027,7 @@ function createWaChannelRoutes(deps) {
|
|
|
16847
17027
|
deps.onReceived?.(senderId, waMessageId);
|
|
16848
17028
|
return c.json({ ok: true });
|
|
16849
17029
|
});
|
|
16850
|
-
|
|
17030
|
+
app50.post("/wa-channel/turn-end", async (c) => {
|
|
16851
17031
|
const body = await c.req.json().catch(() => null);
|
|
16852
17032
|
const senderId = body?.senderId;
|
|
16853
17033
|
const sessionId = body?.sessionId;
|
|
@@ -16878,7 +17058,7 @@ function createWaChannelRoutes(deps) {
|
|
|
16878
17058
|
console.error(`[whatsapp-native] op=turn-end sessionId=${sid} replied=no delivered=fallback bytes=${bytes}`);
|
|
16879
17059
|
return c.json({ ok: true, delivered: "fallback" });
|
|
16880
17060
|
});
|
|
16881
|
-
return
|
|
17061
|
+
return app50;
|
|
16882
17062
|
}
|
|
16883
17063
|
|
|
16884
17064
|
// app/lib/whatsapp/gateway/wa-gateway.ts
|
|
@@ -16967,6 +17147,8 @@ var WaGateway = class {
|
|
|
16967
17147
|
await this.deps.ensureChannelSession({
|
|
16968
17148
|
accountId: input.accountId,
|
|
16969
17149
|
senderId: input.senderId,
|
|
17150
|
+
role: input.role ?? "admin",
|
|
17151
|
+
personId: input.personId ?? null,
|
|
16970
17152
|
gatewayUrl: this.deps.gatewayUrl,
|
|
16971
17153
|
serverPath: this.deps.serverPath
|
|
16972
17154
|
});
|
|
@@ -16990,6 +17172,26 @@ var WaGateway = class {
|
|
|
16990
17172
|
}
|
|
16991
17173
|
};
|
|
16992
17174
|
|
|
17175
|
+
// app/lib/whatsapp/gateway/spawn-request.ts
|
|
17176
|
+
function buildWaSpawnRequest(input) {
|
|
17177
|
+
const personId = input.role === "public" && input.personId ? input.personId : void 0;
|
|
17178
|
+
return {
|
|
17179
|
+
sessionId: adminSessionIdFor(input.accountId, input.senderId, personId),
|
|
17180
|
+
role: input.role,
|
|
17181
|
+
channel: "whatsapp",
|
|
17182
|
+
personId,
|
|
17183
|
+
waChannel: { senderId: input.senderId, gatewayUrl: input.gatewayUrl, serverPath: input.serverPath },
|
|
17184
|
+
logContext: `channel=whatsapp-native role=${input.role} senderId=${input.senderId}`
|
|
17185
|
+
};
|
|
17186
|
+
}
|
|
17187
|
+
|
|
17188
|
+
// app/lib/whatsapp/inbound/route-decision.ts
|
|
17189
|
+
function decideWaRoute(msg) {
|
|
17190
|
+
if (msg.agentType === "admin") return { action: "route", role: "admin", personId: null };
|
|
17191
|
+
if (msg.personId && msg.personId.length > 0) return { action: "route", role: "public", personId: msg.personId };
|
|
17192
|
+
return { action: "drop", reason: "public-unresolved" };
|
|
17193
|
+
}
|
|
17194
|
+
|
|
16993
17195
|
// app/lib/webchat/gateway/inbound-hub.ts
|
|
16994
17196
|
var InboundHub2 = class {
|
|
16995
17197
|
keys = /* @__PURE__ */ new Map();
|
|
@@ -17109,8 +17311,8 @@ var InboundHub2 = class {
|
|
|
17109
17311
|
|
|
17110
17312
|
// app/lib/webchat/gateway/routes.ts
|
|
17111
17313
|
function createWebchatChannelRoutes(deps) {
|
|
17112
|
-
const
|
|
17113
|
-
|
|
17314
|
+
const app50 = new Hono();
|
|
17315
|
+
app50.get("/webchat-channel/inbound", (c) => {
|
|
17114
17316
|
const key = c.req.query("key");
|
|
17115
17317
|
if (!key) return c.json({ error: "key required" }, 400);
|
|
17116
17318
|
return streamSSE(c, async (stream2) => {
|
|
@@ -17128,7 +17330,7 @@ function createWebchatChannelRoutes(deps) {
|
|
|
17128
17330
|
}
|
|
17129
17331
|
});
|
|
17130
17332
|
});
|
|
17131
|
-
|
|
17333
|
+
app50.post("/webchat-channel/reply", async (c) => {
|
|
17132
17334
|
const body = await c.req.json().catch(() => null);
|
|
17133
17335
|
const key = body?.key;
|
|
17134
17336
|
const text = body?.text;
|
|
@@ -17138,7 +17340,7 @@ function createWebchatChannelRoutes(deps) {
|
|
|
17138
17340
|
deps.onReply?.(key, Buffer.byteLength(text, "utf8"));
|
|
17139
17341
|
return c.json({ ok: true });
|
|
17140
17342
|
});
|
|
17141
|
-
|
|
17343
|
+
app50.post("/webchat-channel/ready", async (c) => {
|
|
17142
17344
|
const body = await c.req.json().catch(() => null);
|
|
17143
17345
|
const key = body?.key;
|
|
17144
17346
|
if (typeof key !== "string") {
|
|
@@ -17147,7 +17349,7 @@ function createWebchatChannelRoutes(deps) {
|
|
|
17147
17349
|
deps.onReady?.(key);
|
|
17148
17350
|
return c.json({ ok: true });
|
|
17149
17351
|
});
|
|
17150
|
-
|
|
17352
|
+
app50.post("/webchat-channel/received", async (c) => {
|
|
17151
17353
|
const body = await c.req.json().catch(() => null);
|
|
17152
17354
|
const key = body?.key;
|
|
17153
17355
|
const messageId = body?.messageId;
|
|
@@ -17157,7 +17359,7 @@ function createWebchatChannelRoutes(deps) {
|
|
|
17157
17359
|
deps.onReceived?.(key, messageId);
|
|
17158
17360
|
return c.json({ ok: true });
|
|
17159
17361
|
});
|
|
17160
|
-
return
|
|
17362
|
+
return app50;
|
|
17161
17363
|
}
|
|
17162
17364
|
|
|
17163
17365
|
// app/lib/webchat/gateway/webchat-gateway.ts
|
|
@@ -17662,7 +17864,7 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
|
|
|
17662
17864
|
function isPublicHost(host) {
|
|
17663
17865
|
return host.startsWith("public.") || aliasDomains.has(host);
|
|
17664
17866
|
}
|
|
17665
|
-
var
|
|
17867
|
+
var app49 = new Hono();
|
|
17666
17868
|
var nativeFileFollowers = /* @__PURE__ */ new Map();
|
|
17667
17869
|
var waGateway = new WaGateway({
|
|
17668
17870
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
@@ -17686,29 +17888,23 @@ var waGateway = new WaGateway({
|
|
|
17686
17888
|
});
|
|
17687
17889
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
17688
17890
|
},
|
|
17689
|
-
ensureChannelSession: async ({ accountId, senderId, gatewayUrl, serverPath }) => {
|
|
17690
|
-
const
|
|
17691
|
-
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
// sidecar from the body, the same path every other admin channel uses.
|
|
17697
|
-
// senderId for the sidecar continues to come from the waChannel binding.
|
|
17698
|
-
role: "admin",
|
|
17699
|
-
channel: "whatsapp",
|
|
17700
|
-
waChannel: { senderId, gatewayUrl, serverPath },
|
|
17701
|
-
logContext: `channel=whatsapp-native senderId=${senderId}`
|
|
17702
|
-
});
|
|
17891
|
+
ensureChannelSession: async ({ accountId, senderId, role, personId, gatewayUrl, serverPath }) => {
|
|
17892
|
+
const req = buildWaSpawnRequest({ accountId, senderId, role, personId, gatewayUrl, serverPath });
|
|
17893
|
+
const userId = role === "admin" ? resolveAdminUserId({ accountId, senderPhone: senderId }) ?? void 0 : void 0;
|
|
17894
|
+
if (role === "admin") {
|
|
17895
|
+
console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
|
|
17896
|
+
}
|
|
17897
|
+
const result = await managerRcSpawn({ ...req, userId });
|
|
17703
17898
|
if ("error" in result) {
|
|
17704
|
-
console.error(`[whatsapp-native] spawn-failed senderId=${senderId} error=${result.error} status=${result.status}`);
|
|
17899
|
+
console.error(`[whatsapp-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
|
|
17705
17900
|
return;
|
|
17706
17901
|
}
|
|
17902
|
+
if (role !== "admin") return;
|
|
17707
17903
|
const prior = nativeFileFollowers.get(senderId);
|
|
17708
17904
|
if (prior) prior.abort();
|
|
17709
17905
|
let ac;
|
|
17710
17906
|
ac = startNativeFileFollower({
|
|
17711
|
-
sessionId:
|
|
17907
|
+
sessionId: req.sessionId,
|
|
17712
17908
|
senderId,
|
|
17713
17909
|
accountId,
|
|
17714
17910
|
onClose: () => {
|
|
@@ -17718,7 +17914,7 @@ var waGateway = new WaGateway({
|
|
|
17718
17914
|
nativeFileFollowers.set(senderId, ac);
|
|
17719
17915
|
}
|
|
17720
17916
|
});
|
|
17721
|
-
|
|
17917
|
+
app49.route("/", waGateway.routes());
|
|
17722
17918
|
waGateway.startSweeper();
|
|
17723
17919
|
var webchatGateway = new WebchatGateway({
|
|
17724
17920
|
gatewayUrl: webchatGatewayUrl(),
|
|
@@ -17741,10 +17937,10 @@ var webchatGateway = new WebchatGateway({
|
|
|
17741
17937
|
}
|
|
17742
17938
|
}
|
|
17743
17939
|
});
|
|
17744
|
-
|
|
17940
|
+
app49.route("/", webchatGateway.routes());
|
|
17745
17941
|
webchatGateway.startSweeper();
|
|
17746
|
-
|
|
17747
|
-
|
|
17942
|
+
app49.use("*", clientIpMiddleware);
|
|
17943
|
+
app49.use("*", async (c, next) => {
|
|
17748
17944
|
await next();
|
|
17749
17945
|
c.header("X-Content-Type-Options", "nosniff");
|
|
17750
17946
|
c.header("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
@@ -17754,7 +17950,7 @@ app48.use("*", async (c, next) => {
|
|
|
17754
17950
|
);
|
|
17755
17951
|
});
|
|
17756
17952
|
var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
|
|
17757
|
-
|
|
17953
|
+
app49.use("*", async (c, next) => {
|
|
17758
17954
|
if (!HTTP_LOG_PATHS.has(c.req.path)) {
|
|
17759
17955
|
await next();
|
|
17760
17956
|
return;
|
|
@@ -17772,7 +17968,7 @@ app48.use("*", async (c, next) => {
|
|
|
17772
17968
|
});
|
|
17773
17969
|
}
|
|
17774
17970
|
});
|
|
17775
|
-
|
|
17971
|
+
app49.use("*", async (c, next) => {
|
|
17776
17972
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
17777
17973
|
if (isOperatorHost(host, getOperatorDomains()) || !isPublicHost(host)) {
|
|
17778
17974
|
await next();
|
|
@@ -17803,7 +17999,7 @@ function resolveRemoteAuthOpts() {
|
|
|
17803
17999
|
return brandLoginOpts;
|
|
17804
18000
|
}
|
|
17805
18001
|
var MAX_LOGIN_BODY = 8 * 1024;
|
|
17806
|
-
|
|
18002
|
+
app49.post("/__remote-auth/login", async (c) => {
|
|
17807
18003
|
const client = clientFrom(c);
|
|
17808
18004
|
const clientIp = client.ip || "unknown";
|
|
17809
18005
|
if (!requestIsTlsTerminated(c)) {
|
|
@@ -17848,7 +18044,7 @@ app48.post("/__remote-auth/login", async (c) => {
|
|
|
17848
18044
|
}
|
|
17849
18045
|
});
|
|
17850
18046
|
});
|
|
17851
|
-
|
|
18047
|
+
app49.get("/__remote-auth/logout", (c) => {
|
|
17852
18048
|
const client = clientFrom(c);
|
|
17853
18049
|
const clientIp = client.ip || "unknown";
|
|
17854
18050
|
console.error(`[remote-auth] logout ip=${clientIp}`);
|
|
@@ -17861,7 +18057,7 @@ app48.get("/__remote-auth/logout", (c) => {
|
|
|
17861
18057
|
}
|
|
17862
18058
|
});
|
|
17863
18059
|
});
|
|
17864
|
-
|
|
18060
|
+
app49.post("/__remote-auth/change-password", async (c) => {
|
|
17865
18061
|
const client = clientFrom(c);
|
|
17866
18062
|
const clientIp = client.ip || "unknown";
|
|
17867
18063
|
const rateLimited = checkRateLimit(client);
|
|
@@ -17912,13 +18108,13 @@ app48.post("/__remote-auth/change-password", async (c) => {
|
|
|
17912
18108
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
|
|
17913
18109
|
}
|
|
17914
18110
|
});
|
|
17915
|
-
|
|
18111
|
+
app49.get("/__remote-auth/setup", (c) => {
|
|
17916
18112
|
if (isRemoteAuthConfigured()) {
|
|
17917
18113
|
return c.redirect("/");
|
|
17918
18114
|
}
|
|
17919
18115
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
|
|
17920
18116
|
});
|
|
17921
|
-
|
|
18117
|
+
app49.post("/__remote-auth/set-initial-password", async (c) => {
|
|
17922
18118
|
if (isRemoteAuthConfigured()) {
|
|
17923
18119
|
return c.redirect("/");
|
|
17924
18120
|
}
|
|
@@ -17956,10 +18152,10 @@ app48.post("/__remote-auth/set-initial-password", async (c) => {
|
|
|
17956
18152
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
|
|
17957
18153
|
}
|
|
17958
18154
|
});
|
|
17959
|
-
|
|
18155
|
+
app49.get("/api/remote-auth/status", (c) => {
|
|
17960
18156
|
return c.json({ configured: isRemoteAuthConfigured() });
|
|
17961
18157
|
});
|
|
17962
|
-
|
|
18158
|
+
app49.post("/api/remote-auth/set-password", async (c) => {
|
|
17963
18159
|
let body;
|
|
17964
18160
|
try {
|
|
17965
18161
|
body = await c.req.json();
|
|
@@ -17990,9 +18186,9 @@ app48.post("/api/remote-auth/set-password", async (c) => {
|
|
|
17990
18186
|
return c.json({ error: "Failed to save password" }, 500);
|
|
17991
18187
|
}
|
|
17992
18188
|
});
|
|
17993
|
-
|
|
18189
|
+
app49.route("/api/_client-error", client_error_default);
|
|
17994
18190
|
console.log("[client-error-route] mounted");
|
|
17995
|
-
|
|
18191
|
+
app49.use("*", async (c, next) => {
|
|
17996
18192
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
17997
18193
|
const path2 = c.req.path;
|
|
17998
18194
|
if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
|
|
@@ -18025,20 +18221,24 @@ app48.use("*", async (c, next) => {
|
|
|
18025
18221
|
console.error(`[remote-auth] not configured, redirecting to setup ip=${clientIp} path=${path2} ${disambig}`);
|
|
18026
18222
|
return c.redirect("/__remote-auth/setup");
|
|
18027
18223
|
}
|
|
18028
|
-
|
|
18224
|
+
const respond = isApiRequestPath(path2) ? "401" : "html";
|
|
18225
|
+
console.error(`[remote-auth] login required ip=${clientIp} path=${path2} respond=${respond} ${disambig}`);
|
|
18226
|
+
if (respond === "401") {
|
|
18227
|
+
return c.json({ code: "remote-auth-required" }, 401);
|
|
18228
|
+
}
|
|
18029
18229
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
|
|
18030
18230
|
});
|
|
18031
|
-
|
|
18032
|
-
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
|
|
18040
|
-
|
|
18041
|
-
|
|
18231
|
+
app49.route("/api/health", health_default);
|
|
18232
|
+
app49.route("/api/chat", chat_default);
|
|
18233
|
+
app49.route("/api/whatsapp", whatsapp_default);
|
|
18234
|
+
app49.route("/api/whatsapp-reader", whatsapp_reader_default);
|
|
18235
|
+
app49.route("/api/webchat", createWebchatRoutes({ handleInbound: (input) => webchatGateway.handleInbound(input) }));
|
|
18236
|
+
app49.route("/api/webchat/greeting", webchat_greeting_default);
|
|
18237
|
+
app49.route("/api/telegram", telegram_default);
|
|
18238
|
+
app49.route("/api/onboarding", onboarding_default);
|
|
18239
|
+
app49.route("/api/admin", admin_default);
|
|
18240
|
+
app49.route("/api/access", access_default);
|
|
18241
|
+
app49.route("/api/session", session_default2);
|
|
18042
18242
|
var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
|
|
18043
18243
|
var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
18044
18244
|
var IMAGE_MIME = {
|
|
@@ -18050,7 +18250,7 @@ var IMAGE_MIME = {
|
|
|
18050
18250
|
".svg": "image/svg+xml",
|
|
18051
18251
|
".ico": "image/x-icon"
|
|
18052
18252
|
};
|
|
18053
|
-
|
|
18253
|
+
app49.get("/agent-assets/:slug/:filename", (c) => {
|
|
18054
18254
|
const slug = c.req.param("slug");
|
|
18055
18255
|
const filename = c.req.param("filename");
|
|
18056
18256
|
if (!SAFE_SLUG_RE2.test(slug)) {
|
|
@@ -18085,7 +18285,7 @@ app48.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
18085
18285
|
"Cache-Control": "public, max-age=3600"
|
|
18086
18286
|
});
|
|
18087
18287
|
});
|
|
18088
|
-
|
|
18288
|
+
app49.get("/generated/:filename", (c) => {
|
|
18089
18289
|
const filename = c.req.param("filename");
|
|
18090
18290
|
if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
|
|
18091
18291
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
@@ -18115,10 +18315,10 @@ app48.get("/generated/:filename", (c) => {
|
|
|
18115
18315
|
"Cache-Control": "public, max-age=86400"
|
|
18116
18316
|
});
|
|
18117
18317
|
});
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18318
|
+
app49.route("/sites", sites_default);
|
|
18319
|
+
app49.route("/listings", listings_default);
|
|
18320
|
+
app49.route("/v", visitor_event_default);
|
|
18321
|
+
app49.route("/v", visitor_consent_default);
|
|
18122
18322
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
18123
18323
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
18124
18324
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
@@ -18249,7 +18449,7 @@ function escapeHtml(s) {
|
|
|
18249
18449
|
function agentUnavailableHtml() {
|
|
18250
18450
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(BRAND.productName)}</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1.5rem;color:#222"><h1 style="font-size:1.25rem">Agent unavailable</h1><p>This agent isn't available right now. If you reached this page from a saved link, the agent may have been turned off.</p></body></html>`;
|
|
18251
18451
|
}
|
|
18252
|
-
|
|
18452
|
+
app49.get("/", (c) => {
|
|
18253
18453
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18254
18454
|
const klass = classifyHost(host, getOperatorDomains(), isPublicHost);
|
|
18255
18455
|
if (klass === "operator") {
|
|
@@ -18271,12 +18471,12 @@ app48.get("/", (c) => {
|
|
|
18271
18471
|
console.log(`[host-class] host=${host} class=admin served=index.html`);
|
|
18272
18472
|
return c.html(cachedHtml("index.html"));
|
|
18273
18473
|
});
|
|
18274
|
-
|
|
18474
|
+
app49.get("/public", (c) => {
|
|
18275
18475
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18276
18476
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
18277
18477
|
return c.html(cachedHtml("public.html"));
|
|
18278
18478
|
});
|
|
18279
|
-
|
|
18479
|
+
app49.get("/public-chat", (c) => {
|
|
18280
18480
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18281
18481
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
18282
18482
|
return c.html(cachedHtml("public.html"));
|
|
@@ -18295,9 +18495,9 @@ async function logViewerFetch(c, next) {
|
|
|
18295
18495
|
duration_ms: Date.now() - start
|
|
18296
18496
|
});
|
|
18297
18497
|
}
|
|
18298
|
-
|
|
18299
|
-
|
|
18300
|
-
|
|
18498
|
+
app49.use("/vnc-viewer.html", logViewerFetch);
|
|
18499
|
+
app49.use("/vnc-popout.html", logViewerFetch);
|
|
18500
|
+
app49.get("/vnc-popout.html", (c) => {
|
|
18301
18501
|
let html = htmlCache.get("vnc-popout.html");
|
|
18302
18502
|
if (!html) {
|
|
18303
18503
|
html = readFileSync26(resolve29(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
@@ -18310,7 +18510,7 @@ app48.get("/vnc-popout.html", (c) => {
|
|
|
18310
18510
|
}
|
|
18311
18511
|
return c.html(html);
|
|
18312
18512
|
});
|
|
18313
|
-
|
|
18513
|
+
app49.post("/api/vnc/client-event", async (c) => {
|
|
18314
18514
|
let body;
|
|
18315
18515
|
try {
|
|
18316
18516
|
body = await c.req.json();
|
|
@@ -18331,30 +18531,30 @@ app48.post("/api/vnc/client-event", async (c) => {
|
|
|
18331
18531
|
});
|
|
18332
18532
|
return c.json({ ok: true });
|
|
18333
18533
|
});
|
|
18334
|
-
|
|
18534
|
+
app49.get("/g/:slug", (c) => {
|
|
18335
18535
|
return c.html(brandedPublicHtml());
|
|
18336
18536
|
});
|
|
18337
|
-
|
|
18537
|
+
app49.get("/graph", (c) => {
|
|
18338
18538
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18339
18539
|
if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
|
|
18340
18540
|
return c.html(cachedHtml("graph.html"));
|
|
18341
18541
|
});
|
|
18342
|
-
|
|
18542
|
+
app49.get("/chat", (c) => {
|
|
18343
18543
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18344
18544
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
18345
18545
|
return c.html(cachedHtml("chat.html"));
|
|
18346
18546
|
});
|
|
18347
|
-
|
|
18547
|
+
app49.get("/data", (c) => {
|
|
18348
18548
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18349
18549
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
18350
18550
|
return c.html(cachedHtml("data.html"));
|
|
18351
18551
|
});
|
|
18352
|
-
|
|
18552
|
+
app49.get("/browser", (c) => {
|
|
18353
18553
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18354
18554
|
if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
|
|
18355
18555
|
return c.html(cachedHtml("browser.html"));
|
|
18356
18556
|
});
|
|
18357
|
-
|
|
18557
|
+
app49.get("/:slug", async (c, next) => {
|
|
18358
18558
|
const slug = c.req.param("slug");
|
|
18359
18559
|
if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
|
|
18360
18560
|
const account = resolveAccount();
|
|
@@ -18369,13 +18569,13 @@ app48.get("/:slug", async (c, next) => {
|
|
|
18369
18569
|
await next();
|
|
18370
18570
|
});
|
|
18371
18571
|
if (brandFaviconPath !== "/favicon.ico") {
|
|
18372
|
-
|
|
18572
|
+
app49.get("/favicon.ico", (c) => {
|
|
18373
18573
|
c.header("Cache-Control", "public, max-age=300");
|
|
18374
18574
|
return c.redirect(brandFaviconPath, 302);
|
|
18375
18575
|
});
|
|
18376
18576
|
}
|
|
18377
|
-
|
|
18378
|
-
|
|
18577
|
+
app49.use("/*", serveStatic({ root: "./public" }));
|
|
18578
|
+
app49.all("*", (c) => {
|
|
18379
18579
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
18380
18580
|
const path2 = c.req.path;
|
|
18381
18581
|
if (isPublicHost(host)) {
|
|
@@ -18389,7 +18589,7 @@ app48.all("*", (c) => {
|
|
|
18389
18589
|
});
|
|
18390
18590
|
var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
|
|
18391
18591
|
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
18392
|
-
var httpServer = serve({ fetch:
|
|
18592
|
+
var httpServer = serve({ fetch: app49.fetch, port, hostname });
|
|
18393
18593
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
18394
18594
|
startAuthHealthHeartbeat();
|
|
18395
18595
|
{
|
|
@@ -18428,7 +18628,7 @@ for (const m of SUBAPP_MANIFEST) {
|
|
|
18428
18628
|
}
|
|
18429
18629
|
try {
|
|
18430
18630
|
const registered = [];
|
|
18431
|
-
for (const r of
|
|
18631
|
+
for (const r of app49.routes ?? []) {
|
|
18432
18632
|
if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
|
|
18433
18633
|
if (AGENT_SLUG_PATTERN.test(r.path)) {
|
|
18434
18634
|
registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
|
|
@@ -18577,8 +18777,9 @@ init({
|
|
|
18577
18777
|
console.error(`[whatsapp:route] skipped reason=owner-mirror senderId=${msg.senderPhone}`);
|
|
18578
18778
|
return;
|
|
18579
18779
|
}
|
|
18580
|
-
|
|
18581
|
-
|
|
18780
|
+
const decision = decideWaRoute(msg);
|
|
18781
|
+
if (decision.action === "drop") {
|
|
18782
|
+
console.error(`[whatsapp:route] op=dropped reason=${decision.reason} senderId=${msg.senderPhone}`);
|
|
18582
18783
|
return;
|
|
18583
18784
|
}
|
|
18584
18785
|
const hasFileMedia = msg.media.some((m) => m.type !== "audio");
|
|
@@ -18586,12 +18787,17 @@ init({
|
|
|
18586
18787
|
console.error(`[whatsapp:route] dropped reason=no-text-no-media senderId=${msg.senderPhone} mediaCount=${msg.media.length}`);
|
|
18587
18788
|
return;
|
|
18588
18789
|
}
|
|
18790
|
+
if (decision.role === "public") {
|
|
18791
|
+
console.error(`[whatsapp:route] op=routed agentType=public personId=${decision.personId} senderId=${msg.senderPhone}`);
|
|
18792
|
+
}
|
|
18589
18793
|
try {
|
|
18590
18794
|
void msg.composing().catch(() => {
|
|
18591
18795
|
});
|
|
18592
18796
|
await waGateway.handleInbound({
|
|
18593
18797
|
accountId: msg.accountId,
|
|
18594
18798
|
senderId: msg.senderPhone,
|
|
18799
|
+
role: decision.role,
|
|
18800
|
+
personId: decision.personId,
|
|
18595
18801
|
text: msg.text,
|
|
18596
18802
|
reply: msg.reply,
|
|
18597
18803
|
media: msg.media
|