@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.
Files changed (65) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +12 -0
  3. package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +10 -6
  4. package/payload/platform/plugins/admin/skills/access-manager/SKILL.md +1 -0
  5. package/payload/platform/plugins/admin/skills/access-manager/references/operations.md +22 -0
  6. package/payload/platform/plugins/docs/references/admin-session.md +2 -0
  7. package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +4 -4
  8. package/payload/platform/services/claude-session-manager/dist/channel-mcp.d.ts +15 -0
  9. package/payload/platform/services/claude-session-manager/dist/channel-mcp.d.ts.map +1 -0
  10. package/payload/platform/services/claude-session-manager/dist/channel-mcp.js +34 -0
  11. package/payload/platform/services/claude-session-manager/dist/channel-mcp.js.map +1 -0
  12. package/payload/platform/services/claude-session-manager/dist/channel-targets.d.ts +26 -0
  13. package/payload/platform/services/claude-session-manager/dist/channel-targets.d.ts.map +1 -0
  14. package/payload/platform/services/claude-session-manager/dist/channel-targets.js +19 -0
  15. package/payload/platform/services/claude-session-manager/dist/channel-targets.js.map +1 -0
  16. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  17. package/payload/platform/services/claude-session-manager/dist/http-server.js +80 -51
  18. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  19. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +15 -1
  20. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  21. package/payload/platform/services/whatsapp-channel/dist/notification.js +13 -1
  22. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  23. package/payload/platform/services/whatsapp-channel/dist/server.d.ts +8 -0
  24. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  25. package/payload/platform/services/whatsapp-channel/dist/server.js +183 -21
  26. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  27. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +82 -0
  28. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -0
  29. package/payload/platform/services/whatsapp-channel/dist/targets.js +131 -0
  30. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -0
  31. package/payload/server/{chunk-JHSF5VPE.js → chunk-TUY3RB5G.js} +4 -0
  32. package/payload/server/maxy-edge.js +2 -2
  33. package/payload/server/public/assets/AdminLoginScreens-BXKCagZn.js +1 -0
  34. package/payload/server/public/assets/{AdminShell-CXYu03dO.js → AdminShell-CxKd_V3O.js} +1 -1
  35. package/payload/server/public/assets/{Checkbox-D01p5NFD.js → Checkbox-BHmeAgrw.js} +1 -1
  36. package/payload/server/public/assets/Transcript-DPDJitcb.js +1 -0
  37. package/payload/server/public/assets/{admin-DdoI2RXi.js → admin-DHLuBUtk.js} +1 -1
  38. package/payload/server/public/assets/{audio-attachment-mime-WRXiXXUG.js → audio-attachment-mime-BxI3Kl4_.js} +1 -1
  39. package/payload/server/public/assets/{browser-Db0TaQdG.js → browser-DgtLMsM1.js} +1 -1
  40. package/payload/server/public/assets/chat-Ib78Kdhg.js +1 -0
  41. package/payload/server/public/assets/data-C317FUk4.js +1 -0
  42. package/payload/server/public/assets/file-download-DXFcVkh6.js +1 -0
  43. package/payload/server/public/assets/{graph-CWxi74vF.js → graph-CHf_9bmD.js} +2 -2
  44. package/payload/server/public/assets/{graph-labels-Bxt4mIUp.js → graph-labels-BbiX-sz-.js} +1 -1
  45. package/payload/server/public/assets/{jsx-runtime-BAMC3pMk.css → jsx-runtime-BkXvvpkS.css} +1 -1
  46. package/payload/server/public/assets/operator-BJ5tN5vl.js +1 -0
  47. package/payload/server/public/assets/page-CjuqiMAQ.js +1 -0
  48. package/payload/server/public/assets/{public-C3iO20f3.js → public-D3G4tgFu.js} +1 -1
  49. package/payload/server/public/assets/{useSelectionMode-DDpddXMY.js → useSelectionMode-DcgaoBP6.js} +1 -1
  50. package/payload/server/public/browser.html +6 -6
  51. package/payload/server/public/chat.html +9 -9
  52. package/payload/server/public/data.html +5 -5
  53. package/payload/server/public/graph.html +8 -8
  54. package/payload/server/public/index.html +9 -9
  55. package/payload/server/public/operator.html +11 -11
  56. package/payload/server/public/public.html +6 -6
  57. package/payload/server/server.js +776 -570
  58. package/payload/server/public/assets/AdminLoginScreens-B11_s63y.js +0 -1
  59. package/payload/server/public/assets/Transcript-NcTfeF7W.js +0 -1
  60. package/payload/server/public/assets/chat-BMTC0f2h.js +0 -1
  61. package/payload/server/public/assets/data-BiWVJQmi.js +0 -1
  62. package/payload/server/public/assets/file-download-7T2ye2nL.js +0 -1
  63. package/payload/server/public/assets/operator-Dw1Sep47.js +0 -1
  64. package/payload/server/public/assets/page-DZ5OxzgL.js +0 -1
  65. /package/payload/server/public/assets/{jsx-runtime-DhMPqcKn.js → jsx-runtime-BabLCDgS.js} +0 -0
