@rubytech/create-maxy-code 0.1.281 → 0.1.283

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 (76) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +0 -1
  3. package/payload/platform/plugins/admin/PLUGIN.md +2 -1
  4. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +154 -0
  5. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +24 -0
  6. package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +174 -0
  7. package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +22 -2
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -5
  9. package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +378 -56
  10. package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
  11. package/payload/platform/plugins/cloudflare/PLUGIN.md +5 -5
  12. package/payload/platform/plugins/cloudflare/references/api.md +107 -34
  13. package/payload/platform/plugins/cloudflare/references/d1-data-capture.md +29 -21
  14. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -2
  15. package/payload/platform/plugins/cloudflare/references/hosting-sites.md +7 -5
  16. package/payload/platform/plugins/cloudflare/references/manual-setup.md +3 -2
  17. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +6 -6
  18. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  19. package/payload/platform/plugins/docs/references/telegram-guide.md +3 -3
  20. package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js +25 -1
  21. package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js.map +1 -1
  22. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts +7 -0
  23. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts.map +1 -1
  24. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js +9 -0
  25. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js.map +1 -1
  26. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.d.ts.map +1 -1
  27. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js +6 -2
  28. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js.map +1 -1
  29. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +1 -0
  30. package/payload/platform/plugins/telegram/PLUGIN.md +7 -2
  31. package/payload/platform/plugins/telegram/mcp/dist/index.js +66 -11
  32. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  33. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +6 -0
  34. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -0
  35. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +20 -0
  36. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -0
  37. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +21 -0
  38. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -0
  39. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +52 -0
  40. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -0
  41. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -1
  42. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +5 -2
  43. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -1
  44. package/payload/platform/plugins/telegram/references/setup-guide.md +10 -7
  45. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +79 -0
  46. package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +1 -1
  47. package/payload/platform/plugins/whatsapp/PLUGIN.md +7 -11
  48. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +10 -56
  49. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  50. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +26 -2
  51. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +9 -14
  52. package/payload/platform/scripts/setup-account.sh +7 -0
  53. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  54. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -2
  55. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  56. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  57. package/payload/platform/services/claude-session-manager/dist/http-server.js +80 -3
  58. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  59. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +6 -0
  60. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  61. package/payload/platform/services/whatsapp-channel/dist/notification.js +14 -0
  62. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  63. package/payload/platform/services/whatsapp-channel/dist/server.js +5 -3
  64. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  65. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  66. package/payload/server/public/assets/AdminShell-8Y7-maNi.js +1 -0
  67. package/payload/server/public/assets/{admin-0_VxffD5.js → admin-DyuZ4NT5.js} +1 -1
  68. package/payload/server/public/assets/{browser-Cxjkhtsk.js → browser-XrVAUQdG.js} +1 -1
  69. package/payload/server/public/assets/{data-BOgwOWnw.js → data-tcu3MXmK.js} +1 -1
  70. package/payload/server/public/assets/{graph-BqD2GjQd.js → graph-I4GyC2MW.js} +1 -1
  71. package/payload/server/public/browser.html +2 -2
  72. package/payload/server/public/data.html +2 -2
  73. package/payload/server/public/graph.html +2 -2
  74. package/payload/server/public/index.html +2 -2
  75. package/payload/server/server.js +882 -767
  76. package/payload/server/public/assets/AdminShell-FO766lOW.js +0 -1
@@ -1201,9 +1201,9 @@ var serveStatic = (options = { root: "" }) => {
1201
1201
  };
1202
1202
 
1203
1203
  // server/index.ts
1204
- import { readFileSync as readFileSync23, existsSync as existsSync23, watchFile } from "fs";
1205
- import { resolve as resolve26, join as join18, basename as basename7 } from "path";
1206
- import { homedir as homedir2 } from "os";
1204
+ import { readFileSync as readFileSync24, existsSync as existsSync24, watchFile } from "fs";
1205
+ import { resolve as resolve27, join as join19, basename as basename7 } from "path";
1206
+ import { homedir as homedir3 } from "os";
1207
1207
  import { monitorEventLoopDelay } from "perf_hooks";
1208
1208
 
1209
1209
  // app/lib/agent-slug-pattern.ts
