@rubytech/create-maxy-code 0.1.352 → 0.1.354

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