@@ -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-JHSF5VPE.js";
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 readFileSync5 } from "fs";
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 readFileSync3 } from "fs";
2829
- import { resolve as resolve2 } from "path";
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 = resolve2(accountsDir, name, "account.json");
3134
+ const configPath2 = resolve3(accountsDir, name, "account.json");
2849
3135
  try {
2850
- JSON.parse(readFileSync3(configPath2, "utf-8"));
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 resolve2(root, "..", "data/accounts");
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 randomUUID2 } from "crypto";
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 = `${randomUUID2()}.${ext}`;
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 = checkDmAccess({
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 readFileSync4, writeFileSync as writeFileSync3 } from "fs";
4102
- import { resolve as resolve3 } from "path";
4103
- var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
4104
- var VNC_SCRIPT = resolve3(PLATFORM_ROOT2, "scripts/vnc.sh");
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 = readFileSync4(`/proc/${leaderPid}/environ`, "utf8");
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 resolve3(LOG_DIR, `${name}.log`);
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 = resolve3(BIN_DIR, "chromium");
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="${PLATFORM_ROOT2}/config/chromium-binary.path"
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 = readFileSync5(USERS_FILE, "utf-8").trim();
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 randomUUID3 } from "crypto";
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 resolve4, extname } from "path";
4480
- var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "../platform");
4481
- var ATTACHMENTS_ROOT = resolve4(PLATFORM_ROOT3, "..", "data/uploads");
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 = randomUUID3();
4508
- const dir = resolve4(ATTACHMENTS_ROOT, scope, attachmentId);
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 = resolve4(dir, `${attachmentId}${ext}`);
4512
- const metaPath = resolve4(dir, `${attachmentId}.meta.json`);
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 = resolve4(ATTACHMENTS_ROOT, accountId, attachmentId);
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 = resolve4(dir, `${attachmentId}${ext}`);
4590
- const metaPath = resolve4(dir, `${attachmentId}.meta.json`);
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 resolve6 } from "path";
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 b = createHash("sha256").update(`${accountId}:admin:${senderId}`).digest().subarray(0, 16);
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 randomUUID4 } from "crypto";
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 = randomUUID4();
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 resolve5, basename } from "path";
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 = resolve5(platformRoot2, "..", "data/accounts", maxyAccountId);
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 = resolve6(ATTACHMENTS_ROOT, "public", input.senderId);
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 randomUUID5 } from "crypto";
5917
+ import { randomUUID as randomUUID6 } from "crypto";
5625
5918
  var sessions = /* @__PURE__ */ new Map();