@@ -1513,24 +1513,14 @@ function getLanIp() {
1513
1513
  // app/lib/whatsapp/schema.ts
1514
1514
  import { z } from "zod";
1515
1515
  var DmPolicySchema = z.enum(["open", "allowlist", "disabled"]);
1516
- var GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
1517
1516
  var WhatsAppAccountSchema = z.object({
1518
1517
  name: z.string().optional().describe("Display name for this WhatsApp account (e.g. 'Main', 'Sales')."),
1519
1518
  enabled: z.boolean().optional().describe("If disabled, this account won't connect on startup."),
1520
1519
  dmPolicy: DmPolicySchema.optional().describe("Who can message your public agent via WhatsApp DM. 'Open' lets anyone message. 'Allowlist' restricts to approved phone numbers. 'Disabled' blocks all public messages."),
1521
- groupPolicy: GroupPolicySchema.optional().describe("Whether your agent responds in WhatsApp group chats. 'Open' enables group responses (subject to per-group activation). 'Allowlist' restricts to approved senders. 'Disabled' ignores all group messages."),
1522
1520
  selfChatMode: z.boolean().optional().describe("Enable if the bot uses the owner's personal WhatsApp number (same phone for admin and bot). Changes how self-messages are routed."),
1523
1521
  adminPhones: z.array(z.string()).optional().describe("Phone numbers (E.164) that route to the admin agent. Managed via add-admin-phone / remove-admin-phone."),
1524
1522
  allowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to message the public agent when DM policy is 'allowlist'. Add '*' for open access. Does not affect admin routing \u2014 use adminPhones for that."),
1525
- groupAllowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to trigger the agent in groups when group policy is 'allowlist'."),
1526
1523
  sendReadReceipts: z.boolean().optional().default(true).describe("Whether to send read receipts (blue ticks) when messages are received. Disabling may feel less responsive but preserves privacy."),
1527
- groups: z.record(
1528
- z.string(),
1529
- z.object({
1530
- activation: z.enum(["always", "mention", "off"]).optional(),
1531
- publicAgent: z.string().optional().describe("Per-group public-agent slug override. When set, inbound messages in this group route to this agent instead of the per-account or top-level publicAgent.")
1532
- }).strict().optional()
1533
- ).optional().describe("Per-group settings: 'activation' controls when the agent responds ('always', 'mention', 'off'); 'publicAgent' optionally overrides the public-agent slug for this group (precedence: group \u2192 account \u2192 top-level)."),
1534
1524
  ackReaction: z.object({
1535
1525
  emoji: z.string().optional(),
1536
1526
  direct: z.boolean().optional().default(true),
@@ -1552,10 +1542,8 @@ var WhatsAppAccountSchema = z.object({
1552
1542
  var WhatsAppConfigSchema = z.object({
1553
1543
  accounts: z.record(z.string(), WhatsAppAccountSchema.optional()).optional().describe("Per-account config keyed by account ID."),
1554
1544
  dmPolicy: DmPolicySchema.optional().default("disabled").describe("Who can message your public agent via WhatsApp DM. 'Open' lets anyone message. 'Allowlist' restricts to approved phone numbers. 'Disabled' blocks all public messages."),
1555
- groupPolicy: GroupPolicySchema.optional().default("disabled").describe("Whether your agent responds in WhatsApp group chats. 'Open' enables group responses (subject to per-group activation). 'Allowlist' restricts to approved senders. 'Disabled' ignores all group messages."),
1556
1545
  adminPhones: z.array(z.string()).optional().describe("Phone numbers (E.164) that route to the admin agent. Managed via add-admin-phone / remove-admin-phone."),
1557
1546
  allowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to message the public agent when DM policy is 'allowlist'. Add '*' for open access. Does not affect admin routing \u2014 use adminPhones for that."),
1558
- groupAllowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to trigger the agent in groups when group policy is 'allowlist'."),
1559
1547
  sendReadReceipts: z.boolean().optional().default(true).describe("Whether to send read receipts (blue ticks) when messages are received. Disabling may feel less responsive but preserves privacy."),
1560
1548
  mediaMaxMb: z.number().int().positive().optional().default(50).describe("Maximum file size in MB for media the agent will download and process."),
1561
1549
  textChunkLimit: z.number().int().positive().optional().default(4e3).describe("Maximum characters per outbound message. Longer replies are split into multiple messages."),
@@ -1572,6 +1560,36 @@ var WhatsAppConfigSchema = z.object({
1572
1560
  message: 'dmPolicy="open" requires allowFrom to include "*"'
1573
1561
  });
1574
1562
  });
1563
+ function parseWhatsAppConfigTolerant(raw) {
1564
+ const first = WhatsAppConfigSchema.safeParse(raw);
1565
+ if (first.success) {
1566
+ return { ok: true, data: first.data, droppedKeys: [] };
1567
+ }
1568
+ const issues = first.error.issues;
1569
+ const onlyUnknownKeys = issues.length > 0 && issues.every((i) => i.code === "unrecognized_keys");
1570
+ if (!onlyUnknownKeys) {
1571
+ return { ok: false, error: first.error };
1572
+ }
1573
+ const clone = JSON.parse(JSON.stringify(raw));
1574
+ const droppedKeys = [];
1575
+ for (const issue of issues) {
1576
+ if (issue.code !== "unrecognized_keys") continue;
1577
+ const container = issue.path.reduce(
1578
+ (acc, seg) => acc && typeof acc === "object" ? acc[seg] : void 0,
1579
+ clone
1580
+ );
1581
+ if (!container || typeof container !== "object") continue;
1582
+ for (const key of issue.keys) {
1583
+ delete container[key];
1584
+ droppedKeys.push([...issue.path, key].join("."));
1585
+ }
1586
+ }
1587
+ const second = WhatsAppConfigSchema.safeParse(clone);
1588
+ if (second.success) {
1589
+ return { ok: true, data: second.data, droppedKeys };
1590
+ }
1591
+ return { ok: false, error: second.error };
1592
+ }
1575
1593
 
1576
1594
  // app/lib/whatsapp/auth-store.ts
1577
1595
  import fsSync from "fs";
@@ -1763,7 +1781,7 @@ var credsSaveQueue = Promise.resolve();
1763
1781
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
1764
1782
  console.error(`${TAG2} draining credential save queue\u2026`);
1765
1783
  const timer2 = new Promise(
1766
- (resolve27) => setTimeout(() => resolve27("timeout"), timeoutMs)
1784
+ (resolve28) => setTimeout(() => resolve28("timeout"), timeoutMs)
1767
1785
  );
1768
1786
  const result = await Promise.race([
1769
1787
  credsSaveQueue.then(() => "drained"),
@@ -1891,11 +1909,11 @@ async function createWaSocket(opts) {
1891
1909
  return sock;
1892
1910
  }
1893
1911
  async function waitForConnection(sock) {
1894
- return new Promise((resolve27, reject) => {
1912
+ return new Promise((resolve28, reject) => {
1895
1913
  const handler = (update) => {
1896
1914
  if (update.connection === "open") {
1897
1915
  sock.ev.off("connection.update", handler);
1898
- resolve27();
1916
+ resolve28();
1899
1917
  }
1900
1918
  if (update.connection === "close") {
1901
1919
  sock.ev.off("connection.update", handler);
@@ -2009,14 +2027,14 @@ ${inspected}`;
2009
2027
  return inspect2(err, INSPECT_OPTS2);
2010
2028
  }
2011
2029
  function withTimeout(label, promise, timeoutMs) {
2012
- return new Promise((resolve27, reject) => {
2030
+ return new Promise((resolve28, reject) => {
2013
2031
  const timer2 = setTimeout(() => {
2014
2032
  reject(new Error(`${label} timed out after ${timeoutMs}ms`));
2015
2033
  }, timeoutMs);
2016
2034
  promise.then(
2017
2035
  (value) => {
2018
2036
  clearTimeout(timer2);
2019
- resolve27(value);
2037
+ resolve28(value);
2020
2038
  },
2021
2039
  (err) => {
2022
2040
  clearTimeout(timer2);
@@ -2321,10 +2339,8 @@ function extractMessage(msg) {
2321
2339
  function resolveAccountConfig(config, accountConfig) {
2322
2340
  return {
2323
2341
  dmPolicy: accountConfig?.dmPolicy ?? config.dmPolicy ?? "disabled",
2324
- groupPolicy: accountConfig?.groupPolicy ?? config.groupPolicy ?? "disabled",
2325
2342
  adminPhones: accountConfig?.adminPhones ?? config.adminPhones ?? [],
2326
2343
  allowFrom: accountConfig?.allowFrom ?? config.allowFrom ?? [],
2327
- groupAllowFrom: accountConfig?.groupAllowFrom ?? config.groupAllowFrom ?? [],
2328
2344
  selfChatMode: accountConfig?.selfChatMode ?? false
2329
2345
  };
2330
2346
  }
@@ -2361,36 +2377,8 @@ function checkDmAccess(params) {
2361
2377
  return { allowed: false, reason: "unknown-dm-policy", agentType: "public" };
2362
2378
  }
2363
2379
  }
2364
- function checkGroupAccess(params) {
2365
- const { senderPhone, selfPhone, groupJid, isMentioned } = params;
2366
- const cfg = resolveAccountConfig(params.config, params.accountConfig);
2367
- if (phonesMatch(senderPhone, selfPhone)) {
2368
- return { allowed: true, reason: "self-phone-group", agentType: "admin" };
2369
- }
2370
- switch (cfg.groupPolicy) {
2371
- case "open": {
2372
- const groupConfig = params.accountConfig?.groups?.[groupJid];
2373
- const activation = groupConfig?.activation ?? "mention";
2374
- if (activation === "off") {
2375
- return { allowed: false, reason: "group-activation-off", agentType: "public" };
2376
- }
2377
- if (activation === "mention" && !isMentioned) {
2378
- return { allowed: false, reason: "mention-gating", agentType: "public" };
2379
- }
2380
- return { allowed: true, reason: "group-policy-open", agentType: "public" };
2381
- }
2382
- case "allowlist": {
2383
- const inList = cfg.groupAllowFrom.some((entry) => phonesMatch(entry, senderPhone)) || cfg.adminPhones.some((entry) => phonesMatch(entry, senderPhone));
2384
- if (!inList) {
2385
- return { allowed: false, reason: "not-in-group-allowlist", agentType: "public" };
2386
- }
2387
- return { allowed: true, reason: "group-allowlist-match", agentType: "public" };
2388
- }
2389
- case "disabled":
2390
- return { allowed: false, reason: "group-policy-disabled", agentType: "public" };
2391
- default:
2392
- return { allowed: false, reason: "unknown-group-policy", agentType: "public" };
2393
- }
2380
+ function checkGroupAccess(_params) {
2381
+ return { allowed: false, reason: "group-observe-only", agentType: "public" };
2394
2382
  }
2395
2383
 
2396
2384
  // app/lib/whatsapp/inbound/dedupe.ts
@@ -2601,8 +2589,8 @@ async function persistWhatsAppMessage(input) {
2601
2589
  const { givenName, familyName } = splitName(input.pushName);
2602
2590
  const prev = sessionWriteLocks.get(input.cacheKey);
2603
2591
  let release;
2604
- const mine = new Promise((resolve27) => {
2605
- release = resolve27;
2592
+ const mine = new Promise((resolve28) => {
2593
+ release = resolve28;
2606
2594
  });
2607
2595
  const chained = (prev ?? Promise.resolve()).then(() => mine);
2608
2596
  sessionWriteLocks.set(input.cacheKey, chained);
@@ -3275,11 +3263,20 @@ async function transcribe(audioPath, mimetype) {
3275
3263
  }
3276
3264
  }
3277
3265
 
3266
+ // app/lib/whatsapp/reply-error.ts
3267
+ var WaReplyError = class extends Error {
3268
+ terminal;
3269
+ constructor(message, terminal) {
3270
+ super(message);
3271
+ this.name = "WaReplyError";
3272
+ this.terminal = terminal;
3273
+ }
3274
+ };
3275
+
3278
3276
  // app/lib/whatsapp/manager.ts
3279
3277
  var TAG12 = "[whatsapp:manager]";
3280
3278
  var MAX_RECONNECT_ATTEMPTS = 10;
3281
3279
  var MESSAGE_STORE_MAX = 500;
3282
- var GROUP_META_TTL = 5 * 60 * 1e3;
3283
3280
  var connections = /* @__PURE__ */ new Map();
3284
3281
  var configDir = null;
3285
3282
  var whatsAppConfig = {};
@@ -3375,8 +3372,7 @@ async function startConnection(accountId) {
3375
3372
  reconnectAttempts: 0,
3376
3373
  abortController: new AbortController(),
3377
3374
  debouncer: null,
3378
- lidMapping: null,
3379
- groupMetaCache: /* @__PURE__ */ new Map()
3375
+ lidMapping: null
3380
3376
  };
3381
3377
  connections.set(accountId, conn);
3382
3378
  await connectWithReconnect(conn);
@@ -3412,7 +3408,8 @@ function getStatus() {
3412
3408
  reconnectAttempts: conn.reconnectAttempts,
3413
3409
  lastConnectedAt: conn.lastConnectedAt,
3414
3410
  lastError: conn.lastError,
3415
- sessionStuckReason: conn.sessionStuckReason
3411
+ sessionStuckReason: conn.sessionStuckReason,
3412
+ terminalReason: conn.terminalReason
3416
3413
  }));
3417
3414
  }
3418
3415
  function getSocket(accountId) {
@@ -3456,8 +3453,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
3456
3453
  lastConnectedAt: Date.now(),
3457
3454
  abortController: new AbortController(),
3458
3455
  debouncer: null,
3459
- lidMapping,
3460
- groupMetaCache: /* @__PURE__ */ new Map()
3456
+ lidMapping
3461
3457
  };
3462
3458
  connections.set(accountId, conn);
3463
3459
  try {
@@ -3534,13 +3530,20 @@ function loadConfig(accountConfig) {
3534
3530
  const raw = accountConfig?.whatsapp;
3535
3531
  if (raw && typeof raw === "object") {
3536
3532
  const wa = raw;
3537
- const parsed = WhatsAppConfigSchema.safeParse(wa);
3538
- if (parsed.success) {
3539
- whatsAppConfig = parsed.data;
3533
+ const result = parseWhatsAppConfigTolerant(wa);
3534
+ if (result.ok) {
3535
+ whatsAppConfig = result.data;
3536
+ if (result.droppedKeys.length > 0) {
3537
+ console.error(`${TAG12} config: dropped unknown keys=[${result.droppedKeys.join(",")}] preserved known config`);
3538
+ }
3540
3539
  } else {
3541
- console.error(`${TAG12} config validation failed: ${parsed.error.message}`);
3540
+ console.error(`${TAG12} config validation failed: ${result.error.message}`);
3542
3541
  whatsAppConfig = {};
3543
3542
  }
3543
+ const diskAdmin = Array.isArray(wa.adminPhones) ? wa.adminPhones : [];
3544
+ if (diskAdmin.length > 0 && (whatsAppConfig.adminPhones?.length ?? 0) === 0) {
3545
+ console.error(`${TAG12} WARN adminPhones present on disk but empty after load`);
3546
+ }
3544
3547
  }
3545
3548
  } catch (err) {
3546
3549
  console.error(`${TAG12} config load error: ${String(err)}`);
@@ -3570,6 +3573,7 @@ async function connectWithReconnect(conn) {
3570
3573
  conn.selfLid = selfId.lid;
3571
3574
  conn.lastConnectedAt = connectedAt;
3572
3575
  conn.lastError = void 0;
3576
+ conn.terminalReason = void 0;
3573
3577
  const rawLidMapping = sock.signalRepository?.lidMapping ?? null;
3574
3578
  conn.lidMapping = withSelfLidShortcut(rawLidMapping, selfId.lid, selfId.jid);
3575
3579
  if (selfId.lid) {
@@ -3612,6 +3616,7 @@ async function connectWithReconnect(conn) {
3612
3616
  conn.lastError = classification.message;
3613
3617
  console.error(`${TAG12} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
3614
3618
  if (!classification.shouldRetry) {
3619
+ conn.terminalReason = classification.message;
3615
3620
  console.error(`${TAG12} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
3616
3621
  return;
3617
3622
  }
@@ -3646,11 +3651,11 @@ async function connectWithReconnect(conn) {
3646
3651
  console.error(
3647
3652
  `${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
3648
3653
  );
3649
- await new Promise((resolve27) => {
3650
- const timer2 = setTimeout(resolve27, delay);
3654
+ await new Promise((resolve28) => {
3655
+ const timer2 = setTimeout(resolve28, delay);
3651
3656
  conn.abortController.signal.addEventListener("abort", () => {
3652
3657
  clearTimeout(timer2);
3653
- resolve27();
3658
+ resolve28();
3654
3659
  }, { once: true });
3655
3660
  });
3656
3661
  }
@@ -3658,16 +3663,16 @@ async function connectWithReconnect(conn) {
3658
3663
  }
3659
3664
  }
3660
3665
  function waitForDisconnectEvent(conn) {
3661
- return new Promise((resolve27) => {
3666
+ return new Promise((resolve28) => {
3662
3667
  if (!conn.sock) {
3663
- resolve27();
3668
+ resolve28();
3664
3669
  return;
3665
3670
  }
3666
3671
  const sock = conn.sock;
3667
3672
  const handler = (update) => {
3668
3673
  if (update.connection === "close") {
3669
3674
  sock.ev.off("connection.update", handler);
3670
- resolve27();
3675
+ resolve28();
3671
3676
  }
3672
3677
  };
3673
3678
  sock.ev.on("connection.update", handler);
@@ -3687,37 +3692,6 @@ function watchForDisconnect(conn) {
3687
3692
  }
3688
3693
  });
3689
3694
  }
3690
- async function getGroupMeta(conn, jid) {
3691
- const cached3 = conn.groupMetaCache.get(jid);
3692
- if (cached3 && cached3.expires > Date.now()) return cached3;
3693
- if (!conn.sock) return null;
3694
- console.error(`${TAG12} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
3695
- try {
3696
- const meta = await conn.sock.groupMetadata(jid);
3697
- const participants = await Promise.all(
3698
- (meta.participants ?? []).map(async (p) => {
3699
- return await resolveJidToE164(p.id, conn.lidMapping) ?? p.id;
3700
- })
3701
- );
3702
- const entry = {
3703
- subject: meta.subject,
3704
- participants,
3705
- expires: Date.now() + GROUP_META_TTL
3706
- };
3707
- conn.groupMetaCache.set(jid, entry);
3708
- console.error(
3709
- `${TAG12} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
3710
- );
3711
- return entry;
3712
- } catch (err) {
3713
- console.error(
3714
- `${TAG12} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
3715
- );
3716
- const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
3717
- conn.groupMetaCache.set(jid, emptyEntry);
3718
- return null;
3719
- }
3720
- }
3721
3695
  function monitorInbound(conn) {
3722
3696
  if (!conn.sock || !onInboundMessage) return;
3723
3697
  const sock = conn.sock;
@@ -3926,8 +3900,8 @@ async function handleInboundMessage(conn, msg) {
3926
3900
  const conversationKey = isGroup ? remoteJid : senderPhone;
3927
3901
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
3928
3902
  let resolvePending;
3929
- const sttPending = new Promise((resolve27) => {
3930
- resolvePending = resolve27;
3903
+ const sttPending = new Promise((resolve28) => {
3904
+ resolvePending = resolve28;
3931
3905
  });
3932
3906
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
3933
3907
  try {
@@ -3941,24 +3915,7 @@ async function handleInboundMessage(conn, msg) {
3941
3915
  }
3942
3916
  let accessResult;
3943
3917
  if (isGroup) {
3944
- accessResult = checkGroupAccess({
3945
- senderPhone,
3946
- selfPhone,
3947
- groupJid: remoteJid,
3948
- // Three-way mention recognition (Sub-fix 4): legacy phone-JID equality,
3949
- // legacy E.164-via-jidToE164 equality, plus the new self-LID equality so
3950
- // mentions in LID-addressed groups (deliver as `<self-lid>@lid`) match.
3951
- // Pre-LID groups continue matching the first two — additive, never blocks.
3952
- // Each clause guards against null on its right operand; a creds-parse
3953
- // failure leaves all three of selfJid/selfLid/selfPhone null and
3954
- // jidToE164 of a malformed mention also returns null — without the
3955
- // guards, `null === null` would spuriously match every nullified field.
3956
- isMentioned: extracted.mentionedJids.some(
3957
- (jid) => conn.selfJid !== null && jid === conn.selfJid || conn.selfLid !== null && jid === conn.selfLid || conn.selfPhone !== null && jidToE164(jid) === conn.selfPhone
3958
- ),
3959
- config: whatsAppConfig,
3960
- accountConfig: whatsAppConfig.accounts?.[conn.accountId]
3961
- });
3918
+ accessResult = checkGroupAccess({ groupJid: remoteJid });
3962
3919
  } else {
3963
3920
  accessResult = checkDmAccess({
3964
3921
  senderPhone,
@@ -3971,18 +3928,20 @@ async function handleInboundMessage(conn, msg) {
3971
3928
  `${TAG12} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
3972
3929
  );
3973
3930
  if (!accessResult.allowed) return;
3974
- let groupSubject;
3975
- if (isGroup) {
3976
- const groupMeta = await getGroupMeta(conn, remoteJid);
3977
- groupSubject = groupMeta?.subject;
3978
- }
3979
3931
  const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
3980
3932
  if (sendReceipts && msg.key.id) {
3981
3933
  sendReadReceipt(conn.sock, remoteJid, [msg.key.id], isGroup ? senderJid : void 0);
3982
3934
  }
3983
3935
  const reply = async (text) => {
3936
+ if (isGroupJid(remoteJid)) {
3937
+ console.error(`${TAG12} op=group-reply-blocked jid=${remoteJid}`);
3938
+ throw new WaReplyError("group reply blocked \u2014 observe-only", false);
3939
+ }
3984
3940
  const currentSock = conn.sock;
3985
- if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
3941
+ if (!currentSock) {
3942
+ const reason = conn.terminalReason;
3943
+ throw new WaReplyError(reason ?? "WhatsApp disconnected \u2014 cannot reply", Boolean(reason));
3944
+ }
3986
3945
  await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
3987
3946
  };
3988
3947
  const composing = async () => {
@@ -4004,7 +3963,6 @@ async function handleInboundMessage(conn, msg) {
4004
3963
  text: extracted.text,
4005
3964
  isGroup,
4006
3965
  groupJid: isGroup ? remoteJid : void 0,
4007
- groupSubject,
4008
3966
  reply,
4009
3967
  composing,
4010
3968
  cacheKey,
@@ -4334,20 +4292,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
4334
4292
 
4335
4293
  // server/routes/health.ts
4336
4294
  function checkPort(port2, timeoutMs = 500) {
4337
- return new Promise((resolve27) => {
4295
+ return new Promise((resolve28) => {
4338
4296
  const socket = createConnection2(port2, "127.0.0.1");
4339
4297
  socket.setTimeout(timeoutMs);
4340
4298
  socket.once("connect", () => {
4341
4299
  socket.destroy();
4342
- resolve27(true);
4300
+ resolve28(true);
4343
4301
  });
4344
4302
  socket.once("error", () => {
4345
4303
  socket.destroy();
4346
- resolve27(false);
4304
+ resolve28(false);
4347
4305
  });
4348
4306
  socket.once("timeout", () => {
4349
4307
  socket.destroy();
4350
- resolve27(false);
4308
+ resolve28(false);
4351
4309
  });
4352
4310
  });
4353
4311
  }
@@ -4383,13 +4341,15 @@ app.get("/", async (c) => {
4383
4341
  selfPhone: a.selfPhone ?? null,
4384
4342
  reconnectAttempts: a.reconnectAttempts,
4385
4343
  lastError: a.lastError ?? null,
4386
- sessionStuckReason: a.sessionStuckReason ?? null
4344
+ sessionStuckReason: a.sessionStuckReason ?? null,
4345
+ terminalReason: a.terminalReason ?? null
4387
4346
  }));
4388
4347
  } catch (err) {
4389
4348
  console.error(`[health] failed to read WhatsApp status: ${err instanceof Error ? err.message : String(err)}`);
4390
4349
  }
4391
4350
  const whatsappAnyConnected = whatsappAccounts.some((a) => a.connected);
4392
4351
  const whatsappAnyStuck = whatsappAccounts.some((a) => Boolean(a.sessionStuckReason));
4352
+ const whatsappAnyTerminal = whatsappAccounts.some((a) => Boolean(a.terminalReason));
4393
4353
  const account = resolveAccount();
4394
4354
  const missingPlugins = findMissingPlugins(account?.config.enabledPlugins);
4395
4355
  return c.json({
@@ -4404,6 +4364,7 @@ app.get("/", async (c) => {
4404
4364
  whatsapp: {
4405
4365
  any_connected: whatsappAnyConnected,
4406
4366
  any_stuck: whatsappAnyStuck,
4367
+ any_terminal: whatsappAnyTerminal,
4407
4368
  accounts: whatsappAccounts
4408
4369
  }
4409
4370
  });
@@ -4677,7 +4638,10 @@ async function managerRcSpawn(opts) {
4677
4638
  sliceToken: opts.sliceToken,
4678
4639
  personId: opts.personId,
4679
4640
  userId: opts.userId,
4680
- waChannel: opts.waChannel
4641
+ waChannel: opts.waChannel,
4642
+ role: opts.role,
4643
+ channel: opts.channel,
4644
+ senderId: opts.senderId
4681
4645
  })
4682
4646
  }).catch((err) => ({ __throw: err instanceof Error ? err.message : String(err) }));
4683
4647
  if ("__throw" in res) {
@@ -5286,6 +5250,13 @@ async function ensureEntry(input) {
5286
5250
  spawned = await managerRcSpawn({
5287
5251
  sessionId: adminSessionId,
5288
5252
  name: nameValue ?? void 0,
5253
+ // Task 724 — pass the real channel + role + senderId so the manager writes
5254
+ // the classification sidecar for this admin channel (webchat, email, …),
5255
+ // not just WhatsApp. Without these the live session was left sidecar-less
5256
+ // and the sidebar channel badge / archive classification mis-read it.
5257
+ role: "admin",
5258
+ channel: input.channel,
5259
+ senderId: input.senderId,
5289
5260
  logContext: `channel=${input.channel} senderId=${input.senderId}`
5290
5261
  });
5291
5262
  } else {
@@ -5422,12 +5393,12 @@ async function dispatchOnce(input) {
5422
5393
  });
5423
5394
  if (!entry) return { error: "spawn-failed" };
5424
5395
  entry.lastInboundAt = Date.now();
5425
- let resolve27;
5396
+ let resolve28;
5426
5397
  const turnPromise = new Promise((r) => {
5427
- resolve27 = r;
5398
+ resolve28 = r;
5428
5399
  });
5429
5400
  const listener = (text) => {
5430
- resolve27(text);
5401
+ resolve28(text);
5431
5402
  };
5432
5403
  entry.subscribers.add(listener);
5433
5404
  const writeAtMs = Date.now();
@@ -5987,16 +5958,6 @@ function resolvePublicAgent(accountDir, opts) {
5987
5958
  const config = readConfig(accountDir);
5988
5959
  const wa = config.whatsapp;
5989
5960
  if (!wa) return null;
5990
- if (opts.groupJid) {
5991
- const accounts2 = wa.accounts;
5992
- const account2 = accounts2?.[opts.accountId];
5993
- const groups = account2?.groups;
5994
- const group = groups?.[opts.groupJid];
5995
- const groupSlug = group?.publicAgent;
5996
- if (typeof groupSlug === "string" && groupSlug.trim()) {
5997
- return { slug: groupSlug.trim(), source: "group" };
5998
- }
5999
- }
6000
5961
  const accounts = wa.accounts;
6001
5962
  const account = accounts?.[opts.accountId];
6002
5963
  const accountSlug = account?.publicAgent;
@@ -6012,77 +5973,6 @@ function resolvePublicAgent(accountDir, opts) {
6012
5973
  return null;
6013
5974
  }
6014
5975
  }
6015
- function setGroupPublicAgent(accountDir, accountId, groupJid, slug) {
6016
- const trimmedSlug = slug.trim();
6017
- const trimmedGroup = groupJid.trim();
6018
- const trimmedAccount = accountId.trim();
6019
- if (!trimmedSlug) return { ok: false, error: "Agent slug cannot be empty." };
6020
- if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
6021
- if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
6022
- const agentConfigPath = join4(accountDir, "agents", trimmedSlug, "config.json");
6023
- if (!existsSync4(agentConfigPath)) {
6024
- return { ok: false, error: `Agent "${trimmedSlug}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
6025
- }
6026
- try {
6027
- const config = readConfig(accountDir);
6028
- if (!config.whatsapp || typeof config.whatsapp !== "object") config.whatsapp = {};
6029
- const wa = config.whatsapp;
6030
- if (!wa.accounts || typeof wa.accounts !== "object") wa.accounts = {};
6031
- const accounts = wa.accounts;
6032
- if (!accounts[trimmedAccount] || typeof accounts[trimmedAccount] !== "object") accounts[trimmedAccount] = {};
6033
- const account = accounts[trimmedAccount];
6034
- if (!account.groups || typeof account.groups !== "object") account.groups = {};
6035
- const groups = account.groups;
6036
- const existing = groups[trimmedGroup] && typeof groups[trimmedGroup] === "object" ? groups[trimmedGroup] : {};
6037
- groups[trimmedGroup] = { ...existing, publicAgent: trimmedSlug };
6038
- const parsed = WhatsAppConfigSchema.safeParse(wa);
6039
- if (!parsed.success) {
6040
- const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
6041
- return { ok: false, error: `Validation failed: ${msg}` };
6042
- }
6043
- config.whatsapp = parsed.data;
6044
- writeConfig(accountDir, config);
6045
- console.error(`${TAG17} setGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup} slug=${trimmedSlug}`);
6046
- reloadManagerConfig(accountDir);
6047
- return { ok: true, message: `Per-group public agent set to "${trimmedSlug}" for group ${trimmedGroup}.` };
6048
- } catch (err) {
6049
- const msg = err instanceof Error ? err.message : String(err);
6050
- console.error(`${TAG17} setGroupPublicAgent failed: ${msg}`);
6051
- return { ok: false, error: msg };
6052
- }
6053
- }
6054
- function unsetGroupPublicAgent(accountDir, accountId, groupJid) {
6055
- const trimmedGroup = groupJid.trim();
6056
- const trimmedAccount = accountId.trim();
6057
- if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
6058
- if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
6059
- try {
6060
- const config = readConfig(accountDir);
6061
- const wa = config.whatsapp ?? {};
6062
- const accounts = wa.accounts;
6063
- const account = accounts?.[trimmedAccount];
6064
- const groups = account?.groups;
6065
- const group = groups?.[trimmedGroup];
6066
- if (!group || typeof group.publicAgent !== "string") {
6067
- return { ok: true, message: `No per-group publicAgent override for group ${trimmedGroup}; nothing to remove.` };
6068
- }
6069
- delete group.publicAgent;
6070
- const parsed = WhatsAppConfigSchema.safeParse(wa);
6071
- if (!parsed.success) {
6072
- const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
6073
- return { ok: false, error: `Validation failed: ${msg}` };
6074
- }
6075
- config.whatsapp = parsed.data;
6076
- writeConfig(accountDir, config);
6077
- console.error(`${TAG17} unsetGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup}`);
6078
- reloadManagerConfig(accountDir);
6079
- return { ok: true, message: `Per-group public agent override removed for group ${trimmedGroup}.` };
6080
- } catch (err) {
6081
- const msg = err instanceof Error ? err.message : String(err);
6082
- console.error(`${TAG17} unsetGroupPublicAgent failed: ${msg}`);
6083
- return { ok: false, error: msg };
6084
- }
6085
- }
6086
5976
  function updateConfig(accountDir, fields) {
6087
5977
  const fieldNames = Object.keys(fields);
6088
5978
  if (fieldNames.length === 0) {
@@ -6126,6 +6016,35 @@ function getConfig(accountDir) {
6126
6016
  return null;
6127
6017
  }
6128
6018
  }
6019
+ function migrateRemovedConfigKeys(accountDir) {
6020
+ try {
6021
+ const config = readConfig(accountDir);
6022
+ const wa = config.whatsapp;
6023
+ if (!wa || typeof wa !== "object") return { dropped: [] };
6024
+ const result = parseWhatsAppConfigTolerant(wa);
6025
+ if (!result.ok || result.droppedKeys.length === 0) return { dropped: [] };
6026
+ const block = wa;
6027
+ for (const dotted of result.droppedKeys) {
6028
+ const segs = dotted.split(".");
6029
+ const key = segs.pop();
6030
+ const container = segs.reduce(
6031
+ (acc, seg) => acc && typeof acc === "object" ? acc[seg] : void 0,
6032
+ block
6033
+ );
6034
+ if (container && typeof container === "object") {
6035
+ delete container[key];
6036
+ }
6037
+ }
6038
+ writeConfig(accountDir, config);
6039
+ console.error(
6040
+ `${TAG17} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
6041
+ );
6042
+ return { dropped: result.droppedKeys };
6043
+ } catch (err) {
6044
+ console.error(`${TAG17} migration failed: ${String(err)}`);
6045
+ return { dropped: [] };
6046
+ }
6047
+ }
6129
6048
 
6130
6049
  // app/lib/whatsapp/login.ts
6131
6050
  var TAG18 = "[whatsapp:login]";
@@ -6265,8 +6184,8 @@ async function startLogin(opts) {
6265
6184
  await clearAuth(authDir);
6266
6185
  let resolveCode = null;
6267
6186
  let rejectCode = null;
6268
- const codePromise = new Promise((resolve27, reject) => {
6269
- resolveCode = resolve27;
6187
+ const codePromise = new Promise((resolve28, reject) => {
6188
+ resolveCode = resolve28;
6270
6189
  rejectCode = reject;
6271
6190
  });
6272
6191
  const codeTimer = setTimeout(
@@ -6615,29 +6534,10 @@ app3.post("/reconnect", async (c) => {
6615
6534
  return c.json({ error: String(err) }, 500);
6616
6535
  }
6617
6536
  });
6618
- app3.post("/send", async (c) => {
6619
- try {
6620
- const body = await c.req.json();
6621
- const { to, text } = body;
6622
- const accountId = validateAccountId(body.accountId);
6623
- if (!to || !text) {
6624
- return c.json({ error: "Missing required fields: to, text" }, 400);
6625
- }
6626
- const sock = getSocket(accountId);
6627
- if (!sock) {
6628
- return c.json({ error: `WhatsApp account "${accountId}" is not connected` }, 503);
6629
- }
6630
- const result = await sendTextMessage(sock, to, text, { accountId });
6631
- return c.json(result);
6632
- } catch (err) {
6633
- console.error(`${TAG20} send error: ${String(err)}`);
6634
- return c.json({ error: String(err) }, 500);
6635
- }
6636
- });
6637
6537
  app3.post("/config", async (c) => {
6638
6538
  try {
6639
6539
  const body = await c.req.json().catch(() => ({}));
6640
- const { action, phone, slug, groupJid, fields, accountId } = body;
6540
+ const { action, phone, slug, fields, accountId } = body;
6641
6541
  if (!action || typeof action !== "string") {
6642
6542
  return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
6643
6543
  }
@@ -6677,32 +6577,10 @@ app3.post("/config", async (c) => {
6677
6577
  }
6678
6578
  case "get-public-agent": {
6679
6579
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
6680
- const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
6681
- const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
6682
- console.error(`${TAG20} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
6580
+ const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
6581
+ console.error(`${TAG20} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
6683
6582
  return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
6684
6583
  }
6685
- case "set-group-public-agent": {
6686
- if (!slug || typeof slug !== "string") {
6687
- return c.json({ ok: false, error: 'Missing required field "slug" (the public-agent directory name, e.g. "my-agent").' }, 400);
6688
- }
6689
- if (!groupJid || typeof groupJid !== "string") {
6690
- return c.json({ ok: false, error: 'Missing required field "groupJid" (group JID ending in @g.us).' }, 400);
6691
- }
6692
- const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
6693
- const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
6694
- console.error(`${TAG20} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
6695
- return c.json(result, result.ok ? 200 : 400);
6696
- }
6697
- case "unset-group-public-agent": {
6698
- if (!groupJid || typeof groupJid !== "string") {
6699
- return c.json({ ok: false, error: 'Missing required field "groupJid" (group JID ending in @g.us).' }, 400);
6700
- }
6701
- const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
6702
- const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
6703
- console.error(`${TAG20} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
6704
- return c.json(result, result.ok ? 200 : 400);
6705
- }
6706
6584
  case "list-public-agents": {
6707
6585
  const agentsDir = resolve7(account.accountDir, "agents");
6708
6586
  const agents = [];
@@ -6769,7 +6647,7 @@ app3.post("/config", async (c) => {
6769
6647
  }
6770
6648
  default:
6771
6649
  return c.json(
6772
- { ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent, set-group-public-agent, unset-group-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
6650
+ { ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
6773
6651
  400
6774
6652
  );
6775
6653
  }
@@ -7018,7 +6896,7 @@ function enumerateJsonls(projectsRoot) {
7018
6896
  }
7019
6897
  for (const entry of entries) {
7020
6898
  if (entry.isFile() && SESSION_ID_RE.test(entry.name)) {
7021
- out.push({ path: join7(slugDir, entry.name), isSubagent: false });
6899
+ out.push({ path: join7(slugDir, entry.name), isSubagent: false, archived: false });
7022
6900
  } else if (entry.isDirectory() && entry.name === "subagents") {
7023
6901
  const subDir = join7(slugDir, entry.name);
7024
6902
  let subEntries;
@@ -7031,7 +6909,22 @@ function enumerateJsonls(projectsRoot) {
7031
6909
  }
7032
6910
  for (const sub of subEntries) {
7033
6911
  if (sub.isFile() && SESSION_ID_RE.test(sub.name)) {
7034
- out.push({ path: join7(subDir, sub.name), isSubagent: true });
6912
+ out.push({ path: join7(subDir, sub.name), isSubagent: true, archived: false });
6913
+ }
6914
+ }
6915
+ } else if (entry.isDirectory() && entry.name === "archive") {
6916
+ const archiveDir = join7(slugDir, entry.name);
6917
+ let archiveEntries;
6918
+ try {
6919
+ archiveEntries = readdirSync4(archiveDir, { withFileTypes: true });
6920
+ } catch (err) {
6921
+ const code = err.code ?? "unknown";
6922
+ console.error(`[admin-sessions-list] archive-skipped slug=${slug} code=${code}`);
6923
+ continue;
6924
+ }
6925
+ for (const arc of archiveEntries) {
6926
+ if (arc.isFile() && SESSION_ID_RE.test(arc.name)) {
6927
+ out.push({ path: join7(archiveDir, arc.name), isSubagent: false, archived: true });
7035
6928
  }
7036
6929
  }
7037
6930
  }
@@ -7171,8 +7064,9 @@ app4.get("/", requireAdminSession, async (c) => {
7171
7064
  let liveCount = 0;
7172
7065
  let subagentCount = 0;
7173
7066
  let nonResumableCount = 0;
7067
+ let archivedCount = 0;
7174
7068
  const titleSourceCounts = { user: 0, ai: 0, "first-message": 0, prefix: 0 };
7175
- for (const { path: path2, isSubagent } of jsonls) {
7069
+ for (const { path: path2, isSubagent, archived } of jsonls) {
7176
7070
  const lastSlash = path2.lastIndexOf("/");
7177
7071
  const base = path2.slice(lastSlash + 1);
7178
7072
  const projectDir = path2.slice(0, lastSlash);
@@ -7207,15 +7101,17 @@ app4.get("/", requireAdminSession, async (c) => {
7207
7101
  pid,
7208
7102
  projectDir,
7209
7103
  bridgeIds,
7210
- resumable
7104
+ resumable,
7105
+ archived
7211
7106
  });
7212
7107
  if (live) liveCount += 1;
7213
7108
  if (isSubagent) subagentCount += 1;
7214
7109
  if (!resumable) nonResumableCount += 1;
7110
+ if (archived) archivedCount += 1;
7215
7111
  }
7216
7112
  rows.sort((a, b) => a.startedAt < b.startedAt ? 1 : -1);
7217
7113
  console.log(
7218
- `[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} nonResumable=${nonResumableCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} accountId=${accountId ? accountId.slice(0, 8) : "unset"}`
7114
+ `[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} nonResumable=${nonResumableCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} accountId=${accountId ? accountId.slice(0, 8) : "unset"}`
7219
7115
  );
7220
7116
  return c.json({ sessions: rows, accountId });
7221
7117
  });
@@ -7427,14 +7323,168 @@ app5.get("/stream", requireAdminSession, (c) => {
7427
7323
  });
7428
7324
  var whatsapp_reader_default = app5;
7429
7325
 
7326
+ // server/routes/telegram.ts
7327
+ import { homedir } from "os";
7328
+ import { resolve as resolve10, join as join9 } from "path";
7329
+ import { existsSync as existsSync6, readFileSync as readFileSync10 } from "fs";
7330
+
7331
+ // app/lib/telegram/access-control.ts
7332
+ function checkTelegramAccess(params) {
7333
+ const { senderId, botType, config } = params;
7334
+ if (botType === "admin") {
7335
+ const adminUsers = config.adminUsers ?? [];
7336
+ if (adminUsers.includes(senderId)) {
7337
+ return { allowed: true, reason: "admin-binding", agentType: "admin" };
7338
+ }
7339
+ return { allowed: false, reason: "not-admin-user", agentType: "admin" };
7340
+ }
7341
+ const policy = config.dmPolicy ?? "disabled";
7342
+ switch (policy) {
7343
+ case "open":
7344
+ return { allowed: true, reason: "dm-policy-open", agentType: "public" };
7345
+ case "allowlist": {
7346
+ const allowFrom = config.allowFrom ?? [];
7347
+ if (allowFrom.includes(senderId)) {
7348
+ return { allowed: true, reason: "allowlist-match", agentType: "public" };
7349
+ }
7350
+ return { allowed: false, reason: "not-in-allowlist", agentType: "public" };
7351
+ }
7352
+ case "disabled":
7353
+ return { allowed: false, reason: "dm-policy-disabled", agentType: "public" };
7354
+ default:
7355
+ return { allowed: false, reason: "unknown-dm-policy", agentType: "public" };
7356
+ }
7357
+ }
7358
+
7359
+ // app/lib/telegram/route-inbound.ts
7360
+ function routeTelegramUpdate(input) {
7361
+ const m = input.update.message;
7362
+ if (!m || typeof m.from?.id !== "number" || typeof m.chat?.id !== "number") {
7363
+ return { kind: "ignore", reason: "no-message" };
7364
+ }
7365
+ if (typeof m.text !== "string" || m.text.length === 0) {
7366
+ return { kind: "ignore", reason: "no-text" };
7367
+ }
7368
+ const access = checkTelegramAccess({ senderId: m.from.id, botType: input.botType, config: input.config });
7369
+ if (!access.allowed) {
7370
+ return { kind: "denied", reason: access.reason, agentType: access.agentType };
7371
+ }
7372
+ return { kind: "dispatch", senderId: String(m.from.id), chatId: m.chat.id, text: m.text, agentType: access.agentType };
7373
+ }
7374
+
7375
+ // server/routes/telegram.ts
7376
+ var TAG21 = "[telegram-inbound]";
7377
+ var DISPATCH_TIMEOUT_MS = 12e4;
7378
+ function configDirName() {
7379
+ const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve10(process.cwd(), "..");
7380
+ const brandPath = join9(platformRoot2, "config", "brand.json");
7381
+ if (existsSync6(brandPath)) {
7382
+ try {
7383
+ return JSON.parse(readFileSync10(brandPath, "utf-8")).configDir ?? ".maxy";
7384
+ } catch {
7385
+ }
7386
+ }
7387
+ return ".maxy";
7388
+ }
7389
+ function secretPath(botType) {
7390
+ const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
7391
+ return resolve10(homedir(), configDirName(), filename);
7392
+ }
7393
+ async function sendTelegram(botToken, chatId, text) {
7394
+ try {
7395
+ const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
7396
+ method: "POST",
7397
+ headers: { "Content-Type": "application/json" },
7398
+ body: JSON.stringify({ chat_id: chatId, text })
7399
+ });
7400
+ const data = await res.json();
7401
+ return data.ok ? { ok: true } : { ok: false, error: data.description ?? "send failed" };
7402
+ } catch (e) {
7403
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
7404
+ }
7405
+ }
7406
+ var app6 = new Hono();
7407
+ app6.post("/", async (c) => {
7408
+ const botParam = c.req.query("bot");
7409
+ const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
7410
+ if (!botType) {
7411
+ console.error(`${TAG21} op=reject reason=missing-bot-param`);
7412
+ return c.json({ ok: false }, 400);
7413
+ }
7414
+ const sp = secretPath(botType);
7415
+ if (!existsSync6(sp)) {
7416
+ console.error(`${TAG21} op=reject reason=no-secret-file botType=${botType}`);
7417
+ return c.json({ ok: false }, 401);
7418
+ }
7419
+ const expected = readFileSync10(sp, "utf-8").trim();
7420
+ const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
7421
+ if (got !== expected) {
7422
+ console.error(`${TAG21} op=reject reason=bad-secret botType=${botType}`);
7423
+ return c.json({ ok: false }, 401);
7424
+ }
7425
+ let update;
7426
+ try {
7427
+ update = await c.req.json();
7428
+ } catch {
7429
+ return c.json({ ok: true }, 200);
7430
+ }
7431
+ const account = resolveAccount();
7432
+ if (!account) {
7433
+ console.error(`${TAG21} op=reject reason=no-account`);
7434
+ return c.json({ ok: true }, 200);
7435
+ }
7436
+ const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
7437
+ if (decision.kind === "ignore") {
7438
+ console.error(`${TAG21} op=ignore reason=${decision.reason}`);
7439
+ return c.json({ ok: true }, 200);
7440
+ }
7441
+ if (decision.kind === "denied") {
7442
+ console.error(`${TAG21} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
7443
+ return c.json({ ok: true }, 200);
7444
+ }
7445
+ const role = decision.agentType === "admin" ? "admin" : "public";
7446
+ const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
7447
+ if (!agentSlug) {
7448
+ console.error(`${TAG21} op=reject reason=no-default-agent`);
7449
+ return c.json({ ok: true }, 200);
7450
+ }
7451
+ console.error(`${TAG21} op=update accountId=${account.accountId} from=${decision.senderId}`);
7452
+ const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
7453
+ const { senderId, chatId, text } = decision;
7454
+ void (async () => {
7455
+ const result = await dispatchOnce({
7456
+ accountId: account.accountId,
7457
+ senderId,
7458
+ role,
7459
+ channel: "telegram",
7460
+ agentSlug,
7461
+ text,
7462
+ timeoutMs: DISPATCH_TIMEOUT_MS
7463
+ });
7464
+ if ("error" in result) {
7465
+ console.error(`${TAG21} op=dispatch-failed from=${senderId} error=${result.error}`);
7466
+ return;
7467
+ }
7468
+ if (!botToken) {
7469
+ const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
7470
+ console.error(`${TAG21} op=reply-dropped reason=${reason} botType=${botType}`);
7471
+ return;
7472
+ }
7473
+ const sent = await sendTelegram(botToken, chatId, result.turnText);
7474
+ console.error(`${TAG21} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
7475
+ })();
7476
+ return c.json({ ok: true }, 200);
7477
+ });
7478
+ var telegram_default = app6;
7479
+
7430
7480
  // server/routes/onboarding.ts
7431
7481
  import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync2 } from "child_process";
7432
- import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync6, writeSync, existsSync as existsSync7, readFileSync as readFileSync11, unlinkSync } from "fs";
7482
+ import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync6, writeSync, existsSync as existsSync8, readFileSync as readFileSync12, unlinkSync } from "fs";
7433
7483
  import { createHash as createHash2, randomUUID as randomUUID7 } from "crypto";
7434
7484
 
7435
7485
  // ../lib/admins-write/src/index.ts
7436
- import { existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5, renameSync, mkdirSync as mkdirSync2, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
7437
- import { dirname as dirname2, join as join9 } from "path";
7486
+ import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync5, renameSync, mkdirSync as mkdirSync2, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
7487
+ import { dirname as dirname2, join as join10 } from "path";
7438
7488
  function logLine(input, result) {
7439
7489
  const userIdShort = input.userId.slice(0, 8);
7440
7490
  console.error(
@@ -7451,8 +7501,8 @@ function writeAdminEntry(input) {
7451
7501
  const result = { usersJsonResult: "noop", accountJsonResult: "noop" };
7452
7502
  try {
7453
7503
  let users = [];
7454
- if (existsSync6(input.usersFile)) {
7455
- const raw = readFileSync10(input.usersFile, "utf-8").trim();
7504
+ if (existsSync7(input.usersFile)) {
7505
+ const raw = readFileSync11(input.usersFile, "utf-8").trim();
7456
7506
  if (raw) {
7457
7507
  const parsed = JSON.parse(raw);
7458
7508
  if (!Array.isArray(parsed)) {
@@ -7476,11 +7526,11 @@ function writeAdminEntry(input) {
7476
7526
  return result;
7477
7527
  }
7478
7528
  try {
7479
- const accountJsonPath = join9(input.accountDir, "account.json");
7480
- if (!existsSync6(accountJsonPath)) {
7529
+ const accountJsonPath = join10(input.accountDir, "account.json");
7530
+ if (!existsSync7(accountJsonPath)) {
7481
7531
  throw new Error(`account.json not found at ${accountJsonPath}`);
7482
7532
  }
7483
- const config = JSON.parse(readFileSync10(accountJsonPath, "utf-8"));
7533
+ const config = JSON.parse(readFileSync11(accountJsonPath, "utf-8"));
7484
7534
  const admins = config.admins ?? [];
7485
7535
  if (admins.some((a) => a.userId === input.userId)) {
7486
7536
  result.accountJsonResult = "noop";
@@ -7504,9 +7554,9 @@ function checkAdminAuthInvariant(input) {
7504
7554
  usersWithoutAccount: []
7505
7555
  };
7506
7556
  const usersUserIds = /* @__PURE__ */ new Set();
7507
- if (existsSync6(input.usersFile)) {
7557
+ if (existsSync7(input.usersFile)) {
7508
7558
  try {
7509
- const raw = readFileSync10(input.usersFile, "utf-8").trim();
7559
+ const raw = readFileSync11(input.usersFile, "utf-8").trim();
7510
7560
  if (raw) {
7511
7561
  const users = JSON.parse(raw);
7512
7562
  for (const u of users) {
@@ -7519,7 +7569,7 @@ function checkAdminAuthInvariant(input) {
7519
7569
  }
7520
7570
  }
7521
7571
  const accountUserIds = /* @__PURE__ */ new Set();
7522
- if (existsSync6(input.accountsDir)) {
7572
+ if (existsSync7(input.accountsDir)) {
7523
7573
  let entries;
7524
7574
  try {
7525
7575
  entries = readdirSync5(input.accountsDir);
@@ -7531,17 +7581,17 @@ function checkAdminAuthInvariant(input) {
7531
7581
  }
7532
7582
  for (const entry of entries) {
7533
7583
  if (entry.startsWith(".")) continue;
7534
- const accountDir = join9(input.accountsDir, entry);
7584
+ const accountDir = join10(input.accountsDir, entry);
7535
7585
  try {
7536
7586
  if (!statSync4(accountDir).isDirectory()) continue;
7537
7587
  } catch {
7538
7588
  continue;
7539
7589
  }
7540
- const accountJsonPath = join9(accountDir, "account.json");
7541
- if (!existsSync6(accountJsonPath)) continue;
7590
+ const accountJsonPath = join10(accountDir, "account.json");
7591
+ if (!existsSync7(accountJsonPath)) continue;
7542
7592
  let admins = [];
7543
7593
  try {
7544
- const config = JSON.parse(readFileSync10(accountJsonPath, "utf-8"));
7594
+ const config = JSON.parse(readFileSync11(accountJsonPath, "utf-8"));
7545
7595
  admins = config.admins ?? [];
7546
7596
  } catch (err) {
7547
7597
  const msg = err instanceof Error ? err.message : String(err);
@@ -7581,8 +7631,8 @@ function hashPin(pin) {
7581
7631
  return createHash2("sha256").update(pin).digest("hex");
7582
7632
  }
7583
7633
  function readUsersFile() {
7584
- if (!existsSync7(USERS_FILE)) return null;
7585
- const raw = readFileSync11(USERS_FILE, "utf-8").trim();
7634
+ if (!existsSync8(USERS_FILE)) return null;
7635
+ const raw = readFileSync12(USERS_FILE, "utf-8").trim();
7586
7636
  if (!raw) return [];
7587
7637
  return JSON.parse(raw);
7588
7638
  }
@@ -7612,11 +7662,11 @@ async function waitForAuthPage(timeoutMs = 2e4) {
7612
7662
  }
7613
7663
  return false;
7614
7664
  }
7615
- var app6 = new Hono();
7616
- app6.get("/claude-auth", async (c) => {
7665
+ var app7 = new Hono();
7666
+ app7.get("/claude-auth", async (c) => {
7617
7667
  return c.json({ authenticated: await checkAuthStatus() });
7618
7668
  });
7619
- app6.post("/claude-auth", async (c) => {
7669
+ app7.post("/claude-auth", async (c) => {
7620
7670
  let body = {};
7621
7671
  try {
7622
7672
  body = await c.req.json();
@@ -7680,7 +7730,7 @@ app6.post("/claude-auth", async (c) => {
7680
7730
  await waitForAuthPage(2e4);
7681
7731
  return c.json({ started: true, transport });
7682
7732
  });
7683
- app6.post("/set-pin", async (c) => {
7733
+ app7.post("/set-pin", async (c) => {
7684
7734
  let existingUsers = null;
7685
7735
  try {
7686
7736
  existingUsers = readUsersFile();
@@ -7736,9 +7786,9 @@ app6.post("/set-pin", async (c) => {
7736
7786
  console.log(`[set-pin] wrote users.json + account.json admins: userId=${userId.slice(0, 8)}\u2026 role=owner`);
7737
7787
  if (process.platform === "linux") {
7738
7788
  let installOwner = null;
7739
- if (existsSync7(INSTALL_OWNER_FILE)) {
7789
+ if (existsSync8(INSTALL_OWNER_FILE)) {
7740
7790
  try {
7741
- const raw = readFileSync11(INSTALL_OWNER_FILE, "utf-8").trim();
7791
+ const raw = readFileSync12(INSTALL_OWNER_FILE, "utf-8").trim();
7742
7792
  if (raw.length > 0) installOwner = raw;
7743
7793
  } catch (err) {
7744
7794
  console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
@@ -7782,7 +7832,7 @@ ${body.pin}
7782
7832
  }
7783
7833
  return c.json({ ok: true });
7784
7834
  });
7785
- app6.delete("/set-pin", async (c) => {
7835
+ app7.delete("/set-pin", async (c) => {
7786
7836
  let users = null;
7787
7837
  try {
7788
7838
  users = readUsersFile();
@@ -7816,12 +7866,12 @@ app6.delete("/set-pin", async (c) => {
7816
7866
  }
7817
7867
  return c.json({ ok: true });
7818
7868
  });
7819
- var onboarding_default = app6;
7869
+ var onboarding_default = app7;
7820
7870
 
7821
7871
  // server/routes/client-error.ts
7822
- import { appendFileSync, existsSync as existsSync8, renameSync as renameSync2, statSync as statSync5 } from "fs";
7823
- import { join as join10 } from "path";
7824
- var CLIENT_ERRORS_LOG = join10(LOG_DIR, "client-errors.log");
7872
+ import { appendFileSync, existsSync as existsSync9, renameSync as renameSync2, statSync as statSync5 } from "fs";
7873
+ import { join as join11 } from "path";
7874
+ var CLIENT_ERRORS_LOG = join11(LOG_DIR, "client-errors.log");
7825
7875
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
7826
7876
  var MAX_BODY_SIZE = 8 * 1024;
7827
7877
  var MAX_STACK_LEN = 2e3;
@@ -7864,7 +7914,7 @@ function stackHead(stack) {
7864
7914
  }
7865
7915
  function rotateIfNeeded() {
7866
7916
  try {
7867
- if (!existsSync8(CLIENT_ERRORS_LOG)) return;
7917
+ if (!existsSync9(CLIENT_ERRORS_LOG)) return;
7868
7918
  const stats = statSync5(CLIENT_ERRORS_LOG);
7869
7919
  if (stats.size < MAX_LOG_SIZE) return;
7870
7920
  renameSync2(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
@@ -7872,8 +7922,8 @@ function rotateIfNeeded() {
7872
7922
  console.error(`[client-error] log rotation failed: ${err instanceof Error ? err.message : String(err)}`);
7873
7923
  }
7874
7924
  }
7875
- var app7 = new Hono();
7876
- app7.post("/", async (c) => {
7925
+ var app8 = new Hono();
7926
+ app8.post("/", async (c) => {
7877
7927
  const client = resolveClientIp(
7878
7928
  c.env?.incoming?.socket?.remoteAddress,
7879
7929
  c.req.header("x-forwarded-for")
@@ -7975,21 +8025,21 @@ app7.post("/", async (c) => {
7975
8025
  }
7976
8026
  return c.json({ ok: true });
7977
8027
  });
7978
- var client_error_default = app7;
8028
+ var client_error_default = app8;
7979
8029
 
7980
8030
  // server/routes/admin/session.ts
7981
8031
  import { randomUUID as randomUUID8 } from "crypto";
7982
8032
 
7983
8033
  // app/lib/admin-identity/pin-validator.ts
7984
8034
  import { createHash as createHash3 } from "crypto";
7985
- import { existsSync as existsSync9, readFileSync as readFileSync12, readdirSync as readdirSync6, statSync as statSync6 } from "fs";
7986
- import { join as join11 } from "path";
8035
+ import { existsSync as existsSync10, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync6 } from "fs";
8036
+ import { join as join12 } from "path";
7987
8037
  function hashPin2(pin) {
7988
8038
  return createHash3("sha256").update(pin).digest("hex");
7989
8039
  }
7990
8040
  function readUsersFile2(usersFilePath) {
7991
- if (!existsSync9(usersFilePath)) return null;
7992
- const raw = readFileSync12(usersFilePath, "utf-8").trim();
8041
+ if (!existsSync10(usersFilePath)) return null;
8042
+ const raw = readFileSync13(usersFilePath, "utf-8").trim();
7993
8043
  if (!raw) return [];
7994
8044
  return JSON.parse(raw);
7995
8045
  }
@@ -8008,7 +8058,7 @@ function validatePin(pin, usersFilePath) {
8008
8058
  function scanForOrphanedAccountAdmins(users, accountsDir) {
8009
8059
  const known = new Set(users.map((u) => u.userId));
8010
8060
  const orphans = [];
8011
- if (!existsSync9(accountsDir)) return orphans;
8061
+ if (!existsSync10(accountsDir)) return orphans;
8012
8062
  let entries;
8013
8063
  try {
8014
8064
  entries = readdirSync6(accountsDir);
@@ -8017,16 +8067,16 @@ function scanForOrphanedAccountAdmins(users, accountsDir) {
8017
8067
  }
8018
8068
  for (const entry of entries) {
8019
8069
  if (entry.startsWith(".")) continue;
8020
- const accountDir = join11(accountsDir, entry);
8070
+ const accountDir = join12(accountsDir, entry);
8021
8071
  try {
8022
8072
  if (!statSync6(accountDir).isDirectory()) continue;
8023
8073
  } catch {
8024
8074
  continue;
8025
8075
  }
8026
- const accountJsonPath = join11(accountDir, "account.json");
8027
- if (!existsSync9(accountJsonPath)) continue;
8076
+ const accountJsonPath = join12(accountDir, "account.json");
8077
+ if (!existsSync10(accountJsonPath)) continue;
8028
8078
  try {
8029
- const config = JSON.parse(readFileSync12(accountJsonPath, "utf-8"));
8079
+ const config = JSON.parse(readFileSync13(accountJsonPath, "utf-8"));
8030
8080
  const admins = config.admins ?? [];
8031
8081
  for (const a of admins) {
8032
8082
  if (typeof a.userId === "string" && !known.has(a.userId)) orphans.push(a.userId);
@@ -8093,8 +8143,8 @@ async function createAdminSession(accountId, thinkingView, userId, userName, rol
8093
8143
  sessionId: initialSessionId
8094
8144
  };
8095
8145
  }
8096
- var app8 = new Hono();
8097
- app8.get("/", async (c) => {
8146
+ var app9 = new Hono();
8147
+ app9.get("/", async (c) => {
8098
8148
  const signedSessionToken = c.req.query("session_key");
8099
8149
  if (!signedSessionToken) {
8100
8150
  return c.json({ error: "Invalid or expired admin session" }, 401);
@@ -8140,7 +8190,7 @@ app8.get("/", async (c) => {
8140
8190
  sessionId: getSessionIdForSession(cacheKey) ?? null
8141
8191
  });
8142
8192
  });
8143
- app8.post("/", async (c) => {
8193
+ app9.post("/", async (c) => {
8144
8194
  let body;
8145
8195
  try {
8146
8196
  body = await c.req.json();
@@ -8203,21 +8253,21 @@ app8.post("/", async (c) => {
8203
8253
  const payload = await createAdminSession(selected.accountId, selected.config.thinkingView, userId, userName, selected.role, avatar);
8204
8254
  return c.json(payload);
8205
8255
  });
8206
- var session_default = app8;
8256
+ var session_default = app9;
8207
8257
 
8208
8258
  // server/routes/admin/logs.ts
8209
- import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync7 } from "fs";
8210
- import { resolve as resolve10, basename as basename3 } from "path";
8259
+ import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync7 } from "fs";
8260
+ import { resolve as resolve11, basename as basename3 } from "path";
8211
8261
 
8212
8262
  // app/lib/logs-read-resolve.ts
8213
- import { existsSync as existsSync10 } from "fs";
8214
- import { join as join12 } from "path";
8263
+ import { existsSync as existsSync11 } from "fs";
8264
+ import { join as join13 } from "path";
8215
8265
  function resolveSessionLogPaths(filename, logDirs) {
8216
8266
  const tried = [filename];
8217
8267
  const hits = [];
8218
8268
  for (const dir of logDirs) {
8219
- const fullPath = join12(dir, filename);
8220
- if (existsSync10(fullPath)) {
8269
+ const fullPath = join13(dir, filename);
8270
+ if (existsSync11(fullPath)) {
8221
8271
  hits.push({ path: fullPath, dir });
8222
8272
  }
8223
8273
  }
@@ -8226,15 +8276,15 @@ function resolveSessionLogPaths(filename, logDirs) {
8226
8276
 
8227
8277
  // server/routes/admin/logs.ts
8228
8278
  var TAIL_BYTES = 8192;
8229
- var app9 = new Hono();
8230
- app9.get("/", async (c) => {
8279
+ var app10 = new Hono();
8280
+ app10.get("/", async (c) => {
8231
8281
  const fileParam = c.req.query("file");
8232
8282
  const typeParam = c.req.query("type");
8233
8283
  const sessionIdParam = c.req.query("sessionId");
8234
8284
  const cacheKeyParam = c.req.query("cacheKey");
8235
8285
  const download = c.req.query("download") === "1";
8236
8286
  const account = resolveAccount();
8237
- const accountLogDir = account ? resolve10(account.accountDir, "logs") : null;
8287
+ const accountLogDir = account ? resolve11(account.accountDir, "logs") : null;
8238
8288
  const logDirs = [];
8239
8289
  if (accountLogDir) logDirs.push(accountLogDir);
8240
8290
  logDirs.push(LOG_DIR);
@@ -8242,10 +8292,10 @@ app9.get("/", async (c) => {
8242
8292
  const safe = basename3(fileParam);
8243
8293
  const searched = [];
8244
8294
  for (const dir of logDirs) {
8245
- const filePath = resolve10(dir, safe);
8295
+ const filePath = resolve11(dir, safe);
8246
8296
  searched.push(filePath);
8247
8297
  try {
8248
- const buffer = readFileSync13(filePath);
8298
+ const buffer = readFileSync14(filePath);
8249
8299
  const onDiskBytes = statSync7(filePath).size;
8250
8300
  const headers = {
8251
8301
  "Content-Type": "text/plain; charset=utf-8",
@@ -8310,7 +8360,7 @@ app9.get("/", async (c) => {
8310
8360
  console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
8311
8361
  try {
8312
8362
  const filename = basename3(hit.path);
8313
- const buffer = readFileSync13(hit.path);
8363
+ const buffer = readFileSync14(hit.path);
8314
8364
  const onDiskBytes = statSync7(hit.path).size;
8315
8365
  const headers = {
8316
8366
  "Content-Type": "text/plain; charset=utf-8",
@@ -8345,7 +8395,7 @@ app9.get("/", async (c) => {
8345
8395
  const seen = /* @__PURE__ */ new Set();
8346
8396
  const logs = {};
8347
8397
  for (const dir of logDirs) {
8348
- if (!existsSync11(dir)) continue;
8398
+ if (!existsSync12(dir)) continue;
8349
8399
  let files;
8350
8400
  try {
8351
8401
  files = readdirSync7(dir).filter((f) => f.endsWith(".log"));
@@ -8354,10 +8404,10 @@ app9.get("/", async (c) => {
8354
8404
  console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
8355
8405
  continue;
8356
8406
  }
8357
- files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(resolve10(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
8407
+ files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(resolve11(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
8358
8408
  seen.add(name);
8359
8409
  try {
8360
- const content = readFileSync13(resolve10(dir, name));
8410
+ const content = readFileSync14(resolve11(dir, name));
8361
8411
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
8362
8412
  logs[name] = tail.trim() || "(empty)";
8363
8413
  } catch (err) {
@@ -8369,12 +8419,12 @@ app9.get("/", async (c) => {
8369
8419
  }
8370
8420
  return c.json({ logs });
8371
8421
  });
8372
- var logs_default = app9;
8422
+ var logs_default = app10;
8373
8423
 
8374
8424
  // server/routes/admin/claude-info.ts
8375
8425
  import { execFileSync as execFileSync3 } from "child_process";
8376
- var app10 = new Hono();
8377
- app10.get("/", (c) => {
8426
+ var app11 = new Hono();
8427
+ app11.get("/", (c) => {
8378
8428
  let version = "unknown";
8379
8429
  try {
8380
8430
  const raw = execFileSync3("claude", ["--version"], { encoding: "utf-8", timeout: 5e3 });
@@ -8392,14 +8442,14 @@ app10.get("/", (c) => {
8392
8442
  const thinkingView = resolvedAccount?.config.thinkingView ?? "default";
8393
8443
  return c.json({ version, account, model, thinkingView });
8394
8444
  });
8395
- var claude_info_default = app10;
8445
+ var claude_info_default = app11;
8396
8446
 
8397
8447
  // server/routes/admin/attachment.ts
8398
8448
  import { readFile as readFile2, readdir } from "fs/promises";
8399
- import { existsSync as existsSync12 } from "fs";
8400
- import { resolve as resolve11 } from "path";
8401
- var app11 = new Hono();
8402
- app11.get("/:attachmentId", requireAdminSession, async (c) => {
8449
+ import { existsSync as existsSync13 } from "fs";
8450
+ import { resolve as resolve12 } from "path";
8451
+ var app12 = new Hono();
8452
+ app12.get("/:attachmentId", requireAdminSession, async (c) => {
8403
8453
  const attachmentId = c.req.param("attachmentId");
8404
8454
  const cacheKey = c.var.cacheKey;
8405
8455
  const accountId = getAccountIdForSession(cacheKey);
@@ -8409,12 +8459,12 @@ app11.get("/:attachmentId", requireAdminSession, async (c) => {
8409
8459
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
8410
8460
  return new Response("Not found", { status: 404 });
8411
8461
  }
8412
- const dir = resolve11(ATTACHMENTS_ROOT, accountId, attachmentId);
8413
- if (!existsSync12(dir)) {
8462
+ const dir = resolve12(ATTACHMENTS_ROOT, accountId, attachmentId);
8463
+ if (!existsSync13(dir)) {
8414
8464
  return new Response("Not found", { status: 404 });
8415
8465
  }
8416
- const metaPath = resolve11(dir, `${attachmentId}.meta.json`);
8417
- if (!existsSync12(metaPath)) {
8466
+ const metaPath = resolve12(dir, `${attachmentId}.meta.json`);
8467
+ if (!existsSync13(metaPath)) {
8418
8468
  return new Response("Not found", { status: 404 });
8419
8469
  }
8420
8470
  let meta;
@@ -8428,7 +8478,7 @@ app11.get("/:attachmentId", requireAdminSession, async (c) => {
8428
8478
  if (!dataFile) {
8429
8479
  return new Response("Not found", { status: 404 });
8430
8480
  }
8431
- const filePath = resolve11(dir, dataFile);
8481
+ const filePath = resolve12(dir, dataFile);
8432
8482
  const buffer = await readFile2(filePath);
8433
8483
  return new Response(new Uint8Array(buffer), {
8434
8484
  headers: {
@@ -8438,27 +8488,27 @@ app11.get("/:attachmentId", requireAdminSession, async (c) => {
8438
8488
  }
8439
8489
  });
8440
8490
  });
8441
- var attachment_default = app11;
8491
+ var attachment_default = app12;
8442
8492
 
8443
8493
  // server/routes/admin/agents.ts
8444
- import { resolve as resolve12 } from "path";
8445
- import { readdirSync as readdirSync8, readFileSync as readFileSync14, existsSync as existsSync13, rmSync } from "fs";
8446
- var app12 = new Hono();
8447
- app12.get("/", (c) => {
8494
+ import { resolve as resolve13 } from "path";
8495
+ import { readdirSync as readdirSync8, readFileSync as readFileSync15, existsSync as existsSync14, rmSync } from "fs";
8496
+ var app13 = new Hono();
8497
+ app13.get("/", (c) => {
8448
8498
  const account = resolveAccount();
8449
8499
  if (!account) return c.json({ agents: [] });
8450
- const agentsDir = resolve12(account.accountDir, "agents");
8451
- if (!existsSync13(agentsDir)) return c.json({ agents: [] });
8500
+ const agentsDir = resolve13(account.accountDir, "agents");
8501
+ if (!existsSync14(agentsDir)) return c.json({ agents: [] });
8452
8502
  const agents = [];
8453
8503
  try {
8454
8504
  const entries = readdirSync8(agentsDir, { withFileTypes: true });
8455
8505
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
8456
8506
  if (!entry.isDirectory()) continue;
8457
8507
  if (entry.name === "admin") continue;
8458
- const configPath2 = resolve12(agentsDir, entry.name, "config.json");
8459
- if (!existsSync13(configPath2)) continue;
8508
+ const configPath2 = resolve13(agentsDir, entry.name, "config.json");
8509
+ if (!existsSync14(configPath2)) continue;
8460
8510
  try {
8461
- const config = JSON.parse(readFileSync14(configPath2, "utf-8"));
8511
+ const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
8462
8512
  agents.push({
8463
8513
  slug: entry.name,
8464
8514
  displayName: config.displayName ?? entry.name,
@@ -8474,7 +8524,7 @@ app12.get("/", (c) => {
8474
8524
  }
8475
8525
  return c.json({ agents });
8476
8526
  });
8477
- app12.delete("/:slug", async (c) => {
8527
+ app13.delete("/:slug", async (c) => {
8478
8528
  const slug = c.req.param("slug");
8479
8529
  const account = resolveAccount();
8480
8530
  if (!account) return c.json({ error: "No account resolved" }, 400);
@@ -8484,8 +8534,8 @@ app12.delete("/:slug", async (c) => {
8484
8534
  if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
8485
8535
  return c.json({ error: "Invalid agent slug" }, 400);
8486
8536
  }
8487
- const agentDir = resolve12(account.accountDir, "agents", slug);
8488
- if (!existsSync13(agentDir)) {
8537
+ const agentDir = resolve13(account.accountDir, "agents", slug);
8538
+ if (!existsSync14(agentDir)) {
8489
8539
  return c.json({ error: "Agent not found" }, 404);
8490
8540
  }
8491
8541
  try {
@@ -8504,7 +8554,7 @@ app12.delete("/:slug", async (c) => {
8504
8554
  return c.json({ error: "Failed to delete agent" }, 500);
8505
8555
  }
8506
8556
  });
8507
- app12.post("/:slug/project", async (c) => {
8557
+ app13.post("/:slug/project", async (c) => {
8508
8558
  const slug = c.req.param("slug");
8509
8559
  const account = resolveAccount();
8510
8560
  if (!account) return c.json({ error: "No account resolved" }, 400);
@@ -8514,8 +8564,8 @@ app12.post("/:slug/project", async (c) => {
8514
8564
  if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
8515
8565
  return c.json({ error: "Invalid agent slug" }, 400);
8516
8566
  }
8517
- const agentDir = resolve12(account.accountDir, "agents", slug);
8518
- if (!existsSync13(agentDir)) {
8567
+ const agentDir = resolve13(account.accountDir, "agents", slug);
8568
+ if (!existsSync14(agentDir)) {
8519
8569
  return c.json({ error: "Agent not found on disk" }, 404);
8520
8570
  }
8521
8571
  try {
@@ -8526,12 +8576,12 @@ app12.post("/:slug/project", async (c) => {
8526
8576
  return c.json({ error: `Projection failed: ${msg}` }, 500);
8527
8577
  }
8528
8578
  });
8529
- var agents_default = app12;
8579
+ var agents_default = app13;
8530
8580
 
8531
8581
  // server/routes/admin/sessions.ts
8532
8582
  import crypto2 from "crypto";
8533
8583
  import { resolve as resolvePath } from "path";
8534
- import { existsSync as existsSync14, mkdirSync as mkdirSync3, createWriteStream } from "fs";
8584
+ import { existsSync as existsSync15, mkdirSync as mkdirSync3, createWriteStream } from "fs";
8535
8585
 
8536
8586
  // ../lib/admin-conversation-purge/src/index.ts
8537
8587
  async function purgeAdminConversationJsonls(args) {
@@ -8669,7 +8719,7 @@ var replayJsonl = (..._args) => null;
8669
8719
  var resolveJsonlPath = (..._args) => null;
8670
8720
 
8671
8721
  // server/routes/admin/sessions.ts
8672
- import { homedir } from "os";
8722
+ import { homedir as homedir2 } from "os";
8673
8723
 
8674
8724
  // app/lib/claude-agent/component-attachment.ts
8675
8725
  var pickComponentBytes = (..._args) => null;
@@ -8691,7 +8741,7 @@ function validateAndShapeAttachments(raws, conversationAccountId, sessionId, mes
8691
8741
  let reason = null;
8692
8742
  if (!a.attachmentId || !a.filename || !a.mimeType || !a.storagePath) reason = "schema-fail";
8693
8743
  else if (a.accountId !== conversationAccountId) reason = "account-mismatch";
8694
- else if (!existsSync14(a.storagePath)) reason = "missing-file";
8744
+ else if (!existsSync15(a.storagePath)) reason = "missing-file";
8695
8745
  if (reason) {
8696
8746
  invalid++;
8697
8747
  try {
@@ -8798,8 +8848,8 @@ function formatAge(updatedAtStr) {
8798
8848
  return "unknown";
8799
8849
  }
8800
8850
  }
8801
- var app13 = new Hono();
8802
- app13.get("/", requireAdminSession, async (c) => {
8851
+ var app14 = new Hono();
8852
+ app14.get("/", requireAdminSession, async (c) => {
8803
8853
  const cacheKey = c.var.cacheKey;
8804
8854
  const accountId = getAccountIdForSession(cacheKey);
8805
8855
  if (!accountId) return c.json({ error: "Account not found for session" }, 401);
@@ -8829,7 +8879,7 @@ app13.get("/", requireAdminSession, async (c) => {
8829
8879
  return c.json({ error: "Failed to fetch sessions" }, 500);
8830
8880
  }
8831
8881
  });
8832
- app13.post("/new", requireAdminSession, async (c) => {
8882
+ app14.post("/new", requireAdminSession, async (c) => {
8833
8883
  const oldCacheKey = c.var.cacheKey;
8834
8884
  console.log(`[admin-chat] new-conversation outcome=ingress cacheKey=${oldCacheKey.slice(0, 8)}`);
8835
8885
  const accountId = getAccountIdForSession(oldCacheKey);
@@ -8846,7 +8896,7 @@ app13.post("/new", requireAdminSession, async (c) => {
8846
8896
  console.log(`[admin-chat] new-conversation outcome=ok oldKey=${oldCacheKey.slice(0, 8)} newKey=${newCacheKey.slice(0, 8)}`);
8847
8897
  return c.json({ session_key: newSignedSessionToken, sessionId: null });
8848
8898
  });
8849
- app13.post("/switch", requireAdminSession, async (c) => {
8899
+ app14.post("/switch", requireAdminSession, async (c) => {
8850
8900
  const cacheKey = c.var.cacheKey;
8851
8901
  const accountId = getAccountIdForSession(cacheKey);
8852
8902
  const userId = getUserIdForSession(cacheKey);
@@ -8874,7 +8924,7 @@ app13.post("/switch", requireAdminSession, async (c) => {
8874
8924
  console.log(`[session-switch] from=${cacheKey.slice(0, 8)} to=${targetCacheKey.slice(0, 8)} targetConvId=${targetSessionId?.slice(0, 8) ?? "none"} accountId=${accountId.slice(0, 8)}`);
8875
8925
  return c.json({ session_key: targetSignedSessionToken, sessionId: targetSessionId });
8876
8926
  });
8877
- app13.delete("/:id", requireAdminSession, async (c) => {
8927
+ app14.delete("/:id", requireAdminSession, async (c) => {
8878
8928
  const sessionId = c.req.param("id");
8879
8929
  const cacheKey = c.var.cacheKey;
8880
8930
  const accountId = getAccountIdForSession(cacheKey);
@@ -8919,7 +8969,7 @@ app13.delete("/:id", requireAdminSession, async (c) => {
8919
8969
  500
8920
8970
  );
8921
8971
  });
8922
- app13.post("/:id/resume", async (c) => {
8972
+ app14.post("/:id/resume", async (c) => {
8923
8973
  const sessionId = c.req.param("id");
8924
8974
  const t0 = Date.now();
8925
8975
  const signedSessionToken = c.req.query("session_key") ?? "";
@@ -8984,7 +9034,7 @@ app13.post("/:id/resume", async (c) => {
8984
9034
  return c.json({ error: "Failed to load conversation messages" }, 500);
8985
9035
  }
8986
9036
  if (persistedAgentSessionId) {
8987
- const jsonlPath = resolveJsonlPath(homedir(), resolvePath(ACCOUNTS_DIR, accountId), persistedAgentSessionId);
9037
+ const jsonlPath = resolveJsonlPath(homedir2(), resolvePath(ACCOUNTS_DIR, accountId), persistedAgentSessionId);
8988
9038
  const replay = replayJsonl(jsonlPath);
8989
9039
  jsonlMissing = replay.jsonlMissing;
8990
9040
  jsonlMalformedLines = replay.malformedLines;
@@ -9188,7 +9238,7 @@ app13.post("/:id/resume", async (c) => {
9188
9238
  }
9189
9239
  });
9190
9240
  });
9191
- app13.put("/:id/label", requireAdminSession, async (c) => {
9241
+ app14.put("/:id/label", requireAdminSession, async (c) => {
9192
9242
  const sessionId = c.req.param("id");
9193
9243
  const cacheKey = c.var.cacheKey;
9194
9244
  let body;
@@ -9214,11 +9264,11 @@ app13.put("/:id/label", requireAdminSession, async (c) => {
9214
9264
  return c.json({ error: "Failed to rename session" }, 500);
9215
9265
  }
9216
9266
  });
9217
- var sessions_default = app13;
9267
+ var sessions_default = app14;
9218
9268
 
9219
9269
  // app/lib/claude-agent/spawn-context.ts
9220
- import { existsSync as existsSync15, readFileSync as readFileSync15, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
9221
- import { dirname as dirname3, resolve as resolve13, join as join13 } from "path";
9270
+ import { existsSync as existsSync16, readFileSync as readFileSync16, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
9271
+ import { dirname as dirname3, resolve as resolve14, join as join14 } from "path";
9222
9272
  async function resolveOwnerProfileBlock(accountId, userId) {
9223
9273
  if (!userId) return { ok: false, reason: "missing-user-id" };
9224
9274
  try {
@@ -9247,14 +9297,14 @@ function frontmatterField(manifest, field) {
9247
9297
  function extractPluginToolDescriptions(pluginDir) {
9248
9298
  const out = /* @__PURE__ */ new Map();
9249
9299
  const candidates = [
9250
- join13(pluginDir, "mcp/dist/index.js"),
9251
- join13(pluginDir, "mcp/src/index.ts")
9300
+ join14(pluginDir, "mcp/dist/index.js"),
9301
+ join14(pluginDir, "mcp/src/index.ts")
9252
9302
  ];
9253
9303
  let raw = null;
9254
9304
  for (const path2 of candidates) {
9255
- if (!existsSync15(path2)) continue;
9305
+ if (!existsSync16(path2)) continue;
9256
9306
  try {
9257
- raw = readFileSync15(path2, "utf-8");
9307
+ raw = readFileSync16(path2, "utf-8");
9258
9308
  break;
9259
9309
  } catch {
9260
9310
  }
@@ -9303,24 +9353,24 @@ function parseSkillPathsFromFrontmatter(fm) {
9303
9353
  }
9304
9354
  function listPluginDirs() {
9305
9355
  const out = /* @__PURE__ */ new Map();
9306
- const platformPlugins = resolve13(PLATFORM_ROOT, "plugins");
9307
- if (existsSync15(platformPlugins)) {
9356
+ const platformPlugins = resolve14(PLATFORM_ROOT, "plugins");
9357
+ if (existsSync16(platformPlugins)) {
9308
9358
  for (const name of readdirSync9(platformPlugins)) {
9309
- const dir = join13(platformPlugins, name);
9359
+ const dir = join14(platformPlugins, name);
9310
9360
  try {
9311
9361
  if (statSync8(dir).isDirectory()) out.set(name, dir);
9312
9362
  } catch {
9313
9363
  }
9314
9364
  }
9315
9365
  }
9316
- const premiumRoot = resolve13(dirname3(PLATFORM_ROOT), "premium-plugins");
9317
- if (existsSync15(premiumRoot)) {
9366
+ const premiumRoot = resolve14(dirname3(PLATFORM_ROOT), "premium-plugins");
9367
+ if (existsSync16(premiumRoot)) {
9318
9368
  for (const bundle of readdirSync9(premiumRoot)) {
9319
- const bundlePlugins = join13(premiumRoot, bundle, "plugins");
9320
- if (!existsSync15(bundlePlugins)) continue;
9369
+ const bundlePlugins = join14(premiumRoot, bundle, "plugins");
9370
+ if (!existsSync16(bundlePlugins)) continue;
9321
9371
  try {
9322
9372
  for (const name of readdirSync9(bundlePlugins)) {
9323
- const dir = join13(bundlePlugins, name);
9373
+ const dir = join14(bundlePlugins, name);
9324
9374
  try {
9325
9375
  if (statSync8(dir).isDirectory()) out.set(name, dir);
9326
9376
  } catch {
@@ -9333,10 +9383,10 @@ function listPluginDirs() {
9333
9383
  return out;
9334
9384
  }
9335
9385
  function readEnabledPlugins(accountId) {
9336
- const accountFile = resolve13(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
9337
- if (!existsSync15(accountFile)) return /* @__PURE__ */ new Set();
9386
+ const accountFile = resolve14(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
9387
+ if (!existsSync16(accountFile)) return /* @__PURE__ */ new Set();
9338
9388
  try {
9339
- const parsed = JSON.parse(readFileSync15(accountFile, "utf-8"));
9389
+ const parsed = JSON.parse(readFileSync16(accountFile, "utf-8"));
9340
9390
  return new Set(
9341
9391
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
9342
9392
  );
@@ -9345,10 +9395,10 @@ function readEnabledPlugins(accountId) {
9345
9395
  }
9346
9396
  }
9347
9397
  function readFrontmatter(path2) {
9348
- if (!existsSync15(path2)) return null;
9398
+ if (!existsSync16(path2)) return null;
9349
9399
  let raw;
9350
9400
  try {
9351
- raw = readFileSync15(path2, "utf-8");
9401
+ raw = readFileSync16(path2, "utf-8");
9352
9402
  } catch {
9353
9403
  return null;
9354
9404
  }
@@ -9363,7 +9413,7 @@ function computeActivePlugins(accountId) {
9363
9413
  for (const name of Array.from(enabled).sort()) {
9364
9414
  const dir = pluginDirs.get(name);
9365
9415
  if (!dir) continue;
9366
- const fm = readFrontmatter(join13(dir, "PLUGIN.md"));
9416
+ const fm = readFrontmatter(join14(dir, "PLUGIN.md"));
9367
9417
  if (!fm) continue;
9368
9418
  const description = frontmatterField(`---
9369
9419
  ${fm}
@@ -9380,7 +9430,7 @@ ${fm}
9380
9430
  `plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
9381
9431
  );
9382
9432
  if (toolNames.length > 0 && descMap.size === 0) {
9383
- const hasSource = existsSync15(join13(dir, "mcp/dist/index.js")) || existsSync15(join13(dir, "mcp/src/index.ts"));
9433
+ const hasSource = existsSync16(join14(dir, "mcp/dist/index.js")) || existsSync16(join14(dir, "mcp/src/index.ts"));
9384
9434
  if (hasSource) {
9385
9435
  console.warn(
9386
9436
  `[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
@@ -9390,7 +9440,7 @@ ${fm}
9390
9440
  const skillPaths = parseSkillPathsFromFrontmatter(fm);
9391
9441
  const skills = [];
9392
9442
  for (const relPath of skillPaths) {
9393
- const skillPath = join13(dir, relPath);
9443
+ const skillPath = join14(dir, relPath);
9394
9444
  const skillFm = readFrontmatter(skillPath);
9395
9445
  if (!skillFm) continue;
9396
9446
  const skillName = frontmatterField(`---
@@ -9406,8 +9456,8 @@ ${skillFm}
9406
9456
  return out;
9407
9457
  }
9408
9458
  function computeSpecialistDomains(accountId) {
9409
- const specialistsDir = resolve13(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
9410
- if (!existsSync15(specialistsDir)) return [];
9459
+ const specialistsDir = resolve14(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
9460
+ if (!existsSync16(specialistsDir)) return [];
9411
9461
  let entries;
9412
9462
  try {
9413
9463
  entries = readdirSync9(specialistsDir);
@@ -9434,7 +9484,7 @@ function computeSpecialistDomains(accountId) {
9434
9484
  const out = [];
9435
9485
  for (const file of entries.sort()) {
9436
9486
  if (!file.endsWith(".md")) continue;
9437
- const fm = readFrontmatter(join13(specialistsDir, file));
9487
+ const fm = readFrontmatter(join14(specialistsDir, file));
9438
9488
  if (!fm) continue;
9439
9489
  const wrapped = `---
9440
9490
  ${fm}
@@ -9452,11 +9502,11 @@ ${fm}
9452
9502
  return out;
9453
9503
  }
9454
9504
  function computeDormantPlugins(accountId) {
9455
- const accountFile = resolve13(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
9456
- if (!existsSync15(accountFile)) return [];
9505
+ const accountFile = resolve14(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
9506
+ if (!existsSync16(accountFile)) return [];
9457
9507
  let enabled;
9458
9508
  try {
9459
- const raw = readFileSync15(accountFile, "utf-8");
9509
+ const raw = readFileSync16(accountFile, "utf-8");
9460
9510
  const parsed = JSON.parse(raw);
9461
9511
  enabled = new Set(
9462
9512
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
@@ -9467,8 +9517,8 @@ function computeDormantPlugins(accountId) {
9467
9517
  );
9468
9518
  return [];
9469
9519
  }
9470
- const pluginsDir = resolve13(PLATFORM_ROOT, "plugins");
9471
- if (!existsSync15(pluginsDir)) return [];
9520
+ const pluginsDir = resolve14(PLATFORM_ROOT, "plugins");
9521
+ if (!existsSync16(pluginsDir)) return [];
9472
9522
  let entries;
9473
9523
  try {
9474
9524
  entries = readdirSync9(pluginsDir);
@@ -9478,11 +9528,11 @@ function computeDormantPlugins(accountId) {
9478
9528
  const out = [];
9479
9529
  for (const name of entries) {
9480
9530
  if (enabled.has(name)) continue;
9481
- const manifestPath = resolve13(pluginsDir, name, "PLUGIN.md");
9482
- if (!existsSync15(manifestPath)) continue;
9531
+ const manifestPath = resolve14(pluginsDir, name, "PLUGIN.md");
9532
+ if (!existsSync16(manifestPath)) continue;
9483
9533
  let manifest;
9484
9534
  try {
9485
- manifest = readFileSync15(manifestPath, "utf-8");
9535
+ manifest = readFileSync16(manifestPath, "utf-8");
9486
9536
  } catch {
9487
9537
  continue;
9488
9538
  }
@@ -9496,13 +9546,13 @@ function computeDormantPlugins(accountId) {
9496
9546
  }
9497
9547
 
9498
9548
  // server/routes/admin/claude-sessions.ts
9499
- import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
9500
- import { resolve as resolve14 } from "path";
9549
+ import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
9550
+ import { resolve as resolve15 } from "path";
9501
9551
  function readTunnelState(brandConfigDir) {
9502
9552
  const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
9503
- if (!existsSync16(statePath)) return null;
9553
+ if (!existsSync17(statePath)) return null;
9504
9554
  try {
9505
- const parsed = JSON.parse(readFileSync16(statePath, "utf-8"));
9555
+ const parsed = JSON.parse(readFileSync17(statePath, "utf-8"));
9506
9556
  const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
9507
9557
  const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
9508
9558
  const domain = typeof parsed.domain === "string" ? parsed.domain : null;
@@ -9513,10 +9563,10 @@ function readTunnelState(brandConfigDir) {
9513
9563
  }
9514
9564
  }
9515
9565
  function resolveTunnelUrl() {
9516
- const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
9566
+ const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
9517
9567
  let brand;
9518
9568
  try {
9519
- brand = JSON.parse(readFileSync16(resolve14(platformRoot2, "config", "brand.json"), "utf-8"));
9569
+ brand = JSON.parse(readFileSync17(resolve15(platformRoot2, "config", "brand.json"), "utf-8"));
9520
9570
  } catch {
9521
9571
  return null;
9522
9572
  }
@@ -9527,7 +9577,7 @@ function resolveTunnelUrl() {
9527
9577
  if (!state) return null;
9528
9578
  return `https://${hostname2}.${state.domain}`;
9529
9579
  }
9530
- var TAG21 = "[claude-session-manager:wrapper]";
9580
+ var TAG22 = "[claude-session-manager:wrapper]";
9531
9581
  async function refuseIfClaudeAuthDead(c, route, sessionId) {
9532
9582
  const auth = await ensureAuth();
9533
9583
  if (auth.status !== "dead" && auth.status !== "missing") return null;
@@ -9539,18 +9589,18 @@ async function refuseIfClaudeAuthDead(c, route, sessionId) {
9539
9589
  503
9540
9590
  );
9541
9591
  }
9542
- var app14 = new Hono();
9592
+ var app15 = new Hono();
9543
9593
  async function performSpawnWithInitialMessage(args) {
9544
9594
  const ownerStart = Date.now();
9545
9595
  const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
9546
9596
  const ownerMs = Date.now() - ownerStart;
9547
9597
  const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
9548
- console.log(`${TAG21} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
9598
+ console.log(`${TAG22} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
9549
9599
  const dormantPlugins = computeDormantPlugins(args.senderId);
9550
9600
  const activePlugins = computeActivePlugins(args.senderId);
9551
9601
  const specialistDomains = computeSpecialistDomains(args.senderId);
9552
9602
  const tunnelUrl = resolveTunnelUrl();
9553
- console.log(`${TAG21} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
9603
+ console.log(`${TAG22} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
9554
9604
  const upstreamPayload = JSON.stringify({
9555
9605
  senderId: args.senderId,
9556
9606
  // Task 205 — pass userId through to the manager so it lands as
@@ -9577,24 +9627,24 @@ async function performSpawnWithInitialMessage(args) {
9577
9627
  // unshapely values.
9578
9628
  conversationNodeId: args.conversationNodeId
9579
9629
  });
9580
- console.log(`${TAG21} forward-spawn-start managerBase=${managerBase3("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
9630
+ console.log(`${TAG22} forward-spawn-start managerBase=${managerBase3("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
9581
9631
  const forwardStart = Date.now();
9582
9632
  const upstream = await fetch(`${managerBase3("claude-session-manager:wrapper")}/public-spawn`, {
9583
9633
  method: "POST",
9584
9634
  headers: { "content-type": "application/json" },
9585
9635
  body: upstreamPayload
9586
9636
  }).catch((err) => {
9587
- console.error(`${TAG21} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
9637
+ console.error(`${TAG22} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
9588
9638
  return null;
9589
9639
  });
9590
9640
  if (!upstream) return {
9591
9641
  response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
9592
9642
  claudeSessionId: null
9593
9643
  };
9594
- console.log(`${TAG21} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
9644
+ console.log(`${TAG22} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
9595
9645
  if (args.initialMessage) {
9596
9646
  const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
9597
- console.log(`${TAG21} initial-message-inlined bytes=${inputBytes}`);
9647
+ console.log(`${TAG22} initial-message-inlined bytes=${inputBytes}`);
9598
9648
  }
9599
9649
  const bodyText = await upstream.text().catch(() => "");
9600
9650
  let claudeSessionId = null;
@@ -9616,8 +9666,8 @@ function mintTranscriptEdge(sessionId, claudeSessionId, accountId) {
9616
9666
  if (!sessionId || !claudeSessionId) return;
9617
9667
  void linkConversationTranscript(sessionId, claudeSessionId, accountId);
9618
9668
  }
9619
- app14.use("*", requireAdminSession);
9620
- app14.post("/", async (c) => {
9669
+ app15.use("*", requireAdminSession);
9670
+ app15.post("/", async (c) => {
9621
9671
  const routeStart = Date.now();
9622
9672
  const cacheKey = c.get("cacheKey") ?? "";
9623
9673
  let body = {};
@@ -9629,7 +9679,7 @@ app14.post("/", async (c) => {
9629
9679
  if (refusal) return refusal;
9630
9680
  const senderId = getAccountIdForSession(cacheKey) ?? "";
9631
9681
  if (!senderId) {
9632
- console.error(`${TAG21} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
9682
+ console.error(`${TAG22} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
9633
9683
  return c.json({ error: "admin-account-not-resolved" }, 500);
9634
9684
  }
9635
9685
  const userId = getUserIdForSession(cacheKey) ?? void 0;
@@ -9638,7 +9688,7 @@ app14.post("/", async (c) => {
9638
9688
  const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
9639
9689
  const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
9640
9690
  const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
9641
- console.log(`${TAG21} spawn-request-in surface=cookie accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
9691
+ console.log(`${TAG22} spawn-request-in surface=cookie accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
9642
9692
  const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
9643
9693
  const { response, claudeSessionId } = await performSpawnWithInitialMessage({
9644
9694
  senderId,
@@ -9657,24 +9707,24 @@ app14.post("/", async (c) => {
9657
9707
  claudeSessionId,
9658
9708
  senderId
9659
9709
  );
9660
- console.log(`${TAG21} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
9710
+ console.log(`${TAG22} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
9661
9711
  return response;
9662
9712
  });
9663
- var claude_sessions_default = app14;
9713
+ var claude_sessions_default = app15;
9664
9714
 
9665
9715
  // server/routes/admin/log-ingest.ts
9666
- var TAG22 = "[log-ingest]";
9716
+ var TAG23 = "[log-ingest]";
9667
9717
  var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
9668
9718
  var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
9669
9719
  var MAX_LINE_BYTES = 4096;
9670
- var app15 = new Hono();
9720
+ var app16 = new Hono();
9671
9721
  function isLoopbackAddr(addr) {
9672
9722
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
9673
9723
  }
9674
- app15.post("/", async (c) => {
9724
+ app16.post("/", async (c) => {
9675
9725
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
9676
9726
  if (!isLoopbackAddr(remoteAddr)) {
9677
- console.error(`${TAG22} reject reason=non-loopback remoteAddr=${remoteAddr}`);
9727
+ console.error(`${TAG23} reject reason=non-loopback remoteAddr=${remoteAddr}`);
9678
9728
  return c.json({ error: "log-ingest-loopback-only" }, 403);
9679
9729
  }
9680
9730
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
@@ -9683,7 +9733,7 @@ app15.post("/", async (c) => {
9683
9733
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
9684
9734
  const offender = tokens.find((t) => !isLoopbackAddr(t));
9685
9735
  if (offender !== void 0) {
9686
- console.error(`${TAG22} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
9736
+ console.error(`${TAG23} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
9687
9737
  return c.json({ error: "log-ingest-loopback-only" }, 403);
9688
9738
  }
9689
9739
  }
@@ -9714,7 +9764,7 @@ app15.post("/", async (c) => {
9714
9764
  else console.log(formatted);
9715
9765
  return c.json({ ok: true }, 200);
9716
9766
  });
9717
- var log_ingest_default = app15;
9767
+ var log_ingest_default = app16;
9718
9768
 
9719
9769
  // server/routes/admin/events.ts
9720
9770
  var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
@@ -9723,20 +9773,20 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
9723
9773
  "device-url:vnc-surface-shown",
9724
9774
  "device-url:malformed"
9725
9775
  ]);
9726
- var app16 = new Hono();
9727
- app16.post("/", async (c) => {
9728
- const TAG30 = "[admin:events]";
9776
+ var app17 = new Hono();
9777
+ app17.post("/", async (c) => {
9778
+ const TAG31 = "[admin:events]";
9729
9779
  let body;
9730
9780
  try {
9731
9781
  body = await c.req.json();
9732
9782
  } catch (err) {
9733
9783
  const detail = err instanceof Error ? err.message : String(err);
9734
- console.error(`${TAG30} reject reason=body-not-json detail=${detail}`);
9784
+ console.error(`${TAG31} reject reason=body-not-json detail=${detail}`);
9735
9785
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
9736
9786
  }
9737
9787
  const event = typeof body.event === "string" ? body.event : "";
9738
9788
  if (!ALLOWED_EVENTS.has(event)) {
9739
- console.error(`${TAG30} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
9789
+ console.error(`${TAG31} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
9740
9790
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
9741
9791
  }
9742
9792
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -9755,21 +9805,21 @@ app16.post("/", async (c) => {
9755
9805
  console.error(`[${event}] ${formatted}`);
9756
9806
  return c.json({ ok: true });
9757
9807
  });
9758
- var events_default = app16;
9808
+ var events_default = app17;
9759
9809
 
9760
9810
  // server/routes/admin/files.ts
9761
9811
  import { createReadStream as createReadStream2 } from "fs";
9762
9812
  import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
9763
9813
  import { realpathSync as realpathSync4 } from "fs";
9764
- import { basename as basename5, dirname as dirname4, join as join15, resolve as resolve17, sep as sep5 } from "path";
9814
+ import { basename as basename5, dirname as dirname4, join as join16, resolve as resolve18, sep as sep5 } from "path";
9765
9815
  import { Readable as Readable2 } from "stream";
9766
9816
 
9767
9817
  // app/lib/data-path.ts
9768
9818
  import { realpathSync as realpathSync3 } from "fs";
9769
- import { resolve as resolve15, normalize, sep as sep3, relative } from "path";
9770
- var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve15(process.cwd(), "../platform");
9771
- var DATA_ROOT = resolve15(PLATFORM_ROOT5, "..", "data");
9772
- var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve15(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
9819
+ import { resolve as resolve16, normalize, sep as sep3, relative } from "path";
9820
+ var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve16(process.cwd(), "../platform");
9821
+ var DATA_ROOT = resolve16(PLATFORM_ROOT5, "..", "data");
9822
+ var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve16(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
9773
9823
  var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["uploads", "accounts"]);
9774
9824
  var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9775
9825
  function crossesForeignAccountPartition(relPath, accountId) {
@@ -9782,7 +9832,7 @@ function crossesForeignAccountPartition(relPath, accountId) {
9782
9832
  }
9783
9833
  function resolveUnderRoot(raw, rootAbs, rootLabel) {
9784
9834
  const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
9785
- const absolute = resolve15(rootAbs, cleaned);
9835
+ const absolute = resolve16(rootAbs, cleaned);
9786
9836
  let rootReal;
9787
9837
  try {
9788
9838
  rootReal = realpathSync3(rootAbs);
@@ -10141,7 +10191,7 @@ async function cascadeDeleteDocument(params) {
10141
10191
 
10142
10192
  // app/lib/file-index.ts
10143
10193
  import * as fsp from "fs/promises";
10144
- import { resolve as resolve16, relative as relative2, join as join14, basename as basename4, extname as extname2, sep as sep4 } from "path";
10194
+ import { resolve as resolve17, relative as relative2, join as join15, basename as basename4, extname as extname2, sep as sep4 } from "path";
10145
10195
  import { tmpdir as tmpdir2 } from "os";
10146
10196
  import { execFile as execFile2 } from "child_process";
10147
10197
  import { promisify as promisify2 } from "util";
@@ -10212,7 +10262,7 @@ async function extractPdf(absolute) {
10212
10262
  return stdout.trim();
10213
10263
  }
10214
10264
  async function ocrPdf(absolute) {
10215
- const outPdf = join14(tmpdir2(), `file-index-ocr-${process.pid}-${Date.now()}.pdf`);
10265
+ const outPdf = join15(tmpdir2(), `file-index-ocr-${process.pid}-${Date.now()}.pdf`);
10216
10266
  try {
10217
10267
  await execFileAsync2(
10218
10268
  "ocrmypdf",
@@ -10274,7 +10324,7 @@ async function readDisplayName(absolute) {
10274
10324
  const stem = dot === -1 ? base : base.slice(0, dot);
10275
10325
  if (base === `${stem}.meta.json`) return null;
10276
10326
  try {
10277
- const raw = await fsp.readFile(join14(dir, `${stem}.meta.json`), "utf-8");
10327
+ const raw = await fsp.readFile(join15(dir, `${stem}.meta.json`), "utf-8");
10278
10328
  const meta = JSON.parse(raw);
10279
10329
  const dn = meta.displayName ?? meta.filename;
10280
10330
  return typeof dn === "string" && dn.length > 0 ? dn : null;
@@ -10294,7 +10344,7 @@ async function walkSubtree(root, dataRoot, out) {
10294
10344
  let clean = true;
10295
10345
  for (const entry of entries) {
10296
10346
  if (entry.isSymbolicLink()) continue;
10297
- const abs = join14(root, entry.name);
10347
+ const abs = join15(root, entry.name);
10298
10348
  if (entry.isDirectory()) {
10299
10349
  const sub = await walkSubtree(abs, dataRoot, out);
10300
10350
  if (!sub.clean) clean = false;
@@ -10449,8 +10499,8 @@ async function reconcileFileIndex(accountId, opts) {
10449
10499
  return withSession(opts, async (session) => {
10450
10500
  const walked = [];
10451
10501
  const subtreeRoots = [
10452
- resolve16(dataRoot, "uploads", accountId),
10453
- resolve16(dataRoot, "accounts", accountId)
10502
+ resolve17(dataRoot, "uploads", accountId),
10503
+ resolve17(dataRoot, "accounts", accountId)
10454
10504
  ];
10455
10505
  let clean = true;
10456
10506
  for (const root of subtreeRoots) {
@@ -10531,7 +10581,7 @@ async function reconcileFileIndexDebounced(accountId, opts) {
10531
10581
  async function indexFile(accountId, relativePath, opts) {
10532
10582
  const dataRoot = opts?.dataRoot ?? DATA_ROOT;
10533
10583
  const embed2 = opts?.embed ?? embed;
10534
- const absolute = resolve16(dataRoot, relativePath);
10584
+ const absolute = resolve17(dataRoot, relativePath);
10535
10585
  await withSession(opts, async (session) => {
10536
10586
  const st = await fsp.stat(absolute);
10537
10587
  const wf = {
@@ -10565,7 +10615,7 @@ async function dropFileIndex(accountId, relativePath, opts) {
10565
10615
  var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10566
10616
  async function readMeta(absDir, baseName) {
10567
10617
  try {
10568
- const raw = await readFile4(join15(absDir, `${baseName}.meta.json`), "utf8");
10618
+ const raw = await readFile4(join16(absDir, `${baseName}.meta.json`), "utf8");
10569
10619
  const parsed = JSON.parse(raw);
10570
10620
  if (typeof parsed?.filename === "string") {
10571
10621
  return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
@@ -10576,7 +10626,7 @@ async function readMeta(absDir, baseName) {
10576
10626
  }
10577
10627
  async function readAccountNames() {
10578
10628
  const map = /* @__PURE__ */ new Map();
10579
- const accountsDir = resolve17(DATA_ROOT, "accounts");
10629
+ const accountsDir = resolve18(DATA_ROOT, "accounts");
10580
10630
  let names;
10581
10631
  try {
10582
10632
  names = await readdir3(accountsDir);
@@ -10585,7 +10635,7 @@ async function readAccountNames() {
10585
10635
  }
10586
10636
  for (const name of names) {
10587
10637
  if (!UUID_RE3.test(name)) continue;
10588
- const configPath2 = resolve17(accountsDir, name, "account.json");
10638
+ const configPath2 = resolve18(accountsDir, name, "account.json");
10589
10639
  try {
10590
10640
  const raw = await readFile4(configPath2, "utf8");
10591
10641
  const parsed = JSON.parse(raw);
@@ -10603,7 +10653,7 @@ async function readAccountNames() {
10603
10653
  }
10604
10654
  async function enrich(absolute, entry, accountNames) {
10605
10655
  if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
10606
- const meta = await readMeta(join15(absolute, entry.name), entry.name);
10656
+ const meta = await readMeta(join16(absolute, entry.name), entry.name);
10607
10657
  if (meta?.filename) {
10608
10658
  entry.displayName = meta.filename;
10609
10659
  entry.mimeType = meta.mimeType;
@@ -10663,8 +10713,8 @@ async function knowledgeDocOwnedByAccount(accountId, attachmentId) {
10663
10713
  await session.close();
10664
10714
  }
10665
10715
  }
10666
- var app17 = new Hono();
10667
- app17.get("/", requireAdminSession, async (c) => {
10716
+ var app18 = new Hono();
10717
+ app18.get("/", requireAdminSession, async (c) => {
10668
10718
  const cacheKey = c.var.cacheKey;
10669
10719
  const accountId = getAccountIdForSession(cacheKey);
10670
10720
  if (!accountId) {
@@ -10701,7 +10751,7 @@ app17.get("/", requireAdminSession, async (c) => {
10701
10751
  continue;
10702
10752
  }
10703
10753
  try {
10704
- const entryPath = join15(absolute, name);
10754
+ const entryPath = join16(absolute, name);
10705
10755
  const s = await stat4(entryPath);
10706
10756
  entries.push({
10707
10757
  name,
@@ -10730,7 +10780,7 @@ app17.get("/", requireAdminSession, async (c) => {
10730
10780
  return c.json({ error: message }, 500);
10731
10781
  }
10732
10782
  });
10733
- app17.get("/download", requireAdminSession, async (c) => {
10783
+ app18.get("/download", requireAdminSession, async (c) => {
10734
10784
  const cacheKey = c.var.cacheKey;
10735
10785
  const accountId = getAccountIdForSession(cacheKey);
10736
10786
  if (!accountId) {
@@ -10794,7 +10844,7 @@ app17.get("/download", requireAdminSession, async (c) => {
10794
10844
  return c.json({ error: message }, 500);
10795
10845
  }
10796
10846
  });
10797
- app17.post("/upload", requireAdminSession, async (c) => {
10847
+ app18.post("/upload", requireAdminSession, async (c) => {
10798
10848
  const cacheKey = c.var.cacheKey;
10799
10849
  const accountId = getAccountIdForSession(cacheKey);
10800
10850
  if (!accountId) {
@@ -10826,8 +10876,8 @@ app17.post("/upload", requireAdminSession, async (c) => {
10826
10876
  }
10827
10877
  const safeName = basename5(file.name).replace(/[\0/\\]/g, "_");
10828
10878
  const finalName = `${Date.now()}-${safeName}`;
10829
- const destDir = resolve17(DATA_ROOT, "uploads", accountId);
10830
- const destPath = resolve17(destDir, finalName);
10879
+ const destDir = resolve18(DATA_ROOT, "uploads", accountId);
10880
+ const destPath = resolve18(destDir, finalName);
10831
10881
  try {
10832
10882
  await mkdir3(destDir, { recursive: true });
10833
10883
  const dataRootReal = realpathSync4(DATA_ROOT);
@@ -10855,7 +10905,7 @@ app17.post("/upload", requireAdminSession, async (c) => {
10855
10905
  mimeType: file.type
10856
10906
  });
10857
10907
  });
10858
- app17.delete("/", requireAdminSession, async (c) => {
10908
+ app18.delete("/", requireAdminSession, async (c) => {
10859
10909
  const cacheKey = c.var.cacheKey;
10860
10910
  const accountId = getAccountIdForSession(cacheKey);
10861
10911
  if (!accountId) {
@@ -10892,7 +10942,7 @@ app17.delete("/", requireAdminSession, async (c) => {
10892
10942
  }
10893
10943
  const dot = base.lastIndexOf(".");
10894
10944
  const stem = dot === -1 ? base : base.slice(0, dot);
10895
- const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join15(dirname4(absolute), `${stem}.meta.json`) : null;
10945
+ const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join16(dirname4(absolute), `${stem}.meta.json`) : null;
10896
10946
  await unlink2(absolute);
10897
10947
  if (sidecarPath) {
10898
10948
  try {
@@ -10931,7 +10981,7 @@ app17.delete("/", requireAdminSession, async (c) => {
10931
10981
  return c.json({ error: message }, 500);
10932
10982
  }
10933
10983
  });
10934
- var files_default = app17;
10984
+ var files_default = app18;
10935
10985
 
10936
10986
  // ../lib/graph-search/src/index.ts
10937
10987
  var import_dist = __toESM(require_dist());
@@ -11696,8 +11746,8 @@ var DEFAULT_LIMIT = 20;
11696
11746
  var MAX_LIMIT = 2e3;
11697
11747
  var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
11698
11748
  var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
11699
- var app18 = new Hono();
11700
- app18.get("/", requireAdminSession, async (c) => {
11749
+ var app19 = new Hono();
11750
+ app19.get("/", requireAdminSession, async (c) => {
11701
11751
  const cacheKey = c.var.cacheKey;
11702
11752
  const q = (c.req.query("q") ?? "").trim();
11703
11753
  const rawLimit = c.req.query("limit");
@@ -11833,7 +11883,7 @@ app18.get("/", requireAdminSession, async (c) => {
11833
11883
  await session.close();
11834
11884
  }
11835
11885
  });
11836
- var graph_search_default = app18;
11886
+ var graph_search_default = app19;
11837
11887
 
11838
11888
  // server/routes/admin/graph-subgraph.ts
11839
11889
  import neo4j from "neo4j-driver";
@@ -11911,8 +11961,8 @@ var STRIPPED_PROPERTIES = /* @__PURE__ */ new Set([
11911
11961
  "otpCode",
11912
11962
  "cacheKey"
11913
11963
  ]);
11914
- var app19 = new Hono();
11915
- app19.get("/", requireAdminSession, async (c) => {
11964
+ var app20 = new Hono();
11965
+ app20.get("/", requireAdminSession, async (c) => {
11916
11966
  const cacheKey = c.var.cacheKey;
11917
11967
  const accountId = getAccountIdForSession(cacheKey);
11918
11968
  if (!accountId) {
@@ -12495,12 +12545,12 @@ function pruneNode(node, warnedClasses, conversationWarnings) {
12495
12545
  }
12496
12546
  return trashed ? { id: node.id, labels, properties, trashed: true } : { id: node.id, labels, properties };
12497
12547
  }
12498
- var graph_subgraph_default = app19;
12548
+ var graph_subgraph_default = app20;
12499
12549
 
12500
12550
  // server/routes/admin/graph-delete.ts
12501
12551
  var ALLOWED_BY = ["graph-page", "graph-drag-trash", "graph-side-panel-trash"];
12502
- var app20 = new Hono();
12503
- app20.post("/", requireAdminSession, async (c) => {
12552
+ var app21 = new Hono();
12553
+ app21.post("/", requireAdminSession, async (c) => {
12504
12554
  const cacheKey = c.var.cacheKey;
12505
12555
  const accountId = getAccountIdForSession(cacheKey);
12506
12556
  if (!accountId) {
@@ -12662,11 +12712,11 @@ app20.post("/", requireAdminSession, async (c) => {
12662
12712
  }
12663
12713
  }
12664
12714
  });
12665
- var graph_delete_default = app20;
12715
+ var graph_delete_default = app21;
12666
12716
 
12667
12717
  // server/routes/admin/graph-restore.ts
12668
- var app21 = new Hono();
12669
- app21.post("/", requireAdminSession, async (c) => {
12718
+ var app22 = new Hono();
12719
+ app22.post("/", requireAdminSession, async (c) => {
12670
12720
  const cacheKey = c.var.cacheKey;
12671
12721
  const accountId = getAccountIdForSession(cacheKey);
12672
12722
  if (!accountId) {
@@ -12730,11 +12780,11 @@ app21.post("/", requireAdminSession, async (c) => {
12730
12780
  }
12731
12781
  }
12732
12782
  });
12733
- var graph_restore_default = app21;
12783
+ var graph_restore_default = app22;
12734
12784
 
12735
12785
  // server/routes/admin/graph-labels-in-graph.ts
12736
- var app22 = new Hono();
12737
- app22.get("/", requireAdminSession, async (c) => {
12786
+ var app23 = new Hono();
12787
+ app23.get("/", requireAdminSession, async (c) => {
12738
12788
  const cacheKey = c.var.cacheKey;
12739
12789
  const accountId = getAccountIdForSession(cacheKey);
12740
12790
  if (!accountId) {
@@ -12800,11 +12850,11 @@ var LABELS_IN_GRAPH_CYPHER = `
12800
12850
  sum(halfEdges) AS relDegree
12801
12851
  RETURN label, nodeCount, relDegree
12802
12852
  `;
12803
- var graph_labels_in_graph_default = app22;
12853
+ var graph_labels_in_graph_default = app23;
12804
12854
 
12805
12855
  // server/routes/admin/graph-default-view.ts
12806
- var app23 = new Hono();
12807
- app23.get("/", requireAdminSession, async (c) => {
12856
+ var app24 = new Hono();
12857
+ app24.get("/", requireAdminSession, async (c) => {
12808
12858
  const cacheKey = c.var.cacheKey;
12809
12859
  const accountId = getAccountIdForSession(cacheKey);
12810
12860
  const userId = getUserIdForSession(cacheKey);
@@ -12842,7 +12892,7 @@ app23.get("/", requireAdminSession, async (c) => {
12842
12892
  }
12843
12893
  }
12844
12894
  });
12845
- app23.put("/", requireAdminSession, async (c) => {
12895
+ app24.put("/", requireAdminSession, async (c) => {
12846
12896
  const cacheKey = c.var.cacheKey;
12847
12897
  const accountId = getAccountIdForSession(cacheKey);
12848
12898
  const userId = getUserIdForSession(cacheKey);
@@ -12931,20 +12981,20 @@ var WRITE_CYPHER = `
12931
12981
  p.updatedAt = $updatedAt
12932
12982
  RETURN p.labels AS labels
12933
12983
  `;
12934
- var graph_default_view_default = app23;
12984
+ var graph_default_view_default = app24;
12935
12985
 
12936
12986
  // server/routes/admin/sidebar-artefacts.ts
12937
12987
  import { readdir as readdir4, stat as stat5 } from "fs/promises";
12938
- import { resolve as resolve18, relative as relative3, isAbsolute, sep as sep6, basename as basename6 } from "path";
12939
- import { existsSync as existsSync17 } from "fs";
12988
+ import { resolve as resolve19, relative as relative3, isAbsolute, sep as sep6, basename as basename6 } from "path";
12989
+ import { existsSync as existsSync18 } from "fs";
12940
12990
  var LIMIT = 50;
12941
12991
  var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
12942
12992
  function toDataRootRelPath(absPath) {
12943
12993
  if (absPath !== DATA_ROOT && !absPath.startsWith(DATA_ROOT + sep6)) return null;
12944
12994
  return relative3(DATA_ROOT, absPath);
12945
12995
  }
12946
- var app24 = new Hono();
12947
- app24.get("/", requireAdminSession, async (c) => {
12996
+ var app25 = new Hono();
12997
+ app25.get("/", requireAdminSession, async (c) => {
12948
12998
  const cacheKey = c.var.cacheKey;
12949
12999
  const accountId = getAccountIdForSession(cacheKey);
12950
13000
  if (!accountId) {
@@ -12955,7 +13005,7 @@ app24.get("/", requireAdminSession, async (c) => {
12955
13005
  if (accountFiles === null) {
12956
13006
  return c.json({ error: "Failed to load artefacts" }, 500);
12957
13007
  }
12958
- const accountDir = resolve18(ACCOUNTS_DIR, accountId);
13008
+ const accountDir = resolve19(ACCOUNTS_DIR, accountId);
12959
13009
  const agents = await fetchAgentTemplateRows(accountDir);
12960
13010
  const artefacts = [...accountFiles, ...agents].sort(
12961
13011
  (a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
@@ -13006,8 +13056,8 @@ async function fetchAccountFileArtefacts(accountId) {
13006
13056
  async function fetchAgentTemplateRows(accountDir) {
13007
13057
  const rows = [];
13008
13058
  for (const filename of ADMIN_AGENT_FILES) {
13009
- const overridePath = resolve18(accountDir, "agents", "admin", filename);
13010
- const bundledPath = resolve18(PLATFORM_ROOT, "templates", "agents", "admin", filename);
13059
+ const overridePath = resolve19(accountDir, "agents", "admin", filename);
13060
+ const bundledPath = resolve19(PLATFORM_ROOT, "templates", "agents", "admin", filename);
13011
13061
  const labelStem = filename.replace(/\.md$/, "");
13012
13062
  const row = await readAgentTemplateRow({
13013
13063
  id: `agent-template:admin:${filename}`,
@@ -13020,12 +13070,12 @@ async function fetchAgentTemplateRows(accountDir) {
13020
13070
  });
13021
13071
  if (row) rows.push(row);
13022
13072
  }
13023
- const overrideDir = resolve18(accountDir, "specialists", "agents");
13024
- const bundledDir = resolve18(PLATFORM_ROOT, "templates", "specialists", "agents");
13073
+ const overrideDir = resolve19(accountDir, "specialists", "agents");
13074
+ const bundledDir = resolve19(PLATFORM_ROOT, "templates", "specialists", "agents");
13025
13075
  const specialistNames = await unionSpecialistFilenames(overrideDir, bundledDir);
13026
13076
  for (const filename of specialistNames) {
13027
- const overridePath = resolve18(overrideDir, filename);
13028
- const bundledPath = resolve18(bundledDir, filename);
13077
+ const overridePath = resolve19(overrideDir, filename);
13078
+ const bundledPath = resolve19(bundledDir, filename);
13029
13079
  const row = await readAgentTemplateRow({
13030
13080
  id: `agent-template:specialist:${filename}`,
13031
13081
  displayName: filename.replace(/\.md$/, ""),
@@ -13042,7 +13092,7 @@ async function fetchAgentTemplateRows(accountDir) {
13042
13092
  async function unionSpecialistFilenames(overrideDir, bundledDir) {
13043
13093
  const names = /* @__PURE__ */ new Set();
13044
13094
  for (const dir of [overrideDir, bundledDir]) {
13045
- if (!existsSync17(dir)) continue;
13095
+ if (!existsSync18(dir)) continue;
13046
13096
  try {
13047
13097
  const entries = await readdir4(dir);
13048
13098
  for (const entry of entries) {
@@ -13057,7 +13107,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
13057
13107
  }
13058
13108
  async function readAgentTemplateRow(inp) {
13059
13109
  let chosenPath = null;
13060
- if (existsSync17(inp.overridePath)) {
13110
+ if (existsSync18(inp.overridePath)) {
13061
13111
  try {
13062
13112
  validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
13063
13113
  chosenPath = inp.overridePath;
@@ -13068,7 +13118,7 @@ async function readAgentTemplateRow(inp) {
13068
13118
  );
13069
13119
  return null;
13070
13120
  }
13071
- } else if (existsSync17(inp.bundledPath)) {
13121
+ } else if (existsSync18(inp.bundledPath)) {
13072
13122
  if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
13073
13123
  console.error(
13074
13124
  `[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
@@ -13107,11 +13157,11 @@ function isWithin(target, root) {
13107
13157
  const rel = relative3(root, target);
13108
13158
  return !rel.startsWith("..") && !isAbsolute(rel);
13109
13159
  }
13110
- var sidebar_artefacts_default = app24;
13160
+ var sidebar_artefacts_default = app25;
13111
13161
 
13112
13162
  // server/routes/admin/session-delete.ts
13113
- var app25 = new Hono();
13114
- app25.post("/", requireAdminSession, async (c) => {
13163
+ var app26 = new Hono();
13164
+ app26.post("/", requireAdminSession, async (c) => {
13115
13165
  let body;
13116
13166
  try {
13117
13167
  body = await c.req.json();
@@ -13149,11 +13199,11 @@ app25.post("/", requireAdminSession, async (c) => {
13149
13199
  console.error(`[session-delete] sessionId=${sessionId} delete-status=${del.status}`);
13150
13200
  return c.json({ error: `manager-status-${del.status}` }, 502);
13151
13201
  });
13152
- var session_delete_default = app25;
13202
+ var session_delete_default = app26;
13153
13203
 
13154
13204
  // server/routes/admin/session-stop.ts
13155
- var app26 = new Hono();
13156
- app26.post("/", requireAdminSession, async (c) => {
13205
+ var app27 = new Hono();
13206
+ app27.post("/", requireAdminSession, async (c) => {
13157
13207
  let body;
13158
13208
  try {
13159
13209
  body = await c.req.json();
@@ -13178,11 +13228,63 @@ app26.post("/", requireAdminSession, async (c) => {
13178
13228
  console.error(`[session-stop] sessionId=${sessionId} manager-status=${res.status}`);
13179
13229
  return c.json({ error: `manager-status-${res.status}` }, 502);
13180
13230
  });
13181
- var session_stop_default = app26;
13231
+ var session_stop_default = app27;
13232
+
13233
+ // server/routes/admin/session-archive.ts
13234
+ var app28 = new Hono();
13235
+ app28.post("/", requireAdminSession, async (c) => {
13236
+ let body;
13237
+ try {
13238
+ body = await c.req.json();
13239
+ } catch {
13240
+ return c.json({ error: "invalid JSON body" }, 400);
13241
+ }
13242
+ const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
13243
+ if (!SESSION_ID_RE2.test(sessionId)) {
13244
+ return c.json({ error: "sessionId must be a v4 UUID" }, 400);
13245
+ }
13246
+ const mode = body.mode;
13247
+ if (mode !== "archive" && mode !== "unarchive") {
13248
+ return c.json({ error: "mode must be 'archive' or 'unarchive'" }, 400);
13249
+ }
13250
+ let res;
13251
+ try {
13252
+ res = await fetch(`${managerBase3("session-archive:wrapper")}/${sessionId}/archive`, {
13253
+ method: "POST",
13254
+ headers: { "Content-Type": "application/json" },
13255
+ body: JSON.stringify({ mode })
13256
+ });
13257
+ } catch (err) {
13258
+ console.error(`[session-archive] sessionId=${sessionId} manager-unreachable err=${err instanceof Error ? err.message : String(err)}`);
13259
+ return c.json({ error: "manager-unreachable" }, 502);
13260
+ }
13261
+ if (res.status === 200) {
13262
+ const dir = mode === "archive" ? "top\u2192archive" : "archive\u2192top";
13263
+ console.log(`[session-archive] sessionId=${sessionId.slice(0, 8)} moved ${dir}`);
13264
+ return c.json({ archived: mode === "archive" });
13265
+ }
13266
+ if (res.status === 409) {
13267
+ console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} pty-still-alive`);
13268
+ return c.json({ error: "pty-still-alive", detail: "end the session before archiving" }, 409);
13269
+ }
13270
+ if (res.status === 404) {
13271
+ console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} session-not-found`);
13272
+ return c.json({ error: "session-not-found" }, 404);
13273
+ }
13274
+ if (res.status === 500) {
13275
+ const body2 = await res.json().catch(() => ({}));
13276
+ const detail = typeof body2.detail === "string" ? body2.detail : body2.error ?? "archive-failed";
13277
+ console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} archive-failed detail=${detail}`);
13278
+ return c.json({ error: "archive-failed", detail }, 502);
13279
+ }
13280
+ console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} manager-status=${res.status}`);
13281
+ return c.json({ error: `manager-status-${res.status}` }, 502);
13282
+ });
13283
+ var session_archive_default = app28;
13182
13284
 
13183
13285
  // server/routes/admin/session-rc-spawn.ts
13184
- var app27 = new Hono();
13185
- app27.post("/", requireAdminSession, async (c) => {
13286
+ var app29 = new Hono();
13287
+ app29.post("/", requireAdminSession, async (c) => {
13186
13288
  let body;
13187
13289
  try {
13188
13290
  body = await c.req.json();
@@ -13216,7 +13318,7 @@ app27.post("/", requireAdminSession, async (c) => {
13216
13318
  }
13217
13319
  return c.json(parsed ?? {});
13218
13320
  });
13219
- var session_rc_spawn_default = app27;
13321
+ var session_rc_spawn_default = app29;
13220
13322
 
13221
13323
  // server/routes/admin/system-stats.ts
13222
13324
  import { readFile as readFile5 } from "fs/promises";
@@ -13313,8 +13415,8 @@ async function sample() {
13313
13415
  if (process.platform === "linux") return sampleLinux();
13314
13416
  return sampleOsFallback();
13315
13417
  }
13316
- var app28 = new Hono();
13317
- app28.get("/", async (c) => {
13418
+ var app30 = new Hono();
13419
+ app30.get("/", async (c) => {
13318
13420
  const started = Date.now();
13319
13421
  if (!inflight) {
13320
13422
  inflight = sample().finally(() => {
@@ -13337,27 +13439,27 @@ app28.get("/", async (c) => {
13337
13439
  return c.json({ degraded: true, reason, sampledAtMs: Date.now() });
13338
13440
  }
13339
13441
  });
13340
- var system_stats_default = app28;
13442
+ var system_stats_default = app30;
13341
13443
 
13342
13444
  // server/routes/admin/health.ts
13343
- import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
13344
- import { resolve as resolve19, join as join16 } from "path";
13345
- var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve19(process.cwd(), "..");
13445
+ import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
13446
+ import { resolve as resolve20, join as join17 } from "path";
13447
+ var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
13346
13448
  var brandHostname = "maxy";
13347
- var brandJsonPath = join16(PLATFORM_ROOT6, "config", "brand.json");
13348
- if (existsSync18(brandJsonPath)) {
13449
+ var brandJsonPath = join17(PLATFORM_ROOT6, "config", "brand.json");
13450
+ if (existsSync19(brandJsonPath)) {
13349
13451
  try {
13350
- const brand = JSON.parse(readFileSync17(brandJsonPath, "utf-8"));
13452
+ const brand = JSON.parse(readFileSync18(brandJsonPath, "utf-8"));
13351
13453
  if (brand.hostname) brandHostname = brand.hostname;
13352
13454
  } catch {
13353
13455
  }
13354
13456
  }
13355
- var VERSION_FILE = resolve19(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
13457
+ var VERSION_FILE = resolve20(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
13356
13458
  var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
13357
13459
  var PROBE_TIMEOUT_MS = 1e3;
13358
13460
  function readVersion() {
13359
- if (!existsSync18(VERSION_FILE)) return "unknown";
13360
- return readFileSync17(VERSION_FILE, "utf-8").trim() || "unknown";
13461
+ if (!existsSync19(VERSION_FILE)) return "unknown";
13462
+ return readFileSync18(VERSION_FILE, "utf-8").trim() || "unknown";
13361
13463
  }
13362
13464
  async function probeConversationDb() {
13363
13465
  let session;
@@ -13382,8 +13484,8 @@ async function probeConversationDb() {
13382
13484
  });
13383
13485
  }
13384
13486
  }
13385
- var app29 = new Hono();
13386
- app29.get("/", async (c) => {
13487
+ var app31 = new Hono();
13488
+ app31.get("/", async (c) => {
13387
13489
  const version = readVersion();
13388
13490
  const probe = await probeConversationDb();
13389
13491
  const uptimeMs = Date.now() - new Date(PROCESS_STARTED_AT).getTime();
@@ -13405,11 +13507,11 @@ app29.get("/", async (c) => {
13405
13507
  uptimeMs
13406
13508
  });
13407
13509
  });
13408
- var health_default2 = app29;
13510
+ var health_default2 = app31;
13409
13511
 
13410
13512
  // server/routes/admin/linkedin-ingest.ts
13411
13513
  import { randomUUID as randomUUID9 } from "crypto";
13412
- var TAG23 = "[linkedin-ingest-route]";
13514
+ var TAG24 = "[linkedin-ingest-route]";
13413
13515
  var UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
13414
13516
  var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
13415
13517
  var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
@@ -13507,35 +13609,35 @@ function buildInitialMessage(env) {
13507
13609
  }
13508
13610
  return header;
13509
13611
  }
13510
- var app30 = new Hono();
13511
- app30.post("/", requireAdminSession, async (c) => {
13612
+ var app32 = new Hono();
13613
+ app32.post("/", requireAdminSession, async (c) => {
13512
13614
  let body;
13513
13615
  try {
13514
13616
  body = await c.req.json();
13515
13617
  } catch {
13516
- console.error(TAG23 + " rejected status=400 reason=schema:body-not-json");
13618
+ console.error(TAG24 + " rejected status=400 reason=schema:body-not-json");
13517
13619
  return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
13518
13620
  }
13519
13621
  const v = validate(body);
13520
13622
  if (!v.ok) {
13521
- console.error(TAG23 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
13623
+ console.error(TAG24 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
13522
13624
  return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
13523
13625
  }
13524
13626
  const envelope = v.envelope;
13525
13627
  const cacheKey = c.var.cacheKey ?? "";
13526
13628
  const senderId = getAccountIdForSession(cacheKey) ?? "";
13527
13629
  if (!senderId) {
13528
- console.error(TAG23 + " rejected status=500 reason=admin-account-not-resolved");
13630
+ console.error(TAG24 + " rejected status=500 reason=admin-account-not-resolved");
13529
13631
  return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
13530
13632
  }
13531
13633
  const payloadBytes = JSON.stringify(envelope).length;
13532
13634
  console.log(
13533
- TAG23 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
13635
+ TAG24 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
13534
13636
  );
13535
13637
  const initialMessage = buildInitialMessage(envelope);
13536
13638
  const spawnStart = Date.now();
13537
13639
  const sessionId = randomUUID9();
13538
- console.log(TAG23 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
13640
+ console.log(TAG24 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
13539
13641
  const spawned = await managerRcSpawn({
13540
13642
  sessionId,
13541
13643
  initialMessage,
@@ -13544,20 +13646,20 @@ app30.post("/", requireAdminSession, async (c) => {
13544
13646
  });
13545
13647
  if ("error" in spawned) {
13546
13648
  console.error(
13547
- TAG23 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
13649
+ TAG24 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
13548
13650
  );
13549
13651
  return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
13550
13652
  }
13551
13653
  console.log(
13552
- TAG23 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
13654
+ TAG24 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
13553
13655
  );
13554
13656
  return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
13555
13657
  });
13556
- var linkedin_ingest_default = app30;
13658
+ var linkedin_ingest_default = app32;
13557
13659
 
13558
13660
  // server/routes/admin/post-turn-context.ts
13559
13661
  import neo4j2 from "neo4j-driver";
13560
- var TAG24 = "[post-turn-context]";
13662
+ var TAG25 = "[post-turn-context]";
13561
13663
  var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
13562
13664
  "embedding",
13563
13665
  "passwordHash",
@@ -13595,11 +13697,11 @@ function pruneProperties(raw) {
13595
13697
  }
13596
13698
  return out;
13597
13699
  }
13598
- var app31 = new Hono();
13599
- app31.get("/", async (c) => {
13700
+ var app33 = new Hono();
13701
+ app33.get("/", async (c) => {
13600
13702
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
13601
13703
  if (!isLoopbackAddr2(remoteAddr)) {
13602
- console.error(`${TAG24} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13704
+ console.error(`${TAG25} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13603
13705
  return c.json({ error: "post-turn-context-loopback-only" }, 403);
13604
13706
  }
13605
13707
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
@@ -13608,7 +13710,7 @@ app31.get("/", async (c) => {
13608
13710
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
13609
13711
  const offender = tokens.find((t) => !isLoopbackAddr2(t));
13610
13712
  if (offender !== void 0) {
13611
- console.error(`${TAG24} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13713
+ console.error(`${TAG25} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13612
13714
  return c.json({ error: "post-turn-context-loopback-only" }, 403);
13613
13715
  }
13614
13716
  }
@@ -13640,7 +13742,7 @@ app31.get("/", async (c) => {
13640
13742
  writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
13641
13743
  const total = Date.now() - started;
13642
13744
  console.log(
13643
- `${TAG24} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
13745
+ `${TAG25} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
13644
13746
  );
13645
13747
  return c.json({
13646
13748
  writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
@@ -13649,18 +13751,18 @@ app31.get("/", async (c) => {
13649
13751
  const elapsed = Date.now() - started;
13650
13752
  const message = err instanceof Error ? err.message : String(err);
13651
13753
  console.error(
13652
- `${TAG24} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
13754
+ `${TAG25} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
13653
13755
  );
13654
13756
  return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
13655
13757
  } finally {
13656
13758
  await session.close();
13657
13759
  }
13658
13760
  });
13659
- var post_turn_context_default = app31;
13761
+ var post_turn_context_default = app33;
13660
13762
 
13661
13763
  // server/routes/admin/public-session-context.ts
13662
13764
  import neo4j3 from "neo4j-driver";
13663
- var TAG25 = "[public-session-context]";
13765
+ var TAG26 = "[public-session-context]";
13664
13766
  var STRIPPED_PROPERTIES3 = /* @__PURE__ */ new Set([
13665
13767
  "embedding",
13666
13768
  "passwordHash",
@@ -13698,11 +13800,11 @@ function pruneProperties2(raw) {
13698
13800
  }
13699
13801
  return out;
13700
13802
  }
13701
- var app32 = new Hono();
13702
- app32.get("/", async (c) => {
13803
+ var app34 = new Hono();
13804
+ app34.get("/", async (c) => {
13703
13805
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
13704
13806
  if (!isLoopbackAddr3(remoteAddr)) {
13705
- console.error(`${TAG25} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13807
+ console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13706
13808
  return c.json({ error: "public-session-context-loopback-only" }, 403);
13707
13809
  }
13708
13810
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
@@ -13711,7 +13813,7 @@ app32.get("/", async (c) => {
13711
13813
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
13712
13814
  const offender = tokens.find((t) => !isLoopbackAddr3(t));
13713
13815
  if (offender !== void 0) {
13714
- console.error(`${TAG25} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13816
+ console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13715
13817
  return c.json({ error: "public-session-context-loopback-only" }, 403);
13716
13818
  }
13717
13819
  }
@@ -13742,7 +13844,7 @@ app32.get("/", async (c) => {
13742
13844
  writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
13743
13845
  const total = Date.now() - started;
13744
13846
  console.log(
13745
- `${TAG25} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
13847
+ `${TAG26} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
13746
13848
  );
13747
13849
  return c.json({
13748
13850
  writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
@@ -13751,25 +13853,25 @@ app32.get("/", async (c) => {
13751
13853
  const elapsed = Date.now() - started;
13752
13854
  const message = err instanceof Error ? err.message : String(err);
13753
13855
  console.error(
13754
- `${TAG25} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
13856
+ `${TAG26} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
13755
13857
  );
13756
13858
  return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
13757
13859
  } finally {
13758
13860
  await session.close();
13759
13861
  }
13760
13862
  });
13761
- var public_session_context_default = app32;
13863
+ var public_session_context_default = app34;
13762
13864
 
13763
13865
  // server/routes/admin/public-session-exit.ts
13764
- var TAG26 = "[public-session-exit-route]";
13866
+ var TAG27 = "[public-session-exit-route]";
13765
13867
  function isLoopbackAddr4(addr) {
13766
13868
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
13767
13869
  }
13768
- var app33 = new Hono();
13769
- app33.post("/", async (c) => {
13870
+ var app35 = new Hono();
13871
+ app35.post("/", async (c) => {
13770
13872
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
13771
13873
  if (!isLoopbackAddr4(remoteAddr)) {
13772
- console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13874
+ console.error(`${TAG27} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13773
13875
  return c.json({ error: "public-session-exit-loopback-only" }, 403);
13774
13876
  }
13775
13877
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
@@ -13778,7 +13880,7 @@ app33.post("/", async (c) => {
13778
13880
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
13779
13881
  const offender = tokens.find((t) => !isLoopbackAddr4(t));
13780
13882
  if (offender !== void 0) {
13781
- console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13883
+ console.error(`${TAG27} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13782
13884
  return c.json({ error: "public-session-exit-loopback-only" }, 403);
13783
13885
  }
13784
13886
  }
@@ -13793,18 +13895,18 @@ app33.post("/", async (c) => {
13793
13895
  handlePublicSessionExit(sessionId);
13794
13896
  return c.json({ ok: true });
13795
13897
  });
13796
- var public_session_exit_default = app33;
13898
+ var public_session_exit_default = app35;
13797
13899
 
13798
13900
  // server/routes/admin/access-session-evict.ts
13799
- var TAG27 = "[access-session-evict]";
13901
+ var TAG28 = "[access-session-evict]";
13800
13902
  function isLoopbackAddr5(addr) {
13801
13903
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
13802
13904
  }
13803
- var app34 = new Hono();
13804
- app34.post("/", async (c) => {
13905
+ var app36 = new Hono();
13906
+ app36.post("/", async (c) => {
13805
13907
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
13806
13908
  if (!isLoopbackAddr5(remoteAddr)) {
13807
- console.error(`${TAG27} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13909
+ console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
13808
13910
  return c.json({ error: "access-session-evict-loopback-only" }, 403);
13809
13911
  }
13810
13912
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
@@ -13813,7 +13915,7 @@ app34.post("/", async (c) => {
13813
13915
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
13814
13916
  const offender = tokens.find((t) => !isLoopbackAddr5(t));
13815
13917
  if (offender !== void 0) {
13816
- console.error(`${TAG27} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13918
+ console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
13817
13919
  return c.json({ error: "access-session-evict-loopback-only" }, 403);
13818
13920
  }
13819
13921
  }
@@ -13826,14 +13928,14 @@ app34.post("/", async (c) => {
13826
13928
  const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
13827
13929
  if (!grantId) return c.json({ error: "grantId required" }, 400);
13828
13930
  const dropped = evictAccessSessionsByGrant(grantId);
13829
- console.log(`${TAG27} grantId=${grantId} dropped=${dropped}`);
13931
+ console.log(`${TAG28} grantId=${grantId} dropped=${dropped}`);
13830
13932
  return c.json({ ok: true, dropped });
13831
13933
  });
13832
- var access_session_evict_default = app34;
13934
+ var access_session_evict_default = app36;
13833
13935
 
13834
13936
  // server/routes/admin/browser.ts
13835
- var app35 = new Hono();
13836
- app35.post("/launch", requireAdminSession, async (c) => {
13937
+ var app37 = new Hono();
13938
+ app37.post("/launch", requireAdminSession, async (c) => {
13837
13939
  try {
13838
13940
  const transport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
13839
13941
  console.error(`[admin/browser/launch] op=request transport=${transport}`);
@@ -13861,53 +13963,54 @@ app35.post("/launch", requireAdminSession, async (c) => {
13861
13963
  );
13862
13964
  }
13863
13965
  });
13864
- var browser_default = app35;
13966
+ var browser_default = app37;
13865
13967
 
13866
13968
  // server/routes/admin/index.ts
13867
- var app36 = new Hono();
13868
- app36.route("/session", session_default);
13869
- app36.route("/logs", logs_default);
13870
- app36.route("/claude-info", claude_info_default);
13871
- app36.route("/attachment", attachment_default);
13872
- app36.route("/agents", agents_default);
13873
- app36.route("/sessions", sessions_default);
13874
- app36.route("/claude-sessions", claude_sessions_default);
13875
- app36.route("/log-ingest", log_ingest_default);
13876
- app36.route("/events", events_default);
13877
- app36.route("/files", files_default);
13878
- app36.route("/graph-search", graph_search_default);
13879
- app36.route("/graph-subgraph", graph_subgraph_default);
13880
- app36.route("/graph-delete", graph_delete_default);
13881
- app36.route("/graph-restore", graph_restore_default);
13882
- app36.route("/graph-labels-in-graph", graph_labels_in_graph_default);
13883
- app36.route("/graph-default-view", graph_default_view_default);
13884
- app36.route("/sidebar-artefacts", sidebar_artefacts_default);
13885
- app36.route("/sidebar-sessions", sidebar_sessions_default);
13886
- app36.route("/session-delete", session_delete_default);
13887
- app36.route("/session-stop", session_stop_default);
13888
- app36.route("/browser", browser_default);
13889
- app36.route("/session-rc-spawn", session_rc_spawn_default);
13890
- app36.route("/system-stats", system_stats_default);
13891
- app36.route("/health-brand", health_default2);
13892
- app36.route("/linkedin-ingest", linkedin_ingest_default);
13893
- app36.route("/post-turn-context", post_turn_context_default);
13894
- app36.route("/public-session-context", public_session_context_default);
13895
- app36.route("/public-session-exit", public_session_exit_default);
13896
- app36.route("/access-session-evict", access_session_evict_default);
13897
- var admin_default = app36;
13969
+ var app38 = new Hono();
13970
+ app38.route("/session", session_default);
13971
+ app38.route("/logs", logs_default);
13972
+ app38.route("/claude-info", claude_info_default);
13973
+ app38.route("/attachment", attachment_default);
13974
+ app38.route("/agents", agents_default);
13975
+ app38.route("/sessions", sessions_default);
13976
+ app38.route("/claude-sessions", claude_sessions_default);
13977
+ app38.route("/log-ingest", log_ingest_default);
13978
+ app38.route("/events", events_default);
13979
+ app38.route("/files", files_default);
13980
+ app38.route("/graph-search", graph_search_default);
13981
+ app38.route("/graph-subgraph", graph_subgraph_default);
13982
+ app38.route("/graph-delete", graph_delete_default);
13983
+ app38.route("/graph-restore", graph_restore_default);
13984
+ app38.route("/graph-labels-in-graph", graph_labels_in_graph_default);
13985
+ app38.route("/graph-default-view", graph_default_view_default);
13986
+ app38.route("/sidebar-artefacts", sidebar_artefacts_default);
13987
+ app38.route("/sidebar-sessions", sidebar_sessions_default);
13988
+ app38.route("/session-delete", session_delete_default);
13989
+ app38.route("/session-stop", session_stop_default);
13990
+ app38.route("/session-archive", session_archive_default);
13991
+ app38.route("/browser", browser_default);
13992
+ app38.route("/session-rc-spawn", session_rc_spawn_default);
13993
+ app38.route("/system-stats", system_stats_default);
13994
+ app38.route("/health-brand", health_default2);
13995
+ app38.route("/linkedin-ingest", linkedin_ingest_default);
13996
+ app38.route("/post-turn-context", post_turn_context_default);
13997
+ app38.route("/public-session-context", public_session_context_default);
13998
+ app38.route("/public-session-exit", public_session_exit_default);
13999
+ app38.route("/access-session-evict", access_session_evict_default);
14000
+ var admin_default = app38;
13898
14001
 
13899
14002
  // app/lib/access-gate.ts
13900
14003
  import neo4j4 from "neo4j-driver";
13901
- import { readFileSync as readFileSync18 } from "fs";
13902
- import { resolve as resolve20 } from "path";
14004
+ import { readFileSync as readFileSync19 } from "fs";
14005
+ import { resolve as resolve21 } from "path";
13903
14006
  import { randomUUID as randomUUID10 } from "crypto";
13904
- var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
14007
+ var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve21(process.cwd(), "..");
13905
14008
  var driver = null;
13906
14009
  function readPassword() {
13907
14010
  if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
13908
- const passwordFile = resolve20(PLATFORM_ROOT7, "config/.neo4j-password");
14011
+ const passwordFile = resolve21(PLATFORM_ROOT7, "config/.neo4j-password");
13909
14012
  try {
13910
- return readFileSync18(passwordFile, "utf-8").trim();
14013
+ return readFileSync19(passwordFile, "utf-8").trim();
13911
14014
  } catch {
13912
14015
  throw new Error(
13913
14016
  `Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
@@ -14105,11 +14208,11 @@ async function generateNewMagicToken(grantId) {
14105
14208
  }
14106
14209
 
14107
14210
  // server/routes/access/verify-token.ts
14108
- var TAG28 = "[access-verify]";
14211
+ var TAG29 = "[access-verify]";
14109
14212
  var MINT_TAG = "[access-session-mint]";
14110
14213
  var COOKIE_NAME = "__access_session";
14111
- var app37 = new Hono();
14112
- app37.post("/", async (c) => {
14214
+ var app39 = new Hono();
14215
+ app39.post("/", async (c) => {
14113
14216
  const ip = c.var.clientIp || "unknown";
14114
14217
  let body;
14115
14218
  try {
@@ -14124,39 +14227,39 @@ app37.post("/", async (c) => {
14124
14227
  }
14125
14228
  const rateMsg = checkAccessRateLimit(ip, agentSlug);
14126
14229
  if (rateMsg) {
14127
- console.error(`${TAG28} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
14230
+ console.error(`${TAG29} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
14128
14231
  return c.json({ error: rateMsg }, 429);
14129
14232
  }
14130
14233
  const grant = await findGrantByMagicToken(token);
14131
14234
  if (!grant) {
14132
14235
  recordAccessFailedAttempt(ip, agentSlug);
14133
- console.error(`${TAG28} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
14236
+ console.error(`${TAG29} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
14134
14237
  return c.json({ error: "invalid-or-expired-link" }, 401);
14135
14238
  }
14136
14239
  if (grant.agentSlug !== agentSlug) {
14137
14240
  recordAccessFailedAttempt(ip, agentSlug);
14138
14241
  console.error(
14139
- `${TAG28} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
14242
+ `${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
14140
14243
  );
14141
14244
  return c.json({ error: "invalid-or-expired-link" }, 401);
14142
14245
  }
14143
14246
  if (grant.status === "expired" || grant.status === "revoked") {
14144
14247
  recordAccessFailedAttempt(ip, agentSlug);
14145
14248
  console.error(
14146
- `${TAG28} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
14249
+ `${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
14147
14250
  );
14148
14251
  return c.json({ error: "access-no-longer-valid" }, 401);
14149
14252
  }
14150
14253
  if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
14151
14254
  recordAccessFailedAttempt(ip, agentSlug);
14152
14255
  console.error(
14153
- `${TAG28} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
14256
+ `${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
14154
14257
  );
14155
14258
  return c.json({ error: "access-no-longer-valid" }, 401);
14156
14259
  }
14157
14260
  if (!grant.sliceToken) {
14158
14261
  console.error(
14159
- `${TAG28} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
14262
+ `${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
14160
14263
  );
14161
14264
  return c.json({ error: "grant-misconfigured" }, 500);
14162
14265
  }
@@ -14172,12 +14275,12 @@ app37.post("/", async (c) => {
14172
14275
  await consumeMagicTokenAndActivate(grant.grantId);
14173
14276
  } catch (err) {
14174
14277
  console.error(
14175
- `${TAG28} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
14278
+ `${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
14176
14279
  );
14177
14280
  return c.json({ error: "verification-failed" }, 500);
14178
14281
  }
14179
14282
  clearAccessRateLimit(ip, agentSlug);
14180
- console.log(`${TAG28} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
14283
+ console.log(`${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
14181
14284
  console.log(
14182
14285
  `${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
14183
14286
  );
@@ -14191,13 +14294,13 @@ app37.post("/", async (c) => {
14191
14294
  displayName: grant.displayName
14192
14295
  });
14193
14296
  });
14194
- var verify_token_default = app37;
14297
+ var verify_token_default = app39;
14195
14298
 
14196
14299
  // app/lib/access-email.ts
14197
14300
  import { spawn as spawn2 } from "child_process";
14198
- import { resolve as resolve21 } from "path";
14199
- var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve21(process.cwd(), "..");
14200
- var SEND_SCRIPT = resolve21(
14301
+ import { resolve as resolve22 } from "path";
14302
+ var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
14303
+ var SEND_SCRIPT = resolve22(
14201
14304
  PLATFORM_ROOT8,
14202
14305
  "plugins",
14203
14306
  "email",
@@ -14253,10 +14356,10 @@ async function sendMagicLinkEmail(payload) {
14253
14356
  }
14254
14357
 
14255
14358
  // server/routes/access/request-magic-link.ts
14256
- var TAG29 = "[access-request-link]";
14257
- var app38 = new Hono();
14359
+ var TAG30 = "[access-request-link]";
14360
+ var app40 = new Hono();
14258
14361
  var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
14259
- app38.post("/", async (c) => {
14362
+ app40.post("/", async (c) => {
14260
14363
  let body;
14261
14364
  try {
14262
14365
  body = await c.req.json();
@@ -14270,18 +14373,18 @@ app38.post("/", async (c) => {
14270
14373
  }
14271
14374
  const rateMsg = checkRequestLinkRateLimit(contactValue);
14272
14375
  if (rateMsg) {
14273
- console.error(`${TAG29} contactValue=${maskContact(contactValue)} result=rate-limited`);
14376
+ console.error(`${TAG30} contactValue=${maskContact(contactValue)} result=rate-limited`);
14274
14377
  return c.json({ error: rateMsg }, 429);
14275
14378
  }
14276
14379
  recordRequestLinkAttempt(contactValue);
14277
14380
  const accountId = process.env.ACCOUNT_ID ?? "";
14278
14381
  if (!accountId) {
14279
- console.error(`${TAG29} contactValue=${maskContact(contactValue)} result=no-account-id`);
14382
+ console.error(`${TAG30} contactValue=${maskContact(contactValue)} result=no-account-id`);
14280
14383
  return c.json({ message: VISITOR_MESSAGE }, 200);
14281
14384
  }
14282
14385
  const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
14283
14386
  if (!grant) {
14284
- console.log(`${TAG29} contactValue=${maskContact(contactValue)} result=notfound`);
14387
+ console.log(`${TAG30} contactValue=${maskContact(contactValue)} result=notfound`);
14285
14388
  return c.json({ message: VISITOR_MESSAGE }, 200);
14286
14389
  }
14287
14390
  let token;
@@ -14289,7 +14392,7 @@ app38.post("/", async (c) => {
14289
14392
  token = await generateNewMagicToken(grant.grantId);
14290
14393
  } catch (err) {
14291
14394
  console.error(
14292
- `${TAG29} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
14395
+ `${TAG30} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
14293
14396
  );
14294
14397
  return c.json({ message: VISITOR_MESSAGE }, 200);
14295
14398
  }
@@ -14321,26 +14424,26 @@ app38.post("/", async (c) => {
14321
14424
  });
14322
14425
  if (!sendResult.ok) {
14323
14426
  console.error(
14324
- `${TAG29} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
14427
+ `${TAG30} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
14325
14428
  );
14326
14429
  return c.json({ message: VISITOR_MESSAGE }, 200);
14327
14430
  }
14328
14431
  console.log(
14329
- `${TAG29} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
14432
+ `${TAG30} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
14330
14433
  );
14331
14434
  return c.json({ message: VISITOR_MESSAGE }, 200);
14332
14435
  });
14333
- var request_magic_link_default = app38;
14436
+ var request_magic_link_default = app40;
14334
14437
 
14335
14438
  // server/routes/access/index.ts
14336
- var app39 = new Hono();
14337
- app39.route("/verify-token", verify_token_default);
14338
- app39.route("/request-magic-link", request_magic_link_default);
14339
- var access_default = app39;
14439
+ var app41 = new Hono();
14440
+ app41.route("/verify-token", verify_token_default);
14441
+ app41.route("/request-magic-link", request_magic_link_default);
14442
+ var access_default = app41;
14340
14443
 
14341
14444
  // server/routes/sites.ts
14342
- import { existsSync as existsSync19, readFileSync as readFileSync19, realpathSync as realpathSync5, statSync as statSync9 } from "fs";
14343
- import { resolve as resolve22 } from "path";
14445
+ import { existsSync as existsSync20, readFileSync as readFileSync20, realpathSync as realpathSync5, statSync as statSync9 } from "fs";
14446
+ import { resolve as resolve23 } from "path";
14344
14447
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
14345
14448
  var MIME = {
14346
14449
  ".html": "text/html; charset=utf-8",
@@ -14371,8 +14474,8 @@ function getExt(p) {
14371
14474
  if (idx < p.lastIndexOf("/")) return "";
14372
14475
  return p.slice(idx).toLowerCase();
14373
14476
  }
14374
- var app40 = new Hono();
14375
- app40.get("/:rel{.*}", (c) => {
14477
+ var app42 = new Hono();
14478
+ app42.get("/:rel{.*}", (c) => {
14376
14479
  const reqPath = c.req.path;
14377
14480
  const rawRel = c.req.param("rel") ?? "";
14378
14481
  const trimmed = rawRel.replace(/^\/+/, "").replace(/\/+$/, "");
@@ -14397,15 +14500,15 @@ app40.get("/:rel{.*}", (c) => {
14397
14500
  }
14398
14501
  segments.push(seg);
14399
14502
  }
14400
- const rootDir = resolve22(account.accountDir, "sites");
14401
- let filePath = segments.length === 0 ? rootDir : resolve22(rootDir, ...segments);
14503
+ const rootDir = resolve23(account.accountDir, "sites");
14504
+ let filePath = segments.length === 0 ? rootDir : resolve23(rootDir, ...segments);
14402
14505
  if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
14403
14506
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
14404
14507
  return c.text("Forbidden", 403);
14405
14508
  }
14406
14509
  let stat7;
14407
14510
  try {
14408
- stat7 = existsSync19(filePath) ? statSync9(filePath) : null;
14511
+ stat7 = existsSync20(filePath) ? statSync9(filePath) : null;
14409
14512
  } catch {
14410
14513
  stat7 = null;
14411
14514
  }
@@ -14418,13 +14521,13 @@ app40.get("/:rel{.*}", (c) => {
14418
14521
  return c.redirect(target, 301);
14419
14522
  }
14420
14523
  if (stat7?.isDirectory()) {
14421
- filePath = resolve22(filePath, "index.html");
14524
+ filePath = resolve23(filePath, "index.html");
14422
14525
  }
14423
14526
  if (!filePath.startsWith(rootDir + "/")) {
14424
14527
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
14425
14528
  return c.text("Forbidden", 403);
14426
14529
  }
14427
- if (!existsSync19(filePath)) {
14530
+ if (!existsSync20(filePath)) {
14428
14531
  console.error(`[sites] not-found path=${reqPath} status=404`);
14429
14532
  return c.text("Not found", 404);
14430
14533
  }
@@ -14443,7 +14546,7 @@ app40.get("/:rel{.*}", (c) => {
14443
14546
  }
14444
14547
  let body;
14445
14548
  try {
14446
- body = readFileSync19(realPath);
14549
+ body = readFileSync20(realPath);
14447
14550
  } catch (err) {
14448
14551
  const code = err?.code;
14449
14552
  if (code === "EISDIR") {
@@ -14475,11 +14578,11 @@ app40.get("/:rel{.*}", (c) => {
14475
14578
  "X-Content-Type-Options": "nosniff"
14476
14579
  });
14477
14580
  });
14478
- var sites_default = app40;
14581
+ var sites_default = app42;
14479
14582
 
14480
14583
  // app/lib/visitor-token.ts
14481
14584
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
14482
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
14585
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync21, writeFileSync as writeFileSync7 } from "fs";
14483
14586
  import { dirname as dirname5 } from "path";
14484
14587
  var TOKEN_PREFIX = "v1.";
14485
14588
  var SECRET_BYTES = 32;
@@ -14488,7 +14591,7 @@ var cachedSecret = null;
14488
14591
  function getSecret() {
14489
14592
  if (cachedSecret) return cachedSecret;
14490
14593
  try {
14491
- const hex2 = readFileSync20(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14594
+ const hex2 = readFileSync21(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14492
14595
  if (hex2.length === SECRET_BYTES * 2) {
14493
14596
  cachedSecret = Buffer.from(hex2, "hex");
14494
14597
  return cachedSecret;
@@ -14502,7 +14605,7 @@ function getSecret() {
14502
14605
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
14503
14606
  } catch {
14504
14607
  }
14505
- const hex = readFileSync20(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14608
+ const hex = readFileSync21(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14506
14609
  cachedSecret = Buffer.from(hex, "hex");
14507
14610
  return cachedSecret;
14508
14611
  }
@@ -14555,8 +14658,8 @@ var VISITOR_COOKIE_NAME = "mxy_v";
14555
14658
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
14556
14659
 
14557
14660
  // app/lib/brand-config.ts
14558
- import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
14559
- import { join as join17 } from "path";
14661
+ import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
14662
+ import { join as join18 } from "path";
14560
14663
  var cached2 = null;
14561
14664
  var cachedAttempted = false;
14562
14665
  function readBrandConfig() {
@@ -14564,10 +14667,10 @@ function readBrandConfig() {
14564
14667
  cachedAttempted = true;
14565
14668
  const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
14566
14669
  if (!platformRoot2) return null;
14567
- const brandPath = join17(platformRoot2, "config", "brand.json");
14568
- if (!existsSync20(brandPath)) return null;
14670
+ const brandPath = join18(platformRoot2, "config", "brand.json");
14671
+ if (!existsSync21(brandPath)) return null;
14569
14672
  try {
14570
- cached2 = JSON.parse(readFileSync21(brandPath, "utf-8"));
14673
+ cached2 = JSON.parse(readFileSync22(brandPath, "utf-8"));
14571
14674
  return cached2;
14572
14675
  } catch {
14573
14676
  return null;
@@ -14575,7 +14678,7 @@ function readBrandConfig() {
14575
14678
  }
14576
14679
 
14577
14680
  // server/routes/visitor-consent.ts
14578
- var app41 = new Hono();
14681
+ var app43 = new Hono();
14579
14682
  var CONSENT_COOKIE_NAME = "mxy_consent";
14580
14683
  var CONSENT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
14581
14684
  var DEFAULT_CONSENT_COPY = {
@@ -14620,17 +14723,17 @@ function siteSlugFromReferer(referer) {
14620
14723
  return "";
14621
14724
  }
14622
14725
  }
14623
- app41.options("/consent", (c) => {
14726
+ app43.options("/consent", (c) => {
14624
14727
  const origin = getOrigin(c);
14625
14728
  setCorsHeaders(c, origin);
14626
14729
  return c.body(null, 204);
14627
14730
  });
14628
- app41.options("/brand-config", (c) => {
14731
+ app43.options("/brand-config", (c) => {
14629
14732
  const origin = getOrigin(c);
14630
14733
  setCorsHeaders(c, origin);
14631
14734
  return c.body(null, 204);
14632
14735
  });
14633
- app41.post("/consent", async (c) => {
14736
+ app43.post("/consent", async (c) => {
14634
14737
  const origin = getOrigin(c);
14635
14738
  setCorsHeaders(c, origin);
14636
14739
  let raw;
@@ -14670,7 +14773,7 @@ app41.post("/consent", async (c) => {
14670
14773
  console.log(`[consent] ${parsed.decision} site=${site} brand=${brandName} tokenBound=${tokenBound}`);
14671
14774
  return c.body(null, 204);
14672
14775
  });
14673
- app41.get("/brand-config", (c) => {
14776
+ app43.get("/brand-config", (c) => {
14674
14777
  const origin = getOrigin(c);
14675
14778
  setCorsHeaders(c, origin);
14676
14779
  const brand = readBrandConfig();
@@ -14680,7 +14783,7 @@ app41.get("/brand-config", (c) => {
14680
14783
  c.header("Cache-Control", "public, max-age=300");
14681
14784
  return c.json({ consent: { copy, palette } });
14682
14785
  });
14683
- var visitor_consent_default = app41;
14786
+ var visitor_consent_default = app43;
14684
14787
 
14685
14788
  // server/routes/listings.ts
14686
14789
  function getCookie(headerValue, name) {
@@ -14707,8 +14810,8 @@ function appendConsentParams(pageUrl, token) {
14707
14810
  }
14708
14811
  var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
14709
14812
  var CHAT_SESSION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
14710
- var app42 = new Hono();
14711
- app42.get("/:slug/click", async (c) => {
14813
+ var app44 = new Hono();
14814
+ app44.get("/:slug/click", async (c) => {
14712
14815
  const slug = c.req.param("slug") ?? "";
14713
14816
  const rawSession = c.req.query("session") ?? "";
14714
14817
  const sessionKey = CHAT_SESSION_KEY_RE.test(rawSession) ? rawSession : "invalid";
@@ -14772,10 +14875,10 @@ app42.get("/:slug/click", async (c) => {
14772
14875
  console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} consent=${consentCookie ?? "absent"} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
14773
14876
  return c.redirect(redirectUrl, 302);
14774
14877
  });
14775
- var listings_default = app42;
14878
+ var listings_default = app44;
14776
14879
 
14777
14880
  // server/routes/visitor-event.ts
14778
- var app43 = new Hono();
14881
+ var app45 = new Hono();
14779
14882
  var BOT_UA_RE = /\b(bot|crawl|spider|slurp|headlesschrome|phantomjs|googlebot|bingbot|yandex|baiduspider|ahrefsbot|semrushbot|mj12bot|dotbot|petalbot)\b/i;
14780
14883
  var buckets = /* @__PURE__ */ new Map();
14781
14884
  var RATE_LIMIT = 60;
@@ -14842,12 +14945,12 @@ function originAllowed(origin, allowlist) {
14842
14945
  return false;
14843
14946
  }
14844
14947
  }
14845
- app43.options("/event", (c) => {
14948
+ app45.options("/event", (c) => {
14846
14949
  const origin = getOrigin2(c);
14847
14950
  setCorsHeaders2(c, origin);
14848
14951
  return c.body(null, 204);
14849
14952
  });
14850
- app43.post("/event", async (c) => {
14953
+ app45.post("/event", async (c) => {
14851
14954
  const origin = getOrigin2(c);
14852
14955
  setCorsHeaders2(c, origin);
14853
14956
  const ua = c.req.header("user-agent") ?? "";
@@ -15052,17 +15155,17 @@ async function writeEvent(opts) {
15052
15155
  );
15053
15156
  }
15054
15157
  }
15055
- var visitor_event_default = app43;
15158
+ var visitor_event_default = app45;
15056
15159
 
15057
15160
  // server/routes/session.ts
15058
- import { resolve as resolve23 } from "path";
15059
- import { existsSync as existsSync21, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
15161
+ import { resolve as resolve24 } from "path";
15162
+ import { existsSync as existsSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
15060
15163
  var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15061
15164
  function writeBrandingCache(accountId, agentSlug, branding) {
15062
15165
  try {
15063
- const cacheDir = resolve23(MAXY_DIR, "branding-cache", accountId);
15166
+ const cacheDir = resolve24(MAXY_DIR, "branding-cache", accountId);
15064
15167
  mkdirSync5(cacheDir, { recursive: true });
15065
- writeFileSync8(resolve23(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
15168
+ writeFileSync8(resolve24(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
15066
15169
  } catch (err) {
15067
15170
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
15068
15171
  }
@@ -15098,8 +15201,8 @@ function withVisitorCookie(response, visitorId) {
15098
15201
  headers
15099
15202
  });
15100
15203
  }
15101
- var app44 = new Hono();
15102
- app44.post("/", async (c) => {
15204
+ var app46 = new Hono();
15205
+ app46.post("/", async (c) => {
15103
15206
  let body;
15104
15207
  try {
15105
15208
  body = await c.req.json();
@@ -15150,8 +15253,8 @@ app44.post("/", async (c) => {
15150
15253
  }
15151
15254
  let agentConfig = null;
15152
15255
  if (account) {
15153
- const agentDir = resolve23(account.accountDir, "agents", agentSlug);
15154
- if (!existsSync21(agentDir) || !existsSync21(resolve23(agentDir, "config.json"))) {
15256
+ const agentDir = resolve24(account.accountDir, "agents", agentSlug);
15257
+ if (!existsSync22(agentDir) || !existsSync22(resolve24(agentDir, "config.json"))) {
15155
15258
  return c.json({ error: "Agent not found" }, 404);
15156
15259
  }
15157
15260
  agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
@@ -15310,7 +15413,7 @@ app44.post("/", async (c) => {
15310
15413
  newVisitorId
15311
15414
  );
15312
15415
  });
15313
- var session_default2 = app44;
15416
+ var session_default2 = app46;
15314
15417
 
15315
15418
  // app/lib/graph-health.ts
15316
15419
  var import_dist4 = __toESM(require_dist3(), 1);
@@ -15431,7 +15534,7 @@ function startGraphHealthTimer() {
15431
15534
 
15432
15535
  // app/lib/file-watcher.ts
15433
15536
  import * as fsp2 from "fs/promises";
15434
- import { resolve as resolve24, sep as sep7 } from "path";
15537
+ import { resolve as resolve25, sep as sep7 } from "path";
15435
15538
  var ACCOUNT_UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15436
15539
  var DEFAULT_COALESCE_MS = 500;
15437
15540
  var ROOTS = ["uploads", "accounts"];
@@ -15450,7 +15553,7 @@ async function startFileWatcher(opts = {}) {
15450
15553
  const dropFn = opts.drop ?? dropFileIndex;
15451
15554
  for (const r of ROOTS) {
15452
15555
  try {
15453
- await fsp2.mkdir(resolve24(dataRoot, r), { recursive: true });
15556
+ await fsp2.mkdir(resolve25(dataRoot, r), { recursive: true });
15454
15557
  } catch (err) {
15455
15558
  console.error(
15456
15559
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -15471,7 +15574,7 @@ async function startFileWatcher(opts = {}) {
15471
15574
  timers.set(relativePath, t);
15472
15575
  }
15473
15576
  async function runHook(relativePath, accountId) {
15474
- const absolute = resolve24(dataRoot, relativePath);
15577
+ const absolute = resolve25(dataRoot, relativePath);
15475
15578
  let exists = false;
15476
15579
  try {
15477
15580
  const st = await fsp2.stat(absolute);
@@ -15495,7 +15598,7 @@ async function startFileWatcher(opts = {}) {
15495
15598
  }
15496
15599
  }
15497
15600
  async function watchRoot(rootName) {
15498
- const absRoot = resolve24(dataRoot, rootName);
15601
+ const absRoot = resolve25(dataRoot, rootName);
15499
15602
  try {
15500
15603
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
15501
15604
  for await (const event of iter) {
@@ -15800,8 +15903,8 @@ var streamSSE = (c, cb, onError) => {
15800
15903
 
15801
15904
  // app/lib/whatsapp/gateway/routes.ts
15802
15905
  function createWaChannelRoutes(deps) {
15803
- const app46 = new Hono();
15804
- app46.get("/wa-channel/inbound", (c) => {
15906
+ const app48 = new Hono();
15907
+ app48.get("/wa-channel/inbound", (c) => {
15805
15908
  const senderId = c.req.query("senderId");
15806
15909
  if (!senderId) return c.json({ error: "senderId required" }, 400);
15807
15910
  return streamSSE(c, async (stream2) => {
@@ -15819,7 +15922,7 @@ function createWaChannelRoutes(deps) {
15819
15922
  }
15820
15923
  });
15821
15924
  });
15822
- app46.post("/wa-channel/reply", async (c) => {
15925
+ app48.post("/wa-channel/reply", async (c) => {
15823
15926
  const body = await c.req.json().catch(() => null);
15824
15927
  const senderId = body?.senderId;
15825
15928
  const text = body?.text;
@@ -15830,15 +15933,17 @@ function createWaChannelRoutes(deps) {
15830
15933
  try {
15831
15934
  await deps.sendOutbound(senderId, text);
15832
15935
  } catch (err) {
15936
+ const message = err instanceof Error ? err.message : String(err);
15937
+ const terminal = err instanceof WaReplyError ? err.terminal : false;
15833
15938
  console.error(
15834
- `[whatsapp-native] op=reply-failed senderId=${senderId} bytes=${bytes} error=${err instanceof Error ? err.message : String(err)}`
15939
+ `[whatsapp-native] op=reply-failed senderId=${senderId} bytes=${bytes} terminal=${terminal} error=${message}`
15835
15940
  );
15836
- return c.json({ error: "send-failed" }, 500);
15941
+ return c.json({ error: message, terminal }, 500);
15837
15942
  }
15838
15943
  console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
15839
15944
  return c.json({ ok: true });
15840
15945
  });
15841
- app46.post("/wa-channel/ready", async (c) => {
15946
+ app48.post("/wa-channel/ready", async (c) => {
15842
15947
  const body = await c.req.json().catch(() => null);
15843
15948
  const senderId = body?.senderId;
15844
15949
  if (typeof senderId !== "string") {
@@ -15847,7 +15952,7 @@ function createWaChannelRoutes(deps) {
15847
15952
  deps.onReady?.(senderId);
15848
15953
  return c.json({ ok: true });
15849
15954
  });
15850
- app46.post("/wa-channel/received", async (c) => {
15955
+ app48.post("/wa-channel/received", async (c) => {
15851
15956
  const body = await c.req.json().catch(() => null);
15852
15957
  const senderId = body?.senderId;
15853
15958
  const waMessageId = body?.waMessageId;
@@ -15857,7 +15962,7 @@ function createWaChannelRoutes(deps) {
15857
15962
  deps.onReceived?.(senderId, waMessageId);
15858
15963
  return c.json({ ok: true });
15859
15964
  });
15860
- return app46;
15965
+ return app48;
15861
15966
  }
15862
15967
 
15863
15968
  // app/lib/whatsapp/gateway/wa-gateway.ts
@@ -16050,8 +16155,8 @@ function broadcastAdminShutdown(reason) {
16050
16155
 
16051
16156
  // ../lib/entitlement/src/index.ts
16052
16157
  import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
16053
- import { existsSync as existsSync22, readFileSync as readFileSync22, statSync as statSync10 } from "fs";
16054
- import { resolve as resolve25 } from "path";
16158
+ import { existsSync as existsSync23, readFileSync as readFileSync23, statSync as statSync10 } from "fs";
16159
+ import { resolve as resolve26 } from "path";
16055
16160
 
16056
16161
  // ../lib/entitlement/src/canonicalize.ts
16057
16162
  function canonicalize(value) {
@@ -16086,7 +16191,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
16086
16191
  var GRACE_DAYS = 7;
16087
16192
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
16088
16193
  function pubkeyPath(brand) {
16089
- return resolve25(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
16194
+ return resolve26(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
16090
16195
  }
16091
16196
  var memo = null;
16092
16197
  function memoKey(mtimeMs, account) {
@@ -16098,8 +16203,8 @@ function resolveEntitlement(brand, account) {
16098
16203
  if (brand.commercialMode !== true) {
16099
16204
  return logResolved(implicitTrust(account), null);
16100
16205
  }
16101
- const entitlementPath = resolve25(brand.configDir, "entitlement.json");
16102
- if (!existsSync22(entitlementPath)) {
16206
+ const entitlementPath = resolve26(brand.configDir, "entitlement.json");
16207
+ if (!existsSync23(entitlementPath)) {
16103
16208
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
16104
16209
  }
16105
16210
  const stat7 = statSync10(entitlementPath);
@@ -16114,7 +16219,7 @@ function resolveEntitlement(brand, account) {
16114
16219
  function verifyAndResolve(brand, entitlementPath, account) {
16115
16220
  let pubkeyPem;
16116
16221
  try {
16117
- pubkeyPem = readFileSync22(pubkeyPath(brand), "utf-8");
16222
+ pubkeyPem = readFileSync23(pubkeyPath(brand), "utf-8");
16118
16223
  } catch (err) {
16119
16224
  return logResolved(anonymousFallback("pubkey-missing"), {
16120
16225
  reason: "pubkey-missing"
@@ -16128,7 +16233,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
16128
16233
  }
16129
16234
  let envelope;
16130
16235
  try {
16131
- envelope = JSON.parse(readFileSync22(entitlementPath, "utf-8"));
16236
+ envelope = JSON.parse(readFileSync23(entitlementPath, "utf-8"));
16132
16237
  } catch {
16133
16238
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
16134
16239
  }
@@ -16289,14 +16394,14 @@ function clientFrom(c) {
16289
16394
  );
16290
16395
  }
16291
16396
  var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
16292
- var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join18(PLATFORM_ROOT9, "config", "brand.json") : "";
16397
+ var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join19(PLATFORM_ROOT9, "config", "brand.json") : "";
16293
16398
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
16294
- if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
16399
+ if (BRAND_JSON_PATH && !existsSync24(BRAND_JSON_PATH)) {
16295
16400
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
16296
16401
  }
16297
- if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
16402
+ if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
16298
16403
  try {
16299
- const parsed = JSON.parse(readFileSync23(BRAND_JSON_PATH, "utf-8"));
16404
+ const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
16300
16405
  BRAND = { ...BRAND, ...parsed };
16301
16406
  } catch (err) {
16302
16407
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -16315,11 +16420,11 @@ var brandLoginOpts = {
16315
16420
  bodyFont: BRAND.defaultFonts?.body,
16316
16421
  logoContainsName: !!BRAND.logoContainsName
16317
16422
  };
16318
- var ALIAS_DOMAINS_PATH = join18(homedir2(), BRAND.configDir, "alias-domains.json");
16423
+ var ALIAS_DOMAINS_PATH = join19(homedir3(), BRAND.configDir, "alias-domains.json");
16319
16424
  function loadAliasDomains() {
16320
16425
  try {
16321
- if (!existsSync23(ALIAS_DOMAINS_PATH)) return null;
16322
- const parsed = JSON.parse(readFileSync23(ALIAS_DOMAINS_PATH, "utf-8"));
16426
+ if (!existsSync24(ALIAS_DOMAINS_PATH)) return null;
16427
+ const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH, "utf-8"));
16323
16428
  if (!Array.isArray(parsed)) {
16324
16429
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
16325
16430
  return null;
@@ -16343,16 +16448,21 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
16343
16448
  function isPublicHost(host) {
16344
16449
  return host.startsWith("public.") || aliasDomains.has(host);
16345
16450
  }
16346
- var app45 = new Hono();
16451
+ var app47 = new Hono();
16347
16452
  var waGateway = new WaGateway({
16348
16453
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
16349
- serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve26(process.env.MAXY_PLATFORM_ROOT ?? join18(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
16454
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve27(process.env.MAXY_PLATFORM_ROOT ?? join19(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
16350
16455
  ensureChannelSession: async ({ accountId, senderId, gatewayUrl, serverPath }) => {
16351
16456
  const userId = resolveAdminUserId({ accountId, senderPhone: senderId }) ?? void 0;
16352
16457
  console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
16353
16458
  const result = await managerRcSpawn({
16354
16459
  sessionId: adminSessionIdFor(accountId, senderId),
16355
16460
  userId,
16461
+ // Task 724 — supply the channel so /rc-spawn writes the classification
16462
+ // sidecar from the body, the same path every other admin channel uses.
16463
+ // senderId for the sidecar continues to come from the waChannel binding.
16464
+ role: "admin",
16465
+ channel: "whatsapp",
16356
16466
  waChannel: { senderId, gatewayUrl, serverPath },
16357
16467
  logContext: `channel=whatsapp-native senderId=${senderId}`
16358
16468
  });
@@ -16361,10 +16471,10 @@ var waGateway = new WaGateway({
16361
16471
  }
16362
16472
  }
16363
16473
  });
16364
- app45.route("/", waGateway.routes());
16474
+ app47.route("/", waGateway.routes());
16365
16475
  waGateway.startSweeper();
16366
- app45.use("*", clientIpMiddleware);
16367
- app45.use("*", async (c, next) => {
16476
+ app47.use("*", clientIpMiddleware);
16477
+ app47.use("*", async (c, next) => {
16368
16478
  await next();
16369
16479
  c.header("X-Content-Type-Options", "nosniff");
16370
16480
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
@@ -16374,7 +16484,7 @@ app45.use("*", async (c, next) => {
16374
16484
  );
16375
16485
  });
16376
16486
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
16377
- app45.use("*", async (c, next) => {
16487
+ app47.use("*", async (c, next) => {
16378
16488
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
16379
16489
  await next();
16380
16490
  return;
@@ -16392,7 +16502,7 @@ app45.use("*", async (c, next) => {
16392
16502
  });
16393
16503
  }
16394
16504
  });
16395
- app45.use("*", async (c, next) => {
16505
+ app47.use("*", async (c, next) => {
16396
16506
  const host = (c.req.header("host") ?? "").split(":")[0];
16397
16507
  if (!isPublicHost(host)) {
16398
16508
  await next();
@@ -16423,7 +16533,7 @@ function resolveRemoteAuthOpts() {
16423
16533
  return brandLoginOpts;
16424
16534
  }
16425
16535
  var MAX_LOGIN_BODY = 8 * 1024;
16426
- app45.post("/__remote-auth/login", async (c) => {
16536
+ app47.post("/__remote-auth/login", async (c) => {
16427
16537
  const client = clientFrom(c);
16428
16538
  const clientIp = client.ip || "unknown";
16429
16539
  if (!requestIsTlsTerminated(c)) {
@@ -16468,7 +16578,7 @@ app45.post("/__remote-auth/login", async (c) => {
16468
16578
  }
16469
16579
  });
16470
16580
  });
16471
- app45.get("/__remote-auth/logout", (c) => {
16581
+ app47.get("/__remote-auth/logout", (c) => {
16472
16582
  const client = clientFrom(c);
16473
16583
  const clientIp = client.ip || "unknown";
16474
16584
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -16481,7 +16591,7 @@ app45.get("/__remote-auth/logout", (c) => {
16481
16591
  }
16482
16592
  });
16483
16593
  });
16484
- app45.post("/__remote-auth/change-password", async (c) => {
16594
+ app47.post("/__remote-auth/change-password", async (c) => {
16485
16595
  const client = clientFrom(c);
16486
16596
  const clientIp = client.ip || "unknown";
16487
16597
  const rateLimited = checkRateLimit(client);
@@ -16532,13 +16642,13 @@ app45.post("/__remote-auth/change-password", async (c) => {
16532
16642
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
16533
16643
  }
16534
16644
  });
16535
- app45.get("/__remote-auth/setup", (c) => {
16645
+ app47.get("/__remote-auth/setup", (c) => {
16536
16646
  if (isRemoteAuthConfigured()) {
16537
16647
  return c.redirect("/");
16538
16648
  }
16539
16649
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
16540
16650
  });
16541
- app45.post("/__remote-auth/set-initial-password", async (c) => {
16651
+ app47.post("/__remote-auth/set-initial-password", async (c) => {
16542
16652
  if (isRemoteAuthConfigured()) {
16543
16653
  return c.redirect("/");
16544
16654
  }
@@ -16576,10 +16686,10 @@ app45.post("/__remote-auth/set-initial-password", async (c) => {
16576
16686
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
16577
16687
  }
16578
16688
  });
16579
- app45.get("/api/remote-auth/status", (c) => {
16689
+ app47.get("/api/remote-auth/status", (c) => {
16580
16690
  return c.json({ configured: isRemoteAuthConfigured() });
16581
16691
  });
16582
- app45.post("/api/remote-auth/set-password", async (c) => {
16692
+ app47.post("/api/remote-auth/set-password", async (c) => {
16583
16693
  let body;
16584
16694
  try {
16585
16695
  body = await c.req.json();
@@ -16610,9 +16720,9 @@ app45.post("/api/remote-auth/set-password", async (c) => {
16610
16720
  return c.json({ error: "Failed to save password" }, 500);
16611
16721
  }
16612
16722
  });
16613
- app45.route("/api/_client-error", client_error_default);
16723
+ app47.route("/api/_client-error", client_error_default);
16614
16724
  console.log("[client-error-route] mounted");
16615
- app45.use("*", async (c, next) => {
16725
+ app47.use("*", async (c, next) => {
16616
16726
  const host = (c.req.header("host") ?? "").split(":")[0];
16617
16727
  const path2 = c.req.path;
16618
16728
  if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
@@ -16645,14 +16755,15 @@ app45.use("*", async (c, next) => {
16645
16755
  console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
16646
16756
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
16647
16757
  });
16648
- app45.route("/api/health", health_default);
16649
- app45.route("/api/chat", chat_default);
16650
- app45.route("/api/whatsapp", whatsapp_default);
16651
- app45.route("/api/whatsapp-reader", whatsapp_reader_default);
16652
- app45.route("/api/onboarding", onboarding_default);
16653
- app45.route("/api/admin", admin_default);
16654
- app45.route("/api/access", access_default);
16655
- app45.route("/api/session", session_default2);
16758
+ app47.route("/api/health", health_default);
16759
+ app47.route("/api/chat", chat_default);
16760
+ app47.route("/api/whatsapp", whatsapp_default);
16761
+ app47.route("/api/whatsapp-reader", whatsapp_reader_default);
16762
+ app47.route("/api/telegram", telegram_default);
16763
+ app47.route("/api/onboarding", onboarding_default);
16764
+ app47.route("/api/admin", admin_default);
16765
+ app47.route("/api/access", access_default);
16766
+ app47.route("/api/session", session_default2);
16656
16767
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
16657
16768
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
16658
16769
  var IMAGE_MIME = {
@@ -16664,7 +16775,7 @@ var IMAGE_MIME = {
16664
16775
  ".svg": "image/svg+xml",
16665
16776
  ".ico": "image/x-icon"
16666
16777
  };
16667
- app45.get("/agent-assets/:slug/:filename", (c) => {
16778
+ app47.get("/agent-assets/:slug/:filename", (c) => {
16668
16779
  const slug = c.req.param("slug");
16669
16780
  const filename = c.req.param("filename");
16670
16781
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -16680,26 +16791,26 @@ app45.get("/agent-assets/:slug/:filename", (c) => {
16680
16791
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
16681
16792
  return c.text("Not found", 404);
16682
16793
  }
16683
- const filePath = resolve26(account.accountDir, "agents", slug, "assets", filename);
16684
- const expectedDir = resolve26(account.accountDir, "agents", slug, "assets");
16794
+ const filePath = resolve27(account.accountDir, "agents", slug, "assets", filename);
16795
+ const expectedDir = resolve27(account.accountDir, "agents", slug, "assets");
16685
16796
  if (!filePath.startsWith(expectedDir + "/")) {
16686
16797
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
16687
16798
  return c.text("Forbidden", 403);
16688
16799
  }
16689
- if (!existsSync23(filePath)) {
16800
+ if (!existsSync24(filePath)) {
16690
16801
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
16691
16802
  return c.text("Not found", 404);
16692
16803
  }
16693
16804
  const ext = "." + filename.split(".").pop()?.toLowerCase();
16694
16805
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
16695
16806
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
16696
- const body = readFileSync23(filePath);
16807
+ const body = readFileSync24(filePath);
16697
16808
  return c.body(body, 200, {
16698
16809
  "Content-Type": contentType,
16699
16810
  "Cache-Control": "public, max-age=3600"
16700
16811
  });
16701
16812
  });
16702
- app45.get("/generated/:filename", (c) => {
16813
+ app47.get("/generated/:filename", (c) => {
16703
16814
  const filename = c.req.param("filename");
16704
16815
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
16705
16816
  console.error(`[generated] serve file=${filename} status=403`);
@@ -16710,35 +16821,35 @@ app45.get("/generated/:filename", (c) => {
16710
16821
  console.error(`[generated] serve file=${filename} status=404`);
16711
16822
  return c.text("Not found", 404);
16712
16823
  }
16713
- const filePath = resolve26(account.accountDir, "generated", filename);
16714
- const expectedDir = resolve26(account.accountDir, "generated");
16824
+ const filePath = resolve27(account.accountDir, "generated", filename);
16825
+ const expectedDir = resolve27(account.accountDir, "generated");
16715
16826
  if (!filePath.startsWith(expectedDir + "/")) {
16716
16827
  console.error(`[generated] serve file=${filename} status=403`);
16717
16828
  return c.text("Forbidden", 403);
16718
16829
  }
16719
- if (!existsSync23(filePath)) {
16830
+ if (!existsSync24(filePath)) {
16720
16831
  console.error(`[generated] serve file=${filename} status=404`);
16721
16832
  return c.text("Not found", 404);
16722
16833
  }
16723
16834
  const ext = "." + filename.split(".").pop()?.toLowerCase();
16724
16835
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
16725
16836
  console.log(`[generated] serve file=${filename} status=200`);
16726
- const body = readFileSync23(filePath);
16837
+ const body = readFileSync24(filePath);
16727
16838
  return c.body(body, 200, {
16728
16839
  "Content-Type": contentType,
16729
16840
  "Cache-Control": "public, max-age=86400"
16730
16841
  });
16731
16842
  });
16732
- app45.route("/sites", sites_default);
16733
- app45.route("/listings", listings_default);
16734
- app45.route("/v", visitor_event_default);
16735
- app45.route("/v", visitor_consent_default);
16843
+ app47.route("/sites", sites_default);
16844
+ app47.route("/listings", listings_default);
16845
+ app47.route("/v", visitor_event_default);
16846
+ app47.route("/v", visitor_consent_default);
16736
16847
  var htmlCache = /* @__PURE__ */ new Map();
16737
16848
  var brandLogoPath = "/brand/maxy-monochrome.png";
16738
16849
  var brandIconPath = "/brand/maxy-monochrome.png";
16739
- if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
16850
+ if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
16740
16851
  try {
16741
- const fullBrand = JSON.parse(readFileSync23(BRAND_JSON_PATH, "utf-8"));
16852
+ const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
16742
16853
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
16743
16854
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
16744
16855
  } catch {
@@ -16755,9 +16866,9 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
16755
16866
  function readInstalledVersion() {
16756
16867
  try {
16757
16868
  if (!PLATFORM_ROOT9) return "unknown";
16758
- const versionFile = join18(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
16759
- if (!existsSync23(versionFile)) return "unknown";
16760
- const content = readFileSync23(versionFile, "utf-8").trim();
16869
+ const versionFile = join19(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
16870
+ if (!existsSync24(versionFile)) return "unknown";
16871
+ const content = readFileSync24(versionFile, "utf-8").trim();
16761
16872
  return content || "unknown";
16762
16873
  } catch {
16763
16874
  return "unknown";
@@ -16798,7 +16909,7 @@ var clientErrorReporterScript = `<script>
16798
16909
  function cachedHtml(file) {
16799
16910
  let html = htmlCache.get(file);
16800
16911
  if (!html) {
16801
- html = readFileSync23(resolve26(process.cwd(), "public", file), "utf-8");
16912
+ html = readFileSync24(resolve27(process.cwd(), "public", file), "utf-8");
16802
16913
  const productNameEsc = escapeHtml(BRAND.productName);
16803
16914
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
16804
16915
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -16814,13 +16925,13 @@ ${clientErrorReporterScript}
16814
16925
  }
16815
16926
  var brandedHtmlCache = /* @__PURE__ */ new Map();
16816
16927
  function loadBrandingCache(agentSlug) {
16817
- const configDir2 = join18(homedir2(), BRAND.configDir);
16928
+ const configDir2 = join19(homedir3(), BRAND.configDir);
16818
16929
  try {
16819
16930
  const accountId = getDefaultAccountId();
16820
16931
  if (!accountId) return null;
16821
- const cachePath = join18(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
16822
- if (!existsSync23(cachePath)) return null;
16823
- return JSON.parse(readFileSync23(cachePath, "utf-8"));
16932
+ const cachePath = join19(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
16933
+ if (!existsSync24(cachePath)) return null;
16934
+ return JSON.parse(readFileSync24(cachePath, "utf-8"));
16824
16935
  } catch {
16825
16936
  return null;
16826
16937
  }
@@ -16863,7 +16974,7 @@ function escapeHtml(s) {
16863
16974
  function agentUnavailableHtml() {
16864
16975
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(BRAND.productName)}</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1.5rem;color:#222"><h1 style="font-size:1.25rem">Agent unavailable</h1><p>This agent isn't available right now. If you reached this page from a saved link, the agent may have been turned off.</p></body></html>`;
16865
16976
  }
16866
- app45.get("/", (c) => {
16977
+ app47.get("/", (c) => {
16867
16978
  const host = (c.req.header("host") ?? "").split(":")[0];
16868
16979
  if (isPublicHost(host)) {
16869
16980
  const defaultSlug = resolveDefaultSlug();
@@ -16879,12 +16990,12 @@ app45.get("/", (c) => {
16879
16990
  }
16880
16991
  return c.html(cachedHtml("index.html"));
16881
16992
  });
16882
- app45.get("/public", (c) => {
16993
+ app47.get("/public", (c) => {
16883
16994
  const host = (c.req.header("host") ?? "").split(":")[0];
16884
16995
  if (isPublicHost(host)) return c.text("Not found", 404);
16885
16996
  return c.html(cachedHtml("public.html"));
16886
16997
  });
16887
- app45.get("/chat", (c) => {
16998
+ app47.get("/chat", (c) => {
16888
16999
  const host = (c.req.header("host") ?? "").split(":")[0];
16889
17000
  if (isPublicHost(host)) return c.text("Not found", 404);
16890
17001
  return c.html(cachedHtml("public.html"));
@@ -16903,12 +17014,12 @@ async function logViewerFetch(c, next) {
16903
17014
  duration_ms: Date.now() - start
16904
17015
  });
16905
17016
  }
16906
- app45.use("/vnc-viewer.html", logViewerFetch);
16907
- app45.use("/vnc-popout.html", logViewerFetch);
16908
- app45.get("/vnc-popout.html", (c) => {
17017
+ app47.use("/vnc-viewer.html", logViewerFetch);
17018
+ app47.use("/vnc-popout.html", logViewerFetch);
17019
+ app47.get("/vnc-popout.html", (c) => {
16909
17020
  let html = htmlCache.get("vnc-popout.html");
16910
17021
  if (!html) {
16911
- html = readFileSync23(resolve26(process.cwd(), "public", "vnc-popout.html"), "utf-8");
17022
+ html = readFileSync24(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
16912
17023
  const name = escapeHtml(BRAND.productName);
16913
17024
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
16914
17025
  html = html.replace("</head>", ` ${brandScript}
@@ -16918,7 +17029,7 @@ app45.get("/vnc-popout.html", (c) => {
16918
17029
  }
16919
17030
  return c.html(html);
16920
17031
  });
16921
- app45.post("/api/vnc/client-event", async (c) => {
17032
+ app47.post("/api/vnc/client-event", async (c) => {
16922
17033
  let body;
16923
17034
  try {
16924
17035
  body = await c.req.json();
@@ -16939,30 +17050,30 @@ app45.post("/api/vnc/client-event", async (c) => {
16939
17050
  });
16940
17051
  return c.json({ ok: true });
16941
17052
  });
16942
- app45.get("/g/:slug", (c) => {
17053
+ app47.get("/g/:slug", (c) => {
16943
17054
  return c.html(brandedPublicHtml());
16944
17055
  });
16945
- app45.get("/graph", (c) => {
17056
+ app47.get("/graph", (c) => {
16946
17057
  const host = (c.req.header("host") ?? "").split(":")[0];
16947
17058
  if (isPublicHost(host)) return c.text("Not found", 404);
16948
17059
  return c.html(cachedHtml("graph.html"));
16949
17060
  });
16950
- app45.get("/sessions", (c) => {
17061
+ app47.get("/sessions", (c) => {
16951
17062
  const host = (c.req.header("host") ?? "").split(":")[0];
16952
17063
  if (isPublicHost(host)) return c.text("Not found", 404);
16953
17064
  return c.html(cachedHtml("sessions.html"));
16954
17065
  });
16955
- app45.get("/data", (c) => {
17066
+ app47.get("/data", (c) => {
16956
17067
  const host = (c.req.header("host") ?? "").split(":")[0];
16957
17068
  if (isPublicHost(host)) return c.text("Not found", 404);
16958
17069
  return c.html(cachedHtml("data.html"));
16959
17070
  });
16960
- app45.get("/browser", (c) => {
17071
+ app47.get("/browser", (c) => {
16961
17072
  const host = (c.req.header("host") ?? "").split(":")[0];
16962
17073
  if (isPublicHost(host)) return c.text("Not found", 404);
16963
17074
  return c.html(cachedHtml("browser.html"));
16964
17075
  });
16965
- app45.get("/:slug", async (c, next) => {
17076
+ app47.get("/:slug", async (c, next) => {
16966
17077
  const slug = c.req.param("slug");
16967
17078
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
16968
17079
  const account = resolveAccount();
@@ -16977,13 +17088,13 @@ app45.get("/:slug", async (c, next) => {
16977
17088
  await next();
16978
17089
  });
16979
17090
  if (brandFaviconPath !== "/favicon.ico") {
16980
- app45.get("/favicon.ico", (c) => {
17091
+ app47.get("/favicon.ico", (c) => {
16981
17092
  c.header("Cache-Control", "public, max-age=300");
16982
17093
  return c.redirect(brandFaviconPath, 302);
16983
17094
  });
16984
17095
  }
16985
- app45.use("/*", serveStatic({ root: "./public" }));
16986
- app45.all("*", (c) => {
17096
+ app47.use("/*", serveStatic({ root: "./public" }));
17097
+ app47.all("*", (c) => {
16987
17098
  const host = (c.req.header("host") ?? "").split(":")[0];
16988
17099
  const path2 = c.req.path;
16989
17100
  if (isPublicHost(host)) {
@@ -16997,7 +17108,7 @@ app45.all("*", (c) => {
16997
17108
  });
16998
17109
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
16999
17110
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
17000
- var httpServer = serve({ fetch: app45.fetch, port, hostname });
17111
+ var httpServer = serve({ fetch: app47.fetch, port, hostname });
17001
17112
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
17002
17113
  startAuthHealthHeartbeat();
17003
17114
  {
@@ -17020,6 +17131,7 @@ var SUBAPP_MANIFEST = [
17020
17131
  { prefix: "/api/chat", file: "server/routes/chat.ts", subapp: chat_default },
17021
17132
  { prefix: "/api/whatsapp", file: "server/routes/whatsapp.ts", subapp: whatsapp_default },
17022
17133
  { prefix: "/api/whatsapp-reader", file: "server/routes/whatsapp-reader.ts", subapp: whatsapp_reader_default },
17134
+ { prefix: "/api/telegram", file: "server/routes/telegram.ts", subapp: telegram_default },
17023
17135
  { prefix: "/api/onboarding", file: "server/routes/onboarding.ts", subapp: onboarding_default },
17024
17136
  { prefix: "/api/admin", file: "server/routes/admin/index.ts", subapp: admin_default },
17025
17137
  { prefix: "/api/access", file: "server/routes/access/index.ts", subapp: access_default },
@@ -17035,7 +17147,7 @@ for (const m of SUBAPP_MANIFEST) {
17035
17147
  }
17036
17148
  try {
17037
17149
  const registered = [];
17038
- for (const r of app45.routes ?? []) {
17150
+ for (const r of app47.routes ?? []) {
17039
17151
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
17040
17152
  if (AGENT_SLUG_PATTERN.test(r.path)) {
17041
17153
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
@@ -17063,11 +17175,11 @@ try {
17063
17175
  var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
17064
17176
  async function runAdminUserReconcileTick() {
17065
17177
  try {
17066
- if (!existsSync23(USERS_FILE)) {
17178
+ if (!existsSync24(USERS_FILE)) {
17067
17179
  console.error("[adminuser-self-heal] skip reason=no-users-file");
17068
17180
  return;
17069
17181
  }
17070
- const usersRaw = readFileSync23(USERS_FILE, "utf-8").trim();
17182
+ const usersRaw = readFileSync24(USERS_FILE, "utf-8").trim();
17071
17183
  if (!usersRaw) {
17072
17184
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
17073
17185
  return;
@@ -17117,6 +17229,9 @@ try {
17117
17229
  }
17118
17230
  var configDirForWhatsApp = basename7(MAXY_DIR) || ".maxy";
17119
17231
  var bootAccount = resolveAccount();
17232
+ if (bootAccount) {
17233
+ migrateRemovedConfigKeys(bootAccount.accountDir);
17234
+ }
17120
17235
  var bootAccountConfig = bootAccount?.config;
17121
17236
  var bootPublicAgent = bootAccount ? resolvePublicAgent(bootAccount.accountDir, { accountId: bootAccount.accountId })?.slug ?? null : null;
17122
17237
  var bootEntitlement = bootAccountConfig ? resolveEntitlement(
@@ -17174,7 +17289,7 @@ if (bootAccountConfig?.whatsapp) {
17174
17289
  }
17175
17290
  init({
17176
17291
  configDir: configDirForWhatsApp,
17177
- platformRoot: resolve26(process.env.MAXY_PLATFORM_ROOT ?? join18(__dirname, "..")),
17292
+ platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join19(__dirname, "..")),
17178
17293
  accountConfig: bootAccountConfig,
17179
17294
  onMessage: async (msg) => {
17180
17295
  if (msg.isOwnerMirror) {