5626
5919
  function createAccessSession(input) {
5627
- const sessionId = randomUUID5();
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 resolve8 } from "path";
5892
- import { readdirSync as readdirSync3, readFileSync as readFileSync7, existsSync as existsSync5 } from "fs";
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 randomUUID6 } from "crypto";
6188
+ import { randomUUID as randomUUID7 } from "crypto";
5896
6189
 
5897
6190
  // app/lib/whatsapp/config-persist.ts
5898
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
5899
- import { resolve as resolve7, join as join5 } from "path";
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 resolve7(accountDir, "account.json");
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(readFileSync6(path2, "utf-8"));
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: randomUUID6(),
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 PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
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 = resolve8(account.accountDir, "agents");
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 = resolve8(agentsDir, entry.name, "config.json");
7007
+ const configPath2 = resolve9(agentsDir, entry.name, "config.json");
6715
7008
  if (!existsSync5(configPath2)) continue;
6716
7009
  try {
6717
- const config = JSON.parse(readFileSync7(configPath2, "utf-8"));
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: PLATFORM_ROOT4
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 readFileSync10, watch, statSync as statSync4, openSync, readSync, closeSync, existsSync as existsSync7, readdirSync as readdirSync6 } from "fs";
6987
- import { basename as basename2, dirname, join as join10, resolve as resolve10, sep as sep2 } from "path";
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 readFileSync8, statSync as statSync2 } from "fs";
6991
- import { join as join8, resolve as resolve9 } from "path";
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 = readFileSync8(path2, "utf8");
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 = readFileSync8(metaPath, "utf8");
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 ? resolve9(ACCOUNTS_DIR, accountId) : null;
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 = readFileSync8(path2, "utf8");
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 readFileSync9, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
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 = readFileSync9(usersFilePath, "utf-8").trim();
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(readFileSync9(accountJsonPath, "utf-8"));
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[^>]*>\n?([\s\S]*?)\n?<\/channel>$/;
7360
- function asString(content) {
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 prompt = att.prompt;
7431
- const m = prompt.match(CHANNEL_WRAPPER);
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 m = text.match(CHANNEL_WRAPPER);
7447
- out.push({ kind: "operator-inbound", text: m ? m[1] : text, ts });
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 = readFileSync10(path2, "utf8");
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 ? resolve10(join10(cfg, "projects")) : null;
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 = resolve10(join10(projectDir, `${sessionId}.jsonl`));
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 resolve10(account.accountDir, "logs", "prompt-optimiser-directives", sessionId);
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 = resolve10(join10(dir, name));
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 = readFileSync10(path2, "utf8");
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 readFileSync11, renameSync, writeFileSync as writeFileSync5 } from "fs";
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 app49 = new Hono();
7827
- app49.post("/send", requireAdminSession, async (c) => {
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
- app49.post("/settings", requireAdminSession, async (c) => {
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(readFileSync11(accountJsonPath, "utf8"));
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
- app49.get("/session", requireAdminSession, async (c) => {
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 app49;
8346
+ return app50;
8032
8347
  }
8033
8348
 
8034
8349
  // server/routes/webchat-greeting.ts
8035
- import { resolve as resolve11 } from "path";
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 readFileSync12, readdirSync as readdirSync8 } from "fs";
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 = readFileSync12(join12(specialistsDir, file), "utf-8");
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
- resolve11(ACCOUNTS_DIR, accountId, "specialists", "agents")
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 resolve12, join as join13 } from "path";
8176
- import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
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 ?? resolve12(process.cwd(), "..");
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(readFileSync13(brandPath, "utf-8")).configDir ?? ".maxy";
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 resolve12(homedir(), configDirName(), filename);
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 = readFileSync13(sp, "utf-8").trim();
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 readFileSync15, unlinkSync } from "fs";
8330
- import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
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 readFileSync14, writeFileSync as writeFileSync6, renameSync as renameSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync9, statSync as statSync5 } from "fs";
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 = readFileSync14(input.usersFile, "utf-8").trim();
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(readFileSync14(accountJsonPath, "utf-8"));
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 = readFileSync14(input.usersFile, "utf-8").trim();
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(readFileSync14(accountJsonPath, "utf-8"));
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 = readFileSync15(USERS_FILE, "utf-8").trim();
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 ?? randomUUID7();
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 = readFileSync15(INSTALL_OWNER_FILE, "utf-8").trim();
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 randomUUID8 } from "crypto";
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 = randomUUID8();
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 readFileSync16, statSync as statSync7 } from "fs";
9053
- import { resolve as resolve13, basename as basename4 } from "path";
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 ? resolve13(account.accountDir, "logs") : null;
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 = resolve13(dir, safe);
9403
+ const filePath = resolve14(dir, safe);
9089
9404
  searched.push(filePath);
9090
9405
  try {
9091
- const buffer = readFileSync16(filePath);
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 = readFileSync16(hit.path);
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(resolve13(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
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 = readFileSync16(resolve13(dir, name));
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 resolve14 } from "path";
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 = resolve14(ATTACHMENTS_ROOT, accountId, attachmentId);
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 = resolve14(dir, `${attachmentId}.meta.json`);
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 = resolve14(dir, dataFile);
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 resolve15 } from "path";
9288
- import { readdirSync as readdirSync11, readFileSync as readFileSync17, existsSync as existsSync17, rmSync } from "fs";
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 = resolve15(account.accountDir, "agents");
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 = resolve15(agentsDir, entry.name, "config.json");
9616
+ const configPath2 = resolve16(agentsDir, entry.name, "config.json");
9302
9617
  if (!existsSync17(configPath2)) continue;
9303
9618
  try {
9304
- const config = JSON.parse(readFileSync17(configPath2, "utf-8"));
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 = resolve15(account.accountDir, "agents", slug);
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 = resolve15(account.accountDir, "agents", slug);
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 readFileSync18, readdirSync as readdirSync12, statSync as statSync8 } from "fs";
10064
- import { dirname as dirname4, resolve as resolve16, join as join17 } from "path";
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 = readFileSync18(path2, "utf-8");
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 = resolve16(PLATFORM_ROOT, "plugins");
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 = resolve16(dirname4(PLATFORM_ROOT), "premium-plugins");
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 = resolve16(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
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(readFileSync18(accountFile, "utf-8"));
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 = readFileSync18(path2, "utf-8");
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 = resolve16(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
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 = resolve16(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
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 = readFileSync18(accountFile, "utf-8");
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 = resolve16(PLATFORM_ROOT, "plugins");
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 = resolve16(pluginsDir, name, "PLUGIN.md");
10639
+ const manifestPath = resolve17(pluginsDir, name, "PLUGIN.md");
10325
10640
  if (!existsSync19(manifestPath)) continue;
10326
10641
  let manifest;
10327
10642
  try {
10328
- manifest = readFileSync18(manifestPath, "utf-8");
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 readFileSync19 } from "fs";
10343
- import { resolve as resolve17 } from "path";
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(readFileSync19(statePath, "utf-8"));
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 ?? resolve17(process.cwd(), "..");
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(readFileSync19(resolve17(platformRoot2, "config", "brand.json"), "utf-8"));
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 TAG32 = "[admin:events]";
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(`${TAG32} reject reason=body-not-json detail=${detail}`);
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(`${TAG32} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
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 resolve20, sep as sep5 } from "path";
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 resolve18, normalize, sep as sep3, relative } from "path";
10613
- var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve18(process.cwd(), "../platform");
10614
- var DATA_ROOT = resolve18(PLATFORM_ROOT5, "..", "data");
10615
- var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve18(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
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 = resolve18(rootAbs, cleaned);
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 resolve19, relative as relative2, join as join18, basename as basename5, extname as extname2, sep as sep4 } from "path";
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
- resolve19(dataRoot, "uploads", accountId),
11296
- resolve19(dataRoot, "accounts", accountId)
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 = resolve19(dataRoot, relativePath);
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 = resolve20(DATA_ROOT, "accounts");
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 = resolve20(accountsDir, name, "account.json");
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 = resolve20(DATA_ROOT, "uploads", accountId);
11673
- const destPath = resolve20(destDir, finalName);
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 neo4j from "neo4j-driver";
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: neo4j.int(NEIGHBOURHOOD_LIMIT),
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: neo4j.int(MAX_CLUSTER_MESSAGES)
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 (neo4j.isDateTime(value) || neo4j.isDate(value) || neo4j.isLocalDateTime(value) || neo4j.isLocalTime(value) || neo4j.isTime(value) || neo4j.isDuration(value)) {
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 (neo4j.isInt(value)) {
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 resolve21, relative as relative3, isAbsolute, sep as sep6, basename as basename7 } from "path";
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 = resolve21(ACCOUNTS_DIR, accountId);
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 = resolve21(accountDir, "agents", "admin", filename);
13859
- const bundledPath = resolve21(PLATFORM_ROOT, "templates", "agents", "admin", filename);
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 = resolve21(accountDir, "specialists", "agents");
13873
- const bundledDir = resolve21(PLATFORM_ROOT, "templates", "specialists", "agents");
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 = resolve21(overrideDir, filename);
13877
- const bundledPath = resolve21(bundledDir, filename);
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 randomUUID9 } from "crypto";
14086
- function resolveSpawnPlan(host, operatorDomains, body, mintId = randomUUID9) {
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 = randomUUID9();
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 readFileSync20 } from "fs";
14292
- import { resolve as resolve22, join as join20 } from "path";
14293
- var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
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(PLATFORM_ROOT6, "config", "brand.json");
14610
+ var brandJsonPath = join20(PLATFORM_ROOT7, "config", "brand.json");
14296
14611
  if (existsSync22(brandJsonPath)) {
14297
14612
  try {
14298
- const brand = JSON.parse(readFileSync20(brandJsonPath, "utf-8"));
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 = resolve22(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
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 readFileSync20(VERSION_FILE, "utf-8").trim() || "unknown";
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 randomUUID10 } from "crypto";
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 = randomUUID10();
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 neo4j2 from "neo4j-driver";
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 (neo4j2.isDateTime(value) || neo4j2.isDate(value) || neo4j2.isLocalDateTime(value) || neo4j2.isLocalTime(value) || neo4j2.isTime(value) || neo4j2.isDuration(value)) {
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 (neo4j2.isInt(value)) {
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 neo4j3 from "neo4j-driver";
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 (neo4j3.isDateTime(value) || neo4j3.isDate(value) || neo4j3.isLocalDateTime(value) || neo4j3.isLocalTime(value) || neo4j3.isTime(value) || neo4j3.isDuration(value)) {
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 (neo4j3.isInt(value)) {
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/browser.ts
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("/launch", requireAdminSession, async (c) => {
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 = app38;
15199
+ var browser_default = app39;
14813
15200
 
14814
15201
  // server/routes/admin/index.ts
14815
- var app39 = new Hono();
14816
- app39.route("/session", session_default);
14817
- app39.route("/logs", logs_default);
14818
- app39.route("/claude-info", claude_info_default);
14819
- app39.route("/attachment", attachment_default);
14820
- app39.route("/agents", agents_default);
14821
- app39.route("/sessions", sessions_default);
14822
- app39.route("/claude-sessions", claude_sessions_default);
14823
- app39.route("/log-ingest", log_ingest_default);
14824
- app39.route("/events", events_default);
14825
- app39.route("/files", files_default);
14826
- app39.route("/graph-search", graph_search_default);
14827
- app39.route("/graph-subgraph", graph_subgraph_default);
14828
- app39.route("/graph-delete", graph_delete_default);
14829
- app39.route("/graph-restore", graph_restore_default);
14830
- app39.route("/graph-labels-in-graph", graph_labels_in_graph_default);
14831
- app39.route("/graph-default-view", graph_default_view_default);
14832
- app39.route("/sidebar-artefacts", sidebar_artefacts_default);
14833
- app39.route("/sidebar-sessions", sidebar_sessions_default);
14834
- app39.route("/session-delete", session_delete_default);
14835
- app39.route("/session-stop", session_stop_default);
14836
- app39.route("/session-archive", session_archive_default);
14837
- app39.route("/browser", browser_default);
14838
- app39.route("/session-rc-spawn", session_rc_spawn_default);
14839
- app39.route("/system-stats", system_stats_default);
14840
- app39.route("/health-brand", health_default2);
14841
- app39.route("/linkedin-ingest", linkedin_ingest_default);
14842
- app39.route("/post-turn-context", post_turn_context_default);
14843
- app39.route("/public-session-context", public_session_context_default);
14844
- app39.route("/public-session-exit", public_session_exit_default);
14845
- app39.route("/access-session-evict", access_session_evict_default);
14846
- var admin_default = app39;
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 TAG30 = "[access-verify]";
15237
+ var TAG31 = "[access-verify]";
15058
15238
  var MINT_TAG = "[access-session-mint]";
15059
15239
  var COOKIE_NAME = "__access_session";
15060
- var app40 = new Hono();
15061
- app40.post("/", async (c) => {
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(`${TAG30} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
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(`${TAG30} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
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
- `${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
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
- `${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
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
- `${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
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
- `${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
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
- `${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
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(`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
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 = app40;
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 TAG31 = "[access-request-link]";
15206
- var app41 = new Hono();
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
- app41.post("/", async (c) => {
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(`${TAG31} contactValue=${maskContact(contactValue)} result=rate-limited`);
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(`${TAG31} contactValue=${maskContact(contactValue)} result=no-account-id`);
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(`${TAG31} contactValue=${maskContact(contactValue)} result=notfound`);
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
- `${TAG31} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
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
- `${TAG31} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
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
- `${TAG31} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
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 = app41;
15462
+ var request_magic_link_default = app42;
15283
15463
 
15284
15464
  // server/routes/access/index.ts
15285
- var app42 = new Hono();
15286
- app42.route("/verify-token", verify_token_default);
15287
- app42.route("/request-magic-link", request_magic_link_default);
15288
- var access_default = app42;
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 app43 = new Hono();
15324
- app43.get("/:rel{.*}", (c) => {
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 = app43;
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 app44 = new Hono();
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
- app44.options("/consent", (c) => {
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
- app44.options("/brand-config", (c) => {
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
- app44.post("/consent", async (c) => {
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
- app44.get("/brand-config", (c) => {
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 = app44;
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 app45 = new Hono();
15660
- app45.get("/:slug/click", async (c) => {
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 = app45;
15904
+ var listings_default = app46;
15725
15905
 
15726
15906
  // server/routes/visitor-event.ts
15727
- var app46 = new Hono();
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
- app46.options("/event", (c) => {
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
- app46.post("/event", async (c) => {
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 = app46;
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 app47 = new Hono();
16051
- app47.post("/", async (c) => {
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 = app47;
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 app49 = new Hono();
16753
- app49.get("/wa-channel/inbound", (c) => {
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
- app49.post("/wa-channel/reply", async (c) => {
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
- app49.post("/wa-channel/reply-document", async (c) => {
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
- app49.post("/wa-channel/ready", async (c) => {
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
- app49.post("/wa-channel/received", async (c) => {
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
- app49.post("/wa-channel/turn-end", async (c) => {
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 app49;
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 app49 = new Hono();
17113
- app49.get("/webchat-channel/inbound", (c) => {
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
- app49.post("/webchat-channel/reply", async (c) => {
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
- app49.post("/webchat-channel/ready", async (c) => {
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
- app49.post("/webchat-channel/received", async (c) => {
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 app49;
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 app48 = new Hono();
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 userId = resolveAdminUserId({ accountId, senderPhone: senderId }) ?? void 0;
17691
- console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
17692
- const result = await managerRcSpawn({
17693
- sessionId: adminSessionIdFor(accountId, senderId),
17694
- userId,
17695
- // Task 724 supply the channel so /rc-spawn writes the classification
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: adminSessionIdFor(accountId, senderId),
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
- app48.route("/", waGateway.routes());
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
- app48.route("/", webchatGateway.routes());
17940
+ app49.route("/", webchatGateway.routes());
17745
17941
  webchatGateway.startSweeper();
17746
- app48.use("*", clientIpMiddleware);
17747
- app48.use("*", async (c, next) => {
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
- app48.use("*", async (c, next) => {
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
- app48.use("*", async (c, next) => {
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
- app48.post("/__remote-auth/login", async (c) => {
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
- app48.get("/__remote-auth/logout", (c) => {
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
- app48.post("/__remote-auth/change-password", async (c) => {
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
- app48.get("/__remote-auth/setup", (c) => {
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
- app48.post("/__remote-auth/set-initial-password", async (c) => {
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
- app48.get("/api/remote-auth/status", (c) => {
18155
+ app49.get("/api/remote-auth/status", (c) => {
17960
18156
  return c.json({ configured: isRemoteAuthConfigured() });
17961
18157
  });
17962
- app48.post("/api/remote-auth/set-password", async (c) => {
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
- app48.route("/api/_client-error", client_error_default);
18189
+ app49.route("/api/_client-error", client_error_default);
17994
18190
  console.log("[client-error-route] mounted");
17995
- app48.use("*", async (c, next) => {
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
- console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
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
- app48.route("/api/health", health_default);
18032
- app48.route("/api/chat", chat_default);
18033
- app48.route("/api/whatsapp", whatsapp_default);
18034
- app48.route("/api/whatsapp-reader", whatsapp_reader_default);
18035
- app48.route("/api/webchat", createWebchatRoutes({ handleInbound: (input) => webchatGateway.handleInbound(input) }));
18036
- app48.route("/api/webchat/greeting", webchat_greeting_default);
18037
- app48.route("/api/telegram", telegram_default);
18038
- app48.route("/api/onboarding", onboarding_default);
18039
- app48.route("/api/admin", admin_default);
18040
- app48.route("/api/access", access_default);
18041
- app48.route("/api/session", session_default2);
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
- app48.get("/agent-assets/:slug/:filename", (c) => {
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
- app48.get("/generated/:filename", (c) => {
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
- app48.route("/sites", sites_default);
18119
- app48.route("/listings", listings_default);
18120
- app48.route("/v", visitor_event_default);
18121
- app48.route("/v", visitor_consent_default);
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
- app48.get("/", (c) => {
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
- app48.get("/public", (c) => {
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
- app48.get("/public-chat", (c) => {
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
- app48.use("/vnc-viewer.html", logViewerFetch);
18299
- app48.use("/vnc-popout.html", logViewerFetch);
18300
- app48.get("/vnc-popout.html", (c) => {
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
- app48.post("/api/vnc/client-event", async (c) => {
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
- app48.get("/g/:slug", (c) => {
18534
+ app49.get("/g/:slug", (c) => {
18335
18535
  return c.html(brandedPublicHtml());
18336
18536
  });
18337
- app48.get("/graph", (c) => {
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
- app48.get("/chat", (c) => {
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
- app48.get("/data", (c) => {
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
- app48.get("/browser", (c) => {
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
- app48.get("/:slug", async (c, next) => {
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
- app48.get("/favicon.ico", (c) => {
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
- app48.use("/*", serveStatic({ root: "./public" }));
18378
- app48.all("*", (c) => {
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: app48.fetch, port, hostname });
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 app48.routes ?? []) {
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
- if (msg.agentType !== "admin") {
18581
- console.error(`[whatsapp:route] dropped reason=non-admin-sender senderId=${msg.senderPhone} agentType=${msg.agentType}`);
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